D-13: All four raw OAuth routes and five JWT OAuth management routes replay their recorded PHP contracts against Go and count as ported only after passing.
D-16: A clean recorded lifecycle proves DCR, authorize, consent, token, refresh, replay kill, list, revoke, post-revoke failure, deny, confidential Basic auth, and scope ceiling.
D-14: The final gate contains a fail-closed unchanged-real-MCP stage for discovery, DCR, PKCE, JWT consent, token bootstrap, tool call, refresh, replay, and revoke.
D-15: Live vendor connects remain excluded; automated DCR-plus-PKCE evidence is the Phase 8 acceptance boundary.
path
provides
../fonoteka.go/parity/oauth_flow_test.go
Projected existing flows and full lifecycle replay
replay through the assembled Go app and real Postgres
newConfiguredTarget
from
to
via
pattern
scripts/check-phase8.sh
/media/nvme/dev/golem15/fonoteka/fonoteka-mcp
three configured URLs and scripted MCP SDK lifecycle
FONOTEKA_(API_URL|MCP_PUBLIC_URL|MCP_AUTH_SERVER)
Prove the completed server through recorded PHP parity and assemble the fail-closed unchanged-real-MCP gate that Plan 08-10 executes once at final sign-off.
Purpose: Turn exact route bytes, lifecycle security semantics, protected-resource discovery ownership, and actual SDK compatibility into one repeatable acceptance gate.
Output: Lifecycle fixture/capture policy, projected replay tests, nine ported manifest entries, and a self-validating final scripts/check-phase8.sh.
Phase Goal
As an unchanged MCP client, I want to complete the full OAuth lifecycle against Go exactly as I did against PHP, so that discovery, tool use, refresh, replay defense, and revocation are proven together.
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/08-oauth2-1-authorization-server/08-CONTEXT.md
@.planning/phases/08-oauth2-1-authorization-server/08-RESEARCH.md
@.planning/phases/08-oauth2-1-authorization-server/08-VALIDATION.md
@.planning/phases/08-oauth2-1-authorization-server/08-07-SUMMARY.md
@.planning/phases/08-oauth2-1-authorization-server/08-08-SUMMARY.md
Existing parity seams:
- `newConfiguredTarget(t, db)` boots the same assembled handler used by the app.
- `tide.LoadFlow`, `tide.OpenStore`, and `tide.ReplayFlow` execute captured request/response sequences with private variables.
- `parity/manifest.yaml` status becomes `ported` only when the selected replay subtest passes.
Unchanged MCP inputs:
FONOTEKA_API_URL points to the Go app personal-token API.
FONOTEKA_MCP_PUBLIC_URL points to the MCP resource server.
FONOTEKA_MCP_AUTH_SERVER points to the Go authorization server.
Task 1: Specify recorded and real-client OAuth acceptance before changing fixtures
../fonoteka.go/parity/oauth_flow_test.go, scripts/check-phase8.sh
.planning/phases/08-oauth2-1-authorization-server/08-CONTEXT.md
.planning/phases/08-oauth2-1-authorization-server/08-VALIDATION.md
../fonoteka.go/parity/nuxt_flow_test.go
../fonoteka.go/parity/parity_test.go
../fonoteka.go/parity/fixtures/mcp/mcp-oauth.yaml
../fonoteka.go/parity/fixtures/mcp/mcp-tools.yaml
../fonoteka.go/parity/manifest.yaml
../fonoteka.go/parity/capture_clients.mjs
scripts/check-phase3.sh
/media/nvme/dev/golem15/fonoteka/fonoteka-mcp/src/http.ts
/media/nvme/dev/golem15/fonoteka/fonoteka-mcp/src/config.ts
- Existing broad flows are projected to named OAuth/MCP prerequisite steps and fail if an expected step disappears; unrelated later-phase calls are not replayed.
- The clean lifecycle flow is required and every terminal security action is asserted before routes can be marked ported.
- The gate starts real Postgres, Go app, and unchanged Node MCP; it verifies MCP-owned protected-resource metadata and Bearer hint separately from backend-owned metadata and Basic invalid-client challenge.
D-12: distinguish backend Basic/no-challenge responses from MCP RFC 9728 behavior. D-13: project `mcp-oauth` and `mcp-tools` by stable named step IDs. D-14: declare real Postgres/app/MCP stages with all three environment variables. D-15: keep live vendor connects excluded. D-16: require the full clean lifecycle. D-18: create compiling `TestPhase8RedParityGate` and a fail-closed gate skeleton before changing fixtures/status. Validate the Go RED with exact package/test/sentinel under `go test -json`. Also add a shell-contract RED self-test whose only allowed failure is exit 86 plus exactly `PHASE8_STAGE:real-mcp:FAIL:PHASE8_RED:real-mcp-stage`; reject any other FAIL/ERROR/PANIC stage, extra sentinel, syntax failure, or zero stage. Plan 08-10 alone adds and executes the complete security-review/final gate. Do not edit or patch MCP or Nuxt.
scripts/check-phase8-red.sh go PHASE8_RED:parity-gate git.golem15.com/golem15/fonoteka/parity TestPhase8RedParityGate -- bash -lc "cd ../fonoteka.go && go test -json ./parity -run '^TestPhase8RedParityGate$' -count=1" && scripts/check-phase8-red.sh shell PHASE8_RED:real-mcp-stage real-mcp -- scripts/check-phase8.sh --red-contract real-mcp
- `oauth_flow_test.go` names projections for `mcp-oauth`, `mcp-tools`, and the complete `mcp-lifecycle`, and missing expected steps fail.
- `scripts/check-phase8.sh` uses `set -euo pipefail`, fail-closed dependency/Docker checks, cleanup traps, and all three MCP environment variables.
- The gate distinguishes MCP RFC 9728 metadata/rich Bearer challenge from backend exact Basic invalid-client challenge and unchanged token-surface 401.
- Go RED has no unexpected failing JSON action; shell RED has exactly one expected stage/sentinel and exit 86, with syntax/setup/extra-stage failures rejected.
The acceptance harness demands the exact recorded and real-client lifecycle before any route can be claimed ported.
Task 2: Record, scrub, replay, and promote the complete OAuth lifecycle
../fonoteka.go/parity/capture_clients.mjs, ../fonoteka.go/parity/capture-rules.yaml, ../fonoteka.go/parity/fixtures/mcp/mcp-lifecycle.yaml, ../fonoteka.go/parity/oauth_flow_test.go, ../fonoteka.go/parity/manifest.yaml, ../fonoteka.go/parity/check_corpus.go
../fonoteka.go/parity/oauth_flow_test.go
../fonoteka.go/parity/capture_clients.mjs
../fonoteka.go/parity/capture-rules.yaml
../fonoteka.go/parity/php_parity.sh
../fonoteka.go/parity/fixtures/mcp/mcp-oauth.yaml
../fonoteka.go/parity/fixtures/mcp/mcp-tools.yaml
../fonoteka.go/parity/manifest.yaml
tide/flow.go
tide/replay.go
- Lifecycle records DCR → authorize → consent → token → refresh → spent-token replay → list → revoke → refresh failure → deny, plus confidential Basic and ceiling/invalid-scope cases.
- Every request id, code, verifier, client secret, access token, and refresh token is represented only by a typed placeholder in committed fixtures; private vars are mode 0600.
- Nine manifest entries become ported only after their exact route replay passes; pending never increments passing.
D-16: extend the existing capture script/rules and use the Phase 2 isolated-PHP process to record the locked lifecycle. Issue the confidential client through `fonoteka:oauth-client`; exercise scope ceiling truncation and invalid-scope redirect with `client_secret_basic`. Capture all secret-bearing values with explicit pkce/token/credential categories into the task-verifiable private files `/tmp/summercms-parity/mcp-lifecycle.vars` and `/tmp/summercms-parity/pkce.vars`; create each with mode 0600, retain them only through the focused verification below, and commit only symbolic variable references. Add full and projected replays through `newConfiguredTarget` with real Postgres. After each of the four raw and five JWT route subtests passes, change only those manifest entries to `status: ported`; keep honest corpus accounting.
(cd ../fonoteka.go && go test ./parity -run '^TestOAuthFlows$' -count=1 && go run ./parity/check_corpus.go --manifest parity/manifest.yaml --fixtures parity/fixtures --check-secrets) && test "$(stat -c '%a' /tmp/summercms-parity/mcp-lifecycle.vars)" = 600 && test "$(stat -c '%a' /tmp/summercms-parity/pkce.vars)" = 600
- `mcp-lifecycle.yaml` contains the locked sequence and placeholder references, not recoverable credential values.
- `go run ./parity/check_corpus.go --manifest parity/manifest.yaml --fixtures parity/fixtures --check-secrets` exits 0.
- All nine OAuth manifest entries are `ported`; replay reports them passing with zero failing and does not count any pending route as passing.
- Existing `mcp-oauth`/`mcp-tools` projections fail if a required named step is removed and ignore only explicitly enumerated later-phase steps.
The Go app passes the PHP-recorded OAuth route and lifecycle contracts without committing live secrets.
Task 3: Complete and self-validate the final unchanged-MCP gate
scripts/check-phase8.sh
scripts/check-phase8.sh
scripts/check-phase3.sh
../fonoteka.go/parity/oauth_flow_test.go
/media/nvme/dev/golem15/fonoteka/fonoteka-mcp/package.json
/media/nvme/dev/golem15/fonoteka/fonoteka-mcp/src/http.ts
/media/nvme/dev/golem15/fonoteka/fonoteka-mcp/src/config.ts
/media/nvme/dev/golem15/fonoteka/fonoteka-mcp/src/install.ts
D-14: finish the executable final gate using the existing gate family and installed Node MCP dependencies. Encode stages for disposable Postgres, assembled Go app, unchanged MCP with all three URLs, SDK discovery/DCR/PKCE/login/consent/token, `/me`, tool, refresh, replay, revoke, both repositories' vet/test/race, parity/corpus/secret checks, full UI harness, and unchanged Nuxt/MCP diffs. Verify RFC 9728 ownership separately from exact backend Basic/no-challenge responses. Add `--contract-self-test` that validates `bash -n`, required stage names/order, cleanup traps, loopback-only allocation, redacted output, expected commands, and unchanged-client path scopes without booting services or running long suites. Do not offer `--pre-security` or any mode that executes the complete gate before 08-10; Plan 08-10 adds the review stage and is the sole complete execution site.
bash -n scripts/check-phase8.sh && scripts/check-phase8.sh --contract-self-test
- Contract self-test finds required real-MCP lifecycle, replay/revoke, two-repository vet/test/race, parity, UI, secret-scan, security-review, and unchanged-client stages in fail-closed order.
- The script has cleanup traps and loopback/service readiness checks, exports all three MCP URLs only to the child process, and redacts every raw secret/token/code/verifier.
- No non-final mode can run the complete long suite; `--contract-self-test` performs syntax/source assertions only and is designed for under 30 seconds.
- Final execution remains mandatory in 08-10 Task 3 and fails if either unchanged client worktree gains a Phase 8 source diff.
The complete unchanged-client gate is fail-closed, self-validating, and ready for its sole execution at the final blocking checkpoint.
<threat_model>
Trust Boundaries
Boundary
Description
PHP capture → committed fixtures
Live credentials must become typed placeholders before entering git.
Scripted SDK → Go backend/MCP
External client parsing and redirects exercise public network-facing contracts.
Gate process → child services
Secrets and cleanup state cross shell/Node/Go process boundaries.
STRIDE Threat Register
Threat ID
Category
Component
Disposition
Mitigation Plan
T-08-PKCE
Spoofing/Elevation
real SDK flow
mitigate
Actual SDK S256 authorize/exchange plus wrong-verifier rejection in gate.
PHP fixture and Go replay assert local errors versus trusted ordered redirects.
T-08-SECRET-TIMING
Information Disclosure
confidential Basic flow
mitigate
Actual confidential flow uses the constant-time implementation; final source audit in 08-06.
T-08-SCOPE-CEILING
Elevation
confidential lifecycle
mitigate
Recorded ceiling truncation and invalid-scope redirect.
T-08-CROSS-USER
Elevation
consent/list/revoke replay
mitigate
JWT ownership cases and indistinguishable 404 replay.
T-08-REQUEST-LEAK
Information Disclosure
fixtures/logs
mitigate
Capture categories, 0600 stores, placeholder-only fixtures, check-secrets and quiet gate.
T-08-DCR-FLOOD
Denial of Service
register route
mitigate
Recorded native errors plus focused rate/body/cap tests run by gate.
T-08-SURFACE
Elevation
MCP/backend boundary
mitigate
Gate asserts correct RFC 9728 ownership and exact backend challenges.
T-08-SC
Tampering
reused Node dependencies
mitigate
No install; use checked-in lockfile/node_modules and dependency preflight.
</threat_model>
- `cd ../fonoteka.go && go test ./parity -run '^TestOAuthFlows$' -count=1`
- `bash -n scripts/check-phase8.sh && scripts/check-phase8.sh --contract-self-test`; the full gate runs only in 08-10 Task 3.
<success_criteria>
Nine OAuth routes and the full lifecycle replay pass against Go with no leaked fixture secret.
The final gate encodes the real unchanged-MCP lifecycle and cannot execute incompletely or before the security review.
Resource-server and authorization-server header ownership is proven exactly, not conflated.
</success_criteria>
Create `.planning/phases/08-oauth2-1-authorization-server/08-09-SUMMARY.md` when done.