import type { ActiveRun, KdWriteTransaction } from "./types.ts"; import { evaluateToolGateway, isWriteLikeToolCall } from "./tool-gateway.ts"; import { evaluateToolPostcondition } from "./tool-postcondition.ts"; import { latestSourceAnchorForPath, validateSourceAnchorSnapshot } from "./source-anchors.ts"; import { isSourceLikePath } from "../platform/path.ts"; /** * @deprecated Legacy write transaction module. * When KCODE_GATE_RUNNER_V2=1, write operations go through WritePipeline * (src/harness/tools/write-pipeline.ts) via kingdee-harness-tool-events.ts. * This module remains for backward compatibility with non-V2 paths. */ export interface WriteTransactionInput { cwd: string; run?: ActiveRun; path: string; toolName?: string; } export function beginWriteTransaction(input: WriteTransactionInput, sequence: number, now = new Date().toISOString()): KdWriteTransaction { const toolName = input.toolName ?? "write"; if (!isWriteLikeToolCall(toolName)) { return { id: createTransactionId(sequence), path: input.path, status: "blocked", checks: [`write-like-tool:invalid:${toolName}`], reason: `写入事务只接受集中登记的写类工具:${toolName} 未登记。`, createdAt: now, updatedAt: now, }; } const gateway = evaluateToolGateway({ cwd: input.cwd, run: input.run, toolName, path: input.path }); if (!gateway.allowed) { return { id: createTransactionId(sequence), path: input.path, status: "blocked", checks: [`gateway:${gateway.kind}`], reason: gateway.reason, createdAt: now, updatedAt: now, }; } const anchor = input.run ? latestSourceAnchorForPath(input.cwd, input.run.sourceAnchors, input.path) : undefined; if (anchor) { const validation = validateSourceAnchorSnapshot(input.cwd, anchor); if (!validation.passed) { return { id: createTransactionId(sequence), path: input.path, status: "blocked", checks: ["gateway:passed", `source-anchor:${anchor.id}`, "source-anchor:stale"], reason: validation.reason, createdAt: now, updatedAt: now, }; } return { id: createTransactionId(sequence), path: input.path, status: "planned", checks: ["gateway:passed", `source-anchor:${anchor.id}`, "source-anchor:passed"], createdAt: now, updatedAt: now, }; } if (isSourceLikePath(input.path)) { return { id: createTransactionId(sequence), path: input.path, status: "blocked", checks: ["gateway:passed", "source-anchor:missing"], reason: `源码类文件写入前必须先使用 kd_anchor_read 读取最新锚点:${input.path}`, createdAt: now, updatedAt: now, }; } return { id: createTransactionId(sequence), path: input.path, status: "planned", checks: ["gateway:passed"], createdAt: now, updatedAt: now, }; } export function completeWriteTransaction(input: WriteTransactionInput & { transaction: KdWriteTransaction }, now = new Date().toISOString()): KdWriteTransaction { if (input.transaction.status === "blocked") return input.transaction; const postcondition = evaluateToolPostcondition({ cwd: input.cwd, run: input.run, toolName: input.toolName ?? "write", path: input.path, expected: "write-file", }); return { ...input.transaction, status: postcondition.status === "passed" ? "verified" : "blocked", checks: [...input.transaction.checks, ...postcondition.checks], reason: postcondition.status === "passed" ? undefined : postcondition.reason, updatedAt: now, }; } export function formatWriteTransactions(transactions: KdWriteTransaction[] | undefined, limit = 10): string { const items = Array.isArray(transactions) ? transactions.slice(-Math.max(1, limit)) : []; if (items.length === 0) return "无。"; return items.map((item) => `- ${item.id} [${item.status}] ${item.path}:${item.reason ?? item.checks.join(", ")}`).join("\n"); } export function sanitizeWriteTransaction(value: unknown): KdWriteTransaction | undefined { if (!value || typeof value !== "object" || Array.isArray(value)) return undefined; const record = value as Record; const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : undefined; const path = typeof record.path === "string" && record.path.trim() ? record.path.trim() : undefined; const status = record.status === "blocked" || record.status === "written" || record.status === "verified" ? record.status : record.status === "planned" ? "planned" : undefined; if (!id || !path || !status) return undefined; const now = new Date().toISOString(); return { id, path, status, checks: Array.isArray(record.checks) ? record.checks.filter((item): item is string => typeof item === "string" && Boolean(item.trim())).map((item) => item.trim()) : [], reason: typeof record.reason === "string" && record.reason.trim() ? record.reason.trim() : undefined, createdAt: typeof record.createdAt === "string" ? record.createdAt : now, updatedAt: typeof record.updatedAt === "string" ? record.updatedAt : now, }; } function createTransactionId(sequence: number): string { return `W-${String(sequence).padStart(3, "0")}`; }