addSql(<<<'SQL' CREATE TABLE audit_log ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), aggregate_type VARCHAR(255) NOT NULL, aggregate_id UUID, event_type VARCHAR(255) NOT NULL, payload JSONB NOT NULL, metadata JSONB NOT NULL, occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), sequence_number BIGSERIAL NOT NULL ) SQL); // T1.4: Indexes for efficient querying $this->addSql("CREATE INDEX idx_audit_tenant ON audit_log((metadata->>'tenant_id'))"); $this->addSql("CREATE INDEX idx_audit_user ON audit_log((metadata->>'user_id'))"); $this->addSql('CREATE INDEX idx_audit_occurred ON audit_log(occurred_at)'); $this->addSql('CREATE INDEX idx_audit_aggregate ON audit_log(aggregate_type, aggregate_id)'); $this->addSql("CREATE INDEX idx_audit_correlation ON audit_log((metadata->>'correlation_id'))"); $this->addSql('CREATE INDEX idx_audit_event_type ON audit_log(event_type)'); // T1.3: Append-only constraints - prevent UPDATE and DELETE $this->addSql('CREATE RULE audit_no_update AS ON UPDATE TO audit_log DO INSTEAD NOTHING'); $this->addSql('CREATE RULE audit_no_delete AS ON DELETE TO audit_log DO INSTEAD NOTHING'); // Add comment to document the immutability constraint $this->addSql("COMMENT ON TABLE audit_log IS 'Append-only audit log table. UPDATE and DELETE operations are disabled via PostgreSQL rules for immutability (NFR-S7).'"); } public function down(Schema $schema): void { // Drop rules first $this->addSql('DROP RULE IF EXISTS audit_no_update ON audit_log'); $this->addSql('DROP RULE IF EXISTS audit_no_delete ON audit_log'); // Drop table (indexes are dropped automatically) $this->addSql('DROP TABLE IF EXISTS audit_log'); } }