Files
summercms/.planning/phases/08-oauth2-1-authorization-server/08-08-SUMMARY.md
2026-09-23 22:23:40 +02:00

12 KiB

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, duration, completed
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions patterns-established requirements-completed duration completed
08-oauth2-1-authorization-server 08 auth
mcp
personal-token
inv_token
bouncer
fonoteka-mcp-prerequisite
phase plan provides
08-oauth2-1-authorization-server 06 the D-17 sweep/lifecycle wave this plan builds on top of; more directly, Phase 7's inv_token TokenGuard and inv.scope:read middleware this plan's route reuses unchanged
GET /api/v1/fonoteka/me on the existing personal-token group: the minimal exact bootstrap payload (scopes, collection_ids, user_id, name) fonoteka-mcp's client.me() call needs before constructing its MCP server (D-20)
controllers/api.MeToken: reads bouncer.Credential/bouncer.User directly, no bearer reparse, no second token query
08-09-parity-and-real-mcp-gate
08-10-unit-tests-and-security-review
added patterns
MeToken is the first handler in this codebase that reads bouncer.Credential purely for response serialization (not authorization) -- it trusts the inv_token guard's already-matched *models.ApiToken and never touches gorm.DB, proven by a test that mutates the persisted row after the credential was matched and asserts the response still reflects the pre-mutation in-memory value
scopes/collection_ids serialize through wire.Slice so a nil Jsonable[[]T] (unset scopes or an unrestricted/no-collection token) always emits [] on this endpoint, not null -- an explicit, plan-directed simplification of PHP's collectionIds() null-means-unrestricted semantics, chosen because the unchanged TS MeResponse type does not even consume collection_ids
created modified
../fonoteka.go/plugins/golem15/fonoteka/controllers/api/me_token_controller.go
../fonoteka.go/plugins/golem15/fonoteka/controllers/api/me_token_controller_test.go
../fonoteka.go/plugins/golem15/fonoteka/oauth_tools_test.go
../fonoteka.go/plugins/golem15/fonoteka/routes.go
controllers/api/me_token_controller.go was created in the Task 1 RED commit (not literally in Task 1's files_modified list) as a compiling 501 stub, following the 08-04/08-06/08-07 precedent of shipping a minimal compiling seam in the RED commit so the package and its new test file build while the route stays unmounted; Task 2 replaced the stub body and left the file's identity/location unchanged (Rule 3 -- blocking compile prerequisite)
user_id is emitted as *uint (nil-able), not a bare uint defaulting to 0, to match PHP's $user?->id nullable semantics even though the inv_token guard always populates both Credential and User together in production, so this path is defensive-only
scopes/collection_ids always serialize via wire.Slice (nil -> []), per Task 1's explicit behavior/acceptance criteria ("arrays are never null") -- a deliberate divergence from PHP's collection_ids null-means-unrestricted contract, accepted because fonoteka-mcp's TS MeResponse type does not read collection_ids at all
AUTH-07 stays Pending in REQUIREMENTS.md: its full text also requires 'fonoteka-mcp completes its install and auth flow unchanged', which only 08-09's real MCP gate (D-14) can prove; this plan ships the /me prerequisite that gate needs, not the gate itself
Route-isolation assertion (assertMeRouteIsolation in oauth_tools_test.go) is reused by both the RED anchor and a standalone named test, following the shared-helper convention 08-06's oauthLifecycleGrant established for reusable multi-test setup
~20min 2026-09-23

Phase 08 Plan 08: MCP /me Prerequisite Summary

GET /api/v1/fonoteka/me on the personal-token group returns exactly scopes/collection_ids/user_id/name straight off the inv_token guard's already-matched credential, with no bearer reparse and no second database query, unblocking 08-09's real fonoteka-mcp gate.

Performance

  • Duration: ~20 min
  • Started: ~2026-09-23T20:00:00Z (approx.)
  • Completed: 2026-09-23T20:19:37Z
  • Tasks: 2 completed (2 commits: RED then GREEN, both in fonoteka.go; no summercms.go changes this plan)
  • Files modified: 4 (3 created, 1 modified, all in fonoteka.go)

Accomplishments

  • controllers/api.MeToken serves the exact PHP MeTokenController::me contract as a Go handler: {"data":{"scopes":[...],"collection_ids":[...],"user_id":...,"name":...}}, sourced entirely from bouncer.Credential/bouncer.User -- the request context values the real inv_token guard already populated during its own bearer-parse/hash/DB-lookup pass, so the handler performs zero additional queries
  • wire.Slice on both scopes and collection_ids guarantees neither array ever serializes as JSON null, even for a token minted with no explicit scopes/collections (a TestMeTokenHandlerNilScopesAndCollectionIDsSerializeAsEmptyArraysAndNullableName regression proves this directly against the raw response bytes)
  • TestMeTokenHandlerReusesMatchedCredentialWithoutReQuerying proves the "no second lookup" contract mechanically: it mutates the persisted token row's name column after the credential was matched and asserts the response still reflects the original in-memory value -- if the handler ever re-queried, this test would catch it
  • GET /me is mounted as the third route on the existing /api/v1/fonoteka personal-token group, inheriting inv_token, throttle:fonoteka-api-token, inv.scope:read in that exact order (proven by route-table inspection, not just manual reading of routes.go); missing/unknown-token 401 and wrong-scope 403 bytes are byte-identical to the pre-existing token-surface contract, with no new header
  • Both the Phase 8 RED sentinel (PHASE8_RED:mcp-me in fonoteka, verified fail-closed via scripts/check-phase8-red.sh against the genuine pre-implementation state -- route unmounted, 404 instead of the exact positive body) and the full GREEN suite (go vet/go test/go test -race across every subpackage of plugins/golem15/fonoteka plus plugins/golem15/user) are green

Task Commits

Both tasks carry tdd="true"; RED then GREEN landed as separate commits, both in fonoteka.go (this plan makes no summercms.go changes):

  1. Task 1: RED anchor -- 686d622 (test): TestPhase8RedMCPMe (positive payload, missing/wrong-scope negative bytes, route-table isolation) fails with PHASE8_RED:mcp-me against the unmounted route; controllers/api.MeToken ships as a compiling 501 stub so the package and its new test files build. Verified fail-closed via scripts/check-phase8-red.sh.
  2. Task 2: implement and mount -- 19ac5fd (feat): the real MeToken handler and GET /me mounted on the personal-token group in routes.go.

Plan metadata: committed as part of this summary/state-update commit.

Files Created/Modified

  • ../fonoteka.go/plugins/golem15/fonoteka/controllers/api/me_token_controller.go -- MeToken (real implementation, Task 2; compiling 501 stub in Task 1)
  • ../fonoteka.go/plugins/golem15/fonoteka/controllers/api/me_token_controller_test.go -- TestMeTokenHandlerExactFourFieldsWithScopesAndCollectionIDs, TestMeTokenHandlerNilScopesAndCollectionIDsSerializeAsEmptyArraysAndNullableName, TestMeTokenHandlerReusesMatchedCredentialWithoutReQuerying (package-local unit tests injecting an already-matched credential/principal into context, matching the connected_app_controller_test.go precedent)
  • ../fonoteka.go/plugins/golem15/fonoteka/oauth_tools_test.go -- TestPhase8RedMCPMe (RED anchor), TestOAuthToolsMePositiveAndNegativeContract, TestOAuthToolsMeRouteIsolation, plus shared helpers meToolsInsertToken/meToolsBearerRequest/assertMeRouteIsolation
  • ../fonoteka.go/plugins/golem15/fonoteka/routes.go -- mounts GET /me on the existing /api/v1/fonoteka personal-token group, after /genres

Decisions Made

See frontmatter key-decisions. The most load-bearing: shipping me_token_controller.go as a compiling 501 stub in the RED commit (rather than leaving the file entirely absent until Task 2) follows the exact precedent 08-04's Server.Token stub, 08-06's store-interface extensions, and 08-07's bonfire compiling seam all established -- a RED commit is allowed to add non-literally-listed compile prerequisites when the alternative is an uncompilable package, and this is tracked here as a Rule 3 (blocking issue) auto-fix rather than a silent scope change.

Deviations from Plan

Auto-fixed Issues

1. [Rule 3 - Blocking] me_token_controller.go added in the Task 1 RED commit, not literally in Task 1's files_modified

  • Found during: Task 1, writing me_token_controller_test.go and oauth_tools_test.go
  • Issue: Task 1's plan-listed files are only the two test files. Without me_token_controller.go existing at all, controllers/api would not compile once its test file referenced MeToken, and go test ./plugins/golem15/fonoteka (the RED verify command's target) depends on controllers/api compiling as a regular dependency.
  • Fix: Added a minimal MeToken stub returning an opaque 500, in the same file Task 2's files_modified list already names, so the RED commit compiles cleanly and the RED failure comes from the intended source (unmounted route, 404) rather than a build error.
  • Files modified: ../fonoteka.go/plugins/golem15/fonoteka/controllers/api/me_token_controller.go
  • Verification: go vet ./plugins/golem15/fonoteka/... clean at the RED commit; scripts/check-phase8-red.sh confirms the RED failure is exactly PHASE8_RED:mcp-me with no build/setup/panic noise.
  • Committed in: 686d622 (Task 1 RED commit)

