Orchestrata Ultimate › Workflow
The development workflow — from idea to production
This page describes the path a change takes in orchestrata-ultimate as it runs on
2026-10-23. Every step names the file or command that implements it, so you can check
the description against the tree. The sources are the tracked git hooks
(scripts/hooks/pre-commit, pre-push, commit-msg), scripts/gate.py,
scripts/run_tests.py (read through the AST toolkit), the user-level hooks and launchers
in ~/.claude and ~/bin, the rulings in /rulings, and the push logs of 2026-10-22.
Where a number has no recorded source, the page says "not measured".
The gates mentioned here each have their own page. The catalogue is Gates — master index. The improvements that produced today's shape are logged on Workflow improvements, and the bugs found along the way are told on Bug fixes.
Contents
- The whole path at a glance
- Roles: who writes what
- Briefs and the dispatch gate
- Worktrees
- The worker's stop gate
- The commit gate
- The push gate
- Deploy
- Observation: never wait blind
- Worked example: the item-3 push, 2026-10-22
The whole path at a glance
A change starts as a request from Mohamed. The host Claude turns it into a brief, a worker model writes the code in its own worktree, and the code then has to pass four mechanical walls before it reaches production: the worker's stop gate, the commit gate, the push gate and the deploy verification. Each wall runs the same checks the next one would, as early as it can, so a defect is met by whoever can still fix it.
1. Roles: who writes what
The ruling. RUL-041
(Mohamed, 2026-10-22, recorded as
ADR-0125)
says the host Claude "writes no code when working on my project just perfects the briefs
checks and workflow with the flagship model while measuring sonnect and qwen and deepseek
performance and workflow improvements". Code is anything executed, imported or loaded:
source, tests, scripts, hooks, systemd units, pyproject.toml and uv.lock, terraform,
CI. ADRs, rulings, briefs, plans, docs and wiki pages stay with the host Claude.
There is ONE delegation route — RUL-042 (Mohamed, 2026-10-24): "remember our flow hermes and deepseek provider and deepseek version 4". Updated 2026-10-24. Until then this table listed three routes (Sonnet builders, Hermes on Qwen or DeepSeek, pi-task). That ambiguity made kimi and the host Claude dispatch Sonnet and deepseek-flash on 2026-10-24 against Mohamed's explicit instruction.
| Role | Who | What it does | Where it runs |
|---|---|---|---|
| Coordinator | Host Claude or kimi, flagship model (RUL-041: writes no code) | Writes and corrects briefs, checks every result by execution, reviews the code for what tests miss, commits by path, ships, measures each dispatch | The user's session on the host |
| Worker | Hermes, provider deepseek (DeepSeek API direct), model deepseek-v4-pro: hermes-task run --cwd <wt> --provider deepseek --model deepseek-v4-pro --brief <brief.md> --name <run> --detach |
Writes code, tests, scripts, units or diagnoses to a committed brief. The brief's files block is the write allowlist; its checks block is run by the harness into verdict.json; "the model's claims never set" the verdict |
/opt/hermes, against a provisioned wt-* worktree off origin/master |
Not used for delegated work unless Mohamed names it for that task:
- Sonnet (Agent-tool
builder/wirer/refactorer/test-writer/auditor,claude -p,dispatch.py run sonnet); - Qwen;
deepseek-flash;dispatch.pyand pi-task;- Kimi as an executor.
The step-by-step flow (worktree → brief → dispatch → verify → land), with the exact commands, is on the RUL-042 page.
Two cautions:
- Claude Code's hooks do not run inside Hermes. A brief that changes
.pytherefore carries ruff, black, trace-lint and the module's tests in its checks block. - Hermes runs are not in the Agent-tool dispatch ledger (memory
dispatch-ledger); their record is/opt/hermes/runs/<run-id>/result.json. Itsanswered_bymust showdeepseek/deepseek-v4-pro;fell_back: truemeans the OpenRouter fallback answered.
Measurement (historical, Agent-tool route; superseded by RUL-042). Every finished Agent-tool worker was appended automatically to
~/.local/state/codetools/dispatch-ledger.ndjson by the user-level SubagentStop hook
~/.claude/hooks/dispatch_ledger.py (model, start, end, wall_s, tool_calls,
output_tokens, peak_context, the brief's first line, the last message). On
2026-10-23 at 04:44 UTC it held 41 rows: 27 finished, 14 stopped (jq count). The
rendered table is the Dispatch ledger.
2. Briefs and the dispatch gate
A worker sees only its brief, so the brief carries everything: the measured state, the
decided design, the exact VERIFY command that has already run green somewhere, the traps
earlier runs hit, a run budget and the exact files to stage (memory better-briefs,
2026-10-22). Briefs are committed under plans/briefs/; the skeleton and the guard corpus
every brief restates are in plans/briefs/TEMPLATE-sonnet-agent.md (committed in
d9bcbb7cc, 2026-10-22). See Briefs.
Two PreToolUse hooks of the orchestrata plugin (plugins/orchestrata/hooks/hooks.json)
gate every Agent dispatch. Details are on
Gate: dispatch-brief.
task_context_hook.py(matcherAgent|Edit|MultiEdit|Write|NotebookEdit). ItsREQUIRED_FIELDSareGOAL:,FILES:,CONSTRAINTS:,DONE WHEN:,VERIFY:(module constant, read through the AST toolkit). A brief without them is refused with the template. A brief that passes is rewritten throughupdatedInput: the hook categorizes the task with the sharedworkflow_context_inject.DOMAINS, appends the=== WORKFLOW METHODOLOGYblock and, for each module the brief names, a=== MODULE CONTEXTblock with the module's full AST and its Prolog-inferred dependency contracts. For thedevelopmentandtestingcategories the AST is mandatory (AST_REQUIRED_CATEGORIES). A per-session hash ledger under$XDG_RUNTIME_DIR/claude-inject-ledgerstops the same block being injected twice.require-design-before-build.sh(matcherAgent) enforces RUL-022: abuilder,wirerorrefactorerdispatch passes only with a line that startsDESIGN_WAIVER: <reason>, an existingplans/contracts/path, or a ConstructionArtifact thatfactory_decision.py decideaccepts. A ratified ADR is enough design authority for bounded work (ADR-0048).
These gates bite in practice. The improvement log records two refusals for a label
written as FILES (read-only context; ...): instead of the literal FILES: (D16, D19,
2026-10-22), and while this page was being written its own first three fork dispatches
were refused with "Agent brief is missing required field(s): GOAL:, FILES:,
CONSTRAINTS:, DONE WHEN:, VERIFY:" (2026-10-23, about 04:50 UTC).
Before dispatch the coordinator also runs the template's pre-dispatch check: every sha
equals git rev-parse origin/master now, every FILES path exists, every VERIFY binary
exists, every fleet the work touches was probed this session, and nothing a VERIFIED
number depends on changed since it was measured (D14: restoring plans/ast-ultimate.json
after measuring made the worker's --affected run refuse).
3. Worktrees
Every feature branch gets its own worktree with a full private environment
(RUL-021). The tool is scripts/worktrees.py,
documented by the worktrees skill (.claude/skills/worktrees/SKILL.md):
.venv/bin/python scripts/worktrees.py create <name> --base origin/master # ~/workspace/wt-<NAME>, branch feat/<name>
.venv/bin/python scripts/worktrees.py provision ~/workspace/wt-<NAME> # re-provision or finish a failed create
.venv/bin/python scripts/worktrees.py list # merge, dirty, ahead/behind
.venv/bin/python scripts/worktrees.py gc [--apply] # remove merged + clean wt-* only
What create does, per the skill: it branches off master or --base (use
origin/master for delegated work, because unpushed local commits are invisible to a
delegate); copies .env from the primary; hardlinks vendor/wheels/*; provisions a
private .venv with uv sync --dev inside the worktree (never a symlink); proves source
provenance and that the GIL is off; and installs with UV_LINK_MODE=copy, because uv's
default hardlink mode once rewrote the primary venv's console-script shebangs. gc
removes a worktree only when a --no-ff merge commit in master has the branch tip as a
parent and the tree is clean. gc --apply --quiet runs at SessionStart
(worktree-gc.sh).
Known defects, recorded 2026-10-22 in the session notes: provision run from inside the
worktree fails with SameFileError (it copies .env onto itself), and provision does
not source env/toolchain.env, so the janus-swi build fails with SWIPL=None. Two
worker stops that day were caused by a worktree with no .venv (memory better-briefs).
The checkout must also be a non-editable install. .envrc line 47 exports
UV_NO_EDITABLE=1 so a bare uv sync in a direnv shell installs into site-packages, but
the worktrees have no direnv (brief template guard corpus). A plain uv run from a hook
then re-syncs the venv editable, and the push gate's source-provenance check refuses
(see the worked example).
4. The worker's stop gate
Until 2026-10-22, a worker's DONE WHEN ran only ruff and py_compile; the blocking review
first ran at git commit, after the worker had handed back. Mohamed: "you keep being
blocked on lint every time ... when will you ever fix that" (memory
commit-gate-precheck). The fix is two user-level pieces
(RUL-035):
~/bin/precommit-check <repo> [file…]runs the exact commit gate without committing. It puts the checkout's.venv/binfirst on PATH, runsruff check --fixon each file, copies the index to a temporary file, stages the files into that copy underGIT_INDEX_FILE, and runs.venv/bin/python scripts/gate.py review-staged --repo-root <repo>with PYTHONPATH unset. The real index is never touched. It printsPRECHECK-OKorPRECHECK-FAIL <rc>. A repo withoutscripts/gate.pypasses trivially; a repo without.venv/bin/pythonfails, as the commit hook would.~/.claude/hooks/precommit_gate_subagent.sh, registered underSubagentStopin~/.claude/settings.local.json. It reads the worker's transcript for the.pyfiles it wrote with Edit, Write or MultiEdit, groups them by repo, runsprecommit-checkper repo (timeout 240 s), and on a failure returns{"decision": "block", "reason": ...}with the last 40 lines of findings, so the worker fixes them while it still has the context. It blocks at most 3 times per agent id (counter in~/.local/state/codetools/precheck-<id>.n) and logs to~/.local/state/codetools/precheck-hook.log.
The brief template's DONE WHEN carries ~/bin/precommit-check <worktree> <files> →
PRECHECK-OK. Full page: Gate: commit-precheck.
A caution from 2026-10-23: deploy/install-claude-config.sh mirrors the repo's
claude/hooks/ into ~/.claude/hooks/ with rsync --delete, which deleted
precommit_gate_subagent.sh, dispatch_ledger.py and codemap_refresh.sh while the
settings still registered them. They were restored from the installer's backup; the
durable fix (commit them into claude/hooks/) is open (memory
claude-config-deploy-deletes-user-hooks).
After the stop gate, the coordinator runs the template's post-dispatch check: the fence
(git status --short and git diff --stat <base> equal the brief's Create and Modify
lists; the pre-commit hook adds reports/precommit-walls.ndjson to every commit), a
re-run of the brief's VERIFY, and git log --format='%an %ae%n%B' showing Mohamed only.
5. The commit gate
The tracked hooks live in scripts/hooks/ and are wired with a relative
core.hooksPath = scripts/hooks by scripts/hooks/install-git-hooks.sh, so each worktree
runs the hook version its own branch contains. Before 2026-07-27 every worktree ran one
untracked .git/hooks/pre-push (header of scripts/hooks/pre-push).
git commit runs two hooks: pre-commit, then commit-msg.
scripts/hooks/pre-commit, in order (read 2026-10-23 on branch
feat/maps-via-codetools at 8a2cb42ad):
- Stage-walls ledger. An EXIT trap appends one JSON line per commit attempt to
reports/precommit-walls.ndjsonwith the wall of each stage and each reviewer (ADR-0101 rule 2). Measurement only; a passing commit stages its own line. - Secrets gate.
scripts/check_staged_secrets.py --repo-root <repo>scans the staged blobs for a credential-shaped assignment with a non-placeholder value and exits 1 on one. → Gate: secrets - If no
.pyfile is staged as added, copied or modified, the hook refreshes the codebase maps (step 7) and exits 0. - Interpreter check. No
.venv/bin/pythonin the checkout blocks the commit (RUL-021: "a checkout without one cannot review, so it cannot commit"). - Ruff auto-fix.
uv run --no-sync ruff check --fix <file>on each staged.py, thengit addit again, so the review sees the bytes being committed. Never blocks. (--no-syncis in8a2cb42ad, not yet pushed;origin/masterata4cbc2bdfstill runs a plainuv runhere.) - Review.
PRECOMMIT_WALLS_FILE=<tmp> .venv/bin/python scripts/gate.py review-staged. The staged paths and each reviewer's report become Prolog facts andsrc/orchestrata_ultimate/prolog/commit_review.plderives the verdict: exit 0 pass, 1 fail (blocking findings), 2 invalid ("no verdict is not a pass"). Code review (scripts/code-review.py) coverssrc/and blocks at ERROR; test review (scripts/test-review.py, rules T1–T21) covers test-named files undertests/,end2end/andscripts/, plus every stagedscripts/*.pyfor T21, and blocks at WARNING and up. → Gate: review-staged - Codebase map refresh. For a commit that stages Python (added, copied, modified,
renamed or deleted),
.claude/hooks/inject-codebase-map.shregeneratesdocs/CODEBASE_MAP.mdanddocs/CODEBASE_MAP_FULL.mdthrough/opt/codetools/bin/codetools ast map <repo> --with-full(per-file delta cache), withscripts/gen_codebase_map.pyas the fallback, and stages both maps (RUL-030). A failure prints one WARN line and never blocks.
scripts/hooks/commit-msg then runs python scripts/check_ai_attribution.py --message-file "$1": an AI-attribution trailer, a Claude-Session: line or a "Generated
with" marker in the message blocks the commit
(RUL-007). →
Gate: ai-attribution
Measured walls. The ledger holds 484 lines from 2026-10-14 01:41 UTC to
2026-10-23 04:04 UTC. Of the 244 lines where the review stage ran, the median
review_staged_s is 0.18 s (most such commits staged no file a reviewer covers), the
maximum 123.3 s, and 14 attempts were blocked. The three newest commits that ran a
reviewer took 7.4 s, 8.2 s (blocked by test review) and 33.2 s. The secrets stage median
is 0.13 s and the ruff stage median 0.11 s. The map refresh is not in the ledger.
6. The push gate
git push runs scripts/hooks/pre-push, which delegates test selection to
scripts/run_tests.py --affected <range> and from there to scripts/affected_tests.py.
What the hook itself does, in order:
- Protected-branch guard. It refuses to run the suite from a worktree with
masterormainchecked out. A suite run from such a worktree once landed 16 commits on master, one of which reached GitLab (854bc6d97, 2026-07-27). - Already-verified skip. If
reports/.ship-verifiednames this exact source id (a sha256 over the tracked files outsidereports/plus the untracked names) andreports/run-tests-latest.jsonis green at the stamped commit, the suite is skipped. This mirrorsdeploy/ship.sh's run-evidence reuse. - Base check. No
origin/mastermeans no bounded blast radius, and the hook refuses rather than run the full suite (RUL-040). - Range.
origin/master...<sha being pushed>, read from the ref update git passes on stdin, so a chunked push verifies each chunk (e27c12b50, 2026-10-21). An empty diff exits 0. - Run
.venv/bin/python scripts/run_tests.py --affected "$RANGE"(the venv python directly since949c1628c, 2026-10-21, becauseuv runre-synced the package editable). A green bounded run stampsreports/.ship-verifiedso a re-push of identical source skips.
What run_tests.py --affected does, in order (_main, read through the AST toolkit):
| Step | What | Gate page |
|---|---|---|
| 0 | Freshness guard (scripts/freshness_guard.py): AST runtime merge, Prolog KBs, durations sink, observer registry, packer cache, runtime capture. Ruling 2026-08-04: "no staleness, every run is fresh" |
freshness-guard |
| 0.5 | Prune stale pytest basetemp dirs (the tmpfs-full cascade of 2026-08-05) | — |
| 0.75 | Affected resolution (_resolve_affected → affected_tests.py --json), moved before infra on 2026-10-21 (68024751d). Verdict bounded, named_full or refused; a refusal aborts unless --affected-decision names a human decision. If every changed file is certified inert, infra, gates and the smoke floor are skipped and the run passes |
affected-selection |
| 1 | Infrastructure report (scripts/start_test_services.py): Redis, Graph, Gateway, Kernel in parallel, then the Link check that the gateway is wired to the kernel. The kernel is required=True |
family-suite-preflight |
| 2 | Five safety gates, concurrently, all blocking: source-provenance, parallel-safety (0.1–0.6), test-purity, prolog-boundary, rulings-lint | source-provenance, parallel-safety, test-purity, prolog-boundary, rulings-lint |
| 2b | Proven-run reuse: a passing report at this exact HEAD and dirty signature whose scope covers this run exits 0 | green-ledger |
| 2c | RUL-039 rehearsal exclusion for gate contexts (fail-closed on a missing or stale artifact) and RUL-032 reuse: every module whose content key matches its last green run is reported reused, not re-run |
rehearsal-exclusion, green-ledger |
| 3 | Test phase: prints Selected: N tests in M modules (K modules reused from ledger) · hard cap 300s, then a live run X / selected Y · left Z · failed F line; batches over 8 workers |
test-phase-cap |
| 3a | Solo retry: modules that failed in a batch re-run alone, up to --solo-retry-cap (default 25); a recovery is logged as a load/order flake, the rest stay failed |
test-phase-cap |
| 3b | 300 s hard cap on the test phase (RUL-038 amendment, ORCH_TEST_CAP_SECONDS): at the cap every child kills itself, passing modules go to the green ledger, and the run exits non-zero with CAPPED whatever the failure count |
test-phase-cap |
| 4 | Every module that went green (batch or solo) is recorded in reports/test-green-ledger.json; the report goes to reports/run-tests-latest.json |
green-ledger |
A green --affected run still prints "subset run — non-authoritative, full suite still
owed" (_plan_affected_run).
7. Deploy
Deploy is documented on Deployment and ship and deploy. The parts that belong to this workflow:
deploy/ship.shruns VERIFY, PUSH, DEPLOY and a second VERIFY that the deployed thing is the pushed thing, stopping at the first failure. For this repository its VERIFY runsruff check,black --check, the RUL-007 range scan,run_tests.py --affectedfrom the last verified commit,gen_plugin.py --checkandcheck_env_fleet_agreement.py, then pushes with--no-verifybecause it has already done the hook's work; GitLab closed the idle SSH session during a 489 s hook run on 2026-08-14 (comments indeploy/ship.sh). → Gate: ship-verify, Gate: fleet-drift- Launchers with built-in verify and rollback, each pre-approved by an allow rule in
~/.claude/settings.local.jsonbecause Claude Code's auto-mode classifier blocks the raw installers as "Production Deploy":ct-install <label> [ref]for/opt/codetools: build and promote, thencodetools deploy verify-identityandct-smoke; rollback and exit 12 on failure; about 26 s (memorycodetools-install-launcher).tools-deploy [--check] <commit>for/opt/orchestrata-tools: runs that commit'sdeploy/install-tools.shfrom its own sparse tree and fails unlessVERSIONrecords the commit (memorytools-deploy-launcher).hermes-deploy <commit>for/opt/hermes: refuses unsafe states, exports only the two provider keys, anddeploy/install.shverifies the release and rolls back on failure (memoryhermes-deploy-launcher).
- A prod promote is never delegated. The classifier denies it to a subagent; the coordinator runs it after Mohamed approves in his own words, and the brief ends before it (improvement log D17).
- Use the deployed tools, not a checkout's
scripts/: the AST toolkit at/opt/orchestrata-tools, codetools at/opt/codetools/bin/codetools, Hermes ashermes-task(memoryuse-deployed-tools).run_tests.pystays the worktree's own copy, because it tests that tree.
8. Observation: never wait blind
The rule (skill observe-work): never fire a long run and wait for its exit code. Watch
it, and use the gap for other work.
- Tests run observed, in the background.
pytest-live <paths>puts the observer plugin on PYTHONPATH from/opt/orchestrata-toolsand pins the native math thread pools to 1;observe-pytest listreturns instantly with running and finished sessions and their counts;observe-pytest attach lateststreams PASS and FAIL lines. A Claude session exportsPYTHONPATH=/opt/orchestrata-tools/shimandPYTEST_ADDOPTS=-p orchestrata_pytest_observer(~/.claude/settings.json), so every pytest in a session loads the observer. - The observer refuses unsanctioned multi-file runs. An unstamped pytest session that
selects tests from more than one file exits at collection with rc 4 and 0 tests; the
sanctioned runners (
run_tests.py,ship.sh'sgate_pytest) stampORCHESTRATA_SANCTIONED_RUNNERon every child (RUL-038 amendment). - Delegated work gets a heartbeat.
swarm-supervisor watch --worktree <path> --task-name <name> --stall-minutes 10, thenswarm-supervisor check. The heartbeat is the newest of the git dir's HEAD/index mtimes, the newest tracked file and the last commit; alarms are edge-triggered. - The contention guard. The user-level PreToolUse(Bash) hook
test_run_context.pydenies a heavy command (pytest,run_tests*.py,ship.sh,uv sync,make, …) while an authoritative run is in flight, and lets read-only observation through (tail,grep,observe-pytest,jq, …). The override isORCH_ALLOW_CONTENDING=1with a stated reason (skillshell-guards). - No unbounded waits.
no-unbounded-wait.shrefuses a loop with no exit path, andreap-orphan-waiters.shclears leaked ones at session start; six such waiters once ran at the same time, the oldest for 3 days 4 hours (silent-failure classes). - Test runs need permission and an agreed time. Before any test run the coordinator
proposes the scope and a time taken from the duration ledger and waits for Mohamed's yes
(memory
no-test-runs-without-permission-and-time, 2026-10-22). Reading recorded logs is not a run.
Worked example: the item-3 push, 2026-10-22
Item 3 was the codebase-map work on branch feat/maps-via-codetools in
~/workspace/wt-MAP-PRECOMMIT: pre-commit regenerates the maps (b5468cb1c), in one
process (e1f3a5047), with a delta cache (c995f5b88), through the codetools verb
(d4ae44819). Its push was refused three times before it passed. The four logs are in
the session scratchpad (push-item3.log, push-item3-retry.log, -retry2.log,
-retry3.log); times below are each log's last write, EDT.
| Attempt | Ended | Pushed | Outcome | Cause |
|---|---|---|---|---|
| 1 | 22:03 | d4ae44819 |
refused at the safety gates | source-provenance: "THE SUITE WOULD TEST THE WRONG SOURCE TREE", expected and actual both .../wt-MAP-PRECOMMIT/src, plus "PYTHONPATH=/opt/orchestrata-tools/shim ... Unset it" |
| 2 | 23:37 (WALL 96 s) | d4ae44819 |
refused at the safety gates | same message, no PYTHONPATH line: the venv was an editable install |
| 3 | 23:43 (WALL 222 s) | d4ae44819 |
133 passed, 1 failed | a real failure in tests/scripts/test_hooks_pre_commit.py: docs/CODEBASE_MAP.md was not staged |
| 4 | 23:58 (WALL 349 s) | a4cbc2bdf |
pushed 4ac0db570..a4cbc2bdf |
134 passed, 0 failed; 6 modules reused from the ledger, 12 tests in 1 module run in 31 s |
Refusals 1 and 2: the provenance gate. check_source_provenance.py has two checks
(read through the AST toolkit). Check 1 compares the PEP 610 direct_url.json origin with
this checkout; an editable install records the same checkout, so it passes. Check 2
requires the imported package to live under site-packages; an editable install resolves
to src/, so it fails and prints expected_src and actual_src, which are then the same
path. The identical paths are the tell for an editable install, and the message does not
say so. In attempt 1 the gate also printed its PYTHONPATH hint because the session exports
PYTHONPATH=/opt/orchestrata-tools/shim; that directory holds only
orchestrata_pytest_observer.py (ls), so it could not have supplied
orchestrata_ultimate, and the log does not separate the two causes. The editable flip
came from plain uv run calls in hooks: start-architecture-sync.sh (SessionStart, two
calls) and scripts/hooks/pre-commit line 166 (the ruff step), which re-sync the project
editable in a checkout without UV_NO_EDITABLE. The commit that fixes them, 8a2cb42ad
(uv run --no-sync, 2026-10-23 00:04), records the flip "timestamped at session start
(23:43:43)"; it is on feat/maps-via-codetools and not pushed. Until it is pushed and
deployed, the workaround is env -u PYTHONPATH -u PYTEST_ADDOPTS uv sync --dev --no-editable --frozen before a push, then a check of direct_url.json (memory
uv-run-flips-venv-editable). The venv's orchestrata_ultimate-1.5.0.dist-info carries
an mtime of 23:52:03, between attempts 3 and 4.
Refusal 3: a real bug. With the venv non-editable, the suite ran: 7 modules, 134
tests, hard cap 300 s. test_hook_regenerates_and_stages_both_maps_when_python_is_staged
failed in its batch and again in its solo retry: the staged set was
{'reports/precommit-walls.ndjson', 'src/pkg/mod.py'}, without docs/CODEBASE_MAP.md. The
codetools path of inject-codebase-map.sh moved both maps into docs/ without creating
it, and the || exit 0 swallowed the mv failure, so a tree without docs/ got no maps.
gen_codebase_map.py had created the directory itself. Fix: a4cbc2bdf adds a mkdir -p
(2026-10-22 23:52).
Attempt 4: pass. The RUL-032 ledger reused the 6 modules that had passed in attempt 3
(122 tests) and ran only the changed one (12 tests, 31 s); the summary line counts 134.
The hook stamped reports/.ship-verified with a4cbc2bdf, and the run printed "subset
run — non-authoritative, full suite still owed".
What the example teaches.
- A gate message must name the condition it detected. "Wrong source tree" with two equal paths cost two refusals; the check that actually failed was "not under site-packages". (Not yet changed in the gate; recorded as a gap on Gate: source-provenance.)
- A hook observes; it does not change the environment (
8a2cb42ad's message). The same editable flip had already been fixed once for the pre-push hook itself (949c1628c, 2026-10-21). - The ledger made the retries cheap: attempt 4 re-ran 12 tests, not 134.
Related
- Gates — master index
- Workflow improvements
- Bug fixes and the generated bug-fix log
- How we work — process, deployment, testing, coverage
- Briefs, Dispatch ledger
- Tests, Test reports, Green ledger
- Deployment, ship and deploy
- Claude hooks, Claude skills, codetools
- Rulings: RUL-007, RUL-021, RUL-022, RUL-030, RUL-032, RUL-035, RUL-038, RUL-039, RUL-040, RUL-041