22 KiB
phase, plan, subsystem, tags, requires, provides, affects, actuals, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, coverage, duration, completed, status, plan_head_before, plan_head_after
| phase | plan | subsystem | tags | requires | provides | affects | actuals | tech-stack | key-files | key-decisions | patterns-established | requirements-completed | coverage | duration | completed | status | plan_head_before | plan_head_after | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 13-p-ytarium-api-wishlist-notifications-csv-credentials-public | 04 | api |
|
|
|
|
|
|
|
|
|
|
|
80min | 2026-10-03 | complete | 37405f014878e249bd501ec93b1b7ffaa632772b | c540102eea33d68733ee4a5366be28c035954255 |
Phase 13 Plan 04: CSV export and import Summary
The collection backup downloads as PHP's exact CSV on both groups. The import wizard runs in Go up to a queued import job: upload in UTF-8, Windows-1250 or ISO-8859-2, preview or column mapping, row fixes, commit and cancel. The match and import jobs wait for their Phase 14 workers, and a Discogs pick never invents release data. The parser is a byte-level port of PHP's fgetcsv and CSV classes, checked against PHP on thousands of generated files. The corpus is at 151 ported routes.
Performance
- Duration: 80 min
- Started: 2026-10-03T06:30:14Z
- Completed: 2026-10-03T07:50:37Z
- Tasks: 3 of 3
- Files modified: 141 in fonoteka.go: 24 source, test and script files, 22 parser inputs, 4 truth tables and a README, 8 upload parts, 2 flow parts, 67 route fixtures, the flow and its rows golden, and the manifest, seeds, README and module file
Accomplishments
- Export (Task 1).
csv.WriteRecordportsfputcsvwith an empty escape. A field with a comma, quote, CR, LF, tab or space is quoted; null and false are empty, true is1, floats use PHP's form. It never uses the standard library's CSV writer.csv.ExportRowportsCsvAlbumContract::exportRow. It applies theFORMULA_PATTERNapostrophe guard, pipe-encodes styles, artists and covers, and tab-encodes the tracklist.classes.ExportAlbumsruns the album index's SQL path and filters in the index's default order, in batches of 100.CsvExportwrites the BOM, the headers andContent-Disposition: attachment; filename=plytarium-kolekcja-<Y-m-d>.csv. The route is mounted on the JWT group and on the token group (inv.scope:read).
- Parser (Task 2).
fgetcsvandstr_getcsvare ported fromphp_fgetcsv, including its reads past the line end. Over 16,088 random record reads, 0 differ from PHP.csv.ParseportsCsvAlbumParser. It decodes asiconv //IGNOREdoes, runs the\p{L}check and strips the BOM. It resolves the delimiter withstrtok, detects or overrides the map, backfills headers, and enforces the 5000-row cap and NUL rejection.- The detector, mapper and canonical row parser are ported too. Over 1,507 random files (key order included), 0 differ from PHP.
- The committed truth tables come from
parity/csv_truth_tables.php.
- Import session (Tasks 2 and 3).
- Store checks the extension and size, then parses. Invalid canonical rows are
validation_failed. The file goes to the private bucket. The import is created in preview or mapping, with its rows; canonical ids are matched only inside the importer's collection. - Show paginates rows by
row_index.summaryis the count per status, or[]without rows.progresscomes from the row counts, or from thesummer_jobsrow. - Mapping keeps the client's
column_mapkey order and re-parses the stored file. It cancels the previous match job, replaces the rows, and queuesCsvMatchArgson the import's transaction. - Row edit handles skip, accept_csv and the candidate allow-list pick through the
ReleaseFetcherseam. - Commit is a single
UPDATE ... WHERE status = 'preview'that queues oneCsvImportArgsjob. A replay answers the current job. - Cancel stops both jobs, the
summer_jobsrows and the River jobs.
- Store checks the extension and size, then parses. Invalid canonical rows are
- Parity.
- The
csvstate exists on both sides: albums with a cover, seven imports in every needed status, their rows and job rows, and aligned id sequences. - 67 route fixtures cover the 8 routes. The 5 Task 3 routes were recorded with
QUEUE_CONNECTION=database. - The
nuxt-csvflow (17 steps) and its job-row golden replay green.
- The
Task Commits
fonoteka.go (master, not pushed):
- Task 1: CSV export with the fputcsv port -
643c9dd(feat) - Task 2: parser package, private bucket, store and show -
8af008e(feat) - Task 3: mapping, rows, commit, cancel, token export, nuxt-csv flow -
c540102(feat)
Decisions Made
See key-decisions. The user may want to look at the first one. PHP decodes an ISO-8859-2 upload as Windows-1250, so Polish letters that differ between the two encodings come out wrong (Łódź imports as ŁódĽ). The Go port keeps that behaviour, because the PHP truth table is the contract. Fixing it would be a deliberate change in both implementations.
Deviations from Plan
Auto-fixed Issues
1. [Rule 1 - Plan premise] ISO-8859-2 files do not match their UTF-8 twin in PHP
- Found during: Task 2 (truth-table generation)
- Issue: The plan's encoding edge expected a Windows-1250 file and an ISO-8859-2 file with
Łódźto import with the UTF-8 values. PHP'siconv //IGNOREnever fails on Windows-1250, so ISO-8859-2 bytes are read as Windows-1250. - Fix: The port follows PHP. The truth table pins all three files (
lodz_*.csv), plus an ISO-8859-2 file that uses only shared letters and does match its twin (shared_*.csv). - Commit: 8af008e
2. [Rule 3 - Blocking] The CSV model JSON columns could not hold PHP's data
- Issue:
raw_jsonholds a list for non-canonical files, andcolumn_mapmust keep its key order. - Fix:
ColumnMap,RawJSON,CandidatesJSONandDraftJSONare nowlagoon.Jsonable[json.RawMessage]. No other code used them. - Commit: 8af008e
3. [Rule 1 - Parity] tide masks *_id keys as integer ids
- Issue: Discogs ids are strings, so the mask failed
discogs_idandselected_discogs_id. - Fix: CSV steps disable the mask for those two keys with step
normalizerules. - Commit: 8af008e
4. [Recording] The 5 MiB size case is not recorded
- Issue: The isolated PHP runs with php.ini's default
upload_max_filesizeof 2M, which rejects such a file before the controller does. The Go parity replay also caps uploads at 1 MiB. - Coverage instead:
TestCsvStoreAndShowchecks 5242880 bytes (accepted) and 5242881 bytes (csv_too_large). The 5001-row case is recorded.
5. [Structure] API names differ from the plan's list
ExportAlbums(ctx, db, AlbumSearchParams, coverBase, fn func([]string) error)lives in packageclasses. It reusessearchSQL,orderSearchandresolveSearchSortdirectly, soalbum_search.gois unchanged.MatchCanonicalIDis split in two:csv.CanonicalIDis the pure check;classes.matchCanonicalAlbumis the scoped lookup, since the csv package has no database.
- Other API changes:
CommitCsvImporttakes the mode and whether it was sent.ShowCsvImportwas added.CsvJobscovers Dispatch and CancelJob.
- Route upload parts are in
fixtures/routes/files/, because tide resolves part files relative to the fixture. The flow parts are infixtures/nuxt/files/.
6. [Rule 3 - Test inventories] Two guard tests named the old state
- The Phase 8 coverage subtest for CSV now asserts the real surfaces: export on both groups, the six import routes on JWT only.
- The T-12-17 part-file check accepts small
csv-*uploads that contain no secret shape. - Commits: 643c9dd, 8af008e, c540102
7. [Tooling] go work sync side effects reverted
- Promoting
golang.org/x/textchanged only the plugingo.mod; its hash was already ingo.sum. go work syncalso reorderedgo.workand dropped the root toolchain line. Both were reverted, so the rootgo.mod,go.sumandgo.work.sumare unchanged.
Total deviations: 7: 1 plan premise corrected by the PHP truth, 2 blocking model or harness issues, 1 recording limitation covered by Go tests, 1 structural API naming change, 1 test-inventory update and 1 tooling revert. Impact: every recorded body, header and job row matches PHP.
Issues Encountered
TestPhase09SecurityRoutes(12.2 cabana routes) still fails in the fonoteka plugin package; it is pre-existing and logged in deferred-items.md. Every other suite is green: the root module, parity (TestParityCorpus151/151,TestFonotekaNuxtFlowsincluding nuxt-csv), and the plugin's subpackages with-race.- The shell aliases
rmtorm -i, which stalled two commands; later commands usedcommand rmor-f. - The isolated PHP server was started for recording (sync, then
QUEUE_CONNECTION=database) and stopped before returning. Only the CSV uploads written today were removed from the PHP checkout'sstorage/app/fonoteka-csv/; an older file there was left in place.
Known Stubs
None. The CSV import and match jobs have no worker by design (D-04, Phase 14 JOBS-02). The Discogs pick seam fails by design until Phase 14 (D-05, INTG-01). Neither is a stub.
Threat Flags
None beyond the plan's register:
- T-13-12:
CsvImportFor(creator plus reachable collection) andTestCsvImportScope. - T-13-13: the private bucket, server-side keys, and the public-bucket absence check in
TestCsvStoreAndShow. - T-13-14:
FormulaSafeon every exported cell. - T-13-15: the 5 MiB cap, 5000 rows, NUL rejection and
throttle:10,1. - T-13-16: the candidate allow-list and the failing fetcher.
- T-13-17: the single compare-and-swap, checked by
TestCsvCommitCAS. - T-13-31: unserved queues and cancel stopping River jobs.
- T-13-SC: x/text v0.42.0 pinned by
go.sum.
User Setup Required
None. In production, golem15.fonoteka.csv.bucket_url defaults to file://./storage/app (Laravel's local disk, never served). Point it at another private location if the binary's working directory differs.
Next Phase Readiness
- 13-05 can rely on the token-group route table: export/csv is now mounted with
inv.scope:read. - 13-06 should add the CSV write routes (store, mapping, rows, commit, cancel) to
write_endpoints_fuzz_test.goand the 8 routes to the Phase 13 route-table test. - Phase 14:
- It registers workers for
golem15.fonoteka.csv_importandgolem15.fonoteka.csv_matchonfonoteka_csv_importandfonoteka_csv_match. The rows carry{"csv_import_id":N}. - It installs a real
ReleaseFetcherwithSetReleaseFetcher, which ports DiscogsClient::getRelease and DiscogsMapper::mapRelease. - It records the successful-pick case.
- It registers workers for
Self-Check: PASSED
- Created files exist: every
key-files.createdpath checked withtest -e. - Commits exist in fonoteka.go: 643c9dd, 8af008e, c540102.
- Plan verification:
- fonoteka.go
go vet ./...is clean (root and plugin module). go test ./...is green for the root module and the plugin subpackages; the plugin package fails only the pre-existingTestPhase09SecurityRoutes.TestParityCorpushas 151 routes ported and passing.TestFonotekaNuxtFlows/nuxt-csvpasses.check_corpus --require-recorded --check-secretsis green.
- fonoteka.go
- Acceptance checks:
encoding/csvappears 0 times in writer.go.plytarium-kolekcja-appears once in the export controller.- x/text is a direct requirement of the plugin module, and parser.go imports
charmap. - The plugin
config.yamlhascsv.bucket_urland nouploads/public. - testdata has Windows-1250 and ISO-8859-2 samples.
jobs.gonames no CSV kind.nuxt-csv.rows.jsonholdsfonoteka.csv.import.- The service names
CsvImportLabel,CsvMatchLabelanddiscogs_unavailable.