Skip to content

docs(skills): objectstack-automation teaches the emailField recipient, flow requiredPermissions, excludeSubmitter and the screen file field - #26390

Merged
os-zhuang merged 2 commits into
mainfrom
claude/project-thread-ksiu9w
Oct 27, 2026
Merged

os-zhuang merged 2 commits into
mainfrom
claude/project-thread-ksiu9w

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Warren · project thread
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 chore: version packages #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.

Derived gates — 53 commands, 52 run green, 1 NOT MEASURED (--ran exit 0)
  • 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 756220c4a, 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).
The card's three package gates and the liveness gate
  • 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.

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. (no new names; keys quoted as the spec spells them)
  • 3. No formatter ran; pnpm lint is CI's run (repo-level scan, not owed locally per .claude/agents/os-dev.md).
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. Nothing publishes from a released package (skills/** carries no package.json); skip-changeset, and both declaration lines are above.
  • 6. packages/spec untouched.
  • 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 repo's own exceptions).
  • The Before Submitting list in CONTRIBUTING.md is done.

Generated by Claude Code

…, flow requiredPermissions, excludeSubmitter and the screen file field

Four audit rows the package taught nowhere, each in the fewest lines an AI
author needs:

- `notify.recipients` has a fourth form, `{ emailField }` — an `email`-typed
  field of the trigger record, for a person who is not a user; the node then
  names `channels: 'email'` alone. A CEL envelope must yield user ids, and an
  address no user holds fails the node at run time (the trap SKILL.md's
  "id, list, or envelope" line set).
- flow-level `requiredPermissions` — who may START a flow, checked at every
  door that starts it by name; not a `current_user.positions` check inside it.
- `excludeSubmitter` in the approval config table — segregation of duties;
  refused beside `onEmptyApprovers: 'auto_approve'`.
- the screen field typed `file` — the run resumes with the `sys_file` id,
  which a later record write claims.

Three eval prompts, one per teaching. Paid down first: the CRM Blueprint's
approval row that restated the approvals reference, and Best Practices 1 and
4 that restated the Branching section; the measured remainder raises the
three token-ratchet rows, quoting the maintainer's ruling.

Claude-Session: https://claude.ai/code/session_01JQwxbXsLdroTYaf1DRqVUG
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Reviewed at 2026-10-27T02:05Z — card #26298, PR #26390, independent contract review at the ceiling tier; read-only, adversarial, built from REST reads and git show 83b5ec5e:PATH (never the shared checkout, never the live main tip for the PR's own files).

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 83b5ec5ebae1d19fb98969c103f724b284a033db
Local-runs: none
Scope: skills/objectstack-automation/SKILL.md, skills/objectstack-automation/references/state-machines-and-approvals.md, skills/objectstack-automation/evals/flows-triggers-approvals.json, scripts/check-skills-token-ratchet.mjs + packages/spec/src/automation/io-node-config.zod.ts, packages/spec/src/automation/approval.zod.ts, packages/spec/src/automation/flow.zod.ts, packages/spec/src/automation/builtin-node-config.zod.ts, packages/spec/src/api/automation-api.zod.ts, packages/services/service-automation/src/builtin/notify-node.ts, packages/runtime/src/flow-start-admission.ts, packages/services/service-storage/src/field-file-service.ts, skills/objectstack-automation/references/_index.md, skills/objectstack-automation/references/examples-flows.md, .github/workflows/lint.yml, .claude/agents/os-dev.md, scripts/pm/check-governed-queue-guard.mjs, ui/scripts/check-skill-eval-tokens.mjs

Inputs read: card #26298 body and all four comments (dispatch order 6430836374, claim 6430944943, claim amendment 6431455570 2026-10-27T01:28Z, os-dev-report 6431766118 2026-10-27T01:52Z); audit Part A on #26184 comment 6425678185, § objectstack-automation rows A1–A6; the PR body, file list, diff, reviews, issue events and the head's check-runs over REST; the spec and runtime on origin/main at abb90460; the four changed files and their neighbours on the head; ruling A as the brief quotes the maintainer's tap 「A:先削重复,余额按实测提高」 2026-10-27T01:21Z.

① Derived judgments

(a) Every new sentence is true of the spec on origin/main.

  • notify fourth recipient form (SKILL.md, the recipients comment): { emailField: 'applicant_email' } is NotifyEmailFieldRecipientSchema (io-node-config.zod.ts, a closed object whose one key is a field machine name, "an email-typed field of the record that triggered the flow"), and it is the WHOLE recipients value, as the comment implies by calling it a form. "then channels: 'email' alone" — true and exact: the superRefine of NotifyConfigSchema refuses any channels that is not exactly one entry equal to email when recipients carries emailField (channels.length !== 1 || channels[0] !== 'email' adds an issue at channels; an absent channels is refused too). "An envelope must yield USER ids: an address no user holds fails the node" — true at run time in both channel shapes: screenComputedAddresses (notify-node.ts, the d1e329e96d behaviour) fails a node whose channels include email when a computed address matches no sys_user.email; refuseUndeliverableAddresses (notify: a written-out email address no user holds, on a node that does not name the email channel, writes an inbox row nobody can read and the node reports success #25758) fails a node without email for the same address because nobody can receive it. One nuance the comment does not state: an envelope that evaluates to an address a user DOES hold resolves to that user's id rather than failing — consistent with "must yield user ids", not contradicted.
  • Flow-level requiredPermissions (SKILL.md blockquote): every clause matches the requiredPermissions docblock of FlowSchema (flow.zod.ts ~L1195–1230): ADR-0066 D4; checked by every door that starts the flow by name (trigger route, action of type: 'flow', declared endpoint of type: 'flow') in flow-start-admission.ts; a non-system caller missing any named capability gets 403 PERMISSION_DENIED (requiredPermissionsRefusal, code PERMISSION_DENIED, status 403); the action door inherits the list; a parent flow's subflow node is not a door; "gate on it rather than on current_user.positions inside the flow". CreateFlowRequest.requiredPermissions (api.json:364) is the same key: CreateFlowRequestSchema is a lazySchema alias of FlowSchema in automation-api.zod.ts, so teaching it once on the flow covers the audit row's second surface.
  • screen row (SKILL.md node table): ScreenFieldConfigSchema.type (builtin-node-config.zod.ts ~L945–980) says type is "an open widget hint"; 'file' "renders the platform upload widget; the upload … lands as a sys_file no record owns yet, and the resume carries that file's id"; "A later node writes the id into an object's file field (create_record / update_record fields), and that write claims the file for the record it writes"; "a wizard collects its file before the record that will own it exists, and writes everything after its last screen". The row's "resumes with the sys_file id bound to name" follows from name being "the flow variable the value binds to". The dev's "later record write that claims the upload" is the spec's own sentence and service-storage's claimFiles / file-reference hooks implement it. The row's key list { name, label, type, required, options, defaultValue } is a subset of the strict object (it also declares placeholder, visibleWhen, min, max, inlineHelpText, reference); not false, see ③.
  • excludeSubmitter row (approvals reference, Node Config table): matches approval.zod.ts — true removes the submitter (submitter_id on the request) from the slate "whatever approver type produced them"; an emptied slate "takes the node's onEmptyApprovers route … and never passes silently"; onEmptyApprovers: 'auto_approve' is refused beside excludeSubmitter: true by checkExcludeSubmitterPolicyPairing (issue at onEmptyApprovers, remedy admin_rescue, fail or fallback); the key is .optional() and the spec's own describe says "Omitted or false (the default)", so "Default false" is the behavioural default as the spec states it.
  • Nothing taught is outside the card's Wanted: SKILL.md:107 fourth form + run-time failure ✓; config table excludeSubmitter refused beside auto_approve ✓; a short section on flow requiredPermissions ✓ (a 6-line blockquote); screen fields typed file ✓ (one table row). No sentence invented a key the spec lacks.

(b) The cuts are duplicates with a surviving home, not lost rules (ruling A).

  • SKILL.md CRM Blueprint, Approval flow row (−72 bytes, ≈18 tokens): it named approvers / behavior / lockRecord / approvalStatusField and "branch on approve / reject edges". On the head, the approvals reference § Node Config carries rows for approvers, behavior, minApprovals, lockRecord, approvalStatusField, onEmptyApprovers, excludeSubmitter, decisionOutputs, escalation, maxRevisions; § Branching carries "On approve / on reject — wire downstream nodes … to the approve / reject out-edge". Every clause survives. The new row's pointer "the approvals reference" has no inline link, but SKILL.md:317 links references/state-machines-and-approvals.md by path and references/_index.md lists it — a reader finds it.
  • Approvals reference, Best Practice 1 ("Gate entry on the edge (condition into the Approval node) so the flow only pauses for records that actually need sign-off"): § Branching on the head reads "Conditional step — put a decision node before the Approval node, or a condition on the edge entering it". The mechanism survives verbatim; the rationale clause is the trivially derivable purpose of a conditional step. Not a lost rule.
  • Best Practice 4 ("Model rejection as a visible branch — a back-edge to revise, or an end node to terminate. The path is on the diagram, not hidden in config"): § Branching's lead "These are wired on the graph, not in node config", plus "Roll back on reject — route the reject edge as a back-edge" and "Hard reject — route the reject edge to an end node". All three clauses survive. Practices 2, 3, 5 stay, renumbered 1–3. Deleted span measured by hand: 289 bytes ≈ 72 tokens (the row comment says ~73). Nothing else is removed; references/examples-flows.md and references/_index.md are byte-identical to origin/main.

(c) Ratchet rows pinned at the measurement, ruling quoted where the script requires.

  • The script's measure is Math.ceil(Buffer.byteLength(text,'utf8') / 4) (countTokens), less the whole release-stamp line and its newline for a stamped SKILL.md (pricedTokens / stampLine). Reproduced by a pure byte count over git show 83b5ec5e:PATH (a reading, no script run): SKILL.md 24034 bytes, stamp line + newline 20 bytes → ceil(24014/4) = 6004 (row 6004, headroom 0); approvals reference 22346 bytes → 5587 (row 5587, headroom 0); evals JSON 7289 bytes → 1823 (row 1823, headroom 0). On origin/main the same files measure 5771 / 5555 / 1255 against old rows 5780 / 5569 / 1255, so the arithmetic in the row comments and the PR body — +233 net with 9 absorbed (ceiling +224); +32 net with 14 absorbed (ceiling +18); +568 with nothing to delete — is exactly right. No headroom smuggled in, no red row.
  • The script enforces SHRINK-ONLY by the over-ceiling verdict (tokens above the row) whose message says the other direction "lands only in a PR whose body quotes a maintainer ruling authorizing it"; it parses neither the row comments nor the PR body. The PR body quotes the tap verbatim (「A:先削重复,余额按实测提高」, the card title, the date) and each of the three row comments quotes it too, in the convention every earlier raise in the map uses. The new comment blocks sit inside the CEILINGS map, below the SHRINK-ONLY doc comment, so check-ratchet-remedy-authority's 400-char anchor is undisturbed; pnpm check:ratchet-remedy-authority is reported green locally and the lane that runs the ratchet in CI is green (below). eslint.config.mjs on origin/main sets no max-len; Lint · ESLint on the head is success.
  • Whole-file and whole-package readings in the PR body check out by hand: SKILL.md 432 → 441 lines, approvals reference 383 → 380, evals 55 → 85, package (SKILL.md + references/**/*.md + rules/**/*.md) 995 → 1001 lines.

