Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .harness/docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1448,9 +1448,17 @@ A test that needs a repository it may mutate copies this one. `tests/test_git_hi

**A pattern list is the wrong instrument here and would have looked right, which is why the module says so where the next author will be standing.** `shutil.ignore_patterns` fnmatches **bare entry names**, so a pattern carrying a slash — `.harness/runs/`, the spelling `.gitignore` actually uses — matches nothing, and an exclusion derived from the text of `.gitignore` would have excluded nothing while reading as though it excluded everything. `copy_repository(destination, source=REPO_ROOT)` passes a callable instead, which joins each entry onto its directory's repository-relative path and compares that against git's answer, because the repository-relative path is the form git reports.

**`.git` comes along, and that is a requirement rather than an oversight.** Every assertion the copy exists for bounds itself at its own story's commit range and cannot resolve one without the history — this module is one of the four declared subject readers in the section above. Eleven megabytes of `.git` is not what made the copy expensive, and the question of whether the module needs history at all is settled: it does.
**The history comes along, and that is a requirement rather than an oversight.** Every assertion the copy exists for bounds itself at its own story's commit range and cannot resolve one without the history — this module is one of the four declared subject readers in the section above. Eleven megabytes of `.git` is not what made the copy expensive, and the question of whether the module needs history at all is settled: it does. *How* it comes along changed with story-153, next.

**What `.git` *is* here decides what running git inside the copy answers about, and a checkout under a linked worktree is not the case a reader assumes.** In a linked worktree `.git` is not a directory but a short pointer file naming an admin directory elsewhere, and the copying — before this story and after it — copies that file verbatim. A linked worktree's admin directory carries no `core.worktree`, so git derives the working tree from where it found the `.git` file: run with its working directory set to the copy, git reports about the copy. That is why an assertion reading git's ignored entries *inside* the copy answers about the copy rather than about the worktree the copy was made from, and why the module's bounded history reads resolve there at all. The cost of the arrangement is that the copy's history is reached *through* the source worktree's admin directory rather than held in the copy, so a copy whose pointer no longer resolved would not be caught by the presence check on `.git` — the nested pytest runs inside the copy would redden instead. That is not a property this story introduced and not one it repaired; it is written here because the next author to reason about what the copy contains will otherwise reason about a directory that is a file.
**What `.git` *is* in the source decides what a copy of it would be, and a checkout under a linked worktree is not the case a reader assumes.** In a linked worktree `.git` is not a directory but a one-line pointer file, `gitdir: <path>`, naming an admin directory inside the real repository. Until story-153 the copying copied that file verbatim, so from a worktree the copy's `.git` was a pointer back into the repository it was copied from: git run in the copy reported about the copy's working tree — a linked worktree's admin directory carries no `core.worktree`, so git derives the tree from where it found the file — but read the source's refs and objects and wrote them too. Every harness run of the suite works in a worktree (see "Planning and running each stand on a worktree of their own"), so that was the case the copy met on every run the harness made of it, and only a developer's main checkout, where `.git` is a real directory, produced a genuine copy. The module's whole safety argument is that the repository is copied rather than mutated in place because the one the suite runs from is the one being validated; a copied pointer satisfied the letter of that and none of the substance. The Inspector found it on story-151, the finding was routed to that story and lost (the story-152 section records how), and it was filed by hand.

**story-153 gives the copy a git directory of its own: clone, then overlay.** `copy_repository` now runs `git clone --no-checkout` from the source into the destination first, removes the `origin` remote the clone recorded so the copy holds no reference back, lays the source's working tree over the clone with the same `leave_behind` callable — extended to leave `.git` behind at the root — and runs `git reset` in the copy so its index is rebuilt from HEAD and git there reports the overlaid edits as modifications against the same commit. A clone resolves the `gitdir:` pointer itself, which is why it is the form chosen: nothing in the test spells git's worktree layout. The overlay is not optional. A clone alone would give the copy HEAD's tree, and on every harness run the story's own uncommitted edits are what the nested pytest runs must see. Two forms were considered and refused: cutting a worktree from the source with git's own machinery, because that produces another pointer into the same real repository, which is the defect; and resolving the pointer by hand, which works but bakes the worktree layout into the test.

**The standing assertion is driven from a worktree the test builds, because that is the only place it bites.** `test_a_copy_of_a_worktree_has_a_git_directory_of_its_own` builds a repository with `conftest.init_repository`, cuts a worktree from it through the harness's own `worktrees.add` — so the source is the shape of worktree the harness works in — copies the worktree, and requires `git rev-parse --git-dir` and `--git-common-dir` run in the copy to resolve beneath the copy. Its control is the same two readings in the worktree itself, which must resolve outside it and beneath the origin, so a reading that stopped seeing or a resolution that put every answer under its root fails as itself. Built rather than borrowed for the reason the previous story's built-repository test gives: an assertion that depended on whether the suite happened to be running from a worktree would be green on one machine and red on another with nothing changed. Beside it, `test_the_copy_is_isolated_from_its_source_and_starts_where_it_stood` holds the sameness that makes the isolation worth having — HEAD equal to the worktree's and the copy's symbolic HEAD on the worktree's branch, the uncommitted edit present as text and as what `git status` reports — and then commits inside the copy and requires the origin to hold neither the commit nor the object, with the copy's own history as the control. **The sameness is only a reading if the worktree stands somewhere the origin does not.** A worktree cut fresh from a branch stands on that branch's commit, so a helper that cloned the origin's base branch instead of the worktree would answer a HEAD comparison identically; `worktree_and_copy` therefore commits on the worktree's branch before copying, and the test first asserts that the origin's base branch and the worktree's HEAD differ, so that a copy standing on the origin's HEAD fails rather than passing as sameness.

**The commit made inside the copy carries an identity the test supplies, under an environment where nothing else can.** A clone carries none of its source's local config — `conftest.init_repository` writes `user.name` and `user.email` into the origin's `.git/config`, and the worktree shares that config, but the copy does not — so a commit in the copy finds an identity only where the machine supplies one: a developer's `~/.gitconfig`, a runner's environment, or git's own guess from the account name and hostname, which git accepts whenever the hostname carries a dot and refuses otherwise. A commit left to that would be green on a laptop whose hostname ends in `.local` and red on a runner with nothing in the test changed. So `environment_with_no_identity` points the global and system config files at the null device, removes the `GIT_AUTHOR_*`, `GIT_COMMITTER_*` and `EMAIL` variables, and sets `user.useConfigOnly` through the `GIT_CONFIG_COUNT` slots to switch the hostname guess off; the test makes the commit once under that environment with no identity and requires it to be refused, then makes it with `-c user.email` and `-c user.name` on the command line and requires it to land. The control comes first for the usual reason: without it, an identity leaking in from the machine would let the second commit pass and say nothing about what it stood on. This is the same shape the coordinator uses for its own clone-then-commit (`story_coordinator.py` passes `-c user.email` and `-c user.name` to the commit it makes in a clean clone), and the consequence for the next author is the general one: **anything that commits inside a copy must supply its identity, because the copy has none.** `test_a_copy_of_a_main_checkout_carries_what_it_carried_before` pins the case that was always right, on a source whose `.git` is a real directory with more than one commit, so the clone-and-overlay cannot have narrowed it.

**Two consequences for the next author of anything that runs git inside the copy.** The copy is a repository with HEAD at the source's commit — on the source's branch if the source was on one; if the source was detached, `git clone` puts the copy on whichever branch names that commit and leaves it detached only when none does — with no remote and none of the source's other branches; a branch name that resolved in the source does not necessarily resolve in the copy, so bounded reads should walk from HEAD and name commits, which `story_commit_range` in `tests/conftest.py` already does. And the copy's index is what `git reset` from HEAD produced, so a path the source had staged but not committed arrives in the copy unstaged; the tree is the same, the staging is not.

**One copying implementation, two fixtures, and the second exists because of what the first's consumers do to it.** `repo_copy` calls `copy_repository`; so does `pristine_copy`, which nothing mutates. The standing assertion takes the pristine one deliberately — the subprocess pytest runs inside `repo_copy` write `__pycache__` into it, and `__pycache__` is itself a path the repository ignores, so an assertion about what the *copying* put there would be answered by what the tests did to it afterwards. **A fixture that the tests under it modify cannot be the subject of an assertion about how it was built.**

Expand Down
3 changes: 3 additions & 0 deletions .harness/history/execution-log.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,6 @@
{"story_id": "story-151", "status": "resumed", "retry_count": 0, "timestamp": "2026-09-16 07:36:54"}
{"story_id": "story-151", "status": "completed", "retry_count": 1, "timestamp": "2026-09-16 09:07:08"}
{"story_id": "story-152", "status": "completed", "retry_count": 1, "timestamp": "2026-09-16 10:49:18"}
{"story_id": "story-153", "status": "escalated", "retry_count": 1, "timestamp": "2026-09-16 12:27:50"}
{"story_id": "story-153", "status": "resumed", "retry_count": 1, "timestamp": "2026-09-16 12:32:22"}
{"story_id": "story-153", "status": "completed", "retry_count": 1, "timestamp": "2026-09-16 12:55:13"}
4 changes: 4 additions & 0 deletions .harness/history/inspection-log.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,7 @@
{"story_id": "story-151", "mode": "narrow", "cost_usd": 3.063886, "scope_files": 60, "invocations": 1, "timestamp": "2026-09-16 08:31:20", "findings": 1, "filed": 0, "dropped": 0, "dedupe_ran": true}
{"story_id": "story-152", "mode": "narrow", "cost_usd": 3.002376, "scope_files": 60, "invocations": 1, "timestamp": "2026-09-16 10:28:07", "findings": 2, "filed": 0, "dropped": 0, "dedupe_ran": true}
{"story_id": "story-152", "mode": "narrow", "cost_usd": 2.6565825000000003, "scope_files": 60, "invocations": 1, "timestamp": "2026-09-16 10:40:25", "findings": 2, "filed": 0, "dropped": 0, "dedupe_ran": true}
{"story_id": "story-153", "mode": "narrow", "cost_usd": 2.7320615000000004, "scope_files": 60, "invocations": 1, "timestamp": "2026-09-16 12:22:07", "findings": 3, "filed": 0, "dropped": 0, "dedupe_ran": true}
{"story_id": "story-153", "timestamp": "2026-09-16 12:27:41", "filed": 1, "dropped": 0, "routed": 3, "acted_on": 2}
{"story_id": "story-153", "mode": "narrow", "cost_usd": 2.5583735, "scope_files": 60, "invocations": 1, "timestamp": "2026-09-16 12:46:28", "findings": 1, "filed": 0, "dropped": 0, "dedupe_ran": true}
{"story_id": "story-153", "timestamp": "2026-09-16 12:49:39", "filed": 1, "dropped": 0, "routed": 1, "acted_on": 0}
1 change: 1 addition & 0 deletions .harness/history/mandate-log.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,4 @@
{"story_id": "story-150", "timestamp": "2026-09-15 12:12:00", "conferred_by": "jerod.wilkerson <30474318+jerodw@users.noreply.github.com>", "source_kind": "human", "recorded_by": "l5-plan", "discarded_session_block": false}
{"story_id": "story-151", "timestamp": "2026-09-15 14:59:07", "conferred_by": "jerod.wilkerson <30474318+jerodw@users.noreply.github.com>", "source_kind": "human", "recorded_by": "l5-plan", "discarded_session_block": false}
{"story_id": "story-152", "timestamp": "2026-09-16 09:53:13", "conferred_by": "jerod.wilkerson <30474318+jerodw@users.noreply.github.com>", "source_kind": "human", "recorded_by": "l5-plan", "discarded_session_block": false}
{"story_id": "story-153", "timestamp": "2026-09-16 12:03:52", "conferred_by": "jerod.wilkerson <30474318+jerodw@users.noreply.github.com>", "source_kind": "human", "recorded_by": "l5-plan", "discarded_session_block": false}
2 changes: 2 additions & 0 deletions .harness/history/retry-log.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,5 @@
{"story_id": "story-147", "timestamp": "2026-09-14 18:26:38", "stage": "verifier", "retry_decision": "retry", "retry_category": "validation", "retry_stage": "tester"}
{"story_id": "story-151", "timestamp": "2026-09-15 15:47:59", "stage": "tester", "retry_stage": "tester"}
{"story_id": "story-152", "timestamp": "2026-09-16 10:28:50", "stage": "verifier", "retry_decision": "retry", "retry_category": "validation", "retry_stage": "tester"}
{"story_id": "story-153", "timestamp": "2026-09-16 12:27:41", "stage": "verifier", "retry_decision": "retry", "retry_category": "validation", "retry_stage": "tester"}
{"story_id": "story-153", "timestamp": "2026-09-16 12:27:49", "stage": "tester", "retry_stage": "tester"}
Loading
Loading