9.5 KiB
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;
tidesuggested). - 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:recordhas an--updatemode 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.