@deksden-com/dd-flow-cli 0.9.0-beta.137 → 0.9.0-beta.138

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 (260) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +9 -0
  3. package/dist/build-info.json +6 -6
  4. package/dist/cli/argument-values.js +5 -5
  5. package/dist/cli/command-inputs.js +89 -20
  6. package/dist/cli/command-metadata.js +197 -0
  7. package/dist/cli/command-option-descriptions.js +191 -0
  8. package/dist/cli/completion-cli.js +70 -0
  9. package/dist/cli/completion.js +57 -0
  10. package/dist/cli/error-output.js +52 -0
  11. package/dist/cli/executor-onboarding.js +76 -0
  12. package/dist/cli/flows.js +29 -0
  13. package/dist/cli/harnesses.js +84 -0
  14. package/dist/cli/help.js +240 -6
  15. package/dist/cli/input-preparation.js +54 -5
  16. package/dist/cli/memory-flow-admission.js +44 -0
  17. package/dist/cli/memory-init-invocation.js +78 -0
  18. package/dist/cli/memory-init.js +240 -0
  19. package/dist/cli/memory-storage-admission.js +53 -0
  20. package/dist/cli/models.js +39 -0
  21. package/dist/cli/native-hook-ingress.js +1 -1
  22. package/dist/cli/operator-profiles.js +61 -0
  23. package/dist/cli/output-options.js +35 -0
  24. package/dist/cli/response-file.js +71 -0
  25. package/dist/cli/run-cli.js +283 -160
  26. package/dist/cli/setup.js +149 -0
  27. package/dist/cli.js +54 -2
  28. package/dist/domain/stage-handler-identities.js +20 -0
  29. package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
  30. package/dist/harness-runtime/lib/daemon-operations.mjs +4 -3
  31. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +4 -2
  32. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +11 -5
  33. package/dist/harness-runtime/lib/dd-codex.mjs +22 -10
  34. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +2 -1
  35. package/dist/harness-runtime/lib/dd-droid.mjs +2 -1
  36. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +4 -2
  37. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +6 -4
  38. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +9 -9
  39. package/dist/harness-runtime/lib/delegation-instructions.mjs +5 -3
  40. package/dist/harness-runtime/lib/lifecycle-command.mjs +1 -0
  41. package/dist/harness-runtime/lib/managed-daemon.d.mts +1 -0
  42. package/dist/harness-runtime/lib/managed-daemon.mjs +50 -15
  43. package/dist/harness-runtime/lib/managed-flow-env.mjs +15 -0
  44. package/dist/harness-runtime/lib/native-hook-command.mjs +1 -1
  45. package/dist/harness-runtime/lib/native-request-proof.d.mts +2 -0
  46. package/dist/harness-runtime/lib/native-request-proof.mjs +65 -0
  47. package/dist/harness-runtime/lib/opencode-plugin.mjs +2 -0
  48. package/dist/harness-runtime/lib/operation-progress.mjs +3 -1
  49. package/dist/runtime/context.js +1 -1
  50. package/dist/schemas/discovery-result.schema.json +184 -0
  51. package/dist/schemas/execution-bundle.schema.json +205 -0
  52. package/dist/schemas/execution-settings.schema.json +162 -0
  53. package/dist/schemas/flow-descriptor.schema.json +54 -0
  54. package/dist/schemas/flow-run.schema.json +22 -0
  55. package/dist/schemas/harness-catalog.schema.json +80 -0
  56. package/dist/schemas/mb-init-compose-result.schema.json +457 -0
  57. package/dist/schemas/mb-init-discover-result.schema.json +500 -0
  58. package/dist/schemas/mb-init-finish-result.schema.json +422 -0
  59. package/dist/schemas/mb-init-preflight-result.schema.json +367 -0
  60. package/dist/schemas/mb-init-review-result.schema.json +396 -0
  61. package/dist/schemas/mb-init-verify-result.schema.json +391 -0
  62. package/dist/schemas/memory-bootstrap-receipt-v2.schema.json +67 -0
  63. package/dist/schemas/memory-bootstrap-receipt.schema.json +354 -0
  64. package/dist/schemas/memory-composition-work-result.schema.json +38 -0
  65. package/dist/schemas/memory-repair-work-result.schema.json +49 -0
  66. package/dist/schemas/memory-review-correction-plan.schema.json +30 -0
  67. package/dist/schemas/memory-review-work-result.schema.json +43 -0
  68. package/dist/schemas/model-catalog.schema.json +153 -0
  69. package/dist/schemas/profile-settings.schema.json +122 -0
  70. package/dist/schemas/release-manifest.schema.json +291 -0
  71. package/dist/schemas/run-engine-binding.schema.json +1 -1
  72. package/dist/services/check-runner-evidence.js +51 -4
  73. package/dist/services/cli-operation-classifier.js +8 -47
  74. package/dist/services/cli-upgrade-allowlist.js +27 -0
  75. package/dist/services/code-checks.js +387 -34
  76. package/dist/services/controller-execution.js +88 -0
  77. package/dist/services/controller-fanout.js +71 -12
  78. package/dist/services/dashboard.js +8 -2
  79. package/dist/services/descriptor-coordinator-session.js +35 -0
  80. package/dist/services/descriptor-root-work.js +53 -0
  81. package/dist/services/descriptor-run-settlement.js +105 -0
  82. package/dist/services/descriptor-run-start.js +449 -0
  83. package/dist/services/descriptor-stage-coordinator.js +197 -0
  84. package/dist/services/descriptor-stage-settlement.js +222 -0
  85. package/dist/services/descriptor-stage-start.js +139 -0
  86. package/dist/services/discovery/agy-discovery.js +2 -0
  87. package/dist/services/discovery/codex-discovery.js +2 -0
  88. package/dist/services/discovery/droid-discovery.js +2 -0
  89. package/dist/services/discovery/grok-discovery.js +2 -0
  90. package/dist/services/discovery/opencode-discovery.js +2 -0
  91. package/dist/services/discovery/static.js +31 -0
  92. package/dist/services/discovery/zcode-discovery.js +2 -0
  93. package/dist/services/engines.js +39 -22
  94. package/dist/services/eval-snapshots.js +83 -25
  95. package/dist/services/execution-bundles.js +263 -0
  96. package/dist/services/execution-policy.js +20 -16
  97. package/dist/services/executor-onboarding.js +56 -0
  98. package/dist/services/external-work-launch.js +91 -11
  99. package/dist/services/flow-catalog.js +21 -0
  100. package/dist/services/flow-descriptors.js +82 -0
  101. package/dist/services/frozen-engine.js +242 -0
  102. package/dist/services/generic-execution.js +164 -0
  103. package/dist/services/harness-adapter.js +28 -2
  104. package/dist/services/harness-discovery.js +29 -0
  105. package/dist/services/harness-doctor.js +75 -0
  106. package/dist/services/harness-probe-effects.js +51 -0
  107. package/dist/services/harness-version-probe.js +100 -0
  108. package/dist/services/hooks.js +24 -6
  109. package/dist/services/hybrid-contracts.js +483 -0
  110. package/dist/services/ids.js +29 -1
  111. package/dist/services/installation-lock.js +95 -0
  112. package/dist/services/installed-flow-source.js +183 -0
  113. package/dist/services/lifecycle-invocations.js +257 -78
  114. package/dist/services/memory-accepted-composition.js +77 -0
  115. package/dist/services/memory-baseline-preservation.js +65 -0
  116. package/dist/services/memory-completed-correction-wave.js +320 -0
  117. package/dist/services/memory-compose-handler.js +258 -0
  118. package/dist/services/memory-composition-acceptance.js +199 -0
  119. package/dist/services/memory-composition-graph.js +134 -0
  120. package/dist/services/memory-composition-registration.js +146 -0
  121. package/dist/services/memory-composition-work-custody.js +151 -0
  122. package/dist/services/memory-composition-work-results.js +96 -0
  123. package/dist/services/memory-corrected-review-acceptance.js +129 -0
  124. package/dist/services/memory-corrected-review-batch.js +30 -0
  125. package/dist/services/memory-corrected-review-findings.js +106 -0
  126. package/dist/services/memory-corrected-review-inputs.js +210 -0
  127. package/dist/services/memory-corrected-review-projection.js +78 -0
  128. package/dist/services/memory-corrected-review-receipt.js +170 -0
  129. package/dist/services/memory-corrected-review-registration.js +191 -0
  130. package/dist/services/memory-correction-check-receipt.js +219 -0
  131. package/dist/services/memory-correction-input-owner.js +6 -0
  132. package/dist/services/memory-discover-handler.js +104 -0
  133. package/dist/services/memory-finish-evidence.js +435 -0
  134. package/dist/services/memory-finish-handler.js +215 -0
  135. package/dist/services/memory-finish-integration.js +280 -0
  136. package/dist/services/memory-finish-readiness.js +122 -0
  137. package/dist/services/memory-git-integration.js +151 -0
  138. package/dist/services/memory-historical-corrected-review.js +206 -0
  139. package/dist/services/memory-historical-original-review.js +130 -0
  140. package/dist/services/memory-historical-repair-registration.js +99 -0
  141. package/dist/services/memory-historical-second-review.js +128 -0
  142. package/dist/services/memory-init-bootstrap-readiness.js +210 -0
  143. package/dist/services/memory-init-contexts.js +89 -0
  144. package/dist/services/memory-init-controller-start.js +84 -0
  145. package/dist/services/memory-init-engine-release.js +223 -0
  146. package/dist/services/memory-init-git-policy.js +136 -0
  147. package/dist/services/memory-init-lint-capability.js +185 -0
  148. package/dist/services/memory-init-native-capacity.js +241 -0
  149. package/dist/services/memory-init-preflight.js +312 -0
  150. package/dist/services/memory-init-project-bootstrap.js +599 -0
  151. package/dist/services/memory-init-request-candidate.js +157 -0
  152. package/dist/services/memory-init-request-input.js +66 -0
  153. package/dist/services/memory-init-resource-storage.js +125 -0
  154. package/dist/services/memory-init-run-allocation.js +81 -0
  155. package/dist/services/memory-init-run-bundle.js +98 -0
  156. package/dist/services/memory-init-start.js +175 -0
  157. package/dist/services/memory-init-startup-custody.js +80 -0
  158. package/dist/services/memory-init-startup-input.js +240 -0
  159. package/dist/services/memory-init-storage.js +81 -0
  160. package/dist/services/memory-init-stores.js +359 -0
  161. package/dist/services/memory-init-worktrunk-capability.js +172 -0
  162. package/dist/services/memory-layer-publication.js +611 -0
  163. package/dist/services/memory-layer.js +156 -0
  164. package/dist/services/memory-lint-config.js +85 -0
  165. package/dist/services/memory-native-review-copy.js +165 -0
  166. package/dist/services/memory-normal-flow-entry.js +75 -0
  167. package/dist/services/memory-ordinary-defaults.js +112 -0
  168. package/dist/services/memory-preflight-bootstrap-custody.js +115 -0
  169. package/dist/services/memory-preflight-handler.js +259 -0
  170. package/dist/services/memory-repair-current-view.js +299 -0
  171. package/dist/services/memory-repair-document-delta.js +451 -0
  172. package/dist/services/memory-repair-history.js +491 -0
  173. package/dist/services/memory-repair-physical-binding.js +312 -0
  174. package/dist/services/memory-repair-publication-files.js +152 -0
  175. package/dist/services/memory-repair-publication-intent.js +27 -0
  176. package/dist/services/memory-repair-publication-plan.js +160 -0
  177. package/dist/services/memory-repair-publication-source.js +225 -0
  178. package/dist/services/memory-repair-publication.js +794 -0
  179. package/dist/services/memory-repair-registration.js +239 -0
  180. package/dist/services/memory-repair-work-baseline.js +413 -0
  181. package/dist/services/memory-repair-work-completion.js +97 -0
  182. package/dist/services/memory-repair-work-custody.js +73 -0
  183. package/dist/services/memory-repair-work-results.js +89 -0
  184. package/dist/services/memory-repair-workspace.js +252 -0
  185. package/dist/services/memory-repair-writer-copy.js +735 -0
  186. package/dist/services/memory-result-binding.js +173 -0
  187. package/dist/services/memory-retained-review-authority.js +129 -0
  188. package/dist/services/memory-retained-review-results.js +70 -0
  189. package/dist/services/memory-retained-second-baseline.js +102 -0
  190. package/dist/services/memory-retained-second-repairs.js +156 -0
  191. package/dist/services/memory-review-acceptance.js +214 -0
  192. package/dist/services/memory-review-correction-plan.js +171 -0
  193. package/dist/services/memory-review-handler.js +585 -0
  194. package/dist/services/memory-review-inputs.js +200 -0
  195. package/dist/services/memory-review-receipt.js +180 -0
  196. package/dist/services/memory-review-registration.js +108 -0
  197. package/dist/services/memory-review-work-custody.js +124 -0
  198. package/dist/services/memory-review-work-input.js +72 -0
  199. package/dist/services/memory-review-work-results.js +73 -0
  200. package/dist/services/memory-second-corrected-review-inputs.js +165 -0
  201. package/dist/services/memory-second-correction-plan.js +76 -0
  202. package/dist/services/memory-second-repair-baseline.js +91 -0
  203. package/dist/services/memory-second-repair-launch.js +614 -0
  204. package/dist/services/memory-second-repair-registration.js +88 -0
  205. package/dist/services/memory-second-repair-source.js +187 -0
  206. package/dist/services/memory-second-review-acceptance.js +115 -0
  207. package/dist/services/memory-source-observations.js +117 -0
  208. package/dist/services/memory-stage-dispatch.js +28 -0
  209. package/dist/services/memory-stage-results.js +199 -0
  210. package/dist/services/memory-verification-acceptance.js +135 -0
  211. package/dist/services/memory-verification-checks.js +252 -0
  212. package/dist/services/memory-verification-receipt.js +159 -0
  213. package/dist/services/memory-verify-handler.js +135 -0
  214. package/dist/services/memory-work-result-preparation.js +63 -0
  215. package/dist/services/model-catalog.js +109 -0
  216. package/dist/services/model-discovery.js +46 -0
  217. package/dist/services/operator-profiles.js +236 -0
  218. package/dist/services/projects.js +17 -2
  219. package/dist/services/recovery-snapshot-database.js +416 -1
  220. package/dist/services/release-archive-producer.js +52 -0
  221. package/dist/services/release-archive.js +143 -0
  222. package/dist/services/release-cache.js +142 -0
  223. package/dist/services/release-compatibility.js +18 -0
  224. package/dist/services/release-download.js +67 -0
  225. package/dist/services/release-envelope.js +104 -0
  226. package/dist/services/release-manifest.js +45 -0
  227. package/dist/services/release-payload.js +114 -0
  228. package/dist/services/release-producer.js +63 -0
  229. package/dist/services/release-provenance.js +160 -0
  230. package/dist/services/release-registry.js +66 -0
  231. package/dist/services/release-source.js +194 -0
  232. package/dist/services/repair-intents.js +1 -1
  233. package/dist/services/review-copy.js +10 -2
  234. package/dist/services/run-control.js +80 -9
  235. package/dist/services/run-controller-capture.js +10 -3
  236. package/dist/services/run-controller-process.js +21 -0
  237. package/dist/services/run-controller-recovery.js +3 -1
  238. package/dist/services/run-controller-state.js +1 -1
  239. package/dist/services/run-controller.js +101 -40
  240. package/dist/services/run-recovery-guard.js +2 -0
  241. package/dist/services/run-recovery.js +145 -5
  242. package/dist/services/run-start-custody.js +177 -0
  243. package/dist/services/runs.js +319 -25
  244. package/dist/services/setup.js +377 -0
  245. package/dist/services/stage-context.js +9 -1
  246. package/dist/services/stage-handlers.js +74 -0
  247. package/dist/services/stage-report-renderer.js +63 -0
  248. package/dist/services/stage-work-graph.js +17 -0
  249. package/dist/services/vnext-code.js +3 -3
  250. package/dist/services/vnext-execution-profile.js +2 -2
  251. package/dist/services/vnext-fanout.js +70 -9
  252. package/dist/services/vnext-merge.js +8 -3
  253. package/dist/services/vnext-plan-review.js +6 -4
  254. package/dist/services/work-registry.js +616 -71
  255. package/dist/services/workspace-files.js +1 -1
  256. package/dist/storage/database.js +84 -6
  257. package/dist/storage/writer-contract.js +11 -7
  258. package/package.json +13 -3
  259. package/tools/create-canon-release.mjs +33 -0
  260. package/tools/produce-hybrid-release.mjs +41 -0
