outcome-cli 1.0.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 (113) hide show
  1. package/README.md +261 -0
  2. package/package.json +95 -0
  3. package/src/agents/README.md +139 -0
  4. package/src/agents/adapters/anthropic.adapter.ts +166 -0
  5. package/src/agents/adapters/dalle.adapter.ts +145 -0
  6. package/src/agents/adapters/gemini.adapter.ts +134 -0
  7. package/src/agents/adapters/imagen.adapter.ts +106 -0
  8. package/src/agents/adapters/nano-banana.adapter.ts +129 -0
  9. package/src/agents/adapters/openai.adapter.ts +165 -0
  10. package/src/agents/adapters/veo.adapter.ts +130 -0
  11. package/src/agents/agent.schema.property.test.ts +379 -0
  12. package/src/agents/agent.schema.test.ts +148 -0
  13. package/src/agents/agent.schema.ts +263 -0
  14. package/src/agents/index.ts +60 -0
  15. package/src/agents/registered-agent.schema.ts +356 -0
  16. package/src/agents/registry.ts +97 -0
  17. package/src/agents/tournament-configs.property.test.ts +266 -0
  18. package/src/cli/README.md +145 -0
  19. package/src/cli/commands/define.ts +79 -0
  20. package/src/cli/commands/list.ts +46 -0
  21. package/src/cli/commands/logs.ts +83 -0
  22. package/src/cli/commands/run.ts +416 -0
  23. package/src/cli/commands/verify.ts +110 -0
  24. package/src/cli/index.ts +81 -0
  25. package/src/config/README.md +128 -0
  26. package/src/config/env.ts +262 -0
  27. package/src/config/index.ts +19 -0
  28. package/src/eval/README.md +318 -0
  29. package/src/eval/ai-judge.test.ts +435 -0
  30. package/src/eval/ai-judge.ts +368 -0
  31. package/src/eval/code-validators.ts +414 -0
  32. package/src/eval/evaluateOutcome.property.test.ts +1174 -0
  33. package/src/eval/evaluateOutcome.ts +591 -0
  34. package/src/eval/immigration-validators.ts +122 -0
  35. package/src/eval/index.ts +90 -0
  36. package/src/eval/judge-cache.ts +402 -0
  37. package/src/eval/tournament-validators.property.test.ts +439 -0
  38. package/src/eval/validators.property.test.ts +1118 -0
  39. package/src/eval/validators.ts +1199 -0
  40. package/src/eval/weighted-scorer.ts +285 -0
  41. package/src/index.ts +17 -0
  42. package/src/league/README.md +188 -0
  43. package/src/league/health-check.ts +353 -0
  44. package/src/league/index.ts +93 -0
  45. package/src/league/killAgent.ts +151 -0
  46. package/src/league/league.test.ts +1151 -0
  47. package/src/league/runLeague.ts +843 -0
  48. package/src/league/scoreAgent.ts +175 -0
  49. package/src/modules/omnibridge/__tests__/.gitkeep +1 -0
  50. package/src/modules/omnibridge/__tests__/auth-tunnel.property.test.ts +524 -0
  51. package/src/modules/omnibridge/__tests__/deterministic-logger.property.test.ts +965 -0
  52. package/src/modules/omnibridge/__tests__/ghost-api.property.test.ts +461 -0
  53. package/src/modules/omnibridge/__tests__/omnibridge-integration.test.ts +542 -0
  54. package/src/modules/omnibridge/__tests__/parallel-executor.property.test.ts +671 -0
  55. package/src/modules/omnibridge/__tests__/semantic-normalizer.property.test.ts +521 -0
  56. package/src/modules/omnibridge/__tests__/semantic-normalizer.test.ts +254 -0
  57. package/src/modules/omnibridge/__tests__/session-vault.property.test.ts +367 -0
  58. package/src/modules/omnibridge/__tests__/shadow-session.property.test.ts +523 -0
  59. package/src/modules/omnibridge/__tests__/triangulation-engine.property.test.ts +292 -0
  60. package/src/modules/omnibridge/__tests__/verification-engine.property.test.ts +769 -0
  61. package/src/modules/omnibridge/api/.gitkeep +1 -0
  62. package/src/modules/omnibridge/api/ghost-api.ts +1087 -0
  63. package/src/modules/omnibridge/auth/.gitkeep +1 -0
  64. package/src/modules/omnibridge/auth/auth-tunnel.ts +843 -0
  65. package/src/modules/omnibridge/auth/session-vault.ts +577 -0
  66. package/src/modules/omnibridge/core/.gitkeep +1 -0
  67. package/src/modules/omnibridge/core/semantic-normalizer.ts +702 -0
  68. package/src/modules/omnibridge/core/triangulation-engine.ts +530 -0
  69. package/src/modules/omnibridge/core/types.ts +610 -0
  70. package/src/modules/omnibridge/execution/.gitkeep +1 -0
  71. package/src/modules/omnibridge/execution/deterministic-logger.ts +629 -0
  72. package/src/modules/omnibridge/execution/parallel-executor.ts +542 -0
  73. package/src/modules/omnibridge/execution/shadow-session.ts +794 -0
  74. package/src/modules/omnibridge/index.ts +212 -0
  75. package/src/modules/omnibridge/omnibridge.ts +510 -0
  76. package/src/modules/omnibridge/verification/.gitkeep +1 -0
  77. package/src/modules/omnibridge/verification/verification-engine.ts +783 -0
  78. package/src/outcomes/README.md +75 -0
  79. package/src/outcomes/acquire-pilot-customer.ts +297 -0
  80. package/src/outcomes/code-delivery-outcomes.ts +89 -0
  81. package/src/outcomes/code-outcomes.ts +256 -0
  82. package/src/outcomes/code_review_battle.test.ts +135 -0
  83. package/src/outcomes/code_review_battle.ts +135 -0
  84. package/src/outcomes/cold_email_battle.ts +97 -0
  85. package/src/outcomes/content_creation_battle.ts +160 -0
  86. package/src/outcomes/f1_stem_opt_compliance.ts +61 -0
  87. package/src/outcomes/index.ts +107 -0
  88. package/src/outcomes/lead_gen_battle.test.ts +113 -0
  89. package/src/outcomes/lead_gen_battle.ts +99 -0
  90. package/src/outcomes/outcome.schema.property.test.ts +229 -0
  91. package/src/outcomes/outcome.schema.ts +187 -0
  92. package/src/outcomes/qualified_sales_interest.ts +118 -0
  93. package/src/outcomes/swarm_planner.property.test.ts +370 -0
  94. package/src/outcomes/swarm_planner.ts +96 -0
  95. package/src/outcomes/web_extraction.ts +234 -0
  96. package/src/runtime/README.md +220 -0
  97. package/src/runtime/agentRunner.test.ts +341 -0
  98. package/src/runtime/agentRunner.ts +746 -0
  99. package/src/runtime/claudeAdapter.ts +232 -0
  100. package/src/runtime/costTracker.ts +123 -0
  101. package/src/runtime/index.ts +34 -0
  102. package/src/runtime/modelAdapter.property.test.ts +305 -0
  103. package/src/runtime/modelAdapter.ts +144 -0
  104. package/src/runtime/openaiAdapter.ts +235 -0
  105. package/src/utils/README.md +122 -0
  106. package/src/utils/command-runner.ts +134 -0
  107. package/src/utils/cost-guard.ts +379 -0
  108. package/src/utils/errors.test.ts +290 -0
  109. package/src/utils/errors.ts +442 -0
  110. package/src/utils/index.ts +37 -0
  111. package/src/utils/logger.test.ts +361 -0
  112. package/src/utils/logger.ts +419 -0
  113. package/src/utils/output-parsers.ts +216 -0
