@intutic/gate 2.4.0 → 2.5.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.
- package/README.md +42 -10
- package/dist/actions.d.ts +1 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +1 -0
- package/dist/actions.js.map +1 -1
- package/dist/chaosGate.d.ts +166 -0
- package/dist/chaosGate.d.ts.map +1 -0
- package/dist/chaosGate.js +372 -0
- package/dist/chaosGate.js.map +1 -0
- package/dist/client.d.ts +85 -17
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +129 -34
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +3 -0
- package/dist/errors.js.map +1 -1
- package/dist/eve.d.ts +1 -1
- package/dist/eve.d.ts.map +1 -1
- package/dist/eve.js +1 -1
- package/dist/eve.js.map +1 -1
- package/dist/gate.d.ts +81 -2
- package/dist/gate.d.ts.map +1 -1
- package/dist/gate.js +167 -17
- package/dist/gate.js.map +1 -1
- package/dist/harness.d.ts +0 -38
- package/dist/harness.d.ts.map +1 -1
- package/dist/harness.js +17 -6
- package/dist/harness.js.map +1 -1
- package/dist/hold.d.ts.map +1 -1
- package/dist/hold.js +5 -2
- package/dist/hold.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/opaqueRefusal.d.ts +40 -0
- package/dist/opaqueRefusal.d.ts.map +1 -0
- package/dist/opaqueRefusal.js +47 -0
- package/dist/opaqueRefusal.js.map +1 -0
- package/dist/registry.d.ts +2 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +2 -0
- package/dist/registry.js.map +1 -1
- package/dist/snapshot.d.ts +15 -0
- package/dist/snapshot.d.ts.map +1 -1
- package/dist/snapshot.js +20 -0
- package/dist/snapshot.js.map +1 -1
- package/dist/soprules.d.ts +24 -2
- package/dist/soprules.d.ts.map +1 -1
- package/dist/soprules.js +35 -6
- package/dist/soprules.js.map +1 -1
- package/dist/vercel.d.ts +19 -5
- package/dist/vercel.d.ts.map +1 -1
- package/dist/vercel.js +27 -6
- package/dist/vercel.js.map +1 -1
- package/dist/wrapTools.d.ts +1 -0
- package/dist/wrapTools.d.ts.map +1 -1
- package/dist/wrapTools.js.map +1 -1
- package/package.json +11 -10
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fault injection in the gates: the part of a chaos run (`chaos.ts`) a hook
|
|
3
|
+
* gate or an in-process gate SDK carries.
|
|
4
|
+
*
|
|
5
|
+
* A hook gate is a new process per tool call, and parallel gates share one
|
|
6
|
+
* run, so nothing here keeps state in memory: the plan comes from {@link CHAOS_PLAN_ENV}-style text each call,
|
|
7
|
+
* and every count lives in files beside the events file.
|
|
8
|
+
*
|
|
9
|
+
* - `<events>.gate-plan-loaded` — created once, with an exclusive create; the
|
|
10
|
+
* gate that creates it reports `plan_loaded`, so separate gate processes do
|
|
11
|
+
* not repeat it.
|
|
12
|
+
* - `<events>.gate-counters/<fault index>.seen.<n>` and `.hit.<n>` — one
|
|
13
|
+
* empty file per matching call a fault saw and per call it hit. A call
|
|
14
|
+
* claims the next number with an exclusive create (`open` with `wx`,
|
|
15
|
+
* `O_CREAT | O_EXCL`), which the file system grants to exactly one process,
|
|
16
|
+
* so two gates deciding at once can never both take the same number; the
|
|
17
|
+
* claimed number is the call's 1-based index. A count is the number of
|
|
18
|
+
* files, read once to start the search, so a claim costs one directory
|
|
19
|
+
* read and usually one create. That is `FaultCounter`'s `after_calls` and
|
|
20
|
+
* `times` across processes.
|
|
21
|
+
* - `<events>.gate-counters/<fault index>.held.<key>.<g>` — a call a
|
|
22
|
+
* `permission.deny_or_delay` fault in `delay` or `hold_then_deny` mode
|
|
23
|
+
* held; its modification time is when. `<key>` is the call's digest (the
|
|
24
|
+
* tool and its input, as the gate keys an approval hold), `<g>` counts the
|
|
25
|
+
* holds of that call, claimed like a call number. `.released.<key>.<g>`,
|
|
26
|
+
* created with the same exclusive create, ends that hold: one retry, and
|
|
27
|
+
* only one, is the one the hold lets through or denies.
|
|
28
|
+
*
|
|
29
|
+
* A permission fault never makes a gate wait. It holds the call the way an
|
|
30
|
+
* approval hold does: the call is refused as held, and the identical retry
|
|
31
|
+
* (same tool, same input) is refused as held again until `delay_s` seconds
|
|
32
|
+
* have passed since the first refusal. After that the retry is allowed
|
|
33
|
+
* (`delay`) or denied (`hold_then_deny`), and the hold is over: the next
|
|
34
|
+
* identical call is a new matching call. A held call's retries are the same
|
|
35
|
+
* call, so no fault counts them as matching calls. A test moves the clock by
|
|
36
|
+
* moving the `held` files' modification times back.
|
|
37
|
+
*
|
|
38
|
+
* Only a call governance allowed counts: a fault never applies to a call the
|
|
39
|
+
* gate refused, held or asked about, so such a call is not one of the
|
|
40
|
+
* fault's matching calls either. Every fault whose `match` covers an allowed
|
|
41
|
+
* call counts it as seen; the first one in plan order that is due injects,
|
|
42
|
+
* and the faults after it do not spend a hit on the same call. A `tool.error`
|
|
43
|
+
* on an `mcp__<server>__<tool>` call is the MCP proxy's to inject, so a gate
|
|
44
|
+
* leaves it alone; `permission.deny_or_delay` is the gate's on any tool.
|
|
45
|
+
*
|
|
46
|
+
* The functions are self-contained on purpose — no imports, no module state —
|
|
47
|
+
* because the JavaScript hook gates emit them as source
|
|
48
|
+
* ({@link CHAOS_GATE_JS_SOURCE}) and run them outside this package, and
|
|
49
|
+
* `@intutic/gate` carries a byte-identical copy of this file
|
|
50
|
+
* (`packages/gate-js/src/chaosGate.ts`). The bash gates and
|
|
51
|
+
* `intutic_clawde.gate` run the Python transliteration,
|
|
52
|
+
* `intutic_clawde/gate/chaos.py`, which the sync daemon emits verbatim. All
|
|
53
|
+
* of them run `fixtures/chaos-gate-vectors.json`. File access goes through
|
|
54
|
+
* the `fs` argument (Node's `fs` in a gate).
|
|
55
|
+
*
|
|
56
|
+
* @module
|
|
57
|
+
*/
|
|
58
|
+
/**
|
|
59
|
+
* Decodes and checks a plan: base64url JSON, version 1, with valid values in
|
|
60
|
+
* every field a gate reads. Returns the plan, or why it is not one.
|
|
61
|
+
*/
|
|
62
|
+
export function chaosGateDecode(raw) {
|
|
63
|
+
const ids = [
|
|
64
|
+
'llm.rate_limit', 'llm.upstream_5xx', 'llm.context_trim', 'llm.model_degrade', 'tool.error', 'tool.stale_data',
|
|
65
|
+
'mcp.toolset_change', 'permission.deny_or_delay', 'inject.tool_result', 'egress.dns_fail', 'egress.credential_401',
|
|
66
|
+
];
|
|
67
|
+
const invalid = (why) => ({ plan: null, error: 'INTUTIC_CHAOS_PLAN is not a valid plan: ' + why });
|
|
68
|
+
const isObject = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
69
|
+
const isInt = (v, min, max) => typeof v === 'number' && Number.isInteger(v) && v >= min && v <= max;
|
|
70
|
+
let json;
|
|
71
|
+
try {
|
|
72
|
+
if (typeof raw !== 'string' || !/^[A-Za-z0-9_-]+={0,2}$/.test(raw))
|
|
73
|
+
throw new Error('alphabet');
|
|
74
|
+
const b64 = raw.replace(/=+$/, '').replace(/-/g, '+').replace(/_/g, '/');
|
|
75
|
+
const binary = atob(b64 + '='.repeat((4 - (b64.length % 4)) % 4));
|
|
76
|
+
const bytes = new Uint8Array(binary.length);
|
|
77
|
+
for (let i = 0; i < binary.length; i++)
|
|
78
|
+
bytes[i] = binary.charCodeAt(i);
|
|
79
|
+
json = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return { plan: null, error: 'INTUTIC_CHAOS_PLAN is not base64url-encoded JSON' };
|
|
83
|
+
}
|
|
84
|
+
if (!isObject(json))
|
|
85
|
+
return invalid('not a JSON object');
|
|
86
|
+
if (json.v !== 1)
|
|
87
|
+
return invalid('version ' + JSON.stringify(json.v) + ', and a gate reads only version 1');
|
|
88
|
+
if (typeof json.runId !== 'string' || !/^[A-Za-z0-9_-]{6,64}$/.test(json.runId))
|
|
89
|
+
return invalid('runId');
|
|
90
|
+
if (typeof json.canary !== 'string' || !/^E-INTUTIC-[0-9A-F]{4,16}$/.test(json.canary))
|
|
91
|
+
return invalid('canary');
|
|
92
|
+
if (!Array.isArray(json.faults) || json.faults.length > 50)
|
|
93
|
+
return invalid('faults');
|
|
94
|
+
const faults = [];
|
|
95
|
+
for (let i = 0; i < json.faults.length; i++) {
|
|
96
|
+
const f = json.faults[i];
|
|
97
|
+
const at = 'faults[' + i + ']';
|
|
98
|
+
if (!isObject(f) || typeof f.id !== 'string' || ids.indexOf(f.id) === -1)
|
|
99
|
+
return invalid(at + '.id');
|
|
100
|
+
if (f.id !== 'tool.error' && f.id !== 'permission.deny_or_delay')
|
|
101
|
+
continue;
|
|
102
|
+
let match = null;
|
|
103
|
+
if (f.match !== undefined) {
|
|
104
|
+
if (!isObject(f.match))
|
|
105
|
+
return invalid(at + '.match');
|
|
106
|
+
for (const k of Object.keys(f.match)) {
|
|
107
|
+
const v = f.match[k];
|
|
108
|
+
if ((k !== 'tool' && k !== 'host' && k !== 'model') || typeof v !== 'string' || v.length === 0) {
|
|
109
|
+
return invalid(at + '.match.' + k);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
match = f.match;
|
|
113
|
+
}
|
|
114
|
+
if (f.after_calls !== undefined && !isInt(f.after_calls, 0, 10000))
|
|
115
|
+
return invalid(at + '.after_calls');
|
|
116
|
+
if (f.times !== undefined && !isInt(f.times, 1, 10000))
|
|
117
|
+
return invalid(at + '.times');
|
|
118
|
+
const fault = {
|
|
119
|
+
index: i,
|
|
120
|
+
id: f.id,
|
|
121
|
+
match: match,
|
|
122
|
+
afterCalls: f.after_calls === undefined ? 0 : f.after_calls,
|
|
123
|
+
times: f.times === undefined ? null : f.times,
|
|
124
|
+
message: 'tool failed',
|
|
125
|
+
mode: 'deny',
|
|
126
|
+
delayS: 0,
|
|
127
|
+
};
|
|
128
|
+
if (f.id === 'tool.error' && f.message !== undefined) {
|
|
129
|
+
if (typeof f.message !== 'string' || f.message.length < 1 || f.message.length > 2000)
|
|
130
|
+
return invalid(at + '.message');
|
|
131
|
+
fault.message = f.message;
|
|
132
|
+
}
|
|
133
|
+
if (f.id === 'permission.deny_or_delay') {
|
|
134
|
+
if (f.mode !== undefined) {
|
|
135
|
+
if (f.mode !== 'deny' && f.mode !== 'delay' && f.mode !== 'hold_then_deny')
|
|
136
|
+
return invalid(at + '.mode');
|
|
137
|
+
fault.mode = f.mode;
|
|
138
|
+
}
|
|
139
|
+
if (f.delay_s !== undefined) {
|
|
140
|
+
if (!isInt(f.delay_s, 0, 600))
|
|
141
|
+
return invalid(at + '.delay_s');
|
|
142
|
+
fault.delayS = f.delay_s;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
faults.push(fault);
|
|
146
|
+
}
|
|
147
|
+
return { plan: { runId: json.runId, canary: json.canary, faults: faults }, error: null };
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Whether a fault's `match` covers a gate call. A gate call has a tool and
|
|
151
|
+
* nothing else, so a fault that names a host or a model never matches one.
|
|
152
|
+
* `tool` matches exactly, or by suffix when it starts with `*`.
|
|
153
|
+
*/
|
|
154
|
+
export function chaosGateMatches(match, tool) {
|
|
155
|
+
if (!match)
|
|
156
|
+
return true;
|
|
157
|
+
if (match.host !== undefined || match.model !== undefined)
|
|
158
|
+
return false;
|
|
159
|
+
const p = match.tool;
|
|
160
|
+
if (p === undefined)
|
|
161
|
+
return true;
|
|
162
|
+
return p.charAt(0) === '*' ? tool.endsWith(p.slice(1)) : tool === p;
|
|
163
|
+
}
|
|
164
|
+
/** One events-file line: `ts`, `runId`, `component`, `type`, then the fields that are set. */
|
|
165
|
+
export function chaosGateEvent(plan, type, fields, now) {
|
|
166
|
+
const event = { ts: now, runId: plan.runId, component: 'gate', type: type };
|
|
167
|
+
for (const k of Object.keys(fields))
|
|
168
|
+
if (fields[k] !== undefined && fields[k] !== null)
|
|
169
|
+
event[k] = fields[k];
|
|
170
|
+
return JSON.stringify(event) + '\n';
|
|
171
|
+
}
|
|
172
|
+
/** Appends whole lines in one write. An I/O error is reported, never thrown: it must not decide the call. */
|
|
173
|
+
export function chaosGateAppend(fs, eventsPath, lines) {
|
|
174
|
+
try {
|
|
175
|
+
fs.appendFileSync(eventsPath, lines);
|
|
176
|
+
}
|
|
177
|
+
catch (e) {
|
|
178
|
+
try {
|
|
179
|
+
console.error('[Intutic Governance] chaos: could not append to ' + eventsPath + ': ' + String((e && e.message) || e));
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
/* stderr gone too */
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Claims the next numbered file `<dir>/<prefix><n>` with an exclusive create
|
|
188
|
+
* and returns n, or 0 when every number up to `limit` is taken. -1 on an I/O
|
|
189
|
+
* error, after saying so.
|
|
190
|
+
*/
|
|
191
|
+
export function chaosGateClaim(fs, dir, prefix, limit) {
|
|
192
|
+
try {
|
|
193
|
+
// Beside the events file, never creating its directory: the runner did.
|
|
194
|
+
try {
|
|
195
|
+
fs.mkdirSync(dir);
|
|
196
|
+
}
|
|
197
|
+
catch (e) {
|
|
198
|
+
if (!e || e.code !== 'EEXIST')
|
|
199
|
+
throw e;
|
|
200
|
+
}
|
|
201
|
+
const names = fs.readdirSync(dir);
|
|
202
|
+
let n = 0;
|
|
203
|
+
for (let i = 0; i < names.length; i++)
|
|
204
|
+
if (names[i].indexOf(prefix) === 0)
|
|
205
|
+
n++;
|
|
206
|
+
for (n = n + 1; limit === null || n <= limit; n++) {
|
|
207
|
+
try {
|
|
208
|
+
fs.closeSync(fs.openSync(dir + '/' + prefix + n, 'wx'));
|
|
209
|
+
return n;
|
|
210
|
+
}
|
|
211
|
+
catch (e) {
|
|
212
|
+
if (!e || e.code !== 'EEXIST')
|
|
213
|
+
throw e;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return 0;
|
|
217
|
+
}
|
|
218
|
+
catch (e) {
|
|
219
|
+
try {
|
|
220
|
+
console.error('[Intutic Governance] chaos: could not count calls in ' + dir + ': ' + String((e && e.message) || e));
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
/* stderr gone too */
|
|
224
|
+
}
|
|
225
|
+
return -1;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* The open hold of one fault on one call: the number of the newest
|
|
230
|
+
* `<fault>.held.<key>.<g>` file that has no `.released.` twin, and when it
|
|
231
|
+
* was created. Null when there is none or it cannot be read.
|
|
232
|
+
*/
|
|
233
|
+
export function chaosGateOpenHold(fs, dir, fault, key) {
|
|
234
|
+
try {
|
|
235
|
+
const held = fault + '.held.' + key + '.';
|
|
236
|
+
const names = fs.readdirSync(dir);
|
|
237
|
+
let g = 0;
|
|
238
|
+
for (let i = 0; i < names.length; i++) {
|
|
239
|
+
if (names[i].indexOf(held) !== 0)
|
|
240
|
+
continue;
|
|
241
|
+
const n = Number(names[i].slice(held.length));
|
|
242
|
+
if (n > g)
|
|
243
|
+
g = n;
|
|
244
|
+
}
|
|
245
|
+
if (g === 0 || names.indexOf(fault + '.released.' + key + '.' + g) !== -1)
|
|
246
|
+
return null;
|
|
247
|
+
// In whole milliseconds, as now is: a hold is never younger than the call that reads it.
|
|
248
|
+
return { g: g, sinceMs: Math.floor(fs.statSync(dir + '/' + held + g).mtimeMs) };
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
// No counters yet, or none readable: no hold to continue.
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/** Ends hold g with an exclusive create: true for the one caller that does. */
|
|
256
|
+
export function chaosGateRelease(fs, dir, fault, key, g) {
|
|
257
|
+
try {
|
|
258
|
+
fs.closeSync(fs.openSync(dir + '/' + fault + '.released.' + key + '.' + g, 'wx'));
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
// Another gate released it first, or it cannot be written.
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Reports one call: `plan_loaded` the first time any gate reports to this
|
|
268
|
+
* events file, then `gate_decision`; for an allowed call, the fault that is
|
|
269
|
+
* due (`fault_injected`, once per held call however often it is retried),
|
|
270
|
+
* and `tool_call`: ok when the call goes ahead, not ok when it is held.
|
|
271
|
+
* Returns the fault the gate must apply, or null. `now` is the ISO-8601 time,
|
|
272
|
+
* and the clock a hold is timed by.
|
|
273
|
+
*/
|
|
274
|
+
export function chaosGateCall(fs, plan, eventsPath, call, now) {
|
|
275
|
+
let lines = '';
|
|
276
|
+
let first = true;
|
|
277
|
+
try {
|
|
278
|
+
fs.closeSync(fs.openSync(eventsPath + '.gate-plan-loaded', 'wx'));
|
|
279
|
+
}
|
|
280
|
+
catch (e) {
|
|
281
|
+
// Already reported. Any other error: report again rather than not at all.
|
|
282
|
+
if (e && e.code === 'EEXIST')
|
|
283
|
+
first = false;
|
|
284
|
+
}
|
|
285
|
+
if (first)
|
|
286
|
+
lines += chaosGateEvent(plan, 'plan_loaded', { faults: plan.faults.length }, now);
|
|
287
|
+
lines += chaosGateEvent(plan, 'gate_decision', {
|
|
288
|
+
tool: call.tool,
|
|
289
|
+
decision: call.decision,
|
|
290
|
+
ruleId: call.ruleId || null,
|
|
291
|
+
destructive: !!call.destructive,
|
|
292
|
+
policyHit: !!call.policyHit,
|
|
293
|
+
}, now);
|
|
294
|
+
let fault = null;
|
|
295
|
+
if (call.decision === 'allow') {
|
|
296
|
+
const dir = eventsPath + '.gate-counters';
|
|
297
|
+
const key = String(call.key || '').replace(/[^0-9A-Za-z]/g, '').slice(0, 128) || 'call';
|
|
298
|
+
let nowMs = Date.parse(now);
|
|
299
|
+
if (isNaN(nowMs))
|
|
300
|
+
nowMs = Date.now();
|
|
301
|
+
const tag = ' [' + plan.canary + ']';
|
|
302
|
+
const held = 'Permission held for approval; retrying this exact call passes once it is approved' + tag;
|
|
303
|
+
// The retry of a held call: still held until the delay has passed, then
|
|
304
|
+
// let through or denied by the one retry that releases it.
|
|
305
|
+
let retry = false;
|
|
306
|
+
for (let i = 0; i < plan.faults.length && !retry; i++) {
|
|
307
|
+
const f = plan.faults[i];
|
|
308
|
+
if (f.id !== 'permission.deny_or_delay' || f.mode === 'deny' || !chaosGateMatches(f.match, call.tool))
|
|
309
|
+
continue;
|
|
310
|
+
const open = chaosGateOpenHold(fs, dir, f.index, key);
|
|
311
|
+
if (!open)
|
|
312
|
+
continue;
|
|
313
|
+
retry = true;
|
|
314
|
+
if (f.delayS > 0 && nowMs - open.sinceMs < f.delayS * 1000) {
|
|
315
|
+
fault = { faultId: f.id, kind: 'hold', reason: held };
|
|
316
|
+
}
|
|
317
|
+
else if (chaosGateRelease(fs, dir, f.index, key, open.g)) {
|
|
318
|
+
if (f.mode === 'hold_then_deny') {
|
|
319
|
+
fault = { faultId: f.id, kind: 'deny', reason: 'Permission held for approval, then denied by approver' + tag };
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
// Another gate released it first: this call is a new one.
|
|
324
|
+
retry = false;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
for (let i = 0; i < plan.faults.length && !retry; i++) {
|
|
328
|
+
const f = plan.faults[i];
|
|
329
|
+
if (!chaosGateMatches(f.match, call.tool))
|
|
330
|
+
continue;
|
|
331
|
+
// An MCP tool's error is the MCP proxy's to inject; a gate injects the built-in side.
|
|
332
|
+
if (f.id === 'tool.error' && call.tool.indexOf('mcp__') === 0)
|
|
333
|
+
continue;
|
|
334
|
+
const seen = chaosGateClaim(fs, dir, f.index + '.seen.', null);
|
|
335
|
+
if (fault || seen < 1 || seen <= f.afterCalls)
|
|
336
|
+
continue;
|
|
337
|
+
if (f.times !== null && chaosGateClaim(fs, dir, f.index + '.hit.', f.times) < 1)
|
|
338
|
+
continue;
|
|
339
|
+
if (f.id === 'tool.error') {
|
|
340
|
+
fault = { faultId: f.id, kind: 'deny', reason: f.message + tag };
|
|
341
|
+
}
|
|
342
|
+
else if (f.mode === 'deny') {
|
|
343
|
+
fault = { faultId: f.id, kind: 'deny', reason: 'Permission denied by approver' + tag };
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
// Held from now: the hold's file is what its retries find.
|
|
347
|
+
if (chaosGateClaim(fs, dir, f.index + '.held.' + key + '.', null) < 1)
|
|
348
|
+
continue;
|
|
349
|
+
fault = { faultId: f.id, kind: 'hold', reason: held };
|
|
350
|
+
}
|
|
351
|
+
lines += chaosGateEvent(plan, 'fault_injected', { faultId: f.id, call: seen, target: call.tool }, now);
|
|
352
|
+
}
|
|
353
|
+
if (!fault || fault.kind === 'hold')
|
|
354
|
+
lines += chaosGateEvent(plan, 'tool_call', { tool: call.tool, ok: !fault }, now);
|
|
355
|
+
}
|
|
356
|
+
chaosGateAppend(fs, eventsPath, lines);
|
|
357
|
+
return fault;
|
|
358
|
+
}
|
|
359
|
+
/** Every function above as JavaScript source, for a hook gate to emit. */
|
|
360
|
+
export const CHAOS_GATE_JS_SOURCE = [
|
|
361
|
+
chaosGateDecode,
|
|
362
|
+
chaosGateMatches,
|
|
363
|
+
chaosGateEvent,
|
|
364
|
+
chaosGateAppend,
|
|
365
|
+
chaosGateClaim,
|
|
366
|
+
chaosGateOpenHold,
|
|
367
|
+
chaosGateRelease,
|
|
368
|
+
chaosGateCall,
|
|
369
|
+
]
|
|
370
|
+
.map((f) => f.toString())
|
|
371
|
+
.join('\n');
|
|
372
|
+
//# sourceMappingURL=chaosGate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chaosGate.js","sourceRoot":"","sources":["../src/chaosGate.ts"],"names":[],"mappings":"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"}
|
package/dist/client.d.ts
CHANGED
|
@@ -5,44 +5,88 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Endpoints used:
|
|
7
7
|
*
|
|
8
|
-
* POST /api/v1/hook-gate synchronous allow/deny -> {allowed, reason, incidentId?, code?}
|
|
8
|
+
* POST /api/v1/hook-gate synchronous allow/deny -> {allowed, reason, incidentId?, code?, degraded?, decisionId?}
|
|
9
9
|
* POST /api/v1/hook-events batched telemetry -> creates governance_incidents rows
|
|
10
10
|
* POST /api/v1/decisions record a hold -> joins the review queue (hold.ts)
|
|
11
11
|
* GET /api/v1/decisions/approved-bypasses -> approvals that let a held call through
|
|
12
12
|
*
|
|
13
13
|
* Two behaviours that must not be "improved":
|
|
14
14
|
*
|
|
15
|
-
* * The hook
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* `
|
|
22
|
-
* tier as advisory
|
|
15
|
+
* * The hook gate fails CLOSED, on both sides. The server refuses a call
|
|
16
|
+
* whose checks could not run (403 `GOVERNANCE_UNAVAILABLE`; a shadow-mode
|
|
17
|
+
* workspace gets a degraded allow instead) and a body it cannot read
|
|
18
|
+
* (400 `INVALID_REQUEST`). This client treats `GOVERNANCE_UNAVAILABLE`
|
|
19
|
+
* exactly as it treats a control plane it cannot reach, a non-2xx answer,
|
|
20
|
+
* or an answer with no boolean `allowed`: there is no verdict to defer to,
|
|
21
|
+
* and `failClosed` (default true) decides, reporting `allowed=false`. Set
|
|
22
|
+
* `failClosed=false` to treat the tier as advisory during an outage. A 400
|
|
23
|
+
* is refused whatever `failClosed` says: it is this client's request that
|
|
24
|
+
* is wrong, not the control plane that is down, and allowing would hide it.
|
|
23
25
|
* * `emit` NEVER throws. Telemetry that can break a run is worse than no
|
|
24
26
|
* telemetry.
|
|
27
|
+
*
|
|
28
|
+
* A client with no API key is LOCAL-ONLY ({@link GateClient.localOnly}): it
|
|
29
|
+
* cannot authenticate, so it makes no request at all. Its hook gate answers
|
|
30
|
+
* allow with code `LOCAL_ONLY` and says why, its events and holds are not
|
|
31
|
+
* sent, and the `Gate` skips the SOP register. The local tiers (policy
|
|
32
|
+
* snapshot, SSO groups from the snapshot, MCP registry, image integrity) are
|
|
33
|
+
* unaffected. A client WITH a key that the control plane refuses (401) is not
|
|
34
|
+
* local-only: that is an authentication fault, and fails closed as above.
|
|
25
35
|
*/
|
|
26
36
|
export interface GateResponse {
|
|
27
37
|
allowed: boolean;
|
|
28
38
|
reason: string;
|
|
29
39
|
incidentId?: string | undefined;
|
|
30
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* The code the hook gate names, when it names one: the MCP registry's on its
|
|
42
|
+
* refusals, `GOVERNANCE_UNAVAILABLE` when its checks could not run, and
|
|
43
|
+
* `INVALID_REQUEST` when it could not read the request.
|
|
44
|
+
*/
|
|
31
45
|
code?: string | undefined;
|
|
32
|
-
/** False when the
|
|
46
|
+
/** False when the hook gate gave no verdict and `failClosed` decided it. */
|
|
33
47
|
reached: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* The hook gate's id for this decision, when this answer is its own verdict:
|
|
50
|
+
* every verdict it gives names one, and a `GOVERNANCE_UNAVAILABLE` refusal
|
|
51
|
+
* keeps it only while `failClosed` refuses too. The gate reports the call
|
|
52
|
+
* with it (`gateDecisionId`), so the control plane exports the decision to
|
|
53
|
+
* SIEM once and files its incident once.
|
|
54
|
+
*/
|
|
55
|
+
decisionId?: string | undefined;
|
|
34
56
|
}
|
|
57
|
+
/** Options for a {@link GateClient}. Every field is optional. */
|
|
35
58
|
export interface GateClientOptions {
|
|
59
|
+
/**
|
|
60
|
+
* The control plane's address.
|
|
61
|
+
* @defaultValue `INTUTIC_CONTROL_PLANE_URL`, then Intutic's hosted control plane
|
|
62
|
+
*/
|
|
36
63
|
baseUrl?: string;
|
|
64
|
+
/**
|
|
65
|
+
* The API key. Without one the client is {@link GateClient.localOnly | local-only}.
|
|
66
|
+
* @defaultValue `''`
|
|
67
|
+
*/
|
|
37
68
|
apiKey?: string;
|
|
69
|
+
/** @defaultValue `''` */
|
|
38
70
|
workspaceId?: string;
|
|
71
|
+
/** @defaultValue `''` */
|
|
39
72
|
sessionId?: string;
|
|
40
73
|
/**
|
|
41
74
|
* Attributes incidents to the right adapter. Sent as `harnessType` on every
|
|
42
75
|
* gate call and event.
|
|
76
|
+
* @defaultValue `'generic'`
|
|
43
77
|
*/
|
|
44
78
|
harness?: string;
|
|
79
|
+
/**
|
|
80
|
+
* Block a call when the hook gate gives no verdict: the control plane
|
|
81
|
+
* cannot be reached, answers without one, or answers
|
|
82
|
+
* `GOVERNANCE_UNAVAILABLE`. `false` lets such a call go ahead.
|
|
83
|
+
* @defaultValue `true`
|
|
84
|
+
*/
|
|
45
85
|
failClosed?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* How long one control-plane request may take.
|
|
88
|
+
* @defaultValue `2000`
|
|
89
|
+
*/
|
|
46
90
|
timeoutMs?: number;
|
|
47
91
|
}
|
|
48
92
|
/**
|
|
@@ -62,6 +106,12 @@ export declare class GateClient {
|
|
|
62
106
|
readonly failClosed: boolean;
|
|
63
107
|
readonly timeoutMs: number;
|
|
64
108
|
constructor(opts?: GateClientOptions);
|
|
109
|
+
/**
|
|
110
|
+
* True when there is no API key. Such a client cannot authenticate, so it
|
|
111
|
+
* sends nothing: the control-plane tiers (SOP register, hook gate) are
|
|
112
|
+
* skipped, events and holds are not reported, and the local tiers decide.
|
|
113
|
+
*/
|
|
114
|
+
get localOnly(): boolean;
|
|
65
115
|
private post;
|
|
66
116
|
private request;
|
|
67
117
|
/**
|
|
@@ -71,19 +121,36 @@ export declare class GateClient {
|
|
|
71
121
|
* plus SOP rules. It is defence in depth on top of the local tiers, not a
|
|
72
122
|
* replacement for them.
|
|
73
123
|
*
|
|
74
|
-
* When
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
124
|
+
* When there is no verdict (the control plane cannot be reached, answers
|
|
125
|
+
* non-2xx or without a boolean `allowed`, or answers
|
|
126
|
+
* `GOVERNANCE_UNAVAILABLE` because its own checks could not run), the
|
|
127
|
+
* verdict is decided by `failClosed`: true (the default) reports BLOCK,
|
|
128
|
+
* because a client with no verdict has nothing to defer to; false reports
|
|
129
|
+
* allow. A 400 `INVALID_REQUEST` is a BLOCK whatever `failClosed` says. A
|
|
130
|
+
* shadow-mode workspace's `{allowed: true, degraded: true}` is an allow.
|
|
131
|
+
*
|
|
132
|
+
* A {@link localOnly} client makes no call and answers allow with code
|
|
133
|
+
* `LOCAL_ONLY`, whatever `failClosed` says: with no key there is no
|
|
134
|
+
* control-plane tier to fail, only local ones, which have already run.
|
|
78
135
|
*/
|
|
79
136
|
hookGate(toolName: string, toolInput: unknown): Promise<GateResponse>;
|
|
137
|
+
/**
|
|
138
|
+
* The answer when the hook gate gave no verdict: `failClosed` decides. A
|
|
139
|
+
* `GOVERNANCE_UNAVAILABLE` refusal's `decisionId` is kept when failing
|
|
140
|
+
* closed, which refuses as the hook gate did, and dropped when failing open:
|
|
141
|
+
* the call then runs, and its report must not stand for the refusal.
|
|
142
|
+
*/
|
|
143
|
+
private noVerdict;
|
|
80
144
|
/**
|
|
81
145
|
* Fire-and-forget telemetry. Never throws.
|
|
82
146
|
*
|
|
83
147
|
* A `tool_blocked` event creates a `governance_incidents` row whose
|
|
84
|
-
* description embeds the reason verbatim.
|
|
148
|
+
* description embeds the reason verbatim. `gateDecisionId` is the hook
|
|
149
|
+
* gate's `decisionId` for the call this event reports: the control plane
|
|
150
|
+
* then files the decision's incident once and does not export it to SIEM a
|
|
151
|
+
* second time.
|
|
85
152
|
*/
|
|
86
|
-
emit(event: string, toolName: string, reason?: string, toolInput?: unknown, incidentId?: string, filePath?: string): Promise<boolean>;
|
|
153
|
+
emit(event: string, toolName: string, reason?: string, toolInput?: unknown, incidentId?: string, filePath?: string, gateDecisionId?: string): Promise<boolean>;
|
|
87
154
|
/**
|
|
88
155
|
* Records one hold in the review queue (`POST /api/v1/decisions`), the way
|
|
89
156
|
* the hook gates' records reach it. Never throws; `false` when the control
|
|
@@ -102,6 +169,7 @@ export declare class GateClient {
|
|
|
102
169
|
*
|
|
103
170
|
* Reading `~/.intutic/credentials.json` means the gate works with whatever
|
|
104
171
|
* workspace `intutic login` already bound, instead of duplicating the key.
|
|
172
|
+
* With no key in either place the client is {@link localOnly}.
|
|
105
173
|
*/
|
|
106
174
|
static fromEnv(opts?: {
|
|
107
175
|
sessionId?: string;
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AA4BH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACzB,4EAA4E;IAC5E,OAAO,EAAE,OAAO,CAAA;IAChB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAChC;AA4BD,iEAAiE;AACjE,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,yBAAyB;IACzB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,yBAAyB;IACzB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;GAOG;AACH,qBAAa,UAAU;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAA;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;gBAEd,IAAI,GAAE,iBAAsB;IAaxC;;;;OAIG;IACH,IAAI,SAAS,IAAI,OAAO,CAEvB;YAEa,IAAI;YAIJ,OAAO;IAoCrB;;;;;;;;;;;;;;;;;;OAkBG;IACG,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC;IAuD3E;;;;;OAKG;IACH,OAAO,CAAC,SAAS;IASjB;;;;;;;;OAQG;IACG,IAAI,CACR,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,MAAM,SAAK,EACX,SAAS,CAAC,EAAE,OAAO,EACnB,UAAU,CAAC,EAAE,MAAM,EACnB,QAAQ,CAAC,EAAE,MAAM,EACjB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,OAAO,CAAC;IA6BnB;;;;;OAKG;IACG,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IASjE;;;;OAIG;IACG,gBAAgB,IAAI,OAAO,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC;IASnD;;;;;;OAMG;IACH,MAAM,CAAC,OAAO,CAAC,IAAI,GAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,UAAU;CAoCtG"}
|