{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../../src/core/safety/manager.ts"],"names":[],"mappings":"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","sourcesContent":["import fs, { readFile } from \"node:fs/promises\";\nimport nodePath from \"node:path\";\nimport type { AgentTool, ToolEffects } from \"@apholdings/jensen-agent-core\";\nimport { WorkspaceBoundary, WorkspaceBoundaryError } from \"./boundary.js\";\nimport { CheckpointStore, sha256, type WorkspaceCheckpoint } from \"./checkpoint.js\";\nimport { type LeaseRecord, type LeaseResult, WorkspaceLeaseStore } from \"./lease.js\";\nimport { BASELINE_RULES, PolicyEngine } from \"./policy.js\";\nimport { type TransactionRecord, type WorkspaceEdit, WorkspaceTransactionManager } from \"./transaction.js\";\nimport type {\n\tExecutionMode,\n\tPolicyDecision,\n\tPolicyEvaluation,\n\tPolicyInput,\n\tRecoveryClass,\n\tTransactionId,\n} from \"./types.js\";\n\nexport class PolicyDeniedError extends Error {\n\treadonly decision: PolicyDecision;\n\tconstructor(decision: PolicyDecision) {\n\t\tsuper(`policy denied: ${decision.reasonCode}`);\n\t\tthis.name = \"PolicyDeniedError\";\n\t\tthis.decision = decision;\n\t}\n}\n\nexport class PolicyApprovalRequiredError extends Error {\n\treadonly evaluation: PolicyEvaluation;\n\tconstructor(evaluation: PolicyEvaluation) {\n\t\tsuper(\n\t\t\t`policy requires approval: ${(evaluation.decision as Extract<PolicyDecision, { outcome: \"require_approval\" }>).approvalScope}`,\n\t\t);\n\t\tthis.name = \"PolicyApprovalRequiredError\";\n\t\tthis.evaluation = evaluation;\n\t}\n}\n\nexport interface WorkspaceSafetyOptions {\n\tstorageDir: string;\n\ttimeoutMs?: number;\n\theartbeatMs?: number;\n\tnow?: () => number;\n\tisProcessAlive?: (pid: number) => boolean;\n}\n\nexport interface MutationOutcome {\n\ttransactionId: TransactionId;\n\tstage: \"confirmed\" | \"applied\" | \"rolled_back\";\n\tchanged: string[];\n}\n\n/**\n * Durable mutation-lifecycle events (the canonical long-horizon equivalent for\n * workspace mutations). These are recorded by the safety manager for audit and\n * replay. They complement, and never compete with, the long-horizon execution\n * state machine.\n */\nexport type MutationLifecycleEvent =\n\t| { event: \"MUTATION_POLICY_EVALUATED\"; transactionId?: string; outcome: string; reasonCode: string; at?: number }\n\t| { event: \"WORKSPACE_LEASE_ACQUIRED\"; ownerRunId: string; leaseId: string; at?: number }\n\t| { event: \"CHECKPOINT_CREATED\"; transactionId: string; checkpointId: string; at?: number }\n\t| { event: \"TRANSACTION_APPLY_STARTED\"; transactionId: string; at?: number }\n\t| { event: \"TRANSACTION_APPLIED\"; transactionId: string; at?: number }\n\t| { event: \"TRANSACTION_VALIDATION_STARTED\"; transactionId: string; at?: number }\n\t| { event: \"TRANSACTION_VALIDATED\"; transactionId: string; result: string; at?: number }\n\t| { event: \"TRANSACTION_CONFIRMED\"; transactionId: string; at?: number }\n\t| { event: \"TRANSACTION_ROLLBACK_STARTED\"; transactionId: string; at?: number }\n\t| { event: \"TRANSACTION_ROLLED_BACK\"; transactionId: string; at?: number }\n\t| { event: \"TRANSACTION_RECOVERY_REQUIRED\"; transactionId: string; reason: string; at?: number }\n\t| { event: \"WORKSPACE_LEASE_RELEASED\"; ownerRunId: string; at?: number };\n\nconst KNOWN_MUTATORS = new Set([\"edit\", \"write\", \"bash\", \"powershell\", \"process_manager\"]);\n\nfunction extractTargetPaths(toolName: string, params: Record<string, unknown>): string[] {\n\tif (params && typeof params.path === \"string\") return [params.path];\n\tvoid toolName;\n\treturn [];\n}\n\nfunction effectsMutate(effects: ToolEffects): boolean {\n\treturn (\n\t\teffects.writesWorkspace ||\n\t\teffects.deletesFiles ||\n\t\teffects.mutatesGit ||\n\t\teffects.mutatesExternalState ||\n\t\teffects.executesProcesses ||\n\t\teffects.startsPersistentProcesses\n\t);\n}\n\n/**\n * Plumbs the deterministic safety lifecycle together: policy → boundary →\n * lease → checkpoint → transactional apply → validate → confirm/rollback →\n * durable record. Provides a guarded tool wrapper for production tool\n * execution and a diagnostic surface that never exposes secrets or contents.\n */\nexport class WorkspaceSafety {\n\treadonly storageDir: string;\n\treadonly boundary: WorkspaceBoundary;\n\treadonly policy: PolicyEngine;\n\treadonly leases: WorkspaceLeaseStore;\n\treadonly checkpoints: CheckpointStore;\n\treadonly transactions: WorkspaceTransactionManager;\n\treadonly workspaceId: string;\n\treadonly root: string;\n\texecutionMode: ExecutionMode;\n\n\tconstructor(\n\t\troot: string,\n\t\tboundary: WorkspaceBoundary,\n\t\toptions: WorkspaceSafetyOptions,\n\t\tmode: ExecutionMode = \"observe\",\n\t) {\n\t\tthis.root = nodePath.resolve(root);\n\t\tthis.boundary = boundary;\n\t\tthis.workspaceId = boundary.effectiveRoot.replace(/[\\\\/]+/g, \"/\").toLowerCase();\n\t\tthis.storageDir = options.storageDir;\n\t\tthis.policy = new PolicyEngine(BASELINE_RULES, {});\n\t\tthis.leases = new WorkspaceLeaseStore({\n\t\t\tstorageDir: options.storageDir,\n\t\t\ttimeoutMs: options.timeoutMs ?? 30_000,\n\t\t\theartbeatMs: options.heartbeatMs ?? 5_000,\n\t\t\tnow: options.now ?? (() => Date.now()),\n\t\t\tisProcessAlive: options.isProcessAlive ?? undefined,\n\t\t});\n\t\tthis.checkpoints = new CheckpointStore({ storageDir: options.storageDir });\n\t\tthis.transactions = new WorkspaceTransactionManager(options.storageDir, boundary, this.checkpoints);\n\t\tthis.executionMode = mode;\n\t}\n\n\tstatic async create(\n\t\troot: string,\n\t\toptions: WorkspaceSafetyOptions,\n\t\tmode: ExecutionMode = \"execute\",\n\t): Promise<WorkspaceSafety> {\n\t\tconst boundary = await WorkspaceBoundary.create(root);\n\t\tawait fs.mkdir(nodePath.join(options.storageDir, \"leases\"), { recursive: true });\n\t\tawait fs.mkdir(nodePath.join(options.storageDir, \"transactions\"), { recursive: true });\n\t\tawait fs.mkdir(nodePath.join(options.storageDir, \"checkpoints\"), { recursive: true });\n\t\tconst safety = new WorkspaceSafety(root, boundary, options, mode);\n\t\tawait safety.persistMode();\n\t\treturn safety;\n\t}\n\n\tprivate modePath(): string {\n\t\treturn nodePath.join(this.storageDir, \"mode.json\");\n\t}\n\n\tprivate journalPath(): string {\n\t\tconst key = this.workspaceId.replace(/[^a-z0-9._-]/g, \"_\");\n\t\treturn nodePath.join(this.storageDir, \"events\", `${key}.jsonl`);\n\t}\n\n\t/** Append a durable mutation-lifecycle event (audit/replay, not authority). */\n\tasync appendEvent(ev: MutationLifecycleEvent & { at?: number }): Promise<void> {\n\t\tconst record = { ...ev, at: ev.at ?? Date.now() };\n\t\tawait fs.mkdir(nodePath.dirname(this.journalPath()), { recursive: true });\n\t\tawait fs.appendFile(this.journalPath(), `${JSON.stringify(record)}\\n`, { mode: 0o600 });\n\t}\n\n\tasync readEvents(): Promise<Record<string, unknown>[]> {\n\t\ttry {\n\t\t\tconst raw = await fs.readFile(this.journalPath(), \"utf-8\");\n\t\t\treturn raw\n\t\t\t\t.split(\"\\n\")\n\t\t\t\t.filter((l) => l.trim())\n\t\t\t\t.map((l) => JSON.parse(l) as Record<string, unknown>);\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n\n\t/**\n\t * Completion gate for long-horizon: a step whose required mutation\n\t * transaction is unresolved (unvalidated/unconfirmed/rollback- or\n\t * recovery-required) cannot be marked complete.\n\t */\n\tasync gateStepCompletion(mutating: boolean): Promise<{ canComplete: boolean; blockingReason?: string }> {\n\t\tif (!mutating) return { canComplete: true };\n\t\tconst unresolved = await this.transactions.list();\n\t\tconst blocking = unresolved.find((t) =>\n\t\t\t[\"prepared\", \"checkpointed\", \"applied\", \"validating\", \"validated\", \"recovery_required\"].includes(t.stage),\n\t\t);\n\t\tif (blocking) {\n\t\t\treturn {\n\t\t\t\tcanComplete: false,\n\t\t\t\tblockingReason: `mutation transaction ${blocking.transactionId} is unresolved (${blocking.stage})`,\n\t\t\t};\n\t\t}\n\t\treturn { canComplete: true };\n\t}\n\n\tasync persistMode(): Promise<void> {\n\t\tawait fs.writeFile(this.modePath(), JSON.stringify({ mode: this.executionMode }), { mode: 0o600 });\n\t}\n\n\tasync setExecutionMode(mode: ExecutionMode): Promise<void> {\n\t\tthis.executionMode = mode;\n\t\tawait this.persistMode();\n\t}\n\n\tasync readMode(): Promise<ExecutionMode | null> {\n\t\ttry {\n\t\t\tconst raw = JSON.parse(await fs.readFile(this.modePath(), \"utf-8\")) as { mode?: ExecutionMode };\n\t\t\treturn raw.mode ?? null;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tevaluate(\n\t\tinput: Omit<PolicyInput, \"workspaceId\" | \"executionMode\"> & Partial<Pick<PolicyInput, \"executionMode\">>,\n\t): PolicyEvaluation {\n\t\treturn this.policy.evaluate({\n\t\t\tworkspaceId: this.workspaceId,\n\t\t\texecutionMode: input.executionMode ?? this.executionMode,\n\t\t\t...input,\n\t\t});\n\t}\n\n\t/** Guard a mutation path. Returns resolved abs paths or throws Denied/Approval/Boundary. */\n\tasync guardMutation(input: Omit<PolicyInput, \"workspaceId\" | \"executionMode\">): Promise<{\n\t\tevaluation: PolicyEvaluation;\n\t\tresolved: string[];\n\t}> {\n\t\tconst evaluation = this.evaluate(input);\n\t\tif (evaluation.decision.outcome === \"deny\") throw new PolicyDeniedError(evaluation.decision);\n\t\tconst resolved: string[] = [];\n\t\tfor (const p of input.resolvedPaths ?? []) {\n\t\t\tresolved.push(await this.boundary.resolveWithin(p));\n\t\t}\n\t\treturn { evaluation, resolved };\n\t}\n\n\t/**\n\t * High-level transactional mutation over a structured edit batch.\n\t * Policy is evaluated, an exclusive lease is acquired, a checkpoint is\n\t * created before the first write, the batch is applied, validated, and\n\t * confirmed. Any failure rolls back and releases the lease.\n\t */\n\tasync performMutation(opts: {\n\t\trunId?: string;\n\t\tmode?: ExecutionMode;\n\t\tedits: WorkspaceEdit[];\n\t\tpolicy?: PolicyDecision | null;\n\t\tvalidation?: {\n\t\t\tid: string;\n\t\t\tlabel: string;\n\t\t\trun: (() => Promise<{ exitCode: number; outputArtifact: string }>) | null;\n\t\t};\n\t\teffects?: ToolEffects;\n\t\treleaseAuthorized?: boolean;\n\t}): Promise<MutationOutcome> {\n\t\tconst mode = opts.mode ?? this.executionMode;\n\t\tconst absEdits = await Promise.all(\n\t\t\topts.edits.map(async (e) => ({ e, abs: await this.boundary.resolveWithin(e.path) })),\n\t\t);\n\t\tconst targetPaths = absEdits.map((x) => x.abs);\n\t\t// Evaluate policy.\n\t\tconst input: PolicyInput = {\n\t\t\ttoolName: \"workspace_transaction\",\n\t\t\teffects: opts.effects ?? DEFAULT_MUTATING_EFFECTS,\n\t\t\tresolvedPaths: targetPaths,\n\t\t\tworkspaceId: this.workspaceId,\n\t\t\texecutionMode: mode,\n\t\t\treleaseAuthorized: opts.releaseAuthorized,\n\t\t};\n\t\tconst evaluation = opts.policy ?? this.policy.evaluate(input).decision;\n\t\tif (evaluation.outcome === \"deny\") throw new PolicyDeniedError(evaluation);\n\t\tif (evaluation.outcome === \"require_approval\") {\n\t\t\t// performMutation has no approval channel: an unattended mutation\n\t\t\t// requiring approval must not proceed.\n\t\t\tthrow new PolicyApprovalRequiredError({ decision: evaluation, key: this.workspaceId });\n\t\t}\n\t\tawait this.appendEvent({\n\t\t\tevent: \"MUTATION_POLICY_EVALUATED\",\n\t\t\toutcome: evaluation.outcome,\n\t\t\treasonCode: evaluation.reasonCode,\n\t\t});\n\t\t// Acquire exclusive workspace mutation lease.\n\t\tconst lease = await this.acquireLease(opts.runId ?? \"run\");\n\t\tif (!lease.ok) {\n\t\t\tawait this.appendEvent({\n\t\t\t\tevent: \"TRANSACTION_RECOVERY_REQUIRED\",\n\t\t\t\ttransactionId: \"none\",\n\t\t\t\treason: \"lease_unavailable\",\n\t\t\t});\n\t\t\tthrow new PolicyDeniedError({\n\t\t\t\toutcome: \"deny\",\n\t\t\t\truleId: \"lease\",\n\t\t\t\treasonCode: \"workspace_mutation_lease_unavailable\",\n\t\t\t});\n\t\t}\n\t\tawait this.appendEvent({\n\t\t\tevent: \"WORKSPACE_LEASE_ACQUIRED\",\n\t\t\townerRunId: opts.runId ?? \"run\",\n\t\t\tleaseId: lease.lease.leaseId,\n\t\t});\n\t\ttry {\n\t\t\tconst tx = await this.transactions.begin(this.workspaceId, { runId: opts.runId, mode, policy: evaluation });\n\t\t\tawait this.transactions.checkpoint(tx, targetPaths);\n\t\t\tawait this.appendEvent({\n\t\t\t\tevent: \"CHECKPOINT_CREATED\",\n\t\t\t\ttransactionId: tx.transactionId,\n\t\t\t\tcheckpointId: tx.checkpointId!,\n\t\t\t});\n\t\t\ttry {\n\t\t\t\tawait this.appendEvent({ event: \"TRANSACTION_APPLY_STARTED\", transactionId: tx.transactionId });\n\t\t\t\tconst applied = await this.transactions.apply(tx, opts.edits);\n\t\t\t\tawait this.appendEvent({ event: \"TRANSACTION_APPLIED\", transactionId: tx.transactionId });\n\t\t\t\tawait this.appendEvent({ event: \"TRANSACTION_VALIDATION_STARTED\", transactionId: tx.transactionId });\n\t\t\t\tif (opts.validation) {\n\t\t\t\t\tawait this.transactions.validate(tx, opts.validation);\n\t\t\t\t} else {\n\t\t\t\t\tawait this.transactions.validate(tx, {\n\t\t\t\t\t\tid: \"write-verify\",\n\t\t\t\t\t\tlabel: \"write-verify\",\n\t\t\t\t\t\trun: async () => {\n\t\t\t\t\t\t\tfor (const p of tx.appliedPaths) {\n\t\t\t\t\t\t\t\tconst cur = await readFile(p).catch(() => null);\n\t\t\t\t\t\t\t\tconst expected = tx.appliedSha[p];\n\t\t\t\t\t\t\t\tif (expected !== null && (!cur || sha256(cur) !== expected)) {\n\t\t\t\t\t\t\t\t\treturn { exitCode: 1, outputArtifact: `hash mismatch: ${p}` };\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn { exitCode: 0, outputArtifact: \"\" };\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tawait this.appendEvent({\n\t\t\t\t\tevent: \"TRANSACTION_VALIDATED\",\n\t\t\t\t\ttransactionId: tx.transactionId,\n\t\t\t\t\tresult: tx.validation?.result ?? \"unknown\",\n\t\t\t\t});\n\t\t\t\tif (\n\t\t\t\t\ttx.validation?.result === \"failed\" ||\n\t\t\t\t\ttx.validation?.result === \"aborted\" ||\n\t\t\t\t\ttx.validation?.result === \"timed_out\"\n\t\t\t\t) {\n\t\t\t\t\tawait this.appendEvent({ event: \"TRANSACTION_ROLLBACK_STARTED\", transactionId: tx.transactionId });\n\t\t\t\t\tawait this.transactions.rollback(tx);\n\t\t\t\t\tawait this.appendEvent({ event: \"TRANSACTION_ROLLED_BACK\", transactionId: tx.transactionId });\n\t\t\t\t\tawait this.releaseLease(opts.runId ?? \"run\");\n\t\t\t\t\tawait this.appendEvent({ event: \"WORKSPACE_LEASE_RELEASED\", ownerRunId: opts.runId ?? \"run\" });\n\t\t\t\t\treturn { transactionId: tx.transactionId, stage: \"rolled_back\", changed: [] };\n\t\t\t\t}\n\t\t\t\tawait this.transactions.confirm(tx);\n\t\t\t\tawait this.appendEvent({ event: \"TRANSACTION_CONFIRMED\", transactionId: tx.transactionId });\n\t\t\t\tawait this.releaseLease(opts.runId ?? \"run\");\n\t\t\t\tawait this.appendEvent({ event: \"WORKSPACE_LEASE_RELEASED\", ownerRunId: opts.runId ?? \"run\" });\n\t\t\t\treturn { transactionId: tx.transactionId, stage: \"confirmed\", changed: applied.changed.map((c) => c.path) };\n\t\t\t} catch (err) {\n\t\t\t\tawait this.appendEvent({ event: \"TRANSACTION_ROLLBACK_STARTED\", transactionId: tx.transactionId });\n\t\t\t\tif (tx.checkpointId) await this.transactions.rollback(tx).catch(() => {});\n\t\t\t\tawait this.appendEvent({ event: \"TRANSACTION_ROLLED_BACK\", transactionId: tx.transactionId });\n\t\t\t\tawait this.releaseLease(opts.runId ?? \"run\");\n\t\t\t\tawait this.appendEvent({ event: \"WORKSPACE_LEASE_RELEASED\", ownerRunId: opts.runId ?? \"run\" });\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tif (err instanceof WorkspaceBoundaryError || err instanceof PolicyDeniedError) {\n\t\t\t\tawait this.releaseLease(opts.runId ?? \"run\").catch(() => {});\n\t\t\t}\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tasync acquireLease(ownerRunId: string): Promise<LeaseResult> {\n\t\treturn this.leases.acquire(this.workspaceId, ownerRunId);\n\t}\n\n\tasync releaseLease(ownerRunId: string): Promise<void> {\n\t\tawait this.leases.release(this.workspaceId, ownerRunId);\n\t}\n\n\tasync leaseStatus(): Promise<LeaseRecord | null> {\n\t\treturn this.leases.status(this.workspaceId);\n\t}\n\n\tasync lastCheckpoint(): Promise<WorkspaceCheckpoint | null> {\n\t\tconst all = await this.checkpoints.list();\n\t\tall.sort((a, b) => b.createdAt - a.createdAt);\n\t\treturn all[0] ?? null;\n\t}\n\n\tasync recoverRollback(transactionId: TransactionId): Promise<TransactionRecord | null> {\n\t\tconst rec = await this.transactions.read(transactionId);\n\t\tif (!rec) return null;\n\t\tawait this.transactions.rollback(rec);\n\t\treturn rec;\n\t}\n\n\tasync classify(transactionId: TransactionId): Promise<RecoveryClass> {\n\t\treturn this.transactions.classify(transactionId);\n\t}\n\n\t/**\n\t * Wrap the deterministic mutating tools (edit, write) so that every call is\n\t * guarded by policy + lease + checkpoint + transaction confirm/rollback.\n\t */\n\twrapMutationTools<T extends AgentTool>(tools: T[], authorize?: (scope: string) => boolean | Promise<boolean>): T[] {\n\t\treturn tools.map((tool) => {\n\t\t\tif (!KNOWN_MUTATORS.has(tool.name)) return tool;\n\t\t\tconst rawExecute = tool.execute.bind(tool);\n\t\t\tconst wrapper: T = Object.create(tool);\n\t\t\tObject.defineProperty(wrapper, \"execute\", {\n\t\t\t\tvalue: async (toolCallId: string, params: never, signal?: AbortSignal, onUpdate?: never) => {\n\t\t\t\t\tconst effects = tool.effects ?? DEFAULT_MUTATING_EFFECTS;\n\t\t\t\t\tconst targetPaths = extractTargetPaths(tool.name, params as Record<string, unknown>);\n\t\t\t\t\tconst resolved: string[] = [];\n\t\t\t\t\tfor (const p of targetPaths) {\n\t\t\t\t\t\tresolved.push(await this.boundary.resolveWithin(p));\n\t\t\t\t\t}\n\t\t\t\t\tconst input: PolicyInput = {\n\t\t\t\t\t\ttoolName: tool.name,\n\t\t\t\t\t\teffects,\n\t\t\t\t\t\tresolvedPaths: resolved,\n\t\t\t\t\t\tworkspaceId: this.workspaceId,\n\t\t\t\t\t\texecutionMode: this.executionMode,\n\t\t\t\t\t\trequestedCommand:\n\t\t\t\t\t\t\ttypeof (params as { command?: string }).command === \"string\"\n\t\t\t\t\t\t\t\t? (params as { command?: string }).command\n\t\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t};\n\t\t\t\t\tconst evaluation = this.policy.evaluate(input);\n\t\t\t\t\tif (evaluation.decision.outcome === \"deny\") throw new PolicyDeniedError(evaluation.decision);\n\t\t\t\t\tif (evaluation.decision.outcome === \"require_approval\") {\n\t\t\t\t\t\tconst approved = authorize\n\t\t\t\t\t\t\t? await authorize(\n\t\t\t\t\t\t\t\t\t(evaluation.decision as Extract<PolicyDecision, { outcome: \"require_approval\" }>)\n\t\t\t\t\t\t\t\t\t\t.approvalScope,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t: false;\n\t\t\t\t\t\tif (!approved) throw new PolicyApprovalRequiredError(evaluation);\n\t\t\t\t\t}\n\t\t\t\t\tif (!effectsMutate(effects)) {\n\t\t\t\t\t\treturn rawExecute(toolCallId, params, signal, onUpdate);\n\t\t\t\t\t}\n\t\t\t\t\t// Mutating tool: lease + checkpoint + transactional.\n\t\t\t\t\tif (tool.name === \"bash\" || tool.name === \"powershell\" || tool.name === \"process_manager\") {\n\t\t\t\t\t\t// Dynamic shell effects are not structurally checkpointable.\n\t\t\t\t\t\tconst lease = await this.acquireLease(\"run\");\n\t\t\t\t\t\tif (!lease.ok)\n\t\t\t\t\t\t\tthrow new PolicyDeniedError({\n\t\t\t\t\t\t\t\toutcome: \"deny\",\n\t\t\t\t\t\t\t\truleId: \"lease\",\n\t\t\t\t\t\t\t\treasonCode: \"workspace_mutation_lease_unavailable\",\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst result = await rawExecute(toolCallId, params, signal, onUpdate);\n\t\t\t\t\t\t\tawait this.releaseLease(\"run\");\n\t\t\t\t\t\t\treturn result;\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tawait this.releaseLease(\"run\");\n\t\t\t\t\t\t\tthrow err;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// Deterministic file mutators: checkpoint the target file.\n\t\t\t\t\tconst tx = await this.transactions.begin(this.workspaceId, {\n\t\t\t\t\t\trunId: \"run\",\n\t\t\t\t\t\tmode: this.executionMode,\n\t\t\t\t\t\tpolicy: evaluation.decision,\n\t\t\t\t\t});\n\t\t\t\t\tawait this.transactions.checkpoint(tx, resolved);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst result = await rawExecute(toolCallId, params, signal, onUpdate);\n\t\t\t\t\t\tconst expected = await readFile(resolved[0] ?? \"\").catch(() => null);\n\t\t\t\t\t\tawait this.transactions.validate(tx, {\n\t\t\t\t\t\t\tid: \"tool-write-verify\",\n\t\t\t\t\t\t\tlabel: `verify ${tool.name}`,\n\t\t\t\t\t\t\trun: async () => {\n\t\t\t\t\t\t\t\tif (!resolved.length) return { exitCode: 0, outputArtifact: \"\" };\n\t\t\t\t\t\t\t\tif (!expected) return { exitCode: 1, outputArtifact: `${resolved[0]} missing after write` };\n\t\t\t\t\t\t\t\treturn { exitCode: 0, outputArtifact: sha256(expected) };\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (tx.validation && tx.validation.result !== \"passed\") {\n\t\t\t\t\t\t\tawait this.transactions.rollback(tx);\n\t\t\t\t\t\t\tthrow new PolicyDeniedError({\n\t\t\t\t\t\t\t\toutcome: \"deny\",\n\t\t\t\t\t\t\t\truleId: \"validation\",\n\t\t\t\t\t\t\t\treasonCode: \"tool_write_validation_failed\",\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait this.transactions.confirm(tx);\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tif (tx.checkpointId) await this.transactions.rollback(tx).catch(() => {});\n\t\t\t\t\t\tthrow err;\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn wrapper;\n\t\t});\n\t}\n}\n\nconst DEFAULT_MUTATING_EFFECTS: ToolEffects = {\n\treadsWorkspace: true,\n\twritesWorkspace: true,\n\tcreatesFiles: true,\n\tdeletesFiles: true,\n\texecutesProcesses: false,\n\tstartsPersistentProcesses: false,\n\taccessesNetwork: false,\n\tmutatesGit: false,\n\tmutatesExternalState: false,\n\thandlesSecrets: false,\n\tpotentiallyDestructive: false,\n\trequiresExclusiveWorkspaceLease: true,\n\tparallelSafe: false,\n};\n"]}