audit-tools 0.40.1 → 0.41.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/dist/audit/cli/auditStep.d.ts +1 -1
- package/dist/audit/cli/conceptualDispatch.d.ts +3 -0
- package/dist/audit/cli/conceptualDispatch.d.ts.map +1 -1
- package/dist/audit/cli/conceptualDispatch.js +82 -24
- package/dist/audit/cli/conceptualDispatch.js.map +1 -1
- package/dist/audit/cli/dispatch/hostHandoff.d.ts +12 -0
- package/dist/audit/cli/dispatch/hostHandoff.d.ts.map +1 -1
- package/dist/audit/cli/dispatch/hostHandoff.js +84 -56
- package/dist/audit/cli/dispatch/hostHandoff.js.map +1 -1
- package/dist/audit/cli/fanoutLanes.d.ts +47 -11
- package/dist/audit/cli/fanoutLanes.d.ts.map +1 -1
- package/dist/audit/cli/fanoutLanes.js +15 -7
- package/dist/audit/cli/fanoutLanes.js.map +1 -1
- package/dist/audit/cli/laneSubmissions.d.ts +241 -0
- package/dist/audit/cli/laneSubmissions.d.ts.map +1 -0
- package/dist/audit/cli/laneSubmissions.js +438 -0
- package/dist/audit/cli/laneSubmissions.js.map +1 -0
- package/dist/audit/cli/laneValidators.d.ts +65 -0
- package/dist/audit/cli/laneValidators.d.ts.map +1 -0
- package/dist/audit/cli/laneValidators.js +145 -0
- package/dist/audit/cli/laneValidators.js.map +1 -0
- package/dist/audit/cli/nextStepCommand.d.ts +1 -1
- package/dist/audit/cli/nextStepCommand.d.ts.map +1 -1
- package/dist/audit/cli/nextStepCommand.js +118 -69
- package/dist/audit/cli/nextStepCommand.js.map +1 -1
- package/dist/audit/cli/nextStepHelpers.d.ts +73 -42
- package/dist/audit/cli/nextStepHelpers.d.ts.map +1 -1
- package/dist/audit/cli/nextStepHelpers.js +389 -253
- package/dist/audit/cli/nextStepHelpers.js.map +1 -1
- package/dist/audit/cli/recoverSubmissionCommand.d.ts +16 -0
- package/dist/audit/cli/recoverSubmissionCommand.d.ts.map +1 -0
- package/dist/audit/cli/recoverSubmissionCommand.js +73 -0
- package/dist/audit/cli/recoverSubmissionCommand.js.map +1 -0
- package/dist/audit/cli/semanticReviewStep.d.ts +3 -0
- package/dist/audit/cli/semanticReviewStep.d.ts.map +1 -1
- package/dist/audit/cli/semanticReviewStep.js +26 -1
- package/dist/audit/cli/semanticReviewStep.js.map +1 -1
- package/dist/audit/cli/steps.d.ts +75 -2
- package/dist/audit/cli/steps.d.ts.map +1 -1
- package/dist/audit/cli/steps.js +14 -0
- package/dist/audit/cli/steps.js.map +1 -1
- package/dist/audit/cli.d.ts.map +1 -1
- package/dist/audit/cli.js +5 -1
- package/dist/audit/cli.js.map +1 -1
- package/dist/audit/contracts/wrapperResponse.d.ts +7 -7
- package/dist/audit/contracts/wrapperResponse.js +1 -1
- package/dist/audit/contracts/wrapperResponse.js.map +1 -1
- package/dist/audit/io/artifacts.d.ts +12 -1
- package/dist/audit/io/artifacts.d.ts.map +1 -1
- package/dist/audit/io/artifacts.js +28 -1
- package/dist/audit/io/artifacts.js.map +1 -1
- package/dist/audit/orchestrator/synthesisExecutors.d.ts.map +1 -1
- package/dist/audit/orchestrator/synthesisExecutors.js +2 -0
- package/dist/audit/orchestrator/synthesisExecutors.js.map +1 -1
- package/dist/audit/reporting/synthesis.d.ts +9 -1
- package/dist/audit/reporting/synthesis.d.ts.map +1 -1
- package/dist/audit/reporting/synthesis.js +69 -1
- package/dist/audit/reporting/synthesis.js.map +1 -1
- package/dist/audit/supervisor/operatorHandoff.d.ts +9 -2
- package/dist/audit/supervisor/operatorHandoff.d.ts.map +1 -1
- package/dist/audit/supervisor/operatorHandoff.js +17 -10
- package/dist/audit/supervisor/operatorHandoff.js.map +1 -1
- package/dist/audit/types/designAssessment.d.ts +9 -5
- package/dist/audit/types/designAssessment.d.ts.map +1 -1
- package/dist/remediate/index.d.ts.map +1 -1
- package/dist/remediate/index.js +48 -1
- package/dist/remediate/index.js.map +1 -1
- package/dist/remediate/steps/dispatch/hostHandoff.d.ts +38 -6
- package/dist/remediate/steps/dispatch/hostHandoff.d.ts.map +1 -1
- package/dist/remediate/steps/dispatch/hostHandoff.js +87 -75
- package/dist/remediate/steps/dispatch/hostHandoff.js.map +1 -1
- package/dist/shared/decompose/charterClarification.d.ts +1 -1
- package/dist/shared/decompose/charterClarification.js +1 -1
- package/dist/shared/decompose/charterExtraction.js +1 -1
- package/dist/shared/decompose/charterExtraction.js.map +1 -1
- package/dist/shared/decompose/systemicChallenge.js +1 -1
- package/dist/shared/decompose/systemicChallenge.js.map +1 -1
- package/dist/shared/index.d.ts +11 -1
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +6 -1
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/io/auditToolsPaths.d.ts +24 -4
- package/dist/shared/io/auditToolsPaths.d.ts.map +1 -1
- package/dist/shared/io/auditToolsPaths.js +29 -5
- package/dist/shared/io/auditToolsPaths.js.map +1 -1
- package/dist/shared/io/schemaVersion.d.ts +5 -4
- package/dist/shared/io/schemaVersion.d.ts.map +1 -1
- package/dist/shared/io/schemaVersion.js +17 -5
- package/dist/shared/io/schemaVersion.js.map +1 -1
- package/dist/shared/loopCorePaths.d.ts.map +1 -1
- package/dist/shared/loopCorePaths.js +8 -2
- package/dist/shared/loopCorePaths.js.map +1 -1
- package/dist/shared/submission/expectedSubmissions.d.ts +88 -0
- package/dist/shared/submission/expectedSubmissions.d.ts.map +1 -0
- package/dist/shared/submission/expectedSubmissions.js +84 -0
- package/dist/shared/submission/expectedSubmissions.js.map +1 -0
- package/dist/shared/submission/handRecovery.d.ts +37 -0
- package/dist/shared/submission/handRecovery.d.ts.map +1 -0
- package/dist/shared/submission/handRecovery.js +123 -0
- package/dist/shared/submission/handRecovery.js.map +1 -0
- package/dist/shared/submission/submissionClassifier.d.ts +48 -0
- package/dist/shared/submission/submissionClassifier.d.ts.map +1 -0
- package/dist/shared/submission/submissionClassifier.js +83 -0
- package/dist/shared/submission/submissionClassifier.js.map +1 -0
- package/dist/shared/submission/submissionIdentity.d.ts +49 -0
- package/dist/shared/submission/submissionIdentity.d.ts.map +1 -0
- package/dist/shared/submission/submissionIdentity.js +90 -0
- package/dist/shared/submission/submissionIdentity.js.map +1 -0
- package/dist/shared/submission/submissionLedger.d.ts +37 -0
- package/dist/shared/submission/submissionLedger.d.ts.map +1 -0
- package/dist/shared/submission/submissionLedger.js +81 -0
- package/dist/shared/submission/submissionLedger.js.map +1 -0
- package/docs/audit-pkg/product.md +0 -2
- package/package.json +1 -1
|
@@ -9,9 +9,9 @@ import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { advance, DEFAULT_MAX_TRANSITIONS, isFileMissingError, isJsonParseError, readJsonFile, persistAnalyzerConsent, persistAnalyzerSettings, writeJsonFile, } from "audit-tools/shared";
|
|
11
11
|
import { loadArtifactBundle, promoteFinalAuditReport, writeCoreArtifacts, } from "../io/artifacts.js";
|
|
12
|
-
import { artifactTreeLockPath, auditReportPath, groundDesignFindings, promotedAuditReportPath, withFileLock, } from "audit-tools/shared";
|
|
13
|
-
import { CharterSubmissionSchema, CharterDeltaSubmissionSchema, ClarificationAnswersSubmissionSchema, CriticalFlowFallbackResultSchema, SynthesisNarrativeSchema, SystemicChallengeSubmissionSchema, } from "audit-tools/shared";
|
|
12
|
+
import { artifactTreeLockPath, auditReportPath, groundDesignFindings, laneAssetsDir, promotedAuditReportPath, withFileLock, } from "audit-tools/shared";
|
|
14
13
|
import { charterExtractionKindsForCeiling } from "./charterExtractionPrompt.js";
|
|
14
|
+
import { LANE_SUBMISSION_SCHEMAS, charterLaneSchema, describeSubmissionShapeMismatch, isSubmissionObjectMap, unwrapSubmissionArray, } from "./laneValidators.js";
|
|
15
15
|
import { advanceAudit } from "../orchestrator/advance.js";
|
|
16
16
|
import { captureDesignReviewSnapshot, isDesignReviewStale, } from "../orchestrator/designReviewSnapshot.js";
|
|
17
17
|
import { computeArtifactStateSignature } from "../orchestrator/artifactMetadata.js";
|
|
@@ -20,22 +20,23 @@ import { isHostDelegationExecutor } from "../orchestrator/executors.js";
|
|
|
20
20
|
import { resolveCharterCeiling, ceilingRequestsCharters, } from "../orchestrator/charterExtractionExecutor.js";
|
|
21
21
|
import { resolveClarificationAttention } from "../orchestrator/charterClarificationExecutor.js";
|
|
22
22
|
import { deriveAuditState } from "../orchestrator/state.js";
|
|
23
|
-
import { deriveIntentEquivalenceStatus
|
|
23
|
+
import { deriveIntentEquivalenceStatus } from "../orchestrator/intentEquivalenceExecutor.js";
|
|
24
24
|
import { checkFileIntegrity } from "../orchestrator/fileIntegrity.js";
|
|
25
25
|
import { graphEnrichmentUnresolvedAnalyzers, graphEnrichmentLowConfidenceEdges, pendingAnalyzerConsent, } from "../orchestrator/hostInputPause.js";
|
|
26
26
|
import { runAuditStep } from "./auditStep.js";
|
|
27
27
|
import { writeHandoffOnly, ensureSemanticReviewRun, loadCurrentActiveReviewRun, } from "./reviewRun.js";
|
|
28
28
|
import { buildPendingAuditTasks } from "./dispatch.js";
|
|
29
29
|
import { ingestAuditHostResults } from "./dispatch/hostHandoff.js";
|
|
30
|
+
import { CHARTER_EXTRACTION_MERGED_FILENAME, GATE_LANES, charterExtractionLane, charterExtractionPacketFilename, laneSubmissionPath, recordHostResultOutcomes, recordLaneOutcome, } from "./laneSubmissions.js";
|
|
30
31
|
/**
|
|
31
|
-
* Read a
|
|
32
|
-
* `ok` when the file exists and parses;
|
|
33
|
-
* `malformed` when the file exists but is not
|
|
34
|
-
* CALLER's to quarantine, never an
|
|
35
|
-
* re-throw unchanged.
|
|
32
|
+
* Read a lane's submission from the TOOL-COMPUTED path its emission bound —
|
|
33
|
+
* never a name a host could type. `ok` when the file exists and parses;
|
|
34
|
+
* `absent` on ENOENT-family errors; `malformed` when the file exists but is not
|
|
35
|
+
* JSON — submitted content is the CALLER's to quarantine, never an
|
|
36
|
+
* infrastructure failure. All other IO errors re-throw unchanged.
|
|
36
37
|
*/
|
|
37
|
-
export async function
|
|
38
|
-
const filePath =
|
|
38
|
+
export async function tryConsumeSubmission(artifactsDir, lane) {
|
|
39
|
+
const filePath = laneSubmissionPath(artifactsDir, lane);
|
|
39
40
|
try {
|
|
40
41
|
const value = await readJsonFile(filePath);
|
|
41
42
|
return { status: "ok", value, path: filePath };
|
|
@@ -149,8 +150,8 @@ export async function buildTerminalStep(params, bundle, state, blockedReason) {
|
|
|
149
150
|
* mirroring the analyzer-install consent fold exactly:
|
|
150
151
|
* - nothing pending (acquisition off / token present / all decided) → run the
|
|
151
152
|
* deterministic acquisition executor (`fallthrough`);
|
|
152
|
-
* - a decisions
|
|
153
|
-
* `{ "<id>": "granted" | "declined" }`) → persist the decisions into
|
|
153
|
+
* - a decisions submission arrived on the `analyzer_consent` lane
|
|
154
|
+
* (`{ "<id>": "granted" | "declined" }`) → persist the decisions into
|
|
154
155
|
* session config (decisions durable, tokens never), fold them into the
|
|
155
156
|
* in-flight acquisition options, and re-scan (`continue`);
|
|
156
157
|
* - otherwise → emit the ONE batched operator-interactive offer step
|
|
@@ -167,43 +168,36 @@ export async function handleAnalyzerConsentBranch(params, bundle, state, analyze
|
|
|
167
168
|
});
|
|
168
169
|
if (pending.length === 0)
|
|
169
170
|
return { action: "fallthrough" };
|
|
170
|
-
const incoming = await
|
|
171
|
+
const incoming = await consumeEnumMapSubmission(params.artifactsDir, GATE_LANES.analyzer_consent, ANALYZER_CONSENT_VALUES);
|
|
171
172
|
if (incoming.status === "quarantined") {
|
|
172
173
|
return { action: "continue" };
|
|
173
174
|
}
|
|
174
175
|
if (incoming.status === "ok") {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (Object.keys(decisions).length > 0) {
|
|
182
|
-
await persistAnalyzerConsent(params.root, decisions);
|
|
183
|
-
if (params.externalAcquisition) {
|
|
184
|
-
params.externalAcquisition.analyzerConsent = {
|
|
185
|
-
...(params.externalAcquisition.analyzerConsent ?? {}),
|
|
186
|
-
...decisions,
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
else {
|
|
191
|
-
const invalidEntries = Object.keys(incoming.value).join(", ") || "(none)";
|
|
192
|
-
process.stderr.write(`[audit-code] analyzer-consent-decisions.json ignored: no recognized values (got: ${invalidEntries}). ` +
|
|
193
|
-
`Valid values are: granted, declined.\n`);
|
|
176
|
+
await persistAnalyzerConsent(params.root, incoming.values);
|
|
177
|
+
if (params.externalAcquisition) {
|
|
178
|
+
params.externalAcquisition.analyzerConsent = {
|
|
179
|
+
...(params.externalAcquisition.analyzerConsent ?? {}),
|
|
180
|
+
...incoming.values,
|
|
181
|
+
};
|
|
194
182
|
}
|
|
195
183
|
await unlink(incoming.path).catch(() => { });
|
|
184
|
+
await recordLaneOutcome(params.artifactsDir, GATE_LANES.analyzer_consent, {
|
|
185
|
+
kind: "accepted",
|
|
186
|
+
...(incoming.ignored.length > 0
|
|
187
|
+
? { message: describeIgnoredKeys(incoming.ignored, ANALYZER_CONSENT_VALUES) }
|
|
188
|
+
: {}),
|
|
189
|
+
});
|
|
196
190
|
return { action: "continue" };
|
|
197
191
|
}
|
|
198
192
|
return { action: "return", result: { kind: "analyzer_consent", state, bundle, pending } };
|
|
199
193
|
}
|
|
200
194
|
/**
|
|
201
|
-
* Handle the `graph_enrichment_executor`
|
|
195
|
+
* Handle the `graph_enrichment_executor` submission-polling block.
|
|
202
196
|
* Checks for pending analyzer install decisions and edge-reasoning results.
|
|
203
197
|
* Returns an action object:
|
|
204
|
-
* - `continue` → caller should keep folding (already consumed
|
|
198
|
+
* - `continue` → caller should keep folding (already consumed a submission).
|
|
205
199
|
* - `return` → caller should emit the embedded result to cmdNextStep.
|
|
206
|
-
* - `fallthrough` →
|
|
200
|
+
* - `fallthrough` → nothing submitted; run the deterministic executor.
|
|
207
201
|
*/
|
|
208
202
|
export async function handleGraphEnrichmentBranch(params, bundle, state, analyzersRef, deps = {}) {
|
|
209
203
|
const runStep = deps.runStep ?? runAuditStep;
|
|
@@ -217,37 +211,23 @@ export async function handleGraphEnrichmentBranch(params, bundle, state, analyze
|
|
|
217
211
|
};
|
|
218
212
|
const unresolved = graphEnrichmentUnresolvedAnalyzers(bundle, pauseInputs);
|
|
219
213
|
if (unresolved.length > 0) {
|
|
220
|
-
const incoming = await
|
|
214
|
+
const incoming = await consumeEnumMapSubmission(params.artifactsDir, GATE_LANES.analyzer_decisions, ANALYZER_SETTING_VALUES);
|
|
221
215
|
if (incoming.status === "quarantined") {
|
|
222
216
|
// A non-object top-level value used to be neither merged, deleted, nor
|
|
223
|
-
// diagnosed — the file lingered
|
|
217
|
+
// diagnosed — the file lingered at its bound path and the analyzer_install
|
|
224
218
|
// step re-emitted silently forever. Quarantined + diagnosed instead.
|
|
225
219
|
return { action: "continue" };
|
|
226
220
|
}
|
|
227
221
|
if (incoming.status === "ok") {
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
if (value === "ephemeral" ||
|
|
231
|
-
value === "permanent" ||
|
|
232
|
-
value === "skip" ||
|
|
233
|
-
value === "repo" ||
|
|
234
|
-
value === "auto") {
|
|
235
|
-
settings[id] = value;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
if (Object.keys(settings).length > 0) {
|
|
239
|
-
const merged = await persistAnalyzerSettings(params.root, settings);
|
|
240
|
-
analyzersRef.value = merged.analyzers;
|
|
241
|
-
}
|
|
242
|
-
else {
|
|
243
|
-
// All entries in analyzer-decisions.json failed the recognized-value
|
|
244
|
-
// check (ephemeral|permanent|skip|repo|auto). Emit a diagnostic so the
|
|
245
|
-
// operator knows why no settings were applied (COR-03418a9f fix).
|
|
246
|
-
const invalidEntries = Object.keys(incoming.value).join(", ") || "(none)";
|
|
247
|
-
process.stderr.write(`[audit-code] analyzer-decisions.json ignored: no recognized values (got: ${invalidEntries}). ` +
|
|
248
|
-
`Valid values are: ephemeral, permanent, skip, repo, auto.\n`);
|
|
249
|
-
}
|
|
222
|
+
const merged = await persistAnalyzerSettings(params.root, incoming.values);
|
|
223
|
+
analyzersRef.value = merged.analyzers;
|
|
250
224
|
await unlink(incoming.path).catch(() => { });
|
|
225
|
+
await recordLaneOutcome(params.artifactsDir, GATE_LANES.analyzer_decisions, {
|
|
226
|
+
kind: "accepted",
|
|
227
|
+
...(incoming.ignored.length > 0
|
|
228
|
+
? { message: describeIgnoredKeys(incoming.ignored, ANALYZER_SETTING_VALUES) }
|
|
229
|
+
: {}),
|
|
230
|
+
});
|
|
251
231
|
return { action: "continue" };
|
|
252
232
|
}
|
|
253
233
|
return { action: "return", result: { kind: "analyzer_install", state, bundle, unresolved } };
|
|
@@ -263,15 +243,20 @@ export async function handleGraphEnrichmentBranch(params, bundle, state, analyze
|
|
|
263
243
|
{
|
|
264
244
|
const candidates = graphEnrichmentLowConfidenceEdges(bundle, pauseInputs);
|
|
265
245
|
if (candidates.length > 0) {
|
|
266
|
-
const edgeReasoningIncoming = await
|
|
246
|
+
const edgeReasoningIncoming = await tryConsumeSubmission(params.artifactsDir, GATE_LANES.edge_reasoning);
|
|
267
247
|
if (edgeReasoningIncoming.status === "malformed") {
|
|
268
|
-
const quarantinePath = await
|
|
248
|
+
const quarantinePath = await quarantineSubmissionFile(params.artifactsDir, edgeReasoningIncoming.path, GATE_LANES.edge_reasoning);
|
|
269
249
|
await recordEdgeReasoningRejection(params.artifactsDir, {
|
|
270
|
-
|
|
250
|
+
lane: GATE_LANES.edge_reasoning,
|
|
271
251
|
quarantine_path: quarantinePath,
|
|
272
252
|
reason: edgeReasoningIncoming.reason,
|
|
273
253
|
rejected_at: new Date().toISOString(),
|
|
274
254
|
});
|
|
255
|
+
await recordLaneOutcome(params.artifactsDir, GATE_LANES.edge_reasoning, {
|
|
256
|
+
kind: "rejected",
|
|
257
|
+
issueCode: "submission_malformed",
|
|
258
|
+
message: edgeReasoningIncoming.reason,
|
|
259
|
+
});
|
|
275
260
|
return { action: "continue" };
|
|
276
261
|
}
|
|
277
262
|
if (edgeReasoningIncoming.status === "ok") {
|
|
@@ -283,18 +268,23 @@ export async function handleGraphEnrichmentBranch(params, bundle, state, analyze
|
|
|
283
268
|
// so the same "exactly one array-valued top-level property" rule
|
|
284
269
|
// applies, and a bare rewrites array is accepted too); anything else is
|
|
285
270
|
// quarantined and named in the re-emitted step's prompt.
|
|
286
|
-
const unwrapped =
|
|
271
|
+
const unwrapped = unwrapSubmissionArray(edgeReasoningIncoming.value);
|
|
287
272
|
if (!unwrapped.ok) {
|
|
288
|
-
const quarantinePath = await
|
|
273
|
+
const quarantinePath = await quarantineSubmissionFile(params.artifactsDir, edgeReasoningIncoming.path, GATE_LANES.edge_reasoning);
|
|
289
274
|
await recordEdgeReasoningRejection(params.artifactsDir, {
|
|
290
|
-
|
|
275
|
+
lane: GATE_LANES.edge_reasoning,
|
|
291
276
|
quarantine_path: quarantinePath,
|
|
292
277
|
reason: unwrapped.reason,
|
|
293
278
|
rejected_at: new Date().toISOString(),
|
|
294
279
|
});
|
|
280
|
+
await recordLaneOutcome(params.artifactsDir, GATE_LANES.edge_reasoning, {
|
|
281
|
+
kind: "rejected",
|
|
282
|
+
issueCode: "submission_contract_invalid",
|
|
283
|
+
message: unwrapped.reason,
|
|
284
|
+
});
|
|
295
285
|
return { action: "continue" };
|
|
296
286
|
}
|
|
297
|
-
// Apply BEFORE deleting the
|
|
287
|
+
// Apply BEFORE deleting the submission: if runStep throws (locks,
|
|
298
288
|
// crash), the submission survives for the retry instead of being lost.
|
|
299
289
|
await runStep({
|
|
300
290
|
root: params.root,
|
|
@@ -306,6 +296,9 @@ export async function handleGraphEnrichmentBranch(params, bundle, state, analyze
|
|
|
306
296
|
});
|
|
307
297
|
await unlink(edgeReasoningIncoming.path).catch(() => { });
|
|
308
298
|
await clearEdgeReasoningRejection(params.artifactsDir);
|
|
299
|
+
await recordLaneOutcome(params.artifactsDir, GATE_LANES.edge_reasoning, {
|
|
300
|
+
kind: "accepted",
|
|
301
|
+
});
|
|
309
302
|
return { action: "continue" };
|
|
310
303
|
}
|
|
311
304
|
return { action: "return", result: { kind: "edge_reasoning", state, bundle, candidates } };
|
|
@@ -317,11 +310,11 @@ export async function handleGraphEnrichmentBranch(params, bundle, state, analyze
|
|
|
317
310
|
return { action: "fallthrough" };
|
|
318
311
|
}
|
|
319
312
|
/**
|
|
320
|
-
* Handle the `design_review_contract` or `design_review_conceptual`
|
|
321
|
-
* polling blocks. Checks
|
|
313
|
+
* Handle the `design_review_contract` or `design_review_conceptual` submission
|
|
314
|
+
* polling blocks. Checks the contract and conceptual lanes independently.
|
|
322
315
|
*
|
|
323
316
|
* Returns:
|
|
324
|
-
* - `continue` → one or both
|
|
317
|
+
* - `continue` → one or both lane submissions were consumed; keep folding.
|
|
325
318
|
* - `design_review_parallel` → both passes still needed; dispatch two subagents.
|
|
326
319
|
* - `design_review_contract` → only contract pass still needed.
|
|
327
320
|
* - `design_review_conceptual` → only conceptual pass still needed.
|
|
@@ -333,37 +326,17 @@ function passIsStale(bundle, pass) {
|
|
|
333
326
|
const snapshot = bundle.design_review_snapshots?.[pass];
|
|
334
327
|
return snapshot ? isDesignReviewStale(snapshot, bundle) : false;
|
|
335
328
|
}
|
|
336
|
-
/** Human-readable description of why an incoming value is neither an array nor a single-array-wrapped object. */
|
|
337
|
-
function describeIncomingShapeMismatch(value) {
|
|
338
|
-
if (value === null)
|
|
339
|
-
return "null";
|
|
340
|
-
if (Array.isArray(value))
|
|
341
|
-
return "an array"; // reachable from consumeObjectIncoming (an array is not a key→value map)
|
|
342
|
-
const t = typeof value;
|
|
343
|
-
if (t !== "object")
|
|
344
|
-
return `a bare ${t}`;
|
|
345
|
-
const entries = Object.entries(value);
|
|
346
|
-
if (entries.length === 0)
|
|
347
|
-
return "an empty object";
|
|
348
|
-
const arrayKeys = entries.filter(([, v]) => Array.isArray(v)).map(([k]) => k);
|
|
349
|
-
const allKeys = entries.map(([k]) => k).join(", ");
|
|
350
|
-
if (arrayKeys.length === 0) {
|
|
351
|
-
return `an object with no array-valued properties (keys: ${allKeys})`;
|
|
352
|
-
}
|
|
353
|
-
return (`an object with ${arrayKeys.length} array-valued propert${arrayKeys.length === 1 ? "y" : "ies"} ` +
|
|
354
|
-
`out of ${entries.length} total key(s) (${allKeys}) — exactly one top-level array property is ` +
|
|
355
|
-
`required for the tolerant unwrap`);
|
|
356
|
-
}
|
|
357
329
|
/**
|
|
358
|
-
* Move a
|
|
359
|
-
*
|
|
360
|
-
*
|
|
330
|
+
* Move a refused submission to `<artifactsDir>/quarantine/` rather than
|
|
331
|
+
* deleting it. Falls back to copy+unlink if `rename` fails (e.g. a cross-device
|
|
332
|
+
* artifacts mount) so the content is never lost. The quarantined file is named
|
|
333
|
+
* for its LANE — the bound path is a digest, which tells an operator nothing.
|
|
361
334
|
*/
|
|
362
|
-
async function
|
|
335
|
+
async function quarantineSubmissionFile(artifactsDir, filePath, lane) {
|
|
363
336
|
const quarantineDir = join(artifactsDir, "quarantine");
|
|
364
337
|
await mkdir(quarantineDir, { recursive: true });
|
|
365
338
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
366
|
-
const quarantinePath = join(quarantineDir, `${
|
|
339
|
+
const quarantinePath = join(quarantineDir, `${lane}.${timestamp}.json`);
|
|
367
340
|
try {
|
|
368
341
|
await rename(filePath, quarantinePath);
|
|
369
342
|
}
|
|
@@ -381,94 +354,165 @@ async function quarantineIncomingFile(artifactsDir, filePath, originalFilename)
|
|
|
381
354
|
}
|
|
382
355
|
/**
|
|
383
356
|
* Quarantine a submission that failed zod validation — or failed to parse as
|
|
384
|
-
* JSON at all (a plain string reason): move it
|
|
385
|
-
* unlink-and-discard)
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
*
|
|
357
|
+
* JSON at all (a plain string reason): move it off its bound path (never
|
|
358
|
+
* unlink-and-discard), write a stderr diagnostic naming the quarantined file +
|
|
359
|
+
* the error, and record the refusal on the submission ledger so a repaired run
|
|
360
|
+
* stays distinguishable from a clean one. The single loud-quarantine path
|
|
361
|
+
* shared by every schema-validated gate (`runOmittableGate` +
|
|
362
|
+
* `handleIntentEquivalenceBranch` + the charter lane loop) so the "quarantine
|
|
363
|
+
* loudly" property cannot drift between them. Returns the quarantine path.
|
|
390
364
|
*/
|
|
391
|
-
async function
|
|
365
|
+
async function quarantineMisshapedSubmission(artifactsDir, filePath, lane, error) {
|
|
392
366
|
const reason = typeof error === "string"
|
|
393
367
|
? error
|
|
394
368
|
: error.issues
|
|
395
369
|
.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`)
|
|
396
370
|
.join("; ");
|
|
397
|
-
const quarantinePath = await
|
|
398
|
-
process.stderr.write(`[audit-code] ${
|
|
371
|
+
const quarantinePath = await quarantineSubmissionFile(artifactsDir, filePath, lane);
|
|
372
|
+
process.stderr.write(`[audit-code] ${lane} submission quarantined to ${quarantinePath}: ${reason}. ` +
|
|
399
373
|
`Fix the shape and resubmit.\n`);
|
|
374
|
+
await recordLaneOutcome(artifactsDir, lane, {
|
|
375
|
+
kind: "rejected",
|
|
376
|
+
issueCode: typeof error === "string" ? "submission_malformed" : "submission_contract_invalid",
|
|
377
|
+
message: reason,
|
|
378
|
+
});
|
|
400
379
|
return quarantinePath;
|
|
401
380
|
}
|
|
402
381
|
/**
|
|
403
|
-
*
|
|
404
|
-
* object wrapping exactly one array-valued property is unambiguous and is
|
|
405
|
-
* accepted as that array. Anything else fails with a shape description.
|
|
406
|
-
* Single-sourced so `consumeArrayIncoming` and the edge-reasoning gate cannot
|
|
407
|
-
* drift on what shapes are accepted.
|
|
408
|
-
*/
|
|
409
|
-
function unwrapIncomingArray(value) {
|
|
410
|
-
if (Array.isArray(value))
|
|
411
|
-
return { ok: true, array: value };
|
|
412
|
-
if (value !== null && typeof value === "object") {
|
|
413
|
-
const entries = Object.entries(value);
|
|
414
|
-
if (entries.length === 1 && Array.isArray(entries[0][1])) {
|
|
415
|
-
return { ok: true, array: entries[0][1] };
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
return { ok: false, reason: describeIncomingShapeMismatch(value) };
|
|
419
|
-
}
|
|
420
|
-
/**
|
|
421
|
-
* Read a JSON `incoming/` file expected to be an array (or a top-level object
|
|
382
|
+
* Read a lane submission expected to be an array (or a top-level object
|
|
422
383
|
* wrapping exactly one array-valued property, the tolerant unwrap). Accepts
|
|
423
|
-
* either shape
|
|
424
|
-
*
|
|
384
|
+
* either shape; any other shape is quarantined (never unlinked-and-discarded)
|
|
385
|
+
* and reported with a reason.
|
|
386
|
+
*
|
|
387
|
+
* An accepted submission is NOT deleted here (P25-f) — the caller unlinks after
|
|
388
|
+
* it has applied the value, so a submission is never destroyed before its
|
|
389
|
+
* content has landed somewhere else.
|
|
425
390
|
*/
|
|
426
|
-
export async function
|
|
427
|
-
const incoming = await
|
|
391
|
+
export async function consumeArraySubmission(artifactsDir, lane) {
|
|
392
|
+
const incoming = await tryConsumeSubmission(artifactsDir, lane);
|
|
428
393
|
if (incoming.status === "absent")
|
|
429
394
|
return { status: "absent" };
|
|
430
395
|
if (incoming.status === "malformed") {
|
|
431
|
-
const quarantinePath = await
|
|
432
|
-
|
|
396
|
+
const quarantinePath = await quarantineSubmissionFile(artifactsDir, incoming.path, lane);
|
|
397
|
+
await recordLaneOutcome(artifactsDir, lane, {
|
|
398
|
+
kind: "rejected",
|
|
399
|
+
issueCode: "submission_malformed",
|
|
400
|
+
message: incoming.reason,
|
|
401
|
+
});
|
|
402
|
+
return { status: "quarantined", quarantinePath, lane, reason: incoming.reason };
|
|
433
403
|
}
|
|
434
404
|
const { value, path } = incoming;
|
|
435
|
-
const unwrapped =
|
|
405
|
+
const unwrapped = unwrapSubmissionArray(value);
|
|
436
406
|
if (unwrapped.ok) {
|
|
437
|
-
await unlink(path).catch(() => { });
|
|
438
407
|
return { status: "ok", value: unwrapped.array, path };
|
|
439
408
|
}
|
|
440
|
-
const quarantinePath = await
|
|
441
|
-
|
|
409
|
+
const quarantinePath = await quarantineSubmissionFile(artifactsDir, path, lane);
|
|
410
|
+
await recordLaneOutcome(artifactsDir, lane, {
|
|
411
|
+
kind: "rejected",
|
|
412
|
+
issueCode: "submission_contract_invalid",
|
|
413
|
+
message: unwrapped.reason,
|
|
414
|
+
});
|
|
415
|
+
return { status: "quarantined", quarantinePath, lane, reason: unwrapped.reason };
|
|
442
416
|
}
|
|
443
417
|
/**
|
|
444
|
-
* Read a
|
|
445
|
-
* key → value map, e.g. analyzer
|
|
418
|
+
* Read a lane submission expected to be a plain top-level object (a
|
|
419
|
+
* key → value map, e.g. the analyzer decisions). A non-object value — null,
|
|
446
420
|
* an array, a bare primitive — is quarantined with a stderr diagnostic rather
|
|
447
|
-
* than left lingering
|
|
448
|
-
* re-ask silently forever).
|
|
449
|
-
*
|
|
450
|
-
*
|
|
421
|
+
* than left lingering at the bound path (where it used to make the emitting
|
|
422
|
+
* step re-ask silently forever). An accepted file is NOT deleted here — the
|
|
423
|
+
* caller unlinks after applying, so a crash mid-apply retains the submission
|
|
424
|
+
* for the retry.
|
|
451
425
|
*/
|
|
452
|
-
export async function
|
|
453
|
-
const incoming = await
|
|
426
|
+
export async function consumeObjectSubmission(artifactsDir, lane) {
|
|
427
|
+
const incoming = await tryConsumeSubmission(artifactsDir, lane);
|
|
454
428
|
if (incoming.status === "absent")
|
|
455
429
|
return { status: "absent" };
|
|
456
430
|
if (incoming.status === "malformed") {
|
|
457
|
-
const quarantinePath = await
|
|
458
|
-
process.stderr.write(`[audit-code] ${
|
|
431
|
+
const quarantinePath = await quarantineSubmissionFile(artifactsDir, incoming.path, lane);
|
|
432
|
+
process.stderr.write(`[audit-code] ${lane} submission quarantined to ${quarantinePath}: ${incoming.reason}. ` +
|
|
459
433
|
`Fix the JSON and resubmit.\n`);
|
|
434
|
+
await recordLaneOutcome(artifactsDir, lane, {
|
|
435
|
+
kind: "rejected",
|
|
436
|
+
issueCode: "submission_malformed",
|
|
437
|
+
message: incoming.reason,
|
|
438
|
+
});
|
|
460
439
|
return { status: "quarantined", quarantinePath, reason: incoming.reason };
|
|
461
440
|
}
|
|
462
441
|
const { value, path } = incoming;
|
|
463
|
-
if (
|
|
464
|
-
return { status: "ok", value
|
|
442
|
+
if (isSubmissionObjectMap(value)) {
|
|
443
|
+
return { status: "ok", value, path };
|
|
465
444
|
}
|
|
466
|
-
const reason =
|
|
467
|
-
const quarantinePath = await
|
|
468
|
-
process.stderr.write(`[audit-code] ${
|
|
445
|
+
const reason = describeSubmissionShapeMismatch(value);
|
|
446
|
+
const quarantinePath = await quarantineSubmissionFile(artifactsDir, path, lane);
|
|
447
|
+
process.stderr.write(`[audit-code] ${lane} submission quarantined to ${quarantinePath}: expected a JSON object, got ${reason}. ` +
|
|
469
448
|
`Fix the shape and resubmit.\n`);
|
|
449
|
+
await recordLaneOutcome(artifactsDir, lane, {
|
|
450
|
+
kind: "rejected",
|
|
451
|
+
issueCode: "submission_contract_invalid",
|
|
452
|
+
message: reason,
|
|
453
|
+
});
|
|
470
454
|
return { status: "quarantined", quarantinePath, reason };
|
|
471
455
|
}
|
|
456
|
+
/** The two decision vocabularies the operator-facing analyzer gates accept. */
|
|
457
|
+
const ANALYZER_CONSENT_VALUES = ["granted", "declined"];
|
|
458
|
+
const ANALYZER_SETTING_VALUES = [
|
|
459
|
+
"ephemeral",
|
|
460
|
+
"permanent",
|
|
461
|
+
"skip",
|
|
462
|
+
"repo",
|
|
463
|
+
"auto",
|
|
464
|
+
];
|
|
465
|
+
/** Name the keys that carried no recognized value, for the ledger event. */
|
|
466
|
+
function describeIgnoredKeys(ignored, allowed) {
|
|
467
|
+
return (`ignored ${ignored.length} unrecognized entr${ignored.length === 1 ? "y" : "ies"} ` +
|
|
468
|
+
`(${ignored.join(", ")}); recognized values are: ${allowed.join(", ")}`);
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Read a decisions submission — a `{ "<id>": "<one of a fixed vocabulary>" }`
|
|
472
|
+
* map — for the two operator-facing analyzer gates.
|
|
473
|
+
*
|
|
474
|
+
* The value-enum filter used to be per-gate, and both copies treated a
|
|
475
|
+
* submission with ZERO recognized values as a SUCCESS: nothing was applied, the
|
|
476
|
+
* file was deleted, and an `accepted` event went on the ledger, so a host that
|
|
477
|
+
* answered in the wrong vocabulary had its answer destroyed, the run recorded
|
|
478
|
+
* as clean, and the identical step re-emitted. A submission that says nothing
|
|
479
|
+
* the gate understands is a refusal: it is quarantined (bytes kept), recorded
|
|
480
|
+
* `rejected`, and re-asked. PARTIAL recognition still applies — the operator's
|
|
481
|
+
* real decisions are not held hostage to one typo — with the ignored keys named
|
|
482
|
+
* on the accepting event so the omission is on the record rather than in a
|
|
483
|
+
* stderr line nobody kept.
|
|
484
|
+
*/
|
|
485
|
+
async function consumeEnumMapSubmission(artifactsDir, lane, allowed) {
|
|
486
|
+
const incoming = await consumeObjectSubmission(artifactsDir, lane);
|
|
487
|
+
if (incoming.status === "absent")
|
|
488
|
+
return { status: "absent" };
|
|
489
|
+
if (incoming.status === "quarantined")
|
|
490
|
+
return { status: "quarantined" };
|
|
491
|
+
const values = {};
|
|
492
|
+
const ignored = [];
|
|
493
|
+
for (const [id, value] of Object.entries(incoming.value)) {
|
|
494
|
+
if (allowed.includes(value)) {
|
|
495
|
+
values[id] = value;
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
ignored.push(id);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
if (Object.keys(values).length > 0) {
|
|
502
|
+
return { status: "ok", values, ignored, path: incoming.path };
|
|
503
|
+
}
|
|
504
|
+
const reason = `no recognized values (got: ${Object.keys(incoming.value).join(", ") || "(none)"}). ` +
|
|
505
|
+
`Valid values are: ${allowed.join(", ")}.`;
|
|
506
|
+
const quarantinePath = await quarantineSubmissionFile(artifactsDir, incoming.path, lane);
|
|
507
|
+
process.stderr.write(`[audit-code] ${lane} submission quarantined to ${quarantinePath}: ${reason} ` +
|
|
508
|
+
`Fix the values and resubmit.\n`);
|
|
509
|
+
await recordLaneOutcome(artifactsDir, lane, {
|
|
510
|
+
kind: "rejected",
|
|
511
|
+
issueCode: "submission_contract_invalid",
|
|
512
|
+
message: reason,
|
|
513
|
+
});
|
|
514
|
+
return { status: "quarantined" };
|
|
515
|
+
}
|
|
472
516
|
function edgeReasoningRejectionPath(artifactsDir) {
|
|
473
517
|
return join(artifactsDir, "quarantine", "edge-reasoning.rejection.json");
|
|
474
518
|
}
|
|
@@ -502,27 +546,38 @@ export async function renderEdgeReasoningRejectionNotice(artifactsDir) {
|
|
|
502
546
|
"Your last edge-reasoning submission did not match the expected shape and was " +
|
|
503
547
|
"quarantined (not applied, not silently discarded). Fix the shape and resubmit:",
|
|
504
548
|
"",
|
|
505
|
-
`- \`${rejection.
|
|
549
|
+
`- lane \`${rejection.lane}\` quarantined to \`${rejection.quarantine_path}\` (${rejection.rejected_at}): ${rejection.reason}`,
|
|
506
550
|
"",
|
|
507
551
|
'Expected shape: {"rewrites":[{"from":"...","to":"...","kind":"...","reason":"..."}]} — ' +
|
|
508
552
|
"a bare JSON array of rewrites is also accepted.",
|
|
509
553
|
].join("\n");
|
|
510
554
|
}
|
|
511
555
|
/**
|
|
512
|
-
* Record a quarantined design-review submission
|
|
513
|
-
*
|
|
514
|
-
*
|
|
515
|
-
*
|
|
516
|
-
*
|
|
517
|
-
*
|
|
518
|
-
*
|
|
556
|
+
* Record a quarantined design-review submission.
|
|
557
|
+
*
|
|
558
|
+
* TWO homes, because they answer different questions and one of them can be
|
|
559
|
+
* absent. `design_assessment.rejected_submissions` is what the re-emitted step
|
|
560
|
+
* reads back to tell the host WHY its last submission was refused — so it is
|
|
561
|
+
* written whenever there is an assessment to write it on. The submission ledger
|
|
562
|
+
* is the durable record that a refusal happened at all, and it is written
|
|
563
|
+
* unconditionally.
|
|
564
|
+
*
|
|
565
|
+
* This function used to open `if (!existing) return;`, which meant the one case
|
|
566
|
+
* where a submission had no merge target — exactly the case where the host most
|
|
567
|
+
* needs to be told something went wrong — recorded nothing anywhere. The
|
|
568
|
+
* ledger closes that hole without inventing a partial `design_assessment.json`
|
|
569
|
+
* for an assessment that does not exist yet: every quarantine reaches it, but
|
|
570
|
+
* through the ONE site that performs the quarantine (`consumeArraySubmission`,
|
|
571
|
+
* or `holdWithoutTarget` for the no-merge-target case), never a second append
|
|
572
|
+
* here. Two appends per refusal made a single rejection read as two on a record
|
|
573
|
+
* whose whole value is counting them.
|
|
519
574
|
*/
|
|
520
575
|
async function recordRejectedDesignReviewSubmission(artifactsDir, existing, pass, quarantined) {
|
|
521
576
|
if (!existing)
|
|
522
577
|
return;
|
|
523
578
|
const entry = {
|
|
524
579
|
pass,
|
|
525
|
-
|
|
580
|
+
lane: quarantined.lane,
|
|
526
581
|
quarantine_path: quarantined.quarantinePath,
|
|
527
582
|
reason: quarantined.reason,
|
|
528
583
|
rejected_at: new Date().toISOString(),
|
|
@@ -552,7 +607,7 @@ export function renderDesignReviewRejectionNotice(bundle, passes) {
|
|
|
552
607
|
"",
|
|
553
608
|
];
|
|
554
609
|
for (const r of rejected) {
|
|
555
|
-
lines.push(`- **${r.pass}** — \`${r.
|
|
610
|
+
lines.push(`- **${r.pass}** — lane \`${r.lane}\` quarantined to \`${r.quarantine_path}\` (${r.rejected_at}): ${r.reason}`);
|
|
556
611
|
}
|
|
557
612
|
lines.push("", "Expected shape: a JSON array of findings, or a top-level object wrapping exactly " +
|
|
558
613
|
'one array-valued property (e.g. `{"findings": [...]}`).');
|
|
@@ -560,9 +615,42 @@ export function renderDesignReviewRejectionNotice(bundle, passes) {
|
|
|
560
615
|
}
|
|
561
616
|
export async function handleDesignReviewBranch(params, bundle, state) {
|
|
562
617
|
const existing = bundle.design_assessment;
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
618
|
+
/**
|
|
619
|
+
* P25-f: a valid submission with nowhere to merge it is HELD and RECORDED,
|
|
620
|
+
* never consumed-and-dropped. `design_assessment` is normally present by the
|
|
621
|
+
* time this branch runs (`design_assessment_current` outranks both review
|
|
622
|
+
* passes in PRIORITY) — but that is a reasoning argument, and the old code
|
|
623
|
+
* relied on it: it deleted the file at unwrap time and then skipped the merge
|
|
624
|
+
* on `&& existing`, destroying valid work and re-emitting the identical step
|
|
625
|
+
* with zero signal to the host. Quarantining keeps the bytes and puts the
|
|
626
|
+
* refusal on the record, so the next pass can still use them.
|
|
627
|
+
*/
|
|
628
|
+
const holdWithoutTarget = async (pass, lane, path) => {
|
|
629
|
+
const reason = "no design assessment exists yet to merge this submission into";
|
|
630
|
+
const quarantinePath = await quarantineSubmissionFile(params.artifactsDir, path, lane);
|
|
631
|
+
process.stderr.write(`[audit-code] ${lane} submission quarantined to ${quarantinePath}: no design ` +
|
|
632
|
+
`assessment exists yet to merge it into. Re-run next-step; the assessment is ` +
|
|
633
|
+
`built by a higher-priority obligation.\n`);
|
|
634
|
+
// The ledger append belongs to the site that performs the quarantine — this
|
|
635
|
+
// is the one quarantine `consumeArraySubmission` does not do — so a refusal
|
|
636
|
+
// is recorded exactly once no matter which path refused it. With no
|
|
637
|
+
// assessment on disk there is no `rejected_submissions` note to write, and
|
|
638
|
+
// this event is the only durable record that the submission was received.
|
|
639
|
+
await recordLaneOutcome(params.artifactsDir, lane, {
|
|
640
|
+
kind: "rejected",
|
|
641
|
+
issueCode: "submission_rejected",
|
|
642
|
+
message: `${pass} pass: ${reason}`,
|
|
643
|
+
});
|
|
644
|
+
await recordRejectedDesignReviewSubmission(params.artifactsDir, existing, pass, {
|
|
645
|
+
status: "quarantined",
|
|
646
|
+
quarantinePath,
|
|
647
|
+
lane,
|
|
648
|
+
reason,
|
|
649
|
+
});
|
|
650
|
+
};
|
|
651
|
+
// Legacy: consume the old combined findings submission. Tolerant-unwrap or
|
|
652
|
+
// quarantine (never a bare unconditional delete) — see the block comment above.
|
|
653
|
+
const legacyResult = await consumeArraySubmission(params.artifactsDir, GATE_LANES.design_review_legacy);
|
|
566
654
|
if (legacyResult.status === "quarantined") {
|
|
567
655
|
await recordRejectedDesignReviewSubmission(params.artifactsDir, existing, "legacy", legacyResult);
|
|
568
656
|
return { action: "continue" };
|
|
@@ -573,15 +661,19 @@ export async function handleDesignReviewBranch(params, bundle, state) {
|
|
|
573
661
|
existing.reviewed = true;
|
|
574
662
|
existing.rejected_submissions = (existing.rejected_submissions ?? []).filter((r) => r.pass !== "legacy");
|
|
575
663
|
await writeJsonFile(join(params.artifactsDir, "design_assessment.json"), existing);
|
|
664
|
+
await unlink(legacyResult.path).catch(() => { });
|
|
665
|
+
await recordLaneOutcome(params.artifactsDir, GATE_LANES.design_review_legacy, {
|
|
666
|
+
kind: "accepted",
|
|
667
|
+
});
|
|
576
668
|
return { action: "continue" };
|
|
577
669
|
}
|
|
578
|
-
|
|
670
|
+
await holdWithoutTarget("legacy", GATE_LANES.design_review_legacy, legacyResult.path);
|
|
579
671
|
return { action: "continue" };
|
|
580
672
|
}
|
|
581
673
|
// absent: fall through to the contract/conceptual check.
|
|
582
674
|
// New: consume contract-findings and/or conceptual-findings independently.
|
|
583
|
-
const contractResult = await
|
|
584
|
-
const conceptualResult = await
|
|
675
|
+
const contractResult = await consumeArraySubmission(params.artifactsDir, GATE_LANES.design_review_contract);
|
|
676
|
+
const conceptualResult = await consumeArraySubmission(params.artifactsDir, GATE_LANES.design_review_conceptual);
|
|
585
677
|
let consumed = false;
|
|
586
678
|
if (contractResult.status === "quarantined") {
|
|
587
679
|
await recordRejectedDesignReviewSubmission(params.artifactsDir, existing, "contract", contractResult);
|
|
@@ -592,6 +684,9 @@ export async function handleDesignReviewBranch(params, bundle, state) {
|
|
|
592
684
|
existing.rejected_submissions = (existing.rejected_submissions ?? []).filter((r) => r.pass !== "contract");
|
|
593
685
|
consumed = true;
|
|
594
686
|
}
|
|
687
|
+
else if (contractResult.status === "ok") {
|
|
688
|
+
await holdWithoutTarget("contract", GATE_LANES.design_review_contract, contractResult.path);
|
|
689
|
+
}
|
|
595
690
|
if (conceptualResult.status === "quarantined") {
|
|
596
691
|
await recordRejectedDesignReviewSubmission(params.artifactsDir, existing, "conceptual", conceptualResult);
|
|
597
692
|
}
|
|
@@ -601,6 +696,9 @@ export async function handleDesignReviewBranch(params, bundle, state) {
|
|
|
601
696
|
existing.rejected_submissions = (existing.rejected_submissions ?? []).filter((r) => r.pass !== "conceptual");
|
|
602
697
|
consumed = true;
|
|
603
698
|
}
|
|
699
|
+
else if (conceptualResult.status === "ok") {
|
|
700
|
+
await holdWithoutTarget("conceptual", GATE_LANES.design_review_conceptual, conceptualResult.path);
|
|
701
|
+
}
|
|
604
702
|
if (consumed && existing) {
|
|
605
703
|
await writeJsonFile(join(params.artifactsDir, "design_assessment.json"), existing);
|
|
606
704
|
// Snapshot each just-completed pass (B2 parity port): record the verdict +
|
|
@@ -611,9 +709,19 @@ export async function handleDesignReviewBranch(params, bundle, state) {
|
|
|
611
709
|
const reviewedAt = new Date().toISOString();
|
|
612
710
|
if (contractResult.status === "ok") {
|
|
613
711
|
await captureDesignReviewSnapshot(params.artifactsDir, "contract", existing.contract_findings ?? [], bundle, reviewedAt);
|
|
712
|
+
// Unlink only now: the findings are persisted and snapshotted, so the
|
|
713
|
+
// submission's content survives its own deletion.
|
|
714
|
+
await unlink(contractResult.path).catch(() => { });
|
|
715
|
+
await recordLaneOutcome(params.artifactsDir, GATE_LANES.design_review_contract, {
|
|
716
|
+
kind: "accepted",
|
|
717
|
+
});
|
|
614
718
|
}
|
|
615
719
|
if (conceptualResult.status === "ok") {
|
|
616
720
|
await captureDesignReviewSnapshot(params.artifactsDir, "conceptual", existing.conceptual_findings ?? [], bundle, reviewedAt);
|
|
721
|
+
await unlink(conceptualResult.path).catch(() => { });
|
|
722
|
+
await recordLaneOutcome(params.artifactsDir, GATE_LANES.design_review_conceptual, {
|
|
723
|
+
kind: "accepted",
|
|
724
|
+
});
|
|
617
725
|
}
|
|
618
726
|
return { action: "continue" };
|
|
619
727
|
}
|
|
@@ -636,28 +744,29 @@ export async function handleDesignReviewBranch(params, bundle, state) {
|
|
|
636
744
|
return { action: "continue" };
|
|
637
745
|
}
|
|
638
746
|
/**
|
|
639
|
-
* Drive one "poll
|
|
747
|
+
* Drive one "poll the lane → apply+continue, else omit-or-return" gate — the
|
|
640
748
|
* shape common to synthesis_narrative, charter_extraction,
|
|
641
749
|
* charter_clarification, and systemic_challenge. See the section comment
|
|
642
750
|
* above for the two gates that deviate and are not run through this engine.
|
|
643
751
|
*/
|
|
644
752
|
async function runOmittableGate(descriptor, params, bundle, state) {
|
|
645
|
-
const incoming = await
|
|
753
|
+
const incoming = await tryConsumeSubmission(params.artifactsDir, descriptor.lane);
|
|
646
754
|
if (incoming.status === "malformed") {
|
|
647
755
|
// Not-JSON submission: same quarantine-loudly lifecycle as a mis-shaped one.
|
|
648
|
-
await
|
|
756
|
+
await quarantineMisshapedSubmission(params.artifactsDir, incoming.path, descriptor.lane, incoming.reason);
|
|
649
757
|
}
|
|
650
758
|
else if (incoming.status === "ok") {
|
|
651
759
|
const parsed = descriptor.schema.safeParse(incoming.value);
|
|
652
760
|
if (parsed.success) {
|
|
653
761
|
await descriptor.apply(parsed.data, incoming.path, params);
|
|
654
762
|
await unlink(incoming.path).catch(() => { });
|
|
763
|
+
await recordLaneOutcome(params.artifactsDir, descriptor.lane, { kind: "accepted" });
|
|
655
764
|
return { action: "continue" };
|
|
656
765
|
}
|
|
657
766
|
// Mis-shaped submission: quarantine loudly and fall through to
|
|
658
767
|
// shouldOmit/return — never hand it to the executor to crash on or silently
|
|
659
768
|
// treat as an empty "reviewed, found nothing" result.
|
|
660
|
-
await
|
|
769
|
+
await quarantineMisshapedSubmission(params.artifactsDir, incoming.path, descriptor.lane, parsed.error);
|
|
661
770
|
}
|
|
662
771
|
if (descriptor.shouldOmit(bundle)) {
|
|
663
772
|
return { action: "run_omit" };
|
|
@@ -665,9 +774,9 @@ async function runOmittableGate(descriptor, params, bundle, state) {
|
|
|
665
774
|
return { action: "return", result: { kind: descriptor.kind, state, bundle } };
|
|
666
775
|
}
|
|
667
776
|
/**
|
|
668
|
-
* Handle the `synthesis_narrative_executor`
|
|
777
|
+
* Handle the `synthesis_narrative_executor` submission-polling block.
|
|
669
778
|
* Returns:
|
|
670
|
-
* - `continue` →
|
|
779
|
+
* - `continue` → a narrative submission was consumed + applied (progress
|
|
671
780
|
* made); re-scan on the reloaded bundle.
|
|
672
781
|
* - `return` → a host turn is still needed (narrative enabled, none supplied
|
|
673
782
|
* yet); emit the synthesis_narrative step.
|
|
@@ -679,8 +788,8 @@ async function runOmittableGate(descriptor, params, bundle, state) {
|
|
|
679
788
|
export async function handleSynthesisNarrativeBranch(params, bundle, state) {
|
|
680
789
|
return runOmittableGate({
|
|
681
790
|
kind: "synthesis_narrative",
|
|
682
|
-
|
|
683
|
-
schema:
|
|
791
|
+
lane: GATE_LANES.synthesis_narrative,
|
|
792
|
+
schema: LANE_SUBMISSION_SCHEMAS[GATE_LANES.synthesis_narrative],
|
|
684
793
|
apply: async (_value, path, p) => {
|
|
685
794
|
await runAuditStep({
|
|
686
795
|
root: p.root,
|
|
@@ -705,14 +814,14 @@ export async function handleSynthesisNarrativeBranch(params, bundle, state) {
|
|
|
705
814
|
* - `return` → a prose-only delta awaits the host judge; emit the step.
|
|
706
815
|
*/
|
|
707
816
|
export async function handleIntentEquivalenceBranch(params, bundle, state) {
|
|
708
|
-
const
|
|
709
|
-
const incoming = await
|
|
817
|
+
const lane = GATE_LANES.intent_equivalence;
|
|
818
|
+
const incoming = await tryConsumeSubmission(params.artifactsDir, lane);
|
|
710
819
|
if (incoming.status === "malformed") {
|
|
711
|
-
await
|
|
820
|
+
await quarantineMisshapedSubmission(params.artifactsDir, incoming.path, lane, incoming.reason);
|
|
712
821
|
// Fall through: no valid submission — re-emit or deterministically resolve.
|
|
713
822
|
}
|
|
714
823
|
else if (incoming.status === "ok") {
|
|
715
|
-
const parsed =
|
|
824
|
+
const parsed = LANE_SUBMISSION_SCHEMAS[lane].safeParse(incoming.value);
|
|
716
825
|
if (parsed.success) {
|
|
717
826
|
await runAuditStep({
|
|
718
827
|
root: params.root,
|
|
@@ -721,9 +830,10 @@ export async function handleIntentEquivalenceBranch(params, bundle, state) {
|
|
|
721
830
|
intentEquivalenceVerdictPath: incoming.path,
|
|
722
831
|
});
|
|
723
832
|
await unlink(incoming.path).catch(() => { });
|
|
833
|
+
await recordLaneOutcome(params.artifactsDir, lane, { kind: "accepted" });
|
|
724
834
|
return { action: "continue" };
|
|
725
835
|
}
|
|
726
|
-
await
|
|
836
|
+
await quarantineMisshapedSubmission(params.artifactsDir, incoming.path, lane, parsed.error);
|
|
727
837
|
// Fall through: no valid submission — re-emit or deterministically resolve.
|
|
728
838
|
}
|
|
729
839
|
if (deriveIntentEquivalenceStatus(bundle).kind !== "prose_judgment_pending") {
|
|
@@ -732,7 +842,7 @@ export async function handleIntentEquivalenceBranch(params, bundle, state) {
|
|
|
732
842
|
return { action: "return", result: { kind: "intent_equivalence", state, bundle } };
|
|
733
843
|
}
|
|
734
844
|
/**
|
|
735
|
-
* Handle the `critical_flow_fallback_executor`
|
|
845
|
+
* Handle the `critical_flow_fallback_executor` submission-polling block.
|
|
736
846
|
* The obligation is only ever selected when the deterministic flow inference
|
|
737
847
|
* marked itself below the confidence bar (`critical_flows.fallback_required`),
|
|
738
848
|
* so — unlike the synthesis-narrative / charter gates — there is NO autonomous
|
|
@@ -746,8 +856,8 @@ export async function handleIntentEquivalenceBranch(params, bundle, state) {
|
|
|
746
856
|
export async function handleCriticalFlowFallbackBranch(params, bundle, state) {
|
|
747
857
|
return runOmittableGate({
|
|
748
858
|
kind: "critical_flow_fallback",
|
|
749
|
-
|
|
750
|
-
schema:
|
|
859
|
+
lane: GATE_LANES.critical_flow_fallback,
|
|
860
|
+
schema: LANE_SUBMISSION_SCHEMAS[GATE_LANES.critical_flow_fallback],
|
|
751
861
|
apply: async (_value, path, p) => {
|
|
752
862
|
await runAuditStep({
|
|
753
863
|
root: p.root,
|
|
@@ -762,9 +872,9 @@ export async function handleCriticalFlowFallbackBranch(params, bundle, state) {
|
|
|
762
872
|
}, params, bundle, state);
|
|
763
873
|
}
|
|
764
874
|
/**
|
|
765
|
-
* Handle the `charter_extraction_executor`
|
|
875
|
+
* Handle the `charter_extraction_executor` submission-polling block
|
|
766
876
|
* (Phase C). Mirrors the synthesis-narrative branch:
|
|
767
|
-
* -
|
|
877
|
+
* - every per-kind lane present and valid → assemble+gate the merge via the
|
|
768
878
|
* preferred executor (ingest), then `continue`;
|
|
769
879
|
* - otherwise a `shallow` ceiling → `run_omit` (the deterministic executor
|
|
770
880
|
* writes an empty `status:omitted` register — the conversation-first default,
|
|
@@ -793,42 +903,22 @@ export async function handleCharterExtractionBranch(params, bundle, state) {
|
|
|
793
903
|
const laneValues = new Map();
|
|
794
904
|
let quarantinedAny = false;
|
|
795
905
|
for (const kind of kinds) {
|
|
796
|
-
const
|
|
797
|
-
const incoming = await
|
|
906
|
+
const lane = charterExtractionLane(kind);
|
|
907
|
+
const incoming = await tryConsumeSubmission(params.artifactsDir, lane);
|
|
798
908
|
if (incoming.status === "absent")
|
|
799
909
|
continue;
|
|
800
910
|
if (incoming.status === "malformed") {
|
|
801
911
|
quarantinedAny = true;
|
|
802
|
-
await
|
|
912
|
+
await quarantineMisshapedSubmission(params.artifactsDir, incoming.path, lane, incoming.reason);
|
|
803
913
|
continue;
|
|
804
914
|
}
|
|
805
|
-
const
|
|
806
|
-
submission.nodes.forEach((node, ni) => {
|
|
807
|
-
if (node.kind !== kind) {
|
|
808
|
-
ctx.addIssue({
|
|
809
|
-
code: "custom",
|
|
810
|
-
path: ["nodes", ni, "kind"],
|
|
811
|
-
message: `lane '${kind}' may only carry kind '${kind}', got '${node.kind}'`,
|
|
812
|
-
});
|
|
813
|
-
}
|
|
814
|
-
const unknown = node.files.filter((f) => !universe.has(f));
|
|
815
|
-
if (unknown.length > 0) {
|
|
816
|
-
ctx.addIssue({
|
|
817
|
-
code: "custom",
|
|
818
|
-
path: ["nodes", ni, "files"],
|
|
819
|
-
message: `teleology node cites file(s) outside the repo: ${unknown.sort().join(", ")} — ` +
|
|
820
|
-
"scopes must be repo-relative paths exactly as the evidence packet names them",
|
|
821
|
-
});
|
|
822
|
-
}
|
|
823
|
-
});
|
|
824
|
-
});
|
|
825
|
-
const parsed = laneSchema.safeParse(incoming.value);
|
|
915
|
+
const parsed = charterLaneSchema(kind, universe).safeParse(incoming.value);
|
|
826
916
|
if (parsed.success) {
|
|
827
917
|
laneValues.set(kind, { value: parsed.data, path: incoming.path });
|
|
828
918
|
}
|
|
829
919
|
else {
|
|
830
920
|
quarantinedAny = true;
|
|
831
|
-
await
|
|
921
|
+
await quarantineMisshapedSubmission(params.artifactsDir, incoming.path, lane, parsed.error);
|
|
832
922
|
}
|
|
833
923
|
}
|
|
834
924
|
if (!quarantinedAny && laneValues.size === kinds.length) {
|
|
@@ -842,7 +932,10 @@ export async function handleCharterExtractionBranch(params, bundle, state) {
|
|
|
842
932
|
const merged = {
|
|
843
933
|
nodes: kinds.flatMap((kind) => laneValues.get(kind).value.nodes),
|
|
844
934
|
};
|
|
845
|
-
|
|
935
|
+
// The merged submission is TOOL-written, so it lives with the other lane
|
|
936
|
+
// assets rather than under `submissions/` (which holds only what a host
|
|
937
|
+
// wrote). It is handed to the executor by path and deleted after ingest.
|
|
938
|
+
const mergedPath = join(laneAssetsDir(params.artifactsDir), CHARTER_EXTRACTION_MERGED_FILENAME);
|
|
846
939
|
await writeJsonFile(mergedPath, merged);
|
|
847
940
|
await runAuditStep({
|
|
848
941
|
root: params.root,
|
|
@@ -851,13 +944,16 @@ export async function handleCharterExtractionBranch(params, bundle, state) {
|
|
|
851
944
|
charterSubmissionPath: mergedPath,
|
|
852
945
|
});
|
|
853
946
|
await unlink(mergedPath).catch(() => { });
|
|
854
|
-
for (const lane of laneValues.
|
|
947
|
+
for (const [kind, lane] of laneValues.entries()) {
|
|
855
948
|
await unlink(lane.path).catch(() => { });
|
|
949
|
+
await recordLaneOutcome(params.artifactsDir, charterExtractionLane(kind), {
|
|
950
|
+
kind: "accepted",
|
|
951
|
+
});
|
|
856
952
|
}
|
|
857
953
|
// Evidence packets are consumed inputs like the lane submissions — a stale
|
|
858
954
|
// packet left behind would feed a later re-extraction yesterday's evidence.
|
|
859
955
|
for (const kind of kinds) {
|
|
860
|
-
await unlink(join(params.artifactsDir,
|
|
956
|
+
await unlink(join(laneAssetsDir(params.artifactsDir), charterExtractionPacketFilename(kind))).catch(() => { });
|
|
861
957
|
}
|
|
862
958
|
return { action: "continue" };
|
|
863
959
|
}
|
|
@@ -866,9 +962,9 @@ export async function handleCharterExtractionBranch(params, bundle, state) {
|
|
|
866
962
|
return { action: "return", result: { kind: "charter_extraction", state, bundle } };
|
|
867
963
|
}
|
|
868
964
|
/**
|
|
869
|
-
* Handle the `charter_delta_executor`
|
|
965
|
+
* Handle the `charter_delta_executor` submission-polling block (Phase C.2 —
|
|
870
966
|
* the INDEPENDENT delta-miner). Mirrors the charter-extraction branch:
|
|
871
|
-
* - a pending `
|
|
967
|
+
* - a pending `charter_delta` lane submission → route+gate it via the preferred
|
|
872
968
|
* executor (ingest), then `continue`;
|
|
873
969
|
* - otherwise, when the register is NOT `deltas_pending` (extraction omitted, or
|
|
874
970
|
* found no subsystems to mine) → `run_omit` (the deterministic executor settles
|
|
@@ -879,8 +975,8 @@ export async function handleCharterExtractionBranch(params, bundle, state) {
|
|
|
879
975
|
export async function handleCharterDeltaBranch(params, bundle, state) {
|
|
880
976
|
return runOmittableGate({
|
|
881
977
|
kind: "charter_delta",
|
|
882
|
-
|
|
883
|
-
schema:
|
|
978
|
+
lane: GATE_LANES.charter_delta,
|
|
979
|
+
schema: LANE_SUBMISSION_SCHEMAS[GATE_LANES.charter_delta],
|
|
884
980
|
apply: async (_value, path, p) => {
|
|
885
981
|
await runAuditStep({
|
|
886
982
|
root: p.root,
|
|
@@ -899,7 +995,7 @@ export async function handleCharterDeltaBranch(params, bundle, state) {
|
|
|
899
995
|
* loop). Mirrors the charter-extraction branch, but the loop is DETERMINISTIC — the
|
|
900
996
|
* executor assembles asked/banked from the Phase-C `charter_register` deltas, so the
|
|
901
997
|
* host turn only surfaces the VOI-ranked interactive queue for relay:
|
|
902
|
-
* - a pending `
|
|
998
|
+
* - a pending `charter_clarification` lane submission (host answers) → assemble via
|
|
903
999
|
* the deterministic runner, then `continue`;
|
|
904
1000
|
* - a `shallow` ceiling OR zero attention → `run_omit` (the runner writes the
|
|
905
1001
|
* register autonomously — every question banks as a finding, no host turn);
|
|
@@ -911,8 +1007,8 @@ export async function handleCharterDeltaBranch(params, bundle, state) {
|
|
|
911
1007
|
export async function handleCharterClarificationBranch(params, bundle, state) {
|
|
912
1008
|
return runOmittableGate({
|
|
913
1009
|
kind: "charter_clarification",
|
|
914
|
-
|
|
915
|
-
schema:
|
|
1010
|
+
lane: GATE_LANES.charter_clarification,
|
|
1011
|
+
schema: LANE_SUBMISSION_SCHEMAS[GATE_LANES.charter_clarification],
|
|
916
1012
|
apply: async (_value, path, p) => {
|
|
917
1013
|
await runAuditStep({
|
|
918
1014
|
root: p.root,
|
|
@@ -945,7 +1041,7 @@ export async function handleCharterClarificationBranch(params, bundle, state) {
|
|
|
945
1041
|
/**
|
|
946
1042
|
* Handle the `systemic_challenge_executor` obligation (Phase E — the second-order
|
|
947
1043
|
* adversary loop-until-dry pass). Mirrors the charter-clarification branch:
|
|
948
|
-
* - a pending `
|
|
1044
|
+
* - a pending `systemic_challenge` lane submission (an adversary round's findings) →
|
|
949
1045
|
* fold it via the deterministic runner, then `continue`;
|
|
950
1046
|
* - a `shallow` ceiling → `run_omit` (the runner writes an omitted register
|
|
951
1047
|
* autonomously, no host turn);
|
|
@@ -959,8 +1055,8 @@ export async function handleCharterClarificationBranch(params, bundle, state) {
|
|
|
959
1055
|
export async function handleSystemicChallengeBranch(params, bundle, state) {
|
|
960
1056
|
return runOmittableGate({
|
|
961
1057
|
kind: "systemic_challenge",
|
|
962
|
-
|
|
963
|
-
schema:
|
|
1058
|
+
lane: GATE_LANES.systemic_challenge,
|
|
1059
|
+
schema: LANE_SUBMISSION_SCHEMAS[GATE_LANES.systemic_challenge],
|
|
964
1060
|
apply: async (_value, path, p) => {
|
|
965
1061
|
await runAuditStep({
|
|
966
1062
|
root: p.root,
|
|
@@ -987,49 +1083,72 @@ export async function handleSystemicChallengeBranch(params, bundle, state) {
|
|
|
987
1083
|
},
|
|
988
1084
|
}, params, bundle, state);
|
|
989
1085
|
}
|
|
1086
|
+
/**
|
|
1087
|
+
* The single enumeration of every gate that can be owed a host submission, and
|
|
1088
|
+
* of every lane within it.
|
|
1089
|
+
*
|
|
1090
|
+
* It under-counted before P25 in three ways, each of which meant the "complete"
|
|
1091
|
+
* registry could not be used to derive what a run expects: the analyzer-consent
|
|
1092
|
+
* gate had no entry at all, the conceptual perspective lanes were minted
|
|
1093
|
+
* privately by `conceptualDispatch`, and the charter lane set was hard-coded
|
|
1094
|
+
* three deep instead of derived from `charterExtractionKindsForCeiling`. All
|
|
1095
|
+
* three are fixed here; tool-WRITTEN inputs (lane prompts, evidence packets,
|
|
1096
|
+
* the merged charter submission) are deliberately absent — they are not
|
|
1097
|
+
* submissions, so nothing is ever owed on them.
|
|
1098
|
+
*/
|
|
990
1099
|
export const HOST_GATE_DESCRIPTORS = {
|
|
1100
|
+
analyzer_consent: {
|
|
1101
|
+
driven: "custom",
|
|
1102
|
+
lanes: [GATE_LANES.analyzer_consent],
|
|
1103
|
+
},
|
|
991
1104
|
graph_enrichment: {
|
|
992
1105
|
driven: "custom",
|
|
993
|
-
|
|
1106
|
+
lanes: [GATE_LANES.analyzer_decisions, GATE_LANES.edge_reasoning],
|
|
994
1107
|
},
|
|
995
1108
|
design_review: {
|
|
996
1109
|
driven: "custom",
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1110
|
+
// The deep conceptual pass's independent JUDGE produces the conceptual
|
|
1111
|
+
// submission, so it is the `design_review_conceptual` lane rather than one
|
|
1112
|
+
// of its own. The pass's N perspective lanes are deliberately ABSENT: the
|
|
1113
|
+
// tool never reads a perspective's findings (the judge does), so it is owed
|
|
1114
|
+
// nothing on them — listing them here would state an expectation the run
|
|
1115
|
+
// could never satisfy and could never drop.
|
|
1116
|
+
lanes: [
|
|
1117
|
+
GATE_LANES.design_review_legacy,
|
|
1118
|
+
GATE_LANES.design_review_contract,
|
|
1119
|
+
GATE_LANES.design_review_conceptual,
|
|
1001
1120
|
],
|
|
1002
1121
|
},
|
|
1003
1122
|
critical_flow_fallback: {
|
|
1004
1123
|
driven: "generic",
|
|
1005
|
-
|
|
1124
|
+
lanes: [GATE_LANES.critical_flow_fallback],
|
|
1006
1125
|
},
|
|
1007
1126
|
// Custom: runOmittableGate minus the plain-consume — the verdict is
|
|
1008
1127
|
// schema-validated + quarantined-loudly in the handler itself.
|
|
1009
1128
|
intent_equivalence: {
|
|
1010
1129
|
driven: "custom",
|
|
1011
|
-
|
|
1130
|
+
lanes: [GATE_LANES.intent_equivalence],
|
|
1012
1131
|
},
|
|
1013
|
-
synthesis_narrative: { driven: "generic",
|
|
1132
|
+
synthesis_narrative: { driven: "generic", lanes: [GATE_LANES.synthesis_narrative] },
|
|
1014
1133
|
// Custom: the per-kind blind-lane gate (design resolution 2) — one submission
|
|
1015
|
-
//
|
|
1016
|
-
//
|
|
1017
|
-
//
|
|
1018
|
-
//
|
|
1019
|
-
// at deepest, never a lane (design resolution 4).
|
|
1134
|
+
// per charter kind, each validated (shape + kind purity + scope grounding) and
|
|
1135
|
+
// quarantined loudly per lane, tool-side merge only when every lane is present
|
|
1136
|
+
// + valid. The lane set is DERIVED from the same function the gate loop walks,
|
|
1137
|
+
// so the registry and the loop cannot disagree at any ceiling; `true` is
|
|
1138
|
+
// nominated by the delta miner at deepest, never a lane (design resolution 4).
|
|
1020
1139
|
charter_extraction: {
|
|
1021
1140
|
driven: "custom",
|
|
1022
|
-
|
|
1023
|
-
"
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
],
|
|
1141
|
+
lanes: charterExtractionKindsForCeiling({
|
|
1142
|
+
rung: "deepest",
|
|
1143
|
+
explicit_opt_in: true,
|
|
1144
|
+
}).map(charterExtractionLane),
|
|
1027
1145
|
},
|
|
1028
|
-
charter_delta: { driven: "generic",
|
|
1029
|
-
charter_clarification: { driven: "generic",
|
|
1030
|
-
systemic_challenge: { driven: "generic",
|
|
1146
|
+
charter_delta: { driven: "generic", lanes: [GATE_LANES.charter_delta] },
|
|
1147
|
+
charter_clarification: { driven: "generic", lanes: [GATE_LANES.charter_clarification] },
|
|
1148
|
+
systemic_challenge: { driven: "generic", lanes: [GATE_LANES.systemic_challenge] },
|
|
1031
1149
|
};
|
|
1032
1150
|
export const HOST_GATE_KINDS = [
|
|
1151
|
+
"analyzer_consent",
|
|
1033
1152
|
"graph_enrichment",
|
|
1034
1153
|
"critical_flow_fallback",
|
|
1035
1154
|
"intent_equivalence",
|
|
@@ -1336,7 +1455,7 @@ export function buildAuditObligations() {
|
|
|
1336
1455
|
},
|
|
1337
1456
|
},
|
|
1338
1457
|
{
|
|
1339
|
-
// Graph enrichment: poll the analyzer-decision / edge-reasoning
|
|
1458
|
+
// Graph enrichment: poll the analyzer-decision / edge-reasoning lane
|
|
1340
1459
|
// artifacts first (emit a host step when one is needed), otherwise run the
|
|
1341
1460
|
// deterministic enrichment executor.
|
|
1342
1461
|
id: "graph_enrichment_current",
|
|
@@ -1361,7 +1480,7 @@ export function buildAuditObligations() {
|
|
|
1361
1480
|
deterministic("docs_digest_current"),
|
|
1362
1481
|
{
|
|
1363
1482
|
// Confirm-intent host step: the host writes intent_checkpoint.json (read by
|
|
1364
|
-
// deriveAuditState on re-invocation), so there is no
|
|
1483
|
+
// deriveAuditState on re-invocation), so there is no submission to
|
|
1365
1484
|
// consume — emit the step directly.
|
|
1366
1485
|
id: "intent_checkpoint_current",
|
|
1367
1486
|
derive: deriveObligationState("intent_checkpoint_current"),
|
|
@@ -1392,7 +1511,7 @@ export function buildAuditObligations() {
|
|
|
1392
1511
|
},
|
|
1393
1512
|
},
|
|
1394
1513
|
{
|
|
1395
|
-
// Charter extraction (Phase C): poll the
|
|
1514
|
+
// Charter extraction (Phase C): poll the lane submissions (ingest+gate),
|
|
1396
1515
|
// omit at a shallow ceiling, or emit the host charter-extraction step at a
|
|
1397
1516
|
// deep+ ceiling. Mirrors the synthesis-narrative branch.
|
|
1398
1517
|
id: "charter_extraction_current",
|
|
@@ -1410,7 +1529,7 @@ export function buildAuditObligations() {
|
|
|
1410
1529
|
},
|
|
1411
1530
|
},
|
|
1412
1531
|
{
|
|
1413
|
-
// Charter delta-mining (Phase C.2): poll the
|
|
1532
|
+
// Charter delta-mining (Phase C.2): poll the delta lane submission
|
|
1414
1533
|
// (route+gate it), settle deterministically when the register is not
|
|
1415
1534
|
// deltas_pending (extraction omitted / no subsystems), or emit the host step
|
|
1416
1535
|
// for the INDEPENDENT delta-miner when a deltas_pending register has no
|
|
@@ -1430,21 +1549,21 @@ export function buildAuditObligations() {
|
|
|
1430
1549
|
},
|
|
1431
1550
|
},
|
|
1432
1551
|
{
|
|
1433
|
-
// Contract design-review pass: poll
|
|
1552
|
+
// Contract design-review pass: poll the contract/conceptual lanes;
|
|
1434
1553
|
// emit the dispatch step when a pass still needs to run.
|
|
1435
1554
|
id: "design_review_contract_completed",
|
|
1436
1555
|
derive: deriveObligationState("design_review_contract_completed"),
|
|
1437
1556
|
execute: (bundle, ctx) => runDesignReviewObligation(bundle, ctx),
|
|
1438
1557
|
},
|
|
1439
1558
|
{
|
|
1440
|
-
// Conceptual design-review pass: same
|
|
1559
|
+
// Conceptual design-review pass: same submission-poll handler (it resolves
|
|
1441
1560
|
// which pass remains).
|
|
1442
1561
|
id: "design_review_conceptual_completed",
|
|
1443
1562
|
derive: deriveObligationState("design_review_conceptual_completed"),
|
|
1444
1563
|
execute: (bundle, ctx) => runDesignReviewObligation(bundle, ctx),
|
|
1445
1564
|
},
|
|
1446
1565
|
{
|
|
1447
|
-
// Charter clarification (Phase D triangulation loop): poll
|
|
1566
|
+
// Charter clarification (Phase D triangulation loop): poll the answers lane
|
|
1448
1567
|
// (apply + re-split), assemble the loop deterministically at a shallow ceiling
|
|
1449
1568
|
// / zero attention (autonomous), or emit the host step relaying the VOI-ranked
|
|
1450
1569
|
// interactive queue at a deep+ ceiling with attention > 0. Non-drainable
|
|
@@ -1464,7 +1583,7 @@ export function buildAuditObligations() {
|
|
|
1464
1583
|
},
|
|
1465
1584
|
},
|
|
1466
1585
|
{
|
|
1467
|
-
// Systemic challenge (Phase E loop-until-dry): poll the
|
|
1586
|
+
// Systemic challenge (Phase E loop-until-dry): poll the adversary lane's
|
|
1468
1587
|
// round (fold it), omit at a shallow ceiling, or emit the second-order-adversary
|
|
1469
1588
|
// host step when the loop is open at a deep+ ceiling. Non-drainable
|
|
1470
1589
|
// (host_delegation), so the drain stops here.
|
|
@@ -1494,7 +1613,7 @@ export function buildAuditObligations() {
|
|
|
1494
1613
|
deterministic("runtime_validation_current"),
|
|
1495
1614
|
deterministic("synthesis_current"),
|
|
1496
1615
|
{
|
|
1497
|
-
// Synthesis narrative: poll the
|
|
1616
|
+
// Synthesis narrative: poll the narrative lane; emit the host step when
|
|
1498
1617
|
// narrative is enabled and not yet supplied, otherwise the deterministic
|
|
1499
1618
|
// omit runs (fold on).
|
|
1500
1619
|
id: "synthesis_narrative_current",
|
|
@@ -1511,7 +1630,7 @@ export function buildAuditObligations() {
|
|
|
1511
1630
|
// (A bare reload here would leave it actionable and spin the fold.)
|
|
1512
1631
|
return runDeterministicExecutor(bundle, ctx);
|
|
1513
1632
|
}
|
|
1514
|
-
// continue:
|
|
1633
|
+
// continue: a narrative submission was consumed + applied — re-scan.
|
|
1515
1634
|
return { kind: "transition", state: await loadArtifactBundle(ctx.params.artifactsDir) };
|
|
1516
1635
|
},
|
|
1517
1636
|
},
|
|
@@ -1547,14 +1666,19 @@ async function runHostDelegationObligation(bundle, ctx) {
|
|
|
1547
1666
|
// accepted ledger written before core ingestion is retried, while results
|
|
1548
1667
|
// already reflected in coverage are not replayed.
|
|
1549
1668
|
const currentRun = await loadCurrentActiveReviewRun(ctx.params.artifactsDir);
|
|
1669
|
+
let ingestIssues = [];
|
|
1550
1670
|
if (currentRun) {
|
|
1551
1671
|
let acceptedResults = [];
|
|
1672
|
+
let completedIds = [];
|
|
1552
1673
|
try {
|
|
1553
|
-
|
|
1674
|
+
const ingested = await ingestAuditHostResults({
|
|
1554
1675
|
root: ctx.params.root,
|
|
1555
1676
|
artifactsDir: ctx.params.artifactsDir,
|
|
1556
1677
|
runId: currentRun.run_id,
|
|
1557
|
-
})
|
|
1678
|
+
});
|
|
1679
|
+
acceptedResults = ingested.accepted_results;
|
|
1680
|
+
ingestIssues = ingested.issues;
|
|
1681
|
+
completedIds = ingested.completed_work_item_ids;
|
|
1558
1682
|
}
|
|
1559
1683
|
catch (error) {
|
|
1560
1684
|
// No handoff exists on the first visit, or prepare was interrupted before
|
|
@@ -1562,6 +1686,17 @@ async function runHostDelegationObligation(bundle, ctx) {
|
|
|
1562
1686
|
if (!isFileMissingError(error))
|
|
1563
1687
|
throw error;
|
|
1564
1688
|
}
|
|
1689
|
+
// The ingest CLASSIFIES every failed read — a submission that never
|
|
1690
|
+
// arrived, unreadable bytes (an EACCES or a directory at the bound path
|
|
1691
|
+
// reads as malformed WITH the OS detail in its message), a body that fails
|
|
1692
|
+
// the contract, a replayed result id. Those classifications used to die in
|
|
1693
|
+
// the caller that computed them; they now land on the ledger and, below,
|
|
1694
|
+
// in the re-emitted step the host actually reads. The completed set rides
|
|
1695
|
+
// along so a repaired item closes its own record.
|
|
1696
|
+
await recordHostResultOutcomes(ctx.params.artifactsDir, currentRun.run_id, {
|
|
1697
|
+
issues: ingestIssues,
|
|
1698
|
+
acceptedIds: completedIds,
|
|
1699
|
+
});
|
|
1565
1700
|
const pendingIds = new Set(buildPendingAuditTasks(bundle).map((task) => task.task_id));
|
|
1566
1701
|
const pendingAccepted = acceptedResults.filter((result) => pendingIds.has(result.task_id));
|
|
1567
1702
|
if (pendingAccepted.length > 0) {
|
|
@@ -1589,6 +1724,7 @@ async function runHostDelegationObligation(bundle, ctx) {
|
|
|
1589
1724
|
kind: "semantic_review",
|
|
1590
1725
|
selectedExecutor: decision.selected_executor,
|
|
1591
1726
|
...review,
|
|
1727
|
+
...(ingestIssues.length > 0 ? { ingestIssues } : {}),
|
|
1592
1728
|
},
|
|
1593
1729
|
};
|
|
1594
1730
|
}
|