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

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.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "cli_package": "@deksden-com/dd-flow-cli",
3
- "cli_version": "0.9.0-beta.151",
4
- "cli_commit": "89cad0d654601de59b12929240eee4e958b74207",
5
- "built_at": "2026-10-25T10:59:05.638Z",
3
+ "cli_version": "0.9.0-beta.152",
4
+ "cli_commit": "70ed6da341845d1b7f262b74f6731ac4787aff73",
5
+ "built_at": "2026-10-26T03:31:35.940Z",
6
6
  "built_with_canon": {
7
7
  "version": "4.1.2",
8
8
  "commit": "925ef10ecc0b683f7feba6f74913e15560842500",
@@ -14,7 +14,7 @@ import { durableDaemonDispatch, completeDaemonShutdown, commitDaemonShutdown, tr
14
14
  import { waitForSettlement } from "./session-settlement.mjs";
15
15
  import { nativeExecutionReceipt } from "./native-children.mjs";
16
16
  import { errorRecord, isProvenShellCorrection, reportedError } from "./operation-errors.mjs";
17
- import { checkObservedProfile, observeModel } from "./model-observations.mjs";
17
+ import { checkObservedProfile, observeModel, observedFields, readModelObservations } from "./model-observations.mjs";
18
18
  import os from "node:os";
19
19
  import path from "node:path";
20
20
  import { parse as parseJsonc } from "jsonc-parser";
@@ -202,6 +202,15 @@ export class Runtime {
202
202
  const record = { provider_session_id: id, adapter_session_id: id, parent_provider_session_id: parent, root_provider_session_id: root, cwd: info.directory };
203
203
  this.sessions.set(id, record); return record;
204
204
  }
205
+ registerObservedChild(info, root) {
206
+ const owner = this.sessions.get(root);
207
+ if (!owner || owner.parent_provider_session_id || owner.root_provider_session_id !== root
208
+ || info.id === root || info.parentID !== root || info.fork)
209
+ throw new DaemonError("session_identity_mismatch", "OpenCode child is not a direct member of the owned root");
210
+ if (typeof info.directory !== "string" || !path.isAbsolute(info.directory))
211
+ throw new OpenCodeError("opencode_unavailable", "OpenCode child directory binding is not yet observable");
212
+ return this.retainSession(info, root);
213
+ }
205
214
  async journal(operation, result) { const line = `${JSON.stringify({ schema_id: "dd-opencode/daemon-event@1", event_id: randomUUID(), observed_at: new Date().toISOString(), operation, active_tree: this.state.active_tree, session_id: result?.provider_session_id ?? null })}\n`; for (const journal of new Set([this.paths.journal, this.state.config.journal].filter(Boolean))) { await mkdir(path.dirname(journal), { recursive: true, mode: 0o700 }); await appendFile(journal, line, { mode: 0o600 }); } }
206
215
  async childTree(id, seen = new Set()) {
207
216
  if (seen.has(id)) throw new DaemonError("session_identity_mismatch", "OpenCode native topology contains a cycle");
@@ -299,17 +308,52 @@ export class Runtime {
299
308
  return nativeExecutionReceipt(receipt, { daemonId: this.state.daemon_id, revision: before.get(id) ?? 0 });
300
309
  }
301
310
  async observeModels(id, messages, parent = null) {
302
- this.modelMessages ??= new Set();
311
+ const journal = this.state.config.journal ?? this.paths.journal;
312
+ if (!this.modelMessages) {
313
+ this.modelMessages = new Map();
314
+ for (const event of journal ? await readModelObservations(journal) : []) {
315
+ if (event.harness !== 'opencode-server' || event.source?.method !== 'session/message' || !event.source.message_id) continue;
316
+ const key = `${event.session_id}:${event.source.message_id}`;
317
+ if (event.evidence_conflict) { const prior = this.modelMessages.get(key); if (prior) prior.conflicted = true; continue; }
318
+ if (!this.modelMessages.has(key)) this.modelMessages.set(key, { source: event.source,
319
+ known: { parent: event.parent_session_id, root: event.native_agent_role?.launch?.root_session_id,
320
+ role: event.native_agent_role ?? null, requested: observedFields(event.requested), observed: observedFields(event.observed) } });
321
+ }
322
+ }
303
323
  for (const message of messages ?? []) {
304
324
  const info = message.info;
305
- if (info?.role !== "assistant" || !info.id || this.modelMessages.has(info.id)) continue;
325
+ if (info?.role !== "assistant" || !info.id) continue;
306
326
  if (info.sessionID && info.sessionID !== id) throw new DaemonError("session_identity_mismatch", "native message belongs to another Session");
307
- await observeModel({ journal: this.state.config.journal ?? this.paths.journal, harness: "opencode-server", sessionId: id, parentSessionId: parent,
308
- requested: { ...this.state.config, reasoning: this.state.config.variant, mode: parent ? "dd-flow-worker" : this.state.config.agent },
309
- nativeAgentRole: this.state.config.workerLaunchConfig ? { name: info.mode ?? null, kind: parent ? 'subagent' : 'primary', parent_session_id: parent,
310
- launch: { ...this.state.config.workerLaunchConfig, root_session_id: this.sessions.get(id)?.root_provider_session_id ?? null } } : null,
311
- observed: { provider: info.providerID, model: info.modelID, reasoning: info.variant, mode: info.mode }, source: { method: "session/message", message_id: info.id, event_sha256: createHash("sha256").update(`${id}:${info.id}:${info.modelID}`).digest("hex") }, evidence: "response" });
312
- this.modelMessages.add(info.id);
327
+ const record = this.sessions.get(id);
328
+ if (record && (record.parent_provider_session_id ?? null) !== parent)
329
+ throw new DaemonError("session_identity_mismatch", "OpenCode observed message parent changed");
330
+ if (!record?.root_provider_session_id || !info.mode) {
331
+ await observeModel({ journal, harness: "opencode-server", sessionId: id, parentSessionId: parent,
332
+ source: { channel: "native.message_observer", scope: "response", non_asserting: true }, evidence: "unavailable", reason: "native_role_binding_unavailable" });
333
+ continue;
334
+ }
335
+ const requested = { ...this.state.config, reasoning: this.state.config.variant, mode: parent ? "dd-flow-worker" : this.state.config.agent };
336
+ const nativeAgentRole = this.state.config.workerLaunchConfig ? { name: info.mode, kind: parent ? 'subagent' : 'primary', parent_session_id: parent,
337
+ launch: { ...this.state.config.workerLaunchConfig, root_session_id: record.root_provider_session_id } } : null;
338
+ const observed = observedFields({ provider: info.providerID, model: info.modelID, reasoning: info.variant, mode: info.mode });
339
+ const tuple = { parent, root: record.root_provider_session_id, role: nativeAgentRole, requested: observedFields(requested), observed };
340
+ const key = `${id}:${info.id}`, previous = this.modelMessages.get(key);
341
+ if (previous?.conflicted) throw new DaemonError("model_observation_identity_conflict", "OpenCode native message has a retained identity/profile conflict", false, { session_id: id, message_id: info.id });
342
+ const conflicts = (before, after) => before != null && after != null && (typeof before === "object" && typeof after === "object"
343
+ ? [...new Set([...Object.keys(before), ...Object.keys(after)])].some(field => conflicts(before[field], after[field])) : before !== after);
344
+ if (previous && !conflicts(previous.known, tuple)) {
345
+ // Optional disclosure can arrive late. Do not rewrite the old native fact;
346
+ // remember known values only to detect a later positive contradiction.
347
+ for (const [field, value] of Object.entries(observed)) if (value != null) previous.known.observed[field] = value;
348
+ continue;
349
+ }
350
+ const source = previous?.source ?? { method: "session/message", message_id: info.id, event_sha256: createHash("sha256").update(`${id}:${info.id}`).digest("hex") };
351
+ const published = await observeModel({ journal, harness: "opencode-server", sessionId: id, parentSessionId: parent, requested, nativeAgentRole, observed, source, evidence: "response" });
352
+ if (previous || published?.evidence_conflict) {
353
+ if (previous) previous.conflicted = true;
354
+ throw new DaemonError("model_observation_identity_conflict", "OpenCode native message changed its published identity/profile", false, { session_id: id, message_id: info.id });
355
+ }
356
+ this.modelMessages.set(key, { known: structuredClone(tuple), source });
313
357
  }
314
358
  }
315
359
  async requireSettled(id) {
@@ -462,6 +506,9 @@ export class Runtime {
462
506
  assertDispatch, checkHookFailures, livenessTimeoutMs: params.livenessTimeoutMs ?? this.client.timeoutMs,
463
507
  onDispatched: () => this.journal("native_dispatch", { operation_id: operationId, provider_session_id: params.sessionId }),
464
508
  onModelMessages: (id, messages, parent) => this.observeModels(id, messages, parent),
509
+ onNativeSession: (info, root) => this.registerObservedChild(info, root),
510
+ rootSessionId: this.sessions.get(params.sessionId)?.root_provider_session_id ?? params.sessionId,
511
+ parentSessionId: this.sessions.get(params.sessionId)?.parent_provider_session_id ?? null,
465
512
  onObservationError: error => observeModel({ journal: this.state.config.journal ?? this.paths.journal, harness: "opencode-server", sessionId: params.sessionId, requested: this.state.config, source: "native.message_observer", evidence: "unavailable", reason: error.code ?? "observation_unavailable" })
466
513
  }); await checkHookFailures(); }
467
514
  finally { current.requests.clear(); if (this.nativeHookOperation === current) this.nativeHookOperation = null; }
@@ -169,13 +169,32 @@ export class OpenCodeClient {
169
169
  import(data) { return this.call("session.import", data, { productive: true }); }
170
170
 
171
171
  async prompt(id, { prompt, messageId = `msg_${randomUUID().replaceAll("-", "")}`, retained,
172
- save, onModelMessages, onObservationError, assertDispatch, onDispatched, checkHookFailures, livenessTimeoutMs = this.timeoutMs }) {
172
+ save, onModelMessages, onNativeSession, rootSessionId = id, parentSessionId = null,
173
+ onObservationError, assertDispatch, onDispatched, checkHookFailures, livenessTimeoutMs = this.timeoutMs }) {
173
174
  validateObservationDuration(livenessTimeoutMs);
174
175
  const request = { sessionID: id, id: messageId, text: prompt, delivery: "queue", resume: true }, digest = requestDigest(request);
175
176
  if (retained && (retained.request_sha256 !== digest || retained.message_id !== messageId)) throw new OpenCodeError("operation_request_mismatch", "OpenCode operation already binds another prompt", false, { effect: "no_effect" });
176
177
  let state = retained ?? { message_id: messageId, request_sha256: digest, phase: "prepared", cursor: 0 };
177
- const previous = await this.messages(id), baseline = new Map([[id, new Set(previous.map(item => item.info.id))]]), parents = new Map();
178
- const capture = async session => { for (const child of await this.children(session)) { parents.set(child.id, child.parentID); baseline.set(child.id, new Set((await this.messages(child.id)).map(item => item.info.id))); await capture(child.id); } };
178
+ const previous = await this.messages(id), baseline = new Map([[id, new Set(previous.map(item => item.info.id))]]), parents = new Map([[id, parentSessionId]]);
179
+ // The same native lineage guard applies before dispatch and while polling.
180
+ // Registration precedes message publication; a list ID alone is not ownership.
181
+ const childrenOf = async (session, visited) => {
182
+ const children = await this.children(session);
183
+ for (const child of children) {
184
+ if (!child?.id || child.parentID !== session || child.fork || session !== rootSessionId || visited.has(child.id)
185
+ || parents.has(child.id) && parents.get(child.id) !== session || visited.size >= 1024)
186
+ throw new OpenCodeError("session_identity_mismatch", "OpenCode native child lineage is invalid", false, { session_id: child?.id, parent_session_id: session });
187
+ visited.add(child.id); parents.set(child.id, session);
188
+ await onNativeSession?.(child, rootSessionId);
189
+ }
190
+ return children;
191
+ };
192
+ const capture = async (session, visited = new Set([id])) => {
193
+ for (const child of await childrenOf(session, visited)) {
194
+ baseline.set(child.id, new Set((await this.messages(child.id)).map(item => item.info.id)));
195
+ await capture(child.id, visited);
196
+ }
197
+ };
179
198
  await capture(id);
180
199
  if (retained) {
181
200
  const index = previous.findIndex(item => item.info.id === messageId);
@@ -258,8 +277,8 @@ export class OpenCodeClient {
258
277
  }
259
278
  if (state.cursor !== watermark) { state = { ...state, cursor: watermark }; await save?.(state); }
260
279
  let runningTools = false;
261
- const observe = async (session, visited = new Set()) => {
262
- if (visited.has(session)) throw lost("OpenCode topology contains a cycle"); visited.add(session); known.add(session);
280
+ const observe = async (session, visited = new Set([id])) => {
281
+ known.add(session);
263
282
  const messages = await this.messages(session);
264
283
  await onModelMessages?.(session, messages, parents.get(session) ?? null);
265
284
  const floor = baseline.get(session) ?? new Set(), current = messages.filter(item => item.info.role === "assistant" && !floor.has(item.info.id));
@@ -291,10 +310,7 @@ export class OpenCodeClient {
291
310
  }
292
311
  }
293
312
  }
294
- for (const child of await this.children(session)) {
295
- if (child.parentID !== session) throw lost("OpenCode child has a foreign parent");
296
- parents.set(child.id, session); await observe(child.id, visited);
297
- }
313
+ for (const child of await childrenOf(session, visited)) await observe(child.id, visited);
298
314
  return current;
299
315
  };
300
316
  const current = await observe(id), rootMessages = await this.messages(id);
@@ -309,8 +325,9 @@ export class OpenCodeClient {
309
325
  // Native messages and topology are separate reads: a child can
310
326
  // finish between them. Reconcile its ancestry before rejecting
311
327
  // the notification, never trust the notification alone.
312
- const child = (await this.children(id)).find(child => child.id === item.info.metadata.childID && child.parentID === id);
313
- if (child) { parents.set(child.id, id); await observe(child.id); }
328
+ const visited = new Set([id]);
329
+ const child = (await childrenOf(id, visited)).find(child => child.id === item.info.metadata.childID);
330
+ if (child) await observe(child.id, visited);
314
331
  }
315
332
  if (item.info.role === "user" || item.info.role === "synthetic" && !(
316
333
  item.info.metadata?.source === "subagent" && parents.get(item.info.metadata.childID) === id
@@ -353,7 +370,8 @@ export class OpenCodeClient {
353
370
  }
354
371
  } catch (error) {
355
372
  await checkHookFailures?.();
356
- if (error instanceof OpenCodeError && !["opencode_unavailable", "operation_observation_lost"].includes(error.code)) throw error;
373
+ if (error instanceof OpenCodeError && !["opencode_unavailable", "operation_observation_lost"].includes(error.code)
374
+ || ["session_identity_mismatch", "model_observation_identity_conflict"].includes(error.code)) throw error;
357
375
  await onObservationError?.(error);
358
376
  }
359
377
  if (clock.sample(marker)) throw lost("OpenCode native progress stopped or observation could not recover", { session_id: id, message_id: messageId, observation: clock.state(), ...(observationErrors.count ? { observation_errors: observationErrors } : {}) });
@@ -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);
@@ -266,12 +293,15 @@ export function checkRepairDisposition(receipt) {
266
293
  return "tooling_blocker";
267
294
  if (!evidence.completed)
268
295
  return "unclassified";
269
- if (evidence.failures.some(f => !runnerFailureValid(f) || !candidateFailure(f, evidence, receipt.candidate_root, receipt.candidate_files, receipt.candidate_absences)))
296
+ if (evidence.failures.some(f => !runnerFailureValid(f) || !candidateFailure(f, evidence, receipt.candidate_root, receipt.candidate_files, receipt.candidate_absences, receipt.candidate_library_inputs)))
270
297
  return "unclassified";
271
298
  return "source_repair";
272
299
  }
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." });
300
+ export function assertSourceRepairEligible(receipts, workspaceRoot) {
301
+ const dispositions = receipts.map(receipt => {
302
+ const drift = workspaceRoot !== undefined && !currentCandidateLibraryInputsValid(receipt, workspaceRoot);
303
+ return { check_receipt_id: receipt.id, disposition: drift ? "tooling_blocker" : checkRepairDisposition(receipt), ...(drift ? { reason: "candidate_library_inputs_changed" } : {}) };
304
+ });
305
+ if (!receipts.length || dispositions.some(item => item.disposition !== "source_repair"))
306
+ 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, 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
307
  }
@@ -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);
@@ -1,5 +1,5 @@
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";
2
+ import { candidateAbsencesValid, candidateFailureAbsences, candidateFailureFiles, candidateLibraryInputs, candidateLibraryInputsValid, currentCandidateLibraryInputsValid, checkRepairDisposition, readRunnerEvidence, readRunnerEvidenceDetailed, readRunnerProgress, runnerContractsValid, runnerEvidencePass, runnerObservationDurations, runnerProofArtifacts } from "./check-runner-evidence.js";
3
3
  export { assertSourceRepairEligible, 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";
@@ -352,7 +352,7 @@ 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) || !currentCandidateLibraryInputsValid(receipt, input.workspaceRoot))
356
356
  fail();
357
357
  passed = passed && receipt.status === "passed";
358
358
  for (const artifact of receipt.artifacts)
@@ -666,8 +666,9 @@ async function runCodeChecksOwned(context, input, workspaceGateId, generation, s
666
666
  physicalFailures.push(result.failure);
667
667
  const candidateFiles = candidateFailureFiles(runnerEvidence, input.workspaceRoot);
668
668
  const candidateAbsences = candidateFailureAbsences(runnerEvidence, input.workspaceRoot);
669
+ const libraryInputs = candidateLibraryInputs(runnerEvidence, input.workspaceRoot);
669
670
  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);
671
+ 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
672
  const requiredArtifacts = [...new Set([...group.requiredArtifacts, ...runnerProofArtifacts(runnerEvidence), ...(group.runnerEvidence ? ["engine-outcome.json"] : [])])];
672
673
  artifacts = collectCheckArtifacts(evidenceDir, requiredArtifacts);
673
674
  const status = result.aborted ? "aborted" : result.exitCode === 0 && !result.error && artifacts.complete && mutationPaths.length === 0 && (!group.runnerEvidence || runnerEvidencePass(runnerEvidence)) ? "passed" : "failed";
@@ -676,6 +677,7 @@ async function runCodeChecksOwned(context, input, workspaceGateId, generation, s
676
677
  receipt.candidate_root = input.workspaceRoot;
677
678
  receipt.candidate_files = candidateFiles;
678
679
  receipt.candidate_absences = candidateAbsences;
680
+ receipt.candidate_library_inputs = libraryInputs;
679
681
  receipt.execution_failures = physicalFailures;
680
682
  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
683
  const completion = readCompletion(completionPath);
@@ -768,6 +770,7 @@ export function reconcileUnfinishedChecks(context, input) {
768
770
  physicalFailures.push({ code: "completion_snapshot_unknown", message: "Detached check has no bound scoped completion snapshot; current inputs cannot prove inputs at command completion" });
769
771
  let candidateFiles = candidateFailureFiles(runnerEvidence, input.workspaceRoot);
770
772
  let candidateAbsences = candidateFailureAbsences(runnerEvidence, input.workspaceRoot);
773
+ let libraryInputs = candidateLibraryInputs(runnerEvidence, input.workspaceRoot);
771
774
  if (receipt?.runner_evidence_required) {
772
775
  const evidenceDir = path.join(path.dirname(pendingReceipt.receipt_path), "artifacts");
773
776
  if (fs.existsSync(path.join(evidenceDir, "engine-outcome.json"))) {
@@ -778,13 +781,14 @@ export function reconcileUnfinishedChecks(context, input) {
778
781
  physicalFailures.push(...(proven.execution_failures ?? []));
779
782
  candidateFiles = proven.candidate_files ?? candidateFiles;
780
783
  candidateAbsences = proven.candidate_absences ?? [];
784
+ libraryInputs = proven.candidate_library_inputs ?? [];
781
785
  }
782
786
  else {
783
787
  physicalFailures.push({ code: "engine_outcome_unbound", message: "Existing execution outcome is not bound to retained inventory; prior launcher/cleanup outcome is unknown" });
784
788
  fs.renameSync(path.join(evidenceDir, "engine-outcome.json"), path.join(evidenceDir, `engine-outcome-unconfirmed-${crypto.randomUUID()}.json`));
785
789
  }
786
790
  }
787
- retainExecutionOutcome(evidenceDir, receipt, input.workspaceRoot, physicalFailures, candidateFiles, candidateAbsences);
791
+ retainExecutionOutcome(evidenceDir, receipt, input.workspaceRoot, physicalFailures, candidateFiles, candidateAbsences, libraryInputs);
788
792
  receipt.required_artifacts = [...new Set([...(receipt.required_artifacts ?? []), ...runnerProofArtifacts(runnerEvidence), "engine-outcome.json"])];
789
793
  artifacts = collectCheckArtifacts(evidenceDir, receipt.required_artifacts);
790
794
  }
@@ -798,6 +802,7 @@ export function reconcileUnfinishedChecks(context, input) {
798
802
  repaired.candidate_root = input.workspaceRoot;
799
803
  repaired.candidate_files = candidateFiles;
800
804
  repaired.candidate_absences = candidateAbsences;
805
+ repaired.candidate_library_inputs = libraryInputs;
801
806
  repaired.execution_failures = physicalFailures;
802
807
  writeReceipt(pendingReceipt.receipt_path, repaired);
803
808
  if (status === "failed")
@@ -824,6 +829,11 @@ function reserveOrReuseReceipt(context, input) {
824
829
  const sourceCorrection = Boolean(retryPredecessor && (sourceRepairContractMatches(retryPredecessor, input.group, input.profileHash)
825
830
  || input.retryReason === "check_inputs_changed" && staleRetryContractMatches(retryPredecessor, input.group, input.profileHash, input)));
826
831
  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);
832
+ // Hash files before SQLite's writer reservation. Captured identities remain
833
+ // valid historical evidence, but cannot authorize reuse in a changed install.
834
+ const priorLibrariesCurrent = !prior || currentCandidateLibraryInputsValid(prior, input.workspaceRoot);
835
+ const latestLibrariesCurrent = !latestContract || currentCandidateLibraryInputsValid(latestContract, input.workspaceRoot);
836
+ const libraryDrift = (receipt) => { throw new AppError("check_evidence_stale", "Captured library inputs differ from the current candidate installation; request an explicit restored-condition retry", 2, { reason: "candidate_library_inputs_changed", check_receipt_id: receipt.id, effect: "no_effect" }); };
827
837
  return context.db.writeTransaction(() => {
828
838
  assertRunMutationAllowed(context, input.projectId, input.runId, input.generation);
829
839
  if (JSON.stringify(context.db.all(snapshotSql, [input.projectId, input.runId])) !== snapshot)
@@ -859,10 +869,16 @@ function reserveOrReuseReceipt(context, input) {
859
869
  // An unchanged failed contract needs a concrete restored-condition retry,
860
870
  // not repeated coordinator finish until it happens to become green.
861
871
  if (!retryId && latestContract?.fingerprint_scope === "check_inputs" && latestContract.status === "failed"
862
- && latestContract.execution_failures?.some(failure => failure.code === "check_inputs_changed") && latestContract.execution_contract_hash === input.executionContractHash)
872
+ && latestContract.execution_failures?.some(failure => failure.code === "check_inputs_changed") && latestContract.execution_contract_hash === input.executionContractHash) {
873
+ if (!latestLibrariesCurrent)
874
+ libraryDrift(latestContract);
863
875
  return { receipt: latestContract };
864
- if (prior && ["failed", "aborted"].includes(prior.status) && !retryId)
876
+ }
877
+ if (prior && ["failed", "aborted"].includes(prior.status) && !retryId) {
878
+ if (!priorLibrariesCurrent)
879
+ libraryDrift(prior);
865
880
  return { receipt: prior };
881
+ }
866
882
  if (prior?.status === "passed" && !retryIds.has(prior.id) && input.group.reusable && reusablePrior)
867
883
  return { receipt: prior };
868
884
  let ordinal = (context.db.get("SELECT COALESCE(MAX(reservation_sequence), 0) + 1 AS sequence FROM check_receipts WHERE project_id = ? AND run_id = ?", [input.projectId, input.runId])?.sequence ?? 1);
@@ -1004,12 +1020,12 @@ function resolvedReceiptArtifacts(receiptPath, artifacts, binding) {
1004
1020
  if (retainedOutcome.complete && retainedOutcome.items.every(item => artifacts.some(artifact => relocate(artifact.path) === item.path && artifact.sha256 === item.sha256))) {
1005
1021
  try {
1006
1022
  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"))
1023
+ 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
1024
  outcome = value;
1009
1025
  }
1010
1026
  catch { /* Missing physical proof cannot grant attribution. */ }
1011
1027
  }
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 } : {}) };
1028
+ 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
1029
  }
1014
1030
  function executionFailures(input) {
1015
1031
  const failures = [];
@@ -1027,11 +1043,11 @@ function executionFailures(input) {
1027
1043
  failures.push({ code: "invalid_runner_evidence", message: "Required runner evidence is unavailable or invalid" });
1028
1044
  return failures;
1029
1045
  }
1030
- function retainExecutionOutcome(evidenceDir, binding, candidateRoot, failures, candidateFiles, candidateAbsences = []) {
1046
+ function retainExecutionOutcome(evidenceDir, binding, candidateRoot, failures, candidateFiles, candidateAbsences = [], libraryInputs = []) {
1031
1047
  const temporary = path.join(evidenceDir, `.engine-outcome-${crypto.randomUUID()}.tmp`);
1032
1048
  if (!fs.lstatSync(evidenceDir).isDirectory() || fs.lstatSync(evidenceDir).isSymbolicLink())
1033
1049
  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 });
1050
+ 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
1051
  fs.renameSync(temporary, path.join(evidenceDir, "engine-outcome.json"));
1036
1052
  }
1037
1053
  export function workspaceFingerprint(root) { return workspaceState(root).fingerprint; }
@@ -1361,7 +1377,7 @@ function checkReceiptEvidenceValidForMode(receipt, mode) {
1361
1377
  // Receipt annotations are not execution authority. Compare the SQL-owned
1362
1378
  // failures with the independently hash/binding-checked engine sidecar.
1363
1379
  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 ?? [])))
1380
+ 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
1381
  return false;
1366
1382
  const root = path.join(path.dirname(receipt.receipt_path), "artifacts");
1367
1383
  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 })))