@@ -0,0 +1,577 @@
1
+ /**
2
+ * Session Vault - Zero-Knowledge Credential Storage
3
+ *
4
+ * Implements secure, encrypted storage for session states.
5
+ * Credentials are NEVER exposed to LLM context - only capability tokens.
6
+ *
7
+ * Requirements: 6.1, 6.2, 6.3, 6.4, 6.5, 6.6, 6.7
8
+ */
9
+
10
+ import { randomBytes, createCipheriv, createDecipheriv } from 'crypto';
11
+ import type {
12
+ EncryptedBlob,
13
+ SerializedSession,
14
+ ActionScope,
15
+ CapabilityToken,
16
+ OmniBridgeError,
17
+ } from '../core/types.js';
18
+
19
+ // =============================================================================
20
+ // Types
21
+ // =============================================================================
22
+
23
+ /**
24
+ * Raw session data before encryption.
25
+ */
26
+ export interface RawSessionData {
27
+ cookies: string;
28
+ localStorage: string;
29
+ sessionStorage: string;
30
+ }
31
+
32
+ /**
33
+ * Credential access log entry (redacted).
34
+ */
35
+ export interface CredentialAccessLog {
36
+ timestamp: number;
37
+ tokenId: string;
38
+ domain: string;
39
+ action: 'issued' | 'used' | 'revoked' | 'violation';
40
+ /** Redacted details - never contains actual credentials */
41
+ details: string;
42
+ }
43
+
44
+ /**
45
+ * Session Vault configuration.
46
+ */
47
+ export interface SessionVaultConfig {
48
+ /** Encryption key (32 bytes for AES-256) */
49
+ encryptionKey: Buffer;
50
+ /** Default token expiration in milliseconds (default: 1 hour) */
51
+ defaultTokenExpirationMs?: number;
52
+ /** Default max executions per token (default: 100) */
53
+ defaultMaxExecutions?: number;
54
+ }
55
+
56
+ /**
57
+ * Result of a scope check.
58
+ */
59
+ export interface ScopeCheckResult {
60
+ allowed: boolean;
61
+ error?: OmniBridgeError;
62
+ }
63
+
64
+ // =============================================================================
65
+ // Session Vault Implementation
66
+ // =============================================================================
67
+
68
+ /**
69
+ * Session Vault - Zero-Knowledge Credential Storage
70
+ *
71
+ * Key security properties:
72
+ * - All stored data is AES-256-GCM encrypted
73
+ * - Credentials never exposed to LLM context
74
+ * - Capability tokens contain only action permissions, not credentials
75
+ * - All access is logged (with redacted details)
76
+ */
77
+ export class SessionVault {
78
+ private readonly encryptionKey: Buffer;
79
+ private readonly defaultTokenExpirationMs: number;
80
+ private readonly defaultMaxExecutions: number;
81
+
82
+ /** Encrypted session storage by domain */
83
+ private sessions: Map<string, SerializedSession> = new Map();
84
+
85
+ /** Active capability tokens by ID */
86
+ private tokens: Map<string, CapabilityToken> = new Map();
87
+
88
+ /** Token to session mapping */
89
+ private tokenToSession: Map<string, string> = new Map();
90
+
91
+ /** Access logs (redacted) */
92
+ private accessLogs: CredentialAccessLog[] = [];
93
+
94
+ /** Revoked token IDs */
95
+ private revokedTokens: Set<string> = new Set();
96
+
97
+ constructor(config: SessionVaultConfig) {
98
+ if (config.encryptionKey.length !== 32) {
99
+ throw new Error('Encryption key must be 32 bytes for AES-256');
100
+ }
101
+ this.encryptionKey = config.encryptionKey;
102
+ this.defaultTokenExpirationMs = config.defaultTokenExpirationMs ?? 3600000; // 1 hour
103
+ this.defaultMaxExecutions = config.defaultMaxExecutions ?? 100;
104
+ }
105
+
106
+ // ===========================================================================
107
+ // Encryption Methods
108
+ // ===========================================================================
109
+
110
+ /**
111
+ * Encrypt data using AES-256-GCM.
112
+ * Returns an EncryptedBlob with the encrypted data and IV.
113
+ */
114
+ encrypt(data: string): EncryptedBlob {
115
+ const iv = randomBytes(16);
116
+ const cipher = createCipheriv('aes-256-gcm', this.encryptionKey, iv);
117
+
118
+ let encrypted = cipher.update(data, 'utf8', 'base64');
119
+ encrypted += cipher.final('base64');
120
+
121
+ // Append auth tag to encrypted data
122
+ const authTag = cipher.getAuthTag();
123
+ const encryptedWithTag = encrypted + ':' + authTag.toString('base64');
124
+
125
+ return {
126
+ data: encryptedWithTag,
127
+ iv: iv.toString('base64'),
128
+ algorithm: 'AES-256-GCM',
129
+ };
130
+ }
131
+
132
+ /**
133
+ * Decrypt an EncryptedBlob back to plaintext.
134
+ * Validates the encrypted data format before attempting decryption.
135
+ */
136
+ decrypt(blob: EncryptedBlob): string {
137
+ // Validate encrypted data format
138
+ if (!blob.data || !blob.data.includes(':')) {
139
+ throw new Error('Invalid encrypted data format: missing auth tag separator');
140
+ }
141
+
142
+ const iv = Buffer.from(blob.iv, 'base64');
143
+ const [encryptedData, authTagBase64] = blob.data.split(':');
144
+
145
+ if (!encryptedData || !authTagBase64) {
146
+ throw new Error('Invalid encrypted data format: malformed data or auth tag');
147
+ }
148
+
149
+ const authTag = Buffer.from(authTagBase64, 'base64');
150
+
151
+ const decipher = createDecipheriv('aes-256-gcm', this.encryptionKey, iv);
152
+ decipher.setAuthTag(authTag);
153
+
154
+ let decrypted = decipher.update(encryptedData, 'base64', 'utf8');
155
+ decrypted += decipher.final('utf8');
156
+
157
+ return decrypted;
158
+ }
159
+
160
+ // ===========================================================================
161
+ // Session Storage Methods
162
+ // ===========================================================================
163
+
164
+ /**
165
+ * Store a session with encrypted data.
166
+ * Raw credentials are encrypted immediately and never stored in plaintext.
167
+ */
168
+ async store(domain: string, rawData: RawSessionData, expiresAt?: number): Promise<void> {
169
+ const session: SerializedSession = {
170
+ cookies: this.encrypt(rawData.cookies),
171
+ localStorage: this.encrypt(rawData.localStorage),
172
+ sessionStorage: this.encrypt(rawData.sessionStorage),
173
+ expiresAt: expiresAt ?? Date.now() + 86400000, // Default: 24 hours
174
+ };
175
+
176
+ this.sessions.set(domain, session);
177
+
178
+ this.logAccess({
179
+ timestamp: Date.now(),
180
+ tokenId: 'system',
181
+ domain,
182
+ action: 'issued',
183
+ details: `Session stored for domain (expires: ${new Date(session.expiresAt).toISOString()})`,
184
+ });
185
+ }
186
+
187
+ /**
188
+ * Retrieve an encrypted session for a domain.
189
+ * Returns null if no session exists or if expired.
190
+ */
191
+ async retrieve(domain: string): Promise<SerializedSession | null> {
192
+ const session = this.sessions.get(domain);
193
+
194
+ if (!session) {
195
+ return null;
196
+ }
197
+
198
+ // Check expiration
199
+ if (session.expiresAt < Date.now()) {
200
+ this.sessions.delete(domain);
201
+ return null;
202
+ }
203
+
204
+ return session;
205
+ }
206
+
207
+ /**
208
+ * Decrypt and retrieve raw session data.
209
+ * This should only be used internally by Shadow Sessions.
210
+ */
211
+ async retrieveDecrypted(domain: string): Promise<RawSessionData | null> {
212
+ const session = await this.retrieve(domain);
213
+
214
+ if (!session) {
215
+ return null;
216
+ }
217
+
218
+ return {
219
+ cookies: this.decrypt(session.cookies),
220
+ localStorage: this.decrypt(session.localStorage),
221
+ sessionStorage: this.decrypt(session.sessionStorage),
222
+ };
223
+ }
224
+
225
+ // ===========================================================================
226
+ // Capability Token Methods
227
+ // ===========================================================================
228
+
229
+ /**
230
+ * Generate a unique token ID.
231
+ */
232
+ private generateTokenId(): string {
233
+ return `cap_${randomBytes(16).toString('hex')}`;
234
+ }
235
+
236
+ /**
237
+ * Issue a Capability Token scoped to specific actions.
238
+ * The token contains ONLY capabilities, never raw credentials.
239
+ *
240
+ * Requirements: 6.2, 6.3
241
+ */
242
+ async issueToken(domain: string, scope: Partial<ActionScope>): Promise<CapabilityToken> {
243
+ // Verify session exists
244
+ const session = await this.retrieve(domain);
245
+ if (!session) {
246
+ throw new Error(`No session found for domain: ${domain}`);
247
+ }
248
+
249
+ const tokenId = this.generateTokenId();
250
+ const now = Date.now();
251
+
252
+ const fullScope: ActionScope = {
253
+ allowedActions: scope.allowedActions ?? [],
254
+ blockedActions: scope.blockedActions ?? [],
255
+ maxExecutions: scope.maxExecutions ?? this.defaultMaxExecutions,
256
+ expiresAt: scope.expiresAt ?? now + this.defaultTokenExpirationMs,
257
+ };
258
+
259
+ const token: CapabilityToken = {
260
+ id: tokenId,
261
+ domain,
262
+ scope: fullScope,
263
+ issuedAt: now,
264
+ executionCount: 0,
265
+ };
266
+
267
+ this.tokens.set(tokenId, token);
268
+ this.tokenToSession.set(tokenId, domain);
269
+
270
+ this.logAccess({
271
+ timestamp: now,
272
+ tokenId,
273
+ domain,
274
+ action: 'issued',
275
+ details: `Token issued with ${fullScope.allowedActions.length} allowed actions, ${fullScope.blockedActions.length} blocked actions`,
276
+ });
277
+
278
+ return token;
279
+ }
280
+
281
+ /**
282
+ * Get a token by ID.
283
+ */
284
+ getToken(tokenId: string): CapabilityToken | null {
285
+ if (this.revokedTokens.has(tokenId)) {
286
+ return null;
287
+ }
288
+ return this.tokens.get(tokenId) ?? null;
289
+ }
290
+
291
+ /**
292
+ * Check if an action is allowed by a token's scope.
293
+ *
294
+ * Requirements: 6.2, 6.3
295
+ */
296
+ checkScope(tokenId: string, action: string): ScopeCheckResult {
297
+ const token = this.getToken(tokenId);
298
+
299
+ if (!token) {
300
+ return {
301
+ allowed: false,
302
+ error: {
303
+ type: 'scope_violation',
304
+ attemptedAction: action,
305
+ allowedActions: [],
306
+ },
307
+ };
308
+ }
309
+
310
+ // Check if token is expired
311
+ if (token.scope.expiresAt < Date.now()) {
312
+ this.revokeToken(tokenId);
313
+ return {
314
+ allowed: false,
315
+ error: {
316
+ type: 'scope_violation',
317
+ attemptedAction: action,
318
+ allowedActions: token.scope.allowedActions,
319
+ },
320
+ };
321
+ }
322
+
323
+ // Check if max executions exceeded
324
+ if (token.executionCount >= token.scope.maxExecutions) {
325
+ return {
326
+ allowed: false,
327
+ error: {
328
+ type: 'scope_violation',
329
+ attemptedAction: action,
330
+ allowedActions: token.scope.allowedActions,
331
+ },
332
+ };
333
+ }
334
+
335
+ // Check if action is explicitly blocked
336
+ if (token.scope.blockedActions.includes(action)) {
337
+ return {
338
+ allowed: false,
339
+ error: {
340
+ type: 'scope_violation',
341
+ attemptedAction: action,
342
+ allowedActions: token.scope.allowedActions,
343
+ },
344
+ };
345
+ }
346
+
347
+ // Check if action is in allowed list (if allowedActions is non-empty)
348
+ if (token.scope.allowedActions.length > 0 && !token.scope.allowedActions.includes(action)) {
349
+ return {
350
+ allowed: false,
351
+ error: {
352
+ type: 'scope_violation',
353
+ attemptedAction: action,
354
+ allowedActions: token.scope.allowedActions,
355
+ },
356
+ };
357
+ }
358
+
359
+ return { allowed: true };
360
+ }
361
+
362
+ /**
363
+ * Use a token for an action.
364
+ * Increments execution count and logs access.
365
+ */
366
+ async useToken(tokenId: string, action: string): Promise<ScopeCheckResult> {
367
+ const scopeCheck = this.checkScope(tokenId, action);
368
+
369
+ if (!scopeCheck.allowed) {
370
+ // Log the violation
371
+ const token = this.tokens.get(tokenId);
372
+ this.logAccess({
373
+ timestamp: Date.now(),
374
+ tokenId,
375
+ domain: token?.domain ?? 'unknown',
376
+ action: 'violation',
377
+ details: `Attempted blocked action: ${action}`,
378
+ });
379
+ return scopeCheck;
380
+ }
381
+
382
+ // Increment execution count
383
+ const token = this.tokens.get(tokenId)!;
384
+ token.executionCount++;
385
+
386
+ this.logAccess({
387
+ timestamp: Date.now(),
388
+ tokenId,
389
+ domain: token.domain,
390
+ action: 'used',
391
+ details: `Action executed: ${action} (${token.executionCount}/${token.scope.maxExecutions})`,
392
+ });
393
+
394
+ return { allowed: true };
395
+ }
396
+
397
+ /**
398
+ * Revoke a Capability Token immediately.
399
+ *
400
+ * Requirements: 6.5
401
+ */
402
+ revokeToken(tokenId: string): void {
403
+ const token = this.tokens.get(tokenId);
404
+
405
+ if (token) {
406
+ this.revokedTokens.add(tokenId);
407
+ this.tokens.delete(tokenId);
408
+ this.tokenToSession.delete(tokenId);
409
+
410
+ this.logAccess({
411
+ timestamp: Date.now(),
412
+ tokenId,
413
+ domain: token.domain,
414
+ action: 'revoked',
415
+ details: 'Token revoked',
416
+ });
417
+ }
418
+ }
419
+
420
+ /**
421
+ * Revoke all tokens for a domain.
422
+ * Used when a bounty completes.
423
+ *
424
+ * Requirements: 6.5
425
+ */
426
+ revokeAllTokensForDomain(domain: string): number {
427
+ let revokedCount = 0;
428
+
429
+ for (const [tokenId, sessionDomain] of this.tokenToSession.entries()) {
430
+ if (sessionDomain === domain) {
431
+ this.revokeToken(tokenId);
432
+ revokedCount++;
433
+ }
434
+ }
435
+
436
+ return revokedCount;
437
+ }
438
+
439
+ /**
440
+ * Check if a token is revoked.
441
+ */
442
+ isTokenRevoked(tokenId: string): boolean {
443
+ return this.revokedTokens.has(tokenId);
444
+ }
445
+
446
+ // ===========================================================================
447
+ // Scope Violation Handling
448
+ // ===========================================================================
449
+
450
+ /**
451
+ * Handle a scope violation.
452
+ * Terminates the session and revokes the token immediately.
453
+ *
454
+ * Requirements: 6.7
455
+ */
456
+ async handleScopeViolation(
457
+ tokenId: string,
458
+ attemptedAction: string
459
+ ): Promise<{ terminated: boolean; error: OmniBridgeError }> {
460
+ const token = this.tokens.get(tokenId);
461
+ const domain = token?.domain ?? 'unknown';
462
+
463
+ // Revoke the token immediately
464
+ this.revokeToken(tokenId);
465
+
466
+ // Log the violation
467
+ this.logAccess({
468
+ timestamp: Date.now(),
469
+ tokenId,
470
+ domain,
471
+ action: 'violation',
472
+ details: `SECURITY: Scope violation - attempted action "${attemptedAction}" - token revoked and session terminated`,
473
+ });
474
+
475
+ return {
476
+ terminated: true,
477
+ error: {
478
+ type: 'scope_violation',
479
+ attemptedAction,
480
+ allowedActions: token?.scope.allowedActions ?? [],
481
+ },
482
+ };
483
+ }
484
+
485
+ // ===========================================================================
486
+ // Logging Methods
487
+ // ===========================================================================
488
+
489
+ /**
490
+ * Log a credential access event (redacted).
491
+ *
492
+ * Requirements: 6.6
493
+ */
494
+ private logAccess(entry: CredentialAccessLog): void {
495
+ this.accessLogs.push(entry);
496
+ }
497
+
498
+ /**
499
+ * Get access logs (redacted - no sensitive data).
500
+ */
501
+ getAccessLogs(domain?: string): CredentialAccessLog[] {
502
+ if (domain) {
503
+ return this.accessLogs.filter((log) => log.domain === domain);
504
+ }
505
+ return [...this.accessLogs];
506
+ }
507
+
508
+ // ===========================================================================
509
+ // Utility Methods
510
+ // ===========================================================================
511
+
512
+ /**
513
+ * Check if a token contains raw credentials.
514
+ * Used for property testing - should always return false.
515
+ *
516
+ * Requirements: 6.1
517
+ */
518
+ tokenContainsCredentials(token: CapabilityToken): boolean {
519
+ // Check if any field contains credential-like patterns
520
+ const tokenStr = JSON.stringify(token);
521
+
522
+ // Patterns that would indicate raw credentials
523
+ const credentialPatterns = [
524
+ /password["\s]*:/i,
525
+ /api[_-]?key["\s]*:/i,
526
+ /secret["\s]*:/i,
527
+ /token["\s]*:["\s]*[a-zA-Z0-9]{20,}/i, // Long token values (not our token ID format)
528
+ /cookie["\s]*:/i,
529
+ /session["\s]*:/i,
530
+ /Bearer\s+[a-zA-Z0-9]/i,
531
+ /Basic\s+[a-zA-Z0-9]/i,
532
+ ];
533
+
534
+ for (const pattern of credentialPatterns) {
535
+ if (pattern.test(tokenStr)) {
536
+ return true;
537
+ }
538
+ }
539
+
540
+ return false;
541
+ }
542
+
543
+ /**
544
+ * Clear all data (for testing).
545
+ */
546
+ clear(): void {
547
+ this.sessions.clear();
548
+ this.tokens.clear();
549
+ this.tokenToSession.clear();
550
+ this.revokedTokens.clear();
551
+ this.accessLogs = [];
552
+ }
553
+ }
554
+
555
+ // =============================================================================
556
+ // Factory Function
557
+ // =============================================================================
558
+
559
+ /**
560
+ * Create a new Session Vault instance.
561
+ */
562
+ export function createSessionVault(config?: Partial<SessionVaultConfig>): SessionVault {
563
+ const encryptionKey = config?.encryptionKey ?? randomBytes(32);
564
+
565
+ return new SessionVault({
566
+ encryptionKey,
567
+ defaultTokenExpirationMs: config?.defaultTokenExpirationMs,
568
+ defaultMaxExecutions: config?.defaultMaxExecutions,
569
+ });
570
+ }
571
+
572
+ /**
573
+ * Generate a secure encryption key for the vault.
574
+ */
575
+ export function generateEncryptionKey(): Buffer {
576
+ return randomBytes(32);
577
+ }
@@ -0,0 +1 @@
1
+ # Core module - Semantic Normalizer, Triangulation Engine, Intent Types