@deksden-com/dd-flow-cli 0.9.0-beta.151 → 0.9.0-beta.155

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/build-info.json +8 -8
  3. package/dist/cli/command-inputs.js +33 -3
  4. package/dist/cli/command-metadata.js +73 -2
  5. package/dist/cli/delivery.js +108 -0
  6. package/dist/cli/diagnostic-invocation.js +23 -7
  7. package/dist/cli/diagnostic.js +14 -3
  8. package/dist/cli/help.js +117 -1
  9. package/dist/cli/hook-ingress.js +36 -49
  10. package/dist/cli/interactive-invocation.js +33 -0
  11. package/dist/cli/interactive.js +114 -0
  12. package/dist/cli/memory-fix-invocation.js +52 -0
  13. package/dist/cli/memory-fix.js +100 -0
  14. package/dist/cli/memory-flow-admission.js +7 -3
  15. package/dist/cli/memory-init.js +5 -4
  16. package/dist/cli/native-hook-ingress.js +5 -1
  17. package/dist/cli/native-hook-preparation.js +31 -4
  18. package/dist/cli/run-cli.js +273 -19
  19. package/dist/cli.js +17 -3
  20. package/dist/domain/delivery-flows.js +8 -0
  21. package/dist/domain/distill-flow.js +20 -0
  22. package/dist/domain/interactive-flow.js +1 -0
  23. package/dist/domain/memory-fix-flow.js +2 -0
  24. package/dist/domain/review-fix-flow.js +2 -0
  25. package/dist/domain/source-snapshot-flows.js +4 -0
  26. package/dist/harness-runtime/bin/dd-agy.mjs +18 -3
  27. package/dist/harness-runtime/bin/dd-grok.mjs +15 -5
  28. package/dist/harness-runtime/bin/dd-zcode.mjs +15 -7
  29. package/dist/harness-runtime/lib/agy-error-boundary.mjs +18 -10
  30. package/dist/harness-runtime/lib/agy-native-inspection.mjs +12 -3
  31. package/dist/harness-runtime/lib/agy-native-inventory.mjs +19 -0
  32. package/dist/harness-runtime/lib/agy-terminal-envelope.mjs +13 -6
  33. package/dist/harness-runtime/lib/agy-transcript-stream.mjs +54 -0
  34. package/dist/harness-runtime/lib/daemon-observation.d.mts +1 -0
  35. package/dist/harness-runtime/lib/daemon-observation.mjs +12 -4
  36. package/dist/harness-runtime/lib/daemon-operations.mjs +20 -5
  37. package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +1 -0
  38. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +371 -114
  39. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +43 -3
  40. package/dist/harness-runtime/lib/dd-codex.mjs +36 -4
  41. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +25 -11
  42. package/dist/harness-runtime/lib/dd-droid.mjs +124 -11
  43. package/dist/harness-runtime/lib/dd-grok-daemon.d.mts +1 -0
  44. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +137 -25
  45. package/dist/harness-runtime/lib/dd-grok.mjs +4 -2
  46. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +170 -25
  47. package/dist/harness-runtime/lib/dd-opencode.mjs +40 -19
  48. package/dist/harness-runtime/lib/dd-zcode-daemon.d.mts +1 -0
  49. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +115 -22
  50. package/dist/harness-runtime/lib/dd-zcode.mjs +75 -36
  51. package/dist/harness-runtime/lib/delegation-instructions.d.mts +1 -0
  52. package/dist/harness-runtime/lib/delegation-instructions.mjs +10 -3
  53. package/dist/harness-runtime/lib/delivery-command.mjs +84 -0
  54. package/dist/harness-runtime/lib/driver-recovery.mjs +0 -19
  55. package/dist/harness-runtime/lib/droid-observation.mjs +9 -5
  56. package/dist/harness-runtime/lib/lifecycle-contract.d.mts +1 -0
  57. package/dist/harness-runtime/lib/lifecycle-contract.mjs +2 -1
  58. package/dist/harness-runtime/lib/native-hook-command.d.mts +3 -2
  59. package/dist/harness-runtime/lib/native-hook-command.mjs +63 -8
  60. package/dist/harness-runtime/lib/native-streams.mjs +3 -10
  61. package/dist/harness-runtime/lib/opencode-plugin.mjs +36 -9
  62. package/dist/harness-runtime/lib/operation-errors.mjs +13 -0
  63. package/dist/harness-runtime/lib/operation-progress.mjs +6 -2
  64. package/dist/harness-runtime/lib/process-json.mjs +4 -5
  65. package/dist/harness-runtime/lib/tool-observations.mjs +20 -10
  66. package/dist/harness-runtime/lib/zcode-native-hook-ownership.mjs +109 -0
  67. package/dist/schemas/code-work-result.schema.json +2 -1
  68. package/dist/schemas/delivery-stage-result.schema.json +286 -0
  69. package/dist/schemas/distill-stage-result.schema.json +667 -0
  70. package/dist/schemas/distill-work-result.schema.json +428 -0
  71. package/dist/schemas/execution-bundle.schema.json +6 -2
  72. package/dist/schemas/interactive-stage-result.schema.json +47 -0
  73. package/dist/schemas/interactive-work-result.schema.json +63 -0
  74. package/dist/schemas/mb-fix-stage-result.schema.json +93 -0
  75. package/dist/schemas/project-flow-pack-manifest.schema.json +2 -2
  76. package/dist/schemas/review-fix-stage-result.schema.json +210 -0
  77. package/dist/schemas/run-control-receipt.schema.json +1 -0
  78. package/dist/services/assigned-lifecycle.js +382 -0
  79. package/dist/services/check-runner-evidence.js +77 -10
  80. package/dist/services/check-runner-proof.js +69 -1
  81. package/dist/services/cli-operation-classifier.js +6 -1
  82. package/dist/services/code-checks.js +56 -23
  83. package/dist/services/codex-hook-delivery.js +11 -2
  84. package/dist/services/controller-execution.js +4 -1
  85. package/dist/services/controller-fanout.js +9 -4
  86. package/dist/services/controller-memory-worker.js +6 -4
  87. package/dist/services/controller-result-authority.js +122 -0
  88. package/dist/services/dashboard.js +4 -2
  89. package/dist/services/delivery-access.js +123 -0
  90. package/dist/services/delivery-approval.js +68 -0
  91. package/dist/services/delivery-artifact.js +86 -0
  92. package/dist/services/delivery-checks.js +178 -0
  93. package/dist/services/delivery-execution.js +181 -0
  94. package/dist/services/delivery-intents.js +212 -0
  95. package/dist/services/delivery-operation.js +81 -0
  96. package/dist/services/delivery-policy.js +91 -0
  97. package/dist/services/delivery-procedures.js +43 -0
  98. package/dist/services/delivery-request.js +99 -0
  99. package/dist/services/delivery-result.js +144 -0
  100. package/dist/services/delivery-stage-commands.js +26 -0
  101. package/dist/services/delivery-stage-entry.js +56 -0
  102. package/dist/services/delivery-stage-handlers.js +135 -0
  103. package/dist/services/delivery-stage-results.js +94 -0
  104. package/dist/services/descriptor-coordinator-session.js +3 -1
  105. package/dist/services/descriptor-run-start.js +24 -4
  106. package/dist/services/descriptor-stage-coordinator.js +78 -10
  107. package/dist/services/descriptor-stage-settlement.js +59 -14
  108. package/dist/services/diagnostic-repair-intake.js +123 -0
  109. package/dist/services/diagnostic-repair-source-intake.js +102 -0
  110. package/dist/services/diagnostic-report.js +30 -0
  111. package/dist/services/diagnostic-run-bundle.js +48 -9
  112. package/dist/services/diagnostic-snapshot.js +2 -2
  113. package/dist/services/diagnostic-stage-handlers.js +3 -2
  114. package/dist/services/diagnostic-stage-results.js +26 -0
  115. package/dist/services/diagnostic-start.js +56 -6
  116. package/dist/services/diagnostic-work-collection.js +3 -3
  117. package/dist/services/distill-document-publication.js +280 -0
  118. package/dist/services/distill-promotion.js +197 -0
  119. package/dist/services/distill-publication-custody.js +162 -0
  120. package/dist/services/distill-stage-handlers.js +249 -0
  121. package/dist/services/distill-stage-results.js +105 -0
  122. package/dist/services/distill-user-inputs.js +4 -0
  123. package/dist/services/distill-work-batch.js +42 -0
  124. package/dist/services/distill-work-collection.js +74 -0
  125. package/dist/services/distill-work-registration.js +93 -0
  126. package/dist/services/distill-work-results.js +98 -0
  127. package/dist/services/eval-snapshots.js +17 -7
  128. package/dist/services/external-work-launch.js +8 -2
  129. package/dist/services/flow-catalog.js +24 -0
  130. package/dist/services/frozen-engine.js +19 -2
  131. package/dist/services/generic-execution.js +4 -3
  132. package/dist/services/harness-adapter.js +2 -14
  133. package/dist/services/hook-memory-routing.js +1 -1
  134. package/dist/services/hooks.js +31 -6
  135. package/dist/services/hybrid-contracts.js +6 -1
  136. package/dist/services/installed-flow-source.js +37 -2
  137. package/dist/services/interactive-attached-session.js +215 -0
  138. package/dist/services/interactive-baseline-custody.js +54 -0
  139. package/dist/services/interactive-before-versions.js +148 -0
  140. package/dist/services/interactive-bundle-data.js +85 -0
  141. package/dist/services/interactive-checks.js +179 -0
  142. package/dist/services/interactive-execution.js +33 -0
  143. package/dist/services/interactive-finish-integration.js +236 -0
  144. package/dist/services/interactive-git-baseline.js +230 -0
  145. package/dist/services/interactive-git-dispatch.js +278 -0
  146. package/dist/services/interactive-input-delivery.js +82 -0
  147. package/dist/services/interactive-mutation-scope.js +231 -0
  148. package/dist/services/interactive-request.js +287 -0
  149. package/dist/services/interactive-run-bundle.js +109 -0
  150. package/dist/services/interactive-stage-entry.js +41 -0
  151. package/dist/services/interactive-stage-handlers.js +289 -0
  152. package/dist/services/interactive-stage-observation.js +136 -0
  153. package/dist/services/interactive-stage-results.js +111 -0
  154. package/dist/services/interactive-user-inputs.js +142 -0
  155. package/dist/services/interactive-work-batch.js +43 -0
  156. package/dist/services/interactive-work-collection.js +119 -0
  157. package/dist/services/interactive-work-completion.js +180 -0
  158. package/dist/services/interactive-work-context.js +249 -0
  159. package/dist/services/interactive-work-history.js +112 -0
  160. package/dist/services/interactive-work-registration.js +124 -0
  161. package/dist/services/interactive-work-results.js +115 -0
  162. package/dist/services/interactive-work-start.js +155 -0
  163. package/dist/services/lifecycle-invocations.js +214 -24
  164. package/dist/services/managed-daemon-binding.js +52 -1
  165. package/dist/services/memory-compose-handler.js +2 -22
  166. package/dist/services/memory-finish-integration.js +58 -17
  167. package/dist/services/memory-fix-finish-integration.js +129 -0
  168. package/dist/services/memory-fix-git-dispatch.js +139 -0
  169. package/dist/services/memory-fix-intake-custody.js +11 -0
  170. package/dist/services/memory-fix-intake-settlement.js +127 -0
  171. package/dist/services/memory-fix-publication.js +196 -0
  172. package/dist/services/memory-fix-repair-admission.js +116 -0
  173. package/dist/services/memory-fix-request.js +141 -0
  174. package/dist/services/memory-fix-run-bundle.js +83 -0
  175. package/dist/services/memory-fix-scope.js +78 -0
  176. package/dist/services/memory-fix-sources.js +85 -0
  177. package/dist/services/memory-fix-stage-entry.js +24 -0
  178. package/dist/services/memory-fix-stage-handlers.js +282 -0
  179. package/dist/services/memory-fix-stage-results.js +106 -0
  180. package/dist/services/memory-fix-workspace.js +127 -0
  181. package/dist/services/memory-flow-user-inputs.js +88 -0
  182. package/dist/services/memory-init-contexts.js +55 -5
  183. package/dist/services/memory-init-controller-start.js +20 -4
  184. package/dist/services/memory-layer.js +7 -3
  185. package/dist/services/memory-native-review-copy.js +27 -3
  186. package/dist/services/memory-repair-document-delta.js +1 -0
  187. package/dist/services/project-flow-source.js +195 -0
  188. package/dist/services/project-git-integration.js +92 -0
  189. package/dist/services/repair-intents.js +2 -4
  190. package/dist/services/retained-native-hook.js +245 -0
  191. package/dist/services/review-fix-handoff.js +82 -0
  192. package/dist/services/review-fix-intake-custody.js +17 -0
  193. package/dist/services/review-fix-stage-entry.js +43 -0
  194. package/dist/services/review-fix-stage-handlers.js +176 -0
  195. package/dist/services/review-fix-stage-results.js +86 -0
  196. package/dist/services/run-control-worker.js +42 -1
  197. package/dist/services/run-control.js +40 -5
  198. package/dist/services/run-controller-adapter.js +37 -3
  199. package/dist/services/run-controller-process.js +12 -4
  200. package/dist/services/run-controller-recovery.js +46 -6
  201. package/dist/services/run-controller-state.js +3 -2
  202. package/dist/services/run-controller.js +209 -43
  203. package/dist/services/run-recovery.js +31 -2
  204. package/dist/services/run-timeline.js +59 -0
  205. package/dist/services/runs.js +18 -31
  206. package/dist/services/sessions.js +2 -1
  207. package/dist/services/stage-handlers.js +55 -3
  208. package/dist/services/stage-lifecycle.js +4 -3
  209. package/dist/services/stage-pause.js +26 -9
  210. package/dist/services/stage-report-renderer.js +2 -6
  211. package/dist/services/technical-notes.js +31 -0
  212. package/dist/services/verification-matrix.js +1 -1
  213. package/dist/services/version-status.js +1 -1
  214. package/dist/services/vnext-code-review.js +74 -5
  215. package/dist/services/vnext-code.js +9 -32
  216. package/dist/services/vnext-fanout.js +53 -8
  217. package/dist/services/vnext-merge.js +10 -5
  218. package/dist/services/vnext-plan-review.js +1 -1
  219. package/dist/services/vnext-plan.js +1 -1
  220. package/dist/services/vnext-protocolize.js +1 -1
  221. package/dist/services/vnext-specify.js +10 -5
  222. package/dist/services/work-registry.js +252 -66
  223. package/dist/services/worktrees.js +7 -4
  224. package/dist/storage/database.js +2 -1
  225. package/dist/verification-components.json +82 -0
  226. package/package.json +4 -3
  227. package/tools/verification-jobs.mjs +342 -0
