Skip to content

docs(skills): objectstack-data rules/lifecycle.md teaches the save notice - #28813

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-28253-save-notice-skill
Nov 2, 2026
Merged

os-zhuang merged 2 commits into
mainfrom
claude/issue-28253-save-notice-skill

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by 田騏 · project thread
Fixes #28253
Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:objectstack-data 技能的 rules/lifecycle.md 新增「Save Notice」一节:保存钩子用 ctx.notice('…') 告诉保存者这次保存触发了什么(inline handler 与沙箱 body 同一拼法、不需要能力声明),记录触发的 flow 用 save_notice 节点设同一条消息;info 级、一次保存一条、后设者胜、与校验 warnings 和 record:alert 横幅的区别。另附 changeset(@objectstack/skills patch)。
  • 为什么改:运行时早已支持(hook 面与 flow 面都已合入主干),但技能里没教,AI 作者写保存钩子时仍去拼 record:alert 横幅来「告诉用户」;这是 Save answer: a hook or record-triggered flow can attach one user-facing notice to a record save, shown with the save toast #28000 的 Skills-docs 欠账。
  • 风险与回滚:纯文档,不改 schema、不改运行时;整文件 token 不超原天花板(1590 → 1587)。回滚即 revert 本 PR。
  • 先看:skills/objectstack-data/rules/lifecycle.md 末尾新节(主要 hunk),以及被删去的「Decision Tree」一节(用它付 token 账)。
  • 两分钟验证:node scripts/check-skills-token-ratchet.mjs,预期看到 rules/lifecycle.md is 1587 tokens (ceiling 1590; headroom 3) 且整体 ✓。
  • 需要你做的:skills/** 为受管面(Tier H),需要授权账号的 APPROVED 评审后由席位落地。

What changes

skills/objectstack-data/rules/lifecycle.md gains Save Notice — what the save set in motion, teaching what the live code does:

  • A create/update hook calls ctx.notice('Sent to your manager for approval') — inline handler and sandboxed body alike, no capability (HookContext.notice, bound by the engine on every insert/update dispatch; the body runner marshals it as a host function).
  • The create/update answer carries it as notice and the console shows it on the save toast. Info severity: an outcome, never a problem — distinct from validation warnings, and from a record:alert banner (page metadata shown by the record's state on every visit); the notice is for what this save started.
  • One message per write, the last call wins, never stored; insert/update dispatches only; a write nobody waits on (batch, seed, import, a nested write) sets nothing; a non-string refuses the write; a refused write answers its error, no notice.
  • A flow a record create/update trigger starts sets the same channel with a save_notice node (config.message, {{ }} renders); refused in any other flow, and after an approval / wait.

The intro's "not to be confused with lifecycle hooks" sentence now says the page also carries the save notice.

Paid inside the file's token ceiling (no ceiling moves), by deleting restating prose in the same file:

  • the Decision Tree section — items 1–5 restated the Lifecycle Classes table (class, contract, typical use) and the four Syntax examples; item 6 restated the MIXED syntax example's comment and the fifth Validation rule (onlyWhen + rotation/archive);
  • the telemetry-growth anecdote in Why This Exists (an incident narrative; the rule sentence that follows it stays);
  • "Nothing is deleted." in the first Safety Semantics bullet, which restated Why This Exists ("rows are never deleted").

.changeset/28253-skill-save-notice.md — @objectstack/skills: patch (the skill ships in that package's dist).

Skills readings (skills/** diff, two readings each):

Reading before after
rules/lifecycle.md lines 156 161
rules/lifecycle.md bytes / tokens (ceil(bytes/4)) 6360 / 1590 (ceiling 1590, headroom 0) 6347 / 1587 (ceiling 1590, headroom 3)
package: sum of all 11 skills/*/SKILL.md lines 4561 4561 (no SKILL.md touched)
bundle total (whole shipped tree, tokens) 222242 222239