(d) Evals. Ids 1–8 unique and sequential; evals 6, 7, 8 follow the file's shape (id, prompt, expected_output, files, assertions.must_contain / must_not_contain). Eval 6 is the card-specific acceptance eval ("email the applicant when the application is approved" → recipients: { emailField: 'applicant_email' }, channels: 'email' alone, must_not_contain the CEL envelope over record.applicant_email and channels: ['inbox']). Eval 7 asserts requiredPermissions, excludeSubmitter: true, type: 'position' (a real ApproverType) and forbids onEmptyApprovers: 'auto_approve' and current_user.positions — consistent with the refusal. Eval 8's expected fields: { signed_copy: { dialect: 'cel', source: 'signed_copy' } } is a legal create_record.fields value (FlowValueSlotSchema, a CEL envelope over the flow variable the screen field bound). The repo has no runner for skills/objectstack-*/evals/*.json; ui/scripts/check-skill-eval-tokens.mjs walks only the skills/objectui bundle (skill-bundle-root.mjs), so its whole-token rule does not read these evals. Every new teaching has an eval.

(e) Wanted met, nothing beyond. Four files, all inside the claim + amendment surface (skills/objectstack-automation/** + the touched CEILINGS rows only; no other row, no logic). SKILL.md line 1 and the version: stamp line are byte-identical to origin/main (PR #25640 owns them). references/_index.md is generated and unchanged; check:skill-refs reported in sync.

(f) PR body. Line 1 is the claude-projects-attribution HTML comment, line 2 _Requested by_, line 3 Fixes #26298, then Clause-②: no and Metadata-protocol: none line-initial. os-dev.md line 323 is the exception for a harness-written first-line attribution comment ("Fixes #N 紧随其后"); scripts/pm/check-governed-queue-guard.mjs pins exactly this three-line shape as a project-thread PR body in its own self-test, and neither clause2-line.mjs nor the closing-keyword parsers require Fixes on line 1. The ## 维护者速读 block carries the six labels in order; 需要你做的 names approval (or 「Merge when ready」), the one action the dispatch order allowed for Tier H; 先看 names three files with the risky hunks; 两分钟验证 names one command and the sight it prints (the script's closing line contains 81 authored bundle file(s) within their ceilings and the per-file verdict prints headroom 0). English sections What changes / Why / Tests / Acceptance notes / Verification follow, readings folded into details blocks. No model name in the title, body, commit or trailers (the commit carries the model-free Claude-Session: + Co-authored-by: Claude pair). Only #26298 sits beside a closing keyword.

(g) Report shape and claim. os-dev-report 6431766118: first line os-dev-report, then the JSON with every key of the os-dev.md template (issue, status, branch, pr, session, premise_still_valid, summary, tests, gates, dogfood_battery, mcp_calls, api_writes, open_questions, out_of_scope_findings) plus the line_budget and deviations (6) the order demanded and head / model; files_changed / rules_retired are not in the template and are absent. Branch claude/project-thread-ksiu9w = the claim's Branch: line; mcp_calls: 0 GitHub MCP; api_writes: 2 (relay pr_create, the report comment). The PR's assignee os-warren and label skip-changeset were written by the relay at pr_create (2026-10-27T01:50Z in the issue events); reviewers hotlong and os-zhuang were requested by the relay.

Check-runs on 83b5ec5e (read twice, 2026-10-27T01:58Z and 2026-10-27T02:02Z, 50 runs): every completed run is success or skipped — among them Lint & Repo Gates, Lint · ESLint, Lint · repo & tooling gates, Lint · docs & code guards (carries check:skill-identifier-liveness), Lint · CI, release & engine gates, Lint · PM dispatch-gates, the three scoped Lint lanes, Type Check · source gates (by reading .github/workflows/lint.yml on the head this lane runs check:skill-docs, check:skill-refs and node scripts/check-skills-token-ratchet.mjs --self-test + the gate itself), Type Check · workspace, Type Check · debt ledger, Build Core, Check Changeset, Check PR Size, Dogfood Regression Gate, both Governed Surface Queue Guard runs, Part-of PR must not also close its card, The card this PR closes must claim this branch, both single-claim guards, Test Core 2–8/8. Still in_progress at the second read: Test Core (aggregate, with shard 1/8) and Type Check · consumer gates (the lane that runs check:skill-examples, which the dev reports green locally on the built tree: 264 prose examples across 3 surfaces). Not waited for, per the brief.

② Semver level

skip-changeset is right: skills/objectstack-automation/ holds no package.json (the head's tree lists SKILL.md, evals/, references/ only) and no skills/** path carries one; scripts/check-skills-token-ratchet.mjs is repo tooling. Check Changeset on the head is success. Clause-②: no and Metadata-protocol: none are line-initial on the PR body and match the claim (the skill teaches keys the protocol already carries; no spec change).

③ Boundary flags

  • 收班简报 note, not a card: the screen row's config.fields[]: { name, label, type, required, options, defaultValue } lists six of the strict object's keys and reads as closed; placeholder, visibleWhen, min, max, inlineHelpText, reference (and record_link) are also declared. An author who needs them finds them in the generated reference; no false key is taught.
  • Nothing: the Blueprint row's pointer "the approvals reference" carries no inline link; the path link at SKILL.md:317 serves.
  • Nothing: the dev's deviations (attribution lines ahead of Fixes, model-free trailers instead of the harness's model-named one, no GitHub MCP writes, no PR subscription, the six cited commits outside the shallow clone with the spec on main read instead, worktree kept) are each verified above or are the order's own instructions; none is a defect.
  • Landing state, for the seat: ready_for_review and auto_merge_enabled by os-zhuang at 2026-10-27T01:54Z (merge method merge), before this record; at 2026-10-27T02:02Z the PR is open, not merged, mergeable_state: blocked, and the reviews list is EMPTY (no APPROVED review on record yet). The dispatch order said no seat readies, arms or enqueues a Tier H PR; the readying was a governed-approver account's hand, not the seat's, and is reported, not judged, here.
  • Base: the branch forks from 756220c4; origin/main has moved three commits to abb90460 (e931621a, 2163e806, abb90460), none touching skills/objectstack-automation/** or scripts/check-skills-token-ratchet.mjs (only .github/workflows/lint.yml, 12 lines). No conflict, no silent re-measure of the raised rows on main.

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

Implemented by the card thread session_01JQwxbXsLdroTYaf1DRqVUG (mode:thread) on that branch; reviewed by session_01LqKsGyMNFwwnbF5FTAqyPn (independent contract-review subagent).

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读

改了什么:objectstack-automation 技能包补上四条审计缺口——notify 收件人第四种写法 { emailField }(只能配 channels: 'email';信封算出的地址无用户持有则节点运行时失败)、流程级 requiredPermissions、审批节点 excludeSubmitter(与 auto_approve 并写被拒)、屏幕字段 type: 'file'(续跑拿到 sys_file id,后续写入记录即认领);每条配 eval(6、7、8)。按你的裁决 A:先削同文件重复(Blueprint 审批行改指针、两条最佳实践并入 § Branching),余额按实测抬三行天花板。
为什么改:四个键 spec 已有、技能零命中;AI 作者照旧文写的 recipients 信封会在运行时失败,不写 excludeSubmitter 则提交人可自批。
风险与回滚:只改发布技能文本与 eval,不改运行时;回滚 = revert 本 PR。
先看:SKILL.md(notify 代码块、requiredPermissions 引言段、screen 行)→ references/state-machines-and-approvals.md(配置表新行、删掉的两条最佳实践——去处已核)→ 棘轮三行。
两分钟验证:node scripts/check-skills-token-ratchet.mjs 预期末行 81 authored bundle file(s) within their ceilings,automation 三行 headroom 0。
需要你做的:无——os-zhuang 已 ready + 入队(02:03Z),合入即落地。
席位意见:同意落地。一处可选后续(不挡本 PR):screen 行的 config.fields[] 键表读起来像封闭列表,漏了 placeholder / visibleWhen / min / max / inlineHelpText / reference。另记:本 PR 入队时 reviews 为空(无 APPROVED 记录),队列按档位放行。

席位核对

ACCEPT · domain:skills#2 · session_01LqKsGyMNFwwnbF5FTAqyPn · 2026-10-27T02:10Z — seat review of record for #26298 on head 83b5ec5e, against GitHub, not the report's own account. Published surface skills/** ⇒ Tier H: the record of this tier is the independent ## Contract review at comment 6431951534 (Served-tier: CONTRACT_REVIEW_TIER, head 83b5ec5e, VERDICT: PASS, Local-runs: none; drafted by a reviewer other than the implementer, the card thread's dev). This seat adds the landing read. Order of events, reported: the governed approver's hand ran ahead of this record (see 6).

  1. Shape. Base main, mergeable_state: clean; body lines 1–2 the harness attribution block, line 3 Fixes #26298 (os-dev.md 323's exemption; the closing-keyword guards green), Clause-②: no, Metadata-protocol: none line-initial (kind none ⇒ no Skills-docs: line owed under the rule landed in b33dd25b); ## 维护者速读 six labels in order, then What changes / Tests / Acceptance notes / Verification with folds. Files: SKILL.md (+12/−3), references/state-machines-and-approvals.md (+4/−7), evals/flows-triggers-approvals.json (+30), scripts/check-skills-token-ratchet.mjs (+26/−3, three rows) — the claim's surface (6430944943) as amended at 2026-10-27T01:26Z (+ the package's CEILINGS rows under ruling A); line 1 of SKILL.md untouched. Labels documentation, size/s, skip-changeset — right, no package.json under skills/objectstack-automation. Branch claude/project-thread-ksiu9w, named by the claim. Model: the thread switched to the ceiling tier on the maintainer's own words before its first edit (reported in the os-dev-report).
  2. Record, measured. Every new sentence read true on origin/main: NotifyEmailFieldRecipientSchema with channels: 'email' alone enforced by NotifyConfigSchema.superRefine; an undeliverable envelope address fails the node (refuseUndeliverableAddresses in notify-node.ts); requiredPermissions matches flow.zod.ts + flow-start-admission.ts (403 PERMISSION_DENIED), CreateFlowRequestSchema a lazy alias of FlowSchema; the screen row matches ScreenFieldConfigSchema.type's docblock; excludeSubmitter matches approval.zod.ts + checkExcludeSubmitterPolicyPairing. Every cut has a surviving home (Blueprint row → § Node Config / § Branching; BP1 → 「Conditional step」; BP4's three clauses → § Branching lead + 「Roll back on reject」 + 「Hard reject」); examples-flows.md and _index.md byte-identical to main. Ratchet rows pinned at the measurement 6004 / 5587 / 1823 (headroom 0; arithmetic +233/9/+224, +32/14/+18, +568 exact); ruling quoted in body and rows. Evals 1–8 sequential, eval 6 the card's acceptance eval. Line counts 432→441, 383→380, 55→85, package 995→1001. The seat re-read the diff and reads the same.
  3. Gates, pinned to head 83b5ec5e. 51 check-runs on the head all success or skipped by 2026-10-27T02:06Z, both Governed Surface Queue Guard runs success. Lane map read off today's lint.yml: the token ratchet, check:skill-docs and check:skill-refs run in Type Check · source gates; check:skill-examples in Type Check · consumer gates.
  4. Accepted deviations. (a) Surface widened to the CEILINGS rows — the seat's own amendment under ruling A. (b) The card's cited commits unreachable in the shallow clone; spec lines cited instead. (c) check:dual-build-cjs-loads NOT MEASURED locally (whole-tree build prerequisite), CI's run green. (d) Fixes on line 3 under the harness block.
  5. Carrier. Nothing to file. 收班简报: a Tier H PR was readied, armed and enqueued by a governed approver with zero APPROVED reviews on record and before the contract review landed — the queue admitted it on tier alone (for the maintainer); the screen row key list reads closed but is a subset.
  6. Landing. os-zhuang readied and armed at 2026-10-27T01:54Z and added it to the merge queue at 2026-10-27T02:03Z with no APPROVED review on record — ⛔ nothing for the seat to ready, arm or enqueue. On MERGED the seat closes the card with the landing record and releases skills(objectstack-automation): teach the read-only record_link screen field, with an eval prompt (#26304 follow-up, #26184) #26347 then skills(automation): teach the record-anchored approval lock read (GET /approvals/records/:object/:recordId/lock, { locked } for every reader) with one eval prompt #26362 (both skills/objectstack-automation + the same eval file; serial).

Generated by Claude Code

Resolves scripts/check-skills-token-ratchet.mjs: the sibling rows that
landed around the automation rows (objectstack-api 1234, objectstack-data
1095) are kept with their comments, beside this PR's three automation rows.

Claude-Session: https://claude.ai/code/session_01JQwxbXsLdroTYaf1DRqVUG
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT (addendum, pinned to the merge head) · domain:skills#2 · session_01LqKsGyMNFwwnbF5FTAqyPn · 2026-10-27T02:51Z — head 9418cad4 is the merge of origin/main c51aed4f into claude/project-thread-ksiu9w on top of the reviewed head 83b5ec5e (parents 83b5ec5e + c51aed4f; merge commit, no rebase, no force). The seat read: git diff 83b5ec5e 9418cad4 -- skills/objectstack-automation is empty (the three skills files byte-identical to the reviewed head); against the merge-base c51aed4f the PR's delta is the same four files, +72/−13; in scripts/check-skills-token-ratchet.mjs the only lines differing from main are this PR's three rows (5780 → 6004, 5569 → 5587, 1255 → 1823) — the sibling rows #26393 (data evals 1095) and #26392 (api evals 1234) kept with their comments. So the contract review 6431951534 and the seat's ACCEPT 6432004786 carry over to this head unchanged; the dev's merge report is comment 6432538298 on #26298 (ratchet + self-test, skill-docs, skill-refs, skill-examples exit 0 on 9418cad4). Auto-merge reads armed on the new head; CI pending at the seat's read — the queue lands it on the governed approver's hand. ⛔ Nothing for the seat to ready, arm or enqueue.


Generated by Claude Code

@os-zhuang
os-zhuang added this pull request to the merge queue Oct 27, 2026
Merged via the queue into main with commit 96f1258 Oct 27, 2026
41 checks passed
@os-zhuang
os-zhuang deleted the claude/project-thread-ksiu9w branch October 27, 2026 03:18
zhuangjianguo pushed a commit that referenced this pull request Oct 27, 2026
Brings in main's re-measured authz census (#26406), the fix for main's
own dogfood red (#26400) that the full battery on this branch showed.
Main's automation skill change (#26390) left the approvals reference
two bytes under its token ceiling, so the queue approver row is
shortened to fit (members of the named ownership queue at node entry;
an empty queue adds nobody).

Claude-Session: https://claude.ai/code/session_01GTntg542RUmmRRbMY6wMZD
Co-authored-by: Claude <noreply@anthropic.com>
os-warren pushed a commit that referenced this pull request Oct 27, 2026
Resolves the adjacent-row conflict in scripts/check-skills-token-ratchet.mjs:
main's objectstack-automation row (#26390) and this branch's
objectstack-data examples-objects.md row both kept.

Claude-Session: https://claude.ai/code/session_01JQwxbXTWvHG6MXFD7Gywf2
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 flow-level requiredPermissions, excludeSubmitter, the { emailField } recipient and screen file fields

2 participants