fix: bug-hunt round 8 — guided-apply domain consent, truthful capture summary, local-overlay terminal compare, deps error diagnostics, snapshot containment guard, exact release version gate - #37
Merged
Conversation
… summary, local-overlay terminal compare, deps error diagnostics, snapshot containment guard, exact release version gate Six code/infra fixes, each adversarially confirmed before fixing: - guided apply groups risky changes per FileDomain in registry order; a two-name literal switch had bucketed keybindings/emacs/iterm2-profiles under "dotfiles", so the bulk consent named the wrong domain and one "yes" spanned a mixed bucket - capture counts a brew/npm re-dump only when the manifest bytes changed (brew bundle dump --force rewrites unconditionally, so every clean run claimed "wrote 1 change(s)" over an empty git status); the npm list is written via backup.AtomicWrite and left untouched when identical - capture compares a terminal preference domain against the local-wins source (terminalRepoStatusSource), as apply/status/diff already do; a domain captured to [l]ocal was re-offered as drifted forever - the deps install/dump rails include the package manager's combined output in their errors, matching the uninstall rail, so a failed apply --deps names the failing package instead of "exit status 1" - snapshotCurrent runs the resolved-parent containment guard, covering the exported Snapshot entry point work receive uses; a parent symlink out of $HOME let the pre-receive snapshot persist out-of-home content - the release version-stamp gate compares exactly against "ferry <tag>" in release.yml and release.sh; the dev line is next-release-plus--dev, so the substring grep passed an unstamped binary on the modal release Docs: the README/tutorial "one privileged step" claim covers the apt uninstall rail (restore --packages runs apt-get as root); the compatibility contract names pack, receive, and take-back as claim-file writers; the commands reference gains the agents parent-noun row; make preflight enters the build-and-check lists; the consistency lint's private-tier advice mandates amending committed history. Assisted-by: Claude:claude-fable-5
…eps error site, empty-manifest change detection, groupRisky tests Addresses the final dual review of PR #37: - docs/reference/commands.md's apply row still enumerated the guided walkthrough groups as dotfiles/agents/terminals — the exact list the groupRisky fix deleted — and now names all six domains (the reviewer's one blocking finding); the twin stale comments in cmd/apply.go updated - internal/deps/status.go's read-only drift dump was a fifth site discarding the manager's output on error; now consistent with the other rails, and the CHANGELOG wording is accurate - a brew dump that CREATES an empty manifest now counts as a change: nil-vs-empty bytes compare equal, so reDump checks existence too - groupRisky gains the unit tests it shipped without (per-domain names, registry order, no lumping into dotfiles, preference-item fallback) - the dump.go change-detection comment and capture's "exactly as apply" claim corrected; CHANGELOG narrates the npm 0644 normalisation and receive's abort-first refusal ordering Assisted-by: Claude:claude-fable-5
Closed
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.
Round 8 of the autonomous bug-hunt loop (state: #29). Fresh hunt across the four dimensions with four hunters; 22 unique candidates, each handed to an independent adversarial refuter before any fix — 12 confirmed (8 substantive, 4 nitpick), 12 refuted, 2 hunter-self-refuted.
Confirmed and fixed — substantive
cmd/guided_apply.go:269-291.groupRiskyswitched on a two-name literal list (agents,terminals), so risky keybindings/emacs/iterm2-profiles changes fell into the defaultdotfilesbucket: the group header and the "Apply all N change(s) in dotfiles?" prompt named a domain the user was not reviewing, and one "yes" covered the mixed bucket (consequential on first apply over pre-existing files, where StateRepoAhead adopt/overwrite items pass the conflict refusal). Now grouped per FileDomain in registry order — the same hand-maintained-list classfileDomainIsRepoAuthoritative(cmd/registry.go:121-134) was introduced to end.cmd/capture.go:218-241,internal/deps/dump.go,internal/deps/npm.go.reDumpDeps/reDumpNpmGlobalsreturned true on any successful dump (brew bundle dump --forceand the npm list write rewrite unconditionally), so on any machine managing either domain a clean re-run printed "wrote 1 change(s) into the repo. Review withgit status…" over an emptygit status. Both re-dumps now byte-compare and count only real changes (TestReDump_Brew_ChangeDetection, extendedTestReDumpNpmGlobals_WritesSortedList).cmd/capture.go:1060-1078compared the live iTerm2/Apple Terminal export only against the shared plist, while apply (cmd/apply.go:1680), status, and diff resolve the per-machine local overlay first. After routing a capture to[l]ocal, every later capture re-offered the identical domain as drifted whilestatusreported it clean — and re-accepting[s]haredwould promote machine-divergent settings into the shared repo. Capture now compares against the sameterminalRepoStatusSourcelocal-wins source (newcmd/terminal_status_source_test.gopins the seam; the darwin-onlydefaultsexport has no injectable runner, so the harness cannot drive this end-to-end — stated plainly rather than papered over).internal/deps/install.go:129,227,npm.go:181,dump.go:54discarded the manager's CombinedOutput on error, soapply --depsaborted withexit status 1and no failing package named, while the uninstall rail (cmd/restore.go:522,544) already surfaced output. All four sites now carry the diagnostics (TestInstallBrew_FailureSurfacesManagerOutput, watched fail pre-fix).internal/backup/snapshot.go:67-69vs the guarded siblingrestore.go:55-71.ferry work receive's pre-receive snapshot (internal/work/receive.go:190) read its target paths with no resolved-parent guard, so a parent symlinked out of$HOMElet the snapshot read and persist out-of-home content into the snapshot store — content the downstream write boundary refuses. Adversarial review downgraded severity (no route to~/.sshin practice; plan-time already reads the same bytes; blobs land 0600 in a 0700 store) but confirmed the invariant gap.snapshotCurrentnow runsguardResolvedContainment(resource paths skipped) before creating or reading anything, covering both entry points identically (TestSnapshotRefusesParentSymlinkEscapingHome, watched fail pre-fix)..github/workflows/release.yml:338-344andscripts/release.sh:156-159usedgrep -qF "$TAG"onferry versionoutput. The dev line is next-release-plus--dev(cmd/root.go:17,v0.11.0-dev), so an unstamped binary's output contains the modal next tag and the substring match greened on exactly the moved-symbol-ldflagsfailure the step exists to catch. Both gates now require the output to equal the literal string "ferry " followed by the tag, verbatim (format confirmed against the built binary).Confirmed and fixed — docs/nitpick
README.md:99-103anddocs/tutorials/getting-started.md:19-22namedapply --depsas the sole privileged operation, butrestore --packagesrunsapt-get removeas root (cmd/restore.go:542; the code's own comments atinternal/deps/runner.go:32-33name both rails) and no user-facing page said so. Both claims now cover the full apt rail, and the apt reference section documents the uninstall half.docs/reference/compatibility.md:57said claim files are "written byferry work pack", but receive and take-back write them too (internal/work/receive.go:225,163), each account appending only to its own file (claim.go:18-21). The row now names all three writers, so a version-envelope change on receive's write path is in scope.internal/deps/npm.go:94was the lone repo-side manifest writer violating the documentedbackup.AtomicWritecanon (internal/backup/atomic.go:13-16) — now atomic (folded into fix 2).scripts/consistency-lint.sh:86's advice —git rm --cached— greens the gate while already-committed history still publishes the private tier on push; the advice now mandates amending the unpushed commits that carry it. (A range-based check was considered and rejected:@{push}..HEADbreaks CI's detached checkout and new branches underset -euo pipefail.)docs/reference/commands.mdlacked theagentsparent-noun row itsbundle/worksiblings have.make preflight(the pre-push gate, added round 7) appeared in no build-and-check list; now inAGENTS.mdandCONTRIBUTING.md.Considered and rejected (refuted)
secret.GateValuealready rated that content High — the re-export re-trips the gate, and the iTerm2 allowlist admits no secret-bearing key. Unreachable without a hand-authored placeholder over non-High content.sudo mkdir: round-1 adjudication stands — a host-OS setup example, not a ferry operation; README's claim scopes to ferry's own operation, and on MDM-hardened/Users/Sharedthe sudo form is the one that works.releaseworkflow then…"), which already omits whole verify jobs documented elsewhere.+-metadata tags becominglatest: SemVer-correct and deliberate per the workflow's own comment (release.yml:410-412); the sanctioned release path refuses such tags first.[work]missing from the compatibility schema surface:[work]lives in machine config, neverferry.toml(internal/config/machine.go:31-35) — the enumeration is correct.plans/README.md:29states theshipped in vX.Y.Zconvention universally; a version with no plan is documented as fine.behavioralinevals/README.md: mirrors the package's own doc comment (evals/harness.go:1); the root-markdown prose-tier rule does not reach component READMEs.~/.githooksdispatcher is deliberately external and per-machine; the hazard population equals the hook population, and an in-repocore.hooksPathwould suppress the dispatcher's other hooks.init --githubnamesferry sync), and the path-keyedmanagedcarry (no privilege delta — sync independently re-enforces origin scheme, secret gates, and no-force push). Both stay on the carry-forward list.Gates
make build,gofmt -l .(empty),go vet ./...,go test ./...,go test -race ./internal/..., the full eval suite withFERRY_BINset, andscripts/consistency-lint.share all green on this branch. New tests for fixes 4 and 5 were watched failing against the pre-fix code; fix 2's change-detection tests construct the pre-fix-impossible assertions directly.