import { and, eq } from "drizzle-orm"; import type { Db } from "@paperclipai/db"; import { completionContracts, nativeRunResults, workAssessments } from "@paperclipai/db"; import { classifyNativeEvidence, type NativeEvidenceAssessment } from "./evidence-classifier.js"; import { nativeSha256 } from "./canonical.js"; import type { NativeMonitorDelivery, NativePreservedMonitorWait } from "./native-monitor-delivery.js"; export async function recordNativeWorkAssessment(input: { db: Db; companyId: string; issueId: string; runId: string; turnId: string | null; contractId: string; contractCanonicalSha256: string; resultId: string; resultCanonicalSha256: string; priorIssueStatus: string; priorStatusVersion: number; priorDecisionId: string | null; policyVersion: string; assessment: NativeEvidenceAssessment; /** Workspace repair changes the status decision even when work evidence is unchanged. */ workspaceFinalizeStatus?: "succeeded" | "failed"; /** Timer delivery can settle an old wait without changing the work evidence. */ monitorDelivery?: NativeMonitorDelivery | null; /** A persisted schedule can regain settlement authority after a policy repair. */ monitorWaitAt?: string | null; preservedMonitorWait?: NativePreservedMonitorWait | null; supersedesAssessmentId?: string | null; }) { const assessmentJson = { ...input.assessment, ...(input.monitorDelivery ? { monitorDelivery: input.monitorDelivery } : {}), ...(input.monitorWaitAt ? { monitorWaitAt: input.monitorWaitAt } : {}), ...(input.preservedMonitorWait ? { preservedMonitorWait: input.preservedMonitorWait } : {}), } as unknown as Record; const inputDigest = nativeSha256({ contractId: input.contractId, contractCanonicalSha256: input.contractCanonicalSha256, resultId: input.resultId, resultCanonicalSha256: input.resultCanonicalSha256, priorIssueStatus: input.priorIssueStatus, priorStatusVersion: input.priorStatusVersion, priorDecisionId: input.priorDecisionId, triggerKind: "native_result", triggerRef: input.resultId, triggerActorCompanyId: input.companyId, triggerCapability: "server_native_finalizer", policyVersion: input.policyVersion, assessment: assessmentJson, ...(input.workspaceFinalizeStatus ? { workspaceFinalizeStatus: input.workspaceFinalizeStatus } : {}), }); const existing = await input.db.select().from(workAssessments).where(and( eq(workAssessments.issueId, input.issueId), eq(workAssessments.inputDigest, inputDigest), )).limit(1).then((rows) => rows[0] ?? null); if (existing) return existing; const [row] = await input.db.insert(workAssessments).values({ companyId: input.companyId, issueId: input.issueId, runId: input.runId, turnId: input.turnId, contractId: input.contractId, resultId: input.resultId, triggerKind: "native_result", triggerRef: input.resultId, triggerCapability: "server_native_finalizer", triggerActorCompanyId: input.companyId, priorIssueStatus: input.priorIssueStatus, priorStatusVersion: input.priorStatusVersion, priorDecisionId: input.priorDecisionId, policyVersion: input.policyVersion, assessmentJson, inputDigest, supersedesAssessmentId: input.supersedesAssessmentId ?? null, }).returning(); if (!row) throw new Error("native_assessment_not_persisted"); return row; } export async function recordNativeAttentionAssessment(input: { db: Db; companyId: string; issueId: string; runId: string; turnId: string | null; contractId: string; resultId: string; requestId: string; request: Record; routingFacts: Record; priorIssueStatus: string; priorStatusVersion: number; priorDecisionId: string | null; supersedesAssessmentId: string | null; }) { const assessmentJson = { kind: "native_attention", requestId: input.requestId, request: input.request, routingFacts: input.routingFacts, }; const inputDigest = nativeSha256({ companyId: input.companyId, issueId: input.issueId, runId: input.runId, resultId: input.resultId, requestId: input.requestId, request: input.request, routingFacts: input.routingFacts, priorIssueStatus: input.priorIssueStatus, priorStatusVersion: input.priorStatusVersion, priorDecisionId: input.priorDecisionId, policyVersion: "phase6-v2", }); const existing = await input.db.select().from(workAssessments).where(and( eq(workAssessments.companyId, input.companyId), eq(workAssessments.issueId, input.issueId), eq(workAssessments.runId, input.runId), eq(workAssessments.triggerKind, "native_attention"), eq(workAssessments.triggerRef, input.requestId), )).limit(1).then((rows) => rows[0] ?? null); if (existing) return existing; const [row] = await input.db.insert(workAssessments).values({ companyId: input.companyId, issueId: input.issueId, runId: input.runId, turnId: input.turnId, contractId: input.contractId, resultId: input.resultId, triggerKind: "native_attention", triggerRef: input.requestId, triggerCapability: "server_native_attention_router", triggerActorCompanyId: input.companyId, priorIssueStatus: input.priorIssueStatus, priorStatusVersion: input.priorStatusVersion, priorDecisionId: input.priorDecisionId, policyVersion: "phase6-v2", assessmentJson, inputDigest, supersedesAssessmentId: input.supersedesAssessmentId, }).returning(); if (!row) throw new Error("native_attention_assessment_not_persisted"); return row; } export async function lockCurrentNativeWorkAssessmentEvidence(db: Db, input: { companyId: string; issueId: string; runId: string; resultId: string; assessmentId: string; }): Promise { const [row] = await db.select({ assessment: workAssessments.assessmentJson, contract: completionContracts.contractJson, envelope: nativeRunResults.resultJson, }).from(workAssessments) .innerJoin(completionContracts, and(eq(completionContracts.id, workAssessments.contractId), eq(completionContracts.companyId, input.companyId), eq(completionContracts.issueId, input.issueId))) .innerJoin(nativeRunResults, and(eq(nativeRunResults.id, workAssessments.resultId), eq(nativeRunResults.companyId, input.companyId), eq(nativeRunResults.issueId, input.issueId), eq(nativeRunResults.runId, input.runId))) .where(and(eq(workAssessments.id, input.assessmentId), eq(workAssessments.companyId, input.companyId), eq(workAssessments.issueId, input.issueId), eq(workAssessments.runId, input.runId), eq(workAssessments.resultId, input.resultId))) .limit(1).for("share", { noWait: true }); if (!row) return false; const current = await classifyNativeEvidence({ db, companyId: input.companyId, issueId: input.issueId, runId: input.runId, contract: row.contract, result: row.envelope.result as Record, lockSources: true }); return nativeSha256(current) === nativeSha256(row.assessment); }