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

Nine rulings land as code. Reorders don't stamp — one container-change
predicate (WriteOperation.rewritesOrderOnly): within-container reorders
and the renumber rescale rewrite only order, while cross-lane, cross-board,
and trash moves stamp modified and clear modified-by; no trash special
case exists, and the m8 undo inverses conform through the same seam.
Copies are transactions: the root-strict/nested-lenient split retires for
a whole-subtree stampability preflight that refuses loudly naming the
offender, and every item-level copy severs remote/remote-state at every
level (whole-board forks carry them verbatim). Paste refuses, never
degrades: the embedded-index.md materialization and its loss row retire;
a missing staged snapshot produces nothing and posts an error-tone
one-shot named from manifest metadata. Coerce-tier fallbacks log through
the Defect stream with path context attached loader-side. Displacement is
level-uniform: a file squatting attachments inside a card heals by the
same rename ladder as board-root squatters; comments stays tolerated.
Delete Immediately joins card and lane context menus as Delete's
⌥-alternate with its own VO custom action, routed through an explicit
container so the menu target outranks standing selection. Agent guide v7
teaches the stamp discipline and the card-level attachments claim, and
sheds two stale v6 lines (lanes trash now; kind is taught). Verified
conformant, unchanged: edition-aware Undo/Redo disable, trash marquee
full-height backdrop.

