Skip to content

docs(skills): objectstack-automation teaches the record-anchored approval lock read, with an eval prompt - #26495

Merged
os-zhuang merged 1 commit into
mainfrom
claude/project-thread-ouprob
Oct 27, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/project-thread-ouprob

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Warren · project thread
Fixes #26362
Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:skills/objectstack-automation/references/state-machines-and-approvals.md 新增一小节「Reading the lock」(12 行):什么时候读锁(不在审批上的读者拿不到 request 行)、路由 GET /api/v1/approvals/records/:object/:recordId/lock 答 { success: true, data: { locked } }、SDK client.approvals.getRecordLock(object, recordId)、只答 locked(request 内容仍在对象的 approvalsVisibleToReaders 之后)、读不到的记录与不存在的记录同答 404 RECORD_NOT_FOUND。同文件两处复述 Send-back 节的文字削成指针。eval 文件加第 10 题。棘轮脚本两行上限按实测调整,裁决原文写在行旁。
  • 为什么改:PR feat(plugin-approvals,rest,client): a record-anchored approval lock read, { locked } for every caller who can read the record #26361 把锁读落进了服务、路由与 SDK,文档也写了,技能没教。AI 作者给带审批的对象做记录页时,只会从 request 行的 lock_record 读锁;不在审批上的读者拿不到 request 行,页面就给他一个服务端会用 409 RECORD_LOCKED 拒掉的编辑按钮。
  • 风险与回滚:只改已发布技能包的文字、一个 eval 夹具和棘轮脚本的两行上限,不改运行时;回滚即 revert 本 PR(上限行随之回落)。
  • 先看:① references/state-machines-and-approvals.md L222–L235 新节;② scripts/check-skills-token-ratchet.mjs 两行上限及注释;③ evals/flows-triggers-approvals.json 第 10 题。
  • 两分钟验证:node scripts/check-skills-token-ratchet.mjs → 看到 state-machines-and-approvals.md is 5730 tokens (ceiling 5730; headroom 0) 与 flows-triggers-approvals.json is 2427 tokens (ceiling 2427; headroom 0),末行 81 authored bundle file(s) within their ceilings。
  • 需要你做的:Tier H(skills/**),请 approve。

What changes

Card #26362's 「Who acts」 paragraph, and nothing wider:

  1. references/state-machines-and-approvals.md, one section, "Reading the lock" (after Recording a decision, before Approver Types; 12 lines of prose, no code block): a page over a record under approval must not offer an edit the server refuses with 409 RECORD_LOCKED; the request row tells only a participant, or any reader once the object declares enable.approvalsVisibleToReaders, so a non-participant is served no row; the lock itself is GET /api/v1/approvals/records/:object/:recordId/lock, in the SDK client.approvals.getRecordLock(object, recordId), answered to anyone who can read the record as { success: true, data: { locked } } — locked and nothing else, request content behind that switch, the lock hook's own judgement; the record is read as the caller, so one they cannot read answers 404 RECORD_NOT_FOUND, the same body an id that does not exist gets. Every clause was read from the merged code, not the card: packages/plugins/plugin-approvals/src/approval-service.ts getRecordLock (the record is engine.find-read as the caller; an unreadable row and a missing id answer the same null; the judgement is recordHeldByLockingApproval, the hook's own; recordReaderTierOn reads enable.approvalsVisibleToReaders), record-lock-read.integration.test.ts (the non-participant reader is told { locked: true } and still served no request row; _b and MISSING answer alike), packages/rest/src/approvals-routes.ts + rest-approvals-record-lock-route.test.ts (one 404 RECORD_NOT_FOUND for every null, { locked } alone), packages/client/src/index.ts getRecordLock, and content/docs/automation/approvals.mdx 「Reading the lock」. The deployment-without-the-member 501 is not taught (not authoring guidance).
  2. evals/flows-triggers-approvals.json: entry 10 — an expense record page offers Edit to members not on the approval and their save is refused 409 RECORD_LOCKED; the ideal answer reads the lock (client.approvals.getRecordLock, the route) for anyone who can read the record, hides or disables Edit while locked, names the request row a non-participant is never served and the approvalsVisibleToReaders switch that is not flipped for this, and the 404 for an unreadable record. must_contain: getRecordLock, locked; must_not_contain: lock_record (the request-row lookup the card names), approvalsVisibleToReaders: true (the other wrong fix: exposing request content to every reader). The expected output is written so that it never spells either must_not_contain string (checked by script).
  3. Two genuine restatements cut to pointers, same file, both of the Send-back for revision section, which is their surviving home: the Node Config table's maxRevisions row (it restated item 3 — default 3, 0 disables, only with a revise edge — now "Send-backs per run before auto-reject (ADR-0044) — see Send-back for revision above", −76 bytes) and the Branching section's send-back bullet (it restated the section's three pieces — the revise edge into approval_revise, the type: 'back' resubmit edge — now the pointer alone, −134 bytes). Nothing else in the file restates another passage.
  4. scripts/check-skills-token-ratchet.mjs, the two objectstack-automation rows this PR touches and no other, under the maintainer's ruling on the seat's decision card 「skills 修复卡的教学和 eval 都撞 token 天花板,怎么付?」, tap A (2026-10-27 01:21Z), verbatim and untranslated: 「A:先削重复,余额按实测提高」. Step one, duplicates cut first: the two above (−210 bytes); the eval file's nine fixtures share no sentence (0 bytes to cut). Step two, each row follows its measurement (ceil(utf8 bytes / 4)): state-machines-and-approvals.md 22346 → 22918 bytes (+782 added, −210 cut, +572 net), 5587 → 5730 (headroom was 2 bytes, 0 tokens; +143); evals/flows-triggers-approvals.json 8561 → 9705 bytes (+1144), 2141 → 2427 (headroom was 3 bytes, 0 tokens; +286). Both pinned AT the measurement, each comment quoting the tap. Rows measured against origin/main cc0d77b3 (after docs(skills): objectstack-automation teaches the read-only record_link screen field, with an eval prompt #26446 landed as d1a31d0c), the branch's base.

SKILL.md is untouched: its pointer line already routes every approvals question to this reference, and line 1 belongs to #25640. references/_index.md is generator-owned (gen:skill-refs) and unchanged (no schema moved). The section is prose with inline code, so no os:check block was added and the check:skill-examples count stays as on main.

Two readings (lines, and tokens as the ratchet counts them):

File Lines before → after Tokens before → after
references/state-machines-and-approvals.md 380 → 392 (+12) 5587 → 5730 (+143)
evals/flows-triggers-approvals.json (not .md, reported for the raise) 95 → 105 (+10) 2141 → 2427 (+286)
SKILL.md 441 → 441 (0) 6164 → 6164 (0)
Whole package (SKILL.md + references/**/*.md, 4 files; the package has no rules/) 1001 → 1013 (+12) 14091 → 14234 (+143)

