Files
summercms/.planning/phases/12-p-ytarium-api-collections-and-albums/12-VERIFICATION.md
2026-10-02 17:08:58 +02:00

24 KiB
Raw Blame History

phase, verified, status, score, covered_files, covered_digest, covered_files_note, behavior_unverified, overrides_applied, mvp_mode_note, human_verification
phase verified status score covered_files covered_digest covered_files_note behavior_unverified overrides_applied mvp_mode_note human_verification
12-p-ytarium-api-collections-and-albums 2026-10-02T15:06:59Z human_needed 5/5 roadmap success criteria verified; plan truths 67/68 verified, 1 backstop truth contradicted by the recorded PHP contract (routed to human for an override decision)
.planning/phases/12-p-ytarium-api-collections-and-albums/12-01-PLAN.md
.planning/phases/12-p-ytarium-api-collections-and-albums/12-01-SUMMARY.md
.planning/phases/12-p-ytarium-api-collections-and-albums/12-02-PLAN.md
.planning/phases/12-p-ytarium-api-collections-and-albums/12-02-SUMMARY.md
.planning/phases/12-p-ytarium-api-collections-and-albums/12-03-PLAN.md
.planning/phases/12-p-ytarium-api-collections-and-albums/12-03-SUMMARY.md
.planning/phases/12-p-ytarium-api-collections-and-albums/12-04-PLAN.md
.planning/phases/12-p-ytarium-api-collections-and-albums/12-04-SUMMARY.md
.planning/phases/12-p-ytarium-api-collections-and-albums/12-05-PLAN.md
.planning/phases/12-p-ytarium-api-collections-and-albums/12-05-SUMMARY.md
docs/database/attachments.md
docs/database/casts-and-validation.md
docs/services/localization.md
docs/services/parity-testing.md
docs/services/search.md
docs/services/storage.md
go.mod
go.sum
modules/beachcomber/README.md
modules/beachcomber/engines.go
modules/beachcomber/searchable.go
modules/beachcomber/searchpage_test.go
modules/beachcomber/typesense/engine.go
modules/beachcomber/typesense/searchpage_test.go
modules/lagoon/README.md
modules/lagoon/attach/thumb.go
modules/lagoon/attach/url_test.go
modules/lagoon/validate.go
modules/lagoon/validate_request.go
modules/lagoon/validate_request_test.go
modules/lagoon/validate_rules.go
modules/lagoon/validate_rules_test.go
modules/lagoon/validate_test.go
modules/phrasebook/README.md
modules/phrasebook/lang/en/validation.yaml
modules/phrasebook/lang/pl/validation.yaml
modules/tide/README.md
modules/tide/centrifugo_golden.go
modules/tide/diff.go
modules/tide/fixture.go
modules/tide/flow.go
modules/tide/multipart.go
modules/tide/multipart_test.go
modules/tide/normalize.go
modules/tide/normalize_upload_test.go
modules/tide/record.go
modules/tide/replay.go
modules/tide/variables.go
scripts/check-phase10.1.sh
scripts/check-phase10.sh
scripts/check-phase11.sh
scripts/check-phase12.sh
v2:sha256:8d9eaccb232f77a03e44541c6a9896a3fabeab9aa468d1ec9010c4280d35ca49 verification.fingerprint refuses paths outside the summercms.go root, so the fonoteka.go and sm-user-plugin implementation files (the bulk of this phase) are not in the digest; their state at verification is fonoteka.go f60c3af and sm-user-plugin c258e9f. 0 0 ROADMAP marks Phase 12 mode: mvp, but the goal is not a User Story and no 12-*-PLAN.md carries one. Following the Phase 1/3/5/8/9/10/11 precedent, the five ROADMAP success criteria are the contract, User Flow Coverage is derived from them, and plan must_haves are supporting evidence.
test expected why_human
Decide on the 12-02 backstop truth 'collection names are stored and returned byte-for-byte as sent, including leading and trailing spaces' Accept an override: the recorded PHP fixture POST___fonoteka_api_v1_collections_jwt.yaml sends ' New Shelf ' and PHP answers 'New Shelf' (Winter trimStringAttributes); Go trims in Collection.BeforeSave and replays the fixture green. The plan truth was wrong about Winter; the roadmap's byte-compatible contract is met. The truth is tagged verification: backstop and its literal text is false in the code; only a human can accept the deviation as an override.
test expected why_human
Read 12-SECURITY-REVIEW.md Every T-12 threat's disposition, test and residual risk reads as acceptable (threats_open: 0; T-12-13 accepted; RC-04 is partial by design) The review was self-performed by the 12-05 executor with no separate reviewer agent; 12-05-SUMMARY D7 asks for a human read.
test expected why_human
Send a household invitation in pl and en (serve with the log or a real mailer) and open the mail in a client Layout, copy and link match the PHP collection_invitation mail; the footer reads '© Płytarium.' without the year (documented gap) Tests assert recipient, subject and link only; visual fidelity of the HTML mail needs a human (12-03-SUMMARY D6).
test expected why_human
Read the reworded ROADMAP Phase 12/13/14 and REQUIREMENTS API-01/02/03/07, INTG-01 lines They state the Phase 12 boundary locked in 12-CONTEXT (reservations and public token views in Phase 13, cover-price in Phase 14) Planning wording intent (12-01-SUMMARY D7).
test expected why_human
Push sm-user-plugin master (3 commits ahead: c65ab3c, 8697007, c258e9f) to its origin origin/master contains c258e9f, so a fresh clone of fonoteka.go can check out the submodule pointer f60c3af refers to The executors were told not to push; pushing is a user action and a core-plugin repo change.

