Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Thalweg Plan-vs-Design Reconciliation

Inputs: docs/blueprints/thalweg-delivery-plan.md (v0.5, last-modified 2026-07-24) · docs/adr/README.md (ADRs 0001–0057) · drafted ADR files 0001 (Draft, v0.5) and 0003 (Accepted, v1.0) · crates/ tree (47 crates, all twg- prefixed).

Headline: The plan is one design generation behind. It was written when the whole system was framed as a single ADR (0001) covering ~18 crates; the design has since fragmented into 57 discrete decisions and 47 crates. The plan’s phase spine, layering rules, three-representation model, and fidelity approach remain correct and endorsed. What has drifted is (a) crate naming (unprefixed → twg-), (b) the crate matrix (sinks/sources/formats/contracts/DQ/observability now each have their own crate rather than being folded into a handful), and (c) three named subsystems the plan describes with stale specifics (recovery topology, metadata prefix, observability home). Roughly half the shipped crates have no phase, week budget, or exit gate in the plan at all.

Both ADR files carry an identical import note: “Treat the phase structure and crate layout as current; treat specific version pins and the single-ADR framing as pending update.” That is the reconciliation instruction in miniature — trust the plan’s shape, distrust its specifics.


A. CONFIRMED — plan decisions the ADR set still endorses unchanged

#Plan decisionEndorsing ADR(s)Notes
A1Pure-Rust, sans-io ecosystem; three co-equal representations (bytes / neutral value / Arrow)ADR-0001 (Draft, drafted)Verbatim match; ADR-0001 §Recommended is Option 3.
A2Neutral value model owned by codec-core, not upstream re-exportsADR-0002; ADR-0001 §RecommendedEndorsed.
A3Arrow default-on; codec-* builds clean --no-default-features with no arrow in treeADR-0001 §Recommended; ADR-0003Confirmed.
A4Single Arrow-version chokepoint via twg-codec-core re-exportADR-0003 (Accepted)Now settled — see F for the part the plan pre-empts.
A5Layering enforcement: codec-* may not depend on wire-*/kafka-* or on a runtime; enforced in CIADR-0001 §Context/§RecommendedConfirmed; the three CI rules stand.
A6Offset correlation via sidecar OffsetSpan; unnest cardinality breaks range-correlation and needs per-row locator unionADR-0004Confirmed as a first-class contract.
A7Record metadata as reserved columns (not out-of-band, which dies at the Flight boundary)ADR-0005Column approach confirmed; prefix value changed — see B2.
A8Connector flow control: explicit state machine, Budget backpressure, pause/resume, rate limitingADR-0006Confirmed; extended with admission control (C).
A9Transform DAG with interior sinks and automatic materialisation of multi-dependent nodesADR-0007Confirmed.
A10Two-mechanism extension model: in-process WASM (no network) vs. Flight sidecarADR-0008Confirmed; C Data Interface inbound added (C).
A11Fresh Pulsar client, not a pulsar-rs forkADR-0010Confirmed; flow-control specifics deepened by ADR-0049.
A12Packaging: static musl binary primary, multi-arch (arm64+amd64) OCI derivedADR-0011Confirmed.
A13Table catalogs a separate crate from schema registriesADR-0001 §Recommended; twg-table-catalogConfirmed.
A14Passthrough byte-identity asserted post-decompression; recompression not byte-stableADR-0001 §Context; ADR-0021 (testing standard)Confirmed.
A15Two documented purity exceptions (zstd-encode, GSSAPI) gated off defaultADR-0001 §RecommendedConfirmed.
A16Source-ack invariant: advance offset only on durable sink ackADR-0038Confirmed and hardened: batch-granular default, per-record opt-in, durable mandatory for primary-raw.

B. DEPARTED / SUPERSEDED — plan statements the design has since changed

