Settle the fail-fast decision surface design
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -123,15 +123,27 @@ The tombstone model is **retired** — no `deleted:` key, no in-place flags, no
|
||||
|
||||
## Malformed input — fail fast
|
||||
|
||||
Loud, specific error (path + what's wrong) for: unparseable YAML, missing required fields (`schema`; `order` where required), `schema` newer than the app, board root without `index.md`. No partial loads. The only tolerated absence is a missing `index.md` below the root (skip + warn, per Rules above). An explicitly null value (`order:` with nothing after it, `order: null`) reads as **missing** (settled): on a required field that is the missing-required-field rejection — it describes what the hand-editor actually did, started the key and never gave it a value; on an optional field, null is simply absent and the default applies.
|
||||
Loud, specific error (path + what's wrong) for: unparseable YAML, missing required fields (`schema`; `order` where required), `schema` newer than the app, board root without `index.md`. No partial loads — the only partial load is the decision surface's user-chosen Skip (below), and the only tolerated absence is a missing `index.md` below the root (skip + warn, per Rules above). An explicitly null value (`order:` with nothing after it, `order: null`) reads as **missing** (settled): on a required field that is the missing-required-field rejection — it describes what the hand-editor actually did, started the key and never gave it a value; on an optional field, null is simply absent and the default applies.
|
||||
|
||||
Fail-fast is the **initial-load** contract. Once a board is open, a failed live reload does not blank the board: the window keeps the last good snapshot and surfaces the same loud specifics in a non-modal banner — see 02-architecture.md's live-reload resilience.
|
||||
|
||||
**The decision surface (settled 2026-07-31).** Blocking means one aggregated surface hosted by the pre-snapshot loading window (02-architecture.md ▸ the loading state) — the loading content transforms in place, never a sheet over a spinner. It appears on **attended opens only** (welcome click, File ▸ Open…, Finder): restoration failures keep the retire-to-welcome-row landing, and the row's retry click is the attended open that then shows the surface — repair is an attended act, and launch never chains dialogs. The loader collects every fail-fast defect in the walk rather than stopping at the first. The surface groups defects **by class**: each class section states the defect once, lists the affected files (path + specifics, each with Reveal in Finder and Open in Editor), and carries one class-level choice preselected to its default, with per-item override behind a disclosure. The vocabulary offers only honest choices per class:
|
||||
|
||||
- *Unparseable YAML* — no app-minted rewrite (the app would be guessing at content): Open in Editor + Re-check, or Skip.
|
||||
- *Missing/null/malformed `order`* — minted lossless repair, the default: place at the bottom of its container (max + 1024). Order is app-owned; nothing user-authored is lost.
|
||||
- *Missing `schema`* — stamp `schema: 1`, the default: reliable exactly because the walk just validated the rest of the file against schema 1.
|
||||
- *`schema` newer than the app* — no honest fix (downgrading risks silent loss): an unfixable row ("made by a newer Lanework — update the app") offering only Skip; on the board root it blocks the whole board (Cancel is the only exit).
|
||||
- *Board root without `index.md`* — minted repair, the default: create a board index (folder-name title, `schema: 1`) — the user just opened this folder as a board, and the mint is content-lossless.
|
||||
|
||||
**Skip is user-consented tolerance, loudly marked** (ruled 2026-07-31): a skipped item loads the board without it — the file stays on disk untouched, tolerated-invisible like strays — and the opened board carries a warning-tone notice naming the skipped items, each with Reveal in Finder. Skips are **per-open decisions, never persisted**: the next open of a still-broken board presents the surface again — the notice is the honest residue of this open, not a stored preference.
|
||||
|
||||
Buttons: **Repair and Open** applies every chosen fix in one write bracket — each repaired `index.md` is an ordinary app write (stamps `modified`, clears `modified-by`); interrupted batches are accepted per the renumber precedent, every intermediate state valid, and a partial repair simply re-aggregates on the next walk. On Pro boards the repairs drop heal-marked receipts and commit separately as one repair commit, never folded into anyone else's work (Validation and healing below ▸ Healing mutations commit separately). **Cancel** aborts the open: the window retires and the board lands row-level on welcome, record-before-load unchanged. **Re-check** — and Repair and Open itself — re-runs the whole walk: a disk changed underneath re-aggregates into the *same* surface with the fresh defect list, never a chained second dialog; a clean walk proceeds to the first snapshot.
|
||||
|
||||
## Validation and healing — the integrity taxonomy (settled 2026-07-29)
|
||||
|
||||
Every rule in this document that refuses, tolerates, recovers, or repairs is an instance of **one five-verdict taxonomy**, owned by one vocabulary — the **integrity service** (`IntegrityRules` + `HealScheduler`, 02-architecture.md ▸ Components). A detectable defect classifies into exactly one verdict, and the verdict fixes everything downstream — surface, write behavior, race posture — so no mechanism ever re-reasons its posture individually. The doctrine behind every lenient verdict (stated 2026-07-29): **leniency is recovering recoverable issues through reliable heuristics owned by this service — never accepting loss that could surprise the user.** Where a reliable recovery exists, it applies (silently where nothing is lost); where it doesn't, the defect surfaces — refusal and alerting are the honest ends of the same scale, and "proceed partially, lose a little" is never a verdict:
|
||||
|
||||
- **Refuse** — fail-fast (Malformed input above): the defect defeats rendering or ordering. Initial-load refusal; once open, the reload-breakage condition (02-architecture.md). **Refusal is the healer's last resort, and no longer a dead end** (re-ruled 2026-07-29 — fail-fast is *gated by the integrity service*): a defect with a reliable heal heals and moves on (a non-blocking notice at most); only an unhealable defect blocks, and blocking means a **decision surface** — the load pauses, the user chooses the fix, the fix applies, the load proceeds. Never a modal chain: the loader collects every fail-fast defect in the walk rather than stopping at the first, and one aggregated surface presents them all. The surface's design — per-defect-class decision vocabulary, aggregation shape, reconciliation with the welcome failure row, record-before-load, and the pre-snapshot loading state — is its own design pass (tracked on the findings board).
|
||||
- **Refuse** — fail-fast (Malformed input above): the defect defeats rendering or ordering. Initial-load refusal; once open, the reload-breakage condition (02-architecture.md). **Refusal is the healer's last resort, and no longer a dead end** (re-ruled 2026-07-29 — fail-fast is *gated by the integrity service*): a defect with a reliable heal heals and moves on (a non-blocking notice at most); only an unhealable defect blocks, and blocking means a **decision surface** — the load pauses, the user chooses the fix, the fix applies, the load proceeds. Never a modal chain: the loader collects every fail-fast defect in the walk rather than stopping at the first, and one aggregated surface presents them all. The surface's design is settled (2026-07-31) — Malformed input above ▸ The decision surface.
|
||||
- **Refuse writes** — readable-but-uneditable shapes (Frontmatter above): the file renders fine, and every app write to it fails loudly per file.
|
||||
- **Tolerate** — outside the schema's claim: strays, symlinks, case-twins, lane- and board-level `deleted:`. Preserved verbatim, logged, never rendered (or rendered with the key inert).
|
||||
- **Coerce** — a sensible reading exists (the coercion rulebook, last-wins, null-as-missing, the rescue family): silent, read-side only, bytes preserved — the recovered value is exactly what the writer meant.
|
||||
|
||||
Reference in New Issue
Block a user