D-02 D-03: An isolated PHP backend on 127.0.0.1:8423 is seeded through recorded API requests; only admin user and OAuth client setup use existing artisan commands.
D-01 D-08: The app manifest contains exactly 154 PHP route declarations, each with at least one recorded route-case flow, plus real Nuxt and MCP session flows.
D-07 D-11: The committed corpus uses named credential/id placeholders and contains no live secrets.
D-12 D-16: Self-replay against a freshly seeded PHP backend passes and reports 154 recorded routes with no unrecorded route.
path
provides
../fonoteka.go/parity/manifest.yaml
Exact 154-route source map, auth groups, cases, headers and status
Unmodified Nuxt and MCP clients send requests through the proxy
**As a** port developer, **I want to** replay the PHP contract for every Fonoteka route and actual client journey, **so that** later port phases have a complete accepted corpus.
Purpose: Convert the PHP source and live isolated instance into app-owned fixtures without changing PHP or clients.
Output: Exact route manifest, recorded API seed, 154-route fixture corpus, Nuxt/MCP session fixtures and PHP self-check instructions/evidence.
@.planning/phases/02-api-parity-harness-bootstrap/02-CONTEXT.md
@.planning/phases/02-api-parity-harness-bootstrap/02-RESEARCH.md
@.planning/phases/02-api-parity-harness-bootstrap/02-VALIDATION.md
@.planning/phases/02-api-parity-harness-bootstrap/02-02-SUMMARY.md
@/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/routes.php
@/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/traits/SerializesFonoteka.php
@/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/tests/AccessTestCase.php
@/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/tests/FonotekaTokenTestCase.php
@/media/nvme/dev/golem15/fonoteka/vue-fonoteka-app/nuxt.config.ts
@/media/nvme/dev/golem15/fonoteka/fonoteka-mcp/src/config.ts
@/media/nvme/dev/golem15/fonoteka/fonoteka-mcp/src/client.ts
`../fonoteka.go` is a separate Go module. It currently has no Go handler. Use the generic `tide` YAML schema and manifest runner from Plans 01–02. The route denominator is the 154 line-anchored `Route::get/post/put/patch/delete/...` declarations in PHP `routes.php`; auth endpoints from the user plugin may be seed steps but are outside that denominator. Every route id must encode method, full path pattern and auth group to disambiguate repeated paths. Keep `status: pending` until the matching Go route is actually ported.
Task 1: Seed an isolated PHP instance and capture its first real route
../fonoteka.go/parity/manifest.yaml, ../fonoteka.go/parity/routes.snapshot, ../fonoteka.go/parity/fixtures/seed/bootstrap.yaml, ../fonoteka.go/parity/fixtures/routes/get_genres_jwt.yaml, ../fonoteka.go/parity/check_corpus.go, ../fonoteka.go/parity/README.md
../fonoteka.go/CLAUDE.md, ../fonoteka.go/go.mod, /media/nvme/dev/golem15/fonoteka/README.md, /media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/routes.php, /media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/tests/AccessTestCase.php, /media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/tests/FonotekaTokenTestCase.php, .planning/phases/02-api-parity-harness-bootstrap/02-CONTEXT.md, .planning/phases/02-api-parity-harness-bootstrap/02-RESEARCH.md
Create an isolated fresh parity database with the PHP app's existing migration process; launch PHP using process-local DB settings and `php artisan serve --host=127.0.0.1 --port=8423`, leaving the PHP checkout and developer DB untouched (D-02). Write the seed flow as actual PHP API calls in dependency order: onboarding bootstrap, registration, login, collection, artists, genres, styles, albums, invitations, token minting and all records needed by route cases; capture dynamic ids, JWTs, `inv_` tokens and share values into a mode-0600 temporary `--vars` file outside the corpus (D-03, D-07, D-11). Use the existing artisan `oauth-client` command and admin-user setup only for objects the API cannot create; document exact invocation and isolation settings in `parity/README.md`. Populate `manifest.yaml` with **all 154** source-derived route IDs, full method/path/auth group, stable order, fixture names and `status: pending` now; case request details and fixtures may be absent until Task 2, but the route-ID set must already be exact. Add the first GET genres case and record it via `summer parity:record` against PHP. Generate `routes.snapshot` with the 154 canonical method/path/group IDs and PHP source digest for hermetic app tests. Implement `check_corpus.go` as an app-owned validator that compares all manifest IDs to this snapshot and, when `--routes` is supplied, also checks live PHP source; it must fail on duplicate, missing or invented ids, print recorded/154 in `--allow-incomplete` mode, and fail on incomplete coverage when `--require-recorded` is passed. The seed and first route must self-replay against a new parity DB. Do not modify PHP source, Nuxt, MCP or a developer's existing DB.
go run ../fonoteka.go/parity/check_corpus.go --manifest ../fonoteka.go/parity/manifest.yaml --routes /media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/routes.php --allow-incomplete && go vet ./... && go test ./... && (cd ../fonoteka.go && go vet ./... && go test ./...)
- PHP runs on `127.0.0.1:8423` using a newly created parity database; no PHP repo files change.
- The API seed creates deterministic test identities and records credential/id captures, and first GET genres fixture comes from live PHP.
- Task 1 manifest and snapshot contain all 154 PHP route IDs with exact method/path/group; only fixture coverage remains incomplete.
One real PHP route and the API seed are captured from an isolated dataset.
Task 2: Record a case for each of the 154 PHP routes
../fonoteka.go/parity/manifest.yaml, ../fonoteka.go/parity/fixtures/seed/bootstrap.yaml, ../fonoteka.go/parity/fixtures/routes/*.yaml, ../fonoteka.go/parity/check_corpus.go, ../fonoteka.go/parity/README.md
../fonoteka.go/parity/manifest.yaml, ../fonoteka.go/parity/fixtures/seed/bootstrap.yaml, /media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/routes.php, /media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/traits/SerializesFonoteka.php, /media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/tests/AccessTestCase.php, .planning/phases/02-api-parity-harness-bootstrap/02-CONTEXT.md
Use the already complete 154-ID manifest from Task 1 as the generated case-input index; fill request body/identity/expected-status recipes from PHP handlers and existing PHP tests in deterministic batches of **at most 12 route IDs**, then run `summer parity:record --manifest ... --next-batch 12 --resume --vars ` for only the next incomplete batch (D-01, D-08). Each invocation must validate hashes of completed fixtures, atomically write no more than 12 new route files, print `recorded/154`, and leave a resumable state derived from the manifest plus existing fixture hashes; do not hand-author or commit 154 files as one change. After each batch, run `go run ../fonoteka.go/parity/check_corpus.go --manifest ../fonoteka.go/parity/manifest.yaml --routes /media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/routes.php --allow-incomplete`, PHP self-replay for that batch, root and sibling vet/test, then commit that batch as one logical change before proceeding; including manifest and progress notes, each commit must touch no more than 15 files. Cover all six route groups: JWT with and without password gate, throttled public onboarding, public share, scoped personal-token, and unprefixed OAuth; use full prefixed paths and group-specific identities. Include deterministic success cases when the seed permits and declared 401/404/422/423/throttle cases for relevant error behavior. For external-service routes, record a deterministic local error or validation response as an explicit case, not a fabricated success. Order destructive/mutating cases with unique data or a fresh reset boundary. Store each route case as one-step `routes/.yaml`; use relative binary sidecars for image/CSV. After the final batch, `check_corpus.go --require-recorded` must prove 154 committed fixtures and no unsanitized credentials; recreate the parity DB and self-replay the entire seed+route corpus. Save exact commands and totals in `parity/README.md` without secrets.
go run ../fonoteka.go/parity/check_corpus.go --manifest ../fonoteka.go/parity/manifest.yaml --routes /media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/routes.php --require-recorded && go vet ./... && go test ./... && (cd ../fonoteka.go && go vet ./... && go test ./...)
- Manifest has exactly 154 distinct route ids matching PHP source, with at least one committed flow per id.
- Each resumable record invocation adds at most 12 route fixtures and each batch commit touches at most 15 files; every batch passes its own coverage, PHP replay and Go checks.
- Coverage reports `154 recorded`, `0 unrecorded`; PHP self-replay on a fresh parity DB reports zero failures.
- Route cases use real PHP responses, explicitly declared expected statuses and scrubbed fixture values.
The full PHP route surface is recorded and self-consistent before Go endpoints exist.
Task 3: Capture unchanged Nuxt and MCP client journeys
../fonoteka.go/parity/capture_clients.mjs, ../fonoteka.go/parity/capture-rules.yaml, ../fonoteka.go/parity/fixtures/nuxt/*.yaml, ../fonoteka.go/parity/fixtures/mcp/*.yaml, ../fonoteka.go/parity/README.md
../fonoteka.go/parity/manifest.yaml, ../fonoteka.go/parity/README.md, /media/nvme/dev/golem15/fonoteka/vue-fonoteka-app/README.md, /media/nvme/dev/golem15/fonoteka/vue-fonoteka-app/nuxt.config.ts, /media/nvme/dev/golem15/fonoteka/fonoteka-mcp/README.md, /media/nvme/dev/golem15/fonoteka/fonoteka-mcp/src/config.ts, /media/nvme/dev/golem15/fonoteka/fonoteka-mcp/src/client.ts, .planning/phases/02-api-parity-harness-bootstrap/02-CONTEXT.md
Write `parity/capture-rules.yaml` with method/path-specific kept headers, response JSON paths for JWT/`inv_` tokens, response Location query for OAuth code, request form field for PKCE verifier, cookie names and identity mappings; this committed file contains no values (D-07, D-11). Add app-owned `capture_clients.mjs` as an orchestrator, not a new dependency package: use Node `createRequire` anchored to `/media/nvme/dev/golem15/fonoteka/vue-fonoteka-app/package.json` to resolve its installed `@playwright/test`, and another anchored to `/media/nvme/dev/golem15/fonoteka/fonoteka-mcp/package.json` to resolve its installed `@modelcontextprotocol/sdk`; import the ESM SDK via `pathToFileURL(require.resolve(...))`. `--check-deps` must fail clearly if either package is unavailable. `--capture` starts the unchanged Nuxt app with process-local `NUXT_DEV_BACKEND_ORIGIN=http://127.0.0.1:8422` and MCP server with `FONOTEKA_API_URL` and `FONOTEKA_MCP_AUTH_SERVER` set to the same proxy; it regenerates a mode-0600 temp `--vars` file from the recorded seed and starts proxy with `--rules parity/capture-rules.yaml --vars `. Exercise Nuxt login, collection/album/genre views and a write path; exercise MCP token discovery, at least one read tool, one write or scope-denied tool, and OAuth discovery/PKCE including browser authorize/consent. Since unchanged clients do not set `X-Parity-Session`, run each journey with an exclusive proxy `--session` value and flush/restart between journeys. Capture code/verifier variables and preserve cookies/redirects; automate consent click with Playwright. The wrapper accepts only PHP origin `http://127.0.0.1:8423`, stops child processes and deletes the private vars file after recording. Recreate the isolated database and PHP self-replay seed, route and client flows. Extend `check_corpus.go` with client fixture presence and scrub scans; no real JWT, `inv_` token, cookie, client secret or auth code may survive. Document exact commands and results without modifying clients.
node ../fonoteka.go/parity/capture_clients.mjs --check-deps && node ../fonoteka.go/parity/capture_clients.mjs --capture --php-origin http://127.0.0.1:8423 && go run ../fonoteka.go/parity/check_corpus.go --manifest ../fonoteka.go/parity/manifest.yaml --routes /media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/routes.php --require-recorded --require-clients --check-secrets && go vet ./... && go test ./... && (cd ../fonoteka.go && go vet ./... && go test ./...)
- At least one real multi-step Nuxt flow and one real multi-step MCP flow are committed, including an MCP OAuth discovery/PKCE flow.
- Nuxt/MCP source files are unchanged and both connect through proxy `127.0.0.1:8422` to PHP `127.0.0.1:8423`.
- All client fixtures use placeholders and PHP self-replay passes after a fresh API seed.
The corpus includes the exact traffic emitted by both unchanged real clients.
<threat_model>
Trust Boundaries
Boundary
Description
Existing developer DB → parity PHP process
Process env must select a newly created isolated database.
PHP and clients → committed corpus
Responses can include credentials and private content.
STRIDE Threat Register
Threat ID
Category
Severity
Component
Disposition
Mitigation Plan
T-02-02
Information disclosure
high
parity/fixtures
mitigate
Use deterministic test identities, scrub every credential category before save, reject unknown secrets, scan corpus before commit.
T-02-04
Tampering
high
parity/manifest.yaml and corpus validator
mitigate
Compare exact PHP source route IDs/count and require one fixture per id; PHP self-replay verifies expected data.
T-02-05
Denial of service/Data loss
high
Isolated PHP setup
mitigate
Explicit fresh parity database and process-local DB env; refuse to run record/reset against a database not named for parity.
</threat_model>
Run corpus validator with all flags, both modules' available vet/test checks, and `summer parity:replay --manifest ../fonoteka.go/parity/manifest.yaml --fixtures ../fonoteka.go/parity/fixtures --target http://127.0.0.1:8423 --self-check` against a newly seeded PHP database. Record the full output in the plan summary.
<success_criteria>
Exactly 154 source-matched PHP routes each have a recorded case, real Nuxt and MCP flows are present, secret scan passes, and fresh PHP self-replay is green.
</success_criteria>
Create `.planning/phases/02-api-parity-harness-bootstrap/02-03-SUMMARY.md` after completion.