Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,8 @@ shape is an explicit transition, not a flag day:
never spans communities. Serving leases use the same application admission.
Startup and deletion catalog validation tolerate the universal fence objects
being absent; inventories still record live fences for older executors.
Desired-state bootstraps install only explicitly declared legacy fences.
New tenant-owned tables rely on application admission, without a fence trigger.
- **Replica floor (transition foundation):** runtime now provides a shared
replica-floor lock helper for compliant channel-event writers, and the writer
probe handshake takes the exclusive counterpart before sampling `S`, scanning
Expand Down
4 changes: 4 additions & 0 deletions crates/buzz-db/src/runtime/migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ use crate::Result;

static MIGRATOR: sqlx::migrate::Migrator = sqlx::migrate!("../../migrations");

#[cfg(test)]
#[path = "tests/schema_fence_bootstrap_postgres_tests.rs"]
mod schema_fence_bootstrap_postgres_tests;

/// Run all pending Buzz database migrations.
///
/// The entire run holds the exclusive [`SCHEMA_DESTRUCTION_LOCK_KEY`] session
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use sqlx::PgPool;

/// A fresh bootstrap keeps the declared backstops but never discovers new
/// community tables and silently attaches a trigger to them.
#[tokio::test]
#[ignore = "requires Postgres"]
async fn migration_schema_bootstrap_keeps_only_explicit_legacy_fences() {
let pool = PgPool::connect(&crate::test_support::database_url())
.await
.unwrap();
sqlx::raw_sql("CREATE TABLE new_tenant_probe (community_id UUID NOT NULL)")
.execute(&pool)
.await
.unwrap();
// Only checked-in schema files contribute SQL to this test bootstrap.
sqlx::raw_sql(sqlx::AssertSqlSafe(
crate::test_support::desired_state_schema_sql(),
))
.execute(&pool)
.await
.unwrap();

for (table, expected) in [
("new_tenant_probe", false),
("events", true),
("users", true),
] {
let fenced: bool = sqlx::query_scalar(
"SELECT EXISTS (SELECT 1 FROM pg_trigger \
WHERE tgrelid = $1::regclass \
AND tgfoid = 'enforce_community_write_fence()'::regprocedure)",
)
.bind(table)
.fetch_one(&pool)
.await
.unwrap();
assert_eq!(fenced, expected, "unexpected fence on {table}");
}
}
22 changes: 10 additions & 12 deletions docs/database.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ writer of a fenced table already goes through admission. The remaining
fence-retirement work drops the fence triggers and functions, and MUST also
update ARCHITECTURE.md § *Dual enforcement*.

**Today this blocks new tenant-owned tables.** Startup and deletion validation
**New tenant-owned tables use application admission.** Startup and deletion validation
no longer require a fence on any table; they pass with every universal fence
object dropped (`migration_schema_fence_objects_are_optional_for_serving_and_deletion`
in `crates/buzz-db/src/store/deletion.rs`). Two things still matter:
Expand All @@ -110,21 +110,19 @@ in `crates/buzz-db/src/store/deletion.rs`). Two things still matter:
(`community deletion catalog drift (missing=…, unknown=…)`), and deletion
fails closed for every community, not only the new table's. Listing the
table is enough to pass.
- Desired-state bootstraps run `schema/community_write_fence.sql`, which
attaches a fence trigger to every `community_id` table not excluded by
`community_write_fence_excluded_table`. A fresh desired-state database would
put a trigger on the new table and a migrated one would not, so the two
schema paths would diverge.

So before a feature can add a tenant-owned `community_id` table, the
desired-state bootstrap must stop attaching fence triggers. That change belongs
to the fence-retirement work. Land it before the feature, or as the feature's
first PR. Do not work around it with a trigger, or with an entry in
- Desired-state bootstraps install only the legacy fences explicitly listed
under `schema/triggers/`. They do not discover and fence new tables.
Keep those existing backstops until their own retirement is verified;
new tables receive no fence in either schema path.

The blanket desired-state fence attachment has been removed. A feature may
add a tenant-owned `community_id` table when it follows the checklist below.
Do not add a trigger, or an entry in
`community_write_fence_excluded_table` for a table the community owns.

#### Checklist: adding a tenant-owned `community_id` table

After the desired-state change above has landed, all in the same PR:
All in the same PR:

- [ ] Every writer of the table goes through community admission
(`begin_community_write_transaction` or another constructor that returns
Expand Down
31 changes: 0 additions & 31 deletions schema/community_write_fence.sql

This file was deleted.

7 changes: 3 additions & 4 deletions schema/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
--
-- Everything SchemaBot cannot apply lives in this directory's other
-- subdirectories: `types/`, `functions/`, `partitions/`, `triggers/` (including
-- write-fence attachment), `seeds/`, and the `community_write_fence.sql`
-- bootstrap. This file composes all of them into one fresh database, in load
-- order: types → functions → tables → partitions → triggers → seeds → fence.
-- explicit legacy write-fence attachment), and `seeds/`. This file composes
-- them into one fresh database, in load order:
-- types → functions → tables → partitions → triggers → seeds.
--
-- `./bin/pgschema apply --file schema/schema.sql` resolves the `\i` includes
-- relative to this file (it refuses `..`). `pgschema` skips INSERTs and some
Expand Down Expand Up @@ -209,4 +209,3 @@ CREATE EXTENSION IF NOT EXISTS pgcrypto;
\i seeds/storage_accounting_snapshots.sql

-- Fence any community-scoped table that lacks an explicit attach.
\i community_write_fence.sql
Loading