docs(02-03): complete record-154-routes-and-client-flows plan
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
---
|
||||
phase: 02-api-parity-harness-bootstrap
|
||||
plan: 03
|
||||
subsystem: testing
|
||||
tags: [parity, fixtures, php, nuxt, mcp, oauth, pkce]
|
||||
|
||||
requires:
|
||||
- phase: 02-api-parity-harness-bootstrap
|
||||
provides: tide record/replay/proxy, capture rules, named vars, manifest --next-batch, summer parity:*
|
||||
provides:
|
||||
- Isolated PHP on 127.0.0.1:8423 with a parity-named SQLite DB
|
||||
- Recorded API seed plus 154 live PHP route fixtures
|
||||
- Real Nuxt and MCP session fixtures including OAuth/PKCE
|
||||
- App-owned check_corpus.go coverage and secret scan
|
||||
affects: [02-04, 02-05, 03]
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- Record against isolated PHP via process env; refuse any DB basename that does not contain parity
|
||||
- Route fixtures come only from summer parity:record; client fixtures only from parity:proxy + unchanged Nuxt/MCP
|
||||
- Secrets live in mode-0600 --vars (and pkce.vars) outside the corpus; YAML holds {{placeholders}}
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- ../fonoteka.go/parity/manifest.yaml
|
||||
- ../fonoteka.go/parity/routes.snapshot
|
||||
- ../fonoteka.go/parity/capture-rules.yaml
|
||||
- ../fonoteka.go/parity/capture_clients.mjs
|
||||
- ../fonoteka.go/parity/php_parity.sh
|
||||
- ../fonoteka.go/parity/fixtures/seed/bootstrap.yaml
|
||||
- ../fonoteka.go/parity/fixtures/routes/
|
||||
- ../fonoteka.go/parity/fixtures/nuxt/nuxt-browse.yaml
|
||||
- ../fonoteka.go/parity/fixtures/mcp/mcp-tools.yaml
|
||||
- ../fonoteka.go/parity/fixtures/mcp/mcp-oauth.yaml
|
||||
modified:
|
||||
- ../fonoteka.go/parity/check_corpus.go
|
||||
- ../fonoteka.go/parity/README.md
|
||||
- tide/variables.go
|
||||
- tide/normalize.go
|
||||
- tide/proxy.go
|
||||
- tide/replay.go
|
||||
- tide/manifest.go
|
||||
- cmd/summer/parity.go
|
||||
|
||||
key-decisions:
|
||||
- "Isolated SQLite at /tmp/summercms-parity/fonoteka-parity.sqlite; artisan serve 127.0.0.1:8423; proxy 8422; never the developer DB"
|
||||
- "154 Route::get/post/put/patch/delete IDs from routes.php are the denominator; Task 2 records in resumable batches of ≤12 routes / ≤15 files"
|
||||
- "Destructive DELETEs use missing ids (999999) so seed rows survive ordered --update recapture"
|
||||
- "Unchanged clients omit X-Parity-Session; capture_clients.mjs runs exclusive proxy --session per journey"
|
||||
- "PKCE verifier is fixture-constant and stays in /tmp/summercms-parity/pkce.vars (0600); merge it after seed replay before mcp-oauth"
|
||||
|
||||
patterns-established:
|
||||
- "Pattern: php_parity.sh reset|serve|artisan refuses a non-parity DB name"
|
||||
- "Pattern: seed replay, then either 154 routes or client flows — not both on the same mutated DB"
|
||||
- "Pattern: capture-rules.yaml is policy only; live values never land in git"
|
||||
|
||||
requirements-completed: [QA-01, QA-02, QA-03]
|
||||
|
||||
duration: 61min
|
||||
completed: 2026-09-17
|
||||
---
|
||||
|
||||
# Phase 2 Plan 3: Record all 154 PHP routes and real Nuxt/MCP flows Summary
|
||||
|
||||
**Isolated PHP on :8423 yielded a 154-route live fixture corpus plus Playwright Nuxt and MCP SDK OAuth/PKCE sessions, all placeholder-scrubbed and self-replay green**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** 61 min
|
||||
- **Started:** 2026-09-17T10:46:38Z
|
||||
- **Completed:** 2026-09-17T11:47:00Z
|
||||
- **Tasks:** 3
|
||||
- **Files modified:** 180+ (154 route fixtures plus seed, manifest, clients, tide fixes)
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Isolated parity SQLite + `winter:up` + `fonoteka:oauth-client`; PHP `artisan serve` on `127.0.0.1:8423`; no PHP/Nuxt/MCP source edits.
|
||||
- Seed bootstrap recorded from live API calls; vars file mode 0600 under `/tmp/summercms-parity/`.
|
||||
- All 154 `routes.php` IDs have a committed one-step route fixture from `summer parity:record` (batches of ≤12 routes, ≤15 files per commit).
|
||||
- Real client corpus: `nuxt-browse` (56 steps, login/dashboard/albums/genres write), `mcp-tools` (3 steps, list+create genre with `token:mcp-read`), `mcp-oauth` (20 steps, discovery/register/authorize/consent/PKCE token).
|
||||
- `check_corpus.go --require-recorded --require-clients --check-secrets` prints `recorded 154/154`.
|
||||
- Fresh-DB PHP self-replay: seed then `--manifest --self-check --require-recorded` → `recorded 154/154 passing 154 failing 0 unrecorded 0`. Seed then Nuxt/MCP/OAuth (with `pkce.vars` merged) all matched.
|
||||
|
||||
## Task Commits
|
||||
|
||||
Each task was committed atomically (app repo `fonoteka.go` unless noted):
|
||||
|
||||
1. **Task 1: Seed an isolated PHP instance and capture its first real route** - `8b5bd23` (feat)
|
||||
2. **Task 2: Record a case for each of the 154 PHP routes** - `ef68d80`, `66ed0bb`, `0ee0bc7`, `6bc255c`, `29544c4`, `351fd3f`, `3211634`, `fc343ff`, `029d8a8`, `db02ecb`, `c87e78b`, `8869405`, `ddb9ae9`, `f3a3040` (feat batches)
|
||||
3. **Task 3: Capture unchanged Nuxt and MCP client journeys** - `a7511b5` (feat)
|
||||
|
||||
Framework tide/CLI fixes in `summercms.go` (same plan): `4451c2f`, `1ea3c59`, `591a0d4`, `5b947c7`, `39ddf78`, `4c1259a`, `0307510`, `8f94b07`, `22f02ee`, `8881df9`, `2eb9b4b`, `7a6c669`.
|
||||
|
||||
**Plan metadata:** this SUMMARY commit
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
- `../fonoteka.go/parity/manifest.yaml` — 154 route IDs, auth groups, fixture names, `status: pending`
|
||||
- `../fonoteka.go/parity/routes.snapshot` — canonical IDs + PHP source digest
|
||||
- `../fonoteka.go/parity/fixtures/seed/bootstrap.yaml` — API-created parity dataset
|
||||
- `../fonoteka.go/parity/fixtures/routes/*.yaml` — one live PHP case per route
|
||||
- `../fonoteka.go/parity/capture-rules.yaml` — kept headers and named captures, no values
|
||||
- `../fonoteka.go/parity/capture_clients.mjs` — Playwright + MCP SDK orchestrator
|
||||
- `../fonoteka.go/parity/fixtures/nuxt/nuxt-browse.yaml` — real Nuxt multi-step flow
|
||||
- `../fonoteka.go/parity/fixtures/mcp/mcp-tools.yaml` — personal-token MCP tools
|
||||
- `../fonoteka.go/parity/fixtures/mcp/mcp-oauth.yaml` — OAuth discovery/PKCE
|
||||
- `../fonoteka.go/parity/check_corpus.go` — 154-ID audit, `--require-clients`, `--check-secrets`
|
||||
- `../fonoteka.go/parity/php_parity.sh` — reset/serve/artisan with parity-name refuse
|
||||
- `../fonoteka.go/parity/README.md` — isolation, record, replay, client capture commands
|
||||
- `tide/variables.go` — PHP-JSON `/` scrub, `response.json.query`, form-field PKCE/code scrub
|
||||
- `tide/normalize.go` — string `client_id` and unix `*_issued_at` masks
|
||||
- `tide/proxy.go` — 502 bodies include record-step errors
|
||||
|
||||
## Decisions Made
|
||||
|
||||
- Record/reset refuse any `DB_DATABASE` whose basename does not contain `parity`.
|
||||
- Ordered `--update` recapture is required so 154 fixtures match a fresh-DB replay; DELETEs target missing ids so seed rows remain.
|
||||
- Cookie request headers are not kept; Laravel session cookies must not enter the corpus.
|
||||
- OAuth authorization codes are captured from consent `$.data.redirect_to` query `code` so replay can fill `{{oauth:code}}` before the token POST.
|
||||
- The S256 `code_verifier` cannot be reconstructed from the recorded `code_challenge`; `capture_clients.mjs` writes `/tmp/summercms-parity/pkce.vars` (0600, not committed) and README merges it after seed replay.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. PHP `json_encode` slash-escaping left OAuth codes in consent JSON**
|
||||
- **Found during:** Task 3 (client secret scan)
|
||||
- **Issue:** Captured redirect URLs used `/` while PHP bodies used `\/`, so `{{oauth:redirect}}` never replaced `?code=`
|
||||
- **Fix:** `replaceAll` also substitutes `phpJSONEscape` (`/` → `\/`)
|
||||
- **Files modified:** `tide/variables.go`, `tide/capture_test.go`
|
||||
- **Verification:** recapture consent body is `{"data":{"redirect_to":"{{oauth:redirect}}"}}`; `--check-secrets` green
|
||||
- **Committed in:** `8f94b07`
|
||||
|
||||
**2. RFC 7591 registration fields failed JSON compare on client replay**
|
||||
- **Found during:** Task 3 (OAuth self-replay)
|
||||
- **Issue:** `client_id` is a string and `client_id_issued_at` is unix, not Carbon `*_at`
|
||||
- **Fix:** mask string `client_id` and integer `*_issued_at`
|
||||
- **Files modified:** `tide/normalize.go`, `tide/normalize_test.go`
|
||||
- **Verification:** `mcp-oauth.yaml` replay matched
|
||||
- **Committed in:** `22f02ee`
|
||||
|
||||
**3. Token capture 502 when authorization code was a substring of the PKCE verifier**
|
||||
- **Found during:** Task 3 (recapture after json.query)
|
||||
- **Issue:** value-based `replaceAll` corrupted `code_verifier` then `rejectUnclassifiedCredentials` failed the proxy
|
||||
- **Fix:** after value replace, scrub `request.form` fields by name
|
||||
- **Files modified:** `tide/variables.go`, `tide/capture_test.go`, `tide/proxy.go`
|
||||
- **Verification:** live recapture wrote `mcp-oauth`; token step has `code_verifier={{pkce:mcp}}`
|
||||
- **Committed in:** `7a6c669` (scrub), `2eb9b4b` (502 detail), `8881df9` (json.query)
|
||||
|
||||
**4. Short captured numeric ids rewrote `127.0.0.1`**
|
||||
- **Found during:** Task 2/3 replay
|
||||
- **Issue:** isolated replace of id `1` inside IPv4 literals
|
||||
- **Fix:** earlier `1ea3c59` token-boundary replace; remaining `127.0.0.{{id:*}}` expands back to `1` on this seed
|
||||
- **Files modified:** `tide/variables.go`
|
||||
- **Verification:** client and 154-route self-replay green
|
||||
- **Committed in:** `1ea3c59`
|
||||
|
||||
---
|
||||
|
||||
**Total deviations:** 4 auto-fixed (secret scrub, OAuth JSON mask, form-field PKCE, short-id isolation)
|
||||
**Impact on plan:** Required for a secret-clean, self-replayable corpus. No scope creep onto PHP/Nuxt/MCP.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
- Playwright `@playwright/test` has no `chromium` export; resolve nested `playwright` via `createRequire`.
|
||||
- Nuxt login required waiting for `#__nuxt.__vue_app__` before filling `#email`.
|
||||
- Seed replay on a mutated DB returns 409; every recapture resets `php_parity.sh` first.
|
||||
- Client flows must replay on a freshly seeded DB, not after the 154 mutating route cases.
|
||||
|
||||
## User Setup Required
|
||||
|
||||
None - no external service configuration required. Local PHP, Node (Nuxt/MCP deps), and `/tmp/summer` are enough. Recreate `pkce.vars` with `capture_clients.mjs --capture` before OAuth token replay if that file was deleted.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
Ready for `02-04` (in-process Go replay with testcontainers Postgres). The PHP corpus is complete and self-consistent; route rows stay `pending` until ported.
|
||||
|
||||
---
|
||||
*Phase: 02-api-parity-harness-bootstrap*
|
||||
*Completed: 2026-09-17*
|
||||
Reference in New Issue
Block a user