Migration SQL file existed but was never registered in _journal.json,
so Drizzle skipped it. Every voice recording insert failed with:
PG code 42703: column "transcription" does not exist
Co-Authored-By: Claude <noreply@anthropic.com>
Drizzle wraps PG errors — the previous log only showed the formatted query
text. Now extracts code, detail, schema, table, constraint, severity from
the error envelope and from .cause to make the actual Postgres error visible.
Co-Authored-By: Claude <noreply@anthropic.com>
Redis root cause: VoiceHandler passed redisPub to transmitter.start(),
which called .subscribe() on it — permanently converting the publish
connection to subscriber mode. Every subsequent command reply and status
update failed.
Fixes:
1. Transmitter now creates its own Redis client via new IORedis()
2. Mutual exclusion gate serialises start/stop to prevent null-deref races
3. PassThrough drain listeners cleaned up to stop MaxListenersExceeded
4. FFmpeg SIGTERM flagged as expected exit (no more false level-50 errors)
5. Voice recording repo captures PG error code/detail for diagnostics
Co-Authored-By: Claude <noreply@anthropic.com>
- Expand IMPHNEN domain rule to cover wildcard (*.imphnen.*)
- Trim redundant SARA examples from TEXT_ONLY_MODE (save ~950 tokens)
- Add debugging logs for channel culture injection into prompt
- Sync flag validation set with missing flags: potential_evasion, unclear_context
Co-Authored-By: Claude <noreply@anthropic.com>
- Add explicit system rule that IMPHNEN is the project's own name, not religion
- Rename 'Imphnemia 11:17' example to 'Kitabonia 11:17' to avoid name collision
- Ensures mentioning/promoting the project URL is not flagged as SARA
Co-Authored-By: Claude <noreply@anthropic.com>
Add user_profiles table, store, and background learner worker
that summarizes user communication style, topics, and personality.
- New user_profiles table (user_id PK, guild_id, profile_summary, last_analyzed_at)
- userProfileStore.ts — CRUD (get/update) following channelCultureStore pattern
- userProfileLearner.ts — background worker: queries 100 recent msgs per user,
calls LLM for personality summary, updates every 12h
- Inject <user_profile> XML tag per-message in moderation prompt
- Start worker alongside cultureLearner in aiAnalyzer.ts
- Migration 0008 for user_profiles table
Co-Authored-By: Claude <noreply@anthropic.com>
- hasMediaContent now also checks evidence.attachments from metadata
(not just DB attachment records), catching the race where attachment
DB rows aren't inserted yet when analysis runs.
- Cache-hit guard: treat cached entries as miss when the message has
media evidence in metadata, so stale 24h-freezes are avoided.
- Cache-write guard: skip storing text-only analysis results for
messages whose metadata shows attachments/stickers/embeds. This
prevents a text-only 'clean' result (from failed vision) being
frozen for 24h, blocking future re-analysis with full media context.
Co-Authored-By: Claude <noreply@anthropic.com>
- 0000: CREATE TABLE IF NOT EXISTS, CREATE INDEX IF NOT EXISTS,
FK constraint wrapped in DO 539994 EXCEPTION WHEN duplicate_object
- 0001: ALTER ADD COLUMN wrapped in DO 539994 EXCEPTION WHEN duplicate_column
- 0002: CREATE TABLE IF NOT EXISTS, CREATE INDEX IF NOT EXISTS
- 0003: CREATE TABLE IF NOT EXISTS, CREATE INDEX IF NOT EXISTS
- 0006: ALTER ADD COLUMN wrapped in DO 539994 EXCEPTION,
DROP COLUMN IF EXISTS
Allows migrations to run cleanly regardless of DB state —
handles fresh DB, partial migration, or wiped __drizzle_migrations.
Co-Authored-By: Claude <noreply@anthropic.com>
- Resolve parent channel ID for threads before checking
EXCLUDED_CHANNEL_IDS set
- Thread messages now blocked if their parent channel is excluded
Co-Authored-By: Claude <noreply@anthropic.com>
- Move isAgeRestrictedMessage to messageMetadata.ts alongside
isAgeRestrictedMetadata — single source of truth for NSFW logic
- Replace || chain of channel IDs with a Set for O(1) lookup
- Import isAgeRestrictedMessage in capture layer instead of inline
Co-Authored-By: Claude <noreply@anthropic.com>
Auto-filter all age-restricted and NSFW channels at capture layer
so messages never enter the database or reach frontend.
Co-Authored-By: Claude <noreply@anthropic.com>
Add hardcoded channel ID to shouldCaptureMessageLocation so messages
from this channel are not stored, analyzed by AI, or sent to frontend.
Co-Authored-By: Claude <noreply@anthropic.com>
In PG15+, the CREATE privilege on the public schema is revoked from
non-owner roles by default. The seedDrizzleHistory function's
CREATE TABLE IF NOT EXISTS for __drizzle_migrations fails with 42501,
causing the gateway to crash-loop on startup.
Wrap the CREATE in a try/catch for 42501 — if the table already exists
(created by a prior run), we continue gracefully; otherwise re-throw.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PostgreSQL error 42501 when Drizzle tries CREATE SCHEMA IF NOT EXISTS
"drizzle" — the DB user lacks schema creation privileges. Configuring
migrationsSchema: "public" keeps __drizzle_migrations in the existing
public schema, matching what seedDrizzleHistory already expects.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Update the moderation prompt to include high-priority detection categories for:
- Fake scripture/verse parodies
- Claims of divinity or false religious movements
- Misuse of theological terms as internet slang/memes
- Mockery of religious figures and rituals
This change ensures stricter enforcement of SARA (Suku, Agama, Ras, Antargolongan) policies by explicitly defining religious blasphemy and parody as high-severity violations.
This commit introduces several significant improvements across the backend and gateway services:
- **Data Retention**: Added an automated cleanup scheduler in `discord-gateway` to prune expired messages, attachments, and voice recordings based on configurable retention policies.
- **Observability**: Integrated `prom-client` in the `backend` service to expose Prometheus metrics via `/api/metrics` and added default Node.js runtime metrics.
- **Media Handling**: Enhanced `MediaHandler` in `discord-gateway` to support media URL resolution and improved playback status tracking.
- **API & Config**: Expanded the configuration endpoint to expose more system settings and reorganized `.env.example` for better readability.
- **Refactoring & Cleanup**:
- Removed unused `better-sqlite3` dependency.
- Refactored voice channel routing.
- Improved error handling and testing coverage with comprehensive unit tests for shared utilities and error classes.
- **Documentation**: Added `MEMORY.md` for project context.
Refactor the voice recording and playback systems to improve efficiency, reduce latency, and enhance Docker build performance.
- **Infrastructure**: Optimize Dockerfiles using build mounts for pnpm cache and reorder layers for better caching of dependencies and build tools.
- **Backend/Gateway**:
- Refactor `broadcast` module to use a generic event-based system instead of hardcoded functions.
- Simplify voice recording logic by merging metadata and segment management into a unified `segment.ts`.
- Optimize audio downsampling in `streamSetup.ts` using `Int16Array` views for better performance.
- Implement `withFallback` utility for more robust Redis/Database command execution.
- **Frontend**:
- Optimize audio playback visualization using pre-computed level shapes and efficient RMS calculation.
- Reduce latency in voice commands by prioritizing WebSocket communication over HTTP.
- Improve base64 encoding efficiency in audio transmission.
- **General**:
- Add default value for `ADMIN_PASSWORD` in shared config.
- Fix Docker healthcheck to use `127.0.0.1` instead of `localhost`.
- Shared Redis channel constants as single source of truth (redis-channels.ts)
- commandHandler.ts split into VoiceHandler, MediaHandler, GuildHandler,
ModerationHandler with handler-registry.ts dispatch
- messageStore.ts (1322 lines) split into domain-specific DB files:
messages.db.ts, attachments.db.ts, reviews.db.ts,
moderation-actions.db.ts, retention.db.ts
- recorder.ts startSpeaking callback extracted into speakingHandler.ts,
streamSetup.ts, segmentFinalizer.ts
- autoDeleteManager.ts split into autoDeleteEligibility.ts,
autoDeleteNotify.ts, autoDeleteLogger.ts
- Added createChildLogger() logging across 8 service files
- Backend messages.repository.ts migrated from raw SQL to Drizzle ORM
- Fixed biome.json to exclude packages/**/dist/* from lint
- Fixed config.ts GUILD_ID pre-existing type error
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Improve system reliability and real-time capabilities by implementing a robust lifecycle management system and adding new broadcast events for attachments and voice recordings.
- Implement asynchronous graceful shutdown in backend to close HTTP, WebSocket, Redis, and database connections.
- Add new WebSocket broadcast events: `attachment_created`, `voice_recording_started`, `voice_recording_stopped`, `voice_recording_uploaded`, and `analysis_queue_status`.
- Refactor media status handling to use boolean `playing` state instead of string-based status.
- Centralize `PageResult` and `VoiceRecording` types to improve consistency between frontend and backend.
- Update frontend API client to include `listRecordings` and handle new WebSocket event types.
- Fix type mismatches in voice command handling and media status reporting.
Prevents vitest from exiting with code 1 when no test files exist.
Adds minimal test files to backend and discord-gateway services.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>