Skip to content

docs(skills): objectstack-automation teaches the read-only record_link screen field, with an eval prompt - #26446

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

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

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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

维护者速读

  • 改了什么:skills/objectstack-automation/SKILL.md 的 screen 节点行(L91)教只读的屏幕字段 type: 'record_link':它显示 reference 对象里 id 等于 defaultValue 求值结果的那条记录(按显示名,链接到记录页),不收输入;reference 与 defaultValue 必填,required / options / placeholder / min / max 拒收,恢复时在它名下带值拒收(400 INVALID_SCREEN_INPUT);附一个结果屏示例(defaultValue: { dialect: 'cel', source: 'account.id' } 读 create_record 的 outputVariable),并点明两种会被拒或原样显示的替代写法。eval 文件加第 9 题(卡上原题)。棘轮脚本两行上限按实测调整,裁决原文写在行旁;「Filter operands」小节删去一句重复 item 1 的话。
  • 为什么改:feat(spec,automation,app-shell): a read-only record_link screen field shows a record the run created, by its display name, as a link to its record page #26355 已把 record_link 落进 @objectstack/spec,技能没教。AI 作者写「创建完成后显示新记录链接」的结果屏时,会去写 completed end 的 message(被 spec 拒收)或在屏幕 description 里写 markdown 链接(runner 原样显示成文字)。
  • 风险与回滚:只改已发布技能包的文字、一个 eval 夹具和棘轮脚本的两行上限,不改运行时;回滚即 revert 本 PR(上限行随之回落)。
  • 先看:① skills/objectstack-automation/SKILL.md L91 screen 行的新增子句;② scripts/check-skills-token-ratchet.mjs 两行上限及注释;③ evals/flows-triggers-approvals.json 第 9 题。
  • 两分钟验证:node scripts/check-skills-token-ratchet.mjs → 看到 skills/objectstack-automation/SKILL.md is 6164 tokens (ceiling 6164; headroom 0) 与 flows-triggers-approvals.json is 2141 tokens (ceiling 2141; headroom 0),末行 authored bundle file(s) within their ceilings。
  • 需要你做的:Tier H(skills/**),请 approve 或直接 Merge when ready。

What changes

Card #26347's 「What is owed」 section, and nothing wider:

  1. SKILL.md L91, the screen node row (the one home — the row already enumerates config.fields[] and teaches the file member, so an author writing a screen flow's result screen reads it): 'record_link' (never 'record', an object field type) takes no input — it shows the reference record whose id defaultValue evaluates to, by display name, as a link to its record page; the one example { name: 'account_link', type: 'record_link', reference: 'crm_account', defaultValue: { dialect: 'cel', source: 'account.id' } } over a create_record outputVariable: 'account'; both keys required; required / options / placeholder / min / max refused; a resume value under its name refused (400 INVALID_SCREEN_INPUT); a completed end message is refused and a markdown link in a screen description renders literally. Every clause was read from the merged code, not the card: packages/spec/src/automation/builtin-node-config.zod.ts (SCREEN_FIELD_RECORD_LINK_TYPE, …_REFERENCE_REQUIRED, …_DEFAULT_VALUE_REQUIRED, SCREEN_FIELD_RECORD_LINK_REFUSED_KEYS, the superRefine at the field, and EndConfigSchema's refusal of message on a completed end), screen-field-record-link.test.ts, packages/services/service-automation/src/builtin/screen-record-link-resume.test.ts (the account.id spelling over outputVariable: 'account', INVALID_SCREEN_INPUT), screen-input-contract.ts, the error-code ledger (400), and ui/packages/app-shell/src/views/FlowRunner.tsx (the description is a plain text child of the dialog description).
  2. evals/flows-triggers-approvals.json: entry 9, the card's prompt verbatim; the ideal answer names the screen flow, two create_record nodes with outputVariable, the last screen of two type: 'record_link' fields with reference and defaultValue: { dialect: 'cel', source: 'account.id' } / 'contact.id', no required / placeholder / options, no completed-end message, no markdown link in the description; assertions as the card gives them. The expected output is written so that it never spells either must_not_contain string (it says "never the object field type" instead of the negated spelling).
  3. SKILL.md L209–210, Filter operands — the one genuine duplicate cut: the bullet restated Valid-but-silently-wrong item 1's refusal detail (expression-invalid, "with its envelope"); the surviving home is item 1 at L233–235, which names the gate, the error id and the CEL spelling the refusal carries. −41 bytes.
  4. scripts/check-skills-token-ratchet.mjs, the two objectstack-automation rows only, 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 one above in SKILL.md; the eval file's eight fixtures share no sentence (0 bytes to cut). Step two, each row follows its measurement (ceil(utf8 bytes / 4), SKILL.md minus its 20-byte stamp line): SKILL.md priced bytes 24014 → 24654 (+681 added, −41 cut, +640 net), 6004 → 6164 (headroom was 2 bytes, 0 tokens; +160); evals/flows-triggers-approvals.json 7289 → 8561 bytes (+1272), 1823 → 2141 (headroom was 3 bytes, 0 tokens; +318). Both pinned AT the measurement, each comment quoting the tap. Rows measured against origin/main 75afeffd (skills(objectstack-platform): teach demoStaff and when to pick it over devLogins #26420 landed), the branch's base; no other package's row is touched.

references/_index.md is generator-owned (gen:skill-refs) and unchanged (no schema moved). references/examples-flows.md is untouched (the row held the teaching). SKILL.md line 1 is untouched (#25640 owns the stamp). #26362 (the approval-lock read) is the next card on this package and is not taught here.

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

File Lines before → after Tokens before → after
SKILL.md 441 → 441 (0) 6004 → 6164 (+160)
evals/flows-triggers-approvals.json (not .md, reported for the raise) 85 → 95 (+10) 1823 → 2141 (+318)
Whole package (SKILL.md + references/**/*.md, 4 files; the package has no rules/) 1001 → 1001 (0) 13931 → 14091 (+160)

