← all tickets

20 — Honesty checks (CI)

What to build: The pack manifests can no longer lie. A parity test introspects the live registries and enforces the three checks decided in the design session: (a) every card code registered in any hook registry is marked implemented in a pack manifest; (b) every implemented card either registers hooks or carries an explicit vanilla: true flag — explicit vanilla flags get added to the manifests as part of this; (c) every implemented non-vanilla card has a card_<code>_* test, except cards on a shrink-only grandfather list seeded from today's untested cards.

Blocked by: 19 — Contract: retire the port scaffolding.

Status: fixed

Comments

Implementation notes

tests/test_card_manifest_honesty.py (16 tests) is the parity test. It reads three inputs: the pack manifests, netrunner_engine.registry_inventory() (04's bridge), and a scan of rust/**/*.rs for #[test] fn card_<code>_*. Python side per ADR-0001 — Rust reports what it registers, Python compares.

Each check is a pure function of (entries, registered, tested) returning the offending codes, so the "…fails CI" half of every bullet is exercised against synthetic entries in the same file rather than only asserted to pass against today's manifests. Six real-data checks, seven synthetic negatives, three list-hygiene checks.

(b) and vanilla. No card in sg/elev/vp is vanilla — all 72 implemented cards register hooks, and none of the 225 manifest entries has ability-free NRDB text — so there were no vanilla: true flags to add. The flag is supported and enforced in both directions (implemented-without-hooks needs it; a card carrying it may not register hooks), and only an explicit true counts: absent and false both read as non-vanilla. The first genuinely vanilla card will be the first to carry it.

(c) and the grandfather list. tests/fixtures/untested_cards_grandfathered.json holds the 71 implemented cards that have no card_<code>_* test today (everything except Seamless Launch, 01's pilot). Three properties hold it:

list, which is the mechanism by which it shrinks;

exceed it.

Honest about the limit: nothing can stop someone editing the list file itself, and once it has shrunk the count cap has that much slack. What is machine- checked is that a new untested card never passes silently — it takes a deliberate, reviewable edit to a file whose header says shrink-only.

has_tests was lying. 30030 (Sure Gamble) and 30075 (Hedge Fund) were has_tests: true, pointing at tests/cards/sg/test_*.py — Python card tests deleted at the port cutover (982beb5); 30040, the one card that does have tests, was false. Since the flag is displayed in the card browser and is now derivable, a fourth check pins it to the source scan and the three flags are corrected. Not in the ticket's three checks, but "the manifests can no longer lie" does not stop at the three.

Doc pointers: docs/adding-a-card.md's honesty-check section names the test file and the grandfather list and spells out the four things landing a card touches; cards/registry.rs and test_registry_inventory.py stop forward- referencing "issue 05" and name the test.

Verified (2026-07-30)

warnings, cargo test --workspace` (53 core + 5 AI invariants): all clean.

afterwards):

(30040 must be found), so a regex that silently matches nothing cannot make check (c) pass by default.