docs(02): create verified phase plans
This commit is contained in:
@@ -79,7 +79,28 @@ Plans:
|
|||||||
3. The harness's normalizer and assertions explicitly catch the parity classes named in research (nil vs `[]`, date format, tri-state booleans, envelope/conditional keys) on a synthetic test case, not just status codes.
|
3. The harness's normalizer and assertions explicitly catch the parity classes named in research (nil vs `[]`, date format, tri-state booleans, envelope/conditional keys) on a synthetic test case, not just status codes.
|
||||||
4. `go vet` and `go test ./...` are green, and the harness's own integration tests run against testcontainers Postgres.
|
4. `go vet` and `go test ./...` are green, and the harness's own integration tests run against testcontainers Postgres.
|
||||||
|
|
||||||
**Plans**: TBD
|
**Plans**: 5 plans
|
||||||
|
|
||||||
|
Plans:
|
||||||
|
**Wave 1**
|
||||||
|
|
||||||
|
- [ ] 02-01-PLAN.md — Record and replay one fixture through the `summer` CLI
|
||||||
|
|
||||||
|
**Wave 2** *(blocked on Wave 1 completion)*
|
||||||
|
|
||||||
|
- [ ] 02-02-PLAN.md — Capture safe proxy sessions and manifest routes with strict diffs
|
||||||
|
|
||||||
|
**Wave 3** *(blocked on Wave 2 completion)*
|
||||||
|
|
||||||
|
- [ ] 02-03-PLAN.md — Record all 154 PHP routes and real Nuxt/MCP flows
|
||||||
|
|
||||||
|
**Wave 4** *(blocked on Wave 3 completion)*
|
||||||
|
|
||||||
|
- [ ] 02-04-PLAN.md — Wire honest Go replay with testcontainers Postgres
|
||||||
|
|
||||||
|
**Wave 5** *(blocked on Waves 1–4 completion)*
|
||||||
|
|
||||||
|
- [ ] 02-05-PLAN.md — Complete contract, security and integration tests
|
||||||
|
|
||||||
### Phase 3: First vertical slice — genres end to end
|
### Phase 3: First vertical slice — genres end to end
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
gsd_state_version: 1.0
|
gsd_state_version: 1.0
|
||||||
milestone: v1.0
|
milestone: v1.0
|
||||||
milestone_name: milestone
|
milestone_name: milestone
|
||||||
status: ready_to_plan
|
status: executing
|
||||||
stopped_at: Phase 01 complete (4/4) — ready to discuss Phase 02
|
stopped_at: Phase 2 planned (5 plans) — ready to execute
|
||||||
last_updated: 2026-09-16T12:44:56.877Z
|
last_updated: "2026-09-16T23:56:53.091Z"
|
||||||
last_activity: 2026-09-16 -- Phase 01 execution started
|
last_activity: 2026-09-16 -- Phase 02 planning complete
|
||||||
progress:
|
progress:
|
||||||
total_phases: 15
|
total_phases: 15
|
||||||
completed_phases: 1
|
completed_phases: 1
|
||||||
total_plans: 4
|
total_plans: 9
|
||||||
completed_plans: 4
|
completed_plans: 4
|
||||||
percent: 7
|
percent: 7
|
||||||
---
|
---
|
||||||
@@ -27,8 +27,8 @@ See: .planning/PROJECT.md (updated 2026-09-16)
|
|||||||
|
|
||||||
Phase: 02
|
Phase: 02
|
||||||
Plan: Not started
|
Plan: Not started
|
||||||
Status: Ready to plan
|
Status: Ready to execute
|
||||||
Last activity: 2026-09-16
|
Last activity: 2026-09-16 -- Phase 02 planning complete
|
||||||
|
|
||||||
Progress: [█░░░░░░░░░] 7%
|
Progress: [█░░░░░░░░░] 7%
|
||||||
|
|
||||||
|
|||||||
137
.planning/phases/02-api-parity-harness-bootstrap/02-01-PLAN.md
Normal file
137
.planning/phases/02-api-parity-harness-bootstrap/02-01-PLAN.md
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
---
|
||||||
|
phase: 02-api-parity-harness-bootstrap
|
||||||
|
plan: 01
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on: []
|
||||||
|
files_modified:
|
||||||
|
- go.mod
|
||||||
|
- go.sum
|
||||||
|
- tide/flow.go
|
||||||
|
- tide/fixture.go
|
||||||
|
- tide/record.go
|
||||||
|
- tide/replay.go
|
||||||
|
- tide/diff.go
|
||||||
|
- tide/testdata/one-route-spec.yaml
|
||||||
|
- tide/roundtrip_test.go
|
||||||
|
- cmd/summer/main.go
|
||||||
|
- cmd/summer/parity.go
|
||||||
|
- cmd/summer/parity_test.go
|
||||||
|
autonomous: true
|
||||||
|
requirements: [QA-01, QA-02, QA-03]
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "D-04: A developer can invoke `summer parity:record` and `summer parity:replay` through bonfire against one HTTP route."
|
||||||
|
- "D-05 D-06: The recorded versioned YAML flow contains an ordered request/response step with verbatim JSON body text."
|
||||||
|
- "D-12 D-13: Replaying that fixture against an arbitrary httptest HTTP backend succeeds or reports an exact structural JSON path or raw byte mismatch."
|
||||||
|
artifacts:
|
||||||
|
- path: tide/flow.go
|
||||||
|
provides: Framework-owned, versioned flow and step contracts
|
||||||
|
- path: tide/fixture.go
|
||||||
|
provides: Validated YAML load and atomic save
|
||||||
|
- path: tide/record.go
|
||||||
|
provides: HTTP recording engine
|
||||||
|
- path: tide/replay.go
|
||||||
|
provides: Backend-independent replay engine
|
||||||
|
- path: cmd/summer/parity.go
|
||||||
|
provides: Bonfire parity commands
|
||||||
|
key_links:
|
||||||
|
- from: cmd/summer/main.go
|
||||||
|
to: cmd/summer/parity.go
|
||||||
|
via: toolCommands registration
|
||||||
|
- from: cmd/summer/parity.go
|
||||||
|
to: tide/record.go
|
||||||
|
via: RecordFlow API
|
||||||
|
- from: cmd/summer/parity.go
|
||||||
|
to: tide/replay.go
|
||||||
|
via: ReplayFlow API
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
**As a** port developer, **I want to** record one HTTP route and replay it against another backend, **so that** I can see the first parity result before porting an endpoint.
|
||||||
|
|
||||||
|
Purpose: Establish a complete fixture-to-diff path on the Phase 1 command kernel.
|
||||||
|
Output: Generic `tide` flow schema, fixture IO, one-route recorder/replayer, and two working `summer parity:*` commands.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@/home/jin/.codex/get-shit-done/workflows/execute-plan.md
|
||||||
|
@/home/jin/.codex/get-shit-done/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@.planning/ROADMAP.md
|
||||||
|
@.planning/REQUIREMENTS.md
|
||||||
|
@.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-PATTERNS.md
|
||||||
|
@bonfire/command.go
|
||||||
|
@cmd/summer/main.go
|
||||||
|
<interfaces>
|
||||||
|
`bonfire.Command` is a value with `Name`, `Description`, `Flags []bonfire.Flag`, `Args []bonfire.Arg`, and `Run(context.Context, bonfire.Input, bonfire.Output) error`. Flags are strings; `bonfire.Input.Flag(name)` retrieves them. `cmd/summer/main.go` assembles values in `toolCommands()` and `main()` maps command errors to exit status 1.
|
||||||
|
|
||||||
|
Define the public `tide` contract here for later plans: `Flow{Version, Name, Description, SeedHook, Steps}`, `Step{ID, RouteID, Request, Response, Capture, Normalize, Headers}`, `Request{Method, Path, Query, Headers, Body}` where Query preserves the raw query string, and `Response{Status, Headers, Body, BodyFile, SHA256}`. Use `LoadFlow`, `SaveFlow`, `RecordFlow`, `ReplayFlow`, and a `Result` carrying per-step differences. Keep transport/target, fixture filesystem and output injected; neither `tide` nor its tests import Fonoteka.
|
||||||
|
</interfaces>
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>Task 1: Record and replay one route through the summer CLI</name>
|
||||||
|
<files>go.mod, go.sum, tide/flow.go, tide/fixture.go, tide/record.go, tide/replay.go, tide/diff.go, tide/testdata/one-route-spec.yaml, tide/roundtrip_test.go, cmd/summer/main.go, cmd/summer/parity.go, cmd/summer/parity_test.go</files>
|
||||||
|
<read_first>bonfire/command.go, bonfire/root.go, cmd/summer/main.go, cmd/summer/main_test.go, .planning/phases/02-api-parity-harness-bootstrap/02-CONTEXT.md, .planning/phases/02-api-parity-harness-bootstrap/02-RESEARCH.md</read_first>
|
||||||
|
<behavior>A local httptest server answers GET /sample with JSON. `parity:record --spec one-route-spec.yaml --target URL --output PATH` produces one valid flow; `parity:replay --fixtures PATH --target URL` succeeds; a changed response fails with `$.data` and expected/actual values. A non-JSON response changed by one byte reports the byte offset.</behavior>
|
||||||
|
<action>In one committing task, write a version-1 one-step YAML request spec and black-box tests through `bonfire.NewRoot("summer", toolCommands(), writer)` plus `httptest.Server` (D-04, D-05, D-06). Run the focused tests once to observe RED without committing, then implement enough `tide.Flow`, validated YAML load/save, HTTP record/replay and `bonfire` command registration to make the complete round trip GREEN. The fixture keeps request/response body bytes as YAML literal block scalars, records status and Content-Type, and saves atomically; changed JSON scalar or non-JSON byte returns a nonzero CLI error with expected/actual diagnostics. Include both original and changed handlers in the tests. Before adding direct goccy/go-yaml to `go.mod`, review RESEARCH.md Package Legitimacy Audit and run `/home/jin/.local/bin/slopcheck scan go.mod`; after module resolution inspect `git diff -- go.mod go.sum` and scan again. Keep the package generic, preserve `toolchain go1.27.0`, and commit only after full root vet/test is green. Plan 05 expands behavior coverage.</action>
|
||||||
|
<verify><automated>/home/jin/.local/bin/slopcheck scan go.mod && go vet ./... && go test ./... && go test ./tide ./cmd/summer -run 'TestParityRoundTrip|TestParityCommands' -count=1</automated></verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- RED is observed before implementation within the task, then all tests are GREEN before its single commit.
|
||||||
|
- A real one-route fixture records and replays through `summer` against httptest; changed JSON or bytes report a mismatch.
|
||||||
|
- Full root `go vet ./...` and `go test ./...` pass at commit; tests use temp fixtures and injected writers.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>A developer can already record and replay one HTTP route through the CLI after this task commits.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Make the one-route record/replay path work</name>
|
||||||
|
<files>go.mod, go.sum, tide/flow.go, tide/fixture.go, tide/record.go, tide/replay.go, tide/diff.go, cmd/summer/main.go, cmd/summer/parity.go, tide/roundtrip_test.go, cmd/summer/parity_test.go</files>
|
||||||
|
<read_first>go.mod, bonfire/command.go, bonfire/root.go, cmd/summer/main.go, tide/testdata/one-route-spec.yaml, tide/roundtrip_test.go, cmd/summer/parity_test.go, .planning/phases/02-api-parity-harness-bootstrap/02-PATTERNS.md</read_first>
|
||||||
|
<action>Refine the working Plan 01 Task 1 path into the full public `tide` contract above: strict `version: 1` goccy/go-yaml decode rejects unknown fields, empty flow names, unsafe paths and duplicate step ids (D-05, D-06); atomic save syncs before rename. Inject `http.Client` and context, bound request/response body reads, and reject truncation before committing a fixture. In `diff.go`, decode JSON with `UseNumber`, recursively compare exact key sets, array lengths, scalar values and token types while ignoring object key order; report `$.path` with expected and actual values. For non-JSON content types, compare bytes and report offset plus printable escaped bytes. Keep `parity:record` and `parity:replay` in `toolCommands()` with string flags `--spec`, `--target`, `--output`, `--fixtures`; errors return through bonfire so main exits nonzero (D-04, D-12, D-13). Reinspect the `go.mod`/`go.sum` diff and rerun slopcheck if dependency metadata changes. Do not add an app route, PHP-specific field or standalone CLI.</action>
|
||||||
|
<verify><automated>go vet ./... && go test ./... && go test ./tide ./cmd/summer -run 'TestParityRoundTrip|TestParityCommands' -count=1</automated></verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- `parity:record` writes one replayable YAML flow and `parity:replay` accepts it.
|
||||||
|
- Identical JSON with changed object key order passes; missing keys and changed string/number types fail at named paths.
|
||||||
|
- Non-JSON changes fail with byte diagnostics; malformed YAML and oversized bodies fail before committing a fixture.
|
||||||
|
- Root `go vet ./...` and `go test ./...` exit 0.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>A developer can record and replay one fixture using the installed summer tool.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<threat_model>
|
||||||
|
## Trust Boundaries
|
||||||
|
|
||||||
|
| Boundary | Description |
|
||||||
|
|---|---|
|
||||||
|
| YAML spec and HTTP target → local process | Untrusted paths, headers and bodies become outbound requests and fixture files. |
|
||||||
|
|
||||||
|
## STRIDE Threat Register
|
||||||
|
|
||||||
|
| Threat ID | Category | Severity | Component | Disposition | Mitigation Plan |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| T-02-01 | Tampering/Denial of service | high | `tide.LoadFlow`, recorder and fixture writer | mitigate | Strict schema, bounded request/response body, context cancellation, safe output path and atomic writes; reject partial recordings. |
|
||||||
|
| T-02-SC | Tampering | high | Go module changes | mitigate | Apply RESEARCH.md Package Legitimacy Audit before module resolution and run slopcheck; block any package marked ASSUMED/SUS pending human verification. |
|
||||||
|
</threat_model>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
Run the one-route CLI smoke against httptest, then root `go vet ./...` and `go test ./...`. Confirm the written YAML can be loaded and replayed without changing the input spec.
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
One route fixture records and replays through bonfire, a modified JSON field produces a structural path diff, and modified non-JSON bytes produce a byte diff.
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
Create `.planning/phases/02-api-parity-harness-bootstrap/02-01-SUMMARY.md` after completion.
|
||||||
|
</output>
|
||||||
172
.planning/phases/02-api-parity-harness-bootstrap/02-02-PLAN.md
Normal file
172
.planning/phases/02-api-parity-harness-bootstrap/02-02-PLAN.md
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
---
|
||||||
|
phase: 02-api-parity-harness-bootstrap
|
||||||
|
plan: 02
|
||||||
|
type: execute
|
||||||
|
wave: 2
|
||||||
|
depends_on: [02-01]
|
||||||
|
files_modified:
|
||||||
|
- 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
|
||||||
|
autonomous: true
|
||||||
|
requirements: [QA-01, QA-02, QA-03]
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "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."
|
||||||
|
artifacts:
|
||||||
|
- path: tide/proxy.go
|
||||||
|
provides: Bounded fixed-upstream capture proxy
|
||||||
|
- path: tide/variables.go
|
||||||
|
provides: Capture, placeholder substitution and secret scrub
|
||||||
|
- path: tide/normalize.go
|
||||||
|
provides: Shape-aware per-path normalization
|
||||||
|
- path: tide/manifest.go
|
||||||
|
provides: Validated generic manifest and route-case runner
|
||||||
|
- path: tide/report.go
|
||||||
|
provides: Path differences and route coverage table
|
||||||
|
key_links:
|
||||||
|
- from: cmd/summer/parity.go
|
||||||
|
to: tide/proxy.go
|
||||||
|
via: parity:proxy bonfire command
|
||||||
|
- from: tide/proxy.go
|
||||||
|
to: tide/rules.go
|
||||||
|
via: Loaded per-path capture, keep-header and scrub rules
|
||||||
|
- from: tide/proxy.go
|
||||||
|
to: tide/variables.go
|
||||||
|
via: Private seed variable store shared across session steps
|
||||||
|
- from: tide/manifest.go
|
||||||
|
to: tide/record.go
|
||||||
|
via: Ordered route-case recording
|
||||||
|
- from: tide/replay.go
|
||||||
|
to: tide/variables.go
|
||||||
|
via: Capture and variable resolution before each request
|
||||||
|
- from: tide/replay.go
|
||||||
|
to: tide/normalize.go
|
||||||
|
via: Format assertions before masking and comparison
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
**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.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@/home/jin/.codex/get-shit-done/workflows/execute-plan.md
|
||||||
|
@/home/jin/.codex/get-shit-done/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<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
|
||||||
|
<interfaces>
|
||||||
|
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.
|
||||||
|
</interfaces>
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>Task 1: Capture a complete named session through the proxy</name>
|
||||||
|
<files>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</files>
|
||||||
|
<read_first>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</read_first>
|
||||||
|
<behavior>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.</behavior>
|
||||||
|
<action>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/<session>.yaml` or `mcp/<session>.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.</action>
|
||||||
|
<verify><automated>go vet ./... && go test ./tide ./cmd/summer -run 'TestProxy|TestParityCommands' -count=1 && go test ./...</automated></verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- 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.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>Real Nuxt or MCP traffic can be recorded as a named flow through a loopback proxy.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>Task 2: Replay stateful flows with safe capture and strict differences</name>
|
||||||
|
<files>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</files>
|
||||||
|
<read_first>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</read_first>
|
||||||
|
<behavior>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.</behavior>
|
||||||
|
<action>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.</action>
|
||||||
|
<verify><automated>go vet ./... && go test ./tide -run 'TestProxy|TestCapture|TestScrub|TestNormalize|TestDiff|TestHeaders|TestFlow' -count=1 && go test ./...</automated></verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- 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.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>A captured session replays with regenerated state and detects the PHP response distinctions that matter to clients.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 3: Record manifest route cases and report complete coverage</name>
|
||||||
|
<files>tide/manifest.go, tide/report.go, tide/fixture.go, tide/manifest_test.go, cmd/summer/parity.go, cmd/summer/parity_test.go</files>
|
||||||
|
<read_first>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</read_first>
|
||||||
|
<action>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.</action>
|
||||||
|
<verify><automated>go vet ./... && go test ./tide ./cmd/summer -run 'TestManifest|TestCoverage|TestParityCommands' -count=1 && go test ./...</automated></verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- 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.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>The same command path can capture and audit a complete app-specific route manifest.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
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.
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
Create `.planning/phases/02-api-parity-harness-bootstrap/02-02-SUMMARY.md` after completion.
|
||||||
|
</output>
|
||||||
152
.planning/phases/02-api-parity-harness-bootstrap/02-03-PLAN.md
Normal file
152
.planning/phases/02-api-parity-harness-bootstrap/02-03-PLAN.md
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
---
|
||||||
|
phase: 02-api-parity-harness-bootstrap
|
||||||
|
plan: 03
|
||||||
|
type: execute
|
||||||
|
wave: 3
|
||||||
|
depends_on: [02-02]
|
||||||
|
files_modified:
|
||||||
|
- ../fonoteka.go/parity/manifest.yaml
|
||||||
|
- ../fonoteka.go/parity/routes.snapshot
|
||||||
|
- ../fonoteka.go/parity/capture-rules.yaml
|
||||||
|
- ../fonoteka.go/parity/fixtures/seed/bootstrap.yaml
|
||||||
|
- ../fonoteka.go/parity/fixtures/routes/*.yaml
|
||||||
|
- ../fonoteka.go/parity/fixtures/nuxt/*.yaml
|
||||||
|
- ../fonoteka.go/parity/fixtures/mcp/*.yaml
|
||||||
|
- ../fonoteka.go/parity/capture_clients.mjs
|
||||||
|
- ../fonoteka.go/parity/check_corpus.go
|
||||||
|
- ../fonoteka.go/parity/README.md
|
||||||
|
autonomous: true
|
||||||
|
requirements: [QA-01, QA-02, QA-03]
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "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."
|
||||||
|
artifacts:
|
||||||
|
- path: ../fonoteka.go/parity/manifest.yaml
|
||||||
|
provides: Exact 154-route source map, auth groups, cases, headers and status
|
||||||
|
- path: ../fonoteka.go/parity/fixtures/seed/bootstrap.yaml
|
||||||
|
provides: Recorded API-created deterministic dataset
|
||||||
|
- path: ../fonoteka.go/parity/fixtures/routes/
|
||||||
|
provides: One-step captured PHP cases for every manifest route
|
||||||
|
- path: ../fonoteka.go/parity/fixtures/nuxt/
|
||||||
|
provides: Real browser-driven Nuxt flows
|
||||||
|
- path: ../fonoteka.go/parity/fixtures/mcp/
|
||||||
|
provides: Real MCP server flows including auth discovery
|
||||||
|
key_links:
|
||||||
|
- from: ../fonoteka.go/parity/manifest.yaml
|
||||||
|
to: ../fonoteka.go/parity/fixtures/routes/
|
||||||
|
via: Every route case points to a committed fixture
|
||||||
|
- from: ../fonoteka.go/parity/fixtures/seed/bootstrap.yaml
|
||||||
|
to: ../fonoteka.go/parity/fixtures/routes/
|
||||||
|
via: Captured ids and credentials feed later cases
|
||||||
|
- from: ../fonoteka.go/parity/capture_clients.mjs
|
||||||
|
to: http://127.0.0.1:8422
|
||||||
|
via: Unmodified Nuxt and MCP clients send requests through the proxy
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
**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.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@/home/jin/.codex/get-shit-done/workflows/execute-plan.md
|
||||||
|
@/home/jin/.codex/get-shit-done/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<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-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
|
||||||
|
<interfaces>
|
||||||
|
`../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.
|
||||||
|
</interfaces>
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Seed an isolated PHP instance and capture its first real route</name>
|
||||||
|
<files>../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</files>
|
||||||
|
<read_first>../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</read_first>
|
||||||
|
<action>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.</action>
|
||||||
|
<verify><automated>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 ./...)</automated></verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- 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.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>One real PHP route and the API seed are captured from an isolated dataset.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Record a case for each of the 154 PHP routes</name>
|
||||||
|
<files>../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</files>
|
||||||
|
<read_first>../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</read_first>
|
||||||
|
<action>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 <private-file>` 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/<case-id>.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.</action>
|
||||||
|
<verify><automated>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 ./...)</automated></verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- 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.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>The full PHP route surface is recorded and self-consistent before Go endpoints exist.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 3: Capture unchanged Nuxt and MCP client journeys</name>
|
||||||
|
<files>../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</files>
|
||||||
|
<read_first>../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</read_first>
|
||||||
|
<action>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 <temp-file>`. 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.</action>
|
||||||
|
<verify><automated>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 ./...)</automated></verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- 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.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>The corpus includes the exact traffic emitted by both unchanged real clients.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
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.
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
Create `.planning/phases/02-api-parity-harness-bootstrap/02-03-SUMMARY.md` after completion.
|
||||||
|
</output>
|
||||||
130
.planning/phases/02-api-parity-harness-bootstrap/02-04-PLAN.md
Normal file
130
.planning/phases/02-api-parity-harness-bootstrap/02-04-PLAN.md
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
---
|
||||||
|
phase: 02-api-parity-harness-bootstrap
|
||||||
|
plan: 04
|
||||||
|
type: execute
|
||||||
|
wave: 4
|
||||||
|
depends_on: [02-03]
|
||||||
|
files_modified:
|
||||||
|
- ../fonoteka.go/go.mod
|
||||||
|
- ../fonoteka.go/go.sum
|
||||||
|
- ../fonoteka.go/parity/parity_test.go
|
||||||
|
- ../fonoteka.go/parity/synthetic_test.go
|
||||||
|
- ../fonoteka.go/parity/testdata/synthetic-seed.yaml
|
||||||
|
- ../fonoteka.go/parity/testdata/synthetic-read.yaml
|
||||||
|
- ../fonoteka.go/parity/README.md
|
||||||
|
autonomous: true
|
||||||
|
requirements: [QA-02, QA-03]
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "D-09 D-10: A recorded seed and a named temporary seed hook can establish matching state before a route fixture is replayed."
|
||||||
|
- "D-12: `go test ./parity` starts a testcontainers Postgres and serves a synthetic SQL-backed handler through httptest."
|
||||||
|
- "D-16: Each flow is a selectable subtest, reports path differences and route coverage, and never counts an unported PHP route as a Go pass."
|
||||||
|
- "The Phase 3 genres route has a clear handler and seed-hook seam without claiming a Phase 2 Go implementation of PHP endpoints."
|
||||||
|
artifacts:
|
||||||
|
- path: ../fonoteka.go/parity/parity_test.go
|
||||||
|
provides: App-owned corpus runner, pending/ported subtests and coverage
|
||||||
|
- path: ../fonoteka.go/parity/synthetic_test.go
|
||||||
|
provides: SQL-backed httptest handler and Postgres lifecycle
|
||||||
|
- path: ../fonoteka.go/parity/testdata/synthetic-seed.yaml
|
||||||
|
provides: Integration seed flow with one real HTTP write
|
||||||
|
- path: ../fonoteka.go/parity/testdata/synthetic-read.yaml
|
||||||
|
provides: Integration read flow and named seed hook
|
||||||
|
key_links:
|
||||||
|
- from: ../fonoteka.go/parity/parity_test.go
|
||||||
|
to: ../fonoteka.go/parity/manifest.yaml
|
||||||
|
via: Ordered flow subtests and pending/ported status
|
||||||
|
- from: ../fonoteka.go/parity/parity_test.go
|
||||||
|
to: ../fonoteka.go/parity/synthetic_test.go
|
||||||
|
via: Injected handler factory and seed-hook registry
|
||||||
|
- from: ../fonoteka.go/parity/synthetic_test.go
|
||||||
|
to: tide/replay.go
|
||||||
|
via: httptest target replaying SQL-backed fixtures
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
**As a** port developer, **I want to** run the recorded corpus from the app test suite with real database state, **so that** later routes turn green one at a time without hiding unported coverage.
|
||||||
|
|
||||||
|
Purpose: Prove the in-process Go replay path before the first Fonoteka handler exists.
|
||||||
|
Output: App-owned testcontainers Postgres integration, synthetic write/read handler, seed-hook seam, selectable flow subtests and honest pending coverage.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@/home/jin/.codex/get-shit-done/workflows/execute-plan.md
|
||||||
|
@/home/jin/.codex/get-shit-done/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<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-VALIDATION.md
|
||||||
|
@.planning/phases/02-api-parity-harness-bootstrap/02-03-SUMMARY.md
|
||||||
|
@../fonoteka.go/go.mod
|
||||||
|
@../fonoteka.go/parity/manifest.yaml
|
||||||
|
@../fonoteka.go/parity/fixtures/seed/bootstrap.yaml
|
||||||
|
<interfaces>
|
||||||
|
`tide` exposes `LoadFlow`, `ReplayFlow`, manifest loading and a `Result`/coverage report. The app owns its `parity/` directory and imports `git.golem15.com/golem15/summercms/tide`, using a local `replace` in app `go.mod` if the framework module is unpublished. Testcontainers Postgres is a test-only dependency in the app module. `parity_test.go` should centralize a `newTarget(t, db) http.Handler` function and `seedHooks` map that Phase 3 can replace or extend without changing the generic harness.
|
||||||
|
</interfaces>
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>Task 1: Replay a synthetic write/read flow against Postgres</name>
|
||||||
|
<files>../fonoteka.go/go.mod, ../fonoteka.go/go.sum, ../fonoteka.go/parity/parity_test.go, ../fonoteka.go/parity/synthetic_test.go, ../fonoteka.go/parity/testdata/synthetic-seed.yaml, ../fonoteka.go/parity/testdata/synthetic-read.yaml</files>
|
||||||
|
<read_first>../fonoteka.go/go.mod, ../fonoteka.go/parity/manifest.yaml, ../fonoteka.go/parity/fixtures/seed/bootstrap.yaml, tide/flow.go, tide/replay.go, tide/variables.go, .planning/phases/02-api-parity-harness-bootstrap/02-CONTEXT.md, .planning/phases/02-api-parity-harness-bootstrap/02-RESEARCH.md</read_first>
|
||||||
|
<behavior>A testcontainers Postgres starts; an httptest handler writes a row through POST /synthetic/items, a later GET reads it, and the replay compares fixture response bodies. A declared seed hook inserts a second row through SQL before its read fixture. A wrong row value produces a JSON-path diff. The test does not pass if Docker or Postgres is unavailable.</behavior>
|
||||||
|
<action>Review RESEARCH.md Package Legitimacy Audit, then add test-only `github.com/testcontainers/testcontainers-go/modules/postgres` and `github.com/jackc/pgx/v5/stdlib` for `database/sql`, both named in project research; scan `../fonoteka.go/go.mod` with `/home/jin/.local/bin/slopcheck scan` before dependency download and again after resolution, and inspect `git -C ../fonoteka.go diff -- go.mod go.sum` before commit. Keep versions from Go module resolution and no dependency in framework production code (QA-03). Start one Postgres container/database for the integration suite, clean it up with `t.Cleanup`, and create a small synthetic table. Implement a temporary `http.Handler` that persists POST then reads GET through that `*sql.DB`; serve it with `httptest.NewServer`. Add two app-owned synthetic fixtures separate from PHP corpus: seed write and read, with captured id variable and a named `synthetic-item` seed hook inserting through SQL (D-09, D-10, D-11, D-12). Register only declared hook names; unknown hooks fail. First write a failing integration test and observe RED without committing, then implement the synthetic handler and replay call; commit only when both modules pass full vet/test. Make a single `newTarget`/hook registry seam in `parity_test.go` that Phase 3 can replace with the real app handler and temporary genres hook. Do not add a fake PHP route response or mark a PHP route ported.</action>
|
||||||
|
<verify><automated>/home/jin/.local/bin/slopcheck scan ../fonoteka.go/go.mod && go vet ./... && go test ./... && (cd ../fonoteka.go && go vet ./... && go test ./parity -run TestParitySynthetic -count=1 && go test ./...)</automated></verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- `go test ./parity -run TestParitySynthetic` starts real Postgres via testcontainers and passes HTTP write/read replay.
|
||||||
|
- The declared SQL hook is exercised and an unknown hook is rejected.
|
||||||
|
- Altered SQL-backed response fails at a named JSON path; unavailable Docker does not create a false green skip.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>The app module has a real DB-backed in-process parity integration path.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Expose the full corpus as honest selectable subtests</name>
|
||||||
|
<files>../fonoteka.go/parity/parity_test.go, ../fonoteka.go/parity/README.md</files>
|
||||||
|
<read_first>../fonoteka.go/parity/parity_test.go, ../fonoteka.go/parity/manifest.yaml, ../fonoteka.go/parity/fixtures/seed/bootstrap.yaml, tide/manifest.go, tide/replay.go, tide/report.go, .planning/phases/02-api-parity-harness-bootstrap/02-CONTEXT.md</read_first>
|
||||||
|
<action>Load and validate all 154 manifest route ids and their fixtures in `TestParityCorpus`; create stable `t.Run` names from flow ids so `go test ./parity -run 'TestParityCorpus/...'` selects one (D-08, D-12, D-16). Before ported route fixtures, replay their seed flow against the Go handler; for writes not yet ported, invoke only a manifest-declared temporary named hook, with its replacing route id and removal condition documented (D-09, D-10). In Phase 2, the real PHP seed endpoints and all 154 PHP routes are unported, so classify them `pending`, show them as recorded/pending in the coverage report, and do not send them to the synthetic handler or count them as passing. The separate synthetic seed/read subtests prove replay semantics. As Phase 3 provides the real genres handler, `newTarget` switches to it and its manifest row becomes `ported`; temporary genres SQL hook gives it the row until POST genres is ported. A `ported` missing fixture, missing hook, response mismatch or capture failure must fail its subtest and preserve path diagnostics. Print recorded/passing/failing/unrecorded/pending totals without equating pending with pass. Keep PHP self-replay strict over all fixtures independent of Go status.</action>
|
||||||
|
<verify><automated>go vet ./... && go test ./... && (cd ../fonoteka.go && go vet ./... && go test ./parity -run 'TestParitySynthetic|TestParityCorpus' -count=1 && go test ./...)</automated></verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- All 154 route ids appear as recorded and pending; zero PHP routes appear as Go passing in Phase 2.
|
||||||
|
- Synthetic write/read subtests pass, and `-run` can select one named flow.
|
||||||
|
- A deliberately `ported` route without matching Go behavior fails with the actual path diff.
|
||||||
|
- Both root and sibling Go modules pass vet/test after the task commit.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>The corpus is wired into CI with accurate pending/ported semantics and a Phase 3 handler seam.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<threat_model>
|
||||||
|
## Trust Boundaries
|
||||||
|
|
||||||
|
| Boundary | Description |
|
||||||
|
|---|---|
|
||||||
|
| Fixture/manifest → app test and SQL seed hooks | Data files select HTTP requests and named privileged DB setup functions. |
|
||||||
|
| Test suite → container runtime | Testcontainers creates and destroys a disposable database. |
|
||||||
|
|
||||||
|
## STRIDE Threat Register
|
||||||
|
|
||||||
|
| Threat ID | Category | Severity | Component | Disposition | Mitigation Plan |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| T-02-04 | Tampering | high | `parity_test.go` status gate | mitigate | Require exact manifest route set, distinguish pending from passing, fail ported mismatches and unknown hooks. |
|
||||||
|
| T-02-06 | Elevation of privilege | high | SQL seed-hook dispatch | mitigate | Allow only named functions registered in test code; never execute SQL text from YAML, and use parameterized inserts. |
|
||||||
|
| T-02-SC | Tampering | high | App Go module installs | mitigate | Apply RESEARCH.md Package Legitimacy Audit and slopcheck before testcontainers module addition; block ASSUMED/SUS packages for human verification. |
|
||||||
|
</threat_model>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
Run app `go vet ./...`, `go test ./...`, the selected synthetic subtest and full corpus subtests; run root vet/test too. Confirm the report says 154 recorded/pending and zero passing PHP routes.
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
An actual Postgres-backed synthetic flow passes through the same replay engine; the full PHP corpus is visible as selectable pending subtests without false passes.
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
Create `.planning/phases/02-api-parity-harness-bootstrap/02-04-SUMMARY.md` after completion.
|
||||||
|
</output>
|
||||||
157
.planning/phases/02-api-parity-harness-bootstrap/02-05-PLAN.md
Normal file
157
.planning/phases/02-api-parity-harness-bootstrap/02-05-PLAN.md
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
---
|
||||||
|
phase: 02-api-parity-harness-bootstrap
|
||||||
|
plan: 05
|
||||||
|
type: execute
|
||||||
|
wave: 5
|
||||||
|
depends_on: [02-01, 02-02, 02-03, 02-04]
|
||||||
|
files_modified:
|
||||||
|
- tide/flow_contract_test.go
|
||||||
|
- tide/diff_contract_test.go
|
||||||
|
- tide/proxy_security_test.go
|
||||||
|
- tide/manifest_contract_test.go
|
||||||
|
- cmd/summer/parity_contract_test.go
|
||||||
|
- ../fonoteka.go/parity/parity_contract_test.go
|
||||||
|
- scripts/check-phase2.sh
|
||||||
|
- .planning/phases/02-api-parity-harness-bootstrap/02-VALIDATION.md
|
||||||
|
autonomous: true
|
||||||
|
requirements: [QA-01, QA-02, QA-03]
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "QA-01: A source-to-corpus audit proves 154/154 PHP routes have fixtures and named real Nuxt/MCP sessions exist."
|
||||||
|
- "QA-02: Negative contract tests detect nil versus [], Carbon +00:00 versus Z, null versus absent dates, tri-state booleans, envelope/conditional keys and string versus number money."
|
||||||
|
- "QA-03: Both Go modules pass vet/test/race, and the app integration suite actually starts testcontainers Postgres."
|
||||||
|
- "D-07 D-16: Security and continuation tests prove secrets never persist and failures produce full path-level and coverage reports."
|
||||||
|
artifacts:
|
||||||
|
- path: tide/diff_contract_test.go
|
||||||
|
provides: Complete parity-class negative tests
|
||||||
|
- path: tide/proxy_security_test.go
|
||||||
|
provides: Loopback, bounds, scrub and traversal regression tests
|
||||||
|
- path: ../fonoteka.go/parity/parity_contract_test.go
|
||||||
|
provides: Pending/ported and SQL-hook integration assertions
|
||||||
|
- path: scripts/check-phase2.sh
|
||||||
|
provides: Repeatable root/app vet/test/race and corpus audit gate
|
||||||
|
- path: .planning/phases/02-api-parity-harness-bootstrap/02-VALIDATION.md
|
||||||
|
provides: Measured phase verification evidence
|
||||||
|
key_links:
|
||||||
|
- from: scripts/check-phase2.sh
|
||||||
|
to: ../fonoteka.go/parity/parity_test.go
|
||||||
|
via: App module go test with Postgres
|
||||||
|
- from: scripts/check-phase2.sh
|
||||||
|
to: ../fonoteka.go/parity/check_corpus.go
|
||||||
|
via: Exact PHP route and credential audit
|
||||||
|
- from: tide/diff_contract_test.go
|
||||||
|
to: tide/diff.go
|
||||||
|
via: Behavioral negative cases and path diagnostics
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
**As a** port developer, **I want to** trust the parity corpus and its failure signals, **so that** future endpoint work cannot pass by masking a response difference or skipping an unported route.
|
||||||
|
|
||||||
|
Purpose: Finish dedicated unit and integration coverage after all feature slices exist, as required by project guidance.
|
||||||
|
Output: Contract tests, security regression tests, cross-module phase gate and validation evidence.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@/home/jin/.codex/get-shit-done/workflows/execute-plan.md
|
||||||
|
@/home/jin/.codex/get-shit-done/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<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-VALIDATION.md
|
||||||
|
@.planning/phases/02-api-parity-harness-bootstrap/02-01-SUMMARY.md
|
||||||
|
@.planning/phases/02-api-parity-harness-bootstrap/02-02-SUMMARY.md
|
||||||
|
@.planning/phases/02-api-parity-harness-bootstrap/02-03-SUMMARY.md
|
||||||
|
@.planning/phases/02-api-parity-harness-bootstrap/02-04-SUMMARY.md
|
||||||
|
@tide/flow.go
|
||||||
|
@tide/diff.go
|
||||||
|
@tide/normalize.go
|
||||||
|
@tide/proxy.go
|
||||||
|
@tide/manifest.go
|
||||||
|
@../fonoteka.go/parity/parity_test.go
|
||||||
|
@../fonoteka.go/parity/manifest.yaml
|
||||||
|
<interfaces>
|
||||||
|
Use the public `tide` Record/Replay/Manifest APIs and CLI values established in Plans 01–04. The root and sibling app are separate Go modules; `go test ./...` in one does not traverse the other. The app's `TestParitySynthetic` must run with a real testcontainers Postgres and `TestParityCorpus` must report all 154 PHP routes as recorded/pending until actual Go handlers are ported.
|
||||||
|
</interfaces>
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Lock down every response parity class with negative tests</name>
|
||||||
|
<files>tide/flow_contract_test.go, tide/diff_contract_test.go, tide/manifest_contract_test.go</files>
|
||||||
|
<read_first>tide/flow.go, tide/fixture.go, tide/diff.go, tide/normalize.go, tide/replay.go, tide/manifest.go, tide/report.go, .planning/research/PITFALLS.md, .planning/phases/02-api-parity-harness-bootstrap/02-CONTEXT.md</read_first>
|
||||||
|
<action>Write table-driven public-behavior tests over `RecordFlow`, `ReplayFlow` and manifest reporting. Include one good baseline and individually mutated fixtures for `null` versus `[]` (including empty object), Carbon ISO-8601 `+00:00` versus `Z` and non-date text, date key present-null versus absent, `true`/`false`/`null`, top-level `{data,meta}` envelope and nested conditional-key presence, fixed-decimal money JSON string versus number, integer id versus string/fraction, exact slug, capture-by-reference mismatch, unknown/missing variable and per-step normalization disable (D-11, D-13, D-15). Assert each failure names a stable JSON path and expected/actual type/value; object key reorder must pass. Add status and allow-listed header cases for OAuth `Cache-Control`/`Pragma`, 401 `WWW-Authenticate`/protected-resource-metadata, CSV `Content-Disposition`, plus ignored Date/Server/request id. Add CSV/image exact-byte mismatch and safe binary sidecar digest/path cases. Add two-flow continuation cases: comparison failure allows later steps/flows; capture failure skips only its flow remainder (D-14, D-16). Do not write tests that merely assert private helper structure.</action>
|
||||||
|
<verify><automated>go vet ./... && go test ./tide -run 'TestFlowContract|TestDiffContract|TestManifestContract' -count=1 && go test ./...</automated></verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- Every named QA-02 parity class has a passing baseline and at least one failing mutation with a path-level diagnostic.
|
||||||
|
- Header, binary and flow-continuation cases exercise observable Record/Replay results.
|
||||||
|
- Root vet/test remain green after the task commit.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>Formatting, types and missing-key distinctions cannot be silently normalized away.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Cover proxy, scrub, CLI and Go pending-state boundaries</name>
|
||||||
|
<files>tide/proxy_security_test.go, cmd/summer/parity_contract_test.go, ../fonoteka.go/parity/parity_contract_test.go</files>
|
||||||
|
<read_first>tide/proxy.go, tide/variables.go, tide/fixture.go, cmd/summer/parity.go, cmd/summer/main.go, ../fonoteka.go/parity/parity_test.go, ../fonoteka.go/parity/synthetic_test.go, ../fonoteka.go/parity/manifest.yaml, .planning/phases/02-api-parity-harness-bootstrap/02-VALIDATION.md</read_first>
|
||||||
|
<action>Test fixed loopback proxy target, rejection of client-selected upstream/non-loopback bind, request and response size cap, cookie/redirect passthrough, separate concurrent sessions, atomic write on error, path traversal and binary sidecar digest (T-02-01). Test credential replacement in Authorization, cookie, query and JSON/form body; scan written YAML and CLI error text for JWT, `inv_`, client secret, OAuth code and `auth_token`, then test unknown credential rejection and missing placeholder failure (T-02-02). Run bonfire commands with injected writers and assert `parity:record`, `parity:proxy`, `parity:replay` discovery and nonzero error propagation. In app tests, verify 154 manifest ids match source, all remain pending in Phase 2, a deliberately marked `ported` failing route fails its subtest, unknown seed hooks fail, synthetic SQL read/write and hook are backed by testcontainers Postgres, and pending is never counted as passing (D-08, D-09, D-10, D-12, D-16). Keep Docker absence as an explicit failure in the integration gate; use `-short` only for a separately named fast loop if needed, never for the phase sign-off.</action>
|
||||||
|
<verify><automated>go vet ./... && go test ./tide ./cmd/summer -run 'TestProxySecurity|TestParityCommandContract' -count=1 && (cd ../fonoteka.go && go vet ./... && go test ./parity -run 'TestParitySynthetic|TestParityCorpus|TestParityContract' -count=1)</automated></verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- Proxy and fixture security regressions fail under the named attack cases.
|
||||||
|
- CLI errors produce nonzero exit status and do not print secrets.
|
||||||
|
- App test runs real Postgres and reports 154 recorded/pending, zero Go-passing PHP routes.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>The harness cannot pass by leaking a credential, misrouting proxy traffic, or calling pending routes green.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 3: Run and document the repeatable phase gate</name>
|
||||||
|
<files>scripts/check-phase2.sh, .planning/phases/02-api-parity-harness-bootstrap/02-VALIDATION.md</files>
|
||||||
|
<read_first>scripts/check-phase1.sh, .planning/phases/02-api-parity-harness-bootstrap/02-VALIDATION.md, ../fonoteka.go/parity/README.md, ../fonoteka.go/parity/manifest.yaml, .planning/phases/02-api-parity-harness-bootstrap/02-RESEARCH.md</read_first>
|
||||||
|
<action>Create `scripts/check-phase2.sh` that checks root and `../fonoteka.go` separately with `go vet ./...`, `go test ./...`, `go test -race ./...`, an explicit `TestParitySynthetic` Postgres run, corpus validator `--require-recorded --require-clients --check-secrets`, and CLI synthetic record/replay smoke. In required `--fresh-php` mode, provision a temporary MariaDB container on an ephemeral loopback port with a unique `fonoteka_parity_<run-id>` database and process-local credentials, never the PHP checkout's `.env` or developer DB. Before migrations, query the active PHP DB connection through artisan, assert it equals that unique name and has zero application tables; then run existing PHP migrations. Read `../fonoteka.go/parity/README.md` and execute its exact documented artisan admin-user bootstrap and `oauth-client` commands against this same verified disposable DB using the same process-local DB environment (D-03); fail if either command fails, and redact generated credentials from logs while passing them only through the private variable store. Start `php artisan serve --host=127.0.0.1 --port=8423` as a child with that DB env. The script itself sets the only permitted replay target `http://127.0.0.1:8423`; reject a caller-supplied `PHP_PARITY_TARGET` or non-loopback target, and use a trap to stop PHP and remove the disposable container. Replay the API seed, all 154 routes and both client flows against that fresh instance, requiring zero failures/unrecorded. Fail on missing Docker, failed DB identity/freshness check, either artisan bootstrap failure, missing route fixture/client flow or any nonzero command; do not skip. Record actual commands, test output summary, route totals, fresh DB preflight and PHP self-replay result in `02-VALIDATION.md` without credentials; mark `nyquist_compliant: true` only when all gates pass. Keep planning doc and code changes in separate commits per CLAUDE.md; preserve the Phase 1 gate. Confirm root and app vet/test were green at each prior implementation commit as QA-03 requires.</action>
|
||||||
|
<verify><automated>bash scripts/check-phase2.sh --fresh-php</automated></verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- Both modules pass vet, test and race; the app test visibly starts testcontainers Postgres.
|
||||||
|
- Corpus audit reports exactly 154 recorded routes, zero unrecorded, and Nuxt/MCP flow presence with no secrets.
|
||||||
|
- Fresh PHP self-replay passes every fixture; validation file records command/evidence and only then sets `nyquist_compliant: true`.
|
||||||
|
- The gate proves a unique empty `fonoteka_parity_*` DB before migrations and starts its own PHP child on `127.0.0.1:8423`; caller URL overrides are rejected.
|
||||||
|
- The documented artisan admin-user and OAuth-client bootstrap both run successfully against that same DB before seed replay, with generated secrets absent from output.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>A single documented gate proves the full Phase 2 harness and corpus are ready for Phase 3.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<threat_model>
|
||||||
|
## Trust Boundaries
|
||||||
|
|
||||||
|
| Boundary | Description |
|
||||||
|
|---|---|
|
||||||
|
| Malformed fixtures/proxy requests → public harness APIs | Contract tests must prove fail-closed behavior. |
|
||||||
|
| CI → Docker and isolated PHP | Integration gate must distinguish missing infrastructure from a passing test. |
|
||||||
|
|
||||||
|
## STRIDE Threat Register
|
||||||
|
|
||||||
|
| Threat ID | Category | Severity | Component | Disposition | Mitigation Plan |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| T-02-01 | Spoofing/Denial of service | high | Proxy and fixture IO | mitigate | Regression tests for fixed loopback upstream, bounded bodies, path traversal and atomicity. |
|
||||||
|
| T-02-02 | Information disclosure | high | Recorder and CLI output | mitigate | Scrub tests across headers/query/body/cookies and committed corpus scan. |
|
||||||
|
| T-02-03 | Tampering | high | Normalizer and diff | mitigate | Mutation tests for token type, date shape, conditional keys, selected headers and sidecar digest. |
|
||||||
|
| T-02-04 | Tampering | high | Route coverage gate | mitigate | Source-derived 154-id audit and pending/ported negative tests. |
|
||||||
|
| T-02-05 | Denial of service/Data loss | high | Fresh PHP self-replay gate | mitigate | Provision unique disposable DB, assert active connection and empty schema, pin loopback PHP origin, clean up child and container. |
|
||||||
|
| T-02-06 | Elevation of privilege | high | SQL seed hooks | mitigate | Unknown-hook rejection and parameterized SQL behavior tests. |
|
||||||
|
</threat_model>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
Run `bash scripts/check-phase2.sh --fresh-php`; the script provisions its own isolated PHP DB/server and executes self-replay. Treat an environmental block as an incomplete gate and record it; do not mark the phase compliant without the run.
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
Both modules are vet/test/race green, all 154 PHP routes and real client flows are recorded and self-replay cleanly, Postgres integration executes, and every named parity class has a failing mutation test.
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
Create `.planning/phases/02-api-parity-harness-bootstrap/02-05-SUMMARY.md` after completion.
|
||||||
|
</output>
|
||||||
Reference in New Issue
Block a user