B1. Recovery / coordination topology (the plan’s single biggest drift)

  • Plan says (Phase 5, “Single-agent recovery only”): “One recovery worker needs no leases, no elections and no coordination substrate — it is the only claimant. Distributing recovery across agents is what needs coordination, and that is deferred … (ADR-0055, ADR-0056).” Recovery is framed as an internal capability of the single ingest process, unlocking the second sink.
  • Current position — recovery is now a first-class deployment architecture, not an in-process worker:
    • ADR-0050: recovery is a deployment role (ingest / recovery / both); recovery windows are leased via the offset store; roles autoscale independently, to zero.
    • ADR-0057: reaffirms the plan’s gate (“recovery is a precondition for the second sink, not later hardening”) but narrows the deferralonly distributed recovery is deferred, not recovery itself.
    • ADR-0055: coordination substrate deferred, but with a chosen leading shape — an embedded replicated store, platform-independent, consensus library not written in-house.
    • ADR-0056: quorum is an additional worker role with a reduced throughput budget; modes 0 / 1 / 3+, two-node forbidden, stable assignment + graceful handoff.
    • ADR-0053: the coverage-store mechanics the plan attributes to twg-offset-store are now their own decision — position state may lag, never lead; gap-aware coverage stored with RAW, never in the sink it describes; local write-behind cache; source position only where no broker holds it.
  • Impact: The plan’s ADR citations (0055/0056) are stale numbers pointing at the right idea. The rewrite must (a) split “recovery” into role-based recovery (0050, shippable in Phase 5) vs. distributed/quorum recovery (0055/0056/0057, genuinely deferred), and (b) attribute the coverage-store design to ADR-0053, not fold it into an unspecified twg-offset-store paragraph.

B2. Metadata column prefix — __kafka_ → configurable, default _twg_

  • Plan says (Record-metadata section, repeatedly): reserved columns under a hard-coded __kafka_ prefix — __kafka_key, __kafka_timestamp, __kafka_offset, etc.; a payload field named __kafka_* is a hard decode error. The quality gate is named “Reserved-namespace collision” against __kafka_*.
  • Current positionADR-0051: the metadata column prefix is configurable, default _twg_; changing it on an existing target is a refused migration. The __kafka_ literal is gone (and was transport-specific, which conflicts with the transport-neutral connector goal — Pulsar metadata under a __kafka_ prefix is incoherent).
  • Impact: Every __kafka_* string in the plan (metadata section, the collision gate, the examples) must become _twg_* (default) with a note that it is configurable and immutable-once-set. This is a pure find-and-replace-plus-semantics change, but it touches a named quality gate.

B3. Observability home — folded into connector-core → unified twg-observability crate

  • Plan says (connector layer, Phase 5): “The instrumentation lives in connector-core, so both transports emit identical metric names.” OTel spans/metrics are a connector-core responsibility; the crate table lists connector-core deps as tower, opentelemetry, governor.
  • Current position:
    • ADR-0032: a unified observability crate — metrics + OTLP + health share one surface → twg-observability.
    • ADR-0013: OTLP default, Prometheus scrape (disableable).
    • ADR-0031: health tree with three projections (/health, /ready, /live); readiness gated by source + primary-raw only.
    • ADR-0039: live per-stage memory/throughput digest — OTEL gauges + in-process /stats (30s window), honest per-stage attribution.
    • Confirmed by crate: twg-observability desc = “Prometheus metrics, OTEL/OTLP export, and the health tree with /health, /ready, /live projections.” twg-connector-core desc now reads “…flow control, observability, recovery” — it consumes observability, it no longer owns it.
  • Impact: The plan must relocate OTel from a connector-core sub-bullet to a standalone crate + subsystem, and add the health tree and /stats digest, which have no presence in the plan at all (health/readiness is never mentioned). Cross-transport metric-parity gate stays, but now targets twg-observability.

B4. Sinks/sources — folded into stream-arrow → a modular source/sink crate matrix

  • Plan says (crate table): stream-arrow = BatchSink implementations, batching policy, IPC, Flight, offset correlation” — i.e. all sink concerns (including Flight and IPC) live in one crate; sources are implicit in kafka-client/pulsar-client.
  • Current position — sinks and sources are each their own crate, and their existence is a decision (ADR-0012: all sources and sinks are optional and modular; replay-capable ones may hold primary-raw):
    • Sinks: twg-sink-object-store (ADR-0044), twg-sink-flight (ADR-0020), twg-sink-arrow-ipc (ADR-0026), twg-sink-postgres, twg-sink-zerobus.
    • Sources: twg-source-flight (ADR-0020), twg-source-arrow-ipc (ADR-0026), twg-source-batch (ADR-0015/0027/0033), twg-source-delta-sharing (ADR-0022).
    • twg-stream-arrow survives but is reduced to “batch sinks, IPC, Flight, offset correlation” as a shared substrate the modular sinks build on — not the home of every sink implementation.
  • Impact: The plan’s Phase 5 “stream-arrow: … all four implementations (RawSink, ValueSink, ArrowSink, TeeSink)” is still valid as the core batching/correlation layer, but the plan has no concept of the 9 modular source/sink crates as separate deliverables. These need phase placement (see C and E).

