diff --git a/.planning/phases/14-domain-jobs-and-external-integrations/14-06-SUMMARY.md b/.planning/phases/14-domain-jobs-and-external-integrations/14-06-SUMMARY.md new file mode 100644 index 0000000..7e6f9f1 --- /dev/null +++ b/.planning/phases/14-domain-jobs-and-external-integrations/14-06-SUMMARY.md @@ -0,0 +1,295 @@ +--- +phase: 14-domain-jobs-and-external-integrations +plan: 06 +subsystem: testing +tags: [gate, coverage, mutation-testing, removal-harness, truth-tables, fuzz, security-review, discogs, sm-golem-plugin, sm-feedback-plugin, validation] + +requires: + - phase: 14-domain-jobs-and-external-integrations + provides: "14-01 framework helpers (fetchguard.Client, tide sidecars, sunscreen, DropIndex), 14-02 Discogs core and workers, 14-03 Discogs routes, 14-04 sm-golem-plugin and recognition, 14-05 sm-feedback-plugin" + - phase: 13-p-ytarium-api-wishlist-notifications-csv-credentials-public + provides: "check-phase13.sh structure, FuzzWriteEndpoints, TestPhase13Threats and the removal-harness precedent" +provides: + - "scripts/check-phase14.sh: --self-test --go --parity --named --removal --coverage --evidence --all, fail closed; 175 recorded, 172 ported and passing, 3 pending" + - "TestPHPTruthDiscogs over PHP-generated php_mapper.json, php_input_parser.json, php_scorer.json, php_price_suggestion.json" + - "TestPhase14Threats (T-14-08 to T-14-29), TestPhase14Edges, TestPhase14Jobs, TestPhase14HandlersFailClosed, TestPhase14Classes, TestPhase14ControllerHelpers; TestRouteTablePhase14 with the in-handler buckets; FuzzWriteEndpoints over the 11 Phase 14 routes" + - "sm-golem-plugin TestGolemAdminSchemas, TestGolemImportCommand; sm-feedback-plugin TestFeedbackEdges (pushed)" + - "43 removal checks, one or more per mitigated threat, all failing as required; 14-SECURITY-REVIEW.md" + - "Validated 14-VALIDATION.md, REQUIREMENTS INTG-02 and API-08 Complete, COVERAGE.md confirmed, two folded todos closed" +affects: [14 verify-work, 14.1 oauth-identities and me routes, 15 cutover] + +actuals: + tokens: 79300 # chars/4 over the added lines of all four repos (fuzz corpus and truth tables included) + tasks: 4 + commits: 6 # MEASURED in summercms.go: plan_head_before..plan_head_after (before this SUMMARY) + app_repo_commits: 9 # MEASURED in fonoteka.go: 9fc38d9..cd393e4 + plugin_repo_commits: 3 # sm-golem-plugin 2 (c93e2d9..6646d10), sm-feedback-plugin 1 (af5d77c..3057719), all pushed +plan_head_before: c12641605c0fb174729ad4e1bef23aac66dc847c +plan_head_after: 17b2ef19617f322b87e46fb77656dfc450d9e6ab +app_repo_head_before: 9fc38d9d7fefd9671312e4a148b2f3042acde6eb +app_repo_head_after: cd393e4 + +tech-stack: + added: [] + patterns: + - "Fault injection without a broken database: a second gorm handle on the same pool with Before callbacks that fail statements on one table after N successes (faultDB in classes and the plugin root)" + - "Phase function floor: the functions a phase's diff touched are listed in the gate by file and must each reach 80% by go tool cover -func; an exemption carries its reason" + - "Removal rows reach into the shared plugins' submodule checkouts; the dirty-file refusal runs against the file's own repository" + - "A fuzz route that calls a vendor runs behind a scripted vendor transport (fetchguard.WithTransport), never the network" + +key-files: + created: + - scripts/check-phase14.sh + - modules/tide/upstream_coverage_test.go + - .planning/phases/14-domain-jobs-and-external-integrations/14-SECURITY-REVIEW.md + - ../fonoteka.go/plugins/golem15/fonoteka/classes/discogs/php_truth_test.go + - ../fonoteka.go/plugins/golem15/fonoteka/classes/discogs/testdata/php_mapper.json + - ../fonoteka.go/plugins/golem15/fonoteka/phase14_security_test.go + - ../fonoteka.go/plugins/golem15/fonoteka/phase14_edges_test.go + - ../fonoteka.go/plugins/golem15/fonoteka/phase14_jobs_test.go + - ../fonoteka.go/plugins/golem15/fonoteka/phase14_handlers_test.go + - ../fonoteka.go/plugins/golem15/fonoteka/classes/phase14_classes_test.go + - ../fonoteka.go/plugins/golem15/fonoteka/controllers/api/phase14_controllers_test.go + - ../fonoteka.go/plugins/golem15/fonoteka/testdata/fuzz/FuzzWriteEndpoints/ (11 seeds) + - ../fonoteka.go/plugins/golem15/golem/golem_admin_test.go + - ../fonoteka.go/plugins/golem15/feedback/feedback_edges_test.go + modified: + - ../fonoteka.go/parity/discogs_truth_tables.php + - ../fonoteka.go/parity/README.md + - ../fonoteka.go/plugins/golem15/fonoteka/classes/discogs/discogs_test.go + - ../fonoteka.go/plugins/golem15/fonoteka/classes/discogs/testdata/php_input_parser.json + - ../fonoteka.go/plugins/golem15/fonoteka/classes/discogs/testdata/php_price_suggestion.json + - ../fonoteka.go/plugins/golem15/fonoteka/routes_table_phase14_test.go + - ../fonoteka.go/plugins/golem15/fonoteka/write_endpoints_fuzz_test.go + - .planning/phases/14-domain-jobs-and-external-integrations/14-VALIDATION.md + - .planning/phases/14-domain-jobs-and-external-integrations/COVERAGE.md + - .planning/phases/14-domain-jobs-and-external-integrations/deferred-items.md + - .planning/REQUIREMENTS.md + - .planning/todos/done/fetchguard-guarded-http-client.md + - .planning/todos/done/redacting-slog-handler.md + +key-decisions: + - "The sm-feedback-plugin routes are pinned and fuzzed by the plugin's own tests (TestFeedbackConfig, TestFeedbackSubmit, TestFeedbackEdges), not from TestRouteTablePhase14 or FuzzWriteEndpoints: the fonoteka plugin would otherwise have to require the shared plugin module just for tests" + - "Every mitigated threat gets a removal row (43 rows), not only the high ones; the evidence stage requires one per mitigated threat" + - "Two Phase 14 functions are exempt from the 80% function floor with recorded reasons: cover_importer.go fetch (its 2xx path needs the live image host, deferred from 14-03) and validateDiscogsInput (its error branch needs a malformed rule table)" + - "internal/pgtest of both plugins is exempt from the package floor: a test-only helper whose uncovered lines are container start failures" + - "The Discogs truth tables were restructured into php_mapper.json (mapRelease plus the candidate mappers) and widened; TestPHPTruthDiscogs replaces the replay subtests of TestDiscogsDomainVectors and TestMapReleaseFixture" + - "PHP's behaviour kept against the plan's wording stays pinned: a dry run writes what PHP's writes (T-14-19 pins 'never writes a staged field'), a second identical apply re-imports covers, apply runs without a transaction (14-03 deviations 5-7)" + +patterns-established: + - "Phase gate with a phase-function coverage list and per-mitigated-threat removal rows" + - "Shared-plugin tests committed and pushed in the plugin checkout, then one gitlink-only bump commit per plugin in the application" + +requirements-completed: [JOBS-02, JOBS-03, SRCH-02, INTG-01, INTG-02, API-08, CLI-05] + +coverage: + - id: D1 + description: "One command proves the phase green or red over both repositories, the corpus and the named flows, failing closed" + requirement: INTG-01 + verification: + - kind: other + ref: "bash scripts/check-phase14.sh --all → 'phase14 all passed' (2026-10-04); --self-test plants every detector" + status: pass + human_judgment: false + - id: D2 + description: "The Discogs mapper, parser, scorer and price resolver reproduce PHP's truth tables row for row" + requirement: INTG-01 + verification: + - kind: unit + ref: "plugins/golem15/fonoteka/classes/discogs/php_truth_test.go#TestPHPTruthDiscogs" + status: pass + human_judgment: false + - id: D3 + description: "Every numeric edge one step either side, the CSV job lifecycles, the route buckets and the fuzz over the Phase 14 write routes" + requirement: JOBS-02 + verification: + - kind: unit + ref: "plugins/golem15/fonoteka#TestPhase14Edges, TestPhase14Jobs, TestRouteTablePhase14, FuzzWriteEndpoints, TestPhase14HandlersFailClosed" + status: pass + - kind: unit + ref: "plugins/golem15/feedback#TestFeedbackEdges" + status: pass + human_judgment: false + - id: D4 + description: "Every mitigated threat has a test that fails without its protection" + requirement: INTG-02 + verification: + - kind: unit + ref: "plugins/golem15/fonoteka/phase14_security_test.go#TestPhase14Threats" + status: pass + - kind: other + ref: "bash scripts/check-phase14.sh --removal → 43 of 43 fail as required, 'phase14 removal passed'" + status: pass + human_judgment: false + - id: D5 + description: "The framework, the Phase 14 application functions and both shared plugins at the 80% floor" + requirement: API-08 + verification: + - kind: other + ref: "bash scripts/check-phase14.sh --coverage → 'phase14 coverage passed'" + status: pass + human_judgment: false + - id: D6 + description: "Validation signed off, requirements complete, API coverage checked, security review complete, folded todos closed" + requirement: CLI-05 + verification: + - kind: other + ref: "bash scripts/check-phase14.sh --evidence → 'phase14 evidence passed'; REQUIREMENTS grep prints 7" + status: pass + human_judgment: false + +duration: 132min +completed: 2026-10-04 +status: complete +--- + +# Phase 14 Plan 06: Unit tests and the Phase 14 gate Summary + +**Phase 14 is proven by one fail-closed gate: PHP's own Discogs truth tables replayed row for row, every Phase 14 function and both shared plugins at the 80% floor, every numeric edge pinned one step either side, and 43 anchor-exact mutations, one or more per mitigated threat, each caught by a named test.** + +## Performance + +- **Duration:** 132 min +- **Started:** 2026-10-03T22:35:35Z +- **Completed:** 2026-10-04T00:48Z +- **Tasks:** 4 +- **Commits:** 6 in summercms.go, 9 in fonoteka.go, 2 in sm-golem-plugin and 1 in sm-feedback-plugin (both pushed), plus this summary +- **Files:** 13 created and 13 modified across the four repositories (plus 11 fuzz seeds) + +## Accomplishments + +- **The gate (`scripts/check-phase14.sh`).** + - `--go` runs vet and tests in summercms.go, and with `-race` in fonoteka.go including sm-user-plugin, sm-golem-plugin and sm-feedback-plugin. It refuses a vendor AI SDK in either module graph (D-02, T-14-SC). + - `--parity` reads 175 recorded, 172 ported and passing and 3 pending from the replay's own coverage line and from the manifest. It requires every TestFonotekaNuxtFlows subtest (read from the source, so a new flow cannot be skipped), the broadcast goldens, the sidecar replay, the feedback job sidecar, `check_corpus --require-recorded --check-secrets`, the fuzz corpus scan and the docs checks. + - `--named` runs about 110 tests by exact name, across 21 packages. + - `--coverage` enforces the floors (below). + - `--removal` runs 43 mutations. + - `--evidence` checks the security review, the validation map, COVERAGE.md, the REQUIREMENTS wording and the ROADMAP Phase 14 section. + - `--self-test` plants a failing, skipped, zero-test, racy and non-JSON run, a fourth pending route, a vendor SDK, each secret shape, each coverage and function-floor refusal, each removal-harness refusal and each evidence gap. +- **PHP truth tables.** `parity/discogs_truth_tables.php` was rerun against the isolated PHP instance: + - `php_mapper.json` gained releases without images, with secondary images only, with several formats, with cassette and CD, with unicode artists and a master's main release. + - The barcode table gained check-digit cases. + - The price table gained every market currency, rounding ties and empty or odd suggestions. + - `TestPHPTruthDiscogs` replays all rows and fails if a table shrinks. The price results are compared byte for byte. +- **Application tests (fonoteka).** + - `TestPhase14Edges` pins the limiter (50/51), the wait budget (15/16 s), Retry-After, the candidate cap (10/11), the 90-day prune cutoff, the 60/20/10 inbound buckets and the 12/13 cover-price throttle. + - `TestPhase14Jobs` covers a cancel while a match is paused, a resumed match writing the same rows as an uninterrupted one, already-written rows being skipped, and every worker error branch. + - `TestPhase14HandlersFailClosed` covers gate lookups that fail with a 500 before any vendor call, PHP's no_match, token-rejected and Winter 500 answers, and failures after the fetch. + - `TestPhase14Classes` uses a fault-injecting gorm handle. `TestPhase14ControllerHelpers` covers the controller helpers. + - `TestRouteTablePhase14` now asserts which in-handler bucket each route answers 429 from. + - `FuzzWriteEndpoints` covers the 11 Phase 14 routes through a scripted vendor, with 11 committed seeds (81 routes in all). +- **Threats.** `TestPhase14Threats` has one subtest per application threat from T-14-08 to T-14-29. The framework threats map to their module tests, and the feedback threats to the plugin's own tests. `14-SECURITY-REVIEW.md` maps all 39 ids, and the removal run passed 43 of 43. +- **Shared plugins.** `TestGolemAdminSchemas` and `TestGolemImportCommand` were added to sm-golem-plugin, and `TestFeedbackEdges` to sm-feedback-plugin. Both were pushed, and each pointer was bumped in its own commit. +- **Record.** 14-VALIDATION.md is validated. INTG-02 and API-08 are marked Complete. COVERAGE.md is confirmed, and both folded todos are closed. + +### Coverage (check-phase14.sh --coverage) + +| Package | Coverage | +|---------|----------| +| fetchguard | 84.5% | +| tide | 82.1% | +| sunscreen | 98.5% | +| beachcomber | 84.8% | +| beachcomber/typesense | 95.6% | +| fonoteka classes/discogs | 86.1% | +| fonoteka console | 82.6% | + +- **The 132 Phase 14 functions** of the fonoteka root, classes and controllers/api packages are each at 80% or more. The lowest is 80.0%. Two are exempt. +- **sm-golem-plugin** packages are between 80.0% (updates) and 100%. +- **sm-feedback-plugin** packages are between 82.9% (classes) and 100%. +- **internal/pgtest** is exempt in both plugins. + +## Task Commits + +summercms.go: +1. **Task 1 (tracer): the gate's self-test, go and parity stages**: `88f7683` (test) +2. **Task 3: tide sidecar refusals**: `fc90ae2` (test). **Gate named, coverage and removal stages**: `4c042f7` (test). **Security review**: `8154352` (docs) +3. **Task 4: evidence stage**: `a1fccd3` (test). **Validation, requirements, coverage, todos**: `17b2ef1` (docs) + +fonoteka.go (not pushed): +1. **Task 2: PHP truth tables**: `45052ad`. **Edges and job lifecycles**: `b30698f`. **Route buckets and fuzz**: `bf177e3`. **Class, controller and worker coverage**: `0244392` (all test) +2. **Task 3: sm-golem-plugin bump**: `49dfef5`. **sm-feedback-plugin bump**: `02a4d05` (chore). **TestPhase14Threats**: `21b3eb8` (test). **sm-golem-plugin bump**: `976b446` (chore). **T-14-19 strengthened**: `cd393e4` (test) + +sm-golem-plugin (pushed, master): `70314b5`, `6646d10`. sm-feedback-plugin (pushed, master): `3057719`. + +Tracer gate: after Task 1, `` (`bash -n`, `--self-test`, `--go`, `--parity`) was re-run end to end and passed, so the expansion tasks went ahead. + +## Deviations from Plan + +### Auto-fixed issues + +**1. [Rule 1 - Weak tests found by the removal harness] Two tests did not catch the removal of their protection** +- **Found during:** Task 3, first `--removal` run (5 survivors) +- **Issue:** T-14-19's subtest checked only fields that a dry run stages, so removing the fill-empty skip survived. `TestGolemAdminSchemas` checked only that the key text was absent, which `lagoon.Encrypted`'s redaction already guarantees. +- **Fix:** T-14-19 now also pins a filled barcode and an empty discogs_id. The golem test refuses any `api_key` key. +- **Commits:** `cd393e4` (fonoteka.go), `6646d10` (sm-golem-plugin), bumped in `976b446` +- The other three survivors were mutations that were redundant or did not build (the Origin empty-list check, DropIndex's escaping, an import left unused). Their rows now remove a protection that is load-bearing. The details are in the review. + +**2. [Rule 3 - Blocking] The gate keeps the failed go test log** +- **Found during:** Task 1. `TestCoverFetcherHostLock` failed once under the full `-race` run, and the log had been deleted. +- **Fix:** `phase14_go` moves a failed run's JSON log to `$TMPDIR/phase14-failed-.json` and prints the detector's verdict last. The failure did not recur (see deferred-items.md). + +### Plan details refined + +**3.** The feedback routes are pinned and fuzzed in sm-feedback-plugin, not in `TestRouteTablePhase14`/`FuzzWriteEndpoints`. Activating the feedback plugin from the fonoteka plugin's tests would make the application plugin require the shared plugin module. TestFeedbackConfig, TestFeedbackSubmit and TestFeedbackEdges pin the four routes' throttles, buckets and limits instead. The fonoteka route table pins the eleven Phase 14 routes plus their in-handler buckets. + +**4.** The truth-table files are `php_mapper.json` (the old `php_map_release.json` plus `php_candidates.json`), `php_input_parser.json`, `php_scorer.json` and `php_price_suggestion.json`. `php_scorer.json` regenerated byte-identical. + +**5.** The removal table covers every mitigated threat (43 rows), not only the high ones. The evidence stage enforces a row for every mitigated threat. + +**6.** Coverage files the plan named but did not need were not created. Their packages already met the floor: `fetchguard`, `sunscreen`, `typesense`, golem `services` and `providers`, and feedback `classes`. The new coverage tests were placed where coverage was low: `TestPhase14Classes`, `TestPhase14ControllerHelpers`, `TestPhase14HandlersFailClosed` (new file `phase14_handlers_test.go`), `TestGolemImportCommand` and `TestFeedbackEdges`. + +**7.** Two function exemptions and one package exemption, each with its reason, are recorded in the gate (see key-decisions). + +**8.** The prune cutoff edge in `TestPhase14Edges` runs the real command on the wall clock: one row a minute inside the window and one a second past it. The exact-boundary row stays pinned by the console package's `TestPruneNotifications`, which injects the clock. + +--- + +**Total deviations:** 8. Two are auto-fixes and six are refinements. **Impact:** the only production-adjacent change is test strength. No production code changed. + +## Issues Encountered + +- One `--all` run refused at the coverage stage. A testcontainers Postgres for the `updates` package did not become ready within 60 s under the gate's parallel load. The rerun passed unchanged. This is recorded in deferred-items.md. +- The shell aliases `rm` to its interactive form, and one cleanup blocked on its prompt. Later cleanups use `command rm -f`. +- `/tmp` is at quota, so the parity root was `~/.cache/summercms-parity/p1406` and the Go temp dirs were `~/.cache/gotest-tmp`. The truth-table run wrote nothing into the PHP checkout apart from `php artisan cache:clear`'s cache directory, which earlier recordings also touched. `git status` there shows only the user's own files. + +## Verification + +- `bash scripts/check-phase14.sh --all`: `phase14 all passed`. The stages were self-test, go, parity (175/175 recorded, 172 ported and passing, 0 failing, 3 pending), named, coverage and evidence, and the run took 13 min 55 s. +- `bash scripts/check-phase14.sh --removal`: all 43 rows `fails as required`, then `phase14 removal passed`. Every file was restored byte for byte. +- Per-task verifies: + - Task 2's `-race` run of TestPHPTruthDiscogs, TestPhase14Edges, TestPhase14Jobs, TestRouteTablePhase14 and FuzzWriteEndpoints passed with no SKIP or DATA RACE. + - Task 3's `-race -cover` runs of both plugins passed. +- Acceptance greps: + - `EXPECTED_PORTED=172` and `EXPECTED_PENDING=3` each appear once. + - The golem and feedback plugin paths are present. + - `--bogus` exits 2 with the usage text. + - `php_mapper.json` and `php_price_suggestion.json` exist, and the generator names `PriceSuggestionResolver`. + - The fuzz corpus went from 70 to 81 files. + - `T-14-` appears 96 times in the review. + - There are three `run_(named|removal|coverage)()` functions and one `run_evidence()`, which `--all` calls. + - The REQUIREMENTS grep prints 7. + - The validation file has no row with the pending status, and neither todo is left under pending. + - Both plugins are `## master...origin/master`. + +## Known Stubs + +None. + +## Threat Flags + +None. This plan adds tests and a gate script only. Its own threats, T-14-37 (gate counts) and T-14-38 (removal harness), are in the review with removal rows RC-40 to RC-42. + +## Next Phase Readiness + +- `/gsd-verify-work 14` can run `bash scripts/check-phase14.sh --all`, and separately `--removal`. +- The open items from deferred-items.md carry over: the Phase 12 cover-import replay, the one-off TestCoverFetcherHostLock failure and the testcontainers start wait. +- Phase 14.1 (oauth-identities and the token `me` route) will move `EXPECTED_PENDING` from 3 toward 0. + +--- +*Phase: 14-domain-jobs-and-external-integrations* +*Completed: 2026-10-04* + +## Self-Check: PASSED + +Every created file exists; commits 88f7683, fc90ae2, 4c042f7, 8154352, a1fccd3 and 17b2ef1 (summercms.go), 45052ad, b30698f, bf177e3, 0244392, 49dfef5, 02a4d05, 21b3eb8, 976b446 and cd393e4 (fonoteka.go), 70314b5 and 6646d10 (sm-golem-plugin, on origin/master) and 3057719 (sm-feedback-plugin, on origin/master) are present. diff --git a/.planning/phases/14-domain-jobs-and-external-integrations/deferred-items.md b/.planning/phases/14-domain-jobs-and-external-integrations/deferred-items.md index 2e81e5f..b8d0d33 100644 --- a/.planning/phases/14-domain-jobs-and-external-integrations/deferred-items.md +++ b/.planning/phases/14-domain-jobs-and-external-integrations/deferred-items.md @@ -5,3 +5,9 @@ Out-of-scope discoveries logged by executors. They are not fixed in the plan tha ## From 14-03 - **Phase 12 album cover imports dial the real cover hosts during the parity replay.** `POST albums` (`six-covers`), `PUT albums/{id}` (`cover-urls`), `POST albums/bulk` (`cover-failures`) and `POST albums/{id}/photos` import `cover_urls` through `classes.CoverImporterFromConfig`, whose default fetch is the one-shot `fetchguard.Fetch`. That fetch does not honour `fetchguard.WithTransport`, so the replay reaches `i.discogs.com` instead of an upstream fake, and PHP's recordings of those cases were made against the live hosts too. 14-03 added `classes.NewGuardedCoverImporter` (a `fetchguard.Client` that honours the seam, with PHP's `GuzzleHttp/7` User-Agent) and uses it only on the Discogs routes. Moving the Phase 12 routes onto it needs their cases re-recorded with cover sidecars. Candidate owner: 14-06 or a quick task. + +## From 14-06 + +- **The Phase 12 cover-import replay (from 14-03) is still open.** 14-06 kept it out of scope: moving the Phase 12 album routes onto `classes.NewGuardedCoverImporter` needs their cases re-recorded against PHP with cover sidecars. Until then `classes/cover_importer.go` `fetch`'s 2xx path can only run against the live image hosts, which is why `check-phase14.sh --coverage` exempts that one function (72.7%). +- **`TestCoverFetcherHostLock` failed once under the full `-race` run of `check-phase14.sh --go`** (the first run of 14-06, with the whole fonoteka.go tree under test in parallel). The failure output was not kept, and the test passed in every later run: three `-race` runs of its package under load, two further `--go` runs and two `--all` runs. The gate now keeps the full log of a failed `go test` run (`phase14-failed-.json` under `$TMPDIR`) so a repeat can be diagnosed. Candidate owner: a quick task if it recurs. +- **A testcontainers Postgres start can exceed its 60 s wait under the gate's parallel load** (the `updates` package, seen once in `--all` at the coverage stage). The gate refuses, as it must; a rerun passed. Raising the wait in the shared test harnesses is a candidate quick task.