An unopenable repository fails loudly — the standing row, the paused surface, the honest heal

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
2026-08-06 18:44:12 -04:00
parent 52df210284
commit 0d8ecdb78b
20 changed files with 938 additions and 42 deletions
+60
View File
@@ -732,6 +732,66 @@ struct AutoCommitContentionTests {
#expect(committer.commitCount == 0)
}
/// **The corrupt-`.git` loud failure's engine half** (06-history-undo.md Rules, ruled
/// 2026-07-31): "the board itself loads and edits normally files are the board but the
/// failure is loud with the whole git surface paused", and "the banner clears when a later
/// open or reload finds the repo readable".
@Test("An unreadable repository holds the engine, and heals when it opens again")
func anUnreadableRepositoryHolds() async throws {
let (fixture, git, _) = try await makeGitBoard()
defer { fixture.tearDown() }
let committer = try quickCommitter(git)
var transitions: [Bool] = []
committer.reportRepositoryUnreadable = { transitions.append($0) }
// What a half-copied or half-deleted `.git` looks like to libgit2: the layout no longer
// validates, so the repository will not open at all. Reversible, which is what makes the
// heal half of this test the real thing rather than a second fixture.
let head = fixture.root.appendingPathComponent(".git/HEAD")
let savedHead = try Data(contentsOf: head)
try FileManager.default.removeItem(at: head)
try fixture.item("\(Ident.lane1)/\(Ident.card2)", plain(order: "2048", title: "Second"))
committer.noteReloadLanded(sawForeignChange: true)
await committer.flushNow()
#expect(committer.pause == .unreadable)
#expect(committer.commitCount == 0, "nothing is attempted against a repository the app can't open")
#expect(committer.lastFailure == nil, "a pause is not a failure")
#expect(transitions == [true], "the banner is raised once, on the transition")
// "Edits keep landing on disk files are the board and commit as one settled batch when
// the state clears." Mid-session, the re-read that notices is the paused engine's own.
try fixture.item("\(Ident.lane1)/card-3", plain(order: "3072", title: "Third"))
try savedHead.write(to: head)
await committer.flushNow()
#expect(committer.pause == nil)
#expect(committer.commitCount == 1, "one settled batch, exactly as any other pause")
#expect(transitions == [true, false], "and the banner heals — it is a condition, not an event")
#expect(isClean(at: fixture.root))
}
@Test("The popover's own re-read learns the state without attempting anything")
func refreshPauseLearnsTheUnreadableRepository() async throws {
let (fixture, git, _) = try await makeGitBoard()
defer { fixture.tearDown() }
let committer = try quickCommitter(git)
let head = fixture.root.appendingPathComponent(".git/HEAD")
let savedHead = try Data(contentsOf: head)
try FileManager.default.removeItem(at: head)
await committer.refreshPause()
#expect(committer.pause == .unreadable)
#expect(committer.commitCount == 0)
try savedHead.write(to: head)
await committer.refreshPause()
#expect(committer.pause == nil)
}
@Test("An unborn HEAD is normal — the first settled change commits the whole tree")
func anUnbornHeadIsNormal() async throws {
let fixture = try makeBoard()