Skip to content

feat(spec+approvals+lint): approver value bindings, retire queue authoring (#3508) - #3536

Merged
os-zhuang merged 3 commits into
mainfrom
claude/approval-handler-value-lookup-xytdsd
Jul 27, 2026
Merged

feat(spec+approvals+lint): approver value bindings, retire queue authoring (#3508)#3536
os-zhuang merged 3 commits into
mainfrom
claude/approval-handler-value-lookup-xytdsd

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

框架侧落地 #3508(设计器侧改动在 objectui 同名分支 PR)。

问题

流程设计器里审批节点 Approvers → Value 退化为纯手填:控件把 user/team/department 等接到了元数据注册表端点(GET /api/v1/meta/:type),而这些处理人是数据记录(sys_user/sys_team/sys_business_unit),注册表里列不出来,候选恒空。另外 queue 类型在引擎里没有解析分支(resolveApproverSpec 无 queue 分支,落到死值 queue:<id>),是"已声明未实现"。

改动

spec(packages/spec/src/automation/approval.zod.ts)——把「注册表 vs 数据记录」的分流规则写进契约:

  • 新增导出 APPROVER_VALUE_BINDINGS:逐类型声明 Value 的数据来源与存值 —— usersys_user(id)、teamsys_team(id)、departmentsys_business_unit(id,不是 sys_department)、positionsys_position(存机器名 name,与引擎 sys_user_position.position 按名路由一致、跨环境可移植)、org_membership_level→闭合枚举、manager→运行时自动解析、field→触发对象字段、queue→unsupported。satisfies 保证与 ApproverType 枚举完备对齐(新增枚举成员不声明绑定即编译错)。
  • 新增 NON_AUTHORABLE_APPROVER_TYPES(= 弃用拼写 + queue),经 xEnumDeprecated 发布 —— 设计器下拉不再提供 queue,存量行仍解析、仍渲染(与 role 同机制,弃用窗口内不破坏)。plugin-sharing 早已用同样方式退役其未实现的 queue recipient,平台口径一致。
  • Value 的 xRef.mapmanager: 'manager',设计器可渲染「自动解析」态。

plugin-approvals(approval-service.ts):

  • 存量 queue 审批人在解析时落死值前输出 logger.warn,静默死槽至少对运维可见。

lint(validate-approval-approvers.ts):

为什么 queue 选弃用而不是补实现

正确的 queue-as-approver 语义是「组内任一成员认领后处理」(参照 Salesforce queue approver / ServiceNow group approval),需要 queue 实体 + 成员表 + claim 动作 + 审批槽位改造,且应与 sharing 的 queue recipient 一起设计成平台级 ownership-queue,不宜在审批里单独造一个。在那之前提供该选项就是宣传引擎不兑现的能力。

测试

  • packages/spec 全量 258 文件 / 6885 用例通过(含新增:bindings 完备性、逐绑定与引擎语义对齐、xEnumDeprecated 含 role+queue、xRef map 含 manager/queue)。
  • plugin-approvals 136 用例通过(含新增:queue spec 落死值 + warn)。
  • packages/lint 24 文件 / 327 用例通过(含新增 unsupported 规则用例;原「queue 静默通过」用例按新行为更新)。

验收对照(issue #3508)

  • B:queue 决策 —— 采用「从可授权枚举移除 + spec 标注 + 引擎告警 + lint 门禁」,存量兼容;
  • 各类型存值语义以引擎为准写入 spec 常量并测试锁定;
  • A:设计器记录 lookup —— objectui 同名分支 PR。

Closes 无(保持 #3508 打开直至 objectui 侧合并)。

🤖 Generated with Claude Code

https://claude.ai/code/session_01GzRYYvxLHqzrqG2EceDKFm


Generated by Claude Code

…oring (#3508)
The designer's approver Value cell silently degraded to free text because it
sourced user/team/department candidates from the metadata registry
(GET /api/v1/meta/:type), which lists no records. Declare the contract once in
the spec so every designer sources it right:
- spec: export APPROVER_VALUE_BINDINGS (per-type value sourcing — record
lookup objects + committed field, closed enum, auto, trigger-field,
unsupported), ORG_MEMBERSHIP_LEVELS, and NON_AUTHORABLE_APPROVER_TYPES;
publish 'queue' in xEnumDeprecated (declared-but-unenforced: the engine has
no queue branch, the slot resolves to nobody) and map 'manager' in the value
xRef so designers can render its auto-resolved state.
- plugin-approvals: warn when a stored queue approver is skipped at
resolution time instead of dying silently into the 'queue:<id>' literal.
- lint: new approval-approver-type-unsupported warning, data-driven from
APPROVER_VALUE_BINDINGS, so authoring a queue approver is called out at
publish time (declared != enforced, Prime Directive #10).
Queue still parses — stored flows keep loading and rendering for the
deprecation window. Designer-side record lookups land in objectui.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GzRYYvxLHqzrqG2EceDKFm
@vercel

vercelBot commented Jul 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
objectstackReadyReadyPreview, CommentJul 27, 2026 5:18am
specBuildingBuildingPreview, CommentJul 27, 2026 5:18am

Request Review

# Conflicts:
#	packages/spec/src/automation/approval.zod.ts
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling and removed size/m labels Jul 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/lint, @objectstack/plugin-approvals, @objectstack/spec.

104 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx(via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx(via @objectstack/spec)
  • content/docs/ai/skills.mdx(via @objectstack/spec)
  • content/docs/api/client-sdk.mdx(via @objectstack/spec)
  • content/docs/api/environment-routing.mdx(via @objectstack/spec)
  • content/docs/api/error-catalog.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx(via @objectstack/spec)
  • content/docs/api/index.mdx(via @objectstack/spec)
  • content/docs/automation/approvals.mdx(via @objectstack/plugin-approvals, packages/spec)
  • content/docs/automation/flows.mdx(via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx(via packages/spec)
  • content/docs/automation/hooks.mdx(via @objectstack/spec)
  • content/docs/automation/index.mdx(via @objectstack/spec)
  • content/docs/automation/webhooks.mdx(via @objectstack/spec)
  • content/docs/automation/workflows.mdx(via @objectstack/spec)
  • content/docs/concepts/architecture.mdx(via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx(via packages/spec)
  • content/docs/concepts/index.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx(via packages/spec)
  • content/docs/concepts/north-star.mdx(via packages/spec)
  • content/docs/data-modeling/analytics.mdx(via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx(via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx(via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx(via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx(via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx(via @objectstack/spec)
  • content/docs/data-modeling/index.mdx(via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx(via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx(via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx(via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx(via @objectstack/spec)
  • content/docs/deployment/cli.mdx(via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx(via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx(via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx(via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx(via @objectstack/spec)
  • content/docs/getting-started/examples.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx(via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx(via @objectstack/spec)
  • content/docs/kernel/cluster.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx(via packages/spec)
  • content/docs/kernel/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx(via packages/spec)
  • content/docs/kernel/services-checklist.mdx(via @objectstack/spec)
  • content/docs/permissions/authorization.mdx(via @objectstack/lint, @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx(via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx(via @objectstack/spec)
  • content/docs/permissions/positions.mdx(via @objectstack/spec)
  • content/docs/permissions/rls.mdx(via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx(via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx(via @objectstack/spec)
  • content/docs/plugins/development.mdx(via @objectstack/spec)
  • content/docs/plugins/index.mdx(via @objectstack/spec)
  • content/docs/plugins/packages.mdx(via @objectstack/plugin-approvals, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx(via @objectstack/spec)
  • content/docs/protocol/diagram.mdx(via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx(via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx(via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx(via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx(via @objectstack/plugin-approvals, @objectstack/spec)
  • content/docs/releases/index.mdx(via @objectstack/spec)
  • content/docs/releases/v12.mdx(via @objectstack/spec)
  • content/docs/releases/v13.mdx(via @objectstack/spec)
  • content/docs/releases/v16.mdx(via @objectstack/spec)
  • content/docs/releases/v9.mdx(via @objectstack/plugin-approvals, @objectstack/spec)
  • content/docs/ui/actions.mdx(via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx(via @objectstack/spec)
  • content/docs/ui/dashboards.mdx(via @objectstack/spec)
  • content/docs/ui/forms.mdx(via @objectstack/spec)
  • content/docs/ui/index.mdx(via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx(via @objectstack/spec)
  • content/docs/ui/setup-app.mdx(via @objectstack/spec)
  • content/docs/ui/translations.mdx(via @objectstack/spec)
  • content/docs/ui/views.mdx(via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

…ed (#3508)
The approver `value` description no longer advertises queue; regenerate the
generated reference page to match, and say plainly in the approvals guide that
a queue approver parses but resolves to nobody.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GzRYYvxLHqzrqG2EceDKFm
@os-zhuang
os-zhuang marked this pull request as ready for review July 27, 2026 06:22
@os-zhuang
os-zhuang merged commit 474fe39 into mainJul 27, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/approval-handler-value-lookup-xytdsd branch July 27, 2026 06:22
os-zhuang added a commit that referenced this pull request Jul 30, 2026
… approvers (#3405, #3508) (#3970)
* test(showcase): specimens for the inline picker and the record-backed approvers (#3405, #3508)
Both issues shipped their platform fix but left their last acceptance item on a
real backend, and showcase had no specimen for either shape.
**#3405 — `p_assignee`, an inline `lookup` at `sys_user`.** The gallery already
covered an inline picker aimed at one of the app's own objects
(`p_account` → `showcase_account`). The reported bug (PLAT-DEF-005: "assign an
inspector" handed the supervisor a box wanting a pasted UUID) was a picker at a
SYSTEM object, and a person is the reference a human is least able to identify
by id — `sys_user` ids here are opaque 32-char tokens. That earns its own
specimen rather than riding on the account one.
**#3508 — `showcase_approver_bindings`, one approval node per record-backed
approver kind.** The existing flows cover `position` heavily and
`dynamic-approval` covers `org_membership_level` + `expression`; `user`, `team`,
`department`, `manager` and `field` had no specimen at all, which is why the
designer's Value control could regress unnoticed. `queue` is deliberately
absent — #3536 made it non-authorable.
The flow is `draft` on purpose, not by omission: the record-backed kinds point
at rows a fresh boot does not have (`sys_team` is empty, and unit membership /
position assignment are runtime admin actions by design — see the seed notes).
An `active` flow over them would resolve to nobody, which is the exact silent
dead slot #3508 exists to remove; shipping that as a demo would re-teach the bug.
`draft` keeps it out of the runtime while still loading in the designer, which
is the surface under test.
Verified on a real backend (showcase :5411 + objectui HEAD console :5412, own DB):
- the server publishes `reference` for both inline picker params;
- the param dialog renders Assignee as a searchable picker — typing "Dev" issues
`GET /api/v1/data/sys_user?top=50&search=Dev` and offers "Dev Admin
/ admin@objectos.ai", with no degrade warning logged;
- the designer renders a record picker per kind off the published
`xRef.sources`, listing the real seeded org tree for `department`
(`sys_business_unit`), `manager` as a disabled "Resolved automatically" cell,
`field` as the trigger-object field selector, and no `queue` in the Type
dropdown. No `/api/v1/meta/:type` registry calls — the old broken path.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LkvDs4y4gveyB5NJZKxaZt
* chore: add an empty changeset for the showcase specimens (#3405, #3508)
The "Check Changeset" gate requires one on every PR, not only on PRs that touch
a published package — and it sanctions an empty changeset for a change that
releases nothing, which this is: both specimens live in
`@objectstack/example-showcase`, which is `private: true`, so changesets would
skip it regardless. Matches the existing empty-changeset entries
(`adr-0104-design-doc-only.md` and friends).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LkvDs4y4gveyB5NJZKxaZt
* i18n(showcase): translate the new inline picker param (#3405)
`check-i18n-coverage` ratchets each example at its current untranslated count,
so a newly declared label that skips a locale the example claims to support
pushes the count up and fails the gate — 456 → 460 here (`p_assignee`'s `label`
and `helpText`, each counted on two surfaces).
Translated rather than ratcheting the baseline up: ratcheting is what the gate
exists to prevent, and the script only sanctions `--update` for a count that
went DOWN. The gallery's older params sit inside the 456 baseline; this one is
not allowed to widen it.
`负责人` is what the bundle already renders for `showcase_task.assignee`, so the
same idea keeps one word. The flow specimen's own label/description are not
flagged — the `i18n/missing-action` rule covers action params, not flow nodes.
`check-i18n-coverage` now reports "none new"; validate, typecheck and the 58
example tests all pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LkvDs4y4gveyB5NJZKxaZt
---------
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-zhuang@claude