Files
buzz/migrations/0051_owner_community_deletion_admission.sql
Luke Tornquist 15d44dccee Add owner deletion admission control plane (#7818)
## Summary

Adds the relay and database admission boundary for owner-requested
deletion of archived hosted communities.

The operator endpoint records accepted owner intent with stable UUID
idempotency and immutable owner/operator provenance. Admission
serializes with unarchive and owner transfer, and accepted requests
disappear from actionable owner listings.

This keeps destructive execution out of the request path. The existing
durable deletion engine remains the only executor, so this change adds
no lifecycle state or second deletion mechanism.

Complexity stays bounded: one durable request is both the idempotency
record and the mutation fence.

### Related issue

None found.

### Testing

- `REDIS_URL=redis://127.0.0.1:6379
BUZZ_POSTGRES_ADMIN_URL=postgres://buzz:buzz_dev@localhost:5432/postgres
PGHOST=localhost PGPORT=5432 PGUSER=buzz PGPASSWORD=buzz_dev
PG_BIN_DIR=/tmp/buzz-pg-bin ./scripts/postgres-test-run.sh -p buzz-db -p
buzz-relay --lib --tests` — 385 passed.
- `cargo test -p buzz-db` — passed.
- `cargo fmt --all -- --check` and `cargo clippy -p buzz-db -p
buzz-relay --all-targets -- -D warnings` — passed.
- `just desktop-test desktop-build desktop-tauri-check
desktop-tauri-test web-build` — passed; Desktop ran 6,589 tests.
- Full `just test` could not start uncached Keycloak dependencies
because `quay.io` reset connections. Full `just ci` reached Flutter
dependency resolution, then `pub.dev` reset connections.

Generated with Codex

---------

Signed-off-by: tornquist <tornquist@squareup.com>
Signed-off-by: Codex <noreply@openai.com>
Signed-off-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
2026-09-28 15:12:15 -04:00

82 lines
3.8 KiB
PL/PgSQL

-- Structured provenance for owner-origin whole-community deletion requests.
--
-- The existing request UUID is the stable correlation/idempotency identity.
-- Owner admission supplies that UUID instead of creating a second identity
-- column, while these bounded columns distinguish owner intent from the
-- deployment operator that mediated it.
--
-- The owner branch rejects a NULL in every provenance column before testing
-- its shape. A bare `col ~ '...'` on a NULL column yields NULL, and a CHECK is
-- satisfied by NULL, so `FALSE OR NULL` would silently admit an owner-origin
-- row with no owner key, no mediating operator, or no acknowledgement version.
--
-- The null rejection is spelled `NOT (col IS NULL)` rather than
-- `col IS NOT NULL`: pgschema drops a named CHECK whose body contains
-- `IS NOT NULL` and still exits 0, which would leave the desired-state
-- bootstrap in `schema/schema.sql` silently unguarded while the migration path
-- stayed correct. `store::deletion::owner_provenance_contract` asserts both
-- schema sources against one case table so that divergence fails loudly.
SET LOCAL lock_timeout = '5s';
ALTER TABLE community_deletion_requests
ADD COLUMN request_origin TEXT NOT NULL DEFAULT 'operator'
CHECK (request_origin IN ('operator', 'owner')),
ADD COLUMN owner_pubkey TEXT,
ADD COLUMN mediating_operator_pubkey TEXT,
ADD COLUMN acknowledgement_version INTEGER,
ADD CONSTRAINT community_deletion_owner_provenance CHECK (
(request_origin = 'operator'
AND owner_pubkey IS NULL
AND mediating_operator_pubkey IS NULL
AND acknowledgement_version IS NULL)
OR
(request_origin = 'owner'
AND NOT (owner_pubkey IS NULL)
AND NOT (mediating_operator_pubkey IS NULL)
AND NOT (acknowledgement_version IS NULL)
AND owner_pubkey ~ '^[0-9a-f]{64}$'
AND mediating_operator_pubkey ~ '^[0-9a-f]{64}$'
AND acknowledgement_version BETWEEN 1 AND 32767
AND requested_by = owner_pubkey)
);
CREATE OR REPLACE FUNCTION prevent_community_deletion_request_retargeting()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
IF NEW.community_id IS DISTINCT FROM OLD.community_id
OR NEW.community_host IS DISTINCT FROM OLD.community_host
THEN
RAISE EXCEPTION 'community deletion target identity is immutable'
USING ERRCODE = 'integrity_constraint_violation';
END IF;
IF NEW.request_origin IS DISTINCT FROM OLD.request_origin
OR NEW.owner_pubkey IS DISTINCT FROM OLD.owner_pubkey
OR NEW.mediating_operator_pubkey IS DISTINCT FROM OLD.mediating_operator_pubkey
OR NEW.acknowledgement_version IS DISTINCT FROM OLD.acknowledgement_version
THEN
RAISE EXCEPTION 'community deletion request provenance is immutable'
USING ERRCODE = 'integrity_constraint_violation';
END IF;
IF OLD.inventory_frozen_at IS NOT NULL AND (
NEW.schema_manifest IS DISTINCT FROM OLD.schema_manifest
OR NEW.storage_manifest IS DISTINCT FROM OLD.storage_manifest
OR NEW.inventory_manifest IS DISTINCT FROM OLD.inventory_manifest
OR NEW.inventory_digest IS DISTINCT FROM OLD.inventory_digest
OR NEW.inventory_frozen_at IS DISTINCT FROM OLD.inventory_frozen_at
) THEN
RAISE EXCEPTION 'frozen community deletion inventory is immutable'
USING ERRCODE = 'integrity_constraint_violation';
END IF;
IF OLD.destructive_storage_frozen_at IS NOT NULL AND (
NEW.destructive_storage_manifest IS DISTINCT FROM OLD.destructive_storage_manifest
OR NEW.destructive_storage_frozen_at IS DISTINCT FROM OLD.destructive_storage_frozen_at
) THEN
RAISE EXCEPTION 'frozen destructive storage manifest is immutable'
USING ERRCODE = 'integrity_constraint_violation';
END IF;
RETURN NEW;
END;
$$;