B5. Single twg binary, not a library-only ecosystem

  • Plan implies a library ecosystem plus a “deployable” that appears at the transform layer; there is no CLI/binary crate in the plan’s crate table, and packaging (Phase 9) produces “a binary” without naming its structure.
  • Current positionADR-0045: a single twg binary with subcommands (stream / batch / config), not separate executables. Crate: twg-cli.
  • Impact: The binary is now a named crate with a defined command surface (including a whole batch mode, see C) and needs to appear in the workspace layout and packaging phase.

B6. Batch is now co-equal with streaming (not present in the plan)

  • Plan says nothing — it is streaming-only end to end.
  • Current positionADR-0015: a batch CLI as a bounded source sharing the streaming pipeline; ADR-0027 Avro OCF as a batch source (shared Avro decode); ADR-0033 batch reader libraries (arrow-native + calamine + quick-xml; Polars rejected). Crate twg-source-batch reads Parquet/CSV/JSON/XML/Excel/Avro-OCF.
  • Impact: This is a departure by omission — the plan asserts a streaming-only scope that the design has widened. See C for scope placement.

C. NEW / ABSENT-FROM-PLAN — subsystems with no phase, week budget, or exit criteria

Each entry: subsystem → ADR(s) → implementing crate(s) → recommended phase.

#SubsystemADR(s)Crate(s)Recommended placement
C1Data contracts / ODCS — resolved validation spec, ODCS v3.x precedence, most-restrictive merge0016, 0018, 0035, 0036twg-contract-core, twg-contract-odcsNew sub-phase, sequenced with codecs/DQ. Contract-driven schema (0035) gates codec inference; core belongs alongside Phase 2 codec work, ODCS loader after resource-loader.
C2Data quality — enforcement modes (strict/quarantine/annotate), clean/reject split, drop rules; full-grain audit trail on by default0014, 0034, 0036, 0041twg-dq-enforcer, twg-dq-auditNew phase or large Phase 9 attachment. DQ runs inside the transform DAG; audit is a first-class sink. Depends on contracts (C1) + DAG (Phase 9).
C3Table formats — Parquet, Iceberg (read+write); Delta via Iceberg metadata; Delta Sharing (read); catalog credential vending0022, 0023, 0024, 0044twg-format-parquet, twg-format-iceberg, twg-source-delta-sharingNew phase (largest absent block). Iceberg-only per ADR-0023. Feeds twg-sink-object-store. Roughly parallels/extends the plan’s Phase 7 “AWS block”.
C4Batch mode — bounded source over the streaming pipeline; batch readers0015, 0027, 0033twg-source-batch, twg-cli (batch subcommand)New phase, post Phase 5 (needs the sink contract + source trait). See B6.
C5Modular sink crates0012, 0044, 0020, 0026twg-sink-object-store, twg-sink-flight, twg-sink-arrow-ipc, twg-sink-postgres, twg-sink-zerobusAttach to Phase 5 (object-store + arrow-ipc + flight are core sink lane) and Phase 7/breadth (postgres, zerobus). object-store Parquet raw lane is primary-raw-eligible (0044) → Phase 5 critical.
C6Modular source crates0012, 0020, 0026, 0022twg-source-arrow-ipc, twg-source-flight, twg-source-delta-sharing, twg-source-batchPhase 5 (Flight/IPC) + new format/batch phases.
C7Unified observability + health0013, 0031, 0032, 0039twg-observabilityPull forward into Phase 5 (or a slice into Phase 0/3 for basic metrics). Health tree + readiness gating are wholly absent from the plan. See B3.
C8FFI / Arrow C Data Interface0042, 0043twg-ffiPhase 9 (WASM uses the C Data Interface across its boundary). Sole audited unsafe crate — conflicts with plan’s blanket unsafe_code="forbid" claim (see E, Phase 0). Also the escape-path bridge for ADR-0003.
C9Type-mapping authority0019twg-type-mapPhase 1 — sans-io describe-only leaf that codecs and sinks execute against; foundational, belongs with codec-core/value model.
C10Shared resource loader0029twg-resource-loaderPhase 1/2 — needed by config, ODCS contracts, and transform SQL. Foundational; pull early.
C11Config + secrets — layered TOML<env<CLI, K8s file-mount + pointer, secret enumeration; holds admission-control cold-start seed0028, 0030twg-configPhase 0/1 — config is needed the moment anything is deployable. Absent from plan.
C12DLQ reason-codes — unified DLQ (decode / contract / DQ / retry-exhausted)0017twg-connector-core (+ dq crates)Phase 5 (plan has “DLQ policy” but no reason-code taxonomy). Quarantine non-blocking + overflow drop-with-count is ADR-0041.
C13Content-hash dedup0054twg-connector-core / recovery pathPhase 5 — opt-in, contract-gated, window sized to upstream retry interval. Plan mentions “deduplicated by record id” in recovery but not as a decision.
C14Admission control — self-correcting, live measured p95 record size; static config as cold-start seed + floor0038, 0040twg-connector-core, seed in twg-configPhase 5 — extends Budget flow control. Absent from plan’s flow-control description.
C15Health tree0031twg-observabilityPhase 5 (with C7). Readiness gated by source + primary-raw only — wholly new.
C16Releases / publishing — independent per-crate publishing, dual MIT OR Apache-2.0; release-plz reviewed release PR, semver checks, codec version group0047, 0048workspace-wide (all crates)Phase 0 (publishing discipline) + ongoing. ADR-0047 makes arrow a public dep — the coupling ADR-0003 turns on. Absent from plan.
C17Custom binary decode0025twg-codec-customPhase 7/breadth — batch-oriented WASM default, native compile-in as measured exception. Absent from plan’s codec list.
C18Catalog descriptive metadata — comments/properties/tags on create+evolution, idempotent re-sync0037twg-table-catalogTable-format phase (C3). Extends the plan’s Glue “registration” direction.
C19E2E harness0021twg-e2ePhase 0 onward — the plan describes the test tiers but not a dedicated harness crate.
C20Docs site — Cloudflare Pages at docs.thalweg.dev, gates protect merge, main protected0052(docs infra)Phase 0 / infra — out of crate scope but a committed decision the plan omits.

