Repository navigation
Commit d1a31d0
docs(skills): objectstack-automation teaches the read-only
<!-- claude-projects-attribution: {"github_login":"os-warren"} -->
_Requested by **Warren** · [project
thread](https://claude.ai/code/project/chan_01U5RdnAAmkSQmZbqh6FtqZ8?thread=cmsg_01U5RdnAAmkSQmZbqh6FtqZ8W2xMZ3uXnQ8MwyyurDKART)_
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 的话。
- **为什么改**:#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` (#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).
<details><summary>Every command with its exit code on c9e57c2 (55
lines: the 53 derived, plus `check:skill-refs` and
`check:skill-examples`)</summary>
- `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
</details>
<details><summary>Token ratchet on c9e57c2</summary>
- `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.`
</details>
<details><summary>check:skill-examples on c9e57c2</summary>
`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).
</details>
## Human Minimum
Each item is one rule from [CONTRIBUTING.md › Human
Minimum](https://github.com/objectstack-ai/objectstack/blob/main/CONTRIBUTING.md#human-minimum).
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(No schema change.)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` is CI's run (repo-wide scan).
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. Nothing publishes (`skip-changeset`); both declaration lines
are filled in above.
- [x] 6. `packages/spec` not touched.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English (the 维护者速读 block and
the quoted ruling are the required exceptions).
---
_Generated by [Claude
Code](https://claude.ai/code/session_01JQwxbYLKc8xxih9Em65LNW)_
Co-authored-by: os-warren <warren@objectstack.ai>
Co-authored-by: Claude <noreply@anthropic.com>record_link screen field, with an eval prompt (#26446)1 parent d0852dd commit d1a31d0
3 files changed
Lines changed: 40 additions & 5 deletions
File tree
- scripts
- skills/objectstack-automation
- evals
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
391 | 391 | | |
392 | 392 | | |
393 | 393 | | |
394 | | - | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
395 | 411 | | |
396 | 412 | | |
397 | 413 | | |
| |||
765 | 781 | | |
766 | 782 | | |
767 | 783 | | |
768 | | - | |
| 784 | + | |
| 785 | + | |
| 786 | + | |
| 787 | + | |
| 788 | + | |
| 789 | + | |
| 790 | + | |
| 791 | + | |
| 792 | + | |
| 793 | + | |
769 | 794 | | |
770 | 795 | | |
771 | 796 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
88 | 88 | | |
89 | 89 | | |
90 | 90 | | |
91 | | - | |
| 91 | + | |
92 | 92 | | |
93 | 93 | | |
94 | 94 | | |
| |||
206 | 206 | | |
207 | 207 | | |
208 | 208 | | |
209 | | - | |
210 | | - | |
| 209 | + | |
| 210 | + | |
211 | 211 | | |
212 | 212 | | |
213 | 213 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
80 | 80 | | |
81 | 81 | | |
82 | 82 | | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
83 | 93 | | |
84 | 94 | | |
85 | 95 | | |
0 commit comments