Quickstart: verifying Read-Side Seam: Placement-Authority Closure

How to check this mission's work locally. Never run the full tests/architectural/ suite — it destabilises the session (C-008). Run the named gates only; CI owns the exhaustive sweep.

Inside a lane worktree always prefix with uv run (a bare python/pytest imports the primary checkout's src, not the lane's).

0. Orient

# from the repository root
git rev-parse --abbrev-ref HEAD          # expect fix/read-side-seam-primary-primitive-closure
spec-kitty agent tasks status --mission read-side-seam-primary-primitive-closure-01KYKMMT

1. Census (re-derive, never trust a written count)

# how many real call sites does a primitive have? per-file import ALIASES resolved,
# definition module excluded. The alias pass is load-bearing: `import X as _p` + `_p(...)`
# yields the call name `_p`, which a literal-name match silently misses.
uv run python - <<'PY'
import ast, pathlib
targets = {"primary_feature_dir_for_mission", "resolve_feature_dir_for_mission",
           "candidate_feature_dir_for_mission", "resolve_planning_read_dir"}
counts, files = {t: 0 for t in targets}, {t: set() for t in targets}
for f in pathlib.Path("src").rglob("*.py"):
    if f.name == "_read_path_resolver.py":       # definition module
        continue
    tree = ast.parse(f.read_text(encoding="utf-8"))
    # per-file alias map: local binding -> canonical target name
    alias = {}
    for n in ast.walk(tree):
        if isinstance(n, ast.ImportFrom):
            for a in n.names:
                if a.name in targets:
                    alias[a.asname or a.name] = a.name
    for n in ast.walk(tree):
        if isinstance(n, ast.Call):
            fn = n.func
            local = fn.id if isinstance(fn, ast.Name) else getattr(fn, "attr", None)
            name = alias.get(local, local if local in targets else None)
            if name in targets:
                counts[name] += 1; files[name].add(str(f))
for t in sorted(targets):
    print(f"{t}: {counts[t]} sites / {len(files[t])} files")
PY

Compare against the ledger's per-primitive counts — they must agree (NFR-008, SC-007).

Authority note for WP08's irreversible delete. This recipe is a cross-check, not the authority. Attribute-style and dynamic references still escape any single-pass AST counter, so for the go/no-go on deleting the public wrapper use the gate's own alias-resistant whole-tree scanner (tests/architectural/_placement_whole_tree_scan.py, consumed by test_no_read_side_bypass.py) as the authority, and treat a disagreement between the two as a stop rather than a rounding error.

2. The named gates (targeted only)

PWHEADLESS=1 SPEC_KITTY_SYNC_MINIMAL_IMPORT=1 uv run pytest \
  tests/architectural/test_no_read_side_bypass.py \
  tests/architectural/test_resolution_authority_gates.py \
  tests/architectural/test_gate_read_literal_ban.py \
  tests/architectural/test_coord_read_residuals_closeout.py \
  tests/architectural/test_trio_seam_only.py \
  tests/architectural/test_no_write_side_rederivation.py \
  -q

Do not pipe pytest/mypy through | tail — it masks the exit code. Report exit codes.

3. Step 1 (delegation) — what to expect

After the delegation lands and before any call site changes:

# the two census floors must NOT move — no call site changed
uv run pytest tests/architectural/test_resolution_authority_gates.py -q

Any other divergence must be attributed (anchoring / backfill recovery / husk / raising). The single accepted behavioural delta is the seam's bare-slug backfill recovery, which must be pinned by a test rather than absorbed (FR-003).

4. Equivalence and the edge cases

The decisive fixtures (see research.md R-01) — a real git repo with a meta.json declaring topology, then compare the blind composition against placement_seam(root, handle).read_dir(<PRIMARY kind>) for composed, bare and mid8 handles:

FixtureExpect
flat / no coordequal
coord + materialized worktreeequal (a COORD kind resolves to coord)
coord + husk (no meta.json)equal; the kind-blind resolver returns the husk
coord branch deletedequal, no raise for a PRIMARY kind; a COORD kind raises
backfilled (bare primary / composed coord)differs — the seam recovers the existing dir; the blind composition returns a non-existent path

Build fixtures under the session scratchpad, not a bare /tmp path, and remove any throwaway worktree afterwards.

5. Behaviour preservation

# the mission's own suites plus the surfaces the migration touches
PWHEADLESS=1 SPEC_KITTY_SYNC_MINIMAL_IMPORT=1 uv run pytest \
  tests/merge/ tests/specify_cli/coordination/ tests/specify_cli/status/ -q
uv run ruff check <changed files>
uv run python -m mypy --strict src/specify_cli src/charter src/doctrine   # project mode = what CI runs

Project-mode mypy is authoritative; a single-file mypy <file> spuriously reports Any for cross-package imports and must not be used to justify a cast.

6. Documentation deliverable

PYTHONPATH=. uv run python scripts/docs/check_docs_freshness.py --ci   # expect 0 errors
PWHEADLESS=1 uv run pytest tests/docs/ tests/architectural/test_no_legacy_terminology.py -q
# the frozen stores must be untouched-green (proof neither was edited)
PWHEADLESS=1 uv run pytest tests/architectural/test_glossary_pack_parity.py \
  tests/architectural/test_glossary_pack_no_regression.py -q

Regenerate the two gated registries (page inventory + docs retrieval index) after adding the page; the curated explanation index/TOC are judgement, docs/architecture/index.md is mandatory.

7. Baseline reds — classify, never green-wash

Some failures are pre-existing on this mission's base. Before treating any red as yours, reproduce it on the true mission base (git merge-base HEAD upstream/main) in an isolated worktree under the session scratchpad, and run only the specific node-ids. A lane tip is not a valid baseline — it already contains the mission's own changes.