@@ -7,7 +7,10 @@ import { assertSourceRepairEligible } from "./code-checks.js";
7
7
  export function ensureAggregateRepair(context, input, create) {
8
8
  if (!input.failures.length)
9
9
  throw new AppError("repair_intent_invalid", "Repair requires candidate-source-backed failed receipts", 1);
10
- assertSourceRepairEligible(input.failures);
10
+ const workspace = context.db.get("SELECT workspace_root FROM runs WHERE project_id=? AND id=?", [input.projectId, input.runId]);
11
+ if (!workspace?.workspace_root && input.failures.some(receipt => receipt.candidate_library_inputs?.length))
12
+ throw new AppError("repair_intent_invalid", "Library-backed repair requires the current RUN workspace", 1);
13
+ assertSourceRepairEligible(input.failures, workspace?.workspace_root);
11
14
  const causes = input.failures.map(receipt => ({ id: receipt.id, epoch: receipt.verification_epoch, fingerprint: receipt.fingerprint_scope === "check_inputs" ? receipt.before_fingerprint : receipt.workspace_fingerprint })).sort((a, b) => a.id.localeCompare(b.id));
12
15
  return ensureRepairIntent(context, { ...input, causes: { receipts: causes, origins: [...(input.origins ?? [])].sort() } }, create);
13
16
  }