Phase 12: Płytarium API — Collections and Albums Verification Report

Phase Goal: Collections and Albums endpoints are ported with byte-compatible request/response shapes, including active-context switching, editor invitations, ratings, manual cover URL and Discogs cover import, and search. Verified: 2026-10-02T15:06:59Z Status: human_needed Re-verification: No — initial verification

MVP note: ROADMAP marks this phase mode: mvp, but the goal is not a User Story and no plan carries one. Following the precedent of Phases 1, 3, 5, 8, 9, 10 and 11, the five ROADMAP success criteria are the contract.

User Flow Coverage

Step (from SC) Expected Evidence in codebase Status
Nuxt user manages collections, switches context, reads me/context and its channel PHP bodies on both auth groups 23 collections-area routes in fonoteka.go/plugins/golem15/fonoteka/routes.go lines 50-102, 153-168; manifest cases ported; TestFonotekaNuxtFlows/nuxt-collections PASS (verifier run) VERIFIED
Owner invites, member accepts, owner manages members PHP bodies and DB effects 7 household routes (routes.go 105-114); classes/invitation_service.go; parity cases ported; nuxt-collections flow covers invite/accept/remove VERIFIED
User creates, edits, rates, photographs, bulk-adds, syncs albums with covers PHP bodies and broadcasts 36 album/lookup routes (routes.go 45-47, 71-94, 170-195); TestFonotekaNuxtFlows/nuxt-albums and TestBroadcastGoldens/{created,updated,deleted,bulk} PASS VERIFIED
User searches albums; the index cannot leak Items and total re-gated in SQL classes/album_search.go 202-257; TestSearchLeak PASS (verifier run, also under -race) VERIFIED

Goal Achievement

Roadmap Success Criteria (the contract)