@@ -11,7 +11,7 @@ import { commandOption, commandPosition, continuationCommand, lifecycleCommandFr
11
11
  import { requireProjectByRoot } from "./projects.js";
12
12
  import { assertRunMutationAllowed, assertStageSettlementAllowed, assertWorkSettlementAllowed, recoveryGuard, recoveryPacketReceipt } from "./run-recovery.js";
13
13
  import { readNativeDaemonState, requireManagedDaemonBinding } from "./managed-daemon-binding.js";
14
- import { managedProcessStatus, processIsAlive } from "./managed-processes.js";
14
+ import { managedProcessStatus, processIsAlive, processStartedAt } from "./managed-processes.js";
15
15
  import { appendEvent } from "./run-controller-state.js";
16
16
  import { recoverCommittedWorkStart } from "./work-registry.js";
17
17
  import { observeCommandHook } from "./hooks.js";
@@ -21,7 +21,24 @@ import { isRegisteredRepair } from "./repair-intents.js";
21
21
  import { stageAttempt } from "./stage-work-graph.js";
22
22
  import { contextualPathOptionsForLifecycle } from "../cli/command-inputs.js";
23
23
  import { canonicalPath } from "../storage/paths.js";
24
- import { runtimeCommand } from "./runtime-command.js";
24
+ import { runtimeCommand, shellArgument } from "./runtime-command.js";
25
+ // Process-local custody: an executing SQL row or ambient variable alone cannot
26
+ // recreate admission after a CLI restart, or in another resource database.
27
+ const currentAdmissions = new WeakMap();
28
+ export function currentLifecycleAdmission(context, id) {
29
+ const owned = currentAdmissions.get(context.db)?.get(id);
30
+ if (!owned || context.env.DD_FLOW_CURRENT_INVOCATION !== id || owned.pid !== process.pid
31
+ || process.cwd() !== owned.cwd)
32
+ return null;
33
+ // This private module cannot survive owner death/restart/PID reuse. Its
34
+ // captured physical birth + exact PID/CWD/store remain a lifetime proof;
35
+ // revalidation reads SQL only, including inside the atomic claim writer.
36
+ const row = context.db.get("SELECT * FROM lifecycle_invocations WHERE id=?", [id]);
37
+ if (!row || row.status !== "executing" || row.event_key !== owned.eventKey || row.identity_json !== owned.identity
38
+ || row.scope_json !== owned.scope || row.command !== owned.command || row.updated_at !== owned.admittedAt)
39
+ return null;
40
+ return { admittedAt: owned.admittedAt };
41
+ }
25
42
  /** Auxiliary commands have no invocation capability, but may not escape their
26
43
  * native daemon's project/RUN. Context aliases never grant mutation authority. */
27
44
  export function assertAuxiliaryCommandScope(context, command, projectRoot) {
@@ -94,6 +111,12 @@ export function isCorrectableLifecycleRejection(code, details = {}) {
94
111
  || (code === "fresh_session_required" && details.handoff_required === true
95
112
  && details.recoverable === true);
96
113
  }
114
+ function registeredLifecycleContinuation(context, error, projectId, runId) {
115
+ if (runId && isRegisteredRepair(context, error.details.continuation, { projectId, runId }))
116
+ return true;
117
+ return error.code === "fresh_session_required" && error.details.handoff_required === true && runId !== null
118
+ && typeof error.details.work_id === "string" && Boolean(context.db.get("SELECT 1 FROM works WHERE project_id = ? AND run_id = ? AND work_id = ? AND launch_policy = 'fresh_agent_required' AND status IN ('created','running','paused')", [projectId, runId, error.details.work_id]));
119
+ }
97
120
  function storage(context) {
98
121
  if (!context.db.get("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'lifecycle_invocations'")) {
99
122
  throw new AppError("invocation_storage_unprepared", "Lifecycle invocation storage is not prepared; start dd-flow normally before issuing native work", 1);
@@ -447,24 +470,106 @@ export function observeLifecycleCommand(context, command, nativeEvent) {
447
470
  delete context.observeLifecycleHook;
448
471
  if (!anchor)
449
472
  return;
450
- const error = outcome.error ? errorRecord(outcome.error) : undefined;
451
- const correctable = error && isCorrectableLifecycleRejection(error.code, error.details);
473
+ let error = outcome.error ? errorRecord(outcome.error) : undefined;
474
+ // The exact committed event can diagnose a broken transport, but cannot
475
+ // grant execution authority or select an assignment by command similarity.
476
+ if (error && parsed.kind === "standalone" && (nativeEvent || commandOption(parsed.invocation, "hook-event-id"))) {
477
+ const event = context.db.get("SELECT event_key, daemon_id, provider_session_id FROM hook_events WHERE id = ? AND project_id = ?", [anchor.hook_event_id, anchor.project_id]);
478
+ const assigned = event && context.db.get("SELECT 1 FROM sqlite_master WHERE name = 'lifecycle_invocations'")
479
+ ? context.db.all("SELECT * FROM lifecycle_invocations WHERE event_key = ? AND status = 'observed'", [event.event_key]) : [];
480
+ if (assigned.length === 1 && event?.daemon_id && context.env.DD_FLOW_DAEMON_ID !== event.daemon_id) {
481
+ const scope = JSON.parse(assigned[0].scope_json);
482
+ const identity = assigned[0].identity_json ? JSON.parse(assigned[0].identity_json) : null;
483
+ const project = context.db.get("SELECT root FROM projects WHERE id = ?", [anchor.project_id]);
484
+ if (assigned[0].fingerprint === fingerprint(parsed.invocation) && scope.projectRoot === project?.root && scope.daemonId === event.daemon_id && scope.runId === anchor.run_id && scope.generation === anchor.generation
485
+ && Array.isArray(identity) && identity[0] === event.daemon_id && identity[1] === scope.rootSessionId && identity[2] === (event.provider_session_id ?? anchor.session_id))
486
+ error = errorRecord(new AppError("invocation_scope_unproven", "Managed shell did not receive its native daemon binding", 1, {
487
+ reason: "managed_shell_context_missing", phase: "prepare", effect: "no_effect", recoverable: false,
488
+ cause: { code: error.code, parameter: error.details.parameter, reason: error.details.reason }
489
+ }));
490
+ }
491
+ }
492
+ const registeredContinuation = error && registeredLifecycleContinuation(context, error, anchor.project_id, anchor.run_id);
493
+ const retainedSuccessor = error && typeof error.details.retry_command === "string"
494
+ && typeof error.details.rejection_signature === "string"
495
+ && context.db.get("SELECT 1 FROM sqlite_master WHERE name = 'lifecycle_invocations'")
496
+ && context.db.get(`SELECT 1 FROM lifecycle_invocations a JOIN hook_events h ON h.event_key = a.event_key
497
+ JOIN projects p ON p.id = h.project_id WHERE h.id = ? AND h.project_id = ? AND a.status = 'settled'
498
+ AND json_extract(a.scope_json,'$.projectRoot') = p.root AND json_extract(a.scope_json,'$.runId') = ?
499
+ AND json_extract(a.scope_json,'$.generation') = ? AND json_extract(a.outcome_json,'$.error.details.retry_command') = ?
500
+ AND json_extract(a.outcome_json,'$.error.details.rejection_signature') = ?`, [anchor.hook_event_id, anchor.project_id, anchor.run_id, anchor.generation, error.details.retry_command, error.details.rejection_signature]);
501
+ const hookOnlyArgumentDefect = error && !invocationId && !context.env.DD_FLOW_INVOCATION_SCOPE
502
+ && !retainedSuccessor
503
+ && ["missing_required_option", "missing_option_value", "empty_option_value", "invalid_number", "invalid_choice", "duplicate_option"].includes(String(error.details.reason));
504
+ const hookOnlyCorrection = error && parsed.kind === "standalone" && !auxiliary && !invocationId && !context.env.DD_FLOW_INVOCATION_SCOPE
505
+ && !retainedSuccessor && !hookOnlyArgumentDefect
506
+ && error.details.effect === "no_effect" && isRetryableLifecycleRejection(error.code, error.details)
507
+ && error.code !== "usage" && !["invocation_command_mismatch", "invocation_argument_mismatch"].includes(error.code)
508
+ && parsed.invocation.argv.some(value => /^(?:--(?:(?:result|semantic|decision|verification|context|answer|intake|question|reason)-file|data))(?:=|$)/.test(value))
509
+ && !isRegisteredRepair(context, error.details.continuation, { projectId: anchor.project_id, runId: anchor.run_id });
510
+ let correctionCwd;
511
+ if (error && hookOnlyCorrection) {
512
+ const receipt = context.db.get("SELECT sanitized_summary FROM hook_events WHERE id = ? AND project_id = ?", [anchor.hook_event_id, anchor.project_id]);
513
+ let payload = {};
514
+ try {
515
+ const decoded = JSON.parse(receipt?.sanitized_summary ?? "{}");
516
+ if (decoded && typeof decoded === "object" && !Array.isArray(decoded))
517
+ payload = decoded;
518
+ }
519
+ catch { /* Truncated summaries cannot supply a route. */ }
520
+ if (payload.lifecycle_execution_cwd !== undefined) {
521
+ let confirmed = false;
522
+ const cwd = payload.lifecycle_execution_cwd;
523
+ if (typeof cwd === "string" && path.isAbsolute(cwd) && payload.lifecycle_execution_cwd_provenance === "literal_cd_intent_requires_cli_confirmation") {
524
+ try {
525
+ confirmed = fs.realpathSync(cwd) === fs.realpathSync(process.cwd());
526
+ }
527
+ catch { /* No physical confirmation, no executable correction. */ }
528
+ }
529
+ if (confirmed)
530
+ correctionCwd = cwd;
531
+ else
532
+ error = errorRecord(new AppError("invocation_directory_mismatch", "Hook-only correction has no confirmed native execution directory", 1, {
533
+ reason: "literal_execution_cwd_unconfirmed", phase: "prepare", effect: "no_effect", recoverable: false,
534
+ cause: { code: error.code, parameter: error.details.parameter, reason: error.details.reason }
535
+ }));
536
+ }
537
+ }
538
+ const correctable = error && !hookOnlyArgumentDefect && error.details.recoverable !== false && error.details.effect !== "unknown"
539
+ && (registeredContinuation || error.details.effect === "no_effect" && isCorrectableLifecycleRejection(error.code, error.details)
540
+ && typeof error.details.retry_command === "string");
452
541
  const correlationKey = parsed.kind === "standalone" ? fingerprint(parsed.invocation)
453
542
  : JSON.stringify([auxiliary.operation, auxiliary.operation === "stage_block" ? auxiliary.args.positional[2] ?? null : auxiliary.args.options.get("run")?.[0] ?? null, auxiliary.args.options.get("stage")?.[0] ?? null]);
454
- const record = { ...anchor, correlation_key: correlationKey, ...(parsed.kind === "standalone" ? { fingerprint: correlationKey } : {}), ...(error ? { error } : outcome), effect: error ? error.details.effect ?? (correctable ? "no_effect" : "unknown") : "committed", disposition: error ? auxiliary ? "pending_continuation" : correctable ? "correctable" : "fatal" : "completed" };
543
+ const record = { ...anchor, stage: anchor.stage ?? null, attempt: anchor.attempt ?? null, correlation_key: correlationKey, ...(parsed.kind === "standalone" ? { fingerprint: correlationKey } : {}), ...(error ? { error } : outcome), effect: error ? error.details.effect ?? (correctable ? "no_effect" : "unknown") : "committed", disposition: error ? auxiliary ? "pending_continuation" : correctable ? "correctable" : "fatal" : "completed" };
455
544
  try {
456
545
  context.db.writeTransaction(() => {
457
- if (error && parsed.kind === "standalone" && !auxiliary && !invocationId && !context.env.DD_FLOW_INVOCATION_SCOPE
458
- && error.details.effect === "no_effect" && isRetryableLifecycleRejection(error.code, error.details)
459
- && !isRegisteredRepair(context, error.details.continuation, { projectId: anchor.project_id, runId: anchor.run_id })) {
546
+ const retained = context.db.get("SELECT outcome_json FROM hook_events WHERE id = ?", [anchor.hook_event_id]);
547
+ const previous = retained?.outcome_json ? JSON.parse(retained.outcome_json) : null;
548
+ if (previous?.disposition !== "retrying" && previous?.error && error && previous.correlation_key === correlationKey
549
+ && preparationRejectionSignature(new AppError(previous.error.code, previous.error.message, 1, previous.error.details))
550
+ === preparationRejectionSignature(new AppError(error.code, error.message, 1, error.details))) {
551
+ error = previous.error;
552
+ Object.assign(record, previous);
553
+ return;
554
+ }
555
+ if (error && parsed.kind === "standalone" && hookOnlyCorrection && error.details.recoverable !== false) {
460
556
  const signature = preparationRejectionSignature(new AppError(error.code, error.message, 1, error.details));
461
557
  error.details.hook_retry_signature = signature;
462
- const repeated = context.db.get("SELECT 1 FROM hook_events WHERE project_id = ? AND id <> ? AND json_extract(outcome_json, '$.run_id') = ? AND json_extract(outcome_json, '$.generation') = ? AND json_extract(outcome_json, '$.operation') = ? AND json_extract(outcome_json, '$.error.details.hook_retry_signature') = ? LIMIT 1", [anchor.project_id, anchor.hook_event_id, anchor.run_id, anchor.generation, anchor.operation, signature]);
558
+ const repeated = context.db.get(`SELECT 1 FROM hook_events WHERE project_id = ? AND id <> ?
559
+ AND json_extract(outcome_json, '$.run_id') = ? AND json_extract(outcome_json, '$.generation') = ?
560
+ AND json_extract(outcome_json, '$.operation') = ? AND json_extract(outcome_json, '$.fingerprint') = ?
561
+ AND json_extract(outcome_json, '$.harness') = ? AND json_extract(outcome_json, '$.session_id') = ?
562
+ AND json_type(outcome_json, '$.stage') IS NOT NULL AND json_type(outcome_json, '$.attempt') IS NOT NULL
563
+ AND json_extract(outcome_json, '$.stage') IS ? AND json_extract(outcome_json, '$.attempt') IS ?
564
+ AND json_extract(outcome_json, '$.error.details.hook_retry_signature') = ? LIMIT 1`, [anchor.project_id, anchor.hook_event_id, anchor.run_id, anchor.generation, anchor.operation, correlationKey,
565
+ anchor.harness, anchor.session_id, anchor.stage ?? null, anchor.attempt ?? null, signature]);
463
566
  if (!repeated) {
464
- const argv = parsed.invocation.argv.filter((value, index, all) => value !== "--hook-event-id" && all[index - 1] !== "--hook-event-id" && value !== "--invocation-id" && all[index - 1] !== "--invocation-id");
465
- error.details.retry_command = runtimeCommand(context, argv);
466
- error.details.retry_instruction = "Correct the evidenced input, then run retry_command once as a standalone command in this Session. The rejected attempt had no lifecycle effect.";
567
+ const argv = parsed.invocation.argv.filter((value, index, all) => !/^--(?:hook-event-id|invocation-id)(?:=|$)/.test(value)
568
+ && !/^--(?:hook-event-id|invocation-id)$/.test(all[index - 1] ?? ""));
569
+ error.details.retry_command = `${correctionCwd ? `cd ${shellArgument(correctionCwd)} && ` : ""}${runtimeCommand(context, argv)}`;
570
+ error.details.retry_instruction = "Correct the evidenced input, then execute the exact supplied retry_command once in this Session without changing its launch route. The rejected attempt had no lifecycle effect.";
467
571
  error.details.recoverable = true;
572
+ record.disposition = "correctable";
468
573
  }
469
574
  else {
470
575
  error.details.recoverable = false;
@@ -472,8 +577,11 @@ export function observeLifecycleCommand(context, command, nativeEvent) {
472
577
  record.disposition = "fatal";
473
578
  }
474
579
  }
475
- const retained = context.db.get("SELECT outcome_json FROM hook_events WHERE id = ?", [anchor.hook_event_id]);
476
- const previous = retained?.outcome_json ? JSON.parse(retained.outcome_json) : null;
580
+ if (error && !auxiliary && record.disposition === "fatal") {
581
+ error.details.recoverable = false;
582
+ delete error.details.retry_command;
583
+ delete error.details.retry_instruction;
584
+ }
477
585
  if (previous?.storage_retry === 1)
478
586
  Object.assign(record, { storage_retry: 1, work_id: previous.work_id });
479
587
  const encoded = JSON.stringify(record);
@@ -491,6 +599,11 @@ export function observeLifecycleCommand(context, command, nativeEvent) {
491
599
  catch (persistence) {
492
600
  if (persistence instanceof AppError && persistence.code === "lifecycle_outcome_conflict")
493
601
  throw persistence;
602
+ if (error) {
603
+ error.details.recoverable = false;
604
+ delete error.details.retry_command;
605
+ delete error.details.retry_instruction;
606
+ }
494
607
  // Diagnostic evidence is not replay authority. If SQL is unavailable,
495
608
  // the controller still sees this failure and fences instead of waiting
496
609
  // for a model to report it or invent a new invocation.
@@ -508,11 +621,15 @@ export function observeLifecycleCommand(context, command, nativeEvent) {
508
621
  // Serializers intentionally clone JSON-safe diagnostics. These response
509
622
  // fields are the deliberate API overlay on the original rejection, only
510
623
  // after its exact durable hook outcome has committed.
511
- if (outcome.error && error)
512
- for (const field of ["hook_retry_signature", "retry_command", "retry_instruction", "recoverable", "repeated_without_progress"]) {
513
- if (Object.prototype.hasOwnProperty.call(error.details, field))
514
- outcome.error.details[field] = error.details[field];
624
+ if (outcome.error && error) {
625
+ outcome.error.code = error.code;
626
+ outcome.error.message = error.message;
627
+ Object.assign(outcome.error.details, error.details);
628
+ if (record.disposition === "fatal") {
629
+ delete outcome.error.details.retry_command;
630
+ delete outcome.error.details.retry_instruction;
515
631
  }
632
+ }
516
633
  };
517
634
  }
518
635
  function lifecycleDiagnostics(context, projectId, runId, generation) {
@@ -618,16 +735,16 @@ export function managedLifecycleCommand(context, command, options = {}) {
618
735
  return publicInvocationCommand(context, load(context, issued.id), command);
619
736
  });
620
737
  }
621
- export function retryLifecycleInvocationCommand(context, id) {
738
+ export function retryLifecycleInvocationCommand(context, id, checkReceiptId) {
622
739
  const prior = load(context, id);
623
740
  if (prior.status !== "executing")
624
741
  throw new AppError("invocation_retry_not_authorized", "Only the executing lifecycle operation can publish its next attempt", 1);
625
- return successorLifecycleInvocationCommand(context, prior);
742
+ return successorLifecycleInvocationCommand(context, prior, checkReceiptId);
626
743
  }
627
744
  /** A predecessor may publish exactly one successor only while the caller has
628
745
  * proved that no lifecycle effect was admitted. Shared by CLI argument
629
746
  * validation and conclusive lifecycle/result rejection, never by hooks. */
630
- function successorLifecycleInvocationCommand(context, prior) {
747
+ function successorLifecycleInvocationCommand(context, prior, checkReceiptId) {
631
748
  const parsed = invocation(prior.command);
632
749
  const argv = [...parsed.argv];
633
750
  const at = argv.indexOf("--invocation-id");
@@ -636,7 +753,23 @@ function successorLifecycleInvocationCommand(context, prior) {
636
753
  // The parser proves the marker belongs to argv. Remove that one rendered
637
754
  // token from the retained text so response-file/stdin/heredoc presentation
638
755
  // remains byte-for-byte stable across a retry.
639
- const command = retainedCommandWithoutInvocationId(prior);
756
+ let command = retainedCommandWithoutInvocationId(prior);
757
+ if (checkReceiptId) {
758
+ // Keep the trusted route and all stdin/redirection bytes. Replace only
759
+ // already-parsed literal retry options; ambiguous rendering fails closed.
760
+ let retained = prior.command;
761
+ for (const key of ["retry-check", "reason", "reason-file"]) {
762
+ const value = commandOption(parsed, key);
763
+ if (value === undefined)
764
+ continue;
765
+ const spellings = [...new Set([shellArgument(value), quote([value]), JSON.stringify(value)])];
766
+ const fragments = spellings.flatMap(rendered => [` --${key} ${rendered}`, ` --${key}=${rendered}`]).filter(fragment => retained.includes(fragment));
767
+ if (fragments.length !== 1 || retained.indexOf(fragments[0]) !== retained.lastIndexOf(fragments[0]))
768
+ throw new AppError("invocation_command_invalid", "Retained retry option cannot be rewritten unambiguously", 1);
769
+ retained = retained.replace(fragments[0], "");
770
+ }
771
+ command = retained.replace(` --invocation-id ${prior.id}`, ` --retry-check ${shellArgument(checkReceiptId)} --reason ${shellArgument("Recheck after candidate-source correction")}`);
772
+ }
640
773
  return managedLifecycleCommand({ ...context, env: { ...context.env, DD_FLOW_INVOCATION_SCOPE: prior.scope_json, DD_FLOW_CURRENT_INVOCATION: prior.id } }, command);
641
774
  }
642
775
  // Caller holds the write transaction and has checked authority. Issued (or
@@ -657,31 +790,48 @@ const receiptTimeout = (id) => ({ code: "invocation_receipt_timeout", message: "
657
790
  * outcome and its successor; never a usable successor beside an unknown effect. */
658
791
  export function settleLifecycleRejection(context, id, error) {
659
792
  storage(context);
793
+ // Only this settlement may publish a successor; inherited help is not authority.
794
+ delete error.details.retry_command;
795
+ delete error.details.retry_instruction;
660
796
  const details = { ...error.details };
661
- context.db.writeTransaction(() => {
662
- const prior = load(context, id);
663
- if (prior.status !== "executing")
664
- throw new AppError("invocation_settlement_conflict", "Attempt is no longer executing", 1, { invocation_id: id });
665
- const storageRetry = error.code === "storage_write_failed" && error.details.effect === "no_effect"
666
- && !context.db.get("SELECT 1 FROM lifecycle_invocations WHERE scope_json = ? AND fingerprint = ? AND json_extract(outcome_json, '$.error.details.storage_retry') = 1 LIMIT 1", [prior.scope_json, prior.fingerprint]);
667
- if (isRetryableLifecycleRejection(error.code, error.details) || storageRetry) {
668
- const retry = retryLifecycleInvocationCommand(context, id);
669
- details.retry_command = retry;
670
- details.recoverable = true;
671
- if (storageRetry)
672
- details.storage_retry = 1;
673
- details.retry_instruction = isRegisteredRepair(context, details.continuation)
674
- ? "The repair is already registered. End this coordinator turn so the controller can launch its fresh child. Use retry_command only after that repair completes, with fresh semantic verification."
675
- : "Correct only the evidenced defect, then use retry_command with the required result input. Reissuing the old command only reads its retained outcome.";
676
- }
677
- else if (isCorrectableLifecycleRejection(error.code, error.details)) {
678
- details.effect = "no_effect";
679
- details.recoverable = true;
680
- details.handoff_required = true;
681
- details.recovery_instruction = "Do not retry this command in the current Session. End the coordinator Turn; the controller will launch the already-registered Work in a fresh child Session.";
682
- }
683
- settleLifecycleInvocation(context, id, { error: { ...error, details } });
684
- });
797
+ try {
798
+ context.db.writeTransaction(() => {
799
+ const prior = load(context, id);
800
+ if (prior.status !== "executing")
801
+ throw new AppError("invocation_settlement_conflict", "Attempt is no longer executing", 1, { invocation_id: id });
802
+ const storageRetry = error.code === "storage_write_failed" && error.details.effect === "no_effect"
803
+ && !context.db.get("SELECT 1 FROM lifecycle_invocations WHERE scope_json = ? AND fingerprint = ? AND json_extract(outcome_json, '$.error.details.storage_retry') = 1 LIMIT 1", [prior.scope_json, prior.fingerprint]);
804
+ if (isRetryableLifecycleRejection(error.code, error.details) || storageRetry) {
805
+ const failures = ["work_checks_failed", "code_gate_failed", "code_review_gate_failed", "merge_gate_failed"].includes(error.code) ? error.details.failures : undefined;
806
+ const retry = retryLifecycleInvocationCommand(context, id, failures?.[0]?.id);
807
+ details.retry_command = retry;
808
+ details.effect = "no_effect";
809
+ details.recoverable = true;
810
+ if (storageRetry)
811
+ details.storage_retry = 1;
812
+ details.retry_instruction = isRegisteredRepair(context, details.continuation)
813
+ ? "The repair is already registered. End this coordinator turn so the controller can launch its fresh child. Use retry_command only after that repair completes, with fresh semantic verification."
814
+ : "Correct only the evidenced defect, then use retry_command with the required result input. Reissuing the old command only reads its retained outcome.";
815
+ if (["memory_review_corrections_required", "memory_correction_checks_invalid"].includes(error.code) && typeof details.correction_instruction === "string")
816
+ details.retry_instruction = details.correction_instruction;
817
+ }
818
+ else if (isCorrectableLifecycleRejection(error.code, error.details)
819
+ && registeredLifecycleContinuation(context, error, requireProjectByRoot(context, JSON.parse(prior.scope_json).projectRoot).id, JSON.parse(prior.scope_json).runId)) {
820
+ details.effect = "no_effect";
821
+ details.recoverable = true;
822
+ details.handoff_required = true;
823
+ details.recovery_instruction = "Do not retry this command in the current Session. End the coordinator Turn; the controller will launch the already-registered Work in a fresh child Session.";
824
+ }
825
+ else {
826
+ details.recoverable = false;
827
+ }
828
+ settleLifecycleInvocation(context, id, { error: { ...error, details } });
829
+ });
830
+ }
831
+ catch (persistence) {
832
+ error.details.recoverable = false;
833
+ throw persistence;
834
+ }
685
835
  Object.assign(error.details, details);
686
836
  }
687
837
  /** Recovery preserves the original packet as evidence. Only its retained,
@@ -1593,38 +1743,46 @@ export function retainObservedLifecycleSemantics(context, id, argv) {
1593
1743
  }
1594
1744
  }
1595
1745
  export function settleLifecyclePreparationRejection(context, id, error) {
1596
- return context.db.writeTransaction(() => {
1597
- const row = load(context, id);
1598
- if (row.status === "settled") {
1599
- const details = row.outcome_json ? JSON.parse(row.outcome_json)?.error?.details : undefined;
1600
- return details?.effect === "no_effect" && details.retry_command ? details : { effect: "unknown", recoverable: false };
1601
- }
1602
- if (!["issued", "observed"].includes(row.status))
1603
- return { effect: "unknown", recoverable: false };
1604
- const scope = JSON.parse(row.scope_json);
1605
- if (scope.runId)
1606
- assertLifecycleInvocationCurrent(context, scope, row.command);
1607
- const signature = preparationRejectionSignature(error);
1608
- const repeated = context.db.all("SELECT outcome_json FROM lifecycle_invocations WHERE scope_json = ? AND fingerprint = ? AND id <> ? AND status = 'settled'", [row.scope_json, row.fingerprint, row.id])
1609
- .some(prior => { try {
1610
- return JSON.parse(prior.outcome_json ?? "null")?.error?.details?.rejection_signature === signature;
1611
- }
1612
- catch {
1613
- return false;
1614
- } });
1615
- const retryable = !repeated && isRetryableLifecycleRejection(error.code, error.details);
1616
- const details = { ...error.details, effect: "no_effect", recoverable: retryable, rejection_signature: signature,
1617
- ...(repeated ? { repeated_without_progress: true } : {}),
1618
- ...(retryable ? {
1619
- retry_command: successorLifecycleInvocationCommand(context, row),
1620
- retry_instruction: "Correct the reported input, then execute retry_command verbatim in this same Session. The rejected call changed no lifecycle state."
1621
- } : {}) };
1622
- context.db.run("UPDATE lifecycle_invocations SET status = 'settled', outcome_json = ?, updated_at = ? WHERE id = ?", [JSON.stringify({ error: { ...errorRecord(error), details } }), context.now(), row.id]);
1623
- return details;
1624
- });
1746
+ delete error.details.retry_command;
1747
+ delete error.details.retry_instruction;
1748
+ try {
1749
+ return context.db.writeTransaction(() => {
1750
+ const row = load(context, id);
1751
+ if (row.status === "settled") {
1752
+ const details = row.outcome_json ? JSON.parse(row.outcome_json)?.error?.details : undefined;
1753
+ return details?.effect === "no_effect" && details.retry_command ? details : { effect: "unknown", recoverable: false };
1754
+ }
1755
+ if (!["issued", "observed"].includes(row.status))
1756
+ return { effect: "unknown", recoverable: false };
1757
+ const scope = JSON.parse(row.scope_json);
1758
+ if (scope.runId)
1759
+ assertLifecycleInvocationCurrent(context, scope, row.command);
1760
+ const signature = preparationRejectionSignature(error);
1761
+ const repeated = context.db.all("SELECT outcome_json FROM lifecycle_invocations WHERE scope_json = ? AND fingerprint = ? AND id <> ? AND status = 'settled'", [row.scope_json, row.fingerprint, row.id])
1762
+ .some(prior => { try {
1763
+ return JSON.parse(prior.outcome_json ?? "null")?.error?.details?.rejection_signature === signature;
1764
+ }
1765
+ catch {
1766
+ return false;
1767
+ } });
1768
+ const retryable = !repeated && isRetryableLifecycleRejection(error.code, error.details);
1769
+ const details = { ...error.details, effect: "no_effect", recoverable: retryable, rejection_signature: signature,
1770
+ ...(repeated ? { repeated_without_progress: true } : {}),
1771
+ ...(retryable ? {
1772
+ retry_command: successorLifecycleInvocationCommand(context, row),
1773
+ retry_instruction: "Correct the reported input, then execute retry_command verbatim in this same Session. The rejected call changed no lifecycle state."
1774
+ } : {}) };
1775
+ context.db.run("UPDATE lifecycle_invocations SET status = 'settled', outcome_json = ?, updated_at = ? WHERE id = ?", [JSON.stringify({ error: { ...errorRecord(error), details } }), context.now(), row.id]);
1776
+ return details;
1777
+ });
1778
+ }
1779
+ catch (persistence) {
1780
+ error.details.recoverable = false;
1781
+ throw persistence;
1782
+ }
1625
1783
  }
1626
1784
  function preparationRejectionSignature(error) {
1627
- const omitted = new Set(["retry_command", "retry_instruction", "invocation_id", "hook_event_id", "rejection_signature", "hook_retry_signature"]);
1785
+ const omitted = new Set(["retry_command", "retry_instruction", "invocation_id", "hook_event_id", "rejection_signature", "hook_retry_signature", "event_key", "tool_call_id", "controller_turn", "timestamp", "created_at", "updated_at", "repeated_without_progress", "recoverable"]);
1628
1786
  const normalize = (value) => {
1629
1787
  if (Array.isArray(value))
1630
1788
  return value.map(normalize);
@@ -1797,9 +1955,29 @@ export async function awaitLifecycleInvocation(context, input) {
1797
1955
  }
1798
1956
  // Another native observation can replace an unclaimed receipt. Claim
1799
1957
  // exactly the identity/route checked above, or reload and validate it.
1800
- const claimed = context.db.run("UPDATE lifecycle_invocations SET status = 'executing', updated_at = ? WHERE id = ? AND status = 'observed' AND event_key IS ? AND identity_json IS ?", [context.now(), input.id, row.event_key, row.identity_json]);
1801
- if (claimed.changes === 1)
1958
+ const workStart = invocation(row.command).operation === "work_start";
1959
+ const birth = workStart ? processStartedAt(process.pid) : null, cwd = workStart ? process.cwd() : "";
1960
+ input.assertCurrent(JSON.parse(row.scope_json));
1961
+ let admittedAt = "";
1962
+ const claimed = context.db.writeTransaction(() => {
1963
+ // Acquire the writer before taking the anchor; waiting for SQLite or
1964
+ // physical birth observation must not make an old Hook appear fresh.
1965
+ admittedAt = context.now();
1966
+ return context.db.run("UPDATE lifecycle_invocations SET status = 'executing', updated_at = ? WHERE id = ? AND status = 'observed' AND event_key IS ? AND identity_json IS ? AND scope_json=? AND command=?", [admittedAt, input.id, row.event_key, row.identity_json, row.scope_json, row.command]);
1967
+ });
1968
+ if (claimed.changes === 1) {
1969
+ if (birth) {
1970
+ let owned = currentAdmissions.get(context.db);
1971
+ if (!owned) {
1972
+ owned = new Map();
1973
+ currentAdmissions.set(context.db, owned);
1974
+ }
1975
+ if (owned.size >= 256)
1976
+ owned.delete(owned.keys().next().value);
1977
+ owned.set(input.id, { eventKey: row.event_key, identity: row.identity_json, scope: row.scope_json, command: row.command, cwd, pid: process.pid, birth, admittedAt });
1978
+ }
1802
1979
  return { eventKey: row.event_key, replay: false };
1980
+ }
1803
1981
  continue;
1804
1982
  }
1805
1983
  if (row.status === "expired" || (row.deadline !== null && row.deadline <= Date.now())) {
@@ -1816,6 +1994,7 @@ export async function awaitLifecycleInvocation(context, input) {
1816
1994
  }
1817
1995
  }
1818
1996
  export function settleLifecycleInvocation(context, id, outcome) {
1997
+ currentAdmissions.get(context.db)?.delete(id);
1819
1998
  const encoded = JSON.stringify(outcome);
1820
1999
  if (encoded === undefined)
1821
2000
  throw new AppError("invocation_outcome_invalid", "Invocation outcome must be JSON", 1);
@@ -0,0 +1,77 @@
1
+ import { AppError } from "../shared/errors.js";
2
+ import { createHash } from "node:crypto";
3
+ import { RetainedExecutionBundle } from "./execution-bundles.js";
4
+ import { assertPreparedRunBundleSqlAuthority } from "./runs.js";
5
+ import { readDescriptorRootWork } from "./descriptor-root-work.js";
6
+ import { stageGraphScope } from "./stage-work-graph.js";
7
+ import { selectMemoryCompositionRegistration } from "./memory-composition-registration.js";
8
+ import { PreparedMemoryResultBinding } from "./memory-result-binding.js";
9
+ import { PreparedMemoryStageResult, memorySourceKey } from "./memory-stage-results.js";
10
+ import { PreparedMemoryCompositionAcceptance } from "./memory-composition-acceptance.js";
11
+ import { captureMemoryCompositionSources } from "./memory-compose-handler.js";
12
+ import { readAcceptedStageData } from "./stage-report-renderer.js";
13
+ function refuse(reason) {
14
+ throw new AppError("memory_accepted_composition_invalid", "Accepted composition lacks original plan and current settled output custody", 2, { reason, effect: "no_effect" });
15
+ }
16
+ /** Shared current-document authority for VERIFY and initial REVIEW. Historical
17
+ * replay uses separate retained receipts; repaired outputs cannot masquerade
18
+ * as the original composition. This helper writes nothing and dispatches no Work.
19
+ */
20
+ export class PreparedAcceptedMemoryComposition {
21
+ #bundle;
22
+ #runRoot;
23
+ #blueprint;
24
+ #binding;
25
+ #outputs;
26
+ #result;
27
+ constructor(bundle, runRoot, blueprint, binding, outputs, result) {
28
+ this.#bundle = bundle;
29
+ this.#runRoot = runRoot;
30
+ this.#blueprint = blueprint;
31
+ this.#binding = binding;
32
+ this.#outputs = outputs;
33
+ this.#result = result;
34
+ Object.freeze(this);
35
+ }
36
+ static prepare(context, input) {
37
+ const { scope, bundle } = input;
38
+ if (!(bundle instanceof RetainedExecutionBundle) || bundle.descriptor.descriptor.id !== "mb-init")
39
+ refuse("registered_memory_bundle_required");
40
+ const run = assertPreparedRunBundleSqlAuthority(context, scope, bundle), root = readDescriptorRootWork(context, scope, bundle);
41
+ const registration = selectMemoryCompositionRegistration(context, { ...stageGraphScope(context, scope.projectId, scope.runId, "compose"), stage: "compose", parent_work_id: root.workId });
42
+ const blueprint = PreparedMemoryResultBinding.prepareRegisteredComposition(context, { ...scope, flow: bundle.descriptor, execution: bundle.execution, bundle, acceptedReplay: true,
43
+ registrationScope: registration.scope });
44
+ const outputs = PreparedMemoryCompositionAcceptance.prepare(context, { scope: registration.scope, flow: bundle.descriptor, engine: bundle.engine(), binding: blueprint.binding() });
45
+ const composed = readAcceptedStageData(context, { ...scope, stage: "compose" }), runRoot = run.run_root ?? run.run_dir;
46
+ const binding = PreparedMemoryResultBinding.prepare(context, { ...scope, stage: "compose", flow: bundle.descriptor, execution: bundle.execution, bundle, acceptedReplay: true,
47
+ sources: captureMemoryCompositionSources(context, bundle, runRoot, composed.bytes, outputs), historicalEvidence: outputs.historicalEvidence() });
48
+ if (!binding.acceptedBytes()?.equals(composed.bytes))
49
+ refuse("accepted_composition_changed");
50
+ const result = PreparedMemoryStageResult.prepare(composed.bytes, bundle.descriptor, binding.binding());
51
+ outputs.assertResult(result);
52
+ const prepared = new PreparedAcceptedMemoryComposition(bundle, runRoot, blueprint, binding, outputs, result);
53
+ prepared.assertCustody(context);
54
+ return prepared;
55
+ }
56
+ result() { return this.#result; }
57
+ outputReferences() {
58
+ const hashes = this.#outputs.outputHashes();
59
+ return this.#outputs.outputLocations().map(location => ({ ...location, sha256: hashes.get(memorySourceKey(location.root, location.path)) }));
60
+ }
61
+ historicalEvidence() { return this.#outputs.historicalEvidence(); }
62
+ captureSources(context, bytes, checks) {
63
+ return captureMemoryCompositionSources(context, this.#bundle, this.#runRoot, bytes, this.#outputs, checks, createHash("sha256").update(bytes).digest("hex"));
64
+ }
65
+ assertCustody(context) {
66
+ this.#blueprint.assertCustody(context);
67
+ this.#binding.assertCustody(context);
68
+ this.#outputs.assertCustody(context);
69
+ this.#outputs.assertResult(this.#result);
70
+ }
71
+ revalidate(context) {
72
+ this.#blueprint.revalidate(context);
73
+ this.#binding.revalidate(context);
74
+ this.#outputs.revalidate(context);
75
+ this.assertCustody(context);
76
+ }
77
+ }
@@ -0,0 +1,65 @@
1
+ import { AppError } from "../shared/errors.js";
2
+ import { FrozenGenericExecution } from "./generic-execution.js";
3
+ import { hybridDigest } from "./hybrid-contracts.js";
4
+ import { readMemoryInitBootstrapReadiness } from "./memory-init-bootstrap-readiness.js";
5
+ import { readMemoryInitProjectBootstrap } from "./memory-init-project-bootstrap.js";
6
+ import { assertPublishedMemoryLayerOwnership } from "./memory-layer-publication.js";
7
+ import { observeMemoryInitSourceInventory } from "./memory-init-preflight.js";
8
+ function refuse(reason) {
9
+ throw new AppError("memory_baseline_preservation_invalid", "Composition cannot accept changed bootstrap sources", 1, { reason, effect: "no_effect" });
10
+ }
11
+ /** Pre-integration source preservation, not semantic scope or stage authority.
12
+ * Only the accepted publisher's ownership is checked before using its MB exclusion.
13
+ * This admits no caller-supplied baseline and never repairs or republishes files.
14
+ */
15
+ export class PreparedMemoryBaselinePreservation {
16
+ sourceSha256;
17
+ #execution;
18
+ #authoritySha256;
19
+ #inventorySha256;
20
+ constructor(execution, authoritySha256, inventorySha256, sourceSha256) {
21
+ this.#execution = execution;
22
+ this.#authoritySha256 = authoritySha256;
23
+ this.#inventorySha256 = inventorySha256;
24
+ this.sourceSha256 = sourceSha256;
25
+ Object.freeze(this);
26
+ }
27
+ static accepted(context, execution) {
28
+ if (!(execution instanceof FrozenGenericExecution) || execution.settings.flow_id !== "mb-init")
29
+ refuse("unprepared_execution");
30
+ const scope = execution.snapshot().scope;
31
+ const accepted = readMemoryInitBootstrapReadiness(context, { requestId: scope.requestId, projectRoot: scope.projectRoot, workspaceRoot: scope.workspace });
32
+ if (!accepted || accepted.authority.binding.execution_sha256 !== execution.sha256)
33
+ refuse("accepted_bootstrap_required");
34
+ return accepted;
35
+ }
36
+ static observe(context, accepted) {
37
+ const binding = accepted.authority.binding, scope = binding.scope;
38
+ const registered = readMemoryInitProjectBootstrap(context, scope);
39
+ if (!registered)
40
+ refuse("accepted_bootstrap_required");
41
+ assertPublishedMemoryLayerOwnership(context.ddFlowHome, { ...scope, targetSha256: registered.receipt.target_sha256,
42
+ layerSha256: registered.receipt.layer_sha256, packSha256: binding.pack_sha256, installationSha256: binding.installation_manifest_sha256 }, binding.target, registered.receipt.layer_publication_sha256, registered.receipt.draft_identity_sha256);
43
+ return observeMemoryInitSourceInventory(binding.target, accepted.authority.receipt.baseline_sha256, context.env);
44
+ }
45
+ static prepare(context, execution) {
46
+ const accepted = this.accepted(context, execution);
47
+ const inventory = this.observe(context, accepted);
48
+ const prepared = new PreparedMemoryBaselinePreservation(execution, hybridDigest(accepted.authority), inventory.sha256, accepted.authority.binding.target.source_sha256);
49
+ prepared.assertCustody(context);
50
+ return prepared;
51
+ }
52
+ /** SQL-only acceptance fence. Physical checks must precede the writer. */
53
+ assertCustody(context) {
54
+ if (hybridDigest(PreparedMemoryBaselinePreservation.accepted(context, this.#execution).authority) !== this.#authoritySha256)
55
+ refuse("bootstrap_authority_changed");
56
+ }
57
+ revalidate(context) {
58
+ this.assertCustody(context);
59
+ const accepted = PreparedMemoryBaselinePreservation.accepted(context, this.#execution);
60
+ const inventory = PreparedMemoryBaselinePreservation.observe(context, accepted);
61
+ if (inventory.sha256 !== this.#inventorySha256)
62
+ refuse("baseline_inventory_changed");
63
+ this.assertCustody(context);
64
+ }
65
+ }