Files
summercms/.planning/phases/02-api-parity-harness-bootstrap/02-02-PLAN.md
2026-09-17 01:57:41 +02:00

16 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
02-api-parity-harness-bootstrap 02 execute 2
02-01
tide/flow.go
tide/fixture.go
tide/record.go
tide/replay.go
tide/diff.go
tide/proxy.go
tide/rules.go
tide/variables.go
tide/normalize.go
tide/manifest.go
tide/report.go
tide/proxy_test.go
tide/rules_test.go
tide/manifest_test.go
cmd/summer/parity.go
cmd/summer/parity_test.go
true
QA-01
QA-02
QA-03
truths artifacts key_links
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.

<execution_context> @/home/jin/.codex/get-shit-done/workflows/execute-plan.md @/home/jin/.codex/get-shit-done/templates/summary.md </execution_context>

@.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.