D-01 D-02: `summer parity:proxy` on 127.0.0.1:8422 forwards a named Nuxt or MCP session to fixed PHP upstream 127.0.0.1:8423 and writes an ordered flow.
D-07 D-11: Kept credentials become named placeholders at record time and captures resolve them consistently during replay.
D-13 D-14 D-15: Comparison enforces JSON token types, field presence, date/id shapes, selected headers and exact non-JSON bytes.
D-16: A failing comparison does not hide later independent steps or flows, and the 154-route manifest produces a coverage table.
path
provides
tide/proxy.go
Bounded fixed-upstream capture proxy
path
provides
tide/variables.go
Capture, placeholder substitution and secret scrub
path
provides
tide/normalize.go
Shape-aware per-path normalization
path
provides
tide/manifest.go
Validated generic manifest and route-case runner
path
provides
tide/report.go
Path differences and route coverage table
from
to
via
cmd/summer/parity.go
tide/proxy.go
parity:proxy bonfire command
from
to
via
tide/proxy.go
tide/rules.go
Loaded per-path capture, keep-header and scrub rules
from
to
via
tide/proxy.go
tide/variables.go
Private seed variable store shared across session steps
from
to
via
tide/manifest.go
tide/record.go
Ordered route-case recording
from
to
via
tide/replay.go
tide/variables.go
Capture and variable resolution before each request
from
to
via
tide/replay.go
tide/normalize.go
Format assertions before masking and comparison
**As a** port developer, **I want to** capture real client sessions and a manifest of route cases, **so that** the same recorder can produce a safe and measurable parity corpus.
Purpose: Extend the one-route path into a reusable flow recorder and strict replay engine.
Output: Fixed-upstream proxy, named captures, secret scrub, manifest runner, normalizer, header rules and coverage report.
@.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-PATTERNS.md
@.planning/phases/02-api-parity-harness-bootstrap/02-01-SUMMARY.md
@tide/flow.go
@tide/record.go
@tide/replay.go
@tide/diff.go
@cmd/summer/parity.go
Use Plan 01's `tide.Flow`/`Step`, `LoadFlow`/`SaveFlow`, `RecordFlow`/`ReplayFlow`, and `Result` contracts. Extend them without changing the YAML version. `bonfire.Flag` carries strings only; parse `--listen`, `--upstream`, `--session`, `--rules`, `--vars`, `--manifest`, `--fixtures`, `--update`, and `--target` in the command adapter. `--rules` names committed non-secret YAML rule data; `--vars` names a process-private, mode-0600 variable-store file outside the corpus. `tide` owns no Fonoteka-specific route names.
Task 1: Capture a complete named session through the proxy
tide/proxy.go, tide/rules.go, tide/flow.go, tide/fixture.go, tide/proxy_test.go, tide/rules_test.go, cmd/summer/parity.go, cmd/summer/parity_test.go
tide/flow.go, tide/fixture.go, tide/record.go, cmd/summer/parity.go, bonfire/command.go, .planning/phases/02-api-parity-harness-bootstrap/02-CONTEXT.md, .planning/phases/02-api-parity-harness-bootstrap/02-RESEARCH.md
Two requests carrying one explicit session id become one ordered two-step flow. Cookies, redirect status/location and response bytes reach the client unchanged. A request for another session becomes a different flow. Oversized traffic never leaves a partial fixture. An upstream destination supplied by a client request is ignored.
Write a proxy smoke test against a fixed httptest upstream, then implement `parity:proxy` as a `bonfire.Command` backed by `httputil.ReverseProxy` with `Rewrite`/`SetURL`, fixed upstream and loopback-only bind (D-01, D-02, D-04). Default listen `127.0.0.1:8422` and upstream `http://127.0.0.1:8423`; the CLI may override the upstream only with an explicit loopback URL for tests. Require `--rules` and load a strict YAML rule file through `tide/rules.go`, keyed by method and path pattern, with kept request/response headers and named capture sources; reject unknown rule fields. Accept `--vars` only as a mode-0600 file outside the fixture tree, initialized by the seed flow; use it as the session variable store, never as a committed fixture. Group requests by explicit local `X-Parity-Session` header or command `--session`, and write `nuxt/.yaml` or `mcp/.yaml` under a chosen fixtures directory as ordered flows (D-05); reject path separators and duplicate session names. Buffer bounded request and response bodies, restore them before forwarding, preserve cookies, status, Location and raw bytes, and fail recording on truncation rather than saving partial data. Keep the proxy local plain HTTP; never use an incoming Host or URL as upstream. Fail fixture writes on unclassified credential-shaped values even before Task 2 adds named substitution. Expose a clean stop/flush path so a session is durable after shutdown.
go vet ./... && go test ./tide ./cmd/summer -run 'TestProxy|TestParityCommands' -count=1 && go test ./...
- A named two-request session writes one flow with two ordered steps; a second session does not interleave.
- Forwarded cookie, redirect and response body match the upstream bytes.
- Proxy rejects non-loopback bind/upstream and overflow without a committed partial fixture.
Real Nuxt or MCP traffic can be recorded as a named flow through a loopback proxy.
Task 2: Replay stateful flows with safe capture and strict differences
tide/flow.go, tide/record.go, tide/replay.go, tide/diff.go, tide/proxy.go, tide/rules.go, tide/variables.go, tide/normalize.go, tide/proxy_test.go, tide/rules_test.go, cmd/summer/parity.go, cmd/summer/parity_test.go
tide/flow.go, tide/record.go, tide/replay.go, tide/diff.go, tide/proxy.go, tide/rules.go, cmd/summer/parity.go, .planning/phases/02-api-parity-harness-bootstrap/02-CONTEXT.md, .planning/research/PITFALLS.md
Login captures a JWT; later Authorization, cookie, path and body placeholders resolve from the same store. The committed flow contains `{{jwt:alice}}`, not a token. `*_at` accepts the recorded Carbon `+00:00` form while masking its value; `Z`, number in place of a fixed-decimal string, `null` in place of `[]`, or omitted conditional key fails at its JSON path. A failed capture blocks dependent steps in that flow; other flows continue.
Implement named capture rules with response JSON path, response header/query (including a Location redirect's `code`), request form field (including PKCE `code_verifier`), variable name, identity and secret category; apply them to proxy traffic as well as scripted recording (D-07, D-11). Wire `cmd/summer/parity.go` `--rules` and `--vars` into `tide/proxy.go`; load the mode-0600 seed credential map from `--vars`, update it after each captured step, and scrub known JWT, `inv_` token, OAuth client secret/code, PKCE verifier and `auth_token` cookie occurrences in kept headers, query and body before serialization or error logging. `parity:record` exports seed captures only to this private `--vars` path; never write the plaintext map into YAML or print it. Fail if an unclassified credential-like value remains, and fail replay before HTTP send if a placeholder is unresolved. Support variable references in paths, query, headers and bodies. Test a proxy login/token session where response JSON yields `{{jwt:alice}}`, a later Authorization request resolves it, and a form `code_verifier` plus redirect `code` are captured and scrubbed. Add global/per-step normalizers keyed by JSON path patterns: assert Carbon date shape with explicit `+00:00` before masking `*_at`; assert integer IDs before masking; compare captured variables by reference across steps; allow per-step enable/disable while leaving slugs exact (D-15). Extend `diff.go` to compare the D-13 parity classes explicitly: nil/null versus `[]`, Carbon date versus `Z`, null versus absent date keys, tri-state booleans, envelope/conditional keys and string-versus-number money. Compare only global Content-Type/pagination/CORS allow-list plus per-route additions `Cache-Control`, `Pragma`, `WWW-Authenticate`, protected-resource-metadata and `Content-Disposition` (D-14); never compare Date, Server or request ids. Use content type to choose structural JSON versus exact bytes. Continue after mismatches, stopping only capture-dependent remainder of one flow (D-16). Keep all rules generic and data-driven.
go vet ./... && go test ./tide -run 'TestProxy|TestCapture|TestScrub|TestNormalize|TestDiff|TestHeaders|TestFlow' -count=1 && go test ./...
- YAML and logs contain no recorded credentials; unknown credential-shaped values fail recording.
- The proxy reads committed capture rules and a private seed variable store; login/token and PKCE paths scrub before fixture write.
- Placeholder resolution is deterministic across ordered steps and missing values prevent request dispatch.
- Each named parity class fails with expected/actual at a concrete JSON path; dynamic values only mask after shape checks.
- A failed comparison continues the run, while a failed capture skips only its dependent flow remainder.
A captured session replays with regenerated state and detects the PHP response distinctions that matter to clients.
Task 3: Record manifest route cases and report complete coverage
tide/manifest.go, tide/report.go, tide/fixture.go, tide/manifest_test.go, cmd/summer/parity.go, cmd/summer/parity_test.go
tide/flow.go, tide/fixture.go, tide/record.go, tide/replay.go, tide/variables.go, cmd/summer/parity.go, .planning/phases/02-api-parity-harness-bootstrap/02-CONTEXT.md, .planning/phases/02-api-parity-harness-bootstrap/02-VALIDATION.md
Define a generic manifest contract with ordered route IDs, method/full path pattern, auth group, `status: pending|ported`, identities, case ids, expected statuses, header overrides, normalizer overrides, optional seed hook and fixture path. In both validation modes require every route's unique ID, method, full path, auth group and status. Under explicit `--allow-incomplete`, permit empty or unfinished case details and missing fixture files while still rejecting duplicate/invalid route IDs, unsafe paths and unknown groups/status; report recorded/total. Under `--require-recorded`, require every case to have complete identity/request/status/header/fixture data and a readable, valid fixture for every route. Add `parity:record --manifest ... --target ... --fixtures ... --vars ...` that drives ordered seed then route cases through `RecordFlow`, writes one-step files under `routes/`, and requires explicit `--update` to overwrite committed fixtures; never silently mix old/new runs (D-01, D-05, D-08). Provide `--next-batch 15 --resume`: each invocation selects at most 15 still-unrecorded route ids in stable manifest order, validates existing fixture hashes before skipping them, records atomically, prints a resumable count, and exits successfully while coverage is incomplete; refuse a batch size above 15 in this workflow. Add `parity:replay --manifest ... --fixtures ... --target ...` that runs all recorded flows and prints per-step status/header/JSON-path or byte diffs plus a table of recorded, passing, failing and unrecorded manifest routes (D-12, D-16). Pending routes are measured but do not fail Go CI; `ported` mismatches and unrecorded required cases exit nonzero. The explicit `--self-check` flag checks all recorded cases against PHP regardless of pending/ported. Add sidecar binary body support using a relative file path, digest and content type; reject absolute or parent-traversing paths before read/write. Test with a tiny two-route manifest including one missing fixture, one passing fixture, and one failing case, then test a synthetic 16-route manifest needing two resumable batches. Keep the 154-route value as the app manifest's validated count, not a framework hardcode.
go vet ./... && go test ./tide ./cmd/summer -run 'TestManifest|TestCoverage|TestParityCommands' -count=1 && go test ./...
- Manifest recording produces one flow file per route case and never overwrites one without `--update`.
- Draft validation accepts 154 complete route identities with empty cases only under `--allow-incomplete`; `--require-recorded` rejects every incomplete case or missing fixture.
- `--next-batch 15 --resume` records at most 15 routes per invocation and resumes a 16-route synthetic manifest without recapturing completed cases.
- A two-route synthetic manifest reports recorded/passing/failing/unrecorded accurately and returns nonzero on an unrecorded required case.
- Unsafe binary sidecar paths are rejected and digest mismatch fails replay.
- Framework package and CLI still contain no Fonoteka route names.
The same command path can capture and audit a complete app-specific route manifest.
<threat_model>
Trust Boundaries
Boundary
Description
Browser/MCP → local proxy → PHP
Client-controlled requests cross a recording proxy.
PHP credentials and body data → YAML corpus
Live secrets and test data are serialized for commit.
Manifest/fixture paths → filesystem
Data files select output and binary sidecar paths.
STRIDE Threat Register
Threat ID
Category
Severity
Component
Disposition
Mitigation Plan
T-02-01
Spoofing/Denial of service
high
tide/proxy.go
mitigate
Bind loopback, pin loopback PHP upstream, ignore client destination, cap buffered bodies, reject truncation and flush only complete flows.
T-02-02
Information disclosure
high
tide/variables.go and fixture writer
mitigate
Scrub named credentials before disk/log output; reject unclassified credential shapes and unresolved placeholders.
T-02-03
Tampering
high
tide/normalize.go and tide/diff.go
mitigate
Assert token/date/id shapes before masking, compare key presence and selected security headers, and reject unsafe sidecars.
</threat_model>
Run synthetic proxy, stateful flow and manifest tests, then root vet/test. A real PHP run and 154-route corpus are delivered in Plan 03.
<success_criteria>
The proxy creates scrubbed multi-step flows, manifest mode records one-step route cases, replay prints exact differences and complete route coverage, and all framework tests remain green.
</success_criteria>
Create `.planning/phases/02-api-parity-harness-bootstrap/02-02-SUMMARY.md` after completion.