Removed: the Filter operands restatement only.

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

Why

The key landed in @objectstack/spec with PR #26355 (4548ba34e6) and the skill did not teach it, so an AI author asked for a result screen reached for a completed end message (refused at parse) or a markdown link in the screen description (shown as literal text). Metadata-protocol: none: the protocol already carries the key; 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. The new teaching is a table cell (inline code), so no os:check block was added; the example count stays as on main.
  • Eval 9 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 expected output.

Acceptance notes

  • The dispatch order's "inside the package's ceilings" cannot hold at headroom 0 on both touched files; the seat's claim widened the surface to the touched ratchet rows under the ruling above.
  • evals/README.md, if it enumerates the eval topics, was not updated; not read, not filed: observation only.
  • The PR body's first two lines are the harness-required attribution block (the os-dev exception); Fixes #26347 follows immediately.

Verification

Gate list derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on the final tree (base 75afeffd, head c9e57c28), 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 c9e57c28.

Reconciliation (node scripts/pm/dispatch-gates.mjs --ran ran.list --repo objectstack-ai/objectstack): exit 0 — 53 derived famil(ies) accounted for — 52 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-26347, VERDICT command-exit 0, 276 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, never beside check:skill-examples. check:pm-dispatch-gates ran detached on the final head: fast tier, 1901 cases pass, exit 0. Two runs are outside the derivation and recorded anyway: check:skill-refs and check:skill-examples, the card's acceptance gates (the eval file and the row are the only skill edits; the example count stays at 264).

