docs(19.6-03): release and adoption up to the sm-demo-app checkpoint

This commit is contained in:
2026-10-26 13:24:17 +01:00
parent 5a6461123c
commit 4002beafb6
2 changed files with 255 additions and 145 deletions

View File

@@ -2,7 +2,7 @@
phase: 19.6-two-factor-auth-and-device-flow-in-sm-user-plugin
plan: 03
subsystem: auth
tags: [sm-mfa-plugin, webauthn, go-webauthn, passkeys, passwordless, sm-sociallogin-plugin, login-gate, vue-demo-app, totp-ui, draft]
tags: [sm-mfa-plugin, webauthn, go-webauthn, passkeys, passwordless, sm-sociallogin-plugin, login-gate, vue-demo-app, totp-ui, release, draft]
requires:
- phase: 19.6-01
@@ -13,33 +13,45 @@ provides:
- WebAuthn security keys as a second factor in sm-mfa-plugin (go-webauthn v0.18.2, standard base64url shapes, D-08)
- golem15_user_webauthn_credentials (migration 202610220006) with credential_data and passwordless
- Passwordless passkey sign-in behind two_factor_passwordless_login (D-14)
- D-09 social-login gate in sm-sociallogin-plugin, prepared as commit a0161d7 in a scratch clone (not yet in the real checkout, see Blockers)
- Demo two-factor UI in vue-demo-app (TOTP setup, recovery codes, disable, login challenge; D-20)
- D-09 social-login gate in sm-sociallogin-plugin v0.1.4
- Demo two-factor UI in vue-demo-app (TOTP setup, recovery codes, disable, login challenge; D-20), pushed
- Releases: sm-user-plugin v0.1.12, sm-mfa-plugin v0.1.0 (new private repository), sm-sociallogin-plugin v0.1.4
- sm-bm-app mounts golem15.mfa (API only) at user v0.1.12 and sociallogin v0.1.4, pushed
- fonoteka.go at sm-user-plugin v0.1.12 with its guard tests updated (local commit)
affects: [19.6-04, sm-demo-app, sm-bm-app, fonoteka.go, vue-demo-app, sm-sociallogin-plugin]
actuals:
tokens: 35354 # chars/4 over the added lines of this plan's diffs in the three repositories (go.sum and go.work.sum excluded)
tasks: 3 # Tasks 1-3 of 6; Task 4 is the open checkpoint
commits: 0 # MEASURED in summercms.go before this draft's commit: git rev-list --count 5d78f53..HEAD (code lives in other repositories)
sm_mfa_plugin_commits: 2 # MEASURED: git -C ../sm-mfa-plugin rev-list --count 8a63f6f..HEAD
vue_demo_app_commits: 1 # MEASURED: git -C ../sm-demo-app/vue-demo-app rev-list --count 9c247ab..HEAD
sm_sociallogin_plugin_commits: 0 # the real checkout is unchanged; 1 commit (a0161d7) in the scratch clone
tokens: 37360 # MEASURED chars/4 over added lines: Tasks 1-3 35354, plus 33ad164, b752177, sm-bm-app aafbcce..ebb2266, fonoteka.go 06bc9d0..95fd58a (go.sum and go.work.sum excluded)
tasks: 5 # Tasks 1-5 done; Task 6 parts (2) and (3) done, part (1) sm-demo-app open at a checkpoint
commits: 0 # MEASURED in summercms.go before this draft's commit: git rev-list --count 5a64611..HEAD (code lives in other repositories)
sm_mfa_plugin_commits: 3 # MEASURED: git -C ../sm-mfa-plugin rev-list --count 8a63f6f..HEAD
sm_sociallogin_plugin_commits: 2 # MEASURED: git -C ../sm-sociallogin-plugin rev-list --count 4d3b2dd..HEAD
vue_demo_app_commits: 1 # MEASURED: git -C ../sm-demo-app/vue-demo-app rev-list --count 9c247ab..HEAD
sm_bm_app_commits: 3 # MEASURED: git -C ../sm-bm-app rev-list --count aafbcce..HEAD
fonoteka_go_commits: 1 # MEASURED: git -C ../fonoteka.go rev-list --count 06bc9d0..HEAD
sm_demo_app_commits: 0 # Task 6 part (1) not started (checkpoint)
plan_head_before: 5d78f53ddc890ef53fe52da1828f3f88d9923270
plan_head_after: 5d78f53ddc890ef53fe52da1828f3f88d9923270
plan_head_after: 5a64611
sm_mfa_plugin_head_before: 8a63f6f
sm_mfa_plugin_head_after: 6c8916f
sm_mfa_plugin_head_after: 33ad164
sm_sociallogin_plugin_head_before: 4d3b2dd
sm_sociallogin_plugin_head_after: b752177
vue_demo_app_head_before: 9c247ab
vue_demo_app_head_after: 44a5229
sm_sociallogin_plugin_head: 4d3b2dd # unchanged (v0.1.3 = origin/master); patch prepared on top of it
sm_user_plugin_head: 3b22134 # unchanged, untagged, 5 commits ahead of origin (v0.1.11 = 50da92c)
sm_bm_app_head_before: aafbcce
sm_bm_app_head_after: ebb2266
fonoteka_go_head_before: 06bc9d0
fonoteka_go_head_after: 95fd58a
sm_user_plugin_head: 3b22134 # tagged v0.1.12 and pushed
tech-stack:
added:
- "github.com/go-webauthn/webauthn v0.18.2 (packages webauthn, protocol), sm-mfa-plugin only (approved 2026-10-26); brings fxamacker/cbor/v2, go-webauthn/x, google/go-tpm, tinylib/msgp as indirect modules"
- "github.com/go-webauthn/webauthn v0.18.2 (packages webauthn, protocol), sm-mfa-plugin only (approved 2026-10-26); brings fxamacker/cbor/v2, go-webauthn/x, google/go-tpm, tinylib/msgp as indirect modules (also indirect in sm-bm-app through the mount)"
patterns:
- "WebAuthn ceremony state server-side: registration and passkey state in cooler consumed with Pull; login state on the challenge row"
- "Pure verification helpers (VerifyRegistration, VerifyAssertion) shared by handlers and a no-database smoke test"
- "Virtual authenticator test helper (ECDSA P-256, attestation none, CBOR through go-webauthn's webauthncbor)"
- "Application mount test without a database: route table entry with the owning plugin, a compiled handler, and an anonymous request refused by jwt.auth (sm-bm-app TestMFAMounted)"
key-files:
created:
@@ -53,18 +65,23 @@ key-files:
- ../sm-mfa-plugin/webauthn_smoke_test.go
- ../sm-mfa-plugin/passwordless_smoke_test.go
- ../sm-mfa-plugin/go.work.sum
- ../sm-sociallogin-plugin/social_gate_smoke_test.go
- ../sm-demo-app/vue-demo-app/app/lib/twoFactor.ts
- ../sm-demo-app/vue-demo-app/app/lib/twoFactor.test.ts
- ../sm-demo-app/vue-demo-app/app/composables/useTwoFactor.ts
- ../sm-demo-app/vue-demo-app/app/pages/account/security.vue
- ../sm-bm-app/mfa_test.go
- ../sm-bm-app/plugins/golem15/mfa (submodule)
- .planning/phases/19.6-two-factor-auth-and-device-flow-in-sm-user-plugin/19.6-03-sociallogin-gate.patch
- .planning/phases/19.6-two-factor-auth-and-device-flow-in-sm-user-plugin/deferred-items.md
modified:
- ../sm-mfa-plugin/{go.mod,go.sum,routes.go,plugin.go,README.md,config/config.yaml,lang/en/lang.yaml,lang/pl/lang.yaml}
- ../sm-mfa-plugin/classes/{config.go,methods.go,admin.go,events.go,login.go}
- ../sm-sociallogin-plugin/{classes/flow.go,classes/codes.go,controllers/callback.go,controllers/complete.go,controllers/register_complete.go,lang/en/lang.yaml,lang/pl/lang.yaml,README.md,go.mod,go.sum}
- ../sm-demo-app/vue-demo-app/app/{composables/useDemoAuth.ts,pages/login.vue,components/SiteHeader.vue}
- ../sm-demo-app/vue-demo-app/i18n/locales/{en.json,pl.json}
- "(patch only) ../sm-sociallogin-plugin/{classes/flow.go,classes/codes.go,controllers/callback.go,controllers/complete.go,controllers/register_complete.go,lang/en/lang.yaml,lang/pl/lang.yaml,README.md}, new social_gate_smoke_test.go"
- ../sm-bm-app/{.gitmodules,go.mod,go.sum,go.work,summer.yaml,plugins.gen.go,scripts/build.sh,README.md,plugins/golem15/user,plugins/golem15/sociallogin}
- ../fonoteka.go/{plugins/golem15/user,parity/migrate_test.go,parity/schema_diff_test.go,plugins/golem15/fonoteka/classes/hidden_marshal_test.go,plugins/golem15/fonoteka/phase07_coverage_test.go,plugins/golem15/fonoteka/routes_isolation_test.go}
key-decisions:
- "WebAuthn RP from golem15.mfa.webauthn.rp_id / rp_name / origins (defaults: app.url host, app.name, app.url and golem15.user.frontend_url origins); an IP host or no origin makes WebAuthnFor answer ErrWebAuthnUnavailable, and the routes answer 403 'WebAuthn is not available.'"
@@ -73,12 +90,14 @@ key-decisions:
- "Passkey routes also answer 403 while two_factor_mode is disabled (fail closed), not only while two_factor_passwordless_login is off"
- "Passkey sign-in refuses a ban or suspension from any address (ThrottleStates) and from the request's address (RejectIfThrottled)"
- "A challenged social sign-in does not fire SignedInEvent and does not restore a deactivated account; the identity is stored before the gate"
- "No framework tag needed: sm-mfa-plugin (with the user plugin at 3b22134) and the sociallogin change pass their full suites against the framework's v0.5.0 tag"
- "No framework tag: sm-mfa-plugin and sm-sociallogin-plugin pass against the framework's v0.5.0 tag; sociallogin's require rose from v0.3.1 to v0.5.0 through go mod tidy"
- "Release tags: sm-user-plugin v0.1.12, sm-mfa-plugin v0.1.0, sm-sociallogin-plugin v0.1.4 (the plan's v0.1.6 and v0.1.3 were stale)"
- "fonoteka.go guard tests follow the user plugin across every version the bump crosses (v0.1.10 to v0.1.12), not only this phase's additions, because they were already red at the committed v0.1.10 pointer"
patterns-established:
- "Plugin work in a checkout the executor cannot write: implement and test in a scratch clone with a GOWORK file, export git format-patch, keep the patch in the phase directory"
- "Plugin work in a checkout the executor cannot write: implement and test in a scratch clone with a GOWORK file, export git format-patch, keep the patch in the phase directory, apply with git am once access is granted and verify the tree hash matches"
requirements-completed: [] # draft; the plan's requirements are copied when the plan completes after Task 6
requirements-completed: [] # draft; the plan's requirements are copied when the plan completes after Task 6 part (1)
coverage:
- id: D1
@@ -101,14 +120,13 @@ coverage:
status: pass
human_judgment: false
- id: D3
description: "Social sign-in through the login gate (prepared, not yet in the real sm-sociallogin-plugin checkout)"
description: "Social sign-in through the login gate (sm-sociallogin-plugin a8e3e78, released as v0.1.4)"
requirement: "D-09"
verification:
- kind: integration
ref: "scratch clone a0161d7: social_gate_smoke_test.go#TestSocialGateSmoke (5 subtests) and TestTracerSmoke"
ref: "../sm-sociallogin-plugin/social_gate_smoke_test.go#TestSocialGateSmoke (5 subtests) and TestTracerSmoke, in the real checkout"
status: pass
human_judgment: true
rationale: "The change is not committed in ../sm-sociallogin-plugin yet (owned by jin); it must be applied and the tests re-run there."
human_judgment: false
- id: D4
description: "Demo two-factor UI: TOTP setup with the QR image, recovery codes shown once, turn off, login challenge with TOTP or recovery code"
requirement: "D-20"
@@ -120,76 +138,166 @@ coverage:
ref: "pnpm build; server render of /account/security and /login on the built output (200)"
status: pass
human_judgment: true
rationale: "Look and flow in a browser against a running demo with golem15.mfa mounted (Task 6) are a visual judgement; WebAuthn is not part of the demo UI."
rationale: "Look and flow in a browser against a running demo with golem15.mfa mounted are a visual judgement; the demo mount (Task 6 part 1) is open at the checkpoint."
- id: D5
description: "D-17 adoption: plugins published in order; bm mounts mfa; fonoteka.go green at the new pointer without mfa; demo mount pending"
requirement: "D-17"
verification:
- kind: other
ref: "ls-remote of v0.1.12, v0.1.0, v0.1.4 and the pushed branches; sm-bm-app TestMFAMounted; fonoteka.go vet, -short suites and build"
status: pass
human_judgment: true
rationale: "sm-demo-app part is waiting on the user's decision about another session's uncommitted user pointer."
duration: 40min
duration: 75min
completed: 2026-10-26
status: draft
---
# Phase 19.6 Plan 03: WebAuthn and adoption Summary (draft)
**Status:** draft (checkpoint Task 4). Tasks 1-3 are done. Task 4 waits for the user's release decision. Tasks 5 and 6 run in a continuation.
**Status:** draft at a second checkpoint, during Task 6 part (1).
- **Done:**
- Tasks 1-5.
- Task 6 part (2), sm-bm-app: committed and pushed.
- Task 6 part (3), fonoteka.go: committed locally, not pushed.
- **Not started:** Task 6 part (1), sm-demo-app. Another session's uncommitted edit moved its `plugins/golem15/user` pointer to v0.1.11, and the plan says to stop when another session's edits touch a Task 6 file. See "Open checkpoint".
**Results so far:**
- **sm-mfa-plugin:** WebAuthn security keys as a second factor, using go-webauthn v0.18.2 with server-side ceremony state, and passwordless passkey sign-in behind its setting. Two local commits.
- **sm-sociallogin-plugin:** the social-login gate (D-09) is written and tested in a scratch clone. It is not in the real checkout yet, because that checkout belongs to the user `jin`.
- **vue-demo-app:** the demo's TOTP setup, recovery-code and login-challenge screens. One local commit.
- **Remote:** nothing was pushed, tagged or created.
**Results:**
- **sm-mfa-plugin v0.1.0:** WebAuthn security keys as a second factor (go-webauthn v0.18.2, ceremony state server-side) and passwordless passkey sign-in behind its setting. The new private repository is https://git.golem15.com/golem15/sm-mfa-plugin.
- **sm-sociallogin-plugin v0.1.4:** social sign-in goes through the login gate (D-09).
- **sm-user-plugin v0.1.12:** plan 01's login gates and device authorization, published.
- **vue-demo-app:** the TOTP setup, recovery-code and login-challenge screens are pushed.
- **sm-bm-app:** mounts golem15.mfa (API only), with user v0.1.12 and sociallogin v0.1.4. Pushed.
- **fonoteka.go:** at user v0.1.12, guard tests updated. Local commit only.
## Performance
- **Started:** about 2026-10-26T11:08Z
- **Checkpoint:** 2026-10-26T11:48Z (about 40 min)
- **Tasks:** 3 of 6 (Task 4 is the open checkpoint)
- **Files:**
- sm-mfa-plugin: 23 files, +2358/-37
- vue-demo-app: 9 files, +549/-13
- sociallogin patch: 9 files, +266/-16
- **First checkpoint:** 2026-10-26T11:48Z
- **Continuation:** about 11:55Z to 12:25Z
- **Tasks:** 5 of 6, with Task 6 partly done.
## Task 4 reply
Verbatim: "1. release 2. push demo 3. you can sudo chown golem:golem sm-sociallogin-plugin if needed."
The orchestrator's interpretation, recorded as given:
1. **release**, with the proposed tags:
- sm-user-plugin v0.1.12
- sm-mfa-plugin v0.1.0, in a new private repository golem15/sm-mfa-plugin created with `tea repos create --owner golem15 --name sm-mfa-plugin`
- sm-sociallogin-plugin v0.1.4
2. **"push demo"**:
- Pushing vue-demo-app is authorised, including the 4 unpushed Phase 19.7 commits (`0125e29`, `b973a76`, `3c53182`, `9c247ab`) under `44a5229`.
- Pushing sm-demo-app after Task 6's commits is authorised, including its 19 unpushed commits from other phases.
- sm-bm-app may be pushed after Task 6 only if its unpushed range is this phase's commits.
- Never stage another session's uncommitted edits. sm-demo-app has another session's uncommitted journal, oauthserver and user pointer moves; leave them. sm-bm-app has dirty planning files; leave them too.
3. **Ownership:** the orchestrator ran `sudo chown -R golem:golem` on `sm-sociallogin-plugin`. It is now golem-owned, clean, at `4d3b2dd` (v0.1.3). No other sudo.
Further limits:
- **Plan 04 patch tags:** not authorised. Plan 04 must ask again before any tag.
- Nothing is force-pushed or deployed.
- fonoteka.go is never pushed.
- No framework tag and no changelog post.
## Commits per repository
| Task | Repository | Commit | Message |
|---|---|---|---|
| 1 (tracer) | sm-mfa-plugin (`../sm-mfa-plugin`, no remote) | `2662252` | feat: WebAuthn security keys as a second factor |
| 1 (tracer) | sm-mfa-plugin | `2662252` | feat: WebAuthn security keys as a second factor |
| 2 | sm-mfa-plugin | `6c8916f` | feat: passwordless passkey login |
| 2 | sm-sociallogin-plugin, **scratch clone only** | `a0161d7` (patch `19.6-03-sociallogin-gate.patch`, sha256 `bc0228df…5db72`) | feat: social sign-in through the login gate |
| 3 | vue-demo-app (`../sm-demo-app/vue-demo-app`) | `44a5229` | feat: two-factor setup and the login challenge |
| 2 | sm-sociallogin-plugin | `a8e3e78` | feat: social sign-in through the login gate (patch applied with `git am`; tree `c5f28f3` identical to the scratch clone's `a0161d7`) |
| 3 | vue-demo-app | `44a5229` | feat: two-factor setup and the login challenge |
| 5 | sm-mfa-plugin | `33ad164` | chore: require sm-user-plugin v0.1.12 |
| 5 | sm-sociallogin-plugin | `b752177` | chore: require sm-user-plugin v0.1.12 |
| 6 (2) | sm-bm-app | `aad6004` | chore: bump sm-user-plugin to v0.1.12 |
| 6 (2) | sm-bm-app | `376cff4` | chore: bump sm-sociallogin-plugin to v0.1.4 |
| 6 (2) | sm-bm-app | `ebb2266` | feat: mount golem15.mfa |
| 6 (3) | fonoteka.go (local only) | `95fd58a` | chore: bump sm-user-plugin to v0.1.12 |
| draft | summercms.go | `5a64611` | docs(19.6-03): draft summary at the Task 4 release checkpoint |
- The real `../sm-sociallogin-plugin` is unchanged: `4d3b2dd` = v0.1.3 = origin/master.
- The patch is at `.planning/phases/19.6-two-factor-auth-and-device-flow-in-sm-user-plugin/19.6-03-sociallogin-gate.patch` and applies on `4d3b2dd` with `git am`.
- The scratch clone is at `/tmp/claude-1002/-media-nvme-dev-golem15-summercms-io-summercms-summercms-go/c0ca11bb-a138-4d09-8120-38eddba1c3cf/scratchpad/sociallogin-clone`.
- Tracer feedback gate (interactive, end-of-phase, verify fully automated): both Task 1 verify commands ran green on the committed code before Task 2 started.
## Remote operations (in order)
## Task 4 reply
Before every push I ran `git fetch` and checked that `origin/master..master` held only the authorised commits. Nothing was force-pushed or deployed.
(pending: awaiting the user's reply to the checkpoint)
1. **sm-user-plugin** (`git@git.golem15.com:golem15/sm-user-plugin.git`):
- Pushed master `50da92c..3b22134`: `8501f34`, `7900f47`, `3d4c8d2`, `4e3e5b0`, `3b22134`.
- Annotated tag **v0.1.12** on `3b22134` (tag object `c0e0e85`), pushed.
2. **sm-mfa-plugin:**
- Created the private repository **https://git.golem15.com/golem15/sm-mfa-plugin** with `tea repos create --login git.golem15.com --owner golem15 --name sm-mfa-plugin --private`. The API confirms `"private":true`.
- Remote `origin` = `git@git.golem15.com:golem15/sm-mfa-plugin.git`.
- `git push -u origin master` pushed the new branch at `33ad164`, 6 commits.
- Annotated tag **v0.1.0** on `33ad164` (tag object `b84dcbe`), pushed.
3. **sm-sociallogin-plugin:**
- Pushed master `4d3b2dd..b752177`: `a8e3e78`, `b752177`.
- Annotated tag **v0.1.4** on `b752177` (tag object `18af090`), pushed.
4. **vue-demo-app:** pushed master `21a22b6..44a5229`. That is the 4 named Phase 19.7 commits plus `44a5229`. No tag.
5. **sm-bm-app** (Task 6 part 2): pushed master `aafbcce..ebb2266`, this phase's 3 commits only.
The acceptance checks pass:
- `ls-remote --exit-code --tags` for v0.1.12, v0.1.0 and v0.1.4.
- `grep -c 'sm-user-plugin v0.1.12' ../sm-mfa-plugin/go.mod` = 1.
- `origin/master..master` is empty for sm-user-plugin, vue-demo-app and sm-bm-app.
## Open checkpoint: sm-demo-app (Task 6 part 1)
`git -C ../sm-demo-app status --porcelain` shows:
- `M plugins/golem15/journal`, `M plugins/golem15/oauthserver`, `M plugins/golem15/user`: another session's uncommitted moves.
- Its user submodule clone is checked out at `50da92c` (v0.1.11). It made v0.1.11's last commit there on 2026-10-24.
- The committed gitlink is `bfcfea4` (v0.1.10), and go.mod requires v0.1.10.
- `M vue-demo-app`: this plan's `44a5229`.
Commit A, the user bump, must move that same `plugins/golem15/user` pointer. Commits B and C cannot go first: sociallogin v0.1.4 and mfa v0.1.0 need v0.1.12's login gates, and the demo's go.work replaces the user module with that checkout.
The plan says to stop when another session's edits touch a Task 6 file, and the Task 4 interpretation says to leave the user pointer move alone. So part (1) did not start, and nothing in sm-demo-app was changed.
Proposed resolution:
- Move only the user checkout from v0.1.11 to v0.1.12. That is a superset, `50da92c` stays on origin, and the submodule's working tree is clean, so nothing is lost.
- Leave the journal and oauthserver moves unstaged.
- Then run commits A-D, the tests, and the push of the 19 prior commits plus A-D.
## Tests
All Docker-backed tests ran against testcontainers Postgres (Docker reachable as golem).
- **sm-mfa-plugin:**
- `go vet ./...` and `go test -count=1 -short ./...`: green at both commits.
- `TestWebAuthnCeremonySmoke` (short): pass.
- `TestWebAuthnSmoke`, `TestTracerSmoke`, `TestChallengeSmoke`, `TestTrustedSettingsSmoke`: 4 of 4 pass.
- `TestPasswordlessSmoke` (7 subtests) and `TestWebAuthnSmoke`: 2 of 2 pass.
- Full `go test ./...`: ok. It was also run against the framework's v0.5.0 tag (exported with `git archive` into the scratchpad): ok.
- **sm-sociallogin-plugin (scratch clone, GOWORK pointing at the real summercms.go and user-plugin checkouts):**
- `go vet`: green.
- `-short` suite: ok.
- `TestSocialGateSmoke` (5 subtests) and `TestTracerSmoke`: 2 of 2 pass.
- Full suite: ok, against both the local framework and v0.5.0.
- **NOT RUN in the real checkout**, which cannot be written.
- **vue-demo-app:**
- `pnpm test`: 17 files, 202 tests pass, including `twoFactor.test.ts` with 14 tests.
- **Tasks 1-2:** `go vet ./...` and `-short`: green at every commit.
- `TestWebAuthnCeremonySmoke`: pass.
- `TestWebAuthnSmoke`, `TestTracerSmoke`, `TestChallengeSmoke`, `TestTrustedSettingsSmoke`: pass.
- `TestPasswordlessSmoke` (7 subtests): pass.
- Full suite: ok, also against the framework's v0.5.0 tag.
- **Task 5, after the require bump:**
- Vet and `-short` in workspace mode: green.
- With `GOWORK=off` (module mode, sm-user-plugin v0.1.12 downloaded from git.golem15.com): vet, `-short` and the full suite are green.
- `go list -deps` has no `gopkg.in/yaml.v3`.
- **sm-sociallogin-plugin (real checkout):**
- **After `git am`:** vet, `-short` and the full suite are green, including `TestSocialGateSmoke` (5 subtests) and `TestTracerSmoke`.
- **After the require bump:** vet and `-short` green in workspace mode. Vet and the full suite green with `GOWORK=off`.
- **sm-user-plugin** (before the push): vet from fonoteka.go and in the plugin, then `-short`: ok for all four packages.
- **vue-demo-app (Task 3):**
- `pnpm test`: 202 tests pass, including `twoFactor.test.ts` (14).
- `pnpm build`: ok.
- Server render of `/account/security` and `/login` on `.output`: 200.
- **NOT RUN:**
- Type check: no vue-tsc or `nuxi typecheck` is set up (deferred-items.md).
- A browser run against a demo with golem15.mfa mounted: that needs Task 6.
- Server render of `/account/security` and `/login`: 200.
- **sm-bm-app:**
- Build, vet and `-short` green after each of the 3 commits. The full suite is green too.
- The plan's verify passes: `--- PASS: TestMFAMounted`.
- **fonoteka.go:**
- The plan's verify passes: root `vet ./...`, `test -short ./...` and `build ./...`.
- Root `./...` covers only the root module, so I also ran vet and `-short` in every workspace module (`plugins/golem15/{feedback,fonoteka,golem,user}`). All green.
- Without `-short`:
- `parity` passes after the fix; it was red at v0.1.10 as well.
- Five Docker-backed tests in `plugins/golem15/fonoteka` fail at v0.1.12. They fail identically at the committed v0.1.10 pointer (baseline run), so they predate this plan. They are listed in deferred-items.md.
- Acceptance checks:
- `grep -c '"/devices"'` = 0 and `grep -c '"2fa"'` = 1.
- `summer.yaml` has no golem15.mfa.
- `go list -m all` has no go-webauthn.
- The pointer moved in the bump commit.
- **NOT RUN:**
- The demo verify (`TestMFAMounted`, `TestBootAllPlugins`, `TestGoModRequiresTags`): part (1) is open.
- A vue-demo-app type check: no tooling for it.
- A browser run of the demo screens.
## Exported identifiers (for plan 04 and the continuation)
## Exported identifiers (for plan 04)
**sm-mfa-plugin `classes`:**
- **Relying party and handles:**
@@ -233,11 +341,13 @@ All Docker-backed tests ran against testcontainers Postgres (Docker reachable as
- `POST …/webauthn/register/options`, `POST …/webauthn/register`
- `DELETE …/webauthn/{id}` (digits)
**sm-sociallogin-plugin (patch):**
**sm-sociallogin-plugin v0.1.4:**
- `classes.Outcome.Challenge` and `classes.CompletionPayload.Challenge` (JSON `challenge,omitempty`).
- Lang key `golem15.sociallogin::lang.oauth.two_factor_unavailable`.
- Test `TestSocialGateSmoke`.
- go.mod still requires sm-user-plugin v0.1.5, with go.work replacing it until Task 5.
- go.mod requires sm-user-plugin v0.1.12 and SummerCMS v0.5.0.
**sm-bm-app:** `TestMFAMounted` (`mfa_test.go`).
**vue-demo-app:**
- **`app/lib/twoFactor.ts`:**
@@ -250,7 +360,7 @@ All Docker-backed tests ran against testcontainers Postgres (Docker reachable as
- **UI:**
- Page `/account/security`.
- Header link to it.
- i18n `auth.twoFactor.*`, `security.*`, `header.security` (en, pl).
- i18n `auth.twoFactor.*`, `security.*`, `header.security`.
## Decisions Made
@@ -258,107 +368,93 @@ See key-decisions in the frontmatter.
## Deviations from Plan
### Auto-fixed and drift
**1. [Environment, resolved] sm-sociallogin-plugin write access**
- **Problem:** the checkout was owned by `jin` (first run). The scratch-clone patch is described in the first draft.
- **Fix:** at the user's instruction ("you can sudo chown golem:golem sm-sociallogin-plugin"), the orchestrator ran `sudo chown -R golem:golem`. I used no sudo.
- **Result:** `git am` gave `a8e3e78`, whose tree `c5f28f3` is identical to the scratch clone's `a0161d7`. Vet, `-short` and the full suite were green there.
**1. [Blocking: environment, unresolved] sm-sociallogin-plugin cannot be written**
- **Found during:** Task 2.
- **Issue:** `../sm-sociallogin-plugin` is owned by `jin` (`drwxrwxr-x jin jin`); golem is not in the jin group, and git refuses the checkout as "dubious ownership". As instructed, I did not use sudo and did not write into it.
- **What I did instead:**
- Implemented D-09 in a scratch clone of `4d3b2dd`.
- Tested it there with a GOWORK file pointing at the real summercms.go and user-plugin checkouts. `../fonoteka.go` and `../summercms.go` are symlinked next to the clone so the relative paths in its tests resolve.
- Committed it there as `a0161d7` with the plan's message and exported it as `19.6-03-sociallogin-gate.patch` in this phase directory.
- **Remaining:** `git am` in the real checkout (by jin, or after the user grants golem write access), then rerun its suite there. Task 5 step 3 (require the new user tag, push, tag) needs the same access.
**2. [Drift, corrected] Release numbers**
- The plan proposed sm-user-plugin v0.1.6 and sm-sociallogin-plugin v0.1.3. Both were stale.
- Published as v0.1.12 and v0.1.4, the next free patches, as the reply confirmed.
- sm-mfa-plugin is v0.1.0 as planned.
- sm-mfa-plugin requires framework v0.5.0, not the plan's v0.3.1. Plan 02 already needed v0.5.0.
- sociallogin's require rose from v0.3.1 to v0.5.0 through `go mod tidy`. Its README Dependencies now names v0.5.0 and user v0.1.12, in the same commit.
**2. [Drift] Release numbers**
- **sm-user-plugin:** origin's newest tag is v0.1.11 (`50da92c`). Local master `3b22134` is 5 commits ahead: `8501f34`, `7900f47`, `3d4c8d2`, `4e3e5b0`, `3b22134`. The plan's "v0.1.6" is stale; the next free patch is **v0.1.12**.
- **sm-sociallogin-plugin:** origin's newest tag is v0.1.3 (`4d3b2dd`, Phase 19.8's SignedInEvent). The plan's "v0.1.3" is taken; the next free patch is **v0.1.4**.
- **Framework:** the newest tag is v0.5.0. sm-mfa-plugin and sm-user-plugin require it. sociallogin requires v0.3.1, and after its require bump to user v0.1.12, `go mod tidy` will raise that to v0.5.0.
- **No framework tag is needed.** Both plugins pass against the v0.5.0 tag.
**3. [Drift] vue-demo-app pushed with another phase's commits**
- The push included Phase 19.7's `0125e29`, `b973a76`, `3c53182` and `9c247ab`.
- The Task 4 reply authorised them by name.
**3. [Drift] vue-demo-app carries another phase's unpushed commits**
- The plan expected `5a8d544`. The checkout is at `9c247ab`, 4 commits ahead of origin/master `21a22b6`: Phase 19.7's `0125e29`, `b973a76`, `3c53182`, `9c247ab` (MCP consent and link-admin pages, 2026-10-23).
- Task 3 committed on top (`44a5229`). Pushing vue-demo-app also pushes those four, so the Task 5 pre-push rule requires the reply to name them.
**4. [Checkpoint] sm-demo-app part (1) not started**
- Another session's uncommitted `plugins/golem15/user` move touches a Task 6 file (see "Open checkpoint").
**4. [Drift] sm-demo-app state for Task 6**
- master is 19 commits ahead of origin (other phases).
- Its working tree has another session's uncommitted submodule pointer moves for `plugins/golem15/journal`, `plugins/golem15/oauthserver` and `plugins/golem15/user` (user checked out at v0.1.11 while go.mod still requires v0.1.10).
- The `vue-demo-app` gitlink now shows as modified because of `44a5229`.
- Task 6 must not stage the other sessions' pointers. A push needs the reply to name the 19 commits.
**5. [Rule 3, scope] fonoteka.go guard tests cover every user version the bump crosses**
- **Problem:** the committed pointer was v0.1.10, but the guards had not followed it. `TestHiddenNeverMarshals` expected 8 models with 10 present. The parity migration count expected 11 with 14 present, and the account-link tables had no allowance. So the plan's "+1 for DeviceAuthSession" would have left them red.
- **What `95fd58a` changes:**
- `expectedUserModels` 8 → 11, with a comment naming BackendLink, BackendLinkRequest and DeviceAuthSession.
- New `wantUserMigrations = 15` in parity.
- Allowances for `golem15_user_backend_links`, `golem15_user_backend_link_requests` and `golem15_user_device_auth_sessions`.
- `TestRequirePasswordChangeExemptSet` allows jwt.auth on `POST /_user/api/v1/backend-link/confirm`, which comes from v0.1.11.
- The deferred-routes guard drops the devices fragment and keeps 2fa. Its comment avoids the quoted `"/devices"` so the acceptance grep stays at 0.
- **Untouched:** five other Docker-backed fonoteka failures fail identically at v0.1.10 and do not inventory the user plugin, so they were left alone (deferred-items.md).
**5. [Rule 2 - Security] Availability check on webauthn/register too**
- PHP checked availability only on register/options.
- `register` also answers 403 while webauthn is not an available method, as `totp/confirm` does since plan 02.
- **Commit:** `2662252`.
**6. [Scope] sm-bm-app `scripts/build.sh`**
- It is not in the plan's file list.
- The 117ef8f precedent adds each mounted plugin to `PLUGINS`, which drives the release go.work and the dirty check. mfa was added there.
**6. [Rule 2 - Correctness] Assertion write-back is guarded**
- The credential record is written back with `WHERE sign_count = <read value> AND is_enabled`, inside the completing transaction.
- A concurrent write or a disabled key refuses the login instead of losing the counter.
- **Commit:** `2662252`.
**7. [Scope] fonoteka.go root go.mod still requires sm-user-plugin v0.1.10**
- The plan limits staging to the pointer and the tests.
- The replace makes the build use v0.1.12.
- A follow-up is in deferred-items.md.
**7. [Discretion] Function split**
- The plan named `FinishAssertion(ctx, app, tx, …)` as the one step that verifies and writes.
- It is now `FinishAssertion` (verify, no write, outside the transaction, like the TOTP path), followed by `RecordAssertion` (guarded write in the transaction).
- `VerifyRegistration` and `VerifyAssertion` are exported pure helpers, so the no-database smoke test exercises the handlers' exact code.
- `CompleteLoginAs` was added for the passkey source.
**8. [Cosmetic] sm-mfa-plugin v0.1.0 tag message**
- It says the mode is "optional, required or disabled"; the actual values are `disabled`, `optional` and `enforced`.
- The tag is pushed and is not rewritten, since that would need a force-push (deferred-items.md).
**8. [Rule 2 - Security, fail closed] Passkey routes 403 while two_factor_mode is disabled**
- This is not only tied to `two_factor_passwordless_login`.
- **Commit:** `6c8916f`.
**9. [Rule 2 - Security] Passkey ban check across addresses**
- `ThrottleStates` (a ban or active suspension on any row) runs in addition to `RejectIfThrottled` with the request's address.
- An unactivated account is refused too (tested).
- **Commit:** `6c8916f`.
**10. [Scope] Extra test coverage**
- `TestWebAuthnSmoke` also checks the admin reset deleting keys and the 403 while webauthn is unavailable.
- `TestPasswordlessSmoke` adds `wrong_handle_refused` and an unactivated user.
- `TestSocialGateSmoke` also covers `oauth-register-complete` answering the challenge.
**11. [Scope] Demo UI extras**
- Additional i18n keys beyond the plan's list:
- `auth.twoFactor.busy`, `auth.twoFactor.recoveryPrompt`
- `security.eyebrow`, `security.qrAlt`, `security.cancel`, `security.codeInvalid`, `security.unavailable`
- `header.security`
- The setup form has a cancel link.
---
**Total deviations:** 11 (1 unresolved blocker, 3 drifts, 4 Rule 2 fixes, 1 discretion, 2 scope).
**Impact on plan:** Every must-have truth of Tasks 1 and 3 holds in committed code. D-09 holds in the prepared patch, but not yet in the real repository. No locked CONTEXT decision changed.
**9-15 (from the first run):**
- Rule 2: availability check on `webauthn/register`.
- Rule 2: guarded assertion write-back.
- Discretion: `FinishAssertion` / `RecordAssertion` split, exported `VerifyRegistration` / `VerifyAssertion`, and `CompleteLoginAs`.
- Rule 2: passkey routes 403 while `two_factor_mode` is disabled.
- Rule 2: passkey ban check across addresses (ThrottleStates) and refusal of unactivated accounts.
- Extra test coverage in `TestWebAuthnSmoke`, `TestPasswordlessSmoke` and `TestSocialGateSmoke`.
- Demo UI extras: i18n keys and a cancel link. The commits and details are unchanged from the first draft (`2662252`, `6c8916f`, `44a5229`).
## Known Stubs
None. Plan 02's two stubs are closed:
None.
- `status.webauthn_credentials` lists the enabled keys.
- `Admin.Reset` deletes WebAuthn credentials, and `HasTwoFactor` counts them.
- `Admin.Reset` deletes WebAuthn credentials.
- sm-bm-app mounts the two-factor API without frontend screens, which is intended and documented in its README row. Users cannot enable a second factor there, so sign-in is unchanged.
## Threat Flags
None beyond the plan's register. T-19.6-26..31 and T-19.6-33 are mitigated in code and covered by the tests above. T-19.6-32 (remote operations) is the open Task 4 decision.
None beyond the plan's register.
- T-19.6-26..31 and T-19.6-33 are mitigated in code and tests.
- T-19.6-32: each push followed a fetch and a range check against the authorised commits. Nothing was force-pushed or deployed, and fonoteka.go was not pushed.
## Blockers
- **sm-sociallogin-plugin write access** (Deviation 1). It blocks applying `a0161d7` and the sociallogin half of Task 5.
- **Task 4 release decision.**
- **sm-demo-app user pointer** (another session's uncommitted move). The decision is needed before Task 6 part (1).
## Next
The continuation agent reads this draft and the Task 4 reply, then:
- (if the reply resolves the sociallogin access) applies the patch;
- runs Task 5 in D-17 order and Task 6;
- finalises this SUMMARY: requirements-completed, status complete, final commits;
- updates STATE and ROADMAP.
After the decision, the continuation agent:
- runs Task 6 part (1) in sm-demo-app: commits A-D, the demo verify, then the push of the 19 prior commits plus A-D;
- finalises this SUMMARY: requirements-completed and status complete;
- updates ROADMAP progress.
g15office: this executor has no g15office tool, so the orchestrator should post the plan 03 update on `avoroeb4`.
g15office: this executor has no g15office tool. The orchestrator posts the plan 03 update on `avoroeb4`.
## Self-Check: PASSED (draft)
- Commits exist:
- sm-mfa-plugin: 2662252, 6c8916f
- vue-demo-app: 44a5229
- scratch clone: a0161d7
- Working trees of sm-mfa-plugin and vue-demo-app are clean.
- ../sm-sociallogin-plugin is untouched (4d3b2dd, clean).
- sm-mfa-plugin has no remote, and the sm-user-plugin HEAD is untagged.
- No remote operation was performed; only read-only ls-remote calls.
- **Commits exist:**
- sm-mfa-plugin: `2662252`, `6c8916f`, `33ad164`
- sm-sociallogin-plugin: `a8e3e78`, `b752177`
- vue-demo-app: `44a5229`
- sm-bm-app: `aad6004`, `376cff4`, `ebb2266`
- fonoteka.go: `95fd58a`
- **Tags and branches are on origin:** v0.1.12, v0.1.0 and v0.1.4 via `ls-remote`, and sm-mfa-plugin master.
- **Working trees:**
- The working trees of sm-mfa-plugin, sm-sociallogin-plugin and fonoteka.go are clean.
- sm-bm-app keeps only the other session's planning edits and untracked paths.
- sm-demo-app is unchanged by this run.

View File

@@ -6,3 +6,17 @@
- vue-demo-app has no type-check tooling
status: open
**What:** the Nuxt build does not type-check, and `vue-tsc` is not a dependency, so 19.6-03 Task 3 checked the new page, composables and header with vitest and `pnpm build` only. The user decides whether to add `nuxi typecheck`.
- fonoteka.go: five Docker-backed tests in `plugins/golem15/fonoteka` fail without `-short`, at the committed user pointer v0.1.10 as well as at v0.1.12
status: open
**What:** found by 19.6-03 Task 6 (baseline run with the user submodule at `bfcfea4`; the failures are identical):
- `TestPhase09SecurityRoutes`: unexpected cabana admin routes (account/contributions, account/preferences, reorder, singleton) from the framework working tree.
- `TestPhase8RedMCPMe`, `TestOAuthToolsMeRouteIsolation`: `GET /_user/api/v1/token/me` counts as a `/me`-suffixed route on another surface.
- `TestMustChangePasswordLock`: login answers 401 "Invalid email or password".
- `TestPhase14Edges/prune-cutoff-90-days-then-one-second-older`: prune leaves the "past" row.
None of these tests inventories user-plugin tables, routes or models, so 19.6-03 did not change them. The `-short` suites are green. The parity inventories (`parity/migrate_test.go`, `parity/schema_diff_test.go`) were also red at v0.1.10; `95fd58a` fixed them along with the bump.
- fonoteka.go: root `go.mod` still requires `sm-user-plugin v0.1.10`
status: open
**What:** the module resolves through `replace => ./plugins/golem15/user`, so builds and tests use the v0.1.12 checkout. 19.6-03 Task 6 staged only the paths the plan names. Precedent `06bc9d0` raised the require together with the pointer. Raise it to v0.1.12 (and tidy) in a follow-up `chore:` commit.
- sm-mfa-plugin: the v0.1.0 tag message says the two-factor mode is "optional, required or disabled"
status: open
**What:** the config values are `disabled`, `optional` and `enforced`. The tag is pushed and is not rewritten (that would need a force-push). The README and `config/config.yaml` are correct. The next release's tag message should name the right values.