Changes touching this path
- the trust matrix says what each reader can actually read, and /why stops claiming the relay cannot read your code (#1130)
Publishes /trust as the one canonical answer to who reads what: six readers
against four tiers, with a regardless-of-tier block for path names and DAG
metadata, and footnotes on the two cells whose plain answer misleads —
Restricted vs the git mirror rests on ADR 0028 projection omission rather than
keys, and an embargo reveal is enforced by the relay clock rather than by maths
(ADR 0027, written down nowhere until now).
#1089 falls out of the matrix as its ticket predicted: the /why headline was
false over ordinary code, since Internal is the default and a relay reads it by
design. Every other surface now cites /trust instead of answering independently,
which is what let /why and /docs drift into contradicting each other.
ADR 0069 records that Internal stays the default deliberately — Restricted by
default would make a clone readable by nobody and empty the git projection.
ADR 0068 records the at-rest design for #1138: .loot/keyring is raw oid-key
pairs beside the ciphertext it opens, so the unit of exposure is the whole
.loot/ directory.
Carries ADR 0067 from #1132, which shared this tree uncaptured.
361b2026 · dbf3dbe6… - the alpha states what it promises: a scoped set on /install, v0.5.0-alpha.N carrying it, and no format-major freeze (ADR 0066, #1128)
f85705c7 · dbf3dbe6…diff - ADR 0066 records what its own audit found: line 2 sends scripters to a channel 49 of 61 verbs do not have (#1151)
6a404c79 · dbf3dbe6…diff - the FORMAT_MAJOR redeploy obligation is checked at publish time, so a bump cannot strand a host through inattention (#1152)
64a989f6 · dbf3dbe6…diff - the alpha promise's line 2 tells the truth about machine output: 13 of 63 verbs, the contract number only on --json, and a guard that keeps loot --help's annotation honest (#1151)
95ace495 · dbf3dbe6…diff - the alpha promise ships in the product: /install carries ADR 0066's canonical block, docs and loot --help carry the headline verbatim, and one test pins all three surfaces plus the disclosure route (#1137)
9d724d85 · dbf3dbe6…diff - the verb counts in prose are read off USAGE instead of hand-copied, the /install pin fails on the superseded line 2 it used to pass, and ADR 0066 stops asserting the number it calls a mismeasurement (#1216)
75768f1f · dbf3dbe6…diff - the format-skew rationale states the measured position: an allowlist of one refuses a stranger before any format check, so skew changes which refusal they read, and the land verdict's relay=FAILED was the detector that was always there (#1200)
8a623333 · dbf3dbe6…diff - loot 0.4.17: the Known Issues page is re-reviewed against the binary it now names, four entries move because their fixes shipped, and the land runbook learns that a skip drops three gates and not one
44de9890 · dbf3dbe6…diff - loot id phrase cuts the key for the recovery door loot id recover already opened, issuing the 24 words for any identity ever minted with no derivation change, and refusing outright when stdout is not a terminal because those words are the private key
8bd665a5 · dbf3dbe6…diff - the docs demo runs between two markers that cannot silently widen, and the six surfaces still sending readers to a dead /docs anchor name the route that exists, with a test that fails the next time either class rots (#1394)
3493a354 · dbf3dbe6…diff - loot stops having no way to read one recorded file, because `loot cat <path> [<selector>]` resolves one path at one change through the same endpoint, key oracle and burn lookup the delta seam already resolves a whole side with — and it names which of the four absences it hit rather than printing an empty file, while binary content passes through verbatim because the third option nobody names, a lossy render, writes a corrupt file and exits 0 (#1490)
cc1384f9 · dbf3dbe6…diff - an unexpected head stops being something the operator can only fold on faith, because `loot heads` lists every live head with what introduced it and how many changes it holds of its own — and since a head is a tip no other head reaches, a count of zero can only mean the head is not live, so #1477s empty head is superseded and the converge it asks for would merge nothing (#1496)
955ec9ff · dbf3dbe6…diff - losing one bad file stops costing you every other uncaptured edit, because `loot restore <path>...` discards per path against the same delta seam `status` reads — and it never decrypts to decide, so a path whose recorded side is sealed, embargoed, burned or merely past its grant expiry refuses the whole run rather than clobbering the readable half beside it (#1498)
cd463799 · dbf3dbe6…diff - a named landmark stops needing a format major to carry its message, because a landmark IS an attestation under a reserved role envelope the signature already covers — and that is also what keeps a retirement from eating the thing it retires, since the attestation log is keyed by (change, attester, role) and writing both under one role made the retirement silently REPLACE the attestation rather than record beside it (#1508)
2d06014f · dbf3dbe6…diff - telemetry gets a contract before it gets a sender: ADR 0074 settles what an opt-in loot binary may report, and the alpha promise stops enumerating what leaves your machine because that list could not have absorbed a fifth item anyway - a telemetry report is not experimental in the sense line 3 means, it is us. A report is a fact about the tool and never about the repo: every verb is counted with burn and maroon collapsed to other because each records an incident rather than a feature, timings are refused as repo scale laundered except one bucketed Workspace::open, and the rule that decides future fields is enforceable rather than prose because the report carries no runtime-constructed string except the telemetry id, which forecloses paths, messages, search terms and repo names by construction. machine-id is not reused because the arrival guard and an unlock session compare it, so exporting it would ship a security binding value to a server we operate. The spool is the outbox so show cannot drift from what is sent, the opt-in is a verb and never a first-run prompt because the dominant user of this CLI is a non-interactive agent and an opt-in a machine can trip is not an opt-in, and the relay half splits because loot serve runs from this same binary on a machine somebody owns while relayd and forge would be reporting on third parties who never opted in. What ships is the contract plus the guard behind the rewritten line 3, which sweeps every rs file under a crate src, blanks cfg(test) items by brace depth, and allows six occurrences each carrying a written reason - and it was checked against a planted destination before being trusted (#1626)
f2998fea · dbf3dbe6…diff - the run's own signature defect, found inside the file it built to fix it: source_walk said four consumers while the very next land added a fifth, and the sentence is now generated rather than corrected - it walks src and tests through the module's own code mask so a mention in a comment or a string is not a call, groups by enclosing function because the destination census reaches the walk through a use import and a call-site count would miss it entirely, and pins the shape as well as the number by requiring every consumer's file be named, the declaring units equal the consuming units, and every consumer call the discrimination floor first. that last requirement is where this ticket was wrong: it said one consumer skipped the floor and that two others called it, but the crate held exactly two call sites, so three of five skipped it - all five call it now, and the reason is recorded at the verbs site, since its three anchors check that this walk found Workspace's methods while the shared floor checks the walker on the two cases it was written for and this tier has none of. ADR 0074's section 9 stops describing a walk that no longer exists: only the mechanism sentence moves, with a dated amendment quoting the old text verbatim and saying why it was worse than stale, because it named a bug the walk did not have while carrying two it did, while the purpose paragraph and the six-entry allowlist claim are untouched. the trust page's twelve-item enumeration goes, and the durable half is a fourth pinned surface beside install, the ADR and CONTEXT: the positive arm pulls line three out of ADR 0066 itself and requires both pronoun-free halves verbatim rather than typing them, the negative arm forbids a code element inside the alpha section because that is the regrowth shape twice observed, and both were proved red before being trusted. two stale copies this ticket did not name are repointed at the derived header instead of restating a number, and ADR 0066's own amendment turns out to name four things while listing five, which git show against the pre-amendment line settles as five (#1676)
c1a195b9 · dbf3dbe6…diff - ADR 0074's destination guard was catching one planted shape in three, and the ordinal that a one-word fix left behind stops being a number at all: the census read only a scheme, so a bare relay.millerbyte.com in shipped source and a host assembled at runtime both passed silently while the ADR claimed no hardcoded destination could enter without someone editing the list - the predicate now takes a scheme or two dot-separated labels ending in a known web TLD, with underscore a token character rather than a separator so config.app_name does not read as a host hitting the TLD app, and that widening finds exactly one production line across the whole crates tree, the disclosure address in USAGE, which becomes the seventh allowlisted entry with its reason. every needle in that list is now asserted to still exempt a live line, so a dead entry fails instead of sitting there, and the ADR's own seven-entry sentence is read out of the ADR and compared rather than typed twice, with a stated-exactly-once guard so a vanished sentence fails rather than passing green. the limits that survive the widening are pinned as controls and written into section 9 rather than implied away: a suffix outside the known list, a bare IP, a host built at runtime, and any shipped file that is not rust. and the fifth thing this amendment said ADR 0074 adds becomes one more thing, because the enumeration it counted was deleted by the very change it describes and no referent survives to derive from - a position nobody types cannot fall out of step with a list that moved, which is the second wrong count in that one bullet and the reason for deleting rather than re-numbering. the twice-paid claim beside it gains its predicate too, since under any wrong count in this file the number is at least four and only under a typed count of one of its own lists is it two; and the revset composition #1497 promised turns out to work and is pinned behaviourally at last, the load-bearing half being that a two-change revset draws two nodes and a tilde rather than an off-screen parent (#1680)
37d450f1 · 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 mis-seal question gets a verb, and the reason it could not have one turns out to be that the decision existed three times: visibility_for, is_published and public_by_fallthrough were three near-identical loops over the attribute rules, so there was nowhere to hang an explanation off and the rule against writing a second matcher was impossible to honour until they collapsed into one Attributes::first_match. That collapse, not the line numbers, is this change - the line numbers were already tracked since #1329 for MalformedRule, including the convention of counting blanks and comments so a number matches what an editor shows. loot explain now names the .lootattributes line that decided a path, lists what it passed over so first-match-wins is visible rather than asserted, reports which .lootignore line covered it, and answers for a path with no file on disk, which is the mis-seal question asked before the mistake rather than after. Because explain and the real decision are projections of one traversal they cannot drift, and that is pinned twice rather than assumed: one test deletes the line explain named and asserts the real visibility moves, then deletes each line explain called skipped and asserts it does not, hardcoding nothing about which rule should win; the other runs a real capture and asserts that for every recorded path the sealed tier equals explain's token, with the ignored file absent and a positive control that the sealed set is non-empty. Eight breakages were each proved red before the tests were trusted, including giving explain its own matcher while the decision kept the old one. And loot explain . would have printed a fully furnished answer about a path that is not one, because ** matches the empty string in this glob dialect and the door rebases a bare dot at the repo root to the empty relative path, so in any repo carrying the catch-all ADR 0038 says every real repo wants it would have named a tier and a line for nothing at all - refused at the door now, with a control showing the rules really would have answered. Found by running it rather than reading it. Five censuses needed updating where the plan named two, and the two nobody would predict are the README verb block, which is a docs file failing in the bin suite, and the derived impl-width sentence in verbs/mod.rs, because adding a single inherent method to Workspace reddens a doc comment (#1521)
ebd3285f · dbf3dbe6…diff - loot show becomes the fourth rendering over the one delta seam and grows no differ of its own: it calls Endpoint::base, ws.content_delta and render_delta, produces no hunk, counts no line and matches no content, and the pin is byte equality rather than shape - show ends with exactly the bytes loot diff --content <parent> <sel> produces in both content and stat modes, with the leftover prefix asserted to name no path, hold no hunk header and hold no disclosure line, so a second opinion about the parent or a renderer choosing its own words for the seam's tally both go red. Four breakages proved it, and a fifth attempt was a false negative caught by its author: truncating to the last newline is a no-op, so it passed, which is exactly as misleading as a vacuous test and is why it is named here rather than quietly replaced. Three of this ticket's premises were wrong. The two commands it says reading a change takes are really one command plus reading a parent id out of loot log by eye, because <version-prefix>~1 is not a form the #305 grammar spells and only HEAD~<n> is. Subject does not mean what the criteria assume: the tree defines it as the first line of the message and this repo writes single-line paragraphs, so the first run printed a two-thousand-character subject row - nothing truncates anywhere in the CLI, so rather than invent a rule the subject moves to the last header row, above the diff where git show puts the body, instead of pushing the lineage off screen. And the no-selector arm was ambiguous in a way that made the first message false: loot status prints a live working-change row computed off the disk under ADR 0030 while working_id and the @ selector both say there is none, so saying nothing is in progress contradicted the verb next door about the same tree - the message now names which absence it is, explains the live row, and points at diff and status, and show is deliberately not widened to render anchor against disk, because that delta is bare loot diff's by construction and a second verb answering it under a header claiming a non-durable version id is the duplication #1541 deleted. A merge names which parent it is taken against and prints the command for the other side rather than pretending the diff is whole. PROSE_ONLY_CEILING rises 50 to 51 deliberately with its reason written beside it, which is the mechanism landed one change ago doing what it was built for, and a pre-existing comment claiming the arity gate is a no-op for all but one verb is repointed at the property since it has been four for some time (#1492)
7ff52d8e · dbf3dbe6…diff - 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 - a contributor can offer work to a repo they do not own: loot propose over a three-path family - posture, propose and withdraw - and none of them is the ref-moving endpoint, which stays single because ADR 0075 refused putting the contribution path one flag away from the path that advances someone's tip. The bytes ride /stow unchanged, since the forge's stow literally calls the same push, so the batches are byte-identical and there is no new transport. One premise had to be WIDENED and the reason is a real replay: the ticket says sign the claim exactly as the head declaration does, but a head declaration names no repo and does not have to, because require_pusher catches a replay at another namespace - while a proposal is BY DEFINITION a write by a non-owner, so every repo with an open door admits the same signer, and a tip-and-base-only envelope is replayable by any observer at every one of them, in the contributor's name. The claim therefore carries the repo path and the server compares it against the URL's namespace, proved by RUNNING the replay and watching the second namespace admit it rather than by arguing it. The disclosure is an ORDERING rule and the posture route exists only to make it one: a client that learned metadata_public from the act's own answer could only ever speak afterwards, so posture comes first, the disclosure is printed second, and only then the ref read, the stow and the claim. It is pinned twice - once by a transport that records every call into the SAME trace as the output lines, so the assertion compares positions rather than presence, and once by byte offsets in real CLI stdout against a real forge - and a shut door still discloses first and sends nothing at all. Withdrawal is the tip change's AUTHOR and only them, where visibility admits three readers, because seeing is not acting and the carrier's remedies are the author's withdrawal or the owner's decline; it does not consult the door, since a shut door must not strand what is already offered, and absent and concealed refuse in byte-identical sentences. The lane refusal keeps every one of its old assertions, including that nothing reaches the forge before it fires, and gains the sentence naming the alternative. Twelve breaks were each run and read before being restored byte-identical, and the ones worth naming are the replay above, deleting the author check so a presenter can withdraw work they did not write, adding the door to the withdraw path so a shut door strands an offer already disclosed, and declaring the tip as a head so the CAS counter moves. The ref-moving path is asserted untouched rather than described as untouched: a proposal leaves generation and head set alone, and the SAME fixture then lands a change the ordinary way and the generation does move, which is the control (#1651)
83c5afa5 · dbf3dbe6…diff - loot merge <head> is converge restricted to one head rather than a second fold: merge_head and converge_heads both reach the graph through one private fold_onto, so the three-way content merge is the ADR 0044 seam unchanged, the conflict record is the one loot conflicts and loot resolve already read, the merge node carries a minted mechanical subject naming the head it folded, and loot undo steps it back through an op recorded off the merge node rather than off an outcome map that can be empty. What it deliberately does not do is retire anything: converge drops superseded heads on the way past, and a retirement is a change to a head the operator did not name, which is the whole of what separates this verb from the one it restricts. A flat head is refused rather than silently skipped, because naming a divergent co-version does not make divergence content-mergeable. Two premises of the ticket were wrong. The un-described-change refusal does not apply as it does to the other merges: that refusal belongs to adopt, raised inside fold_line_in, which captures and signs its own merge parent, while converge never reaches it at all because capture-first defers on any working change one branch earlier - so this verb inherits the deferral, and the refusal is unreachable by construction rather than reimplemented. And loot heads resolves no selector, it only lists, so the spelling merge takes comes from loot abandon --head, which routes through resolve_selector. A third premise is narrower: conflicts are recorded and nothing proceeds, but a single named head has no loop to stop, so that criterion is merge_tips behaviour unchanged rather than anything this change makes true. One inherited shape is named rather than altered - a deferred merge --porcelain prints nothing and exits 0, exactly as a deferred converge does, because a deferral rides the human field while the machine contract is verdict rows (#1505)
6107561a · dbf3dbe6…diff - loot format-patch lands as the fourth rendering over the one delta seam rather than a second differ: a header block plus Rendering::Patch, which drops the four-space body indent every other rendering files its content under and prints git's extended header, so what comes out is a real unified diff that git apply --check accepts and a real git apply reconstructs byte for byte, proved by applying it in a scratch repo and by a strict applier in the tests that checks every context and deletion line against the base at the line the @@ header declares, since a shape assertion cannot see a wrong range. Two of the ticket's premises did not survive contact. AC3 asks to omit AND NAME, and a sealed row has no name to give: the #306 rule already withholds it, so what is named is the row, its class and content address and tier and the seam's own four-way reason, and the wording of the decision promised a path this export is not allowed to print. And a rendering over seam ROWS alone is not quite sufficient for a faithful patch: the hunks come off blame::lcs_match's line space, which ate both the trailing newline and the carriage return, so the no-newline marker is recovered from the row's raw bytes instead, and a CRLF file's patch is LF, named as a limit rather than closed with the second differ this ticket was rescoped to avoid. One neighbouring behaviour is deliberately not inherited, the sanitizer: a patch exists to reconstruct a file, so caging a control character would emit a patch that rebuilds different bytes than the change recorded, silently, and content therefore leaves verbatim with the usage line saying so. Everything else is read rather than re-derived, the path set and each row's rung and the closing in-the-clear count all being the seam's, and the header is loot show's Header widened by the author pubkey and the whole message rather than a second read of the graph. That header is designed as loot apply-patch's input: its own format number on the first line, full-width ids because a patch outlives the store a prefix would resolve against, absence spelled none rather than the dash every human header uses, and the message folded RFC-822 style so that no body can collide with a delimiter. (#1512)
9b32538e · dbf3dbe6…diff - loot count-objects lands, and the constraint it is built under is that its number has to be the number gc acts on rather than a second opinion about it: the unreachable count and the reclaim figure are gc OWN pruner and sweeper called with dry_run, over a keep set extracted out of gc as DagRepo::gc_keep_set the way 1505 pulled fold_onto out of converge, so a census that disagrees with a collection is not a thing that can be written, and the pin measures a store, really collects it, then asserts the two halves and the sum all match. One premise of the ticket was wrong in the direction that saved work: nothing reported how large a store is as a VERB, but the totals half was already factored, since RepoStore::objects_size has been the perf record load-bearing counter since 632, so this adds no third walk of the object directory at all, it adds a caller. The extraction surfaced something gc had been getting away with: its keep set was position-dependent, because the primary own working change is the one root in neither the shared graph file nor the lane registry and verify has been adding it by hand since 1309. gc could not notice, since it refuses from anywhere but the primary, where those oids ride in the loaded graph anyway; a read-only census can run from a lane, so the root moved into the shared function and the answer stopped depending on who asks. Machine output is this verb product rather than a garnish, which is why the porcelain is one row per metric keyed by a leading mark instead of one wide row: ADR 0023 makes a column unaddable, and a census is the shape most likely to be asked for one more number. The anti-vacuity pin is the same fixture reading 1 and then 0 with nothing changed but a change naming the orphan, and the read-only pin fingerprints every file under .loot rather than trusting the op log, which would never have shown a pruned object. On this lane own store it reads 7910 objects at 146.7 MiB with 2330 of them unreachable at 36.9 MiB, which is the 1524 baseline (#1523)
f10ba1d9 · dbf3dbe6…diff - nearest-landmark naming lands, and the one real trap in this ticket is that distance is not well defined on a DAG: the number printed is the count of changes reachable from the selector and not from the landmark, which is what git rev-list counts for landmark..selector, and it is not a path length - pinned over a diamond where one input reads 5 as a count, 3 as the shortest path and 4 as the longest, because a straight-line fixture cannot tell the three apart and would have pinned nothing. the briefing had this backwards: git describe does not count the longest path, its number is the cardinality of the same set difference, so taking the count here agrees with git rather than departing from it. a shortest path would call you 2 changes past a landmark that fifty changes have landed on top of, and a count is the size of a set, so nothing in it depends on a traversal order or on a clock - which matters on a lane where authored_at is advisory and never an ordering input. the candidate set is deliberately not the one loot buoy resolves: buoy keeps only the maximal attested change per name, so running nearest over that answer would report no landmark for every change below the tip of a name that has moved, when an older v1.0 is exactly what a change under the newer one comes after - so attested applies the same three ADR 0025 filters across every non-reserved role and skips the maximality reduction, and that split is pinned by asserting both halves in one test. ties are refused rather than broken: two landmark NAMES at the minimum distance exit 3 and are named, the ground ADR 0025 already refuses an ambiguous buoy on, while one name on two tied changes is not ambiguous at all, since every tied change renders the identical string and there is nothing to pick between. three things were widened rather than copied - the ancestor walk now has one home in buoy::ancestors, reached by the maximality reduction, by the new distance and by Workspace::ancestors_of, where there were about to be three walks; the three attestation filters are one predicate; and --dirty asks the same reflected-tree question the converge chokepoint and bisect start already ask. --nearest is the first valued flag this verb has ever had, so the door hands it the token that follows and loot buoy --nearest --dirty would have resolved a selector spelled --dirty, silently dropping the marker: refused by name, the remedy config --get landed, with the door handing the flag over asserted as the control. no landmark anywhere in the ancestry falls back to the short version id and still exits 0, because an answer was produced rather than withheld. (#1509)
b7b0b031 · dbf3dbe6…diff - loot apply-patch lands as the inbound half of patch interchange, and the limit 1512 deliberately left open is CLOSED BY REFUSING: CRLF is refused on both sides, because the two halves are not the same kind of problem. A patch declaring lossy: crlf-normalized cannot reconstruct the bytes the change it names recorded - that is fidelity, read off the exporters own measurement rather than re-derived, since by the time the content is in the file it is LF. The half that actually bites is the LOCAL one: a hunks line space is LF, so writing an applied result back over a CRLF working file rewrites EVERY line ending in it, a whole-file change no hunk describes, that no report would show as more than the lines the patch touched, and that the next capture would sign. Reconstruction was rejected as a guess - the line space carries no endings at all, so an inserted line in a mixed file has nothing to decide from - and documentation as too weak for a silent whole-file rewrite. The cost is named rather than hidden: a CRLF repo cannot use patch interchange in either direction, and the remedy is a bundle, which carries content verbatim because it carries objects. Refusing is also what makes the round-trip pin true rather than approximately true. This is a separate verb from loot apply and not a mode of it, since a bundle carries objects, keys and signed change nodes while a patch carries none of those; dispatch is an exact-name lookup so nothing can collide there, and the place that DOES match loosely is verb_usage, which compares the token after loot rather than a prefix - pinned, because a prefix match would have dragged the whole apply-patch usage line into loot apply --help. Hunks apply strictly at the lines they declare, and what does not falls back to the ADR 0044 three-way against the change the patch names in its own parent row, which is the git am -3 shape and the reason that row is full width; loot-core gains one narrow door, converge::compose, because the theirs side of a patch is text at NO address while three_way_merge exists to pick between two addresses. A contended path is recorded and the run stops with the tree untouched, which makes this the sixth member of the 1506 resume family, and the census is what said so. Three of the ticket premises moved under it. The AC3 re-seal under the local .lootattributes is a SENTENCE and not a step, exactly as 1499 found for restore: the verb writes plaintext and then captures, so the tier is todays local policy by construction, and the export side already carries no tier for it to have inherited - the work was pinning it and saying it in the report. The AC4 no-partial-application rule forced the capture-first snapshot to run AFTER the plan rather than before, found by a fixture rather than reasoned out: a repo holding a path it cannot open has that paths plaintext on disk, capture_uncaptured_edits refuses to re-seal content it cannot read, and with the capture first the verbs own refusal was never reached at all. And a hunk body must be consumed by the counts in its @@ header and never by scanning for the closing separator, because a deleted line whose content is exactly a dash renders as two dashes, byte-identical to it. ADR 0082 freezes the header grammar now that a reader exists, which is why the 1786 fidelity rows went in before this ticket rather than after; the machine shape 1512 expected this ticket to name is deferred, with the reason written into ADR 0066 and PROSE_ONLY_CEILING, because it is one shape shared by both verbs and freezing the import side alone is the same half-contract 1512 declined on its own side (#1513)
d5fba81e · dbf3dbe6…diff - loot fetch lands as the first half of pull by call rather than by imitation: the negotiate-fetch-apply loop comes out of pull_via_declaring as Workspace::fetch_via, and pull now runs capture-first, that call, then the fold, so the two verbs cannot drift about what arriving means - the same extraction merge made of fold_onto. the custody routing comes out of pull_routed the same way, so a locked fetch takes the exact fork pull takes, relay unsigned and forge refused by name, and fetch becomes the second caller of the sealing door, which the custody gate now enumerates by name instead of counting to one. safe on a dirty tree is structural rather than guarded, because capture-first sits above the extracted half, and it is pinned by byte-comparing status porcelain across the call with a modified tracked file in the fixture - the live version id is what moves, and a capture-first mutation reddens it while the path list stays identical. no re-fetch is measured rather than asserted: the test relay now records the raw wants count per round, a later pull is required to ask for zero addresses, and the non-zero ask the fetch itself made is the positive control - an ingest-free mutation makes that number read 2. what integrating would involve is read off the head partition converge executes, through a shared converge_partition, so fast-forward means no merge node rather than one head. two premises of the ticket are wrong. a subsequent converge or pull completes the GRAPH integration but not the working tree when the repo was strictly behind, because converge_heads returns before materializing on a single head and the auto-surface of pull fires only on a non-empty apply, so the content lands in the graph and never on disk while the pull prints nothing new - the same un-materialized state a no-surface pull and a locked pull leave, and fetch now prints that and names loot rehome first. and --remote is the first valued flag of six verbs rather than of this one, so loot fetch --remote --json resolved a remote named --json; the refusal went into remote_sel, where push, pull, propose, grants and pull-grants all reach it (#1514)
577ae796 · dbf3dbe6…diff - loot clean lands, and the two nevers the ticket asks for are properties of how the candidate set is BUILT rather than filters over one that already exists: the protected set is the union over every change tree in the graph, consulted inside the keep predicate of the tree walk itself, so a recorded path never enters a list at all, and nothing under .loot can be reached because that same walk already skips it at name == DOT ahead of every rule, which is why this file adds no second check free to drift from the first. the walk grew a WalkFilter trait so that skip stayed one line with two implementations rather than a second walk beside it. AC6 is the one that eats work and it has its own fixture: a delta compares path-and-address pairs, so a file the graph records which currently holds an uncaptured edit reads as absent and gets deleted - which is why the set is keyed by PATH and drawn from ANY change rather than the current one. a second fixture pins the other half of that word: a path only the FIRST of two changes records, which a set read off the tip or off surface_target calls unrecorded. both were run red by named mutations before being left green, and deleting the DOT skip put twenty three .loot/objects entries in the candidate list, so the pin is not vacuous. the default is a listing and --force is the only thing that deletes; -n is accepted as the explicit spelling of that default rather than a fourth spelling of the preview idea 1809 is open about, and -n --force is refused because one of the two readings deletes files. -x and -X choose which side of .lootignore the run acts on, and the rules are read by the same parser a capture uses so first-match-wins and the absence of negation hold; -e is repeatable through a new FlagSpec::values that value now delegates to, is parsed by that same parser, holds under -x the way git keeps its own, refuses a leading bang because unlike an inherited ignore file the operator is authoring it right now, and refuses a value spelled like a flag because a valued flag swallowing the bare flag after it has been the defect five times this run. the verb is prose-only and the ceiling moved to 55 with its reason beside it: the shape worth freezing is shared with loot view --prune, which has none, and the column that matters is the per-path spared-reason neither remover computes yet (#1515)
c0b301ed · 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 - bisect grows log, replay and terms, and the replay refuses WHOLE at a gap rather than replaying up to it, because a partial session is INDISTINGUISHABLE FROM A WHOLE ONE IN THE ARTIFACT: status renders both identically, the search then runs from a range missing a bound it was TOLD about, and the only evidence is a warning that has scrolled away. all-or-nothing has no partial result to mistake. the pre-flight resolves every id against the graph before writing anything and names EVERY missing change with the line that asked for it, and on a shallow position it says so and names --deepen rather than pull. SESSION_VERSION stays 1 and that is the shape of the preservation criterion rather than a promise about it: the terms pair is a TRAILING codec section written only when non-default, so a default session is byte identical to what the previous release wrote and a pre-1532 session still decodes. the transcript is an INPUT and what is frozen is its GRAMMAR rather than its emitted text - what the parser accepts today it accepts forever with the same meaning, and later work may add directives rather than re-read existing lines. an unparseable line refuses the whole file and points at the line, which is the rule 1516 set for an unreadable ignore file, and EMPTY or all-comment is refused TOO, because zero directives and a session with no marks are DIFFERENT FACTS and a truncated download is exactly how the first arrives looking like the second. ids are full 64 hex only, no prefixes and no HEAD, because a transcript is read on a machine where neither means what it meant there. the vocabulary TRAVELS with the transcript: a terms line rides ahead of the marks and replay resolves mark words against a RUNNING table, so it round trips, and a transcript written under old and new whose terms line is missing is REFUSED rather than re-read as good and bad - the one reading that would silently FLIP A VERDICT. the rename ADDS a spelling rather than retiring one, so bisect good keeps working afterwards, and that is safe ONLY because a term may not shadow a subcommand name, which is what makes the inversion terms bad good unspellable. two of its own assertions were not AIMED until they were fixed: the start assertion was vacuous because the fixture made start and the transcript bad the same id, repaired by finalizing a sixth change between the log and the replay, and a doc claimed its consequence one assertion earlier than the one that fires. and the tests produced a finding worth keeping: writing the transcript INSIDE the repo makes it an uncaptured edit, so replay refuses over work the operator did not knowingly make - the guard was deliberately NOT widened, since a replay checks a midpoint out and must refuse over the same work bisect start does, so instead the refusal names the likely culprit and the smoke test ASSERTS that refusal rather than arranging to avoid it (#1532)
05f9263e · dbf3dbe6…diff - loot move lands as the sixth member of the REFIT FAMILY rather than as machinery of its own, and the whole re-anchor is ONE DELTA over tree entries: the shift, the new parent tree against the old parent, applied to every node of the moved line - so node N tree becomes old_parent then delta N, which is why DESCENDANTS NEED NO SEPARATE RULE and why the clash set is decidable from trees BEFORE anything is minted. the naming decision is the ticket own proposal taken rather than a third option invented on taste: loot move <selector> --onto <selector>, with no collision to find - loot has no mv, and a rename is something a capture RECORDS since v12 - and it is recorded in CONTEXT.md as a new Move entry that states the decision against the word rebase, which the glossary deliberately avoids. AC6 follows an EXISTING mechanism rather than a new one: split moves an entry whole and duplicate reuses entries entry-for-entry under ADR 0004, so move reuses that shape, and the structural proof is that there is NO put_sealed and NO ObjectStore::get on the move path at all - a re-seal would require acquiring a key the code never asks for. its pin carries a POSITIVE CONTROL, open_object failing before AND after, because without one an entry comparing equal proves nothing. the clash arm is the squash clash copied in shape - record conflicts, persist, op, mint nothing - and move joins STOPPING so it carries the abort, continue and force trio. two things are reported rather than papered over: the ticket parent line calls this the start of the jj rebase -r item but AC2 asks for the OPPOSITE of -r, descendants travelling WITH the change rather than re-parenting onto the old parent, and the AC as WRITTEN is what shipped. and the working change is itself a descendant, so it travels, re-recorded as a superseding version but deliberately UNSIGNED, because signing it would mint permanent history from work nobody named - which also means a clean move reports two descendants where the operator sees one, and that is pinned as true rather than hidden. eleven pins, each reddened by a named mutation with the count read rather than the word ok (#1517)
7ff4ef4d · dbf3dbe6…diff - the branch and reorder forms land as FLAGS ON MOVE rather than as verbs of their own, and the branch form is a SELECTOR SUBSTITUTION rather than a second planner: branch_root walks the first-parent line and stops at the first node the destination already descends from, the change just below it becomes the source, and the ordinary plan_move decides everything else - so the branch form CANNOT DRIFT from the subtree form refusals, clash set or sealed-carry rule, because there is one planner under both. reorder is DELTA COMPOSITION: the moved manifest becomes anchor then lower-over-upper, the overtaken one anchor then lower then upper, which equals its old tree exactly when the two touch disjoint paths - which is WHY descendants are re-parented with their manifests UNTOUCHED - and the intersection of the two deltas is the clash set, decided from trees before anything is minted. a DEFECT IN WHAT LANDED AN HOUR AGO is fixed here rather than filed: loot move <finalized> --onto @ was NOT refused, because @ on a different fork passes every #1517 guard - not self, not a descendant, not the current parent, not a merge - so it would have minted SIGNED HISTORY PARENTED ON THE UNSIGNED WORKING CHANGE, a version no push, bundle or fetch carries. the repair is stated as a rule over the parent-to-child EDGES a restructure creates, asked of the DRAFT POINTER and never the signature, because a keyless repo signs nothing - so both forms and any seventh Refit inherit the refusal instead of each enumerating its own cases. AC1 was already satisfied by #1517 and is CITED rather than re-pinned, verified against move_onto live-descendant closure rather than taken on trust. non-adjacent insertion is REFUSED BY NAME rather than half-built, because inserting at an arbitrary point re-parents the line it lands in front of, which is the -r shape #1836 holds. fourteen pins, each reddened by a named mutation with the count read - including two draft pins that go red together, which is the census proving the rule sits on the edge rather than on the verb. and two prose censuses caught the USAGE text itself, where a -b inside a jj rebase citation tokenized as an undeclared flag (#1518)
066564a1 · dbf3dbe6…diff - loot notes lands as a SECOND WRITER ON THE ATTESTATION LANE rather than as a record type of its own, so AC3 and AC6 arrive FREE and the wire does not move: a note is an attestation carrying a reserved role, the sibling of #1508 landmark record in the namespace the TAG field was shaped to admit, so every shipped reader parses the bytes, FORMAT_MAJOR does not move and NO RELAY OR FORGE REDEPLOY IS OWED. the visibility rule is the decision and it is DERIVED rather than chosen: the attestation lane carries no key and no grant list and rides the bundle beside the change it names, so a note audience is EVERY HOLDER OF THE CHANGE including a relay holding no content key - on a wholly-Internal change that audience can already open every byte, so no text ABOUT it can tell them anything new, and that implication FAILS THE INSTANT one path is Restricted or Embargoed. so the verb REFUSES on a change recording any path sealed narrower than Internal and names the paths, while Published PASSES because it is Internal plus world, a WIDER audience. inherit was rejected on its own terms - a Change HAS NO SINGLE VISIBILITY, that is the founding sentence of the glossary, so inheriting means a key granted to the INTERSECTION of holders which goes stale on every grant and maroon - and own-visibility was rejected because it is the only option that can OVER-disclose, making the safe answer an operator choice every time on an append-only already-delivered lane. an allow-leak hatch was declined because it would consent to nothing enforceable. the gate is the unconditional FIRST STATEMENT of the write path above a match exhaustive over the three writing ops, so a fourth does not COMPILE until it has an arm, and the limit is stated rather than implied: the check is at WRITE time, a path demoted afterwards leaves the note standing, and burn is the verb for that. seventeen mutations, each red with the count read, and both risky pins carry POSITIVE CONTROLS - the version-id pin mints a real version three lines below the assertion that notes did not, and the visibility pin has a peer who provably CANNOT open the sealed object reading the note in the clear beside it, each half the control for the other. a smoke run caught a defect this change had introduced rather than inherited: a multi-line note broke the attested-by line at column zero, so the cut is made AT THE DOOR, which covers a multi-line tag message that was latent and never exercised (#1519)
0ec3069f · dbf3dbe6…diff - range-diff pairs by the DURABLE CHANGE ID and by nothing else, which resolves a real tension between two of its own acceptance criteria rather than papering over it: AC1 asks for content similarity and AC6 forbids comparing content across a visibility boundary, and a similarity scorer is EXACTLY the construction AC6 rules out. git scores patches because a git commit has no durable identity - loot RECORDS what git infers, and the handle survives re-snapshot, amend and refit, which is precisely what a rework is. so the pairing is exact, O(n+m) instead of O(n times m) content deltas, and STRUCTURALLY INCAPABLE of comparing content: pair takes two slices of identifier pairs, no Workspace, no keyring, no clock, no tree, and its only production builder reads GraphView, which loot-core documents as excluding anything that touches stored bytes. the evidence that the property is held by the TYPES is that the pairing tests need no repo at all. the verb is the FIFTH RENDERING over the delta seam and builds no differ: each pair body is the seam spec, the seam delta and the seam renderer, byte for byte. what the rule cannot pair it SAYS SO ABOUT rather than guessing - no change id from a legacy or git-bridge ingest, copies, which mint a fresh handle by design, and divergent handles, paired in order with the surplus named. the skip count is argued rather than waved past: its three inputs are change ids that are cleartext on every node, recorded addresses the engine already compares without decrypting, and a fact about THIS READER keys, so the most it can say is that two versions differ in paths you cannot open, which any holder computes from the recorded addresses alone. fifteen mutations, all red with counts read, and AC6 positive control pairs an EMBARGOED change and a readable one in the SAME RUN by the SAME RULE, with only the rendering differing. two findings came from looking at real output rather than reasoning: an amend records the superseded version as a PARENT as well as a predecessor, so a different-bases warning fired on every amended pair until same_base learned it, and a superseded version CANNOT BE NAMED BY A SELECTOR at all, so the revset door is the only way to aim at the left-hand side of an amend - a thing this verb can do that diff cannot (#1533)
c4b0cd67 · 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 - the opt-in telemetry channel is built, and off means no call because a paired test says so rather than a sentence
loot#1658, building what ADR 0074 specified and loot#1626 shipped only the
guard for. loot telemetry on | off | status | show; an append-only accumulator
that writes one line per dispatched verb, the dispatch-table name, with burn and
maroon collapsed to other AT WRITE TIME so no local trace of an incident exists
either; a sender that runs in main after the verb has printed and decided its
exit code, at most once per 24 hours, behind a 3-second hard timeout, that can
never print, never change the code, and never delay a verb; a spool that IS the
outbox, so show reads the same entry the sender transmits; and a 16-byte id
minted on opt-in and deleted on opt-out, never machine-id.
The guarantee is the pairing. One local listener, the real binary, a sandboxed
config home: init, status, doctor and show with telemetry off make ZERO
connections; on, the same verbs make EXACTLY ONE, and the same verbs again in
the same period make no more; off again makes none. A lone assert-zero passes
when the listener is broken, the binary crashed early, or it pointed at the
wrong port - the exact shape of the logrotate -f check that could never fail.
The endpoint override is https only with the loopback LITERAL the sole
exception, enforced twice, and localhost is refused because what it resolves
to is the resolver business; the refusal happens BEFORE an id is minted, so a
broken override never leaves an identifier that can never be used.
Clause 5 of the field rule is a property of the wire bytes, not a promise:
a test parses every string out of the JSON and requires each to be a
compile-time constant, a member of a fixed array, a dispatch-table name, or
the hex id. The platform is a build-script constant rather than a runtime
assembly from std consts, which is what the rule forbids spelled from constants.
The verb emits machine output - show --json IS the report - so it joins the
emitting tier rather than the prose-only debt list the ceiling forbids growing.
Every count that moved was moved in the documents that state it, because the
censuses read them: 129 verb lines in three sites, 82 dispatched and 80 in the
table and 25 emitting in ADR 0066 with telemetry in the named list, all 82
verbs and the block in the README, the CLI page, and ADR 0074 eight-entry
allowlist - because the endpoint is now the binary ONE destination it contacts
rather than prints, and its ALLOWED row says under what condition.
The destination census flagged three lines that dial nothing: a printed hint
naming http://127.0.0.1 and the two scheme-prefix tests in check_endpoint. They
are respelled rather than exempted - the check splits on the separator and
compares the scheme WORD, same rule, no allowlist row - because a row is a
sentence someone has to be able to defend and these had nothing to defend.
The site half: POST /api/telemetry validates a report against the SAME closed
sets the binary declares, pinned cross-repo by reading the Rust source, and
refuses WHOLE on any field outside them. A valid report becomes source=cli
rows, one per verb invocation under /cli/<verb> plus two bucket rows, all
stamped with the arrival time so no ordering exists to reconstruct; the
telemetry_id rides the row and expires with the 30-day partitions. It sits
behind the same /api/ limit_req the beacon needed, which is why the ADR put it
there. doctor prints on/off, the EFFECTIVE endpoint, last send and the spool.
The notice gains the CLI tier, as ADR 0074 §11 requires: sends nothing unless
you run on, the id links reports and off deletes it, what is never in a report,
show prints it before you decide, and the erasure route - quote your id within
30 days, you hold the key. The test that asserted the page does NOT collect
telemetry is inverted, as it was for the visitor key.
1214 lib and 118 bin tests, 51 suites, 634 site tests, budget and font gates
green, rendered and read.
8a919b01 · dbf3dbe6…diff - review sweep 7 fix-up: loot-first now reads loot --version --json with a JSON parser, so the relay push is decided on a single JSON object whose top-level contract fits a u8 and whose top-level version is a string, in any key order and beside other fields, while a duplicated field, trailing text or a looser number refuses the push. the pusher= path now percent-encodes control characters as well as percent signs and whitespace, and workflow.md says so. land-change gains the relay=REFUSED recovery (catch the primary up, cargo build --release, check the contract loot --version --json prints against the major the landed commit declares, then loot push, and record the push output on the issue), says the refused block names both majors on a mismatch and a read failure otherwise, and stops counting the relay and branch states. afk-loop agrees that a FAILED or REFUSED land stands and owes a push, gains that push as a step, and counts the ticket done once the push is recorded on the issue. format.rs, ADR 0066 and ADR 0083 note that a land carrying a FORMAT_MAJOR bump normally comes out relay=REFUSED rather than FAILED, and the --version usage line, the print_out and dispatcher comments and a verdict test name follow. both new pins went red before the change (2 passed, 2 failed), the control-character pin went red with that arm removed (1 passed, 1 failed), and a serde_json Value reader went red on the duplicated field (1 passed, 1 failed). the workspace suite is green (#1979)
ce994e1d · dbf3dbe6…diff - 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 - 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 - what makes a line of loot --help a verb declaration is now one rule, in crates/loot-cli/src/usage.rs: the column a verb row is printed in, plus the two spaces before its description, instead of the word loot at the head of a trimmed line. the ticket named three readers of that string and the tree holds more - besides the arity census, the flags-only census and the site page pin it names, the help router, the migrate vis-spec reader, the #67 flag census, the delta-shape rung check and the revset census over the printed output all read it, most with a spelling of their own - and they key on the one function now, the site test included. the typescript cannot call a rust function in the site gate, so it reads DECLARATION and DESCRIPTION_GAP out of that module rather than respelling them, and README is the weld for the code that applies them: its verb block is pinned to the declared set from the rust side and cross-read from the site side, so a reader that drifts from the rule disagrees with README and goes red rather than measuring another population in silence. the second half is a refusal where the line is written: a USAGE line that opens with the word loot either declares a verb or is named in the failure, because such a line prints as a command to the reader of the help even when nothing mints a phantom from it. measured rather than inspected, both directions. with a wrapped description planted in USAGE and the old reading restored, five rust guards and three site assertions went red naming an invented verb changes, one of them telling the reader to give it a row on the docs page (118 passed and 5 failed in the bin, 3 failed and 4 passed on the site); with the rule in place the same line reddens the refusal alone, which prints it (122 passed and 1 failed, site 7 passed), and so does the same wrap landing in the declaration column itself. the other direction still bites: one real verb line deleted reddens seven rust guards and three site assertions (116 passed and 7 failed, site 3 failed and 4 passed). what is not claimed is a wrap that lands in that column and carries a description gap too - that is a verb row by every reading, the reader of the help included. loot view was the row whose shape ran long enough to leave a single space before its description, which made usage_signature answer with the whole line and read description words as positionals, so it gets its gap and that exception leaves the comment; ADR 0066 loses the half of its placement reason this closes. 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 (3975 passed over 122 binaries, 7 ignored) and the site gate is green end to end (662 passed and 61 skipped over 60 files, 62 surfaces) with no ceiling moved (#1883)
2f9db8e6 · dbf3dbe6…diff - a written docs page with no usable body publishes nothing and is refused at the gate, where under #2088 it published the planned template and stayed indexable: content/docs renders CONTENT[slug], so docs-content.test.ts builds its filed set from that resolved value instead of from Object.keys and the set the gate checks is the set the renderer uses, with a null or undefined value a missing body to both. routes/docs.$.tsx asks isPlanned once, for the body and for the noindex meta, so the two that drifted apart are read off one expression, and it asks for planned positively so a status added to DocStatus later renders no claim. the template was pinned nowhere and is pinned now over the built documents: every planned page must carry the sentence and its noindex, every written page neither. red first with the counts read each time - with the #2088 renderer back, the Object.keys keying back and concepts/docks filed as null, the three registry checks stayed green while the built document of a written page carried the sentence (2 failed and 6 passed, the second failure the eager-asset check losing the prose it reads); with the value keying restored the gate saw the null too (3 failed and 5 passed); with the renderer fixed and the null still filed the document went clean and the gate alone refused (2 failed and 6 passed). /privacy stops promising the deploy-time strip of any deployment at all, which was wider than the code - installEdgeLogging runs only from setupEdge, which setup-site.js calls when SETUP_EDGE is set - and instead names the deploy that reconfigures the web server and says the hourly job runs whether or not anything is deployed, the half that holds with no deploy; it is narrowed and not weakened, since the strip and the timer are built and on the box. the pin moves with the sentence, refusing the unqualified phrase over the whole source, comments included, and reading the narrowed claims off the component, because a sentence a reader never meets is not the published claim (1 failed and 37 passed with the wide claim restored, and again with the timer clause deleted). ADR 0066 says what its #1883 amendment amends: the expired reason was no longer above to expire, the same edit having replaced it, so the note says the paragraph lost a reason and gained one, names the surviving CARGO_PKG_VERSION half, and records the correction. tsconfig.test.json stops resting on a sentence that reads as a class and is one file - type-program.test.ts walks the import graph out of test, the program a test-only include would hold, and reddens when nothing it reaches under src carries the vite/client reference (1 failed and 1 passed with the directive deleted from components/private.tsx, the one carrier it reaches). not done here: the counted list in the #1883 closing record is a github comment, which this session does not write. the published wording moved, so this owes a site deploy. the workspace suite is green (3977 passed over 122 binaries, 7 ignored) and the site gate is green end to end (668 passed and 61 skipped over 61 files, 62 surfaces) with no ceiling moved (#2098)
dd749359 · 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 - buoy joins the dispatch table and bisect is left alone on the early path with the only reason that is still its own: #1764 made an output shape able to carry its own exit code, which was the whole of why buoy was dispatched ahead of COMMANDS, so the arm is gone and cmd_buoy returns a shape like every other verb. emit::Buoy reads ADR 0025 exit codes off the BuoyVerdict it already holds, one arm per row of that table, so the rendered answer and the code are one decision and the empty porcelain of the none row cannot come apart from its 2; buoy --nearest carries its code as a value on the message instead, having collapsed to one token with no structure to read an outcome back off; and buoy_write is deleted rather than moved, because the dispatcher outcome was already byte-for-byte the same #870 rule about a reader that left early. everything that walked the table and the buoy spec separately now walks the table alone — the telemetry note_dispatched call, the machine-output census, the verb census, the documented-flag census, the template-flag census, help_for and the completion list — and nothing is counted twice: the telemetry report is asserted to say buoy once, the table is asserted to declare no name twice, the offered completion names are asserted to hold each name once, and the census sum is now an equality with the table rather than the table plus one. going through the argv door means declaring an arity, so exactly one invocation moved: loot buoy reviewed junk refuses by name and exits 1 where an open claim used to drop the word and resolve for reviewed, which is #1562 reaching a verb that had been standing outside the gate enforcing it. every code is pinned through the spawned binary in tests/buoy_exit.rs, each outcome in every rendering it accepts. red under mutation, counts read each time: the verdict codes flattened to zero (buoy_exit 4 passed and 2 failed, emit_snapshot 12 passed and 1 failed), Message::coded discarding the code it is handed (loot-cli lib 1334 passed and 1 failed, buoy_exit 5 passed and 1 failed), the early telemetry call put back beside the one in the table (telemetry 2 passed and 1 failed, reporting buoy three times for two runs), buoy pushed onto the completion list beside its own row (loot-cli bin 125 passed and 1 failed, buoy_exit 5 passed and 1 failed), the row declared twice (loot-cli bin 121 passed and 5 failed), the row declining its arity again (loot-cli bin 123 passed and 3 failed, buoy_exit 5 passed and 1 failed), and the census sum left at the table plus one (loot-cli bin 125 passed and 1 failed). ADR 0025 records where each code now comes from and the one that moved, ADR 0076 closes the open work it had recorded and narrows the early-dispatch path to bisect, ADR 0066 moves the split inside its dispatched total, and CONTEXT.md stops naming buoy beside bisect. no migration, no wire or format byte moves and no host behaviour moves, so this owes no deploy. the workspace suite is green (4058 passed over 129 binaries, 8 ignored) (#1976)
fa68a7d3 · dbf3dbe6…diff - the filter in front of the import resolver becomes part of what it refuses, and a dated amendment gets back the figure it landed with: #2148 made the branches of imported_names refuse by default and left a filter in front of them that kept only a line starting use, and a default a filter feeds is a default over what the filter let through, so a module named on a continuation line and a pub use re-export reached no branch at all and were skipped in silence, which is the #1946 under-count surviving the ticket that closed it and then the ticket that closed that, a third time. use_items reads items and not lines, a statement being the run between one semicolon and the next and a use item being the statement the keyword stands in, so a visibility, an attribute and where the lines break decide nothing, and what would make that wrong is a statement spelling the keyword without being one, which costs a refusal naming the line rather than a reach passed over. the call walk stops deciding the same question a second way and reads the code with those items blanked by without_use_items, so an import can never also be read as a call and the use and mod line prefixes it skipped by are gone, the declaration it does look for being found by what it declares. the re-export and the wrapped item are planted as outcomes, one resolving and one refusing, beside the alias #2148 planted, and the blanking is planted with them. the ASCII identifier reading in temp_root_census is gone for census_text is_ident_char, which whole_word_matches is now bounded by too, so where a name begins has one home and the wider letter a narrow reading lets answer as a whole word is planted where that census can see it. ADR 0066 #2127 amendment is restored to 82 in the dispatch table, which is what was true on its date, and the cause is named rather than the number: the_verb_counts_stated_in_prose_are_the_ones_usage_holds held every occurrence of its phrases to the count the code holds today, records included, so the cheapest green was to rewrite the record and #1976 rewrote it; claims_only takes the amendment entries out before that census reads, an entry stating what was true on the date it carries. emit.rs stops saying buoy is dispatched ahead of the table, ADR 0088 stops walking a buoy flags constant that has no references left, three plurals about verbs dispatched ahead of the table go singular where one verb is left, the telemetry pin reads the count instead of a substring of it, since buoy 1 stands inside buoy 10, and the glossary stops spelling the secret-shaped set with two env positions where #1930 made it three. red under mutation, counts read each time: the line shape deciding again which items are read (loot-cli lib 6 passed and 2 failed), an item ending where its line does (7 passed and 1 failed), the shared boundary widened to admit every character (loot-cli lib 4 passed and 4 failed, temp_root_census 1 passed and 2 failed) and narrowed back to ASCII, which nothing caught before this (temp_root_census 2 passed and 1 failed), the ADR records read as claims again (loot-cli bin 0 passed and 1 failed at the control, and with the control stood down 0 passed and 1 failed naming 82 where the code says 83), the cut widened to take every line (0 passed and 1 failed, the phrase it guards no longer stated), and the count reader stopping at the first digit (telemetry 3 passed and 1 failed). item 6 is a correction to two landed commit records and is reported rather than made. no migration, no wire or format byte moves and no host behaviour moves, so this owes no deploy. the workspace suite is green (4062 passed over 129 binaries, 8 ignored) (#2156)
54167f6e · dbf3dbe6…diff - a refused keypair archive puts back the half it moved, and the cut that blanks amendment records stops blanking half the document: #2112 made the order safe and left the re-run broken, because the suffix is a tag the two halves share and the search wants both names free, so a refusal at the second rename orphaned the public half under rotated-ts and sent the next run to the next suffix, handing back a path nothing had written. archive_keypair now undoes its own first rename before it returns, so the run after it meets the repo the refused one met and archives a pair under a tag of its own, which is what a re-run at a later second needs, and where the put-back is itself refused the refusal names where the public half was left; the archived public path comes back as None where there was no id.pub to move, so what it hands back is where a file is and not a name it picked. the amendment cut now asks for a date, the date being the whole reason an entry is not read: ADR 0066 opens with an undated Amended inline header and the reader blanked from there to the end of that quote, 482 lines of 953, taking the index of the amendments and the claims around it with no guarded phrase lost and nothing to say so. its control is new and reads the other way, walking every blanked line up to the header it stands under and spelling that header itself rather than reading the constant, so a widening from either end arrives red naming the lines and the guarded phrases on them, where the old control was green over half a document and the per-row found check fires only when the cut takes the last copy of a phrase. ADR 0066 stops pointing the reader at the newest entry, which is blanked, and says instead that the figures in that block are the ones held against the code, and what makes a block an entry. use_items stops naming one cost for two callers: a false item is refused in the resolver only where it also spells the module name, and is blanked in the call walk, which is the #1946 direction in the reading written to close it. the judgement items: both sides unterminated over a line they share and an empty file in each direction are pinned through the binary, the empty-lines arm in the marker test is gone with its reason moved onto the expect that ends_clean makes true, a name bound to two types three lines apart in the apply path is split, and the walk guard in store_rename_census reads the workspace members instead of the crates directory it already walks, asserted both ways. red under mutation, counts read each time: the put-back removed (loot-identity 48 passed and 1 failed, the refusal leaving id.pub.rotated-7 orphaned), the public path returned unconditionally (48 passed and 1 failed), the date requirement removed (loot-cli bin 0 passed and 1 failed, naming line 15 and 103 lines), that same defect with the new control stood down (1 passed and 0 failed, which is what shipped), the cut never leaving the quote (0 passed and 1 failed, naming line 197 and every guarded phrase the rows carry), the member list read short (store_rename_census 3 passed and 1 failed), a member declared outside the walk (3 passed and 1 failed), the empty-side carve-out removed from ends_clean (patch_trailing_newline 3 passed and 1 failed on the expect), and the marker withheld from a kept line (3 passed and 1 failed). ADR 0016 records the put-back in a new entry rather than rewriting the one that was wrong. the deploy sentence #2112 landed is narrower than that change, and that correction is a record posted on the ticket rather than code here. no migration, no wire or format byte moves and no relay or forge behaviour moves, so this owes no deploy, though the client rotation path moves and rides the next release. the workspace suite is green (4086 passed over 130 binaries, 8 ignored) (#2169)
b17b2955 · dbf3dbe6…diff - the stale narrowing moves to the enum that defines the set, and the plaintext census stops asking its constants what the store holds: pinned.rs still said the opener refuses a directory git cannot name a revision for, which is the vocabulary of the round before #637 gave that door a loot arm, and #1895 had just pointed the corrected loot-perf-cli header at this paragraph as its argument, so the module header now names no arm and sends the reader to Names, the door doc says what the accepted set widens with, and the struct doc and the refusal beside it follow. the binary header stops calling the door the only constructor of the type a measurement is taken against, a claim about a set the module can grow with one function and nothing derived behind it, and states privacy instead, which the compiler holds: the fields are private to pinned, so a value cannot be made out there at all. the census clock window opened at the first redaction, which runs after the fixture has built every repo, so the seconds it redacts sat below the lower edge and only the slack held them in; it now opens at the span the fixture records itself writing in, frozen once so both worlds redact through one window, and the slack is stated as paying for skew between two clock reads rather than for build time. the same census read green over a fixture whose varied path never reached the store, every planted control still catching its oracle because an oracle is planted from the recorded constants; the premise is read back per build through the repo door now and compared on the bytes that come out. the keypair archive record stops making a later second part of why a re-run works, since ts is a whole second and the put-back frees the tag, so a re-run inside the refused second takes that tag back, which is what the test exercises now; the branch where the put-back is itself refused stays unexercised and that deviation is recorded at the code with what makes it unreachable from a fixture. adr 0066 stops answering how many today with the figures in one block, because the verb-line figure the census holds stands outside it, and says instead that what decides is surviving the cut. red under mutation, counts read each time: the fixture slowed by seventy seconds between the worlds under the old window (census 4 passed and 2 failed, the value rows naming one unix second against eight redaction bytes), the window start taken at the reading again (6 passed and 1 failed), the capture stopped from recording the varied path before the new reading existed (6 passed and 0 failed, every control green) and after it (6 passed and 1 failed, naming b.txt), the put-back removed (loot-identity 48 passed and 1 failed), the suffix search started past the refused tag (46 passed and 3 failed, the resume test naming rotated-7-2), and the amendment date requirement removed, re-measured on the landed tree (loot-cli bin 0 passed and 1 failed, naming line 15 and 111 lines where the landed record says 103, that document being 952 lines before this land and not 953). no migration, no wire or format byte moves and no host behaviour moves, so this owes no deploy. the workspace suite is green (4093 passed over 132 binaries, 8 ignored) (#2179)
19bc2ec4 · 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 - ADR 0099 records the move of loot from millerbyte.com to loot.build (map #2412): the site and one-liner at the apex with www redirecting, artifacts at dl.loot.build, the forge at forge.loot.build, security@loot.build through Cloudflare Email Routing with no catch-all, the relay not recreated, and the production Clerk instance on loot.build; and a hard cutover, the operator decision, in which the new names serve beside the old, a release carries them, every clone re-points its forge remote, and the old names are removed with no alias or redirect kept. it states what that breaks for anything made before it, read from the code: an opted-in old binary POSTs telemetry to the old name silently, so the old names must end NXDOMAIN rather than answered by the millerbyte.com wildcard, which would hand those reports to Vercel; old --help links; installers and manifests pinned to dl.millerbyte.com; the crates.io placeholders published with the old homepage, which only a new publish by the operator fixes; and it records the measured costs of the move (a 19.2 s first unlock on the new origin, no forge change because a signed request does not bind the host). ADRs 0037, 0041, 0045, 0049, 0061, 0064, 0066, 0067, 0074 and 0097, whose decisions name a loot host, each gain an amendment pointing here, and a banner where they had none; their bodies keep the names they were written with (#2414)
bf2242c5 · 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.