{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../../src/core/mission/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAeH,OAAO,KAAK,EAAE,eAAe,EAA0B,gBAAgB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1G,MAAM,WAAW,sBAAsB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,YAAY,CAAC;IACpB,UAAU,EAAE,gBAAgB,EAAE,CAAC;IAC/B,SAAS,CAAC,EAAE,eAAe,EAAE,CAAC;CAC9B;AAED,wBAAgB,kBAAkB,IAAI,MAAM,CAI3C;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAElE;AA4DD,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAkQ3E","sourcesContent":["/**\n * Durable Mission Graph — Mission Control CLI (2.0.0).\n *\n * Exposes:\n *   jensen mission create <definition.json>\n *   jensen mission validate <id|file>\n *   jensen mission plan <id|file>\n *   jensen mission graph <id|file>\n *   jensen mission start <id>\n *   jensen mission status <id>\n *   jensen mission replay <id>\n *   jensen mission reconcile --preview <id>\n *   jensen doctor mission\n */\n\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport { deriveReadiness, initializeRuntime, missionStatus, promoteMission, reconcileAfterReboot } from \"./engine.js\";\nimport {\n\tbuildMissionDocument,\n\tcomputeCriticalPath,\n\tcomputeMissionGraphDigest,\n\ttopologicallyOrdered,\n\tvalidateMissionGraph,\n} from \"./graph.js\";\nimport { buildSchedulePlan } from \"./scheduler.js\";\nimport { MissionStore } from \"./store.js\";\nimport type { MissionContract, MissionGraphDocumentV1, MissionObjective, MissionScope } from \"./types.js\";\n\nexport interface MissionDefinitionInput {\n\tmissionId: string;\n\tscope: MissionScope;\n\tobjectives: MissionObjective[];\n\tcontracts?: MissionContract[];\n}\n\nexport function defaultMissionRoot(): string {\n\tconst env = process.env.JENSEN_MISSION_STORE;\n\tif (env?.trim()) return env.trim();\n\treturn path.join(os.homedir(), \".jensen\", \"missions\");\n}\n\nexport function missionDir(root: string, missionId: string): string {\n\treturn path.join(root, missionId);\n}\n\nfunction storeFor(root: string, missionId: string): MissionStore {\n\treturn new MissionStore({ root: missionDir(root, missionId) });\n}\n\nfunction loadDocumentFromFile(file: string): MissionGraphDocumentV1 {\n\tconst raw = fs.readFileSync(path.resolve(file), \"utf8\");\n\tconst def = JSON.parse(raw) as MissionDefinitionInput | MissionGraphDocumentV1;\n\tif (\"schemaVersion\" in def && (def as MissionGraphDocumentV1).schemaVersion === 1) {\n\t\treturn def as MissionGraphDocumentV1;\n\t}\n\tconst d = def as MissionDefinitionInput;\n\treturn buildMissionDocument(\n\t\td.missionId,\n\t\t{ ...d.scope, requireDeclaredRepositories: true },\n\t\td.objectives,\n\t\td.contracts ?? [],\n\t\t1,\n\t\t\"DRAFT\",\n\t\tDate.now(),\n\t);\n}\n\nasync function loadDocument(root: string, idOrFile: string): Promise<MissionGraphDocumentV1 | undefined> {\n\tif (idOrFile.includes(\"/\") || idOrFile.endsWith(\".json\")) {\n\t\treturn loadDocumentFromFile(idOrFile);\n\t}\n\tconst store = storeFor(root, idOrFile);\n\tawait store.initialize();\n\treturn store.loadGraph();\n}\n\nfunction usage(): string {\n\treturn [\n\t\t\"Mission Control (2.0.0)\",\n\t\t\"  mission create <definition.json>   create and store a mission graph\",\n\t\t\"  mission validate <id|file>         validate a mission graph\",\n\t\t\"  mission plan <id|file>             print the schedule plan (waves + critical path)\",\n\t\t\"  mission graph <id|file>            print objectives, dependencies and critical path\",\n\t\t\"  mission start <id>                 promote to ACTIVE\",\n\t\t\"  mission status <id>                print objective/mission status\",\n\t\t\"  mission replay <id>                replay the event log (zero effects)\",\n\t\t\"  mission reconcile --preview <id>   show post-reboot reconciliation actions (no mutation)\",\n\t\t\"  doctor mission                     health-check the mission store\",\n\t\t\"\",\n\t\t\"Durable mission control plane (2.9.0)\",\n\t\t\"  mission list [--json]              list durable missions\",\n\t\t\"  mission show <id> [--json]         inspect one durable mission\",\n\t\t\"  mission tree <id> [--json]         traverse a durable mission tree\",\n\t\t\"  mission attempts <id> [--json]     attempt/execution history\",\n\t\t\"  mission ownership <id> [--json]    lease/fencing ownership\",\n\t\t\"  mission result <id> [--json]       canonical terminal result\",\n\t\t\"  mission evidence <id> [--json]     evidence references\",\n\t\t\"  mission resume <id> [--json]       explicit durable-child resume\",\n\t\t\"  mission cancel <id> [--json]       cancel a locally-owned execution\",\n\t\t\"  mission recover [--json]           conservative restart reconciliation\",\n\t].join(\"\\n\");\n}\n\nexport async function handleMissionCommand(args: string[]): Promise<boolean> {\n\tif (args[0] !== \"mission\" && !(args[0] === \"doctor\" && args[1] === \"mission\")) {\n\t\treturn false;\n\t}\n\n\tif (args[0] === \"doctor\" && args[1] === \"mission\") {\n\t\tconst root = defaultMissionRoot();\n\t\tconst sysRoot = new MissionStore({ root: path.join(root, \"__sys__\") });\n\t\tawait sysRoot.initialize();\n\t\tconst json = args.includes(\"--json\");\n\t\tconst report = { name: \"mission\", status: \"ok\", store: root, message: \"mission store reachable\" };\n\t\tif (json) {\n\t\t\tprocess.stdout.write(`${JSON.stringify(report, null, 2)}\\n`);\n\t\t} else {\n\t\t\tprocess.stdout.write(`✓ mission: store reachable at ${root}\\n`);\n\t\t}\n\t\treturn true;\n\t}\n\n\tconst sub = args[1];\n\tconst json = args.includes(\"--json\");\n\tconst preview = args.includes(\"--preview\");\n\tconst root = defaultMissionRoot();\n\tconst valueArgs = args.filter((a) => !a.startsWith(\"--\"));\n\n\tswitch (sub) {\n\t\tcase \"create\": {\n\t\t\tconst file = valueArgs[2];\n\t\t\tif (!file) {\n\t\t\t\tprocess.stderr.write(`missing definition file\\n${usage()}\\n`);\n\t\t\t\tprocess.exitCode = 1;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tconst doc = loadDocumentFromFile(file);\n\t\t\tconst validation = validateMissionGraph(doc);\n\t\t\tif (!validation.valid) {\n\t\t\t\tprocess.stderr.write(`mission '${doc.missionId}' invalid\\n`);\n\t\t\t\tfor (const e of validation.errors) process.stderr.write(`  ${e.severity}: ${e.message}\\n`);\n\t\t\t\tprocess.exitCode = 1;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tconst store = storeFor(root, doc.missionId);\n\t\t\tawait store.initialize();\n\t\t\tawait store.saveGraph(doc);\n\t\t\tconst out = { missionId: doc.missionId, revision: doc.revision, digest: doc.digest, status: doc.status };\n\t\t\tif (json) process.stdout.write(`${JSON.stringify(out, null, 2)}\\n`);\n\t\t\telse process.stdout.write(`created ${doc.missionId} rev ${doc.revision} ${doc.digest}\\n`);\n\t\t\treturn true;\n\t\t}\n\n\t\tcase \"validate\": {\n\t\t\tconst idOrFile = valueArgs[2];\n\t\t\tif (!idOrFile) {\n\t\t\t\tprocess.stderr.write(\"missing mission id or file\\n\");\n\t\t\t\tprocess.exitCode = 1;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tconst doc = await loadDocument(root, idOrFile);\n\t\t\tif (!doc) {\n\t\t\t\tprocess.stderr.write(\"mission not found\\n\");\n\t\t\t\tprocess.exitCode = 1;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tconst validation = validateMissionGraph(doc);\n\t\t\tconst payload = {\n\t\t\t\tmissionId: doc.missionId,\n\t\t\t\tvalid: validation.valid,\n\t\t\t\tdigest: validation.digest,\n\t\t\t\trevision: validation.revision,\n\t\t\t\terrors: validation.errors.map((e) => ({ severity: e.severity, message: e.message })),\n\t\t\t\tcycles: validation.cycles,\n\t\t\t};\n\t\t\tif (json) process.stdout.write(`${JSON.stringify(payload, null, 2)}\\n`);\n\t\t\telse if (validation.valid) process.stdout.write(`valid ${doc.missionId} ${validation.digest}\\n`);\n\t\t\telse {\n\t\t\t\tprocess.stderr.write(`invalid ${doc.missionId}\\n`);\n\t\t\t\tprocess.exitCode = 1;\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\n\t\tcase \"plan\":\n\t\tcase \"graph\": {\n\t\t\tconst idOrFile = valueArgs[2];\n\t\t\tif (!idOrFile) {\n\t\t\t\tprocess.stderr.write(\"missing mission id or file\\n\");\n\t\t\t\tprocess.exitCode = 1;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tconst doc = await loadDocument(root, idOrFile);\n\t\t\tif (!doc) {\n\t\t\t\tprocess.stderr.write(\"mission not found\\n\");\n\t\t\t\tprocess.exitCode = 1;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tconst runtime = initializeRuntime(doc);\n\t\t\tconst readiness = deriveReadiness(doc, runtime);\n\t\t\tconst plan = buildSchedulePlan(doc, {\n\t\t\t\tstatus: new Map(Object.entries(readiness.status)),\n\t\t\t\tparallelismBound: 4,\n\t\t\t\tenforceBudget: false,\n\t\t\t});\n\t\t\tconst topo = topologicallyOrdered(doc);\n\t\t\tif (sub === \"graph\") {\n\t\t\t\tconst payload = {\n\t\t\t\t\tmissionId: doc.missionId,\n\t\t\t\t\tobjectives: doc.objectives.map((o) => ({\n\t\t\t\t\t\tid: o.id,\n\t\t\t\t\t\tdeps: o.dependencies,\n\t\t\t\t\t\trepos: o.declaredRepositories,\n\t\t\t\t\t})),\n\t\t\t\t\ttopologicalOrder: topo,\n\t\t\t\t\tcriticalPath: computeCriticalPath(doc),\n\t\t\t\t\tdigest: computeMissionGraphDigest(doc.scope, doc.objectives, doc.contracts),\n\t\t\t\t};\n\t\t\t\tif (json) process.stdout.write(`${JSON.stringify(payload, null, 2)}\\n`);\n\t\t\t\telse {\n\t\t\t\t\tprocess.stdout.write(`objectives: ${doc.objectives.map((o) => o.id).join(\", \")}\\n`);\n\t\t\t\t\tprocess.stdout.write(`topo: ${topo.join(\" -> \")}\\n`);\n\t\t\t\t\tprocess.stdout.write(\n\t\t\t\t\t\t`critical path: ${payload.criticalPath.path.join(\" -> \")} (${payload.criticalPath.weight})\\n`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst payload = {\n\t\t\t\t\tmissionId: doc.missionId,\n\t\t\t\t\twaves: plan.waves,\n\t\t\t\t\tcriticalPath: plan.criticalPath,\n\t\t\t\t\tserializedGroups: plan.serializedGroups,\n\t\t\t\t\tbudgetExceeded: plan.budgetExceeded,\n\t\t\t\t};\n\t\t\t\tif (json) process.stdout.write(`${JSON.stringify(payload, null, 2)}\\n`);\n\t\t\t\telse {\n\t\t\t\t\tprocess.stdout.write(`waves:\\n`);\n\t\t\t\t\tfor (const [i, w] of plan.waves.entries()) {\n\t\t\t\t\t\tprocess.stdout.write(`  wave ${i + 1}: ${w.join(\", \")}\\n`);\n\t\t\t\t\t}\n\t\t\t\t\tprocess.stdout.write(`critical path: ${plan.criticalPath.join(\" -> \")}\\n`);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\n\t\tcase \"start\": {\n\t\t\tconst id = valueArgs[2];\n\t\t\tif (!id) {\n\t\t\t\tprocess.stderr.write(\"missing mission id\\n\");\n\t\t\t\tprocess.exitCode = 1;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tconst store = storeFor(root, id);\n\t\t\tawait store.initialize();\n\t\t\tconst doc = await store.loadGraph();\n\t\t\tif (!doc) {\n\t\t\t\tprocess.stderr.write(\"mission not found\\n\");\n\t\t\t\tprocess.exitCode = 1;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tconst promoted = promoteMission(doc);\n\t\t\tif (!promoted.ok || !promoted.value) {\n\t\t\t\tprocess.stderr.write(`cannot start: ${promoted.error}\\n`);\n\t\t\t\tprocess.exitCode = 1;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tawait store.saveGraph(promoted.value);\n\t\t\tif (json)\n\t\t\t\tprocess.stdout.write(\n\t\t\t\t\t`${JSON.stringify({ missionId: id, status: promoted.value.status, revision: promoted.value.revision }, null, 2)}\\n`,\n\t\t\t\t);\n\t\t\telse process.stdout.write(`${id} -> ${promoted.value.status} (rev ${promoted.value.revision})\\n`);\n\t\t\treturn true;\n\t\t}\n\n\t\tcase \"status\": {\n\t\t\tconst id = valueArgs[2];\n\t\t\tif (!id) {\n\t\t\t\tprocess.stderr.write(\"missing mission id\\n\");\n\t\t\t\tprocess.exitCode = 1;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tconst doc = await loadDocument(root, id);\n\t\t\tif (!doc) {\n\t\t\t\tprocess.stderr.write(\"mission not found\\n\");\n\t\t\t\tprocess.exitCode = 1;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tconst runtime = initializeRuntime(doc);\n\t\t\tconst readiness = deriveReadiness(doc, runtime);\n\t\t\tconst payload = {\n\t\t\t\tmissionId: doc.missionId,\n\t\t\t\tstatus: missionStatus(doc, runtime),\n\t\t\t\trevision: doc.revision,\n\t\t\t\tobjectives: Object.entries(readiness.status).map(([id, s]) => ({ id, status: s })),\n\t\t\t};\n\t\t\tif (json) process.stdout.write(`${JSON.stringify(payload, null, 2)}\\n`);\n\t\t\telse {\n\t\t\t\tprocess.stdout.write(`${payload.missionId} [${payload.status}]\\n`);\n\t\t\t\tfor (const o of payload.objectives) process.stdout.write(`  ${o.id}: ${o.status}\\n`);\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\n\t\tcase \"replay\": {\n\t\t\tconst id = valueArgs[2];\n\t\t\tif (!id) {\n\t\t\t\tprocess.stderr.write(\"missing mission id\\n\");\n\t\t\t\tprocess.exitCode = 1;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tconst store = storeFor(root, id);\n\t\t\tawait store.initialize();\n\t\t\tconst events = await store.replayEvents(id);\n\t\t\tconst payload = {\n\t\t\t\tmissionId: id,\n\t\t\t\treplayedEvents: events.length,\n\t\t\t\tdistinctEventIds: new Set(events.map((e) => e.id)).size,\n\t\t\t\tduplicatesSkipped: events.length - new Set(events.map((e) => e.id)).size,\n\t\t\t\tnote: \"replay is read-only; zero external effects\",\n\t\t\t};\n\t\t\tif (json) process.stdout.write(`${JSON.stringify(payload, null, 2)}\\n`);\n\t\t\telse process.stdout.write(`replayed ${events.length} events (read-only)\\n`);\n\t\t\treturn true;\n\t\t}\n\n\t\tcase \"reconcile\": {\n\t\t\tconst id = valueArgs[2];\n\t\t\tif (!id) {\n\t\t\t\tprocess.stderr.write(\"missing mission id\\n\");\n\t\t\t\tprocess.exitCode = 1;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tconst doc = await loadDocument(root, id);\n\t\t\tif (!doc) {\n\t\t\t\tprocess.stderr.write(\"mission not found\\n\");\n\t\t\t\tprocess.exitCode = 1;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tconst runtime = initializeRuntime(doc);\n\t\t\tconst actions = reconcileAfterReboot(doc, runtime, /*processStillExists*/ false);\n\t\t\tconst payload = {\n\t\t\t\tmissionId: id,\n\t\t\t\tpreview,\n\t\t\t\tactions: actions.actions,\n\t\t\t\tprocessRecordedMissing: actions.processRecordedMissing,\n\t\t\t};\n\t\t\tif (json) process.stdout.write(`${JSON.stringify(payload, null, 2)}\\n`);\n\t\t\telse {\n\t\t\t\tprocess.stdout.write(`${preview ? \"preview\" : \"reconcile\"} ${id}:\\n`);\n\t\t\t\tfor (const a of actions.actions) process.stdout.write(`  ${a}\\n`);\n\t\t\t\tif (preview) process.stdout.write(\"  (no changes applied)\\n\");\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\n\t\tdefault:\n\t\t\tprocess.stdout.write(`${usage()}\\n`);\n\t\t\treturn true;\n\t}\n}\n"]}