auditor-lambda 0.10.8 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/cli/args.d.ts +3 -3
- package/dist/cli/args.js +4 -7
- package/dist/cli/auditStep.js +15 -1
- package/dist/cli/dispatch.d.ts +1 -0
- package/dist/cli/dispatch.js +27 -5
- package/dist/cli/prompts.js +7 -6
- package/dist/cli/resynthesizeCommand.d.ts +1 -0
- package/dist/cli/resynthesizeCommand.js +50 -0
- package/dist/cli.js +5 -1
- package/dist/extractors/disposition.js +15 -1
- package/dist/extractors/pathPatterns.d.ts +13 -0
- package/dist/extractors/pathPatterns.js +28 -1
- package/dist/io/artifacts.d.ts +3 -1
- package/dist/io/artifacts.js +1 -0
- package/dist/orchestrator/advance.js +6 -0
- package/dist/orchestrator/dependencyMap.js +7 -0
- package/dist/orchestrator/designReviewPrompt.js +31 -10
- package/dist/orchestrator/executors.js +6 -0
- package/dist/orchestrator/ingestionExecutors.js +29 -1
- package/dist/orchestrator/intentCheckpointExecutor.d.ts +3 -0
- package/dist/orchestrator/intentCheckpointExecutor.js +29 -0
- package/dist/orchestrator/lensSelection.d.ts +32 -0
- package/dist/orchestrator/lensSelection.js +69 -0
- package/dist/orchestrator/planningExecutors.js +18 -3
- package/dist/orchestrator/resultIngestion.d.ts +14 -0
- package/dist/orchestrator/resultIngestion.js +28 -0
- package/dist/orchestrator.d.ts +1 -1
- package/dist/orchestrator.js +3 -4
- package/dist/prompts/renderWorkerPrompt.js +9 -0
- package/dist/quota/index.d.ts +5 -3
- package/dist/quota/index.js +1 -1
- package/dist/reporting/synthesis.d.ts +10 -0
- package/dist/reporting/synthesis.js +23 -0
- package/dist/reporting/synthesisNarrativePrompt.js +3 -1
- package/dist/validation/auditResults.js +7 -3
- package/docs/development.md +6 -0
- package/package.json +2 -1
- package/schemas/audit_findings.schema.json +14 -1
- package/schemas/dispatch_quota.schema.json +86 -2
- package/schemas/finding.schema.json +14 -1
|
@@ -112,9 +112,36 @@ export function runResultIngestionExecutor(bundle, results) {
|
|
|
112
112
|
excludeArtifacts: ["audit_tasks.json"],
|
|
113
113
|
});
|
|
114
114
|
const requeuePayload = buildRequeuePayload(updatedCoverageMatrix, selectiveDeepening.bundle.critical_flows, selectiveDeepening.bundle.flow_coverage, selectiveDeepening.bundle.external_analyzer_results);
|
|
115
|
+
// Fold pending requeue tasks into the dispatch task list so mandatory
|
|
116
|
+
// coverage gaps produce actual dispatch packets. Enrich with line-count
|
|
117
|
+
// hints and dedupe against existing tasks by task_id.
|
|
118
|
+
const deepenedTasks = selectiveDeepening.bundle.audit_tasks ?? [];
|
|
119
|
+
const lineIndex = lineIndexFromTasks(deepenedTasks);
|
|
120
|
+
const sizeIndex = sizeIndexFromManifest(selectiveDeepening.bundle.repo_manifest);
|
|
121
|
+
const existingTaskIds = new Set(deepenedTasks.map((t) => t.task_id));
|
|
122
|
+
const pendingRequeueTasks = requeuePayload.tasks
|
|
123
|
+
.filter((t) => t.status === "pending")
|
|
124
|
+
.filter((t) => !existingTaskIds.has(t.task_id))
|
|
125
|
+
.map((t) => ({
|
|
126
|
+
...t,
|
|
127
|
+
file_line_counts: Object.fromEntries(t.file_paths
|
|
128
|
+
.filter((p) => lineIndex[p] != null)
|
|
129
|
+
.map((p) => [p, lineIndex[p]])),
|
|
130
|
+
}));
|
|
131
|
+
const allDispatchTasks = [...deepenedTasks, ...pendingRequeueTasks];
|
|
115
132
|
const finalBundle = {
|
|
116
133
|
...selectiveDeepening.bundle,
|
|
117
134
|
requeue_tasks: requeuePayload.tasks,
|
|
135
|
+
audit_plan_metrics: buildAuditPlanMetrics(allDispatchTasks, {
|
|
136
|
+
graphBundle: selectiveDeepening.bundle.graph_bundle,
|
|
137
|
+
lineIndex,
|
|
138
|
+
sizeIndex,
|
|
139
|
+
}),
|
|
140
|
+
review_packets: buildReviewPackets(allDispatchTasks, {
|
|
141
|
+
graphBundle: selectiveDeepening.bundle.graph_bundle,
|
|
142
|
+
lineIndex,
|
|
143
|
+
sizeIndex,
|
|
144
|
+
}),
|
|
118
145
|
};
|
|
119
146
|
return {
|
|
120
147
|
updated: finalBundle,
|
|
@@ -125,7 +152,8 @@ export function runResultIngestionExecutor(bundle, results) {
|
|
|
125
152
|
...(runtimeValidationReport ? ["runtime_validation_report.json"] : []),
|
|
126
153
|
"audit_results.jsonl",
|
|
127
154
|
"audit_tasks.json",
|
|
128
|
-
|
|
155
|
+
"audit_plan_metrics.json",
|
|
156
|
+
"review_packets.json",
|
|
129
157
|
"requeue_tasks.json",
|
|
130
158
|
],
|
|
131
159
|
progress_summary: `Ingested ${results.length} audit result entries and refreshed dependent artifacts.` +
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { resolveAuditScope } from "./scope.js";
|
|
2
|
+
import { isAuditExcludedStatus } from "../extractors/disposition.js";
|
|
3
|
+
export async function runIntentCheckpointExecutor(bundle, root, since) {
|
|
4
|
+
const scope = resolveAuditScope({ root, since, bundle });
|
|
5
|
+
let filesInScope = 0;
|
|
6
|
+
if (scope.mode === "delta") {
|
|
7
|
+
filesInScope = scope.seed_files.length + scope.expanded_files.length;
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
// Count auditable files in disposition. Fall back to manifest or 0.
|
|
11
|
+
const auditableCount = bundle.file_disposition?.files.filter((file) => !isAuditExcludedStatus(file.status)).length ?? (bundle.repo_manifest?.files.length ?? 0);
|
|
12
|
+
filesInScope = auditableCount;
|
|
13
|
+
}
|
|
14
|
+
const intent = {
|
|
15
|
+
schema_version: "intent-checkpoint/v1",
|
|
16
|
+
confirmed_at: new Date().toISOString(),
|
|
17
|
+
scope_summary: `Root: ${root}${scope.since ? ` (since ${scope.since})` : ""}, files in scope: ${filesInScope}`,
|
|
18
|
+
intent_summary: scope.mode === "delta" ? `delta-audit since ${scope.since}` : "full-audit",
|
|
19
|
+
confirmed_by: "host",
|
|
20
|
+
};
|
|
21
|
+
return {
|
|
22
|
+
updated: {
|
|
23
|
+
...bundle,
|
|
24
|
+
intent_checkpoint: intent,
|
|
25
|
+
},
|
|
26
|
+
artifacts_written: ["intent_checkpoint.json"],
|
|
27
|
+
progress_summary: `Recorded scope/intent checkpoint: ${intent.scope_summary} (${intent.intent_summary}).`,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lens selection resolver for operator-selected audit lenses.
|
|
3
|
+
*
|
|
4
|
+
* Accepts the `lenses.selected` array from session-config, validates it,
|
|
5
|
+
* de-duplicates, sorts with the canonical LENSES registry order, and always
|
|
6
|
+
* unions in the mandatory base set required for cross-perspective coverage.
|
|
7
|
+
*/
|
|
8
|
+
import { type Lens } from "@audit-tools/shared";
|
|
9
|
+
import type { ValidationIssue } from "@audit-tools/shared";
|
|
10
|
+
/**
|
|
11
|
+
* The mandatory base lenses that are always included regardless of the
|
|
12
|
+
* operator's selection. These are required for cross-perspective obligations
|
|
13
|
+
* that every audit must satisfy.
|
|
14
|
+
*/
|
|
15
|
+
export declare const MANDATORY_LENSES: readonly Lens[];
|
|
16
|
+
/**
|
|
17
|
+
* Validate the `lenses.selected` session-config value and return any errors.
|
|
18
|
+
* Returns an empty array when the value is undefined (meaning "all lenses").
|
|
19
|
+
*/
|
|
20
|
+
export declare function validateLensSelection(value: unknown, path?: string): ValidationIssue[];
|
|
21
|
+
/**
|
|
22
|
+
* Resolve the effective lens set from the operator-selected lenses.
|
|
23
|
+
*
|
|
24
|
+
* - When `selected` is undefined/null, returns all lenses (current behavior).
|
|
25
|
+
* - When `selected` is an array of valid lens names, unions in the mandatory
|
|
26
|
+
* base lenses, de-duplicates, and sorts to the canonical LENSES order.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveEffectiveLenses(selected: string[] | undefined | null): Lens[];
|
|
29
|
+
/** Returns true when the given lens is in the effective set. */
|
|
30
|
+
export declare function isLensEffective(lens: Lens, effectiveLenses: Lens[]): boolean;
|
|
31
|
+
/** Returns true when the given lens is in the mandatory base set. */
|
|
32
|
+
export declare function isMandatoryLens(lens: Lens): boolean;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lens selection resolver for operator-selected audit lenses.
|
|
3
|
+
*
|
|
4
|
+
* Accepts the `lenses.selected` array from session-config, validates it,
|
|
5
|
+
* de-duplicates, sorts with the canonical LENSES registry order, and always
|
|
6
|
+
* unions in the mandatory base set required for cross-perspective coverage.
|
|
7
|
+
*/
|
|
8
|
+
import { LENSES, VALID_LENSES } from "@audit-tools/shared";
|
|
9
|
+
import { pushValidationIssue } from "@audit-tools/shared";
|
|
10
|
+
/**
|
|
11
|
+
* The mandatory base lenses that are always included regardless of the
|
|
12
|
+
* operator's selection. These are required for cross-perspective obligations
|
|
13
|
+
* that every audit must satisfy.
|
|
14
|
+
*/
|
|
15
|
+
export const MANDATORY_LENSES = [
|
|
16
|
+
"security",
|
|
17
|
+
"correctness",
|
|
18
|
+
"reliability",
|
|
19
|
+
"data_integrity",
|
|
20
|
+
];
|
|
21
|
+
const MANDATORY_LENS_SET = new Set(MANDATORY_LENSES);
|
|
22
|
+
/**
|
|
23
|
+
* Validate the `lenses.selected` session-config value and return any errors.
|
|
24
|
+
* Returns an empty array when the value is undefined (meaning "all lenses").
|
|
25
|
+
*/
|
|
26
|
+
export function validateLensSelection(value, path = "lenses.selected") {
|
|
27
|
+
const issues = [];
|
|
28
|
+
if (value === undefined || value === null) {
|
|
29
|
+
return issues;
|
|
30
|
+
}
|
|
31
|
+
if (!Array.isArray(value)) {
|
|
32
|
+
pushValidationIssue(issues, path, `${path} must be an array of lens names.`);
|
|
33
|
+
return issues;
|
|
34
|
+
}
|
|
35
|
+
for (const [index, item] of value.entries()) {
|
|
36
|
+
if (typeof item !== "string" || !VALID_LENSES.has(item)) {
|
|
37
|
+
pushValidationIssue(issues, `${path}[${index}]`, `${path}[${index}] "${String(item)}" is not a valid lens. Valid lenses: ${[...LENSES].join(", ")}.`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return issues;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolve the effective lens set from the operator-selected lenses.
|
|
44
|
+
*
|
|
45
|
+
* - When `selected` is undefined/null, returns all lenses (current behavior).
|
|
46
|
+
* - When `selected` is an array of valid lens names, unions in the mandatory
|
|
47
|
+
* base lenses, de-duplicates, and sorts to the canonical LENSES order.
|
|
48
|
+
*/
|
|
49
|
+
export function resolveEffectiveLenses(selected) {
|
|
50
|
+
if (selected === undefined || selected === null) {
|
|
51
|
+
// Default: all lenses.
|
|
52
|
+
return [...LENSES];
|
|
53
|
+
}
|
|
54
|
+
// Filter to valid lenses only (invalid ones are caught by validation; tolerate
|
|
55
|
+
// them here so the runtime path doesn't throw on a misconfigured file).
|
|
56
|
+
const validSelected = selected.filter((s) => VALID_LENSES.has(s));
|
|
57
|
+
// Union with mandatory base lenses.
|
|
58
|
+
const combined = new Set([...validSelected, ...MANDATORY_LENSES]);
|
|
59
|
+
// Sort to canonical registry order.
|
|
60
|
+
return LENSES.filter((lens) => combined.has(lens));
|
|
61
|
+
}
|
|
62
|
+
/** Returns true when the given lens is in the effective set. */
|
|
63
|
+
export function isLensEffective(lens, effectiveLenses) {
|
|
64
|
+
return effectiveLenses.includes(lens);
|
|
65
|
+
}
|
|
66
|
+
/** Returns true when the given lens is in the mandatory base set. */
|
|
67
|
+
export function isMandatoryLens(lens) {
|
|
68
|
+
return MANDATORY_LENS_SET.has(lens);
|
|
69
|
+
}
|
|
@@ -44,17 +44,32 @@ export async function runPlanningExecutor(bundle, root, lineIndex = {}, sizeInde
|
|
|
44
44
|
...task,
|
|
45
45
|
status: task.status ?? "pending",
|
|
46
46
|
}));
|
|
47
|
-
const
|
|
47
|
+
const requeuePayload = buildRequeuePayload(coverage, bundle.critical_flows, flowCoverage, externalAnalyzerResults);
|
|
48
|
+
// Fold pending requeue tasks into the dispatch task list so mandatory coverage
|
|
49
|
+
// gaps produce actual dispatch packets. Enrich with line-count hints from the
|
|
50
|
+
// index and dedupe against existing audit tasks by task_id so each task
|
|
51
|
+
// appears exactly once in the merged list.
|
|
52
|
+
const existingTaskIds = new Set(taggedAuditTasks.map((t) => t.task_id));
|
|
53
|
+
const pendingRequeueTasks = requeuePayload.tasks
|
|
54
|
+
.filter((t) => t.status === "pending")
|
|
55
|
+
.filter((t) => !existingTaskIds.has(t.task_id))
|
|
56
|
+
.map((t) => ({
|
|
57
|
+
...t,
|
|
58
|
+
file_line_counts: Object.fromEntries(t.file_paths
|
|
59
|
+
.filter((p) => lineIndex[p] != null)
|
|
60
|
+
.map((p) => [p, lineIndex[p]])),
|
|
61
|
+
}));
|
|
62
|
+
const allDispatchTasks = [...taggedAuditTasks, ...pendingRequeueTasks];
|
|
63
|
+
const reviewPackets = buildReviewPackets(allDispatchTasks, {
|
|
48
64
|
graphBundle: bundle.graph_bundle,
|
|
49
65
|
lineIndex,
|
|
50
66
|
sizeIndex: resolvedSizeIndex,
|
|
51
67
|
});
|
|
52
|
-
const auditPlanMetrics = buildAuditPlanMetrics(
|
|
68
|
+
const auditPlanMetrics = buildAuditPlanMetrics(allDispatchTasks, {
|
|
53
69
|
graphBundle: bundle.graph_bundle,
|
|
54
70
|
lineIndex,
|
|
55
71
|
sizeIndex: resolvedSizeIndex,
|
|
56
72
|
});
|
|
57
|
-
const requeuePayload = buildRequeuePayload(coverage, bundle.critical_flows, flowCoverage, externalAnalyzerResults);
|
|
58
73
|
const scopeSummary = resolvedScope.mode === "delta"
|
|
59
74
|
? ` Delta scope since ${resolvedScope.since}: ${resolvedScope.seed_files.length} changed file(s) + ${resolvedScope.expanded_files.length} graph neighbour(s) queued; a full audit is advised before release.`
|
|
60
75
|
: "";
|
|
@@ -1,3 +1,17 @@
|
|
|
1
1
|
import type { AuditResult, AuditTask, CoverageMatrix } from "../types.js";
|
|
2
2
|
export declare function ingestAuditResults(coverageMatrix: CoverageMatrix, results: AuditResult[]): CoverageMatrix;
|
|
3
3
|
export declare function updateAuditTaskStatuses(tasks: AuditTask[] | undefined, results: AuditResult[]): AuditTask[] | undefined;
|
|
4
|
+
/**
|
|
5
|
+
* Splits raw (unvalidated) audit results into those whose `task_id` is still in
|
|
6
|
+
* the active task manifest and those orphaned by a later re-plan (e.g. selective-
|
|
7
|
+
* deepening tasks pruned in a subsequent round). Orphaned results cannot be
|
|
8
|
+
* ingested — coverage is keyed by the active task set — and must not abort an
|
|
9
|
+
* otherwise-valid batch at the ingestion validation gate. Returns the retained
|
|
10
|
+
* results plus the orphaned task ids so the caller can skip-and-warn, or `null`
|
|
11
|
+
* when there is nothing to filter (not an array, or no active manifest yet),
|
|
12
|
+
* signaling the caller to pass the results through unchanged.
|
|
13
|
+
*/
|
|
14
|
+
export declare function partitionOrphanedAuditResults(results: unknown, activeTaskIds: Set<string>): {
|
|
15
|
+
retained: unknown[];
|
|
16
|
+
orphanedTaskIds: string[];
|
|
17
|
+
} | null;
|
|
@@ -32,3 +32,31 @@ export function updateAuditTaskStatuses(tasks, results) {
|
|
|
32
32
|
};
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Splits raw (unvalidated) audit results into those whose `task_id` is still in
|
|
37
|
+
* the active task manifest and those orphaned by a later re-plan (e.g. selective-
|
|
38
|
+
* deepening tasks pruned in a subsequent round). Orphaned results cannot be
|
|
39
|
+
* ingested — coverage is keyed by the active task set — and must not abort an
|
|
40
|
+
* otherwise-valid batch at the ingestion validation gate. Returns the retained
|
|
41
|
+
* results plus the orphaned task ids so the caller can skip-and-warn, or `null`
|
|
42
|
+
* when there is nothing to filter (not an array, or no active manifest yet),
|
|
43
|
+
* signaling the caller to pass the results through unchanged.
|
|
44
|
+
*/
|
|
45
|
+
export function partitionOrphanedAuditResults(results, activeTaskIds) {
|
|
46
|
+
if (!Array.isArray(results) || activeTaskIds.size === 0) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const orphanedTaskIds = [];
|
|
50
|
+
const retained = results.filter((entry) => {
|
|
51
|
+
const taskId = entry && typeof entry === "object" && !Array.isArray(entry) &&
|
|
52
|
+
typeof entry.task_id === "string"
|
|
53
|
+
? entry.task_id
|
|
54
|
+
: undefined;
|
|
55
|
+
if (taskId !== undefined && !activeTaskIds.has(taskId)) {
|
|
56
|
+
orphanedTaskIds.push(taskId);
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
});
|
|
61
|
+
return { retained, orphanedTaskIds };
|
|
62
|
+
}
|
package/dist/orchestrator.d.ts
CHANGED
|
@@ -3,4 +3,4 @@ export interface TaskBuildOptions {
|
|
|
3
3
|
pass_prefix?: string;
|
|
4
4
|
limit_lenses?: Lens[];
|
|
5
5
|
}
|
|
6
|
-
export declare function buildAuditTasks(unitManifest: UnitManifest, options?: TaskBuildOptions): AuditTask[];
|
|
6
|
+
export declare function buildAuditTasks(unitManifest: UnitManifest, options?: TaskBuildOptions | string): AuditTask[];
|
package/dist/orchestrator.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isLens } from "./types.js";
|
|
2
|
+
import { coerceJsonObjectArg } from "@audit-tools/shared";
|
|
2
3
|
const DEFAULT_LENS_ORDER = [
|
|
3
4
|
"correctness",
|
|
4
5
|
"architecture",
|
|
@@ -43,10 +44,8 @@ function assertUnitManifest(value) {
|
|
|
43
44
|
assertLensArray(unit.required_lenses, `${label}.required_lenses`);
|
|
44
45
|
});
|
|
45
46
|
}
|
|
46
|
-
function normalizedOptions(
|
|
47
|
-
|
|
48
|
-
throw new TypeError("buildAuditTasks options must be an object.");
|
|
49
|
-
}
|
|
47
|
+
function normalizedOptions(rawOptions) {
|
|
48
|
+
const options = coerceJsonObjectArg(rawOptions, "buildAuditTasks options");
|
|
50
49
|
if (options.pass_prefix !== undefined && typeof options.pass_prefix !== "string") {
|
|
51
50
|
throw new TypeError("buildAuditTasks options.pass_prefix must be a string.");
|
|
52
51
|
}
|
|
@@ -23,6 +23,8 @@ export function renderWorkerPrompt(task) {
|
|
|
23
23
|
`${task.artifacts_dir}/audit_tasks.json`;
|
|
24
24
|
const lines = [
|
|
25
25
|
`Audit run: ${task.run_id}`,
|
|
26
|
+
`Repository root: ${task.repo_root}`,
|
|
27
|
+
"Set the shell/tool workdir to the repository root before executing worker_command.",
|
|
26
28
|
`Read: ${tasksPath}`,
|
|
27
29
|
"Scope: review only the tasks listed in the Read file. Do not add tasks,",
|
|
28
30
|
"edit source files, remediate findings, run unrelated audits, or write result_path.",
|
|
@@ -40,6 +42,11 @@ export function renderWorkerPrompt(task) {
|
|
|
40
42
|
" and include verification {verified, needs_followup, concerns, coverage_concerns,",
|
|
41
43
|
" confidence_concerns, followup_tasks}.",
|
|
42
44
|
"Constraint: line_end must not exceed total_lines for that file.",
|
|
45
|
+
"Windows PowerShell: do not pipe an inline foreach statement directly into ConvertTo-Json.",
|
|
46
|
+
"Assign the foreach output to a variable first, then pipe that variable to ConvertTo-Json.",
|
|
47
|
+
"PowerShell also unwraps single-element arrays: @(@{...}) collapses to one object, so a one-result",
|
|
48
|
+
"submission serializes as an object (not a 1-element array) and is rejected. Wrap it yourself:",
|
|
49
|
+
"'[' + (ConvertTo-Json $obj -Depth 12) + ']', or build the array with Write-Output -NoEnumerate.",
|
|
43
50
|
`Write only the JSON array of AuditResult objects to: ${task.audit_results_path}`,
|
|
44
51
|
];
|
|
45
52
|
if (usesDeferredWorkerCommand(task)) {
|
|
@@ -56,6 +63,8 @@ export function renderWorkerPrompt(task) {
|
|
|
56
63
|
return [
|
|
57
64
|
`Task: ${task.run_id}`,
|
|
58
65
|
`Executor: ${task.preferred_executor}`,
|
|
66
|
+
`Repository root: ${task.repo_root}`,
|
|
67
|
+
"Set the shell/tool workdir to the repository root before executing worker_command.",
|
|
59
68
|
"Execute worker_command from task.json exactly.",
|
|
60
69
|
`Command: ${commandArgv}`,
|
|
61
70
|
"Write result to: " + task.result_path,
|
package/dist/quota/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { ResolvedLimits as _ResolvedLimits, LimitConfidence as _LimitConfidence, LimitSource as _LimitSource, HostConcurrencyLimit as _HostConcurrencyLimit, QuotaUsageSnapshot as _QuotaUsageSnapshot, BackoffState as _BackoffState } from "@audit-tools/shared";
|
|
2
|
-
export { resolveLimits, lookupKnownModel, classifyProvider, readQuotaState, writeQuotaState, computeMaxSafeConcurrency, recordWaveOutcome, getQuotaStatePath, decayWeight, applyDecayToEntry, computeBackoffCooldownMs, computeBackoffFailureWeight, computeRampUpConcurrency, setQuotaStateDir, detectRateLimitError, computeCooldownUntil, acquireLock, releaseLock, withFileLock, FileLockTimeoutError, runSlidingWindow, LearnedQuotaSource, CompositeQuotaSource, GenericErrorParser, ClaudeCodeErrorParser, getErrorParserForProvider, } from "@audit-tools/shared";
|
|
3
|
-
export type { LimitResolutionResult, ResolveLimitsOptions, ProviderType, ResolvedLimits, LimitSource, LimitConfidence, HostConcurrencyLimit, HostConcurrencyLimitSource, QuotaState, QuotaStateEntry, ConcurrencyBucket, WaveSchedule, BackoffState, ObservedWaveOutcome, RateLimitDetectionResult, SlidingWindowResult, QuotaSource, QuotaUsageSnapshot, ErrorParser, } from "@audit-tools/shared";
|
|
1
|
+
import type { ResolvedLimits as _ResolvedLimits, LimitConfidence as _LimitConfidence, LimitSource as _LimitSource, HostConcurrencyLimit as _HostConcurrencyLimit, QuotaUsageSnapshot as _QuotaUsageSnapshot, BackoffState as _BackoffState, WaveBindingCap as _WaveBindingCap, DispatchCapacityPoolSummary as _DispatchCapacityPoolSummary } from "@audit-tools/shared";
|
|
2
|
+
export { resolveLimits, lookupKnownModel, classifyProvider, readQuotaState, writeQuotaState, computeMaxSafeConcurrency, recordWaveOutcome, getQuotaStatePath, decayWeight, applyDecayToEntry, computeBackoffCooldownMs, computeBackoffFailureWeight, computeRampUpConcurrency, setQuotaStateDir, detectRateLimitError, computeCooldownUntil, acquireLock, releaseLock, withFileLock, FileLockTimeoutError, runSlidingWindow, LearnedQuotaSource, CompositeQuotaSource, GenericErrorParser, ClaudeCodeErrorParser, getErrorParserForProvider, summarizeDispatchCapacityPools, } from "@audit-tools/shared";
|
|
3
|
+
export type { LimitResolutionResult, ResolveLimitsOptions, ProviderType, ResolvedLimits, LimitSource, LimitConfidence, HostConcurrencyLimit, HostConcurrencyLimitSource, QuotaState, QuotaStateEntry, ConcurrencyBucket, WaveSchedule, BackoffState, ObservedWaveOutcome, RateLimitDetectionResult, SlidingWindowResult, QuotaSource, QuotaUsageSnapshot, ErrorParser, WaveBindingCap, DispatchCapacityPoolSummary, } from "@audit-tools/shared";
|
|
4
4
|
export { scheduleWave, buildProviderModelKey, resolveHostModel } from "@audit-tools/shared";
|
|
5
5
|
export type { ScheduleWaveOptions } from "@audit-tools/shared";
|
|
6
6
|
export { computeDispatchCapacity } from "@audit-tools/shared";
|
|
@@ -25,6 +25,8 @@ export interface DispatchQuota {
|
|
|
25
25
|
wave_size: number;
|
|
26
26
|
estimated_wave_tokens: number;
|
|
27
27
|
cooldown_until: string | null;
|
|
28
|
+
binding_cap?: _WaveBindingCap;
|
|
29
|
+
capacity_pools?: _DispatchCapacityPoolSummary[];
|
|
28
30
|
quota_source_snapshot?: _QuotaUsageSnapshot | null;
|
|
29
31
|
backoff_state?: _BackoffState | null;
|
|
30
32
|
}
|
package/dist/quota/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Re-exported from @audit-tools/shared
|
|
2
|
-
export { resolveLimits, lookupKnownModel, classifyProvider, readQuotaState, writeQuotaState, computeMaxSafeConcurrency, recordWaveOutcome, getQuotaStatePath, decayWeight, applyDecayToEntry, computeBackoffCooldownMs, computeBackoffFailureWeight, computeRampUpConcurrency, setQuotaStateDir, detectRateLimitError, computeCooldownUntil, acquireLock, releaseLock, withFileLock, FileLockTimeoutError, runSlidingWindow, LearnedQuotaSource, CompositeQuotaSource, GenericErrorParser, ClaudeCodeErrorParser, getErrorParserForProvider, } from "@audit-tools/shared";
|
|
2
|
+
export { resolveLimits, lookupKnownModel, classifyProvider, readQuotaState, writeQuotaState, computeMaxSafeConcurrency, recordWaveOutcome, getQuotaStatePath, decayWeight, applyDecayToEntry, computeBackoffCooldownMs, computeBackoffFailureWeight, computeRampUpConcurrency, setQuotaStateDir, detectRateLimitError, computeCooldownUntil, acquireLock, releaseLock, withFileLock, FileLockTimeoutError, runSlidingWindow, LearnedQuotaSource, CompositeQuotaSource, GenericErrorParser, ClaudeCodeErrorParser, getErrorParserForProvider, summarizeDispatchCapacityPools, } from "@audit-tools/shared";
|
|
3
3
|
// Wave scheduler now lives in @audit-tools/shared (single source of truth for
|
|
4
4
|
// both orchestrators). Auditor passes its discovered-limits via the structural
|
|
5
5
|
// DiscoveredRateLimitsInput the shared scheduler accepts.
|
|
@@ -73,3 +73,13 @@ export interface RenderAuditReportOptions {
|
|
|
73
73
|
scope?: AuditScopeManifest;
|
|
74
74
|
}
|
|
75
75
|
export declare function renderAuditReportMarkdown(report: RenderableAuditReport, options?: RenderAuditReportOptions): string;
|
|
76
|
+
/**
|
|
77
|
+
* Re-derive the summary fields that can be computed from the existing findings
|
|
78
|
+
* and work_blocks, bump the contract_version to the current constant, and leave
|
|
79
|
+
* upstream-derived fields that cannot be reconstructed (audited/excluded counts,
|
|
80
|
+
* runtime validation breakdown) untouched.
|
|
81
|
+
*
|
|
82
|
+
* Safe to call on already-promoted `audit-findings.json` files without access to
|
|
83
|
+
* the pruned `.audit-tools/audit` working-bundle intermediates.
|
|
84
|
+
*/
|
|
85
|
+
export declare function normalizeExistingFindingsReport(report: AuditFindingsReport): AuditFindingsReport;
|
|
@@ -200,6 +200,7 @@ export function renderAuditReportMarkdown(report, options = {}) {
|
|
|
200
200
|
lines.push(`- Severity: ${finding.severity}`);
|
|
201
201
|
lines.push(`- Confidence: ${finding.confidence}`);
|
|
202
202
|
lines.push(`- Lens: ${finding.lens}`);
|
|
203
|
+
lines.push(`- Category: ${finding.category}`);
|
|
203
204
|
if (finding.theme_id) {
|
|
204
205
|
lines.push(`- Theme: ${finding.theme_id}`);
|
|
205
206
|
}
|
|
@@ -236,3 +237,25 @@ export function renderAuditReportMarkdown(report, options = {}) {
|
|
|
236
237
|
lines.push("");
|
|
237
238
|
return lines.join("\n");
|
|
238
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* Re-derive the summary fields that can be computed from the existing findings
|
|
242
|
+
* and work_blocks, bump the contract_version to the current constant, and leave
|
|
243
|
+
* upstream-derived fields that cannot be reconstructed (audited/excluded counts,
|
|
244
|
+
* runtime validation breakdown) untouched.
|
|
245
|
+
*
|
|
246
|
+
* Safe to call on already-promoted `audit-findings.json` files without access to
|
|
247
|
+
* the pruned `.audit-tools/audit` working-bundle intermediates.
|
|
248
|
+
*/
|
|
249
|
+
export function normalizeExistingFindingsReport(report) {
|
|
250
|
+
return {
|
|
251
|
+
...report,
|
|
252
|
+
contract_version: AUDIT_FINDINGS_CONTRACT_VERSION,
|
|
253
|
+
summary: {
|
|
254
|
+
...report.summary,
|
|
255
|
+
finding_count: report.findings.length,
|
|
256
|
+
work_block_count: report.work_blocks.length,
|
|
257
|
+
severity_breakdown: severityBreakdown(report.findings),
|
|
258
|
+
lens_breakdown: lensBreakdown(report.findings),
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
}
|
|
@@ -4,7 +4,7 @@ function summarizeFinding(finding) {
|
|
|
4
4
|
.map((file) => file.path)
|
|
5
5
|
.slice(0, 4)
|
|
6
6
|
.join(", ");
|
|
7
|
-
return `- ${finding.id} [${finding.severity}/${finding.lens}] ${finding.title} — ${finding.summary}${files ? ` (files: ${files})` : ""}`;
|
|
7
|
+
return `- ${finding.id} [${finding.severity}/${finding.lens}/${finding.category}] ${finding.title} — ${finding.summary}${files ? ` (files: ${files})` : ""}`;
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
10
|
* Prompt for the optional synthesis-narrative pass. The host groups the
|
|
@@ -27,6 +27,8 @@ export function renderSynthesisNarrativePrompt(report) {
|
|
|
27
27
|
"",
|
|
28
28
|
"Do not re-audit the code, change severities, or invent new findings. Use only the findings below; reference them by their exact `id`.",
|
|
29
29
|
"",
|
|
30
|
+
"When categories distinguish observational contract assessment findings from conceptual design critique findings, keep that distinction visible in themes and top risks instead of flattening them into one architecture bucket.",
|
|
31
|
+
"",
|
|
30
32
|
"## Summary",
|
|
31
33
|
"",
|
|
32
34
|
`- Findings: ${report.summary.finding_count}`,
|
|
@@ -305,7 +305,8 @@ function validateVerificationFollowupTask(task, label, taskId, resultIndex, expe
|
|
|
305
305
|
result_index: resultIndex,
|
|
306
306
|
task_id: taskId,
|
|
307
307
|
field: `${label}.file_paths[${index}]`,
|
|
308
|
-
message: `${label}.file_paths[${index}] references '${path}', which is outside the verification task's file_coverage
|
|
308
|
+
message: `${label}.file_paths[${index}] references '${path}', which is outside the verification task's file_coverage. ` +
|
|
309
|
+
`Followup tasks list files in 'file_paths' (array of strings), not 'file_coverage'; allowed: ${[...allowedPaths].join(", ")}.`,
|
|
309
310
|
});
|
|
310
311
|
}
|
|
311
312
|
}
|
|
@@ -481,7 +482,8 @@ export function validateAuditResults(results, tasks, options = {}) {
|
|
|
481
482
|
result_index: i,
|
|
482
483
|
task_id: taskId,
|
|
483
484
|
field: `file_coverage[${j}].path`,
|
|
484
|
-
message: `file_coverage path '${entry.path}' is not listed in the task file_paths
|
|
485
|
+
message: `file_coverage path '${entry.path}' is not listed in the task file_paths. ` +
|
|
486
|
+
`Declare only assigned files; allowed for this task: ${task.file_paths.join(", ")}.`,
|
|
485
487
|
});
|
|
486
488
|
}
|
|
487
489
|
else if (seenCoveragePaths.has(entryNorm)) {
|
|
@@ -595,7 +597,9 @@ export function validateAuditResults(results, tasks, options = {}) {
|
|
|
595
597
|
result_index: i,
|
|
596
598
|
task_id: taskId,
|
|
597
599
|
field: `${label}.affected_files[${k}].path`,
|
|
598
|
-
message: `affected_files path '${affected.path}' is not in the declared assigned file_coverage
|
|
600
|
+
message: `affected_files path '${affected.path}' is not in the declared assigned file_coverage. ` +
|
|
601
|
+
`Add it to this result's file_coverage first` +
|
|
602
|
+
(task ? `; the task's assigned files are: ${task.file_paths.join(", ")}.` : "."),
|
|
599
603
|
});
|
|
600
604
|
continue;
|
|
601
605
|
}
|
package/docs/development.md
CHANGED
|
@@ -23,9 +23,15 @@ once the work ships.
|
|
|
23
23
|
npm install
|
|
24
24
|
npm run check
|
|
25
25
|
npm test
|
|
26
|
+
npm run test:single -- tests/next-step.test.mjs
|
|
26
27
|
npm run verify:release
|
|
27
28
|
```
|
|
28
29
|
|
|
30
|
+
Run `npm install` from the repository root before running build, check, test, or
|
|
31
|
+
package-scoped workflows in a fresh clone or git worktree. Missing
|
|
32
|
+
`node_modules` can surface as misleading `@audit-tools/shared` export or type
|
|
33
|
+
errors because dependents may resolve stale compiled `dist/` output.
|
|
34
|
+
|
|
29
35
|
The test suite is intentionally contract-heavy. Update tests when changing
|
|
30
36
|
schema shape, prompt contracts, dispatch behavior, installer output, or release
|
|
31
37
|
workflow semantics.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auditor-lambda",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Portable hybrid code-auditing framework for arbitrary repositories.",
|
|
6
6
|
"type": "module",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"build": "tsc -p tsconfig.json",
|
|
25
25
|
"check": "tsc -p tsconfig.json --noEmit",
|
|
26
26
|
"test": "npm run build && node --import tsx/esm --test tests/*.test.mjs",
|
|
27
|
+
"test:single": "npm run build && node --import tsx/esm --test",
|
|
27
28
|
"release:patch": "node scripts/release-and-publish.mjs patch --bump-only",
|
|
28
29
|
"release:minor": "node scripts/release-and-publish.mjs minor --bump-only",
|
|
29
30
|
"release:major": "node scripts/release-and-publish.mjs major --bump-only",
|
|
@@ -86,7 +86,20 @@
|
|
|
86
86
|
"reproduction": { "type": "array", "minItems": 1, "items": { "type": "string" } },
|
|
87
87
|
"systemic": { "type": "boolean" },
|
|
88
88
|
"related_findings": { "type": "array", "minItems": 1, "items": { "type": "string" } },
|
|
89
|
-
"theme_id": { "type": "string" }
|
|
89
|
+
"theme_id": { "type": "string" },
|
|
90
|
+
"contract_goal_id": { "type": "string" },
|
|
91
|
+
"contract_obligation_ids": {
|
|
92
|
+
"type": "array",
|
|
93
|
+
"items": { "type": "string" }
|
|
94
|
+
},
|
|
95
|
+
"verification_obligation_ids": {
|
|
96
|
+
"type": "array",
|
|
97
|
+
"items": { "type": "string" }
|
|
98
|
+
},
|
|
99
|
+
"targeted_commands": {
|
|
100
|
+
"type": "array",
|
|
101
|
+
"items": { "type": "string" }
|
|
102
|
+
}
|
|
90
103
|
},
|
|
91
104
|
"additionalProperties": false
|
|
92
105
|
}
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
},
|
|
57
57
|
"source": {
|
|
58
58
|
"type": "string",
|
|
59
|
-
"enum": ["explicit_config", "cli_flags", "known_metadata", "learned", "default"],
|
|
59
|
+
"enum": ["explicit_config", "cli_flags", "known_metadata", "provider_default", "learned", "default"],
|
|
60
60
|
"description": "Where the resolved limits came from."
|
|
61
61
|
},
|
|
62
62
|
"host_concurrency_limit": {
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
},
|
|
76
76
|
"source": {
|
|
77
77
|
"type": "string",
|
|
78
|
-
"enum": ["cli_flags", "session_config", "environment"]
|
|
78
|
+
"enum": ["cli_flags", "host_reported", "session_config", "environment"]
|
|
79
79
|
},
|
|
80
80
|
"description": {
|
|
81
81
|
"type": "string",
|
|
@@ -98,6 +98,90 @@
|
|
|
98
98
|
"format": "date-time",
|
|
99
99
|
"description": "If non-null, the host should wait until this timestamp before launching the next wave."
|
|
100
100
|
},
|
|
101
|
+
"binding_cap": {
|
|
102
|
+
"type": "string",
|
|
103
|
+
"enum": ["rpm", "tpm", "learned", "fallback", "first_contact", "cooldown", "host_concurrency", "none"],
|
|
104
|
+
"description": "The most constraining cap across capacity pools."
|
|
105
|
+
},
|
|
106
|
+
"capacity_pools": {
|
|
107
|
+
"type": "array",
|
|
108
|
+
"description": "Per-pool allocation summaries when dispatch capacity was computed across one or more pools.",
|
|
109
|
+
"items": {
|
|
110
|
+
"type": "object",
|
|
111
|
+
"required": [
|
|
112
|
+
"pool_id",
|
|
113
|
+
"slots",
|
|
114
|
+
"model",
|
|
115
|
+
"confidence",
|
|
116
|
+
"source",
|
|
117
|
+
"resolved_limits",
|
|
118
|
+
"host_concurrency_limit",
|
|
119
|
+
"cooldown_until",
|
|
120
|
+
"estimated_wave_tokens",
|
|
121
|
+
"binding_cap"
|
|
122
|
+
],
|
|
123
|
+
"additionalProperties": false,
|
|
124
|
+
"properties": {
|
|
125
|
+
"pool_id": { "type": "string", "minLength": 1 },
|
|
126
|
+
"slots": { "type": "integer", "minimum": 1 },
|
|
127
|
+
"model": { "type": ["string", "null"] },
|
|
128
|
+
"confidence": { "type": "string", "enum": ["high", "medium", "low"] },
|
|
129
|
+
"source": {
|
|
130
|
+
"type": "string",
|
|
131
|
+
"enum": ["explicit_config", "cli_flags", "known_metadata", "provider_default", "learned", "default"]
|
|
132
|
+
},
|
|
133
|
+
"resolved_limits": {
|
|
134
|
+
"type": "object",
|
|
135
|
+
"required": [
|
|
136
|
+
"context_tokens",
|
|
137
|
+
"output_tokens",
|
|
138
|
+
"requests_per_minute",
|
|
139
|
+
"input_tokens_per_minute",
|
|
140
|
+
"output_tokens_per_minute"
|
|
141
|
+
],
|
|
142
|
+
"additionalProperties": false,
|
|
143
|
+
"properties": {
|
|
144
|
+
"context_tokens": { "type": "integer", "minimum": 1 },
|
|
145
|
+
"output_tokens": { "type": "integer", "minimum": 1 },
|
|
146
|
+
"requests_per_minute": { "type": ["integer", "null"], "minimum": 1 },
|
|
147
|
+
"input_tokens_per_minute": { "type": ["integer", "null"], "minimum": 1 },
|
|
148
|
+
"output_tokens_per_minute": { "type": ["integer", "null"], "minimum": 1 }
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
"host_concurrency_limit": {
|
|
152
|
+
"type": ["object", "null"],
|
|
153
|
+
"required": ["active_subagents", "source", "description"],
|
|
154
|
+
"additionalProperties": false,
|
|
155
|
+
"properties": {
|
|
156
|
+
"active_subagents": { "type": "integer", "minimum": 1 },
|
|
157
|
+
"source": {
|
|
158
|
+
"type": "string",
|
|
159
|
+
"enum": ["cli_flags", "host_reported", "session_config", "environment"]
|
|
160
|
+
},
|
|
161
|
+
"description": { "type": "string", "minLength": 1 }
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
"cooldown_until": { "type": ["string", "null"], "format": "date-time" },
|
|
165
|
+
"estimated_wave_tokens": { "type": "integer", "minimum": 0 },
|
|
166
|
+
"binding_cap": {
|
|
167
|
+
"type": "string",
|
|
168
|
+
"enum": ["rpm", "tpm", "learned", "fallback", "first_contact", "cooldown", "host_concurrency", "none"]
|
|
169
|
+
},
|
|
170
|
+
"quota_source_snapshot": {
|
|
171
|
+
"type": ["object", "null"],
|
|
172
|
+
"additionalProperties": false,
|
|
173
|
+
"properties": {
|
|
174
|
+
"remaining_pct": { "type": ["number", "null"] },
|
|
175
|
+
"reset_at": { "type": ["string", "null"], "format": "date-time" },
|
|
176
|
+
"requests_remaining": { "type": ["integer", "null"] },
|
|
177
|
+
"tokens_remaining": { "type": ["integer", "null"] },
|
|
178
|
+
"captured_at": { "type": "string", "format": "date-time" },
|
|
179
|
+
"source": { "type": "string" }
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
},
|
|
101
185
|
"quota_source_snapshot": {
|
|
102
186
|
"type": ["object", "null"],
|
|
103
187
|
"description": "Real-time usage snapshot from a QuotaSource, if available.",
|