From bd1f21fe387ec57da76c300cbb1ebc0db48553a7 Mon Sep 17 00:00:00 2001 From: soryu 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) --- makima/frontend/src/lib/api.ts | 54 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) (limited to 'makima/frontend/src/lib') diff --git a/makima/frontend/src/lib/api.ts b/makima/frontend/src/lib/api.ts index 17cf1d0..f777ba0 100644 --- a/makima/frontend/src/lib/api.ts +++ b/makima/frontend/src/lib/api.ts @@ -3597,7 +3597,7 @@ export async function pickUpOrders(directiveId: string): Promise endpoint and surface +// 400 with a plain message string when the transition is invalid. + +async function postContractAction( + contractId: string, + action: "start" | "pause" | "complete" | "unlock", + body?: object, +): Promise { + const res = await authFetch(`${API_BASE}/api/v1/contracts/${contractId}/${action}`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body ?? {}), + }); + if (!res.ok) { + let detail = res.statusText; + try { + const err = await res.json(); + if (err?.message) detail = err.message; + } catch { + /* fall through to statusText */ + } + throw new Error(`Failed to ${action} contract: ${detail}`); + } + return res.json(); +} + +export async function startDirectiveContract(contractId: string): Promise { + return postContractAction(contractId, "start"); +} + +export async function pauseDirectiveContract(contractId: string): Promise { + return postContractAction(contractId, "pause"); +} + +export async function completeDirectiveContract( + contractId: string, + opts: { prUrl?: string; prBranch?: string } = {}, +): Promise { + return postContractAction(contractId, "complete", opts); +} + +export async function unlockDirectiveContract(contractId: string): Promise { + return postContractAction(contractId, "unlock"); +} + /** Steps and tasks attached to a single contract. Drives the per-contract * `tasks/` subfolder in the sidebar — when the contract ships, its * tasks visually move with it. */ -- cgit v1.2.3