@provos/ironcurtain 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 (160) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +311 -0
  3. package/dist/agent/index.d.ts +10 -0
  4. package/dist/agent/index.js +71 -0
  5. package/dist/agent/index.js.map +1 -0
  6. package/dist/agent/prompts.d.ts +5 -0
  7. package/dist/agent/prompts.js +26 -0
  8. package/dist/agent/prompts.js.map +1 -0
  9. package/dist/agent/tools.d.ts +13 -0
  10. package/dist/agent/tools.js +51 -0
  11. package/dist/agent/tools.js.map +1 -0
  12. package/dist/cli.d.ts +2 -0
  13. package/dist/cli.js +78 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/config/constitution.md +16 -0
  16. package/dist/config/generated/compiled-policy.json +236 -0
  17. package/dist/config/generated/test-scenarios.json +765 -0
  18. package/dist/config/generated/tool-annotations.json +955 -0
  19. package/dist/config/index.d.ts +25 -0
  20. package/dist/config/index.js +151 -0
  21. package/dist/config/index.js.map +1 -0
  22. package/dist/config/mcp-servers.json +22 -0
  23. package/dist/config/model-provider.d.ts +49 -0
  24. package/dist/config/model-provider.js +78 -0
  25. package/dist/config/model-provider.js.map +1 -0
  26. package/dist/config/paths.d.ts +59 -0
  27. package/dist/config/paths.js +96 -0
  28. package/dist/config/paths.js.map +1 -0
  29. package/dist/config/types.d.ts +89 -0
  30. package/dist/config/types.js +2 -0
  31. package/dist/config/types.js.map +1 -0
  32. package/dist/config/user-config.d.ts +93 -0
  33. package/dist/config/user-config.js +309 -0
  34. package/dist/config/user-config.js.map +1 -0
  35. package/dist/hash.d.ts +17 -0
  36. package/dist/hash.js +34 -0
  37. package/dist/hash.js.map +1 -0
  38. package/dist/index.d.ts +1 -0
  39. package/dist/index.js +61 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/logger.d.ts +11 -0
  42. package/dist/logger.js +93 -0
  43. package/dist/logger.js.map +1 -0
  44. package/dist/pipeline/annotate.d.ts +9 -0
  45. package/dist/pipeline/annotate.js +136 -0
  46. package/dist/pipeline/annotate.js.map +1 -0
  47. package/dist/pipeline/compile.d.ts +23 -0
  48. package/dist/pipeline/compile.js +386 -0
  49. package/dist/pipeline/compile.js.map +1 -0
  50. package/dist/pipeline/constitution-compiler.d.ts +22 -0
  51. package/dist/pipeline/constitution-compiler.js +197 -0
  52. package/dist/pipeline/constitution-compiler.js.map +1 -0
  53. package/dist/pipeline/generate-with-repair.d.ts +22 -0
  54. package/dist/pipeline/generate-with-repair.js +64 -0
  55. package/dist/pipeline/generate-with-repair.js.map +1 -0
  56. package/dist/pipeline/handwritten-scenarios.d.ts +9 -0
  57. package/dist/pipeline/handwritten-scenarios.js +321 -0
  58. package/dist/pipeline/handwritten-scenarios.js.map +1 -0
  59. package/dist/pipeline/llm-logger.d.ts +42 -0
  60. package/dist/pipeline/llm-logger.js +78 -0
  61. package/dist/pipeline/llm-logger.js.map +1 -0
  62. package/dist/pipeline/pipeline-shared.d.ts +47 -0
  63. package/dist/pipeline/pipeline-shared.js +145 -0
  64. package/dist/pipeline/pipeline-shared.js.map +1 -0
  65. package/dist/pipeline/policy-verifier.d.ts +46 -0
  66. package/dist/pipeline/policy-verifier.js +277 -0
  67. package/dist/pipeline/policy-verifier.js.map +1 -0
  68. package/dist/pipeline/scenario-generator.d.ts +11 -0
  69. package/dist/pipeline/scenario-generator.js +128 -0
  70. package/dist/pipeline/scenario-generator.js.map +1 -0
  71. package/dist/pipeline/tool-annotator.d.ts +24 -0
  72. package/dist/pipeline/tool-annotator.js +201 -0
  73. package/dist/pipeline/tool-annotator.js.map +1 -0
  74. package/dist/pipeline/types.d.ts +122 -0
  75. package/dist/pipeline/types.js +10 -0
  76. package/dist/pipeline/types.js.map +1 -0
  77. package/dist/sandbox/index.d.ts +39 -0
  78. package/dist/sandbox/index.js +178 -0
  79. package/dist/sandbox/index.js.map +1 -0
  80. package/dist/session/agent-session.d.ts +83 -0
  81. package/dist/session/agent-session.js +382 -0
  82. package/dist/session/agent-session.js.map +1 -0
  83. package/dist/session/cli-transport.d.ts +61 -0
  84. package/dist/session/cli-transport.js +320 -0
  85. package/dist/session/cli-transport.js.map +1 -0
  86. package/dist/session/errors.d.ts +19 -0
  87. package/dist/session/errors.js +33 -0
  88. package/dist/session/errors.js.map +1 -0
  89. package/dist/session/index.d.ts +29 -0
  90. package/dist/session/index.js +104 -0
  91. package/dist/session/index.js.map +1 -0
  92. package/dist/session/message-compactor.d.ts +32 -0
  93. package/dist/session/message-compactor.js +81 -0
  94. package/dist/session/message-compactor.js.map +1 -0
  95. package/dist/session/prompts.d.ts +5 -0
  96. package/dist/session/prompts.js +62 -0
  97. package/dist/session/prompts.js.map +1 -0
  98. package/dist/session/resource-budget-tracker.d.ts +124 -0
  99. package/dist/session/resource-budget-tracker.js +327 -0
  100. package/dist/session/resource-budget-tracker.js.map +1 -0
  101. package/dist/session/step-loop-detector.d.ts +63 -0
  102. package/dist/session/step-loop-detector.js +136 -0
  103. package/dist/session/step-loop-detector.js.map +1 -0
  104. package/dist/session/transport.d.ts +24 -0
  105. package/dist/session/transport.js +2 -0
  106. package/dist/session/transport.js.map +1 -0
  107. package/dist/session/truncate-result.d.ts +35 -0
  108. package/dist/session/truncate-result.js +71 -0
  109. package/dist/session/truncate-result.js.map +1 -0
  110. package/dist/session/types.d.ts +220 -0
  111. package/dist/session/types.js +6 -0
  112. package/dist/session/types.js.map +1 -0
  113. package/dist/trusted-process/audit-log.d.ts +7 -0
  114. package/dist/trusted-process/audit-log.js +21 -0
  115. package/dist/trusted-process/audit-log.js.map +1 -0
  116. package/dist/trusted-process/call-circuit-breaker.d.ts +33 -0
  117. package/dist/trusted-process/call-circuit-breaker.js +61 -0
  118. package/dist/trusted-process/call-circuit-breaker.js.map +1 -0
  119. package/dist/trusted-process/escalation.d.ts +7 -0
  120. package/dist/trusted-process/escalation.js +38 -0
  121. package/dist/trusted-process/escalation.js.map +1 -0
  122. package/dist/trusted-process/index.d.ts +32 -0
  123. package/dist/trusted-process/index.js +151 -0
  124. package/dist/trusted-process/index.js.map +1 -0
  125. package/dist/trusted-process/mcp-client-manager.d.ts +25 -0
  126. package/dist/trusted-process/mcp-client-manager.js +90 -0
  127. package/dist/trusted-process/mcp-client-manager.js.map +1 -0
  128. package/dist/trusted-process/mcp-proxy-server.d.ts +24 -0
  129. package/dist/trusted-process/mcp-proxy-server.js +451 -0
  130. package/dist/trusted-process/mcp-proxy-server.js.map +1 -0
  131. package/dist/trusted-process/path-utils.d.ts +50 -0
  132. package/dist/trusted-process/path-utils.js +158 -0
  133. package/dist/trusted-process/path-utils.js.map +1 -0
  134. package/dist/trusted-process/policy-engine.d.ts +88 -0
  135. package/dist/trusted-process/policy-engine.js +523 -0
  136. package/dist/trusted-process/policy-engine.js.map +1 -0
  137. package/dist/trusted-process/policy-roots.d.ts +50 -0
  138. package/dist/trusted-process/policy-roots.js +67 -0
  139. package/dist/trusted-process/policy-roots.js.map +1 -0
  140. package/dist/trusted-process/policy-types.d.ts +6 -0
  141. package/dist/trusted-process/policy-types.js +2 -0
  142. package/dist/trusted-process/policy-types.js.map +1 -0
  143. package/dist/trusted-process/sandbox-integration.d.ts +92 -0
  144. package/dist/trusted-process/sandbox-integration.js +184 -0
  145. package/dist/trusted-process/sandbox-integration.js.map +1 -0
  146. package/dist/types/argument-roles.d.ts +112 -0
  147. package/dist/types/argument-roles.js +344 -0
  148. package/dist/types/argument-roles.js.map +1 -0
  149. package/dist/types/audit.d.ts +18 -0
  150. package/dist/types/audit.js +2 -0
  151. package/dist/types/audit.js.map +1 -0
  152. package/dist/types/mcp.d.ts +20 -0
  153. package/dist/types/mcp.js +2 -0
  154. package/dist/types/mcp.js.map +1 -0
  155. package/package.json +83 -0
  156. package/src/config/constitution.md +16 -0
  157. package/src/config/generated/compiled-policy.json +236 -0
  158. package/src/config/generated/test-scenarios.json +765 -0
  159. package/src/config/generated/tool-annotations.json +955 -0
  160. package/src/config/mcp-servers.json +22 -0
