Plan U: State Bundle Pattern (child plan of Plan S umbrella)
On this page
Status
| Step | Description | Status |
|---|---|---|
U0 |
|
Done (2026-06-10) — shipped in Plan T1.5 ( |
U1 |
Extract the neutral partner-type taxonomy into |
Done (2026-06-13) — |
U2 |
Land the theme + terminology contribution types + product-default consts in |
Done (2026-06-13) — |
U3 |
RENAME |
Done (2026-06-13) — |
U4 |
NEW |
Done (2026-06-13) — |
U5 |
Cargo feature gating |
Done (2026-06-13) — |
U6 |
CI feature-matrix as an |
Done (2026-06-13) — |
U7 |
|
Done (2026-06-13) — |
U8 |
Theme contribution per ADR-036 ( |
Done (2026-06-14) — |
U9 |
Terminology contribution per ADR-034 (Fluent overlay). |
Done (2026-06-14) — |
U10 |
State-aware seed data. Populate the empty |
Done (2026-06-14) — |
U11 |
NEW |
Done (2026-06-14) — NEW |
U12 |
Plan-completion audit + archive. Fresh Explore plan-completion-audit subagent verifies every U-step cell carries a concrete |
Done (2026-06-15) — this MR: plan-completion audit (CLEAN — every U0–U11 cell carries a concrete |
Epic: &46 (Plan U)
Scoped label: Plan::U (filed with this body MR; one Plan::* label per issue — scoped-label collisions 404)
Branch prefix: <type>/plan-u-step<N>- for child code-execution MRs
*Parent: Plan S umbrella Steps 12 (this body) + 13 (execution)
Gating ADRs (both Done 2026-06-11): ADR-034 (terminology → U9) + ADR-036 (theme → U8)
Context
Plan S Phase 3 makes CRAIG a state-neutral CCWIS platform. Plan T (the adapter-registry pivot) is complete and archived: T1.5 shipped the StateBundle trait + BundleContribution aggregate + the five immutable registries in crates/craig-state-bundle, and T1.6 shipped the concrete seed bundle in crates/craig-state-default. But the seam is not yet open at runtime — exactly one bundle is hardcoded at three boot sites (vec![Box::new(DefaultBundle)]), and the CRAIG__ACTIVE_STATE_BUNDLES env var that ADR-032 §2.7 specifies has zero wiring (seven source comments still say "Plan U Step 4 ships it").
Plan U opens that activation seam. The active jurisdiction bundle becomes selectable two ways that compose: at compile time by Cargo feature (state-ga / state-tx-stub, so a deployment links only its jurisdiction’s partner crates), and at boot by the CRAIG__ACTIVE_STATE_BUNDLES env var (fail-fast if unset). Plan U also lands the two Phase-2 BundleContribution axes the design team specified — theme (ADR-036, served from GET /assets/theme.css) and terminology (ADR-034, a Fluent overlay) — both materialized BFF-local in craig-web.
The success condition is concrete: a second jurisdiction (the Texas stub) builds and boots — across all five bundle-consuming crates — with zero changes to the consuming crates, and its seed output contains none of Georgia’s partner tokens.
Key decision: rename craig-state-default → craig-state-ga
craig-state-default is already Georgia. Its jurisdiction_code() returns "georgia", and its content is the ten Georgia-specific partner integrations (CAPS, CPRS, SHINES, STARS, …) — not a neutral baseline. "Create a new craig-state-ga`" is therefore really a rename, and the honest refactor is to perform it: rename the crate, rename `DefaultBundle → GeorgiaBundle, change name() "default" → "georgia" (U3). This matches the umbrella’s own verification criteria, which build --features state-ga and never state-default.
The alternative — keep the default name and add craig-state-ga alongside — leaves an unanswerable question (what content is left in craig-state-default? its content is Georgia), produces either an empty shell crate or duplicated partner wiring, and forces a crate/feature name mismatch (craig-state-default built --features state-ga) that a later rename would have to clean up anyway. Rejected.
The genuinely jurisdiction-neutral artifacts — the 14-token product partner-type taxonomy (U1) and the product-default theme/terminology fallbacks (U2, per ADR-036 §5 / ADR-034) — move into craig-state-bundle, the trait crate every bundle already depends on. craig-state-tx-stub (U4) becomes the genuinely-minimal second bundle that proves the pattern.
Step DAG
U0 (DONE: StateBundle trait + registries — Plan T1.5)
U1 (extract neutral 14-token taxonomy → craig-state-bundle) ─┐
U2 (theme+terminology types + product-default consts; │
grow BundleContribution additively; NO toml dep) │
│ ▼
│ U3 (RENAME default→ga; ATOMIC keystone)
│ │
│ ├──► U4 (NEW craig-state-tx-stub)
│ │ │
│ └────┬─────┘
│ ▼
│ U5 (feature-gate state-ga/state-tx-stub
│ across the 3 existing consumers)
│ ├──► U6 (CI feature-matrix xtask)
│ ▼
│ U7 (CRAIG__ACTIVE_STATE_BUNDLES activation +
│ fail-fast; replaces 3 boot sites;
│ fixes 7 "Step 4" comments)
│ │
└──────────(U2 types)──────────────┬────────────────────┼──► U8 (theme: build.rs codegen,
│ │ /assets/theme.css, alias layer)
│ ├──► U9 (terminology: I18n::load
│ │ fallible, Fluent overlay)
│ └──► U10 (state-aware seed)
│ │
└──────────────────────────────┴──► U11 (extensibility doc)
│
▼
U12 (audit + archive)
Gate summary: U1 + U2 front-load the craig-state-bundle/craig-state-default neutral work so the U3 rename is purely mechanical. U3 is the keystone — U4/U5/U7/U8/U9/U10 all reference craig-state-ga. U5 needs both bundles (U3 + U4). U7 needs the feature gating (U5) and gates the three consumers (U8/U9/U10), each of which also needs the U2 types. U8/U9/U10 are mutually independent. U11 documents shipped reality; U12 archives.
Risk register
| Risk | Mitigation |
|---|---|
Atomic rename (U3) breaks the workspace mid-MR — |
Single atomic MR; |
|
Optional dep behind a |
A RUNTIME |
U2 lands pure-Rust types only; U8 parses the palette |
Growing |
Caught at compile; U2 updates all literals (incl. |
Making |
ADR-034 §8 exempts an empty contribution from the bilingual gate; |
The token alias layer (U8) is a temporary double-definition; a missed sweep leaves dead |
The alias layer lives through Plan U execution per ADR-036 §7; the mechanical ~180-reference sweep + an alias-gone lint is tracked as Plan U follow-through, explicitly out of U8. |
The |
The app never defaults (ADR-032 §2.7 fail-fast on empty/missing/unknown). The devstack/compose layer SETS the var (mirroring T3.6’s |
The shared 14-token taxonomy (U1) plus multi-active bundles would duplicate |
U7’s fail-fast enforces exactly one active jurisdiction bundle (ADR-032 1-deployment-1-jurisdiction), so a single activation never collides. The host-injected-taxonomy alternative is deferred — only needed if a deployment ever serves ≥2 jurisdictions, which ADR-032 excludes. |
The CI feature-matrix (U6) drifts or flakes as features change. |
Single-source-of-truth |
Cross-cutting invariants
Each invariant is a runnable check; together they are the Plan-U-relevant subset of the umbrella’s program-complete criteria (the partner/transport and UI-composability rows belong to Plans T/V/W/X/Y).
-
State-neutral TX build.
for c in craig-exchange craig-web craig-seed craig-mock-server craig-reporting; do cargo check -p $c --no-default-features --features state-tx-stub || exit 1; doneexits 0. (umbrella §Verification) -
Georgia build unchanged. The same loop with
--features state-gaexits 0; full-workspacenextestwithstate-gais green. -
TX seed emits zero Georgia tokens.
cargo run --bin craig-seed --no-default-features --features state-tx-stubproduces output containing none ofcaps cprs smile stars wic ies ions tcm doe_slds empi shines georgia. -
Theme route is CSP-clean.
curl -s http://localhost:8080/assets/theme.cssreturns custom properties (--primary:/--accent:);grep -rn "<style>" services/craig-web/templates/returns zero (no inline styles; the strictstyle-src 'self'admits the same-origin route). -
Theme token migration.
var(--color-*)references survive only through the generated alias layer (one file); the canonical--primary/--accent/--inktokens are the source of truth. -
Terminology overlay + fallback. Jurisdiction
.ftlcontent loads via the bundle contribution; the fallback chain (jurisdiction → product default → key-as-literal) resolves, never blank. -
No runtime
tomlleak.craig-state-bundle/Cargo.tomldeclarestomlonly under[build-dependencies](itsbuild.rsparses the palettetheme/*.tomlat build time); it has no RUNTIMEtomldependency, socraig-exchange/craig-reporting/craig-webinherit none (verified viacargo tree -e normal).
Related decisions
-
Plan S — Multi-Jurisdiction Foundation — the umbrella; this body is Step 12, execution is Step 13.
-
ADR-032 — §2.7 boot activation + fail-fast (U7 implements it); §4 + A11 additive
BundleContributiongrowth (U2); A5/A6 bundles-own-their-content + crate placement. -
ADR-034 — the terminology axis U9 implements (Fluent overlay, bundle-embedded
.ftl, BFF-local materialization, boot fail-fast). -
ADR-036 — the theme axis U8 implements (
GET /assets/theme.css, bundle-parsedPalette, alias layer,--body, the product-default palette in §5). -
ADR-038 — §3 registry materialization (BFF-local for theme/terminology) + §4 field-sourcing / orphan rule (drives where contribution types live).
-
Design engineering contracts — Contract 2 (terminology → U9) + Contract 4 (theme → U8).