8.3 KiB
phase, fixed_at, review_path, iteration, findings_in_scope, fixed, skipped, status
| phase | fixed_at | review_path | iteration | findings_in_scope | fixed | skipped | status |
|---|---|---|---|---|---|---|---|
| 14-domain-jobs-and-external-integrations | 2026-10-04T06:20:00Z | .planning/phases/14-domain-jobs-and-external-integrations/14-REVIEW.md | 1 | 7 | 7 | 0 | all_fixed |
Phase 14: Code Review Fix Report
Fixed at: 2026-10-04T06:20:00Z Source review: .planning/phases/14-domain-jobs-and-external-integrations/14-REVIEW.md Iteration: 1
Summary:
- Findings in scope: 7 (CR-01, WR-01 to WR-06; the Info findings were out of scope)
- Fixed: 7
- Skipped: 0
All fixes landed in fonoteka.go and its plugin submodules sm-feedback-plugin and sm-golem-plugin. No change was needed in summercms.go. Each submodule fix is committed in the submodule first, then the pointer is bumped in fonoteka.go with its own fix(14): <id> bump … commit.
Verification
- Where it ran: the main checkout, because
workflow.use_worktreesisfalse. The numbers can be reproduced from the current tree. - After every fix:
go vetandgo teston the affected module, with the testcontainers Postgres suites running under Docker. - After the last fix:
go vetandgo testover the wholefonoteka.goworkspace (root, parity, fonoteka, golem, feedback and user plugins). Everything was green. - Environment note: in this sandbox
/tmphit a disk quota. The linker and the CSV multipart uploads failed with "disk quota exceeded" or a 500 untilGOTMPDIRandTMPDIRpointed at the nvme volume. This affects the environment only, not the code.TestCsvStoreAndShowfailed the same way on the unmodified tree. - Each new regression test was checked to fail on the pre-fix code, where practical (CR-01, WR-01, WR-05).
Fixed Issues
CR-01: Album writes after slow Discogs I/O save every column from a stale struct, silently reverting concurrent edits
Status: fixed: requires human verification (write-shape and logic change)
Files modified: fonoteka.go/plugins/golem15/fonoteka/classes/album_write_service.go, fonoteka.go/plugins/golem15/fonoteka/classes/discogs/applicator.go, fonoteka.go/plugins/golem15/fonoteka/classes/discogs/cover_fetcher.go, fonoteka.go/plugins/golem15/fonoteka/classes/discogs/postgres_test.go
Commit: d64d3ab (fonoteka.go)
Applied fix:
- Added
classes.SaveAlbumColumns(ctx, db, a, cols...). It runs the Album model rules, thenModel(a).Select(cols…).Updates(a), so GORM hooks and lighthouse broadcasts still fire. It also adds the columnsAlbum.BeforeSavederives from the given ones (tracklist→track_titles;market_price_stored→ currency, source and checked_at;market_price_currency→ source) and always addsupdated_at. - The applicator now records each field it actually sets and writes only those.
persistMarketPricewrites only the market-price columns.attachCoverwrites onlycover_import_failures.- A new subtest edits name, notes and shelf after the album is loaded and checks that the apply keeps them.
- One behaviour to check: an apply that changes no catalog field still issues an
updated_at-only UPDATE and broadcast. The old fullSavedid the same. Eloquent would skip the write.
WR-01: A stale CSV match pass can cancel, advance or fork a remapped import (the race D-10 claims to close)
Status: fixed: requires human verification (concurrency logic)
Files modified: fonoteka.go/plugins/golem15/fonoteka/csv_match_job.go, fonoteka.go/plugins/golem15/fonoteka/phase14_jobs_test.go
Commit: 41fffae (fonoteka.go)
Applied fix:
- The cancel branch, the final
previewwrite andfail()now requirestatus = matching AND (match_job_id IS NULL OR match_job_id = <this job>). The cancel branch used to be unconditional. pause()re-dispatches only when the locked row still names this job (or none). Otherwise it ends throughlostImport.- NULL
match_job_idis tolerated, the same way the existing start update tolerates it, because a remap always sets a non-NULL id. - A new subtest covers each of the four branches (cancel, final, fail, pause) against a mid-pass remap to a newer job. All four failed before the fix.
WR-02: The anonymous feedback upload keeps an attacker-chosen file extension in public storage
Files modified: fonoteka.go/plugins/golem15/feedback/classes/submission_store.go, fonoteka.go/plugins/golem15/feedback/feedback_test.go
Commit: 5a8b7f8 (sm-feedback-plugin), pointer bump 19add38 (fonoteka.go)
Applied fix:
- The disk-name extension now comes from the sniffed content type. The client's extension is kept only when it names that type (
jpg/jpeg,png,gif,webp), so ordinary uploads keep their Winter-style names. Anything else, such asx.html,x.svgor no extension, gets the type's own extension. An unknown type is refused. file_nameis reduced to the client's base name, the same cleaningattach.Storedoes.- A new subtest checks
x.html,../../x.svg,noextandShot.PNG.
WR-03: Credential-test routes are unthrottled oracles for third-party keys (parity security hole)
Files modified: fonoteka.go/plugins/golem15/fonoteka/routes.go, fonoteka.go/plugins/golem15/fonoteka/routes_table_phase14_test.go, fonoteka.go/plugins/golem15/fonoteka/ai_routes_test.go
Commit: e3c8599 (fonoteka.go)
Applied fix:
throttle:10,1added toPOST ai-credential/testandPOST discogs-credential/test, matching the other sensitive JWT routes. Responses below the limit are unchanged, and the parity suite stays green.- The route table records the deviation from routes.php.
TestAICredentialTestRoutemade 13 calls as one user, so its stored-credential half now runs as a second user.
WR-04: The "write-only" admin AI key can be redirected to any host by changing base_url
Files modified: fonoteka.go/plugins/golem15/golem/controllers/models_admin_controller.go, fonoteka.go/plugins/golem15/golem/golem_test.go, fonoteka.go/plugins/golem15/golem/README.md
Commit: 93b2b05 (sm-golem-plugin), pointer bump 80cafb2 (fonoteka.go)
Applied fix:
FormBeforeUpdatenow loads the storedadapterandbase_urlalong with the key. When the key is sent empty or redacted and either value differs, it returns a validation error onapi_key("Re-enter the API key when changing the adapter or base URL."). Base URLs are compared with nil and blank treated as equal.- The README describes the new rule.
- Not done: the review's secondary suggestion to narrow
TrustedModefor admin models. That is a design decision (D-05 accepts http and local model servers), so it is left for a decision note.
WR-05: golem:import-settings silently drops models from a repeater saved with non-sequential keys
Files modified: fonoteka.go/plugins/golem15/golem/console/import_settings.go, fonoteka.go/plugins/golem15/golem/golem_admin_test.go
Commit: 9f60b61 (sm-golem-plugin), pointer bump 8d60670 (fonoteka.go)
Applied fix:
- An object-shaped repeater now reads every key. Integer keys come first in ascending order, then any other keys in byte order.
- A new case imports
{"10":…,"0":…,"2":…}and checks that all three rows arrive in the order Zero, Two, Ten. The old code imported only 2.
WR-06: The G15Office sync job is not idempotent, and a retry files duplicate tasks
Files modified: fonoteka.go/plugins/golem15/feedback/classes/sync_g15office.go, fonoteka.go/plugins/golem15/feedback/jobs.go, fonoteka.go/plugins/golem15/feedback/classes/classes_test.go, fonoteka.go/plugins/golem15/feedback/README.md
Commit: e52b1ec (sm-feedback-plugin), pointer bump 98fb4a8 (fonoteka.go)
Applied fix:
SyncG15Officereturns early when a submission is alreadysentwith a task id.- The task id is stored as soon as
CreateTaskreturns it. A retry with a stored id only re-attempts the attachment. jobs.gologs aCompleteJobfailure (job and submission id only) instead of returning it.- The
attach-failuretest now checks:- the task id survives the failed attempt;
- the retry attaches to the same task;
- a third run on the sent submission sends nothing.
- In total: 1 task request and 2 attachment requests.
- Behaviour change: a submission that is
failedbecause its attachment failed now carries itsg15office_task_id. The task does exist on G15Office.
Fixed: 2026-10-04T06:20:00Z Fixer: Claude (gsd-code-fixer) Iteration: 1