# ADR-0012: Python sanctioned-fallback registry mirrors the TypeScript REGISTRY; verify-laws gate is parallel

**Status:** Accepted (P1 of console-v2-fix-build, 2026-05-04).

## Context

The TS side ships `packages/fixtures/src/fallbacks.ts` REGISTRY +
`fireFallback()` + `scripts/verify_laws/check_fallback_registration.mjs`
CI gate (Law 4 enforcement). The Python side had ZERO matches for
`FALLBACK_FIRED` / `register_fallback` / `sanctioned_fallback` across
`recoil/api/**` (audit Cluster 4). At least six unregistered silent
recoveries existed: take-id KeyError swallow, `Project.episodes=[]`
default, `list_beats` ignoring scene_id, `_load_shot` JSONDecodeError
drop, malformed JSONL skip in events adapter, `recoil.core.paths` import
literal in 4 adapters, `severity_map.get(decision, "info")` in
proposal_dispatch.

This was asymmetric enforcement of Law 4's central invariant.

## Decision

Stand up `recoil/api/sanctioned_fallbacks.py` mirroring the TS file's
public surface:
- `REGISTRY: Mapping[str, FallbackEntry]` with quality-neutral justifications.
- `emit_fallback(name, scope, payload)` raises on unregistered names.
- Per-name in-process counters (no persistence; reset on restart).
- BUS event emission with `severity="fallback"`.
- `logger.warning("FALLBACK_FIRED %s …")` token mirroring the TS
  `console.warn("FALLBACK_FIRED <name>")` for log-grep parity.

Plus a parallel CI gate `scripts/verify_laws/check_fallback_registration_python.py`
that greps `emit_fallback("…")` call sites against REGISTRY and fails the
build on drift. The gate is invoked from `pnpm verify-laws`.

The Python registry is the canonical home for fallback names that fire
inside `recoil/api/**`. Engine-side fallbacks (in `recoil/pipeline/**`)
are out of scope for this build; they get their own registry in a future
CP. Documented separately.

## Consequences

Law 4 enforcement is symmetric across the codebase. The verify-laws CI
gate prevents a future drift where someone adds a silent recovery without
registering it. Counter values surface via `/api/system-status.fallback_counts`
(P4) so the chrome can show JT how many of each fallback fired in the
current process lifetime.

Cost: ~7 names today, growing as the API matures. Each name needs a
substantive justification line. The discipline IS the point.

Cross-references: ADR-0011 (P3 hierarchy synthesis registers two names
on this substrate); ADR-0013 (chrome status pill reads
`fallback_counts` from this registry's `_COUNTERS` dict).
