Skip to content

Commit 3bb382b

Browse files
os-zhuangclaude
andauthored
docs: withdraw ADR-0107, drop the structured writes proposal (#3700) (#3735)
Owner decision: the structured writes declaration will not be built — it duplicates the write set in a second place authors must keep in sync, adding friction and a new error surface, while testing hooks against a SQL driver (in-memory SQLite) already surfaces the failure mode that schemaless memory:// green lights hide. - delete docs/adr/0107-hook-body-write-set-accepted-static-gap.md and its changeset; the author-facing documentation of the gap (hook-bodies.mdx, hooks.mdx best practice, ScriptBodySchema TSDoc) stays — it is the whole disposition - strip now-dangling ADR-0107/#3700 references from hook-bodies.mdx and the TSDoc; reword the mitigation intro that implied a declaration was coming - audit assessment §5 D4: record the revision — (b) dropped, #3700 closed as not planned; (c) unchanged under ADR-0078 §4 Claude-Session: https://claude.ai/code/session_01EsRh53G7SMwFQL57BpFDsx Co-authored-by: Claude <noreply@anthropic.com>
1 parent b098b0e commit 3bb382b

6 files changed

Lines changed: 8 additions & 88 deletions

File tree

.changeset/adr-0107-hook-body-write-set-accepted-gap.md

Lines changed: 0 additions & 4 deletions
This file was deleted.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
---
3+
4+
Docs-only: withdraw ADR-0107 and drop the structured `writes` proposal (#3700, closed as not planned). The hook-body write-set gap stays documented at the authoring surfaces (`hook-bodies.mdx`, `ScriptBodySchema` TSDoc) without a decision-record apparatus. No runtime or API change; releases nothing.

content/docs/automation/hook-bodies.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,11 @@ Static validation around a hook is asymmetric, and it is worth knowing exactly w
122122

123123
- **Checked — read side.** `hook.condition` is validated at build time against the target object's fields by the expression validator (`@objectstack/lint`), including array-valued `hook.object` targets. A condition referencing a nonexistent field fails the lint.
124124
- **Checked — capability side.** `body.capabilities` gates which `ctx` APIs the body may call at all; the sandbox throws on an undeclared call.
125-
- **Not checked — write side.** Nothing validates *which fields* your body writes. `body.source` is an opaque JS string: the fields it assigns (`ctx.input.foo = …`) or writes cross-object (`ctx.api.object('x').update(id, { foo })`) are not represented anywhere in metadata, so no lint can verify they exist. This is an **accepted gap** (ADR-0107); the structured `writes` declaration that would close it is tracked in [#3700](https://github.com/objectstack-ai/objectstack/issues/3700).
125+
- **Not checked — write side.** Nothing validates *which fields* your body writes. `body.source` is an opaque JS string: the fields it assigns (`ctx.input.foo = …`) or writes cross-object (`ctx.api.object('x').update(id, { foo })`) are not represented anywhere in metadata, so no lint can verify they exist. This is an **accepted gap** with no planned closure — a structured `writes` declaration was considered and dropped ([#3700](https://github.com/objectstack-ai/objectstack/issues/3700), closed as not planned).
126126

127127
Writing a field the target object doesn't have is not caught at runtime either — the write-path validator skips unknown keys. On a SQL driver the stray column reaches the database and the whole operation fails with a driver-level error, far from the authoring mistake; a schemaless driver (memory, MongoDB) silently persists the stray key.
128128

129-
Until a structured write declaration exists:
129+
Because the write set carries no static checking:
130130

131131
- **Check your write targets by hand.** Every field the body assigns must exist on the target object(s) — for an array or `"*"` hook, on every target.
132132
- **Prefer a flow `update_record` node when the write set is fixed.** Flow nodes declare their writes structurally in `fields`, so they get the static checking hook bodies can't.

docs/adr/0107-hook-body-write-set-accepted-static-gap.md

Lines changed: 0 additions & 80 deletions
This file was deleted.

docs/audits/2026-07-app-metadata-reference-integrity-assessment.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ Field-level checks inside a cross-package object stay S3 (skip) — we cannot ju
115115
- **D1 — knowledge indexes/sources need a definition site or an experimental marker.** `agent.knowledge.indexes` references a namespace with no stack slot (`KnowledgeSourceSchema` is orphaned from `stack.zod.ts`). Per the ADR-0049/0078 trichotomy the current state — parsed, unmarked, unresolvable — is the prohibited fourth state. Either add `knowledgeSources` to the stack (spec change, ADR-worthy) or mark the fields `[EXPERIMENTAL — not enforced]`. R7 is blocked on this for the knowledge half only.
116116
- **D2 — runtime-registered skills/tools vs. static lint.** `ask`/`build` kernel agents register skills/tools via service at boot (same invisibility as plugin objects). Extend the Phase-1 registry with official skill/tool names, or run R7 at S2 advisory severity. Small decision; take it when R7 starts.
117117
- **D3 — where does nav-access belong?** R5-as-lint-warning (proposed) vs. folding into `compile`'s access-matrix snapshot gate. Lint wins on path symmetry (ADR-0078); the snapshot gate stays the drift detector.
118-
- **D4 — hook body writes are not statically checkable, say so.** The write set lives in opaque JS (`hook-body.zod.ts`). Options: (a) accept the gap and document it (hook writes are the one HotCRM category with no static answer); (b) a structured `writes: [field]` declaration on `HookSchema` that the runtime enforces (contract-first, but new spec surface + runtime work); (c) registration-time dev diagnostics (ADR-0078 §4, deferred/evidence-gated there). Recommendation: (a) now, file (b) as its own issue — do not let this category stall the other eight. **Decided 2026-07-27 (#3700): (a).** The accepted gap is recorded in [ADR-0107](../adr/0107-hook-body-write-set-accepted-static-gap.md) and documented at the authoring surfaces (`content/docs/automation/hook-bodies.mdx` "Not statically checked: the write set"; `ScriptBodySchema` TSDoc). (b) stays open as #3700's deferred, evidence-gated proposal; (c) remains deferred under ADR-0078 §4, unchanged.
118+
- **D4 — hook body writes are not statically checkable, say so.** The write set lives in opaque JS (`hook-body.zod.ts`). Options: (a) accept the gap and document it (hook writes are the one HotCRM category with no static answer); (b) a structured `writes: [field]` declaration on `HookSchema` that the runtime enforces (contract-first, but new spec surface + runtime work); (c) registration-time dev diagnostics (ADR-0078 §4, deferred/evidence-gated there). Recommendation: (a) now, file (b) as its own issue — do not let this category stall the other eight. **Decided 2026-07-27 (#3700): (a); revised 2026-07-28 — (b) dropped, #3700 closed as not planned.** The gap stays documented at the authoring surfaces (`content/docs/automation/hook-bodies.mdx` "Not statically checked: the write set"; `ScriptBodySchema` TSDoc); the documentation is the whole disposition, so ADR-0107, which only recorded it, was withdrawn. (b) will not be built — the declaration duplicates the write set in a second place the author must keep in sync, adding authoring friction and a new error surface (worst for AI authors); in practice, exercising hooks against a SQL driver (in-memory SQLite) covers the failure mode that schemaless `memory://` green lights hide. (c) remains deferred under ADR-0078 §4, unchanged.
119119
- **D5 — rule-suite wiring drift.** `validate`/`lint`/`compile`/`doctor` each hand-pick rule subsets. A shared "run the reference-integrity suite" entry point in `@objectstack/lint` would make the next rule's wiring a one-liner and end the drift. Worth doing with Phase 2, not before.
120120

121121
---

packages/spec/src/data/hook-body.zod.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export type ExpressionBody = z.infer<typeof ExpressionBodySchema>;
6262
* - `process`, `globalThis`, `eval`, `new Function`
6363
* - any identifier resolved from a value-only top-level import
6464
*
65-
* **Write-set opacity — accepted static-analysis gap (ADR-0107, #3700).**
65+
* **Write-set opacity — accepted static-analysis gap.**
6666
* `source` is opaque to static analysis: no lint verifies that the fields the
6767
* body writes (`ctx.input.x = …`, `ctx.api.object('y').update(id, { x })`)
6868
* exist on the target object(s). Only the read side (`hook.condition`) and

0 commit comments

Comments
 (0)