audit-tools 0.42.1 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dispatch/merge-results.mjs +229 -140
- package/dispatch/validate-result.mjs +98 -40
- package/dist/audit/cli/conceptualDispatch.d.ts.map +1 -1
- package/dist/audit/cli/conceptualDispatch.js +35 -3
- package/dist/audit/cli/conceptualDispatch.js.map +1 -1
- package/dist/audit/cli/dispatch/hostHandoff.d.ts +9 -1
- package/dist/audit/cli/dispatch/hostHandoff.d.ts.map +1 -1
- package/dist/audit/cli/dispatch/hostHandoff.js +94 -38
- package/dist/audit/cli/dispatch/hostHandoff.js.map +1 -1
- package/dist/audit/cli/nextStepCommand.d.ts +64 -0
- package/dist/audit/cli/nextStepCommand.d.ts.map +1 -1
- package/dist/audit/cli/nextStepCommand.js +877 -783
- package/dist/audit/cli/nextStepCommand.js.map +1 -1
- package/dist/audit/cli/nextStepHelpers.d.ts +12 -0
- package/dist/audit/cli/nextStepHelpers.d.ts.map +1 -1
- package/dist/audit/cli/nextStepHelpers.js +34 -1
- package/dist/audit/cli/nextStepHelpers.js.map +1 -1
- package/dist/audit/cli/steps.d.ts +2 -2
- package/dist/audit/extractors/graph.d.ts +28 -2
- package/dist/audit/extractors/graph.d.ts.map +1 -1
- package/dist/audit/extractors/graph.js +122 -11
- package/dist/audit/extractors/graph.js.map +1 -1
- package/dist/audit/extractors/graphRoutes.d.ts +8 -0
- package/dist/audit/extractors/graphRoutes.d.ts.map +1 -1
- package/dist/audit/extractors/graphRoutes.js +71 -4
- package/dist/audit/extractors/graphRoutes.js.map +1 -1
- package/dist/audit/io/artifacts.d.ts +55 -0
- package/dist/audit/io/artifacts.d.ts.map +1 -1
- package/dist/audit/io/artifacts.js +189 -12
- package/dist/audit/io/artifacts.js.map +1 -1
- package/dist/audit/io/toolingManifest.d.ts +11 -0
- package/dist/audit/io/toolingManifest.d.ts.map +1 -1
- package/dist/audit/io/toolingManifest.js +13 -3
- package/dist/audit/io/toolingManifest.js.map +1 -1
- package/dist/audit/orchestrator/advance.d.ts +64 -0
- package/dist/audit/orchestrator/advance.d.ts.map +1 -1
- package/dist/audit/orchestrator/advance.js +191 -57
- package/dist/audit/orchestrator/advance.js.map +1 -1
- package/dist/audit/orchestrator/charterClarificationExecutor.d.ts.map +1 -1
- package/dist/audit/orchestrator/charterClarificationExecutor.js +55 -21
- package/dist/audit/orchestrator/charterClarificationExecutor.js.map +1 -1
- package/dist/audit/orchestrator/flowCoverage.d.ts.map +1 -1
- package/dist/audit/orchestrator/flowCoverage.js +7 -10
- package/dist/audit/orchestrator/flowCoverage.js.map +1 -1
- package/dist/audit/orchestrator/flowPlanning.d.ts +54 -1
- package/dist/audit/orchestrator/flowPlanning.d.ts.map +1 -1
- package/dist/audit/orchestrator/flowPlanning.js +77 -18
- package/dist/audit/orchestrator/flowPlanning.js.map +1 -1
- package/dist/audit/orchestrator/flowRequeue.d.ts.map +1 -1
- package/dist/audit/orchestrator/flowRequeue.js +6 -10
- package/dist/audit/orchestrator/flowRequeue.js.map +1 -1
- package/dist/audit/orchestrator/requeueFold.d.ts.map +1 -1
- package/dist/audit/orchestrator/requeueFold.js +18 -5
- package/dist/audit/orchestrator/requeueFold.js.map +1 -1
- package/dist/audit/orchestrator/staleness.d.ts +24 -1
- package/dist/audit/orchestrator/staleness.d.ts.map +1 -1
- package/dist/audit/orchestrator/staleness.js +147 -16
- package/dist/audit/orchestrator/staleness.js.map +1 -1
- package/dist/audit/orchestrator/syntaxResolutionExecutor.d.ts +26 -0
- package/dist/audit/orchestrator/syntaxResolutionExecutor.d.ts.map +1 -1
- package/dist/audit/orchestrator/syntaxResolutionExecutor.js +97 -12
- package/dist/audit/orchestrator/syntaxResolutionExecutor.js.map +1 -1
- package/dist/audit/orchestrator/taskBuilder.d.ts.map +1 -1
- package/dist/audit/orchestrator/taskBuilder.js +111 -21
- package/dist/audit/orchestrator/taskBuilder.js.map +1 -1
- package/dist/audit/orchestrator/trivialAudit.d.ts +17 -1
- package/dist/audit/orchestrator/trivialAudit.d.ts.map +1 -1
- package/dist/audit/orchestrator/trivialAudit.js +31 -4
- package/dist/audit/orchestrator/trivialAudit.js.map +1 -1
- package/dist/audit/types/analyzerCapability.d.ts +6 -6
- package/dist/audit/types/charterRegister.d.ts +30 -12
- package/dist/audit/types/charterRegister.d.ts.map +1 -1
- package/dist/audit/types/charterRegister.js +22 -9
- package/dist/audit/types/charterRegister.js.map +1 -1
- package/dist/audit/validation/anchorGrounding.d.ts.map +1 -1
- package/dist/audit/validation/anchorGrounding.js +17 -0
- package/dist/audit/validation/anchorGrounding.js.map +1 -1
- package/dist/audit/validation/auditResults.d.ts +17 -0
- package/dist/audit/validation/auditResults.d.ts.map +1 -1
- package/dist/audit/validation/auditResults.js +50 -17
- package/dist/audit/validation/auditResults.js.map +1 -1
- package/dist/remediate/contractPipeline/cyclicSeamResolution.d.ts +48 -0
- package/dist/remediate/contractPipeline/cyclicSeamResolution.d.ts.map +1 -1
- package/dist/remediate/contractPipeline/cyclicSeamResolution.js +128 -0
- package/dist/remediate/contractPipeline/cyclicSeamResolution.js.map +1 -1
- package/dist/remediate/contractPipeline/derive.d.ts +8 -5
- package/dist/remediate/contractPipeline/derive.d.ts.map +1 -1
- package/dist/remediate/contractPipeline/derive.js +8 -16
- package/dist/remediate/contractPipeline/derive.js.map +1 -1
- package/dist/remediate/index.d.ts.map +1 -1
- package/dist/remediate/index.js +43 -2
- package/dist/remediate/index.js.map +1 -1
- package/dist/remediate/intent/intentOrdering.d.ts +13 -5
- package/dist/remediate/intent/intentOrdering.d.ts.map +1 -1
- package/dist/remediate/intent/intentOrdering.js +56 -25
- package/dist/remediate/intent/intentOrdering.js.map +1 -1
- package/dist/remediate/phases/close.d.ts +57 -2
- package/dist/remediate/phases/close.d.ts.map +1 -1
- package/dist/remediate/phases/close.js +260 -36
- package/dist/remediate/phases/close.js.map +1 -1
- package/dist/remediate/state/disposition.d.ts +26 -4
- package/dist/remediate/state/disposition.d.ts.map +1 -1
- package/dist/remediate/state/itemStatus.d.ts +33 -6
- package/dist/remediate/state/itemStatus.d.ts.map +1 -1
- package/dist/remediate/state/itemStatus.js +154 -39
- package/dist/remediate/state/itemStatus.js.map +1 -1
- package/dist/remediate/state/types.d.ts +41 -2
- package/dist/remediate/state/types.d.ts.map +1 -1
- package/dist/remediate/state/types.js.map +1 -1
- package/dist/remediate/steps/contractPipeline.d.ts +287 -15
- package/dist/remediate/steps/contractPipeline.d.ts.map +1 -1
- package/dist/remediate/steps/contractPipeline.js +1655 -981
- package/dist/remediate/steps/contractPipeline.js.map +1 -1
- package/dist/remediate/steps/dispatch/hostHandoff.d.ts +137 -2
- package/dist/remediate/steps/dispatch/hostHandoff.d.ts.map +1 -1
- package/dist/remediate/steps/dispatch/hostHandoff.js +713 -59
- package/dist/remediate/steps/dispatch/hostHandoff.js.map +1 -1
- package/dist/remediate/steps/finalGate.d.ts +136 -37
- package/dist/remediate/steps/finalGate.d.ts.map +1 -1
- package/dist/remediate/steps/finalGate.js +120 -86
- package/dist/remediate/steps/finalGate.js.map +1 -1
- package/dist/remediate/steps/gateCommands.d.ts +8 -0
- package/dist/remediate/steps/gateCommands.d.ts.map +1 -1
- package/dist/remediate/steps/gateCommands.js +40 -11
- package/dist/remediate/steps/gateCommands.js.map +1 -1
- package/dist/remediate/steps/nextStep.d.ts +96 -5
- package/dist/remediate/steps/nextStep.d.ts.map +1 -1
- package/dist/remediate/steps/nextStep.js +668 -147
- package/dist/remediate/steps/nextStep.js.map +1 -1
- package/dist/remediate/steps/types.d.ts +4 -1
- package/dist/remediate/steps/types.d.ts.map +1 -1
- package/dist/remediate/steps/types.js +10 -0
- package/dist/remediate/steps/types.js.map +1 -1
- package/dist/remediate/validation/artifacts.d.ts +20 -0
- package/dist/remediate/validation/artifacts.d.ts.map +1 -1
- package/dist/remediate/validation/artifacts.js +239 -13
- package/dist/remediate/validation/artifacts.js.map +1 -1
- package/dist/remediate/validation/contractPipelineGates.d.ts +58 -0
- package/dist/remediate/validation/contractPipelineGates.d.ts.map +1 -1
- package/dist/remediate/validation/contractPipelineGates.js +143 -27
- package/dist/remediate/validation/contractPipelineGates.js.map +1 -1
- package/dist/shared/analyzers/acquisitionEngine.d.ts +72 -25
- package/dist/shared/analyzers/acquisitionEngine.d.ts.map +1 -1
- package/dist/shared/analyzers/acquisitionEngine.js +149 -15
- package/dist/shared/analyzers/acquisitionEngine.js.map +1 -1
- package/dist/shared/analyzers/binaryAcquisition.d.ts +27 -2
- package/dist/shared/analyzers/binaryAcquisition.d.ts.map +1 -1
- package/dist/shared/analyzers/binaryAcquisition.js +149 -43
- package/dist/shared/analyzers/binaryAcquisition.js.map +1 -1
- package/dist/shared/analyzers/candidates.d.ts +9 -8
- package/dist/shared/analyzers/candidates.d.ts.map +1 -1
- package/dist/shared/analyzers/candidates.js +96 -64
- package/dist/shared/analyzers/candidates.js.map +1 -1
- package/dist/shared/analyzers/normalizeExternal.d.ts +40 -12
- package/dist/shared/analyzers/normalizeExternal.d.ts.map +1 -1
- package/dist/shared/analyzers/normalizeExternal.js +74 -18
- package/dist/shared/analyzers/normalizeExternal.js.map +1 -1
- package/dist/shared/analyzers/rubocop.d.ts +14 -13
- package/dist/shared/analyzers/rubocop.d.ts.map +1 -1
- package/dist/shared/analyzers/rubocop.js +29 -6
- package/dist/shared/analyzers/rubocop.js.map +1 -1
- package/dist/shared/analyzers/types.d.ts +196 -13
- package/dist/shared/analyzers/types.d.ts.map +1 -1
- package/dist/shared/analyzers/types.js +122 -9
- package/dist/shared/analyzers/types.js.map +1 -1
- package/dist/shared/decompose/charterClarification.d.ts +12 -1
- package/dist/shared/decompose/charterClarification.d.ts.map +1 -1
- package/dist/shared/decompose/charterClarification.js +11 -4
- package/dist/shared/decompose/charterClarification.js.map +1 -1
- package/dist/shared/decompose/charterExtraction.d.ts +20 -10
- package/dist/shared/decompose/charterExtraction.d.ts.map +1 -1
- package/dist/shared/decompose/charterExtraction.js +52 -23
- package/dist/shared/decompose/charterExtraction.js.map +1 -1
- package/dist/shared/intent/freeFormIntentInterpreter.d.ts +12 -2
- package/dist/shared/intent/freeFormIntentInterpreter.d.ts.map +1 -1
- package/dist/shared/intent/freeFormIntentInterpreter.js +27 -22
- package/dist/shared/intent/freeFormIntentInterpreter.js.map +1 -1
- package/dist/shared/intent/pathScope.d.ts +12 -1
- package/dist/shared/intent/pathScope.d.ts.map +1 -1
- package/dist/shared/intent/pathScope.js +56 -19
- package/dist/shared/intent/pathScope.js.map +1 -1
- package/dist/shared/intent/sharedIntentData.d.ts +19 -1
- package/dist/shared/intent/sharedIntentData.d.ts.map +1 -1
- package/dist/shared/intent/sharedIntentData.js +33 -8
- package/dist/shared/intent/sharedIntentData.js.map +1 -1
- package/dist/shared/io/fileLock.d.ts +12 -0
- package/dist/shared/io/fileLock.d.ts.map +1 -1
- package/dist/shared/io/fileLock.js +48 -5
- package/dist/shared/io/fileLock.js.map +1 -1
- package/dist/shared/io/json.d.ts +13 -0
- package/dist/shared/io/json.d.ts.map +1 -1
- package/dist/shared/io/json.js +19 -1
- package/dist/shared/io/json.js.map +1 -1
- package/dist/shared/steps/stepEmissionScaffold.d.ts +69 -0
- package/dist/shared/steps/stepEmissionScaffold.d.ts.map +1 -0
- package/dist/shared/steps/stepEmissionScaffold.js +83 -0
- package/dist/shared/steps/stepEmissionScaffold.js.map +1 -0
- package/dist/shared/submission/handRecovery.d.ts.map +1 -1
- package/dist/shared/submission/handRecovery.js +69 -4
- package/dist/shared/submission/handRecovery.js.map +1 -1
- package/dist/shared/submission/submissionIdentity.d.ts +10 -1
- package/dist/shared/submission/submissionIdentity.d.ts.map +1 -1
- package/dist/shared/submission/submissionIdentity.js +12 -3
- package/dist/shared/submission/submissionIdentity.js.map +1 -1
- package/dist/shared/submission/submissionLedger.d.ts +43 -2
- package/dist/shared/submission/submissionLedger.d.ts.map +1 -1
- package/dist/shared/submission/submissionLedger.js +50 -7
- package/dist/shared/submission/submissionLedger.js.map +1 -1
- package/dist/shared/tooling/allowlistedExec.d.ts +9 -0
- package/dist/shared/tooling/allowlistedExec.d.ts.map +1 -1
- package/dist/shared/tooling/allowlistedExec.js +19 -2
- package/dist/shared/tooling/allowlistedExec.js.map +1 -1
- package/dist/shared/types/charter.d.ts +24 -0
- package/dist/shared/types/charter.d.ts.map +1 -1
- package/dist/shared/types/charter.js +9 -0
- package/dist/shared/types/charter.js.map +1 -1
- package/dist/shared/types/remediationOutcome.d.ts +282 -9
- package/dist/shared/types/remediationOutcome.d.ts.map +1 -1
- package/dist/shared/types/remediationOutcome.js +174 -1
- package/dist/shared/types/remediationOutcome.js.map +1 -1
- package/package.json +2 -1
- package/remediate-code.mjs +15 -0
- package/wrapper/remediate-code-wrapper-install-hosts.mjs +45 -4
- package/wrapper/remediate-code-wrapper-install-renderers.mjs +12 -2
|
@@ -1,168 +1,257 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { readFileSync, writeFileSync, readdirSync, existsSync } from "node:fs";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
3
4
|
import { validateResult } from "./validate.mjs";
|
|
4
|
-
import { PACKET_SCHEMA_FILENAMES } from "../dist/audit/io/runArtifacts.js";
|
|
5
5
|
import { resolveArtifactsDir } from "./artifacts-dir.mjs";
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Importable merge core (CP-NODE-21 / dispatch-scripts-test-reach). Every
|
|
9
|
+
* acceptance branch this script owns — fail-closed manifest read, INV-01
|
|
10
|
+
* array-payload expansion, duplicate-task_id rejection, unknown-task_id
|
|
11
|
+
* rejection, missing-result-is-a-failure, no-destructive-truncation, and the
|
|
12
|
+
* INV-03 partial-merge signal (surfaced here as `failing.length > 0`, which
|
|
13
|
+
* the CLI shim below turns into a non-zero exit) — lives in this function so
|
|
14
|
+
* a test can drive it in-process against a fabricated run directory, without
|
|
15
|
+
* spawning a child process and without process.exit ever firing mid-test.
|
|
16
|
+
* The CLI shim is a byte-compatible wrapper: same argv parsing, same
|
|
17
|
+
* stdout/stderr text, same exit codes as before this split.
|
|
18
|
+
*
|
|
19
|
+
* @param {{ artifactsDir: string, runId: string }} options
|
|
20
|
+
* @returns {{
|
|
21
|
+
* ok: boolean,
|
|
22
|
+
* fatal?: { message: string },
|
|
23
|
+
* passing: object[],
|
|
24
|
+
* failing: { task_id: string, errors: string[] }[],
|
|
25
|
+
* total: number,
|
|
26
|
+
* auditResultsPath: string,
|
|
27
|
+
* failedTasksPath: string,
|
|
28
|
+
* wroteAuditResults: boolean,
|
|
29
|
+
* wroteFailedTasks: boolean,
|
|
30
|
+
* }}
|
|
31
|
+
*/
|
|
32
|
+
export function mergeResults({ artifactsDir, runId }) {
|
|
33
|
+
const taskResultsDir = join(artifactsDir, "runs", runId, "task-results");
|
|
34
|
+
const auditResultsPath = join(artifactsDir, "runs", runId, "run-results.json");
|
|
35
|
+
const failedTasksPath = join(artifactsDir, "runs", runId, "failed-tasks.json");
|
|
36
|
+
const tasksPath = join(artifactsDir, "runs", runId, "pending-audit-tasks.json");
|
|
28
37
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
if (!existsSync(taskResultsDir)) {
|
|
39
|
+
return {
|
|
40
|
+
ok: false,
|
|
41
|
+
fatal: { message: `task-results directory not found: ${taskResultsDir}` },
|
|
42
|
+
passing: [],
|
|
43
|
+
failing: [],
|
|
44
|
+
total: 0,
|
|
45
|
+
auditResultsPath,
|
|
46
|
+
failedTasksPath,
|
|
47
|
+
wroteAuditResults: false,
|
|
48
|
+
wroteFailedTasks: false,
|
|
49
|
+
};
|
|
39
50
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const taskMap = new Map(tasks.filter(t => t && typeof t.task_id === "string").map(t => [t.task_id, t]));
|
|
48
|
-
|
|
49
|
-
// Result-file selection excludes the schema pointer files prepare-dispatch
|
|
50
|
-
// copies into task-results/ for optional worker self-validation — they are
|
|
51
|
-
// support artifacts, never results, and must not be counted as failures.
|
|
52
|
-
const PACKET_SCHEMA_FILENAME_SET = new Set(PACKET_SCHEMA_FILENAMES);
|
|
53
|
-
const files = readdirSync(taskResultsDir)
|
|
54
|
-
.filter(f => f.endsWith(".json") && !PACKET_SCHEMA_FILENAME_SET.has(f))
|
|
55
|
-
.sort();
|
|
56
|
-
|
|
57
|
-
const failing = [];
|
|
58
|
-
const resultsByTaskId = new Map();
|
|
59
|
-
|
|
60
|
-
for (const filename of files) {
|
|
61
|
-
const filePath = join(taskResultsDir, filename);
|
|
62
|
-
let parsed;
|
|
51
|
+
|
|
52
|
+
// Manifest-reconciled completeness (COR-7602834d cluster): acceptance is judged against the
|
|
53
|
+
// pending manifest, never against whichever task-results files happen to exist.
|
|
54
|
+
// A missing or unreadable manifest means no task context can be established, so
|
|
55
|
+
// the merge hard-fails BEFORE any write (fail-closed task-identity gate) instead
|
|
56
|
+
// of validating results fail-open with no assigned task.
|
|
57
|
+
let tasks;
|
|
63
58
|
try {
|
|
64
|
-
|
|
59
|
+
tasks = JSON.parse(readFileSync(tasksPath, "utf8"));
|
|
60
|
+
if (!Array.isArray(tasks)) {
|
|
61
|
+
throw new Error("pending-audit-tasks.json must be a JSON array of AuditTasks");
|
|
62
|
+
}
|
|
65
63
|
} catch (e) {
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
return {
|
|
65
|
+
ok: false,
|
|
66
|
+
fatal: {
|
|
67
|
+
message:
|
|
68
|
+
`Cannot read pending manifest ${tasksPath}: ${e.message}\n` +
|
|
69
|
+
"merge-results judges acceptance against the pending manifest (fail-closed); aborting before any write.",
|
|
70
|
+
},
|
|
71
|
+
passing: [],
|
|
72
|
+
failing: [],
|
|
73
|
+
total: 0,
|
|
74
|
+
auditResultsPath,
|
|
75
|
+
failedTasksPath,
|
|
76
|
+
wroteAuditResults: false,
|
|
77
|
+
wroteFailedTasks: false,
|
|
78
|
+
};
|
|
68
79
|
}
|
|
80
|
+
const taskMap = new Map(tasks.filter(t => t && typeof t.task_id === "string").map(t => [t.task_id, t]));
|
|
81
|
+
|
|
82
|
+
// Schema-pointer-file exclusion was retired along with the rest of the
|
|
83
|
+
// schema-copy feature in 467b1e8f ("retire the execution substrate: zero
|
|
84
|
+
// adapters, host-owned execution"), which deleted PACKET_SCHEMA_FILENAMES
|
|
85
|
+
// and its sole producer (nothing in src/ copies schema files into
|
|
86
|
+
// task-results/ anymore) but left this import pointing at the deleted
|
|
87
|
+
// export — an orphaned reference that made this whole script unimportable
|
|
88
|
+
// (ESM throws on a missing named export). If a schema-pointer-file
|
|
89
|
+
// convention is reintroduced, re-add the matching exclusion here sourced
|
|
90
|
+
// from wherever that filename list lives then; there is nothing to exclude
|
|
91
|
+
// today.
|
|
92
|
+
const files = readdirSync(taskResultsDir)
|
|
93
|
+
.filter(f => f.endsWith(".json"))
|
|
94
|
+
.sort();
|
|
69
95
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
for (const
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
failing.push({
|
|
81
|
-
task_id: filename,
|
|
82
|
-
errors: ["Result has no task_id; cannot bind it to an assigned task."],
|
|
83
|
-
});
|
|
96
|
+
const failing = [];
|
|
97
|
+
const resultsByTaskId = new Map();
|
|
98
|
+
|
|
99
|
+
for (const filename of files) {
|
|
100
|
+
const filePath = join(taskResultsDir, filename);
|
|
101
|
+
let parsed;
|
|
102
|
+
try {
|
|
103
|
+
parsed = JSON.parse(readFileSync(filePath, "utf8"));
|
|
104
|
+
} catch (e) {
|
|
105
|
+
failing.push({ task_id: filename, errors: [`Invalid JSON: ${e.message}`] });
|
|
84
106
|
continue;
|
|
85
107
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
108
|
+
|
|
109
|
+
// Expand top-level AuditResult[] arrays — a worker that emits an array
|
|
110
|
+
// payload must not be treated as one invalid object (INV-01 / COR-bf5c7331).
|
|
111
|
+
const candidates = Array.isArray(parsed) ? parsed : [parsed];
|
|
112
|
+
for (const resultObj of candidates) {
|
|
113
|
+
const taskId =
|
|
114
|
+
resultObj && typeof resultObj === "object" && !Array.isArray(resultObj) &&
|
|
115
|
+
typeof resultObj.task_id === "string"
|
|
116
|
+
? resultObj.task_id
|
|
117
|
+
: undefined;
|
|
118
|
+
if (!taskId) {
|
|
119
|
+
failing.push({
|
|
120
|
+
task_id: filename,
|
|
121
|
+
errors: ["Result has no task_id; cannot bind it to an assigned task."],
|
|
122
|
+
});
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (resultsByTaskId.has(taskId)) {
|
|
126
|
+
// Dedup-by-task_id: the first on-disk result for a task is authoritative;
|
|
127
|
+
// later copies are rejected, never double-merged.
|
|
128
|
+
failing.push({
|
|
129
|
+
task_id: taskId,
|
|
130
|
+
errors: [`Duplicate audit result for assigned task '${taskId}'.`],
|
|
131
|
+
});
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (!taskMap.has(taskId)) {
|
|
135
|
+
// Identity is the tool's authority: a result that matches no assigned
|
|
136
|
+
// task in the pending manifest never validates (fail-closed).
|
|
137
|
+
failing.push({
|
|
138
|
+
task_id: taskId,
|
|
139
|
+
errors: [`Unknown task_id '${taskId}': not in the pending manifest for run '${runId}'.`],
|
|
140
|
+
});
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
resultsByTaskId.set(taskId, resultObj);
|
|
94
144
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Validate each MANIFEST task's result: present → validate in the assigned
|
|
148
|
+
// task's context; absent → a genuine failure (the manifest, not the file
|
|
149
|
+
// listing, decides completeness).
|
|
150
|
+
const passing = [];
|
|
151
|
+
for (const task of taskMap.values()) {
|
|
152
|
+
const resultObj = resultsByTaskId.get(task.task_id);
|
|
153
|
+
if (resultObj === undefined) {
|
|
154
|
+
failing.push({ task_id: task.task_id, errors: ["Missing audit result for assigned task."] });
|
|
102
155
|
continue;
|
|
103
156
|
}
|
|
104
|
-
|
|
157
|
+
const { valid, errors } = validateResult(resultObj, task);
|
|
158
|
+
if (valid) {
|
|
159
|
+
passing.push(resultObj);
|
|
160
|
+
} else {
|
|
161
|
+
failing.push({ task_id: task.task_id, errors });
|
|
162
|
+
}
|
|
105
163
|
}
|
|
106
|
-
}
|
|
107
164
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
for (const task of taskMap.values()) {
|
|
113
|
-
const resultObj = resultsByTaskId.get(task.task_id);
|
|
114
|
-
if (resultObj === undefined) {
|
|
115
|
-
failing.push({ task_id: task.task_id, errors: ["Missing audit result for assigned task."] });
|
|
116
|
-
continue;
|
|
165
|
+
let wroteFailedTasks = false;
|
|
166
|
+
if (failing.length > 0) {
|
|
167
|
+
writeFileSync(failedTasksPath, JSON.stringify(failing, null, 2));
|
|
168
|
+
wroteFailedTasks = true;
|
|
117
169
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
170
|
+
|
|
171
|
+
// No destructive truncation on re-run (COR-44bea9c0 cluster): run-results.json is written
|
|
172
|
+
// only when there is something to merge. A blocked no-op (nothing passed while
|
|
173
|
+
// something failed) — e.g. a stray re-invocation after a successful merge —
|
|
174
|
+
// must never truncate a previously written run-results.json to [].
|
|
175
|
+
let wroteAuditResults = false;
|
|
176
|
+
if (passing.length > 0) {
|
|
177
|
+
writeFileSync(auditResultsPath, JSON.stringify(passing, null, 2));
|
|
178
|
+
wroteAuditResults = true;
|
|
123
179
|
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
ok: true,
|
|
183
|
+
passing,
|
|
184
|
+
failing,
|
|
185
|
+
total: passing.length + failing.length,
|
|
186
|
+
auditResultsPath,
|
|
187
|
+
failedTasksPath,
|
|
188
|
+
wroteAuditResults,
|
|
189
|
+
wroteFailedTasks,
|
|
190
|
+
};
|
|
124
191
|
}
|
|
125
192
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
193
|
+
function runCli() {
|
|
194
|
+
const runIdIdx = process.argv.indexOf("--run-id");
|
|
195
|
+
if (runIdIdx === -1 || !process.argv[runIdIdx + 1]) {
|
|
196
|
+
console.error("Usage: node dispatch/merge-results.mjs --run-id <run_id> [--artifacts-dir <dir>]");
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
const runId = process.argv[runIdIdx + 1];
|
|
200
|
+
|
|
201
|
+
// Default must match where the orchestrator/wrapper actually writes runs:
|
|
202
|
+
// <root>/.audit-tools/audit (COR-bf5c7331). The prior `.audit-artifacts`
|
|
203
|
+
// default resolved to a directory the pipeline never populates.
|
|
204
|
+
const artifactsDir = resolveArtifactsDir(process.argv);
|
|
205
|
+
|
|
206
|
+
const result = mergeResults({ artifactsDir, runId });
|
|
207
|
+
|
|
208
|
+
if (!result.ok) {
|
|
209
|
+
console.error(result.fatal.message);
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const { passing, failing, total, auditResultsPath, failedTasksPath } = result;
|
|
214
|
+
|
|
215
|
+
if (failing.length > 0) {
|
|
216
|
+
process.stderr.write(`${failing.length} task(s) failed validation and were excluded:\n`);
|
|
217
|
+
for (const f of failing) {
|
|
218
|
+
for (const err of f.errors) {
|
|
219
|
+
process.stderr.write(` ✗ ${f.task_id}: ${err}\n`);
|
|
220
|
+
}
|
|
132
221
|
}
|
|
133
222
|
}
|
|
134
|
-
}
|
|
135
223
|
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
224
|
+
// FND-OBS-bf5c7331: emit a structured JSON summary line to stdout so callers
|
|
225
|
+
// get a machine-readable payload alongside the human-readable text line.
|
|
226
|
+
// Both are written to stdout; the JSON summary line always comes first so
|
|
227
|
+
// programmatic consumers can parse it without stripping the text line.
|
|
228
|
+
process.stdout.write(
|
|
229
|
+
JSON.stringify({
|
|
230
|
+
ts: new Date().toISOString(),
|
|
231
|
+
source: "audit-code:merge-results",
|
|
232
|
+
event: "merge_summary",
|
|
233
|
+
total,
|
|
234
|
+
accepted: passing.length,
|
|
235
|
+
rejected: failing.length,
|
|
236
|
+
audit_results_path: auditResultsPath,
|
|
237
|
+
...(failing.length > 0 ? { failed_tasks_path: failedTasksPath } : {}),
|
|
238
|
+
}) + "\n",
|
|
239
|
+
);
|
|
240
|
+
console.log(`✓ ${passing.length}/${total} tasks valid → ${auditResultsPath}`);
|
|
241
|
+
if (failing.length > 0) {
|
|
242
|
+
console.log(" Re-run those tasks in the next cycle.");
|
|
243
|
+
}
|
|
143
244
|
|
|
144
|
-
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
// Both are written to stdout; the JSON summary line always comes first so
|
|
148
|
-
// programmatic consumers can parse it without stripping the text line.
|
|
149
|
-
process.stdout.write(
|
|
150
|
-
JSON.stringify({
|
|
151
|
-
ts: new Date().toISOString(),
|
|
152
|
-
source: "audit-code:merge-results",
|
|
153
|
-
event: "merge_summary",
|
|
154
|
-
total,
|
|
155
|
-
accepted: passing.length,
|
|
156
|
-
rejected: failing.length,
|
|
157
|
-
audit_results_path: auditResultsPath,
|
|
158
|
-
...(failing.length > 0 ? { failed_tasks_path: failedTasksPath } : {}),
|
|
159
|
-
}) + "\n",
|
|
160
|
-
);
|
|
161
|
-
console.log(`✓ ${passing.length}/${total} tasks valid → ${auditResultsPath}`);
|
|
162
|
-
if (failing.length > 0) {
|
|
163
|
-
console.log(" Re-run those tasks in the next cycle.");
|
|
245
|
+
// Exit non-zero when any result failed validation so callers can detect
|
|
246
|
+
// partial merges and avoid treating them as clean success (INV-03 / COR-bf5c7331-2).
|
|
247
|
+
process.exit(failing.length > 0 ? 1 : 0);
|
|
164
248
|
}
|
|
165
249
|
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
|
|
250
|
+
// Only run the CLI when this file is the process entrypoint — not when a test
|
|
251
|
+
// imports mergeResults() directly. Compares resolved file:// URLs (not raw
|
|
252
|
+
// argv[1] strings) so this is correct on Windows, where argv[1] is a
|
|
253
|
+
// backslash path that never equals import.meta.url's forward-slash form.
|
|
254
|
+
const isMainModule = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
255
|
+
if (isMainModule) {
|
|
256
|
+
runCli();
|
|
257
|
+
}
|
|
@@ -1,57 +1,115 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { readFileSync, existsSync } from "node:fs";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
3
4
|
import { validateResult } from "./validate.mjs";
|
|
4
5
|
import { resolveArtifactsDir } from "./artifacts-dir.mjs";
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Resolve the AuditTask context for `taskId` from the run's pending
|
|
9
|
+
* manifest. Degrades to `task: null` (never throws) when the manifest is
|
|
10
|
+
* absent or unreadable — validateResult's fail-closed null-task branch is the
|
|
11
|
+
* single enforcement point downstream; this helper only ever supplies
|
|
12
|
+
* context, it never itself decides pass/fail.
|
|
13
|
+
*
|
|
14
|
+
* @param {{ artifactsDir: string, runId: string, taskId: string }} options
|
|
15
|
+
* @returns {{ task: object | null, warning?: string }}
|
|
16
|
+
*/
|
|
17
|
+
export function resolveTaskContext({ artifactsDir, runId, taskId }) {
|
|
18
|
+
const tasksPath = join(artifactsDir, "runs", runId, "pending-audit-tasks.json");
|
|
19
|
+
if (!existsSync(tasksPath)) {
|
|
20
|
+
return { task: null };
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const tasks = JSON.parse(readFileSync(tasksPath, "utf8"));
|
|
24
|
+
return { task: tasks.find(t => t.task_id === taskId) ?? null };
|
|
25
|
+
} catch (e) {
|
|
26
|
+
return {
|
|
27
|
+
task: null,
|
|
28
|
+
warning: `[warn] Could not read pending-audit-tasks.json; line-count validation will be skipped: ${e.message}`,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
8
32
|
|
|
9
|
-
|
|
10
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Importable core (CP-NODE-21 / dispatch-scripts-test-reach): reads one
|
|
35
|
+
* task-results file, resolves its task context via {@link resolveTaskContext},
|
|
36
|
+
* and validates it. No process.exit — the CLI shim below translates the
|
|
37
|
+
* outcome into console output and an exit code, byte-compatible with this
|
|
38
|
+
* script's behavior before the split.
|
|
39
|
+
*
|
|
40
|
+
* @param {{ artifactsDir: string, runId: string, taskId: string }} options
|
|
41
|
+
* @returns {{
|
|
42
|
+
* ok: boolean,
|
|
43
|
+
* fatal?: { message: string },
|
|
44
|
+
* warning?: string,
|
|
45
|
+
* resultPath: string,
|
|
46
|
+
* valid?: boolean,
|
|
47
|
+
* errors?: string[],
|
|
48
|
+
* }}
|
|
49
|
+
*/
|
|
50
|
+
export function validateOneResult({ artifactsDir, runId, taskId }) {
|
|
51
|
+
const sanitized = taskId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
52
|
+
const resultPath = join(artifactsDir, "runs", runId, "task-results", sanitized + ".json");
|
|
53
|
+
|
|
54
|
+
if (!existsSync(resultPath)) {
|
|
55
|
+
return { ok: false, fatal: { message: `File not found: ${resultPath}` }, resultPath };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let resultObj;
|
|
59
|
+
try {
|
|
60
|
+
resultObj = JSON.parse(readFileSync(resultPath, "utf8"));
|
|
61
|
+
} catch (e) {
|
|
62
|
+
return { ok: false, fatal: { message: `Invalid JSON in ${resultPath}: ${e.message}` }, resultPath };
|
|
63
|
+
}
|
|
11
64
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
65
|
+
const { task, warning } = resolveTaskContext({ artifactsDir, runId, taskId });
|
|
66
|
+
const { valid, errors } = validateResult(resultObj, task);
|
|
67
|
+
return { ok: true, warning, resultPath, valid, errors };
|
|
15
68
|
}
|
|
16
69
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
70
|
+
function runCli() {
|
|
71
|
+
const runIdIdx = process.argv.indexOf("--run-id");
|
|
72
|
+
const taskIdIdx = process.argv.indexOf("--task-id");
|
|
20
73
|
|
|
21
|
-
const
|
|
22
|
-
const
|
|
74
|
+
const runId = runIdIdx !== -1 ? process.argv[runIdIdx + 1] : undefined;
|
|
75
|
+
const taskId = taskIdIdx !== -1 ? process.argv[taskIdIdx + 1] : undefined;
|
|
23
76
|
|
|
24
|
-
if (!
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
77
|
+
if (!runId || !taskId) {
|
|
78
|
+
console.error("Usage: node dispatch/validate-result.mjs --run-id <run_id> --task-id <task_id> [--artifacts-dir <dir>]");
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
28
81
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
} catch (e) {
|
|
33
|
-
console.error(`Invalid JSON in ${resultPath}: ${e.message}`);
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
82
|
+
// Default must match where the orchestrator/wrapper actually writes runs:
|
|
83
|
+
// <root>/.audit-tools/audit (COR-bf5c7331), not the legacy `.audit-artifacts`.
|
|
84
|
+
const artifactsDir = resolveArtifactsDir(process.argv);
|
|
36
85
|
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
if (
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
86
|
+
const result = validateOneResult({ artifactsDir, runId, taskId });
|
|
87
|
+
|
|
88
|
+
if (!result.ok) {
|
|
89
|
+
console.error(result.fatal.message);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (result.warning) {
|
|
94
|
+
process.stderr.write(result.warning + "\n");
|
|
45
95
|
}
|
|
46
|
-
}
|
|
47
96
|
|
|
48
|
-
|
|
97
|
+
if (result.valid) {
|
|
98
|
+
console.log("✓ valid:", taskId);
|
|
99
|
+
process.exit(0);
|
|
100
|
+
} else {
|
|
101
|
+
console.error("✗ invalid:", taskId);
|
|
102
|
+
console.error(JSON.stringify(result.errors, null, 2));
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
49
106
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
107
|
+
// Only run the CLI when this file is the process entrypoint — not when a test
|
|
108
|
+
// imports validateOneResult()/resolveTaskContext() directly. Compares
|
|
109
|
+
// resolved file:// URLs (not raw argv[1] strings) so this is correct on
|
|
110
|
+
// Windows, where argv[1] is a backslash path that never equals
|
|
111
|
+
// import.meta.url's forward-slash form.
|
|
112
|
+
const isMainModule = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
113
|
+
if (isMainModule) {
|
|
114
|
+
runCli();
|
|
57
115
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conceptualDispatch.d.ts","sourceRoot":"","sources":["../../../src/audit/cli/conceptualDispatch.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,gBAAgB,EAEtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAUzD,OAAO,EAML,KAAK,uBAAuB,EAC7B,MAAM,sBAAsB,CAAC;AAE9B,MAAM,WAAW,wBAAwB;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,SAAS,GAAG,MAAM,CAAC;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,WAAW,CAAC,gBAAgB,CAAC,eAAe,CAAC,CAAC,EAC1D,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,aAAa,EAAE,gBAAgB,CAAC,gBAAgB,CAAC,EACjD,aAAa,EAAE,SAAS,GAAG,MAAM,GAChC,MAAM,CAUR;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,cAAc,GACrB,wBAAwB,CA+B1B;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,CAAC;IACd;;;OAGG;IACH,qBAAqB,EAAE,MAAM,CAAC;IAC9B,mEAAmE;IACnE,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,mDAAmD;IACnD,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,8CAA8C;IAC9C,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,+CAA+C;IAC/C,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,mEAAmE;IACnE,SAAS,EAAE,uBAAuB,CAAC;CACpC;AAED;;;;;;;;GAQG;AACH,wBAAsB,yBAAyB,CAAC,IAAI,EAAE;IACpD,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,wBAAwB,CAAC;IACnC;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GAAG,OAAO,CAAC,kBAAkB,CAAC,
|
|
1
|
+
{"version":3,"file":"conceptualDispatch.d.ts","sourceRoot":"","sources":["../../../src/audit/cli/conceptualDispatch.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,gBAAgB,EAEtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAUzD,OAAO,EAML,KAAK,uBAAuB,EAC7B,MAAM,sBAAsB,CAAC;AAE9B,MAAM,WAAW,wBAAwB;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,SAAS,GAAG,MAAM,CAAC;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,WAAW,CAAC,gBAAgB,CAAC,eAAe,CAAC,CAAC,EAC1D,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,aAAa,EAAE,gBAAgB,CAAC,gBAAgB,CAAC,EACjD,aAAa,EAAE,SAAS,GAAG,MAAM,GAChC,MAAM,CAUR;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,cAAc,GACrB,wBAAwB,CA+B1B;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,CAAC;IACd;;;OAGG;IACH,qBAAqB,EAAE,MAAM,CAAC;IAC9B,mEAAmE;IACnE,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,mDAAmD;IACnD,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,8CAA8C;IAC9C,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,+CAA+C;IAC/C,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,mEAAmE;IACnE,SAAS,EAAE,uBAAuB,CAAC;CACpC;AAED;;;;;;;;GAQG;AACH,wBAAsB,yBAAyB,CAAC,IAAI,EAAE;IACpD,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,wBAAwB,CAAC;IACnC;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAqM9B"}
|
|
@@ -158,7 +158,22 @@ export async function prepareConceptualDispatch(opts) {
|
|
|
158
158
|
...f,
|
|
159
159
|
promptPath: promptPathFor(f.lane),
|
|
160
160
|
}));
|
|
161
|
-
|
|
161
|
+
// Resume (COR-4c8bd93a) narrows the INSTRUCTION surface only. writePaths/
|
|
162
|
+
// readPaths/artifactPaths below stay the full, stable perspective set —
|
|
163
|
+
// round identity (conceptual-perspective-round-identity.test.ts) pins a
|
|
164
|
+
// re-emission of the SAME round to re-declare identical bound paths
|
|
165
|
+
// regardless of partial delivery, so they are a stable per-round access
|
|
166
|
+
// declaration, not a must-write list. What resume changes is which lanes
|
|
167
|
+
// the narrative tells the host to actually run: a delivered lane is never
|
|
168
|
+
// re-instructed and its landed submission is never clobbered, because the
|
|
169
|
+
// host is simply never told to write there again.
|
|
170
|
+
const pendingLaneIds = new Set(fanout.pendingLanes.map((lane) => lane.id));
|
|
171
|
+
const pendingPerspectives = perspectivePrompts
|
|
172
|
+
.map((f, i) => ({ ordinal: i + 1, f }))
|
|
173
|
+
.filter(({ f }) => pendingLaneIds.has(f.lane));
|
|
174
|
+
const pendingCount = pendingPerspectives.length;
|
|
175
|
+
const deliveredCount = perspectivePrompts.length - pendingCount;
|
|
176
|
+
const perspectiveLines = pendingPerspectives.map(({ ordinal, f }) => ` - Perspective ${ordinal} (${f.name}): prompt \`${f.promptPath}\` → findings \`${f.resultsPath}\``);
|
|
162
177
|
const artifactPaths = {
|
|
163
178
|
conceptual_results: conceptualResultsPath,
|
|
164
179
|
conceptual_judge_prompt: judgePromptPath,
|
|
@@ -173,8 +188,25 @@ export async function prepareConceptualDispatch(opts) {
|
|
|
173
188
|
instructionLines: [
|
|
174
189
|
...(settings.reuse_notice ? [settings.reuse_notice] : []),
|
|
175
190
|
`**Conceptual review** (generative, deep — ${total}-perspective fan-out):`,
|
|
176
|
-
|
|
177
|
-
|
|
191
|
+
// A resumed round with some (but not all) perspectives already delivered
|
|
192
|
+
// renders this notice so the host sees, in prose, exactly which lanes are
|
|
193
|
+
// being skipped and why — the mechanical guarantee is the narrowed
|
|
194
|
+
// perspectiveLines/step-1 list below; this line is purely informational,
|
|
195
|
+
// the same "state what changed, never rely on the host noticing" pattern
|
|
196
|
+
// `reuse_notice` above uses for checkpoint reuse.
|
|
197
|
+
...(deliveredCount > 0
|
|
198
|
+
? [
|
|
199
|
+
`_${deliveredCount} of ${total} perspective lane(s) already delivered a submission this round — reusing that output, not re-executing them._`,
|
|
200
|
+
]
|
|
201
|
+
: []),
|
|
202
|
+
...(pendingCount > 0
|
|
203
|
+
? [
|
|
204
|
+
`1. Execute ${pendingCount === total ? `these ${total}` : `these ${pendingCount} still-pending`} independent perspective lane(s) — one subagent per lane **in parallel** if a subagent facility exists, else sequentially yourself. Each lane reviews only through its own value system and must NOT see the others' output:`,
|
|
205
|
+
...perspectiveLines,
|
|
206
|
+
]
|
|
207
|
+
: [
|
|
208
|
+
`1. All ${total} perspective lanes have already delivered a submission this round — nothing to execute here.`,
|
|
209
|
+
]),
|
|
178
210
|
`2. When all ${total} perspectives have written their findings, execute ONE **independent judge** lane — a fresh subagent that is not any of the perspectives when a facility exists; with no facility, execute it yourself as the explicitly-degraded fallback, setting the perspectives' reasoning aside and merging only their written findings: read the prompt at \`${judgePromptPath}\`, write the merged findings to \`${conceptualResultsPath}\`.`,
|
|
179
211
|
"Each prompt file above is self-contained — it already defines the reviewer's persona, scope, file grants, and output schema. Pass the `prompt_path` to the executor as its instruction verbatim; do NOT restate the persona or re-describe the task in your dispatch message (the parenthesised name is only a label for you).",
|
|
180
212
|
],
|