@deksden-com/dd-flow-cli 0.9.0-beta.144 → 0.9.0-beta.149

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 (254) hide show
  1. package/CHANGELOG.md +130 -0
  2. package/README.md +13 -8
  3. package/dist/build-info.json +7 -7
  4. package/dist/cli/command-inputs.js +13 -3
  5. package/dist/cli/command-metadata.js +51 -16
  6. package/dist/cli/diagnostic-invocation.js +46 -0
  7. package/dist/cli/diagnostic.js +120 -0
  8. package/dist/cli/help.js +67 -1
  9. package/dist/cli/hook-ingress.js +27 -3
  10. package/dist/cli/memory-flow-admission.js +9 -3
  11. package/dist/cli/memory-init-invocation.js +10 -3
  12. package/dist/cli/memory-init.js +77 -68
  13. package/dist/cli/memory-storage-admission.js +3 -2
  14. package/dist/cli/memory-storage-qualification.js +105 -0
  15. package/dist/cli/memory-storage-transition.js +123 -0
  16. package/dist/cli/memory-upgrade.js +189 -0
  17. package/dist/cli/native-hook-ingress.js +14 -4
  18. package/dist/cli/native-hook-preparation.js +55 -0
  19. package/dist/cli/response-file.js +2 -1
  20. package/dist/cli/run-cli.js +79 -40
  21. package/dist/cli/setup.js +2 -1
  22. package/dist/cli.js +18 -0
  23. package/dist/domain/diagnostic-aspects.js +103 -0
  24. package/dist/domain/diagnostic-flows.js +8 -0
  25. package/dist/domain/stage-handler-identities.js +8 -0
  26. package/dist/harness-runtime/bin/dd-agy.mjs +3 -3
  27. package/dist/harness-runtime/bin/dd-grok.mjs +3 -3
  28. package/dist/harness-runtime/bin/dd-zcode.mjs +5 -5
  29. package/dist/harness-runtime/lib/agy-error-boundary.mjs +58 -0
  30. package/dist/harness-runtime/lib/agy-native-inspection.mjs +216 -0
  31. package/dist/harness-runtime/lib/agy-native_pb.js +117 -0
  32. package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
  33. package/dist/harness-runtime/lib/daemon-operations.mjs +84 -10
  34. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +366 -52
  35. package/dist/harness-runtime/lib/dd-agy.mjs +17 -3
  36. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +27 -13
  37. package/dist/harness-runtime/lib/dd-codex.mjs +4 -0
  38. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +4 -3
  39. package/dist/harness-runtime/lib/dd-droid.mjs +71 -29
  40. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +73 -16
  41. package/dist/harness-runtime/lib/dd-grok.mjs +6 -3
  42. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +66 -20
  43. package/dist/harness-runtime/lib/dd-opencode.mjs +30 -3
  44. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +7 -6
  45. package/dist/harness-runtime/lib/dd-zcode.mjs +44 -14
  46. package/dist/harness-runtime/lib/delegation-instructions.mjs +1 -1
  47. package/dist/harness-runtime/lib/driver-recovery.mjs +9 -8
  48. package/dist/harness-runtime/lib/entity-references.d.mts +2 -0
  49. package/dist/harness-runtime/lib/entity-references.mjs +9 -0
  50. package/dist/harness-runtime/lib/managed-daemon.d.mts +1 -1
  51. package/dist/harness-runtime/lib/managed-daemon.mjs +24 -9
  52. package/dist/harness-runtime/lib/native-children.d.mts +8 -1
  53. package/dist/harness-runtime/lib/native-children.mjs +114 -8
  54. package/dist/harness-runtime/lib/native-hook-command.mjs +4 -4
  55. package/dist/harness-runtime/lib/native-request-proof.mjs +1 -1
  56. package/dist/harness-runtime/lib/observation-clock.d.mts +2 -2
  57. package/dist/harness-runtime/lib/observation-clock.mjs +7 -4
  58. package/dist/harness-runtime/lib/opencode-plugin.mjs +3 -3
  59. package/dist/harness-runtime/lib/operation-errors.mjs +1 -1
  60. package/dist/harness-runtime/lib/provider-overload-policy.d.mts +5 -3
  61. package/dist/harness-runtime/lib/provider-overload-policy.mjs +20 -7
  62. package/dist/harness-runtime/lib/runner-events.mjs +3 -1
  63. package/dist/harness-runtime/lib/setup-progress.mjs +8 -6
  64. package/dist/harness-runtime/lib/tool-observations.mjs +1 -1
  65. package/dist/schemas/diagnostic-stage-result.schema.json +354 -0
  66. package/dist/schemas/diagnostic-work-result.schema.json +296 -0
  67. package/dist/schemas/execution-bundle.schema.json +7 -1
  68. package/dist/schemas/mb-upgrade-stage-result.schema.json +863 -0
  69. package/dist/schemas/memory-repair-work-result.schema.json +9 -2
  70. package/dist/schemas/project-flow-pack-manifest.schema.json +1 -1
  71. package/dist/schemas/release-impact.schema.json +3 -3
  72. package/dist/schemas/stage-start-response.schema.json +4 -0
  73. package/dist/services/accepted-intent.js +13 -1
  74. package/dist/services/canon.js +43 -7
  75. package/dist/services/check-environment.js +21 -0
  76. package/dist/services/check-runner-evidence.js +267 -15
  77. package/dist/services/cli-operation-classifier.js +1 -1
  78. package/dist/services/cli-upgrade-allowlist.js +1 -1
  79. package/dist/services/code-checks.js +34 -19
  80. package/dist/services/compatibility-preflight.js +5 -2
  81. package/dist/services/controller-execution.js +10 -4
  82. package/dist/services/controller-fanout.js +34 -10
  83. package/dist/services/controller-memory-task.js +45 -0
  84. package/dist/services/controller-memory-worker.js +49 -0
  85. package/dist/services/dashboard.js +2 -2
  86. package/dist/services/descriptor-run-settlement.js +5 -3
  87. package/dist/services/descriptor-run-start.js +46 -20
  88. package/dist/services/descriptor-stage-coordinator.js +66 -13
  89. package/dist/services/descriptor-stage-settlement.js +2 -1
  90. package/dist/services/descriptor-stage-start.js +40 -4
  91. package/dist/services/diagnostic-evidence.js +73 -0
  92. package/dist/services/diagnostic-report-publication.js +72 -0
  93. package/dist/services/diagnostic-report.js +67 -0
  94. package/dist/services/diagnostic-run-bundle.js +74 -0
  95. package/dist/services/diagnostic-snapshot.js +159 -0
  96. package/dist/services/diagnostic-stage-handlers.js +108 -0
  97. package/dist/services/diagnostic-stage-results.js +132 -0
  98. package/dist/services/diagnostic-start.js +84 -0
  99. package/dist/services/diagnostic-work-batch.js +34 -0
  100. package/dist/services/diagnostic-work-collection.js +97 -0
  101. package/dist/services/diagnostic-work-registration.js +101 -0
  102. package/dist/services/diagnostic-work-results.js +93 -0
  103. package/dist/services/engines.js +64 -14
  104. package/dist/services/eval-snapshots.js +186 -119
  105. package/dist/services/execution-bundles.js +19 -7
  106. package/dist/services/external-work-launch.js +5 -3
  107. package/dist/services/flow-catalog.js +13 -4
  108. package/dist/services/frozen-engine.js +148 -11
  109. package/dist/services/hook-memory-routing.js +188 -0
  110. package/dist/services/hook-recovery-guard.js +14 -0
  111. package/dist/services/hook-session-observation.js +24 -0
  112. package/dist/services/hooks.js +27 -8
  113. package/dist/services/hybrid-contracts.js +4 -13
  114. package/dist/services/hybrid-digest.js +14 -0
  115. package/dist/services/installed-flow-source.js +47 -4
  116. package/dist/services/lifecycle-invocations.js +179 -38
  117. package/dist/services/memory-accepted-composition.js +5 -4
  118. package/dist/services/memory-completed-correction-wave.js +23 -15
  119. package/dist/services/memory-compose-handler.js +163 -21
  120. package/dist/services/memory-composition-acceptance.js +186 -10
  121. package/dist/services/memory-composition-graph.js +42 -11
  122. package/dist/services/memory-composition-registration.js +23 -8
  123. package/dist/services/memory-composition-work-custody.js +14 -9
  124. package/dist/services/memory-composition-work-results.js +15 -3
  125. package/dist/services/memory-corrected-review-batch.js +10 -3
  126. package/dist/services/memory-corrected-review-findings.js +4 -2
  127. package/dist/services/memory-corrected-review-inputs.js +10 -5
  128. package/dist/services/memory-corrected-review-receipt.js +17 -9
  129. package/dist/services/memory-corrected-review-registration.js +3 -2
  130. package/dist/services/memory-correction-check-receipt.js +20 -4
  131. package/dist/services/memory-custody-snapshot.js +36 -0
  132. package/dist/services/memory-finish-evidence.js +44 -22
  133. package/dist/services/memory-finish-handler.js +37 -23
  134. package/dist/services/memory-finish-integration.js +218 -48
  135. package/dist/services/memory-finish-readiness.js +66 -19
  136. package/dist/services/memory-git-integration.js +87 -18
  137. package/dist/services/memory-historical-corrected-review.js +21 -15
  138. package/dist/services/memory-historical-original-review.js +22 -9
  139. package/dist/services/memory-historical-repair-registration.js +14 -8
  140. package/dist/services/memory-historical-second-review.js +30 -11
  141. package/dist/services/memory-init-contexts.js +44 -2
  142. package/dist/services/memory-init-controller-start.js +27 -8
  143. package/dist/services/memory-init-git-policy.js +50 -21
  144. package/dist/services/memory-init-lint-capability.js +24 -6
  145. package/dist/services/memory-init-native-capacity.js +17 -4
  146. package/dist/services/memory-init-preflight.js +21 -17
  147. package/dist/services/memory-init-request-candidate.js +73 -17
  148. package/dist/services/memory-init-request-input.js +23 -7
  149. package/dist/services/memory-init-run-bundle.js +49 -12
  150. package/dist/services/memory-init-storage.js +17 -7
  151. package/dist/services/memory-init-worktrunk-capability.js +3 -7
  152. package/dist/services/memory-layer.js +71 -6
  153. package/dist/services/memory-lint-config.js +111 -2
  154. package/dist/services/memory-native-review-copy.js +5 -67
  155. package/dist/services/memory-normal-flow-entry.js +12 -0
  156. package/dist/services/memory-ordinary-defaults.js +22 -5
  157. package/dist/services/memory-preflight-handler.js +15 -0
  158. package/dist/services/memory-repair-current-view.js +1 -1
  159. package/dist/services/memory-repair-document-delta.js +70 -33
  160. package/dist/services/memory-repair-history.js +12 -7
  161. package/dist/services/memory-repair-physical-binding.js +1 -119
  162. package/dist/services/memory-repair-publication-plan.js +5 -3
  163. package/dist/services/memory-repair-publication-source.js +1 -1
  164. package/dist/services/memory-repair-publication.js +3 -2
  165. package/dist/services/memory-repair-registration.js +2 -1
  166. package/dist/services/memory-repair-work-baseline.js +4 -2
  167. package/dist/services/memory-repair-work-completion.js +32 -12
  168. package/dist/services/memory-repair-work-results.js +21 -2
  169. package/dist/services/memory-repair-workspace.js +11 -2
  170. package/dist/services/memory-repair-writer-copy.js +2 -1
  171. package/dist/services/memory-result-binding.js +78 -12
  172. package/dist/services/memory-retained-review-authority.js +17 -8
  173. package/dist/services/memory-retained-second-baseline.js +1 -1
  174. package/dist/services/memory-retained-second-repairs.js +12 -6
  175. package/dist/services/memory-review-correction-plan.js +47 -12
  176. package/dist/services/memory-review-handler.js +17 -17
  177. package/dist/services/memory-review-inputs.js +55 -9
  178. package/dist/services/memory-review-limits.js +10 -0
  179. package/dist/services/memory-review-receipt.js +2 -2
  180. package/dist/services/memory-review-registration.js +13 -4
  181. package/dist/services/memory-review-work-custody.js +3 -1
  182. package/dist/services/memory-review-work-input.js +26 -7
  183. package/dist/services/memory-review-work-results.js +26 -4
  184. package/dist/services/memory-second-corrected-review-inputs.js +5 -3
  185. package/dist/services/memory-second-correction-plan.js +2 -1
  186. package/dist/services/memory-second-repair-launch.js +4 -4
  187. package/dist/services/memory-second-repair-source.js +1 -1
  188. package/dist/services/memory-stage-dispatch.js +38 -1
  189. package/dist/services/memory-stage-results.js +113 -22
  190. package/dist/services/memory-tool-probe.js +25 -0
  191. package/dist/services/memory-upgrade-analysis-handler.js +243 -0
  192. package/dist/services/memory-upgrade-bootstrap-readiness.js +251 -0
  193. package/dist/services/memory-upgrade-evidence.js +100 -0
  194. package/dist/services/memory-upgrade-inventory.js +442 -0
  195. package/dist/services/memory-upgrade-publication-custody.js +73 -0
  196. package/dist/services/memory-upgrade-publication.js +413 -0
  197. package/dist/services/memory-upgrade-selection.js +430 -0
  198. package/dist/services/memory-upgrade-start.js +60 -0
  199. package/dist/services/memory-upgrade-storage-readiness.js +340 -0
  200. package/dist/services/memory-verification-acceptance.js +38 -17
  201. package/dist/services/memory-verification-checks.js +92 -27
  202. package/dist/services/memory-verification-receipt.js +6 -5
  203. package/dist/services/memory-verify-handler.js +15 -13
  204. package/dist/services/memory-work-result-json.js +12 -0
  205. package/dist/services/memory-work-result-preparation.js +5 -4
  206. package/dist/services/merge-server.js +4 -2
  207. package/dist/services/migrations.js +127 -98
  208. package/dist/services/native-daemon-history.js +2 -1
  209. package/dist/services/physical-startup.js +45 -0
  210. package/dist/services/project-registry.js +97 -0
  211. package/dist/services/project-summary.js +1 -1
  212. package/dist/services/projects.js +3 -93
  213. package/dist/services/prompt-delivery.js +71 -0
  214. package/dist/services/provider-capacity.js +112 -46
  215. package/dist/services/recovery-snapshot-database.js +32 -4
  216. package/dist/services/release-cache.js +8 -0
  217. package/dist/services/repair-intents.js +1 -12
  218. package/dist/services/repair-receipt.js +13 -0
  219. package/dist/services/run-control-worker.js +35 -18
  220. package/dist/services/run-control.js +2 -1
  221. package/dist/services/run-controller-adapter.js +2 -1
  222. package/dist/services/run-controller-process.js +4 -7
  223. package/dist/services/run-controller-recovery.js +8 -2
  224. package/dist/services/run-controller.js +215 -94
  225. package/dist/services/run-fork.js +1 -1
  226. package/dist/services/run-recovery-guard.js +30 -0
  227. package/dist/services/run-recovery.js +11 -44
  228. package/dist/services/run-start-custody.js +17 -5
  229. package/dist/services/runs.js +6 -1
  230. package/dist/services/runtime-scope-resume.js +12 -6
  231. package/dist/services/runtime-scope-stop.js +3 -2
  232. package/dist/services/runtime-scope-worker.js +46 -19
  233. package/dist/services/runtime-service.js +2 -1
  234. package/dist/services/sessions.js +3 -24
  235. package/dist/services/setup.js +63 -13
  236. package/dist/services/stage-handlers.js +22 -7
  237. package/dist/services/stage-pause.js +111 -44
  238. package/dist/services/version-status.js +1 -1
  239. package/dist/services/vnext-code.js +2 -2
  240. package/dist/services/vnext-fanout.js +51 -9
  241. package/dist/services/vnext-plan-review.js +3 -5
  242. package/dist/services/work-registry.js +117 -39
  243. package/dist/services/workspace-bootstrap.js +2 -1
  244. package/dist/services/workspace-files.js +1 -1
  245. package/dist/shared/entity-references.js +1 -8
  246. package/dist/storage/data-preservation.js +65 -0
  247. package/dist/storage/database.js +53 -19
  248. package/dist/storage/import-guard.js +84 -27
  249. package/dist/storage/memory-upgrade-schema-bootstrap.js +38 -0
  250. package/dist/storage/paired-writer-migration.js +424 -0
  251. package/dist/storage/writer-contract.js +11 -4
  252. package/dist/storage/writer-migration.js +313 -52
  253. package/dist/storage/writer-restore.js +217 -0
  254. package/package.json +13 -6
