Files
summercms/.planning/phases/12-p-ytarium-api-collections-and-albums/12-VALIDATION.md
Jakub Zych 1f4e1e01c4 docs(12-05): sign off the Phase 12 security review, validation and API-01/API-02
- 12-SECURITY-REVIEW.md maps T-12-01..T-12-34 and T-12-SC to a named test
  and 25 removal checks, all seen failing with the protection removed
- 12-VALIDATION.md validated with the final per-task map, nyquist_compliant
- REQUIREMENTS.md: API-01 and API-02 complete
2026-10-02 16:55:29 +02:00

14 KiB

phase, slug, status, nyquist_compliant, wave_0_complete, created, validated, gate
phase slug status nyquist_compliant wave_0_complete created validated gate
12 p-ytarium-api-collections-and-albums validated true true 2026-10-02 2026-10-02 scripts/check-phase12.sh --all

Phase 12 — Validation Strategy

Per-phase validation contract for feedback sampling during execution.


Test Infrastructure

Property Value
Framework Go testing (+ testify assert/require, Go fuzzing), testcontainers Postgres
Config file none (parity/parity_test.go TestMain starts Postgres)
Quick run command cd fonoteka.go && go test ./plugins/golem15/fonoteka/... -short -count=1
Full suite command cd fonoteka.go && go vet ./... && go test ./... -count=1, plus cd summercms.go && go vet ./... && go test ./... -count=1 for framework changes
Parity command `cd fonoteka.go && go test ./parity -run 'TestParityCorpus
Phase gate scripts/check-phase12.sh --self-test && scripts/check-phase12.sh --all (summercms.go); --removal runs the RC mutations of 12-SECURITY-REVIEW.md
Estimated runtime ~180 seconds (full suite, both repos, with containers); the gate's --all about 12 minutes

Sampling Rate

  • After every task commit: quick run command plus go vet in the touched repo
  • After every plan wave: full suite in both repos plus the parity command
  • Before /gsd-verify-work: scripts/check-phase12.sh --all (vet and tests in both repos, the parity corpus with 99 ported routes, the broadcast goldens, both Nuxt flows, check_corpus.go --require-recorded --check-secrets, every named test by exact name, the coverage floors and this file)
  • Max feedback latency: 60 seconds (quick run)

Per-Task Verification Map

Task IDs are <plan>-T<task>. Every row's command was run on 2026-10-02 and passed; scripts/check-phase12.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
12-01-T1 12-01 1 API-01, API-02 T-12-14, T-12-15 Laravel 9 request validation (implicit stop, wildcards, size-typed messages, pl/en catalogs); lagoon.Validate min/between fix keeps user-api bodies unit go test ./modules/lagoon -run '^(TestValidateRequestEmptyArrayStopsAtRequired|TestValidateRequestWildcardNamesIndexedAttribute|TestValidateRequestWildcardWithoutParentAddsNothing|TestValidateRequestStringLengthCountsCharacters|TestValidateRequestBetweenIntegerBoundary|TestValidateRequestNumericPrecision|TestValidateRequestPolishFallsBackToEnglish|TestValidateRequestPresenceSemantics|TestValidateRequestBailAndOrder|TestValidateRequestCustomRuleMessageVerbatim|TestValidateRequestParseRules|TestValidateRequestUploadedFile|TestValidateRequestEmailURLBoolean|TestValidateRequestExistsNeedsDatabase|TestValidateRequestErrorKeysDeclarationOrder|TestValidateNumericRangeMessagePicksFailedBound)$' -count=1 -v ✅ modules/lagoon/validate_request_test.go, validate_test.go ✅ green
12-01-T2 12-01 1 API-01, API-02 T-12-16, T-12-17 Winter upload URLs (URL, PublicURL), webp decode, tide multipart parts with sha256, upload URL and publication date masks unit go test ./modules/lagoon/attach ./modules/tide -run '^(TestFileURLWinterLayout|TestThumbWebP|TestMultipartRecordReplaySendsIdenticalBytes|TestMultipartTamperedPartFileFailsLoad|TestMultipartRejectsInvalidParts|TestMultipartKeepsNonMultipartContentType|TestNormalizeUploadURLMasksRandomParts|TestNormalizeUploadURLReportsWrongShape|TestNormalizePublicationAlbumDates)$' -count=1 -v ✅ modules/lagoon/attach/url_test.go, modules/tide/multipart_test.go ✅ green
12-01-T3 12-01 1 API-01, API-02 T-12-28, T-12-18 beachcomber SearchPage found and query_by_weights; user groups additive (user-api payload unchanged) unit + integration go test ./modules/beachcomber/... -run '^(TestSearchPageUsesPageSearcher|TestSearchPageFallsBackToSearchIDs|TestSearchPageNullEngine|TestTypesenseSearchPageFoundAndWeights|TestTypesenseSearchPageRejectsBadQueries)$' -count=1 -v && go -C ../fonoteka.go test ./plugins/golem15/user/updates -run '^(TestUserGroupsMigration|TestUserGroupsCodesAndRelation)$' -count=1 -v ✅ modules/beachcomber/searchpage_test.go, modules/beachcomber/typesense/searchpage_test.go, plugins/golem15/user/updates/user_groups_test.go ✅ green
12-01-T4 12-01 1 API-01, API-02 — ROADMAP/REQUIREMENTS reworded per D-03, D-04, D-06, D-19, D-20 docs check grep -q 'realtime/channels' .planning/ROADMAP.md && grep -q 'realtime/channels' .planning/REQUIREMENTS.md ✅ ✅ green
12-02-T1 12-02 2 API-01 T-12-01, T-12-03, T-12-12 Token-aware resolver, AccessibleBy narrowing, one-time provisioning under locks, collections list on both groups, per-route scopes (D-26) integration + parity go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestCollectionsIndexBothGroups|TestResolveProvisionsOnce|TestResolvePinnedToken|TestResolveFallbackHasNoKindFilter|TestTokenGroupOneScopePerRoute)$' -count=1 -race -v ✅ plugins/golem15/fonoteka/collections_smoke_test.go, routes_group_test.go ✅ green
12-02-T2 12-02 2 API-01 T-12-05, T-12-29, T-12-30, T-12-19 Collection CRUD, per-album delete (D-26), photos and image uploads, switch with Winter 404 page integration + parity go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestCollectionDeleteRemovesAlbumsOneByOne|TestCollectionPhotoUpload|TestCollectionSwitchRefusals)$' -count=1 -race -v ✅ plugins/golem15/fonoteka/collections_smoke_test.go ✅ green
12-02-T3 12-02 2 API-01 T-12-04, T-12-08, T-12-13 me/context flags (site admin via groups), realtime/channels, owner-only share with crypto/rand token integration + parity go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestMeContextFlags|TestRealtimeChannelsName|TestShareTokenAlphabet|TestShareOwnerOnly)$' -count=1 -race -v ✅ plugins/golem15/fonoteka/collections_smoke_test.go ✅ green
12-03-T1 12-03 3 API-01 T-12-06, T-12-07, T-12-22 Invite mail job enqueued in tx with encrypted token, absent on rollback; accept adds editor and notification integration + parity go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestInvitationMailEnqueuedInTx|TestInvitationAcceptAddsEditor)$' -count=1 -race -v ✅ plugins/golem15/fonoteka/household_smoke_test.go ✅ green
12-03-T2 12-03 3 API-01 T-12-31, T-12-32, T-12-21 Owner-only household management, member removal repairs context, the 409 guard for an unaccepted registration invitation, single accept under concurrency integration + parity go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestRemoveEditorRepairsContext|TestPendingInvitationGuard|TestConcurrentAcceptSingleEditor)$' -count=1 -race -v ✅ plugins/golem15/fonoteka/household_smoke_test.go ✅ green
12-03-T3 12-03 3 API-01 T-12-20 nuxt-collections flow replay; ValidationException envelopes; no raw invitation token in the corpus parity flow go -C ../fonoteka.go test ./parity -run '^(TestFonotekaNuxtFlows|TestParityCorpus|TestCheckCorpusInvitationToken)$' -count=1 -v ✅ parity/fonoteka_flows_test.go, parity/check_corpus_test.go ✅ green
12-04-T1 12-04 4 API-02 T-12-11, T-12-23 Album create on both groups, single created event, album_added notifications, created golden asserted integration + parity go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestAlbumStoreSingleCreatedEvent|TestAlbumAddedNotifiesHousehold|TestAlbumWriteHelpersMatchPHP)$' -count=1 -race -v ✅ plugins/golem15/fonoteka/albums_smoke_test.go ✅ green
12-04-T2 12-04 4 API-02 T-12-33, T-12-09, T-12-10, T-12-24 Album CRUD, ratings, uploads with image guard, SSRF-guarded cover fetches after commit, bulk single summary, stats/value/missing/sync integration + parity go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestCoverImportAfterCommit|TestManualCoverReasons|TestAlbumPhotoUpload|TestBulkSingleSummaryEvent|TestRatingUpsertConcurrent|TestAlbumValueFormatting)$' -count=1 -race -v ✅ plugins/golem15/fonoteka/albums_smoke_test.go ✅ green
12-04-T3 12-04 4 API-02 T-12-02, T-12-34 Search SQL escaping and Scout-exact recount, lookups, nuxt-albums flow, 99 ported routes integration + parity go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestAlbumSearchSQLEscaping|TestAlbumSearchTypesenseRecount)$' -count=1 -race -v && go -C ../fonoteka.go test ./parity -run '^(TestFonotekaNuxtFlows|TestParityCorpus|TestBroadcastGoldens)$' -count=1 -v ✅ plugins/golem15/fonoteka/albums_smoke_test.go, parity/broadcast_goldens_test.go ✅ green
12-05-T1 12-05 5 API-02 T-12-02, T-12-28 D-18 leak test (stale-moved, mis-scoped, soft-deleted, removed-editor with the resolve race, token-pin) with the D-19 total, short page, recount and 1000-id cap on both groups security go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^TestSearchLeak$' -count=1 -race -v ✅ plugins/golem15/fonoteka/search_leak_test.go, fake_engine_test.go ✅ green
12-05-T2 12-05 5 API-01, API-02 T-12-01..T-12-34 Route-table one-scope test (D-10, D-26), request-DTO fuzz over all 41 write endpoints (C-02) with a seed corpus per route, one subtest per threat security + fuzz go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^(TestRouteTablePhase12|FuzzWriteEndpoints|TestPhase12Threats)$' -count=1 -race -v && go -C ../fonoteka.go test ./plugins/golem15/fonoteka -run '^$' -fuzz '^FuzzWriteEndpoints$' -fuzztime 60s ✅ plugins/golem15/fonoteka/routes_table_phase12_test.go, write_endpoints_fuzz_test.go, testdata/fuzz/FuzzWriteEndpoints/ (41 seeds), phase12_security_test.go ✅ green
12-05-T3 12-05 5 API-01, API-02 T-12-25, T-12-26, T-12-27 Full unit coverage (80% floor per package), PHP truth tables for the validator and request casts, fail-closed gate with removal mutations, security review and validation sign-off unit + gate go test ./modules/lagoon ./modules/lagoon/attach ./modules/tide -run '^(TestValidateRulesMatchLaravel|TestPHPFloatStringMatchesPHPCast|TestValidateRequestGoTypedValues|TestValidateRequestMimesSniffing|TestValidateRequestUploadedFileFromHeader|TestValidateRequestRuleBuilders|TestValidateRequestCustomLinePlaceholders|TestValidateRequestExistsRule|TestThumbBrokenSourceServesPlaceholder|TestThumbModesAndFormats|TestBucketAndURLEdges|TestMultipartPartEdges|TestNormalizeMaskEdges|TestCoverageReportHelpers)$' -count=1 && go -C ../fonoteka.go test ./plugins/golem15/fonoteka/... ./plugins/golem15/user/classes -run '^(TestPHPValueCasts|TestValidLaravelEmailRFC|TestParseTracklistTextMatchesPHP|TestParseAddedDateMatchesPHP|TestMatchNormalizersMatchPHP|TestFormatValueTotalMatchesPHP|TestSyncCursorAndCarbonTime|TestSearchHelpers|TestDecodeInput|TestRequestCasts|TestWinterErrorWriters|TestAlbumSyncRoute|TestAlbumsMissingRoute|TestStylesRoutes|TestHouseholdInvitationsIndexRoute|TestHandlersFailClosedOnDatabaseErrors|TestHandlerRequestPaths|TestUserGroupCodesAndHasGroupCode|TestUserClassHelpers)$' -count=1 && scripts/check-phase12.sh --self-test && scripts/check-phase12.sh --all ✅ scripts/check-phase12.sh, the test files named in 12-05-SUMMARY.md, 12-SECURITY-REVIEW.md ✅ green

Status: ⬜ to do · ✅ green · ❌ red · ⚠️ flaky


Wave 0 Requirements

  • Re-record the HttpException cases under APP_DEBUG=false (accept 410, switch 404, household/members 404, invitations 404, guard 409) — D-21 (12-02-T2, 12-03-T1, 12-03-T2)
  • tide request multipart parts + url/thumb_url disk-name normalizer + publication date masking (framework) — 12-01-T2
  • Seed hook fonoteka with alice, bob (editor), an outsider and personal tokens (reuse the id:outsider recipe from Phase 11) — 12-02-T1
  • Fake beachcomber engine with scripted ids and found for D-18/D-19 — smoke in 12-04-T3 (scriptedEngine), full suite in 12-05-T1 (fake_engine_test.go)

Manual-Only Verifications

Behavior Requirement Why Manual Test Instructions
Recording new PHP fixtures against the isolated PHP instance API-01, API-02 Needs the running PHP reference instance and capture tooling Follow the Phase 2 tide capture rules (private 0600 vars, no live tokens in git), then run check_corpus.go --require-recorded --check-secrets (done for 12-02..12-04; the gate's --parity stage re-runs the check)

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 < 60s (the -short package runs; the testcontainers suites take minutes and run per wave and in the gate)
  • The frontmatter sets nyquist_compliant to true

Approval: validated 2026-10-02 by plan 12-05 (scripts/check-phase12.sh --all prints "phase12 all passed"; --removal reports every RC row failing as required). The manual-only row above is collected at /gsd-verify-work.