import type { NodeChange } from "../domain/types.ts"; import type { ReconstructedCheckpoint } from "../application/branch-reconstruction.ts"; function asRecord(value: unknown): Record { return value && typeof value === "object" ? (value as Record) : {}; } function eventTarget(kind: string, payload: Record): string { if (kind === "root_intent.set") return "root_intent"; for (const key of [ "transitionId", "refinementId", "placeId", "arcId", "targetId", "oldTransitionId", "oldRefinementId", "tokenId", "evidenceId", ]) { if (typeof payload[key] === "string") return payload[key] as string; } return "unknown"; } function eventSummary(kind: string, payload: Record): string { switch (kind) { case "root_intent.set": return `Set North Star to ${String(payload.rootIntent ?? "unknown")}`; case "place.created": return `Created Place ${String(payload.label ?? payload.placeId ?? "unknown")}`; case "transition.created": return `Created ${String(payload.commitment ?? "planned")} Transition ${String(payload.intent ?? payload.transitionId)}`; case "arc.created": return `Connected ${String(payload.fromId)} → ${String(payload.toId)} (${String(payload.arcKind)})`; case "place.state_set": return `Set Place ${String(payload.placeId)} to ${String(payload.placeState)}`; case "transition.contract_set": return `Updated contract for ${String(payload.transitionId)}`; case "transition.planning_set": return `Updated planning for ${String(payload.transitionId)}`; case "transition.refined": return `Created refinement ${String(payload.refinementId)} for ${String(payload.parentTransitionId)}`; case "refinement.superseded": return `Superseded refinement ${String(payload.oldRefinementId)} with ${String(payload.newRefinementId)}`; case "refinement.entered": return `Entered refinement ${String(payload.refinementId)}`; case "refinement.exited": return `Exited refinement ${String(payload.refinementId)} with ${String(payload.outcome)}`; case "transition.activated": return `Activated ${String(payload.transitionId)}`; case "transition.completed": return `Completed ${String(payload.transitionId)}`; case "transition.failed": return `Failed ${String(payload.transitionId)}`; case "transition.retired": return `Retired ${String(payload.transitionId)}`; case "transition.superseded": return `Superseded ${String(payload.oldTransitionId)} with ${String(payload.newTransitionId)}`; case "token.moved": return `Moved token ${String(payload.fromPlaceId)} → ${String(payload.toPlaceId)}`; case "evidence.attached": return `Attached Evidence ${String(payload.evidenceId)}`; case "gate.marked": return `Marked Gate ${String(payload.placeId)} ${String(payload.gateState)}`; default: return kind; } } function eventDiff(kind: string, payload: Record): { before?: unknown; after?: unknown } { if ("before" in payload || "after" in payload) { return { ...(payload.before !== undefined ? { before: structuredClone(payload.before) } : {}), ...(payload.after !== undefined ? { after: structuredClone(payload.after) } : {}), }; } switch (kind) { case "transition.activated": return { before: { status: "planned" }, after: { status: "active" } }; case "transition.completed": return { before: { status: "active" }, after: { status: "completed" } }; case "transition.failed": return { before: { status: "active" }, after: { status: "failed" } }; case "transition.retired": return { before: { status: "planned" }, after: { status: "retired" } }; case "transition.superseded": case "refinement.superseded": return { after: { status: "superseded" } }; case "refinement.entered": return { before: { status: "draft" }, after: { status: "active" } }; case "refinement.exited": return { before: { status: "active" }, after: { status: "completed", outcome: payload.outcome } }; case "token.moved": return { before: { placeId: payload.fromPlaceId }, after: { placeId: payload.toPlaceId } }; default: return Object.keys(payload).length > 0 ? { after: structuredClone(payload) } : {}; } } function fallbackOpTarget(op: Record): string { for (const key of [ "transitionId", "placeId", "arcId", "refinementId", "oldTransitionId", "oldRefinementId", "targetId", ]) { if (typeof op[key] === "string") return op[key] as string; } return op.op === "set_root_intent" ? "root_intent" : "unknown"; } export function deriveNodeChanges(history: ReconstructedCheckpoint[]): NodeChange[] { const changes: NodeChange[] = []; for (const checkpoint of history) { const batch = checkpoint.batch; if (!batch) continue; if (batch.events.length === 0) { for (const [opIndex, rawOp] of batch.patch.ops.entries()) { const op = asRecord(rawOp); changes.push({ revision: checkpoint.state.revision, committedAt: batch.committedAt, checkpointEntryId: checkpoint.entryId, ...(checkpoint.parentEntryId ? { parentEntryId: checkpoint.parentEntryId } : {}), patchId: batch.patch.patchId, actor: "agent", targetId: fallbackOpTarget(op), operation: String(op.op ?? "unknown"), opIndex, eventIndex: 0, summary: String(op.op ?? "unknown"), ...(typeof op.reason === "string" ? { reason: op.reason } : {}), }); } continue; } for (const domainEvent of batch.events) { const payload = asRecord(domainEvent.payload); const rawOp = batch.patch.ops[domainEvent.opIndex]; const op = asRecord(rawOp); changes.push({ revision: checkpoint.state.revision, committedAt: batch.committedAt, checkpointEntryId: checkpoint.entryId, ...(checkpoint.parentEntryId ? { parentEntryId: checkpoint.parentEntryId } : {}), patchId: batch.patch.patchId, actor: "agent", targetId: eventTarget(domainEvent.kind, payload), operation: domainEvent.kind, opIndex: domainEvent.opIndex, eventIndex: domainEvent.eventIndex, summary: eventSummary(domainEvent.kind, payload), ...eventDiff(domainEvent.kind, payload), ...(typeof op.reason === "string" ? { reason: op.reason } : {}), }); } } return changes; }