docs(11): verify gap closure
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
---
|
||||
phase: 11-jobs-realtime-and-search-infrastructure
|
||||
verified: 2026-09-30T13:28:50Z
|
||||
status: gaps_found
|
||||
score: 4/5 roadmap success criteria verified (SC-5 partial); plan truths 66/70 verified, 1 failed, 3 backstop (insufficient_spec, routed to human)
|
||||
verified: 2026-09-30T20:26:54Z
|
||||
status: human_needed
|
||||
score: 5/5 roadmap success criteria verified; plan truths 67/70 verified, 3 backstop (insufficient_spec, routed to human)
|
||||
covered_files:
|
||||
- ".planning/phases/11-jobs-realtime-and-search-infrastructure/11-01-PLAN.md"
|
||||
- ".planning/phases/11-jobs-realtime-and-search-infrastructure/11-01-SUMMARY.md"
|
||||
@@ -18,6 +18,8 @@ covered_files:
|
||||
- ".planning/phases/11-jobs-realtime-and-search-infrastructure/11-06-SUMMARY.md"
|
||||
- ".planning/phases/11-jobs-realtime-and-search-infrastructure/11-07-PLAN.md"
|
||||
- ".planning/phases/11-jobs-realtime-and-search-infrastructure/11-07-SUMMARY.md"
|
||||
- ".planning/phases/11-jobs-realtime-and-search-infrastructure/11-08-PLAN.md"
|
||||
- ".planning/phases/11-jobs-realtime-and-search-infrastructure/11-08-SUMMARY.md"
|
||||
- "README.md"
|
||||
- "cmd/summer/main.go"
|
||||
- "cmd/summer/main_test.go"
|
||||
@@ -55,6 +57,7 @@ covered_files:
|
||||
- "modules/bouncer/jwt.go"
|
||||
- "modules/bouncer/jwt_test.go"
|
||||
- "modules/cabana/crud.go"
|
||||
- "modules/cabana/relation.go"
|
||||
- "modules/compass/README.md"
|
||||
- "modules/compass/persist.go"
|
||||
- "modules/compass/persist_test.go"
|
||||
@@ -129,29 +132,19 @@ covered_files:
|
||||
- "scripts/check-phase10.1.sh"
|
||||
- "scripts/check-phase10.sh"
|
||||
- "scripts/check-phase11.sh"
|
||||
covered_digest: "v2:sha256:5e6b25eb51444acda651d1e01476692917ab8ecbf999cd039c3a54ab2f8457f6"
|
||||
covered_files_note: "fonoteka.go files are outside the project root and cannot be fingerprinted. They were checked at fonoteka.go HEAD c222e03 with a clean working tree and are listed in the report body. modules/cabana/crud.go is not a Phase 11 change; it is included because the gap below names it."
|
||||
covered_digest: "v2:sha256:8668b94496d5c813e84363932641ab9a9f277a34db12d3268aa94cae599fafc7"
|
||||
covered_files_note: "fonoteka.go files are outside the project root and cannot be fingerprinted. They were checked at fonoteka.go HEAD 1c88199 with a clean working tree and are listed in the report body."
|
||||
mvp_mode_note: "ROADMAP marks Phase 11 mode: mvp, but the goal is not a User Story and no 11-*-PLAN.md carries one. Following the Phase 1/3/5/8/9/10 precedent, the five ROADMAP success criteria are the contract, User Flow Coverage is derived from them, and plan must_haves are supporting evidence."
|
||||
behavior_unverified: 0
|
||||
overrides_applied: 0
|
||||
gaps:
|
||||
- truth: "Per D-20 (11-05 must-have, supports SC-5/SRCH-01): Searchable sync runs after commit; a rolled-back write sends nothing, and the document is built from the committed row"
|
||||
status: failed
|
||||
reason: "lagoon.AfterCommit runs its callback immediately inside a plain gorm Transaction (no lagoon buffer, no gorm:started_transaction). The framework's admin write path (cabana CRUDService) and fonoteka SaveAlbum both use plain gdb.Transaction, and both call tx.Save(album) before writing the artist pivots. Result on the live admin album form (fields.yaml has an `artists` relation): Typesense is upserted mid-transaction with the pre-edit artist_ids and never corrected; if a later step in the transaction fails, the rollback leaves Typesense holding an upsert (or missing a deleted document) that the database never committed. The behaviour is locked in by passing tests: beachcomber TestSyncAfterCommit/plain_gorm_transaction_syncs_through_the_tx asserts 'an immediate sync from the uncommitted row', and fonoteka search_smoke_test asserts 'a plain gorm transaction syncs immediately through the tx handle'. Same root cause makes the Album `updated` broadcast payload (realtime.go albumPayload) carry the pre-edit artist list on admin edits."
|
||||
artifacts:
|
||||
- path: "modules/lagoon/transaction.go"
|
||||
issue: "AfterCommit falls through to runAfterCommit immediately when called inside a foreign *sql.Tx (lines ~100-117)"
|
||||
- path: "modules/cabana/crud.go"
|
||||
issue: "create/update/delete/bulk-delete run in s.DB.WithContext(ctx).Transaction(...); tx.Save(target) at ~366 precedes syncBelongsToMany at ~373"
|
||||
- path: "../fonoteka.go/plugins/golem15/fonoteka/classes/album_write_service.go"
|
||||
issue: "SaveAlbum uses gdb.WithContext(ctx).Transaction; tx.Save(album) precedes syncArtists and there is no re-save/touch after the pivot sync (PHP AlbumWriteService re-saves)"
|
||||
- path: "modules/beachcomber/sync_test.go"
|
||||
issue: "plain_gorm_transaction_syncs_through_the_tx asserts the incorrect pre-commit behaviour and must be inverted"
|
||||
missing:
|
||||
- "Route cabana CRUDService writes (create, update, delete, bulk delete, relation writes) through lagoon.Transaction so after-commit work waits for the commit"
|
||||
- "Route fonoteka SaveAlbum through lagoon.Transaction (or re-sync after syncArtists)"
|
||||
- "Make lagoon.AfterCommit refuse to run immediately inside a foreign *sql.Tx (skip with a Warn log, or return an error) instead of pushing uncommitted state to an external system"
|
||||
- "A cabana admin-edit test that changes an album's artists and asserts the indexed artist_ids equal the committed pivots, and a plain-transaction rollback test asserting zero engine calls"
|
||||
re_verification:
|
||||
previous_status: gaps_found
|
||||
previous_score: "4/5 roadmap success criteria; plan truths 66/70"
|
||||
gaps_closed:
|
||||
- "CR-01: Searchable sync now runs after commit on Cabana and SaveAlbum writes, refuses unmanaged transactions, sends nothing on rollback, and builds from committed ordered pivots"
|
||||
gaps_remaining: []
|
||||
regressions: []
|
||||
advisory: []
|
||||
behavior_unverified_items: []
|
||||
human_verification:
|
||||
- test: "Start Centrifugo v6 with the production secret layout, run the app's `serve` with real secrets, log in through the unchanged Nuxt app, change an album and watch the event arrive"
|
||||
@@ -174,9 +167,9 @@ human_verification:
|
||||
# Phase 11: Jobs, realtime and search infrastructure — Verification Report
|
||||
|
||||
**Phase Goal:** River jobs run on the correct dual-driver split, Centrifugo publishing and channel authorization match the existing server, and Typesense sync stays a re-gated pre-filter — all brought up before the API phases that depend on them.
|
||||
**Verified:** 2026-09-30T13:28:50Z (summercms.go HEAD 61da4d1, fonoteka.go HEAD c222e03, both trees clean of code changes)
|
||||
**Status:** gaps_found
|
||||
**Re-verification:** No — initial verification
|
||||
**Verified:** 2026-09-30T20:26:54Z (summercms.go HEAD c6f6bdf, fonoteka.go HEAD 1c88199; fonoteka.go clean, summercms.go has only unrelated pre-existing planning/untracked workspace changes)
|
||||
**Status:** human_needed
|
||||
**Re-verification:** Yes — CR-01 gap closure
|
||||
|
||||
**MVP note:** ROADMAP marks this phase `mode: mvp`, but the goal is not a User Story and no plan carries one. Following the precedent of Phases 1, 3, 5, 8, 9 and 10, the five ROADMAP success criteria are the contract.
|
||||
|
||||
@@ -190,7 +183,7 @@ Derived story: *As a host application developer, I want to dispatch River jobs,
|
||||
| Worker picks jobs up promptly | LISTEN wake-up, not poll latency | TestListenPickupLatency re-run: 3ms pickup with a 30s poll; poll-only control not picked up in 2s | ✓ |
|
||||
| Run `queue:work` / `schedule:run` | Foreground worker; scheduler runs registered commands | conga.RuntimeCommands wired into generated main and fonoteka main.go; TestQueueWork, TestScheduleRunForeground pass | ✓ |
|
||||
| Nuxt gets a token and subscribes | Same HS256 claims; proxy re-authorizes | TokenIssuer matches PHP JwtTokenGenerator claim-for-claim; ProxyHandler looks up registry per request; parity routes replay green | ✓ (live Centrifugo: human) |
|
||||
| Album edit reaches Typesense correctly | Committed document, collection-scoped, gated | Scoping and gate correct; **admin edit path indexes pre-commit, stale artist_ids** | ✗ (CR-01) |
|
||||
| Album edit reaches Typesense correctly | Committed document, collection-scoped, gated | Cabana and SaveAlbum use `lagoon.Transaction`; the assembled-router regression compares the one imported document's `artist_ids` with committed ordered pivots | ✓ |
|
||||
|
||||
## Goal Achievement
|
||||
|
||||
@@ -202,27 +195,29 @@ Derived story: *As a host application developer, I want to dispatch River jobs,
|
||||
| 2 | `summer queue:work` runs the worker; `summer schedule:run` runs recurring commands | ✓ VERIFIED (WR-02 warning) | `conga/commands.go` queue:work, schedule:run (daemon and `--once`), queue:clear; appended by `internal/build/build.go:115` and in `../fonoteka.go/main.go:39`; `bonfire.NewCatalog` published (build.go:124). Scheduled worker calls commands through the catalog. Tests: TestQueueWork, TestScheduleRunsCommand, TestScheduleRunForeground, TestScheduleRunOnce, TestScheduleUniqueByPeriod pass in the gate. Caveat WR-02: a scheduled command that opens its own DB via the `withDB` pattern fails inside a running worker (`backpack: duplicate provider for *sql.DB`). |
|
||||
| 3 | Token + subscription JWTs with the same secret/claims/channel names at GET /api/realtime/token; publishes to existing Centrifugo | ✓ VERIFIED (live server: human) | `centrifugo/token.go` ports all five PHP generators with identical claims (`sub` string id, `exp`, `info.name` only; anonymous 300s; identifier info `[]`). `TokenHandler` 401/503/200 bodies match PHP routes.php. Client posts `/publish` and `/broadcast` with `Authorization: apikey`, 5s timeout. fonoteka mounts with `jwt.auth` + `throttle:ws-api` (routes.go:86). Album channels `collection:<id>` only for kind=collection. TestTokenClaims, TestTokenHandler, TestClientRequests, parity token/subscribe routes and TestBroadcastGoldens (deleted + bulk vs PHP goldens) pass; created/updated goldens pending Phase 12 by design. |
|
||||
| 4 | Namespace authorizer registry re-validates every subscribe; broadcastable model with bulk suppression emits exactly one summary event | ✓ VERIFIED | `ProxyHandler` calls `svc.Registry().Get(namespace)` and `Authorize` per request, no cache; constant-time secret compare; generic deny body. fonoteka registers `collection` and `wishlist` authorizers. `WithoutBroadcasting[T]` + `Service.Emit`; `TestBulkEmitsOnce` asserts *exactly* one publication (`waitPublications` fails on extras) and none on rollback. Broadcast callbacks now register `.Before("gorm:commit_or_rollback_transaction")` (deferred item 1 fixed). TestProxy, TestWsAuthorizer, TestSuppression, TestBroadcastTx pass. |
|
||||
| 5 | Typesense sync scoped by collection_id behind a settings kill-switch, degrades gracefully without DB/config | ⚠ PARTIAL | The literal clauses hold: `Album.ToSearchableArray` refuses collection_id 0; `settingsGate` reads `search_use_typesense` per sync and treats read errors as off; `syncOne` returns early with no engine/api_key or no published DB. **But** the sync contract underneath it (D-20, after-commit, built from the committed row) fails on the framework admin write path — see Gap 1. |
|
||||
| 5 | Typesense sync scoped by collection_id behind a settings kill-switch, degrades gracefully without DB/config | ✓ VERIFIED | `Album.ToSearchableArray` refuses collection_id 0; `settingsGate` reads `search_use_typesense` per sync and treats read errors as off; `syncOne` returns early with no engine/api_key or no published DB. CR-01 is closed: Cabana writes and `SaveAlbum` use `lagoon.Transaction`, unmanaged GORM transactions are warned and skipped, and `TestAlbumsAdminSearchUsesCommittedArtists`, `TestSaveAlbumDefersAfterCommitUntilArtistsSync`, `TestTransactionAfterCommit`, and `TestSyncAfterCommit` prove committed-row/pivot timing and rollback silence. |
|
||||
|
||||
### Plan must-have truths (supporting evidence)
|
||||
|
||||
70 truths across 7 plans. 66 verified by code reading plus the named tests the gate runs (all pass, none skipped except the two declared Phase 12 goldens); 3 are `verification: backstop` (11-02 multi-process leader election, 11-03 delivery order, 11-07 real clients) and route to human verification as `insufficient_spec`; 1 failed:
|
||||
70 truths carried forward from the initial verification. 67 are verified by code reading plus the named tests the gate runs (all pass, none skipped except the two declared Phase 12 goldens); 3 are `verification: backstop` (11-02 multi-process leader election, 11-03 delivery order, 11-07 real clients) and route to human verification as `insufficient_spec`. The sole failed truth is now closed by Plan 11-08:
|
||||
|
||||
**Score:** 5/5 roadmap success criteria verified; 67/70 plan truths verified (3 backstop checks routed to human).
|
||||
|
||||
| Plan | Truth | Status | Evidence |
|
||||
|------|-------|--------|----------|
|
||||
| 11-05 | D-20: sync after commit; rolled-back write sends nothing | ✗ FAILED | Holds for `lagoon.Transaction` and implicit single-statement transactions, fails for plain `gorm.Transaction`, which is how cabana admin CRUD and SaveAlbum write. Passing tests assert the pre-commit sync. |
|
||||
| 11-01 | "outside any lagoon-managed transaction the callback runs immediately" | ✓ VERIFIED as written | This is the design that produces Gap 1. The two plan truths contradict each other on the framework's main write path. |
|
||||
| 11-05 | D-20: sync after commit; rolled-back write sends nothing | ✓ VERIFIED | Cabana CRUD create/update/delete/bulk-delete and relation Link/Unlink, plus fonoteka `SaveAlbum`, now use `lagoon.Transaction`. Foreign plain GORM transactions are refused. Focused behavioral tests prove one post-commit import with committed artist order and zero calls on rollback. |
|
||||
| 11-01 | Outside a Lagoon-managed or implicit single-statement transaction, callbacks run immediately | ✓ VERIFIED (clarified) | Truly non-transactional handles still run immediately. A foreign `gorm.TxCommitter` is now explicitly detected, warned, and skipped because Lagoon cannot observe its commit. |
|
||||
|
||||
### Required Artifacts
|
||||
|
||||
| Artifact | Status | Details |
|
||||
|----------|--------|---------|
|
||||
| `modules/lagoon/queue_migrations.go`, `ondatabase.go`, `transaction.go` | ✓ VERIFIED | Migrations wired from `migrations.go`; OnDatabase drained by Publish; Transaction/AfterCommit present (see gap for semantics) |
|
||||
| `modules/lagoon/queue_migrations.go`, `ondatabase.go`, `transaction.go` | ✓ VERIFIED | Migrations wired from `migrations.go`; OnDatabase drained by Publish; Transaction buffers callbacks, validates exact parent ownership, and refuses unmanaged transactions |
|
||||
| `modules/conga/*` (conga, worker, client, commands, schedule, scheduler, job, record) | ✓ VERIFIED | Substantive, wired into serve (`surf/serve.go:57`), generated main and fonoteka main |
|
||||
| `modules/pact/capabilities.go`, `modules/bonfire/call.go` | ✓ VERIFIED | HasSchedule/Daily/DailyAt/Every; Catalog/Call used by scheduler |
|
||||
| `modules/lighthouse/*`, `modules/lighthouse/centrifugo/*` | ✓ VERIFIED | Registry, Mount, Broadcastable, suppression, token issuer, proxy, HTTP client, health command |
|
||||
| `modules/flare/*` | ✓ VERIFIED | RFC 8291 encryption, VAPID, commands; registered by fonoteka Commands() |
|
||||
| `modules/beachcomber/*`, `beachcomber/typesense/*` | ✓ VERIFIED (exists, wired) / ✗ semantics | Wired through OnDatabase and fonoteka `wireSearch`; AfterCommit semantics defect (Gap 1) |
|
||||
| `modules/beachcomber/*`, `beachcomber/typesense/*` | ✓ VERIFIED | Wired through OnDatabase and fonoteka `wireSearch`; after-commit and rollback semantics are covered by `TestSyncAfterCommit` |
|
||||
| `modules/tide/centrifugo.go`, `centrifugo_golden.go`, `cmd/summer/parity.go` | ✓ VERIFIED | Loopback recorder, goldens, parity:broadcasts |
|
||||
| `../fonoteka.go/plugins/golem15/fonoteka/{realtime,search,schedule,routes}.go`, `classes/ws/*`, `models/album_search.go` | ✓ VERIFIED | Authorizers, Album binding, settings gate, daily prune entry, route mount |
|
||||
| `../fonoteka.go/config/{queue,realtime,search,push}.yaml` | ✓ VERIFIED | PHP defaults; push disabled |
|
||||
@@ -241,15 +236,17 @@ Derived story: *As a host application developer, I want to dispatch River jobs,
|
||||
| fonoteka routes.go | lighthouse/route.go | lighthouse.Mount | ✓ WIRED |
|
||||
| lighthouse/broadcast.go | conga | Enqueue on write tx in savepoint | ✓ WIRED |
|
||||
| centrifugo/handlers.go | registry.go | Registry().Get per subscribe | ✓ WIRED |
|
||||
| beachcomber/sync.go | lagoon/transaction.go | lagoon.AfterCommit | ⚠ WIRED, wrong timing inside plain gorm tx (Gap 1) |
|
||||
| beachcomber/sync.go | lagoon/transaction.go | lagoon.AfterCommit | ✓ WIRED; managed transactions buffer until commit and unmanaged transactions are refused |
|
||||
| cabana CRUD/relation writes | lagoon/transaction.go | lagoon.Transaction | ✓ WIRED; three CRUD and two relation write entry points use the managed transaction |
|
||||
| fonoteka SaveAlbum | lagoon/transaction.go | lagoon.Transaction | ✓ WIRED; Album save and ordered artist pivot sync share the managed transaction |
|
||||
| fonoteka plugin.go | search.go | wireSearch | ✓ WIRED |
|
||||
|
||||
### Data-Flow Trace (Level 4)
|
||||
|
||||
| Artifact | Data | Source | Real data | Status |
|
||||
|----------|------|--------|-----------|--------|
|
||||
| Album search document | artist_ids | reload inside `syncOne` | Reads pivots at the moment of the callback; on cabana edits that is before `syncBelongsToMany` | ⚠ STALE on admin edit path |
|
||||
| Album `updated` broadcast payload | album.artists | `albumPayload` Preload on tx | Same timing; pre-edit artists on admin edits (subtree asserted only in Phase 12) | ⚠ STALE (same root cause) |
|
||||
| Album search document | artist_ids | reload inside `syncOne` after managed commit | Reads the committed ordered pivots; assembled-router test compares the imported ids to a committed query and requires exactly one document | ✓ FLOWING |
|
||||
| Album `updated` broadcast payload | album.artists | `albumPayload` Preload through the managed write transaction | Cabana write timing is now commit-safe; delivery/payload compatibility remains covered by the existing broadcast tests and Phase 12 goldens | ✓ FLOWING |
|
||||
| Token `info.name` | user name | `SetUserLookup` DB read | Real query | ✓ FLOWING |
|
||||
|
||||
### Behavioral Spot-Checks
|
||||
@@ -257,8 +254,10 @@ Derived story: *As a host application developer, I want to dispatch River jobs,
|
||||
| Behavior | Command | Result | Status |
|
||||
|----------|---------|--------|--------|
|
||||
| LISTEN pickup not poll latency | `go test ./modules/conga/ -run '^TestListenPickupLatency$' -v -count=1` | listen 3.0ms (30s poll); poll-only not picked up in 2s | ✓ PASS |
|
||||
| Plain gorm tx syncs before commit (gap evidence) | `go test ./modules/beachcomber/ -run '^TestSyncAfterCommit$/plain_gorm_transaction' -v -count=1` | PASS — the test asserts the immediate, pre-commit sync | ✓ PASS (confirms gap) |
|
||||
| Full phase gate | `bash scripts/check-phase11.sh --all` | self-test, hygiene, go, postgres, named, evidence passed; `phase11 all passed` | ✓ PASS |
|
||||
| Committed artist pivots reach Typesense once | `TestAlbumsAdminSearchUsesCommittedArtists` (recorded by `check-phase11.sh --all`) | PASS — committed order equals the one imported document's `artist_ids` | ✓ PASS |
|
||||
| Managed/unmanaged after-commit invariants | `TestTransactionAfterCommit`, `TestTransactionEdges`, `TestSyncAfterCommit` (recorded gate evidence) | PASS — commit ordering, rollback silence, exact parent ownership, clean handles, unmanaged refusal | ✓ PASS |
|
||||
| Full phase gate | `bash scripts/check-phase11.sh --all` | Recorded final run: self-test, hygiene, go, postgres, named, evidence passed; `phase11 all passed` | ✓ PASS |
|
||||
| Removal coverage | `PHASE11_RC=RC-15 bash scripts/check-phase11.sh --removal` | Recorded final run: removing exact parent ownership makes the named Lagoon test fail as required; file restored | ✓ PASS |
|
||||
| Pre-existing hello failure | `go test ./examples/hello -run TestTypedItemRoute` at HEAD and in a 718a35c worktree | Same failure (`surf: config http.body_limits.default_bytes is required`) at both | ℹ pre-existing, not a Phase 11 regression |
|
||||
|
||||
### Probe Execution
|
||||
@@ -275,9 +274,9 @@ Step 7c: no `scripts/*/tests/probe-*.sh` probes declared or present; the phase g
|
||||
| RT-01 | 11-03, 11-04, 11-06, 11-07 | ✓ SATISFIED (live: human) | SC-3 evidence; hand-rolled client per the D-12/D-16 note |
|
||||
| RT-02 | 11-03, 11-06, 11-07 | ✓ SATISFIED | SC-4 evidence; channel names are opaque `collection:<id>` as in PHP |
|
||||
| RT-03 | 11-03, 11-06, 11-07 | ✓ SATISFIED | Bulk suppression + single Emit; rollback publishes nothing (broadcast jobs ride the write tx) |
|
||||
| SRCH-01 | 11-05, 11-07 | ✗ BLOCKED (partial) | Scoping, kill-switch and degradation satisfied; "Album documents sync to Typesense" is incorrect on the admin edit path (Gap 1) |
|
||||
| SRCH-01 | 11-05, 11-07, 11-08 | ✓ SATISFIED | Scoping, kill-switch and graceful degradation hold; CR-01 closure makes Cabana/SaveAlbum sync commit-safe and proves committed ordered pivots plus rollback silence |
|
||||
|
||||
No orphaned requirements: REQUIREMENTS.md maps exactly these seven IDs to Phase 11, and every one is claimed by a plan. REQUIREMENTS.md already marks all seven Complete; SRCH-01 should not be treated as complete until Gap 1 closes.
|
||||
No orphaned requirements: REQUIREMENTS.md maps exactly these seven IDs to Phase 11, and every one is claimed by a plan. The status table still says `Gaps Found` for JOBS-01/CLI-04/CLI-06/RT-01/RT-02/RT-03; that planning metadata is stale relative to this re-verification and should be refreshed by the orchestrator when it records the phase result.
|
||||
|
||||
### Prohibitions
|
||||
|
||||
@@ -285,35 +284,35 @@ All prohibitions are `verification: test` and each has a named test run by the g
|
||||
|
||||
### Anti-Patterns and Review Findings
|
||||
|
||||
No TBD/FIXME/XXX markers in the Phase 11 files. Code review findings weighed against the success criteria:
|
||||
No TBD/FIXME/XXX markers were found in the Plan 11-08 implementation/test files. The final `11-REVIEW.md` reviews all 13 gap-closure files and reports zero critical, warning, or info findings. Its resolved findings confirm exact parent-transaction ownership, fail-closed expected-skip enforcement, exact named-test coverage, and RC-15 removal coverage. No re-verification regression or new-scope blocker remains.
|
||||
|
||||
| Finding | File | Severity here | Impact on goal |
|
||||
|---------|------|---------------|----------------|
|
||||
| CR-01 | lagoon/transaction.go, cabana/crud.go, fonoteka album_write_service.go | 🛑 Blocker | Gap 1 (SC-5/SRCH-01, D-20 truth) |
|
||||
| WR-02 | conga/commands.go withDB, lagoon/commands.go withDB | ⚠ Warning | SC-2 holds for in-process commands; any scheduled command opening its own DB fails inside a worker. Must be fixed before Phase 14 registers `fonoteka:prune-notifications` |
|
||||
| WR-01 | lighthouse/broadcast.go:443-449 | ⚠ Warning | Framework API trap for Binding functions that call `WithContext`; fonoteka bindings unaffected |
|
||||
| WR-03 | lagoon/transaction.go:54-65 | ⚠ Warning | Nested Transaction over the root handle; same area as Gap 1, fix together |
|
||||
| WR-04 | flare/commands.go; fonoteka.go/.gitignore | ⚠ Warning (security) | Confirmed: `git check-ignore config/env/dev/overrides.yaml` reports not ignored, so `--update` can leave the VAPID private key committable |
|
||||
| WR-05 | centrifugo/token.go, handlers.go | ⚠ Warning (security) | Identifier tokens with numeric prefixes authorize as user ids; no callers today |
|
||||
| WR-06 | lighthouse/broadcast.go:424-432 | ⚠ Warning | Default payload publishes in-memory model; Album overrides payload |
|
||||
| WR-07 | fonoteka routes.go/plugin.go, lighthouse/route.go | ⚠ Warning | Subscribe proxy shares IP bucket with public traffic (PHP parity, DoS vector) |
|
||||
| IN-01..IN-10 | various | ℹ Info | None affects a success criterion |
|
||||
### Advisory (New Scope, Unevidenced)
|
||||
|
||||
None.
|
||||
|
||||
### Decision Coverage
|
||||
|
||||
All 20 trackable `11-CONTEXT.md` decisions are honored by shipped artifacts (`check.decision-coverage-verify`: 20/20, non-blocking gate).
|
||||
|
||||
### Test Quality Audit
|
||||
|
||||
No disabled requirement-linked tests or circular expected-value generators were found in the CR-01 closure tests. The strongest assertions are behavioral: the assembled admin route must commit ordered pivots and send exactly one matching import; rollback paths must make zero callbacks/engine calls; removal checks RC-14/RC-15 must turn the named Lagoon test red. The final phase gate rejects missing, skipped, zero-match, or unexpectedly passing named tests.
|
||||
|
||||
### Human Verification Required
|
||||
|
||||
1. **Live Centrifugo v6 + unchanged Nuxt** — change an album, confirm the event arrives with a Go-issued token.
|
||||
2. **Live Typesense 26.0 upsert** — enable the switch, save an album, query the collection. Re-run after Gap 1 is fixed, and edit the album's artists in the admin form to confirm artist_ids.
|
||||
2. **Live Typesense 26.0 upsert** — enable the switch, edit an album's artists, query the collection, and confirm `artist_ids` matches the committed pivot order.
|
||||
3. **Real-browser Web Push** through the flare VAPID driver.
|
||||
4. **Multi-process scheduler leader election** (11-02 backstop).
|
||||
5. **Broadcast delivery order** accepted as unordered (11-03 backstop).
|
||||
|
||||
### Gaps Summary
|
||||
|
||||
One root cause blocks the phase: `lagoon.AfterCommit` treats a plain `gorm` transaction as "no transaction" and runs immediately. beachcomber relies on it for every Searchable write, and both real album write paths (the framework's cabana admin CRUD, live since Phase 10, and fonoteka `SaveAlbum`, which Phase 12 will call) use plain transactions that save the album before writing its artist pivots. Typesense therefore receives a document built mid-transaction with stale `artist_ids`, and a later failure in the same transaction leaves Typesense diverged from the committed database. Passing tests lock this in. Phase 12's SQL re-gate keeps a stale document from leaking an unauthorized result, so this is a correctness and parity defect rather than a data leak. It is not deferred: no later phase owns the AfterCommit contract or the cabana write path (Phase 12 SC-3 covers the re-gate, Phase 14 only the reindex command). The fix is small and local: route cabana and SaveAlbum through `lagoon.Transaction`, make `AfterCommit` refuse a foreign transaction, invert the two tests, and add an admin artists-edit test. Fix WR-03 in the same change and WR-02 before Phase 14.
|
||||
No automated implementation gaps remain. Plan 11-08 closes CR-01 at every required level: substantive transaction ownership/refusal logic exists, all real Album write paths are wired to it, committed data flows to the Typesense import, and behavioral/removal tests fail when the protections are removed. The clean code review and final `check-phase11.sh --all` evidence show no regression.
|
||||
|
||||
Everything else in the goal is in place and tested: River's dual-driver split with measured LISTEN pickup, the job manager, queue and schedule commands, Centrifugo tokens, the proxy, publishing matched against PHP goldens, and the authorizer registry with bulk suppression.
|
||||
Five pre-existing human checks remain: live Centrifugo/Nuxt integration, live Typesense, browser Web Push, multi-process River leader election, and the declared unordered broadcast-delivery backstop. Under the verifier decision tree these make the final status `human_needed`; they are not code gaps and do not reopen CR-01.
|
||||
|
||||
---
|
||||
|
||||
_Verified: 2026-09-30T13:28:50Z_
|
||||
_Verifier: Claude (gsd-verifier)_
|
||||
_Verified: 2026-09-30T20:26:54Z_
|
||||
_Verifier: the agent (gsd-verifier)_
|
||||
|
||||
Reference in New Issue
Block a user