From 5446d493ecfcb8e501cc3153f815c49875e770a6 Mon Sep 17 00:00:00 2001
From: Elrond
<28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
Date: Wed, 14 Oct 2026 14:19:17 -0400
Subject: [PATCH 1/5] feat(push-gateway): declarative desired-state schema, one
file per table
Add crates/buzz-push-gateway/schema/ in the SchemaBot shape the relay
uses (schema.sql \i manifest + tables/public/
.sql) declaring the
end state of migrations 0001-0006, plus a layout test and a
pgschema-vs-migrations catalog parity test.
Signed-off-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
---
crates/buzz-push-gateway/schema/schema.sql | 31 +++
.../tables/public/push_gateway_challenges.sql | 13 +
.../public/push_gateway_delegations.sql | 17 ++
.../push_gateway_delivery_auth_replays.sql | 9 +
.../push_gateway_delivery_request_replays.sql | 9 +
.../public/push_gateway_endpoint_quotas.sql | 9 +
.../public/push_gateway_installations.sql | 23 ++
crates/buzz-push-gateway/src/postgres.rs | 4 +
.../postgres/desired_schema_postgres_tests.rs | 260 ++++++++++++++++++
9 files changed, 375 insertions(+)
create mode 100644 crates/buzz-push-gateway/schema/schema.sql
create mode 100644 crates/buzz-push-gateway/schema/tables/public/push_gateway_challenges.sql
create mode 100644 crates/buzz-push-gateway/schema/tables/public/push_gateway_delegations.sql
create mode 100644 crates/buzz-push-gateway/schema/tables/public/push_gateway_delivery_auth_replays.sql
create mode 100644 crates/buzz-push-gateway/schema/tables/public/push_gateway_delivery_request_replays.sql
create mode 100644 crates/buzz-push-gateway/schema/tables/public/push_gateway_endpoint_quotas.sql
create mode 100644 crates/buzz-push-gateway/schema/tables/public/push_gateway_installations.sql
create mode 100644 crates/buzz-push-gateway/src/postgres/desired_schema_postgres_tests.rs
diff --git a/crates/buzz-push-gateway/schema/schema.sql b/crates/buzz-push-gateway/schema/schema.sql
new file mode 100644
index 00000000000..8e4b0728117
--- /dev/null
+++ b/crates/buzz-push-gateway/schema/schema.sql
@@ -0,0 +1,31 @@
+-- Push gateway desired-state database manifest.
+--
+-- The gateway's authority store is deployment-global and lives in its own
+-- dedicated database, outside relay community tenancy. This directory declares
+-- its end state; `../migrations/` remains the incremental path the gateway
+-- binary applies at startup.
+-- `src/postgres/desired_schema_postgres_tests.rs` proves the two produce the
+-- same catalog.
+--
+-- `tables/public/
.sql` declares one table per file: its CREATE TABLE
+-- and the CREATE INDEX statements on it, the one-table-per-file shape SchemaBot
+-- reads. `tables/` is the SchemaBot schema directory and `public` is its
+-- namespace, so nothing else may live under it. The gateway has no types,
+-- functions, triggers, or seed rows. Its pre-migration bootstrap guard and
+-- runtime-role grants are operational steps in `src/postgres.rs`, not schema.
+--
+-- `./bin/pgschema apply --file crates/buzz-push-gateway/schema/schema.sql`
+-- resolves the `\i` includes relative to this file. Raw `psql` resolves `\i`
+-- against the working directory instead, so run it from here:
+-- `cd crates/buzz-push-gateway/schema && psql -f schema.sql`.
+--
+-- Adding a table: add `tables/public/
.sql` and include it below after
+-- the tables it references. This file holds only `\i` lines. The layout
+-- test in `src/postgres/desired_schema_postgres_tests.rs` enforces both.
+
+\i tables/public/push_gateway_challenges.sql
+\i tables/public/push_gateway_installations.sql
+\i tables/public/push_gateway_delegations.sql
+\i tables/public/push_gateway_endpoint_quotas.sql
+\i tables/public/push_gateway_delivery_auth_replays.sql
+\i tables/public/push_gateway_delivery_request_replays.sql
diff --git a/crates/buzz-push-gateway/schema/tables/public/push_gateway_challenges.sql b/crates/buzz-push-gateway/schema/tables/public/push_gateway_challenges.sql
new file mode 100644
index 00000000000..0ada41a5343
--- /dev/null
+++ b/crates/buzz-push-gateway/schema/tables/public/push_gateway_challenges.sql
@@ -0,0 +1,13 @@
+-- Single-use App Attest enrollment challenges. Only the hash is stored.
+CREATE TABLE push_gateway_challenges (
+ id UUID PRIMARY KEY,
+ challenge_hash BYTEA NOT NULL CHECK (length(challenge_hash) = 32),
+ expires_at TIMESTAMPTZ NOT NULL,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
+);
+
+CREATE INDEX push_gateway_challenges_expiry ON push_gateway_challenges (expires_at);
+
+-- The unauthenticated challenge route applies a deployment-global rolling
+-- issuance quota. Keep its count query bounded as challenge volume grows.
+CREATE INDEX push_gateway_challenges_created_at ON push_gateway_challenges (created_at);
diff --git a/crates/buzz-push-gateway/schema/tables/public/push_gateway_delegations.sql b/crates/buzz-push-gateway/schema/tables/public/push_gateway_delegations.sql
new file mode 100644
index 00000000000..0fcfbc0f163
--- /dev/null
+++ b/crates/buzz-push-gateway/schema/tables/public/push_gateway_delegations.sql
@@ -0,0 +1,17 @@
+-- An installation's delegation to one relay signing key. Installations may
+-- authorize multiple relay deployments, one row per relay key.
+CREATE TABLE push_gateway_delegations (
+ id UUID PRIMARY KEY,
+ installation_id UUID NOT NULL REFERENCES push_gateway_installations(id),
+ relay_pubkey BYTEA NOT NULL CHECK (length(relay_pubkey) = 32),
+ endpoint_epoch BIGINT NOT NULL CHECK (endpoint_epoch > 0),
+ generation BIGINT NOT NULL CHECK (generation > 0),
+ not_before TIMESTAMPTZ NOT NULL,
+ expires_at TIMESTAMPTZ NOT NULL,
+ revoked_at TIMESTAMPTZ,
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
+ UNIQUE (installation_id, relay_pubkey),
+ CHECK (not_before < expires_at)
+);
+
+CREATE INDEX push_gateway_delegations_expiry ON push_gateway_delegations (expires_at) WHERE revoked_at IS NULL;
diff --git a/crates/buzz-push-gateway/schema/tables/public/push_gateway_delivery_auth_replays.sql b/crates/buzz-push-gateway/schema/tables/public/push_gateway_delivery_auth_replays.sql
new file mode 100644
index 00000000000..0a16dbf7214
--- /dev/null
+++ b/crates/buzz-push-gateway/schema/tables/public/push_gateway_delivery_auth_replays.sql
@@ -0,0 +1,9 @@
+-- Replay ledger for relay delivery auth events, retained until expiry.
+CREATE TABLE push_gateway_delivery_auth_replays (
+ relay_pubkey BYTEA NOT NULL CHECK (length(relay_pubkey) = 32),
+ auth_event_id BYTEA NOT NULL CHECK (length(auth_event_id) = 32),
+ expires_at TIMESTAMPTZ NOT NULL,
+ PRIMARY KEY (relay_pubkey, auth_event_id)
+);
+
+CREATE INDEX push_gateway_delivery_auth_replays_expiry ON push_gateway_delivery_auth_replays (expires_at);
diff --git a/crates/buzz-push-gateway/schema/tables/public/push_gateway_delivery_request_replays.sql b/crates/buzz-push-gateway/schema/tables/public/push_gateway_delivery_request_replays.sql
new file mode 100644
index 00000000000..101b98a3721
--- /dev/null
+++ b/crates/buzz-push-gateway/schema/tables/public/push_gateway_delivery_request_replays.sql
@@ -0,0 +1,9 @@
+-- Replay ledger for relay delivery request ids, retained until expiry.
+CREATE TABLE push_gateway_delivery_request_replays (
+ relay_pubkey BYTEA NOT NULL CHECK (length(relay_pubkey) = 32),
+ request_id UUID NOT NULL,
+ expires_at TIMESTAMPTZ NOT NULL,
+ PRIMARY KEY (relay_pubkey, request_id)
+);
+
+CREATE INDEX push_gateway_delivery_request_replays_expiry ON push_gateway_delivery_request_replays (expires_at);
diff --git a/crates/buzz-push-gateway/schema/tables/public/push_gateway_endpoint_quotas.sql b/crates/buzz-push-gateway/schema/tables/public/push_gateway_endpoint_quotas.sql
new file mode 100644
index 00000000000..f1b55551d05
--- /dev/null
+++ b/crates/buzz-push-gateway/schema/tables/public/push_gateway_endpoint_quotas.sql
@@ -0,0 +1,9 @@
+-- Per-APNs-token delivery admission window.
+CREATE TABLE push_gateway_endpoint_quotas (
+ token_fingerprint BYTEA PRIMARY KEY CHECK (length(token_fingerprint) = 32),
+ window_started_at TIMESTAMPTZ NOT NULL,
+ admitted BIGINT NOT NULL CHECK (admitted >= 0),
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
+);
+
+CREATE INDEX push_gateway_endpoint_quotas_updated ON push_gateway_endpoint_quotas (updated_at);
diff --git a/crates/buzz-push-gateway/schema/tables/public/push_gateway_installations.sql b/crates/buzz-push-gateway/schema/tables/public/push_gateway_installations.sql
new file mode 100644
index 00000000000..a40c532f542
--- /dev/null
+++ b/crates/buzz-push-gateway/schema/tables/public/push_gateway_installations.sql
@@ -0,0 +1,23 @@
+-- App Attest-bound device installations and their encrypted APNs tokens.
+-- Revoked installations remain as retry tombstones until authority expiry, so
+-- ownership uniqueness covers live rows only: a replacement can enroll without
+-- deleting the old installation's idempotency state.
+CREATE TABLE push_gateway_installations (
+ id UUID PRIMARY KEY,
+ app_attest_key_id BYTEA NOT NULL CHECK (octet_length(app_attest_key_id) BETWEEN 1 AND 128),
+ app_attest_public_key BYTEA NOT NULL CHECK (octet_length(app_attest_public_key) BETWEEN 33 AND 256),
+ assertion_counter BIGINT NOT NULL CHECK (assertion_counter BETWEEN 0 AND 4294967295),
+ token_ciphertext BYTEA NOT NULL CHECK (octet_length(token_ciphertext) BETWEEN 1 AND 2048),
+ token_fingerprint BYTEA NOT NULL CHECK (length(token_fingerprint) = 32),
+ endpoint_epoch BIGINT NOT NULL CHECK (endpoint_epoch > 0),
+ expires_at TIMESTAMPTZ NOT NULL,
+ revoked_at TIMESTAMPTZ,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
+);
+
+CREATE INDEX push_gateway_installations_expiry ON push_gateway_installations (expires_at) WHERE revoked_at IS NULL;
+
+CREATE UNIQUE INDEX push_gateway_installations_active_app_attest_key ON push_gateway_installations (app_attest_key_id) WHERE revoked_at IS NULL;
+
+CREATE UNIQUE INDEX push_gateway_installations_active_token ON push_gateway_installations (token_fingerprint) WHERE revoked_at IS NULL;
diff --git a/crates/buzz-push-gateway/src/postgres.rs b/crates/buzz-push-gateway/src/postgres.rs
index a58d8c209b4..dd842bf7fd2 100644
--- a/crates/buzz-push-gateway/src/postgres.rs
+++ b/crates/buzz-push-gateway/src/postgres.rs
@@ -10,6 +10,10 @@ use uuid::Uuid;
#[path = "postgres/bootstrap_tests.rs"]
mod bootstrap_postgres_tests;
+#[cfg(test)]
+#[path = "postgres/desired_schema_postgres_tests.rs"]
+mod desired_schema_postgres_tests;
+
static GATEWAY_MIGRATOR: sqlx::migrate::Migrator = sqlx::migrate!("./migrations");
#[cfg(test)]
diff --git a/crates/buzz-push-gateway/src/postgres/desired_schema_postgres_tests.rs b/crates/buzz-push-gateway/src/postgres/desired_schema_postgres_tests.rs
new file mode 100644
index 00000000000..b6d7d86c5d3
--- /dev/null
+++ b/crates/buzz-push-gateway/src/postgres/desired_schema_postgres_tests.rs
@@ -0,0 +1,260 @@
+//! `schema/` is the gateway's desired-state schema in the one-table-per-file
+//! shape SchemaBot reads; `migrations/` is the incremental path the binary
+//! applies at startup. These tests keep the two from drifting apart.
+use super::*;
+use std::collections::BTreeSet;
+use std::path::{Path, PathBuf};
+
+fn schema_dir() -> PathBuf {
+ Path::new(env!("CARGO_MANIFEST_DIR")).join("schema")
+}
+
+fn strip_comments(text: &str) -> String {
+ text.lines()
+ .map(|line| line.split_once("--").map_or(line, |(code, _)| code))
+ .collect::>()
+ .join("\n")
+}
+
+/// `schema/schema.sql` includes every file under `schema/` exactly once and
+/// holds nothing but `\i` lines. `schema/tables/public/
.sql` declares
+/// exactly that table plus plain `CREATE [UNIQUE] INDEX ... ON
`
+/// statements (SchemaBot refuses `IF NOT EXISTS` and `CONCURRENTLY`).
+#[test]
+fn desired_state_schema_layout_is_complete_and_schemabot_shaped() {
+ fn sql_files(dir: &Path, root: &Path, out: &mut BTreeSet) {
+ for entry in std::fs::read_dir(dir).expect("read schema directory") {
+ let path = entry.expect("schema directory entry").path();
+ if path.is_dir() {
+ sql_files(&path, root, out);
+ } else {
+ let relative = path.strip_prefix(root).expect("schema-relative path");
+ out.insert(relative.to_str().expect("utf-8 path").replace('\\', "/"));
+ }
+ }
+ }
+
+ let dir = schema_dir();
+ let manifest = std::fs::read_to_string(dir.join("schema.sql")).expect("read schema.sql");
+ let mut included = Vec::new();
+ for line in strip_comments(&manifest).lines().map(str::trim) {
+ if line.is_empty() {
+ continue;
+ }
+ let include = line.strip_prefix("\\i ").unwrap_or_else(|| {
+ panic!(
+ "schema/schema.sql may only hold \\i includes; \
+ put table DDL in tables/public/
.sql: {line:.80}"
+ )
+ });
+ included.push(include.trim().to_owned());
+ }
+ let included_set: BTreeSet<_> = included.iter().cloned().collect();
+ assert_eq!(
+ included.len(),
+ included_set.len(),
+ "schema.sql includes a file twice"
+ );
+
+ let mut on_disk = BTreeSet::new();
+ sql_files(&dir, &dir, &mut on_disk);
+ on_disk.remove("schema.sql");
+ assert_eq!(
+ on_disk, included_set,
+ "every schema/ file must be included by schema/schema.sql exactly once"
+ );
+
+ for file in &included {
+ let table = file
+ .strip_prefix("tables/public/")
+ .and_then(|name| name.strip_suffix(".sql"))
+ .unwrap_or_else(|| {
+ panic!("{file}: the gateway schema holds only tables/public/
.sql")
+ });
+ let text = std::fs::read_to_string(dir.join(file)).expect("read table file");
+ let mut creates = 0;
+ for statement in strip_comments(&text)
+ .split(';')
+ .map(|s| s.split_whitespace().collect::>().join(" "))
+ .filter(|s| !s.is_empty())
+ {
+ if let Some(rest) = statement.strip_prefix("CREATE TABLE ") {
+ assert!(
+ rest.starts_with(&format!("{table} (")),
+ "{file} may only create table {table}: {statement:.80}"
+ );
+ creates += 1;
+ continue;
+ }
+ let index = statement
+ .strip_prefix("CREATE INDEX ")
+ .or_else(|| statement.strip_prefix("CREATE UNIQUE INDEX "))
+ .unwrap_or_else(|| {
+ panic!("{file} may only hold CREATE TABLE/INDEX: {statement:.80}")
+ });
+ assert!(
+ !index.starts_with("IF NOT EXISTS ") && !index.starts_with("CONCURRENTLY "),
+ "{file} must use a plain CREATE INDEX: {statement:.80}"
+ );
+ assert!(
+ index.contains(&format!(" ON {table} ")),
+ "{file} may only index {table}: {statement:.80}"
+ );
+ }
+ assert_eq!(creates, 1, "{file} must create exactly one table");
+ }
+ assert!(
+ !included.is_empty(),
+ "schema/tables/public declares no tables"
+ );
+}
+
+/// One probe database is bootstrapped from `schema/schema.sql` through the
+/// real `bin/pgschema apply` binary (the SchemaBot-equivalent desired-state
+/// path); another runs every gateway migration. Their `public` catalogs must
+/// match: the same tables, column shapes (keyed by name, since migrations drop
+/// and append columns), constraints, and index shapes including per-key
+/// `indoption`. The whole catalog is compared, so a new migration that is not
+/// mirrored in `schema/` fails here.
+#[tokio::test]
+#[ignore = "requires PostgreSQL"]
+async fn desired_state_schema_matches_migrations() {
+ type Rows = Vec>>;
+ async fn catalog(pool: &PgPool, sql: &str) -> Rows {
+ sqlx::query(AssertSqlSafe(sql.to_owned()))
+ .fetch_all(pool)
+ .await
+ .expect("read catalog")
+ .iter()
+ .map(|row| {
+ (0..row.len())
+ .map(|i| row.get::