Files
summercms/.planning/phases/19.7-generic-mcp-module/deferred-items.md

22 KiB

Deferred items

Out-of-scope discoveries made while executing phase 19.7 plans. Nothing here was changed by the plan that recorded it.

Deferred Items

  • Pre-existing gofmt drift in cabana (found during 19.7-01 Task 1) status: open What: gofmt -l modules/cabana lists account_tokens.go, account_tokens_test.go, field_trigger_smoke_test.go, token_postgres_smoke_test.go, token_routes_test.go and token_scopes_test.go as unformatted before this plan touched the module (alignment of map literals and struct tags). Left as is: reformatting would add diff noise unrelated to the plan (a whole-file gofmt -w on token_routes_test.go rewrote 132 lines and was reverted). A one-line gofmt -w on those six files in a style(cabana) commit of its own fixes it.
  • AdminRecordAction.Prompt acceptance grep counted two prompt,omitempty tags in schema_types.go (found during 19.7-01 Task 4) status: resolved What: the second match is the pre-existing FormField.Prompt JSON tag at schema_types.go:376, not a duplicate of the new RecordAction.Prompt; the criterion's "prints 1" was written without that line in mind. No change needed.
  • Stateful pier sessions cannot carry the SDK's protocol-version context value (found during 19.7-02 Task 3) status: open What: go-sdk v1.8.0's StreamableHTTPHandler.ServeHTTP stores the Mcp-Protocol-Version header under an unexported context key that the transport's servePOST/serveGET read; pier's own session router (needed for Redis-shared sessions, D-25) serves StreamableServerTransport directly and cannot set that key, so stateful streams run with the SDK's 2025-03-26 defaults for stream close/reconnect hints and the JSON-RPC batch rule (lenient, spec-compliant for older clients; 2026-07-28 clients go through the SDK's own stateless handler). Revisit if a later SDK exports the key or a transport-level setter; plan 05 may add a test pinning the behaviour.
  • notifications/initialized landing on another instance than initialize (found during 19.7-02 Task 3) status: open What: the session state is persisted after initialize and again after the client's initialized notification; when a load balancer sends the notification to a second instance, the first instance's local copy keeps InitializedParams nil and refuses calls until that copy is dropped (session end, revocation or process restart) while the second instance serves them. Sticky sessions or a per-request re-read of the stored initialized flag would close the gap; not needed for the single-instance demo.
  • cabana's backend subject events never reach pier (found during 19.7-03 Task 2) status: resolved What: modules/cabana/subject_events.go fires pact.MCPSubjectChanged as a value (app.Events.Fire(ctx, pact.MCPSubjectChanged{...})), while modules/pier/events.go listens for a pointer (func(ctx, ev *pact.MCPSubjectChanged)). festival.Bus keys listeners by the type parameter, so pier never sees the backend subject events cabana fires (admin deletion, permission and role changes, token revocation): no list_changed and no session close for the backend guard from cabana. cabana also fires inside the save's transaction instead of after commit, so a rolled-back save would still notify. sm-user-plugin v0.1.10 fires *pact.MCPSubjectChanged (pointer) through lagoon.AfterCommit, which pier does receive. Fix in the framework: fire &pact.MCPSubjectChanged{} from cabana and defer it with lagoon.AfterCommit, with a pier test that a cabana save reaches handleEvent. Must be fixed before v0.5.0. Resolved: cabana now fires &pact.MCPSubjectChanged{} through lagoon.AfterCommit (immediately outside a transaction); subject_events_test.go pins delivery to a pointer listener, no firing before commit and none after a rollback.
  • pier publishes *pier.Pier once per application (found during 19.7-04 Task 1) status: open What: pier.Mount publishes the mounted endpoint on the app, so a second surf.BuildRouter/surf.Assemble on the same *backpack.App with mcp.enabled: true fails with backpack: duplicate provider for *pier.Pier. A binary builds its router once, so serving is unaffected; tests that build twice on one app (sm-demo-app TestRealtimeRoutes, socialServer) now set mcp.enabled: false. A find-or-replace publish (or skipping the publish when the same app already carries one) in pier would lift it.
  • MCP grants are not in the authorized-apps list (found during 19.7-04 Task 2) status: open What: the MCP flow (wristband IssueCode, sm-oauthserver-plugin's MCPStore) stamps the client's consented_at but writes no row to golem15_oauthserver_consents, so GET /oauth/authorizations does not list an MCP client and DELETE /oauth/authorizations/{client_id} answers 404 for it. The access token itself can still be revoked (row revoked_at, refused by the verifier at once) and the user cascade removes every grant, but a user has no self-service "disconnect this agent" for MCP yet. A consent row per MCP grant (written at IssueCode) would make the existing list and revocation (which already fires the revoked event) cover MCP clients; plan 05's revoke matrix should decide. Plan 05 decision: accepted for this phase (19.7-SECURITY.md AR-19.7-03): access still ends through client revocation, user deletion or deactivation, token expiry and the verifier's refusal of a revoked token, and TestMCPEndToEnd pins that the revoked event ends the session. The consent row per MCP grant stays the follow-up.
  • Pre-existing gofmt drift in sm-journal-plugin (found during 19.7-04 Task 3) status: open What: gofmt -l lists classes/blog_editor_test.go before this plan touched the repository. Left as is.
  • sm-demo-app push and the demo deploy wait for the user (found during 19.7-04 Task 5) status: open What: sm-demo-app master is eight commits ahead of origin (a25444e, 985af53 and this plan's 44cac13, 8668918, the pins aec16c4 oauthserver v0.1.2, 996586c journal v0.2.3, 7e76957 vue-demo-app 21a22b6, and c21ae9d with the nginx MCP locations and DEPLOY.md "MCP endpoint"). None is pushed and nothing is deployed: both need the user's go-ahead. The deploy also installs the changed vhost (DEPLOY.md, "When deploy/nginx/demo.summercms.io.conf changed") and runs the MCP smoke. The sm-oauthserver-plugin v0.1.2 and sm-journal-plugin v0.2.3 tags and the vue-demo-app commit are pushed.
  • Release builds pinning summercms v0.4.4 lack the cabana subject-event fix (found during 19.7-04 Task 5) status: open What: the demo (GOWORK=off release build), oauthserver v0.1.2 and Journal v0.2.3 require summercms v0.4.4, which predates d96c726 (cabana fires *pact.MCPSubjectChanged after commit). Development builds use the go.work replace and carry the fix; a release binary does not: admin deletion, permission or role changes and token revocation in the admin do not end MCP sessions or send list_changed until the next framework tag is required by the demo. The verifier and the backend resolver still refuse at the next request, so access does not outlive the change; only the live session notification is missing. Deploy the demo after the next framework tag, or accept the gap for the demo.
  • Redis for MCP sessions on rome (found during 19.7-04 Task 5) status: open What: the demo runs one instance with mcp.sessions.store: memory. A second instance needs SUMMER_MCP__SESSIONS__STORE=redis and SUMMER_REDIS__URL on every instance (DEPLOY.md, "MCP endpoint") and a Redis on rome; also see the notifications/initialized item above.
  • Real MCP client check of the demo not run (found during 19.7-04 Task 5) status: open What: Task 5's human check (a real client against the local demo: OAuth sign-in on /connect, user_me only, link an admin through /link-admin, reconnect with backend, the journal_ tools, a draft post, unlink) was not performed in the executor run; the automated TestMCPEndToEnd covers the same path. The local setup is DEPLOY.md, "MCP endpoint", "Locally".
  • Retracted github.com/gabriel-vasile/mimetype v1.4.15 in sm-demo-app (found during 19.7-04 Task 5) status: open What: go get in sm-demo-app warns that mimetype v1.4.15 is retracted (quadratic time on crafted CDF files). It arrives indirectly and predates this plan; a go get github.com/gabriel-vasile/mimetype@latest in the framework (and a tag) would lift it for every consumer.
  • Framework fixes of plan 05 are on master only (found during 19.7-05 Tasks 1-2) status: open What: c49983d fix(cabana) (the backend middleware refuses a dispatch marker with a frontend principal), c29d228 fix(pier) (a tools/call without arguments) and 1111828 fix(pier) (an ended session is not written back by a late request) are not in any tag. Applications pinning summercms v0.4.4 (the demo's release build, oauthserver v0.1.2, Journal v0.2.3) lack them until they require the next framework tag (v0.5.0 at phase completion, after the user's go-ahead). Gap closure (19.7-09): the same holds for every framework fix of plans 06 and 07; they ship together in v0.5.0 (see "Release order after the gap closure" below).
  • Plan 05 test commits are local and untagged (found during 19.7-05 Task 3) status: resolved What: sm-user-plugin 72de822 (in the sm-demo-app submodule checkout plugins/golem15/user), sm-oauthserver-plugin 2929c91, sm-journal-plugin a9a9b80 and sm-demo-app 6fa2d56 add tests only; none is pushed or tagged. sm-demo-app's plugins/golem15/user working tree is ahead of its v0.1.10 pin by that test-only commit and the pointer is not committed; scripts/check-pins.sh reports the mismatch, which the phase gate accepts only because the commits touch *_test.go files alone. A patch tag (sm-user-plugin v0.1.11, sm-oauthserver-plugin v0.1.3, sm-journal-plugin v0.2.4) is optional; it would change no shipped code. Push and tags wait for the user. Superseded (19.7-09): the plugins now also carry the non-test fixes of plan 08 and the tests of plan 09, so the plan 05 test commits ship in the gap-closure plugin tags that require framework v0.5.0; the entries "Demo plugin submodules and pointers wait for the release" and "Release order after the gap closure" below track them.
  • Superuser refusal switch, conditional (found during 19.7-05 Task 4) status: open What: the security review accepted superuser reach through an agent (19.7-SECURITY.md AR-19.7-02) and did not add mcp.refuse_superuser_backend. When a plugin declares a superuser-only or destructive backend tool beyond content editing, add the switch (a fix(pier) with README and docs) and cut a framework patch tag with the user's go-ahead.
  • B-IN-05: a prompted record action refused with 422 closes the dialog (found in the code review, deferred by 19.7-07) status: open What: when a prompted record action answers 422, the SPA shows the error toast and closes the prompt dialog, so the typed text is lost. Plan 07 corrected the contract text (the 422 toast behaviour is now what the docs and the OpenAPI description say). Keeping the dialog open needs a change to the shared confirm primitive every action uses (the dialog resolves before the request answers), which is out of the gap closure's scope. Follow-up: let the confirm primitive keep the dialog open on a refused submit and show the field error inline.
  • B-IN-06: window.open after an awaited POST can be blocked by a popup blocker (found in the code review) status: resolved What: no fix required. My account always renders the answered URL as a visible link next to the section (data-account-contribution-url), so an administrator whose browser blocked the popup clicks the link (pinned by admin/tests/account/AccountContributions.test.ts).
  • admin:token:revoke fires no MCP event (found during 19.7-07) status: open What: the CLI command revokes an admin personal token's row but does not fire the token-specific revoked subject event that the My account revoke fires (fireTokenRevoked). The verifier side refuses the revoked token on the next request, so access ends; only a live MCP session of that token keeps its stream until its next request or re-check. Same shape as before plan 07. Fix: call the same helper from the command after its transaction. Re-review (RB-IN-03): kept deferred. Calling fireTokenRevoked from the command is not enough: the command runs in its own process, which mounts no MCP endpoint, so no pier listener receives the event and nothing reaches the store a running server reads. Closing it needs the command to publish into pier's store directly (a cross-module seam). 4d7eb9d documents in the cabana README that only the My account revoke fires the event.
  • RB-IN-04 optional narrowing: a focused widget host also mutes Esc, / and ? (found in the gap-closure re-review) status: open What: 89b85f5 documents the trade-off in admin/src/app/hotkeys.ts. The optional narrowing to plugin field types (a data-field-type marker) was not made: a type: widget element with an input in a closed shadow root would then let Esc leave the form while the admin types in it.
  • OIDC grants on a ban or a password reset (decided during 19.7-08) status: resolved What: the user decided on 2026-10-23 that a ban, a password reset, an administrator setting a password and the user's own password change end both the MCP and the OIDC grants of the user (tokens revoked, consents and unused codes deleted, subjects kept). Implemented by Cascade.OnAccessRevoked (sm-oauthserver-plugin 671513d) and pinned by TestMCPGrantRevocation ("access revoked ends earlier MCP and OIDC grants and keeps later ones") and TestMCPRevocationSmoke.
  • Local sm-oauthserver-plugin go.work retarget (found during 19.7-08 Task 2) status: open What: the tracked go.work of sm-oauthserver-plugin is modified in the working tree only: its sm-user-plugin replace points at ../sm-demo-app/plugins/golem15/user (the canonical user checkout of the gap closure) instead of the committed ../fonoteka.go/plugins/golem15/user. The phase gate's workspace check requires exactly that target, so the gate passes on this machine only with the local edit. Before the release either restore the committed line or decide whether go.work should leave version control; never commit the local retarget.
  • Demo plugin submodules and pointers wait for the release (found during 19.7-08, updated by 19.7-09) status: open What: in sm-demo-app the working-tree checkouts of plugins/golem15/oauthserver (671513d, its canonical checkout is at 65f71dc with plan 09's test), plugins/golem15/journal (b129902, canonical at 2aeeccc) and plugins/golem15/user (the canonical user checkout itself, 38551a4) are ahead of their committed pins (v0.1.2, v0.2.3, v0.1.10) by unreleased commits. The gate reports them as pins: … is ahead of its pin by unreleased commits and refuses them with PHASE197_RELEASED=1. The pointers are committed only after the plugin tags. sm-demo-app's vue-demo-app pointer is committed (00f57bf, then f53d30a for the link code test): vue-demo-app (0125e29, b973a76) must be pushed before sm-demo-app.
  • Release order after the gap closure (decided 2026-10-23) status: open What: nothing of plans 06-09 is tagged or pushed. After the re-review of the gap closure, phase verification, Nyquist validation and UAT pass, and on the user's go-ahead:
    1. framework v0.5.0 (and the changelog post);
    2. the sm-user-plugin, sm-oauthserver-plugin and sm-journal-plugin tags that require it;
    3. the bumps in sm-demo-app, fonoteka.go and sm-bm-app, with vue-demo-app pushed before sm-demo-app;
    4. sm-servers-app picks up E-1 and E-2 after the user plugin tag;
    5. then PHASE197_RELEASED=1 bash scripts/check-phase19.7.sh --consumers must pass (every demo pin on its tag, no drift).
  • readLinkCode accepts a valid code next to other fragment parameters (found during 19.7-09 Task 3) status: resolved (gap-closure re-review: not a defect; vue-demo-app 9c247ab pins the lenient parse) What: plan 09 described "extra parameters give ''" for the link page's code reader. readLinkCode parses the fragment with URLSearchParams, so #code=<64 hex>&next=/x answers the code, while an extended or foreign value (#code=<hex>x, #other=<hex>) answers ''. linkCode.test.ts pins the latter and leaves the former unasserted. My account only ever sends #code=<code>, so nothing depends on it; the re-review decides whether to keep the lenient parse or require the fragment to be exactly code=<64 hex>.
  • docs/examples/blog test failed once on GOPROXY=off (found during 19.7-09 Task 1) status: open What: one go test -short ./... run of the gate's go stage failed in docs/examples/blog with "module lookup disabled by GOPROXY=off" for golang.org/x/exp; the package passed alone and in more than twenty later full runs. The test builds a scratch module offline and depends on the module cache holding every dependency; a run that coincides with a cache change may miss one. Not caused by this phase's code; watch for a recurrence.
  • RA-IN-04: the cap of the non-reserving store fallback is still check-then-act (found in the gap-closure re-review, deferred by its lane A fixes) status: open What: 034aa19 makes serveNew's fallback for a store without Reserve write the record before the server is built, so a reaper pass can no longer close a session that has no record yet. The Count-then-Put cap of that fallback remains racy. Closing it means making Reserve part of the exported pier.Store interface or refusing a store without it at mount, which changes pier's exported API for a branch only a test wrapper (WrapStore) reaches; both shipped stores reserve atomically.
  • RA-WR-06 optional: the demo pin check does not require unreleased commits to be on the canonical checkout's upstream (found in the gap-closure re-review) status: open What: 2fdda99 qualifies the gate's verdict ("Phase 19.7 gate passed WITH UNRELEASED PINS: the release pins are not yet proven") while demo submodules carry unreleased non-test commits; the exit status stays 0 so the pre-release verification can run. The review's optional git branch -r --contains check was not added: the gap-closure commits are deliberately unpushed until the release order runs, so it would refuse every pre-release run. PHASE197_RELEASED=1 after the tags is the release proof.
  • RC-CR-01 optional: base the session cutoff on auth_time (found in the gap-closure re-review, lane C) status: open What: the self-service password change now revokes every OAuth grant as of the request (sm-user-plugin 30f7a01). The calling session's own cutoff is still its iat second, so an attacker's frontend session JWT refreshed after the victim's iat survives the change too. That predates this phase; closing it means basing tokens_valid_after on the calling token's auth_time and having POST /refresh and the jwt guard compare auth_time with the cutoff, a change to session revocation in a core plugin (and to how bouncer's refresh is checked) that needs its own decision.
  • RC-IN-01 nginx: keep the deprecated link GET route's code out of access logs (found in the gap-closure re-review, lane C) status: open What: sm-user-plugin 92676ba makes the show and confirm POSTs ignore a code in the query string. GET /_user/api/v1/backend-link/{code} stays for compatibility (removed only in a major release), so nginx still logs the code if a third-party client calls it. access_log off; (or a log_format without $request_uri) for location ~ ^/_user/api/v1/backend-link/ in the demo's nginx config is a deploy change, made with the next demo deploy; the shipped page never calls the GET route.
  • RC-IN-06: the legacy MintAPIToken fallback is widened before the phrase-key check (found in the gap-closure re-review, lane C) status: open What: classes.CompileAPITokenScopes adds the declared names to the process-wide fallback set before bootAPITokenScopes checks their phrase keys, so a boot that fails on a missing key has already widened what MintAPIToken accepts. Moving the add after the check needs a new exported seam or a behaviour change of the exported CompileAPITokenScopes in a core plugin; a failed boot ends the process, so the widened set is never used in production. MintAPIToken is now marked Deprecated: in favour of MintAPITokenFor (sm-user-plugin 9892404), which checks the application's own catalog.
  • RC-IN-07: sm-oauthserver-plugin builds only through its workspace file (found in the gap-closure re-review, lane C) status: open (release-time) What: the plugin uses userclasses.AccessRevokedEvent, FireSubjectEvent and FireSubjectEventAfterCommit, which the required sm-user-plugin v0.1.10 lacks, so GOWORK=off go build ./... fails. The release checklist for the oauthserver tag must bump sm-user-plugin to the new user plugin tag and pass GOWORK=off go build ./... before the tag; the requires are not bumped before the user plugin is tagged.
  • Lane C re-review fixes moved the demo's oauthserver checkout (gap-closure re-review, lane C) status: open What: pier now gives a backend subject only to a grant that records its consented administrator (pact.MCPGrant.BackendUserID, framework c37c6ce), so the demo's e2e tests need the oauthserver commits that record it. The working-tree checkout of sm-demo-app's plugins/golem15/oauthserver was moved from 671513d to the canonical b6a9586 (detached, fast-forward; the pointer stays uncommitted until the plugin tag, as before). Canonical heads after the lane C fixes: sm-user-plugin 9892404, sm-oauthserver-plugin 9813c1d, sm-journal-plugin 521df9a (its prompt test's fake grants now name the consented administrator), vue-demo-app 9c247ab. backend grants issued before the oauthserver migration 202610230001 record no administrator and stop acting as one until the agent is connected again.