Changes touching this path

  • the browser mints the seed and the account binds the key: Clerk in the site's own routes, variant-B onboarding, and the account tier's one producer (#753) Migration 0007 adds account_seed (the passphrase-wrapped envelope, opaque ciphertext the server cannot open) and forge_identity, a NOLOGIN role scoped to exactly the identity tier -- SELECT/INSERT on tenant, SELECT/INSERT plus a column-scoped UPDATE of retired_at on account_key, the full row on account_seed, and nothing else (pinned live by forge_identity_is_scoped_to_the_identity_tier). The producer lives in site/src/server/identity/ behind a verified Clerk session (verifyToken in the site's own route handlers, reusing the millerbyte Clerk instance and pattern, never the gateway service). Registration is one transaction: upsert tenant on the verified clerk_user_id, add the key, store the envelope, retire what rotation names -- and never zero live keys. The Rust service still writes none of the tier; tests/account_tier.rs now guards that boundary instead of a vacancy. Onboarding is #487's variant B, the two-column contract: the app owns the 32 bytes (crypto.getRandomValues -> 24-word BIP39 over the ENTROPY, never the 64-byte derivation -> ed25519 pubkey via @noble/curves, held to the engine's from_seed by the pinned RFC 8032 vector), a WebCrypto PBKDF2+AES-GCM envelope, a .txt download that excludes the passphrase, and two consent checkboxes that no download waives. Clerk mounts only under /account, which joins /k/ on the prerender deny-list; every static surface stays static. Follow-ons filed rather than folded: #926 (CLI raw-seed import, so BIP39 recovery reaches the CLI) and #927 (account-aware may_push, burn entitlement via account, and the #506 historical-key 301s the anonymous read tier deliberately cannot serve today). 278e1886 · dbf3dbe6…
  • 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.