|
| 1 | +# Namespace publication transaction core |
| 2 | + |
| 3 | +Status: implemented storage core, 2026-09-06; **not an enabled publisher API**. |
| 4 | +The application composer, all production writer integrations, release-policy |
| 5 | +enforcement, prepare/retention pins and authorization remain required before |
| 6 | +the namespace capability can be announced. |
| 7 | + |
| 8 | +## Ownership and ordering |
| 9 | + |
| 10 | +`PublicationStorage::begin(request, expected_head, writer_epoch)` owns one |
| 11 | +database transaction. Its first write reserves the unique |
| 12 | +`(actor_domain, operation_id)` row. A duplicate committed request returns its |
| 13 | +receipt before exposing any ref-writing handle. Reusing the same committed key |
| 14 | +with a different request digest or instance is a conflict. Failed/aborted |
| 15 | +transactions leave no operation reservation or success receipt. |
| 16 | + |
| 17 | +The request digest is supplied by a trusted application adapter and MUST cover |
| 18 | +the complete canonical mutation plan: fixed base/head, expected refs, binding |
| 19 | +policy/read set and prepared content identities. The storage facade cannot |
| 20 | +infer these fields from an opaque digest. The authenticated actor domain must |
| 21 | +not be accepted from untrusted request JSON. Receipt reads require current |
| 22 | +authorization independently of the operation key. |
| 23 | + |
| 24 | +A ready result owns `PublicationTransaction`. Writers can borrow its underlying |
| 25 | +transaction for conditional refs, prepared metadata, scope attestations and |
| 26 | +index nodes, but cannot obtain ownership and independently commit it. Explicit |
| 27 | +abort or dropping the owner rolls back. Publication's `finish` is the only |
| 28 | +commit path exposed by this wrapper. |
| 29 | + |
| 30 | +`finish` validates the prepared view identity/byte bound and same instance, then |
| 31 | +stages insert-only view bytes, conditional head update, publication history, |
| 32 | +operation result and outbox event. They commit together with the borrowed |
| 33 | +transaction's ref changes. A database error after head CAS still rolls back |
| 34 | +the head, view and refs. No notification is dispatched before COMMIT. |
| 35 | + |
| 36 | +## Compare-and-swap and receipts |
| 37 | + |
| 38 | +The head condition includes instance, expected sequence, expected view ID and |
| 39 | +writer epoch. Bootstrap is an insert-if-absent head, not an upsert. Sequences and |
| 40 | +epochs are positive SQL BIGINT values and sequence increment checks overflow. |
| 41 | + |
| 42 | +When the descriptor is unchanged, the operation may be a no-op for namespace |
| 43 | +publication: preserve sequence/view and do not insert publication/outbox rows. |
| 44 | +It STILL executes the head/epoch fence. For example, a non-selected branch may |
| 45 | +change without changing the default namespace view. Determining that the view |
| 46 | +really represents the complete post-write state belongs to the application; |
| 47 | +the storage facade must not be used to hide a selected-ref mutation. |
| 48 | + |
| 49 | +`GitDbStorage::update_ref_if_unchanged` adds one conditional SQL update on |
| 50 | +repo ID, fully qualified ref name and expected object ID, returning whether |
| 51 | +exactly one row changed. It accepts the publication transaction and does not |
| 52 | +silently rebase/retry. Existing legacy writers are not yet switched to this |
| 53 | +method. The caller must abort the whole publication if any required ref/read |
| 54 | +condition fails. |
| 55 | + |
| 56 | +A COMMIT error is reported as an uncertain outcome, not a proven rollback. |
| 57 | +Look up the original actor/operation/request digest on a new connection before |
| 58 | +retrying. Receipt replay never dispatches a second ref mutation or outbox |
| 59 | +event. Outbox rows have unique event IDs and pending/delivered state, but the |
| 60 | +delivery worker and external side effects are not implemented by this core. |
| 61 | + |
| 62 | +A writer_epoch column does not fence an old binary that never checks it. |
| 63 | +Maintenance cutover and an audit of every production writer remain G04/G05 |
| 64 | +requirements; a passing storage test cannot establish those conditions. |
| 65 | + |
| 66 | +## Schema and reproduction |
| 67 | + |
| 68 | +The additive migration `m20260906_160000_namespace_publication` creates |
| 69 | +namespace_view, namespace_head, namespace_publication, snapshot_operation and |
| 70 | +namespace_outbox. It creates no initial head/catalog and enables no feature. |
| 71 | +Generated Callisto fields were produced with sea-orm-cli 2.0.2 from the actual |
| 72 | +SQLite migration schema; PostgreSQL tests verify the same runtime schema. |
| 73 | + |
| 74 | +View payloads are bounded to 16 KiB in SQL and checked against their SHA-256 ID. |
| 75 | +The application supplies the already validated namespace-manifest-v1 codec. |
| 76 | +An opaque-byte storage fixture is not proof that the manifest describes the |
| 77 | +actual native/import objects. No foreign-key cascade from a mutable ref or |
| 78 | +registry path deletes published metadata. Retention/GC and referential audits |
| 79 | +must be supplied by the full publisher before deployment. |
| 80 | + |
| 81 | +Use the explicit loopback disposable PostgreSQL URL described in |
| 82 | +[jupiter-migrate](../../jupiter-migrate/README.md), then run: |
| 83 | + |
| 84 | +```bash |
| 85 | +cargo test -p jupiter --lib publication_storage --locked -- --include-ignored --nocapture |
| 86 | +cargo test -p jupiter-migrate --lib snapshot --locked -- --include-ignored --nocapture |
| 87 | +``` |
| 88 | + |
| 89 | +The six publication tests cover SQLite lifecycle, duplicate-key concurrency and |
| 90 | +expected-old competition, plus PostgreSQL lifecycle/reconnect, concurrent |
| 91 | +duplicates/expected-old writers and an independent-connection epoch change. |
| 92 | +Shared lifecycle checks also inject failure after head CAS, drop an uncommitted |
| 93 | +transaction, reject different-payload replay, preserve old views and verify |
| 94 | +no-op ref writes. They use the REAL import_refs and new publication tables. |
| 95 | +PostgreSQL tests create fresh random schemas, retain diagnostics and never |
| 96 | +refresh a supplied database. Tests do not cover a process/host power loss, |
| 97 | +external payload durability, notification delivery, source/path permissions or |
| 98 | +release-policy bypass through actual production routes. |
| 99 | + |
| 100 | +The CI focused snapshot job runs these PostgreSQL tests explicitly instead of |
| 101 | +silently skipping ignored tests. MG06/MG09/MG15 have additional storage-level |
| 102 | +evidence; the broader acceptance IDs remain incomplete until application and |
| 103 | +real-service integration are tested. |
0 commit comments