21 KiB
phase, plan, subsystem, tags, requires, provides, affects, actuals, plan_head_before, plan_head_after, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, coverage, duration, completed, status
| phase | plan | subsystem | tags | requires | provides | affects | actuals | plan_head_before | plan_head_after | tech-stack | key-files | key-decisions | patterns-established | requirements-completed | coverage | duration | completed | status | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 12-p-ytarium-api-collections-and-albums | 02 | api |
|
|
|
|
|
02e1aa94612fbfe0940d8424ae7efedb00ea0190 | 6c729a708f96fe941c4791049b89a9cad321461f |
|
|
|
|
|
|
46min | 2026-10-02 | complete |
Phase 12 Plan 02: Collections, me/context, realtime channel and share link Summary
Token-aware tenant core (pinned tokens, row-locked resolve and provisioning, one access scope) and all 23 collections-area routes ported byte-compatibly on both auth groups, with Winter 404 pages, multipart photo/image uploads under the Winter URL layout, per-album collection deletes, me/context gates and crypto/rand share tokens, replayed from 82 fresh PHP recordings.
Performance
- Duration: 46 min
- Started: 2026-10-02T09:51:46Z
- Completed: 2026-10-02T10:37:30Z
- Tasks: 3 (tracer verified end to end before expansion)
- Files modified: 119 in fonoteka.go (code, tests, 75 fixture files, docs)
Accomplishments
classes.ResolveportsActiveCollectionResolver::resolve: a personal token needs exactly one accessible pinned id (elseErrCollectionNotFound, the Winter 404 page); a JWT user gets the stored context while accessible, else the lowest-id accessible collection, else a provisionedMoja kolekcja, underSELECT ... FOR UPDATEon the users row then the context row. Four concurrent first resolves create one collection and one context row.classes.AccessibleBy/AlbumsAccessibleByare the request-facing access rule: grouped owner-or-editor membership on live rows, plus the token pin(id IN pin OR own wishlist). Genres on the token group now resolve the pin, as PHP does.- Collections CRUD, photos, image, switch,
me/context,realtime/channelsandcollection/shareare mounted once each; the token group carries exactly oneinv.scopeper route, so a write-only token updates a collection and is refused on reads with PHP's 403 body (which now carriesCache-Control: no-cache, private). - Uploads write Winter-style disk names under
storage/app/uploads/public, setsort_orderto the id, and answerurland a 200x200 cropthumb_url; photo removal detaches the row asAttachMany::removedoes; image upload replaces the previous image. - Deleting a collection soft-deletes its albums one by one (each gets its search removal and
deleted.fonoteka.albumbroadcast after commit, none on rollback) and repairs the context of the owner and every editor. me/contextreturns exactly the ten recorded flags (site admin from the user'sadmingroup, AI org lock and inheritance, Discogs tiers, market currency);realtime/channelsreturnscollection:<id>; the share surface is owner-only with tokens drawn fromcrypto/randwith rejection at 248.- 82 parity cases recorded from the isolated PHP (APP_DEBUG=false);
TestParityCorpusreports 56 ported routes passing;check_corpus.go --require-recorded --check-secretsis green.
Task Commits
All commits are in fonoteka.go (summercms.go code is untouched):
- Task 1: tenant core and GET collections on both groups (tracer) -
574b2f1(feat) - Task 2: collection CRUD, photos, image and switch -
b41d5ca(feat) - Task 3: me/context, realtime/channels and the share link -
6c729a7(feat)
Ledger: fonoteka.go 02e1aa9..6c729a7, 3 commits (git rev-list --count).
Files Created/Modified
classes/active_collection.go,access.go,collection_provisioner.go: resolver, switch, display id, access scopes, provisionerclasses/serialize.go: CollectionDTO/CollectionIndexDTO/PhotoDTO, album counts and media loadersclasses/gates.go,share_service.go,fingerprint.go: me/context gates, share tokens and state, collection_key HMACcontrollers/api/*: request decoding helpers, Winter error pages, collections, media, share, me/context and channel handlersmodels/collection.go: per-album BeforeDelete and Winter string trimming in BeforeSaveroutes.go: JWT routes and token twins with per-route scopes, inline throttles,{id}/{fileId}constraintsconfig/storage.yaml, pluginconfig/config.yaml,README.md: Winter upload layout and the newgolem15.fonoteka.*keysparity/*: fonoteka seed hook (reseeded per case), manifest flips (56 ported), PHP reset script, recording recipe, 75 fixtures
Decisions Made
See key-decisions in the frontmatter.
Deviations from Plan
Auto-fixed Issues
1. [Rule 1 - Bug] Winter trims string attributes, so names are not stored byte-for-byte
- Found during: Task 1 (first PHP recordings)
- Issue: The plan's encoding truth says names keep leading and trailing spaces. Winter's
Model::setAttributetrims every string ($trimStringAttributes = true); the recorded PHP stores" New Shelf "asNew Shelf. - Fix:
Collection.BeforeSavetrims name and description with PHP's trim set; share renames trim too. Recorded cases pin it (POST, PUT, share rename). - Commit: b41d5ca, 6c729a7
2. [Rule 1 - Bug] PHP's provisioner and resolve fallback have no kind filter
- Found during: Task 1
- Issue: The plan said the provisioner reuses the lowest kind=collection collection;
CollectionProvisioner::provisionusesaccessibleByMembership()->orderBy('id')->first()with no kind filter. - Fix: Followed PHP;
TestResolveFallbackHasNoKindFilterpins the wishlist case, and the recorded owner-delete case shows the repaired context. - Commit: 574b2f1
3. [Rule 1 - Bug] Display id for a token with other than one pinned id is null
- Issue: The plan said such a token falls back to the stored context;
activeCollectionIdForDisplayreturns null for any token without exactly one id. - Fix: Followed PHP (
TestCollectionsIndexBothGroupsunpinned-token rows haveis_activenull). - Commit: 574b2f1
4. [Rule 1 - Bug] Name order is PHP SORT_REGULAR, not a plain byte compare
- Issue:
sortBy('name')usesasort(SORT_REGULAR): two numeric names compare as numbers ("9" before "10"). - Fix:
phpCompareStringsin a stable sort; the recordedorderingcase covers numeric, duplicate, accented and case-differing names. - Commit: 574b2f1
5. [Rule 1 - Bug] InvScope refusals lacked Cache-Control: no-cache, private
- Issue: The recorded PHP 403 carries the header every Laravel response has; the Go middleware omitted it.
- Fix:
middleware/token_scope.gosets it before writing 401/403. - Commit: 574b2f1
6. [Rule 3 - Blocking] Shared parity database across routes
- Issue: Every ported route replays against one Postgres; the fonoteka hook's tokens and alice's organisation broke the tokens, connected-apps and user-api fixtures, and an unpinned
token:mcp-readnow 404s on the token genres route as PHP would. - Fix: The genres hook deletes the fonoteka tokens and serves
token:mcp-readfrom a dedicated pinned reader (alice's "Parity MCP" token stays unpinned for the tokens fixtures; all genre counts are zero either way); the user-api hook resets the organisation fields; fonoteka routes reseed before every case. - Commit: 574b2f1
7. [Rule 3 - Blocking] Backend album binding must not provision
- Issue:
ResolveActiveCollectionnow has PHP resolve semantics (provisioning, no kind filter), but the admin album binding (a Go-only Phase 10 path) relied on the old non-provisioning read. - Fix:
backendActiveCollectionkeeps the old behaviour for the backend binding; API callers (token store, OAuth consent) use the full resolver as PHP does. - Commit: 574b2f1
8. [Rule 3 - Blocking] Upload part files live under parity/fixtures/routes/files/
- Issue: The plan named
parity/fixtures/files/; tide refuses part paths that leave the fixture's directory (../). - Fix: Files sit in
parity/fixtures/routes/files/, pinned by sha256. - Commit: b41d5ca
9. [Rule 3 - Blocking] Deferred-scope placeholder tests and the unported-route check
- Issue:
TestOAuthTokenSurfaceIsolationCoveragesubtests fail by design once token collections, write scopes, switch and share routes exist;assertPortedMismatchusedme/contextas its unported route. - Fix: Replaced each placeholder with the real assertion (401 then read 200 then write-only PUT 200; read-only token 403 on every write route; switch and share JWT-only); the mismatch check now uses
GET ai-credential(Phase 14). - Commits: 574b2f1, b41d5ca, 6c729a7
10. [Rule 2 - Missing critical] Replaced and deleted image blobs are removed after commit
- Issue: PHP's
image()->delete()leaves the old bytes on disk; the plan asked for after-commit deletion. - Fix: Rows are deleted in the request, blobs after the write succeeds; not observable in responses.
- Commit: b41d5ca
11. [Rule 3 - Blocking] Over-cap uploads answer 413 from the handler
- Issue: surf only wraps the body in
http.MaxBytesReader; there is no router 413 response, and PHP'spost_max_sizepath cannot be recorded. - Fix:
decodeInputreports the cap and the handler answers 413{"error":"Payload too large"};TestCollectionPhotoUploadasserts it and that no row is stored. - Commit: b41d5ca
12. [Recording approach] PHP state from a tinker reset per case
- The plan suggested the bootstrap seed plus tinker additions. A single reset script (
parity/fonoteka_reset.php) that mirrors the Go hook was used instead, run before each case, with collection and file id sequences lifted to 8-digit ranges so captured ids are scrubbed and never collide. The README documents the recipe.
Total deviations: 11 auto-fixed (5 bugs, 1 missing critical, 5 blocking) plus 1 recording-approach change. Impact on plan: All 23 routes ported and replaying; every deviation follows the recorded PHP contract or keeps the shared corpus honest. No scope creep.
Issues Encountered
- The org-lock-on
me/contextcase was not recorded (it needs a PHP restart withFONOTEKA_AI_ORG_LOCK);TestMeContextFlagscovers lock on and off. - A personal-token caller cannot reach
collection/shareorswitchover HTTP (the routes are absent from the token group), so the in-handler second lock is covered by Go tests rather than recordings. - Recording uploads against the isolated PHP wrote 16 files into the PHP checkout's
storage/app/uploads/public/6ab/f82/; they were removed afterwards. - The pending-invitation 409 guard is not part of
Resolveyet; it lands with household and invitations in 12-03 (invitation_acceptance_requiredis always false, as PHP returns it).
Known Stubs
None.
User Setup Required
None. Production keeps storage.uploads on the Winter layout; the new golem15.fonoteka.* keys default to PHP's values.
Next Phase Readiness
- 12-03 adds the pending-invitation guard to
Resolve, more Winter pages (409/410 as newwinter_<status>.htmlfiles) and reusesProvisionCollectionfor member removal. - 12-04 builds album handlers on
AlbumsAccessibleBy,requestScope,decodeInputand the media helpers;models.marketCurrency()still returns EUR and should readclasses.MarketCurrency. - 12-05 can reuse
collectionsHarness, the fonoteka seed hook andGenerateShareTokenFromfor coverage and fuzzing.
Phase: 12-p-ytarium-api-collections-and-albums Completed: 2026-10-02
Self-Check: PASSED
All 18 created files listed in key-files exist on disk. Commits 574b2f1, b41d5ca and 6c729a7 exist in fonoteka.go. Plan-level verification passed: go vet ./... and go test ./... -count=1 in fonoteka.go (root, parity, the fonoteka plugin and sm-user-plugin), TestParityCorpus with 56 ported and passing, and check_corpus.go --require-recorded --check-secrets exiting 0.