Plan U: State Bundle Pattern (child plan of Plan S umbrella)

On this page

Status

Step Description Status

U0

StateBundle trait + BundleContribution aggregate + 5 registries — ALREADY SHIPPED in Plan T1.5. The umbrella sketch’s "U1 pub trait StateBundle interface" is done: crates/craig-state-bundle exports the trait (bundle.rsname/jurisdiction_code/contribute/federal_mapping), BundleContribution (5 Phase-1 fields), BootContext, and the 5 immutable registries with from_bundles(iter) → Result<_, BundleMergeError>. Recorded here as U0 so the Plan U step numbering reflects reality; no work.

Done (2026-06-10) — shipped in Plan T1.5 (craig-state-bundle; !666 / 00843cab)

U1

Extract the neutral partner-type taxonomy into craig-state-bundle. Move the 14-token PARTNER_TYPE_TOKENS list (jurisdiction-neutral CRAIG product taxonomy: state_agency, court_system, federal_agency, … — currently a private const in craig-state-default/src/lib.rs:66-81) into a shared pub const in craig-state-bundle. The GA bundle’s contribute().partner_types and the migration-CHECK lockstep test (craig-state-default/src/lib.rs:239-269) consume the shared const. Pure refactor, zero behavior change (the same 14 tokens are emitted). Each bundle contributes the shared const via its partner_types field — safe under single-jurisdiction activation (U7 enforces exactly one active jurisdiction bundle, so PartnerTypeRegistry::from_bundles sees no duplicate keys). The host-injected-taxonomy alternative (contribute the neutral set once at the orchestrator, bundles carry only jurisdiction extras) is deferred — it is only needed if one deployment ever serves ≥2 jurisdictions, which ADR-032 1-deployment-1-jurisdiction excludes. Front-loaded so the U3 rename + U4 tx-stub diffs stay mechanical.

Done (2026-06-13) — PRODUCT_PARTNER_TYPES in craig-state-bundle; GA bundle rewired. !691 / a56895ec.

U2

Land the theme + terminology contribution types + product-default consts in craig-state-bundle; grow BundleContribution additively. NEW src/theme.rs (TokenPair, ModeTokens, ThemeBranding, Palette with non-optional light/dark + optional high_contrast, ThemeContribution { palette: Option<Palette> }, and a PRODUCT_DEFAULT_PALETTE const) + NEW src/terminology.rs (TerminologyContribution). Add pub theme: ThemeContribution + pub terminology: TerminologyContribution to BundleContribution, each with a cheap empty default per ADR-032 §4 (ThemeContribution::default() = palette: None). No toml dependency is added to craig-state-bundle — these are pure-Rust consts; the theme.tomlPalette parse lands in the bundle impl crate at U8, so the non-theme consumers (craig-exchange, craig-reporting) never inherit toml. Update every inline BundleContribution { … } literal, including the EmptyBundle / ShinesContributingBundle test fixtures at services/craig-exchange/src/bundle_orchestrator.rs:779,807. Additive types, default-constructed everywhere; no consumer reads them yet. Pre-materialized per ADR-032 A11 / ADR-038 §3.

Done (2026-06-13) — theme/terminology modules + the product default realized as product_default_palette() (a fn, not a literal constModeTokens holds a Vec; dark = light placeholder until U8); BundleContribution 5→7 fields; 4 literals updated; no toml dep added. !692 / 585468ab.

U3

RENAME craig-state-defaultcraig-state-ga (DefaultBundleGeorgiaBundle, name() "default""georgia"). ATOMIC keystone. craig-state-default already IS Georgia (jurisdiction_code() = "georgia", the 10 GA-specific partners CAPS/CPRS/SHINES/STARS/…); the honest refactor names it so. One atomic MR: rename the crate directory + Cargo.toml name; workspace root members list + [workspace.dependencies]; the boot-site use/Box::new references (exchange bundle_orchestrator.rs:339, reporting federal_mapping.rs:43, mock-server lib.rs:81-102); the three consumer Cargo.toml dep lines (mock-server keeps features = ["mock"]); and the crate doc-comment language ("default seed bundle" → "Georgia bundle"). Mechanical, zero behavior change — same 10 adapters, 10 codecs, 14 tokens, 5 federal entries, mock fan-out. Atomicity is the risk: members ↔ path-dep ↔ use sites must move together or cargo won’t resolve; cargo xtask validate is the gate. The neutral product-default theme/terminology fallbacks live in craig-state-bundle (U2), not in a residual "default" crate. Keystone — every downstream step references craig-state-ga.

