Worker portal redesign — composability runtime + design-system extraction
On this page
- Status
- Child issues
- Context
- Scope
- Design
- Steps
- Stage 1 — Design system extraction (1 MR)
- Stage 1.5 — Panel-state primitives upgrade (1 MR, follow-up to Stage 1)
- Stage 2 — ADR ratification (2 MRs shipped; 3rd deferred)
- Stage 3 — Composability runtime + storage layering (3 active MRs; 1 deferred)
- Stage 4 — Identity rework (2 MRs)
- Stage 5 — Core surfaces (4-5 MRs, one per surface)
- Stage 6 — Maintainer surfaces (3 MRs)
- Stage 7 — Polish (2-3 MRs)
- Files Touched
- Branch + label hygiene
- CHANGELOG entries
- Verification
- Documentation Updates
- Pre-commit Q1-Q8 expectations (every MR)
- Risk + Rollback
- Open decisions revisited as Stages land
This plan tracks #460 under group epic &51 (19 active child issues, total weight 64; 2 deferred). Originated from a May 2026 design exploration; all load-bearing design context has been incorporated into this plan and the ratified ADR-021 + ADR-022. This file is the canonical source-of-truth for the redesign; older handoff artifacts are non-normative.
2026-05-20 update: Stage 2 closes at 2 of 3 ADRs ratified. ADR-021 and ADR-022 shipped. The third (originally "ADR-023: promote-PR mechanism") was deferred during drafting — user feedback reframed the underlying question from "how does composition open PRs against canopy" to the broader "what is the unified config-backend abstraction across all canopy config domains (service config, secrets, jurisdiction policy, rulesets, theme, IDP, composition)". Filed separately as #507. Composition v1 ships using the existing filesystem rulesets/{juris}/ pattern; Studio promote affordance descoped from v1 (admins use their existing baseline-edit workflow external to canopy). When #507 ratifies the unified backend + adds a write-capable backend, the Studio promote affordance lands as a follow-up. #488 + #492 closed-deferred. #500 weight 5 → 3.
Status
| Step | Description | Status |
|---|---|---|
1 |
Design system extraction (1 MR). Extract 8 Askama macro primitives — |
Done (2026-05-21) — !349 |
1.5 |
Panel-state primitives upgrade (1 MR, follow-up to Stage 1, non-blocking). Upgrade the four CSS-only panel-state utility classes from Stage 1 to first-class Askama-macro primitives with proper props: |
Done (2026-05-21) |
2 |
ADR ratification (2 MRs shipped; 3rd deferred). ADR-021 (composability runtime + plugin model — ratified 2026-05-20). ADR-022 (storage layering — ratified 2026-05-20). Originally ADR-023 (promote-PR mechanism) — deferred 2026-05-20 in favor of #507 (unified config backend ADR across canopy). |
Done (2 of 3; ADR-023 deferred to #507) |
3 |
Composability runtime + storage layering (2 MRs; 1 deferred). MR1 lands the forward-only migration creating |
Done (2026-05-22 — MR1 + MR2 both merged) |
4 |
Identity rework (2 MRs). Generic IDP loader from |
Done (2026-05-22 — MR1 !353 + MR2 !354 both merged) |
5 |
Core surfaces (4-5 MRs, one per surface). Worker dashboard (12-panel kit composition-driven). Supervisor dashboard. Analyst dashboard. Case detail with 3 shell strategies (scroll / card-grid / tabs) and 13 section types. Customize-my-dashboard (worker-level deltas). MR1 (495) lands the worker dashboard via the Stage 3 composition runtime: 12 panel plugins registered through |
Done (2026-05-24 — MR1 !355 + MR2 !356 + MR2.1 !357 + MR3 !359 + MR4a !363 + MR4b !364 + MR4c TBD all merged) |
6 |
Maintainer surfaces (3 MRs). Jurisdiction Studio: onboarding wizard. Studio: live-mode composer (promote modal descoped from v1 pending #507). Studio: plugin developer view (Plugin Studio). |
In progress |
7 |
Polish (2-3 MRs). ⌘K command palette. Audit log (system + case scopes sharing one primitive). First-impression states (splash, maintenance, expired, 404). |
In progress |
Child issues
| Stage | Issue | Title | Weight |
|---|---|---|---|
1 |
#485 |
Design system extraction (Askama partials + CSS utility classes) |
3 |
1.5 |
#505 |
Upgrade panel-state utility classes to 4 Askama-macro primitives |
3 |
2 |
#486 |
ADR-021 — composability runtime + plugin model (ratified) |
2 |
2 |
#487 |
ADR-022 — composition override storage layering (ratified) |
2 |
2 |
~#488~ |
~~ADR — promote-PR mechanism~~ deferred to #507 |
— |
3 |
#489 |
DB migrations for composition override layers |
3 |
3 |
#490 |
Composition loader (TOML parse + role filter + override merge) |
5 |
3 |
#491 |
Live override APIs (read/write/archive endpoints) |
3 |
3 |
~#492~ |
~~Promote-live-to-baseline (git-host PR generation)~~ deferred to #507 |
— |
4 |
#493 |
Generic IDP loader from idp.toml |
3 |
4 |
#494 |
IDP-aware sign-in template |
2 |
5 |
#495 |
Worker dashboard (12-panel kit, composition-driven) |
5 |
5 |
#496 |
Supervisor + analyst dashboards (role overrides) |
3 |
5 |
#497 |
Case detail (3 shell strategies + 13 section types) |
8 |
5 |
#498 |
Customize my dashboard (per-worker deltas) |
3 |
6 |
#499 |
Studio onboarding wizard |
3 |
6 |
#500 |
Studio live-mode composer (promote modal descoped pending #507) |
3 |
6 |
#501 |
Plugin Studio (developer view) |
5 |
7 |
#502 |
⌘K command palette |
3 |
7 |
#503 |
Audit log unified surface (system + case scopes) |
3 |
7 |
#504 |
First-impression states (splash / maintenance / expired / 404) |
2 |
Context
What we have today
services/canopy-web/ ships an Askama + htmx + Alpine.js (CSP build) worker portal with:
-
Orchard design system tokens (light/dark/system theme via
--orchard-*CSS custom properties) -
8 pages (dashboard, case search, case detail with 6 tabs, application process, renewal queue, applications list, notices list, appeals list, 404)
-
30 caseworker action handlers across SNAP/TANF/Medicaid/CAPS/WIC (per #392, #393, #394 / MRs already shipped)
-
Program-specific action handlers with cross-program views and per-program upstream endpoints (ADR-019 service-class JWT)
-
JWS-signed determinations rendered via
canopy_signing::SignableDetermination(ADR-002) -
FTI auditor role + advisory naming (per the most-recent FTI work)
Already shipped (pre-epic, foundation for this work):
-
✅ Palette revision (orchard tokens) — !295
-
✅
--orchard-infoCSS token -
✅ Light / dark / system theme support
-
✅ Per-jurisdiction theme configuration (
src/theme.rs+rulesets/{jurisdiction}/theme.toml)
Intended outcome
The worker portal becomes composition-driven: jurisdictions edit TOML in rulesets/{jurisdiction}/ rather than fork canopy. The same three layers (dashboard, case-detail, identity) follow the same composition + override pattern, with a five-layer storage model (user delta → role override → jurisdiction live override → TOML baseline → system defaults).
Scope
In scope (across Stages 1-7)
-
Design system extraction into reusable Askama partials + CSS utility classes (Stage 1) + four-state primitive upgrade (Stage 1.5)
-
Composability runtime: TOML composition loader, 5-layer merge (RFC 7396 baseline + RFC 6902 DB layers per ADR-022), plugin manifest validation per ADR-021
-
Storage layering: forward-only migration for
composition_documents+composition_documents_archive; live override APIs (read/write/archive) -
Generic IDP loader + IDP-aware sign-in
-
Worker / supervisor / analyst dashboards (composition-driven)
-
Case detail (3 shell strategies + 13 section types)
-
Customize-my-dashboard (per-worker deltas)
-
Jurisdiction Studio (onboarding wizard + live-mode composer + Plugin Studio)
-
⌘K command palette
-
Audit log unified surface (system + case scopes)
-
First-impression states (splash, maintenance, expired, 404)
Out of scope
-
Plugin Marketplace federation — defer to v2 (in-process Askama partials in v1 per ADR-021 require canopy-core PRs to install plugins; federation arrives when
WasmPluginSourceis added) -
Studio promote modal / promote-live-to-baseline endpoint — descoped from v1 pending #507 (unified canopy config-backend ADR). Admins promote live overrides to baseline via their jurisdiction’s existing workflow (PR / Salt / manual edit) external to canopy until #507 lands.
-
Applicant portal (ADR-008, Dioxus) — separate project
-
Mobile / tablet — not in scope for v1
-
Print views — ADR-010 Typst territory, separate surface
-
Worker-journey microspec — needs design iteration before build; file separate design-spec issues per journey (intake / IEVS resolution / sign-and-authorize / notice compose)
-
Determination sign-and-authorize ceremony — needs design before build; tracked as separate spec issue
-
Notice compose/preview UX — needs design before build; tracked as separate spec issue
-
Income tab interaction depth (IEVS resolution, add/edit/remove income, employer lookup) — section shape is designed; interaction depth is TBD
Design
Stage 2 closed at 2 of 3 ADRs ratified 2026-05-20: ADR-021 (composability runtime + plugin model) and ADR-022 (storage layering). The third ADR (originally "ADR-023: promote-PR mechanism") was reframed during drafting and deferred to #507 (unified config backend across canopy domains). This Design section is the canonical narrative; the ADRs are the canonical contract.
Composability runtime + plugin model
Ratified in ADR-021. Key shape:
-
PluginSourcetrait abstracts plugin discovery. v1 ships onlyCompileTimePluginSource. v2 federation (WasmPluginSourceetc) is additive. -
Plugins are in-process Askama partials registered via
#[canopy_plugin]macro +linkmedistributed slice. Compile-timePlugin.toml↔ Rust handler validation prevents manifest-handler drift. -
Composition loader signature:
load_composition(jurisdiction, role, user_id, surface) → Result<ComposedSurface, CompositionLoadError>. -
Cache: invalidate-on-write, single-replica v1. Studio writes invalidate the in-process cache; multi-replica RabbitMQ fanout invalidation deferred post-UAT.
-
Role filtering applies after override merge — items the role can’t use are silently dropped, no aria-disabled clutter.
-
Closed-set errors:
UnknownPlugin,SpanOutOfRange,RowOverflow,RoleNotFound.
Composition override storage layering
Ratified in ADR-022. Key shape:
-
Three DB-backed layers (user delta, role override, jurisdiction live) share one
composition_documentstable keyed by(jurisdiction_id, layer, scope_key, surface).scope_keyis polymorphic: user UUID foruser, role slug forrole,'jurisdiction'sentinel forjurisdiction_live. -
Override body is an RFC 6902 JSON Patch op list. Studio "add one panel" maps to one
{"op":"add","path":"/items/-",…}op (not a full-document rewrite).testops support optimistic concurrency. -
Composition loader fetches all DB layers in one indexed SQL query (
WHERE jurisdiction_id AND surface AND (layer IN …)) and replays the patch lists against the jurisdiction TOML baseline injurisdiction_live → role → userorder. -
Override lifecycle: live override stays after promote-merge as a no-op; Studio surfaces a "live matches baseline" hint with an explicit Archive button. No canopy-core-repo watcher in v1.
-
Uniform 1-year audit retention for override-layer events; every write emits a JWS-signed
AuditEventper ADR-014 (chain integrity extends across composition mutations).
Composability thesis (architectural)
Three layers all follow the same composition + override pattern:
-
Dashboard composition — which panels appear, in what rows, at what spans
-
Case-detail composition — which sections appear, in what order, with which shell strategy (scroll / card-grid / tabs)
-
Identity composition — which IDP(s), in what order, with what label/color
Every plugin (panel or section) declares its slug, allowed programs, default span, permissions, and i18n catalogs in its manifest. Runtime renders only what jurisdiction TOML references.
Case-detail shell strategy (locked 2026-05-19)
Shells are jurisdiction-selectable per role, not progressive enhancements at viewport breakpoints. Same jurisdiction can run different shells per role; any given user in any given context sees exactly one shell. No responsive shell-switching in v1.
Rationale (per design):
-
Desktop-first product — workers don’t review cases on phones
-
Runtime cost of two shells live: double templates, double htmx swap targets, ambiguous focus management when the viewport crosses a breakpoint mid-action
-
Escape hatch already exists: a jurisdiction that needs viewport flexibility picks
tabs(the most viewport-tolerant shell)
TOML shape per role in rulesets/{jurisdiction}/composition/case-detail.toml:
[shell.eligibility_worker]
strategy = "scroll"
[shell.intake_screener]
strategy = "card_grid"
Section list and shell strategy are independent knobs. A jurisdiction on tabs can still reorder sections, mark some required = true, or exclude program-specific ones via the same TOML’s sections = […] list. Section partials know nothing about which shell hosts them — section content shape is identical across shells; only the chrome changes per strategy.
Georgia migration default: existing 6-tab implementation becomes strategy = "tabs" with current section slugs in sections = […]. scroll and card_grid ship as opt-in; Studio admin switches when ready. No auto-migration.
Storage layering (5 layers, top wins)
| Layer | Storage | Edited via | Audience |
|---|---|---|---|
User personal layout |
|
"Customize my dashboard" |
Each worker |
Role overrides |
|
Studio live mode |
Jurisdiction admin |
Jurisdiction live overrides |
|
Studio live mode |
Jurisdiction admin |
Jurisdiction baseline |
TOML on disk under |
Jurisdiction’s own workflow (PR / Salt / manual edit), external to canopy v1; Studio promote pending #507 |
Maintainer |
System defaults |
Compiled into canopy core binary (panel/section registry initial state) |
canopy core PRs |
canopy team |
Schema + merge semantics ratified in ADR-022: one unified composition_documents table for the three DB-backed layers (NOT three per-layer tables); RFC 6902 JSON Patch for DB-backed layers; RFC 7396 JSON Merge Patch for the baseline-over-defaults overlay.
Generic IDP interface
-
Email-first discovery routes user to matching IDP by domain or claim mapping
-
idp.tomldeclares N IDPs with slug + label + provider type (Keycloak / SAML / OIDC) + host + chip styling -
Local accounts as a toggle (
localAccounts.enabled = false→ section vanishes) -
Zero-IDP graceful state pointing at Studio → Identity
Audit (one primitive, two scopes)
AuditEvent is JWS-signed (ADR-014 already shipped this for canopy-security):
-
System audit — admin-facing, jurisdiction-wide, filterable, exportable to CSV / signed PDF / compliance report
-
Case history — same data, filtered to one household, exportable as "Cite for hearing" → signed PDF
Plugin manifest
Plugin.toml declares [plugin] (slug + name + version + author + license + canopy_min), [plugin.exports] (panels + case_sections), per-panel config (display_name + icon + programs + default_span + allowed_spans + required_states), [data] (source + auth + cache_ttl + timeout + endpoints), [permissions] (required_roles + audit), [i18n] (default + catalogs). Full schema with v1 constraints is in ADR-021.
Compile-time validation via the #[canopy_plugin] macro: slug uniqueness across the registered set; semver on version + canopy_min (with canopy core’s CARGO_PKG_VERSION asserted to satisfy); allowed_spans ⊆ {1, 2, 3, 4, 6, 12} (12-column grid breakpoints only); programs ⊆ {snap, tanf, medicaid, caps, wic}; manifest-declared endpoints' URL parameters resolve to handler request type fields (prevents manifest-handler drift). Composition-time validation (per render): every slug in composition TOML resolves in the PluginRegistry; per-item span ∈ allowed_spans; total row span ≤ 12.
Steps
(Each "Step" below is a discrete MR. Steps within a stage may parallelize. Stages 1 + 2 are gating; Stages 3-7 unlock incrementally.)
Stage 1 — Design system extraction (1 MR)
-
#485 Extract 8 Askama primitives (
PanelFrame,Overline,GoldRule,BigNumber,HeroStrip,LeafGlyph,StatusPill,MoneyCell) intoservices/canopy-web/templates/_primitives/*.html -
Reuses existing
.skeleton+@keyframes pulse+.u-empty-stateand adds one new.u-error-blockutility (the four Stage-1.5 macros wrap these classes; no renames) -
Outcome: existing canopy-web templates rewritable to use primitives without composability runtime
Stage 1.5 — Panel-state primitives upgrade (1 MR, follow-up to Stage 1)
-
#505 Upgrade the 4 panel-state utility classes to first-class Askama-macro primitives (
EmptyState,Skeleton,SkeletonRow,ErrorBlock) -
Rule: every panel renders all four states
-
Non-blocking — runs in parallel with Stage 2 / Stage 3
Stage 2 — ADR ratification (2 MRs shipped; 3rd deferred)
No code in Stage 2. Pure architectural ratification.
Stage 3 — Composability runtime + storage layering (3 active MRs; 1 deferred)
-
#489 Forward-only migration (per ADR-016) landing the unified
composition_documentstable +composition_documents_archiveper ADR-022 (one table for all three DB-backed layers, polymorphicscope_key) -
#490 Composition loader: 5-layer merge (system defaults compiled into binary → jurisdiction baseline TOML via RFC 7396 → DB-backed layers via RFC 6902 in
jurisdiction_live → role → userprecedence) + role filter applied post-merge; integrated with the invalidate-on-write cache from ADR-021 -
#491 Live override APIs (read/write/archive endpoints accepting
application/json-patch+json; If-Match ETag for full-list replace; JWS-signedAuditEventper ADR-014 on every mutation) -
Deferred: Promote-live-to-baseline (#492 closed-deferred to #507). Studio’s v1 "promote" affordance is admin-driven: admin uses jurisdiction’s existing baseline-edit workflow (PR / Salt / manual edit) external to canopy.
Stage 4 — Identity rework (2 MRs)
-
#493 Generic IDP loader from
rulesets/{juris}/idp.toml(email-discovery routing; OIDC provider typeskeycloak+oidc-genericin v1; local-accounts toggle; zero-IDP graceful state). SAML federation stays upstream of the OIDC IdP per CRAIG-aligned design; full multi-shape claims (authentik / zitadel / kanidm) deferred to #515. -
#494 IDP-aware sign-in template — email-first discovery + multiple IDP chips with per-IDP
chip_color+chip_icon; CSP-clean (Alpine.js + htmx only); axe-core WCAG 2.1 AA -
Plan: worker-portal-redesign-stage4-idp-loader-and-sign-in.adoc
-
Follow-ups filed: #512 (SAML spike), #513 (local accounts), #514 (introspection mode), #515 (multi-shape claims), #516 (multi-juris sign-in), #517 (identity verify xtask), #518 (env-var retirement)
Stage 5 — Core surfaces (4-5 MRs, one per surface)
-
Worker dashboard (12-panel kit, composition-driven)
-
Supervisor dashboard (jurisdiction-aware overrides)
-
Analyst dashboard (panel subset)
-
Case detail with 3 shell strategies + 13 section types
-
Customize-my-dashboard (per-worker deltas)
Stage 6 — Maintainer surfaces (3 MRs)
-
#499 Studio onboarding wizard — MR1a + MR1b + MR1c shipped 2026-05-25 (!367 / !368 / forthcoming). MR1a promoted
rulesets/georgia/torulesets/default/(reference ruleset). MR1b made canopy-core jurisdiction-agnostic:FilesystemJurisdictionRegistry+active_jurisdiction(state)+WorkerRole::StudioAdmin+ role coverage across 40 Plugin.tomls. MR1c shipped the 5-step wizard at/studio/onboarding/step/{1..5}emitting a downloadable scaffold bundle (deep-copy ofdefault/+ 3 overridden TOMLs + README documenting integrator’s required next steps). 3 FUs deferred: #568 multi-IdP, #569 custom theme palette, #570 IdP discovery probe. -
#500 Studio live-mode composer (promote modal descoped from v1 pending #507; admin promotes via jurisdiction’s external workflow until then)
-
#501 Studio plugin developer view (Plugin Studio — authoring scope is one of the 3 open design questions on #486)
Files Touched
Stage-by-stage rough touch list. Per-MR files-touched specifics live in each MR’s description; this is the planning-level inventory.
Stage 1 (design system extraction)
-
NEW:
services/canopy-web/templates/_primitives/orchard.html(single file housing all 8 macros) -
NEW:
services/canopy-web/templates/_primitives/_smoke.html(smoke fixture for unit tests) -
NEW:
services/canopy-web/tests/primitives_test.rs(per-macro variant assertions) -
MODIFIED:
services/canopy-web/static/css/canopy-web.css— adds ~220 lines for the 8 primitives' classes +.u-error-block; reuses existing.skeleton,@keyframes pulse, and.u-empty-state; no inlinestyle=per CSP. Status-pill kind backgrounds hardcoded for cross-jurisdiction consistency (matches existing.u-status-*convention) -
Stage-1 implementation plan lives at worker-portal-redesign-stage1-design-system.adoc
Stage 1.5 (panel-state primitives upgrade)
-
NEW: macros for
empty_state/skeleton/skeleton_row/error_blockappended to_primitives/orchard.html -
NEW:
services/canopy-web/templates/cases/_results_error.html(htmx error fragment for the validating surface) -
NEW:
tests/e2e/specs/panel-states.spec.ts(Playwright spec exercising each of the four states) -
MODIFIED:
services/canopy-web/static/css/canopy-web.css— no changes (macros wrap existing.u-empty-state/.skeleton/.u-error-block) -
MODIFIED: ≥ 1 in-tree panel template validating the four-state rule (Stage-1.5 plan picks
cases/search.htmlto avoid the dashboard.spec.ts coupling Stage 1 deferred)
Stage 3 (composability runtime + storage)
-
NEW:
services/canopy-web/migrations/<timestamp>_composition_documents.sqlper ADR-022 schema -
NEW:
crates/canopy-plugin-macros/(proc-macro crate;#[canopy_plugin]derive + compile-time Plugin.toml validation) -
NEW:
crates/canopy-composition/(or canopy-web internal module — TBD by Stage 3 implementation plan):PluginSourcetrait +CompileTimePluginSource+load_composition+ 5-layer merge -
NEW: HTTP handlers in canopy-web for
GET/PATCH/DELETE /v1/composition/{surface}/{layer}/…per ADR-022 write API contract -
MODIFIED:
services/canopy-web/src/api/…to wire the composition loader into existing case-detail + dashboard request paths
Branch + label hygiene
-
Branch root:
feat/worker-portal-redesign-{stage}-{surface}(one per MR) -
Labels:
type::feature+priority::medium(orpriority::lowfor v2 deferrals) +program::infrastructure(UI affects multiple programs) +service::web+workflow::ready(orworkflow::needs-specfor design-iteration-required surfaces)
CHANGELOG entries
One === Changed entry per Stage MR. Format mirrors the canopy-test-lib port plan’s entries.
Verification
Per-MR
-
cargo xtask validateclean (fmt + clippy + nextest + check-docs) -
cargo xtask docs plan-lintclean (this plan’s Status column stays canonical) -
Pre-push validate + Playwright E2E (≥ 136 currently green)
-
cargo xtask coverageat or above the currentDEFAULT_THRESHOLDfloor (xtask/src/cmd/coverage.rs; unit-lane scoped since #1382)
Stage acceptance
-
Stage 1: every primitive exercised by the smoke-fixture template (
_primitives/_smoke.html) + a per-variant assertion intests/primitives_test.rs. Consumer-template migration happens per Stage 5-7 issues. -
Stage 1.5: ≥ 1 in-tree panel template renders all four states (
empty,loading,error,populated) using the fourempty_state/skeleton/skeleton_row/error_blockmacros; all 20.u-empty-statetemplate consumers migrated too::empty_state; underlying.u-empty-state/.skeleton/.u-error-blockCSS classes retained (macros wrap them per Stage 1 Decision 4) -
Stage 2: ADR-021 + ADR-022 merged + cross-referenced from this plan’s Design section (Stage 2 done at 2 of 3; ADR-023 deferred to #507)
-
Stage 3: forward-only migration applied cleanly; composition loader unit-tested with fixture TOML + fixture patch op lists covering all 5 layers;
PatchFailederror surfaces test-op failures + missing paths with layer + op-index context -
Stage 4: IDP loader unit-tested per provider type (
keycloak+oidc-generic); sign-in template axe-core WCAG 2.1 AA clean; zero-IDP graceful state renders. SAML federation dropped from app layer (handled upstream of OIDC IdP); full multi-shape claims (authentik / zitadel / kanidm) deferred to #515. -
Stage 5-7: per-surface acceptance defined in the surface’s own MR
Documentation Updates
-
CHANGELOG.adoc— one=== Changedper Stage MR -
This plan — Status column updated after each MR
-
ADRs (Stage 2 outputs)
-
Coding Conventions — adds composition-runtime conventions after Stage 3
-
Architecture — gains a "Worker portal composability" section after Stage 3
Pre-commit Q1-Q8 expectations (every MR)
-
Q1 — every stage MR adds tests for its new code (composability loader, override merge, IDP discovery, etc.)
-
Q2 — no
unwrapoutside tests, nounsafe, no#[allow] -
Q3 — no test deletions or weakened assertions
-
Q4 — design deviations update this plan’s Design section + file separate design-iteration issues if material
-
Q5 — only the final MR of Stage 7 closes #460
-
Q6 — out-of-scope items stay deferred (marketplace, applicant portal, mobile, print, worker-journey microspec, sign-and-authorize ceremony, notice compose)
-
Q7 — per-stage CHANGELOG + this plan’s Status row + relevant doc updates
-
Q8 — zero new TODO/FIXME tokens
Risk + Rollback
-
Risk — Stage 3 (composability runtime). Real architectural commitment touching rendering pipeline, DB schema, override merge semantics, plugin lifecycle. Mitigation: ADR-first sequencing is non-negotiable (ADR-021 + ADR-022 ratified before any Stage-3 code).
-
Risk — Stage 6 (Studio). Largest design surface; live-mode composer + Plugin Studio + onboarding wizard. Mitigation: ship Stage 6 strictly after Stage 5; if Studio churns, core surfaces remain usable without it. (Studio promote modal already descoped from v1 pending #507 — reducing Stage 6 risk.)
-
Risk — design gaps surface mid-build (worker journeys, IEVS resolution depth, sign-and-authorize ceremony, notice compose). Mitigation: file separate design-iteration issues per journey; surface them in this plan’s "Out of scope" list and resolve via independent design+build cycles.
-
Risk —
linkmeplatform portability (Stage 3).linkmedistributed slices are linker-dependent; works reliably on canopy’s Linux Alpine production target but unproven on some edge targets. Mitigation: ADR-021 documents Option B (build.rsscan) as a portable fallback shouldlinkmebite — sameCompileTimePluginSourceshape, different population mechanism. -
Rollback: revert the offending MR. Each Stage’s MRs are independent; partial-stage rollback leaves the workspace consistent.
Open decisions revisited as Stages land
Architectural decisions resolved by ratified ADRs (no longer open):
-
✅ DB override storage shape — unified
composition_documentstable (ADR-022 Decision 1) -
✅ Override merge semantics — RFC 6902 for DB layers, RFC 7396 for baseline-over-defaults (ADR-022 Decision 2)
-
✅ Override lifecycle — explicit Studio Archive (ADR-022 Decision 3)
-
✅ Audit retention — uniform 1-year for override-layer events (ADR-022 Decision 4)
-
✅ Plugin sandboxing — in-process Askama partials behind
PluginSourcetrait (ADR-021) -
✅ Plugin discovery —
#[canopy_plugin]macro +linkme(ADR-021) -
✅ Composition cache — invalidate-on-write, single-replica v1 (ADR-021)
Still open (need decisions before the relevant stage starts):
-
Marketplace
installstracking (self-reported vs central registry) — only relevant once Plugin Marketplace federation is in scope (post-v1; tied to #507’s unified config backend) -
Worker-journey microspec ownership (design vs engineering) — design-iteration issues to file per journey
Routed to design (open on #486):
-
Plugin Studio (#501) authoring scope in v1
-
Multi-jurisdiction plugin visibility (compile-time visibility vs jurisdiction opt-out)
-
i18n catalog fallback when user locale not shipped by a plugin