over / src /lib /commitment /store.ts
luguog's picture
Deploy mailbox-automation Next.js app
9646e24 verified
Raw
History Blame Contribute Delete
6.47 kB
import { nanoid } from "nanoid";
import { CommitmentContract, CommitmentMetrics } from "@/types";
import {
loadCommitments,
saveCommitments,
loadCommitmentMetrics,
saveCommitmentMetrics,
} from "@/lib/config";
function nowIso(): string {
return new Date().toISOString();
}
export function listCommitments(): CommitmentContract[] {
return loadCommitments();
}
export function getCommitmentById(id: string): CommitmentContract | null {
const all = loadCommitments();
return all.find((c) => c.id === id) ?? null;
}
export function getCommitmentByEmailId(emailId: string): CommitmentContract | null {
const all = loadCommitments();
return all.find((c) => c.emailId === emailId) ?? null;
}
function normalizeContract(contract: CommitmentContract): CommitmentContract {
return {
...contract,
id: contract.id || nanoid(),
mandatoryOutputs: Array.isArray(contract.mandatoryOutputs)
? contract.mandatoryOutputs
: [],
inferredOutputs: Array.isArray(contract.inferredOutputs)
? contract.inferredOutputs
: [],
permittedTools: Array.isArray(contract.permittedTools)
? contract.permittedTools
: [],
dependencies: Array.isArray(contract.dependencies)
? contract.dependencies
: [],
assumptions: Array.isArray(contract.assumptions)
? contract.assumptions
: [],
auditEvents: Array.isArray(contract.auditEvents) ? contract.auditEvents : [],
detectedAt: contract.detectedAt || nowIso(),
};
}
function mergeContracts(existing: CommitmentContract, incoming: CommitmentContract): CommitmentContract {
const preserveStatus =
existing.status !== "detected" && incoming.status === "detected";
const merged: CommitmentContract = {
...existing,
...incoming,
id: existing.id,
status: preserveStatus ? existing.status : incoming.status,
deliverables: incoming.deliverables ?? existing.deliverables,
executedAt: incoming.executedAt ?? existing.executedAt,
completedAt: incoming.completedAt ?? existing.completedAt,
auditEvents: [
...(Array.isArray(existing.auditEvents) ? existing.auditEvents : []),
...(Array.isArray(incoming.auditEvents) ? incoming.auditEvents : []),
],
};
return merged;
}
export function upsertCommitment(contract: CommitmentContract): CommitmentContract {
const all = loadCommitments();
const idx = all.findIndex((c) => c.id === contract.id);
const normalized = normalizeContract(contract);
if (idx >= 0) {
all[idx] = mergeContracts(all[idx], normalized);
} else {
all.unshift(normalized);
}
saveCommitments(all);
return idx >= 0 ? all[idx] : normalized;
}
export function upsertCommitmentByEmailId(contract: CommitmentContract): CommitmentContract {
const existing = getCommitmentByEmailId(contract.emailId);
if (existing) {
return upsertCommitment({ ...contract, id: existing.id });
}
return upsertCommitment(contract);
}
export function addCommitmentAuditEvent(
id: string,
event: string,
detail: string,
): CommitmentContract {
const all = loadCommitments();
const idx = all.findIndex((c) => c.id === id);
if (idx < 0) {
throw new Error(`Commitment not found: ${id}`);
}
const updated: CommitmentContract = {
...all[idx],
auditEvents: [
...(Array.isArray(all[idx].auditEvents) ? all[idx].auditEvents : []),
{ timestamp: nowIso(), event, detail },
],
};
all[idx] = updated;
saveCommitments(all);
return updated;
}
export function updateCommitmentStatus(
id: string,
status: CommitmentContract["status"],
detail?: string,
): CommitmentContract {
const all = loadCommitments();
const idx = all.findIndex((c) => c.id === id);
if (idx < 0) {
throw new Error(`Commitment not found: ${id}`);
}
const updated: CommitmentContract = {
...all[idx],
status,
};
if (status === "executing") {
updated.executedAt = updated.executedAt || nowIso();
}
if (status === "completed") {
updated.completedAt = updated.completedAt || nowIso();
}
updated.auditEvents = [
...(Array.isArray(updated.auditEvents) ? updated.auditEvents : []),
{
timestamp: nowIso(),
event: "status",
detail: detail ?? `Status changed to ${status}`,
},
];
all[idx] = updated;
saveCommitments(all);
return updated;
}
export function loadMetrics(): CommitmentMetrics {
return loadCommitmentMetrics();
}
export function recordExecutionOutcome(input: {
capabilityOk: boolean;
inputsOk: boolean;
toolsOk: boolean;
qaOk: boolean;
durationMs: number;
}): CommitmentMetrics {
const metrics = loadCommitmentMetrics();
const bump = (key: keyof Pick<CommitmentMetrics,
"capability" | "inputsAvailable" | "toolCompletion" | "qualityApproval" | "acceptedWithoutRevision"
>, ok: boolean) => {
const existing = metrics[key];
metrics[key] = {
success: existing.success + (ok ? 1 : 0),
total: existing.total + 1,
};
};
bump("capability", input.capabilityOk);
bump("inputsAvailable", input.inputsOk);
bump("toolCompletion", input.toolsOk);
bump("qualityApproval", input.qaOk);
const duration = Math.max(0, Math.round(input.durationMs));
metrics.durationsMs = Array.isArray(metrics.durationsMs)
? [...metrics.durationsMs, duration].slice(-500)
: [duration];
metrics.lastUpdatedAt = nowIso();
saveCommitmentMetrics(metrics);
return metrics;
}
export function recordAcceptanceOutcome(input: {
acceptedWithoutRevision: boolean;
}): CommitmentMetrics {
const metrics = loadCommitmentMetrics();
metrics.acceptedWithoutRevision = {
success: metrics.acceptedWithoutRevision.success + (input.acceptedWithoutRevision ? 1 : 0),
total: metrics.acceptedWithoutRevision.total + 1,
};
metrics.lastUpdatedAt = nowIso();
saveCommitmentMetrics(metrics);
return metrics;
}
export function recordOutcome(input: {
capabilityOk: boolean;
inputsOk: boolean;
toolsOk: boolean;
qaOk: boolean;
acceptedWithoutRevision?: boolean;
durationMs: number;
}): CommitmentMetrics {
const afterExecution = recordExecutionOutcome({
capabilityOk: input.capabilityOk,
inputsOk: input.inputsOk,
toolsOk: input.toolsOk,
qaOk: input.qaOk,
durationMs: input.durationMs,
});
if (typeof input.acceptedWithoutRevision === "boolean") {
return recordAcceptanceOutcome({ acceptedWithoutRevision: input.acceptedWithoutRevision });
}
return afterExecution;
}