Changes touching this path
- the browser opens what the forge cannot: own-private read ships an owner read tier, a once-per-device unlock, and a grant mailbox that never acks (#1043)
c8dd4da1 · dbf3dbe6… - the foreign grant stops applying itself, and that was a LIVE defect rather than a guard this ticket adds: pullMailbox indexed every verified blob alike and discarded the grantor pubkey, so a grant somebody else deposited in the mailbox was applied SILENTLY the first time any file at its address was opened - the exact opposite of #488's decision that nothing auto-applies. The mailbox is sorted BY GRANTOR now: a self-grant under ADR 0057 still goes straight into the key index, while anything signed by another key becomes an offer in a second map the decrypt path cannot reach, so the property is structural rather than a check somebody has to remember. Restoring the old line turns nine of sixteen cases red, headed by a stranger's key entering the key index, and the positive controls are what make locked a decision rather than a broken fixture: an accepted grant reads open with bytes equal to the content key, and a self-grant in the SAME pull still opens itself. The copy is asserted as RENDERED rather than as source, and the decline half is honest for a reason it states out loud - the only wire act that could take a grant out of your mailbox is an ack, and an ack would delete the blob for every other device you own, so this page does not have one - which is why declining revokes nothing and says so, against an absent-phrase list that refuses has been revoked and no longer has access. Expiry's first line is WELDED to the refusal constant rather than restating it, an expired offer stays visible after a refused accept, and a control files the same grant one second before it expires. The unprovisioned member tier is ASKED rather than remembered: the me route answers whether the read class is live and the browser tests that for exactly true, so a missing field lands on the arm that promises nothing, and the dark arm - the live one today - says the repo answers not yours as a 404 whether or not you have been added. Three things were found on the way and each is measured rather than argued. Importing the fingerprint helper dragged a curve library into the shared entry at eight kilobytes on every PUBLIC route, fixed by moving the function to the dependency-free module beside it. A lazy route was measured and REJECTED at 581 bytes against 270 eager, because the split chunk's manifest outweighs the code it saves, and that is recorded in the route so nobody fixes it back. And the byte record had to be raised, so the raise is SPLIT rather than apportioned: the same tree with this ticket removed already reads a thousand and thirty bytes over the recorded numbers on every surface, which is pre-existing shared-entry drift, where this page itself costs 270. The budget tool had a real bug that recording surfaced - its lowering note is anchored to the end of the line so a later lowering can replace its predecessor, but a raise appended after it buried the note out of that anchor's reach and the next lowering stamped a second one - fixed with a fixture that is red without it (#1640)
152d0635 · dbf3dbe6…diff - an api route answers an undeclared method with the same 404 an unserved path gets, and the all-platforms table goes through the counted hop with the direct link beside it
loot#1925 and the last site item of loot#1647, in one change because both are
about what a URL on this site tells a stranger.
The router looks up handlers[method], then handlers.ANY, and on neither falls
through to SSR - so GET /api/beacon answered 200 with 5 KB of HTML while
/api/definitely-not-a-route answered 404, measured live the day the beacon
deployed. The difference told a prober which routes exist, which is the oracle
the download hop refuses to be. One ANY_NOT_FOUND spread into every api.* route
answers the bare 404 an unserved path gets. NOT a 405: an Allow header is the
polite answer and exactly the one that confirms the route and lists its
methods. A declared method still wins, and that is pinned as a fact about the
router read from its source rather than assumed. A test walks every api.*
file, so a new endpoint cannot arrive without the fallback.
The table links and the sentence about them moved TOGETHER, which loot#1647
insisted on: switching the links alone produces a page that counts a click it
calls anonymous. Each row now links through loot.millerbyte.com/dl with the
direct R2 URL visible beside it, hopHref is null for anything the hop would
refuse so a row can never render a counted link that 404s, and the manual-
install note says the hop counts the release, the platform and the time and
sends you on to the CDN that serves anonymously. The checksum instructions
still fetch sha256.sum direct, because the hop refuses it on purpose - every
land fetches that file and admitting it would count every land as a download.
Rendered: all five rows carry both links, the note reads as one sentence. 610
site tests, budget green.
7c4f1a17 · dbf3dbe6…diff - the private shell stops dying a minute after /account: it authenticated with Clerk 60-second __session cookie while no Clerk JS ran on /private to refresh it, so every private call 401d and a reload wiped the vault. per the operator grill (ADR 0096, reversing #930 dec. 4), Clerk JS now runs on the private routes, loaded on demand behind PrivateGate so public pages carry none, under the strict nonce CSP widened only for Clerk (its frontend API host read out of the publishable key, img.clerk.com, the Turnstile and protect hosts, and worker-src blob: for its session timers) with the request nonce passed to ClerkProvider; every private call goes through one privateFetch that sends a Bearer from getToken and retries once with a fresh token, and /api/private/* no longer accepts the cookie; the vault is wiped only when Clerk reports signed out or a different user, or on Lock, and an HTTP status never wipes it; signed-out /private renders SignIn in place and returns to the requested URL, and the private bar gains Sign out beside Lock. the vault, private shell and CSP comments and CONTEXT.md now say what is true: the wrap key can decrypt, so any script on this origin can recover the seed, and the wrap protects it at rest; the boundary is this origin script set, loot code plus Clerk. tests for the CSP, bearer lane, retry and wipe triggers and the built server, each red under a named mutation and restored; site gate 804 passed, every surface lighter; a CSP-enforcing signed-out load renders SignIn with no violation and /why loads no Clerk; workspace suite 4549 passed. the operator live check closes the ticket (#2396)
25ab3fa8 · dbf3dbe6…diff - key management on the site: a key can be retired by itself, named, and chosen at unlock. per the operator grill on #2398 (ADR 0100): POST /api/identity/retire retires one key of the signed-in account, refusing the last live key under the account-row lock registration takes and never re-dating a retirement, with no route to un-retire and no deletion, since #506 keeps every key resolvable; /account lists live keys and folds retired ones under a collapsed section, and the retire confirmation names the repos the key owns. names live in a new owner-only account_key_label table (migration 0027) that only forge_identity reaches, trimmed, at most 40 characters, with control, line-separator and direction characters refused and a CHECK repeating the cap; they reach only /account, the unlock picker and the private bar, always beside the fingerprint. the unlock picker lists every live key, defaulting to the key that owns the URL namespace, then the key this device last unlocked, then the only live key, and a key with no stored passphrase asks for its 24 words and can save one. both routes are Bearer only and refuse a key of another account; /api/identity/me stops returning the internal tenant id. rebased by hand over migration 0026 and ADR 0099. pg tests pin the role reach, route and picker tests pin the rules, each red under a named mutation; bash ci/local.sh green against Postgres 18 (4568 passed), site gate 842 passed. owes the setup-forge tripwire update, a forge deploy, then a site deploy (#2398)
a90c85fb · 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.