Version. 0.1
Date. 2026-06-12
Status. Landed. Both commits built, tested, and pushed — engine 5d7b291 and Operator Layer becf598. This change request records the conformance change as one unit and carries the standing note on the vestigial state it left.
Render-type. Change request. Operator-facing — HTML primary, Markdown source alongside.
Source. Seed v0.12 (Authentication framework — Identity and Sign-in clauses); the recovery-completeness session handoff (2026-06-12); the read-only inspection of the sign-in path across engine and Operator Layer (2026-06-12).
Repos touched. loomworks-engine and loomworks (Operator Layer). No record-doc behaviour change.
Seed v0.12 says sign-in must never use email to find a user. The system was still allowing it: the engine's sign-in challenge accepted an optional email and, when given one, looked the person up by email to narrow which passkeys the browser offered — a forbidden email-keyed lookup. The Operator Layer fed it through an optional email field on the sign-in form.
This change removes both. Sign-in now runs the WebAuthn discoverable-credential flow only: the browser offers every passkey registered for Loomworks, the operator picks one, and identity is resolved from the chosen credential — never from email. The sign-in form is now a single "Sign in with passkey" action with no email field. The engine already resolved identity by credential at the completion step, so nothing about who can sign in changes — only the forbidden lookup is gone.
Seed v0.12, Authentication framework — the two clauses this change brings the system into line with:
The discoverable-credential flow was already the default and was test-covered on both sides (verified read-only, 2026-06-12). This change makes it the only path.
5d7b291 (email-keyed lookup removed, discoverable-only)LoginBeginRequest: the email field is removed (the request model is now empty).begin_login: dropped the email parameter, the get_person_by_email call and its import, and the allow_credentials narrowing. The challenge runs the discoverable-credential flow unconditionally (allow_credentials=None)./auth/login/begin route: description and docstring rewritten to the discoverable-only path; no longer passes email.complete_login is unchanged — it already resolves identity by credential_id, with no email in LoginCompleteRequest.begin_login tests (known-email narrowing; unknown-email anti-enumeration); kept test_begin_login_without_email_returns_discoverable_options as the canonical sign-in-begin test; dropped the email argument from the remaining begin_login call sites.becf598 (email field removed, passkey-only)signin-flow.ts: beginLogin() takes no argument and always POSTs an empty body; removed the no_passkey result and the 404 catch (the engine never 404s from /auth/login/begin); removed the no_passkey step from the sign-in state machine. The ApiError import is retained — still used by the TOTP step (401/403) and reactivation (410).IdentifierStep.tsx: removed the email input and its state; the step is now a single "Sign in with passkey" action.page.tsx: the identifier handler no longer takes or sends email; removed the no_passkey branch and its render block.strings.ts: removed the six email* sign-in strings; added passkeyStartButton ("Sign in with passkey"). The "Sign in to a different account" string is retained — it is still used by the reactivation card.no_passkey state — is deleted); kept the two empty-path tests as canonical; updated the remaining tests to start sign-in via the passkey action; rewrote the file header./auth/login/begin. The Operator Layer sends an empty body; the engine request model carries no email field.get_person_by_email is no longer called from sign-in; identity is resolved at completion by credential_id.
> Vestigial state left by email removal — get_person_by_email orphaned, db unused in begin_login, LoginBeginRequest now empty. All retained deliberately, not dead-by-accident, pending a separate cleanup decision.
Detail, so a later reader does not mistake any of these for an oversight:
get_person_by_email (persons/registry.py) is now zero-caller. It was removed from the sign-in path only; the function itself is shared code and was left in place — deleting it would have widened this change beyond the sign-in route contract.db is now an unused parameter on begin_login. It was retained to keep the change scoped to email removal; dropping it would fan out to every begin_login call site.LoginBeginRequest is now an empty model, retained as the (empty) request-body contract the Operator Layer posts. The seed change called for removing email from the model, not removing the model.These three are one separate cleanup decision, to be taken on their own.
tests/components/tags/TagChipArea.test.tsx (a ProjectTag.project_id drift) predate this change and are unrelated to sign-in. Flagged, not fixed — their own decision.DUNIN7 — Done In Seven LLC — Miami, Florida Loomworks — Sign-in Email Conformance — Change Request — v0.1 — 2026-06-12