Files
summercms/.planning/phases/13-p-ytarium-api-wishlist-notifications-csv-credentials-public/13-VALIDATION.md
Jakub Zych 931c02db31 docs(13-06): sign off the Phase 13 security review and validation
- 13-SECURITY-REVIEW.md: every T-13 threat with its strictest severity and
  disposition, protecting code, named tests and the 31 removal checks,
  all failing as required; the CSV export logging fix and the Phase 9
  route inventory update
- 13-VALIDATION.md: per-task map 13-01-T1 to 13-06-T3 all green, the gate
  command, coverage per package, Wave 0 ticked, status validated
- REQUIREMENTS.md: API-03, API-04, API-06 and API-07 complete
- deferred-items.md: 13-06 findings (process-wide job dispatcher, scalar
  mapping body, tmpfs quota)
2026-10-03 11:43:39 +02:00

15 KiB

phase, slug, status, nyquist_compliant, wave_0_complete, created, validated, gate
phase slug status nyquist_compliant wave_0_complete created validated gate
13 p-ytarium-api-wishlist-notifications-csv-credentials-public validated true true 2026-10-02 2026-10-03 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
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 <plan>-T<task>. 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

  • surf constraint-aware overlap dispatch, plus a test (blocks every wishlist route)
  • conga unregistered-kind insert while a worker runs, plus a test
  • lagoon prohibited rule; tide Content-Disposition date and notification publication masks
  • php_parity.sh QUEUE_CONNECTION override; capture-rules.yaml share:wishlist capture
  • fonoteka_reset.php + seedFonotekaCase states: wishlist, csv, credentials, empty, invite-for-register
  • 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

  • All tasks have <automated> 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 compliance set in the frontmatter

Approval: validated 2026-10-03 (plan 13-06; gate scripts/check-phase13.sh --all and --removal green)