using InvercargillSql.Migrations; namespace ClankerSaddle.Data.Migrations { /** * The full ClankerSaddle schema in one migration (plan 11: every * earlier migration flattened — the database is not deployed, so * tear-down-and-recreate replaced incremental evolution; plan 17 * flattened again over the force-wake era). Covers: projects and * their links; the plan-12 goals (with links — plan 15 C1/C2 * deleted the goal-side block/comment tables: the goal agent's * blocks and comments ride the run task's own rows) and the * flattened task shape (`goal_id`, `kind`, `mode`, `creator`, * `enqueue_after_task_id`, plus the force-wake flags folded in * from the deleted M0002); tasks with comments and typed blocks; * saddle registrations with git identity and the per-saddle * token; sessions; the plan-17 UNIFIED EVENT LEDGER (`events`) * that replaced `session_commands`, `task_progress`, * `session_raw_log` and `saddle_entry_dedup` — every timeline, * narrative, resume-notice and log-view surface derives from it, * and daemon chunk pushes are idempotent on its * UNIQUE(session_id, seq); rules and their attachments; project * notes; capability tags; the plan-11 notification tables. */ public class M0001_Initial : Migration { public override string name { get { return "Create the ClankerSaddle schema"; } } public override uint64 serial { get { return 1; } } public override string migration_namespace { get { return "clanker-saddle"; } } public override void up(MigrationBuilder b) { b.create_table("projects", (t) => { t.column("id") .auto_increment() .primary_key(); t.column("name") .not_null(); t.column("git_url") .not_null(); t.column("default_branch") .default_value("main"); t.column("summary"); t.column("created_at"); }); b.create_table("project_links", (t) => { t.column("id") .auto_increment() .primary_key(); t.column("parent_project_id") .not_null() .references("projects", "id"); t.column("linked_project_id") .not_null() .references("projects", "id"); t.column("relationship") .not_null(); }); // Stacks (goal 8 task 2): a named bundle of reference // projects applied one-click to a project's or a goal's // link set — the common reference collections a fleet of // repos always wants attached. b.create_table("stacks", (t) => { t.column("id") .auto_increment() .primary_key(); t.column("name") .not_null() .unique(); t.column("summary"); t.column("created_at"); }); b.create_table("stack_projects", (t) => { t.column("id") .auto_increment() .primary_key(); t.column("stack_id") .not_null() .references("stacks", "id") .indexed(); t.column("project_id") .not_null() .references("projects", "id") .indexed(); t.column("relationship") .not_null() .default_value("reference"); t.column("created_at"); t.unique("uq_stack_projects_pair", {"stack_id", "project_id"}); }); b.create_table("goals", (t) => { t.column("id") .auto_increment() .primary_key(); t.column("project_id") .not_null() .references("projects", "id") .indexed(); t.column("title") .not_null(); t.column("brief") .not_null(); t.column("branch_name") .not_null(); t.column("base_branch"); t.column("status") .default_value("draft") .indexed(); t.column("priority") .default_value(3); t.column("plan"); t.column("completion_report"); t.column("creator") .default_value("human"); t.column("last_terminal_watermark"); t.column("created_at"); t.column("started_at"); t.column("completed_at"); t.column("paused_at"); t.column("force_wake") .default_value(false); }); b.create_table("goal_links", (t) => { t.column("id") .auto_increment() .primary_key(); t.column("goal_id") .not_null() .references("goals", "id") .indexed(); t.column("project_id") .not_null() .references("projects", "id") .indexed(); t.column("relationship") .not_null(); }); b.create_table("tasks", (t) => { t.column("id") .auto_increment() .primary_key(); t.column("project_id") .not_null() .references("projects", "id") .indexed(); t.column("goal_id") .references("goals", "id") .indexed(); t.column("kind") .default_value("task"); t.column("mode") .default_value("implement"); t.column("creator") .default_value("human"); t.column("enqueue_after_task_id") .references("tasks", "id"); t.column("title") .not_null(); t.column("description"); t.column("status") .default_value("draft") .indexed(); t.column("priority") .default_value(5); t.column("summary"); t.column("task_branch"); t.column("branch_from"); t.column("created_at"); t.column("started_at"); t.column("completed_at"); t.column("suspended_at"); t.column("force_wake") .default_value(false); }); b.create_table("task_comments", (t) => { t.column("id") .auto_increment() .primary_key(); t.column("task_id") .not_null() .references("tasks", "id") .indexed(); t.column("author") .not_null(); t.column("body") .not_null(); t.column("quote"); t.column("created_at"); }); b.create_table("task_blocks", (t) => { t.column("id") .auto_increment() .primary_key(); t.column("task_id") .not_null() .references("tasks", "id") .indexed(); t.column("question") .not_null(); t.column("answer"); t.column("type") .default_value("question"); t.column("options"); t.column("body"); t.column("answered_at"); t.column("answered_by"); t.column("created_at"); }); b.create_table("saddles", (t) => { t.column("id") .auto_increment() .primary_key(); t.column("name") .not_null(); t.column("host") .not_null(); t.column("status") .default_value("idle"); t.column("git_name"); t.column("git_email"); t.column("ssh_public_key"); t.column("token"); }); b.create_table("sessions", (t) => { t.column("id") .auto_increment() .primary_key(); t.column("task_id") .not_null() .references("tasks", "id") .indexed(); t.column("saddle_id") .not_null() .references("saddles", "id"); t.column("started_at"); t.column("ended_at"); t.column("outcome"); t.column("resume_notice"); }); // Plan 17 §7.3: the unified append-only event ledger. // Daemon events carry their session and its monotonic seq // (idempotent upsert on UNIQUE(session_id, seq) — the // entire dedup machinery in one constraint); web-originated // events carry neither (NULL session_id rows never collide // under SQLite's NULLs-distinct unique indexes). Everything // downstream — timelines, goal narratives, resume-notice // coalescing, the event log view — derives from this table. b.create_table("events", (t) => { t.column("id") .auto_increment() .primary_key(); t.column("session_id") .references("sessions", "id"); t.column("seq"); t.column("kind") .not_null(); t.column("actor") .not_null(); t.column("scope_type") .not_null(); t.column("scope_id") .not_null(); t.column("payload") .not_null(); t.column("created_at") .not_null(); t.unique("uq_events_session_seq", {"session_id", "seq"}); }); b.create_table("rules", (t) => { t.column("id") .auto_increment() .primary_key(); t.column("name") .not_null() .unique(); t.column("body") .not_null(); t.column("created_at"); t.column("updated_at"); }); b.create_table("rule_projects", (t) => { t.column("id") .auto_increment() .primary_key(); t.column("rule_id") .not_null() .references("rules", "id") .indexed(); t.column("project_id") .not_null() .references("projects", "id") .indexed(); t.column("created_at"); t.unique("uq_rule_projects_pair", {"rule_id", "project_id"}); }); b.create_table("task_rules", (t) => { t.column("id") .auto_increment() .primary_key(); t.column("rule_id") .not_null() .references("rules", "id") .indexed(); t.column("task_id") .not_null() .references("tasks", "id") .indexed(); t.column("created_at"); t.unique("uq_task_rules_pair", {"rule_id", "task_id"}); }); b.create_table("project_notes", (t) => { t.column("id") .auto_increment() .primary_key(); t.column("project_id") .not_null() .references("projects", "id") .indexed(); t.column("source_task_id") .references("tasks", "id"); t.column("body") .not_null(); t.column("created_at"); }); b.create_table("capabilities", (t) => { t.column("id") .auto_increment() .primary_key(); t.column("name") .not_null() .unique(); t.column("created_at"); }); b.create_table("saddle_capabilities", (t) => { t.column("id") .auto_increment() .primary_key(); t.column("capability_id") .not_null() .references("capabilities", "id") .indexed(); t.column("saddle_id") .not_null() .references("saddles", "id") .indexed(); t.unique("uq_saddle_capabilities_pair", {"capability_id", "saddle_id"}); }); b.create_table("task_capabilities", (t) => { t.column("id") .auto_increment() .primary_key(); t.column("capability_id") .not_null() .references("capabilities", "id") .indexed(); t.column("task_id") .not_null() .references("tasks", "id") .indexed(); t.unique("uq_task_capabilities_pair", {"capability_id", "task_id"}); }); b.create_table("push_subscriptions", (t) => { t.column("id") .auto_increment() .primary_key(); t.column("user_id") .not_null() .indexed(); t.column("endpoint") .not_null() .unique(); t.column("p256dh") .not_null(); t.column("auth") .not_null(); t.column("created_at"); }); b.create_table("task_notify_prefs", (t) => { t.column("id") .auto_increment() .primary_key(); t.column("task_id") .not_null() .references("tasks", "id") .indexed(); t.column("user_id") .not_null() .indexed(); t.unique("uq_task_notify_prefs_pair", {"task_id", "user_id"}); }); } public override void down(MigrationBuilder b) { b.drop_table("task_notify_prefs"); b.drop_table("push_subscriptions"); b.drop_table("task_capabilities"); b.drop_table("saddle_capabilities"); b.drop_table("capabilities"); b.drop_table("project_notes"); b.drop_table("task_rules"); b.drop_table("rule_projects"); b.drop_table("rules"); b.drop_table("events"); b.drop_table("sessions"); b.drop_table("saddles"); b.drop_table("task_blocks"); b.drop_table("task_comments"); b.drop_table("tasks"); b.drop_table("goal_links"); b.drop_table("goals"); b.drop_table("project_links"); b.drop_table("stack_projects"); b.drop_table("stacks"); b.drop_table("projects"); } } }