Changes touching this path

  • Spec the jj-ergonomics trio: ADRs 0029/0030/0031 (#138) (#141) * Dogfood drive: log day 2 (clean bridge day, 2/5) Day 2 was a git-clean day: the CA epic close-out had landed on main via PRs, so the ferry carried it across (4 commits ingested, 0 conflicts, 0 projected back; 146 objects pushed, resumable). Record the day in the drive log + evidence table (2/5). Also fix loot-day.ps1 doubling the "ferry:" prefix in the logged line (loot's own output already carries it). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Spec the jj-ergonomics trio: ADRs 0029/0030/0031 (#138) The hand-off deliverable of wayfinder map #132. Folds the three design decisions (#134/#135/#136) and the prototype's validated verb surface (#137) into three implementation-ready ADRs: - 0029 stable change-ids — durable random change_id beside the content version id; signature binds version_id ‖ change_id; divergence is a first-class data state; letters-vs-digits display; FORMAT_MAJOR 5→6. - 0030 implicit auto-snapshot + reconciled verb surface — snapshot on mutating verbs only; status read-only (-m dropped); new mints+prints the next change id; columnar log/status; divergent-change marker + `loot abandon`; demotion guard travels on the implicit snapshot; never signs. - 0031 operation log & undo — view-only undo over an append-only graph; op log grows on undo (redo); grant/maroon/pull-grants/push are non-undoable barriers; keyring/manifest/escrow/purges never touched; local-only .loot/ops. Points CONTEXT.md's out-of-scope note at the spec. Implementation graduates to a follow-on build map. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Git-Author: Connor Miller <53197564+Connor-Miller@users.noreply.github.com> 4403ce13
  • Merge branch 'main' of https://github.com/Connor-Miller/loot 2f8096b7 · dbf3dbe6…diff
  • One materialize chokepoint: pull/apply capture-first, converge waits (#219) pull/apply now capture uncaptured disk edits into the working change before they touch the tree, like every other mutating verb (ADR 0030 amendment). A dirty pull ingests (graph append is always safe) but DEFERS convergence — the working-change guard makes converge a no-op for that pass — and emits a note ("captured working change <id>; heads left unconverged — finalize then re-run"). A clean pull converges as before. The seam: one internal tree-write chokepoint. converge_heads evaluates dirtiness ONCE at entry (before any head is dropped, so the reference stays queryable) and the adopt/merge materialize paths refuse over uncaptured dirt rather than clobber it; undo/abandon resurface is exempt by intent. capture skips only a mid-flight transfer (anchor closure incomplete, via new engine closure_complete) — a genuine delete-all still captures, never refuses. - workspace: capture_uncaptured_edits, tree_is_dirty_over chokepoint, PullReport { outcomes, deferred }; converge_heads gates both writes on disk_dirty. - cli: cmd_pull renders the defer note + records the op; cmd_apply captures. - core: extract closure_complete (negotiation_have reuses it). - ADR 0030 dated amendment + CONTEXT.md glossary; #169 pull-over-dirty gotcha becomes a guarantee. - tests: dirty divergent pull, dirty independent-head pull (defer then converge-after-finalize), chokepoint invariant, delete-all-captures. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> d5dd28e4 · dbf3dbe6…diff
  • finalize refuses to sign an un-described change, and status's hint names describe -m (#174) `status`'s hint on a dirty tree pointed at `loot new`, but `new` is capture-*then*-finalize: following it signed the dirty tree in one stroke under the `(working change)` placeholder, skipped the review lane, and rode to git main as a permanent commit subject. The hint was the trigger; `new`'s willingness to sign a nameless change was the foot-gun. - The hint names `describe -m` — capture *without* finalize, the first verb on dirty work. - `finalize_capturing` refuses an un-described change (no message, or the stored placeholder). It sits *after* the capture, so edits are held and only the signature is withheld, and *below* the empty/duplicate drop, so a bare `new` on a clean tree stays a no-op. Both callers inherit it — `loot new` and `loot-first land` — so 'describe before landing' is enforced, not remembered. - Deriving a subject from changed paths was rejected: it mints plausible history nobody wrote, and loot has no changed-path concept to derive from. - The placeholder now has one mint (`working_message_or_placeholder`) and one test (`is_undescribed`); 10 stray literals collapsed onto them, so the guard cannot silently stop firing on a drifted copy. ADR 0030 amendment + CONTEXT/workflow/evidence updated. Residual (dock merge and ferry sign the operator's authored work in passing) named in the ADR and filed as #275. a8fb4684 · dbf3dbe6…diff
  • reject unknown flags on every verb instead of silently ignoring them (#67) e4583cb7 · dbf3dbe6…diff
  • merges refuse to sign an un-described change too, closing the #174 residual (#275) #174 stopped the *deliberate* finalize from signing a nameless change, but three verbs still sealed the operator's own work in passing, to make a signed merge parent: `dock merge` and the `adopt` catch-up (via `fold_line_in`), and `ferry` over a git main that moved (via `reconcile_capture`). Only the *trigger* was mechanical — the content was authored — so the placeholder still reached main. Reproduced against the pre-fix binary: a ferry over unnamed disk work landed the literal subject `(working change)` on git main in one silent pass. Both paths now refuse an un-described merge parent, below their capture (edits held, only the signature waits) and below a redundant-capture drop (a pass with no real work to sign stays a no-op, never a nag). Two reasons the ADR gave for NOT doing this were checked and dropped: a land never reaches these paths dirty (it finalizes before it ferries), and #219's refuse-on-dirt rejection was about being forced to capture, not about being asked for a name. The merge *nodes* are untouched — they are machine-authored and already carry an honest mechanical subject. That is the line: mechanical content may be named mechanically, authored content may not. - `refuse_if_undescribed` (one rule, two messages) + `drop_capture_if_redundant` (the empty/duplicate drop, previously copied at two sites and missing at a third — which is what would have made `dock merge` nag). - Cost, documented: uncaptured dirt takes two passes, since naming *is* capturing. `loot-first review` asks for a name only when it must merge. ADR 0030 amendment (correcting the two wrong claims), CONTEXT, workflow, and concurrent.md updated. Closes #275. aced27c1 · dbf3dbe6…diff
  • reconcile_onto captures whenever it may materialize, not when git brought new commits (#280) 802cbf5f · dbf3dbe6…diff
  • Gate flags per subcommand: a sibling subcommand's flag refuses instead of riding ignored (#278) The #67 gate declared flag specs per verb, so `loot lane`'s spec was the union over new/list/name/rm/gc and a flag real on a sibling subcommand (`lane new --stale-hours`, `dock rm --at x`) passed the gate silently. Each branching verb (`lane`, `dock` -- the others declare no flags) now re-checks the resolved subcommand's own spec via FlagSpec::check_sub before the workspace opens; a test pins the table's union to the subcommand specs so the two gates cannot drift. Ride-along: bare `loot lane` no longer panics slicing an empty argv. 1ada4da4 · dbf3dbe6…diff
  • Judge the tip-duplicate drop by recorded manifests, so deletions count (#289) A change whose only content was DELETING files was silently destroyed at finalize: same_tree_content compared tree_at ancestry overlays, and the union resurrects an ancestor's entry for every path the child deleted, so a deletion-only capture read as content-identical to the tip and drop_capture_if_redundant ate it - describe message and all. This very bug ate its discoverer's cleanup change (#284/PR #285); the workaround that landed was riding a one-line edit alongside the deletions. same_tree_content now compares the two changes' recorded manifests (every change carries its complete tree, deletion = absence): path set AND content. An empty capture is redundant only when nothing is held to compare against - over a non-empty tip it is a delete-everything change and signs like any other work. Same judgment at the adopt catch-up's inline site, the live status row, and working_preview's tip slice (a clean tree no longer reads dirty forever once a deletion lands). Truly identical captures - the bare new on a clean tree, the co-located checkout after a git pull - still evaporate; an undescribed deletion now hits the #174 refusal instead of silently no-op'ing. ADR 0030 amended. 06d1880e · dbf3dbe6…diff
  • primary catch-up: checkout at the #291 squash (site/ scaffold) folded for the drift reconcile (#297) e6f7079a · dbf3dbe6…diff
  • a view step refuses over work no change records, and a primary finalize says so on stderr (#436) a3248ede · dbf3dbe6…diff
  • the three hints that speak when a git pull ran ahead of a loot pull say one thing, and the hazard names loot new, the verb that actually signs, instead of describe, which does not (#1391) 7a273f8d · dbf3dbe6…diff
  • re-pointing the working tree becomes one call that carries the #436 clobber guard with it, so the four bisect checkouts stop destroying an edit made while testing a midpoint, and no caller is left owing a guard that only a doc comment could name for it (#1414) 17abc3a0 · dbf3dbe6…diff

Renames are not followed. loot's tree maps a path to an address, so a rename is a delete and an add. This list is the history of the name, not of the bytes.