Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
145 commits
Select commit Hold shift + click to select a range
6883bfb
docs(04): research PAS boundary phase
chris-adam Jul 31, 2026
5b4d19c
docs(04): add validation strategy
chris-adam Jul 31, 2026
8102f97
docs(04): create phase 4 plans
chris-adam Jul 31, 2026
fb41f24
docs(04): track the phase 4 pattern map
chris-adam Jul 31, 2026
84c285b
docs(04): record phase 4 planned in STATE.md
chris-adam Jul 31, 2026
a9838e2
feat(04-01): move 2FA redirect from authenticateCredentials to IPubBe…
chris-adam Jul 31, 2026
b5468aa
test(04-01): assert the empty body over a real HTTP round trip
chris-adam Jul 31, 2026
db8cc18
docs(04-01): complete pas-boundary redirect relocation plan
chris-adam Jul 31, 2026
b3f5e18
feat(04-02): assert PAS plugin ordering with movePluginsTop, re-asser…
chris-adam Jul 31, 2026
3b2c6d7
docs(04-02): record decision to keep credentials_basic_auth active
chris-adam Jul 31, 2026
09a0dc6
docs(04-02): complete PAS plugin ordering plan
chris-adam Jul 31, 2026
2ab4998
feat(04-03): add IChallengePlugin.challenge for the Unauthorized path
chris-adam Jul 31, 2026
5161203
test(04-03): one veto assertion per credentials extractor, plus excep…
chris-adam Jul 31, 2026
6e8b557
docs(04-03): complete PAS boundary unauthorized-challenge plan
chris-adam Jul 31, 2026
fd1e854
docs(04-04): document Zope-root boundary and Basic Auth consequence (…
chris-adam Jul 31, 2026
6bd4264
docs(04-04): changelog phase 4 -- PAS boundary rework as shipped
chris-adam Jul 31, 2026
cdf580f
docs(04-04): complete DOC-01/DOC-02 README + changelog plan
chris-adam Jul 31, 2026
cb0c0e0
docs(04): add code review report
chris-adam Jul 31, 2026
9718410
test(04): add verification report and persist human verification item…
chris-adam Jul 31, 2026
7afb267
test(04): complete UAT - 1 passed, 0 issues
chris-adam Jul 31, 2026
03798aa
docs(phase-04): add security threat verification
chris-adam Jul 31, 2026
3eb1caa
docs(phase-04): complete phase and advance to phase 5
chris-adam Jul 31, 2026
b8cd170
docs(phase-04): update validation strategy after execution audit
chris-adam Jul 31, 2026
f01d214
docs(phase-05): research drift, replay and lockout domain
chris-adam Jul 31, 2026
1b20955
docs(phase-05): add validation strategy and record lockout scope deci…
chris-adam Jul 31, 2026
7546aeb
docs(05): create phase plan
chris-adam Jul 31, 2026
0a6f4bb
docs(05): commit the pattern map the plans reference
chris-adam Jul 31, 2026
06e6fb3
docs(05): record phase planned, annotate roadmap wave dependencies
chris-adam Jul 31, 2026
bb528fe
feat(05-01): declare lockout counters and wire the lock gate into tok…
chris-adam Jul 31, 2026
4993047
test(05-01): prove the memberdata/registry declarations actually took…
chris-adam Jul 31, 2026
bd8a195
test(05-01): prove the lock is not an oracle, expires, resets, and su…
chris-adam Jul 31, 2026
52ee7a6
docs(05-01): complete lockout substrate and gate plan
chris-adam Jul 31, 2026
69ea86d
feat(05-02): accept one step of clock drift, refuse a replayed TOTP code
chris-adam Jul 31, 2026
43fdd53
test(05-02): prove the TOTP replay rejection is logged without a user…
chris-adam Jul 31, 2026
fc4a7b3
docs(05-02): complete drift and replay plan
chris-adam Jul 31, 2026
b4139b1
feat(05-03): meter @@reset-bar-code with the shared lockout counter
chris-adam Jul 31, 2026
bd8e44f
test(05-03): prove the reset path is metered and the lock has no bypass
chris-adam Jul 31, 2026
e7ed383
docs(05-03): record phase 5 in CHANGES.rst, including the profile-imp…
chris-adam Jul 31, 2026
a691c4a
docs(05-03): fill the MFA-05/06/07 rows plan 05-02 left as TBD
chris-adam Jul 31, 2026
ed88c45
docs(05-03): complete reset-bar-code lockout plan
chris-adam Jul 31, 2026
972ae68
docs(05): add code review report
chris-adam Jul 31, 2026
27a65b1
docs(phase-05): add verification report, revert premature Complete re…
chris-adam Jul 31, 2026
d94d2ac
docs(05-04): plan the CR-01 lockout-oracle gap closure
chris-adam Aug 1, 2026
ff28800
fix(05-04): move lockout gate behind signature check in token.py
chris-adam Aug 1, 2026
eee1d29
test(05-04): prove locked/unlocked/unknown accounts are indistinguish…
chris-adam Aug 1, 2026
6a3a8ef
docs(05-04): complete CR-01 lockout-oracle gap closure plan
chris-adam Aug 1, 2026
b1a2ecf
docs(05): re-review code after 05-04 gap closure
chris-adam Aug 1, 2026
bcba7b1
docs(phase-05): re-verify after 05-04; revert premature MFA-08 Complete
chris-adam Aug 1, 2026
f580db2
docs(05): plan 05-05 gap closure for the reset-bar-code lock-state or…
chris-adam Aug 1, 2026
1f373e7
docs(05-05): apply plan-checker fixes
chris-adam Aug 1, 2026
21d5572
test(05-05): add failing test for reset-bar-code lock-state oracle
chris-adam Aug 1, 2026
be31592
feat(05-05): close the reset-bar-code lock-state oracle (MFA-08)
chris-adam Aug 1, 2026
deaf3d0
docs(05-05): record MFA-08 not-an-oracle reset-path row in 05-VALIDAT…
chris-adam Aug 1, 2026
cef6904
docs(05-05): complete reset-bar-code lock-state oracle gap closure plan
chris-adam Aug 1, 2026
de07713
docs(05): re-review code after 05-05 gap closure
chris-adam Aug 1, 2026
626bcda
test(05): persist human verification items as UAT
chris-adam Aug 1, 2026
b0baad8
docs(05): declare no external API integration in COVERAGE.md
chris-adam Aug 3, 2026
a14b012
fix: keep the bar-code reset request on its own form instead of the s…
chris-adam Aug 3, 2026
ccf9f95
test(05): record two field findings from server.dmsmail testing
chris-adam Aug 3, 2026
6634113
fix(05): keep lockout counters off the user-profile schema
chris-adam Aug 3, 2026
dc1b2d4
test(05): record the profile-form crash as gap G-05-A, correct the F-…
chris-adam Aug 3, 2026
452b66c
fix: pin both JS registrations below jQuery in jsregistry.xml
chris-adam Aug 3, 2026
dccc34a
test(05): UAT test 1 passes; close out the F-1 JavaScript finding
chris-adam Aug 3, 2026
a123bd7
test(05): UAT test 2 passes; JS ordering fix confirmed on the deployment
chris-adam Aug 3, 2026
9f8ea3b
test(05): UAT test 3 passes, replay-on-reuse recorded as intended
chris-adam Aug 3, 2026
97d217f
test(05): UAT test 4 passes -- token endpoint leaks no lock state thr…
chris-adam Aug 3, 2026
6d5c370
test(05): revert UAT test 4 to pending -- both proxy comparisons ran …
chris-adam Aug 3, 2026
aea4854
test(05): record the real cause of the tests 4 and 5 404s
chris-adam Aug 3, 2026
e23ffff
test(05): reject the GET-based attempt at tests 4 and 5 -- wrong HTTP…
chris-adam Aug 3, 2026
0ab2707
test(05): complete UAT -- 6 passed, 0 issues
chris-adam Aug 3, 2026
446b83b
docs(phase-05): add security threat verification
chris-adam Aug 3, 2026
d716ac1
docs(05): verification status human_needed -> passed
chris-adam Aug 3, 2026
1800741
docs(phase-05): complete phase, transition to phase 6
chris-adam Aug 3, 2026
8efa3ab
docs(05): correct stale "Gaps Found" traceability rows for MFA-05 to …
chris-adam Aug 3, 2026
a605aa1
docs(phase-05): evolve PROJECT.md after phase completion
chris-adam Aug 3, 2026
993afb9
docs(06): research recovery-codes phase domain
chris-adam Aug 3, 2026
f8fb5ab
docs(phase-6): add validation strategy
chris-adam Aug 3, 2026
73f4074
docs(06): create phase plan
chris-adam Aug 3, 2026
9314b28
docs(06): record phase 6 planned, add pattern map
chris-adam Aug 3, 2026
429a873
feat(06-01): recovery code login end-to-end (tracer)
chris-adam Aug 3, 2026
f6d74e5
test(06-01): pin the recovery-code storage contract
chris-adam Aug 3, 2026
e0e0724
docs(06-01): complete recovery-code substrate plan
chris-adam Aug 3, 2026
dc9deb5
feat(06-02): issue recovery codes at enrollment, render once in same …
chris-adam Aug 3, 2026
6c035ac
feat(06-02): add regeneration portal action, no new view
chris-adam Aug 3, 2026
fdfde6c
test(06-02): prove issue-once, never-again, and regeneration invalida…
chris-adam Aug 3, 2026
1f8d7a4
docs(06-02): complete recovery-code enrollment display and regenerati…
chris-adam Aug 3, 2026
6f5c6c0
feat(06-03): warn on three or fewer recovery codes remaining
chris-adam Aug 4, 2026
623fa04
test(06-03): assert the shared lockout counter, the low-count warning…
chris-adam Aug 4, 2026
00091e7
test(06-03): extend the MFA-12 guard to this phase's new writers
chris-adam Aug 4, 2026
26c14ab
docs(06-03): complete lockout-sharing and low-count warning plan
chris-adam Aug 4, 2026
a6b06ec
docs(06): add code review report
chris-adam Aug 4, 2026
c1e59d1
test(06): persist human verification items as UAT
chris-adam Aug 4, 2026
ce6ce0e
test(06): complete UAT - 1 passed, 0 issues
chris-adam Aug 4, 2026
cddd826
docs(phase-06): add security threat verification
chris-adam Aug 4, 2026
c218a89
docs(phase-06): complete phase execution
chris-adam Aug 4, 2026
bf8819b
docs(07): research phase domain
chris-adam Aug 4, 2026
5c4958a
docs(phase-7): add validation strategy
chris-adam Aug 4, 2026
886ad1f
docs(07): add pattern map
chris-adam Aug 4, 2026
aed3b9a
docs(07): create phase plan — 4 plans, tracer-first, waves 1-4
chris-adam Aug 4, 2026
56afb71
docs(07): add artifacts and removals sections to 07-04
chris-adam Aug 4, 2026
04cc9df
docs(07): record planning completion for phase 7
chris-adam Aug 4, 2026
df39c1d
feat(07-01): restore stock login overlay by deleting vendored popupfo…
chris-adam Aug 4, 2026
6c14064
feat(07-01): delete vendored login_form.cpt and close both ends of th…
chris-adam Aug 4, 2026
26d413a
docs(07-01): complete restore-stock-login-overlay plan
chris-adam Aug 4, 2026
925ea17
feat(07-02): convert live templates to descriptors and delete skin me…
chris-adam Aug 4, 2026
726d24d
test(07-02): pin skin-layer deletion with test_skin_layer_is_removed
chris-adam Aug 4, 2026
23b4594
docs(07-02): complete convert-live-templates-and-delete-skin-mechanis…
chris-adam Aug 4, 2026
31cb9a9
feat(07-03): ship a scoped uninstall profile for main.js and main.css
chris-adam Aug 4, 2026
0ca0566
docs(07-03): correct the two overridden-asset claims and add the Phas…
chris-adam Aug 4, 2026
548edca
docs(07-03): complete uninstall-profile-and-coexistence-proof plan
chris-adam Aug 4, 2026
184f053
fix(quick-260805-f5m): stop the user-created subscriber breaking site…
chris-adam Aug 5, 2026
bd1323a
docs(quick-260805-f5m): record COEX-10, the quick task, and two open …
chris-adam Aug 5, 2026
3fd0d51
docs(07-04): record both manual verifications and the enrolment gap
chris-adam Aug 5, 2026
a83313d
docs(phase-07): complete phase execution
chris-adam Aug 5, 2026
3b7e3e2
docs(08): capture phase context
chris-adam Aug 5, 2026
2a0820b
docs(state): record phase 8 context session
chris-adam Aug 5, 2026
311b4b8
docs(08): research coverage instrumentation and test-layer migration
chris-adam Aug 5, 2026
7aea6d9
docs(phase-8): add validation strategy
chris-adam Aug 5, 2026
cf6094d
docs(08): create phase plans
chris-adam Aug 5, 2026
5466586
docs(08): record planning state, wave annotations, and pattern map
chris-adam Aug 5, 2026
352a612
feat(08-01): repair coverage instrument end to end
chris-adam Aug 5, 2026
cab1c1d
docs(08-01): complete coverage instrument repair plan
chris-adam Aug 5, 2026
9caf656
chore(08-01): gitignore htmlcov/ coverage-html build output
chris-adam Aug 5, 2026
c1b17e6
feat(08-02): give the test layer its own install hook, drop ZSERVER f…
chris-adam Aug 5, 2026
d64788d
feat(08-02): delete the quickinstaller install path, rewrite installe…
chris-adam Aug 5, 2026
f999920
docs(08-02): complete test-layer install mechanism plan
chris-adam Aug 5, 2026
deea135
docs(08-02): correct SUMMARY requirements-completed for QUAL-05's two…
chris-adam Aug 5, 2026
4c6be92
feat(08-03): move every test class to the functional layer, retire th…
chris-adam Aug 5, 2026
b20be96
docs(08-03): complete test-layer migration plan
chris-adam Aug 5, 2026
df830be
feat(08-04): test the per-user 2FA-disable view's three branches
chris-adam Aug 5, 2026
6f84f1e
feat(08-04): test the bulk disable view and two untested save-handler…
chris-adam Aug 5, 2026
6fdafab
feat(08-04): close the branch-coverage gap above 90% and gate CI on it
chris-adam Aug 5, 2026
2ecf9f5
docs(08-04): complete branch-coverage-gap-closure plan
chris-adam Aug 5, 2026
c1ff26c
style(08-05): mechanical import sweep with bin/isort -rc -y src/
chris-adam Aug 5, 2026
a3f6643
style(08-05): hand-fix remaining lint findings, clear the gate (QUAL-06)
chris-adam Aug 5, 2026
a9f0374
docs(08-05): correct CLAUDE.md and TESTING.md to match the cleared ga…
chris-adam Aug 5, 2026
76e5fb2
docs(08-05): add plan summary
chris-adam Aug 5, 2026
d955a39
docs(08-05): append self-check results to summary
chris-adam Aug 5, 2026
d49cb00
docs(08-05): complete lint-gate-cleanup-and-documentation-correction …
chris-adam Aug 5, 2026
d46604e
docs(08): add code review report
chris-adam Aug 5, 2026
da44189
test(08): add verification report and persist human verification item…
chris-adam Aug 5, 2026
1d7aa73
test(08): complete UAT - 1 passed, 0 issues
chris-adam Aug 6, 2026
301d0c3
docs(phase-08): add security threat verification
chris-adam Aug 6, 2026
16b163f
docs(phase-08): complete phase, evolve PROJECT.md and STATE.md for mi…
chris-adam Aug 6, 2026
f431c7f
docs(08): mark verification passed after UAT and security review
chris-adam Aug 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
[report]
include = src/imio/googleauthenticator/*
[run]
source = src/imio/googleauthenticator
omit = */tests/*
branch = True
2 changes: 1 addition & 1 deletion .github/workflows/package-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ jobs:
buildout_config_file: test-4.3.cfg
requirements_file: requirements-4.3.txt
runner_label: gha-runners-docs-py2
test_command: 'bin/test -t !robot'
test_command: 'bin/test-coverage -t !robot'
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pip-log.txt

# Unit test / coverage reports
.coverage
htmlcov
.tox
nosetests.xml

Expand Down
269 changes: 191 additions & 78 deletions .planning/PROJECT.md

Large diffs are not rendered by default.

164 changes: 84 additions & 80 deletions .planning/REQUIREMENTS.md

Large diffs are not rendered by default.

128 changes: 108 additions & 20 deletions .planning/ROADMAP.md

Large diffs are not rendered by default.

158 changes: 133 additions & 25 deletions .planning/STATE.md

Large diffs are not rendered by default.

246 changes: 137 additions & 109 deletions .planning/codebase/TESTING.md

Large diffs are not rendered by default.

560 changes: 560 additions & 0 deletions .planning/phases/04-pas-boundary/04-01-PLAN.md

Large diffs are not rendered by default.

185 changes: 185 additions & 0 deletions .planning/phases/04-pas-boundary/04-01-SUMMARY.md

Large diffs are not rendered by default.

536 changes: 536 additions & 0 deletions .planning/phases/04-pas-boundary/04-02-PLAN.md

Large diffs are not rendered by default.

145 changes: 145 additions & 0 deletions .planning/phases/04-pas-boundary/04-02-SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
---
phase: 04-pas-boundary
plan: 02
subsystem: auth
tags: [pas, pluginregistry, movePluginsTop, genericsetup, plone4, python2]

# Dependency graph
requires:
- phase: 04-01
provides: "the decide-only authenticateCredentials / IPubBeforeCommit redirect this plugin's ordering protects"
provides:
- "movePluginsTop(interface, [plugin.getId()]) as the explicit, re-asserted-on-every-reinstall ordering mechanism for _add_plugin"
- "test_plugin_is_first_authenticator, test_reapply_profile_keeps_plugin_first_and_unique, test_plugin_declares_no_challenge_protocol in tests/test_setuphandlers.py"
- "a recorded, repository-visible decision to keep credentials_basic_auth active (comment block above _add_plugin in setuphandlers.py)"
affects: [04-03, 04-04]

# Tech tracking
tech-stack:
added: []
patterns:
- "Split idempotency guard: object creation guarded by objectIds() membership (runs once), activation guarded by listPluginIds() membership (raises KeyError if skipped), ordering unconditional (movePluginsTop is self-idempotent) -- so reinstall is a real recovery for a displaced plugin"
- "Repository-visible decision record: a checkpoint answer to 'should we mutate a plugin we don't own' is written as a dated comment naming the evidence and its gap, not left implicit in a planning artifact only"

key-files:
created: []
modified:
- src/imio/googleauthenticator/setuphandlers.py
- src/imio/googleauthenticator/tests/test_setuphandlers.py

key-decisions:
- "MFA-03 checkpoint (Task 2), answered by human operator Chris on 2026-07-31: KEEP credentials_basic_auth active. Do NOT deactivate it. Deactivating a plugin this package does not own would be the same global-mutation-with-no-uninstall-counterpart anti-pattern the ROADMAP already condemns for Phase 7's popupforms.js. The research covered three iMio repositories -- imio.dms.mail, server.dmsmail, industrialisation -- and found no live dependency on HTTP Basic Auth against this Plone site's own acl_users, but that search was explicitly non-exhaustive (RESEARCH Assumptions Log A1), so a site-wide deactivation's blast radius if wrong is a silent automation failure with no signal in this repository."
- "Consequence accepted with the 'keep' decision: correctness stays order-dependent. test_plugin_is_first_authenticator is therefore the ONLY control standing between a future plugin reorder and a Basic Auth bypass, and must never be weakened or deleted -- stated in both the setuphandlers.py comment and this summary so it survives context handoff."
- "No profiles/uninstall/ counterpart is owed by this plan or by Phase 7 -- that obligation only existed under the 'deactivate' branch and does not apply here."
- "Guidance for plan 04-03: test_basic_auth_veto must be written asserting the veto through the normal _extractUserIds() path (credentials_basic_auth extractor is still registered and active), NOT through a direct authenticateCredentials call bypassing extraction. Had 'deactivate' been chosen, _extractUserIds would produce no credentials at all and a veto assertion through that path would pass for the wrong (vacuous) reason -- that concern does not apply under 'keep'."

patterns-established:
- "Pattern: an ordering invariant this package's whole security model rests on is asserted directly (test_plugin_is_first_authenticator) rather than inferred from an implementation detail (movePluginsDown bubbling the most-recent entry to index 0)"

requirements-completed: [MFA-03]

coverage:
- id: D1
description: "_add_plugin uses movePluginsTop(interface, [plugin.getId()]) instead of the movePluginsDown accident, and re-asserts ordering on every profile application (not only first install)"
requirement: "MFA-03"
verification:
- kind: unit
ref: "tests/test_setuphandlers.py#test_plugin_is_first_authenticator"
status: pass
- kind: unit
ref: "tests/test_setuphandlers.py#test_reapply_profile_keeps_plugin_first_and_unique"
status: pass
human_judgment: false
- id: D2
description: "GoogleAuthenticatorPlugin declares no protocol attribute, so PAS's challenger-protocol fallback keeps it out of HTTPBasicAuthHelper's protocol group"
requirement: "MFA-03"
verification:
- kind: unit
ref: "tests/test_setuphandlers.py#test_plugin_declares_no_challenge_protocol"
status: pass
human_judgment: false
- id: D3
description: "credentials_basic_auth deactivation decision settled by human checkpoint (keep active) and recorded visibly in the repository, not left as an unstated assumption"
verification: []
human_judgment: true
rationale: "The decision itself (keep vs. deactivate) was a human judgment call weighing an explicitly non-exhaustive evidence search against a security hardening; no automated test can validate that the recorded rationale is complete, only that the code matches what was decided (covered by the grep-based acceptance criteria run below)."

# Metrics
duration: ~15min (continuation from checkpoint; Task 1 duration recorded separately)
completed: 2026-07-31
status: complete
---

# Phase 04 Plan 02: PAS Plugin Ordering + Basic Auth Decision Summary

**movePluginsTop replaces an accidental movePluginsDown ordering side-effect, re-asserted on every profile reinstall, plus a human-recorded decision to keep credentials_basic_auth active rather than deactivate it.**

## Performance

- **Duration:** Task 1 ~70min (per STATE.md per-plan metrics, recorded by the prior executor agent before the checkpoint); Task 3 (this continuation) ~15min
- **Tasks:** 3 (Task 1: auto: ordering fix; Task 2: checkpoint:decision; Task 3: auto: implement recorded choice)
- **Files modified:** 2 (`src/imio/googleauthenticator/setuphandlers.py`, `src/imio/googleauthenticator/tests/test_setuphandlers.py`)

## Accomplishments

- `_add_plugin` in `setuphandlers.py` now calls `pas.plugins.movePluginsTop(interface, [plugin.getId()])` instead of the previous `movePluginsDown(interface, listPlugins(interface)[:-1])`, which only reached index 0 because the plugin happened to be the most recently activated entry.
- The idempotency guard was split: object creation (`_setObject`) is still guarded by `pluginid not in pas.objectIds()` and runs once; activation is separately guarded by `listPluginIds` membership (because `activatePlugin` raises `KeyError: 'Duplicate plugin id'` for an already-active plugin); ordering (`movePluginsTop`) now runs unconditionally on every profile application, because it is self-idempotent for an already-first plugin. This makes reinstalling the profile a real recovery if a third-party add-on has displaced the plugin from index 0.
- Three new tests in `test_setuphandlers.py`: `test_plugin_is_first_authenticator` (the security control itself), `test_reapply_profile_keeps_plugin_first_and_unique` (proves the re-assert path survives a deliberate displacement + reinstall, and does not raise or duplicate on a second `applyProfile`), and `test_plugin_declares_no_challenge_protocol` (Open Question 3 — asserts the plugin has no `protocol` attribute, keeping it out of `HTTPBasicAuthHelper`'s protocol group).
- The ROADMAP's open `credentials_basic_auth` decision is settled: **keep it active**. Recorded as a dated comment block directly above `_add_plugin` in `setuphandlers.py`, naming the date, the three repositories searched, the veto test that still protects the path, and the load-bearing consequence for `test_plugin_is_first_authenticator`.

## Task Commits

Each task was committed atomically:

1. **Task 1: movePluginsTop, re-asserted on every profile application, with the ordering test as the control** - `b3f5e18` (feat) — completed by the prior executor agent before the checkpoint.
2. **Task 2: checkpoint:decision — deactivate `credentials_basic_auth`?** — no commit (decision task); answered by human operator Chris on 2026-07-31, selecting option B (keep).
3. **Task 3: Implement the recorded choice ("keep" branch)** - `3b2c6d7` (docs) — recorded the decision as a comment block; no code behavior change, no new test (per plan's "keep"/"defer" branch instructions).

**Plan metadata:** (this commit, following SUMMARY.md write)

## Files Created/Modified

- `src/imio/googleauthenticator/setuphandlers.py` — `_add_plugin` restructured (Task 1: `movePluginsTop`, split guard); dated decision-record comment added above `_add_plugin` (Task 3).
- `src/imio/googleauthenticator/tests/test_setuphandlers.py` — three new test methods and `self.pas` added to `setUp` (Task 1). No changes in Task 3 — the "keep" branch adds no test per the plan's own instructions (the `deactivate` branch was the one requiring `test_basic_auth_extractor_is_deactivated`).

## Decisions Made

**MFA-03 checkpoint decision (Task 2), answered by human operator Chris on 2026-07-31: KEEP `credentials_basic_auth` active. Do NOT deactivate it.**

- **Repositories the evidence covered (all three, per 04-RESEARCH.md Assumptions Log A1):** `imio.dms.mail`, `server.dmsmail`, `industrialisation`. No live dependency on HTTP Basic Auth against this Plone site's own `acl_users` was found — `server.dmsmail`'s only `webdav-address` setting is commented out everywhere it appears; no XML-RPC client targeting the site was found; `scripts/run-copy-missing-blobs.py` authenticates *outward* to a different remote site, not into this one; and `pack_zeo.sh` (from `industrialisation`) targets the Zope-root `Control_Panel`, above any Plone site's `acl_users`, so it is unaffected either way.
- **The gap, stated explicitly:** the search covered three repositories, not every iMio repository, and no test can prove the absence of an external consumer. That gap is the reason the "keep" option's stated cost — correctness depending on plugin ordering, enforced only in CI and not at request time — was accepted rather than eliminated.
- **Rationale for "keep" over "deactivate":** deactivating a plugin this package does not own is a site-wide mutation with no uninstall counterpart, structurally the same anti-pattern the ROADMAP condemns for `popupforms.js` in Phase 7. Choosing it would have incurred a `profiles/uninstall/` obligation for a hardening whose necessity the evidence could not confirm.
- **What "keep" does NOT give up:** the Basic Auth credentials path is still vetoed. Plan 04-03's `test_basic_auth_veto` asserts that directly. No global mutation occurs, no uninstall obligation is owed by this plan or by Phase 7.
- **Load-bearing consequence, stated in both the code comment and here:** `test_plugin_is_first_authenticator` is now the *only* thing standing between a future plugin reorder and a Basic Auth bypass. It must never be weakened or deleted.

**Guidance for plan 04-03 (`test_basic_auth_veto`):** write it asserting the veto through the normal `_extractUserIds()` path — `credentials_basic_auth` remains registered and active, so the extractor still produces a credentials dict that this plugin's in-place wipe (per `authenticateCredentials`) must blind. Do **not** write it as a direct `authenticateCredentials` call that bypasses extraction; that concern only applied under the (unselected) `deactivate` branch, where `_extractUserIds` would produce no credentials at all and a veto assertion would pass for the wrong, vacuous reason.

## Deviations from Plan

None - Task 3 executed exactly as the plan's "keep"/"defer" branch specifies: no deactivation code, a comment block recording the decision, date, repositories, veto-test pointer, and the load-bearing consequence for the ordering test.

## Issues Encountered

None. The checkpoint answer was unambiguous (option B, "keep") and the plan's own text specified exactly what the "keep" branch requires — no interpretation was needed.

## User Setup Required

None - no external service configuration required.

## Next Phase Readiness

- Plan 04-03 can proceed: `test_basic_auth_veto` should be written against the normal `_extractUserIds()` path (guidance above), and it can rely on `test_plugin_is_first_authenticator` (this plan) as the ordering control it composes with.
- Plan 04-04 (DOC-02) has what it needs: the decision id (`keep`), its date (2026-07-31), the three repositories covered, and the explicit statement that no `profiles/uninstall/` counterpart is owed.
- No blockers carried forward from this plan. The `credentials_basic_auth` ROADMAP open decision is now closed for this milestone.

---
*Phase: 04-pas-boundary*
*Completed: 2026-07-31*

## Self-Check: PASSED

- FOUND: src/imio/googleauthenticator/setuphandlers.py
- FOUND: src/imio/googleauthenticator/tests/test_setuphandlers.py
- FOUND: .planning/phases/04-pas-boundary/04-02-SUMMARY.md
- FOUND commit: b3f5e18 (Task 1)
- FOUND commit: 3b2c6d7 (Task 3)
Loading
Loading