Removed: the two Send-back restatements only (−210 bytes, −2 lines).

skip-changeset: the published skills packages carry no package.json; nothing is released from any package.

Why

PR #26361 (#26314) landed IApprovalService.getRecordLock, the route and the SDK call, and documented them; the owning skill did not teach them, so an AI author building a page over an object with approvals learned the lock only from the request row, which a reader not on the approval is never served — the page then offered an edit the server refused with 409 RECORD_LOCKED. Metadata-protocol: none: the protocol already carries the route; the gap was the teaching.

Tests

  • node scripts/check-skills-token-ratchet.mjs — exit 0: both touched rows at headroom 0, 81 authored bundle file(s) within their ceilings; 10 generator-owned file(s) measured, not ratcheted.
  • node scripts/check-skills-token-ratchet.mjs --self-test — exit 0, 79 cases pass.
  • pnpm --filter @objectstack/spec run check:skill-docs, check:skill-refs, check:skill-examples — see Verification.
  • Eval 10 is a fixture (the repo has no eval runner): a script checked every must_contain string occurs in its expected output and neither must_not_contain string occurs in the prompt or the expected output.
  • grep -i -w role over the added lines: no hit (check:role-word green); no control byte in the three files.

Acceptance notes

  • SKILL.md's pointer line to this reference names only the state-introspection route; the lock-read route now lives in the reference's new section. Not changed here (the dispatch order allows a SKILL.md edit only if the approvals row must point at the section; the pointer already routes approvals questions to the file): observation only.
  • The PR body's first two lines are the harness-required attribution block (the os-dev exception); Fixes #26362 follows immediately.

Verification

Gate list derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on the final tree (base cc0d77b3, head 0f8b9708), each run locally with the exit code captured before any pipe; --ran reconciliation below. No commit followed the gate runs, so every reading is of 0f8b9708.

Reconciliation (node scripts/pm/dispatch-gates.mjs --ran ran.list --repo objectstack-ai/objectstack): exit 0 — 52 derived famil(ies) accounted for — 51 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3), 0 UNRUN.

