Problem
renderRegion(host, build, {sig}) requires the caller to enumerate, in a separate string, every value the build closure reads — CLAUDE.md documents the failure mode (forget one → the region silently goes stale) and mitigates it with an opt-in runtime audit (_auditSigCoversOutput, templates.js:106-117) plus long defensive comments at each call site (people.js:185-200's sig docstring is 9 lines of 'must list EVERY value'; spine.js:307-331 likewise). The audit detects drift only when enabled and only for states the e2e test happens to exercise; the correctness burden stays on hand-synchronising two representations of the same dependency set. The views' sigs are already ad-hoc serialisations of a model (people.js:195-200 maps people → strings; spine.js:323-331 maps sessions → strings) — the model just isn't reified.
Evidence / where
tapscribe/web/js/templates.js
tapscribe/web/js/next/views/people.js
tapscribe/web/js/next/components/spine.js
Suggested fix
Add a thin sibling primitive, e.g. renderRegionModel(host, model, buildFromModel), where model is a plain JSON-serialisable object the caller derives from state and sig = JSON.stringify(model) is computed inside the primitive; buildFromModel receives only the model, so the render cannot read a value that isn't in the sig — drift becomes impossible by construction rather than detectable by audit. Interaction-hold semantics (focus/selection guards, defer-without-advancing, replaceChildren swap, markRegionStale) are untouched, so this does not reopen ADR-0004's rejection of DOM-diffing; migrate views opportunistically (people.js is the cleanest first candidate) and keep the audit as the backstop for legacy closure-based call sites.
Severity: low
Filed from the 2026-07-03 adversarially-verified deep review (85 findings, 0 refuted). This issue survived an independent skeptic pass. Severity is the verifier's calibration; for nice-to-haves it is operator value.
Problem
renderRegion(host, build, {sig}) requires the caller to enumerate, in a separate string, every value the build closure reads — CLAUDE.md documents the failure mode (forget one → the region silently goes stale) and mitigates it with an opt-in runtime audit (_auditSigCoversOutput, templates.js:106-117) plus long defensive comments at each call site (people.js:185-200's sig docstring is 9 lines of 'must list EVERY value'; spine.js:307-331 likewise). The audit detects drift only when enabled and only for states the e2e test happens to exercise; the correctness burden stays on hand-synchronising two representations of the same dependency set. The views' sigs are already ad-hoc serialisations of a model (people.js:195-200 maps people → strings; spine.js:323-331 maps sessions → strings) — the model just isn't reified.
Evidence / where
tapscribe/web/js/templates.jstapscribe/web/js/next/views/people.jstapscribe/web/js/next/components/spine.jsSuggested fix
Add a thin sibling primitive, e.g.
renderRegionModel(host, model, buildFromModel), wheremodelis a plain JSON-serialisable object the caller derives from state andsig = JSON.stringify(model)is computed inside the primitive;buildFromModelreceives only the model, so the render cannot read a value that isn't in the sig — drift becomes impossible by construction rather than detectable by audit. Interaction-hold semantics (focus/selection guards, defer-without-advancing, replaceChildren swap, markRegionStale) are untouched, so this does not reopen ADR-0004's rejection of DOM-diffing; migrate views opportunistically (people.js is the cleanest first candidate) and keep the audit as the backstop for legacy closure-based call sites.Severity: low
Filed from the 2026-07-03 adversarially-verified deep review (85 findings, 0 refuted). This issue survived an independent skeptic pass. Severity is the verifier's calibration; for nice-to-haves it is operator value.