# Web Foundations dogfood notes This living note records concrete pressure discovered while Gamertan services adopt Web Foundations. It is implementation evidence, not a promise that every application concern belongs in the shared module. ## Gamertan accounts and commerce - The account email remains required and unique. Gamertan uses normalized email as the canonical login identifier and keeps username as a stable public identity. Until a mail package exists, the application must not describe an address as verified merely because it was entered during registration. - Password authentication is sufficient for an ordinary customer base session. Privileged application actions use an exact operation binding with `authwebauthn.BeginApproval` and `FinishApproval`; that is safer than a broad long-lived "elevated" session. A user without a passkey can use ordinary features but must enroll one before performing protected work. - `auth.Service.VerifyPassword` remains available for flows that truly require password plus passkey before session issuance. - Public registration exposed a cross-package transaction boundary. The `account` package now keeps an unusable bounded registration draft and makes recovery-code digests, personal organization, membership, owner binding, activation, audits, and an optional initial passkey one repository commit. A failed WebAuthn ceremony can be restarted, or an ordinary password account can finish without it, without persisting a partly privileged account. - Media belongs behind a storage-neutral interface with a hardened local adapter. Content workflow, references, and authorization remain application policy. - Historical `authsqlite.Open` still migrates for compatibility. Applications with reviewed deployment gates use `OpenWithOptions` with migration disabled, require the current schema at startup, and invoke `Migrate` only from an explicit operator command. - Commerce remains a separately versioned nested module so payment-provider policy and catalog evolution do not enlarge the authentication core. - Self-service enrollment exposed an authorization seam: completing a valid ceremony and checking its user only after persistence is too late. `FinishRegistrationForUser` now consumes mismatched ceremonies and checks the application-authenticated user before storing a credential. - First-owner provisioning exposed another cross-package transaction boundary. `bootstrap` now commits the passkey-only user, enrollment digest, non-personal organization, membership, direct owner binding, and audits together. Applications must seed their owner role first and must write the returned raw token only to a newly created private file. - Recovery-code consumption alone is not a complete recovery path. The restricted grant must survive an interrupted authenticator prompt yet be consumed in the same transaction that stores the verified replacement passkey and replacement code digests. `authrecovery.BeginPasskey` and `FinishPasskey` now provide that boundary without creating an authenticated session; Gamertan keeps the raw grant only in a short-lived HttpOnly cookie. - A portless-only WebAuthn origin rule made an unprivileged local HTTPS exercise impossible even though WebAuthn origins include ports. The passkey service now permits an explicit development port only when applications opt in and the RP ID is `localhost` or reserved `.test`; production origins keep the original portless default. - Gamertan's staff-access page exposed a dangerous composition gap between individual grant/revoke calls. Foundations now owns one optimistic, transactional direct-role replacement that preserves the final active owner and appends its audit before commit. The application still owns route authorization, role presentation, CSRF, and the exact fresh-passkey operation binding. - Extending that page to membership suspension, reactivation, and removal exposed the same time-of-check gap in the older lifecycle methods. The new optimistic extension serializes on the active administrator membership, rechecks the exact state bound into the passkey assertion, applies team and direct-binding consequences, and writes the audit in one transaction.