# ADR-036: edoc-1 — the external-dynamic-engine observational certificate **Status:** accepted (design authority — unblocks three deferred rows; no code ships this sprint) **Date:** 2026-07-16 **Deciders:** external-engine track — the certificate ADR that [ADR-030](adr-030-external-dta-simulators-deferred.md) names as the single unblocker for three rows: MATSim (ADR-030's own deferral) plus the `duaIterate` and `simulation()` follow-ups [ADR-027](adr-027-sumo-marouter.md) and [ADR-029](adr-029-dtalite-tap.md) defer to the DTA/DNL track **File:** `docs/design/adr-036-external-dynamic-observational-certificate.md` ## Context — the blocker ADR-030 named, and three pilots that measured the way through ADR-030 deferred three adapter rows — MATSim, DynaMIT, DYNASMART — on a **measured** record, and named the one ADR that would unblock a *different* trio: MATSim plus the ADR-027/029 named follow-ups `duaIterate` and `simulation()` (DynaMIT/DYNASMART stay blocked upstream — see Consequences). Its blocker, verbatim: *"The blocker is formulation — A2 is impossible in kind, not degree. ... MATSim's QSim has no static latency function: each link's traversal time is `length/freespeed` — a constant — whenever the flow capacity does not bind ... No `(freespeed, flowCapacity, storageCapacity)` choice produces `t = fft·(1 + b·(v/c)^p)` on any interior flow range."* And its remedy, verbatim: *"One ADR: the external-dynamic-engine observational certificate ... It unblocks three queued rows at once — MATSim, SUMO `duaIterate`, DTALite `simulation()`."* `docs/ARCHITECTURE.md` records the same gap: external dynamic engines are *"otherwise scored on the observational track (which for external dynamic engines does not exist yet ... the mandatory cost-matched anchor is impossible in kind until that certificate ADR ships)."* This is that ADR. The deferral was correct and stays correct in its own terms: there is no static latency function to match, so ADR-027/029's shipping bar — the A2 cost-matched anchor, *"the row is not shipped without A2"* — cannot be met **in kind**. This ADR does not weaken that bar; it supplies the object that plays A2's role when no cost law exists to match: **the pinned engine itself**. The certified claim shifts accordingly — not "certified Wardrop UE under the declared BPR" but "the literature-standard simulation-DTA experienced-time gap w.r.t. the engine's own frozen realized field," a different, honestly-labeled quantity on its own leaderboard scale. Three pilots ran the candidate engines fresh on this box (2026-07-16) and measured every load-bearing quantity the certificate needs. **SUMO `duaIterate`** (eclipse-sumo 1.27.1 wheel): a 20-iteration meso run emits per-iteration experienced edge-cost dumps, plan/ alternative files, and tripinfo+vehroute; one pinned `duarouter` best-response call on the frozen final dump recomputes a relative gap on experienced costs model-blind, with a working negative control (AON `RG_D1` 0.17203 vs converged 0.01526 — the 11x separation ADR-030 said the dynamic track lacked). **MATSim** (2025.0 release on Temurin 21): `lastIteration = firstIteration` on the emitted plans is a zero-replanning replay that reproduces all 100 per-agent (departure, arrival, route) tuples exactly, and `global.randomSeed` gives raw- gzip-byte determinism even with replanning — the first stochastic-track external engine. **DTALite `simulation()`** (0.8.1): a deterministic sub-second queue-DNL map from an explicit plan artifact (`vehicle.csv`) to an experienced-time artifact (`trajectory.csv`), bit-identical across re-runs, whose gap the harness recomputes from `trajectory.csv` alone. Each pilot also measured the hazards that make the naive design wrong — insertion-backlog invisibility, self-report substitution, plan-set impoverishment, aggregation-window noise, head-block infinite loops at `rc=0` — and those hazards *are* the certificate's gates. ## Decision — EDOC-1, the external-dynamic-engine observational certificate **EDOC-1** scores **D1** (the frozen-field best-response gap) with a **gating slice of D3** (conservation/self-consistency) and non-gating D3 diagnostics; **D2** (held-out real counts) applies only where a real sensor panel exists and is never part of this score (its own ADR, ruling R11). The engine is part of the instance on the **hashed-loading-kernel** precedent of ADR-031 (`TDTAScenario` hashes the loading operator; the certificate is defined w.r.t. one kernel) and the **pinned-engine observational** posture of ADR-034's stage 2 (re-run the pinned engine at emitted inputs, score on its own scale). Putting engine identity + version **inside the instance hash** is a NEW move relative to ADR-027/029, where the engine's cost law was matched against the *declared* BPR and its version was manifest provenance + a CI pin (marouter's A2 anchor existed precisely to SEPARATE the engine's hardcoded law from the certificate). Here no declared cost law exists to certify against, so the certifier instead re-derives every scored number by re-running that pinned engine on the model's emitted plans — the engine is the matched object, not a provenance note. ### The instance A content hash under a new domain prefix **`"tabench-edoc-scenario-v1;"`**, length-framing every array (the newell-3det / ADR-031 defense-in-depth, done while the edoc hashes are unpublished), over: the network + demand artifacts; the **engine identity + EXACT version pin** (pip wheel version for `eclipse-sumo`/`DTALite`; the release-zip md5 + JDK major for MATSim); the pinned **seed** (or the pinned macrorep seed **list** on the stochastic track, R5); the semantic engine config; the cost-field aggregation interval **Δ**; the backlog bound; the negative-control separation factor; the **resolution floor and replay hard-deadline constant**; and — the same criterion, **every certifier-side constant that changes a censor / floor / score outcome** — the **R2 field-semantics + field-completion-rule selections**, the **origin-wait convention**, and the **option-B BR walk universe + length bound**. Each bears on the scored outcome (censor / `<= floor` / ranked eligibility), so a post-publication edit to any of them must mint a new instance hash — the same hash-everything discipline this domain invokes. Domain-separated, so no existing static/ dnl/dta/tdta hash moves — the golden Braess `cf00f411…` is byte-untouched (re-asserted in the first shipping row's tests, per every prior ADR). ### Emitted artifacts (mandatory) A model emits, and only emits: * **Plans `P`** — per-agent route + departure time (SUMO `vehroute`/`.rou.alt.xml.gz`; MATSim `output_plans.xml.gz`; DTALite `vehicle.csv`). * **Experienced record `X`** — per-agent **door-to-door experienced time including pre-insertion delay** (SUMO `tripinfo` `duration + departDelay`; MATSim events; DTALite `trajectory.csv`), plus the per-interval experienced link-cost field the run consumed (`dump_Δ.xml.gz` etc.). * **Provenance** — engine configs, version string, seed. Self-reported convergence metrics (`duaIterate` relStdDev, engine gap prints) are provenance **only, NEVER gated on** — the ADR-029 posture, earned again by measurement (`duaIterate`'s relStdDev was 29x smaller than the recomputed gap and read exactly 0.00000 while stuck at pure AON). ### Gates (censor; only wrong shapes / config errors raise) * **G0 — pin.** The installed-engine version is read at certify time (`importlib.metadata` / jar md5 / `sumo` version string), the instance hash and seed are checked; a mismatch is a **configuration error and RAISES eagerly** (the ADR-020 eager-config discipline). The pin extends to the **model-side execution environment** the engine's byte-determinism requires — `OMP_NUM_THREADS=1` for DTALite (its `#pragma omp parallel for` over shared `std::deque` is a *correctness* requirement, not just determinism hygiene) and the certifier's **full** JDK version (not only the major) for MATSim, since G1 demands canonicalized byte equality and a JDK minor/patch drift between the model's and certifier's toolchains is otherwise an uncontrolled censor surface. These sit in the instance provenance; a mismatch RAISES. * **G1 — replay fidelity (the A2 ANALOGUE).** With no static latency function there is no cost-matched anchor, so the matched object becomes **the engine itself**: the certifier re-runs the pinned engine in **zero-replanning replay** on the emitted plans (one pinned `sumo` meso run; MATSim `lastIteration = firstIteration` — measured: replanning fires only *between* iterations, so this is a pure replay; DTALite `simulation()` on `vehicle.csv`), and the replayed per-agent (departure, arrival, route, experienced-time) tuples must equal `X` **exactly** under the pinned canonicalization (R10). The replay runs **TWICE** and the two canonicalized outputs must be identical (the determinism gate). Any divergence from `X` censors as non-replayable / self-report substitution. * **G2 — plan-demand match, two-sided.** The agent set, per-agent OD endpoints, and per-OD agent *count* match the instance trip table **EXACTLY** — a bijection, no tolerance; per-agent departure times match **exactly up to the instance's declared engine-grid quantization**. **Fixed departures give the model ZERO timing freedom** (the ADR-031 demand-match pattern; it is the strongest gate here). ADR-031's `eps_od = tol·max(1, D_od)` is inherited as *ancestry, not identity*: it applies ONLY where the trip table is real-valued and must be integerized (the R9 DTALite `route_assignment.csv` construction), bounding per-OD vehicle-*count* mismatch — never departure-time slack (a timing tolerance would reopen the very door this gate closes). A construction-time rule (eager G0-style RAISE) restricts the pinned semantic config to **route/selection-only replanning strategies**: a departure-time- or mode-mutating strategy (e.g. MATSim `TimeAllocationMutator`) is a config error, because its faithful final iterate would break the exact-departure bijection. * **G3 — two-sided delivery.** A per-agent completion census from the replay (arrivals == departures, no period-end truncation, no pre-period head-block loss), **`departDelay` included in every cost**, and max/mean insertion backlog under the scenario-declared bound (the Primer's vehicles-waiting-to-enter warning, made a gate). Violations censor. * **G4 — conservation / self-consistency (gating D3).** Per-link entered/left conservation, monotone cumulatives, per-agent link-time chains (`arrival_{k+1} >= departure_k`) — the `dnl_gaps` C0/C1 shapes run on the replay output. ### The scored metric — RG_D1, the frozen-field best-response gap From the G1 replay the harness builds the **frozen experienced cost field `Ĉ`** (per-link, per-interval experienced traversal times, entry-time-resolved at the pinned Δ, R2). For each agent `i` departing at `t_i`: * `c_drv(i)` = the **origin-wait profile** for the driven route's first link at `t_i` (a per-first-link interval-mean of the replay's own `departDelay` samples, built with the same construction and occupancy-aware completion as the link field — the frozen field `Ĉ` cannot itself carry off-network insertion delay) **plus** the time-dependent evaluation of the **driven** route on `Ĉ` from the agent's first-link entry time; * `c_br(i)` = the min over the **FULL-NETWORK** route universe of [the origin-wait profile for **that route's** first link at `t_i` **plus** the same field evaluation] — a **certifier-owned time-dependent shortest path** (R3) starting at the agent's origin, so the best response is charged the **alternative** first link's own measured wait. Both sides compose the *same* frozen profiles (origin-wait + link field), so the driven route is in the min set and `c_drv(i) >= c_br(i)` per agent **by construction** (the `tdta_gaps` property, ADR-031). **This DEFAULT scores entrance-choice (insertion-queue) disequilibrium** — a state where every agent could skip a measured 186 s queue via a cheaper first link no longer scores 0. A family MAY instead declare the **agent-symmetric** convention (the same agent's `departDelay_i` on both sides, which cancels in the numerator); it then MUST disclose **entrance-choice-disequilibrium blindness** in its scope section and its R8 non-comparability sentence. The origin-wait convention is a **hashed instance field** (the instance section). The score is RG_D1 = Σ_i (c_drv − c_br) / Σ_i c_drv (TSTT-normalized — ruling R1) D1 scores the **PREDICTED frozen-cost gap ONLY** — never the realized re-simulation of the BR plan (ruling R12; the measured overshoots are exactly why: SUMO +9.3%, MATSim naive one-step −0.252, DTALite 0.394 → 0.513). **Disclosure (pilot vs shipped estimator).** The pilot anchors quoted below are *estimator-approximate* to this composition, in two disclosed ways. (i) Each used the **agent-symmetric** origin-wait convention (the same agent's wait on both sides, cancelling in the numerator) — now the *optional* convention, not the shipped **default** (the per-first-link origin-wait profile), so the pilot anchors are **blind to entrance-choice disequilibrium** by construction; the shipped default charges the alternative first link's own measured wait and is re-derived on the first row. (ii) Each used a simplified BR universe/profile: SUMO a pinned `duarouter` on the Δ=300 dump; MATSim experienced driven cost vs a nearest-neighbor field capped `min(c_br, c_exp)` (the by-construction inequality does *not* hold in that estimator — the cap is the tell); DTALite an observed-two-route nearest-neighbor estimator (the exact observational shape forgery pair 3 disqualifies). The separation factor and floor anchors (R4) inherit this approximation and are **re-derived with the shipped substrate on the first row before family constants freeze** — the posture R2 already takes for Δ. ### The negative-control anchor and the resolution floor * **Negative-control anchor (per shipped instance, the ADR-027 incremental-control analogue).** The pinned AON / step-0 state must score `RG_D1` at least the declared **separation factor** above the reference state (measured separations, **estimator-approximate** — re-derived on the first row, see the composition disclosure: 0.17203 vs 0.01526 = **11x SUMO**; 0.393839 vs ~0.04 **DTALite**). An instance whose control cannot separate — e.g. the meso shared-edge queue-storage topology where the cost signal cancels — is **REFUSED at scenario construction**, never certified (ruling R4). * **Resolution-floor gate.** `delta = mean| Ĉ-evaluated driven cost − experienced cost from X |`, **always computed on the RAW interval-mean field** (it measures aggregation fidelity, not monotonization distance — so option-A monotonization, which deflates `Ĉ` vs experienced by a measured mean 189.8 s on a non-FIFO field, never self-fires this gate), must stay under the scenario-declared floor (measured **25.6 s mean / 80 s max at Δ=300 ≈ 7.9% of a ~325 s trip**). A run whose `delta` exceeds the floor is **CENSORED** (field-unfaithful — this also closes intra-interval oscillation gaming); run-specific `delta` only ever **censors**, it **never** reclassifies a row to sub-floor. **Sub-floor classification is by the FAMILY floor expressed in gap units:** the floor is declared in seconds, and the seconds→gap conversion is stated once here as `floor_gap = floor_seconds / (Σ c_drv / N)` (the mean per-agent driven cost); a row with `RG_D1 < floor_gap` is reported **`<= floor`, not ranked**, displayed at the floor value, tied with other sub-floor rows, and ordered relative to ranked rows **by that displayed value like any row** (no categorical placement). Because every *ranked* row scores `>= floor_gap` by definition, the ordering is total and no true-worse row can outrank a true-better one — closing the inversion a run-specific threshold would open. ### Tier-B (report-never-gate) The per-departure-interval `RG_D1` profile (the Primer's mandate, flow-weighted, bin = the pinned Δ, R1) + per-OD `RG_D1`, `n_improvers`, the observational experienced-cost gap from `X` alone (coverage-limited — vacuously 0 at AON, which is *why* it is not the score), the realized-BR overshoot (**only when the row enables the optional realized-BR run**, R12), max queue, the backlog series (which bounds the entrance-choice channel when the agent-symmetric origin-wait convention is used), the `delta` distribution, and the **BR-path field-coverage fraction** (the share of the certifier's TD-SP path-link-intervals actually loaded vs filled by completion — R2), a **disclosure by default**; a family MAY promote the coverage fraction to a censor gate at construction, in which case it is hashed and listed under **Gates**, not here. ### Seed semantics and the stochastic track The seed sits **inside the instance hash**. Deterministic engines (DTALite: an LCG keyed by time step — `seedable=False`) ride the **deterministic track** with no macroreps (disclosed). `duaIterate` pins `duarouter --seed` / `sumo --seed` as provenance — but it is *seed-dependent*, and since it ships first (before the MATSim stochastic track), **its track is decided at row time**: the **row-shipping sprint** must measure a cross-seed `RG_D1` spread on the reference solver state (in gap units per the floor's declared seconds→gap conversion, after the R4 floor re-derivation and before family constants freeze) and either (a) justify single-pinned-seed **deterministic-track** treatment when the spread is below the family floor, disclosing the spread as the score's seed-conditionality, or (b) fall back to the R5 pinned-seed-list stochastic shape. A single-seed leaderboard number with unquantified seed-lottery variance is forbidden. MATSim (`global.randomSeed`, raw-gzip-byte deterministic) is the first stochastic-track external engine: **P8 macroreps over a pinned seed list (≥5, ruling R5)** + bootstrap CIs; single- iteration / single-seed readouts are **forbidden** (measured `RG` noise 0.0012–0.0979 across it.14..19). Each macrorep emits and certifies **its own** final iterate under its own seed (G1 per seed); the score is the mean `RG_D1` with a reported bootstrap CI. ### Wall, the crash-vs-censor map, and rc-is-never-trusted The **certifier** pays the re-run wall — measured marginals 0.092 + 0.131 s (SUMO D1), ~10.6 s per MATSim seed (2× replay + BR; **~71–124 s** for the ≥5-seed macrorep set — R7), 0.04–0.05 s (DTALite). Every certifier engine call gets `stdin=DEVNULL` + a **hard subprocess deadline** (scenario-declared; the default is a **fixed interpreter/JVM-startup allowance plus a multiple of the measured whole-run replay wall** — not a bare multiple of the per-iteration wall, which fails against MATSim's own numbers: 10× the 0.39 s mean per-iteration wall is 3.9 s vs a measured 3.42 s replay atop ~2.8 s of fixed JVM startup that does not scale, so a naive multiplier censors an honest replay; ruling R6), and `rc` is **never trusted** — success is DEFINED as G1 matching (the ADR-029 doctrine; the engines exit 0 on missing/garbage inputs). **Crash-vs-censor map (ruling R6).** An engine crash/timeout while replaying **model-emitted plans** = **CENSOR with diagnostics** — an unexecutable / head-blocking plan is an invalid emission (DTALite's `loadNewAgents` head-block loops forever at `rc=0` on a pre-period departure, measured, the canonical case). A crash in the certifier's **OWN** BR / field computation over valid inputs = **RuntimeError infrastructure RAISE**, never laundered into `feasible=0`. ### Headline discipline and the leaderboard (rulings R1, R8) `RG_D1` is a **monotone transform** of the literature-standard simulation-DTA gap w.r.t. the **frozen realized field** — TSTT-normalized here (R1), with the Primer's SPTT-normalized form recoverable as `g ↦ g/(1−g)` (Chiu et al. `chiu2011dynamic`, the experienced-time relative gap over used routes; the Lu–Mahmassani–Zhou lineage `lu2009equivalent`; the Peeta–Mahmassani experienced-time conditions `peeta1995system`). It is **NOT** a Wardrop-under- declared-BPR certificate and **NOT** comparable to the static bfw-certified RG scale. Dynamic-external rows therefore get their **OWN leaderboard table**; the metric column is named **"frozen-field BR gap (RG_D1)"** with an explicit non-comparability sentence vs static Wardrop RG, echoed in the ROADMAP/MODELS rows. The re-simulated deviation would change costs — the `tdta_gaps` Tier-B caveat (ADR-031), disclosed, never scored. ### The twelve rulings (parent decisions; stated, not relitigated) **R1 — Normalization.** `RG_D1 = Σ(c_drv − c_br) / Σ c_drv`, TSTT-denominator (the house static-RG shape; all pilot numbers are already in it). *Rationale:* one convention must be pinned for the dynamic external track, and the TSTT denominator keeps the whole external family — static (ADR-027/029) and dynamic — on one arithmetic shape. The DTA Primer's SPTT convention and the shipped `tdue_gap`'s `TC_min` denominator are **disclosed** with the monotone equivalence `g ↦ g/(1−g)`; the per-departure-interval profile the Primer mandates is **Tier-B**, flow-weighted, bin = the pinned Δ. **R2 — Frozen field.** Entry-time-resolved evaluation on interval-mean experienced profiles at the pinned Δ (a hashed scenario field), with **waiting-not-allowed** TD-SP labels (matching the engine routers). **The field's temporal semantics are pinned to close a false-certify hole:** on a non-FIFO frozen field (a queue clearing across a Δ boundary makes an interval-mean cost drop faster than real time — an overtake), a naive FIFO-assuming label-*setting* Dijkstra prunes the boundary-crossing path and returns a too-high `c_br`, which **deflates** `RG_D1` (a false-certify, invisible to G1–G4 — measured on a constructed Δ=300 field: label-setting `c_br` 380 s where label-correcting finds 115 s). Each scenario family therefore pins **EXACTLY ONE** field semantics — either **monotonized + label-setting** (FIFO-consistent lower-envelope arrival-time smoothing of the interval means) OR **raw + label-correcting / time-expanded** no-wait TD-SP over an explicit universe (simple paths, or walks under a hashed length bound — under non-FIFO no-wait semantics the optimum need not be simple, so 'min over routes' must be pinned). **The two are NOT scored-outcome-equivalent** (measured on the constructed field: `c_br` 455 / 405 / 400 across option-B-simple-paths / option-B-walks / option-A on one emission — disclosed), so the choice, and option B's universe + length bound, are **hashed instance fields**; the `delta` resolution-floor gate is defined on the **raw** field regardless (above), because it scores aggregation fidelity, not monotonization distance. **Field completion is occupancy-aware and normative** (moved here from the forgery analysis): never-loaded links stay at free flow (optimistic *for* the deviation — the plan-set-impoverishment defense); an interior gap of a **loaded** link carries forward the last congested cost **ONLY where the replay shows nonzero occupancy / a standing entrance queue** during that gap (all three engines' artifacts reconstruct occupancy), and a **zero-occupancy** interior gap falls back to free flow (deviation-optimistic, pair-3-consistent). A *blind* carry-forward would let two bracketing congested samples **poison every interior interval** of an alternative route — the burst-poisoning attack (forgery pair 8), deflating `RG_D1` ~100× at the cost of a handful of agents; occupancy-awareness is the detection that closes it. Δ must be chosen **per scenario family** so the measured field-vs-experienced `delta` floor is below the family's declared resolution. *Rationale:* the RULE ships here; the numeric Δ default ships with each row, justified by measurement (300 s is documented too coarse — the 7.9% floor). **Non-FIFO and poisoned-alternative regression instances** are named substrate deliverables (the pilots' 4–6-link nets never produced link costs near Δ nor the burst topology, so both regimes are untested by the record). This closes interval-aggregation gaming, intra-interval oscillation, the non-FIFO deflation hole, and the carry-forward poison. **R3 — BR oracle.** The certifier-owned time-dependent shortest path is **NORMATIVE everywhere**; where a pinned engine router exists (`duarouter` on the frozen dump with `--weights.expand --skip-new-routes --keep-all-routes --routing-threads 1`) it serves as a mandatory **CROSS-CHECK** on that engine's rows — disagreement beyond the declared tolerance is an **infrastructure RAISE, never a censor**. *Rationale:* owning the SP is strictly stronger than ADR-031's declared-path-universe disclosure (the certifier supplies the counterfactual, so hidden cheap paths score against the plan); the cross-check keeps a pinned production router honest against the harness implementation. **The harness multi-OD TD-SP module is a named deliverable of the FIRST shipping row** (the repo's DNL ladder has the ingredients — pilot C's named remaining piece). **R4 — Floors / factors.** The `delta` floor and the negative-control separation factor are declared **PER SCENARIO FAMILY at construction** (eager gates). *Rationale:* the ADR sets the mechanism + the **estimator-approximate** anchors (**11x SUMO, ~10x DTALite** — each measured under its pilot's simplified estimator, not the R2/R3 composition; separation-factor **default ≥5**); the anchors are **re-derived with the shipped substrate on the first row before the family constants freeze** (extending R2's Δ posture to the separation factor and floor), because the resolvable floor is engine- and net-specific (the 6 s DTALite quantization floor differs from the 300 s SUMO meso aggregation floor). **R5 — MATSim stochastic shape.** P8 macroreps over a pinned seed **list (≥5)**; each macrorep emits and certifies **its own** final iterate under its own seed (G1 per seed); the score is the mean `RG_D1` with a **bootstrap CI** reported. Last-k within-run averaging is **Tier-B diagnostic only**. *Rationale:* mixing states across iterations would blur the certified-artifact identity (G1 must replay a single, fixed emission); a model never chooses its seed, and a single-iteration readout is noise-dominated (RG 0.0012–0.0979). **R6 — Replay timeout.** Replaying model-emitted plans that hang/crash = **CENSOR with diagnostics** (the DTALite head-block infinite loop at `rc=0` is the canonical case); default wall = a **fixed interpreter/JVM-startup allowance plus a multiple of the measured whole-run replay wall**, on the mean not the min, family-declarable. (NOT a bare 10× the per-iteration wall — that censors MATSim's own honest 3.42 s replay: 10× the 0.39 s mean per-iteration wall = 3.9 s barely clears it, 10× the 0.34 s min already fails, and ~2.8 s of the replay is fixed JVM startup that does not scale with per-iteration wall.) Failures in the certifier's OWN BR/field computation = **RuntimeError RAISE**. *Rationale:* an unexecutable plan is an invalid emission (the model's fault → censor); a certifier crash on valid inputs is infrastructure (never laundered into `feasible=0`). **R7 — Scale.** The per-row CI budget follows the ADR-027 **2–4 min job shape**; HPC-scale instances are **refused at construction** (the `Bo4MobHpcOnlyError` precedent, ADR-034); big-net certifier walls are measured per row. The stochastic-track marginal is `n_seeds × (2×replay + BR) + toolchain` — for MATSim at the R5 floor of 5 seeds, `5 × (2×3.42 + 3.71) + 17.8 ≈ 71 s` (≈ 124 s if each macrorep's certified run is also regenerated in CI), NOT the ~10.6 s single-seed figure; the 2–4 min budget holds at exactly 5 seeds, so **each family must bound `n_seeds`** at construction or it silently busts R7. *Rationale:* the certifier pays the re-run wall and MATSim's 550–772 MB JVM baseline is the CI sizing constraint; DTALite big-net scaling is unmeasured beyond 4 links, so each row measures its own wall before shipping. **R8 — Leaderboard.** Dynamic-external rows get their **OWN table**; the metric column is named **"frozen-field BR gap (RG_D1)"** with an explicit non-comparability sentence vs static Wardrop RG; ROADMAP/MODELS rows say it. *Rationale:* the headline discipline of ADR-025/027/ 029/034 — the score names whose equilibrium and which axis, and this is not the static bfw scale (the ADR-034 stage-2 separate-table posture). **R9 — DTALite plans.** The adapter constructs `vehicle.csv` from `route_assignment.csv` volumes (the engine emission is **dead code** — `TAPLite.cpp:2238` `route_volume = 0`); the integerization rule + its quantization bound are pinned in that row's ADR; `assignment()`- seeded plans are **adapter plumbing, NOT a model row**. *Rationale:* `assignment()`'s `vehicle.csv` is always header-only, so plans must be built from the FW split; the integer- agent quantization is adapter policy whose noise bound must be disclosed like any mapping floor. **R10 — Canonicalization.** A **versioned, hashed harness module**, domain prefix **`"tabench-edoc-canon-v1;"`** (trailing semicolon, the house form), length-framed per ADR-031. Its founding spec is the **four measured necessities**: strip the SUMO `generated on` timestamp comment + `summary` duration attr; sort MATSim same-timestamp event ties; hash the **decompressed** payload; positional-parse the DTALite trajectory (13-header / 12-field rows). The spec also **defines an explicit hash surface**: the four strips do NOT make the whole emitted tree byte-identical (measured: **23/184** SUMO files — `*.sumo.log`, `driver.out`, `dua.log`, `stdout.log` — still differ on wall-clock text after stripping), so **only simulation-state artifacts are hashed** (tripinfo, vehroute, dumps, `.rou`/`.rou.alt`, configs; events; trajectory) and engine/driver logs are provenance, **never** on the G1 hash surface — else the determinism double would over-censor every honest run. Upstream format drift **bumps the version** = new instance hashes, disclosed. *Rationale:* every one of the four is a measured false-hash surface from a pilot; a hashed, versioned module makes the canonicalization itself auditable and its drift explicit. **R11 — D2 boundary.** The BO4Mob stage-2 held-out-count certificate gets **its own ADR** but **SHALL reuse this ADR's replay + canonicalization substrate**; this ADR defines that substrate interface (the ADR-034 co-design). *Rationale:* D2 (real-sensor NRMSE on held-out dates) is a different scored object with no true OD and no declared BPR, but it re-runs the pinned engine at emitted inputs exactly as G1 does — shared plumbing, separate score and scale. **R12 — Realized-BR.** The realized one-step re-simulation is **never part of the SCORE or any gate**; it ships as an **optional per-row Tier-B diagnostic** run (budgeted in R7 when a row enables it), because it is overshoot-noisy by construction — measured **+9.3% SUMO, 0.394 → 0.513 DTALite** — so a re-simulated full deviation changes the field it was measured against (everyone deviates simultaneously) and is not a gap bound. **No small-fraction-reroute variant** either (an unprincipled dial). The overshoot numbers appear in the ADR as the **REASON D1 scores the predicted frozen-field gap**. *Rationale:* a fractional-reroute knob would be a tunable the benchmark cannot defend; the optional realized run is a probe (forgery pair 8), never a score, so the certifier's default path does not double engine calls. ## Forgery analysis — twelve attack/defense pairs Named so an adversarial review attacks them; each carries a tag and a measured defense. 1. **Unreached-demand hiding** — *[GATE].* Insertion backlog (`departDelay`) is off-network waiting invisible to every edge dump (measured mean **186 s / max 414 s** on the pathology nets); DTALite silently drops pre-period departures AND head-blocks later agents on the same first link, and truncates in-flight agents at period end — all at `rc=0`. **Defense:** G3 two-sided delivery — a per-agent completion census from the pinned replay (arrivals == departures; DTALite completion inferred from `current_link_seq_no`, never the dead `loaded_status` flag), `departDelay` in every scored cost, a scenario-declared backlog bound, and a construction gate quantizing departures into `[start, end)`. 2. **Self-report substitution** — *[GATE].* Emit doctored `tripinfo`/`trajectory`/experienced costs, or lean on the engine's own convergence flag (`duaIterate` relStdDev measured **29x smaller** than the recomputed gap and **exactly 0.00000 while stuck at pure AON**; DTALite's printed gap uses a different normalization and can be negative/frozen). **Defense:** G1 replay fidelity — the pinned engine re-run reproduces every per-agent tuple exactly under the pinned canonicalization (doctored artifacts diverge byte-canonically: MATSim sorted-stream md5 identical on both sides; DTALite trajectory md5-pinned; SUMO bit-identical modulo timestamp text). Engine self-reports are provenance-only. 3. **Plan-set impoverishment** — *[STRUCTURAL + GATE].* Emit only plans the best-response step cannot improve because alternatives were never loaded (a deliberately tiny route universe); pure observational scoring gives AON `RG=0` by construction (measured: only **6/10** depart bins observe both routes even at the fixed point). **Defense:** the BR route universe is the FULL network via the certifier-owned TD-SP (R3), so unloaded links enter the frozen field at free flow — optimistic *for* the deviation — and hidden cheap paths score AGAINST the plan (measured: the AON / step-0 states score **0.17203** (SUMO, pure AON — all 500 on one route) **/ 0.393839** (DTALite, the step-0 781/219 FW proportional split), not 0). Strictly stronger than ADR-031's declared-path-universe disclosure. The residual shared-edge-cancellation case is closed by the construction-time negative-control separation gate. 4. **Seed shopping** — *[GATE].* Run many seeds, emit the luckiest state. **Defense:** the seed is inside the instance content hash; the emission must replay under **the** pinned seed or G1 censors. Stochastic-track rows (MATSim) are scored as P8 macroreps over a PINNED seed list with bootstrap CIs (R5) — a model never chooses its seed. 5. **Cost-averaging games** — *[GATE].* Emit seed-averaged or smoothed artifacts to flatter the gap. **Defense:** an averaged artifact is not any fixed-seed replay output, so G1 censors it; each macrorep's artifacts must replay under their own pinned seed. 6. **Engine-version drift** — *[GATE + RAISE].* Solve under a newer/better engine build, claim the pinned version. **Defense:** version is read from the INSTALLED engine at certify time and sits in the instance hash (G0: mismatch RAISES as a config error); G1's replay under the pinned build diverges byte-canonically from another build's artifacts. CI pins the tested engine exactly (the ADR-027/029 floor-not-pin + CI-pin split). 7. **Aggregation-window gaming** — *[GATE + DISCLOSED-SCOPE].* Shape within-interval congestion so the interval-averaged field under-reports the driven plan's cost (measured field-vs- experienced divergence **25.6 s mean / 80 s max at Δ=300, ~7.9%**). **Defense:** same-basis scoring (driven AND best-response both on the same frozen field, driven route in the min set) keeps the gap `>= 0` by construction; the `delta` field-vs-experienced gate censors when the field no longer represents experienced costs; Δ is a pinned scenario field required much smaller than the congestion timescale, and gaps below the **family** floor (in gap units, per the floor-gate conversion) are reported `<= floor`, not ranked. 8. **Poison-the-alternative** — *[STRUCTURAL + DISCLOSED-SCOPE].* Route some of the instance's own agents onto alternative routes to inflate their frozen-field costs so the best response stays home — including the **burst-poisoning** variant: reroute only the first- and last-departing agents onto an alternative and congest it inside *their own* intervals, so a blind carry-forward completion would paint every interior interval congested (measured: `RG_D1` 0.4024 → 0.0040, a ~100× deflation, at the cost of a handful of agents). **Defense:** G2's demand-match bijection forbids phantom demand, so poison agents are real agents whose experienced cost lands inside `Σ c_drv` — the attack raises the score it tries to lower unless it actually congests the alternative, i.e. approaches a frozen-field equilibrium, which is exactly the certified object; and R2's **occupancy-aware** field completion refuses to carry a congested cost across an interior gap the replay shows **empty** (zero-occupancy → free flow), so the burst cannot paint the intervals between its two samples. The remaining wedge (frozen-field equilibrium vs re-simulated DUE) is the literature-standard caveat, disclosed in the headline and probed by the **optional** realized-BR Tier-B diagnostic (R12), never gated. 9. **Hash games on volatile bytes** — *[GATE].* SUMO's `generated on` timestamp comment in every XML, summary wall-clock duration attrs, MATSim same-timestamp event-order permutation between replay and original, gzip metadata, DTALite's 13-header/12-field trajectory rows. **Defense:** the pinned canonicalization spec (versioned, hashed harness module, `tabench-edoc-canon-v1;`, R10) — strip timestamp comments and duration attrs, sort same- timestamp events, hash decompressed payloads, positional trajectory parse — all four measured necessities from the pilots. 10. **Departure-time gaming** — *[GATE].* Shift departures to dodge congestion while claiming the instance's demand (the ADR-031 timing-freedom door). **Defense:** G2 requires per-agent departure times equal to the instance trip table **exactly, up to the declared engine-grid quantization** — NOT at `eps_od`, which is a vehicle-*count* tolerance for the R9 integerization only (a timing tolerance would reopen this door); fixed departures give zero timing freedom, the ADR-031 demand-match pattern. 11. **Emit the certifier's own best-response output as the plan** — *[NO DEFENSE NEEDED].* It is legal optimization, and self-defeating: the BR plan re-scored under ITS OWN replay field shows the measured simultaneous-deviation overshoot (**DTALite RG 0.393839 → 0.512861; SUMO +9.3%**), so gaming the metric means actually solving the fixed point. 12. **Vacuous or degenerate instances** — *[RAISE at construction].* Zero/sub-floor demand, unclearable horizons, the DTALite clearance-boost overlap, shared-edge-queue topologies where every state self-certifies. **Defense:** RAISE at scenario construction (the ADR-020/ 031 eager-config discipline): sub-floor demand, unclearable horizon, boost-window overlap (the DTALite **x10 discharge boost fires whenever `total_intervals − t <= 720` intervals** — a units bug, `2·60·6` where 2 h *should* be 1200 six-second intervals, so the boost is ON for the last **72 min of ANY horizon**; a `≥2 h period` does NOT exclude it, and the constructor must instead refuse unless the completion census shows **every agent exits before `total_intervals − 720`** (horizon ≥ clearing time + 72 min) on **every constructor-side run — reference, negative control, and any anchor-derivation iterates** — **re-asserted at certify time on the replay**, where a replay whose census crosses `total_intervals − 720` is a **CENSOR** with diagnostics (an invalid emission, R6), never a construction RAISE), and a non-separating AON negative control are the constructor refusals — config errors, never certified rows. ## Per-engine unblock record The **first shipping row** carries the shared deliverables: the **EDOC certifier substrate** (G0–G4 + the RG_D1 scorer), the **canonicalization module** (`tabench-edoc-canon-v1;`, R10), and the **general multi-OD TD-SP harness module** (R3, with the non-FIFO and poisoned-alternative regression instances). SUMO `duaIterate` is the natural first row — it has the lowest certifier wall and a pinned `duarouter` that supplies the R3 cross- check for the harness TD-SP — with MATSim as the stochastic-track flagship. ### SUMO `duaIterate` — UNBLOCKED (resolves the ADR-027 deferral) Measured on the shipped `eclipse-sumo 1.27.1` wheel (`SUMO_HOME` via `sumo.SUMO_HOME` only, never the stale `/opt/sumo-1.12`). Per-iteration artifacts are complete (`tripinfo` + `vehroute` + `dump_Δ` + `.rou`/`.rou.alt` + pinned configs); bit-deterministic under pinned seeds modulo timestamp text — **all simulation-state payloads** (tripinfo, vehroute, dumps, `.rou`/`.rou.alt`, configs) byte-identical after the two strips (the `generated on` comment + the `summary` duration attr); the **23/184** `*.sumo.log` / `driver.out` / `dua.log` / `stdout.log` files carry wall-clock text and are **excluded from the hash surface** (R10). Full 20-iteration run **6.73 s / 6.44 s (0.30–0.34 s per iteration)**. D1 is one pinned `duarouter` call on the frozen final dump + optional one pinned `sumo` replay: measured **0.092 s + 0.131 s**. **Converged `RG_D1` = (157005.87 − 154609.65)/157005.87 = 0.01526** (126/500 strict improvers, BR split 248/252) vs the step-0 AON control **`RG_D1` = (174381.01 − 144382.31)/174381.01 = 0.17203** (285/500 improvers) — the **11x** attributable negative control ADR-030 said the dynamic track lacked. (These anchors use the pilot estimator — pinned `duarouter` on the Δ=300 dump, origin wait excluded on both sides — and are re-derived under the R2/R3 composition on the first row.) The observational gap from `X` alone (Tier-B) is **RG_global 0.03887, RG_binned 0.04699 with only 6/10 depart bins observing both routes** — coverage-limited, which is why it is not the score. The cost basis MUST be `duration + departDelay` (the engine's own convergence metric excludes `departDelay` and "converges" while hiding door-to-door cost: measured hidden backlog mean **186 s / max 414 s**). The Δ=300 aggregation noise floor (**mean 25.6 s / max 80 s ≈ 7.9%**) must shrink or be disclosed as the gap resolution. The realized-BR run overshoots (**meanCost 353.87 s vs 323.88 s, +9.3%, realized RG 0.159**) — Tier-B, never the score (R12). The scenario family must place capacity drops **inside** route-distinguishing edges (meso stores bottleneck queues on the upstream edge; the shared-edge topology is refused by the negative- control separation gate). **Row deliverables:** the `sumo-duaiterate` model + the shared substrate/canon/TD-SP; the `duarouter` R3 cross-check; the departDelay backlog gate. ### MATSim — UNBLOCKED (supersedes the ADR-030 formulation blocker) Two pilot amendments are baked into the certificate. **(1)** Replay = `lastIteration = firstIteration` on `output_plans.xml.gz` — MATSim replans only *between* iterations, so this is the zero-replanning experienced-cost recompute (measured: reproduces all 100 per-agent (dep, arr, route) tuples exactly; **sorted-stream md5 `a8d8e6f60497e468b8908f2d9a73d61a` identical on both sides**). **(2)** `RG_D1` is evaluated harness-side against the FIXED replay field (**measured 0.0916 at the it.19 state**), never the executed one-step (naive gap **−0.252**: `ReRoute 1.0` moved 100/100 agents on its coarse 15-min-bin field and overshot). The comparator sensitivity on that ONE state — **route-average RG 0.0045 vs harness fixed- field BR gap 0.0916 vs naive engine one-step −0.252** (C0 248.0 s → C1 310.5 s) — is exactly why R1/R2 pin one definition. (The 0.0916 is the pilot estimator — experienced driven cost vs a nearest-neighbor field, capped `min(c_br, c_exp)` precisely because `c_drv >= c_br` does not hold there — re-derived under the R2/R3 composition on the first row.) First stochastic-track external engine: `global.randomSeed` gives raw-gzip-byte determinism even with replanning (events, links, AND plans), so **P8 macroreps over a pinned seed list + bootstrap CIs** are executable (R5); single-iteration readouts are noise (route-average RG **0.0012–0.0979 across it.14..19**). Canonicalize event ties (same-timestamp order permutes between replay and original) before any hash; derive flows from entered-link events (`output_links.csv` undercounts the arrival link). **Version pin: `matsim-2025.0-release.zip` md5 `c65f35eafabea2456b818875f08048ca` on Temurin 21.0.11+10.** Walls: certified run **10.62 s / 772 MB**, replay **3.42 s / 312 MB**, BR run **3.71 s**, fresh toolchain **17.8 s**, **~10.6 s per seed** (2× replay + BR); the ≥5-seed macrorep marginal is **~71–124 s** (R7), and the JVM RSS baseline is the CI sizing constraint. **Row deliverables:** the `matsim` stochastic-track row (the first agent- based, first stochastic-track external engine); the P8 macrorep + bootstrap-CI harness for this certificate; the event-tie canonicalization (R10). ### DTALite `simulation()` — UNBLOCKED (resolves the ADR-029 deferral) `simulation()` 0.8.1 is a deterministic (LCG keyed by time step — `seedable=False`, deterministic track only, no macroreps, disclosed), version-pinnable, **sub-second (0.04–0.05 s)** plan-to-experienced-time map: `vehicle.csv` in, `trajectory.csv` out, bit-identical (**md5 `655b1dde23c6d1e3fb94779b4ffec81e` re-verified across three dirs**, also at OMP=4 on the 4-link net — but `OMP_NUM_THREADS=1` stays a **correctness** requirement, not just determinism hygiene: raw `#pragma omp parallel for` over shared `std::deque`). G1 replay is exact; **`RG_D1` from artifacts alone = (22654.000 − 13731.967)/22654.000 = 0.393839** (706/ 1000 would-switch agents, 1000/1000 completed); the harness MSA `1/(k+2)` loop drives it to **~0.0397 in 12 pinned re-runs** (plateau 0.03–0.05 = the 6 s quantization floor), so the dial is meaningful. (This gap uses the pilot's observed-two-route nearest-neighbor estimator — not the R2/R3 interval-mean field + TD-SP — and is re-derived under the shipped composition on the first row.) The engine has **no router** (`simulation()` never routes; `assignment()` never simulates), so the BR is certifier-owned TD-SP + one pinned re-simulation — which *strengthens* P1. Mandatory adapter gates, all from measured `rc=0` silent failures: departure-window `[start, end)` + per-agent completion census; positional trajectory parse (13-col header / 12-field rows; `loaded_status` dead; `07:00:00` filler on unvisited links); **plans constructed from `route_assignment.csv` since `assignment()`'s `vehicle.csv` emission is dead code (`TAPLite.cpp:2238` `route_volume = 0`, R9)**; an **agent-level boost-clean horizon** (every agent must exit before `total_intervals − 720`; the x10 discharge-boost `<=720`-interval units bug means a bare `≥2 h period` does not suffice — see below); links sorted by `(from, to)`. The **~600 veh/h effective-inflow law** (`entrance_queue.size() < capacity/3600` admission; measured **36.5 min mean vs 14 free-flow** on a nominal-1200 link) is **documented as the engine's cost law inside the instance definition** — no A2 cost-match exists, consistent with ADR-030. The unsmoothed one-step BR overshoots as theory predicts (**TSTT 22654.000 → 33365.933, RG 0.393839 → 0.512861**) — Tier-B, not the score (R12). **Boost-clean re-measurement (this review-revision sprint, verified on disk).** The pilot's anchors were measured on a 7→9 h (2 h) horizon whose last 72 min (from 7:48) run under the x10 discharge boost — 463/1000 agents are in flight then, 37.3% of veh-min fall in the window. Re-running the identical 781/219 step-0 split on a **7→13 h horizon** (boost window 11:48 onward; every agent — base, one-step BR, and all 12 MSA iterates — exiting by ≤ 8:47 h, so **provably boost-clean**) reproduces **every anchor byte-for-byte**: `trajectory.csv` md5 `655b1dde…` identical to the 2 h run, `RG_D1` 0.393839, TSTT 22654.000, 706 would-switch, the one-step overshoot 22654.000 → 33365.933 (`RG_D1` 0.512861), and MSA final 0.039738. The boost fired but changed nothing here because it inflates only the **discharge** `base_capacity` (`calculateOutflowCapacity`, `TAPLite.cpp:5127`), while the binding constraint on this net is the **un-boosted entrance admission** `entrance_queue.size() < capacity_per_time_step = Link_Capacity/3600` (`TAPLite.cpp:5301`/`4979`) — the ~600 veh/h inflow ceiling. So the anchors stand; the gate is still required, because on a net where **discharge** binds the boost WOULD move experienced times — hence the agent-level completion census above (which CENSORs an emission whose certify-time replay crosses `total_intervals − 720` as invalid per R6, and at construction must pass on every constructor-side run — as this re-measurement censused across the base, one-step BR, and all 12 MSA iterates), not a bare period length. **Row deliverables:** the `dtalite-simulation` deterministic-track row; the `vehicle.csv`-from-`route_assignment.csv` construction with its integerization bound (R9); the positional-parse + departure-window + completion-census adapter gates. ## Consequences * **This ADR is the design authority for the EDOC certifier substrate, the canonicalization module, and the TD-SP harness — which ship with the first row, not in this sprint.** No code, no dependency, no CI job, no hash change lands here; it is a documentation-only record, like ADR-030. * **Supersedes ADR-030's MATSim formulation blocker** (A2 impossible in kind) **and resolves the ADR-027 `duaIterate` and ADR-029 `simulation()` named-follow-up deferrals** — the three rows ADR-030 said one ADR would unblock at once. A2's shipping bar is not weakened: its role is played by the pinned engine under **G1 replay fidelity**, and the certified claim is relabeled (frozen-field BR gap, not Wardrop RG). * **ADR-030 forecast the C0–C8 DNL ladder as the natural scoring substrate;** EDOC-1 narrows that: it realizes C0–C8 as the **gating D3 slice** (G4 runs the `dnl_gaps` C0/C1 conservation shapes on the replay) and moves the *score* to `RG_D1`, because conservation alone cannot separate a non-equilibrium AON state from equilibrium — the measured 11x negative control is a frozen-field BR gap, not a conservation residual. The narrowing is disclosed here so a reader diffing the two ADRs finds it named. * **ADR-030's DynaMIT artifact-block and DYNASMART license-block are UNCHANGED.** DynaMIT (`benakiva2001network`) has zero public artifact; DYNASMART (Jayakrishnan et al., `jayakrishnan1994evaluation`) is FHWA/McTrans-licensed with no CI-installable binary. Those need upstream changes no repo ADR can supply; the Peeta & Mahmassani (1995) white-box row (`peeta1995system`, shipped as ADR-031) is unrelated and already live. * **Named follow-up rows and deliverables** (each its own adversarial-review sprint): * **First shipping row — `sumo-duaiterate`** (SUMO `duaIterate`) + the **shared substrate** (G0–G4 + RG_D1 scorer), the **canonicalization module** (`tabench-edoc-canon-v1;`), and the **multi-OD TD-SP harness** with the `duarouter` R3 cross-check and the non-FIFO + poisoned-alternative regressions. * **Second row — `matsim`**, the first agent-based / first stochastic-track external engine: the P8 macrorep + bootstrap-CI harness for this certificate, on the pinned `matsim-2025.0` / Temurin 21 build. * **Third row — `dtalite-simulation`** (DTALite `simulation()`, deterministic track): the `vehicle.csv`-construction rule (R9) + the positional-parse / departure-window / completion- census adapter gates + the documented ~600 veh/h inflow law. * **Then — the BO4Mob stage-2 D2 held-out-count observational certificate** (`ryu2025bo4mob`): its **own** ADR (R11), reusing this ADR's replay + canonicalization substrate — the ADR-034 co-design (ADR-034: *"the honest-oracle surface arrives with the stage-2 certificate ADR"*; *"Do not start it until the certificate design is written."*), now satisfied for the substrate interface. * **ROADMAP/MODELS (to reality):** the ROADMAP Horni et al. (2016) `horni2016multiagent` row replaces its adr-030 deferral note with a pointer here; the **shipped** `lopez2018microscopic` (marouter) and `zhou2014dtalite` (tap) rows **extend** their annotations with the `duaIterate` / `simulation()` unblock pointer + the R8 non-comparability sentence ("frozen-field BR gap, not Wardrop RG; separate leaderboard table") — they carry **no** deferral note to remove; and MODELS.md gains cards only when the rows ship, via `tools/generate_models.py` — there is nothing to annotate there now (no MATSim/duaIterate/simulation() card exists). Hand-annotated like every shipped flip — `tools/generate_references.py` is **never** run. ## Sources (the honest ledger) Reproduced from the theory synthesis; the read-in-full vs attributed-unread distinctions are preserved and not upgraded, and the ADR author's own repo reads are listed separately. * **READ (open, fetched + text-extracted this session):** Chiu, Bottom, Mahut, Paz, Balakrishna, Waller & Hicks (2011), *Dynamic Traffic Assignment: A Primer*, TRB Transportation Research Circular E-C153 (`chiu2011dynamic`) — the experienced-time relative gap over used routes normalized by total shortest-path times; the mandate that the gap "must be calculated for (and reported by) each departure time interval"; the MSA critique that flow-change stability is "an algorithmic construct unrelated to the equilibrium requirement of minimizing experienced travel time"; vehicles-waiting-to-enter as a warning measure (grounds the backlog gate). * **READ (open, fetched this session):** SUMO documentation, "Dynamic User Assignment" (`sumo.dlr.de/docs/Demand/Dynamic_User_Assignment.html`) — `duaIterate` convergence is the relative standard deviation of average travel times (`--max-convergence-deviation` / `--convergence-iterations` / `--convergence-steps`); the docs themselves caution convergence "is hard to predict and results may continue to vary even after 1000 iterations"; no experienced-time gap is defined by the tool. * **ATTRIBUTED UNREAD (paywalled; abstract + bibliographic records read):** Lu, Mahmassani & Zhou (2009), "Equivalent gap function-based reformulation and solution algorithm for the dynamic user equilibrium problem," *Transportation Research Part B* 43(3):345–364, doi:10.1016/j.trb.2008.07.005 (`lu2009equivalent`) — the gap-function DUE lineage D1 descends from; already the named canon successor in ADR-031. * **HOUSE PRIMARY, read in full for ADR-031 (not re-read here):** Peeta & Mahmassani (1995), *Annals of Operations Research* 60:81–113, doi:10.1007/BF02031941 (`peeta1995system`) — experienced-time TD-UE conditions; equilibrium on instantaneous times "conceptually meaningless" — the conceptual basis of scoring on experienced costs. * **ATTRIBUTED UNREAD (canon tool/method papers):** Horni, Nagel & Axhausen (2016) *The Multi- Agent Transport Simulation MATSim* (`horni2016multiagent`); Lopez et al. (2018) IEEE ITSC (`lopez2018microscopic`); Zhou & Taylor (2014) Cogent Engineering (`zhou2014dtalite`); Gawron (1998) IJMPC (`gawron1998iterative`, `duaIterate`'s default route-choice dynamics); Sbayti, Lu & Mahmassani (2007) TRR (`sbayti2007efficient`). * **Repo precedents read in full (theory synthesis + this ADR author):** `docs/design/adr-020, adr-027, adr-029, adr-030, adr-031, adr-034`; `src/tabench/metrics/gaps.py`; `src/tabench/metrics/tdta_gaps.py`; the `dnl_gaps.py` C0–C8 gate headers; `docs/ARCHITECTURE.md` (the observational-track paragraph this certificate fills). The ADR author additionally read `docs/ROADMAP.md` (the deferred external-engine rows) and the pilot-C vendored `TAPLite.cpp` (the boost / entrance-admission / dead-code line numbers, re-checked this revision sprint). * **Pilot numbers:** from Pilots A (SUMO `duaIterate`), B (MATSim), C (DTALite `simulation()`), whose artifact trees are on disk under `scratchpad/extdyn/{fable-duaiterate,pilotB,pilotC}`. **Re-verified on disk by the ADR author this sprint:** Pilot C's gap recompute (`analyze.py` re-run: **RG 0.393839, 706/1000 would-switch, 1000/1000 completed**) and determinism (`md5sum`: **655b1dde…** identical across `sim1`/`det1`/`det2`); Pilot A's step-19 gaps from `gap_runC1.json` (**rg_global 0.03887385796059968, rg_binned 0.04699429081763454, split 344/156, 6/10 bins**). **In the review-revision sprint** the DTALite anchors were additionally re-measured **boost-clean** by re-invoking the pinned 0.8.1 engine on a fresh 7→13 h horizon (a NEW scratch dir, never mutating the pilot tree; same `stdin=DEVNULL` / `OMP_NUM_THREADS=1` / timeout discipline): base `RG_D1` 0.393839 / TSTT 22654.000 / 706 would-switch, one-step overshoot 33365.933 (`RG_D1` 0.512861), MSA final 0.039738 — all byte-identical (`trajectory.csv` md5 `655b1dde…`) to the pilot's 2 h run and provably boost-clean (every agent exits ≤ 8:47 h vs boost onset 11:48). Apart from that disclosed re-measurement, no engine binaries were re-invoked to author this document.