@provos/ironcurtain 0.8.0 → 0.9.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.
- package/dist/auth/auth-command.d.ts +15 -0
- package/dist/auth/auth-command.js +424 -0
- package/dist/auth/auth-command.js.map +1 -0
- package/dist/auth/oauth-flow.d.ts +46 -0
- package/dist/auth/oauth-flow.js +279 -0
- package/dist/auth/oauth-flow.js.map +1 -0
- package/dist/auth/oauth-provider.d.ts +55 -0
- package/dist/auth/oauth-provider.js +73 -0
- package/dist/auth/oauth-provider.js.map +1 -0
- package/dist/auth/oauth-registry.d.ts +34 -0
- package/dist/auth/oauth-registry.js +69 -0
- package/dist/auth/oauth-registry.js.map +1 -0
- package/dist/auth/oauth-token-provider.d.ts +52 -0
- package/dist/auth/oauth-token-provider.js +159 -0
- package/dist/auth/oauth-token-provider.js.map +1 -0
- package/dist/auth/oauth-token-store.d.ts +30 -0
- package/dist/auth/oauth-token-store.js +93 -0
- package/dist/auth/oauth-token-store.js.map +1 -0
- package/dist/auth/providers/google-scopes.d.ts +35 -0
- package/dist/auth/providers/google-scopes.js +183 -0
- package/dist/auth/providers/google-scopes.js.map +1 -0
- package/dist/auth/providers/google.d.ts +2 -0
- package/dist/auth/providers/google.js +28 -0
- package/dist/auth/providers/google.js.map +1 -0
- package/dist/cli.js +6 -0
- package/dist/cli.js.map +1 -1
- package/dist/config/config-command.js +39 -1
- package/dist/config/config-command.js.map +1 -1
- package/dist/config/constitution-readonly.md +14 -11
- package/dist/config/constitution-user-base.md +15 -0
- package/dist/config/generated/test-scenarios.json +1 -1
- package/dist/config/generated/tool-annotations.json +10617 -296
- package/dist/config/generated-readonly/compiled-policy.json +787 -244
- package/dist/config/index.d.ts +2 -2
- package/dist/config/index.js +8 -0
- package/dist/config/index.js.map +1 -1
- package/dist/config/mcp-servers.json +23 -0
- package/dist/config/paths.d.ts +15 -0
- package/dist/config/paths.js +35 -0
- package/dist/config/paths.js.map +1 -1
- package/dist/config/types.d.ts +8 -0
- package/dist/config/user-config.d.ts +2 -0
- package/dist/config/user-config.js +2 -0
- package/dist/config/user-config.js.map +1 -1
- package/dist/cron/headless-transport.d.ts +4 -0
- package/dist/cron/headless-transport.js +1 -1
- package/dist/cron/headless-transport.js.map +1 -1
- package/dist/daemon/ironcurtain-daemon.js +6 -1
- package/dist/daemon/ironcurtain-daemon.js.map +1 -1
- package/dist/docker/docker-agent-session.d.ts +3 -1
- package/dist/docker/docker-agent-session.js +49 -28
- package/dist/docker/docker-agent-session.js.map +1 -1
- package/dist/docker/docker-infrastructure.js +33 -8
- package/dist/docker/docker-infrastructure.js.map +1 -1
- package/dist/docker/docker-manager.d.ts +2 -0
- package/dist/docker/docker-manager.js +9 -1
- package/dist/docker/docker-manager.js.map +1 -1
- package/dist/docker/mitm-proxy.d.ts +50 -0
- package/dist/docker/mitm-proxy.js +220 -31
- package/dist/docker/mitm-proxy.js.map +1 -1
- package/dist/docker/platform.d.ts +6 -6
- package/dist/docker/platform.js +8 -6
- package/dist/docker/platform.js.map +1 -1
- package/dist/docker/proxy-tools.d.ts +80 -0
- package/dist/docker/proxy-tools.js +248 -0
- package/dist/docker/proxy-tools.js.map +1 -0
- package/dist/docker/pty-session.js +48 -29
- package/dist/docker/pty-session.js.map +1 -1
- package/dist/docker/pty-types.d.ts +4 -0
- package/dist/docker/pty-types.js.map +1 -1
- package/dist/escalation/escalation-watcher.d.ts +3 -1
- package/dist/escalation/escalation-watcher.js +6 -2
- package/dist/escalation/escalation-watcher.js.map +1 -1
- package/dist/escalation/listener-command.js +24 -10
- package/dist/escalation/listener-command.js.map +1 -1
- package/dist/escalation/listener-lock.d.ts +5 -0
- package/dist/escalation/listener-lock.js +14 -0
- package/dist/escalation/listener-lock.js.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/memory/auto-save.d.ts +25 -0
- package/dist/memory/auto-save.js +89 -0
- package/dist/memory/auto-save.js.map +1 -0
- package/dist/mux/mux-app.d.ts +4 -0
- package/dist/mux/mux-app.js +168 -18
- package/dist/mux/mux-app.js.map +1 -1
- package/dist/mux/mux-command.d.ts +2 -2
- package/dist/mux/mux-command.js +14 -22
- package/dist/mux/mux-command.js.map +1 -1
- package/dist/mux/mux-escalation-manager.d.ts +9 -3
- package/dist/mux/mux-escalation-manager.js +69 -19
- package/dist/mux/mux-escalation-manager.js.map +1 -1
- package/dist/mux/mux-input-handler.d.ts +31 -1
- package/dist/mux/mux-input-handler.js +106 -25
- package/dist/mux/mux-input-handler.js.map +1 -1
- package/dist/mux/mux-renderer.d.ts +17 -1
- package/dist/mux/mux-renderer.js +288 -7
- package/dist/mux/mux-renderer.js.map +1 -1
- package/dist/mux/pty-bridge.d.ts +4 -0
- package/dist/mux/pty-bridge.js +13 -1
- package/dist/mux/pty-bridge.js.map +1 -1
- package/dist/mux/types.d.ts +24 -1
- package/dist/mux/types.js +10 -1
- package/dist/mux/types.js.map +1 -1
- package/dist/persona/persona-command.js +0 -5
- package/dist/persona/persona-command.js.map +1 -1
- package/dist/pipeline/annotate.js +1 -7
- package/dist/pipeline/annotate.js.map +1 -1
- package/dist/pipeline/compile.d.ts +5 -2
- package/dist/pipeline/compile.js +23 -11
- package/dist/pipeline/compile.js.map +1 -1
- package/dist/pipeline/constitution-compiler.d.ts +302 -3
- package/dist/pipeline/constitution-compiler.js +285 -23
- package/dist/pipeline/constitution-compiler.js.map +1 -1
- package/dist/pipeline/generate-with-repair.d.ts +2 -2
- package/dist/pipeline/generate-with-repair.js +2 -2
- package/dist/pipeline/handwritten-scenarios.d.ts +20 -7
- package/dist/pipeline/handwritten-scenarios.js +20 -41
- package/dist/pipeline/handwritten-scenarios.js.map +1 -1
- package/dist/pipeline/list-resolver.d.ts +18 -20
- package/dist/pipeline/list-resolver.js +28 -36
- package/dist/pipeline/list-resolver.js.map +1 -1
- package/dist/pipeline/pipeline-runner.d.ts +102 -13
- package/dist/pipeline/pipeline-runner.js +497 -237
- package/dist/pipeline/pipeline-runner.js.map +1 -1
- package/dist/pipeline/pipeline-shared.d.ts +7 -9
- package/dist/pipeline/pipeline-shared.js +8 -18
- package/dist/pipeline/pipeline-shared.js.map +1 -1
- package/dist/pipeline/policy-verifier.d.ts +32 -5
- package/dist/pipeline/policy-verifier.js +137 -8
- package/dist/pipeline/policy-verifier.js.map +1 -1
- package/dist/pipeline/proxy-mcp-connections.d.ts +25 -0
- package/dist/pipeline/proxy-mcp-connections.js +121 -0
- package/dist/pipeline/proxy-mcp-connections.js.map +1 -0
- package/dist/pipeline/refresh-lists.js +21 -13
- package/dist/pipeline/refresh-lists.js.map +1 -1
- package/dist/pipeline/scenario-generator.d.ts +29 -49
- package/dist/pipeline/scenario-generator.js +180 -143
- package/dist/pipeline/scenario-generator.js.map +1 -1
- package/dist/pipeline/scenario-schema-validator.d.ts +51 -0
- package/dist/pipeline/scenario-schema-validator.js +132 -0
- package/dist/pipeline/scenario-schema-validator.js.map +1 -0
- package/dist/pipeline/tool-annotator.d.ts +9 -8
- package/dist/pipeline/tool-annotator.js +48 -95
- package/dist/pipeline/tool-annotator.js.map +1 -1
- package/dist/pipeline/types.d.ts +31 -24
- package/dist/sandbox/index.js +20 -0
- package/dist/sandbox/index.js.map +1 -1
- package/dist/servers/fetch-server.js +10 -5
- package/dist/servers/fetch-server.js.map +1 -1
- package/dist/session/agent-session.d.ts +6 -19
- package/dist/session/agent-session.js +18 -69
- package/dist/session/agent-session.js.map +1 -1
- package/dist/session/base-transport.d.ts +17 -1
- package/dist/session/base-transport.js +25 -1
- package/dist/session/base-transport.js.map +1 -1
- package/dist/session/cli-transport.d.ts +5 -0
- package/dist/session/cli-transport.js +48 -25
- package/dist/session/cli-transport.js.map +1 -1
- package/dist/session/prompts.js +0 -1
- package/dist/session/prompts.js.map +1 -1
- package/dist/session/session-manager.d.ts +6 -0
- package/dist/session/session-manager.js +12 -0
- package/dist/session/session-manager.js.map +1 -1
- package/dist/session/types.d.ts +6 -1
- package/dist/session/types.js.map +1 -1
- package/dist/signal/signal-bot-daemon.js +24 -8
- package/dist/signal/signal-bot-daemon.js.map +1 -1
- package/dist/signal/signal-transport.d.ts +5 -2
- package/dist/signal/signal-transport.js +3 -3
- package/dist/signal/signal-transport.js.map +1 -1
- package/dist/trusted-process/approval-whitelist.d.ts +117 -0
- package/dist/trusted-process/approval-whitelist.js +237 -0
- package/dist/trusted-process/approval-whitelist.js.map +1 -0
- package/dist/trusted-process/gworkspace-credentials.d.ts +40 -0
- package/dist/trusted-process/gworkspace-credentials.js +45 -0
- package/dist/trusted-process/gworkspace-credentials.js.map +1 -0
- package/dist/trusted-process/index.d.ts +11 -1
- package/dist/trusted-process/index.js +63 -27
- package/dist/trusted-process/index.js.map +1 -1
- package/dist/trusted-process/mcp-proxy-server.d.ts +14 -0
- package/dist/trusted-process/mcp-proxy-server.js +325 -64
- package/dist/trusted-process/mcp-proxy-server.js.map +1 -1
- package/dist/trusted-process/policy-engine.d.ts +8 -2
- package/dist/trusted-process/policy-engine.js +18 -22
- package/dist/trusted-process/policy-engine.js.map +1 -1
- package/dist/trusted-process/policy-types.d.ts +17 -0
- package/dist/trusted-process/sandbox-integration.d.ts +23 -0
- package/dist/trusted-process/sandbox-integration.js +115 -3
- package/dist/trusted-process/sandbox-integration.js.map +1 -1
- package/dist/trusted-process/token-file-refresher.d.ts +69 -0
- package/dist/trusted-process/token-file-refresher.js +120 -0
- package/dist/trusted-process/token-file-refresher.js.map +1 -0
- package/dist/types/argument-roles.d.ts +7 -1
- package/dist/types/argument-roles.js +50 -0
- package/dist/types/argument-roles.js.map +1 -1
- package/dist/types/audit.d.ts +11 -0
- package/docker/Dockerfile.claude-code +2 -2
- package/docker/entrypoint-claude-code.sh +15 -5
- package/package.json +3 -2
- package/src/config/constitution-user-base.md +15 -0
- package/src/config/generated/test-scenarios.json +1 -1
- package/src/config/generated/tool-annotations.json +10617 -296
- package/src/config/mcp-servers.json +23 -0
- package/dist/pipeline/mcp-connections.d.ts +0 -16
- package/dist/pipeline/mcp-connections.js +0 -61
- package/dist/pipeline/mcp-connections.js.map +0 -1
|
@@ -4,11 +4,19 @@
|
|
|
4
4
|
* Generates concrete test scenarios (tool call + expected decision) from
|
|
5
5
|
* the constitution and tool annotations. Always includes mandatory
|
|
6
6
|
* handwritten scenarios alongside LLM-generated ones.
|
|
7
|
+
*
|
|
8
|
+
* Large servers (100+ tools) are handled via batching: annotations are
|
|
9
|
+
* split into batches of SCENARIO_BATCH_SIZE, each batch gets its own
|
|
10
|
+
* scoped system prompt and Zod schema, and results are deduplicated
|
|
11
|
+
* across batches.
|
|
7
12
|
*/
|
|
8
|
-
import { generateText } from 'ai';
|
|
9
13
|
import { z } from 'zod';
|
|
10
|
-
import { DEFAULT_MAX_TOKENS, generateObjectWithRepair
|
|
11
|
-
|
|
14
|
+
import { DEFAULT_MAX_TOKENS, generateObjectWithRepair } from './generate-with-repair.js';
|
|
15
|
+
import { chunk } from './tool-annotator.js';
|
|
16
|
+
import { isConditionalRoles } from '../types/argument-roles.js';
|
|
17
|
+
import { buildScenarioArgsSuperRefine, buildToolArgNamesMap, formatToolArgNames } from './scenario-schema-validator.js';
|
|
18
|
+
export const SCENARIO_BATCH_SIZE = 25;
|
|
19
|
+
function buildGeneratorResponseSchema(serverNames, toolNames, argsSuperRefine) {
|
|
12
20
|
const scenarioSchema = z.object({
|
|
13
21
|
description: z.string(),
|
|
14
22
|
request: z.object({
|
|
@@ -19,8 +27,9 @@ function buildGeneratorResponseSchema(serverNames, toolNames) {
|
|
|
19
27
|
expectedDecision: z.enum(['allow', 'deny', 'escalate']),
|
|
20
28
|
reasoning: z.string(),
|
|
21
29
|
});
|
|
30
|
+
const finalSchema = argsSuperRefine ? scenarioSchema.superRefine(argsSuperRefine) : scenarioSchema;
|
|
22
31
|
return z.object({
|
|
23
|
-
scenarios: z.array(
|
|
32
|
+
scenarios: z.array(finalSchema),
|
|
24
33
|
});
|
|
25
34
|
}
|
|
26
35
|
/** Maximum number of example values shown per list to keep prompts concise. */
|
|
@@ -49,18 +58,70 @@ ${entries}
|
|
|
49
58
|
For list-based scenarios, use values FROM these lists for positive/allow cases and obviously unrelated values (e.g., a random unrelated domain or identifier) for negative/deny/escalate cases.
|
|
50
59
|
`;
|
|
51
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Formats a single conditional role spec as a human-readable string.
|
|
63
|
+
* Shows the discriminator argument, its valid values, and the role
|
|
64
|
+
* assignments per mode so the LLM knows exact argument names/values.
|
|
65
|
+
*/
|
|
66
|
+
export function formatConditionalRoles(argName, spec) {
|
|
67
|
+
const clauses = spec.when.map((entry) => {
|
|
68
|
+
const cond = entry.condition;
|
|
69
|
+
let condStr;
|
|
70
|
+
if (cond.equals !== undefined) {
|
|
71
|
+
condStr = `${cond.arg}=${JSON.stringify(cond.equals)}`;
|
|
72
|
+
}
|
|
73
|
+
else if (cond.in !== undefined) {
|
|
74
|
+
condStr = `${cond.arg} in ${JSON.stringify(cond.in)}`;
|
|
75
|
+
}
|
|
76
|
+
else if (cond.is !== undefined) {
|
|
77
|
+
condStr = `${cond.arg} is ${cond.is}`;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
condStr = `${cond.arg}=?`;
|
|
81
|
+
}
|
|
82
|
+
return `when ${condStr} → [${entry.roles.join(', ')}]`;
|
|
83
|
+
});
|
|
84
|
+
return `${argName}: default=[${spec.default.join(', ')}], ${clauses.join('; ')}`;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Formats a tool annotation summary line for the scenario generator prompt.
|
|
88
|
+
* When a stored annotation is available, includes conditional role details
|
|
89
|
+
* so the LLM knows the exact discriminator argument names and valid values.
|
|
90
|
+
*/
|
|
91
|
+
function formatAnnotationForPrompt(annotation, storedAnnotation) {
|
|
92
|
+
const args = storedAnnotation?.args ?? annotation.args;
|
|
93
|
+
const argsDesc = Object.entries(args)
|
|
94
|
+
.map(([name, spec]) => {
|
|
95
|
+
if (isConditionalRoles(spec)) {
|
|
96
|
+
return formatConditionalRoles(name, spec);
|
|
97
|
+
}
|
|
98
|
+
// After isConditionalRoles check, spec is always ArgumentRole[]
|
|
99
|
+
return `${name}: [${spec.join(', ')}]`;
|
|
100
|
+
})
|
|
101
|
+
.join(', ');
|
|
102
|
+
return ` ${annotation.serverName}/${annotation.toolName}: ${annotation.comment}, args={${argsDesc || 'none'}}`;
|
|
103
|
+
}
|
|
52
104
|
/**
|
|
53
105
|
* Builds the stable system prompt portion for the scenario generator.
|
|
54
106
|
* Contains: role preamble, constitution, annotations, system config, and instructions.
|
|
55
107
|
* This is the cacheable part.
|
|
108
|
+
*
|
|
109
|
+
* @param storedAnnotations - Optional raw annotations with conditional role specs.
|
|
110
|
+
* When provided, the prompt includes discriminator argument names and valid values
|
|
111
|
+
* so the LLM generates scenarios with correct argument names for multi-mode tools.
|
|
56
112
|
*/
|
|
57
|
-
export function buildGeneratorSystemPrompt(constitutionText, annotations, sandboxDirectory, permittedDirectories, dynamicLists) {
|
|
113
|
+
export function buildGeneratorSystemPrompt(constitutionText, annotations, sandboxDirectory, permittedDirectories, dynamicLists, storedAnnotations) {
|
|
114
|
+
// Build a lookup from stored annotations for conditional role detail
|
|
115
|
+
const storedByKey = new Map();
|
|
116
|
+
if (storedAnnotations) {
|
|
117
|
+
for (const sa of storedAnnotations) {
|
|
118
|
+
storedByKey.set(`${sa.serverName}/${sa.toolName}`, sa);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
58
121
|
const annotationsSummary = annotations
|
|
59
122
|
.map((a) => {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
.join(', ');
|
|
63
|
-
return ` ${a.serverName}/${a.toolName}: ${a.comment}, sideEffects=${a.sideEffects}, args={${argsDesc || 'none'}}`;
|
|
123
|
+
const stored = storedByKey.get(`${a.serverName}/${a.toolName}`);
|
|
124
|
+
return formatAnnotationForPrompt(a, stored);
|
|
64
125
|
})
|
|
65
126
|
.join('\n');
|
|
66
127
|
return `You are generating test scenarios for a security policy engine. Each scenario is a concrete tool call with an expected policy decision.
|
|
@@ -73,6 +134,7 @@ ${constitutionText}
|
|
|
73
134
|
|
|
74
135
|
${annotationsSummary}
|
|
75
136
|
|
|
137
|
+
${storedAnnotations ? `## Valid Tool Arguments (from MCP input schemas)\n\n${formatToolArgNames(storedAnnotations)}\n\nCRITICAL: Only use argument names listed above for each tool. Using unlisted argument names will cause validation failure.\n` : ''}
|
|
76
138
|
## System Configuration
|
|
77
139
|
|
|
78
140
|
- Sandbox directory: ${sandboxDirectory}
|
|
@@ -93,7 +155,7 @@ Generate diverse test scenarios covering:
|
|
|
93
155
|
2. **Negative cases**: operations that SHOULD be denied (categorically forbidden by the constitution, e.g., deletes outside sandbox)
|
|
94
156
|
3. **Escalation cases**: operations outside the sandbox and permitted directories that are NOT categorically forbidden — these require human approval
|
|
95
157
|
4. **Edge cases**: path traversal attacks, boundary conditions
|
|
96
|
-
5. **
|
|
158
|
+
5. **Read-only tools**: tools where all arguments have \`none\`-role (no resource-identifier arguments) — these should typically be allowed
|
|
97
159
|
6. **Move operations**: all moves involve a delete-path role on the source argument
|
|
98
160
|
7. **Domain-based operations**: if any tools have URL roles (fetch-url, git-remote-url), generate scenarios for allowed domains, disallowed domains, and named remote resolution. For git-remote-url args, always supply an explicit URL (https:// or git@ form) — never omit them (see IMPORTANT below)
|
|
99
161
|
|
|
@@ -137,149 +199,124 @@ function areSimilar(a, b) {
|
|
|
137
199
|
const bArgs = JSON.stringify(b.request.arguments);
|
|
138
200
|
return aArgs === bArgs;
|
|
139
201
|
}
|
|
140
|
-
export async function generateScenarios(constitutionText, annotations, handwrittenScenarios, sandboxDirectory, llm, permittedDirectories, onProgress, system, dynamicLists) {
|
|
141
|
-
const serverNames = [...new Set(annotations.map((a) => a.serverName))];
|
|
142
|
-
const toolNames = [...new Set(annotations.map((a) => a.toolName))];
|
|
143
|
-
const schema = buildGeneratorResponseSchema(serverNames, toolNames);
|
|
144
|
-
const effectiveSystem = system ??
|
|
145
|
-
buildGeneratorSystemPrompt(constitutionText, annotations, sandboxDirectory, permittedDirectories, dynamicLists);
|
|
146
|
-
const { output } = await generateObjectWithRepair({
|
|
147
|
-
model: llm,
|
|
148
|
-
schema,
|
|
149
|
-
system: effectiveSystem,
|
|
150
|
-
prompt: 'Generate test scenarios following the instructions above.',
|
|
151
|
-
onProgress,
|
|
152
|
-
});
|
|
153
|
-
// Mark all LLM-generated scenarios with source: 'generated'
|
|
154
|
-
const generated = output.scenarios.map((s) => ({
|
|
155
|
-
...s,
|
|
156
|
-
source: 'generated',
|
|
157
|
-
}));
|
|
158
|
-
// Deduplicate: remove generated scenarios that are substantially
|
|
159
|
-
// similar to handwritten ones
|
|
160
|
-
const unique = generated.filter((g) => !handwrittenScenarios.some((h) => areSimilar(g, h)));
|
|
161
|
-
// Handwritten first, then generated
|
|
162
|
-
return [...handwrittenScenarios, ...unique];
|
|
163
|
-
}
|
|
164
|
-
// ---------------------------------------------------------------------------
|
|
165
|
-
// Multi-Turn Scenario Generator Session
|
|
166
|
-
// ---------------------------------------------------------------------------
|
|
167
|
-
const INITIAL_USER_MESSAGE = 'Generate test scenarios following the instructions above.';
|
|
168
202
|
/**
|
|
169
|
-
*
|
|
170
|
-
*
|
|
203
|
+
* Builds the user-message prompt for a single scenario generation batch.
|
|
204
|
+
* When handwritten scenarios exist for this batch's tools, they are
|
|
205
|
+
* included so the LLM avoids duplicating their coverage.
|
|
171
206
|
*/
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const lines = feedback.corrections.map((c) => `- "${c.scenarioDescription}": correct decision is ${c.correctedDecision} (${c.correctedReasoning})`);
|
|
176
|
-
sections.push('## Corrected Scenarios\n\n' +
|
|
177
|
-
'These scenarios had wrong expectedDecision values. The verifier determined the correct decisions:\n\n' +
|
|
178
|
-
lines.join('\n'));
|
|
179
|
-
}
|
|
180
|
-
if (feedback.discardedScenarios.length > 0) {
|
|
181
|
-
const lines = feedback.discardedScenarios.map((d) => `- "${d.scenario.description}": ${d.rule} always returns ${d.actual}`);
|
|
182
|
-
sections.push('## Discarded Scenarios (Structural Conflicts)\n\n' +
|
|
183
|
-
'These scenarios conflict with hardcoded structural invariants and were removed. Do NOT regenerate them:\n\n' +
|
|
184
|
-
lines.join('\n'));
|
|
207
|
+
function buildBatchPrompt(batchHandwritten) {
|
|
208
|
+
if (batchHandwritten.length === 0) {
|
|
209
|
+
return 'Generate test scenarios following the instructions above.';
|
|
185
210
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
'Keep your new scenarios consistent with the corrections. ' +
|
|
195
|
-
'Do not repeat discarded scenarios or reproduce the original wrong expectations.\n');
|
|
211
|
+
const handwrittenSummary = batchHandwritten
|
|
212
|
+
.map((s) => `- ${s.request.serverName}/${s.request.toolName}: "${s.description}" (${s.expectedDecision})`)
|
|
213
|
+
.join('\n');
|
|
214
|
+
return `The following handwritten scenarios already exist for these tools. Generate additional scenarios that complement them without duplicating their coverage.
|
|
215
|
+
|
|
216
|
+
${handwrittenSummary}
|
|
217
|
+
|
|
218
|
+
Generate test scenarios following the instructions above.`;
|
|
196
219
|
}
|
|
197
220
|
/**
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
* loop (corrections, discarded scenarios, probes) can be communicated to
|
|
202
|
-
* the LLM in follow-up turns. The system prompt is fixed at construction
|
|
203
|
-
* and never changes, enabling prompt caching.
|
|
204
|
-
*
|
|
205
|
-
* Lifecycle:
|
|
206
|
-
* 1. Construct with system prompt and config (once per pipeline run)
|
|
207
|
-
* 2. Call generate() for the initial scenario set
|
|
208
|
-
* 3. After verification, call regenerate(feedback) with repair feedback
|
|
209
|
-
* 4. Session is GC'd when the pipeline finishes (no explicit close needed)
|
|
221
|
+
* Generates test scenarios by batching annotations into groups of
|
|
222
|
+
* SCENARIO_BATCH_SIZE, running generateObjectWithRepair per batch with
|
|
223
|
+
* scoped system prompts and Zod schemas, then deduplicating results.
|
|
210
224
|
*/
|
|
211
|
-
export
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
this.handwrittenScenarios = options.handwrittenScenarios;
|
|
223
|
-
const serverNames = [...new Set(options.annotations.map((a) => a.serverName))];
|
|
224
|
-
const toolNames = [...new Set(options.annotations.map((a) => a.toolName))];
|
|
225
|
-
this.schema = buildGeneratorResponseSchema(serverNames, toolNames);
|
|
226
|
-
this.schemaHint = schemaToPromptHint(this.schema);
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* Initial generation: sends the first user message and returns scenarios.
|
|
230
|
-
* Semantically equivalent to the existing single-shot generateScenarios().
|
|
231
|
-
*/
|
|
232
|
-
async generate(onProgress) {
|
|
233
|
-
const userMessage = INITIAL_USER_MESSAGE + this.schemaHint;
|
|
234
|
-
this.history.push({ role: 'user', content: userMessage });
|
|
235
|
-
onProgress?.('Generating scenarios...');
|
|
236
|
-
const result = await generateText({
|
|
237
|
-
model: this.model,
|
|
238
|
-
system: this.systemPrompt,
|
|
239
|
-
messages: this.history,
|
|
240
|
-
maxOutputTokens: DEFAULT_MAX_TOKENS,
|
|
241
|
-
});
|
|
242
|
-
this.history.push({ role: 'assistant', content: result.text });
|
|
243
|
-
this.turns++;
|
|
244
|
-
const output = parseJsonWithSchema(result.text, this.schema);
|
|
245
|
-
const generated = output.scenarios.map((s) => ({
|
|
246
|
-
...s,
|
|
247
|
-
source: 'generated',
|
|
248
|
-
}));
|
|
249
|
-
// Deduplicate against handwritten scenarios
|
|
250
|
-
const unique = generated.filter((g) => !this.handwrittenScenarios.some((h) => areSimilar(g, h)));
|
|
251
|
-
return [...this.handwrittenScenarios, ...unique];
|
|
225
|
+
export async function generateScenarios(constitutionText, annotations, handwrittenScenarios, sandboxDirectory, llm, permittedDirectories, onProgress, dynamicLists, wrapSystemPrompt, storedAnnotations) {
|
|
226
|
+
if (annotations.length === 0)
|
|
227
|
+
return [...handwrittenScenarios];
|
|
228
|
+
const batches = chunk(annotations, SCENARIO_BATCH_SIZE);
|
|
229
|
+
const allGenerated = [];
|
|
230
|
+
// Build stored annotation lookup for batching
|
|
231
|
+
const storedByKey = new Map();
|
|
232
|
+
if (storedAnnotations) {
|
|
233
|
+
for (const sa of storedAnnotations) {
|
|
234
|
+
storedByKey.set(`${sa.serverName}/${sa.toolName}`, sa);
|
|
235
|
+
}
|
|
252
236
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
237
|
+
for (let i = 0; i < batches.length; i++) {
|
|
238
|
+
const batch = batches[i];
|
|
239
|
+
if (batches.length > 1) {
|
|
240
|
+
onProgress?.(`Batch ${i + 1}/${batches.length} (${batch.length} tools)`);
|
|
241
|
+
}
|
|
242
|
+
// Scoped schema: only this batch's server/tool names
|
|
243
|
+
const serverNames = [...new Set(batch.map((a) => a.serverName))];
|
|
244
|
+
const toolNames = [...new Set(batch.map((a) => a.toolName))];
|
|
245
|
+
// Filter stored annotations to this batch
|
|
246
|
+
const batchStored = storedAnnotations
|
|
247
|
+
? batch
|
|
248
|
+
.map((a) => storedByKey.get(`${a.serverName}/${a.toolName}`))
|
|
249
|
+
.filter((sa) => sa !== undefined)
|
|
250
|
+
: undefined;
|
|
251
|
+
// Build Zod schema with superRefine that validates argument names at parse time
|
|
252
|
+
const argsSuperRefine = batchStored ? buildScenarioArgsSuperRefine(buildToolArgNamesMap(batchStored)) : undefined;
|
|
253
|
+
const schema = buildGeneratorResponseSchema(serverNames, toolNames, argsSuperRefine);
|
|
254
|
+
// Per-batch system prompt with only this batch's annotations
|
|
255
|
+
const batchPromptText = buildGeneratorSystemPrompt(constitutionText, batch, sandboxDirectory, permittedDirectories, dynamicLists, batchStored);
|
|
256
|
+
// Apply cache strategy wrapping if provided
|
|
257
|
+
const batchSystem = wrapSystemPrompt ? wrapSystemPrompt(batchPromptText) : batchPromptText;
|
|
258
|
+
// Filter handwritten scenarios to those relevant to this batch (composite key avoids cross-server collisions)
|
|
259
|
+
const batchToolKeySet = new Set(batch.map((a) => `${a.serverName}:::${a.toolName}`));
|
|
260
|
+
const batchHandwritten = handwrittenScenarios.filter((s) => batchToolKeySet.has(`${s.request.serverName}:::${s.request.toolName}`));
|
|
261
|
+
const { output } = await generateObjectWithRepair({
|
|
262
|
+
model: llm,
|
|
263
|
+
schema,
|
|
264
|
+
system: batchSystem,
|
|
265
|
+
prompt: buildBatchPrompt(batchHandwritten),
|
|
266
|
+
maxOutputTokens: 16384,
|
|
267
|
+
onProgress: batches.length > 1 ? (msg) => onProgress?.(`Batch ${i + 1}/${batches.length}: ${msg}`) : onProgress,
|
|
271
268
|
});
|
|
272
|
-
|
|
273
|
-
this.turns++;
|
|
274
|
-
const output = parseJsonWithSchema(result.text, this.schema);
|
|
275
|
-
return output.scenarios.map((s) => ({
|
|
269
|
+
allGenerated.push(...output.scenarios.map((s) => ({
|
|
276
270
|
...s,
|
|
277
271
|
source: 'generated',
|
|
278
|
-
}));
|
|
279
|
-
}
|
|
280
|
-
/** Returns the number of turns completed so far. */
|
|
281
|
-
get turnCount() {
|
|
282
|
-
return this.turns;
|
|
272
|
+
})));
|
|
283
273
|
}
|
|
274
|
+
// Deduplicate generated against handwritten AND across batches
|
|
275
|
+
const seen = new Set();
|
|
276
|
+
const unique = allGenerated.filter((g) => {
|
|
277
|
+
if (handwrittenScenarios.some((h) => areSimilar(g, h)))
|
|
278
|
+
return false;
|
|
279
|
+
const key = `${g.request.serverName}/${g.request.toolName}/${JSON.stringify(g.request.arguments)}`;
|
|
280
|
+
if (seen.has(key))
|
|
281
|
+
return false;
|
|
282
|
+
seen.add(key);
|
|
283
|
+
return true;
|
|
284
|
+
});
|
|
285
|
+
return [...handwrittenScenarios, ...unique];
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Generates replacement scenarios for structurally discarded ones.
|
|
289
|
+
* Uses a single-shot generateObjectWithRepair call (no batching needed
|
|
290
|
+
* since the discarded set is typically small).
|
|
291
|
+
*/
|
|
292
|
+
export async function repairScenarios(discardedScenarios, constitutionText, annotations, sandboxDirectory, llm, permittedDirectories, dynamicLists, onProgress, storedAnnotations) {
|
|
293
|
+
const serverNames = [...new Set(annotations.map((a) => a.serverName))];
|
|
294
|
+
const toolNames = [...new Set(annotations.map((a) => a.toolName))];
|
|
295
|
+
const argsSuperRefine = storedAnnotations
|
|
296
|
+
? buildScenarioArgsSuperRefine(buildToolArgNamesMap(storedAnnotations))
|
|
297
|
+
: undefined;
|
|
298
|
+
const schema = buildGeneratorResponseSchema(serverNames, toolNames, argsSuperRefine);
|
|
299
|
+
const discardedList = discardedScenarios
|
|
300
|
+
.map((b, i) => `${i + 1}. "${b.scenario.description}" (${b.scenario.request.serverName}/${b.scenario.request.toolName}): ${b.feedback}`)
|
|
301
|
+
.join('\n');
|
|
302
|
+
const prompt = `The following scenarios were discarded because they conflict with structural invariants (hardcoded engine behavior that cannot be overridden by compiled rules).
|
|
303
|
+
Generate replacement scenarios that cover similar tools and decision types but with correct expectations.
|
|
304
|
+
|
|
305
|
+
${discardedList}
|
|
306
|
+
|
|
307
|
+
Generate one replacement scenario per discarded scenario.`;
|
|
308
|
+
const system = buildGeneratorSystemPrompt(constitutionText, annotations, sandboxDirectory, permittedDirectories, dynamicLists, storedAnnotations);
|
|
309
|
+
const { output } = await generateObjectWithRepair({
|
|
310
|
+
model: llm,
|
|
311
|
+
schema,
|
|
312
|
+
system,
|
|
313
|
+
prompt,
|
|
314
|
+
maxOutputTokens: DEFAULT_MAX_TOKENS,
|
|
315
|
+
onProgress,
|
|
316
|
+
});
|
|
317
|
+
return output.scenarios.map((s) => ({
|
|
318
|
+
...s,
|
|
319
|
+
source: 'generated',
|
|
320
|
+
}));
|
|
284
321
|
}
|
|
285
322
|
//# sourceMappingURL=scenario-generator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scenario-generator.js","sourceRoot":"","sources":["../../src/pipeline/scenario-generator.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"scenario-generator.js","sourceRoot":"","sources":["../../src/pipeline/scenario-generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates test scenario arguments against tool input schemas.
|
|
3
|
+
*
|
|
4
|
+
* Catches common LLM errors: wrong argument names, missing required fields,
|
|
5
|
+
* and invalid enum values. Uses the JSON Schema stored in tool annotations
|
|
6
|
+
* (captured from MCP servers at annotation time).
|
|
7
|
+
*/
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import type { StoredToolAnnotation, TestScenario, DiscardedScenario } from './types.js';
|
|
10
|
+
export interface SchemaValidationError {
|
|
11
|
+
readonly message: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Validates a scenario's arguments against the matching tool's inputSchema.
|
|
15
|
+
*
|
|
16
|
+
* Returns an empty array if the scenario is valid or if no matching
|
|
17
|
+
* annotation/schema is found (graceful skip).
|
|
18
|
+
*/
|
|
19
|
+
export declare function validateScenarioArgs(scenario: TestScenario, storedAnnotations: StoredToolAnnotation[]): SchemaValidationError[];
|
|
20
|
+
/**
|
|
21
|
+
* Filters scenarios with invalid arguments, returning valid and discarded sets.
|
|
22
|
+
*
|
|
23
|
+
* Scenarios without a matching annotation or inputSchema are kept (not discarded).
|
|
24
|
+
*/
|
|
25
|
+
export declare function filterInvalidSchemaScenarios(scenarios: TestScenario[], storedAnnotations: StoredToolAnnotation[]): {
|
|
26
|
+
valid: TestScenario[];
|
|
27
|
+
discarded: DiscardedScenario[];
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Builds a Map from toolName → Set of valid argument names, extracted from inputSchema.
|
|
31
|
+
* Used by Zod superRefine to reject scenarios with invalid argument names at parse time.
|
|
32
|
+
*/
|
|
33
|
+
export declare function buildToolArgNamesMap(annotations: StoredToolAnnotation[]): Map<string, Set<string>>;
|
|
34
|
+
/**
|
|
35
|
+
* Creates a Zod superRefine function that validates scenario arguments against
|
|
36
|
+
* tool input schemas. When integrated into the scenario Zod schema, validation
|
|
37
|
+
* errors are returned to the LLM via generateObjectWithRepair for automatic fix.
|
|
38
|
+
*
|
|
39
|
+
* This is the PRIMARY validation point — it replaces post-hoc filtering.
|
|
40
|
+
*/
|
|
41
|
+
export declare function buildScenarioArgsSuperRefine(toolArgNames: Map<string, Set<string>>): (scenario: {
|
|
42
|
+
request: {
|
|
43
|
+
toolName: string;
|
|
44
|
+
arguments: Record<string, unknown>;
|
|
45
|
+
};
|
|
46
|
+
}, ctx: z.RefinementCtx) => void;
|
|
47
|
+
/**
|
|
48
|
+
* Formats tool argument names for inclusion in LLM prompts, so the LLM knows
|
|
49
|
+
* which argument names are valid per tool.
|
|
50
|
+
*/
|
|
51
|
+
export declare function formatToolArgNames(annotations: StoredToolAnnotation[]): string;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates test scenario arguments against tool input schemas.
|
|
3
|
+
*
|
|
4
|
+
* Catches common LLM errors: wrong argument names, missing required fields,
|
|
5
|
+
* and invalid enum values. Uses the JSON Schema stored in tool annotations
|
|
6
|
+
* (captured from MCP servers at annotation time).
|
|
7
|
+
*/
|
|
8
|
+
function asJsonSchema(raw) {
|
|
9
|
+
return raw;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Validates a scenario's arguments against the matching tool's inputSchema.
|
|
13
|
+
*
|
|
14
|
+
* Returns an empty array if the scenario is valid or if no matching
|
|
15
|
+
* annotation/schema is found (graceful skip).
|
|
16
|
+
*/
|
|
17
|
+
export function validateScenarioArgs(scenario, storedAnnotations) {
|
|
18
|
+
const annotation = storedAnnotations.find((a) => a.serverName === scenario.request.serverName && a.toolName === scenario.request.toolName);
|
|
19
|
+
if (!annotation?.inputSchema)
|
|
20
|
+
return [];
|
|
21
|
+
const schema = asJsonSchema(annotation.inputSchema);
|
|
22
|
+
if (!schema.properties)
|
|
23
|
+
return [];
|
|
24
|
+
const errors = [];
|
|
25
|
+
const scenarioArgs = scenario.request.arguments;
|
|
26
|
+
const schemaPropertyNames = new Set(Object.keys(schema.properties));
|
|
27
|
+
// Check for unknown argument names
|
|
28
|
+
for (const argName of Object.keys(scenarioArgs)) {
|
|
29
|
+
if (!schemaPropertyNames.has(argName)) {
|
|
30
|
+
errors.push({ message: `unknown argument "${argName}" (valid: ${[...schemaPropertyNames].join(', ')})` });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Check for missing required fields
|
|
34
|
+
if (schema.required) {
|
|
35
|
+
for (const requiredArg of schema.required) {
|
|
36
|
+
if (!(requiredArg in scenarioArgs)) {
|
|
37
|
+
errors.push({ message: `missing required argument "${requiredArg}"` });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Check enum constraints
|
|
42
|
+
for (const [argName, propSchema] of Object.entries(schema.properties)) {
|
|
43
|
+
if (!propSchema.enum || !(argName in scenarioArgs))
|
|
44
|
+
continue;
|
|
45
|
+
const value = scenarioArgs[argName];
|
|
46
|
+
if (!propSchema.enum.includes(value)) {
|
|
47
|
+
errors.push({
|
|
48
|
+
message: `argument "${argName}" has invalid value ${JSON.stringify(value)} (valid: ${propSchema.enum.map((v) => JSON.stringify(v)).join(', ')})`,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return errors;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Filters scenarios with invalid arguments, returning valid and discarded sets.
|
|
56
|
+
*
|
|
57
|
+
* Scenarios without a matching annotation or inputSchema are kept (not discarded).
|
|
58
|
+
*/
|
|
59
|
+
export function filterInvalidSchemaScenarios(scenarios, storedAnnotations) {
|
|
60
|
+
const valid = [];
|
|
61
|
+
const discarded = [];
|
|
62
|
+
for (const scenario of scenarios) {
|
|
63
|
+
const errors = validateScenarioArgs(scenario, storedAnnotations);
|
|
64
|
+
if (errors.length > 0) {
|
|
65
|
+
discarded.push({
|
|
66
|
+
scenario,
|
|
67
|
+
actual: 'deny',
|
|
68
|
+
rule: `invalid-schema: ${errors.map((e) => e.message).join('; ')}`,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
valid.push(scenario);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return { valid, discarded };
|
|
76
|
+
}
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// Zod integration: validate scenario arguments at schema parse time
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
/**
|
|
81
|
+
* Builds a Map from toolName → Set of valid argument names, extracted from inputSchema.
|
|
82
|
+
* Used by Zod superRefine to reject scenarios with invalid argument names at parse time.
|
|
83
|
+
*/
|
|
84
|
+
export function buildToolArgNamesMap(annotations) {
|
|
85
|
+
const map = new Map();
|
|
86
|
+
for (const ann of annotations) {
|
|
87
|
+
const schema = asJsonSchema(ann.inputSchema);
|
|
88
|
+
if (schema.properties) {
|
|
89
|
+
map.set(ann.toolName, new Set(Object.keys(schema.properties)));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return map;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Creates a Zod superRefine function that validates scenario arguments against
|
|
96
|
+
* tool input schemas. When integrated into the scenario Zod schema, validation
|
|
97
|
+
* errors are returned to the LLM via generateObjectWithRepair for automatic fix.
|
|
98
|
+
*
|
|
99
|
+
* This is the PRIMARY validation point — it replaces post-hoc filtering.
|
|
100
|
+
*/
|
|
101
|
+
export function buildScenarioArgsSuperRefine(toolArgNames) {
|
|
102
|
+
return (scenario, ctx) => {
|
|
103
|
+
const validArgs = toolArgNames.get(scenario.request.toolName);
|
|
104
|
+
if (!validArgs)
|
|
105
|
+
return; // unknown tool — can't validate, Zod enum on toolName catches it
|
|
106
|
+
for (const argName of Object.keys(scenario.request.arguments)) {
|
|
107
|
+
if (!validArgs.has(argName)) {
|
|
108
|
+
ctx.addIssue({
|
|
109
|
+
code: 'custom',
|
|
110
|
+
path: ['request', 'arguments', argName],
|
|
111
|
+
message: `Unknown argument "${argName}" for tool "${scenario.request.toolName}". Valid arguments: ${[...validArgs].join(', ')}`,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Formats tool argument names for inclusion in LLM prompts, so the LLM knows
|
|
119
|
+
* which argument names are valid per tool.
|
|
120
|
+
*/
|
|
121
|
+
export function formatToolArgNames(annotations) {
|
|
122
|
+
const lines = [];
|
|
123
|
+
for (const ann of annotations) {
|
|
124
|
+
const schema = asJsonSchema(ann.inputSchema);
|
|
125
|
+
if (schema.properties) {
|
|
126
|
+
const args = Object.keys(schema.properties).join(', ');
|
|
127
|
+
lines.push(`- ${ann.toolName}: args=[${args}]`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return lines.join('\n');
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=scenario-schema-validator.js.map
|