Changes touching this path
- catch up: GB1 loot ferry lands (PR #114)
6816f50d · dbf3dbe6… - (working change)
02306b53 · dbf3dbe6…diff - Ferry drive binding: close the uncaptured-WIP clobber hole; ritual runs the bridge (#115)
ferry_capture now snapshots unconditionally: disk edits that never saw a
`loot status` had no working change, so the adopt path's full-tree
materialize silently overwrote them. A capture snapshot identical to the
anchor is dropped from the graph again (new DagRepo::drop_working over
remove_head), so a quiet pass mints no redundant change and leaves no
stray head for reconcile or the next pass's anchor derivation.
tools/loot-day.ps1 now runs the daily ritual through the bridge (ADR
0028): capture day WIP when git sees it (-ForceSnapshot for git-ignored
loot paths like docs/pitch), sync the local-only private mirror at
.loot/git-mirror/mirror.git, one `loot ferry` pass, then the relay push
and drive-log entry as before.
Git-Author: Connor Miller <53197564+Connor-Miller@users.noreply.github.com>
481fbc26diff - Ferry: recognize pulled content, or the co-located mirror walks off main (#116)
In the co-located binding (the dogfood repo), git pull writes incoming
content to disk before ferry runs, so the pre-ingest capture snapshotted
it as a spurious working change, merged it with the identical ingested
line, projected both, and moved the mirror main ahead of the checkout -
breaking the next day's mirror-sync fast-forward push.
The pass now ingests first (graph-only), then captures against BOTH the
pinned anchor and the incoming target, forking explicitly from the
anchor (snapshot_from) so a pre-dock home capture cannot fold the fresh
ingested head in unclassified. Tree equality is by CONTENT
(DagRepo::same_tree_content): sealing mints a fresh key+nonce per write
(#98), so the capture and the ingest give the same bytes different
addresses.
New regression: pulled_content_is_not_recaptured (pure ingest, no merge,
mirror main stays at the native commit, single head after reload).
Git-Author: Connor Miller <53197564+Connor-Miller@users.noreply.github.com>
28d855d0diff - resolve conflict at crates\loot-cli\src\ferry.rs
f3ebcd7a · dbf3dbe6…diff - catch up: sign-resolutions fix lands (PR #118)
8c37c7c7 · dbf3dbe6…diff - loot-first tooling: ferry --with-wip review lane + loot-first.ps1 (#155) (#160)
The build half of map #148. `loot ferry --with-wip` projects the ambient
dock's unfinalized working change to a sealed-free review/<dock> branch:
provisional commits carry Loot-Change-Id + Loot-Provisional and NO
Loot-Signature (the missing trailer is the machine-checkable "not
finalized" marker) while staying SSHSIG-signed for integrity. Lanes are
keyed by the durable change_id in .loot/git-mirror/wip - deliberately
outside the mark map - and reaped lazily on every ferry pass once the
change id turns up signed (landed) or gone (abandoned). Rounds append
per revision (#150); ingest refuses provisional commits and mark
rebuilds skip them, so nothing throwaway can enter the round-trip spine.
tools/loot-first.ps1 is the orchestrator that owns every GitHub call
(loot core stays git-agnostic): `review` publishes the branch by
single-ref inline-URL push and opens the PR (pr-map ledger, #153 seam
2); `land -Pr` verifies approval, finalizes on the PR's dock (seam 3),
ferries, fast-forwards GitHub main and points the PR head at the landed
sha so GitHub marks it Merged by reachability, then pushes the relay;
`init-hook` installs the warn-only direct-commit guard (#151).
Shipped git-first, deliberately: this commit is the last one that has
to be - the tooling it lands is what flips the workflow.
Git-Author: Connor Miller <53197564+Connor-Miller@users.noreply.github.com>
533d94d5diff - Publication is a public-delta, never the readable tree (#155 leak fix) (#163)
Round 1 of the #155 live run pushed docs/pitch/ into a public PR diff:
the projection filtered on READABILITY (repo.get succeeding), and the
dev's own mirror identity can read restricted content — ADR 0028's
full-readable-tree contract is exactly the tree that must never be
published. Contained same-hour (PR #161 closed, review/main deleted;
residual dangling diff accepted + logged).
Fix: project_change and project_wip now build a **public-delta** tree —
the git first-parent tree plus this change's delta restricted to
Visibility::Public paths. Sealed content never publishes even when
readable; publication also becomes git-shaped for free (loot-only paths
like .scratch/ are untouched by the delta, killing the PR-diff noise).
Omitted paths surface as ferry notes. Tests now cover the exact leak
class: a path restricted TO the projecting identity stays out of both
the review lane and the landed main.
Git-Author: Connor Miller <53197564+Connor-Miller@users.noreply.github.com>
fc11eff1diff - Publication preserves git filemodes (#164 live catch) (#165)
The public-delta rebuild flattened the git parent tree to path->blob
and re-inserted everything as 100644, silently stripping the exec bit
from untouched scripts (surfaced as mode-only hunks on PR #164). The
flat map now carries (blob, filemode); loot still does not track modes
- the git side owns them, and an untouched path keeps its bit.
Git-Author: Connor Miller <53197564+Connor-Miller@users.noreply.github.com>
f5e092c6diff - R6: structural snapshot-before-mutate - the Snapshotted handle (ADR 0030, #182)
6f0c24b7 · dbf3dbe6…diff - R1: Workspace becomes the CLI's only face of the engine (#177)
06340e98 · dbf3dbe6…diff - R2: one home for reconcile orchestration - Workspace::reconcile_onto (#178)
b6c013fb · dbf3dbe6…diff - review fixes: retire with_repo, collapse forwarders, reconcile_* renames, fork-view naming (#177/#178/#182 + code-review)
b071df66 · dbf3dbe6…diff - (working change)
782a6799 · dbf3dbe6…diff - ADR 0033: ferry survives amends — predecessor-conditional threading, Loot-Predecessors trailer, amend-aware reap, reconcile supersession guard, land review-currency guard (#199)
f4129edb · dbf3dbe6…diff - Liveness: one loot-core home for live/superseded/divergent/parked + the head partition (#216); CONTEXT.md vocabulary; rides along: cargo-dist release config (dist-workspace.toml, release workflow, 0.1.0 manifests) from the install-prototype session (loot-site map, #206)
3630469b · dbf3dbe6…diff - loot-first in Rust: land policy behind a Forge seam (#218) — new loot-first bin crate (ledger/forge/policy/orchestrator), in-process Workspace reads via loot-cli lib split, typed pr-map owner + shared ferry::WipState; every policy decide-tested against a fake forge. Build+test only; shadow-run, real land, and ps1 deletion remain operator-gated.
58c28888 · dbf3dbe6…diff - The harbor: an on-demand lock serializes landing to git-main (#229, closes #195)
8adf34a2 · dbf3dbe6…diff - feat(loot-first): tag-push ferry verb — release tags reach GitHub-projected main (#256)
Add `loot-first tag <version> [-m msg]`: mint an annotated release tag on the
projected, sealed-free git-mirror `main` and push it to GitHub through the
Forge seam so cargo-dist's release.yml fires — no break-glass raw git.
- loot-cli ferry::tag_projected_main creates the tag object in the harbor
mirror at refs/heads/main (sealed paths are already omitted by projection,
so a release tag never widens the public boundary); refuses on an unbound
mirror or an existing tag.
- loot-first orchestrator::tag holds the harbor lock across project → main FF
push → tag push, matching land's serialization; execute_tag_push is split
out and tested against FakeForge (main first, then the tag; a diverged main
aborts before the tag is pushed).
Closes #256.
ba29acec · dbf3dbe6…diff - feat(loot-cli): ferry adopts a gc-pruned change's content when its object is gone (#263)
When a projected git commit names a loot change the store no longer has (pruned by
gc after a land the primary never adopted, #263), the ferry cannot reconstruct it
byte-identically: a version-id hashes the tree's store-local, randomly-addressed
object oids (sealed::seal mints a fresh nonce per put), which the projection does
not carry. So instead of refusing, fall through to the git-native path and adopt
the commit's content as a fresh change marked as represented by the commit. Because
it is marked, it is never re-projected, so git main stays exactly where it is (no
force-push); the dock advances onto the recovered content and future work builds on
it. This is the baseline-anchor recovery, the same move the bootstrap makes for
pre-bridge history, extended to a trailered commit whose change was lost.
Adds a test proving the adopted content round-trips with git main untouched.
Refs #263.
8aef9dd9 · dbf3dbe6…diff - gc roots the whole shared store and catch-up verbs ingest the harbor lineage, so a lane-landed change the primary never adopted fast-forwards instead of merging, duplicating, or being pruned (#265, #263 prevention)
4c03b44c · dbf3dbe6…diff - merges refuse to sign an un-described change too, closing the #174 residual (#275)
#174 stopped the *deliberate* finalize from signing a nameless change, but three
verbs still sealed the operator's own work in passing, to make a signed merge
parent: `dock merge` and the `adopt` catch-up (via `fold_line_in`), and `ferry`
over a git main that moved (via `reconcile_capture`). Only the *trigger* was
mechanical — the content was authored — so the placeholder still reached main.
Reproduced against the pre-fix binary: a ferry over unnamed disk work landed the
literal subject `(working change)` on git main in one silent pass.
Both paths now refuse an un-described merge parent, below their capture (edits
held, only the signature waits) and below a redundant-capture drop (a pass with
no real work to sign stays a no-op, never a nag). Two reasons the ADR gave for
NOT doing this were checked and dropped: a land never reaches these paths dirty
(it finalizes before it ferries), and #219's refuse-on-dirt rejection was about
being forced to capture, not about being asked for a name.
The merge *nodes* are untouched — they are machine-authored and already carry an
honest mechanical subject. That is the line: mechanical content may be named
mechanically, authored content may not.
- `refuse_if_undescribed` (one rule, two messages) + `drop_capture_if_redundant`
(the empty/duplicate drop, previously copied at two sites and missing at a
third — which is what would have made `dock merge` nag).
- Cost, documented: uncaptured dirt takes two passes, since naming *is*
capturing. `loot-first review` asks for a name only when it must merge.
ADR 0030 amendment (correcting the two wrong claims), CONTEXT, workflow, and
concurrent.md updated. Closes #275.
aced27c1 · dbf3dbe6…diff - reconcile_onto captures whenever it may materialize, not when git brought new commits (#280)
802cbf5f · dbf3dbe6…diff - review refs carry the position, not the dock (#281)
Every lane's home dock is main, so dock-named review refs made N
concurrent lanes share one review/main branch: the second lane's
ferry --with-wip force-pushed over the first's in-flight PR head,
and either position's reap pass could misjudge (and retire) the
other's live entry, since liveness reads the positional working
pointer a foreign position cannot see.
The review lane is now keyed by its owner position end to end:
- ferry projects review/<lane-id> from a lane, review/<dock> on the
primary; the wip and pr-map ledgers gain an owner column (- =
primary; pre-#281 short rows parse as primary-owned) and the
review line carries owner=.
- Reap is owner-scoped: only the owner judges liveness; a foreign
pass reaps exactly the entries whose owner lane is gone from the
registry, so an abandoned lane's review ref dies with it instead
of leaking.
- land derives the collapse branch from the pr-map lane's owner and
refuses to run from any other position: it finalizes the current
position's working change, and the dock guard cannot catch that
mismatch when every lane's dock is main.
Docs: ADR 0033 amendment, concurrent.md (review projection is now
genuinely parallel-safe), workflow.md, CONTEXT.md review-lane entry.
aa68437a · dbf3dbe6…diff - Judge the tip-duplicate drop by recorded manifests, so deletions count (#289)
A change whose only content was DELETING files was silently destroyed at
finalize: same_tree_content compared tree_at ancestry overlays, and the
union resurrects an ancestor's entry for every path the child deleted, so a
deletion-only capture read as content-identical to the tip and
drop_capture_if_redundant ate it - describe message and all. This very bug
ate its discoverer's cleanup change (#284/PR #285); the workaround that
landed was riding a one-line edit alongside the deletions.
same_tree_content now compares the two changes' recorded manifests (every
change carries its complete tree, deletion = absence): path set AND
content. An empty capture is redundant only when nothing is held to compare
against - over a non-empty tip it is a delete-everything change and signs
like any other work. Same judgment at the adopt catch-up's inline site, the
live status row, and working_preview's tip slice (a clean tree no longer
reads dirty forever once a deletion lands). Truly identical captures - the
bare new on a clean tree, the co-located checkout after a git pull - still
evaporate; an undescribed deletion now hits the #174 refusal instead of
silently no-op'ing. ADR 0030 amended.
06d1880e · dbf3dbe6…diff - Fix reconcile-merge resurrection of long-deleted files (#288): a change tree is a manifest, not an ancestry overlay
Live incident 2026-07-16: the #281 land's `ferry --with-wip` reconcile minted
merge d3ca4b8 carrying tools/loot-first.ps1 (deleted in the #218 tail) and
crates/loot-first/src/ledger.rs (moved in #232) — deleted months earlier on
every line involved; neither merge parent held them. Published to origin/main,
cleaned up by PR #286.
Root cause (loot-core, not the bridge, and not the merge base): every recorded
change carries a FULL path->address manifest — snapshot, ingest_change and
merge_tips all record whole trees, and deletion is absence from the child's
manifest — but ChangeGraph::tree_at/current_tree computed a tip's tree by
unioning every ANCESTOR's tree child-wins (delta semantics no production node
ever had). Every path ever deleted anywhere in the ancestry re-entered the
computed tree forever. merge_tips fed those polluted trees to the converge
classifier, which saw the same stale address on both sides (untouched) and
kept it; projection then faithfully published the merge manifest. The suspicion
that the merge base predated the deletions was wrong — common_ancestor_tree
always returned the ancestor's exact manifest; the resurrection needed no base
at all (both POLLUTED inputs re-raised the paths, base or no base). The live
history had exactly two ever-deleted files, and the merge resurrected exactly
those two — confirming the union mechanism.
Fix: tree_at returns the change's own manifest; current_tree unions the HEAD
manifests only (preserving the pre-dock multi-head view). Repro tests at all
three layers, each proven red under the old semantics: change_graph
(tree_at_honors_a_deletion_instead_of_unioning_the_ancestry), engine
(merge_tips_does_not_resurrect_a_path_deleted_before_the_fork), and the
incident-shaped ferry test
(reconcile_merge_does_not_resurrect_files_deleted_on_the_spine): spine deletes
a file, lines fork after it, a git-native commit lands concurrently, the
reconcile merges — the deleted path must appear in neither the merged loot
manifest nor the projected merge commit's git tree nor on disk.
Two tests that encoded the union semantics were corrected to record full
manifests (an empty/partial tree in a seeded change means delete-all, which is
what those tests accidentally said). ADR 0028 gains an amendment. Out of scope,
noted there: the classifier still has no deletion-vs-base rule, so a path
freshly deleted on ONE side since the fork is still re-adopted from the other
side of a reconcile merge — follow-up ticket to come.
77f95feb · dbf3dbe6…diff - Restore the #289 regression tests the #288 reconcile clobbered; re-delete the resurrected pair
The #288 land (tree_at/current_tree = recorded manifests) crossed this
lane mid-flight; its reconcile-merge took the landed engine.rs and
ferry.rs wholesale, dropping the #289 additions there. Restored: the
engine-level same_tree_content deletion pin, the end-to-end
deletion-projection ferry test, and the frozen-primary fixture line in
the gc-pruned adopt test (an unmaterialized dir now reads as a
delete-everything edit - #289 deletions are real work).
Also re-deletes tools/loot-first.ps1 and crates/loot-first/src/ledger.rs
a third time: this lane's earlier reconcile merges were minted by a
pre-#288 binary whose overlay merge manifests resurrected them, and the
final reconcile re-added them right after the #288 land's 7bcdf8e
re-deletion. The lane binary now carries both fixes, so this deletion
can sign (#289) and the merge line stays a manifest (#288).
81d04c47 · dbf3dbe6…diff - resolve conflict at crates\loot-cli\src\ferry.rs
dff407b4 · dbf3dbe6…diff - Re-delete the two files pre-#288 sibling reconciles resurrected; carry the #288 fix line to main
The #288 root fix (change pyzurxul, projected commit 9763144) is already on
this line: ChangeGraph::tree_at/current_tree treated node trees as ancestry
deltas while every recorded change carries a full manifest, so every path ever
deleted re-entered every computed tree and merge_tips resurrected long-deleted
files into reconcile merges (live incident d3ca4b8, cleaned by PR #286).
tree_at now returns the change's own manifest; current_tree unions the head
manifests only. Repro tests proven red at three layers (change_graph, engine
merge_tips, and the incident-shaped ferry reconcile test); ADR 0028 amended.
This change is the tail of the same land: while this lane reviewed, sibling
lanes running pre-fix binaries reconciled and the bug fired AGAIN — merge
07d2e19 re-raised tools/loot-first.ps1 and crates/loot-first/src/ledger.rs
into the shared mirror main line, and this lane's (fixed, deletion-safe)
reconcile could only adopt what the incoming manifests carried. Deleting them
here makes the landed tip clean, the third time these two files have been
deleted (69fd3cd/4f0a34b, f0525bf, here) and the last: with the union overlay
gone their deletion can no longer be forgotten. Also folds in the #287
(pre-land gate tree) and #289 (deletion-aware tip-duplicate drop) lands via
conflict resolutions on ferry.rs/workspace.rs/engine.rs — both sides kept,
full suite green (551 tests).
0e0fdbb6 · dbf3dbe6…diff - primary catch-up: checkout at the #291 squash (site/ scaffold) folded for the drift reconcile (#297)
e6f7079a · dbf3dbe6…diff - Refuse the review catch-up fold; never strand a described WIP (#292) (#302)
`loot-first review` (and `loot ferry --with-wip`) run a catch-up ferry
before projecting the WIP. When git `main` moved *under the lane* (a
concurrent land) while a live, described working change sat on the tree,
`reconcile_onto` captured and FINALIZED that WIP as a merge parent, folded
it into a "ferry: reconcile git main" change, and minted a fresh empty
working change. Review then projected the empty change and reported
"nothing to review" — the work signed-but-stranded: unreviewable,
unlandable (land finalizes a *working* change), and with no op-log entry
to undo. #257 had to land via break-glass git PR.
Fix: thread `preserve_wip` into `reconcile_onto`/`reconcile_capture`,
set only by the review projection (`with_wip`). Under it, a catch-up that
would fold real local work refuses (REFUSE_REVIEW_STALE_ANCHOR) BEFORE
finalizing, leaving the WIP a live, unfinalized working change (only the
signature withheld — the capture persisted). The no-op paths (main where
we left it) still project un-described WIP untouched, and a plain
ferry/land (with_wip=false) keeps the existing fold.
Defect 2: the review's in-process ferry never passed through cmd_ferry's
op record, so an honest catch-up (a clean fast-forward onto landed main)
left no op to undo. The orchestrator now records a "ferry" op after the
review pass, mirroring cmd_ferry.
Tests: a new reconcile_onto regression proves the described-WIP fold is
refused and the WIP survives; the #288 resurrection test moves to the
plain-ferry reconcile path (review no longer folds); the #275 review test
now asserts the #292 refusal instead of the old name-then-fold. Full
`cargo test --workspace` green.
Git-Author: Connor Miller <53197564+Connor-Miller@users.noreply.github.com>
3e83f40ediff - Catch up to git main 809ddfe: #260 smoke default, #271 arm64 build, loot diff (#1), ADR 0034 dock retirement (#253)
587684dd · dbf3dbe6…diff - ferry ingest: compose the full parent tree or refuse; roll back an aborted pass's ingest (#307)
cc115440 · dbf3dbe6…diff - Workspace accepts its clock; lane flag-gate goes pure so cmd_* tests never touch a real .loot (#322)
20ac82e1 · dbf3dbe6…diff - ADR 0039 build: pure-projection review + carry-at-land (#362)
Review mode (`ferry --with-wip` / `loot-first review`) is now a pure
projection: no ingest, no dock reconcile, no mirror-main advance, no
spine rewrite - it mints the provisional commit from the lane's own
anchor marks and pushes only review/<position>. A lane behind git main
reviews normally; REFUSE_REVIEW_STALE_ANCHOR is deleted with the fold
it guarded (#292/#302), and the #349 review-mode trigger is
structurally gone.
Reconcile lives only at the signing verbs, and its diverged-line shape
is now the carry (DagRepo::carry_line): a self-authored suffix replays
onto landed main as superseding versions - same change id, same
subject, single parent, stale original kept as predecessor - so landed
history stays exactly one commit per change with no ferry:
1412f811 · dbf3dbe6…diff - Pass-2 candidate 5: unify the key=value config codec into loot-cli::kv
ferry::parse_kv and workspace::parse_config_text were byte-for-byte identical
(trim, skip #/blank, split_once('=') → BTreeMap), and three call sites
hand-rolled the matching `{k} = {v}\n` encode (ferry::write_kv, Config::save,
GlobalConfig::save). One `kv::{parse, encode}` module now owns the dialect;
ferry (git_config / git_identity_map spine files), the per-repo .loot/config,
and the global config all call it. Behaviour-preserving; the module carries its
own round-trip/comment-skip/ordering tests (4). loot-cli lib suite green (227,
+4 kv).
This is the non-blocked core of pass-1's candidate 6 — pure text, no WipState →
loot-cli-ledger cross-crate coupling.
9b171a5c · dbf3dbe6…diff - Build the seal-WIP guard (#418): refuse a bare sync verb that seals live described WIP
Graduated from #356's "Prevent + hint" resolution (map #354). After ADR 0039
(#362) made review a pure projection, the only way to strand a described
working change as a PR-less signed line is a *deliberate* bare sync verb.
Guard that path at its source.
- New typed refusal RepoError::SealWip { subject, verb } in loot-core, a sibling
of MisSeal/Demotion, on the ADR 0030/0038 guard+override pattern. Overridable
with --seal-wip on both verbs.
- ferry seal path: thread seal_wip through ferry::run -> reconcile_onto; the
Merge-with-wip arm (the ONLY place a bare ferry seals — land pre-finalizes,
adopt folds through fold_line_in) refuses unless overridden. reconcile_onto
now returns the sealed subject so the caller reports the seal from the seam
that decided it, not a post-state heuristic.
- adopt seal path: thread seal_wip through adopt_harbor; refuse before
fold_line_in when a described line diverged. The clean fast-forward and
redundant-capture drop return earlier, so a no-op catch-up never trips.
- Fires ONLY on a described line the sync would actually fold: an un-described
one stays the #275 refusal; a break-glass ferry / no-op sync (no live
described WIP) is untouched.
- On override the verb prints the follow-up-round recovery recipe
(SEAL_WIP_RECOVERY, one shared seam); the review "nothing to review" and land
"not in the pr-map" paths print it too when sealed_unlanded_anchor() detects a
signed line ahead of mirror main with no PR.
- tag's ferry passes seal_wip=false — it is not a finalizer, so it refuses
rather than silently seal live WIP while cutting a release.
- docs/agents/concurrent.md §"One seal path remains" rewritten to describe the
guard; CONTEXT.md gains the "Seal-WIP guard" vocabulary entry.
Tests: guard at both seams (refuse without / seal with --seal-wip), un-described
stays #275, no-wip catch-up untripped, full ferry::run break-glass + no-op pass
untripped, sealed_unlanded_anchor signal. Full suite green (loot-core 337,
loot-cli 243, loot-first 69); the describe_contention CPU-load flake passes
in isolation.
42409371 · dbf3dbe6…diff - CLI: thread typed engine errors through Workspace so slugs travel (#430 follow-up)
#430 laid the CliError plumbing but the taxonomy stayed inert on live verb
paths: workspace.rs methods returned Result<_, String>, stringifying RepoError
before the verb's `?`, so every engine variant collapsed to code "error" at the
--json boundary. Convert that boundary: impl Workspace / Snapshotted / lanes
methods return Result<_, CliError>; engine (self.repo.*) sites use
.map_err(CliError::from) so code() is preserved; the MisSeal/SealWip guards
propagate the typed RepoError instead of .to_string(). Non-RepoError sources
(io, format!, bare strings) stay code "error", messages byte-for-byte intact.
Workspace::open/init stay Result<_, String> (open_repo() stamps the CLI-level
no_repo slug), keeping the loot-first ripple to two boundary conversions. New
emit_snapshot test proves a real RepoError::UnsupportedFormat reaches --json
stderr as {"code":"unsupported_format"} on `loot apply <garbage>`, with the
non-json `loot: <message>` line unchanged.
Also: share loot_core::verdict::json_string (drop error.rs's byte-identical
twin) and collapse the emit_snapshot run_with_code/run_stderr_with_code helpers
into one run_streams. Known residual: the ferry.rs bridge path still flattens
engine errors to "error" (deliberate scope boundary — git2 errors ride its
String contract). cargo test --workspace green.
627897cb · dbf3dbe6…diff - Rename the Public visibility tier to Internal (ADR 0041 §2, #480)
6969626d · dbf3dbe6…diff - Lift the wip review-lane ledger out of ferry.rs into ledger.rs
WipState/WipEntry — the `wip` ledger of in-flight review-lane projections —
lived in ferry.rs (the git-bridge file) though they are a typed on-disk-format
owner with nothing to do with git mechanics, read cross-crate by the loot-first
orchestrator. Move them into ledger.rs, joining PrMap as the two typed owners of
the `.loot/git-mirror/` ledgers (both sharing the review_handle rule). The old
"it lives in ferry because ferry writes it" rationale never held — PrMap's writer
(the orchestrator) doesn't own it either; ledger.rs does, because loot lanes reads
it and the workspace dependency points this way.
Not a pure relocation: ferry reached straight into `WipState.entries` (a ~50-line
reap loop, an upsert, and a lookup), so the move designs a real interface. The
entries vector is now private, driven through:
- record(entry) — the upsert (replaces ferry's retain-then-push)
- retain(pred) — for ferry's reap loop, which stays in ferry because its
liveness predicate reads positional Workspace/git/report state
- entry_for(change, dock, owner) — the lookup, parallel to PrMap::lane_for
- reviewed_version — kept (loot-first's read), now delegating to entry_for
- parse / encode — the format, encode now pub (PrMap's caller-writes pattern)
WipEntry is a pub-field data carrier like the sibling PrLane. The wip unit tests
move with the type into ledger.rs's test module. loot-first imports WipState
from ledger now; ferry keeps only the projection that writes it.
ferry.rs: -140 lines. Full workspace suite green.
0579b5f4 · dbf3dbe6…diff - Diagnose a premature finalize, and stop a stray ferry silencing the hint (#512)
b16d77f2 · dbf3dbe6…diff - ferry: floor a projected commit date past its git parents (#626)
940772ac · dbf3dbe6…diff - gitignore .scratch/ so both ignore files agree (#627)
22f7d0c8 · dbf3dbe6…diff - GitMirror: the bridge owns its own layout (#665)
b0461c4c · dbf3dbe6…diff - edit guard agrees with status; premature-finalize remedies tell the truth (#682, #683)
c78e9a39 · dbf3dbe6…diff - a view step refuses over work no change records, and a primary finalize says so on stderr (#436)
a3248ede · dbf3dbe6…diff - a ferry pass refuses to project over an open conflict, so a catch-up after a resolve can no longer land the side nobody chose (#653)
a9b5fd64 · dbf3dbe6…diff - a change records when it was authored, so a projected commit stops reading '6 years ago' on GitHub (ADR 0043)
loot changes carried no timestamp, so the git bridge fabricated one:
BASE_EPOCH + generation, one second per ancestor depth from a 2020 epoch.
All 526 commits sat inside seven minutes of September 2020. Earlier repairs
(#626's floor, the missing-generation refusal) fixed ORDERING and never
touched the absolute date, which is why this kept coming back.
ADR 0028 inherited the no-timestamp constraint rather than choosing it. The
real reason is upstream: a version id is blake3 over authored content, and a
clock inside that hash gives two peers different ids for identical content,
destroying the dedup and convergence of ADR 0001/0004.
So authored_at rides the label seam ADR 0029 already cut for change_id --
covered by the finalize signature (no relay can restamp it) but never folded
into the version id. The wasm golden vectors prove the separation held:
FROZEN_VERSION_ID, FROZEN_OBJ_ADDR and FROZEN_SIGN are byte-identical, and
only the version marker and one presence byte moved.
Advisory, and never an ordering input: a self-reported clock is a claim, not
evidence (ADR 0025). in_order/ids_topo, buoy and path_touch.ordinal are all
untouched, the forge indexes nothing on it, and the projection floors it past
every git parent so ancestry holds whoever's clock is wrong.
Format v11: additive for readers (a v<=10 change decodes as None and an absent
timestamp adds nothing to the signed message, so every existing signature
still verifies), breaking for writers, so loot-cli and loot-forge go to 0.4.0
in lockstep. Forge migration 0004 stores it as bigint, not timestamptz --
signed data must round-trip bit-exact or pullers reject the change.
Only new work gets real dates. Every commit on main predates v11, main is
push-fast-forward-only, and backfilling would fabricate the very claim this
replaces.
Perf-Baseline: reset the change body grew one presence byte, plus eight where a timestamp is present, so bundle_bytes/store_bytes/wire_bytes step once at the v11 boundary
eaa56d99 · dbf3dbe6…diff - a resolution records which side it answered, so catching up after resolving no longer re-raises the path (#744); ADR 0039 says what the one-commit promise actually guarantees (#653)
b837289e · dbf3dbe6…diff - a resolution records which side it answered, so catching up after resolving no longer re-raises the path (#744); ADR 0039 says what the one-commit promise actually guarantees (#653) (conflict resolution: crates\loot-cli\src\ferry.rs)
5d191fde · dbf3dbe6…diff - a publication survives a merge, an ingest and a resolve, so a catch-up stops silently reverting one (#783)
The reported symptom was consent fatigue: every capture in the repo refused with
"refusing to publish README.md", including changes touching nothing published.
The cause was the opposite of the symptom -- README was not published any more,
and the gate was correctly reporting a new @world transition on a publication
that had been reverted.
The ticket's hypothesis (an adopt/ferry fold re-sealing README from git-side
content) was not the mechanism. Reading the real graph, #750's own publication
change never published README at all, while it did publish both licenses. The
difference is that the licenses were NEW paths, so the snapshot seal minted them
@world, while README already existed and went through the merge -- where converge
picked between two addresses holding identical plaintext under identical
Visibility and its tie-break took the unpublished one.
@world is address-neutral, outside every signature and ignored by open,
key-travel and compression. That inertness is what made publication cheap; it is
also why nothing carries the marker structurally. It survives only where the code
writing a tree entry chooses to carry it, and four writers did not: converge, the
git bridge's ingest, resolve (so a bounce quietly unpublished the conflicted
path) and absorb's span rebuild. The snapshot seal learned this at #481 and
nothing generalized it, so Repo::put_sealed now is the generalization.
converge gains an is_published question on KeyOracle and compares publish-status
as part of a path's content, which makes the existing 3-way answer publish flips
correctly in both directions: a publication since the fork is an edit and wins
over a stale untouched side, and a deliberate unpublish is equally an edit and
still lands. On identical bytes with no base to judge from, the published side
wins -- widening was consented to once, and reverting it on no evidence is the
only outcome that loses something silently.
The other three carry, and carry ONLY. A re-seal never originates a publication:
ingest takes its policy from a git commit, which reaches loot without passing a
consent seam, so a break-glass commit adding a `published` rule seals plain and
leaves the transition for the capture gate to ask about in the usual place.
Sealing more tightly than the rule says is always safe; more loosely is what this
subsystem exists to prevent.
Ask 2 -- should an unpublish need consent? No. A prompt would make consent MORE
frequent, which is the failure the ticket is actually about: a gate that fires
routinely teaches operators and agents to pass publication flags reflexively. The
fix is fewer ways to unpublish, not another prompt, so dropping the rule is now
the only one. For the same reason the gate still asks the anchor OBJECT's marker
and not the anchor's .lootattributes: policy and marker disagree both when a
marker was lost and when a rule has just begun publishing a path, nothing there
can tell them apart, and trusting policy would turn the standing rule into a way
to publish unasked. A future marker-losing path would make the gate nag again,
and that is the failure worth having.
Ask 4 -- the forge's published index cannot disagree with main: this repo pushes
to relay.millerbyte.com and has never pushed to the forge, so the index holds no
README row. Had the push happened inside the window, it would have received the
unpublished object.
ADR 0041 gains a 2026-08-04 amendment; CONTEXT.md's Published entry records the
carry/originate split.
ec50222e · dbf3dbe6…diff - a lane carries its own hold, and custody crosses back whole at finalize (#811)
941cafb9 · dbf3dbe6…diff - the mirror's tree may carry paths loot has no record of, and ADR 0028 never recorded the public-delta (#645)
c612e0e3 · dbf3dbe6…diff - a ferry refusal reaches a machine consumer as itself, not as a generic error (#867)
Every seam inside `ferry::run` returned `Result<_, String>`, so a pass — which
is mostly other people's failures — flattened all of them before the CLI could
emit them. A typed `RepoError` the engine had already coded, and the reconcile
executor's `Plan::Refuse`, both reached a `--json` consumer as the generic
`error` code. The prose was fine; the machine channel was the loss.
The nine phase signatures now carry `CliError` end to end, which restores the
engine's own slugs for free: engine calls propagate with a bare `?` instead of
`.map_err(|e| e.to_string())`, which is precisely what was flattening them.
Exactly three slugs join the frozen ADR 0023 contract (amendment recorded):
`undescribed_parent` and `foreign_paths` from a new `reconcile::Refusal::code`,
built alongside the unchanged wording by BOTH executors — `apply_plan` and
#866's adopt catch-up — so two verbs sharing one table cannot become two
taxonomies; and `git_mirror` where ferry lifts git2, deliberately coarse
because the bit a consumer acts on is retry-the-environment versus obey.
`git_mirror.rs` stays stringly inside: inventing a loot taxonomy for git2's
errors would be fiction.
`rollback_note` keeps the abort's code and grows only its message — a rollback
is context about that failure, not a different one.
Every refusal's wording is byte-identical; the human channel does not move.
Ferry's own handful of refusals stay honestly generic, and so do the tier's
remaining flatten sites, which graduate one at a time when a consumer needs
them.
The one loot-first line is the seam back out: its orchestrator keeps a String
channel, so `From<CliError> for String` makes that a `?` rather than a
hand-written `.to_string()` at every call.
Found while pinning the codes: the uncaptured-WIP ferry test refuses with
`foreign_paths`, not `undescribed_parent` as it reads. Both wordings offer
`describe -m`, which is why prose was never a taxonomy.
4fc73479 · dbf3dbe6…diff - the change graph gets one read view, and the two forwarding bands over it retire (#868)
826c3339 · dbf3dbe6…diff - a no-op ingest still mints its change: the spine needs a mark per mirrored sha (#842)
316935ec · dbf3dbe6…diff - a release tag verifies and mints: the ferry leaves the tag path, so cutting one materializes nothing and names what it leaves behind (#933)
4979e4fc · dbf3dbe6…diff - the second machine's bridge stops being frozen: ferry learns landed main from the checkout, and adopt says when its answer is stale (#1000)
93bce6cc · dbf3dbe6…diff - a ticket claims one lane: a repeat claim refuses instead of forking a silent sibling, and one search answers for both the id and the directory (#1002)
dda6e6a6 · dbf3dbe6…diff - an empty holder list means unknown, not nobody: a pulled Restricted path stops reading as a permanent local edit, and one engine rule answers for every content comparison (#1005)
65b6946c · dbf3dbe6…diff - ferry stops calling an unpulled change gc'd: where a relay could still produce it, the ingest refuses and names loot pull (#1007)
e896ee3b · dbf3dbe6…diff - a land gates the tree it publishes: the merged-tree gate runs inside the ferry pass, so a lane behind landed main queues and converges instead of being refused (#1013)
4d9d4c0f · dbf3dbe6…diff - a tag stops firing a workflow that can only fail: the GitHub release pipeline retires, and every live pointer to it says where publishing went (#1025)
f9a18c18 · dbf3dbe6…diff - a relay's deposit stops standing in for the forge's: the push-time dedupe moves off the Manifest onto a per-remote deposit ledger, so relay-first Restricted custody reaches the forge inbox the browser reads (#1052)
499739af · dbf3dbe6…diff - every durable .loot artifact declares its format discipline in one inventory, and the fixed-width set files refuse malformed bytes instead of reading as empty (#1135)
58072a13 · dbf3dbe6…diff - a seal to a holder list that excludes its author refuses and names what nobody could ever open, --allow-lockout is the deliberate way through, and ADR 0038 records why the gate refuses rather than filing a key for the author (#1249)
bbb858b0 · dbf3dbe6…diff - the consent slices become one Allowances struct a swap cannot compile against, the lockout gate calls the engine predicate it used to mirror, and the site gate steps destructure so a third step stops compiling (#1272)
ac6e2096 · dbf3dbe6…diff - the landed-content hazard names loot edit, the second route to the same signature, because reopening a finalized change carries its handle and the loot new that closes the amend signs under it, and the block three hints rebuilt by hand at an indent a comment asked for is now assembled once in landed.rs (#1404)
909e8b37 · dbf3dbe6…diff - deleting a named read on Workspace stops being free, because the 377 places that reached past it into the engine and the store now go through those faces instead — repo() no longer exists, store() is module-private, and the three extracted children share their fixtures by name rather than through the parent's private test module (#1411)
c454db04 · dbf3dbe6…diff - the store stops threading a dock nothing can ever produce through 43 signatures, because a store instance is already the position ADR 0034 retired named docks in favor of, and the sixteen path getters no caller outside loot-core reaches stop being public while the one main reservation that guards a git ref name stays (#1413)
23ab3abd · dbf3dbe6…diff - content whose embargo has already lifted stops reading as sealed, because promoting due keys out of escrow is now the construction of the reader every content read hangs off instead of a doc-comment obligation six callers hoisted by hand, and deleting four of those hoists left all 2438 tests green while loot surface told an author to request a grant from herself (#1464)
e59e46b3 · dbf3dbe6…diff - a merge stops settling a tip whose tree the caller still owes on the next line, because the two sites that open-coded finalize plus materialize_tree now settle through SettleTree::Materialize, and converge's hand-written persist() is the settle tail its own write was spelled out beside, with the one of the three that no test in the crate ever entered now pinned (#1468)
15e33421 · dbf3dbe6…diff - a publication marker that could not be read stops being carried as an unpublished one, because the three re-seals that carry @world across a rewrite now ask a read that refuses instead of a predicate whose swallow was argued for the publish guard alone, and the refusal is a typed variant rather than prose so a corrupt seal reaches the run-loot-verify remedy a vanished one already did, while ADR 0012 table rows name their consumer and state the grep the census is the output of (#1582)
6ee38984 · dbf3dbe6…diff - a corrected census stops being replaced by a fresh one, because ADR 0012's table drops the completeness claim that had already missed a consumer on the day it landed and states its rule instead, the swallowing publication read that consumer used is deleted along with it so outside loot-core there is no route to one left to enumerate, and HUNT-PERF's list of which perf halves re-open their fixture per repetition, which refuted itself inside a single sentence, is deleted in favour of each half's own doc rather than pinned as prose, which is #1587's half of the same defect (#1593)
4995641a · dbf3dbe6…diff - loot ferry parses both policy files once per commit instead of once per changed path, and the instrument had to be built before the fix because PolicyLoads sat on Attributes::load while both ferry doors call parse, so the counter read a structural zero over this whole path and would have read zero after the fix too - a 0-valued counter being indistinguishable from one watching code that does not run. The tally moves to Attributes::parse and Ignore::parse_recorded rather than being routed through a counted door, because the fix itself removes those door calls: a counter on them would read zero afterwards, which is the same blindness relocated. That changes what the counter MEANS, from policy file reads to policy re-derivations, so all three existing pins were re-read rather than adjusted until green - status moves 2/3 to 3/4 with the +1 being its single Ignore::load now counted, and the point is that the GROWTH half passed untouched (narrow equals wide) while only the constant moved, so the test is re-pinned and not re-decided; the tier-exclusion test is still green because the in-process tier links loot-core and never loot-cli, so ADR 0073's exclusion needs no re-taking; and lane_new_sweep's note that policy_loads is one per read_tree_at is repointed to two, since read_tree_at calls both loads. The instrument was proved non-vacuous against the UNFIXED code first, at 18, 66 and 258 parses over four commits of two, eight and thirty-two changed paths, which is exactly 2 plus 2 times commits times width; after, it is 2 plus 2 times commits, so 258 falls to 10 at width 32 and stays 10 as width grows. The most useful thing learned here is a red proof that inverts an assumption: blinding the instrument by putting the tally back on load makes the counter read a constant 1 everywhere, so the constancy pin passes AND the parses-greater-than-zero guard passes, and only the GROWTH assertion catches it - a positive-value pin does not protect against a blinded instrument, which is what ADR 0072's controls bullet credited it with, and that bullet is corrected rather than left standing. This ticket's own wall clock does not reproduce and is corrected rather than repeated: the removed re-parse is 4.63 microseconds per path in release against this repo's real policy files, not 22.0, so a full-history ferry is about 1.9 seconds rather than 8,819 milliseconds - 22.0 is close to the debug reading of 31.53, so the hunt appears to have measured a debug build, and a figure taken under a different build is not a smaller version of the same number. ignored_under is deleted rather than kept as a pure forward once the parse is hoisted, and ADR 0028 is amended because it argued its delete-arm decision partly on an Ignore::parse per deleted path, a cost that no longer exists - the decision stands on the attribution argument, which was load-bearing anyway. seal_under takes a parsed Attributes and narrows pub to pub(crate) since Attributes is crate-private, and its doc said it keeps the bridge from re-parsing the policy twice per path, which was true about the wrong unit: it halved a cost that should never have been per-path. One honest regression is recorded rather than hidden: a deletions-only commit now costs 2 parses where it cost 0, because the hoist is unconditional (#1704)
1822132f · dbf3dbe6…diff - the second review sweep's fix-up, and the item that mattered most was a correction to a correction: #1704's amendment to ADR 0072 refuted a sentence the bullet does not contain. The bullet credits an EXACT-VALUE pin, and the amendment answered that a positive-value pin is not the protection it credits - true about a greater-than-zero guard, untrue about the thing being amended, which is doc scope-drift inside the ADR whose subject is that class. Blinding the instrument settles it by running rather than by reading: with the tally back on Attributes::load, status_derives_its_policy_a_fixed_number_of_times fails on its VALUE half, and the ticket's own arithmetic was wrong in the same direction as the amendment - the blind arm reads 2 and not 1, because status still reaches Attributes::load once, while 1 is the ferry fixture's reading. Both numbers are now named in the text, the greater-than-zero finding is kept because it is real and newly demonstrated, and the head count stays at two of the four with the reason narrowed: what is still missing is a refusing FIXTURE, not a refusing pin, since two pins were each measured refusing a mis-placed tally. ferry.rs claimed the pin asserts the 2 plus term, which was wrong twice over - nothing asserted a constant at all, and the 2 being described is the per-commit coefficient rather than the pass-level term - so rather than correct the sentence the constant is now ASSERTED, as parses equals PER_PASS plus PER_COMMIT times ingested over both sweeps, fitted to measurement rather than predicted. Its red proof is the regression the old comment claimed was already pinned: a third unconditional parse per commit moves the readings to 14/14/14 and 8/14/26, where the constancy half passes AND the growth half passes and only the new assertion fires. offers_any_object's note that no address is ever reached twice is repointed because it is false in exactly the case the memo two screens up is built for - a store where every named address answers Err never returns early and re-probes each repeated address per change - and that arm is left uncovered with its cost stated as unmeasured rather than justified. The 291-against-283 disagreement turns out not to be one: four fresh runs read 296.3, 289.6, 293.2 and 283.6 microseconds, so both recorded figures sit inside the instrument's own run-to-run spread of about 4.5 percent, which is wider than the 8 microseconds they differed by. Recording a POINT was the defect, so it is now a dated spread of about 290 with its four raw readings written in one place, and measure.rs carries no number at all but points at PUSH_SHAPE - killing the duplicate rather than syncing it. PUSH_SHAPE_DEPTH's pre-#1700 pair gains the marker the three sibling sites already carried (#1717)
d895d727 · dbf3dbe6…diff - the ferry test named for reconcile_merge is renamed to the_adr_0039_carry_does_not_resurrect_files_deleted_on_the_spine, because a probe on the first line of each seam says it enters reconcile_carry then carry_line then CarryOutcome::Carried and never enters reconcile_merge or merge_tips - the name and the inline comment about the ordinary reconcile-merge path have been stale since ADR 0039 moved a described self-authored line onto the carry. the sibling sweep was done the same way rather than by reading: 16 named seams probed across ferry.rs and reconcile.rs plus converge, converge_heads, merge_tips, carry_line, seal_wip_gate, finalize_merge, fast_forward_to and refuse_open_conflicts, with each of the 53 ferry tests run alone so the probe output is attributed to one test, and the headline test is the ONLY misnamed one. a_keyless_reconcile_merge_writes_the_merged_tree_to_disk really does enter reconcile_merge and is the only ferry test that does; every ingest-named test enters ingest_commit, every adopt-named one enters reconcile_adopt, and tag, rollback_note, project_wip and reconcile_capture all enter what their names promise. the two that reading made me suspect - amend_of_landed_threads_onto_predecessor_as_a_fast_forward and bootstrap_adopts_prebridge_history_without_ingesting - are naming the git-side shape and the baseline note, not fast_forward_to and reconcile_adopt, so they stay. the ticket got one thing wrong and it is the larger finding: it asked me to confirm the pin still fails for its original reason, and it does not. restoring the pre-9763144 union-the-ancestry tree_at leaves this test and all 53 in the module GREEN while the two loot-core pins go red on the same mutation, which is the positive control saying the mutation was live. the cause is structural: carry_line replays the full manifest each node records against a tree_at base, so an absent path reads as deleted-since-the-fork and the deletion wins even when both trees are polluted, where merge_tips took tree_at on BOTH sides and read the stale entry as untouched. so this is a live pin on the composition the carry performs and #288 is guarded a layer down, not here - recorded in the comment rather than fixed, since a ferry-layer #288 pin is a separate ticket. no behaviour changed: ferry.rs is the only file touched and only comments and one fn name in it (#1479)
093b8a20 · dbf3dbe6…diff - the condition that could not be false is gone, and so is the pair that made it possible: merged stops being a FIELD set by hand at three construction sites and becomes a DERIVED method over the one bit that decides it, so the two cannot disagree - unrepresentable rather than guarded. eighteen reads follow it, and eight of those were conjunctions that are now TAUTOLOGIES, reduced to one term rather than left standing to read as two conditions. the test was rebuilt by moving a DIFFERENT AXIS, because a second catch-up can never reach the block at all: the fixture now puts a real second commit on the checkout main, so the DOCK is still behind and the catch-up genuinely runs, writes the tree and reaches the block, while the CHECKOUT is level and the block declines - and deleting the predicate reddens two tests. the DISCRIMINATION is the part worth reading: under that same mutation the OLD test stays GREEN, which is precisely the defect this ticket describes, demonstrated rather than asserted. the printed recipe stops handing the operator a command that ERRORS. the tree delta now returns what was WRITTEN and what was REMOVED apart, and a removal gets a tree read for its proof, because there is no file to hash, and a checkout for its repair - placed BEFORE the merge, since restoring the file is what lets the merge run at all. the plumbing needed a pin of its own, and that is proved rather than assumed: folding removals back into the written set reddens the workspace pin while the rendering pin stays GREEN. the off-main arm gains its PROOF and withholds only its REMEDY, and says that it withholds it and why, because the byte comparison does not turn on where HEAD is while the repair does - with that limit stated in the same list the module other limits live in, rather than left for a reader to discover. the section this run added about careful sentences gets its own grammar fixed, a stray fragment from an earlier draft removed and a clause given its missing object - and NO count added, with a number that was being carried forward replaced by the property, consecutive sweeps each turning one up. the only-shorthand claim is narrowed at all three sites to the width of the check that backs it, which is a claim about ONE declaration and nothing wider. and the tenth copy of the short-hash helper becomes one, with the unguarded universal above it replaced by what it actually is and an explicit note of what it does not cover (#1911)
53ede622 · dbf3dbe6…diff - a const inserted BETWEEN a doc block and its function had silently stolen two of them, and the consts move above the blocks so each documents its own subject again: diff_spec gets back the section recording that diff is a POINT VERB AT EACH ENDPOINT - the deliverable of the very ticket whose edit displaced it - and archive gets back its omit-versus-refuse argument, its two inherited limits and its pathspec section. each const gains a doc about ITSELF, saying what the string holds and who interpolates it, which is the shape the two siblings that were already right had. a THIRD site of the same shape was found by sweeping for it rather than by assuming the two were all, and it is from August rather than from this run. the nothing-catches-this claim is MEASURED rather than asserted: re-inserting the defect gives zero build warnings, zero rustdoc diagnostics at the site and a fully green suite, because intra-doc links still resolve. and the guard question is answered honestly as REVIEW rather than a gate - the mechanical heuristic yields seventeen candidates workspace-wide, all seventeen were READ, and exactly ONE was real, so a gate built on it would ship sixteen hand-maintained exemptions of noise, which is the roster class this run has been burned by five sweeps running. the sha-shortening item finishes the EXTRACTION rather than fixing the sentence, and on evidence found before deciding: one arm of the same enum already called the helper while another sliced flat, two arms of one message shape disagreeing about the same value. the doc stops saying every, states the property, names what each of the two sweeps covered, names the non-scope, and says OUTRIGHT that nothing enforces the scope - a hand-written slice still compiles and review is all there is - with its surviving headline claim, a prefix and never a panic, pinned for the first time. and the ticket OWN PREMISES were refuted twice, which is the part worth carrying forward. the diff exemption was DEAD rather than load-bearing: deleting it leaves the test green, and an instrumented run prints diff answering thirty-six bytes, because loot diff leaves the second endpoint at the pending state and an empty WORKING CHANGE is not an empty DELTA - so it is deleted rather than justified, with the reason recorded at the site. and the claim that all twenty-five selector-taking verbs read the selector HEAD is false, because edit refuses it through a THIRD resolver - measured against the built binary, and now pinned with a control rather than left as prose. the unpublished figure behind a 215-millisecond claim is rounded to the table rather than published unmeasured, and the removals proof gives BOTH its outcomes a meaning, pinned by a mutation whose sibling stays GREEN (#1917)
0ad19cad · dbf3dbe6…diff - the policy counter is renamed PolicyParses and policy_parses because it has counted parses since #1704, and the policy.rs block and the ADR 0073 row now point at the variant doc instead of restating why; no stored perf record carried the old key, because the gate does not record this counter. PUSH_SHAPE_DEPTH, OFFER_GUARD_DEPTH and MISSING_DEPTH are literals rather than aliases because their reasons have diverged, and each is still 128. ride_entry takes one RideState instead of three maps, while the one_pass_bundle oracle keeps its own transcribed walk and memo so the byte-identity test still compares two walks, and still went red when the key arm was disabled. the gated counters read 743, 200 and 24 before and after (#1718)
bbd04ef4 · dbf3dbe6…diff - the ferry layer gets its own #288 pin, and the decision is recorded: a_keyless_reconcile_merge_does_not_resurrect_files_deleted_on_the_spine replays the deleted-on-the-spine incident in a keyless repo, so the reconcile takes the merge arm through merge_tips rather than the carry, and asserts the reconcile minted a two-parent merge so the pin cannot slide onto the carry unnoticed. restoring the pre-9763144 ancestry-union tree_at turns it red (53 passed, 1 failed in the ferry module) while the carry pin stays green and both loot-core pins go red as the positive control. the carry pin stays green there because its replay feeds the node manifest against a polluted base and carried tree, and the converge DeleteWins arm drops the stale path: with that arm also made to re-adopt theirs the carry pin goes red too (52 passed, 2 failed), and with the arm alone broken the ferry module stays green (54 passed) while four loot-core tests go red, among them delete_vs_untouched_ours_deleted_theirs_kept_deletion_wins, the carry replay input shape. carry_line doc records that reason at the seam, ADR 0028 gains a #1790 amendment, and the carry and keyless test headers drop the claims the new test made stale. no behaviour changed, only a test, comments and the ADR, and the workspace suite is green (#1790)
588d7a2b · dbf3dbe6…diff - review sweep 17 fix-up: the separator-rule census checks what its name claims now and reads every caller of a pattern door, two more callers ask policy::unix_separators instead of answering the separator by hand, and the one class of path whose protection depends on that answer is pinned rather than lucky. clean::Sieve::keep spelled the replacement itself and compared the key against the recorded store keys and against an Ignore built from operator-typed -e values, the #1859 class in a third file at a verb that DELETES what it declines to protect; ferry::ingest_commit did the same for the .lootignore and .lootattributes it reads out of a git commit, a fourth home the ticket did not name and one that is inert because git hands over /-separated paths. both ask the normalizer now, whose body is the expression each replaced, so no path moves - measured through the spawned binary before and after on a scratch repo over ten invocations, -x, -X, the default, three backslash-spelled -e values with their slash twins, and a --force run with the tree it left, whose transcripts are byte-identical. what was NOT pinned is the half that matters: the protected set holds /-separated store keys while the walk hands keep a native rel, so only a recorded path with a separator in it can catch a key nobody normalized, and the clean fixture held none - sub/recorded-deep.txt is in it now, and let key = rel.to_string() reddens four cases over that file, the removal matrix among them, having taken a recorded path as a candidate (10 passed, 4 failed), where the whole file was green under that same mutation before. the census is renamed and derived on both halves rather than trusted: the files are every .rs under this crate src, production halves only, and the doors are read off policy.rs, a function there taking rel or path as a &str, so a new door joins by existing; an offence is the pair per function rather than the spelling alone, because a backslash pass on the way to a display line or a map key is the other axis and a spelling-only census would have to name its exceptions. mutations: the hand-spelling put back reddens it naming src/clean.rs fn keep, and put back in ferry naming src/ferry.rs fn ingest_commit (0 passed, 1 failed each); the door reader blinded reddens its anchors; the needle blinded reddens the fixture (left empty, right the one site); and the floor call dropped reddens source_walk own consumer census, whose derived count line this land repastes with the new consumer. #1859 AC4 is measured rather than argued, as #2028 did: from another module of the crate, Patterns and Pattern literals are E0451 on their fields and reaching for .list is E0616, throwaway probes, and the doc says privacy is what holds the pair and that a literal inside the module is not closed by it. a_recorded_inert_pattern_moves_no_paths_tier runs its claim over the paths its own rule list mints, with the inert line and without it, over the tier and over publish-ness, and the separator honoured reddens it naming a derived probe (27 passed, 4 failed); ADR 0038 and CONTEXT.md stop citing a three-path sample for no path changes tier and rest that on the parse. ADR 0038 and view.rs stop rating the .loot/view gap oppositely: closed on the tier axis, and the cost is materialization, which view.rs holds and the ADR now points at. conformance.rs stops resting its counted pair on a comment and points at ClassReads, where every other MetadataStore method is unreachable and the trait defaults none, so a third read in AccessClass::of panics there instead of undercounting here - measured, it does (0 passed, 1 failed). propose::visible_to and Viewer are pub(crate), with the field privacy named as what holds the pair and the in-module literal named as what it does not. seven string literals in cli_smoke.rs that carried a raw newline where the file spells the escape are fixed, five more than the ticket named, from a scan of the whole file. no migration, and no forge behaviour moves - a visibility narrowing, doc prose and one test fixture - so this owes no deploy. the live loot-forge suite is green against a throwaway 17.0 cluster with the CI env block and LOOT_FORGE_TEST_REQUIRE_DATABASE (476 passed over nine binaries, no skip note) and the workspace suite is green (3859 passed over 120 binaries, 7 ignored) (#2033)
4720219d · dbf3dbe6…diff - the git bridge projection asks the seal for the tier it publishes on, so a restricted seal entered as internal stops reaching a pushed branch: #2196's premise was re-verified on the tree first and it holds, ferry's public_delta_tree chose from the change tree entry's visibility, a field sealed::seal's address does not cover and compute_change_id_raw discards before the finalize signature is taken over it, and the bound was never the mirror being local but the open, which refuses an unauthorized or embargoed seal, so a live embargo never rode a lying entry out while a restricted seal this position holds the key for did, as did an embargo whose reveal had passed. the cost was measured rather than assumed: workspace::readable_object_and_class, the door #1581 built, hands back the seal's tier and sealed::open's bytes from the one DagRepo::object the projection was already paying for, and the same projection moves object_gets by 3 before and after with every other counter identical, so the store-read objection that defers #2187's lane selection does not arise at this site; it also retires ferry's hand-rolled copy of the sealed-to-you rule. the entry is still read and can now only withhold, which is what keeps a pass over every unrepresented change from opening a store object per sealed path, and on a disagreement the path is dropped into the omitted-sealed-paths report rather than refusing, because project walks every change no mark stands for and a refusal there would stop every land and every review push over an entry the refusing operator did not write. both directions are pinned on one hand-recorded tree, under a control asserting each lying seal opens from this position so an omission cannot pass for the open's doing. red under mutation, counts read each time: the seal's tier ignored again (55 passed and 2 failed), the match tightened so nothing projects (37 passed and 20 failed), the entry's cheap withholding half dropped (56 passed and 1 failed, object_gets reading 4 where 3 belongs), the disagreement dropped off the report (55 passed and 2 failed) and the fixture's seal made unopenable (56 passed and 1 failed, the vacuity control firing), each restored to 57 passed and 0 failed. the census row moves with the decision and stops asserting which sites have moved, and ADR 0012 takes a ninth amendment recording the direction, the measurement and the git-side ingest demotion guard this does not reach. no migration, no wire or format byte moves and no host behaviour moves, but what a ferry projects moves on the client, so this rides the next release and owes no deploy. the workspace suite is green (4123 passed over 132 binaries, 8 ignored) (#2196)
4c0c6c9e · dbf3dbe6…diff - the git-side ingest asks the seal for the tier it guards on and for the one it reuses under, so an entry overclaiming Internal can no longer buy a silent demotion nor ride into the next tree as this position own recording: #2203 asked for every premise to be re-verified on the tree first and the body named one reader where there are two decisions a few lines apart over one field, the demotion refusal taking demotes(&old_entry.1, &vis) and the Act::Reuse decision comparing old_entry.1 with the derived tier and then carrying that entry oid AND its tier forward, beside a third use that decides nothing, parent_tree going over whole to ingest_change as the base every untouched path is carried on. the two decisions get two answers because they are two questions and proximity is not an argument, that being what produced the bound #2187 had to refute: the guard is about the tier a path is recorded at, a path in disagreement is recorded two ways, so both are weighed and the narrower stands, the seal supplying the half that was missing while the entry keeps the withholding half it has at public_delta_tree, because asking the seal alone would have deleted a refusal rather than repaired one — an entry narrower than its seal is exactly what withholds that path from the projection, so ingesting over it widens the recording that was doing the withholding. the reuse gets the object alone, its question being whether a fresh put_sealed would produce what is already on disk, which the entry is not a recording of, and a disagreement there needs no refusal because withholding a reuse only means doing the work; it refuses where the projection drops, because dropping a path here drops a break-glass commit edit out of the very change that stands for that commit. the comparison is == and not the deposit lanes discriminant (#2187), the holder list here being exactly who could open the object a reuse declines to re-seal. the cost is measured at this site rather than inherited from #2196 already-open projection object: the arm pays 2 object_gets for a path the parent tree records and 0 for one it does not, read as a difference over a real ingest_commit, and the entry spelling cost the same pair. red under mutation, counts read each time: the seal term dropped from the guard (62 passed and 1 failed), the entry term dropped (62 passed and 1 failed), the reuse comparison reading the entry (61 passed and 2 failed), Act::Reuse carrying the entry tier (62 passed and 1 failed), the tier taken by a second store read (62 passed and 1 failed, object_gets reading 4 where 3 belongs), the overclaiming fixture seal made unopenable (62 passed and 1 failed, the vacuity control firing) and the underclaiming fixture entry made honest (62 passed and 1 failed, the refusal correctly stopping), each restored to 63 passed and 0 failed. the site is a census row for the first time, the field having been reached through .1 which that census header names as outside its needle, so its silence was never evidence, and ADR 0012 takes a thirteenth amendment recording the two answers, the measurement and the carry this does not repair. no migration, no wire or format byte moves and no host behaviour moves, but which git commits ingest moves on the client, so this rides the next release and owes no deploy. the workspace suite is green (4137 passed over 132 binaries, 8 ignored) (#2203)
231f38e6 · dbf3dbe6…diff - the sync ingest door weighs the seal this store holds rather than the one that arrived beside the key, so a tag-0 bundle spelling a weaker tier at an address this position already owns can no longer file a live embargo's key into the Keyring: #2212 asked for item 1 to be demonstrated before it was repaired and the reproduction read exactly as the ticket claimed, bob holding oid sealed Embargoed 9_000 and a Sync frame carrying a byte-identical object that spells vis Internal beside the real key leaving bob's Keyring holding that key at a clock of 0, which is the state #2205's own new pin forbids reached by changing the frame tag, no plaintext escaping because sealed::open's header gate still refuses. the same read carries grant_ids and that half was demonstrated too, a copy spelling the ANYONE marker over a held Restricted seal getting a key past the entitlement filter #864 built, so every question this door puts to a seal now goes to the copy that will stand at the address. it is asked before the put while the arriving object is still in hand, because first-write-wins makes the held copy the standing one only where the store holds the address at all, so the cost is zero store reads on a fresh address and one on a dedup, which is the read the key verification already owed and now answers the seal's questions with its own; the already-held guard consults both lanes, so the lane the first door chose is the one that stands. the prose is the harder half and the lesson is sharper than do not list members: the sentence that failed was in the correct derived shape, the set of them is Keyring::insert's callers which the compiler enumerates, with a hand-maintained count welded on in the same breath, and the count is the half that was wrong, so every replacement names what decides membership and stops there, at ADR 0007's amendment, escrow.rs, CONTEXT.md, custody.rs twice, ADR 0012's tenth and twelfth amendments, negotiation.rs and the grant-door pin, and escrow.rs's headline stops claiming that no route moves a key between lanes when flush is one and a grant is a new filing at the recipient rather than a carry. secondary items: the stale pin citation and the now-false claim around it, spawn.rs's three false statements about the orphaned child, the unproducible-seal fallback recorded as releasing nothing only at the instant it files, expires_at declined as a term of the staging max with the reason at the code, the demotion refusal naming which of the two recordings fired, the census group sentence that named its members, ADR 0012's push qualifier at the tip with no want, the ingest cost fixture given a publishes-nothing control, workflow.md's three refusals derived from CargoTestFailure and the PRE_LAND constants, a usize subtraction restated as a sum so the sentence beside it can print, and orchestrator.rs's tombstoned pin names declined with the reason. red under mutation, counts read each time: the vis term reverted to the arriving copy (659 passed and 1 failed), the grant_ids term reverted (659 passed and 1 failed), the already-held guard narrowed to the one lane it writes (659 passed and 1 failed), the seal question asked through a second store read (659 passed and 1 failed, object_gets reading 8 where 0 belongs), the lying sync copy made a different object (659 passed and 1 failed, the vacuity control firing), the lying grant ids made to agree (659 passed and 1 failed, the second vacuity control firing), the publishes-nothing control inverted (1353 passed and 1 failed), the refusal made to say both either way (1352 passed and 2 failed) and the ingest cost relation moved by one (1353 passed and 1 failed), each restored to 660 and 1354 passed with 0 failed. no migration, no wire or format byte moves and no host behaviour moves, but which lane a sync-borne key is filed into moves on the client, so this rides the next release and owes no deploy. the workspace suite is green (4140 passed over 132 binaries, 8 ignored) (#2212)
c643aadc · dbf3dbe6…diff - the arc's one self-contradiction is gone and its newest list shape is answered wherever it stands: #2214 asked for every premise to be re-verified on the tree first and items 1-5 all held, and two sites the ticket did not cite held with them plus one list that was already stale — ADR 0007's own #2205 header carried both the hand-maintained variant count and a second copy of the false carry claim, and the impl doc over the store door named the ingest paths where the chokepoint property would have named itself, missing the tag-1 grant door and put_published. the self-contradiction is ADR 0012's twelfth amendment, which #2212's commit message lists as corrected and which it edited one clause later, still saying a crossing between positions carries a key into the lane it is already in while ADR 0007 and escrow.rs both say a grant is a new filing at the recipient that reads the seal and answers for itself; the clause is deleted with the false reason named rather than quietly dropped, because the surviving conclusion has to be seen standing on what an older binary already wrote and on the absence of any route that takes it back out. the sharper lesson is item 4's and it is sharper than do not list members: the sentence that failed was already in the correct derived shape, and a count welded on beside it in the same breath was the half that went stale, so every replacement says what decides membership and stops there — store's call sites are the ingest paths and the compiler enumerates them, file_granted_key's call sites are the Frame variants that carry a grant, and the headline over the grant-door pin stops counting the doors it drives. secondary items: the store door's promise that a garbage key is rejected rather than filed is narrowed to the held-address arm with the fresh arm's literal true named as vacuous where a reader meets it, the carry definition stops being true by construction and says what a carry does to the lane so its own falsifier lands on that axis, the three co-travelling seal facts become a Weighed struct whose third field is named for what both arms make it, embargo_reveal_at delegates its Visibility half to embargo_instant so Embargoed is destructured for an instant in one place and the zero belongs to whoever asks for a number, the demotion refusal spells every bool pair and returns the name from the match so no arm asserts a pair it cannot be reached with, embargo instant becomes the glossary's reveal_at, the 127-character ADR line is rewrapped, and the one-address sync fixture preamble collapses into sync_of_one. red under mutation, counts read each time: the entry arm of the demotion refusal made to say the seal (2 passed and 1 failed), embargo_reveal_at's collapse moved off zero (655 passed and 5 failed) and the fresh arm's unrefuted made false (523 passed and 137 failed), each restored to 3 and 660 passed with 0 failed. no migration, no wire or format byte moves, no host behaviour moves and nothing an operator or a client can observe moves, so this rides the next release and owes no deploy. the workspace suite is green (4140 passed over 132 binaries, 8 ignored) (#2214)
ea968e98 · dbf3dbe6…diff - adopt and ferry stop re-reading the whole shared graph once per absent tip, and the correctness question came before the perf one: ingest_shared_lineage is the one seam that re-reads that file mid-process (#265) because another session may have written it, so nothing may be memoised across a verb, and what lands is a LineagePool the verb owns rather than a cache the repo holds. a pool is one read of the file, handed to the tips of a single pass and dropped with it, and it is never trusted to say no: a tip it cannot name refills it from disk, which is the authoritative answer the per-tip loop used to take every time, so a change another session lands mid-pass is still found. WHAT IS GIVEN UP IS NAMED RATHER THAN LEFT TO BE FOUND: a node the pool holds and the file no longer does is spliced where a fresh read would have called it absent, which needs a prune inside the window between a pass reading the graph and that same pass reaching a tip. objects and custody catch up once per pool at its first arrival instead of once per tip, on the ordering the per-tip version already rested on, a node the pool names having been in the graph file before that scan ran and its objects written before its graph entry. measured in store_file_reads, a count that is bit-exact under load, and not in wall clock, because another session had the desktop. a ferry pass over trailered commits whose changes landed outside the lineage-filtered load read 42 store files over 2 such commits and 48 over 5 before, and reads 40 at both after; resolving an adopt prefix over the same two shapes read 6 and 12 before and reads 3 at both after, the prefix search having been a whole read of the same file on its own. red under mutation, counts read each time: ferry put back to a read per commit failed its pin (0 passed and 1 failed, 43 against 49), the adopt resolution put back to a read per tip failed its own (0 passed and 1 failed, 6 against 12), a pool believed on a miss failed the later-arrival pin (1 passed and 1 failed) and a pool that never catches up failed the every-tip-brings-its-bytes pin (1 passed and 1 failed), each restored. the hand-written workspace width in verbs/mod.rs moves to 389, which its own derived census demanded. no migration, no wire or format byte moves and no host behaviour moves, so this owes no deploy. the workspace suite is green (4162 passed over 132 binaries, 8 ignored) (#2224)
96a35669 · dbf3dbe6…diff - shared_version_ids is deleted from DagRepo and Workspace, since LineagePool::ids answers the same whole-store id set out of the pool the adopt resolver already holds and the door had no caller left outside a test; the #829 reason it was added now sits on LineagePool::ids, and the adopt test that used it as a precondition asks the pool instead. the two #2224 counter pins stay two tests, and the adopt pin now says why at the code: what they share is the relation and its non-vacuity check, while what gives each its meaning is a fixture building a different position, which is where #2247 differs, its guards being one question put to one ingest timed and counted. the verb tier module doc now states 390 methods, the width its derived pin reads after the deletion. riding along, the can_open block no longer calls the discarded 1,091.9 ms reading neighbourless in the sentence that says its neighbours did not support it. red under mutation, counts read: the pool precondition inverted (0 passed and 1 failed), restored to 1 passed. no migration and no format byte moves, so this owes no deploy. the workspace suite is green (4254 passed, 10 ignored) (#2236)
160e9a33 · dbf3dbe6…diff - review missing-mark refusal chooses its remedy by position: in a lane it no longer advises a plain loot ferry, which projects the lane unlanded line onto the mirror main every position shares so the next land from any of them pushes it to git main whether or not a PR reviewed it, and names the fresh-lane route of #962 instead; the primary keeps the plain-ferry advice ADR 0039 kept. reproduced through the 0.4.24 binaries in a scratch repo with its own lanes: after adopt --seal-wip and a follow-up edit the review refused with the old advice, and the advised ferry projected 2 lane changes onto mirror main; the lane-built binary prints the lane remedy, the named route opens a review in a fresh lane, and the primary still prints the old line. pinned by a_lane_after_seal_wip_is_not_told_to_run_a_plain_ferry and a_lane_over_its_own_bare_seal_gets_the_lane_advice_too, the primary pin now also refusing the lane text. red with each piece undone, counts read each time, each restored green: every position given the primary advice (1 passed and 2 failed), every position given the lane advice (2 and 1). no format constant, codec byte or migration moves. the workspace suite is green (4436 passed over 139 binaries, 13 ignored) (#2314)
60610950 · dbf3dbe6…diff - a plain loot ferry in a lane refuses to project anything no land has carried, where it projected the lane unlanded line onto the mirror main every position shares so the next land from any of them pushed it unreviewed: the pass asks what it would project before the ingest and again at the gate seam after the reconcile, since --seal-wip can seal into the line, and loot-first land marks its own pass with RunOpts landing; a lane catch-up that projects nothing runs as before, and the refusal names the land of the PR that carries the lane tip or else the fresh-lane route of #962. the review reap says landed only from a land pass, on main for a projected version otherwise, and signed, no land has carried it for an unprojected one, where it said landed for any signed version. the seal-wip recovery recipe is chosen by position: the primary keeps its follow-up round and a lane gets the fresh-lane route, one const shared with the #2314 refusal. reproduced through the 0.4.24 binaries in a scratch repo with its own lanes (the advised ferry projected 2 lane changes and printed landed) and checked through the lane-built binary (refused, mirror main unmoved, lane catch-up still runs, reap and recipe reworded). the lane-simulated lands in the ferry tests now run as land passes, and 2 primary reap pins read on main. pinned by six new tests; red with each piece undone, counts read each time, each restored green over the ferry tests: the guard off (69 passed and 3 failed), a land pass refused too (61 and 11), every lane pass refused (70 and 2), the seam ask dropped (71 and 1), the PR route dropped (71 and 1), the old reap word (69 and 3), the recipe always the primary one (71 and 1) or always the lane one (71 and 1). ADR 0039 amended, CONTEXT.md, concurrent.md and the land-change skill updated. no format constant, codec byte or migration moves. the workspace suite is green (4442 passed over 139 binaries, 13 ignored) (#2366)
dac3385d · dbf3dbe6…diff - review sweep 12 over the lane projection guard: items 1 and 2 did not reproduce through the binaries in a scratch repo with its own lanes, since a position graph is its own lineage-filtered load (ADR 0022), so after a lane signed K a primary plain ferry projected nothing and minted no mark for K and the lane ferry still refused, and a lane with nothing of its own caught up while a sibling seal sat unmarked; the guard question stands and both scenarios are pinned. a lane ferry --seal-wip over described work refuses before the reconcile, where it sealed into the catch-up and then refused, leaving a sealed line no PR carries (reproduced on the landed binary). the reap never says landed, since the push that follows a land pass can still be refused, and says projected for a marked version, which a mark is and on main is not. the ferry verb landing false is pinned through the spawned binary. a refused pass carries its notes. the primary recovery recipe names --allow-primary for the verbs that need it there. the #2314 and #2366 lane refusals print one route rule, lane_landing_route, which sends a lane to its PR land only when the PR carries the tip with nothing captured on top. ADR 0039 amendment, ADR 0092 (--skip-gates), CONTEXT.md and concurrent.md corrected. red with each piece undone, counts read each time, each restored green: the verb flag flipped (0 passed and 1 failed), the seal-wip refusal dropped (75 and 1), the reap saying landed (73 and 3), the route ignoring captured work (75 and 1), the route ignoring the PR (75 and 1), notes dropped (75 and 1), the lane recipe on the primary (75 and 1), the guard off (71 and 5), every lane pass refused (73 and 3). the scripts MAINTAIN ask is its own scripts commit. no format constant, codec byte or migration moves. the workspace suite is green (4450 passed over 140 binaries, 13 ignored) (#2370)
18ea6db5 · dbf3dbe6…diff - review sweep 13 over the proposal conversation: loot propose --show verifies a comment against the repo and change id of the proposal it reads, since one change proposed to two repos shares its tip and a forge could serve one repo comment as said in another, and cages the body, the path and a refusal quoting the envelope through render sanitize, so ESC or a bare CR from a stranger prints as its control picture. an author is an account: a live key of the account whose key wrote a comment edits and withdraws it after a rotation, the key that signs an edit becomes its author so readers still verify it, and the author withdraws without the visibility check, so a lost view does not strand their words. the door arm calls may_propose_in and a party is asked live_account_in, its second half, where a copy stood. the comment routes read at most COMMENT_REQUEST_MAX, refusing a larger request before its signature is checked. /propose/read serves a thread a page of THREAD_PAGE comments at a time with a cursor, a trailer on both payloads, and --show follows it. an anchor must be spelled as a tree spells a path and is asked of tree_entry by its key through manifest_holds; Manifest::address_of reads manifest bytes the forge does not hold. a runner key under no account is refused moderation, and ADR 0095 now says any live key on the owner account moderates. finalized_before_review sends a lane to lane_landing_route rather than a land with no PR to name. ADR 0095 records the account rule, the paged read, the unbuilt withdrawal warning of its section 6 and the rewritten #2332 pin; ferry.rs says the #2370 pins run in-process; a pin holds the 0022 CHECK equal to COMMENT_BODY_MAX. red with each piece undone, counts read each time, each restored green: the envelope not bound to its proposal (0 passed and 1 failed in loot-net and in the CLI), the body or the path printed uncaged (0 and 1 each), authorship by key, and an edit keeping the old key as author (0 and 1 each), the author asked to see before withdrawing (0 and 1), the door arm a copy (0 and 1), no per-route limit (0 and 1), the forge thread unbounded and the CLI stopping at one page (0 and 1 each), anchors matched by Path over the whole tree (0 and 1), a runner key moderating (0 and 1), the lane land its own (0 and 1), the cap moved on one side (0 and 1). bash ci/local.sh is green against Postgres 18 (4498 passed over 139 binaries, 13 ignored). no format constant or migration moves; the proposal read gains trailers and the forge behaviour changes, so this owes a forge deploy (#2374)
bb95f4fb · dbf3dbe6…diff - loot show, the deletion rows of loot status and a surface listing name the published tier off the marker the object carries rather than off the .lootattributes on disk, so a published object whose rule was deleted no longer prints internal and a plain one given a rule since no longer prints published: the delta seam reads each row marker in Workspace::rows_of, off the side that carries the row, and keeps the rule for a live side (ADR 0079 section 1) and for a seal it cannot read, while the clobber guards take ResolvedDelta::classes and pay no read; surface, rehome and the pull auto-surface list through the new Workspace::with_sealed_publish_status. the status machine listing is the working tree on disk and keeps the rule. measured over a 400-path tree with the counters, every added read is a memo hit: object_gets moves by one per row with a recorded side (show of a half-edited change 1602 to 1802, status with a third deleted 804 to 938, surface --json 802 to 1203) and object_disk_reads is flat on each (601, 401, 401), a clean status is flat on both, and a hyperfine A/B over 2000 paths reads within noise (show 98.2 vs 97.3 ms, status 93.1 vs 92.6, surface --porcelain 165.4 vs 168.3, status with deletions 130.8 vs 129.8, load 0 to 6 percent). surface_object_reads counts the listing marker read apart from the opens, and the restore pin reads the seals its capture wrote. both directions pinned red first (0 passed and 2 failed with the fix undone), each through the verb: show and status deletion rows, and surface over trees ferried from git commits that delete and add a published rule. red with each piece undone, counts read each time, each restored green: the delta seam on the rule (1 passed and 1 failed), marker or rule, the overstating repair (1 and 1), the new side asked for a deletion (1 and 1), the listing on the rule (1 and 1), the listing as marker or rule (1 and 1), the guard paying the reads (0 and 2 in surface_object_reads). the published values in diff and surface machine output change for such rows and no key or column moves (ADR 0023). no format, wire or migration byte moves, so this owes no deploy. the workspace suite is green (4527 passed over 140 binaries, 13 ignored) (#2352)
0c27bd77 · dbf3dbe6…diff - tickets never reach git main (ADR 0098 section 8, map #2422): the ferry leaves every path under tickets/ out of each projected tree at every tier, set aside before the tier is asked so a sealed tickets/security path is not reported as an omitted sealed path, and drops any the git parent carries. a change whose projection repeats its one git parent tree and which writes a ticket path against one of its loot parents is commitless: it is marked at that parent commit and makes none, after the git parents are reduced so a merge with a commitless ticket change stands at the code commit in either order, while a sealed-only change still commits as before. one sha can now stand for several changes and change_for answers with the latest mark; the refs phase marks the change main moves to last, so mirror_main_change, adopt and the ticket harbor tip read a ticket-only land as landed, and the marks file keeps its format by writing the earlier marks at a sha before the latest, which older readers parse as the same map. the ingest leaves a git-side ticket path out with a note rather than refusing it, a review round of tickets alone is still made with no diff and a note saying why, and loot-first land lets through a land whose tip stands at main with no commit of its own, which the #195 guard refused. ADR 0028 records how it is built and its limits (a trailer rebuild loses commitless marks, a second machine learns such a land late), CONTEXT.md gains Commitless change and workflow.md the land case. red under sixteen named mutations, each 0 passed and restored, 1 failed unless noted: the ticket set-aside removed, the parent strip removed, both at once (2 failed), never commitless (3 failed), a commitless change left unmarked, refs not marking the anchor latest, the ingest taking tickets in, the land predicate skipping its trailer check, no parent reduction, writes_tickets asking the first parent only, the review note dropped, a text-prefix ticket path, the encode dropping earlier marks (seen in loot-core and through a re-ferry), the latest written first, and the land guard branch removed. cargo test green, 4614 passed over 142 binaries with 13 ignored (#2425)
f305ece5 · 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.