Plan S: Multi-Jurisdiction Foundation

On this page

Status

Step Description Status

1

Closure of superseded work + epic stubs filed at once + memory updates. File umbrella Epic &44 + child epic stubs &45 (Plan T) + &46 (Plan U) + &47 (Plan V) + tracking issue #557 (M6/M7 deferred webMethods broker config destination). Close Epic &43 with supersession comment. Close issues #547-#556 individually with cross-ref comments. Memory updates: NEW project_multi_jurisdiction_foundation.md; EDIT project_epic_43_webmethods_substrate.md to mark superseded; EDIT MEMORY.md index.

Done (2026-06-08) — Epic &44 + &45 + &46 + &47 filed + tracking #557 filed via glab api Python urllib script. Epic &43 closed via curl after glab 404’d on the epic-notes endpoint (gitlab.com epic notes API quirk; description-update PUT used as workaround). All 10 M-issues #547-#556 closed with cross-ref notes then state_event: close. One NEW memory file written (project_multi_jurisdiction_foundation.md); one EDITED (project_epic_43_webmethods_substrate.md with SUPERSEDED preamble); MEMORY.md index updated with 2 entries (new + edit). Closure happened immediately after !653 merged.

2

ADR-032 authoring + filing — original scope. Drafted in its own plan-mode session for external review. Title: "Multi-Jurisdiction Partner Registry and Transport Abstraction". Scope: (a) ErasedAdapter seam + macro; (b) AdapterRegistry immutable lookup; (c) StateBundle + BundleContribution aggregate (Phase 1 fields only); (d) DB open-TEXT + dual-site validation; (e) OutboundTransport trait + co-located crate; (f) mock-manifest contract; (g) PartnerTypeRegistry + federal mapping; (h) hardcoded jurisdiction defaults removed; (i) CRAIG__ACTIVE_STATE_BUNDLES env var fail-fast. Format mirrors ADR-031. Cross-references to ADRs 033-037 use plain text "ADR-033 (anticipated)" form rather than xref: syntax to avoid broken-link rendering until those ADRs land. BundleContribution code block in ADR-032 shows ONLY Phase 1 fields with a comment that the aggregate grows additively per ADRs 033-037 (each landing its own field). Lands as single docs MR + nav.adoc § ADRs updated to add ADR-032 entry after ADR-031.

Done (2026-06-09) — filed via single docs MR. Body lives at docs/modules/ROOT/pages/adrs/adr-032-multi-jurisdiction-partner-registry-and-transport.adoc. Phase-3 finalization edits all landed in this MR: §2.7 env var renamed CRAIG_EXCHANGEACTIVE_STATE_BUNDLESCRAIGACTIVE_STATE_BUNDLES (deployment-global namespace); NEW §4 "BundleContribution grows additively across Plan S phases" section + code block listing Phase 1 (5 fields) + Phase 2 (5 fields) additions anchored by ADRs 033-037; Status section flags both Phase 1 (!653 / 9a6d018a) + Phase 2 (!655 / f1f4db31) anchoring; Related decisions adds plain-text cross-refs to anticipated ADRs 033-037 (each scoped to its BundleContribution field + Plan); ADR-028 cross-ref expanded to call out Plan X composition-service auth pass-through. nav.adoc § ADRs entry added after ADR-031. CHANGELOG entry. Unblocks Plan T body authoring (Step 3).

3

Plan T umbrella body — Adapter Registry Pivot (keystone child plan; restructured 2026-06-09). Plan T body authoring iterated through 5 contextless reviewer rounds + 3 user-driven rounds (8 rounds total). Architectural depth surfaced during iteration justified restructuring into 3 sub-plans (T1 Foundations & Traits / T2 Registry Migration / T3 Open Closures & Hardening) under a Plan T umbrella. Phase A (Step 2 follow-up) shipped ADR-038 (Trait-Object & Registry Patterns) + ADR-032 amendments A1-A11 + closed-aggregator tracking #558 via !657 / sha 1ffe2971. Phase B (this step) ships the Plan T umbrella body sequencing the 3 sub-plans + Plan S restructure + ADR-032 main-body sync with amendments. Umbrella body lives at docs/modules/ROOT/pages/plans/archive/adapter-registry-pivot.adoc (archived 2026-06-11). Umbrella Status table has 7 rows covering T1 body+exec / T2 body+exec / T3 body+exec / umbrella audit+archive. Foundational BundleContribution aggregate ships in Plan T1 Step T1.5 (per ADR-032 A6 — NEW crates/craig-state-bundle crate). 10 D-decisions from the 8-round iteration locked in ADR-032 amendments + ADR-038 (D1-D10 documented in umbrella Context section + memory). 3 sub-plan child epics filed inline: &51 (Plan T1) / &52 (Plan T2) / &53 (Plan T3) under epic &45.

Done (2026-06-09) — umbrella body filed via !658 / 5cecd870

4

Plan T1 — Foundations & Traits authored + filed. Drafted in own plan-mode session per delivery-protocol.md:131-136. Body at docs/modules/ROOT/pages/plans/archive/adapter-registry-pivot-t1-foundations.adoc (archived at T1.9). Child epic &51 + ~9 step issues (T1.1-T1.9) filed deferred to right before T1 plan-mode session per Plan S Phase 2 precedent. See Plan T umbrella Status row 1 for full sub-plan scope (SHINES audit + ErasedAdapter trait/macro + AuditCodec trait + FederalPartnerCategory extension + NEW state-bundle crate + NEW state-default seed bundle + per-partner macro adoption + audit decoders + archive).

Done (2026-06-09) — body filed via !659 / 92ca2bb1

5

Plan T1 executed end-to-end. All T1.1-T1.9 Done; child epic &51 closed; Plan T1 body archived. Hard sequencing gate — Plan T1 foundations MUST land substantively before Plan T2 authoring can start. Plans U/V/W/X/Y all gate on T1.5 + T1.6 landing (foundational BundleContribution aggregate + concrete seed bundle).

Done (2026-06-10) — 8 step MRs !662-!669 + T1.9 audit-archive MR; epic &51 closed; T1.5 craig-state-bundle + T1.6 craig-state-default landed (workspace 27 members); Plan T2 execution now gated only on F-065 / #462

6

