diff --git a/.planning/phases/02-api-parity-harness-bootstrap/02-03-SUMMARY.md b/.planning/phases/02-api-parity-harness-bootstrap/02-03-SUMMARY.md new file mode 100644 index 0000000..ae878d0 --- /dev/null +++ b/.planning/phases/02-api-parity-harness-bootstrap/02-03-SUMMARY.md @@ -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*