test(14): verification report and human verification items as UAT
This commit is contained in:
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
status: testing
|
||||||
|
phase: 14-domain-jobs-and-external-integrations
|
||||||
|
source: [14-VERIFICATION.md]
|
||||||
|
started: 2026-10-04T01:13:37Z
|
||||||
|
updated: 2026-10-04T01:13:37Z
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current Test
|
||||||
|
|
||||||
|
number: 1
|
||||||
|
name: Decide CR-01 (stale whole-row album save after slow Discogs I/O)
|
||||||
|
expected: |
|
||||||
|
Either fix before Phase 15 cutover (a column-scoped save that still runs checkAlbumModelRules and the broadcast hooks) or accept it as a known risk. The verifier recommends fixing it.
|
||||||
|
awaiting: user response
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
### 1. Decide CR-01 (stale whole-row album save after slow Discogs I/O)
|
||||||
|
expected: Fix before Phase 15 (recommended) or accept as a known risk. PHP saves only dirty columns, so in PHP a concurrent edit survives; in Go it is silently reverted.
|
||||||
|
result: [pending]
|
||||||
|
|
||||||
|
### 2. Accept or reject the two 14-03 PHP-over-plan overrides
|
||||||
|
expected: dry_run writes covers and catalog fields (stages only DRAFT_FIELDS); a second identical apply re-imports the covers. If accepted, add the suggested override entries from the 14-VERIFICATION.md Gaps Summary.
|
||||||
|
result: [pending]
|
||||||
|
|
||||||
|
### 3. Live vendor smoke test (Discogs, OpenAI, Anthropic)
|
||||||
|
expected: Results match PHP for the same inputs: covers download from i.discogs.com, prices are stored, and recognised albums come back, for both user credentials and the admin vision model.
|
||||||
|
result: [pending]
|
||||||
|
|
||||||
|
### 4. Triage review warnings WR-01..WR-06
|
||||||
|
expected: Each warning gets a disposition in 14-REVIEW-DISPOSITION.md. WR-02 (feedback upload extension) and WR-05 (import-settings drops models) are resolved before Phase 15.
|
||||||
|
result: [pending]
|
||||||
|
|
||||||
|
### 5. Admin screens and embed.js widget in a browser
|
||||||
|
expected: The golem AI models list and form (key write-only), the feedback settings screen and the read-only submissions list render and save. The widget loads its config, submits (202) and hides.
|
||||||
|
result: [pending]
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
total: 5
|
||||||
|
passed: 0
|
||||||
|
issues: 0
|
||||||
|
pending: 5
|
||||||
|
skipped: 0
|
||||||
|
blocked: 0
|
||||||
|
|
||||||
|
## Gaps
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
---
|
||||||
|
phase: 14-domain-jobs-and-external-integrations
|
||||||
|
verified: 2026-10-04T01:11:09Z
|
||||||
|
status: human_needed
|
||||||
|
score: 6/6 roadmap success criteria verified; plan truths 54/56 verified, 2 deviate from their literal text to keep PHP behaviour (14-03 dry_run and second-apply covers, routed to human for an override decision)
|
||||||
|
covered_files:
|
||||||
|
- ".planning/phases/14-domain-jobs-and-external-integrations/14-01-PLAN.md"
|
||||||
|
- ".planning/phases/14-domain-jobs-and-external-integrations/14-01-SUMMARY.md"
|
||||||
|
- ".planning/phases/14-domain-jobs-and-external-integrations/14-02-PLAN.md"
|
||||||
|
- ".planning/phases/14-domain-jobs-and-external-integrations/14-02-SUMMARY.md"
|
||||||
|
- ".planning/phases/14-domain-jobs-and-external-integrations/14-03-PLAN.md"
|
||||||
|
- ".planning/phases/14-domain-jobs-and-external-integrations/14-03-SUMMARY.md"
|
||||||
|
- ".planning/phases/14-domain-jobs-and-external-integrations/14-04-PLAN.md"
|
||||||
|
- ".planning/phases/14-domain-jobs-and-external-integrations/14-04-SUMMARY.md"
|
||||||
|
- ".planning/phases/14-domain-jobs-and-external-integrations/14-05-PLAN.md"
|
||||||
|
- ".planning/phases/14-domain-jobs-and-external-integrations/14-05-SUMMARY.md"
|
||||||
|
- ".planning/phases/14-domain-jobs-and-external-integrations/14-06-PLAN.md"
|
||||||
|
- ".planning/phases/14-domain-jobs-and-external-integrations/14-06-SUMMARY.md"
|
||||||
|
- "cmd/summer/main.go"
|
||||||
|
- "cmd/summer/parity.go"
|
||||||
|
- "examples/hello/main.go"
|
||||||
|
- "internal/build/build.go"
|
||||||
|
- "modules/beachcomber/searchable.go"
|
||||||
|
- "modules/beachcomber/typesense/engine.go"
|
||||||
|
- "modules/conga/job.go"
|
||||||
|
- "modules/fetchguard/client.go"
|
||||||
|
- "modules/fetchguard/fetch.go"
|
||||||
|
- "modules/fetchguard/ip.go"
|
||||||
|
- "modules/fetchguard/policy.go"
|
||||||
|
- "modules/sunscreen/sunscreen.go"
|
||||||
|
- "modules/surf/cors.go"
|
||||||
|
- "modules/tide/normalize.go"
|
||||||
|
- "modules/tide/upstream.go"
|
||||||
|
- "modules/tide/upstream_proxy.go"
|
||||||
|
- "scripts/check-phase14.sh"
|
||||||
|
covered_digest: "v2:sha256:63033691c194f6c817c36b8fe27cc414ecb1affd35099a92cdf9844275fd1b2e"
|
||||||
|
covered_files_note: "verification.fingerprint covers only paths under the summercms.go root, so the fonoteka.go, sm-golem-plugin and sm-feedback-plugin implementation files (most of this phase) are not in the digest. Their state at verification: fonoteka.go cd393e4, sm-golem-plugin 6646d10, sm-feedback-plugin 3057719, all with clean trees."
|
||||||
|
behavior_unverified: 0
|
||||||
|
overrides_applied: 0
|
||||||
|
mvp_mode_note: "ROADMAP marks Phase 14 mode: mvp, but the goal is not a User Story and no 14-*-PLAN.md carries one. As in Phases 1, 3, 5 and 8 to 13, the six ROADMAP success criteria are the contract, User Flow Coverage is derived from them, and plan must_haves are supporting evidence."
|
||||||
|
human_verification:
|
||||||
|
- test: "Decide CR-01 (critical, open): the Discogs apply-release (album and wishlist) and the MCP cover-price/discogs route load the album at request start, then call Discogs (limiter wait up to 15 s, GetRelease, price suggestions, cover download up to 10 s), then save through classes.SaveAlbumRow, which is GORM Save of every column."
|
||||||
|
expected: "Either fix it before Phase 15 cutover (column-scoped save: SaveAlbumColumns helper that still runs checkAlbumModelRules and the broadcast hooks), or accept it as known risk. Verifier recommendation: fix. PHP's $album->save() writes only dirty attributes, so a concurrent PUT albums/{id} from Nuxt, MCP or another household member survives in PHP and is silently reverted in Go."
|
||||||
|
why_human: "No success criterion is falsified: every SC4 route passes its parity diff and sequential behaviour matches PHP. The defect only appears under concurrent writes, which the replay corpus cannot express. Whether a lost update that PHP does not have blocks the phase is a product decision."
|
||||||
|
- test: "Accept or reject the two 14-03 plan statements that were not implemented as written, because PHP behaves differently: (a) prohibition 'dry_run MUST NOT write anything' (PHP's dry run stages only year, label, catalog_number, country and tracklist and still writes covers, genre_id, barcode, edition, discogs_id, format and market price); (b) backstop truth 'a second identical apply imports no second cover' (PHP re-imports the covers; recorded case 'twice')."
|
||||||
|
expected: "If accepted, add the two override entries suggested under Gaps Summary to this file's frontmatter."
|
||||||
|
why_human: "Overrides need a human accepted_by. The code follows PHP, as CLAUDE.md requires, and TestApplyReleaseModes and T-14-19 pin the PHP behaviour. The plan text is still violated, though, and an agent instruction cannot accept that."
|
||||||
|
- test: "Live vendor smoke test: with a real Discogs personal token, run discogs-credential/test, albums/{id}/match, apply-release and the MCP cover-price tool. With a real OpenAI key and a real Anthropic key, run ai-credential/test and albums/recognize on a real album photo. Use both a user credential and the admin vision model."
|
||||||
|
expected: "Same results as the PHP backend gives for the same inputs. Cover images download from i.discogs.com, prices are stored, and recognised albums come back."
|
||||||
|
why_human: "Every upstream answer in the parity corpus is a scripted response (parity/upstream/scripts/*.yaml) served through the recording proxy. Go's outbound requests are asserted equal to PHP's, but Go has never parsed a real vendor response. CI makes no live calls (D-15)."
|
||||||
|
- test: "Triage the six open review warnings: WR-01 (stale CSV match pass can cancel a remapped import; PHP parity, but the code comment and the D-10 narrative claim it is closed), WR-02 (anonymous feedback screenshot keeps a client-chosen extension such as .html in public storage), WR-03 (ai-credential/test and discogs-credential/test are unthrottled), WR-04 (an admin can redirect the stored write-only model key by changing base_url), WR-05 (golem:import-settings drops models from a non-sequential repeater; check the production golem_settings row before cutover), WR-06 (G15Office sync is not idempotent on retry)."
|
||||||
|
expected: "Each warning gets a disposition (fixed, deferred to a quick task or todo, or accepted) in 14-REVIEW-DISPOSITION.md. WR-02 and WR-05 should be resolved before Phase 15 cutover."
|
||||||
|
why_human: "None of them falsifies a success criterion, and most reproduce PHP behaviour. They are security or data-migration risks, so someone has to decide whether to accept or fix each one."
|
||||||
|
- test: "Admin screens and widget in a browser: the golem AI models list and form (key write-only), the feedback settings screen and read-only submissions list, and the embed.js widget on a page served by the Nuxt app (config load, submit with a screenshot, hide)."
|
||||||
|
expected: "Screens render and save. The widget loads its config, submits (202), and hides when asked."
|
||||||
|
why_human: "Visual and interactive behaviour. Tests cover the schemas and endpoints, not the rendered UI."
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 14: Domain jobs and external integrations Verification Report
|
||||||
|
|
||||||
|
**Phase Goal:** The domain-specific River jobs (CSV import write, Discogs match, wishlist digest), the reindex command, the Discogs client and AI cover recognition are ported on top of the Phase 11 jobs/realtime/search infrastructure and the Phase 13 API surface they serve.
|
||||||
|
**Verified:** 2026-10-04T01:11:09Z
|
||||||
|
**Status:** human_needed
|
||||||
|
**Re-verification:** No, initial verification
|
||||||
|
|
||||||
|
**MVP note:** ROADMAP marks this phase `mode: mvp`, but the goal is not a User Story. As in Phases 1, 3, 5 and 8 to 13, the six ROADMAP success criteria are the contract.
|
||||||
|
|
||||||
|
## User Flow Coverage
|
||||||
|
|
||||||
|
| # | Step (from SC) | Expected | Evidence in codebase | Status |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 1 | Collector uploads a CSV; match job runs | Rows matched against the collection and Discogs; a rate limit pauses and re-dispatches | `csv_match_job.go` `deliverCsvMatch` / `pause`; `jobs.go:37` `conga.Timeout(240 * time.Second)`; TestCsvMatchJob (php-recorded, re-dispatch delay, 240 s Describe) PASS | ✓ |
|
||||||
|
| 2 | Collector commits; import job writes albums | Canonical, draft or CSV rows written; one bulk event | `csv_import_job.go`; TestCsvImportJob, TestPhase14Jobs PASS | ✓ |
|
||||||
|
| 3 | Subscriber gets one digest per 30-minute window | Queue row deleted, one mail | `wishlist_digest_job.go` (DELETE-gated mail); Phase 13 `EnqueueWishlistDigest` dispatches only on insert with 1800 s delay; TestWishlistDigestJob PASS | ✓ |
|
||||||
|
| 4 | Operator runs `fonoteka:reindex [--drop-old-items-index]` | Tenantless abort, rebuild, zero collection_id:=0 docs, legacy drop | `console/reindex.go`, matches `ReindexAlbums.php` line for line; TestReindexCommand PASS | ✓ |
|
||||||
|
| 5 | Collector matches, applies and imports Discogs releases; MCP fetches cover and price | PHP's bodies and limiter answers | 8 routes in `routes.go`; parity 172/172 passing | ✓ (see CR-01) |
|
||||||
|
| 6 | Collector or MCP photographs albums | Anthropic or OpenAI-compatible recognition; admin tier uses the global vision model | sm-golem-plugin adapters; `golem_wiring.go` → `SetAdminVisionModel`; recognize parity cases (20) PASS | ✓ |
|
||||||
|
| 7 | Visitor submits feedback; collector hides widget | 202 submit, config, `me/hidden`, `feedback_widget_hidden` on user payload | sm-feedback-plugin routes and listener; 28 parity cases PASS | ✓ |
|
||||||
|
|
||||||
|
## Goal Achievement
|
||||||
|
|
||||||
|
### Observable Truths (ROADMAP Success Criteria)
|
||||||
|
|
||||||
|
| # | Truth | Status | Evidence |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | The CSV import write job and the self-redispatching Discogs match job (240 s timeout, re-dispatching with a delay on a Discogs rate-limit error) both complete correctly | ✓ VERIFIED | Workers registered in `jobs.go:45-49` on queues that conga serves (`knownQueues` adds every registered job's queue). `pause()` dispatches a new match job with `Delay = max(RetryAfter, SecondsUntilAvailable)` under a row lock and completes the old one `{"paused":"discogs_rate_limited"}`. Behavioural tests run by verifier: TestCsvMatchJob (subtests "a rate limit beyond the budget re-dispatches with the computed delay", "sub-second remainder delays one second", "registered with a 240 s timeout", "php-recorded"), TestCsvImportJob, TestPhase14Jobs, TestCsvWR02: all PASS. Cancel-branch write is unconditional, as in PHP `AlbumCsvMatchJob.php:100-103` (WR-01, warning). |
|
||||||
|
| 2 | The wishlist digest job coalesces a 30-minute window and deletes its queue row on completion | ✓ VERIFIED | `EnqueueWishlistDigest` upserts the queue row and dispatches only when the insert created it (`RETURNING (xmax = 0)`), delay `WishlistDigestDelay = 1800 * time.Second`. `deliverWishlistDigest` deletes the row and mails only if the DELETE removed it. TestWishlistDigestJob asserts row count 0 after send, skip on missing or zero row, PL/EN templates and a single mail for two runs: PASS. |
|
||||||
|
| 3 | The `reindex` command asserts zero `collection_id`-0 documents before and after and can drop the legacy index | ✓ VERIFIED | `console/reindex.go`: aborts on `collection_id IS NULL OR <= 0`, Flush, EnsureIndex, upsert by id in batches of 500, `collection_id:=0` search must be 0, `--drop-old-items-index` drops only `prefix+golem15_fonoteka_items` via `beachcomber.DropIndex` with PHP's two messages. Registered in `plugin.go:296`. TestReindexCommand (batch ordering, empty table, integrity fail, tenantless abort, legacy drop twice) PASS. |
|
||||||
|
| 4 | Discogs client enforces proactive rate threshold, bounded wait budget, retry-after fallback and host-locked cover fetch; the 8 Discogs routes and the CSV row-edit `selected_discogs_id` pick pass the parity diff | ✓ VERIFIED | `classes/discogs/client.go`: code-constant `https://api.discogs.com`, fetchguard AllowHostsMode, Acquire + 429 loop with `RegisterRetryAfter` fallback and deadline budget. `rate_store.go` UNLOGGED table plus one `INSERT … ON CONFLICT … WHERE … RETURNING`. `discogs_wiring.go` installs the real ReleaseFetcher (`plugin.go:138`). Routes mounted at `routes.go:102-112, 192-194, 224, 298`. Verifier ran TestParityCorpus: `recorded 175/175 passing 172 failing 0 unrecorded 0 pending 3`, and TestUpstreamSidecarsAreReplayed PASS. TestDiscogsClientStatuses, TestRateLimiterBudget, TestRegisterRetryAfter, TestDiscogsRateWindowConcurrent, TestCoverFetcherHostLock, TestPHPTruthDiscogs PASS. CR-01 (lost update under concurrency) does not falsify this criterion; it is escalated to human. |
|
||||||
|
| 5 | AI cover recognition through Anthropic and OpenAI-compatible adapters over the guarded client with per-credential overrides; `albums/recognize` (JWT and token groups) and `ai-credential/test` pass parity; admin tier uses the backend global vision model | ✓ VERIFIED | sm-golem-plugin `classes/providers/{anthropic,openai}.go` are hand-rolled (x-api-key, anthropic-version, `/messages`; Bearer, `/chat/completions`, max_completion_tokens, reasoning_effort). No vendor SDK in the module graph (gate). `AIService.client` uses TrustedMode for admin models and PublicOnlyMode otherwise. `golem_wiring.go` sets `SetAdminVisionModel(golemVisionModel)` with `Trusted: true`, called at `plugin.go:141`. Routes at `routes.go:109, 218, 285` (`inv.scope:ai`). TestRecognizeRoutes, TestAICredentialTestRoute PASS. Parity cases are in the 172 passing. sm-golem-plugin `go test ./...` PASS. |
|
||||||
|
| 6 | Feedback submissions, widget config and hide preference work (sitemap dropped, D-14); oauth-client, prune-notifications and reindex commands run correctly; oauth-identities and token `me` stay pending (D-09) | ✓ VERIFIED | sm-feedback-plugin `routes.go`: config and submit with throttle buckets, `PUT me/hidden`, embed.js. `embed.js` sha256 is identical to PHP's (a76d27dc…). The `GetApiArrayEvent` listener adds `feedback_widget_hidden`. sm-feedback-plugin `go test ./...` PASS. Its 28 parity cases are in the 172 passing. `Commands()` registers OAuthClient, PruneNotifications and Reindex. The schedule entry `fonoteka:prune-notifications` Daily resolves (TestFonotekaSchedulePrune PASS). TestPruneNotifications (90-day boundary, two concurrent runs delete once) PASS. Exactly 3 pending manifest entries, all D-09 routes; Phase 14.1 owns them. |
|
||||||
|
|
||||||
|
**Score:** 6/6 roadmap truths verified (0 present, behavior-unverified)
|
||||||
|
|
||||||
|
### Plan must_haves (supporting evidence)
|
||||||
|
|
||||||
|
All 56 plan truths were checked. 54 hold, including both 14-02 backstop truths: reindex ordering is pinned by the TestReindexCommand subtest "every live album by id in batches of 500", and prune concurrency by TestPruneNotifications "two concurrent runs". Two 14-03 statements deviate from their literal text to keep PHP behaviour:
|
||||||
|
|
||||||
|
| Plan | Statement | Actual | Disposition |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 14-03 truth 4 / prohibition | "`dry_run` writes nothing" | Writes covers and catalog fields and stages only DRAFT_FIELDS, exactly as `AlbumReleaseApplicator.php:75-133` | Human: suggested override |
|
||||||
|
| 14-03 backstop | "second identical apply imports no second cover" | Re-imports covers, as PHP's recorded `twice` case does | Human: suggested override |
|
||||||
|
|
||||||
|
The intermediate counts in plan truths (14-03 "165 ported, 6 pending", 14-04 "168") were superseded by 14-05 and 14-06. The final state, 175/172/3, is the one that holds.
|
||||||
|
|
||||||
|
### Required Artifacts
|
||||||
|
|
||||||
|
| Artifact | Status | Details |
|
||||||
|
|---|---|---|
|
||||||
|
| summercms.go `modules/fetchguard/client.go`, `modules/tide/upstream.go`, `upstream_proxy.go`, `modules/sunscreen/sunscreen.go`, `modules/beachcomber/searchable.go`, `cmd/summer/parity.go` | ✓ VERIFIED | Present, substantive, wired (`internal/build/build.go` emits `sunscreen.InstallDefault`; parity replay uses `tide.NewUpstreamFake` through `fetchguard.WithTransport`) |
|
||||||
|
| fonoteka.go `classes/discogs/{client,rate_store,rate_limiter,cover_fetcher}.go`, `updates/22_discogs_rate_windows_table.go`, `csv_match_job.go`, `csv_import_job.go`, `wishlist_digest_job.go`, `console/{reindex,prune_notifications}.go`, `controllers/api/{release_match,inbound_limits,…}.go`, `classes/album_recognition.go`, `golem_wiring.go`, `discogs_wiring.go` | ✓ VERIFIED | All present (32–536 lines), registered in `plugin.go`/`jobs.go`/`routes.go` |
|
||||||
|
| sm-golem-plugin `plugin.go`, `classes/services/ai_service.go`, `classes/security/ssrf_guard.go`, `models/ai_model.go` | ✓ VERIFIED | Submodule at 6646d10, listed in `summer.yaml` before fonoteka |
|
||||||
|
| sm-feedback-plugin `plugin.go`, `controllers/api/feedback_api_controller.go`, `classes/g15office_client.go`, `assets/js/embed.js` | ✓ VERIFIED | Submodule at 3057719, listed in `summer.yaml` |
|
||||||
|
| `scripts/check-phase14.sh`, `parity/discogs_truth_tables.php`, `phase14_security_test.go` | ✓ VERIFIED | `--evidence` stage run by verifier: passed |
|
||||||
|
|
||||||
|
### Key Link Verification
|
||||||
|
|
||||||
|
| From | To | Via | Status |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `plugin.go` | `classes.SetReleaseFetcher` | `discogsReleaseFetcher{}` at Boot (line 138) | WIRED |
|
||||||
|
| `plugin.go` | `classes.SetAdminVisionModel` | `installGolemWiring()` (line 141) | WIRED |
|
||||||
|
| `discogs/client.go` | fetchguard | `fetchguard.NewClient(AllowHostsMode, api.discogs.com)` | WIRED |
|
||||||
|
| `jobs.go` | match/import/digest workers | `conga.Job(...)` with OnQueue, 240 s timeout | WIRED |
|
||||||
|
| `routes.go` | 11 Phase 14 handlers | JWT and token groups with scopes and throttles | WIRED (TestRouteTablePhase14 PASS) |
|
||||||
|
| feedback `plugin.go` | user payload | `GetApiArrayEvent` listener | WIRED |
|
||||||
|
| `parity_test.go` | tide sidecars | `LoadUpstream` + `WithTransport` | WIRED (TestUpstreamSidecarsAreReplayed PASS) |
|
||||||
|
|
||||||
|
### Data-Flow Trace (Level 4)
|
||||||
|
|
||||||
|
| Artifact | Data | Source | Real data | Status |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Discogs routes | release, search, price | `discogs.ForUser` → guarded GET → `csv.DecodeOrdered` | Yes (replayed from recorded sidecars) | ✓ FLOWING |
|
||||||
|
| Recognize | album list | `AIService` → adapter → `RecognizeAlbums` normaliser | Yes | ✓ FLOWING |
|
||||||
|
| Admin AI tier | vision model | `services.VisionModel(ctx, db)` from `golem15_golem_models` | Yes | ✓ FLOWING |
|
||||||
|
| Digest mail | itemCount, ownerName, wishlistName | queue row, collections, users | Yes | ✓ FLOWING |
|
||||||
|
|
||||||
|
### Behavioral Spot-Checks (run by verifier)
|
||||||
|
|
||||||
|
| Behavior | Command | Result | Status |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Parity corpus | `go test ./parity -run '^(TestParityCorpus\|TestUpstreamSidecarsAreReplayed)$'` | recorded 175/175 passing 172 failing 0 pending 3 | ✓ PASS |
|
||||||
|
| Jobs, routes, threats, edges | `go test ./plugins/golem15/fonoteka -run '^(TestCsvMatchJob\|TestCsvImportJob\|TestWishlistDigestJob\|TestPhase14Jobs\|TestCsvWR02\|TestPhase14Edges\|TestRouteTablePhase14\|TestApplyReleaseModes\|TestCoverPriceRoute\|TestDiscogsCredentialTestRoute\|TestAICredentialTestRoute\|TestRecognizeRoutes\|TestPhase14Threats\|TestFonotekaSchedulePrune\|TestWishlistDigestWorkerRegistered)$'` | 15/15 PASS | ✓ PASS |
|
||||||
|
| Commands | `go test ./plugins/golem15/fonoteka/console -run '^(TestPruneNotifications\|TestReindexCommand)$'` | PASS | ✓ PASS |
|
||||||
|
| Discogs core | `go test ./plugins/golem15/fonoteka/classes/discogs -run '^(TestPHPTruthDiscogs\|TestDiscogsClientStatuses\|TestRateLimiterBudget\|TestRegisterRetryAfter\|TestDiscogsRateWindowConcurrent\|TestCoverFetcherHostLock\|TestBucketID)$'` | 7/7 PASS | ✓ PASS |
|
||||||
|
| Shared plugins | `go test ./plugins/golem15/golem/... ./plugins/golem15/feedback/...` | all ok | ✓ PASS |
|
||||||
|
| Gate evidence | `bash scripts/check-phase14.sh --evidence` | phase14 evidence passed | ✓ PASS |
|
||||||
|
|
||||||
|
`--removal` (about 25 min) was not re-run by the verifier. Its 43/43 result is taken from the 14-06 SUMMARY, and the named threat tests it depends on (TestPhase14Threats) pass.
|
||||||
|
|
||||||
|
### Probe Execution
|
||||||
|
|
||||||
|
No `scripts/*/tests/probe-*.sh` exist and no plan declares a probe. The phase's runnable gate is `scripts/check-phase14.sh`. The orchestrator ran `--go` (build, vet, `go test ./...` green), and the verifier ran `--evidence` above.
|
||||||
|
|
||||||
|
### Requirements Coverage
|
||||||
|
|
||||||
|
| Requirement | Source Plan | Status | Evidence |
|
||||||
|
|---|---|---|---|
|
||||||
|
| JOBS-02 | 14-02, 14-06 | ✓ SATISFIED | SC1 |
|
||||||
|
| JOBS-03 | 14-02, 14-06 | ✓ SATISFIED | SC2 |
|
||||||
|
| SRCH-02 | 14-01, 14-02, 14-06 | ✓ SATISFIED | SC3 |
|
||||||
|
| INTG-01 | 14-01, 14-02, 14-03, 14-06 | ✓ SATISFIED | SC4 (CR-01 escalated) |
|
||||||
|
| INTG-02 | 14-01, 14-04, 14-06 | ✓ SATISFIED | SC5 |
|
||||||
|
| API-08 | 14-01, 14-05, 14-06 | ✓ SATISFIED | SC6 |
|
||||||
|
| CLI-05 | 14-02, 14-06 | ✓ SATISFIED | SC6 (oauth-client from Phase 8, prune and reindex here) |
|
||||||
|
|
||||||
|
No orphaned requirements: REQUIREMENTS.md maps exactly these seven IDs to Phase 14, and all are marked Complete.
|
||||||
|
|
||||||
|
### Anti-Patterns Found
|
||||||
|
|
||||||
|
No TBD, FIXME, XXX, TODO, HACK or placeholder markers in the Phase 14 implementation files of the four repos.
|
||||||
|
|
||||||
|
| File | Line | Pattern | Severity | Impact |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `fonoteka/classes/album_write_service.go` | 269 | GORM `Save` (all columns) after long Discogs I/O (CR-01) | ⚠️ Warning (escalated) | Lost update under concurrency, which PHP does not have |
|
||||||
|
| `fonoteka/csv_match_job.go` | 127 | Unconditional cancel write; comment claims every status write is conditional (WR-01) | ⚠️ Warning | PHP parity; comment and D-10 narrative overstate the fix |
|
||||||
|
| `feedback/classes/submission_store.go` | 85 | Client-chosen extension in public storage (WR-02) | ⚠️ Warning | Possible stored XSS surface |
|
||||||
|
| `fonoteka/routes.go` | 218, 224 | Credential test routes unthrottled (WR-03) | ⚠️ Warning | PHP parity |
|
||||||
|
| `golem/console/import_settings.go` | 152 | Map-keyed repeater read by 0..len-1 only (WR-05) | ⚠️ Warning | One-time import can drop models silently |
|
||||||
|
| `feedback/classes/sync_g15office.go` | 174 | Non-idempotent on retry (WR-06) | ⚠️ Warning | Duplicate G15Office tasks |
|
||||||
|
|
||||||
|
### Human Verification Required
|
||||||
|
|
||||||
|
### 1. CR-01 decision (recommended: fix before Phase 15)
|
||||||
|
**Test:** Run apply-release or MCP cover-price on an album while a concurrent `PUT albums/{id}` changes, for example, its notes.
|
||||||
|
**Expected:** The edit survives, as in PHP.
|
||||||
|
**Why human:** Parity cannot express concurrency, and no SC is literally falsified. Deciding whether this blocks the phase is a product call. The verifier recommends a column-scoped save that keeps the model rules and the broadcast hooks.
|
||||||
|
|
||||||
|
### 2. Override decision for the two 14-03 PHP-parity deviations
|
||||||
|
**Test:** Read the dry_run and second-apply rows above.
|
||||||
|
**Expected:** Accept by adding the overrides below, or reject and re-plan.
|
||||||
|
**Why human:** An agent instruction cannot accept a violated must-have.
|
||||||
|
|
||||||
|
### 3. Live vendor smoke test
|
||||||
|
**Test:** Use real Discogs, OpenAI and Anthropic credentials against the Go backend.
|
||||||
|
**Expected:** Same results as PHP.
|
||||||
|
**Why human:** Every upstream response in the corpus is scripted. Go's requests are proven equal to PHP's, but no real vendor response has ever been parsed.
|
||||||
|
|
||||||
|
### 4. Triage WR-01 to WR-06
|
||||||
|
**Expected:** Each one gets a disposition. Resolve WR-02 and WR-05 before cutover.
|
||||||
|
|
||||||
|
### 5. Admin screens and embed widget in a browser
|
||||||
|
**Expected:** Screens render and save; the widget loads, submits and hides.
|
||||||
|
|
||||||
|
### Gaps Summary
|
||||||
|
|
||||||
|
No success criterion failed. The phase goal is achieved as the roadmap states it:
|
||||||
|
- all four job and command pieces run;
|
||||||
|
- the Discogs client enforces its rate rules;
|
||||||
|
- the 11 Phase 14 routes and 4 feedback routes pass the PHP parity diff, independently re-run: 175/175 recorded, 172 passing, 3 D-09 pending owned by Phase 14.1;
|
||||||
|
- recognition works through both adapters, with the admin vision tier wired.
|
||||||
|
|
||||||
|
The status is `human_needed`, not `passed`, for three reasons:
|
||||||
|
|
||||||
|
1. **CR-01 is real** (confirmed in `applicator.go:105` and `cover_fetcher.go:345,378` → `album_write_service.go:269`). It is a lost update that PHP does not have, in exactly the routes SC4 names. It does not falsify the literal criterion, which is parity diffs that cannot see concurrency, so it is escalated rather than marked FAILED. The verifier recommends fixing it in a quick task before Phase 15.
|
||||||
|
2. **The two 14-03 statements were deliberately not met** because PHP behaves otherwise. That is correct under the CLAUDE.md parity rule, but it needs a human-accepted override. Suggested entries:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
overrides:
|
||||||
|
- must_have: "apply-release dry_run MUST NOT write anything"
|
||||||
|
reason: "PHP AlbumReleaseApplicator stages only DRAFT_FIELDS in dry run and writes covers and catalog fields; the recorded dry-run case is the contract. Go never writes a staged field (T-14-19)."
|
||||||
|
accepted_by: "{name}"
|
||||||
|
accepted_at: "{ISO timestamp}"
|
||||||
|
- must_have: "Edge (INTG-01 idempotency): applying the same release to the same album twice fills nothing on the second call and imports no second cover"
|
||||||
|
reason: "PHP re-imports covers on a second apply (recorded 'twice' case); Go matches it and fills no field twice."
|
||||||
|
accepted_by: "{name}"
|
||||||
|
accepted_at: "{ISO timestamp}"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **The vendor interop is proven only against scripted upstream responses.** There are also six open review warnings, two of which (WR-02, WR-05) should be resolved before cutover.
|
||||||
|
|
||||||
|
Deferred items carried from deferred-items.md (informational): the Phase 12 cover-import replay still dials live image hosts, the one-off TestCoverFetcherHostLock failure under full `-race` load (it passed in the verifier's run), and the testcontainers start wait.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Verified: 2026-10-04T01:11:09Z_
|
||||||
|
_Verifier: Claude (gsd-verifier)_
|
||||||
Reference in New Issue
Block a user