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

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 (34) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +17 -22
  3. package/dist/build-info.json +6 -6
  4. package/dist/harness-runtime/lib/agy-error-boundary.mjs +26 -5
  5. package/dist/harness-runtime/lib/agy-terminal-envelope.mjs +67 -0
  6. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +10 -14
  7. package/dist/harness-runtime/lib/dd-agy.mjs +3 -3
  8. package/dist/harness-runtime/lib/dd-droid.mjs +2 -2
  9. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +8 -3
  10. package/dist/harness-runtime/lib/dd-opencode.mjs +2 -2
  11. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +1 -1
  12. package/dist/harness-runtime/lib/dd-zcode.mjs +37 -12
  13. package/dist/harness-runtime/lib/delegation-instructions.d.mts +1 -1
  14. package/dist/harness-runtime/lib/delegation-instructions.mjs +13 -4
  15. package/dist/harness-runtime/lib/model-observations.mjs +19 -7
  16. package/dist/harness-runtime/lib/native-children.d.mts +12 -1
  17. package/dist/harness-runtime/lib/native-children.mjs +47 -1
  18. package/dist/harness-runtime/lib/provider-overload-policy.d.mts +9 -1
  19. package/dist/harness-runtime/lib/provider-overload-policy.mjs +97 -6
  20. package/dist/services/check-runner-evidence.js +17 -302
  21. package/dist/services/check-runner-proof.js +305 -0
  22. package/dist/services/code-checks.js +4 -1
  23. package/dist/services/controller-child-capacity.js +58 -0
  24. package/dist/services/controller-child-observer.js +9 -3
  25. package/dist/services/memory-init-native-capacity.js +1 -1
  26. package/dist/services/provider-capacity.js +15 -7
  27. package/dist/services/run-controller-adapter.js +1 -1
  28. package/dist/services/run-controller.js +87 -18
  29. package/dist/services/run-recovery-guard.js +1 -1
  30. package/dist/services/run-recovery-runtime.js +1 -1
  31. package/dist/services/stage-context.js +56 -8
  32. package/dist/services/vnext-fanout.js +1 -1
  33. package/dist/verification-components.json +299 -0
  34. package/package.json +7 -6
@@ -45,14 +45,14 @@ import { assertNoCorrelatedRejection } from "./continuation-outcome.js";
45
45
  import { spawnController, recordControllerLauncher, claimControllerProcess, markControllerDispatchReady, assertControllerLeaseAllowed } from "./run-controller-process.js";
46
46
  import { controllerAdapter, controllerEnvironment, sessionArguments } from "./run-controller-adapter.js";
47
47
  import { retainedChildObserver } from "./controller-child-observer.js";
48
- import { capacityDelay, continueProviderCapacity, eligibleProviderChildOverload, providerChildFailure, ownedProviderBoundary } from "./provider-capacity.js";
48
+ import { capacityDelay, continueProviderCapacity, eligibleProviderChildOverload, providerChildFailure, ownedProviderBoundary, unavailableProviderChildRecovery } from "./provider-capacity.js";
49
49
  import { resumeControllerSession, failControllerRecovery } from "./run-controller-recovery.js";
50
50
  import { captureControllerBoundary } from "./run-controller-capture.js";
51
51
  import { requestRunControl } from "./run-control-worker.js";
52
52
  import { storageSessionId } from "./session-identity.js";
53
- import { CAPACITY_POLICY, nativeItemsSettled, overloadExhausted, refusalObservation, terminalProviderOverload } from "../harness-runtime/lib/provider-overload-policy.mjs";
53
+ import { RECOVERY_POLICY as CAPACITY_POLICY, nativeItemsSettled, recoveryExhausted as overloadExhausted, recoveryRefusal as refusalObservation, terminalProviderRecovery as terminalProviderOverload, normalizeRecoveryFailure } from "../harness-runtime/lib/provider-overload-policy.mjs";
54
54
  import { renderNativeChildFollowupInstruction } from "../harness-runtime/lib/delegation-instructions.mjs";
55
- import { closeSuccessfulChildCapacity } from "./controller-child-capacity.js";
55
+ import { childCapacityDispatchAllowed, childCapacityRecoveryDecision, closeSuccessfulChildCapacity, reconcileChildCapacity } from "./controller-child-capacity.js";
56
56
  import { isObservationLoss } from "../harness-runtime/lib/operation-errors.mjs";
57
57
  export { resumeRunController } from "./run-controller-recovery.js";
58
58
  const preparedLaunchInputs = new WeakMap();
@@ -754,7 +754,8 @@ async function executeController(context, row, manifest, state, assertOwnership,
754
754
  throw new AppError("capacity_continuation_conflict", "Retained capacity root has no failure receipt", 1);
755
755
  throw JSON.parse(root.error_json);
756
756
  }
757
- if (childObserver(session)?.failed().length)
757
+ const failedChildren = childObserver(session)?.failed() ?? [];
758
+ if (failedChildren.length && !(kind === "child" && childCapacityDispatchAllowed(state, failedChildren, session.id, firstOperationId, digestBytes(text))))
758
759
  throw new AppError("fanout_dispatch_fenced", "A retained child failure forbids another coordinator dispatch", 1);
759
760
  promptPending = true;
760
761
  observedPromptOperation = firstOperationId;
