auditor-lambda 0.3.6 → 0.3.8
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 +27 -5
- package/audit-code-wrapper-lib.mjs +231 -44
- package/dist/cli.js +26 -0
- package/dist/io/runArtifacts.js +2 -2
- package/dist/orchestrator/internalExecutors.js +1 -0
- package/dist/orchestrator/selectiveDeepening.d.ts +4 -0
- package/dist/orchestrator/selectiveDeepening.js +359 -0
- package/dist/prompts/renderWorkerPrompt.js +3 -4
- package/dist/types.d.ts +9 -0
- package/dist/validation/auditResults.js +158 -0
- package/docs/agent-integrations.md +45 -11
- package/docs/bootstrap-install.md +27 -5
- package/docs/contract.md +3 -0
- package/docs/github-copilot.md +21 -5
- package/docs/next-steps.md +4 -2
- package/docs/packaging.md +6 -0
- package/docs/product-direction.md +20 -1
- package/docs/production-launch-bar.md +8 -3
- package/docs/production-readiness.md +4 -3
- package/docs/usage.md +22 -1
- package/package.json +1 -1
- package/schemas/audit_result.schema.json +28 -0
- package/scripts/postinstall.mjs +52 -15
- package/skills/audit-code/SKILL.md +19 -3
- package/skills/audit-code/audit-code.prompt.md +14 -1
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
const DEFAULT_MAX_DEEPENING_TASKS = 6;
|
|
3
3
|
const DEEPENING_TAG = "selective_deepening";
|
|
4
|
+
const LENS_VERIFICATION_TAG = "lens_verification";
|
|
5
|
+
const LENS_VERIFICATION_FOLLOWUP_TAG = "lens_verification_followup";
|
|
6
|
+
const MAX_LENS_VERIFICATION_FILES = 12;
|
|
7
|
+
const MAX_LENS_VERIFICATION_RESULT_SUMMARIES = 12;
|
|
8
|
+
const MAX_VERIFICATION_FOLLOWUP_TASKS_PER_RESULT = 4;
|
|
9
|
+
const IMPORTANT_LENS_VERIFICATION_LENSES = new Set([
|
|
10
|
+
"security",
|
|
11
|
+
"data_integrity",
|
|
12
|
+
"reliability",
|
|
13
|
+
]);
|
|
4
14
|
const SEVERITY_RANK = {
|
|
5
15
|
critical: 5,
|
|
6
16
|
high: 4,
|
|
@@ -27,6 +37,9 @@ function priorityRank(priority) {
|
|
|
27
37
|
function isDeepeningTask(task) {
|
|
28
38
|
return task?.tags?.includes(DEEPENING_TAG) ?? false;
|
|
29
39
|
}
|
|
40
|
+
function isLensVerificationTask(task) {
|
|
41
|
+
return task?.tags?.includes(LENS_VERIFICATION_TAG) ?? false;
|
|
42
|
+
}
|
|
30
43
|
function sanitizeSegment(value) {
|
|
31
44
|
const sanitized = value
|
|
32
45
|
.replace(/[^a-zA-Z0-9_-]+/g, "-")
|
|
@@ -85,6 +98,29 @@ function lineCountFromSources(path, tasks, results, lineIndex) {
|
|
|
85
98
|
}
|
|
86
99
|
return lineIndex?.[path] ?? 0;
|
|
87
100
|
}
|
|
101
|
+
function formatList(values, maxItems) {
|
|
102
|
+
const visible = values.slice(0, maxItems);
|
|
103
|
+
const suffix = values.length > maxItems ? `, ... (+${values.length - maxItems} more)` : "";
|
|
104
|
+
return `${visible.join(", ")}${suffix}`;
|
|
105
|
+
}
|
|
106
|
+
function priorityLabel(priority) {
|
|
107
|
+
return priority ?? "low";
|
|
108
|
+
}
|
|
109
|
+
function getExternalAnalyzerPaths(externalAnalyzerResults) {
|
|
110
|
+
return new Set((externalAnalyzerResults?.results ?? [])
|
|
111
|
+
.map((result) => result && typeof result.path === "string" && result.path.length > 0
|
|
112
|
+
? result.path
|
|
113
|
+
: null)
|
|
114
|
+
.filter((path) => path !== null));
|
|
115
|
+
}
|
|
116
|
+
function isRecord(value) {
|
|
117
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
118
|
+
}
|
|
119
|
+
function normalizedSuggestedPriority(value, fallback = "medium") {
|
|
120
|
+
return value === "high" || value === "medium" || value === "low"
|
|
121
|
+
? value
|
|
122
|
+
: fallback;
|
|
123
|
+
}
|
|
88
124
|
function buildFindingFollowupTask(params) {
|
|
89
125
|
const paths = pathsForFinding(params.finding, params.result, params.task);
|
|
90
126
|
const triggerLabel = params.triggers.join("+");
|
|
@@ -261,6 +297,309 @@ function buildRuntimeValidationFollowupTask(params) {
|
|
|
261
297
|
status: "pending",
|
|
262
298
|
};
|
|
263
299
|
}
|
|
300
|
+
function sourceTaskIds(sources) {
|
|
301
|
+
return uniqueSorted(sources.map((source) => source.result.task_id));
|
|
302
|
+
}
|
|
303
|
+
function resultFiles(source) {
|
|
304
|
+
return uniqueSorted(source.task?.file_paths && source.task.file_paths.length > 0
|
|
305
|
+
? source.task.file_paths
|
|
306
|
+
: source.result.file_coverage.map((coverage) => coverage.path));
|
|
307
|
+
}
|
|
308
|
+
function lensVerificationTriggers(params) {
|
|
309
|
+
const filePaths = uniqueSorted(params.sources.flatMap(resultFiles));
|
|
310
|
+
const findingPaths = new Set(params.sources.flatMap((source) => source.result.findings.flatMap((finding) => finding.affected_files.map((file) => file.path))));
|
|
311
|
+
const externalPathsInScope = filePaths.filter((path) => params.externalAnalyzerPaths.has(path));
|
|
312
|
+
const unresolvedExternalPaths = externalPathsInScope.filter((path) => !findingPaths.has(path));
|
|
313
|
+
const cleanResults = params.sources.filter((source) => source.result.findings.length === 0 &&
|
|
314
|
+
source.result.requires_followup !== false);
|
|
315
|
+
const highRiskCleanResults = params.sources.filter((source) => isHighRiskCleanResult(source.result, source.task));
|
|
316
|
+
const totalLines = filePaths.reduce((sum, path) => {
|
|
317
|
+
const owner = params.sources.find((source) => resultFiles(source).includes(path));
|
|
318
|
+
return (sum +
|
|
319
|
+
(owner
|
|
320
|
+
? lineCountForPath(path, owner.task, owner.result)
|
|
321
|
+
: 0));
|
|
322
|
+
}, 0);
|
|
323
|
+
const triggers = [];
|
|
324
|
+
if (params.sources.some((source) => source.task?.priority === "high")) {
|
|
325
|
+
triggers.push("high_priority_lens");
|
|
326
|
+
}
|
|
327
|
+
if (params.sources.some((source) => source.task?.tags?.some((tag) => tag === "critical_flow" || tag.startsWith("critical_flow:")))) {
|
|
328
|
+
triggers.push("critical_flow");
|
|
329
|
+
}
|
|
330
|
+
if (params.sources.some((source) => source.task?.tags?.some((tag) => tag === "external_analyzer_signal" ||
|
|
331
|
+
tag.startsWith("external_tool:"))) ||
|
|
332
|
+
externalPathsInScope.length > 0) {
|
|
333
|
+
triggers.push("external_analyzer_signal");
|
|
334
|
+
}
|
|
335
|
+
if (unresolvedExternalPaths.length > 0) {
|
|
336
|
+
triggers.push("unresolved_external_signal");
|
|
337
|
+
}
|
|
338
|
+
if (params.sources.length >= 3 ||
|
|
339
|
+
filePaths.length >= 4 ||
|
|
340
|
+
totalLines >= 2000) {
|
|
341
|
+
triggers.push("large_lens_surface");
|
|
342
|
+
}
|
|
343
|
+
if (cleanResults.length >= 2 && cleanResults.length >= params.sources.length / 2) {
|
|
344
|
+
triggers.push("many_no_finding_results");
|
|
345
|
+
}
|
|
346
|
+
if (highRiskCleanResults.length > 0) {
|
|
347
|
+
triggers.push("high_risk_clean_result");
|
|
348
|
+
}
|
|
349
|
+
if (params.sources.some((source) => source.task?.tags?.some((tag) => tag === "large_file"))) {
|
|
350
|
+
triggers.push("large_file_reviewed");
|
|
351
|
+
}
|
|
352
|
+
return uniqueSorted(triggers);
|
|
353
|
+
}
|
|
354
|
+
function hasPendingBaseTaskForLens(lens, tasks, completedResultIds) {
|
|
355
|
+
return tasks.some((task) => task.lens === lens &&
|
|
356
|
+
!isDeepeningTask(task) &&
|
|
357
|
+
!completedResultIds.has(task.task_id) &&
|
|
358
|
+
task.status !== "complete");
|
|
359
|
+
}
|
|
360
|
+
function shouldBuildLensVerificationTask(params) {
|
|
361
|
+
if (!IMPORTANT_LENS_VERIFICATION_LENSES.has(params.lens)) {
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
if (params.sources.length === 0 || params.triggers.length === 0) {
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
const explicitlyClosedCleanScope = params.sources.every((source) => source.result.findings.length === 0 &&
|
|
368
|
+
source.result.requires_followup === false);
|
|
369
|
+
if (explicitlyClosedCleanScope &&
|
|
370
|
+
!params.triggers.some((trigger) => ["external_analyzer_signal", "unresolved_external_signal"].includes(trigger))) {
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
if (hasPendingBaseTaskForLens(params.lens, params.existingTasks, params.completedResultIds)) {
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
const enoughSurface = params.sources.length >= 2 ||
|
|
377
|
+
params.triggers.some((trigger) => [
|
|
378
|
+
"critical_flow",
|
|
379
|
+
"external_analyzer_signal",
|
|
380
|
+
"unresolved_external_signal",
|
|
381
|
+
"large_lens_surface",
|
|
382
|
+
].includes(trigger));
|
|
383
|
+
if (!enoughSurface) {
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
const sourceSignature = sourceTaskIds(params.sources);
|
|
387
|
+
const candidateId = taskIdFor("steward", [params.lens, ...sourceSignature]);
|
|
388
|
+
return !params.existingTasks.some((task) => task.task_id === candidateId);
|
|
389
|
+
}
|
|
390
|
+
function selectLensVerificationFiles(sources, externalAnalyzerPaths) {
|
|
391
|
+
const scores = new Map();
|
|
392
|
+
function add(path, score, lines) {
|
|
393
|
+
const current = scores.get(path) ?? { score: 0, lines };
|
|
394
|
+
current.score += score;
|
|
395
|
+
current.lines = Math.max(current.lines, lines);
|
|
396
|
+
scores.set(path, current);
|
|
397
|
+
}
|
|
398
|
+
for (const source of sources) {
|
|
399
|
+
const priorityScore = priorityRank(source.task?.priority);
|
|
400
|
+
const highRiskClean = isHighRiskCleanResult(source.result, source.task);
|
|
401
|
+
for (const path of resultFiles(source)) {
|
|
402
|
+
add(path, priorityScore, lineCountForPath(path, source.task, source.result));
|
|
403
|
+
if (source.task?.tags?.includes("critical_flow"))
|
|
404
|
+
add(path, 6, 0);
|
|
405
|
+
if (source.task?.tags?.includes("external_analyzer_signal"))
|
|
406
|
+
add(path, 6, 0);
|
|
407
|
+
if (source.task?.tags?.includes("large_file"))
|
|
408
|
+
add(path, 4, 0);
|
|
409
|
+
if (highRiskClean)
|
|
410
|
+
add(path, 5, 0);
|
|
411
|
+
}
|
|
412
|
+
for (const finding of source.result.findings) {
|
|
413
|
+
for (const file of finding.affected_files) {
|
|
414
|
+
add(file.path, SEVERITY_RANK[finding.severity], 0);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
for (const path of externalAnalyzerPaths) {
|
|
419
|
+
if (scores.has(path)) {
|
|
420
|
+
add(path, 8, 0);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return [...scores.entries()]
|
|
424
|
+
.sort((a, b) => {
|
|
425
|
+
const scoreDelta = b[1].score - a[1].score;
|
|
426
|
+
if (scoreDelta !== 0)
|
|
427
|
+
return scoreDelta;
|
|
428
|
+
const lineDelta = b[1].lines - a[1].lines;
|
|
429
|
+
if (lineDelta !== 0)
|
|
430
|
+
return lineDelta;
|
|
431
|
+
return a[0].localeCompare(b[0]);
|
|
432
|
+
})
|
|
433
|
+
.slice(0, MAX_LENS_VERIFICATION_FILES)
|
|
434
|
+
.map(([path]) => path);
|
|
435
|
+
}
|
|
436
|
+
function summarizeLensVerificationSource(source) {
|
|
437
|
+
const findings = source.result.findings.length === 0
|
|
438
|
+
? "findings=none"
|
|
439
|
+
: `findings=${source.result.findings
|
|
440
|
+
.slice(0, 3)
|
|
441
|
+
.map((finding) => `${finding.id} ${finding.severity}/${finding.confidence} ${finding.category}: ${finding.title}`)
|
|
442
|
+
.join("; ")}${source.result.findings.length > 3 ? "; ..." : ""}`;
|
|
443
|
+
const tags = source.task?.tags?.length
|
|
444
|
+
? ` tags=${source.task.tags.join(",")}`
|
|
445
|
+
: "";
|
|
446
|
+
return (`- ${source.result.task_id} priority=${priorityLabel(source.task?.priority)}` +
|
|
447
|
+
`${tags} files=${formatList(resultFiles(source), 4)} ${findings}` +
|
|
448
|
+
(source.result.requires_followup === true ? " requires_followup=true" : ""));
|
|
449
|
+
}
|
|
450
|
+
function buildLensVerificationTask(params) {
|
|
451
|
+
const sourceIds = sourceTaskIds(params.sources);
|
|
452
|
+
const selectedPaths = selectLensVerificationFiles(params.sources, params.externalAnalyzerPaths);
|
|
453
|
+
const allPaths = uniqueSorted(params.sources.flatMap(resultFiles));
|
|
454
|
+
const omittedPathCount = Math.max(0, allPaths.length - selectedPaths.length);
|
|
455
|
+
const externalPathsInScope = allPaths.filter((path) => params.externalAnalyzerPaths.has(path));
|
|
456
|
+
const summaries = params.sources
|
|
457
|
+
.sort((a, b) => a.result.task_id.localeCompare(b.result.task_id))
|
|
458
|
+
.slice(0, MAX_LENS_VERIFICATION_RESULT_SUMMARIES)
|
|
459
|
+
.map(summarizeLensVerificationSource);
|
|
460
|
+
return {
|
|
461
|
+
task_id: taskIdFor("steward", [params.lens, ...sourceIds]),
|
|
462
|
+
unit_id: `lens-steward:${params.lens}`,
|
|
463
|
+
pass_id: `lens-steward:${params.lens}`,
|
|
464
|
+
lens: params.lens,
|
|
465
|
+
file_paths: selectedPaths,
|
|
466
|
+
file_line_counts: Object.fromEntries(selectedPaths.map((path) => [
|
|
467
|
+
path,
|
|
468
|
+
lineCountFromSources(path, params.sources.map((source) => source.task).filter((task) => task !== undefined), params.sources.map((source) => source.result), params.lineIndex),
|
|
469
|
+
])),
|
|
470
|
+
inputs: {
|
|
471
|
+
source_task_ids: sourceIds.join(","),
|
|
472
|
+
trigger_summary: params.triggers.join(","),
|
|
473
|
+
},
|
|
474
|
+
rationale: `Lens steward verification for ${params.lens} after ${params.sources.length} completed base result(s) across ${allPaths.length} file(s). ` +
|
|
475
|
+
`Triggers: ${params.triggers.join(", ")}. ` +
|
|
476
|
+
"Review whether high-risk packets are suspiciously clean, severity/confidence levels are consistent, external analyzer signals were resolved rather than hand-waved, cross-packet issues are visible, no-finding conclusions are believable, and related-file findings contradict each other. " +
|
|
477
|
+
"Do not write direct findings from this verification task; return findings: [] plus verification metadata with bounded follow-up AuditTask suggestions when needed.\n" +
|
|
478
|
+
`Selected verification files: ${formatList(selectedPaths, 8)}${omittedPathCount > 0 ? `; omitted ${omittedPathCount} lower-priority file(s) from direct source checks` : ""}.\n` +
|
|
479
|
+
(externalPathsInScope.length > 0
|
|
480
|
+
? `External analyzer paths in scope: ${formatList(externalPathsInScope, 8)}.\n`
|
|
481
|
+
: "") +
|
|
482
|
+
"Source result summary:\n" +
|
|
483
|
+
summaries.join("\n") +
|
|
484
|
+
(params.sources.length > MAX_LENS_VERIFICATION_RESULT_SUMMARIES
|
|
485
|
+
? `\n- ... (+${params.sources.length - MAX_LENS_VERIFICATION_RESULT_SUMMARIES} more result summaries omitted)`
|
|
486
|
+
: ""),
|
|
487
|
+
priority: "high",
|
|
488
|
+
tags: [
|
|
489
|
+
DEEPENING_TAG,
|
|
490
|
+
LENS_VERIFICATION_TAG,
|
|
491
|
+
`lens:${params.lens}`,
|
|
492
|
+
...params.triggers.map((trigger) => `trigger:${trigger}`),
|
|
493
|
+
],
|
|
494
|
+
status: "pending",
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
function buildLensVerificationTasks(params) {
|
|
498
|
+
const taskById = new Map(params.existingTasks.map((task) => [task.task_id, task]));
|
|
499
|
+
const completedResultIds = new Set(params.results.map((result) => result.task_id));
|
|
500
|
+
const externalAnalyzerPaths = getExternalAnalyzerPaths(params.externalAnalyzerResults);
|
|
501
|
+
const tasks = [];
|
|
502
|
+
for (const lens of [...IMPORTANT_LENS_VERIFICATION_LENSES].sort((a, b) => a.localeCompare(b))) {
|
|
503
|
+
const sources = params.results
|
|
504
|
+
.map((result) => ({ result, task: taskById.get(result.task_id) }))
|
|
505
|
+
.filter((source) => source.result.lens === lens &&
|
|
506
|
+
!isDeepeningTask(source.task) &&
|
|
507
|
+
!isLensVerificationTask(source.task));
|
|
508
|
+
const triggers = lensVerificationTriggers({
|
|
509
|
+
lens,
|
|
510
|
+
sources,
|
|
511
|
+
externalAnalyzerPaths,
|
|
512
|
+
});
|
|
513
|
+
if (!shouldBuildLensVerificationTask({
|
|
514
|
+
lens,
|
|
515
|
+
sources,
|
|
516
|
+
triggers,
|
|
517
|
+
existingTasks: params.existingTasks,
|
|
518
|
+
completedResultIds,
|
|
519
|
+
})) {
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
tasks.push(buildLensVerificationTask({
|
|
523
|
+
lens,
|
|
524
|
+
sources,
|
|
525
|
+
triggers,
|
|
526
|
+
externalAnalyzerPaths,
|
|
527
|
+
lineIndex: params.lineIndex,
|
|
528
|
+
}));
|
|
529
|
+
}
|
|
530
|
+
return tasks;
|
|
531
|
+
}
|
|
532
|
+
function buildVerificationFollowupTasks(params) {
|
|
533
|
+
if (!params.result.verification?.needs_followup ||
|
|
534
|
+
!Array.isArray(params.result.verification.followup_tasks) ||
|
|
535
|
+
!isLensVerificationTask(params.task)) {
|
|
536
|
+
return [];
|
|
537
|
+
}
|
|
538
|
+
const coverageByPath = new Map(params.result.file_coverage.map((coverage) => [
|
|
539
|
+
coverage.path,
|
|
540
|
+
coverage.total_lines,
|
|
541
|
+
]));
|
|
542
|
+
const concerns = [
|
|
543
|
+
...(params.result.verification.concerns ?? []),
|
|
544
|
+
...(params.result.verification.coverage_concerns ?? []),
|
|
545
|
+
...(params.result.verification.confidence_concerns ?? []),
|
|
546
|
+
];
|
|
547
|
+
const tasks = [];
|
|
548
|
+
for (let index = 0; index < params.result.verification.followup_tasks.length; index++) {
|
|
549
|
+
if (tasks.length >= MAX_VERIFICATION_FOLLOWUP_TASKS_PER_RESULT) {
|
|
550
|
+
break;
|
|
551
|
+
}
|
|
552
|
+
const suggestion = params.result.verification.followup_tasks[index];
|
|
553
|
+
if (!isRecord(suggestion)) {
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
if (suggestion.lens !== params.result.lens) {
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
const suggestedPaths = Array.isArray(suggestion.file_paths)
|
|
560
|
+
? suggestion.file_paths.filter((path) => typeof path === "string" && coverageByPath.has(path))
|
|
561
|
+
: [];
|
|
562
|
+
const paths = uniqueSorted(suggestedPaths);
|
|
563
|
+
if (paths.length === 0) {
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
const suggestedRationale = typeof suggestion.rationale === "string" && suggestion.rationale.trim().length > 0
|
|
567
|
+
? suggestion.rationale.trim()
|
|
568
|
+
: concerns[0] ?? "Lens steward requested bounded follow-up.";
|
|
569
|
+
const suggestedId = typeof suggestion.task_id === "string" && suggestion.task_id.trim().length > 0
|
|
570
|
+
? suggestion.task_id
|
|
571
|
+
: `suggestion-${index + 1}`;
|
|
572
|
+
tasks.push({
|
|
573
|
+
task_id: taskIdFor("steward-followup", [
|
|
574
|
+
params.result.task_id,
|
|
575
|
+
String(index),
|
|
576
|
+
suggestedId,
|
|
577
|
+
...paths,
|
|
578
|
+
suggestedRationale,
|
|
579
|
+
]),
|
|
580
|
+
unit_id: typeof suggestion.unit_id === "string" && suggestion.unit_id.trim().length > 0
|
|
581
|
+
? suggestion.unit_id
|
|
582
|
+
: params.result.unit_id,
|
|
583
|
+
pass_id: `deepening:${params.result.pass_id}`,
|
|
584
|
+
lens: params.result.lens,
|
|
585
|
+
file_paths: paths,
|
|
586
|
+
file_line_counts: Object.fromEntries(paths.map((path) => [
|
|
587
|
+
path,
|
|
588
|
+
coverageByPath.get(path) ?? params.lineIndex?.[path] ?? 0,
|
|
589
|
+
])),
|
|
590
|
+
rationale: `Lens steward follow-up from ${params.result.task_id}. ${suggestedRationale}`,
|
|
591
|
+
priority: normalizedSuggestedPriority(suggestion.priority, "medium"),
|
|
592
|
+
tags: [
|
|
593
|
+
DEEPENING_TAG,
|
|
594
|
+
LENS_VERIFICATION_FOLLOWUP_TAG,
|
|
595
|
+
"trigger:lens_verification",
|
|
596
|
+
`source_task:${sanitizeSegment(params.result.task_id)}`,
|
|
597
|
+
],
|
|
598
|
+
status: "pending",
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
return tasks;
|
|
602
|
+
}
|
|
264
603
|
function findingContexts(results, taskById) {
|
|
265
604
|
const contexts = [];
|
|
266
605
|
for (const result of results) {
|
|
@@ -345,6 +684,16 @@ export function buildSelectiveDeepeningTasks(options) {
|
|
|
345
684
|
lineIndex: options.lineIndex,
|
|
346
685
|
}));
|
|
347
686
|
}
|
|
687
|
+
for (const result of options.results) {
|
|
688
|
+
const task = taskById.get(result.task_id);
|
|
689
|
+
for (const followupTask of buildVerificationFollowupTasks({
|
|
690
|
+
result,
|
|
691
|
+
task,
|
|
692
|
+
lineIndex: options.lineIndex,
|
|
693
|
+
})) {
|
|
694
|
+
pushIfNew(followupTask);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
348
697
|
const runtimeTaskById = new Map((options.runtimeValidationTasks?.tasks ?? []).map((task) => [
|
|
349
698
|
task.id,
|
|
350
699
|
task,
|
|
@@ -369,6 +718,14 @@ export function buildSelectiveDeepeningTasks(options) {
|
|
|
369
718
|
lineIndex: options.lineIndex,
|
|
370
719
|
}));
|
|
371
720
|
}
|
|
721
|
+
for (const task of buildLensVerificationTasks({
|
|
722
|
+
existingTasks,
|
|
723
|
+
results: options.results,
|
|
724
|
+
lineIndex: options.lineIndex,
|
|
725
|
+
externalAnalyzerResults: options.externalAnalyzerResults,
|
|
726
|
+
})) {
|
|
727
|
+
pushIfNew(task);
|
|
728
|
+
}
|
|
372
729
|
const cleanResults = options.results
|
|
373
730
|
.map((result) => ({ result, task: taskById.get(result.task_id) }))
|
|
374
731
|
.filter(({ result, task }) => isHighRiskCleanResult(result, task))
|
|
@@ -389,4 +746,6 @@ export function buildSelectiveDeepeningTasks(options) {
|
|
|
389
746
|
}
|
|
390
747
|
export const selectiveDeepeningTestUtils = {
|
|
391
748
|
DEEPENING_TAG,
|
|
749
|
+
LENS_VERIFICATION_TAG,
|
|
750
|
+
LENS_VERIFICATION_FOLLOWUP_TAG,
|
|
392
751
|
};
|
|
@@ -7,13 +7,9 @@ export function renderWorkerPrompt(task) {
|
|
|
7
7
|
if (task.preferred_executor === "agent" && task.audit_results_path) {
|
|
8
8
|
const tasksPath = task.pending_audit_tasks_path ??
|
|
9
9
|
`${task.artifacts_dir}/audit_tasks.json`;
|
|
10
|
-
const resultsSchemaPath = `${task.artifacts_dir}/dispatch/audit-results.schema.json`;
|
|
11
|
-
const singleResultSchemaPath = `${task.artifacts_dir}/dispatch/audit-result.schema.json`;
|
|
12
10
|
const lines = [
|
|
13
11
|
`Audit run: ${task.run_id}`,
|
|
14
12
|
`Read: ${tasksPath}`,
|
|
15
|
-
`Array schema: ${resultsSchemaPath}`,
|
|
16
|
-
`Single-result schema: ${singleResultSchemaPath}`,
|
|
17
13
|
"Scope: review only the tasks listed in the Read file. Do not add tasks,",
|
|
18
14
|
"edit source files, remediate findings, run unrelated audits, or write result_path.",
|
|
19
15
|
"For each listed task: read the assigned file_paths under the specified lens,",
|
|
@@ -25,6 +21,9 @@ export function renderWorkerPrompt(task) {
|
|
|
25
21
|
"Each finding: id, title, category, severity, confidence, lens, summary,",
|
|
26
22
|
" affected_files [{path, line_start, line_end, symbol}] (objects, not strings; min 1 entry),",
|
|
27
23
|
" evidence [strings] (min 1 entry).",
|
|
24
|
+
"For tasks tagged lens_verification: do not write direct findings; use findings: []",
|
|
25
|
+
" and include verification {verified, needs_followup, concerns, coverage_concerns,",
|
|
26
|
+
" confidence_concerns, followup_tasks}.",
|
|
28
27
|
"Constraint: line_end must not exceed total_lines for that file.",
|
|
29
28
|
`Write only the JSON array of AuditResult objects to: ${task.audit_results_path}`,
|
|
30
29
|
];
|
package/dist/types.d.ts
CHANGED
|
@@ -88,6 +88,14 @@ export interface Finding {
|
|
|
88
88
|
systemic?: boolean;
|
|
89
89
|
related_findings?: string[];
|
|
90
90
|
}
|
|
91
|
+
export interface AuditVerification {
|
|
92
|
+
verified: boolean;
|
|
93
|
+
needs_followup: boolean;
|
|
94
|
+
concerns?: string[];
|
|
95
|
+
coverage_concerns?: string[];
|
|
96
|
+
confidence_concerns?: string[];
|
|
97
|
+
followup_tasks?: AuditTask[];
|
|
98
|
+
}
|
|
91
99
|
export interface AuditResult {
|
|
92
100
|
task_id: string;
|
|
93
101
|
unit_id: string;
|
|
@@ -102,4 +110,5 @@ export interface AuditResult {
|
|
|
102
110
|
notes?: string[];
|
|
103
111
|
requires_followup?: boolean;
|
|
104
112
|
followup_tasks?: string[];
|
|
113
|
+
verification?: AuditVerification;
|
|
105
114
|
}
|
|
@@ -10,6 +10,8 @@ const REQUIRED_FINDING_FIELDS = [
|
|
|
10
10
|
];
|
|
11
11
|
const VALID_SEVERITIES = new Set(["critical", "high", "medium", "low", "info"]);
|
|
12
12
|
const VALID_CONFIDENCES = new Set(["high", "medium", "low"]);
|
|
13
|
+
const VALID_PRIORITIES = new Set(["high", "medium", "low"]);
|
|
14
|
+
const LENS_VERIFICATION_TAG = "lens_verification";
|
|
13
15
|
const VALID_LENSES = new Set([
|
|
14
16
|
"correctness",
|
|
15
17
|
"architecture",
|
|
@@ -207,6 +209,161 @@ function validateFinding(finding, label, taskId, resultIndex) {
|
|
|
207
209
|
}
|
|
208
210
|
return issues;
|
|
209
211
|
}
|
|
212
|
+
function validateOptionalStringArray(value, label, taskId, resultIndex, issues) {
|
|
213
|
+
if (value === undefined) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (!Array.isArray(value)) {
|
|
217
|
+
pushIssue(issues, {
|
|
218
|
+
result_index: resultIndex,
|
|
219
|
+
task_id: taskId,
|
|
220
|
+
field: label,
|
|
221
|
+
message: `${label} must be an array of strings, got ${describeValue(value)}.`,
|
|
222
|
+
});
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
for (let index = 0; index < value.length; index++) {
|
|
226
|
+
if (typeof value[index] !== "string") {
|
|
227
|
+
pushIssue(issues, {
|
|
228
|
+
result_index: resultIndex,
|
|
229
|
+
task_id: taskId,
|
|
230
|
+
field: `${label}[${index}]`,
|
|
231
|
+
message: `${label}[${index}] must be a string, got ${describeValue(value[index])}.`,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function validateVerificationFollowupTask(task, label, taskId, resultIndex, expectedLens, allowedPaths, issues) {
|
|
237
|
+
if (!isRecord(task)) {
|
|
238
|
+
pushIssue(issues, {
|
|
239
|
+
result_index: resultIndex,
|
|
240
|
+
task_id: taskId,
|
|
241
|
+
field: label,
|
|
242
|
+
message: `${label} must be an AuditTask object, got ${describeValue(task)}.`,
|
|
243
|
+
});
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
for (const field of ["task_id", "unit_id", "pass_id", "lens", "rationale"]) {
|
|
247
|
+
validateRequiredStringField(task[field], `${label}.${field}`, taskId, resultIndex, issues);
|
|
248
|
+
}
|
|
249
|
+
if (typeof task.lens === "string" && !VALID_LENSES.has(task.lens)) {
|
|
250
|
+
pushIssue(issues, {
|
|
251
|
+
result_index: resultIndex,
|
|
252
|
+
task_id: taskId,
|
|
253
|
+
field: `${label}.lens`,
|
|
254
|
+
message: `Invalid lens '${task.lens}'. Must be one of: ${[...VALID_LENSES].join(", ")}.`,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
if (typeof expectedLens === "string" &&
|
|
258
|
+
typeof task.lens === "string" &&
|
|
259
|
+
task.lens !== expectedLens) {
|
|
260
|
+
pushIssue(issues, {
|
|
261
|
+
result_index: resultIndex,
|
|
262
|
+
task_id: taskId,
|
|
263
|
+
field: `${label}.lens`,
|
|
264
|
+
message: `${label}.lens must match the lens verification task ` +
|
|
265
|
+
`(expected '${expectedLens}', got '${task.lens}').`,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
if (task.priority !== undefined &&
|
|
269
|
+
(typeof task.priority !== "string" || !VALID_PRIORITIES.has(task.priority))) {
|
|
270
|
+
pushIssue(issues, {
|
|
271
|
+
result_index: resultIndex,
|
|
272
|
+
task_id: taskId,
|
|
273
|
+
field: `${label}.priority`,
|
|
274
|
+
message: `${label}.priority must be one of: ${[...VALID_PRIORITIES].join(", ")}.`,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
if (!Array.isArray(task.file_paths) || task.file_paths.length === 0) {
|
|
278
|
+
pushIssue(issues, {
|
|
279
|
+
result_index: resultIndex,
|
|
280
|
+
task_id: taskId,
|
|
281
|
+
field: `${label}.file_paths`,
|
|
282
|
+
message: `${label}.file_paths must be a non-empty array.`,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
for (let index = 0; index < task.file_paths.length; index++) {
|
|
287
|
+
const path = task.file_paths[index];
|
|
288
|
+
if (!isNonEmptyString(path)) {
|
|
289
|
+
pushIssue(issues, {
|
|
290
|
+
result_index: resultIndex,
|
|
291
|
+
task_id: taskId,
|
|
292
|
+
field: `${label}.file_paths[${index}]`,
|
|
293
|
+
message: `${label}.file_paths[${index}] must be a non-empty string.`,
|
|
294
|
+
});
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if (!allowedPaths.has(path)) {
|
|
298
|
+
pushIssue(issues, {
|
|
299
|
+
result_index: resultIndex,
|
|
300
|
+
task_id: taskId,
|
|
301
|
+
field: `${label}.file_paths[${index}]`,
|
|
302
|
+
message: `${label}.file_paths[${index}] references '${path}', which is outside the verification task's file_coverage.`,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
validateOptionalStringArray(task.tags, `${label}.tags`, taskId, resultIndex, issues);
|
|
308
|
+
}
|
|
309
|
+
function validateVerification(value, result, task, coverage, taskId, resultIndex, issues) {
|
|
310
|
+
if (value === undefined) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
if (!isRecord(value)) {
|
|
314
|
+
pushIssue(issues, {
|
|
315
|
+
result_index: resultIndex,
|
|
316
|
+
task_id: taskId,
|
|
317
|
+
field: "verification",
|
|
318
|
+
message: `verification must be an object, got ${describeValue(value)}.`,
|
|
319
|
+
});
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (typeof value.verified !== "boolean") {
|
|
323
|
+
pushIssue(issues, {
|
|
324
|
+
result_index: resultIndex,
|
|
325
|
+
task_id: taskId,
|
|
326
|
+
field: "verification.verified",
|
|
327
|
+
message: `verification.verified must be a boolean, got ${describeValue(value.verified)}.`,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
if (typeof value.needs_followup !== "boolean") {
|
|
331
|
+
pushIssue(issues, {
|
|
332
|
+
result_index: resultIndex,
|
|
333
|
+
task_id: taskId,
|
|
334
|
+
field: "verification.needs_followup",
|
|
335
|
+
message: `verification.needs_followup must be a boolean, got ${describeValue(value.needs_followup)}.`,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
if (task && !task.tags?.includes(LENS_VERIFICATION_TAG)) {
|
|
339
|
+
pushIssue(issues, {
|
|
340
|
+
result_index: resultIndex,
|
|
341
|
+
task_id: taskId,
|
|
342
|
+
field: "verification",
|
|
343
|
+
message: "verification is intended only for tasks tagged lens_verification.",
|
|
344
|
+
severity: "warning",
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
validateOptionalStringArray(value.concerns, "verification.concerns", taskId, resultIndex, issues);
|
|
348
|
+
validateOptionalStringArray(value.coverage_concerns, "verification.coverage_concerns", taskId, resultIndex, issues);
|
|
349
|
+
validateOptionalStringArray(value.confidence_concerns, "verification.confidence_concerns", taskId, resultIndex, issues);
|
|
350
|
+
if (value.followup_tasks === undefined) {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (!Array.isArray(value.followup_tasks)) {
|
|
354
|
+
pushIssue(issues, {
|
|
355
|
+
result_index: resultIndex,
|
|
356
|
+
task_id: taskId,
|
|
357
|
+
field: "verification.followup_tasks",
|
|
358
|
+
message: `verification.followup_tasks must be an array, got ${describeValue(value.followup_tasks)}.`,
|
|
359
|
+
});
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const allowedPaths = new Set(coverage.map((entry) => entry.path));
|
|
363
|
+
for (let index = 0; index < value.followup_tasks.length; index++) {
|
|
364
|
+
validateVerificationFollowupTask(value.followup_tasks[index], `verification.followup_tasks[${index}]`, taskId, resultIndex, result.lens, allowedPaths, issues);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
210
367
|
function coversAffectedSpan(coverage, path, start, end) {
|
|
211
368
|
return coverage.some((entry) => entry.path === path &&
|
|
212
369
|
start > 0 &&
|
|
@@ -438,6 +595,7 @@ export function validateAuditResults(results, tasks, options = {}) {
|
|
|
438
595
|
}
|
|
439
596
|
}
|
|
440
597
|
}
|
|
598
|
+
validateVerification(result.verification, result, task, normalizedFileCoverage, taskId, i, issues);
|
|
441
599
|
}
|
|
442
600
|
return issues;
|
|
443
601
|
}
|