# Phase 2: API parity harness bootstrap - Discussion Log > **Audit trail only.** Do not use as input to planning, research, or execution agents. > Decisions are captured in CONTEXT.md — this log preserves the alternatives considered. **Date:** 2026-09-16 **Phase:** 02-api-parity-harness-bootstrap **Areas discussed:** Recording strategy, Fixture format and layout, Replay auth and data state, Diff strictness and reporting --- ## Recording strategy ### How should fixtures be captured from the PHP backend? | Option | Description | Selected | |--------|-------------|----------| | Both: proxy + scripted | Recording reverse proxy in front of :8422 captures real Nuxt and MCP sessions; a manifest-driven runner fills coverage for all 154 routes | ✓ | | Recording proxy only | Point Nuxt and MCP at the proxy and record whatever they send | | | Scripted runner only | A Go program issues each request from a manifest with seeded auth | | **User's choice:** Both: proxy + scripted ### Which PHP instance and dataset should recordings come from? | Option | Description | Selected | |--------|-------------|----------| | Local :8422 with a seeded parity dataset | Deterministic dataset built on a fresh DB before recording | ✓ | | Local :8422 with existing dev database | Record against whatever is in the dev DB | | | Production plytarium.com (read-only routes) | GET routes from the live site, writes locally | | **User's choice:** Local :8422 with a seeded parity dataset ### How should the seeded parity dataset be built on the PHP side? | Option | Description | Selected | |--------|-------------|----------| | Seed through the API itself | Recorded setup flow calls the PHP API; artisan only for what the API cannot create | ✓ | | A PHP seeder command in the fonoteka plugin | `fonoteka:parity-seed` artisan command with fixed ids | | | A checked-in SQL dump | Restore a hand-prepared dump before recording | | **User's choice:** Seed through the API itself ### What shape should the recorder tool take, given Phase 1's CLI kernel may not exist yet? | Option | Description | Selected | |--------|-------------|----------| | Standalone `parity` binary now, wrapped later | `cmd/parity` on plain flag/cobra, exposed as `summer parity:*` after Phase 1 | | | go test driven only | Recording and replay as `go test` runs with env-var switches | | | Wait for Phase 1 and build `summer parity:*` directly | Serialize Phase 2 after Phase 1; recorder is a bonfire command from day one | ✓ | **User's choice:** Wait for Phase 1 and build `summer parity:*` directly **Notes:** This makes Phase 2 depend on Phase 1 and drops the roadmap's "parallel with Phase 1" note; ROADMAP.md needs updating. --- ## Fixture format and layout ### How should recorded fixtures be organized on disk? | Option | Description | Selected | |--------|-------------|----------| | One file per flow, steps inside | Ordered steps per flow; manifest runner emits one-step flows per route | ✓ | | One file per request/response pair | Every exchange its own file | | | One file per route with all cases | 154 files each holding every case | | **User's choice:** One file per flow, steps inside ### What file format should a flow fixture use? | Option | Description | Selected | |--------|-------------|----------| | Custom YAML via goccy/go-yaml | Small schema, human-editable, same YAML library as config and admin schemas | ✓ | | Custom JSON | Same schema as JSON | | | HAR files | Browser-standard archive with a sidecar for rules | | **User's choice:** Custom YAML via goccy/go-yaml ### How should credentials and volatile headers be handled in stored fixtures? | Option | Description | Selected | |--------|-------------|----------| | Scrub to named placeholders at record time | `{{jwt:alice}}`-style placeholders from a credential map; replay resolves them | ✓ | | Store real values, gitignore the fixture directory | Never commit fixtures | | | Strip auth entirely, re-add at replay from the manifest | Manifest says which identity each step runs as | | **User's choice:** Scrub to named placeholders at record time ### Where should the fixture corpus and the Płytarium manifest live? | Option | Description | Selected | |--------|-------------|----------| | fonoteka.go/parity/ with fixtures beside the manifest | manifest.yaml, fixtures/{nuxt,mcp,routes}, replay test; framework ships library + synthetic corpus | ✓ | | fonoteka.go/plugins/fonoteka/testdata/parity/ | Inside the fonoteka plugin module | | | A third repo for the corpus | Shared by PHP and Go sides | | **User's choice:** fonoteka.go/parity/ with fixtures beside the manifest --- ## Replay auth and data state ### How should the Go backend's database reach the same state the recording was made against? | Option | Description | Selected | |--------|-------------|----------| | Replay the recorded seed flow against Go first | Same API-driven seed flow on both sides | ✓ | | Direct Postgres seed from the fixture corpus | Insert rows from a seed file | | | Hybrid: SQL seed now, API seed once writes exist | Two mechanisms during the port | | **User's choice:** Replay the recorded seed flow against Go first ### When a replay needs state that no ported write endpoint can create yet, what fills the gap? | Option | Description | Selected | |--------|-------------|----------| | Per-flow Go seed hook, deleted once the write route lands | Named hook registered by the app test, listed as temporary in the manifest | ✓ | | Port the matching write endpoint in the same phase | Phase 3 would grow beyond one route | | | Permanent SQL seed files per flow | Second source of truth for state | | **User's choice:** Per-flow Go seed hook, deleted once the write route lands ### How does a replay obtain credentials to substitute for placeholders? | Option | Description | Selected | |--------|-------------|----------| | Capture rules on seed-flow steps | JSON path from response into a variable store; same for ids, share tokens, OAuth codes | ✓ | | Harness-level identity registry | Harness logs identities in outside any flow | | | Mint tokens directly against the Go DB | Sign JWTs and insert tokens in Postgres | | **User's choice:** Capture rules on seed-flow steps ### How is the Go backend hosted while fixtures replay against it? | Option | Description | Selected | |--------|-------------|----------| | Both: in-process httptest.Server in tests, any URL from the CLI | Hermetic go test with testcontainers Postgres; `summer parity:replay --target` for any server including PHP as self-check | ✓ | | In-process httptest.Server only | Replay exists only as a Go test | | | External URL only | Always target a running server | | **User's choice:** Both: in-process httptest.Server in tests, any URL from the CLI --- ## Diff strictness and reporting ### What counts as a body mismatch for JSON responses? | Option | Description | Selected | |--------|-------------|----------| | Structural equality plus token-type checks | Key sets, values and JSON token types; key order ignored | ✓ | | Raw byte equality after canonicalization | Sorted keys, fixed whitespace, compare bytes | | | Strict byte equality, key order included | PHP key order becomes part of the contract | | **User's choice:** Structural equality plus token-type checks ### Which response headers should the diff compare? | Option | Description | Selected | |--------|-------------|----------| | Global allow-list plus per-route additions | Status, Content-Type, pagination/CORS always; OAuth cache headers, WWW-Authenticate, Content-Disposition per route | ✓ | | Status and Content-Type only | Ignore all other headers | | | All headers except a deny-list | Compare everything not on a volatile list | | **User's choice:** Global allow-list plus per-route additions ### How should nondeterministic values be normalized before the diff? | Option | Description | Selected | |--------|-------------|----------| | Global rules by field pattern plus per-step overrides | Pattern-keyed normalizers with format assertions on masked fields; captured variables compare by reference | ✓ | | Per-step ignore lists only | Each step lists JSON paths to skip | | | Mask by value type at record time | Recorder rewrites dates/ids/tokens into placeholders | | **User's choice:** Global rules by field pattern plus per-step overrides ### How should a parity run report its results? | Option | Description | Selected | |--------|-------------|----------| | Run everything, report per step, summarize per route | Path-level diff per failing step; coverage table over 154 routes; flows as go test subtests | ✓ | | Fail fast on first mismatch | Stop at the first failing step | | | Report only, never fail | JSON report, exit zero until cutover | | **User's choice:** Run everything, report per step, summarize per route --- ## Claude's Discretion - Harness library package name (summer-themed; `tide` suggested). - Expected-failure marking for unported routes so CI stays green on ported routes. - Fresh DB per flow vs one seeded DB per run (default: one per run, manifest-ordered flows). - Capturing the MCP OAuth PKCE flow through the proxy and into variables. - Binary response storage (base64 block vs sidecar file). - Proxy transport details (plain HTTP on localhost, cookie passthrough, buffered bodies). - Whether `summer parity:record` has an `--update` mode or writes to a new directory. ## Deferred Ideas - ROADMAP.md dependency update for Phase 2 (now depends on Phase 1) via `/gsd-phase edit 2`. - Recording read-only routes against production as a late sanity check before cutover (Phase 15 may consider). - Reusing the harness for the keios.eu port when it starts.