feat: Worker portal Stage 2 ADR — composability runtime + plugin model

Description

Ratify the composability-runtime architecture as an ADR before any Stage 3 runtime code lands. This ADR defines the runtime composition loader, the plugin manifest schema, role/jurisdiction/user override merge semantics, and the plugin sandboxing posture. ADR-only — no runtime code.

The architectural shift is real: three composition surfaces (dashboard, case-detail, identity) all follow the same composition + override pattern, with jurisdictions editing TOML in rulesets/{jurisdiction}/ rather than forking canopy. This ADR locks the contract in durably.

Acceptance Criteria

  • docs/modules/ROOT/pages/adrs/adr-NNN-composability-runtime-and-plugin-model.adoc (NNN per current ADR sequence; coordinate with #484 which earmarked ADR-021 for CHANGELOG conventions)
  • ADR documents:
    • Composition loader contract (input: TOML composition file + role + jurisdiction; output: rendered template tree)
    • Plugin.toml schema: [plugin] (slug + name + version + author + license + canopy_min), [plugin.exports] (panels + case_sections), per-panel config (display_name + icon + programs + default_span + allowed_spans), [data] (source + auth + cache_ttl + timeout + endpoints), [permissions] (required_roles + audit), [i18n] (default + catalogs)
    • Validation rules: slug unique in catalog; semver; programs are known program slugs; allowed_spans ⊆ {1..12}; required roles exist
    • Sandboxing posture: same-process Askama partials vs out-of-process; explicit decision with rationale
    • Plugin lifecycle: discovery, validation, load, hot-reload (dev) vs deploy-time (prod)
  • Antora nav updated to list the new ADR
  • Cross-referenced from worker-portal-redesign.adoc Design section
  • cargo xtask check-docs clean

Open questions (resolve in ADR)

  • Plugin sandboxing: do plugins run in-process (trusted, fast) or out-of-process (sandboxed, slower)? V1 likely in-process.
  • Plugin discovery: filesystem scan vs explicit manifest registry?
  • Plugin versioning relative to canopy core (canopy_min field) — how is this enforced at load?

Context & References

  • Tracking issue: #460
  • Epic: &51
  • Plan: worker-portal-redesign.adoc, Stage 2
  • Blocks: Stage 3 child issues
  • Origin: 2026-05-14 design package HANDOFF.md Plugin.toml reference schema

Labels

type::feature, priority::medium, program::infrastructure, service::web, workflow::ready