The loader collects every fail-fast defect and honors per-open skips

Phase 1 of the decision surface (01 ▸ Malformed input, settled
2026-07-31): BoardLoadFailure aggregates the walk's defects in walk
order — stop-at-first retires. Environmental failures (unreadable root,
not-a-directory) stay immediate single-defect throws: there is no walk
to collect from. A defective root index is recorded and the walk
continues into the children (nothing in the walk consults the parsed
root document — verified); a defective lane, card, or trash-entry index
records and skips its subtree, Re-check's whole-walk re-aggregation
being the designed loop for what hides beneath. load(skipping:) is the
per-open skip channel: a skipped path's item is omitted from the model
and surfaces as LoadWarning.userSkipped; root paths are unskippable by
construction. The reload-breakage banner carries the aggregate ("…and
N more"), single-defect sentences byte-identical to before. Two new
multi-defect fixture boards; suite 2591 green.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
2026-08-01 09:12:49 -04:00
parent 94e60cd444
commit ba1726fa77
35 changed files with 897 additions and 117 deletions
+7 -7
View File
@@ -204,12 +204,12 @@ struct BoardStoreRegistryTests {
try fixture.item(Ident.lane1, brokenIndex)
let registry = BoardStoreRegistry()
do throws(BoardLoadError) {
do throws(BoardLoadFailure) {
_ = try registry.acquire(fixture.root)
Issue.record("expected the load to fail fast")
} catch {
if case .unparseableYAML = error.reason {} else {
Issue.record("expected unparseable YAML, got \(error.reason)")
if case .unparseableYAML = error.primary.reason {} else {
Issue.record("expected unparseable YAML, got \(error.primary.reason)")
}
}
@@ -265,15 +265,15 @@ struct BoardStoreRegistryTests {
let missing = FileManager.default.temporaryDirectory
.appendingPathComponent("no-such-board-\(UUID().uuidString)", isDirectory: true)
do throws(BoardLoadError) {
do throws(BoardLoadFailure) {
_ = try registry.acquire(missing)
Issue.record("expected a missing root to fail")
} catch {
// The identity read fails first, and the registry deliberately says nothing about that
// it lets `BoardStore`'s load produce the honest reason.
#expect(error.path == ".")
if case .unreadableRoot = error.reason {} else {
Issue.record("expected an unreadable root, got \(error.reason)")
#expect(error.primary.path == ".")
if case .unreadableRoot = error.primary.reason {} else {
Issue.record("expected an unreadable root, got \(error.primary.reason)")
}
}
#expect(registry.openBoardCount == 0)