@deksden-com/dd-flow-cli 0.9.0-beta.151 → 0.9.0-beta.155
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -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 +273 -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 +170 -25
- package/dist/harness-runtime/lib/dd-opencode.mjs +40 -19
- 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 +1 -0
- package/dist/harness-runtime/lib/delegation-instructions.mjs +10 -3
- 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 +77 -10
- package/dist/services/check-runner-proof.js +69 -1
- package/dist/services/cli-operation-classifier.js +6 -1
- package/dist/services/code-checks.js +56 -23
- package/dist/services/codex-hook-delivery.js +11 -2
- package/dist/services/controller-execution.js +4 -1
- package/dist/services/controller-fanout.js +9 -4
- 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 +214 -24
- 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 -4
- package/dist/services/retained-native-hook.js +245 -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 +12 -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 +209 -43
- 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 +9 -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 +252 -66
- package/dist/services/worktrees.js +7 -4
- package/dist/storage/database.js +2 -1
- package/dist/verification-components.json +82 -0
- package/package.json +4 -3
- package/tools/verification-jobs.mjs +342 -0
|
@@ -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 });
|
|
@@ -145,10 +145,11 @@ export class FrozenGenericExecution {
|
|
|
145
145
|
this.#flow.stage(id);
|
|
146
146
|
return { profile: this.profile(this.settings.stage_profiles[id]), session_mode: this.settings.stage_session_mode };
|
|
147
147
|
}
|
|
148
|
-
work(stage, profileId) {
|
|
148
|
+
work(stage, profileId, workerStage = stage) {
|
|
149
149
|
const coordinator = this.stage(stage).profile;
|
|
150
|
-
const
|
|
151
|
-
|
|
150
|
+
const roleProfile = this.stage(workerStage).profile;
|
|
151
|
+
const id = profileId ?? roleProfile.id;
|
|
152
|
+
if (id !== roleProfile.id && id !== coordinator.id && !this.settings.work_profiles.includes(id))
|
|
152
153
|
fail("Work profile is not approved for this RUN");
|
|
153
154
|
const worker = this.profile(id);
|
|
154
155
|
if (this.settings.delegation.mode === "native" && !sameSessionProfile(coordinator, worker))
|
|
@@ -11,7 +11,6 @@ import { readOperationProgress, readOperationRecord, readOperationWaiting, suspe
|
|
|
11
11
|
import { recoverDaemonReply } from "../harness-runtime/lib/daemon-observation.mjs";
|
|
12
12
|
import { isObservationLoss } from "../harness-runtime/lib/operation-errors.mjs";
|
|
13
13
|
import { readBoundedReleaseFile } from "./release-source.js";
|
|
14
|
-
const maxAdapterOutputBytes = 1_000_000;
|
|
15
14
|
/** Native hooks need a real executable, not a PATH-dependent shell expression. */
|
|
16
15
|
export function prepareHarnessFlowExecutable(context, stateDir, engine) {
|
|
17
16
|
// Native login shells rewrite search paths. The owned SDK must execute with
|
|
@@ -132,7 +131,6 @@ function runAdapterTransport(input, stateDir, binding = {}) {
|
|
|
132
131
|
const child = spawn(input.executable, input.args, { env, ...(input.cwd ? { cwd: input.cwd } : {}), detached, stdio: ["ignore", "pipe", "pipe"] });
|
|
133
132
|
let stdout = "";
|
|
134
133
|
let stderr = "";
|
|
135
|
-
let outputTooLarge = false;
|
|
136
134
|
let timedOut = false;
|
|
137
135
|
let aborted = false;
|
|
138
136
|
let cleanup;
|
|
@@ -159,7 +157,7 @@ function runAdapterTransport(input, stateDir, binding = {}) {
|
|
|
159
157
|
reject(callbackError);
|
|
160
158
|
}
|
|
161
159
|
else
|
|
162
|
-
reject(new AppError(
|
|
160
|
+
reject(new AppError(timedOut ? "harness_adapter_timeout" : aborted ? "harness_adapter_aborted" : "harness_adapter_cleanup_unconfirmed", "Adapter transport cleanup could not be confirmed", 1, details));
|
|
163
161
|
});
|
|
164
162
|
};
|
|
165
163
|
const deliver = (callback) => {
|
|
@@ -220,14 +218,6 @@ function runAdapterTransport(input, stateDir, binding = {}) {
|
|
|
220
218
|
else
|
|
221
219
|
input.signal?.addEventListener("abort", abort, { once: true });
|
|
222
220
|
const evidence = (value) => input.onEvidence?.({ operation_id: input.operationId, executable: input.executable, args: input.args, ...value, at: new Date().toISOString() });
|
|
223
|
-
const append = (current, chunk) => {
|
|
224
|
-
if (Buffer.byteLength(current) + Buffer.byteLength(chunk) > maxAdapterOutputBytes) {
|
|
225
|
-
outputTooLarge = true;
|
|
226
|
-
stopTransport();
|
|
227
|
-
return current;
|
|
228
|
-
}
|
|
229
|
-
return current + chunk;
|
|
230
|
-
};
|
|
231
221
|
const observeDiagnostic = (line) => {
|
|
232
222
|
if (prettyDiagnostic === null) {
|
|
233
223
|
if (line.trimEnd() === "}")
|
|
@@ -281,7 +271,7 @@ function runAdapterTransport(input, stateDir, binding = {}) {
|
|
|
281
271
|
}
|
|
282
272
|
}
|
|
283
273
|
};
|
|
284
|
-
child.stdout.setEncoding("utf8").on("data", (chunk) => { stdout
|
|
274
|
+
child.stdout.setEncoding("utf8").on("data", (chunk) => { stdout += chunk; });
|
|
285
275
|
// Diagnostics have no lifetime quota. Stream to the existing evidence sink
|
|
286
276
|
// and carry only a bounded tail in the disposable RPC receipt.
|
|
287
277
|
child.stderr.setEncoding("utf8").on("data", (chunk) => {
|
|
@@ -322,8 +312,6 @@ function runAdapterTransport(input, stateDir, binding = {}) {
|
|
|
322
312
|
deliver(() => evidence({ status, signal, timed_out: timedOut, aborted, stdout, stderr, ...(spawnError ? { error: spawnError.message } : {}) }));
|
|
323
313
|
if (spawnError)
|
|
324
314
|
return reject(new AppError("harness_adapter_failed", "Harness adapter call failed", 1, { cause: adapterCause(spawnError.message), ...(callbackError ? { evidence_error: String(callbackError) } : {}) }));
|
|
325
|
-
if (outputTooLarge)
|
|
326
|
-
return reject(new AppError("harness_adapter_output_limit", "Harness adapter exceeded the bounded reply limit", 1, { executable: input.executable, args: input.args.slice(0, 3), max_output_bytes: maxAdapterOutputBytes }));
|
|
327
315
|
if (timedOut)
|
|
328
316
|
return reject(Object.assign(new AppError("harness_adapter_timeout", "Harness adapter call timed out", 1, { executable: input.executable, args: input.args.slice(0, 3), timeout_ms: timeoutMs }), { observationClock: clock }));
|
|
329
317
|
if (aborted)
|
|
@@ -143,7 +143,7 @@ export function memoryRepairAssignedHookCwd(context, input) {
|
|
|
143
143
|
export function memoryNativeReviewAssignedHookCwd(context, input) {
|
|
144
144
|
const work = context.db.get("SELECT status,payload_json FROM works WHERE project_id=? AND run_id=? AND work_id=?", [input.projectId, input.runId, input.workId]);
|
|
145
145
|
const payload = JSON.parse(work?.payload_json ?? "null");
|
|
146
|
-
if (
|
|
146
|
+
if (!["memory-review", "interactive-review"].includes(payload?.kind) || payload?.read_only !== true)
|
|
147
147
|
return null;
|
|
148
148
|
const ready = context.db.get("SELECT 1 FROM audit_events WHERE project_id=? AND event_type='memory.native_review_copy_ready' AND json_extract(payload_json,'$.intent.run_id')=? AND json_extract(payload_json,'$.intent.work_id')=?", [input.projectId, input.runId, input.workId]);
|
|
149
149
|
if (!ready)
|