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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/build-info.json +8 -8
  3. package/dist/cli/command-inputs.js +33 -3
  4. package/dist/cli/command-metadata.js +73 -2
  5. package/dist/cli/delivery.js +108 -0
  6. package/dist/cli/diagnostic-invocation.js +23 -7
  7. package/dist/cli/diagnostic.js +14 -3
  8. package/dist/cli/help.js +117 -1
  9. package/dist/cli/hook-ingress.js +36 -49
  10. package/dist/cli/interactive-invocation.js +33 -0
  11. package/dist/cli/interactive.js +114 -0
  12. package/dist/cli/memory-fix-invocation.js +52 -0
  13. package/dist/cli/memory-fix.js +100 -0
  14. package/dist/cli/memory-flow-admission.js +7 -3
  15. package/dist/cli/memory-init.js +5 -4
  16. package/dist/cli/native-hook-ingress.js +5 -1
  17. package/dist/cli/native-hook-preparation.js +31 -4
  18. package/dist/cli/run-cli.js +273 -19
  19. package/dist/cli.js +17 -3
  20. package/dist/domain/delivery-flows.js +8 -0
  21. package/dist/domain/distill-flow.js +20 -0
  22. package/dist/domain/interactive-flow.js +1 -0
  23. package/dist/domain/memory-fix-flow.js +2 -0
  24. package/dist/domain/review-fix-flow.js +2 -0
  25. package/dist/domain/source-snapshot-flows.js +4 -0
  26. package/dist/harness-runtime/bin/dd-agy.mjs +18 -3
  27. package/dist/harness-runtime/bin/dd-grok.mjs +15 -5
  28. package/dist/harness-runtime/bin/dd-zcode.mjs +15 -7
  29. package/dist/harness-runtime/lib/agy-error-boundary.mjs +18 -10
  30. package/dist/harness-runtime/lib/agy-native-inspection.mjs +12 -3
  31. package/dist/harness-runtime/lib/agy-native-inventory.mjs +19 -0
  32. package/dist/harness-runtime/lib/agy-terminal-envelope.mjs +13 -6
  33. package/dist/harness-runtime/lib/agy-transcript-stream.mjs +54 -0
  34. package/dist/harness-runtime/lib/daemon-observation.d.mts +1 -0
  35. package/dist/harness-runtime/lib/daemon-observation.mjs +12 -4
  36. package/dist/harness-runtime/lib/daemon-operations.mjs +20 -5
  37. package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +1 -0
  38. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +371 -114
  39. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +43 -3
  40. package/dist/harness-runtime/lib/dd-codex.mjs +36 -4
  41. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +25 -11
  42. package/dist/harness-runtime/lib/dd-droid.mjs +124 -11
  43. package/dist/harness-runtime/lib/dd-grok-daemon.d.mts +1 -0
  44. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +137 -25
  45. package/dist/harness-runtime/lib/dd-grok.mjs +4 -2
  46. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +170 -25
  47. package/dist/harness-runtime/lib/dd-opencode.mjs +40 -19
  48. package/dist/harness-runtime/lib/dd-zcode-daemon.d.mts +1 -0
  49. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +115 -22
  50. package/dist/harness-runtime/lib/dd-zcode.mjs +75 -36
  51. package/dist/harness-runtime/lib/delegation-instructions.d.mts +1 -0
  52. package/dist/harness-runtime/lib/delegation-instructions.mjs +10 -3
  53. package/dist/harness-runtime/lib/delivery-command.mjs +84 -0
  54. package/dist/harness-runtime/lib/driver-recovery.mjs +0 -19
  55. package/dist/harness-runtime/lib/droid-observation.mjs +9 -5
  56. package/dist/harness-runtime/lib/lifecycle-contract.d.mts +1 -0
  57. package/dist/harness-runtime/lib/lifecycle-contract.mjs +2 -1
  58. package/dist/harness-runtime/lib/native-hook-command.d.mts +3 -2
  59. package/dist/harness-runtime/lib/native-hook-command.mjs +63 -8
  60. package/dist/harness-runtime/lib/native-streams.mjs +3 -10
  61. package/dist/harness-runtime/lib/opencode-plugin.mjs +36 -9
  62. package/dist/harness-runtime/lib/operation-errors.mjs +13 -0
  63. package/dist/harness-runtime/lib/operation-progress.mjs +6 -2
  64. package/dist/harness-runtime/lib/process-json.mjs +4 -5
  65. package/dist/harness-runtime/lib/tool-observations.mjs +20 -10
  66. package/dist/harness-runtime/lib/zcode-native-hook-ownership.mjs +109 -0
  67. package/dist/schemas/code-work-result.schema.json +2 -1
  68. package/dist/schemas/delivery-stage-result.schema.json +286 -0
  69. package/dist/schemas/distill-stage-result.schema.json +667 -0
  70. package/dist/schemas/distill-work-result.schema.json +428 -0
  71. package/dist/schemas/execution-bundle.schema.json +6 -2
  72. package/dist/schemas/interactive-stage-result.schema.json +47 -0
  73. package/dist/schemas/interactive-work-result.schema.json +63 -0
  74. package/dist/schemas/mb-fix-stage-result.schema.json +93 -0
  75. package/dist/schemas/project-flow-pack-manifest.schema.json +2 -2
  76. package/dist/schemas/review-fix-stage-result.schema.json +210 -0
  77. package/dist/schemas/run-control-receipt.schema.json +1 -0
  78. package/dist/services/assigned-lifecycle.js +382 -0
  79. package/dist/services/check-runner-evidence.js +77 -10
  80. package/dist/services/check-runner-proof.js +69 -1
  81. package/dist/services/cli-operation-classifier.js +6 -1
  82. package/dist/services/code-checks.js +56 -23
  83. package/dist/services/codex-hook-delivery.js +11 -2
  84. package/dist/services/controller-execution.js +4 -1
  85. package/dist/services/controller-fanout.js +9 -4
  86. package/dist/services/controller-memory-worker.js +6 -4
  87. package/dist/services/controller-result-authority.js +122 -0
  88. package/dist/services/dashboard.js +4 -2
  89. package/dist/services/delivery-access.js +123 -0
  90. package/dist/services/delivery-approval.js +68 -0
  91. package/dist/services/delivery-artifact.js +86 -0
  92. package/dist/services/delivery-checks.js +178 -0
  93. package/dist/services/delivery-execution.js +181 -0
  94. package/dist/services/delivery-intents.js +212 -0
  95. package/dist/services/delivery-operation.js +81 -0
  96. package/dist/services/delivery-policy.js +91 -0
  97. package/dist/services/delivery-procedures.js +43 -0
  98. package/dist/services/delivery-request.js +99 -0
  99. package/dist/services/delivery-result.js +144 -0
  100. package/dist/services/delivery-stage-commands.js +26 -0
  101. package/dist/services/delivery-stage-entry.js +56 -0
  102. package/dist/services/delivery-stage-handlers.js +135 -0
  103. package/dist/services/delivery-stage-results.js +94 -0
  104. package/dist/services/descriptor-coordinator-session.js +3 -1
  105. package/dist/services/descriptor-run-start.js +24 -4
  106. package/dist/services/descriptor-stage-coordinator.js +78 -10
  107. package/dist/services/descriptor-stage-settlement.js +59 -14
  108. package/dist/services/diagnostic-repair-intake.js +123 -0
  109. package/dist/services/diagnostic-repair-source-intake.js +102 -0
  110. package/dist/services/diagnostic-report.js +30 -0
  111. package/dist/services/diagnostic-run-bundle.js +48 -9
  112. package/dist/services/diagnostic-snapshot.js +2 -2
  113. package/dist/services/diagnostic-stage-handlers.js +3 -2
  114. package/dist/services/diagnostic-stage-results.js +26 -0
  115. package/dist/services/diagnostic-start.js +56 -6
  116. package/dist/services/diagnostic-work-collection.js +3 -3
  117. package/dist/services/distill-document-publication.js +280 -0
  118. package/dist/services/distill-promotion.js +197 -0
  119. package/dist/services/distill-publication-custody.js +162 -0
  120. package/dist/services/distill-stage-handlers.js +249 -0
  121. package/dist/services/distill-stage-results.js +105 -0
  122. package/dist/services/distill-user-inputs.js +4 -0
  123. package/dist/services/distill-work-batch.js +42 -0
  124. package/dist/services/distill-work-collection.js +74 -0
  125. package/dist/services/distill-work-registration.js +93 -0
  126. package/dist/services/distill-work-results.js +98 -0
  127. package/dist/services/eval-snapshots.js +17 -7
  128. package/dist/services/external-work-launch.js +8 -2
  129. package/dist/services/flow-catalog.js +24 -0
  130. package/dist/services/frozen-engine.js +19 -2
  131. package/dist/services/generic-execution.js +4 -3
  132. package/dist/services/harness-adapter.js +2 -14
  133. package/dist/services/hook-memory-routing.js +1 -1
  134. package/dist/services/hooks.js +31 -6
  135. package/dist/services/hybrid-contracts.js +6 -1
  136. package/dist/services/installed-flow-source.js +37 -2
  137. package/dist/services/interactive-attached-session.js +215 -0
  138. package/dist/services/interactive-baseline-custody.js +54 -0
  139. package/dist/services/interactive-before-versions.js +148 -0
  140. package/dist/services/interactive-bundle-data.js +85 -0
  141. package/dist/services/interactive-checks.js +179 -0
  142. package/dist/services/interactive-execution.js +33 -0
  143. package/dist/services/interactive-finish-integration.js +236 -0
  144. package/dist/services/interactive-git-baseline.js +230 -0
  145. package/dist/services/interactive-git-dispatch.js +278 -0
  146. package/dist/services/interactive-input-delivery.js +82 -0
  147. package/dist/services/interactive-mutation-scope.js +231 -0
  148. package/dist/services/interactive-request.js +287 -0
  149. package/dist/services/interactive-run-bundle.js +109 -0
  150. package/dist/services/interactive-stage-entry.js +41 -0
  151. package/dist/services/interactive-stage-handlers.js +289 -0
  152. package/dist/services/interactive-stage-observation.js +136 -0
  153. package/dist/services/interactive-stage-results.js +111 -0
  154. package/dist/services/interactive-user-inputs.js +142 -0
  155. package/dist/services/interactive-work-batch.js +43 -0
  156. package/dist/services/interactive-work-collection.js +119 -0
  157. package/dist/services/interactive-work-completion.js +180 -0
  158. package/dist/services/interactive-work-context.js +249 -0
  159. package/dist/services/interactive-work-history.js +112 -0
  160. package/dist/services/interactive-work-registration.js +124 -0
  161. package/dist/services/interactive-work-results.js +115 -0
  162. package/dist/services/interactive-work-start.js +155 -0
  163. package/dist/services/lifecycle-invocations.js +214 -24
  164. package/dist/services/managed-daemon-binding.js +52 -1
  165. package/dist/services/memory-compose-handler.js +2 -22
  166. package/dist/services/memory-finish-integration.js +58 -17
  167. package/dist/services/memory-fix-finish-integration.js +129 -0
  168. package/dist/services/memory-fix-git-dispatch.js +139 -0
  169. package/dist/services/memory-fix-intake-custody.js +11 -0
  170. package/dist/services/memory-fix-intake-settlement.js +127 -0
  171. package/dist/services/memory-fix-publication.js +196 -0
  172. package/dist/services/memory-fix-repair-admission.js +116 -0
  173. package/dist/services/memory-fix-request.js +141 -0
  174. package/dist/services/memory-fix-run-bundle.js +83 -0
  175. package/dist/services/memory-fix-scope.js +78 -0
  176. package/dist/services/memory-fix-sources.js +85 -0
  177. package/dist/services/memory-fix-stage-entry.js +24 -0
  178. package/dist/services/memory-fix-stage-handlers.js +282 -0
  179. package/dist/services/memory-fix-stage-results.js +106 -0
  180. package/dist/services/memory-fix-workspace.js +127 -0
  181. package/dist/services/memory-flow-user-inputs.js +88 -0
  182. package/dist/services/memory-init-contexts.js +55 -5
  183. package/dist/services/memory-init-controller-start.js +20 -4
  184. package/dist/services/memory-layer.js +7 -3
  185. package/dist/services/memory-native-review-copy.js +27 -3
  186. package/dist/services/memory-repair-document-delta.js +1 -0
  187. package/dist/services/project-flow-source.js +195 -0
  188. package/dist/services/project-git-integration.js +92 -0
  189. package/dist/services/repair-intents.js +2 -4
  190. package/dist/services/retained-native-hook.js +245 -0
  191. package/dist/services/review-fix-handoff.js +82 -0
  192. package/dist/services/review-fix-intake-custody.js +17 -0
  193. package/dist/services/review-fix-stage-entry.js +43 -0
  194. package/dist/services/review-fix-stage-handlers.js +176 -0
  195. package/dist/services/review-fix-stage-results.js +86 -0
  196. package/dist/services/run-control-worker.js +42 -1
  197. package/dist/services/run-control.js +40 -5
  198. package/dist/services/run-controller-adapter.js +37 -3
  199. package/dist/services/run-controller-process.js +12 -4
  200. package/dist/services/run-controller-recovery.js +46 -6
  201. package/dist/services/run-controller-state.js +3 -2
  202. package/dist/services/run-controller.js +209 -43
  203. package/dist/services/run-recovery.js +31 -2
  204. package/dist/services/run-timeline.js +59 -0
  205. package/dist/services/runs.js +18 -31
  206. package/dist/services/sessions.js +2 -1
  207. package/dist/services/stage-handlers.js +55 -3
  208. package/dist/services/stage-lifecycle.js +4 -3
  209. package/dist/services/stage-pause.js +26 -9
  210. package/dist/services/stage-report-renderer.js +2 -6
  211. package/dist/services/technical-notes.js +31 -0
  212. package/dist/services/verification-matrix.js +1 -1
  213. package/dist/services/version-status.js +1 -1
  214. package/dist/services/vnext-code-review.js +74 -5
  215. package/dist/services/vnext-code.js +9 -32
  216. package/dist/services/vnext-fanout.js +53 -8
  217. package/dist/services/vnext-merge.js +10 -5
  218. package/dist/services/vnext-plan-review.js +1 -1
  219. package/dist/services/vnext-plan.js +1 -1
  220. package/dist/services/vnext-protocolize.js +1 -1
  221. package/dist/services/vnext-specify.js +10 -5
  222. package/dist/services/work-registry.js +252 -66
  223. package/dist/services/worktrees.js +7 -4
  224. package/dist/storage/database.js +2 -1
  225. package/dist/verification-components.json +82 -0
  226. package/package.json +4 -3
  227. package/tools/verification-jobs.mjs +342 -0