Codecs not in the plan’s Phase 7 list: twg-codec-custom (C17). Plan’s Phase 7 lists protobuf/json/xml only.


D. CRATE-LEVEL MAP — plan (~18, unprefixed) vs. actual (43, twg-)

Naming drift: The plan uses unprefixed names throughout (wire-tls, codec-core, stream-arrow). Every shipped crate is twg- prefixed. The plan already used the prefix in exactly one place — twg-offset-store in Phase 5 prose — showing the convention post-dates the plan and was applied inconsistently. The rewrite must prefix all 43.

Actual crateStatusPlan name / originADR / note
twg-wire-tlsin-plan (renamed)wire-tls
twg-wire-saslin-plan (renamed)wire-sasl
twg-wire-compressionin-plan (renamed)wire-compression
twg-kafka-clientin-plan (renamed)kafka-client
twg-pulsar-clientin-plan (renamed)pulsar-clientADR-0010/0049
twg-connector-corein-plan (renamed, scope reduced)connector-coreObservability moved out (B3)
twg-pipeline-sqlin-plan (renamed)pipeline-sqlADR-0007
twg-pipeline-wasmin-plan (renamed)pipeline-wasmADR-0008
twg-pipeline-flightin-plan (renamed)pipeline-flightADR-0008
twg-schema-registryin-plan (renamed)schema-registry
twg-table-catalogin-plan (renamed, extended)table-catalog+metadata/vending 0024/0037
twg-codec-corein-plan (renamed)codec-coreADR-0003 chokepoint
twg-codec-avroin-plan (renamed)codec-avro
twg-codec-protobufin-plan (renamed)codec-protobufADR-0046 open
twg-codec-jsonin-plan (renamed)codec-json
twg-codec-xmlin-plan (renamed)codec-xml
twg-stream-arrowin-plan (renamed, scope reduced)stream-arrowSinks split out (B4)
twg-kafka-arrowin-plan (renamed)kafka-arrow
twg-pulsar-arrowin-plan (renamed)pulsar-arrow (mermaid only)
twg-offset-storein-plan (already prefixed)twg-offset-store (Phase 5 prose)ADR-0053
twg-cliNEWADR-0045/0015
twg-codec-customNEWADR-0025
twg-configNEWADR-0028/0030/0040
twg-contract-coreNEWADR-0016/0036
twg-contract-odcsNEWADR-0016/0018
twg-dq-auditNEWADR-0034
twg-dq-enforcerNEWADR-0014/0036/0041
twg-e2eNEWADR-0021
twg-ffiNEWADR-0042/0043
twg-format-icebergNEWADR-0022/0023
twg-format-parquetNEWADR-0022/0044
twg-observabilityNEWADR-0013/0031/0032/0039
twg-proto-flattenNEWADR-0046
twg-proto-schemaNEWADR-0046
twg-proto-decodeNEWADR-0046
twg-resource-loaderNEWADR-0029
twg-sink-arrow-ipcNEWADR-0026
twg-sink-flightNEWADR-0020
twg-sink-object-storeNEWADR-0044
twg-sink-postgresNEWADR-0012
twg-sink-zerobusNEWADR-0012
twg-source-arrow-ipcNEWADR-0026
twg-source-batchNEWADR-0015/0027/0033
twg-source-delta-sharingNEWADR-0022
twg-source-flightNEWADR-0020
twg-type-mapNEWADR-0019

