From 5492c79fd360691c84899e98b6dc37c87a82d07b Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Tue, 25 Aug 2026 13:50:32 -0500 Subject: [PATCH] docs(adr): ADR 0068 gains the acceptance criterion for the reset-MFA self-exclusion (BACKLOG #1022) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Lander caught this reviewing PR 590: #1022's fix added a refusal to a shipped route and ADR 0068's formal criteria run AC-10 through AC-16 with nothing added for it. Two of the three record corrections that item owed were covered; this is the third. AC-17 states the behaviour in the file's own EARS form and carries its test, so the criterion is checkable rather than descriptive. It also says what the refusal does NOT do -- cross-user reset is unaffected and remains the §2 recovery path -- because the risk with this particular AC is a reader taking it as a narrowing of the one path that exists when a user has lost every factor. WHY IT SITS BESIDE AC-10 CONCEPTUALLY. AC-10 refuses the last-factor case on the self-service deletion path. The admin reset was a THIRD route to zero factors that reached neither AC-10 nor its TOTP sibling, which is what #1022 is about; AC-17 is where that now has a criterion. THE CITATION RESOLVES ON THE ITEM'S OWN BRANCH, verified rather than assumed: `git show claude/builder-1-1022-self-exclusion:tests/test_api_auth.py | grep -c test_admin_reset_mfa_refuses_to_target_the_caller` returns 1. It is NOT on this branch, and that is deliberate -- see below. *** WHY THIS IS A SEPARATE BRANCH RATHER THAN A COMMIT ON claude/builder-1-1022-self-exclusion. *** The worktree gate REFUSED to switch this worktree onto that branch, and it was right to: switching a linked worktree onto an existing branch is the hijack shape it exists to stop. Its suggested remedy is a dedicated worktree, which conflicts with one-worktree-per-session, and the alternative -- having the Lander commit it -- fails the claim gate, because #1022 is claimed by this worktree and the committer must hold the claim. So this is cut off origin/main with only the ADR edit. LANDER: it belongs with PR 590. Cherry-pick it onto that branch if that is easy for you, or take it as its own PR against the same item -- your call. I did NOT route around the gate to avoid the split. VERIFIED, scope named: pytest 62 passed test_dangling_citation_check + test_docs_security_pathways pytest 29 passed test_asvs_residual_lint scripts/docs/dangling_citation_check.py -- no new dangling citation from this edit docs-only change; no code, no test behaviour altered NOT a full-suite run Co-Authored-By: Claude Opus 5 --- docs/adr/0068-browser-webauthn-passkeys-offloopback.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/adr/0068-browser-webauthn-passkeys-offloopback.md b/docs/adr/0068-browser-webauthn-passkeys-offloopback.md index f6f5ed09..af3ff4fe 100644 --- a/docs/adr/0068-browser-webauthn-passkeys-offloopback.md +++ b/docs/adr/0068-browser-webauthn-passkeys-offloopback.md @@ -317,6 +317,11 @@ advisory-only, restated). SYSTEM SHALL log a startup advisory naming `admin_reset_mfa` and render a legible "passkeys unavailable on this install" notice at reauth — never a silent loop. → `tests/test_webui.py::test_webauthn_extra_less_renders_notice` + `tests/test_webui.py::test_reauth_extra_less_with_credentials_renders_notice` +- **AC-17** — IF an operator targets their OWN account on `POST /users/{user_id}/reset-mfa`, THEN + THE SYSTEM SHALL refuse (400) and direct them to the self-service MFA settings; cross-user reset + is unaffected and remains the recovery path of §2. AC-10 refuses the last-factor case on the + self-service paths, and this closes the third route to zero factors that reached neither. + → `tests/test_api_auth.py::test_admin_reset_mfa_refuses_to_target_the_caller` ## Options considered