@@ -5,8 +5,8 @@ import { parse } from "shell-quote";
5
5
  import { AppError } from "../shared/errors.js";
6
6
  import { validateObservationDuration } from "../harness-runtime/lib/observation-clock.mjs";
7
7
  import { isDeepStrictEqual } from "node:util";
8
- import { runnerFailureValid, validateRunnerProof, validatedRunnerChild, hasValidatedNativeProof, hasValidatedBrowserProof, hasUnsupportedObservation } from "./check-runner-proof.js";
9
- export { candidateAbsencesValid, runnerFailureValid, runnerEvidencePass, runnerProofArtifacts } from "./check-runner-proof.js";
8
+ import { runnerFailureValid, validateRunnerProof, validatedRunnerChild, hasValidatedNativeProof, hasValidatedBrowserProof, hasUnsupportedObservation, runnerLibraryInputs } from "./check-runner-proof.js";
9
+ export { candidateAbsencesValid, candidateLibraryInputsValid, runnerFailureValid, runnerEvidencePass, runnerProofArtifacts } from "./check-runner-proof.js";
10
10
  const absoluteFile = (value) => typeof value === "string" && value.length > 0 && value.length <= 4096 && !value.includes("\0") && path.isAbsolute(value);
11
11
  function candidateFile(root, file) {
12
12
  if (!root || !path.isAbsolute(file))
@@ -45,6 +45,28 @@ export function candidateFailureFiles(evidence, root) {
45
45
  }
46
46
  }))];
47
47
  }
48
+ export function candidateLibraryInputs(evidence, root) {
49
+ const inputs = new Map();
50
+ for (const failure of evidence?.failures ?? []) {
51
+ const child = validatedRunnerChild(failure);
52
+ const candidates = child ? candidateLibraryInputs({ ...child.evidence, failures: [child.failure] }, root)
53
+ : failure.repair_evidence?.rule === "candidate_library_query_error" ? runnerLibraryInputs(failure.repair_evidence) ?? [] : [];
54
+ try {
55
+ if (candidates.some(input => !candidateFile(root, input.file) || crypto.createHash("sha256").update(readBoundedProofFile(input.file, 2 * 1024 * 1024)).digest("hex") !== input.sha256))
56
+ continue;
57
+ }
58
+ catch {
59
+ continue;
60
+ }
61
+ for (const input of candidates)
62
+ inputs.set(input.file, input.sha256);
63
+ }
64
+ return [...inputs].map(([file, sha256]) => ({ file, sha256 }));
65
+ }
66
+ /** Current execution/repair boundary only; historical readers retain captured proof. */
67
+ export function currentCandidateLibraryInputsValid(receipt, workspaceRoot) {
68
+ return isDeepStrictEqual(receipt.candidate_library_inputs ?? [], candidateLibraryInputs(receipt.runner_evidence, workspaceRoot));
69
+ }
48
70
  function absentCandidate(root, file) {
49
71
  if (!root || !absoluteFile(file))
50
72
  return false;
@@ -104,10 +126,10 @@ export function candidateFailureAbsences(evidence, root) {
104
126
  }
105
127
  });
106
128
  }
