@kognai/orchestrator-core 0.1.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.
Files changed (144) hide show
  1. package/README.md +44 -0
  2. package/dist/index.d.ts +63 -0
  3. package/dist/index.js +175 -0
  4. package/dist/lib/aar-middleware.d.ts +6 -0
  5. package/dist/lib/aar-middleware.js +70 -0
  6. package/dist/lib/aar-types.d.ts +34 -0
  7. package/dist/lib/aar-types.js +4 -0
  8. package/dist/lib/acp-engine.d.ts +68 -0
  9. package/dist/lib/acp-engine.js +123 -0
  10. package/dist/lib/acp.d.ts +61 -0
  11. package/dist/lib/acp.js +425 -0
  12. package/dist/lib/agent-registry.d.ts +50 -0
  13. package/dist/lib/agent-registry.js +137 -0
  14. package/dist/lib/anthropic-direct.d.ts +27 -0
  15. package/dist/lib/anthropic-direct.js +109 -0
  16. package/dist/lib/asmr-extractor.d.ts +40 -0
  17. package/dist/lib/asmr-extractor.js +151 -0
  18. package/dist/lib/asmr-retrieval.d.ts +76 -0
  19. package/dist/lib/asmr-retrieval.js +311 -0
  20. package/dist/lib/asmr.d.ts +8 -0
  21. package/dist/lib/asmr.js +24 -0
  22. package/dist/lib/brainx-client.d.ts +72 -0
  23. package/dist/lib/brainx-client.js +200 -0
  24. package/dist/lib/brainx-embed.d.ts +14 -0
  25. package/dist/lib/brainx-embed.js +139 -0
  26. package/dist/lib/brainx-swarm-bridge.d.ts +93 -0
  27. package/dist/lib/brainx-swarm-bridge.js +242 -0
  28. package/dist/lib/byterover-client.d.ts +19 -0
  29. package/dist/lib/byterover-client.js +59 -0
  30. package/dist/lib/ceo-wallet.d.ts +37 -0
  31. package/dist/lib/ceo-wallet.js +176 -0
  32. package/dist/lib/chomsky-gate.d.ts +24 -0
  33. package/dist/lib/chomsky-gate.js +178 -0
  34. package/dist/lib/chomsky-runner.d.ts +29 -0
  35. package/dist/lib/chomsky-runner.js +157 -0
  36. package/dist/lib/citizen-score-contract.d.ts +72 -0
  37. package/dist/lib/citizen-score-contract.js +16 -0
  38. package/dist/lib/citizen-score-registry.d.ts +25 -0
  39. package/dist/lib/citizen-score-registry.js +65 -0
  40. package/dist/lib/citizenship.d.ts +103 -0
  41. package/dist/lib/citizenship.js +272 -0
  42. package/dist/lib/clawrouter-client.d.ts +37 -0
  43. package/dist/lib/clawrouter-client.js +148 -0
  44. package/dist/lib/code-asset-crystalliser.d.ts +41 -0
  45. package/dist/lib/code-asset-crystalliser.js +181 -0
  46. package/dist/lib/code-failure-logger.d.ts +27 -0
  47. package/dist/lib/code-failure-logger.js +42 -0
  48. package/dist/lib/cto-approval-gate.d.ts +45 -0
  49. package/dist/lib/cto-approval-gate.js +478 -0
  50. package/dist/lib/cto-gate-types.d.ts +28 -0
  51. package/dist/lib/cto-gate-types.js +8 -0
  52. package/dist/lib/decomposer-feedback.d.ts +54 -0
  53. package/dist/lib/decomposer-feedback.js +115 -0
  54. package/dist/lib/emotional-safety-gate.d.ts +48 -0
  55. package/dist/lib/emotional-safety-gate.js +97 -0
  56. package/dist/lib/engine-paths.d.ts +13 -0
  57. package/dist/lib/engine-paths.js +32 -0
  58. package/dist/lib/event-bus-listener.d.ts +8 -0
  59. package/dist/lib/event-bus-listener.js +144 -0
  60. package/dist/lib/event-bus-publisher.d.ts +25 -0
  61. package/dist/lib/event-bus-publisher.js +188 -0
  62. package/dist/lib/event-bus-types.d.ts +73 -0
  63. package/dist/lib/event-bus-types.js +23 -0
  64. package/dist/lib/failure-library.d.ts +178 -0
  65. package/dist/lib/failure-library.js +349 -0
  66. package/dist/lib/ksl/error-log.d.ts +28 -0
  67. package/dist/lib/ksl/error-log.js +43 -0
  68. package/dist/lib/ksl/index.d.ts +9 -0
  69. package/dist/lib/ksl/index.js +25 -0
  70. package/dist/lib/ksl/orchestrator-tap.d.ts +16 -0
  71. package/dist/lib/ksl/orchestrator-tap.js +85 -0
  72. package/dist/lib/ksl/record-writer.d.ts +46 -0
  73. package/dist/lib/ksl/record-writer.js +45 -0
  74. package/dist/lib/llm-cost-table.d.ts +36 -0
  75. package/dist/lib/llm-cost-table.js +90 -0
  76. package/dist/lib/local-model-router.d.ts +27 -0
  77. package/dist/lib/local-model-router.js +61 -0
  78. package/dist/lib/mc-client.d.ts +51 -0
  79. package/dist/lib/mc-client.js +249 -0
  80. package/dist/lib/model-router-contract.d.ts +91 -0
  81. package/dist/lib/model-router-contract.js +19 -0
  82. package/dist/lib/model-router-registry.d.ts +24 -0
  83. package/dist/lib/model-router-registry.js +52 -0
  84. package/dist/lib/model-router.d.ts +20 -0
  85. package/dist/lib/model-router.js +79 -0
  86. package/dist/lib/monotask-state-machine.d.ts +19 -0
  87. package/dist/lib/monotask-state-machine.js +131 -0
  88. package/dist/lib/neutral-prompt-checker.d.ts +22 -0
  89. package/dist/lib/neutral-prompt-checker.js +130 -0
  90. package/dist/lib/notion-direct.d.ts +92 -0
  91. package/dist/lib/notion-direct.js +381 -0
  92. package/dist/lib/ollama-client.d.ts +37 -0
  93. package/dist/lib/ollama-client.js +158 -0
  94. package/dist/lib/omel/credential-vault.d.ts +57 -0
  95. package/dist/lib/omel/credential-vault.js +324 -0
  96. package/dist/lib/omel/human-brake.d.ts +32 -0
  97. package/dist/lib/omel/human-brake.js +289 -0
  98. package/dist/lib/omel/index.d.ts +10 -0
  99. package/dist/lib/omel/index.js +26 -0
  100. package/dist/lib/omel/phantom-workspace.d.ts +31 -0
  101. package/dist/lib/omel/phantom-workspace.js +256 -0
  102. package/dist/lib/omel/wipe-witness.d.ts +75 -0
  103. package/dist/lib/omel/wipe-witness.js +398 -0
  104. package/dist/lib/orchestrate-engine.d.ts +25 -0
  105. package/dist/lib/orchestrate-engine.js +4436 -0
  106. package/dist/lib/perm-judge.d.ts +46 -0
  107. package/dist/lib/perm-judge.js +173 -0
  108. package/dist/lib/plumber/conformance.d.ts +54 -0
  109. package/dist/lib/plumber/conformance.js +121 -0
  110. package/dist/lib/plumber/index.d.ts +9 -0
  111. package/dist/lib/plumber/index.js +25 -0
  112. package/dist/lib/plumber/observer.d.ts +52 -0
  113. package/dist/lib/plumber/observer.js +180 -0
  114. package/dist/lib/plumber/types.d.ts +78 -0
  115. package/dist/lib/plumber/types.js +29 -0
  116. package/dist/lib/research-impl-gate.d.ts +16 -0
  117. package/dist/lib/research-impl-gate.js +105 -0
  118. package/dist/lib/sherlock-memory.d.ts +29 -0
  119. package/dist/lib/sherlock-memory.js +105 -0
  120. package/dist/lib/skill-crystalliser.d.ts +44 -0
  121. package/dist/lib/skill-crystalliser.js +60 -0
  122. package/dist/lib/sprint-runner-engine.d.ts +27 -0
  123. package/dist/lib/sprint-runner-engine.js +1042 -0
  124. package/dist/lib/sprint-state.d.ts +71 -0
  125. package/dist/lib/sprint-state.js +202 -0
  126. package/dist/lib/stuck-handler.d.ts +17 -0
  127. package/dist/lib/stuck-handler.js +249 -0
  128. package/dist/lib/task-contract-checker.d.ts +17 -0
  129. package/dist/lib/task-contract-checker.js +29 -0
  130. package/dist/lib/task-router/index.d.ts +17 -0
  131. package/dist/lib/task-router/index.js +52 -0
  132. package/dist/lib/task-router/router/generate-execution-id.d.ts +10 -0
  133. package/dist/lib/task-router/router/generate-execution-id.js +24 -0
  134. package/dist/lib/task-router/router/resolve-route.d.ts +2 -0
  135. package/dist/lib/task-router/router/resolve-route.js +49 -0
  136. package/dist/lib/task-router/types.d.ts +79 -0
  137. package/dist/lib/task-router/types.js +39 -0
  138. package/dist/lib/token-budget-validator.d.ts +44 -0
  139. package/dist/lib/token-budget-validator.js +84 -0
  140. package/dist/lib/trust-score-updater.d.ts +30 -0
  141. package/dist/lib/trust-score-updater.js +107 -0
  142. package/dist/lib/wallet-state.d.ts +26 -0
  143. package/dist/lib/wallet-state.js +85 -0
  144. package/package.json +27 -0