Total deviations: 1 auto-fixed (compile prerequisite, no scope change) Impact on plan: None beyond making the RED commit itself buildable; Task 2 implemented the handler in the same file, at the same location, matching the plan's own file list.

Issues Encountered

None beyond the auto-fixed item above. Full go vet/go test ./... are green across every subpackage of fonoteka.go's plugins/golem15/fonoteka module (classes, classes/auth, console, controllers/api, middleware, models, updates) and plugins/golem15/user; go test -race on the touched packages (controllers/api, top-level fonoteka) is also green. go build ./... at the fonoteka.go workspace root is clean. This plan makes no summercms.go changes, so summercms.go's own go vet/go test ./... are unaffected (not re-run).

User Setup Required

None -- no external service configuration required.

Next Phase Readiness

  • GET /api/v1/fonoteka/me is live on the assembled app and ready for 08-09's real fonoteka-mcp process (started via scripts/check-phase8.sh, D-14) to call as its first authenticated request before constructing the MCP server.
  • The response's scopes field is exactly what fonoteka-mcp's scopeGatedTools-style factory needs to gate tool visibility (a read-only token never sees write/ai tools) -- no further backend change is anticipated for that consumption path.
  • AUTH-07 remains Pending in REQUIREMENTS.md: this plan ships the /me prerequisite its text implicitly depends on, but the requirement's own "fonoteka-mcp completes its install and auth flow unchanged" clause is only provable once 08-09's real Node MCP gate runs end to end against the Go backend.
  • No blockers.

Self-Check: PASSED

  • FOUND: ../fonoteka.go/plugins/golem15/fonoteka/controllers/api/me_token_controller.go
  • FOUND: ../fonoteka.go/plugins/golem15/fonoteka/controllers/api/me_token_controller_test.go
  • FOUND: ../fonoteka.go/plugins/golem15/fonoteka/oauth_tools_test.go
  • FOUND: ../fonoteka.go/plugins/golem15/fonoteka/routes.go (modified)
  • FOUND commits (fonoteka.go): 686d622, 19ac5fd

Phase: 08-oauth2-1-authorization-server Completed: 2026-09-23