Data Model: Relocation-Hardened Dead-Code Scanners

Test-infra "entities" — in-memory value objects the key + gate operate on. No storage.

SymbolKey (value object)

The relocation-tolerant identity produced by _symbol_key.py.

FieldTypeNotes
bare_namestrthe symbol's unqualified name (e.g. ArtifactKind)
body_hashstrhash of anchoring.code_tokens_by_line over the definition span (interpreter-independent)
module_path`str \None`
tier`"content" \"module_path"`

live __all__ location. Resolving to 0 → dangling (prune, D-4). Resolving to ≥2 → fail-closed or escalated to module_path tier (D-3).

span → fail-closed at load (FR-009). No silent exemption.

longer matches → exactly one signal (offender-refresh), reconciled with dangling (D-4).

  • Invariant: a content-tier key (module_path is None) MUST resolve to exactly one
  • Undecidable: the resolver returns None (not a SymbolKey) for a shape it cannot
  • Body-sensitivity: editing the definition body changes body_hash → the key no

AllowlistEntry

A hand-curated sanctioned dead symbol, re-keyed off module::Name.

FieldTypeNotes
keySymbolKeyreplaces the old module::Name string
rationalestrpreserved from the existing category frozenset context
categorystrone of the 19 category frozensets (e.g. GRANDFATHERED_LEGACY)

charter_activate_app / charter_deactivate_app).

  • Population: 394 live (across 19 frozensets); 2 stale dropped (FR-006:
  • Keyability invariant (DoD k): all 394 resolve to a SymbolKey (0 un-keyable).

CollisionSet (recomputed, not stored)

The bare_names whose content resolves to ≥2 live locations — rebuilt every gate run (D-2). Today's set = the ArtifactKind trio (+ any future byte-identical same-name pair). Members are denied relocation tolerance (module_path tier or fail-close).

DanglingEntry (transient, ratchet input)

An allow-list entry whose SymbolKey no longer resolves to a live __all__ declaration. Tier-specific detection (D-4):

TierDangling test
content(bare_name, body_hash) → 0 live locations
module_path(bare_name, module_path) → no live __all__ decl

→ pruned by the third ratchet direction (FR-008).

AutoExemptCategory (symbol-granular, FR-010)

Auto-derived exemptions, computed per-symbol (never per-module):

CategoryRule
registered migration@MigrationRegistry.register-decorated class only (~96 m_*.py; dead helpers still caught)
re-export shimby definition-shape (docstring/__all__-only re-export)
Typer sub-appby call/decorator parse
  • Invariant (disjointness meta-test): auto_exempt ∩ hand_allowlist = ∅.

Preserved surfaces (byte-unchanged — C-005/FR-012)

Not modified; listed so ownership treats them as read-only: known_modules, _record_*_edges, _imports_by_target (caller-graph / anti-re-blind), the 4 T004 dynamic-dispatch detectors, test-not-caller semantics, the bidirectional stale-entry ratchet + its 4 T004 tests.