From bd1f21fe387ec57da76c300cbb1ebc0db48553a7 Mon Sep 17 00:00:00 2001
From: soryu <soryu@soryu.co>
Date: Fri, 8 May 2026 12:12:21 +0100
Subject: feat(contracts): lifecycle — Lock/Start/Pause/Complete/Unlock + queue
 scheduler
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Adds the contract lifecycle layer on top of the unified-contracts
backbone (#128). State machine:

    draft → queued → active → shipped → archived

At most one contract per directive sits in `active` at any time —
the queue is serialised because each directive owns a single shared
worktree. Repository helpers handle the transition checks AND
auto-promote the next-up `queued` contract whenever the active slot
frees (pause / complete / unlock-from-active / archive-from-active).

Endpoints (all under /api/v1/contracts/{id}):
  POST /start    draft → queued | active (depending on slot)
  POST /pause    active → queued; promotes next queued
  POST /complete active → shipped; optional pr_url + pr_branch
  POST /unlock   queued | active → draft; promotes if was active

Frontend wiring:
  * `DirectiveContractStatus` now includes `queued`.
  * Migration adds `queued` to the CHECK constraint on
    directive_documents.status.
  * `ContractHeader` component renders breadcrumb + status pill +
    status-driven action buttons + a merge-mode (shared / own_pr)
    radio. Merge mode is editable only while draft / queued so a
    running flow's branch target can't change mid-stream.
  * RepositoryError gains a `Validation(String)` arm; the three
    existing exhaustive matches (files, mesh, versions) get a
    400 BAD_REQUEST response for it.

Drag-to-reorder UI deferred to a small follow-up — the backend
endpoint already exists from the backbone PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
 .../20260509000000_contract_lifecycle_states.sql      | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)
 create mode 100644 makima/migrations/20260509000000_contract_lifecycle_states.sql

(limited to 'makima/migrations/20260509000000_contract_lifecycle_states.sql')

diff --git a/makima/migrations/20260509000000_contract_lifecycle_states.sql b/makima/migrations/20260509000000_contract_lifecycle_states.sql
new file mode 100644
index 0000000..fc9c7bf
--- /dev/null
+++ b/makima/migrations/20260509000000_contract_lifecycle_states.sql
@@ -0,0 +1,19 @@
+-- Add 'queued' to the contract status enum.
+--
+-- The unified directive workflow runs contracts sequentially in the
+-- directive's shared worktree — only one contract is `active` at a time.
+-- When a user clicks "Lock & Start" on a draft, it goes to `active` if
+-- the slot is free, otherwise it goes to `queued` and waits for the
+-- current active contract to ship/archive. The `complete_contract`
+-- handler auto-promotes the lowest-position `queued` row to `active`.
+--
+-- The constraint replacement is straightforward — drop + re-add. No
+-- existing rows can be in 'queued' yet, so the new CHECK is satisfied
+-- by every row.
+
+ALTER TABLE directive_documents
+    DROP CONSTRAINT IF EXISTS directive_documents_status_check;
+
+ALTER TABLE directive_documents
+    ADD CONSTRAINT directive_documents_status_check
+    CHECK (status IN ('draft', 'queued', 'active', 'shipped', 'archived'));
-- 
cgit v1.2.3

