Changes touching this path

  • a second renderer stops being able to disagree with the first about which paths changed, because "what changed between two points" is now one seam taking (from, to, pathspec) whose endpoints may be a recorded change or the live working tree, and the six-rung content ladder and the in-the-clear plaintext count moved into it — so a renderer formats a number it is handed instead of tallying its own, and status and diff stop computing the same delta twice (#1541) 289ddd4e · dbf3dbe6…
  • loot diff and loot cat stop giving two answers about one embargoed path, because the four-rung absence ladder cat.rs already had moved into the delta seam and both renderers now match on it instead of each re-deriving burned-vs-sealed — and beside it an option group stops claiming the positionals of a leaf that never declared an arity, which is what would have turned `loot diff HEAD~1 HEAD` into a pathspec of its own selectors (#1551) 2248a1f4 · dbf3dbe6…diff
  • a disclosure invariant stops being written down with a reason its own test disproves twelve lines earlier, because what keeps a live delta from printing withheld content is two gates and neither is about the disk — `content_side_of` seals a recorded path outside `visible_paths_at`, and `classify` refuses rung 1 unless both sides hold bytes — which matters because `DagRepo::get` never checks grant expiry at all, so an expired grant is one decrypt away and only the seam refuses it (#1597) bc388741 · dbf3dbe6…diff
  • loot show becomes the fourth rendering over the one delta seam and grows no differ of its own: it calls Endpoint::base, ws.content_delta and render_delta, produces no hunk, counts no line and matches no content, and the pin is byte equality rather than shape - show ends with exactly the bytes loot diff --content <parent> <sel> produces in both content and stat modes, with the leftover prefix asserted to name no path, hold no hunk header and hold no disclosure line, so a second opinion about the parent or a renderer choosing its own words for the seam's tally both go red. Four breakages proved it, and a fifth attempt was a false negative caught by its author: truncating to the last newline is a no-op, so it passed, which is exactly as misleading as a vacuous test and is why it is named here rather than quietly replaced. Three of this ticket's premises were wrong. The two commands it says reading a change takes are really one command plus reading a parent id out of loot log by eye, because <version-prefix>~1 is not a form the #305 grammar spells and only HEAD~<n> is. Subject does not mean what the criteria assume: the tree defines it as the first line of the message and this repo writes single-line paragraphs, so the first run printed a two-thousand-character subject row - nothing truncates anywhere in the CLI, so rather than invent a rule the subject moves to the last header row, above the diff where git show puts the body, instead of pushing the lineage off screen. And the no-selector arm was ambiguous in a way that made the first message false: loot status prints a live working-change row computed off the disk under ADR 0030 while working_id and the @ selector both say there is none, so saying nothing is in progress contradicted the verb next door about the same tree - the message now names which absence it is, explains the live row, and points at diff and status, and show is deliberately not widened to render anchor against disk, because that delta is bare loot diff's by construction and a second verb answering it under a header claiming a non-durable version id is the duplication #1541 deleted. A merge names which parent it is taken against and prints the command for the other side rather than pretending the diff is whole. PROSE_ONLY_CEILING rises 50 to 51 deliberately with its reason written beside it, which is the mechanism landed one change ago doing what it was built for, and a pre-existing comment claiming the arity gate is a no-op for all but one verb is repointed at the property since it has been four for some time (#1492) 7ff52d8e · dbf3dbe6…diff
  • ADR 0079 settles what a visibility tier MEANS for a live delta row, and the answer is the shipped one for a reason narrower than the ticket argued: a live side is read at the tier the disk assigns it, because visibility describes SEALED content and loot gates on a key rather than on a tier - reading a live row at its recorded tier would make a declared tier withhold something all by itself for the first time anywhere in loot, and the first bytes it would withhold are the author's own from the author. The ticket's framing overstates what actually ships, and that was measured rather than repeated: with the embargoed path FINALIZED so the anchor holds it, a bare loot diff --content REFUSES at 0 paths rendered in the clear with the plaintext sitting unsealed on disk, so the real rule is that the record wins wherever it holds a version and the disk answers only for the rest - and the rest is a path added since the anchor. The two positions were far closer than the ticket says. The losing position is recorded in its own terms rather than strawmanned, because #536's discipline is about what leaves in a transcript and not about what the operator could otherwise reach, and an agent piping into a PR body does capture what the verb used to refuse; it lost because it has no well-formed answer for the row this ticket is actually about - a path with no recorded tier must fall back to .lootattributes, which is the disk's tier the long way round - and because the refusal is a BAD signal for that job, firing on paths the operator wrote thirty seconds ago while staying silent on the restricted paths #536 always meant to print. The residual is named with no mitigation claimed, since nothing stops the PR-body case and no mechanism at this seam could, the same bytes being one cat away. Its concern nevertheless landed as two real fixes that nothing covered. Unreadable::of_row asked the OLD side about prev_visibility, which is always absent over a live endpoint, so it was really asking about the disk: one path at one instant answered embargoed from cat, content not readable here from --content and sealed from --stat, which is verbatim the #1551 defect, and two of those three pointed the reader at loot grant for something ADR 0007 lets nobody grant. Reverting that term fails the new agreement pin while #1491's own pin stays GREEN, which is the demonstration that the old pin never covered it. And the in-the-clear tally under-reported: Tier::disclosed_by read one side, so dropping a restricted rule and editing the path - the ordinary declarative un-restrict, and the case the function's own doc names - printed recorded restricted plaintext under 2 paths rendered in the clear naming no tier. It now reads 1 restricted, filled through a REQUIRED constructor argument rather than by relaxing prev_visibility, which would print a was-to-now transition for a policy edit nothing recorded and reopen the #1005 phantom; over a both-recorded delta the tiers are equal and max is idempotent, so the tally can only gain a note and never lose one. A pin that closed on embargoed greater-or-equal to one is tightened to full set equality on the disclosure, because a nonzero check is not a guard. One label changed and no gate did: content not readable here becomes the embargo sentence, which AC2 cannot be satisfied without, and no byte of content moves in either direction. A row may now read internal in its header and embargoed in its body, which is correct under this ADR and asserted deliberately - the header is the disk's tier and the body is why the record's side would not open, and moving the header would be the losing position arriving one field at a time (#1574) daaf69d3 · dbf3dbe6…diff
  • a silent merge left one row reading its content at one key and its tier at another, and the fix is a function that cannot hold two keys: #1539 read the old side of a move at the key it had, ADR 0079 read the old side's tier at the row's path, the two changes rewrote the same three lines, and the converge took one line from each without conflicting - so content came from old_key while the tier came from delta.path, and for a renamed row the old side holds no entry at the new key. That made from_visibility None on every rename, and prev_visibility is None there too since rows fills it only under Modified, so both terms in the disclosure tally were empty and everything fell through to the destination's tier. Demonstrated rather than argued: before the fix, a path recorded restricted=connor and moved to a key the attributes call internal prints its recorded plaintext to stdout under a closing line naming no tier, which is ADR 0079 section 4's defect verbatim. The repair is not the one word it looks like. side_at takes a single key parameter and returns the side and its visibility together, and delta.path is not in scope inside it, so content at one key and tier at another stops being a mistake the function can make rather than one a comment asks it not to - reopening this would require deliberately widening the signature, which no textual merge can do by taking one line from each side because there are no longer two lines to take. The ticket's second half is not production-reachable and saying so is the more useful half of this report: an embargoed old side cannot become a move's source, because plan_moves fails closed on an object it cannot open and ADR 0007 means nobody can open an embargoed one, while the capture refuses to overwrite content it cannot see before that. The unreadable ladder was reached the only way it exists, by recording the move while the embargo has lapsed and reading the delta at a clock standing before the reveal, and a third test now asserts both guards so that if either is ever loosened the ladder shape becomes production and whoever loosened it is told. Two smaller framings were wrong too: a move that also changes tier is never recorded as a move at all, since plan_moves pairs only at an identical visibility, so the reachable shape is a move at one tier and then a dropped rule - which is how un-restricting actually works. And the existing tally test stayed green under a faithful revert, which is exactly why this landed silently in the first place (#1767) 796d7e87 · dbf3dbe6…diff
  • the delta reaches the TypeScript SDK as a SHAPE rather than as prose, and it is the FIRST INSTANCE of #1763 rule rather than a bespoke design: DeltaShape::of is a PROJECTION of the seam and nothing else - the mark is the #306 gutter rather than a new alphabet, the rung is the LineDelta variant, the counts are its own counts and the tally is the disclosure verbatim - so nothing here was hand-designed and the shape cannot drift from what the human rendering shows. three decisions carry the weight. added and deleted are NEVER ZERO where the count is unknown, they are absent, exactly where --stat calls a row uncounted, and ONE function now feeds both channels so the two cannot disagree. a sealed row WITHHOLDS the path, the from-path and the recipient list in both channels, because a path name is CONTENT under #306 - the shape refuses to leak through the encoding what the prose refuses to print. and the machine channel is ONE SHAPE REGARDLESS of --content and --stat, verified byte-identical, because those flags pick WORDS over a delta while this picks an ENCODING of it. the contract number is the shared VERDICT_CONTRACT and not a per-verb one, which ADR 0023 already answered for every shape and #1516 declined explicitly for porcelain, and an SDK test asserts diff and status report the SAME number so a per-verb version would go RED. a defect was caught BEFORE the freeze, which is the only time that is cheap: the first encoder used to_string_lossy, so on Windows a FROZEN contract would have shipped a backslash path beside a human line printing a forward slash - caught by the pin that compares the two RENDERINGS rather than asserting each is non-empty, and its unit pin uses an EMBEDDED backslash rather than a nested path, because a nested-path fixture is vacuous on POSIX. wire names stay snake_case deliberately, since a camelCase mirror would be a second vocabulary for one frozen contract and the only thing it could do is drift. nine mutations with counts read, and the ceiling comes DOWN 57 to 56 - the first entry on that list to PAY rather than be excepted (#1554) ac5700af · 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.