# Plugin ring: product families as Cargo features

Status: surface gate landed 2026-09-17 (unverified by a local build — see
"Verification" below). Audit and rationale:
`claudedocs/LEANER_CORE_PLUGIN_AUDIT_2026-09-17.md`.

`hsm_console` used to mount 145 sub-routers and register 139 tools
unconditionally. The plugin ring splits that surface into a **core** and
fifteen **product families**. Each family is a Cargo feature; `full` (the
default) enables all of them and is byte-for-byte the composition that shipped
before the families existed.

```text
cargo build --bin hsm_console                                   # full  (default)
cargo build --bin hsm_console --features full,otlp-telemetry    # the runtime image
cargo build --bin hsm_console --no-default-features             # core only
cargo build --bin hsm_console --no-default-features --features flywheel,passkeys
```

Machine-readable map: `config/company-os-plugin-ring.v1.json`.
Contract: `node --test scripts/plugin-ring-contract.test.mjs`
(CI: `contracts-and-console`); core-only compile: CI job `core-only-composition`.

## What a family gates

| Site | How |
|---|---|
| Routers | `src/company_os/http_api/families.rs`: one `#[cfg(feature)]` / `#[cfg(not(feature))]` pair per family. The core chain in `http_api::router()` mounts `families::<family>_routes()` and must never merge a family module directly. |
| Tool registrations | `src/tools/mod.rs::register_all_tools`: a family's tools are HTTP clients of that family's routes, so they register only with the family — a core-only build never advertises a tool whose endpoint it does not serve. |
| Boot hooks | `src/bin/hsm_console.rs`: desktop adapter registration, fleet heartbeat, external-adapter and bead-correlation reconcilers. |
| Binaries | `required-features` on family-owned entrypoints (`economics_backfill`, `hsm_vcs_broker*`). |

What a family does **not** gate yet: the `mod` declarations. Every family
module is still compiled in every configuration. That is deliberate for the
first slice — the gate is correct by construction under `full` and the
core-only build cannot fail to compile — but it means the compile-time and
link-time win is not yet realised. Promoting a family to a module-level gate
means cutting its inbound seams (audit §3.7) with a compiler available and
recording the change in the map's `gate_level`.

The first partial module gates are now recorded in `module_gates` in the ring
manifest: `decision_calibration`, `decision_action_bindings_http`, and
`decision_outcomes_http` are compiled only with `decision-calibration`.
`decision_episodes_http` remains available because the self-improvement
family reuses its idempotency and registration helpers. This is a partial seam,
so the family remains at `surface` gate level until the shared helper is split
into a core-owned module and every family module can be gated independently.

## The families

| Feature | Ring | Modules (`src/company_os/`) | Also gates |
|---|---|---|---|
| `desktop-control` | R2a | `desktop_control`, `web_run_api` | `company_web_run` tool; desktop adapter boot hook |
| `openapi-connectors` | R2a | `openapi_connector`, `connector_oauth` | |
| `passkeys` | R2a | `webauthn_api` | |
| `fleet-telemetry` | R2a | `fleet_telemetry` | heartbeat boot hook |
| `email-control-plane` | R2a | `email_control_plane`, `customer_intake` | |
| `beads-bridge` | R2a | `bead_outbox`, `bead_correlation` | correlation reconciler boot hook |
| `external-adapters` | R2a | `external_adapter`, `adapter_protocol`, `executor_bridge`, `gateway`, `trigger_delivery` | adapter reconciler boot hook |
| `vcs-broker` | R2a | `vcs_broker`, `vcs_backend`, `vcs_workspace_ledger`, `vcs_rewind` | `hsm_vcs_broker`, `_probe`, `_supervisor` binaries |
| `flywheel` | R2b | `flywheel_selector`, `flywheel_dynamics`, `flywheel_calibration`, `flywheel_context` | |
| `self-improvement` | R2b | `self_improvement`, `trace_skill_promotion`, `store_promotion`, `promotion_registry`, `context_map_promotion`, `improvement_episodes_http`, `quality_ratchet`, `dsh_adaptation_catalog` | |
| `review-intelligence` | R2b | `review_delivery_store`, `review_artifacts`, `review_calibration_store`, `review_intelligence`, `review_maps`, `review_readiness_store`, `review_annotation_store` | |
| `decision-calibration` | R2b | `decision_calibration`, `decision_episodes_http`, `decision_outcomes_http`, `decision_action_bindings_http` | |
| `scenario-lab` | R2b | `scenario_lab`, `company_resilience`, `company_navigator`, `workflow_economics`, `outcome_radar`, `outcome_quality` | `company_scenario_run` tool; `economics_backfill` binary |
| `radars` | R2b | `paperclip_parity`, `investor_demo`, `dead_star`, `sentinel`, `board`, `gepa_service`, `intelligence_signals`, `intelligence_operator`, `goal_graph`, `risk_posture`, `handoff_brief`, `ccir_registry`, `business_data`, `enterprise_context`, `db_intelligence` | |
| `execution-lanes` | R2c | `rlm_tools_bridge`, `rlm_repl`, `code_mode_driver`, `code_mode_session`, `ttsr_runtime`, `ttsr`, `run_fork_execution`, `run_fork`, `trace_router`, `hitl_scheduler`, `validation_broker` | `company_validation_*` tools (5) |

