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
  • a ferry refusal reaches a machine consumer as itself, not as a generic error (#867) Every seam inside `ferry::run` returned `Result<_, String>`, so a pass — which is mostly other people's failures — flattened all of them before the CLI could emit them. A typed `RepoError` the engine had already coded, and the reconcile executor's `Plan::Refuse`, both reached a `--json` consumer as the generic `error` code. The prose was fine; the machine channel was the loss. The nine phase signatures now carry `CliError` end to end, which restores the engine's own slugs for free: engine calls propagate with a bare `?` instead of `.map_err(|e| e.to_string())`, which is precisely what was flattening them. Exactly three slugs join the frozen ADR 0023 contract (amendment recorded): `undescribed_parent` and `foreign_paths` from a new `reconcile::Refusal::code`, built alongside the unchanged wording by BOTH executors — `apply_plan` and #866's adopt catch-up — so two verbs sharing one table cannot become two taxonomies; and `git_mirror` where ferry lifts git2, deliberately coarse because the bit a consumer acts on is retry-the-environment versus obey. `git_mirror.rs` stays stringly inside: inventing a loot taxonomy for git2's errors would be fiction. `rollback_note` keeps the abort's code and grows only its message — a rollback is context about that failure, not a different one. Every refusal's wording is byte-identical; the human channel does not move. Ferry's own handful of refusals stay honestly generic, and so do the tier's remaining flatten sites, which graduate one at a time when a consumer needs them. The one loot-first line is the seam back out: its orchestrator keeps a String channel, so `From<CliError> for String` makes that a `?` rather than a hand-written `.to_string()` at every call. Found while pinning the codes: the uncaptured-WIP ferry test refuses with `foreign_paths`, not `undescribed_parent` as it reads. Both wordings offer `describe -m`, which is why prose was never a taxonomy. 4fc73479 · dbf3dbe6…diff
  • the docs gate themselves: a land refuses a link that resolves to nothing, five rotted ADR cross-links are fixed, every amended ADR warns in its status, and the artifact sweep retires with its producer 586e3260 · dbf3dbe6…diff
  • loot log gets a machine shape, and the seam it needed turns out to have existed all along: every verb already renders through Emit, so nothing had to be plumbed - what was missing is that the prose-only verbs return Message, which collapses their structure into a String at the cmd_ site, before the format is known. So adding machine output is not teaching the renderer a second format, it is stopping a verb destroying its own data on the way out, which is why it cannot be done to forty-seven verbs mechanically and why this ticket does one. Log, LogChange and LogWorking hold the structure and render all three formats from it, consuming topology.rs by making the same two calls --graph makes and reading row, edges, elided and merge straight off the nodes - no second walk, no ancestry query, and the module never sees a Workspace, so the manifests d6be741 stopped materializing stay unmaterialized. The load-bearing subtlety is that edges are indices into the shipped array, so --reverse has to happen before Topology::of rather than after, and the pin reads edges back as version-byte pairs so a wrong index cannot pass as a right one. The empty head is why head rows exist at all: b0cffc66 records no changes of its own, so it has an H row and no change row anywhere, and without the head rows a two-head repo would reach a machine reader looking unforked. What stops the other verbs being forgotten is a census that names rather than counts, two set-equal lists over derived vectors plus a sum, reusing the existing machine_output_verbs so it cannot disagree with the USAGE census - and the debt list may only shrink. Two ADRs are amended rather than edited past: 0023 quotes its own sentence that nothing needs machine output from log or manifest and says log graduates while manifest does not, and narrows its porcelain-first framing as written for a homogeneous per-path verdict rather than a shape with nested arrays; 0066's three counts had to move in place because a test asserts them against the roster. And both numbers in this ticket's scoping were wrong the same way source_walk was wrong in 9559f0a, a grep for one spelling of a thing with two: the table holds 66 verbs and not 54 because twelve rows are verb_of, sixteen took a machine selector and not seven, and ADR 0066 had recorded the right figures three weeks earlier. Criterion four had nothing to satisfy, because the SDK never calls loot log at all (#1540) 915db92c · dbf3dbe6…diff
  • the fix-up a review sweep asked for, and the sweep's own premise was wrong twice: explain's porcelain row stops being two frozen positional booleans and becomes the comma-joined open set heads and log already document, with policy-file added as a genuine third flag so the growth is demonstrated rather than promised - and that flag makes a policy file distinguishable from an ordinary not-ignored path for the first time, since false true said nothing, so the #62 answer reaches porcelain at all. The pin asserts one row width AND at least three distinct flag cardinalities, which is what makes it discriminating: reverting to the two booleans leaves the width clause green because the row is still constant and fails only the cardinality clause, where a literal-string pin would have passed the revert entirely. ADR 0023 gains the explain amendment its own rule required, describing the corrected shape rather than the one being replaced, since #1540 wrote one verb at a time each with an amendment here and #1521 then cited this ADR while amending 0066 only. The stale verb_of figures turn out to sit in three places rather than the one the ticket named, and the third is inside the doc comment of the census whose whole subject is that a count is not a census, saying the table holds 66 rows where it holds 67 - so the number is deleted and the property asserted instead: a test derives both spellings, pins that 54 plus 13 accounts for the table, and reads emit.rs's warning back to assert it still exists exactly once and carries no count at all, which fired on its author's own first draft over the word two. PROSE_ONLY gains a ceiling so appending a name goes red rather than being the cheapest green, and the claim narrows to exactly that rather than inventing fifty reasons no assertion can read - the sentence is kept because it carries the argument for the list existing instead of forty-six tickets. source_walk.rs moved because the new census is a sixth consumer of the one walker and its header is generated by the test that counts them, which is the mechanism #1676 built after that header said four for too long. And this ticket argued urgency from a contract that froze on landing: explain is after the v0.4.19 tag, so no released binary ever emitted the old row and no consumer could have read one - the shape is still better, and it was cheaper to change than the ticket claimed (#1696) 0dc35b55 · dbf3dbe6…diff
  • user-declared gates arrive as data rather than as hooks, and the decision the ticket owed splits rather than picking one of its three options, because the gate vocabulary is two halves with different properties. The mechanism moves down into loot-cli, since its value is concentrated in the fake - a FakeSpawner that panics on an unstubbed step is what turns no process spawned from a claim into a failure, and two fakes drift with a fake that has quietly stopped intercepting being indistinguishable from one that works; loot-first keeps a re-export shim so no orchestrator caller changed. The phase axis is duplicated deliberately because it is not the same axis: loot-first asks about two trees, one of which only a land produces, while the CLI asks about two commitments, and a shared enum would carry four variants of which each consumer must refuse two - a rule nothing checks, where two small enums are checked by the type system. A new crate was rejected on arithmetic rather than taste, since loot-first already depends on loot-cli and there is no pair needing a bridge; loot-core was rejected because only half of #1682's criterion transfers, and that half is now a census asserting the engine spawns nothing rather than a sentence. A gate runs after the capture and can never prevent it: it does not decide whether the tree is recorded, only whether it is signed or sent, so a refusal costs the signature and nothing else, which is the ordering #174 already chose. On the confidentiality boundary the absolute form of the criterion is unachievable by any mechanism that runs a command in the working tree, and saying so is better than a strong claim that will not hold - loot surface writes and never deletes, so a path whose grant lapsed stays on disk as stale plaintext readable by cat, grep, the editor and every process running as the operator. The guarantee is therefore the narrow checkable one, that a gate is granted no read the operator does not already have, held up by four facts each of which a regression would have to break first: the runner never sees a Workspace, nothing is materialized, a Step carries program args and cwd with no environment channel, and there is no shell. A fifth fact answers a different attack - the declaration is untracked, so a pull cannot write one, which is git's own reason for never transporting hooks and matters more here since ADR 0075 has the forge holding proposals. This ticket also understated the surface that already exists: loot resolve --tool hands a user command decrypted plaintext today via LOOT_BASE and its siblings, so gates are not the first such door but they are the one that opens none (#1538) 44d4869a · dbf3dbe6…diff
  • blame grows six options and the one that mattered was -w, because ignore whitespace when attributing a line reads like a second comparison and this crate has exactly one line matcher: MatchOpts is a parameter of lcs_match_with, lcs_match is defined as that function at the default options rather than as a copy of its body, and the only place two lines are ever compared is match_key inside it, so textdiff, blame and hunks stay byte identical and the whitespace rule could reach diff tomorrow without anything gaining a compare of its own. -L slices a FINISHED annotation rather than narrowing the walk, which is why a range costs exactly the object reads a bare blame costs and the 98 content-address collapse still fires under it, and its rows carry the line numbers of the file rather than of the slice. --since bounds the walk by authored_at, which ADR 0043 makes advisory and never an ordering input, so two rules keep an advisory clock from dropping history silently: a change recording NO clock never cuts the walk, and the first version past the bound is KEPT as a boundary, marked with a caret, so a line older than the bound says the walk stopped instead of being re-attributed to the oldest change the bound happened to keep. --ignore-rev and --ignore-revs-file send a line to the next OLDER version of the path, marked with a tilde, which is a list filter over the versions and not the nearby-line guess git makes there; an unreadable ignore file is REFUSED rather than read as empty, because that failure is silent and inverted, the annotation that comes back blaming exactly the change the operator wrote the file to skip. the ticket got one criterion wrong and it is the porcelain one: it asked for the contract version on that channel like the other machine shapes, but the other machine shapes do not do that, ADR 0023 settled in its 1540 amendment that json carries contract and porcelain stays header-free, and the sentence claiming otherwise is one ADR 0066 records as false by design and guards off the shipped surfaces. so both channels ship and the version rides json, and the spelling is --porcelain rather than -p because sixty verbs answer to one pair of machine selectors. sealed is pinned under every flag rather than once, and --since was the hardest of them: a bound tight enough to stop short of the seal legitimately yields no sealed row at all, so what it must not do is NAME those lines, and it does not (#1516) b8cb568b · dbf3dbe6…diff
  • ahead and behind against a remote without pushing to find out, and the thesis constraint is met as a property of the REQUEST rather than as advice: the question carries an EMPTY PAYLOAD and every comparison is local. a relay is asked POST /haves with a zero length body, a forge POST /ref with a signed envelope over zero bytes, so a repo of one path emits BYTE IDENTICAL bytes to a repo of ten thousand and the question has no room to encode paths, object addresses, or even our own heads. the two endpoints that could are forbidden on this path and ADR 0021 now records why: /wants sends our object addresses, which are per-content identifiers, so a status in a loop hands the relay a per-path edit-frequency profile of ciphertext it cannot open - ADR 0083 refusal at a higher call rate - and /offer sends our head ids, which a push may do because a push is a CHOSEN act and status is not. the pin asserts the recorded path-and-body list SET EQUAL to exactly /info and /haves both empty, asserts the two recordings equal across two very different positions, and carries a positive control that a real push through the same stub records a NON-EMPTY body; mutated into the rejected design it goes red with /wants carrying a literal 32 byte address. offline is split POSITIVELY, the site gate SKIPPED-OFFLINE rule: only reqwest own is_connect and is_timeout may be called offline, and anything that ARRIVED - a refusal, a 404, a proxy page, a truncated body - is unusable, because those are different facts and collapsing them is how a guess gets reported as a measurement. the relay answered and holds nothing is a THIRD thing and reads as declared with a count. every unknown count renders dash or null and NEVER zero, so a machine that never reached the network cannot emit a number, and log --unpushed REFUSES rather than printing an empty listing, because a listing has no row meaning I could not ask and an empty one reads as everything is pushed. the asymmetry is stated rather than faked: unpushed is exact, being the same change lane a push would send through the same ancestor_closure, while unpulled is zero exactly when every declared head is held here and UNKNOWN otherwise, since a head declaration says what the tips are and not how deep they run. no FORMAT_MAJOR bump and the reason is recorded: ADR 0019 marker exists to prevent misparse of a durable or on-wire artifact, an unasked status emits byte identical porcelain, and the R row lives behind a flag that did not exist when the shape froze. no revset predicate either, because loot-revset is handed a GraphView and a KeyOracle and nothing else (#868), so a predicate answering over HTTP would put a network round trip inside revset::select and therefore inside grep and format-patch too (#1522) 2237a331 · dbf3dbe6…diff
  • 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
  • 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
  • a newline in a path does not LOOK WRONG, it FORGES ROWS - a name spelled notes.md then newline then percent then four numbers injects a SECOND DISCLOSURE TALLY, which is the number an agent reads before it asks for content - so porcelain WITHHOLDS an unframeable name rather than escaping it, refusing it, or leaving it to a sentence: path becomes dash, flags gains unprintable, and --json carries the name escaped. the three alternatives are rejected WITH REASONS AT THE SITE - documenting JSON as the only safe channel is the trade ADR 0082 already refused for CRLF, since a porcelain consumer has no way to DETECT it; escaping mints a second spelling of one path inside one tool, the #988 class whose answer was ADR 0051 ONE spelling; and refusing denies the OTHER rows, disagrees with --stat, and lets anyone who can name a file BLIND the machine channel. a tab is deliberately NOT withheld, because the path is last and the documented split recovers it - the rule is exactly as wide as the frame it protects - and with both withholdings flagged, a bare dash with NEITHER flag is now unambiguously a file NAMED dash. flags order is frozen as the encoder own, sealed first because it is the member a reader must not miss, and later members APPEND so every combination without a new one keeps todays bytes. and the run caught itself: #1554 re-created the class #1553 had fixed FIVE HOURS EARLIER, four pub items with zero out-of-crate callers, one of them a pub wrapper returning exactly what #1553 had narrowed to pub(crate) - the narrowing undone THROUGH A NEW DOOR - so all four are narrowed and the rule gains its worked example for the FUNCTION half, the half nothing enforces. the frozen shape description is corrected in four places, including a TypeScript doc that omitted the bare restricted token a peer-received path really carries, which would have mis-parsed in a consumer. the residue attribution in #1860 doc is WITHDRAWN rather than explained, restated as unexplained with what measuring it would take, because that document own thesis is that unmeasured causal claims about the instrument are the defect. eight mutations with counts read, four compile_fail probes with positive controls, and ONE MUTATION WAS CAUGHT BEING TOO WEAK - un-backticking a single table row left the pin GREEN, so it proved nothing and was redone against all four mentions (#1870) 3dfefe4f · dbf3dbe6…diff
  • OPEN_BUT_TAKES_NONE gets the ceiling PROSE_ONLY got in #1696: its census compares the list to the tree by set-equality, which is symmetric, so appending a name was the cheapest way back to green, and OPEN_BUT_TAKES_NONE_CEILING now makes a list longer than it red until the number is raised as its own edit. the ceiling went red with its arithmetic when set one below the list (0 passed, 1 failed), and again when doctor was declared open and appended to the list in table order, where the set-equality passed and the ceiling alone fired (0 passed, 1 failed) and raising the ceiling turned it green. the list comment now says what the ceiling enforces, that the list cannot grow quietly, and that the reason for a raise is not checked, the census doc stops giving the distance to the list in lines, and ADR 0023 gains a dated amendment saying the gap it left open is closed. the workspace suite is green (#1761) d9f6bed4 · dbf3dbe6…diff
  • the agent surface is settled as a decision rather than left as precedents: ADR 0088 records that loot has no template language and no template flag, that a verb gains machine output by deriving a shape from the value it already computes rather than by designing one, and states the porcelain vocabulary as a rule - tab-separated rows, a leading mark, a dash for an absent value and never a zero for an unknown count, yes/no facts in one open flag set, free text last, shared tokens, the version on json alone, and no new column on a frozen row. it names the shipped shapes that diverge from that rule, among them gates space-separated rows and telemetry key rows with a true/false column and no contract field, and records that count-objects and telemetry graduated with no ADR 0023 amendment and that nothing asserts the rule across every shape. ADR 0023 gains a dated amendment saying a shape is derived rather than designed and pointing at the rule, CONTEXT.md gains a porcelain vocabulary entry, and a new test fails when any table spec, the buoy spec or a subcommand leaf declares --template, -T or --pretty, which went red with --template added to archive and with -T added to lane gc (0 passed, 1 failed each). the workspace suite is green (#1763) b29ce89a · dbf3dbe6…diff
  • review sweep 6 fix-up: ADR 0088 section 4 gains the divergences the sweep found, each checked against the code - the evolog E row printing a raw whole message (#1973), the status and buoy R rows and the tag name column holding free text ahead of the last column, tag and heads naming people by display name with no key in heads json, a sealed diff row printing a bare tier label, telemetry status and off printing a spool dash where V3 says null, and telemetry on beside show as report json with no contract - and says it records what was found rather than proving nothing else diverges. V7 now means no version line or preamble, and the visibility token and Delta renderer member lists point at the code that defines them. the template-flag census gains a twin in loot-first over its FLAGS table, both reading one spelling list now in loot_core flags; the twin went red with --template and with --pretty planted in loot-first tag, and the loot census with -T planted in lane gc (0 passed, 1 failed each). ADR 0023 credits the open flag set to ADR 0047, the OPEN_BUT_TAKES_NONE census is called an ordered comparison, its assertion points at the list instead of naming describe and new, a ceiling raise has one place for its reason, a stale family count and a stale entry position are dropped, rewrap leftovers in ADR 0088 and ADR 0035 are reflowed, CONTEXT.md follows, and owned_lane takes a Removal with named verb and refusing fields instead of two swappable strings. the workspace suite is green (#1974) ecd53283 · dbf3dbe6…diff
  • loot-first land now asks the loot binary it pushes with which FORMAT_MAJOR it writes, through a new loot --version --json that prints the contract and the crate version, and reads the major the landed commit declares out of the mirror. the relay push runs only when both were read and are equal; otherwise the verdict says relay=REFUSED, the land stands, and a final block gives the reason. the landed line gains a trailing pusher= field naming that binary as version/v<major>@path, with UNKNOWN in place of the version and major when it reported neither, and the bare loot --version line is unchanged. stub pushers staged as real child processes pin the refusal, the matched push, a pre-1776 binary and an unreadable tree: removing the equality went red (207 passed, 1 failed), pushing past the refusal went red (205 passed, 3 failed), trusting the linked constant instead of asking went red (206 passed, 2 failed) and dropping the field went red (202 passed, 6 failed), and the spawned --version --json pin was red before the shape existed (0 passed, 1 failed). the land-change and afk-loop skills, workflow.md, CONTEXT.md, ADR 0023 and ADR 0088 follow. the workspace suite is green (#1776) d3e2f994 · dbf3dbe6…diff
  • loot evolog --porcelain no longer prints a whole recorded message in its E row: the last column was the message as recorded and a porcelain row is one line, so a message with a body printed that body at column zero as a line which is not a row, and a consumer reading a row per line read it as one and was then wrong about the rows after it by position, while an empty message left the field empty where ADR 0088 V3 puts the dash. each was reproduced by hand through the spawned binary first. the column is the message first line now, through the new render::subject_line, which is the rule the log terminal column already followed and is now one home for both frozen shapes, and col gives it the dash when the message is empty; a tab stays inside it, since V5 puts the free-text column last so a consumer splits the leading fields with a limit and takes the tail whole. the human subject column takes the same first line, so a version with a body renders on one line there too. evolog --json gains a trailing message field carrying the whole message and its subject becomes the first line, the pair log --json already spells: that re-means a shipped value, and the amendment records it rather than leaving two shapes disagreeing about the word. the new spawned pins for a multi-line, a tab-bearing and an empty message went red before the change (1 passed, 3 failed) and are green after (4 passed), with a single-line row as the control that stayed green throughout; they redden with the whole message put back in the column (1 passed, 3 failed; the unit pins 6 passed, 2 failed), with the dash dropped (3 passed, 1 failed; unit 7 passed, 1 failed) and with the json message field removed (3 passed, 1 failed; unit 6 passed, 2 failed). the pre-change release binary and this one print byte-identical porcelain and human rows for a single-line message over one repo. ADR 0023 carries the dated amendment with the shape and what the fix moved, ADR 0088 section 4 no longer lists evolog and says an item leaves when its fix lands, CONTEXT.md names both, and the workspace suite is green (3830 passed) (#1973) c9a99b16 · dbf3dbe6…diff
  • review sweep 15 fix-up: the evolog dash is on an empty FIRST LINE, the subject rule now has one home, and the surface prune says plainly that it discards. loot evolog --porcelain dashes its last column when the message first line is empty, which an empty message and a message whose body starts under a blank one both are, and three places said an empty message: the doc above the encoder, the ADR 0023 amendment of 2026-09-17 and CONTEXT.md. The wording moved rather than the bytes, because dashing on an empty message instead is a porcelain byte change for a message that opens with a newline, and a new spawned pin, a_message_whose_first_line_is_empty_is_the_dash_too, puts a run under the sentence: the file reads 5 passed, and with the whole message put back in the column it is 2 passed, 3 failed, the single-line control among the two that stay green. render::subject_line was billed as the one home while six call sites derived that first line inline, the four the ticket named plus the shortlog author rollup and the missing-object listing, so log --oneline, heads, show, range-diff and those two all ask it now, and the near-cousins that each do something more, the note ellipsis, the revert subject and the loot-first PR title, are named beside it rather than routed. The control is the spawned binary: 28 outputs over two repos, one carrying a multi-line message and one a blank first line, are byte-identical before and after, and with subject_line returning the whole message 10 of 11 of them move and the loot-cli lib suite reads 1219 passed, 4 failed, naming the shortlog rollup and the oneline row among them. The surface stale-debt prune keeps the removal error it discards, which #2018 declined and its closing note then reported as fixed, and the doc above it now says the let _ is deliberate, that apply_change_delta propagates where this one does not, and why: the delta writer snapshots, so a refused removal would be recorded as a deletion the disk never took. The removal census in workspace.rs states what its matcher is blind to, a type-annotated binding, a call split over lines and a path derived from a root-joined one, and names disk_entry::drop_empty_parents, whose remove_dir climbs from a dest its caller joined and which the fold cannot reach, because remove_dir takes an empty directory only and a directory holding a file the writer just wrote is not empty; widening the matcher would not have reached that call, so the blindness is stated instead. CONTEXT.md gives that census its crate and spelling scope. migration_serial records that hold blocks and is not re-entrant, and that it serialises migration runs rather than pg_authid, naming the fixed-name CREATE ROLE in tests/rls.rs that writes the shared catalog outside it; Held loses its Option and its Drop impl, since dropping the field is what the compiler already does. The migration census compares a BTreeSet the way the removal census does: with the const reordered it stays green where the Vec compare it replaced goes red (0 passed, 1 failed), and a name dropped from the const reddens it (0 passed, 1 failed). pg/tests.rs cross-references its own source walk and fn finder to source_walk in loot-cli and states where the pair differs. The folds_case helpers in two test files become folds_case_under, so neither shadows the disk_entry rule it asks. The rewrap leftover in account.tsx that #2018 moved rather than fixed is rewrapped with its neighbours, and so is a second one mid-paragraph in CONTEXT.md that the same rewrap reached. The forge suite is green against a throwaway 17.0 cluster (469 passed over nine binaries, no skip note) and its lock case goes red with the session dropped rather than held (0 passed, 1 failed), the site gate is green (643 passed, every surface under its ceiling), and the workspace suite is green (3833 passed) (#2023) c38e3299 · dbf3dbe6…diff
  • the Refit family reports which version each re-anchored change became, and move, squash and absorb emit that mapping through one machine shape instead of counting it. the executor already built the old-version to rebuilt-version remap to thread each line, and each arm reported its length as re-anchored N descendant(s) or intervening change(s): the column the #1517 census named as missing from three verbs at once and declined to freeze for one of them. the reports carry the list now as Reanchored rows (from, to, the durable change id both carry, and carried for a change rewritten only because it sat above one the verb was aimed at), and the human lines print the same bytes derived from that list rather than a count kept beside it - measured through the spawned v0.4.20 primary binary against the lane build over nine fixtures across move --onto, move --before, squash and absorb (the clean arms, the move and squash stops with their aborts, the absorb stay and a mixed absorb) with minted ids masked: the transcripts are identical. the shape is spelled once in loot_cli::refit_shape over the executor seam, by the rule that filed delta_shape beside its seam: R rows for the pairs in the order minted, C rows for the paths a stop recorded, and the A and S rows only absorb fills for where each hunk went and which stayed with its reason, following ADR 0088 at every rule, with a path the frame cannot carry withheld under unprintable through the predicate delta_shape already decides with, and change null rather than a dash string on a keyless repo. the handle is read off the rebuilt version because rebuild_node drops a superseded draft from the graph, which the first cut of the pins found by reading None off the original id. squash and move declare the machine pair beside the resume trio, absorb takes OUT, the usage lines advertise it, PROSE_ONLY loses the three and the ceiling comes down 56 to 53 as the #1517 entry paid, ADR 0066 tiers move 25 to 28 and 55 to 52 with the #1517 amendment marked paid rather than left stale, ADR 0023 gains the amendment recording the value, the rows, the flag members and the JSON fields, and CONTEXT.md the Refit entry. the pins resolve both ends against the graph in-process for every report and through a separate loot evolog process for every R row off the spawned binary, where a carried working draft is asked about as @ because the hex door excludes the working change by design, and the superseded end refusing by name is asserted as the reason the mapping exists. red first: the move arm marking every row aimed-at reddens the two workspace pins and the two spawned move pins (42 passed, 2 failed; 5 passed, 2 failed), the squash arm marking its intervening rows aimed-at reddens the squash workspace pin and both spawned squash pins (43 passed, 1 failed; 5 passed, 2 failed), and the porcelain R row printing from in the to column reddens the encoder pin and the two spawned resolve pins (43 passed, 1 failed; 5 passed, 2 failed). no migration, no wire or format byte moves, and no forge or relay byte moves, so this owes no deploy. the workspace suite is green over the final tree (loot-cli 1789 passed over 52 binaries, 3 ignored; the remaining crates 2094 passed over 69 binaries, 4 ignored) (#1837) 1be80495 · dbf3dbe6…diff
  • sweep 1 fix-up over #1826, #1837 and #1839: a live change only a superseded head reaches is listed in the fork view, in a third section of its own, rather than in no section at all. LogGraph::retain_heads leaves such a change with an empty reachable_from, and history() partitioned the fork on exactly one head and more than one, so after a move --onto the ancestor the old tip alone reaches was printed nowhere - reproduced first through the spawned binary with two live lines (0 passed, 1 failed), the fork and the non-empty superseded head both holding as controls. GraphHistory gains unreached, the renderer prints it under behind a superseded tip after the shared ancestry, drawn_rows leads with it because such a change can only be a child of a lane or shared row, the path filter, retain_versions and all_rows walk it, and both machine shapes carry the row with no new column. HEAD on an untracked position with no working change now counts the heads Liveness::is_superseded rejects and answers the sole counted head by name, because Position::anchor answers the first graph head, which can be the superseded one; measured at the other two sites the ticket named, propose already reads is_live and IntegrationPreview deliberately counts the whole partition with retire for the superseded ones, so the liveness doc now points at the callers of the predicate rather than listing two readers. the move --onto usage line gets its space back; refit_shape spells its path columns through treepath::store_key with a backslash pin, and the #2033 census records the tree-path axis it is blind to by design; the shallow clause has one spelling, workspace::parents_not_held, read by the notice and the push refusal, with the pin the suite lacked; carried is carried_count, Reanchored.change is change_id, the squash and absorb op-log lines say re-anchored, frontier_beyond_the_host is private, the one-flag-member sentences drop one, ADR 0089 names both readers of .loot/shallow and both frontier spellings, and the one raw exit ADR 0089 rests on is held to one by a census in tutorial.rs. own > 0 for the move --onto stale head is pinned off the loot heads row. red first, six mutations in one round over disjoint pins: the unreached filter blinded, drawn_rows reordered, the HEAD filter blinded, store_key undone and a second raw exit added redden five pins with the sync shallow control green (1 passed, 5 failed) and the spawned fork pin (2 passed, 1 failed); the noun-verb swap survived tests/shallow (10 passed, 0 failed), which is why the clause has a pin now (0 passed, 1 failed under the swap). ADR 0066 keeps its gated 28 and points at the gate instead of at tickets. no migration, no wire or format byte moves, and no forge or relay byte moves, so this owes no deploy. the workspace suite is green (3895 passed over 122 binaries, 7 ignored) (#2046) 0b55a20f · dbf3dbe6…diff
  • the attestation lane has one machine row, and it says whether the listing is raw or folded. loot manifest and loot notes show gain --porcelain/--json through loot_cli::attestation_shape, one row for a lane record spelled once over the record rather than per verb: the change and the attester key as full hex, a closed kind word (role, landmark-note, landmark-retire, note, reserved), a note generation, an open flag set (removed, unprintable) and the payload last, marked R for manifest listing every record as held and F for notes show listing the trusted live register, with folded in --json; manifest also emits its grant book as G rows ahead of them, by the absorb precedent of #1837. what a role carries is read through a new loot_codec::reserved::ReservedRecord::parse that reads the envelope tag once, and render::role_display now matches on it without a wildcard instead of trying the landmark parser and then the note parser - the door #1850 asks for, built only as far as this row needs; a scratch variant fails to compile at both matches. only the notes show leaf takes the flags, and the writing leaves refuse them naming it. the prose of both verbs is byte-identical, measured against the v0.4.20 primary binary on one fixture, where both verbs refused a machine flag with unknown_flag, so no frozen row moved. PROSE_ONLY_CEILING comes down 53 to 51 as the #1519 entry paid, ADR 0066 tiers move 28 to 30 and 52 to 50 with the #1519 amendment marked paid, ADR 0023 gains the amendment recording the rows, flags and JSON fields, and CONTEXT.md the Attestation entry. red first, four ways: every row marked R reddens the shape pin, the notes pin and the spawned pin (6 passed, 2 failed; 2 passed, 1 failed); notes show building its rows from the raw lane reddens the notes pin and the spawned pin (7 passed, 1 failed; 2 passed, 1 failed); manifest dropping its grant book reddens the manifest pin (7 passed, 1 failed); and the door misreading the note tag reddens the codec pin (4 passed, 1 failed), five cli pins (4 passed, 5 failed) and two spawned pins (1 passed, 2 failed). no migration, no wire or format byte moves, and no forge or relay byte moves, so this owes no deploy. the workspace suite is green (3911 passed over 123 binaries, 7 ignored) (#1846) 5f1a824d · dbf3dbe6…diff
  • a prose pass narrows claims that read wider than the code, and LineDelta::rendered goes crate-private. ADR 0007, CONTEXT.md and the object_and_key_at doc say the reveal gate is asked at every read through the grant key door, not at every read, since grant_sealed keeps its escrow fallback by design; the Refit enum count, the family list in CONTEXT.md and the refit.rs header, and a stale claim that every planner runs the draft check, now point at the enum and the wildcard-free minted_edges match instead of a count; role_display gets back the blank doc line rustdoc folded into the last bullet, verified in the built HTML, and a record type added to ReservedRecord replaces a third record type; the Landmark entry records the one-line rendering from #1519; the revset named door and the hunkpick PATCH constant state the exceptions a reader finds (grep writing its own refusal, restore spelling -p out under the main.rs census); the ChangeGraph insert doc stops saying the callers do not insert parents-first, names DagRepo::apply_sync for an apply_bundle DagRepo does not have, and says why the ordered ingest_shared_lineage splice stays on insert; ADR 0023 now says what #1870 wrote in the delta_shape header, that the reveal_at split is about the field and not the number, which embargoed@ can carry. nineteen assertion messages lose the run of spaces a rewrap left inside the literal. LineDelta::rendered and Rendered become pub(crate), with a compile_fail probe and a positive control: the probe was red before the narrowing (2 passed, 1 failed), and making the method pub again reddens it (3 passed, 1 failed). items 2, 8 and 14 need no fix here: the #1515 raise is real because #1516 lowered the ceiling between the two raises, #1968 dropped the width narrative, and #1860 withdrew the figures. cargo doc warnings are unchanged for loot-core, loot-codec and loot-cli. no migration, no wire or format byte moves, and no forge or relay byte moves, so this owes no deploy. the workspace suite is green (3913 passed over 123 binaries, 7 ignored) (#1848) eb966bdb · dbf3dbe6…diff
  • every place that repeats a stop now says what --continue will and will not do, and an F row carries the role as it was signed. resume::continue_clause is the one wording, read through ways_out, once_resolved and stop_advice by the stops, by the in-progress note status and conflicts print (which said only --continue once loot conflicts is empty), by the wrong-verb refusal (which said --continue finishes it), by apply-patch (whose own copy never said its clean paths stay unwritten), and by the apply and merge stops, which now name both ways out without the disclaimer, the reason written at stop_advice. a census in resume.rs refuses a production line of loot-cli outside that module that spells --continue, USAGE lines carrying USAGE_NOTE excepted, and its first run caught the move --before USAGE line paraphrasing the note without its disclaimer, which every_stopping_verb_documents_one_resume_sentence misses because it reads one line per verb. the note fold carries each live record role as signed (loot_core::note::LiveNote), so notes show --json role is the signed bytes for a generation spelled +2 where it was re-encoded as 2; every other row byte is pinned unchanged through the spawned binary, ADR 0023 records the amendment, and Note::parse is not made stricter, since this repo lane held no records to measure and refusing would reclassify signed records held elsewhere. manifest prose names an attester through attestation_shape::attester_name, the naming its JSON used, so the own key stops printing as hex there; unprintable is one const in delta_shape; ADR 0066 says notes stands for notes show; the ChangeGraph insert doc states its rule instead of a caller list; Pathspec::matches names the crate-private policy items instead of linking them, which removes five cargo doc warnings. declined: compile_fail,E0624, measured inert on the pinned stable toolchain (a probe edited to E0599 stayed green) and honoured only under RUSTC_BOOTSTRAP=1, the reason written at the probe. red first: the new pins failed before the fix (resume 21 passed, 2 failed; attestation_machine_shape 3 passed, 1 failed; apply_patch 12 passed, 1 failed); with the fixes undone in two rounds, the in-progress note, apply-patch copy, apply advice, re-encoded role and hex naming redden the census (1267 passed, 1 failed), apply_patch (12 passed, 1 failed), attestation_machine_shape (2 passed, 2 failed) and resume (21 passed, 2 failed), and the fold re-encoding, the old wrong-verb sentence and the old merge sentence redden the codec pin (158 passed, 1 failed), the census (1267 passed, 1 failed), attestation_machine_shape (3 passed, 1 failed) and resume (22 passed, 1 failed). no migration, no wire or format byte moves, and no forge or relay byte moves, so this owes no deploy. the workspace suite ran 3917 tests over 122 binaries with 7 ignored, and all passed but concurrent_stage_of_same_address_does_not_tear, an os error 5 in untouched persist_codec code that passed on a loot-core rerun (637 passed) and three times alone (#2052) d5fad7c8 · dbf3dbe6…diff
  • loot seek asks a repo one of three questions without materializing it: list the paths a pathspec admits, search them with --text, or read one file verbatim with --read, in the ambient repo or in another loot repo on disk named by --in and opened with its own identity and keyring; a remote name, a URL or a git directory refuse naming the map ticket that builds them (#2063 tickets 3 to 5). the question is explicit, never inferred, so a glob matching one file does not turn a listing into a read; --read refuses a machine format rather than render bytes lossily. the verb computes no content of its own: the listing and the search are Workspace::readable_tree_at, the read is path_content_at, a hit is grep::scan_file, and the spawned-binary pin holds the listing to surface --porcelain, the hits to grep -n and the bytes to cat on one fixture. the frozen shape is P path rows, T hit rows under the nearest P, one S summary row carrying the sealed-skip count and a truncated flag, and --json with contract first and target.kind drawn from the Target enum; zero rows exits 0 and a refusal alone is non-zero. the usage line, the README verb block, ADR 0066 four counts and its amendment log, the MACHINE_OUTPUT and ATTACHED censuses and the revset_everywhere lists all learned the verb, and ADR 0023 gains the #2068 amendment; PROSE_ONLY_CEILING does not move. red under mutation: the sealed count dropped (0 passed, 1 failed), truncated reported for every bound (0 passed, 1 failed), --read under --json allowed (0 passed, 1 failed), the S row omitted (0 passed, 1 failed), and a sealed path name pushed into a row (0 passed, 1 failed). no migration, no wire or format byte moves, and no forge or relay byte moves, so this owes no deploy. the workspace suite is green (3942 passed over 123 binaries, 7 ignored) (#2068) a733d45c · dbf3dbe6…diff
  • loot seek answers for a local git repository: when --in names a directory that is not a loot repo but is or is inside a git checkout or a bare repo, the git backend lists the blobs of the tree at a git revision, searches them through the same grep scanner, or reads one blob verbatim, through the git2 crate the mirror already links, with no spawned git. the rows are the same P, T and S rows and the same --json: change is the commit id, visibility is the machine format one dash in porcelain and null in json as ADR 0088 V3 says, nothing is sealed so the count is zero, and --at is a git revision peeled to one commit, whose refusal says it is not a loot revset. a path the commit does not hold refuses in cat's words rather than printing an empty file, and a tree or submodule entry is skipped because it has no bytes to list or read. the change column became full hex text rather than a loot object id so a twenty-byte commit id fits the row unchanged, the resolved target now carries a backend, a boxed workspace or a git repository, so the shape names four target kinds while two answer, and the two backends build a row through one rule, Question::hits_in. the two-axis review before landing turned a minted dash constant into the shared one, made the Target doc point at Backend instead of listing which kinds lack one, and added the pins it found missing: a bare repo holding a commit is listed, --first and --last run on a git target, a nested read goes through the binary, and the resolved git root carries no trailing separator. the unit tests build a two-commit checkout in-process with git2 and pin the listing, the search, the read, the revision, the pathspec, the bound and the resolution of a checkout, a subdirectory of one and a bare repo; the spawned-binary pin covers the same through the binary. red under mutation: a tree entry listed whatever its kind (0 passed, 1 failed), the revision ignored (0 passed, 1 failed), a search row kept with no hits (0 passed, 1 failed), an absent path read as an empty file (0 passed, 1 failed), the pathspec unapplied (0 passed, 1 failed), and a git directory refused as not a repo (0 passed, 1 failed). no migration, no wire or format byte moves, and no forge or relay byte moves, so this owes no deploy. the workspace suite is green (3952 passed over 123 binaries, 7 ignored) (#2075) 32edebda · dbf3dbe6…diff
  • loot seek carries less when asked and its refusals carry a code: under --text, -l keeps the P rows of the paths that hit and nothing under them, --count puts one N row under each P row with how many lines hit, and --max-line <bytes> cuts a hit line at a character boundary into a C row of T arity, clipped true under --json, each emitted only under a flag that did not exist before, the rule the R row set in #1522, so a consumer that never passes them reads the bytes it always read. the three are one scan rendered three ways, so skipped_sealed, truncated and the row order never move, and an uncut hit carries no clipped key so every hit shipped before renders byte for byte as it did. measured on this repo, a search for fn over crates/loot-cli/src is 259,793 bytes in 3,888 rows whole, 9,923 bytes in 92 rows under -l and 10,369 bytes in 183 rows under --count. the refusals join the #430 coded channel: a refusal under --json is one object with contract first and a code the CliError was built with, never a parse of its sentence, declared beside the verb in seek::code as conflicting_flags, read_is_bytes, no_identity, multi_head, bad_revision and no_such_remote, while a directory that is neither repo keeps no_repo and an unusable --limit or --max-line keeps bad_flag_value because a second spelling of one fault is the #1597 defect; there is no unreadable slug because --read refuses every machine format before it reads and a listing or a search counts a sealed path rather than refusing. the multi-head refusal names each head with its id prefix and subject so the next call pins one with --at in one round trip; the ticket first asked for the newest head by default and that cannot be honest from a depth-one cache, where every head reads generation 0, and authored_at is never an ordering input (ADR 0043), so the ticket was corrected before the lane opened and ADR 0023 records why. error.rs stops listing the three CLI slugs as the whole set and names what defines it, and loses a two-raiser census that was stale before this change. the pin on that one object found a defect #2092 shipped: every remote loot seek printed the dispatcher shallow note after its answer, because the body-less cache is refreshed at depth one by design and the width it records is the process-wide one main reads, so the note was wrong twice, about a cut nobody can close and naming a loot fetch --deepen that runs where the operator stands; the cache open and its object rounds now run outside_shallow_notice and a remote answer leaves stderr empty. the two-axis review before landing also refused --max-line beside -l or --count where it was silently inert, mapped the selector door stringified refusals to bad_revision on a loot target so a typo in --at carries the same code on both backends, pinned the multi-head refusal through the verb and the prefix it prints as a selector the verb accepts, pinned the cut through the scan on a multi-byte line and on an invalid byte the lossy decode widens, read the three remaining driven refusals back under --json, and narrowed the code claim to the refusals the verb decides, a stringified failure staying error as #430 says. pinned in the shape tests on every format, on a multi-byte line for the cut, on the code of each parse refusal, and on two signed heads over a spawned relay, and through the spawned binary on the seek fixture with every refusal it drives under --json read back as one object with its code. red under mutation, counts read each time: -l still writing the T rows (0 passed, 1 failed), --count off by one (0 passed, 1 failed), the JSON hit_count dropped (0 passed, 1 failed), the clip ignoring character boundaries (0 passed, 1 failed), the clip marking nothing in JSON (0 passed, 1 failed), the clipped row keeping the T mark (0 passed, 1 failed), --max-line 0 accepted (0 passed, 1 failed), the shaping flags riding a listing (0 passed, 1 failed), -l with --count accepted (0 passed, 1 failed), a parse refusal on the generic code (0 passed, 1 failed), the read refusal losing its code on the wire (0 passed, 1 failed), the multi-head refusal naming no head (0 passed, 1 failed), the multi-head refusal on the generic code (0 passed, 1 failed), the git bad-revision code generic (0 passed, 1 failed), --max-line beside -l or --count accepted and inert (0 passed, 1 failed), a bad --at on a loot target left on the generic code (0 passed, 1 failed), the multi-head guard unwired from the verb (0 passed, 1 failed), the clip through the scan measuring raw bytes (0 passed, 1 failed), and the cache refresh recording its width for the shallow note (0 passed, 1 failed). no migration, no wire or format byte moves, and no forge or relay byte moves, so this owes no deploy. the workspace suite is green (3984 passed over 124 binaries, 7 ignored) (#2105) 25d946e7 · dbf3dbe6…diff
  • loot seek pays fewer round trips: a remote target is answered from its cache inside --fresh <secs> with every round trip the open makes past the record check skipped, today the info probe, the metadata pull and the grant round, and a bare http URL a loot cache already stands for is routed to it before the probe, from a record the cache writes on every refresh, the first included, holding the clock the workspace reads and never a file mtime, and a record that is missing, will not parse or is ahead of the clock refreshes rather than trusts; the answer says so, target.refreshed under --json only under the flag, the key that did not exist before riding the flag that did not exist before as the R row rule has it, and one prose line when the refresh was skipped, on stdout beside the other summary lines and on stderr for a read whose stdout is the bytes; --fresh over a local target is conflicting_flags since there is no refresh to skip and a window that is not a number is bad_flag_value; the window skips the refresh rounds and never the fetch a question needs, and since a host answers an object round with whatever the declared closure lacks, a change pushed since the refresh rides back beside the bodies a search or a read fetched, which ADR 0090 records and the smoke pins rather than hides. the bodies a question needs travel in rounds of seek::OBJECTS_PER_FETCH, 256, through fetch_objects_via whose batch is now the caller's while the pull keeps its own 32, chosen by measurement over a relay spawned in-process holding 400 small bodies, best of three on loopback: 32 per round is 13 rounds and 243 ms, 64 is 7 and 193 ms, 128 is 4 and 158 ms, 256 is 2 and 136 ms, 512 is 1 and 126 ms, the table flattening past 256 while the memory a round holds keeps doubling; the 413 halving the ticket first asked for is not built and the ticket was corrected before the first test, because a fetch request carries ids alone and the response is unbounded, so there is no 413 to fall back from. several --text patterns are one read of each kept object and the union, a line carried once however many matched it so --count counts it once and --max-line cuts it once, with a pattern that will not parse naming itself as it did alone; a repeated --text was accepted before and silently narrowed to its first value, the one shape that moved, and ADR 0023 says so. ADR 0023 records the key and the union, ADR 0090 the window and where the record lives, and CONTEXT the three. pinned on the record round trip and the inclusive window arithmetic, the union over a fixture line two patterns hit, a git cache and a loot cache each answering stale inside a window and fresh outside it, the batch read off the test relay round count with the verb own prefetch one round, and through the spawned binary on a head pushed between two calls, unseen at the recording clock inside the window and seen at a clock outside it, with the refreshed key present under the flag and absent without. red under mutation, counts read each time: a line two patterns hit carried twice (0 passed, 1 failed), only the first --text scanned (0 passed, 1 failed), the window never holding (0 passed, 1 failed), the window exclusive (0 passed, 1 failed), the git cache ignoring the window (0 passed, 1 failed), the loot cache not recorded on the first make (0 passed, 1 failed), the refreshed key written without --fresh on a loot remote (0 passed, 1 failed) and on a git remote (0 passed, 1 failed), a window over a local target accepted and inert (0 passed, 1 failed), the refreshed key dropped from the JSON target (0 passed, 1 failed), the batch argument ignored (0 passed, 1 failed), the verb prefetching at the pull batch (0 passed, 1 failed), a record ahead of the clock trusted (0 passed, 1 failed), the git answer dropping the refresh (0 passed, 1 failed), a stale read saying nothing (0 passed, 1 failed), and a stale read fetching no body (0 passed, 1 failed); the loot-remote key mutation first ran green against a pin that read only the git remote, and the pin was widened before it was believed. no migration, no wire or format byte moves, and no forge or relay byte moves, so this owes no deploy. the workspace suite is green (3987 passed over 124 binaries, 7 ignored) (#2108) 83c96c6a · dbf3dbe6…diff
  • loot seek is easier to drive: --name <glob> matches a basename at any depth, spelled into the same pathspec as --path three ways, the glob for the root, **/glob for everything below it because the dialect **/ crosses one directory or more and never none, and **/glob/** for what a directory of that name holds because the bare-directory affordance reaches a root-level pattern only, joined by the group any-match rule, because the dialect stops * at a slash and every Rust file was a --path pair agents get wrong, with a glob carrying a separator refused as a path by code; the ambient target resolved is the root lexical absolute path rather than a dot, the same root --in <dir> prints, with no verbatim prefix on Windows, a value that moves where the key does not because a dot was never a location a later call could use, which ADR 0023 says; and loot seek --schema prints the verb machine contract as one JSON document from anywhere, contract first, derived from the code rather than typed: the flags off SPEC and the attached group, the marks and their columns off MARK_COLUMNS, the codes off code::ALL, and three examples the verb renders itself, an answer carrying every optional key, a counted answer and a coded refusal, with --json accepted and any question beside it refused. the codes come in two sets that say which is which, verb_codes off code::ALL and shared_codes off CliError::SHARED defined in error.rs where those slugs are minted, and the tables that are lists are pinned against what the code does: every mark constant read off the file is in MARK_COLUMNS with the arity a rendering under every Detail emits, code::ALL is every pub const in the module read off its own source, SHARED is every slug error.rs mints, and every key Answer::json can write is in the examples, the refusal example rendered by the multi-head raiser itself. the two-axis review before landing renamed the codes key before it froze under a name that read as the whole set, moved the --name refusal ahead of the target open so a bad glob costs a remote no round trip, said that --name is not rebased by the argv door because a basename is not a path and pinned it from a subdirectory, pinned the git-local and relative --in resolved, and corrected the ADR to the spelling the code makes. pinned in the unit tests on a nested fixture and the refusal, on the schema derivation, and through the spawned binary on --name equal to the pair it spells and not to **/ alone, unioned with --path, the ambient resolved absolute and equal to --in <root>, and --schema from a directory that is no repo. red under mutation, counts read each time: --name not spelled at any depth (0 passed, 1 failed), a --name with a separator accepted (0 passed, 1 failed), the ambient resolved staying where the verb ran (0 passed, 1 failed), --in <dir> resolved staying as typed (0 passed, 1 failed), --schema taking a question beside it (0 passed, 1 failed), MARK_COLUMNS forgetting the count row (0 passed, 1 failed), MARK_COLUMNS with an arity wrong (0 passed, 1 failed), code::ALL forgetting a slug (0 passed, 1 failed), the schema example carrying no refresh (0 passed, 1 failed), the schema omitting the bare flags (0 passed, 1 failed), SHARED forgetting a slug (0 passed, 1 failed), the schema omitting the shared codes (0 passed, 1 failed), the schema refusal example not the raiser (0 passed, 1 failed), a --name refusal waiting for the target to open (0 passed, 1 failed), and --schema answering a question instead through the binary (0 passed, 1 failed). no migration, no wire or format byte moves, and no forge or relay byte moves, so this owes no deploy. the workspace suite is green (3999 passed over 124 binaries, 7 ignored) (#2115) ca25f385 · dbf3dbe6…diff
  • loot pipeline (#2127, ADR 0091, map #2099 ticket 3): the tracked .lootpipeline at the repo root, in the .lootattributes grain with no dependency column, one step per line as name, trigger, kind and what, where the trigger names the tree a step judges, change or main, never the moment, and the kind is run, argv through the spawn seam with {message} and {paths} as gates reads them through one shared builder gates::spawnable, or check, a loot-native gate deferred naming #2128 until the checks live where the verb can reach them, and a deferred step is a refusal exiting 1, never a pass. loot pipeline lists and spawns nothing; run judges the working tree by default or, under --version, a scratch tree of a held version written by Workspace::scratch_tree from readable_tree_at so the readability decision precedes any file, holding the version own .lootpipeline, and removed on drop, with the hand-off from Workspace::version_handoff naming the paths whose readable content differs from the first parent; --only narrows to one step and an unknown name is refused naming the declared set; status reads the pipeline/TRIGGER/KIND passes and pipeline/approve admissions trusted keys signed on a version through attester_trust, passes only. A tracked file never runs implicitly: loot new with a run step declared and no .loot/gates line reaches no spawner, pinned. Frozen shapes in ADR 0023 amendment, exit code on the shape. Measured on the release binary: a two-step run 74 ms, run --version 47 ms and a listing 18 ms on a three-file repo; on this repo of 1452 tracked files, run --version HEAD materializes the whole readable tree in 1.2 s against a 0.15 s listing. Found by that measurement, not by thought: under load a Windows scanner held a freshly written scratch file, the one-shot remove_dir_all failed and its swallowed error left plaintext in the temp dir, so ScratchTree drop now retries over half a second and names the path on stderr if it still cannot remove it. Seven censuses moved, each read off its own refusal: the verb tier method count to 387, MACHINE_OUTPUT gains pipeline in sorted position, the usage spells the list leaf as an invocation line, README block and its all 84 verbs sentence, 133 verb lines in three places, the revset placeholder lists take <change|main>, <step> and <id> and SELECTOR_ONLY takes pipeline status, and the site CLI page names the verb; ADR 0066 counts 84 dispatched, 82 in the table, 32 emit machine output with an amendment. CONTEXT.md Pipeline entry says what is built. Nine unit pins plus two in change.rs, five broken once by a named mutation and confirmed red at 0 passed 1 failed then green at 1 passed: a check made a pass, the trigger filter widened, the scratch removal removed, the approval role made a job, and the tracked file run at a finalize. loot-cli and loot-first green across 64 binaries, 2162 passed; the site verb census green. No wire, format or store byte moves, no migration (#2127) fe693773 · dbf3dbe6…diff
  • loot-hygiene (#2128, map #2099 ticket 4): the land text gates move from loot-first into a crate with no dependency that builds for wasm32, re-exported as loot_first::hygiene so every gate row, checkout test and AGENTS.md path reads unchanged; a new crate rather than loot-codec because three consumers now read it, loot-first, loot-cli and loot-wasm, and the wasm one is the fact ADR 0076 §2 no-new-crate reasoning did not have. Corpus::from_entries files an in-memory tree under exactly the allowlist the directory walk applies, top-level files plus SOURCE_ROOTS filtered by TEXT_EXTENSIONS minus SKIP_DIRS and SKIP_PATHS, through one filing rule both constructors share, and a link resolves against the entry set with dot and dotdot folded, so a Worker with no directory answers what the land answers. CORPUS_CHECKS is the table a .lootpipeline check step names, keyed by the land gate names and pinned to them, holding the floor-free halves: the land refusals still refuse a vacuous walk first because a mis-rooted land must not read as clean, but a three-file repo is not mis-rooted, so a check answers about the files it was handed. loot pipeline now runs check steps in-process over one corpus walk per run, a finding is failed and exits 2, an unknown name cannot start and names the set and exits 1, and the ? deferral #2127 shipped this morning is retired the same day with the ADR 0023 amendment saying so. loot-wasm links the crate and a wasm-bindgen test runs the three checks from entries under node. Measured: the site wasm is 475667 bytes before and after, unchanged, because nothing exported calls the crate; native the three checks over a 1120-file synthetic corpus take 0.8 ms in release and 28.0 ms under wasm in node; through the release binary on this repo, roughly 1355 covered files and 20 MB by an approximate count, the three checks cost 160 ms warm against a 73 ms listing and 6.5 s on a cold page cache. Two lands worth of censuses read off their refusals: the destination allowlist row for the moved file, and no other. Five pins broken once by a named mutation, red at 0 passed 1 failed then green at 1 passed: from_entries skipping widened, link resolution made true, the table key misspelled, a finding made a pass, an unknown check made a pass; the fifth sweep needed a second pass because its first restore string matched twice and left the mutation applied, caught by the full run that followed. 66 test binaries green across loot-hygiene, loot-cli and loot-first. No wire, format or store byte moves, no migration (#2128) 858575e1 · dbf3dbe6…diff
  • the seek cache home is bounded, and loot seek --gc tends it: every remote an agent touches leaves a position under the cache home and nothing bounded it, so the rule is now one function, seek::gc::plan, that the verb and the cap share and cannot disagree on, a position being a directory the two caches make, a .git holding a HEAD or a .loot holding a .loot, with its bytes on disk and the clock of its last refresh read off the #2108 record and never an mtime; a position whose record is older than the age asked for goes, then oldest first by that record positions go until the home is within the cap, and a position with no readable record is never removed by the cap because its age is not known and a cap is not a reason to guess, listed with a dash and removed only on the explicit ask, --older-than 0, which removes every position; a position another invocation is refreshing right now is busy and removed by nothing, since both caches write their record last and a position in use would otherwise be exactly the oldest candidate, so a refresh writes loot-seek-refreshing when it begins and removes it on every exit, a marker older than an hour reading as a crashed refresh; 0 is no cap. on every invocation that refreshed a position the home is measured against LOOT_SEEK_CACHE_CAP, 2 GiB when unset, and the oldest other positions go until it fits, said on stderr in one line, never the position just refreshed whatever its age, and a skipped refresh inside --fresh measures nothing, nor does the ambient repo; the environment cap is read before any target opens so a value that will not parse is bad_flag_value at no round trip, the just-refreshed position is matched on canonical paths so a URL spelled in another case still names its own, a removal that fails is said in the same line and never refuses the answer, and the measure is a walk of sizes and records only, targets read by --gc alone, paid once per refreshing invocation, 2 ms over this desktop cache of three positions and 217 files against the 30 ms process floor, which is the cost of having no size record to trust. loot seek --gc does the same on demand with --older-than <days> defaulting to 30, --cap <bytes> defaulting to the environment, and --dry-run deciding and removing nothing, printing one row per position with kind, bytes, refresh clock, what was done and why, and target, and the home bytes before and after, a failed removal a failed row with the error on the notice channel; beside any question it is refused by code as --schema is, its flags without it the same way, and a value that will not parse is bad_flag_value. its porcelain is two new marks, G and H, under a flag that did not exist before, in their own GC_COLUMNS table the mark census now reads beside MARK_COLUMNS, and --schema files the four flags under maintenance rather than among a question flags, since the SDK reads that set as the fields of a question, which its schema pin holds green unchanged; the shapes are frozen where every seek shape is, in an ADR 0023 amendment, and the one stderr line is recorded against its remote-answer sentence there. pinned pure on the rule, by age, no age, oldest first under the cap, the unrecorded position standing though the home is still over, the explicit ask, the kept position under both, the busy one under all three, a record ahead of the clock, and a tie on the clock going in path order; on the walk over a hand-made home with a bare git repo, a loot position, an unrecorded one, two directories that are not positions, a fresh marker and a stale one, canonical paths and the sizes-only walk; on a failed removal rendered failed with its bytes standing and the removals around it still done; on the schema census over both tables and the maintenance key; and through the spawned binary in the smoke suite over the relay and the git caches it already makes, the cap on an invocation removing the older git position and never the loot one just refreshed, a malformed cap variable refused by code before an unreachable host costs a round trip, the ambient repo measuring nothing, a skipped refresh measuring nothing, --dry-run in porcelain and JSON removing nothing with the H totals the sums of the G rows, a cap the two standing positions just fit removing the older git one in JSON, a cap of one byte unable to reach the unrecorded position, thirty-one days removing by age, a busy marker holding the unrecorded position against the explicit ask until it ages out, and the seven refusals by code. ADR 0090 and ADR 0023 amended, CONTEXT Seek, the usage line and the synopsis. red under mutation, counts read each time: age never removing (0 passed and 1 failed), the cap removing newest first (0 passed and 1 failed), the cap guessing at an unrecorded position (0 passed and 1 failed), the just-refreshed position going by cap (0 passed and 1 failed), the just-refreshed position going by age (0 passed and 1 failed), --older-than 0 not the explicit ask (0 passed and 1 failed), the walk skipping the loot kind (0 passed and 1 failed), the walk reading no record (0 passed and 1 failed), a loot target never read (0 passed and 1 failed), the gc flags landing among a question flags in the schema (0 passed and 1 failed), the H row losing its removed count (0 passed and 1 failed), --dry-run removing (0 passed and 1 failed), the cap on an invocation never running (0 passed and 1 failed), the cap on an invocation removing the just-refreshed position (0 passed and 1 failed), a skipped refresh measuring the home (0 passed and 1 failed), a gc flag beside a question admitted (0 passed and 1 failed), a question beside --gc admitted (0 passed and 1 failed), a busy position going (0 passed and 1 failed), a stale marker busy forever (0 passed and 1 failed), the walk reporting the spelled path rather than the canonical one (0 passed and 1 failed), a failed removal reported as removed (0 passed and 1 failed), the loot refresh writing no busy marker (0 passed and 1 failed), the git refresh writing no busy marker (0 passed and 1 failed), the guard never dropping the marker (0 passed and 1 failed), the busy marker surviving the refresh (0 passed and 1 failed), and the environment cap read after the target opens (0 passed and 1 failed). no migration, no wire or format byte moves, and no host behaviour moves, so this owes no deploy beyond the one #2123 already owes the release. the workspace suite is green (4023 passed over 126 binaries, 7 ignored) and the SDK seek suite is green in the lane (11 passed) (#2125) 70ea581b · dbf3dbe6…diff
  • the two shapes that graduated without a record get their amendments, telemetry status and off carry the contract field, and the comma-joined column gets one home: #1971 carries the ADR 0088 section 4 follow-ups and asks for each premise to be re-verified first, so each was read against the tree — ADR 0023 held no amendment for count-objects (#1523) or telemetry (#1658), telemetry status and off printed no contract field and the string dash where V3 says null, and the Verdict entry in CONTEXT.md still typed 17 of the 68 beside a roster where the dispatch table derives 32 emitting verbs and 51 prose-only ones; the ticket comment correcting item 2 is the later word and is taken as such, telemetry on printing report JSON for the same deliberate reason as show. ADR 0023 gains two dated amendments describing what shipped: the one-metric-per-mark census rows, and the name-keyed telemetry rows with the divergences frozen beside them. status and off lead their JSON with contract now and spell an unresolvable spool null, rendered by status_shape and off_shape, which take values rather than reading the config so all three renderings are assertable; show and on stay exempt because what they print is the report the sender posts, and the exemption is pinned over Report::to_json rather than over a verb, that string being what the endpoint receives. The shared flag column item 3 asks for is verdict::joined_col, the comma join with the dash when empty, and what defines membership is that spelling rather than a roster, so the columns spelled that way are its callers: blame, delta_shape, explain, heads, log, evolog, attestation_shape, refit_shape, seek, tag, both pipeline listings and lanes route through it and no frozen shape pin moved. The cross-shape census is declined with its cost recorded in ADR 0088 section 4, since a census over values needs a pub on each shape that #1553 refuses, one over a spawned binary reads nothing for a verb whose arguments it cannot supply, and its exemption table would be a hand-maintained list asserted as complete. Red under mutation, counts read each time: joined_col joining with a tab (loot-cli 1335 passed and 8 failed), the empty set printing an empty field instead of the dash (1324 passed and 19 failed, and loot-core 647 passed and 1 failed naming lanes_porcelain_rows_are_the_frozen_contract), the contract field taken off status and off (5 passed and 3 failed), the spool null spelled as the string dash again (7 passed and 1 failed), and a contract field added to the report the sender posts (6 passed and 2 failed), each restored. No migration, no wire or format byte moves and no host behaviour moves, but two leaves of client JSON move, so this rides the next release and owes no deploy. The workspace suite is green (4107 passed over 132 binaries, 8 ignored) (#1971) 4ef9c62b · dbf3dbe6…diff
  • the permissive deposit arm gets the pin its defence was resting on, the standing-lane assertion that could not fail gets a comparison, and the declined cross-shape census weighs the instrument it never considered: #2194 carries the sweep 14 fix-ups plus the five a focused review of #2188 found, each premise re-verified on the tree first. item 8 is judged kept rather than closed to match its siblings, because where the seal cannot be produced embargoed_paths keeps the tree entry claim and the tree entry instant, which an unsigned tree can spell 0, and the recorded defence is real, grant_sealed reading the same object through the same door before it seals anything; so the arm stays and both halves are asserted now, the row surviving with the number the entry chose and the grant that row plans refusing at the same address, under a control that the same call succeeds while the seal reads. item 9 is made to bite: a second path over the same Restricted seal is entered honestly, so the standing-lane arm is a comparison rather than an absence, and the comment names the mutation that reddens it. item 10 narrows a pin message that reached past what it asserted, onto the deposit-plan pins that do assert the fan-out. item 1 corrects the ADR 0023 cost paragraph, which named the spool leaf where the endpoint moved too, off an unescaped format string onto the shared escaping, so an endpoint holding a quote or a backslash had been emitting JSON a parser rejects; the matching commit correction is already a comment on #1971. item 3 weighs the text census and records a measurement instead of an assumption: it needs no visibility widening and spawns nothing, so no census can was the wrong shape of decline, but what a text census reads is a spelling and section 3 is a rule about rendered bytes, and measured, the comma-join needle finds its own home and sites that are not columns while the contract field is not spelled one way, so a needle on either spelling reads a subset and says nothing about a shape carrying no such field at all, which is the defect #1971 came here to fix. item 2 replaces a false inference in the land-change skill: those three headings belong to the cargo test gate, and every other gate arrives as a finding under prose of its own, the line-ending gate among them, so an unfamiliar heading is another gate before it is a new kind; workflow.md scopes the same sentence. item 11 takes the completeness claim off the deposit accessor doc, where privacy is what bounds the callers, and off the census block that asserted a key guard over a membership rule which does not check it. item 12 names the two readers still deciding off the entry, the mirror projection and the forge manifest fold, with what bounds each, and records that neither belongs to #2187. items 4 to 7 are the smells: the forwarding closure, the clone taken and then borrowed, the exemption pin that read the real machine config and now builds its report from values, and the reversed import. red under mutation, counts read each time: the unreadable-seal arm dropped (68 passed and 1 failed, the row reading empty), that arm handing on a substituted instant (68 passed and 1 failed, 9999 where 0 belongs), restricted_paths made to ask the seal (68 passed and 1 failed, secret.txt joining owned.txt in the standing lane), a contract field added to the report the sender posts (6 passed and 2 failed) and joined_col joining with a semicolon (loot-cli 1335 passed and 8 failed), each restored to 69 passed and 0 failed. no migration, no wire or format byte moves and no host behaviour moves, and no output byte moves either, every code edit here being a refactor or a test, so this owes no deploy. the workspace suite is green (4110 passed over 132 binaries, 8 ignored) (#2194) 609afd9f · dbf3dbe6…diff
  • loot runners lists the runner rows a forge holds for this repo, part 2 of 2 of this ticket: name, key, the step kinds each may claim, live or retired, and when the forge last heard from it, owner-only because it is the owner configuration, retired rows included because who was allowed to run a version is what an audit asks. last seen needed a column, migration 0021 runner.last_seen_at, written by the runner door on every admitted request and throttled in its WHERE clause to one write per runner per 60 seconds, so a runner polling every second costs one write a minute; a registration never sets or clears it, so a runner registered and not started reads never seen, and deriving it from job rows was rejected because an idle runner finding no work leaves none. the first spelling of that throttle, last_seen_at <= $3 - $4, failed on Postgres 18 at the first touch, which would have made every runner request a 500 on the live forge while every in-memory test passed; the edge is computed in Rust now, and landed part 1 never wrote the column. the shape is born frozen under ADR 0023: porcelain pubkey state capabilities last_seen and the name last because it may hold a space, JSON with contract, remote and one object per runner, absent times as null, both from one value and pinned byte for byte. the verb census cost, counted: MACHINE_OUTPUT gains runners, the README block and its sentence move to 85 verbs, verb lines 133 to 134 at three main.rs sites and one ADR 0066 claim, dispatched verbs 84 to 85, dispatch table 83 to 84, machine output 32 to 33 with its named list, and two ADR 0066 lines that are dated records were left alone as the census skips them; the site verb list gains runners and its gate is green. the verb runs end to end through the spawned binary against a real forge. red under mutation, counts read each time: the touch unthrottled (1 passed and 1 failed), the door never touching (0 passed and 1 failed), the name not last (0 and 1), an absent time spelled as a dash in JSON (0 and 1), and a registration allowed to set last seen (1 and 1) once its pin moved into the store, where the defence lives, after a first attempt at the route proved unobservable; on Postgres 18 the throttle removed from the SQL went red at 0 passed and 1 failed after a green unmutated arm, each restored green. bash ci/local.sh is green against Postgres 18 (4258 passed over 136 binaries, 10 ignored). migration 0021 rides the forge binary, so the forge owes a deploy (#2158) 6dd6ae69 · dbf3dbe6…diff
  • loot seek --gc exits 1 when a removal it decided failed, with every row and the H row still written and the error still on stderr: gc::Report now implements Emit::exit_code (#1764), 1 when any decision carries failed and 0 otherwise, 1 being the ADR 0025 row for an I/O error and the code the error path in main.rs exits with, the reasoning gates::EXIT_UNRUNNABLE records; the cap after a refresh is unchanged and a failed removal there does not move the question exit. the ticket named the hook #2127, which is loot pipeline, the verb whose code it cites; the hook is #1764. the ADR 0023 #2125 amendment gains the exit sentence, the ADR 0090 #2125 amendment says the invocation exits 1, and the seek module doc scopes its refusal-only exit rule to a question. pinned in a_failed_removal_is_reported_as_failed_and_stops_nothing: the report exits 1, and 0 with the failure cleared. red first against the unchanged code (0 passed and 1 failed). red under mutation, counts read each time: the code fixed at 0 (0 and 1), fixed at 1 (0 and 1), keyed on a decided removal rather than a failed one (0 and 1), each restored to green. through the binary under a scratch LOOT_SEEK_CACHE with one position held open, the lane binary exits 1 and the primary release binary 0, both printing the removed and failed rows. no migration, no format byte and no published wording moves, so this owes no deploy. the workspace suite is green (4325 passed, 0 failed, 12 ignored) (#2154) 1dc9e314 · dbf3dbe6…diff
  • the review-sweep fix-up over #2299, #2142 and #2154. a seek cache round now claims the busy marker before it decides anything: LootCache::claim_and_make creates loot-seek-refreshing with create_new before it asks whether the position stands or makes one, so a position another invocation is making carries a marker from before its store exists, and a round that finds a live one neither makes the position over it (refused, naming the state, when no store stands yet) nor removes it when its own refresh fails; a marker past the hour is taken over. before, the marker was written after the make, and a second round inside that window read the half-made store as a leftover and removed it. a first refresh now means no record file at all (Refresh::absent): the record is written through atomic_write rather than truncated in place and read through read_replaced, so an empty or unreadable record is a refresh that finished and a failed refresh leaves that position standing. pinned in seek.rs by a_make_in_progress_is_not_removed_by_another_invocation, which drives a make through claim_and_make and fails a second round inside it, and by a_record_that_does_not_read_is_not_a_first_refresh; red first against the unchanged rule (1 passed and 2 failed, the make extracted into claim_and_make with the marker still after it). red under mutation, counts read each time: the claim moved after the make (1 passed and 3 failed), begin in place of claim (1 and 3), a round that finds a live marker deciding first on the record (2 and 2), absent read as unreadable (3 and 1), the record written with fs::write (3 and 1), no refusal over a claimed unmade position (3 and 1), a stale marker read as live (3 and 1), each restored to green. the seek unit tests now default to a cache home under the temp directory, pinned by the_unit_tests_cache_home_is_never_the_runners (red with the default dropped, 4 passed and 1 failed). stale sentences fixed: the ADR 0090 #2142 amendment is corrected, the CONTEXT.md seek entry names the marker exception and the gc exit, ADR 0023 scopes the refusal-only exit to a question, root_notice and discovered_store_dot say why the function is free rather than counting callers, the in_progress verb is the verb whose stop created the record and another verb is refused, the last-updated header states its import reach by reachedModules, the seek module doc states the exit property, and a CONTEXT.md rewrap leftover is rewrapped. through the lane binary under a scratch LOOT_SEEK_CACHE, a position with an empty record stands after a failed refresh and a marker-only position is refused with its marker left. no migration, no format byte and no published wording moves, so this owes no deploy. the site gate is green (680 passed and 62 skipped over 62 files, 62 surfaces) and the workspace suite is green (4328 passed over 137 binaries, 12 ignored) (#2303) 4c489c97 · dbf3dbe6…diff
  • loot ticket new, loot ticket show and loot tickets ship as slice 1 of the native tracker (spec §12, map #2422): a new ticket module in loot-cli holds the layout (tickets/<id>, tickets/public/<id>, tickets/<group>/<id>, where an 8-letter k-z segment is always an id and never a space), the one file format of header lines, a blank line and a verbatim body, ids of 16 random bytes shown as 8 letters and minted again on a collision with any ticket at the read position, and causal order keyed on the space-stripped path, found by walking the change graph from the read position in descending generation to the change where each key first appeared, with a key only the working tree holds sorting after every recorded one. a read folds the tree at the harbor tip, this position and the working tree, so a lane sees a ticket a sibling landed without adopt and its own uncaptured one. new writes meta, the body and one waits-on edge per --on as plain files in the working tree and prints the id alone on stdout; the body comes only through --body-file, a file or - for stdin, with a leading byte-order mark dropped and CRLF made LF, and --title refuses a tab or a line break. show --json and tickets --porcelain are the spec §7 shapes, recorded in an ADR 0023 amendment; the JSON leads with contract as ADR 0023 has every --json do, the open form and the sealed {contract, id, sealed: true} alike, the spec field list naming the payload after it. a ticket this identity cannot open is an S row, and a field filter keeps it. --restricted-to and --published are left to #2428. red under eleven named mutations over sixteen runs, each 0 passed and 1 failed and restored: position keyed on the spaced path, the harbor tip skipped, a colliding id kept, the byte-order mark kept (once through the binary), a tab allowed in a header, an unopenable meta dropped, an id-shaped segment read as a space, labels folded add-only, a field filter dropping a sealed ticket, and contract left off the sealed form and off the open form. the verb census moves: README 87 verbs, 137 usage lines, ADR 0066 at 87 dispatched and 35 with machine output, MACHINE_OUTPUT, the placeholder census and the site verb list. cargo test green in two runs, 4576 passed with 13 ignored, and the site gate green at 819 passed (#2424) 1a26ff0a · 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.