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

131 lines
11 KiB
Markdown

---
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 &amp;&amp; go vet ./... &amp;&amp; go test ./... &amp;&amp; (cd ../fonoteka.go &amp;&amp; go vet ./... &amp;&amp; go test ./parity -run TestParitySynthetic -count=1 &amp;&amp; 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 ./... &amp;&amp; go test ./... &amp;&amp; (cd ../fonoteka.go &amp;&amp; go vet ./... &amp;&amp; go test ./parity -run 'TestParitySynthetic|TestParityCorpus' -count=1 &amp;&amp; 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>