@mneme-ai/core 2.85.0 → 2.87.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,192 @@
1
+ /**
2
+ * v2.86.0 — HEPHAESTUS (Ἥφαιστος, the smith god) · GEPHYRA's OS lane.
3
+ *
4
+ * NOT "an AI that runs commands" (that's Grok Computer / Warp / Claude Code —
5
+ * crowded, we'd lose). HEPHAESTUS is the neutral SUBSTRATE a shell + AI run ON:
6
+ * every command that wants to touch the machine first CROSSES it — gets risk-
7
+ * classified, policy-gated, optionally judged by a cross-vendor tribunal, has its
8
+ * output immune-scanned, and is recorded as a signed, tamper-evident crossing
9
+ * (who: human vs which AI). It is GEPHYRA's claim-crossing, applied to COMMANDS.
10
+ *
11
+ * DECISION-FIRST, EXECUTION-OPTIONAL: the value is the SIGNED VERDICT (ALLOW /
12
+ * NEEDS_COSIGN / BLOCK + reasons + tribunal + provenance), not the runner. The
13
+ * gate is pure logic → deterministic + identical across every OS. Execution is a
14
+ * separate, guarded, opt-in step.
15
+ *
16
+ * THE SAFETY INVARIANT (pinned in tests): a DESTRUCTIVE command can NEVER be ALLOW
17
+ * without an explicit co-sign. A fox cannot guard its own henhouse — so for
18
+ * destructive ops a cross-vendor tribunal (Grok+Gemini+Claude, UNcorrelated errors)
19
+ * judges, and Mneme — owned by no vendor — is the only legitimate convener.
20
+ *
21
+ * Composes flight_recorder (the black box) + notary (the stamp) + mesh_immune
22
+ * (injection scan). Never throws — every organ degrades gracefully.
23
+ */
24
+ import { type MeshThreat } from "../mesh_immune/index.js";
25
+ import { type NotaryReceipt } from "../notary/index.js";
26
+ export type CommandRisk = "read" | "write" | "destructive";
27
+ export type Disposition = "ALLOW" | "NEEDS_COSIGN" | "BLOCK";
28
+ export type TribunalConsensus = "safe" | "danger" | "split";
29
+ export interface RiskClassification {
30
+ risk: CommandRisk;
31
+ signals: string[];
32
+ }
33
+ /**
34
+ * Classify a command's blast radius. Destructive wins, then write, then read.
35
+ * UNKNOWN defaults to "write" (conservative — gets policy-gated, never silently
36
+ * treated as harmless). Deterministic + OS-agnostic (pure pattern logic).
37
+ */
38
+ export declare function classifyCommandRisk(command: string): RiskClassification;
39
+ export interface Policy {
40
+ /** Destructive commands require an explicit human co-sign. Default true. */
41
+ destructiveNeedsCosign: boolean;
42
+ /** On hosts tagged prod, anything beyond read-only is blocked. Default false. */
43
+ prodReadOnly: boolean;
44
+ }
45
+ export declare const DEFAULT_POLICY: Policy;
46
+ /** Parse a one-time, plain-language policy ("destructive must co-sign, prod is read-only"). */
47
+ export declare function parsePolicy(text: string): Policy;
48
+ export interface CrossCommandInput {
49
+ command: string;
50
+ /** Who is asking — "human" or an AI agent id (claude/grok/gemini/cursor/...). */
51
+ agent: string;
52
+ /** Optional host/context tag (e.g. "prod-db-1"). "prod" substring triggers prodReadOnly. */
53
+ host?: string;
54
+ /** A human co-sign was provided out-of-band for a destructive op. */
55
+ cosigned?: boolean;
56
+ }
57
+ export interface CrossCommandDeps {
58
+ policy?: Policy;
59
+ /** The cross-vendor TRIBUNAL — judge a destructive command via independent
60
+ * vendors (e.g. via diff_arena adapters). Mneme is the neutral convener.
61
+ * Returns each vendor's verdict + the consensus. Pluggable; CLI/MCP wire it. */
62
+ tribunal?: (command: string, risk: CommandRisk) => Promise<{
63
+ verdicts: Array<{
64
+ vendor: string;
65
+ verdict: "safe" | "danger";
66
+ }>;
67
+ consensus: TribunalConsensus;
68
+ }>;
69
+ now?: number;
70
+ }
71
+ export interface CrossCommandResult {
72
+ disposition: Disposition;
73
+ risk: CommandRisk;
74
+ signals: string[];
75
+ reasons: string[];
76
+ agent: string;
77
+ host: string | null;
78
+ /** provenance: was the requester a human or an AI? */
79
+ origin: "human" | "ai";
80
+ threats: MeshThreat[];
81
+ tribunal?: {
82
+ verdicts: Array<{
83
+ vendor: string;
84
+ verdict: "safe" | "danger";
85
+ }>;
86
+ consensus: TribunalConsensus;
87
+ };
88
+ /** The tamper-evident signed crossing (flight-recorder frame's NOTARY receipt). */
89
+ receipt: NotaryReceipt | null;
90
+ degraded: string[];
91
+ }
92
+ /**
93
+ * Cross a command into the OS: classify → immune-scan → policy/tribunal gate →
94
+ * record a signed provenance frame → return the verdict. NEVER executes here and
95
+ * NEVER throws. The SAFETY INVARIANT holds: destructive ⇒ never ALLOW without a
96
+ * co-sign (or a unanimous-safe tribunal under a no-cosign policy).
97
+ */
98
+ export declare function crossCommand(repoRoot: string, input: CrossCommandInput, deps?: CrossCommandDeps): Promise<CrossCommandResult>;
99
+ export type Platform = "linux" | "macos" | "powershell";
100
+ export declare function currentPlatform(): Platform;
101
+ /** Translate a canonical intent to the right shell for a platform (default: this OS). */
102
+ export declare function polyglot(intent: string, platform?: Platform): {
103
+ intent: string;
104
+ platform: Platform;
105
+ command: string;
106
+ } | null;
107
+ export declare function polyglotIntents(): string[];
108
+ export declare function scanCommandOutput(output: string): {
109
+ clean: boolean;
110
+ threats: MeshThreat[];
111
+ };
112
+ export interface ExecResult {
113
+ ran: boolean;
114
+ reason: string;
115
+ exitCode: number | null;
116
+ stdout: string;
117
+ stderr: string;
118
+ outputThreats: MeshThreat[];
119
+ receipt: NotaryReceipt | null;
120
+ }
121
+ /**
122
+ * Execute a command ONLY if its crossing verdict is ALLOW. Captures stdout/stderr/
123
+ * exit, immune-scans the output (so the AI isn't pwned by what it reads), and
124
+ * records the result. Refuses anything not ALLOW. Cross-platform (uses the OS shell).
125
+ */
126
+ export declare function executeGuarded(repoRoot: string, input: {
127
+ command: string;
128
+ agent: string;
129
+ disposition: Disposition;
130
+ timeoutMs?: number;
131
+ }): Promise<ExecResult>;
132
+ export interface HephStatus {
133
+ crossings: number;
134
+ allowed: number;
135
+ needsCosign: number;
136
+ blocked: number;
137
+ chainValid: boolean;
138
+ }
139
+ /** Live HEPHAESTUS status from the shared flight-recorder black box. */
140
+ export declare function hephaestusStatus(repoRoot: string): HephStatus;
141
+ /** Verify a HEPHAESTUS crossing/execution receipt offline. */
142
+ export declare function verifyHephReceipt(receipt: unknown): {
143
+ valid: boolean;
144
+ reason: string;
145
+ };
146
+ import type { VendorAdapter } from "../diff_arena/adapters.js";
147
+ /** A tribunal juror = a diff_arena vendor adapter (use mockAdapter/httpAdapter/cliAdapter). */
148
+ export type TribunalVendor = VendorAdapter;
149
+ /**
150
+ * Build a real cross-vendor TRIBUNAL backed by diff_arena. Each vendor judges the
151
+ * command INDEPENDENTLY ("safe"/"danger") — errors are UNcorrelated across vendors,
152
+ * so the panel catches a blind spot a single-vendor ensemble can't. Mneme is the
153
+ * neutral convener (no vendor owns the judge). With no live vendors configured it
154
+ * fails SAFE (consensus "danger" ⇒ a destructive op is blocked) rather than waving
155
+ * it through. Wire real vendors via diff_arena's httpAdapter/cliAdapter (API keys);
156
+ * tests/offline pass deterministic vendor stubs.
157
+ */
158
+ export declare function makeDiffArenaTribunal(repoRoot: string, opts?: {
159
+ vendors?: TribunalVendor[];
160
+ }): NonNullable<CrossCommandDeps["tribunal"]>;
161
+ export interface Reversibility {
162
+ reversible: boolean;
163
+ effects: string[];
164
+ irreversibleWarnings: string[];
165
+ }
166
+ /** Deterministically classify whether a command's effect can be undone. */
167
+ export declare function classifyReversibility(command: string): Reversibility;
168
+ export interface PreflightResult {
169
+ command: string;
170
+ risk: CommandRisk;
171
+ reversible: boolean;
172
+ effects: string[];
173
+ irreversibleWarnings: string[];
174
+ /** Optional cross-vendor effect prediction (free text). */
175
+ prediction?: string;
176
+ /** Signed pre-mortem receipt — provable "we previewed + warned before crossing". */
177
+ receipt: NotaryReceipt | null;
178
+ }
179
+ /**
180
+ * 🔮 Pre-flight a command BEFORE crossing: predict blast radius + flag what cannot
181
+ * be undone + (optionally) ask a panel to predict the effect — and SIGN the
182
+ * pre-mortem. Never executes. The proof that the irreversible was foreseen + warned.
183
+ */
184
+ export declare function preflightCommand(repoRoot: string, input: {
185
+ command: string;
186
+ agent: string;
187
+ }, deps?: {
188
+ predict?: (command: string) => Promise<string>;
189
+ }): Promise<PreflightResult>;
190
+ /** Materialise a tribunal panel from env-present vendor keys (zero keys ⇒ [] ⇒ fail-safe BLOCK). */
191
+ export declare function tribunalVendorsFromEnv(): Promise<TribunalVendor[]>;
192
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hephaestus/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,425 @@
1
+ /**
2
+ * v2.86.0 — HEPHAESTUS (Ἥφαιστος, the smith god) · GEPHYRA's OS lane.
3
+ *
4
+ * NOT "an AI that runs commands" (that's Grok Computer / Warp / Claude Code —
5
+ * crowded, we'd lose). HEPHAESTUS is the neutral SUBSTRATE a shell + AI run ON:
6
+ * every command that wants to touch the machine first CROSSES it — gets risk-
7
+ * classified, policy-gated, optionally judged by a cross-vendor tribunal, has its
8
+ * output immune-scanned, and is recorded as a signed, tamper-evident crossing
9
+ * (who: human vs which AI). It is GEPHYRA's claim-crossing, applied to COMMANDS.
10
+ *
11
+ * DECISION-FIRST, EXECUTION-OPTIONAL: the value is the SIGNED VERDICT (ALLOW /
12
+ * NEEDS_COSIGN / BLOCK + reasons + tribunal + provenance), not the runner. The
13
+ * gate is pure logic → deterministic + identical across every OS. Execution is a
14
+ * separate, guarded, opt-in step.
15
+ *
16
+ * THE SAFETY INVARIANT (pinned in tests): a DESTRUCTIVE command can NEVER be ALLOW
17
+ * without an explicit co-sign. A fox cannot guard its own henhouse — so for
18
+ * destructive ops a cross-vendor tribunal (Grok+Gemini+Claude, UNcorrelated errors)
19
+ * judges, and Mneme — owned by no vendor — is the only legitimate convener.
20
+ *
21
+ * Composes flight_recorder (the black box) + notary (the stamp) + mesh_immune
22
+ * (injection scan). Never throws — every organ degrades gracefully.
23
+ */
24
+ import { record, replay, readCdr } from "../flight_recorder/index.js";
25
+ import { scanMessage, quarantineDecision } from "../mesh_immune/index.js";
26
+ import { verifyReceipt } from "../notary/index.js";
27
+ // Order matters: destructive patterns win over write/read.
28
+ const DESTRUCTIVE = [
29
+ [/\brm\s+(-[a-z]*r[a-z]*f|-[a-z]*f[a-z]*r|-rf|-fr)\b/i, "rm -rf"],
30
+ [/\brm\s+-[a-z]*r\b/i, "recursive rm"],
31
+ [/\b(rmdir|rd)\s+\/s\b/i, "rmdir /s"],
32
+ [/\bdel\s+\/[a-z]*[qsf]/i, "del /q|/s|/f"],
33
+ [/\b(mkfs|fdisk|parted|wipefs|diskpart)\b/i, "disk format/partition"],
34
+ [/\bdd\s+if=/i, "dd"],
35
+ [/>\s*\/dev\/(sd|nvme|disk)/i, "write to raw disk"],
36
+ [/\bformat\s+[a-z]:/i, "format drive"],
37
+ [/\bkubectl\s+delete\b/i, "kubectl delete"],
38
+ [/\bhelm\s+(delete|uninstall)\b/i, "helm delete"],
39
+ [/\bterraform\s+destroy\b/i, "terraform destroy"],
40
+ [/\bdocker\s+(system\s+prune|rm\s+-f|volume\s+rm)\b/i, "docker destructive"],
41
+ [/\bdrop\s+(table|database|schema|index|view|user|role)\b/i, "SQL drop"],
42
+ [/\btruncate\s+(table\s+)?["`[]?\w/i, "SQL truncate"],
43
+ [/\bdelete\s+from\b(?![^;]*\bwhere\b)/i, "SQL delete without where"],
44
+ [/\bgit\s+(push\s+(-f|--force)|reset\s+--hard|clean\s+-[a-z]*f)/i, "git force/reset/clean"],
45
+ [/\b(shutdown|reboot|halt|poweroff|Stop-Computer|Restart-Computer)\b/i, "power state"],
46
+ [/\b(systemctl|service)\s+(stop|disable|mask)\b/i, "stop/disable service"],
47
+ [/\bchmod\s+-R\s+777\b/i, "chmod -R 777"],
48
+ [/:\s*\(\s*\)\s*\{.*\|.*&\s*\}\s*;/i, "fork bomb"],
49
+ [/\b(Remove-Item|ri|rm)\b.*-Recurse.*-Force|\b(Remove-Item|ri)\b.*-Force.*-Recurse/i, "Remove-Item -Recurse -Force"],
50
+ ];
51
+ const WRITE = [
52
+ [/\b(apt|apt-get|yum|dnf|brew|npm|pnpm|yarn|pip|pip3|cargo|gem)\s+(install|add|i|update|upgrade)\b/i, "package install"],
53
+ [/\b(mv|cp|mkdir|touch|ln|chmod|chown|tee|truncate)\b/i, "filesystem write"],
54
+ [/>>?\s*[^&|]/, "output redirect"],
55
+ [/\bsed\s+-i\b|\bperl\s+-i\b/i, "in-place edit"],
56
+ [/\bgit\s+(commit|merge|rebase|checkout|stash|add|tag)\b/i, "git mutate"],
57
+ [/\b(Set-|New-|Add-|Out-File|Set-Content|Add-Content)\b/, "PowerShell write cmdlet"],
58
+ [/\b(docker\s+(run|build|start)|kubectl\s+(apply|create|patch|scale)|systemctl\s+(start|restart|enable))\b/i, "deploy/start"],
59
+ [/\b(echo|printf)\b.*>/, "write via echo"],
60
+ ];
61
+ const READ = [
62
+ [/^\s*(ls|dir|cat|bat|head|tail|less|more|grep|rg|find|fd|wc|ps|top|htop|df|du|free|ss|netstat|lsof|ip|ifconfig|ping|traceroute|whoami|id|pwd|cd|which|where|env|printenv|uname|hostname|date|uptime|history|stat|file|tree|jq|awk|sort|uniq|diff)\b/i, "read tool"],
63
+ [/\b(kubectl\s+(get|describe|logs|top)|docker\s+(ps|images|logs|inspect)|systemctl\s+status|git\s+(status|log|diff|show|branch))\b/i, "read subcommand"],
64
+ [/\b(Get-|Test-|Measure-|Select-|Where-|Format-)\b/, "PowerShell read cmdlet"],
65
+ [/^\s*(echo|printf)\b(?![^|]*>)/, "echo (no redirect)"],
66
+ ];
67
+ /**
68
+ * Classify a command's blast radius. Destructive wins, then write, then read.
69
+ * UNKNOWN defaults to "write" (conservative — gets policy-gated, never silently
70
+ * treated as harmless). Deterministic + OS-agnostic (pure pattern logic).
71
+ */
72
+ export function classifyCommandRisk(command) {
73
+ const c = String(command ?? "");
74
+ const signals = [];
75
+ for (const [re, label] of DESTRUCTIVE)
76
+ if (re.test(c))
77
+ signals.push(label);
78
+ if (signals.length)
79
+ return { risk: "destructive", signals };
80
+ for (const [re, label] of WRITE)
81
+ if (re.test(c))
82
+ signals.push(label);
83
+ if (signals.length)
84
+ return { risk: "write", signals };
85
+ for (const [re, label] of READ)
86
+ if (re.test(c))
87
+ signals.push(label);
88
+ if (signals.length)
89
+ return { risk: "read", signals };
90
+ return { risk: "write", signals: ["unknown command — defaulting to write (gated)"] };
91
+ }
92
+ export const DEFAULT_POLICY = { destructiveNeedsCosign: true, prodReadOnly: false };
93
+ /** Parse a one-time, plain-language policy ("destructive must co-sign, prod is read-only"). */
94
+ export function parsePolicy(text) {
95
+ const t = String(text ?? "").toLowerCase();
96
+ const mentionsDestructive = /destructive|dangerous|rm|delete|drop/.test(t);
97
+ const mentionsCosign = /co-?sign|cosign|human|approval|confirm|two-person|2-person/.test(t);
98
+ const noCosign = /no\s+co-?sign|without\s+co-?sign|don'?t\s+(require\s+)?co-?sign/.test(t);
99
+ const prodReadOnly = /prod[a-z]*\s*(is\s*)?(read[- ]?only|ro\b|no\s+writes?)/.test(t) || /read[- ]?only\s+(on\s+)?prod/.test(t);
100
+ return {
101
+ destructiveNeedsCosign: noCosign ? false : (mentionsDestructive && mentionsCosign ? true : DEFAULT_POLICY.destructiveNeedsCosign),
102
+ prodReadOnly: prodReadOnly || DEFAULT_POLICY.prodReadOnly,
103
+ };
104
+ }
105
+ /**
106
+ * Cross a command into the OS: classify → immune-scan → policy/tribunal gate →
107
+ * record a signed provenance frame → return the verdict. NEVER executes here and
108
+ * NEVER throws. The SAFETY INVARIANT holds: destructive ⇒ never ALLOW without a
109
+ * co-sign (or a unanimous-safe tribunal under a no-cosign policy).
110
+ */
111
+ export async function crossCommand(repoRoot, input, deps = {}) {
112
+ const degraded = [];
113
+ const command = String(input.command ?? "");
114
+ const agent = String(input.agent ?? "unknown");
115
+ const host = input.host ? String(input.host) : null;
116
+ const origin = /^human$|^user$/i.test(agent) ? "human" : "ai";
117
+ const policy = deps.policy ?? DEFAULT_POLICY;
118
+ const reasons = [];
119
+ // 1. IMMUNE — injection hidden in the command itself.
120
+ let threats = [];
121
+ try {
122
+ const scan = scanMessage(command);
123
+ threats = scan.threats;
124
+ if (quarantineDecision(scan) === "QUARANTINE")
125
+ reasons.push("injection signature in command");
126
+ }
127
+ catch (e) {
128
+ degraded.push(`immune:${e.message}`);
129
+ }
130
+ const injected = reasons.length > 0;
131
+ // 2. RISK.
132
+ const { risk, signals } = classifyCommandRisk(command);
133
+ // 3/4. POLICY + TRIBUNAL gate → disposition.
134
+ let disposition;
135
+ let tribunal;
136
+ const prodLocked = policy.prodReadOnly && !!host && /prod/i.test(host) && risk !== "read";
137
+ if (injected) {
138
+ disposition = "BLOCK";
139
+ }
140
+ else if (prodLocked) {
141
+ disposition = "BLOCK";
142
+ reasons.push(`policy: ${host} is prod / read-only — ${risk} command blocked`);
143
+ }
144
+ else if (risk === "destructive") {
145
+ if (deps.tribunal) {
146
+ try {
147
+ const r = await deps.tribunal(command, risk);
148
+ tribunal = r;
149
+ if (r.consensus === "danger" || r.consensus === "split") {
150
+ disposition = "BLOCK";
151
+ reasons.push(`tribunal: ${r.consensus} (${r.verdicts.map((v) => `${v.vendor}=${v.verdict}`).join(", ")}) — a fox can't guard its own henhouse`);
152
+ }
153
+ else {
154
+ // unanimous safe — still co-sign unless policy waives it.
155
+ disposition = policy.destructiveNeedsCosign && !input.cosigned ? "NEEDS_COSIGN" : "ALLOW";
156
+ if (disposition === "NEEDS_COSIGN")
157
+ reasons.push("destructive: tribunal says safe but policy requires human co-sign");
158
+ }
159
+ }
160
+ catch (e) {
161
+ degraded.push(`tribunal:${e.message}`);
162
+ disposition = "BLOCK"; // tribunal down ⇒ fail CLOSED for destructive (safe default)
163
+ reasons.push("tribunal unavailable — failing closed on a destructive command");
164
+ }
165
+ }
166
+ else {
167
+ disposition = input.cosigned ? "ALLOW" : (policy.destructiveNeedsCosign ? "NEEDS_COSIGN" : "ALLOW");
168
+ if (disposition === "NEEDS_COSIGN")
169
+ reasons.push("destructive command requires human co-sign");
170
+ }
171
+ }
172
+ else if (risk === "write") {
173
+ disposition = "ALLOW";
174
+ }
175
+ else {
176
+ disposition = "ALLOW";
177
+ }
178
+ if (disposition === "ALLOW" && reasons.length === 0)
179
+ reasons.push(`${risk} command — allowed`);
180
+ // 5/6. BLACK BOX + STAMP — record the crossing (provenance: who + risk + verdict).
181
+ const td = disposition === "BLOCK" ? "CONTRADICT" : disposition === "ALLOW" ? "MATCH" : "UNVERIFIED";
182
+ let receipt = null;
183
+ try {
184
+ const frame = record(repoRoot, {
185
+ agent, kind: disposition === "ALLOW" ? "tool-call" : "decision",
186
+ action: `heph:${disposition}:${command.slice(0, 80)}`,
187
+ claim: command, observedReality: `${disposition} (${risk}) by ${origin}:${agent}`, truthDelta: td,
188
+ });
189
+ receipt = frame.receipt;
190
+ }
191
+ catch (e) {
192
+ degraded.push(`recorder:${e.message}`);
193
+ }
194
+ return { disposition, risk, signals, reasons, agent, host, origin, threats, tribunal, receipt, degraded };
195
+ }
196
+ const POLYGLOT = {
197
+ "list listening ports": { linux: "ss -tlnp", macos: "lsof -iTCP -sTCP:LISTEN -n -P", powershell: "Get-NetTCPConnection -State Listen" },
198
+ "list processes": { linux: "ps aux", macos: "ps aux", powershell: "Get-Process" },
199
+ "disk usage": { linux: "df -h", macos: "df -h", powershell: "Get-PSDrive -PSProvider FileSystem" },
200
+ "memory usage": { linux: "free -h", macos: "vm_stat", powershell: "Get-CimInstance Win32_OperatingSystem | Select FreePhysicalMemory,TotalVisibleMemorySize" },
201
+ "current directory": { linux: "pwd", macos: "pwd", powershell: "Get-Location" },
202
+ "list files": { linux: "ls -la", macos: "ls -la", powershell: "Get-ChildItem -Force" },
203
+ "environment variables": { linux: "printenv", macos: "printenv", powershell: "Get-ChildItem Env:" },
204
+ "network interfaces": { linux: "ip addr", macos: "ifconfig", powershell: "Get-NetIPAddress" },
205
+ };
206
+ export function currentPlatform() {
207
+ return process.platform === "win32" ? "powershell" : process.platform === "darwin" ? "macos" : "linux";
208
+ }
209
+ /** Translate a canonical intent to the right shell for a platform (default: this OS). */
210
+ export function polyglot(intent, platform) {
211
+ const key = String(intent ?? "").toLowerCase().trim();
212
+ const row = POLYGLOT[key];
213
+ if (!row)
214
+ return null;
215
+ const p = platform ?? currentPlatform();
216
+ return { intent: key, platform: p, command: row[p] };
217
+ }
218
+ export function polyglotIntents() { return Object.keys(POLYGLOT); }
219
+ // ── Immune Shell — scan command OUTPUT before it's fed back to the AI ──────
220
+ export function scanCommandOutput(output) {
221
+ try {
222
+ const s = scanMessage(String(output ?? ""));
223
+ return { clean: s.clean, threats: s.threats };
224
+ }
225
+ catch {
226
+ return { clean: true, threats: [] };
227
+ }
228
+ }
229
+ /**
230
+ * Execute a command ONLY if its crossing verdict is ALLOW. Captures stdout/stderr/
231
+ * exit, immune-scans the output (so the AI isn't pwned by what it reads), and
232
+ * records the result. Refuses anything not ALLOW. Cross-platform (uses the OS shell).
233
+ */
234
+ export async function executeGuarded(repoRoot, input) {
235
+ if (input.disposition !== "ALLOW") {
236
+ return { ran: false, reason: `refused: disposition is ${input.disposition}, not ALLOW`, exitCode: null, stdout: "", stderr: "", outputThreats: [], receipt: null };
237
+ }
238
+ const { spawnSync } = await import("node:child_process");
239
+ let stdout = "", stderr = "", exitCode = null;
240
+ try {
241
+ const r = spawnSync(input.command, {
242
+ shell: true, encoding: "utf8", timeout: input.timeoutMs ?? 30_000, windowsHide: true,
243
+ maxBuffer: 8 * 1024 * 1024,
244
+ });
245
+ stdout = r.stdout ?? "";
246
+ stderr = r.stderr ?? "";
247
+ exitCode = r.status;
248
+ }
249
+ catch (e) {
250
+ stderr = e.message;
251
+ exitCode = null;
252
+ }
253
+ const scan = scanCommandOutput(stdout + "\n" + stderr);
254
+ let receipt = null;
255
+ try {
256
+ const frame = record(repoRoot, {
257
+ agent: input.agent, kind: "tool-call", action: `heph:executed:${input.command.slice(0, 80)}`,
258
+ claim: input.command, observedReality: `exit=${exitCode} outputThreats=${scan.threats.length}`,
259
+ truthDelta: scan.clean ? "MATCH" : "CONTRADICT",
260
+ });
261
+ receipt = frame.receipt;
262
+ }
263
+ catch { /* */ }
264
+ return { ran: true, reason: "executed", exitCode, stdout, stderr, outputThreats: scan.threats, receipt };
265
+ }
266
+ /** Live HEPHAESTUS status from the shared flight-recorder black box. */
267
+ export function hephaestusStatus(repoRoot) {
268
+ try {
269
+ const rep = replay(repoRoot);
270
+ const frames = readCdr(repoRoot);
271
+ let allowed = 0, needsCosign = 0, blocked = 0;
272
+ for (const f of frames) {
273
+ const p = (f.payload ?? {});
274
+ if (typeof p.action !== "string" || !p.action.startsWith("heph:"))
275
+ continue;
276
+ if (p.action.startsWith("heph:ALLOW") || p.action.startsWith("heph:executed"))
277
+ allowed++;
278
+ else if (p.action.startsWith("heph:NEEDS_COSIGN"))
279
+ needsCosign++;
280
+ else if (p.action.startsWith("heph:BLOCK"))
281
+ blocked++;
282
+ }
283
+ return { crossings: allowed + needsCosign + blocked, allowed, needsCosign, blocked, chainValid: rep.chainValid };
284
+ }
285
+ catch {
286
+ return { crossings: 0, allowed: 0, needsCosign: 0, blocked: 0, chainValid: true };
287
+ }
288
+ }
289
+ /** Verify a HEPHAESTUS crossing/execution receipt offline. */
290
+ export function verifyHephReceipt(receipt) {
291
+ const v = verifyReceipt(receipt);
292
+ return { valid: v.valid, reason: v.reason };
293
+ }
294
+ function parseSafetyVerdict(text) {
295
+ const t = String(text ?? "").trim().toLowerCase();
296
+ if (/^\s*safe\b/.test(t))
297
+ return "safe";
298
+ if (/^\s*danger/.test(t))
299
+ return "danger";
300
+ // unparseable / refusal ⇒ fail SAFE (treat as danger so a destructive op is gated).
301
+ return "danger";
302
+ }
303
+ /**
304
+ * Build a real cross-vendor TRIBUNAL backed by diff_arena. Each vendor judges the
305
+ * command INDEPENDENTLY ("safe"/"danger") — errors are UNcorrelated across vendors,
306
+ * so the panel catches a blind spot a single-vendor ensemble can't. Mneme is the
307
+ * neutral convener (no vendor owns the judge). With no live vendors configured it
308
+ * fails SAFE (consensus "danger" ⇒ a destructive op is blocked) rather than waving
309
+ * it through. Wire real vendors via diff_arena's httpAdapter/cliAdapter (API keys);
310
+ * tests/offline pass deterministic vendor stubs.
311
+ */
312
+ export function makeDiffArenaTribunal(repoRoot, opts = {}) {
313
+ return async (command) => {
314
+ const vendors = opts.vendors;
315
+ if (!vendors || vendors.length === 0) {
316
+ return { verdicts: [{ vendor: "(no-panel)", verdict: "danger" }], consensus: "danger" };
317
+ }
318
+ const prompt = `A command is about to run on a machine:\n\n ${command}\n\nIs it SAFE or DANGEROUS to run as-is (data loss, prod impact, irreversibility)? Reply starting with EXACTLY "safe" or "danger", then one short reason.`;
319
+ let verdicts;
320
+ try {
321
+ const { diffArenaAsk } = await import("../diff_arena/index.js");
322
+ const res = await diffArenaAsk({ prompt, vendors, cwd: repoRoot });
323
+ verdicts = res.responses.map((r) => ({ vendor: r.vendor, verdict: r.ok ? parseSafetyVerdict(r.text) : "danger" }));
324
+ }
325
+ catch {
326
+ // diff_arena unavailable ⇒ ask vendors directly (still independent), fail safe on error.
327
+ verdicts = [];
328
+ for (const v of vendors) {
329
+ try {
330
+ const r = await v.ask(prompt);
331
+ verdicts.push({ vendor: v.name, verdict: r.ok ? parseSafetyVerdict(r.text) : "danger" });
332
+ }
333
+ catch {
334
+ verdicts.push({ vendor: v.name, verdict: "danger" });
335
+ }
336
+ }
337
+ }
338
+ const safes = verdicts.filter((v) => v.verdict === "safe").length;
339
+ const consensus = safes === verdicts.length ? "safe" : safes === 0 ? "danger" : "split";
340
+ return { verdicts, consensus };
341
+ };
342
+ }
343
+ // ════════════════════════════════════════════════════════════════════════
344
+ // v2.87.0 — 🔮 PRE-FLIGHT SIMULATION (counterfactual + irreversibility warning)
345
+ // The honest answer to "time-travel shell": we cannot UNDO an irreversible
346
+ // effect (rm/dd/DROP) — so we PREDICT + WARN before crossing, cross-vendor +
347
+ // signed. "Preview the future before you cross."
348
+ // ════════════════════════════════════════════════════════════════════════
349
+ const IRREVERSIBLE = [
350
+ [/\brm\s+-[a-z]*r|\b(Remove-Item|ri)\b.*-Recurse/i, "deletes files/dirs — NOT recoverable without a backup"],
351
+ [/\bdd\s+if=|>\s*\/dev\/(sd|nvme|disk)|\b(mkfs|wipefs|fdisk|format)\b/i, "overwrites raw disk — NOT recoverable"],
352
+ [/\b(drop|truncate)\s+(table|database|schema)?|delete\s+from\b(?![^;]*\bwhere\b)/i, "drops/empties data — NOT recoverable without a DB backup"],
353
+ [/\bshred\b/i, "securely erases — designed to be unrecoverable"],
354
+ [/\bgit\s+push\s+(-f|--force)/i, "force-pushes — rewrites remote history others may depend on"],
355
+ [/\bgit\s+reset\s+--hard/i, "discards uncommitted changes — NOT recoverable"],
356
+ [/\bterraform\s+destroy|kubectl\s+delete|helm\s+(delete|uninstall)/i, "tears down infrastructure — recreate ≠ restore data/state"],
357
+ [/\b(shutdown|reboot|halt|poweroff|Stop-Computer)\b/i, "interrupts the running system — in-flight work is lost"],
358
+ ];
359
+ const REVERSIBLE_HINTS = [
360
+ [/\bgit\s+commit\b/i, "git revert undoes it"],
361
+ [/\bgit\s+(add|stash|checkout\s+-b|branch)\b/i, "git can undo it"],
362
+ [/\b(mkdir|touch)\b/i, "remove the created path to undo"],
363
+ [/\b(apt|apt-get|yum|dnf|brew|npm|pnpm|pip|cargo)\s+(install|add|i)\b/i, "uninstall to undo (config/data may persist)"],
364
+ [/\bdocker\s+(run|start)\b/i, "docker stop/rm to undo"],
365
+ ];
366
+ /** Deterministically classify whether a command's effect can be undone. */
367
+ export function classifyReversibility(command) {
368
+ const c = String(command ?? "");
369
+ const irreversibleWarnings = [];
370
+ for (const [re, msg] of IRREVERSIBLE)
371
+ if (re.test(c))
372
+ irreversibleWarnings.push(msg);
373
+ const effects = [];
374
+ for (const [re, msg] of REVERSIBLE_HINTS)
375
+ if (re.test(c))
376
+ effects.push(msg);
377
+ const { risk } = classifyCommandRisk(c);
378
+ if (risk === "read")
379
+ return { reversible: true, effects: ["read-only — no state change"], irreversibleWarnings: [] };
380
+ return { reversible: irreversibleWarnings.length === 0, effects, irreversibleWarnings };
381
+ }
382
+ /**
383
+ * 🔮 Pre-flight a command BEFORE crossing: predict blast radius + flag what cannot
384
+ * be undone + (optionally) ask a panel to predict the effect — and SIGN the
385
+ * pre-mortem. Never executes. The proof that the irreversible was foreseen + warned.
386
+ */
387
+ export async function preflightCommand(repoRoot, input, deps = {}) {
388
+ const command = String(input.command ?? "");
389
+ const { risk } = classifyCommandRisk(command);
390
+ const rev = classifyReversibility(command);
391
+ let prediction;
392
+ if (deps.predict) {
393
+ try {
394
+ prediction = await deps.predict(command);
395
+ }
396
+ catch { /* best-effort */ }
397
+ }
398
+ let receipt = null;
399
+ try {
400
+ const frame = record(repoRoot, {
401
+ agent: String(input.agent ?? "unknown"), kind: "decision",
402
+ action: `heph:preflight:${command.slice(0, 72)}`,
403
+ claim: command,
404
+ observedReality: `risk=${risk} reversible=${rev.reversible} warnings=${rev.irreversibleWarnings.length}`,
405
+ truthDelta: rev.reversible ? "MATCH" : "CONTRADICT",
406
+ });
407
+ receipt = frame.receipt;
408
+ }
409
+ catch { /* */ }
410
+ return { command, risk, reversible: rev.reversible, effects: rev.effects, irreversibleWarnings: rev.irreversibleWarnings, prediction, receipt };
411
+ }
412
+ const ENV_VENDORS = [
413
+ { name: "openai", endpoint: "https://api.openai.com/v1/chat/completions", apiKeyEnv: "OPENAI_API_KEY", model: process.env["MNEME_OPENAI_MODEL"] ?? "gpt-4o-mini" },
414
+ { name: "grok", endpoint: "https://api.x.ai/v1/chat/completions", apiKeyEnv: "XAI_API_KEY", model: process.env["MNEME_GROK_MODEL"] ?? "grok-2-latest" },
415
+ { name: "gemini", endpoint: "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions", apiKeyEnv: "GEMINI_API_KEY", model: process.env["MNEME_GEMINI_MODEL"] ?? "gemini-1.5-flash" },
416
+ { name: "deepseek", endpoint: "https://api.deepseek.com/v1/chat/completions", apiKeyEnv: "DEEPSEEK_API_KEY", model: process.env["MNEME_DEEPSEEK_MODEL"] ?? "deepseek-chat" },
417
+ { name: "openrouter", endpoint: "https://openrouter.ai/api/v1/chat/completions", apiKeyEnv: "OPENROUTER_API_KEY", model: process.env["MNEME_OPENROUTER_MODEL"] ?? "anthropic/claude-3.5-sonnet" },
418
+ ];
419
+ /** Materialise a tribunal panel from env-present vendor keys (zero keys ⇒ [] ⇒ fail-safe BLOCK). */
420
+ export async function tribunalVendorsFromEnv() {
421
+ const { httpAdapter } = await import("../diff_arena/adapters.js");
422
+ return ENV_VENDORS.filter((v) => (process.env[v.apiKeyEnv] ?? "").length > 0)
423
+ .map((v) => httpAdapter({ name: v.name, endpoint: v.endpoint, apiKeyEnv: v.apiKeyEnv, model: v.model, headers: v.headers, timeoutMs: 20000 }));
424
+ }
425
+ //# sourceMappingURL=index.js.map