block / block/buzz

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

Open
#4,919 5 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
32.7k
Forks
4.3k
Avg merge
1d 13h
Merged PRs (30d)
253

Description

## 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:

```sql
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:

```sql
-- 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:
```sql
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)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.