docs(13-02): complete notifications, credentials and onboarding plan
This commit is contained in:
@@ -0,0 +1,300 @@
|
||||
---
|
||||
phase: 13-p-ytarium-api-wishlist-notifications-csv-credentials-public
|
||||
plan: 02
|
||||
subsystem: api
|
||||
tags: [notifications, credentials, lagoon-encrypted, onboarding, invitations, sm-user-plugin, register-event, parity, tide]
|
||||
|
||||
requires:
|
||||
- phase: 13-01
|
||||
provides: lagoon prohibited rule, check_corpus ported case-status check, php_parity.sh rows/QUEUE_CONNECTION, D-15 manifest fix
|
||||
- phase: 12
|
||||
provides: fonoteka seed hook and PARITY_CASE recipe, Winter error pages, invitation service and acceptance guard, ProvisionOrgFor, ProvisionCollection
|
||||
provides:
|
||||
- "Notifications: GET notifications (50 newest, stored payload bytes), unread-count, read-all, {id}/read (Winter 404 for foreign/missing/out-of-range ids)"
|
||||
- "Credentials: ai-credential GET/POST, org-ai-credential GET/POST/DELETE, discogs-credential GET/POST with PHP's check order, bodies and Winter 500 pages; secrets only as lagoon.Encrypted"
|
||||
- "classes.ResolveAIConfig with PHP AiConfigResolver tiers, AIConfig, ErrNoAICredential, VisionModelFor and the AdminVisionModel seam (none until Phase 14); AIAllowed's site-admin branch reads it"
|
||||
- "sm-user-plugin: RegisterEvent.Payload (input minus password keys), RegisterUser/RegisterOptions/FireRegisterEvent/IssueToken/APIArray, README"
|
||||
- "Onboarding status/bootstrap (409 before validation, advisory-locked recount), public GET invitations/{token} inspection, fonoteka RegisterEvent listener (pending registration or collection provisioning)"
|
||||
- "Parity: notifications, credentials, empty, invite-for-register seed states on both sides; 13 routes re-recorded and ported (113); fixtures/nuxt/onboarding.yaml and TestFonotekaNuxtFlows/onboarding"
|
||||
affects: [13-03, 13-04, 13-05, 13-06, 14]
|
||||
|
||||
actuals:
|
||||
tokens: 67300
|
||||
tasks: 3
|
||||
commits: 5
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "Raw JSON passthrough: a jsonable column whose key order is the contract is read as json.RawMessage and emitted unchanged"
|
||||
- "Credential upserts lock the owner row by id only (SELECT id ... FOR UPDATE) and update selected columns, so a secret stored under another key never has to decrypt"
|
||||
- "Fixtures carry test secrets and passwords only as {{secret:...}} vars written by the reset and set by the Go seed (parityCredentialSecrets)"
|
||||
- "Parity routes whose cases need disruptive state (no live user) replay on their own database (isolatedParityRoutes)"
|
||||
- "Cross-plugin registration: other plugins create users through the user plugin's exported RegisterUser/FireRegisterEvent/IssueToken/APIArray, never by duplicating /register"
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- fonoteka.go/plugins/golem15/fonoteka/classes/notifications.go
|
||||
- fonoteka.go/plugins/golem15/fonoteka/classes/ai_config_resolver.go
|
||||
- fonoteka.go/plugins/golem15/fonoteka/classes/onboarding.go
|
||||
- fonoteka.go/plugins/golem15/fonoteka/controllers/api/notifications_controller.go
|
||||
- fonoteka.go/plugins/golem15/fonoteka/controllers/api/credentials_controller.go
|
||||
- fonoteka.go/plugins/golem15/fonoteka/controllers/api/onboarding_controller.go
|
||||
- fonoteka.go/plugins/golem15/fonoteka/controllers/api/invitation_inspect_controller.go
|
||||
- fonoteka.go/plugins/golem15/fonoteka/notifications_smoke_test.go
|
||||
- fonoteka.go/plugins/golem15/fonoteka/credentials_smoke_test.go
|
||||
- fonoteka.go/plugins/golem15/fonoteka/onboarding_smoke_test.go
|
||||
- fonoteka.go/plugins/golem15/user/controllers/registration.go
|
||||
- fonoteka.go/parity/fixtures/nuxt/onboarding.yaml
|
||||
modified:
|
||||
- fonoteka.go/plugins/golem15/fonoteka/classes/credential_write_service.go
|
||||
- fonoteka.go/plugins/golem15/fonoteka/classes/gates.go
|
||||
- fonoteka.go/plugins/golem15/fonoteka/classes/invitation_service.go
|
||||
- fonoteka.go/plugins/golem15/fonoteka/classes/php_values.go
|
||||
- fonoteka.go/plugins/golem15/fonoteka/plugin.go
|
||||
- fonoteka.go/plugins/golem15/fonoteka/routes.go
|
||||
- fonoteka.go/plugins/golem15/fonoteka/routes_table_phase12_test.go
|
||||
- fonoteka.go/plugins/golem15/fonoteka/routes_isolation_test.go
|
||||
- fonoteka.go/plugins/golem15/fonoteka/phase08_coverage_test.go
|
||||
- fonoteka.go/plugins/golem15/fonoteka/handler_failures_test.go
|
||||
- fonoteka.go/plugins/golem15/user/classes/events.go
|
||||
- fonoteka.go/plugins/golem15/user/controllers/api_controller.go
|
||||
- fonoteka.go/plugins/golem15/user/register_test.go
|
||||
- fonoteka.go/plugins/golem15/user/README.md
|
||||
- fonoteka.go/parity/manifest.yaml
|
||||
- fonoteka.go/parity/fixtures/routes/ (13 routes, 51 fixtures)
|
||||
- fonoteka.go/parity/fonoteka_seed_test.go
|
||||
- fonoteka.go/parity/fonoteka_reset.php
|
||||
- fonoteka.go/parity/fonoteka_flows_test.go
|
||||
- fonoteka.go/parity/parity_test.go
|
||||
- fonoteka.go/parity/parity_contract_test.go
|
||||
- fonoteka.go/parity/README.md
|
||||
|
||||
key-decisions:
|
||||
- "The Discogs store ports PHP's actual rules (token nullable|string|regex, shared nullable|boolean), not the plan's paraphrase (token required, shared sometimes): an absent token reuses the stored one, a null or blank token is the 500 page, and a manager's not-shared store deletes the organisation's Discogs credential as PHP does."
|
||||
- "Credential secrets, onboarding passwords and a well-formed unknown invitation token are fixed test-only vars (secret:ai-key, secret:discogs-token, secret:owner-password, secret:short-password, secret:unknown-invite) written by the reset and the Go seed, so no key, token or password literal reaches a fixture and check_corpus --check-secrets stays green."
|
||||
- "RegisterEvent.Payload is the register input as read (plus /register's password_confirmation default), minus password and password_confirmation; the IP columns PHP adds to $data are not in it (they are on the user row). The bootstrap fires it with the validated data, as PHP passes $reg."
|
||||
- "The 'empty' onboarding state soft-deletes every user on both sides; the two onboarding routes replay on databases of their own and every empty case is recorded from a fresh php_parity.sh reset, so the shared replay database and the PHP instance never carry stray users."
|
||||
- "ListNotifications returns NotificationEntry (raw payload) instead of the plan's []models.Notification, because the model's Jsonable[map] re-marshals keys alphabetically and the contract is PHP's stored key order."
|
||||
- "Commits went to master in fonoteka.go and the sm-user-plugin submodule (not pushed), as the orchestrator directed for this sequential run and as 13-01 did; the GSD protected-branch check reports master as protected."
|
||||
|
||||
patterns-established:
|
||||
- "Phase 13 ported routes are listed in parity_contract_test.go phase13SeedRoutes and mapped to their seed states through fonotekaRouteExtras (route default) and fonotekaCaseExtras (exceptions)."
|
||||
- "Public (unauthenticated) /_fonoteka/api/v1 routes are listed in routes_isolation_test.go publicFonotekaRoutes and must carry throttle:10,1 and neither jwt.auth nor inv.must-change-password."
|
||||
|
||||
requirements-completed: [API-04, API-06, API-07]
|
||||
|
||||
coverage:
|
||||
- id: D1
|
||||
description: "Bell list: the caller's 50 newest notifications, newest id first, payload bytes as stored (PHP key order), Carbon times, [] when none"
|
||||
requirement: API-04
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "fonoteka.go/plugins/golem15/fonoteka/notifications_smoke_test.go#TestNotificationsRoutes"
|
||||
status: pass
|
||||
- kind: integration
|
||||
ref: "fonoteka.go/parity/parity_test.go#TestParityCorpus/GET___fonoteka_api_v1_notifications_jwt"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D2
|
||||
description: "unread-count, read-all and {id}/read scoped to the caller; foreign, missing and out-of-range ids are the Winter 404 page; reading a read row again is 200"
|
||||
requirement: API-04
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "fonoteka.go/plugins/golem15/fonoteka/notifications_smoke_test.go#TestNotificationsRoutes"
|
||||
status: pass
|
||||
- kind: integration
|
||||
ref: "fonoteka.go/parity/parity_test.go#TestParityCorpus (notifications unread-count, read-all, {id}/read)"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D3
|
||||
description: "AI, organisation AI and Discogs credential CRUD with PHP's order of checks, bodies, 403/404 guards, provisioning before the guard and the Winter 500 page for every validation failure and missing secret; secrets stored only as ciphertext and never echoed or logged"
|
||||
requirement: API-06
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "fonoteka.go/plugins/golem15/fonoteka/credentials_smoke_test.go#TestCredentialsCRUD"
|
||||
status: pass
|
||||
- kind: unit
|
||||
ref: "fonoteka.go/plugins/golem15/fonoteka/credentials_smoke_test.go#TestCredentialSecretsNeverSerialized"
|
||||
status: pass
|
||||
- kind: unit
|
||||
ref: "fonoteka.go/plugins/golem15/fonoteka/credentials_smoke_test.go#TestDiscogsSharedMirror"
|
||||
status: pass
|
||||
- kind: integration
|
||||
ref: "fonoteka.go/parity/parity_test.go#TestParityCorpus (7 credential routes, 33 recorded cases)"
|
||||
status: pass
|
||||
- kind: other
|
||||
ref: "go run ./parity/check_corpus.go --manifest parity/manifest.yaml --routes .../routes.php --require-recorded --check-secrets"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D4
|
||||
description: "ResolveAIConfig walks PHP's tiers (admin global model via AdminVisionModel, org lock, personal, organisation, ErrNoAICredential) with provider defaults; AIAllowed admits a site admin only with a global model"
|
||||
requirement: API-06
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "fonoteka.go/plugins/golem15/fonoteka/credentials_smoke_test.go#TestResolveAIConfigPrecedence"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D5
|
||||
description: "sm-user-plugin RegisterEvent.Payload without password keys and the RegisterUser/FireRegisterEvent/IssueToken/APIArray exports; /register's statuses and bodies unchanged"
|
||||
requirement: API-07
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "fonoteka.go/plugins/golem15/user/register_test.go#TestRegisterEventPayload"
|
||||
status: pass
|
||||
- kind: integration
|
||||
ref: "fonoteka.go/parity/parity_test.go#TestParityCorpus/POST___user_api_v1_register_user-api and TestUserAPINuxtFlows"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D6
|
||||
description: "Onboarding status and bootstrap (one owner and one organisation under concurrency, 409 before validation, Winter 500 on invalid input), public invitation inspection, and the register listener holding an invited registrant at acceptance"
|
||||
requirement: API-07
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "fonoteka.go/plugins/golem15/fonoteka/onboarding_smoke_test.go#TestBootstrapConcurrent"
|
||||
status: pass
|
||||
- kind: unit
|
||||
ref: "fonoteka.go/plugins/golem15/fonoteka/onboarding_smoke_test.go#TestRegisterInvitationListener"
|
||||
status: pass
|
||||
- kind: unit
|
||||
ref: "fonoteka.go/plugins/golem15/fonoteka/onboarding_smoke_test.go#TestInspectInvitation"
|
||||
status: pass
|
||||
- kind: e2e
|
||||
ref: "fonoteka.go/parity/fonoteka_flows_test.go#TestFonotekaNuxtFlows/onboarding"
|
||||
status: pass
|
||||
- kind: integration
|
||||
ref: "fonoteka.go/parity/parity_test.go#TestParityCorpus/coverage (113 ported, 113 passing)"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
|
||||
duration: 42min
|
||||
completed: 2026-10-03
|
||||
status: complete
|
||||
plan_head_before: 75b89dd24255fa5aa227fc30552ebba4a40992f5
|
||||
plan_head_after: ec054a2c8ee2d887dd4899bb07fb01cda720ce3d
|
||||
user_plugin_head_before: c258e9fade235bf7414ed02478ca99925e97d360
|
||||
user_plugin_head_after: 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 notifications` reads the payload column as raw JSON, so PHP's stored key order and escapes reach the client unchanged; `unread-count`, `read-all` and `{id}/read` are scoped by `user_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_forbidden` text), 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 only `CredentialFillFields` plus the secret as `lagoon.NewEncrypted`; upserts lock the row by id, so an existing secret never has to decrypt.
|
||||
- **AI resolver.** `ResolveAIConfig` ports the four tiers. `AdminVisionModel` is the package-level seam for the global vision model: none until Phase 14 (INTG-02), which matches PHP with no global model. `AIAllowed` now admits a site admin only when that seam returns a model.
|
||||
- **User plugin (D-09, D-11).** `RegisterEvent.Payload` and the exported registration steps (`RegisterUser`, `RegisterOptions`, `FireRegisterEvent`, `IssueToken`, `APIArray`) are additive; `Register` calls them in its old order. The `/register` corpus case and `TestUserAPINuxtFlows` replay unchanged.
|
||||
- **Onboarding.**
|
||||
- `status` counts live users.
|
||||
- `bootstrap` answers 409 before validation. Otherwise it validates (500 page), then takes `pg_advisory_xact_lock(hashtext('fonoteka:onboarding:bootstrap'))`, recounts, creates the organisation (`Str::slug`), registers the activated owner through `RegisterUser` and fires the event after commit. It answers `{"token","user"}` exactly as `/register` does.
|
||||
- **Register listener (D-10).** A valid `invitation_token` (sha256 match, pending, unexpired, trimmed lowercased email match) upserts the pending registration on `user_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`, `empty` and `invite-for-register` exist 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.yaml` was recorded with the two-run invite recipe and replays on an empty database.
|
||||
|
||||
## Task Commits
|
||||
|
||||
fonoteka.go (master, not pushed):
|
||||
1. **Task 1: bell list** - `93dd666` (feat)
|
||||
2. **Task 2: notification clearing and credentials** - `9cf3a66` (feat)
|
||||
3. **Task 3: sm-user-plugin pointer bump** - `473d37f` (chore)
|
||||
4. **Task 3: onboarding, inspection, register listener** - `ec054a2` (feat)
|
||||
|
||||
sm-user-plugin submodule (master, not pushed):
|
||||
1. **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:** `pathID` accepts uint32, and Postgres refused to bind 4000000000 for the `integer` id, so `POST notifications/4000000000/read` answered the opaque 500 instead of PHP's 404.
|
||||
- **Fix:** `MarkNotificationRead` treats 0 and ids above `math.MaxInt32` as not found. The same gap on other `pathID` routes 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 `TestParityContract` was already failing after 93dd666)
|
||||
- **Issue:** `TestParityContract` allow-lists ported routes. `TestPhase08Coverage` asserted the whole `discogs` family absent. `TestRequirePasswordChangeExemptSet` required `inv.must-change-password` on every `/_fonoteka/api/v1` route.
|
||||
- **Fix:**
|
||||
- `phase13SeedRoutes` lists the Phase 13 ported routes.
|
||||
- The discogs subtests now assert that the credential routes are JWT-only, that `/discogs-credential/test` is absent and that the import and cover-price routes are absent.
|
||||
- A `publicFonotekaRoutes` set asserts that the three public routes carry `throttle:10,1` and neither `jwt.auth` nor 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_added` payload to jsonb, and my seeded `not json` row 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|regex` and `shared nullable|boolean`; the plan paraphrased them as `required`/`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=3` shell 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_url` is stored only, and `ResolveAIConfig` documents that the Phase 14 client owns the SSRF guard.
|
||||
|
||||
## User Setup Required
|
||||
|
||||
None.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
- 13-03 (wishlist) can reuse the `notifications` seed state and `WriteNotification`. Add the wishlist routes to `phase13SeedRoutes` and `fonotekaRouteExtras`.
|
||||
- 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 `d80d799` is local to the submodule; push it with `ssu` when the user asks.
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
- Created files exist: all twelve `key-files.created` paths checked with `test -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 test` is green for the root, parity and sm-user-plugin. The fonoteka plugin is green except the pre-existing `TestPhase09SecurityRoutes`. The parity corpus is at 113 ported and passing. `TestFonotekaNuxtFlows/onboarding` and `TestUserAPINuxtFlows` pass. `check_corpus --require-recorded --check-secrets` is green.
|
||||
@@ -7,3 +7,9 @@ Out-of-scope findings logged by plan executors. Not fixed by the plan that found
|
||||
- **fonoteka.go `TestPhase09SecurityRoutes` fails on the current framework.** `plugins/golem15/fonoteka/admin_phase09_security_test.go:52` reports the cabana admin file and relation routes (`/plytadmin/api/v1/{vendor}/{plugin}/{controller}/{id}/files/...`, `.../relations/{name}/records/{child}...`, `.../relations/{name}/pivot/{child}`) as unexpected. They were added by Phase 12.2 commits e54fd25, afb05b6 and fe9e8ba in summercms.go; the Phase 9 assembled-route inventory in fonoteka.go was not updated. Unrelated to 13-01 (no route was added or removed by the surf overlap change; `Routes()` is unchanged). Fix: extend the test's expected admin route set, or confirm with the 12.2 owner.
|
||||
- **`FORCE_COLOR=3` in the agent shell fails `modules/bonfire` TestColorPolicy and TestInjectedOutputCapture.** They pass with the variable unset; the suite was run with `env -u FORCE_COLOR`. Environment, not code.
|
||||
- **gofmt drift in files 13-01 did not touch:** `fonoteka.go/plugins/golem15/fonoteka/household_smoke_test.go`, `summercms.go/modules/tide/flow_test.go`, `summercms.go/modules/tide/headers_test.go`.
|
||||
|
||||
## From 13-02
|
||||
|
||||
- **Path ids between 2^31 and 2^32 bind-fail into an opaque 500 on other routes.** `pathID` (`fonoteka.go/plugins/golem15/fonoteka/controllers/api/collections_controller.go:264`) parses up to uint32, but the id columns are Postgres `integer`, so for example `DELETE /household/invitations/3000000000` fails to encode the argument and answers the opaque 500 where PHP answers its 404. 13-02 guards only `MarkNotificationRead` (`id > math.MaxInt32` is not found). Fix: have `pathID` treat values above `math.MaxInt32` as 0 (no row), or guard each caller.
|
||||
- **A Phase 2 fixture of a still-pending route carries a masked provider key text.** `parity/fixtures/routes/POST___fonoteka_api_v1_ai-credential_test_jwt.yaml:21` holds OpenAI's error text with `sk-parit******real` (a fake, masked value). The route stays pending until Phase 14 (D-02), which re-records it.
|
||||
- **`TestPhase09SecurityRoutes` and the `household_smoke_test.go` gofmt drift** from 13-01's list are still open; 13-02 did not touch them.
|
||||
|
||||
Reference in New Issue
Block a user