jev-agent-tools 0.2.0 → 0.3.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/CHANGELOG.md +37 -1
- package/CONTRIBUTING.md +3 -0
- package/README.md +22 -14
- package/SECURITY.md +17 -1
- package/dist/adapters/ask-files.js +11 -2
- package/dist/adapters/ask-proof.js +63 -7
- package/dist/adapters/command.js +82 -29
- package/dist/adapters/docs.js +30 -10
- package/dist/adapters/evidence-context.js +119 -0
- package/dist/adapters/files.js +141 -16
- package/dist/adapters/find.js +34 -6
- package/dist/adapters/git-base.js +7 -1
- package/dist/adapters/git.js +51 -7
- package/dist/adapters/locate-file.js +47 -9
- package/dist/adapters/private-storage.js +14 -6
- package/dist/adapters/risk-callers.js +3 -0
- package/dist/adapters/shell.js +23 -7
- package/dist/adapters/test-inventory.js +10 -2
- package/dist/configuration.js +17 -7
- package/dist/constants.js +26 -5
- package/dist/core/ask-references.js +193 -109
- package/dist/core/asks.js +78 -7
- package/dist/core/locate.js +8 -8
- package/dist/core/output.js +17 -0
- package/dist/core/result-report.js +302 -0
- package/dist/core/secret-path.js +34 -0
- package/dist/core/state.js +8 -1
- package/dist/core/units.js +1 -1
- package/dist/jev/client.js +34 -12
- package/dist/mcp/protocol.js +50 -27
- package/dist/mcp/tools.js +20 -7
- package/dist/render.js +72 -0
- package/dist/report-schema.js +1356 -0
- package/dist/result-types.js +1 -0
- package/dist/texts/ask-files.js +3 -1
- package/dist/texts/ask.js +3 -1
- package/dist/texts/check-diff.js +7 -4
- package/dist/texts/find.js +7 -2
- package/dist/texts/guide.js +3 -16
- package/dist/texts/instructions.js +72 -0
- package/dist/texts/locate.js +7 -2
- package/dist/texts/select-tests.js +3 -1
- package/dist/tools/ask-files.js +248 -15
- package/dist/tools/ask.js +523 -62
- package/dist/tools/check-diff.js +222 -30
- package/dist/tools/docs-check.js +122 -13
- package/dist/tools/find.js +320 -27
- package/dist/tools/locate.js +317 -18
- package/dist/tools/review-report.js +230 -0
- package/dist/tools/select-tests.js +273 -19
- package/dist/tools/spec-check.js +119 -22
- package/docs/adr/0001-strict-typescript-pure-core-offline-tests.md +3 -3
- package/docs/agent-instructions.md +59 -30
- package/docs/design.md +13 -1
- package/docs/mcp.md +8 -6
- package/docs/tools/jev_ask.md +8 -5
- package/docs/tools/jev_ask_files.md +2 -1
- package/docs/tools/jev_check_diff.md +4 -1
- package/docs/tools/jev_find_files.md +2 -1
- package/docs/tools/jev_locate_in_file.md +5 -0
- package/docs/tools/jev_select_tests.md +4 -1
- package/package.json +1 -1
- package/rules/jev-ask.md +22 -1
- package/server.json +2 -2
- package/src/adapters/ask-files.ts +11 -3
- package/src/adapters/ask-proof.ts +69 -11
- package/src/adapters/command.ts +96 -33
- package/src/adapters/docs.ts +33 -14
- package/src/adapters/evidence-context.ts +169 -0
- package/src/adapters/files.ts +146 -16
- package/src/adapters/find.ts +37 -7
- package/src/adapters/git-base.ts +7 -1
- package/src/adapters/git.ts +61 -8
- package/src/adapters/locate-file.ts +51 -9
- package/src/adapters/private-storage.ts +17 -5
- package/src/adapters/risk-callers.ts +3 -0
- package/src/adapters/shell.ts +23 -7
- package/src/adapters/test-inventory.ts +12 -4
- package/src/configuration.ts +16 -2
- package/src/constants.ts +26 -5
- package/src/core/ask-references.ts +262 -146
- package/src/core/asks.ts +79 -7
- package/src/core/import-boundaries.ts +8 -3
- package/src/core/locate.ts +8 -5
- package/src/core/output.ts +34 -0
- package/src/core/result-report.ts +410 -0
- package/src/core/secret-path.ts +37 -0
- package/src/core/state.ts +8 -1
- package/src/core/units.ts +3 -2
- package/src/index.ts +3 -0
- package/src/jev/client.ts +54 -16
- package/src/jev/types.ts +18 -3
- package/src/mcp/protocol.ts +91 -41
- package/src/mcp/tools.ts +26 -13
- package/src/render.ts +109 -0
- package/src/report-schema.ts +1380 -0
- package/src/result-types.ts +234 -0
- package/src/result.ts +4 -1
- package/src/runtime.ts +6 -0
- package/src/texts/ask-files.ts +4 -1
- package/src/texts/ask.ts +8 -1
- package/src/texts/check-diff.ts +7 -4
- package/src/texts/find.ts +8 -2
- package/src/texts/guide.ts +8 -16
- package/src/texts/instructions.ts +98 -0
- package/src/texts/locate.ts +8 -2
- package/src/texts/run-end.ts +2 -2
- package/src/texts/select-tests.ts +4 -1
- package/src/tools/ask-files.ts +309 -14
- package/src/tools/ask.ts +700 -77
- package/src/tools/check-diff.ts +331 -28
- package/src/tools/docs-check.ts +241 -39
- package/src/tools/find.ts +386 -29
- package/src/tools/locate.ts +384 -19
- package/src/tools/review-report.ts +308 -0
- package/src/tools/select-tests.ts +479 -21
- package/src/tools/spec-check.ts +193 -19
package/src/tools/check-diff.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { type Static, Type } from "@sinclair/typebox";
|
|
3
3
|
import { createAnalysisContext } from "../adapters/analysis-context.ts";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
type EvidenceContext,
|
|
6
|
+
resolveEvidenceContext,
|
|
7
|
+
withEvidenceContext,
|
|
8
|
+
} from "../adapters/evidence-context.ts";
|
|
5
9
|
import { collectUnits } from "../adapters/git.ts";
|
|
6
10
|
import { resolveBase } from "../adapters/git-base.ts";
|
|
7
11
|
import { shareGitInventory } from "../adapters/git-inventory.ts";
|
|
@@ -13,6 +17,7 @@ import {
|
|
|
13
17
|
CANNOT_TELL_MIN,
|
|
14
18
|
FLAG_MIN,
|
|
15
19
|
STATE_MAX_CHARS,
|
|
20
|
+
TIMEOUT_MS,
|
|
16
21
|
} from "../constants.ts";
|
|
17
22
|
import { prepareBatches } from "../core/batches.ts";
|
|
18
23
|
import { isTestFile } from "../core/diff.ts";
|
|
@@ -23,6 +28,11 @@ import {
|
|
|
23
28
|
type Envelope,
|
|
24
29
|
type Limitation,
|
|
25
30
|
} from "../core/output.ts";
|
|
31
|
+
import {
|
|
32
|
+
type Cause,
|
|
33
|
+
known,
|
|
34
|
+
type ResultReportV1,
|
|
35
|
+
} from "../core/result-report.ts";
|
|
26
36
|
import type { CallerProof, EvidenceSpan } from "../core/risk-callers.ts";
|
|
27
37
|
import type { EvidenceUnit } from "../core/units.ts";
|
|
28
38
|
import type { GuideContext } from "../guide.ts";
|
|
@@ -38,15 +48,18 @@ import {
|
|
|
38
48
|
prepareRiskMatrix,
|
|
39
49
|
prepareRiskSeverity,
|
|
40
50
|
} from "../presets/risk.ts";
|
|
41
|
-
import {
|
|
51
|
+
import { renderResultReport } from "../render.ts";
|
|
42
52
|
import type { ToolDependencies } from "../runtime.ts";
|
|
43
53
|
import { CHECK_DIFF_DESCRIPTION } from "../texts/check-diff.ts";
|
|
44
54
|
import { NOT_CONFIGURED } from "../texts/configuration.ts";
|
|
55
|
+
import { CHECK_DIFF_GUIDELINE } from "../texts/instructions.ts";
|
|
45
56
|
import { runDocsCheck } from "./docs-check.ts";
|
|
57
|
+
import { controlsFor, ReviewReport, reportMetrics } from "./review-report.ts";
|
|
46
58
|
import { runSpecCheck } from "./spec-check.ts";
|
|
47
59
|
|
|
48
60
|
export const checkDiffParameters = Type.Object(
|
|
49
61
|
{
|
|
62
|
+
root: Type.Optional(Type.String()),
|
|
50
63
|
check: Type.Union([
|
|
51
64
|
Type.Literal("risk"),
|
|
52
65
|
Type.Literal("docs"),
|
|
@@ -72,6 +85,8 @@ export const checkDiffParameters = Type.Object(
|
|
|
72
85
|
export type CheckDiffArgs = Static<typeof checkDiffParameters>;
|
|
73
86
|
export interface RiskDetails {
|
|
74
87
|
ok: boolean;
|
|
88
|
+
evidenceContext: EvidenceContext;
|
|
89
|
+
result: ResultReportV1;
|
|
75
90
|
envelope: Envelope;
|
|
76
91
|
judgments: Judgment[];
|
|
77
92
|
callerProofs: CallerProof[];
|
|
@@ -96,9 +111,7 @@ export function createCheckDiffTool(dependencies: ToolDependencies) {
|
|
|
96
111
|
: {
|
|
97
112
|
promptSnippet:
|
|
98
113
|
"Check the uncommitted diff for risky changes, stale docs or spec drift",
|
|
99
|
-
promptGuidelines: [
|
|
100
|
-
"jev_check_diff: run it once before you report done, not on a half-written diff",
|
|
101
|
-
],
|
|
114
|
+
promptGuidelines: [CHECK_DIFF_GUIDELINE],
|
|
102
115
|
}),
|
|
103
116
|
async execute(
|
|
104
117
|
_id: string,
|
|
@@ -108,8 +121,53 @@ export function createCheckDiffTool(dependencies: ToolDependencies) {
|
|
|
108
121
|
ctx: { cwd: string } & GuideContext,
|
|
109
122
|
) {
|
|
110
123
|
const client = dependencies.client;
|
|
111
|
-
const
|
|
124
|
+
const evidence = await resolveEvidenceContext(ctx.cwd, args.root, {
|
|
125
|
+
exec: execute,
|
|
126
|
+
signal,
|
|
127
|
+
origin: dependencies.evidenceOrigin,
|
|
128
|
+
});
|
|
129
|
+
const evidenceContext = evidence.context;
|
|
130
|
+
const report = new ReviewReport();
|
|
131
|
+
const cwd = evidence.ok ? evidence.cwd : evidenceContext.authority.path;
|
|
112
132
|
const exec = shareGitInventory(execute);
|
|
133
|
+
if (!evidence.ok) {
|
|
134
|
+
const envelope = buildEnvelope({
|
|
135
|
+
refusal: evidence.error,
|
|
136
|
+
yield: {
|
|
137
|
+
calls: 0,
|
|
138
|
+
questions: 0,
|
|
139
|
+
cacheHits: 0,
|
|
140
|
+
cacheRequests: 0,
|
|
141
|
+
elapsedMs: 0,
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
runtime.session.record(envelope);
|
|
145
|
+
runtime.guide.deliver(ctx);
|
|
146
|
+
report.refusal = true;
|
|
147
|
+
report.diagnose(evidence.cause, evidence.error, args.root);
|
|
148
|
+
const result = report.build(
|
|
149
|
+
"jev_check_diff",
|
|
150
|
+
evidenceContext,
|
|
151
|
+
reportMetrics(envelope),
|
|
152
|
+
);
|
|
153
|
+
return {
|
|
154
|
+
content: [
|
|
155
|
+
{
|
|
156
|
+
type: "text" as const,
|
|
157
|
+
text: renderResultReport(result, { details: envelope }),
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
details: {
|
|
161
|
+
ok: false,
|
|
162
|
+
envelope,
|
|
163
|
+
judgments: [],
|
|
164
|
+
callerProofs: [],
|
|
165
|
+
evidenceContext,
|
|
166
|
+
result,
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
evidenceContext.requestedBase = args.base ?? "HEAD";
|
|
113
171
|
if (args.check === "docs" || args.check === "spec") {
|
|
114
172
|
const deps = { client, host, runtime, exec };
|
|
115
173
|
const result =
|
|
@@ -119,6 +177,7 @@ export function createCheckDiffTool(dependencies: ToolDependencies) {
|
|
|
119
177
|
base: args.base,
|
|
120
178
|
signal,
|
|
121
179
|
maxCalls: args.max_calls,
|
|
180
|
+
evidenceContext,
|
|
122
181
|
})
|
|
123
182
|
: await runSpecCheck(deps, {
|
|
124
183
|
cwd: cwd,
|
|
@@ -126,6 +185,7 @@ export function createCheckDiffTool(dependencies: ToolDependencies) {
|
|
|
126
185
|
specPath: args.spec_path,
|
|
127
186
|
signal,
|
|
128
187
|
maxCalls: args.max_calls,
|
|
188
|
+
evidenceContext,
|
|
129
189
|
});
|
|
130
190
|
runtime.session.record(result.envelope);
|
|
131
191
|
runtime.guide.deliver(ctx);
|
|
@@ -138,9 +198,14 @@ export function createCheckDiffTool(dependencies: ToolDependencies) {
|
|
|
138
198
|
);
|
|
139
199
|
return {
|
|
140
200
|
content: [
|
|
141
|
-
{
|
|
201
|
+
{
|
|
202
|
+
type: "text" as const,
|
|
203
|
+
text: renderResultReport(result.result, {
|
|
204
|
+
details: result.envelope,
|
|
205
|
+
}),
|
|
206
|
+
},
|
|
142
207
|
],
|
|
143
|
-
details: { ...result, callerProofs: [] },
|
|
208
|
+
details: { ...result, callerProofs: [], evidenceContext },
|
|
144
209
|
...hostUsage(host.isOmp, usage),
|
|
145
210
|
};
|
|
146
211
|
}
|
|
@@ -155,7 +220,7 @@ export function createCheckDiffTool(dependencies: ToolDependencies) {
|
|
|
155
220
|
let sent = 0;
|
|
156
221
|
let matrixHealthy = true;
|
|
157
222
|
let emptyBase: string | undefined;
|
|
158
|
-
const finish = (refusal?: string) => {
|
|
223
|
+
const finish = (refusal?: string, cause?: Cause) => {
|
|
159
224
|
answers.sort((a, b) =>
|
|
160
225
|
(unitOrder.get(a) ?? "").localeCompare(unitOrder.get(b) ?? ""),
|
|
161
226
|
);
|
|
@@ -172,7 +237,14 @@ export function createCheckDiffTool(dependencies: ToolDependencies) {
|
|
|
172
237
|
{ inputTokens: 0, costUsd: 0 },
|
|
173
238
|
);
|
|
174
239
|
const noFindings =
|
|
175
|
-
!refusal &&
|
|
240
|
+
!refusal &&
|
|
241
|
+
matrixHealthy &&
|
|
242
|
+
!answers.length &&
|
|
243
|
+
!unchecked.length &&
|
|
244
|
+
report.items.size > 0 &&
|
|
245
|
+
[...report.items.values()].every(
|
|
246
|
+
(item) => item.treatment === "judged",
|
|
247
|
+
);
|
|
176
248
|
const envelope = buildEnvelope({
|
|
177
249
|
answers,
|
|
178
250
|
limitations:
|
|
@@ -202,7 +274,11 @@ export function createCheckDiffTool(dependencies: ToolDependencies) {
|
|
|
202
274
|
yield: {
|
|
203
275
|
calls: judgments.reduce((n, j) => n + (j.calls ?? 0), 0),
|
|
204
276
|
questions: judgments.reduce((n, j) => n + (j.questions ?? 0), 0),
|
|
205
|
-
costUsd:
|
|
277
|
+
costUsd:
|
|
278
|
+
judgments.every((j) => j.usage !== undefined) &&
|
|
279
|
+
judgments.length > 0
|
|
280
|
+
? usage.costUsd
|
|
281
|
+
: undefined,
|
|
206
282
|
cacheHits: judgments.reduce((n, j) => n + (j.cacheHits ?? 0), 0),
|
|
207
283
|
cacheRequests: judgments.reduce(
|
|
208
284
|
(n, j) => n + (j.cacheRequests ?? 0),
|
|
@@ -211,11 +287,38 @@ export function createCheckDiffTool(dependencies: ToolDependencies) {
|
|
|
211
287
|
elapsedMs: performance.now() - started,
|
|
212
288
|
},
|
|
213
289
|
});
|
|
290
|
+
if (emptyBase !== undefined)
|
|
291
|
+
report.diagnose(
|
|
292
|
+
"no_changed_units",
|
|
293
|
+
`No changed units against ${emptyBase}; no risk judgment requested.`,
|
|
294
|
+
emptyBase,
|
|
295
|
+
[],
|
|
296
|
+
false,
|
|
297
|
+
);
|
|
298
|
+
if (
|
|
299
|
+
refusal &&
|
|
300
|
+
!report.diagnostics.some((d) => d.effect === "blocking")
|
|
301
|
+
) {
|
|
302
|
+
report.refusal = refusal !== NOT_CONFIGURED;
|
|
303
|
+
report.diagnose(cause ?? "internal_error", refusal);
|
|
304
|
+
}
|
|
305
|
+
const result = report.build(
|
|
306
|
+
"jev_check_diff",
|
|
307
|
+
evidenceContext,
|
|
308
|
+
reportMetrics(envelope),
|
|
309
|
+
);
|
|
214
310
|
runtime.session.record(envelope);
|
|
215
311
|
runtime.guide.deliver(ctx);
|
|
216
312
|
return {
|
|
217
|
-
content: [
|
|
313
|
+
content: [
|
|
314
|
+
{
|
|
315
|
+
type: "text" as const,
|
|
316
|
+
text: renderResultReport(result, { details: envelope }),
|
|
317
|
+
},
|
|
318
|
+
],
|
|
218
319
|
details: {
|
|
320
|
+
evidenceContext,
|
|
321
|
+
result,
|
|
219
322
|
ok: !refusal,
|
|
220
323
|
envelope,
|
|
221
324
|
judgments,
|
|
@@ -224,9 +327,11 @@ export function createCheckDiffTool(dependencies: ToolDependencies) {
|
|
|
224
327
|
...hostUsage(host.isOmp, usage),
|
|
225
328
|
};
|
|
226
329
|
};
|
|
227
|
-
if (!client) return finish(NOT_CONFIGURED);
|
|
330
|
+
if (!client) return finish(NOT_CONFIGURED, "not_configured");
|
|
228
331
|
const comparison = await resolveBase(exec, cwd, args.base, signal);
|
|
229
|
-
if (!comparison.ok)
|
|
332
|
+
if (!comparison.ok)
|
|
333
|
+
return finish(comparison.error, comparison.cause ?? "invalid_base");
|
|
334
|
+
evidenceContext.resolvedBase = comparison.base;
|
|
230
335
|
const base = comparison.base;
|
|
231
336
|
const analysis = await createAnalysisContext();
|
|
232
337
|
const collected = await collectUnits(
|
|
@@ -238,7 +343,41 @@ export function createCheckDiffTool(dependencies: ToolDependencies) {
|
|
|
238
343
|
},
|
|
239
344
|
analysis.parser,
|
|
240
345
|
);
|
|
241
|
-
if (!collected.ok)
|
|
346
|
+
if (!collected.ok)
|
|
347
|
+
return finish(collected.error, collected.cause ?? "git_failure");
|
|
348
|
+
const repositoryRoot = await exec(
|
|
349
|
+
"git",
|
|
350
|
+
["rev-parse", "--show-toplevel"],
|
|
351
|
+
{ cwd, timeout: TIMEOUT_MS, signal },
|
|
352
|
+
);
|
|
353
|
+
if (
|
|
354
|
+
!repositoryRoot.code &&
|
|
355
|
+
!repositoryRoot.killed &&
|
|
356
|
+
evidenceContext.effectiveRoot
|
|
357
|
+
)
|
|
358
|
+
evidenceContext.effectiveRoot.path = repositoryRoot.stdout.trim();
|
|
359
|
+
report.inventories.push({
|
|
360
|
+
id: "changed-units",
|
|
361
|
+
kind: "units",
|
|
362
|
+
rules: [
|
|
363
|
+
"Changed source units against resolved base; tests are evidence, not risk units",
|
|
364
|
+
],
|
|
365
|
+
restrictions: args.only ?? [],
|
|
366
|
+
discovered: known(collected.units.length),
|
|
367
|
+
considered: known(collected.units.length),
|
|
368
|
+
scopeRestricted: Boolean(args.only?.length),
|
|
369
|
+
criteria: [],
|
|
370
|
+
});
|
|
371
|
+
for (const unit of collected.units)
|
|
372
|
+
report.expect(`unit:${unit.id}`, unitLabel(unit), "unit");
|
|
373
|
+
for (const limit of collected.limits)
|
|
374
|
+
report.diagnose(
|
|
375
|
+
limit.kind === "secret_pattern"
|
|
376
|
+
? "secret_pattern"
|
|
377
|
+
: "collection_omitted",
|
|
378
|
+
`${limit.file}: ${limit.kind}`,
|
|
379
|
+
limit.file,
|
|
380
|
+
);
|
|
242
381
|
for (const limit of collected.limits)
|
|
243
382
|
limitations.push({
|
|
244
383
|
fact: `${limit.file}: ${limit.kind}`,
|
|
@@ -247,6 +386,12 @@ export function createCheckDiffTool(dependencies: ToolDependencies) {
|
|
|
247
386
|
const readableUnits = collected.units.filter((unit) => {
|
|
248
387
|
if (unit.before !== null || unit.after !== null) return true;
|
|
249
388
|
unchecked.push(`${unitLabel(unit)} source unavailable`);
|
|
389
|
+
report.diagnose(
|
|
390
|
+
"binary_or_non_utf8",
|
|
391
|
+
"Changed source unavailable",
|
|
392
|
+
unit.file,
|
|
393
|
+
[`unit:${unit.id}`],
|
|
394
|
+
);
|
|
250
395
|
return false;
|
|
251
396
|
});
|
|
252
397
|
if (!collected.units.length) {
|
|
@@ -258,22 +403,43 @@ export function createCheckDiffTool(dependencies: ToolDependencies) {
|
|
|
258
403
|
...args,
|
|
259
404
|
testFilesPresent: collected.files.some((file) => isTestFile(file.path)),
|
|
260
405
|
});
|
|
261
|
-
if (!matrix.ok) return finish(matrix.error);
|
|
406
|
+
if (!matrix.ok) return finish(matrix.error, "invalid_arguments");
|
|
262
407
|
const prepared = matrix;
|
|
263
|
-
|
|
408
|
+
prepared.state = withEvidenceContext(prepared.state, evidenceContext);
|
|
409
|
+
for (const unit of readableUnits) report.items.delete(`unit:${unit.id}`);
|
|
410
|
+
for (const cell of prepared.cells)
|
|
411
|
+
report.expect(
|
|
412
|
+
`risk:${cell.id}`,
|
|
413
|
+
`${cell.unitId} ${cell.dimension}`,
|
|
414
|
+
"unit",
|
|
415
|
+
`risk:${cell.unitId}`,
|
|
416
|
+
);
|
|
417
|
+
for (const warning of prepared.warnings) {
|
|
418
|
+
limitations.push(warning);
|
|
419
|
+
report.diagnose(
|
|
420
|
+
"unsupported_syntax",
|
|
421
|
+
warning.fact,
|
|
422
|
+
undefined,
|
|
423
|
+
[],
|
|
424
|
+
false,
|
|
425
|
+
);
|
|
426
|
+
}
|
|
264
427
|
if (JSON.stringify(prepared.state).length > STATE_MAX_CHARS)
|
|
265
428
|
return finish(
|
|
266
429
|
`risk state exceeds STATE_MAX_CHARS=${STATE_MAX_CHARS}; rerun with base= a nearer ref`,
|
|
430
|
+
"evidence_too_large",
|
|
267
431
|
);
|
|
268
432
|
const batches = prepareBatches(prepared.state, prepared.questions, {
|
|
269
433
|
groups: prepared.groups,
|
|
270
434
|
witnesses: prepared.witnessQuestionIds,
|
|
271
435
|
});
|
|
272
|
-
if (!batches.ok) return finish(batches.error);
|
|
436
|
+
if (!batches.ok) return finish(batches.error, "group_too_large");
|
|
273
437
|
let reservedMatrixCalls = batches.batches.length;
|
|
274
438
|
const optionsFor = (matrixRequest = false): JudgmentOptions => ({
|
|
275
439
|
signal,
|
|
276
440
|
...runtime.session.requestGate(),
|
|
441
|
+
admissionCause: () =>
|
|
442
|
+
budget?.kind === "session" ? "session_budget" : "call_budget",
|
|
277
443
|
beforeRequest(questionCount) {
|
|
278
444
|
if (matrixRequest && reservedMatrixCalls > 0) reservedMatrixCalls--;
|
|
279
445
|
const limit =
|
|
@@ -303,6 +469,12 @@ export function createCheckDiffTool(dependencies: ToolDependencies) {
|
|
|
303
469
|
extra?: JudgmentOptions,
|
|
304
470
|
matrixRequest = false,
|
|
305
471
|
) => {
|
|
472
|
+
state = withEvidenceContext(state, evidenceContext);
|
|
473
|
+
if (JSON.stringify(state).length > STATE_MAX_CHARS)
|
|
474
|
+
return {
|
|
475
|
+
ok: false as const,
|
|
476
|
+
error: `required evidence exceeds STATE_MAX_CHARS=${STATE_MAX_CHARS}`,
|
|
477
|
+
};
|
|
306
478
|
const result = await client.judge(state, questions, {
|
|
307
479
|
...optionsFor(matrixRequest),
|
|
308
480
|
...extra,
|
|
@@ -323,6 +495,23 @@ export function createCheckDiffTool(dependencies: ToolDependencies) {
|
|
|
323
495
|
: { proofs: [], limits: [] };
|
|
324
496
|
const uncheckedCallers = new Set<string>();
|
|
325
497
|
for (const limit of local.limits) {
|
|
498
|
+
if (limit.kind !== "dynamic_access_uncovered") {
|
|
499
|
+
const id = `caller:${limit.unitId}`;
|
|
500
|
+
report.expect(id, `${limit.unitId} local caller`, "unit");
|
|
501
|
+
report.diagnose(
|
|
502
|
+
"collection_omitted",
|
|
503
|
+
limit.reason,
|
|
504
|
+
limit.paths.join(" + "),
|
|
505
|
+
[id],
|
|
506
|
+
);
|
|
507
|
+
} else
|
|
508
|
+
report.diagnose(
|
|
509
|
+
"dynamic_dependency",
|
|
510
|
+
limit.reason,
|
|
511
|
+
limit.paths.join(" + "),
|
|
512
|
+
[],
|
|
513
|
+
false,
|
|
514
|
+
);
|
|
326
515
|
limitations.push({
|
|
327
516
|
fact: `${limit.unitId}: ${limit.reason} — ${limit.paths.join(" + ")}`,
|
|
328
517
|
next: "read the named caller/provider pieces or supply an observation via jev_ask",
|
|
@@ -368,6 +557,53 @@ export function createCheckDiffTool(dependencies: ToolDependencies) {
|
|
|
368
557
|
unhealthyQuestionIds: new Map<string, string>(),
|
|
369
558
|
};
|
|
370
559
|
matrixHealthy = health.healthy;
|
|
560
|
+
for (const failure of health.controls)
|
|
561
|
+
report.diagnose(
|
|
562
|
+
health.healthy ? "conservative_widening" : "control_failure",
|
|
563
|
+
failure.fact,
|
|
564
|
+
undefined,
|
|
565
|
+
prepared.cells
|
|
566
|
+
.filter((cell) => health.unhealthyQuestionIds.has(cell.id))
|
|
567
|
+
.map((cell) => `risk:${cell.id}`),
|
|
568
|
+
false,
|
|
569
|
+
);
|
|
570
|
+
report.countControls(result, prepared.witnessQuestionIds);
|
|
571
|
+
for (const cell of prepared.cells) {
|
|
572
|
+
const rawAnswer = result.ok ? result.answers[cell.id] : undefined;
|
|
573
|
+
const answer =
|
|
574
|
+
rawAnswer?.type === "unjudged" && !rawAnswer.cause && budget
|
|
575
|
+
? {
|
|
576
|
+
...rawAnswer,
|
|
577
|
+
cause:
|
|
578
|
+
budget.kind === "session"
|
|
579
|
+
? ("session_budget" as const)
|
|
580
|
+
: ("call_budget" as const),
|
|
581
|
+
}
|
|
582
|
+
: rawAnswer;
|
|
583
|
+
const invalid = health.unhealthyQuestionIds.get(cell.id);
|
|
584
|
+
if (!result.ok)
|
|
585
|
+
report.failure(
|
|
586
|
+
result,
|
|
587
|
+
[`risk:${cell.id}`],
|
|
588
|
+
budget
|
|
589
|
+
? budget.kind === "session"
|
|
590
|
+
? "session_budget"
|
|
591
|
+
: "call_budget"
|
|
592
|
+
: undefined,
|
|
593
|
+
);
|
|
594
|
+
else
|
|
595
|
+
report.answer(
|
|
596
|
+
`risk:${cell.id}`,
|
|
597
|
+
answer,
|
|
598
|
+
{
|
|
599
|
+
band: invalid ? "unsure" : "verdict",
|
|
600
|
+
reason: invalid,
|
|
601
|
+
uncalibrated: cell.uncalibrated,
|
|
602
|
+
},
|
|
603
|
+
controlsFor(cell.id, result, prepared.witnessQuestionIds),
|
|
604
|
+
false,
|
|
605
|
+
);
|
|
606
|
+
}
|
|
371
607
|
const decoys = new Set(
|
|
372
608
|
prepared.witnesses
|
|
373
609
|
.filter((witness) => witness.expected === "no")
|
|
@@ -428,16 +664,36 @@ export function createCheckDiffTool(dependencies: ToolDependencies) {
|
|
|
428
664
|
for (const { proof, result: localResult } of locals) {
|
|
429
665
|
callerProofs.push(proof);
|
|
430
666
|
const unit = collected.units.find((u) => u.id === proof.unitId);
|
|
431
|
-
|
|
432
|
-
|
|
667
|
+
const reportId = `caller:${proof.unitId}`;
|
|
668
|
+
report.expect(
|
|
669
|
+
reportId,
|
|
670
|
+
`${unit ? unitLabel(unit) : proof.unitId} local caller`,
|
|
671
|
+
"unit",
|
|
672
|
+
);
|
|
673
|
+
if (!localResult.ok) {
|
|
674
|
+
report.failure(
|
|
675
|
+
localResult,
|
|
676
|
+
[reportId],
|
|
677
|
+
budget
|
|
678
|
+
? budget.kind === "session"
|
|
679
|
+
? "session_budget"
|
|
680
|
+
: "call_budget"
|
|
681
|
+
: undefined,
|
|
682
|
+
);
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
const answer = localResult.answers.caller;
|
|
433
686
|
if (answer?.type !== "choice") {
|
|
434
|
-
|
|
687
|
+
report.answer(reportId, answer);
|
|
688
|
+
unchecked.push(
|
|
689
|
+
`${unit ? unitLabel(unit) : proof.unitId} local caller`,
|
|
690
|
+
);
|
|
435
691
|
continue;
|
|
436
692
|
}
|
|
437
693
|
const p = answer.probabilities.new_failure ?? 0;
|
|
438
694
|
const missing =
|
|
439
695
|
(answer.probabilities.cannot_tell ?? 0) >= CANNOT_TELL_MIN;
|
|
440
|
-
|
|
696
|
+
const negative = !missing && p < BAND_BOOL_GRAY_A;
|
|
441
697
|
const merged: EvidenceSpan[] = [];
|
|
442
698
|
for (const span of [...proof.paths].sort(
|
|
443
699
|
(a, b) =>
|
|
@@ -458,15 +714,45 @@ export function createCheckDiffTool(dependencies: ToolDependencies) {
|
|
|
458
714
|
.map((span) => `${span.path}:${span.start}-${span.end}`)
|
|
459
715
|
.join(" + ");
|
|
460
716
|
const line: AnswerInput = {
|
|
461
|
-
label: `${unitLabel(unit)} reliability — static code only; ${passages}${omitted ? ` (+${omitted} passages)` : ""}`,
|
|
717
|
+
label: `${unit ? unitLabel(unit) : proof.unitId} reliability — static code only; ${passages}${omitted ? ` (+${omitted} passages)` : ""}`,
|
|
462
718
|
value: {
|
|
463
|
-
head: missing
|
|
464
|
-
|
|
719
|
+
head: missing
|
|
720
|
+
? "cannot_tell"
|
|
721
|
+
: negative
|
|
722
|
+
? "no_new_failure"
|
|
723
|
+
: "new_failure",
|
|
724
|
+
p: missing
|
|
725
|
+
? (answer.probabilities.cannot_tell ?? 0)
|
|
726
|
+
: negative
|
|
727
|
+
? (answer.probabilities.no_new_failure ?? 0)
|
|
728
|
+
: p,
|
|
465
729
|
},
|
|
466
|
-
band: missing
|
|
467
|
-
|
|
468
|
-
|
|
730
|
+
band: missing
|
|
731
|
+
? "abstain"
|
|
732
|
+
: negative || p >= FLAG_MIN
|
|
733
|
+
? "verdict"
|
|
734
|
+
: "unsure",
|
|
735
|
+
reason: missing
|
|
736
|
+
? "actual provider or binding missing; supply its declaration or an observation via jev_ask"
|
|
737
|
+
: negative
|
|
738
|
+
? "Local caller check does not flag a new failure; static evidence does not prove every caller safe."
|
|
739
|
+
: p < FLAG_MIN
|
|
740
|
+
? "Local caller new-failure probability is in the gray band; inspect the stated proof passages natively."
|
|
741
|
+
: "Local caller new-failure probability meets the finding threshold; static code evidence only.",
|
|
469
742
|
};
|
|
743
|
+
const callerItem = report.items.get(reportId);
|
|
744
|
+
if (callerItem)
|
|
745
|
+
report.items.set(reportId, { ...callerItem, label: line.label });
|
|
746
|
+
report.answer(reportId, answer, line);
|
|
747
|
+
if (missing && line.reason)
|
|
748
|
+
report.diagnose(
|
|
749
|
+
"missing_required",
|
|
750
|
+
line.reason,
|
|
751
|
+
line.label,
|
|
752
|
+
[reportId],
|
|
753
|
+
false,
|
|
754
|
+
);
|
|
755
|
+
if (!unit || negative) continue;
|
|
470
756
|
answers.push(line);
|
|
471
757
|
unitOrder.set(line, unit.id);
|
|
472
758
|
if (!missing && p >= FLAG_MIN) {
|
|
@@ -494,6 +780,23 @@ export function createCheckDiffTool(dependencies: ToolDependencies) {
|
|
|
494
780
|
item.evidence?.callerEvidence,
|
|
495
781
|
);
|
|
496
782
|
const value = await judge(request.state, request.questions);
|
|
783
|
+
const reportId = `severity:${item.unit.id}:${item.dimension}`;
|
|
784
|
+
report.expect(
|
|
785
|
+
reportId,
|
|
786
|
+
`${item.unit.id} ${item.dimension} severity`,
|
|
787
|
+
"unit",
|
|
788
|
+
);
|
|
789
|
+
if (value.ok) report.answer(reportId, value.answers.severity);
|
|
790
|
+
else
|
|
791
|
+
report.failure(
|
|
792
|
+
value,
|
|
793
|
+
[reportId],
|
|
794
|
+
budget
|
|
795
|
+
? budget.kind === "session"
|
|
796
|
+
? "session_budget"
|
|
797
|
+
: "call_budget"
|
|
798
|
+
: undefined,
|
|
799
|
+
);
|
|
497
800
|
const answer = value.ok ? value.answers.severity : undefined;
|
|
498
801
|
if (answer?.type === "score") {
|
|
499
802
|
const suffix = ` · severity ${answer.score.toFixed(1)}/3`;
|