Files
summercms/.planning/phases/02-api-parity-harness-bootstrap/02-03-SUMMARY.md
2026-09-17 13:47:29 +02:00

9.7 KiB

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, duration, completed
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions patterns-established requirements-completed duration completed
02-api-parity-harness-bootstrap 03 testing
parity
fixtures
php
nuxt
mcp
oauth
pkce
phase provides
02-api-parity-harness-bootstrap tide record/replay/proxy, capture rules, named vars, manifest --next-batch, summer parity:*
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
02-04
02-05
03
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}}
created modified
../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
../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
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
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
QA-01
QA-02
QA-03
61min 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