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… - 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 - 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.