docs(08-08): complete mcp-me-prerequisite plan

This commit is contained in:
Jakub Zych
2026-09-23 22:23:40 +02:00
parent 27845490e8
commit f13f76ed4b
3 changed files with 143 additions and 9 deletions

View File

@@ -350,7 +350,7 @@ Plans:
**Wave 7** *(parallel; blocked on 08-06)*
- [x] 08-07-PLAN.md — Provision confidential clients through the exact operator command
- [ ] 08-08-PLAN.md — Serve the MCP personal-token bootstrap on the existing token surface
- [x] 08-08-PLAN.md — Serve the MCP personal-token bootstrap on the existing token surface
**Wave 8** *(blocked on 08-07 and 08-08)*
@@ -496,7 +496,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 →
| 5. Data layer full fidelity | 6/6 | Complete | 2026-09-18 |
| 6. HTTP routing, auth groups and rate limiting | 14/14 | Complete | 2026-09-21 |
| 7. User plugin and authentication | 8/8 | Complete | 2026-09-23 |
| 8. OAuth2.1 authorization server | 7/10 | In Progress| |
| 8. OAuth2.1 authorization server | 8/10 | In Progress| |
| 9. Backend admin authentication and schema pipeline | 0/TBD | Not started | - |
| 10. Admin Vue SPA | 0/TBD | Not started | - |
| 11. Jobs, realtime and search infrastructure | 0/TBD | Not started | - |

View File

@@ -3,14 +3,14 @@ gsd_state_version: 1.0
milestone: v1.0
milestone_name: milestone
status: executing
stopped_at: Completed 08-07-PLAN.md
last_updated: "2026-09-23T20:05:28.781Z"
stopped_at: Completed 08-08-PLAN.md
last_updated: "2026-09-23T20:23:27.498Z"
last_activity: 2026-09-23
progress:
total_phases: 15
completed_phases: 7
total_plans: 55
completed_plans: 52
completed_plans: 53
percent: 47
---
@@ -26,11 +26,11 @@ See: .planning/PROJECT.md (updated 2026-09-16)
## Current Position
Phase: 08 (oauth2-1-authorization-server) — EXECUTING
Plan: 8 of 10
Plan: 9 of 10
Status: Ready to execute
Last activity: 2026-09-23
Progress: [██████████] 95%
Progress: [██████████] 96%
## Performance Metrics
@@ -98,6 +98,7 @@ Progress: [██████████] 95%
| Phase 08 P05 | 55min | 3 tasks | 12 files |
| Phase 08 P06 | 50min | 3 tasks | 10 files |
| Phase 08 P07 | 35min | 2 tasks | 9 files |
| Phase 08 P08 | 20min | 2 tasks | 4 files |
## Accumulated Context
@@ -244,6 +245,9 @@ Recent decisions affecting current work:
- [Phase ?]: [Phase 08 P07]: list/update operate directly on models.OAuthClient via *gorm.DB rather than extending wristband.ClientStore -- only client issuance needs the shared wristband hash/validation path (T-08-SECRET-TIMING)
- [Phase ?]: [Phase 08 P07]: Fixed clientRecordToModel to persist ScopeCeiling, a mapping gap silent since 08-02 because DCR never sets a ceiling
- [Phase ?]: [Phase 08 P07]: AUTH-05/AUTH-07 remain Pending in REQUIREMENTS.md -- both requirements' full text still needs 08-08/08-09's unchanged fonoteka-mcp install/auth flow proof
- [Phase ?]: [Phase 08 P08]: me_token_controller.go was created in the Task 1 RED commit as a compiling 501 stub (Rule 3), following the 08-04/08-06/08-07 precedent -- controllers/api must compile with its new test file while the route stays unmounted
- [Phase ?]: [Phase 08 P08]: scopes/collection_ids on GET /api/v1/fonoteka/me always serialize via wire.Slice (nil -> []), a deliberate divergence from PHP's collection_ids null-means-unrestricted semantics, accepted because fonoteka-mcp's TS MeResponse type never reads collection_ids
- [Phase ?]: [Phase 08 P08]: AUTH-07 stays Pending in REQUIREMENTS.md -- this plan ships the /me prerequisite but the requirement's 'fonoteka-mcp completes its install and auth flow unchanged' clause needs 08-09's real MCP gate
### Pending Todos
@@ -265,6 +269,6 @@ Items acknowledged and carried forward from previous milestone close:
## Session Continuity
Last session: 2026-09-23T20:05:28.751Z
Stopped at: Completed 08-07-PLAN.md
Last session: 2026-09-23T20:23:27.474Z
Stopped at: Completed 08-08-PLAN.md
Resume file: None

View File

@@ -0,0 +1,130 @@
---
phase: 08-oauth2-1-authorization-server
plan: 08
subsystem: auth
tags: [mcp, personal-token, inv_token, bouncer, fonoteka-mcp-prerequisite]
# Dependency graph
requires:
- phase: 08-oauth2-1-authorization-server
plan: 06
provides: "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"
provides:
- "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"
affects: [08-09-parity-and-real-mcp-gate, 08-10-unit-tests-and-security-review]
# Tech tracking
tech-stack:
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"
key-files:
created:
- ../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
modified:
- ../fonoteka.go/plugins/golem15/fonoteka/routes.go
key-decisions:
- "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"
patterns-established:
- "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"
requirements-completed: []
# Metrics
duration: ~20min
completed: 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*