story-014: Verify the suite in a clean clone before the story commits - #12
Merged
Conversation
Implemented by the l5 harness story workflow.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The verifier runs the suite in the working tree — the one environment where the story's own commit does not yet exist.
_completethen commits that tree, after the documenter and after every check the workflow performs. The state the code actually ships in is created after the last thing that could object to it, and nothing ran the suite there.Three failures across two stories lived in that gap, and every one reported green to the verifier:
git show HEAD:orchestration/story_coordinator.py— the pre-story coordinator only while uncommitted.419 passeduncommitted against394 passed, 25 errorscommitted.actions/checkoutdefaults to a one-commit clone, so the baseline walk had no history to walk.git status --porcelain -- tests/lists the tester's new files — true mid-run, false once_completecommits them — on a run that reported460/460and passed verification on its first iteration.This story gives the coordinator a second run of the suite, in a fresh clone with the story committed into it, executed after the verifier passes and before the documenter runs. A failure reroutes to the implementer as a retry. The result is written to the run directory as
clean-clone-result.json, so a reader can tell the check ran rather than inferring it from a pass.This is not verifier leniency. The suite genuinely passes where the verifier stands. The bug is the absent check, not a missed one. It does not replace CI, which stays the final word — it moves discovery earlier, so a story is not reported complete and committed before the failure is known.
Changes
orchestration/story_coordinator.py—clean_clone_checkis the last thing the verifier branch does on a passing verdict. It builds a scratch clone withtempfile.mkdtemp, runs the suite, removes the scratch directory in afinallywhatever the result, and writesCleanCloneResult.as_record()under the declared artifact name._build_clonedoesgit clone --no-hardlinksfrom the target's filesystem path — never the network — applies the target's tracked edits asgit diff --binary HEADpiped togit apply, copies untracked-but-not-ignored files fromgit ls-files --others --exclude-standard, then commits inside the clone. The target repository is only read: no commit, no branch, no index change, no stash._link_interpreter_rootslinks each configured interpreter's top-level directory into the clone and appends those names to the clone's.git/info/exclude— a virtualenv is gitignored and therefore absent from a fresh clone, and a.gitignoreentry for a directory does not cover a symlink standing in its place.archive_attemptabove the increment, then increment, save, event, and reroute toon_failure.retry_stage— or the existing escalation path at the ceiling.workflows/story-workflow.json— the verifier stage declaresclean_clone: "clean-clone-result.json". That key is what turns the check on: the coordinator readsstage.get("clean_clone")and does nothing when absent, so removing the declaration disables the check with no orchestration change, and the artifact name never appears in code.schemas/clean-clone-result.schema.json(new) — records whether the check ran, the command, the interpreter and its version, the exit code and an output tail. Coordinator-written likeexecution-history, so it appears in no stage'sschemasmap. Optional fields are expressed by absence, not null — a check that refused to run has no exit code to report.orchestration/context_assembler.py,prompts/implementer.md— a{{clean_clone_result}}placeholder carries the evidence into a retried implementer's prompt. This exists because a clean-clone retry has no verifier finding to carry: the verifier passed, and the coordinator must not fabricate an agent's judgement by writingretry-guidance.jsonitself.schemas/execution-history.schema.json,tests/test_schema_validator.py,tests/test_story_004_validation.py— the new event kinds, and the two schema-inventory assertions updated. Both still assert exact set equality..harness/docs/ARCHITECTURE.md— the check, its placement in the post-verifier order, the clone construction and why it is a clone rather than a copy, the reroute-rather-than-escalate decision, and the note that story-014's own run is not governed by the check it adds.Testing
549 passed— on both Python 3.14 (the harness interpreter) and Python 3.10 (the version the check now targets).New validation is in
tests/test_story_014_validation.py, written by the tester stage: 1156 lines covering the clone construction, the gitignore exclusion, scratch removal after a failing run, target immutability, the retry routing, the ceiling escalation, the omitted-declaration case, and theHEAD-baseline fixture.Notes for review
clean_clone_pythonin.harness/config.yamlnames it. The.venvhere is 3.14 while CI tests 3.10–3.12, so before this nothing local exercised a version CI runs — a 3.10 incompatibility would have passed the verifier, passed the clean-clone check, and failed only in CI. That is the same class of gap as the terminal-width bug that reached CI on story-016, on a different axis. The suite passes on 3.10, so nothing was hiding.clean_cloneexisted in it. The run directory therefore contains noclean-clone-result.json. The verifier recorded this rather than treating it as a defect.tests/test_story_011_validation.pyproves its own non-vacuity by deleting the firstretry_decision="retry",line at the verification-failed branch's indentation. An inline clean-clone branch nests deeper and sits earlier in the file, so its line would contain that indented text and the mutation would land there instead of where it was aimed.tests/test_story_009_validation.pyandtests/test_story_010_validation.pyassertedgit diff HEAD -- <paths>overworkflows/,schemas/andprompts/— all of which this story edits, so they went red while the tree was dirty. The implementer repointed both at their own story's commit range. This is story-015's work arriving early by necessity rather than by choice; the verifier confirmed the markers resolve toff80849and2239a23, so both assertions compare a real commit against its parent and can still fail._build_cloneraisesRuntimeErrorifgit clone,git applyor the clone-side commit fails, which would propagate out without writing the artifact or escalating. No case could be constructed wheregit applyofgit diff --binary HEADfails against a fresh clone of the sameHEAD, so there is no evidence the path is reachable.🤖 Generated with Claude Code