@@ -785,15 +786,15 @@ async function executeController(context, row, manifest, state, assertOwnership,
785
786
  assertCurrent: () => {
786
787
  assertOwner();
787
788
  },
788
- decision: async (boundary) => {
789
+ decision: async (boundary, currentFailure) => {
789
790
  if (kind === "child") {
790
- const target = Object.entries(state.child_capacity ?? {}).find(([, retained]) => retained.operation_id === firstOperationId);
791
- if (!target)
792
- return "reconciliation_blocked";
793
791
  const seen = await inspect(["session", "inspect", "--session-id", session.id, ...sessionArguments(context, manifest, session)], `${firstOperationId}:child-followup-observe`);
794
- const child = controllerNativeChildren(seen, session.id).find(item => item.session_id === target[0]);
795
- if (child?.parent_session_id === session.id && (child.status === "running" || child.terminal_turn_id && child.terminal_turn_id !== target[1].turn_id))
796
- return "completed";
792
+ const parent = normalizeRecoveryFailure(currentFailure, session.id);
793
+ const decision = childCapacityRecoveryDecision(state, controllerNativeChildren(seen, session.id), session.id, firstOperationId, parent?.request_proof.kind === "native_turn" ? parent.turn_id : undefined);
794
+ if (decision !== "continue") {
795
+ saveState(context, row, state);
796
+ return decision;
797
+ }
797
798
  }
798
799
  const view = getFlowRunStatus(executionContext, { projectRoot: manifest.request.project_root, runId: row.run_id });
799
800
  const next = view.continuation;
@@ -1081,7 +1082,32 @@ async function executeController(context, row, manifest, state, assertOwnership,
1081
1082
  const session = state.sessions[state.current_session];
1082
1083
  const childHarness = runtimeHarness(session.profile);
1083
1084
  const baseline = new Set(state.native_baseline ?? []);
1085
+ for (const intent of Object.values(state.child_capacity ?? {})) {
1086
+ if (!intent.dispatched || !intent.operation_id || intent.parent_turn_id || !intent.prompt_sha256)
1087
+ continue;
1088
+ let saved = context.db.get("SELECT input_json,receipt_json,status FROM run_controller_operations WHERE operation_id = ? AND controller_id = ? AND generation = ?", [intent.operation_id, row.controller_id, row.generation]);
1089
+ if (!saved)
1090
+ continue;
1091
+ const dispatch = JSON.parse(saved.input_json);
1092
+ if (!saved.receipt_json && saved.status === "outcome_unknown" && dispatch.dispatch_phase !== "prepared" && dispatch.prompt_sha256 === intent.prompt_sha256 && Array.isArray(dispatch.args)) {
1093
+ // Existing adapter recovery reads its durable operation; it cannot
1094
+ // redispatch a productive operation past external_dispatch_possible.
1095
+ await controllerAdapter(executionContext, row, manifest, session, dispatch.args, assertOwner, intent.operation_id, cancellation.signal, renewLease);
1096
+ saved = context.db.get("SELECT input_json,receipt_json,status FROM run_controller_operations WHERE operation_id = ? AND controller_id = ? AND generation = ?", [intent.operation_id, row.controller_id, row.generation]);
1097
+ }
1098
+ if (!saved?.receipt_json)
1099
+ continue;
1100
+ const receipt = JSON.parse(saved.receipt_json);
1101
+ if ((saved.status === "completed" || dispatch.dispatch_phase === "receipt_observed") && dispatch.prompt_sha256 === intent.prompt_sha256
1102
+ && receipt.provider_session_id === session.id && receipt.turn?._meta?.zcode?.ddFlowRecovery?.operation_id === intent.operation_id
1103
+ && typeof receipt.turn._meta.zcodeNativeTurnId === "string" && receipt.turn._meta.zcodeNativeTurnId) {
1104
+ intent.parent_turn_id = receipt.turn._meta.zcodeNativeTurnId;
1105
+ saveState(context, row, state);
1106
+ }
1107
+ }
1084
1108
  let children = controllerNativeChildren(state.last_receipt ?? {}, session.id).filter(child => !baseline.has(child.session_id));
1109
+ if (reconcileChildCapacity(state, children, session.id))
1110
+ saveState(context, row, state);
1085
1111
  childObserver(session)?.observeSettledChildren(children);
1086
1112
  const retainedFailures = childObserver(session)?.failed() ?? [];
1087
1113
  children = controllerNativeChildren({ descendants: [...children, ...retainedFailures] }, session.id);
@@ -1091,7 +1117,35 @@ async function executeController(context, row, manifest, state, assertOwnership,
1091
1117
  saveState(context, row, state);
1092
1118
  appendEvent(context, row.controller_id, "capacity_child_episodes_completed", { child_session_ids: closedChildEpisodes });
1093
1119
  });
1094
- if (children.some(child => child.status === "failed" && renderNativeChildFollowupInstruction(childHarness, child.session_id))) {
1120
+ const queuedChild = children.find(child => state.child_capacity?.[child.session_id]?.phase === "native_message_accepted"
1121
+ && (child.status === "failed" && state.child_capacity[child.session_id].turn_id === child.terminal_turn_id
1122
+ || child.status === "running" && child.continuation_admission?.proof_kind === "parent_tool_admission"));
1123
+ if (queuedChild) {
1124
+ const observedAt = Date.parse(state.child_capacity[queuedChild.session_id].last_signal_at ?? "");
1125
+ if (!Number.isFinite(observedAt) || Date.now() - observedAt >= 600_000)
1126
+ throw new AppError("operation_observation_lost", "Accepted native child continuation has no new observed signal", 1, { child_session_id: queuedChild.session_id });
1127
+ setStatus("waiting_for_children");
1128
+ await delay(1000, undefined, { signal: cancellation.signal });
1129
+ state.last_receipt = await call(session, ["session", "inspect", "--session-id", session.id, ...sessionArguments(context, manifest, session)], false);
1130
+ saveState(context, row, state);
1131
+ continue;
1132
+ }
1133
+ const recoveryUnavailable = children.map(child => {
1134
+ const links = context.db.all("SELECT w.status FROM work_sessions ws JOIN works w ON w.work_id = ws.work_id WHERE w.project_id = ? AND w.run_id = ? AND ws.session_id = ?", [row.project_id, row.run_id, storageSessionId({ harness_id: childHarness, session_id: child.session_id })]);
1135
+ return links.length > 1 || links.some(link => link.status !== "running") ? null : unavailableProviderChildRecovery(child, session.id);
1136
+ }).find(Boolean);
1137
+ if (recoveryUnavailable) {
1138
+ if (children.every(child => ["completed", "failed", "cancelled", "settled_by_root"].includes(child.status)))
1139
+ throw recoveryUnavailable;
1140
+ // A blocked child does not cancel healthy siblings. Observe their own
1141
+ // native completion before reporting the unavailable continuation.
1142
+ setStatus("waiting_for_children");
1143
+ await delay(1000, undefined, { signal: cancellation.signal });
1144
+ state.last_receipt = await call(session, ["session", "inspect", "--session-id", session.id, ...sessionArguments(context, manifest, session)], false);
1145
+ saveState(context, row, state);
1146
+ continue;
1147
+ }
1148
+ if (children.some(child => child.status === "failed" && renderNativeChildFollowupInstruction(childHarness, child))) {
1095
1149
  const inspected = await call(session, ["session", "inspect", "--session-id", session.id, ...sessionArguments(context, manifest, session)], false);
1096
1150
  state.last_receipt = inspected;
1097
1151
  saveState(context, row, state);
@@ -1104,6 +1158,8 @@ async function executeController(context, row, manifest, state, assertOwnership,
1104
1158
  if (linked.length > 1 || linked.some(link => link.status !== "running"))
1105
1159
  continue;
1106
1160
  const retained = state.child_capacity?.[failedChild.session_id];
1161
+ if (retained?.policy && retained.policy !== CAPACITY_POLICY)
1162
+ throw new AppError("capacity_policy_mismatch", "Retained child episode requires its original pinned recovery policy", 1);
1107
1163
  const refusals = [...(retained?.refusals ?? [])];
1108
1164
  if (refusals.some(item => item.turn_id === failedChild.terminal_turn_id))
1109
1165
  continue;
@@ -1114,11 +1170,11 @@ async function executeController(context, row, manifest, state, assertOwnership,
1114
1170
  await prepareMutation(() => { saveState(context, row, state); appendEvent(context, row.controller_id, "capacity_child_refusal", { child_session_id: failedChild.session_id, refusal: refusals.at(-1) }); });
1115
1171
  const exhausted = overloadExhausted(refusals);
1116
1172
  if (exhausted)
1117
- throw new AppError("provider_unavailable", "Native child exhausted its overload continuation attempts", 1, { ...exhausted, reason: "overload_retries_exhausted", child_session_id: failedChild.session_id, primary_error: nativeFailure });
1173
+ throw new AppError("provider_unavailable", "Native child exhausted its provider continuation attempts", 1, { ...exhausted, child_session_id: failedChild.session_id, primary_error: nativeFailure });
1118
1174
  }
1119
1175
  // Continue one existing child at a time; another failed child does not
1120
1176
  // create a second fanout wave or share this child's refusal counter.
1121
- const child = children.every(item => item.status === "failed" || item.status === "completed") ? failed.find(item => renderNativeChildFollowupInstruction(childHarness, item.session_id) && eligibleProviderChildOverload(item, session.id)) : undefined;
1177
+ const child = children.every(item => item.status === "failed" || item.status === "completed") ? failed.find(item => renderNativeChildFollowupInstruction(childHarness, item) && eligibleProviderChildOverload(item, session.id)) : undefined;
1122
1178
  const turnId = child?.terminal_turn_id;
1123
1179
  const native = child && turnId ? providerChildFailure(child) : null;
1124
1180
  const prior = child && state.child_capacity?.[child.session_id];
@@ -1143,7 +1199,7 @@ async function executeController(context, row, manifest, state, assertOwnership,
1143
1199
  }
1144
1200
  const exhausted = overloadExhausted(refusals);
1145
1201
  if (exhausted)
1146
- throw new AppError("provider_unavailable", "Native child exhausted its overload continuation attempts", 1, { ...exhausted, reason: "overload_retries_exhausted", child_session_id: child.session_id, primary_error: native });
1202
+ throw new AppError("provider_unavailable", "Native child exhausted its provider continuation attempts", 1, { ...exhausted, child_session_id: child.session_id, primary_error: native });
1147
1203
  let wait;
1148
1204
  try {
1149
1205
  wait = prepared ? Math.max(0, Date.parse(prepared.not_before) - Date.now()) : capacityDelay(native, attempt);
@@ -1157,7 +1213,8 @@ async function executeController(context, row, manifest, state, assertOwnership,
1157
1213
  const rootOperationId = prior?.root_operation_id ?? `${row.controller_id}:child:${digest([child.session_id, turnId]).slice(0, 16)}`;
1158
1214
  const operationId = prepared?.operation_id ?? `${rootOperationId}:followup:${attempt}`;
1159
1215
  state.child_capacity ??= {};
1160
- state.child_capacity[child.session_id] = { turn_id: turnId, attempts: attempt, operation_id: operationId, root_operation_id: rootOperationId, policy: CAPACITY_POLICY, refusals, not_before: prepared?.not_before ?? new Date(Date.now() + wait).toISOString(), dispatched: false };
1216
+ const followupText = renderNativeChildFollowupInstruction(childHarness, child);
1217
+ state.child_capacity[child.session_id] = { turn_id: turnId, attempts: attempt, operation_id: operationId, root_operation_id: rootOperationId, policy: CAPACITY_POLICY, refusals, not_before: prepared?.not_before ?? new Date(Date.now() + wait).toISOString(), dispatched: false, phase: "prepare", prompt_sha256: digestBytes(followupText), ...(child.continuation_ref ? { continuation_ref: child.continuation_ref } : {}) };
1161
1218
  await prepareMutation(() => { saveState(context, row, state); appendEvent(context, row.controller_id, "capacity_child_followup", { child_session_id: child.session_id, turn_id: turnId, attempt, operation_id: operationId }); });
1162
1219
  const until = performance.now() + wait;
1163
1220
  while (performance.now() < until) {
@@ -1186,15 +1243,27 @@ async function executeController(context, row, manifest, state, assertOwnership,
1186
1243
  throw new AppError("capacity_child_followup_blocked", "Native child or Work changed during capacity wait", 1, { child_session_id: child.session_id, turn_id: turnId });
1187
1244
  await renewLease();
1188
1245
  assertOwner();
1189
- const text = renderNativeChildFollowupInstruction(childHarness, child.session_id);
1246
+ const text = renderNativeChildFollowupInstruction(childHarness, child);
1247
+ // Fence dispatch before awaiting the parent: a lost ack must never
1248
+ // resend SendMessage into a child which may already be running.
1249
+ state.child_capacity[child.session_id].dispatched = true;
1250
+ state.child_capacity[child.session_id].phase = "dispatched";
1251
+ saveState(context, row, state);
1190
1252
  await prompt(session, text, operationId, "child");
1253
+ const parentTurnId = state.last_receipt?.turn?._meta?.zcodeNativeTurnId;
1254
+ if (typeof parentTurnId === "string" && parentTurnId)
1255
+ state.child_capacity[child.session_id].parent_turn_id = parentTurnId;
1191
1256
  state.child_capacity[child.session_id].dispatched = true;
1192
1257
  saveState(context, row, state);
1193
1258
  const after = await call(session, ["session", "inspect", "--session-id", session.id, ...sessionArguments(context, manifest, session)], false);
1259
+ const afterChildren = controllerNativeChildren(after, session.id);
1260
+ reconcileChildCapacity(state, afterChildren, session.id);
1261
+ if (after.descendants)
1262
+ after.descendants = afterChildren;
1194
1263
  state.last_receipt = after;
1195
1264
  saveState(context, row, state);
1196
1265
  const observed = controllerNativeChildren(after, session.id).find(item => item.session_id === child.session_id);
1197
- if (!observed || observed.status === "failed" && observed.terminal_turn_id === turnId)
1266
+ if (!observed || observed.status === "failed" && observed.terminal_turn_id === turnId && state.child_capacity[child.session_id].phase !== "native_message_accepted")
1198
1267
  throw new AppError("capacity_child_followup_unconfirmed", "Native child did not advance to a new Turn after parent followup", 1, { child_session_id: child.session_id, turn_id: turnId });
1199
1268
  continue;
1200
1269
  }
@@ -1,6 +1,6 @@
1
1
  import crypto from "node:crypto";
2
2
  import { AppError } from "../shared/errors.js";
3
- import { CAPACITY_POLICY, MAX_OVERLOAD_RETRIES, overloadExhausted } from "../harness-runtime/lib/provider-overload-policy.mjs";
3
+ import { RECOVERY_POLICY as CAPACITY_POLICY, MAX_OVERLOAD_RETRIES, recoveryExhausted as overloadExhausted } from "../harness-runtime/lib/provider-overload-policy.mjs";
4
4
  import { assertRunStartupSettled } from "./run-start-custody.js";
5
5
  export function recoveryPacketReceipt(binding) {
6
6
  if (!binding.packet_json || !binding.packet_sha256 || crypto.createHash("sha256").update(binding.packet_json).digest("hex") !== binding.packet_sha256)
@@ -8,7 +8,7 @@ import { managedProcessBinding, processIsAlive, resourceHome } from "./managed-p
8
8
  import { getResourceDatabase } from "../storage/database.js";
9
9
  import { requireManagedDaemonBinding } from "./managed-daemon-binding.js";
10
10
  import { lifecycleRetryInstruction } from "../harness-runtime/lib/delegation-instructions.mjs";
11
- import { CAPACITY_POLICY, MAX_OVERLOAD_RETRIES, overloadExhausted } from "../harness-runtime/lib/provider-overload-policy.mjs";
11
+ import { RECOVERY_POLICY as CAPACITY_POLICY, MAX_OVERLOAD_RETRIES, recoveryExhausted as overloadExhausted } from "../harness-runtime/lib/provider-overload-policy.mjs";
12
12
  /** Pin retained Session loading and its first acknowledgement Turn, never a general fence bypass. */
13
13
  export function prepareRunRecoveryDispatch(context, input) {
14
14
  if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/.test(input.operationId) || !path.isAbsolute(input.stateDir))
@@ -16,6 +16,16 @@ function assertContextRelocation(receipt) {
16
16
  || !Array.isArray(receipt.path_mapping) || receipt.path_mapping.some(item => !item || typeof item.from !== "string" || typeof item.to !== "string" || !path.isAbsolute(item.from) || !path.isAbsolute(item.to)))
17
17
  throw new AppError("context_relocation_invalid", "Stage context relocation lacks a verified import binding", 2);
18
18
  }
19
+ function relocateDeclaredPath(source, receipts) {
20
+ let mapped = source;
21
+ for (const receipt of receipts) {
22
+ assertContextRelocation(receipt);
23
+ const selected = [...receipt.path_mapping].sort((a, b) => b.from.length - a.from.length).find(item => mapped === item.from || mapped.startsWith(`${item.from}${path.sep}`));
24
+ if (selected)
25
+ mapped = path.join(selected.to, path.relative(selected.from, mapped));
26
+ }
27
+ return mapped;
28
+ }
19
29
  /** Physical packet locators may move; accepted decision/provenance blocks may not. */
20
30
  export function projectPacketLocators(text, receipts) {
21
31
  receipts.forEach(assertContextRelocation);
@@ -89,13 +99,7 @@ export function renderRetainedStageContext(input) {
89
99
  || !/^[a-f0-9]{64}$/.test(input.workspaceView.source_sha256) || !/^[a-f0-9]{64}$/.test(input.workspaceView.copy_sha256)))
90
100
  throw new AppError("context_workspace_view_invalid", "Stage context workspace view lacks its bound review-copy proof", 2);
91
101
  const mapPath = (source, workspaceSource = true) => {
92
- let mapped = source;
93
- for (const receipt of input.receipts) {
94
- assertContextRelocation(receipt);
95
- const selected = [...receipt.path_mapping].sort((a, b) => b.from.length - a.from.length).find(item => mapped === item.from || mapped.startsWith(`${item.from}${path.sep}`));
96
- if (selected)
97
- mapped = path.join(selected.to, path.relative(selected.from, mapped));
98
- }
102
+ let mapped = relocateDeclaredPath(source, input.receipts);
99
103
  const view = input.workspaceView;
100
104
  if (view && workspaceSource && (mapped === view.from || mapped.startsWith(`${view.from}${path.sep}`)))
101
105
  mapped = path.join(view.to, path.relative(view.from, mapped));
@@ -149,6 +153,7 @@ export function loadExternalStageContextBytes(input) {
149
153
  return { value, sourcePath, materializedSha256, bytes };
150
154
  }
151
155
  export function prepareExternalStageContext(input) {
156
+ verifyImmutableContextSources(input.loaded.value);
152
157
  const destination = externalStageContextDestination(input);
153
158
  if (!fs.existsSync(destination))
154
159
  prepareOutputFile(destination);
@@ -206,6 +211,7 @@ export function applyExternalStageContext(input) {
206
211
  return installed;
207
212
  }
208
213
  export function renderExternalStageContext(input) {
214
+ verifyImmutableContextSources(input.loaded.value);
209
215
  const value = input.loaded.value;
210
216
  const sources = value.sources ?? [];
211
217
  const taskInput = value.task_input ?? [];
@@ -251,7 +257,7 @@ function validateContext(value, expectedStage) {
251
257
  for (const item of value[key]) {
252
258
  const valid = key === "accepted_decisions" ? text(item) || object(item) && (text(item.id) || text(item.statement)) && [item.id, item.statement].every(field => field === undefined || text(field))
253
259
  : object(item) && text(item.role) && (key === "dynamic_roles" ? item.reason === undefined || text(item.reason)
254
- : text(item.path) && (key === "sources" ? text(item.reason) && (item.required === undefined || typeof item.required === "boolean") && (item.root === undefined || typeof item.root === "string" && ["project", "workspace", "run"].includes(item.root)) : item.sha256 === undefined || text(item.sha256)));
260
+ : text(item.path) && (key === "sources" ? text(item.reason) && (item.sha256 === undefined || typeof item.sha256 === "string" && /^[a-f0-9]{64}$/.test(item.sha256)) && (item.required === undefined || typeof item.required === "boolean") && (item.root === undefined || typeof item.root === "string" && ["project", "workspace", "run"].includes(item.root)) : item.sha256 === undefined || text(item.sha256)));
255
261
  if (!valid)
256
262
  throw new AppError("validation", `Invalid stage context ${key} item`, 2, { parameter: key });
257
263
  }
@@ -261,4 +267,46 @@ function validateContext(value, expectedStage) {
261
267
  throw new AppError("validation", `Stage context ${key} must be a nonempty string`, 2);
262
268
  }
263
269
  }
270
+ function verifyImmutableContextSources(value) {
271
+ for (const source of value.sources ?? []) {
272
+ if (!source.sha256)
273
+ continue; // Ordinary mutable project documents are not frozen.
274
+ try {
275
+ const info = fs.lstatSync(source.path);
276
+ if (!info.isFile() || info.isSymbolicLink() || info.size > 2 * 1024 * 1024 || sha256(fs.readFileSync(source.path)) !== source.sha256)
277
+ throw new Error("changed");
278
+ }
279
+ catch {
280
+ throw new AppError("context_source_checksum_mismatch", "Immutable stage source is unavailable or changed", 2, { role: source.role });
281
+ }
282
+ }
283
+ }
284
+ /** Only an accepted CODE context may bind a diagnostic capability to checks. */
285
+ export function browserCheckCapabilityEnvironment(context, runId, runHome) {
286
+ const row = context.db.get("SELECT project_id, index_json FROM runs WHERE id = ?", [runId]);
287
+ const ref = row ? JSON.parse(row.index_json).execution_profile?.stage_context_inputs?.code : null;
288
+ if (!ref)
289
+ return {};
290
+ const file = path.resolve(runHome, ref.path), relative = path.relative(runHome, file);
291
+ if (relative.startsWith('..') || path.isAbsolute(relative))
292
+ throw new AppError('context_source_checksum_mismatch', 'Stage context escaped RUN', 2);
293
+ const loaded = loadExternalStageContext({ file, expectedSha256: ref.sha256, stage: 'code' });
294
+ const sources = loaded.value.sources?.filter(item => item.role === 'browser_environment_capabilities' && item.root === 'run') ?? [];
295
+ if (!sources.length)
296
+ return {};
297
+ if (sources.length !== 1)
298
+ throw new AppError('context_source_checksum_mismatch', 'Browser capability source is ambiguous', 2);
299
+ const source = { ...sources[0], path: relocateDeclaredPath(sources[0].path, runContextRelocations(context, row.project_id, runId)) };
300
+ const filename = path.basename(source.path), namedHash = filename.match(/^browser-capabilities(?:-([a-f0-9]{64}))?\.json$/);
301
+ const expected = path.join(runHome, 'environment', filename);
302
+ let owned = false;
303
+ try {
304
+ owned = fs.realpathSync(source.path) === path.join(fs.realpathSync(runHome), 'environment', filename);
305
+ }
306
+ catch { /* Missing proof is not a foreign host lookup. */ }
307
+ if (!source.sha256 || !namedHash || namedHash[1] && namedHash[1] !== source.sha256 || path.resolve(source.path) !== expected || !owned)
308
+ throw new AppError('context_source_checksum_mismatch', 'Browser capability lacks its RUN binding', 2);
309
+ verifyImmutableContextSources({ ...loaded.value, sources: [source] });
310
+ return { DD_FLOW_BROWSER_CAPABILITY_PATH: source.path, DD_FLOW_BROWSER_CAPABILITY_SHA256: source.sha256 };
311
+ }
264
312
  function sha256(value) { return crypto.createHash("sha256").update(value).digest("hex"); }
@@ -305,7 +305,7 @@ export async function reconcileVnextFanout(context, input) {
305
305
  continue;
306
306
  }
307
307
  const link = active[0];
308
- if (renderNativeChildFollowupInstruction(String(value.harness_id), String(child.session_id)) && eligibleProviderChildOverload(child, value.parent_session_id)) {
308
+ if (renderNativeChildFollowupInstruction(String(value.harness_id), child) && eligibleProviderChildOverload(child, value.parent_session_id)) {
309
309
  issues.push({ code: "child_capacity_pending", work_id: link.work_id, session_id: child.session_id });
310
310
  continue;
311
311
  }
@@ -0,0 +1,299 @@
1
+ {
2
+ "schema_id": "dd-flow/verification-components@1",
3
+ "scope": "technical-matrix-publication-not-native-execution",
4
+ "matrix": {
5
+ "roots": [
6
+ "services/verification-matrix.js",
7
+ "services/stage-report-renderer.js",
8
+ "services/accepted-intent.js",
9
+ "services/check-runner-evidence.js",
10
+ "services/check-runner-proof.js"
11
+ ],
12
+ "runner_proof_contract": "dd-flow/check-runner-proof@1",
13
+ "files": [
14
+ "cli/command-inputs.js",
15
+ "cli/command-metadata.js",
16
+ "cli/command-option-descriptions.js",
17
+ "cli/help.js",
18
+ "cli/input-preparation.js",
19
+ "cli/native-hook-preparation.js",
20
+ "domain/contracts.js",
21
+ "domain/diagnostic-aspects.js",
22
+ "domain/diagnostic-flows.js",
23
+ "domain/entity-ids.js",
24
+ "domain/flow-contract.js",
25
+ "domain/session-coverage.js",
26
+ "domain/stage-catalog.js",
27
+ "domain/stage-handler-identities.js",
28
+ "domain/validation.js",
29
+ "protocol/local-files.js",
30
+ "runtime/context.js",
31
+ "schemas/merge-result.schema.json",
32
+ "schemas/merge-stage-report-legacy-0.4.2.schema.json",
33
+ "schemas/merge-stage-report.schema.json",
34
+ "schemas/plan-aspect-map.schema.json",
35
+ "schemas/plan-review-decision.schema.json",
36
+ "schemas/plan-review-result.schema.json",
37
+ "schemas/plan-stage-report.schema.json",
38
+ "schemas/protocol-dashboard-data.schema.json",
39
+ "schemas/protocol-plan.schema.json",
40
+ "schemas/run-control-receipt.schema.json",
41
+ "schemas/run-control-request.schema.json",
42
+ "schemas/run-engine-binding.schema.json",
43
+ "schemas/stage-finish-input.schema.json",
44
+ "schemas/stage-prompt.schema.json",
45
+ "schemas/stage-report.schema.json",
46
+ "schemas/stage-start-response.schema.json",
47
+ "services/accepted-intent.js",
48
+ "services/audit.js",
49
+ "services/branch-context.js",
50
+ "services/build-info.js",
51
+ "services/canon.js",
52
+ "services/check-environment.js",
53
+ "services/check-runner-evidence.js",
54
+ "services/check-runner-proof.js",
55
+ "services/cli-operation-classifier.js",
56
+ "services/cli-upgrade-allowlist.js",
57
+ "services/code-checks.js",
58
+ "services/command-context.js",
59
+ "services/config.js",
60
+ "services/descriptor-coordinator-session.js",
61
+ "services/descriptor-root-work.js",
62
+ "services/descriptor-run-settlement.js",
63
+ "services/descriptor-run-start.js",
64
+ "services/descriptor-stage-coordinator.js",
65
+ "services/descriptor-stage-settlement.js",
66
+ "services/descriptor-stage-start.js",
67
+ "services/diagnostic-evidence.js",
68
+ "services/diagnostic-report-publication.js",
69
+ "services/diagnostic-report.js",
70
+ "services/diagnostic-snapshot.js",
71
+ "services/diagnostic-stage-handlers.js",
72
+ "services/diagnostic-stage-results.js",
73
+ "services/diagnostic-work-batch.js",
74
+ "services/diagnostic-work-collection.js",
75
+ "services/diagnostic-work-registration.js",
76
+ "services/diagnostic-work-results.js",
77
+ "services/engines.js",
78
+ "services/eval-snapshots.js",
79
+ "services/execution-bundles.js",
80
+ "services/execution-policy.js",
81
+ "services/executor-onboarding.js",
82
+ "services/external-work-launch.js",
83
+ "services/flow-descriptors.js",
84
+ "services/flow-guidance.js",
85
+ "services/frozen-engine.js",
86
+ "services/generic-execution.js",
87
+ "services/hook-memory-routing.js",
88
+ "services/hook-recovery-guard.js",
89
+ "services/hook-session-observation.js",
90
+ "services/hooks.js",
91
+ "services/hybrid-contracts.js",
92
+ "services/hybrid-digest.js",
93
+ "services/ids.js",
94
+ "services/installation-lock.js",
95
+ "services/installed-flow-source.js",
96
+ "services/lanes.js",
97
+ "services/lifecycle-command.js",
98
+ "services/lifecycle-contract.js",
99
+ "services/lifecycle-invocations.js",
100
+ "services/managed-daemon-binding.js",
101
+ "services/managed-processes.js",
102
+ "services/memory-accepted-composition.js",
103
+ "services/memory-baseline-preservation.js",
104
+ "services/memory-completed-correction-wave.js",
105
+ "services/memory-compose-handler.js",
106
+ "services/memory-composition-acceptance.js",
107
+ "services/memory-composition-graph.js",
108
+ "services/memory-composition-registration.js",
109
+ "services/memory-composition-work-custody.js",
110
+ "services/memory-composition-work-results.js",
111
+ "services/memory-corrected-review-acceptance.js",
112
+ "services/memory-corrected-review-batch.js",
113
+ "services/memory-corrected-review-findings.js",
114
+ "services/memory-corrected-review-inputs.js",
115
+ "services/memory-corrected-review-projection.js",
116
+ "services/memory-corrected-review-receipt.js",
117
+ "services/memory-corrected-review-registration.js",
118
+ "services/memory-correction-check-receipt.js",
119
+ "services/memory-correction-input-owner.js",
120
+ "services/memory-custody-snapshot.js",
121
+ "services/memory-discover-handler.js",
122
+ "services/memory-finish-evidence.js",
123
+ "services/memory-finish-handler.js",
124
+ "services/memory-finish-integration.js",
125
+ "services/memory-finish-readiness.js",
126
+ "services/memory-git-integration.js",
127
+ "services/memory-historical-corrected-review.js",
128
+ "services/memory-historical-original-review.js",
129
+ "services/memory-historical-repair-registration.js",
130
+ "services/memory-historical-second-review.js",
131
+ "services/memory-init-bootstrap-readiness.js",
132
+ "services/memory-init-engine-release.js",
133
+ "services/memory-init-git-policy.js",
134
+ "services/memory-init-lint-capability.js",
135
+ "services/memory-init-preflight.js",
136
+ "services/memory-init-project-bootstrap.js",
137
+ "services/memory-init-request-candidate.js",
138
+ "services/memory-init-request-input.js",
139
+ "services/memory-init-resource-storage.js",
140
+ "services/memory-init-run-allocation.js",
141
+ "services/memory-init-startup-custody.js",
142
+ "services/memory-init-storage.js",
143
+ "services/memory-init-worktrunk-capability.js",
144
+ "services/memory-layer-publication.js",
145
+ "services/memory-layer.js",
146
+ "services/memory-lint-config.js",
147
+ "services/memory-native-review-copy.js",
148
+ "services/memory-normal-flow-entry.js",
149
+ "services/memory-ordinary-defaults.js",
150
+ "services/memory-permissions.js",
151
+ "services/memory-preflight-bootstrap-custody.js",
152
+ "services/memory-preflight-handler.js",
153
+ "services/memory-repair-current-view.js",
154
+ "services/memory-repair-document-delta.js",
155
+ "services/memory-repair-history.js",
156
+ "services/memory-repair-physical-binding.js",
157
+ "services/memory-repair-publication-files.js",
158
+ "services/memory-repair-publication-intent.js",
159
+ "services/memory-repair-publication-plan.js",
160
+ "services/memory-repair-publication-source.js",
161
+ "services/memory-repair-publication.js",
162
+ "services/memory-repair-registration.js",
163
+ "services/memory-repair-work-baseline.js",
164
+ "services/memory-repair-work-completion.js",
165
+ "services/memory-repair-work-custody.js",
166
+ "services/memory-repair-work-results.js",
167
+ "services/memory-repair-workspace.js",
168
+ "services/memory-repair-writer-copy.js",
169
+ "services/memory-result-binding.js",
170
+ "services/memory-retained-review-authority.js",
171
+ "services/memory-retained-review-results.js",
172
+ "services/memory-retained-second-baseline.js",
173
+ "services/memory-retained-second-repairs.js",
174
+ "services/memory-review-acceptance.js",
175
+ "services/memory-review-correction-plan.js",
176
+ "services/memory-review-handler.js",
177
+ "services/memory-review-inputs.js",
178
+ "services/memory-review-limits.js",
179
+ "services/memory-review-receipt.js",
180
+ "services/memory-review-registration.js",
181
+ "services/memory-review-work-custody.js",
182
+ "services/memory-review-work-input.js",
183
+ "services/memory-review-work-results.js",
184
+ "services/memory-second-corrected-review-inputs.js",
185
+ "services/memory-second-correction-plan.js",
186
+ "services/memory-second-repair-baseline.js",
187
+ "services/memory-second-repair-launch.js",
188
+ "services/memory-second-repair-registration.js",
189
+ "services/memory-second-repair-source.js",
190
+ "services/memory-second-review-acceptance.js",
191
+ "services/memory-source-observations.js",
192
+ "services/memory-stage-dispatch.js",
193
+ "services/memory-stage-results.js",
194
+ "services/memory-tool-probe.js",
195
+ "services/memory-upgrade-analysis-handler.js",
196
+ "services/memory-upgrade-bootstrap-readiness.js",
197
+ "services/memory-upgrade-evidence.js",
198
+ "services/memory-upgrade-inventory.js",
199
+ "services/memory-upgrade-publication-custody.js",
200
+ "services/memory-upgrade-publication.js",
201
+ "services/memory-upgrade-selection.js",
202
+ "services/memory-upgrade-storage-readiness.js",
203
+ "services/memory-verification-acceptance.js",
204
+ "services/memory-verification-checks.js",
205
+ "services/memory-verification-receipt.js",
206
+ "services/memory-verify-handler.js",
207
+ "services/memory-work-result-json.js",
208
+ "services/memory-work-result-preparation.js",
209
+ "services/merge-queue.js",
210
+ "services/migrations.js",
211
+ "services/native-session-control.js",
212
+ "services/operator-profiles.js",
213
+ "services/plan-runtime.js",
214
+ "services/portable-refs.js",
215
+ "services/project-registry.js",
216
+ "services/project-summary.js",
217
+ "services/projects.js",
218
+ "services/prompt-delivery.js",
219
+ "services/protocol-lifecycle.js",
220
+ "services/protocols.js",
221
+ "services/provider-capacity.js",
222
+ "services/recovery-snapshot-database.js",
223
+ "services/release-archive.js",
224
+ "services/release-cache.js",
225
+ "services/release-download.js",
226
+ "services/release-envelope.js",
227
+ "services/release-manifest.js",
228
+ "services/release-payload.js",
229
+ "services/release-source.js",
230
+ "services/repair-intents.js",
231
+ "services/repair-receipt.js",
232
+ "services/review-copy.js",
233
+ "services/review-deferral.js",
234
+ "services/run-control-receipt.js",
235
+ "services/run-control.js",
236
+ "services/run-controller-state.js",
237
+ "services/run-engine-bindings.js",
238
+ "services/run-observations.js",
239
+ "services/run-projection.js",
240
+ "services/run-recovery-guard.js",
241
+ "services/run-recovery.js",
242
+ "services/run-start-custody.js",
243
+ "services/runs.js",
244
+ "services/runtime-budget.js",
245
+ "services/runtime-command.js",
246
+ "services/schema-validation.js",
247
+ "services/scope-writer-cleanup.js",
248
+ "services/session-identity.js",
249
+ "services/sessions.js",
250
+ "services/setup.js",
251
+ "services/stage-context.js",
252
+ "services/stage-handlers.js",
253
+ "services/stage-lifecycle.js",
254
+ "services/stage-pause.js",
255
+ "services/stage-report-renderer.js",
256
+ "services/stage-work-graph.js",
257
+ "services/tooling.js",
258
+ "services/usage.js",
259
+ "services/verification-matrix.js",
260
+ "services/version-status.js",
261
+ "services/vnext-code-review.js",
262
+ "services/vnext-code.js",
263
+ "services/vnext-contracts.js",
264
+ "services/vnext-execution-profile.js",
265
+ "services/vnext-fanout.js",
266
+ "services/vnext-merge.js",
267
+ "services/vnext-plan-review.js",
268
+ "services/vnext-plan.js",
269
+ "services/vnext-protocolize.js",
270
+ "services/vnext-specify.js",
271
+ "services/vnext-workspace-policy.js",
272
+ "services/work-registry.js",
273
+ "services/workspace-bootstrap.js",
274
+ "services/workspace-files.js",
275
+ "services/worktrees.js",
276
+ "shared/entity-references.js",
277
+ "shared/errors.js",
278
+ "shared/json.js",
279
+ "storage/agent-output.js",
280
+ "storage/database.js",
281
+ "storage/foreign-key-transaction.js",
282
+ "storage/immutable-input.js",
283
+ "storage/import-guard.js",
284
+ "storage/native-cleanup.js",
285
+ "storage/paths.js",
286
+ "storage/projection-lock.js",
287
+ "storage/work-references.js",
288
+ "storage/writer-contract.js"
289
+ ],
290
+ "dependencies": [
291
+ "ajv/dist/2020.js",
292
+ "ajv/dist/ajv.js",
293
+ "semver",
294
+ "shell-quote",
295
+ "smol-toml",
296
+ "yaml"
297
+ ]
298
+ }
299
+ }