Repository navigation
docs(skills): objectstack-automation teaches the record-anchored approval lock read, with an eval prompt - #26495
Conversation
…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>
Contract reviewReviewed at 2026-10-27T05:37Z — PR #26495, card #26362, Served-tier: 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, 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
(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 (c) Ratchet rows pinned at the measurement; ruling quoted where the script requires. (d) Evals. Eval 10 has the file's shape ( (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, (f) PR body shape. Line 1 is the harness attribution comment, line 2 the harness (g) Report shape and claim. os-dev-report 6434547904 carries the template's keys plus Check-runs on ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
维护者速读改了什么:objectstack-automation 技能的 席位核对ACCEPT ·
Generated by Claude Code |
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 } }、SDKclient.approvals.getRecordLock(object, recordId)、只答locked(request 内容仍在对象的approvalsVisibleToReaders之后)、读不到的记录与不存在的记录同答404 RECORD_NOT_FOUND。同文件两处复述 Send-back 节的文字削成指针。eval 文件加第 10 题。棘轮脚本两行上限按实测调整,裁决原文写在行旁。{ locked }for every caller who can read the record #26361 把锁读落进了服务、路由与 SDK,文档也写了,技能没教。AI 作者给带审批的对象做记录页时,只会从 request 行的lock_record读锁;不在审批上的读者拿不到 request 行,页面就给他一个服务端会用409 RECORD_LOCKED拒掉的编辑按钮。references/state-machines-and-approvals.mdL222–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。skills/**),请 approve。What changes
Card #26362's 「Who acts」 paragraph, and nothing wider:
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 with409 RECORD_LOCKED; the request row tells only a participant, or any reader once the object declaresenable.approvalsVisibleToReaders, so a non-participant is served no row; the lock itself isGET /api/v1/approvals/records/:object/:recordId/lock, in the SDKclient.approvals.getRecordLock(object, recordId), answered to anyone who can read the record as{ success: true, data: { locked } }—lockedand 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 answers404 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.tsgetRecordLock(the record isengine.find-read as the caller; an unreadable row and a missing id answer the samenull; the judgement isrecordHeldByLockingApproval, the hook's own;recordReaderTierOnreadsenable.approvalsVisibleToReaders),record-lock-read.integration.test.ts(the non-participant reader is told{ locked: true }and still served no request row;_bandMISSINGanswer alike),packages/rest/src/approvals-routes.ts+rest-approvals-record-lock-route.test.ts(one404 RECORD_NOT_FOUNDfor everynull,{ locked }alone),packages/client/src/index.tsgetRecordLock, andcontent/docs/automation/approvals.mdx「Reading the lock」. The deployment-without-the-member501is not taught (not authoring guidance).evals/flows-triggers-approvals.json: entry 10 — anexpenserecord page offers Edit to members not on the approval and their save is refused409 RECORD_LOCKED; the ideal answer reads the lock (client.approvals.getRecordLock, the route) for anyone who can read the record, hides or disables Edit whilelocked, names the request row a non-participant is never served and theapprovalsVisibleToReadersswitch that is not flipped for this, and the404for 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 eithermust_not_containstring (checked by script).maxRevisionsrow (it restated item 3 — default3,0disables, only with areviseedge — 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 — thereviseedge intoapproval_revise, thetype: 'back'resubmit edge — now the pointer alone, −134 bytes). Nothing else in the file restates another passage.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.md22346 → 22918 bytes (+782 added, −210 cut, +572 net), 5587 → 5730 (headroom was 2 bytes, 0 tokens; +143);evals/flows-triggers-approvals.json8561 → 9705 bytes (+1144), 2141 → 2427 (headroom was 3 bytes, 0 tokens; +286). Both pinned AT the measurement, each comment quoting the tap. Rows measured againstorigin/maincc0d77b3(after docs(skills): objectstack-automation teaches the read-onlyrecord_linkscreen field, with an eval prompt #26446 landed asd1a31d0c), the branch's base.SKILL.mdis untouched: its pointer line already routes every approvals question to this reference, and line 1 belongs to #25640.references/_index.mdis generator-owned (gen:skill-refs) and unchanged (no schema moved). The section is prose with inline code, so noos:checkblock was added and thecheck:skill-examplescount stays as onmain.Two readings (lines, and tokens as the ratchet counts them):
references/state-machines-and-approvals.mdevals/flows-triggers-approvals.json(not .md, reported for the raise)SKILL.mdSKILL.md+references/**/*.md, 4 files; the package has norules/)Removed: the two Send-back restatements only (−210 bytes, −2 lines).
skip-changeset: the published skills packages carry nopackage.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 with409 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.must_containstring occurs in its expected output and neithermust_not_containstring occurs in the prompt or the expected output.grep -i -w roleover the added lines: no hit (check:role-wordgreen); 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 aSKILL.mdedit only if the approvals row must point at the section; the pointer already routes approvals questions to the file): observation only.Fixes #26362follows immediately.Verification
Gate list derived with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon the final tree (basecc0d77b3, head0f8b9708), each run locally with the exit code captured before any pipe;--ranreconciliation below. No commit followed the gate runs, so every reading is of0f8b9708.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'sLint & Repo Gatesruns it — a declared narrowing). The closures forcheck:skill-examplesandcheck:doc-formula-expressions(@objectstack/client...,@objectstack/client-react...,@objectstack/lint...,@objectstack/formula...) were built underscripts/pm/os-verify-lock.sh(slotissue-26362,VERDICT command-exit 0, 298 s held, 0 s waited);check:dts-closureandcheck:lean-entry-closureran after that build (38 built package(s) swept - 160/160 declared declaration file(s) present).node scripts/check-comment-mask-corpus.mjsran in the serial cheap runner and had finished beforecheck:skill-examplesstarted.check:pm-dispatch-gatesran detached on the final head:fast tier, 1935 cases pass, exit 0. Two runs are outside the derivation and recorded anyway:check:skill-docsandcheck: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 0node scripts/check-closing-keyword-parity.mjs— exit 0node scripts/check-closing-keyword-parity.mjs --self-test— exit 0node scripts/check-comment-mask-corpus.mjs— exit 0node scripts/check-declaration-mirrors.mjs— exit 0node scripts/check-declaration-mirrors.mjs --self-test— exit 0node scripts/check-doc-route-spelling.mjs --advisory— exit 0node scripts/check-doc-route-spelling.mjs --self-test— exit 0node scripts/check-dts-emitted.mjs --self-test— exit 0node scripts/check-scripts-symbol-anchors.mjs— exit 0node scripts/check-scripts-symbol-anchors.mjs --self-test— exit 0node scripts/check-self-test-wired.mjs— exit 0node scripts/check-self-test-wired.mjs --self-test— exit 0node scripts/check-self-test-workflow-commands.mjs— exit 0node scripts/check-self-test-workflow-commands.mjs --self-test— exit 0node scripts/check-skills-token-ratchet.mjs— exit 0node scripts/check-skills-token-ratchet.mjs --self-test— exit 0node scripts/check-whole-set-label-write.mjs— exit 0node scripts/check-whole-set-label-write.mjs --self-test— exit 0node scripts/ci/assert-turbo-remote-cache.mjs --self-test— exit 0node scripts/ci/plan-test-core-shards.mjs --self-test— exit 0node scripts/ci/prune-turbo-cache.mjs --self-test— exit 0node scripts/pm/bare-root-worklist.mjs --self-test— exit 0pnpm --filter @objectstack/lint run check:doc-formula-expressions— exit 0pnpm --filter @objectstack/spec run check:skill-refs— exit 0pnpm check:agent-test-spelling— exit 0pnpm check:bash32-floor— exit 0pnpm check:cli-command-ids— exit 0pnpm check:corpus-claim-drift— exit 0pnpm check:cross-package-test-inputs— exit 0pnpm check:doc-authoring— exit 0pnpm check:driver-memory-census— exit 0pnpm check:dts-closure— exit 0pnpm 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 0pnpm check:gitlink-declared— exit 0pnpm check:lean-entry-closure— exit 0pnpm check:nul-bytes— exit 0pnpm check:parse-guard— exit 0pnpm check:pm-dispatch-gates— exit 0pnpm check:pm-governed-merges— exit 0pnpm check:pm-skill-id-lint— exit 0pnpm check:pnpm-filter-targets— exit 0pnpm check:pnpm-flag-spellings— exit 0pnpm check:ratchet-remedy-authority— exit 0pnpm check:refd-timer-probe— exit 0pnpm check:role-word— exit 0pnpm check:skill-compatibility— exit 0pnpm check:skill-frame-sync— exit 0pnpm check:skill-identifier-liveness— exit 0pnpm check:sourcemap-no-sources-content— exit 0pnpm check:watch-hint-literal— exit 0pnpm --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 noos:checkblock: the new section is prose with inline code).Human Minimum
Each item is one rule from CONTRIBUTING.md › Human Minimum.
packages/specholds no runtime logic. (No schema change.)pnpm lintis CI's run (repo-wide scan).main, one pull request.skip-changeset); both declaration lines are filled in above.packages/specnot touched.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Generated by Claude Code