Both schemes 1854 tests / 318 suites green; verify-editions 30/30.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
2026-07-30 06:49:11 -04:00
parent 5ae48de0ea
commit 69084fdff7
27 changed files with 2159 additions and 542 deletions
+157 -5
View File
@@ -182,6 +182,30 @@ public enum IntegrityRules: Sendable {
ClaimedName(name: ".gitignore", expected: .file, displacesSquatters: false),
]
/// **The card-level claimed names** the same table one level down (01-storage-format.md § Fractal
/// layout Rules, extended 2026-07-29: "**The rule is level-uniform**: a card's reserved child
/// names are claimed the same way a regular file or symlink squatting `attachments` (a directory
/// name) displaces by the same ladder (`attachments` `attachments 2`), so imports, Finder drops,
/// and the sidebar listing never fail one gesture at a time against a squatted name").
///
/// **`attachments` displaces; `comments` does not**, and the split is the *timing principle*
/// rather than a hedge 01 calls the reserved-but-unconsumed `comments` "the timing principle's own
/// illustration": nothing reads that name until the tracker era, so a wrong-kind holder degrades no
/// behavior while it stands and stays a **tolerated stray** today, joining the scheduled class the
/// day the name becomes load-bearing. `attachments`, by contrast, is load-bearing now: while a file
/// wears the name, every import into that card, every Finder drop on it, and the card window's
/// listing are broken which is exactly the "proactive when the defect is load-bearing now"
/// condition (§ Validation and healing).
///
/// `index.md` is deliberately not here. It is not a *reserved child* the app protects from
/// squatters it is the card's content, and a directory named `index.md` makes the folder an
/// index-less stray the loader already skips with a warning (§ Fractal layout Rules). Displacing it
/// would mean the app deciding a folder's content is a squatter.
public static let claimedCardChildNames: [ClaimedName] = [
ClaimedName(name: attachmentsFolderName, expected: .directory, displacesSquatters: true),
ClaimedName(name: "comments", expected: .directory, displacesSquatters: false),
]
/// The claimed names as the lane walk needs them: lowercased, for a `contains` against a
/// directory entry. Compared lowercased for `reservedCardChildNames`' reason.
public static let claimedRootNameSet: Set<String> = Set(claimedRootNames.map { $0.name.lowercased() })
@@ -222,6 +246,38 @@ public enum IntegrityRules: Sendable {
return ClaimedNameSquatter(name: claimed.name, found: found, expected: claimed.expected)
}
/// The claimed-name defects inside one **card** folder the level-uniform half of the same ruling
/// (`claimedCardChildNames`, extended 2026-07-29).
///
/// Only names whose `displacesSquatters` is `true` can produce one, which today means `attachments`
/// and only `attachments`: a `comments` held by the wrong kind of node is a tolerated stray until
/// the feature consumes the name.
///
/// **A plural answer, unlike the board root's**, because the reason the root's is singular does not
/// apply here: there, the second displacing name (`CLAUDE.md`) is the agent guide's own to heal, so
/// answering it twice would be two mechanisms racing one node. Nothing else owns a card's children,
/// so this returns every offender it finds and the table stays the only thing to edit when
/// `comments` graduates.
///
/// - Parameter path: the card folder's path **relative to the board root**, carried into the defect
/// so the write lands wherever the board lives at heal time (`LooseCardFiles`' convention).
public static func squattedClaimedNames(inCardAt cardFolder: URL, path: String) -> [ClaimedNameSquatter] {
claimedCardChildNames.compactMap { claimed in
guard claimed.displacesSquatters,
let found = node(at: cardFolder.appendingPathComponent(claimed.name)),
found != claimed.expected
else {
return nil
}
return ClaimedNameSquatter(
name: claimed.name,
found: found,
expected: claimed.expected,
location: .card(path: path)
)
}
}
// MARK: - Object kinds
/// The kinds the schema knows (01-storage-format.md § Frontmatter Common to all levels, the
@@ -454,6 +510,20 @@ public enum IntegrityRules: Sendable {
/// and reminted by the scheduled heal (re-ruled 2026-07-29 the silent remint).
case duplicateIdentity(DuplicateIdentity)
/// **A coerce-tier fallback**: one file's lenient fields that had no sensible reading and
/// rendered as their defaults (ruled 2026-07-29 "A no-sensible-reading fallback logs: field,
/// path, and raw text, carried as coerce-tier entries in the integrity service's Defect
/// stream").
///
/// **The one case in this enum that is not work**, which is the ruling rather than a
/// contradiction: the design puts coerce-tier fallbacks in *this* stream on purpose, because
/// this is where "an observed-in-the-wild shape can later be promoted to a heuristic heal or a
/// notice" and the promotion would happen right here, by giving the case a heal class. Until
/// then it has none (`healClass` answers `nil`), raises no banner, and changes no behavior: the
/// value already rendered as its default and the bytes on disk are untouched. It is
/// observability, carried in the vocabulary that would act on it if the app ever decided to.
case coercedFrontmatter(CoercedFrontmatter)
/// The scheduled-heal classes, which are also the engine's memo keys and its
/// banner-posture rows (`HealScheduler`).
///
@@ -470,12 +540,18 @@ public enum IntegrityRules: Sendable {
case staleAgentGuide
}
public var healClass: Class {
/// The scheduled-heal class this defect belongs to, or **`nil` where there is no heal** the
/// coerce tier (`coercedFrontmatter`), which is carried for observability and acted on by
/// nothing. Optional rather than a synthetic class, because a class *is* a memo key and a
/// banner-posture row in the engine (`HealScheduler`): inventing one for work that does not
/// exist would arm a memo against a repair nobody wrote.
public var healClass: Class? {
switch self {
case .looseCardFiles: .looseCardFiles
case .legacyTombstone: .legacyTombstone
case .claimedNameSquatted: .claimedNameSquatted
case .duplicateIdentity: .duplicateIdentity
case .coercedFrontmatter: nil
}
}
@@ -495,13 +571,21 @@ public enum IntegrityRules: Sendable {
case let .claimedNameSquatted(work):
// The node *kind* is part of the picture: a squatter replaced by a different kind
// of squatter is a new defect, and a heal that failed on one has no claim to have
// failed on the other.
["claimed:\(work.name):\(work.found.rawValue)"]
// failed on the other. The location leads, so two cards squatting `attachments` are two
// pieces of work and a board-root squatter signs exactly as it always did.
["claimed:\(work.location.signatureComponent)\(work.name):\(work.found.rawValue)"]
case let .duplicateIdentity(work):
// The *identity* is part of the picture beside the path: the same folder losing a
// different collision (its winner reminted, a third copy landing) is new work, and a
// heal that failed on one has no claim to have failed on the other.
["duplicate:\(work.path):\(work.identity)"]
case let .coercedFrontmatter(work):
// One signature per field, `looseCardFiles`' shape: the unit of the observation is a
// field, and a file whose `width` healed while its `icon` did not is a changed picture.
// Nothing memoizes these today there is no heal to guard but a signature is what a
// defect *is* in this vocabulary, and omitting it would make this case the one members
// of the stream cannot be compared by.
work.fields.map { "coerce:\(work.path):\($0.key)" }
}
}
}
@@ -805,18 +889,59 @@ public struct LegacyTombstone: Sendable, Equatable {
/// **An invalid artifact, not a resident.** The heal moves it aside via the Finder-style rename
/// ladder (`.trash` `.trash 2`), preserved verbatim and never destroyed, with a warning-tone
/// notice naming old and new the invariant that survives is displacement-never-destruction.
/// **The rule is level-uniform** (extended 2026-07-29): a card's `attachments` is claimed exactly as the
/// board root's `.trash` is, and displaces by the same ladder which is why `location` exists rather
/// than a second defect type. The heals compose: the displaced file, now an ordinary loose file, rides
/// the next loose-file relocation into the real `attachments/`.
public struct ClaimedNameSquatter: Sendable, Equatable {
/// The claimed name, exactly as the app spells it (`.trash`, `CLAUDE.md`).
/// **Which claimed name this is** the board root's, or one card's reserved child.
///
/// A path rather than a URL, relative to the board root, so the heal joins it onto the store's
/// *current* root and a board renamed mid-session heals at its new location (`LooseCardFiles`' and
/// `IdentityOccurrence.path`'s convention).
public enum Location: Sendable, Equatable {
case boardRoot
case card(path: String)
/// The folder the claimed name lives in, under `root`.
public func folder(under root: URL) -> URL {
switch self {
case .boardRoot: root
case let .card(path): root.appendingPathComponent(path, isDirectory: true)
}
}
/// The location as a signature component `""` for the board root, so the existing root-level
/// signature spelling is unchanged and only a card-level defect adds a path segment.
var signatureComponent: String {
switch self {
case .boardRoot: ""
case let .card(path): path + "/"
}
}
}
/// The claimed name, exactly as the app spells it (`.trash`, `CLAUDE.md`, `attachments`).
public let name: String
/// What is actually sitting there never followed if it is a symlink.
public let found: IntegrityRules.NodeKind
/// What the app needs the name to be.
public let expected: IntegrityRules.NodeKind
/// Where the name lives. Defaults to the board root, which is where every squatter was before the
/// rule went level-uniform so the root-level call sites and their tests read exactly as they did.
public let location: Location
public init(name: String, found: IntegrityRules.NodeKind, expected: IntegrityRules.NodeKind) {
public init(
name: String,
found: IntegrityRules.NodeKind,
expected: IntegrityRules.NodeKind,
location: Location = .boardRoot
) {
self.name = name
self.found = found
self.expected = expected
self.location = location
}
}
@@ -855,6 +980,33 @@ public struct DuplicateIdentity: Sendable, Equatable {
}
}
/// One file's **coerce-tier fallbacks**: the lenient fields whose value had no sensible reading, so the
/// field rendered as its default (01-storage-format.md § Frontmatter, ruled 2026-07-29: "A
/// no-sensible-reading fallback logs: field, path, and raw text, carried as coerce-tier entries in the
/// integrity service's Defect stream the one place where an observed-in-the-wild shape can later be
/// promoted to a heuristic heal or a notice; no banner, no behavior change").
///
/// **Per file, not per field** `LooseCardFiles`' shape and for its reason: the walk meets a document
/// once and reads all of its fields there, so one record per `index.md` is what the loader naturally
/// has, and a caller that wants per-field granularity has `fields` (and the per-field `signatures`).
///
/// The path is root-relative, as every load-side path in this app is (`BoardLoadError.path`,
/// `IdentityOccurrence.path`) it names the `index.md`, because that is the file whose bytes were
/// read and the thing a developer would open.
public struct CoercedFrontmatter: Sendable, Equatable {
/// The `index.md`'s path relative to the board root `"index.md"`, `"<lane>/index.md"`,
/// `"<lane>/<card>/index.md"`, `".trash/<card>/index.md"`.
public let path: String
/// The fields that fell back, in schema order. Never empty a document that read cleanly
/// contributes no defect at all.
public let fields: [CoercedField]
public init(path: String, fields: [CoercedField]) {
self.path = path
self.fields = fields
}
}
/// A folder whose name is a **case-spelled twin** of another occurrence of the same identity one
/// item typed two ways, not two items (01-storage-format.md § Fractal layout Rules: "the canonical
/// all-lowercase spelling wins where present, else the lexicographically first spelling; the loser