test(12): persist human verification items as UAT
This commit is contained in:
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
status: testing
|
||||||
|
phase: 12-p-ytarium-api-collections-and-albums
|
||||||
|
source: [12-VERIFICATION.md]
|
||||||
|
started: 2026-10-02T15:08:57Z
|
||||||
|
updated: 2026-10-02T15:08:57Z
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current Test
|
||||||
|
|
||||||
|
number: 1
|
||||||
|
name: Decide on the 12-02 collection-name trimming override
|
||||||
|
expected: |
|
||||||
|
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.
|
||||||
|
awaiting: user response
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
### 1. Decide on the 12-02 collection-name trimming override
|
||||||
|
expected: The plan truth "names stored byte-for-byte including spaces" was wrong about Winter; Go trims like PHP and the byte-compatible contract is met.
|
||||||
|
result: [pending]
|
||||||
|
|
||||||
|
### 2. Read 12-SECURITY-REVIEW.md
|
||||||
|
expected: Every T-12 threat's disposition, test and residual risk reads as acceptable (threats_open: 0; T-12-13 accepted; RC-04 partial by design). Self-performed by the 12-05 executor.
|
||||||
|
result: [pending]
|
||||||
|
|
||||||
|
### 3. Household invitation mail in pl and en in a real mail client
|
||||||
|
expected: Layout, copy and link match the PHP collection_invitation mail; footer reads '© Płytarium.' without the year (documented gap).
|
||||||
|
result: [pending]
|
||||||
|
|
||||||
|
### 4. Read the reworded ROADMAP Phase 12/13/14 and REQUIREMENTS API-01/02/03/07, INTG-01 lines
|
||||||
|
expected: They state the Phase 12 boundary locked in 12-CONTEXT (reservations and public token views in Phase 13, cover-price in Phase 14).
|
||||||
|
result: [pending]
|
||||||
|
|
||||||
|
### 5. Push sm-user-plugin master (c65ab3c, 8697007, c258e9f)
|
||||||
|
expected: origin/master contains c258e9f so a fresh fonoteka.go clone can check out submodule pointer f60c3af.
|
||||||
|
result: [pending]
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
total: 5
|
||||||
|
passed: 0
|
||||||
|
issues: 0
|
||||||
|
pending: 5
|
||||||
|
skipped: 0
|
||||||
|
blocked: 0
|
||||||
|
|
||||||
|
## Gaps
|
||||||
@@ -0,0 +1,266 @@
|
|||||||
|
---
|
||||||
|
phase: 12-p-ytarium-api-collections-and-albums
|
||||||
|
verified: 2026-10-02T15:06:59Z
|
||||||
|
status: human_needed
|
||||||
|
score: 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)
|
||||||
|
covered_files:
|
||||||
|
- ".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"
|
||||||
|
covered_digest: "v2:sha256:8d9eaccb232f77a03e44541c6a9896a3fabeab9aa468d1ec9010c4280d35ca49"
|
||||||
|
covered_files_note: "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."
|
||||||
|
behavior_unverified: 0
|
||||||
|
overrides_applied: 0
|
||||||
|
mvp_mode_note: "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."
|
||||||
|
human_verification:
|
||||||
|
- test: "Decide on the 12-02 backstop truth 'collection names are stored and returned byte-for-byte as sent, including leading and trailing spaces'"
|
||||||
|
expected: "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."
|
||||||
|
why_human: "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: "Read 12-SECURITY-REVIEW.md"
|
||||||
|
expected: "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)"
|
||||||
|
why_human: "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: "Send a household invitation in pl and en (serve with the log or a real mailer) and open the mail in a client"
|
||||||
|
expected: "Layout, copy and link match the PHP collection_invitation mail; the footer reads '© Płytarium.' without the year (documented gap)"
|
||||||
|
why_human: "Tests assert recipient, subject and link only; visual fidelity of the HTML mail needs a human (12-03-SUMMARY D6)."
|
||||||
|
- test: "Read the reworded ROADMAP Phase 12/13/14 and REQUIREMENTS API-01/02/03/07, INTG-01 lines"
|
||||||
|
expected: "They state the Phase 12 boundary locked in 12-CONTEXT (reservations and public token views in Phase 13, cover-price in Phase 14)"
|
||||||
|
why_human: "Planning wording intent (12-01-SUMMARY D7)."
|
||||||
|
- test: "Push sm-user-plugin master (3 commits ahead: c65ab3c, 8697007, c258e9f) to its origin"
|
||||||
|
expected: "origin/master contains c258e9f, so a fresh clone of fonoteka.go can check out the submodule pointer f60c3af refers to"
|
||||||
|
why_human: "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) |
|
||||||
|
|
||||||
|
### Key Link Verification
|
||||||
|
|
||||||
|
| 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:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
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)_
|
||||||
Reference in New Issue
Block a user