Missing ALTER TABLE migration for audit_log multi-tenant schema (breaks existing deployments upgrading to 0.2.0)
- 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.
This then successfully create a relay using the Kubernetes deployment!!! thank you gys!
Finally the full relay working
## 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
Assessment
This issue has not been assessed yet.