RFD 0072 — Persisted derived read-model: populating the .oxbin projection-cache section
- State: discussion
- Renumbered: originally allocated as 0070; renumbered to 0072 after
mainratified the concurrently-allocated 0070 (derive-backed classification) and 0071 was claimed by the agent-tooling-distribution branch — the discussion-state RFD yields (the 0016 → 0017 precedent). - Opened: 2026-07-08
- Decides: how a cold process — the CLI first among them — avoids re-deriving the entire reasoning
model from an artifact that has not changed. Populates
.oxbinsection id 6 (projection-cache, reserved since the format’s first cut, inert ever since) with the post-fixpoint derived read-model (the serializedRelationCatalog) at build time, keyed on a derivation-input commitment (the already-shippedartifact_hashwidened with the GlobalControl semantic fields, the mapping section, the accepted preamble axes, a derivation semantic epoch, and the payload schema version), consumed fail-closed by the strict read path — gated on self-built / explicitly-trusted provenance, foreign caches ignored loudly — with the module’s evaluation configuration restamped on load, and gated by the existing maintainer differential oracle plus new golden-anchored conformance entries. Amends the staleFORMAT.md§D.10 Cap’n Proto sketch to the shipped deterministic-CBOR codec (deterministic, not canonical — D1 states the distinction), with an explicit, justified artifact-tier deviation from RFD 0035 D7’s persisted-columnar runtime segment (D1). Section id 7 (arrangement-section) stays reserved; its specifiedOE1205refusal is today defined but wired nowhere — making it real is an explicit obligation of this RFD (D1). - Built on: RFD 0018 (D4 CQRS event-log + persisted read model; D6 generation-driven
invalidation; D7 arrangements), RFD 0021 (D7 “cross-query reuse needs the persisted
read-model” — the open follow-on this RFD closes for the artifact tier), RFD 0033
(names the reserved-but-unpopulated projection cache as “a real forward arc”), RFD 0035
(D7 the columnar content-addressed segment + IVM maintainer is the primary read-model; D8 the
maintainer proof obligation), RFD 0036 (D6 the mapping-artifact hash-pinned section
pattern; D9 the runtime segment manifest), RFD 0053 (D6 the checkpoint-seeded
maintainer,
IncrementalMaintainer::from_model), RFD 0055 (the golden conformance corpus this must plug into), RFD 0062 (CI tiers the gates run in). - Grounded in: a measured baseline (2026-07-07, 96-core Linux dev box, release
ox,perf stat -r 10instruction counts ±0.00%,perf record -gcall graphs) plus a four-track code recon with verified anchors. Numbers below are from that run; ratios, not absolutes, are the claim.
Question
ox query on a 23 KB example package retires 553 M instructions (~45 ms); the essential work —
read ~60 KB, evaluate a handful of rules over a few hundred tuples, print rows — is on the order of
5 M. Every CLI invocation re-decodes the artifact, re-seeds a cold store, re-plans every rule, and
re-runs the full stratified fixpoint, then exits and discards all of it
(oxc-driver/src/lib.rs:1435-1442 → Store::new() with every cache field cold,
oxc-runtime/src/write_gate.rs:46-64). The artifact did not change. Where should the derived state
live so that work is proportional to the delta — and what is the invalidation and correctness story
that lets a cache of reasoning results ship without reintroducing the silent-wrong-answer class
(RFD 0055)?
Context
Measured cost structure. On examples/residential_lease_breach (the heaviest example cell):
check 137 M / build 148 M / query 553 M instructions. The query call graph is ~40 %+ ciborium
CBOR decode (Header::try_from 10.3 %, Decoder::pull ~19 % across monomorphizations,
deserialize_struct + serde visitors ~9 %), ~15-20 % allocator traffic (malloc/cfree/realloc),
~5 % memcpy family — while the named reasoner/runtime code (evaluate_rule, reorder_body,
stratify, build_views, …) is single digits. Decode-and-rederive dominates; reasoning does not.
Eliminating translation alone caps at ~2.7× (Amdahl); the 10-100× headroom is in not re-deriving
unchanged results. The engine’s own bench baseline quantifies the spring: full recompute
tc_chains/100K ≈ 457 ms vs incremental_insert 37 µs — ~12,000× — and 4.5-5.7 s on the adversarial
closure shapes (oxc-reasoning/benches/BASELINE.md).
What a cold read actually pays (verified anchors): read_oxbin slices sections
(oxc-oxbin/src/reader.rs:65-105, GlobalControl is the only eager CBOR decode);
Module::load eagerly decodes every event envelope + every decl body
(oxc-runtime/src/module_load.rs:57,201-720), with a bytes→ciborium::Value→bytes→typed double
round-trip per rule (helpers.rs:313-319, module_load.rs:497-504); Store::seed_from bulk-loads
the log (store_init.rs:406-438); the first strict read misses model_cache and runs
build_strict_maintainer = full EDB materialization + rule lowering/stratification/optimization +
Engine::evaluate fixpoint (strict_cache.rs:138-169,486-498); every query body is re-lowered,
re-stratified, re-optimized on every dispatch — no plan cache exists at any layer
(federate_translate.rs:50-78, lib.rs:258-261). For a zero-mutation read of a v1-fragment
module (undated, clock-free, non-temporal — D3) with no external providers registered (D4), all of
§3’s bundle is a function of the artifact bytes plus the fixed evaluation configuration the D5
commitment carries (GlobalControl semantics, mapping, axes, evaluator epoch) — the qualified sense
in which “the artifact determines the model” holds here.
The persistence machinery already exists — at the wrong tier for the CLI. The durable server path
checkpoints the columnar read-model (ColumnarModel::to_bytes, serde-derived,
oxc-reasoning/src/catalog/columnar.rs:33,159-171) into a content-addressed, integrity-checked
segment (ReadModelSegment::seal, oxc-storage-durable/src/lib.rs:121-134) and warm-starts from it
(oxc-runtime/src/persist.rs:179-249,275-346), installing a FrozenMaintainer placeholder whose
first write drops the cache. RFD 0053 D6 added IncrementalMaintainer::from_model
(maintainer/mod.rs:367-388): adopt an already-derived model without a fixpoint. The CLI touches
none of this: Store::new() is pure in-memory, nothing is ever written back, and oxc-driver never
calls write_oxbin.
The artifact slot exists and was engineered for exactly this. Section 6 projection-cache and
section 7 arrangement-section are declared (oxc-oxbin/src/section.rs:42-44), carried as opaque
Option<Vec<u8>> through reader/writer with automatic per-section BLAKE3 content hashes
(writer.rs:172-177), mirrored in Lean as deliberately-abstract Option ByteArray
(spec/lean/Argon/BuildArtifact/Oxbin.lean:65-66), and set to None by every producer
(oxc-workspace/src/build.rs:602-603). GlobalControl.artifact_hash — BLAKE3 over symbol-table ∥
events ∥ standpoint-lattice ∥ tier-table — deliberately excludes the cache sections “so the hash
stays stable across cache churn” (oxc-oxbin/src/content_hash.rs:41-64), proven by
artifact_hash_ignores_cache_sections (writer.rs:348-365). The book already promises the exact
consumer semantics: “Maintained projections; runtime re-saturates if absent”
(spec/reference/src/execution/build/section-model.md:13) and blesses a DRedc-shaped section 6
beside a DBSP-shaped section 7 (versioning.md:18).
The spec sketch for the body is dead weight. FORMAT.md §D.10/§D.11 specify Cap’n Proto structs;
no capnp dependency, no .capnp file, and no RFD deciding Cap’n Proto exist anywhere in the repo,
and the sketch’s row-oriented Segment.rowBytes shape was superseded by RFD 0035 D7’s
columnar decision. FORMAT.md self-describes as a mirror that “may lag the code” and demonstrably
does (sections 10/11 undocumented; artifact_hash missing from its §D.4 key table). The newest
shipped section — Mapping, id 11 (RFD 0036 D6) — is the live precedent for a compiled,
content-addressed artifact section: self-contained module, deterministic CBOR via the crate codec, its
own hash pins, a load-time drift refusal (OE1245) (oxc-oxbin/src/mapping.rs).
Why the artifact tier is the safe one. A 2026-07-07 adversarial test campaign against
runtime-side decode-memo warm-starts (overlay/deep-clone seams, write_gate.rs:76-140) surfaced
four distinct torn-snapshot interleavings (backend/memo snapshot gap; mid-rebind re-seed gap;
durable-append tear; overlay rebind gap) — the stale-cache bug class in its natural habitat, live
mutation. A build-time artifact section has none of those coordinates: ox build is a single writer
over an immutable event set; the cache is written once, atomically, alongside the very bytes its
validity key hashes.
Decision
D1 — Payload = the derived read-model as the serialized RelationCatalog, deterministic CBOR,
in section 6. The projection-cache body carries the derived model of the built event log at the
default scope as RelationCatalog::to_bytes — the codec that already exists, is documented
deterministic and byte-round-tripping, and embeds the wire↔internal interning dictionary
(catalog/mod.rs:434-460). Deterministic, not canonical, stated precisely: the codec
serializes serde structures through ciborium in struct/BTreeMap order, which does not satisfy
the repo’s canonical-CBOR key rule (encoded-key length, then lexical bytes — codec.rs:1-17;
CatalogWireRef itself emits keys out of that order). Determinism is the property this design
needs — identical models seal to identical bytes for the directory hash and reproducible builds —
and canonicality is not: the payload is byte-hashed, never structurally re-hashed or
key-order-compared. FORMAT.md §D.10 documents the section as deterministic-CBOR with exactly
this note, so the format doc does not over-claim either. What the wire form does NOT carry, stated completely: it serializes
only internal_to_wire + relations; decoding resets the world-assumption map and the metric
resolution to defaults and per-tuple temporal intervals to empty (catalog/mod.rs:112,476-480).
The first two are evaluation configuration fresh evaluation stamps from the module
(catalog_seed.rs:20-25) — a cache-seeded catalog used without restoring them would evaluate
open-world NAF under closed-world defaults, the exact silent-wrong class this design must exclude —
so D4 mandates restamping the world map and metric resolution from the module immediately after
decode, before any use; both inputs are inside the D5 commitment, so a module change that alters
them invalidates the cache rather than being silently restamped over. Temporal intervals are
model content, not configuration — they cannot be restamped — which is why D3 excludes
metric-temporal programs from the v1 fragment outright: within the fragment the wire form loses
nothing, so slice-2 from_model receives the full model and D6’s structural equality is
well-defined.
Can-faithfulness (binding). RFD 0070 §6 binds this design by name: a well-founded-undefined
(Can) membership atom must never cross a materialization boundary into a position where a
consumer projects Can → absent → Not — “materializing only wfTrue and letting a later
read treat absence as Not re-creates issue #165 at the storage layer.” The persisted catalog
satisfies the constraint structurally: the engine’s well-founded bookkeeping rides inside
relations as the $undefined:: / $assumed:: companion relations, so
RelationCatalog::to_bytes persists the full three-valued state, the decoded catalog carries it
back verbatim, and the serving view is built from that same catalog — a cache-served read
reports exactly the Can extent a live evaluation reports, never a two-valued projection of
it. The D6 differential arm pins whole-catalog equality (companions included) on every seeded
corpus package, and D6 additionally requires an explicit round-trip test on a well-founded
standoff, so the constraint is load-bearing in CI rather than an implementation accident.
The consumer rebuilds the columnar read view via to_columnar() on load (the cheap,
decode-once direction); the catalog — not ColumnarModel — is persisted because ColumnarModel
has no inverse conversion and IncrementalMaintainer::from_model — the eventual incremental
upgrade — requires the full catalog. (That upgrade is deferred out of v1 entirely: from_model
takes an EDB and a derived model whose exact relationship the caller must guarantee
(maintainer/mod.rs:359-367), and the recovery code names re-interning a model against a
reconstructed live EDB as a correctness hazard (persist.rs:328). The reconstruction contract —
identical dictionary, EDB re-derived from module events, post-seed mutation differential coverage —
is an Open Question, not a v1 promise; persisting the catalog now keeps the payload stable when it
lands.) This is a
deliberate, named deviation from RFD 0035 D7, whose persisted-columnar decision
targets the runtime segment: at the artifact tier one payload must serve both slice-1 reads (via
the rebuilt columnar view) and slice-2 from_model (which needs the catalog); persisting both
doubles the section, and persisting only columnar makes from_model unreachable today. When a
from-columnar maintainer seed exists, payload_version migrates the section to the columnar form —
recorded as the reconciliation path, not left implicit. Wrapped in a self-contained payload type
(mapping.rs Pattern B — own module in oxc-oxbin, not a sections/ row codec). No Cap’n Proto:
the dependency was never decided, never vendored, and no consumer exists. FORMAT.md §D.10 is
rewritten by this RFD to the shipped shape. §D.11 (section 7, DBSP Z-sets) stays reserved per
versioning.md:18’s two-representations design — and this RFD wires the specified OE1205
refusal, which today is a defined-but-never-constructed diagnostic (error.rs:59-61; no
validation layer examines arrangement_body): the loader gains a check that a present section-7
body raises OE1205, so the format’s stated MVP posture becomes enforced rather than aspirational.
D2 — Payload shape.
ProjectionCachePayload {
payload_version: u32, // independent of the four preamble axes; consumer gates on it
// AND commits it — the LAST commitment component (below)
derivation_commitment: [u8; 32], // BLAKE3 over the COMPLETE derivation input (D5), encoded as a
// DOMAIN-SEPARATED, LENGTH-FRAMED tuple (length-framing per
// content_hash.rs; the domain tag is NEW here — the existing
// hash has no tag: a domain tag, then each component
// length-prefixed; Options framed with explicit presence
// tags — no bare `∥` concatenation, no ambiguity):
// artifact_hash
// ∥ the GlobalControl semantic fields, ENUMERATED — no
// ellipsis: default_world, metric_resolution_nanos,
// tzdb_version (adding a semantic field to GlobalControl
// REQUIRES adding it here — the same AGENTS.md review rule
// as the epoch)
// ∥ mapping_body-or-absent (placement worlds shift NAF
// semantics and are outside artifact_hash)
// ∥ ALL FOUR preamble axes (oxbin_format, core_ir,
// tier_ladder, runtime_contract) — the format axis is
// semantic too: a future additive section can reshape the
// producer's model while an older consumer still accepts
// the artifact (versioning.md:12's forward-minor
// acceptance is for EXECUTION; cache consumption requires
// exact format equality, gated in D4)
// ∥ DERIVATION_SEMANTIC_EPOCH — a new oxc-runtime constant
// (the crate that owns the WHOLE producer bundle:
// materialization + rule preparation + evaluation), bumped
// whenever any of those change semantics without a format
// axis bump. Lives above oxc-reasoning so a materialize/
// prepare change is inside the epoch, not beside it (D5).
// ∥ payload_version — framed LAST (the tuple evolves
// append-only). The version participates in what the
// consumer ACCEPTS: the producer commits the version it
// WRITES, the consumer the version it READS, so a
// wire-form bump invalidates by commitment as well as by
// the explicit D4 version gate — belt and gate, the same
// pattern as the format axis.
seed_generation: (u64, u64), // the post-seed storage_gen TUPLE (next event seq, event count) —
// the count alone is ABA-unsafe (append+forget returns to the
// same count; the tuple exists for this, strict_cache.rs:27)
fragment_v1: { // build-time assertions of the v1-eligible fragment (D3); the
dated: false, // consumer re-derives each predicate from ITS module view and
clock_reading: false, // cross-checks — a mismatch is an invalid payload, not a
metric_temporal: false, // trusted claim (D4/D5)
},
scope: { tenant: DEFAULT, fork: DEFAULT, standpoint: default, read_point: Now }, // explicit, v1 fixed
model: bytes, // RelationCatalog::to_bytes — dictionary + relations ONLY;
// world map + metric resolution are NOT in the wire form and
// are restamped from the module on load (D1/D4) — both are
// commitment inputs, so drift invalidates rather than restamps
}
Row counts and per-relation sizes are derivable from model; they are not duplicated. The section’s
directory entry already carries BLAKE3 of the body (writer-automatic) — and D4 requires the consumer
to actually verify it, which no reader does today for any section (reader.rs:65-105 slices without
checking) — so the payload does not re-hash itself.
D3 — Producer: ox build, default on, with hard bounds and honest skips. After event assembly,
build runs the same bundle the first cold read would run (materialize + pipeline_prepared_rules +
Engine::evaluate, i.e. literally build_strict_maintainer over a store seeded from the built
events) and serializes read_model()’s catalog into the section. Opt-out: ox build --no-derived-cache. Build skips emission (with an info-class diagnostic naming why) when any
of the v1 fragment predicates fail, or the payload is over cap:
- the module carries valid-time annotations (dated modules change model validity when the wall clock crosses an epoch with no storage change — the RFD 0063 invariant; v1 does not persist epochs, it declines them);
- any rule reads the evaluation clock (
today()/now:clock.rs:17snapshots the host date per evaluation andeval.rs:1699feeds it into rule bodies — a day-N model read on day N+1 would pass every D5 gate while carrying day-N clock-derived tuples; this is RFD 0063’s frozen-concrete-read-point contract applied to builds, and it also bounds the determinism claim below). Detection is static: the compiled rules name their builtin reads; - the program carries metric-temporal atoms (reuse the existing
program_has_temporalpredicate family): the catalog wire form excludes per-tuple temporal intervals (catalog/mod.rs:112; reset to empty on decode,catalog/mod.rs:476) and metric evaluation writes explicit intervals even for undated programs (temporal/executor.rs:420) — a temporal model cannot round-trip structurally, so v1 declines it rather than proving an interval normalization; - the encoded payload exceeds the v1 cap (
min(64 MiB, 4 × events_body.len()), constants tunable by follow-up without an RFD). Build cost rises by exactly one cold-query fixpoint — work the artifact’s first consumer pays today anyway; amortization starts at the second read. Alongside emission,ox buildrecords provenance for the D4 trust gate: it appends the BLAKE3 hash of the artifact file it wrote to the per-user trusted-artifacts ledger (D4). The record lives outside the artifact and outside the workspace, so it neither perturbs the artifact’s bytes (the reproducible-build property below is untouched) nor travels with a copied repository. Determinism, scoped honestly: within the v1 fragment (undated, clock-free, non-temporal) the model is a function of the event log together with the D5 commitment inputs — GlobalControl semantics, mapping, axes, epoch — all fixed at build time (the fixpoint is deterministic; clock reads are excluded by construction), andRelationCatalog::to_bytesis documented byte-deterministic (“an unchanged model always seals to the same content id”,catalog/mod.rs:440-446), so byte-identical inputs yield byte-identical sections — the reproducible-build property the rest of the artifact already has.
D4 — Consumer: the strict read path, fail-closed to recompute, with hard preconditions.
ensure_strict_cache (strict_cache.rs:138-169) gains one arm ahead of build_strict_maintainer.
The arm is taken only when all hold:
- the store is in the pristine-seed state for this exact artifact.
storage_genequality alone is an invalidator within one store history, not a content identity:load_eventsaccepts arbitrary events under their existing ids and its own contract permits an id rebind that leaves the(next_event_seq, event_count)pair unchanged (store_init.rs:410-438) — generation equality over different contents. TheStoretherefore gains an unforgeable pristine-seed marker: set exclusively byseed_from(module)(recording the seeding module’sartifact_hash), cleared permanently by every other ingest or mutation path (load_events,append_events_durable,append_events,retract_matching, any committed mutation — the clear sites are exactly the existing cache-reset chokepoints). The arm requires: marker present ∧ marker’s hash == this artifact’sartifact_hash∧storage_gen== the payload’sseed_generation(the tuple now serving as a cheap cross-check, not the identity); - no foreign connector and no analytical provider is registered on the store: the strict
maintainer otherwise materializes prefetched foreign rows and analytical output into the EDB
(
derive_read.rs:135-199,store_init.rs:105-174) that a build-time model cannot contain — serving the cache there would be a smaller, wrong closure. Registries-empty is the CLI/default shape; connector-bearing deployments fall through to today’s path untouched; - the module is inside the v1 fragment, re-derived on the consumer side (undated, clock-free,
non-temporal — the D3 predicates) and consistent with the payload’s
fragment_v1assertions; a mismatch is an invalid payload; - for programs using zone-aware builtins (static detection, same discipline as the clock
predicate): the consumer’s bundled tzdb version equals the artifact’s pinned
tzdb_version(oxbin.rs:50— the replay pin the artifact already carries). Zone-aware evaluation reads the consumer’s bundle (builtins.rs:247-288), so a newer bundle silently changes answers that the commitment — which hashes the pinned version on both sides — cannot see. This gate is mechanical, unlike the epoch discipline it complements; zone-free programs skip it; - the artifact’s
oxbin_format_versionequals the consumer’s own — forward-minor acceptance remains for artifact execution (versioning.md:12), but a newer format can carry additive semantic sections an older consumer cannot interpret, so cache consumption requires exact equality (the axis is also inside the commitment — belt and gate); - provenance admits it — the self-built trust gate, enforced by default. A cache section
moves the artifact’s trust surface from auditable inputs (events — checkable by replay) to
unauditable conclusions: a planted cache can serve rows the rules never derived, and nothing
recomputes to notice. Consumption therefore defaults to self-built artifacts only,
determined mechanically, never by path shape — workspace-path resolution is syntactic (it maps
a package directory to an existing
target/*.oxbinwith no build, freshness, or producer-identity check,oxc-workspace/src/lib.rs:1581-1593), so it authenticates nothing and does not admit by itself. Self-built means one of:- same invocation: the artifact was built by this process (
ox test/ox run-scenariobuilding before reading; a build immediately consumed) — self-built by construction; - prior invocation:
ox buildappends the BLAKE3 hash of every artifact file it writes to a per-user trusted-artifacts ledger outside every workspace ($ARGON_TRUSTED_ARTIFACTSexplicit path for tests/overrides, else$XDG_STATE_HOME/argon/trusted-artifacts, else$HOME/.local/state/argon/trusted-artifacts— the same dependency-free resolution the per-user config file already uses); a consumer hashes the artifact bytes it loaded and admits on ledger membership. The ledger lives outside the workspace deliberately: the threat is a cloned or written-into repository shipping a poisonedtarget/*.oxbin, and a co-located trust record would be forgeable by exactly the writer it gates. Entries are content hashes, not paths — a self-built artifact stays trusted if moved or copied within the user account; bytes built elsewhere are never trusted implicitly. If no ledger path resolves (no home directory), nothing is recorded and nothing admits — the fail-closed direction. - Environment variables are user authority — except when they point back into the
workspace. The trust paths are env-resolvable by design (
ARGON_TRUSTED_ARTIFACTS,XDG_STATE_HOME,ARGON_CONFIG,XDG_CONFIG_HOME,HOME), and repository-shipped machinery the user runs (a direnv.envrc, a Makefile, a.cargo/config.toml[env]table) can set them — so an env-resolved trust path that RESOLVES INSIDE the current workspace root would let a repository vouch for its own artifacts, re-opening the planted-cache hole through the environment. Such a grant is VOID and the refusal is LOUD (the same trust-refusal warning, naming the resolved path and the workspace root): a workspace-resident ledger is never read and the artifact classifies foreign; a workspace-resident per-user config loses itstrust-derived-cachegrant specifically, every other key of the file still applying. Containment is checked on the RESOLVED path (canonicalized, against the outermostox.tomlroots enclosing the invocation’s input and the working directory) regardless of which variable produced it, so redirectingXDG_STATE_HOMEorHOMEinto the repository is the same refusal. Two honest limits: a repository carrying noox.tomlanywhere is not a workspace and anchors nothing (its artifacts already confer execution when operated on directly — the same trust posture as above); and a user whose home directory itself sits inside a workspace root will see every standing grant void, loudly — the loud direction, never the silent one. Trust can also be granted deliberately, when speed over the self-built guarantee is a chosen trade: - per load:
--derived-cachetrusts the named artifact’s cache for this invocation; - standing:
trust-derived-cache = trueunder[cache]in the per-user config file ($ARGON_CONFIG, else$XDG_CONFIG_HOME/argon/config.toml, else$HOME/.config/argon/config.toml). The key is honored from the per-user config only — a workspaceox.tomlmust not self-authorize its own artifacts (a repository-controlled file granting trust to repository-controlled bytes would re-open the planted-cache hole).--no-derived-cacheforce-disables under everything above. A FOREIGN artifact’s present cache — admitted by none of the above — is ignored loudly: reads recompute from the event log (answers stay correct) and a warning-class diagnostic (OW1209, allocated viagrammar.d/diagnostics/) names the ignored section and both opt-ins. Loud is the point: a trust refusal is a visible event, not a silent per-read performance cliff. Declines that are the user’s own request (--no-derived-cache) or a store-shape matter (non-pristine, registry-bearing, non-default scope) stay silent as before;
- same invocation: the artifact was built by this process (
- the section’s directory
content_hashverifies against the body (accidental-corruption gate the reader currently never runs for any section), and the payload passes bounds enforced before materialization — mechanism, not aspiration: (1) the encoded-length gate (D3 cap) on the raw body; (2) a structural preflight over the payload bytes with the low-level CBOR decoder (ciborium-llheader walk — today only a transitive dependency viaciborium; the implementation declares it explicitly inoxc-oxbin’s Cargo.toml): validate declared dictionary / relation / per-relation row counts and cumulative declared byte lengths against fixed caps before anyserdematerialization allocates; only thenRelationCatalog::from_bytes(catalog/mod.rs:460today decodes generically — the preflight wraps it, it does not modify it); - every D5 validity gate passes.
Then the arm seeds a
ModelCachemirroring the durable warm path’s architecture precisely — stated precisely because thepersist.rs:236-243precedent is subtler than “frozen maintainer”: there the frozen placeholder wraps an empty catalog and reads are served from the separate columnar view. Here: decode the catalog, restamp the world-assumption map and metric resolution from the module (D1 — the wire form does not carry them;catalog_seed.rs:20-25is the restamp source), build the columnar view from the restamped catalog as the serving surface, and install the frozen maintainer over the restamped catalog itself (not empty — it keeps the lockstep invariant meaningful and is the ready input for the deferredfrom_modelupgrade). First ineligible write drops the cache and rebuilds exactly as today. (Thefrom_modelincremental upgrade is deferred out of v1 — see D1 and Open Questions; v1 is frozen-seed only.) Consumers, scoped honestly: every read that flows throughensure_strict_cache—ox query,ox derive,ox test,ox run-scenario(decided here because D6’s golden gate runs exactly those commands; a cache the conformance gate never exercises would be an untested consumer).ox whyis out of v1:converged_model_for_explain(explain.rs:846-889) deliberately re-materializes and re-evaluates for explanation fidelity and does not read this cache. An absent section is silent fall-through (the book’s “re-saturates if absent”). An invalid section (commitment mismatch, generation mismatch, hash mismatch, undecodable, over-bounds, version- or epoch-refused) is also fall-through-to-recompute — never served — but emits a new warning-class diagnostic (OE12xx, allocated viagrammar.d/diagnostics/) so a corrupt cache is visible, not silent. Durable-backend (Postgres-backed) stores are out of v1 scope explicitly, and fail closed by construction: only the one-shot CLI read commands thread consumption consent (ox serve— the only--storage pgsurface — never does), and the pristine-seed marker is set only by in-memory module seeding, so a pg-backed store never reaches this arm; the pg-backed seed watermark is untested and stays out of scope until a serve-side consumption design (Open Questions) provides its own provenance answer.
D5 — Validity = a complete derivation-input commitment, generation lockstep, and an explicit
trust boundary. artifact_hash alone is not a sufficient key: the derived model also depends
on GlobalControl semantic fields the evaluation catalog copies in (default_world,
metric_resolution_nanos), on the mapping section’s placement worlds (outside the four hashed
sections), on the accepted CoreIR/tier/runtime preamble axes, and on the semantics of the whole
derivation bundle — not oxc-reasoning alone: build_strict_maintainer composes
materialize_predicates + rule preparation + Engine::evaluate (strict_cache.rs:486-498), and a
semantic change to any of them re-shapes the model with no format-axis bump. The epoch constant
therefore lives in oxc-runtime (DERIVATION_SEMANTIC_EPOCH), the crate that owns that bundle.
The consumer recomputes the D2 derivation_commitment from its own view — its recomputed
Oxbin::computed_artifact_hash() (oxbin.rs:398-414), its decoded GlobalControl fields, its
mapping_body, its accepted axes, its compiled-in epoch, and the payload schema version it
ACCEPTS (the producer committed the version it WROTE, so a wire-form bump invalidates by
commitment as well as by the explicit version gate) — and requires byte equality with the
payload’s commitment; requires payload_version in range; requires seed_generation tuple
equality (D4). Any failure → fail-closed recompute. The epoch’s honest limit: no in-repo test
can detect a forgotten bump — CI builds and re-derives with the same binary, so both sides of any
differential agree by construction (this falsifies the first draft’s claim that the corpus arm nets
that omission). What ships instead: (a) a committed epoch-mismatch fixture (a payload stamped
epoch−1 via a test-only constructor) proving the refusal path mechanically; (b) a review rule added
to compiler/AGENTS.md in the implementation PR — any semantic change under
materialize/prepare/evaluate must bump the epoch — making the omission a reviewable defect;
(c) the residual risk recorded here as accepted, with mechanical epoch derivation in Open
Questions. The events-sensitivity and cache-churn-invariance halves of the inner artifact_hash
are already proven in-tree (writer_stamps_deterministic_artifact_hash,
artifact_hash_ignores_cache_sections). The weak in-memory module_fingerprint (DefaultHasher,
strict_cache.rs:103-122) is not persisted — compile/rule.rs:103-110 records why a 64-bit
non-crypto hash must not be collision-load-bearing.
Trust boundary, stated plainly: the commitment authenticates consistency, not honesty — the
payload and the hashes it is checked against travel in the same file and share one trust domain. A
crafted artifact can pair a truthful event log with a well-formed, wrong model and a matching
commitment; no in-file check can detect that, by construction. The cache therefore carries exactly
the artifact’s trust level, no more: today a hostile artifact must lie in its inputs (events —
auditable by replay), while a cache lets it lie in a conclusion while the inputs look clean.
The boundary is therefore enforced by default, not merely documented (D4): consumption
defaults ON only for artifacts this user’s own builds produced — same-process builds, plus prior
builds recorded in the per-user trusted-artifacts ledger — never for bytes that merely sit at a
workspace path; a foreign artifact’s cache is ignored loudly (OW1209) and reads recompute.
Trust can be granted deliberately: --derived-cache for one load, the per-user-config
trust-derived-cache key standing. Consumers who must audit a third-party artifact’s cache run
ox verify-cache (D6), which re-derives the model from the event log and diffs it against the
persisted section — the differential arm as an on-demand command, making conclusion-lies
detectable without paying recompute on every read. Artifact signing/attestation is the complete
answer and a separate arc this RFD does not open.
D6 — The oracle gate ships in the same arc, before the consumer defaults on. Per RFD 0035 D8 (“no optimization ships without the oracle”):
- A differential arm in the maintainer harness: a cache-seeded, restamped model (D4) must be
RelationCatalog-equal to a from-scratch derivation over the same events — run across the realAUDIT_CORPUSexamples (oxc-runtime/tests/examples_corpus.rs:2577-2739), closing RFD 0055’s open follow-up of registering reasoning-evaluable entries into the differential audit. The arm carries an eligibility filter with positive route assertions, and the filter is the production routing predicate itself, not a re-derivation: the implementation exposes the exact conjunction under which the D4 arm serves (the strict-read routing intoensure_strict_cacheAND the fragment/provenance/pristine-seed gates) as a deliberately narrow#[doc(hidden)] pubaudit API —pub(crate)alone is unreachable fromtests/examples_corpus.rs, an integration-test crate; the doc-hidden export keeps the surface out of the documented API while letting the audit call the same function the consumer calls, so filter and consumer cannot drift. The arm asserts the cache route was actually taken for every package the predicate admits (a route counter, mirroring the audit’s existingaudited/fellbackdiscipline), asserts byte-identical fall-through behavior for every package it rejects, and requires every fall-through to be explained by the predicate — an arm that silently exercises zero packages is a failing gate, not a passing one. Route-away classes, corrected to measured reality (slice-4 execution evidence, superseding the v5 review’s static reading ofstrict_cache.rs:124): among the corpus, only the defeat-plane (defeasible) packages route away from the warm-cache read path; federated, modal, temporal, and standpoint strict reads DO flow throughensure_strict_cache. The audit therefore asserts per-class route outcomes as measured — defeat-plane: nonzero routed-away; each served class: nonzero served — and, separately and crucially, that temporal/clock-reading/dated packages are served but never cache-SEEDED (the D3 fragment gate is a seeding gate, one layer below routing). The arm also carries the Can-faithfulness round-trip (RFD 0070 §6): over a well-founded-standoff package (examples/robot_plan_execution— its symmetric conflict pair is well-founded-undefined, and the module is inside the v1 fragment and strict-monotone, so it genuinely cache-seeds), a dedicated named test asserts (a) the standoff atoms are undefined in live evaluation — the non-vacuity half, so the test cannot pass emptily if the package changes — and (b) the cache-served undefined extent (the$undefined::companions) equals live evaluation’s exactly: the storage-layer regression RFD 0070 §6 names, pinned as its own test rather than implied by whole-catalog equality. - Conformance-corpus entries (RFD 0055 gate, auto-discovered — and exercised for
real because D4 makes
ox test/ox run-scenariocache consumers): (a) a package whose goldens are answered through a cache-seeded load; (b) a poisoned-cache entry covering both undecodable corruption and a well-formed body with a mismatched commitment — goldens must still pass via re-saturation and diagnostics must show the D4 warning. The remaining adversarial case — well-formed, wrong model, matching commitment — is undetectable in-file by construction; it is covered by arm 1 in CI (cache-seeded ≡ recomputed over the corpus catches any producer bug that would emit it honestly) and by the D5 trust boundary operationally. The front-half placement is the point: this is the pipeline region where the nine historical silent-wrong bugs lived, and goldens are the only oracle class attached there. - Fuzz:
check_oxbinalready fuzzesread_oxbinend-to-end; seed corpus gains cache-bearing artifacts, and the payload decoder gets structure-aware proptest cases inoxc-fuzz(the MAGIC_BYTES-prefix pattern) with the D4 size bounds asserted (over-bounds inputs must refuse before allocation). - CI placement (RFD 0062 tiers, stated accurately):
compiler-testand the corpus gates do not run on PRs — the PR tier is build/clippy/fmt/deny/drift only; they run in the merge queue (whereci-gateenforces) and post-merge. The differential arm, both conformance entries, the epoch-mismatch fixture, and the new unit suites therefore gate in the queue; no new PR-tier job is added (the PR tier’s build+clippy already covers compile health of the touched crates); libFuzzer stays nightly-only per the existing fuzz posture. Nothing new lands post-merge-only. - The on-demand verify mode:
ox verify-cache <input>loads an artifact (the trust gate deliberately does not apply — verification is how an untrusted cache becomes auditable), re-derives the model from the event log through the same producer bundle (materialize + rule preparation + evaluate), restamps per D4, and diffs the result against the decoded section-6 payload. Identical ⇒ exit 0; divergent or undecodable ⇒ nonzero exit naming the first divergent relation. This is the D6 differential arm as a command: conclusion-lies in a foreign artifact are detectable on demand without paying recompute on every read, and a CI job can pin any artifact population it chooses.
D7 — Format versioning: no axis bump. Populating section 6 does not move
oxbin_format_version (it stays 3.1). The bump trigger for that axis is a section-model or
container-encoding change (the versioning chapter’s rule: minor = additive section, major =
breaking section/encoding), and neither happens here: section 6 has been declared in the section
model since the format’s first cut, every 3.x reader already carries its body as opaque bytes
under a directory entry, and the container encoding is untouched. The in-repo precedent is
decisive in both directions: the 3.0 → 3.1 bump marked an additive field in GlobalControl — a
structure every reader eagerly decodes — while the mapping section (id 11), a genuinely NEW
section joining the model, shipped inside 3.1 with no bump; populating a pre-declared reserved
section is strictly weaker than the mapping precedent. Consumer evolution of the payload is
governed by the payload’s own payload_version (D2), which is both explicitly gated (D4) and
inside the derivation commitment (D5) — and cache consumption additionally requires exact
format-axis equality (D4) — so a bump would gate nothing on any side: it would be documentary
only, and noisier than the repo’s own practice. The §18.5 acceptance predicate is unchanged;
older runtimes carry the body as the opaque bytes they already carry today and never consume it.
D8 — Scope: default scope only, v1. (TenantId::DEFAULT, ForkId::DEFAULT, default standpoint, Now) — exactly what build-time knowledge determines and exactly what the CLI seeds. Every other
coordinate (named tenants/forks, non-default standpoints, as_of reads, past-belief) falls through
to today’s paths untouched. The runtime, multi-scope, post-mutation world remains
RFD 0036 D9’s segment manifest; this RFD is the build-artifact tier of the same
RFD 0018 D4 CQRS arc, not a competitor to it.
D9 — Docs ship in the same change (repo spec-first rule): FORMAT.md §D.10 rewritten (and its
§D.3 “IDs 10..99 reserved” note corrected for shipped 10/11 while touched); the build chapter’s
section-model row stands as written (it becomes true); appendix-c gains the D4 diagnostics (the invalid-section warning and the foreign-cache warning); CHANGELOG entry.
D10 — Explicitly out of scope, each a named follow-up rather than scope creep:
mmap/FLAG_LAZY realization (aspirational for every section today — the reader byte-copies all of
them, no mmap crate exists); the events-section decode-path fixes (the Value-tree double round-trip
helpers.rs:313-319 and the per-call QueryDeclBody re-decode — additive wins that need no RFD);
checkpointing JoinCache/DredcState (requires a rule-shape fingerprint that does not exist; first
measure from_model’s recount cost); section 7 / DBSP Z-sets; a query-body plan cache (none exists
at any layer — a separate, likely large win); the mem-backend serve path’s rebuild-per-request
(#231-1 territory); multi-standpoint prematerialization.
Rationale
Work should be proportional to change. The artifact is immutable between builds; the model is a pure
function of it and of the evaluation configuration (§Context anchors); therefore deriving it once,
at the single-writer point, and validating by a content commitment is the minimal-machinery design —
no clocks, no generation protocol. The commitment’s core (artifact_hash) exists, is stamped, and is
tested for exactly the cache-exclusion property; the first-draft review of this RFD established that
the full key must also commit the GlobalControl semantic fields, the mapping section, the accepted
axes, and an evaluator semantic version (D5) — hence derivation_commitment rather than the bare
hash — and the perf-slice review appended the payload schema version (D2): the version shapes what
the consumer accepts, so it must invalidate by commitment, not only by the explicit gate. The
payload reuses an existing, documented-deterministic codec (RelationCatalog::to_bytes,
sibling of the durable checkpoint’s columnar seal), so the new surface is the placement, not the
serialization. Choosing the artifact tier sidesteps the concurrency coordinates where a 2026-07-07
adversarial campaign showed stale-cache bugs actually live. Choosing CBOR over the FORMAT.md sketch
follows both the strongest precedent (the newest shipped section) and the already-decided direction
(RFD 0035 D7); adopting Cap’n Proto would be a new dependency decision with no RFD and
no consumer, taken inside an unrelated change — exactly what the design-citation rule exists to
prevent.
The honest limit is stated rather than papered over: a mutation-bearing session on a
refinement-heavy module (iff concepts ⇒ not delta-simple) still drops the cache at its first
commit and pays the fixpoint at the new generation — unchanged from today. What the persisted model
buys is exactly the D4 scope, no more: (a) zero-mutation strict reads through ensure_strict_cache
— ox query/derive/test/run-scenario on v1-fragment, workspace-resolved artifacts (the
dominant CLI and CI shape); (b) forward payload compatibility — the persisted catalog is already
the exact input the deferred from_model upgrade needs, so no format migration when it lands
(the upgrade itself is out of v1; Open Questions). ox why re-derives by design, --extent reads bypass the model cache entirely, and
serve hydration is out of v1 (Open Questions) — none of those are claimed as consequences. Widening
the eligibility rules is RFD 0018’s D6 arc, not this RFD.
Alternatives
- Cap’n Proto per the FORMAT.md sketch. Rejected: undecided dependency, nonexistent schema file, row-oriented shape superseded by 0035 D7, zero precedent (every shipped section is CBOR).
- A sidecar cache file (
root.oxbin.cache). Rejected: the reserved slot exists; in-artifact placement inherits per-section hashing, the composition-signature discipline, single-file distribution, and the tested hash-exclusion property for free; a sidecar invents parallel invalidation and dirties every consumer that ships artifacts around. - Runtime/daemon-only answers (
ox serve, a build daemon). Orthogonal and insufficient: the CLI one-shot (build/test/CI, agent tooling) is a primary product surface; the serve mem-backend rebuilds per request by design today; residency does not help the first read after a build. - Persist the maintainer’s hot state (arrangements + DRedc counters) now. Premature: needs a
rule-shape fingerprint (
JoinCachekeys are positions into a specific optimizer output) and a dictionary-consistency treatment;from_modelmay already make it unnecessary. Measure first (D10). - Do nothing / rely on OS page cache. The page cache preserves bytes, not derivations; the 553 M instructions are paid warm.
Consequences
ox buildruns one fixpoint it previously deferred to the first reader; artifacts grow by the serialized catalog, bounded by the D3 cap (dated modules and over-cap payloads skip with a diagnostic — those artifacts behave exactly as today).- Zero-mutation strict CLI reads on the v1 fragment skip
Engine::evaluate, rule replanning, and the model’s share of decode; with D10’s decode follow-ups this is the 10× CLI arc, and with residency the 100× interactive arc. Connector-bearing, dated, clock-reading, and metric-temporal modules see no change until follow-up work extends the fragment (each exclusion is a named predicate with a named upgrade path, not an implicit gap). - A new payload type joins the compatibility surface, governed by
payload_version+ minor format bump; old readers are bit-compatible.DERIVATION_SEMANTIC_EPOCHbecomes a discipline with an accepted residual risk (D5): a semantic change that forgets the bump can serve a stale-but-committed model, and no same-binary CI differential can catch that by construction. The mitigations are the committed epoch-mismatch refusal fixture, thecompiler/AGENTS.mdreview rule, and this sentence — not a test that cannot exist. - The differential harness and conformance corpus grow permanent entries that outlive this feature — any future derived-state persistence (section 7, maintainer state) inherits the gate.
- FORMAT.md sheds a fictional dependency and matches shipped reality for the sections this RFD
touches; section 7’s
OE1205refusal goes from prose to code. - A crafted artifact can carry a lying model behind a matching commitment (D5): cache
consumption is a trust-domain decision enforced by a self-built default — same-process
builds and the per-user trusted-artifacts ledger admit; a foreign artifact’s cache is ignored
loudly with recompute unless explicitly opted in — with
ox verify-cacheas the on-demand audit for caches trust alone cannot vouch for. ox whykeeps re-deriving in v1 (explanation fidelity); its latency is unchanged.
Open questions
- Cap values. Are
min(64 MiB, 4 × events_body)and the decode-side count caps (D3/D4) the right v1 constants? (Constant tuning is follow-up work, not an RFD; sharded/streaming segments remain 0036 D9 territory.) - The
from_modelupgrade (deferred out of v1). Two obligations before it lands: (a) the EDB/dictionary reconstruction contract —from_modeltakes an EDB and a model whose exact relationship the caller guarantees (maintainer/mod.rs:359-367); the EDB must be re-derived from the module’s events against the payload’s dictionary without the re-interning hazardpersist.rs:328names, and the differential arm must gain post-seed mutation coverage (apply deltas, compare against recompute), not just seeded-model equality; (b) the cost question — is the recount (DredcStaterebuild + arrangement warm-up) cheap enough to beat frozen-first? Measure on the bench baseline shapes. - Dated modules. v1 declines them (D3, the RFD 0063 epoch invariant). The upgrade
is persisting the model’s validity epoch (
valid_untilfrontier) and gating consumption on the read clock falling inside it — worth doing only with evidence dated modules are a hot CLI shape. ox buildself-reuse. Build’s own internal load repetitions (check/discharge/encode) could consume the model they just derived — measure whether that’s worth the plumbing.- Mechanical epoch derivation. Deriving
DERIVATION_SEMANTIC_EPOCHfrom the build itself (e.g. a hash of the derivation-relevant crate sources or of a semantic-vector fixture output) would close the forgotten-bump residual (D5); over-invalidation cost vs. review-rule reliability is the trade to study. - Serve-side consumption.
oxc-serve/RuntimeServicehydration from the artifact cache (generation-matched cold starts) is deliberately out of v1; it needs its own provenance answer for uploaded artifacts before defaulting on. - Housekeeping surfaced during recon, tracked separately: RFD 0063 is double-allocated
(
0063-bitemporal-read-point.mdindexed;0063-datalogmtl-temporal-engine.mdorphaned from README/SUMMARY); FORMAT.md carries further drift beyond §D.10 (missing §D.4artifact_hashkey, stale appendix cross-references);read_oxbinverifies no section directory hash today (D4 fixes it only for section 6 — a general verification pass is a small hardening follow-up). None block this RFD; all deserve their own PRs.