Repository navigation
Commit 719a502
docs(skills): name the save_notice node in the automation skill (#31044)
<!-- claude-projects-attribution: {"github_login":"huangyiirene"} -->
_Requested by **Irene** · [project
thread](https://claude.ai/code/project/chan_01TJXtQ1tVD26ecLDZxFS67x?thread=cmsg_01TJXtQ1tVD26ecLDZxFS67xUyUS8qSzmmqesMRnrdULsA)_
Fixes #28787
Clause-②: no
Metadata-protocol: none
## 维护者速读
- **改了什么**:`objectstack-automation` skill 的 Data Operations 表新增
`save_notice` 一行,指向 `objectstack-data` `rules/lifecycle.md` § Save
Notice(仍是唯一完整教学处)。该节补上代码已做而文中缺的四点:hook 的 `ctx.notice`
文案不翻译;`save_notice` 文案的翻译键
`flows.<flow>.notices.<node_id>.message`(按保存人的语言读取);在 `subflow` / `map`
子流程里按名拒绝;其后若有可挂起子流程的 `subflow` /
`map`,流程不装配(`notice_after_suspending_subflow`)。新增 eval 16。
- **为什么改**:#28251 已上线保存提示,但 automation skill 0 处提到
`save_notice`,助手写「已提交审批」提示时仍会用 `record:alert` 横幅绕路。
- **风险与回滚**:只改 skill 文本、一条 eval 和一个
changeset;两文件增长均以同文件删除重复内容支付,棘轮行不动;回滚即 revert,无运行时影响。
- **先看**:`skills/objectstack-data/rules/lifecycle.md` § Save Notice
新增的五行,对照
`packages/services/service-automation/src/builtin/save-notice-node.ts`、`subflow-node.ts`
L123、`map-node.ts` L246、`flow-definitions.ts` L314。
- **两分钟验证**:`node scripts/check-skills-token-ratchet.mjs`,预期 exit 0,读数
automation `SKILL.md` 6426/6426、`lifecycle.md` 1590/1590。
- **需要你做的**:`skills/**` 属 Tier H,需要 `os-zhuang` 或 `hotlong` 批准。
## What changes
Every claim was read from the code on `main` at `3fe5f2a02`, not from
the card:
| Taught | Source |
|:--|:--|
| `save_notice` row in automation Data Operations: create/update record
trigger only, not after a pause, pointer to `rules/lifecycle.md` § Save
Notice | `packages/spec/src/automation/save-notice-placement.ts`
(`SAVE_NOTICE_TRIGGER`, `suspendedLegRefusals`) |
| A hook's `ctx.notice` message shows untranslated | `HookNoticeSetter =
(message: string) => void` in `packages/spec/src/data/hook.zod.ts`;
ruling A, #28251 comment 6562118323 |
| The node's message translates under
`flows.<flow>.notices.<node_id>.message` in the saver's locale |
`flowNoticeMessageKey` in `packages/spec/src/system/i18n-resolver.ts`;
`save-notice-node.ts` (`renderContext` takes `channel.locale`, the
writer's) |
| Refused by name in a `subflow` / `map` child | `subflow-node.ts` L123
and `map-node.ts` L246 `delete childContext.saveNotice`;
`save-notice-node.ts` refusal when the run holds no channel |
| After a `subflow` / `map` whose child can pause, the flow is not armed
(`unboundCause: 'notice_after_suspending_subflow'`) |
`packages/services/service-automation/src/subflow-notice-guard.ts`;
`flow-definitions.ts` L314 |
`lifecycle.md` keeps "after an `approval` / `wait`" (the parse refusal
set is `FLOW_PAUSE_CAPABLE_NODE_TYPES` = `screen`, `wait`, `approval`,
`approval_revise`; a `screen` cannot stand in a record-triggered flow
and `approval_revise` follows an `approval`, so the two named cover a
record-triggered flow).
`skills/objectstack-automation/evals/flows-triggers-approvals.json`
gains case 16: when a saved `deal`'s `status` is changed to `submitted`,
route it for approval and tell the saver "Sent {{ record.name }} to your
manager for approval" in their language. The expected answer is a
`record-after-update` trigger (on update `previous` is bound, so the
`previous.status` guard cannot fault), a `save_notice` before the `type:
'approval'` node, and the translation key. `must_contain`:
`save_notice`, `triggerType`, `notices.`, `type: 'approval'` (each
spelled in the `expected_output`); `must_not_contain`: `type:
'record:alert'` (an authored banner; the `expected_output` names no
banner type). No existing eval in the skill teaches the banner
workaround (`git grep -n "record:alert\|notice" --
skills/objectstack-automation/evals/` on `main`: no hit).
Every claim on the card is done by the code; nothing it asks for is
missing.
## Token budget
Readings from `node scripts/check-skills-token-ratchet.mjs`:
| File | Before (`3fe5f2a02`) | After | Ceiling |
|:--|--:|--:|:--|
| `skills/objectstack-automation/SKILL.md` | 6426 | 6426 | 6426
(unchanged) |
| `skills/objectstack-data/rules/lifecycle.md` | 1590 | 1590 | 1590
(unchanged) |
| bundle total (whole shipped tree, eval included) | 228257 | 228616 | —
|
No ratchet row moves. Growth is paid by deletion-only edits of restated
text in the same file, under the seat's claim widening on #28787
(comment 6694188691: deletion-only edits allowed anywhere in both files,
each with its surviving home named; additions stay in § Save Notice and
the `save_notice` row).
Deleted in `skills/objectstack-automation/SKILL.md`:
1. The `connector_action` row's ": `registerFlow` and `os validate`
refuse a `{…}` token at `connectorConfig.input.<key>`". Home:
Valid-but-silently-wrong item 1 (a value slot refuses a `{…}` token at
`objectstack validate`, `registerFlow` and the executor); the row still
calls `connectorConfig.input` a value-slot map.
2. The Triggers intro's "A `record_change` flow fires on a data event;".
Home: Flow Types, `record_change` row ("Fires automatically on record
create/update/delete").
3. The Triggers intro's "— the binding is the `start` node's `config`".
Home: § Trigger Configuration — on the `start` node, and § Trigger Types
(start-node `config.triggerType`).
Deleted in `skills/objectstack-data/rules/lifecycle.md`:
1. " — system data lifecycle & retention" in the intro. Home: the H1
title.
2. Code comment "O(1) shard DROP on SQLite". Home: Safety Semantics,
Rotation bullet (expired shards are DROPped on SQLite).
3. Code comment "paused rows never reaped". Home: the block comment
above it ("paused rows are live workflow state — `onlyWhen` scopes the
age sweep").
4. "— e.g. `'6h'`, `'14d'`, `'12w'`, `'7y'`" after Duration literals.
Home: the Syntax examples (`'14d'`, `'1d'`, `'90d'`, `'7y'`, `'30d'`)
and the ❌ `'2w'` example.
5. Safety bullet "**No `lifecycle` block = today's behavior**: `record`
is the implicit default." Home: Why This Exists ("Every object without a
`lifecycle` block is **immortal**") and `objectstack-data` `SKILL.md`'s
field table (`lifecycle` default: `record` semantics).
6. Safety bullet's "— a compliance ledger cannot be destroyed by
declaring a lifecycle" (review round 1). Home: the same sentence ("rows
are simply retained") and the bullet's lead ("safe by default").
Round 1 restored two comments first cut: "reap 1 day after expires_at"
(the only statement that `expireAfter` counts from `ttl.field`) and
"must: after === maxAge" (the only statement that `archive.after` must
equal `retention.maxAge`, `object.zod.ts` L1067–1070); both lines are
byte-identical to `main` again.
## Review round 1
Contract review FAIL at `7d0260975` (comment on this PR). All five fixed
in one delta commit on top, no rebase, no force-push:
- M1: eval 16 accepts `record-after-update` only, and its prompt asks
for a change of `status` on a saved deal, so the unguarded
`previous.status` condition cannot meet a create leg.
- M2 / S2: the two lifecycle comments restored (see above).
- S1: `must_not_contain` is now `type: 'record:alert'`, which the
`expected_output` does not contain; the `expected_output` spells `type:
'approval'`.
- S3: the key line says the key is read in the saver's locale.
Paid by deletion 6 and by folding: the hook line reads "A hook's message
is untranslated."
## Review tier
`dispatch-gates` mandates the contract-review tier for `skills/**`
(clause ①); waived by the maintainer's standing order in this project
(2026-11-03). The seat runs the contract review.
## Verification
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` at `2d885ad4c` (4 paths vs merge base
`3fe5f2a02`): **45 commands**, each run under `timeout 900` with
`NODE_OPTIONS=--max-old-space-size=8192` after a full `pnpm build`
(119/119 tasks), exit code captured before any pipe → **45 × exit 0**
(round 0 at `7d0260975` was also 45 × exit 0). `--ran` reconciliation:
`45 derived famil(ies) accounted for — 45 run, 0 NOT-MEASURED`. `node
scripts/check-commit-card-trailers.mjs --range origin/main..HEAD` → exit
0 before each push. `origin/main` is now `eb96480d`; since the branch
point it touches none of the three skill files nor
`scripts/check-skills-token-ratchet.mjs`, and `git merge-tree` merges it
cleanly, so the readings above stand.
<details><summary>Ran list</summary>
```
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: 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-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-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.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/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/ci/snapshot-next.mjs --self-test :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0
pnpm --filter @objectstack/spec run check:skill-docs :: exit 0
pnpm check:agent-test-spelling :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:ci-filter-parity :: exit 0
pnpm check:closed-ceremony-text :: 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:dts-closure :: exit 0
pnpm check:dual-build-cjs-loads :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:lean-entry-closure :: exit 0
pnpm check:merge-driver :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:pm-governed-merges :: exit 0
pnpm check:pm-skill-id-lint :: exit 0
pnpm check:published-files :: 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
```
</details>
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(not touched)
- [x] 2. Every name I added uses the casing for its kind. (no names
added)
- [ ] 3. No formatter ran; `pnpm lint` was not run (Markdown and JSON
fixture only, no lint target).
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset
(`'@objectstack/skills': patch`).
- [x] 6. `packages/spec` touched: n/a.
- [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, except the `## 维护者速读`
summary the maintainer asks for in Chinese.
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01J8VVAjrkobcs8zUPH1QvRm
<!-- signed: domain:skills#3 · huangyiirene ·
session_01J8VVAj7CvjkGXvgXzd6RZ7 -->
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 226c988 commit 719a502
4 files changed
Lines changed: 41 additions & 12 deletions
File tree
- .changeset
- skills
- objectstack-automation
- evals
- objectstack-data/rules
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
86 | 86 | | |
87 | 87 | | |
88 | 88 | | |
| 89 | + | |
89 | 90 | | |
90 | 91 | | |
91 | 92 | | |
92 | 93 | | |
93 | 94 | | |
94 | 95 | | |
95 | 96 | | |
96 | | - | |
| 97 | + | |
97 | 98 | | |
98 | 99 | | |
99 | 100 | | |
| |||
328 | 329 | | |
329 | 330 | | |
330 | 331 | | |
331 | | - | |
332 | | - | |
333 | | - | |
| 332 | + | |
| 333 | + | |
334 | 334 | | |
335 | 335 | | |
336 | 336 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
277 | 277 | | |
278 | 278 | | |
279 | 279 | | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
280 | 297 | | |
281 | 298 | | |
282 | 299 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| |||
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
41 | | - | |
| 41 | + | |
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
| |||
79 | 79 | | |
80 | 80 | | |
81 | 81 | | |
82 | | - | |
| 82 | + | |
83 | 83 | | |
84 | 84 | | |
85 | 85 | | |
| |||
88 | 88 | | |
89 | 89 | | |
90 | 90 | | |
91 | | - | |
92 | | - | |
| 91 | + | |
93 | 92 | | |
94 | 93 | | |
95 | 94 | | |
| |||
117 | 116 | | |
118 | 117 | | |
119 | 118 | | |
120 | | - | |
121 | 119 | | |
122 | 120 | | |
123 | 121 | | |
124 | | - | |
125 | | - | |
| 122 | + | |
126 | 123 | | |
127 | 124 | | |
128 | 125 | | |
| |||
155 | 152 | | |
156 | 153 | | |
157 | 154 | | |
| 155 | + | |
158 | 156 | | |
159 | 157 | | |
160 | 158 | | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
0 commit comments