feat(family): the adoption ledger - assessment, the conflict order, owed->task, recheck, the adoption matrix (milestone 463 step 4, #4990)
CI & Build / Plugin hooks (push) Successful in 15s
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 1m0s
CI & Build / integration (push) Successful in 1m8s
CI & Build / Python tests (push) Failing after 1m28s
CI & Build / Build & push image (push) Skipped
CI & Build / Plugin hooks (push) Successful in 15s
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 1m0s
CI & Build / integration (push) Successful in 1m8s
CI & Build / Python tests (push) Failing after 1m28s
CI & Build / Build & push image (push) Skipped
- services/family_adoption.py: assess one project against one canon idea by the four outcomes in order (exempt, variant, adopted, owed). Every outcome needs a reason and adopted needs evidence. The engine records the precedents itself: this idea's answers elsewhere, and this project's answers to the nearest ideas. The same answer given twice records nothing. - owed files a task in the OWING project, tagged to the System matching the idea's canonical area, naming the gap and the reference for that project's language. The task follows the answer: adopted closes it, exempt or variant cancels it, owed again reopens it. Each move is logged on the task. - the conflict order is enforced: every ground above the deciding one must say why it did not decide. The losing side is folded into the idea's note as a trap, an alternative or a condition branch, the version moves, and both rows are answered against the revision. - recheck is derived (row version != idea version). family.revise moves the version when substance changes. undo covers a project's latest answer too. - set_family_references names the reference implementations. - MCP: get/list/assess adoption, resolve_family_conflict, revise_family_idea, set_family_references. Web: GET /api/family/matrix. - UI: an adoption matrix on /family (platform filter, cell detail with reason, recheck and owed-task link) and the same matrix narrowed to one project on its Family tab. The decision log now reads project-level decisions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -40,15 +40,29 @@ export interface IdeaSnapshot {
|
||||
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 | null;
|
||||
after: IdeaSnapshot | null;
|
||||
before: IdeaSnapshot | AdoptionSnapshot | null;
|
||||
after: IdeaSnapshot | AdoptionSnapshot | null;
|
||||
evidence: Record<string, unknown>;
|
||||
precedent_ids: number[];
|
||||
decided_via: "agent" | "operator" | "system";
|
||||
@@ -57,7 +71,17 @@ export interface FamilyDecision {
|
||||
undoable?: boolean;
|
||||
}
|
||||
|
||||
export async function listFamilyIdeas(status = ""): Promise<{ ideas: FamilyIdea[]; criteria: Criterion[] }> {
|
||||
/** 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}`);
|
||||
}
|
||||
@@ -76,3 +100,57 @@ export async function undoFamilyDecision(id: number, reason: string) {
|
||||
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;
|
||||
}
|
||||
|
||||
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<AdoptionMatrix> {
|
||||
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}` : ""}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user