@@ -253,7 +253,7 @@ export async function finishVnextCodeReview(context, input) {
253
253
  const matrix = prepareRunVerificationMatrix(context, { projectId: project.id, runId: run.id, stage, role: "output", receipts, semanticFile: decisionFile });
254
254
  const failed = receipts.filter((receipt) => receipt.status !== "passed");
255
255
  if (failed.length) {
256
- assertSourceRepairEligible(failed);
256
+ assertSourceRepairEligible(failed, run.workspace_root);
257
257
  context.db.writeTransaction(() => acceptVerificationMatrix(context, matrix));
258
258
  publishVerificationMatrix(context, matrix);
259
259
  throw new AppError("code_review_gate_failed", "CODE-REVIEW repair changed the project but the aggregate gate failed", 2, {
@@ -225,7 +225,7 @@ export async function finishVnextCode(context, input) {
225
225
  const matrix = prepareRunVerificationMatrix(context, { projectId: project.id, runId: run.id, stage, role: "output", receipts, semanticFile: input.verificationFile });
226
226
  const matrixInstruction = matrix ? verificationMatrixInstruction(matrix.metadata, home, "semantic verification in code-verification.json") : "";
227
227
  if (failed.length) {
228
- assertSourceRepairEligible(failed);
228
+ assertSourceRepairEligible(failed, run.workspace_root);
229
229
  context.db.writeTransaction(() => acceptVerificationMatrix(context, matrix));
230
230
  publishVerificationMatrix(context, matrix);
231
231
  const continuation = registerAggregateCodeRepair(context, { projectRoot, runId: run.id, stage, failures: failed, checks, generation });
@@ -408,8 +408,9 @@ export function prepareVnextCodeRepair(context, input) {
408
408
  check_receipt_id: input.checkReceiptId
409
409
  });
410
410
  }
411
+ // Aggregate registration validated current inputs before its SQL transaction.
411
412
  if (input.checkReceiptId)
412
- assertSourceRepairEligible(checkReceipts(context, { projectId: project.id, runId: run.id }).filter(item => item.id === input.checkReceiptId));
413
+ assertSourceRepairEligible(checkReceipts(context, { projectId: project.id, runId: run.id }).filter(item => item.id === input.checkReceiptId), input.registrationOwned ? undefined : run.workspace_root);
413
414
  const origins = [...new Set(input.originWorkIds)].map((id) => requireCodeWork(context, project.id, run.id, id));
414
415
  const packets = origins.map((work) => packet(work));
415
416
  const invariantPackets = receipt || input.semanticUnresolved?.length
@@ -479,7 +479,7 @@ export async function repairVnextMerge(context, input) {
479
479
  if (request.project_id !== project.id || request.status !== "action_required" || errorCode(request) !== "merge_gate_failed") {
480
480
  throw new AppError("invalid_merge_repair_state", "MERGE source repair requires an action-required failed integration gate", 2, { merge_request_id: input.requestId, status: request.status, code: errorCode(request) });
481
481
  }
482
- assertSourceRepairEligible(checkReceipts(context, { projectId: project.id, runId: request.run_id }).filter(receipt => failedReceiptIds(request).includes(receipt.id)));
482
+ assertSourceRepairEligible(checkReceipts(context, { projectId: project.id, runId: request.run_id }).filter(receipt => failedReceiptIds(request).includes(receipt.id)), request.target_workspace);
483
483
  abortIntegration(request);
484
484
  const originWorkIds = sourceCodeWorkIds(context, request.project_id, request.run_id);
485
485
  if (!originWorkIds.length)
@@ -1564,7 +1564,7 @@ async function settle(context, id, status, result, progress, retry, memoryResult
1564
1564
  receipts = await runCodeChecks(context, { projectId: work.project_id, runId: work.run_id, runHome: requireRunHome(run), workspaceRoot: run.workspace_root, workId: work.work_id, artifactDir, scope: "work", checks: requiredChecks, ...(retry ? { retryReceiptId: retry.receiptId, retryReason: retry.reason } : {}), ...(progress ? { progress } : {}) });
1565
1565
  const failed = receipts.filter((receipt) => receipt.status !== "passed");
1566
1566
  if (failed.length)
1567
- assertSourceRepairEligible(failed);
1567
+ assertSourceRepairEligible(failed, run.workspace_root);
1568
1568
  if (failed.length)
1569
1569
  throw new AppError("work_checks_failed", "Work remains running because required checks failed", 2, { work_id: id, failures: failed, all_receipts: receipts });
1570
1570
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deksden-com/dd-flow-cli",
3
- "version": "0.9.0-beta.151",
3
+ "version": "0.9.0-beta.152",
4
4
  "description": "Mechanical runtime CLI for dd-flow workflows.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.23.0",
@@ -30,7 +30,7 @@
30
30
  "test": "node scripts/verification.mjs run",
31
31
  "gate:merge": "node scripts/verification.mjs run",
32
32
  "verify:plan": "node scripts/verification.mjs plan",
33
- "test:verification": "node --test --test-concurrency=1 scripts/verification-policy.test.mjs scripts/generate-verification-components.test.mjs",
33
+ "test:verification": "node --test --test-concurrency=1 scripts/verification-policy.test.mjs scripts/generate-verification-components.test.mjs scripts/verification-jobs.test.mjs",
34
34
  "test:hybrid": "node --import ./scripts/source-test-loader.mjs --test --test-concurrency=1 scripts/hybrid-contracts.test.mjs",
35
35
  "test:hybrid:canon": "node --import ./scripts/source-test-loader.mjs --test --test-concurrency=1 scripts/hybrid-canon.test.mjs scripts/hybrid-inventory.test.mjs scripts/hybrid-consumers.test.mjs scripts/hybrid-schema-copies.test.mjs scripts/verify-e3-canon.test.mjs",
36
36
  "test:e2": "node --import ./scripts/source-test-loader.mjs --test --test-concurrency=1 scripts/e2-*.test.mjs",
@@ -0,0 +1,317 @@
1
+ // SSOT for the cooperating verification entrypoints. Standalone consumers vendor
2
+ // this file byte-for-byte; contract catalogs and report validators stay local.
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { randomUUID } from 'node:crypto';
7
+ import { spawn, execFileSync } from 'node:child_process';
8
+ import { setTimeout as delay } from 'node:timers/promises';
9
+ import { setTimeout, clearTimeout } from 'node:timers';
10
+ import process from 'node:process';
11
+
12
+ const realHome = os.userInfo().homedir; // OS identity, never a fixture's HOME.
13
+ const providerCredentials = ['OPENAI_API_KEY','OPENROUTER_API_KEY','OPENAI_DECISIONS_API_KEY','ANTHROPIC_API_KEY','GEMINI_API_KEY','GOOGLE_API_KEY','XAI_API_KEY','ZAI_API_KEY','NODE_AUTH_TOKEN','NPM_TOKEN','NPM_ACCESS_TOKEN'];
14
+ const read = file => JSON.parse(fs.readFileSync(file, 'utf8'));
15
+ const optional = file => { try { return read(file); } catch (e) { if (e.code === 'ENOENT') return null; throw e; } };
16
+ const blocked = message => Object.assign(new Error(message), { code: 'verification_ownership_unknown' });
17
+ export function atomicJson(file, value) {
18
+ fs.mkdirSync(path.dirname(file), { recursive: true });
19
+ const temporary = `${file}.${randomUUID()}.tmp`;
20
+ try { fs.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { flag: 'wx' }); fs.renameSync(temporary, file); }
21
+ catch (error) {
22
+ try { fs.unlinkSync(temporary); } catch (cleanup) { if (cleanup.code !== 'ENOENT') error.cleanup_error = cleanup; }
23
+ throw error;
24
+ }
25
+ }
26
+
27
+ // Same physical identity fields as EVAL's process-snapshot helper. Observation
28
+ // failure is never interpreted as an empty process table.
29
+ function snapshot() {
30
+ return execFileSync('ps', ['-axo', 'pid=,ppid=,pgid=,lstart=,stat='], {
31
+ encoding: 'utf8', timeout: 5000, env: { PATH: process.env.PATH, LC_ALL: 'C', TZ: 'UTC' },
32
+ }).trim().split('\n').flatMap(line => {
33
+ const p = line.trim().split(/\s+/);
34
+ return p.length >= 9 ? [{ pid: +p[0], ppid: +p[1], pgid: +p[2], birth: p.slice(3, 8).join(' '), zombie: p[8].startsWith('Z') }] : [];
35
+ });
36
+ }
37
+ const same = (a, b) => a && b && a.pid === b.pid && a.birth === b.birth && !b.zombie;
38
+ function self() {
39
+ const identity = snapshot().find(p => p.pid === process.pid && !p.zombie);
40
+ if (!identity) throw blocked('Cannot establish verification owner identity');
41
+ return identity;
42
+ }
43
+ function alive(owner, processes = snapshot()) { return same(owner, processes.find(p => p.pid === owner?.pid)); }
44
+ function release(lock, owner) {
45
+ // Only one reclaimer can remove this unique fence. A late reclaimer cannot
46
+ // unlink the next owner's record after the directory has been reacquired.
47
+ fs.unlinkSync(path.join(lock, `fence-${owner.token}`));
48
+ fs.unlinkSync(path.join(lock, 'owner.json')); fs.rmdirSync(lock);
49
+ }
50
+ async function claim(lock, signal) {
51
+ fs.mkdirSync(path.dirname(lock), { recursive: true });
52
+ const identity = self();
53
+ for (;;) {
54
+ signal?.throwIfAborted();
55
+ try {
56
+ fs.mkdirSync(lock);
57
+ const owner = { ...identity, token: randomUUID(), groups: [] };
58
+ fs.writeFileSync(path.join(lock, `fence-${owner.token}`), '', { flag: 'wx' });
59
+ atomicJson(path.join(lock, 'owner.json'), owner);
60
+ return owner;
61
+ } catch (e) { if (e.code !== 'EEXIST') throw e; }
62
+ const owner = optional(path.join(lock, 'owner.json'));
63
+ if (owner) {
64
+ const processes = snapshot();
65
+ if (!alive(owner, processes)) {
66
+ // A dead coordinator is NOT cleanup. A surviving process group keeps
67
+ // the whole lease until its physical absence is positively observed.
68
+ if (!Array.isArray(owner.groups)) throw blocked(`Unconfirmed lease groups: ${lock}`);
69
+ if (!owner.groups.some(g => !g.pgid || processes.some(p => p.pgid === g.pgid && !p.zombie))) {
70
+ // Recheck the immutable owner token before removing its record.
71
+ if (optional(path.join(lock, 'owner.json'))?.token === owner.token) {
72
+ try { release(lock, owner); } catch (e) { if (!['ENOENT', 'ENOTEMPTY'].includes(e.code)) throw e; }
73
+ }
74
+ continue;
75
+ }
76
+ }
77
+ }
78
+ await delay(50, undefined, { signal });
79
+ }
80
+ }
81
+
82
+ /** One immutable producer attempt. The index is scheduling state, not proof;
83
+ * callers must validate the returned report bytes and meaningful inputs. */
84
+ export async function withContractAttempt(index, { rerun = false, reason, signal, validate, validationKey } = {}, action) {
85
+ if (rerun && !reason) throw new Error('Explicit rerun requires a reason');
86
+ const lock = `${index}.claim`;
87
+ const initial = optional(index);
88
+ if (initial?.status === 'running' && !rerun) {
89
+ const result = await joinAttempt(initial, signal);
90
+ await validate?.(result.result, initial);
91
+ signal?.throwIfAborted();
92
+ return result;
93
+ }
94
+ let joined = null;
95
+ const owner = await claim(lock, signal);
96
+ let attempt;
97
+ try {
98
+ const current = optional(index); // Mandatory reread under claim.
99
+ if (current?.status === 'running' && !rerun) { joined = current; }
100
+ else if (current && !rerun) {
101
+ if (current.status !== 'passed') throw blocked(`Retained ${current.status} attempt requires explicit rerun: ${index}`);
102
+ try {
103
+ const terminal = read(current.report);
104
+ if (terminal.attemptId !== current.attemptId || terminal.status !== 'passed') throw new Error('Invalid terminal attempt');
105
+ await validate?.(terminal.result, current);
106
+ signal?.throwIfAborted();
107
+ return { ...terminal, reused: true, joined: false };
108
+ } catch (error) {
109
+ signal?.throwIfAborted(); // A cancelled reader never invalidates its producer.
110
+ atomicJson(index, { ...current, status: 'evidence_invalid', reason: String(error.message) });
111
+ reason = `Invalid evidence: ${error.message}`;
112
+ }
113
+ }
114
+ if (!joined) {
115
+ if (current?.status === 'running') throw blocked('Cannot supersede an active producer');
116
+ const attemptId = randomUUID();
117
+ const root = path.join(`${index}.attempts`, attemptId);
118
+ fs.mkdirSync(root, { recursive: true });
119
+ attempt = { attemptId, root, report: path.join(root, 'terminal.json'), owner, status: 'running', reason: reason ?? null, validationKey };
120
+ // Accepted rerun supersedes an old PASS before dispatch.
121
+ atomicJson(index, attempt);
122
+ }
123
+ } finally { release(lock, owner); }
124
+ if (joined) {
125
+ const result = await joinAttempt(joined, signal);
126
+ await validate?.(result.result, joined);
127
+ signal?.throwIfAborted();
128
+ return result;
129
+ }
130
+ try {
131
+ signal?.throwIfAborted();
132
+ const result = await action({ attemptId: attempt.attemptId, root: attempt.root });
133
+ await validate?.(result, attempt);
134
+ signal?.throwIfAborted();
135
+ const terminal = { attemptId: attempt.attemptId, result, status: 'passed' };
136
+ atomicJson(attempt.report, terminal);
137
+ atomicJson(index, { ...attempt, status: 'passed' });
138
+ return { ...terminal, reused: false, joined: false };
139
+ } catch (error) {
140
+ const terminal = { attemptId: attempt.attemptId, status: signal?.aborted ? 'interrupted' : 'failed', error: String(error.stack ?? error) };
141
+ atomicJson(attempt.report, terminal);
142
+ atomicJson(index, { ...attempt, status: terminal.status });
143
+ throw error;
144
+ }
145
+ }
146
+ async function joinAttempt(attempt, signal) {
147
+ for (;;) {
148
+ signal?.throwIfAborted(); // Cancels only this waiter.
149
+ const terminal = optional(attempt.report);
150
+ if (terminal) {
151
+ if (terminal.attemptId !== attempt.attemptId || terminal.status !== 'passed') throw blocked(`Producer attempt did not pass: ${attempt.attemptId}`);
152
+ return { ...terminal, reused: false, joined: true };
153
+ }
154
+ if (!alive(attempt.owner)) throw blocked(`Producer died without terminal evidence: ${attempt.attemptId}`);
155
+ await delay(50, undefined, { signal });
156
+ }
157
+ }
158
+
159
+ function isAncestor(identity, processes) {
160
+ let cursor = processes.find(p => p.pid === process.pid);
161
+ const seen = new Set();
162
+ while (cursor && !seen.has(cursor.pid)) {
163
+ if (same(identity, cursor)) return true;
164
+ seen.add(cursor.pid); cursor = processes.find(p => p.pid === cursor.ppid);
165
+ }
166
+ return false;
167
+ }
168
+ async function inheritedSlot(env, home, signal) {
169
+ if (!env.DD_FLOW_VERIFICATION_SLOT) return false;
170
+ let context;
171
+ try { context = JSON.parse(env.DD_FLOW_VERIFICATION_SLOT); } catch { throw blocked('Malformed inherited slot'); }
172
+ for (;;) {
173
+ const owner = optional(path.join(home, 'lease', 'owner.json')), processes = snapshot();
174
+ const group = owner?.groups?.find(g => g.token === context.token);
175
+ if (!group || owner.token !== context.owner || !alive(owner, processes)) break;
176
+ // A child may reach its first import before spawn() publishes its identity.
177
+ if (!group.pid) { await delay(10, undefined, { signal }); continue; }
178
+ if (isAncestor(group, processes)) return { owner, group };
179
+ break;
180
+ }
181
+ throw blocked('Inherited slot is not owned by an ancestor job');
182
+ }
183
+ async function nestedScope(env, home, outer, signal) {
184
+ if (!env.DD_FLOW_VERIFICATION_NESTED_SLOT) return outer ? `${outer.owner.token}-${outer.group.token}` : null;
185
+ let context;
186
+ try { context = JSON.parse(env.DD_FLOW_VERIFICATION_NESTED_SLOT); } catch { throw blocked('Malformed nested slot'); }
187
+ if (!outer || !/^[a-f0-9-]+$/.test(context.scope ?? '')) throw blocked('Invalid nested slot scope');
188
+ for (;;) {
189
+ const owner = optional(path.join(home, 'nested-slots', context.scope, 'owner.json')), processes = snapshot();
190
+ const child = owner?.groups?.find(group => group.token === context.child);
191
+ if (!child || owner.token !== context.owner || !alive(owner, processes)) break;
192
+ if (!child.pid) { await delay(10, undefined, { signal }); continue; }
193
+ if (isAncestor(child, processes)) return `${owner.token}-${child.token}`;
194
+ break;
195
+ }
196
+ throw blocked('Nested slot is not owned by an ancestor dispatch');
197
+ }
198
+
199
+ /** FIFO host-wide pool; descriptors are child commands, never callbacks sharing
200
+ * process.env. Exclusive drains both slots; inherited runners use one slot. */
201
+ export async function runJobs(jobs, { jobs: budget = 2, signal, env = process.env, coordinationHome } = {}) {
202
+ const queuedAt = Date.now();
203
+ if (![1, 2].includes(budget)) throw new Error('Verification budget must be 1 or 2');
204
+ if (Array.isArray(jobs) && jobs.some(job => job?.credentialKeys !== undefined && (!Array.isArray(job.credentialKeys) || job.credentialKeys.some(name => typeof name !== 'string' || !providerCredentials.includes(name))))) throw new Error('Invalid credential allowlist');
205
+ if (!Array.isArray(jobs) || jobs.some(job=>!job || typeof job.id!=='string' || !job.id || typeof job.command!=='string' || !job.command || job.args && (!Array.isArray(job.args)||job.args.some(arg=>typeof arg!=='string')) || job.executionClass!==undefined && !['isolated','exclusive'].includes(job.executionClass)) || new Set(jobs.map(job=>job.id)).size!==jobs.length) throw new Error('Invalid/duplicate verification job descriptor');
206
+ const home = coordinationHome ?? env.DD_FLOW_VERIFICATION_COORDINATION_HOME ?? path.join(realHome, '.dd-flow', 'verification-coordinator');
207
+ const nested = await inheritedSlot(env, home, signal);
208
+ const scope = await nestedScope(env, home, nested, signal);
209
+ const lock = scope ? path.join(home, 'nested-slots', scope) : path.join(home, 'lease');
210
+ const owner = await claim(lock, signal);
211
+ // A dead nested coordinator cannot free this slot while its outer group lives.
212
+ if (nested) { owner.groups = [{ ...nested.group, token: 'outer-group' }]; atomicJson(path.join(lock, 'owner.json'), owner); }
213
+ const results = new Array(jobs.length);
214
+ const active = new Set();
215
+ let failure;
216
+ const save = () => { if (owner) atomicJson(path.join(lock, 'owner.json'), owner); };
217
+ const start = (job, index) => {
218
+ signal?.throwIfAborted();
219
+ if (failure) throw failure;
220
+ const promise = execute(job, { home, owner, nested, scope, signal, env, save, queuedAt }).then(result => { results[index] = result; }, error => { failure ??= error; }).finally(() => active.delete(promise));
221
+ active.add(promise); return promise;
222
+ };
223
+ try {
224
+ for (let i = 0; i < jobs.length; i++) {
225
+ signal?.throwIfAborted();
226
+ if (failure) throw failure;
227
+ if (jobs[i].executionClass === 'exclusive') { await Promise.all(active); await start(jobs[i], i); }
228
+ else { while (active.size >= (nested ? 1 : budget)) await Promise.race(active); start(jobs[i], i); }
229
+ }
230
+ await Promise.all(active);
231
+ if (failure) throw failure;
232
+ return results;
233
+ } finally {
234
+ await Promise.allSettled(active);
235
+ // Never release a lease after unknown cleanup.
236
+ if (nested && owner.groups.every(group => group.token === 'outer-group')) owner.groups = [];
237
+ if (owner.groups.length === 0) release(lock, owner);
238
+ }
239
+ }
240
+
241
+ async function execute(job, { home, owner, nested, scope, signal, env, save, queuedAt }) {
242
+ const started = Date.now();
243
+ const attemptId = randomUUID();
244
+ const root = path.join(home, 'jobs', attemptId);
245
+ fs.mkdirSync(root, { recursive: true });
246
+ const childEnv = { ...env, ...job.env };
247
+ for (const name of providerCredentials) if (!job.credentialKeys?.includes(name)) delete childEnv[name];
248
+ childEnv.DD_FLOW_VERIFICATION_COORDINATION_HOME = home;
249
+ // node:test sets this private flag for its own children; a separately managed
250
+ // test command must produce a standalone report, not use its parent's IPC.
251
+ delete childEnv.NODE_TEST_CONTEXT;
252
+ for (const name of ['HOME', 'TMPDIR', 'XDG_CACHE_HOME', 'XDG_CONFIG_HOME', 'XDG_RUNTIME_DIR', 'CODEX_HOME', 'AGY_HOME', 'DD_FLOW_HOME', 'DD_FLOW_RESOURCE_HOME', 'DD_FLOW_CONFIG_HOME']) {
253
+ childEnv[name] = path.join(root, name.toLowerCase()); fs.mkdirSync(childEnv[name], { recursive: true });
254
+ }
255
+ // Only an explicit native/delivery descriptor may opt into provider credentials.
256
+ const token = randomUUID();
257
+ if (!nested) {
258
+ childEnv.DD_FLOW_VERIFICATION_SLOT = JSON.stringify({ owner: owner.token, token });
259
+ delete childEnv.DD_FLOW_VERIFICATION_NESTED_SLOT;
260
+ } else {
261
+ childEnv.DD_FLOW_VERIFICATION_SLOT = env.DD_FLOW_VERIFICATION_SLOT;
262
+ childEnv.DD_FLOW_VERIFICATION_NESTED_SLOT = JSON.stringify({ scope, owner: owner.token, child: token });
263
+ }
264
+ // Unknown spawn outcome must retain the lease after coordinator death.
265
+ owner.groups.push({ token, attemptId, root, pgid: null }); save();
266
+ const child = spawn(job.command, job.args ?? [], { cwd: job.cwd, env: childEnv, stdio: job.stdio ?? 'inherit', detached: !nested });
267
+ let spawnError;
268
+ const closed = new Promise(resolve => child.once('close', resolve));
269
+ const completion = new Promise(resolve => { child.once('error', error => { spawnError = error; resolve({ code: null, signal: null }); }); child.once('exit', (code, exitSignal) => resolve({ code, signal: exitSignal })); });
270
+ // A pipe is owned by this helper, not exposed to callers. Drain it while
271
+ // preserving output; an orphan holding it open must not hide direct exit.
272
+ for (const [name, stream] of [['stdout', child.stdout], ['stderr', child.stderr]]) stream?.on('data', bytes => fs.appendFileSync(path.join(root, `${name}.log`), bytes));
273
+ let identity;
274
+ if (child.pid) identity = snapshot().find(p => p.pid === child.pid && !p.zombie);
275
+ if (identity && owner) { identity = { ...identity, token, attemptId, root }; owner.groups = owner.groups.map(g => g.token === token ? identity : g); save(); }
276
+ let cleanupError;
277
+ const stop = (killSignal = 'SIGTERM') => {
278
+ if (!identity) return;
279
+ try {
280
+ const current = snapshot().find(p => p.pid === identity.pid);
281
+ if (same(identity, current)) process.kill(nested ? identity.pid : -identity.pgid, killSignal);
282
+ } catch (error) { if (error.code !== 'ESRCH') cleanupError = error; }
283
+ };
284
+ let forceTimer;
285
+ const cancel = () => { stop(); forceTimer = setTimeout(() => stop('SIGKILL'), 1000); };
286
+ signal?.addEventListener('abort', cancel, { once: true });
287
+ if (signal?.aborted) cancel();
288
+ const result = await completion;
289
+ clearTimeout(forceTimer);
290
+ signal?.removeEventListener('abort', cancel);
291
+ try {
292
+ if (cleanupError) throw cleanupError;
293
+ if (identity && !nested) {
294
+ for (let n = 0; n < 100 && snapshot().some(p => p.pgid === identity.pgid && !p.zombie); n++) {
295
+ stop(); await delay(50);
296
+ }
297
+ if (snapshot().some(p => p.pgid === identity.pgid && !p.zombie)) throw blocked(`Owned group cleanup unconfirmed: ${identity.pgid}`);
298
+ }
299
+ if (child.pid && !identity && snapshot().some(p => !p.zombie && (nested ? p.pid === child.pid : p.pgid === child.pid))) throw blocked(`Spawned process identity unconfirmed: ${child.pid}`);
300
+ const ioClosed = await Promise.race([closed.then(() => true), delay(5000, undefined, { ref: false }).then(() => false)]);
301
+ if (!ioClosed) {
302
+ owner.groups = owner.groups.map(group => group.token === token ? { ...group, pgid: null, reason: 'stdio_cleanup_unconfirmed' } : group); save();
303
+ throw blocked(`Owned stdio cleanup unconfirmed: ${child.pid}`);
304
+ }
305
+ // Missing startup identity is not an eternal unknown after actual exit plus
306
+ // positive absence of the exact spawned group. Never kill by an inferred PID.
307
+ owner.groups = owner.groups.filter(g => g.token !== token); save();
308
+ if (spawnError) throw spawnError;
309
+ const finished = Date.now();
310
+ const terminal = { id: job.id, attemptId, root, ...result, started_at: new Date(started).toISOString(), finished_at: new Date(finished).toISOString(), queue_ms: started - queuedAt, elapsed_ms: finished - started, status: signal?.aborted ? 'interrupted' : result.code === 0 ? 'passed' : 'failed' };
311
+ atomicJson(path.join(root, 'terminal.json'), terminal);
312
+ return terminal;
313
+ } catch (error) {
314
+ child.stdout?.destroy(); child.stderr?.destroy();
315
+ throw error;
316
+ }
317
+ }