@vibecheckai/cli 3.8.0 → 3.9.1

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 (37) hide show
  1. package/bin/runners/lib/agent-firewall/enforcement/index.js +98 -98
  2. package/bin/runners/lib/agent-firewall/enforcement/mode.js +318 -318
  3. package/bin/runners/lib/agent-firewall/enforcement/orchestrator.js +484 -484
  4. package/bin/runners/lib/agent-firewall/enforcement/proof-artifact.js +418 -418
  5. package/bin/runners/lib/agent-firewall/enforcement/verdict-v2.js +333 -333
  6. package/bin/runners/lib/agent-firewall/intent/alignment-engine.js +634 -622
  7. package/bin/runners/lib/agent-firewall/intent/index.js +102 -102
  8. package/bin/runners/lib/agent-firewall/intent/schema.js +352 -352
  9. package/bin/runners/lib/agent-firewall/intent/store.js +283 -283
  10. package/bin/runners/lib/agent-firewall/interceptor/base.js +7 -3
  11. package/bin/runners/lib/engine/ast-cache.js +210 -210
  12. package/bin/runners/lib/engine/auth-extractor.js +211 -211
  13. package/bin/runners/lib/engine/billing-extractor.js +112 -112
  14. package/bin/runners/lib/engine/enforcement-extractor.js +100 -100
  15. package/bin/runners/lib/engine/env-extractor.js +207 -207
  16. package/bin/runners/lib/engine/express-extractor.js +208 -208
  17. package/bin/runners/lib/engine/extractors.js +849 -849
  18. package/bin/runners/lib/engine/index.js +207 -207
  19. package/bin/runners/lib/engine/repo-index.js +514 -514
  20. package/bin/runners/lib/engine/types.js +124 -124
  21. package/bin/runners/lib/unified-cli-output.js +16 -0
  22. package/bin/runners/runCI.js +353 -0
  23. package/bin/runners/runCheckpoint.js +2 -2
  24. package/bin/runners/runIntent.js +906 -906
  25. package/bin/runners/runPacks.js +2089 -2089
  26. package/bin/runners/runReality.js +178 -1
  27. package/bin/runners/runShield.js +1282 -1282
  28. package/mcp-server/handlers/index.ts +2 -2
  29. package/mcp-server/handlers/tool-handler.ts +47 -8
  30. package/mcp-server/lib/executor.ts +5 -5
  31. package/mcp-server/lib/index.ts +14 -4
  32. package/mcp-server/lib/sandbox.test.ts +4 -4
  33. package/mcp-server/lib/sandbox.ts +2 -2
  34. package/mcp-server/package.json +1 -1
  35. package/mcp-server/registry.test.ts +18 -12
  36. package/mcp-server/tsconfig.json +1 -0
  37. package/package.json +2 -1
