/* Maintains packages/os/site-kernel-handoff/src/werkstatt/operation.ts as an authored site-kernel-handoff authored module so agents can evolve it without rediscovering local boundaries. Do not store raw command arguments — hashes only. RFC-0362: initial operation record helpers (start, complete, fail, read). */ import path from "node:path"; import { createHash } from "node:crypto"; import { werkstattOperationRecordSchema, type WerkstattOperationRecord, } from "@warpgogol/werkstatt-engine/schemas"; import { createDefaultIO, type WorkspaceIO } from "@warpgogol/werkstatt-shared/kernel/workspace-io"; const OPERATIONS_DIR = path.join(".werkstatt", "operations"); function resolveOperationsDir(workspaceRoot: string): string { return path.join(workspaceRoot, OPERATIONS_DIR); } function resolveOperationPath(workspaceRoot: string, operationId: string): string { const safeId = operationId.replace(/[^a-zA-Z0-9_-]/g, "_"); return path.join(resolveOperationsDir(workspaceRoot), `${safeId}.json`); } function sanitizeInput(input: Record): Record { const sanitized: Record = {}; for (const [key, value] of Object.entries(input)) { if (/token|password|secret|credential/i.test(key)) { sanitized[key] = "[redacted]"; } else if (typeof value === "string" && /:\/\/[^@]+@/.test(value)) { sanitized[key] = "[redacted]"; } else { sanitized[key] = value; } } return sanitized; } export function computeInputHash(input: Record): string { const sanitized = sanitizeInput(input); const json = JSON.stringify(sanitized, Object.keys(sanitized).sort()); return `sha256:${createHash("sha256").update(json).digest("hex")}`; } export function generateOperationId(): string { const ts = Date.now().toString(36); const rand = Math.random().toString(36).slice(2, 10); return `op-${ts}-${rand}`; } export async function startOperation( workspaceRoot: string, operationId: string, command: string, scopes: string[], inputHash: string, io: WorkspaceIO = createDefaultIO().io, ): Promise { const opsDir = resolveOperationsDir(workspaceRoot); if (!(await io.exists(opsDir))) { await io.mkdir(opsDir); } const record: WerkstattOperationRecord = { schemaVersion: "1.0.0", operationId, command, scopes, state: "started", startedAt: new Date().toISOString(), completedAt: null, inputHash, resultHash: null, artifacts: [], error: null, }; werkstattOperationRecordSchema.parse(record); await io.writeFile( resolveOperationPath(workspaceRoot, operationId), JSON.stringify(record, null, 2) + "\n", ); return record; } export async function completeOperation( workspaceRoot: string, operationId: string, resultHash: string | null, artifacts: string[], io: WorkspaceIO = createDefaultIO().io, ): Promise { const opPath = resolveOperationPath(workspaceRoot, operationId); const raw = await io.readFile(opPath); const record = werkstattOperationRecordSchema.parse(JSON.parse(raw)); record.state = "completed"; record.completedAt = new Date().toISOString(); record.resultHash = resultHash; record.artifacts = artifacts; werkstattOperationRecordSchema.parse(record); await io.writeFile(opPath, JSON.stringify(record, null, 2) + "\n"); return record; } export async function failOperation( workspaceRoot: string, operationId: string, error: string, io: WorkspaceIO = createDefaultIO().io, ): Promise { const opPath = resolveOperationPath(workspaceRoot, operationId); const raw = await io.readFile(opPath); const record = werkstattOperationRecordSchema.parse(JSON.parse(raw)); record.state = "failed"; record.completedAt = new Date().toISOString(); record.error = error; werkstattOperationRecordSchema.parse(record); await io.writeFile(opPath, JSON.stringify(record, null, 2) + "\n"); return record; } export async function readOperation( workspaceRoot: string, operationId: string, io: WorkspaceIO = createDefaultIO().io, ): Promise { const opPath = resolveOperationPath(workspaceRoot, operationId); if (!(await io.exists(opPath))) return null; const raw = await io.readFile(opPath); try { return werkstattOperationRecordSchema.parse(JSON.parse(raw)); } catch { return null; } }