Changes touching this path
- Forge service skeleton: /ingest, the sync surface, and CLI forge remotes (#502)
A new `loot-forge` crate and binary, sibling to the relay and never a mutation
of it: `loot-net` is a dependency of `loot-cli`, so a storage driver in the
relay would ship inside the released CLI binary (ADR 0041:53-57). The ban runs
one way -- the forge may use the other crates; none of them may gain a driver.
Every endpoint is envelope-authenticated, where a relay authenticates only
/stow. The one declared exception is GET /info: a client reads the capability
probe before it knows how to authenticate.
POST /ingest = envelope_sign(bundle || head_declaration), one signature over
both, so a genuine bundle can never be replayed under someone else's head
claim. CAS first; nothing is written before the generation compare. A lost CAS
answers with a 409 BODY carrying the current generation and head set -- behind,
not bounced -- so the client re-carries with no extra round trip.
/stow keeps relay parity and moves no ref, and consumes no generation either,
since a client reads the generation once and then stows N batches. It DEFERS a
change whose objects have not all arrived; /ingest REFUSES one, because a
declared head whose objects are missing is a repo no pull can complete. That
asymmetry is what keeping /stow is for.
Bundles are filtered PER OBJECT by the caller's access class, closing the leak
the ticket names: bundle_impl ships a key for every ANYONE-granted object, i.e.
every Internal object. Change metadata rides the second #494 axis and gates the
whole walk -- a ChangeNode carries the full tree, so shipping one while
withholding its bytes still discloses every path, address, message and author.
Validation before any write: author signatures, and a tree whose visibility
contradicts its objects is rejected. vis_tag is derived from the SealedObject
and never trusted from the tree, which is what makes content-addressed upserts
safe.
Forge purge policy: burn iff burner == object.introduced_by, recorded at first
arrival as introducing.author else the envelope pubkey; maroon read off
grant_log, global and exact. Both are judged before any object is stored and
outside the CAS transaction.
Repos are pubkey-addressed at /k/<pubkey>/<repo>, auto-created on first push,
and may_push holds iff the signer owns the namespace. resolve_remote is
untouched -- still no default host. CORS from day one.
Storage sits behind three narrow traits with in-process reference
implementations the whole suite runs against; the Postgres DDL they mirror is
checked in at docs/sql/forge-schema.sql and its driver is #516.
CLI: /info capability detection, the live head declaration off the Liveness
view, and a refusal to push a lane -- v1 ingests the primary position only.
verify_authored_change moves to loot-codec so the forge runs the identical gate
without linking the fs-hardwired engine.
Recorded in ADR 0041, the spec and CONTEXT.md: the forge stores no content key
but a published one, because filing the ANYONE key lane would let the server
read every Internal object, and the keystone is that only Published is
server-readable. The cost is stated rather than hidden -- a fresh clone of your
own Internal content from a forge is ciphertext you cannot open until #488
lands.
Closes #502.
300e06ff · dbf3dbe6… - Give the forge an operator channel, with one redaction rule (#522)
f9ca9f91 · dbf3dbe6…diff - Forge storage drivers: Postgres metadata tier, S3 blob tier, and one conformance suite both must pass (#516)
f65970e6 · dbf3dbe6…diff - Release v0.2.0: lockstep versions, dist-able loot-forge, pinned rust-toolchain (#628)
7eef425d · dbf3dbe6…diff - release: bump loot-cli and loot-forge to v0.3.0 for a relay+forge redeploy
15ba4ad7 · dbf3dbe6…diff - release: bump loot-cli and loot-forge to v0.3.1 for a relay+forge redeploy
beab8725 · 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 - release: bump loot-cli and loot-forge to v0.4.1 for the forge ingress deploy (#690)
70c27a07 · dbf3dbe6…diff - adopt: catch up to landed main f266c856
da01da3d · dbf3dbe6…diff - release: bump loot-cli and loot-forge to v0.4.2 for the forge ingress deploy (#690)
f266c856 · dbf3dbe6…diff - the metadata tier speaks TLS when the URL asks for it, and refuses rather than falling back to plaintext (#689)
adf57547 · dbf3dbe6…diff - release: bump loot-cli and loot-forge to v0.4.3 for the relay grant-auth redeploy (#818)
a595bfd9 · dbf3dbe6…diff - release: bump loot-cli and loot-forge to v0.4.4 for the 0006 migrate and the gc-tmp and auth-preamble redeploys (#895)
324e05c8 · dbf3dbe6…diff - release: bump loot-cli and loot-forge to v0.4.5 for the 0007 identity migrate and the /account deploy (#753)
2f7eb70e · dbf3dbe6…diff - release: bump loot-cli and loot-forge to v0.4.6 so a deploy release carries the 0052 relay abandon-head (#996)
470db09e · dbf3dbe6…diff - release: bump loot-cli and loot-forge to v0.4.7 so the public installer matrix has a tag to build off GitHub (#1021)
042e1270 · dbf3dbe6…diff - release: bump loot-cli and loot-forge to v0.4.8 so a deploy release carries the account-aware write rule (#927)
f69ff6fd · dbf3dbe6…diff - release: bump loot-cli and loot-forge to v0.4.9 so a deployed binary carries migration 0008 (#927)
40c9e2d6 · dbf3dbe6…diff - release: bump loot-cli and loot-forge to v0.4.10 so a deployed forge and site can read credentials from a file (#1034)
21181dc8 · dbf3dbe6…diff - release: bump loot-cli and loot-forge to v0.4.11 so the deployed forge carries migration 0009's owner tier and a /wants that answers a whole address list (#1056)
43ce995d · dbf3dbe6…diff - release: bump to v0.4.12 so the deployed forge carries an ingest that loads the arriving delta's visibilities in one query (#1072)
44dbe40f · dbf3dbe6…diff - release: bump to v0.4.13 so the public catalog can carry the allow widening that its own durable-host guide documents (#1100)
ec7728db · dbf3dbe6…diff - release: bump to v0.4.14 so the deployed forge can serve the grants peek that #1114 mounted
076042d3 · dbf3dbe6…diff - the relay store goes behind a RelayStorage seam, the SigV4 driver moves to its own crate, and an object-store relay is a second deployable the CLI never ships (#38)
330df915 · dbf3dbe6…diff - release: bump to v0.4.15 so the deployed forge carries the /offer walk that stopped probing Postgres and R2 once per (change x entry) (#1154)
0d37053d · dbf3dbe6…diff - release: bump to v0.4.16 so the public matrix carries the custody passphrase, the widened mis-seal gate, loot converge and the honest doctor
e61b48cc · 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 0.4.18: the Known Issues page is re-reviewed against the binary it now names, the seal-away-from-yourself entry leaves because the lockout gate shipped, and the reverted-peer-edit entry narrows to the locked pull that can still do it
29f571e7 · dbf3dbe6…diff - loot 0.4.19: the Known Issues page is re-read against the binary it now names by running it rather than re-reading its source, the six entries pin 5 held leave on the release that finally carries their fixes, and the pin retires with its emptied table on its own instruction — the re-run is what caught the evolog entry rotting without an edit, because loot log grew a when column somewhere in this range and the entry had claimed for two releases that evolog was the only time the CLI prints, and the recovery-door note is rewritten rather than deleted since loot id phrase shipping does not change that both it and recover open the repo first; and the restore no-op test stops calling its own instrument blind when the clock is the coarse thing, because it stamped the setup write and the restore microseconds apart against a file clock that ticks every 15.6 ms — it was failing four runs in five on main, standalone, while passing inside a full suite slow enough to straddle a tick, so it now probes for a distinguishable mtime rather than sleeping a constant that would bake this filesystem granularity into the assertion
4dcc9ff6 · dbf3dbe6…diff - loot 0.4.20: loot-cli and loot-forge move to 0.4.20 with their lock entries, the first public release since 0.4.19 and the one that carries the opt-in telemetry client, format major 14 and forge migration 0017; the relay and forge already read format 14, which ADR 0066 requires before the CLI publishes. the Known Issues page is re-reviewed by running the release binary against a local relay rather than re-reading source: both entries reproduce word for word and the unlock session file holds no raw passphrase, but the run falsified the pages own remedy. it read unlock, rehome, then converge, and when rehome refuses, which it does in exactly that state, carrying on to converge captures the stale copy the same way describe does, after which surface --force re-materializes the stale bytes. measured from the pristine post-pull state, moving the file aside and re-running rehome, or surface --force, each put the arrived version on disk, and describe then undo does too; the second column now says that in that order and says to stop before converge, and the entry says converge does the capture unasked. gone from the history was wider than the run, since the arrived change stays in the log one below a tip that reverts it, and the entry now says that. the converge capture is filed as 1963. tsc, the site suite, the build, the byte budget and the published-surfaces pins are green (#1960)
b53bd00d · dbf3dbe6…diff - loot 0.4.21: loot-cli and loot-forge move to 0.4.21 with their two lock entries, the first public release since 0.4.20 and the one that carries the locked-pull fix. format major stays at 14 and the live relay already reports 14, so ADR 0066 has nothing to sequence here, and the top forge migration is still 0017, so this release owes no schema step. the Known Issues page is re-reviewed by running the 0.4.21 binary against a local loot-relayd rather than by re-reading source, and the locked-pull revert NO LONGER REPRODUCES: rehome materialized rather than refusing and put v2 from alice on disk, the converge the note names next answered already on one line and left it there, loot status reported an empty working change and loot log showed the arrived change as the tip, which is the state the printed advice implies, reached by typing the printed advice. so that entry is deleted, and FIXED_IN_MAIN, its HELD_ENTRIES row and pin 5 go with it, on that pin own instruction, leaving loot edit, which reproduced word for word, as the one entry. REVIEWED_AGAINST moves to v0.4.21 and LAST_REVIEWED to 2026-09-19, and the locked-pull paragraph further down the page stops pointing at a deleted entry and says what rehome does now. RELEASE_TAG is deliberately NOT moved: it names what dl.millerbyte.com can serve, and this release has reached nothing yet. the privacy page verb-count claim was re-checked against the binary rather than assumed, since that is what fell over before the last cut, and note_dispatched is called on all three dispatch routes, the table, buoy and bisect, so every verb this release newly publishes is counted. the site byte budget is re-recorded and exactly one ceiling moves, /known-issues lowered from 210944 to 208896 by the deleted entry, with no raise anywhere. tsc, the site suite (659 passed and 61 skipped over 60 files), the build, the byte budget and the published-surfaces pins are green, and the workspace suite is green (3970 passed over 122 binaries, 7 ignored) (#2089)
71ed6938 · dbf3dbe6…diff - loot 0.4.22: loot-cli and loot-forge move to 0.4.22 with their two lock entries, the first public release since 0.4.21 and the one that carries the seek follow-ups, a fetch depth the hosts honour (#2123), the browser SDK bounded read (#2124) and the seek cache cap with --gc (#2125), beside the pipeline verb (#2127) and every land since 3039521e. format major stays at 14 and both live hosts already report 14, so ADR 0066 has nothing to sequence here, and the top forge migration is still 0017, so this release owes no schema step; the relay and the forge still deploy before the site, since #2123 is what the SDK bounded read refuses without. the Known Issues page is re-reviewed by running the 0.4.22 binary in a sandboxed home rather than by re-reading source: loot edit on an older change refuses with change tlqyroot has descendants, v1 edits only a tip (childless) change, word for word and exit 1, while on the tip it reopens the version as the working change, so the one entry stays as written; loot seek --gc --dry-run in that sandboxed home lists no positions and the defaults. REVIEWED_AGAINST moves to v0.4.22 and LAST_REVIEWED to 2026-09-20. RELEASE_TAG is deliberately NOT moved: it names what dl.millerbyte.com can serve, and this release has reached nothing yet. the site byte budget is re-recorded: NOT re-recorded, because no ceiling has to move: every one of the 62 surfaces is under its ceiling, and the uniform +150 to +170 B on every surface is #2127 CLI row in the shared docs chunk they all fetch, one growth and not sixty-two, and not this cut own; the site gate is green in the lane (669 passed and 62 skipped over 61 files). the workspace suite is green at the base (4023 passed over 126 binaries, 7 ignored, on the #2125 lane a commit below, and this land gate runs it again) and cargo build --release --locked validates the lock edit (loot 0.4.22 from the lane binary, and loot-forge 0.4.22 built beside it, refusing to open its storage without its URL, which is its answer outside a deploy)
8b4aa08f · dbf3dbe6…diff - loot 0.4.23: loot-cli and loot-forge move to 0.4.23 with their two lock entries, the first public release since 0.4.22 and the one that carries the seal-over-tree-entry arc, where every site that acted on a change tree entry unsigned visibility field now asks the seal instead (#2185 the three acting sites, #2188 the timed deposit lane, #2187 the push-time deposit plan, #2196 the git bridge projection, #2203 the git-side ingest, #2205 both grant doors and #2212 the sync ingest door, with #2206 and #2214 measuring and correcting the prose behind them), beside the forge runner and job tables (#2157, ADR 0091, migration 0018), the owner side of a proposal (#2162, ADR 0075), the pre-land gate learning which stream a doctest reports on and refusing a cargo it could not start (#2084, #2140, #2199, #2066), a patch that carries the ending of the line it shows (#2005), the relay mailbox taking the door the relay already writes objects through (#2112), and every other land since 3cfbe9b, 30 in all, the first of which is the RELEASE_TAG move that published 0.4.22 (#2147). format major stays at 14, format.rs has no diff at all in the range, and both live hosts answer format_major 14 over /info, so ADR 0066 has nothing to sequence. the top forge migration is now 0018_runner_and_job.sql where the 0.4.22 cut said 0017, so unlike the last release this one DOES owe a schema step: 0018 is include_str-ed into MIGRATIONS and rides the forge binary, so the forge deploys BEFORE the site. the Known Issues page is re-reviewed by running the 0.4.23 binary in a throwaway home, with HOME and USERPROFILE and XDG_CONFIG_HOME and XDG_CACHE_HOME all redirected into a temp directory and a local loot-relayd for the pull half, rather than by re-reading source: the one entry reproduces word for word, change xoxokopy has descendants - v1 edits only a tip (childless) change at exit 1, while loot edit on the tip reopens version 0101b025 as the working change, so it stays. the custody prose was re-run rather than re-read as well: once loot lock cleared the session, status, log, diff, grep, surface, whoami, describe and push each refused with the ADR 0068 message while lock and unlock still ran, a wrong LOOT_PASSPHRASE said so and was ignored, the session file held loot-unlock v2 and machine and sealed hex with the passphrase own hex in no file under the config directory, loot id phrase refused both a pipe and a redirect with no override, loot undo refused across the push barrier in the words the page quotes, and loot burn printed the never-pushed and pushed tiers with one line per disclosed host. ONE CLAIM IS CORRECTED, and it had rotted without an edit: the locked-pull paragraph read since {REVIEWED_AGAINST} the ingest writes down the claim it could not check, which was exact when #2089 wrote it at v0.4.21 and false one cut later, .loot/stale-disk-unverified being in the v0.4.21 tag and absent from v0.4.20, so it is the literal v0.4.21 now, for the reason the session-file boundary is the literal v0.4.17; the behaviour itself was re-run and holds, a locked pull leaving the receiving disk on the old bytes and parking .loot/stale-disk-unverified while the rehome its note names materialized the arrived version after unlock. REVIEWED_AGAINST moves to v0.4.23 and LAST_REVIEWED to 2026-09-21. RELEASE_TAG is deliberately NOT moved: it names what dl.millerbyte.com can serve, and this release has reached nothing yet. the site byte budget is NOT re-recorded, because no ceiling has to move: all 62 surfaces are under theirs, /known-issues is +7 B against a 208896 B ceiling, and the one figure worth a reader eye is /privacy at +586 vs recorded with 907 B of headroom, which is not this cut own since nothing here touches that page. the site gate is green in the lane (tsc, the build, 669 passed and 62 skipped over 61 files, and the budget), the workspace suite is green (4140 passed over 132 binaries, 8 ignored, 0 failed) and cargo build --release --locked validates the lock edit (loot 0.4.23 from the lane binary, and loot-forge 0.4.23 built beside it)
078d5ce1 · dbf3dbe6…diff - loot 0.4.24: loot-cli and loot-forge move to 0.4.24 with their two lock entries, the first public release since 0.4.23 and the one that carries the proposal read surface and its database views (#2174, #2175, #2177, #2180), the land gate reading foreign authorship and refusing on a require line (#2178), the forge keeping the attestations a push carries (#2250, migration 0019), the runner routes and loot runners (#2158, migration 0021), the browser sdk transport watchdog (#2070), the perf follow-ups from #2222 to #2247, and every other land since 8fb0716, 35 in all. format major stays at 14 and format.rs has no diff in the range, so ADR 0066 has nothing to sequence. the top forge migration is now 0021_runner_last_seen.sql; 0019 and 0020 are already live from the deploy-only v0.4.23-deploy.1, so 0021 is the one new schema step, and it rides the forge binary, so the forge deploys BEFORE the site. the Known Issues page is re-reviewed by running the 0.4.24 binary in a throwaway home with a local loot-relayd and a loot-forge --dev, not by re-reading source: the one entry reproduces word for word, change lkorrwmk has descendants - v1 edits only a tip (childless) change at exit 1, while loot edit on the tip reopens version f318c57a, so it stays; the eight custody verbs refuse with the ADR 0068 message once loot lock clears the session and lock and unlock still run, a wrong LOOT_PASSPHRASE on a relay pull is reported and ignored, that locked pull files keys and leaves the disk on its old bytes and parks .loot/stale-disk-unverified while rehome after unlock materializes what arrived, a locked forge pull refuses by name, the session file holds loot-unlock v2 and machine and sealed with the passphrase hex in no file under the config directory, loot id phrase refuses a pipe and a redirect, loot undo refuses at the push barrier in the quoted words, and loot burn prints the never-pushed tier and the pushed tier with one line per disclosed host. three probes were first mis-driven by the harness and re-run, which the page records. REVIEWED_AGAINST moves to v0.4.24 and LAST_REVIEWED to 2026-09-23; RELEASE_TAG is deliberately NOT moved, since it names what dl.millerbyte.com can serve and this release has reached nothing yet. cargo build --release --locked validates the lock edit (loot 0.4.24), the published-surfaces pins pass (6 passed) and the site gate is green with nothing recorded (#2276)
4ad8f503 · dbf3dbe6…diff - loot-cli and loot-forge are 0.4.25, the release that first carries the loot.build names (map #2412, ADR 0099), and the Known Issues page is re-read against it by hand in a throwaway home: one claim had rotted with no edit, since #2389 gated the reads of an allowlisted relay and a locked pull there now refuses by name as a forge does, so the page says an open relay runs while locked; one entry is added first, found by driving burn further than any pass had: burning a finalized path a forge has not yet received makes every later push to that forge refuse and the path cannot leave the tree, older than this release and filed as #2441, with the undo-first workaround that was run; LAST_REVIEWED is 2026-09-26 and REVIEWED_AGAINST v0.4.25. published_surfaces 6 passed; site gate 819 passed, /known-issues within budget; release build of the lane reports loot 0.4.25 and names security@loot.build and https://loot.build/trust in its help (#2419)
c594ee28 · 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.