# Success criterion Status Evidence
SC1 Collections CRUD with photos and image, switch and me/context flags, opaque channel from GET realtime/channels, editor invitation/acceptance and members, owner-only collection/share show/update/regenerate pass the parity diff ✓ VERIFIED Every PHP route of this surface (routes.php 77, 94, 109-128, 262-268 and token twins 458-466) is mounted in Go and status: ported in parity/manifest.yaml with recorded cases (e.g. me/context 7, POST collections 7, POST household/invitations 8, accept 5). Verifier run of go test ./parity -run TestParityCorpus: "recorded 171/171 passing 99 failing 0 unrecorded 0 pending 72". check_corpus.go --manifest parity/manifest.yaml --require-recorded --check-secrets exit 0. me_context_controller.go struct carries exactly the ten recorded flags and no id or channel. Public token views are pending and owned by Phase 13 SC5.
SC2 Albums CRUD, ratings, photo upload, manual cover URL and Discogs cover import on create/bulk (cover_urls), plus sync/stats/value/missing/bulk pass the parity diff ✓ VERIFIED All albums routes of routes.php 228-259 except match/apply/recognize/import (Phase 14) are ported (POST albums 17 cases, PUT 12, photos 9, rating 8, search 25, sync 5, missing 7, bulk 6). classes/cover_importer.go:94 uses fetchguard AllowHostsMode; classes/manual_cover_fetcher.go:119 uses PublicOnlyMode; store (albums_controller.go:445) and bulk (albums_bulk_controller.go) import after commit. Cover-price is pending and named in Phase 14 SC4; reservations in Phase 13 SC1.
SC3 Album search treats Typesense as a pre-filter re-gated in SQL; the total is the re-gated SQL count over at most 1000 engine ids, proven by a security test ✓ VERIFIED album_search.go: page ids re-gated through ScopedAlbums (= AlbumsAccessibleBy + active collection, album_queries.go:15), total recounted through the same scope over fetchEngineKeys capped at searchMaxTotalResults = 1000, pages of 250. TestSearchLeak (stale-moved, mis-scoped, soft-deleted, removed-editor and race, token-pin, short-page, recount, cap, engine-error; both auth groups; page ids and meta.total/last_page compared exactly; search setting on) PASS in the verifier's run, also under -race. Query shape matches PHP query_by order and weights 10,10,5,5,3,1,3,3.
SC4 Artists/genres/styles lookups pass the parity diff ✓ VERIFIED GET artists (5 jwt, 2 token cases), GET/POST styles, POST genres ported on both groups (routes.go 44-47, 191-195), all passing in the corpus run.
SC5 Request-DTO fuzz over every write endpoint asserts unknown and server-owned keys are never persisted ✓ VERIFIED FuzzWriteEndpoints (write_endpoints_fuzz_test.go:878) enumerates write routes from surf.BuildRouter(...).Routes() (routes_table_phase12_test.go:171-175), fails if a write route lacks a spec or a spec matches no route, requires exactly 41, sends every server-owned key with hostile values and diffs Postgres snapshots; 41 committed seeds. Verifier run PASS (plain and -race).

Score: 5/5 roadmap success criteria; plan truths 67/68 verified (0 present-but-behavior-unverified; 1 backstop truth routed to human).

Plan must_haves (supporting evidence)

Every package that holds a named test of 12-VALIDATION.md was run in full by the verifier: summercms.go lagoon, lagoon/attach, tide, beachcomber, beachcomber/typesense, phrasebook (all ok); fonoteka.go plugins/golem15/fonoteka/... and plugins/golem15/user/... (all ok); parity TestParityCorpus, TestFonotekaNuxtFlows, TestBroadcastGoldens, TestSchemaMatchesPHPSnapshot, TestUserAPINuxtFlows (all PASS).