@@ -0,0 +1,324 @@
1
+ "use strict";
2
+ // OMEL Credential Vault — Sprint 177 + Sprint 182 Hardening (AMD-13)
3
+ // Controlled access to secrets from process.env.
4
+ // NEVER logs secret values — only key names and masked representations.
5
+ //
6
+ // Public API:
7
+ // getSecret(key) — reads from process.env, NEVER logs value
8
+ // listSecrets() — returns key NAMES only, never values
9
+ // hasSecret(key) — boolean presence check
10
+ // maskForLog(value) — "sk-****...****" pattern for safe logging
11
+ // scanForLeaks(line) — detect verbatim secret values in a log line
12
+ // audit() — CTO pre-check report: all secrets status
13
+ //
14
+ // Sprint 182 hardening additions:
15
+ // - Secret rotation detection: alert if key unchanged for >90 days
16
+ // - Passive log scanner: scan last 100 log lines per orchestrator run
17
+ // - Access rate limiting: same key accessed >50× in 60s → alert
18
+ // - Required secrets check: startup validation of critical env vars
19
+ // - audit() report method for CTO approval gate
20
+ //
21
+ // Audit log: logs/omel/vault-access-YYYY-MM-DD.jsonl (key name + agent_id, never value)
22
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ var desc = Object.getOwnPropertyDescriptor(m, k);
25
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
26
+ desc = { enumerable: true, get: function() { return m[k]; } };
27
+ }
28
+ Object.defineProperty(o, k2, desc);
29
+ }) : (function(o, m, k, k2) {
30
+ if (k2 === undefined) k2 = k;
31
+ o[k2] = m[k];
32
+ }));
33
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
34
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
35
+ }) : function(o, v) {
36
+ o["default"] = v;
37
+ });
38
+ var __importStar = (this && this.__importStar) || (function () {
39
+ var ownKeys = function(o) {
40
+ ownKeys = Object.getOwnPropertyNames || function (o) {
41
+ var ar = [];
42
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
43
+ return ar;
44
+ };
45
+ return ownKeys(o);
46
+ };
47
+ return function (mod) {
48
+ if (mod && mod.__esModule) return mod;
49
+ var result = {};
50
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
51
+ __setModuleDefault(result, mod);
52
+ return result;
53
+ };
54
+ })();
55
+ Object.defineProperty(exports, "__esModule", { value: true });
56
+ exports.credentialVault = exports.CredentialVault = void 0;
57
+ const fs = __importStar(require("fs"));
58
+ const path = __importStar(require("path"));
59
+ const engine_paths_1 = require("../engine-paths");
60
+ const https = __importStar(require("https"));
61
+ const LOGS_DIR = path.join((0, engine_paths_1.resolveEnginePaths)().root, 'logs', 'omel');
62
+ const VAULT_META = path.join(LOGS_DIR, 'vault-rotation-meta.json');
63
+ const AUDIT_FILE = () => {
64
+ const date = new Date().toISOString().slice(0, 10);
65
+ return path.join(LOGS_DIR, `vault-access-${date}.jsonl`);
66
+ };
67
+ fs.mkdirSync(LOGS_DIR, { recursive: true });
68
+ // ── Telegram fire-and-forget ──────────────────────────────────────────────────
69
+ function sendTelegramAlert(message) {
70
+ const botToken = process.env.TELEGRAM_BOT_TOKEN || '';
71
+ const chatId = process.env.OWNER_TELEGRAM_CHAT_ID || '';
72
+ if (!botToken || !chatId)
73
+ return;
74
+ const payload = JSON.stringify({ chat_id: chatId, text: message, parse_mode: 'Markdown' });
75
+ try {
76
+ const req = https.request({
77
+ hostname: 'api.telegram.org',
78
+ path: `/bot${botToken}/sendMessage`,
79
+ method: 'POST',
80
+ headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
81
+ });
82
+ req.on('error', () => { });
83
+ req.write(payload);
84
+ req.end();
85
+ }
86
+ catch { /* silent */ }
87
+ }
88
+ // ── Known secret key prefixes for passive leak scanner ───────────────────────
89
+ const SECRET_KEY_PATTERNS = [
90
+ /^ANTHROPIC_API_KEY$/,
91
+ /^OPENAI_API_KEY$/,
92
+ /^TELEGRAM_BOT_TOKEN$/,
93
+ /^CEO_TELEGRAM_BOT_TOKEN$/,
94
+ /^STRIPE_SECRET_KEY$/,
95
+ /^STRIPE_WEBHOOK_SECRET$/,
96
+ /^TIKTOK_ACCESS_TOKEN$/,
97
+ /.*_API_KEY$/,
98
+ /.*_SECRET$/,
99
+ /.*_TOKEN$/,
100
+ /.*_PASSWORD$/,
101
+ /.*_PRIVATE_KEY$/,
102
+ ];
103
+ // Required secrets that must be present at startup
104
+ const REQUIRED_SECRETS = [
105
+ 'TELEGRAM_BOT_TOKEN',
106
+ 'OWNER_TELEGRAM_CHAT_ID',
107
+ ];
108
+ // ── Collect known secret keys ─────────────────────────────────────────────────
109
+ function collectSecretKeys() {
110
+ return Object.keys(process.env).filter(k => SECRET_KEY_PATTERNS.some(p => p.test(k)));
111
+ }
112
+ // ── Audit log ─────────────────────────────────────────────────────────────────
113
+ function auditLog(key, agentId, action) {
114
+ const entry = {
115
+ ts: new Date().toISOString(),
116
+ key,
117
+ agent_id: agentId,
118
+ action,
119
+ };
120
+ try {
121
+ fs.appendFileSync(AUDIT_FILE(), JSON.stringify(entry) + '\n');
122
+ }
123
+ catch { /* if log write fails, never crash caller */ }
124
+ }
125
+ function loadRotationMeta() {
126
+ try {
127
+ if (fs.existsSync(VAULT_META)) {
128
+ return JSON.parse(fs.readFileSync(VAULT_META, 'utf-8'));
129
+ }
130
+ }
131
+ catch { /* ignore */ }
132
+ return {};
133
+ }
134
+ function saveRotationMeta(meta) {
135
+ try {
136
+ fs.writeFileSync(VAULT_META, JSON.stringify(meta, null, 2), 'utf-8');
137
+ }
138
+ catch { /* non-fatal */ }
139
+ }
140
+ function hashSecret(value) {
141
+ // Simple FNV-1a hash for rotation detection (not cryptographic, just drift detection)
142
+ let h = 2166136261;
143
+ for (let i = 0; i < value.length; i++) {
144
+ h ^= value.charCodeAt(i);
145
+ h = (h * 16777619) >>> 0;
146
+ }
147
+ return h.toString(16);
148
+ }
149
+ const RATE_LIMIT_MAX = 50; // accesses per window
150
+ const RATE_LIMIT_WINDOW = 60_000; // 60 seconds
151
+ // ── CredentialVault class ─────────────────────────────────────────────────────
152
+ class CredentialVault {
153
+ rateCounters = new Map();
154
+ // ── Rate limiting helper ────────────────────────────────────────────────────
155
+ checkRateLimit(key, agentId) {
156
+ const now = Date.now();
157
+ const win = this.rateCounters.get(key);
158
+ if (!win || now - win.windowStart > RATE_LIMIT_WINDOW) {
159
+ // Start fresh window
160
+ this.rateCounters.set(key, { count: 1, windowStart: now });
161
+ return;
162
+ }
163
+ win.count++;
164
+ if (win.count > RATE_LIMIT_MAX) {
165
+ auditLog(key, agentId, 'rate_limit');
166
+ sendTelegramAlert(`⚠️ *[OMEL Vault] Rate Limit Alert*\n` +
167
+ `Key: \`${key}\` accessed ${win.count}× in 60s by \`${agentId}\`\n` +
168
+ `Possible infinite loop or runaway agent.`);
169
+ }
170
+ }
171
+ // ── Rotation detection ──────────────────────────────────────────────────────
172
+ checkRotation(key, value) {
173
+ const today = new Date().toISOString().slice(0, 10);
174
+ const meta = loadRotationMeta();
175
+ const h = hashSecret(value);
176
+ if (!meta[key]) {
177
+ meta[key] = { last_seen_date: today, hash: h };
178
+ saveRotationMeta(meta);
179
+ return;
180
+ }
181
+ if (meta[key].hash !== h) {
182
+ // Secret rotated — update record
183
+ meta[key] = { last_seen_date: today, hash: h };
184
+ saveRotationMeta(meta);
185
+ return;
186
+ }
187
+ // Same hash — check age
188
+ const daysSince = Math.floor((Date.now() - new Date(meta[key].last_seen_date).getTime()) / 86_400_000);
189
+ if (daysSince > 90) {
190
+ auditLog(key, 'system', 'rotation_alert');
191
+ sendTelegramAlert(`🔑 *[OMEL Vault] Rotation Alert*\n` +
192
+ `Key: \`${key}\` has not been rotated in *${daysSince} days* (threshold: 90)\n` +
193
+ `Consider rotating this secret.`);
194
+ }
195
+ }
196
+ /**
197
+ * Read a secret from process.env.
198
+ * The value is returned to the caller but NEVER written to logs.
199
+ */
200
+ getSecret(key, agentId = 'orchestrator') {
201
+ auditLog(key, agentId, 'get');
202
+ this.checkRateLimit(key, agentId);
203
+ const value = process.env[key] ?? '';
204
+ if (value)
205
+ this.checkRotation(key, value);
206
+ return value;
207
+ }
208
+ /**
209
+ * Return the NAMES of all env variables matching known secret patterns.
210
+ * Values are NEVER returned.
211
+ */
212
+ listSecrets(agentId = 'orchestrator') {
213
+ const keys = collectSecretKeys();
214
+ auditLog('__all__', agentId, 'list');
215
+ return keys;
216
+ }
217
+ /**
218
+ * Check if a secret is present (non-empty).
219
+ */
220
+ hasSecret(key, agentId = 'orchestrator') {
221
+ auditLog(key, agentId, 'check');
222
+ return Boolean(process.env[key]);
223
+ }
224
+ /**
225
+ * Mask a secret value for safe inclusion in logs or Telegram messages.
226
+ * Format: first 4 chars + "****...****" + last 4 chars.
227
+ */
228
+ maskForLog(value) {
229
+ if (!value)
230
+ return '****';
231
+ if (value.length <= 8)
232
+ return '****';
233
+ const prefix = value.slice(0, 4);
234
+ const suffix = value.slice(-4);
235
+ return `${prefix}****...****${suffix}`;
236
+ }
237
+ /**
238
+ * Passive leak scanner: check if any known secret value appears verbatim
239
+ * in the given log line. Returns the leaked key names (never the values).
240
+ */
241
+ scanForLeaks(line) {
242
+ const leaked = [];
243
+ for (const key of collectSecretKeys()) {
244
+ const value = process.env[key];
245
+ if (value && value.length >= 8 && line.includes(value)) {
246
+ leaked.push(key);
247
+ process.stderr.write(`[OMEL-VAULT] ⚠️ Secret value for ${key} detected in log line — redact before writing!\n`);
248
+ }
249
+ }
250
+ return leaked;
251
+ }
252
+ /**
253
+ * Scan last N lines of a log file for secret leaks.
254
+ * Returns list of (key, line_number) tuples where leaks were detected.
255
+ * Intended for use by CTO gate pre-check and orchestrator run startup.
256
+ */
257
+ scanLogFile(logFilePath, lastNLines = 100) {
258
+ const results = [];
259
+ try {
260
+ if (!fs.existsSync(logFilePath))
261
+ return results;
262
+ const lines = fs.readFileSync(logFilePath, 'utf-8')
263
+ .split('\n').filter(l => l.trim());
264
+ const slice = lines.slice(-lastNLines);
265
+ slice.forEach((line, i) => {
266
+ const leaked = this.scanForLeaks(line);
267
+ for (const key of leaked)
268
+ results.push({ key, lineIndex: i });
269
+ });
270
+ }
271
+ catch { /* non-fatal */ }
272
+ return results;
273
+ }
274
+ /**
275
+ * Startup required-secrets validation.
276
+ * Logs + throws if any required key is missing.
277
+ * Call once at orchestrator startup.
278
+ */
279
+ validateRequired(requiredKeys = REQUIRED_SECRETS) {
280
+ const missing = requiredKeys.filter(k => !process.env[k]);
281
+ if (missing.length === 0)
282
+ return;
283
+ const msg = `[OMEL-VAULT] Missing required secrets: ${missing.join(', ')}`;
284
+ process.stderr.write(msg + '\n');
285
+ try {
286
+ fs.appendFileSync(AUDIT_FILE(), JSON.stringify({
287
+ ts: new Date().toISOString(),
288
+ action: 'startup_validation_failed',
289
+ missing,
290
+ }) + '\n');
291
+ }
292
+ catch { /* ignore */ }
293
+ throw new Error(msg);
294
+ }
295
+ /**
296
+ * CTO approval gate audit report.
297
+ * Returns a summary of vault state: which secrets are present, rotation status.
298
+ * Values are NEVER included.
299
+ */
300
+ audit() {
301
+ const keys = collectSecretKeys();
302
+ const present = keys.filter(k => Boolean(process.env[k]));
303
+ const missing = REQUIRED_SECRETS.filter(k => !process.env[k]);
304
+ const rotationWarnings = [];
305
+ const meta = loadRotationMeta();
306
+ for (const key of present) {
307
+ if (meta[key]) {
308
+ const daysSince = Math.floor((Date.now() - new Date(meta[key].last_seen_date).getTime()) / 86_400_000);
309
+ if (daysSince > 90)
310
+ rotationWarnings.push(`${key} (${daysSince}d)`);
311
+ }
312
+ }
313
+ return {
314
+ total_secrets: keys.length,
315
+ present,
316
+ missing,
317
+ rotation_warnings: rotationWarnings,
318
+ generated_at: new Date().toISOString(),
319
+ };
320
+ }
321
+ }
322
+ exports.CredentialVault = CredentialVault;
323
+ // Exported singleton
324
+ exports.credentialVault = new CredentialVault();
@@ -0,0 +1,32 @@
1
+ export type HighRiskOp = 'file_delete' | 'git_reset_hard' | 'env_change' | 'schema_migration' | 'bulk_overwrite' | 'new_file';
2
+ export interface ApprovalResult {
3
+ approved: boolean;
4
+ approvedBy?: string;
5
+ reason?: string;
6
+ ts: string;
7
+ risk_score?: number;
8
+ response_ms?: number;
9
+ }
10
+ export declare class HumanBrake {
11
+ /**
12
+ * Returns true if operation is a known HighRiskOp type,
13
+ * OR if context.filePath touches protected files.
14
+ */
15
+ isHighRisk(operation: string, context?: any): boolean;
16
+ /**
17
+ * Returns a risk score 1-10 for the given operation + context.
18
+ * High (8-10): requires approval. Medium (5-7): log only. Low (1-4): silent pass.
19
+ *
20
+ * Context fields considered:
21
+ * - filePath: if it's an orchestrator core file, score bumped to max
22
+ * - size: bulk ops on large files get higher score
23
+ */
24
+ getRiskScore(operation: string, context?: any): number;
25
+ /**
26
+ * Send Telegram approval request and poll for up to 5 minutes.
27
+ * Returns ApprovalResult: approved on 'yes'/'approve', rejected on other reply or timeout.
28
+ * Logs response time and emits AAR entry (Sprint 184).
29
+ */
30
+ requireApproval(operation: HighRiskOp, context?: any): Promise<ApprovalResult>;
31
+ }
32
+ export declare const humanBrake: HumanBrake;
@@ -0,0 +1,289 @@
1
+ "use strict";
2
+ // OMEL Human Brake — Sprint 179 + Sprint 184 Hardening (AMD-13)
3
+ // Human-in-the-loop safety gate for high-risk operations.
4
+ // Telegram approval flow: send notification → poll getUpdates up to 5 min.
5
+ // ABORT on timeout or non-approval reply. JSONL audit trail.
6
+ //
7
+ // API:
8
+ // requireApproval(operation: HighRiskOp): Promise<ApprovalResult>
9
+ // isHighRisk(operation: string, context: any): boolean
10
+ // getRiskScore(operation: string, context: any): number (Sprint 184)
11
+ //
12
+ // Sprint 184 hardening additions:
13
+ // - Risk scoring: getRiskScore() → 1-10 scale
14
+ // - Operation risk table: file_delete=9, git_reset_hard=10, new_file=2, etc.
15
+ // - Approval response time logged per operation
16
+ // - HUMAN_BRAKE_DISABLED=true triggers daily Telegram reminder
17
+ // - AARMiddleware integration: approved/rejected ops get AAR entry
18
+ //
19
+ // Audit log: logs/omel/human-brake-YYYY-MM-DD.jsonl
20
+ // Env: TELEGRAM_BOT_TOKEN, OWNER_TELEGRAM_CHAT_ID, HUMAN_BRAKE_DISABLED
21
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
22
+ if (k2 === undefined) k2 = k;
23
+ var desc = Object.getOwnPropertyDescriptor(m, k);
24
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
25
+ desc = { enumerable: true, get: function() { return m[k]; } };
26
+ }
27
+ Object.defineProperty(o, k2, desc);
28
+ }) : (function(o, m, k, k2) {
29
+ if (k2 === undefined) k2 = k;
30
+ o[k2] = m[k];
31
+ }));
32
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
33
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
34
+ }) : function(o, v) {
35
+ o["default"] = v;
36
+ });
37
+ var __importStar = (this && this.__importStar) || (function () {
38
+ var ownKeys = function(o) {
39
+ ownKeys = Object.getOwnPropertyNames || function (o) {
40
+ var ar = [];
41
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
42
+ return ar;
43
+ };
44
+ return ownKeys(o);
45
+ };
46
+ return function (mod) {
47
+ if (mod && mod.__esModule) return mod;
48
+ var result = {};
49
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
50
+ __setModuleDefault(result, mod);
51
+ return result;
52
+ };
53
+ })();
54
+ Object.defineProperty(exports, "__esModule", { value: true });
55
+ exports.humanBrake = exports.HumanBrake = void 0;
56
+ const fs = __importStar(require("fs"));
57
+ const path = __importStar(require("path"));
58
+ const engine_paths_1 = require("../engine-paths");
59
+ const https = __importStar(require("https"));
60
+ // ── Risk score table (Sprint 184) ─────────────────────────────────────────────
61
+ const RISK_SCORES = {
62
+ git_reset_hard: 10,
63
+ file_delete: 9,
64
+ env_change: 8,
65
+ schema_migration: 8,
66
+ bulk_overwrite: 7,
67
+ new_file: 2,
68
+ };
69
+ // ── Logging ───────────────────────────────────────────────────────────────────
70
+ const LOGS_DIR = path.join((0, engine_paths_1.resolveEnginePaths)().root, 'logs', 'omel');
71
+ fs.mkdirSync(LOGS_DIR, { recursive: true });
72
+ function logFile() {
73
+ const date = new Date().toISOString().slice(0, 10);
74
+ return path.join(LOGS_DIR, `human-brake-${date}.jsonl`);
75
+ }
76
+ function appendLog(entry) {
77
+ try {
78
+ fs.appendFileSync(logFile(), JSON.stringify(entry) + '\n');
79
+ }
80
+ catch { /* never crash caller */ }
81
+ }
82
+ // ── AARMiddleware integration (Sprint 184) ────────────────────────────────────
83
+ // Lazy import to avoid circular dependency — AARMiddleware may import OMEL
84
+ function writeAAREntry(operation, approved, risk_score, response_ms) {
85
+ try {
86
+ const aarDir = path.join((0, engine_paths_1.resolveEnginePaths)().root, 'logs', 'aar');
87
+ fs.mkdirSync(aarDir, { recursive: true });
88
+ const date = new Date().toISOString().slice(0, 10);
89
+ const aarFile = path.join(aarDir, `${date}.jsonl`);
90
+ const entry = {
91
+ ts: new Date().toISOString(),
92
+ source: 'omel_human_brake',
93
+ operation,
94
+ approved,
95
+ risk_score,
96
+ response_ms,
97
+ receipt_hash: Buffer.from(`${operation}${Date.now()}`).toString('base64').slice(0, 32),
98
+ };
99
+ fs.appendFileSync(aarFile, JSON.stringify(entry) + '\n');
100
+ }
101
+ catch { /* non-fatal */ }
102
+ }
103
+ // ── Telegram helpers ──────────────────────────────────────────────────────────
104
+ const HIGH_RISK_OP_SET = new Set([
105
+ 'file_delete', 'git_reset_hard', 'env_change', 'schema_migration', 'bulk_overwrite',
106
+ ]);
107
+ const HIGH_RISK_PATH_FRAGMENTS = ['orchestrate-agents', 'clawrouter', '.env'];
108
+ function sendTelegram(message) {
109
+ const botToken = process.env.TELEGRAM_BOT_TOKEN || '';
110
+ const chatId = process.env.OWNER_TELEGRAM_CHAT_ID || '';
111
+ if (!botToken || !chatId)
112
+ return;
113
+ const payload = JSON.stringify({ chat_id: chatId, text: message, parse_mode: 'Markdown' });
114
+ try {
115
+ const req = https.request({
116
+ hostname: 'api.telegram.org',
117
+ path: `/bot${botToken}/sendMessage`,
118
+ method: 'POST',
119
+ headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
120
+ });
121
+ req.on('error', () => { }); // fire-and-forget, never crash
122
+ req.write(payload);
123
+ req.end();
124
+ }
125
+ catch { /* silent */ }
126
+ }
127
+ function getUpdates(offset, longPollSeconds) {
128
+ return new Promise((resolve) => {
129
+ const botToken = process.env.TELEGRAM_BOT_TOKEN || '';
130
+ if (!botToken) {
131
+ resolve([]);
132
+ return;
133
+ }
134
+ const req = https.request({
135
+ hostname: 'api.telegram.org',
136
+ path: `/bot${botToken}/getUpdates?offset=${offset}&timeout=${longPollSeconds}`,
137
+ method: 'GET',
138
+ }, (res) => {
139
+ let raw = '';
140
+ res.on('data', (chunk) => { raw += chunk; });
141
+ res.on('end', () => {
142
+ try {
143
+ const parsed = JSON.parse(raw);
144
+ resolve(parsed.ok ? parsed.result : []);
145
+ }
146
+ catch {
147
+ resolve([]);
148
+ }
149
+ });
150
+ });
151
+ req.on('error', () => resolve([]));
152
+ req.setTimeout((longPollSeconds + 10) * 1000, () => { req.destroy(); resolve([]); });
153
+ req.end();
154
+ });
155
+ }
156
+ // ── HUMAN_BRAKE_DISABLED daily reminder (Sprint 184) ─────────────────────────
157
+ const DISABLED_REMINDER_FILE = path.join(LOGS_DIR, 'brake-disabled-reminder.json');
158
+ function sendDisabledReminderIfNeeded() {
159
+ const today = new Date().toISOString().slice(0, 10);
160
+ try {
161
+ let lastSent = '';
162
+ if (fs.existsSync(DISABLED_REMINDER_FILE)) {
163
+ lastSent = JSON.parse(fs.readFileSync(DISABLED_REMINDER_FILE, 'utf-8')).last_sent ?? '';
164
+ }
165
+ if (lastSent === today)
166
+ return; // already sent today
167
+ sendTelegram(`⚠️ *[OMEL HumanBrake] Brake DISABLED*\n` +
168
+ `\`HUMAN_BRAKE_DISABLED=true\` is set in your environment.\n` +
169
+ `High-risk operations will NOT require approval.\n` +
170
+ `This reminder fires daily until the flag is removed.`);
171
+ fs.writeFileSync(DISABLED_REMINDER_FILE, JSON.stringify({ last_sent: today }), 'utf-8');
172
+ }
173
+ catch { /* non-fatal */ }
174
+ }
175
+ // ── HumanBrake class ──────────────────────────────────────────────────────────
176
+ class HumanBrake {
177
+ /**
178
+ * Returns true if operation is a known HighRiskOp type,
179
+ * OR if context.filePath touches protected files.
180
+ */
181
+ isHighRisk(operation, context = {}) {
182
+ // Gate by the score-threshold the docstring on getRiskScore already
183
+ // promises ("High (8-10): requires approval"). Catches file_delete (9),
184
+ // git_reset_hard (10), env_change (8), schema_migration (8), and any
185
+ // op bumped to ≥8 by HIGH_RISK_PATH_FRAGMENTS or size context.
186
+ // Drops bulk_overwrite (7) — was sweep-catching every routine source
187
+ // edit and forcing a 5-min Telegram approval wait per task, blocking
188
+ // autonomous swarm runs.
189
+ return this.getRiskScore(operation, context) >= 8;
190
+ }
191
+ /**
192
+ * Returns a risk score 1-10 for the given operation + context.
193
+ * High (8-10): requires approval. Medium (5-7): log only. Low (1-4): silent pass.
194
+ *
195
+ * Context fields considered:
196
+ * - filePath: if it's an orchestrator core file, score bumped to max
197
+ * - size: bulk ops on large files get higher score
198
+ */
199
+ getRiskScore(operation, context = {}) {
200
+ let score = RISK_SCORES[operation] ?? 5; // default 5 (medium) for unknown ops
201
+ // Bump to 10 if touching core orchestrator or config files
202
+ const filePath = String(context.filePath || '');
203
+ if (HIGH_RISK_PATH_FRAGMENTS.some(f => filePath.includes(f))) {
204
+ score = Math.max(score, 9);
205
+ }
206
+ // Bump for large file bulk ops
207
+ const sizeBytes = Number(context.sizeBytes || 0);
208
+ if (operation === 'bulk_overwrite' && sizeBytes > 50_000) {
209
+ score = Math.max(score, 9);
210
+ }
211
+ return Math.min(10, Math.max(1, score));
212
+ }
213
+ /**
214
+ * Send Telegram approval request and poll for up to 5 minutes.
215
+ * Returns ApprovalResult: approved on 'yes'/'approve', rejected on other reply or timeout.
216
+ * Logs response time and emits AAR entry (Sprint 184).
217
+ */
218
+ async requireApproval(operation, context = {}) {
219
+ const ts = new Date().toISOString();
220
+ const risk_score = this.getRiskScore(operation, context);
221
+ const requestedAt = Date.now();
222
+ // ── HUMAN_BRAKE_DISABLED bypass ──────────────────────────────────────────
223
+ if (process.env.HUMAN_BRAKE_DISABLED === 'true') {
224
+ sendDisabledReminderIfNeeded();
225
+ const response_ms = Date.now() - requestedAt;
226
+ appendLog({ event: 'brake_disabled', operation, risk_score, response_ms, ts });
227
+ writeAAREntry(operation, true, risk_score, response_ms);
228
+ return { approved: true, approvedBy: 'HUMAN_BRAKE_DISABLED', risk_score, response_ms, ts };
229
+ }
230
+ // ── Notify operator via Telegram ─────────────────────────────────────────
231
+ sendTelegram(`🛑 *[OMEL HumanBrake] Approval Required*\n` +
232
+ `Operation: \`${operation}\`\n` +
233
+ `Risk Score: *${risk_score}/10*\n` +
234
+ `Requested: ${ts}\n\n` +
235
+ `Reply *yes* or *approve* to allow.\n` +
236
+ `Any other reply or 5-minute timeout = ABORT.`);
237
+ appendLog({ event: 'approval_requested', operation, risk_score, ts });
238
+ // ── Poll Telegram for up to 5 minutes ────────────────────────────────────
239
+ const chatId = process.env.OWNER_TELEGRAM_CHAT_ID || '';
240
+ const deadline = Date.now() + 5 * 60 * 1000;
241
+ let offset = 0;
242
+ while (Date.now() < deadline) {
243
+ const remainingMs = deadline - Date.now();
244
+ if (remainingMs <= 0)
245
+ break;
246
+ const pollSecs = Math.min(30, Math.floor(remainingMs / 1000));
247
+ if (pollSecs <= 0)
248
+ break;
249
+ const updates = await getUpdates(offset, pollSecs);
250
+ for (const update of updates) {
251
+ if (typeof update.update_id === 'number') {
252
+ offset = update.update_id + 1;
253
+ }
254
+ const msg = update.message || update.edited_message;
255
+ if (!msg)
256
+ continue;
257
+ if (chatId && String(msg.chat?.id) !== chatId)
258
+ continue;
259
+ const text = String(msg.text || '').trim().toLowerCase();
260
+ if (text === 'yes' || text === 'approve') {
261
+ const approvedTs = new Date().toISOString();
262
+ const response_ms = Date.now() - requestedAt;
263
+ appendLog({ event: 'approved', operation, risk_score, approvedBy: 'telegram', response_ms, ts: approvedTs });
264
+ writeAAREntry(operation, true, risk_score, response_ms);
265
+ return { approved: true, approvedBy: 'telegram', risk_score, response_ms, ts: approvedTs };
266
+ }
267
+ // Any non-approval reply → reject immediately
268
+ const rejectedTs = new Date().toISOString();
269
+ const response_ms = Date.now() - requestedAt;
270
+ appendLog({ event: 'rejected', operation, risk_score, reason: 'rejected', replyText: text, response_ms, ts: rejectedTs });
271
+ writeAAREntry(operation, false, risk_score, response_ms);
272
+ sendTelegram(`❌ *[OMEL HumanBrake] ABORTED*\nOperation \`${operation}\` rejected by operator.`);
273
+ return { approved: false, reason: 'rejected', risk_score, response_ms, ts: rejectedTs };
274
+ }
275
+ }
276
+ // ── Timeout ──────────────────────────────────────────────────────────────
277
+ const timeoutTs = new Date().toISOString();
278
+ const response_ms = Date.now() - requestedAt;
279
+ appendLog({ event: 'timeout_abort', operation, risk_score, reason: 'timeout', response_ms, ts: timeoutTs });
280
+ writeAAREntry(operation, false, risk_score, response_ms);
281
+ sendTelegram(`⏰ *[OMEL HumanBrake] TIMEOUT — ABORTED*\n` +
282
+ `Operation \`${operation}\` aborted — no response within 5 minutes.\n` +
283
+ `Risk Score: *${risk_score}/10*`);
284
+ return { approved: false, reason: 'timeout', risk_score, response_ms, ts: timeoutTs };
285
+ }
286
+ }
287
+ exports.HumanBrake = HumanBrake;
288
+ // ── Singleton export ──────────────────────────────────────────────────────────
289
+ exports.humanBrake = new HumanBrake();
@@ -0,0 +1,10 @@
1
+ /**
2
+ * OMEL (Operational Memory & Execution Ledger) safety cluster — Phase 3b-3 Wave B.
3
+ * Summer Yu §8 measures: credential vault, human-brake kill switch, phantom
4
+ * workspace sandbox, wipe-witness tamper ledger. Self-contained, stdlib-only;
5
+ * roots resolved via engine-paths (<root>/logs/omel, <root>/logs/aar).
6
+ */
7
+ export * from './credential-vault';
8
+ export * from './human-brake';
9
+ export * from './phantom-workspace';
10
+ export * from './wipe-witness';
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ /**
18
+ * OMEL (Operational Memory & Execution Ledger) safety cluster — Phase 3b-3 Wave B.
19
+ * Summer Yu §8 measures: credential vault, human-brake kill switch, phantom
20
+ * workspace sandbox, wipe-witness tamper ledger. Self-contained, stdlib-only;
21
+ * roots resolved via engine-paths (<root>/logs/omel, <root>/logs/aar).
22
+ */
23
+ __exportStar(require("./credential-vault"), exports);
24
+ __exportStar(require("./human-brake"), exports);
25
+ __exportStar(require("./phantom-workspace"), exports);
26
+ __exportStar(require("./wipe-witness"), exports);