Changes touching this path

  • day 0: loot hosts loot f4c30e75 · dbf3dbe6…
  • evidence: crew minted and verified (#86) 1fada823 · dbf3dbe6…diff
  • normalize working tree to LF: byte-stable co-located bridge (.gitattributes -text) e58fdda6 · dbf3dbe6…diff
  • converge: three-way deletion-vs-base rule (#295) (#300) merge_trees had no deletion-vs-base case: a path in theirs but not ours was always AdoptTheirs, and a path in ours but not theirs was never visited (the walk only iterated theirs). Either way a one-side deletion since the fork was silently undone -- it fired twice while landing #288, re-adopting freshly deleted files from the sibling line. Apply the standard 3-way rule against the base merge_tips already computes: - one side deleted + other unchanged-from-base -> the deletion wins (both directions; a symmetric ours-only pass covers the path theirs dropped); - one side deleted + other edited-since-base -> a delete/edit Conflict, recorded so it surfaces through the harbor bounce (ADR 0036) rather than silently resurrecting or deleting (base content stands in for the deleted side's missing oid); - base lacks the path -> a genuine add, adopted as before. Unchanged-ness: address equality first, then plaintext via the key oracle (re-seals mint fresh addresses, #65/#98); an unopenable side keeps the conservative pre-#295 keep/adopt. New same_content helper. Tests: converge unit tests for both delete-vs-untouched directions (incl. a reseal-address case), both delete-vs-modified conflict directions, delete-vs- delete clean, base-lacks-path add, unopenable conservative, and a classify theirs-side case; engine merge_tips tests proving the one-side deletion is honored (both orders, clean) and a delete/edit collision surfaces a conflict. Amends ADR 0028 (new #295 amendment) + CONTEXT.md Convergence-classifier and Adopt entries + concurrent.md, retiring the "the merge is what resurrects files deleted upstream" caveat for the reconcile/adopt merge. Git-Author: Connor Miller <53197564+Connor-Miller@users.noreply.github.com> 9fed0444diff
  • Workspace accepts its clock; lane flag-gate goes pure so cmd_* tests never touch a real .loot (#322) 20ac82e1 · dbf3dbe6…diff
  • conflicts map stores base OID + loot resolve --tool for external 3-way merge (#400, #401) a0711731 · dbf3dbe6…diff
  • Rename the Public visibility tier to Internal (ADR 0041 §2, #480) 6969626d · dbf3dbe6…diff
  • a resolution records which side it answered, so catching up after resolving no longer re-raises the path (#744); ADR 0039 says what the one-commit promise actually guarantees (#653) b837289e · dbf3dbe6…diff
  • a publication survives a merge, an ingest and a resolve, so a catch-up stops silently reverting one (#783) The reported symptom was consent fatigue: every capture in the repo refused with "refusing to publish README.md", including changes touching nothing published. The cause was the opposite of the symptom -- README was not published any more, and the gate was correctly reporting a new @world transition on a publication that had been reverted. The ticket's hypothesis (an adopt/ferry fold re-sealing README from git-side content) was not the mechanism. Reading the real graph, #750's own publication change never published README at all, while it did publish both licenses. The difference is that the licenses were NEW paths, so the snapshot seal minted them @world, while README already existed and went through the merge -- where converge picked between two addresses holding identical plaintext under identical Visibility and its tie-break took the unpublished one. @world is address-neutral, outside every signature and ignored by open, key-travel and compression. That inertness is what made publication cheap; it is also why nothing carries the marker structurally. It survives only where the code writing a tree entry chooses to carry it, and four writers did not: converge, the git bridge's ingest, resolve (so a bounce quietly unpublished the conflicted path) and absorb's span rebuild. The snapshot seal learned this at #481 and nothing generalized it, so Repo::put_sealed now is the generalization. converge gains an is_published question on KeyOracle and compares publish-status as part of a path's content, which makes the existing 3-way answer publish flips correctly in both directions: a publication since the fork is an edit and wins over a stale untouched side, and a deliberate unpublish is equally an edit and still lands. On identical bytes with no base to judge from, the published side wins -- widening was consented to once, and reverting it on no evidence is the only outcome that loses something silently. The other three carry, and carry ONLY. A re-seal never originates a publication: ingest takes its policy from a git commit, which reaches loot without passing a consent seam, so a break-glass commit adding a `published` rule seals plain and leaves the transition for the capture gate to ask about in the usual place. Sealing more tightly than the rule says is always safe; more loosely is what this subsystem exists to prevent. Ask 2 -- should an unpublish need consent? No. A prompt would make consent MORE frequent, which is the failure the ticket is actually about: a gate that fires routinely teaches operators and agents to pass publication flags reflexively. The fix is fewer ways to unpublish, not another prompt, so dropping the rule is now the only one. For the same reason the gate still asks the anchor OBJECT's marker and not the anchor's .lootattributes: policy and marker disagree both when a marker was lost and when a rule has just begun publishing a path, nothing there can tell them apart, and trusting policy would turn the standing rule into a way to publish unasked. A future marker-losing path would make the gate nag again, and that is the failure worth having. Ask 4 -- the forge's published index cannot disagree with main: this repo pushes to relay.millerbyte.com and has never pushed to the forge, so the index holds no README row. Had the push happened inside the window, it would have received the unpublished object. ADR 0041 gains a 2026-08-04 amendment; CONTEXT.md's Published entry records the carry/originate split. ec50222e · dbf3dbe6…diff
  • the converge seam merges with a diff3 three-way, so two disjoint edits compose instead of one silently dropping the other (#790) 2fd16e7f · dbf3dbe6…diff
  • a fourth caller stops being able to read the merge's unsealed tree, because merge_trees now returns an Unsealed with no tree field and exactly two exits, one for the two callers that seal it into a change and one for the cherry-pick that re-seals under its own attributes (#1409) e14639ff · dbf3dbe6…diff
  • a publication marker that could not be read stops being carried as an unpublished one, because the three re-seals that carry @world across a rewrite now ask a read that refuses instead of a predicate whose swallow was argued for the publish guard alone, and the refusal is a typed variant rather than prose so a corrupt seal reaches the run-loot-verify remedy a vanished one already did, while ADR 0012 table rows name their consumer and state the grep the census is the output of (#1582) 6ee38984 · dbf3dbe6…diff
  • loot apply-patch lands as the inbound half of patch interchange, and the limit 1512 deliberately left open is CLOSED BY REFUSING: CRLF is refused on both sides, because the two halves are not the same kind of problem. A patch declaring lossy: crlf-normalized cannot reconstruct the bytes the change it names recorded - that is fidelity, read off the exporters own measurement rather than re-derived, since by the time the content is in the file it is LF. The half that actually bites is the LOCAL one: a hunks line space is LF, so writing an applied result back over a CRLF working file rewrites EVERY line ending in it, a whole-file change no hunk describes, that no report would show as more than the lines the patch touched, and that the next capture would sign. Reconstruction was rejected as a guess - the line space carries no endings at all, so an inserted line in a mixed file has nothing to decide from - and documentation as too weak for a silent whole-file rewrite. The cost is named rather than hidden: a CRLF repo cannot use patch interchange in either direction, and the remedy is a bundle, which carries content verbatim because it carries objects. Refusing is also what makes the round-trip pin true rather than approximately true. This is a separate verb from loot apply and not a mode of it, since a bundle carries objects, keys and signed change nodes while a patch carries none of those; dispatch is an exact-name lookup so nothing can collide there, and the place that DOES match loosely is verb_usage, which compares the token after loot rather than a prefix - pinned, because a prefix match would have dragged the whole apply-patch usage line into loot apply --help. Hunks apply strictly at the lines they declare, and what does not falls back to the ADR 0044 three-way against the change the patch names in its own parent row, which is the git am -3 shape and the reason that row is full width; loot-core gains one narrow door, converge::compose, because the theirs side of a patch is text at NO address while three_way_merge exists to pick between two addresses. A contended path is recorded and the run stops with the tree untouched, which makes this the sixth member of the 1506 resume family, and the census is what said so. Three of the ticket premises moved under it. The AC3 re-seal under the local .lootattributes is a SENTENCE and not a step, exactly as 1499 found for restore: the verb writes plaintext and then captures, so the tier is todays local policy by construction, and the export side already carries no tier for it to have inherited - the work was pinning it and saying it in the report. The AC4 no-partial-application rule forced the capture-first snapshot to run AFTER the plan rather than before, found by a fixture rather than reasoned out: a repo holding a path it cannot open has that paths plaintext on disk, capture_uncaptured_edits refuses to re-seal content it cannot read, and with the capture first the verbs own refusal was never reached at all. And a hunk body must be consumed by the counts in its @@ header and never by scanning for the closing separator, because a deleted line whose content is exactly a dash renders as two dashes, byte-identical to it. ADR 0082 freezes the header grammar now that a reader exists, which is why the 1786 fidelity rows went in before this ticket rather than after; the machine shape 1512 expected this ticket to name is deferred, with the reason written into ADR 0066 and PROSE_ONLY_CEILING, because it is one shape shared by both verbs and freezing the import side alone is the same half-contract 1512 declined on its own side (#1513) d5fba81e · 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.