Contracts

symbol-key-resolver.md

Contract: Symbol-Key Resolver + Live Collision Classifier

Module: tests/architectural/_symbol_key.py (NEW; consumed by test_no_dead_symbols.py).

resolve_symbol_key(node, module_path, tree) -> SymbolKey | None

Produces the relocation-tolerant key for one __all__-declared symbol.

Span MUST cover: ClassDef, FunctionDef, AsyncFunctionDef, Assign, AnnAssign (FR-002 — the T001 gap), and ImportFrom scoped to the single aliased name (FR-004 — not the whole statement).

{name:(module,attr)} (2-tuple) and runtime _EXPORT_MODULES {name:module_const} (1-value; the 6 specify_cli.runtime::* entries the gate's _record_facade_edges skips via its len!=2 guard). Re-derive the dict-parse KEY-side to yield name → (module, attr) (needed to locate the body to hash). Reuse ONLY the two PURE helpers _find_facade_lazy_dict_name + _resolve_relative_module; do NOT reuse/edit _record_facade_edges (byte-frozen C-005, caller-graph-shaped, discards the name). Enumerate by shape, not "all 8".

  • Content tier (default): (bare_name, body_hash), module_path=None.
  • body_hash: hash of anchoring.code_tokens_by_line over the definition span.
  • Facade-dict exports (FR-003 — rescoped): handle BOTH shapes — sync _LAZY_IMPORTS
  • Undecidable: return None for any shape the resolver cannot span. NEVER guess.

Postconditions

single-alias; the spike proved ClassDef/FunctionDef).

whitespace) → identical key (NFR-001).

  • Deterministic across 3.11↔3.12 for every supported shape (DoD j proves AnnAssign +
  • Pure code motion (module move, sibling reorder, blank/comment insertion, annotation

classify_collisions(all_symbols) -> dict[str, list[Location]] (LIVE, per run — FR-005/D-2)

walk of the src/ tree. Build once per gate run, not per entry (perf).

hard-code it (a future byte-identical pair must be caught automatically).

  • Build a bare_name → [live __all__ locations with matching body_hash] index by ONE
  • A bare_name with ≥2 entries sharing a body_hash is a collision bare_name.
  • Today's collision set == the ArtifactKind trio; the classifier MUST re-derive it, not

key_tier(key, collision_index) -> "content" | "module_path" | FAIL (FR-005/FR-009/D-3)

one live location → content (relocation-proof).

locations → escalate to module_path (if module_path disambiguates) OR FAIL (fail-closed) — never silently exempt.

  • content key whose bare_name is NOT in the collision index AND resolves to exactly
  • content key whose bare_name IS a collision bare_name, OR that resolves to ≥2 live
  • None key → FAIL (fail-closed).

is_dangling(entry, live_index) -> bool (FR-008/D-4)

also trip the prune ratchet (no double-flag).

  • content-tier entry: (bare_name, body_hash) → 0 live locations → dangling.
  • module_path-tier entry: (bare_name, module_path) → no live __all__ decl → dangling.
  • A body edit produces exactly ONE signal (offender-refresh), reconciled so it does not

Non-negotiables

path** (C-007), not this module's functions in isolation.

ZERO). Build the (bare_name → [locations]) index + hashes ONCE per run over the cached _walk_modules trees (extend _walk_modules to retain source — it is NOT C-005-frozen); add a perf-budget assertion to the gate self-test so a regression shows.

  • No if key is None: <exempt> fallback ([[no_legacy_resolver_paths]]).
  • Reuse anchoring.code_tokens_by_line; do not fork a normalizer (S3776).
  • The bite battery drives assertions through the **production _compute_offenders/stale
  • Perf: the body-hash introduces a net-new tokenize pass (the current gate makes

warning-remediation.md

Contract: Warning Remediation (FR-016 / NFR-006 / SC-005)

Census (done — analysis/test-change-coupling, full tests/architectural/)

40 warnings; near-all intentional warnings.warn(UserWarning) report-only diagnostics:

Source~countClassDisposition
tests/architectural/test_migration_chain_integrity.py~13patch-skip "may warrant verification"in-mission (owned)
tests/architectural/test_gate_coverage.py1804 tests selected by ≥2 CI gates (report-only)in-mission (owned)
tests/architectural/test_template_governance_payload_contract.pya fewgovernance payloadin-mission (owned)
tests/architectural/test_charter_references_resolve.py, test_wp_prompt_build_latency.pya fewreport-onlyin-mission (owned)
tests/contract/test_example_round_trip.py:514~13legacy-contract # pydantic_model: backfilltracked follow-up (cross-package)
src/doctrine/base.py:1081invalid terminology-guard.toolguide.yaml skipped (pydantic extra_forbidden on references)tracked follow-up OR scoped fix (src-side)

Remediation rule (binding)

1. Preserve the signal, change the channel. The patch-skip / duplicate-gate / legacy-backfill diagnostics are load-bearing. Route them off the warnings channel (pytest record_property, captured log, or a report line) OR register an expected-warning with an inline rationale. Do NOT delete the diagnostic. 2. NO blanket filterwarnings = ignore. A narrowly-scoped, individually-justified @pytest.mark.filterwarnings (or filterwarnings ini entry) is permitted ONLY for a third-party warning we cannot fix at source, each carrying an inline rationale. 3. Scope boundary. Arch-side emitters (tests/architectural/*) are in-mission and owned. tests/contract/test_example_round_trip.py (legacy-backfill) and src/doctrine/base.py + the invalid toolguide YAML are cross-package/src-side: remediate in-mission ONLY if within a declared owned surface; else split to a tracked follow-up named in issue-matrix.md — never silently suppressed.

Acceptance (SC-005 / NFR-006)

warnings** from the arch suite.

entry OR a tracked src//contract follow-up in issue-matrix.md — verifiable by re-running the census.

  • Re-run the census (pytest tests/architectural/ -W default -r w): **0 first-party
  • Any residual is a documented, individually-justified third-party filterwarnings