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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/build-info.json +8 -8
  3. package/dist/cli/command-inputs.js +33 -3
  4. package/dist/cli/command-metadata.js +73 -2
  5. package/dist/cli/delivery.js +108 -0
  6. package/dist/cli/diagnostic-invocation.js +23 -7
  7. package/dist/cli/diagnostic.js +14 -3
  8. package/dist/cli/help.js +117 -1
  9. package/dist/cli/hook-ingress.js +36 -49
  10. package/dist/cli/interactive-invocation.js +33 -0
  11. package/dist/cli/interactive.js +114 -0
  12. package/dist/cli/memory-fix-invocation.js +52 -0
  13. package/dist/cli/memory-fix.js +100 -0
  14. package/dist/cli/memory-flow-admission.js +7 -3
  15. package/dist/cli/memory-init.js +5 -4
  16. package/dist/cli/native-hook-ingress.js +5 -1
  17. package/dist/cli/native-hook-preparation.js +31 -4
  18. package/dist/cli/run-cli.js +273 -19
  19. package/dist/cli.js +17 -3
  20. package/dist/domain/delivery-flows.js +8 -0
  21. package/dist/domain/distill-flow.js +20 -0
  22. package/dist/domain/interactive-flow.js +1 -0
  23. package/dist/domain/memory-fix-flow.js +2 -0
  24. package/dist/domain/review-fix-flow.js +2 -0
  25. package/dist/domain/source-snapshot-flows.js +4 -0
  26. package/dist/harness-runtime/bin/dd-agy.mjs +18 -3
  27. package/dist/harness-runtime/bin/dd-grok.mjs +15 -5
  28. package/dist/harness-runtime/bin/dd-zcode.mjs +15 -7
  29. package/dist/harness-runtime/lib/agy-error-boundary.mjs +18 -10
  30. package/dist/harness-runtime/lib/agy-native-inspection.mjs +12 -3
  31. package/dist/harness-runtime/lib/agy-native-inventory.mjs +19 -0
  32. package/dist/harness-runtime/lib/agy-terminal-envelope.mjs +13 -6
  33. package/dist/harness-runtime/lib/agy-transcript-stream.mjs +54 -0
  34. package/dist/harness-runtime/lib/daemon-observation.d.mts +1 -0
  35. package/dist/harness-runtime/lib/daemon-observation.mjs +12 -4
  36. package/dist/harness-runtime/lib/daemon-operations.mjs +20 -5
  37. package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +1 -0
  38. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +371 -114
  39. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +43 -3
  40. package/dist/harness-runtime/lib/dd-codex.mjs +36 -4
  41. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +25 -11
  42. package/dist/harness-runtime/lib/dd-droid.mjs +124 -11
  43. package/dist/harness-runtime/lib/dd-grok-daemon.d.mts +1 -0
  44. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +137 -25
  45. package/dist/harness-runtime/lib/dd-grok.mjs +4 -2
  46. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +114 -16
  47. package/dist/harness-runtime/lib/dd-opencode.mjs +10 -7
  48. package/dist/harness-runtime/lib/dd-zcode-daemon.d.mts +1 -0
  49. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +115 -22
  50. package/dist/harness-runtime/lib/dd-zcode.mjs +75 -36
  51. package/dist/harness-runtime/lib/delegation-instructions.d.mts +1 -0
  52. package/dist/harness-runtime/lib/delegation-instructions.mjs +10 -3
  53. package/dist/harness-runtime/lib/delivery-command.mjs +84 -0
  54. package/dist/harness-runtime/lib/driver-recovery.mjs +0 -19
  55. package/dist/harness-runtime/lib/droid-observation.mjs +9 -5
  56. package/dist/harness-runtime/lib/lifecycle-contract.d.mts +1 -0
  57. package/dist/harness-runtime/lib/lifecycle-contract.mjs +2 -1
  58. package/dist/harness-runtime/lib/native-hook-command.d.mts +3 -2
  59. package/dist/harness-runtime/lib/native-hook-command.mjs +63 -8
  60. package/dist/harness-runtime/lib/native-streams.mjs +3 -10
  61. package/dist/harness-runtime/lib/opencode-plugin.mjs +36 -9
  62. package/dist/harness-runtime/lib/operation-errors.mjs +13 -0
  63. package/dist/harness-runtime/lib/operation-progress.mjs +6 -2
  64. package/dist/harness-runtime/lib/process-json.mjs +4 -5
  65. package/dist/harness-runtime/lib/tool-observations.mjs +20 -10
  66. package/dist/harness-runtime/lib/zcode-native-hook-ownership.mjs +109 -0
  67. package/dist/schemas/code-work-result.schema.json +2 -1
  68. package/dist/schemas/delivery-stage-result.schema.json +286 -0
  69. package/dist/schemas/distill-stage-result.schema.json +667 -0
  70. package/dist/schemas/distill-work-result.schema.json +428 -0
  71. package/dist/schemas/execution-bundle.schema.json +6 -2
  72. package/dist/schemas/interactive-stage-result.schema.json +47 -0
  73. package/dist/schemas/interactive-work-result.schema.json +63 -0
  74. package/dist/schemas/mb-fix-stage-result.schema.json +93 -0
  75. package/dist/schemas/project-flow-pack-manifest.schema.json +2 -2
  76. package/dist/schemas/review-fix-stage-result.schema.json +210 -0
  77. package/dist/schemas/run-control-receipt.schema.json +1 -0
  78. package/dist/services/assigned-lifecycle.js +382 -0
  79. package/dist/services/check-runner-evidence.js +45 -8
  80. package/dist/services/cli-operation-classifier.js +6 -1
  81. package/dist/services/code-checks.js +46 -29
  82. package/dist/services/codex-hook-delivery.js +11 -2
  83. package/dist/services/controller-execution.js +4 -1
  84. package/dist/services/controller-fanout.js +9 -4
  85. package/dist/services/controller-memory-worker.js +6 -4
  86. package/dist/services/controller-result-authority.js +122 -0
  87. package/dist/services/dashboard.js +4 -2
  88. package/dist/services/delivery-access.js +123 -0
  89. package/dist/services/delivery-approval.js +68 -0
  90. package/dist/services/delivery-artifact.js +86 -0
  91. package/dist/services/delivery-checks.js +178 -0
  92. package/dist/services/delivery-execution.js +181 -0
  93. package/dist/services/delivery-intents.js +212 -0
  94. package/dist/services/delivery-operation.js +81 -0
  95. package/dist/services/delivery-policy.js +91 -0
  96. package/dist/services/delivery-procedures.js +43 -0
  97. package/dist/services/delivery-request.js +99 -0
  98. package/dist/services/delivery-result.js +144 -0
  99. package/dist/services/delivery-stage-commands.js +26 -0
  100. package/dist/services/delivery-stage-entry.js +56 -0
  101. package/dist/services/delivery-stage-handlers.js +135 -0
  102. package/dist/services/delivery-stage-results.js +94 -0
  103. package/dist/services/descriptor-coordinator-session.js +3 -1
  104. package/dist/services/descriptor-run-start.js +24 -4
  105. package/dist/services/descriptor-stage-coordinator.js +78 -10
  106. package/dist/services/descriptor-stage-settlement.js +59 -14
  107. package/dist/services/diagnostic-repair-intake.js +123 -0
  108. package/dist/services/diagnostic-repair-source-intake.js +102 -0
  109. package/dist/services/diagnostic-report.js +30 -0
  110. package/dist/services/diagnostic-run-bundle.js +48 -9
  111. package/dist/services/diagnostic-snapshot.js +2 -2
  112. package/dist/services/diagnostic-stage-handlers.js +3 -2
  113. package/dist/services/diagnostic-stage-results.js +26 -0
  114. package/dist/services/diagnostic-start.js +56 -6
  115. package/dist/services/diagnostic-work-collection.js +3 -3
  116. package/dist/services/distill-document-publication.js +280 -0
  117. package/dist/services/distill-promotion.js +197 -0
  118. package/dist/services/distill-publication-custody.js +162 -0
  119. package/dist/services/distill-stage-handlers.js +249 -0
  120. package/dist/services/distill-stage-results.js +105 -0
  121. package/dist/services/distill-user-inputs.js +4 -0
  122. package/dist/services/distill-work-batch.js +42 -0
  123. package/dist/services/distill-work-collection.js +74 -0
  124. package/dist/services/distill-work-registration.js +93 -0
  125. package/dist/services/distill-work-results.js +98 -0
  126. package/dist/services/eval-snapshots.js +17 -7
  127. package/dist/services/external-work-launch.js +8 -2
  128. package/dist/services/flow-catalog.js +24 -0
  129. package/dist/services/frozen-engine.js +19 -2
  130. package/dist/services/generic-execution.js +4 -3
  131. package/dist/services/harness-adapter.js +2 -14
  132. package/dist/services/hook-memory-routing.js +1 -1
  133. package/dist/services/hooks.js +31 -6
  134. package/dist/services/hybrid-contracts.js +6 -1
  135. package/dist/services/installed-flow-source.js +37 -2
  136. package/dist/services/interactive-attached-session.js +215 -0
  137. package/dist/services/interactive-baseline-custody.js +54 -0
  138. package/dist/services/interactive-before-versions.js +148 -0
  139. package/dist/services/interactive-bundle-data.js +85 -0
  140. package/dist/services/interactive-checks.js +179 -0
  141. package/dist/services/interactive-execution.js +33 -0
  142. package/dist/services/interactive-finish-integration.js +236 -0
  143. package/dist/services/interactive-git-baseline.js +230 -0
  144. package/dist/services/interactive-git-dispatch.js +278 -0
  145. package/dist/services/interactive-input-delivery.js +82 -0
  146. package/dist/services/interactive-mutation-scope.js +231 -0
  147. package/dist/services/interactive-request.js +287 -0
  148. package/dist/services/interactive-run-bundle.js +109 -0
  149. package/dist/services/interactive-stage-entry.js +41 -0
  150. package/dist/services/interactive-stage-handlers.js +289 -0
  151. package/dist/services/interactive-stage-observation.js +136 -0
  152. package/dist/services/interactive-stage-results.js +111 -0
  153. package/dist/services/interactive-user-inputs.js +142 -0
  154. package/dist/services/interactive-work-batch.js +43 -0
  155. package/dist/services/interactive-work-collection.js +119 -0
  156. package/dist/services/interactive-work-completion.js +180 -0
  157. package/dist/services/interactive-work-context.js +249 -0
  158. package/dist/services/interactive-work-history.js +112 -0
  159. package/dist/services/interactive-work-registration.js +124 -0
  160. package/dist/services/interactive-work-results.js +115 -0
  161. package/dist/services/interactive-work-start.js +155 -0
  162. package/dist/services/lifecycle-invocations.js +214 -24
  163. package/dist/services/managed-daemon-binding.js +52 -1
  164. package/dist/services/memory-compose-handler.js +2 -22
  165. package/dist/services/memory-finish-integration.js +58 -17
  166. package/dist/services/memory-fix-finish-integration.js +129 -0
  167. package/dist/services/memory-fix-git-dispatch.js +139 -0
  168. package/dist/services/memory-fix-intake-custody.js +11 -0
  169. package/dist/services/memory-fix-intake-settlement.js +127 -0
  170. package/dist/services/memory-fix-publication.js +196 -0
  171. package/dist/services/memory-fix-repair-admission.js +116 -0
  172. package/dist/services/memory-fix-request.js +141 -0
  173. package/dist/services/memory-fix-run-bundle.js +83 -0
  174. package/dist/services/memory-fix-scope.js +78 -0
  175. package/dist/services/memory-fix-sources.js +85 -0
  176. package/dist/services/memory-fix-stage-entry.js +24 -0
  177. package/dist/services/memory-fix-stage-handlers.js +282 -0
  178. package/dist/services/memory-fix-stage-results.js +106 -0
  179. package/dist/services/memory-fix-workspace.js +127 -0
  180. package/dist/services/memory-flow-user-inputs.js +88 -0
  181. package/dist/services/memory-init-contexts.js +55 -5
  182. package/dist/services/memory-init-controller-start.js +20 -4
  183. package/dist/services/memory-layer.js +7 -3
  184. package/dist/services/memory-native-review-copy.js +27 -3
  185. package/dist/services/memory-repair-document-delta.js +1 -0
  186. package/dist/services/project-flow-source.js +195 -0
  187. package/dist/services/project-git-integration.js +92 -0
  188. package/dist/services/repair-intents.js +2 -7
  189. package/dist/services/retained-native-hook.js +245 -0
  190. package/dist/services/review-fix-handoff.js +82 -0
  191. package/dist/services/review-fix-intake-custody.js +17 -0
  192. package/dist/services/review-fix-stage-entry.js +43 -0
  193. package/dist/services/review-fix-stage-handlers.js +176 -0
  194. package/dist/services/review-fix-stage-results.js +86 -0
  195. package/dist/services/run-control-worker.js +42 -1
  196. package/dist/services/run-control.js +40 -5
  197. package/dist/services/run-controller-adapter.js +37 -3
  198. package/dist/services/run-controller-process.js +12 -4
  199. package/dist/services/run-controller-recovery.js +46 -6
  200. package/dist/services/run-controller-state.js +3 -2
  201. package/dist/services/run-controller.js +209 -43
  202. package/dist/services/run-recovery.js +31 -2
  203. package/dist/services/run-timeline.js +59 -0
  204. package/dist/services/runs.js +18 -31
  205. package/dist/services/sessions.js +2 -1
  206. package/dist/services/stage-handlers.js +55 -3
  207. package/dist/services/stage-lifecycle.js +4 -3
  208. package/dist/services/stage-pause.js +26 -9
  209. package/dist/services/stage-report-renderer.js +2 -6
  210. package/dist/services/technical-notes.js +31 -0
  211. package/dist/services/verification-matrix.js +1 -1
  212. package/dist/services/version-status.js +1 -1
  213. package/dist/services/vnext-code-review.js +74 -5
  214. package/dist/services/vnext-code.js +8 -32
  215. package/dist/services/vnext-fanout.js +53 -8
  216. package/dist/services/vnext-merge.js +10 -5
  217. package/dist/services/vnext-plan-review.js +1 -1
  218. package/dist/services/vnext-plan.js +1 -1
  219. package/dist/services/vnext-protocolize.js +1 -1
  220. package/dist/services/vnext-specify.js +10 -5
  221. package/dist/services/work-registry.js +252 -66
  222. package/dist/services/worktrees.js +7 -4
  223. package/dist/storage/database.js +2 -1
  224. package/dist/verification-components.json +82 -0
  225. package/package.json +3 -2
  226. package/tools/verification-jobs.mjs +35 -10