@@ -0,0 +1,34 @@
1
+ import { AppError } from "../shared/errors.js";
2
+ import { hybridDigest } from "./hybrid-contracts.js";
3
+ /** Only applicable aspects receive analytical Work. Critics receive all candidates,
4
+ * including candidates they may reject; no empty pseudo-Work proves coverage. */
5
+ export function diagnosticWorkBatch(input) {
6
+ const review = input.stage === "review";
7
+ if (!review && input.stage !== (input.flow_id === "mb-audit" ? "audit" : "analyze")) {
8
+ throw new AppError("diagnostic_work_batch_invalid", "Diagnostic Work stage belongs to another flow", 2, { effect: "no_effect" });
9
+ }
10
+ const applicable = input.coverage.filter(value => value.applicability === "applicable");
11
+ const architecture = applicable.some(value => value.id ===
12
+ (input.flow_id === "mb-audit" ? "05" : "architecture_harmonization_review"));
13
+ const assignments = review
14
+ ? ["universal", ...(architecture ? ["architecture"] : [])].map(id => ({ id, role: "critic" }))
15
+ : applicable.map(value => ({ id: value.id, role: "aspect" }));
16
+ return { works: assignments.map((assignment, index) => {
17
+ const key = `${assignment.role}-${String(index + 1).padStart(3, "0")}`;
18
+ const packet = { schema_id: "dd-flow/diagnostic-work-input@1", flow_id: input.flow_id, run_id: input.run_id,
19
+ stage: input.stage, key, aspect_id: assignment.id, role: assignment.role,
20
+ input_sha256: input.input_sha256, snapshot_sha256: input.snapshot_sha256,
21
+ instruction: input.instruction, coverage: input.coverage, support: input.support ?? [],
22
+ candidates: review ? input.candidates : [] };
23
+ return { key, depends_on: [], launch_policy: "fresh_agent_required",
24
+ result_schema: "dd-flow/diagnostic-work-result@1",
25
+ task: (review
26
+ ? `Independently assess every supplied candidate as the ${assignment.id} critic. Read the assigned immutable snapshot. Retain rejected findings and explain disagreement. Do not invent implementation, checks, sources or completion. Return exactly one decision per candidate using the assigned diagnostic result schema. Do not edit project files, repair findings, allocate Work or coordinate the Stage.`
27
+ : `Examine only the assigned aspect ${JSON.stringify(assignment.id)} using the frozen instruction and actual snapshot sources. Distinguish implemented behavior from plans and absent evidence. Cite exact sources for material findings; retain uncertainty and active DEFs. Do not edit project files, execute project setup, repair findings, allocate Work or claim independent review. Return the assigned diagnostic Work result.`)
28
+ + " Read applicable criterion files in diagnostic.support relative to run_home. They are frozen reference criteria, not executable/manual lifecycle instructions or implemented project facts. Cite snapshot sources as project evidence, not canon references."
29
+ + " Every sources/evidence path must be relative to the assigned snapshot workspace root, using forward slashes (for example src/index.ts or .memory-bank/index.md), with the SHA256 of its exact snapshot bytes. Never cite absolute paths, .git, intake transport files, RUN packets or canon support as snapshot evidence; never invent a relative path for a file outside the snapshot."
30
+ + " Files under .dd-flow/review-evidence are CLI-generated observations, not original project sources: do not put them in sources/evidence or infer the original repository's history or unborn state from the working copy's Git metadata. For missing linked targets cite the existing referring project document and describe the absence as a limitation, not as a source file.",
31
+ payload: { kind: review ? "diagnostic-critic" : "diagnostic-aspect", read_only: true,
32
+ diagnostic: { ...packet, packet_sha256: hybridDigest(packet) } } };
33
+ }) };
34
+ }
@@ -0,0 +1,97 @@
1
+ import { AppError } from "../shared/errors.js";
2
+ import { isDiagnosticFlow } from "../domain/diagnostic-flows.js";
3
+ import { readFlowRunExecutionBundle, assertPreparedRunBundleSqlAuthority } from "./runs.js";
4
+ import { requireProjectByRoot } from "./projects.js";
5
+ import { resolveProjectRoot } from "../storage/paths.js";
6
+ import { readRegisteredDiagnosticGraph } from "./diagnostic-work-registration.js";
7
+ import { diagnosticWorkBatch } from "./diagnostic-work-batch.js";
8
+ import { validateDiagnosticWorkResult } from "./diagnostic-work-results.js";
9
+ import { diagnosticSnapshotPaths, readDiagnosticSnapshot, readDiagnosticSnapshotCustody } from "./diagnostic-snapshot.js";
10
+ import { readDescriptorRootWork } from "./descriptor-root-work.js";
11
+ import { hybridDigest } from "./hybrid-contracts.js";
12
+ function refuse(reason) { throw new AppError("diagnostic_work_collection_invalid", "Diagnostic graph is not independently settled", 2, { reason, effect: "no_effect" }); }
13
+ const physicalKey = (session) => `${session.harness}\0${session.provider_session_id}`;
14
+ export function assertDiagnosticSessionIndependence(workers, excluded) {
15
+ const keys = workers.map(physicalKey), excludedKeys = new Set(excluded.map(physicalKey));
16
+ if (workers.some(value => !value.harness || !value.provider_session_id)
17
+ || new Set(keys).size !== keys.length || keys.some(key => excludedKeys.has(key)))
18
+ refuse("physical_session_reused");
19
+ }
20
+ function completedWork(context, scope, workId) {
21
+ const work = context.db.get("SELECT status,result FROM works WHERE project_id=? AND run_id=? AND work_id=?", [scope.projectId, scope.runId, workId]);
22
+ if (work?.status !== "completed" || !work.result || Buffer.byteLength(work.result) > 1024 * 1024)
23
+ refuse("work_not_completed");
24
+ const sessions = context.db.all("SELECT ws.id,ws.session_id,ws.status,s.harness,s.provider_session_id,ws.hook_event_id FROM work_sessions ws JOIN sessions s ON s.project_id=? AND s.session_id=ws.session_id AND s.run_id=? WHERE ws.work_id=? AND ws.status IN ('running','completed') LIMIT 3", [scope.projectId, scope.runId, workId]);
25
+ if (sessions.length !== 1 || sessions[0].status !== "completed")
26
+ refuse("work_session_not_settled");
27
+ const session = sessions[0];
28
+ const hook = context.db.get("SELECT session_id,harness,provider_session_id,status FROM hook_events WHERE project_id=? AND event_key=?", [scope.projectId, session.hook_event_id]);
29
+ if (!hook || hook.status !== "claimed" || hook.session_id !== session.session_id
30
+ || physicalKey(hook) !== physicalKey(session))
31
+ refuse("physical_work_owner_missing");
32
+ return { workId, result: work.result, session };
33
+ }
34
+ function collectSql(context, scope, rootId) {
35
+ const graph = readRegisteredDiagnosticGraph(context, scope);
36
+ if (!graph || graph.parent_work_id !== rootId)
37
+ refuse("registration_required");
38
+ const batch = diagnosticWorkBatch(graph.input);
39
+ const works = batch.works.map(item => completedWork(context, scope, graph.work_ids[item.key]));
40
+ const coordinators = context.db.all("SELECT s.harness,s.provider_session_id FROM work_sessions ws JOIN sessions s ON s.project_id=? AND s.run_id=? AND s.session_id=ws.session_id WHERE ws.work_id=?", [scope.projectId, scope.runId, rootId]);
41
+ const authorGraph = scope.stage === "review" ? readRegisteredDiagnosticGraph(context, { ...scope, stage: graph.input.flow_id === "mb-audit" ? "audit" : "analyze" }) : null;
42
+ if (scope.stage === "review" && !authorGraph)
43
+ refuse("author_graph_missing");
44
+ const authors = authorGraph ? Object.values(authorGraph.work_ids).map(id => completedWork(context, scope, id)) : [];
45
+ assertDiagnosticSessionIndependence(works.map(value => value.session), [...coordinators, ...authors.map(value => value.session)]);
46
+ return { graph, works, authors, coordinators };
47
+ }
48
+ /** File/schema/evidence checks occur before settlement. assertCustody is SQL-only
49
+ * and keeps the selected results and physical owners stable in its writer. */
50
+ export class PreparedDiagnosticWorkCollection {
51
+ #scope;
52
+ #rootId;
53
+ #sql;
54
+ #snapshot;
55
+ #results;
56
+ constructor(scope, rootId, sql, snapshot, results) {
57
+ this.#scope = scope;
58
+ this.#rootId = rootId;
59
+ this.#sql = sql;
60
+ this.#snapshot = snapshot;
61
+ this.#results = results;
62
+ Object.freeze(this);
63
+ }
64
+ static prepare(context, input) {
65
+ const bundle = readFlowRunExecutionBundle(context, input), project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
66
+ if (!bundle || !isDiagnosticFlow(bundle.manifest.flow_id))
67
+ refuse("diagnostic_bundle_required");
68
+ const scope = { projectId: project.id, runId: input.runId, stage: input.stage };
69
+ const frozen = bundle.execution.snapshot().scope;
70
+ const snapshot = readDiagnosticSnapshot(context, { projectId: project.id, projectRoot: frozen.projectRoot,
71
+ sourceRoot: frozen.projectRoot, requestId: frozen.requestId, flowId: bundle.manifest.flow_id }, input.runId);
72
+ if (!snapshot || snapshot.root !== frozen.workspace)
73
+ refuse("snapshot_required");
74
+ const root = readDescriptorRootWork(context, scope, bundle);
75
+ const sql = context.db.readSnapshot(() => {
76
+ assertPreparedRunBundleSqlAuthority(context, scope, bundle);
77
+ return collectSql(context, scope, root.workId);
78
+ });
79
+ if (sql.graph.bundle_sha256 !== bundle.sha256 || sql.graph.input.snapshot_sha256 !== snapshot.source_sha256)
80
+ refuse("foreign_graph");
81
+ const batch = diagnosticWorkBatch(sql.graph.input), schema = bundle.blob("schemas/diagnostic-work-result.schema.json");
82
+ const results = sql.works.map((work, index) => ({ workId: work.workId,
83
+ result: validateDiagnosticWorkResult(Buffer.from(work.result), schema, diagnosticSnapshotPaths(context, snapshot, input.runId).root, sql.graph.input, {
84
+ workId: work.workId, aspectId: batch.works[index].payload.diagnostic.aspect_id,
85
+ role: batch.works[index].payload.diagnostic.role
86
+ }) }));
87
+ return new PreparedDiagnosticWorkCollection(scope, root.workId, sql, snapshot, results);
88
+ }
89
+ results() { return structuredClone(this.#results); }
90
+ assertCustody(context) {
91
+ const s = this.#snapshot;
92
+ if (hybridDigest(collectSql(context, this.#scope, this.#rootId)) !== hybridDigest(this.#sql)
93
+ || hybridDigest(readDiagnosticSnapshotCustody(context, { projectId: s.projectId, projectRoot: s.projectRoot,
94
+ sourceRoot: s.sourceRoot, requestId: s.requestId, flowId: s.flowId }, this.#scope.runId)) !== hybridDigest(s))
95
+ refuse("collection_changed");
96
+ }
97
+ }
@@ -0,0 +1,101 @@
1
+ import { AppError } from "../shared/errors.js";
2
+ import { isDiagnosticFlow } from "../domain/diagnostic-flows.js";
3
+ import { requireProjectByRoot } from "./projects.js";
4
+ import { resolveProjectRoot } from "../storage/paths.js";
5
+ import { readFlowRunExecutionBundle, assertPreparedRunBundleSqlAuthority } from "./runs.js";
6
+ import { readDescriptorStageInput } from "./descriptor-stage-coordinator.js";
7
+ import { readDescriptorRootWork } from "./descriptor-root-work.js";
8
+ import { diagnosticSnapshotPaths, readDiagnosticSnapshot, readDiagnosticSnapshotCustody } from "./diagnostic-snapshot.js";
9
+ import { readAcceptedStageData } from "./stage-report-renderer.js";
10
+ import { validateDiagnosticStageResult } from "./diagnostic-stage-results.js";
11
+ import { diagnosticWorkBatch } from "./diagnostic-work-batch.js";
12
+ import { addWorkBatch, prepareWorkBatch, readWorkBatchCustody } from "./work-registry.js";
13
+ import { stageGraphScope } from "./stage-work-graph.js";
14
+ import { hybridDigest } from "./hybrid-contracts.js";
15
+ import { appendAudit } from "./audit.js";
16
+ const event = "diagnostic.work_registration";
17
+ function refuse(reason) { throw new AppError("diagnostic_work_registration_invalid", "Diagnostic graph lacks its assigned stage authority", 2, { reason, effect: "no_effect" }); }
18
+ /** SQL-only reader. An explicitly registered empty graph is distinct from no graph. */
19
+ export function readRegisteredDiagnosticGraph(context, scope) {
20
+ const graph = stageGraphScope(context, scope.projectId, scope.runId, scope.stage);
21
+ const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type=? AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.stage')=? AND json_extract(payload_json,'$.attempt')=? LIMIT 2", [scope.projectId, event, scope.runId, scope.stage, graph.attempt]);
22
+ if (!rows.length)
23
+ return null;
24
+ if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 2 * 1024 * 1024)
25
+ refuse("ambiguous_registration");
26
+ const receipt = JSON.parse(rows[0].payload_json);
27
+ if (receipt.schema_id !== "dd-flow/diagnostic-work-registration@1" || receipt.run_id !== scope.runId
28
+ || receipt.stage !== scope.stage || receipt.attempt !== graph.attempt || !receipt.work_ids
29
+ || receipt.input.run_id !== scope.runId || receipt.input.stage !== scope.stage
30
+ || receipt.input.input_sha256 !== receipt.input_sha256)
31
+ refuse("foreign_registration");
32
+ const batch = diagnosticWorkBatch(receipt.input);
33
+ if (receipt.batch_sha256 !== hybridDigest(batch.works) || batch.works.length !== Object.keys(receipt.work_ids).length
34
+ || new Set(Object.values(receipt.work_ids)).size !== batch.works.length)
35
+ refuse("changed_batch");
36
+ for (const item of batch.works) {
37
+ const id = receipt.work_ids[item.key];
38
+ if (!id)
39
+ refuse("missing_work");
40
+ const custody = readWorkBatchCustody(context, id);
41
+ if (custody.item_key !== item.key || custody.batch_sha256 !== receipt.batch_sha256
42
+ || hybridDigest(custody.work_ids) !== hybridDigest(receipt.work_ids)
43
+ || hybridDigest(custody.scope) !== hybridDigest({ ...graph, stage: scope.stage,
44
+ parent_work_id: receipt.parent_work_id, request_key: "diagnostic-graph" }))
45
+ refuse("changed_work_custody");
46
+ }
47
+ return receipt;
48
+ }
49
+ /** The registered handler calls this only after generic Stage entry. Applicability
50
+ * and candidates come from the accepted predecessor, not a caller-owned batch. */
51
+ export function registerDiagnosticStageGraph(context, input) {
52
+ const bundle = readFlowRunExecutionBundle(context, input), project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
53
+ if (!bundle || !isDiagnosticFlow(bundle.manifest.flow_id) || !["audit", "analyze", "review"].includes(input.stage))
54
+ refuse("diagnostic_stage_required");
55
+ const flowId = bundle.manifest.flow_id, frozen = bundle.execution.snapshot().scope;
56
+ const scope = { projectId: project.id, runId: input.runId, stage: input.stage };
57
+ const selection = readDescriptorStageInput(context, scope, bundle), root = readDescriptorRootWork(context, scope, bundle);
58
+ const snapshotSelection = { projectId: project.id, projectRoot: frozen.projectRoot, sourceRoot: frozen.projectRoot, requestId: frozen.requestId, flowId };
59
+ const snapshot = readDiagnosticSnapshot(context, snapshotSelection, input.runId);
60
+ if (!snapshot || snapshot.root !== frozen.workspace)
61
+ refuse("snapshot_required");
62
+ const previousStage = input.stage === "review" ? flowId === "mb-audit" ? "audit" : "analyze" : "preflight";
63
+ if (selection.input.predecessor?.stage !== previousStage)
64
+ refuse("predecessor_required");
65
+ const previousScope = { ...scope, stage: previousStage };
66
+ const accepted = readAcceptedStageData(context, previousScope), previousInput = readDescriptorStageInput(context, previousScope, bundle, true, true);
67
+ const previous = validateDiagnosticStageResult(accepted.bytes, bundle.descriptor.stageBlob(previousStage, "result_schema"), {
68
+ flowId, runId: input.runId, stage: previousStage, inputSha256: previousInput.inputSha256,
69
+ snapshotRoot: diagnosticSnapshotPaths(context, snapshot, input.runId).root, snapshotSha256: snapshot.source_sha256,
70
+ predecessors: previousInput.input.predecessor ? [{ stage: previousInput.input.predecessor.stage,
71
+ path: previousInput.input.predecessor.result.path, sha256: previousInput.input.predecessor.result.sha256 }] : []
72
+ });
73
+ const workInput = { flow_id: flowId, run_id: input.runId, stage: input.stage,
74
+ input_sha256: selection.inputSha256, snapshot_sha256: snapshot.source_sha256,
75
+ instruction: bundle.descriptor.stageBlob(input.stage, "instruction").toString("utf8"),
76
+ coverage: previous.details.coverage, candidates: input.stage === "review" ? previous.details.findings : [],
77
+ support: bundle.manifest.inventory.filter(ref => ref.path.startsWith("support/")).map(ref => ({ path: `execution-bundle/${ref.path}`, sha256: ref.sha256 })) };
78
+ const batch = diagnosticWorkBatch(workInput), batchInput = { parentWorkId: root.workId, stage: input.stage, requestKey: "diagnostic-graph", value: batch };
79
+ const prepared = batch.works.length ? prepareWorkBatch(context, batchInput) : [];
80
+ return context.db.writeTransaction(() => {
81
+ assertPreparedRunBundleSqlAuthority(context, scope, bundle);
82
+ if (hybridDigest(readDiagnosticSnapshotCustody(context, snapshotSelection, input.runId)) !== hybridDigest(snapshot)
83
+ || hybridDigest(readDescriptorStageInput(context, scope, bundle)) !== hybridDigest(selection)
84
+ || hybridDigest(readAcceptedStageData(context, previousScope)) !== hybridDigest(accepted)
85
+ || readDescriptorRootWork(context, scope, bundle).workId !== root.workId)
86
+ refuse("stage_authority_changed");
87
+ const expected = { schema_id: "dd-flow/diagnostic-work-registration@1", run_id: input.runId, stage: input.stage,
88
+ attempt: selection.attempt, parent_work_id: root.workId, bundle_sha256: bundle.sha256,
89
+ input_sha256: selection.inputSha256, input: workInput, batch_sha256: hybridDigest(batch.works) };
90
+ const prior = readRegisteredDiagnosticGraph(context, scope);
91
+ if (prior) {
92
+ if (hybridDigest({ ...prior, work_ids: undefined }) !== hybridDigest(expected))
93
+ refuse("registration_changed");
94
+ return prior;
95
+ }
96
+ const workIds = batch.works.length ? addWorkBatch(context, { ...batchInput, prepared }).work_ids : {};
97
+ const receipt = { ...expected, work_ids: workIds };
98
+ appendAudit(context, { projectId: project.id, eventType: event, payload: receipt });
99
+ return readRegisteredDiagnosticGraph(context, scope);
100
+ });
101
+ }
@@ -0,0 +1,93 @@
1
+ import { Ajv2020 } from "ajv/dist/2020.js";
2
+ import { verifyDiagnosticSources, verifyDiagnosticCritic } from "./diagnostic-evidence.js";
3
+ import { AppError } from "../shared/errors.js";
4
+ import { hybridDigest } from "./hybrid-contracts.js";
5
+ const refuse = (reason) => {
6
+ throw new AppError("diagnostic_work_result_invalid", "Diagnostic Work result does not match its assigned task", 2, { reason, effect: "no_effect", ...(reason === "invalid_schema" ? { phase: "prepare", recoverable: true } : {}) });
7
+ };
8
+ /** Accepts semantic data only after the caller has established completed Work,
9
+ * immutable batch custody and physical Session independence. */
10
+ export function validateDiagnosticWorkResult(bytes, schemaBytes, snapshotRoot, input, assignment) {
11
+ if (!bytes.length || bytes.length > 1024 * 1024 || schemaBytes.length > 1024 * 1024)
12
+ refuse("result_exceeds_bound");
13
+ const validate = (() => {
14
+ try {
15
+ const schema = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(schemaBytes));
16
+ if (schema.$id !== "dd-flow/diagnostic-work-result@1")
17
+ refuse("frozen_schema_invalid");
18
+ return new Ajv2020({ strict: false, validateFormats: false }).compile(schema);
19
+ }
20
+ catch {
21
+ return refuse("frozen_schema_invalid");
22
+ }
23
+ })();
24
+ let result;
25
+ try {
26
+ result = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
27
+ }
28
+ catch {
29
+ return refuse("invalid_schema");
30
+ }
31
+ if (!validate(result))
32
+ refuse("invalid_schema");
33
+ if (result.flow_id !== input.flow_id || result.run_id !== input.run_id || result.stage !== input.stage
34
+ || result.work_id !== assignment.workId || result.aspect_id !== assignment.aspectId)
35
+ refuse("foreign_assignment");
36
+ verifyDiagnosticSources(snapshotRoot, result.sources);
37
+ if (assignment.role === "critic") {
38
+ if (input.stage !== "review" || result.findings.length)
39
+ refuse("critic_cannot_replace_candidates");
40
+ verifyDiagnosticCritic(snapshotRoot, input.candidates, result.decisions);
41
+ }
42
+ else {
43
+ if (input.stage === "review" || result.decisions.length
44
+ || !input.coverage.some(value => value.id === assignment.aspectId && value.applicability === "applicable"))
45
+ refuse("foreign_aspect");
46
+ const ids = new Set();
47
+ for (const finding of result.findings) {
48
+ if (ids.has(finding.id) || finding.aspect_id !== assignment.aspectId || !finding.evidence.length)
49
+ refuse("foreign_or_duplicate_finding");
50
+ ids.add(finding.id);
51
+ verifyDiagnosticSources(snapshotRoot, finding.evidence);
52
+ }
53
+ }
54
+ return result;
55
+ }
56
+ /** Work-local IDs are not globally unique. Keep their origin when assigning
57
+ * stable report IDs, so one author's FIND-1 cannot overwrite another's. */
58
+ export function collectDiagnosticCandidates(results) {
59
+ const findings = [], origins = [];
60
+ const seen = new Set();
61
+ for (const [workIndex, item] of results.entries()) {
62
+ if (seen.has(item.workId) || item.result.work_id !== item.workId || item.result.stage === "review")
63
+ refuse("duplicate_or_foreign_work");
64
+ seen.add(item.workId);
65
+ for (const [findingIndex, finding] of item.result.findings.entries()) {
66
+ const id = `FIND-${String(workIndex + 1).padStart(3, "0")}-${String(findingIndex + 1).padStart(3, "0")}`;
67
+ findings.push({ ...structuredClone(finding), id });
68
+ origins.push({ id, work_id: item.workId, original_id: finding.id });
69
+ }
70
+ }
71
+ return { findings, origins };
72
+ }
73
+ /** Assign CLI-owned global IDs only to an exact, ordered copy of admitted Work
74
+ * findings. Any other proposal still goes through the ordinary strict guards. */
75
+ export function sealDiagnosticCandidateIds(bytes, results) {
76
+ if (!bytes.length || bytes.length > 1024 * 1024)
77
+ return bytes;
78
+ let value;
79
+ try {
80
+ value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
81
+ }
82
+ catch {
83
+ return bytes;
84
+ }
85
+ if (!value || !Array.isArray(value.details?.findings)
86
+ || hybridDigest(value.details.findings) !== hybridDigest(results.flatMap(item => item.result.findings)))
87
+ return bytes;
88
+ const canonical = collectDiagnosticCandidates(results).findings;
89
+ if (hybridDigest(value.details.findings) === hybridDigest(canonical))
90
+ return bytes;
91
+ value.details.findings = canonical;
92
+ return Buffer.from(JSON.stringify(value));
93
+ }
@@ -9,7 +9,8 @@ import { createRequire } from "node:module";
9
9
  import { valid } from "semver";
10
10
  import { getCliBuildInfo } from "./build-info.js";
11
11
  import { AppError } from "../shared/errors.js";
12
- import { ensureDir, engineStoreRoot, engineVersionRoot, resolveProjectRoot } from "../storage/paths.js";
12
+ import { canonicalPath, ensureDir, engineStoreRoot, engineVersionRoot, resolveProjectRoot } from "../storage/paths.js";
13
+ import { resolveWorkReference } from "../storage/work-references.js";
13
14
  import { classifyCliOperation, isRouterNative } from "./cli-operation-classifier.js";
14
15
  import { requireProtocol } from "./protocols.js";
15
16
  import { resolveCanonRootBeforeContext } from "./canon.js";
@@ -346,9 +347,21 @@ export async function upgradeForkedRunEngine(context, input) {
346
347
  fs.renameSync(temporary, located.binding_path);
347
348
  return binding;
348
349
  }
349
- function boundRunEngineForArgs(context, args, projectRoot) {
350
- const runId = runIdForArgs(args);
351
- if (!runId || !projectRoot)
350
+ function boundRunEngineForArgs(context, args, projectRoot, options = {}) {
351
+ if (!projectRoot)
352
+ return null;
353
+ let runId = runIdForArgs(args);
354
+ const parsed = parseLightArgs(args), [family, command] = parsed.positional;
355
+ if (family === "work" && command !== "repair") {
356
+ const reference = option(parsed, "parent") ?? positional(parsed, command === "deps" ? 3 : 2);
357
+ if (reference) {
358
+ const project = context.db.get("SELECT id FROM projects WHERE root = ?", [canonicalPath(projectRoot, false)]);
359
+ if (!project)
360
+ return null;
361
+ runId = resolveWorkReference(context, reference, { projectId: project.id, runId }).run_id;
362
+ }
363
+ }
364
+ if (!runId)
352
365
  return null;
353
366
  const located = locateRunRoot(context.db, projectRoot, runId);
354
367
  if (!located)
@@ -356,24 +369,58 @@ function boundRunEngineForArgs(context, args, projectRoot) {
356
369
  const binding = readRunEngineBinding(located.binding_path, { allowMissing: true });
357
370
  if (!binding)
358
371
  return null;
372
+ // SQL locates the current project; a restored binding retains its historical
373
+ // project_root bytes. Do not reinterpret that provenance as a live locator.
374
+ if (binding.run_id !== located.run_id)
375
+ throw new AppError("run_engine_binding_invalid", "RUN engine binding belongs to another scope", 1, { run_id: located.run_id });
359
376
  const manifest = requireBoundManifest(context.ddFlowHome, {
360
377
  packageName: binding.engine.package_name,
361
378
  packageVersion: binding.engine.package_version,
362
379
  integrityChecksum: binding.engine.integrity_checksum,
363
380
  snapshotRoot: binding.engine.snapshot_root
364
- });
381
+ }, options);
365
382
  return { binding, manifest };
366
383
  }
384
+ /** Detached RUN owners may be launched by a scope spanning several engines.
385
+ * Resolve the same retained pin as the public router before spawning directly. */
386
+ export function boundRunEngineEntrypoint(context, input) {
387
+ const bound = boundRunEngineForArgs(context, ["run", "status", input.runId], input.projectRoot);
388
+ if (!bound)
389
+ throw new AppError("run_engine_binding_missing", "Detached RUN ownership requires its retained engine", 1, { run_id: input.runId });
390
+ return resolveManifestEntrypoint(bound.manifest);
391
+ }
392
+ /** Routing already admitted the complete snapshot. Inside that executor the
393
+ * RUN pin, not a mutable project's old/new compatibility range, owns writes.
394
+ * Compare the actual physical module identity; environment aliases cannot
395
+ * impersonate another installed engine. No snapshot scan on each lease ACK.
396
+ */
397
+ export function selectBoundExecutingEngine(context, args, projectRoot, env) {
398
+ if (!isEngineMode(env))
399
+ return null;
400
+ const bound = boundRunEngineForArgs(context, args, projectRoot, { verifyContent: false });
401
+ if (!bound)
402
+ return null;
403
+ const current = currentBuildManifest(context, getCliBuildInfo(), false);
404
+ if (!sameEngineIdentity(engineIdentity(current), bound.binding.engine))
405
+ throw new AppError("run_engine_binding_invalid", "Executing engine differs from the retained RUN pin", 1, { run_id: bound.binding.run_id });
406
+ return { status: "selected", package_name: current.package_name, required_range: `=${current.package_version}`,
407
+ recommended_version: current.package_version, selected: current, install_hint: null, project_root: projectRoot,
408
+ memory_bank_version: readCompatibilityForProject(projectRoot)?.memory_bank_version ?? null, diagnostics: [] };
409
+ }
367
410
  function runIdForArgs(args) {
368
- const [family, command, ...rest] = args;
369
- const parsed = parseLightArgs(rest);
411
+ const input = parseLightArgs(args), [family, command, ...rest] = input.positional;
412
+ const parsed = { ...input, positional: rest };
370
413
  if (family === "run" && command !== "start" && command !== "list") {
371
- if (["drive", "control", "recovery", "capacity"].includes(command ?? ""))
414
+ if (["drive", "control"].includes(command ?? ""))
372
415
  return option(parsed, "run");
373
- if (command === "flags")
416
+ if (["flags", "vars", "config", "recovery", "capacity"].includes(command ?? ""))
374
417
  return positional(parsed, 1);
418
+ if (command === "snapshot")
419
+ return positional(parsed, 0) === "create" ? positional(parsed, 1) : null;
375
420
  return positional(parsed, 0);
376
421
  }
422
+ if (family === "stage" && ["fanout", "native"].includes(command ?? ""))
423
+ return positional(parsed, 1);
377
424
  if (family === "stage" && command !== "start")
378
425
  return positional(parsed, 0);
379
426
  if (family === "stage" && command === "start" && !parsed.options.has("bootstrap"))
@@ -384,7 +431,7 @@ function runIdForArgs(args) {
384
431
  return option(parsed, "run");
385
432
  if (family === "protocol" && command === "sync-from-run")
386
433
  return option(parsed, "run");
387
- return null;
434
+ return option(parsed, "run");
388
435
  }
389
436
  function requireBoundManifest(ddFlowHome, input, options = {}) {
390
437
  const manifest = input.snapshotRoot
@@ -1049,7 +1096,6 @@ function spawnEngine(manifest, entrypoint, args, io, stdin, env, routedFromVersi
1049
1096
  return new Promise((resolve, reject) => {
1050
1097
  const child = spawn(process.execPath, [entrypoint, ...args], {
1051
1098
  env: {
1052
- ...process.env,
1053
1099
  ...env,
1054
1100
  DD_FLOW_ENGINE_MODE: "1",
1055
1101
  DD_FLOW_ENGINE_HOME: manifest.snapshot_root,
@@ -1143,8 +1189,12 @@ function findPackageRoot(start) {
1143
1189
  current = parent;
1144
1190
  }
1145
1191
  }
1192
+ let checksumScanOrdinal = 0;
1146
1193
  export function engineSnapshotChecksum(root) {
1147
- publishSetup('probe_started', `engine-digest:${root}`);
1194
+ // A CLI can legitimately revalidate the same snapshot at several boundaries.
1195
+ // Distinguish actual scans without making duplicate phase notifications progress.
1196
+ const scan = `engine-digest:${root}:scan-${++checksumScanOrdinal}`;
1197
+ publishSetup('probe_started', scan);
1148
1198
  const files = [];
1149
1199
  collectFiles(root, root, files);
1150
1200
  const checksum = crypto.createHash("sha256");
@@ -1154,11 +1204,11 @@ export function engineSnapshotChecksum(root) {
1154
1204
  checksum.update("\0");
1155
1205
  checksum.update(fs.readFileSync(path.join(root, file)));
1156
1206
  if (++checked % 64 === 0)
1157
- publishSetup('identity_digest_progress', `${root}:${checked}`);
1207
+ publishSetup('identity_digest_progress', `${scan}:${checked}`);
1158
1208
  checksum.update("\0");
1159
1209
  }
1160
1210
  const result = checksum.digest("hex");
1161
- publishSetup('identity_digest_completed', root);
1211
+ publishSetup('identity_digest_completed', scan);
1162
1212
  return result;
1163
1213
  }
1164
1214
  function collectFiles(root, current, files) {