17 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, gap_closure, requirements, estimate, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | gap_closure | requirements | estimate | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 11-jobs-realtime-and-search-infrastructure | 08 | execute | 6 |
|
|
true | true |
|
|
|
Phase Goal
ROADMAP Phase 11 goal remains the contract; it is not written as a user story. This gap slice closes its failed Typesense criterion by making the framework's real Album write paths commit-safe.
Close verifier gap CR-01: make Album search synchronization observe committed relation state and make unmanaged transaction use fail safe.Purpose: D-20 promises an inline, non-fatal sync built from the committed row. Cabana and SaveAlbum currently save the Album before artist pivots and use plain GORM transactions, so Typesense can receive stale or rolled-back state. Output: commit-safe Cabana and SaveAlbum transactions, a foreign-transaction guard in lagoon.AfterCommit, and regression tests for committed artist_ids and rollback silence.
<execution_context> @/home/jin/.codex/gsd-core/workflows/execute-plan.md @/home/jin/.codex/gsd-core/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/11-jobs-realtime-and-search-infrastructure/11-CONTEXT.md @.planning/phases/11-jobs-realtime-and-search-infrastructure/11-VERIFICATION.md @.planning/phases/11-jobs-realtime-and-search-infrastructure/11-01-SUMMARY.md @.planning/phases/11-jobs-realtime-and-search-infrastructure/11-05-SUMMARY.md @.planning/phases/11-jobs-realtime-and-search-infrastructure/11-07-SUMMARY.mdArtifacts this phase produces
No new production files, schema objects, exported symbols, or dependencies. Changed symbols: cabana.CRUDService.save, cabana.CRUDService.Delete, cabana.CRUDService.BulkDelete, cabana.RelationService.Link, cabana.RelationService.Unlink, lagoon.Transaction, lagoon.AfterCommit, and classes.SaveAlbum. New test symbols: TestAlbumsAdminSearchUsesCommittedArtists and TestSaveAlbumDefersAfterCommitUntilArtistsSync.
Flagged assumptions (spec-less fallback: unresolved)
- SRCH-01 remains
unclassified: this closure guarantees that each sync reads its own committed row and ordered pivots, but does not serialize concurrent commits for the same Album; the Phase 12 SQL re-gate remains the safety boundary. - RT-03 remains
unclassifiedand is not newly claimed by this plan. The same transaction buffering corrects timing for Album update callbacks, but batch zero-primary-key writes and restore behavior retain the unresolved assumptions already recorded in 11-03.
Per D-20, replace only Cabana's write transactions with lagoon.Transaction: CRUDService.save, Delete, BulkDelete, and RelationService.Link/Unlink. Use the callback-supplied context for loads, validation, lifecycle hooks, relation scope checks, pivot writes, and deletes so lagoon.AfterCommit can see the buffer. Leave read-only ShowRecord and relation queries unchanged.
go test ./modules/cabana -count=1 && (cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^TestAlbumsAdminSearchUsesCommittedArtists$' -count=1 -v)
<fails_when>Either command exits non-zero; the named admin test is absent, skipped, reports no tests to run, or its indexed artist_ids differ from the committed pivot order.</fails_when>
<acceptance_criteria>
- Cabana has exactly three write transaction entry points in crud.go and two in relation.go, all through lagoon.Transaction; read-only transaction sites remain unchanged.
- TestAlbumsAdminSearchUsesCommittedArtists exercises the assembled HTTP router, not CRUDService directly.
- The test proves the engine call occurs only after the ordered pivots are visible on the committed connection.
</acceptance_criteria>
An admin can change an Album's artists and the resulting Typesense document contains the committed ordered artist_ids.
Invert the tests that currently bless immediate plain-GORM sync. In lagoon, assert callback suppression plus the warning. In beachcomber, use a plain GORM transaction that writes and then rolls back and assert zero fake-engine calls. In fonoteka's search smoke test, assert a foreign transaction never sends to Typesense. Retain explicit regression coverage for the implicit single-statement and lagoon-managed paths. go test ./modules/lagoon -run '^(TestTransactionAfterCommit|TestTransactionEdges)$' -count=1 -v && go test ./modules/beachcomber -run '^TestSyncAfterCommit$' -count=1 -v && (cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^TestAlbumSearchDeleteAndFailures$' -count=1 -v) <fails_when>Any command exits non-zero; a named test is absent, skipped, or reports no tests to run; a foreign transaction invokes an after-commit callback or records an engine call; an implicit or lagoon-managed commit stops syncing.</fails_when> <acceptance_criteria> - The plain-transaction tests assert zero callbacks/engine calls and a warning, replacing the old immediate-sync expectation. - The rollback test observes no committed row and no external request. - Nested lagoon.Transaction with a root handle cannot independently commit under a parent buffer. - modules/lagoon/README.md describes all four cases: lagoon-managed, implicit statement, foreign transaction, and outside a transaction. </acceptance_criteria> Unmanaged transactions cannot leak uncommitted state externally, while every supported after-commit path keeps its documented behavior.
Task 3: SaveAlbum defers sync until ordered artists commit ../fonoteka.go/plugins/golem15/fonoteka/classes/album_write_service.go, ../fonoteka.go/plugins/golem15/fonoteka/classes/album_write_service_db_test.go ../fonoteka.go/plugins/golem15/fonoteka/classes/album_write_service.go, ../fonoteka.go/plugins/golem15/fonoteka/classes/album_write_service_db_test.go, ../fonoteka.go/plugins/golem15/fonoteka/classes/artist_resolver.go, modules/lagoon/transaction.go, .planning/phases/11-jobs-realtime-and-search-infrastructure/11-VERIFICATION.md - SaveAlbum validates, resolves artists, saves the Album, replaces pivots, commits, and only then runs after-commit callbacks. - A pivot or validation failure rolls back the Album and runs no callback. Per D-20, replace SaveAlbum's plain GORM transaction with `lagoon.Transaction`, using the callback-supplied context throughout validation and writes. Keep the existing save-before-pivot order; buffering now makes the post-commit reload occur after `syncArtists` and commit. Add `TestSaveAlbumDefersAfterCommitUntilArtistsSync`, registering a test callback that proves it observes the committed ordered pivots, plus a rollback case proving it never runs. Do not add a second save/touch and do not change validation, price provenance, or artist resolution semantics. (cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/classes -run '^TestSaveAlbum' -count=1 -v && go test ./plugins/golem15/fonoteka -run '^(TestAlbumsAdminSearchUsesCommittedArtists|TestAlbumSearchDeleteAndFailures)$' -count=1 -v) Either command exits non-zero; the new SaveAlbum test or either gap regression test is absent, skipped, reports no tests to run, sees pre-pivot artist state, or runs a callback after rollback. - SaveAlbum calls lagoon.Transaction and uses its callback context. - TestSaveAlbumDefersAfterCommitUntilArtistsSync observes the submitted pivot order from the callback and zero callbacks on rollback. - Existing SaveAlbum validation and market-price provenance tests remain green. Both real Album write paths expose only committed row-and-pivot state to after-commit consumers.<threat_model>
Trust Boundaries
| Boundary | Description |
|---|---|
| Open SQL transaction -> external search engine | Album and relation data must not leave the database before commit |
| Cabana admin request -> Album row and pivots | One user edit spans scalar and ordered relation writes that must commit atomically |
STRIDE Threat Register (ASVS L1; blocking threshold high)
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|---|---|---|---|---|---|
| T-11-31 | Information Disclosure | lagoon.AfterCommit / beachcomber sync | high | mitigate | Buffer supported writes until commit, refuse foreign *sql.Tx callbacks, and prove a rollback causes zero engine calls (Tasks 1-2). |
| T-11-32 | Tampering | Cabana and SaveAlbum ordered artist pivots | medium | mitigate | Route complete row-and-pivot units through lagoon.Transaction and compare the indexed artist_ids with committed pivot order (Tasks 1 and 3). |
| T-11-SC | Tampering | npm/pip/cargo installs | high | mitigate | No package-manager install occurs in this gap plan; existing dependency lockfiles remain unchanged. |
| </threat_model> |
The phase gate fails if either repository's vet/tests fail, a named test is skipped or missing, or it does not print phase11 all passed.
<success_criteria>
- The Cabana admin artist-edit regression indexes the committed ordered pivots.
- Plain GORM transaction rollback reaches no search engine, and AfterCommit warns instead of executing in that foreign transaction.
- Cabana write methods and SaveAlbum use lagoon.Transaction without changing their HTTP, validation, lifecycle, or relation contracts.
- All focused tests and the existing Phase 11 gate pass; no UI-SPEC, UI file, schema file, dependency, or runtime mirror is created. </success_criteria>
Multi-Source Coverage Audit (gap-closure scope)
| Source | ID | Gap item | Plan | Status |
|---|---|---|---|---|
| GOAL | SC-5 | Typesense sync reflects committed Album state | 11-08 | COVERED |
| REQ | SRCH-01 | Album documents sync correctly and safely | 11-08 | COVERED |
| RESEARCH | Pattern 10 | after-commit buffer and rollback silence | 11-08 | COVERED |
| CONTEXT | D-20 | after-commit, inline, non-fatal committed-row sync | 11-08 | COVERED |
| VERIFICATION | CR-01 | Cabana/SaveAlbum transaction paths, foreign-tx refusal, regression tests | 11-08 | COVERED |
All other Phase 11 goals, requirements, research items, and D-01..D-19 decisions are excluded from this gap-closure audit because 11-VERIFICATION.md already marks them verified or out of this failed truth's scope. Deferred ideas remain excluded.
Create `.planning/phases/11-jobs-realtime-and-search-infrastructure/11-08-SUMMARY.md` when done.