@@ -0,0 +1,123 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { types } from "node:util";
3
+ import { AppError } from "../shared/errors.js";
4
+ import { hybridDigest } from "./hybrid-contracts.js";
5
+ import { resolveDeliveryBinding } from "./delivery-policy.js";
6
+ import { executeDeliveryStep } from "./delivery-execution.js";
7
+ const dimensions = ["identity", "authority", "target"];
8
+ const freshnessMs = 30000;
9
+ const record = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
10
+ function refuse(reason) {
11
+ throw new AppError("delivery_access_required", "Delivery access is not established for the selected operation; do not login or change context automatically", 2, { reason, effect: "no_effect" });
12
+ }
13
+ function identity(context, input) {
14
+ return hybridDigest({ home: context.ddFlowHome, project_id: input.projectId, run_id: input.runId,
15
+ operation_id: input.operationId, workspace: input.workspace, binding: input.binding });
16
+ }
17
+ /** Private same-process observation, not serialized publication authority.
18
+ * The owning Stage still establishes project-policy/readback safety and checks
19
+ * separate scoped approval, artifact custody and retained operation intent. */
20
+ export class PreparedDeliveryAccess {
21
+ #scope;
22
+ #pid = process.pid;
23
+ #expires;
24
+ #consumed = false;
25
+ #report;
26
+ constructor(context, input, observed, report) {
27
+ this.#scope = identity(context, input);
28
+ this.#expires = observed + freshnessMs;
29
+ this.#report = structuredClone(report);
30
+ }
31
+ static async observe(context, raw) {
32
+ if (context.db.isOwnedWriteTransaction?.())
33
+ refuse("observation_outside_writer_required");
34
+ const binding = resolveDeliveryBinding({ schema_id: "dd-flow/delivery-policy@1", bindings: [raw.binding] }, raw.binding.selector);
35
+ const input = { ...raw, binding }, times = new Map(), processes = [];
36
+ const assertCurrent = () => {
37
+ const result = raw.assertCurrent();
38
+ if (types.isPromise(result)) {
39
+ void result.catch(() => { });
40
+ refuse("synchronous_owner_fence_required");
41
+ }
42
+ };
43
+ assertCurrent();
44
+ if (binding.access.applicability === "required") {
45
+ const access = binding.access, procedure = binding.procedures.access_readback, deadline = Date.now() + procedure.timeout_ms;
46
+ // The project-owned procedure is the structured extractor. Each step
47
+ // emits only this public-context envelope (possibly partial/empty).
48
+ for (const step of procedure.procedure_steps) {
49
+ const remaining = deadline - Date.now();
50
+ if (remaining <= 0)
51
+ refuse("readback_failed");
52
+ let malformed = false;
53
+ try {
54
+ const result = await executeDeliveryStep(context, { projectId: input.projectId, runId: input.runId,
55
+ operationId: `access-${randomUUID()}`, stepId: step.id, workspace: input.workspace, procedure, deadlineAt: deadline,
56
+ assertCurrent, admit() { }, consumeOutput(bytes) {
57
+ let value;
58
+ try {
59
+ value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
60
+ }
61
+ catch {
62
+ malformed = true;
63
+ return;
64
+ }
65
+ if (!record(value) || value.schema_id !== "dd-flow/delivery-access-observation@1"
66
+ || Object.keys(value).some(key => !["schema_id", ...dimensions].includes(key))) {
67
+ malformed = true;
68
+ return;
69
+ }
70
+ for (const dimension of dimensions) {
71
+ if (!Object.hasOwn(value, dimension))
72
+ continue;
73
+ const expected = access[dimension], observed = value[dimension];
74
+ if (expected === "not_required") {
75
+ if (observed !== "not_required") {
76
+ malformed = true;
77
+ return;
78
+ }
79
+ }
80
+ else if (!record(observed) || Object.keys(observed).sort().join() !== "id,kind"
81
+ || observed.kind !== expected.kind || observed.id !== expected.id) {
82
+ malformed = true;
83
+ return;
84
+ }
85
+ times.set(dimension, Date.parse(context.now()));
86
+ }
87
+ } });
88
+ processes.push(result.process_id);
89
+ if (Date.now() >= deadline)
90
+ refuse("readback_failed");
91
+ }
92
+ catch {
93
+ refuse("readback_failed");
94
+ }
95
+ if (malformed)
96
+ refuse("context_mismatch_or_unsafe_output");
97
+ }
98
+ if (!times.has("identity") || !times.has("target") || access.authority !== "not_required" && !times.has("authority"))
99
+ refuse("required_context_not_observable");
100
+ }
101
+ assertCurrent();
102
+ const now = Date.parse(context.now());
103
+ if (!Number.isFinite(now) || [...times.values()].some(time => !Number.isFinite(time) || time > now))
104
+ refuse("observation_clock_invalid");
105
+ const observed = times.size ? Math.min(...times.values()) : now;
106
+ const prepared = new PreparedDeliveryAccess(context, input, observed, { schema_id: "dd-flow/delivery-access@1",
107
+ operation_id: input.operationId, binding_id: binding.id, observed_at: new Date(observed).toISOString(),
108
+ applicability: binding.access.applicability === "required" ? "verified" : "not_required",
109
+ identity: binding.access.applicability === "required" ? "verified" : "not_required",
110
+ authority: binding.access.applicability === "required" && binding.access.authority !== "not_required" ? "verified" : "not_required",
111
+ target: binding.access.applicability === "required" ? "verified" : "not_required", expected: binding.access, processes });
112
+ prepared.assertFresh(context, input);
113
+ return prepared;
114
+ }
115
+ value() { return structuredClone(this.#report); }
116
+ assertFresh(context, input) {
117
+ const now = Date.parse(context.now());
118
+ if (this.#pid !== process.pid || this.#consumed || identity(context, input) !== this.#scope
119
+ || !Number.isFinite(now) || now < Date.parse(this.#report.observed_at) || now >= this.#expires)
120
+ refuse("fresh_same_process_readback_required");
121
+ }
122
+ consume(context, input) { this.assertFresh(context, input); this.#consumed = true; }
123
+ }
@@ -0,0 +1,68 @@
1
+ import { AppError } from "../shared/errors.js";
2
+ import { hybridDigest } from "./hybrid-contracts.js";
3
+ import { readPreparedMemoryFlowUserInputs } from "./memory-flow-user-inputs.js";
4
+ import { resolveDeliveryBinding } from "./delivery-policy.js";
5
+ const digest = (value) => typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
6
+ const record = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
7
+ const keys = (value, names) => Object.keys(value).sort().join() === [...names].sort().join();
8
+ const text = (value) => typeof value === "string" && Boolean(value.trim()) && value.length <= 4096 && !value.includes("\0");
9
+ function refuse(reason) {
10
+ throw new AppError("delivery_approval_invalid", "Delivery needs current user approval for the exact selected operation", 2, { reason, effect: "no_effect" });
11
+ }
12
+ /** Public decision scope, not observed artifact bytes, account access or grant.
13
+ * The Stage owner supplies its retained artifact/target selection and freezes
14
+ * the same scope in the operation intent before execution. */
15
+ export function deliveryApprovalScope(scope, bundle, selection) {
16
+ const flow = bundle.manifest.flow_id;
17
+ if (flow !== "release" && flow !== "deploy" && flow !== "publish")
18
+ refuse("delivery_bundle_required");
19
+ const binding = resolveDeliveryBinding({ schema_id: "dd-flow/delivery-policy@1", bindings: [selection.binding] }, selection.binding.selector);
20
+ if (binding.selector.operation !== flow || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(selection.operationId)
21
+ || !record(selection.artifact) || !keys(selection.artifact, ["subject_ref", "version", "sha256"])
22
+ || selection.artifact.subject_ref !== binding.selector.subject_ref || !text(selection.artifact.version) || !digest(selection.artifact.sha256))
23
+ refuse("exact_operation_scope_required");
24
+ const request = bundle.execution.snapshot().scope.requestId;
25
+ if (!text(request) || !digest(bundle.sha256))
26
+ refuse("original_request_required");
27
+ return { schema_id: "dd-flow/delivery-approval-scope@1", project_id: scope.projectId, run_id: scope.runId,
28
+ request_id: request, bundle_sha256: bundle.sha256, operation_id: selection.operationId, flow_id: flow,
29
+ binding_id: binding.id, binding_sha256: hybridDigest(binding), selector: binding.selector, artifact: structuredClone(selection.artifact) };
30
+ }
31
+ /** Custody and exact scope only: interpreting whether the cited user really
32
+ * approves this operation remains the owning agent/review's responsibility.
33
+ * Flow startup, a question or successful login never becomes approval here.
34
+ * This prepared value is not a publication token or dispatch authority. */
35
+ export function prepareDeliveryApproval(context, scope, bundle, selection, proposal) {
36
+ if (!record(proposal) || !keys(proposal, ["scope_sha256", "user_inputs_sha256", "basis", "expires_at", "reason"])
37
+ || !digest(proposal.scope_sha256) || !digest(proposal.user_inputs_sha256) || !text(proposal.reason)
38
+ || !text(proposal.expires_at) || !record(proposal.basis) || !keys(proposal.basis, ["role", "event_id", "sha256"])
39
+ || !["request", "answer"].includes(proposal.basis.role) || !Number.isSafeInteger(proposal.basis.event_id)
40
+ || proposal.basis.event_id < 0 || !digest(proposal.basis.sha256))
41
+ refuse("invalid_approval_proposal");
42
+ const selected = deliveryApprovalScope(scope, bundle, selection), flow = selected.flow_id;
43
+ const users = readPreparedMemoryFlowUserInputs(context, scope, flow, bundle), inputsHash = hybridDigest(users);
44
+ if (proposal.scope_sha256 !== hybridDigest(selected))
45
+ refuse("approval_scope_mismatch");
46
+ if (proposal.user_inputs_sha256 !== inputsHash)
47
+ refuse("current_user_input_required");
48
+ const source = users.sources.find(item => item.role === proposal.basis.role && item.event_id === proposal.basis.event_id && item.sha256 === proposal.basis.sha256);
49
+ if (!source || proposal.basis.role === "request" && proposal.basis.event_id !== 0)
50
+ refuse("delivered_approval_basis_required");
51
+ const expiry = Date.parse(proposal.expires_at), approvedAt = context.now();
52
+ if (!Number.isFinite(expiry) || !Number.isFinite(Date.parse(approvedAt)) || expiry <= Date.parse(approvedAt))
53
+ refuse("approval_expired");
54
+ const value = { schema_id: "dd-flow/delivery-approval@1", scope: selected, scope_sha256: hybridDigest(selected),
55
+ user_inputs_sha256: inputsHash, approver: "delivered_user", basis: structuredClone(proposal.basis),
56
+ approved_at: approvedAt, expires_at: new Date(expiry).toISOString(), interpretation: proposal.reason };
57
+ return {
58
+ value: () => structuredClone(value),
59
+ /** SQL/data-only fence. A later answer (including revocation) invalidates
60
+ * the interpretation until the owning Stage reassesses the full input set. */
61
+ assertCustody(writer) {
62
+ if (hybridDigest(readPreparedMemoryFlowUserInputs(writer, scope, flow, bundle)) !== inputsHash)
63
+ refuse("approval_inputs_changed");
64
+ if (!Number.isFinite(Date.parse(writer.now())) || Date.parse(writer.now()) >= expiry)
65
+ refuse("approval_expired");
66
+ }
67
+ };
68
+ }
@@ -0,0 +1,86 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { createHash } from "node:crypto";
4
+ import { AppError } from "../shared/errors.js";
5
+ function refuse(reason) {
6
+ throw new AppError("delivery_artifact_invalid", "Delivery requires the exact accepted artifact bytes", 2, { reason, phase: "prepare", effect: "no_effect", recoverable: true });
7
+ }
8
+ function observe(workspace, logicalPath) {
9
+ if (!path.isAbsolute(workspace) || typeof logicalPath !== "string" || !logicalPath || logicalPath.length > 4096
10
+ || logicalPath.includes("\\") || logicalPath.includes("\0") || path.isAbsolute(logicalPath)
11
+ || logicalPath.split("/").some(part => !part || part === "." || part === ".." || part === ".git"))
12
+ refuse("contained_artifact_required");
13
+ try {
14
+ if (fs.realpathSync(workspace) !== workspace || !fs.lstatSync(workspace).isDirectory())
15
+ refuse("canonical_workspace_required");
16
+ let file = workspace;
17
+ for (const part of logicalPath.split("/").slice(0, -1)) {
18
+ file = path.join(file, part);
19
+ if (!fs.lstatSync(file).isDirectory())
20
+ refuse("contained_artifact_required");
21
+ }
22
+ file = path.join(workspace, logicalPath);
23
+ if (!fs.lstatSync(file).isFile())
24
+ refuse("regular_artifact_required");
25
+ const fd = fs.openSync(file, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0) | (fs.constants.O_NONBLOCK ?? 0));
26
+ try {
27
+ const before = fs.fstatSync(fd, { bigint: true }), digest = createHash("sha256"), buffer = Buffer.allocUnsafe(256 * 1024);
28
+ if (!before.isFile())
29
+ refuse("regular_artifact_required");
30
+ let length = 0n;
31
+ for (;;) {
32
+ const count = fs.readSync(fd, buffer, 0, buffer.length, null);
33
+ if (!count)
34
+ break;
35
+ digest.update(buffer.subarray(0, count));
36
+ length += BigInt(count);
37
+ }
38
+ const after = fs.fstatSync(fd, { bigint: true }), named = fs.lstatSync(file, { bigint: true });
39
+ if (length !== before.size || after.size !== before.size || after.mtimeNs !== before.mtimeNs || after.ctimeNs !== before.ctimeNs
40
+ || !named.isFile() || named.dev !== before.dev || named.ino !== before.ino || fs.realpathSync(file) !== file)
41
+ refuse("artifact_changed_during_observation");
42
+ return { sha256: digest.digest("hex"), bytes: length.toString() };
43
+ }
44
+ finally {
45
+ fs.closeSync(fd);
46
+ }
47
+ }
48
+ catch (error) {
49
+ if (error instanceof AppError)
50
+ throw error;
51
+ return refuse("artifact_unavailable");
52
+ }
53
+ }
54
+ /** Exact file/archive bytes, or a project-owned manifest selecting immutable
55
+ * remote artifacts. A manifest digest alone does not prove its remote objects:
56
+ * the owning verify Stage must establish those references before approval.
57
+ * Observation is synchronous and outside SQL writers, immediately before the
58
+ * managed admission fence; it is not an OS lock against a hostile local writer. */
59
+ export class PreparedDeliveryArtifact {
60
+ #workspace;
61
+ #path;
62
+ #artifact;
63
+ #bytes;
64
+ constructor(workspace, logicalPath, artifact, bytes) {
65
+ this.#workspace = workspace;
66
+ this.#path = logicalPath;
67
+ this.#artifact = structuredClone(artifact);
68
+ this.#bytes = bytes;
69
+ }
70
+ static prepare(workspace, logicalPath, artifact) {
71
+ if (!artifact || Object.keys(artifact).sort().join() !== "sha256,subject_ref,version"
72
+ || typeof artifact.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(artifact.sha256)
73
+ || [artifact.subject_ref, artifact.version].some(value => typeof value !== "string" || !value.trim() || value.length > 4096 || value.includes("\0")))
74
+ refuse("accepted_artifact_required");
75
+ const actual = observe(workspace, logicalPath);
76
+ if (actual.sha256 !== artifact.sha256)
77
+ refuse("accepted_artifact_changed");
78
+ return new PreparedDeliveryArtifact(workspace, logicalPath, artifact, actual.bytes);
79
+ }
80
+ value() { return { schema_id: "dd-flow/delivery-artifact@1", workspace: this.#workspace, path: this.#path, artifact: structuredClone(this.#artifact), bytes: this.#bytes }; }
81
+ assertCurrent() {
82
+ const actual = observe(this.#workspace, this.#path);
83
+ if (actual.sha256 !== this.#artifact.sha256 || actual.bytes !== this.#bytes)
84
+ refuse("accepted_artifact_changed");
85
+ }
86
+ }
@@ -0,0 +1,178 @@
1
+ import { AppError } from "../shared/errors.js";
2
+ import { readDeliveryStageScope } from "./delivery-stage-entry.js";
3
+ import { readDescriptorStageInput } from "./descriptor-stage-coordinator.js";
4
+ import { readActiveDescriptorStageCoordinator, readAcceptedDescriptorStageSettlement } from "./descriptor-stage-settlement.js";
5
+ import { readAcceptedStageData } from "./stage-report-renderer.js";
6
+ import { prepareDeliveryProjectBinding } from "./delivery-policy.js";
7
+ import { PreparedDeliveryArtifact } from "./delivery-artifact.js";
8
+ import { deliveryApprovalScope } from "./delivery-approval.js";
9
+ import { hybridDigest } from "./hybrid-contracts.js";
10
+ import { codeCheckProfileHash, projectPolicyCheckDeclarations, runCodeChecks, PreparedCheckEvidence, PreparedRetainedCheckEvidence } from "./code-checks.js";
11
+ import { entityReferenceVariants } from "../shared/entity-references.js";
12
+ import { stageLifecycleMatchKey } from "./hooks.js";
13
+ import { assertDiagnosticSessionIndependence } from "./diagnostic-work-collection.js";
14
+ import { memoryCheckEnvironment } from "./memory-verification-checks.js";
15
+ function refuse(reason) {
16
+ throw new AppError("delivery_checks_invalid", "Delivery verification needs current project checks for the exact selected artifact", 2, { reason, effect: "no_effect" });
17
+ }
18
+ /** Compare actual provider Sessions, not model names or distinct logical IDs.
19
+ * Accepted generic settlement proves the author; current coordinator custody
20
+ * proves the verifier. Lifecycle match keys resolve paths before the writer. */
21
+ export function prepareDeliveryVerifierIndependence(context, input) {
22
+ if (input.stage !== "verify" || context.db.isOwnedWriteTransaction?.())
23
+ refuse("verification_stage_required");
24
+ const selected = readDeliveryStageScope(context, input), { scope, bundle } = selected;
25
+ const stages = selected.flowId === "deploy" ? ["prepare", "deploy"] : ["prepare"];
26
+ const sources = stages.map(stage => ({ stage, keys: entityReferenceVariants(input.runId)
27
+ .map(reference => stageLifecycleMatchKey("stage_finish", reference, stage, input.projectRoot)) }));
28
+ const observe = (reader) => {
29
+ selected.assertCustody(reader);
30
+ const physical = (sessionId) => {
31
+ const session = reader.db.get("SELECT harness,provider_session_id FROM sessions WHERE project_id=? AND run_id=? AND session_id=?", [scope.projectId, scope.runId, sessionId]);
32
+ if (!session?.harness || !session.provider_session_id)
33
+ refuse("verification_physical_owner_required");
34
+ return session;
35
+ };
36
+ const verifier = readActiveDescriptorStageCoordinator(reader, scope, bundle);
37
+ const authors = sources.map(source => readAcceptedDescriptorStageSettlement(reader, { ...scope, stage: source.stage }, bundle, source.keys));
38
+ const verifierSession = physical(verifier.session_id), authorSessions = authors.map(author => physical(author.binding.sessionId));
39
+ assertDiagnosticSessionIndependence([verifierSession], authorSessions);
40
+ return { verifier, authors, verifierSession, authorSessions };
41
+ };
42
+ const original = observe(context);
43
+ return (writer) => {
44
+ if (hybridDigest(observe(writer)) !== hybridDigest(original))
45
+ refuse("verification_physical_owner_changed");
46
+ };
47
+ }
48
+ /** Downstream acceptance reuses the original accepted execution proof, not
49
+ * today's declarations, invented hashes or a new run of publication checks. */
50
+ export function prepareDeliveryHistoricalChecks(context, input) {
51
+ if (context.db.isOwnedWriteTransaction?.())
52
+ refuse("preparation_outside_writer_required");
53
+ const selected = readDeliveryStageScope(context, input), { scope, bundle } = selected;
54
+ if (input.sourceStage !== (selected.flowId === "deploy" && input.stage === "deploy" ? "prepare" : "verify"))
55
+ refuse("accepted_verification_stage_required");
56
+ const keys = entityReferenceVariants(input.runId).map(reference => stageLifecycleMatchKey("stage_finish", reference, input.sourceStage, input.projectRoot));
57
+ const read = (reader) => {
58
+ const accepted = readAcceptedStageData(reader, { ...scope, stage: input.sourceStage });
59
+ const data = JSON.parse(accepted.bytes.toString("utf8"));
60
+ const rows = reader.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='delivery.stage_checks' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.stage')=? LIMIT 2", [scope.projectId, scope.runId, input.sourceStage]);
61
+ if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 1024 * 1024)
62
+ refuse("accepted_verification_required");
63
+ const value = JSON.parse(rows[0].payload_json), receipt = value.receipt;
64
+ if (data.schema_id !== "dd-flow/delivery-stage-result@1" || data.flow_id !== selected.flowId || data.run_id !== input.runId || data.stage !== input.sourceStage
65
+ || !receipt || value.sha256 !== hybridDigest(receipt) || value.sha256 !== data.details?.verification_sha256
66
+ || receipt.schema_id !== "dd-flow/delivery-checks@1" || receipt.run_id !== input.runId || receipt.stage !== input.sourceStage
67
+ || receipt.bundle_sha256 !== bundle.sha256 || receipt.input_sha256 !== data.input_sha256
68
+ || receipt.operation_scope_sha256 !== input.operationScopeSha256 || data.details.delivery_scope_sha256 !== input.operationScopeSha256
69
+ || !Array.isArray(receipt.checks) || !receipt.checks.length)
70
+ refuse("accepted_verification_changed");
71
+ return { accepted, value, owner: readAcceptedDescriptorStageSettlement(reader, { ...scope, stage: input.sourceStage }, bundle, keys) };
72
+ };
73
+ const original = read(context);
74
+ const proof = PreparedRetainedCheckEvidence.prepare(context, { ...scope, references: original.value.receipt.checks });
75
+ return { sha256: original.value.sha256, receipt: structuredClone(original.value.receipt),
76
+ revalidate() { proof.revalidate(context); },
77
+ assertCustody(writer) {
78
+ selected.assertCustody(writer);
79
+ proof.assertCustody(writer);
80
+ if (hybridDigest(read(writer)) !== hybridDigest(original))
81
+ refuse("accepted_verification_changed");
82
+ }
83
+ };
84
+ }
85
+ /** Immediately before an external effect, historical success is insufficient:
86
+ * the existing check owner must still match today's workspace and policy.
87
+ * This also catches an edited publish/deploy script with unchanged argv. */
88
+ export function prepareDeliveryCurrentAcceptedChecks(context, input) {
89
+ const historical = prepareDeliveryHistoricalChecks(context, input), selected = readDeliveryStageScope(context, input);
90
+ if (!selected.run.run_root)
91
+ refuse("run_root_required");
92
+ const workspace = selected.bundle.execution.snapshot().scope.workspace;
93
+ const checks = [...projectPolicyCheckDeclarations(workspace, "code"), ...projectPolicyCheckDeclarations(workspace, "readiness")];
94
+ const environment = memoryCheckEnvironment(context, selected.scope.projectId, input.runId);
95
+ const proof = PreparedCheckEvidence.prepare(context, { projectId: selected.scope.projectId, runId: input.runId,
96
+ runHome: selected.run.run_root, workspaceRoot: workspace, scope: "aggregate", checks,
97
+ expectedProfileHash: historical.receipt.profile_sha256, evidenceContext: historical.receipt.evidence_context,
98
+ ...(environment ? { checkEnvironment: environment } : {}) });
99
+ if (!proof.qualityPassed() || hybridDigest(proof.retainedReferences(context)) !== hybridDigest(historical.receipt.checks))
100
+ refuse("accepted_verification_changed");
101
+ return {
102
+ revalidate() {
103
+ if (hybridDigest(memoryCheckEnvironment(context, selected.scope.projectId, input.runId) ?? null) !== hybridDigest(environment ?? null))
104
+ refuse("managed_check_home_changed");
105
+ historical.revalidate();
106
+ proof.revalidate(context);
107
+ },
108
+ assertCustody(writer) { historical.assertCustody(writer); proof.assertCustody(writer); }
109
+ };
110
+ }
111
+ /** The existing aggregate runner/check receipts, not model-reported tests.
112
+ * Declarations come from project policy, never the result being accepted.
113
+ * Semantic adequacy and independent verification remain Stage responsibilities. */
114
+ export function prepareDeliveryChecks(context, input) {
115
+ if (context.db.isOwnedWriteTransaction?.() || !["prepare", "verify"].includes(input.stage))
116
+ refuse("verification_stage_required");
117
+ if (!input.artifact || typeof input.artifact !== "object" || Array.isArray(input.artifact))
118
+ refuse("accepted_artifact_selection_required");
119
+ const selected = readDeliveryStageScope(context, input), { scope, bundle } = selected;
120
+ const independent = input.stage === "verify" ? prepareDeliveryVerifierIndependence(context, input) : undefined;
121
+ const packet = readDescriptorStageInput(context, scope, bundle);
122
+ if (!packet.input.predecessor || !selected.run.run_root)
123
+ refuse("accepted_predecessor_required");
124
+ const previousStage = packet.input.predecessor.stage;
125
+ const previous = readAcceptedStageData(context, { ...scope, stage: previousStage });
126
+ const prior = JSON.parse(previous.bytes.toString("utf8"));
127
+ const decision = prior.details.decision;
128
+ if (!decision || input.stage !== "prepare" && hybridDigest(prior.details.artifact) !== hybridDigest(input.artifact))
129
+ refuse("accepted_artifact_selection_required");
130
+ const workspace = bundle.execution.snapshot().scope.workspace;
131
+ const policy = prepareDeliveryProjectBinding(workspace, decision.policy, decision.binding.selector);
132
+ if (hybridDigest(policy.binding) !== hybridDigest(decision.binding))
133
+ refuse("accepted_policy_changed");
134
+ const artifact = PreparedDeliveryArtifact.prepare(workspace, input.artifact.path, { subject_ref: input.artifact.subject_ref, version: input.artifact.version, sha256: input.artifact.sha256 });
135
+ if (input.artifact.version !== decision.version)
136
+ refuse("accepted_version_required");
137
+ const operation = deliveryApprovalScope(scope, bundle, { operationId: decision.operation_id, binding: policy.binding,
138
+ artifact: { subject_ref: input.artifact.subject_ref, version: input.artifact.version, sha256: input.artifact.sha256 } });
139
+ const checks = [...projectPolicyCheckDeclarations(workspace, "code"), ...projectPolicyCheckDeclarations(workspace, "readiness")];
140
+ if (!checks.length)
141
+ refuse("project_verification_checks_required");
142
+ const owner = readActiveDescriptorStageCoordinator(context, scope, bundle), profile = codeCheckProfileHash(workspace, checks);
143
+ const environment = memoryCheckEnvironment(context, scope.projectId, input.runId);
144
+ const evidenceContext = `delivery:${hybridDigest({ operation, stage: input.stage, input: packet.inputSha256, users: selected.userInputsSha256 })}`;
145
+ const command = { projectId: scope.projectId, runId: scope.runId, runHome: selected.run.run_root, workspaceRoot: workspace,
146
+ scope: "aggregate", checks, expectedProfileHash: profile, evidenceContext,
147
+ ...(environment ? { checkEnvironment: environment } : {}) };
148
+ const assertCustody = (writer) => {
149
+ selected.assertCustody(writer);
150
+ independent?.(writer);
151
+ if (hybridDigest(readDescriptorStageInput(writer, scope, bundle)) !== hybridDigest(packet)
152
+ || hybridDigest(readAcceptedStageData(writer, { ...scope, stage: previousStage })) !== hybridDigest(previous)
153
+ || hybridDigest(readActiveDescriptorStageCoordinator(writer, scope, bundle)) !== hybridDigest(owner))
154
+ refuse("verification_owner_or_inputs_changed");
155
+ };
156
+ const revalidate = () => {
157
+ assertCustody(context);
158
+ policy.assertCurrent();
159
+ artifact.assertCurrent();
160
+ if (hybridDigest(memoryCheckEnvironment(context, scope.projectId, input.runId) ?? null) !== hybridDigest(environment ?? null))
161
+ refuse("managed_check_home_changed");
162
+ if (codeCheckProfileHash(workspace, checks) !== profile)
163
+ refuse("verification_policy_changed");
164
+ };
165
+ revalidate();
166
+ return { operation: structuredClone(operation), artifact: structuredClone(input.artifact), assertCustody, revalidate,
167
+ async run() { revalidate(); const result = await runCodeChecks(context, command); revalidate(); return result; },
168
+ evidence() {
169
+ revalidate();
170
+ const proof = PreparedCheckEvidence.prepare(context, command);
171
+ if (!proof.qualityPassed())
172
+ refuse("verification_checks_failed");
173
+ return { proof, receipt: { schema_id: "dd-flow/delivery-checks@1", run_id: scope.runId, stage: input.stage,
174
+ bundle_sha256: bundle.sha256, input_sha256: packet.inputSha256, operation_scope_sha256: hybridDigest(operation),
175
+ evidence_context: evidenceContext, profile_sha256: profile, checks: proof.retainedReferences(context) } };
176
+ }
177
+ };
178
+ }
@@ -0,0 +1,181 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { spawn } from "node:child_process";
4
+ import { types } from "node:util";
5
+ import { AppError } from "../shared/errors.js";
6
+ import { prepareDeliveryProcedure } from "./delivery-procedures.js";
7
+ import { managedLeaseMonitor } from "./managed-lease-monitor.js";
8
+ import { physicalStartup } from "./physical-startup.js";
9
+ import { registerManagedProcess, confirmManagedProcess, renewManagedProcess, finishManagedProcess, assertManagedProcessLease, processTreeIsAlive } from "./managed-processes.js";
10
+ import { stopProcessGroup } from "../harness-runtime/lib/managed-daemon.mjs";
11
+ const id = (value) => typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(value);
12
+ /** One step of an already retained delivery intent, not a public launcher or
13
+ * approval/retry policy. Reuse the ordinary process registry/lease/stop owners.
14
+ * No raw command output, environment values or argv enter logs or the registry. */
15
+ export async function executeDeliveryStep(context, input) {
16
+ const procedure = prepareDeliveryProcedure(input.procedure, "execute"), step = procedure.procedure_steps.find(item => item.id === input.stepId);
17
+ if (context.db.isOwnedWriteTransaction?.() || !step || ![input.projectId, input.runId, input.operationId, input.stepId].every(id)
18
+ || typeof input.workspace !== "string" || !path.isAbsolute(input.workspace) || typeof input.assertCurrent !== "function" || typeof input.admit !== "function"
19
+ || typeof input.consumeOutput !== "function")
20
+ throw new AppError("delivery_execution_invalid", "Delivery execution requires its retained operation and owner fence", 2, { effect: "no_effect" });
21
+ if (input.deadlineAt !== undefined && (!Number.isSafeInteger(input.deadlineAt) || input.deadlineAt <= Date.now())) {
22
+ throw new AppError("delivery_execution_invalid", "The original procedure deadline has expired", 2, { effect: "no_effect" });
23
+ }
24
+ const assertCurrent = () => {
25
+ const value = input.assertCurrent();
26
+ if (types.isPromise(value)) {
27
+ void value.catch(() => { });
28
+ throw new AppError("delivery_execution_invalid", "Delivery owner fences must complete synchronously", 2, { effect: "no_effect" });
29
+ }
30
+ };
31
+ try {
32
+ assertCurrent();
33
+ }
34
+ catch {
35
+ throw new AppError("delivery_execution_invalid", "Delivery owner fence refused before process allocation", 2, { effect: "no_effect" });
36
+ }
37
+ let record = registerManagedProcess(context, { kind: "delivery", ownerId: input.operationId, projectId: input.projectId,
38
+ runId: input.runId, operationId: `${input.operationId}:${input.stepId}`, ownerPid: process.pid, uniqueActiveOperation: true });
39
+ const lease = managedLeaseMonitor(attemptMs => renewManagedProcess({ ...context, databaseDeadlineAt: Date.now() + attemptMs }, { id: record.id, leaseToken: record.lease_token }), record.lease_expires_at);
40
+ let admitted = false, completed = false, settled = false, frame, outputSize = 0, publicBytes = 0, publicText = "";
41
+ const chunks = [];
42
+ const internalFailures = new WeakSet();
43
+ let interrupt;
44
+ const interrupted = new Promise((_resolve, reject) => { interrupt = reject; });
45
+ void interrupted.catch(() => { });
46
+ const failure = (reason) => {
47
+ const error = new AppError("delivery_execution_failed", "Delivery command requires original-operation reconciliation", 1, { reason, process_id: record.id, operation_id: input.operationId, step_id: input.stepId,
48
+ effect: admitted && procedure.side_effect === "mutation" && !(settled && frame?.started === false && frame.error === "spawn_failed") ? "unknown" : "no_effect" });
49
+ internalFailures.add(error);
50
+ return error;
51
+ };
52
+ let ready;
53
+ const readiness = new Promise(resolve => { ready = resolve; });
54
+ const child = spawn(process.execPath, [fileURLToPath(new URL("../harness-runtime/lib/delivery-command.mjs", import.meta.url))], { cwd: input.workspace, env: {}, detached: process.platform !== "win32", windowsHide: true, stdio: ["pipe", "pipe", "pipe", "pipe", "pipe"] });
55
+ child.once("error", () => interrupt(failure("wrapper_spawn_failed")));
56
+ const closed = new Promise(resolve => child.once("close", () => { if (!completed)
57
+ interrupt(failure("wrapper_closed_before_result")); resolve(); }));
58
+ for (const stream of child.stdio)
59
+ stream?.on("error", () => interrupt(failure("transport_unavailable")));
60
+ child.stderr.resume();
61
+ child.stdout.on("data", bytes => {
62
+ publicBytes += bytes.length;
63
+ if (publicBytes > 8192) {
64
+ interrupt(failure("unbounded_transport_frame"));
65
+ return;
66
+ }
67
+ publicText += bytes.toString("utf8");
68
+ while (publicText.includes("\n")) {
69
+ const end = publicText.indexOf("\n"), line = publicText.slice(0, end);
70
+ publicText = publicText.slice(end + 1);
71
+ try {
72
+ const value = JSON.parse(line);
73
+ if (value.operation_id !== input.operationId || value.step_id !== input.stepId)
74
+ throw new Error();
75
+ if (value.schema_id === "dd-flow/delivery-command-ready@1" && !admitted && !completed)
76
+ ready();
77
+ else if (value.schema_id === "dd-flow/delivery-command-result@1" && !completed) {
78
+ frame = value;
79
+ completed = true;
80
+ }
81
+ else
82
+ throw new Error();
83
+ }
84
+ catch {
85
+ interrupt(failure("invalid_transport_frame"));
86
+ }
87
+ }
88
+ });
89
+ child.stdio[4].on("data", (bytes) => {
90
+ outputSize += bytes.length;
91
+ if (outputSize > 1024 * 1024) {
92
+ interrupt(failure("unbounded_private_output"));
93
+ return;
94
+ }
95
+ chunks.push(Buffer.from(bytes));
96
+ });
97
+ const deadline = Math.min(Date.now() + procedure.timeout_ms, input.deadlineAt ?? Infinity);
98
+ const timeout = setTimeout(() => interrupt(failure("timeout")), Math.max(1, deadline - Date.now()));
99
+ const heartbeat = setInterval(() => lease.background(assertCurrent, () => interrupt(failure("owner_unconfirmed"))), 5000);
100
+ let terminalError;
101
+ try {
102
+ child.stdio[3].end(JSON.stringify({ schema_id: "dd-flow/delivery-command@1", operation_id: input.operationId,
103
+ step_id: input.stepId, argv: step.argv, permitted_environment_variables: procedure.permitted_environment_variables,
104
+ environment: Object.fromEntries(procedure.permitted_environment_variables.filter(name => context.env[name] !== undefined).map(name => [name, context.env[name]])) }));
105
+ await Promise.race([readiness, interrupted]);
106
+ record = await physicalStartup(Math.min(deadline, context.databaseDeadlineAt ?? deadline)).observe(() => confirmManagedProcess(context, { id: record.id, leaseToken: record.lease_token, pid: child.pid,
107
+ ...(process.platform === "win32" ? {} : { processGroupId: child.pid }), ownerPid: process.pid }), () => { assertCurrent(); if (child.exitCode !== null || child.signalCode !== null)
108
+ throw failure("wrapper_closed_before_result"); }, record.lease_expires_at);
109
+ await Promise.race([lease.ensureConfirmed(assertCurrent), interrupted]);
110
+ assertCurrent();
111
+ assertManagedProcessLease(context, { id: record.id, leaseToken: record.lease_token, pid: child.pid });
112
+ const admission = input.admit(record);
113
+ if (types.isPromise(admission)) {
114
+ void admission.catch(() => { });
115
+ throw failure("asynchronous_admission_refused");
116
+ }
117
+ lease.assertNoRenewal();
118
+ assertCurrent();
119
+ if (Date.now() >= deadline)
120
+ throw failure("timeout");
121
+ admitted = true;
122
+ child.stdin.end("admitted\n");
123
+ await Promise.race([closed, interrupted]);
124
+ if (frame?.admitted === true && frame.started === false && frame.error === "spawn_failed")
125
+ throw failure("command_spawn_failed");
126
+ if (!frame || frame.admitted !== true || frame.started !== true || frame.error !== null || frame.exit_code !== 0
127
+ || frame.signal !== null || frame.output_available !== true || frame.stdout_truncated !== false)
128
+ throw failure("command_unsuccessful");
129
+ }
130
+ catch (error) {
131
+ terminalError = error;
132
+ }
133
+ finally {
134
+ clearTimeout(timeout);
135
+ clearInterval(heartbeat);
136
+ lease.close();
137
+ try {
138
+ await stopProcessGroup(child, 1000, { detached: process.platform !== "win32" });
139
+ if (processTreeIsAlive(record))
140
+ terminalError = failure("process_tree_unsettled");
141
+ else if (!finishManagedProcess(context, { id: record.id, leaseToken: record.lease_token, state: "stopped",
142
+ reason: terminalError ? "delivery_command_failed" : "delivery_command_exited" }))
143
+ terminalError = failure("process_owner_changed");
144
+ else
145
+ settled = true;
146
+ }
147
+ catch {
148
+ terminalError = failure("process_tree_unsettled");
149
+ }
150
+ }
151
+ if (terminalError) {
152
+ chunks.length = 0;
153
+ throw failure(terminalError instanceof AppError && internalFailures.has(terminalError)
154
+ ? String(terminalError.details.reason) : "owner_or_execution_failed");
155
+ }
156
+ try {
157
+ assertCurrent();
158
+ }
159
+ catch {
160
+ chunks.length = 0;
161
+ throw failure("owner_or_execution_failed");
162
+ }
163
+ const bytes = Buffer.concat(chunks);
164
+ chunks.length = 0;
165
+ try {
166
+ const projection = input.consumeOutput(bytes);
167
+ if (types.isPromise(projection)) {
168
+ void projection.catch(() => { });
169
+ throw failure("output_projection_failed");
170
+ }
171
+ }
172
+ catch {
173
+ throw failure("output_projection_failed");
174
+ }
175
+ finally {
176
+ bytes.fill(0);
177
+ }
178
+ return { process_id: record.id, operation_id: input.operationId, step_id: input.stepId, outcome: "command_succeeded",
179
+ // Exit zero is not publication/deployment readback or permission to repeat.
180
+ effect: procedure.side_effect === "mutation" ? "unknown" : "no_effect" };
181
+ }