107
- function candidateFailure(failure, evidence, root, capturedFiles, capturedAbsences) {
129
+ function candidateFailure(failure, evidence, root, capturedFiles, capturedAbsences, capturedLibraryInputs) {
108
130
  const child = validatedRunnerChild(failure);
109
131
  if (child)
110
- return candidateFailure(child.failure, child.evidence, root, capturedFiles, capturedAbsences);
132
+ return candidateFailure(child.failure, child.evidence, root, capturedFiles, capturedAbsences, capturedLibraryInputs);
111
133
  const owned = (file) => {
112
134
  if (capturedFiles !== undefined)
113
135
  return capturedFiles.includes(file);
@@ -126,6 +148,11 @@ function candidateFailure(failure, evidence, root, capturedFiles, capturedAbsenc
126
148
  if (failure.category === "assertion")
127
149
  return failure.origin === undefined || failure.origin === "candidate" && failure.file !== undefined && owned(failure.file);
128
150
  const proof = failure.repair_evidence;
151
+ if (proof?.rule === "candidate_library_query_error") {
152
+ const required = runnerLibraryInputs(proof), captured = capturedFiles !== undefined ? capturedLibraryInputs : root ? candidateLibraryInputs({ ...evidence, failures: [failure] }, root) : undefined;
153
+ return hasValidatedNativeProof(proof) && failure.category === "unclassified" && failure.origin === "candidate" && failure.phase === "test" && failure.native_code === proof.exception_type
154
+ && failure.test_id === proof.test_id && failure.file === proof.file && owned(proof.file) && Boolean(required?.length && captured && required.every(input => captured.some(item => item.file === input.file && item.sha256 === input.sha256)));
155
+ }
129
156
  if (proof?.rule === "candidate_test_runtime_error")
130
157
  return hasValidatedNativeProof(proof) && failure.category === "unclassified" && failure.origin === "candidate"
131
158
  && failure.phase === proof.phase && failure.native_code === proof.exception_type && failure.file === proof.file && owned(proof.file);
@@ -246,6 +273,51 @@ export function readRunnerEvidenceDetailed(root, contract, binding) {
246
273
  });
247
274
  }
248
275
  export function readRunnerEvidence(root, contract, binding) { return readRunnerEvidenceDetailed(root, contract, binding).evidence; }
276
+ /** Semantic attribution is diagnostic information, never permission to investigate a failed check. */
277
+ export const checkDiagnosisPolicy = "settled-failure@1";
278
+ export function checkDiagnosisDisposition(receipt) {
279
+ if (receipt.status === "running"
280
+ || receipt.execution_failures?.some(failure => ["cleanup_unconfirmed", "engine_outcome_unbound"].includes(failure.code)))
281
+ return "settlement_required";
282
+ if (!["passed", "failed", "aborted"].includes(receipt.status))
283
+ return "invalid_binding";
284
+ // Caller supplies the SQL-owned receipt, never a proposed runner report.
285
+ // Missing, malformed or foreign optional reports are diagnostic failures;
286
+ // they cannot change receipt ownership or grant a successful check.
287
+ if (receipt.status === "passed")
288
+ return "passed";
289
+ // New launchers record physical settlement even for spawn errors/signals.
290
+ // Historical aborted receipts with no such proof must first reconcile.
291
+ if (!receipt.execution_failures?.some(failure => failure.code === "check_execution_settled") && !Number.isSafeInteger(receipt.exit_code))
292
+ return "settlement_required";
293
+ return "diagnosis_required";
294
+ }
295
+ export function assertCheckDiagnosisEligible(receipts, _workspaceRoot) {
296
+ const dispositions = receipts.map(receipt => ({ check_receipt_id: receipt.id, disposition: checkDiagnosisDisposition(receipt) }));
297
+ if (!receipts.length || dispositions.some(item => item.disposition !== "diagnosis_required"))
298
+ throw new AppError("check_gate_blocked", "Check diagnosis requires owned, physically settled failed executions", 2, {
299
+ failures: receipts, dispositions, outcome: "blocked", recoverable: false,
300
+ instruction: "Reconcile any running or unknown check process before changing its workspace. Do not treat a foreign receipt or an agent report as proof of completion."
301
+ });
302
+ }
303
+ /** Human-readable retained facts; tool output remains data, never instructions. */
304
+ export function checkDiagnosisSummary(receipt, workspaceRoot) {
305
+ const available = (file) => {
306
+ try {
307
+ fs.accessSync(file, fs.constants.R_OK);
308
+ return fs.statSync(file).isFile() ? file : `${file} (not a regular file)`;
309
+ }
310
+ catch {
311
+ return `${file} (unavailable; investigate the missing diagnostic, do not invent its content)`;
312
+ }
313
+ };
314
+ return [`Check: ${receipt.id}`, `Command: ${receipt.command}`, `cwd: ${workspaceRoot}`,
315
+ `Outcome: ${receipt.status}; exit: ${receipt.exit_code ?? "unavailable"}; epoch: ${receipt.verification_epoch}`,
316
+ `Receipt: ${available(receipt.receipt_path)}`, `stdout: ${available(receipt.stdout_path)}`, `stderr: ${available(receipt.stderr_path)}`,
317
+ `Execution facts: ${JSON.stringify(receipt.execution_failures ?? [])}`,
318
+ `Report: ${receipt.runner_evidence_required?.path ? path.join(path.dirname(receipt.receipt_path), "artifacts", receipt.runner_evidence_required.path) : "not declared"}`].join("\n");
319
+ }
320
+ /** Historical attribution reader only. New repair routing uses checkDiagnosisDisposition. */
249
321
  export function checkRepairDisposition(receipt) {
250
322
  if (receipt.fingerprint_scope != null && receipt.fingerprint_scope !== "check_inputs")
251
323
  return "tooling_blocker";
@@ -266,12 +338,7 @@ export function checkRepairDisposition(receipt) {
266
338
  return "tooling_blocker";
267
339
  if (!evidence.completed)
268
340
  return "unclassified";
269
- if (evidence.failures.some(f => !runnerFailureValid(f) || !candidateFailure(f, evidence, receipt.candidate_root, receipt.candidate_files, receipt.candidate_absences)))
341
+ if (evidence.failures.some(f => !runnerFailureValid(f) || !candidateFailure(f, evidence, receipt.candidate_root, receipt.candidate_files, receipt.candidate_absences, receipt.candidate_library_inputs)))
270
342
  return "unclassified";
271
343
  return "source_repair";
272
344
  }
273
- export function assertSourceRepairEligible(receipts) {
274
- const blockers = receipts.filter(receipt => checkRepairDisposition(receipt) !== "source_repair");
275
- if (!receipts.length || blockers.length)
276
- throw new AppError("check_gate_blocked", "Checks require tooling diagnosis or settlement before product repair; retained nonzero is not a candidate-source verdict", 2, { failures: receipts, dispositions: receipts.map(receipt => ({ check_receipt_id: receipt.id, disposition: checkRepairDisposition(receipt) })), outcome: "blocked", recoverable: false, instruction: "Inspect retained runner evidence. Do not change product code to repair tooling, unknown observation or unclassified failure. Retry only after establishing and correcting the cause." });
277
- }
@@ -8,11 +8,13 @@ function boundedString(value, bytes) { return typeof value === "string" && Buffe
8
8
  const validatedBrowserProofs = new WeakSet();
9
9
  const unsupportedObservationContracts = new WeakSet();
10
10
  const validatedNativeProofs = new WeakSet();
11
+ const validatedLibraryInputs = new WeakMap();
11
12
  const validatedChildren = new WeakMap();
12
13
  const validatedReports = new WeakMap();
13
14
  const hash = (value) => typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
14
15
  const absoluteFile = (value) => typeof value === "string" && value.length > 0 && value.length <= 4096 && !value.includes("\0") && path.isAbsolute(value);
15
16
  export function candidateAbsencesValid(value) { return Array.isArray(value) && value.length <= 64 && value.every(item => object(item) && absoluteFile(item.file) && item.path_state === "missing" && object(item.declaration) && absoluteFile(item.declaration.file) && hash(item.declaration.sha256)); }
17
+ export function candidateLibraryInputsValid(value) { return Array.isArray(value) && value.length <= 64 && new Set(value.map(item => object(item) ? item.file : null)).size === value.length && value.every(item => object(item) && absoluteFile(item.file) && hash(item.sha256)); }
16
18
  function relativeProofPath(value) { return typeof value === "string" && value.length > 0 && Buffer.byteLength(value) <= 4096 && !path.isAbsolute(value) && !value.includes("\\") && value.split("/").every(part => part !== "" && part !== "." && part !== ".."); }
17
19
  function proofArtifact(value) { return object(value) && relativeProofPath(value.relative_path) && typeof value.sha256 === "string" && /^[a-f0-9]{64}$/.test(value.sha256); }
18
20
  function browserProofShape(proof) {
@@ -35,6 +37,13 @@ export function runnerFailureValid(value) {
35
37
  return false;
36
38
  if (value.repair_evidence !== undefined) {
37
39
  const proof = value.repair_evidence;
40
+ if (object(proof) && proof.rule === "candidate_library_query_error")
41
+ return proof.exception_type === "TestingLibraryElementError" && proof.phase === "test"
42
+ && boundedString(proof.test_id, 4096) && Boolean(proof.test_id) && absoluteFile(proof.file) && hash(proof.file_sha256)
43
+ && [proof.line, proof.column].every(n => Number.isSafeInteger(n) && Number(n) > 0) && object(proof.library)
44
+ && proof.library.name === "@testing-library/dom" && proof.library.version === "10.4.1" && hash(proof.library.package_json_sha256) && hash(proof.library.lockfile_sha256)
45
+ && Array.isArray(proof.helper_frames) && proof.helper_frames.length >= 2 && proof.helper_frames.length <= 8
46
+ && proof.helper_frames.every(frame => object(frame) && absoluteFile(frame.file) && hash(frame.file_sha256) && [frame.line, frame.column].every(n => Number.isSafeInteger(n) && Number(n) > 0)) && proofArtifact(proof.native_artifact);
38
47
  if (object(proof) && proof.rule === "candidate_test_runtime_error")
39
48
  return ["TypeError", "ReferenceError", "Error"].includes(String(proof.exception_type)) && (proof.exception_type !== "Error" || proof.phase === "test")
40
49
  && absoluteFile(proof.file) && hash(proof.file_sha256) && ["test", "setup", "collection"].includes(String(proof.phase))
@@ -82,6 +91,62 @@ function readProofArtifact(root, ref, context, limit = 2 * 1024 * 1024) {
82
91
  throw new Error("Invalid proof object");
83
92
  return value;
84
93
  }
94
+ // Exact known helper bytes keep this a library-specific proof, not permission
95
+ // to skip arbitrary dependency frames. New versions need explicit evidence.
96
+ const queryInputHashes = {
97
+ "dist/config.js": "3627f6265cccd26604777602d7249788c2347a273bdd1cae41719c790f2c5a7a",
98
+ "dist/query-helpers.js": "901f513f2ac901992d7c5002e644aef69286edbcd7d050d3ee7dd6aaaebfc9cc",
99
+ "package.json": "4e62e2aa8b974b6a5d2d532396f7610d1b4e806d9d2aaef3c84221f0d2fab06d",
100
+ };
101
+ function validateLibraryQuery(root, proof, failure, evidence, context) {
102
+ const native = readProofArtifact(root, proof.native_artifact, context, 256 * 1024);
103
+ const facts = Object.fromEntries(Object.entries(proof).filter(([key]) => key !== "native_artifact"));
104
+ if (evidence.schema_id !== "dd-flow/check-runner-evidence@2" || !isDeepStrictEqual(native.binding, evidence.binding)
105
+ || Object.entries(facts).some(([key, value]) => !isDeepStrictEqual(native[key], value)) || native.executing_file !== proof.file || native.exception_name !== proof.exception_type
106
+ || failure.category !== "unclassified" || failure.origin !== "candidate" || failure.native_code !== proof.exception_type || failure.phase !== "test" || failure.test_id !== proof.test_id || failure.file !== proof.file
107
+ || native.stack_complete !== true || native.frames_complete !== true || !Array.isArray(native.frame_lines) || native.frame_lines.length !== proof.helper_frames.length + 1
108
+ || native.frame_lines.some(line => !boundedString(line, 4096)) || Buffer.byteLength(JSON.stringify(native.frame_lines)) > 128 * 1024)
109
+ return false;
110
+ const expectedFrames = [...proof.helper_frames, { file: proof.file, line: proof.line, column: proof.column }];
111
+ for (const [index, line] of native.frame_lines.entries()) {
112
+ const match = String(line).match(/^\s*at (?:.*?\()?((?:file:\/\/)?\/[^\n]+):(\d+):(\d+)\)?$/), expected = expectedFrames[index];
113
+ if (!match || (match[1].startsWith("file:") ? fileURLToPath(match[1]) : match[1]) !== expected.file || Number(match[2]) !== expected.line || Number(match[3]) !== expected.column)
114
+ return false;
115
+ }
116
+ const packageRoot = path.dirname(path.dirname(proof.helper_frames[0].file));
117
+ if (proof.helper_frames[0].file !== path.join(packageRoot, "dist/config.js") || !String(native.frame_lines[0]).includes("getElementError")
118
+ || proof.helper_frames.slice(1).some(frame => frame.file !== path.join(packageRoot, "dist/query-helpers.js")))
119
+ return false;
120
+ if (!Array.isArray(native.library_inputs) || native.library_inputs.length !== 4)
121
+ return false;
122
+ const inputs = [];
123
+ let packageJson;
124
+ for (const [index, value] of native.library_inputs.entries()) {
125
+ if (!object(value) || !absoluteFile(value.file) || !hash(value.sha256) || !proofArtifact(value.artifact))
126
+ return false;
127
+ const expectedName = Object.keys(queryInputHashes)[index];
128
+ if (expectedName ? value.file !== path.join(packageRoot, expectedName) || value.sha256 !== queryInputHashes[expectedName]
129
+ : path.basename(value.file) !== "pnpm-lock.yaml" || value.sha256 !== proof.library.lockfile_sha256)
130
+ return false;
131
+ const bytes = readBytes(context, path.posix.join(root, value.artifact.relative_path), 2 * 1024 * 1024);
132
+ if (crypto.createHash("sha256").update(bytes).digest("hex") !== value.sha256 || value.artifact.sha256 !== value.sha256)
133
+ return false;
134
+ if (index === 2)
135
+ packageJson = JSON.parse(bytes.toString("utf8"));
136
+ if (index === 3 && !bytes.toString("utf8").includes("@testing-library/dom@10.4.1"))
137
+ return false;
138
+ inputs.push({ file: value.file, sha256: value.sha256, artifact: value.artifact });
139
+ }
140
+ if (packageJson?.name !== proof.library.name || packageJson.version !== proof.library.version || inputs[2].sha256 !== proof.library.package_json_sha256
141
+ || proof.helper_frames.some(frame => inputs.find(input => input.file === frame.file)?.sha256 !== frame.file_sha256))
142
+ return false;
143
+ const candidateRoot = path.dirname(inputs[3].file);
144
+ if ([proof.file, ...inputs.map(input => input.file)].some(file => { const relative = path.relative(candidateRoot, file); return !relative || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative); }))
145
+ return false;
146
+ validatedLibraryInputs.set(proof, inputs);
147
+ validatedNativeProofs.add(proof);
148
+ return true;
149
+ }
85
150
  function matches(actual, expected) { return object(actual) && Object.entries(expected).every(([key, value]) => actual[key] === value); }
86
151
  function validateBrowserArtifacts(root, proof, binding, context) {
87
152
  const snapshot = readProofArtifact(root, proof.worker_snapshot, context), native = readProofArtifact(root, proof.native_result, context);
@@ -104,7 +169,7 @@ export function runnerProofArtifacts(evidence) {
104
169
  if (child && failure.child_evidence)
105
170
  return [failure.child_evidence.report.relative_path, ...runnerProofArtifacts(child.evidence).map(file => path.posix.join(path.posix.dirname(failure.child_evidence.report.relative_path), file))];
106
171
  const proof = failure.repair_evidence;
107
- return [...(failure.diagnostic_artifact ? [failure.diagnostic_artifact.relative_path] : []), ...(proof?.rule === "candidate_browser_action_inactivity" ? [proof.worker_snapshot.relative_path, proof.native_result.relative_path] : proof && "native_artifact" in proof ? [proof.native_artifact.relative_path] : [])];
172
+ return [...(failure.diagnostic_artifact ? [failure.diagnostic_artifact.relative_path] : []), ...(proof?.rule === "candidate_browser_action_inactivity" ? [proof.worker_snapshot.relative_path, proof.native_result.relative_path] : proof && "native_artifact" in proof ? [proof.native_artifact.relative_path, ...(proof.rule === "candidate_library_query_error" ? (validatedLibraryInputs.get(proof) ?? []).map(input => input.artifact.relative_path) : [])] : [])];
108
173
  }), ...(evidence ? validatedReports.get(evidence) ?? [] : []).flatMap(child => [child.report.relative_path, ...runnerProofArtifacts(child.evidence).map(file => path.posix.join(path.posix.dirname(child.report.relative_path), file))])])];
109
174
  }
110
175
  function diagnostic(context, value) {
@@ -224,6 +289,8 @@ function readRunnerEvidenceInternal(root, contract, binding, depth, budget, cont
224
289
  if (failure.diagnostic_artifact && !matches(readProofArtifact(root, failure.diagnostic_artifact, context, 2 * 1024 * 1024).binding, binding))
225
290
  return null;
226
291
  const proof = failure.repair_evidence;
292
+ if (proof?.rule === "candidate_library_query_error" && !validateLibraryQuery(root, proof, failure, evidence, context))
293
+ return null;
227
294
  if (proof?.rule === "candidate_migration_sql" || proof?.rule === "candidate_tool_diagnostic" || proof?.rule === "candidate_test_runtime_error") {
228
295
  const native = readProofArtifact(root, proof.native_artifact, context, 2 * 1024 * 1024), facts = Object.fromEntries(Object.entries(proof).filter(([key]) => key !== "native_artifact"));
229
296
  if (!isDeepStrictEqual(native.binding, binding) || Object.entries(facts).some(([key, value]) => !isDeepStrictEqual(native[key], value)))
@@ -301,5 +368,6 @@ function readRunnerEvidenceInternal(root, contract, binding, depth, budget, cont
301
368
  export function runnerEvidencePass(evidence) { return Boolean(evidence?.completed && !evidence.failure_projection && !evidence.observation_unknown && evidence.counts.failed === 0 && evidence.failures.length === 0); }
302
369
  export const validatedRunnerChild = (failure) => validatedChildren.get(failure);
303
370
  export const hasValidatedNativeProof = (proof) => validatedNativeProofs.has(proof);
371
+ export const runnerLibraryInputs = (proof) => validatedLibraryInputs.get(proof)?.map(({ file, sha256 }) => ({ file, sha256 }));
304
372
  export const hasValidatedBrowserProof = (proof) => validatedBrowserProofs.has(proof);
305
373
  export const hasUnsupportedObservation = (evidence) => unsupportedObservationContracts.has(evidence);
@@ -89,6 +89,10 @@ function isReadOnlyDiagnostic(family, command, action) {
89
89
  return ["status", "ls", "show"].includes(command ?? "");
90
90
  if (family === "stage")
91
91
  return command === "fanout" && action === "status";
92
+ if (family === "interactive")
93
+ return command === "inputs" || command === "git-inputs";
94
+ if (family === "delivery")
95
+ return command === "inputs";
92
96
  if (family === "lane") {
93
97
  return (command === "status" ||
94
98
  command === "waiters" ||
@@ -108,7 +112,8 @@ function isReadOnlyDiagnostic(family, command, action) {
108
112
  if (family === "dashboard")
109
113
  return command === "data" || command === "open";
110
114
  if (family === "memory")
111
- return command === "permissions" && action === "preflight";
115
+ return command === "permissions" && action === "preflight" || command === "distill" && action === "inputs"
116
+ || command === "fix" && ["context", "scope"].includes(action ?? "");
112
117
  if (family === "codex" && command === "hooks")
113
118
  return action === "print" || action === "status";
114
119
  if (family === "codex" && command === "home")
@@ -1,6 +1,6 @@
1
1
  import crypto from "node:crypto";
2
- import { candidateAbsencesValid, candidateFailureAbsences, candidateFailureFiles, checkRepairDisposition, readRunnerEvidence, readRunnerEvidenceDetailed, readRunnerProgress, runnerContractsValid, runnerEvidencePass, runnerObservationDurations, runnerProofArtifacts } from "./check-runner-evidence.js";
3
- export { assertSourceRepairEligible, checkRepairDisposition } from "./check-runner-evidence.js";
2
+ import { candidateAbsencesValid, candidateFailureAbsences, candidateFailureFiles, candidateLibraryInputs, candidateLibraryInputsValid, currentCandidateLibraryInputsValid, checkDiagnosisDisposition, readRunnerEvidence, readRunnerEvidenceDetailed, readRunnerProgress, runnerContractsValid, runnerEvidencePass, runnerObservationDurations, runnerProofArtifacts } from "./check-runner-evidence.js";
3
+ export { assertCheckDiagnosisEligible, checkDiagnosisDisposition, checkRepairDisposition } from "./check-runner-evidence.js";
4
4
  import { isGitlessWorkspace, ownedWorkspaceHookPath, projectServicePathFilter, workspaceGitFiles } from "./workspace-files.js";
5
5
  import { spawn, spawnSync } from "node:child_process";
6
6
  import { ObservationClock, validateObservationDuration } from "../harness-runtime/lib/observation-clock.mjs";
@@ -352,14 +352,16 @@ export class PreparedCheckEvidence {
352
352
  candidates: receipts.filter(item => item.scope === input.scope && item.work_id === (input.workId ?? null))
353
353
  .slice(-16).map(item => ({ id: item.id, input_hash: item.input_hash, profile_hash: item.profile_hash }))
354
354
  });
355
- if (!checkReceiptEvidenceValidForMode(bindReceipt(receipt, group), input.mode))
355
+ if (!checkReceiptEvidenceValidForMode(bindReceipt(receipt, group), input.mode) || input.mode !== "diagnostic" && !currentCandidateLibraryInputsValid(receipt, input.workspaceRoot))
356
356
  fail();
357
357
  passed = passed && receipt.status === "passed";
358
- for (const artifact of receipt.artifacts)
358
+ const availableArtifacts = input.mode === "diagnostic" ? availableDiagnosticArtifacts(receipt) : receipt.artifacts;
359
+ for (const artifact of availableArtifacts)
359
360
  artifacts.push({ root: "run", path: path.relative(input.runHome, path.join(path.dirname(receipt.receipt_path), "artifacts", artifact.path)).split(path.sep).join("/"), sha256: artifact.sha256 });
360
361
  const bytes = fs.readFileSync(receipt.receipt_path);
361
- completions.push({ root: "run", path: path.relative(input.runHome, receipt.completion_path).split(path.sep).join("/"), sha256: crypto.createHash("sha256").update(fs.readFileSync(receipt.completion_path)).digest("hex") });
362
- if (receipt.input_manifest)
362
+ if (input.mode !== "diagnostic" || fs.existsSync(receipt.completion_path))
363
+ completions.push({ root: "run", path: path.relative(input.runHome, receipt.completion_path).split(path.sep).join("/"), sha256: crypto.createHash("sha256").update(readBoundedReleaseFile(receipt.completion_path, 1024 * 1024)).digest("hex") });
364
+ if (receipt.input_manifest && (input.mode !== "diagnostic" || fs.existsSync(path.join(path.dirname(receipt.receipt_path), receipt.input_manifest.path))))
363
365
  completions.push({ root: "run", path: path.relative(input.runHome, path.join(path.dirname(receipt.receipt_path), receipt.input_manifest.path)).split(path.sep).join("/"), sha256: receipt.input_manifest.sha256 });
364
366
  return { id: receipt.id, path: receipt.receipt_path, sha256: crypto.createHash("sha256").update(bytes).digest("hex") };
365
367
  });
@@ -562,7 +564,7 @@ async function runCodeChecksOwned(context, input, workspaceGateId, generation, s
562
564
  const snapshot = workspaceState(input.workspaceRoot, [input.runHome]);
563
565
  const group = groups.find(candidate => candidate.declarations.some(check => check.run_at === predecessor.gate)
564
566
  && (checkInputHash(input.workspaceRoot, { ...candidate, gate: predecessor.gate }, profileHash, workspaceInputFingerprint(input.workspaceRoot, snapshot.files, candidate.inputs, [input.runHome]), input.evidenceContext, input.checkEnvironment) === predecessor.input_hash
565
- || sourceRepairContractMatches(predecessor, candidate, profileHash)
567
+ || diagnosisRetryContractMatches(predecessor, candidate, profileHash, checkInputHash(input.workspaceRoot, { ...candidate, gate: predecessor.gate }, profileHash, "", input.evidenceContext, input.checkEnvironment, false), checkInputHash(input.workspaceRoot, { ...candidate, gate: predecessor.gate }, profileHash, "", input.evidenceContext, input.checkEnvironment))
566
568
  || input.retryReason === "check_inputs_changed" && staleRetryContractMatches(predecessor, candidate, profileHash, input)));
567
569
  if (!group || retries.has(group))
568
570
  throw new AppError("retry_check_invalid", "Retry must match exactly one current execution contract", 2, { check_receipt_id: receiptId });
@@ -579,7 +581,9 @@ async function runCodeChecksOwned(context, input, workspaceGateId, generation, s
579
581
  const inputFingerprint = inputFilesFingerprint(inputFiles);
580
582
  const inputHash = checkInputHash(input.workspaceRoot, group, profileHash, inputFingerprint, input.evidenceContext, input.checkEnvironment);
581
583
  const epoch = crypto.createHash("sha256").update(`${group.gate}\0${inputFingerprint}`).digest("hex");
582
- const executionContractHash = checkInputHash(input.workspaceRoot, group, profileHash, "", input.evidenceContext, input.checkEnvironment);
584
+ // Retry preserves the declared check/wave, not the failed conditions being
585
+ // corrected. Observed environment still participates in inputHash/PASS reuse.
586
+ const executionContractHash = checkInputHash(input.workspaceRoot, group, profileHash, "", input.evidenceContext, input.checkEnvironment, false);
583
587
  const reservationInput = { ...input, retryReceiptIds: retries.has(group) ? [retries.get(group)] : [], generation, group, before, inputFiles, inputFingerprint, executionContractHash, profileHash, inputHash, epoch, startedAt };
584
588
  delete reservationInput.retryReceiptId;
585
589
  const decision = reserveOrReuseReceipt(context, reservationInput);
@@ -592,6 +596,7 @@ async function runCodeChecksOwned(context, input, workspaceGateId, generation, s
592
596
  const { id, evidenceDir, stdoutPath, stderrPath, receiptPath, completionPath } = reserved;
593
597
  let resources = {};
594
598
  let result = { exitCode: null, error: "check did not start", aborted: true };
599
+ let dispatched = false, physicallySettled = false;
595
600
  try {
596
601
  fs.mkdirSync(evidenceDir, { recursive: true });
597
602
  retainInputManifest(context, reserved);
@@ -624,6 +629,7 @@ async function runCodeChecksOwned(context, input, workspaceGateId, generation, s
624
629
  const runnerEnvironment = group.runnerEvidence ? { DD_FLOW_CHECK_RUNNER_EVIDENCE_PATH: path.join(evidenceDir, group.runnerEvidence.path), DD_FLOW_CHECK_RUNNER_PROGRESS_PATH: path.join(evidenceDir, "runner-progress.jsonl"), DD_FLOW_CHECK_INVOCATION_ID: id, DD_FLOW_CHECK_ID: id, DD_FLOW_CHECK_INPUT_HASH: inputHash, DD_FLOW_CHECK_VERIFICATION_EPOCH: epoch } : {};
625
630
  if (group.runnerEvidence)
626
631
  fs.mkdirSync(path.dirname(path.join(evidenceDir, group.runnerEvidence.path)), { recursive: true });
632
+ dispatched = true;
627
633
  result = await runCheck(group.command, input.workspaceRoot, { ...codeExecutionEnvironment(input.workspaceRoot, input.checkEnvironment), ...browserCheckCapabilityEnvironment(context, input.runId, input.runHome), DD_FLOW_EVIDENCE_DIR: evidenceDir, DD_FLOW_CHECK_COMPLETION_FILE: completionPath, ...runnerEnvironment, ...portEnvironment(resources) }, stdout, stderr, (elapsed) => input.progress?.(`check ${index + 1}/${groups.length} still running (${elapsed}s): ${group.command}`), (pid) => {
628
634
  confirmedExpiry = confirmManagedProcess(context, { id: managed.id, leaseToken: managed.lease_token, pid, processGroupId: process.platform === "win32" ? null : pid }).lease_expires_at;
629
635
  assertRunMutationAllowed(context, input.projectId, input.runId, generation);
@@ -638,6 +644,7 @@ async function runCodeChecksOwned(context, input, workspaceGateId, generation, s
638
644
  const current = managedProcessStatus(context).find(item => item.id === managed.id);
639
645
  if (processTreeIsAlive(current))
640
646
  throw new AppError("check_in_progress", "The check shell exited but its process tree is still live; retain this receipt and its resources until settlement", 2, { process_id: managed.id, check_receipt_id: id });
647
+ physicallySettled = !result.cleanupUnconfirmed;
641
648
  allocation.release();
642
649
  if (!result.cleanupUnconfirmed)
643
650
  finishManagedProcess(context, { id: managed.id, leaseToken: managed.lease_token, state: result.aborted || result.exitCode !== 0 || Boolean(result.error) ? "failed" : "stopped", reason: result.error });
@@ -664,10 +671,14 @@ async function runCodeChecksOwned(context, input, workspaceGateId, generation, s
664
671
  const physicalFailures = executionFailures({ fingerprintScope: "check_inputs", exitCode: result.exitCode, launcherError: result.error ?? undefined, cleanupUnconfirmed: result.cleanupUnconfirmed, missing: artifacts.missing, mutations: mutationPaths, invalidRunner: Boolean(group.runnerEvidence && !runnerEvidence) });
665
672
  if (result.failure)
666
673
  physicalFailures.push(result.failure);
674
+ // This marker is SQL-owned execution metadata, not a semantic runner verdict.
675
+ if (result.aborted && (!dispatched || physicallySettled))
676
+ physicalFailures.push({ code: "check_execution_settled", message: "No owned check process remains; investigate the retained launch or execution failure" });
667
677
  const candidateFiles = candidateFailureFiles(runnerEvidence, input.workspaceRoot);
668
678
  const candidateAbsences = candidateFailureAbsences(runnerEvidence, input.workspaceRoot);
679
+ const libraryInputs = candidateLibraryInputs(runnerEvidence, input.workspaceRoot);
669
680
  if (group.runnerEvidence)
670
- retainExecutionOutcome(evidenceDir, { id, command: group.command, input_hash: inputHash, verification_epoch: epoch, fingerprint_scope: "check_inputs", input_manifest: reserved.inputManifest ?? null, execution_contract_hash: executionContractHash }, input.workspaceRoot, physicalFailures, candidateFiles, candidateAbsences);
681
+ retainExecutionOutcome(evidenceDir, { id, command: group.command, input_hash: inputHash, verification_epoch: epoch, fingerprint_scope: "check_inputs", input_manifest: reserved.inputManifest ?? null, execution_contract_hash: executionContractHash }, input.workspaceRoot, physicalFailures, candidateFiles, candidateAbsences, libraryInputs);
671
682
  const requiredArtifacts = [...new Set([...group.requiredArtifacts, ...runnerProofArtifacts(runnerEvidence), ...(group.runnerEvidence ? ["engine-outcome.json"] : [])])];
672
683
  artifacts = collectCheckArtifacts(evidenceDir, requiredArtifacts);
673
684
  const status = result.aborted ? "aborted" : result.exitCode === 0 && !result.error && artifacts.complete && mutationPaths.length === 0 && (!group.runnerEvidence || runnerEvidencePass(runnerEvidence)) ? "passed" : "failed";
@@ -676,6 +687,7 @@ async function runCodeChecksOwned(context, input, workspaceGateId, generation, s
676
687
  receipt.candidate_root = input.workspaceRoot;
677
688
  receipt.candidate_files = candidateFiles;
678
689
  receipt.candidate_absences = candidateAbsences;
690
+ receipt.candidate_library_inputs = libraryInputs;
679
691
  receipt.execution_failures = physicalFailures;
680
692
  context.db.run("UPDATE check_receipts SET execution_failures_json = ? WHERE project_id = ? AND id = ? AND status = 'running'", [JSON.stringify(physicalFailures), input.projectId, id]);
681
693
  const completion = readCompletion(completionPath);
@@ -768,6 +780,7 @@ export function reconcileUnfinishedChecks(context, input) {
768
780
  physicalFailures.push({ code: "completion_snapshot_unknown", message: "Detached check has no bound scoped completion snapshot; current inputs cannot prove inputs at command completion" });
769
781
  let candidateFiles = candidateFailureFiles(runnerEvidence, input.workspaceRoot);
770
782
  let candidateAbsences = candidateFailureAbsences(runnerEvidence, input.workspaceRoot);
783
+ let libraryInputs = candidateLibraryInputs(runnerEvidence, input.workspaceRoot);
771
784
  if (receipt?.runner_evidence_required) {
772
785
  const evidenceDir = path.join(path.dirname(pendingReceipt.receipt_path), "artifacts");
773
786
  if (fs.existsSync(path.join(evidenceDir, "engine-outcome.json"))) {
@@ -778,13 +791,14 @@ export function reconcileUnfinishedChecks(context, input) {
778
791
  physicalFailures.push(...(proven.execution_failures ?? []));
779
792
  candidateFiles = proven.candidate_files ?? candidateFiles;
780
793
  candidateAbsences = proven.candidate_absences ?? [];
794
+ libraryInputs = proven.candidate_library_inputs ?? [];
781
795
  }
782
796
  else {
783
797
  physicalFailures.push({ code: "engine_outcome_unbound", message: "Existing execution outcome is not bound to retained inventory; prior launcher/cleanup outcome is unknown" });
784
798
  fs.renameSync(path.join(evidenceDir, "engine-outcome.json"), path.join(evidenceDir, `engine-outcome-unconfirmed-${crypto.randomUUID()}.json`));
785
799
  }
786
800
  }
787
- retainExecutionOutcome(evidenceDir, receipt, input.workspaceRoot, physicalFailures, candidateFiles, candidateAbsences);
801
+ retainExecutionOutcome(evidenceDir, receipt, input.workspaceRoot, physicalFailures, candidateFiles, candidateAbsences, libraryInputs);
788
802
  receipt.required_artifacts = [...new Set([...(receipt.required_artifacts ?? []), ...runnerProofArtifacts(runnerEvidence), "engine-outcome.json"])];
789
803
  artifacts = collectCheckArtifacts(evidenceDir, receipt.required_artifacts);
790
804
  }
@@ -798,6 +812,7 @@ export function reconcileUnfinishedChecks(context, input) {
798
812
  repaired.candidate_root = input.workspaceRoot;
799
813
  repaired.candidate_files = candidateFiles;
800
814
  repaired.candidate_absences = candidateAbsences;
815
+ repaired.candidate_library_inputs = libraryInputs;
801
816
  repaired.execution_failures = physicalFailures;
802
817
  writeReceipt(pendingReceipt.receipt_path, repaired);
803
818
  if (status === "failed")
@@ -821,7 +836,7 @@ function reserveOrReuseReceipt(context, input) {
821
836
  const successor = retryIds.size === 1 ? receipts.find((receipt) => receipt.retry_of_receipt_id === [...retryIds][0] && receipt.input_hash === input.inputHash && receipt.profile_hash === input.profileHash) : null;
822
837
  const reusableSuccessor = successor?.status === "passed" && checkReceiptEvidenceValid(successor);
823
838
  const retryPredecessor = receipts.find(receipt => retryIds.has(receipt.id));
824
- const sourceCorrection = Boolean(retryPredecessor && (sourceRepairContractMatches(retryPredecessor, input.group, input.profileHash)
839
+ const sourceCorrection = Boolean(retryPredecessor && (diagnosisRetryContractMatches(retryPredecessor, input.group, input.profileHash, input.executionContractHash, checkInputHash(input.workspaceRoot, input.group, input.profileHash, "", input.evidenceContext, input.checkEnvironment))
825
840
  || input.retryReason === "check_inputs_changed" && staleRetryContractMatches(retryPredecessor, input.group, input.profileHash, input)));
826
841
  const latestContract = receipts.filter(receipt => receipt.command === input.group.command && receipt.profile_hash === input.profileHash && receipt.gate === input.group.gate && receipt.scope === input.scope && receipt.work_id === (input.workId ?? null)).at(-1);
827
842
  return context.db.writeTransaction(() => {
@@ -879,9 +894,12 @@ function reserveOrReuseReceipt(context, input) {
879
894
  return { reserved };
880
895
  }, "checks.reserveOrReuseReceipt");
881
896
  }
882
- function sourceRepairContractMatches(receipt, group, profileHash) {
883
- return checkRepairDisposition(receipt) === "source_repair" && group.command === receipt.command && profileHash === receipt.profile_hash && isDeepStrictEqual(group.inputs, receipt.inputs)
884
- && isDeepStrictEqual(derivedReceiptArtifacts(group, receipt).sort(), [...receipt.required_artifacts].sort()) && isDeepStrictEqual([...group.ports].sort(), Object.keys(receipt.resources.ports).sort());
897
+ function diagnosisRetryContractMatches(receipt, group, profileHash, executionContractHash, legacyExecutionContractHash) {
898
+ return checkDiagnosisDisposition(receipt) === "diagnosis_required" && group.command === receipt.command && profileHash === receipt.profile_hash && isDeepStrictEqual(group.inputs, receipt.inputs)
899
+ && [executionContractHash, legacyExecutionContractHash].includes(receipt.execution_contract_hash ?? "")
900
+ && group.requiredArtifacts.every(artifact => receipt.required_artifacts.includes(artifact))
901
+ && isDeepStrictEqual(group.runnerEvidence ?? null, receipt.runner_evidence_required ?? null)
902
+ && isDeepStrictEqual([...group.ports].sort(), Object.keys(receipt.resources.ports).sort());
885
903
  }
886
904
  function staleCheckRetryEligible(receipt) {
887
905
  if (receipt.fingerprint_scope !== "check_inputs" || receipt.status !== "failed" || receipt.exit_code !== 0 || receipt.retry_reason === "check_inputs_changed"
@@ -892,7 +910,8 @@ function staleCheckRetryEligible(receipt) {
892
910
  }
893
911
  function staleRetryContractMatches(receipt, group, profileHash, input) {
894
912
  return group.reusable && staleCheckRetryEligible(receipt) && isDeepStrictEqual(group.runnerEvidence ?? null, receipt.runner_evidence_required ?? null)
895
- && receipt.execution_contract_hash === checkInputHash(input.workspaceRoot, group, profileHash, "", input.evidenceContext, input.checkEnvironment);
913
+ && [checkInputHash(input.workspaceRoot, group, profileHash, "", input.evidenceContext, input.checkEnvironment, false),
914
+ checkInputHash(input.workspaceRoot, group, profileHash, "", input.evidenceContext, input.checkEnvironment)].includes(receipt.execution_contract_hash ?? "");
896
915
  }
897
916
  function receiptFor(input) { return { id: input.id, local_id: input.localId, declaration_id: input.group.declarations[0].id, check_refs: input.group.declarations.map(checkRef), gate: input.group.gate, scope: input.scope, work_id: input.workId ?? null, command: input.group.command, input_hash: input.inputHash, verification_epoch: input.epoch, retry_of_receipt_id: input.retryOf ?? null, retry_reason: input.retryReason ?? null, status: input.status, ...(input.status === "aborted" ? { abort_reason: input.result.error } : {}), exit_code: input.status === "running" ? null : input.result.exitCode, stdout_path: input.stdoutPath, stderr_path: input.stderrPath, receipt_path: input.receiptPath, completion_path: input.completionPath, required_artifacts: input.group.requiredArtifacts, inputs: input.group.inputs, fingerprint_scope: "check_inputs", input_manifest: input.inputManifest ?? null, execution_contract_hash: input.executionContractHash, workspace_fingerprint: input.before.fingerprint, before_fingerprint: input.inputFingerprint, after_fingerprint: input.status === "running" ? input.inputFingerprint : input.after.fingerprint, profile_hash: input.profileHash, mutation_paths: input.mutationPaths, artifacts: input.artifacts, resources: { ports: input.resources }, started_at: input.startedAt, finished_at: input.status === "running" ? input.startedAt : new Date().toISOString() }; }
898
917
  function retainInputManifest(context, reserved) {
@@ -950,8 +969,6 @@ export function validateFailedCheckRetry(context, input) {
950
969
  const declared = new Set(input.declarations.flatMap((check) => [check.id, checkRef(check)]));
951
970
  if (!receipt.check_refs.some((ref) => declared.has(ref)))
952
971
  throw new AppError("retry_check_invalid", "--retry-check does not belong to this stage gate", 2, { check_receipt_id: input.receiptId, check_refs: receipt.check_refs });
953
- if (receipt.fingerprint_scope === "check_inputs" && receipt.execution_failures?.some(failure => failure.code === "check_inputs_changed") && input.reason !== "check_inputs_changed")
954
- throw new AppError("retry_check_invalid", "Scoped drift uses its bounded stale transition, not an environment/source retry", 2, { check_receipt_id: receipt.id });
955
972
  if (input.reason === "check_inputs_changed" && (!staleCheckRetryEligible(receipt) || receipt.retry_reason === "check_inputs_changed"))
956
973
  throw new AppError("retry_check_invalid", "Scoped input retry requires a bound, safely completed stale predecessor and one unconsumed episode", 2, { check_receipt_id: receipt.id });
957
974
  return receipt;
@@ -1004,12 +1021,12 @@ function resolvedReceiptArtifacts(receiptPath, artifacts, binding) {
1004
1021
  if (retainedOutcome.complete && retainedOutcome.items.every(item => artifacts.some(artifact => relocate(artifact.path) === item.path && artifact.sha256 === item.sha256))) {
1005
1022
  try {
1006
1023
  const value = JSON.parse(fs.readFileSync(outcomePath, "utf8"));
1007
- if (value.schema_id === "dd-flow/check-execution-outcome@1" && value.id === binding.id && value.command === binding.command && value.input_hash === binding.input_hash && value.verification_epoch === binding.verification_epoch && (value.fingerprint_scope ?? null) === (binding.fingerprint_scope ?? null) && (value.execution_contract_hash ?? null) === (binding.execution_contract_hash ?? null) && JSON.stringify(value.input_manifest ?? null) === JSON.stringify(binding.input_manifest ?? null) && typeof value.candidate_root === "string" && path.isAbsolute(value.candidate_root) && (value.candidate_files === undefined || Array.isArray(value.candidate_files) && value.candidate_files.length <= 64 && value.candidate_files.every((file) => typeof file === "string" && path.isAbsolute(file) && Buffer.byteLength(file) <= 4096)) && (value.candidate_absences === undefined || candidateAbsencesValid(value.candidate_absences)) && Array.isArray(value.execution_failures) && value.execution_failures.every((failure) => failure && typeof failure === "object" && typeof failure.code === "string" && typeof failure.message === "string"))
1024
+ if (value.schema_id === "dd-flow/check-execution-outcome@1" && value.id === binding.id && value.command === binding.command && value.input_hash === binding.input_hash && value.verification_epoch === binding.verification_epoch && (value.fingerprint_scope ?? null) === (binding.fingerprint_scope ?? null) && (value.execution_contract_hash ?? null) === (binding.execution_contract_hash ?? null) && JSON.stringify(value.input_manifest ?? null) === JSON.stringify(binding.input_manifest ?? null) && typeof value.candidate_root === "string" && path.isAbsolute(value.candidate_root) && (value.candidate_files === undefined || Array.isArray(value.candidate_files) && value.candidate_files.length <= 64 && value.candidate_files.every((file) => typeof file === "string" && path.isAbsolute(file) && Buffer.byteLength(file) <= 4096)) && (value.candidate_absences === undefined || candidateAbsencesValid(value.candidate_absences)) && (value.candidate_library_inputs === undefined || candidateLibraryInputsValid(value.candidate_library_inputs)) && Array.isArray(value.execution_failures) && value.execution_failures.every((failure) => failure && typeof failure === "object" && typeof failure.code === "string" && typeof failure.message === "string"))
1008
1025
  outcome = value;
1009
1026
  }
1010
1027
  catch { /* Missing physical proof cannot grant attribution. */ }
1011
1028
  }
1012
- return { completion_path: path.join(currentRoot, "completion.json"), required_artifacts: original.required_artifacts ?? [], inputs: original.inputs ?? [], artifacts: artifacts.map((artifact) => ({ ...artifact, path: relocate(artifact.path) })), ...(original.runner_evidence_required ? { runner_evidence_required: original.runner_evidence_required, runner_evidence: evidence, runner_evidence_diagnostics: diagnostics, ...(diagnosticsTruncated ? { runner_evidence_diagnostics_truncated: true } : {}) } : {}), execution_failures: outcome.execution_failures ?? [], ...(outcome.candidate_root ? { candidate_root: outcome.candidate_root } : {}), ...(outcome.candidate_files ? { candidate_files: outcome.candidate_files } : {}), ...(outcome.candidate_absences ? { candidate_absences: outcome.candidate_absences } : {}) };
1029
+ return { completion_path: path.join(currentRoot, "completion.json"), required_artifacts: original.required_artifacts ?? [], inputs: original.inputs ?? [], artifacts: artifacts.map((artifact) => ({ ...artifact, path: relocate(artifact.path) })), ...(original.runner_evidence_required ? { runner_evidence_required: original.runner_evidence_required, runner_evidence: evidence, runner_evidence_diagnostics: diagnostics, ...(diagnosticsTruncated ? { runner_evidence_diagnostics_truncated: true } : {}) } : {}), execution_failures: outcome.execution_failures ?? [], ...(outcome.candidate_root ? { candidate_root: outcome.candidate_root } : {}), ...(outcome.candidate_files ? { candidate_files: outcome.candidate_files } : {}), ...(outcome.candidate_absences ? { candidate_absences: outcome.candidate_absences } : {}), ...(outcome.candidate_library_inputs ? { candidate_library_inputs: outcome.candidate_library_inputs } : {}) };
1013
1030
  }
1014
1031
  function executionFailures(input) {
1015
1032
  const failures = [];
@@ -1027,11 +1044,11 @@ function executionFailures(input) {
1027
1044
  failures.push({ code: "invalid_runner_evidence", message: "Required runner evidence is unavailable or invalid" });
1028
1045
  return failures;
1029
1046
  }
1030
- function retainExecutionOutcome(evidenceDir, binding, candidateRoot, failures, candidateFiles, candidateAbsences = []) {
1047
+ function retainExecutionOutcome(evidenceDir, binding, candidateRoot, failures, candidateFiles, candidateAbsences = [], libraryInputs = []) {
1031
1048
  const temporary = path.join(evidenceDir, `.engine-outcome-${crypto.randomUUID()}.tmp`);
1032
1049
  if (!fs.lstatSync(evidenceDir).isDirectory() || fs.lstatSync(evidenceDir).isSymbolicLink())
1033
1050
  throw new AppError("check_evidence_stale", "Check evidence directory was replaced", 1);
1034
- fs.writeFileSync(temporary, JSON.stringify({ schema_id: "dd-flow/check-execution-outcome@1", id: binding.id, command: binding.command, input_hash: binding.input_hash, verification_epoch: binding.verification_epoch, fingerprint_scope: binding.fingerprint_scope ?? null, input_manifest: binding.input_manifest ?? null, execution_contract_hash: binding.execution_contract_hash ?? null, candidate_root: candidateRoot, candidate_files: candidateFiles, candidate_absences: candidateAbsences, execution_failures: failures }), { flag: "wx", mode: 0o600 });
1051
+ fs.writeFileSync(temporary, JSON.stringify({ schema_id: "dd-flow/check-execution-outcome@1", id: binding.id, command: binding.command, input_hash: binding.input_hash, verification_epoch: binding.verification_epoch, fingerprint_scope: binding.fingerprint_scope ?? null, input_manifest: binding.input_manifest ?? null, execution_contract_hash: binding.execution_contract_hash ?? null, candidate_root: candidateRoot, candidate_files: candidateFiles, candidate_absences: candidateAbsences, candidate_library_inputs: libraryInputs, execution_failures: failures }), { flag: "wx", mode: 0o600 });
1035
1052
  fs.renameSync(temporary, path.join(evidenceDir, "engine-outcome.json"));
1036
1053
  }
1037
1054
  export function workspaceFingerprint(root) { return workspaceState(root).fingerprint; }
@@ -1310,12 +1327,28 @@ function explicitInputFiles(root, inputs, ignoredRoots, sourceFiles) {
1310
1327
  visit(path.resolve(root, input), input, new Set());
1311
1328
  return [...result.entries()];
1312
1329
  }
1313
- function checkInputHash(workspaceRoot, group, profileHash, inputFingerprint, evidenceContext, checkEnvironment) { return crypto.createHash("sha256").update(JSON.stringify({ workspace_root: path.resolve(workspaceRoot), gate: group.reusable ? null : group.gate, command: group.command, required_artifacts: group.requiredArtifacts, ports: group.ports, inputs: group.inputs, ...(group.runnerEvidence ? { runner_evidence: group.runnerEvidence } : {}), profile_hash: profileHash, environment: environmentFingerprint(workspaceRoot, group.environmentInputs, checkEnvironment), input_fingerprint: inputFingerprint, ...(group.environmentInputs === undefined ? {} : { environment_inputs: group.environmentInputs }), ...(evidenceContext === undefined ? {} : { evidence_context: evidenceContext }) })).digest("hex"); }
1330
+ function checkInputHash(workspaceRoot, group, profileHash, inputFingerprint, evidenceContext, checkEnvironment, observedEnvironment = true) { return crypto.createHash("sha256").update(JSON.stringify({ workspace_root: path.resolve(workspaceRoot), gate: group.reusable ? null : group.gate, command: group.command, required_artifacts: group.requiredArtifacts, ports: group.ports, inputs: group.inputs, ...(group.runnerEvidence ? { runner_evidence: group.runnerEvidence } : {}), profile_hash: profileHash, environment: observedEnvironment ? environmentFingerprint(workspaceRoot, group.environmentInputs, checkEnvironment) : null, input_fingerprint: inputFingerprint, ...(group.environmentInputs === undefined ? {} : { environment_inputs: group.environmentInputs }), ...(evidenceContext === undefined ? {} : { evidence_context: evidenceContext }) })).digest("hex"); }
1314
1331
  function derivedReceiptArtifacts(group, receipt) { return [...new Set([...group.requiredArtifacts, ...runnerProofArtifacts(receipt.runner_evidence), ...(receipt.required_artifacts.includes("engine-outcome.json") ? ["engine-outcome.json"] : [])])]; }
1315
1332
  function bindReceipt(receipt, group) { return { ...receipt, gate: group.gate, declaration_id: group.declarations[0].id, check_refs: group.declarations.map(checkRef), required_artifacts: derivedReceiptArtifacts(group, receipt), inputs: group.inputs }; }
1316
1333
  export function checkReceiptEvidenceValid(receipt) { return checkReceiptEvidenceValidForMode(receipt); }
1334
+ function availableDiagnosticArtifacts(receipt) {
1335
+ const root = path.join(path.dirname(receipt.receipt_path), "artifacts");
1336
+ return receipt.artifacts.flatMap(artifact => {
1337
+ const relative = path.isAbsolute(artifact.path) ? path.relative(root, artifact.path) : artifact.path;
1338
+ const observed = collectCheckArtifacts(root, [relative]);
1339
+ return observed.items.filter(item => item.sha256 === artifact.sha256);
1340
+ });
1341
+ }
1317
1342
  function checkReceiptEvidenceValidForMode(receipt, mode) {
1318
1343
  try {
1344
+ if (mode === "diagnostic" && receipt.status !== "passed") {
1345
+ if (checkDiagnosisDisposition(receipt) !== "diagnosis_required")
1346
+ return false;
1347
+ // SQL selects this execution. Only its identity must survive in the
1348
+ // diagnostic packet; missing reports/logs cannot erase a failed command.
1349
+ const stored = JSON.parse(readBoundedReleaseFile(receipt.receipt_path, 1024 * 1024).toString("utf8"));
1350
+ return ["id", "scope", "work_id", "command", "input_hash", "verification_epoch", "status", "exit_code", "started_at", "finished_at"].every(field => (stored[field] ?? null) === (receipt[field] ?? null));
1351
+ }
1319
1352
  const staleDiagnostic = mode === "diagnostic" && receipt.fingerprint_scope === "check_inputs" && receipt.status === "failed" && Number.isInteger(receipt.exit_code)
1320
1353
  && receipt.before_fingerprint !== receipt.after_fingerprint && receipt.mutation_paths.length > 0 && receipt.execution_failures?.some(failure => failure.code === "check_inputs_changed");
1321
1354
  if (receipt.fingerprint_scope != null && receipt.fingerprint_scope !== "check_inputs")
@@ -1361,7 +1394,7 @@ function checkReceiptEvidenceValidForMode(receipt, mode) {
1361
1394
  // Receipt annotations are not execution authority. Compare the SQL-owned
1362
1395
  // failures with the independently hash/binding-checked engine sidecar.
1363
1396
  const hasEngineOutcome = receipt.required_artifacts.includes("engine-outcome.json") || receipt.artifacts.some(artifact => artifact.path === "engine-outcome.json" || artifact.path === path.join(path.dirname(receipt.receipt_path), "artifacts", "engine-outcome.json"));
1364
- if (hasEngineOutcome && (!isDeepStrictEqual(retainedProof.execution_failures ?? [], receipt.execution_failures ?? []) || !isDeepStrictEqual(retainedProof.candidate_absences ?? [], receipt.candidate_absences ?? [])))
1397
+ if (hasEngineOutcome && (!isDeepStrictEqual(retainedProof.execution_failures ?? [], receipt.execution_failures ?? []) || !isDeepStrictEqual(retainedProof.candidate_absences ?? [], receipt.candidate_absences ?? []) || !isDeepStrictEqual(retainedProof.candidate_library_inputs ?? [], receipt.candidate_library_inputs ?? [])))
1365
1398
  return false;
1366
1399
  const root = path.join(path.dirname(receipt.receipt_path), "artifacts");
1367
1400
  if (stored.runner_evidence_required && mode !== "diagnostic" && !runnerEvidencePass(readRunnerEvidence(root, stored.runner_evidence_required, { invocation_id: receipt.id, check_id: receipt.id, command: receipt.command, input_hash: receipt.input_hash, verification_epoch: receipt.verification_epoch })))
@@ -19,7 +19,10 @@ export async function verifyCodexHookDelivery(env, payload) {
19
19
  throw new AppError("native_hook_timeout", "Native hook deadline expired before delivery verification", 1, { phase: "verification", effect: "no_effect", recoverable: false });
20
20
  const proof = await callDaemon(env.DD_FLOW_CODEX_STATE_DIR, "hook.verify", {
21
21
  daemonId: env.DD_FLOW_DAEMON_ID, rootSessionId: payload.session_id,
22
- sessionId: payload.agent_id ?? payload.session_id, toolCallId: payload.tool_use_id, turnId: payload.turn_id
22
+ sessionId: payload.agent_id ?? payload.session_id, toolCallId: payload.tool_use_id, turnId: payload.turn_id,
23
+ // The daemon verifies the native delivery before retaining it; the source
24
+ // command cannot itself grant admission to the CLI.
25
+ nativePayload: payload, nativeCommand: command
23
26
  }, Math.min(5000, remaining));
24
27
  if (typeof proof.projectRoot !== "string"
25
28
  || typeof proof.ddFlowHome !== "string" || !env.DD_FLOW_HOME || path.resolve(env.DD_FLOW_HOME) !== path.resolve(proof.ddFlowHome)
@@ -27,6 +30,12 @@ export async function verifyCodexHookDelivery(env, payload) {
27
30
  || typeof proof.toolCallId !== "string" || !(proof.parentSessionId === null || typeof proof.parentSessionId === "string")) {
28
31
  throw new AppError("native_hook_unproven", "Native hook proof differs from the requested project/runtime", 1, { effect: "no_effect" });
29
32
  }
33
+ const binding = proof.nativeHookBinding;
34
+ const retained = binding && typeof binding === "object" && !Array.isArray(binding) ? binding : null;
30
35
  return { daemonId: proof.daemonId, rootSessionId: proof.rootSessionId, sessionId: proof.sessionId,
31
- parentSessionId: proof.parentSessionId, toolCallId: proof.toolCallId, projectRoot: proof.projectRoot, command: command };
36
+ parentSessionId: proof.parentSessionId, toolCallId: proof.toolCallId, projectRoot: proof.projectRoot, command: command,
37
+ ...(retained?.schema_id === "dd-flow/hook-request@1" && retained.source_harness === "codex-desktop"
38
+ && retained.daemon_id === proof.daemonId && retained.root_provider_session_id === proof.rootSessionId
39
+ && retained.provider_session_id === proof.sessionId && retained.tool_call_id === proof.toolCallId
40
+ && retained.native_command === command && typeof retained.request_id === "string" ? { nativeHookBinding: retained } : {}) };
32
41
  }
@@ -5,6 +5,7 @@ import { resolveExecutionPolicy, resolveStageTransition, stageExecutionCwd } fro
5
5
  import { readFlowRunExecutionBundle } from "./runs.js";
6
6
  import { hybridDigest } from "./hybrid-contracts.js";
7
7
  import { isDiagnosticFlow } from "../domain/diagnostic-flows.js";
8
+ import { isDeliveryFlow } from "../domain/delivery-flows.js";
8
9
  /** Adapt generic routing to shared fanout APIs, not to SDLC settings. No alias,
9
10
  * operator file, merge policy or project default participates in this selection.
10
11
  */
@@ -44,7 +45,9 @@ export function genericControllerRouting(execution, workspaceRoot) {
44
45
  * Legacy SDLC manifests retain their original settings and routing behavior.
45
46
  */
46
47
  export function selectControllerExecution(context, run, manifest) {
47
- if (manifest?.schema_id === "dd-flow/run-controller-manifest@2" || ["mb-init", "mb-upgrade"].includes(run.flow_kind) || isDiagnosticFlow(run.flow_kind)) {
48
+ const boundDistill = run.flow_kind === "mb-distill" && JSON.parse(run.index_json).execution_bundle?.flow_id === "mb-distill";
49
+ const boundDelivery = isDeliveryFlow(run.flow_kind) && JSON.parse(run.index_json).execution_bundle?.flow_id === run.flow_kind;
50
+ if (manifest?.schema_id === "dd-flow/run-controller-manifest@2" || ["mb-init", "mb-upgrade", "mb-fix", "review-fix", "interactive"].includes(run.flow_kind) || isDiagnosticFlow(run.flow_kind) || boundDistill || boundDelivery) {
48
51
  const bundle = readFlowRunExecutionBundle(context, { projectRoot: run.project_root, runId: run.id });
49
52
  if (!bundle || bundle.descriptor.descriptor.id !== run.flow_kind || bundle.descriptor.descriptor.id === "sdlc")
50
53
  throw new AppError("controller_execution_invalid", "Generic controller requires its retained non-SDLC bundle", 1);