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
  • 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
  • the private shell unlocks without waiting for its mailbox, and the mailbox pull becomes incremental: measured live, forge /pull-grants took 2.78 s of a 4 s /private load, because the whole mailbox of 1,640 standing self-grants was downloaded before the shell counted as unlocked. /pull-grants now takes an optional 17-byte cursor in its signed payload (a tag byte, then the inbox seq high-water mark and the forge clock at the answer, issued by the server and opaque to the browser, which may only send the all-zero one) and answers rows with seq past it or a reveal_at past its time, due on the forge clock, followed by the next cursor after the counted frames; the Postgres read takes a share lock on grant_inbox so a deposit that took a lower seq but commits later cannot fall behind every future cursor, and an empty payload answers byte-identically to before, so the CLI, the SDK and the relay client are unchanged and either deploy order works. the site keeps the sealed grant blobs and the cursor in IndexedDB beside the vault (version 2, the seed record kept), builds the keyring from them, shows the shell unlocked and pulls only the delta in the background; a file whose key is missing waits for an in-flight pull, the bars say keys are updating, and Lock clears every store with the vault. the gate shows Unlocking while a silent unlock runs rather than flashing the unlock card. forge tests on memory and Postgres and site tests, each red under a named mutation and restored; bash ci/local.sh green against Postgres 18 (4559 passed), site gate 819 passed. owes a forge deploy and a site deploy (#2403) 11cc1e3a · 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.