feat(spec)+fix(approvals): approver value sources on the wire, author-facing type order, no more silent dead slots (#3508 / #3807) - #3817
Merged
Conversation
…type enum for authors, surface dead approver slots (#3508 / #3807) Four follow-ups from browser-verifying the #3508 approver work end to end. 1. `APPROVER_VALUE_SOURCES` (spec) — `xRef.map` named a picker KIND but never where its rows come from, so the designer kept its own copy of the data contract and the first copy pointed every directory kind at the metadata REGISTRY, which cannot list `sys_user` / `sys_team` / `sys_business_unit` / `sys_position` rows (#3508). The binding now ships on the published JSON schema as `xRef.sources`, derived from `APPROVER_VALUE_BINDINGS` so the two cannot drift and a new `ApproverType` with no declared source stays a compile error. Presentation stays a renderer decision. 2. `ApproverType` order (spec) — the Studio picker derives from this enum via the published schema, not from objectui's own options array, so the indirect-bindings-first order objectui#2834 argued for never took effect. The enum now carries it: manager, position, department, team, field, expression, org_membership_level, user. Deprecated `role`/`queue` still parse and stay out of every picker via `xEnumDeprecated`. 3. Dead approver slots (plugin-approvals) — `queue` already warned; every other graph type (`team`, `department`, `position`, `org_membership_level`, `manager`) fell back to the same unactionable `type:value` literal in total silence. That is what let #3807 hide: an empty slate, no log line, and a permanently stuck approval as the first symptom (#3424). The fallback stays; it now names the type, value and organization that produced it. `user` and `field` stay quiet — they never had an "expanded to nobody" state. 4. plugin-sharing's identical org scope (tests only) — `BusinessUnitGraphService` .orgScope carries the same strict equality #3807 fixed in approvals. It is unreachable today (every materialized `sys_sharing_rule` is null-org, so the filter is skipped) and it is an authorization path, so it is pinned by tests rather than widened blind: the reachable paths and the divergence itself are now executable facts. Tests: spec 6742 pass (+6 new: sources exhaustive + on the wire, enum order, non-authorable spellings); plugin-approvals 258 pass (+6: each graph type warns with type/value/org, resolvable expansions and `user` stay quiet); plugin-sharing 107 pass (+6 new BU-graph cases); lint 471 pass; repo lint clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BVRVgSvmyCwmDfTmKmCZoH
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 3 package(s): 104 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…prover changes `check:docs` and `check:api-surface` are both generated-artifact gates: - `content/docs/references/automation/approval.mdx` — the `ApproverType` table and its Allowed Values list follow the enum's declaration order, which this branch reordered. Also reflowed the enum's own TSDoc: the doc generator emits one paragraph per source line, so the long ordering rationale now lives in `//` comments above the block and the JSDoc keeps a two-line summary. - `packages/spec/api-surface.json` — picks up the new `APPROVER_VALUE_SOURCES` export. NOTE: regenerating the API surface also absorbs drift that PREDATES this branch — `MEMBERSHIP_ROLE_NAME_MIN_LENGTH` / `MEMBERSHIP_ROLE_NAME_PATTERN`, `AppTranslationBundle*`, `ObjectTranslationNode*` added, and `LEGACY_OBJECT_FIRST_KEYS` / `LegacyObjectFirstKey` / `TranslationItem*` / `defineTranslation` removed. Those come from already-merged PRs that did not re-run the generator; `check:api-surface` fails on main without any of this branch's changes (verified by stashing them and re-running the gate). The snapshot exists to mirror the real export surface, so it is brought fully in sync rather than hand-patched to only this branch's line. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BVRVgSvmyCwmDfTmKmCZoH
…new export The previous commit regenerated the snapshot against a `dist/` that turbo had restored from another checkout's cache, so it recorded exports this branch's source does not have (`AppTranslationBundle*`, `ObjectTranslationNode*`, `MEMBERSHIP_ROLE_NAME_*`) and dropped ones it does (`defineTranslation`, `TranslationItem*`, `LEGACY_OBJECT_FIRST_KEYS`). The generator reads the BUILT `.d.ts` from the exports map, so a stale dist silently produces a wrong snapshot — and it read as 8 breaking removals, which is exactly the alarm the gate exists to raise. `rm -rf packages/spec/dist && pnpm --filter @objectstack/spec build` first, then regenerate: the snapshot now differs from main by the single line this branch actually adds, `APPROVER_VALUE_SOURCES (const)`, and `check:api-surface` reports the surface unchanged. Every other spec gate (docs, skill-refs, skill-docs, react-blocks, spec-changes, upgrade-guide) re-verified green off the clean build. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BVRVgSvmyCwmDfTmKmCZoH
os-zhuang
marked this pull request as ready for review
July 28, 2026 07:45
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#3508 真机验收里发现的四条尾巴,一次收掉。框架侧完整;设计器侧(消费本 PR 新增的
xRef.sources)另开 objectui PR。1|
APPROVER_VALUE_SOURCES—— 设计器不必再自己猜候选从哪来(spec)xRef.map只说了渲染哪种 picker('team'),从没说这个 picker 的候选存在哪。于是设计器只能自带一份数据契约副本,而第一版副本恰恰是错的:所有目录类型都接到了GET /api/v1/meta/:type(元数据注册表),而sys_user/sys_team/sys_business_unit/sys_position的记录根本不在注册表里 —— 候选恒空,控件退化成手填,这就是 #3508。现在把绑定投影到发布出去的 JSON schema 上,作为
xRef.sources:它派生自
APPROVER_VALUE_BINDINGS,不是第二份真相:两者不可能漂移,且继承了原有的satisfies完备性 —— 新增ApproverType成员却不声明来源仍然是编译错误。source: 'data'这个词是刻意的,它对应的正是设计器过去打错的那个meta。表现层(显示哪个字段、要不要弹 PeoplePicker、副标题放什么)仍归渲染方自己决定,spec 只出数据契约。
2|
ApproverType的声明顺序就是给作者的推荐(spec)objectui#2834 主张「间接绑定优先、
user垫底」,但把顺序写在了它自己的 options 数组里 —— 而 Studio 的节点检查器根本不读那个数组,它是从发布的 JSON schema 里按本枚举(减去xEnumDeprecated)派生的。所以那个引导从来没生效过,实测下拉仍是User排第一。顺序只有写进枚举本身才算数,现在写进去了:
绑定某个具体的人是作者能做的最不可移植的选择 —— 换环境那个 id 不存在,人一走审批就悄悄发给了不该看的人;
manager/position/department/team两样都扛得住。废弃的role/queue仍可 parse,靠xEnumDeprecated继续不出现在任何 picker 里,放在枚举何处都不影响。3|图展开落空不再是静默的(plugin-approvals)
queue早就有告警(#3508),但其余每一种图类型 ——team/department/position/org_membership_level/manager—— 查不到人时都落到同一个没人能操作的type:value字面量,且一个字都不说。这正是 #3807 能藏这么久的原因:请求照常打开、名单为空、日志无声,第一个症状是一条永远卡住的审批(#3424 就是它的下游形态)。
兜底字面量保留(15.x 的存量槽位和子串匹配的 fixture 依赖它),只是不再隐身:现在会带上 type、value、organizationId 记一条 warn。
user/field保持安静 —— 它们拿到 id 就用,压根没有「展开到空」这个状态。4|plugin-sharing 那份同形代码,用测试钉住(仅测试)
BusinessUnitGraphService.orgScope有与 #3807 完全同形的严格等值。今天不可达(实测所有物化的sys_sharing_rule都是 null-org,过滤器整个被跳过),而它是授权路径 —— 决定谁能看到记录 —— 在没有可复现失败的前提下不该跟着放宽。所以不改行为,改成可执行的事实:新增 6 个用例,既锁住今天真正跑到的路径,也把「与 approvals 的分歧」本身写成一条名为
[divergence]的测试。将来若平台统一认定 null-org = env-wide,那就是对一条有名字的测试做一次有意的修改,而不是一次静默的行为变化。测试
@objectstack/spec@objectstack/plugin-approvalsuser保持安静)@objectstack/plugin-sharing@objectstack/lintpnpm lintpnpm gen:schema已重跑确认xRef.sources与新枚举顺序确实进了产物(packages/spec/json-schema/是 gitignore 的构建产物,故 diff 中不含)。后续(不在本 PR)
objectui 侧消费
xRef.sources替掉手写的KIND_TO_RECORD_LOOKUP镜像常量,并清掉flow-node-config.ts里那份不生效的 options 顺序。🤖 Generated with Claude Code
https://claude.ai/code/session_01BVRVgSvmyCwmDfTmKmCZoH
Generated by Claude Code