ADR-0054: A live authorable property must be proven at runtime, not merely have a consumer (prove-it-runs gate)
Status: Accepted (2026-06-18)
Deciders: ObjectStack Protocol Architects
Builds on: ADR-0049 (enforce-or-remove gate), ADR-0005 (artifact vs runtime), ADR-0053 (the domain of the motivating regression)
Consumers: @objectstack/spec (liveness ledger packages/spec/liveness/<type>.json), the Spec Liveness Check CI gate (#1919), @objectstack/dogfood (the runtime gate, #2020), @objectstack/verify (the published proof engine + CLI, #2041), spec authors, platform contributors.
Surfaced by: PR #2018 — "organization timezone drives analytics date bucketing" was green on every static gate (build, ~900 unit tests, spec-liveness, CodeQL) yet broken end-to-end across three integration seams; and the field-type capability-matrix dogfood (#2022), which on its first run found rating/slider/toggle reading back wrong-typed.
ObjectStack is a development platform: third parties have an AI author arbitrary metadata, and the promise is that it works at runtime. ADR-0049 closed false compliance — a property declared but unenforced. The liveness ledger (#1919) then made every authorable property declare a status (live / experimental / dead) with evidence, killing silent dead surface.
But "live" today means only a static file:line pointer to a consumer —
proof that something reads the property. That is necessary but not
sufficient. A property can be live at every individual layer and still be
broken end-to-end, because the break lives in the integration — engine ↔
driver ↔ service ↔ HTTP ↔ execution-context. #2018 is the proof: timezone,
date bucketing, the analytics strategy, and the REST context were each
individually correct (and individually unit-tested against mocks); the bucket
was wrong only when they ran together. Call this gap unproven liveness: the
ledger says "live", the AI is told "you may author this", and it silently
misbehaves at runtime.
A metadata-driven platform whose authors are AI cannot ship unproven liveness
for the primitives that matter. The static pointer must be upgradable to a
runtime proof — a @objectstack/dogfood test that
authors the property against the real, in-process stack and asserts the runtime
result.
Decision. Extend the enforce-or-remove gate (ADR-0049) with a third leg —
prove-it-runs. For a defined high-risk class of authorable properties, a
live classification must carry a proof (a dogfood test reference), not just a
consumer pointer. Applied as a ratchet, not a retrofit: required for newly
added/changed high-risk properties and for any property implicated in a shipped
runtime regression — never as a one-shot demand to prove all 200 live properties.
Three gates already guard the authorable surface, each at a different layer:
| Gate | Question it answers | Where it can be fooled |
|---|---|---|
| AI-authoring guardrails (build-time lint, broken→error / fragile→warning) | Is the authored metadata valid? | Valid ≠ correct at runtime. |
| Spec liveness ledger (ADR-0049 + #1919) | Does any code read this property? | A consumer existing ≠ the integrated path being correct. |
| Dogfood gate (#2020) | Does authoring it produce correct runtime behavior? | Coverage is currently incidental — whatever the example apps happen to exercise. |
The liveness ledger's evidence is a static pointer
(packages/spec/liveness/<type>.json, e.g. field:line). It is excellent at
killing dead surface (parsed, no consumer). It is blind to integration
correctness: #2018's properties all had valid consumer pointers and were
classified live, yet the end-to-end result was wrong. The same blindness let
rating/slider/toggle be live (they persist) while reading back as the
wrong JS type — found only when #2022
wrote one and read it back over the real API.
The cost is asymmetric for this platform. When a human authors metadata and it misbehaves, they notice and adjust. When an AI is told a property is live and emits it across the combinatorial space the examples never cover, the misbehavior ships silently into a third-party app. "Live" must therefore carry a stronger guarantee for the primitives an AI is most likely to combine in ways the curated examples don't.
The liveness ledger gains an optional, stronger evidence form for live
properties: alongside the static consumer pointer, an entry may carry a
proof — a reference to a @objectstack/dogfood test that authors the
property against the real in-process stack and asserts the runtime outcome
(a value, a bucket, a count, a denied write — observable behavior, not "no
error").
A proof supersedes a static pointer: it subsumes "a consumer exists" and adds "the integrated path is correct."
A proof is required (CI-enforced via the liveness gate) only for:
- (a) High-risk authorable classes, on add/change. The classes whose values
cross the engine↔driver↔service↔HTTP boundary and have repeatedly broken in
integration despite green unit tests:
- field types — persistence + read-coercion fidelity (the field-zoo matrix),
- analytics dimensions / measures — bucketing, aggregation, timezone,
- RLS / sharing — read and by-id-write enforcement,
- flow nodes — execution + variable wiring,
- form layout/section/widget — server-side resolution.
- (b) Regression carriers. Any property implicated in a shipped runtime
regression: the fix PR must add (or un-quarantine) its dogfood proof — exactly
as #2018 added the tz proof, and as the
rating/slider/togglefix must lift theit.failsquarantine in the field-zoo matrix.
Properties outside these classes (labels, descriptions, pure presentation hints) do not require a proof — a static pointer remains sufficient. The ratchet grows coverage where silent runtime breakage is plausible, not everywhere.
- Phase 1 (in progress). Capability-matrix proofs for the two classes with a demonstrated break: field types (#2022, field-zoo) and analytics (#2018, tz bucketing).
- Phase 2. Extend the matrix to flow nodes, form widgets, and RLS patterns (the member-edit-others by-id-write hole, #1994, is the seed RLS proof — it also drives a multi-user harness capability reused by every later RLS proof).
- Phase 3 (deferred, evidence-gated). A generative pass that emits random valid metadata from the spec's Zod surface and asserts invariants — pursued only once the matrix proves the harness scales, and scoped to narrow high-value slices. Generative testing is high-ceiling and high-maintenance; it does not lead.
- CI binding lands incrementally. The liveness gate begins requiring
prooffor class (a) one class at a time as its matrix is populated, and for class (b) immediately. No big-bang demand to backfill all live properties.
A proof is a dogfood test because dogfood is the only gate that boots the real stack in-process and exercises a property end-to-end (the thing that caught #2018). In-process Hono request-injection keeps a proof at ~2s with no ports, so the proof corpus stays CI-cheap as it grows.
- Positive. Closes unproven liveness: every high-risk authorable primitive an AI can emit carries a runtime guarantee, not just a "someone reads it" pointer. Every shipped regression leaves behind a permanent guard (the fix carries its proof). The three gates compose into one honest chain — valid (build) → has a consumer (liveness) → runs correctly (dogfood).
- Negative / cost. A proof is more work than a static pointer, and the dogfood harness must scale (per-class fixtures, boot cost). Mitigated by in-process inject (~2s/proof) and by scoping the requirement to high-risk classes on change — not a retrofit. Risk that the proof corpus slows CI; bounded by the same scoping and by keeping generative testing deferred.
- Follow-up. (1) Define the authoritative high-risk-class list and add the
prooffield + ratchet to the liveness gate. (2) The field-fidelity fix (rating/slider/toggle) is the first "regression carrier" instance — it must lift the field-zoo quarantine. (3) Seed the RLS proof (#1994) and the multi-user harness capability.
- Proving all 200 live properties now. Trivial static properties don't need runtime proofs; the ratchet targets the high-risk class.
- Building the generative tester now. Deferred to Phase 3, evidence-gated.
- Replacing unit tests. Dogfood proofs add the integration dimension; they complement, not replace, layer-level unit tests. A proof must assert something a mocked unit test structurally cannot.
- Client-side render proofs. The backend dogfood harness covers server-reachable behavior. Pure objectui/React render correctness belongs in objectui's own suite; a property whose only failure mode is client render is out of scope for this gate.
The proof mechanism this ADR assigns to @objectstack/dogfood has since been
extracted into a published, app-agnostic package: @objectstack/verify
(#2041) — bootStack
(the real in-process stack via Hono request-injection), deriveCrudCases
(a runtime contract auto-derived from any app's metadata), runCrudVerification
(write → read → assert type fidelity), and runRlsProofs (the #1994
"can't-write-what-you-can't-read" invariant) — plus an objectstack verify CLI.
This sharpens the decision without changing it:
- The gate vs. the engine.
@objectstack/dogfoodremains the gate — the framework's own hand-written golden proofs (e.g. the #2018 tz-bucketing test, whichderivecan never auto-generate) — and now runs on the@objectstack/verifyengine instead of carrying it. Aproof(§1) is still a dogfood test; what changed is that the harness underneath it is reusable. - Phase 1 is now a reusable matrix. The field-type matrix (#2022) is the
published
deriveCrudCases+runCrudVerification; the #1994 RLS seed isrunRlsProofs. They are no longer internal to dogfood. - Phase 3 has a concrete vehicle.
deriveCrudCases— metadata → synthesized record → asserted round-trip — is the seed of the deferred generative pass; Phase 3 grows it rather than starting from zero. - Third parties get the same gate. Because it is published, a third-party or
template author runs the identical proofs against their own app
(
objectstack verify --rls), extending prove-it-runs beyond the framework's curated examples — the AI-authoring audience this ADR is written for. Validated against the externalhotcrmapp and the 9-app template corpus (#2041).
Honest scope of the auto-derived path. @objectstack/verify's auto-derive
asserts only scalar field round-trip fidelity and the by-id RLS invariant,
and skips objects whose required fields it can't synthesize (lookups /
master-detail) and field classes it can't assert (computed/formula, flow nodes,
analytics bucketing, UI). A green objectstack verify therefore proves those two
dimensions over the auto-reachable subset — it does not subsume the
hand-written golden proofs, which is exactly why §4's gate keeps them. Closing
that gap (related-record topological synthesis; computed/flow/analytics
assertions) is the substance of Phases 2–3.