diff --git a/.planning/phases/02-api-parity-harness-bootstrap/02-05-SUMMARY.md b/.planning/phases/02-api-parity-harness-bootstrap/02-05-SUMMARY.md new file mode 100644 index 0000000..5d675e1 --- /dev/null +++ b/.planning/phases/02-api-parity-harness-bootstrap/02-05-SUMMARY.md @@ -0,0 +1,157 @@ +--- +phase: 02-api-parity-harness-bootstrap +plan: 05 +subsystem: testing +tags: [parity, contract-tests, race, testcontainers, check-phase2, mariadb] + +requires: + - phase: 02-api-parity-harness-bootstrap + provides: tide record/replay/proxy, capture rules, named vars, manifest --next-batch, summer parity:* + - phase: 02-api-parity-harness-bootstrap + provides: Isolated PHP on 127.0.0.1:8423 with a parity-named SQLite DB + - phase: 02-api-parity-harness-bootstrap + provides: Recorded API seed plus 154 live PHP route fixtures + - phase: 02-api-parity-harness-bootstrap + provides: App-owned testcontainers Postgres lifecycle for go test ./parity + - phase: 02-api-parity-harness-bootstrap + provides: TestParityCorpus: 154 recorded/pending selectable subtests, zero Go passes +provides: + - Public-API contract tests for every named QA-02 parity class + - Proxy/CLI security regressions and app pending-vs-passing contracts + - Repeatable scripts/check-phase2.sh --fresh-php gate with disposable MariaDB + - Measured 02-VALIDATION.md evidence with nyquist_compliant true +affects: [03] + +tech-stack: + added: [] + patterns: + - Contract tests go through RecordFlow/ReplayFlow/manifest public APIs, not private helpers + - check-phase2.sh owns PHP_PARITY_TARGET=http://127.0.0.1:8423 and refuses caller overrides + - Fresh PHP gate uses unique fonoteka_parity_* MariaDB, artisan preflight tables=0, then winter:up plus oauth-client + +key-files: + created: + - tide/diff_contract_test.go + - tide/flow_contract_test.go + - tide/manifest_contract_test.go + - tide/proxy_security_test.go + - cmd/summer/parity_contract_test.go + - ../fonoteka.go/parity/parity_contract_test.go + - scripts/check-phase2.sh + modified: + - .planning/phases/02-api-parity-harness-bootstrap/02-VALIDATION.md + +key-decisions: + - "Fresh PHP self-replay uses disposable MariaDB fonoteka_parity_* plus process-local hex credentials, never the developer DB or caller-supplied PHP_PARITY_TARGET" + - "Client flows run on a second winter:up after dropping tables so they are not replayed after the mutating 154-route suite" + - "Capture-by-reference mismatch is a two-step {{share:item}} flow with a live token change on the second /show" + +patterns-established: + - "Pattern: Phase 2 sign-off is one script — scripts/check-phase2.sh --fresh-php — and Phase 1 remains scripts/check-phase1.sh" + - "Pattern: MariaDB readiness is mariadb SELECT 1; passwords are token_hex so -p never sees a leading dash" + - "Pattern: pkce.vars merge from /tmp/summercms-parity/pkce.vars before mcp-oauth replay" + +requirements-completed: [QA-01, QA-02, QA-03] + +duration: 15min +completed: 2026-09-17 +--- + +# Phase 2 Plan 5: Complete contract, security and integration tests Summary + +**Public-API negative tests lock every named parity class, proxy/CLI/pending-state contracts fail closed, and `scripts/check-phase2.sh --fresh-php` proves 154/154 PHP self-replay plus testcontainers Postgres** + +## Performance + +- **Duration:** 15 min +- **Started:** 2026-09-17T12:00:43Z +- **Completed:** 2026-09-17T12:15:30Z +- **Tasks:** 3 +- **Files modified:** 8 (7 created, 1 validation doc) plus this SUMMARY + +## Accomplishments + +- Table-driven `TestDiffContract` / `TestFlowContract` / `TestManifestContract` cover null vs `[]`, Carbon `+00:00` vs `Z`, null vs absent dates, tri-state bools, envelope/conditional keys, money string vs number, integer ids, exact slug, capture-by-reference, missing variables, and per-step normalize disable — each failure names a JSON path. +- `TestProxySecurity` and `TestParityCommandContract` pin loopback-only proxy, body caps, traversal, scrub of JWT/`inv_`/OAuth secrets, and nonzero CLI errors without printing secrets. +- App `TestParityContract` keeps 154 routes recorded/pending with zero Go passes, unknown seed hooks fail, and synthetic SQL still uses testcontainers Postgres. +- `scripts/check-phase2.sh --fresh-php` is the repeatable gate: root and app vet/test/race, `TestParitySynthetic` Postgres, corpus `--require-recorded --require-clients --check-secrets`, CLI smoke, disposable MariaDB + artisan bootstrap + 154-route and client-flow PHP self-replay. + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: Lock down every response parity class with negative tests** - `59b5276` (test, summercms.go) +2. **Task 2: Cover proxy, scrub, CLI and Go pending-state boundaries** - `5ed920b` (test, summercms.go) and `d93392d` (test, fonoteka.go) +3. **Task 3: Run and document the repeatable phase gate** - `a296e98` (feat), `066c3d3` (fix), `84a5f00` (docs VALIDATION) + +**Plan metadata:** this SUMMARY commit + +## Files Created/Modified + +- `tide/diff_contract_test.go` — QA-02 parity-class mutations with path-level diffs +- `tide/flow_contract_test.go` — Record/Replay continuation, capture-by-reference, headers/binary +- `tide/manifest_contract_test.go` — coverage reports, duplicate/missing ids, 154-route snapshot +- `tide/proxy_security_test.go` — loopback bind/upstream, caps, scrub, traversal, atomic write +- `cmd/summer/parity_contract_test.go` — CLI discovery, error exit, unclassified JWT with `--vars` +- `../fonoteka.go/parity/parity_contract_test.go` — pending ≠ passing, Postgres, unknown hooks +- `scripts/check-phase2.sh` — root/app vet/test/race, corpus, CLI smoke, `--fresh-php` MariaDB/PHP +- `.planning/phases/02-api-parity-harness-bootstrap/02-VALIDATION.md` — measured evidence, `nyquist_compliant: true` + +## Decisions Made + +- Keep MariaDB for `--fresh-php` (plan), not the 02-03 SQLite file; uniqueness is `fonoteka_parity_` on an ephemeral loopback port. +- Hex-only process credentials so `mysql`/`mariadb` `-p` cannot treat a leading `-` as a flag. +- Client OAuth replay still merges `/tmp/summercms-parity/pkce.vars`; `client_secret` is redacted from gate logs. +- Do not re-record PHP and do not implement Fonoteka Go API endpoints in this plan. + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 1 - Bug] MariaDB urlsafe passwords broke `-p` and readiness** +- **Found during:** Task 3 (`bash scripts/check-phase2.sh --fresh-php`) +- **Issue:** `token_urlsafe` passwords could start with `-`, so `mariadb -p$PASS` parsed them as flags. Container also needed a real SQL ping, not only `docker inspect`. +- **Fix:** Switch to `token_hex` credentials and wait on `mariadb ... -e 'SELECT 1'`. +- **Files modified:** `scripts/check-phase2.sh` +- **Verification:** Second `--fresh-php` run exited 0 in 116s with `phase2 check passed` +- **Committed in:** `066c3d3` + +--- + +**Total deviations:** 1 auto-fixed (1 blocking MariaDB readiness) +**Impact on plan:** Required for a true empty-DB PHP self-replay. No PHP/Nuxt/MCP source changes. No Go API endpoints. + +## Issues Encountered + +- First `--fresh-php` attempt also found leftover `php artisan serve` on 8423 (pid 3289319); the process was killed so the gate could bind. Not a plan change. +- `TestFlowContract/unknown_capture_source` needed `OpenStore` so `CaptureStep` runs; a nil store skipped capture and falsely passed. +- Unclassified JWT CLI record succeeded until `--vars` was passed so `ScrubStep` ran. Both were fixed before the Task 1/2 commits. +- `PHP_PARITY_TARGET=http://example.com bash scripts/check-phase2.sh --fresh-php` exits 1 with `refuse: caller-supplied PHP_PARITY_TARGET is not permitted`. + +## User Setup Required + +None - no external service configuration required. Local Docker is required for `--fresh-php` and for `go test ./parity` without `-short`. + +## Next Phase Readiness + +Phase 2 complete. Ready for Phase 3 (first vertical slice: `GET /_fonoteka/api/v1/genres`) to swap `newTarget` for the real app handler, mark that route `ported`, and keep PHP fixtures as the acceptance test. + +## Verification + +- Task 1: `go test ./tide -run 'TestFlowContract|TestDiffContract|TestManifestContract' -count=1` pass +- Task 2: `go test ./tide ./cmd/summer -run 'TestProxySecurity|TestParityCommandContract'` pass; app `go test ./parity -run 'TestParitySynthetic|TestParityCorpus|TestParityContract' -count=1` **4.888s** (Postgres started) +- Task 3: `bash scripts/check-phase2.sh --fresh-php` **116s**, `phase2 check passed`; corpus `recorded 154/154`; PHP self-replay **passing 154 failing 0 unrecorded 0**; seed, nuxt-browse, mcp-tools, mcp-oauth matched +- Root and app `go vet ./...`, `go test ./...`, `go test -race ./...` green +- `scripts/check-phase1.sh` still present; `PHP_PARITY_TARGET` override refused + +## Self-Check: PASSED + +- [x] Key files exist on disk +- [x] `git log --grep=02-05` returns Task 1–3 commits in summercms.go; fonoteka.go has `d93392d` +- [x] Task acceptance criteria re-run and pass +- [x] Plan verification command `bash scripts/check-phase2.sh --fresh-php` passed +- [x] `02-VALIDATION.md` records measured evidence and `nyquist_compliant: true` + +--- +*Phase: 02-api-parity-harness-bootstrap* +*Completed: 2026-09-17*