@@ -1,484 +1,484 @@
1
- /**
2
- * Firewall Orchestrator - Main Enforcement Entry Point
3
- *
4
- * ═══════════════════════════════════════════════════════════════════════════════
5
- * AGENT FIREWALL™ - ENFORCEMENT ORCHESTRATOR
6
- * ═══════════════════════════════════════════════════════════════════════════════
7
- *
8
- * The Agent Firewall is a mandatory enforcement layer that intercepts AI-generated
9
- * actions and BLOCKS them unless they are:
10
- *
11
- * 1) Explicitly aligned with declared user INTENT
12
- * 2) Structurally valid (routes, env vars, permissions exist)
13
- * 3) Proven real at runtime via Reality Mode
14
- *
15
- * If proof is missing → the action MUST NOT SHIP.
16
- *
17
- * This is enforcement infrastructure, not developer guidance.
18
- *
19
- * @module enforcement/orchestrator
20
- * @version 2.0.0
21
- */
22
-
23
- "use strict";
24
-
25
- const path = require("path");
26
- const fs = require("fs");
27
- const crypto = require("crypto");
28
-
29
- // Intent system
30
- const { IntentStore } = require("../intent/store");
31
- const { checkAlignmentBatch, VIOLATION_CODES } = require("../intent/alignment-engine");
32
- const { verifyIntentIntegrity, isIntentExpired } = require("../intent/schema");
33
-
34
- // Verdict system
35
- const { generateVerdict, VERDICT, OBSERVATION, MODE, createVerdictManifest } = require("./verdict-v2");
36
-
37
- // Proof system
38
- const { ProofCollection, determineRequiredProofs, PROOF_STATUS } = require("./proof-artifact");
39
-
40
- // Mode system
41
- const { ModeConfig, createModeEvent, formatModeBanner } = require("./mode");
42
-
43
- // Existing components
44
- const { resolveEvidence } = require("../evidence/resolver");
45
- const { evaluatePolicy } = require("../policy/engine");
46
- const { buildChangePacket, buildMultiFileChangePacket } = require("../change-packet/builder");
47
-
48
- /**
49
- * Change Event types
50
- */
51
- const CHANGE_EVENT_TYPE = {
52
- FILE_WRITE: "file_write",
53
- FILE_CREATE: "file_create",
54
- FILE_DELETE: "file_delete",
55
- ROUTE_ADD: "route_add",
56
- ROUTE_MODIFY: "route_modify",
57
- ENV_REF: "env_ref",
58
- PERMISSION_CHANGE: "permission_change",
59
- UI_STATE: "ui_state",
60
- CONFIG_CHANGE: "config_change",
61
- };
62
-
63
- /**
64
- * Normalize a raw change into a Change Event
65
- * @param {Object} rawChange - Raw change from interception
66
- * @returns {Object} Normalized Change Event
67
- */
68
- function normalizeChangeEvent(rawChange) {
69
- return {
70
- type: rawChange.type || CHANGE_EVENT_TYPE.FILE_WRITE,
71
- location: rawChange.path || rawChange.file || rawChange.location,
72
- diff: rawChange.diff || null,
73
- content: rawChange.content || rawChange.diff?.after || null,
74
- resource: rawChange.resource || rawChange.path || null,
75
- domain: rawChange.domain || classifyDomain(rawChange.path),
76
- claims: rawChange.claims || [],
77
- method: rawChange.method || null,
78
- env_exists: rawChange.env_exists,
79
- includes_tests: rawChange.includes_tests || false,
80
- file_count: rawChange.file_count || 1,
81
- timestamp: rawChange.timestamp || new Date().toISOString(),
82
- };
83
- }
84
-
85
- /**
86
- * Classify file domain
87
- * @param {string} filePath - File path
88
- * @returns {string} Domain
89
- */
90
- function classifyDomain(filePath) {
91
- if (!filePath) return "general";
92
- const s = filePath.toLowerCase();
93
- if (s.includes("auth")) return "auth";
94
- if (s.includes("stripe") || s.includes("payment")) return "payments";
95
- if (s.includes("routes") || s.includes("router") || s.includes("api")) return "routes";
96
- if (s.includes("schema") || s.includes("contract")) return "contracts";
97
- if (s.includes("ui") || s.includes("components") || s.includes("pages")) return "ui";
98
- if (s.includes("database") || s.includes("prisma") || s.includes("migration")) return "database";
99
- return "general";
100
- }
101
-
102
- /**
103
- * Firewall Orchestrator
104
- *
105
- * Main entry point for all enforcement operations.
106
- */
107
- class FirewallOrchestrator {
108
- constructor(projectRoot) {
109
- this.projectRoot = projectRoot;
110
- this.intentStore = new IntentStore(projectRoot);
111
- this.modeConfig = new ModeConfig(projectRoot);
112
- this.runId = crypto.randomBytes(8).toString("hex");
113
- this.startTime = Date.now();
114
- }
115
-
116
- /**
117
- * Main enforcement entry point
118
- *
119
- * @param {Object} params - Enforcement parameters
120
- * @param {Object[]} params.changes - Array of raw changes
121
- * @param {string} params.agentId - Agent identifier
122
- * @param {Object} params.proposal - Structured proposal (optional)
123
- * @param {Object} params.options - Options
124
- * @returns {Promise<Object>} Enforcement result with verdict
125
- */
126
- async enforce({ changes, agentId, proposal = null, options = {} }) {
127
- const mode = this.modeConfig.getMode();
128
- const result = {
129
- runId: this.runId,
130
- mode,
131
- blocked: false,
132
- verdict: null,
133
- violations: [],
134
- proofs: [],
135
- intent_hash: null,
136
- timestamp: new Date().toISOString(),
137
- };
138
-
139
- try {
140
- // Step 1: Get and validate intent
141
- const intent = this.intentStore.getCurrent();
142
- result.intent_hash = intent?.hash || null;
143
-
144
- // Verify intent integrity
145
- if (intent) {
146
- const integrityCheck = verifyIntentIntegrity(intent);
147
- if (!integrityCheck.valid) {
148
- result.violations.push({
149
- code: VIOLATION_CODES.INTENT_CORRUPTED,
150
- rule: "intent_integrity",
151
- message: `Intent integrity check failed: ${integrityCheck.reason}`,
152
- resource: "intent",
153
- intent_ref: "system",
154
- severity: "block",
155
- });
156
- }
157
-
158
- // Check expiration
159
- if (isIntentExpired(intent)) {
160
- result.violations.push({
161
- code: VIOLATION_CODES.INTENT_EXPIRED,
162
- rule: "intent_expiration",
163
- message: "Intent has expired",
164
- resource: "intent",
165
- intent_ref: "system",
166
- severity: "block",
167
- });
168
- }
169
- }
170
-
171
- // Step 2: Normalize change events
172
- const changeEvents = changes.map(normalizeChangeEvent);
173
-
174
- // Step 3: Run intent alignment check
175
- const alignmentResult = checkAlignmentBatch(intent, changeEvents);
176
- result.violations.push(...alignmentResult.violations);
177
-
178
- // Step 4: Extract claims and resolve evidence (existing system)
179
- const allClaims = changeEvents.flatMap(e => e.claims || []);
180
- const evidence = resolveEvidence(this.projectRoot, allClaims);
181
-
182
- // Step 5: Run policy engine (existing rules)
183
- const policyResult = await evaluatePolicy({
184
- policy: this.loadPolicy(),
185
- claims: allClaims,
186
- evidence,
187
- files: changeEvents.map(e => ({ path: e.location, domain: e.domain })),
188
- intent: intent?.summary || "",
189
- });
190
-
191
- // Convert policy violations to v2 format
192
- if (policyResult.violations) {
193
- const policyViolations = policyResult.violations.map(v => ({
194
- code: v.rule?.toUpperCase().replace(/-/g, "_") || "POLICY_VIOLATION",
195
- rule: v.rule || "unknown",
196
- message: v.message || "Policy violation",
197
- resource: v.claimId || v.file || "unknown",
198
- intent_ref: "policy",
199
- severity: "block",
200
- }));
201
- result.violations.push(...policyViolations);
202
- }
203
-
204
- // Step 6: Collect proofs (if required)
205
- const proofCollection = new ProofCollection();
206
- const requiredProofs = determineRequiredProofs(allClaims, intent);
207
-
208
- for (const proofType of requiredProofs) {
209
- proofCollection.require(proofType);
210
- }
211
-
212
- // In reality mode, we'd run actual verification here
213
- // For now, mark proofs based on evidence
214
- for (const e of evidence) {
215
- if (e.result === "PROVEN") {
216
- proofCollection.add({
217
- id: `prf-${e.claimId}`,
218
- type: this.claimTypeToProofType(allClaims.find(c => c.type === e.type)?.type),
219
- status: PROOF_STATUS.VERIFIED,
220
- target: e.pointer || e.claimId,
221
- trace: e.sources?.[0]?.pointer,
222
- });
223
- } else if (e.result === "UNPROVEN" || e.result === "CONTRADICTS") {
224
- proofCollection.add({
225
- id: `prf-${e.claimId}`,
226
- type: this.claimTypeToProofType(allClaims.find(c => c.type === e.type)?.type),
227
- status: PROOF_STATUS.FAILED,
228
- target: e.pointer || e.claimId,
229
- error: e.reason || "Could not verify claim",
230
- });
231
- }
232
- }
233
-
234
- result.proofs = proofCollection.getAll();
235
-
236
- // Step 7: Generate final verdict
237
- result.verdict = generateVerdict({
238
- alignmentResult: { violations: result.violations },
239
- proofs: result.proofs,
240
- mode,
241
- intent_hash: result.intent_hash,
242
- });
243
-
244
- result.blocked = result.verdict.blocked;
245
-
246
- // Step 8: Store change packet for audit
247
- const packet = buildMultiFileChangePacket({
248
- agentId,
249
- intent: intent?.summary || "NO INTENT",
250
- changes: changes.map((c, i) => ({
251
- filePath: changeEvents[i].location,
252
- diff: c.diff,
253
- claims: changeEvents[i].claims,
254
- })),
255
- evidence,
256
- verdict: {
257
- decision: result.verdict.decision,
258
- violations: result.violations,
259
- message: result.verdict.summary,
260
- },
261
- policy: this.loadPolicy(),
262
- proposal,
263
- });
264
-
265
- this.storePacket(packet);
266
-
267
- // Step 9: Store verdict manifest
268
- const manifest = createVerdictManifest(result.verdict, {
269
- runId: this.runId,
270
- agentId,
271
- fileCount: changes.length,
272
- });
273
- this.storeVerdictManifest(manifest);
274
-
275
- return result;
276
-
277
- } catch (error) {
278
- // On error, default to BLOCK
279
- result.violations.push({
280
- code: "ENFORCEMENT_ERROR",
281
- rule: "system",
282
- message: `Enforcement error: ${error.message}`,
283
- resource: "system",
284
- intent_ref: "system",
285
- severity: "block",
286
- });
287
-
288
- result.verdict = generateVerdict({
289
- alignmentResult: { violations: result.violations },
290
- proofs: [],
291
- mode,
292
- intent_hash: result.intent_hash,
293
- });
294
-
295
- result.blocked = true;
296
-
297
- return result;
298
- }
299
- }
300
-
301
- /**
302
- * Quick check - fast path for single file changes
303
- * @param {Object} change - Single change
304
- * @returns {Object} Quick check result
305
- */
306
- quickCheck(change) {
307
- const intent = this.intentStore.getCurrent();
308
- const changeEvent = normalizeChangeEvent(change);
309
- const alignmentResult = checkAlignmentBatch(intent, [changeEvent]);
310
-
311
- return {
312
- allowed: alignmentResult.aligned,
313
- decision: alignmentResult.decision,
314
- violations: alignmentResult.violations,
315
- intent_hash: intent?.hash || null,
316
- };
317
- }
318
-
319
- /**
320
- * Map claim type to proof type
321
- * @param {string} claimType - Claim type
322
- * @returns {string} Proof type
323
- */
324
- claimTypeToProofType(claimType) {
325
- const mapping = {
326
- route: "route",
327
- auth_boundary: "auth",
328
- ui_success_claim: "ui",
329
- http_call: "integration",
330
- env_used: "env",
331
- data_contract: "contract",
332
- };
333
- return mapping[claimType] || "integration";
334
- }
335
-
336
- /**
337
- * Load policy configuration
338
- * @returns {Object} Policy config
339
- */
340
- loadPolicy() {
341
- const policyPath = path.join(this.projectRoot, ".vibecheck", "policy.json");
342
-
343
- if (fs.existsSync(policyPath)) {
344
- try {
345
- return JSON.parse(fs.readFileSync(policyPath, "utf-8"));
346
- } catch {
347
- // Return default policy
348
- }
349
- }
350
-
351
- return {
352
- version: "2.0.0",
353
- profile: "strict",
354
- mode: this.modeConfig.getMode(),
355
- rules: {},
356
- };
357
- }
358
-
359
- /**
360
- * Store change packet
361
- * @param {Object} packet - Change packet
362
- */
363
- storePacket(packet) {
364
- const date = new Date().toISOString().split("T")[0];
365
- const packetDir = path.join(this.projectRoot, ".vibecheck", "packets", date);
366
-
367
- if (!fs.existsSync(packetDir)) {
368
- fs.mkdirSync(packetDir, { recursive: true });
369
- }
370
-
371
- const packetPath = path.join(packetDir, `${packet.id}.json`);
372
- fs.writeFileSync(packetPath, JSON.stringify(packet, null, 2));
373
- }
374
-
375
- /**
376
- * Store verdict manifest
377
- * @param {Object} manifest - Verdict manifest
378
- */
379
- storeVerdictManifest(manifest) {
380
- const verdictDir = path.join(this.projectRoot, ".vibecheck", "verdicts");
381
-
382
- if (!fs.existsSync(verdictDir)) {
383
- fs.mkdirSync(verdictDir, { recursive: true });
384
- }
385
-
386
- const verdictPath = path.join(verdictDir, `${manifest.verdict.id}.json`);
387
- fs.writeFileSync(verdictPath, JSON.stringify(manifest, null, 2));
388
-
389
- // Also write latest
390
- const latestPath = path.join(verdictDir, "latest.json");
391
- fs.writeFileSync(latestPath, JSON.stringify(manifest, null, 2));
392
- }
393
-
394
- /**
395
- * Get latest verdict
396
- * @returns {Object|null} Latest verdict manifest
397
- */
398
- getLatestVerdict() {
399
- const latestPath = path.join(this.projectRoot, ".vibecheck", "verdicts", "latest.json");
400
-
401
- if (fs.existsSync(latestPath)) {
402
- try {
403
- return JSON.parse(fs.readFileSync(latestPath, "utf-8"));
404
- } catch {
405
- return null;
406
- }
407
- }
408
-
409
- return null;
410
- }
411
-
412
- /**
413
- * Format enforcement result for output
414
- * @param {Object} result - Enforcement result
415
- * @param {Object} options - Output options
416
- * @returns {string} Formatted output
417
- */
418
- formatResult(result, options = {}) {
419
- const { ansi = {} } = options;
420
- const r = ansi.reset || "";
421
- const b = ansi.bold || "";
422
- const g = ansi.green || "";
423
- const y = ansi.yellow || "";
424
- const red = ansi.red || "";
425
- const d = ansi.dim || "";
426
-
427
- const lines = [];
428
-
429
- // Mode banner
430
- lines.push(formatModeBanner(result.mode, ansi));
431
-
432
- // Verdict
433
- const verdictColor = result.verdict?.decision === "PASS" || result.verdict?.decision === "WOULD_PASS" ? g : red;
434
- lines.push(`\n${b}Verdict:${r} ${verdictColor}${result.verdict?.decision}${r}`);
435
-
436
- // Summary
437
- if (result.verdict?.summary) {
438
- lines.push(`${d}${result.verdict.summary}${r}`);
439
- }
440
-
441
- // Violations
442
- if (result.violations.length > 0) {
443
- lines.push(`\n${b}Violations (${result.violations.length}):${r}`);
444
- for (const v of result.violations.slice(0, 5)) {
445
- lines.push(` ${red}•${r} [${v.code}] ${v.message}`);
446
- lines.push(` ${d}Resource: ${v.resource}${r}`);
447
- lines.push(` ${d}Intent ref: ${v.intent_ref}${r}`);
448
- }
449
- if (result.violations.length > 5) {
450
- lines.push(` ${d}... and ${result.violations.length - 5} more${r}`);
451
- }
452
- }
453
-
454
- // Proofs
455
- if (result.proofs.length > 0) {
456
- const verified = result.proofs.filter(p => p.status === "verified").length;
457
- const failed = result.proofs.filter(p => p.status === "failed").length;
458
- lines.push(`\n${b}Proofs:${r} ${g}${verified} verified${r}, ${red}${failed} failed${r}`);
459
- }
460
-
461
- // Intent
462
- lines.push(`\n${d}Intent hash: ${result.intent_hash || "NONE"}${r}`);
463
- lines.push(`${d}Run ID: ${result.runId}${r}`);
464
-
465
- return lines.join("\n");
466
- }
467
- }
468
-
469
- /**
470
- * Create orchestrator instance
471
- * @param {string} projectRoot - Project root
472
- * @returns {FirewallOrchestrator} Orchestrator instance
473
- */
474
- function createOrchestrator(projectRoot) {
475
- return new FirewallOrchestrator(projectRoot);
476
- }
477
-
478
- module.exports = {
479
- FirewallOrchestrator,
480
- createOrchestrator,
481
- normalizeChangeEvent,
482
- classifyDomain,
483
- CHANGE_EVENT_TYPE,
484
- };
1
+ /**
2
+ * Firewall Orchestrator - Main Enforcement Entry Point
3
+ *
4
+ * ═══════════════════════════════════════════════════════════════════════════════
5
+ * AGENT FIREWALL™ - ENFORCEMENT ORCHESTRATOR
6
+ * ═══════════════════════════════════════════════════════════════════════════════
7
+ *
8
+ * The Agent Firewall is a mandatory enforcement layer that intercepts AI-generated
9
+ * actions and BLOCKS them unless they are:
10
+ *
11
+ * 1) Explicitly aligned with declared user INTENT
12
+ * 2) Structurally valid (routes, env vars, permissions exist)
13
+ * 3) Proven real at runtime via Reality Mode
14
+ *
15
+ * If proof is missing → the action MUST NOT SHIP.
16
+ *
17
+ * This is enforcement infrastructure, not developer guidance.
18
+ *
19
+ * @module enforcement/orchestrator
20
+ * @version 2.0.0
21
+ */
22
+
23
+ "use strict";
24
+
25
+ const path = require("path");
26
+ const fs = require("fs");
27
+ const crypto = require("crypto");
28
+
29
+ // Intent system
30
+ const { IntentStore } = require("../intent/store");
31
+ const { checkAlignmentBatch, VIOLATION_CODES } = require("../intent/alignment-engine");
32
+ const { verifyIntentIntegrity, isIntentExpired } = require("../intent/schema");
33
+
34
+ // Verdict system
35
+ const { generateVerdict, VERDICT, OBSERVATION, MODE, createVerdictManifest } = require("./verdict-v2");
36
+
37
+ // Proof system
38
+ const { ProofCollection, determineRequiredProofs, PROOF_STATUS } = require("./proof-artifact");
39
+
40
+ // Mode system
41
+ const { ModeConfig, createModeEvent, formatModeBanner } = require("./mode");
42
+
43
+ // Existing components
44
+ const { resolveEvidence } = require("../evidence/resolver");
45
+ const { evaluatePolicy } = require("../policy/engine");
46
+ const { buildChangePacket, buildMultiFileChangePacket } = require("../change-packet/builder");
47
+
48
+ /**
49
+ * Change Event types
50
+ */
51
+ const CHANGE_EVENT_TYPE = {
52
+ FILE_WRITE: "file_write",
53
+ FILE_CREATE: "file_create",
54
+ FILE_DELETE: "file_delete",
55
+ ROUTE_ADD: "route_add",
56
+ ROUTE_MODIFY: "route_modify",
57
+ ENV_REF: "env_ref",
58
+ PERMISSION_CHANGE: "permission_change",
59
+ UI_STATE: "ui_state",
60
+ CONFIG_CHANGE: "config_change",
61
+ };
62
+
63
+ /**
64
+ * Normalize a raw change into a Change Event
65
+ * @param {Object} rawChange - Raw change from interception
66
+ * @returns {Object} Normalized Change Event
67
+ */
68
+ function normalizeChangeEvent(rawChange) {
69
+ return {
70
+ type: rawChange.type || CHANGE_EVENT_TYPE.FILE_WRITE,
71
+ location: rawChange.path || rawChange.file || rawChange.location,
72
+ diff: rawChange.diff || null,
73
+ content: rawChange.content || rawChange.diff?.after || null,
74
+ resource: rawChange.resource || rawChange.path || null,
75
+ domain: rawChange.domain || classifyDomain(rawChange.path),
76
+ claims: rawChange.claims || [],
77
+ method: rawChange.method || null,
78
+ env_exists: rawChange.env_exists,
79
+ includes_tests: rawChange.includes_tests || false,
80
+ file_count: rawChange.file_count || 1,
81
+ timestamp: rawChange.timestamp || new Date().toISOString(),
82
+ };
83
+ }
84
+
85
+ /**
86
+ * Classify file domain
87
+ * @param {string} filePath - File path
88
+ * @returns {string} Domain
89
+ */
90
+ function classifyDomain(filePath) {
91
+ if (!filePath) return "general";
92
+ const s = filePath.toLowerCase();
93
+ if (s.includes("auth")) return "auth";
94
+ if (s.includes("stripe") || s.includes("payment")) return "payments";
95
+ if (s.includes("routes") || s.includes("router") || s.includes("api")) return "routes";
96
+ if (s.includes("schema") || s.includes("contract")) return "contracts";
97
+ if (s.includes("ui") || s.includes("components") || s.includes("pages")) return "ui";
98
+ if (s.includes("database") || s.includes("prisma") || s.includes("migration")) return "database";
99
+ return "general";
100
+ }
101
+
102
+ /**
103
+ * Firewall Orchestrator
104
+ *
105
+ * Main entry point for all enforcement operations.
106
+ */
107
+ class FirewallOrchestrator {
108
+ constructor(projectRoot) {
109
+ this.projectRoot = projectRoot;
110
+ this.intentStore = new IntentStore(projectRoot);
111
+ this.modeConfig = new ModeConfig(projectRoot);
112
+ this.runId = crypto.randomBytes(8).toString("hex");
113
+ this.startTime = Date.now();
114
+ }
115
+
116
+ /**
117
+ * Main enforcement entry point
118
+ *
119
+ * @param {Object} params - Enforcement parameters
120
+ * @param {Object[]} params.changes - Array of raw changes
121
+ * @param {string} params.agentId - Agent identifier
122
+ * @param {Object} params.proposal - Structured proposal (optional)
123
+ * @param {Object} params.options - Options
124
+ * @returns {Promise<Object>} Enforcement result with verdict
125
+ */
126
+ async enforce({ changes, agentId, proposal = null, options = {} }) {
127
+ const mode = this.modeConfig.getMode();
128
+ const result = {
129
+ runId: this.runId,
130
+ mode,
131
+ blocked: false,
132
+ verdict: null,
133
+ violations: [],
134
+ proofs: [],
135
+ intent_hash: null,
136
+ timestamp: new Date().toISOString(),
137
+ };
138
+
139
+ try {
140
+ // Step 1: Get and validate intent
141
+ const intent = this.intentStore.getCurrent();
142
+ result.intent_hash = intent?.hash || null;
143
+
144
+ // Verify intent integrity
145
+ if (intent) {
146
+ const integrityCheck = verifyIntentIntegrity(intent);
147
+ if (!integrityCheck.valid) {
148
+ result.violations.push({
149
+ code: VIOLATION_CODES.INTENT_CORRUPTED,
150
+ rule: "intent_integrity",
151
+ message: `Intent integrity check failed: ${integrityCheck.reason}`,
152
+ resource: "intent",
153
+ intent_ref: "system",
154
+ severity: "block",
155
+ });
156
+ }
157
+
158
+ // Check expiration
159
+ if (isIntentExpired(intent)) {
160
+ result.violations.push({
161
+ code: VIOLATION_CODES.INTENT_EXPIRED,
162
+ rule: "intent_expiration",
163
+ message: "Intent has expired",
164
+ resource: "intent",
165
+ intent_ref: "system",
166
+ severity: "block",
167
+ });
168
+ }
169
+ }
170
+
171
+ // Step 2: Normalize change events
172
+ const changeEvents = changes.map(normalizeChangeEvent);
173
+
174
+ // Step 3: Run intent alignment check
175
+ const alignmentResult = checkAlignmentBatch(intent, changeEvents);
176
+ result.violations.push(...alignmentResult.violations);
177
+
178
+ // Step 4: Extract claims and resolve evidence (existing system)
179
+ const allClaims = changeEvents.flatMap(e => e.claims || []);
180
+ const evidence = resolveEvidence(this.projectRoot, allClaims);
181
+
182
+ // Step 5: Run policy engine (existing rules)
183
+ const policyResult = await evaluatePolicy({
184
+ policy: this.loadPolicy(),
185
+ claims: allClaims,
186
+ evidence,
187
+ files: changeEvents.map(e => ({ path: e.location, domain: e.domain })),
188
+ intent: intent?.summary || "",
189
+ });
190
+
191
+ // Convert policy violations to v2 format
192
+ if (policyResult.violations) {
193
+ const policyViolations = policyResult.violations.map(v => ({
194
+ code: v.rule?.toUpperCase().replace(/-/g, "_") || "POLICY_VIOLATION",
195
+ rule: v.rule || "unknown",
196
+ message: v.message || "Policy violation",
197
+ resource: v.claimId || v.file || "unknown",
198
+ intent_ref: "policy",
199
+ severity: "block",
200
+ }));
201
+ result.violations.push(...policyViolations);
202
+ }
203
+
204
+ // Step 6: Collect proofs (if required)
205
+ const proofCollection = new ProofCollection();
206
+ const requiredProofs = determineRequiredProofs(allClaims, intent);
207
+
208
+ for (const proofType of requiredProofs) {
209
+ proofCollection.require(proofType);
210
+ }
211
+
212
+ // In reality mode, we'd run actual verification here
213
+ // For now, mark proofs based on evidence
214
+ for (const e of evidence) {
215
+ if (e.result === "PROVEN") {
216
+ proofCollection.add({
217
+ id: `prf-${e.claimId}`,
218
+ type: this.claimTypeToProofType(allClaims.find(c => c.type === e.type)?.type),
219
+ status: PROOF_STATUS.VERIFIED,
220
+ target: e.pointer || e.claimId,
221
+ trace: e.sources?.[0]?.pointer,
222
+ });
223
+ } else if (e.result === "UNPROVEN" || e.result === "CONTRADICTS") {
224
+ proofCollection.add({
225
+ id: `prf-${e.claimId}`,
226
+ type: this.claimTypeToProofType(allClaims.find(c => c.type === e.type)?.type),
227
+ status: PROOF_STATUS.FAILED,
228
+ target: e.pointer || e.claimId,
229
+ error: e.reason || "Could not verify claim",
230
+ });
231
+ }
232
+ }
233
+
234
+ result.proofs = proofCollection.getAll();
235
+
236
+ // Step 7: Generate final verdict
237
+ result.verdict = generateVerdict({
238
+ alignmentResult: { violations: result.violations },
239
+ proofs: result.proofs,
240
+ mode,
241
+ intent_hash: result.intent_hash,
242
+ });
243
+
244
+ result.blocked = result.verdict.blocked;
245
+
246
+ // Step 8: Store change packet for audit
247
+ const packet = buildMultiFileChangePacket({
248
+ agentId,
249
+ intent: intent?.summary || "NO INTENT",
250
+ changes: changes.map((c, i) => ({
251
+ filePath: changeEvents[i].location,
252
+ diff: c.diff,
253
+ claims: changeEvents[i].claims,
254
+ })),
255
+ evidence,
256
+ verdict: {
257
+ decision: result.verdict.decision,
258
+ violations: result.violations,
259
+ message: result.verdict.summary,
260
+ },
261
+ policy: this.loadPolicy(),
262
+ proposal,
263
+ });
264
+
265
+ this.storePacket(packet);
266
+
267
+ // Step 9: Store verdict manifest
268
+ const manifest = createVerdictManifest(result.verdict, {
269
+ runId: this.runId,
270
+ agentId,
271
+ fileCount: changes.length,
272
+ });
273
+ this.storeVerdictManifest(manifest);
274
+
275
+ return result;
276
+
277
+ } catch (error) {
278
+ // On error, default to BLOCK
279
+ result.violations.push({
280
+ code: "ENFORCEMENT_ERROR",
281
+ rule: "system",
282
+ message: `Enforcement error: ${error.message}`,
283
+ resource: "system",
284
+ intent_ref: "system",
285
+ severity: "block",
286
+ });
287
+
288
+ result.verdict = generateVerdict({
289
+ alignmentResult: { violations: result.violations },
290
+ proofs: [],
291
+ mode,
292
+ intent_hash: result.intent_hash,
293
+ });
294
+
295
+ result.blocked = true;
296
+
297
+ return result;
298
+ }
299
+ }
300
+
301
+ /**
302
+ * Quick check - fast path for single file changes
303
+ * @param {Object} change - Single change
304
+ * @returns {Object} Quick check result
305
+ */
306
+ quickCheck(change) {
307
+ const intent = this.intentStore.getCurrent();
308
+ const changeEvent = normalizeChangeEvent(change);
309
+ const alignmentResult = checkAlignmentBatch(intent, [changeEvent]);
310
+
311
+ return {
312
+ allowed: alignmentResult.aligned,
313
+ decision: alignmentResult.decision,
314
+ violations: alignmentResult.violations,
315
+ intent_hash: intent?.hash || null,
316
+ };
317
+ }
318
+
319
+ /**
320
+ * Map claim type to proof type
321
+ * @param {string} claimType - Claim type
322
+ * @returns {string} Proof type
323
+ */
324
+ claimTypeToProofType(claimType) {
325
+ const mapping = {
326
+ route: "route",
327
+ auth_boundary: "auth",
328
+ ui_success_claim: "ui",
329
+ http_call: "integration",
330
+ env_used: "env",
331
+ data_contract: "contract",
332
+ };
333
+ return mapping[claimType] || "integration";
334
+ }
335
+
336
+ /**
337
+ * Load policy configuration
338
+ * @returns {Object} Policy config
339
+ */
340
+ loadPolicy() {
341
+ const policyPath = path.join(this.projectRoot, ".vibecheck", "policy.json");
342
+
343
+ if (fs.existsSync(policyPath)) {
344
+ try {
345
+ return JSON.parse(fs.readFileSync(policyPath, "utf-8"));
346
+ } catch {
347
+ // Return default policy
348
+ }
349
+ }
350
+
351
+ return {
352
+ version: "2.0.0",
353
+ profile: "strict",
354
+ mode: this.modeConfig.getMode(),
355
+ rules: {},
356
+ };
357
+ }
358
+
359
+ /**
360
+ * Store change packet
361
+ * @param {Object} packet - Change packet
362
+ */
363
+ storePacket(packet) {
364
+ const date = new Date().toISOString().split("T")[0];
365
+ const packetDir = path.join(this.projectRoot, ".vibecheck", "packets", date);
366
+
367
+ if (!fs.existsSync(packetDir)) {
368
+ fs.mkdirSync(packetDir, { recursive: true });
369
+ }
370
+
371
+ const packetPath = path.join(packetDir, `${packet.id}.json`);
372
+ fs.writeFileSync(packetPath, JSON.stringify(packet, null, 2));
373
+ }
374
+
375
+ /**
376
+ * Store verdict manifest
377
+ * @param {Object} manifest - Verdict manifest
378
+ */
379
+ storeVerdictManifest(manifest) {
380
+ const verdictDir = path.join(this.projectRoot, ".vibecheck", "verdicts");
381
+
382
+ if (!fs.existsSync(verdictDir)) {
383
+ fs.mkdirSync(verdictDir, { recursive: true });
384
+ }
385
+
386
+ const verdictPath = path.join(verdictDir, `${manifest.verdict.id}.json`);
387
+ fs.writeFileSync(verdictPath, JSON.stringify(manifest, null, 2));
388
+
389
+ // Also write latest
390
+ const latestPath = path.join(verdictDir, "latest.json");
391
+ fs.writeFileSync(latestPath, JSON.stringify(manifest, null, 2));
392
+ }
393
+
394
+ /**
395
+ * Get latest verdict
396
+ * @returns {Object|null} Latest verdict manifest
397
+ */
398
+ getLatestVerdict() {
399
+ const latestPath = path.join(this.projectRoot, ".vibecheck", "verdicts", "latest.json");
400
+
401
+ if (fs.existsSync(latestPath)) {
402
+ try {
403
+ return JSON.parse(fs.readFileSync(latestPath, "utf-8"));
404
+ } catch {
405
+ return null;
406
+ }
407
+ }
408
+
409
+ return null;
410
+ }
411
+
412
+ /**
413
+ * Format enforcement result for output
414
+ * @param {Object} result - Enforcement result
415
+ * @param {Object} options - Output options
416
+ * @returns {string} Formatted output
417
+ */
418
+ formatResult(result, options = {}) {
419
+ const { ansi = {} } = options;
420
+ const r = ansi.reset || "";
421
+ const b = ansi.bold || "";
422
+ const g = ansi.green || "";
423
+ const y = ansi.yellow || "";
424
+ const red = ansi.red || "";
425
+ const d = ansi.dim || "";
426
+
427
+ const lines = [];
428
+
429
+ // Mode banner
430
+ lines.push(formatModeBanner(result.mode, ansi));
431
+
432
+ // Verdict
433
+ const verdictColor = result.verdict?.decision === "PASS" || result.verdict?.decision === "WOULD_PASS" ? g : red;
434
+ lines.push(`\n${b}Verdict:${r} ${verdictColor}${result.verdict?.decision}${r}`);
435
+
436
+ // Summary
437
+ if (result.verdict?.summary) {
438
+ lines.push(`${d}${result.verdict.summary}${r}`);
439
+ }
440
+
441
+ // Violations
442
+ if (result.violations.length > 0) {
443
+ lines.push(`\n${b}Violations (${result.violations.length}):${r}`);
444
+ for (const v of result.violations.slice(0, 5)) {
445
+ lines.push(` ${red}•${r} [${v.code}] ${v.message}`);
446
+ lines.push(` ${d}Resource: ${v.resource}${r}`);
447
+ lines.push(` ${d}Intent ref: ${v.intent_ref}${r}`);
448
+ }
449
+ if (result.violations.length > 5) {
450
+ lines.push(` ${d}... and ${result.violations.length - 5} more${r}`);
451
+ }
452
+ }
453
+
454
+ // Proofs
455
+ if (result.proofs.length > 0) {
456
+ const verified = result.proofs.filter(p => p.status === "verified").length;
457
+ const failed = result.proofs.filter(p => p.status === "failed").length;
458
+ lines.push(`\n${b}Proofs:${r} ${g}${verified} verified${r}, ${red}${failed} failed${r}`);
459
+ }
460
+
461
+ // Intent
462
+ lines.push(`\n${d}Intent hash: ${result.intent_hash || "NONE"}${r}`);
463
+ lines.push(`${d}Run ID: ${result.runId}${r}`);
464
+
465
+ return lines.join("\n");
466
+ }
467
+ }
468
+
469
+ /**
470
+ * Create orchestrator instance
471
+ * @param {string} projectRoot - Project root
472
+ * @returns {FirewallOrchestrator} Orchestrator instance
473
+ */
474
+ function createOrchestrator(projectRoot) {
475
+ return new FirewallOrchestrator(projectRoot);
476
+ }
477
+
478
+ module.exports = {
479
+ FirewallOrchestrator,
480
+ createOrchestrator,
481
+ normalizeChangeEvent,
482
+ classifyDomain,
483
+ CHANGE_EVENT_TYPE,
484
+ };