20 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, user_plugin_head_before, user_plugin_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 | user_plugin_head_before | user_plugin_head_after | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 13-p-ytarium-api-wishlist-notifications-csv-credentials-public | 02 | api |
|
|
|
|
|
|
|
|
|
|
|
42min | 2026-10-03 | complete | 75b89dd24255fa5aa227fc30552ebba4a40992f5 | ec054a2c8ee2d887dd4899bb07fb01cda720ce3d | c258e9fade235bf7414ed02478ca99925e97d360 | d80d7990f6f8b743c4df2f0fa9538d9e2acf780e |
Phase 13 Plan 02: Notifications, credentials, onboarding and the register hook Summary
The bell (list, count, clear), personal and organisation AI and Discogs keys, the first-run owner bootstrap, public invitation inspection and the invitation-aware register hook now run in Go with PHP's bytes, error pages and resolution order: 13 routes re-recorded from the fonoteka reset and ported (113 total), plus a recorded and replayed onboarding journey, through an additive sm-user-plugin change.
Performance
- Duration: 42 min
- Started: 2026-10-03T04:46:57Z
- Completed: 2026-10-03T05:29:00Z
- Tasks: 3 of 3
- Files modified: 30 source/test files plus 51 route fixtures and one flow fixture (fonoteka.go and the user submodule)
Accomplishments
- Notifications.
GET notificationsreads the payload column as raw JSON, so PHP's stored key order and escapes reach the client unchanged;unread-count,read-alland{id}/readare scoped byuser_id. A foreign, missing or out-of-int4-range id is the Winter 404 page. - Credentials. The seven handlers follow each PHP controller's order:
- organisation store provisions an org-less caller, then checks
canManage(403), then validates; - Discogs store trims and validates the token, then reuses the stored token, then provisions and checks (403 with the localized
shared_forbiddentext), then writes the user row and the organisation mirror (or the mirror delete) in one transaction. Every$request->validate()or missing-secret failure is the Winter 500 page. Writes fill onlyCredentialFillFieldsplus the secret aslagoon.NewEncrypted; upserts lock the row by id, so an existing secret never has to decrypt.
- organisation store provisions an org-less caller, then checks
- AI resolver.
ResolveAIConfigports the four tiers.AdminVisionModelis the package-level seam for the global vision model: none until Phase 14 (INTG-02), which matches PHP with no global model.AIAllowednow admits a site admin only when that seam returns a model. - User plugin (D-09, D-11).
RegisterEvent.Payloadand the exported registration steps (RegisterUser,RegisterOptions,FireRegisterEvent,IssueToken,APIArray) are additive;Registercalls them in its old order. The/registercorpus case andTestUserAPINuxtFlowsreplay unchanged. - Onboarding.
statuscounts live users.bootstrapanswers 409 before validation. Otherwise it validates (500 page), then takespg_advisory_xact_lock(hashtext('fonoteka:onboarding:bootstrap')), recounts, creates the organisation (Str::slug), registers the activated owner throughRegisterUserand fires the event after commit. It answers{"token","user"}exactly as/registerdoes.
- Register listener (D-10). A valid
invitation_token(sha256 match, pending, unexpired, trimmed lowercased email match) upserts the pending registration onuser_id; anything else provisions the user's collection. - Inspection. Public
GET invitations/{token}answers pending with the collection name, else unavailable. - Parity. New seed states
notifications,credentials,emptyandinvite-for-registerexist on both sides. The 13 routes were re-recorded and ported (51 cases), and the onboarding routes replay on their own databases.fixtures/nuxt/onboarding.yamlwas recorded with the two-run invite recipe and replays on an empty database.
Task Commits
fonoteka.go (master, not pushed):
- Task 1: bell list -
93dd666(feat) - Task 2: notification clearing and credentials -
9cf3a66(feat) - Task 3: sm-user-plugin pointer bump -
473d37f(chore) - Task 3: onboarding, inspection, register listener -
ec054a2(feat)
sm-user-plugin submodule (master, not pushed):
- Task 3: RegisterEvent.Payload and the registration exports -
d80d799(feat)
Decisions Made
See key-decisions above.
Deviations from Plan
Auto-fixed Issues
1. [Rule 1 - Bug] Notification ids above the int4 range answered an opaque 500
- Found during: Task 2
- Issue:
pathIDaccepts uint32, and Postgres refused to bind 4000000000 for theintegerid, soPOST notifications/4000000000/readanswered the opaque 500 instead of PHP's 404. - Fix:
MarkNotificationReadtreats 0 and ids abovemath.MaxInt32as not found. The same gap on otherpathIDroutes is logged in deferred-items.md. - Files modified: classes/notifications.go
- Commit: 9cf3a66
2. [Rule 3 - Blocking] Route-inventory tests broke on the newly mounted routes
- Found during: Task 2 (Task 1's commit ran only TestParityCorpus, so
TestParityContractwas already failing after 93dd666) - Issue:
TestParityContractallow-lists ported routes.TestPhase08Coverageasserted the wholediscogsfamily absent.TestRequirePasswordChangeExemptSetrequiredinv.must-change-passwordon every/_fonoteka/api/v1route. - Fix:
phase13SeedRouteslists the Phase 13 ported routes.- The discogs subtests now assert that the credential routes are JWT-only, that
/discogs-credential/testis absent and that the import and cover-price routes are absent. - A
publicFonotekaRoutesset asserts that the three public routes carrythrottle:10,1and neitherjwt.authnor the password-change lock.
- Commits: 9cf3a66, ec054a2
3. [Rule 1 - Test isolation] My unreadable-payload row broke T-12-23 on the shared test database
- Found during: Task 2
- Issue: T-12-23 casts every
album_addedpayload to jsonb, and my seedednot jsonrow was of that type. - Fix: my seeded rows now use another type, and the unreadable row is deleted on cleanup.
- Commit: 9cf3a66
4. [Rule 2 - Security] No secret or password literal in fixtures
- Found during: Tasks 2 and 3
- Issue: tide refuses unclassified password values, and check_corpus refuses 64-hex values.
- Fix: the request bodies use
{{secret:...}}vars, set by both seeds. - Commits: 9cf3a66, ec054a2
5. [Rule 1 - Contract] The Discogs store rules follow the PHP source, not the plan's wording
- Found during: Task 2
- Issue: the PHP source has
token nullable|string|regexandshared nullable|boolean; the plan paraphrased them asrequired/sometimes. - Fix: the recordings confirm PHP's behaviour, and the port follows the source.
- Commit: 9cf3a66
6. [Rule 3 - Blocking] assertPortedMismatch used GET ai-credential as its unported example
- Found during: Task 2
- Fix: it now uses
POST ai-credential/test, which stays pending until Phase 14. - Commit: 9cf3a66
Total deviations: 6 auto-fixed: 2 bugs, 2 blocking test inventories, 1 security hardening, 1 contract correction. Impact: the response contracts match PHP. The interface changes from the plan are ListNotifications returning NotificationEntry, and the lang files needing no change because discogs.shared_forbidden was already ported in pl and en.
Issues Encountered
TestPhase09SecurityRoutes(pre-existing, 12.2 cabana routes) still fails; it is logged in deferred-items.md from 13-01.go test ./...at the fonoteka.go root covers only the root module; the plugin modules of the workspace were run explicitly (./plugins/golem15/fonoteka/... ./plugins/golem15/user/...).- The
FORCE_COLOR=3shell variable was unset for test runs, as in 13-01.
Known Stubs
None. classes.AdminVisionModel reports no global vision model until Phase 14 ports the Golem15.Golem setting (INTG-02). That is a named dependency boundary that matches PHP with no global model, not a stub. The two credential /test routes stay unmounted and pending (D-02).
Threat Flags
None beyond the plan's register:
- T-13-08 is mitigated: TestCredentialSecretsNeverSerialized scans the bodies, the serialized models, the resolver output, the stored columns and the captured slog output.
- T-13-09, T-13-10, T-13-18, T-13-19, T-13-20, T-13-21 and T-13-27 are mitigated and tested as planned.
- T-13-11 is accepted:
base_urlis stored only, andResolveAIConfigdocuments that the Phase 14 client owns the SSRF guard.
User Setup Required
None.
Next Phase Readiness
- 13-03 (wishlist) can reuse the
notificationsseed state andWriteNotification. Add the wishlist routes tophase13SeedRoutesandfonotekaRouteExtras. - 13-06 (unit tests and fuzz) should add the credential, notification and onboarding write routes to
write_endpoints_fuzz_test.go. Per C-04, this plan did not touch it. - The sm-user-plugin commit
d80d799is local to the submodule; push it withssuwhen the user asks.
Self-Check: PASSED
- Created files exist: all twelve
key-files.createdpaths checked withtest -f. - Commits exist: 93dd666, 9cf3a66, 473d37f, ec054a2 (fonoteka.go); d80d799 (sm-user-plugin).
- Plan verification:
go vet ./...is clean for the root and both plugin modules.go testis green for the root, parity and sm-user-plugin. The fonoteka plugin is green except the pre-existingTestPhase09SecurityRoutes. The parity corpus is at 113 ported and passing.TestFonotekaNuxtFlows/onboardingandTestUserAPINuxtFlowspass.check_corpus --require-recorded --check-secretsis green.