Realign code with the 2026-07-31 findings-resolution rulings

The full bullet list from Implementation card bf080d9a — both ruling
batches, including the three appended mid-session by 16ef377:

- Restore subjects compose the inverse, never nest: crossing "Undo: S"
  emits "Redo: S" and vice versa; parity, not stack depth, reads a
  legacy double prefix (GitHistoryProvider.restoreSubject).
- Git-operation failures join the one-shot failure banner tier:
  BannerCenter.GitFailureBanner (undo/redo/branchSwitch/addGit), error
  tone at failure rank merged with write one-shots by recency; the
  postLoss compromise is retired at both AppModel wirings.
- order/schema optional below the board root: append-at-end reading
  (ordered siblings first, folder-name tie-break among the order-less),
  schema reads 1, both coerce-tier logged; the root keeps its
  requirements. Ranks.resolvedOrders materializes finite ranks so
  models and placement math stay untouched; first Writer rewrite
  stamps a real rank on touch, placement against an order-less sibling
  stamps that sibling inline in the same bracket. Agent guide v10
  teaches optional keys and zero-read filing. Hostile-YAML order
  shapes become coercion tests; Fixtures/Valid/optional-keys.kanban
  replaces the four retired Malformed boards.
- .gitignore is the relocation-heal noise gate: GitignoreRules pure
  matcher (standard semantics, board-root file only), loader consults
  it once per walk so matched loose files keep the stray posture;
  seeded (.DS_Store + .*.lanework-*) at board creation and template
  instantiation, healed in when missing at open — repo-nested
  included; empty file honored, existing files never edited; the
  committer's obedience via libgit2 status is pinned by test.
- Comments crash-residue sweep gates on step ownership: HistoryStep
  derives backing from its own undo expectations, backedContent unions
  both stacks, the sweep purges per-entry only what no live step owns.
- Skip-purge decoupled (16ef377): a stale-skipped coarse step strands
  whole in NativeHistoryProvider.strandedSteps — still backing, retired
  only at session end; clean exits purge as before.
- Coarse close step named "Changes to '<card>'"; the fine body-edit
  wording never leaks onto the board menu.
- Branch-switch settle clears every open card window's fine stack on
  Save All and Discard alike; the empty fold registers no coarse step.
- Close flush awaits its covering snapshot (quiesce + one generation
  bump, 1s bound), and an explicit flush now queues behind an
  in-flight one instead of skipping — the audit-caught interleaving
  could lose a close flush permanently when the debounce fired inside
  the close sequence; regression tests force both races.
- Commit comment bullets sort chronologically by created, not UUID.
- The production-unwired CardBodyEditSession.editSessionDidChange seam
  is deleted with its seam-only tests.
- Composition-root pins: beginSession composes the committer with the
  store's own EchoLedger and binds the announcer (the miswire class).
