From aa6941814e95a1040eea5c23138124cb6e849908 Mon Sep 17 00:00:00 2001 From: hekataion Date: Thu, 23 Jul 2026 17:22:41 -0400 Subject: [PATCH 1/3] fix: squash drizzle migrations into one idempotent baseline (#713) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 19 migrations in drizzle/*.sql had drifted out of sync with the 8 snapshots under drizzle/meta/ (all with empty `tables: {}`), leaving the snapshot chain malformed and `npm run db:generate` broken. With no external installs yet (only bardo, fully upgraded), re-syncing the metadata carefully isn't worth it — so this is a clean slate: delete every migration and snapshot and regenerate a single baseline from schema.ts. drizzle-kit's SQLite dialect emits bare `CREATE TABLE`/`CREATE INDEX`, which would fail on an install already at this schema. Every statement is hand-edited to `IF NOT EXISTS` so bardo's populated DB runs the baseline as a no-op (drizzle still applies it, since its `when` postdates bardo's last migration) while a fresh DB builds the full schema. Verified both paths plus data survival. Removes test/db-migrations.test.ts: it exercised in-place data migration *through* the intermediate rename/retype steps (0015/0016/0017), slicing the journal at those exact tags. The squash collapses those steps into the baseline, so that scenario — a live DB upgrading across the renames, which bardo already completed — no longer exists in the chain. Co-Authored-By: Claude Opus 4.8 --- drizzle/0000_baseline.sql | 152 -- drizzle/0000_crazy_epoch.sql | 232 +++ drizzle/0001_add_conversation_private.sql | 1 - drizzle/0002_turns.sql | 21 - drizzle/0003_turn_feed_links.sql | 11 - drizzle/0004_spirits.sql | 5 - drizzle/0005_meta_summaries.sql | 15 - drizzle/0006_channels.sql | 17 - drizzle/0007_drop_conversation_name_title.sql | 11 - drizzle/0008_performance_indexes.sql | 6 - drizzle/0009_mind_notices.sql | 14 - drizzle/0010_delivery_queue_redrive.sql | 10 - drizzle/0011_drop_turn_summary_event_id.sql | 3 - drizzle/0012_channel_gates.sql | 17 - drizzle/0013_variant_purpose.sql | 3 - drizzle/0014_system_events.sql | 26 - drizzle/0015_rename_session_to_thread.sql | 12 - drizzle/0016_brain_to_human.sql | 4 - drizzle/0017_event_history_rows.sql | 9 - drizzle/0018_api_tokens.sql | 20 - drizzle/meta/0000_snapshot.json | 1423 ++++++++++++++++- drizzle/meta/0001_snapshot.json | 7 - drizzle/meta/0002_snapshot.json | 7 - drizzle/meta/0003_snapshot.json | 7 - drizzle/meta/0004_snapshot.json | 7 - drizzle/meta/0005_snapshot.json | 7 - drizzle/meta/0006_snapshot.json | 7 - drizzle/meta/0007_snapshot.json | 7 - drizzle/meta/_journal.json | 130 +- test/db-migrations.test.ts | 193 --- 30 files changed, 1654 insertions(+), 730 deletions(-) delete mode 100644 drizzle/0000_baseline.sql create mode 100644 drizzle/0000_crazy_epoch.sql delete mode 100644 drizzle/0001_add_conversation_private.sql delete mode 100644 drizzle/0002_turns.sql delete mode 100644 drizzle/0003_turn_feed_links.sql delete mode 100644 drizzle/0004_spirits.sql delete mode 100644 drizzle/0005_meta_summaries.sql delete mode 100644 drizzle/0006_channels.sql delete mode 100644 drizzle/0007_drop_conversation_name_title.sql delete mode 100644 drizzle/0008_performance_indexes.sql delete mode 100644 drizzle/0009_mind_notices.sql delete mode 100644 drizzle/0010_delivery_queue_redrive.sql delete mode 100644 drizzle/0011_drop_turn_summary_event_id.sql delete mode 100644 drizzle/0012_channel_gates.sql delete mode 100644 drizzle/0013_variant_purpose.sql delete mode 100644 drizzle/0014_system_events.sql delete mode 100644 drizzle/0015_rename_session_to_thread.sql delete mode 100644 drizzle/0016_brain_to_human.sql delete mode 100644 drizzle/0017_event_history_rows.sql delete mode 100644 drizzle/0018_api_tokens.sql delete mode 100644 drizzle/meta/0001_snapshot.json delete mode 100644 drizzle/meta/0002_snapshot.json delete mode 100644 drizzle/meta/0003_snapshot.json delete mode 100644 drizzle/meta/0004_snapshot.json delete mode 100644 drizzle/meta/0005_snapshot.json delete mode 100644 drizzle/meta/0006_snapshot.json delete mode 100644 drizzle/meta/0007_snapshot.json delete mode 100644 test/db-migrations.test.ts diff --git a/drizzle/0000_baseline.sql b/drizzle/0000_baseline.sql deleted file mode 100644 index a570f9e78..000000000 --- a/drizzle/0000_baseline.sql +++ /dev/null @@ -1,152 +0,0 @@ --- Baseline schema: all tables as of v0.30.x --- Uses IF NOT EXISTS so existing installs safely skip everything. - -CREATE TABLE IF NOT EXISTS `users` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `username` text NOT NULL, - `password_hash` text NOT NULL, - `role` text NOT NULL DEFAULT 'pending', - `user_type` text NOT NULL DEFAULT 'brain', - `display_name` text, - `description` text, - `avatar` text, - `created_at` text NOT NULL DEFAULT (datetime('now')) -); ---> statement-breakpoint -CREATE UNIQUE INDEX IF NOT EXISTS `users_username_unique` ON `users` (`username`); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS `minds` ( - `name` text PRIMARY KEY NOT NULL, - `port` integer NOT NULL, - `parent` text REFERENCES `minds`(`name`) ON DELETE CASCADE, - `dir` text, - `branch` text, - `stage` text, - `template` text, - `template_hash` text, - `running` integer NOT NULL DEFAULT 0, - `created_at` text NOT NULL DEFAULT (datetime('now')) -); ---> statement-breakpoint -CREATE UNIQUE INDEX IF NOT EXISTS `idx_minds_port` ON `minds` (`port`); ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS `idx_minds_parent` ON `minds` (`parent`); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS `conversations` ( - `id` text PRIMARY KEY NOT NULL, - `mind_name` text, - `channel` text NOT NULL, - `type` text NOT NULL DEFAULT 'dm', - `name` text, - `user_id` integer REFERENCES `users`(`id`), - `title` text, - `created_at` text NOT NULL DEFAULT (datetime('now')), - `updated_at` text NOT NULL DEFAULT (datetime('now')) -); ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS `idx_conversations_mind_name` ON `conversations` (`mind_name`); ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS `idx_conversations_user_id` ON `conversations` (`user_id`); ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS `idx_conversations_updated_at` ON `conversations` (`updated_at`); ---> statement-breakpoint -CREATE UNIQUE INDEX IF NOT EXISTS `idx_conversations_name` ON `conversations` (`name`); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS `messages` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `conversation_id` text NOT NULL REFERENCES `conversations`(`id`) ON DELETE CASCADE, - `role` text NOT NULL, - `sender_name` text, - `content` text NOT NULL, - `created_at` text NOT NULL DEFAULT (datetime('now')) -); ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS `idx_messages_conversation_id` ON `messages` (`conversation_id`); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS `conversation_participants` ( - `conversation_id` text NOT NULL REFERENCES `conversations`(`id`) ON DELETE CASCADE, - `user_id` integer NOT NULL REFERENCES `users`(`id`) ON DELETE CASCADE, - `role` text NOT NULL DEFAULT 'member', - `joined_at` text NOT NULL DEFAULT (datetime('now')) -); ---> statement-breakpoint -CREATE UNIQUE INDEX IF NOT EXISTS `idx_cp_unique` ON `conversation_participants` (`conversation_id`, `user_id`); ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS `idx_cp_user_id` ON `conversation_participants` (`user_id`); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS `sessions` ( - `id` text PRIMARY KEY NOT NULL, - `user_id` integer NOT NULL REFERENCES `users`(`id`) ON DELETE CASCADE, - `created_at` integer NOT NULL -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS `mind_history` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `mind` text NOT NULL, - `channel` text, - `session` text, - `sender` text, - `message_id` text, - `type` text NOT NULL DEFAULT 'inbound', - `content` text, - `metadata` text, - `created_at` text NOT NULL DEFAULT (datetime('now')) -); ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS `idx_mind_history_mind` ON `mind_history` (`mind`); ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS `idx_mind_history_mind_channel` ON `mind_history` (`mind`, `channel`); ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS `idx_mind_history_mind_type` ON `mind_history` (`mind`, `type`); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS `system_prompts` ( - `key` text PRIMARY KEY NOT NULL, - `content` text NOT NULL, - `updated_at` text NOT NULL DEFAULT (datetime('now')) -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS `shared_skills` ( - `id` text PRIMARY KEY NOT NULL, - `name` text NOT NULL, - `description` text NOT NULL DEFAULT '', - `author` text NOT NULL, - `version` integer NOT NULL DEFAULT 1, - `created_at` text NOT NULL DEFAULT (datetime('now')), - `updated_at` text NOT NULL DEFAULT (datetime('now')) -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS `delivery_queue` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `mind` text NOT NULL, - `session` text NOT NULL, - `channel` text, - `sender` text, - `status` text NOT NULL DEFAULT 'pending', - `payload` text NOT NULL, - `created_at` text NOT NULL DEFAULT (datetime('now')) -); ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS `idx_delivery_queue_mind_session` ON `delivery_queue` (`mind`, `session`); ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS `idx_delivery_queue_mind_status` ON `delivery_queue` (`mind`, `status`); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS `activity` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `type` text NOT NULL, - `mind` text NOT NULL, - `summary` text NOT NULL, - `metadata` text, - `created_at` text NOT NULL DEFAULT (datetime('now')) -); ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS `idx_activity_created_at` ON `activity` (`created_at`); ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS `idx_activity_mind` ON `activity` (`mind`); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS `conversation_reads` ( - `user_id` integer NOT NULL REFERENCES `users`(`id`) ON DELETE CASCADE, - `conversation_id` text NOT NULL REFERENCES `conversations`(`id`) ON DELETE CASCADE, - `last_read_message_id` integer NOT NULL DEFAULT 0 -); ---> statement-breakpoint -CREATE UNIQUE INDEX IF NOT EXISTS `idx_conversation_reads_unique` ON `conversation_reads` (`user_id`, `conversation_id`); diff --git a/drizzle/0000_crazy_epoch.sql b/drizzle/0000_crazy_epoch.sql new file mode 100644 index 000000000..024e7da64 --- /dev/null +++ b/drizzle/0000_crazy_epoch.sql @@ -0,0 +1,232 @@ +-- Squashed baseline: the full schema as of schema.ts (see #713). +-- Every statement uses IF NOT EXISTS so existing installs already at this +-- schema (e.g. bardo) run it as a no-op instead of failing on CREATE. +CREATE TABLE IF NOT EXISTS `activity` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `type` text NOT NULL, + `mind` text NOT NULL, + `summary` text NOT NULL, + `metadata` text, + `turn_id` text, + `source_event_id` integer, + `created_at` text DEFAULT (datetime('now')) NOT NULL +); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_activity_created_at` ON `activity` (`created_at`);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_activity_mind` ON `activity` (`mind`);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_activity_turn_id` ON `activity` (`turn_id`);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_activity_type` ON `activity` (`type`);--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `api_tokens` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `user_id` integer NOT NULL, + `token_hash` text NOT NULL, + `label` text, + `created_at` text DEFAULT (datetime('now')) NOT NULL, + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS `idx_api_tokens_hash` ON `api_tokens` (`token_hash`);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_api_tokens_user` ON `api_tokens` (`user_id`);--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `channel_gates` ( + `mind` text NOT NULL, + `channel` text NOT NULL, + `state` text NOT NULL, + `updated_at` text DEFAULT (datetime('now')) NOT NULL, + PRIMARY KEY(`mind`, `channel`) +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `channels` ( + `conversation_id` text PRIMARY KEY NOT NULL, + `name` text NOT NULL, + `description` text, + `rules` text, + `char_limit` integer, + `private` integer DEFAULT 0 NOT NULL, + `created_at` text DEFAULT (datetime('now')) NOT NULL, + `updated_at` text DEFAULT (datetime('now')) NOT NULL, + FOREIGN KEY (`conversation_id`) REFERENCES `conversations`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS `idx_channels_name` ON `channels` (`name`);--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `conversation_participants` ( + `conversation_id` text NOT NULL, + `user_id` integer NOT NULL, + `role` text DEFAULT 'member' NOT NULL, + `joined_at` text DEFAULT (datetime('now')) NOT NULL, + FOREIGN KEY (`conversation_id`) REFERENCES `conversations`(`id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS `idx_cp_unique` ON `conversation_participants` (`conversation_id`,`user_id`);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_cp_user_id` ON `conversation_participants` (`user_id`);--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `conversation_reads` ( + `user_id` integer NOT NULL, + `conversation_id` text NOT NULL, + `last_read_message_id` integer DEFAULT 0 NOT NULL, + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`conversation_id`) REFERENCES `conversations`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS `idx_conversation_reads_unique` ON `conversation_reads` (`user_id`,`conversation_id`);--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `conversations` ( + `id` text PRIMARY KEY NOT NULL, + `type` text DEFAULT 'dm' NOT NULL, + `user_id` integer, + `private` integer DEFAULT 0 NOT NULL, + `created_at` text DEFAULT (datetime('now')) NOT NULL, + `updated_at` text DEFAULT (datetime('now')) NOT NULL, + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_conversations_user_id` ON `conversations` (`user_id`);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_conversations_updated_at` ON `conversations` (`updated_at`);--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `delivery_queue` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `mind` text NOT NULL, + `target_mind` text, + `thread` text NOT NULL, + `channel` text, + `sender` text, + `status` text DEFAULT 'pending' NOT NULL, + `payload` text NOT NULL, + `attempts` integer DEFAULT 0 NOT NULL, + `next_attempt_at` text, + `created_at` text DEFAULT (datetime('now')) NOT NULL +); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_delivery_queue_mind_thread` ON `delivery_queue` (`mind`,`thread`);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_delivery_queue_mind_status` ON `delivery_queue` (`mind`,`status`);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_delivery_queue_status` ON `delivery_queue` (`status`);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_delivery_queue_status_next` ON `delivery_queue` (`status`,`next_attempt_at`);--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `messages` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `conversation_id` text NOT NULL, + `role` text NOT NULL, + `sender_name` text, + `content` text NOT NULL, + `source_event_id` integer, + `turn_id` text, + `created_at` text DEFAULT (datetime('now')) NOT NULL, + FOREIGN KEY (`conversation_id`) REFERENCES `conversations`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_messages_conversation_id` ON `messages` (`conversation_id`);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_messages_turn_id` ON `messages` (`turn_id`);--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `mind_history` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `mind` text NOT NULL, + `channel` text, + `thread` text, + `sender` text, + `message_id` text, + `type` text NOT NULL, + `content` text, + `metadata` text, + `turn_id` text, + `created_at` text DEFAULT (datetime('now')) NOT NULL +); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_mind_history_mind` ON `mind_history` (`mind`);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_mind_history_mind_channel` ON `mind_history` (`mind`,`channel`);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_mind_history_mind_type` ON `mind_history` (`mind`,`type`);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_mind_history_turn_id` ON `mind_history` (`turn_id`);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_mind_history_thread` ON `mind_history` (`thread`);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_mind_history_mind_created_at` ON `mind_history` (`mind`,`created_at`);--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `minds` ( + `name` text PRIMARY KEY NOT NULL, + `port` integer NOT NULL, + `parent` text, + `dir` text, + `branch` text, + `stage` text, + `purpose` text, + `template` text, + `template_hash` text, + `running` integer DEFAULT 0 NOT NULL, + `mind_type` text DEFAULT 'mind' NOT NULL, + `created_by` text, + `created_at` text DEFAULT (datetime('now')) NOT NULL, + FOREIGN KEY (`parent`) REFERENCES `minds`(`name`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS `minds_port_unique` ON `minds` (`port`);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_minds_parent` ON `minds` (`parent`);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_minds_mind_type` ON `minds` (`mind_type`);--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `sessions` ( + `id` text PRIMARY KEY NOT NULL, + `user_id` integer NOT NULL, + `created_at` integer NOT NULL, + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `shared_skills` ( + `id` text PRIMARY KEY NOT NULL, + `name` text NOT NULL, + `description` text DEFAULT '' NOT NULL, + `author` text NOT NULL, + `version` integer DEFAULT 1 NOT NULL, + `created_at` text DEFAULT (datetime('now')) NOT NULL, + `updated_at` text DEFAULT (datetime('now')) NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `summaries` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `mind` text NOT NULL, + `period` text NOT NULL, + `period_key` text NOT NULL, + `content` text NOT NULL, + `metadata` text, + `created_at` text DEFAULT (datetime('now')) NOT NULL +); +--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS `idx_summaries_unique` ON `summaries` (`mind`,`period`,`period_key`);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_summaries_mind_period` ON `summaries` (`mind`,`period`);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_summaries_mind_period_key` ON `summaries` (`mind`,`period_key`);--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `system_events` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `mind` text NOT NULL, + `type` text NOT NULL, + `body` text NOT NULL, + `meta` text, + `delivery` text DEFAULT 'immediate' NOT NULL, + `thread` text DEFAULT 'main' NOT NULL, + `created_at` text DEFAULT (datetime('now')) NOT NULL, + `delivered_at` text, + `reflection` text +); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_system_events_mind` ON `system_events` (`mind`);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_system_events_mind_delivery` ON `system_events` (`mind`,`delivery`,`delivered_at`);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_system_events_mind_type` ON `system_events` (`mind`,`type`);--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `system_prompts` ( + `key` text PRIMARY KEY NOT NULL, + `content` text NOT NULL, + `updated_at` text DEFAULT (datetime('now')) NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `turns` ( + `id` text PRIMARY KEY NOT NULL, + `mind` text NOT NULL, + `thread` text, + `trigger_event_id` integer, + `summary_id` integer, + `status` text DEFAULT 'active' NOT NULL, + `created_at` text DEFAULT (datetime('now')) NOT NULL +); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_turns_mind` ON `turns` (`mind`);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_turns_mind_status` ON `turns` (`mind`,`status`);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS `idx_turns_mind_created_at` ON `turns` (`mind`,`created_at`);--> statement-breakpoint +CREATE TABLE IF NOT EXISTS `users` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `username` text NOT NULL, + `password_hash` text NOT NULL, + `role` text DEFAULT 'pending' NOT NULL, + `user_type` text DEFAULT 'human' NOT NULL, + `display_name` text, + `description` text, + `avatar` text, + `created_at` text DEFAULT (datetime('now')) NOT NULL +); +--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS `users_username_unique` ON `users` (`username`); \ No newline at end of file diff --git a/drizzle/0001_add_conversation_private.sql b/drizzle/0001_add_conversation_private.sql deleted file mode 100644 index 5a1deea4d..000000000 --- a/drizzle/0001_add_conversation_private.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE `conversations` ADD `private` integer NOT NULL DEFAULT 0; \ No newline at end of file diff --git a/drizzle/0002_turns.sql b/drizzle/0002_turns.sql deleted file mode 100644 index 50179ade9..000000000 --- a/drizzle/0002_turns.sql +++ /dev/null @@ -1,21 +0,0 @@ --- First-class turns: track turn lifecycle and link events/messages to turns - -CREATE TABLE IF NOT EXISTS `turns` ( - `id` text PRIMARY KEY NOT NULL, - `mind` text NOT NULL, - `session` text, - `trigger_event_id` integer, - `summary_event_id` integer, - `status` text NOT NULL DEFAULT 'active', - `created_at` text NOT NULL DEFAULT (datetime('now')) -); ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS `idx_turns_mind` ON `turns` (`mind`); ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS `idx_turns_mind_status` ON `turns` (`mind`, `status`); ---> statement-breakpoint -ALTER TABLE `mind_history` ADD `turn_id` text; ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS `idx_mind_history_turn_id` ON `mind_history` (`turn_id`); ---> statement-breakpoint -ALTER TABLE `messages` ADD `source_event_id` integer; diff --git a/drizzle/0003_turn_feed_links.sql b/drizzle/0003_turn_feed_links.sql deleted file mode 100644 index 6a40f690c..000000000 --- a/drizzle/0003_turn_feed_links.sql +++ /dev/null @@ -1,11 +0,0 @@ --- Link feed items (messages, activities) to turns via turn_id - -ALTER TABLE `messages` ADD `turn_id` text; ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS `idx_messages_turn_id` ON `messages` (`turn_id`); ---> statement-breakpoint -ALTER TABLE `activity` ADD `turn_id` text; ---> statement-breakpoint -ALTER TABLE `activity` ADD `source_event_id` integer; ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS `idx_activity_turn_id` ON `activity` (`turn_id`); diff --git a/drizzle/0004_spirits.sql b/drizzle/0004_spirits.sql deleted file mode 100644 index b84387f12..000000000 --- a/drizzle/0004_spirits.sql +++ /dev/null @@ -1,5 +0,0 @@ -ALTER TABLE `minds` ADD `mind_type` text NOT NULL DEFAULT 'mind'; ---> statement-breakpoint -ALTER TABLE `minds` ADD `created_by` text; ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS `idx_minds_mind_type` ON `minds` (`mind_type`); diff --git a/drizzle/0005_meta_summaries.sql b/drizzle/0005_meta_summaries.sql deleted file mode 100644 index f03603852..000000000 --- a/drizzle/0005_meta_summaries.sql +++ /dev/null @@ -1,15 +0,0 @@ -CREATE TABLE IF NOT EXISTS `summaries` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `mind` text NOT NULL, - `period` text NOT NULL, - `period_key` text NOT NULL, - `content` text NOT NULL, - `metadata` text, - `created_at` text NOT NULL DEFAULT (datetime('now')) -); ---> statement-breakpoint -CREATE UNIQUE INDEX IF NOT EXISTS `idx_summaries_unique` ON `summaries` (`mind`, `period`, `period_key`); ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS `idx_summaries_mind_period` ON `summaries` (`mind`, `period`); ---> statement-breakpoint -ALTER TABLE `turns` ADD `summary_id` integer REFERENCES `summaries`(`id`); diff --git a/drizzle/0006_channels.sql b/drizzle/0006_channels.sql deleted file mode 100644 index 5372a1191..000000000 --- a/drizzle/0006_channels.sql +++ /dev/null @@ -1,17 +0,0 @@ -CREATE TABLE IF NOT EXISTS `channels` ( - `conversation_id` text PRIMARY KEY NOT NULL, - `name` text NOT NULL, - `description` text, - `rules` text, - `char_limit` integer, - `private` integer NOT NULL DEFAULT 0, - `created_at` text NOT NULL DEFAULT (datetime('now')), - `updated_at` text NOT NULL DEFAULT (datetime('now')), - FOREIGN KEY (`conversation_id`) REFERENCES `conversations`(`id`) ON UPDATE no action ON DELETE cascade -); ---> statement-breakpoint -CREATE UNIQUE INDEX IF NOT EXISTS `idx_channels_name` ON `channels` (`name`); ---> statement-breakpoint -INSERT INTO `channels` (`conversation_id`, `name`, `private`, `created_at`, `updated_at`) -SELECT `id`, `name`, `private`, `created_at`, `updated_at` -FROM `conversations` WHERE `type` = 'channel' AND `name` IS NOT NULL; diff --git a/drizzle/0007_drop_conversation_name_title.sql b/drizzle/0007_drop_conversation_name_title.sql deleted file mode 100644 index 769e371c0..000000000 --- a/drizzle/0007_drop_conversation_name_title.sql +++ /dev/null @@ -1,11 +0,0 @@ -DROP INDEX IF EXISTS idx_conversations_name; ---> statement-breakpoint -DROP INDEX IF EXISTS idx_conversations_mind_name; ---> statement-breakpoint -ALTER TABLE conversations DROP COLUMN name; ---> statement-breakpoint -ALTER TABLE conversations DROP COLUMN title; ---> statement-breakpoint -ALTER TABLE conversations DROP COLUMN mind_name; ---> statement-breakpoint -ALTER TABLE conversations DROP COLUMN channel; diff --git a/drizzle/0008_performance_indexes.sql b/drizzle/0008_performance_indexes.sql deleted file mode 100644 index 8d8a8658d..000000000 --- a/drizzle/0008_performance_indexes.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE INDEX IF NOT EXISTS `idx_turns_mind_created_at` ON `turns` (`mind`,`created_at`); -CREATE INDEX IF NOT EXISTS `idx_mind_history_session` ON `mind_history` (`session`); -CREATE INDEX IF NOT EXISTS `idx_mind_history_mind_created_at` ON `mind_history` (`mind`,`created_at`); -CREATE INDEX IF NOT EXISTS `idx_summaries_mind_period_key` ON `summaries` (`mind`,`period_key`); -CREATE INDEX IF NOT EXISTS `idx_activity_type` ON `activity` (`type`); -CREATE INDEX IF NOT EXISTS `idx_delivery_queue_status` ON `delivery_queue` (`status`); diff --git a/drizzle/0009_mind_notices.sql b/drizzle/0009_mind_notices.sql deleted file mode 100644 index 96551ebcc..000000000 --- a/drizzle/0009_mind_notices.sql +++ /dev/null @@ -1,14 +0,0 @@ --- Failure notices delivered to minds on their next successful turn - -CREATE TABLE IF NOT EXISTS `mind_notices` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `mind` text NOT NULL, - `session` text NOT NULL, - `kind` text NOT NULL, - `reason` text NOT NULL, - `detail` text NOT NULL, - `raw` text, - `created_at` text NOT NULL DEFAULT (datetime('now')) -); ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS `idx_mind_notices_mind_session` ON `mind_notices` (`mind`, `session`); diff --git a/drizzle/0010_delivery_queue_redrive.sql b/drizzle/0010_delivery_queue_redrive.sql deleted file mode 100644 index 5bfe9931d..000000000 --- a/drizzle/0010_delivery_queue_redrive.sql +++ /dev/null @@ -1,10 +0,0 @@ --- Make delivery_queue an at-least-once queue: track attempts and a backoff --- window so a redrive loop can re-deliver stranded rows without hot-looping. - -ALTER TABLE `delivery_queue` ADD `attempts` integer DEFAULT 0 NOT NULL;--> statement-breakpoint -ALTER TABLE `delivery_queue` ADD `next_attempt_at` text;--> statement-breakpoint --- Original delivery target (may be a variant name); redrive resolves the port from --- this so a variant's stranded row is re-delivered to the variant, not the parent. --- Nullable: legacy rows fall back to `mind` (the base name) at read time. -ALTER TABLE `delivery_queue` ADD `target_mind` text;--> statement-breakpoint -CREATE INDEX IF NOT EXISTS `idx_delivery_queue_status_next` ON `delivery_queue` (`status`, `next_attempt_at`); diff --git a/drizzle/0011_drop_turn_summary_event_id.sql b/drizzle/0011_drop_turn_summary_event_id.sql deleted file mode 100644 index d1cc0d7a1..000000000 --- a/drizzle/0011_drop_turn_summary_event_id.sql +++ /dev/null @@ -1,3 +0,0 @@ --- Drop the dead turns.summary_event_id column (superseded by summary_id, never written). - -ALTER TABLE `turns` DROP COLUMN `summary_event_id`; diff --git a/drizzle/0012_channel_gates.sql b/drizzle/0012_channel_gates.sql deleted file mode 100644 index 56947ab53..000000000 --- a/drizzle/0012_channel_gates.sql +++ /dev/null @@ -1,17 +0,0 @@ --- Per-(mind, channel) gate state so a mind can explicitly decline an unrouted --- channel. Absence of a row means "pending" (undecided); the only stored state --- is "declined". Declined channels are never released and never re-notify. -CREATE TABLE `channel_gates` ( - `mind` text NOT NULL, - `channel` text NOT NULL, - `state` text NOT NULL, - `updated_at` text DEFAULT (datetime('now')) NOT NULL, - PRIMARY KEY(`mind`, `channel`) -); ---> statement-breakpoint --- Backfill: archive stale gated rows so the first routes.json edit on an existing --- mind with a months-old backlog can't release hundreds of messages at once. The --- bounded release only ever promotes the newest N per channel anyway; anything --- older than a week is treated as history and made inert here. -UPDATE `delivery_queue` SET `status` = 'archived' - WHERE `status` = 'gated' AND `created_at` < datetime('now', '-7 days'); diff --git a/drizzle/0013_variant_purpose.sql b/drizzle/0013_variant_purpose.sql deleted file mode 100644 index 9f7a9ed73..000000000 --- a/drizzle/0013_variant_purpose.sql +++ /dev/null @@ -1,3 +0,0 @@ --- Purpose/intent captured at split time, recorded on the variant's row so it can --- orient the variant, feed the variant list UI, and default the join message. -ALTER TABLE `minds` ADD `purpose` text; \ No newline at end of file diff --git a/drizzle/0014_system_events.sql b/drizzle/0014_system_events.sql deleted file mode 100644 index e6f04bc0e..000000000 --- a/drizzle/0014_system_events.sql +++ /dev/null @@ -1,26 +0,0 @@ --- System events: environment → mind traffic (replaces mind_notices). --- No sender, no reply target; a separate stream from chat. - -CREATE TABLE IF NOT EXISTS `system_events` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `mind` text NOT NULL, - `type` text NOT NULL, - `body` text NOT NULL, - `meta` text, - `delivery` text NOT NULL DEFAULT 'immediate', - `session` text NOT NULL DEFAULT 'main', - `created_at` text NOT NULL DEFAULT (datetime('now')), - `delivered_at` text, - `reflection` text -); ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS `idx_system_events_mind` ON `system_events` (`mind`); ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS `idx_system_events_mind_delivery` ON `system_events` (`mind`, `delivery`, `delivered_at`); ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS `idx_system_events_mind_type` ON `system_events` (`mind`, `type`); ---> statement-breakpoint --- Deliberately discards any pending mind_notices rows at upgrade: notice rows were --- transient (deleted after delivery), so losing an undelivered failure notice across --- an upgrade is acceptable — no data migration is performed. -DROP TABLE IF EXISTS `mind_notices`; diff --git a/drizzle/0015_rename_session_to_thread.sql b/drizzle/0015_rename_session_to_thread.sql deleted file mode 100644 index 68ed2e396..000000000 --- a/drizzle/0015_rename_session_to_thread.sql +++ /dev/null @@ -1,12 +0,0 @@ --- Rename the routing "session" column to "thread" on turns, mind_history, --- delivery_queue, and system_events. The routing sense of "session" is renamed --- to "thread" (issue #493); auth sessions and SDK transcript files keep "session". -ALTER TABLE `turns` RENAME COLUMN `session` TO `thread`;--> statement-breakpoint -ALTER TABLE `mind_history` RENAME COLUMN `session` TO `thread`;--> statement-breakpoint -DROP INDEX IF EXISTS `idx_mind_history_session`;--> statement-breakpoint -CREATE INDEX IF NOT EXISTS `idx_mind_history_thread` ON `mind_history` (`thread`);--> statement-breakpoint -ALTER TABLE `delivery_queue` RENAME COLUMN `session` TO `thread`;--> statement-breakpoint -DROP INDEX IF EXISTS `idx_delivery_queue_mind_session`;--> statement-breakpoint -CREATE INDEX IF NOT EXISTS `idx_delivery_queue_mind_thread` ON `delivery_queue` (`mind`, `thread`);--> statement-breakpoint --- system_events is created by 0014 on this same train, so the rename is always valid. -ALTER TABLE `system_events` RENAME COLUMN `session` TO `thread`; diff --git a/drizzle/0016_brain_to_human.sql b/drizzle/0016_brain_to_human.sql deleted file mode 100644 index 45405730f..000000000 --- a/drizzle/0016_brain_to_human.sql +++ /dev/null @@ -1,4 +0,0 @@ --- Retire the "brain" user_type in favour of "human" (issue #493). Human users --- were stored as user_type = 'brain'; migrate live rows so prefixes and profile --- cards render [human]. Mind, system, and puppet types are unchanged. -UPDATE `users` SET `user_type` = 'human' WHERE `user_type` = 'brain'; diff --git a/drizzle/0017_event_history_rows.sql b/drizzle/0017_event_history_rows.sql deleted file mode 100644 index 5edc94f1a..000000000 --- a/drizzle/0017_event_history_rows.sql +++ /dev/null @@ -1,9 +0,0 @@ --- System events are not messages. They were recorded in mind_history as `inbound` rows, --- so every history surface rendered them as chat with a phantom sender ("user"), and minds --- read them as messages they were expected to reply to. Retype the delivered-event rows so --- the timeline and `volute mind history` can render them as system markers instead. --- --- The `event::` channel is written only by recordEventRow, so it identifies --- these rows exactly. Turn linkage (turn_id / trigger_event_id) is untouched — the linkers --- match both types, so reflection capture keeps working for historical turns. -UPDATE `mind_history` SET `type` = 'event' WHERE `type` = 'inbound' AND `channel` LIKE 'event:%'; diff --git a/drizzle/0018_api_tokens.sql b/drizzle/0018_api_tokens.sql deleted file mode 100644 index dc763d56e..000000000 --- a/drizzle/0018_api_tokens.sql +++ /dev/null @@ -1,20 +0,0 @@ --- Durable, hashed, revocable per-user API credentials. --- --- Keyed to a `users` row rather than a mind name, so the same token type serves --- external minds (users rows with user_type "mind" and no minds registry row) --- and, later, external humans. Only the SHA-256 hash is stored; revocation is a --- row DELETE, and the FK cascade drops a user's tokens along with the user. --- --- Orthogonal to the in-memory native-mind token map: these survive a restart. - -CREATE TABLE IF NOT EXISTS `api_tokens` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `user_id` integer NOT NULL REFERENCES `users`(`id`) ON DELETE CASCADE, - `token_hash` text NOT NULL, - `label` text, - `created_at` text NOT NULL DEFAULT (datetime('now')) -); ---> statement-breakpoint -CREATE UNIQUE INDEX IF NOT EXISTS `idx_api_tokens_hash` ON `api_tokens` (`token_hash`); ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS `idx_api_tokens_user` ON `api_tokens` (`user_id`); diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json index d22b1258d..702380b81 100644 --- a/drizzle/meta/0000_snapshot.json +++ b/drizzle/meta/0000_snapshot.json @@ -1,7 +1,1424 @@ { - "id": "0000_baseline", - "prevId": "", "version": "6", "dialect": "sqlite", - "tables": {} + "id": "d7bf9184-e44d-43ad-ade0-6eed5fbf7bc8", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "activity": { + "name": "activity", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mind": { + "name": "mind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "turn_id": { + "name": "turn_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source_event_id": { + "name": "source_event_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + } + }, + "indexes": { + "idx_activity_created_at": { + "name": "idx_activity_created_at", + "columns": ["created_at"], + "isUnique": false + }, + "idx_activity_mind": { + "name": "idx_activity_mind", + "columns": ["mind"], + "isUnique": false + }, + "idx_activity_turn_id": { + "name": "idx_activity_turn_id", + "columns": ["turn_id"], + "isUnique": false + }, + "idx_activity_type": { + "name": "idx_activity_type", + "columns": ["type"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "api_tokens": { + "name": "api_tokens", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + } + }, + "indexes": { + "idx_api_tokens_hash": { + "name": "idx_api_tokens_hash", + "columns": ["token_hash"], + "isUnique": true + }, + "idx_api_tokens_user": { + "name": "idx_api_tokens_user", + "columns": ["user_id"], + "isUnique": false + } + }, + "foreignKeys": { + "api_tokens_user_id_users_id_fk": { + "name": "api_tokens_user_id_users_id_fk", + "tableFrom": "api_tokens", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "channel_gates": { + "name": "channel_gates", + "columns": { + "mind": { + "name": "mind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "channel_gates_mind_channel_pk": { + "columns": ["mind", "channel"], + "name": "channel_gates_mind_channel_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "channels": { + "name": "channels", + "columns": { + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rules": { + "name": "rules", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "char_limit": { + "name": "char_limit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "private": { + "name": "private", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + } + }, + "indexes": { + "idx_channels_name": { + "name": "idx_channels_name", + "columns": ["name"], + "isUnique": true + } + }, + "foreignKeys": { + "channels_conversation_id_conversations_id_fk": { + "name": "channels_conversation_id_conversations_id_fk", + "tableFrom": "channels", + "tableTo": "conversations", + "columnsFrom": ["conversation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "conversation_participants": { + "name": "conversation_participants", + "columns": { + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'member'" + }, + "joined_at": { + "name": "joined_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + } + }, + "indexes": { + "idx_cp_unique": { + "name": "idx_cp_unique", + "columns": ["conversation_id", "user_id"], + "isUnique": true + }, + "idx_cp_user_id": { + "name": "idx_cp_user_id", + "columns": ["user_id"], + "isUnique": false + } + }, + "foreignKeys": { + "conversation_participants_conversation_id_conversations_id_fk": { + "name": "conversation_participants_conversation_id_conversations_id_fk", + "tableFrom": "conversation_participants", + "tableTo": "conversations", + "columnsFrom": ["conversation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "conversation_participants_user_id_users_id_fk": { + "name": "conversation_participants_user_id_users_id_fk", + "tableFrom": "conversation_participants", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "conversation_reads": { + "name": "conversation_reads", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_read_message_id": { + "name": "last_read_message_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "idx_conversation_reads_unique": { + "name": "idx_conversation_reads_unique", + "columns": ["user_id", "conversation_id"], + "isUnique": true + } + }, + "foreignKeys": { + "conversation_reads_user_id_users_id_fk": { + "name": "conversation_reads_user_id_users_id_fk", + "tableFrom": "conversation_reads", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "conversation_reads_conversation_id_conversations_id_fk": { + "name": "conversation_reads_conversation_id_conversations_id_fk", + "tableFrom": "conversation_reads", + "tableTo": "conversations", + "columnsFrom": ["conversation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "conversations": { + "name": "conversations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'dm'" + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "private": { + "name": "private", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + } + }, + "indexes": { + "idx_conversations_user_id": { + "name": "idx_conversations_user_id", + "columns": ["user_id"], + "isUnique": false + }, + "idx_conversations_updated_at": { + "name": "idx_conversations_updated_at", + "columns": ["updated_at"], + "isUnique": false + } + }, + "foreignKeys": { + "conversations_user_id_users_id_fk": { + "name": "conversations_user_id_users_id_fk", + "tableFrom": "conversations", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "delivery_queue": { + "name": "delivery_queue", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "mind": { + "name": "mind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_mind": { + "name": "target_mind", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "thread": { + "name": "thread", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sender": { + "name": "sender", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + } + }, + "indexes": { + "idx_delivery_queue_mind_thread": { + "name": "idx_delivery_queue_mind_thread", + "columns": ["mind", "thread"], + "isUnique": false + }, + "idx_delivery_queue_mind_status": { + "name": "idx_delivery_queue_mind_status", + "columns": ["mind", "status"], + "isUnique": false + }, + "idx_delivery_queue_status": { + "name": "idx_delivery_queue_status", + "columns": ["status"], + "isUnique": false + }, + "idx_delivery_queue_status_next": { + "name": "idx_delivery_queue_status_next", + "columns": ["status", "next_attempt_at"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "messages": { + "name": "messages", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sender_name": { + "name": "sender_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_event_id": { + "name": "source_event_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "turn_id": { + "name": "turn_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + } + }, + "indexes": { + "idx_messages_conversation_id": { + "name": "idx_messages_conversation_id", + "columns": ["conversation_id"], + "isUnique": false + }, + "idx_messages_turn_id": { + "name": "idx_messages_turn_id", + "columns": ["turn_id"], + "isUnique": false + } + }, + "foreignKeys": { + "messages_conversation_id_conversations_id_fk": { + "name": "messages_conversation_id_conversations_id_fk", + "tableFrom": "messages", + "tableTo": "conversations", + "columnsFrom": ["conversation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "mind_history": { + "name": "mind_history", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "mind": { + "name": "mind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "thread": { + "name": "thread", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sender": { + "name": "sender", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "turn_id": { + "name": "turn_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + } + }, + "indexes": { + "idx_mind_history_mind": { + "name": "idx_mind_history_mind", + "columns": ["mind"], + "isUnique": false + }, + "idx_mind_history_mind_channel": { + "name": "idx_mind_history_mind_channel", + "columns": ["mind", "channel"], + "isUnique": false + }, + "idx_mind_history_mind_type": { + "name": "idx_mind_history_mind_type", + "columns": ["mind", "type"], + "isUnique": false + }, + "idx_mind_history_turn_id": { + "name": "idx_mind_history_turn_id", + "columns": ["turn_id"], + "isUnique": false + }, + "idx_mind_history_thread": { + "name": "idx_mind_history_thread", + "columns": ["thread"], + "isUnique": false + }, + "idx_mind_history_mind_created_at": { + "name": "idx_mind_history_mind_created_at", + "columns": ["mind", "created_at"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "minds": { + "name": "minds", + "columns": { + "name": { + "name": "name", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "parent": { + "name": "parent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dir": { + "name": "dir", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "stage": { + "name": "stage", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "purpose": { + "name": "purpose", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "template": { + "name": "template", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "template_hash": { + "name": "template_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "running": { + "name": "running", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "mind_type": { + "name": "mind_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'mind'" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + } + }, + "indexes": { + "minds_port_unique": { + "name": "minds_port_unique", + "columns": ["port"], + "isUnique": true + }, + "idx_minds_parent": { + "name": "idx_minds_parent", + "columns": ["parent"], + "isUnique": false + }, + "idx_minds_mind_type": { + "name": "idx_minds_mind_type", + "columns": ["mind_type"], + "isUnique": false + } + }, + "foreignKeys": { + "minds_parent_minds_name_fk": { + "name": "minds_parent_minds_name_fk", + "tableFrom": "minds", + "tableTo": "minds", + "columnsFrom": ["parent"], + "columnsTo": ["name"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sessions": { + "name": "sessions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shared_skills": { + "name": "shared_skills", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "author": { + "name": "author", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "summaries": { + "name": "summaries", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "mind": { + "name": "mind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "period": { + "name": "period", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "period_key": { + "name": "period_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + } + }, + "indexes": { + "idx_summaries_unique": { + "name": "idx_summaries_unique", + "columns": ["mind", "period", "period_key"], + "isUnique": true + }, + "idx_summaries_mind_period": { + "name": "idx_summaries_mind_period", + "columns": ["mind", "period"], + "isUnique": false + }, + "idx_summaries_mind_period_key": { + "name": "idx_summaries_mind_period_key", + "columns": ["mind", "period_key"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "system_events": { + "name": "system_events", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "mind": { + "name": "mind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "meta": { + "name": "meta", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "delivery": { + "name": "delivery", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'immediate'" + }, + "thread": { + "name": "thread", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'main'" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + }, + "delivered_at": { + "name": "delivered_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reflection": { + "name": "reflection", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_system_events_mind": { + "name": "idx_system_events_mind", + "columns": ["mind"], + "isUnique": false + }, + "idx_system_events_mind_delivery": { + "name": "idx_system_events_mind_delivery", + "columns": ["mind", "delivery", "delivered_at"], + "isUnique": false + }, + "idx_system_events_mind_type": { + "name": "idx_system_events_mind_type", + "columns": ["mind", "type"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "system_prompts": { + "name": "system_prompts", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "turns": { + "name": "turns", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "mind": { + "name": "mind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "thread": { + "name": "thread", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "trigger_event_id": { + "name": "trigger_event_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "summary_id": { + "name": "summary_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + } + }, + "indexes": { + "idx_turns_mind": { + "name": "idx_turns_mind", + "columns": ["mind"], + "isUnique": false + }, + "idx_turns_mind_status": { + "name": "idx_turns_mind_status", + "columns": ["mind", "status"], + "isUnique": false + }, + "idx_turns_mind_created_at": { + "name": "idx_turns_mind_created_at", + "columns": ["mind", "created_at"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "user_type": { + "name": "user_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'human'" + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(datetime('now'))" + } + }, + "indexes": { + "users_username_unique": { + "name": "users_username_unique", + "columns": ["username"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } } diff --git a/drizzle/meta/0001_snapshot.json b/drizzle/meta/0001_snapshot.json deleted file mode 100644 index 8f86f3da8..000000000 --- a/drizzle/meta/0001_snapshot.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "id": "0001_add_conversation_private", - "prevId": "0000_baseline", - "version": "6", - "dialect": "sqlite", - "tables": {} -} diff --git a/drizzle/meta/0002_snapshot.json b/drizzle/meta/0002_snapshot.json deleted file mode 100644 index 048f768db..000000000 --- a/drizzle/meta/0002_snapshot.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "id": "0002_turns", - "prevId": "0001_add_conversation_private", - "version": "6", - "dialect": "sqlite", - "tables": {} -} diff --git a/drizzle/meta/0003_snapshot.json b/drizzle/meta/0003_snapshot.json deleted file mode 100644 index 472831aa7..000000000 --- a/drizzle/meta/0003_snapshot.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "id": "0003_turn_feed_links", - "prevId": "0002_turns", - "version": "6", - "dialect": "sqlite", - "tables": {} -} diff --git a/drizzle/meta/0004_snapshot.json b/drizzle/meta/0004_snapshot.json deleted file mode 100644 index 9d76d54e4..000000000 --- a/drizzle/meta/0004_snapshot.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "id": "0004_spirits", - "prevId": "0003_turn_feed_links", - "version": "6", - "dialect": "sqlite", - "tables": {} -} diff --git a/drizzle/meta/0005_snapshot.json b/drizzle/meta/0005_snapshot.json deleted file mode 100644 index 58f91914a..000000000 --- a/drizzle/meta/0005_snapshot.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "id": "0005_meta_summaries", - "prevId": "0004_spirits", - "version": "6", - "dialect": "sqlite", - "tables": {} -} diff --git a/drizzle/meta/0006_snapshot.json b/drizzle/meta/0006_snapshot.json deleted file mode 100644 index 8c337aacf..000000000 --- a/drizzle/meta/0006_snapshot.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "id": "0006_channels", - "prevId": "0005_meta_summaries", - "version": "6", - "dialect": "sqlite", - "tables": {} -} diff --git a/drizzle/meta/0007_snapshot.json b/drizzle/meta/0007_snapshot.json deleted file mode 100644 index c835c45db..000000000 --- a/drizzle/meta/0007_snapshot.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "id": "0007_drop_conversation_name_title", - "prevId": "0006_channels", - "version": "6", - "dialect": "sqlite", - "tables": {} -} diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index f20e0f2ca..67e052354 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -5,134 +5,8 @@ { "idx": 0, "version": "6", - "when": 1772600000000, - "tag": "0000_baseline", - "breakpoints": true - }, - { - "idx": 1, - "version": "6", - "when": 1773700000000, - "tag": "0001_add_conversation_private", - "breakpoints": true - }, - { - "idx": 2, - "version": "6", - "when": 1773800000000, - "tag": "0002_turns", - "breakpoints": true - }, - { - "idx": 3, - "version": "6", - "when": 1774800000000, - "tag": "0003_turn_feed_links", - "breakpoints": true - }, - { - "idx": 4, - "version": "6", - "when": 1774900000000, - "tag": "0004_spirits", - "breakpoints": true - }, - { - "idx": 5, - "version": "6", - "when": 1775000000000, - "tag": "0005_meta_summaries", - "breakpoints": true - }, - { - "idx": 6, - "version": "6", - "when": 1775100000000, - "tag": "0006_channels", - "breakpoints": true - }, - { - "idx": 7, - "version": "6", - "when": 1775200000000, - "tag": "0007_drop_conversation_name_title", - "breakpoints": true - }, - { - "idx": 8, - "version": "6", - "when": 1775300000000, - "tag": "0008_performance_indexes", - "breakpoints": true - }, - { - "idx": 9, - "version": "6", - "when": 1775400000000, - "tag": "0009_mind_notices", - "breakpoints": true - }, - { - "idx": 10, - "version": "6", - "when": 1775500000000, - "tag": "0010_delivery_queue_redrive", - "breakpoints": true - }, - { - "idx": 11, - "version": "6", - "when": 1775600000000, - "tag": "0011_drop_turn_summary_event_id", - "breakpoints": true - }, - { - "idx": 12, - "version": "6", - "when": 1775700000000, - "tag": "0012_channel_gates", - "breakpoints": true - }, - { - "idx": 13, - "version": "6", - "when": 1775800000000, - "tag": "0013_variant_purpose", - "breakpoints": true - }, - { - "idx": 14, - "version": "6", - "when": 1775900000000, - "tag": "0014_system_events", - "breakpoints": true - }, - { - "idx": 15, - "version": "6", - "when": 1776000000000, - "tag": "0015_rename_session_to_thread", - "breakpoints": true - }, - { - "idx": 16, - "version": "6", - "when": 1776100000000, - "tag": "0016_brain_to_human", - "breakpoints": true - }, - { - "idx": 17, - "version": "6", - "when": 1776200000000, - "tag": "0017_event_history_rows", - "breakpoints": true - }, - { - "idx": 18, - "version": "6", - "when": 1776300000000, - "tag": "0018_api_tokens", + "when": 1784840885350, + "tag": "0000_crazy_epoch", "breakpoints": true } ] diff --git a/test/db-migrations.test.ts b/test/db-migrations.test.ts deleted file mode 100644 index 3b4a81f55..000000000 --- a/test/db-migrations.test.ts +++ /dev/null @@ -1,193 +0,0 @@ -import assert from "node:assert/strict"; -import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { resolve } from "node:path"; -import { after, describe, it } from "node:test"; -import { sql } from "drizzle-orm"; -import { drizzle } from "drizzle-orm/libsql"; -import { migrate } from "drizzle-orm/libsql/migrator"; - -// Data-preservation test for the #493 rename migrations. The regular test suite -// only ever applies migrations to fresh empty databases; this one populates a -// scratch DB with pre-rename rows (via migrations 0000–0013), then applies -// 0015 (session→thread column renames) and 0016 (brain→human user_type) and -// asserts the data survives under the new names. - -const MIGRATIONS_DIR = resolve(import.meta.dirname, "../drizzle"); -const CUTOFF_TAG = "0015_rename_session_to_thread"; - -type JournalEntry = { idx: number; tag: string } & Record; -type Journal = { entries: JournalEntry[] } & Record; - -/** Copy the real migrations folder, truncating the journal to entries before `beforeTag`. */ -function migrationsSubset(dest: string, beforeTag: string | null): void { - rmSync(dest, { recursive: true, force: true }); - cpSync(MIGRATIONS_DIR, dest, { recursive: true }); - if (beforeTag == null) return; - const journalPath = resolve(dest, "meta/_journal.json"); - const journal = JSON.parse(readFileSync(journalPath, "utf-8")) as Journal; - const cutoff = journal.entries.findIndex((e) => e.tag === beforeTag); - assert.ok(cutoff > 0, `journal must contain ${beforeTag}`); - journal.entries = journal.entries.slice(0, cutoff); - writeFileSync(journalPath, JSON.stringify(journal, null, 2)); -} - -describe("rename migrations preserve data (0015 session→thread, 0016 brain→human)", () => { - const scratch = mkdtempSync(resolve(tmpdir(), "volute-migration-test-")); - after(() => rmSync(scratch, { recursive: true, force: true })); - - it("migrates populated tables in place", async () => { - const db = drizzle({ connection: { url: `file:${resolve(scratch, "test.db")}` } }); - - // 1. Apply everything BEFORE the rename, so the schema still has `session` - // columns and the users table still defaults to "brain"-era values. - const preDir = resolve(scratch, "migrations-pre"); - mkdirSync(preDir, { recursive: true }); - migrationsSubset(preDir, CUTOFF_TAG); - await migrate(db, { migrationsFolder: preDir }); - - // 2. Insert legacy-shaped rows. - await db.run( - sql.raw( - `INSERT INTO turns (id, mind, session, status) VALUES ('t1', 'echo', 'main', 'complete')`, - ), - ); - await db.run( - sql.raw( - `INSERT INTO mind_history (mind, channel, session, type, content) VALUES ('echo', '@alice', '@alice', 'inbound', 'hello')`, - ), - ); - // A pending delivery_queue row — exactly what a live system would have - // in flight across the upgrade. - await db.run( - sql.raw( - `INSERT INTO delivery_queue (mind, session, channel, status, payload) VALUES ('echo', 'discord', 'discord:general', 'pending', '{}')`, - ), - ); - await db.run( - sql.raw( - `INSERT INTO system_events (mind, type, body, delivery, session) VALUES ('echo', 'notice', 'heads up', 'next-turn', 'main')`, - ), - ); - await db.run( - sql.raw( - `INSERT INTO users (username, password_hash, role, user_type) VALUES ('alice', '!x', 'admin', 'brain')`, - ), - ); - await db.run( - sql.raw( - `INSERT INTO users (username, password_hash, role, user_type) VALUES ('echo', '!x', 'user', 'mind')`, - ), - ); - - // 3. Apply the rename migrations (0015, 0016) on top. - const fullDir = resolve(scratch, "migrations-full"); - mkdirSync(fullDir, { recursive: true }); - migrationsSubset(fullDir, null); - await migrate(db, { migrationsFolder: fullDir }); - - // 4. Data survives under the new column name / value. - const turn = (await db.all(sql.raw(`SELECT * FROM turns WHERE id = 't1'`)))[0] as Record< - string, - unknown - >; - assert.equal(turn.thread, "main"); - assert.ok(!("session" in turn), "turns must not keep a session column"); - - const hist = ( - await db.all(sql.raw(`SELECT * FROM mind_history WHERE mind = 'echo'`)) - )[0] as Record; - assert.equal(hist.thread, "@alice"); - assert.ok(!("session" in hist), "mind_history must not keep a session column"); - - const dq = ( - await db.all(sql.raw(`SELECT * FROM delivery_queue WHERE mind = 'echo'`)) - )[0] as Record; - assert.equal(dq.thread, "discord"); - assert.equal(dq.status, "pending", "in-flight delivery must survive the rename"); - assert.ok(!("session" in dq), "delivery_queue must not keep a session column"); - - const se = ( - await db.all(sql.raw(`SELECT * FROM system_events WHERE mind = 'echo'`)) - )[0] as Record; - assert.equal(se.thread, "main"); - assert.ok(!("session" in se), "system_events must not keep a session column"); - - const userTypes = (await db.all( - sql.raw(`SELECT username, user_type FROM users ORDER BY username`), - )) as { username: string; user_type: string }[]; - assert.deepEqual( - userTypes.map((u) => `${u.username}:${u.user_type}`), - ["alice:human", "echo:mind"], - "brain users become human; mind users are untouched", - ); - - // 5. The renamed indexes were recreated. - const indexes = (await db.all( - sql.raw(`SELECT name FROM sqlite_master WHERE type = 'index'`), - )) as { name: string }[]; - const names = new Set(indexes.map((i) => i.name)); - assert.ok(names.has("idx_mind_history_thread"), "mind_history thread index exists"); - assert.ok(names.has("idx_delivery_queue_mind_thread"), "delivery_queue thread index exists"); - assert.ok(!names.has("idx_mind_history_session"), "old mind_history index dropped"); - assert.ok(!names.has("idx_delivery_queue_mind_session"), "old delivery_queue index dropped"); - }); -}); - -describe("0017 retypes delivered system events, and nothing else", () => { - const scratch = mkdtempSync(resolve(tmpdir(), "volute-migration-0017-")); - after(() => rmSync(scratch, { recursive: true, force: true })); - - it("flips only inbound rows on an event: channel", async () => { - const db = drizzle({ connection: { url: `file:${resolve(scratch, "test.db")}` } }); - - // Everything before 0017: system events were still recorded as `inbound` rows. - const preDir = resolve(scratch, "migrations-pre"); - mkdirSync(preDir, { recursive: true }); - migrationsSubset(preDir, "0017_event_history_rows"); - await migrate(db, { migrationsFolder: preDir }); - - const rows: [string, string, string][] = [ - // The two legacy event rows the migration must retype... - ["event:orientation:1", "inbound", "welcome to the world"], - ["event:schedule:42", "inbound", "morning check-in"], - // ...and rows it must not touch. The `channel LIKE 'event:%'` guard is all that stands - // between this migration and retyping every message a mind ever received — which would - // erase every conversation from every timeline on the host. - ["@alice", "inbound", "hey, you around?"], - ["#general", "inbound", "standup in 5"], - ["@alice", "outbound", "yep, here"], - // A channel that merely mentions events without being one. - ["#events-planning", "inbound", "who is booking the venue?"], - ]; - for (const [channel, type, content] of rows) { - await db.run( - sql.raw( - `INSERT INTO mind_history (mind, channel, thread, type, content) VALUES ('echo', '${channel}', 'main', '${type}', '${content}')`, - ), - ); - } - - const fullDir = resolve(scratch, "migrations-full"); - mkdirSync(fullDir, { recursive: true }); - migrationsSubset(fullDir, null); - await migrate(db, { migrationsFolder: fullDir }); - - const after = (await db.all( - sql.raw(`SELECT channel, type FROM mind_history WHERE mind = 'echo' ORDER BY id`), - )) as { channel: string; type: string }[]; - - assert.deepEqual( - after.map((r) => `${r.channel}=${r.type}`), - [ - "event:orientation:1=event", - "event:schedule:42=event", - "@alice=inbound", - "#general=inbound", - "@alice=outbound", - "#events-planning=inbound", - ], - "only event: channels flip to type=event; real conversation is untouched", - ); - }); -}); From 9d86416aa3eef9909a772cec2bee8fe1b8696572 Mon Sep 17 00:00:00 2001 From: hekataion Date: Fri, 24 Jul 2026 09:22:15 -0400 Subject: [PATCH 2/3] docs: add drizzle README with no-manual-migrations rule Explains why hand-written migrations break the Drizzle snapshot chain and documents the idempotent baseline pattern. Co-Authored-By: Claude --- drizzle/README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 drizzle/README.md diff --git a/drizzle/README.md b/drizzle/README.md new file mode 100644 index 000000000..d6f13546b --- /dev/null +++ b/drizzle/README.md @@ -0,0 +1,42 @@ +# Database Migrations + +## The Rule: No Hand-Written Migrations + +**Never write SQL migration files by hand.** All migrations must be generated +from `schema.ts` using: + +```sh +npm run db:generate +``` + +Drizzle tracks schema state via the `meta/` snapshots. Hand-written SQL breaks +this chain—the next `db:generate` won't know what exists, and you'll get +duplicate or conflicting migrations. + +## Why This Matters + +Volute uses **idempotent migrations** (`CREATE TABLE IF NOT EXISTS`, etc.) so +the same migration file can run safely on both fresh installs and existing +databases. This pattern only works when: + +1. Every migration is generated from the source-of-truth schema +2. The `meta/` snapshots stay in sync with the SQL files +3. No one sneaks in manual DDL that the snapshots don't reflect + +## Workflow + +1. Edit `src/db/schema.ts` +2. Run `npm run db:generate` — it creates the migration and updates snapshots +3. Review the generated SQL (in `drizzle/NNNN_*.sql`) +4. Run `npm run db:migrate` to apply + +If `db:generate` produces nothing, your schema change is already reflected. +If it produces something unexpected, your schema or the snapshots drifted— +investigate before committing. + +## The Baseline + +`0000_baseline.sql` is a squashed, idempotent snapshot of the entire schema +as of July 2024. It uses `IF NOT EXISTS` throughout so it runs as a no-op on +databases that already have these tables. All future migrations build on top +of this baseline. From cab8a84aef54c2e5e97b4d29ccd6bf2199e59e4b Mon Sep 17 00:00:00 2001 From: hekataion Date: Fri, 24 Jul 2026 09:25:17 -0400 Subject: [PATCH 3/3] docs: add no-manual-migrations rule to CLAUDE.md Ensures Claude Code coders see the rule without needing to read drizzle/README.md first. Co-Authored-By: Claude --- CLAUDE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CLAUDE.md b/CLAUDE.md index 20240a166..f89e9a3ba 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -138,6 +138,7 @@ The daemon serves a Hono web server (default port 1618) with a Svelte frontend. - **Frontend** (`packages/web/`): Svelte 5 SPA with login, dashboard, and mind detail pages (chat, logs, files, variants, connections tabs). Shared UI components imported from `@volute/ui` - **Auth**: Cookie-based (`volute_session`), in-memory session map, first user auto-admin; durable `vmt_` API tokens in the `api_tokens` table - **Database**: libSQL at `~/.volute/system/volute.db`. The Drizzle schema (`packages/daemon/src/lib/schema.ts`) is the source of truth for the table list — don't enumerate tables in docs +- **Migrations**: All migrations must be generated from `schema.ts` via `npm run db:generate` — **never write SQL migration files by hand**. Hand-written SQL breaks the Drizzle snapshot chain in `drizzle/meta/`, leaving the next `db:generate` out of sync. See `drizzle/README.md` for the full workflow. - **Build**: `vite build` → `dist/web-assets/` ### Extensions