auditor-lambda 0.2.12 → 0.2.13

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/dist/cli.js CHANGED
@@ -400,7 +400,7 @@ async function ingestBatchAuditResults(options) {
400
400
  }
401
401
  const bundle = lastStep?.updated_bundle ??
402
402
  (await loadArtifactBundle(options.artifactsDir));
403
- const state = lastStep?.audit_state ?? deriveAuditState(bundle);
403
+ const state = deriveAuditState(bundle);
404
404
  const decision = decideNextStep(bundle);
405
405
  return {
406
406
  batchFiles,
@@ -720,7 +720,7 @@ async function cmdRunToCompletion(argv) {
720
720
  if (preferredExecutor === "agent" && provider.name === LOCAL_SUBPROCESS_PROVIDER_NAME) {
721
721
  const blocker = buildManualReviewBlocker(provider.name);
722
722
  const blockedState = buildBlockedAuditState({
723
- state: bundle.audit_state ?? decision.state,
723
+ state: decision.state,
724
724
  obligationId,
725
725
  executor: preferredExecutor,
726
726
  blocker,
@@ -784,7 +784,7 @@ async function cmdRunToCompletion(argv) {
784
784
  return;
785
785
  }
786
786
  if (!preferredExecutor) {
787
- const state = bundle.audit_state ?? decision.state;
787
+ const state = decision.state;
788
788
  await clearDispatchFiles(artifactsDir);
789
789
  await emitEnvelope({
790
790
  root,
@@ -1251,12 +1251,12 @@ async function cmdRunToCompletion(argv) {
1251
1251
  const shouldBlock = workerResult.status === "failed" || workerResult.status === "blocked";
1252
1252
  const state = shouldBlock
1253
1253
  ? buildBlockedAuditState({
1254
- state: bundleAfter.audit_state ?? deriveAuditState(bundleAfter),
1254
+ state: deriveAuditState(bundleAfter),
1255
1255
  obligationId: workerResult.obligation_id,
1256
1256
  executor: workerResult.selected_executor,
1257
1257
  blocker: buildWorkerFailureBlocker(workerResult),
1258
1258
  })
1259
- : bundleAfter.audit_state ?? deriveAuditState(bundleAfter);
1259
+ : deriveAuditState(bundleAfter);
1260
1260
  if (shouldBlock) {
1261
1261
  await writeCoreArtifacts(artifactsDir, {
1262
1262
  ...bundleAfter,
@@ -1285,7 +1285,7 @@ async function cmdRunToCompletion(argv) {
1285
1285
  }
1286
1286
  const bundle = await loadArtifactBundle(artifactsDir);
1287
1287
  const decision = decideNextStep(bundle);
1288
- const state = bundle.audit_state ?? decision.state;
1288
+ const state = decision.state;
1289
1289
  if (state.status === "complete") {
1290
1290
  await clearDispatchFiles(artifactsDir);
1291
1291
  }
@@ -1,4 +1,4 @@
1
- import { isNodeModulesOrGit, isBuildOutput, isVendorPath, isBinaryArtifact, isLicensePath, isLockfilePath, isLogPath, isDocPath, isGeneratedInstallArtifactPath, normalizeExtractorPath, } from "./pathPatterns.js";
1
+ import { isNodeModulesOrGit, isBuildOutput, isVendorPath, isBinaryArtifact, isLicensePath, isLockfilePath, isLogPath, isDocPath, isAuditArtifactPath, isGeneratedInstallArtifactPath, normalizeExtractorPath, } from "./pathPatterns.js";
2
2
  function inferDisposition(path) {
3
3
  const normalized = normalizeExtractorPath(path);
4
4
  if (isNodeModulesOrGit(normalized)) {
@@ -26,6 +26,13 @@ function inferDisposition(path) {
26
26
  if (isLockfilePath(normalized)) {
27
27
  return { path, status: "generated", reason: "Lockfile excluded from code audit scope." };
28
28
  }
29
+ if (isAuditArtifactPath(normalized)) {
30
+ return {
31
+ path,
32
+ status: "generated",
33
+ reason: "Generated audit artifact.",
34
+ };
35
+ }
29
36
  if (isDocPath(normalized)) {
30
37
  return { path, status: "doc_only", reason: "Documentation artifact." };
31
38
  }
@@ -14,6 +14,7 @@ export declare function isLicensePath(normalized: string): boolean;
14
14
  export declare function isLockfilePath(normalized: string): boolean;
15
15
  export declare function isDocPath(normalized: string): boolean;
16
16
  export declare function isGeneratedInstallArtifactPath(normalized: string): boolean;
17
+ export declare function isAuditArtifactPath(normalized: string): boolean;
17
18
  export declare function isTestPath(normalized: string): boolean;
18
19
  export declare function isInterfacePath(normalized: string): boolean;
19
20
  export declare function isDataLayerPath(normalized: string): boolean;
@@ -103,6 +103,9 @@ export function isDocPath(normalized) {
103
103
  export function isGeneratedInstallArtifactPath(normalized) {
104
104
  return normalized.startsWith(".audit-code/install/");
105
105
  }
106
+ export function isAuditArtifactPath(normalized) {
107
+ return splitSegments(normalized).some((segment) => segment.startsWith(".audit-artifacts"));
108
+ }
106
109
  export function isTestPath(normalized) {
107
110
  return includesAny(normalized, TEST_KEYWORDS);
108
111
  }
@@ -9,6 +9,7 @@ import type { GraphBundle } from "../types/graph.js";
9
9
  import type { RiskRegister } from "../types/risk.js";
10
10
  import type { RuntimeValidationReport, RuntimeValidationTaskManifest } from "../types/runtimeValidation.js";
11
11
  import type { SurfaceManifest } from "../types/surfaces.js";
12
+ import type { ToolingManifest } from "../types/toolingManifest.js";
12
13
  type ArtifactPayloadMap = {
13
14
  repo_manifest: RepoManifest;
14
15
  file_disposition: FileDisposition;
@@ -29,6 +30,7 @@ type ArtifactPayloadMap = {
29
30
  audit_report: string;
30
31
  audit_state: AuditState;
31
32
  artifact_metadata: ArtifactMetadataManifest;
33
+ tooling_manifest: ToolingManifest;
32
34
  };
33
35
  /**
34
36
  * Audit artifacts accumulate phase-by-phase as the orchestrator advances.
@@ -63,6 +65,7 @@ export declare const ARTIFACT_DEFINITIONS: {
63
65
  readonly audit_report: ArtifactDefinition<"audit_report">;
64
66
  readonly audit_state: ArtifactDefinition<"audit_state">;
65
67
  readonly artifact_metadata: ArtifactDefinition<"artifact_metadata">;
68
+ readonly tooling_manifest: ArtifactDefinition<"tooling_manifest">;
66
69
  };
67
70
  export declare const ARTIFACT_FILE_TO_BUNDLE_KEY: Record<string, ArtifactBundleKey>;
68
71
  export declare function getArtifactValue(bundle: ArtifactBundle, artifactName: string): unknown;
@@ -1,6 +1,7 @@
1
1
  import { cp, rm, unlink } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { isFileMissingError, readOptionalJsonFile, readOptionalNdjsonFile, readOptionalTextFile, writeJsonFile, writeNdjsonFile, writeTextFile, } from "./json.js";
4
+ import { buildToolingManifest } from "./toolingManifest.js";
4
5
  function jsonArtifact(fileName, phase) {
5
6
  return {
6
7
  fileName,
@@ -45,6 +46,7 @@ export const ARTIFACT_DEFINITIONS = {
45
46
  audit_report: textArtifact("audit-report.md", "reporting"),
46
47
  audit_state: jsonArtifact("audit_state.json", "supervisor"),
47
48
  artifact_metadata: jsonArtifact("artifact_metadata.json", "supervisor"),
49
+ tooling_manifest: jsonArtifact("tooling_manifest.json", "supervisor"),
48
50
  };
49
51
  const ARTIFACT_ENTRIES = Object.entries(ARTIFACT_DEFINITIONS);
50
52
  export const ARTIFACT_FILE_TO_BUNDLE_KEY = Object.fromEntries(ARTIFACT_ENTRIES.map(([key, definition]) => [definition.fileName, key]));
@@ -62,6 +64,7 @@ export async function loadArtifactBundle(root) {
62
64
  bundleRecord[key] = value;
63
65
  }
64
66
  }
67
+ bundle.tooling_manifest = await buildToolingManifest();
65
68
  return bundle;
66
69
  }
67
70
  export async function writeCoreArtifacts(root, bundle) {
@@ -0,0 +1,2 @@
1
+ import type { ToolingManifest } from "../types/toolingManifest.js";
2
+ export declare function buildToolingManifest(): Promise<ToolingManifest>;
@@ -0,0 +1,75 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readdir, readFile, stat } from "node:fs/promises";
3
+ import { dirname, join, relative, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
6
+ const TOOLING_INPUTS = [
7
+ "audit-code.mjs",
8
+ "audit-code-wrapper-lib.mjs",
9
+ "package.json",
10
+ "dist",
11
+ "schemas",
12
+ "skills/audit-code",
13
+ ];
14
+ async function pathExists(path) {
15
+ try {
16
+ await stat(path);
17
+ return true;
18
+ }
19
+ catch {
20
+ return false;
21
+ }
22
+ }
23
+ async function collectFiles(path) {
24
+ const info = await stat(path);
25
+ if (info.isFile()) {
26
+ return [path];
27
+ }
28
+ if (!info.isDirectory()) {
29
+ return [];
30
+ }
31
+ const entries = await readdir(path, { withFileTypes: true });
32
+ const files = [];
33
+ for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
34
+ files.push(...(await collectFiles(join(path, entry.name))));
35
+ }
36
+ return files;
37
+ }
38
+ async function readPackageVersion() {
39
+ const packageJsonPath = join(PACKAGE_ROOT, "package.json");
40
+ if (!(await pathExists(packageJsonPath))) {
41
+ return null;
42
+ }
43
+ try {
44
+ const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
45
+ return typeof packageJson.version === "string" ? packageJson.version : null;
46
+ }
47
+ catch {
48
+ return null;
49
+ }
50
+ }
51
+ export async function buildToolingManifest() {
52
+ const hash = createHash("sha256");
53
+ const existingInputs = [];
54
+ for (const input of TOOLING_INPUTS) {
55
+ const absolute = join(PACKAGE_ROOT, input);
56
+ if (!(await pathExists(absolute))) {
57
+ continue;
58
+ }
59
+ existingInputs.push(input);
60
+ const files = await collectFiles(absolute);
61
+ for (const file of files.sort((a, b) => a.localeCompare(b))) {
62
+ hash.update(relative(PACKAGE_ROOT, file).replace(/\\/g, "/"));
63
+ hash.update("\n");
64
+ hash.update(await readFile(file));
65
+ hash.update("\n");
66
+ }
67
+ }
68
+ return {
69
+ generated_at: new Date().toISOString(),
70
+ package_root: PACKAGE_ROOT,
71
+ package_version: await readPackageVersion(),
72
+ implementation_hash: hash.digest("hex"),
73
+ inputs: existingInputs,
74
+ };
75
+ }
@@ -108,8 +108,12 @@ export async function advanceAudit(bundle, options = {}) {
108
108
  catch (error) {
109
109
  throw formatExecutorFailure(selectedExecutor, selectedObligation, error);
110
110
  }
111
- const metadata = computeArtifactMetadata(run.updated, bundle.artifact_metadata);
112
- const metadataBundle = { ...run.updated, artifact_metadata: metadata };
111
+ const metadata = computeArtifactMetadata(run.updated, bundle.artifact_metadata, [...run.artifacts_written, "tooling_manifest.json"]);
112
+ const metadataBundle = {
113
+ ...run.updated,
114
+ tooling_manifest: bundle.tooling_manifest,
115
+ artifact_metadata: metadata,
116
+ };
113
117
  const updatedState = deriveAuditState(metadataBundle);
114
118
  updatedState.last_executor = selectedExecutor;
115
119
  updatedState.last_obligation = selectedObligation ?? undefined;
@@ -1,4 +1,4 @@
1
1
  import type { ArtifactMetadataManifest } from "../types/artifactMetadata.js";
2
2
  import type { ArtifactBundle } from "../io/artifacts.js";
3
3
  export declare function present(bundle: ArtifactBundle, artifactName: string): boolean;
4
- export declare function computeArtifactMetadata(bundle: ArtifactBundle, previous?: ArtifactMetadataManifest): ArtifactMetadataManifest;
4
+ export declare function computeArtifactMetadata(bundle: ArtifactBundle, previous?: ArtifactMetadataManifest, updatedArtifacts?: Iterable<string>): ArtifactMetadataManifest;
@@ -28,6 +28,14 @@ function normalizeForMetadataHash(artifactName, value) {
28
28
  const { generated_at: _generatedAt, ...rest } = record;
29
29
  return rest;
30
30
  }
31
+ if (artifactName === "tooling_manifest.json" &&
32
+ value &&
33
+ typeof value === "object" &&
34
+ !Array.isArray(value)) {
35
+ const record = value;
36
+ const { generated_at: _generatedAt, ...rest } = record;
37
+ return rest;
38
+ }
31
39
  return value;
32
40
  }
33
41
  function buildReverseDependencyMap() {
@@ -72,8 +80,9 @@ export function present(bundle, artifactName) {
72
80
  const value = getArtifactValue(bundle, artifactName);
73
81
  return value !== undefined && value !== null;
74
82
  }
75
- export function computeArtifactMetadata(bundle, previous) {
83
+ export function computeArtifactMetadata(bundle, previous, updatedArtifacts = []) {
76
84
  const artifacts = {};
85
+ const updated = new Set(updatedArtifacts);
77
86
  const presentArtifacts = Object.keys(REVERSE_DEPENDENCY_MAP).filter((artifactName) => artifactName !== "artifact_metadata.json" &&
78
87
  present(bundle, artifactName));
79
88
  const orderedArtifacts = computeDependencyFirstOrder(presentArtifacts);
@@ -83,8 +92,12 @@ export function computeArtifactMetadata(bundle, previous) {
83
92
  const value = getArtifactValue(bundle, artifactName);
84
93
  if (value === undefined || value === null)
85
94
  continue;
86
- const contentHash = hashValue(normalizeForMetadataHash(artifactName, value));
87
95
  const previousEntry = previous?.artifacts[artifactName];
96
+ if (previousEntry && !updated.has(artifactName)) {
97
+ artifacts[artifactName] = previousEntry;
98
+ continue;
99
+ }
100
+ const contentHash = hashValue(normalizeForMetadataHash(artifactName, value));
88
101
  const dependencyRevisions = Object.fromEntries((REVERSE_DEPENDENCY_MAP[artifactName] ?? [])
89
102
  .filter((dependencyName) => dependencyName !== "artifact_metadata.json")
90
103
  .sort()
@@ -1,4 +1,7 @@
1
1
  export const ARTIFACT_DEPENDENCY_MAP = {
2
+ "tooling_manifest.json": [
3
+ "repo_manifest.json",
4
+ ],
2
5
  "repo_manifest.json": [
3
6
  "file_disposition.json",
4
7
  "unit_manifest.json",
@@ -174,6 +174,18 @@ export function runResultIngestionExecutor(bundle, results) {
174
174
  const flowCoverage = bundle.critical_flows
175
175
  ? buildFlowCoverage(bundle.critical_flows, updatedCoverageMatrix)
176
176
  : bundle.flow_coverage;
177
+ const runtimeCommand = bundle.runtime_validation_tasks?.tasks.find((task) => task.command && task.command.length > 0)?.command;
178
+ const runtimeValidationTasks = bundle.unit_manifest && flowCoverage
179
+ ? buildRuntimeValidationTasks({
180
+ unitManifest: bundle.unit_manifest,
181
+ criticalFlows: bundle.critical_flows,
182
+ flowCoverage,
183
+ command: runtimeCommand,
184
+ })
185
+ : bundle.runtime_validation_tasks;
186
+ const runtimeValidationReport = runtimeValidationTasks
187
+ ? mergeRuntimeValidationReport(runtimeValidationTasks, bundle.runtime_validation_report)
188
+ : bundle.runtime_validation_report;
177
189
  const requeuePayload = buildRequeuePayload(updatedCoverageMatrix, bundle.critical_flows, flowCoverage, bundle.external_analyzer_results);
178
190
  const mergedResults = [...(bundle.audit_results ?? []), ...results];
179
191
  const updatedAuditTasks = updateAuditTaskStatuses(bundle.audit_tasks, mergedResults);
@@ -182,6 +194,8 @@ export function runResultIngestionExecutor(bundle, results) {
182
194
  ...bundle,
183
195
  coverage_matrix: updatedCoverageMatrix,
184
196
  flow_coverage: flowCoverage,
197
+ runtime_validation_tasks: runtimeValidationTasks,
198
+ runtime_validation_report: runtimeValidationReport,
185
199
  audit_results: mergedResults,
186
200
  audit_tasks: updatedAuditTasks,
187
201
  requeue_tasks: requeuePayload.tasks,
@@ -190,6 +204,8 @@ export function runResultIngestionExecutor(bundle, results) {
190
204
  artifacts_written: [
191
205
  "coverage_matrix.json",
192
206
  "flow_coverage.json",
207
+ ...(runtimeValidationTasks ? ["runtime_validation_tasks.json"] : []),
208
+ ...(runtimeValidationReport ? ["runtime_validation_report.json"] : []),
193
209
  "audit_results.jsonl",
194
210
  "audit_tasks.json",
195
211
  "requeue_tasks.json",
@@ -23,6 +23,14 @@ function normalizeForMetadataHash(artifactName, value) {
23
23
  const { generated_at: _generatedAt, ...rest } = record;
24
24
  return rest;
25
25
  }
26
+ if (artifactName === "tooling_manifest.json" &&
27
+ value &&
28
+ typeof value === "object" &&
29
+ !Array.isArray(value)) {
30
+ const record = value;
31
+ const { generated_at: _generatedAt, ...rest } = record;
32
+ return rest;
33
+ }
26
34
  return value;
27
35
  }
28
36
  function computeContentHash(artifactName, bundle) {
@@ -33,6 +41,18 @@ function computeContentHash(artifactName, bundle) {
33
41
  .update(stableStringify(normalizeForMetadataHash(artifactName, value)))
34
42
  .digest("hex");
35
43
  }
44
+ function buildReverseDependencyMap() {
45
+ const reverse = {};
46
+ for (const [upstream, downstreamList] of Object.entries(ARTIFACT_DEPENDENCY_MAP)) {
47
+ reverse[upstream] ??= [];
48
+ for (const downstream of downstreamList) {
49
+ reverse[downstream] ??= [];
50
+ reverse[downstream].push(upstream);
51
+ }
52
+ }
53
+ return reverse;
54
+ }
55
+ const REVERSE_DEPENDENCY_MAP = buildReverseDependencyMap();
36
56
  export function computeStaleArtifacts(bundle) {
37
57
  const stale = new Set();
38
58
  const metadata = bundle.artifact_metadata;
@@ -40,6 +60,15 @@ export function computeStaleArtifacts(bundle) {
40
60
  for (const [artifactName, entry] of Object.entries(metadata.artifacts)) {
41
61
  if (!present(bundle, artifactName))
42
62
  continue;
63
+ const expectedDependencies = [...(REVERSE_DEPENDENCY_MAP[artifactName] ?? [])]
64
+ .filter((dependencyName) => dependencyName !== "artifact_metadata.json")
65
+ .sort();
66
+ const recordedDependencies = Object.keys(entry.dependency_revisions).sort();
67
+ if (stableStringify(expectedDependencies) !==
68
+ stableStringify(recordedDependencies)) {
69
+ stale.add(artifactName);
70
+ continue;
71
+ }
43
72
  let isStale = false;
44
73
  for (const [dependencyName, recordedRevision] of Object.entries(entry.dependency_revisions)) {
45
74
  if (!present(bundle, dependencyName)) {
@@ -51,7 +80,7 @@ export function computeStaleArtifacts(bundle) {
51
80
  }
52
81
  const dependencyEntry = metadata.artifacts[dependencyName];
53
82
  if (!dependencyEntry) {
54
- if (recordedRevision > 0) {
83
+ if (present(bundle, dependencyName) || recordedRevision > 0) {
55
84
  isStale = true;
56
85
  break;
57
86
  }
@@ -70,6 +99,9 @@ export function computeStaleArtifacts(bundle) {
70
99
  }
71
100
  }
72
101
  for (const [upstream, downstreamList] of Object.entries(ARTIFACT_DEPENDENCY_MAP)) {
102
+ if (upstream === "tooling_manifest.json" && !present(bundle, upstream)) {
103
+ continue;
104
+ }
73
105
  if (!present(bundle, upstream)) {
74
106
  for (const downstream of downstreamList) {
75
107
  const hasMetadataEntry = Boolean(metadata?.artifacts[downstream]);
@@ -79,5 +111,20 @@ export function computeStaleArtifacts(bundle) {
79
111
  }
80
112
  }
81
113
  }
114
+ let changed = true;
115
+ while (changed) {
116
+ changed = false;
117
+ for (const [upstream, downstreamList] of Object.entries(ARTIFACT_DEPENDENCY_MAP)) {
118
+ if (!stale.has(upstream)) {
119
+ continue;
120
+ }
121
+ for (const downstream of downstreamList) {
122
+ if (present(bundle, downstream) && !stale.has(downstream)) {
123
+ stale.add(downstream);
124
+ changed = true;
125
+ }
126
+ }
127
+ }
128
+ }
82
129
  return stale;
83
130
  }
@@ -0,0 +1,7 @@
1
+ export interface ToolingManifest {
2
+ generated_at: string;
3
+ package_root: string;
4
+ package_version: string | null;
5
+ implementation_hash: string;
6
+ inputs: string[];
7
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -40,6 +40,9 @@ export function validateArtifactBundle(bundle) {
40
40
  if (bundle.external_analyzer_results) {
41
41
  issues.push(...requireKeys(bundle.external_analyzer_results, "external_analyzer_results", ["tool", "results"]));
42
42
  }
43
+ if (bundle.tooling_manifest) {
44
+ issues.push(...requireKeys(bundle.tooling_manifest, "tooling_manifest", ["generated_at", "package_root", "implementation_hash", "inputs"]));
45
+ }
43
46
  const repoManifestFiles = asArray(bundle.repo_manifest?.files);
44
47
  const fileDispositionEntries = asArray(bundle.file_disposition?.files);
45
48
  const unitManifestUnits = asArray(bundle.unit_manifest?.units);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auditor-lambda",
3
- "version": "0.2.12",
3
+ "version": "0.2.13",
4
4
  "private": false,
5
5
  "description": "Portable hybrid code-auditing framework for arbitrary repositories.",
6
6
  "type": "module",
@@ -46,6 +46,15 @@ audit-code
46
46
 
47
47
  from the target repository root.
48
48
 
49
+ When developing inside the `auditor-lambda` repository itself, prefer:
50
+
51
+ ```bash
52
+ node audit-code.mjs
53
+ ```
54
+
55
+ That keeps the run pinned to the local wrapper and local `dist/` output instead
56
+ of whichever global `audit-code` binary happens to be on `PATH`.
57
+
49
58
  Debug one-step mode:
50
59
 
51
60
  ```bash
@@ -18,7 +18,7 @@ To move the state machine forward, execute the backend framework using your term
18
18
  audit-code
19
19
  ```
20
20
 
21
- _(If the wrapper is only available as a package dependency in the current repository, `npx audit-code` is equivalent. If developing locally against this repository, run `node audit-code.mjs`.)_
21
+ _(If the wrapper is only available as a package dependency in the current repository, `npx audit-code` is equivalent. If you are developing inside the `auditor-lambda` repository itself, prefer `node audit-code.mjs` so the run uses the local wrapper and local `dist/` output instead of a potentially stale global install.)_
22
22
 
23
23
  ## Step 2: Handle Blockages (The "Thinking" Phase)
24
24