test(13): persist human verification items as UAT
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
---
|
||||
status: testing
|
||||
phase: 13-p-ytarium-api-wishlist-notifications-csv-credentials-public
|
||||
source: [13-VERIFICATION.md]
|
||||
started: 2026-10-03T10:04:54Z
|
||||
updated: 2026-10-03T10:04:54Z
|
||||
---
|
||||
|
||||
## Current Test
|
||||
|
||||
number: 1
|
||||
name: Decide on the D-03 queue-name deviation (underscored River queues)
|
||||
expected: |
|
||||
Accept the override — fonoteka_csv_import, fonoteka_csv_match, fonoteka_wishlist_digest
|
||||
(River rejects dotted queue names; kinds, dotted summer_jobs labels, args and delay unchanged) —
|
||||
or ask for a different spelling before any production row carries these queues.
|
||||
awaiting: user response
|
||||
|
||||
## Tests
|
||||
|
||||
### 1. Decide on the D-03 queue-name deviation (underscored River queues)
|
||||
expected: Accept the override (classes/job_contract.go:33-46), or request a different spelling before production rows exist. STATE.md still lists it as pending confirmation.
|
||||
result: [pending]
|
||||
|
||||
### 2. Accept the conga unregistered-kind backstop truth on code evidence, or ask for a race test
|
||||
expected: modules/conga/conga.go clientFor returns insertOnlyLocked() for every kind not in m.jobs (no path to the worker client); TestUnregisteredKindWithWorker/WithoutWorker cover both states but nothing races a worker start against a Dispatch.
|
||||
result: [pending]
|
||||
|
||||
### 3. WR-04 — share-link subscribers keep access after the share is disabled or regenerated
|
||||
expected: Either amend 13-SECURITY-REVIEW.md T-13-03 / T-13-06 residual risk (currently "None known"), or open a product decision for revocation (a PHP behaviour change, needs a decision note).
|
||||
result: [pending]
|
||||
|
||||
### 4. Triage code-review warnings WR-01, WR-02, WR-03 (13-REVIEW-DISPOSITION.md all open)
|
||||
expected: Each fixed, deferred with a reason, or skipped. WR-02 (CSV mapping/row/cancel vs commit race) matters before Phase 14 starts CSV workers.
|
||||
result: [pending]
|
||||
|
||||
### 5. Push sm-user-plugin master to origin
|
||||
expected: origin/master contains 8a65890 (5 commits ahead now), so a fresh clone of fonoteka.go can check out its submodule pointer. Use ssu.
|
||||
result: [pending]
|
||||
|
||||
## Summary
|
||||
|
||||
total: 5
|
||||
passed: 0
|
||||
issues: 0
|
||||
pending: 5
|
||||
skipped: 0
|
||||
blocked: 0
|
||||
|
||||
## Gaps
|
||||
@@ -0,0 +1,251 @@
|
||||
---
|
||||
phase: 13-p-ytarium-api-wishlist-notifications-csv-credentials-public
|
||||
verified: 2026-10-03T10:02:27Z
|
||||
status: human_needed
|
||||
score: 5/5 roadmap success criteria verified; plan truths 87/89 verified, 1 deviates from its literal text (D-03 queue names, routed to human for an override decision), 1 backstop truth holds by control flow with no dedicated race test (insufficient_spec, routed to human)
|
||||
covered_files:
|
||||
- ".planning/phases/13-p-ytarium-api-wishlist-notifications-csv-credentials-public/13-01-PLAN.md"
|
||||
- ".planning/phases/13-p-ytarium-api-wishlist-notifications-csv-credentials-public/13-01-SUMMARY.md"
|
||||
- ".planning/phases/13-p-ytarium-api-wishlist-notifications-csv-credentials-public/13-02-PLAN.md"
|
||||
- ".planning/phases/13-p-ytarium-api-wishlist-notifications-csv-credentials-public/13-02-SUMMARY.md"
|
||||
- ".planning/phases/13-p-ytarium-api-wishlist-notifications-csv-credentials-public/13-03-PLAN.md"
|
||||
- ".planning/phases/13-p-ytarium-api-wishlist-notifications-csv-credentials-public/13-03-SUMMARY.md"
|
||||
- ".planning/phases/13-p-ytarium-api-wishlist-notifications-csv-credentials-public/13-04-PLAN.md"
|
||||
- ".planning/phases/13-p-ytarium-api-wishlist-notifications-csv-credentials-public/13-04-SUMMARY.md"
|
||||
- ".planning/phases/13-p-ytarium-api-wishlist-notifications-csv-credentials-public/13-05-PLAN.md"
|
||||
- ".planning/phases/13-p-ytarium-api-wishlist-notifications-csv-credentials-public/13-05-SUMMARY.md"
|
||||
- ".planning/phases/13-p-ytarium-api-wishlist-notifications-csv-credentials-public/13-06-PLAN.md"
|
||||
- ".planning/phases/13-p-ytarium-api-wishlist-notifications-csv-credentials-public/13-06-SUMMARY.md"
|
||||
- "modules/conga/conga.go"
|
||||
- "modules/lagoon/validate_rules.go"
|
||||
- "modules/surf/overlap.go"
|
||||
- "modules/surf/router.go"
|
||||
- "modules/tide/centrifugo_golden.go"
|
||||
- "modules/tide/diff.go"
|
||||
- "scripts/check-phase13.sh"
|
||||
covered_digest: "v2:sha256:78681e983350c9a3f226b9d36a65ce24aa1fc92a9387d72798ffc30d5aee688c"
|
||||
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 40c575e (clean tree) and sm-user-plugin 8a65890 (clean tree)."
|
||||
behavior_unverified: 0
|
||||
overrides_applied: 0
|
||||
mvp_mode_note: "ROADMAP marks Phase 13 mode: mvp, but the goal is not a User Story (user-story.validate: valid=false) and no 13-*-PLAN.md carries one. Following the Phase 1/3/5/8-12 precedent, the five ROADMAP success criteria are the contract, User Flow Coverage is derived from them, and plan must_haves are supporting evidence."
|
||||
deferred:
|
||||
- truth: "Wishlist match/apply-release routes, ai-credential/test and discogs-credential/test pass the parity diff"
|
||||
addressed_in: "Phase 14"
|
||||
evidence: "Phase 14 SC4: 'the wishlist wishlist/albums/{id}/match and apply-release routes, discogs-credential/test and the CSV row-edit Discogs pick (selected_discogs_id) pass the parity diff'; SC5: 'ai-credential/test passes the parity diff, and the AI resolver's admin tier uses the backend global vision model'"
|
||||
- truth: "CSV import/match jobs and the wishlist digest job are worked (bodies, digest mail, queue-row delete)"
|
||||
addressed_in: "Phase 14"
|
||||
evidence: "Phase 14 SC1: 'The CSV import write job and the self-redispatching Discogs match job ... both complete correctly'; SC2: 'The wishlist digest job coalesces a 30-minute window and deletes its queue row on completion'"
|
||||
- truth: "Notification pruning (fonoteka:prune-notifications)"
|
||||
addressed_in: "Phase 14"
|
||||
evidence: "Phase 14 SC6: 'the ported oauth-client/prune-notifications/reindex commands all run correctly'"
|
||||
human_verification:
|
||||
- test: "Decide on the D-03 queue-name deviation: the 13-01 truth pins queues fonoteka.csv.import, fonoteka.csv.match, fonoteka.wishlist.digest; the code (classes/job_contract.go:33-46) uses fonoteka_csv_import, fonoteka_csv_match, fonoteka_wishlist_digest because River rejects dotted queue names. STATE.md line 486 still records this as 'pending user confirmation'."
|
||||
expected: "Accept an override (kinds, labels, args and delay are unchanged and are what Phase 14 workers and summer_jobs parity consume; the dotted names survive as summer_jobs labels), or ask for a different spelling before any production row carries these queues. D-03 is marked costly to reverse."
|
||||
why_human: "D-03 is a signed-off, costly-to-reverse contract; only the user can accept a change to it."
|
||||
- test: "Decide on the 13-01 backstop truth 'a Dispatch of an unregistered kind racing a worker start never routes through the worker client'"
|
||||
expected: "Accept on the control-flow evidence: modules/conga/conga.go clientFor returns insertOnlyLocked() for every kind not in m.jobs, with no path to m.worker; TestUnregisteredKindWithWorker/WithoutWorker cover both states but no test races a worker start against a Dispatch. Or ask for a race test."
|
||||
why_human: "The truth is tagged verification: backstop; code reading is not the explicit test evidence a backstop truth requires."
|
||||
- test: "Read code-review warning WR-04 and decide whether 13-SECURITY-REVIEW.md T-13-03 and T-13-06 should record the residual risk"
|
||||
expected: "A user who subscribed via wishlist/token/{token}/subscribe keeps peer-album read, reserve and notifications after the owner disables or regenerates the share (classes/wishlist_resolver.go ReservableWishlistIDs). This matches PHP AlbumReservationService::reservableWishlistIds, so parity holds; the security review currently says 'None known'. Either amend the residual-risk cells or open a product decision for revocation (a PHP behaviour change)."
|
||||
why_human: "Product/security judgment; changing behaviour would break the parity rule and needs a decision note."
|
||||
- test: "Triage code-review warnings WR-01, WR-02, WR-03 (13-REVIEW-DISPOSITION.md: all still 'open')"
|
||||
expected: "Each is fixed, deferred with a reason, or skipped. None blocks a Phase 13 success criterion: WR-01 is latent (no catch-all under the family prefix today; the app boots and routes dispatch), WR-02 is a mapping/row/cancel-vs-commit race PHP shares (commit-vs-commit CAS holds, TestCsvCommitCAS), WR-03 is a double-click purchase double-notify. WR-02 matters before Phase 14 starts CSV workers."
|
||||
why_human: "Disposition of review findings is a human decision; the review disposition file records none."
|
||||
- test: "Push sm-user-plugin master (5 commits ahead of origin/master, including d80d799 RegisterEvent.Payload and 8a65890) to its origin"
|
||||
expected: "origin/master contains 8a65890, so a fresh clone of fonoteka.go can check out its submodule pointer"
|
||||
why_human: "Pushing a core-plugin repo is a user action; executors were told not to push."
|
||||
---
|
||||
|
||||
# Phase 13: Płytarium API — wishlist, notifications, CSV, credentials, public routes Verification Report
|
||||
|
||||
**Phase Goal:** The remaining core API surface — wishlist, notifications, CSV import/export, per-user/org credentials, and onboarding/public/invitation routes — is ported with byte-compatible shapes and their own public rate-limit buckets.
|
||||
**Verified:** 2026-10-03T10:02:27Z
|
||||
**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. As in Phases 1, 3, 5 and 8 to 12, the five ROADMAP success criteria are the contract.
|
||||
|
||||
## User Flow Coverage
|
||||
|
||||
| Step (from SC) | Expected | Evidence in codebase | Status |
|
||||
| --- | --- | --- | --- |
|
||||
| Nuxt user curates, shares, subscribes to, reserves, reveals and purchases wishlist items | PHP bodies, bell rows, digest-queue rows, publications | 30 wishlist routes (fonoteka.go `routes.go` 137-179, 283-287); `TestFonotekaNuxtFlows/nuxt-wishlist` and `/mcp-wishlist` PASS (verifier run); `TestBroadcastGoldens/{wishlist-item-added,reservation-revealed,wishlist-purchased}` in the gate | VERIFIED |
|
||||
| User reads the bell list, unread count, marks one or all read | PHP bodies; Winter 404 page for foreign ids | `routes.go` 123-126; `notifications_controller.go`; recorded 404 fixtures are Winter HTML; `TestNotificationsRoutes` PASS | VERIFIED |
|
||||
| User uploads a CSV, polls, remaps, edits rows, commits, cancels; exports on both groups | PHP bodies, queued job rows equal to PHP's | `routes.go` 54-67, 294; `TestFonotekaNuxtFlows/nuxt-csv` (job rows diffed against `nuxt-csv.rows.json`) PASS | VERIFIED |
|
||||
| User stores personal/org AI and Discogs credentials | Encrypted at rest, PHP bodies, resolver order | `routes.go` 198-204; `lagoon.Encrypted` model fields with `json:"-"`; `TestCredentialsCRUD`, `TestCredentialSecretsNeverSerialized`, `TestResolveAIConfigPrecedence`, `TestDiscogsSharedMirror` PASS | VERIFIED |
|
||||
| Anonymous visitor opens onboarding, an invitation, a shared collection or wishlist | No auth, PHP headers, own throttles and pubfail lockout | `routes.go` 306-334; `TestFonotekaNuxtFlows/{onboarding,public-anonymous,public-pubfail}` PASS; `TestPublicBucketsPerRoute` PASS | VERIFIED |
|
||||
|
||||
## Goal Achievement
|
||||
|
||||
### Roadmap Success Criteria (the contract)
|
||||
|
||||
| # | Success criterion | Status | Evidence |
|
||||
| --- | --- | --- | --- |
|
||||
| SC1 | Wishlist items, subscriptions, `public-wishlist/{token}` views, reservations (reserve/reveal) and purchase/digest triggers pass the parity diff; match/apply-release are Phase 14; the digest trigger queues its job here | ✓ VERIFIED | All 28 JWT and 4 token-group wishlist routes are mounted (`routes.go` 137-179, 283-287) and `status: ported` with recorded cases (reserve 5 cases incl. 422/409/409/404; reveal 4; purchase 3; token subscribe GET 5). The 3 `public-wishlist` routes are ported (`routes.go` 332-334). Purchase enqueues `WishlistPurchasedMailArgs` in the transaction (`TestPurchaseMailAfterCommit`, `TestPurchaseSideEffects` PASS). Item-added runs from `albumAddedCallback` and upserts `wishlist_digest_queue` with `ON CONFLICT`, dispatching `WishlistDigestArgs` (1800 s) only on first insert (`TestDigestCoalescing` PASS). The verifier ran the `nuxt-wishlist` and `mcp-wishlist` flows: PASS. `wishlist/albums/{id}/match` and `apply-release` are `pending` in the manifest and absent from the router (`TestRouteTablePhase13/phase14-routes-absent`). |
|
||||
| SC2 | Notifications list, unread count and mark-read (one and all) pass the parity diff; pruning is a Phase 14 command | ✓ VERIFIED | 4 routes (`routes.go` 123-126), all ported, with recorded cases (`{id}/read` 200, 200, 404, 404; the 404s are the Winter production HTML page). `TestNotificationsRoutes` PASS (50-row cap, empty list, foreign id). No prune route exists. |
|
||||
| SC3 | CSV import as a multi-step session (store, show/poll, mapping patch, per-row edit, commit, cancel) and CSV export on both groups pass the parity diff; commit and mapping enqueue jobs whose bodies are Phase 14 | ✓ VERIFIED | 6 import routes plus export on JWT and token groups (`routes.go` 54-67, 294), all ported (for example store 9 cases, show 11, rows 11, commit 11). `csv_import_service.go` dispatches `CsvMatchArgs` (line 571) and `CsvImportArgs` (line 860) through conga with their labels. Commit is an `UPDATE ... WHERE status='preview'` compare-and-swap (`TestCsvCommitCAS` PASS). `jobs.go` registers only the invitation and purchase-mail workers, so the CSV jobs stay queued (`TestCsvJobRows` PASS). The `selected_discogs_id` branch answers `discogs_unavailable` through the seam (`TestCsvRowPickSeam` PASS). The export fixture body (BOM, HEADERS, formula guard, fputcsv quoting) replays green. The verifier ran `nuxt-csv` (job rows diffed against PHP's `golem15_apparatus_jobs` dump): PASS. |
|
||||
| SC4 | Per-user and per-org Discogs/AI credentials CRUD store encrypted values, honor the org-lock flag, and resolve correctly; the `/test` routes are Phase 14 | ✓ VERIFIED | The 7 CRUD routes (`routes.go` 198-204) are ported (POST ai-credential 7 cases, POST discogs-credential 10, POST org-ai-credential 6). Secrets are `lagoon.Encrypted` with `json:"-"` (`models/*_credential.go`), written only via `lagoon.NewEncrypted` (`credential_write_service.go` 98, 130, 225). `ResolveAIConfig` matches PHP `AiConfigResolver::resolve` tier by tier: admin, then org-lock for org members, then user, then org, else `ErrNoAICredential`. `ResolveDiscogsConfig` (`gates.go` 136) follows admin/env, then user, then org. `AIAllowed` applies the org-lock on `me/context`. `TestResolveAIConfigPrecedence`, `TestCredentialsCRUD`, `TestCredentialSecretsNeverSerialized` and `TestDiscogsSharedMirror` PASS. The `/test` routes are pending and absent from the router. Info: `ResolveAIConfig` has no route caller yet. In PHP its only caller is `RecognizeApiController` (Phase 14), so this matches. |
|
||||
| SC5 | Onboarding, public and invitation-inspection routes, including `public/{token}`, `.../albums`, `.../albums/{id}`, are reachable without auth and enforce their own public rate-limit buckets | ✓ VERIFIED | The onboarding group (`routes.go` 308) and invitation inspection (315) carry only `throttle:10,1`, with no auth. The public group (325-334) carries `public.share-headers` and nothing else. The resolve routes carry `throttle:10,1`; the four albums routes carry `throttle:fonoteka-public-token` (60/min, `pubtok:<token>`) and then `throttle:fonoteka-public-ip` (120/min, client IP), exactly as in routes.php 399-428 and Plugin.php `RateLimiter::for`. The middleware sets `X-Robots-Tag` and `Cache-Control: no-store, private` and rewrites 429 bodies to JSON, as the PHP `PublicShareHeaders` does. The `pubfail:<ip>` counter is shared by all 6 routes. `TestPublicBucketsPerRoute`, `TestPublicResolve`, `TestPubfailCounter` (including goroutine concurrency), `TestPublicAlbumFieldSet` and the flows `public-anonymous`, `public-pubfail` (10x 404 then 429) and `onboarding` PASS in the verifier run. |
|
||||
|
||||
**Score:** 5/5 roadmap success criteria verified (0 present, behavior-unverified).
|
||||
|
||||
### Plan must-haves (supporting)
|
||||
|
||||
89 plan truths across 13-01 to 13-06. The gate's `--named` stage ran each by exact name with skips refused, and `-race` was on for the fonoteka package. The verifier re-ran 22 of the load-bearing named tests plus the parity corpus and every Nuxt flow:
|
||||
|
||||
| Group | Status | Notes |
|
||||
| --- | --- | --- |
|
||||
| 13-01 framework (surf overlap, conga workerless kinds, lagoon `prohibited`, tide masks, parity scaffolding, planning rewording) | 15/17 VERIFIED, 1 deviation, 1 insufficient_spec | **Deviation:** the job-contract truth names dotted queues, but the code uses underscores because River refuses dots. Kinds, labels, args and delay match. Routed to human with an override suggestion. **insufficient_spec:** the backstop race truth holds by control flow (`clientFor` has no worker path for unregistered kinds), but no race test exists. Routed to human. |
|
||||
| 13-02 notifications, credentials, onboarding, invitation inspect, sm-user-plugin Payload and exports | 18/18 VERIFIED | `TestBootstrapConcurrent`, `TestRegisterInvitationListener` and `TestRegisterEventPayload` pass (gate). The sm-user-plugin diff `c258e9f..HEAD` is additive: `events.go` gains only `Payload`, no exported func is removed, and `/register` replays in `TestUserAPINuxtFlows`. The backstop truth on org-less provisioning is covered by `credentials_smoke_test.go:186-198`. |
|
||||
| 13-03 wishlist | 18/18 VERIFIED | The backstop truth on concurrent first-read provisioning is covered by `wishlist_smoke_test.go` subtest `concurrent-first-reads`. |
|
||||
| 13-04 CSV | 15/15 VERIFIED | The backstop truth on export ordering and header columns is proven by the parity replay of the PHP-recorded export bodies (`GET___fonoteka_api_v1_export_csv_jwt*.yaml`). |
|
||||
| 13-05 public | 11/11 VERIFIED | The backstop truth on concurrent pubfail is covered by the goroutine block in `TestPubfailCounter`. |
|
||||
| 13-06 tests, gate, sign-off | 10/10 VERIFIED | `FuzzWriteEndpoints` and `TestPhase13Threats` pass under `-race` in the gate. Coverage floors are met (gate log). The `--removal` mutation evidence (RC rows) is the 13-06 executor's run; it is not part of `--all`, and the verifier did not re-run it because it edits tracked source. |
|
||||
|
||||
### Prohibitions (all `verification: test`)
|
||||
|
||||
Each of the 17 prohibitions has a wired, passing test, so none are flagged:
|
||||
|
||||
| Prohibition | Enforcing evidence |
|
||||
| --- | --- |
|
||||
| No Phase 13 job worked or faked (D-04), for CSV and digest | `TestJobContractDispatchWhileWorkerRuns`, `TestCsvJobRows`, `TestDigestCoalescing`; `jobs.go` registers no CSV or digest worker |
|
||||
| Overlap dispatcher keeps every route in the table | `TestOverlappingConstrainedRoutes`, `TestRouteTablePhase13` |
|
||||
| Framework code does not name the application | No new mentions in surf/conga/lagoon/tide or the 4 docs pages. The existing mentions in `lagoon/*_test.go` and `tide/capture_test.go` come from Phase 10.2. `TestDocsTree` PASS |
|
||||
| Payload carries no password | `events.go` doc and `TestRegisterEventPayload` |
|
||||
| sm-user-plugin additive only | diff review above, plus `TestUserAPINuxtFlows` |
|
||||
| No secret in a response, log, job arg or fixture | `TestCredentialSecretsNeverSerialized`; `check_corpus --check-secrets` (gate) |
|
||||
| `/test`, match and apply-release routes not mounted | `TestRouteTablePhase13/phase14-routes-absent`; manifest `pending` |
|
||||
| Owner cannot learn the reserver before reveal | `TestReservationMask`, `TestPhase13ReservationMask` |
|
||||
| No mail from a rolled-back purchase | `TestPurchaseMailAfterCommit` |
|
||||
| `selected_discogs_id` never fabricates | `TestCsvRowPickSeam` |
|
||||
| Uploaded CSV is not publicly served | The bucket is `file://./storage/app` under the `fonoteka-csv/` prefix, and the static uploads root is `storage/app/uploads/public` (`config/storage.yaml`). Also covered by `TestPhase13Threats` T-13-1x |
|
||||
| Public view exposes no private fields | `TestPublicAlbumFieldSet` (12-key allow-list) |
|
||||
| Disabled, regenerated or wrong-kind token does not resolve | `TestPublicResolve`, `TestPhase13Threats/T-13-03`, the `public-anonymous` regenerate step |
|
||||
| No threat marked mitigated without a removal-checked test; pending routes never count as passing | gate `--evidence`, `corpus_coverage` (passing 157 + pending 14 = 171) |
|
||||
|
||||
### Deferred Items
|
||||
|
||||
| # | Item | Addressed In | Evidence |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | Wishlist match/apply-release, `ai-credential/test`, `discogs-credential/test`, CSV `selected_discogs_id` success | Phase 14 | SC4, SC5 |
|
||||
| 2 | CSV import/match and wishlist digest job bodies | Phase 14 | SC1, SC2 (JOBS-02, JOBS-03) |
|
||||
| 3 | `fonoteka:prune-notifications` | Phase 14 | SC6 |
|
||||
|
||||
### Required Artifacts
|
||||
|
||||
| Artifact | Status | Details |
|
||||
| --- | --- | --- |
|
||||
| `summercms.go/modules/surf/overlap.go` | ✓ VERIFIED | Contains `SetPathValue`, wired from `router.go` compile, and the fonoteka overlap pairs dispatch (`TestWishlistOverlapRoutesAssembled`) |
|
||||
| `summercms.go/modules/conga/conga.go` | ✓ VERIFIED | `ErrUnregisteredKindQueue`, `clientFor`/`knownQueues` |
|
||||
| `fonoteka.go/.../classes/job_contract.go` | ✓ VERIFIED (queue spelling deviation) | Kinds, labels and args are pinned by `TestJobContract`; queues use underscores |
|
||||
| `fonoteka.go/.../controllers/api/{notifications,credentials,wishlist_*,csv_*,public_share,onboarding}_controller.go` | ✓ VERIFIED | Substantive (75 to 395 lines each), routed in `routes.go`, and exercised by the parity replay |
|
||||
| `fonoteka.go/.../classes/{ai_config_resolver,wishlist_resolver,reservations,wishlist_notifications,csv_import_service,public_share}.go`, `classes/csv/*` | ✓ VERIFIED | Wired from the handlers. `ResolveAIConfig` has no Phase 13 caller; it matches PHP, where the only caller is recognize (Phase 14) |
|
||||
| `fonoteka.go/plugins/golem15/user/classes/events.go` | ✓ VERIFIED | `Payload map[string]any`; listener wired in `plugin.go` |
|
||||
| `fonoteka.go/parity/fixtures/nuxt/{nuxt-wishlist,nuxt-csv,onboarding,public-anonymous,public-pubfail}.yaml`, `mcp/mcp-wishlist.yaml`, `*.rows.json` | ✓ VERIFIED | Present, replayed green. Fixtures carry Winter HTML 404 pages and Laravel header shapes, which points to PHP recording |
|
||||
| `summercms.go/scripts/check-phase13.sh` | ✓ VERIFIED | `EXPECTED_PORTED=157`, fail-closed detectors; the orchestrator's `--all` run exited 0 |
|
||||
|
||||
### Key Link Verification
|
||||
|
||||
| From | To | Via | Status |
|
||||
| --- | --- | --- | --- |
|
||||
| `routes.go` | surf overlap families | 4 PHP wishlist overlap pairs on one router | WIRED (`TestWishlistOverlapRoutesAssembled` PASS) |
|
||||
| `notification_service.go` `albumAddedCallback` | `wishlist_notifications.go` | `NotifyWishlistItemAdded` on the insert tx | WIRED (`TestWishlistItemAddedOncePerPath` in gate) |
|
||||
| `wishlist_notifications.go` / `csv_import_service.go` | `job_contract.go` | conga `Dispatch` with labels and queues | WIRED |
|
||||
| `plugin.go` | sm-user-plugin `RegisterEvent` | `HandleRegisterEvent` listener | WIRED (`TestRegisterInvitationListener`, onboarding flow) |
|
||||
| `public_share_controller.go` | `share_service.go` `ResolvePublic` | after the pubfail check | WIRED |
|
||||
| `routes.go` public albums routes | `plugin.go` `Buckets()` | `throttle:fonoteka-public-token`, `throttle:fonoteka-public-ip` | WIRED (`TestPublicBucketsPerRoute`) |
|
||||
| credential write service | `lagoon.NewEncrypted` | api_key and token | WIRED |
|
||||
|
||||
### Behavioral Spot-Checks (verifier's own runs; TMPDIR/GOTMPDIR on the home disk, FORCE_COLOR unset)
|
||||
|
||||
| Behavior | Command | Result | Status |
|
||||
| --- | --- | --- | --- |
|
||||
| Parity corpus replays against Go | `go test -count=1 ./parity -run '^(TestFonotekaNuxtFlows\|TestParityCorpus)$'` | `recorded 171/171 passing 157 failing 0 unrecorded 0 pending 14`; ok 44.4s | ✓ PASS |
|
||||
| All 8 Nuxt flows | same run, `-v` | nuxt-collections, nuxt-albums, onboarding, nuxt-wishlist, mcp-wishlist, nuxt-csv, public-anonymous, public-pubfail all PASS | ✓ PASS |
|
||||
| 22 load-bearing named tests | `go test -count=1 ./plugins/golem15/fonoteka -run '^(TestRouteTablePhase13\|...\|TestPhase13Threats)$'` | all 22 PASS, ok 20.1s | ✓ PASS |
|
||||
| Pending routes are only Phase 14 or out of scope | manifest parse | 14 pending: 4 Phase 13 to 14 (D-01, D-02) plus 10 album/oauth/recognize routes outside this phase | ✓ PASS |
|
||||
| Full gate | orchestrator `scripts/check-phase13.sh --all` | exit 0 (`gate-all.log`) | ✓ PASS (orchestrator evidence) |
|
||||
|
||||
### Probe Execution
|
||||
|
||||
Step 7c: no `scripts/*/tests/probe-*.sh` is declared by the phase. The phase gate `scripts/check-phase13.sh --all` is the runnable check (orchestrator run, exit 0). `--removal` was not re-run because it mutates tracked source.
|
||||
|
||||
### Requirements Coverage
|
||||
|
||||
| Requirement | Source Plan | Description | Status | Evidence |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| API-03 | 13-01, 13-03, 13-05, 13-06 | Wishlist items, subscriptions, public-wishlist, reservations, purchase and digest triggers | ✓ SATISFIED | SC1 |
|
||||
| API-04 | 13-01, 13-02, 13-06 | Notifications list, unread, mark read | ✓ SATISFIED | SC2 |
|
||||
| API-05 | 13-01, 13-04, 13-06 | CSV session and export on both groups | ✓ SATISFIED | SC3 |
|
||||
| API-06 | 13-01, 13-02, 13-06 | Credentials CRUD, encryption, org-lock, resolution | ✓ SATISFIED | SC4 |
|
||||
| API-07 | 13-01, 13-02, 13-05, 13-06 | Onboarding, public, invitation inspection with public buckets | ✓ SATISFIED | SC5 |
|
||||
|
||||
All 5 IDs appear in plan frontmatter and are marked Complete in REQUIREMENTS.md (lines 78-82, 217-221). No orphaned requirement maps to Phase 13.
|
||||
|
||||
### Anti-Patterns Found
|
||||
|
||||
| File | Line | Pattern | Severity | Impact |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `fonoteka.go/.../classes/csv_import_service.go` | 519-587, 639-705, 889-901 | Status check before an unlocked write (WR-02) | ⚠️ Warning | A mapping, row or cancel write can race a commit and leave a second import job queued. PHP has the same race. It matters once Phase 14 starts the workers |
|
||||
| `fonoteka.go/.../controllers/api/wishlist_albums_controller.go` | 366-383 | Purchase re-check outside the tx (WR-03) | ⚠️ Warning | A double submit sends duplicate bell rows and mail jobs |
|
||||
| `summercms.go/modules/surf/overlap.go` | 224-288 | Method-less family pattern (WR-01) | ⚠️ Warning | Latent boot failure when a family sits beside a method-specific catch-all; does not affect fonoteka today |
|
||||
| `13-SECURITY-REVIEW.md` | T-13-03, T-13-06 rows | Residual risk "None known" (WR-04) | ⚠️ Warning | Under-states the PHP-parity subscriber-retains-access behaviour |
|
||||
| `classes/job_contract.go` | 33-46 | D-03 queue spelling differs from the signed-off text | ℹ️ Info | Forced by River; pending user confirmation per STATE.md:486 |
|
||||
| debt markers | none | No TBD, FIXME or XXX in Phase 13 files | none | none |
|
||||
|
||||
### Human Verification Required
|
||||
|
||||
#### 1. D-03 queue-name deviation
|
||||
**Test:** Compare `classes/job_contract.go` queues (`fonoteka_csv_import`, `fonoteka_csv_match`, `fonoteka_wishlist_digest`) with D-03's dotted names.
|
||||
**Expected:** Accept an override, or choose another River-valid spelling, before production rows carry them.
|
||||
**Why human:** D-03 is a costly, signed-off contract, and STATE.md still lists it as pending user confirmation.
|
||||
|
||||
Suggested override if accepted:
|
||||
```yaml
|
||||
overrides:
|
||||
- must_have: "the job contract lives in one file, classes/job_contract.go, and TestJobContract pins every string: ... queues fonoteka.csv.import, fonoteka.csv.match, fonoteka.wishlist.digest, mail ..."
|
||||
reason: "River rejects dotted queue names; the dotted names remain the summer_jobs labels (PHP parity), kinds/args/delay unchanged; queues spelled with underscores"
|
||||
accepted_by: "<name>"
|
||||
accepted_at: "<ISO timestamp>"
|
||||
```
|
||||
|
||||
#### 2. Backstop truth: unregistered-kind dispatch racing a worker start
|
||||
**Test:** Read `modules/conga/conga.go` `clientFor`.
|
||||
**Expected:** Every unregistered kind reaches `insertOnlyLocked()`, and no branch returns `m.worker`. Accept, or ask for a race test.
|
||||
**Why human:** A backstop truth requires explicit test evidence; this one only has control-flow evidence.
|
||||
|
||||
#### 3. WR-04 residual risk
|
||||
**Test:** Read 13-REVIEW.md WR-04 next to 13-SECURITY-REVIEW.md T-13-03 and T-13-06.
|
||||
**Expected:** Amend the residual-risk cells, or record a product decision on revoking token subscribers.
|
||||
**Why human:** This is a security and product judgment, and any change departs from PHP parity.
|
||||
|
||||
#### 4. Review findings disposition
|
||||
**Test:** Set a disposition for WR-01, WR-02 and WR-03 (and the IN items) in 13-REVIEW-DISPOSITION.md.
|
||||
**Expected:** Each is fixed, deferred with a reason, or skipped. WR-02 should be resolved before the Phase 14 CSV workers.
|
||||
**Why human:** Triage decision.
|
||||
|
||||
#### 5. Push sm-user-plugin
|
||||
**Test:** `git -C fonoteka.go/plugins/golem15/user push` (5 commits ahead of origin/master).
|
||||
**Expected:** origin contains 8a65890.
|
||||
**Why human:** Pushing a core-plugin repo is a user action.
|
||||
|
||||
### Gaps Summary
|
||||
|
||||
No blocking gaps. All five roadmap success criteria hold in the code and in the verifier's own runs:
|
||||
- 58 Phase 13 routes are mounted and ported.
|
||||
- The parity corpus passes 157 of 171 recorded routes with 0 failing; the 14 pending are the 4 deliberate Phase 14 routes plus 10 routes outside this phase.
|
||||
- Every Phase 13 Nuxt and MCP flow replays green.
|
||||
- Credentials are encrypted, and the resolvers follow PHP tier by tier.
|
||||
- The public surface carries PHP's exact throttle buckets, headers and pubfail lockout.
|
||||
|
||||
The four review warnings do not defeat a success criterion:
|
||||
- WR-01 is latent.
|
||||
- WR-02 and WR-03 are concurrency edges outside the recorded contract; WR-02 is shared with PHP.
|
||||
- WR-04 matches PHP, so the issue is a documentation gap.
|
||||
|
||||
Status is `human_needed` for five reasons: the signed-off D-03 queue spelling still needs the user's confirmation, one backstop truth has only control-flow evidence, the WR-04 residual risk needs a decision, the review findings have no disposition, and the sm-user-plugin commits are unpushed.
|
||||
|
||||
---
|
||||
|
||||
_Verified: 2026-10-03T10:02:27Z_
|
||||
_Verifier: Claude (gsd-verifier)_
|
||||
Reference in New Issue
Block a user