Done (2026-06-13) — git mv + DefaultBundleGeorgiaBundle + name()→"georgia"; workspace members + [workspace.dependencies] + 3 consumer deps + 4 boot-site refs + all docs/comments/test-messages; zero dangling refs; cargo check --workspace green. !693 / 19cbd450.

U4

NEW crates/craig-state-tx-stub — the genuinely-minimal second bundle. TxStubBundle: name() = "tx-stub", jurisdiction_code() = "texas", reuses the shared U1 taxonomy const, a minimal/empty adapter set (no GA partners), its own minimal federal_mapping(), empty theme/terminology contributions. Add the crate to workspace members + [workspace.dependencies]. Proves the pattern is state-neutral. Compiles + unit-tests in isolation; no consumer wires it yet (feature gating is U5). Mirrors the post-rename craig-state-ga shape (depends on U3) and reuses the shared taxonomy (depends on U1).

Done (2026-06-13) — TxStubBundle (name "tx-stub" / jurisdiction "texas"); reuses PRODUCT_PARTNER_TYPES; zero adapters/codecs/mock-routes; minimal federal map; workspace 27→28. !694 / 1d86f38a.

U5

Cargo feature gating state-ga / state-tx-stub across the 3 existing consumers. services/craig-exchange, services/craig-reporting, tools/craig-mock-server each declare BOTH a state-ga and a state-tx-stub feature, with their bundle deps flipped to optional = true behind them; the default feature set is state-ga (current behavior byte-identical). Each boot site’s active_bundles() (mock-server’s inline Box::new(…)) becomes a [cfg(feature = …)]-assembled candidate list. The 5-crate "both features" contract (umbrella §Verification) completes across U5 (these 3) + U8 (craig-web, both) + U10 (craig-seed, both) — craig-web/craig-seed acquire their first bundle dep only at their consuming step (the cargo machete unused-dep gate flags a dep added before its [cfg] consumer exists). The default-features build remains the current single-jurisdiction GA build. Depends U3 + U4.

Done (2026-06-13) — [features] (default=state-ga / state-ga / state-tx-stub) + optional bundle deps on exchange/reporting/mock-server; boot sites assemble via [cfg] (Option+flatten, clippy-clean, no [allow]) + a compile_error! no-jurisdiction guard; all 3×2 combos compile + clippy-clean, default byte-identical. !695 / a791ac99.

U6

CI feature-matrix as an xtask subcommand. NEW xtask feature-matrix subcommand (mirroring the existing Command::CoverageMatrix shape) and a .gitlab-ci.yml job invoking cargo xtask feature-matrix (the same single-source-of-truth pattern as cargo xtask validate, so local and CI run the identical matrix). Per crate it runs cargo check -p <crate> --no-default-features --features state-tx-stub (TX-only — proves state-neutrality; a bare --features state-tx-stub would build GA+TX because state-ga is the default, and would NOT prove a TX-only build) AND --features state-ga (GA), plus a default-all build. The matrix member list is pinned by a fingerprint-style test. It covers the crates that declare state-* features at this point (the 3 from U5) and GROWS to all 5 as U8 adds craig-web and U10 adds craig-seed. Pure tooling. Depends U5.

Done (2026-06-13) — cargo xtask feature-matrix subcommand (3 consumers × {state-ga, state-tx-stub} --no-default-features checks + default-all) + .gitlab-ci.yml feature-matrix job + a fingerprint test (pre-push-enforced via nextest) pinning the crate list to the state-tx-stub-declaring set. Ran green end-to-end. !696 / 02548e56.

U7

