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
  • 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
  • loot lanes now reads the shared graph and scans the object directory once per board draw rather than once per registered lane: a new DagRepo::load_positions_from reads the position files of every lane first and the shared half once after them, so each row keeps the read order load_from_needing documents while every row reports against one moment of the shared store. that is the decision the ticket asked for, recorded on peek_lanes with its reasons, among them that option c cannot help because a row needs both the graph and the objects. a single load now reads the working change before the graph, the order that doc states. on a scratch repo with twelve lanes and a 4.9 MB graph the board went from 237 to 190 ms, paired in both orders with loot status flat as the control, and its porcelain and json output is unchanged apart from heartbeat age. a new board test relating the store file reads of the board to those of its rows built one at a time went red on the unchanged code and with per-row loads put back (0 passed, 1 failed each), and an engine test comparing a batch load with solo loads went red with the mixed-store refusal removed and with one position half reused (0 passed, 1 failed each). the perf gate reads no move, CONTEXT.md follows, and the workspace suite is green (#1782) caed7d82 · 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.