@zanii/blackbox 0.22.0 → 0.23.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/README.md +1 -0
- package/dist/analysis/accepted.d.ts +26 -0
- package/dist/analysis/accepted.js +61 -0
- package/dist/analysis/detectors.js +12 -9
- package/dist/analysis/faults.js +71 -0
- package/dist/analysis/grounding.js +5 -9
- package/dist/analysis/guard.d.ts +72 -0
- package/dist/analysis/guard.js +240 -0
- package/dist/analysis/hallucination.js +2 -2
- package/dist/analysis/index.d.ts +21 -1
- package/dist/analysis/index.js +101 -19
- package/dist/analysis/injection.d.ts +2 -1
- package/dist/analysis/injection.js +16 -16
- package/dist/analysis/landing.js +60 -14
- package/dist/analysis/plan-checks.d.ts +55 -0
- package/dist/analysis/plan-checks.js +748 -0
- package/dist/analysis/taxonomy.js +1 -0
- package/dist/analysis/waste.js +2 -3
- package/dist/approvals/index.d.ts +12 -3
- package/dist/approvals/index.js +36 -10
- package/dist/authority/index.js +3 -3
- package/dist/aviation/index.d.ts +422 -0
- package/dist/aviation/index.js +1014 -0
- package/dist/aviation/qrh.d.ts +4 -0
- package/dist/aviation/qrh.js +171 -0
- package/dist/budgets/index.d.ts +63 -0
- package/dist/budgets/index.js +161 -0
- package/dist/cli.js +172 -24
- package/dist/compliance/index.d.ts +2 -0
- package/dist/compliance/index.js +26 -2
- package/dist/cost/batch.d.ts +26 -0
- package/dist/cost/batch.js +173 -0
- package/dist/cost/index.d.ts +21 -0
- package/dist/cost/index.js +115 -30
- package/dist/cv/index.js +1 -1
- package/dist/duty/index.js +2 -1
- package/dist/evals/index.d.ts +233 -0
- package/dist/evals/index.js +488 -0
- package/dist/federation/index.d.ts +9 -0
- package/dist/federation/index.js +8 -1
- package/dist/finance/index.js +3 -4
- package/dist/fleet/index.d.ts +25 -0
- package/dist/fleet/index.js +57 -17
- package/dist/fleet/insights.js +4 -1
- package/dist/health/index.js +2 -2
- package/dist/hooks/ai-sdk.d.ts +17 -19
- package/dist/hooks/ai-sdk.js +13 -7
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/mastra.d.ts +26 -0
- package/dist/hooks/mastra.js +63 -0
- package/dist/index.d.ts +22 -9
- package/dist/index.js +23 -9
- package/dist/mcp-requests/index.d.ts +26 -0
- package/dist/mcp-requests/index.js +154 -0
- package/dist/mcp-server/index.d.ts +12 -0
- package/dist/mcp-server/index.js +84 -2
- package/dist/mcp-wrap/index.js +8 -4
- package/dist/otlp/index.d.ts +155 -0
- package/dist/otlp/index.js +468 -0
- package/dist/otlp/protobuf.d.ts +7 -0
- package/dist/otlp/protobuf.js +314 -0
- package/dist/packs/index.d.ts +2 -0
- package/dist/packs/index.js +5 -1
- package/dist/pdf/arabic.d.ts +45 -0
- package/dist/pdf/arabic.js +373 -0
- package/dist/pdf/index.d.ts +77 -0
- package/dist/pdf/index.js +673 -0
- package/dist/policy/index.d.ts +0 -4
- package/dist/policy/index.js +13 -5
- package/dist/realtime/index.d.ts +33 -0
- package/dist/realtime/index.js +204 -0
- package/dist/reconcile/record.d.ts +12 -0
- package/dist/reconcile/record.js +114 -65
- package/dist/session/index.d.ts +43 -0
- package/dist/session/index.js +83 -0
- package/dist/undo/index.js +3 -3
- package/dist/verify/anchor.d.ts +5 -1
- package/dist/verify/anchor.js +13 -4
- package/dist/verify/envelope.d.ts +1 -1
- package/dist/verify/envelope.js +5 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/walls/index.js +2 -2
- package/dist/warehouse/index.d.ts +62 -0
- package/dist/warehouse/index.js +142 -0
- package/package.json +6 -3
package/dist/analysis/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import { healthFindings } from "../health/index.js";
|
|
|
8
8
|
import { policyFindings } from "../policy/index.js";
|
|
9
9
|
import { precogFindings } from "../precog/index.js";
|
|
10
10
|
import { preflightFindings } from "../preflight/index.js";
|
|
11
|
+
import { envelopesOf, foldState, trackRecord } from "../reconcile/record.js";
|
|
11
12
|
import { canonical } from "../reconcile/shared.js";
|
|
12
13
|
import { undoFindings } from "../undo/index.js";
|
|
13
14
|
import { wallFindings } from "../walls/index.js";
|
|
@@ -26,27 +27,68 @@ export { checkFlightPlan, landing, } from "./landing.js";
|
|
|
26
27
|
export { checkAgainstPacks, loadReferencePacks, packsFor, referenceContext, } from "./reference.js";
|
|
27
28
|
export { OUTCOMES, rollup, waste, } from "./waste.js";
|
|
28
29
|
const decoder = new TextDecoder();
|
|
29
|
-
/** Parses the lines once, attaching SDK event bodies.
|
|
30
|
+
/** Parses the lines once, attaching SDK event bodies. On a tracked record (`Analyzer`), only the
|
|
31
|
+
* lines added since the last call are read. */
|
|
30
32
|
export function eventsOf(lines, bodies) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
try {
|
|
39
|
-
const parsed = b === undefined ? {} : JSON.parse(decoder.decode(b));
|
|
40
|
-
e.data =
|
|
41
|
-
typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
e.data = {};
|
|
45
|
-
}
|
|
33
|
+
const fold = foldState(lines, "eventsOf", () => ({ done: 0, out: [], bodies }));
|
|
34
|
+
if (fold && fold.bodies === bodies) {
|
|
35
|
+
const all = envelopesOf(lines);
|
|
36
|
+
for (; fold.done < all.length; fold.done++) {
|
|
37
|
+
const e = withData(all[fold.done], bodies);
|
|
38
|
+
if (e)
|
|
39
|
+
fold.out.push(e);
|
|
46
40
|
}
|
|
47
|
-
return [
|
|
41
|
+
return [...fold.out];
|
|
42
|
+
}
|
|
43
|
+
return envelopesOf(lines).flatMap((e) => {
|
|
44
|
+
const out = withData(e, bodies);
|
|
45
|
+
return out ? [out] : [];
|
|
48
46
|
});
|
|
49
47
|
}
|
|
48
|
+
function withData(e, bodies) {
|
|
49
|
+
// N4 (idea R6): a re-sent SDK event (meta.duplicate_of) stays on the record but counts once.
|
|
50
|
+
if (e.kind === "sdk.event" && e.meta.duplicate_of !== undefined)
|
|
51
|
+
return null;
|
|
52
|
+
if (e.kind !== "sdk.event")
|
|
53
|
+
return e;
|
|
54
|
+
const b = bodies(e.body_hash);
|
|
55
|
+
let data = {};
|
|
56
|
+
try {
|
|
57
|
+
const parsed = b === undefined ? {} : JSON.parse(decoder.decode(b));
|
|
58
|
+
data = typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
|
|
59
|
+
}
|
|
60
|
+
catch { }
|
|
61
|
+
return Object.isFrozen(e) ? Object.freeze({ ...e, data: deepFreezeJson(data) }) : { ...e, data };
|
|
62
|
+
}
|
|
63
|
+
function deepFreezeJson(value) {
|
|
64
|
+
if (typeof value === "object" && value !== null && !Object.isFrozen(value)) {
|
|
65
|
+
Object.freeze(value);
|
|
66
|
+
for (const v of Object.values(value))
|
|
67
|
+
deepFreezeJson(v);
|
|
68
|
+
}
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* spec/findings.md §14 (G33): the analyses over one session's growing record. Each `findings` call
|
|
73
|
+
* reads only the lines added since the last one (each line is parsed once, the calls are rebuilt
|
|
74
|
+
* from the new events only) and gives exactly what `analyze` gives over the whole record.
|
|
75
|
+
*/
|
|
76
|
+
export class Analyzer {
|
|
77
|
+
lines = [];
|
|
78
|
+
bodies;
|
|
79
|
+
constructor(bodies) {
|
|
80
|
+
this.bodies = bodies;
|
|
81
|
+
trackRecord(this.lines);
|
|
82
|
+
}
|
|
83
|
+
/** Appends the record's new lines (in seq order; a record only grows). */
|
|
84
|
+
push(lines) {
|
|
85
|
+
for (const l of lines)
|
|
86
|
+
this.lines.push(l);
|
|
87
|
+
}
|
|
88
|
+
findings(options) {
|
|
89
|
+
return analyze(this.lines, { ...options, bodies: this.bodies });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
50
92
|
/** Every analysis, in spec order. */
|
|
51
93
|
export function analyze(lines, options) {
|
|
52
94
|
const events = eventsOf(lines, options.bodies);
|
|
@@ -60,6 +102,7 @@ export function analyze(lines, options) {
|
|
|
60
102
|
...toolChanged(events),
|
|
61
103
|
...toolDrifted(events),
|
|
62
104
|
...secretSent(events),
|
|
105
|
+
...elicitationSecret(events),
|
|
63
106
|
...userDownvotes(events),
|
|
64
107
|
...selfChanges(events),
|
|
65
108
|
...promptChanged(events),
|
|
@@ -473,16 +516,55 @@ export function secretSent(events) {
|
|
|
473
516
|
n = count(e.meta.redacted);
|
|
474
517
|
to = "tool";
|
|
475
518
|
}
|
|
519
|
+
else if ((e.kind === "tool.elicitation" || e.kind === "tool.sampling") &&
|
|
520
|
+
e.meta.phase === "reply" &&
|
|
521
|
+
typeof e.meta.method === "string") {
|
|
522
|
+
// spec/mcp.md §6: the client's answer to a server's request went to that MCP server
|
|
523
|
+
n = count(e.meta.redacted);
|
|
524
|
+
to = "server";
|
|
525
|
+
}
|
|
476
526
|
if (!to || n <= 0)
|
|
477
527
|
continue;
|
|
478
528
|
out.push({
|
|
479
529
|
code: "SECRET_SENT",
|
|
480
530
|
source: "detectors",
|
|
481
531
|
severity: "warning",
|
|
482
|
-
ref: {
|
|
532
|
+
ref: {
|
|
533
|
+
seq: e.seq,
|
|
534
|
+
to: to === "server" ? "tool" : to,
|
|
535
|
+
count: n,
|
|
536
|
+
...(to === "model"
|
|
537
|
+
? {}
|
|
538
|
+
: { tool: (to === "tool" ? e.meta.tool : e.meta.method) }),
|
|
539
|
+
},
|
|
483
540
|
detail: to === "model"
|
|
484
541
|
? "A credential (a key, a token, a password) went to the model provider in the request."
|
|
485
|
-
:
|
|
542
|
+
: to === "tool"
|
|
543
|
+
? "A credential (a key, a token, a password) went to a tool in its arguments; the gateway's credential broker keeps them out of the agent's hands."
|
|
544
|
+
: "A credential (a key, a token, a password) went to an MCP server in the client's answer to its request.",
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
return out;
|
|
548
|
+
}
|
|
549
|
+
/** spec/mcp.md §6: an MCP server asked the user for a credential through `elicitation/create`. */
|
|
550
|
+
export function elicitationSecret(events) {
|
|
551
|
+
const out = [];
|
|
552
|
+
for (const e of events) {
|
|
553
|
+
const fields = e.meta.secret_fields;
|
|
554
|
+
if (e.kind !== "tool.elicitation" || e.meta.phase !== "request")
|
|
555
|
+
continue;
|
|
556
|
+
if (!Array.isArray(fields) || fields.length === 0)
|
|
557
|
+
continue;
|
|
558
|
+
out.push({
|
|
559
|
+
code: "ELICITATION_SECRET",
|
|
560
|
+
source: "detectors",
|
|
561
|
+
severity: "warning",
|
|
562
|
+
ref: {
|
|
563
|
+
seq: e.seq,
|
|
564
|
+
...(typeof e.meta.server === "string" ? { server: e.meta.server } : {}),
|
|
565
|
+
fields: fields.map(String).join(","),
|
|
566
|
+
},
|
|
567
|
+
detail: "An MCP server asked the user for a credential (a password, a token, a key) through elicitation; a server should never collect one this way.",
|
|
486
568
|
});
|
|
487
569
|
}
|
|
488
570
|
return out;
|
|
@@ -7,7 +7,8 @@ export declare const INJECTION_RULES: ReadonlyArray<{
|
|
|
7
7
|
export declare function hiddenText(text: string): boolean;
|
|
8
8
|
/** The first rule a text breaks, or null. */
|
|
9
9
|
export declare function injectionRule(text: string): string | null;
|
|
10
|
-
/** spec/findings.md §1b: PROMPT_INJECTION findings over a record.
|
|
10
|
+
/** spec/findings.md §1b: PROMPT_INJECTION findings over a record. Each event is judged on its own
|
|
11
|
+
* (on a tracked record, once: G33). */
|
|
11
12
|
export declare function injectionFindings(lines: readonly string[], bodies: (hash: string) => Uint8Array | undefined): {
|
|
12
13
|
code: string;
|
|
13
14
|
source: string;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// role hijacks, exfiltration asks and hidden text, in English and Arabic. One finding per event and
|
|
5
5
|
// source; the finding names the rule, never the text. Mirrors analysis/injection.py; pinned by
|
|
6
6
|
// spec/vectors/injection.json.
|
|
7
|
+
import { perEvent } from "../reconcile/record.js";
|
|
7
8
|
const decoder = new TextDecoder();
|
|
8
9
|
const MAX_TEXT = 200_000; // characters scanned per event: an injection is near the start or not worth the cost
|
|
9
10
|
/** The rules, in the order they're tried. Patterns run on ASCII-lowercased, space-folded text. */
|
|
@@ -120,10 +121,10 @@ function newestTurn(body) {
|
|
|
120
121
|
}
|
|
121
122
|
return out;
|
|
122
123
|
}
|
|
123
|
-
/** spec/findings.md §1b: PROMPT_INJECTION findings over a record.
|
|
124
|
+
/** spec/findings.md §1b: PROMPT_INJECTION findings over a record. Each event is judged on its own
|
|
125
|
+
* (on a tracked record, once: G33). */
|
|
124
126
|
export function injectionFindings(lines, bodies) {
|
|
125
|
-
const
|
|
126
|
-
const add = (seq, source, rule, tool) => out.push({
|
|
127
|
+
const found = (seq, source, rule, tool) => ({
|
|
127
128
|
code: "PROMPT_INJECTION",
|
|
128
129
|
source: "detectors",
|
|
129
130
|
severity: "warning",
|
|
@@ -132,32 +133,31 @@ export function injectionFindings(lines, bodies) {
|
|
|
132
133
|
? "Text the reader can't see (hidden characters) reached the model: a classic way to smuggle instructions."
|
|
133
134
|
: "Text that reached the model tries to give it new instructions: an override, a role change, or a request to send out secrets.",
|
|
134
135
|
});
|
|
135
|
-
|
|
136
|
-
const e =
|
|
136
|
+
return perEvent(lines, "injection", bodies, (envelope) => {
|
|
137
|
+
const e = envelope;
|
|
137
138
|
if (e.kind !== "tool.result" && e.kind !== "llm.request")
|
|
138
|
-
|
|
139
|
+
return [];
|
|
139
140
|
const raw = bodies(e.body_hash);
|
|
140
141
|
if (!raw || raw.length === 0)
|
|
141
|
-
|
|
142
|
+
return [];
|
|
142
143
|
let doc;
|
|
143
144
|
try {
|
|
144
145
|
doc = JSON.parse(decoder.decode(raw));
|
|
145
146
|
}
|
|
146
147
|
catch {
|
|
147
|
-
|
|
148
|
+
return [];
|
|
148
149
|
}
|
|
149
150
|
if (e.kind === "tool.result") {
|
|
150
151
|
const result = doc?.result;
|
|
151
152
|
if (result === undefined)
|
|
152
|
-
|
|
153
|
+
return [];
|
|
153
154
|
const rule = injectionRule(textOf(result));
|
|
154
155
|
const tool = typeof e.meta.tool === "string" ? e.meta.tool : undefined;
|
|
155
|
-
|
|
156
|
-
add(e.seq, "tool_result", rule, tool);
|
|
157
|
-
continue;
|
|
156
|
+
return rule ? [found(e.seq, "tool_result", rule, tool)] : [];
|
|
158
157
|
}
|
|
159
158
|
if (!doc || typeof doc !== "object" || Array.isArray(doc))
|
|
160
|
-
|
|
159
|
+
return [];
|
|
160
|
+
const out = [];
|
|
161
161
|
const seen = new Set();
|
|
162
162
|
for (const part of newestTurn(doc)) {
|
|
163
163
|
if (seen.has(part.source))
|
|
@@ -165,9 +165,9 @@ export function injectionFindings(lines, bodies) {
|
|
|
165
165
|
const rule = injectionRule(part.text);
|
|
166
166
|
if (rule) {
|
|
167
167
|
seen.add(part.source);
|
|
168
|
-
|
|
168
|
+
out.push(found(e.seq, part.source, rule));
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
|
-
|
|
172
|
-
|
|
171
|
+
return out;
|
|
172
|
+
});
|
|
173
173
|
}
|
package/dist/analysis/landing.js
CHANGED
|
@@ -4,6 +4,8 @@ import { checkDuty } from "../duty/index.js";
|
|
|
4
4
|
import { callsOf } from "../reconcile/record.js";
|
|
5
5
|
import { isObj } from "../reconcile/shared.js";
|
|
6
6
|
import { toolCallsOf, toolResultsOf } from "./detectors.js";
|
|
7
|
+
import { answerText } from "./hallucination.js";
|
|
8
|
+
import { checkPredicate, judgeAnswer, judgeArgs } from "./plan-checks.js";
|
|
7
9
|
/** Every tool call in the record, with what its result said (provider tool_use ids; MCP call_seq). */
|
|
8
10
|
function usesOf(events, lines, bodies) {
|
|
9
11
|
const calls = callsOf(lines, bodies);
|
|
@@ -13,16 +15,26 @@ function usesOf(events, lines, bodies) {
|
|
|
13
15
|
results.set(r.id, r);
|
|
14
16
|
const uses = toolCallsOf(calls).map((t) => {
|
|
15
17
|
const r = t.id === undefined ? undefined : results.get(t.id);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
: { seq: t.seq, tool: t.tool };
|
|
18
|
+
const base = { seq: t.seq, tool: t.tool, args: t.args ?? null, hasArgs: true };
|
|
19
|
+
return r ? { ...base, ok: !r.error, resultSeq: r.seq } : base;
|
|
19
20
|
});
|
|
20
21
|
const mcp = new Map();
|
|
21
22
|
for (const e of events) {
|
|
22
23
|
if (e.kind === "tool.call" &&
|
|
23
24
|
e.meta.method === "tools/call" &&
|
|
24
25
|
typeof e.meta.tool === "string") {
|
|
25
|
-
const u = { seq: e.seq, tool: e.meta.tool };
|
|
26
|
+
const u = { seq: e.seq, tool: e.meta.tool, hasArgs: false };
|
|
27
|
+
// the arguments are the request body's params.arguments (gone when the body was erased)
|
|
28
|
+
try {
|
|
29
|
+
const body = bodies(e.body_hash);
|
|
30
|
+
if (body !== undefined) {
|
|
31
|
+
const rpc = JSON.parse(new TextDecoder().decode(body));
|
|
32
|
+
const params = isObj(rpc) ? rpc.params : undefined;
|
|
33
|
+
u.args = isObj(params) && "arguments" in params ? params.arguments : {};
|
|
34
|
+
u.hasArgs = true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch { }
|
|
26
38
|
mcp.set(e.seq, u);
|
|
27
39
|
uses.push(u);
|
|
28
40
|
}
|
|
@@ -62,13 +74,33 @@ function plansOf(events) {
|
|
|
62
74
|
return { plan: null, invalid };
|
|
63
75
|
}
|
|
64
76
|
const listOf = (v) => (Array.isArray(v) ? v : []);
|
|
65
|
-
/**
|
|
66
|
-
function
|
|
77
|
+
/** The final answers on the record (a complete model response with text and no tool call). */
|
|
78
|
+
function answersOf(lines, bodies) {
|
|
79
|
+
const calls = callsOf(lines, bodies);
|
|
80
|
+
const ends = new Set(toolCallsOf(calls).map((t) => t.seq));
|
|
81
|
+
return [...calls]
|
|
82
|
+
.sort((a, b) => a.endSeq - b.endSeq)
|
|
83
|
+
.filter((c) => c.complete && c.request && !ends.has(c.endSeq))
|
|
84
|
+
.map((c) => ({ seq: c.endSeq, text: answerText(c) }))
|
|
85
|
+
.filter((a) => a.text.length > 0);
|
|
86
|
+
}
|
|
87
|
+
/** One predicate over the tool calls (and results, and answers) recorded before `before`. */
|
|
88
|
+
function judge(p, uses, before, answers = []) {
|
|
67
89
|
const seen = uses.filter((u) => u.seq < before);
|
|
68
90
|
const one = isObj(p) && Object.keys(p).length === 1 ? Object.entries(p)[0] : null;
|
|
69
91
|
const [key, arg] = one ?? ["", null];
|
|
70
92
|
const named = typeof arg === "string" ? seen.filter((u) => u.tool === arg) : [];
|
|
71
93
|
const out = (result, gap) => ({ predicate: p, result, ...(gap ? { gap } : {}) });
|
|
94
|
+
if (key === "tool_args" || key === "answer") {
|
|
95
|
+
// spec/findings.md §5a: a claim's own asserts aren't checked when filed, so here
|
|
96
|
+
const bad = checkPredicate(p);
|
|
97
|
+
if (bad)
|
|
98
|
+
return out("cannot_verify", `invalid predicate: ${bad}`);
|
|
99
|
+
const r = key === "tool_args"
|
|
100
|
+
? judgeArgs(arg, uses, before)
|
|
101
|
+
: judgeAnswer(arg, answers, before);
|
|
102
|
+
return out(r.result, r.gap);
|
|
103
|
+
}
|
|
72
104
|
if (key === "tool_called" && typeof arg === "string")
|
|
73
105
|
return named.length ? out("pass") : out("fail", `${arg} was never called`);
|
|
74
106
|
if (key === "tool_not_called" && typeof arg === "string")
|
|
@@ -94,6 +126,7 @@ function judge(p, uses, before) {
|
|
|
94
126
|
export function landing(events, lines, bodies) {
|
|
95
127
|
const plan = planOf(events);
|
|
96
128
|
const uses = usesOf(events, lines, bodies);
|
|
129
|
+
const answers = answersOf(lines, bodies);
|
|
97
130
|
const claims = [];
|
|
98
131
|
for (const e of events) {
|
|
99
132
|
if (e.kind !== "sdk.event" || e.meta.type !== "claim")
|
|
@@ -102,11 +135,11 @@ export function landing(events, lines, bodies) {
|
|
|
102
135
|
const criteria = [
|
|
103
136
|
...listOf(plan?.criteria).map((p, i) => ({
|
|
104
137
|
criterion: `plan:${i}`,
|
|
105
|
-
...judge(p, uses, e.seq),
|
|
138
|
+
...judge(p, uses, e.seq, answers),
|
|
106
139
|
})),
|
|
107
140
|
...listOf(data.asserts).map((p, i) => ({
|
|
108
141
|
criterion: `assert:${i}`,
|
|
109
|
-
...judge(p, uses, e.seq),
|
|
142
|
+
...judge(p, uses, e.seq, answers),
|
|
110
143
|
})),
|
|
111
144
|
];
|
|
112
145
|
const verdict = criteria.some((c) => c.result === "fail")
|
|
@@ -145,7 +178,9 @@ export function flightPlan(events, lines, bodies) {
|
|
|
145
178
|
}
|
|
146
179
|
}
|
|
147
180
|
listOf(plan.criteria).forEach((p, i) => {
|
|
148
|
-
|
|
181
|
+
// broken as soon as it is: a limit, or a rule on every call's arguments (§5a.2)
|
|
182
|
+
const every = isObj(p) && isObj(p.tool_args) && p.tool_args.for === "every";
|
|
183
|
+
if (!isObj(p) || !("tool_not_called" in p || "max_tool_calls" in p || every))
|
|
149
184
|
return;
|
|
150
185
|
const r = judge(p, uses, Number.POSITIVE_INFINITY);
|
|
151
186
|
if (r.result === "fail")
|
|
@@ -196,11 +231,22 @@ export function checkFlightPlan(plan) {
|
|
|
196
231
|
tools.every((x) => typeof x === "string" && x.length > 0 && x.length <= 128)))
|
|
197
232
|
return "flight_plan.expected_tools must be at most 100 tool names";
|
|
198
233
|
const value = (v) => typeof v === "string" ? v.length <= 128 : Number.isSafeInteger(v) && v >= 0;
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
234
|
+
const legacy = 'flight_plan.criteria must be at most 50 predicates like {"tool_called": "run_tests"}';
|
|
235
|
+
if (criteria !== undefined) {
|
|
236
|
+
if (!Array.isArray(criteria) || criteria.length > 50)
|
|
237
|
+
return legacy;
|
|
238
|
+
for (const [i, c] of criteria.entries()) {
|
|
239
|
+
if (!isObj(c) || Object.keys(c).length !== 1)
|
|
240
|
+
return legacy;
|
|
241
|
+
if ("tool_args" in c || "answer" in c) {
|
|
242
|
+
const bad = checkPredicate(c); // spec/findings.md §5a
|
|
243
|
+
if (bad)
|
|
244
|
+
return `flight_plan.criteria[${i}]: ${bad}`;
|
|
245
|
+
}
|
|
246
|
+
else if (!value(Object.values(c)[0]))
|
|
247
|
+
return legacy;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
204
250
|
const name = (x) => typeof x === "string" && x.length > 0 && x.length <= 128;
|
|
205
251
|
const { sterile } = plan;
|
|
206
252
|
if (sterile !== undefined &&
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
type Test = (c: string) => boolean;
|
|
2
|
+
type Inst = {
|
|
3
|
+
op: "char";
|
|
4
|
+
test: Test;
|
|
5
|
+
} | {
|
|
6
|
+
op: "split";
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
} | {
|
|
10
|
+
op: "jmp";
|
|
11
|
+
x: number;
|
|
12
|
+
} | {
|
|
13
|
+
op: "assert";
|
|
14
|
+
kind: "bol" | "eol" | "word" | "nonword";
|
|
15
|
+
} | {
|
|
16
|
+
op: "match";
|
|
17
|
+
};
|
|
18
|
+
export interface Pattern {
|
|
19
|
+
prog: Inst[];
|
|
20
|
+
}
|
|
21
|
+
/** Text longer than this (in code points) isn't matched (cannot_verify). */
|
|
22
|
+
export declare const MAX_MATCH_TEXT = 100000;
|
|
23
|
+
/** Parses and compiles a pattern, or says why it isn't one (spec/findings.md §5a.1). */
|
|
24
|
+
export declare function compilePattern(source: unknown): Pattern | string;
|
|
25
|
+
/** Whether the pattern matches anywhere in the text (a search, not a full match). */
|
|
26
|
+
export declare function patternMatches(p: Pattern, text: string): boolean;
|
|
27
|
+
/** A value in integer milli-units (spec/findings.md §5a.2), or null. */
|
|
28
|
+
export declare function milliOf(v: unknown): number | null;
|
|
29
|
+
/** Why a schema is outside the subset, or undefined. */
|
|
30
|
+
export declare function checkSchema(schema: unknown): string | undefined;
|
|
31
|
+
/** The first place a value breaks a (checked) schema, or undefined (spec/findings.md §5a.3). */
|
|
32
|
+
export declare function schemaError(value: unknown, schema: unknown, path?: string): string | undefined;
|
|
33
|
+
/** Why a `tool_args` or `answer` predicate is invalid, or undefined. */
|
|
34
|
+
export declare function checkPredicate(p: unknown): string | undefined;
|
|
35
|
+
/** A tool call with its arguments, when they're on the record (`args` absent: unknown). */
|
|
36
|
+
export interface ArgUse {
|
|
37
|
+
seq: number;
|
|
38
|
+
tool: string;
|
|
39
|
+
args?: unknown;
|
|
40
|
+
hasArgs: boolean;
|
|
41
|
+
}
|
|
42
|
+
type Out = {
|
|
43
|
+
result: "pass" | "fail" | "cannot_verify";
|
|
44
|
+
gap?: string;
|
|
45
|
+
};
|
|
46
|
+
/** A `tool_args` predicate over the tool calls before `before` (spec/findings.md §5a.2). */
|
|
47
|
+
export declare function judgeArgs(arg: Record<string, unknown>, uses: readonly ArgUse[], before: number): Out;
|
|
48
|
+
/** The JSON an answer holds: the whole text, or one ``` fence around it; ABSENT when it isn't JSON. */
|
|
49
|
+
export declare function answerJson(text: string): unknown;
|
|
50
|
+
/** An `answer` predicate over the last final answer before `before` (spec/findings.md §5a.4). */
|
|
51
|
+
export declare function judgeAnswer(arg: Record<string, unknown>, answers: ReadonlyArray<{
|
|
52
|
+
seq: number;
|
|
53
|
+
text: string;
|
|
54
|
+
}>, before: number): Out;
|
|
55
|
+
export {};
|