Not touched: skills/objectstack-data/SKILL.md (PR #28795 is open on it). Its rule index entry for rules/lifecycle.md (line 45) still describes the page as retention/TTL/rotation/archive only and does not name the save notice; if the index must name the new topic, that is a follow-up on that file once #28795 lands.

Tests

Docs-only diff in a published skill package: no test file is owed. The gate family that measures the page is the token ratchet, run on the final head:

  • node scripts/check-skills-token-ratchet.mjs → exit 0 — rules/lifecycle.md is 1587 tokens (ceiling 1590; headroom 3); 57 authored bundle file(s) within their ceilings.

Acceptance notes

  • Every sentence in the new section is taken from the live code, not from the card's summary: packages/objectql/src/hook-notice.ts (binding, the non-string refusal, the no-listener no-op), packages/spec/src/data/hook.zod.ts (HookContext.notice, handler and body alike, no capability), packages/runtime/src/sandbox/body-runner.ts and quickjs-runner.ts (the sandbox marshal), packages/spec/src/contracts/data-engine.ts (last call wins, refused write answers its error, nothing stored), packages/spec/src/api/protocol.zod.ts (the answer's notice, info severity, distinct from warnings), ui/packages/react/src/record-save-feedback.ts (the save toast), packages/spec/src/automation/save-notice-placement.ts and packages/services/service-automation/src/builtin/save-notice-node.ts (the flow node and its refusals).
  • Noted, not filed (no defect class): skills/objectstack-data/references/data-hooks.md — the sandbox ctx.* table and the HookContext interface listing carry no ctx.notice row; that file sits at 2 tokens of headroom, so a row there must be paid for by deletion in that file. Carrier: none.
  • No label was sent with the create: the dispatch named none, and skip-changeset does not apply (a changeset is included).

Verification