Totals: 20 in-plan (19 renamed + 1 already-prefixed) · 26 NEW · 46 actual (of 47 workspace crates; this reconciliation table predates and omits twg-udf). The plan accounts for less than half the crate tree.


E. PER-PHASE IMPACT

Phase 0 — Foundations (2w)

  • Wrong/incomplete: claims workspace-wide unsafe_code = "forbid" — contradicted by ADR-0043: twg-ffi is the sole audited unsafe exception. The rewrite must state “forbid with one crate-level allow, audited.”
  • Absent scope to attach: publishing discipline (ADR-0047/0048, release-plz, dual-licence, semver checks), twg-config skeleton, twg-e2e harness scaffolding, docs-site infra (ADR-0052 — main branch protection is a gate precondition).
  • Static-musl/aws-lc-rs proof stays correct.

Phase 1 — Transport primitives + value model (5w)

  • Correct in spine.
  • Absent scope: twg-type-map (ADR-0019) belongs here as a sans-io leaf beside the value model; twg-resource-loader (ADR-0029) foundational, pull in here or Phase 2; twg-config (ADR-0028/0030).

Phase 2 — Avro codec, all three surfaces (6w)

  • Correct. Note ADR-0027 (Avro OCF) shares this decode — flag the reuse forward to batch mode.
  • Absent scope: twg-contract-core/twg-contract-odcs first landing — contract-beats-inference (ADR-0035) interacts with codec schema resolution and should be designed alongside, not after.

Phase 3 — Kafka consumer, no groups (5w)

  • Correct. Passthrough-ships-early claim stands.
  • Absent scope: basic twg-observability metrics slice could start here (plan defers all OTel to Phase 5).

Phase 4 — Consumer groups (8w)

  • Correct and unchanged. No drift. Still the hard phase per ADR-0001.

Phase 5 — Sinks, connector layer, coverage & recovery (11w) — most drift

  • Wrong: “instrumentation lives in connector-core” → moved to twg-observability (B3). __kafka_ prefix → _twg_ (B2). “single-agent recovery only (ADR-0055/0056)” → role-based recovery per ADR-0050/0053/0057, distributed deferred (B1).
  • Absent scope to attach: twg-observability + health tree + /stats (C7/C15); modular sink lane twg-sink-object-store/-arrow-ipc/-flight + source twg-source-flight/-arrow-ipc (C5/C6); DLQ reason-codes (C12); admission control (C14); dedup (C13); source-ack invariant hardening (ADR-0038). twg-offset-store mechanics → attribute to ADR-0053.
  • Resequencing: Phase 5 is already 11 weeks and monolithic; this much added scope likely forces a split into 5a (single-sink + observability + connector-core) and 5b (coverage/recovery/multi-sink + object-store primary-raw lane).

Phase 6 — Producer, idempotence, transactions (6w)

  • Correct. No drift.

Phase 7 — Ecosystem breadth (“ongoing”)

  • Flagged as requested: Phase 7 is labelled “ongoing” with no exit gate — the only phase without one. This must be closed: either give it explicit exit criteria or split its contents into gated phases. It currently absorbs codecs (protobuf/json/xml), OAUTHBEARER crates, GSSAPI, and the whole AWS/Glue block, which is too much to leave ungated.
  • Absent scope: twg-codec-custom (C17); the modular DB sinks twg-sink-postgres/twg-sink-zerobus (C5); the table-format block (C3) is far larger than the plan’s “Glue Data Catalog” bullet and probably deserves its own phase, not a Phase 7 sub-item.

