summaryrefslogtreecommitdiff
path: root/makima/migrations
diff options
context:
space:
mode:
Diffstat (limited to 'makima/migrations')
-rw-r--r--makima/migrations/20260502000000_create_directive_documents.sql50
-rw-r--r--makima/migrations/20260503000000_add_document_id_to_tasks.sql59
2 files changed, 109 insertions, 0 deletions
diff --git a/makima/migrations/20260502000000_create_directive_documents.sql b/makima/migrations/20260502000000_create_directive_documents.sql
new file mode 100644
index 0000000..99871b5
--- /dev/null
+++ b/makima/migrations/20260502000000_create_directive_documents.sql
@@ -0,0 +1,50 @@
+-- Multi-document model under directives.
+--
+-- A directive is a folder that owns N documents (the user calls these
+-- "directive contracts"). Each document has its own markdown body, status,
+-- and may end up attached to a PR. Multiple documents can be active at the
+-- same time. When a doc ships (PR raised) it transitions to 'shipped';
+-- editing a shipped doc later flips it back to 'active' (handled in repo
+-- update logic — wired into handlers in a later step).
+--
+-- Today, directives.goal IS the single contract document, so we backfill
+-- one document per existing directive populated from directives.goal so the
+-- existing UI keeps working.
+
+CREATE TABLE directive_documents (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ directive_id UUID NOT NULL REFERENCES directives(id) ON DELETE CASCADE,
+ title VARCHAR(500) NOT NULL DEFAULT '',
+ body TEXT NOT NULL DEFAULT '',
+ status VARCHAR(32) NOT NULL DEFAULT 'draft'
+ CHECK (status IN ('draft', 'active', 'shipped', 'archived')),
+ pr_url TEXT,
+ pr_branch TEXT,
+ shipped_at TIMESTAMPTZ,
+ archived_at TIMESTAMPTZ,
+ version INTEGER NOT NULL DEFAULT 1,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+CREATE INDEX idx_directive_documents_directive_id
+ ON directive_documents(directive_id);
+CREATE INDEX idx_directive_documents_status
+ ON directive_documents(directive_id, status);
+
+-- Backfill: one document per existing directive, copying the current goal
+-- so the existing UI keeps showing what it shows today. Directives that
+-- already have a PR url are seeded as 'shipped' (with shipped_at set),
+-- everything else as 'active'. COALESCE protects against any unexpected
+-- NULL goal rows even though the column is currently NOT NULL.
+INSERT INTO directive_documents (
+ directive_id, title, body, status, pr_url, shipped_at
+)
+SELECT
+ d.id,
+ d.title,
+ COALESCE(d.goal, ''),
+ CASE WHEN d.pr_url IS NOT NULL THEN 'shipped' ELSE 'active' END,
+ d.pr_url,
+ CASE WHEN d.pr_url IS NOT NULL THEN NOW() ELSE NULL END
+FROM directives d;
diff --git a/makima/migrations/20260503000000_add_document_id_to_tasks.sql b/makima/migrations/20260503000000_add_document_id_to_tasks.sql
new file mode 100644
index 0000000..71d122d
--- /dev/null
+++ b/makima/migrations/20260503000000_add_document_id_to_tasks.sql
@@ -0,0 +1,59 @@
+-- Attach tasks and directive_steps to a specific directive_document.
+--
+-- Until now, directive-driven tasks and steps were only linked to the
+-- directive itself (tasks.directive_id, directive_steps.directive_id). The
+-- document model that landed in 20260502000000_create_directive_documents.sql
+-- introduced N documents per directive, each with its own draft → active →
+-- shipped → archived lifecycle. To make the sidebar match the goal — each
+-- document carries its own tasks/ subfolder, and a shipped document's task
+-- history visually moves with it under shipped/ — we need a per-document
+-- foreign key on tasks and steps.
+--
+-- The column is nullable + ON DELETE SET NULL so:
+-- * Old, pre-document tasks can keep working with directive_document_id = NULL.
+-- * Deleting a document detaches its tasks (it does not cascade-delete them),
+-- because task output is valuable history we don't want to lose.
+--
+-- Backfill rule: every existing directive currently has exactly one document
+-- (created by the previous migration's INSERT). We attach all of that
+-- directive's existing tasks and steps to that one document. Directives with
+-- zero documents are a no-op — the subquery returns NULL, the column stays
+-- NULL, and nothing breaks.
+
+ALTER TABLE tasks
+ ADD COLUMN directive_document_id UUID
+ REFERENCES directive_documents(id) ON DELETE SET NULL;
+
+CREATE INDEX idx_tasks_directive_document_id
+ ON tasks(directive_document_id);
+
+ALTER TABLE directive_steps
+ ADD COLUMN directive_document_id UUID
+ REFERENCES directive_documents(id) ON DELETE SET NULL;
+
+CREATE INDEX idx_directive_steps_directive_document_id
+ ON directive_steps(directive_document_id);
+
+-- Backfill: each task whose directive_id is set inherits the directive's
+-- single existing document (the oldest one — the one created by the previous
+-- migration's backfill). Tasks not associated with any directive stay NULL.
+UPDATE tasks t
+ SET directive_document_id = (
+ SELECT id FROM directive_documents d
+ WHERE d.directive_id = t.directive_id
+ ORDER BY d.created_at ASC
+ LIMIT 1
+ )
+ WHERE t.directive_id IS NOT NULL;
+
+-- Same for directive_steps. directive_id is NOT NULL on this table, so we
+-- don't need an explicit guard — the LIMIT-1 subquery returns NULL for any
+-- directive that happens to have no documents (defensive against bad data),
+-- which is fine because the column is nullable.
+UPDATE directive_steps s
+ SET directive_document_id = (
+ SELECT id FROM directive_documents d
+ WHERE d.directive_id = s.directive_id
+ ORDER BY d.created_at ASC
+ LIMIT 1
+ );