audit-tools 0.51.10 → 0.52.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.
Files changed (83) hide show
  1. package/dist/audit/orchestrator/executorRunners.d.ts.map +1 -1
  2. package/dist/audit/orchestrator/executorRunners.js +14 -3
  3. package/dist/audit/orchestrator/executorRunners.js.map +1 -1
  4. package/dist/audit/orchestrator/resultBaseline.d.ts +1 -1
  5. package/dist/audit/orchestrator/synthesisExecutors.d.ts +10 -3
  6. package/dist/audit/orchestrator/synthesisExecutors.d.ts.map +1 -1
  7. package/dist/audit/orchestrator/synthesisExecutors.js +5 -4
  8. package/dist/audit/orchestrator/synthesisExecutors.js.map +1 -1
  9. package/dist/audit/reporting/synthesis.d.ts +7 -3
  10. package/dist/audit/reporting/synthesis.d.ts.map +1 -1
  11. package/dist/audit/reporting/synthesis.js +13 -2
  12. package/dist/audit/reporting/synthesis.js.map +1 -1
  13. package/dist/remediate/phases/close.d.ts +11 -4
  14. package/dist/remediate/phases/close.d.ts.map +1 -1
  15. package/dist/remediate/phases/close.js +78 -14
  16. package/dist/remediate/phases/close.js.map +1 -1
  17. package/dist/remediate/phases/closeVerifyHeadEvidence.d.ts +19 -35
  18. package/dist/remediate/phases/closeVerifyHeadEvidence.d.ts.map +1 -1
  19. package/dist/remediate/phases/closeVerifyHeadEvidence.js +38 -12
  20. package/dist/remediate/phases/closeVerifyHeadEvidence.js.map +1 -1
  21. package/dist/remediate/phases/closeVerifyLandingGates.d.ts +85 -0
  22. package/dist/remediate/phases/closeVerifyLandingGates.d.ts.map +1 -0
  23. package/dist/remediate/phases/closeVerifyLandingGates.js +48 -0
  24. package/dist/remediate/phases/closeVerifyLandingGates.js.map +1 -0
  25. package/dist/remediate/state/types.d.ts +31 -4
  26. package/dist/remediate/state/types.d.ts.map +1 -1
  27. package/dist/remediate/state/types.js +12 -1
  28. package/dist/remediate/state/types.js.map +1 -1
  29. package/dist/remediate/steps/contractPipeline.d.ts +23 -1
  30. package/dist/remediate/steps/contractPipeline.d.ts.map +1 -1
  31. package/dist/remediate/steps/contractPipeline.js +122 -10
  32. package/dist/remediate/steps/contractPipeline.js.map +1 -1
  33. package/dist/remediate/steps/contractPipelinePrompts.d.ts.map +1 -1
  34. package/dist/remediate/steps/contractPipelinePrompts.js +11 -7
  35. package/dist/remediate/steps/contractPipelinePrompts.js.map +1 -1
  36. package/dist/remediate/steps/dispatch/hostHandoff.d.ts.map +1 -1
  37. package/dist/remediate/steps/dispatch/hostHandoff.js +41 -5
  38. package/dist/remediate/steps/dispatch/hostHandoff.js.map +1 -1
  39. package/dist/remediate/steps/nextStep.d.ts.map +1 -1
  40. package/dist/remediate/steps/nextStep.js +23 -7
  41. package/dist/remediate/steps/nextStep.js.map +1 -1
  42. package/dist/remediate/types/options.d.ts +6 -0
  43. package/dist/remediate/types/options.d.ts.map +1 -1
  44. package/dist/shared/contentKey.d.ts +4 -4
  45. package/dist/shared/contentKey.js +3 -3
  46. package/dist/shared/git.d.ts +31 -0
  47. package/dist/shared/git.d.ts.map +1 -1
  48. package/dist/shared/git.js +105 -0
  49. package/dist/shared/git.js.map +1 -1
  50. package/dist/shared/index.d.ts +7 -5
  51. package/dist/shared/index.d.ts.map +1 -1
  52. package/dist/shared/index.js +5 -4
  53. package/dist/shared/index.js.map +1 -1
  54. package/dist/shared/prompts.d.ts +14 -3
  55. package/dist/shared/prompts.d.ts.map +1 -1
  56. package/dist/shared/prompts.js +17 -4
  57. package/dist/shared/prompts.js.map +1 -1
  58. package/dist/shared/reporting/auditDeliverable.d.ts +8 -3
  59. package/dist/shared/reporting/auditDeliverable.d.ts.map +1 -1
  60. package/dist/shared/reporting/auditDeliverable.js +10 -3
  61. package/dist/shared/reporting/auditDeliverable.js.map +1 -1
  62. package/dist/shared/tooling/landingGates.d.ts +118 -0
  63. package/dist/shared/tooling/landingGates.d.ts.map +1 -0
  64. package/dist/shared/tooling/landingGates.js +314 -0
  65. package/dist/shared/tooling/landingGates.js.map +1 -0
  66. package/dist/shared/tooling/projectTestAdmission.d.ts +16 -0
  67. package/dist/shared/tooling/projectTestAdmission.d.ts.map +1 -1
  68. package/dist/shared/tooling/projectTestAdmission.js +73 -10
  69. package/dist/shared/tooling/projectTestAdmission.js.map +1 -1
  70. package/dist/shared/tooling/testCommand.d.ts.map +1 -1
  71. package/dist/shared/tooling/testCommand.js +10 -0
  72. package/dist/shared/tooling/testCommand.js.map +1 -1
  73. package/dist/shared/types/finding.d.ts +56 -0
  74. package/dist/shared/types/finding.d.ts.map +1 -1
  75. package/dist/shared/types/finding.js +27 -0
  76. package/dist/shared/types/finding.js.map +1 -1
  77. package/dist/shared/types/intentCheckpoint.d.ts +2 -2
  78. package/dist/shared/validation/findingsReport.d.ts +13 -2
  79. package/dist/shared/validation/findingsReport.d.ts.map +1 -1
  80. package/dist/shared/validation/findingsReport.js +19 -2
  81. package/dist/shared/validation/findingsReport.js.map +1 -1
  82. package/docs/audit-pkg/product.md +2 -4
  83. package/package.json +1 -1
