From 3c74442755c82f72d394ab0fe32a524be0b1da9e Mon Sep 17 00:00:00 2001 From: Logan Lindquist Land Date: Sat, 24 Oct 2026 08:22:29 -0500 Subject: [PATCH] docs: replace stale issue references in comments --- cmd/agentsync/doctor_test.go | 2 +- cmd/agentsync/home_test.go | 8 +++----- cmd/agentsync/import_test.go | 6 +++--- internal/doctor/doctor.go | 4 ++-- internal/doctor/doctor_test.go | 2 +- internal/importer/importer_test.go | 7 +++---- internal/importer/plan.go | 3 +-- internal/inventory/inventory_test.go | 6 +++--- internal/linker/plan.go | 13 +++++-------- internal/reconcile/plan.go | 18 +++++++++--------- internal/reconcile/reconcile_test.go | 6 +++--- internal/scan/git.go | 23 ++++++++++------------- internal/scan/git_test.go | 26 +++++++++++--------------- internal/scan/home_test.go | 8 ++++---- internal/scan/state.go | 9 +++------ internal/scan/state_test.go | 8 ++++---- internal/testfs/git.go | 13 ++++--------- internal/testfs/testfs.go | 14 +++++++------- internal/testfs/testfs_test.go | 6 +++--- 19 files changed, 80 insertions(+), 102 deletions(-) diff --git a/cmd/agentsync/doctor_test.go b/cmd/agentsync/doctor_test.go index ed29ca0..f13225b 100644 --- a/cmd/agentsync/doctor_test.go +++ b/cmd/agentsync/doctor_test.go @@ -295,7 +295,7 @@ func TestRunDoctor_ReadmeIsNeverACapture(t *testing.T) { // about, so "nothing is wrong" cannot be said about it. Same rule as import and // link: an incomplete diagnosis must not exit the way a clean one does. // -// Structural rather than chmod 000 (issue #11), so the CI gate — which runs as +// Structural rather than chmod 000, so the CI gate — which runs as // root, for whom permission bits are advisory — actually runs it. The chmod // form skipped there, and a skip prints nothing. func TestRunDoctor_UnreadableSubtreeIsNotACleanRun(t *testing.T) { diff --git a/cmd/agentsync/home_test.go b/cmd/agentsync/home_test.go index 73187a2..15c728e 100644 --- a/cmd/agentsync/home_test.go +++ b/cmd/agentsync/home_test.go @@ -34,11 +34,9 @@ func scanArgs(t *testing.T, repoRoot, root string, extra ...string) []string { return append([]string{"scan", "--" + allFlag}, extra...) } -// TestRunScan_ValidButWrongHomeWarns is the issue itself. $HOME is a real, -// readable, resolvable directory — it is simply not the home these projects -// live under — so a validity check passes it and, before this change, the scan -// completed with every key silently re-based under _outside-home/ and nothing -// on stderr. +// TestRunScan_ValidButWrongHomeWarns requires a warning when $HOME is valid but +// is not the home these projects live under. A directory validity check alone +// would let the scan silently re-base every key under _outside-home/. // // The exit code must not change: warning is the right severity for a // read-only command, and CI keys off these codes. diff --git a/cmd/agentsync/import_test.go b/cmd/agentsync/import_test.go index d343e70..3cf356c 100644 --- a/cmd/agentsync/import_test.go +++ b/cmd/agentsync/import_test.go @@ -562,7 +562,7 @@ func TestRunImport_ConflictDoesNotBlockTheOtherProjects(t *testing.T) { // else in the tree is not a reason to leave a genuinely unprotected file // unprotected. // -// The subtree is made unreadable structurally, not with chmod 000 (issue #11). +// The subtree is made unreadable structurally, not with chmod 000. // The chmod form skipped on the CI gate, which runs as root, and it skipped // without printing anything — so the assertion that mattered most was verified // on one laptop and nowhere else. testfs.UnreadableSubtree does not care what @@ -636,8 +636,8 @@ func TestRunImport_UnreadableSubtreeBlocksAnOtherwiseEmptyRun(t *testing.T) { // --- the home guard --------------------------------------------------------- -// TestRunImport_WrongHomeWritesNothing is the write-path guard from issue #3, -// and the reason RequireHome exists. +// TestRunImport_WrongHomeWritesNothing checks that RequireHome blocks writes +// when the configured home would give the projects incorrect capture keys. // // $HOME is a real, readable directory that is simply not the one these projects // live under — the sudo case. Nothing on disk looks wrong; the run would diff --git a/internal/doctor/doctor.go b/internal/doctor/doctor.go index cd57e8a..860e30d 100644 --- a/internal/doctor/doctor.go +++ b/internal/doctor/doctor.go @@ -10,7 +10,7 @@ // That is a decision, not an unfinished feature. Every drift below has an // obvious-looking repair — unlink the broken one, repoint the mismatched one, // delete the stale capture — and every one of those repairs needs a fact this -// tool does not have. Issue #2's state file is the missing fact: it records +// diagnosis alone cannot establish. The state file records // every link the tool created and the content hash at link time, which is the // only thing that separates "the user deleted this on purpose" from "this went // missing", and "this capture is obsolete" from "this capture is the last copy @@ -18,7 +18,7 @@ // guesses that look most reasonable — unlinking a broken link and deleting a // stale capture — are exactly the two that destroy something unrecoverable. // -// So repair and reconcile are a separate future command, gated on the state +// Reconciliation belongs to the separate reconcile command, gated on the state // file. doctor's whole contract is: find it, name it, explain why it might be // there, and leave the judgement to a human. Its exit code is the only thing it // asserts. diff --git a/internal/doctor/doctor_test.go b/internal/doctor/doctor_test.go index 57d8f75..df023d6 100644 --- a/internal/doctor/doctor_test.go +++ b/internal/doctor/doctor_test.go @@ -701,7 +701,7 @@ func TestWalkCaptures_NonRegularCaptureIsAProblem(t *testing.T) { // treated as empty — an unreadable directory and an empty one must not produce // the same report. // -// Structural rather than chmod 000 (issue #11): the CI gate runs as root, and +// Structural rather than chmod 000: the CI gate runs as root, and // permission bits do not restrict root's walk, so the chmod form of this test // skipped in CI and covered nothing there. testfs.UnreadableSubtree fails the // same way for every uid. diff --git a/internal/importer/importer_test.go b/internal/importer/importer_test.go index b00813c..5df610d 100644 --- a/internal/importer/importer_test.go +++ b/internal/importer/importer_test.go @@ -98,7 +98,7 @@ func (f *fixture) commitRepo() { // noGit makes git unexecutable for the rest of the test, which is how the // "could not ask" arm is reached without depending on file permissions. // -// Permissions are the wrong lever here: CI runs as root (issue #11), where a +// Permissions are the wrong lever here: CI runs as root, where a // chmod-based fixture stops being unreadable and the test quietly passes // without exercising anything. An empty PATH fails the exec itself, identically // for every user. @@ -657,9 +657,8 @@ func TestApply_UpdateRefusesWhenHEADMovedAfterThePlan(t *testing.T) { // TestNewPlan_UpdateRefusesWhenGitCannotAnswer is the fail-closed property, and // the one most likely to regress. // -// "Not committed" and "git could not be asked" are different facts, and the bug -// class in issue #12 is exactly a caller reading the second as the first — or, -// here, as the more permissive third thing. Only scan.CommittedYes may +// "Not committed" and "git could not be asked" are different facts, but neither +// permits replacing a capture. Only scan.CommittedYes may // authorise replacing a capture; every other answer, including one the probe // could not produce at all, refuses. // diff --git a/internal/importer/plan.go b/internal/importer/plan.go index d02d4b4..ac949ee 100644 --- a/internal/importer/plan.go +++ b/internal/importer/plan.go @@ -497,8 +497,7 @@ func judgeDest(item Item, opts Options) (verdict, error) { // case rather than the default arm is the point: every way the probe can fail // to say yes — git missing, a repo git will not talk about under safe.directory, // a corrupt object store, no repository at all — then lands on the refusing side -// without any call site remembering to handle it. That is the discipline issue -// #12 exists about, applied at the site rather than documented upstream of it. +// without any call site remembering to handle it. func commitGate(dest string) (Refusal, string) { switch scan.CommittedInHEAD(dest) { case scan.CommittedYes: diff --git a/internal/inventory/inventory_test.go b/internal/inventory/inventory_test.go index ced2b67..528910f 100644 --- a/internal/inventory/inventory_test.go +++ b/internal/inventory/inventory_test.go @@ -1126,9 +1126,9 @@ func TestBuild_EmptyMachineStillEmitsArrays(t *testing.T) { // The top level is exactly these five keys, asserted as a closed set rather // than by looking for the fields we do not want. // -// The issue's own sketch named a generated-at timestamp and a tool version, and -// both were dropped: either one makes every regeneration a diff, which destroys -// the property --check depends on and the reason this file is worth committing. +// A generated-at timestamp or tool version would change the output even when +// the inventory content is unchanged, undermining --check and creating noise +// in a committed inventory file. // Their absence is a contract, so adding a sixth key has to fail here and be // argued for, not slip in. func TestMarshal_TopLevelIsSchemaAndContentOnly(t *testing.T) { diff --git a/internal/linker/plan.go b/internal/linker/plan.go index daf26be..0082e64 100644 --- a/internal/linker/plan.go +++ b/internal/linker/plan.go @@ -298,8 +298,7 @@ func (u Unresolved) String() string { // The zero value covers every project the scan found and provisions only what // is already on disk there. Detection — deciding which agents a project // *should* have — is deliberately not part of this package; link provisions -// what it is told to provision, and the manifest from issue #2 will feed the -// same Agents field when it exists. +// what it is told to provision through the Agents field. type Options struct { // Projects restricts the run to these project keys, as produced by // scan.ProjectKey. Empty means every project found under the scan root. @@ -700,9 +699,8 @@ func atRisk(entries []scan.Entry) []scan.Entry { // Judge maps one classified entry onto the action link takes for it. // -// The mapping is the state table from issue #2 and nothing else: states 3, 4, -// 6 and 7 are reported and left alone, state 2 is a no-op, and state 5 is the -// only on-disk state link writes for. +// States 3, 4, 6 and 7 are reported and left alone, state 2 is a no-op, and +// state 5 is the only on-disk state link writes for. // // Exported for doctor, which reports on the same link states. "Is this link // healthy, and is it pointing at this project's counterpart?" is one question, @@ -914,9 +912,8 @@ func deriveDir(repo *scan.Repo, key string) (string, error) { // an ordinary layout. The Lstat rule governs agent *files*, where // following a link is what turns a healthy link into a false adoptable. // - // This does fold "not there" together with "could not look" — audited - // under issue #12 and kept, because here both answers are the same - // answer. Every non-nil error returns, so an unreadable path refuses to + // "Not there" and "could not look" both refuse provisioning here. + // Every non-nil error returns, so an unreadable path refuses to // provision exactly as a missing one does, and refusing is the safe arm // on a write path. The wording is the only casualty: "no project at X" // is imprecise for a permission error. It is not misleading in the way diff --git a/internal/reconcile/plan.go b/internal/reconcile/plan.go index 3639202..7f05a5f 100644 --- a/internal/reconcile/plan.go +++ b/internal/reconcile/plan.go @@ -542,8 +542,8 @@ func (p *Plan) refuse(op Op, selector, path string, guard Guard, format string, } // judgeRecords walks what this tool says it created and compares each record -// against what is on disk now. It is where the three-row table from issue #13 -// is decided, and where the prune gate is evaluated. +// against what is on disk now: a symlink, a regular file, or an absent path. +// It also evaluates the prune gate for recorded links. func (p *Plan) judgeRecords(repo *scan.Repo, records []state.Record, opts Options) { for _, rec := range records { item := Item{ @@ -572,15 +572,15 @@ func (p *Plan) judgeRecords(repo *scan.Repo, records []state.Record, opts Option } } -// judgeAbsent separates row three of the table from a project that is no longer -// at this path at all. +// judgeAbsent distinguishes a deleted link from a project that is no longer +// at this path. // -// Row three is one link deleted out of a directory that is still there, which -// is evidence about that agent. A missing agents directory is evidence about -// the project — an ordinary `mv` produces it — and reading it as row three +// One link deleted out of a directory that is still there is evidence about +// that agent. A missing agents directory is evidence about +// the project — an ordinary `mv` produces it — and treating it as a deleted link // writes a declined marker for every agent in the project, suppressing agents -// nobody declined. That is the false-positive direction #13 names, and the -// decline is the one write no flag guards. +// nobody declined. Declines are recorded without an apply flag, so this +// distinction must hold even in a dry run. func (p *Plan) judgeAbsent(item Item, rec state.Record) { dir := filepath.Dir(rec.Path) diff --git a/internal/reconcile/reconcile_test.go b/internal/reconcile/reconcile_test.go index d0290ea..0180d88 100644 --- a/internal/reconcile/reconcile_test.go +++ b/internal/reconcile/reconcile_test.go @@ -183,9 +183,9 @@ func noErrs(t *testing.T, errs []error) { // --- the record-against-disk table ------------------------------------------ -// TestNewPlan_RecordAgainstDisk is the three-row table from issue #13 plus the -// link states that surround it. It is the whole of what reconcile decides about -// a link it has a record for, so every arm is exercised from one fixture shape. +// TestNewPlan_RecordAgainstDisk covers recorded paths that are now symlinks, +// regular files, or absent. Every reconciliation outcome for a recorded link +// is exercised from one fixture shape. func TestNewPlan_RecordAgainstDisk(t *testing.T) { const project = "Web/proj" diff --git a/internal/scan/git.go b/internal/scan/git.go index a3a59f8..2395a3a 100644 --- a/internal/scan/git.go +++ b/internal/scan/git.go @@ -215,8 +215,8 @@ func probeGitRoot(dir string) gitRoot { // and nothing upstream can catch it. Reading the 128 as "unborn" made // this gitNo, hence TrackedNo, hence CommittedNo, hence import --update // telling the user to commit project-agents/ in a repository whose refs - // are unreadable — the same wrong remedy this issue was opened over, - // reached by a different route. `git pack-refs --all` is an ordinary + // are unreadable, where committing cannot fix the failure. + // `git pack-refs --all` is an ordinary // maintenance command and `git gc` runs it automatically, so a packed // refs file is the normal state of any repository that has been alive // for a while, not an exotic one. @@ -232,12 +232,10 @@ func probeGitRoot(dir string) gitRoot { // rev-parse --verify --quiet HEAD -> 1 // // So a repository whose refs cannot be read still lands on gitNo here - // and reports as "nothing is committed". It is pre-existing, it is - // identical in InspectDir below, and it is recorded on issue #12 rather - // than fixed: the only disambiguator that survives is filesystem - // corroboration in the style of hasGitEntry — asking whether refs - // actually exist — and that is a larger change than this audit should - // make on a path that already fails closed for every other reason. + // and reports as "nothing is committed". This remains a limitation here + // and in InspectDir below: distinguishing unreadable refs from an unborn + // HEAD requires filesystem corroboration in the style of hasGitEntry, + // rather than relying on the exit code alone. _, ans, code := runGitCode(top, "rev-parse", "--verify", "--quiet", "HEAD") switch { case ans == gitYes: @@ -414,9 +412,8 @@ func (c Committed) String() string { // The exit codes are read the way the rest of this file reads them: 0 is a yes, // a documented 1 is a no, and anything else is git failing rather than // answering. Only a clean yes to all three questions returns CommittedYes; -// every other outcome refuses. That is the discipline issue #12 exists about, -// and making CommittedUnknown the zero value is what keeps a forgotten case on -// the refusing side. +// every other outcome refuses. Making CommittedUnknown the zero value keeps +// a forgotten case on the refusing side. // // Moving this into the calling package was rejected: it would duplicate // runGitCode's exit-code handling into a second place, which is exactly how @@ -674,8 +671,8 @@ func InspectDir(dir string) DirGit { // git reports an unreadable refs/heads/ with its documented negative // rather than with 128. A repository whose refs cannot be read therefore // reaches the ignore check as though it had no commits, and lands on - // DirVisible or DirIgnored — the permissive side. Pre-existing, recorded - // on issue #12, not fixed here. + // DirVisible or DirIgnored — the permissive side. Distinguishing these + // cases remains an unresolved limitation of this exit-code check. _, ans, code := runGitCode(top, "rev-parse", "--verify", "--quiet", "HEAD") switch { case ans == gitYes: diff --git a/internal/scan/git_test.go b/internal/scan/git_test.go index cab28f9..95afcc7 100644 --- a/internal/scan/git_test.go +++ b/internal/scan/git_test.go @@ -17,13 +17,9 @@ func requireGit(t *testing.T) { } } -// This file used to carry its own git() and gitOutput(), each building the -// isolated environment the assertions depend on. They now live in testfs -// alongside the breakage fixtures, for the reason issue #12 is an audit rather -// than six fixes: a second copy of a rule drifts from the first, silently, and -// this bug class is what drift in a git probe looks like. One definition of -// "a git command under test" means a fixture written next year gets the same -// isolation and the same self-checks as the ones written today. +// Git helpers live in testfs alongside the breakage fixtures so every test +// uses the same isolated environment and fixture self-checks. Keeping that +// setup in one place prevents git-probe tests from drifting apart. // initGitRepo makes a repository at dir with no commits yet. func initGitRepo(t *testing.T, dir string) { @@ -497,8 +493,8 @@ func TestCommittedInHEAD_FailsClosed(t *testing.T) { t.Run("git unavailable is unknown, never a no", func(t *testing.T) { // The two answers mean different things to a caller: one has a fix - // the user can carry out and the other does not. Collapsing them is - // the bug class issue #12 tracks. + // the user can carry out and the other does not. An unavailable git + // command must return unknown rather than claim the file is uncommitted. repo := filepath.Join(t.TempDir(), "project") mustMkdir(t, repo) p := filepath.Join(repo, "agent.md") @@ -540,8 +536,8 @@ func TestCommittedInHEAD_FailsClosed(t *testing.T) { // The recipe is deliberate rather than incidental: `ls-tree` reads only tree // objects, so deleting the blob leaves it answering perfectly well, while every // question about content has to go to the object that is gone. No permissions -// and no privilege are involved, so this runs identically for root — the same -// property issue #11 required of the walk fixtures. +// and no privilege are involved, so this runs identically for root and +// non-root users. func TestCommittedInHEAD_DamagedObjectStoreIsNeverAYes(t *testing.T) { requireGit(t) @@ -586,8 +582,8 @@ func TestCommittedInHEAD_DamagedObjectStoreIsNeverAYes(t *testing.T) { }) } -// The regression guards for issue #12's audit: three probes in probeGitRoot and -// gitProber.tracked that read a non-zero git exit as a clean negative. +// These regression guards ensure probes in probeGitRoot and gitProber.tracked +// distinguish failed git commands from clean negative answers. // // Every one of these passed before the fix, because every one of them was // unreachable from a repository that works. That is the property of this bug @@ -709,8 +705,8 @@ func TestProbeGitRoot_UnbornHeadIsTheOnlyNo(t *testing.T) { // upstream can catch it because --show-toplevel still exits 0. Read as // "unborn" it became TrackedNo, then CommittedNo, then import --update // telling the user to commit project-agents/ in a repository whose refs - // git cannot parse — the wrong remedy this issue exists over, reached by - // a route the first fix did not cover. + // git cannot parse. A corrupt refs file must be reported as a probe failure, + // since committing cannot repair it. t.Run("a corrupt packed-refs is not an unborn HEAD", func(t *testing.T) { project := filepath.Join(t.TempDir(), "project") testfs.GitRepo(t, project, "agent.md", "content\n") diff --git a/internal/scan/home_test.go b/internal/scan/home_test.go index 0e74c49..b40036a 100644 --- a/internal/scan/home_test.go +++ b/internal/scan/home_test.go @@ -25,8 +25,8 @@ func agentsDirIn(projectDir string) string { return filepath.Join(projectDir, ".claude", "agents") } -// TestCheckHome_Statuses is the heart of the issue: /var/root is a valid, -// readable, resolvable directory, so a validity check passes it and the wrong +// TestCheckHome_Statuses guards against a valid but wrong home: /var/root is +// readable and resolvable, so a validity check passes it and the wrong // keys go unreported. Only the keys the projects actually get separate a wrong // home from a right one. func TestCheckHome_Statuses(t *testing.T) { @@ -130,8 +130,8 @@ func TestCheckHome_Statuses(t *testing.T) { // // Under `sudo -i` with --root / or --root /Users, home sits UNDER the root, so // any root-relative test concludes "fine" — while every project key is -// silently re-based under _outside-home/. That is precisely the invocation -// shape the issue is about, and it must warn. +// silently re-based under _outside-home/. This must warn even though the +// scan root contains the configured home. func TestCheckHome_RootIsNotTheSignal(t *testing.T) { base := resolvePath(t.TempDir()) realHome := filepath.Join(base, "home") diff --git a/internal/scan/state.go b/internal/scan/state.go index 362077f..08f7102 100644 --- a/internal/scan/state.go +++ b/internal/scan/state.go @@ -216,9 +216,7 @@ func (r *Repo) Counterpart(project, name string) string { filepath.Join(r.Root, "agents", name+".md"), } // This conflates "no counterpart exists" with "the counterpart could not - // be stat-ed", and it is left that way deliberately — audited under - // issue #12 and recorded here so the next reader does not have to - // re-derive it. + // be stat-ed" deliberately: either result leaves the entry unprotected. // // Both collapse to "", which routes the entry to StateUnadopted. That // is the loud direction, not the quiet one: StateUnadopted.AtRisk() is @@ -505,9 +503,8 @@ func FindAgentDirs(root string) ([]string, []ScanProblem, error) { // Glob's contract is explicit — "Glob ignores file system errors such as I/O // errors reading directories. The only possible returned error is // ErrBadPattern" — so an unreadable agents directory produces `nil, nil`, -// byte-for-byte the answer an empty one produces. That is issue #12's shape -// with no git in it: one return value carrying both "there is nothing here" -// and "I could not look". +// byte-for-byte the answer an empty one produces. A single return value then +// carries both "there is nothing here" and "I could not look". // // It defeated the guarantee FindAgentDirs is written to provide a few lines up // — "a permission-denied directory must never be indistinguishable from an diff --git a/internal/scan/state_test.go b/internal/scan/state_test.go index bb65292..7f7f226 100644 --- a/internal/scan/state_test.go +++ b/internal/scan/state_test.go @@ -311,8 +311,8 @@ func TestFindAgentDirs_UnreadableRootIsFatal(t *testing.T) { // TestFindAgentDirs_UnreadableSubtreeIsAProblem guards the distinction between // "this subtree is empty" and "this subtree could not be read". // -// The unreadable subtree is built structurally rather than with chmod 000 -// (issue #11). Permission bits do not restrict root's walk, so the chmod form +// The unreadable subtree is built structurally rather than with chmod 000. +// Permission bits do not restrict root's walk, so the chmod form // of this test skipped on the CI gate, which runs as root — silently, since // `go test` prints nothing for a skip. testfs.UnreadableSubtree fails for root // exactly as it does for anyone else; see that package for why, and for the @@ -1221,8 +1221,8 @@ func TestKeyDependsOnHome_MatchesProjectDir(t *testing.T) { } } -// TestScan_UnreadableAgentsDirIsNotAnEmptyOne is issue #12's shape with no git -// in it, and the reason the audit was told to look past git at all. +// TestScan_UnreadableAgentsDirIsNotAnEmptyOne requires a read failure to be +// reported as a problem rather than as an empty agents directory. // // Scan asked filepath.Glob for the *.md files in an agents directory. Glob's // contract is explicit that it ignores filesystem errors and can return only diff --git a/internal/testfs/git.go b/internal/testfs/git.go index 18a5b18..6abc6a8 100644 --- a/internal/testfs/git.go +++ b/internal/testfs/git.go @@ -11,12 +11,9 @@ import ( // Git fixtures that force one arm of the three-way gitYes / gitNo / // gitUnavailable answer that internal/scan is built on. // -// Issue #12 is an audit of one bug class: a probe's "no" and its "could not -// answer" share a return value, and the caller reads the ambiguous value as -// the definite one. Seven members have now been found in this repo. Every one -// of them was invisible to the tests, because the tests only ever ran git -// against repositories that worked — and a probe that conflates "no" with -// "broken" is indistinguishable from a correct one until something is broken. +// A probe must distinguish "no" from "could not answer" so callers cannot +// mistake a failed probe for a definite result. Tests need broken repositories +// to expose that distinction; healthy repositories exercise only clean answers. // // The two fixtures the earlier fixes needed were non-obvious enough to be // worth not re-deriving, and the third — dubious ownership — is the realistic @@ -283,9 +280,7 @@ func CorruptPackedRefs(t testing.TB, dir string) { // rest of the test, with the same fatal it produces when a checkout is owned // by another user. // -// This is the fixture issue #12 singles out as the highest-value one and the -// realistic trigger for the worst instance of the bug class. It is not exotic: -// git refuses a repository whose worktree is owned by someone other than the +// Git refuses a repository whose worktree is owned by someone other than the // caller, which is what sudo, cron, a container running as root over a // bind-mounted host checkout, and a shared checkout on a build agent all // produce. Every probe then exits 128 in a repository that is completely diff --git a/internal/testfs/testfs.go b/internal/testfs/testfs.go index 6dcaebd..04e81ac 100644 --- a/internal/testfs/testfs.go +++ b/internal/testfs/testfs.go @@ -1,12 +1,12 @@ // Package testfs builds filesystem fixtures the test suites cannot build with // permission bits. // -// It exists for one reason, recorded in issue #11: the CI gate runs as root, -// and root's directory walk is not restricted by permission bits. Five tests -// covering the same property — an unreadable subtree must never read as a -// clean result — built their fixture with chmod 000, so all five skipped in CI -// on `os.Geteuid() == 0`. `go test` without -v prints nothing for a skip, so a -// green pipeline said "everything passed" when it meant "everything that ran +// The CI gate runs as root, whose directory walk is not restricted by +// permission bits. Five tests covering the same property — an unreadable +// subtree must never read as a clean result — built their fixture with chmod +// 000, so all five skipped in CI on `os.Geteuid() == 0`. `go test` without -v +// prints nothing for a skip, so a green pipeline said "everything passed" +// when it meant "everything that ran // passed", and the property those five guard is the exact defect that failed // import in review: exit 0 while paths went unread. // @@ -151,7 +151,7 @@ const padMax = 250 // way to report one, the scan says "this project has no agent files". // // It is the exact fixture needed to distinguish an empty agents directory from -// an unreadable one, which is issue #12's shape with no git in it. +// an unreadable one: failure to read must never mean "nothing is here". // // The mechanism is the same as UnreadableSubtree's — path length, not // permission, so root is not exempt — but it has to be tuned rather than diff --git a/internal/testfs/testfs_test.go b/internal/testfs/testfs_test.go index 774835d..6a046f8 100644 --- a/internal/testfs/testfs_test.go +++ b/internal/testfs/testfs_test.go @@ -14,7 +14,7 @@ import ( // TestUnreadableSubtree_IsUnreadableAndStopsTheWalk pins the two claims the // fixture makes, because five tests in three packages now rest on them and a // fixture that quietly stopped being unreadable would take their assertions -// with it — the same silent-pass failure mode issue #11 is about. +// with it, allowing those tests to pass without exercising an unreadable path. func TestUnreadableSubtree_IsUnreadableAndStopsTheWalk(t *testing.T) { root := t.TempDir() locked := filepath.Join(root, "locked") @@ -73,8 +73,8 @@ func TestUnreadableSubtree_IsUnreadableAndStopsTheWalk(t *testing.T) { // TestNoTestGuardsOnRoot is the recurrence guard. // -// Issue #11 was not one bad test; it was a habit. Five tests reached for -// chmod 000 because it is the obvious way to make a directory unreadable, and +// Permission-based unreadability fixtures do not work under root. Five tests +// reached for chmod 000 to make a directory unreadable, and // every one of them then had to skip under root, invisibly, because `go test` // prints nothing for a skip. The considered alternative — an env var CI sets // that turns a privilege-guarded skip into a failure — makes the gap visible