/** * Family canon: ideas every project on a platform shares, and * the log of every decision about them. * * The agent promotes by written criteria — no approval step. This door is for * reading what was decided and why, and for retiring an idea or undoing a * decision when a person disagrees. */ import { apiGet, apiPost } from "@/api/client"; export type IdeaStatus = "candidate" | "canon" | "retired"; export type DecisionAction = "propose" | "promote" | "revise" | "retire" | "assess" | "undo"; export interface Criterion { key: string; title: string; test: string; } export interface IdeaTopic { id: number; title: string; rulebook_id: number; /** Present on a single idea's read, not on the list. */ rules?: { id: number; title: string }[]; } export interface FamilyIdea { note_id: number; status: IdeaStatus; applies_when: string; canon_version: number; topic_id: number | null; /** The rule topic holding the idea's binding norms; null when none is * linked or the reader does not own its rulebook. */ topic: IdeaTopic | null; title: string; note_type: string; is_task: boolean; project_id: number | null; platforms: string[]; created_at: string | null; updated_at: string | null; } /** A state snapshot a decision records — slugs, never ids. */ export interface IdeaSnapshot { status: IdeaStatus; applies_when: string; canon_version: number; platforms: string[]; } export type AdoptionStatus = "unassessed" | "adopted" | "variant" | "exempt" | "owed"; /** One project's answer as a decision records it. */ export interface AdoptionSnapshot { status: AdoptionStatus; reason: string; canon_version: number | null; } /** * A decision about the idea itself (project_id null) snapshots the idea; a * project's assessment (project_id set) snapshots that project's answer. */ export interface FamilyDecision { id: number; idea_id: number; idea_title?: string; project_id: number | null; project_title?: string | null; action: DecisionAction; reason: string; before: IdeaSnapshot | AdoptionSnapshot | null; after: IdeaSnapshot | AdoptionSnapshot | null; evidence: Record; precedent_ids: number[]; decided_via: "agent" | "operator" | "system"; user_id: number | null; created_at: string | null; undoable?: boolean; } /** One ground of the conflict order, in order. */ export interface ConflictGround { key: string; title: string; test: string; fold_as: "alternative" | "trap" | "condition"; } export async function listFamilyIdeas( status = "", ): Promise<{ ideas: FamilyIdea[]; criteria: Criterion[]; conflict_order?: ConflictGround[] }> { const q = status ? `?status=${encodeURIComponent(status)}` : ""; return apiGet(`/api/family/ideas${q}`); } export async function listFamilyDecisions(limit = 50, offset = 0): Promise { const data = await apiGet<{ decisions: FamilyDecision[] }>( `/api/family/decisions?limit=${limit}&offset=${offset}`, ); return data.decisions; } export async function undoFamilyDecision(id: number, reason: string) { return apiPost<{ decision: FamilyDecision }>(`/api/family/decisions/${id}/undo`, { reason }); } export async function retireFamilyIdea(noteId: number, reason: string) { return apiPost<{ decision: FamilyDecision }>(`/api/family/ideas/${noteId}/retire`, { reason }); } export interface Outcome { key: AdoptionStatus; title: string; test: string; } /** One project's answer to one canon idea — a cell of the matrix. */ export interface AdoptionCell { project_id: number; project_title: string; idea_id: number; idea_title: string; idea_version: number; /** The project is on one of the idea's platforms. */ in_scope: boolean; /** A ledger row exists. False: the idea reaches the project, nobody asked yet. */ reached: boolean; status: AdoptionStatus; reason: string; canon_version: number | null; assessed_at: string | null; decided_via: "agent" | "operator" | "system" | null; /** Answered against a canon version that is not the current one. */ needs_recheck: boolean; owed_task: { id: number; title: string; status: string } | null; /** Live shapes in the project classified against one of the idea's references. */ shapes: { path: string; symbol: string; status: "canonical" | "instance" | "variant" }[]; } export interface MatrixIdea { note_id: number; title: string; note_type: string; is_task: boolean; canon_version: number; applies_when: string; platforms: string[]; } export interface AdoptionMatrix { ideas: MatrixIdea[]; projects: { id: number; title: string; platforms: string[] }[]; cells: AdoptionCell[]; outcomes: Outcome[]; } export async function fetchAdoptionMatrix( opts: { platform?: string; projectId?: number } = {}, ): Promise { const q = new URLSearchParams(); if (opts.platform) q.set("platform", opts.platform); if (opts.projectId) q.set("project_id", String(opts.projectId)); const qs = q.toString(); return apiGet(`/api/family/matrix${qs ? `?${qs}` : ""}`); }