Phase 8 — Pulsar (10w)

  • Correct. Deepen with ADR-0049 (Pulsar flow control: staged subscription, topic-spread shedding, pod-seeded order, pressure-as-scale-signal) — the plan’s Pulsar gaps list predates this decision.

Phase 9 — Transform layer + packaging (12w)

  • Correct in spine.
  • Absent scope: twg-ffi (C8, the WASM C Data Interface boundary + ADR-0003 escape bridge); data-quality enforcement + audit (C2) run inside the DAG and have no home otherwise; twg-cli command surface + batch subcommand (B5/C4).

Cross-cutting resequencing gaps

  • No headcount / parallelism assumption stated. The plan asserts “three critical paths” and “Phase 9 runs parallel to Phase 6 given separate ownership” but never states how many people/streams the timeline assumes. With 23 new crates (contracts, DQ, formats, batch, observability, 9 sink/source crates), the parallelism assumption is now load-bearing and must be made explicit — otherwise the Gantt is unfalsifiable.
  • New subsystems have no Gantt bars at all: contracts, DQ, table formats, batch mode, observability/health, and the modular sink/source matrix appear nowhere in the timeline.

F. OPEN / PROVISIONAL — decisions the plan presents as settled but are genuinely open

#ItemStatusPlan’s (over-)commitment
F1Protobuf decode strategyADR-0046 — Open, gated on benchmark (README calls it out explicitly: direction chosen, Apache-2.0 licence cleared, “now gated only on a benchmark against the upstream parser on our hardware and schemas”)Plan’s Phase 7 lists “Protobuf codec, both surfaces (prost-reflect dynamic …)” as settled work — but the parser choice (vendor a zero-copy parser vs. prost-reflect) is undecided pending benchmark.
F2Coordination substrate shapeADR-0055 — deferred; leading shape only (embedded replicated store, consensus library not yet chosen)Plan cites 0055/0056 as if the deferral is clean; the shape is provisional, not merely deferred.
F3Arrow version escape-path measurementsADR-0003 (Accepted) but with deferred measurements — IPC-vs-C-Data-Interface crossing cost and the SQL-vs-projection traffic split are explicitly “no longer gating” but unmeasured; the escape path (Options 2/3/4) is designed but unexercisedPlan (v0.5) predates ADR-0003 entirely and treats “Arrow / DataFusion version strategy” as unstated. The rewrite should record 0003 as Accepted (pin Arrow 58) and carry its escape-path measurements as open follow-ups, not settled.
F4connector-core traits may prove Kafka-shapedPlan’s own risk register flags this High; ADR-0006 is still PlannedLegitimately provisional until the Phase 5 Kafka retrofit + paper review against Pulsar’s permit/cursor model. Keep as stated risk, not settled contract.
F5Pulsar in-place seek feasibilityPlan’s risk register keeps “destroy-and-recreate as documented fallback behind a flag”Genuinely provisional; correctly hedged in the plan — retain.

Summary for the rewrite

  1. Prefix everything twg- and expand the crate table from ~18 to 43 (§D).
  2. Fix the three named departures verbatim: recovery topology (0050/0053/0055/0056/0057, not “single-agent only 0055/0056”), metadata prefix (_twg_ configurable, not __kafka_), observability home (twg-observability, not connector-core) (§B1–B3).
  3. Add the absent half of the system: contracts/ODCS, data quality + audit, table formats (Iceberg-only), batch mode, the modular source/sink matrix, unified observability + health, FFI, type-map, resource-loader, config/secrets, admission control, dedup, DLQ reason-codes, releases/publishing, custom-decode (§C) — each needs a phase, budget, and exit gate.
  4. Close Phase 7’s open-ended “ongoing” with an exit gate, and split the overloaded Phase 5.
  5. State the parallelism/headcount assumption the timeline silently depends on.
  6. Record ADR-0003 as Accepted (pin Arrow 58) and keep F1–F5 as explicitly open, not settled.
  7. Reconcile the unsafe_code = "forbid" claim with the audited twg-ffi exception (ADR-0043).