Plan Truths Verified Notes
12-01 framework gaps, user groups, wording 14 14 lagoon.ValidateRequest (validate_request.go), pl/en validation.yaml (pl lacks after_or_equal), attach.PublicURL/File.URL, webp import in thumb.go, tide.MultipartBoundary/Parts, beachcomber.PageSearcher/SearchPage/QueryByWeights (typesense sends query_by_weights), sm-user-plugin user_groups migration, UserGroupCodes/HasGroupCode, Groups json:"-"; user-api fixtures replay unchanged. ROADMAP/REQUIREMENTS wording present.
12-02 active context, collections, share 16 15 Resolver with FOR UPDATE, AccessibleBy pin, one inv.scope per token route (TestRouteTablePhase12), per-album delete, Winter 404 page, Winter upload layout, share tokens from crypto/rand. Backstop truth "names stored byte-for-byte including leading/trailing spaces" is contradicted: Go trims (Collection.BeforeSave) because the recorded PHP answer trims (" New Shelf " to "New Shelf"). Routed to human for an override.
12-03 household and invitations 14 14 Encrypted-token River mail in the write tx, sha256 at rest, accept under FOR UPDATE, notification emit, 409 guard, nuxt-collections flow. The planned 422 envelopes are, per recorded PHP, Winter 500 pages; Go reproduces the recording.
12-04 albums, search, lookups 14 14 Write path under WithoutBroadcasting, one event per write, bulk summary, covers after commit, Scout-exact recount. Deviation: engine-path items are re-gated by access plus active collection only, not "plus filters"; PHP AlbumSearchService.php:210-217 applies only the extra scope and collection_id in the Scout query() closure, so the Go code is the PHP contract.
12-05 security proof, coverage, gate 10 10 Leak test, route-table test, fuzz, 26 T-12 subtests, gate script. Coverage re-measured by the verifier with scripts/check-phase12.sh --coverage (exit 0): beachcomber 84.3, typesense 95.9, lagoon 84.4, attach 87.7, tide 81.4, fonoteka classes 83.6, controllers/api 81.7, user classes 88.7, user updates 86.2. Backstop truth (no data race under -race): verifier ran FuzzWriteEndpoints seeds, TestRatingUpsertConcurrent, TestConcurrentAcceptSingleEditor, TestResolveProvisionsOnce, TestBulkSingleSummaryEvent, TestSearchLeak with -race; all PASS, no race report.

Prohibitions (all test-tier, all with wired enforcement)

Prohibition Enforcing test(s) Status
User-groups change alters no user-plugin response TestUserAPINuxtFlows, user-api parity routes (16 ported) ✓ (verifier run PASS)
No invented validation message TestValidateRulesMatchLaravel (162 recorded PHP cases) ✓ (lagoon package PASS)
me/context returns no id or channel TestMeContextFlags; struct inspected ✓
owner_name never exposes a full foreign email collections_smoke_test.go (owner_name cases) ✓
Collection delete leaves no searchable album, every member keeps a context TestCollectionDeleteRemovesAlbumsOneByOne ✓
Raw invitation token readable only in the mail TestPhase12Threats/T-12-07, TestInvitationMailEnqueuedInTx, check_corpus 64-hex rule ✓
Accept/removal never deletes the user's collections TestRemoveEditorRepairsContext, TestPhase12Threats ✓
Editor or token cannot manage household TestPhase12Threats/T-12-31, T-12-04 ✓
Search never returns or counts an inaccessible album TestSearchLeak ✓
Failed cover never loses the album TestCoverImportAfterCommit ✓
album_added never reaches the actor or outsiders TestAlbumAddedNotifiesHousehold ✓
Leak test cannot pass by disabling search Code read: setSearchSetting(t, gdb, true), ids and totals compared exactly ✓
No threat marked mitigated without a removal-proven test scripts/check-phase12.sh --removal (25 RC rows) ✓ by script inspection and the orchestrator-reported run; not re-run by the verifier because it mutates tracked source files while a concurrent session works in the repo

Required Artifacts

