- TestEndpointGuards: a foreign browser Origin is 403 before the bearer is
read, a body over the endpoint cap is 413, a long GET stream ends once
its grant stops verifying (T-19.7-18, T-19.7-19)
- TestDispatch: an argument key the input struct does not declare is
refused before dispatch (T-19.7-17)
- the gate requires TestEndpointGuards (untagged and tagged) and runs the
demo's deploy check (T-19.7-37)
- plugins: vet, short tests and the additive-export check of the user
(GOWORK=off, against 930b158 and v0.1.9), OAuth server (d31f8e7) and blog
(ffa2c8f) plugins, the blog plugin's MCP tests by name
- consumers: the three applications build and pass at their pins, the demo
also with the MCP tag; the pin check accepts a plugin submodule ahead of
its pin only by test-only commits
- docker: the cabana, user plugin, OAuth server and demo Postgres suites by
name
- docs: decision note and STACK rows, docs checks, no consuming application
named in sources, docs or the phase's tests
- self-test proves additive_check on a planted repository
- pier: TestCatalog, TestDispatch, TestGuardRules, TestSessions (Redis
across two instances and the memory store), TestFanOut; the store clock
and stateless-server seams in export_test.go
- surf: TestStreamingRoute (throttle, CORS profile and access-log pattern on
a stream route) and TestPierMount in both builds; the streaming smoke
restores the mount hook it replaces
- wristband TestMCPScopes; cabana TestAccountContributions (Postgres part:
transaction and unchanged My account payloads), TestRecordActionPrompt,
TestScopeLimitForMethods; TestRecordActionPromptRun pins the empty Value
of an action without a prompt
- vitest: 403 and failed-load cases for My account contributions, the
cancelled prompt
- gate: every name required (tagged runs included), account.go unchanged
since 51d088a, admin stage live
- persisting a session's protocol state after a request now rewrites an
existing record only (Redis SET XX, the memory store likewise) and closes
the local copy when the record is gone
- before, a request that finished after DELETE, a revoked or deleted subject
event or the idle expiry put the record back, and the next request
rehydrated the ended session
- found by plan 19.7-05's TestFanOut (revocation and deletion cases, about
one run in a hundred)
- MCP's tools/call arguments are optional; pier validated a JSON null
against the input schema and refused every call of a tool without
required input that an agent sent without arguments
- found by plan 19.7-05's TestDispatch
- scripts/check-phase19.7.sh: fail-closed multi-repo gate (self-test, go,
marker; plugins, consumers, docker, admin and docs are stubs until the
later tasks fill them)
- modules/pier/helpers_test.go: the plan 02 fixtures moved out of the smokes
plus fakeGuard, fakeVerifier, fakeLinkResolver, fakePersonalResolver,
routePlugin, newApp, twoInstances, sdkClient and fireSubjectChanged
- TestToolsList (pier), TestDispatchMarker (bouncer, cabana with a Postgres
part through the screens harness, which now keeps its app)
- cabana wraps the bouncer backend guard middleware: a marker that lists the
backend guard with Backend false answers 401 before the guard honours it,
as ResolveBackend already does, so a frontend user id is never read as an
administrator's by the admin API or a backend-guarded plugin route
- found by plan 19.7-05's TestDispatchMarker (admin API in-process case)
- SUMMARY with every deviation from the checkpoint report
- deferred items: sm-demo-app push and deploy wait for the user, release
builds on summercms v0.4.4 lack the cabana subject-event fix, Redis for a
second instance, the real-client check, the retracted mimetype
Plan 03 summary: verified account link, Administrators-screen link actions, plugin frontend permissions, user MCP guard and user_me in sm-user-plugin v0.1.10 (requires SummerCMS v0.4.4). deferred-items: cabana fires pact.MCPSubjectChanged as a value inside the transaction while pier listens for a pointer; fix before v0.5.0.
- hotkey core: combos, AltGr and layouts, labels, aria-keyshortcuts, editable
targets, modal root, refcounted install, disabled and repeat
- smoke: record, create, Create and new (new session key), Esc, modal scope,
settings Save and close, My account, list search and the help dialog
- ShortcutHelpDialog rows, keys and close button
- '?' help dialog (ShortcutHelpDialog) listing the current screen's shortcuts
- Esc = Cancel on record forms; '/' focuses the list search
- settings forms: Save and close (Ctrl/Cmd+Enter, then /settings) and Ctrl/Cmd+S
- My account: Ctrl/Cmd+S
- modal forms: Ctrl/Cmd+S and Ctrl/Cmd+Enter submit the innermost dialog only
- docs: Keyboard shortcuts section in the admin SPA page
- app/hotkeys.ts: dependency-free data-hotkey core (Ctrl/Cmd+S, Ctrl/Cmd+Enter,
Ctrl/Cmd+Shift+S, Esc, / and ?), innermost-modal root, capture-phase modifier
listener, platform labels and the help-dialog state
- Button hotkey prop (data-hotkey, aria-keyshortcuts); shortcuts installed by AppShell
- FormView: save keys and tooltips, Create and new on create forms via remountView
- phrasebook: form.create_and_new and the shortcuts group (en, pl)
- personal tokens only behind mcp.allow_personal_tokens (401 without a
lookup otherwise); an admin token yields a backend-only identity with
its scope limit per dispatched method, a user token a frontend-only one
- read/write ceiling and prompts proven by the tracer's subtests; the
audit line asserted to carry the tool and never the arguments or token
- the mounted *pier.Pier is published on the application (test seams in
export_test.go)
- docs/services/mcp.md with the tested acme.blog example, site.yaml note,
cross-links from authentication.md, registration.md and the surf README
- store_redis.go: records with PX TTLs, per-binding sets, pub/sub events,
opened lazily from redis.url under cooler's prefix (nothing dials at mount)
- events.go: pact.MCPSubjectChanged republished through the store; every
instance recomputes the subject's sessions and stateless servers
(list_changed) or ends them on revocation; the link cache is dropped
- stateless.go: the SDK's stateless handler over per-identity servers for
2026-07-28 clients, routed by the protocol header or server/discover
- prm.go: RFC 9728 metadata at both well-known paths; long GET streams
re-verify their bearer every minute
- TestPierSessionsSmoke, TestPierFanOutSmoke, TestPierStatelessSmoke on
miniredis; README features, configuration and operational notes
- modules/pier: Host contract, mcp.* config, catalog compiled from
pact.HasMCP (prefixing, route binding, guard resolution, schema
reflection, annotations), per-request identity through the published
verifier and resolvers, built-in backend guard provider, in-process
dispatch with the bouncer marker, per-identity SDK server with the
per-request re-filter, stateful session router with rehydration over a
memory store, audit lines
- modules/surf/pier_on.go: the summercms_mcp hook adapting the router to
pier.Host; the untagged hello binary carries no SDK
- go.mod: go-sdk v1.8.0 and jsonschema-go v0.4.3 (decision note committed
before this change)
- TestPierTracerSmoke: SDK client lists and calls acme_echo end to end
through surf's compiled guard chain; README and root table row
- decision note for go-sdk v1.8.0 and jsonschema-go v0.4.3 with the
transitive graph, size, govulncheck result and the build-tag containment
- STACK.md rows for both modules naming the note (D-24)