The one NOT MEASURED is pnpm check:dual-build-cjs-loads (exit 3: its prerequisite is a whole-repo build; this diff touches no package source, and CI's Lint & Repo Gates runs it — a declared narrowing). The closures for check:skill-examples and check:doc-formula-expressions (@objectstack/client..., @objectstack/client-react..., @objectstack/lint..., @objectstack/formula...) were built under scripts/pm/os-verify-lock.sh (slot issue-26362, VERDICT command-exit 0, 298 s held, 0 s waited); check:dts-closure and check:lean-entry-closure ran after that build (38 built package(s) swept - 160/160 declared declaration file(s) present). node scripts/check-comment-mask-corpus.mjs ran in the serial cheap runner and had finished before check:skill-examples started. check:pm-dispatch-gates ran detached on the final head: fast tier, 1935 cases pass, exit 0. Two runs are outside the derivation and recorded anyway: check:skill-docs and check:skill-examples, the card's acceptance gates (the section is prose, so no marked block was added).

Every command with its exit code on 0f8b970 (54 lines: the 52 derived, plus `check:skill-docs` and `check:skill-examples`)
  • node scripts/check-ci-filter-parity.mjs — exit 0
  • node scripts/check-closing-keyword-parity.mjs — exit 0
  • node scripts/check-closing-keyword-parity.mjs --self-test — exit 0
  • node scripts/check-comment-mask-corpus.mjs — exit 0
  • node scripts/check-declaration-mirrors.mjs — exit 0
  • node scripts/check-declaration-mirrors.mjs --self-test — exit 0
  • node scripts/check-doc-route-spelling.mjs --advisory — exit 0
  • node scripts/check-doc-route-spelling.mjs --self-test — exit 0
  • node scripts/check-dts-emitted.mjs --self-test — exit 0
  • node scripts/check-scripts-symbol-anchors.mjs — exit 0
  • node scripts/check-scripts-symbol-anchors.mjs --self-test — exit 0
  • node scripts/check-self-test-wired.mjs — exit 0
  • node scripts/check-self-test-wired.mjs --self-test — exit 0
  • node scripts/check-self-test-workflow-commands.mjs — exit 0
  • node scripts/check-self-test-workflow-commands.mjs --self-test — exit 0
  • node scripts/check-skills-token-ratchet.mjs — exit 0
  • node scripts/check-skills-token-ratchet.mjs --self-test — exit 0
  • node scripts/check-whole-set-label-write.mjs — exit 0
  • node scripts/check-whole-set-label-write.mjs --self-test — exit 0
  • node scripts/ci/assert-turbo-remote-cache.mjs --self-test — exit 0
  • node scripts/ci/plan-test-core-shards.mjs --self-test — exit 0
  • node scripts/ci/prune-turbo-cache.mjs --self-test — exit 0
  • node scripts/pm/bare-root-worklist.mjs --self-test — exit 0
  • pnpm --filter @objectstack/lint run check:doc-formula-expressions — exit 0
  • pnpm --filter @objectstack/spec run check:skill-refs — exit 0
  • pnpm check:agent-test-spelling — exit 0
  • pnpm check:bash32-floor — exit 0
  • pnpm check:cli-command-ids — exit 0
  • pnpm check:corpus-claim-drift — exit 0
  • pnpm check:cross-package-test-inputs — exit 0
  • pnpm check:doc-authoring — exit 0
  • pnpm check:driver-memory-census — exit 0
  • pnpm check:dts-closure — exit 0
  • pnpm check:dual-build-cjs-loads — NOT MEASURED (exit 3, PREREQUISITE NOT MET: this gate reads built output and some package has no dist/; CI's Lint & Repo Gates runs it)
  • pnpm check:entry-guard — exit 0
  • pnpm check:gitlink-declared — exit 0
  • pnpm check:lean-entry-closure — exit 0
  • pnpm check:nul-bytes — exit 0
  • pnpm check:parse-guard — exit 0
  • pnpm check:pm-dispatch-gates — exit 0
  • pnpm check:pm-governed-merges — exit 0
  • pnpm check:pm-skill-id-lint — exit 0
  • pnpm check:pnpm-filter-targets — exit 0
  • pnpm check:pnpm-flag-spellings — exit 0
  • pnpm check:ratchet-remedy-authority — exit 0
  • pnpm check:refd-timer-probe — exit 0
  • pnpm check:role-word — exit 0
  • pnpm check:skill-compatibility — exit 0
  • pnpm check:skill-frame-sync — exit 0
  • pnpm check:skill-identifier-liveness — exit 0
  • pnpm check:sourcemap-no-sources-content — exit 0
  • pnpm check:watch-hint-literal — exit 0
  • pnpm --filter @objectstack/spec run check:skill-docs — exit 0 (card acceptance gate, outside the derivation)
  • pnpm --filter @objectstack/spec run check:skill-examples — exit 0 (card acceptance gate, outside the derivation)
Token ratchet on 0f8b970
  • skills/objectstack-automation/references/state-machines-and-approvals.md is 5730 tokens (ceiling 5730; headroom 0)
  • skills/objectstack-automation/evals/flows-triggers-approvals.json is 2427 tokens (ceiling 2427; headroom 0)
  • 81 authored bundle file(s) within their ceilings; 10 generator-owned file(s) measured, not ratcheted.
  • --self-test: 79 cases pass.
check:skill-examples on 0f8b970

265 prose examples type-check across 3 surface(s) — every marked block parsed, so tsc ran the SEMANTIC pass on all of them (this diff adds no os:check block: the new section is prose with inline code).

Human Minimum

Each item is one rule from CONTRIBUTING.md › Human Minimum.

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (No schema change.)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint is CI's run (repo-wide scan).
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. Nothing publishes (skip-changeset); both declaration lines are filled in above.
  • 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 and the quoted ruling are the required exceptions).

Generated by Claude Code

…oval lock read, with an eval prompt

One short section, "Reading the lock", in
references/state-machines-and-approvals.md: when a record page reads the lock
(a reader not on the approval is served no request row), the route
GET /api/v1/approvals/records/:object/:recordId/lock answering
{ success: true, data: { locked } }, the SDK call
client.approvals.getRecordLock(object, recordId), `locked` alone with request
content behind `approvalsVisibleToReaders`, and an unreadable record answered
with the same 404 as a missing one. Two restatements of the Send-back section
are cut to pointers (the `maxRevisions` config row, the Branching bullet).

evals/flows-triggers-approvals.json gains prompt 10, which expects the lock
read rather than a request-row lookup for a non-participant.

scripts/check-skills-token-ratchet.mjs: the two touched rows follow their
measurement (5587 -> 5730, 2141 -> 2427) under the maintainer's ruling quoted
in each row comment.

Claude-Session: https://claude.ai/code/session_01JQwxbZpLcVkA9KV78eHZYr
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 27, 2026
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation labels Oct 27, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review October 27, 2026 05:33
@os-zhuang
os-zhuang enabled auto-merge October 27, 2026 05:33
@os-zhuang
os-zhuang added this pull request to the merge queue Oct 27, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Reviewed at 2026-10-27T05:37Z — PR #26495, card #26362, skills/objectstack-automation.

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 0f8b9708b881babf6588cb9e2794d608e6ad188b
Local-runs: probe — token counts, byte deltas and line spans recomputed over git show blobs (wc -c, ceil(utf8 bytes / 4)) and the eval JSON parsed in Python; no gate, build or script was run
Scope: skills/objectstack-automation/references/state-machines-and-approvals.md, skills/objectstack-automation/evals/flows-triggers-approvals.json, scripts/check-skills-token-ratchet.mjs + packages/plugins/plugin-approvals/src/approval-service.ts, packages/plugins/plugin-approvals/src/lifecycle-hooks.ts, packages/plugins/plugin-approvals/src/record-lock-read.integration.test.ts, packages/rest/src/approvals-routes.ts, packages/rest/src/rest-approvals-record-lock-route.test.ts, packages/client/src/index.ts, packages/client/src/client.test.ts, packages/spec/src/data/object.zod.ts, content/docs/automation/approvals.mdx, skills/objectstack-automation/SKILL.md, skills/objectstack-automation/references/_index.md, .github/workflows/lint.yml, .github/workflows/ci.yml, .claude/agents/os-dev.md

Inputs: card #26362 body and all six comments (triage 6430134159, unblock 6430551341, unlock 6431110682, dispatch order 6434160193, claim 6434214089, os-dev-report 6434547904), the PR body, file list and diff, the check-runs on the head, origin/main at 00a4593a, and the branch claude/project-thread-ouprob at the head. Base reconciled: the API's base.sha is the main tip 00a4593a; the branch's merge-base is cc0d77b3; the three commits between (c91544a4, f8f4095a, 00a4593a) touch neither scripts/check-skills-token-ratchet.mjs nor skills/objectstack-automation/** (empty git diff --stat), so no sibling ratchet row is stale and the diff applies to main as it stands.

Landing state as read: the PR was a draft at 2026-10-27T05:30Z; by 2026-10-27T05:35Z it had been readied, approved by os-zhuang (review at 2026-10-27T05:33Z) and auto-merge enabled by the same account, before this record. Reported, nothing touched.

① Derived judgments

(a) Every new sentence against the code on origin/main — right, with two precision notes.

  • Route GET /api/v1/approvals/records/:object/:recordId/lock, envelope { success: true, data: { locked } }: approvals-routes.ts registers ${dataPath}/approvals/records/:object/:recordId/lock and answers sendOk(res, { locked: lock.locked === true }); the route test pins { success: true, data: { locked: true } } and that extra service fields are dropped. Right.
  • SDK client.approvals.getRecordLock(object, recordId) returning { locked: boolean }: packages/client/src/index.ts and client.test.ts (/api/v1/approvals/records/order/rec%2F1/lock, GET). Right.
  • "answered to anyone who can read the record", "the same judgement the lock hook applies to an edit": ApprovalService.getRecordLock reads the record with engine.find as the caller and returns recordHeldByLockingApproval(engine, object, id), the same function the record-lock hook judges by (lifecycle-hooks.ts, nodeConfigLocksRecord); the integration test pins a non-participant reader told { locked: true } while their write is refused RECORD_LOCKED. Right. (The hook additionally exempts the opening run's own writes, a property of the write, not of the record; the section does not claim otherwise.)
  • 409 RECORD_LOCKED: lockedError sets code = 'RECORD_LOCKED', statusCode = 409. Right.
  • "one they cannot read answers 404 RECORD_NOT_FOUND, the same body an id that does not exist gets": the service answers null for both from the same branch; the route sends one sendEnvelopeError(res, 404, 'RECORD_NOT_FOUND', ...) for every null; the route test asserts the two responses are deep-equal and name no id. Right.
  • "locked and nothing else (request, approvers and history stay behind that switch)", enable.approvalsVisibleToReaders: the key lives in the object's enable block (object.zod.ts L350, default false); recordReaderTierOn reads schema.enable.approvalsVisibleToReaders === true. Spelling right.
  • Precision note 1 (not a falsehood for the author, but wider than the code): "The request row tells only a participant, or any reader once the object declares enable.approvalsVisibleToReaders; everyone else is served no row". The code also serves the row to administrators/override actors (object.zod.ts L350: participants and administrators) and turns the reader tier on by a second source, the host's recordReaderVisibleObjects constructor set (recordReaderTierOn). The teaching's point — a plain reader not on the approval gets no row, so a page must not learn the lock from it — holds; the word "only" overstates. To tighten on the next edit of the file, not now (every token is paid).
  • Precision note 2: the body says "12 lines of prose"; the section is 14 lines (L222–L235: heading, blank, 11 prose lines, blank), 782 bytes. Cosmetic.
  • Not taught and rightly so: the 403 for a caller with no read on the object (the service throws it, the route relays it) and the 501 for a deployment without the member. Both are outside the card's 「Who acts」 paragraph.

(b) The two cuts are genuine duplicates with a surviving home. Both point at Send-back for revision (ADR-0044), which on the head still carries every clause the cut text had: the revise out-edge "targeting an approval_revise node" (item 1), the "type: 'back' resubmit edge" (item 2), and maxRevisions "default 3", "maxRevisions: 0 disables send-back, so never pair 0 with a revise edge" (item 3), plus the lint ids and the showcase pointer. Neither cut text carried an error code or envelope. Measured deltas: the maxRevisions row 185 → 109 bytes (−76), the bullet 214 → 80 bytes (−134); −210 as the body says.

(c) Ratchet rows pinned at the measurement; ruling quoted where the script requires. TOKEN_CONVENTION is ceil(utf8 bytes / 4), the stamp line excluded for SKILL.md only. Recomputed over the blobs: state-machines-and-approvals.md 22346 B → 22918 B, ceil(22918/4) = 5730 (row 5587 → 5730, headroom 0; the old headroom was 2 bytes); flows-triggers-approvals.json 8561 B → 9705 B, ceil(9705/4) = 2427 (row 2141 → 2427, headroom 0; old headroom 3 bytes). Neither row sits above its measurement. The script's discipline ("Going the other way lands only in a PR whose body quotes a maintainer ruling") is met: the tap 「A:先削重复,余额按实测提高」 is quoted verbatim in both row comments and in the PR body (What changes item 4), with the option-A arithmetic per file. No other row changed (the head's script differs from origin/main only in these two rows and their comments). check:ratchet-remedy-authority runs in Type Check · source gates, success on the head. No eslint max-len rule exists in the repo config; Lint · ESLint success.

(d) Evals. Eval 10 has the file's shape (id, prompt, expected_output, files: [], assertions); ids 1–10 sequential (9 from #26446 is on main); both must_contain strings (getRecordLock, locked) are in the expected output; neither must_not_contain string (lock_record, approvalsVisibleToReaders: true) is in the prompt or the expected output (the latter spells "approvalsVisibleToReaders, which is not switched on"). The prompt's 409 RECORD_LOCKED matches the hook. The card's eval line (expect the read rather than a lock_record lookup for a non-participant) is met. The repo has no eval runner, so the fixture is read, not executed. Pre-existing fixtures 5, 7 and 8 in the same file break the same discipline on main (see ③).

(e) Card Wanted lines met, nothing beyond. The section carries every item of 「Who acts」: when to read the lock, the route, the SDK call, locked only with request content behind approvalsVisibleToReaders, the unreadable record answered as a missing one; one eval prompt. SKILL.md untouched (441 lines, stamp line 18.1.0 intact, line 1 is #25640's); references/_index.md is gen:skill-refs output indexing Zod files, unaffected by a prose heading; no file outside the claim's surface. Line readings confirmed: md 380 → 392, eval 95 → 105; whole package (SKILL.md + three references/*.md) 1001 → 1013 lines, 14091 → 14234 tokens — exactly the body's table.

(f) PR body shape. Line 1 is the harness attribution comment, line 2 the harness _Requested by_ line, line 3 Fixes #26362, then Clause-②: no and Metadata-protocol: none line-initial; no Skills-docs: line (kind none, none owed). os-dev.md L323 lets the harness attribution comment stand and asks Fixes to follow it; here one harness line sits between, as on every sibling PR of the wave, and both closing-keyword gates (The card this PR closes must claim this branch, Part-of PR must not also close its card) are success on the head. The six 速读 labels are present in order; 需要你做的 names exactly one action (approve, right for Tier H); 先看 names three files with the risky hunk first; 两分钟验证 is one command whose expected sight matches the script's own ... is N tokens (ceiling N; headroom 0). and ... authored bundle file(s) within their ceilings lines. English sections What changes / Tests / Acceptance notes / Verification present, readings folded in details blocks. Commit 0f8b9708 carries the model-free trailer pair and no card trailer. One deviation between body and files: the body and the report say CI's "Lint & Repo Gates" runs check:dual-build-cjs-loads; on origin/main it runs in ci.yml's Build Core (lint.yml's own comment at L5522 says so). The gate itself was still in progress at the 2026-10-27T05:35Z read.

(g) Report shape and claim. os-dev-report 6434547904 carries the template's keys plus deviations (6), line_budget, body_amendments, out_of_scope_findings; files_changed / rules_retired are absent and the os-dev.md template (L364–L379) lists neither, so nothing is missing. Branch claude/project-thread-ouprob equals the claim's Branch: line; mcp_calls 0 GitHub; api_writes 2 (pr_create via the relay, the report). The report's session field appends the served model readings beyond the template's session_ID shape; the model-name ban in the order covers the commit and PR body, and both are clean.

Check-runs on 0f8b9708, read at 2026-10-27T05:35Z: 47 runs — 29 success, 16 skipped (UI, docs, dogfood and opt-in lanes, per the filter), 2 in progress (Build Core, which hosts check:dual-build-cjs-loads, and the Test Core aggregate; its three shards had finished). Success includes Type Check · source gates (token ratchet + self-test, check:skill-docs, check:skill-refs, check:ratchet-remedy-authority), Type Check · consumer gates (check:skill-examples), Lint · repo & tooling gates, Lint · ESLint, Lint · docs & code guards, Lint · scoped · tooling self-tests, Check Changeset, Governed Surface Queue Guard (twice, the second after readying), the closing-keyword and single-claim guards. Pending at the read: the two named above; nothing red.

② Semver level

skip-changeset is right: skills/objectstack-automation holds no package.json and scripts/** publishes nothing; Check Changeset success on the head. Declaration lines line-initial. No npm package changes.

③ Boundary flags

  1. Card candidate (class c, named producer skills/objectstack-automation/evals): on origin/main, fixtures 5, 7 and 8 of flows-triggers-approvals.json contradict their own assertions — 5's expected output spells its must_not_contain string label: 'error'; 7 lacks its must_contain type: 'position' and spells its must_not_contain current_user.positions; 8 lacks type: 'screen'. The dispatch order's rule (the ideal answer never spells its own must_not_contain strings) is met by 9 and 10 only. Dedupe words: flows-triggers-approvals evals 5 7 8 assertions expected_output. Not this PR's.
  2. Precision to-do, next edit of the reference file: the "only a participant ... everyone else is served no row" sentence omits administrators and the host's recordReaderVisibleObjects source (①a note 1). 收班简报 note, no card.
  3. The dev's observation that SKILL.md L317 names only the state-introspection route: nothing now (the pointer routes every approvals question to the reference, and the line costs ratchet tokens); fold into the next card that edits SKILL.md.
  4. Body/report say check:dual-build-cjs-loads runs in "Lint & Repo Gates"; it runs in Build Core. Precision to-do for the dev's gate vocabulary; no card.
  5. Harness body shape: Fixes on line 3 behind two harness lines while os-dev.md L323 says "紧随其后". The gates accept it; a seat note on whether L323 should name the two-line block.
  6. Report session field carries served-model readings beyond the template shape; the order's ban covers the commit and PR, which are clean. Seat note.
  7. Tier H sequence: approval, readying and auto-merge by a governed approver landed before this record. Reported only.

Implemented-by: claude/project-thread-ouprob
Reviewed-by: session_01LqKsGyMNFwwnbF5FTAqyPn

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读

改了什么:objectstack-automation 技能的 references/state-machines-and-approvals.md 新增一节「Reading the lock」:什么时候读记录锁、路由 GET /api/v1/approvals/records/:object/:recordId/lock 与返回 { success: true, data: { locked } }、SDK client.approvals.getRecordLock(object, recordId)、答案只有 locked(请求内容仍在 approvalsVisibleToReaders 之后)、读不到的记录与不存在的记录同答 404;eval 第 10 题(非参与者场景期望读锁而不是查 lock_record)。按你的裁决 A:同文件削两处 Send-back 规则的重述(−210 B,原文保留在 Send-back 节),两行天花板按实测改(参考文件 5587 → 5730,eval 2141 → 2427)。
为什么改:#26361 把记录锁的只读接口落进了服务、路由、SDK 与文档,技能没教;AI 作者为不在审批里的读者做页面时,只能从请求行推锁,拿不到请求行就给出一个服务器会以 409 RECORD_LOCKED 拒绝的编辑。
风险与回滚:只改一个参考 markdown、一题 eval JSON、棘轮两行数字;无运行时改动;回滚 = revert 本 PR。风险在教学措辞是否属实——复核员逐句对着 approval-service.ts 的 getRecordLock、lifecycle-hooks.ts、approvals-routes.ts 及其测试、client/src/index.ts、object.zod.ts L350 核过,全部为真。
先看:① references/state-machines-and-approvals.md L222–L235 新节;② 同文件两处削减(Send-back 重述);③ scripts/check-skills-token-ratchet.mjs 两行(5730 / 2427,各钉在实测值)。
两分钟验证:node scripts/check-skills-token-ratchet.mjs 全绿,两文件各印 headroom 0;pnpm --filter @objectstack/spec run check:skill-examples 265 个示例通过。
需要你做的:无——os-zhuang 已于 05:33Z 批准、就绪并布自动合并,队列在走。
席位意见:同意落地。一条备注留收班简报:该节写「请求行只告诉参与者,或对象声明 enable.approvalsVisibleToReaders 后的任何读者」,漏了管理员/override 角色,结论不变、「只」字偏重;另从本复审立一张卡:同 eval 文件的第 5、7、8 题在 main 上自相矛盾(断言与理想答案冲突)。

席位核对

ACCEPT · domain:skills#2 · session_01LqKsGyMNFwwnbF5FTAqyPn · 2026-10-27T06:00Z — seat review of record for #26362 on head 0f8b9708, against GitHub, not the report's own account. Published surface skills/** ⇒ Tier H (skills/objectstack-automation/** ×2 on the register, scripts/check-skills-token-ratchet.mjs off it, 56 changed lines): the record of this tier is the independent ## Contract review at comment 6434669721 (Served-tier: CONTRACT_REVIEW_TIER, head 0f8b9708, VERDICT: PASS, Local-runs: probe — byte counts and the eval JSON parse, declared; drafted by a reviewer other than the implementer, the card thread's dev). This seat adds the landing read.

  1. Shape. Not draft (os-zhuang readied it at 2026-10-27T05:33Z), base main, mergeable: true (clean), one commit; body lines 1–2 the harness attribution block, line 3 Fixes #26362, Clause-②: no, Metadata-protocol: none line-initial, no Skills-docs: line (kind none, none owed); ## 维护者速读 six labels in order, then What changes / the two readings / Tests / Acceptance notes / Verification. Files: references/state-machines-and-approvals.md (+16/−4), evals/flows-triggers-approvals.json (+10), scripts/check-skills-token-ratchet.mjs (+24/−2, two rows) — the claim's surface exactly (claim 6434214089: one section, one prompt, the package's CEILINGS rows under ruling A); SKILL.md untouched. Labels documentation, size/s, skip-changeset — right, no package.json under skills/. Branch claude/project-thread-ouprob, named by the claim; based on cc0d77b3, after docs(skills): objectstack-automation teaches the read-only record_link screen field, with an eval prompt #26446 d1a31d0c.
  2. Record, measured. The reviewer read every clause of the new section against origin/main: getRecordLock reads the record as the caller and answers null for unreadable and missing alike (so the route's one 404 RECORD_NOT_FOUND holds), recordHeldByLockingApproval is the lock judgement the hook itself uses (RECORD_LOCKED, 409), the route envelope and its test, the SDK signature and client.test.ts L709, enable.approvalsVisibleToReaders at object.zod.ts L350, the docs paragraph. Both cuts are genuine duplicates (the Send-back section still carries the revise edge, type: 'back', maxRevisions default and the 0-disables rule; −76 and −134 bytes; no error code or envelope was in the cut text). Ratchet rows recomputed with the script's measure: 22918 B → 5730 and 9705 B → 2427, pinned at zero headroom, tap quoted in both row comments and the body; the script differs from origin/main only by these two rows and the three commits between merge-base and main tip touch neither the script nor the package. Eval 10: ids 1–10 sequential, must_contain present, neither must_not_contain string in prompt or expected output. Readings exact (md 380 → 392 lines, eval 95 → 105, package 1001 → 1013 lines / 14091 → 14234 tokens). The seat re-read the diff and the body and reads the same.
  3. Gates, pinned to head 0f8b9708. 47 check-runs, 31 success and 16 skipped, none red or pending (reviewer's read 2026-10-27T05:38Z). Token ratchet + self-test, check:skill-docs, check:skill-refs, ratchet-remedy-authority in Type Check · source gates; check:skill-examples in Type Check · consumer gates; Build Core carries check:dual-build-cjs-loads (NOT-MEASURED locally by the dev, exit 3) — all success; Lint lanes, Governed Surface Queue Guard, Check Changeset, the claim/closing guards success.
  4. Accepted deviations. (a) Fixes on line 3 behind the harness's two attribution lines. (b) Branch is the thread's harness branch. (c) pr_create through the relay, 0 MCP calls. (d) check:dual-build-cjs-loads NOT-MEASURED locally; Build Core green is the reading; body and report place it in 「Lint & Repo Gates」 — lane label only. (e) Body says 「12 lines of prose」 for a 14-line section (11 prose) — cosmetic. (f) Tier H sequence: os-zhuang approved, readied and armed at 05:33Z, before the contract review (05:37Z) and this record — reported, nothing touched.
  5. Carrier. Card candidate, seat-filed after landing (class c, producer skills/objectstack-automation/evals): fixtures 5, 7, 8 of flows-triggers-approvals.json on origin/main contradict their own assertions (5 spells its own must_not_contain label: 'error'; 7 lacks must_contain type: 'position' and spells current_user.positions; 8 lacks type: 'screen') — dedupe flows-triggers-approvals evals 5 7 8 assertions expected_output. 收班简报: the section's 「only a participant, or any reader once … approvalsVisibleToReaders」 omits administrators/override actors (object.zod.ts L350, recordReaderTierOn); SKILL.md L317 pointer names only the state-introspection route (next SKILL.md edit); os-dev.md L323 one-line vs two-line harness attribution (already carried).
  6. Landing. Tier H ⇒ this record and the contract review are on the head; os-zhuang's APPROVED review 5662660229 on 0f8b9708 is the authorised hand; auto-merge was armed by him and the queue is processing — nothing to ready or enqueue. On MERGED the seat closes skills(automation): teach the record-anchored approval lock read (GET /approvals/records/:object/:recordId/lock, { locked } for every reader) with one eval prompt #26362 with the landing record and files the eval-fixtures card.

Generated by Claude Code

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 skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

3 participants