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 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 last sweep's fix-up, and the item with teeth was a doc that told its reader to make the edit its own route file forbids: the component said it was exported so the route could load it LAZILY, where the route records that lazy was measured and REJECTED at 581 bytes against 270 and says in as many words not to fix it back without re-measuring. It now carries the route's true reason - so a test can render either half without a router - and marks explicitly that this is not a lazy split, pointing at the route for the readings rather than copying its numbers. The receipt showed eight hex characters wearing the class the module reserves for a FULL fingerprint, on the one surface whose whole purpose is an out-of-band fingerprint comparison, with adjacent copy inviting a comparison eight characters cannot support; it now shows the full fingerprint, derived in the accept from the bytes the apply itself re-verified rather than re-shortened in the surface, so the receipt cannot be rendered from the offer the apply was meant to re-check. Dropping the class and keeping eight was rejected because it would make the receipt say LESS about the one thing being checked. Rendered bytes did not really move - three gate builds read the same surface at 199873, 199872 and 199873 against 199872 recorded, and the whole sixty-route spread is minus seven to plus four - so nothing was re-recorded and the two bytes the ticket asked about turn out to be the instrument rather than an unaccounted byte. CONTEXT.md's list of account_of's consumers stops being a third copy and POINTS at the header that owns it, naming the one consumer #1746's own restatement dropped - which is the lesson of this fix-up, since that restatement was itself the remedy for a count-drift finding and drifted in the same way. The budget note is corrected as PROSE with no ceiling and no measurement touched: uniformly on every one of these surfaces was authored from a single weighing, and one surface moved the other way and had its ceiling LOWERED after its page lost six entries. The decline receipt moves into the register module that owns the vocabulary, with an absent-phrase list and a RENDERING control, because an absent-phrase list over copy that never renders proves nothing. And a count of refusals is removed rather than corrected: the numbers are labels for rules and do not line up with the code, since the first propagates from the walk and the fifth leaves from more than one site with different sentences (#1751) 571d3533 · 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.