@deksden-com/dd-flow-cli 0.9.0-beta.152 → 0.9.0-beta.156
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.
- package/CHANGELOG.md +31 -0
- package/dist/build-info.json +8 -8
- package/dist/cli/command-inputs.js +33 -3
- package/dist/cli/command-metadata.js +73 -2
- package/dist/cli/delivery.js +108 -0
- package/dist/cli/diagnostic-invocation.js +23 -7
- package/dist/cli/diagnostic.js +14 -3
- package/dist/cli/help.js +117 -1
- package/dist/cli/hook-ingress.js +36 -49
- package/dist/cli/interactive-invocation.js +33 -0
- package/dist/cli/interactive.js +114 -0
- package/dist/cli/memory-fix-invocation.js +52 -0
- package/dist/cli/memory-fix.js +100 -0
- package/dist/cli/memory-flow-admission.js +7 -3
- package/dist/cli/memory-init.js +5 -4
- package/dist/cli/native-hook-ingress.js +5 -1
- package/dist/cli/native-hook-preparation.js +31 -4
- package/dist/cli/run-cli.js +274 -19
- package/dist/cli.js +17 -3
- package/dist/domain/delivery-flows.js +8 -0
- package/dist/domain/distill-flow.js +20 -0
- package/dist/domain/interactive-flow.js +1 -0
- package/dist/domain/memory-fix-flow.js +2 -0
- package/dist/domain/review-fix-flow.js +2 -0
- package/dist/domain/source-snapshot-flows.js +4 -0
- package/dist/harness-runtime/bin/dd-agy.mjs +18 -3
- package/dist/harness-runtime/bin/dd-grok.mjs +15 -5
- package/dist/harness-runtime/bin/dd-zcode.mjs +15 -7
- package/dist/harness-runtime/lib/agy-error-boundary.mjs +18 -10
- package/dist/harness-runtime/lib/agy-native-inspection.mjs +12 -3
- package/dist/harness-runtime/lib/agy-native-inventory.mjs +19 -0
- package/dist/harness-runtime/lib/agy-terminal-envelope.mjs +13 -6
- package/dist/harness-runtime/lib/agy-transcript-stream.mjs +54 -0
- package/dist/harness-runtime/lib/daemon-observation.d.mts +1 -0
- package/dist/harness-runtime/lib/daemon-observation.mjs +12 -4
- package/dist/harness-runtime/lib/daemon-operations.mjs +20 -5
- package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +371 -114
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +43 -3
- package/dist/harness-runtime/lib/dd-codex.mjs +36 -4
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +25 -11
- package/dist/harness-runtime/lib/dd-droid.mjs +124 -11
- package/dist/harness-runtime/lib/dd-grok-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +137 -25
- package/dist/harness-runtime/lib/dd-grok.mjs +4 -2
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +115 -17
- package/dist/harness-runtime/lib/dd-opencode.mjs +10 -7
- package/dist/harness-runtime/lib/dd-zcode-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +115 -22
- package/dist/harness-runtime/lib/dd-zcode.mjs +75 -36
- package/dist/harness-runtime/lib/delegation-instructions.d.mts +2 -1
- package/dist/harness-runtime/lib/delegation-instructions.mjs +15 -5
- package/dist/harness-runtime/lib/delivery-command.mjs +84 -0
- package/dist/harness-runtime/lib/driver-recovery.mjs +0 -19
- package/dist/harness-runtime/lib/droid-observation.mjs +9 -5
- package/dist/harness-runtime/lib/lifecycle-contract.d.mts +1 -0
- package/dist/harness-runtime/lib/lifecycle-contract.mjs +2 -1
- package/dist/harness-runtime/lib/native-hook-command.d.mts +3 -2
- package/dist/harness-runtime/lib/native-hook-command.mjs +63 -8
- package/dist/harness-runtime/lib/native-streams.mjs +3 -10
- package/dist/harness-runtime/lib/opencode-plugin.mjs +36 -9
- package/dist/harness-runtime/lib/operation-errors.mjs +13 -0
- package/dist/harness-runtime/lib/operation-progress.mjs +6 -2
- package/dist/harness-runtime/lib/process-json.mjs +4 -5
- package/dist/harness-runtime/lib/tool-observations.mjs +20 -10
- package/dist/harness-runtime/lib/zcode-native-hook-ownership.mjs +109 -0
- package/dist/schemas/code-work-result.schema.json +2 -1
- package/dist/schemas/delivery-stage-result.schema.json +286 -0
- package/dist/schemas/distill-stage-result.schema.json +667 -0
- package/dist/schemas/distill-work-result.schema.json +428 -0
- package/dist/schemas/execution-bundle.schema.json +6 -2
- package/dist/schemas/interactive-stage-result.schema.json +47 -0
- package/dist/schemas/interactive-work-result.schema.json +63 -0
- package/dist/schemas/mb-fix-stage-result.schema.json +93 -0
- package/dist/schemas/project-flow-pack-manifest.schema.json +2 -2
- package/dist/schemas/review-fix-stage-result.schema.json +210 -0
- package/dist/schemas/run-control-receipt.schema.json +1 -0
- package/dist/services/assigned-lifecycle.js +382 -0
- package/dist/services/check-runner-evidence.js +45 -8
- package/dist/services/cli-operation-classifier.js +6 -1
- package/dist/services/code-checks.js +46 -29
- package/dist/services/codex-hook-delivery.js +11 -2
- package/dist/services/controller-child-repair.js +95 -0
- package/dist/services/controller-execution.js +4 -1
- package/dist/services/controller-fanout.js +19 -9
- package/dist/services/controller-memory-worker.js +6 -4
- package/dist/services/controller-result-authority.js +122 -0
- package/dist/services/dashboard.js +4 -2
- package/dist/services/delivery-access.js +123 -0
- package/dist/services/delivery-approval.js +68 -0
- package/dist/services/delivery-artifact.js +86 -0
- package/dist/services/delivery-checks.js +178 -0
- package/dist/services/delivery-execution.js +181 -0
- package/dist/services/delivery-intents.js +212 -0
- package/dist/services/delivery-operation.js +81 -0
- package/dist/services/delivery-policy.js +91 -0
- package/dist/services/delivery-procedures.js +43 -0
- package/dist/services/delivery-request.js +99 -0
- package/dist/services/delivery-result.js +144 -0
- package/dist/services/delivery-stage-commands.js +26 -0
- package/dist/services/delivery-stage-entry.js +56 -0
- package/dist/services/delivery-stage-handlers.js +135 -0
- package/dist/services/delivery-stage-results.js +94 -0
- package/dist/services/descriptor-coordinator-session.js +3 -1
- package/dist/services/descriptor-run-start.js +24 -4
- package/dist/services/descriptor-stage-coordinator.js +78 -10
- package/dist/services/descriptor-stage-settlement.js +59 -14
- package/dist/services/diagnostic-repair-intake.js +123 -0
- package/dist/services/diagnostic-repair-source-intake.js +102 -0
- package/dist/services/diagnostic-report.js +30 -0
- package/dist/services/diagnostic-run-bundle.js +48 -9
- package/dist/services/diagnostic-snapshot.js +2 -2
- package/dist/services/diagnostic-stage-handlers.js +3 -2
- package/dist/services/diagnostic-stage-results.js +26 -0
- package/dist/services/diagnostic-start.js +56 -6
- package/dist/services/diagnostic-work-collection.js +3 -3
- package/dist/services/distill-document-publication.js +280 -0
- package/dist/services/distill-promotion.js +197 -0
- package/dist/services/distill-publication-custody.js +162 -0
- package/dist/services/distill-stage-handlers.js +249 -0
- package/dist/services/distill-stage-results.js +105 -0
- package/dist/services/distill-user-inputs.js +4 -0
- package/dist/services/distill-work-batch.js +42 -0
- package/dist/services/distill-work-collection.js +74 -0
- package/dist/services/distill-work-registration.js +93 -0
- package/dist/services/distill-work-results.js +98 -0
- package/dist/services/eval-snapshots.js +17 -7
- package/dist/services/external-work-launch.js +8 -2
- package/dist/services/flow-catalog.js +24 -0
- package/dist/services/frozen-engine.js +19 -2
- package/dist/services/generic-execution.js +4 -3
- package/dist/services/harness-adapter.js +2 -14
- package/dist/services/hook-memory-routing.js +1 -1
- package/dist/services/hooks.js +31 -6
- package/dist/services/hybrid-contracts.js +6 -1
- package/dist/services/installed-flow-source.js +37 -2
- package/dist/services/interactive-attached-session.js +215 -0
- package/dist/services/interactive-baseline-custody.js +54 -0
- package/dist/services/interactive-before-versions.js +148 -0
- package/dist/services/interactive-bundle-data.js +85 -0
- package/dist/services/interactive-checks.js +179 -0
- package/dist/services/interactive-execution.js +33 -0
- package/dist/services/interactive-finish-integration.js +236 -0
- package/dist/services/interactive-git-baseline.js +230 -0
- package/dist/services/interactive-git-dispatch.js +278 -0
- package/dist/services/interactive-input-delivery.js +82 -0
- package/dist/services/interactive-mutation-scope.js +231 -0
- package/dist/services/interactive-request.js +287 -0
- package/dist/services/interactive-run-bundle.js +109 -0
- package/dist/services/interactive-stage-entry.js +41 -0
- package/dist/services/interactive-stage-handlers.js +289 -0
- package/dist/services/interactive-stage-observation.js +136 -0
- package/dist/services/interactive-stage-results.js +111 -0
- package/dist/services/interactive-user-inputs.js +142 -0
- package/dist/services/interactive-work-batch.js +43 -0
- package/dist/services/interactive-work-collection.js +119 -0
- package/dist/services/interactive-work-completion.js +180 -0
- package/dist/services/interactive-work-context.js +249 -0
- package/dist/services/interactive-work-history.js +112 -0
- package/dist/services/interactive-work-registration.js +124 -0
- package/dist/services/interactive-work-results.js +115 -0
- package/dist/services/interactive-work-start.js +155 -0
- package/dist/services/lifecycle-invocations.js +250 -33
- package/dist/services/managed-daemon-binding.js +52 -1
- package/dist/services/memory-compose-handler.js +2 -22
- package/dist/services/memory-finish-integration.js +58 -17
- package/dist/services/memory-fix-finish-integration.js +129 -0
- package/dist/services/memory-fix-git-dispatch.js +139 -0
- package/dist/services/memory-fix-intake-custody.js +11 -0
- package/dist/services/memory-fix-intake-settlement.js +127 -0
- package/dist/services/memory-fix-publication.js +196 -0
- package/dist/services/memory-fix-repair-admission.js +116 -0
- package/dist/services/memory-fix-request.js +141 -0
- package/dist/services/memory-fix-run-bundle.js +83 -0
- package/dist/services/memory-fix-scope.js +78 -0
- package/dist/services/memory-fix-sources.js +85 -0
- package/dist/services/memory-fix-stage-entry.js +24 -0
- package/dist/services/memory-fix-stage-handlers.js +282 -0
- package/dist/services/memory-fix-stage-results.js +106 -0
- package/dist/services/memory-fix-workspace.js +127 -0
- package/dist/services/memory-flow-user-inputs.js +88 -0
- package/dist/services/memory-init-contexts.js +55 -5
- package/dist/services/memory-init-controller-start.js +20 -4
- package/dist/services/memory-layer.js +7 -3
- package/dist/services/memory-native-review-copy.js +27 -3
- package/dist/services/memory-repair-document-delta.js +1 -0
- package/dist/services/project-flow-source.js +195 -0
- package/dist/services/project-git-integration.js +92 -0
- package/dist/services/repair-intents.js +2 -7
- package/dist/services/retained-native-hook.js +274 -0
- package/dist/services/review-fix-handoff.js +82 -0
- package/dist/services/review-fix-intake-custody.js +17 -0
- package/dist/services/review-fix-stage-entry.js +43 -0
- package/dist/services/review-fix-stage-handlers.js +176 -0
- package/dist/services/review-fix-stage-results.js +86 -0
- package/dist/services/run-control-worker.js +42 -1
- package/dist/services/run-control.js +40 -5
- package/dist/services/run-controller-adapter.js +37 -3
- package/dist/services/run-controller-process.js +25 -4
- package/dist/services/run-controller-recovery.js +46 -6
- package/dist/services/run-controller-state.js +3 -2
- package/dist/services/run-controller.js +257 -44
- package/dist/services/run-recovery.js +31 -2
- package/dist/services/run-timeline.js +59 -0
- package/dist/services/runs.js +18 -31
- package/dist/services/sessions.js +2 -1
- package/dist/services/stage-handlers.js +55 -3
- package/dist/services/stage-lifecycle.js +4 -3
- package/dist/services/stage-pause.js +26 -9
- package/dist/services/stage-report-renderer.js +2 -6
- package/dist/services/technical-notes.js +31 -0
- package/dist/services/verification-matrix.js +1 -1
- package/dist/services/version-status.js +1 -1
- package/dist/services/vnext-code-review.js +74 -5
- package/dist/services/vnext-code.js +8 -32
- package/dist/services/vnext-fanout.js +53 -8
- package/dist/services/vnext-merge.js +10 -5
- package/dist/services/vnext-plan-review.js +1 -1
- package/dist/services/vnext-plan.js +1 -1
- package/dist/services/vnext-protocolize.js +1 -1
- package/dist/services/vnext-specify.js +10 -5
- package/dist/services/work-registry.js +255 -69
- package/dist/services/worktrees.js +7 -4
- package/dist/storage/database.js +2 -1
- package/dist/verification-components.json +82 -0
- package/package.json +3 -2
- package/tools/verification-jobs.mjs +35 -10
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
3
|
+
import { AppError } from "../shared/errors.js";
|
|
4
|
+
import { distillAspects } from "../domain/distill-flow.js";
|
|
5
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
6
|
+
import { verifyDiagnosticCoverage, verifyDiagnosticSources } from "./diagnostic-evidence.js";
|
|
7
|
+
function refuse(reason) {
|
|
8
|
+
throw new AppError("distill_stage_result_invalid", "Distillation stage result lacks its exact input, reviewed scope or publication proof", 2, { reason, effect: "no_effect", ...(reason === "invalid_schema" ? { phase: "prepare", recoverable: true } : {}) });
|
|
9
|
+
}
|
|
10
|
+
const portable = (value) => Boolean(value) && !path.posix.isAbsolute(value) && !path.win32.isAbsolute(value)
|
|
11
|
+
&& !value.includes("\\") && !value.includes("\0") && value.split("/").every(part => Boolean(part) && ![".", "..", ".git", "node_modules"].includes(part));
|
|
12
|
+
/** Shape/evidence checks do not establish independent Work or publication
|
|
13
|
+
* custody. The registered handler must prepare those bindings and fence them
|
|
14
|
+
* again in its settlement transaction. */
|
|
15
|
+
export function validateDistillStageResult(bytes, schemaBytes, binding) {
|
|
16
|
+
if (!bytes.length || bytes.length > 1024 * 1024 || !schemaBytes.length || schemaBytes.length > 1024 * 1024)
|
|
17
|
+
refuse("result_exceeds_bound");
|
|
18
|
+
const validate = (() => {
|
|
19
|
+
try {
|
|
20
|
+
const schema = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(schemaBytes));
|
|
21
|
+
if (schema.$id !== "dd-flow/distill-stage-result@1")
|
|
22
|
+
refuse("frozen_schema_invalid");
|
|
23
|
+
return new Ajv2020({ strict: false, validateFormats: false }).compile(schema);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return refuse("frozen_schema_invalid");
|
|
27
|
+
}
|
|
28
|
+
})();
|
|
29
|
+
let result;
|
|
30
|
+
try {
|
|
31
|
+
result = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return refuse("invalid_schema");
|
|
35
|
+
}
|
|
36
|
+
if (!validate(result))
|
|
37
|
+
refuse("invalid_schema");
|
|
38
|
+
if (result.flow_id !== "mb-distill" || result.run_id !== binding.runId || result.stage !== binding.stage
|
|
39
|
+
|| result.input_sha256 !== binding.inputSha256 || result.snapshot_sha256 !== binding.snapshotSha256
|
|
40
|
+
|| hybridDigest(result.predecessors) !== hybridDigest(binding.predecessors))
|
|
41
|
+
refuse("foreign_stage_binding");
|
|
42
|
+
const details = result.details;
|
|
43
|
+
verifyDiagnosticCoverage(binding.snapshotRoot, distillAspects.map(aspect => aspect.id), details.coverage);
|
|
44
|
+
const ids = new Set();
|
|
45
|
+
const baseline = (refs) => {
|
|
46
|
+
if (refs.some(ref => !binding.support.some(source => source.path === ref.path && source.sha256 === ref.sha256)))
|
|
47
|
+
refuse("foreign_baseline");
|
|
48
|
+
};
|
|
49
|
+
for (const candidate of details.candidates) {
|
|
50
|
+
if (ids.has(candidate.id) || !details.coverage.some(row => row.id === candidate.aspect_id && row.applicability === "applicable"))
|
|
51
|
+
refuse("candidate_outside_selection");
|
|
52
|
+
ids.add(candidate.id);
|
|
53
|
+
baseline(candidate.baseline_refs);
|
|
54
|
+
verifyDiagnosticSources(binding.snapshotRoot, [...candidate.source_refs, ...candidate.usage_refs]);
|
|
55
|
+
}
|
|
56
|
+
const decided = new Set();
|
|
57
|
+
for (const decision of details.decisions) {
|
|
58
|
+
if (!ids.has(decision.candidate_id) || decided.has(decision.candidate_id))
|
|
59
|
+
refuse("foreign_or_duplicate_decision");
|
|
60
|
+
decided.add(decision.candidate_id);
|
|
61
|
+
baseline(decision.baseline_refs);
|
|
62
|
+
verifyDiagnosticSources(binding.snapshotRoot, decision.source_refs);
|
|
63
|
+
}
|
|
64
|
+
if (!["preflight", "extract"].includes(binding.stage) && decided.size !== ids.size)
|
|
65
|
+
refuse("unresolved_candidate");
|
|
66
|
+
if (hybridDigest(details.write_scope) !== hybridDigest(binding.writeScope)
|
|
67
|
+
|| hybridDigest(details.promotion_receipt) !== hybridDigest(binding.promotionReceipt))
|
|
68
|
+
refuse("unowned_publication_claim");
|
|
69
|
+
const scope = binding.writeScope;
|
|
70
|
+
if (!["verify", "finish"].includes(binding.stage) && details.verification.length)
|
|
71
|
+
refuse("verification_before_publication_review");
|
|
72
|
+
if (scope) {
|
|
73
|
+
if (!["promote", "verify", "finish"].includes(binding.stage) || !path.isAbsolute(scope.destination_root)
|
|
74
|
+
|| path.resolve(scope.destination_root) !== scope.destination_root || scope.paths.some(value => !portable(value))
|
|
75
|
+
|| scope.candidate_ids.some(id => !details.decisions.some(decision => decision.candidate_id === id && ["adopt", "adapt"].includes(decision.disposition))))
|
|
76
|
+
refuse("invalid_write_scope");
|
|
77
|
+
verifyDiagnosticSources(binding.runRoot, [scope.approval_source]);
|
|
78
|
+
}
|
|
79
|
+
if (binding.promotionReceipt)
|
|
80
|
+
verifyDiagnosticSources(binding.runRoot, [binding.promotionReceipt]);
|
|
81
|
+
if (binding.stage === "promote" && details.decisions.some(decision => ["adopt", "adapt"].includes(decision.disposition))
|
|
82
|
+
&& (!scope || !binding.promotionReceipt))
|
|
83
|
+
refuse("promotion_requires_accepted_scope_and_receipt");
|
|
84
|
+
const verified = new Set();
|
|
85
|
+
for (const observation of details.verification) {
|
|
86
|
+
if (!scope?.candidate_ids.includes(observation.candidate_id) || verified.has(observation.candidate_id))
|
|
87
|
+
refuse("foreign_verification");
|
|
88
|
+
verified.add(observation.candidate_id);
|
|
89
|
+
for (const evidence of observation.evidence)
|
|
90
|
+
verifyDiagnosticSources(evidence.root === "run" ? binding.runRoot : scope.destination_root, [evidence]);
|
|
91
|
+
}
|
|
92
|
+
if (["verify", "finish"].includes(binding.stage) && details.decisions.some(decision => ["adopt", "adapt"].includes(decision.disposition))
|
|
93
|
+
&& (!scope || !binding.promotionReceipt || scope.candidate_ids.some(id => !verified.has(id))
|
|
94
|
+
|| details.verification.some(observation => observation.verdict !== "verified")))
|
|
95
|
+
refuse("unverified_promotion");
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
export function assertDistillStageScope(result, prior) {
|
|
99
|
+
if (hybridDigest(result.details.coverage) !== hybridDigest(prior.details.coverage)
|
|
100
|
+
|| prior.details.limitations.some(value => !result.details.limitations.includes(value))
|
|
101
|
+
|| (prior.stage !== "preflight" && hybridDigest(result.details.candidates) !== hybridDigest(prior.details.candidates))
|
|
102
|
+
|| (!["preflight", "extract"].includes(prior.stage) && hybridDigest(result.details.decisions) !== hybridDigest(prior.details.decisions))
|
|
103
|
+
|| (prior.details.write_scope && hybridDigest(result.details.write_scope) !== hybridDigest(prior.details.write_scope)))
|
|
104
|
+
refuse("accepted_scope_changed");
|
|
105
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { distillAspects } from "../domain/distill-flow.js";
|
|
2
|
+
import { AppError } from "../shared/errors.js";
|
|
3
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
4
|
+
function refuse(reason) {
|
|
5
|
+
throw new AppError("distill_work_batch_invalid", "Distillation requires its accepted complete aspect selection", 2, { reason, effect: "no_effect" });
|
|
6
|
+
}
|
|
7
|
+
/** One selected aspect is one independent semantic unit. Capacity changes
|
|
8
|
+
* scheduling in the existing Work runtime, never selection or dependencies. */
|
|
9
|
+
export function distillWorkBatch(input) {
|
|
10
|
+
const ids = input.coverage.map(row => row.id);
|
|
11
|
+
if (input.flow_id !== "mb-distill" || !["extract", "review"].includes(input.stage)
|
|
12
|
+
|| ids.length !== distillAspects.length || new Set(ids).size !== ids.length
|
|
13
|
+
|| distillAspects.some(aspect => !ids.includes(aspect.id))
|
|
14
|
+
|| input.coverage.some(row => !row.reason.trim() || !["applicable", "not_applicable", "blocked", "out_of_scope"].includes(row.applicability)))
|
|
15
|
+
refuse("invalid_coverage");
|
|
16
|
+
const selected = distillAspects.filter(aspect => input.coverage.some(row => row.id === aspect.id && row.applicability === "applicable"));
|
|
17
|
+
if (input.stage === "extract" && input.candidates.length)
|
|
18
|
+
refuse("extract_cannot_accept_candidates");
|
|
19
|
+
if (new Set(input.candidates.map(candidate => candidate.id)).size !== input.candidates.length
|
|
20
|
+
|| input.candidates.some(candidate => !selected.some(aspect => aspect.id === candidate.aspect_id)))
|
|
21
|
+
refuse("candidate_outside_selection");
|
|
22
|
+
for (const aspect of selected)
|
|
23
|
+
if (!input.support.some(ref => ref.path === aspect.criterion))
|
|
24
|
+
refuse("missing_selected_criterion");
|
|
25
|
+
const review = input.stage === "review";
|
|
26
|
+
const assignments = review
|
|
27
|
+
? [{ id: "novelty", criterion: null, role: "critic" }, { id: "portability", criterion: null, role: "critic" }]
|
|
28
|
+
: selected.map(aspect => ({ id: aspect.id, criterion: aspect.criterion, role: "aspect" }));
|
|
29
|
+
return { works: assignments.map((assignment, index) => {
|
|
30
|
+
const key = `${assignment.role}-${String(index + 1).padStart(3, "0")}`;
|
|
31
|
+
const packet = { schema_id: "dd-flow/distill-work-input@1", ...structuredClone(input), key,
|
|
32
|
+
aspect_id: assignment.id, role: assignment.role, criterion: assignment.criterion };
|
|
33
|
+
return { key, depends_on: [], launch_policy: "fresh_agent_required",
|
|
34
|
+
result_schema: "dd-flow/distill-work-result@1",
|
|
35
|
+
task: (review
|
|
36
|
+
? `Independently review every candidate through the ${assignment.id} lens. Compare exact project usage and frozen canon baseline. Preserve observe/duplicate/reject decisions and disagreements. Adopt/adapt requires transferable applicability and sufficient evidence, not popularity or missing project compliance. Return one decision per candidate. Do not replace candidates, promote documents, change scope, allocate Work or claim another critic's review.`
|
|
37
|
+
: `Extract transferable practices only for ${assignment.id}. Read its frozen criterion and canon baseline, then actual assigned project sources. Do not audit project compliance. Explain novelty, usage, applicability, cost and complexity; distinguish established practice from plans. Keep domain and process lenses separate. Return an explicit checked/no-candidate explanation when nothing substantial is found. Decide whether external research can change the assessment and disclose use and sources. Do not mutate project/canon, promote documents, allocate Work or self-accept candidates.`)
|
|
38
|
+
+ " Read support references relative to run_home/execution-bundle; preserve their supplied support paths and hashes in baseline_refs. Project source_refs/usage_refs are relative to the assigned snapshot workspace with exact snapshot-file hashes, never canon, intake or RUN files. Old manual lifecycle/dispatch snippets in criteria are reference data, not execution authority.",
|
|
39
|
+
payload: { kind: review ? "distill-critic" : "distill-aspect", read_only: true,
|
|
40
|
+
distill: { ...packet, packet_sha256: hybridDigest(packet) } } };
|
|
41
|
+
}) };
|
|
42
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { AppError } from "../shared/errors.js";
|
|
2
|
+
import { readFlowRunExecutionBundle, assertPreparedRunBundleSqlAuthority } from "./runs.js";
|
|
3
|
+
import { requireProjectByRoot } from "./projects.js";
|
|
4
|
+
import { resolveProjectRoot } from "../storage/paths.js";
|
|
5
|
+
import { readDescriptorRootWork } from "./descriptor-root-work.js";
|
|
6
|
+
import { diagnosticSnapshotPaths, readDiagnosticSnapshot, readDiagnosticSnapshotCustody } from "./diagnostic-snapshot.js";
|
|
7
|
+
import { readRegisteredDistillGraph } from "./distill-work-registration.js";
|
|
8
|
+
import { distillWorkBatch } from "./distill-work-batch.js";
|
|
9
|
+
import { validateDistillWorkResult } from "./distill-work-results.js";
|
|
10
|
+
import { assertDiagnosticSessionIndependence, readCompletedResearchWork } from "./diagnostic-work-collection.js";
|
|
11
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
12
|
+
function refuse(reason) {
|
|
13
|
+
throw new AppError("distill_work_collection_invalid", "Distillation requires the complete independently settled registered graph", 2, { reason, effect: "no_effect" });
|
|
14
|
+
}
|
|
15
|
+
function collectSql(context, scope, rootId) {
|
|
16
|
+
const graph = readRegisteredDistillGraph(context, scope);
|
|
17
|
+
if (!graph || graph.parent_work_id !== rootId)
|
|
18
|
+
refuse("registration_required");
|
|
19
|
+
const batch = distillWorkBatch(graph.input);
|
|
20
|
+
const works = batch.works.map(item => readCompletedResearchWork(context, scope, graph.work_ids[item.key]));
|
|
21
|
+
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]);
|
|
22
|
+
const authorGraph = scope.stage === "review" ? readRegisteredDistillGraph(context, { ...scope, stage: "extract" }) : null;
|
|
23
|
+
if (scope.stage === "review" && !authorGraph)
|
|
24
|
+
refuse("author_graph_missing");
|
|
25
|
+
const authors = authorGraph ? Object.values(authorGraph.work_ids).map(id => readCompletedResearchWork(context, scope, id)) : [];
|
|
26
|
+
assertDiagnosticSessionIndependence(works.map(work => work.session), [...coordinators, ...authors.map(work => work.session)]);
|
|
27
|
+
return { graph, works, authors, coordinators };
|
|
28
|
+
}
|
|
29
|
+
export class PreparedDistillWorkCollection {
|
|
30
|
+
#scope;
|
|
31
|
+
#rootId;
|
|
32
|
+
#sql;
|
|
33
|
+
#snapshot;
|
|
34
|
+
#results;
|
|
35
|
+
constructor(scope, rootId, sql, snapshot, results) {
|
|
36
|
+
this.#scope = scope;
|
|
37
|
+
this.#rootId = rootId;
|
|
38
|
+
this.#sql = sql;
|
|
39
|
+
this.#snapshot = snapshot;
|
|
40
|
+
this.#results = results;
|
|
41
|
+
Object.freeze(this);
|
|
42
|
+
}
|
|
43
|
+
static prepare(context, input) {
|
|
44
|
+
const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot)), bundle = readFlowRunExecutionBundle(context, input);
|
|
45
|
+
if (!bundle || bundle.manifest.flow_id !== "mb-distill")
|
|
46
|
+
refuse("distill_bundle_required");
|
|
47
|
+
const scope = { projectId: project.id, runId: input.runId, stage: input.stage }, frozen = bundle.execution.snapshot().scope;
|
|
48
|
+
const snapshot = readDiagnosticSnapshot(context, { projectId: project.id, projectRoot: frozen.projectRoot,
|
|
49
|
+
sourceRoot: frozen.projectRoot, requestId: frozen.requestId, flowId: "mb-distill" }, input.runId);
|
|
50
|
+
if (!snapshot || snapshot.root !== frozen.workspace)
|
|
51
|
+
refuse("snapshot_required");
|
|
52
|
+
const root = readDescriptorRootWork(context, scope, bundle);
|
|
53
|
+
const sql = context.db.readSnapshot(() => {
|
|
54
|
+
assertPreparedRunBundleSqlAuthority(context, scope, bundle);
|
|
55
|
+
return collectSql(context, scope, root.workId);
|
|
56
|
+
});
|
|
57
|
+
if (sql.graph.bundle_sha256 !== bundle.sha256 || sql.graph.input.snapshot_sha256 !== snapshot.source_sha256)
|
|
58
|
+
refuse("foreign_graph");
|
|
59
|
+
const batch = distillWorkBatch(sql.graph.input), schema = bundle.blob("schemas/distill-work-result.schema.json");
|
|
60
|
+
const results = sql.works.map((work, index) => ({ workId: work.workId,
|
|
61
|
+
result: validateDistillWorkResult(Buffer.from(work.result), schema, diagnosticSnapshotPaths(context, snapshot, input.runId).root, sql.graph.input, {
|
|
62
|
+
workId: work.workId, aspectId: batch.works[index].payload.distill.aspect_id, role: batch.works[index].payload.distill.role
|
|
63
|
+
}) }));
|
|
64
|
+
return new PreparedDistillWorkCollection(scope, root.workId, sql, snapshot, results);
|
|
65
|
+
}
|
|
66
|
+
results() { return structuredClone(this.#results); }
|
|
67
|
+
assertCustody(context) {
|
|
68
|
+
const s = this.#snapshot;
|
|
69
|
+
if (hybridDigest(collectSql(context, this.#scope, this.#rootId)) !== hybridDigest(this.#sql)
|
|
70
|
+
|| hybridDigest(readDiagnosticSnapshotCustody(context, { projectId: s.projectId, projectRoot: s.projectRoot,
|
|
71
|
+
sourceRoot: s.sourceRoot, requestId: s.requestId, flowId: s.flowId }, this.#scope.runId)) !== hybridDigest(s))
|
|
72
|
+
refuse("collection_changed");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { AppError } from "../shared/errors.js";
|
|
2
|
+
import { readFlowRunExecutionBundle, assertPreparedRunBundleSqlAuthority } from "./runs.js";
|
|
3
|
+
import { requireProjectByRoot } from "./projects.js";
|
|
4
|
+
import { resolveProjectRoot } from "../storage/paths.js";
|
|
5
|
+
import { readDescriptorStageInput } from "./descriptor-stage-coordinator.js";
|
|
6
|
+
import { readDescriptorRootWork } from "./descriptor-root-work.js";
|
|
7
|
+
import { readAcceptedStageData } from "./stage-report-renderer.js";
|
|
8
|
+
import { diagnosticSnapshotPaths, readDiagnosticSnapshot, readDiagnosticSnapshotCustody } from "./diagnostic-snapshot.js";
|
|
9
|
+
import { validateDistillStageResult } from "./distill-stage-results.js";
|
|
10
|
+
import { distillWorkBatch } from "./distill-work-batch.js";
|
|
11
|
+
import { prepareWorkBatch, addWorkBatch, readWorkBatchCustody } from "./work-registry.js";
|
|
12
|
+
import { stageGraphScope } from "./stage-work-graph.js";
|
|
13
|
+
import { appendAudit } from "./audit.js";
|
|
14
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
15
|
+
const event = "distill.work_registration";
|
|
16
|
+
function refuse(reason) {
|
|
17
|
+
throw new AppError("distill_work_registration_invalid", "Distillation graph requires its accepted predecessor and frozen stage", 2, { reason, effect: "no_effect" });
|
|
18
|
+
}
|
|
19
|
+
export function readRegisteredDistillGraph(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/distill-work-registration@1" || receipt.run_id !== scope.runId || receipt.stage !== scope.stage
|
|
28
|
+
|| receipt.attempt !== graph.attempt || !receipt.work_ids || receipt.input.run_id !== scope.runId
|
|
29
|
+
|| receipt.input.stage !== scope.stage || receipt.input.input_sha256 !== receipt.input_sha256)
|
|
30
|
+
refuse("foreign_registration");
|
|
31
|
+
const batch = distillWorkBatch(receipt.input);
|
|
32
|
+
if (receipt.batch_sha256 !== hybridDigest(batch.works) || batch.works.length !== Object.keys(receipt.work_ids).length
|
|
33
|
+
|| new Set(Object.values(receipt.work_ids)).size !== batch.works.length)
|
|
34
|
+
refuse("changed_batch");
|
|
35
|
+
for (const item of batch.works) {
|
|
36
|
+
const id = receipt.work_ids[item.key];
|
|
37
|
+
if (!id)
|
|
38
|
+
refuse("missing_work");
|
|
39
|
+
const custody = readWorkBatchCustody(context, id);
|
|
40
|
+
if (custody.item_key !== item.key || custody.batch_sha256 !== receipt.batch_sha256
|
|
41
|
+
|| hybridDigest(custody.work_ids) !== hybridDigest(receipt.work_ids)
|
|
42
|
+
|| hybridDigest(custody.scope) !== hybridDigest({ ...graph, stage: scope.stage, parent_work_id: receipt.parent_work_id, request_key: "distill-graph" }))
|
|
43
|
+
refuse("changed_work_custody");
|
|
44
|
+
}
|
|
45
|
+
return receipt;
|
|
46
|
+
}
|
|
47
|
+
export function registerDistillStageGraph(context, input) {
|
|
48
|
+
const bundle = readFlowRunExecutionBundle(context, input), project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
|
|
49
|
+
if (!bundle || bundle.manifest.flow_id !== "mb-distill" || !["extract", "review"].includes(input.stage))
|
|
50
|
+
refuse("distill_stage_required");
|
|
51
|
+
const frozen = bundle.execution.snapshot().scope, scope = { projectId: project.id, runId: input.runId, stage: input.stage };
|
|
52
|
+
const selection = readDescriptorStageInput(context, scope, bundle), root = readDescriptorRootWork(context, scope, bundle);
|
|
53
|
+
const snapshotScope = { projectId: project.id, projectRoot: frozen.projectRoot, sourceRoot: frozen.projectRoot, requestId: frozen.requestId, flowId: "mb-distill" };
|
|
54
|
+
const snapshot = readDiagnosticSnapshot(context, snapshotScope, input.runId);
|
|
55
|
+
if (!snapshot || snapshot.root !== frozen.workspace)
|
|
56
|
+
refuse("snapshot_required");
|
|
57
|
+
const previousStage = input.stage === "extract" ? "preflight" : "extract";
|
|
58
|
+
if (selection.input.predecessor?.stage !== previousStage)
|
|
59
|
+
refuse("predecessor_required");
|
|
60
|
+
const previousScope = { ...scope, stage: previousStage }, accepted = readAcceptedStageData(context, previousScope);
|
|
61
|
+
const priorInput = readDescriptorStageInput(context, previousScope, bundle, true, true);
|
|
62
|
+
const support = bundle.manifest.inventory.filter(ref => ref.path.startsWith("support/"));
|
|
63
|
+
const previous = validateDistillStageResult(accepted.bytes, bundle.descriptor.stageBlob(previousStage, "result_schema"), {
|
|
64
|
+
runId: input.runId, stage: previousStage, inputSha256: priorInput.inputSha256, snapshotSha256: snapshot.source_sha256,
|
|
65
|
+
snapshotRoot: diagnosticSnapshotPaths(context, snapshot, input.runId).root, runRoot: selection.runRoot, support, writeScope: null, promotionReceipt: null,
|
|
66
|
+
predecessors: priorInput.input.predecessor ? [{ stage: priorInput.input.predecessor.stage, ...priorInput.input.predecessor.result }] : []
|
|
67
|
+
});
|
|
68
|
+
const workInput = { flow_id: "mb-distill", run_id: input.runId, stage: input.stage,
|
|
69
|
+
input_sha256: selection.inputSha256, snapshot_sha256: snapshot.source_sha256, instruction: bundle.descriptor.stageBlob(input.stage, "instruction").toString("utf8"),
|
|
70
|
+
coverage: previous.details.coverage, candidates: input.stage === "review" ? previous.details.candidates : [], support };
|
|
71
|
+
const batch = distillWorkBatch(workInput), batchInput = { parentWorkId: root.workId, stage: input.stage, requestKey: "distill-graph", value: batch };
|
|
72
|
+
const prepared = batch.works.length ? prepareWorkBatch(context, batchInput) : [];
|
|
73
|
+
return context.db.writeTransaction(() => {
|
|
74
|
+
assertPreparedRunBundleSqlAuthority(context, scope, bundle);
|
|
75
|
+
if (hybridDigest(readDiagnosticSnapshotCustody(context, snapshotScope, input.runId)) !== hybridDigest(snapshot)
|
|
76
|
+
|| hybridDigest(readDescriptorStageInput(context, scope, bundle)) !== hybridDigest(selection)
|
|
77
|
+
|| hybridDigest(readAcceptedStageData(context, previousScope)) !== hybridDigest(accepted)
|
|
78
|
+
|| readDescriptorRootWork(context, scope, bundle).workId !== root.workId)
|
|
79
|
+
refuse("stage_authority_changed");
|
|
80
|
+
const expected = { schema_id: "dd-flow/distill-work-registration@1", run_id: input.runId, stage: input.stage, attempt: selection.attempt,
|
|
81
|
+
parent_work_id: root.workId, bundle_sha256: bundle.sha256, input_sha256: selection.inputSha256, input: workInput, batch_sha256: hybridDigest(batch.works) };
|
|
82
|
+
const prior = readRegisteredDistillGraph(context, scope);
|
|
83
|
+
if (prior) {
|
|
84
|
+
if (hybridDigest({ ...prior, work_ids: undefined }) !== hybridDigest(expected))
|
|
85
|
+
refuse("registration_changed");
|
|
86
|
+
return prior;
|
|
87
|
+
}
|
|
88
|
+
const workIds = batch.works.length ? addWorkBatch(context, { ...batchInput, prepared }).work_ids : {};
|
|
89
|
+
const receipt = { ...expected, work_ids: workIds };
|
|
90
|
+
appendAudit(context, { projectId: project.id, eventType: event, payload: receipt });
|
|
91
|
+
return readRegisteredDistillGraph(context, scope);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
2
|
+
import { AppError } from "../shared/errors.js";
|
|
3
|
+
import { verifyDiagnosticSources } from "./diagnostic-evidence.js";
|
|
4
|
+
function refuse(reason) {
|
|
5
|
+
throw new AppError("distill_work_result_invalid", "Distillation result does not match its assigned aspect, evidence and baseline", 2, { reason, effect: "no_effect", ...(reason === "invalid_schema" ? { phase: "prepare", recoverable: true } : {}) });
|
|
6
|
+
}
|
|
7
|
+
/** Semantic judgments remain agent-owned. Mechanically verify the assigned
|
|
8
|
+
* unit, exact project evidence and frozen canon baseline before Work admission. */
|
|
9
|
+
export function validateDistillWorkResult(bytes, schemaBytes, snapshotRoot, input, assignment) {
|
|
10
|
+
if (!bytes.length || bytes.length > 1024 * 1024 || !schemaBytes.length || schemaBytes.length > 1024 * 1024)
|
|
11
|
+
refuse("result_exceeds_bound");
|
|
12
|
+
const validate = (() => {
|
|
13
|
+
try {
|
|
14
|
+
const schema = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(schemaBytes));
|
|
15
|
+
if (schema.$id !== "dd-flow/distill-work-result@1")
|
|
16
|
+
refuse("frozen_schema_invalid");
|
|
17
|
+
return new Ajv2020({ strict: false, validateFormats: false }).compile(schema);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return refuse("frozen_schema_invalid");
|
|
21
|
+
}
|
|
22
|
+
})();
|
|
23
|
+
let result;
|
|
24
|
+
try {
|
|
25
|
+
result = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return refuse("invalid_schema");
|
|
29
|
+
}
|
|
30
|
+
if (!validate(result))
|
|
31
|
+
refuse("invalid_schema");
|
|
32
|
+
if (result.flow_id !== input.flow_id || result.run_id !== input.run_id || result.stage !== input.stage
|
|
33
|
+
|| result.work_id !== assignment.workId || result.aspect_id !== assignment.aspectId)
|
|
34
|
+
refuse("foreign_assignment");
|
|
35
|
+
const baseline = (refs) => {
|
|
36
|
+
if (new Set(refs.map(ref => ref.path)).size !== refs.length
|
|
37
|
+
|| refs.some(ref => !input.support.some(source => source.path === ref.path && source.sha256 === ref.sha256)))
|
|
38
|
+
refuse("foreign_baseline");
|
|
39
|
+
};
|
|
40
|
+
const external = result.external_research;
|
|
41
|
+
if (!external.used && (external.sources.length || external.questions.length || external.signal !== "none" || external.effect !== "neutral"))
|
|
42
|
+
refuse("unperformed_research_claim");
|
|
43
|
+
if (external.signal !== "none" && !external.sources.length)
|
|
44
|
+
refuse("external_signal_without_sources");
|
|
45
|
+
for (const source of external.sources) {
|
|
46
|
+
try {
|
|
47
|
+
const url = new URL(source.url);
|
|
48
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password)
|
|
49
|
+
refuse("invalid_external_source");
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
refuse("invalid_external_source");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (assignment.role === "critic") {
|
|
56
|
+
const decisions = result.decisions.map(decision => decision.candidate_id);
|
|
57
|
+
if (input.stage !== "review" || result.candidates.length || new Set(decisions).size !== decisions.length
|
|
58
|
+
|| decisions.length !== input.candidates.length || input.candidates.some(candidate => !decisions.includes(candidate.id)))
|
|
59
|
+
refuse("incomplete_critic_coverage");
|
|
60
|
+
for (const decision of result.decisions) {
|
|
61
|
+
if (!decision.source_refs.length && !decision.baseline_refs.length)
|
|
62
|
+
refuse("decision_without_evidence");
|
|
63
|
+
verifyDiagnosticSources(snapshotRoot, decision.source_refs);
|
|
64
|
+
baseline(decision.baseline_refs);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
if (input.stage !== "extract" || result.decisions.length
|
|
69
|
+
|| !input.coverage.some(row => row.id === assignment.aspectId && row.applicability === "applicable"))
|
|
70
|
+
refuse("foreign_aspect");
|
|
71
|
+
const ids = new Set();
|
|
72
|
+
for (const candidate of result.candidates) {
|
|
73
|
+
if (candidate.aspect_id !== assignment.aspectId || ids.has(candidate.id))
|
|
74
|
+
refuse("foreign_or_duplicate_candidate");
|
|
75
|
+
ids.add(candidate.id);
|
|
76
|
+
verifyDiagnosticSources(snapshotRoot, candidate.source_refs);
|
|
77
|
+
verifyDiagnosticSources(snapshotRoot, candidate.usage_refs);
|
|
78
|
+
baseline(candidate.baseline_refs);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
/** Collect admitted Work only; keep local IDs and all rejected/observed data. */
|
|
84
|
+
export function collectDistillCandidates(results) {
|
|
85
|
+
const candidates = [], origins = [];
|
|
86
|
+
const owners = new Set();
|
|
87
|
+
for (const [workIndex, work] of results.entries()) {
|
|
88
|
+
if (owners.has(work.workId) || work.workId !== work.result.work_id || work.result.stage !== "extract")
|
|
89
|
+
refuse("foreign_or_duplicate_work");
|
|
90
|
+
owners.add(work.workId);
|
|
91
|
+
for (const [index, candidate] of work.result.candidates.entries()) {
|
|
92
|
+
const id = `DST-${String(workIndex + 1).padStart(3, "0")}-${String(index + 1).padStart(3, "0")}`;
|
|
93
|
+
candidates.push({ ...structuredClone(candidate), id });
|
|
94
|
+
origins.push({ id, work_id: work.workId, original_id: candidate.id });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return { candidates, origins };
|
|
98
|
+
}
|
|
@@ -22,15 +22,25 @@ import { prepareOutputFile } from "../cli/input-preparation.js";
|
|
|
22
22
|
import { localEnvironmentAllowed, projectServicePathFilter } from "./workspace-files.js";
|
|
23
23
|
import { cleanupScopeWriters } from "./scope-writer-cleanup.js";
|
|
24
24
|
import { PreparedInstalledFlowSource } from "./installed-flow-source.js";
|
|
25
|
+
import { PreparedProjectFlowSource } from "./project-flow-source.js";
|
|
25
26
|
import { readSetupStatus } from "./setup.js";
|
|
26
|
-
import {
|
|
27
|
+
import { isSourceSnapshotFlow } from "../domain/source-snapshot-flows.js";
|
|
28
|
+
import { isDeliveryFlow } from "../domain/delivery-flows.js";
|
|
27
29
|
import { isObservationOperation, nativeInventoryVersion, retainedDaemonOperations } from "./run-control.js";
|
|
28
30
|
/** Capture the only flow boundary which exists before `stage start --bootstrap` creates a RUN. */
|
|
29
31
|
export function prepareEvalBootstrapSnapshot(context, input) {
|
|
30
32
|
let source;
|
|
31
|
-
if (input.flow
|
|
32
|
-
|
|
33
|
+
if (input.flow === "interactive") {
|
|
34
|
+
const projectRoot = resolveProjectRoot(input.projectRoot);
|
|
35
|
+
source = PreparedProjectFlowSource.fromProject({ projectRoot, workspace: projectRoot, flowId: "interactive" });
|
|
36
|
+
if (source.resolveRegisteredFlow().descriptor.stages[0]?.id !== "preflight")
|
|
37
|
+
throw new AppError("snapshot_flow_unsupported", "Interactive bootstrap requires its preflight entry", 2, { phase: "prepare", effect: "no_effect" });
|
|
38
|
+
}
|
|
39
|
+
else if (input.flow !== undefined && input.flow !== "sdlc") {
|
|
40
|
+
if (input.flow !== "mb-init" && input.flow !== "mb-upgrade" && !isSourceSnapshotFlow(input.flow) && !isDeliveryFlow(input.flow))
|
|
33
41
|
throw new AppError("snapshot_flow_unsupported", "Bootstrap snapshot flow is not registered", 2, { phase: "prepare", effect: "no_effect" });
|
|
42
|
+
if (input.flow === "review-fix")
|
|
43
|
+
throw new AppError("snapshot_flow_unsupported", "Review-fix starts at intake, not the preflight bootstrap boundary", 2, { phase: "prepare", effect: "no_effect" });
|
|
34
44
|
const home = context.env.DD_FLOW_CONFIG_HOME ?? context.ddFlowHome;
|
|
35
45
|
const selected = readSetupStatus(home);
|
|
36
46
|
if (!selected.installation)
|
|
@@ -223,7 +233,7 @@ export function prepareEvalRunSnapshotCreation(context, input) {
|
|
|
223
233
|
throw new AppError("snapshot_output_inside_project", "Snapshot output must be outside the project and RUN workspace trees", 1, { output });
|
|
224
234
|
prepareOutputFile(output);
|
|
225
235
|
prepareSnapshotProjectGit(projectRoot, recovery && status.run.flow_kind === "mb-init");
|
|
226
|
-
prepareSnapshotWorkspace(workspaceRoot, projectRoot,
|
|
236
|
+
prepareSnapshotWorkspace(workspaceRoot, projectRoot, isSourceSnapshotFlow(status.run.flow_kind));
|
|
227
237
|
return { projectRoot, project, status, recovery, output, workspaceRoot, activeChildren };
|
|
228
238
|
}
|
|
229
239
|
function captureEvalRunSnapshot(context, input) {
|
|
@@ -241,7 +251,7 @@ function captureEvalRunSnapshot(context, input) {
|
|
|
241
251
|
}
|
|
242
252
|
}
|
|
243
253
|
else
|
|
244
|
-
copyRuntimeTree(context.ddFlowHome, path.join(output, "runtime"), false, [...databaseFiles, ...codexExclusions, ...(
|
|
254
|
+
copyRuntimeTree(context.ddFlowHome, path.join(output, "runtime"), false, [...databaseFiles, ...codexExclusions, ...(isSourceSnapshotFlow(status.run.flow_kind) ? [workspaceRoot] : [])], grokProviderHomes(context.ddFlowHome), input.observationCuts);
|
|
245
255
|
for (const [file, cut] of input.journalCuts ?? []) {
|
|
246
256
|
const target = path.join(output, "runtime", path.relative(context.ddFlowHome, file));
|
|
247
257
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
@@ -281,7 +291,7 @@ function captureEvalRunSnapshot(context, input) {
|
|
|
281
291
|
filter: projectPayloadFilter(projectRoot)
|
|
282
292
|
});
|
|
283
293
|
const projectGit = snapshotProjectGit(projectRoot, output, recovery && status.run.flow_kind === "mb-init");
|
|
284
|
-
const workspace = snapshotWorkspace(workspaceRoot, projectRoot, output,
|
|
294
|
+
const workspace = snapshotWorkspace(workspaceRoot, projectRoot, output, isSourceSnapshotFlow(status.run.flow_kind));
|
|
285
295
|
// Accepted Work results are captured in SQLite. Their file representations
|
|
286
296
|
// are retained evidence, not a second authority or a checkpoint gate.
|
|
287
297
|
if (recovery) {
|
|
@@ -1546,7 +1556,7 @@ function readBootstrapManifest(snapshot) {
|
|
|
1546
1556
|
throw new AppError("snapshot_missing", "Bootstrap snapshot manifest is missing", 1, { snapshot });
|
|
1547
1557
|
const value = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
1548
1558
|
const validBoundary = value.schema_id === "dd-eval/bootstrap-snapshot@1" ? value.stage === "specify" && value.flow === undefined
|
|
1549
|
-
: value.schema_id === "dd-eval/bootstrap-snapshot@2" && (value.flow?.id === "mb-init" || value.flow?.id === "mb-upgrade" ||
|
|
1559
|
+
: value.schema_id === "dd-eval/bootstrap-snapshot@2" && (value.flow?.id === "mb-init" || value.flow?.id === "mb-upgrade" || value.flow?.id === "interactive" || isSourceSnapshotFlow(value.flow?.id) || isDeliveryFlow(value.flow?.id)) && value.stage === "preflight"
|
|
1550
1560
|
&& Object.keys(value.flow).sort().join() === "descriptor_sha256,id,source_sha256"
|
|
1551
1561
|
&& /^[a-f0-9]{64}$/.test(value.flow.source_sha256) && /^[a-f0-9]{64}$/.test(value.flow.descriptor_sha256);
|
|
1552
1562
|
if (!validBoundary || !value.project_root || !value.project_sha256 || !value.project_git?.branch || !value.project_git.head || !value.project_git.bundle_sha256)
|
|
@@ -22,6 +22,7 @@ import { readFlowRunExecutionBundle } from "./runs.js";
|
|
|
22
22
|
import { stageWorkIds } from "./stage-work-graph.js";
|
|
23
23
|
import { hybridDigest } from "./hybrid-contracts.js";
|
|
24
24
|
import { prepareMemoryRepairDispatchFromStage, prepareMemorySecondRepairDispatchFromStage, prepareExternalMemoryRepairCopyFromStage, prepareMemorySecondRepairLaunchFromStage } from "./memory-review-handler.js";
|
|
25
|
+
import { readRegisteredInteractiveWork } from "./interactive-work-registration.js";
|
|
25
26
|
/**
|
|
26
27
|
* Creates an external provider root for one already-created Work. The launch
|
|
27
28
|
* is not a Work claim: only the worker's trusted `work start` hook binds it.
|
|
@@ -60,7 +61,12 @@ export async function launchExternalWork(context, input) {
|
|
|
60
61
|
}
|
|
61
62
|
if (override?.delegation !== undefined)
|
|
62
63
|
throw new AppError("generic_execution_invalid", "Work cannot replace the RUN's frozen delegation", 2);
|
|
63
|
-
const
|
|
64
|
+
const interactive = bundle.manifest.flow_id === "interactive"
|
|
65
|
+
? readRegisteredInteractiveWork(context, { projectId: work.project_id, runId: work.run_id }, bundle, work.work_id).packet : null;
|
|
66
|
+
if (interactive && interactive.owner_stage !== stage)
|
|
67
|
+
throw new AppError("execution_stage_mismatch", "Interactive role differs from its owning Stage", 2);
|
|
68
|
+
const route = interactive ? bundle.execution.work(stage, override?.agent_profile_id, interactive.stage)
|
|
69
|
+
: bundle.execution.work(stage, override?.agent_profile_id);
|
|
64
70
|
const selected = { coordinator: route.coordinator, worker: route.worker,
|
|
65
71
|
delegation: { mode: route.delegation.mode, max_parallel: route.delegation.mode === "external" ? route.delegation.max_parallel : null } };
|
|
66
72
|
resolved = { ...selected, profile_hash: crypto.createHash("sha256").update(JSON.stringify(selected)).digest("hex") };
|
|
@@ -182,7 +188,7 @@ export async function launchExternalWork(context, input) {
|
|
|
182
188
|
if (reviewCopy && reviewSourceHash(run.workspace_root, reviewInputs, reviewBase) !== reviewCopy.source_sha256)
|
|
183
189
|
throw new AppError("read_only_input_changed", "Authoritative review workspace changed after the execution copy was made", 2, { work_id: work.work_id });
|
|
184
190
|
const promptFile = path.join(root, "launch.md");
|
|
185
|
-
fs.writeFileSync(promptFile, `Start the assigned Work now. Your first tool call must be this exact standalone command:\n\n${workStartCommand(runtimeContext, work)}\n\nUse the packet returned by that command. Do not claim another Work or run this command more than once.\n\n${lifecycleRetryInstruction}${isolatedWorkspace ? `\n\nThe review copy has no .git. Read the captured status/diff or gitless note in ${path.join(isolatedWorkspace, ".dd-flow", "review-evidence")}; do not run Git inside the copy.\n` : "\n"}`);
|
|
191
|
+
fs.writeFileSync(promptFile, `Start the assigned Work now. Your first tool call must be this exact standalone command:\n\n${workStartCommand(runtimeContext, work)}\n\nUse the packet returned by that command. Do not claim another Work or run this command more than once.\n\nAfter the issued Work finish succeeds, end this Turn immediately. The coordinator, not you, continues the Stage. Do not then read or follow other runtime/coordinator prompts, run Stage checks, write Stage proposals, or invoke stage start/finish/resume.\n\n${lifecycleRetryInstruction}${isolatedWorkspace ? `\n\nThe review copy has no .git. Read the captured status/diff or gitless note in ${path.join(isolatedWorkspace, ".dd-flow", "review-evidence")}; do not run Git inside the copy.\n` : "\n"}`);
|
|
186
192
|
const promptOperationId = `${operationId}:session.prompt`;
|
|
187
193
|
context.db.run("UPDATE work_launches SET adapter_receipt_json = ?, updated_at = ? WHERE operation_id = ? AND status = 'launching'", [JSON.stringify({ create: created, progress_policy: "native-inactivity@1" }), context.now(), operationId]);
|
|
188
194
|
let prompted;
|
|
@@ -2,6 +2,11 @@ import { vnextStages } from "../domain/stage-catalog.js";
|
|
|
2
2
|
import { sdlcHandlerIdentities, memoryInitHandlerIdentities, memoryUpgradeHandlerIdentities } from "../domain/stage-handler-identities.js";
|
|
3
3
|
import { AppError } from "../shared/errors.js";
|
|
4
4
|
import { diagnosticFlowStages } from "../domain/diagnostic-flows.js";
|
|
5
|
+
import { distillStages } from "../domain/distill-flow.js";
|
|
6
|
+
import { memoryFixStages } from "../domain/memory-fix-flow.js";
|
|
7
|
+
import { reviewFixStages } from "../domain/review-fix-flow.js";
|
|
8
|
+
import { interactiveStages } from "../domain/interactive-flow.js";
|
|
9
|
+
import { deliveryFlowStages } from "../domain/delivery-flows.js";
|
|
5
10
|
/** Engine-owned catalog, not descriptor discovery or permission to execute a RUN. */
|
|
6
11
|
export function listBundledFlows() {
|
|
7
12
|
return [
|
|
@@ -16,6 +21,25 @@ export function listBundledFlows() {
|
|
|
16
21
|
edges: stages.slice(0, -1).flatMap((handler, index) => [{ from: handler.stage, to: stages[index + 1].stage, on: "accepted" },
|
|
17
22
|
...(handler.stage === "verify" ? [{ from: handler.stage, to: "review", on: "needs_repair" }] : [])]) };
|
|
18
23
|
}),
|
|
24
|
+
{ id: "mb-distill", handler_status: "registered", entry_stage: "preflight", terminal_stages: ["finish"],
|
|
25
|
+
stages: distillStages.map((id, index) => ({ id, directory: `${String(index + 1).padStart(2, "0")}-${id}`, entry_handler: "distill-stage-entry", finish_handler: "distill-stage-finish" })),
|
|
26
|
+
edges: distillStages.slice(0, -1).map((from, index) => ({ from, to: distillStages[index + 1], on: "accepted" })) },
|
|
27
|
+
{ id: "mb-fix", handler_status: "registered", entry_stage: "intake", terminal_stages: ["finish"],
|
|
28
|
+
stages: memoryFixStages.map((id, index) => ({ id, directory: `${String(index + 1).padStart(2, "0")}-${id}`,
|
|
29
|
+
entry_handler: "memory-fix-stage-entry", finish_handler: "memory-fix-stage-finish" })),
|
|
30
|
+
edges: memoryFixStages.slice(0, -1).map((from, index) => ({ from, to: memoryFixStages[index + 1], on: "accepted" })) },
|
|
31
|
+
{ id: "review-fix", handler_status: "registered", entry_stage: "intake", terminal_stages: ["handoff"],
|
|
32
|
+
stages: reviewFixStages.map((id, index) => ({ id, directory: `${String(index + 1).padStart(2, "0")}-${id}`,
|
|
33
|
+
entry_handler: "review-fix-stage-entry", finish_handler: "review-fix-stage-finish" })),
|
|
34
|
+
edges: reviewFixStages.slice(0, -1).map((from, index) => ({ from, to: reviewFixStages[index + 1], on: "accepted" })) },
|
|
35
|
+
{ id: "interactive", handler_status: "registered", entry_stage: "preflight", terminal_stages: ["integration"],
|
|
36
|
+
stages: interactiveStages.map((id, index) => ({ id, directory: `${String(index + 1).padStart(2, "0")}-${id}`,
|
|
37
|
+
entry_handler: "interactive-stage-entry", finish_handler: "interactive-stage-finish" })),
|
|
38
|
+
edges: interactiveStages.slice(0, -1).map((from, index) => ({ from, to: interactiveStages[index + 1], on: "accepted" })) },
|
|
39
|
+
...Object.entries(deliveryFlowStages).map(([id, stages]) => ({ id, handler_status: "registered", entry_stage: "preflight", terminal_stages: ["finish"],
|
|
40
|
+
stages: stages.map((stage, index) => ({ id: stage, directory: `${String(index + 1).padStart(2, "0")}-${stage}`,
|
|
41
|
+
entry_handler: "delivery-stage-entry", finish_handler: "delivery-stage-finish" })),
|
|
42
|
+
edges: stages.slice(0, -1).map((from, index) => ({ from, to: stages[index + 1], on: "accepted" })) })),
|
|
19
43
|
...Object.entries(diagnosticFlowStages).map(([id, stages]) => ({ id, handler_status: "registered", entry_stage: "preflight", terminal_stages: ["report"],
|
|
20
44
|
stages: stages.map((stage, index) => ({ id: stage, directory: `${String(index + 1).padStart(2, "0")}-${stage}`,
|
|
21
45
|
entry_handler: "diagnostic-stage-entry", finish_handler: "diagnostic-stage-finish" })),
|
|
@@ -12,6 +12,7 @@ import { isMemoryInitResourceStorageFacts } from "./memory-init-resource-storage
|
|
|
12
12
|
import { memoryInitStartupInputSchemas } from "./memory-init-startup-custody.js";
|
|
13
13
|
import { SETUP_STARTUP_MS } from "../harness-runtime/lib/setup-progress.mjs";
|
|
14
14
|
import { isDiagnosticFlow } from "../domain/diagnostic-flows.js";
|
|
15
|
+
import { isDeliveryFlow } from "../domain/delivery-flows.js";
|
|
15
16
|
function storageProbeRefuse(reason, probe) {
|
|
16
17
|
const summary = probe ? ` (${probe.error_code ?? probe.signal ?? `exit ${probe.status}`})` : "";
|
|
17
18
|
throw new AppError("frozen_engine_storage_incompatible", `Selected frozen engine cannot admit memory-flow storage${summary}`, 1, { reason, effect: "no_effect", ...(probe ? { probe } : {}) });
|
|
@@ -160,6 +161,22 @@ export class AdmittedFrozenEngine {
|
|
|
160
161
|
admitDiagnosticFlow(source, flowId) {
|
|
161
162
|
return this.#admitMemoryFlow(source, flowId);
|
|
162
163
|
}
|
|
164
|
+
admitDistillFlow(source) {
|
|
165
|
+
return this.#admitMemoryFlow(source, "mb-distill");
|
|
166
|
+
}
|
|
167
|
+
admitMemoryFixFlow(source) {
|
|
168
|
+
return this.#admitMemoryFlow(source, "mb-fix");
|
|
169
|
+
}
|
|
170
|
+
admitReviewFixFlow(source) {
|
|
171
|
+
return this.#admitMemoryFlow(source, "review-fix");
|
|
172
|
+
}
|
|
173
|
+
admitInteractiveFlow(source) {
|
|
174
|
+
source.assertEngineCompatibility(this.#manifest);
|
|
175
|
+
return this.#admitMemoryFlow(source, "interactive");
|
|
176
|
+
}
|
|
177
|
+
admitDeliveryFlow(source, flowId) {
|
|
178
|
+
return this.#admitMemoryFlow(source, flowId);
|
|
179
|
+
}
|
|
163
180
|
#admitMemoryFlow(source, flowId, startupInputSchema) {
|
|
164
181
|
const fail = (reason, details = {}) => {
|
|
165
182
|
throw new AppError("frozen_engine_flow_incompatible", `Selected frozen engine cannot admit ${flowId} handlers`, 1, { ...details, reason, phase: "prepare", effect: "no_effect" });
|
|
@@ -191,7 +208,7 @@ export class AdmittedFrozenEngine {
|
|
|
191
208
|
const flow = source.resolveRegisteredFlow();
|
|
192
209
|
if (flow.descriptor.id !== flowId)
|
|
193
210
|
fail("foreign_flow_source");
|
|
194
|
-
const expected = { schema_id: isDiagnosticFlow(flowId) ? "dd-flow/diagnostic-engine-flow@1" : flowId === "mb-upgrade" ? "dd-flow/memory-upgrade-engine-flow@1"
|
|
211
|
+
const expected = { schema_id: isDeliveryFlow(flowId) ? "dd-flow/delivery-engine-flow@1" : flowId === "interactive" ? "dd-flow/interactive-engine-flow@1" : flowId === "review-fix" ? "dd-flow/review-fix-engine-flow@1" : flowId === "mb-fix" ? "dd-flow/memory-fix-engine-flow@1" : flowId === "mb-distill" ? "dd-flow/distill-engine-flow@1" : isDiagnosticFlow(flowId) ? "dd-flow/diagnostic-engine-flow@1" : flowId === "mb-upgrade" ? "dd-flow/memory-upgrade-engine-flow@1"
|
|
195
212
|
: startupInputSchema === undefined ? "dd-flow/memory-init-engine-flow@1" : "dd-flow/memory-init-engine-flow@2",
|
|
196
213
|
...(startupInputSchema === undefined ? {} : { startup_input_schema: startupInputSchema }),
|
|
197
214
|
flow_id: flowId, source_sha256: source.sha256,
|
|
@@ -199,7 +216,7 @@ export class AdmittedFrozenEngine {
|
|
|
199
216
|
revalidate();
|
|
200
217
|
const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !["NODE_OPTIONS", "NODE_PATH"].includes(key) && !key.startsWith("DD_FLOW_")));
|
|
201
218
|
const started = performance.now();
|
|
202
|
-
const result = spawnSync(this.nodeExecutable, [this.entrypoint, "runtime", "flow", "admit", "--operation", isDiagnosticFlow(flowId) ? flowId : flowId === "mb-init" ? "memory-init" : "memory-upgrade", "--json"], { input, cwd: this.#manifest.snapshot_root, env: { ...env, DD_FLOW_ENGINE_MODE: "1", DD_FLOW_ENGINE_HOME: this.#manifest.snapshot_root },
|
|
219
|
+
const result = spawnSync(this.nodeExecutable, [this.entrypoint, "runtime", "flow", "admit", "--operation", isDeliveryFlow(flowId) || isDiagnosticFlow(flowId) || flowId === "interactive" || flowId === "mb-distill" || flowId === "mb-fix" || flowId === "review-fix" ? flowId : flowId === "mb-init" ? "memory-init" : "memory-upgrade", "--json"], { input, cwd: this.#manifest.snapshot_root, env: { ...env, DD_FLOW_ENGINE_MODE: "1", DD_FLOW_ENGINE_HOME: this.#manifest.snapshot_root },
|
|
203
220
|
// This is cold startup of a selected CLI, not an already-loaded handler.
|
|
204
221
|
// Admission is bounded local validation; no provider operation is started.
|
|
205
222
|
timeout: SETUP_STARTUP_MS, maxBuffer: 1024 * 1024, shell: false, windowsHide: true });
|