Changes touching this path
- losing one bad file stops costing you every other uncaptured edit, because `loot restore <path>...` discards per path against the same delta seam `status` reads — and it never decrypts to decide, so a path whose recorded side is sealed, embargoed, burned or merely past its grant expiry refuses the whole run rather than clobbering the readable half beside it (#1498)
cd463799 · dbf3dbe6… - a flags value stops being read as a flag, because `FlagSpec::given` asks in flag position the way `positionals` already does and every verb-local scan in both binaries is deleted behind a census that refuses the shape — which is what had `loot tag -m "-d"` signing a retirement, `loot describe -m "--no-snapshot"` silently skipping the capture, and `loot-first land --allow-publish "--skip-tests"` skipping the gate that is its whole job (#1612)
b40e1843 · 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 move becomes a recorded fact instead of a later guess, and the premise this ticket rested on was false: #98's object reuse is keyed by PATH, so a moved path is absent from the outgoing tree at its new key, falls through to put_sealed and gets a fresh address - the same object under a different key was not a fact waiting to be read off the tree, it had to be made true by extending the reuse across the move, and everything else follows from that. The rule is that a move is recorded only when the path's sealed object survives it, so a move whose content also changed in the same capture window shares no object, records nothing and is two rows, while a move and then an edit across two captures of one change composes and reads as one row saying the content also changed. Empty content and any ambiguity are refused for the same reason the whole design exists: zero bytes equal all zero bytes, and choosing between two vanished twins would make a signed fact depend on iteration order. On the boundary the render side compares nothing at all - resolve reads keys and never an address, a visibility or a byte, pinned by running it twice over trees that agree on every key and differ on every address at three unopenable tiers, with a positive control beside it so agreement is not evidence it answers nothing. The capture side compares once: it opens a vanished object with this identity's own keys, fail-closed so an unopenable object is never read or hashed, and pairs only against an addition at an identical visibility and publication tier, with the digest living for one call, never an address, never stored and never on the wire. What a relay newly learns is that two keys held equal ciphertext, which recording a move states in plaintext anyway since tree paths are plaintext at every tier, so it is inherent to the feature rather than the ADR 0004 oracle - written into that ADR rather than left in a message. Renames ride the label seam change_id and authored_at already use: covered by the finalize signature so a relay cannot rewrite a move, never folded into the version id so two peers reaching one tree by different routes still agree on its address, which is why every existing id and signature is unchanged and a v11 store reads with no move recorded. That was tested rather than argued, against a real store built by the shipped binary, where the old move still renders as a delete and an add in the same repo the new one renders as a rename, because inferring the old one would be the equality oracle arriving through the compatibility door. Three further things were wrong and are corrected rather than worked around: ADR 0019 says an additive change bumps the minor and has never described this project, since the minor is still zero and all five additive changes took the major; plan_moves own doc comment claimed the tier is checked before the digest, three lines above code that does the lookup first, when the property actually holds by the stronger route that nothing unopenable enters the index at all; and the empty-content refusal was written on both sides of the pairing, where each made the other unprovable and removing either left the test green - a duplicated guard is not belt and braces, it is two guards neither of which can be shown to be doing anything (#1539)
3c7e029b · dbf3dbe6…diff - loot restore grows --source and it is the same verb with a different base rather than a second reader or a second writer: Workspace::delta_against takes the base mirrored_delta used to hardcode, so the disclosure gate, the four-kind absence ladder and the refuse-whole rule are inherited rather than restated, and what the seam hands back is now the base TREE rather than its key set because a caller re-sealing restored content has to be able to say what the base recorded it as. AC3 is plan-then-apply: every action is materialized from verdicts that were all decided above the refusal gate, so an openable path named FIRST beside an unopenable one is not written, which is the assertion a write-as-you-go loop would fail and the pin names it in that order. Three things are decided rather than inherited. A path the SOURCE does not record is reported and its local copy kept unless --delete-absent is passed - refused without --source, where it could only be a silent no-op wearing a destructive word - but a path in neither the source nor the working tree still refuses, because that is a typo and #67 says a typo must not exit zero looking clean, and AC4s deletion half presupposes a local copy to delete. The loss sentence is a different sentence: what a --source write replaces is recorded content plus whatever uncaptured edit sat on top of it, so #1498s nothing ever recorded them would tell an operator they had lost something loot cat prints in one command. And AC6 is a sentence rather than a behaviour, which is the ticket premise that turned out wrong in the useful direction: restore writes plaintext and seals nothing, so the next capture already derives the tier from todays .lootattributes and there was no publication decision to get backwards - what was missing was the report saying so, per path and on the dry side too. Two further premises were wrong. The raw --dry-run scan #1612 replaced was correct only by a coincidence this ticket expired, since --source is the verbs first valued flag and loot restore --source --dry-run a.rs now reads --dry-run as the selectors value. And the shared recoverability sentence opened in the past tense, so a dry run printed running this without --dry-run REPLACES ... what those paths held on disk is gone - found by running the shipped binary, not by review, and the tense now lives in the real runs frame where it is true (#1499)
c193cf35 · dbf3dbe6…diff - hunk selection lands on split and restore and AC2 is STRUCTURAL RATHER THAN CHECKED: everything the picker shows comes off the ONE DELTA SEAM, whose sides resolve through visible_paths_at - the key check plus embargo escrow plus grant expiry, the same door grep and archive take since #1500 - and the hunk grain has EXACTLY ONE CONSTRUCTION SITE, reachable only where both sides hold bytes, so an unopenable path is an Unreadable row that HAS NO HUNKS IN IT and there is no second visibility test to keep in step. such a path is still OFFERED AS AN OPAQUE WHOLE rather than dropped, because dropping it silently is #67 at the verb where it costs content, and its entry then moves BY ADDRESS with nothing decrypted. the keys are git add -p keys derived from ONE TABLE so parser, prompt and help cannot drift, with two forced divergences stated as such: q ABANDONS THE WHOLE SELECTION, because git keeps what it staged and loot has no index to keep it in, and there is no edit or navigate arm because each would mint a second grouping rule or a screen model. the terminal is a DOOR rather than a check - demand is the only constructor of the only Answers the binary has, so a verb cannot obtain a keyboard without crossing the refusal - and -p with --dry-run refuses as a PAIR, because -p IS the preview and a dry run would ask every question then discard the answers. content that does not survive a line split is offered whole, MEASURED BY ROUND-TRIPPING rather than by enumerating hazards. eighteen mutations, all red with counts read, and AC2 carries a POSITIVE CONTROL that moves the other way: emitting no hunk grain at all leaves the never-a-sealed-hunk half GREEN and reddens nine, which is exactly what that half alone cannot see. ONE ACCEPTANCE CRITERION IS DELIBERATELY NOT BUILT and that is the finding: AC1 names shelve -p, and loot shelve does NOT EXIST - #1502 was closed NOT PLANNED as premature scaffolding, so building a shelf to hang a flag on would reverse a recorded decision sideways from inside a ticket about something else (#1529)
1eb98dc8 · dbf3dbe6…diff - a verb arm now receives the argv door answer instead of the raw slice: VerbRun takes an Admitted value, the rebased tail bound to the spec of the row it was dispatched from, and its readers are the only way in, so argv[0] or argv.first() in an arm fails to compile (both planted, read as E0608 and E0599). spec_of is test-only and its 104 production lookups read through the admitted spec; six of the seven raw-index arms (bundle, grant-status, embargo-status, attest, resolve, completions) read positionals through the door and relay reads its leading word through a named exception; subcommand families resolve their leaf into a new Admitted, and bisect and buoy are handed one ahead of the table. the two named exceptions, leading_word for relay and bisect and pass_through_tail for the bisect run command, plus the one process argv read, are held to a named set of functions by a new census that went red on a planted leading_word read in cmd_bundle, and the source walker consumer header now counts it. refusals and output are unchanged by every suite and by a read of each migrated arm, with one exception found on the way: resolve with a trailing --tool and no command read --tool as the file and now refuses with the usage line, pinned by a new test. CONTEXT.md gains the Admitted argv term; loot-first keeps its own spec_of and DECLARED_READS is untouched (#1929)
d92e95f2 · dbf3dbe6…diff - loot new prints each first-seal tier off the seal the path was sealed under, so a path the same finalize had just published no longer reads internal: the summary printed the .lootattributes resolution seal_gate hands back, a Visibility, which cannot spell published because a published object is Internal plus the @world marker (#481). the tier is now read back off the signed change, seal_visibility and published_marker_to_carry over the address the finalized tree holds at each path, rendered through a new render::tier_label that restore now shares. reading rather than folding the standing rule in, as with_publish_status does for surface, is what makes it right under --no-snapshot, where the finalize signs an earlier capture and the rules may have moved in either direction; a row the finalized tree does not hold is left out, and an unreadable seal prints as a refusal and never as a tier. both directions pinned red first (0 passed and 2 failed): every Tier plus published on a fresh capture, and under --no-snapshot a published seal whose rule was deleted, a restricted seal whose rule was deleted and an internal seal given a publish rule since. red under mutation, counts read each time, each restored green: the row printed again (0 and 2), the marker ignored (0 and 2), the marker taken from the standing rule (1 and 1), seal or standing rule, the overstating repair (1 and 1, late.txt read published), the Visibility taken from the row (1 and 1), internal spelled published (0 and 2), an unreadable seal falling back to the row (2 and 1), a path the change does not hold kept (2 and 1). reproduced through the 0.4.24 binary before and a lane build after. CONTEXT.md, the census row for new_with and the Workspace width move with it. no migration, format byte or wire byte moves, so this owes no deploy. the workspace suite is green (4407 passed over 139 binaries, 13 ignored) (#2219)
a4254982 · 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.