CRAIGACTIVE_STATE_BUNDLES env-var activation + boot fail-fast. A shared env-parse/fail-fast helper in craig-state-bundle operates over a per-host #[cfg]-assembled candidate list (this preserves the dependency direction — the trait crate never depends on the concrete bundle crates). Per ADR-032 §2.7 the application has NO runtime default: boot FAILS on an empty/missing env var, on an unknown bundle token, and on ≥2 jurisdiction bundles (1-deployment-1-jurisdiction — this fail-fast is what closes the shared-taxonomy duplicate-key hole from U1). Replaces the three hardcoded DefaultBundle boot sites and corrects the seven stale activation comments that flag this as "Plan U Step 4" — at craig-state-bundle/src/bundle.rs:15, …/lib.rs:43, craig-state-default/Cargo.toml:15, bundle_orchestrator.rs:7 + :335, federal_mapping.rs:41, mock-server lib.rs:72 (two of these wrap the phrase across lines — Cargo.toml:14-15 and mock-server lib.rs:71-72 — so a contiguous grep finds only five; all seven are real). NOT the unrelated "Plan H Step 4" panicking-lint comments. Deploy-surface wiring (net-new — the var is in no surface today): add it to .env.example and the devstack compose / seed.sh layer with a ${CRAIGACTIVE_STATE_BUNDLES:-georgia} compose-layer default (mirroring T3.6’s ${CRAIG_SEED__JURISDICTION:-georgia} at devstack/seed/seed.sh:15), and set it explicitly in the test harnesses that boot these services. The compose layer defaults it; the app never does. Depends U5; gates U8/U9/U10 (the ADR-pinned "active bundle must be a runtime concept" prerequisite — theme/terminology/seed cannot read "the active bundle" until activation exists).

Done (2026-06-13) — resolve_active_bundle + ActivationError in craig-state-bundle (pure, takes candidate NAMES — dep direction preserved); exchange + reporting fail-fast (boot reads env → _for variant; OrchestratorError / new FederalMappingBootError += Activation); mock-server LENIENT (filter if set, else compiled-in — keeps spawn_for_test); the remaining stale "Step 4" comments corrected; wired .env.example (REQUIRED) + docker-compose.yml &active_state_bundles anchor (georgia default, both services). Service boot tests use the _for variant (no unsafe set_var); seed.sh untouched (the seed tool becomes a consumer at U10). !697 / 5aa645d6.

U8

