← all tickets

19 — Contract: retire the port scaffolding

What to build: Registries become the only dispatch path — the match-arm fallback is removed. The hand-maintained registry mirror and its frozen-fixture parity test are deleted (their port-guarding job is done, per ADR-0002); anything that still consumes a registry manifest reads live registry introspection instead. After this, adding a card touches one block, one test, one manifest flag.

Blocked by: 17 — Migrate: System Gateway cards; 18 — Migrate: Elevation + vp cards.

Status: fixed

Comments

Implementation notes

The fallback was already gone — 03 emptied the last dispatcher — so this ticket is the deletion plus the introspection that replaces what the deleted files were checking.

Deleted outright:

the 45 Python registries) and its pub mod line

rust/netrunner-core/registry_manifest.json — the bin existed only to feed the Python parity test

Introspection. Registry::inventory() returns `(registry name, sorted codes) for all 46 registries, read off the live maps; registered_codes()` flattens it to "every card this engine implements". The [Registry] struct is now declared by a small registries! macro that generates the fields *and* inventory() from one list, so a new hook kind cannot be added to the struct and forgotten in the introspection — which matters because 05's honesty checks trust the inventory to be complete. Registry names are the struct's field names (on_play, playability_gate, …), the snake_case forms of the game/effects.py names the card comments still cite.

Python reads it through a new netrunner_engine.registry_inventory() (rust/netrunner-py/src/registry.rs) returning dict[str, list[str]]. Rust never parses the pack manifests (ADR-0001), so 05's checks run Python-side and this is the bridge they need; tests/test_registry_inventory.py (3 tests) guards the bridge and its shape in the meantime.

Tests. cards/registry.rs's every_manifest_registration_is_in_the_registry walked the deleted manifest, so it became inventory_reports_every_registration: same guard numbers (46 registries, 133 registrations, 72 cards = 57 sg + 8 elev + 7 vp) taken from the live inventory instead, plus a sorted/de-duplicated check on every registry's codes. It is a drift guard, not a truth check — with no fallback arms left, a deleted or never-called block shows up as a smaller count. Tying those codes to the manifests' implemented flags is 05's job.

Rust core unit tests go 56 → 53 (the three tests inside registry_manifest.rs went with it); the Python suite goes 136 → 93 (test_registry_parity.py was parametrized over 45 registries, so it was 46 of those, and 3 new ones land).

Doc pointers repointed rather than dropped: hooks.rs and cards/registry.rs module docs, server/serialize.py's MU_BONUS/HAND_SIZE_BONUS source-of-truth comment (now cards/*.rs), claude_todo.md's where-things-live/tests paragraphs, and docs/adding-a-card.md's honesty-check section, which now names registry_inventory() as the entry point. ADR-0002 and PORT-DELTA.md are left alone — they describe the port as history, and their prediction ("deleted once the registries are introspectable") is what just happened.

Verified (2026-07-30)

-- -D warnings`: clean.

test_registry_inventory.py.

Wildcat Strike's registration: fails with 132 ≠ 133.

ignoring the timing lines — expected, since nothing in the dispatch path changed, but cheap confirmation that the macro-declared struct and the pack registration order are unchanged.