@@ -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
+ }
@@ -0,0 +1,212 @@
1
+ import { AppError } from "../shared/errors.js";
2
+ import { appendAudit } from "./audit.js";
3
+ import { hybridDigest } from "./hybrid-contracts.js";
4
+ import { prepareDeliveryApproval } from "./delivery-approval.js";
5
+ import { resolveDeliveryBinding } from "./delivery-policy.js";
6
+ import { PreparedDeliveryAccess } from "./delivery-access.js";
7
+ import { PreparedDeliveryArtifact } from "./delivery-artifact.js";
8
+ import { assertPreparedRunBundleSqlAuthority } from "./runs.js";
9
+ import { assertRunMutationAllowed, assertStageSettlementAllowed } from "./run-recovery-guard.js";
10
+ import { readDescriptorStageInput } from "./descriptor-stage-coordinator.js";
11
+ import { readActiveDescriptorStageCoordinator } from "./descriptor-stage-settlement.js";
12
+ import { readAcceptedStageData } from "./stage-report-renderer.js";
13
+ import { assertManagedProcessLease, managedProcessBinding, managedProcessStatus, processTreeIsAlive, resourceHome } from "./managed-processes.js";
14
+ const intentEvent = "delivery.operation_intent", progressEvent = "delivery.operation_event", anchor = '$.variables."delivery.operation"';
15
+ function refuse(reason) {
16
+ throw new AppError("delivery_intent_invalid", "Delivery needs its original approved operation and active Stage owner", 2, { reason, effect: "no_effect" });
17
+ }
18
+ function pending(reason) {
19
+ throw new AppError("delivery_operation_pending", "Observe or reconcile the original delivery before another dispatch", 2, { reason, effect: "unknown", next_action: "reconcile_original_delivery" });
20
+ }
21
+ /** Original SQL operation custody; never infer 'not dispatched' from a lost
22
+ * log, editable file, new request ID, expired observer or terminal process. */
23
+ export function readDeliveryIntent(context, scope) {
24
+ const run = context.db.get("SELECT json_extract(index_json,?) value FROM runs WHERE project_id=? AND id=?", [anchor, scope.projectId, scope.runId]);
25
+ if (!run)
26
+ refuse("bound_run_required");
27
+ const plans = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type=? AND json_extract(payload_json,'$.plan.approval.scope.run_id')=? LIMIT 2", [scope.projectId, intentEvent, scope.runId]);
28
+ const events = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type=? AND json_extract(payload_json,'$.run_id')=? ORDER BY id LIMIT 34", [scope.projectId, progressEvent, scope.runId]);
29
+ if (plans.length > 1 || events.length > 33 || plans.some(row => Buffer.byteLength(row.payload_json) > 262144)
30
+ || events.some(row => Buffer.byteLength(row.payload_json) > 131072) || Boolean(plans.length) !== Boolean(run.value) || !plans.length && events.length)
31
+ pending("operation_custody_lost");
32
+ if (!plans.length)
33
+ return null;
34
+ let value, log, saved;
35
+ try {
36
+ value = JSON.parse(plans[0].payload_json);
37
+ log = events.map(row => JSON.parse(row.payload_json));
38
+ saved = JSON.parse(run.value);
39
+ }
40
+ catch {
41
+ return pending("operation_custody_invalid");
42
+ }
43
+ const plan = value.plan;
44
+ if (plan?.schema_id !== "dd-flow/delivery-intent@1" || plan.approval?.scope?.project_id !== scope.projectId
45
+ || plan.approval.scope.run_id !== scope.runId || value.identity !== hybridDigest(plan) || saved.identity !== value.identity
46
+ || !Array.isArray(plan.steps) || !plan.steps.length || plan.steps.length > 16
47
+ || plan.steps.some(step => typeof step !== "string" || !/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/.test(step)) || new Set(plan.steps).size !== plan.steps.length
48
+ || saved.count !== log.length)
49
+ pending("operation_custody_changed");
50
+ let head = value.identity, next = 0, failed = false, open = null;
51
+ for (const entry of log) {
52
+ if (!entry || typeof entry !== "object" || Array.isArray(entry) || failed || entry.run_id !== scope.runId
53
+ || entry.identity !== value.identity || entry.previous !== head || entry.step_id !== plan.steps[next])
54
+ pending("operation_event_changed");
55
+ if (entry.kind === "admitted" && !open && entry.process && typeof entry.process.resource_home === "string"
56
+ && typeof entry.process.process_id === "string" && /^[a-f0-9]{64}$/.test(entry.process.registration_sha256)
57
+ && entry.access?.schema_id === "dd-flow/delivery-access@1" && entry.access.operation_id === plan.approval.scope.operation_id
58
+ && entry.access.binding_id === plan.approval.scope.binding_id)
59
+ open = entry;
60
+ else if (entry.kind === "command_result" && open && entry.result && entry.result.process_id === open.process.process_id
61
+ && entry.result.operation_id === plan.approval.scope.operation_id && entry.result.step_id === entry.step_id
62
+ && ["command_succeeded", "failed"].includes(entry.result.outcome) && ["no_effect", "unknown"].includes(entry.result.effect)) {
63
+ // A failed or uncertain command is never permission to run a later step.
64
+ if (entry.result.outcome === "command_succeeded") {
65
+ next++;
66
+ open = null;
67
+ }
68
+ else
69
+ failed = true;
70
+ }
71
+ else
72
+ pending("operation_event_order_invalid");
73
+ head = hybridDigest(entry);
74
+ }
75
+ if (head !== saved.head)
76
+ pending("operation_anchor_changed");
77
+ return { ...value, events: log, head, next, open, failed };
78
+ }
79
+ /** Original intent/admission owner, not an executor. The Stage independently
80
+ * verifies manifest references and project procedure safety. No public route yet. */
81
+ export function prepareDeliveryIntent(context, scope, bundle, raw, proposal) {
82
+ if (context.db.isOwnedWriteTransaction?.())
83
+ refuse("preparation_outside_writer_required");
84
+ const binding = resolveDeliveryBinding({ schema_id: "dd-flow/delivery-policy@1", bindings: [raw.binding] }, raw.binding.selector);
85
+ if (binding.procedures.execute.side_effect !== "mutation")
86
+ refuse("protected_mutation_required");
87
+ const selection = { ...raw, binding }, approval = prepareDeliveryApproval(context, scope, bundle, selection, proposal), grant = approval.value();
88
+ const artifact = PreparedDeliveryArtifact.prepare(bundle.execution.snapshot().scope.workspace, raw.artifactPath, selection.artifact);
89
+ const stage = grant.scope.flow_id === "deploy" ? "deploy" : "publish", previous = stage === "deploy" ? "prepare" : "verify";
90
+ const predecessor = readAcceptedStageData(context, { ...scope, stage: previous });
91
+ let data;
92
+ try {
93
+ data = JSON.parse(predecessor.bytes.toString("utf8"));
94
+ }
95
+ catch {
96
+ return refuse("accepted_selection_required");
97
+ }
98
+ if (data.details?.delivery_scope_sha256 !== grant.scope_sha256)
99
+ refuse("accepted_selection_required");
100
+ const generation = assertRunMutationAllowed(context, scope.projectId, scope.runId);
101
+ const authority = (writer) => {
102
+ assertRunMutationAllowed(writer, scope.projectId, scope.runId, generation);
103
+ const run = assertPreparedRunBundleSqlAuthority(writer, scope, bundle), index = JSON.parse(run.index_json);
104
+ if (run.status !== "running" || index.current_stage !== stage
105
+ || !index.stage_runs?.some((item) => item.stage === stage && item.status === "running"))
106
+ refuse("active_delivery_stage_required");
107
+ const packet = readDescriptorStageInput(writer, { ...scope, stage }, bundle), actor = readActiveDescriptorStageCoordinator(writer, { ...scope, stage }, bundle);
108
+ if (packet.input.predecessor?.stage !== previous || hybridDigest(packet.input.predecessor.result) !== hybridDigest(predecessor.result))
109
+ refuse("accepted_predecessor_changed");
110
+ if (writer.db.get("SELECT 1 FROM works WHERE project_id=? AND run_id=? AND parent_work_id=? AND status IN ('created','running','paused') LIMIT 1", [scope.projectId, scope.runId, actor.work_id]))
111
+ refuse("active_child_work");
112
+ return { packet, actor };
113
+ };
114
+ const owner = authority(context), original = readDeliveryIntent(context, scope);
115
+ const proposed = { schema_id: "dd-flow/delivery-intent@1", approval: grant, artifact: artifact.value(), predecessor: { stage: previous, result: predecessor.result },
116
+ steps: binding.procedures.execute.procedure_steps.map(step => step.id) };
117
+ // Retain the first human-readable interpretation rather than requiring a
118
+ // future observer to reproduce its wording. Scope, basis, inputs and expiry
119
+ // remain exact; neither commentary nor a fresh timestamp renews permission.
120
+ const semantic = (plan) => ({ ...plan, approval: { ...plan.approval, approved_at: null, interpretation: null } });
121
+ if (original && hybridDigest(semantic(original.plan)) !== hybridDigest(semantic(proposed)))
122
+ pending("original_operation_scope_changed");
123
+ const plan = original?.plan ?? proposed, identity = hybridDigest(plan), accessScope = {
124
+ projectId: scope.projectId, runId: scope.runId, operationId: selection.operationId, workspace: bundle.execution.snapshot().scope.workspace, binding
125
+ };
126
+ const assertCustody = (writer) => {
127
+ approval.assertCustody(writer);
128
+ if (hybridDigest(authority(writer)) !== hybridDigest(owner))
129
+ refuse("delivery_owner_changed");
130
+ const current = readDeliveryIntent(writer, scope);
131
+ if (current && current.identity !== identity)
132
+ pending("original_operation_changed");
133
+ return current;
134
+ };
135
+ const settlementCustody = (writer) => {
136
+ // Expiry/revocation forbids new effects, not recording what an already
137
+ // admitted command actually did. Ordinary Stage/control fences still own
138
+ // the allowed generation and cooperative settlement window.
139
+ assertStageSettlementAllowed(writer, scope.projectId, scope.runId, stage, generation);
140
+ assertPreparedRunBundleSqlAuthority(writer, scope, bundle);
141
+ const current = readDeliveryIntent(writer, scope);
142
+ if (!current || current.identity !== identity)
143
+ pending("original_operation_changed");
144
+ return current;
145
+ };
146
+ const assertArtifact = (writer) => {
147
+ if (writer.db.isOwnedWriteTransaction?.())
148
+ refuse("artifact_observation_outside_writer_required");
149
+ artifact.assertCurrent();
150
+ };
151
+ const retain = (writer) => {
152
+ assertArtifact(writer);
153
+ return writer.db.writeTransaction(() => {
154
+ const current = assertCustody(writer);
155
+ if (current)
156
+ return current;
157
+ appendAudit(writer, { projectId: scope.projectId, eventType: intentEvent, payload: { identity, plan } });
158
+ writer.db.run("UPDATE runs SET index_json=json_set(index_json,?,json(?)) WHERE project_id=? AND id=?", [anchor, JSON.stringify({ identity, count: 0, head: identity }), scope.projectId, scope.runId]);
159
+ return readDeliveryIntent(writer, scope);
160
+ });
161
+ };
162
+ return { value: () => structuredClone(plan), assertCustody, retain,
163
+ admitStep(writer, stepId, process, access) {
164
+ if (!(access instanceof PreparedDeliveryAccess))
165
+ refuse("owned_access_observation_required");
166
+ assertArtifact(writer);
167
+ return writer.db.writeTransaction(() => {
168
+ const current = assertCustody(writer);
169
+ if (!current)
170
+ refuse("retained_intent_required");
171
+ if (current.open || current.plan.steps[current.next] !== stepId)
172
+ pending("original_step_already_admitted_or_out_of_order");
173
+ if (process.kind !== "delivery" || process.project_id !== scope.projectId || process.run_id !== scope.runId
174
+ || process.owner_id !== selection.operationId || process.operation_id !== `${selection.operationId}:${stepId}` || !process.pid)
175
+ refuse("selected_process_required");
176
+ assertManagedProcessLease(writer, { id: process.id, leaseToken: process.lease_token, pid: process.pid });
177
+ access.consume(writer, accessScope);
178
+ const entry = { run_id: scope.runId, identity, previous: current.head, kind: "admitted", step_id: stepId,
179
+ process: managedProcessBinding(process, resourceHome(writer)), access: access.value() };
180
+ appendAudit(writer, { projectId: scope.projectId, eventType: progressEvent, payload: entry });
181
+ writer.db.run("UPDATE runs SET index_json=json_set(index_json,?,json(?)) WHERE project_id=? AND id=?", [anchor,
182
+ JSON.stringify({ identity, count: current.events.length + 1, head: hybridDigest(entry) }), scope.projectId, scope.runId]);
183
+ });
184
+ },
185
+ recordCommand(writer, result) {
186
+ if (writer.db.isOwnedWriteTransaction?.())
187
+ refuse("physical_observation_outside_writer_required");
188
+ const before = settlementCustody(writer), admitted = before.open;
189
+ if (before.failed || !admitted || result.step_id !== admitted.step_id || result.process_id !== admitted.process.process_id
190
+ || result.operation_id !== selection.operationId || !["command_succeeded", "failed"].includes(result.outcome)
191
+ || !["no_effect", "unknown"].includes(result.effect))
192
+ pending("original_command_result_required");
193
+ if (admitted.process.resource_home !== resourceHome(writer))
194
+ pending("original_resource_home_required");
195
+ const process = managedProcessStatus(writer).find(item => item.id === result.process_id);
196
+ if (!process || managedProcessBinding(process, resourceHome(writer)).registration_sha256 !== admitted.process.registration_sha256
197
+ || process.state !== "stopped" || processTreeIsAlive(process))
198
+ pending("original_process_not_settled");
199
+ return writer.db.writeTransaction(() => {
200
+ const current = settlementCustody(writer);
201
+ if (!current || current.head !== before.head)
202
+ pending("operation_changed_before_result");
203
+ const entry = { run_id: scope.runId, identity, previous: current.head, kind: "command_result", step_id: result.step_id,
204
+ result: { process_id: result.process_id, operation_id: result.operation_id, step_id: result.step_id, outcome: result.outcome, effect: result.effect } };
205
+ appendAudit(writer, { projectId: scope.projectId, eventType: progressEvent, payload: entry });
206
+ writer.db.run("UPDATE runs SET index_json=json_set(index_json,?,json(?)) WHERE project_id=? AND id=?", [anchor,
207
+ JSON.stringify({ identity, count: current.events.length + 1, head: hybridDigest(entry) }), scope.projectId, scope.runId]);
208
+ return readDeliveryIntent(writer, scope);
209
+ });
210
+ }
211
+ };
212
+ }