Contracts

read-side-gate.md

Contract: Structural Read-Side Gate

Concern: IC-06 · Requirements: FR-005, FR-006, NFR-003, NFR-004

New test module tests/architectural/test_no_read_side_bypass.py, mirroring the structural write gate test_no_write_side_rederivation.py.

Scope

non-sanctioned src/*/.py set. Do NOT fork the walk (NFR-003).

scan_scope() object.

  • Reuse tests/architectural/_placement_whole_tree_scan.py::scan_scope() — the
  • A symmetry meta-test asserts the read gate and write gate consume the same

Finding

or resolve_planning_read_dir (bare Name or Attribute.attr).

analog needed).

  • Walk ast.Call; flag any callee resolving to candidate_feature_dir_for_mission
  • Callee identity IS the finding (reads have no ref arg; no "seam-derived"

Exemptions

and the infra self-consumers per the classification ledger — asserted sanctioned (mirror resolution.py's treatment), not silently skipped.

content-descriptor entries (_ratchet_keys.resolve_descriptor) with a required rationale. Shrink-only: a staleness twin-guard reds until a routed entry is deleted (FR-006, NFR-004). No file-scoped blanket exemptions (C-003).

  • Sanctioned modules: _read_path_resolver.py (the primitive authority),
  • Allow-list: genuinely-deferred / must-stay-lenient residuals as

MUST pass

fixture reds; a prose/docstring mention stays green.

documented, shrinking allow-list (gate seeded-red / lands last per C-002).

  • Bite test: a planted candidate_feature_dir_for_mission(root, slug) in a
  • Non-vacuity: every allow-list entry proven live by the staleness twin-guard.
  • Green with the migration: after IC-02..IC-05, the gate is green with a

MUST NOT

file-scoped exemption. Ship a vacuous allow-list entry.

  • Fork a second tree walk. Introduce a second read authority. Use a blanket