Files
buzz/schema/schema.sql
ravarora2 2c28e07133 BUZZ-413: Add user deletion fence storage (#8632)
## Summary

Add storage and lock helpers for the first user-deletion fencing PR. No
serving handler or deletion worker calls these helpers yet, so this does
not block users or enable real deletion.

The operator-global `user_deletion_fences` table stores global user
restrictions and community-scoped agent restrictions. Community and
profile deletion retain these rows unchanged. `scope_community_id`
records a restriction's scope; it does not make the row community-owned.

The helpers use the caller's transaction, stable identity lock keys,
ordered shared/exclusive locks, and indexed lookups. Inserts preserve
the original request and owner on retry and reject conflicting
provenance. Inserts validate the current worker lease and approved
inventory through the existing entity-deletion guards. Callers must
still verify that the target identities and agent ownership belong to
the request.

This is an additive schema expansion with a five-second migration lock
timeout. It adds no database triggers, functions, foreign keys, or
backfills. Both schema sources and operator-global registrations are
updated. The unused request-ID index was removed. The existing
community-deletion catalog and purge logic are unchanged. Tests cover
live schema parity, concurrency, rollback, and retention through the
existing purge and verification functions.

### Related issue

Part of
[BUZZ-413](https://linear.app/squareup/issue/BUZZ-413/gate-user-and-owned-agent-activity).
This is the storage foundation; serving checks and real workflow
handlers follow separately. No duplicate fence-storage PR found.

### Testing

No manual app testing: these utilities have no application callers yet.
Local PostgreSQL validation uses an isolated test container and the
repository's per-test database runner.

Passed 84 selected PostgreSQL tests covering fence authorization,
concurrent inserts, schema parity, retention, community deletion,
entity-deletion storage, and user discovery. The authorization
regression also failed as expected when the insertion guard was
temporarily removed; the guard was restored before the final run.

Command: `scripts/postgres-test-run.sh -p buzz-db --lib -E
'test(/user_deletion_fences/) | test(/admin_schema_parity_between/) |
test(/^store::deletion::postgres_tests::/) |
test(/^store::entity_deletion::postgres_tests::/) |
test(/^store::user_deletion::/)'` with the local test PostgreSQL
connection and `PG_BIN_DIR` configured.

The full `just test` run had one failure:
`composition_tests::composition_log_does_not_leak_issuer_url` missed an
expected log message. The same compiled test passed when rerun alone. No
relay logging code is changed by this PR.

The full local `just ci` run and all normal pre-push hooks passed on the
updated branch.

Generated with Codex

---------

Signed-off-by: Ravneet Arora <rarora@squareup.com>
2026-10-28 14:36:17 -04:00

218 lines
8.7 KiB
PL/PgSQL

-- Desired-state database manifest.
--
-- `tables/public/<table>.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.
--
-- Everything SchemaBot cannot apply lives in this directory's other
-- subdirectories: `types/`, `functions/`, `partitions/`, `triggers/` (including
-- 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
-- storage parameters, so every apply caller must then run
-- `scripts/reconcile-schema-after-pgschema.sql`. Raw `psql` resolves `\i`
-- against the working directory instead, so run it from here:
-- `cd schema && psql -f schema.sql`.
--
-- Adding a table: add `tables/public/<table>.sql` and include it below after
-- the tables it references; put its partitions, triggers, and seed rows in the
-- matching subdirectories and include those too. This file holds only `\i`
-- lines and `CREATE EXTENSION`. Outside `tables/public/`, no file may create,
-- alter or drop a table or index, except `CREATE TABLE ... PARTITION OF` in
-- `partitions/`. The layout test enforces both.
-- Buzz initial Postgres schema — multi-tenant.
--
-- Source of truth for fresh database setup. This is a clean, from-scratch
-- schema in which `community_id` is a first-class, server-resolved key on
-- every tenant-scoped row. It is NOT additive over the single-community
-- schema; the rewrite replaces it. Existing single-community deployments
-- migrate via the documented backfill migration (0002), which assigns all
-- pre-existing rows to one default community.
--
-- The governing contract is docs/multi-tenant-conformance.md. Every table
-- below cites the conformance surface it implements. The invariant behind the
-- whole schema (conformance "row zero"): a request's community is resolved
-- from the connection host by the server, never supplied by the client, and
-- every scoped row carries that immutable `community_id`.
--
-- Migration-lint obligations enforced by the Lane 0 lint harness:
-- 1. Every tenant-scoped table has `community_id NOT NULL`.
-- 2. No UNIQUE / PRIMARY KEY / FK on a scoped table is observable across
-- communities: each leads with `community_id` (or, for child rows whose
-- parent already pins the community, joins carry the community tuple).
-- 3. `channels.community_id` is immutable (triggers/channels.sql; no UPDATE path).
-- 4. Operator-global tables are named in the explicit allowlist, not implied.
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- types
\i types/channel_type.sql
\i types/channel_visibility.sql
\i types/member_role.sql
\i types/workflow_status.sql
\i types/run_status.sql
\i types/approval_status.sql
\i types/delivery_method.sql
\i types/subscription_status.sql
\i types/pause_reason.sql
\i types/channel_add_policy.sql
-- functions
\i functions/channels_community_id_immutable.sql
\i functions/guard_channel_roster_snapshot.sql
\i functions/events_created_at_floor_guard.sql
\i functions/prevent_community_deletion_request_retargeting.sql
\i functions/prevent_community_deletion_approval_removal.sql
\i functions/protect_community_deletion_manifest_keys.sql
\i functions/community_deletion_lock_key.sql
\i functions/community_write_fence_excluded_table.sql
\i functions/community_write_allowed.sql
\i functions/assert_community_write_allowed.sql
\i functions/enforce_community_write_fence.sql
\i functions/enforce_community_tombstone.sql
\i functions/attach_community_write_fence.sql
\i functions/profile_name_fields.sql
-- tables
\i tables/public/communities.sql
\i tables/public/channels.sql
\i tables/public/channel_members.sql
\i tables/public/users.sql
\i tables/public/personal_read_accounts.sql
\i tables/public/personal_read_frontiers.sql
\i tables/public/events.sql
\i tables/public/event_mentions.sql
\i tables/public/subscriptions.sql
\i tables/public/delivery_log.sql
\i tables/public/workflows.sql
\i tables/public/workflow_runs.sql
\i tables/public/workflow_approvals.sql
\i tables/public/scheduled_workflow_fires.sql
\i tables/public/api_tokens.sql
\i tables/public/rate_limit_violations.sql
\i tables/public/thread_metadata.sql
\i tables/public/reactions.sql
\i tables/public/pubkey_allowlist.sql
\i tables/public/relay_members.sql
\i tables/public/join_policy_acceptances.sql
\i tables/public/relay_invites.sql
\i tables/public/archived_identities.sql
\i tables/public/audit_log.sql
\i tables/public/moderation_actions.sql
\i tables/public/moderation_reports.sql
\i tables/public/community_bans.sql
\i tables/public/community_nip_fi_issuers.sql
\i tables/public/_operator_global_tables.sql
\i tables/public/git_repo_names.sql
\i tables/public/parameterized_event_watermarks.sql
\i tables/public/product_feedback.sql
\i tables/public/push_leases.sql
\i tables/public/push_wake_outbox.sql
\i tables/public/push_match_queue.sql
\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
\i tables/public/replica_heartbeat.sql
\i tables/public/community_deletion_requests.sql
\i tables/public/entity_deletion_approvals.sql
\i tables/public/entity_deletion_requests.sql
\i tables/public/entity_deletion_stage_history.sql
\i tables/public/user_deletion_fences.sql
\i tables/public/community_deletion_approvals.sql
\i tables/public/community_deletion_checkpoints.sql
\i tables/public/community_deletion_manifest_keys.sql
\i tables/public/storage_taxonomy_sweeps.sql
\i tables/public/community_serving_write_leases.sql
\i tables/public/community_deletion_executor_heartbeats.sql
\i tables/public/relay_operators.sql
\i tables/public/relay_admin_actions.sql
\i tables/public/relay_admin_outbox.sql
\i tables/public/operator_listener_pubkeys.sql
\i tables/public/operator_listener_outbox.sql
\i tables/public/relay_operator_audit.sql
\i tables/public/storage_accounting_snapshots.sql
\i tables/public/artifact_heads.sql
\i tables/public/artifact_revisions.sql
-- partitions
\i partitions/events.sql
\i partitions/delivery_log.sql
-- triggers
\i triggers/channels.sql
\i triggers/events.sql
\i triggers/community_deletion_requests.sql
\i triggers/community_deletion_approvals.sql
\i triggers/community_deletion_manifest_keys.sql
\i triggers/communities.sql
\i triggers/api_tokens.sql
\i triggers/archived_identities.sql
\i triggers/audit_log.sql
\i triggers/channel_members.sql
\i triggers/community_bans.sql
\i triggers/community_nip_fi_issuers.sql
\i triggers/delivery_log.sql
\i triggers/event_mentions.sql
\i triggers/git_repo_names.sql
\i triggers/join_policy_acceptances.sql
\i triggers/moderation_actions.sql
\i triggers/moderation_reports.sql
\i triggers/parameterized_event_watermarks.sql
\i triggers/personal_read_accounts.sql
\i triggers/personal_read_frontiers.sql
\i triggers/pubkey_allowlist.sql
\i triggers/push_leases.sql
\i triggers/push_match_queue.sql
\i triggers/push_wake_outbox.sql
\i triggers/reactions.sql
\i triggers/relay_invites.sql
\i triggers/relay_members.sql
\i triggers/scheduled_workflow_fires.sql
\i triggers/subscriptions.sql
\i triggers/thread_metadata.sql
\i triggers/users.sql
\i triggers/workflow_approvals.sql
\i triggers/workflow_runs.sql
\i triggers/workflows.sql
\i triggers/artifact_heads.sql
\i triggers/artifact_revisions.sql
-- seeds
\i seeds/communities.sql
\i seeds/rate_limit_violations.sql
\i seeds/_operator_global_tables.sql
\i seeds/product_feedback.sql
\i seeds/push_gateway_challenges.sql
\i seeds/push_gateway_installations.sql
\i seeds/push_gateway_delegations.sql
\i seeds/push_gateway_endpoint_quotas.sql
\i seeds/push_gateway_delivery_auth_replays.sql
\i seeds/push_gateway_delivery_request_replays.sql
\i seeds/replica_heartbeat.sql
\i seeds/community_deletion_requests.sql
\i seeds/entity_deletion_approvals.sql
\i seeds/entity_deletion_requests.sql
\i seeds/entity_deletion_stage_history.sql
\i seeds/user_deletion_fences.sql
\i seeds/community_deletion_approvals.sql
\i seeds/community_deletion_checkpoints.sql
\i seeds/community_deletion_manifest_keys.sql
\i seeds/storage_taxonomy_sweeps.sql
\i seeds/community_serving_write_leases.sql
\i seeds/community_deletion_executor_heartbeats.sql
\i seeds/relay_operators.sql
\i seeds/relay_admin_actions.sql
\i seeds/relay_admin_outbox.sql
\i seeds/operator_listener_pubkeys.sql
\i seeds/operator_listener_outbox.sql
\i seeds/relay_operator_audit.sql
\i seeds/storage_accounting_snapshots.sql