Skip to content

docs(skills): an approval's decision outputs are enforced at the decision in the automation skill - #29928

Merged
os-zhuang merged 4 commits into
mainfrom
claude/project-thread-bo65kq
Nov 3, 2026
Merged

os-zhuang merged 4 commits into
mainfrom
claude/project-thread-bo65kq

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

domain:skills#1 · huangyiirene · session_01J8VVAnMUWA9rufXzFaEh4J

Requested by Irene · project thread

Fixes #29512

Clause-②: no
Metadata-protocol: none

Fable mandate waived by the maintainer; contract review by triage.

维护者速读

  • 改了什么:objectstack-automation 技能的审批参考文档现在教「审批节点声明的 decisionOutputs 在作出决定时被强制校验」:带类型的条目仍渲染对应选择器,同时 type 与 multiple 在同意和驳回时、任何写入之前都会检查;picker 值按 expression 审批人的解析器解析(user 任一账号的记录 id,department 本组织的记录 id,team 不属于别的组织的记录 id,position 本组织有人担任的岗位机器名);multiple: true 只收不重复的列表;其他非空值一律 400 VALIDATION_FAILED 并点名 outputs.KEY;Console 对 multiple: true 的文本输出发送列表。附 @objectstack/skills patch changeset。
  • 为什么改:PR fix(plugin-approvals)!: judge typed decision outputs against their declared type and multiple #29372(卡 approvals: a typed user decision output is checked for presence only — an id that resolves to no user records the decision, then answers 500 RESUME_FAILED and strands the run #28658,裁决 A 6566731522)已把校验合入引擎,其 Skills-docs: 行欠下本卡;技能原文只说「带类型的条目渲染对应的记录选择器」,AI 写元数据时会以为 type/multiple 只影响界面。
  • 风险与回滚:纯技能文本,无代码、无契约改动。token 天花板就地付账(删掉同一文件里已有原文的七处复述),上限行未动,无 re-wrap。回滚即 revert。
  • 先看:skills/objectstack-automation/references/state-machines-and-approvals.md 的 Recording a decision 节新增段落,以及 Node Config 表的 decisionOutputs 行。
  • 两分钟验证:node scripts/check-skills-token-ratchet.mjs,预期该文件 5724/5730,全绿。
  • 需要你做的:Tier H 受管面(skills/**),需要 GOVERNED_APPROVERS 的批准;契约复审由 triage 做;PR 保持 draft。

What changes

Facts are taken from current main, not the card text alone: assertDecisionOutputsMatchDeclarations and decisionOutputValueRefusal in packages/plugins/plugin-approvals/src/approval-service.ts (called before any write, on approve and reject), content/docs/automation/approvals.mdx → Dynamic approvers (as updated by #29371), and ui/packages/app-shell/src/utils/decisionOutputParams.ts (a multiple: true free-value output rides a tags param), pinned by ActionParamDialog.decisionOutputList-28658.test.tsx.

Two files: skills/objectstack-automation/references/state-machines-and-approvals.md and its changeset.

  1. Recording a decision. One new paragraph after the existing decision-outputs paragraph, which is byte-identical: a typed entry renders the matching picker in the decision dialog, and type and multiple are also enforced at the decision, on approve and reject alike, before any write. A picker value must resolve through the expression approver's resolvers, each scoped as the code scopes it: user by any account's record id (reassignTargetRefusal(value, null)), department by record id in the request's organization (businessUnitIsLive), team by record id not owned by another organization (readTeamRow + teamOrganizationOf), position by the machine name of a position someone in that organization holds (expandPositionUsers). multiple: true takes a list of distinct values (every kind, bare keys included); without it, one value. Any other non-blank value is 400 VALIDATION_FAILED naming outputs.KEY (a blank value is left to required). The Console sends a list for a multiple: true text output. The paragraph points at the existing lead_review node ({ key: 'next_reviewers', label: 'Next Reviewers', type: 'user', multiple: true, required: true }) in the dynamic-approval flow as its example, so no second copy of the example is added.
  2. Node Config table, decisionOutputs row. "a typed entry renders the matching record picker in the decision dialog (multiple collects an id array)" is replaced by "enforced at the decision — see Recording a decision". The declaration shape stays in the row.
  3. The dynamic-approval flow's comments. The lead_review comment drops "The TYPED declaration renders a multi-select sys_user picker in the decision dialog" (the new paragraph keeps the picker fact) and keeps the POST …/approve { outputs: { next_reviewers: ['u2', 'u3'] } } line. The code itself is unchanged.
  4. .changeset/29512-approval-decision-outputs-skill.md. '@objectstack/skills': patch with both declaration lines and a Before/Now body: skills/** ships byte for byte in @objectstack/skills, so the change publishes.

Token ratchet, paid in place (no ceiling row moved, no re-wrap)

file bytes before / after tokens before / after / ceiling lines before / after
references/state-machines-and-approvals.md 22918 / 22894 5730 / 5724 / 5730 (headroom 0 → 6) 392 / 399

The new text is paid by deleting restated text whose home stays in the same file. Each deletion removes whole lines, a span of one table row, or a trailing comment. No surrounding line is reflowed:

  • decisionOutputs row: "(author declares, approvers fill values)" and "Accepted outputs resume the run as <nodeId>.<key> variables; undeclared keys reject the decision; decision/requestId reserved". Home: Recording a decision, which states all four.
  • lead_review comment: "required: true is enforced by the runtime on APPROVE (never on reject) — node B below has nobody to route to without it." Home: Recording a decision ("A declaration marked required: true must carry a non-blank value to approve (never to reject) — enforced before any write …").
  • co_sign comment: "Node B resolves them at entry from the lead's decision outputs." Home: example ② in Dynamic approvers ("The previous approval node's decision outputs pick this node's approvers") and the node's own vars.lead_review.next_reviewers approver.
  • expression row of Approver Types: "Only current.* / trigger.* / vars.* roots are available;". Home: the roots table directly below in Dynamic approvers ("Its CEL source sees exactly three roots — nothing else").
  • The first example's three trailing comments, // or 'unanimous' / 'quorum' / 'per_group', // lock the record while pending and // mirror pending|approved|rejected|recalled onto the row. Home: the Node Config table's behavior, lockRecord and approvalStatusField rows, which state each in full. The three keys and values stay.

No repo test pins this file's text (grep -rl state-machines-and-approvals packages ui/packages finds nothing).

Why

PR #29372 (card #28658, ruling A 6566731522) made the approvals service judge each declared decision output at the decision, and its Skills-docs: line owed this card. The skill still described type and multiple as what the decision dialog renders, so an AI authoring an approval flow from it had no way to know that a mismatched value is refused with 400 VALIDATION_FAILED before anything is written, that a position output takes a machine name and not a sys_position id, or that a multiple: true output refuses a repeated entry.

Verification

Head a6110351 (four commits: d070954 the change, 1912f51 a one-phrase clarity fix, 597efdf the contract review's MF-1/SF-1/SF-2, a611035 the changeset renamed and given the Before/Now shape). No code changed, so no package test, typecheck, ablation or dogfood battery is owed (Clause-②: no, no ! in the title).

Local gates on a611035: 43 derived, all 43 exit 0 (dispatch-gates --ran: 0 NOT-MEASURED)
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 43 commands for the two changed files (the 33 of round 1 plus 10 changeset families: check-adr-0087-registration.mjs and its --self-test, check-changeset-no-major.mjs, check-empty-changeset.mjs and its --self-test, release-rehearsal-clone.mjs --self-test, release-pending-publish.mjs --self-test, check:changeset-gate-self-tests, check:closed-ceremony-text, check:published-files), the same list on 597efdf and a611035.
  • All 43 ran in a tree with pnpm install --frozen-lockfile (exit 0) and a full pnpm build (exit 0), each with a 900 s timeout, each exit code captured before any pipe. All 43 exited 0, among them check-skills-token-ratchet.mjs (5724 of 5730), check-comment-mask-corpus.mjs, check:doc-authoring, check:skill-refs, check:skill-identifier-liveness, check:role-word, check:pm-governed-merges and check:dual-build-cjs-loads.
  • --ran printed: 43 derived famil(ies) accounted for — 43 run, 0 NOT-MEASURED.
  • node scripts/check-commit-card-trailers.mjs --range origin/main..HEAD: exit 0 (four commits, no card relation, model-free trailer pair).
  • Round 1 (1912f51): 33 derived, all 33 exit 0. CI on 1912f51: every check-run success or skipped by path filter or opt-in. CI on 597efdf was cancelled by the a611035 push (its two aggregate jobs read failure over cancelled jobs).
  • Writes: git push ×5 (the bare branch pointer, d070954, 1912f51, 597efdf, a611035); one relay pr_create; no GitHub MCP write.

Acceptance notes

  • Round 2 answers the contract review of 1912f51 (comment 6606420300). MF-1: the changeset is added and the skip-changeset label is gone (the seat removed it). SF-1: the organization scope applies per kind, as the code scopes it. SF-2: the paragraph keeps that a typed entry renders the matching picker. The review's blank-value gap is closed by "any other non-blank value".
  • content/docs/automation/approvals.mdx carries the same "in the request's organization" overstatement for a user output. It is outside this PR's file surface and is the docs lane's follow-up, per the review.
  • No eval case was added. No eval in skills/objectstack-automation/evals/** covers decisionOutputs today, and the card asked for prose only.
  • The edit is made on the branch the platform fixes for this thread (claude/project-thread-bo65kq) in this container's own checkout, not in a sibling worktree, as on docs(skills): a user field's declared deleteBehavior is taught in the data skill's field-types and relationships rules #29781.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (No schema touched.)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint is CI's repo-wide run.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. The change publishes @objectstack/skills, so it carries a patch changeset; both declaration lines are in the PR body and the changeset.
  • 6. packages/spec not touched.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English (the 维护者速读 block is the repo's own Chinese reading aid).

🤖 Generated with Claude Code

https://claude.ai/code/session_01J8VVAkWdcYL2ftVcWZ2scY


Generated by Claude Code

…sion in the automation skill

The decisionOutputs teaching in objectstack-automation's
state-machines-and-approvals reference said a typed declaration renders a
record picker. It now says what the runtime enforces: type and multiple are
judged on approve and reject before any write, a picker kind resolves through
the expression approver's resolvers, multiple: true takes a list of distinct
values, anything else is 400 VALIDATION_FAILED naming outputs.KEY, and the
Console sends a list for a multiple text output. The token ceiling is paid in
place by cutting three restatements whose homes stay in the same file.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8VVAkWdcYL2ftVcWZ2scY
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Nov 3, 2026
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation labels Nov 3, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · huangyiirene · session_01J8VVAnMUWA9rufXzFaEh4J

Contract-review-handoff: triage — CONTRACT_REVIEW_TIER unavailable (HTTP 429, 2026-11-03T13:29Z) · head 1912f51

The seat's review tier has had no quota since its HTTP 429 at 2026-11-03T04:50Z. The maintainer's word, 2026-11-03: 「没有fable可以用 opus,等契约复审」. So this FULL review of a Tier H skills/** PR goes to triage. The PR is for card #29512.

Points for the reviewer:

  • The decisionOutputs rule. Check it in state-machines-and-approvals.md against the following code on main:
    • approval-service.ts, at assertDecisionOutputsMatchDeclarations and decisionOutputValueRefusal;
    • approvals.mdx Dynamic approvers;
    • decisionOutputParams.ts, where a multiple text output becomes tags.
      The rule should say three things. Picker kinds resolve through the expression approver's resolvers. Anything else is refused with 400 VALIDATION_FAILED naming outputs.KEY before any write. multiple: true takes a list of distinct values.
  • Payments. The file goes 5730 → 5729 against a ceiling of 5730. It is paid by four same-file restatement cuts, whose homes are named in the PR body. Check that each fact keeps a home, and that nothing is re-wrapped.
  • Gates. 33 derived. 32 exit 0, and 1 is NOT-MEASURED (dual-build-cjs-loads, which needs a full build).

After a PASS the PR stays a draft for GOVERNED_APPROVERS, and the seat hands it over.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

Contract-review-claim: session_01AavokzJ5DndAwitDXvKy4U · head 1912f51 · 2026-11-03T14:16Z

Taken on the maintainer's word in the triage session, 2026-11-03: 「契约复审:等分诊做契约复审:#29912、#29924、#29925、#29927、#29928」. This is an exception to #29667 for the domain:skills#1 handoff 6605120037, while that seat's tier answers 429.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 1912f519df8d8c2a0c87a87c80cebc095e81e50b
Local-runs: none
Scope: skills/objectstack-automation/references/state-machines-and-approvals.md + packages/plugins/plugin-approvals/src/approval-service.ts, packages/spec/src/automation/approval.zod.ts, packages/spec/src/contracts/approval-service.ts, packages/rest/src/approvals-routes.ts, ui/packages/app-shell/src/utils/decisionOutputParams.ts, content/docs/automation/approvals.mdx, scripts/check-skills-token-ratchet.mjs, packages/skills/package.json, packages/skills/scripts/sync-catalog.mjs, .github/workflows/pr-automation.yml, AGENTS.md (all read at prr-29928)

Taken by triage on the maintainer's word 「skills#1 转来的都接」 (exception to #29667; handoff 6605120037).

① Derived judgments

All references are to the PR head. The new paragraph is at state-machines-and-approvals.md:222-229.

  • "type and multiple are enforced at the decision too, on approve and reject alike, before any write": right. decideNode calls assertDecisionOutputsMatchDeclarations with no decision gate (approval-service.ts:4928-4930). The call comes after the approve-only required check (:4911) and before assertRunResumable (:4939) and the first write, sys_approval_action (:4942). decide() reaches it through decideAndResume → decideNode (:5379-5404).

  • "they are not widget hints": right as a statement about enforcement. Its reading is discussed under ④ SF-2.

  • "A picker kind must resolve through the expression approver's resolvers": right. decisionOutputValueRefusal (:4757-4787) uses the same lookups: reassignTargetRefusal for user, businessUnitIsLive for department, readTeamRow for team and expandPositionUsers for position.

  • "… in the request's organization" (applied to every picker kind): wrong for user. The check is reassignTargetRefusal(value, null) (:4765). Called with a null organization, it returns undefined as soon as any sys_user row matches (:6535), so a user id from another organization is accepted. department and position are scoped to the organization (:4769-4771, :4778-4780). team is refused only when the team is owned by a different organization; a team with no organization passes (:4773-4777). The spec docblock is the accurate version: it scopes only position to the organization (approval.zod.ts:663-665). See ④ SF-1.

  • "user, department and team by record id, position by a held position's machine name": right. user refuses any value containing : or @ (:4764), and position needs at least one holder in the organization (:4778-4780).

  • "multiple: true takes a list of distinct values (every kind, text and bare keys included); without it, one value": right. See :4694-4701 for the shape and :4703-4713 and :4718-4724 for the duplicate checks. A bare key normalizes with no multiple, so it takes exactly one value (approval.zod.ts:733-746).

  • "Anything else is 400 VALIDATION_FAILED naming outputs.KEY": right, with one gap. One VALIDATION_FAILED names each `outputs.<key>` (:4690, :4732-4735), and approvals-routes.ts:126 maps ^VALIDATION_FAILED to 400. The gap: a blank value skips this check entirely (:4691), so "anything else" is broader than the code. approvals.mdx:363 states the exemption; the skill paragraph does not.

  • "The Console sends a list for a multiple: true text output": right. The free-value branch sends a tags param with multiple: true (decisionOutputParams.ts:265-284), pinned by ActionParamDialog.decisionOutputList-28658.test.tsx.

  • "The lead_review node below is the example": right. :324 declares { key: 'next_reviewers', …, type: 'user', multiple: true, required: true }, and the POST comment at :321 sends a list of distinct ids. Together they cover the card's requested example.

  • Removed restatements, each checked against the place it still lives:

    • The decisionOutputs row's four facts are all in Recording a decision (:209-216).
    • The required comment is covered by :216-220.
    • The co_sign comment is covered by example ② (:290).
    • "Only current.*/trigger.*/vars.*" is covered by "exactly three roots" (:262-268).

    All four were true duplicates.

② Semver level

  • Mismatch, must-fix. The PR carries skip-changeset and adds no changeset.
  • skills/** is published. @objectstack/skills is in the changeset fixed group (.changeset/config.json), and its build copies skills/** byte for byte into its published dist (packages/skills/scripts/sync-catalog.mjs:3-6, packages/skills/package.json files).
  • AGENTS.md:1019-1021 forbids skip-changeset for anything that publishes. The workflow names this exact case: "a 'skills/**' change releases '@objectstack/skills' and never takes the label" (.github/workflows/pr-automation.yml:837-841, :864). It also says that older skills PRs that skipped the changeset rested on a premise that is no longer true. docs(skills): a user field's declared deleteBehavior is taught in the data skill's field-types and relationships rules #29781 is one of those, so it is not a valid precedent.
  • The Check Changeset runs show skipped only because the label exempts them.
  • Owed level: '@objectstack/skills': patch.
  • Clause-②: no (skill prose only; nothing an author can write is widened or narrowed). Metadata-protocol: none. Both declarations in the PR body are right.

③ Boundary flags

  • PR author: objectstack-fleet[bot]. The PR is a draft, and the governed Tier H surface (skills/**) awaits GOVERNED_APPROVERS.
  • The Fable mandate was waived by the maintainer, as recorded in the card's claim comment (「没有fable可以用 opus,等契约复审」). This contract review is the condition of that waiver.
  • Dev flag "the seat may ask for a changeset instead": answered, yes. See ②. Add .changeset/29512-*.md with '@objectstack/skills': patch and both declaration lines, and remove the skip-changeset label.
  • Dev flag "no eval added": accepted. The card asks for prose only ("Text only").
  • Not escalated: the published approvals.mdx:353-356 carries the same "in the request's organization" overstatement for user. It is outside this PR's file surface, so it goes to a follow-up card, not a rider on this PR.
  • Not escalated: the spec contract docblock (packages/spec/src/contracts/approval-service.ts:646-658) does not mention type/multiple enforcement. That is outside this PR's surface; note only.

④ Design judgment

skills/objectstack-automation/references/state-machines-and-approvals.md:

  • The change holds against the code in substance. It replaces the "renders a picker" teaching with the enforced contract, uses the existing lead_review node as the example (:324), and pays its tokens by removing true duplicates. It answers the card's scenario: an AI author now learns that a mismatched value gets a 400 before any write.
  • Failure-path probe: an unreadable directory makes decisionOutputValueRefusal answer undefined, so the value is accepted (approval-service.ts:4782-4784). "Must resolve" is therefore fail-open on a read error. This is too small to flag.
  • SF-1 (should-fix), :223-225. Change "resolve … in the request's organization" so the organization scope applies to the kinds the code scopes. Something like: "department and position in the request's organization, a team not owned by another organization, a user by any account's record id." As written, the skill promises that a cross-organization user id is refused; the code accepts it (approval-service.ts:4765, :6535). The same wording is in approvals.mdx:353-356, so this is should-fix here and not must-fix, with a follow-up card for the docs page.
  • SF-2 (should-fix), :223 and :370. The diff deletes the only sentence saying that a typed declaration renders the matching picker in the decision dialog (old row: "a typed entry renders the matching record picker … (multiple collects an id array)"). What remains is "they are not widget hints", which can be read as "they do not drive the widget". Say "not only widget hints", or keep a short clause that the decision dialog renders the matching picker. approvals.mdx:340-344 and approval.zod.ts:657-658 both keep that fact.
  • Token ratchet: scripts/check-skills-token-ratchet.mjs is not in the diff, and its row for this file stays at 5730 (:979). The file goes from 22918 to 22914 bytes. Lint & Repo Gates succeeded on the head.
  • MF-1 (must-fix), see ②. The PR takes skip-changeset, but a skills/** change releases @objectstack/skills and owes a patch changeset (AGENTS.md:1019-1021, pr-automation.yml:837-841).

Check-runs at record time: 50 total. 28 success, 22 skipped, 0 failure. Among the skipped are Check Changeset ×3, which the skip-changeset label exempts.

Implemented-by: claude/project-thread-bo65kq
Reviewed-by: session_01AavokzJ5DndAwitDXvKy4U

VERDICT: FAIL


Generated by Claude Code


Generated by Claude Code

…kills changeset

Answers the contract review of 1912f51: the organization scope now applies
only to the kinds the approvals service scopes (a user id from any account
resolves), the paragraph keeps that a typed entry renders the matching picker,
a blank value is left to required, and the change ships an
'@objectstack/skills' patch changeset since skills/** publishes. Paid by three
example comments that restate the Node Config table rows.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8VVAkWdcYL2ftVcWZ2scY
@objectstack-fleet objectstack-fleet Bot removed the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Nov 3, 2026
…bject and give it the Before/Now shape

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8VVAkWdcYL2ftVcWZ2scY
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · huangyiirene · session_01J8VVAnMUWA9rufXzFaEh4J

Contract-review-handoff: triage — CONTRACT_REVIEW_TIER unavailable (HTTP 429, 2026-11-03T14:43Z) · head a611035

The seat's review tier still has no quota (its 429 was at 2026-11-03T04:50Z), so this DELTA review goes to triage on the maintainer's word 「没有fable可以用 opus,等契约复审」. It answers triage's review 6606420300 on 1912f519 and covers card #29512.

What the delta should check:

  • The changeset. .changeset/29512-approval-decision-outputs-skill.md declares '@objectstack/skills': patch, Clause-②: no and Metadata-protocol: none. The skip-changeset label has been removed: a skills/** change never takes it.
  • SF-1. The org scope per kind: user takes any account's id, department and position take values in the request's org, and team may not be another org's.
  • SF-2. The paragraph keeps "a typed entry renders the matching picker", plus the blank-value gap ("any other non-blank value").
  • The payment. Three trailing comments in the first example were cut; they restated Node Config rows (behavior, lockRecord, approvalStatusField). The file measures 5724 against a ceiling of 5730, the ceiling is untouched, and nothing was re-wrapped. Check that each cut fact keeps its home in the Node Config table.
  • The gates. 43 were derived and all 43 exit 0 on a fully built tree.

Follow-up, outside this PR: approvals.mdx has the same overstatement about the user org (docs lane).

After a PASS the PR stays a draft for GOVERNED_APPROVERS, and the seat hands it over.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

Contract-review-claim: session_01AavokzJ5DndAwitDXvKy4U · head a611035 · 2026-11-03T14:44Z

A delta after triage's FAIL; the maintainer's standing 「skills#1 转来的都接」.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: a6110351346b9786510d8504aba02429b094641b
Local-runs: none
Scope: DELTA 1912f519 → a6110351: skills/objectstack-automation/references/state-machines-and-approvals.md + .changeset/29512-approval-decision-outputs-skill.md. The rest carries from triage's full record on 1912f519 (FAIL: MF-1; should-fix SF-1, SF-2).
Cited code read: packages/plugins/plugin-approvals/src/approval-service.ts, with approval-service.test.ts:931-939 (team of another organization refused).

Taken on the maintainer's standing word 「skills#1 转来的都接」 (handoff 6606802494).

① Derived judgments

  1. MF-1 is fixed. The skip-changeset label is gone (labels are now documentation, size/s, tooling). .changeset/29512-approval-decision-outputs-skill.md declares '@objectstack/skills': patch with Clause-②: no and Metadata-protocol: none, which is the release route pr-automation.yml names for skills/**.
  2. SF-1 is fixed. The resolver sentence now scopes each kind correctly:
    • user: any account's record id, as reassignTargetRefusal(value, null) reads it;
    • department: in the request's organization;
    • team: not another organization's, pinned at approval-service.test.ts:931-939;
    • position: a position someone in that organization holds.
      The old blanket "in the request's organization" is gone.
  3. SF-2 is fixed. "A typed entry renders the matching picker in the decision dialog, and type and multiple are also enforced": the picker sentence is back, and the misreadable "not widget hints" is gone.
  4. The blank exemption is now stated: "any other non-blank value is 400 VALIDATION_FAILED". That matches the full record's note on the exemption at :4691.
  5. Token payment. The three inline comments removed from the first example (behavior alternatives, lockRecord, approvalStatusField) are each still taught, in the config table at L366, L368 and L369. Nothing is lost. The changeset states 5724 of 5730, and no ceiling row moved: scripts/** is untouched.

② Semver level

'@objectstack/skills': patch, which is right for a skills-docs change that ships in that package. Clause-②: no and Metadata-protocol: none are right.

③ Boundary flags

  • content/docs/automation/approvals.mdx:353-356 has the same organization overstatement (the full record's SF-1 note). It is still owed as its own docs card.
  • The PR author is objectstack-fleet[bot]. Tier H (skills/**): this record does not lift the landing; the maintainer's merge or an authorized approval does.

④ Design judgment

state-machines-and-approvals.md: the paragraph now says what the dialog shows and what the service enforces, scoped per kind as the code scopes it. A user picker from another organization reads as allowed, which matches reassignTargetRefusal's null organization. It holds.

Check-runs on a6110351 at record time: 45 check-runs, 30 success, 15 skipped, 0 failure, 0 in progress.

Implemented-by: claude/project-thread-bo65kq
Reviewed-by: session_01AavokzJ5DndAwitDXvKy4U

VERDICT: PASS


Generated by Claude Code


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · huangyiirene · session_01J8VVAnMUWA9rufXzFaEh4J

维护者速读

席位核对 — ACCEPT · domain:skills#1 · head a6110351346b9786510d8504aba02429b094641b

  1. 形态: draft → main,Fixes #29512;作者 objectstack-fleet[bot];文件在认领面内,附 @objectstack/skills patch changeset(⛔ 不挂 skip-changeset)。
  2. 前提实测: 复核记录逐条对过合并后的代码;本席核对复核结论与 PR 正文一致。
  3. 门禁(钉 head a6110351): check-runs skipped 15 / success 30,无失败。派生门禁见 PR 正文。
  4. 接受的偏差: 开发在 Opus 上进行、契约复核由分诊代审,均依维护者原话。
  5. 范围外去向: approvals.mdx 有同样的 user 组织范围过度表述,属文档车道,另行立卡。
  6. 落地条款: Tier H。保持 draft,挂 needs-user-decision,请 os-zhuang、hotlong 审批;获批后本席过落地前检、清标、ready、auto-merge;维护者直合亦可。

Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review November 3, 2026 16:03
@os-zhuang
os-zhuang added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit cde9a80 Nov 3, 2026
51 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

skills(objectstack-automation): an approval's decision outputs are enforced at the decision — type and multiple are checked, not widget hints (#28658)

2 participants