Gate derivation on the final head (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths, change set derived from git: 2 files, +34 / −18): 43 families derived; every command recorded with its exit code captured before any pipe, then reconciled with --ran.

  • First sweep at head 63afc09f2d (install only, no dist): 38 families exit 0; 5 exit 3 (PREREQUISITE NOT MET, no built dist): check:doc-formula-expressions (lint), check:dts-closure, check:dual-build-cjs-loads, check:lean-entry-closure, check:sourcemap-no-sources-content.
  • pnpm build --concurrency=2 under scripts/pm/os-verify-lock.sh (VERDICT command-exit 0, 116/117 tasks replayed from the shared turbo cache), then the five re-run on the same head: all exit 0.
  • node scripts/pm/dispatch-gates.mjs --ran over the merged record: 43 derived famil(ies) accounted for — 43 run, 0 NOT-MEASURED, 0 UNRUN (exit 0).
  • Among them, every skills family the derivation names: check:skills-token-ratchet (+ --self-test), check:skill-frame-sync, check:skill-identifier-liveness, check:skill-compatibility, check:doc-authoring (the no-tracker-id rule over skills/**), check:pm-skill-id-lint, check:role-word, check:corpus-claim-drift, check:published-files, the changeset gates (check-empty-changeset, check-changeset-no-major, check-adr-0087-registration) — all exit 0.
  • Local-only families not derived and not run: check:dev-prereqs, check:skill-frame-freshness (the diff touches no frame file and no packages/spec source).
  • CI families (Lint & Repo Gates, the type-check lanes and the rest) are the PR's check-runs; nothing there is re-run locally.

Dogfood battery: n/a — no ! in the title and no (narrowing) arm.

🤖 Generated with Claude Code

…tice

The data skill's lifecycle rule gains a Save Notice section: ctx.notice
for inline handler and sandboxed body alike, info severity distinct from
validation warnings and from a record:alert banner, one message per
write with the last call winning, and the save_notice flow node. Paid
inside the file's token ceiling by deleting the Decision Tree, the
telemetry-growth anecdote and a Safety Semantics restatement.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ihL2g7RyXa8ErM6GAkHyc
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tooling labels Nov 1, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#2 · marchtian · session_017ihL2g7RyXa8ErM6GAkHyc

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 63afc09f2dc6c50cca54ca4fefd99367161d27d9
Local-runs: none
Scope: skills/objectstack-data/rules/lifecycle.md + packages/objectql/src/hook-notice.ts, packages/objectql/src/engine.ts (triggerHooks), packages/spec/src/data/hook.zod.ts (HookContext.notice), packages/spec/src/contracts/data-engine.ts (onNotice), packages/spec/src/api/protocol.zod.ts (WRITE_ANSWER_NOTICE_DESCRIPTION, Create/UpdateDataResponseSchema.notice), packages/runtime/src/sandbox/body-runner.ts, packages/runtime/src/sandbox/quickjs-runner.ts, packages/runtime/src/sandbox/hook-notice.integration.test.ts, ui/packages/react/src/record-save-feedback.ts, ui/packages/app-shell/src/providers/writeWarningToast.ts, packages/spec/src/automation/save-notice-placement.ts, packages/services/service-automation/src/builtin/save-notice-node.ts, packages/triggers/trigger-record-change/src/record-change-trigger.ts, packages/services/service-automation/src/builtin/subflow-node.ts, map-node.ts, packages/spec/src/ui/page.zod.ts + content/docs/protocol/objectui/record-alert.mdx (record:alert), skills/objectstack-data/SKILL.md (rule index), skills/objectstack-data/references/data-hooks.md (ctx table, HookContext listing, handler-vs-body table), skills/objectstack-automation/SKILL.md (node table), scripts/check-skills-token-ratchet.mjs (lifecycle.md ceiling row), packages/skills/package.json. Remainder of the diff (.changeset/28253-skill-save-notice.md) is the lane seat's ACCEPT at TIER_DEFAULT.

Inputs: card #28253 body and all four comments (triage 6532361474, 6563078644; claim 6563777116; dev report 6564123997); PR #28813 body, file list (2 files, +34/−18) and git diff origin/main...63afc09f ; the 38 check-runs on the head. Dispatch order not read.

① Derived judgments

  • Published surface: the only publish is @objectstack/skills dist (packages/skills/package.json files: dist, build = sync-catalog), which ships skills/** as authored — the new Save Notice section is added and the Decision Tree section, the telemetry-growth anecdote and "Nothing is deleted." are removed from the published page. Docs-only; no schema, key, type or behaviour changes. Right.
  • Accept set (token ratchet): the ceiling row ['skills/objectstack-data/rules/lifecycle.md', 1590] (check-skills-token-ratchet.mjs:702) is untouched; the head file is 6347 bytes → ceil(6347/4) = 1587 tokens (main: 6360 → 1590), headroom 0 → 3. No ceiling moves. Right. (Computed from wc -c on the fetched blob, not a gate run.)
  • Package SKILL.md line sum unchanged (no SKILL.md touched). Right.
  • Clause-②: no on PR body and changeset: the diff narrows no published capability — it adds teaching and removes restating prose. Right.
  • Metadata-protocol: none: no protocol change. Right.
  • Gates: the 40 check-runs on 63afc09f have all completed — 28 success, 12 skipped (UI/docs/opt-in lanes the diff does not reach), 0 failure, 0 cancelled, 0 pending. Among the successes the families this diff answers to: Lint · repo & tooling gates, Lint · docs & code guards, Lint · PM dispatch-gates, Lint · CI, release & engine gates, Check Changeset, Governed Surface Queue Guard, PR-body guards, Check PR Size, Build Core, Test Core (both shards), every Type Check lane. Those conclusions are the gate verdicts; nothing was re-run.
  • Tier H: skills/** is governed; the PR stays draft / needs-user-decision until an authorised approver acts. This record does not substitute for that approval.

② Semver level

.changeset/28253-skill-save-notice.md → '@objectstack/skills': patch. Correct: the published change is prose in a docs package's dist; no API, schema or key is added or removed, so patch is the right level and skip-changeset does not apply (the package publishes the file). Clause-②: no is right — nothing declared on main is taken away at any consumer. Changeset body matches the diff (names the three deletions and that no ceiling moves).

③ Boundary flags

Dev report 6564123997, each flag answered:

  1. Deviation — no label sent with pr_create; labels now on the PR set by a labeler. Answered: correct. The dispatch named none, a changeset is included so skip-changeset is wrong here; the labeler's labels are the labeler's. Nothing to do.
  2. Deviation — skills/objectstack-data/SKILL.md not touched (PR docs(skills): objectstack-data teaches an object extension's fieldPatches — relabel a vendor field without redeclaring it in fields #28795 open on it). Answered: the serial constraint is real (single-writer path), so not touching it in this PR is right. See open question below for what it costs.
  3. Deviation — worktree and node_modules removed after the report. Answered: housekeeping per the os-dev cleanup clause; the branch is on origin at the reviewed head. Nothing to do.
  4. open_question — should SKILL.md's rule index (line 45) name the save notice, and should references/data-hooks.md's sandbox ctx table gain a ctx.notice row? Answered: A, and the data-hooks.md row with it — escalated as a follow-up card to file now, not "noted, not filed". Reason, from the tree: the card's Reach is an AI author writing a save hook. On main that author is routed by SKILL.md line 46 to references/data-hooks.md (the hooks reference), whose sandbox ctx.* table (lines 357–369) and HookContext listing (574–610) carry no notice; line 45 describes rules/lifecycle.md as retention/TTL/rotation/archive and says "distinct from lifecycle hooks below", so the index actively steers a hook author away from the page that now teaches the notice. The teaching on this head is correct, but its discoverability from the skill's own index is zero until (a) line 45 gains one clause and (b) data-hooks.md gains a ctx.notice row (paid in-file; 2 tokens of headroom). Both are outside this PR's claimed surface and (a) is blocked by docs(skills): objectstack-data teaches an object extension's fieldPatches — relabel a vendor field without redeclaring it in fields #28795, so they are not a FAIL here — but the Reach is only closed once they land. Recommend one follow-up card covering both, Blocked-by: #28795.
  5. out_of_scope — data-hooks.md ctx table / HookContext listing lack ctx.notice. Answered: folded into 4 above; it is the same gap, and it should be filed rather than noted, because it sits on the exact page the Reach scenario's author reads.
  6. Reviewer-found, not in the dev report: skills/objectstack-automation/SKILL.md's node table (lines 60–95) lists wait, approval, screen … but no save_notice. The flow face taught in the new section is not taught where a flow author looks. Outside this card (domain:skills, different skill); escalated for the same follow-up card or its own. Not a defect of this PR.

Boundary facts verified against the dev's facts table: every file:line cited resolves on origin/main to the code the sentence describes (hook-notice.ts:29,42–47,48–52,61–67; engine.ts:5996–5998; hook.zod.ts:1612–1629; data-engine.ts:128–169; protocol.zod.ts:2755–2764,2785,2996; body-runner.ts:1302–1306; quickjs-runner.ts:945–967; record-save-feedback.ts:159,184–194; writeWarningToast.ts:256–262,285–301; save-notice-node.ts:37–61,62–107; save-notice-placement.ts:31,52–80; record-change-trigger.ts:175–197,363–370,605–611; subflow-node.ts:121–123; map-node.ts:244–246; page.zod.ts:238,1436; hook-notice.integration.test.ts:118–120). No fact in the report contradicts the tree.

④ Design judgment

skills/objectstack-data/rules/lifecycle.md — judged against the card's eval scenario (an AI author writing a save hook keeps reaching for a record:alert banner; does the rule on the head lead it to the save notice, and only where that is right?) and the spec it teaches on main.

Claim-by-claim against the cited code:

  • "A create/update hook calls ctx.notice('…')" — hook-notice.ts:29 (NOTICE_EVENTS = before/afterInsert, before/afterUpdate), :61–67 bound / deleted per event; engine.ts:5998 binds on every triggerHooks. Holds.
  • "inline handler and sandboxed body alike, no capability" — hook.zod.ts:1612–1617 (in-process handler calls the member; body runner marshals it; no capability token); body-runner.ts:1306 hands the engine's own function; quickjs-runner.ts:955–967 installs it with no capability gate and relays the TypeError. Holds (see should-fix 1 on emphasis).
  • "The answer carries it as notice; the console shows it on the save toast" — protocol.zod.ts:2785 and :2996 (notice: z.string().min(1).optional() on both answers); record-save-feedback.ts:184–194 (toastRecordSave: notice leads the description lines; notice alone stays on the success variant); writeWarningToast.ts:285–301 (unclaimed: an info toast of its own). Holds.
  • "Info severity: an outcome, never a problem — distinct from validation warnings" — data-engine.ts:130–132 ("never rides onValidationAdvisory"); protocol.zod.ts:2759–2760. Holds.
  • "A record:alert banner is page metadata shown by the record's state on every visit; the notice is for what this save started" — page.zod.ts:238 (a slotted record-page block), record-alert.mdx:27 (visible predicate over record state), page.zod.ts:1436 / record-alert.mdx:33–34 (dismissible per viewer). The contrast is the right one for the eval: standing state → banner, this write's outcome → notice. The rule leads the author to the notice only for the "what this save started" case and keeps record:alert for standing state — that is "only where it is right". Holds (see should-fix 2 on "every visit").
  • "One message per write, the last call wins; never stored" — data-engine.ts:157–162; protocol.zod.ts:2761–2764; record-save-feedback.ts:159 (client side, last parked notice wins). Holds.
  • "Insert/update dispatches only. A write nobody waits on (batch, seed, import, a nested write) sets nothing" — hook-notice.ts:16–18, :48–52 (no listener → no-op); hook.zod.ts:1623–1629 (nested write carries its own options, so its notice never reaches this answer). Holds.
  • "Not a non-empty string ⇒ the write refuses; a refused write answers its error, no notice" — hook-notice.ts:42–47 throws TypeError; engine.ts:6016–6027 triggerHooks awaits each handler with no catch, so the throw is the write's; pinned at hook-notice.integration.test.ts:118–120 (rejects.toThrow); data-engine.ts:159–160 for the refused-write rule. Holds.
  • "A flow a record create/update trigger starts sets it with a save_notice node (config.message, {{ }} renders); refused in any other flow, and after an approval / wait" — save-notice-node.ts:46–60 (message required, {{ }}), :80–91 (render); save-notice-placement.ts:31 (record-(before|after)-(create|insert|update|write)), :52–80 (parse-time refusal at any region depth, so "any other flow" includes schedule/api/screen/autolaunched/subflow starts); runtime refusal after a suspension: record-change-trigger.ts:183–190 (channel closed once the handler returns), :366–369, save-notice-node.ts:65–73 (a run holding no channel refuses by name, naming approval / wait); subflow-node.ts:123 and map-node.ts:246 strip the channel from child runs. approval and wait are the spellings skills/objectstack-automation/SKILL.md:67,95 teach. Holds.

The three deleted blocks, each against the rest of the file:

  • Decision Tree (items 1–6) — item 1 (record → omit) is the Lifecycle Classes row "policies FORBIDDEN" + Safety Semantics "record is the implicit default"; item 2 (audit + retention, archive if cold storage) is the audit row + ConsentLog example + the archive Safety bullet; item 3 (telemetry + retention, rotation for hottest) is the telemetry row + ApiCallLog example + the Rotation bullet; item 4 (transient + ttl on expiry field) is the transient row + ImportTicket; item 5 (event + short ttl, hours) is the event row "very short TTL (hours)"; item 6 (onlyWhen, not combinable with rotation/archive) is the AutomationRun example comment "paused rows never reaped" + the fifth Validation rule. No taught fact lost. Nothing in the skills tree links to #decision-tree.
  • Telemetry-growth anecdote — an incident narrative; the rule sentence that carried its lesson ("any append-only, high-write-rate object MUST declare a lifecycle block") stays. No fact lost.
  • "Nothing is deleted." — restates "rows are never deleted" at the top of Why This Exists. No fact lost.

Eval scenario verdict: an author who opens this page learns the notice, its spelling on both doors, where it surfaces, and the exact line between it and a record:alert banner — the rule leads to the notice and only where that is right. The remaining weakness is routing, not teaching (③-4).

Must-fix: none.

Should-fix:

  1. Lead with body, name handler as the deprecated door. references/data-hooks.md:317–325 (this skill's own hooks rule) marks handler Deprecated and body Preferred for new code; the new section's "inline handler and sandboxed body alike" is true of the code (hook.zod.ts:1612–1617) but lists the deprecated door first and as an equal. One reordering — "sandboxed body (and the deprecated inline handler) alike" — keeps the fact and the skill's own preference in step. Token-neutral or near it.
  2. "shown by the record's state on every visit" — a record:alert is dismissible per viewer (page.zod.ts:1436; record-alert.mdx:33–34), so "on every visit" overstates it. "shown whenever the record's state says so" (or "until dismissed") keeps the contrast without the wrong word.
  3. Intro sentence reads as self-contradicting: "Not to be confused with lifecycle hooks … this page is … plus the save notice a hook hands the saver." The page now does teach one hook member. Rephrase to "distinct from lifecycle hooks, which run business logic on writes; this page covers the rows' retention, rotation and archival — and, at the end, the save notice a hook hands the saver." Non-blocking; SKILL.md line 45's "(distinct from lifecycle hooks below)" needs the same touch in the follow-up.

Implemented-by: claude/issue-28253-save-notice-skill
Reviewed-by: session_017ihL2g7RyXa8ErM6GAkHyc

VERDICT: PASS

Read at 2026-11-01T21:37Z.

…anner contrast, reword the intro

Contract-review should-fixes: the sandboxed body is named first and the
inline handler as the deprecated door, matching the hooks reference; a
record:alert banner shows whenever the record's state says so rather
than on every visit, since a viewer can dismiss it; the intro no longer
reads as contradicting itself about hooks. Paid inside the file's token
ceiling by shortening the section heading and one intro clause.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ihL2g7RyXa8ErM6GAkHyc
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#2 · marchtian · session_017ihL2g7RyXa8ErM6GAkHyc

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 3fc28cac1f20a6ff2b5f5e8ffad392a3ee83f8ce
Local-runs: none
Scope: skills/objectstack-data/rules/lifecycle.md + packages/spec/src/data/hook.zod.ts (HookContext.notice), skills/objectstack-data/references/data-hooks.md (handler-vs-body table), packages/spec/src/ui/page.zod.ts + content/docs/protocol/objectui/record-alert.mdx (record:alert visible / dismissible), scripts/check-skills-token-ratchet.mjs (lifecycle.md ceiling row), and the cited code of review 6564252529, re-read where a changed sentence cites it. Remainder of the diff (.changeset/28253-skill-save-notice.md, unchanged this round) is the lane seat's ACCEPT at TIER_DEFAULT.

Delta round on review 6564252529 (head 63afc09f). Inputs: card #28253 body and every comment through the delta report 6564403990; PR #28813 body, file list and comments; git diff 63afc09f..3fc28cac (one file, 9/9) and the net diff against main (2 files, +35/−19; main moved 70bf434 → 2081a5e under the PR and touched neither skills/objectstack-data/**, packages/skills nor the ceiling row); the 41 check-runs on 3fc28cac. Dispatch order not read.

① Derived judgments

  • Published surface: unchanged from round one — @objectstack/skills dist carries the page; docs-only, no schema, key, type or behaviour change. The delta is nine lines of prose in the same file. Right.
  • Accept set (token ratchet): ceiling row ['skills/objectstack-data/rules/lifecycle.md', 1590] (check-skills-token-ratchet.mjs:702 on current main) untouched; the head file is 6358 bytes → ceil(6358/4) = 1590 tokens, headroom 0 — exactly main's own reading before this PR, so the file lands pinned AT its ceiling, which the ratchet accepts (≤). The round-one headroom of 3 is spent on the fixes. Any later edit to this file pays by deletion; not a finding. Right. (Computed from wc -c on the fetched blob, not a gate run.)
  • Package SKILL.md line sum and bundle total unchanged / back to main's. Right.
  • Clause-②: no, Metadata-protocol: none: still right; nothing declared on main is taken away.
  • Gates: the 41 check-runs on 3fc28cac have all completed — 28 success, 13 skipped (UI/docs/opt-in lanes the diff does not reach), 0 failure, 0 cancelled, 0 pending. Successes include Lint · repo & tooling gates, Lint · docs & code guards, Lint · PM dispatch-gates, Lint · CI, release & engine gates, Check Changeset, Governed Surface Queue Guard, PR-body guards, Check PR Size, Build Core, Test Core (both shards), every Type Check lane. Those conclusions are the gate verdicts; nothing was re-run.
  • Tier H: skills/** is governed; the PR stays draft / needs-user-decision until an authorised approver acts. This record does not substitute for that approval.

② Semver level

.changeset/28253-skill-save-notice.md unchanged → '@objectstack/skills': patch. Still correct for a docs-only change in a published package; skip-changeset does not apply. The changeset body still describes the tree: the three round-one deletions it names all stand, and this round's two cuts (a heading tail, one intro clause) are below the grain a changeset records. Clause-②: no right.

③ Boundary flags

Delta report 6564403990, each entry answered:

  1. Deviation — worktree kept this round, clean at 3fc28cac. Answered: as the patch order asks; nothing to do.
  2. Deviation — changeset body unchanged. Answered: right, see ②.
  3. open_questions: none. Nothing to answer.
  4. out_of_scope — SKILL.md index clause, data-hooks.md ctx.notice row, automation skill node table; carrier the seat. Answered: carried — the seat filed the index and data-hooks follow-up as skills(objectstack-data): the save notice is undiscoverable — the SKILL.md rule index calls rules/lifecycle.md retention-only, and references/data-hooks.md has no ctx.notice row #28819, and skills(automation): teach the save notice — the save_notice flow node and ctx.notice in hooks (#28251) #28787 already covers the automation skill. Not re-flagged here.

Round-one ③ items 1–3 (labels, SKILL.md untouched, cleanup) stand as answered in 6564252529.

④ Design judgment

skills/objectstack-data/rules/lifecycle.md — the three round-one should-fixes, each against the code it cites, then the two cuts that paid for them against the rest of the file.

  • Should-fix 1 (lead with body, name handler deprecated) — now "a sandboxed body or the deprecated inline handler, no capability". body first; handler named as the deprecated door, matching references/data-hooks.md:324 (Deprecated — prefer body); the fact that both reach the member is kept and still true to hook.zod.ts:1612–1617 and quickjs-runner.ts:955–967 (no capability gate). Resolved.
  • Should-fix 2 ("on every visit") — now "page metadata shown whenever the record's state says so". That is the visible predicate (record-alert.mdx:27) and no longer contradicts per-viewer dismissal (page.zod.ts:1436; record-alert.mdx:33–34). The contrast with "what this save started" is intact, so the rule still leads the eval author to the notice only for this write's outcome and leaves record:alert to standing state. Resolved.
  • Should-fix 3 (self-contradicting intro) — now "Distinct from lifecycle hooks (…), which run logic on writes: this page covers the rows' retention, rotation and archival — and, at its end, the save notice a hook hands the saver." The page's one hook member is named as its tail, not its subject; the clause "which run logic on writes" restores, shortened, the "those run business logic on data operations" that main carried and round one had dropped. Resolved.

The two cuts:

  • Heading "Save Notice — what the save set in motion" → "Save Notice". The dropped tail restated the paragraph's last sentence ("the notice is for what this save started") and the first sentence's example. No anchor in the skills tree points at the old heading. Nothing lost.
  • Intro clause "Not to be confused with … this page is …" → "Distinct from …, which run logic on writes: this page covers …". A wording swap, net shorter; the fact the hooks reference carries (hooks run logic on writes) is now stated rather than implied. Nothing lost.

Everything else in the file is byte-identical to the round-one head, whose claim-by-claim verification in 6564252529 stands (hook-notice.ts, engine.ts, hook.zod.ts, data-engine.ts, protocol.zod.ts, the sandbox runners, the two toasts, save-notice-node.ts, save-notice-placement.ts, record-change-trigger.ts, subflow/map nodes; the Decision Tree, anecdote and "Nothing is deleted." deletions lose no taught fact).

Eval scenario: unchanged verdict — an author who opens the page is led to ctx.notice on the preferred door, told where it surfaces, and given the right line between it and a record:alert banner. Routing from the skill's index is #28819's.

Must-fix: none.

Should-fix: none.

Implemented-by: claude/issue-28253-save-notice-skill
Reviewed-by: session_017ihL2g7RyXa8ErM6GAkHyc

VERDICT: PASS

Read at 2026-11-01T21:53Z.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#2 · marchtian · session_017ihL2g7RyXa8ErM6GAkHyc

维护者速读(席位终稿 · head 3fc28cac1f20a6ff2b5f5e8ffad392a3ee83f8ce)

  • 改了什么:对外发布的 objectstack-data skill 的 rules/lifecycle.md 末尾加了一节「Save Notice」。内容:create/update 钩子(沙箱 body 或已弃用的内联 handler)可调用 ctx.notice(message),给保存者一条 info 级提示,随保存 toast 显示;一次写入只有一条,最后一次调用生效,不落库;它和校验 warnings、record:alert 横幅不是一回事。flow 端对应 save_notice 节点,只能用在 create/update 记录触发的 flow 里。
  • 为什么改:Save answer: a hook or record-triggered flow can attach one user-facing notice to a record save, shown with the save toast #28000/Save notice from a sandboxed hook or a flow: hook context and flow node set the write answer's notice #28251 已经上线保存提示,但 skill 没教。AI 作者想告诉用户「已提交审批」时,还会去搭 record:alert 横幅。
  • 风险与回滚:只改 skill 文本,外加一个 @objectstack/skills patch changeset。为腾出 token 预算删了 Decision Tree 一节和一处轶事;复审确认删掉的内容在同文件的表格和示例里都还有。回滚 = revert。
  • 先看:skills/objectstack-data/rules/lifecycle.md 末节「Save Notice」。
  • 两分钟验证:check-skills-token-ratchet lifecycle.md 1590/1590,通过。
  • 需要你做的:批准本 PR(skills/** 对外发布,以你的批准为裁决)。契约复审 PASS:6564433152(Head-sha: 3fc28cac1f20a6ff2b5f5e8ffad392a3ee83f8ce)。

席位意见:ACCEPT.

席位核对

@os-zhuang
os-zhuang marked this pull request as ready for review November 2, 2026 00:00
@os-zhuang
os-zhuang enabled auto-merge November 2, 2026 00:01
@os-zhuang
os-zhuang added this pull request to the merge queue Nov 2, 2026
Merged via the queue into main with commit 56bd596 Nov 2, 2026
50 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-28253-save-notice-skill branch November 2, 2026 00:20
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 tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

skills: teach the save notice in the objectstack-data lifecycle rule

2 participants