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/dist/tools/check-diff.js
CHANGED
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { Type } from "@sinclair/typebox";
|
|
3
3
|
import { createAnalysisContext } from "../adapters/analysis-context.js";
|
|
4
|
-
import {
|
|
4
|
+
import { resolveEvidenceContext, withEvidenceContext, } from "../adapters/evidence-context.js";
|
|
5
5
|
import { collectUnits } from "../adapters/git.js";
|
|
6
6
|
import { resolveBase } from "../adapters/git-base.js";
|
|
7
7
|
import { shareGitInventory } from "../adapters/git-inventory.js";
|
|
8
8
|
import { collectRiskCallers } from "../adapters/risk-callers.js";
|
|
9
9
|
import { hostUsage } from "../adapters/usage.js";
|
|
10
|
-
import { BAND_BOOL_GRAY_A, CALLER_DISPLAY_MAX_SPANS, CANNOT_TELL_MIN, FLAG_MIN, STATE_MAX_CHARS, } from "../constants.js";
|
|
10
|
+
import { BAND_BOOL_GRAY_A, CALLER_DISPLAY_MAX_SPANS, CANNOT_TELL_MIN, FLAG_MIN, STATE_MAX_CHARS, TIMEOUT_MS, } from "../constants.js";
|
|
11
11
|
import { prepareBatches } from "../core/batches.js";
|
|
12
12
|
import { isTestFile } from "../core/diff.js";
|
|
13
13
|
import { buildEnvelope, } from "../core/output.js";
|
|
14
|
+
import { known, } from "../core/result-report.js";
|
|
14
15
|
import { evaluateWitnessHealth, prepareRiskMatrix, prepareRiskSeverity, } from "../presets/risk.js";
|
|
15
|
-
import {
|
|
16
|
+
import { renderResultReport } from "../render.js";
|
|
16
17
|
import { CHECK_DIFF_DESCRIPTION } from "../texts/check-diff.js";
|
|
17
18
|
import { NOT_CONFIGURED } from "../texts/configuration.js";
|
|
19
|
+
import { CHECK_DIFF_GUIDELINE } from "../texts/instructions.js";
|
|
18
20
|
import { runDocsCheck } from "./docs-check.js";
|
|
21
|
+
import { controlsFor, ReviewReport, reportMetrics } from "./review-report.js";
|
|
19
22
|
import { runSpecCheck } from "./spec-check.js";
|
|
20
23
|
export const checkDiffParameters = Type.Object({
|
|
24
|
+
root: Type.Optional(Type.String()),
|
|
21
25
|
check: Type.Union([
|
|
22
26
|
Type.Literal("risk"),
|
|
23
27
|
Type.Literal("docs"),
|
|
@@ -53,14 +57,53 @@ export function createCheckDiffTool(dependencies) {
|
|
|
53
57
|
? { approval: "read", loadMode: "essential" }
|
|
54
58
|
: {
|
|
55
59
|
promptSnippet: "Check the uncommitted diff for risky changes, stale docs or spec drift",
|
|
56
|
-
promptGuidelines: [
|
|
57
|
-
"jev_check_diff: run it once before you report done, not on a half-written diff",
|
|
58
|
-
],
|
|
60
|
+
promptGuidelines: [CHECK_DIFF_GUIDELINE],
|
|
59
61
|
}),
|
|
60
62
|
async execute(_id, args, signal, _update, ctx) {
|
|
61
63
|
const client = dependencies.client;
|
|
62
|
-
const
|
|
64
|
+
const evidence = await resolveEvidenceContext(ctx.cwd, args.root, {
|
|
65
|
+
exec: execute,
|
|
66
|
+
signal,
|
|
67
|
+
origin: dependencies.evidenceOrigin,
|
|
68
|
+
});
|
|
69
|
+
const evidenceContext = evidence.context;
|
|
70
|
+
const report = new ReviewReport();
|
|
71
|
+
const cwd = evidence.ok ? evidence.cwd : evidenceContext.authority.path;
|
|
63
72
|
const exec = shareGitInventory(execute);
|
|
73
|
+
if (!evidence.ok) {
|
|
74
|
+
const envelope = buildEnvelope({
|
|
75
|
+
refusal: evidence.error,
|
|
76
|
+
yield: {
|
|
77
|
+
calls: 0,
|
|
78
|
+
questions: 0,
|
|
79
|
+
cacheHits: 0,
|
|
80
|
+
cacheRequests: 0,
|
|
81
|
+
elapsedMs: 0,
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
runtime.session.record(envelope);
|
|
85
|
+
runtime.guide.deliver(ctx);
|
|
86
|
+
report.refusal = true;
|
|
87
|
+
report.diagnose(evidence.cause, evidence.error, args.root);
|
|
88
|
+
const result = report.build("jev_check_diff", evidenceContext, reportMetrics(envelope));
|
|
89
|
+
return {
|
|
90
|
+
content: [
|
|
91
|
+
{
|
|
92
|
+
type: "text",
|
|
93
|
+
text: renderResultReport(result, { details: envelope }),
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
details: {
|
|
97
|
+
ok: false,
|
|
98
|
+
envelope,
|
|
99
|
+
judgments: [],
|
|
100
|
+
callerProofs: [],
|
|
101
|
+
evidenceContext,
|
|
102
|
+
result,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
evidenceContext.requestedBase = args.base ?? "HEAD";
|
|
64
107
|
if (args.check === "docs" || args.check === "spec") {
|
|
65
108
|
const deps = { client, host, runtime, exec };
|
|
66
109
|
const result = args.check === "docs"
|
|
@@ -69,6 +112,7 @@ export function createCheckDiffTool(dependencies) {
|
|
|
69
112
|
base: args.base,
|
|
70
113
|
signal,
|
|
71
114
|
maxCalls: args.max_calls,
|
|
115
|
+
evidenceContext,
|
|
72
116
|
})
|
|
73
117
|
: await runSpecCheck(deps, {
|
|
74
118
|
cwd: cwd,
|
|
@@ -76,6 +120,7 @@ export function createCheckDiffTool(dependencies) {
|
|
|
76
120
|
specPath: args.spec_path,
|
|
77
121
|
signal,
|
|
78
122
|
maxCalls: args.max_calls,
|
|
123
|
+
evidenceContext,
|
|
79
124
|
});
|
|
80
125
|
runtime.session.record(result.envelope);
|
|
81
126
|
runtime.guide.deliver(ctx);
|
|
@@ -85,9 +130,14 @@ export function createCheckDiffTool(dependencies) {
|
|
|
85
130
|
}), { inputTokens: 0, costUsd: 0 });
|
|
86
131
|
return {
|
|
87
132
|
content: [
|
|
88
|
-
{
|
|
133
|
+
{
|
|
134
|
+
type: "text",
|
|
135
|
+
text: renderResultReport(result.result, {
|
|
136
|
+
details: result.envelope,
|
|
137
|
+
}),
|
|
138
|
+
},
|
|
89
139
|
],
|
|
90
|
-
details: { ...result, callerProofs: [] },
|
|
140
|
+
details: { ...result, callerProofs: [], evidenceContext },
|
|
91
141
|
...hostUsage(host.isOmp, usage),
|
|
92
142
|
};
|
|
93
143
|
}
|
|
@@ -102,7 +152,7 @@ export function createCheckDiffTool(dependencies) {
|
|
|
102
152
|
let sent = 0;
|
|
103
153
|
let matrixHealthy = true;
|
|
104
154
|
let emptyBase;
|
|
105
|
-
const finish = (refusal) => {
|
|
155
|
+
const finish = (refusal, cause) => {
|
|
106
156
|
answers.sort((a, b) => (unitOrder.get(a) ?? "").localeCompare(unitOrder.get(b) ?? ""));
|
|
107
157
|
const uniqueLimitations = [
|
|
108
158
|
...new Map(limitations.map((limit) => [`${limit.fact}\0${limit.next}`, limit])).values(),
|
|
@@ -111,7 +161,12 @@ export function createCheckDiffTool(dependencies) {
|
|
|
111
161
|
inputTokens: sum.inputTokens + (value.usage?.inputTokens ?? 0),
|
|
112
162
|
costUsd: sum.costUsd + (value.usage?.costUsd ?? 0),
|
|
113
163
|
}), { inputTokens: 0, costUsd: 0 });
|
|
114
|
-
const noFindings = !refusal &&
|
|
164
|
+
const noFindings = !refusal &&
|
|
165
|
+
matrixHealthy &&
|
|
166
|
+
!answers.length &&
|
|
167
|
+
!unchecked.length &&
|
|
168
|
+
report.items.size > 0 &&
|
|
169
|
+
[...report.items.values()].every((item) => item.treatment === "judged");
|
|
115
170
|
const envelope = buildEnvelope({
|
|
116
171
|
answers,
|
|
117
172
|
limitations: emptyBase !== undefined && !refusal
|
|
@@ -140,17 +195,35 @@ export function createCheckDiffTool(dependencies) {
|
|
|
140
195
|
yield: {
|
|
141
196
|
calls: judgments.reduce((n, j) => n + (j.calls ?? 0), 0),
|
|
142
197
|
questions: judgments.reduce((n, j) => n + (j.questions ?? 0), 0),
|
|
143
|
-
costUsd: usage
|
|
198
|
+
costUsd: judgments.every((j) => j.usage !== undefined) &&
|
|
199
|
+
judgments.length > 0
|
|
200
|
+
? usage.costUsd
|
|
201
|
+
: undefined,
|
|
144
202
|
cacheHits: judgments.reduce((n, j) => n + (j.cacheHits ?? 0), 0),
|
|
145
203
|
cacheRequests: judgments.reduce((n, j) => n + (j.cacheRequests ?? 0), 0),
|
|
146
204
|
elapsedMs: performance.now() - started,
|
|
147
205
|
},
|
|
148
206
|
});
|
|
207
|
+
if (emptyBase !== undefined)
|
|
208
|
+
report.diagnose("no_changed_units", `No changed units against ${emptyBase}; no risk judgment requested.`, emptyBase, [], false);
|
|
209
|
+
if (refusal &&
|
|
210
|
+
!report.diagnostics.some((d) => d.effect === "blocking")) {
|
|
211
|
+
report.refusal = refusal !== NOT_CONFIGURED;
|
|
212
|
+
report.diagnose(cause ?? "internal_error", refusal);
|
|
213
|
+
}
|
|
214
|
+
const result = report.build("jev_check_diff", evidenceContext, reportMetrics(envelope));
|
|
149
215
|
runtime.session.record(envelope);
|
|
150
216
|
runtime.guide.deliver(ctx);
|
|
151
217
|
return {
|
|
152
|
-
content: [
|
|
218
|
+
content: [
|
|
219
|
+
{
|
|
220
|
+
type: "text",
|
|
221
|
+
text: renderResultReport(result, { details: envelope }),
|
|
222
|
+
},
|
|
223
|
+
],
|
|
153
224
|
details: {
|
|
225
|
+
evidenceContext,
|
|
226
|
+
result,
|
|
154
227
|
ok: !refusal,
|
|
155
228
|
envelope,
|
|
156
229
|
judgments,
|
|
@@ -160,10 +233,11 @@ export function createCheckDiffTool(dependencies) {
|
|
|
160
233
|
};
|
|
161
234
|
};
|
|
162
235
|
if (!client)
|
|
163
|
-
return finish(NOT_CONFIGURED);
|
|
236
|
+
return finish(NOT_CONFIGURED, "not_configured");
|
|
164
237
|
const comparison = await resolveBase(exec, cwd, args.base, signal);
|
|
165
238
|
if (!comparison.ok)
|
|
166
|
-
return finish(comparison.error);
|
|
239
|
+
return finish(comparison.error, comparison.cause ?? "invalid_base");
|
|
240
|
+
evidenceContext.resolvedBase = comparison.base;
|
|
167
241
|
const base = comparison.base;
|
|
168
242
|
const analysis = await createAnalysisContext();
|
|
169
243
|
const collected = await collectUnits(exec, {
|
|
@@ -172,7 +246,30 @@ export function createCheckDiffTool(dependencies) {
|
|
|
172
246
|
signal,
|
|
173
247
|
}, analysis.parser);
|
|
174
248
|
if (!collected.ok)
|
|
175
|
-
return finish(collected.error);
|
|
249
|
+
return finish(collected.error, collected.cause ?? "git_failure");
|
|
250
|
+
const repositoryRoot = await exec("git", ["rev-parse", "--show-toplevel"], { cwd, timeout: TIMEOUT_MS, signal });
|
|
251
|
+
if (!repositoryRoot.code &&
|
|
252
|
+
!repositoryRoot.killed &&
|
|
253
|
+
evidenceContext.effectiveRoot)
|
|
254
|
+
evidenceContext.effectiveRoot.path = repositoryRoot.stdout.trim();
|
|
255
|
+
report.inventories.push({
|
|
256
|
+
id: "changed-units",
|
|
257
|
+
kind: "units",
|
|
258
|
+
rules: [
|
|
259
|
+
"Changed source units against resolved base; tests are evidence, not risk units",
|
|
260
|
+
],
|
|
261
|
+
restrictions: args.only ?? [],
|
|
262
|
+
discovered: known(collected.units.length),
|
|
263
|
+
considered: known(collected.units.length),
|
|
264
|
+
scopeRestricted: Boolean(args.only?.length),
|
|
265
|
+
criteria: [],
|
|
266
|
+
});
|
|
267
|
+
for (const unit of collected.units)
|
|
268
|
+
report.expect(`unit:${unit.id}`, unitLabel(unit), "unit");
|
|
269
|
+
for (const limit of collected.limits)
|
|
270
|
+
report.diagnose(limit.kind === "secret_pattern"
|
|
271
|
+
? "secret_pattern"
|
|
272
|
+
: "collection_omitted", `${limit.file}: ${limit.kind}`, limit.file);
|
|
176
273
|
for (const limit of collected.limits)
|
|
177
274
|
limitations.push({
|
|
178
275
|
fact: `${limit.file}: ${limit.kind}`,
|
|
@@ -182,6 +279,7 @@ export function createCheckDiffTool(dependencies) {
|
|
|
182
279
|
if (unit.before !== null || unit.after !== null)
|
|
183
280
|
return true;
|
|
184
281
|
unchecked.push(`${unitLabel(unit)} source unavailable`);
|
|
282
|
+
report.diagnose("binary_or_non_utf8", "Changed source unavailable", unit.file, [`unit:${unit.id}`]);
|
|
185
283
|
return false;
|
|
186
284
|
});
|
|
187
285
|
if (!collected.units.length) {
|
|
@@ -195,22 +293,30 @@ export function createCheckDiffTool(dependencies) {
|
|
|
195
293
|
testFilesPresent: collected.files.some((file) => isTestFile(file.path)),
|
|
196
294
|
});
|
|
197
295
|
if (!matrix.ok)
|
|
198
|
-
return finish(matrix.error);
|
|
296
|
+
return finish(matrix.error, "invalid_arguments");
|
|
199
297
|
const prepared = matrix;
|
|
200
|
-
|
|
298
|
+
prepared.state = withEvidenceContext(prepared.state, evidenceContext);
|
|
299
|
+
for (const unit of readableUnits)
|
|
300
|
+
report.items.delete(`unit:${unit.id}`);
|
|
301
|
+
for (const cell of prepared.cells)
|
|
302
|
+
report.expect(`risk:${cell.id}`, `${cell.unitId} ${cell.dimension}`, "unit", `risk:${cell.unitId}`);
|
|
303
|
+
for (const warning of prepared.warnings) {
|
|
201
304
|
limitations.push(warning);
|
|
305
|
+
report.diagnose("unsupported_syntax", warning.fact, undefined, [], false);
|
|
306
|
+
}
|
|
202
307
|
if (JSON.stringify(prepared.state).length > STATE_MAX_CHARS)
|
|
203
|
-
return finish(`risk state exceeds STATE_MAX_CHARS=${STATE_MAX_CHARS}; rerun with base= a nearer ref
|
|
308
|
+
return finish(`risk state exceeds STATE_MAX_CHARS=${STATE_MAX_CHARS}; rerun with base= a nearer ref`, "evidence_too_large");
|
|
204
309
|
const batches = prepareBatches(prepared.state, prepared.questions, {
|
|
205
310
|
groups: prepared.groups,
|
|
206
311
|
witnesses: prepared.witnessQuestionIds,
|
|
207
312
|
});
|
|
208
313
|
if (!batches.ok)
|
|
209
|
-
return finish(batches.error);
|
|
314
|
+
return finish(batches.error, "group_too_large");
|
|
210
315
|
let reservedMatrixCalls = batches.batches.length;
|
|
211
316
|
const optionsFor = (matrixRequest = false) => ({
|
|
212
317
|
signal,
|
|
213
318
|
...runtime.session.requestGate(),
|
|
319
|
+
admissionCause: () => budget?.kind === "session" ? "session_budget" : "call_budget",
|
|
214
320
|
beforeRequest(questionCount) {
|
|
215
321
|
if (matrixRequest && reservedMatrixCalls > 0)
|
|
216
322
|
reservedMatrixCalls--;
|
|
@@ -235,6 +341,12 @@ export function createCheckDiffTool(dependencies) {
|
|
|
235
341
|
onUsage: (usage) => runtime.session.recordUsage(usage),
|
|
236
342
|
});
|
|
237
343
|
const judge = async (state, questions, extra, matrixRequest = false) => {
|
|
344
|
+
state = withEvidenceContext(state, evidenceContext);
|
|
345
|
+
if (JSON.stringify(state).length > STATE_MAX_CHARS)
|
|
346
|
+
return {
|
|
347
|
+
ok: false,
|
|
348
|
+
error: `required evidence exceeds STATE_MAX_CHARS=${STATE_MAX_CHARS}`,
|
|
349
|
+
};
|
|
238
350
|
const result = await client.judge(state, questions, {
|
|
239
351
|
...optionsFor(matrixRequest),
|
|
240
352
|
...extra,
|
|
@@ -247,6 +359,13 @@ export function createCheckDiffTool(dependencies) {
|
|
|
247
359
|
: { proofs: [], limits: [] };
|
|
248
360
|
const uncheckedCallers = new Set();
|
|
249
361
|
for (const limit of local.limits) {
|
|
362
|
+
if (limit.kind !== "dynamic_access_uncovered") {
|
|
363
|
+
const id = `caller:${limit.unitId}`;
|
|
364
|
+
report.expect(id, `${limit.unitId} local caller`, "unit");
|
|
365
|
+
report.diagnose("collection_omitted", limit.reason, limit.paths.join(" + "), [id]);
|
|
366
|
+
}
|
|
367
|
+
else
|
|
368
|
+
report.diagnose("dynamic_dependency", limit.reason, limit.paths.join(" + "), [], false);
|
|
250
369
|
limitations.push({
|
|
251
370
|
fact: `${limit.unitId}: ${limit.reason} — ${limit.paths.join(" + ")}`,
|
|
252
371
|
next: "read the named caller/provider pieces or supply an observation via jev_ask",
|
|
@@ -281,6 +400,35 @@ export function createCheckDiffTool(dependencies) {
|
|
|
281
400
|
unhealthyQuestionIds: new Map(),
|
|
282
401
|
};
|
|
283
402
|
matrixHealthy = health.healthy;
|
|
403
|
+
for (const failure of health.controls)
|
|
404
|
+
report.diagnose(health.healthy ? "conservative_widening" : "control_failure", failure.fact, undefined, prepared.cells
|
|
405
|
+
.filter((cell) => health.unhealthyQuestionIds.has(cell.id))
|
|
406
|
+
.map((cell) => `risk:${cell.id}`), false);
|
|
407
|
+
report.countControls(result, prepared.witnessQuestionIds);
|
|
408
|
+
for (const cell of prepared.cells) {
|
|
409
|
+
const rawAnswer = result.ok ? result.answers[cell.id] : undefined;
|
|
410
|
+
const answer = rawAnswer?.type === "unjudged" && !rawAnswer.cause && budget
|
|
411
|
+
? {
|
|
412
|
+
...rawAnswer,
|
|
413
|
+
cause: budget.kind === "session"
|
|
414
|
+
? "session_budget"
|
|
415
|
+
: "call_budget",
|
|
416
|
+
}
|
|
417
|
+
: rawAnswer;
|
|
418
|
+
const invalid = health.unhealthyQuestionIds.get(cell.id);
|
|
419
|
+
if (!result.ok)
|
|
420
|
+
report.failure(result, [`risk:${cell.id}`], budget
|
|
421
|
+
? budget.kind === "session"
|
|
422
|
+
? "session_budget"
|
|
423
|
+
: "call_budget"
|
|
424
|
+
: undefined);
|
|
425
|
+
else
|
|
426
|
+
report.answer(`risk:${cell.id}`, answer, {
|
|
427
|
+
band: invalid ? "unsure" : "verdict",
|
|
428
|
+
reason: invalid,
|
|
429
|
+
uncalibrated: cell.uncalibrated,
|
|
430
|
+
}, controlsFor(cell.id, result, prepared.witnessQuestionIds), false);
|
|
431
|
+
}
|
|
284
432
|
const decoys = new Set(prepared.witnesses
|
|
285
433
|
.filter((witness) => witness.expected === "no")
|
|
286
434
|
.map((witness) => witness.unitId)).size;
|
|
@@ -332,17 +480,25 @@ export function createCheckDiffTool(dependencies) {
|
|
|
332
480
|
for (const { proof, result: localResult } of locals) {
|
|
333
481
|
callerProofs.push(proof);
|
|
334
482
|
const unit = collected.units.find((u) => u.id === proof.unitId);
|
|
335
|
-
|
|
483
|
+
const reportId = `caller:${proof.unitId}`;
|
|
484
|
+
report.expect(reportId, `${unit ? unitLabel(unit) : proof.unitId} local caller`, "unit");
|
|
485
|
+
if (!localResult.ok) {
|
|
486
|
+
report.failure(localResult, [reportId], budget
|
|
487
|
+
? budget.kind === "session"
|
|
488
|
+
? "session_budget"
|
|
489
|
+
: "call_budget"
|
|
490
|
+
: undefined);
|
|
336
491
|
continue;
|
|
337
|
-
|
|
492
|
+
}
|
|
493
|
+
const answer = localResult.answers.caller;
|
|
338
494
|
if (answer?.type !== "choice") {
|
|
339
|
-
|
|
495
|
+
report.answer(reportId, answer);
|
|
496
|
+
unchecked.push(`${unit ? unitLabel(unit) : proof.unitId} local caller`);
|
|
340
497
|
continue;
|
|
341
498
|
}
|
|
342
499
|
const p = answer.probabilities.new_failure ?? 0;
|
|
343
500
|
const missing = (answer.probabilities.cannot_tell ?? 0) >= CANNOT_TELL_MIN;
|
|
344
|
-
|
|
345
|
-
continue;
|
|
501
|
+
const negative = !missing && p < BAND_BOOL_GRAY_A;
|
|
346
502
|
const merged = [];
|
|
347
503
|
for (const span of [...proof.paths].sort((a, b) => a.path.localeCompare(b.path) || a.start - b.start || a.end - b.end)) {
|
|
348
504
|
const previous = merged.at(-1);
|
|
@@ -359,14 +515,40 @@ export function createCheckDiffTool(dependencies) {
|
|
|
359
515
|
.map((span) => `${span.path}:${span.start}-${span.end}`)
|
|
360
516
|
.join(" + ");
|
|
361
517
|
const line = {
|
|
362
|
-
label: `${unitLabel(unit)} reliability — static code only; ${passages}${omitted ? ` (+${omitted} passages)` : ""}`,
|
|
518
|
+
label: `${unit ? unitLabel(unit) : proof.unitId} reliability — static code only; ${passages}${omitted ? ` (+${omitted} passages)` : ""}`,
|
|
363
519
|
value: {
|
|
364
|
-
head: missing
|
|
365
|
-
|
|
520
|
+
head: missing
|
|
521
|
+
? "cannot_tell"
|
|
522
|
+
: negative
|
|
523
|
+
? "no_new_failure"
|
|
524
|
+
: "new_failure",
|
|
525
|
+
p: missing
|
|
526
|
+
? (answer.probabilities.cannot_tell ?? 0)
|
|
527
|
+
: negative
|
|
528
|
+
? (answer.probabilities.no_new_failure ?? 0)
|
|
529
|
+
: p,
|
|
366
530
|
},
|
|
367
|
-
band: missing
|
|
368
|
-
|
|
531
|
+
band: missing
|
|
532
|
+
? "abstain"
|
|
533
|
+
: negative || p >= FLAG_MIN
|
|
534
|
+
? "verdict"
|
|
535
|
+
: "unsure",
|
|
536
|
+
reason: missing
|
|
537
|
+
? "actual provider or binding missing; supply its declaration or an observation via jev_ask"
|
|
538
|
+
: negative
|
|
539
|
+
? "Local caller check does not flag a new failure; static evidence does not prove every caller safe."
|
|
540
|
+
: p < FLAG_MIN
|
|
541
|
+
? "Local caller new-failure probability is in the gray band; inspect the stated proof passages natively."
|
|
542
|
+
: "Local caller new-failure probability meets the finding threshold; static code evidence only.",
|
|
369
543
|
};
|
|
544
|
+
const callerItem = report.items.get(reportId);
|
|
545
|
+
if (callerItem)
|
|
546
|
+
report.items.set(reportId, { ...callerItem, label: line.label });
|
|
547
|
+
report.answer(reportId, answer, line);
|
|
548
|
+
if (missing && line.reason)
|
|
549
|
+
report.diagnose("missing_required", line.reason, line.label, [reportId], false);
|
|
550
|
+
if (!unit || negative)
|
|
551
|
+
continue;
|
|
370
552
|
answers.push(line);
|
|
371
553
|
unitOrder.set(line, unit.id);
|
|
372
554
|
if (!missing && p >= FLAG_MIN) {
|
|
@@ -387,6 +569,16 @@ export function createCheckDiffTool(dependencies) {
|
|
|
387
569
|
await Promise.all(severity.map(async (item) => {
|
|
388
570
|
const request = prepareRiskSeverity(item.unit, item.dimension, item.evidence?.callerEvidence);
|
|
389
571
|
const value = await judge(request.state, request.questions);
|
|
572
|
+
const reportId = `severity:${item.unit.id}:${item.dimension}`;
|
|
573
|
+
report.expect(reportId, `${item.unit.id} ${item.dimension} severity`, "unit");
|
|
574
|
+
if (value.ok)
|
|
575
|
+
report.answer(reportId, value.answers.severity);
|
|
576
|
+
else
|
|
577
|
+
report.failure(value, [reportId], budget
|
|
578
|
+
? budget.kind === "session"
|
|
579
|
+
? "session_budget"
|
|
580
|
+
: "call_budget"
|
|
581
|
+
: undefined);
|
|
390
582
|
const answer = value.ok ? value.answers.severity : undefined;
|
|
391
583
|
if (answer?.type === "score") {
|
|
392
584
|
const suffix = ` · severity ${answer.score.toFixed(1)}/3`;
|