The live instance had no way to add a passkey or a user. Two core pages now
make the signed-in experience usable (SPEC-010):
* the ACCOUNT page (GET /{GUID}/account) shows the user's factors (passkey
count · TOTP enrolled), an "Add a passkey" button that LINKS to the
existing D-041 ceremony screen — the WebAuthn flow is never duplicated —
and a "Don't ask again" control writing users.passkey_opt_out;
* the admin USERS page (GET /{GUID}/users) lists every user (name · email ·
role · manager · identity status) and creates one: only roles below the
creator (D-019), the manager defaulting to the creator, then users.Create
-> identities.Mint -> the /{GUID} invite is EMAILED and shown for
copy/paste.
Both ride the door as core dispatch modules, so the door still gates them
(session + role + CSRF). The entrypoint requires admin for the users
functions; account stays at the team floor.
Additive support: auth.Identities.TOTPEnrolled, users.Users.List,
server.Deps.Credentials (the banner's "has a passkey?" read) and the
CoreDispatchNames guard now covers account/users.
The shell now renders the CORE nav entries — Home · Account for everyone,
Users · Modules only for an admin — alongside the registry entries, with the
discovered `home` dashboard collapsed onto the core Home entry (SPEC-010 §3).
A signed-in user with no passkey, who has not opted out and is not on a
loopback origin, now sees a prominent "Add a passkey" banner linking to the
Account page (SPEC-010 §6). This closes the D-041 gap where the wizard's
direct session mint bypasses the TOTP-login nudge, so the admin was never
prompted.
The door computes (admin, banner) in serveGUID — it already holds the
session, identity and user — and attaches it to the request context; the new
web.PageFor copies it onto every authenticated page (RequestContext is
untouched, so identification stays separate from authorisation, D-003).
- Build-time discovery (D-028, the Caddy pattern): a module self-registers from
init(); internal/modules/imports_gen.go (go:generate) blank-imports every
module directory, so THE DIRECTORY IS THE SOURCE OF TRUTH
- The registry: deterministic registration order, config-backed enable/disable
(D-038), required modules, name-prefixed migrations, Mount() into the door
- The name-collision guard now also fails a module named for a reserved
level-1 workflow (SPEC-009 §13 Q-1) — access · static · api · media · ws ·
dev — derived from routing.ReservedWorkflows, `home` excepted (D-2)
- home: the reference dashboard module, and the core Modules page