Plan T2 — Registry Migration authored + filed. Drafted in own plan-mode session. Body at docs/modules/ROOT/pages/plans/archive/adapter-registry-pivot-t2-registry-migration.adoc (archived 2026-06-10). Child epic &52 + ~5 step issues (T2.1-T2.5; noop:// folded into T2.1). See Plan T umbrella Status row 3 for full scope (AdapterRegistry + bundle_orchestrator + ExchangeAppState + AuditCodecRegistry + MockRouterRegistry + DDL adapter_kind drop). Sequencing gatesauthoring CAN start once Plan T1 Step T1.5 + T1.6 land substantively, OR earlier against the locked Plan T1 body specs (T2 body authored 2026-06-09 per planning-sprint decision; drift risk carried as T2 body risk row 9); execution (T2.1 MR opens) gated on Plan T1 archived + F-065 / Plan G Step 6 / #462 closed.

Done (2026-06-09) — body filed via !660 / 3b62f07c

7

Plan T2 executed end-to-end. All T2.1-T2.5 Done; child epic &52 closed; Plan T2 body archived.

Done (2026-06-10) — 5 MRs (!673 / !674 / !676 / !677 + the archive MR); body at plans/archive/adapter-registry-pivot-t2-registry-migration.adoc; Plan T3 execution gate CLEAR

8

Plan T3 — Open Closures & Hardening authored + filed. Drafted in own plan-mode session. Body at docs/modules/ROOT/pages/plans/archive/adapter-registry-pivot-t3-open-closures.adoc (archived 2026-06-11). Child epic &53 + ~8 step issues (T3.1-T3.8). See Plan T umbrella Status row 5 for full scope (open ExchangeAdapterKind + request validation + PartnerType deletion + FederalPartnerMappingRegistry + DDL partner_type + georgia removal + seed plumbing + per-service test paths + archive). Hard sequencing gatesauthoring CAN start parallel with Plan T2 execution (planning only; no file conflicts at authoring phase), OR earlier against the locked Plan T1+T2 body specs (T3 body authored 2026-06-09 per planning-sprint decision; drift risk carried as T3 body risk row 8); execution (T3.1 MR opens) gated on Plan T2 archived (T3.1 touches services/craig-exchange/src/adapters/mod.rs + send_worker.rs which T2.1+T2.2 also touch; sequential execution avoids merge conflicts).

Done (2026-06-09) — body filed via the Phase E docs MR (see CHANGELOG § Unreleased, adapter-registry-pivot-t3-open-closures entry)

9

Plan T3 executed end-to-end + Plan T umbrella audit + archive. All T3.1-T3.8 Done; child epic &53 closed; Plan T3 body archived. Plan T umbrella audit (fresh plan-completion-audit Explore subagent verifies T1/T2/T3 cells have concrete MR + sha cites) + umbrella archive happen in this step’s final MR per the plan-completion-audit bias.

Done (2026-06-11) — Plan T3 executed in 8 MRs (!679-!685 + !686 archive); epics &51/&52/&53 all closed; T1/T2/T3 + the Plan T umbrella body all archived under plans/archive/. Umbrella audit (fresh Explore subagent) PASS on all 7 checks. Parent epic &45 closed. The Plan T arc (adapter-registry pivot — open ExchangeAdapterKind/PartnerType, registry-driven dual-site validation, federal-mapping seam, georgia seed default removed) is complete; Phase 3 continues with Plans U/V (transport) + W/X/Y (UI composability).

10

ADR-034 — Terminology Resolution authored + filed. Drafted in its own plan-mode session for external review. Body lives at docs/modules/ROOT/pages/adrs/adr-034-terminology-resolution.adoc. Title: "Per-Jurisdiction Terminology Resolution via Bundle-Overlaid Fluent Catalogs". Decision: extend CRAIG’s existing Fluent infrastructure (fluent-bundle + fluent-syntax + unic-langid at services/craig-web/src/i18n.rs); bundle provides terminology: TerminologyContribution containing additional .ftl catalog content (NOT TOML — five-contracts.adoc had it as TOML; ADR-034 supersedes that detail with Fluent to match CRAIG’s existing stack) that overlay the product-default catalogs at startup. Catalog layout: per-jurisdiction .ftl is bundle-embedded (crates/craig-state-<jurisdiction>/terminology/<lang>/.ftl, via include_str!) and contributed through terminology: TerminologyContribution; the product default stays at services/craig-web/locales/<lang>/.ftl; the boot overlay merge is scoped to i18n.rs (ADR-034 §3 refines the earlier locales/<jurisdiction>/ sketch). Unknown keys fall back jurisdiction → product default → key-as-literal (never blank). en-US + at least one es-US required per jurisdiction. Format mirrors ADR-031. Implementation lives in Plan U Step 10. Authoring CAN start parallel with Plan T execution. Lands as single docs MR + nav.adoc § ADRs updated. Also updates docs/modules/ROOT/pages/design/five-contracts.adoc Contract 2 section to standardize on Fluent + the bundle-embedded catalog layout (replacing the original TOML proposal).

Done (2026-06-11) — filed via !688 / 3f07f99c.

11

ADR-036 — Token + Theme Export authored + filed. Drafted in its own plan-mode session for external review. Body lives at docs/modules/ROOT/pages/adrs/adr-036-token-and-theme-export.adoc. Title: "Token + Theme Export via Bundle-Provided CSS Custom Properties Served From a Same-Origin Route". Decision: theme tokens emitted via a generated same-origin CSS route at GET /assets/theme.css (NOT inline <style>) — strict-CSP-compliant per the existing Plan C F-018 posture; the route returns :root { --primary: …​; …​ } from the active bundle’s theme.toml. Light + dark required; high-contrast mode required; --accent (decoration) MUST NOT carry text contrast; red reserved exclusively for danger. Token migration: existing CRAIG CSS uses --color-* token names (e.g. --color-primary); ADR-036 + Plan U Step 9 ship a migration to design-team-defined names (--primary, --accent, etc.) with an alias layer (--color-primary: var(--primary)) for grace-period back-compat through Plan U execution. Cross-references Brand Identity, Palettes, and Typography + Contract 4. Format mirrors ADR-031. Implementation lives in Plan U Step 9. Lands as single docs MR + nav.adoc § ADRs updated. (theme.toml is bundle-embedded via include_str! + parsed by the bundle crate into a typed Palette per ADR-036 §2-§3; --ink defined per §7.)

Done (2026-06-11) — filed via !689 / 51b06cdc.

12

Plan U — State Bundle Pattern (SCOPE EXPANDED) authored + filed. Drafted in its own plan-mode session for external review. Body lives at docs/modules/ROOT/pages/plans/archive/state-bundle-pattern.adoc (archived at U12). Anticipated child epic &46 + ~10 step issues (expanded from ~8 original to host theme + terminology + state-aware features across all bundle-consuming crates). Steps (anticipated): U1 pub trait StateBundle interface; U2 NEW crates/craig-state-ga bundle (Cargo features state-ga defined on services/craig-exchange/Cargo.toml, services/craig-web/Cargo.toml, tools/craig-seed/Cargo.toml, tools/craig-mock-server/Cargo.toml, services/craig-reporting/Cargo.toml); U3 NEW crates/craig-state-tx-stub reference example (matching feature definitions); U4 Cargo feature gating using package-scoped commands; U5 CI matrix: 5 builds validated (-p craig-exchange --features state-ga + -p craig-web --features state-ga + -p craig-seed --features state-ga + each with state-tx-stub + default-all); U6 NEW multi-jurisdiction-extensibility.adoc Antora doc; U7 state-aware seed data; U8 CRAIG__ACTIVE_STATE_BUNDLES env var wiring + fail-fast on empty; U9 theme contribution loading per ADR-036 (CSS route + token migration); U10 terminology contribution loading per ADR-034 (Fluent overlay + path scheme extension). Authoring depends on ADRs 034 + 036 Done.

Done (2026-06-12) — body filed via !690 / a8c51f7f; epic &46 + Plan::U label + U1-U12 step issues filed. Final breakdown is U0-U12 (U0 = trait, DONE in T1.5; keystone U3 = craig-state-defaultcraig-state-ga rename) — supersedes the anticipated U1-U10 list above.

13

Plan U executed end-to-end. All Plan U steps Done; child epic &46 closed; Plan U body archived. Multi-crate feature-flag matrix verified across craig-exchange / craig-web / craig-seed / craig-mock-server / craig-reporting. State-neutrality demonstrated end-to-end including theme + terminology overlay.

Done (2026-06-15) — Plan U executed U1–U12 (epic &46; #584–#595 closed) + archived (plans/archive/state-bundle-pattern.adoc). The 5-crate feature matrix (craig-exchange / craig-reporting / craig-mock-server / craig-web / craig-seed) is green; theme (U8) + terminology (U9) + seed (U10) axes shipped; extensibility guide (U11) published. Epic &46 closed. Executed via !691–!702 (archive !702 / b487ebdf).

14

Plan V — Transport + Substrate authored + filed. Drafted in its own plan-mode session for external review. Body lives at docs/modules/ROOT/pages/plans/archive/outbound-transport-and-substrate.adoc (archived at V8). Anticipated child epic &47 + ~8 step issues. Steps (anticipated): V1 NEW crates/craig-exchange-transport crate with OutboundTransport trait + DirectHttpTransport + WebMethodsTransport stub co-located; V2 refactor 10 per-partner constructors reqwest::ClientArc<dyn OutboundTransport>; V3 WebMethodsTransport runtime stub from Software AG public docs; V4 fault injection in spawn_for_test(); V5 request inspection API; V6 proptest + cargo-fuzz; V7 per-partner coverage matrix + interface-doc predicates + open-questions docs; V8 plan-completion audit + archive. Hard sequencing gate — Plan V Step 2 cannot start until Plan T2 archived (AdapterRegistry lands in T2.1); Plan V Step 1 (crate creation) can parallel Plan T Steps 2-3. Plan V authoring can run parallel with Plan U.

Done (2026-06-15) — body filed via !703 / 2a6b7b57 (body commit 4518243a); epic &47 + Plan::V label + V1–V8 step issues filed. Final breakdown is V1–V8 (StubTransport folded into V1; V2 atomic swap + ADR amendment per the user decisions) — supersedes the anticipated list in the cell above.

15

Plan V executed end-to-end. All Plan V steps Done; child epic &47 closed; Plan V body archived.

Done (2026-06-15) — Plan V executed V1–V8 (epic &47; #597–#604 closed) + archived (plans/archive/outbound-transport-and-substrate.adoc). The OutboundTransport seam is live: the 10 partner adapters + the SHINES StandardAdapter route every wire call through Arc<dyn OutboundTransport>; DirectHttpTransport is the boot default; WebMethodsTransport is a test-reachable broker stub; the mock substrate gained fault injection + request inspection; proptest + cargo-fuzz cover the transport + parse paths; a per-partner interface-doc coverage matrix + predicates pin the wire shapes. Executed via !704–!711 (archive !711 / f402b3dd). The umbrella’s partner-only program-completion grep (§ line ~395) needs a SHINES-standard.rs widen — flagged for umbrella Step 20.

16

ADR-033 — Plugin Manifest + Render Contract authored + filed. Drafted in its own plan-mode session for external review. Body lives at docs/modules/ROOT/pages/adrs/adr-033-plugin-manifest-and-render-contract.adoc. Title: "Plugin Manifest + Render Contract (Panel + Case-Section)". Two-audience model explicit: (1) Plugin authors (core team + external contributors) write Rust crates with #[craig_plugin] macro + Plugin.toml manifest. (2) Jurisdiction operators USE plugins via config — rulesets/<jurisdiction>/dashboards.toml references plugin slugs; no Rust required. The design team’s "everything is config" tagline describes the OPERATOR experience; plugin authoring is still Rust. Decision: per-plugin Plugin.toml manifest declares slug + exports (panels + case-sections) + data source (the jurisdiction’s OWN endpoint, NOT proxied) + permissions + required four-state surfaces; CSP-clean server-side rendering; plugin discovery via linkme-style compile-time distributed slice (canopy precedent at canopy repo’s crates/canopy-composition/src/source.rs — NOT in CRAIG yet, ported by Plan W); display_name is a terminology key per ADR-034. Format mirrors ADR-031. Implementation: Plan W (Steps 12-13). Cross-references Contract 1 + The Four-State Panel Contract.

Done (2026-06-16) — filed via !712 / 0bd2ffa6.

17

Plan W — Plugin Manifest + Render Runtime authored + filed. Drafted in its own plan-mode session for external review. Body lives at docs/modules/ROOT/pages/plans/archive/plugin-manifest-and-render-runtime.adoc (archived at W8). Child epic &48 + ~8 step issues. Steps (anticipated): W1 NEW crates/craig-plugin-contracts crate — Plugin.toml manifest schema + PluginManifest type + PluginManifestError typed enum + manifest parser + PluginContribution field added to BundleContribution; W2 [craig_plugin] macro + linkme-style compile-time registration (ports canopy’s CANOPY_PLUGINS distributed slice pattern); the macro expands to ALSO register the plugin’s compiled Askama templates into the same linkme distributed slice (a sibling CRAIG_PLUGIN_TEMPLATES slice) — plugin templates are compile-time-known via [derive(askama::Template)] per plugin module + walk-time-registered into the BFF’s render registry at boot; no boot-time template parsing; W3 plugin loader + PluginRegistry populated from BundleContribution::plugins; W4 four-state contract enforcement — build-time lint xtask lints four-state-contract verifies plugin templates ship all four named state blocks; W5 server-side rendering pipeline at the BFF — craig-web renders plugins via the compile-time-registered Askama templates from W2 (no new DB or MQ deps required for plugin runtime; plugins fetch their own data via reqwest to their declared endpoints); W6 per-plugin data fetch + cache layer (TTL from manifest; auth from manifest; reqwest client shared); W7 reference plugin: NEW plugins/example/ with all four states + Plugin.toml; W8 plan-completion audit + archive. Hard sequencing gate — cannot start authoring until ADR-033 + Plan T Step 4 are Done.

Done (2026-06-16) — body filed via !713 / 3555d395; epic &48 + Plan::W label + W1–W8 step issues filed. Supersedes the anticipated W-step sketch above per ADR-033: no BundleContribution::plugins field (ADR-032 A12), no CRAIG_PLUGIN_TEMPLATES sibling slice (render-fn carried in the single CRAIG_PLUGINS registration), host-fetches + plugin-pure-render not in-plugin reqwest (ADR-033 §4/§5). Body at plans/archive/plugin-manifest-and-render-runtime.adoc.

18

Plan W executed end-to-end. All Plan W steps Done; child epic closed; Plan W body archived.

Done (2026-06-16) — Plan W executed W1–W8 (epic &48; 605–#612 closed) + archived (plans/archive/plugin-manifest-and-render-runtime.adoc). The server-side plugin runtime is live: [craig_plugin] registers a sync-pure render fn into the CRAIG_PLUGINS linkme slice; craig-web boot-validates a PluginRegistry; GET /plugins/<slug> host-fetches (SSRF-guarded, deny-by-default allow-list) + renders the plugin’s four-state fragment; the reference plugin plugins/example/ ships behind an opt-in plugin-example feature; the feature-matrix gained the plugin axis. Epic &48 closed. Executed via !714–!722 (archive !723 / b6b18bef). Successor: ADR-035 → Plan X (composition; epic &49).

19

ADR-035 — Composition Layer Engine authored + filed. Drafted in its own plan-mode session for external review. Body lives at docs/modules/ROOT/pages/adrs/adr-035-composition-layer-engine.adoc. Title: "5-Layer Composition Engine for Dashboards + Case-Detail; NEW craig-composition Backend Service". Decision: composition lives in a NEW backend service services/craig-composition (NOT in craig-web BFF — craig-web has no DB/MQ deps today and adding them would be a significant scope creep for the BFF role). The new service hosts composition_overrides table + RabbitMQ invalidation publisher; serves composition resolution to craig-web via HTTP (BFF becomes a client). Service infrastructure: craig-composition claims port 8009 (CLAUDE.md § Service Ports runs 8001-8008 + 8080; 8009 is the next unclaimed); ships a new services/craig-composition/Dockerfile mirroring existing 8001-8008 service Dockerfiles; adds a Compose entry under devstack/docker-compose.yml; gains a per-service DB craig_composition (mirrors existing per-service DB pattern); devstack-guard rebuild on bring-up. Auth pass-through: BFF (craig-web) holds the user’s OIDC session; each composition-service request needs the user’s role + jurisdiction. Per ADR-028 (Service Identity + On-Behalf-Of) the BFF mints a per-request X-Craig-Actor JWT identifying the acting worker + signs outbound calls with its own client_credentials service token; craig-composition validates both via the existing peer-JWKS map + Claims::actor. NO new Keycloak / IdP deps on the new service. 5-layer top-down merge: user delta → role override → jurisdiction live override → jurisdiction baseline (git-managed TOML in rulesets/<jurisdiction>/) → product default. Baselines are ops territory (edited via normal git against rulesets/, NOT via UI); Studio writes live overrides + user deltas only. RFC 6902 JSON Patch for user delta (canopy precedent at canopy repo’s crates/canopy-composition/src/user_delta.rsPlan X Step 1 verifies canopy path exists at canopy repo HEAD before authoring; if missing, file a blocker issue). Invalidate-on-write cache (canopy cache.rs); multi-replica invalidation via composition.invalidated event on RabbitMQ fanout. Role filter applies AFTER merge. Resolved composition canonically serialized (RFC 8785) + SHA-256 hashed for cache validation. Format mirrors ADR-031. Implementation: Plan X (Steps 15-16). Cross-references Contract 3 + canopy repo crates/canopy-composition/ (external; ported, not consumed) + canopy ADR-021 + ADR-028 for the actor JWT auth seam.

Done (2026-06-17) — filed via !724 / 1abb055b. Body at adrs/adr-035-composition-layer-engine.adoc; nav.adoc § ADRs entry added after ADR-034; five-contracts Contract 3 engineering-touchpoints refined (composition resolution + store + invalidation publisher home in the NEW craig-composition service, not the BFF — the BFF becomes an HTTP client). Resolves ADR-038 A11 toward a pre-materialized compositions field. ADR-032 left untouched (its frozen Anticipated list is the filing-time snapshot, per the ADR-034/036 precedent; ADR-035 introduces no divergence from what ADR-032 §4 anticipated). Successor: Plan X (composition; epic &49).

20

Plan X — Composition Layer Engine authored + filed. Drafted in its own plan-mode session for external review. Body lives at docs/modules/ROOT/pages/plans/composition-layer-engine.adoc. Child epic &49 + ~10 step issues. Steps (anticipated): X1 NEW crates/craig-composition crate — port canopy repo’s composition crate adapted to CRAIG’s BundleContribution pattern (PluginSource trait maps to PluginRegistry from Plan W; JurisdictionSlug types map to CRAIG’s bundle activation model); X2 NEW services/craig-composition backend service — gains sqlx + craig-mq + craig-db deps; X3 CompositionLoader walking 5 layers with role-filter-after-merge semantics; X4 UserDelta JSONB schema + apply + validate (RFC 6902 JSON Patch + versioned #[serde(tag = "type", rename_all = "snake_case")] user_delta_v1 envelope); X5 NEW table composition_overrides (jurisdiction_code TEXT, role TEXT, user_sub UUID NULL, surface_key TEXT, delta JSONB, updated_at TIMESTAMPTZ) migration on services/craig-composition DB; X6 invalidate-on-write cache + composition.invalidated RabbitMQ event publisher + subscriber wiring (multi-replica + 10-minute TTL fallback for event-delivery failure); X7 BFF (craig-web) becomes HTTP client to craig-composition service — no new deps on craig-web side; outbound calls signed with the BFF’s client_credentials token + X-Craig-Actor JWT per ADR-028 (no new IdP deps on composition service); X8 Studio admin UI for live overrides (jurisdiction-scoped); X9 user-delta write endpoint + UI integration (drag-to-reorder, pin, hide); X10 reference dashboards: NEW rulesets/georgia/dashboards.toml + rulesets/georgia/case_detail.toml baselines + plan-completion audit + archive. Hard sequencing gate — cannot start authoring until ADR-035 + Plan T + Plan U + Plan W are Done.

Done (2026-06-17) — body filed via !725 / 71a4f59c. Body archived at plans/archive/composition-layer-engine.adoc (10 steps X1–X10, mirroring the Plan W body shape); epic &49 (PRE-EXISTING stub — GET+reuse) description updated + Plan::X label + X1–X10 step issues filed (#618–#627); nav.adoc § Active Plan X entry added (removed again at Plan X archive, Step 21). Supersedes the anticipated-X-step sketch above only on detail that ADR-035 fixed: baselines are runtime-loaded from rulesets/<jurisdiction>/ (NOT bundle-embedded); the compositions: CompositionContribution field is a pre-materialized surface declaration carrying NO baseline trees (ADR-035 §8); the engine splits into a pure crates/craig-composition (X1) + the services/craig-composition host (X2). Successor: Plan X execution (Step 21).

21

Plan X executed end-to-end. All Plan X steps Done; child epic closed; Plan X body archived. NEW craig-composition service deployed in devstack; composition_overrides table migrated; invalidate-on-write cache cross-replica-tested.

Done (2026-06-18) — Plan X executed X1–X10 (epic &49; #618–#627 closed) + archived (plans/archive/composition-layer-engine.adoc). The NEW craig-composition service (port 8009, DB craig_composition) hosts the 5-layer composition engine: a CompositionLoader walking user-delta → role → jurisdiction-live → jurisdiction-baseline (rulesets/<j>/{dashboard,case_detail}.toml) → compiled product default, role-filtered after merge + RFC 8785/SHA-256 content-hashed; composition_overrides store with RFC 7232 conditional writes; a per-replica cache + transactional-outbox composition.invalidated cross-replica fanout. The craig-web BFF is an HTTP client (ADR-004 stateless preserved) rendering the composed dashboard via the Plan W GET /plugins/<slug> route; X8 Studio (jurisdiction-live admin) + X9 self-service user-delta personalization + X10 GA reference baselines (the example panel, force-linked) close the surface end-to-end. Archive MR !735 / 05f79cd2. Successor: ADR-037 (Step 22) → Plan Y (epic &50).

22

ADR-037 — Field Ownership + Authz Extension authored + filed. Drafted in its own plan-mode session for external review. Body lives at docs/modules/ROOT/pages/adrs/adr-037-field-ownership-authz.adoc. Title: "Per-Field Ownership Tables for Multi-Tenant Edit Surfaces; Backend Service Enforcement, BFF Reflection". Decision: field ownership enforcement happens at the BACKEND service handling each surface (e.g. craig-cases for case-detail field reads/writes; future per-tenant edit surfaces at their respective backend), NOT at craig-web BFF (which doesn’t host the zen-engine authz stack today and shouldn’t grow to host it). The BFF reflects the backend’s authz decision via UI lock-icons; the lock-icon is a reflection of the backend table, not the source of truth. Per-field ownership: state-owned / provider-owned / shared (read + propose for non-owner). Backend authz uses the existing zen-engine policy DSL (ADR-023) extended with field-level granularity. Propose-approve queue: proposing-role’s write lands in pending_edits_<surface> table on the OWNING backend service; owning-role accepts/rejects from a queue served by the same backend service. Audit emission via existing per-service audit pipeline (NOT new BFF-side audit). Format mirrors ADR-031. Implementation: Plan Y (Steps 18-19). Cross-references Contract 5 + ADR-023.

Done (2026-06-18) — ADR-037 (Field Ownership + Authz Extension) filed at docs/modules/ROOT/pages/adrs/adr-037-field-ownership-authz.adoc: per-field ownership enforced at the OWNING backend (the live-data owner, not the BFF; ADR-004 preserved, BFF reflects via lock-icons over the ADR-028 seam), static owner map on disk fed as a field_owner attr into the existing {jurisdiction}-authz-<resource> zen ruleset, a NEW craig-authz field-permission evaluation path (not a check overload) returning FieldPermission, "propose" as an effective-permission value under Action::Update (no new Action variant), pre-materialized field_ownership: FieldOwnershipContribution declaration + on-disk owner map (ADR-035 §8 split), pending_edits_<surface> + accept/reject on the owning backend (single-transaction + ADR-022 outbox audit; audit on every read AND write). Supersedes-in-part Contract 5’s BFF-enforcement language + adds ADR-032 Amendment A13 (A11 shape refinement); nav + Contract 5 refinement + CHANGELOG updated. Filed via !736 / c6d1ce54. Successor: Plan Y (Step 23, epic &50).

23

Plan Y — Field Ownership + Authz Extension authored + filed. Drafted in its own plan-mode session for external review. Body lives at docs/modules/ROOT/pages/plans/field-ownership-authz.adoc. Child epic &50 + ~7 step issues. Steps (anticipated): Y1 field_ownership: FieldOwnershipContribution field added to BundleContribution; per-surface ownership tables loaded at boot on the OWNING backend service (each service whose handlers serve a multi-tenant edit surface); Y2 craig-authz policy DSL extended with field_ownership.<surface>.<field> predicates at the backend; Y3 propose-approve queue tables + migrations on the OWNING backend service. Decision rubric (Plan Y authoring fixes): pin to existing services/craig-cases IF the CWCA field surface stays small (≤ ~30 fields) AND the propose-approve tables fit alongside existing case attachments (single migration, no new event topics). Spin NEW services/craig-cwca-provider IF (a) the field count > 30, OR (b) the provider portal needs its own session/auth boundary distinct from the caseworker BFF, OR (c) the CWCA contract spans > 2 new tables. Plan Y authoring records the verdict + carries it through Y4-Y6; Y4 propose-approve backend API endpoints (the OWNING backend) + Studio admin UI (BFF surface that calls the backend); Y5 field-attached audit emission (per-service audit pipeline, NOT BFF); Y6 reference ownership table: NEW rulesets/georgia/cwca_ownership.toml mapping the CWCA portal field set + lock-icon reflection in the BFF template; Y7 plan-completion audit + archive. Hard sequencing gate — cannot start authoring until ADR-037 + Plan T + Plan U are Done.

Done (2026-06-19) — Plan Y body authored + filed at docs/modules/ROOT/pages/plans/field-ownership-authz.adoc (nav Planned entry; moves to Active at Step 24). Supersedes the anticipated Y1–Y7 surface-bound sketch above: field ownership is cross-tenant + consumer-driven, so Plan Y delivers ONLY the surface-agnostic craig-authz field-permission capability (ADR-037 §2/§3 — a FieldPermission enum + a resolve_field_permission eval path + parse_field_permission_output, proven against a fixture ruleset). The surface-bound application (§4–§7: the field_ownership bundle axis, per-surface ownership tables + boot-validate, pending_edits_<surface> + propose/accept/reject + field audit, BFF lock-icon + Studio) is sequenced to its real consumer + 2nd-tenant surface, the Phase-11 CWCA provider portal (tracked under epic &50, which stays open). ADR-037 is unedited — it is satisfied incrementally (Plan Y §2/§3 + Phase 11 §4–§7); this umbrella is the sequencing authority. Epic &50 description filled + 2 step issues (#631 Y1, #632 Y2) filed. Archive MR !739 / f85d1350. Successor: Step 24 (Plan Y execution = the authz capability).

24

Plan Y executed end-to-end. Plan Y execution is the craig-authz field-permission capability (Y1) + audit/archive (Y2); the surface-bound field-ownership application (ADR-037 §4–§7) is sequenced to Phase 11 (the CWCA provider portal), tracked under epic &50 (which stays open). All Plan Y steps Done; the Y1/Y2 issues closed; Plan Y body archived.

Done (2026-06-19) — Y1 the craig-authz field-permission capability (FieldPermission enum + resolve_field_permission evaluation path + disjoint parse_field_permission_output; default-deny default impl + ZenAuthzEngine impl; 11 fixture tests; check/auto_scope_list untouched) shipped + nav moved Planned→Active (!740 / c56c00c2 / merge 26e7234a). Y2 plan-completion audit (caught + fixed the body’s "12 tests" → 11) + archive: body git-mv’d to plans/archive/field-ownership-authz.adoc, nav Active entry removed, plans/archive.adoc § Architecture row added; resolve_field_permission + craig-composition-engine registered in shared-crates.adoc (the latter folds in #628); the Phase-11 §4–§7 application issue (#634) filed under epic &50 (kept OPEN as the durable tracker); #631 + #632 + #628 closed. The surface-bound application (ADR-037 §4–§7) is deferred to Phase 11 (the CWCA provider portal). Y2 = !741 / f02ccd0e / merge dd83baa8.

25

Umbrella program-completion audit + archive. Standard close-out per delivery-protocol.md § Plan Completion Audit. Plan-completion-audit subagent verifies all 19 prior steps Done with concrete MR + sha citations for each child plan + ADRs 032-037. Each child plan (T/U/V/W/X/Y) is archived by ITS OWN final step (Plan T3 Step T3.8 (umbrella archive Step 7); Plan U Step U10+; Plan V Step V8; Plan W Step W8; Plan X Step X10; Plan Y Step Y7) via cargo xtask docs plan-archive invoked in that child’s own MR — NOT by Step 20. Step 20 only archives THIS umbrella body (multi-jurisdiction-foundation.adoc). Run cargo xtask docs plan-archive --dry-run first to confirm only the umbrella file is the candidate. Manual-archive fallback if umbrella Status table carries Deferred (…​) tokens (M6/M7 outcomes) per Plan L precedent: git mv + manual nav.adoc rewrite + manual plans/archive.adoc row. nav.adoc § Active Plan S row removed; NEW row in plans/archive.adoc § Architecture subsection. .claude/CLAUDE.md § Project status active-program line updated (Plan S → complete) + the phase recorded in roadmap.adoc (CLAUDE.md no longer carries Phase Status / Testing tables — moved to Antora in the 2026-06 cleanup). Memory updates: EDIT project_multi_jurisdiction_foundation.md to mark all sub-plans + ADRs Done; NEW reference_bundle_contribution_pattern.md (canonical quick-lookup for adding a contract field to BundleContribution).

Done (2026-06-19) — this archive MR. Plan S COMPLETE. Program-completion audit run as a 7-slice fan-out + adversarial completeness critic + synthesis (Ultracode workflow): ALL slices ARCHIVE_READY, 0 broken cites, 0 stale cells, 0 blockers; the critic independently re-resolved the child-plan + ADR + Step-24 Y1/Y2 SHAs, re-confirmed all 6 child plans (T/U/V/W/X/Y) + 6 ADRs (032–037) archived, and re-ran plan-lint (0 violations). Step-24 Y2 cite backfilled (!741 / dd83baa8, last lag-by-one). §Verification corrected for accuracy: criterion 1 (PartnerAuditEvent retained by design, not removed), criterion 4 (SHINES standard.rs wire-path check folded in per the Plan V V8 flag), criterion 11 (Plan Y re-scoped — the field-permission evaluation capability shipped; the surface-bound application ADR-037 §4–§7 deferred to Phase 11 / #634 / epic &50). Umbrella body git-mv’d to plans/archive/; nav § Active Plan S row removed; plans/archive.adoc § Architecture row added; .claude/CLAUDE.md + roadmap.adoc updated; epic &44 closed (child epics &45–&49 closed; &50 kept OPEN for the Phase-11 deferral). MR/sha for this archive: this MR (terminal step — no successor to backfill into).

Epic: &44 (filed at Step 1)
Child epics: &45 (Plan T), &46 (Plan U), &47 (Plan V), &48 (Plan W), &49 (Plan X), &50 (Plan Y) — all 6 child epic stubs filed; iids locked-in. W/X/Y were filed at this Phase 2 commit MR (matching the Step 1 precedent for T/U/V).
ADRs: ADR-032 (in flight; original scope), ADR-033 (Plugin Manifest; Step 11), ADR-034 (Terminology; Step 5), ADR-035 (Composition Engine; Step 14), ADR-036 (Theme; Step 6), ADR-037 (Field Ownership; Step 17)
Issues: filed per-step under &44 + child epics; tracking issue #557 for M6/M7 deferred work
Branch prefix: docs/plan-s-step<N>- for umbrella; <type>/adr-NNN- for ADR-only MRs; <type>/plan-<letter>-step<N>- for child code-execution MRs
*Milestone
: TBD

Context

CRAIG ships as open-source CCWIS with a stated multi-jurisdiction mission. Six jurisdiction-variability dimensions are already abstracted across prior plans (rules, admin units, IdP, authz, ICPC, i18n stack). The partners + broker and UI composability dimensions are the last two unaddressed axes before 1.0.

Plan S (Phase 1, !653) addressed partners + broker via 11 closure surfaces opened by ErasedAdapter + AdapterRegistry + StateBundle + BundleContribution + OutboundTransport. ADR-032 anchored; Plans T/U/V execute.

Phase 2 (this re-authoring, 2026-06-08 evening) adds UI composability surfaced by the design team’s response bundle (!654 / sha 8777674f). The 5 engineering contracts (The Five Engineering Contracts) each extend the same BundleContribution aggregate Phase 1 established:

Contract Field added to BundleContribution ADR + step Implementation plan + step

1. Plugin manifest + render

plugins: Vec<PluginManifest>

ADR-033 (Step 11)

Plan W (Steps 12-13) — runtime in BFF

2. Terminology dictionary

terminology: TerminologyContribution

ADR-034 (Step 5)

Plan U Step 10 — Fluent overlay

3. Composition + persistence

compositions: CompositionContribution

ADR-035 (Step 14)

Plan X (Steps 15-16) — NEW craig-composition service

4. Token + theme export

theme: ThemeContribution

ADR-036 (Step 6)

Plan U Step 9 — CSS route + token migration

5. Field ownership + authz

field_ownership: FieldOwnershipContribution

ADR-037 (Step 17)

Plan Y (Steps 18-19) — backend enforcement

The architectural pattern is shared. Same BundleContribution aggregate. Same boot orchestrator validation. Each contract = one field + one validation step + one registry materialization. The trait grows additively across plans (no field type is mentioned in code before its plan’s first MR introduces it).

Pre-1.0 + no production users = expanding scope here is moderate (~12-14 working weeks total program vs ~5-7 for partner-only). Post-1.0 retrofitting the UI composability layer costs substantially more.

Pre-flight audit findings

Phase 1 findings + new Phase 2 findings:

  1. ExchangeAdapter is NOT object-safe — RPITIT; per-crate macro is the seam.

  2. Object-safe registry precedentClaimsExtractor; StaticActorJwksRegistry; Store::from_config.

  3. Blanket impl is wrong; per-crate macro is right.

  4. StandardAdapter is behaviorally Value-typed.

  5. 10 per-partner crates take reqwest::Client directly → Plan V Step 2 sequenced after Plan T2 archived.

  6. DB CHECK constraint closure surface → dual-site validation.

  7. No jurisdiction_code column on exchange_partners today — 1-deployment-1-jurisdiction preserved.

  8. PartnerType taxonomy closure opens via PartnerTypeRegistry.

  9. Federal AFCARS/NCANDS mapping interface required — closed enum + bundle-provided mapping enforced at boot.

  10. Hardcoded jurisdiction: "georgia" defaults at 4 production sites — removed in Plan T3 Step T3.6.

  11. StateBundle::contribute(&self) → BundleContribution aggregate.

  12. Seed + mock-server closure surfaces addressed by bundle pattern.

  13. Create/update flow closure surface — registry-driven validation at API handler.

Phase 2 additions:

  1. CRAIG already has Fluent-based i18n (fluent-bundle + fluent-syntax + unic-langid at services/craig-web/src/i18n.rs; locales at services/craig-web/locales/{en,…​}). Plan U Step 10 + ADR-034 extend this existing infrastructure; the design team’s TOML-format proposal at Contract 2 is SUPERSEDED by the Fluent convention (ADR-034 also updates Contract 2 to align).

  2. Canopy has a near-complete composition reference in the canopy repo at /home/bitskrieg/code/canopy/crates/canopy-composition/ (NOT in CRAIG today — Plan X Step 1 ports it). Public surface: CompositionLoader + UserDelta + cache + audit + PluginSource trait + role filtering + RFC 6902 JSON Patch + invalidate-on-write. Plan X Step 1 ports this into NEW CRAIG crates/craig-composition with adaptations to BundleContribution.

  3. Composition baselines are ops territory (user direction 2026-06-08): UI does NOT handle merges/conflicts/PR creation/git workflow. Ops updates baselines via normal git operations against rulesets/<jurisdiction>/. Studio writes live overrides + user deltas ONLY.

  4. Plugin manifest uses linkme-style compile-time registration per canopy precedent (CANOPY_PLUGINS distributed slice in canopy repo’s crates/canopy-composition/src/source.rs). Plan W Step 2 ports.

  5. Red is reserved exclusively for danger (design-team locked decision); future build-time lint enforces; PR-review responsibility until.

  6. Every plugin must implement all four panel states — build-time lint at xtask lints four-state-contract (Plan W Step 4).

  7. Two-audience model for plugins (this revision clarifies): plugin AUTHORS write Rust crates with #[craig_plugin] macro; jurisdiction OPERATORS use config (rulesets/<jurisdiction>/dashboards.toml) to reference plugins by slug. The design team’s "everything is config, no Rust" tagline describes the OPERATOR experience; plugin AUTHORING is still Rust.

  8. craig-web (BFF) has NO DB/MQ deps todayservices/craig-web/Cargo.toml has no sqlx/craig-db/craig-mq/lapin. Plans X + Y must NOT add these deps to the BFF. Composition + field-ownership enforcement land in BACKEND services (Plan X spawns NEW craig-composition service; Plan Y enforces at existing/relevant backend services like craig-cases).

  9. CRAIG CSS uses --color-* token naming (e.g. tokens.css declares --color-primary). Design-team-defined names are --primary/--accent/etc. ADR-036 + Plan U Step 9 ship the migration + a back-compat alias layer (--color-primary: var(--primary);) for grace period through Plan U execution.

  10. Strict CSP forbids inline <style> (per Plan C F-018). ADR-036 emits theme tokens via a generated same-origin CSS route at GET /assets/theme.css, NOT inline.

  11. Feature-flag matrix needs 5 crates (not just craig-exchange): craig-exchange + craig-web + craig-seed + craig-mock-server + craig-reporting each declare their own state-ga and state-tx-stub features in their respective Cargo.toml files.

What this plan does NOT touch

  • Other jurisdiction-variability dimensions (rules / admin units / IdP / authz core / ICPC / i18n stack) — already abstracted

  • Operator-side multi-tenancy (one binary serving N states concurrently)

  • Per-state workflow / BPM beyond rulesets

  • Per-state data retention + reporting beyond AFCARS/NCANDS

  • RetryPolicy + IdempotencyPolicy for WebMethodsTransport (#557; GA DHS docs blocker)

  • BrokerContract cross-state generalization beyond WebMethodsTransport

  • Cross-state PartnerType taxonomy governance + composition harmonization

  • In-UI baseline editing (baselines stay in ops/git)

  • Red-is-danger build-time lint (future sub-plan)

  • A11y annotation sweep (design open thread)

  • Notifications model alignment with craig-mq (design open thread)

  • Print/legal output formats for AFCARS + NCANDS (design open thread)

  • Sign-in / generic IdP deeper config story (design open thread)

  • Studio→Git baseline-editing UI (explicitly out: ops handles baselines via normal git)

Project-convention conformance

Convention Source Applied where

AsciiDoc plan body with ADR-030 Status table

docs/modules/ROOT/pages/adrs/adr-030-plan-lifecycle-and-status-vocabulary.adoc

All Status cells + child plan bodies + ADR Status blocks

Status vocabulary tokens

ADR-030 §1

Every Status cell

GitLab epic + step issues via glab api

Plan N/Q/R precedent + recent session (!653/!654)

Step 1 (DONE) + Phase 2 commit (W/X/Y stubs) + each child plan filing step

Branch naming <type>/plan-s-step<N>- or <type>/adr-NNN- or <type>/plan-<letter>-step<N>-*

the git-workflow standard

Each MR

Commit subjects ≤ 72 chars + type-prefix + Co-Authored-By: trailer naming model from current system prompt

the git-workflow standard

Every commit

Token-gated pre-commit D1-D8 via fresh Explore subagent

.githooks/pre-commit:40-80 + feedback_precommit_checklist.md

Every commit

Pre-push battery green; never --no-verify; never git push | tail

git-workflow.md + feedback_no_tail_on_git_push.md

Every push

CHANGELOG == Unreleased per MR with test-count reconciliation

Reconcile test-count deltas

Every MR

cargo xtask validate --skip-devstack --skip-docker 14 phases green

delivery-protocol.md + Plan N Step 10 gate

Every MR

cargo xtask docs plan-lint blocking

validate [4b/14]

After every Status cell update

cargo xtask lints no-silent-skips / dto-length / secrets-yaml / route-role-coverage / no-transitional-allows / struct-method-count / fn-name-and blocking

validate [4c-4k/14]

Every step touching code

cargo xtask quality-budgets --report 8/8 LOCKED

validate [4i/14]

Every step

cargo xtask axis-coverage 0 drift

validate [4j/14]

Every step touching tests

cargo machete unused-dep gate

validate [9b/14]

Every step adding/removing deps

No squash-merge

the git-workflow standard’s merge-don’t-squash rule

Every MR

MR creation via glab api (NOT glab mr create)

the Force-merge runbook

Every MR

Skip CI pipeline; merge immediately; retry on 405 with 15s sleep

the Force-merge runbook

Every MR

Wait for main before branching; cleanup after merge

Branch hygiene (wait for main before branching; clean up after merge)

Every step transition

CLAUDE.md § Project status active-program line at archive (+ a roadmap.adoc phase row; CLAUDE.md no longer carries Phase Status / Testing tables — moved to Antora in the 2026-06 cleanup)

delivery-protocol.md

Step 20

Plan-completion audit subagent

delivery-protocol.md + the plan-completion-audit bias

Step 20 + each child archive

3-4 contextless reviewer passes per plan body + per ADR body

delivery-protocol.md:131-136

Every plan body + every ADR body

Pre-1.0 destructive migrations + reseed

the pre-1.0 destructive-rebuild posture

Plan T2 Step T2.4 + Plan X Step 5 + Plan Y Step 3 commits cite

Plain-text "ADR-NNN (anticipated)" cross-refs when target file doesn’t exist yet; convert to xref: when file lands

this revision

ADR-032 finalization + Status-cell descriptions + Context cross-refs

Documentation Update Checklist scope: umbrella touches nav.adoc (multiple steps) + .claude/CLAUDE.md (Step 20). Child plans + ADRs touch Services — index + Shared Crates — Public API Surface + Architecture per their own scope.

delivery-protocol.md

Step 1 + Step 20; detailed updates in each child plan

Per-MR execution checklist (no skipping)

  1. Pre-branch: standard cleanup sequence

  2. Branch: per step’s template

  3. Implement per step’s Files:

  4. Verify: cargo fmt --all, cargo nextest run -p xtask --bin xtask when xtask changed, cargo clippy -p <touched-crate> --all-targets — -D warnings, cargo xtask validate --skip-devstack --skip-docker, cargo xtask quality-budgets --report 8/8 LOCKED, cargo xtask axis-coverage 0 drift, cargo machete if deps changed

  5. CHANGELOG with test-count reconciliation

  6. Stage + token-gated commit: extract PRECOMMIT_TOKEN → fresh Explore subagent for D1-D8 → re-commit with token + step’s titled subject

  7. Push: git push -u origin <branch>; verify exit code; verify remote with git ls-remote

  8. Open MR via API: POST /merge_requests with squash: false

  9. Merge immediately: PUT /merge?should_remove_source_branch=true&squash=false

  10. Sync + cleanup: post-merge sequence

  11. Close issue: PUT /issues/<id> with state_event: close (when applicable)

  12. Update plan body Status cell at next MR (or piggyback)

  13. Memory sync when applicable: if the MR introduces new project state (new ADR/plan landing, child plan archive, scope shift, gate flip), update project_multi_jurisdiction_foundation.md + MEMORY.md index in the same MR. For intermediate code-only step MRs that don’t change project state, no memory edit required. Per delivery-protocol.md § Context Hygiene — keep memory tight, not append-only.

Architecture summary (full content lives across ADR-032 + ADRs 033-037)

The pivot rests on five core abstractions shared across both phases. Each abstraction is jurisdiction-neutral; bundles contribute the per-state specifics.

  1. ErasedAdapter seam trait + per-crate impl_erased_adapter! macro (ADR-032 §1.1).

  2. Atomically-built immutable registries (ADR-032 §1.2 + sibling ADRs) — all populated at boot from validated `BundleContribution`s, never mutated post-boot. Dual-site validation (boot + request) closes TOCTOU. Each registry lands in a specific child plan; no registry exists in code before its plan’s first MR introduces it.

  3. StateBundle trait + BundleContribution aggregate (grows additively) (ADR-032 §2.1).

    Phase 1 fields ship in Plan T Step 2 (and ONLY Phase 1 fields exist in craig-exchange-contracts after Plan T’s keystone work):

    // craig-exchange-contracts — after Plan T Step 2
    pub struct BundleContribution {
        pub adapters: Vec<(&'static str, Arc<dyn ErasedAdapter>)>,
        pub audit_codecs: Vec<(&'static str, Arc<dyn AuditCodec>)>,
        pub mock_routes: Vec<(&'static str, MockRouteFactory)>,
        pub partner_types: Vec<PartnerTypeMeta>,
        pub seed_data: SeedContribution,
    }

    Phase 2 fields added additively by each ADR’s implementation plan (each field’s type ships in the SAME MR that adds the field to BundleContribution):

    • Plan W Step 1 → pub plugins: Vec<PluginManifest> (+ PluginManifest defined in NEW craig-plugin-contracts)

    • Plan U Step 10 → pub terminology: TerminologyContribution (+ TerminologyContribution defined)

    • Plan X Step 1 → pub compositions: CompositionContribution (+ CompositionContribution defined in NEW craig-composition)

    • Plan U Step 9 → pub theme: ThemeContribution (+ ThemeContribution defined)

    • Plan Y Step 1 → pub field_ownership: FieldOwnershipContribution (+ FieldOwnershipContribution defined)

      The end-state shape (after Step 19) is the union of all 10 fields. No field type appears in any cross-crate reference before its plan’s MR lands.

  4. OutboundTransport trait + concrete impls co-located in crates/craig-exchange-transport (ADR-032 §3.1).

  5. 5-layer top-down composition merge (ADR-035) — user delta → role override → jurisdiction live override → jurisdiction baseline → product default. Ops manages baselines via git; Studio writes live overrides + user deltas. Resolution lives in NEW backend service services/craig-composition; BFF is a client.

Service-boundary table

Plans X + Y create or extend service boundaries. The BFF (craig-web) stays free of new DB/MQ deps; new state lives in dedicated backend services.

Surface Hosting service Why

Plugin manifest registry + render

craig-web (existing BFF)

Plugins fetch their own data via reqwest; no DB or MQ needed for plugin runtime; existing Askama integration sufficient.

Theme CSS route

craig-web (existing BFF)

GET /assets/theme.css is a static-ish handler emitting from active bundle’s ThemeContribution; no new deps.

Terminology overlay

craig-web (existing BFF)

Extends existing Fluent loader; no new deps; bundles contribute .ftl files.

Composition resolution + storage + invalidation

NEW services/craig-composition

Requires sqlx + craig-mq + craig-db. BFF stays a client (HTTP).

Field ownership tables + propose-approve queue + enforcement

OWNING backend per surface (initially craig-cases for case-attached fields; future scope: NEW craig-cwca-provider if CWCA boundary is substantial enough). BFF reflects via lock icons. The authz capability (craig-authz::resolve_field_permission) ships in Plan Y (ADR-037 §2/§3); the ownership tables + propose-approve queue + enforcement land in Phase 11 with the CWCA provider portal (the owning backend chosen there per the rubric).

Existing zen-engine authz stack lives in backend services; BFF stays free of authz enforcement.

Verification — what "umbrella program complete" looks like

Each criterion runnable as a single command. Greps that include AsciiDoc table-cell escapes (\|) unescape to | when copied to a shell.

  1. Closed enums removed: git grep -nE "^(pub )?enum (ExchangeAdapterKind|AnyAdapter|PartnerType)\b" — '*.rs' | wc -l returns 0 — the three runtime-switch enums were deleted in Plan T2/T3. NB: PartnerAuditEvent is RETAINED by design (it is the closed typed audit-event union the AuditCodec trait produces — ADR-038 §2 trait-location; SHINES/noop have no codec per it), NOT a runtime-switch enum, so it intentionally remains in crates/craig-partner-audit.

  2. State-neutral build works across all 5 bundle-consuming crates: 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.

  3. Georgia build unchanged across the same 5 crates: same loop with state-ga feature; exits 0; full workspace nextest with state-ga is green.

  4. Transport abstraction is the only wire path (all 11 consumers — 10 partner adapters + the SHINES StandardAdapter): git grep -nE "reqwest::Client|self\.client\.(post\|get\|put\|delete)" crates/craig-partner-*/src/adapter.rs AND git grep -nE "reqwest::(Client\|Error\|StatusCode)" services/craig-exchange/src/adapters/standard.rs both return only ////// doc-comments + the intended *Transport::new(reqwest::Client) constructor seam — no live wire call. (The SHINES check flagged at Plan V Step V8 is folded in here; Plan V’s archived §Cross-cutting-invariants covers both.)

  5. CHECK constraint dropped: psql -c "SELECT COUNT(*) FROM pg_constraint WHERE conname LIKE 'exchange_partners_adapter_kind_%'" returns 0.

  6. Web + CLI registry-driven selectors: grep templates + clap args.

  7. API validation is registry-driven: git grep -nE "is_known_adapter_kind\|is_known_partner_type" services/craig-exchange/src/api/ returns matches; git grep -nE "default_adapter_kind\|serde\(default = " services/craig-exchange/src/api/partners_dtos.rs returns zero.

  8. Seed is state-neutral: cargo run --bin craig-seed --no-default-features --features state-tx-stub | grep -ci 'caps\|cprs\|smile\|stars\|wic\|ies\|ions\|tcm\|doe_slds\|empi\|shines\|georgia' returns 0.

  9. Jurisdiction defaults removed: no PRODUCTION code defaults jurisdiction to "georgia" (the last production default was removed in Plan T3.6). git grep -nE 'jurisdiction.[:=]."georgia"' crates/ services/ tools/ | grep -v 'tests/\|examples/\|fixtures/' returns only matches inside inline [cfg(test)] modules (georgia is the reference jurisdiction in fixtures) — the criterion’s path filter does not exclude inline test modules, so confirm with a [cfg(test)]-aware scan that non-test hits are zero.

  10. Federal mapping validated at boot: bootstrap path contains validation block iterating partner_types_registry.keys() asserting federal_mapping_registry.contains_key(…​).

  11. Plugin manifest infrastructure exists: crates/craig-plugin-contracts/ exists + #[craig_plugin] macro compiles + reference plugin at plugins/example/ builds + xtask lints four-state-contract passes.

  12. Composition service exists: NEW services/craig-composition/ exists; composition_overrides table migration ships; invalidate-on-write cache + RabbitMQ fanout cross-replica-tested in devstack; BFF calls composition service via HTTP (no sqlx/craig-mq/lapin deps added to services/craig-web/Cargo.toml).

  13. Terminology overlay extends Fluent: services/craig-web/locales/<jurisdiction>/<lang>/<bundle>.ftl files load via bundle contributions; fallback chain (jurisdiction → product default → key-as-literal) works.

  14. Theme contribution served from CSS route: curl -s http://localhost:8080/assets/theme.css | grep -E "--primary:|--accent:" returns matches; grep -rn '<style>' services/craig-web/templates/ returns zero matches (no inline styles).

  15. Theme token migration complete: git grep -nE "var\(--color-primary\)|var\(--color-accent\)" services/craig-web/static/css/ returns zero (or only references the alias layer in a single file).

  16. Field-permission EVALUATION capability shipped (ADR-037 §2/§3, surface-agnostic — Plan Y was re-scoped to deliver exactly this): git grep -n 'resolve_field_permission' crates/craig-authz/src/engine.rs returns matches + git grep -nE 'enum FieldPermission' crates/craig-authz/src/types.rs returns one. The surface-bound APPLICATION (ADR-037 §4–§7 — field_ownership.<surface>.<field> predicates at the owning backend, pending_edits_<surface>, BFF lock-icon + Studio) is DEFERRED to Phase 11 (the CWCA provider portal), tracked by #634 under epic &50 (kept open) — NOT a Plan S deliverable.

  17. ADRs 032-037 exist + linked: [ -f docs/modules/ROOT/pages/adrs/adr-032-multi-jurisdiction-partner-registry-and-transport.adoc -a -f docs/modules/ROOT/pages/adrs/adr-033-plugin-manifest-and-render-contract.adoc -a -f docs/modules/ROOT/pages/adrs/adr-034-terminology-resolution.adoc -a -f docs/modules/ROOT/pages/adrs/adr-035-composition-layer-engine.adoc -a -f docs/modules/ROOT/pages/adrs/adr-036-token-and-theme-export.adoc -a -f docs/modules/ROOT/pages/adrs/adr-037-field-ownership-authz.adoc ] && grep -cE 'adr-03[2-7]' docs/modules/ROOT/nav.adoc returns 6.

  18. multi-jurisdiction-extensibility.adoc exists + walks the TX stub: file at docs/modules/ROOT/pages/multi-jurisdiction-extensibility.adoc contains craig-state-tx-stub.

  19. All quality gates green: cargo xtask validate --skip-devstack --skip-docker exits 0; cargo xtask quality-budgets --report shows 8/8 LOCKED.

  20. Plan body archived: scratch absent + archive present + Plan S row removed from Active in nav.adoc.

Sequencing + estimated calendar

Phase Activity Plan-mode sessions Calendar

0 (Phase 1)

Plan S initial filing + Step 1 closure

1 (past)

Done 2026-06-08

0.5 (Phase 2)

Plan S re-authoring (this document) + iteration to clean

1 (now)

~1 day

1

Step 2: ADR-032 finalization (expand for BundleContribution-grows-additively + plain-text refs) + filing

0 (already drafted)

~1-2 days

2

Step 3: Plan T umbrella body authoring + filing (replaces single-Plan-T body per Phase B restructure 2026-06-09)

1

~1 day

3

Step 4: Plan T1 (Foundations & Traits) authoring + filing

1

~1-2 days

4

Step 5: Plan T1 execution (~9 child MRs)

0

~1.5 weeks

5

Step 6: Plan T2 (Registry Migration) authoring + filing

1

~1 day

6

Step 7: Plan T2 execution (~5 child MRs; gated on Plan T1 archived + F-065 closed)

0

~1 week

7

Step 8: Plan T3 (Open Closures & Hardening) authoring + filing (CAN parallel Plan T2 execution)

1

~1-2 days

8

Step 9: Plan T3 execution + Plan T umbrella audit + archive (~8 child MRs + 1 umbrella archive)

0

~1.5 weeks

9

Steps 10-11: ADRs 034 + 036 authoring + filing

2

~2-3 days

9.5

Gate: ADRs 034 + 036 Done before Plan U authoring starts (Plan U body cites both)

0

10

Steps 12-13: Plan U authoring + execution (~10 child MRs)

1

~2 weeks

11

Steps 14-15: Plan V authoring + execution (~8 child MRs; can parallel Plan U after Plan T2 archived)

1

~2 weeks (parallel-OK)

12

Step 16: ADR-033 authoring + filing

1

~2 days

13

Steps 17-18: Plan W authoring + execution (~8 child MRs)

1

~2-2.5 weeks

14

Step 19: ADR-035 authoring + filing

1

~2 days

15

Steps 20-21: Plan X authoring + execution (~10 child MRs; canopy port + NEW backend service)

1

~3-3.5 weeks

16

Step 22: ADR-037 authoring + filing

1

~2 days

17

Steps 23-24: Plan Y authoring + execution (~7 child MRs)

1

~2 weeks

18

Step 25: Umbrella audit + archive

0

0.5 day

Total

25 umbrella steps (was 20 pre-Phase-B); ~76 child MRs (was ~54 — Plan T umbrella alone has ~22 child MRs across T1/T2/T3 vs original ~11 single-Plan-T estimate) + 7 ADR MRs (was 6; added ADR-038)

14 plan-mode review sessions (was 11; +3 sub-plan sessions for Plan T1/T2/T3)

~14-16 working weeks (was ~12-14)

Risk register

Risk Mitigation

ErasedAdapter macro bounds break a future partner

Compile-fail at the per-call-site invocation; no silent surprise.

CHECK-drop allows DB to hold unregistered adapter_kind

Boot + per-request validation closes TOCTOU.

StandardAdapter doesn’t fit standard macro

Plan T1 Step T1.1 audit characterizes; macro likely passthrough variant.

Plan V constructor refactor conflicts with Plan T macro adoption

Plan V Step 2 hard-sequenced after Plan T2 archived.

Cargo features cascade

Build-time controls compile; env var controls activate. Empty ACTIVE_STATE_BUNDLES fails fast.

Child plans expand past step estimate

Each plan sizes its own scope; umbrella calendar is working estimate.

PartnerType taxonomy fragments

Per-deployment registry; cross-state alignment is governance.

Bundle author forgets federal mapping entry

Boot validation fails-fast.

Operator sets wrong CRAIG_JURISDICTION for active bundle

Boot cross-checks against bundle jurisdiction_code().

Umbrella Status table carries Deferred (…​) → breaks plan-archive

Step 20 manual archive fallback per Plan L precedent.

ADR-032 references ADRs 033-037 with broken xrefs

Plain-text "ADR-NNN (anticipated)" form until target files land; convert to xref: per ADR landing MR.

BundleContribution referenced before its fields exist

The shape is documented as growing additively; no code references a field before its plan’s MR adds it.

Composition multi-replica invalidation event lost

10-minute TTL cache fallback bounds staleness; Plan X Step 6 ships + tests the fallback.

User-delta JSONB schema drift

UserDelta versioned via #[serde(tag = "type")] envelope; future v2 deserializer accepts both; additive migration.

Studio live-override writes land out-of-order across replicas

Last-write-wins per key; updated_at + serialized writer queue; concurrent same-key edits are operationally rare.

Terminology overlay collisions across bundles

Multi-bundle deployments are rare per ACTIVE_STATE_BUNDLES posture; boot orchestrator validates uniqueness across bundles; conflicting keys fail boot with explicit error.

Theme --accent token used for text contrast

ADR-036 documents; PR review until lint ships; future build-time lint is its own sub-plan.

Red used for branding by a future state bundle

Same as above.

Field ownership omits a CWCA field

Plan Y Step 1 boot validation: every field on every multi-tenant edit surface MUST have a field_ownership entry.

Plugin’s data endpoint unreachable

Per-plugin cache + circuit breaker (Plan W Step 6); failed plugin renders four-state error block, not global error.

Fluent overlay path scheme conflicts with existing loader

Plan U Step 10 ships loader change scoped to i18n.rs; existing catalogs continue to work; new per-jurisdiction subdirectory is additive (loader walks both old + new layouts during transition; cuts over at Plan U execution close).

Canopy port carries assumptions not valid in CRAIG

Plan X Step 1 explicit adaptation step; canopy’s PluginSource → CRAIG’s PluginRegistry; canopy’s per-jurisdiction layer → bundle activation model.

BFF gains DB/MQ deps despite the ownership boundary

Verification §12 explicitly checks services/craig-web/Cargo.toml for sqlx/craig-mq/lapin absence.

CSP violation from inline theme styles

Verification §14 greps templates for <style>; CSS route at GET /assets/theme.css keeps strict-CSP compliant.

--color-* token migration breaks existing CSS during transition

Alias layer (--color-primary: var(--primary); etc.) declared in one transition CSS file; Plan U execution removes the alias layer in the final sub-step after all consumer CSS is migrated.

Plan U execution waits on ADRs 034 + 036 across two plan-mode sessions (delays Plan U start)

ADRs 034 + 036 are small (Fluent overlay + CSS route + token names); ~2-3 days total. Sequencing accepts the delay rather than blocking Plan U on parallel-authored ADRs.

Out of scope (deferred)

  • Operator-side multi-tenancy

  • Per-state workflow / BPM beyond rulesets

  • Per-state data retention + reporting beyond AFCARS/NCANDS

  • RetryPolicy + IdempotencyPolicy for WebMethodsTransport (#557)

  • BrokerContract cross-state generalization beyond WebMethodsTransport

  • Per-state UI brand layers (theme is bundle-level; agency-specific assets are operator concern)

  • Plan G Step 6 / #462 cross-handler DRY scan

  • Cross-state PartnerType taxonomy governance

  • In-UI baseline editing for composition (ops/git)

  • Red-is-danger build-time lint

  • --color-* alias layer removal (small post-Plan-U sub-plan after CSS consumer migration)

  • A11y annotation sweep (design open thread)

  • Notifications model alignment with craig-mq (design open thread)

  • Print/legal output for AFCARS + NCANDS (design open thread)

  • Sign-in / generic IdP deeper config story (design open thread)

  • Studio→Git baseline-editing UI (explicit user direction: ops handles via normal git)

Next action

  1. Continue iteration of this re-authored Plan S body via 2-3 contextless subagent review rounds per delivery-protocol.md:131-136.

  2. Apply round feedback.

  3. Surface for user review round 2.

  4. Once clean: execute the Phase 2 re-authoring commit MR with full scope below.

Phase 2 re-authoring commit MR scope

Single docs MR. Branch docs/plan-s-phase-2-re-authoring. Ships:

  1. REPLACE docs/modules/ROOT/pages/plans/multi-jurisdiction-foundation.adoc with the re-authored body (this scratch content moved + SCRATCH header dropped).

  2. FILE 3 NEW child epic stubs via glab api POST /groups/…​/epics (lock iids matching Step 1 precedent). One stub per child plan with a one-line description pointing at the umbrella; full description added when the child plan’s body is authored:

    1. Plan W — Plugin Manifest + Render Runtime

    2. Plan X — Composition Layer Engine (depends on NEW craig-composition backend service)

    3. Plan Y — Field Ownership + Authz Extension (backend service enforcement)

    4. After filing: edit the Status cells for Steps 11/12/14/15/17/18 to replace &<W|X|Y> placeholders with the actual iids returned by the API.

  3. UPDATE project_multi_jurisdiction_foundation.md memory — replace T/U/V-only fact statement with expanded T-Y scope; reference 5 design contracts; cross-link new ADRs; add "Plan letters in flight" subsection (T/U/V Phase 1; W/X/Y Phase 2; Z stays available) and "Calendar shift" note (~5-7 weeks → ~12-14 weeks).

  4. UPDATE MEMORY.md index — Plan S entry’s description grows to include Phase 2 additions.

  5. NEW CHANGELOG entry under docs(plans, multi-jurisdiction-foundation) describing Phase 2 re-authoring.

  6. Verification: cargo xtask docs plan-lint clean; cargo xtask validate --skip-devstack --skip-docker green; pre-commit D1-D8 via fresh Explore subagent (docs-only MR; expect all PASS/N/A).

  7. Token-gated commit with Co-Authored-By: trailer from current system prompt.

  8. MR: POST /merge_requests with squash: false; immediate PUT /merge with retry-on-405 loop.

  9. Post-merge cleanup: standard sequence.

After the Phase 2 commit

  1. Resume Step 2 (ADR-032 finalization) — apply expanded-scope BundleContribution-grows-additively language + plain-text "ADR-NNN (anticipated)" cross-refs to ADRs 033-037; commit + merge per the same checklist (uncommitted ADR-032 at docs/modules/ROOT/pages/adrs/adr-032-multi-jurisdiction-partner-registry-and-transport.adoc still in working tree from Phase 1).

  2. Continue per Status table.

Edit this page · latest