docs(19.4-01): complete OIDC provider on wristband plan
This commit is contained in:
@@ -0,0 +1,288 @@
|
||||
---
|
||||
phase: 19.4-oidc-provider-on-wristband
|
||||
plan: 01
|
||||
subsystem: auth
|
||||
tags: [oidc, oauth, wristband, golang-jwt, jwks, rs256, es256, pkce, consent, pairwise]
|
||||
|
||||
requires:
|
||||
- phase: 08
|
||||
provides: hand-written wristband MCP authorization server (PKCE, hashed secrets, redirect writer, refresh lineage)
|
||||
- phase: 19.3
|
||||
provides: bouncer cookie transport (used by plan 02's OIDCHost)
|
||||
provides:
|
||||
- wristband OIDC flavour (NewOIDCServer) next to the unchanged MCP flavour
|
||||
- OIDCBackend/OIDCTx, KeyStore and OIDCHost seams for plan 02's sm-oauthserver-plugin
|
||||
- Discovery, JWKS, Authorize (start + decide), Resume, SwitchAccount, Token with id_token, UserInfo
|
||||
- ConsentRequest/DecideConsent JSON-mode operations
|
||||
- ClientRecord.Kind with ClientKindMCP/ClientKindOIDC isolation
|
||||
- RS256 + ES256 keys, pairwise subjects, opt-in OIDC refresh
|
||||
- neutral wristband DefaultOptions().Resource (folded todo)
|
||||
- SC-1 decision note (golang-jwt v5, zitadel rejected)
|
||||
affects: [19.4-02, 19.4-03, 19.4-04, 19.7, fonoteka]
|
||||
|
||||
actuals:
|
||||
tokens: 48000
|
||||
tasks: 4
|
||||
commits: 6
|
||||
plan_head_before: 95051d26cef7e9fd9e06312501df1a55f2865927
|
||||
plan_head_after: 811124259c1b1e9c0867e58601d7d6cc41aabcc1
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "One engine, two flavours: Server.flavour dispatch at the top of Authorize and Token; flavour-only handlers answer 404 on the other flavour"
|
||||
- "Protocol errors commit: the OIDC transaction closure returns nil with an outcome so burns and revocations persist"
|
||||
- "Host render seam: engine sets security headers, host writes status and body"
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- .planning/notes/2026-10-19-oidc-on-wristband-golang-jwt.md
|
||||
- modules/wristband/oidc_options.go
|
||||
- modules/wristband/oidc_stores.go
|
||||
- modules/wristband/oidc_keys.go
|
||||
- modules/wristband/oidc_discovery.go
|
||||
- modules/wristband/oidc_authorize.go
|
||||
- modules/wristband/oidc_consent.go
|
||||
- modules/wristband/oidc_token.go
|
||||
- modules/wristband/oidc_userinfo.go
|
||||
- modules/wristband/oidc_redirect.go
|
||||
- modules/wristband/oidc_subject.go
|
||||
- modules/wristband/oidc_fakes_test.go
|
||||
- modules/wristband/oidc_tracer_test.go
|
||||
- modules/wristband/oidc_authorize_smoke_test.go
|
||||
- modules/wristband/oidc_token_smoke_test.go
|
||||
- ../fonoteka.go/plugins/golem15/fonoteka/oauth_resource_test.go
|
||||
modified:
|
||||
- modules/wristband/server.go
|
||||
- modules/wristband/stores.go
|
||||
- modules/wristband/authorize.go
|
||||
- modules/wristband/token.go
|
||||
- modules/wristband/register.go
|
||||
- modules/wristband/client_issue.go
|
||||
- modules/wristband/example_test.go
|
||||
- modules/wristband/README.md
|
||||
- README.md
|
||||
- docs/services/oauth-server.md
|
||||
- .planning/research/STACK.md
|
||||
- ../fonoteka.go/plugins/golem15/fonoteka/plugin.go
|
||||
|
||||
key-decisions:
|
||||
- "D-16: extend the hand-written wristband; golang-jwt/jwt/v5 v5.3.1 signs id_tokens via MapClaims (aud stays a string); stdlib JWK encoding; zitadel/oidc v3 rejected; go-oidc v3.21.0 and x/oauth2 v0.37.0 test-only in sm-oauthserver-plugin"
|
||||
- "OIDC redirects send Cache-Control: no-store, private as the plan specifies; the PHP provider sends Laravel's default no-cache, private on these redirects, so plan 03's tide replay must accept or settle that diff"
|
||||
- "ConsentView carries RedirectHost and Incremental besides the planned fields, because the PHP consent page shows both"
|
||||
- "SwitchAccount without a signed-in user redirects to the resume path (PHP behaviour) instead of rendering expired_request"
|
||||
- "Discovery and JWKS 500 bodies are {\"message\": ...} (Laravel abort shape) with the PHP message texts"
|
||||
- "Pairwise sector errors (ErrSectorRequired) at resume, token or userinfo answer 500; plan 03 adds the admin save-time check"
|
||||
|
||||
patterns-established:
|
||||
- "OIDC flavour code lives in oidc_*.go; MCP files change only at the flavour dispatch and the Kind checks"
|
||||
- "PHP query semantics: lastValue (parse_str last wins), inputValue (body over query)"
|
||||
|
||||
requirements-completed: [SC-1, SC-2, SC-3, SC-5, D-01, D-04, D-06, D-07, D-08, D-09, D-12, D-14, D-15, D-16, D-17]
|
||||
|
||||
coverage:
|
||||
- id: D1
|
||||
description: "SC-1 decision note names golang-jwt v5, the test-only RP modules and rejects zitadel/oidc, committed before the first OIDC code; STACK.md row superseded"
|
||||
requirement: SC-1
|
||||
verification:
|
||||
- kind: other
|
||||
ref: "git merge-base --is-ancestor <note commit> <first oidc_keys.go commit>; grep checks on the note and STACK.md"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D2
|
||||
description: "Relying party flow end to end over memory fakes: discovery, JWKS, authorize, resume, consent, code exchange, id_token verified against the served JWKS, userinfo"
|
||||
requirement: SC-2
|
||||
verification:
|
||||
- kind: integration
|
||||
ref: "modules/wristband/oidc_tracer_test.go#TestOIDCTracerSmoke"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D3
|
||||
description: "Authorize start, resume, silent and incremental consent, one-shot consent token, deny, switch account, PHP redirect encoding, is_dev loopback ports"
|
||||
requirement: SC-3
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "modules/wristband/oidc_authorize_smoke_test.go#TestOIDCAuthorizeStartSmoke"
|
||||
status: pass
|
||||
- kind: unit
|
||||
ref: "modules/wristband/oidc_authorize_smoke_test.go#TestOIDCResumeDecideSwitchSmoke"
|
||||
status: pass
|
||||
- kind: unit
|
||||
ref: "modules/wristband/oidc_authorize_smoke_test.go#TestPHPRedirectEncodingSmoke"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D4
|
||||
description: "Kind isolation in both flavours; MCP still refuses openid; flavour-only handlers 404"
|
||||
requirement: D-08
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "modules/wristband/oidc_authorize_smoke_test.go#TestKindIsolationSmoke"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D5
|
||||
description: "Token errors, burn-then-check replay revocation, uniform userinfo 401, mixed RS256/ES256 ring, pairwise subjects, opt-in refresh"
|
||||
requirement: SC-2
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "modules/wristband/oidc_token_smoke_test.go#TestOIDCTokenErrorsSmoke"
|
||||
status: pass
|
||||
- kind: unit
|
||||
ref: "modules/wristband/oidc_token_smoke_test.go#TestOIDCReplayRevokesSmoke"
|
||||
status: pass
|
||||
- kind: unit
|
||||
ref: "modules/wristband/oidc_token_smoke_test.go#TestOIDCUserInfoSmoke"
|
||||
status: pass
|
||||
- kind: unit
|
||||
ref: "modules/wristband/oidc_token_smoke_test.go#TestMixedKeyRingSmoke"
|
||||
status: pass
|
||||
- kind: unit
|
||||
ref: "modules/wristband/oidc_token_smoke_test.go#TestPairwiseSubjectSmoke"
|
||||
status: pass
|
||||
- kind: unit
|
||||
ref: "modules/wristband/oidc_token_smoke_test.go#TestOIDCRefreshOptInSmoke"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D6
|
||||
description: "Neutral DefaultOptions().Resource; fonoteka sets its resource explicitly and keeps its MCP behaviour (D-09)"
|
||||
requirement: D-09
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "modules/wristband/server_test.go#TestDefaultOptionsResourceEmpty"
|
||||
status: pass
|
||||
- kind: integration
|
||||
ref: "../fonoteka.go/plugins/golem15/fonoteka/oauth_resource_test.go#TestOAuthResourceExplicit (Docker, real Postgres)"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D7
|
||||
description: "wristband README and docs/services/oauth-server.md describe the OIDC flavour; every identifier exists"
|
||||
requirement: SC-5
|
||||
verification:
|
||||
- kind: other
|
||||
ref: "go test ./cmd/summer -run TestDocsTree; go run ./cmd/summer docs:build --check"
|
||||
status: pass
|
||||
human_judgment: true
|
||||
rationale: "The docs checker proves identifiers and links; whether the OIDC section reads well for a host author is a reader judgment"
|
||||
|
||||
duration: 28min
|
||||
completed: 2026-10-19
|
||||
status: complete
|
||||
---
|
||||
|
||||
# Phase 19.4 Plan 01: OIDC provider on wristband Summary
|
||||
|
||||
**wristband gains an OpenID Connect flavour on its existing engine: PHP-compatible discovery, JWKS (RS256 and ES256), resumable authorize with one-shot consent and account switching, token with an id_token signed through golang-jwt map claims, userinfo, pairwise subjects and opt-in refresh, behind new OIDCBackend, KeyStore and OIDCHost seams; the MCP flavour is unchanged and its default resource is now neutral.**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** 28 min
|
||||
- **Started:** 2026-10-19T16:05:54Z
|
||||
- **Completed:** 2026-10-19T16:34:15Z
|
||||
- **Tasks:** 4
|
||||
- **Files modified:** 34 (32 in summercms.go, 2 in fonoteka.go)
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- SC-1 decision note on record before the code: extend wristband, golang-jwt v5.3.1 signs, stdlib JWKs, go-oidc and x/oauth2 test-only in the plugin repo, zitadel/oidc v3 rejected (D-06, D-09, D-14).
|
||||
- `wristband.NewOIDCServer` builds a second `*Server` on the same engine; `Authorize` and `Token` dispatch by flavour before any MCP line runs; MCP-only handlers 404 on OIDC and the reverse.
|
||||
- PHP contract ported: validation order and messages, rendered local errors with the four security headers, login handoff with only `redirect=`, silent and incremental consent, deny, switch account, PHP `http_build_query` redirects without `iss`, Basic-or-form client auth with the lowercase realm, burn-then-check codes with replay revocation, uniform userinfo 401.
|
||||
- Keys: RS256 and ES256 generation and parsing (PKCS#8, PKCS#1, SEC1), stdlib JWK encoding, mixed rings with retired keys inside the grace window, discovery advertising what the ring holds.
|
||||
- Subjects: lazy random UUIDv4 per (user, sector), public by default, pairwise by sector identifier or single redirect host.
|
||||
- Opt-in OIDC refresh (`OIDCOptions.RefreshEnabled`), only for `OfflineAccess` clients granted `offline_access`; rotation and lineage revocation.
|
||||
- Folded todo closed: `DefaultOptions().Resource` is empty, framework comments name no application, fonoteka sets its resource explicitly.
|
||||
|
||||
## Task Commits
|
||||
|
||||
1. **Task 1 (tracer), planning doc:** `756a97c` docs(19.4): decide OIDC on wristband with golang-jwt (roadmap corrections were already in the plan-creation commit, so no separate roadmap commit)
|
||||
2. **Task 1 (tracer), code:** `659b42a` feat(19.4-01): OIDC flavour of wristband with an end-to-end tracer
|
||||
3. **Task 2:** `dfa3c3e` feat(19.4-01): OIDC authorize, consent and switch account match the PHP provider
|
||||
4. **Task 3:** `e25e733` feat(19.4-01): OIDC token, keys, subjects and opt-in refresh
|
||||
5. **Task 4, framework:** `ddda59a` feat(19.4-01): neutral wristband resource default and OIDC provider docs
|
||||
6. **Task 4, fonoteka.go:** `687e25a` fix(fonoteka): set the OAuth resource explicitly (in `../fonoteka.go`, only `plugin.go` and `oauth_resource_test.go` staged)
|
||||
7. **Task 4, todo:** `8111242` docs(19.4): close neutral wristband resource todo
|
||||
|
||||
**Plan metadata:** this SUMMARY commit (docs(19.4-01)).
|
||||
|
||||
## Exported identifiers (for plan 02)
|
||||
|
||||
Constructors and handlers: `NewOIDCServer(opts Options, oidc OIDCOptions) *Server`, `(*Server).SetOIDCBackend(b OIDCBackend, keys KeyStore, host OIDCHost) error`, `(*Server).Discovery`, `(*Server).JWKS`, `(*Server).Authorize` (OIDC: GET start, POST decide), `(*Server).Resume` (reads `r.PathValue("request")`), `(*Server).SwitchAccount`, `(*Server).Token`, `(*Server).UserInfo`, `(*Server).ConsentRequest(ctx, requestID string, userID uint) (ConsentView, error)`, `(*Server).DecideConsent(ctx, requestID string, userID uint, consentToken string, allow bool) (string, error)`.
|
||||
|
||||
Options: `OIDCOptions{LoginURL, IDTokenTTL, AccessTokenTTL, CodeTTL, AuthRequestTTL, RefreshEnabled, RefreshTokenTTL, ScopesSupported, ClaimsSupported, IdentityClaims, SubjectTypesSupported, ClientIDPrefix, ClientSecretPrefix, CodePrefix, AccessTokenPrefix, RefreshTokenPrefix}`, `DefaultOIDCOptions()`.
|
||||
|
||||
Host seam: `OIDCHost{SignedInUser(r) (uint, bool, error); RenderAuthorizeError(w, r, code string); RenderConsent(w, r, view ConsentView); EndSession(w, r) error}`, `ConsentView{RequestID, ConsentToken, ClientID, ClientName, Scopes, UserID, RedirectHost, Incremental}`, `AuthorizeErrorUnknownClient`, `AuthorizeErrorRedirectMismatch`, `AuthorizeErrorInactiveClient`, `AuthorizeErrorExpiredRequest`, `ErrAuthRequestExpired`.
|
||||
|
||||
Storage seam: `OIDCBackend{WithinOIDCTx(ctx, fn func(OIDCTx) error) error}`; `OIDCTx` = `OIDCClientStore{OIDCClientByClientID}` + `AuthRequestStore{CreateAuthRequest, AuthRequestByRequestID, AuthRequestByRequestIDForUpdate, ConsumeAuthRequest(ctx, id, now) (bool, error), SetConsentTokenHash(ctx, id, *string)}` + `OIDCCodeStore{CreateCode, ConsumeCode(ctx, hash, now) (*OIDCCodeRecord, error), CodeByHash}` + `OIDCAccessTokenStore{CreateAccessToken, AccessTokenByHash, TouchAccessToken(ctx, id, now), RevokeAccessTokensForCode(ctx, codeID, now)}` + `ConsentStore{ConsentFor, SaveConsent(ctx, userID, clientID, scopes, now), TouchConsent(ctx, id, now)}` + `SubjectStore{SubjectFor(ctx, userID, sector, candidate)}` + `IdentityStore{IdentityClaims(ctx, userID, scopes) (map[string]any, bool, error)}` + `OIDCRefreshTokenStore{CreateRefreshToken, RefreshTokenByHashForUpdate, MarkRefreshRotated(ctx, id, successorID), RevokeRefreshLineage(ctx, startID, now)}`. Records: `OIDCClientRecord`, `AuthRequestRecord`, `OIDCCodeRecord`, `OIDCAccessTokenRecord`, `OIDCRefreshTokenRecord`, `ConsentRecord` (fields as planned). Create methods fill `rec.ID`; not found is `(nil, nil)`; `SaveConsent` stamps `LastUsedAt`; `RevokeAccessTokensForCode` also revokes refresh tokens linked to those access tokens.
|
||||
|
||||
Keys: `KeyStore{ActiveSigningKey(ctx) (SigningKey, error); PublishedKeys(ctx, now) ([]PublicKey, error)}`, `SigningKey{Kid, Alg, Signer}` (Signer must be `*rsa.PrivateKey` or P-256 `*ecdsa.PrivateKey`), `PublicKey{Kid, Alg, Key}`, `AlgRS256`, `AlgES256`, `GenerateSigningKey(alg)`, `ParsePrivateKeyPEM`, `ParsePublicKeyPEM`.
|
||||
|
||||
Kinds and subjects: `ClientRecord.Kind`, `ClientKindMCP`, `ClientKindOIDC`, `SubjectTypePublic`, `SubjectTypePairwise`, `SubjectSector(c *OIDCClientRecord) (string, error)`, `ErrSectorRequired`, `IssueOIDCClientCredentials(o OIDCOptions) (clientID, secret, secretHash string, err error)`.
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
- `modules/wristband/oidc_options.go` - OIDCOptions, NewOIDCServer, SetOIDCBackend, OIDCHost, ConsentView, error codes, IssueOIDCClientCredentials
|
||||
- `modules/wristband/oidc_stores.go` - OIDCBackend, OIDCTx and the eight component stores, record types
|
||||
- `modules/wristband/oidc_keys.go` - KeyStore, key generation and parsing, JWK encoding, id_token signing, at_hash
|
||||
- `modules/wristband/oidc_discovery.go` - Discovery and JWKS handlers
|
||||
- `modules/wristband/oidc_authorize.go` - start, Resume, decide handler, validation, PHP query helpers
|
||||
- `modules/wristband/oidc_consent.go` - consent token, prepareConsent, decideConsent, ConsentRequest, DecideConsent, SwitchAccount
|
||||
- `modules/wristband/oidc_token.go` - OIDC token endpoint, client auth, code redemption, refresh grant
|
||||
- `modules/wristband/oidc_userinfo.go` - UserInfo, bearer parsing, ordered claims JSON
|
||||
- `modules/wristband/oidc_redirect.go` - phpURLEncode, appendPHPQuery, phpURLDecode, matchOIDCRedirectURI
|
||||
- `modules/wristband/oidc_subject.go` - SubjectSector, ErrSectorRequired, subject types
|
||||
- `modules/wristband/{server,authorize,token,register,stores,client_issue}.go` - flavour field and dispatch, 404s, Kind, neutral Resource and comments
|
||||
- `modules/wristband/oidc_*_test.go` - memory fakes, tracer, ten smoke tests
|
||||
- `modules/wristband/{authorize,token,server,consent,registration,example}_test.go` - neutral literals, explicit resource, TestDefaultOptionsResourceEmpty, ExampleNewOIDCServer
|
||||
- `modules/wristband/README.md`, `README.md`, `docs/services/oauth-server.md` - OIDC documentation
|
||||
- `../fonoteka.go/plugins/golem15/fonoteka/plugin.go`, `oauth_resource_test.go` - explicit resource and its test
|
||||
|
||||
## Decisions Made
|
||||
|
||||
See `key-decisions` in the frontmatter. The main judgement calls beyond the plan text:
|
||||
- Redirect Cache-Control follows the plan (`no-store, private`), not PHP's Laravel default (`no-cache, private`). This is open for plan 03's tide replay.
|
||||
- `ConsentView` gained `RedirectHost` and `Incremental` (additive), which the PHP consent page shows.
|
||||
- `SubjectTypePublic` and `SubjectTypePairwise` constants were added, so the documented values of `OIDCClientRecord.SubjectType` exist in code.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. [Rule 3 - Blocking] Task boundaries moved for code that one task could not leave half-written**
|
||||
- **Found during:** Task 1
|
||||
- **Issue:** The tracer's code redirect needs the PHP encoder (Task 2's `oidc_redirect.go`). The token path already needed burn-then-check redemption and the full client-auth rules for a correct happy path. Resume needed the silent-consent branch to be complete.
|
||||
- **Fix:** Task 1 shipped `phpURLEncode`/`appendPHPQuery`, PHP client auth, burn-then-check redemption, the uniform userinfo 401 and the silent resume path. Task 2 added the loopback matcher, kinds, the remaining validation steps, switch account and the exported JSON operations; Task 3 added ES256, pairwise, refresh and client credentials. All planned smoke tests landed in their planned tasks.
|
||||
- **Verification:** each task's verify command passed at its commit.
|
||||
- **Committed in:** 659b42a, dfa3c3e, e25e733
|
||||
|
||||
**2. [Rule 1 - Bug] The plan's own acceptance grep would have matched a doc comment**
|
||||
- **Found during:** Task 3
|
||||
- **Issue:** A comment in `oidc_keys.go` named the golang-jwt package global, which breaks the "MarshalSingle... appears 0 times" criterion even though the code never touches it.
|
||||
- **Fix:** Reworded the comment.
|
||||
- **Committed in:** e25e733
|
||||
|
||||
**3. [Rule 2 - Missing critical] Non-OIDC clients refused at userinfo and token as well as authorize**
|
||||
- **Found during:** Task 2
|
||||
- **Fix:** Kind checks at all three OIDC entry points and both MCP entry points (T-19.4-05), as the threat register requires.
|
||||
- **Committed in:** dfa3c3e
|
||||
|
||||
---
|
||||
|
||||
**Total deviations:** 3 auto-fixed (1 blocking/sequencing, 1 bug, 1 missing critical). **Impact:** no scope change. Every name in the plan's Artifacts exists as planned. Additive extras: `ConsentView.RedirectHost`, `ConsentView.Incremental`, `SubjectTypePublic`, `SubjectTypePairwise`.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
- `../fonoteka.go` full Docker-backed run: `TestPhase09SecurityRoutes` fails on cabana admin routes that other phases added (account preferences, reorder, singleton). It has nothing to do with this plan; it is logged in `deferred-items.md`. Every fonoteka OAuth test passes (Docker-backed subset `OAuth|Phase8|Authorize|Token|Consent|Connected|Register` green, `go test -short ./plugins/golem15/fonoteka/...` green).
|
||||
- **Docker-backed tests run:** `TestOAuthResourceExplicit` PASS (real Postgres via testcontainers, no sudo needed); the fonoteka OAuth subset PASS; the full fonoteka plugin tree ran (one unrelated failure above). The framework's own Docker tests (`go test ./...` without `-short`) were not run; `go test -short ./...` is green.
|
||||
- **g15office:** not updated. The g15office MCP tools are not available in this executor session (no ToolSearch or MCP tools in the toolset). The orchestrator should move task `iarcg5dk` (folded todo) to Live with the comment "Closed in 19.4-01: DefaultOptions().Resource is empty, fonoteka sets its resource explicitly", and post a plan-executed comment on the phase 19.4 task `l7t8dbyl`.
|
||||
|
||||
## User Setup Required
|
||||
|
||||
None - no external service configuration required.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
- Plan 02 (sm-oauthserver-plugin) can implement `OIDCBackend`, `KeyStore` and `OIDCHost` against the identifiers above. Before installing go-oidc v3.21.0 and x/oauth2 v0.37.0 it needs the blocking human package check.
|
||||
- Open for plan 03's tide replay: redirect `Cache-Control` (`no-store, private` here vs PHP `no-cache, private`), id_token header member order (golang-jwt map order), the 500 body shape of discovery/JWKS.
|
||||
- Nothing is pushed or tagged. fonoteka must pick up a framework version with these changes together with commit 687e25a.
|
||||
|
||||
---
|
||||
*Phase: 19.4-oidc-provider-on-wristband*
|
||||
*Completed: 2026-10-19*
|
||||
@@ -0,0 +1,6 @@
|
||||
## Deferred Items
|
||||
|
||||
- fonoteka `TestPhase09SecurityRoutes` fails against the current framework (found by 19.4-01, outside its scope)
|
||||
status: open
|
||||
**What:** `go test ./plugins/golem15/fonoteka/...` in `../fonoteka.go` (Docker-backed, not `-short`) reports "unexpected admin routes" for cabana routes other phases added: `GET`/`PUT /plytadmin/api/v1/account/preferences`, `GET`/`POST .../{controller}/reorder` and `GET .../{controller}/singleton`. The test's admin route allowlist predates them. Nothing in wristband or the OAuth wiring is involved; every OAuth test in that package passes.
|
||||
**Where:** `../fonoteka.go/plugins/golem15/fonoteka/admin_phase09_security_test.go:52`.
|
||||
Reference in New Issue
Block a user