- Deliberate 06 conformance pass over every 2026-07-31-tagged
  sentence: fixed Change-custom-key subjects (the retired named
  generic was the only producer), the unbuilt Replace attachment
  vocabulary, heal commits now authored Lanework Integrity, the config
  reader scopes identity to plain [user] sections, add-git re-runs
  detection at create (a stale mode-none could initialize inside the
  user's repo), and add-git failures answer at the form or the banner.
  Structural residue filed on the Redesign board.

2554 tests / 439 suites green.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
2026-08-01 07:43:45 -04:00
parent 16ef3779e8
commit 274ccd9ff5
75 changed files with 5619 additions and 791 deletions
+90 -23
View File
@@ -172,14 +172,6 @@ struct IntegrityValidationTests {
private func bytes(_ text: String) -> Data { Data(text.utf8) }
/// The per-kind field table: `order` is required on lanes and cards, **never** on the board.
@Test("Order is required per kind")
func orderIsRequiredPerKind() {
#expect(!IntegrityRules.requiresOrder(.board))
#expect(IntegrityRules.requiresOrder(.lane))
#expect(IntegrityRules.requiresOrder(.card))
}
@Test("A board index validates without an order")
func boardValidatesWithoutOrder() throws {
let document = try IntegrityRules.validateIndex(
@@ -191,33 +183,71 @@ struct IntegrityValidationTests {
#expect(document.title == .valid("Board"))
}
@Test("A lane or card index without an order is refused")
func laneAndCardRequireOrder() {
/// **`order` is optional at every kind** (01-storage-format.md § Ordering, re-ruled 2026-07-31):
/// a lane or card without one reads as append-at-end rather than being refused, so the validator
/// has nothing to say about it.
@Test("A lane or card index without an order validates")
func laneAndCardDoNotRequireOrder() throws {
for kind in [IntegrityRules.ObjectKind.lane, .card] {
#expect(throws: BoardLoadError(path: "index.md", reason: .missingOrder)) {
try IntegrityRules.validateIndex(
bytes("---\nschema: 1\n---\nbody\n"),
path: "index.md",
kind: kind,
supportedSchema: 1
)
}
let document = try IntegrityRules.validateIndex(
bytes("---\nschema: 1\n---\nbody\n"),
path: "index.md",
kind: kind,
supportedSchema: 1
)
#expect(document.order.isMissing)
}
}
/// **The root's `schema` is required; below it, absence reads as 1** the one per-kind
/// difference the validator still draws.
@Test("Schema is required at the board and optional below it")
func schemaIsRequiredAtTheRootOnly() throws {
let schemaless = bytes("---\ntitle: No Schema\n---\nbody\n")
#expect(throws: BoardLoadError(path: "index.md", reason: .missingSchema)) {
try IntegrityRules.validateIndex(
schemaless, path: "index.md", kind: .board, supportedSchema: 1)
}
for kind in [IntegrityRules.ObjectKind.lane, .card, .comment] {
let document = try IntegrityRules.validateIndex(
schemaless, path: "index.md", kind: kind, supportedSchema: 1)
#expect(document.schema.isMissing)
}
}
/// The card window's gate is this rule at `kind: .card` one function, not a copy.
@Test("validateCardIndex is validateIndex at card")
func cardValidatorIsTheGeneralOne() {
let missingOrder = bytes("---\nschema: 1\n---\nbody\n")
#expect(throws: BoardLoadError(path: "index.md", reason: .missingOrder)) {
try BoardLoader.validateCardIndex(missingOrder, path: "index.md")
}
func cardValidatorIsTheGeneralOne() throws {
// No `order`, no `schema` the minimum agent card, and a legal raw-source Apply since
// 2026-07-31.
let minimum = try BoardLoader.validateCardIndex(bytes("---\ntitle: Minimum\n---\nbody\n"), path: "index.md")
#expect(minimum.title == .valid("Minimum"))
let newer = bytes("---\nschema: 99\norder: 1\n---\n")
#expect(throws: BoardLoadError(path: "index.md", reason: .schemaNewerThanApp(found: 99))) {
try BoardLoader.validateCardIndex(newer, path: "index.md")
}
}
/// The rulebook's own readings, without a filesystem in the way.
@Test("The order reading is stated over usability")
func orderReadingIsStatedOverUsability() throws {
func reading(_ frontmatter: String) throws -> (order: Double?, coerced: CoercedField?) {
IntegrityRules.resolvedOrder(in: try FrontmatterDocument.parse("---\n\(frontmatter)---\n"))
}
#expect(try reading("order: 1024\n").order == 1024)
#expect(try reading("order: 1024\n").coerced == nil)
for (frontmatter, raw) in [
("schema: 1\n", ""), ("order:\n", ""), ("order: null\n", "null"),
("order: banana\n", "banana"), ("order: .nan\n", ".nan"), ("order: .inf\n", ".inf"),
] {
let read = try reading(frontmatter)
#expect(read.order == nil, "\(frontmatter) should be unusable")
#expect(read.coerced == CoercedField(key: "order", raw: raw), "\(frontmatter)")
}
}
/// The refuse-writes verdict's rule, named in the vocabulary rather than left as a property one
/// call site happens to read.
@Test("The uneditable shape is the document's, named here")
@@ -275,6 +305,43 @@ struct IntegrityOnTouchTests {
#expect(IntegrityRules.healOnTouch(&document, kind: nil).isEmpty)
#expect(document.kind == .missing)
}
/// **The rank materializes on touch** (01-storage-format.md § Ordering, re-ruled 2026-07-31):
/// the value written is the append-at-end reading the board was already rendering, so nothing
/// moves when the stamp lands.
@Test("A missing order is stamped with the rank it read as")
func missingOrderIsStamped() throws {
var document = try FrontmatterDocument.parse("---\nschema: 1\ntitle: Minimum\n---\nbody\n")
let heals = IntegrityRules.healOnTouch(&document, kind: .card, rank: 3072)
#expect(heals == [.kindBackfilled(.card), .rankStamped(3072)])
#expect(document.order == .valid(3072))
#expect(document.keys == ["schema", "title", "kind", "order"])
#expect(document.body == "body\n")
}
/// **Unlike `kind`, an unusable *present* value is replaced**: a rank has to be a number for the
/// midpoint math to mean anything, so `banana` and `.nan` heal exactly like an absent key.
@Test("An unusable order is stamped too")
func unusableOrderIsStamped() throws {
for text in ["order: banana", "order: .nan", "order:"] {
var document = try FrontmatterDocument.parse("---\nschema: 1\n\(text)\nkind: card\n---\n")
#expect(IntegrityRules.healOnTouch(&document, kind: .card, rank: 2048) == [.rankStamped(2048)])
#expect(document.order == .valid(2048), "\(text)")
}
}
/// A usable rank is never rewritten, and a caller with no rank to offer stamps nothing the
/// board root and a comment, which have no ladder to sit in.
@Test("A present rank, and a nil rank, stamp nothing")
func presentOrAbsentRankStampsNothing() throws {
var ranked = try FrontmatterDocument.parse("---\nschema: 1\norder: 1024\nkind: card\n---\n")
#expect(IntegrityRules.healOnTouch(&ranked, kind: .card, rank: 9999).isEmpty)
#expect(ranked.order == .valid(1024))
var rankless = try FrontmatterDocument.parse("---\nschema: 1\nkind: board\n---\n")
#expect(IntegrityRules.healOnTouch(&rankless, kind: .board, rank: nil).isEmpty)
#expect(rankless.order == .missing)
}
}
// MARK: - The defect vocabulary