Theme contribution per ADR-036 (/assets/theme.css route + token migration). The token contract is the fixed 28-token role set locked 2026-06-14 — see Theme Token Schema (token names, both baseline palettes' light/dark values, the legacy --color- migration map, and the generated-CSS shape). The canonical palette source theme/simple-statehouse.toml lives in craig-state-bundle, parsed at BUILD time by a new build.rs (toml/serde are BUILD-dependencies of craig-state-bundle, never runtime — so no toml leaks into its consumers); craig-state-ga selects that named palette and overrides only branding in Rust (no toml, no include_str!). craig-web gains craig-state-bundle (unconditional) + BOTH optional bundle deps behind its own state-ga AND state-tx-stub features + the U7-style #[cfg] candidate-list (craig-web’s activation and first consumption land together) — and craig-web is ADDED to the U6 matrix (both features). New GET /assets/theme.css route + materialize_theme_css(&ThemeContribution) → Result<Arc<str>, ThemeBootError> (BFF-local, fallible, ?-propagated in main); an Arc<str> CSS field on AppState. Swap the override-stylesheet link in ALL THREE templates that carry it — templates/base.html:8 (uses {{ ctx.theme }}), templates/login.html:8 and templates/report_base.html:8 (use bare {{ theme }}) — to /assets/theme.css. static/css/tokens.css drops its 19 --color- color definitions (retains the type/layout tokens); the generated CSS emits the canonical 28 design-team tokens (light :root + dark @media/[data-theme] per the schema) + the alias layer + the -ink triad text colors + OS media-query modes (prefers-color-scheme / prefers-contrast). Retire static/themes/georgia-orchard/override.css. The mechanical ~176-reference var(--color-*) sweep is OUT of this step — the alias layer covers it through the grace window (ADR-036 §7). Depends U2 + U7 + U3.

Done (2026-06-14) — build.rs palette codegen in craig-state-bundle (no runtime toml — verified cargo tree -e normal); craig-state-ga named-palette + branding override; craig-web first bundle dep (unconditional craig-state-bundle + optional state-ga/state-tx-stub + #[cfg] candidate-list + resolve_active_bundle); materialize_theme_css (typed ThemeBootError, structural gate) + GET /assets/theme.css route + Arc<str> AppState field; tokens.css color-drop + 18-token alias layer + 3 template <link> swaps + override.css retired + dead theme template fields removed; craig-web added to the feature-matrix. !698 / 3ae01701.

U9

Terminology contribution per ADR-034 (Fluent overlay). craig-state-ga gains jurisdiction .ftl content via include_str!TerminologyContribution (raw &'static strcraig-web already owns a Fluent parser, so no new parser is added). craig-web’s `I18n::load becomes fallible (Result<_, TerminologyBootError>; infallible today); the load path overlays the jurisdiction catalog onto the product default via add_resource_overriding (plain add_resource is first-wins); boot fail-fast on a missing en bucket or zero es bucket. An empty terminology contribution (today’s product-default path) is exempt from the bilingual gate (ADR-034 §8). Keyed by primary-subtag buckets (en / es); flat term-* Fluent messages. Structurally identical to U8 by construction (ADR-036 §10). Depends U2 + U7 + U3.

Done (2026-06-14) — craig-state-ga ships terminology/{en,es}/worker.ftl (flat term-* messages) via include_str!georgia_terminology() (no toml/build.rs, no new dep). craig-web I18n::load is now fallible: load_disk_bundles then apply_terminology_overlay (add_resource_overriding — product first-wins, jurisdiction overrides), creating the es bucket from the overlay when absent on disk + harvesting ids from the merged bundle so overlay-only keys resolve; validate_bilingual_coverage fail-fasts (TerminologyBootError; empty contribution exempt). The active-bundle resolution was extracted from theme.rs into a shared bundle.rs, so main resolves the active BundleContribution ONCE and feeds .theme + .terminology. !699 / 9ab900e7.

U10

State-aware seed data. Populate the empty SeedContribution placeholder; craig-state-ga populates its seed_data; tools/craig-seed gains craig-state-bundle + BOTH optional bundle deps behind its own state-ga AND state-tx-stub features + the #[cfg] candidate-list — and craig-seed is ADDED to the U6 matrix, completing the 5-crate contract. Reconcile the REQUIRED --jurisdiction clap flag (env CRAIG_SEED__JURISDICTION, non_empty_string, from T3.6) against the active bundle’s jurisdiction_code(). Last consumer — least-coupled. Depends U3 + U7 + U5.

Done (2026-06-14) — SeedContribution gains rate_tables: Vec<RateTableSpec> (craig-state-bundle; RateTableSpec reuses craig_reference::PaymentType, keeping the generated SQL byte-identical). craig-state-ga populates seed_data with Georgia’s 5-row daily rate schedule (extracted from the generator’s hardcoded rate_configs); craig-state-tx-stub ships none (a stub declares no policy). tools/craig-seed gains craig-state-bundle (unconditional) + BOTH optional bundle deps behind its own state-ga/state-tx-stub features + a bundle.rs resolver IN THE LIB (so the optional deps satisfy unused_crate_dependencies); main derives the jurisdiction + rate schedule from CRAIGACTIVE_STATE_BUNDLES via active_seed_inputs(). RECONCILIATION (USER decision): the --jurisdiction flag + CRAIG_SEEDJURISDICTION env are REMOVED — the active bundle is the single source of truth; devstack seed.sh/compose + xtask e2e set CRAIG__ACTIVE_STATE_BUNDLES instead. craig-seed joins the U6 feature-matrix (5 crates — contract complete); byte-identity SQL test unchanged (no rebless). !700 / 9caeca60.

U11

NEW multi-jurisdiction-extensibility.adoc Antora guide. Author "how to add a jurisdiction" at docs/modules/ROOT/pages/multi-jurisdiction-extensibility.adoc (the path the umbrella §Verification pins) + a nav.adoc entry. Walks the craig-state-tx-stub example end to end (the bundle crate, the feature gate, the theme/terminology/seed contributions, the env-var activation). Documents shipped reality — landed late, after the pattern is real. Pure docs.

Done (2026-06-14) — NEW docs/modules/ROOT/pages/multi-jurisdiction-extensibility.adoc (the umbrella §Verification-pinned path) + nav.adoc entry under Architecture & Design. An 8-step "how to add a jurisdiction" guide using craig-state-tx-stub as the worked minimal example: crate + StateBundle impl → the BundleContribution axes + federal_mapping → theme (ADR-036, named-palette vs custom theme.toml) → terminology (ADR-034, bilingual .ftl) → seed (SeedContribution/RateTableSpec) → workspace + state-* feature wiring across the 5 consumers → the feature-matrix contract + fingerprint guard → CRAIG__ACTIVE_STATE_BUNDLES activation + boot fail-fast. Closes with a runnable Verification checklist + Related-decisions xrefs. Authored from shipped code + adversarially verified. !701 / 4fba69db.

U12

Plan-completion audit + archive. Fresh Explore plan-completion-audit subagent verifies every U-step cell carries a concrete !MR / sha cite. cargo xtask docs plan-archive (dry-run then execute): nav.adoc Active→archive move + a plans/archive.adoc § Architecture row + sibling-xref rewrites to the plans/archive/ path; flip :status: Active → Complete in the archived body. Flip umbrella Step 13 → Done + close epic &46 (description PUT then a separate state_event PUT — the combined PUT 500s). Append a .claude/CLAUDE.md § Phase Status row. Memory sync. Depends all.

Done (2026-06-15) — this MR: plan-completion audit (CLEAN — every U0–U11 cell carries a concrete !MR / sha, verified against git log) + archive. cargo xtask docs plan-archive git-mv’d this body to plans/archive/; sibling xrefs + nav.adoc rewritten; plans/archive.adoc § Architecture row added. Umbrella Step 13 → Done (Step 12 cite backfilled !690 / a8c51f7f); epic &46 closed; .claude/CLAUDE.md § Completed Plans updated (State Bundle Pattern (U)).

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-defaultcraig-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 `DefaultBundleGeorgiaBundle, 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 — members ↔ path-dep ↔ the boot-site use sites must move together.

Single atomic MR; cargo xtask validate is the gate. U1/U2 are front-loaded so the rename diff carries no logic churn.

craig-web / craig-seed gain their first bundle dependency.

Optional dep behind a state-* feature, added only at the consuming step (U8/U10) so the cargo machete gate stays green. The dependency was already committed to by ADR-036 §9.

A RUNTIME toml dependency leaks into craig-state-bundle, so craig-exchange / craig-reporting / craig-web inherit it for nothing.

U2 lands pure-Rust types only; U8 parses the palette theme.toml in craig-state-bundle’s `build.rs — a BUILD-dependency, so no RUNTIME toml reaches its consumers. cargo tree -e normal plus a no-runtime-toml invariant guard the boundary.

Growing BundleContribution (U2) breaks every inline struct literal lacking ..Default::default(), including the boot-orchestrator test fixtures.

Caught at compile; U2 updates all literals (incl. bundle_orchestrator.rs:779,807) in the same MR. Additive-with-cheap-default is the ADR-032 §4 contract seed_data already follows.

Making I18n::load fallible (U9) regresses the single-locale product-default boot path.

ADR-034 §8 exempts an empty contribution from the bilingual gate; es/ is absent on disk today, so the overlay supplies it; the gate fires only when a bundle declares a non-empty terminology contribution.

The token alias layer (U8) is a temporary double-definition; a missed sweep leaves dead --color-* aliases (and ~25 hardcoded hexes that ignore dark palettes).

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 CRAIG__ACTIVE_STATE_BUNDLES fail-fast (U7) over-fires in dev/test.

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 ${CRAIG_SEED__JURISDICTION:-georgia} at devstack/seed/seed.sh:15) and the test harnesses set it explicitly. The Cargo state-ga default feature governs only what is COMPILED IN — orthogonal to runtime activation. Fail-fast is tested as a sad path.

The shared 14-token taxonomy (U1) plus multi-active bundles would duplicate partner_type keys in PartnerTypeRegistry::from_bundles.

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 xtask subcommand (local == CI); a fingerprint test pins the member list.

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).

  1. 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; done exits 0. (umbrella §Verification)

  2. Georgia build unchanged. The same loop with --features state-ga exits 0; full-workspace nextest with state-ga is green.

  3. TX seed emits zero Georgia tokens. cargo run --bin craig-seed --no-default-features --features state-tx-stub produces output containing none of caps cprs smile stars wic ies ions tcm doe_slds empi shines georgia.

  4. Theme route is CSP-clean. curl -s http://localhost:8080/assets/theme.css returns custom properties (--primary: / --accent:); grep -rn "<style>" services/craig-web/templates/ returns zero (no inline styles; the strict style-src 'self' admits the same-origin route).

  5. Theme token migration. var(--color-*) references survive only through the generated alias layer (one file); the canonical --primary/--accent/--ink tokens are the source of truth.

  6. Terminology overlay + fallback. Jurisdiction .ftl content loads via the bundle contribution; the fallback chain (jurisdiction → product default → key-as-literal) resolves, never blank.

  7. No runtime toml leak. craig-state-bundle/Cargo.toml declares toml only under [build-dependencies] (its build.rs parses the palette theme/*.toml at build time); it has no RUNTIME toml dependency, so craig-exchange / craig-reporting / craig-web inherit none (verified via cargo tree -e normal).

  • 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 BundleContribution growth (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-parsed Palette, 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).

Edit this page · latest