Repository navigation
Conversation
|
Thanks for opening this pull request! A SPACE maintainer will take a look. CONTRIBUTING.md explains how we review outside contributions. |
960d867 to
1265e6e
Compare
1265e6e to
ea3ddcc
Compare
ea3ddcc to
8f20eb5
Compare
Withdrawing this approval for now. Any feedback above still stands.
8f20eb5 to
2510443
Compare
|
No actionable comments were generated in the recent review. Walkthrough
Merge Risk:
|
2510443 to
f51255f
Compare
f51255f to
037e84f
Compare
037e84f to
677bae0
Compare
677bae0 to
5219b86
Compare
|
@coderabbitai full review |
Action performed
|
5219b86 to
8a1e110
Compare
8a1e110 to
cb9e827
Compare
|
@coderabbitai full review |
Action performed
|
…ing a send survives template-manager's node_dirty_refs outbox worker lists rows, calls node-ctl without holding the database lock, then deletes the listed refs by key alone. An enqueue of the same ref between the list and the delete updates the row in place, and the delete then removed that newer entry as well. The ref was sent again only when it was marked again or node-ctl's periodic reconciliation ran. Add an additive template-manager SQLite migration that gives node_dirty_refs a revision column, stamped from a node-wide counter by insert and update triggers (the same shape sandbox-manager's outbox uses), and make the ack delete only rows whose revision still matches the one listed. Existing rows keep revision 0 and are acked as before. The MarkFacetDirty request sent to node-ctl is unchanged. The migration's node_dirty_ref_revision counter belongs to pplx-space-template-mgr-store, which declares the table. The outbox's list and its revision-guarded delete, the statements that read and acknowledge the revisions, move from template-manager's db module into the store as node_dirty_refs::list_node_dirty_refs and node_dirty_refs::delete_node_dirty_refs_at_revision, with sql! locators. Both take a rusqlite Connection; template-manager's delete_node_dirty_refs keeps its IMMEDIATE transaction and runs the store's delete inside it. template-manager now depends on the store, and [rules.layers.store_users] lists it as the store's user. This ships in a release after one that lists 20261031050126 among the template-manager migrations its runner can reverse. That release returns a database recording the migration to its own schema at startup and keeps every outbox row, so rolling back from this release to that one is image-only; an older release refuses such a database. The bundled up SQL must stay byte-identical to that release's history copy, because that release compares the recorded checksum with its copy. The pplx-space-sqlite module doc now says that this release bundles both migrations it lists, 20261026220935 and 20261031050126, and that a migration ships only in a release after one that lists it, so the image a rollback returns to can reverse it. Tests: template-manager covers a re-enqueue during a send, a stale ack against a deleted and recreated key, a same-timestamp write with recursive_triggers off and on, and revision exhaustion leaving the pending row unchanged. In pplx-space-sqlite, this catalog bundles both listed migrations. The dirty-ref reversal tests roll back to the previous release's catalog (this one without 20261031050126, so 20261026220935 stays applied), and the round trip starts from that release's database so this catalog applies the migration and keeps it on a second startup. The tests that reverse both migrations together roll back to a catalog that bundles neither. The bundled-schema test runs this catalog and each catalog without one of the two, and checks that recorded revisions and an environment-partition row survive a restart, which they would not if either migration were reversed. The retired-migration tests derive the expected history from the catalog they migrate with, and a new test upgrades a database from the release that recorded the retired migration (baseline plus that migration only) to a catalog without it, reversing it and applying 20261031050126 in one startup.
cb9e827 to
453245b
Compare
Summary
template-manager's
node_dirty_refsoutbox worker (crates/l40_api/template/mgr/server/src/node_state_sync.rs) lists a batch, sendsMarkFacetDirtyto node-ctl without holding the database lock, then deletes the listed refs by key. A re-enqueue of the same ref in between updates that row in place, so the delete dropped it until the ref was marked again or reconciliation caught up. Now it stays pending for the next pass.db/template-manager/20261031050126_node_dirty_ref_revisions(additive, sandbox-manager's outbox shape):node_dirty_refs.revision INTEGER NOT NULL DEFAULT 0, a singleton counternode_dirty_ref_revision, and insert and update triggers that stamp each enqueue with the next value. The update trigger fires onUPDATE OF requested_at, updated_at, which every upsert sets, so a same-second re-enqueue gets a new revision.pplx-space-template-mgr-store(template-mgr-store: add a store that owns template-manager's node_dirty_ref_revision #1429):node_dirty_refs::list_node_dirty_refsreadsrevision.node_dirty_refs::delete_node_dirty_refs_at_revisiondeletes only rows still at the listed revision; template-manager'sdelete_node_dirty_refsruns it in its IMMEDIATE transaction. Enqueues andMarkFacetDirtyare unchanged.sqlite/src/template_manager.rs: the module doc now says a migration ships only in a release after one that lists it, so the image a rollback returns to can reverse it;20261031050126's check never refuses, so that rollback is image-only. template-manager: reverse the next release's node_dirty_ref_revisions migration so its rollback stays image-only #1277'sDIRTY_REF_REVISIONSentry stays, documented as bundled from here on; the bundled up SQL is byte-identical to its history copy, which template-manager: reverse the next release's node_dirty_ref_revisions migration so its rollback stays image-only #1277's checksum tests pin.recursive_triggersoff and on); revision exhaustion.pplx-space-sqlite:UNREVISIONED(this catalog without20261031050126: the previous release).a_bundled_schema_is_left_untouchedchecks recorded revisions survive a restart.HISTORICALcovers upgrading from the release that recorded it.Store
pplx-space-template-mgr-store(#1429) declares the migration'snode_dirty_ref_revisioncounter. This PR moves the outbox's list and revision-guarded delete from template-manager'sdbmodule into that store, asnode_dirty_refs::list_node_dirty_refsandnode_dirty_refs::delete_node_dirty_refs_at_revision, withsql!locators. template-manager'sdelete_node_dirty_refskeeps its IMMEDIATE transaction and runs the store's delete inside it. template-manager now depends on the store, and[rules.layers.store_users]lists it. archcheck reports that entry as the advisorylayers.policy_changed, for review.Stack
#1277 and #1429 have merged; this PR is on
main. It must ship in a release after one that contains #1277, which reverses20261031050126when a database records it without the catalog bundling it.Validation
At this head, after #1075:
-D warnings,check-migration-versions.shpass; archcheck: 0 required, 1 advisory (store_users).with-nocow-test-filesystem.sh; db-migrate'sviews_hide_other_substrates_rows_but_their_unique_indexes_still_arbitratefails on main too (hostpostgres:16-alpine).space-db-migrator, main and this head, one file: this head records[20260930000000, 20261026220935, 20261031050126]; main reverses only20261031050126, keeping both outbox rows; this head re-applies it, and a re-enqueue's revision survives anotherup.local.template_startup_recovery_decision_table,local.garbage_collectors_reclaim_independent_resources(was…template_cache_lru_eviction_reclaims),local.unanswered_template_deleted_after_wait,local.ecw_template_build_from_oci_boots,local.private_template_build_isolation,local.node_ctl_fence_refuses_and_reconciles,local.btrfs_send_restore_pulls_parent_template,ecw.background_process_control,ecw.cold_create_environment,ecw.warm_pool_environment. The outbox stayed empty, counters advanced, dirty marks were allok, and template-manager logged no errors.Not validated: a fleet rollout; environment-partition rows or a mid-send re-enqueue on Kind (the regression test covers the latter).
Severity and risk
Severity: medium. A lost ack delays a node's copy of a template-manager change until the reference is marked again or reconciliation catches up.
Risk: two small trigger updates per enqueue, in its existing transaction. A stale ack leaves the row to be resent, at-least-once delivery node-ctl already handles.
Rollout
Node-local SQLite; template-manager migrates at startup. The previous release works on the migrated schema (explicit columns; the triggers stamp its writes). Existing rows keep revision 0.
Rollback to a release containing #1277 is image-only: its startup (or migrator
up) reverses20261031050126in one transaction, keeps every outbox row and acks by key again;20261026220935stays where bundled (main since #1075). A release without #1277 refuses the database (unbundled migration).AI assistance: Claude Code (an AI agent) wrote the code, the migration, the tests and this description, and ran the validation above.