26 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 | 03 | api |
|
|
|
|
|
|
|
|
|
|
|
51min | 2026-10-03 | complete | ec054a2c8ee2d887dd4899bb07fb01cda720ce3d | 37405f014878e249bd501ec93b1b7ffaa632772b |
Phase 13 Plan 03: Wishlist Summary
All 30 Phase 13 wishlist routes now run in Go with PHP's bodies, error pages and reservation mask. That covers the own list, item CRUD, similar, share, settings, household, subscriptions, secret reservations, reveal, purchase and peer views. Item-added bells and a coalesced digest job hang off the album insert hook, and purchase mail goes through a River job that is sent only after commit. The corpus is at 143 ported, and both real clients' journeys and three publication goldens replay green.
Performance
- Duration: 51 min
- Started: 2026-10-03T05:31:59Z
- Completed: 2026-10-03T06:23:00Z
- Tasks: 4 of 4
- Files modified: 132 in fonoteka.go: 25 source/test files, 108 route fixtures and 7 flow, rows and golden files
Accomplishments
-
Resolver and scopes.
ActiveWishlist/ProvisionWishlistlock the users row FOR UPDATE, reuse the own wishlist or createMoja lista życzeń. Concurrent first reads create one row.WishlistsVisibleToandHouseholdPeerIDsderive peers as PHP does: owners and live editors of every collection the user belongs to.ReservableWishlistIDsadds the subscribed wishlists, re-checked as live wishlists.
-
Serializer.
AlbumDTO.Reservationusesjson:"reservation,omitempty".SerializeAlbumstakes options:WithReservation(rc):stateForViewer's mask. The owner before reveal getsreserved,is_mineandrevealedonly.WithoutArtists(): the artists are[], as in PHP's reserve and reveal bodies.
With no option, every existing album body is unchanged; the album broadcast goldens and corpus confirm it.
-
Item writes. Store, update and destroy work on both groups through the Phase 12 write path:
conditionandshelfareprohibited. A2 is settled by recording: the message isvalidation.prohibited.- The duplicate check runs against the real collection.
similarusesILIKE ... ESCAPE '\'with%,_and\escaped.
-
Item-added (D-08).
albumAddedCallbackbranches on the collection kind, andNotifyWishlistItemAddedusesSubscribersOf:- persisted rows come first, then synthetic household peers with both channels on;
- the actor is skipped;
- ws subscribers get
wishlist_item_addedwith{album_id, album_name, collection_id, owner_name}; - email subscribers go through
EnqueueWishlistDigest(the atomic upsert, with the dispatch on the first insert only).
-
Share, settings, household.
- Share mirrors
collection/shareon the own wishlist (/w/paths). - Settings validate before resolving, write the flag directly and report
followers_count. - Household groups
subscriptionsForwith album counts and eight name-ordered previews.
- Share mirrors
-
Subscriptions.
Subscribeis an upsert that stampssubscribed_at.Unsubscribeis also ported.SubscriptionStateForhasforcedfor peers. Token routes use the newResolvePublic; collection routes useWishlistsVisibleTo. Every miss is the Winter 404 page. -
Reservations.
ReserveAlbumchecks 422 own and 409 disabled, then locks the album row; an existing reservation gives the Winter 409 page.CancelReservationworks for the reserver only.RevealReservationis one-way and writes a bell row only.ReleaseForAlbumis also ported.
-
Purchase (D-07).
MoveToCollectionruns in one transaction:- the save under suppression, then one
updatedalbum event on the new collection channel; - the reservation release;
- bells for every ws subscriber (the actor too) and for the reserver once;
WishlistPurchasedMailArgsenqueued onmailper email subscriber.
The worker sends
wishlist_item_purchased(-en)bypreferred_localewithalbumNameandwishlistName. - the save under suppression, then one
-
Peer views. Index and show go through
ReservableWishlistIDswith the viewer's mask,is_ownerandowner. The four overlap pairs dispatch correctly through the assembled router. -
Parity.
- The
wishlistseed state exists on both sides. - 30 routes are re-recorded (108 fixtures), so 143 routes are ported.
nuxt-wishlistwas recorded withQUEUE_CONNECTION=database. Its rows golden is bob's digest atitem_count2, with one digest job.mcp-wishlistreplays.- The
wishlist-item-added,reservation-revealedandwishlist-purchasedgoldens replay. - The README documents the recipes.
- The
Task Commits
fonoteka.go (master, not pushed):
- Task 1: own wishlist read path and reservation mask -
338f518(feat) - Task 2: item writes, item-added bells and digest, share/settings/household -
ff38bad(feat) - Task 3: subscriptions, reservations, peer views, purchase and its mail -
791daad(feat) - Task 4: nuxt-wishlist and mcp-wishlist flows, rows golden, publication goldens -
37405f0(test)
Decisions Made
See key-decisions above. The user may want to confirm one of them: the purchase emits the album updated event before the bells. That matches the order PHP records under sync. In production the PHP broadcast is queued and would arrive after the bells. No client depends on the cross-channel order.
Deviations from Plan
Auto-fixed Issues
1. [Rule 1 - Bug] Job dispatch inside the album insert hook inserted extra album rows
- Found during: Task 2 (TestWishlistItemAddedOncePerPath)
- Issue:
cleanSession(Session{NewDB: true}) keeps the callback's album statement until the first chained call.conga.DispatchcalledWithContext, which clones that statement, and itsCreate(&record)re-ran the album insert. Each digest dispatch added two album rows. - Fix: job queues get
jobDB(tx, ctx)(cleanSession(...).Scopes(), a fresh statement). The latent hazard is logged in deferred-items.md. - Files modified: classes/wishlist_notifications.go
- Commit: ff38bad
2. [Rule 1 - Parity] A provisioned wishlist's settings answer reservations_allowed: false
- Found during: Task 2 (GET wishlist/settings
provisioncase) - Issue: PHP serializes the unsaved model, whose flag was never set, while the row holds the default true.
- Fix:
ProvisionWishlistreturns the in-memory flag as false; later reads see true. - Commit: ff38bad
3. [Rule 1 - Test seed] New notifications sorted below the seeded one in Go
- Issue: a Postgres sequence does not advance past an explicit id the way SQLite's AUTOINCREMENT does.
- Fix:
seedParityWishlistlifts the notifications sequence. - Commit: ff38bad
4. [Rule 3 - Blocking] Phase 8 isolation subtests asserted the whole wishlist family absent
- Fix: they now assert the real invariant through
assertWishlistSurfaces:- token-group wishlist routes are exactly the four CRUD routes, each with its one scope;
- every other wishlist route is JWT only;
- match and apply-release are absent;
- the share subtests allow
/wishlist/share.
- Commits: 338f518, ff38bad
5. [Rule 1 - Ordering] Purchase publications
- Found during: Task 4 (the wishlist-purchased golden)
- Issue: PHP publishes the moved album's
updatedevent inside the save, before the bells. - Fix:
MoveToCollectionnow emits it right after the save under suppression; it gained a bucket parameter, and the handler no longer emits separately. - Commit: 37405f0
6. [Rule 3 - Test harness] Broadcast goldens with several publications
- Issue 1: small Go ids collided in the normaliser (a user id equal to a collection id).
- Issue 2: River delivers publications in no fixed order.
- Issue 3: seeding the shared database broke
TestOAuthFlows, which runs later. - Fix: the wishlist subtests run on a database of their own with lifted collection ids, and compare order-insensitively (
assertMatchesGoldenUnordered). - Commit: 37405f0
7. [Rule 2 - Correctness] Path ids above the int4 range
- Fix: the wishlist routes use
int4PathID, so ids above the int4 range get PHP's 404 (anout-of-rangecase is recorded). - Commit: 338f518
8. [Scope] Small refactor outside the plan's file list
albums_controller.go'screateAlbumWithCoversnow delegates tocreateAlbumIn(collectionID), so the wishlist store reuses the exact write path.- Commit: ff38bad
9. [Recording] The wishlist share 422 case was not recorded
- Issue: the
share:wishlistcapture rule onPUT wishlist/shareneeds a token in the response, so a 422 cannot be recorded. - Coverage instead: the 422 path shares
collectionShareRuleswith the collection share and is asserted inTestWishlistShareSettingsHousehold/share.
Total deviations: 9: 4 bugs or parity fixes, 2 blocking test inventories or harness issues, 1 correctness hardening, 1 refactor, 1 recording limitation. Impact: every response body and side effect matches PHP. The interface changes from the plan are:
MoveToCollection(ctx, tx, svc, bucket, album, target);- serializer options rather than a
SerializeAlbumparameter; ReserveAlbumtakes the wishlist so it can perform the controller's 422/409 checks.
Issues Encountered
TestPhase09SecurityRoutes(pre-existing, Phase 12.2 cabana routes) still fails in the fonoteka plugin package; it is logged in deferred-items.md. Every other suite is green:- the root module;
- parity (including
TestParityCorpus143/143,TestFonotekaNuxtFlows,TestBroadcastGoldens,TestOAuthFlows); - the plugin's subpackages.
- The
FORCE_COLORshell variable was unset for test runs, as in 13-01/13-02. - The isolated PHP server was started for recording (sync, then
QUEUE_CONNECTION=database) and stopped before returning.
Known Stubs
None. The digest job kind has no worker by design (D-04/D-08, Phase 14 JOBS-03). The match and apply-release routes stay pending (D-01).
Threat Flags
None beyond the plan's register:
- T-13-04, T-13-05: TestReservationMask, TestReserveConcurrent and TestRevealIdempotent; no bell or publication names the reserver to the owner.
- T-13-06: scoped lookups with identical 404 pages.
- T-13-07:
assertWishlistSurfacesplus the acceptance greps. - T-13-28: the rollback cases in TestDigestCoalescing and TestPurchaseMailAfterCommit.
- T-13-29: ResolvePublic with disabled, regenerated, case-folded and malformed tokens tested.
- T-13-30: the purchase mail args carry an id and two names only, and the worker logs the subscriber id only.
User Setup Required
None.
Next Phase Readiness
- 13-05 can reuse
classes.ResolvePublic(ctx, db, token, "wishlist")forpublic-wishlist/{token}. Thewishlistseed state already holds a shared wishlist (share:wishlist) with three albums. - 13-06 should add the wishlist write routes to
write_endpoints_fuzz_test.goand the wishlist routes to the Phase 13 route-table test. - Phase 14 consumes the queued
golem15.fonoteka.wishlist_digestjobs (fonoteka_wishlist_digest) and the digest-queue rows.
Self-Check: PASSED
- Created files exist: all 22
key-files.createdpaths checked withtest -f. - Commits exist in fonoteka.go: 338f518, ff38bad, 791daad, 37405f0.
- Plan verification:
- fonoteka.go
go vet ./...(root, plugin, parity) is clean. go testis green for the root, parity and the plugin's subpackages; the plugin package is green except the pre-existing TestPhase09SecurityRoutes.- The corpus has 143 routes ported and passing.
nuxt-wishlist,mcp-wishlistand the three new broadcast goldens pass.check_corpus --require-recorded --check-secretsis green.
- fonoteka.go