Rings: R2a wraps an external system (plugin by transport); R2b reads ledgers
and proposes, owning no authority (plugin by effect class); R2c is an alternate
way to run a turn (plugin by adapter). Everything else — identity, tenancy,
tasks, runs, agents, admission, receipts, memory, tool registry, MCP registry,
extension host, workspaces, policy, migrations — is the core and is not a
feature.

Families are flat on purpose. If enabling one family alone fails to compile
once module gates exist, that is a seam to cut, not a feature dependency to
declare.

## Rules

- A new product family is: a feature in `Cargo.toml` (listed in `full`), an
  entry in `config/company-os-plugin-ring.v1.json`, a router pair in
  `families.rs`, and gates on its tools and boot hooks. The contract test
  fails on any half of that.
- The core chain never merges a family module. The contract test fails on a
  leak.
- `--no-default-features` is the core-only composition. Operators never get
  it from `scripts/company-os-up.sh` (the launcher's opt-out flag keeps
  `full`); the runtime image names `full` explicitly.
- Legacy, research and optional-integration features (`legacy-roodb`,
  `research-tools`, `email-imap`, `wasi-plugins`, …) are not families and
  are never in `full`.
- A family module cannot receive Postgres access or bypass admission,
  receipts or settlement by being a family — the universal extension
  contract's boundary still applies when a family leaves the process.

## Verification

Landed on a workstation with ~1 GB of disk, so no `cargo` command ran.
What was checked:

- `rustfmt --check` parses cleanly for `families.rs`, `http_api.rs`,
  `hsm_console.rs`, `tools/mod.rs` (the only rustfmt diffs in `tools/mod.rs`
  belong to a different uncommitted change).
- `node --test scripts/plugin-ring-contract.test.mjs`: 6/6.
- `node --test scripts/architecture-extraction-contract.test.mjs`: the
  `default`-must-not-reach-`legacy-roodb` traversal passes with
  `default = ["full"]`; the A8 catalog test was already failing before this
  change (three binaries missing from the catalog).

Still to run, in order: `cargo check --bin hsm_console` (full),
`cargo check --bin hsm_console --no-default-features --features otlp-telemetry`
(core), `cargo test -p hyper-stigmergy --test company_os_routes`, then
`cargo bloat --release --bin hsm_console` for both to record the surface win.

## Next slices

1. Module-level gates, one family at a time, starting with the ones whose
   only inbound edge was the router merge: `decision-calibration`,
   `flywheel` (one seam in `admission_control.rs`), `email-control-plane`,
   `fleet-telemetry`, `desktop-control`. Measure `check --tests` memory
   before and after.
2. Browser tools → a platform MCP stdio connector, digest-pinned and
   promoted at bootstrap (audit §4B).
3. One reference plugin that depends on `hsm-extension-sdk` only (audit §4C).
4. `GET /api/company/capabilities` so the Console hides pages for absent
   families (audit §4E).
