13 KiB
Phase 2: API parity harness bootstrap — Research
Researched: 2026-09-16
Status: Ready for planning; port split and five-plan count confirmed by user
What exists
- Phase 1 is implemented.
bonfire.Commandis a value withName,Flags,Args, andRun(ctx, Input, Output) error;cmd/summer/main.goregisters tool commands throughbonfire.NewRoot.parity:proxy,parity:record, andparity:replaycan jointoolCommands()without a second CLI framework.bonfire.Flagcurrently represents string flags only, so the commands should parse string values rather than assume native bool/int flags. - The sibling app repository is
../fonoteka.go(not/media/nvme/dev/golem15/fonoteka.go). It contains onlygo.mod, README, and CLAUDE.md. Phase 2 must create itsparity/tree. It cannot claim a real Go Fonoteka handler exists yet. - The PHP source
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/routes.phpcontains 154Route::get/post/put/patch/delete/...declarations by a line anchored count. The groups include JWT with and without password gate, public share, throttled onboarding, scoped personal tokens, and unprefixed OAuth. Core user register/login routes used by seed flow live outside this file and should be listed separately from the 154-route coverage denominator. - The PHP README runs
php artisan serve --port=8422; Nuxt'sNUXT_DEV_BACKEND_ORIGINand MCP'sFONOTEKA_API_URLcan point at a local recording proxy. The PHP originals and clients do not need code changes.
Recommended implementation slices
- One real round trip first. A generic
tidelibrary owns a versioned YAML flow schema and aRecord/ReplayAPI. Asummer parity:recordcall against a localhttptest.Serverwrites one route fixture;summer parity:replay --targetreads that same fixture and reports a mismatch with expected and actual bytes. This is the first complete user path, not a schema-only plan. - Capture both sources. Add
parity:proxywithhttputil.ReverseProxyfor live Nuxt/MCP sessions and manifest-drivenparity:recordfor exhaustive route cases. UseRewrite/SetURLfor upstream routing; wrap the transport or response body to observe bytes while forwarding unchanged.ModifyResponsesees a response before it is copied to the client, but a recorder must replace the consumed body for forwarding. Cap buffered request and response sizes and record truncation as an error rather than silently committing a partial fixture. Group proxy requests into named sessions through an explicit local recording session header or CLI session id; ordered arrival alone is insufficient across concurrent browser requests. Preserve cookies and redirects needed by PKCE, while retaining only declared headers in fixtures. - One fixture model. Flow name/description, ordered steps, request method/path/raw query/kept headers/body, expected response status/kept headers/body,
capture,normalize, optionalseed_hook, and route id/status belong in a strict versioned schema. JSON body text stays a YAML literal block scalar for review and raw diagnostics; for binary responses, use a relative sidecar file plus digest and content type. Reject absolute or parent-traversing sidecar paths. Route manifest and fixture schema are app-specific data over the framework-owned types. - Replay state explicitly. A variable store resolves
{{jwt:alice}},{{token:mcp-read}}, ids, share tokens, OAuth codes, and PKCE verifier references before each request; capture rules populate variables from recorded and replayed responses. Scrub credentials at record time, before writing YAML or error logs. An unrecognized credential-shaped value in a kept auth/cookie field should fail the recording; a missing variable should fail the flow, not send an unresolved placeholder. Seed runs first, then ordered route and client flows. Pending routes are recorded but do not fail Go CI until markedported; PHP self-replay checks every recorded flow. - Diff without weakening contracts. Decode JSON into maps/slices with
json.Decoder.UseNumber, compare key presence, array/object/null/bool/string/number types and values recursively, and report JSON-path-like locations.UseNumberretains numeric lexemes and avoids float64 conversion; decide whether numerically equivalent spellings are acceptable, but never equate a number with a string. Ignore JSON object key order. Keep raw byte comparison for non-JSON bodies and printable raw-body diagnostics. The term “byte-level diff” in the roadmap should mean exact body bytes for non-JSON and an exact expected/actual rendering at mismatched JSON paths; JSON key order is intentionally ignored by D-13. - Normalize narrowly. Match explicit JSON paths (
*_at, known ids, captured values) with small in-house path rules; run a shape assertion before masking. For dates require an offset+00:00style where the PHP contract does, including key present withnullwhere recorded. Assert ids are JSON integers before masking. Preserve nil versus[], tri-state bool, fixed-decimal string versus number, envelope keys, and conditional key presence. A rule can be disabled per step; a broad “ignore timestamps/ids” filter would hide these failures. - Report the whole run. Continue after a failed comparison, but stop dependent steps within a flow after a failed capture. Print status/header/path/body differences and a route coverage table (recorded/passing/failing/unrecorded) against 154 manifest route ids. Exclude seed and client-only flows from the denominator. CLI exits nonzero on a ported failure or an unrecorded required route; app tests create one subtest per flow so
-runcan select it.
Package Legitimacy Audit
All three proposed direct modules have an explicit project decision or requirement, an upstream-maintained module path, and a narrow use. No package in this phase is inferred from a similarly named fork or an unverified import path. Resolve versions with go get/go mod tidy at execution time, review the resulting go.mod/go.sum, and retain the existing Go 1.27 toolchain directive. Do not treat a package appearing transitively as permission to import an unrelated API.
| Package | Why it is allowed | Primary-source evidence and scope | Verdict |
|---|---|---|---|
github.com/goccy/go-yaml |
D-06 explicitly selects it for the framework fixture schema; project STACK.md independently selects it for YAML parsing. | The upstream repository documents tagged struct encode/decode and custom marshaling. Use directly in tide fixture and manifest IO; test unknown-field rejection and literal block body output rather than assuming encoder defaults. |
VERIFIED |
github.com/testcontainers/testcontainers-go/modules/postgres |
QA-03 explicitly requires integration tests on testcontainers Postgres; Phase 2 D-12 requires a hermetic app test. | The official Postgres module guide documents postgres.Run, ConnectionString, container cleanup and snapshots. Add only to ../fonoteka.go test code; do not add it to framework production packages. |
VERIFIED |
github.com/jackc/pgx/v5/stdlib |
The project already chooses Postgres/pgx via the GORM stack; the app's synthetic SQL-backed integration needs a database/sql driver before the real app handler exists. |
The upstream pgx stdlib documentation identifies it as the database/sql compatibility layer and shows sql.Open("pgx", ...). Use only in the app integration test with parameterized SQL; reconcile its resolved v5 version with the app module when GORM arrives. |
VERIFIED |
net/http/httputil, net/http/httptest, encoding/json, and database/sql are Go standard-library packages and need no third-party install. No JSONPath or diff dependency is needed; small path and comparison code keeps the assertion semantics explicit.
Validation Architecture
- Fast checks at every implementation commit:
go vet ./... && go test ./...in the framework root, plus the same in../fonoteka.goonce that module has code.go.workdoes not make root./...traverse sibling modules; check each explicitly, as Phase 1'sscripts/check-phase1.shdoes for the hello modules. - Framework synthetic tests should cover a recorded
httptest.Serverround trip, HTTP error/redirect/cookie preservation, YAML round trip, capture and placeholder resolution, secret scrub, path traversal rejection, JSON token-type diff, each named parity class, header allow-list, flow continuation, and CLI nonzero exit. This is meaningful behavioral coverage rather than snapshotting implementation. - The app repository should have a testcontainers Postgres integration test using a small synthetic
http.Handlerbacked by the container, with a real write/read flow and a temporary SQL seed hook. It proves the harness and database state protocol before Phase 3 provides the first ported route. The app test must not report the 154 PHP routes as passing against an absent Go app. Testcontainers' Postgres module exposespostgres.Run,ConnectionString, cleanup and snapshot options; one container/database per suite and ordered flows are consistent with D-09 and the cost constraint. - An operator-only PHP self-check should run on an isolated local database with
summer parity:replay --target <php-origin>and must pass before committed fixtures are accepted. The Phase 2 completion evidence includes one actual PHP capture, the complete 154-route manifest and scripted route-case coverage, and named Nuxt/MCP recorded sessions. External-service-dependent routes may need deterministic error cases with declared expected statuses, but every route must have at least one recorded case under QA-01. - Reserve the final plan for comprehensive unit and integration tests, per
CLAUDE.md. Earlier slices still need smoke tests and both modules' vet/test commands to stay green at each commit.
Risks and decisions to settle
- Port collision resolved. D-01 originally put the proxy on
:8422, while D-02 and the PHP README also used:8422for PHP. The user chose PHP on127.0.0.1:8423and proxy on127.0.0.1:8422on 2026-09-16; D-02 in CONTEXT.md now records this split. Nuxt/MCP point at the proxy. - Scope is large.
QA-01asks for all 154 routes plus real client flows, while the roadmap's first success criterion says at least one captured route. The plan must satisfy QA-01 and separately show the one-route vertical slice early. It must not turn the 154-route corpus into a future-phase placeholder. - No Go app handler yet. D-12's in-process app replay cannot honestly run against the real port before Phase 3. Use a synthetic handler with testcontainers in Phase 2 and preserve an app handler wiring seam for Phase 3.
pending|portedgates Go regressions; PHP self-replay validates the full corpus now. - Secrets and proxy exposure. Bind the proxy to loopback, require a fixed upstream URL rather than an arbitrary request-chosen destination, redact auth/cookie/query/body credential sources before disk/log output, reject unsanitized fixtures, and keep fixture writes atomic. Raw fixture bodies can still include private user data, so the parity dataset must contain only deterministic test identities.
- Replay changes state. Mutating route cases can invalidate later cases. Give the manifest deterministic order and explicit seed or reset strategy. Reserve unique test data per case or use database snapshots for destructive cases; testcontainers Postgres snapshots are available but PHP self-replay may need a fresh database instead.
- Cross-repo execution. Planning docs live here, while app corpus and test live in
../fonoteka.go; execution must have write access to that sibling checkout. Existing frameworkgo.workneed not absorb the app module if it is intentionally separate.
Sources
- Local:
bonfire/command.go,cmd/summer/main.go,../fonoteka.go/go.mod, PHProutes.php, PHP and Nuxt/MCP READMEs,.planning/research/PITFALLS.md, Phase 2 CONTEXT.md. - Go
httputil.ReverseProxy—Rewrite,SetURL,ModifyResponse, and forwarding semantics. - Go
encoding/json—Decoder.UseNumberandjson.Number. - Go
httptest— in-process HTTP servers for synthetic and app tests. - goccy/go-yaml — tagged field encode/decode and custom marshaling.
- Testcontainers Postgres module —
postgres.Run, connection string, cleanup and snapshots.
Phase: 02-api-parity-harness-bootstrap
Ready for planning: yes; five plans approved