RUL-033 — The Read tool is allowed on a module the session already read through the AST toolkit
Ruling
Mohamed, 2026-09-23 (plan PLAN-2026-09-23-production-deployable-tools.md,
ruling R9): the Read tool is permitted on a Python file whose module the
same session has already read through scripts/serialize_codebase_ast.py source <name>. Everything else in the 2026-08-02 mandate ("ZERO
hand-reading of Python source — the AST toolkit serves everything") stands:
grep, sed, head, cat and the other text tools stay denied on this
repo's src/, scripts/ and tests/ Python, and a Read that no source
call preceded is denied exactly as before.
Why
Edit and Write refuse a file the session has not Read. Measured 2026-09-23
on two swarm tasks (U-T3, U-T8a): both agents pulled the exact bytes through
source, drafted the patch, and could not land it — Bash redirects, rm,
mv and cp over the target were each refused by a sibling guard. The only
satisfying action was impossible, which is the defect the guard's own header
names: such a guard blocks work and teaches the operator to route around it.
The working method that had grown up (build in the scratchpad, diff against
git show HEAD:<path>, copy over) was exactly that route.
Enforcement
- hook .claude/hooks/force-ast-toolkit.sh
- hook plugins/orchestrata/project-hooks/force-ast-toolkit.sh
Mechanism (RUL-014)
orchestrata-ultimate 1395825c4, .claude/hooks/force-ast-toolkit.sh and
its plugin mirror; ADR-0063 in the project register.
- The Bash branch records every
serialize_codebase_ast.py source <name>argument into${XDG_RUNTIME_DIR:-/tmp}/orchestrata-project-hooks/<session>.ast-read-allow, keyed by the payload'ssession_id(an unsafe id records nothing). - The Read branch allows a
.pywhose stem is a dotted segment of a recorded name;source deploy.test_ship._orchestrata_repounlockstests/deploy/test_ship.pyfor that session and nothing else. - Guard tests:
tests/scripts/test_force_ast_toolkit.py(unchanged, 47) andtests/scripts/test_force_ast_toolkit_read_allowance.py(8: allowed after source; per module; per session; no session id → nothing; text tools still denied; memo location; unsafe session id).
Consequences
- A builder that must Edit an existing module runs
sourceon one of its symbols, then Reads and Edits it. The scratchpad-and-copy method is retired. - The plugin copy of the hook goes live only with a ship
(
install-claude-config.shrefuses unpushed commits); a hook change is sequenced before a swarm wave, never during one. - Rejected: exempting
tests/from the guard — it would have restored hand-reading for the largest part of the tree and leftsrc//scripts/deadlocked.