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

158 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 ./... &amp;&amp; go test ./tide -run 'TestFlowContract|TestDiffContract|TestManifestContract' -count=1 &amp;&amp; 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 ./... &amp;&amp; go test ./tide ./cmd/summer -run 'TestProxySecurity|TestParityCommandContract' -count=1 &amp;&amp; (cd ../fonoteka.go &amp;&amp; go vet ./... &amp;&amp; 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>