Changes touching this path

  • packed object storage lands its READ half, and the decision that could not wait for the writer is what a pack is ALLOWED TO GROUP: membership is the address first byte, order is the address order, and the reader REFUSES both violations rather than the packer promising them. a pack adds structure over addresses and that structure sits OUTSIDE the ciphertext, so a packer grouping by grant list or visibility tier would make the pack boundary BE the set of objects a reader cannot open - an authorization-set disclosure produced by a storage layout, which no encryption below it helps. the rule is safe structurally rather than by good behaviour: addresses are blake3 over nonce and ciphertext (ADR 0004), so bucketing by addr[0] is provably uncorrelated with custody and a packer CANNOT group everything one identity can open. the stronger rule, no loose object in a packed bucket, is rejected as unimplementable, since one object arriving after a repack would make every store refused; the residual packed-or-loose bit is an arrival-order fact mtime already discloses. FORMAT_MAJOR goes 13 to 14 at the READER and not at the writer, because the number should mean written by a binary that can read packs, and deferring it to 1525 leaves a window where some binaries read packs and some do not and all report 13. what the bump buys is a named refusal instead of a silent lie: pack/ is not a 64 character hex name, so a v13 walk SKIPS it and reports every packed object MISSING, referenced by change and by path, where v14 answers UnsupportedFormat found 14 supported 13. v14 is the first major in this history that moves no wire layout at all, measured rather than argued - the two frozen parity vectors moved by their marker byte and every other frozen vector beside them is byte identical. two findings beyond the criteria. packing breaks ADR 0038 burn SILENTLY: destroy_objects_loose deletes a file, cannot reach a span in a pack, and would not fail either, the already-destroyed arm swallowing it while the caller records a tombstone over LIVE ciphertext - it now refuses, and the guard asks the DISK rather than the live index, because the index version was vacuously green with burn having removed the address before the save runs. and a packed read must re-hash where a loose read need not, since a wrong offset yields a DIFFERENT VALID object under the wrong address rather than garbage. the membership pin was vacuous on its first writing too, filtering with the same function the code bucketed with, so it asserted only that the two agreed (#1524) eb753b9f · dbf3dbe6…
  • the first review sweep of this run finds three live defects and six sentences that read wider than their subject, and the one worth the sweep is that status and log DISAGREED: a working change is a head, so RemoteDelta counted it, while scoped_view drops it from the listing - status said 3 where log listed 2, and the extra was a change push would never send. it was unpinned because every fixture FINALIZES, so the fixture came first and settled it rather than the argument; the subtraction is spelled as the two node projections, author present and signature absent, which is the same discriminator the bundle uses and ferry already reads. verify graded a pack I/O failure as object CORRUPTION while the loose arm propagated the identical fact, twelve lines under a doc saying cannot-read must never pass as verified - now verifies() is one predicate literally shared by both arms rather than two spellings that agree today, and scan_one_pack is split out so the propagating half is TESTABLE at all: an index read and a body read are two opens of a file not immutable at its name, so the only deterministic way to fail the second and not the first is to take the index and then remove the file. and [info] swallowed a real failure, since any resolve_remote error read as unconfigured including an unreadable settings file, so the arm now asks settings() a second time rather than matching on prose, keeping unconfigured for a genuine solo repo. the prose half is the doc scope-drift class six times over. read_index claimed to open a store in fewer syscalls WITHOUT reading a body and reads the whole file into memory; the syscall half was true and the bytes half was not, and the ranged read is left to 1525 with the obligation recorded there, including that a span past the end is currently checked against a buffer read_index already holds. two operator surfaces still said LOOSE objects after 1524 taught the census to count packed, and a THIRD site the ticket never named was found in DagRepo::census own doc. ADR 0023 claimed nothing changes what an unasked status emits, which was false twice - the JSON gained a field and 1524 moved contract 13 to 14 on the same shape in the same diff - so the surviving claim is the narrow one, that neither channel can be misparsed, for two DIFFERENT reasons. ADR 0019 now records that contract 14 names two status JSON shapes, because the marker buys a REFUSAL rather than shape identity, a floor and not an equality. the verbs census sentence had gone wrong one run after 1799 guarded the number beside it and warned in that very file that a derived figure can sit next to a claim that has not moved. the forge arm of the privacy pin was prose only and is now tested, and deliberately NOT by byte equality, because a forge envelope names its asker under ADR 0061 - what is pinned is that the payload unwrapped through the real unwrap_envelope is ZERO bytes on both positions, with the greater-than-64-byte envelope beside it refuting the recorder cannot see bodies (#1819) 124faca4 · dbf3dbe6…diff
  • loot gc --pack lands the write half, and the claim ADR 0083 deferred to this ticket is taken and comes back NEGATIVE: there is no size win and there cannot be one. a packed object is byte identical to the loose file it replaces, so a pack weighs its bodies plus 11 plus 44 per entry and the census total always RISES - measured through the CLI on a throwaway fixture, 12 objects in 11 buckets, 12094 to 12743 bytes. the groupings that WOULD compress are exactly the ones the ADR rejects as leaks, so the absence is the PRICE OF THE ANTI-LEAK RULE rather than an optimization nobody wrote, and the verb says so at the number instead of leaving a true pair that reads as a loss. the repack IS the collector: a rewritten bucket carries forward only the keep set, so an unreachable entry is simply not copied and there is no delete-a-span path to get wrong. what that leaves is a plain gc unable to collect packed garbage, and the hazard is the SILENCE rather than the retention, because nothing to prune - every stored object is referenced by a change is true of the files the pruner walked and reads as a claim about the store, printed at the operator; so a plain gc now OBSERVES it, one header and table read per pack, and prints what it could not reach beside the flag that reaches it. the census asymmetry is untouched: reclaimable_bytes stays a dry run of the plain collector, because a census agreeing with the collection is the whole reason it is trustworthy. the ranged read is implemented and it reintroduced a hazard the ticket never named - a table sized from a corruptible u32 is u32::MAX times 44, 188 GiB of prealloc - bounded now by the file, and the mutation removing that bound ABORTS the test process rather than failing it. burn still refuses a packed address and the remedy now exists and is pinned, since gc_keep_set already drops burned oids so the ordinary collector rewrites the bucket without one and no burn-shaped branch exists in the packer; it is deliberately NOT wired into destroy_objects, which runs from save taking a shared reference, because a bucket rewrite moves every OTHER member offset and the live index spans for the burned object innocent bucket-mates would go stale instantly - a destruction path that repairs itself by breaking unrelated reads is not the fix. crash safety is temp, read back through this module own reader, rename, caller unlinks, and the invariant at every instant is that every address being carried forward is readable from at least one complete file at a stable name, both of them in between; an interrupted repack is not a new failure mode but one of 817 torn writes, since the stage name is skipped by read_all and collected by the existing sweeper. two pins the mutation sweep caught as weak are fixed, one vacuous because a repo that put its own objects holds them in memory where the re-point deliberately does not reach, and one mis-named because its injected failure fires BEFORE the ordering it claimed to prove; the read-back-before-rename ordering has NO fault injection pin and that is said rather than invented, argued structurally from write_bucket having exactly one rename as its last act. and the verbs census attribution went stale a THIRD run running, caught in review here rather than after landing (#1525) bbbe984d · dbf3dbe6…diff
  • loot verify --unreachable lands the dangling report and closes the packing family, and the thing it does NOT do is walk: it answers from gc_keep_set, so the whole new computation is a SET DIFFERENCE over the scan the integrity check was already doing - no third reachability walk and no second directory pass either. 1523 refused to write the second one and this refuses to write the third, which matters because a report that could disagree with the collection it names as the remedy is worse than no report. the two directions deliberately take DIFFERENT root sets and that is the subtle half: verify own node list stays the roots for MISSING, because it carries the referencing change and path that a bare oid set cannot, while the keep set is the roots for UNREACHABLE, because it additionally holds the loaded graph and it DROPS burned oids under ADR 0038 - so using it for missing would hide deliberate destruction, and reporting a burned object as missing damage is exactly the inversion burn exists to avoid. the primary own working change is seeded once in each, by the code that already seeded it. the exit rule is structural rather than a renderer discipline: is_clean does not read unreachable at all, so garbage alone cannot fail a verify however the report is rendered, and the process pin deletes an object the listing did NOT name, chosen by subtraction, so its missing half is a positive control on its unreachable half rather than a second assertion of the same thing. size is the SPAN length for a packed object and the file length for a loose one, never plaintext, which loot cannot know without keys - and the two halves are reported APART rather than summed, because that is what keeps each one checkable against its own collector: the packed half is GcReport packed_orphans by construction and the loose half is the census unreachable figure, and the census must go on excluding packed garbage to keep the promise it is pinned to. ObjectScan now carries how each present object is stored, which costs ZERO extra syscalls because both arms already held both facts at the instant they graded an address. one residual is documented rather than hidden: an address stored both loose and packed, the window inside a repack, is attributed once to its loose copy. and the empty case prints that every stored object is referenced by a change - the exact sentence 1525 had to qualify at gc as scope drift - where here it is NOT drift, because the enumeration behind it is scan_objects and that sees packed spans too; the asymmetry is pinned as a test rather than asserted in prose. the verbs census moved to 345 and its sentence moved with it, the first run in four where that did not go stale (#1526) 061af7f3 · dbf3dbe6…diff
  • the second review sweep of this run finds gc --pack crediting packing with the PRUNE bytes and staying silent about it, and the repair is a NARROWER SIGNATURE rather than a wider condition: size_note took the before and after totals and returned nothing whenever the store shrank, so on this repo own store, 2330 unreachable objects against roughly 250 KB of table, it would print 146.7 MiB falling to 110 MiB and say NOTHING - the exact misreading ADR 0083 warns about in its own text, in the verb written to honour that warning. packing_note now takes the REPORT and cannot see the totals at all, so no combined direction is able to suppress it, and it fires on buckets_repacked. the figure is measured by the packer rather than derived from the pair: overhead_of is header plus table, encode sizes its first body offset FROM it, and the report carries the table a rewritten bucket now holds LESS the one it replaced, computed above the dry-run bail so a dry run reports what a real run does. the pin the attribution actually rests on is new and runs a real repack over a store holding a loose orphan AND a packed one, asserting that before minus pruned minus dropped plus table equals after with every term from its own producer and the sum checked against a census none of them took, 11012 - 4141 - 4141 + 275 = 3005, and asserting the two directions genuinely disagree, which is the defect in miniature on real bytes. the pin that encoded the WRONG rule is deleted rather than preserved. four weak pins have now been found in one run and the last two were invisible to the mutation sweeps, because a sweep only tests the pins you AIM it at and therefore cannot find a pin that tests nothing - the count rule of 1812 proves a pin is LIVE, not that it is aimed at anything, and that limit is now written down. two mutations make the point by experiment rather than argument: under a copy-instead-of-rename and under a helper that unlinks a bucket without writing its pack, the OLD tests stay GREEN. one proposed fix is refused with its reason, that routing a reader fixture through the writer would assert only that the two agree and go on agreeing if both move together, which is the same vacuity the membership pin was written to avoid and which eight reader tests depend on not having; the real complaint is fixed by asserting a structural post-condition instead. the walk census contradicted itself inside one commit and the count is now stated ONCE in CONTEXT.md with the code referring to it, choosing live set as the load-bearing word because verify own node list is a ROOT SET FOR ABSENCE that is never consulted about deletion and deliberately KEEPS burned oids where a live set drops them. the commit record of c6b1cae carries the wrong version and cannot be fixed, so CONTEXT.md records that too (#1823) 907d2a9d · dbf3dbe6…diff
  • review sweep 7 fix-up: loot-first now reads loot --version --json with a JSON parser, so the relay push is decided on a single JSON object whose top-level contract fits a u8 and whose top-level version is a string, in any key order and beside other fields, while a duplicated field, trailing text or a looser number refuses the push. the pusher= path now percent-encodes control characters as well as percent signs and whitespace, and workflow.md says so. land-change gains the relay=REFUSED recovery (catch the primary up, cargo build --release, check the contract loot --version --json prints against the major the landed commit declares, then loot push, and record the push output on the issue), says the refused block names both majors on a mismatch and a read failure otherwise, and stops counting the relay and branch states. afk-loop agrees that a FAILED or REFUSED land stands and owes a push, gains that push as a step, and counts the ticket done once the push is recorded on the issue. format.rs, ADR 0066 and ADR 0083 note that a land carrying a FORMAT_MAJOR bump normally comes out relay=REFUSED rather than FAILED, and the --version usage line, the print_out and dispatcher comments and a verdict test name follow. both new pins went red before the change (2 passed, 2 failed), the control-character pin went red with that arm removed (1 passed, 1 failed), and a serde_json Value reader went red on the duplicated field (1 passed, 1 failed). the workspace suite is green (#1979) ce994e1d · 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.