Skip to content

Missing ALTER TABLE migration for audit_log multi-tenant schema (breaks existing deployments upgrading to 0.2.0) #4919

Description

@marcellodesales

Summary

The 0001_initial_schema.sql migration was rewritten on ~June 29, 2026 (commit 14fba21, "Multi-tenant Buzz relay: community_id as a server-resolved key") to add multi-tenancy support. However, no incremental migration was added (0028_...) to ALTER existing databases. Fresh installs work correctly, but existing deployments upgrading from 0.1.x to 0.2.0 encounter a non-fatal but persistent runtime error on every audit log write.

Image

This then successfully create a relay using the Kubernetes deployment!!! thank you gys!

Image

Finally the full relay working

Image

Error (relay 0.2.0 log, every event ingestion)

{"level":"ERROR","message":"Audit log failed: database error: error returned from database: column \"community_id\" does not exist at line 3722","target":"buzz_relay::state"}

Root cause

crates/buzz-audit/src/service.rs now INSERTs using the new 9-column multi-tenant schema:

INSERT INTO audit_log
    (community_id, seq, hash, prev_hash, action, actor_pubkey, object_id, detail, created_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)

But existing databases created from the original 0001_initial_schema.sql have the old 10-column single-tenant schema. PostgreSQL fails at the first column in the INSERT list, so only community_id appears in the error — but the full mismatch is deeper:

Column in INSERT Relay 0.2.0 expects Existing DB has Issue
community_id uuid NOT NULL MISSING
seq bigint bigint ✅ OK
hash bytea varchar(64) Wrong type
prev_hash bytea varchar(64) Wrong type
action varchar varchar(64) ✅ OK
actor_pubkey bytea varchar(255) Wrong type
object_id text MISSING
detail jsonb (column named metadata) MISSING / renamed
created_at timestamptz (column named timestamp) MISSING / renamed

Additionally the primary key changed from (seq) to (community_id, seq).

Impact

  • Relay 0.2.0 starts successfully and serves traffic (event ingest returns HTTP 200)
  • All audit log writes silently fail for the lifetime of the deployment
  • Compliance and audit trail is broken for existing deployments
  • Error is non-fatal so it can be silently missed in production

Reproduction

  1. Deploy relay 0.1.x against a fresh database → migrations applied from original 0001_initial_schema.sql
  2. Upgrade relay binary to 0.2.0 (:main)
  3. Ingest any Nostr event
  4. Observe ERROR Audit log failed: ... column "community_id" does not exist in relay logs
  5. Query audit_log table — rows from before the upgrade exist; no new rows are inserted

Proposed fix

Add migrations/0028_audit_log_multitenant.sql to migrate existing databases:

-- Align existing audit_log with the multi-tenant schema from 0001_initial_schema.sql rewrite.

-- 1. Add community_id (nullable first for backfill, then constrain)
ALTER TABLE audit_log
  ADD COLUMN IF NOT EXISTS community_id uuid
  REFERENCES communities(id) ON DELETE CASCADE;

-- Backfill existing rows with the default community
UPDATE audit_log
  SET community_id = (SELECT id FROM communities LIMIT 1)
  WHERE community_id IS NULL;

ALTER TABLE audit_log
  ALTER COLUMN community_id SET NOT NULL;

-- 2. Add object_id
ALTER TABLE audit_log
  ADD COLUMN IF NOT EXISTS object_id text;

-- 3. Add detail (backfill from existing metadata column)
ALTER TABLE audit_log
  ADD COLUMN IF NOT EXISTS detail jsonb;
UPDATE audit_log SET detail = metadata WHERE detail IS NULL;
ALTER TABLE audit_log ALTER COLUMN detail SET NOT NULL;

-- 4. Add created_at (backfill from existing timestamp column)
ALTER TABLE audit_log
  ADD COLUMN IF NOT EXISTS created_at timestamptz;
UPDATE audit_log SET created_at = "timestamp" WHERE created_at IS NULL;
ALTER TABLE audit_log ALTER COLUMN created_at SET NOT NULL;
ALTER TABLE audit_log ALTER COLUMN created_at SET DEFAULT now();

-- 5. Convert varchar hex columns to bytea
ALTER TABLE audit_log
  ALTER COLUMN hash TYPE bytea USING decode(hash, 'hex');
ALTER TABLE audit_log
  ALTER COLUMN prev_hash TYPE bytea USING decode(prev_hash, 'hex');
ALTER TABLE audit_log
  ALTER COLUMN actor_pubkey TYPE bytea
  USING CASE WHEN actor_pubkey = '' THEN NULL ELSE decode(actor_pubkey, 'hex') END;

-- 6. Rebuild primary key as composite
ALTER TABLE audit_log DROP CONSTRAINT IF EXISTS audit_log_pkey;
ALTER TABLE audit_log ADD PRIMARY KEY (community_id, seq);

-- 7. Add per-community hash uniqueness index
CREATE UNIQUE INDEX IF NOT EXISTS idx_audit_log_hash
  ON audit_log (community_id, hash);

Note on actor_pubkey: deployments should verify no non-hex data exists before applying step 5:

SELECT COUNT(*) FROM audit_log
WHERE actor_pubkey !~ '^[0-9a-fA-F]*$' AND actor_pubkey IS NOT NULL AND actor_pubkey != '';

Environment

  • Relay: block/buzz:main (0.2.0)
  • Database: PostgreSQL 16 (Azure DB for PostgreSQL Flexible Server)
  • SQLx migrations: 0001–0027 applied; no migration 0028 exists
  • Existing audit_log rows: ~300 (all pre-upgrade, none written by 0.2.0)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions