diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index 0e7bdbc..41acc99 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -80,7 +80,7 @@ Requirements for v1 (the Płytarium port). Each maps to roadmap phases. "User" b - [x] **API-05**: CSV import as a multi-step session (store, show/poll, mapping patch, per-row edit, commit, cancel) and CSV export on both authenticated groups - [x] **API-06**: Per-user and per-org Discogs and AI credentials CRUD with encrypted storage, org-lock flag, and env-to-org-to-user resolution (the live ai-credential/test and discogs-credential/test routes are Phase 14, with INTG-01 and INTG-02) - [x] **API-07**: Onboarding, public and invitation inspection routes, including the anonymous collection public-token views (public/{token}, its albums and album detail), with their public rate-limit buckets -- [ ] **API-08**: Feedback submissions, widget config and the per-user hide preference from the stack feedback plugin (sitemap dropped for this application, D-14) +- [x] **API-08**: Feedback submissions, widget config and the per-user hide preference from the stack feedback plugin (sitemap dropped for this application, D-14) - [ ] **API-09**: All 154 routes are registered on the correct groups with identical paths, methods, status codes and bodies ### Background jobs (JOBS) @@ -103,7 +103,7 @@ Requirements for v1 (the Płytarium port). Each maps to roadmap phases. "User" b ### Integrations (INTG) - [x] **INTG-01**: Discogs client with proactive rate threshold, bounded in-request wait budget, retry-after fallback, and host-locked cover fetch, plus the albums/{id}/cover-price/discogs route, the wishlist/albums/{id}/match and apply-release routes, the album routes albums/match, albums/{id}/match, albums/{id}/apply-release and albums/import/discogs (D-07), discogs-credential/test and the CSV row-edit Discogs pick (selected_discogs_id) -- [ ] **INTG-02**: AI cover recognition through Anthropic and OpenAI-compatible adapters over the guarded client, with per-credential model and base URL overrides, plus the ai-credential/test route and the backend global vision model behind the AI resolver's admin tier +- [x] **INTG-02**: AI cover recognition through Anthropic and OpenAI-compatible adapters over the guarded client, with per-credential model and base URL overrides, plus the ai-credential/test route and the backend global vision model behind the AI resolver's admin tier ### Admin (ADMIN) @@ -219,7 +219,7 @@ Which phases cover which requirements. Updated during roadmap creation. | API-05 | Phase 13 | Complete | | API-06 | Phase 13 | Complete | | API-07 | Phase 13 | Complete | -| API-08 | Phase 14 | Pending | +| API-08 | Phase 14 | Complete | | API-09 | Phase 15 | Pending | | JOBS-01 | Phase 11 | Gaps Found | | JOBS-02 | Phase 14 | Complete | @@ -230,7 +230,7 @@ Which phases cover which requirements. Updated during roadmap creation. | SRCH-01 | Phase 11 | Complete | | SRCH-02 | Phase 14 | Complete | | INTG-01 | Phase 14 | Complete | -| INTG-02 | Phase 14 | Pending | +| INTG-02 | Phase 14 | Complete | | ADMIN-01 | Phase 9 | Complete | | ADMIN-02 | Phase 9 | Complete | | ADMIN-03 | Phase 9 | Complete | diff --git a/.planning/phases/14-domain-jobs-and-external-integrations/14-VALIDATION.md b/.planning/phases/14-domain-jobs-and-external-integrations/14-VALIDATION.md index e565646..8783a51 100644 --- a/.planning/phases/14-domain-jobs-and-external-integrations/14-VALIDATION.md +++ b/.planning/phases/14-domain-jobs-and-external-integrations/14-VALIDATION.md @@ -2,10 +2,13 @@ phase: "14" slug: "domain-jobs-and-external-integrations" # status lifecycle: draft (seeded by plan-phase) → validated (set by validate-phase §6) -status: draft -nyquist_compliant: false -wave_0_complete: false +# audit-milestone §5.5 distinguishes NOT-VALIDATED (draft) from PARTIAL (validated + nyquist_compliant: false) (#2117) +status: validated +nyquist_compliant: true +wave_0_complete: true created: "2026-10-03" +validated: "2026-10-04" +gate: "scripts/check-phase14.sh --all" --- # Phase 14 — Validation Strategy @@ -19,10 +22,10 @@ created: "2026-10-03" | Property | Value | |----------|-------| | **Framework** | go test (+ testify), testcontainers-go postgres, tide parity replay | -| **Config file** | none (Go); gate script `scripts/check-phase14.sh` (Wave 0, modelled on `check-phase13.sh`) | +| **Config file** | none (Go); gate script `scripts/check-phase14.sh` (`--self-test --go --parity --named --removal --coverage --evidence --all`, modelled on `check-phase13.sh`) | | **Quick run command** | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka/... -count=1 -short` | -| **Full suite command** | `go test ./... -count=1 && go -C ../fonoteka.go test ./... -count=1 -race && bash scripts/check-phase14.sh --all` | -| **Estimated runtime** | ~180 seconds | +| **Full suite command** | `bash scripts/check-phase14.sh --all` (vet and tests in both repos with `-race` in fonoteka.go, the corpus, every named test, the coverage floors and this file), then `bash scripts/check-phase14.sh --removal` on its own | +| **Estimated runtime** | about 15 minutes for `--all`, about 25 minutes for `--removal` | --- @@ -30,39 +33,68 @@ created: "2026-10-03" - **After every task commit:** the task's named `-run` tests plus `go vet` in the touched repo - **After every plan wave:** `go test ./...` in both repos plus `TestParityCorpus` / `TestFonotekaNuxtFlows` -- **Before `/gsd-verify-work`:** `scripts/check-phase14.sh --all` must be green -- **Max feedback latency:** 180 seconds +- **Before `/gsd-verify-work`:** `scripts/check-phase14.sh --all` must be green: vet and tests in summercms.go and, with `-race`, in fonoteka.go including sm-user-plugin, sm-golem-plugin and sm-feedback-plugin; no vendor AI SDK in either module graph; the parity corpus (175 recorded, 172 ported and passing, the 3 D-09 routes not ported, read from the replay's own coverage line and the manifest), every TestFonotekaNuxtFlows subtest, the broadcast goldens, TestUpstreamSidecarsAreReplayed, TestFeedbackJobSidecarMatchesPlugin, `check_corpus --require-recorded --check-secrets`, the fuzz corpus scan, TestDocsTree and `docs:build --check`; every named test by exact name; the coverage floors; and this file, the security review, COVERAGE.md, REQUIREMENTS.md and the ROADMAP Phase 14 section +- **Max feedback latency:** 180 seconds per task's named tests --- ## Per-Task Verification Map -| Req ID | Behavior | Test Type | Automated Command | File Exists | Status | -|--------|----------|-----------|-------------------|-------------|--------| -| JOBS-02 | CSV match job: 0/1/many, gate off, cancel, superseded, 429 re-dispatch delay, 240 s timeout value | unit + Postgres | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^TestCsvMatchJob' -count=1 -race` | ❌ W0 | ⬜ pending | -| JOBS-02 | CSV import job: overwrite/fill/create, write_failed row, canceled, bulk_updated publish once | unit + Postgres | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^TestCsvImportJob' -count=1 -race` | ❌ W0 | ⬜ pending | -| JOBS-02 | WR-02: racing commit vs mapping/row-edit/cancel queues at most one import job | Postgres | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^TestCsvWR02' -count=1 -race` | ❌ W0 | ⬜ pending | -| JOBS-03 | Digest: 30-min coalescing, sent count, queue row deleted, en/pl template | unit + Postgres | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^TestWishlistDigestJob' -count=1 -race` | ❌ W0 | ⬜ pending | -| SRCH-02 | reindex: zero collection_id-0 before/after, drop legacy index | unit (fake engine) | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^TestReindexCommand' -count=1` | ❌ W0 | ⬜ pending | -| CLI-05 | prune-notifications + schedule entry; reindex; oauth-client | unit + Postgres | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestPruneNotifications|TestSchedule)' -count=1` | partial | ⬜ pending | -| INTG-01 | Discogs limiter threshold, wait budget, retry-after, fake clock | unit + Postgres | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka/classes/discogs -count=1 -race` | ❌ W0 | ⬜ pending | -| INTG-01 | Discogs routes parity with upstream sidecars | parity | `go -C ../fonoteka.go test ./parity -run '^(TestParityCorpus|TestCheckCorpusPortedCaseStatus)$' -count=1` | partial | ⬜ pending | -| INTG-02 | Adapters build PHP payloads; SSRF guard; admin trusted bypass | unit + parity | `go -C ../fonoteka.go test ./plugins/golem15/golem/... -count=1` | ❌ W0 | ⬜ pending | -| INTG-02 | Recognition retry, truncation, admin vision tier | unit + parity | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^TestRecognize' -count=1` | ❌ W0 | ⬜ pending | -| API-08 | Feedback config/submit/options/me-hidden, origin gate, G15Office job via fake | unit + parity | `go -C ../fonoteka.go test ./plugins/golem15/feedback/... -count=1 -race` | ❌ W0 | ⬜ pending | -| framework | fetchguard client modes; redact handler; tide sidecar replay | unit | `go test ./modules/fetchguard/... ./modules/tide/... -count=1` | ❌ W0 | ⬜ pending | +Every row's tests are run by name by `scripts/check-phase14.sh --named` (the evidence stage refuses a row naming a test it does not run); the parity rows also by `--parity`. All rows were green in the 2026-10-04 run of `--all`. -*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky* +| Task ID | Plan | Wave | Requirement | Threat Ref | Behavior | Test Type | Automated Command | File Exists | Status | +|---------|------|------|-------------|------------|----------|-----------|-------------------|-------------|--------| +| 14-01-T1 | 14-01 | 1 | INTG-02 (framework) | T-14-01, T-14-02 | A guarded JSON POST through fetchguard.Client is answered offline by the asserting tide upstream fake; the transport seam is code-only | unit | `go test ./modules/fetchguard -run '^(TestClientPostJSONThroughUpstreamFake\|TestTransportSeamIsCodeOnly\|TestClientNeverFollowsRedirects)$' -count=1 -v` | ✅ `modules/fetchguard/client_test.go`, `client_internal_test.go` | ✅ green | +| 14-01-T2 | 14-01 | 1 | INTG-02 (framework) | T-14-01, T-14-06 | PUT, multipart, bearer and trusted mode; https and host checks before I/O; the body cap | unit | `go test ./modules/fetchguard -run '^(TestClientModes\|TestClientSchemeGuard\|TestClientMultipart\|TestClientBodyCap\|TestClientPutJSONHeaderOrder)$' -count=1 -v` | ✅ `modules/fetchguard/client_test.go` | ✅ green | +| 14-01-T3 | 14-01 | 1 | INTG-01 (framework) | T-14-04, T-14-05 | `summer parity:upstream` records masked sidecars on loopback only; the fake asserts the request; refusals of malformed sidecars | unit | `go test ./modules/tide -run '^(TestUpstreamProxyScriptMode\|TestUpstreamProxyRefusesNonLoopback\|TestEnsureParityCA\|TestUpstreamProxyMultipartAndForwardGuard\|TestWriteUpstreamRefusesUnmaskedCredential\|TestUpstreamFakeRejectsMismatchedRequest\|TestUpstreamFakeHashesBase64Bodies\|TestUpstreamSidecarRefusals)$' -count=1 -v` ; `go test ./cmd/summer -run '^(TestParityCommandContract\|TestToolCommandNames)$' -count=1` | ✅ `modules/tide/upstream_test.go`, `upstream_proxy_test.go`, `upstream_coverage_test.go` | ✅ green | +| 14-01-T4 | 14-01 | 1 | SRCH-02 (framework) | T-14-03, T-14-07 | sunscreen redacts credentials in every generated main; surf hides panic text; DropIndex and EnsureIndex | unit | `go test ./modules/sunscreen ./modules/surf ./internal/build ./modules/beachcomber/... -run '^(TestRedactHandler\|TestScrub\|TestInstallDefault\|TestRecoverHidesPanicDetails\|TestGenerateMainInstallsRedactingLogger\|TestDropIndex\|TestEngineDropIndex\|TestEngineEnsureIndex)$' -count=1` | ✅ `modules/sunscreen/sunscreen_test.go`, `modules/surf/recover_redaction_test.go`, `modules/beachcomber/typesense/engine_test.go` | ✅ green | +| 14-01-T5 | 14-01 | 1 | INTG-02, API-08 | — | REQUIREMENTS, ROADMAP and PROJECT reworded (D-06, D-07, D-13, D-14) | gate | `bash scripts/check-phase14.sh --evidence` (the SDK and sitemap wording refused; the ROADMAP Phase 14 repos and criteria name the routes and both plugin repos) | ✅ `.planning/REQUIREMENTS.md`, `.planning/ROADMAP.md` | ✅ green | +| 14-02-T1 | 14-02 | 2 | JOBS-02, INTG-01 | T-14-08, T-14-11 | A CSV row's Discogs pick resolves with PHP's draft, replayed from PHP's recorded exchange; the HMAC bucket | unit + parity | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestCsvRowPickSeam\|TestCsvRowPickResolves)$' -count=1 -race` ; `go -C ../fonoteka.go test ./plugins/golem15/fonoteka/classes/discogs -run '^(TestBucketID\|TestRateLimiterPostgresAcquire)$' -count=1` ; `go -C ../fonoteka.go test ./parity -run '^(TestParityCorpus\|TestUpstreamSidecarsAreReplayed)$' -count=1` | ✅ `csv_smoke_test.go`, `classes/discogs/postgres_test.go` | ✅ green | +| 14-02-T2 | 14-02 | 2 | INTG-01 | T-14-09 | Client statuses, limiter budget and Retry-After, the PHP truth tables of the mapper, parser, scorer and price resolver | unit + truth table | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka/classes/discogs -run '^(TestPHPTruthDiscogs\|TestClientGetRelease\|TestDiscogsClientStatuses\|TestRateLimiterBudget\|TestRateLimiterSyncFromHeaders\|TestRegisterRetryAfter\|TestDiscogsDomainVectors\|TestDiscogsRateWindowConcurrent\|TestNumberFormat4)$' -count=1 -race` | ✅ `classes/discogs/discogs_test.go`, `php_truth_test.go`, `testdata/php_*.json` | ✅ green | +| 14-02-T3 | 14-02 | 2 | JOBS-02 | T-14-10, T-14-12, T-14-13 | Match and import workers: 0/1/many, pause and re-dispatch, cancel, superseded, write_failed, lost access; WR-02 locks | unit + Postgres | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestCsvMatchJob\|TestCsvImportJob\|TestCsvWR02)$' -count=1 -race` | ✅ `csv_jobs_test.go`, `csv_wr02_test.go` | ✅ green | +| 14-02-T4 | 14-02 | 2 | JOBS-03, SRCH-02, CLI-05 | T-14-14, T-14-15 | The digest mails once per window; prune-notifications on the daily schedule; reindex with its integrity checks and the legacy drop | unit + Postgres | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestWishlistDigestJob\|TestWishlistDigestWorkerRegistered\|TestFonotekaSchedulePrune)$' -count=1` ; `go -C ../fonoteka.go test ./plugins/golem15/fonoteka/console -run '^(TestPruneNotifications\|TestReindexCommand)$' -count=1` | ✅ `wishlist_digest_job_test.go`, `schedule_test.go`, `console/phase14_commands_test.go` | ✅ green | +| 14-03-T1 | 14-03 | 3 | INTG-01 | T-14-16, T-14-17 | Album match: scope first, the gate, the shared bucket, PHP's scored candidates | unit + parity | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestDiscogsMatchRoute\|TestDiscogsInboundLimits\|TestRouteTablePhase14)$' -count=1 -race` | ✅ `discogs_routes_test.go`, `routes_table_phase14_test.go` | ✅ green | +| 14-03-T2 | 14-03 | 3 | INTG-01 | T-14-18, T-14-19 | Apply-release fill-empty, overwrite, cover_only and PHP's dry run; draft match; wishlist twins | unit + parity | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestApplyReleaseModes)$' -count=1 -race` ; `go -C ../fonoteka.go test ./plugins/golem15/fonoteka/classes/discogs -run '^(TestCoverFetcherHostLock)$' -count=1` | ✅ `discogs_routes_test.go`, `classes/discogs/cover_fetcher_test.go` | ✅ green | +| 14-03-T3 | 14-03 | 3 | INTG-01 | T-14-17, T-14-20, T-14-21 | Import box, MCP cover-price (write scope, throttle 12/min), token test | unit + parity | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestDiscogsImportRoute\|TestCoverPriceRoute\|TestDiscogsCredentialTestRoute)$' -count=1 -race` | ✅ `discogs_routes_test.go` | ✅ green | +| 14-04-T1 | 14-04 | 4 | INTG-02 | T-14-29 | sm-golem-plugin boots before fonoteka; ai-credential/test answers PHP's bodies | unit + parity | `go -C ../fonoteka.go test ./plugins/golem15/golem -run '^(TestGolemPluginBoot)$' -count=1` ; `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestAICredentialTestRoute)$' -count=1` | ✅ `golem/golem_test.go`, `ai_routes_test.go` | ✅ green | +| 14-04-T2 | 14-04 | 4 | INTG-02 | T-14-22, T-14-24 | Both adapters' payloads, every AIService call, the SSRF guard, the models admin and importer | unit | `go -C ../fonoteka.go test ./plugins/golem15/golem/... -run '^(TestOpenAIAdapterPayload\|TestAnthropicAdapterPayload\|TestAIServiceFailures\|TestAIServiceStream\|TestDefaultModelQuirk\|TestPromptFactory\|TestSSRFGuard\|TestImportSettings\|TestAdminModelsForm\|TestGolemAdminSchemas\|TestGolemImportCommand)$' -count=1` | ✅ `golem/classes/*/..._test.go`, `golem/golem_admin_test.go` | ✅ green | +| 14-04-T3 | 14-04 | 4 | INTG-02 | T-14-22, T-14-23 | The AI tier: admin vision model trusted, user and org guarded, PHP's precedence | unit | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestAdminVisionTier\|TestResolveAIConfigPrecedence)$' -count=1` ; `go -C ../fonoteka.go test ./plugins/golem15/golem/classes/services -run '^(TestAdminModelTrusted)$' -count=1` | ✅ `ai_routes_test.go`, `credentials_smoke_test.go` | ✅ green | +| 14-04-T4 | 14-04 | 4 | INTG-02 | T-14-25, T-14-26, T-14-27 | Recognition on both groups: prompt, retry, truncation, caps, image guard, bucket | unit + parity | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestRecognizeRoutes)$' -count=1 -race` ; `go -C ../fonoteka.go test ./plugins/golem15/fonoteka/classes -run '^(TestRecognizeAlbums)$' -count=1` | ✅ `ai_routes_test.go`, `classes/album_recognition_test.go` | ✅ green | +| 14-05-T1 | 14-05 | 5 | API-08 | T-14-30 | Widget config behind the constant-time key and the fail-closed Origin list | unit + parity | `go -C ../fonoteka.go test ./plugins/golem15/feedback -run '^(TestFeedbackPluginBoot\|TestFeedbackConfig\|TestKeyMatches\|TestURLHost\|TestAllowedOriginHosts\|TestFeedbackOptionsPreflight)$' -count=1` ; `go test ./modules/surf -run '^(TestCORSOptionsMatchesLaravel)$' -count=1` | ✅ `feedback/feedback_test.go`, `modules/surf/cors_coverage_test.go` | ✅ green | +| 14-05-T2 | 14-05 | 5 | API-08 | T-14-31, T-14-32, T-14-35 | Submit with PHP's validation and image guard, me/hidden, the user payload key | unit + parity | `go -C ../fonoteka.go test ./plugins/golem15/feedback -run '^(TestFeedbackSubmit\|TestMeHidden\|TestFeedbackApiArrayHook)$' -count=1 -race` ; `go -C ../fonoteka.go test ./plugins/golem15/feedback/classes -run '^(TestImageGuardCopy)$' -count=1` | ✅ `feedback/feedback_test.go`, `feedback/classes/classes_test.go` | ✅ green | +| 14-05-T3 | 14-05 | 5 | API-08 | T-14-33, T-14-34, T-14-36 | G15Office job replays PHP's exchange; settings and submissions admin; embed.js; settings import | unit + parity | `go -C ../fonoteka.go test ./plugins/golem15/feedback/classes -run '^(TestSyncG15Office\|TestTaskText)$' -count=1` ; `go -C ../fonoteka.go test ./plugins/golem15/feedback -run '^(TestFeedbackAdminSchemas\|TestEmbedJSServed\|TestFeedbackImportSettings)$' -count=1` ; `go -C ../fonoteka.go test ./parity -run '^(TestFeedbackJobSidecarMatchesPlugin)$' -count=1` | ✅ `feedback/classes/classes_test.go`, `parity/feedback_seed_test.go` | ✅ green | +| 14-06-T1 | 14-06 | 6 | JOBS-02, JOBS-03, SRCH-02, INTG-01, INTG-02, API-08, CLI-05 | T-14-37 | The gate runs both repositories, the corpus at 175/172 and the flows, and fails closed | gate | `bash scripts/check-phase14.sh --self-test && bash scripts/check-phase14.sh --go && bash scripts/check-phase14.sh --parity` ; `go -C ../fonoteka.go test ./parity -run '^(TestParityCorpus\|TestCheckCorpusPortedCaseStatus\|TestFonotekaNuxtFlows\|TestCheckCorpusUpstreamCredential\|TestParityContract\|TestSchemaMatchesPHPSnapshot)$' -count=1` | ✅ `scripts/check-phase14.sh` | ✅ green | +| 14-06-T2 | 14-06 | 6 | INTG-01, JOBS-02 | T-14-19 | PHP truth tables row for row; the numeric edges one step either side; the job lifecycles; the route table and the fuzz over the Phase 14 routes | unit + truth table + fuzz seeds | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestPhase14Edges\|TestPhase14Jobs\|TestRouteTablePhase14\|TestRouteTablePhase13\|TestRouteTablePhase12\|FuzzWriteEndpoints\|TestPhase14HandlersFailClosed)$' -count=1 -race` ; `go -C ../fonoteka.go test ./plugins/golem15/fonoteka/classes ./plugins/golem15/fonoteka/controllers/api -run '^(TestPhase14Classes\|TestPhase14ControllerHelpers)$' -count=1` ; `go -C ../fonoteka.go test ./plugins/golem15/fonoteka/classes/discogs -run '^(TestPHPTruthDiscogs)$' -count=1` | ✅ `phase14_edges_test.go`, `phase14_jobs_test.go`, `phase14_handlers_test.go`, `write_endpoints_fuzz_test.go`, `classes/phase14_classes_test.go`, `controllers/api/phase14_controllers_test.go`, `classes/discogs/php_truth_test.go` | ✅ green | +| 14-06-T3 | 14-06 | 6 | INTG-02, API-08 | T-14-38, all mitigated | Every mitigated threat fails without its protection; the framework and both plugins at the floor | unit + gate | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestPhase14Threats)$' -count=1 -race` ; `go -C ../fonoteka.go test ./plugins/golem15/feedback -run '^(TestFeedbackEdges)$' -count=1` ; `bash scripts/check-phase14.sh --named && bash scripts/check-phase14.sh --coverage && bash scripts/check-phase14.sh --removal` | ✅ `phase14_security_test.go`, `feedback/feedback_edges_test.go`, `14-SECURITY-REVIEW.md` | ✅ green | +| 14-06-T4 | 14-06 | 6 | JOBS-02, JOBS-03, SRCH-02, INTG-01, INTG-02, API-08, CLI-05 | — | Validation signed off, requirements marked, API coverage checked, folded todos closed, the whole gate green | gate | `bash scripts/check-phase14.sh --evidence && bash scripts/check-phase14.sh --all` | ✅ this file, `COVERAGE.md`, `.planning/REQUIREMENTS.md` | ✅ green | + +*Status: ✅ green · ❌ red · ⚠️ flaky* + +### Coverage (scripts/check-phase14.sh --coverage, 2026-10-04) + +| Package | Statements covered | +|---------|--------------------| +| summercms.go `modules/fetchguard` | 84.5% | +| summercms.go `modules/tide` | 82.1% | +| summercms.go `modules/sunscreen` | 98.5% | +| summercms.go `modules/beachcomber` | 84.8% | +| summercms.go `modules/beachcomber/typesense` | 95.6% | +| fonoteka `classes/discogs` | 86.1% | +| fonoteka `console` | 82.6% | +| fonoteka root, `classes`, `controllers/api`: the 132 Phase 14 functions | each at 80.0% or more (lowest 80.0%: `AlbumReleaseMatchDraft`, both workers' `fail`), two exempt with reasons: `cover_importer.go fetch` 72.7% (its 2xx path needs the live image host) and `validateDiscogsInput` 77.8% (its error branch needs a malformed rule table) | +| sm-golem-plugin root, `classes/factories`, `providers`, `security`, `services`, `valueobjects`, `console`, `controllers`, `models`, `updates` | 100.0%, 90.0%, 93.4%, 98.0%, 85.5%, 90.8%, 96.1%, 93.1%, 100.0%, 80.0% | +| sm-feedback-plugin root, `classes`, `console`, `controllers`, `controllers/api`, `models`, `updates` | 93.8%, 82.9%, 92.6%, 100.0%, 86.0%, 90.8%, 91.7% | +| `internal/pgtest` of both plugins | exempt: a test-only helper whose uncovered lines are container start failures | --- ## Wave 0 Requirements -- [ ] `fonoteka.go/parity/discogs_truth_tables.php` — truth-table generator (csv_truth_tables.php pattern) -- [ ] tide upstream sidecar loader + fake and the replay hook for `*.upstream.yaml` -- [ ] Shared fake clock helper for limiter, client and job tests -- [ ] Harness to run a conga worker function directly without a live River client -- [ ] `scripts/check-phase14.sh` with updated `EXPECTED_PORTED` / `EXPECTED_PENDING` +- [x] `fonoteka.go/parity/discogs_truth_tables.php` — truth-table generator (csv_truth_tables.php pattern; 14-02, widened in 14-06 to `php_mapper.json`, `php_input_parser.json`, `php_scorer.json`, `php_price_suggestion.json`) +- [x] tide upstream sidecar loader + fake and the replay hook for `*.upstream.yaml` (14-01, 14-02) +- [x] Shared fake clock helper for limiter, client and job tests (`classes/discogs/discogstest`, 14-02) +- [x] Harness to run a conga worker function directly without a live River client (`deliverCsvMatch`, `deliverCsvImport`, `deliverWishlistDigest` take the job id, 14-02) +- [x] `scripts/check-phase14.sh` with `EXPECTED_ROUTES=175`, `EXPECTED_PORTED=172`, `EXPECTED_PENDING=3` (14-06) --- @@ -77,11 +109,13 @@ created: "2026-10-03" ## Validation Sign-Off -- [ ] All tasks have `` verify or Wave 0 dependencies -- [ ] Sampling continuity: no 3 consecutive tasks without automated verify -- [ ] Wave 0 covers all MISSING references -- [ ] No watch-mode flags -- [ ] Feedback latency < 180s -- [ ] `nyquist_compliant: true` set in frontmatter +- [x] All tasks have `` verify or Wave 0 dependencies +- [x] Sampling continuity: no 3 consecutive tasks without automated verify +- [x] Wave 0 covers all MISSING references +- [x] No watch-mode flags +- [x] Feedback latency < 180s per task's named tests +- [x] Nyquist compliance set in the frontmatter -**Approval:** pending +**Approval:** validated 2026-10-04 (plan 14-06; gate `scripts/check-phase14.sh --all` and `--removal` green) + +**Final gate run:** `bash scripts/check-phase14.sh --all` on 2026-10-04 ended with `phase14 all passed` (self-test, go, parity at "175/175 recorded, 172 ported and passing, 0 failing, 3 pending", named, coverage and evidence; 13 min 55 s). `bash scripts/check-phase14.sh --removal` on its own ended with `phase14 removal passed` (43 of 43 mutations failed their named test; every file restored byte for byte). One earlier `--all` run refused at the coverage stage when the `updates` package's testcontainers Postgres did not start within its 60 s wait under load; the rerun passed unchanged. diff --git a/.planning/phases/14-domain-jobs-and-external-integrations/COVERAGE.md b/.planning/phases/14-domain-jobs-and-external-integrations/COVERAGE.md index 4b7d176..3b5a4b0 100644 --- a/.planning/phases/14-domain-jobs-and-external-integrations/COVERAGE.md +++ b/.planning/phases/14-domain-jobs-and-external-integrations/COVERAGE.md @@ -3,6 +3,8 @@ > Full coverage by default. Opt-outs are explicit, reasoned decisions. > > Scope: Phase 14 is a parity port. The PHP reference (Płytarium's Golem15.Fonoteka, Golem15.Golem and Golem15.Feedback plugins) defines the surface. Every capability the PHP code calls is INTEGRATE. Every capability it never calls is OPT-OUT with the reason "not used by the PHP reference — parity port". The INTEGRATE reason column names the test that proves the capability. Plan 14-06 confirms each named test passes, and `scripts/check-phase14.sh --evidence` refuses a row whose test is missing or failing. +> +> Confirmed 2026-10-04 (plan 14-06): every test an INTEGRATE row names is run by exact name by `scripts/check-phase14.sh --named` and passed in that run; `--evidence` refuses an INTEGRATE row naming a test outside that stage and an OPT-OUT row without a reason. Live vendor calls stay out of the gate (D-15): each INTEGRATE capability is proven against PHP's recorded exchange or a scripted vendor answer. ## Discogs API (api.discogs.com, i.discogs.com) diff --git a/.planning/todos/pending/fetchguard-guarded-http-client.md b/.planning/todos/done/fetchguard-guarded-http-client.md similarity index 100% rename from .planning/todos/pending/fetchguard-guarded-http-client.md rename to .planning/todos/done/fetchguard-guarded-http-client.md diff --git a/.planning/todos/pending/redacting-slog-handler.md b/.planning/todos/done/redacting-slog-handler.md similarity index 100% rename from .planning/todos/pending/redacting-slog-handler.md rename to .planning/todos/done/redacting-slog-handler.md