{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../../src/core/safety/cli.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,uBAAuB,+BAA+B,CAAC;AAEpE,6DAA6D;AAC7D,wBAAgB,wBAAwB,IAAI,MAAM,CAGjD;AAyBD,wBAAsB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAoD7E","sourcesContent":["import os from \"node:os\";\nimport nodePath from \"node:path\";\nimport { PRODUCTION_TOOL_EFFECTS } from \"./effects.js\";\nimport { WorkspaceSafety } from \"./manager.js\";\nimport type { TransactionId } from \"./types.js\";\n\nexport const WORKSPACE_STATE_DIR_ENV = \"JENSEN_WORKSPACE_STATE_DIR\";\n\n/** Resolve the durable workspace-state storage directory. */\nexport function defaultWorkspaceStateDir(): string {\n\tif (process.env[WORKSPACE_STATE_DIR_ENV]) return process.env[WORKSPACE_STATE_DIR_ENV]!;\n\treturn nodePath.join(os.homedir(), \".local\", \"state\", \"jensen\", \"workspace\");\n}\n\nfunction usage(): string {\n\treturn [\n\t\t\"Usage: jensen workspace <command> [args]\",\n\t\t\"\",\n\t\t\"Commands:\",\n\t\t\"  status                          Show workspace safety status\",\n\t\t\"  policy                          Show the deterministic policy profile\",\n\t\t\"  lease                           Show the active mutation lease\",\n\t\t\"  transactions                    List mutation transactions\",\n\t\t\"  transaction inspect <id>        Inspect one transaction\",\n\t\t\"  checkpoint inspect <id>         Inspect one checkpoint (metadata only)\",\n\t\t\"  rollback <transaction-id>       Roll back one failed/unconfirmed transaction\",\n\t\t\"  recovery status                 Classify incomplete transactions\",\n\t\t\"  recovery inspect <id>           Inspect recovery state for a transaction\",\n\t\t\"  recovery resume <id>            Recover a transaction flagged for resume\",\n\t\t\"  recovery rollback <id>          Roll back a transaction flagged for rollback\",\n\t].join(\"\\n\");\n}\n\nfunction red(s: string): string {\n\treturn s;\n}\n\nexport async function handleWorkspaceCommand(args: string[]): Promise<boolean> {\n\tif (args[0] !== \"workspace\") return false;\n\tconst sub = args[1] ?? \"status\";\n\tconst storageDir = defaultWorkspaceStateDir();\n\tconst root = process.cwd();\n\ttry {\n\t\tconst safety = await WorkspaceSafety.create(root, { storageDir }, \"execute\");\n\t\tswitch (sub) {\n\t\t\tcase \"status\":\n\t\t\t\tawait printStatus(safety);\n\t\t\t\tbreak;\n\t\t\tcase \"policy\":\n\t\t\t\tprintPolicy();\n\t\t\t\tbreak;\n\t\t\tcase \"lease\":\n\t\t\t\tawait printLease(safety);\n\t\t\t\tbreak;\n\t\t\tcase \"transactions\":\n\t\t\t\tawait printTransactions(safety);\n\t\t\t\tbreak;\n\t\t\tcase \"transaction\":\n\t\t\t\tif (args[2] !== \"inspect\") {\n\t\t\t\t\tconsole.error(usage());\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tawait inspectTransaction(safety, args[3]);\n\t\t\t\tbreak;\n\t\t\tcase \"checkpoint\":\n\t\t\t\tif (args[2] !== \"inspect\") {\n\t\t\t\t\tconsole.error(usage());\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tawait inspectCheckpoint(safety, args[3]);\n\t\t\t\tbreak;\n\t\t\tcase \"rollback\": {\n\t\t\t\tif (!args[2]) {\n\t\t\t\t\tconsole.error(\"Missing transaction-id\");\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tawait doRollback(safety, args[2]);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"recovery\":\n\t\t\t\tawait handleRecovery(safety, args);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tconsole.error(usage());\n\t\t}\n\t} catch (err) {\n\t\tconsole.error(red(`workspace command failed: ${err instanceof Error ? err.message : String(err)}`));\n\t}\n\treturn true;\n}\n\nasync function printStatus(safety: WorkspaceSafety): Promise<void> {\n\tconst lease = await safety.leaseStatus();\n\tconst txs = await safety.transactions.list();\n\tconst cp = await safety.lastCheckpoint();\n\tconst incomplete = txs.filter((t) =>\n\t\t[\"prepared\", \"checkpointed\", \"applied\", \"validating\", \"validated\", \"recovery_required\"].includes(t.stage),\n\t);\n\tconsole.log(`Workspace:       ${safety.root}`);\n\tconsole.log(`Canonical root:  ${safety.boundary.effectiveRoot}`);\n\tconsole.log(`Workspace id:    ${safety.workspaceId}`);\n\tconsole.log(`Execution mode:  ${safety.executionMode}`);\n\tconsole.log(`Policy profile:  baseline (deny > approval > allow > default)`);\n\tconsole.log(\n\t\t`Active lease:    ${lease ? `${lease.ownerRunId} acquired ${new Date(lease.acquiredAt).toISOString()}` : \"none\"}`,\n\t);\n\tconsole.log(`Transactions:    ${txs.length} (${incomplete.length} incomplete)`);\n\tconsole.log(`Last checkpoint: ${cp ? cp.checkpointId : \"none\"}`);\n\tconsole.log(`Rollback cap:    ${incomplete[0]?.rollbackCapability ?? \"-\"}`);\n}\n\nfunction printPolicy(): void {\n\tconsole.log(\"Policy precedence: deny > require_approval > allow > default\");\n\tconsole.log(\"\");\n\tconsole.log(\"Baseline rules:\");\n\tconst rules = [\n\t\t\"deny.destructive_shell       - rm -rf /, git reset --hard, force-push to protected branch, policy-bypass markers\",\n\t\t\"deny.paths_outside_workspace - targets resolved outside the authorized root\",\n\t\t\"deny.secrets                 - private key material, .env, credentials\",\n\t\t\"allow.authorized_release     - publication only under explicit release authorization\",\n\t\t\"deny.unknown_effects         - dynamic/unknown-effect tools are conservative\",\n\t\t\"mode.observe/plan/execute    - explicit durable execution modes\",\n\t];\n\tfor (const r of rules) console.log(`  ${r}`);\n\tconsole.log(\"\");\n\tconsole.log(\"Tool effects registered:\");\n\tfor (const name of Object.keys(PRODUCTION_TOOL_EFFECTS)) {\n\t\tconst e = PRODUCTION_TOOL_EFFECTS[name];\n\t\tconst flags =\n\t\t\t[\n\t\t\t\te.writesWorkspace && \"writes\",\n\t\t\t\te.deletesFiles && \"deletes\",\n\t\t\t\te.executesProcesses && \"exec\",\n\t\t\t\te.accessesNetwork && \"net\",\n\t\t\t\te.mutatesGit && \"git\",\n\t\t\t\te.potentiallyDestructive && \"destructive\",\n\t\t\t\te.parallelSafe && \"parallel\",\n\t\t\t]\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.join(\",\") || \"read\";\n\t\tconsole.log(`  ${name.padEnd(20)} ${flags}`);\n\t}\n}\n\nasync function printLease(safety: WorkspaceSafety): Promise<void> {\n\tconst lease = await safety.leaseStatus();\n\tif (!lease) {\n\t\tconsole.log(\"No active mutation lease.\");\n\t\treturn;\n\t}\n\tconsole.log(`Lease id:        ${lease.leaseId}`);\n\tconsole.log(`Workspace:       ${lease.workspaceId}`);\n\tconsole.log(`Owner run:       ${lease.ownerRunId}`);\n\tconsole.log(`Process id:      ${lease.processId}`);\n\tconsole.log(`Acquired at:     ${new Date(lease.acquiredAt).toISOString()}`);\n\tconsole.log(`Last heartbeat:  ${new Date(lease.lastHeartbeat).toISOString()}`);\n\tconsole.log(`Expiry:          ${new Date(lease.expiresAt).toISOString()}`);\n}\n\nasync function printTransactions(safety: WorkspaceSafety): Promise<void> {\n\tconst txs = await safety.transactions.list();\n\tif (txs.length === 0) {\n\t\tconsole.log(\"No transactions recorded.\");\n\t\treturn;\n\t}\n\tfor (const t of txs) {\n\t\tconsole.log(\n\t\t\t`${t.transactionId}  ${t.stage.padEnd(18)} ${new Date(t.createdAt).toISOString()}  paths=${t.appliedPaths.length}`,\n\t\t);\n\t}\n}\n\nasync function inspectTransaction(safety: WorkspaceSafety, id?: string): Promise<void> {\n\tif (!id) {\n\t\tconsole.error(\"Missing transaction id\");\n\t\treturn;\n\t}\n\tconst t = await safety.transactions.read(id as TransactionId);\n\tif (!t) {\n\t\tconsole.error(`Transaction not found: ${id}`);\n\t\treturn;\n\t}\n\tconsole.log(`Transaction:    ${t.transactionId}`);\n\tconsole.log(`Workspace:      ${t.workspaceId}`);\n\tconsole.log(`Stage:          ${t.stage}`);\n\tconsole.log(`Mode:           ${t.mode}`);\n\tconsole.log(`Policy:         ${t.policy ? `${t.policy.outcome}/${t.policy.reasonCode}` : \"none\"}`);\n\tconsole.log(`Checkpoint:     ${t.checkpointId ?? \"none\"}`);\n\tconsole.log(`Created:        ${new Date(t.createdAt).toISOString()}`);\n\tconsole.log(`Rollback cap:   ${t.rollbackCapability}`);\n\tconst v = t.validation;\n\tif (v) {\n\t\tconsole.log(`Validation:     ${v.result} (exit ${v.exitCode}, ${v.durationMs}ms)`);\n\t}\n\tconsole.log(`Changed paths:  ${t.appliedPaths.length}`);\n\tfor (const p of t.appliedPaths.slice(0, 20)) console.log(`  ${p}`);\n}\n\nasync function inspectCheckpoint(safety: WorkspaceSafety, id?: string): Promise<void> {\n\tif (!id) {\n\t\tconsole.error(\"Missing checkpoint id\");\n\t\treturn;\n\t}\n\tconst cp = await safety.checkpoints.read(id!);\n\tif (!cp) {\n\t\tconsole.error(`Checkpoint not found: ${id}`);\n\t\treturn;\n\t}\n\tconsole.log(`Checkpoint:     ${cp.checkpointId}`);\n\tconsole.log(`Status:         ${cp.status}`);\n\tconsole.log(`Transaction:    ${cp.transactionId}`);\n\tconsole.log(`Entries:        ${cp.entries.length}`);\n\tconsole.log(`Manifest sha:   ${cp.manifestSha256.slice(0, 16)}…`);\n\tfor (const e of cp.entries.slice(0, 20)) {\n\t\tconsole.log(`  ${(e.type + (e.existed ? \"\" : \" (missing)\")).padEnd(22)} ${nodePath.basename(e.path)}`);\n\t}\n}\n\nasync function doRollback(safety: WorkspaceSafety, id: string): Promise<void> {\n\tconst rec = await safety.transactions.read(id as TransactionId);\n\tif (!rec) {\n\t\tconsole.error(`Transaction not found: ${id}`);\n\t\treturn;\n\t}\n\tconsole.log(`Rolling back transaction ${id}...`);\n\tconst result = await safety.transactions.rollback(rec);\n\tif (result.status === \"conflict\") {\n\t\tconsole.error(`Rollback conflict on ${result.conflicts.length} path(s). Not overwriting user changes.`);\n\t\tfor (const c of result.conflicts) console.error(`  ${c.path}: ${c.message}`);\n\t\treturn;\n\t}\n\tconsole.log(`${result.status}: ${result.restored.length} path(s) restored.`);\n}\n\nasync function handleRecovery(safety: WorkspaceSafety, args: string[]): Promise<void> {\n\tconst action = args[2];\n\tconst id = args[3];\n\tswitch (action) {\n\t\tcase \"status\": {\n\t\t\tconst txs = await safety.transactions.list();\n\t\t\tconst incomplete = txs.filter((t) =>\n\t\t\t\t[\"prepared\", \"checkpointed\", \"applied\", \"validating\", \"validated\", \"recovery_required\"].includes(t.stage),\n\t\t\t);\n\t\t\tconsole.log(`${incomplete.length} incomplete transaction(s).`);\n\t\t\tfor (const t of incomplete) {\n\t\t\t\tconst cls = await safety.transactions.classify(t.transactionId);\n\t\t\t\tconsole.log(`  ${t.transactionId}  stage=${t.stage}  recovery=${cls}`);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase \"inspect\":\n\t\t\tif (!id) return console.error(\"Missing transaction id\");\n\t\t\tconsole.log(await safety.transactions.classify(id as TransactionId));\n\t\t\tbreak;\n\t\tcase \"resume\":\n\t\tcase \"rollback\": {\n\t\t\tif (!id) return console.error(\"Missing transaction id\");\n\t\t\tconst cls = await safety.transactions.classify(id as TransactionId);\n\t\t\tif (action === \"rollback\" || cls === \"rollback_required\") {\n\t\t\t\treturn doRollback(safety, id);\n\t\t\t}\n\t\t\tconsole.log(`Transaction ${id} classified as ${cls}; no rollback performed.`);\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tconsole.error(usage());\n\t}\n}\n"]}