Skip to content

Commit 0c32c32

Browse files
committed
chore(changeset): the save door refuses every hook with no body
Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi Co-authored-by: Claude <noreply@anthropic.com>
1 parent f3d2ea2 commit 0c32c32

1 file changed

Lines changed: 26 additions & 0 deletions

File tree

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
'@objectstack/metadata-protocol': minor
3+
---
4+
5+
The runtime save door refuses every hook that carries no `body`, including one with neither a `body` nor a `handler`: a hook stored there ships with no code package, so its `body` is the only code it can run
6+
7+
Clause-②: no (narrowing)
8+
9+
<!-- adr-0087: not-required (no-migration-prescription) A validity narrowing at one runtime write door over existing keys: no key of `HookSchema` or of any other metadata schema is removed, renamed or re-shaped, so there is no tombstone and nothing mechanical for `objectstack migrate meta` to rewrite. What `body` a refused hook should carry is authoring intent no conversion entry can decide. New saves are refused with the remedy; a row stored before this change keeps its bytes, and no stored row is re-saved (measured: `migrateStoredMetadata({ apply: true })` on a stored hook row with neither field reports it canonical, failed 0, and the bytes are unchanged). The census of writers through this door, taken first at `1db5322ba2`: Studio at the objectui pin (`ab18797215`, unchanged since the census of the handler-only refusal, which read its hook skeleton carrying a `body`); objectstack `examples/**` and `packages/qa/**` seed no `sys_metadata` hook rows (zero `type: 'hook'` items); `os meta register` forwards the author's own file and emits no hook of its own; the artifact, boot and install-local doors never call `saveMetaItem` for a hook (every call site in the tree saves a fixed type other than `hook`, or forwards an author's request: the REST and dispatcher `/meta` saves, `migrateStoredMetadata`, `duplicatePackage`). The only first-party bodies of this shape were test probes (seven suites), which now carry a `body`. Package duplication of a package holding such a row now reports that row as failed with this refusal (measured: copied 1, failed 1, the source rows' bytes unchanged). Hosted tenants and the cloud AI author were not measured. The other categories are closed on facts: the package publishes (not unpublished); no ADR-0087 id covers this rule and this diff adds none (not registered / already-registered); and the change narrows what a runtime write door accepts, not a runtime interface or a type surface alone (not runtime-interface-only / type-surface-only). -->
10+
11+
**BREAKING** accept-set narrowing at the runtime save door, shipped as `minor` under the repo's launch-window convention for breaking changes, the grade the same door's earlier refusals shipped with.
12+
13+
**One rule.** `saveMetaItem`, which `PUT /api/v1/meta/hook/:name` and the dispatcher's metadata save both call, now refuses every `hook` that carries no `body`. It already refused a hook whose `handler` names a function and that carries no `body`; that refusal is now one case of this rule, with the same envelope and the same message. The new case is a hook with neither field (or with an empty `handler`). Before this change the door answered 200 for it, the hook was served by name, the runtime skipped it at every re-sync (`skipping hook with unresolved handler`, in the server log only), and it never ran. The refusal is `VALIDATION_ERROR` / 400, in draft and in publish mode, before anything is stored or bound. It names the hook and prescribes a `body`.
14+
15+
**Before and after** (with `{ name: 'stamp_status', object: 'crm_note', events: ['beforeInsert'] }`):
16+
17+
- Before: 200 `Saved hook 'stamp_status'`, the row stored and served by name, and the hook never run.
18+
- After: 400 `VALIDATION_ERROR`, naming `stamp_status`, and nothing stored.
19+
20+
**What still saves.** A hook with a `body`, with or without a `handler` beside it: the binder runs the body and never consults the name. A malformed `body` still gets the type schema's located `422 INVALID_METADATA`.
21+
22+
**What is unchanged.** `HookSchema` still accepts a hook with no `body`, because a build artifact carries a `handler` hook: `objectstack build` lowers an inline function to the hook's name and ships the function in the artifact's runtime module. A hook in an artifact or a `defineStack` config binds on its own door, which never reaches this one. `os validate` and `os build` are unchanged.
23+
24+
**Rows stored before this change.** They keep their bytes, nothing re-saves them, and the runtime skips them at every re-sync as before. A new save of one, a re-save included, is refused until it carries a `body`. Package duplication reports such a row as failed with this refusal; `migrate meta --stored` leaves it as it is. Delete stays open.
25+
26+
**The fix.** Give the hook a `body`: sandboxed JS (`{ language: 'js', source }`) or an expression (`{ language: 'expression', source }`). A hook that must run a package's own function belongs in that package's code, where its `handler` resolves.

0 commit comments

Comments
 (0)