Every command with its exit code on c9e57c2 (55 lines: the 53 derived, plus `check:skill-refs` 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 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:entry-guard — exit 0
  • pnpm check:gitlink-declared — exit 0
  • pnpm check:merge-driver — exit 0
  • pnpm check:nul-bytes — exit 0
  • pnpm check:parse-guard — 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
  • pnpm --filter @objectstack/spec run check:skill-refs — exit 0
  • pnpm --filter @objectstack/spec run check:skill-examples — exit 0
  • pnpm --filter @objectstack/lint run check:doc-formula-expressions — exit 0
  • pnpm check:dts-closure — exit 0
  • pnpm check:lean-entry-closure — exit 0
  • pnpm check:dual-build-cjs-loads — NOT MEASURED (exit 3, PREREQUISITE NOT MET: whole-repo build; CI's Lint & Repo Gates runs it)
  • pnpm check:pm-dispatch-gates — exit 0
Token ratchet on c9e57c2
  • skills/objectstack-automation/SKILL.md is 6164 tokens (ceiling 6164; headroom 0)
  • skills/objectstack-automation/evals/flows-triggers-approvals.json is 2141 tokens (ceiling 2141; 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 c9e57c2

264 prose examples type-check across 3 surface(s) — every marked block parsed, so tsc ran the SEMANTIC pass on all of them (unchanged from main: the new teaching is a table cell, not an os:check block).

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

…nk` screen field, with an eval prompt

The `screen` node row of SKILL.md teaches `type: 'record_link'`: the field
shows the `reference` record whose id `defaultValue` evaluates to, as a link
to its record page, and takes no input — both keys required, the five input
keys refused, a resume value under its name refused — with the one
result-screen example over a `create_record` `outputVariable`, and the two
workarounds an author otherwise reaches for (a completed `end` `message`, a
markdown link in a screen `description`). One eval prompt (id 9) covers the
result screen. The Filter operands bullet's restatement of item 1's refusal
detail is cut; the two touched token-ratchet rows move by the measured
remainder under the maintainer's ruling A.

Claude-Session: https://claude.ai/code/session_01JQwxbYLKc8xxih9Em65LNW
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
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Reviewed at 2026-10-27T04:13Z by the independent contract-review subagent, on the PR's one commit, read adversarially against the card, the spec and the engine on origin/main (tip 26e7c75 at the time of reading), ruling A, and the head's check-runs — not the dispatch order's conclusions, not the seat's, not the dev's report.

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: c9e57c28c29d74c88d939d0f68b69d8da968e373
Local-runs: none
Scope: skills/objectstack-automation/SKILL.md, skills/objectstack-automation/evals/flows-triggers-approvals.json, scripts/check-skills-token-ratchet.mjs + packages/spec/src/automation/builtin-node-config.zod.ts (L810–853 and the ScreenFieldConfigSchema refinement L1065–1100, EndConfigSchema L1351–1413), packages/spec/src/automation/screen-field-record-link.test.ts, packages/services/service-automation/src/screen-input-contract.ts (validateScreenInputs, judgeHeadlessScreen), packages/services/service-automation/src/screen-input-record-link-field.test.ts, packages/services/service-automation/src/builtin/screen-record-link-resume.test.ts, packages/qa/dogfood/test/flow-screen-record-link.dogfood.test.ts, ui/packages/app-shell/src/views/FlowRunner.tsx L662, skills/objectstack-automation/references/** (line and token sums), .github/workflows/lint.yml and ci.yml (which job runs each gate), .claude/agents/os-dev.md and scripts/pm/clause2-line.mjs on the head, .github/PULL_REQUEST_TEMPLATE.md

Method: every reading is git show c9e57c28:PATH or git show origin/main:PATH (the shared checkout's HEAD was not read); the PR object, reviews, comments and check-runs were read over REST GET. The token arithmetic below is the script's own hand-audit formula from its header (ceil((wc -c − stamp line) / 4), every other file ceil(wc -c / 4)) applied to the blobs — not a run of the gate; CI's Type Check · source gates is the gate verdict. Base: the PR's merge-base with main is 75afeff (one commit, +40/−5 over 3 files, identical whether diffed against 75afeff or against the origin/main tip 26e7c75 — the extra ConsoleToaster paths in a tip diff are main's #26426, not this PR's).

① Derived judgments

(a) Every new sentence is true of the spec and the engine on origin/main. The rewritten screen row (SKILL.md L91, the only teaching hunk; lines 1–90 and 92–207 are byte-identical to the base):

  • 'record_link' (never 'record', an object field type) — RIGHT: SCREEN_FIELD_RECORD_LINK_TYPE = 'record_link' with the doc comment giving exactly that reason (FieldType already spells 'record' for embedded sub-objects), builtin-node-config.zod.ts L810–816; the test's first case pins the spelling.
  • "takes no input — it shows the reference record whose id defaultValue evaluates to, by display name, as a link to its record page" — RIGHT, near-verbatim from the type .describe() text at L973–977 and the doc comment at L964–971 (display name = ADR-0079 nameField).
  • "the result screen of a flow that created records" — RIGHT: the doc comment's last sentence, and the engine's resume test (convertFlow: two create_record nodes, then the screen).
  • the example { name: 'account_link', type: 'record_link', reference: 'crm_account', defaultValue: { dialect: 'cel', source: 'account.id' } } over a create_record outputVariable: 'account' — RIGHT and the spelling the resume actually resolves: screen-record-link-resume.test.ts declares outputVariable: 'account' and defaultValue: CEL('account.id'), and asserts the paused screen serves defaultValue: 'crm_account_1'; the dogfood fixture uses the same shape. (The spec's own refusal text example is source: 'account_id', a bare flow variable — also legal, a different flow shape; the row's choice matches the card's and the engine test's.)
  • "Both keys required" (reference, defaultValue) — RIGHT: the superRefine branch at L1079–1092 adds SCREEN_FIELD_RECORD_LINK_REFERENCE_REQUIRED for a missing/blank reference and …_DEFAULT_VALUE_REQUIRED for undefined/null defaultValue; both pinned in the spec test.
  • "required / options / placeholder / min / max refused" — RIGHT: SCREEN_FIELD_RECORD_LINK_REFUSED_KEYS is exactly that five-tuple (L844, pinned by toEqual in the test), each refused at its own key (required: false included, since the key itself is the error).
  • "a resume value under its name refused (400 INVALID_SCREEN_INPUT)" — RIGHT: validateScreenInputs refuses ANY value under a record_link field's name, null and hidden included, with invalid_value (screen-input-contract.ts L173–184 and the record-link-field test); the engine answers code INVALID_SCREEN_INPUT (resume test, refusal keeps the pause and binds nothing); the dogfood test asserts HTTP status 400 with that code (flow-screen-record-link.dogfood.test.ts L110–115). The row does not teach judgeHeadlessScreen (a caller param never counts a link as answered) — not owed by the card, not a gap an author needs.
  • "A completed end message is refused" — RIGHT: EndConfigSchema (L1351, L1413: "message is only rendered when outcome is 'refused' — on a completed end nothing shows it, so the key …" is refused).
  • "a markdown link in a screen description renders literally" — RIGHT: FlowRunner.tsx L662 renders shown.description as a plain text child of DialogDescription; no markdown pass anywhere in the file.
  • lookup contrast: the row (and the whole SKILL.md) never teaches lookup, so no contrast clause was owed; none drawn.

(b) The one cut is a genuine duplicate with a surviving home. Filter operands bullet, L209–210: "every other {…} token is refused (expression-invalid) with its envelope" → "every other {…} token is refused". Surviving home, same file, Valid-but-silently-wrong item 1 (head L228–236): "… every CRUD filter operand, a string is the literal text it spells, and a {…} token is refused — at objectstack validate (expression-invalid), registerFlow and the executor — naming its CEL spelling. Compute with { dialect: 'cel', source: '…' }". Item 1 names the filter operand surface explicitly, the error id expression-invalid, the two further refusal sites, and the CEL envelope the refusal names — every clause of the cut survives and the home says MORE than the cut did. The surviving filter bullet still carries what is specific to it (the whole-string query-placeholder pass-through, the no-shadowing rule, "only filter gets this hand-off"). No rule lost. Cosmetic only: the cut left L210 short ("never shadows a placeholder. Only") — a re-wrap would have bought no tokens and was rightly not done.

(c) Ratchet rows pinned at the measurement; ruling A applied and quoted. Hand audit on the blobs: SKILL.md head 24674 bytes, stamp line L16 version: "18.1.0" 20 bytes → priced 24654 → ceil(24654/4) = 6164 = the row (headroom 2 bytes, 0 tokens); base 24034 − 20 = 24014 → 6004 = main's row (headroom 2 bytes, 0 tokens) — the file was at zero headroom, so ruling A applies. Per-hunk bytes: L91 399 → 1080 (+681), the filter bullet 230 → 189 (−41), net +640 = 24674 − 24034 — the row comment's and the body's arithmetic (+681/−41/+640, 24014 → 24654) is exact. Eval file 7289 → 8561 bytes (+1272) → ceil(8561/4) = 2141 = the row (base 1823, headroom 3 bytes, 0 tokens). Neither row is above its measurement (no smuggled headroom), neither below (no red gate). The tap 「A:先削重复,余额按实测提高」 is quoted verbatim and untranslated in both row comments and in the PR body (What changes item 4 and 维护者速读); the script's header requires the ruling in "a PR whose body quotes a maintainer ruling" — met. Sibling rows: git diff origin/main c9e57c28 -- scripts/check-skills-token-ratchet.mjs touches ONLY the two objectstack-automation rows and their comments; every other row on the head equals origin/main NOW, so nothing is stale against #26420 or any later sibling. The new comment blocks sit inside the CEILINGS map like every other row comment (the remedy-authority anchor is the SHRINK-ONLY doc comment above the map, untouched). Lane: Lint · ESLint success on the head (long comment lines are not capped); Type Check · source gates (job typecheck-source-gates, lint.yml L5419–5420: ratchet self-test + ratchet; L5156 check:skill-docs; L5288 check:skill-refs) success at 2026-10-27T04:07Z.

(d) Eval 9. The file parses; ids 1–9 unique and sequential; entry 9 has the same key set as entry 8 (id, prompt, expected_output, files, assertions). The prompt is byte-for-byte the card's. All five must_contain strings occur in the expected output; neither must_not_contain string (type: 'record', ]() occurs in the prompt or the expected output — type: 'record_link' does not contain type: 'record' (the quote closes after _link), and the ideal answer says "never the object field type" instead of spelling the negation. The expected output is consistent with the row: screen flow, two create_record nodes with outputVariable, the contact's fields linking via a CEL envelope (a legal value slot per item 1 and the engine test's audit node), a last screen of two record_link fields with reference and defaultValue envelopes, no required/placeholder/options, no completed-end message, no markdown link. The literal ]( is a plain JSON string. No README enumerates this package's evals (skills/objectstack-automation/evals/README.md does not exist on the head; only objectstack-data and objectstack-ui have one), so nothing was owed there — the dev's "if it enumerates" observation is moot.

(e) Card Wanted met, nothing beyond. Teach: shape ✓, two required keys ✓, five refused keys ✓, resume refusal ✓, record_link not record ✓, one example ✓. The two "workaround" clauses are inside the card's scope (its Reach paragraph names them and its eval asserts both), not an extension. Eval entry ✓. One home (the row), no references/examples-flows.md fragment ✓. references/_index.md, examples-flows.md, state-machines-and-approvals.md byte-identical to the base ✓. Line 1 and the stamp line of SKILL.md untouched ✓. File list = exactly the three files of the claim surface ✓. The teaching is one table cell, no new os:check block — the small-feature-written-small default holds (441 → 441 lines).

(f) PR body. Line 1 the harness attribution HTML comment, line 2 the harness _Requested by_ line, line 3 Fixes #26347, then Clause-②: no and Metadata-protocol: none line-initial; clause2-line.mjs reads both keys per line (CLAUSE2_KEY_LINE, METADATA_PROTOCOL_KEY_LINE, not anchored to line 1), and the closing-keyword check-runs (The card this PR closes must claim this branch, Part-of PR must not also close its card, No other open PR may claim the same issue) all succeeded. os-dev.md L323 exempts the harness-written first-line attribution comment with Fixes "紧随其后"; the harness now writes two lines, so Fixes is line 3 — reported by the dev as deviation 1; the parsers do not care (precision note for the seat, below). No Skills-docs: line owed (Metadata-protocol: none, os-dev.md L279) and none written ✓. 维护者速读: the six labels in the required order; 先看 = 3 files naming the risky hunk; 两分钟验证 = one command with the expected sight, and the sight matches the script's print format exactly (X is N tokens (ceiling N; headroom 0). L1066; authored bundle file(s) within their ceilings L1154); 需要你做的 = one action (approve / Merge when ready, as the order allowed for Tier H). English sections What changes / Why / Tests / Acceptance notes / Verification with readings in details blocks. Two readings: SKILL.md 441 → 441 ✓ (recomputed); eval 85 → 95 ✓; package = SKILL.md 441 + _index.md 46 + examples-flows.md 134 + state-machines-and-approvals.md 380 = 1001 → 1001 ✓; tokens 6004 + 919 + 1421 + 5587 = 13931 → 6164 + 919 + 1421 + 5587 = 14091 ✓. No model name in the commit, body or report ✓.

(g) Report shape and claim. os-dev-report 6433471282 (2026-10-27T04:05Z) carries every key of the os-dev.md template on the head plus deviations (5), line_budget and body_amendments as the dispatch order required; the template on the head lists neither files_changed nor rules_retired, so none is owed. Branch claude/project-thread-opn3zo = the claim's Branch: line (6433203119) = the PR head ref ✓. mcp_calls: 0, api_writes: 2 (relay pr_create, report comment) — consistent with the PR object (assignee os-warren, label skip-changeset written at pr_create) and no other write visible on the PR (0 reviews, 0 comments at my read).

② Semver level

skip-changeset is right: skills/objectstack-automation has no package.json on the head (git ls-tree), scripts/** publishes nothing; the diff touches no packages/**. Check Changeset success. Clause-②: no and Metadata-protocol: none line-initial ✓.

③ Boundary flags

  • The dev's one observation (evals/README.md) — nothing: the file does not exist in this package.
  • Precision (not a card, a 收班简报 note): os-dev.md L323 words the harness exemption as a one-line attribution comment with Fixes immediately after; the harness writes two lines (comment + _Requested by_), so every PR of this wave has Fixes on line 3. Dedupe words: "harness attribution two lines Requested-by Fixes line 3".
  • Precision (note only): the spec's SCREEN_FIELD_RECORD_LINK_DEFAULT_VALUE_REQUIRED text shows source: 'account_id' while the skill, the engine test and the dogfood fixture teach source: 'account.id' over an outputVariable; both are legal, the skill's is the one the tests resolve. Nothing to change in this PR.
  • Base reconciliation: PR base.sha 26e7c75 = the origin/main tip; merge-base 75afeff; mergeable: true, mergeable_state: blocked (draft, Tier H approval pending).
  • Check-runs on c9e57c2, read at 2026-10-27T04:11Z: 45 runs — 28 success, 16 skipped, 1 in progress (Test Core (1/8)); none failed. Gate lanes this PR turns on: Type Check · source gates success (ratchet + self-test, check:skill-docs, check:skill-refs), Type Check · consumer gates success (check:skill-examples), Lint · ESLint, Lint · repo & tooling gates, Lint · docs & code guards, Lint · PM dispatch-gates, Lint · CI, release & engine gates, all scoped lint lanes success; Build Core success (ci.yml job build-core hosts check:dual-build-cjs-loads, the dev's one NOT-MEASURED family); Governed Surface Queue Guard, Check Changeset, Check PR Size, Dogfood Regression Gate success. Landing state at that read: draft, 0 reviews, 0 comments, requested reviewers hotlong and os-zhuang, no approval yet.

Implemented-by: claude/project-thread-opn3zo
Reviewed-by: session_01LqKsGyMNFwwnbF5FTAqyPn (independent contract-review subagent)

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读

改了什么:objectstack-automation 技能的 screen 节点行(SKILL.md L91)教只读屏幕字段 type: 'record_link'——显示 reference 对象里 id 等于 defaultValue 求值结果的那条记录(按显示名,链到记录页),不收输入;reference 与 defaultValue 必填,required/options/placeholder/min/max 拒收,恢复时在它名下带值拒收(400 INVALID_SCREEN_INPUT);一个结果屏示例(defaultValue: { dialect: 'cel', source: 'account.id' } 读 create_record 的 outputVariable: 'account');点名两种会被拒或原样显示的替代写法。eval 第 9 题(卡上原题)。按你的裁决 A:同文件削一处真重复(Filter operands 段重述第 1 项的拒收细节,−41 B,第 1 项保留全文),两行天花板按实测改(SKILL.md 6004 → 6164,eval 1823 → 2141)。
为什么改:#26355 把 record_link 落进了 spec,技能没教;AI 作者写「创建完成后显示新记录链接」的结果屏时,会写 completed end 的 message(spec 拒收)或在屏幕 description 里写 markdown 链接(runner 原样显示成文字)。
风险与回滚:只改技能 markdown 一行、eval JSON 一题和棘轮两行数字;不发版、无运行时改动;回滚 = revert 本 PR(一个提交)。风险在教学措辞是否属实——复核员逐句对着 builtin-node-config.zod.ts L810–1092、validateScreenInputs、EndConfigSchema 与 FlowRunner 核过,全部为真。
先看:① skills/objectstack-automation/SKILL.md L91 screen 行新增子句;② 同文件 L209–210 的一处削减(expression-invalid 与 envelope 的重述);③ scripts/check-skills-token-ratchet.mjs 两行(6164 / 2141,各钉在实测值)。
两分钟验证:node scripts/check-skills-token-ratchet.mjs 预期全绿,两文件各印 headroom 0;pnpm --filter @objectstack/spec run check:skill-examples 预期 264 个示例通过。
需要你做的:Approve(Tier H,skills/** 是发布面)——本 PR 至今没有任何批准,是本波第一张等你或 os-zhuang 的手的卡;批后席位翻 ready、布自动合并。
席位意见:同意落地。两条不挡本 PR 的备注:spec 的 …DEFAULT_VALUE_REQUIRED 提示文写 source: 'account_id',技能、引擎测试与 dogfood 夹具都用 'account.id'(两者合法,提示文可统一);#26362(approval lock 读)是同包下一张卡,正确地没有混进来。

席位核对

ACCEPT · domain:skills#2 · session_01LqKsGyMNFwwnbF5FTAqyPn · 2026-10-27T04:16Z — seat review of record for #26347 on head c9e57c28, 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, 45 changed lines): the record of this tier is the independent ## Contract review at comment 6433585723 (Served-tier: CONTRACT_REVIEW_TIER, head c9e57c28, VERDICT: PASS, Local-runs: none; drafted by a reviewer other than the implementer, the card thread's dev). This seat adds the landing read.

  1. Shape. Draft, base main, mergeable: true (clean), one commit; body lines 1–2 the harness attribution block, line 3 Fixes #26347, 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: skills/objectstack-automation/SKILL.md (+3/−3), evals/flows-triggers-approvals.json (+10), scripts/check-skills-token-ratchet.mjs (+27/−2, two rows) — the claim's surface exactly (claim of 2026-10-27T03:44Z: one home for the teaching, the package's eval file, the package's CEILINGS rows under ruling A); SKILL.md line 1 untouched. Labels documentation, size/s, skip-changeset — right, no package.json under skills/. Branch claude/project-thread-opn3zo, named by the claim. Reviewers hotlong + os-zhuang requested by the relay at pr_create; no review yet.
  2. Record, measured. The reviewer read every new clause against origin/main: SCREEN_FIELD_RECORD_LINK_TYPE and the 「never record」 rationale (builtin-node-config.zod.ts L810–816), the describe text (L973–977), the superRefine with the two required keys and the exact five-key refused tuple (L1079–1092), validateScreenInputs refusing any value under the field's name (incl. null/hidden, invalid_value, engine code INVALID_SCREEN_INPUT, dogfood asserts HTTP 400), account.id over outputVariable: 'account' as the spelling the resume test resolves, EndConfigSchema refusing message on a completed end (L1351/1413), FlowRunner L662 rendering description as plain text. The one cut is a genuine duplicate (item 1, head L228–236, carries every CRUD filter operand, expression-invalid, the refusal sites and the CEL envelope). Ratchet rows recomputed with the script's measure: SKILL.md 24674 B − 20 → 6164 = row (base 24014 B → 6004, +640 B exact); eval 8561 B → 2141 = row (base 7289 B); both pinned at zero headroom, tap quoted in both row comments and the body; the script differs from origin/main only by these two rows. Eval 9: ids 1–9 sequential, prompt byte-identical to the card, all must_contain present, neither must_not_contain string in prompt or expected. Readings exact (SKILL.md 441 → 441 lines, eval 85 → 95, package 1001 → 1001 lines / 13931 → 14091 tokens). The seat re-read the diff and the body and reads the same.
  3. Gates, pinned to head c9e57c28. 46 check-runs, 30 success and 16 skipped, none red or pending (reviewer's read 2026-10-27T04:14Z; the seat read mergeable_state: clean after). Token ratchet + self-test, check:skill-docs, check:skill-refs 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) — 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 (os-dev.md's exemption; parsers read either). (b) Branch is the thread's claude/project-thread-opn3zo, as the claim names it. (c) The MCP assignee/reviewer step replaced by the relay's pr_create (0 MCP calls). (d) check:dual-build-cjs-loads NOT-MEASURED locally; Build Core green is the reading. (e) PR-template headings folded into the os-dev body order. (f) The dev's one out-of-scope note (evals/README.md) is moot — no such file in this package.
  5. Carrier. Nothing to file. 收班简报: the spec's SCREEN_FIELD_RECORD_LINK_DEFAULT_VALUE_REQUIRED message spells source: 'account_id' while the skill, the engine test and the dogfood fixture use 'account.id' over an outputVariable (both legal; one spelling would be kinder); SKILL.md L210 is a short wrapped line after the cut (cosmetic, a re-wrap buys no tokens at headroom 0); os-dev.md L323's one-line attribution wording vs the harness's two lines (already on the brief's list from skills(objectstack-ui): teach list-view grouping and orderBy, the duration measure, action copy holes, and a flow action's onSuccess #26385).
  6. Landing. Tier H ⇒ this record and the contract review are on the head; the PR waits for a GOVERNED_APPROVERS approval (the relay's summons at create went to hotlong + os-zhuang). On that approval the seat readies it and arms auto-merge through the relay, follows it to MERGED, closes skills(objectstack-automation): teach the read-only record_link screen field, with an eval prompt (#26304 follow-up, #26184) #26347 with the landing record, and dispatches skills(automation): teach the record-anchored approval lock read (GET /approvals/records/:object/:recordId/lock, { locked } for every reader) with one eval prompt #26362 (same package, serial).

Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review October 27, 2026 04:36
@os-zhuang
os-zhuang enabled auto-merge October 27, 2026 04:36
@os-zhuang
os-zhuang added this pull request to the merge queue Oct 27, 2026
Merged via the queue into main with commit d1a31d0 Oct 27, 2026
47 checks passed
@os-zhuang
os-zhuang deleted the claude/project-thread-opn3zo branch October 27, 2026 05:07
os-warren pushed a commit that referenced this pull request Oct 27, 2026
…ide #26446)

PR #26448 went dirty after #26446 (d1a31d0) raised the objectstack-automation
rows of scripts/check-skills-token-ratchet.mjs beside the objectstack-platform
rows this branch re-pins. Resolved by keeping both sides: main's sibling rows
and this card's rows; nothing else conflicted.

check:skills-token-ratchet exit 0, --self-test exit 0 (79 cases).

Claude-Session: https://claude.ai/code/session_01JQwxbX1XjSzpntF9n85TgB
Co-authored-by: Claude <noreply@anthropic.com>
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

Development

Successfully merging this pull request may close these issues.

skills(objectstack-automation): teach the read-only record_link screen field, with an eval prompt (#26304 follow-up, #26184)

2 participants