--- phase: "13" slug: "p-ytarium-api-wishlist-notifications-csv-credentials-public" # status lifecycle: draft (seeded by plan-phase) → validated (set by validate-phase §6) # 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-02" validated: "2026-10-03" gate: "scripts/check-phase13.sh --all" --- # Phase 13 — Validation Strategy > Per-phase validation contract for feedback sampling during execution. --- ## Test Infrastructure | Property | Value | |----------|-------| | **Framework** | Go `testing` (+ testify, Go fuzzing), testcontainers Postgres | | **Config file** | none — `fonoteka.go/parity/parity_test.go` TestMain starts Postgres | | **Quick run command** | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka/... ./plugins/golem15/user/... -short -count=1` | | **Full suite command** | `go vet ./... && go test ./... -count=1 && go -C ../fonoteka.go vet ./... && go -C ../fonoteka.go test ./... ./plugins/golem15/fonoteka/... ./plugins/golem15/user/... -count=1` | | **Parity command** | `go -C ../fonoteka.go test ./parity -run 'TestParityCorpus|TestBroadcastGoldens|TestFonotekaNuxtFlows|TestUserAPINuxtFlows' -count=1` | | **Phase gate** | `scripts/check-phase13.sh --self-test && scripts/check-phase13.sh --all` (run from summercms.go; the script lives in summercms.go/scripts like check-phase12.sh); `--removal` runs the RC mutations of 13-SECURITY-REVIEW.md | | **Estimated runtime** | ~180 seconds (full suite with testcontainers); the gate's `--all` about 15 minutes, `--removal` about 20 minutes | The gate unsets `FORCE_COLOR` itself. On a host whose `/tmp` is a small tmpfs, run it with `TMPDIR` and `GOTMPDIR` on a larger disk (the link step of `go test ./...` needs space). --- ## Sampling Rate - **After every task commit:** Run the quick run command plus `go vet` in the touched repo - **After every plan wave:** Run the full suite command, the parity command and the corpus check - **Before `/gsd-verify-work`:** `scripts/check-phase13.sh --all` must be green: vet and tests in both repos, the parity corpus (157 ported and passing, 14 recorded but not ported, read from the replay's own coverage line), the broadcast goldens including the three wishlist goldens, every TestFonotekaNuxtFlows subtest, TestUserAPINuxtFlows, `check_corpus --require-recorded --check-secrets`, TestDocsTree and `docs:build --check`, every named test by exact name, the coverage floors and this file - **Max feedback latency:** 180 seconds --- ## Per-Task Verification Map Task IDs are `-T`. Every row's command was run on 2026-10-03 and passed; `scripts/check-phase13.sh --named` runs all of these tests by exact name and refuses a skip, a missing pass or "no tests to run" (the fonoteka plugin tests under `-race`). Framework commands run from `summercms.go`; application commands use `go -C ../fonoteka.go`. | Task ID | Plan | Wave | Requirement | Threat Ref | Secure Behavior | Test Type | Automated Command | File Exists | Status | |---------|------|------|-------------|------------|-----------------|-----------|-------------------|-------------|--------| | 13-01-T1 | 13-01 | 1 | API-03 (framework) | T-13-23 | Overlapping constrained routes dispatch to the right handler; app wishlist shapes dispatch | unit | `go test ./modules/surf -run '^(TestOverlappingConstrainedRoutes)$' -count=1 -v` ; `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestWishlistOverlapPatternsDispatch)$' -count=1 -v` | ✅ `modules/surf/overlap_test.go`, `plugins/golem15/fonoteka/routes_overlap_test.go` | ✅ green | | 13-01-T2 | 13-01 | 1 | API-03, API-05 (framework) | T-13-22 | Unregistered job kinds queue while a worker runs and are never discarded; job contract pinned | integration | `go test ./modules/conga -run '^(TestUnregisteredKindWithWorker)$' -count=1 -v` ; `go -C ../fonoteka.go test ./plugins/golem15/fonoteka/classes -run '^(TestJobContract)$' -count=1 -v` ; `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestJobContractDispatchWhileWorkerRuns)$' -count=1 -v` | ✅ `modules/conga/unregistered_kind_test.go`, `classes/job_contract_test.go`, `job_contract_worker_test.go` | ✅ green | | 13-01-T3 | 13-01 | 1 | API-03, API-05 (framework) | T-13-24, T-13-25 | prohibited rule; Content-Disposition date and publication masks never hide a real diff | unit | `go test ./modules/lagoon ./modules/tide -run '^(TestValidateRequestProhibited\|TestNormalizeContentDispositionDate\|TestNormalizeNotificationPublication)$' -count=1 -v` | ✅ `modules/lagoon/validate_request_test.go`, `modules/tide/normalize_phase13_test.go` | ✅ green | | 13-01-T4 | 13-01 | 1 | API-03..API-07 | T-13-26 | Queue override, rows dump, share:wishlist capture, ported case-status check, planning rewording | unit | `go -C ../fonoteka.go test ./parity -run '^(TestCheckCorpusPortedCaseStatus\|TestParityCorpus)$' -count=1 -v` | ✅ `parity/check_corpus_test.go` | ✅ green | | 13-02-T1 | 13-02 | 2 | API-04 | T-13-21 | Bell list newest 50, caller's rows only | parity + smoke | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestNotificationsRoutes)$' -count=1 -race -v` | ✅ `notifications_smoke_test.go` | ✅ green | | 13-02-T2 | 13-02 | 2 | API-04, API-06 | T-13-08, T-13-09, T-13-10, T-13-21 | Notifications read; credentials CRUD encrypted, secret-free, org checks, AI/Discogs resolution order | parity + integration | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestNotificationsRoutes\|TestCredentialsCRUD\|TestCredentialSecretsNeverSerialized\|TestResolveAIConfigPrecedence\|TestDiscogsSharedMirror)$' -count=1 -race -v` | ✅ `credentials_smoke_test.go` | ✅ green | | 13-02-T3 | 13-02 | 2 | API-07 | T-13-18, T-13-19, T-13-20, T-13-27 | Single first owner; register hook; payload without passwords; inspection | parity flow + integration | `go -C ../fonoteka.go test ./plugins/golem15/user -run '^(TestRegisterEventPayload)$' -count=1 -v` ; `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestBootstrapConcurrent\|TestRegisterInvitationListener\|TestInspectInvitation)$' -count=1 -race -v` ; `TestFonotekaNuxtFlows/onboarding` | ✅ `plugins/golem15/user/register_test.go`, `onboarding_smoke_test.go`, `parity/fixtures/nuxt/onboarding.yaml` | ✅ green | | 13-03-T1 | 13-03 | 3 | API-03 | T-13-05 | Own wishlist list/show on both groups with the reservation mask | parity + smoke | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestWishlistOwnListAndShow)$' -count=1 -race -v` | ✅ `wishlist_smoke_test.go` | ✅ green | | 13-03-T2 | 13-03 | 3 | API-03 | T-13-28 | Item writes, prohibited 422, item-added once per path, digest coalescing, share/settings/household | parity + integration | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestWishlistItemAddedOncePerPath\|TestDigestCoalescing\|TestWishlistShareSettingsHousehold)$' -count=1 -race -v` | ✅ `wishlist_notifications_test.go`, `wishlist_smoke_test.go` | ✅ green | | 13-03-T3 | 13-03 | 3 | API-03 | T-13-04, T-13-05, T-13-06, T-13-07, T-13-29, T-13-30 | Subscriptions, secret reservations, reveal, purchase with mail after commit, peers, overlap routes assembled | parity + integration | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestReserveConcurrent\|TestRevealIdempotent\|TestReservationMask\|TestWishlistSubscriptions\|TestPurchaseSideEffects\|TestPurchaseMailAfterCommit\|TestWishlistOverlapRoutesAssembled)$' -count=1 -race -v` | ✅ `wishlist_reservations_test.go`, `wishlist_notifications_test.go` | ✅ green | | 13-03-T4 | 13-03 | 3 | API-03 | T-13-28 | nuxt-wishlist and mcp-wishlist flows, digest rows, publication goldens | parity flow | `go -C ../fonoteka.go test ./parity -run '^(TestFonotekaNuxtFlows\|TestBroadcastGoldens)$' -count=1 -v` | ✅ `parity/fixtures/nuxt/nuxt-wishlist.yaml`, `parity/fixtures/mcp/mcp-wishlist.yaml`, `parity/fixtures/broadcasts/` | ✅ green | | 13-04-T1 | 13-04 | 4 | API-05 | T-13-14 | Export with PHP fputcsv quoting, BOM, header, formula guard | parity + unit | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka/classes/csv -run '^(TestPHPFputcsv)$' -count=1 -v` ; `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestCsvExport)$' -count=1 -race -v` | ✅ `classes/csv/csv_test.go`, `csv_smoke_test.go` | ✅ green | | 13-04-T2 | 13-04 | 4 | API-05 | T-13-12, T-13-13, T-13-15 | Parser truth tables across encodings and limits; private storage; import scope | unit + integration | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka/classes/csv -run '^(TestCsvParserTruthTable\|TestCsvDetectorTruthTable)$' -count=1 -v` ; `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestCsvStoreAndShow\|TestCsvImportScope)$' -count=1 -race -v` | ✅ `classes/csv/truth_table_test.go`, `csv_smoke_test.go` | ✅ green | | 13-04-T3 | 13-04 | 4 | API-05 | T-13-16, T-13-17, T-13-31 | Commit CAS, job rows, cancel, Discogs seam, nuxt-csv flow | parity flow + integration | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestCsvCommitCAS\|TestCsvJobRows\|TestCsvCancel\|TestCsvRowPickSeam)$' -count=1 -race -v` ; `TestFonotekaNuxtFlows/nuxt-csv` | ✅ `csv_smoke_test.go`, `parity/fixtures/nuxt/nuxt-csv.yaml` | ✅ green | | 13-05-T1 | 13-05 | 5 | API-07 | T-13-01, T-13-03, T-13-33 | Public resolve, exact headers, pubfail counter, D-14 layout | parity + smoke | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestPublicResolve\|TestPubfailCounter)$' -count=1 -race -v` | ✅ `public_share_smoke_test.go` | ✅ green | | 13-05-T2 | 13-05 | 5 | API-03, API-07 | T-13-01, T-13-02 | Public albums, facets, field set, per-route buckets | parity + smoke | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestPublicBucketsPerRoute\|TestPublicAlbumFieldSet)$' -count=1 -race -v` | ✅ `routes_bucket_test.go`, `public_share_smoke_test.go` | ✅ green | | 13-05-T3 | 13-05 | 5 | API-07 | T-13-01, T-13-03 | public-anonymous and public-pubfail flows | parity flow | `go -C ../fonoteka.go test ./parity -run '^(TestFonotekaNuxtFlows)$' -count=1 -v` | ✅ `parity/fixtures/nuxt/public-anonymous.yaml`, `public-pubfail.yaml` | ✅ green | | 13-06-T1 | 13-06 | 6 | API-03..API-07 (C-01) | T-13-07, T-13-23 | Route table: groups, scopes, constraints, throttles, Phase 14 routes absent, overlap 404/405 | unit | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestRouteTablePhase13\|TestRouteTablePhase12\|TestFullRouteTableAuthGroupMutualExclusivity)$' -count=1 -race -v` | ✅ `routes_table_phase13_test.go` | ✅ green | | 13-06-T2 | 13-06 | 6 | API-03..API-07 (C-04) | all mitigated T-13 | Request-DTO fuzz over every write route; one subtest per threat | fuzz + integration | `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(FuzzWriteEndpoints\|TestPhase13Threats)$' -count=1 -race -v` ; `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^$' -fuzz '^FuzzWriteEndpoints$' -fuzztime 60s` | ✅ `write_endpoints_fuzz_test.go`, `testdata/fuzz/FuzzWriteEndpoints/` (70 seeds), `phase13_security_test.go` | ✅ green | | 13-06-T3 | 13-06 | 6 | API-03..API-07 | T-13-34, T-13-35, T-13-36 | Unit coverage in both repos and the user plugin, empty bodies, boundaries, fail-closed handlers, the gate, security review, validation sign-off | unit + gate | `go test ./modules/surf ./modules/conga ./modules/lagoon ./modules/tide -run '^(TestOverlapConstraintFallsThrough\|TestOverlapFamilyOfThree\|TestOverlapHeadAndAllow\|TestOverlapFamilyAcrossPlugins\|TestOverlapUnsupportedShapes\|TestUnregisteredKindRefusalAndDelay\|TestValidateRequestProhibitedNested\|TestNormalizePhase13Edges)$' -count=1` ; `go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestPhase13EmptyBodies\|TestPhase13Boundaries\|TestPhase13HandlersFailClosed\|TestPhase09SecurityRoutes)$' -count=1 -race` ; `go -C ../fonoteka.go test ./plugins/golem15/fonoteka/classes/... ./plugins/golem15/fonoteka/controllers/api ./plugins/golem15/fonoteka/middleware ./plugins/golem15/user -run '^(TestPhase13ReservationMask\|TestPhase13PubfailWindow\|TestPhase13SmallHelpers\|TestPhase13NilHandles\|TestCsvPHPCasts\|TestCsvOrderedMap\|TestCsvMappingInput\|TestPublicShareHeadersRewrites429\|TestPublicShareHeadersLeaves200Body\|TestPublicShareHeadersExactBytes\|TestRegisterUserExports)$' -count=1` ; `scripts/check-phase13.sh --self-test && scripts/check-phase13.sh --all && scripts/check-phase13.sh --removal` | ✅ `scripts/check-phase13.sh`, `13-SECURITY-REVIEW.md`, `phase13_controllers_test.go`, `classes/phase13_classes_test.go`, `classes/csv/csv_edges_test.go`, `controllers/api/phase13_controllers_test.go`, `plugins/golem15/user/registration_test.go` | ✅ green | *Status: ✅ green · ❌ red · ⚠️ flaky* ### Coverage (scripts/check-phase13.sh --coverage, 2026-10-03) | Package | Statements | Floor | |---------|------------|-------| | summercms.go `modules/surf` | 85.8% | 80% | | summercms.go `modules/conga` | 92.8% | 80% | | summercms.go `modules/lagoon` | 85.1% | 80% | | summercms.go `modules/tide` | 81.7% | 80% | | fonoteka `classes` | 85.8% | 80% | | fonoteka `classes/csv` | 94.5% | 80% | | fonoteka `controllers/api` | 81.9% | 80% | | fonoteka `middleware` | 100.0% | 80% | | sm-user-plugin `classes` | 88.7% | 80% | | sm-user-plugin `controllers` | 70.6% | 68.8% (its value before Phase 13) | | sm-user-plugin `controllers/registration.go` | every function ≥ 87.5% (RegisterUser 87.5%, the other three 100%) | 80% per function | The fonoteka packages are measured with `-coverpkg` over every test of the plugin, as check-phase12.sh does; each framework package with its own tests. --- ## Wave 0 Requirements - [x] `surf` constraint-aware overlap dispatch, plus a test (blocks every wishlist route) - [x] `conga` unregistered-kind insert while a worker runs, plus a test - [x] `lagoon` `prohibited` rule; tide Content-Disposition date and notification publication masks - [x] `php_parity.sh` `QUEUE_CONNECTION` override; `capture-rules.yaml` `share:wishlist` capture - [x] `fonoteka_reset.php` + `seedFonotekaCase` states: `wishlist`, `csv`, `credentials`, `empty`, `invite-for-register` - [x] `scripts/check-phase13.sh` (copy of the check-phase12 structure) --- ## Manual-Only Verifications | Behavior | Requirement | Why Manual | Test Instructions | |----------|-------------|------------|-------------------| | Re-recording PHP fixtures against the isolated PHP instance | API-03..API-07 | Needs the local PHP stack running | Run `php_parity.sh` recordings per D-12 with the database queue override (done in plans 13-02 to 13-05; replay is automated) | --- ## Validation Sign-Off - [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 - [x] Nyquist compliance set in the frontmatter **Approval:** validated 2026-10-03 (plan 13-06; gate `scripts/check-phase13.sh --all` and `--removal` green)