Repository navigation
Commit 96f1258
docs(skills): objectstack-automation teaches the emailField recipient, flow requiredPermissions, excludeSubmitter and the screen file field (#26390)
<!-- claude-projects-attribution: {"github_login":"os-warren"} -->
_Requested by **Warren** · [project
thread](https://claude.ai/code/project/chan_01U5RdnAAmkSQmZbqh6FtqZ8?thread=cmsg_01U5RdnAAmkSQmZbqh6FtqZ8GtSu7285zjAYqM8VgQB5Gr)_
Fixes #26298
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:objectstack-automation 技能包补上四条审计缺口 —— `notify` 收件人的第四种写法 `{
emailField }`(给非用户的申请人发邮件;CEL 信封算出的地址若无用户持有,节点运行时失败)、流程级
`requiredPermissions`(谁能启动流程)、审批节点 `excludeSubmitter`(提交人不能批自己的单;与
`auto_approve` 并写被拒)、屏幕字段 `type: 'file'`(续跑拿到 `sys_file`
id,后续写入记录即认领文件);每条配一条 eval。
- 为什么改:这四个键在 spec 里已存在,技能包零命中;AI 作者照现有文本写出的 `recipients` 信封现在会在运行时失败,不写
`excludeSubmitter` 则提交人可自批。
- 风险与回滚:只改已发布技能文本与 eval,不改运行时;token 天花板三行按实测余额上调(你 2026-10-27 的裁决 A)。回滚
= revert 本 PR。
- 先看:`skills/objectstack-automation/SKILL.md`(`notify` 代码块与
`requiredPermissions`
引言段)、`references/state-machines-and-approvals.md`(配置表新行、删掉的两条最佳实践)、`scripts/check-skills-token-ratchet.mjs`(三行上限)。
- 两分钟验证:`node scripts/check-skills-token-ratchet.mjs` —— 预期末行 `81
authored bundle file(s) within their ceilings`,automation 三行 headroom 0。
- 需要你做的:批准(Approve)或「Merge when ready」—— `skills/**` 是 Tier H,席位不入队。
## What changes
Four rows from the skills gap audit (#26184, Part A §
objectstack-automation, rows A1–A4), each taught in the fewest lines an
AI author needs:
| Row | Where | Teaching |
|:--|:--|:--|
| A3 `{ emailField }` | `SKILL.md` `notify` block, the `recipients` line
| the fourth recipient form `{ emailField: 'applicant_email' }` (an
`email`-typed field of the trigger record, for a non-user), `channels:
'email'` alone beside it, and that a CEL envelope must yield user ids —
an address no user holds fails the node
(`packages/spec/src/automation/io-node-config.zod.ts`
`NotifyEmailFieldRecipientSchema`, the `recipients` describe and the
`channels` refinement) |
| A1 `requiredPermissions` | `SKILL.md`, a blockquote after *Elevate the
write, not the flow* | flow-level `requiredPermissions` is checked at
every door that starts the flow by name; an action starting it inherits
the list; a `subflow` node is not a door; gate an elevated `screen` /
`api` flow here, never on `current_user.positions` (`flow.zod.ts`
`requiredPermissions`, ADR-0066 D4) |
| A4 screen `file` field | `SKILL.md` node table, the `screen` row |
`config.fields[]` shape; `type: 'file'` renders the upload widget and
resumes with the `sys_file` id bound to `name`; a later `create_record`
/ `update_record` writes it into a `file` field, which claims the file —
collect first, write after the last screen (`builtin-node-config.zod.ts`
`ScreenFieldConfigSchema.type`) |
| A2 `excludeSubmitter` | `references/state-machines-and-approvals.md`,
the Node Config table | drops the run's submitter from the resolved
slate; an emptied slate takes the `onEmptyApprovers` route;
`onEmptyApprovers: 'auto_approve'` is refused beside it
(`approval.zod.ts` `excludeSubmitter`,
`checkExcludeSubmitterPolicyPairing`) |
Three eval prompts in `evals/flows-triggers-approvals.json`, in the
file's existing shape: #6 the card's acceptance eval (email the
applicant → `recipients: { emailField: … }`), #7 `requiredPermissions` +
`excludeSubmitter` on one approval, #8 the screen `file` field written
by a later `create_record`.
**Paid down first (surviving homes named):**
- `SKILL.md` CRM Blueprint, the *Approval flow* row restated `approvers`
/ `behavior` / `lockRecord` / `approvalStatusField` and the `approve` /
`reject` branching — cut to a pointer; the surviving home is
`references/state-machines-and-approvals.md` § Node Config
(`ApprovalNodeConfigSchema`) and § Branching, side-effects & rejection.
- `references/state-machines-and-approvals.md` Approval Best Practices 1
(*Gate entry on the edge*) and 4 (*Model rejection as a visible branch*)
restated § Branching's *Conditional step*, *Roll back on reject* and
*Hard reject* bullets and the section lead "wired on the graph, not in
node config" — deleted; items 2, 3, 5 (each carrying a rationale found
nowhere else) stay, renumbered.
- Nothing else is removed. `SKILL.md` line 1 (the `version:` stamp PR
#25640 owns) is untouched.
**Token ratchet (`scripts/check-skills-token-ratchet.mjs`), the measured
remainder per file, under the maintainer's ruling.** The decision card
asked 「skills 修复卡的教学和 eval 都撞 token 天花板,怎么付?」; Warren's tap, 2026-10-27,
verbatim: 「A:先削重复,余额按实测提高」.
| File | lines before → after | tokens before → after | ceiling before →
after |
|:--|:--|:--|:--|
| `skills/objectstack-automation/SKILL.md` | 432 → 441 | 5771 → 6004
(+233 net; ~17 paid by the Blueprint cut; 9 absorbed by headroom) | 5780
→ 6004 (+224) |
|
`skills/objectstack-automation/references/state-machines-and-approvals.md`
| 383 → 380 | 5555 → 5587 (+32 net; ~73 paid by the two deleted
practices; 14 absorbed) | 5569 → 5587 (+18) |
| `skills/objectstack-automation/evals/flows-triggers-approvals.json` |
55 → 85 | 1255 → 1823 (+568; nothing in the file to delete) | 1255 →
1823 (+568) |
| `references/examples-flows.md`, `references/_index.md` (generated) |
134, 46 (unchanged) | 1421, 919 (unchanged) | — |
| **Whole package** (`SKILL.md` + `references/**/*.md` +
`rules/**/*.md`) | **995 → 1001** | **13666 → 13931** (authored 12747 →
13012) | — |
## Why
The audit measured 0 hits in the package for `requiredPermissions`,
`excludeSubmitter` and `emailField`; `SKILL.md`'s `recipients` comment
read "id, list, or envelope", which since `d1e329e96d` sends an author
mailing an applicant into a run-time failure; the approval config table
had no `excludeSubmitter` row, so a submitter could approve their own
request; screen field types were not enumerated. The premise was re-read
on `origin/main` at `756220c4` before the first edit and holds (the
seat's read-only check of 2026-10-27 00:36Z, re-measured here: `grep
-rn` of the three keys over `skills/objectstack-automation/` → 0 hits;
the screen `file` type's spec location is `ScreenFieldConfigSchema.type`
in `packages/spec/src/automation/builtin-node-config.zod.ts`, which the
seat had not located).
## Tests
No runtime code changes; the eval file parses (`python3 -c json.load` →
ok) and the package gates below read the new text. The three eval
prompts are the test surface for the teachings.
## Acceptance notes
- `check:skill-examples` has three surfaces; the skills + docs surface
is the one this PR touches. Its verdict needs `packages/spec`,
`@objectstack/client` and `@objectstack/client-react` built first (it
refuses with exit 3, `PREREQUISITE NOT MET`, otherwise) — see
Verification for the run against the built tree.
- Six commits the card cites (`49653f9181`, `4bcc4df649`, `6021b5e837`,
`d1e329e96d`, `6d9d3ef857`, `f1ddace887`) are outside this container's
shallow clone (`git cat-file -e` → missing); the spec text on `main` was
read directly instead, so the teachings quote what `main` holds, not the
commits.
- No out-of-scope findings: nothing reproducible, no contract breach, no
new authoring trap beyond the four the card names.
## Verification
Gate list derived with `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` on the final tree (`83b5ec5e`), 54
commands, every exit code captured before any pipe, reconciled with
`--ran`.
<details><summary>Derived gates — 53 commands, 52 run green, 1 NOT
MEASURED (<code>--ran</code> exit 0)</summary>
- `node scripts/pm/dispatch-gates.mjs --ran ran.list --repo
objectstack-ai/objectstack` → `53 derived famil(ies) accounted for — 52
run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)`, exit 0; change
set `4 path(s) vs merge base 756220c`, `changed lines: 85 (+72 /
-13)`.
- NOT MEASURED: `pnpm check:dual-build-cjs-loads` → exit 3,
`PREREQUISITE NOT MET — some package has no dist/` (33 packages unbuilt
in this container; a repo-wide build is CI's run). CI's `Lint & Repo
Gates` carries it.
- Two gates raced this thread's own concurrent builds in the batch and
were re-run alone, the re-run codes recorded: `node
scripts/check-comment-mask-corpus.mjs` (first run `ENOENT …
packages/spec/.examples-build/…`, a transient dir the concurrent
`check:skill-examples` run was materialising; re-run exit 0, `18308
files, 0 disagree, 0 unparseable`); `pnpm check:dts-closure` (first run
while `@objectstack/client-react` was mid-build under the verify lock;
re-run exit 0, `38 built package(s) swept - 160/160 declared declaration
file(s) present`).
- `node scripts/check-skills-token-ratchet.mjs` exit 0 — `81 authored
bundle file(s) within their ceilings`; automation rows `6004 / 6004
(+0)`, `1823 / 1823 (+0)`, `5587 / 5587 (+0)`; `--self-test` exit 0, `79
cases pass`.
- `pnpm check:pm-dispatch-gates` exit 0, `pnpm check:pm-governed-merges`
exit 0, `pnpm check:ratchet-remedy-authority` exit 0 (all in the batch).
</details>
<details><summary>The card's three package gates and the liveness
gate</summary>
- `pnpm --filter @objectstack/spec check:skill-docs` exit 0 — `✅ Skill
docs in sync`.
- `pnpm --filter @objectstack/spec check:skill-refs` exit 0 — `✅ 9
generated files in sync with packages/spec` (`references/_index.md`
unchanged, nothing to regenerate).
- `pnpm --filter @objectstack/spec check:skill-examples` exit 0 after
`pnpm --filter @objectstack/spec build` (lock `VERDICT command-exit 0`,
88 s) and `pnpm --filter "@objectstack/client-react..." build` (`VERDICT
command-exit 0`, 304 s) — `✅ 264 prose examples type-check across 3
surface(s)`; the two earlier runs on an unbuilt tree exited 3
(`PREREQUISITE NOT MET`), not findings.
- `pnpm check:skill-identifier-liveness` exit 0 — `Leg 1: 491
citation(s) over 69 published file(s) … Leg 2: 8 registered exhaustive
section(s), 0 ledgered gap(s)`.
- Not run locally (CI-owned, not derived): `pnpm lint`, `test:repo`, the
full battery.
</details>
## 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. (no new names;
keys quoted as the spec spells them)
- [x] 3. No formatter ran; `pnpm lint` is CI's run (repo-level scan, not
owed locally per `.claude/agents/os-dev.md`).
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. Nothing publishes from a released package (`skills/**` carries
no `package.json`); `skip-changeset`, and both declaration lines are
above.
- [x] 6. `packages/spec` untouched.
- [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 repo's own exceptions).
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01JQwxbXsLdroTYaf1DRqVUG)_
Co-authored-by: objectstack-fleet[bot] <objectstack-fleet[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>1 parent 56b0a58 commit 96f1258
4 files changed
Lines changed: 72 additions & 13 deletions
File tree
- scripts
- skills/objectstack-automation
- evals
- references
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
381 | 381 | | |
382 | 382 | | |
383 | 383 | | |
384 | | - | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
385 | 395 | | |
386 | 396 | | |
387 | 397 | | |
| |||
701 | 711 | | |
702 | 712 | | |
703 | 713 | | |
704 | | - | |
| 714 | + | |
| 715 | + | |
| 716 | + | |
| 717 | + | |
| 718 | + | |
| 719 | + | |
| 720 | + | |
705 | 721 | | |
706 | 722 | | |
707 | 723 | | |
| |||
745 | 761 | | |
746 | 762 | | |
747 | 763 | | |
748 | | - | |
| 764 | + | |
| 765 | + | |
| 766 | + | |
| 767 | + | |
| 768 | + | |
| 769 | + | |
| 770 | + | |
| 771 | + | |
749 | 772 | | |
750 | 773 | | |
751 | 774 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
88 | 88 | | |
89 | 89 | | |
90 | 90 | | |
91 | | - | |
| 91 | + | |
92 | 92 | | |
93 | 93 | | |
94 | 94 | | |
| |||
104 | 104 | | |
105 | 105 | | |
106 | 106 | | |
107 | | - | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
108 | 110 | | |
109 | 111 | | |
110 | 112 | | |
| |||
182 | 184 | | |
183 | 185 | | |
184 | 186 | | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
185 | 194 | | |
186 | 195 | | |
187 | 196 | | |
| |||
399 | 408 | | |
400 | 409 | | |
401 | 410 | | |
402 | | - | |
| 411 | + | |
403 | 412 | | |
404 | 413 | | |
405 | 414 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
50 | 50 | | |
51 | 51 | | |
52 | 52 | | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
53 | 83 | | |
54 | 84 | | |
55 | 85 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
347 | 347 | | |
348 | 348 | | |
349 | 349 | | |
| 350 | + | |
350 | 351 | | |
351 | 352 | | |
352 | 353 | | |
| |||
369 | 370 | | |
370 | 371 | | |
371 | 372 | | |
372 | | - | |
373 | | - | |
374 | | - | |
| 373 | + | |
375 | 374 | | |
376 | | - | |
| 375 | + | |
377 | 376 | | |
378 | | - | |
379 | | - | |
380 | | - | |
| 377 | + | |
381 | 378 | | |
382 | 379 | | |
383 | 380 | | |
0 commit comments