@@ -1,6 +1,7 @@
1
- // sites-pinned: tests/remediate/contract-pipeline.test.ts
2
- // (the promotion
3
- // computes no finding field FindingSchema would drop)
1
+ // sites-pinned: tests/remediate/contract-pipeline.test.ts, tests/remediate/dc3.test.ts
2
+ // contract-pipeline: the promotion computes no finding field FindingSchema would drop.
3
+ // dc3: the fan-out wording (needs, not mechanism) and the TRANSPORT report for a
4
+ // partially returned wave.
4
5
  /**
5
6
  * Contract-pipeline gate for ALL remediation starts (both paths).
6
7
  *
@@ -25,7 +26,7 @@
25
26
  import { existsSync } from "node:fs";
26
27
  import { mkdir, readFile, rename, rm } from "node:fs/promises";
27
28
  import { isAbsolute, join, resolve } from "node:path";
28
- import { writeJsonFile, readOptionalJsonFile, formatValidationIssues, hashContent, isRecord, withFsRetry, projectApprovedFindings, captureStepBoundaryFriction, climbOutOfAuditTools, partitionCommandsByDeclaredShape, splitSequentialCommandChain, normalizeRepoPath, repoRelativePath, toPosixPath, neutralProjectFacts, } from "audit-tools/shared";
29
+ import { writeJsonFile, readOptionalJsonFile, formatValidationIssues, hashContent, isRecord, withFsRetry, auditReadOf, projectApprovedFindings, captureStepBoundaryFriction, climbOutOfAuditTools, partitionCommandsByDeclaredShape, splitSequentialCommandChain, normalizeRepoPath, repoRelativePath, toPosixPath, neutralProjectFacts, } from "audit-tools/shared";
29
30
  import { createStepEmissionScaffold, } from "../../shared/steps/stepEmissionScaffold.js";
30
31
  import { OBLIGATION_KIND_PRIORITY, } from "../contractPipeline/obligationKinds.js";
31
32
  import { CP_ARTIFACT_NAMES, contractArtifactExists, contractArtifactFilePath, contractInputFilePath, contractPipelineDir, detectStaleArtifacts, dropReviewSnapshot, envelopePayload, envelopeSemanticHash, isEnvelope, pathASeedFilePath, payloadSemanticHash, readContractArtifact, stampToolCreatedAt, writeContractArtifact, writeDerivedContractArtifact, } from "../contractPipeline/artifactStore.js";
@@ -1498,6 +1499,48 @@ ${outputPaths.map((p, i) => `${i + 1}. \`${p}\` (${phases[i]})`).join("\n")}`;
1498
1499
  stopCondition: `Stop after writing all ${phases.length} collapsed artifacts (${phases.join(", ")}) and running next-step once.`,
1499
1500
  });
1500
1501
  }
1502
+ /**
1503
+ * The TRANSPORT-failure report for a re-emitted module wave, or `""` on a first
1504
+ * dispatch.
1505
+ *
1506
+ * A shard absent after dispatch is NOT a refusal of the work and NOT a contract
1507
+ * the worker got wrong — it is an item that was published and did not come back.
1508
+ * The two used to be indistinguishable: the re-emitted wave was byte-identical
1509
+ * to the first one, so a host that lost items mid-output saw only "run this
1510
+ * wave again", and nothing named the loss. Naming it is what lets a host
1511
+ * respond to the right thing (re-deliver the missing items, or report that it
1512
+ * cannot run them) instead of re-running a wave whose other shards already
1513
+ * exist.
1514
+ *
1515
+ * The classification is stated in the prompt because that is the channel the
1516
+ * host actually reads — the re-emission IS the delivery. It is a RE-EMISSION
1517
+ * report, so the absence is derived from the shards on disk, never from a
1518
+ * worker's claim that it wrote one.
1519
+ */
1520
+ function transportReport(absent, total) {
1521
+ // A PARTIAL return is the transport fact. All-absent is not reported, and the
1522
+ // reason is that NOTHING PERSISTED CAN TELL THE TWO APART. Searched, so the
1523
+ // claim is not an assumption: no dispatch marker exists under the artifacts
1524
+ // dir (the wave directory holds only the shards `scanModuleShards` reads, so a
1525
+ // wave emitted-and-lost leaves the same entries as one never emitted); the
1526
+ // rejected-payload archive covers AGGREGATED artifacts, never a shard, so an
1527
+ // unparseable shard is simply absent; `current-step.json` is overwritten by
1528
+ // EVERY emission and is a request, not a receipt — the tool cannot observe
1529
+ // that a host read it; and this writer has no `runLogger` seam at all, so no
1530
+ // emission is recorded in the run log either. Inventing the distinction would
1531
+ // mean guessing, and guessing here fires a transport failure on every ordinary
1532
+ // first emission — a report that is wrong on the normal case is one a host
1533
+ // learns to ignore, which costs more than the distinguishable half is worth.
1534
+ //
1535
+ // So the report is scoped to what it can actually ASSERT: some items came back
1536
+ // and some did not. That case needs no dispatch record, because the returned
1537
+ // shards ARE the record that a wave was published.
1538
+ if (absent.length === 0 || absent.length === total)
1539
+ return "";
1540
+ const list = absent.map((name) => `\`${name}\``).join(", ");
1541
+ return `> **TRANSPORT failure — ${absent.length} of ${total} items did not return.** The previous wave published ${list}, and no valid shard exists for ${absent.length === 1 ? "it" : "them"} on disk. This is a DELIVERY failure, not a refusal: the item was published and did not come back, which says nothing about whether the work is right. Re-deliver ${absent.length === 1 ? "that item" : "those items"} — the other ${total - absent.length} shard(s) are already present and must NOT be re-run. If the host cannot deliver ${absent.length === 1 ? "it" : "them"} independently, say so rather than serializing the whole wave through one context.
1542
+ `;
1543
+ }
1501
1544
  /**
1502
1545
  * DC-3: fan a parallel phase out to one bounded item per module. The host owns
1503
1546
  * grouping, concurrency, and execution choices; this tool supplies only the
@@ -1511,6 +1554,15 @@ async function writeParallelModuleWaveStep(ctx, phase) {
1511
1554
  if (modules.length <= 1) {
1512
1555
  return writeContractPhaseStep(ctx, phase);
1513
1556
  }
1557
+ // THE TRANSPORT REPORT. This wave is emitted either as the FIRST dispatch (no
1558
+ // shard on disk yet — an empty missing set) or as a RE-emission after a
1559
+ // previous wave came back with shards absent. The two are different facts and
1560
+ // used to render identically: a host that lost two of nine items mid-output
1561
+ // got the same undiagnosed "run this wave" it got the first time, so the only
1562
+ // thing that ever noticed the loss was a repeat of the same prompt. The set is
1563
+ // read from disk here rather than threaded through the plan, because the plan
1564
+ // is a pure classifier and the shard scan is a filesystem fact.
1565
+ const absent = (await scanModuleShards(ctx.artifactsDir, phase, modules)).missing;
1514
1566
  const inputArtifact = "module_decomposition";
1515
1567
  const inputPaths = ["goal_spec", "context_bundle", "module_decomposition"].map((key) => `- \`${ctx.artifactPaths[key]}\` (${key})`);
1516
1568
  const moduleLines = modules
@@ -1535,17 +1587,31 @@ async function writeParallelModuleWaveStep(ctx, phase) {
1535
1587
  const taskVerb = "draft its module contract";
1536
1588
  const cwdNote = `\n> Set the shell/tool working directory to \`${ctx.root}\` before running any commands.\n`;
1537
1589
  const nextCommand = loaderCommand("next-step");
1590
+ // THE STEP STATES WHAT IT NEEDS, NOT A MECHANISM. It used to say "dispatch one
1591
+ // sub-agent PER MODULE" — a mechanism the host may not have (in-process
1592
+ // subagents are not universal, and the fallback is a shell-out lane this tool
1593
+ // neither knows nor sizes for). Two of nine such dispatches died mid-output
1594
+ // and only the step's presence check noticed. What the work actually requires
1595
+ // is stated instead: N INDEPENDENT CONTEXTS with no shared authorship. A host
1596
+ // with subagents dispatches N of them; a host without runs the items however
1597
+ // it can, as long as no single context drafts both sides of a seam — which is
1598
+ // the property the seam-reconciliation gate downstream depends on.
1538
1599
  const prompt = `# Per-Module Contract Drafting (${modules.length} modules)
1539
1600
 
1540
- This phase publishes one bounded item per module. Complete all ${modules.length} items below; the host owns how they are grouped or executed. Each item reads only its module's file scope, then writes ONLY that module's contract shard — no item owns both sides of a seam, and no item writes the aggregated artifact.
1601
+ This phase publishes one bounded item per module. Complete all ${modules.length} items below; the host owns how they are grouped or executed.
1602
+ ${transportReport(absent, modules.length)}
1603
+
1604
+ **What this work needs:** ${modules.length} independent contexts, one per module — no shared authorship. Each module's contract must be drafted by a context that has NOT drafted any module it seams against. That independence is the input the seam reconciliation relies on: a single context drafting both sides of a seam reconciles the seam against itself and reports no mismatch, however mismatched the interfaces are. The host chooses the mechanism; if it has no way to run ${modules.length} independent contexts, say so rather than serializing them through one — a serialized draft is worse than a re-emitted wave, because it produces a wrong aggregate that nothing downstream can detect.
1605
+
1606
+ Each item reads only its module's file scope, then writes ONLY that module's contract shard — no item owns both sides of a seam, and no item writes the aggregated artifact.
1541
1607
  ${cwdNote}
1542
- ## Shared Inputs (every sub-agent may read these)
1608
+ ## Shared Inputs (every item may read these)
1543
1609
 
1544
1610
  ${inputPaths.join("\n")}
1545
1611
 
1546
- ## Per-Module Assignments — one sub-agent each
1612
+ ## Per-Module Assignments — one independent context each
1547
1613
 
1548
- For each module, dispatch one sub-agent to read its file scope from \`${inputArtifact}\` and ${taskVerb}, writing the result to the module's shard path:
1614
+ For each module, an independent context reads its file scope from \`${inputArtifact}\` and ${taskVerb}, writing the result to the module's shard path:
1549
1615
 
1550
1616
  ${moduleLines}
1551
1617
 
@@ -1555,13 +1621,13 @@ Each shard must be a single JSON object of this shape (the orchestrator merges a
1555
1621
  ${perModuleSchema}
1556
1622
  \`\`\`
1557
1623
 
1558
- ## After All Sub-Agents Finish
1624
+ ## After Every Item Finishes
1559
1625
 
1560
1626
  Once every module's shard above has been written (all ${modules.length}), run:
1561
1627
 
1562
1628
  \`${nextCommand}\`
1563
1629
 
1564
- The orchestrator verifies every module shard is present, merges them into \`${PHASE_TO_ARTIFACT[phase]}\`, and advances. If any shard is missing, this same wave is re-emitted for the missing modules — never a partial aggregate.
1630
+ The orchestrator verifies every module shard is present, merges them into \`${PHASE_TO_ARTIFACT[phase]}\`, and advances. If any shard is missing, this same wave is re-emitted for the missing modules — never a partial aggregate. Do not re-run items whose shard is already present.
1565
1631
 
1566
1632
  **Stop after the per-module shards are written and you run next-step.** Do not edit source files. Do not write the aggregated artifact. Do not advance further.
1567
1633
  `;
@@ -3360,6 +3426,52 @@ export async function collectPathARefusals(artifactsDir) {
3360
3426
  seedPresent: pathASeed !== undefined,
3361
3427
  }).refusals;
3362
3428
  }
3429
+ /**
3430
+ * What the AUDIT read, for the run whose Path-A seed lives in `artifactsDir` —
3431
+ * the value plan application stamps onto `state.plan.audit_read`.
3432
+ *
3433
+ * Read by the TOOL from the seed's own source report, which passed the strict
3434
+ * shared validator (`projectApprovedFindings`) before it may answer. It never
3435
+ * rides `extracted-plan.json`: that file is host-writable, and the close
3436
+ * phase's evidence leg turns this commit into terminal dispositions, so a
3437
+ * host-supplied value would let a host author its own `refuted`.
3438
+ *
3439
+ * BOUND TO THE SEED'S OWN DIGEST. The seed recorded a sha256 of the source
3440
+ * report when it was built (`source_digests`); the bytes read here must hash to
3441
+ * it, so a report swapped afterwards — even for another VALID report — answers
3442
+ * `null`. The file is read ONCE and the same bytes are hashed and parsed. A seed
3443
+ * that carries no digest for its source binds nothing, and an unbound commit is
3444
+ * not one this function will vouch for.
3445
+ *
3446
+ * `null` — "no commit is known" — when there is no seed (the run did not start
3447
+ * from a findings report), the source is unbound, changed, unreadable or
3448
+ * invalid, or the report itself states `null`.
3449
+ */
3450
+ export async function readSeedAuditRead(artifactsDir) {
3451
+ const pathASeed = await readOptionalJsonFile(pathASeedFilePath(artifactsDir));
3452
+ if (!pathASeed)
3453
+ return null;
3454
+ const bound = (pathASeed.source_digests ?? []).find((entry) => entry?.path === pathASeed.audit_findings_path);
3455
+ if (typeof bound?.sha256 !== "string")
3456
+ return null;
3457
+ let source;
3458
+ try {
3459
+ const bytes = await readFile(pathASeed.audit_findings_path);
3460
+ if (hashContent(bytes) !== bound.sha256)
3461
+ return null;
3462
+ source = JSON.parse(bytes.toString("utf8"));
3463
+ }
3464
+ catch {
3465
+ return null;
3466
+ }
3467
+ try {
3468
+ projectApprovedFindings(source);
3469
+ }
3470
+ catch {
3471
+ return null;
3472
+ }
3473
+ return auditReadOf(source);
3474
+ }
3363
3475
  /**
3364
3476
  * Convert a completed ImplementationDAG into the extracted-plan.json format
3365
3477
  * that the existing handlePendingExtractedPlan/applyPlanPipeline path consumes.