fix(08): revise plans based on checker feedback

This commit is contained in:
Jakub Zych
2026-09-23 17:13:47 +02:00
parent 241af16ba7
commit 3c6a505c5f
14 changed files with 1100 additions and 911 deletions

View File

@@ -39,12 +39,12 @@ Out of scope: social login (`/oauth/{provider}`, `oauth-identities` routes, Phas
- **D-15:** No live vendor connect (Claude, ChatGPT, Grok) is part of acceptance; the automated gates cover the same DCR-plus-PKCE chain those apps use. The first real vendor connect happens at cutover.
### Parity corpus and lifecycle
- **D-16 (corpus):** A second PHP flow, `mcp-lifecycle`, is recorded against the isolated PHP instance with the Phase 2 `tide` tooling (capture rules, private 0600 vars store, `pkce.vars`, no live secrets in git): DCR → authorize → consent → token → refresh → replay of the spent refresh token (`invalid_grant`, lineage dead) → connected-apps list showing the app → revoke → refresh after revoke fails → a deny path; plus a confidential client issued by `fonoteka:oauth-client` with a scope ceiling using `client_secret_basic`, showing ceiling truncation and the `invalid_scope` redirect. The four RFC route entries and five JWT-group entries in `parity/manifest.yaml` flip pending → ported through the usual gate; pending never counts as passing.
- **D-17 (sweep):** Wristband adds an expiry sweep that PHP lacks, run where PHP runs its client sweep (`/register`) and also on `/token`, deleting only rows past `expires_at`: pending requests, codes (used or not) and refresh rows. Revoked or rotated rows that have not expired stay, so replay detection and connected-apps semantics match PHP. No timer, no goroutine; Phase 11 may move it into a River job. It is unobservable in recorded replays because nothing expires within a run; the schema-diff and db-capture harness must not be affected.
- **D-18 (tests):** All PHP OAuth tests are ported (functional: OAuthAuthorizeTest, OAuthClientCommandTest, OAuthMetadataTest, OAuthMigrationTest, OAuthRegisterTest, OAuthTokenTest; security: OAuthConsentScopeCeilingTest, OAuthRefreshRotationTest, OAuthRevocationTest, TokenSurfaceIsolationTest). Framework behaviour tests run on wristband with the in-memory store; app tests run on real Postgres through the existing `classes` TestMain harness, and route-surface isolation tests inspect the surf route table as Phase 6 did. Each PHP test method maps to a named Go test so coverage can be audited.
- **D-19 (command):** `fonoteka:oauth-client` is ported in `fonoteka.go` as a bonfire command scaffolded the Phase 4 way with the same signature (`name`, `--redirect-uri=*`, `--scope=*`, `--auth-method`, `--client-id`, `--list`) and the same output lines (`client_id=`, `client_secret=` printed once, the non-recoverable warning, `--list` never printing a secret). It is thin over wristband's client issuing helper and the app `ClientStore`.
- **D-20 (MCP prerequisite):** Phase 8 ports the minimal exact `GET /api/v1/fonoteka/me` personal-token endpoint required by `fonoteka-mcp/src/http.ts` before it constructs the MCP server. The endpoint authenticates through the existing `inv_token` surface and implements only the response contract needed by the unchanged MCP client. This prerequisite is in scope solely to preserve D-14's real MCP tool-call gate; broader user/profile API work remains deferred.
- **D-21 (DCR body bound):** `POST /oauth/mcp/register` accepts at most 64 KiB of JSON request body. An oversized document returns the endpoint's normal `invalid_client_metadata` response rather than a house envelope or generic HTML error. The bound is enforced before unbounded JSON decoding and is covered by the `T-08-DCR-FLOOD` failing-when-broken test.
- **D-16:** (corpus) A second PHP flow, `mcp-lifecycle`, is recorded against the isolated PHP instance with the Phase 2 `tide` tooling (capture rules, private 0600 vars store, `pkce.vars`, no live secrets in git): DCR → authorize → consent → token → refresh → replay of the spent refresh token (`invalid_grant`, lineage dead) → connected-apps list showing the app → revoke → refresh after revoke fails → a deny path; plus a confidential client issued by `fonoteka:oauth-client` with a scope ceiling using `client_secret_basic`, showing ceiling truncation and the `invalid_scope` redirect. The four RFC route entries and five JWT-group entries in `parity/manifest.yaml` flip pending → ported through the usual gate; pending never counts as passing.
- **D-17:** (sweep) Wristband adds an expiry sweep that PHP lacks, run where PHP runs its client sweep (`/register`) and also on `/token`, deleting only rows past `expires_at`: pending requests, codes (used or not) and refresh rows. Revoked or rotated rows that have not expired stay, so replay detection and connected-apps semantics match PHP. No timer, no goroutine; Phase 11 may move it into a River job. It is unobservable in recorded replays because nothing expires within a run; the schema-diff and db-capture harness must not be affected.
- **D-18:** (tests) All PHP OAuth tests are ported (functional: OAuthAuthorizeTest, OAuthClientCommandTest, OAuthMetadataTest, OAuthMigrationTest, OAuthRegisterTest, OAuthTokenTest; security: OAuthConsentScopeCeilingTest, OAuthRefreshRotationTest, OAuthRevocationTest, TokenSurfaceIsolationTest). Framework behaviour tests run on wristband with the in-memory store; app tests run on real Postgres through the existing `classes` TestMain harness, and route-surface isolation tests inspect the surf route table as Phase 6 did. Each PHP test method maps to a named Go test so coverage can be audited.
- **D-19:** (command) `fonoteka:oauth-client` is ported in `fonoteka.go` as a bonfire command scaffolded the Phase 4 way with the same signature (`name`, `--redirect-uri=*`, `--scope=*`, `--auth-method`, `--client-id`, `--list`) and the same output lines (`client_id=`, `client_secret=` printed once, the non-recoverable warning, `--list` never printing a secret). It is thin over wristband's client issuing helper and the app `ClientStore`.
- **D-20:** (MCP prerequisite) Phase 8 ports the minimal exact `GET /api/v1/fonoteka/me` personal-token endpoint required by `fonoteka-mcp/src/http.ts` before it constructs the MCP server. The endpoint authenticates through the existing `inv_token` surface and implements only the response contract needed by the unchanged MCP client. This prerequisite is in scope solely to preserve D-14's real MCP tool-call gate; broader user/profile API work remains deferred.
- **D-21:** (DCR body bound) `POST /oauth/mcp/register` accepts at most 64 KiB of JSON request body. An oversized document returns the endpoint's normal `invalid_client_metadata` response rather than a house envelope or generic HTML error. The bound is enforced before unbounded JSON decoding and is covered by the `T-08-DCR-FLOOD` failing-when-broken test.
### Claude's Discretion
- Interface names and signatures in wristband, the transaction seam, in-memory store design, and where shared helpers (base64url, sha256 hex, constant-time compare) live.