# Routing-spec boot-invariant audit — 2026-04-27

| Field | Value |
|-------|-------|
| **Spec** | [`docs/superpowers/specs/2026-04-27-routing-control-data-plane-design.md`](../specs/2026-04-27-routing-control-data-plane-design.md) |
| **Constraint doc** | [`docs/superpowers/specs/2026-04-27-routing-substrate-attempt-history.md`](../specs/2026-04-27-routing-substrate-attempt-history.md) |
| **Generated by** | [`apps/web/scripts/audit-routing-spec-boot-invariants.ts`](../../../apps/web/scripts/audit-routing-spec-boot-invariants.ts) (tracked; runs in CI via [`.github/workflows/audit-routing-invariants.yml`](../../../.github/workflows/audit-routing-invariants.yml)) |
| **Run against** | live install, 2026-04-27T03:47Z |
| **Invariants checked** | 10 (6 routing from §8.1 + 4 pricing from §12.7) |
| **Violations found** | 3 |

## Why this exists

The routing architectural spec lists 10 boot invariants that the implementation should enforce. This audit runs each check against the *current* state of the install and reports violations *before* the spec's enforcement code lands. The output is a list of pre-existing issues that Phase A and Phase B of the migration must address.

Prior substrate attempts (per the constraint document) consistently shipped before measuring the gap they were meant to close. Running the audit on day zero is the differentiator: every violation is a tracked backlog item with a triageOutcome and effortSize before any code is written.

## Findings

### INV-6 — `BI-A58F94A2` (medium)

`ModelProfile.capabilityTier` still carries the legacy LLM-grading vocabulary in 15 rows: `'deep-thinker'` (8), `'advanced'` (4), `'moderate'` (3). Spec §8.1 requires this column to be retired (renamed to `capabilityCategory` to clarify its actual purpose as admin-UI categorization, not routing tier). The routing layer should read only `qualityTier`. Phase B of the migration is the natural place for this.

### INV-6b — `BI-6B87B3BF` (small)

Two routing source files still reference `capabilityTier`:

- `apps/web/lib/routing/known-provider-models.ts`
- `apps/web/lib/routing/recipe-types.ts`

These need an audit pass to determine whether the references are reading the value as a *routing input* (which is the bug INV-6 names) or as *admin-UI categorization* (which is fine but should be tagged for the rename). Either way, both should be addressed alongside INV-6.

### INV-9 — `BI-54411FE8` (medium)

The `anthropic-sub` provider (active subscription) is configured with `costModel='token'` and `inputPricePerMToken=0` / `outputPricePerMToken=0`. This is the misclassification documented in spec §12.2: subscription-priced providers should declare `costModel='subscription'` with a `subscriptionWindowKind`. Until that lands, the $0 token pricing means real spend (subscription quota consumed) goes uncaptured in the cost ledger — the bug Phase J just partially closed at the *token-counting* layer but cannot fully close at the *spend-attribution* layer until the subscription pricing model is added.

## Invariants that passed

- **INV-1**: every `PLATFORM_TOOLS` entry has a `TOOL_TO_GRANTS` mapping. (PR #309 fixed the 7 missing entries; PR #308 fixed those that were missing earlier.)
- **INV-2**: every active LLM provider has at least one active model profile.
- **INV-3a / INV-3b**: every `ModelProvider.status` and `ModelProfile.modelStatus` value is in the canonical enum.
- **INV-4**: every `ModelProfile.qualityTier` matches the family-tier prediction.
- **INV-5**: no duplicate `(providerId, modelId)` pairs in `ModelProfile`.
- **INV-7**: every active LLM provider has a non-null `costModel`.
- **INV-8**: token-priced providers all have non-null prices (0 is allowed for free models).
- **INV-10**: compute-priced providers all have `computeWatts` and `electricityRateKwh` set.

## Re-running the audit

The audit runs automatically on every PR and push to `main` via [`.github/workflows/audit-routing-invariants.yml`](../../../.github/workflows/audit-routing-invariants.yml). The CI job spins up a fresh Postgres, applies migrations, runs the seed, then diffs the audit's findings against this report's JSON sibling at [`2026-04-27-routing-spec-boot-invariants.json`](2026-04-27-routing-spec-boot-invariants.json). It fails ONLY when NEW violations appear — pre-existing baseline findings stay tracked as backlog items and don't block merges.

To run locally against a live DB:

```bash
DATABASE_URL=postgresql://... pnpm --filter web exec tsx \
  scripts/audit-routing-spec-boot-invariants.ts \
  --baseline docs/superpowers/audits/2026-04-27-routing-spec-boot-invariants.json
```

To regenerate the baseline (when fixing a tracked violation):

```bash
DATABASE_URL=postgresql://... pnpm --filter web exec tsx \
  scripts/audit-routing-spec-boot-invariants.ts \
  --json-out docs/superpowers/audits/2026-04-27-routing-spec-boot-invariants.json
```

Commit the updated baseline alongside the fix.

The script is read-only against both DB and code; it does not modify state. Safe to run anytime against a live install to surface drift.

## Filing follow-ups

If the script discovers new violations on a re-run that aren't already tracked, file each via:

```bash
node e2e/debug/file-audit-findings.mjs
```

(The filing helper lives in the gitignored `e2e/debug/` directory; check it out from a prior commit if needed, or rewrite — it's small. The audit script itself was promoted out of `e2e/debug/` into `apps/web/scripts/` once the CI workflow landed.)

## What this is *not*

- This audit doesn't fix anything. It surfaces the gaps and creates trackable items.
- It doesn't validate that the spec's *implementation* is correct; that's Phase A/B testing.
- It doesn't cover the *deferred* concerns from §9 of the spec (artifact provenance, capability-derived grants, MDM, build-phase state machine). Those have their own specs and own audits.