Artifact Status Details
modules/lagoon/validate_request.go ✓ VERIFIED func ValidateRequest( present; used by fonoteka controllers (validateInput)
modules/phrasebook/lang/pl/validation.yaml ✓ VERIFIED Winter catalog port; loaded as lagoon::validation.*
modules/lagoon/attach/thumb.go ✓ VERIFIED webp import, PublicURL, File.URL, broken-image fallback
modules/tide/multipart.go ✓ VERIFIED Parts and fixed boundary; used by replay of 30+ multipart fixtures
modules/beachcomber/searchable.go ✓ VERIFIED PageSearcher; called by album_search.go:225,268,277
sm-user-plugin updates/202610020001_create_user_groups.go, classes/user_groups.go ✓ VERIFIED Read by classes/gates.go:31 IsSiteAdmin
fonoteka classes/active_collection.go, access.go, share_service.go, controllers/api/http_errors.go ✓ VERIFIED Wired from routes.go handlers
fonoteka classes/invitation_service.go, jobs.go, notification_service.go ✓ VERIFIED Wired from household routes
fonoteka classes/album_write_service.go, album_search.go, cover_importer.go, image_guard.go ✓ VERIFIED Wired from album handlers
parity/fixtures/nuxt/nuxt-collections.yaml, nuxt-albums.yaml ✓ VERIFIED Replayed green
search_leak_test.go, write_endpoints_fuzz_test.go, routes_table_phase12_test.go, phase12_security_test.go, scripts/check-phase12.sh ✓ VERIFIED Run by the verifier (coverage stage of the gate re-run)
From To Via Status
routes.go collections_controller.go one handler value on both groups WIRED
collections_controller.go classes/access.go Scopes(classes.AccessibleBy(userID, token)) WIRED
me_context_controller / gates.go sm-user-plugin user_groups.go userclasses.HasGroupCode(..., "admin") WIRED
album_search.go beachcomber.SearchPage page and recount WIRED
album_search.go AlbumsAccessibleBy via ScopedAlbums for items and total WIRED
cover_importer.go fetchguard AllowHostsMode WIRED
manual_cover_fetcher.go fetchguard PublicOnlyMode WIRED
albums/bulk handlers lighthouse WithoutBroadcasting[models.Album] then one Emit WIRED
FuzzWriteEndpoints surf router surf.BuildRouter(...).Routes() WIRED

Data-Flow Trace (Level 4)

Artifact Data Source Real data Status
albums/search response data, meta.total engine ids then Postgres ScopedAlbums Find/Count yes ✓ FLOWING
me/context ten flags gates over users, groups, organisations, credentials yes ✓ FLOWING
realtime/channels collection:<id> Resolve yes ✓ FLOWING

Behavioral Spot-Checks

Behavior Command Result Status
Parity corpus, flows, goldens, schema, user API go test ./parity -run '^(TestParityCorpus|TestFonotekaNuxtFlows|TestBroadcastGoldens|TestSchemaMatchesPHPSnapshot|TestUserAPINuxtFlows)$' -count=1 -v ok; 171/171 recorded, 99 passing, 0 failing; both flows and four goldens PASS ✓ PASS
Corpus recorded and secret-free go run ./parity/check_corpus.go --manifest parity/manifest.yaml --require-recorded --check-secrets recorded 171/171, exit 0 ✓ PASS
Security tests go test ./plugins/golem15/fonoteka -run '^(TestSearchLeak|TestRouteTablePhase12|FuzzWriteEndpoints|TestPhase12Threats)$' -count=1 -v all PASS, no SKIP ✓ PASS
Concurrency under race detector same package, six concurrency tests and fuzz seeds, -race all PASS ✓ PASS
Framework packages go test ./modules/lagoon/... ./modules/tide/... ./modules/beachcomber/... ./modules/phrasebook/... -cover all ok ✓ PASS
App plugins go test ./plugins/golem15/fonoteka/... ./plugins/golem15/user/... -cover all ok ✓ PASS
Vet both repos, docs tree go vet ./... (both), go test ./cmd/summer -run TestDocsTree clean, ok ✓ PASS
Coverage floors scripts/check-phase12.sh --coverage "phase12 coverage passed" ✓ PASS

Probe Execution

Step 7c: no scripts/*/tests/probe-*.sh exist and no plan declares one. The phase gate scripts/check-phase12.sh is the analogue; its --coverage stage was run by the verifier (exit 0) and --all/--removal passed in the orchestrator's run after 12-05.

Requirements Coverage

Requirement Source plans Description Status Evidence
API-01 12-01, 12-02, 12-03, 12-05 Collections CRUD, context switch (me/context flags plus channel), invitations and acceptance, owner-only share ✓ SATISFIED SC1 evidence; REQUIREMENTS.md marks Complete
API-02 12-01, 12-04, 12-05 Albums CRUD, ratings, photo upload and manual cover URL, cover_urls import, lookups, search re-gated in items and total ✓ SATISFIED SC2-SC5 evidence; REQUIREMENTS.md marks Complete

No orphaned requirements: REQUIREMENTS.md maps only API-01 and API-02 to Phase 12.

Anti-Patterns Found

File Line Pattern Severity Impact
(none) — No TBD/FIXME/XXX or TODO/placeholder in Phase 12 code; the only hits are detector regexes in scripts/check-phase11.sh:714 and check-phase12.sh:508,555 ℹ️ Info none
sm-user-plugin submodule — 3 commits ahead of origin/master; fonoteka.go pointer f60c3af references unpushed c258e9f (fonoteka.go itself has no remote) ⚠️ Warning A fresh clone cannot check out the submodule until it is pushed; listed as a human item
fonoteka.go/plugins/golem15/fonoteka/controllers/api/albums_bulk_controller.go 128-160 bulkRows comment says an object of rows keeps its key order, but sortedKeys sorts the keys (numeric, then string), while PHP array_values keeps insertion order ℹ️ Info Only reachable when albums is a JSON object with out-of-order keys; Nuxt sends an array
.planning/ROADMAP.md 616 Phase 12 section still says "Plans: 4/5 plans executed" while the phase list and progress table say 5/5 Complete ℹ️ Info Planning-doc consistency only; left untouched because another session is editing ROADMAP
12-SECURITY-REVIEW.md RC-04 — Removing AlbumsAccessibleBy from ScopedAlbums fails only the token-pin and removed-editor cases (the active-collection filter and GORM's soft-delete filter still cover the others) ℹ️ Info Documented by design; defence in depth holds

Human Verification Required

1. Override decision on the 12-02 encoding truth

Test: Compare the 12-02 backstop truth "collection names are stored and returned byte-for-byte as sent ... including leading and trailing spaces" with parity/fixtures/routes/POST___fonoteka_api_v1_collections_jwt.yaml. Expected: PHP answers "name":"New Shelf" for " New Shelf ", and Go matches it. If you accept, add to this file's frontmatter:

overrides:
  - must_have: "Edge (API-01 encoding): collection names are stored and returned byte-for-byte as sent (Winter has no TrimStrings or ConvertEmptyStringsToNull), including leading and trailing spaces."
    reason: "Winter Model::setAttribute trims string attributes ($trimStringAttributes); the recorded PHP response trims, and API parity is the acceptance test (CLAUDE.md rule 6)."
    accepted_by: "<name>"
    accepted_at: "<ISO timestamp>"

Why human: The literal truth is false in code; only a human can accept the deviation.

2. Security review read

Test: Read 12-SECURITY-REVIEW.md. Expected: Dispositions, tests and residual risks are acceptable. Why human: Self-performed by the executor.

3. Invitation mail rendering

Test: Send a pl and an en invitation and open both mails in a client. Expected: Matches the PHP mail; footer without year is acceptable. Why human: Visual fidelity.

4. Planning wording

Test: Read the reworded ROADMAP Phase 12-14 criteria and REQUIREMENTS API-01/02/03/07, INTG-01. Expected: They state the boundary locked in 12-CONTEXT. Why human: Intent.

5. Push sm-user-plugin

Test: git -C ../fonoteka.go/plugins/golem15/user push origin master. Expected: origin holds c258e9f. Why human: User action on a core-plugin repo.

Gaps Summary

No gaps. Every roadmap success criterion is backed by running code that the verifier exercised: all Phase 12 PHP routes on both auth groups are mounted and pass the recorded parity diff (99 ported, 0 failing, 171/171 recorded), both Nuxt flows and all four broadcast goldens replay, the search leak test asserts both items and totals against a poisoned engine, and the write fuzz enumerates all 41 write routes from the assembled router. Deferred surfaces (reservations, public token views, invitation preview, cover-price, match/recognize/import) are pending in the manifest and named in Phases 13 and 14. The status is human_needed because of one backstop truth whose literal text the recorded PHP contract contradicts, plus four human-judgment items carried over from the summaries.


Verified: 2026-10-02T15:06:59Z Verifier: Claude (gsd-verifier)