You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Save notice from a sandboxed hook or a flow: hook context and flow node set the write answer's notice #28251
Filing gate: ③ the server half of #28000, whose contract half lands in PR #28247 (claim 6528694653; os-dev-report 6531067088; contract review 6531609713 PASS). ⛔ Not a claim. Triage sets the lane and grade.
Reach: HotCRM sends a deal for approval on save and shows a record:alert banner as the workaround (#27380 row 3). Its hooks are lowered into sandboxed bodies, so they cannot reach the listener PR #28247 adds.
CreateDataResponseSchema / UpdateDataResponseSchema carry notice (one non-empty string, info, last call wins, never stored), set through WriteObservabilityOptions.onNotice(message) on ctx.input.options. Only a code handler holding the write's options can call it.
No hook-context spelling a sandboxed body can call, and no flow node sets it. Batch writes (createManyData / batchData), clone and flow-written saves do not answer notice (the card scoped single-record create and update).
Wanted
A hook-context member a sandboxed body (and a handler the build lowers into one) can call to set the notice. The engine binds it from the write's onNotice at each dispatch site; the body runner marshals it.
A flow node that sets the notice on the running record-triggered write.
Contract review should-fix (a): the surface will want a translatable message (a message id plus params, or a catalog key) rather than only a plain string. Decide it here, and widen the answer key compatibly if needed.
Dedupe words: hook save notice, ctx notice, flow notice on save, sandboxed hook notice.
Should-fix (a), a translatable message (a message id plus params, or a catalog key), is a published shape. If the thread picks one, the spec half comes as its own spec card, or under a declared one-PR exception with Clause-② stated, and a contract review is owed.
Blocked-by: objectstack-ai/objectstack#28247 is added to the body. pm:blocked.
Claim: PM loop round 1 · card thread · 2026-11-01T15:49Z
Session: session_01FEkQ5kE5BrPUPNMMyv6oCH (the seat); the card thread is session_01FEkQ5omF1nCbQTGgW1nbRA
Account: os-project-manager (the seat's linked user; the card's assignee)
Branch: claude/project-thread-9kerqb (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
Worktree: the card thread's own container checkout
Domain: domain:server
Seat: domain:server#3
Ruling-ref: the maintainer, 2026-11-01T03:46Z: 「server 车道现在就是你负责,任务有一大堆啊,都可以派,包括 p3」, with 2026-10-26T10:29Z 「p1 p2 处理完了,就可以处理 p3」, and 2026-11-01T06:39Z 「server 队列还是很多任务,再开一个席位吧」. Also 2026-11-01T10:18Z: dispatch every graded, unclaimed server card. Grade: triage p3 (6532349633, unblocked 6533331783).
File surface: packages/spec/src/** (hook-context member, flow node or node member, notice widening), packages/objectql/src/** / packages/runtime/src/** (dispatch-site binding), the sandboxed body runner, packages/services/service-automation/src/** (the flow node), tests through the REST door, and a changeset. ⛔ No hotcrm edits.
Container & model: M, mode:thread, model: opus (--tier names no path mandate: PM's call, default), effort: default (the coordinator sets effort only from the maintainer's words)
Clause-②: yes (widening) — expected: a new hook-context member, a flow node or member, and a compatibly widened notice; spec path ⇒ CONTRACT_REVIEW_TIER review before landing. The dev measures and declares the final value.
Metadata-protocol: none (expected; the dev measures and declares)
Responsibility: the code is the hook dispatch sites, the sandboxed body runner and the flow engine · the platform path is the single-record create/update answer notice (PR #28247) · reached by an app whose sandboxed hook or record-triggered flow wants to tell the saver something (HotCRM #27380 row 3)
Thread-read: 6533331783
Premise: holds as filed: PR #28247 (contract half) merged 2026-10-30T20:25Z; no sandboxed or flow path sets notice (the dev re-measures on current main)
Serial constraints cleared: none found: no open PR on the notice path at this read; the dev re-reads overlap at start and before pr_create.
Resume-from: none — fresh from origin/main
State: → pm:dispatched, assigned to os-project-manager, recorded by this claim. Order: pm-orders/server/order-28251.md (project folder).
domain:server#3 · os-project-manager · session_01FEkQ5omF1nCbQTGgW1nbRA
os-dev-report
{
"issue": 28251,
"status": "done",
"branch": "claude/project-thread-9kerqb",
"pr": "#28753",
"head": "3e38b8142d757f1816a093bb8df2e91e327843c2",
"session": "session_01FEkQ5omF1nCbQTGgW1nbRA",
"premise_still_valid": true,
"summary": "One save-notice channel with two faces. Hooks: HookContext gains notice, bound by ObjectQL triggerHooks on insert/update dispatches from the write's own onNotice listener, marshalled into QuickJS bodies; a non-string message throws a TypeError naming the call. Flows: new builtin save_notice node (SaveNoticeConfigSchema, one double-brace text slot); the record-change trigger hands the run AutomationContext.saveNotice and closes it when the write answers; FlowSchema refuses the node outside a create/update record trigger (INVALID_METADATA 422 at nodes..type), and the executor refuses by name on a resumed leg, a subflow/map child or a run with no write. Message form: the platform translatable shape is a server-rendered catalog key, so the flow face uses flows..notices.<node_id>.message in the writer's locale (extract, coverage, validate and liveness wired) and the wire notice stays a plain string; a hook-face catalogue is left as needs_decision. Node vs member measured: HotCRM's submit flow has no record-write node before the suspending approval, and end runs only after it.",
"tests": "Live REST red/green on the showcase (pnpm --filter @objectstack/example-showcase dev -- --fresh): base 4bf48a8 (spec/objectql/runtime/trigger/automation rebuilt from base) POST /api/v1/data/probe1_deal -> 201 with no notice, PATCH -> 200 with no notice, a save_notice flow under record-after-delete saved 200; head POST -> 201 "notice":"Sent Acme for approval" (sandboxed hook body ctx.notice), PATCH -> 200 "notice":"Moved Acme to review" (record-after-update flow save_notice), delete-trigger flow -> 422 INVALID_METADATA issues[0].path nodes.1.type. New/extended tests: spec save-notice-node.test.ts 17/17; objectql protocol-save-notice-answer.test.ts 22/22; runtime sandbox hook-notice.integration.test.ts 1/1 (real ObjectQL + SqlDriver + QuickJS); trigger-record-change save-notice-channel.test.ts 3/3 + record-change-integration #28251 2/2; service-automation builtin/save-notice-node.test.ts 7/7; lint validate-translation-references save notices 3/3; cli i18n-flow-save-notice-coverage.test.ts 6/6. Package suites (vitest --maxWorkers=2, under os-verify-lock): trigger-record-change 12 files / 121 tests pass; service-automation 252 / 3204 pass; objectql 516 / 9937 pass; runtime full 508 / 9204 pass (19 skipped); lint 184 / 7721 pass; spec full 873 files: 24666 pass + 1 red (scripts/dropped-refinements.test.ts header totals 247 vs 248) fixed in 3e38b81 and rerun 27/27; spec enumeration pins (builtin node keys/values, text slots, flows translation face) updated in 39ca46a, 159/159; cli i18n flow coverage trio 40/40; showcase coverage + connector-ping 16/16. NOT MEASURED: cli full suite (stopped after ~30 of ~550 files, no failure seen, spawn-heavy; left to CI Test Core). Ablations via scripts/ablation-replace.mjs, each red then restored to the HEAD blob (git diff HEAD empty): A1 bindHookNotice call deleted (objectql 8 red); A2 non-string refusal disabled (3 red); A3 QuickJS install disabled (runtime 1 red); A4 body-runner spread disabled (1 red); A5 trigger channel spread deleted (2 red); A6 trigger close in finally deleted (1 red); A7 subflow delete removed (1 red); map delete removed (1 red, heard ["From the child"]); A8 executor no-channel refusal disabled (2 red); A9 placement refusal sliced to none (spec 5 red); A10 lint notices.add deleted (2 red); A11 lint orphan check short-circuited (2 red); A12 cli extract skipped (4 red).",
"gates": "dispatch-gates --commands on the final diff: 116 derived. Each run with < /dev/null, exit captured before any pipe. --ran: 116 accounted, 115 run, 1 NOT MEASURED (pnpm check:dual-build-cjs-loads, exit 3 PREREQUISITE NOT MET: needs every package dist). First-pass non-zeros and their resolution: check-plugin-teardown-shape --self-test exit 3 (shallow clone) -> after git fetch --depth=1 of the fixture commit, exit 0; spec check:skill-examples exit 3 (client-react unbuilt) -> after building it, exit 0; check:platform-checklist exit 1 (automation.flow-node-type-matrix variants stale, FlowNodeAction 21) -> item revised to revision 12 with save_notice and enumSource.expect 21, exit 0; check:slot-lookup exit 1 (two new getService(...) as any in record-change-integration.test.ts) -> typed as TestObjectQLEngine, exit 0; check:i18n / check:i18n-coverage / check:i18n-walk-parity rerun after the showcase translation, exit 0; check:examples-live-imports (new after the showcase edit) exit 0. pnpm lint (eslint --no-inline-config) over packages/spec, objectql, runtime, triggers/trigger-record-change, services/service-automation, cli, lint: exit 0. CI: no check-runs on head 3e38b81 at report time (get_check_runs total_count 0).",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"line_budget": "no budget in the order; diff 63 files +1655 -45 (hand-written non-test about +730; the rest tests and generated spec artifacts)",
"deviations": [
"Gates ran on head ae1462f; later commits (dc106ae showcase/checklist/test typing, 39ca46a spec pin updates, 3e38b81 ledger header totals) were followed by targeted reruns of the gates and suites they touch, not by a second full gate pass.",
"Scope additions beyond the order's list, each forced by a gate: the showcase authors save_notice on showcase_task_completed with zh-CN (the showcase coverage test requires every FlowNodeAction member authored), and docs/qa/platform-checklist automation.flow-node-type-matrix revision 12.",
"dropped-refinements.baseline.json grows by one entry (automation/SaveNoticeConfig, the text-slot superRefine every text-slot config carries) and 9 notices sites under the translation bundle schemas; header totals 248 / 961.",
"git push ran 7 times on the branch (one per compilable step), not once."
],
"files_changed": [
".changeset/28251-save-notice-from-hooks-and-flows.md",
"content/docs/references/api/protocol.mdx",
"content/docs/references/automation/builtin-node-config.mdx",
"content/docs/references/automation/flow.mdx",
"content/docs/references/data/hook.mdx",
"content/docs/references/index.mdx",
"content/docs/references/system/translation.mdx",
"docs/audits/2026-07-unknown-key-strictness-ledger.counts/automation.md",
"docs/audits/2026-07-unknown-key-strictness-ledger.counts/system.md",
"docs/qa/platform-checklist/areas/automation.json",
"examples/app-showcase/src/automation/flows/index.ts",
"examples/app-showcase/src/system/translations/index.ts",
"packages/cli/src/utils/i18n-extract.ts",
"packages/cli/test/i18n-flow-save-notice-coverage.test.ts",
"packages/lint/src/flow-cel-root-scope.ts",
"packages/lint/src/validate-translation-references.test.ts",
"packages/lint/src/validate-translation-references.ts",
"packages/objectql/src/engine.ts",
"packages/objectql/src/hook-notice.ts",
"packages/objectql/src/protocol-save-notice-answer.test.ts",
"packages/runtime/src/sandbox/body-runner.ts",
"packages/runtime/src/sandbox/hook-notice.integration.test.ts",
"packages/runtime/src/sandbox/quickjs-runner.ts",
"packages/runtime/src/sandbox/script-runner.ts",
"packages/services/service-automation/src/builtin/index.ts",
"packages/services/service-automation/src/builtin/map-node.ts",
"packages/services/service-automation/src/builtin/save-notice-node.test.ts",
"packages/services/service-automation/src/builtin/save-notice-node.ts",
"packages/services/service-automation/src/builtin/subflow-node.ts",
"packages/spec/api-surface/automation.json",
"packages/spec/api-surface/contracts.json",
"packages/spec/api-surface/data.json",
"packages/spec/api-surface/system.json",
"packages/spec/authorable-surface/automation.json",
"packages/spec/authorable-surface/data.json",
"packages/spec/declaration-map/automation.json",
"packages/spec/dropped-refinements.baseline.json",
"packages/spec/export-origins/automation.json",
"packages/spec/export-origins/contracts.json",
"packages/spec/export-origins/data.json",
"packages/spec/export-origins/system.json",
"packages/spec/json-schema.manifest/automation.json",
"packages/spec/liveness/state-counts/translation.md",
"packages/spec/liveness/translation.json",
"packages/spec/src/api/protocol.zod.ts",
"packages/spec/src/automation/builtin-node-config.zod.ts",
"packages/spec/src/automation/flow-builtin-node-config-keys.test.ts",
"packages/spec/src/automation/flow-builtin-node-config-values.test.ts",
"packages/spec/src/automation/flow-node-config-refusals.ts",
"packages/spec/src/automation/flow-text-slot-template.test.ts",
"packages/spec/src/automation/flow-text-slot-template.ts",
"packages/spec/src/automation/flow.zod.ts",
"packages/spec/src/automation/save-notice-node.test.ts",
"packages/spec/src/automation/save-notice-placement.ts",
"packages/spec/src/contracts/automation-service.ts",
"packages/spec/src/contracts/data-engine.ts",
"packages/spec/src/data/hook.zod.ts",
"packages/spec/src/system/flows-translation-face.test.ts",
"packages/spec/src/system/i18n-resolver.ts",
"packages/spec/src/system/translation.zod.ts",
"packages/triggers/trigger-record-change/src/record-change-integration.test.ts",
"packages/triggers/trigger-record-change/src/record-change-trigger.ts",
"packages/triggers/trigger-record-change/src/save-notice-channel.test.ts"
],
"mcp_calls": "4 — mcp__github__issue_read ×4 (get #28251, get_comments #28251 ×2, get #27380); no write tool",
"api_writes": "1 relay dispatch so far — fleet-write pr_create (POST /repos/objectstack-ai/objectstack/dispatches -> run 41330718852 created draft PR #28753, no labels, no assignees, read-back identical 8355 bytes); this report comment is the second (post-stamped.mjs). No GitHub MCP writes.",
"open_questions": [
{
"question": "Should the HOOK face of the save notice get a translation catalogue (hooks..notices..message, a hook declaring notices the way it declares refusals), so a shipped body can call ctx.notice with a key and the server renders it in the writer's locale?",
"status": "needs_decision",
"options": [
"A — not now: ctx.notice takes the rendered sentence; a hook that wants a localized notice builds it itself. Flows already translate through flows..notices.",
"B — add a declared notices catalogue on HookSchema plus ctx.notice({ key, params }) rendered server-side, mirroring hooks..refusals..message."
],
"analysis": {
"real_business_need": "Measured: the one named consumer (HotCRM deal submission, objectstack-ai/objectstack#27380 row 3) is a flow, which this PR translates. No shipped hook body in hotcrm or the showcase sets a notice today, so B has no measured writer yet.",
"long_term_soundness": "B is the contract-first symmetric shape (hooks already translate refusals by catalogue). A leaves hooks on author-built strings, which is the same posture hook refusals had before objectstack-ai/objectstack#26170. Neither is a workaround.",
"ai_error_resistance": "B adds a declared surface that must be enforced (unknown key refused at publish and at call), more for an AI to get wrong if half-built. A keeps one string argument, refused loudly when it is not a non-empty string.",
"startup_focus": "A: no pull for the hook catalogue yet; implementation-first says add it with its first real writer."
},
"recommendation": "A, because the only measured consumer is a flow and is served here; open B with the first hook that needs a localized notice."
}
],
"out_of_scope_findings": [
"carrier: hotcrm lane · HotCRM can retire its record:alert banner workaround (#27380 row 3) for a save_notice node ahead of the approval in its deal-submit flow once this lands · noted, not filed · dedupe words: hotcrm record:alert save notice approval",
"carrier: none · the Studio palette (ui/packages/app-shell/src/views/metadata-admin/previews/flow-canvas-parts.tsx NODE_PALETTE) does not list save_notice; the node saves, runs and its descriptor is in GET /api/v1/automation/actions · noted in Acceptance notes, not filed (polish) · dedupe words: flow palette save_notice studio"
]
}
Seat ruling · open question in os-dev-report 6562102476 · 2026-11-01T18:31Z
Question: should the hook face of the save notice get a translation catalogue (hooks.<hook>.notices.<KEY>.message, with ctx.notice({ key, params }) rendered server-side)?
Ruling: A, not now.ctx.notice takes the rendered sentence. The flow face keeps its catalogue, flows.<flow>.notices.<node_id>.message.
B is a declared surface that needs publish-time and call-time enforcement. Building it with no writer goes against implementation-first, and it is new governed surface with no pull behind it.
Ruling-ref: the maintainer, 2026-11-01T12:11Z: 「定级你可以自己决定」. The standing rule is that new gates default to no. This ruling does not change PR #28753's scope.
PR #28753 accepted (seat ACCEPT #28753 (comment), contract review PASS #28753 (comment)) and enqueued for merge. Landing record follows after merge, with the follow-up card for the review should-fixes and the hotcrm record:alert removal.
declared at line 61; FlowNodeAction member at line 56
Landed: merged as a00f3359; Fixes objectstack-ai/objectstack#28251 closed the card. The save notice now has two faces on one channel, the write's own onNotice:
Hooks, including sandboxed shipped bodies, call ctx.notice(message).
Record create/update flows use the new save_notice node. Its message is translatable under flows.<flow>.notices.<node_id>.message.
The wire notice stays a plain string.
save_notice outside a create/update record trigger is refused at publish (INVALID_METADATA 422). On a resumed leg, a subflow or a map child it is refused by name at run time.
Contract review (CONTRACT_REVIEW_TIER) PASS 6562490492 on the landed head 36c1fb8f; seat ACCEPT 6562635160. Open question ruled A (no hook-face catalogue now), 6562118323.
Follow-ups named in the landing record: #28786 (the review should-fixes, domain:server, p3), #28787 (teach save_notice / ctx.notice in the automation skill, domain:skills) and objectstack-ai/hotcrm#2398 (HotCRM retires the record:alert workaround, repo:hotcrm).
Activity
objectstack-fleet commentedon Oct 30, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: p3 ·
domain:server·area:automation·pm:blockedon #28247 · 2026-10-30T20:01Z⛔ Not a claim.
notice: a hook-context member, and a flow node that sets it. The parent Save answer: a hook or record-triggered flow can attach one user-facing notice to a record save, shown with the save toast #28000 is p3, so this card is p3 too.Clause-②stated, and a contract review is owed.Blocked-by: objectstack-ai/objectstack#28247is added to the body.pm:blocked.Generated by Claude Code
objectstack-fleet commentedon Oct 30, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: unblocked · p3 · 2026-10-30T20:58Z
⛔ Not a claim.
Blocked-by: objectstack-ai/objectstack#28247, and PR feat(spec): a hook can tell the person who saved a record what the save set in motion #28247 merged at 2026-10-30T20:25Z.pm:blockedis removed. It stays at p3 withoutpm:queue.Generated by Claude Code
objectstack-fleet commentedon Nov 1, 2026
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH
Claim: PM loop round 1 · card thread · 2026-11-01T15:49Z
Session:
session_01FEkQ5kE5BrPUPNMMyv6oCH(the seat); the card thread issession_01FEkQ5omF1nCbQTGgW1nbRAAccount:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-9kerqb(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#3Ruling-ref: the maintainer, 2026-11-01T03:46Z: 「server 车道现在就是你负责,任务有一大堆啊,都可以派,包括 p3」, with 2026-10-26T10:29Z 「p1 p2 处理完了,就可以处理 p3」, and 2026-11-01T06:39Z 「server 队列还是很多任务,再开一个席位吧」. Also 2026-11-01T10:18Z: dispatch every graded, unclaimed server card. Grade: triage p3 (
6532349633, unblocked6533331783).File surface:
packages/spec/src/**(hook-context member, flow node or node member,noticewidening),packages/objectql/src/**/packages/runtime/src/**(dispatch-site binding), the sandboxed body runner,packages/services/service-automation/src/**(the flow node), tests through the REST door, and a changeset. ⛔ No hotcrm edits.Container & model:
M,mode:thread,model: opus(--tiernames no path mandate: PM's call, default),effort: default (the coordinator sets effort only from the maintainer's words)Clause-②: yes (widening) — expected: a new hook-context member, a flow node or member, and a compatibly widened
notice; spec path ⇒CONTRACT_REVIEW_TIERreview before landing. The dev measures and declares the final value.Metadata-protocol: none (expected; the dev measures and declares)
Responsibility: the code is the hook dispatch sites, the sandboxed body runner and the flow engine · the platform path is the single-record create/update answer
notice(PR #28247) · reached by an app whose sandboxed hook or record-triggered flow wants to tell the saver something (HotCRM #27380 row 3)Thread-read: 6533331783
Premise: holds as filed: PR #28247 (contract half) merged 2026-10-30T20:25Z; no sandboxed or flow path sets
notice(the dev re-measures on currentmain)Serial constraints cleared: none found: no open PR on the notice path at this read; the dev re-reads overlap at start and before
pr_create.Resume-from: none — fresh from
origin/mainState: →
pm:dispatched, assigned toos-project-manager, recorded by this claim. Order:pm-orders/server/order-28251.md(project folder).objectstack-fleet commentedon Nov 1, 2026
domain:server#3 · os-project-manager · session_01FEkQ5omF1nCbQTGgW1nbRA
os-dev-report
{
"issue": 28251,
"status": "done",
"branch": "claude/project-thread-9kerqb",
"pr": "#28753",
"head": "3e38b8142d757f1816a093bb8df2e91e327843c2",
"session": "session_01FEkQ5omF1nCbQTGgW1nbRA",
"premise_still_valid": true,
"summary": "One save-notice channel with two faces. Hooks: HookContext gains
notice, bound by ObjectQL triggerHooks on insert/update dispatches from the write's own onNotice listener, marshalled into QuickJS bodies; a non-string message throws a TypeError naming the call. Flows: new builtinsave_noticenode (SaveNoticeConfigSchema, one double-brace text slot); the record-change trigger hands the run AutomationContext.saveNotice and closes it when the write answers; FlowSchema refuses the node outside a create/update record trigger (INVALID_METADATA 422 at nodes..type), and the executor refuses by name on a resumed leg, a subflow/map child or a run with no write. Message form: the platform translatable shape is a server-rendered catalog key, so the flow face uses flows..notices.<node_id>.message in the writer's locale (extract, coverage, validate and liveness wired) and the wirenoticestays a plain string; a hook-face catalogue is left as needs_decision. Node vs member measured: HotCRM's submit flow has no record-write node before the suspending approval, andendruns only after it.","tests": "Live REST red/green on the showcase (pnpm --filter @objectstack/example-showcase dev -- --fresh): base 4bf48a8 (spec/objectql/runtime/trigger/automation rebuilt from base) POST /api/v1/data/probe1_deal -> 201 with no notice, PATCH -> 200 with no notice, a save_notice flow under record-after-delete saved 200; head POST -> 201 "notice":"Sent Acme for approval" (sandboxed hook body ctx.notice), PATCH -> 200 "notice":"Moved Acme to review" (record-after-update flow save_notice), delete-trigger flow -> 422 INVALID_METADATA issues[0].path nodes.1.type. New/extended tests: spec save-notice-node.test.ts 17/17; objectql protocol-save-notice-answer.test.ts 22/22; runtime sandbox hook-notice.integration.test.ts 1/1 (real ObjectQL + SqlDriver + QuickJS); trigger-record-change save-notice-channel.test.ts 3/3 + record-change-integration #28251 2/2; service-automation builtin/save-notice-node.test.ts 7/7; lint validate-translation-references save notices 3/3; cli i18n-flow-save-notice-coverage.test.ts 6/6. Package suites (vitest --maxWorkers=2, under os-verify-lock): trigger-record-change 12 files / 121 tests pass; service-automation 252 / 3204 pass; objectql 516 / 9937 pass; runtime full 508 / 9204 pass (19 skipped); lint 184 / 7721 pass; spec full 873 files: 24666 pass + 1 red (scripts/dropped-refinements.test.ts header totals 247 vs 248) fixed in 3e38b81 and rerun 27/27; spec enumeration pins (builtin node keys/values, text slots, flows translation face) updated in 39ca46a, 159/159; cli i18n flow coverage trio 40/40; showcase coverage + connector-ping 16/16. NOT MEASURED: cli full suite (stopped after ~30 of ~550 files, no failure seen, spawn-heavy; left to CI Test Core). Ablations via scripts/ablation-replace.mjs, each red then restored to the HEAD blob (git diff HEAD empty): A1 bindHookNotice call deleted (objectql 8 red); A2 non-string refusal disabled (3 red); A3 QuickJS install disabled (runtime 1 red); A4 body-runner spread disabled (1 red); A5 trigger channel spread deleted (2 red); A6 trigger close in finally deleted (1 red); A7 subflow delete removed (1 red); map delete removed (1 red, heard ["From the child"]); A8 executor no-channel refusal disabled (2 red); A9 placement refusal sliced to none (spec 5 red); A10 lint notices.add deleted (2 red); A11 lint orphan check short-circuited (2 red); A12 cli extract skipped (4 red).",
"gates": "dispatch-gates --commands on the final diff: 116 derived. Each run with < /dev/null, exit captured before any pipe. --ran: 116 accounted, 115 run, 1 NOT MEASURED (pnpm check:dual-build-cjs-loads, exit 3 PREREQUISITE NOT MET: needs every package dist). First-pass non-zeros and their resolution: check-plugin-teardown-shape --self-test exit 3 (shallow clone) -> after git fetch --depth=1 of the fixture commit, exit 0; spec check:skill-examples exit 3 (client-react unbuilt) -> after building it, exit 0; check:platform-checklist exit 1 (automation.flow-node-type-matrix variants stale, FlowNodeAction 21) -> item revised to revision 12 with save_notice and enumSource.expect 21, exit 0; check:slot-lookup exit 1 (two new
getService(...) as anyin record-change-integration.test.ts) -> typed as TestObjectQLEngine, exit 0; check:i18n / check:i18n-coverage / check:i18n-walk-parity rerun after the showcase translation, exit 0; check:examples-live-imports (new after the showcase edit) exit 0. pnpm lint (eslint --no-inline-config) over packages/spec, objectql, runtime, triggers/trigger-record-change, services/service-automation, cli, lint: exit 0. CI: no check-runs on head 3e38b81 at report time (get_check_runs total_count 0).","dogfood_battery": "n/a — no
!in the PR title and no (narrowing) arm","line_budget": "no budget in the order; diff 63 files +1655 -45 (hand-written non-test about +730; the rest tests and generated spec artifacts)",
"deviations": [
"Gates ran on head ae1462f; later commits (dc106ae showcase/checklist/test typing, 39ca46a spec pin updates, 3e38b81 ledger header totals) were followed by targeted reruns of the gates and suites they touch, not by a second full gate pass.",
"Scope additions beyond the order's list, each forced by a gate: the showcase authors save_notice on showcase_task_completed with zh-CN (the showcase coverage test requires every FlowNodeAction member authored), and docs/qa/platform-checklist automation.flow-node-type-matrix revision 12.",
"dropped-refinements.baseline.json grows by one entry (automation/SaveNoticeConfig, the text-slot superRefine every text-slot config carries) and 9 notices sites under the translation bundle schemas; header totals 248 / 961.",
"git push ran 7 times on the branch (one per compilable step), not once."
],
"files_changed": [
".changeset/28251-save-notice-from-hooks-and-flows.md",
"content/docs/references/api/protocol.mdx",
"content/docs/references/automation/builtin-node-config.mdx",
"content/docs/references/automation/flow.mdx",
"content/docs/references/data/hook.mdx",
"content/docs/references/index.mdx",
"content/docs/references/system/translation.mdx",
"docs/audits/2026-07-unknown-key-strictness-ledger.counts/automation.md",
"docs/audits/2026-07-unknown-key-strictness-ledger.counts/system.md",
"docs/qa/platform-checklist/areas/automation.json",
"examples/app-showcase/src/automation/flows/index.ts",
"examples/app-showcase/src/system/translations/index.ts",
"packages/cli/src/utils/i18n-extract.ts",
"packages/cli/test/i18n-flow-save-notice-coverage.test.ts",
"packages/lint/src/flow-cel-root-scope.ts",
"packages/lint/src/validate-translation-references.test.ts",
"packages/lint/src/validate-translation-references.ts",
"packages/objectql/src/engine.ts",
"packages/objectql/src/hook-notice.ts",
"packages/objectql/src/protocol-save-notice-answer.test.ts",
"packages/runtime/src/sandbox/body-runner.ts",
"packages/runtime/src/sandbox/hook-notice.integration.test.ts",
"packages/runtime/src/sandbox/quickjs-runner.ts",
"packages/runtime/src/sandbox/script-runner.ts",
"packages/services/service-automation/src/builtin/index.ts",
"packages/services/service-automation/src/builtin/map-node.ts",
"packages/services/service-automation/src/builtin/save-notice-node.test.ts",
"packages/services/service-automation/src/builtin/save-notice-node.ts",
"packages/services/service-automation/src/builtin/subflow-node.ts",
"packages/spec/api-surface/automation.json",
"packages/spec/api-surface/contracts.json",
"packages/spec/api-surface/data.json",
"packages/spec/api-surface/system.json",
"packages/spec/authorable-surface/automation.json",
"packages/spec/authorable-surface/data.json",
"packages/spec/declaration-map/automation.json",
"packages/spec/dropped-refinements.baseline.json",
"packages/spec/export-origins/automation.json",
"packages/spec/export-origins/contracts.json",
"packages/spec/export-origins/data.json",
"packages/spec/export-origins/system.json",
"packages/spec/json-schema.manifest/automation.json",
"packages/spec/liveness/state-counts/translation.md",
"packages/spec/liveness/translation.json",
"packages/spec/src/api/protocol.zod.ts",
"packages/spec/src/automation/builtin-node-config.zod.ts",
"packages/spec/src/automation/flow-builtin-node-config-keys.test.ts",
"packages/spec/src/automation/flow-builtin-node-config-values.test.ts",
"packages/spec/src/automation/flow-node-config-refusals.ts",
"packages/spec/src/automation/flow-text-slot-template.test.ts",
"packages/spec/src/automation/flow-text-slot-template.ts",
"packages/spec/src/automation/flow.zod.ts",
"packages/spec/src/automation/save-notice-node.test.ts",
"packages/spec/src/automation/save-notice-placement.ts",
"packages/spec/src/contracts/automation-service.ts",
"packages/spec/src/contracts/data-engine.ts",
"packages/spec/src/data/hook.zod.ts",
"packages/spec/src/system/flows-translation-face.test.ts",
"packages/spec/src/system/i18n-resolver.ts",
"packages/spec/src/system/translation.zod.ts",
"packages/triggers/trigger-record-change/src/record-change-integration.test.ts",
"packages/triggers/trigger-record-change/src/record-change-trigger.ts",
"packages/triggers/trigger-record-change/src/save-notice-channel.test.ts"
],
"mcp_calls": "4 — mcp__github__issue_read ×4 (get #28251, get_comments #28251 ×2, get #27380); no write tool",
"api_writes": "1 relay dispatch so far — fleet-write pr_create (POST /repos/objectstack-ai/objectstack/dispatches -> run 41330718852 created draft PR #28753, no labels, no assignees, read-back identical 8355 bytes); this report comment is the second (post-stamped.mjs). No GitHub MCP writes.",
"open_questions": [
{
"question": "Should the HOOK face of the save notice get a translation catalogue (hooks..notices..message, a hook declaring
noticesthe way it declaresrefusals), so a shipped body can call ctx.notice with a key and the server renders it in the writer's locale?","status": "needs_decision",
"options": [
"A — not now: ctx.notice takes the rendered sentence; a hook that wants a localized notice builds it itself. Flows already translate through flows..notices.",
"B — add a declared
noticescatalogue on HookSchema plus ctx.notice({ key, params }) rendered server-side, mirroring hooks..refusals..message."],
"analysis": {
"real_business_need": "Measured: the one named consumer (HotCRM deal submission, objectstack-ai/objectstack#27380 row 3) is a flow, which this PR translates. No shipped hook body in hotcrm or the showcase sets a notice today, so B has no measured writer yet.",
"long_term_soundness": "B is the contract-first symmetric shape (hooks already translate refusals by catalogue). A leaves hooks on author-built strings, which is the same posture hook refusals had before objectstack-ai/objectstack#26170. Neither is a workaround.",
"ai_error_resistance": "B adds a declared surface that must be enforced (unknown key refused at publish and at call), more for an AI to get wrong if half-built. A keeps one string argument, refused loudly when it is not a non-empty string.",
"startup_focus": "A: no pull for the hook catalogue yet; implementation-first says add it with its first real writer."
},
"recommendation": "A, because the only measured consumer is a flow and is served here; open B with the first hook that needs a localized notice."
}
],
"out_of_scope_findings": [
"carrier: hotcrm lane · HotCRM can retire its
record:alertbanner workaround (#27380 row 3) for asave_noticenode ahead of the approval in its deal-submit flow once this lands · noted, not filed · dedupe words: hotcrm record:alert save notice approval","carrier: none · the Studio palette (ui/packages/app-shell/src/views/metadata-admin/previews/flow-canvas-parts.tsx NODE_PALETTE) does not list
save_notice; the node saves, runs and its descriptor is in GET /api/v1/automation/actions · noted in Acceptance notes, not filed (polish) · dedupe words: flow palette save_notice studio"]
}
objectstack-fleet commentedon Nov 1, 2026
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH
Seat ruling · open question in os-dev-report
6562102476· 2026-11-01T18:31ZQuestion: should the hook face of the save notice get a translation catalogue (
hooks.<hook>.notices.<KEY>.message, withctx.notice({ key, params })rendered server-side)?Ruling: A, not now.
ctx.noticetakes the rendered sentence. The flow face keeps its catalogue,flows.<flow>.notices.<node_id>.message.hooks.<hook>.refusals.<CODE>.message(i18n: an app hook's business refusal has no translation key — a zh-CN user reads "A current clean version is required before signing." in English #26170).Ruling-ref: the maintainer, 2026-11-01T12:11Z: 「定级你可以自己决定」. The standing rule is that new gates default to no. This ruling does not change PR #28753's scope.
objectstack-fleet commentedon Nov 1, 2026
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH
PR #28753 accepted (seat ACCEPT #28753 (comment), contract review PASS #28753 (comment)) and enqueued for merge. Landing record follows after merge, with the follow-up card for the review should-fixes and the hotcrm
record:alertremoval.objectstack-fleet commentedon Nov 1, 2026
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH
Landing record · #28251 · PR #28753
domain:serverseat 3 ·session_01FEkQ5kE5BrPUPNMMyv6oCH· 2026-11-01T19:41Zgit ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-28753-*'git merge-base --is-ancestor a00f3359 origin/maingit grep -n "export function bindHookNotice" origin/main -- packages/objectql/src/hook-notice.ts;git grep -n "'save_notice'" origin/main -- packages/spec/src/automation/flow.zod.tsFlowNodeActionmember at line 56a00f3359;Fixes objectstack-ai/objectstack#28251closed the card. The save notice now has two faces on one channel, the write's ownonNotice:ctx.notice(message).save_noticenode. Its message is translatable underflows.<flow>.notices.<node_id>.message.noticestays a plain string.save_noticeoutside a create/update record trigger is refused at publish (INVALID_METADATA422). On a resumed leg, a subflow or a map child it is refused by name at run time.CONTRACT_REVIEW_TIER) PASS6562490492on the landed head36c1fb8f; seat ACCEPT6562635160. Open question ruled A (no hook-face catalogue now),6562118323.save_notice/ctx.noticein the published automation skill, and the HotCRM card to replace therecord:alertworkaround (console: what a save or bulk action tells the user: a bulk action reports Succeeded N / N whatever each row did, a warning save shows two toasts, a hook cannot add a sentence to the save toast, and Delete has no per-object confirm text #27380 row 3). Their numbers follow in a comment below.mainata00f3359or later.objectstack-fleet commentedon Nov 1, 2026
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH
Follow-ups named in the landing record: #28786 (the review should-fixes,
domain:server, p3), #28787 (teachsave_notice/ctx.noticein the automation skill,domain:skills) and objectstack-ai/hotcrm#2398 (HotCRM retires therecord:alertworkaround,repo:hotcrm).