{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../../src/core/routing/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA6CH,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAyD3E","sourcesContent":["/**\n * Routing CLI surfaces.\n *\n * Provides `jensen routing <subcommand>` covering decision, explain, replay,\n * compare, candidates, features, evidence, shadow, policy lifecycle, drift and\n * `jensen doctor routing`. Read-only diagnostic commands have no execution\n * effects. Supports `--json`.\n */\n\nimport { BASELINE_RULES, baselineSelect } from \"./baseline.js\";\nimport { activePolicyStatus, fixtureEvidence, generatePolicyFromFixture } from \"./cli-helpers.js\";\nimport { checkDriftHealth, type DriftDimension, detectDrift } from \"./drift.js\";\nimport { decide, replayDecision } from \"./engine.js\";\nimport { extractFeatures } from \"./features.js\";\nimport { runRoutingSelfProbe } from \"./probe.js\";\nimport { comparePolicies, promotePolicy, rollbackPolicy, validatePromotionGates } from \"./promotion.js\";\nimport { listDecisions, listPolicies, listShadowDecisions, readPolicy } from \"./store.js\";\nimport type { OrchestrationDecision } from \"./types.js\";\n\nconst DRIFT_DIMENSIONS: DriftDimension[] = [\n\t\"quality\",\n\t\"cost\",\n\t\"latency\",\n\t\"failure_cluster\",\n\t\"retrieval\",\n\t\"task_distribution\",\n\t\"flakiness\",\n\t\"policy_selection\",\n];\n\nfunction output(value: unknown): void {\n\tconsole.log(JSON.stringify(value, null, 2));\n}\n\nfunction usage(): string {\n\treturn [\n\t\t\"Usage: jensen routing <command> [args]\",\n\t\t\"  status\",\n\t\t\"  decide --task <text> [--json]\",\n\t\t\"  explain <decision-id>\",\n\t\t\"  replay <decision-id>\",\n\t\t\"  compare <decision-a> <decision-b>\",\n\t\t\"  candidates\",\n\t\t\"  features\",\n\t\t\"  evidence\",\n\t\t\"  evaluate\",\n\t\t\"  shadow status|compare\",\n\t\t\"  policy list|inspect <id>|validate <id>|compare <a> <b>|generate|promote <id>|rollback <id>\",\n\t\t\"  drift status|inspect [--dimension <d>]\",\n\t].join(\"\\n\");\n}\n\nexport async function handleRoutingCommand(args: string[]): Promise<boolean> {\n\tif (args[0] === \"doctor\" && args[1] === \"routing\") {\n\t\toutput(routingDoctor());\n\t\treturn true;\n\t}\n\tif (args[0] !== \"routing\") return false;\n\tconst sub = args[1];\n\tconst _json = args.includes(\"--json\");\n\ttry {\n\t\tswitch (sub) {\n\t\t\tcase \"status\":\n\t\t\t\toutput(routingStatus());\n\t\t\t\treturn true;\n\t\t\tcase \"decide\":\n\t\t\t\treturn handleDecide(args);\n\t\t\tcase \"explain\":\n\t\t\t\treturn handleExplain(args[2]);\n\t\t\tcase \"replay\":\n\t\t\t\treturn handleReplay(args[2]);\n\t\t\tcase \"compare\":\n\t\t\t\treturn handleCompare(args[2], args[3]);\n\t\t\tcase \"candidates\":\n\t\t\t\toutput(candidateOverview());\n\t\t\t\treturn true;\n\t\t\tcase \"features\":\n\t\t\t\toutput(featureOverview());\n\t\t\t\treturn true;\n\t\t\tcase \"evidence\":\n\t\t\t\toutput({ fixtureEvidence: fixtureEvidence(), evidenceCount: Object.keys(fixtureEvidence()).length });\n\t\t\t\treturn true;\n\t\t\tcase \"evaluate\": {\n\t\t\t\tconst result = runRoutingSelfProbe();\n\t\t\t\toutput(result);\n\t\t\t\tif (!result.passed) process.exitCode = 1;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tcase \"shadow\":\n\t\t\t\treturn handleShadow(args.slice(2));\n\t\t\tcase \"policy\":\n\t\t\t\treturn handlePolicy(args.slice(2));\n\t\t\tcase \"drift\":\n\t\t\t\treturn handleDrift(args.slice(2));\n\t\t\tcase undefined:\n\t\t\t\tconsole.error(chalkRed(usage()));\n\t\t\t\tprocess.exitCode = 1;\n\t\t\t\treturn true;\n\t\t\tdefault:\n\t\t\t\tconsole.error(chalkRed(`Unknown routing subcommand \"${sub}\".`));\n\t\t\t\tconsole.error(usage());\n\t\t\t\tprocess.exitCode = 1;\n\t\t\t\treturn true;\n\t\t}\n\t} catch (error) {\n\t\tconsole.error(error instanceof Error ? error.message : String(error));\n\t\tprocess.exitCode = 2;\n\t\treturn true;\n\t}\n}\n\nfunction chalkRed(s: string): string {\n\treturn s; // plain (no chalk dependency needed here; keep minimal)\n}\n\nfunction routingStatus(): unknown {\n\tconst active = activePolicyStatus();\n\tconst decisions = listDecisions(10);\n\tconst shadow = listShadowDecisions(10);\n\tconst drift = checkDriftHealth();\n\treturn {\n\t\tschemaVersion: 1,\n\t\tactivePolicy: active,\n\t\tdecisionsCount: decisions.length,\n\t\trecentDecisions: decisions.map((d) => ({\n\t\t\tdecisionId: d.decisionId,\n\t\t\tpolicyId: d.policyId,\n\t\t\tpolicyVersion: d.policyVersion,\n\t\t\tselectedCandidateId: d.selectedCandidateId,\n\t\t\tconfidence: d.confidence,\n\t\t\tselectedAt: d.selectedAt,\n\t\t})),\n\t\tshadowCount: shadow.length,\n\t\tdriftHealth: drift,\n\t\tdriftDimCount: DRIFT_DIMENSIONS.length,\n\t};\n}\n\nfunction handleDecide(args: string[]): boolean {\n\tconst taskArg = args.indexOf(\"--task\");\n\tif (taskArg === -1) {\n\t\tconsole.error(chalkRed(\"routing decide requires --task <text>\"));\n\t\tprocess.exitCode = 1;\n\t\treturn true;\n\t}\n\tconst task = args[taskArg + 1];\n\tif (!task) {\n\t\tconsole.error(chalkRed(\"routing decide requires --task <text>\"));\n\t\tprocess.exitCode = 1;\n\t\treturn true;\n\t}\n\tconst operatorOverrideId = option(args, \"--operator-override\");\n\tconst policyId = option(args, \"--policy\");\n\tconst { decision, selectedCandidate } = decide({\n\t\ttask,\n\t\tpolicyContext: policyId ? loadPolicyContext(policyId) : undefined,\n\t\toperatorOverride: operatorOverrideId\n\t\t\t? { authorizedBy: \"cli\", candidateId: operatorOverrideId, reason: \"operator override via CLI\" }\n\t\t\t: undefined,\n\t\tevidence: fixtureEvidence(),\n\t});\n\tif (args.includes(\"--json\")) {\n\t\toutput({ decision, selectedCandidate: selectedCandidate ? summarizeCandidate(selectedCandidate) : undefined });\n\t} else {\n\t\toutput({\n\t\t\tdecisionId: decision.decisionId,\n\t\t\tpolicyId: decision.policyId,\n\t\t\tpolicyVersion: decision.policyVersion,\n\t\t\ttask: task,\n\t\t\tselectedCandidateId: decision.selectedCandidateId,\n\t\t\tconfidence: decision.confidence,\n\t\t\treasonCodes: decision.reasonCodes,\n\t\t\tselectedCandidate: selectedCandidate ? summarizeCandidate(selectedCandidate) : undefined,\n\t\t});\n\t}\n\treturn true;\n}\n\nfunction handleExplain(decisionId: string | undefined): boolean {\n\tif (!decisionId) {\n\t\tusageError(\"routing explain requires <decision-id>\");\n\t\treturn true;\n\t}\n\tconst d = replayDecision(decisionId);\n\tif (!d) {\n\t\tconsole.error(chalkRed(`No decision found for ${decisionId}`));\n\t\tprocess.exitCode = 1;\n\t\treturn true;\n\t}\n\toutput({ decision: d, explanation: explainDecision(d) });\n\treturn true;\n}\n\nfunction handleReplay(decisionId: string | undefined): boolean {\n\tif (!decisionId) {\n\t\tusageError(\"routing replay requires <decision-id>\");\n\t\treturn true;\n\t}\n\tconst d = replayDecision(decisionId);\n\tif (!d) {\n\t\tconsole.error(chalkRed(`No decision found for ${decisionId}; replay requires its durable record`));\n\t\tprocess.exitCode = 1;\n\t\treturn true;\n\t}\n\toutput({ replay: \"zero-effect\", decision: d });\n\treturn true;\n}\n\nfunction handleCompare(a: string | undefined, b: string | undefined): boolean {\n\tif (!a || !b) {\n\t\tusageError(\"routing compare requires <decision-a> <decision-b>\");\n\t\treturn true;\n\t}\n\tconst da = replayDecision(a);\n\tconst db = replayDecision(b);\n\toutput({ left: da, right: db, compare: compareDecisions(da, db) });\n\treturn true;\n}\n\nfunction handleShadow(args: string[]): boolean {\n\tconst cmd = args[0];\n\tif (cmd === \"status\") {\n\t\toutput({\n\t\t\tshadowDecisions: listShadowDecisions(10).map((s) => ({\n\t\t\t\tshadowId: s.shadowId,\n\t\t\t\tshadowPolicyId: s.shadowPolicyId,\n\t\t\t\tshadowCandidateId: s.shadowCandidateId,\n\t\t\t\twouldSelectDifferent: s.wouldSelectDifferent,\n\t\t\t\trecordedAt: s.recordedAt,\n\t\t\t})),\n\t\t\tzeroEffects: true,\n\t\t});\n\t\treturn true;\n\t}\n\tif (cmd === \"compare\") {\n\t\tconst list = listShadowDecisions(10);\n\t\toutput({ shadowComparisons: list });\n\t\treturn true;\n\t}\n\tusageError(\"routing shadow status|compare\");\n\treturn true;\n}\n\nfunction handlePolicy(args: string[]): boolean {\n\tconst cmd = args[0];\n\tswitch (cmd) {\n\t\tcase \"list\":\n\t\t\toutput({\n\t\t\t\tpolicies: listPolicies().map((p) => ({\n\t\t\t\t\tpolicyId: p.policyId,\n\t\t\t\t\tpolicyVersion: p.policyVersion,\n\t\t\t\t\tstatus: p.status,\n\t\t\t\t\tsourceDatasetHash: p.sourceDatasetHash,\n\t\t\t\t\thash: p.hash,\n\t\t\t\t})),\n\t\t\t});\n\t\t\treturn true;\n\t\tcase \"inspect\": {\n\t\t\tconst p = readPolicy(args[1] ?? \"\");\n\t\t\tif (!p) {\n\t\t\t\tconsole.error(chalkRed(`No policy ${args[1]}`));\n\t\t\t\tprocess.exitCode = 1;\n\t\t\t} else output(p);\n\t\t\treturn true;\n\t\t}\n\t\tcase \"validate\": {\n\t\t\tconst p = readPolicy(args[1] ?? \"\");\n\t\t\tif (!p) {\n\t\t\t\tconsole.error(chalkRed(`No policy ${args[1]}`));\n\t\t\t\tprocess.exitCode = 1;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tconst gate = validatePromotionGates(p, fixtureEvidence(), {\n\t\t\t\tsafetyFloor: 0.5,\n\t\t\t\tcorrectnessFloor: 0.5,\n\t\t\t\tflakinessCeiling: 0.3,\n\t\t\t\trequiredScenarioPack: \"routing\",\n\t\t\t\toperatorAuthorized: true,\n\t\t\t});\n\t\t\toutput({ policyId: p.policyId, valid: gate.passed, reasonCodes: gate.reasonCodes });\n\t\t\treturn true;\n\t\t}\n\t\tcase \"compare\":\n\t\t\toutput(comparePolicies(readPolicy(args[1] ?? \"\")!, readPolicy(args[2] ?? \"\")!));\n\t\t\treturn true;\n\t\tcase \"generate\":\n\t\t\toutput(generatePolicyFromFixture());\n\t\t\treturn true;\n\t\tcase \"promote\": {\n\t\t\tconst res = promotePolicy(args[1] ?? \"\", \"operator\", fixtureEvidence(), {\n\t\t\t\tsafetyFloor: 0.5,\n\t\t\t\tcorrectnessFloor: 0.5,\n\t\t\t\tflakinessCeiling: 0.3,\n\t\t\t\trequiredScenarioPack: \"routing\",\n\t\t\t\toperatorAuthorized: true,\n\t\t\t});\n\t\t\toutput(res);\n\t\t\treturn true;\n\t\t}\n\t\tcase \"rollback\": {\n\t\t\tconst res = rollbackPolicy(args[1] ?? \"\", \"operator\");\n\t\t\toutput(res);\n\t\t\treturn true;\n\t\t}\n\t\tdefault:\n\t\t\tusageError(\"routing policy list|inspect|validate|compare|generate|promote|rollback\");\n\t\t\treturn true;\n\t}\n}\n\nfunction handleDrift(args: string[]): boolean {\n\tconst cmd = args[0];\n\tif (cmd === \"status\") {\n\t\tconst results = DRIFT_DIMENSIONS.map((_d) => checkDriftHealth());\n\t\toutput({ driftStatus: results, dimensions: DRIFT_DIMENSIONS });\n\t\treturn true;\n\t}\n\tif (cmd === \"inspect\") {\n\t\tconst dim = option(args, \"--dimension\") as DriftDimension | undefined;\n\t\tconst dimension = dim && (DRIFT_DIMENSIONS as string[]).includes(dim) ? dim : \"quality\";\n\t\tconst result = detectDrift(dimension, 0.1);\n\t\toutput(result);\n\t\treturn true;\n\t}\n\tusageError(\"routing drift status|inspect\");\n\treturn true;\n}\n\nfunction routingDoctor(): unknown {\n\tconst active = activePolicyStatus();\n\tconst drift = checkDriftHealth();\n\tconst policies = listPolicies();\n\tconst checks = [\n\t\t{\n\t\t\tname: \"active-policy\",\n\t\t\tstatus: active ? \"pass\" : \"fail\",\n\t\t\tmessage: active ? `policy ${active.policyId} v${active.policyVersion}` : \"no active policy\",\n\t\t},\n\t\t{ name: \"policy-schema\", status: \"pass\", message: `${policies.length} policy artifacts present` },\n\t\t{ name: \"candidate-registries\", status: \"pass\", message: \"candidate generation from canonical registries\" },\n\t\t{\n\t\t\tname: \"drift-detector\",\n\t\t\tstatus: drift.ok ? \"pass\" : \"fail\",\n\t\t\tmessage: drift.ok ? \"detectors healthy\" : drift.reasons.join(\"; \"),\n\t\t},\n\t\t{ name: \"evidence-baseline\", status: \"pass\", message: \"deterministic baseline available\" },\n\t];\n\tconst failed = checks.some((c) => c.status === \"fail\");\n\t// A fail returns authoritative nonzero exit.\n\tif (failed) process.exitCode = 1;\n\treturn { checks, passed: !failed };\n}\n\nfunction loadPolicyContext(policyId: string):\n\t| {\n\t\t\tpolicyId: string;\n\t\t\tpolicyVersion: number;\n\t\t\tpreferences?: { candidateId: string; quality: number }[];\n\t\t\tdominatedCandidateIds?: string[];\n\t  }\n\t| undefined {\n\tconst p = readPolicy(policyId);\n\tif (!p) return undefined;\n\treturn {\n\t\tpolicyId: p.policyId,\n\t\tpolicyVersion: p.policyVersion,\n\t\tpreferences: p.preferences ?? undefined,\n\t\tdominatedCandidateIds: p.dominatedCandidateIds,\n\t};\n}\n\nfunction explainDecision(d: OrchestrationDecision): unknown {\n\treturn {\n\t\tfeatures: d.features,\n\t\tcandidateRejections: d.rejections,\n\t\tscores: d.candidates,\n\t\tselectedCandidateId: d.selectedCandidateId,\n\t\tconfidence: d.confidence,\n\t\treasonCodes: d.reasonCodes,\n\t\tprovenance: {\n\t\t\tpolicyId: d.policyId,\n\t\t\tpolicyVersion: d.policyVersion,\n\t\t\tdecisionId: d.decisionId,\n\t\t\trunId: d.runId,\n\t\t},\n\t};\n}\n\nfunction compareDecisions(a: OrchestrationDecision | undefined, b: OrchestrationDecision | undefined): unknown {\n\tif (!a || !b) return { comparable: false, reason: \"missing decision\" };\n\treturn {\n\t\tcomparable: true,\n\t\tsameSelected: a.selectedCandidateId === b.selectedCandidateId,\n\t\taSelected: a.selectedCandidateId,\n\t\tbSelected: b.selectedCandidateId,\n\t\taConfidence: a.confidence,\n\t\tbConfidence: b.confidence,\n\t};\n}\n\nfunction candidateOverview(): unknown {\n\treturn {\n\t\tcandidates: baselineRulesOverview(),\n\t\tbaseline: BASELINE_RULES.map((r) => ({ ruleId: r.ruleId, precedence: r.precedence })),\n\t};\n}\n\nfunction baselineRulesOverview(): unknown[] {\n\tconst samples = [\n\t\t\"Find the symbol Foo\",\n\t\t\"Fix the off-by-one bug in parser.cpp\",\n\t\t\"Release version 1.9.0 across seven packages\",\n\t];\n\treturn samples.map((t) => {\n\t\tconst f = extractFeatures(t);\n\t\tconst b = baselineSelect(f);\n\t\treturn {\n\t\t\ttask: t,\n\t\t\truleId: b.ruleId,\n\t\t\tcandidateId: b.candidate.candidateId,\n\t\t\ttopology: b.candidate.executionTopology,\n\t\t\tbudgetClass: b.candidate.budgetClass,\n\t\t\tretrieval: b.candidate.retrievalPolicy,\n\t\t};\n\t});\n}\n\nfunction featureOverview(): unknown {\n\treturn {\n\t\tschemaVersion: 1,\n\t\tsampleFeature: extractFeatures(\"Fix a cross-platform release bug in the Windows builder\"),\n\t};\n}\n\nfunction summarizeCandidate(c: {\n\tproviderProfile: string;\n\tconfiguredModel: string;\n\texecutionTopology: string;\n\tretrievalPolicy: string;\n\tbudgetClass: string;\n}): unknown {\n\treturn {\n\t\tproviderProfile: c.providerProfile,\n\t\tconfiguredModel: c.configuredModel,\n\t\texecutionTopology: c.executionTopology,\n\t\tretrievalPolicy: c.retrievalPolicy,\n\t\tbudgetClass: c.budgetClass,\n\t};\n}\n\nfunction option(args: string[], name: string): string | undefined {\n\tconst i = args.indexOf(name);\n\treturn i === -1 ? undefined : args[i + 1];\n}\n\nfunction usageError(msg: string): void {\n\tconsole.error(chalkRed(msg));\n\tconsole.error(usage());\n\tprocess.exitCode = 1;\n}\n"]}