@@ -0,0 +1,42 @@
1
+ /**
2
+ * LLM Interaction Logger -- AI SDK middleware that captures all LLM
3
+ * prompts and responses during the policy compilation pipeline.
4
+ *
5
+ * Uses `wrapLanguageModel()` with a custom middleware that intercepts
6
+ * every `doGenerate` call. The caller sets `context.stepName` before
7
+ * each pipeline phase so logs are labeled without changing module APIs.
8
+ *
9
+ * Writes a single JSONL file with one entry per LLM call.
10
+ */
11
+ import type { LanguageModelMiddleware } from 'ai';
12
+ export interface LlmLogEntry {
13
+ timestamp: string;
14
+ stepName: string;
15
+ modelId: string;
16
+ /**
17
+ * For the first entry (promptOffset === 0), this is the full prompt array.
18
+ * For subsequent entries, this contains only new messages since the last
19
+ * logged entry. Reconstruct the full prompt by concatenating all entries'
20
+ * prompt arrays in order.
21
+ */
22
+ prompt: unknown;
23
+ /** Index into the full prompt array where this entry's messages start. */
24
+ promptOffset: number;
25
+ responseText: string;
26
+ usage: {
27
+ inputTokens: number | undefined;
28
+ outputTokens: number | undefined;
29
+ };
30
+ durationMs: number;
31
+ }
32
+ export interface LlmLogContext {
33
+ stepName: string;
34
+ }
35
+ /**
36
+ * Creates an AI SDK middleware that logs every generate call to a JSONL file.
37
+ *
38
+ * @param logPath - Absolute path to the output JSONL file.
39
+ * @param context - Mutable context object. Set `context.stepName` before each
40
+ * pipeline phase to label the log entries.
41
+ */
42
+ export declare function createLlmLoggingMiddleware(logPath: string, context: LlmLogContext): LanguageModelMiddleware;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * LLM Interaction Logger -- AI SDK middleware that captures all LLM
3
+ * prompts and responses during the policy compilation pipeline.
4
+ *
5
+ * Uses `wrapLanguageModel()` with a custom middleware that intercepts
6
+ * every `doGenerate` call. The caller sets `context.stepName` before
7
+ * each pipeline phase so logs are labeled without changing module APIs.
8
+ *
9
+ * Writes a single JSONL file with one entry per LLM call.
10
+ */
11
+ import { writeFileSync, appendFileSync, mkdirSync } from 'node:fs';
12
+ import { dirname } from 'node:path';
13
+ // ---------------------------------------------------------------------------
14
+ // Middleware factory
15
+ // ---------------------------------------------------------------------------
16
+ /**
17
+ * Creates an AI SDK middleware that logs every generate call to a JSONL file.
18
+ *
19
+ * @param logPath - Absolute path to the output JSONL file.
20
+ * @param context - Mutable context object. Set `context.stepName` before each
21
+ * pipeline phase to label the log entries.
22
+ */
23
+ export function createLlmLoggingMiddleware(logPath, context) {
24
+ initLogFile(logPath);
25
+ // Track how many prompt items were logged so far to enable delta logging.
26
+ let previousPromptLength = 0;
27
+ return {
28
+ specificationVersion: 'v3',
29
+ wrapGenerate: async ({ doGenerate, params, model }) => {
30
+ const start = Date.now();
31
+ const result = await doGenerate();
32
+ const durationMs = Date.now() - start;
33
+ const responseText = extractTextFromContent(result.content);
34
+ // Only log new messages since the last entry (delta logging).
35
+ const fullPrompt = params.prompt;
36
+ const promptArray = Array.isArray(fullPrompt) ? fullPrompt : [fullPrompt];
37
+ const promptOffset = previousPromptLength;
38
+ const newMessages = promptArray.slice(promptOffset);
39
+ previousPromptLength = promptArray.length;
40
+ const entry = {
41
+ timestamp: new Date().toISOString(),
42
+ stepName: context.stepName,
43
+ modelId: model.modelId,
44
+ prompt: newMessages,
45
+ promptOffset,
46
+ responseText,
47
+ usage: {
48
+ inputTokens: result.usage?.inputTokens?.total,
49
+ outputTokens: result.usage?.outputTokens?.total,
50
+ },
51
+ durationMs,
52
+ };
53
+ appendLogEntry(logPath, entry);
54
+ return result;
55
+ },
56
+ };
57
+ }
58
+ // ---------------------------------------------------------------------------
59
+ // File I/O helpers
60
+ // ---------------------------------------------------------------------------
61
+ function initLogFile(logPath) {
62
+ mkdirSync(dirname(logPath), { recursive: true });
63
+ // Truncate any existing file from a previous run
64
+ writeFileSync(logPath, '');
65
+ }
66
+ function appendLogEntry(logPath, entry) {
67
+ appendFileSync(logPath, JSON.stringify(entry) + '\n');
68
+ }
69
+ // ---------------------------------------------------------------------------
70
+ // Content extraction
71
+ // ---------------------------------------------------------------------------
72
+ function extractTextFromContent(content) {
73
+ return content
74
+ .filter((part) => part.type === 'text' && typeof part.text === 'string')
75
+ .map(part => part.text)
76
+ .join('');
77
+ }
78
+ //# sourceMappingURL=llm-logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-logger.js","sourceRoot":"","sources":["../../src/pipeline/llm-logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACnE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgCpC,8EAA8E;AAC9E,qBAAqB;AACrB,8EAA8E;AAE9E;;;;;;GAMG;AACH,MAAM,UAAU,0BAA0B,CACxC,OAAe,EACf,OAAsB;IAEtB,WAAW,CAAC,OAAO,CAAC,CAAC;IAErB,0EAA0E;IAC1E,IAAI,oBAAoB,GAAG,CAAC,CAAC;IAE7B,OAAO;QACL,oBAAoB,EAAE,IAAI;QAC1B,YAAY,EAAE,KAAK,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE;YACpD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACzB,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;YAClC,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;YAEtC,MAAM,YAAY,GAAG,sBAAsB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAE5D,8DAA8D;YAC9D,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC;YACjC,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;YAC1E,MAAM,YAAY,GAAG,oBAAoB,CAAC;YAC1C,MAAM,WAAW,GAAG,WAAW,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YACpD,oBAAoB,GAAG,WAAW,CAAC,MAAM,CAAC;YAE1C,MAAM,KAAK,GAAgB;gBACzB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACnC,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,MAAM,EAAE,WAAW;gBACnB,YAAY;gBACZ,YAAY;gBACZ,KAAK,EAAE;oBACL,WAAW,EAAE,MAAM,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK;oBAC7C,YAAY,EAAE,MAAM,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK;iBAChD;gBACD,UAAU;aACX,CAAC;YAEF,cAAc,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YAC/B,OAAO,MAAM,CAAC;QAChB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E,SAAS,WAAW,CAAC,OAAe;IAClC,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACjD,iDAAiD;IACjD,aAAa,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AAC7B,CAAC;AAED,SAAS,cAAc,CAAC,OAAe,EAAE,KAAkB;IACzD,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;AACxD,CAAC;AAED,8EAA8E;AAC9E,qBAAqB;AACrB,8EAA8E;AAE9E,SAAS,sBAAsB,CAAC,OAA+C;IAC7E,OAAO,OAAO;SACX,MAAM,CAAC,CAAC,IAAI,EAA0C,EAAE,CACvD,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,CACtD;SACA,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;SACtB,IAAI,CAAC,EAAE,CAAC,CAAC;AACd,CAAC"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Shared utilities for the policy compilation pipeline.
3
+ *
4
+ * Used by both `annotate.ts` (tool annotation) and `compile.ts`
5
+ * (policy compilation) to avoid duplication of config loading,
6
+ * content-hash caching, and spinner helpers.
7
+ */
8
+ import type { LanguageModel } from 'ai';
9
+ import { type Ora } from 'ora';
10
+ import type { MCPServerConfig } from '../config/types.js';
11
+ import { type LlmLogContext } from './llm-logger.js';
12
+ export interface PipelineConfig {
13
+ constitutionPath: string;
14
+ constitutionText: string;
15
+ constitutionHash: string;
16
+ mcpServers: Record<string, MCPServerConfig>;
17
+ generatedDir: string;
18
+ packageGeneratedDir: string;
19
+ allowedDirectory: string;
20
+ protectedPaths: string[];
21
+ }
22
+ /**
23
+ * Loads the combined constitution text (base + optional user constitution).
24
+ * The user constitution file is at ~/.ironcurtain/constitution-user.md.
25
+ * When present, it is appended to the base constitution text.
26
+ */
27
+ export declare function loadConstitutionText(basePath: string): string;
28
+ export declare function loadPipelineConfig(): PipelineConfig;
29
+ export declare function computeHash(...inputs: string[]): string;
30
+ export declare function loadExistingArtifact<T>(generatedDir: string, filename: string, fallbackDir?: string): T | undefined;
31
+ export declare function showCached(text: string): void;
32
+ export declare function withSpinner<T>(text: string, fn: (spinner: Ora) => Promise<T>, successFn?: (result: T, elapsed: number) => string): Promise<{
33
+ result: T;
34
+ elapsed: number;
35
+ }>;
36
+ /** Writes a JSON artifact to the generated directory with consistent formatting. */
37
+ export declare function writeArtifact(generatedDir: string, filename: string, data: unknown): void;
38
+ export interface PipelineLlm {
39
+ model: LanguageModel;
40
+ logContext: LlmLogContext;
41
+ logPath: string;
42
+ }
43
+ /**
44
+ * Creates a language model wrapped with pipeline logging middleware.
45
+ * Both annotate.ts and compile.ts use this to set up their LLM instance.
46
+ */
47
+ export declare function createPipelineLlm(generatedDir: string, initialStepName: string): Promise<PipelineLlm>;
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Shared utilities for the policy compilation pipeline.
3
+ *
4
+ * Used by both `annotate.ts` (tool annotation) and `compile.ts`
5
+ * (policy compilation) to avoid duplication of config loading,
6
+ * content-hash caching, and spinner helpers.
7
+ */
8
+ import { createHash } from 'node:crypto';
9
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
10
+ import { resolve, dirname } from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+ import { wrapLanguageModel } from 'ai';
13
+ import chalk from 'chalk';
14
+ import ora from 'ora';
15
+ import { computeProtectedPaths } from '../config/index.js';
16
+ import { createLanguageModel } from '../config/model-provider.js';
17
+ import { getIronCurtainHome, getUserConstitutionPath, getUserGeneratedDir } from '../config/paths.js';
18
+ import { loadUserConfig } from '../config/user-config.js';
19
+ import { createLlmLoggingMiddleware } from './llm-logger.js';
20
+ const __dirname = dirname(fileURLToPath(import.meta.url));
21
+ /**
22
+ * Loads the combined constitution text (base + optional user constitution).
23
+ * The user constitution file is at ~/.ironcurtain/constitution-user.md.
24
+ * When present, it is appended to the base constitution text.
25
+ */
26
+ export function loadConstitutionText(basePath) {
27
+ const base = readFileSync(basePath, 'utf-8');
28
+ const userPath = getUserConstitutionPath();
29
+ if (existsSync(userPath)) {
30
+ const user = readFileSync(userPath, 'utf-8');
31
+ return `${base}\n\n${user}`;
32
+ }
33
+ return base;
34
+ }
35
+ export function loadPipelineConfig() {
36
+ const configDir = resolve(__dirname, '..', 'config');
37
+ const constitutionPath = resolve(configDir, 'constitution.md');
38
+ const constitutionText = loadConstitutionText(constitutionPath);
39
+ const constitutionHash = createHash('sha256').update(constitutionText).digest('hex');
40
+ const mcpServersPath = resolve(configDir, 'mcp-servers.json');
41
+ const mcpServers = JSON.parse(readFileSync(mcpServersPath, 'utf-8'));
42
+ const generatedDir = getUserGeneratedDir();
43
+ const packageGeneratedDir = resolve(configDir, 'generated');
44
+ const defaultAllowedDir = resolve(getIronCurtainHome(), 'sandbox');
45
+ const allowedDirectory = process.env.ALLOWED_DIRECTORY ?? defaultAllowedDir;
46
+ const auditLogPath = process.env.AUDIT_LOG_PATH ?? './audit.jsonl';
47
+ const protectedPaths = computeProtectedPaths({
48
+ constitutionPath,
49
+ generatedDir,
50
+ packageGeneratedDir,
51
+ mcpServersPath,
52
+ auditLogPath,
53
+ });
54
+ return {
55
+ constitutionPath,
56
+ constitutionText,
57
+ constitutionHash,
58
+ mcpServers,
59
+ generatedDir,
60
+ packageGeneratedDir,
61
+ allowedDirectory,
62
+ protectedPaths,
63
+ };
64
+ }
65
+ // ---------------------------------------------------------------------------
66
+ // Content-Hash Caching
67
+ // ---------------------------------------------------------------------------
68
+ export function computeHash(...inputs) {
69
+ const hash = createHash('sha256');
70
+ for (const input of inputs) {
71
+ hash.update(input);
72
+ }
73
+ return hash.digest('hex');
74
+ }
75
+ export function loadExistingArtifact(generatedDir, filename, fallbackDir) {
76
+ const candidates = [resolve(generatedDir, filename)];
77
+ if (fallbackDir) {
78
+ candidates.push(resolve(fallbackDir, filename));
79
+ }
80
+ for (const path of candidates) {
81
+ if (existsSync(path)) {
82
+ try {
83
+ return JSON.parse(readFileSync(path, 'utf-8'));
84
+ }
85
+ catch {
86
+ // Corrupt file -- try next candidate
87
+ }
88
+ }
89
+ }
90
+ return undefined;
91
+ }
92
+ // ---------------------------------------------------------------------------
93
+ // Spinner Helpers
94
+ // ---------------------------------------------------------------------------
95
+ export function showCached(text) {
96
+ const spinner = ora({ text, stream: process.stderr, discardStdin: false }).start();
97
+ spinner.succeed(`${text} ${chalk.dim('(cached)')}`);
98
+ }
99
+ export async function withSpinner(text, fn, successFn) {
100
+ const spinner = ora({ text, stream: process.stderr, discardStdin: false }).start();
101
+ const start = Date.now();
102
+ const timer = setInterval(() => {
103
+ const secs = ((Date.now() - start) / 1000).toFixed(0);
104
+ spinner.text = `${text} (${secs}s)`;
105
+ }, 1000);
106
+ try {
107
+ const result = await fn(spinner);
108
+ clearInterval(timer);
109
+ const elapsed = (Date.now() - start) / 1000;
110
+ const successText = successFn
111
+ ? successFn(result, elapsed)
112
+ : `${text} (${elapsed.toFixed(1)}s)`;
113
+ spinner.succeed(successText);
114
+ return { result, elapsed };
115
+ }
116
+ catch (err) {
117
+ clearInterval(timer);
118
+ spinner.fail(chalk.red(text));
119
+ throw err;
120
+ }
121
+ }
122
+ // ---------------------------------------------------------------------------
123
+ // Artifact I/O
124
+ // ---------------------------------------------------------------------------
125
+ /** Writes a JSON artifact to the generated directory with consistent formatting. */
126
+ export function writeArtifact(generatedDir, filename, data) {
127
+ mkdirSync(generatedDir, { recursive: true });
128
+ writeFileSync(resolve(generatedDir, filename), JSON.stringify(data, null, 2) + '\n');
129
+ }
130
+ /**
131
+ * Creates a language model wrapped with pipeline logging middleware.
132
+ * Both annotate.ts and compile.ts use this to set up their LLM instance.
133
+ */
134
+ export async function createPipelineLlm(generatedDir, initialStepName) {
135
+ const userConfig = loadUserConfig();
136
+ const baseLlm = await createLanguageModel(userConfig.policyModelId, userConfig);
137
+ const logContext = { stepName: initialStepName };
138
+ const logPath = resolve(generatedDir, 'llm-interactions.jsonl');
139
+ const model = wrapLanguageModel({
140
+ model: baseLlm,
141
+ middleware: createLlmLoggingMiddleware(logPath, logContext),
142
+ });
143
+ return { model, logContext, logPath };
144
+ }
145
+ //# sourceMappingURL=pipeline-shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline-shared.js","sourceRoot":"","sources":["../../src/pipeline/pipeline-shared.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,IAAI,CAAC;AACvC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAiB,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEtG,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,0BAA0B,EAAsB,MAAM,iBAAiB,CAAC;AAEjF,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAiB1D;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAAgB;IACnD,MAAM,IAAI,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC7C,MAAM,QAAQ,GAAG,uBAAuB,EAAE,CAAC;IAC3C,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC7C,OAAO,GAAG,IAAI,OAAO,IAAI,EAAE,CAAC;IAC9B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IACrD,MAAM,gBAAgB,GAAG,OAAO,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;IAC/D,MAAM,gBAAgB,GAAG,oBAAoB,CAAC,gBAAgB,CAAC,CAAC;IAChE,MAAM,gBAAgB,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACrF,MAAM,cAAc,GAAG,OAAO,CAAC,SAAS,EAAE,kBAAkB,CAAC,CAAC;IAC9D,MAAM,UAAU,GAAoC,IAAI,CAAC,KAAK,CAC5D,YAAY,CAAC,cAAc,EAAE,OAAO,CAAC,CACtC,CAAC;IACF,MAAM,YAAY,GAAG,mBAAmB,EAAE,CAAC;IAC3C,MAAM,mBAAmB,GAAG,OAAO,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IAC5D,MAAM,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,EAAE,EAAE,SAAS,CAAC,CAAC;IACnE,MAAM,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,iBAAiB,CAAC;IAC5E,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,eAAe,CAAC;IAEnE,MAAM,cAAc,GAAG,qBAAqB,CAAC;QAC3C,gBAAgB;QAChB,YAAY;QACZ,mBAAmB;QACnB,cAAc;QACd,YAAY;KACb,CAAC,CAAC;IAEH,OAAO;QACL,gBAAgB;QAChB,gBAAgB;QAChB,gBAAgB;QAChB,UAAU;QACV,YAAY;QACZ,mBAAmB;QACnB,gBAAgB;QAChB,cAAc;KACf,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,uBAAuB;AACvB,8EAA8E;AAE9E,MAAM,UAAU,WAAW,CAAC,GAAG,MAAgB;IAC7C,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IAClC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC;IACD,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,YAAoB,EACpB,QAAgB,EAChB,WAAoB;IAEpB,MAAM,UAAU,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC;IACrD,IAAI,WAAW,EAAE,CAAC;QAChB,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC;IAClD,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACrB,IAAI,CAAC;gBACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;YACjD,CAAC;YAAC,MAAM,CAAC;gBACP,qCAAqC;YACvC,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,8EAA8E;AAC9E,kBAAkB;AAClB,8EAA8E;AAE9E,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,MAAM,OAAO,GAAG,GAAG,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;IACnF,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,IAAI,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,IAAY,EACZ,EAAgC,EAChC,SAAkD;IAElD,MAAM,OAAO,GAAG,GAAG,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;IACnF,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACzB,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACtD,OAAO,CAAC,IAAI,GAAG,GAAG,IAAI,KAAK,IAAI,IAAI,CAAC;IACtC,CAAC,EAAE,IAAI,CAAC,CAAC;IACT,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC;QACjC,aAAa,CAAC,KAAK,CAAC,CAAC;QACrB,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC;QAC5C,MAAM,WAAW,GAAG,SAAS;YAC3B,CAAC,CAAC,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC;YAC5B,CAAC,CAAC,GAAG,IAAI,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;QACvC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAC7B,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IAC7B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,aAAa,CAAC,KAAK,CAAC,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;QAC9B,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,eAAe;AACf,8EAA8E;AAE9E,oFAAoF;AACpF,MAAM,UAAU,aAAa,CAAC,YAAoB,EAAE,QAAgB,EAAE,IAAa;IACjF,SAAS,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7C,aAAa,CACX,OAAO,CAAC,YAAY,EAAE,QAAQ,CAAC,EAC/B,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CACrC,CAAC;AACJ,CAAC;AAYD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,YAAoB,EACpB,eAAuB;IAEvB,MAAM,UAAU,GAAG,cAAc,EAAE,CAAC;IACpC,MAAM,OAAO,GAAG,MAAM,mBAAmB,CAAC,UAAU,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC;IAChF,MAAM,UAAU,GAAkB,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC;IAChE,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,EAAE,wBAAwB,CAAC,CAAC;IAChE,MAAM,KAAK,GAAG,iBAAiB,CAAC;QAC9B,KAAK,EAAE,OAAO;QACd,UAAU,EAAE,0BAA0B,CAAC,OAAO,EAAE,UAAU,CAAC;KAC5D,CAAC,CAAC;IACH,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;AACxC,CAAC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Policy Verifier -- Multi-round verification with real engine execution
3
+ * and LLM judge.
4
+ *
5
+ * The verifier instantiates a real PolicyEngine, runs test scenarios through
6
+ * it, and uses an LLM judge to analyze results, identify gaps, and generate
7
+ * additional probe scenarios. Up to maxRounds iterations.
8
+ */
9
+ import type { LanguageModel } from 'ai';
10
+ import { PolicyEngine } from '../trusted-process/policy-engine.js';
11
+ import type { CompiledPolicyFile, ToolAnnotationsFile, TestScenario, ExecutionResult, VerificationResult, AttributedFailure, ScenarioCorrection } from './types.js';
12
+ export declare function executeScenarios(engine: PolicyEngine, scenarios: TestScenario[]): ExecutionResult[];
13
+ export declare function formatExecutionResults(results: ExecutionResult[]): string;
14
+ export declare function verifyPolicy(constitutionText: string, compiledPolicy: CompiledPolicyFile, toolAnnotations: ToolAnnotationsFile, protectedPaths: string[], scenarios: TestScenario[], llm: LanguageModel, maxRounds?: number, allowedDirectory?: string, onProgress?: (message: string) => void): Promise<VerificationResult>;
15
+ export interface DiscardedScenario {
16
+ scenario: TestScenario;
17
+ actual: ExecutionResult['actualDecision'];
18
+ rule: string;
19
+ }
20
+ /**
21
+ * Filters out scenarios whose expected decision conflicts with a structural
22
+ * invariant result. Structural invariants are ground truth (hardcoded in the
23
+ * engine), so a disagreeing scenario expectation is wrong by definition.
24
+ *
25
+ * Returns the valid scenarios and the discarded ones (with metadata for
26
+ * logging / feedback).
27
+ */
28
+ export declare function filterStructuralConflicts(engine: PolicyEngine, scenarios: TestScenario[]): {
29
+ valid: TestScenario[];
30
+ discarded: DiscardedScenario[];
31
+ };
32
+ /**
33
+ * Extracts scenario corrections from attributed failures.
34
+ * Only failures blamed on 'scenario' or 'both' produce corrections.
35
+ * Handwritten scenarios are never auto-corrected — they are returned
36
+ * separately as warnings so the caller can reclassify them as rule issues.
37
+ */
38
+ export declare function extractScenarioCorrections(attributedFailures: AttributedFailure[], scenarios: TestScenario[]): {
39
+ corrections: ScenarioCorrection[];
40
+ handwrittenWarnings: string[];
41
+ };
42
+ /**
43
+ * Applies corrections to a scenario list, returning a new array with
44
+ * updated expectedDecision and reasoning for corrected scenarios.
45
+ */
46
+ export declare function applyScenarioCorrections(scenarios: TestScenario[], corrections: ScenarioCorrection[]): TestScenario[];
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Policy Verifier -- Multi-round verification with real engine execution
3
+ * and LLM judge.
4
+ *
5
+ * The verifier instantiates a real PolicyEngine, runs test scenarios through
6
+ * it, and uses an LLM judge to analyze results, identify gaps, and generate
7
+ * additional probe scenarios. Up to maxRounds iterations.
8
+ */
9
+ import { z } from 'zod';
10
+ import { generateObjectWithRepair } from './generate-with-repair.js';
11
+ import { PolicyEngine } from '../trusted-process/policy-engine.js';
12
+ const DEFAULT_MAX_ROUNDS = 3;
13
+ export function executeScenarios(engine, scenarios) {
14
+ return scenarios.map(scenario => {
15
+ const request = {
16
+ requestId: `verify-${Date.now()}-${Math.random().toString(36).slice(2)}`,
17
+ serverName: scenario.request.serverName,
18
+ toolName: scenario.request.toolName,
19
+ arguments: scenario.request.arguments,
20
+ timestamp: new Date().toISOString(),
21
+ };
22
+ const result = engine.evaluate(request);
23
+ return {
24
+ scenario,
25
+ actualDecision: result.decision,
26
+ matchingRule: result.rule,
27
+ pass: result.decision === scenario.expectedDecision,
28
+ };
29
+ });
30
+ }
31
+ export function formatExecutionResults(results) {
32
+ return results.map((r, i) => {
33
+ const status = r.pass ? 'PASS' : 'FAIL';
34
+ return [
35
+ `[${i + 1}] ${status}: ${r.scenario.description}`,
36
+ ` Tool: ${r.scenario.request.serverName}/${r.scenario.request.toolName}`,
37
+ ` Args: ${JSON.stringify(r.scenario.request.arguments)}`,
38
+ ` Expected: ${r.scenario.expectedDecision}`,
39
+ ` Actual: ${r.actualDecision} (rule: ${r.matchingRule})`,
40
+ ` Source: ${r.scenario.source}`,
41
+ ].join('\n');
42
+ }).join('\n\n');
43
+ }
44
+ function buildJudgePrompt(constitutionText, compiledPolicy, toolAnnotations, protectedPaths, executionResults, roundNumber, previousAnalysis, availableTools) {
45
+ const rulesText = compiledPolicy.rules
46
+ .map((r, i) => ` ${i + 1}. [${r.name}] if: ${JSON.stringify(r.if)} then: ${r.then} -- ${r.reason}`)
47
+ .join('\n');
48
+ const resultsText = formatExecutionResults(executionResults);
49
+ const previousContext = previousAnalysis
50
+ ? `\n## Previous Round Analysis\n\n${previousAnalysis}\n`
51
+ : '';
52
+ return `You are a security policy verifier. Analyze whether the compiled policy correctly implements the constitution.
53
+
54
+ ## Constitution
55
+
56
+ ${constitutionText}
57
+
58
+ ## Compiled Policy Rules (evaluated in order, first match wins)
59
+
60
+ ${rulesText}
61
+
62
+ ## Structural Invariants (hardcoded, evaluated before compiled rules)
63
+
64
+ - Unknown tools (no annotation): denied
65
+ - Protected paths (any write/delete targeting these is automatically denied):
66
+ ${protectedPaths.map(p => ` - ${p}`).join('\n')}
67
+
68
+ A \`deny\` result for write/delete operations on these paths is correct structural behavior, not a policy gap. Files with similar names inside the sandbox are NOT protected.
69
+
70
+ ## Execution Results (Round ${roundNumber})
71
+ ${previousContext}
72
+ ${resultsText}
73
+
74
+ ## Decision Types
75
+
76
+ The three possible policy decisions are:
77
+ - **allow** — the operation is explicitly permitted by the constitution
78
+ - **deny** — the operation is categorically forbidden by the constitution (absolute prohibition)
79
+ - **escalate** — the operation is not explicitly permitted but also not forbidden; it requires human approval
80
+
81
+ When analyzing FAIL results, pay attention to whether the constitution implies "deny" vs "escalate". If the constitution does not explicitly forbid an operation, the correct decision is typically "escalate" (not "deny") so a human can make the judgment call.
82
+
83
+ ## Instructions
84
+
85
+ 1. Analyze any FAIL results. For each failure, determine the blame:
86
+ - **"rule"**: The compiled rule is wrong and needs to be fixed. The scenario expectation correctly reflects the constitution.
87
+ - **"scenario"**: The scenario expectation is wrong. The compiled rule is correct per the constitution. Provide the corrected expectedDecision and reasoning.
88
+ - **"both"**: The rule needs adjustment AND the scenario expectation is wrong. Provide the corrected expectedDecision and reasoning.
89
+ 2. Identify suspicious patterns (e.g., a broad allow rule shadowing a narrow deny, or "deny" used where "escalate" would be more appropriate).
90
+ 3. Identify missing coverage -- scenarios the constitution implies that were not tested.
91
+ 4. If you suspect gaps, generate additional test scenarios to probe them.
92
+ 5. Set "pass" to true ONLY if all results are correct and coverage is adequate.
93
+ 6. Return a "failureAttributions" entry for EVERY FAIL result. The scenarioDescription must exactly match the FAIL scenario's description.
94
+
95
+ For additional scenarios, use concrete paths matching the directories in the compiled rules. Note: sandbox containment is handled by a structural invariant before compiled rules run — any tool call where all paths are within the sandbox directory is automatically allowed.
96
+
97
+ ## Available Tools
98
+
99
+ IMPORTANT: Only use these exact server/tool combinations in additional scenarios. Do NOT invent tool names.
100
+
101
+ ${(availableTools ?? []).map(t => `- ${t.serverName}/${t.toolName}`).join('\n')}
102
+
103
+ ## Response Format
104
+
105
+ Be concise. Keep the analysis to 2-3 sentences per issue found. Only generate additional scenarios that test genuinely untested gaps -- do not duplicate existing coverage. Limit additional scenarios to at most 5.`;
106
+ }
107
+ export async function verifyPolicy(constitutionText, compiledPolicy, toolAnnotations, protectedPaths, scenarios, llm, maxRounds = DEFAULT_MAX_ROUNDS, allowedDirectory, onProgress) {
108
+ const engine = new PolicyEngine(compiledPolicy, toolAnnotations, protectedPaths, allowedDirectory);
109
+ const allAnnotations = Object.values(toolAnnotations.servers).flatMap(s => s.tools);
110
+ const serverNamesList = [...new Set(allAnnotations.map(a => a.serverName))];
111
+ const toolNamesList = [...new Set(allAnnotations.map(a => a.toolName))];
112
+ const availableTools = allAnnotations.map(a => ({ serverName: a.serverName, toolName: a.toolName }));
113
+ const blameSchema = z.discriminatedUnion('kind', [
114
+ z.object({
115
+ kind: z.literal('rule'),
116
+ reasoning: z.string(),
117
+ }),
118
+ z.object({
119
+ kind: z.literal('scenario'),
120
+ reasoning: z.string(),
121
+ correctedDecision: z.enum(['allow', 'deny', 'escalate']),
122
+ correctedReasoning: z.string(),
123
+ }),
124
+ z.object({
125
+ kind: z.literal('both'),
126
+ reasoning: z.string(),
127
+ correctedDecision: z.enum(['allow', 'deny', 'escalate']),
128
+ correctedReasoning: z.string(),
129
+ }),
130
+ ]);
131
+ const responseSchema = z.object({
132
+ analysis: z.string(),
133
+ pass: z.boolean(),
134
+ failureAttributions: z.array(z.object({
135
+ scenarioDescription: z.string(),
136
+ blame: blameSchema,
137
+ })),
138
+ additionalScenarios: z.array(z.object({
139
+ description: z.string(),
140
+ request: z.object({
141
+ serverName: z.enum(serverNamesList),
142
+ toolName: z.enum(toolNamesList),
143
+ arguments: z.record(z.string(), z.unknown()),
144
+ }),
145
+ expectedDecision: z.enum(['allow', 'deny', 'escalate']),
146
+ reasoning: z.string(),
147
+ })),
148
+ });
149
+ const rounds = [];
150
+ const allFailedScenarios = [];
151
+ let currentScenarios = scenarios;
152
+ let previousAnalysis;
153
+ for (let round = 1; round <= maxRounds; round++) {
154
+ // Execute scenarios against the real engine
155
+ const executionResults = executeScenarios(engine, currentScenarios);
156
+ const failures = executionResults.filter(r => !r.pass);
157
+ allFailedScenarios.push(...failures);
158
+ // Send results to LLM judge
159
+ const prompt = buildJudgePrompt(constitutionText, compiledPolicy, toolAnnotations, protectedPaths, executionResults, round, previousAnalysis, availableTools);
160
+ const { output: judgment } = await generateObjectWithRepair({
161
+ model: llm,
162
+ schema: responseSchema,
163
+ prompt,
164
+ onProgress,
165
+ });
166
+ const newScenarios = judgment.additionalScenarios.map(s => ({
167
+ ...s,
168
+ source: 'generated',
169
+ }));
170
+ rounds.push({
171
+ round,
172
+ executionResults,
173
+ llmAnalysis: judgment.analysis,
174
+ newScenarios,
175
+ attributedFailures: judgment.failureAttributions,
176
+ });
177
+ previousAnalysis = judgment.analysis;
178
+ // If judge says pass and no new scenarios, we are done
179
+ if (judgment.pass && newScenarios.length === 0) {
180
+ return {
181
+ pass: true,
182
+ rounds,
183
+ summary: judgment.analysis,
184
+ failedScenarios: allFailedScenarios,
185
+ };
186
+ }
187
+ // If no new scenarios to probe, exit the loop
188
+ if (newScenarios.length === 0)
189
+ break;
190
+ // Run additional scenarios in next round
191
+ currentScenarios = newScenarios;
192
+ }
193
+ // Determine final result based on failed scenarios
194
+ const lastRound = rounds[rounds.length - 1];
195
+ const hasFailures = allFailedScenarios.length > 0;
196
+ return {
197
+ pass: !hasFailures && (lastRound?.llmAnalysis.toLowerCase().includes('pass') ?? false),
198
+ rounds,
199
+ summary: lastRound?.llmAnalysis ?? 'Verification did not complete',
200
+ failedScenarios: allFailedScenarios,
201
+ };
202
+ }
203
+ /**
204
+ * Filters out scenarios whose expected decision conflicts with a structural
205
+ * invariant result. Structural invariants are ground truth (hardcoded in the
206
+ * engine), so a disagreeing scenario expectation is wrong by definition.
207
+ *
208
+ * Returns the valid scenarios and the discarded ones (with metadata for
209
+ * logging / feedback).
210
+ */
211
+ export function filterStructuralConflicts(engine, scenarios) {
212
+ const results = executeScenarios(engine, scenarios);
213
+ const valid = [];
214
+ const discarded = [];
215
+ for (const result of results) {
216
+ const isStructural = result.matchingRule.startsWith('structural-');
217
+ if (isStructural && !result.pass) {
218
+ discarded.push({
219
+ scenario: result.scenario,
220
+ actual: result.actualDecision,
221
+ rule: result.matchingRule,
222
+ });
223
+ }
224
+ else {
225
+ valid.push(result.scenario);
226
+ }
227
+ }
228
+ return { valid, discarded };
229
+ }
230
+ // ---------------------------------------------------------------------------
231
+ // Scenario Corrections
232
+ // ---------------------------------------------------------------------------
233
+ /**
234
+ * Extracts scenario corrections from attributed failures.
235
+ * Only failures blamed on 'scenario' or 'both' produce corrections.
236
+ * Handwritten scenarios are never auto-corrected — they are returned
237
+ * separately as warnings so the caller can reclassify them as rule issues.
238
+ */
239
+ export function extractScenarioCorrections(attributedFailures, scenarios) {
240
+ const corrections = [];
241
+ const handwrittenWarnings = [];
242
+ for (const af of attributedFailures) {
243
+ if (af.blame.kind === 'rule')
244
+ continue;
245
+ const scenario = scenarios.find(s => s.description === af.scenarioDescription);
246
+ if (!scenario)
247
+ continue;
248
+ if (scenario.source === 'handwritten') {
249
+ handwrittenWarnings.push(`Judge blamed scenario "${af.scenarioDescription}" but it is handwritten — treating as rule issue`);
250
+ continue;
251
+ }
252
+ corrections.push({
253
+ scenarioDescription: af.scenarioDescription,
254
+ correctedDecision: af.blame.correctedDecision,
255
+ correctedReasoning: af.blame.correctedReasoning,
256
+ });
257
+ }
258
+ return { corrections, handwrittenWarnings };
259
+ }
260
+ /**
261
+ * Applies corrections to a scenario list, returning a new array with
262
+ * updated expectedDecision and reasoning for corrected scenarios.
263
+ */
264
+ export function applyScenarioCorrections(scenarios, corrections) {
265
+ const correctionMap = new Map(corrections.map(c => [c.scenarioDescription, c]));
266
+ return scenarios.map(s => {
267
+ const correction = correctionMap.get(s.description);
268
+ if (!correction)
269
+ return s;
270
+ return {
271
+ ...s,
272
+ expectedDecision: correction.correctedDecision,
273
+ reasoning: correction.correctedReasoning,
274
+ };
275
+ });
276
+ }
277
+ //# sourceMappingURL=policy-verifier.js.map