@deksden-com/dd-flow-cli 0.9.0-beta.155 → 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 +8 -0
- package/dist/build-info.json +3 -3
- package/dist/cli/run-cli.js +2 -1
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +2 -2
- package/dist/harness-runtime/lib/delegation-instructions.d.mts +1 -1
- package/dist/harness-runtime/lib/delegation-instructions.mjs +5 -2
- package/dist/services/controller-child-repair.js +95 -0
- package/dist/services/controller-fanout.js +10 -5
- package/dist/services/lifecycle-invocations.js +36 -9
- package/dist/services/retained-native-hook.js +34 -5
- package/dist/services/run-controller-process.js +19 -6
- package/dist/services/run-controller.js +49 -2
- package/dist/services/work-registry.js +3 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @deksden-com/dd-flow-cli
|
|
2
2
|
|
|
3
|
+
## 0.9.0-beta.156
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- b59c32b: Preserve current lifecycle successors, restore OpenCode retained child admission, and separate Work correction from technical pause and native resource cleanup. Continue an eligible completed child once per Work without replaying unknown effects or treating descriptive deviations as unfinished obligations.
|
|
8
|
+
|
|
9
|
+
Observe a concurrent controller-launch winner instead of rejecting an already accepted duplicate resume; retain physical ownership and launch CAS fences.
|
|
10
|
+
|
|
3
11
|
## 0.9.0-beta.155
|
|
4
12
|
|
|
5
13
|
Versions beta.153 and beta.154 were used by unpublished preparation candidates; this release uses a new immutable identity. Older pending Changesets below reconcile release accounting rather than claim their functionality is new.
|
package/dist/build-info.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"cli_package": "@deksden-com/dd-flow-cli",
|
|
3
|
-
"cli_version": "0.9.0-beta.
|
|
4
|
-
"cli_commit": "
|
|
5
|
-
"built_at": "2026-10-
|
|
3
|
+
"cli_version": "0.9.0-beta.156",
|
|
4
|
+
"cli_commit": "1a2bd0ea6d022b22e94f552f1cde1fb8e8b6689e",
|
|
5
|
+
"built_at": "2026-10-28T07:26:47.345Z",
|
|
6
6
|
"built_with_canon": {
|
|
7
7
|
"version": "4.1.3",
|
|
8
8
|
"commit": "052ffdf0fff642a46485d2decc79bf78b1970d5e",
|
package/dist/cli/run-cli.js
CHANGED
|
@@ -408,7 +408,8 @@ async function runCliRequest(args, io, env) {
|
|
|
408
408
|
return;
|
|
409
409
|
nextNativeObservation = Date.now() + 250;
|
|
410
410
|
const { reconcileRetainedNativeHook } = await import("../services/retained-native-hook.js");
|
|
411
|
-
await reconcileRetainedNativeHook(dispatchContext, { scope, invocationId, onProgress
|
|
411
|
+
await reconcileRetainedNativeHook(dispatchContext, { scope, invocationId, onProgress,
|
|
412
|
+
onDiagnostic: value => io.stderr.write(`${JSON.stringify({ kind: "native_hook_diagnostic", ...value })}\n`) });
|
|
412
413
|
} } : {}) });
|
|
413
414
|
if (admission.replay) {
|
|
414
415
|
retainedOutcome = admission.outcome;
|
|
@@ -69,9 +69,9 @@ async function forwardUsage(config, sessionId, snapshot) {
|
|
|
69
69
|
async function freePort() { return await new Promise((resolve, reject) => { const server = net.createServer(); server.once("error", reject); server.listen(0, "127.0.0.1", () => { const port = server.address().port; server.close(() => resolve(port)); }); }); }
|
|
70
70
|
const errorPayload = errorRecord;
|
|
71
71
|
|
|
72
|
-
async function writePlugin(paths, config) {
|
|
72
|
+
export async function writePlugin(paths, config) {
|
|
73
73
|
const directory = path.join(paths.config, "opencode", "plugins"); await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
74
|
-
const source = `import { makeOpenCodePlugin } from ${JSON.stringify(new URL("./opencode-plugin.mjs", import.meta.url).href)};\nexport default makeOpenCodePlugin(${JSON.stringify({ stateDir: paths.dir, daemonId: config.daemonId, version: config.version, ddFlowBin: config.ddFlowBin, nodeBin: process.execPath, ddFlowHome: config.ddFlowHome, projectRoot: config.projectRoot, resourceHome: config.resourceHome, nativeWorkStartAdmission: true })});\n`;
|
|
74
|
+
const source = `import { makeOpenCodePlugin } from ${JSON.stringify(new URL("./opencode-plugin.mjs", import.meta.url).href)};\nexport default makeOpenCodePlugin(${JSON.stringify({ stateDir: paths.dir, daemonId: config.daemonId, version: config.version, ddFlowBin: config.ddFlowBin, nodeBin: process.execPath, ddFlowHome: config.ddFlowHome, projectRoot: config.projectRoot, resourceHome: config.resourceHome, runtime_owner: config.runtime_owner, noFlow: config.noFlow, nativeWorkStartAdmission: true })});\n`;
|
|
75
75
|
const checksum = createHash("sha256").update(source).digest("hex"); await writeFile(path.join(directory, "dd-flow.js"), source, { mode: 0o600 }); return checksum;
|
|
76
76
|
}
|
|
77
77
|
async function prepare(paths, config, authSource) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type DelegationWork = { work_id: string; start_command: string; launch_policy?: string };
|
|
2
2
|
export type DelegationInput = { harness: string; stage: string; capacity: number; works?: DelegationWork[]; totalWorks?: number; remainingWorks?: number };
|
|
3
3
|
export function delegationAdapter(harness: string): string | null;
|
|
4
|
-
export function renderNativeChildFollowupInstruction(harness: string, childId: string | { session_id?: unknown; parent_session_id?: unknown; terminal_turn_id?: unknown; continuation_ref?: unknown }): string | null;
|
|
4
|
+
export function renderNativeChildFollowupInstruction(harness: string, childId: string | { session_id?: unknown; parent_session_id?: unknown; terminal_turn_id?: unknown; continuation_ref?: unknown }, options?: { purpose: "work_repair"; context?: string }): string | null;
|
|
5
5
|
export function delegationContract(harness: string): string;
|
|
6
6
|
export const lifecycleRetryInstruction: string;
|
|
7
7
|
export const lifecycleCommandCompletionInstruction: string;
|
|
@@ -38,12 +38,15 @@ export function delegationAdapter(harness) {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/** Only documented same-child APIs may continue an existing native worker. */
|
|
41
|
-
export function renderNativeChildFollowupInstruction(harness, childId) {
|
|
41
|
+
export function renderNativeChildFollowupInstruction(harness, childId, options = {}) {
|
|
42
42
|
const child = childId && typeof childId === "object" ? childId : null;
|
|
43
43
|
childId = child?.session_id ?? childId;
|
|
44
44
|
if (typeof childId !== "string" || !childId) return null;
|
|
45
45
|
const adapter = delegationAdapter(harness), target = JSON.stringify(childId);
|
|
46
|
-
|
|
46
|
+
if (options.purpose === "work_repair" && ["zcode", "antigravity"].includes(adapter)) return null;
|
|
47
|
+
const message = JSON.stringify(options.purpose === "work_repair"
|
|
48
|
+
? `Continue the same assigned Work using this retained correction, not provider recovery. Inspect the original check/result diagnostic, correct the evidenced defect, and use only its issued successor. Do not restart the Work, spawn another child, or repeat committed/pending lifecycle commands.\n${options.context ?? ""}`
|
|
49
|
+
: "Continue the same assigned Work after the transient provider failure; do not restart it or repeat committed lifecycle commands.");
|
|
47
50
|
if (adapter === "zcode") {
|
|
48
51
|
const ref = child?.continuation_ref;
|
|
49
52
|
if (ref?.schema_id !== "zcode/child-continuation@1" || ref.capability !== "dd-flow/managed-provider-recovery@1" || ref.child_session_id !== childId
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { AppError } from "../shared/errors.js";
|
|
2
|
+
import { renderNativeChildFollowupInstruction, renderWaitInstructions } from "../harness-runtime/lib/delegation-instructions.mjs";
|
|
3
|
+
import { nativeItemsSettled } from "../harness-runtime/lib/provider-overload-policy.mjs";
|
|
4
|
+
import { parseLifecycleCommand } from "./lifecycle-command.js";
|
|
5
|
+
import { storageSessionId } from "./session-identity.js";
|
|
6
|
+
import { stageWorkIds } from "./stage-work-graph.js";
|
|
7
|
+
import { appendEvent, digest } from "./run-controller-state.js";
|
|
8
|
+
const blocked = (workId, reason, diagnostic = {}) => new AppError("child_work_repair_blocked", "Completed child has no safe automatic Work correction", 1, { work_id: workId, reason, reconciliation_required: true, effect: "no_effect", ...diagnostic });
|
|
9
|
+
/** One durable same-child correction per Work, not a provider retry or a new worker. */
|
|
10
|
+
export function completedChildRepair(context, row, input) {
|
|
11
|
+
const members = stageWorkIds(context, row.project_id, row.run_id, input.stage);
|
|
12
|
+
for (const child of input.children) {
|
|
13
|
+
if (child.status !== "completed")
|
|
14
|
+
continue;
|
|
15
|
+
const links = context.db.all(`SELECT w.work_id,w.status FROM work_sessions ws
|
|
16
|
+
JOIN works w ON w.work_id=ws.work_id JOIN sessions s ON s.session_id=ws.session_id AND s.project_id=w.project_id
|
|
17
|
+
WHERE w.project_id=? AND w.run_id=? AND ws.session_id=? AND s.parent_session_id=?`, [row.project_id, row.run_id, storageSessionId({ harness_id: input.harness, session_id: child.session_id }), storageSessionId({ harness_id: input.harness, session_id: input.parentId })])
|
|
18
|
+
.filter(link => members.has(link.work_id));
|
|
19
|
+
if (!links.some(link => link.status === "running"))
|
|
20
|
+
continue;
|
|
21
|
+
if (links.length !== 1 || child.parent_session_id !== input.parentId || child.provenance === "parent_mismatch"
|
|
22
|
+
|| !child.terminal_turn_id || !nativeItemsSettled(child.native_turn_items))
|
|
23
|
+
throw blocked(links[0].work_id, "native_identity_unconfirmed");
|
|
24
|
+
const workId = links[0].work_id;
|
|
25
|
+
if (context.db.get("SELECT 1 FROM check_receipts WHERE project_id=? AND run_id=? AND work_id=? AND status='running' LIMIT 1", [row.project_id, row.run_id, workId]))
|
|
26
|
+
continue;
|
|
27
|
+
if (context.db.get("SELECT 1 FROM works WHERE parent_work_id=? AND status IN ('created','running','paused') LIMIT 1", [workId]))
|
|
28
|
+
throw blocked(workId, "descendants_unsettled");
|
|
29
|
+
const previous = context.db.get(`SELECT e.controller_id,e.data_json FROM run_controller_events e
|
|
30
|
+
JOIN run_controllers c ON c.controller_id=e.controller_id WHERE c.project_id=? AND c.run_id=?
|
|
31
|
+
AND e.event_type='work_repair_reserved' AND json_extract(e.data_json,'$.work_id')=? ORDER BY e.sequence LIMIT 1`, [row.project_id, row.run_id, workId]);
|
|
32
|
+
if (previous) {
|
|
33
|
+
const retained = JSON.parse(previous.data_json);
|
|
34
|
+
const operation = context.db.get("SELECT status,input_json FROM run_controller_operations WHERE operation_id=?", [retained.operation_id]);
|
|
35
|
+
const diagnostic = { operation_id: retained.operation_id, refusal_invocation_id: retained.refusal_invocation_id };
|
|
36
|
+
if (previous.controller_id !== row.controller_id || retained.generation !== row.generation || retained.terminal_turn_id !== child.terminal_turn_id
|
|
37
|
+
|| retained.child_session_id !== child.session_id)
|
|
38
|
+
throw blocked(workId, "automatic_episode_exhausted", diagnostic);
|
|
39
|
+
if (operation?.status === "completed" || operation?.status === "failed")
|
|
40
|
+
throw blocked(workId, "followup_admission_unconfirmed", diagnostic);
|
|
41
|
+
// Same operation ID lets the adapter observe an uncertain dispatch. It
|
|
42
|
+
// may dispatch only if its own durable phase proves it never crossed out.
|
|
43
|
+
return retained;
|
|
44
|
+
}
|
|
45
|
+
const refusals = context.db.all("SELECT id,command,scope_json,identity_json,outcome_json FROM lifecycle_invocations WHERE status='settled' AND json_extract(outcome_json,'$.error.details.work_id')=? ORDER BY rowid DESC", [workId]);
|
|
46
|
+
const refusal = refusals.find(attempt => {
|
|
47
|
+
try {
|
|
48
|
+
const scope = JSON.parse(attempt.scope_json), identity = JSON.parse(attempt.identity_json ?? "null");
|
|
49
|
+
const parsed = parseLifecycleCommand(attempt.command);
|
|
50
|
+
return attempt.scope_json === context.env.DD_FLOW_INVOCATION_SCOPE && parsed.kind === "standalone" && parsed.invocation.operation === "work_finish" && scope.runId === row.run_id
|
|
51
|
+
&& scope.generation === row.generation && scope.rootSessionId === input.parentId
|
|
52
|
+
&& Array.isArray(identity) && identity[2] === child.session_id && identity[3] === input.parentId;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
if (!refusal)
|
|
59
|
+
throw blocked(workId, "repair_refusal_missing");
|
|
60
|
+
const outcome = JSON.parse(refusal.outcome_json), error = outcome.error, details = error?.details;
|
|
61
|
+
const diagnostic = { refusal_invocation_id: refusal.id, primary_error: { code: error?.code, message: error?.message } };
|
|
62
|
+
if (details?.effect !== "no_effect" || details.recoverable !== true || typeof details.retry_command !== "string"
|
|
63
|
+
|| typeof outcome.successor_invocation_id !== "string")
|
|
64
|
+
throw blocked(workId, "repair_successor_unproven", diagnostic);
|
|
65
|
+
const successor = context.db.get("SELECT scope_json,status FROM lifecycle_invocations WHERE id=?", [outcome.successor_invocation_id]);
|
|
66
|
+
if (successor?.status !== "issued" || successor.scope_json !== refusal.scope_json)
|
|
67
|
+
throw blocked(workId, "repair_successor_not_issued", diagnostic);
|
|
68
|
+
const instruction = renderNativeChildFollowupInstruction(input.harness, child, { purpose: "work_repair", context: JSON.stringify({ work_id: workId, code: error.code, message: error.message, retry_command: details.retry_command }) });
|
|
69
|
+
if (!instruction)
|
|
70
|
+
throw blocked(workId, "same_child_repair_unsupported", diagnostic);
|
|
71
|
+
return { work_id: workId, generation: row.generation, child_session_id: child.session_id, terminal_turn_id: child.terminal_turn_id, refusal_invocation_id: refusal.id,
|
|
72
|
+
successor_invocation_id: outcome.successor_invocation_id, operation_id: `${row.controller_id}:work-repair:${digest([row.run_id, workId])}`,
|
|
73
|
+
prompt: `Continue only the existing child for this unfinished Work. This is one bounded correction, not a new fanout wave.\n${instruction}\n\nA follow-up submission acknowledgement or the child's previous completed Turn is not completion of this correction. Wait for this same child's follow-up; never resend the message to poll it.\n${renderWaitInstructions({ harness: input.harness, stage: input.stage })}` };
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
export function reserveChildRepair(context, row, repair, assertOwner) {
|
|
78
|
+
context.db.writeTransaction(() => {
|
|
79
|
+
assertOwner();
|
|
80
|
+
if (repair.generation !== row.generation)
|
|
81
|
+
throw blocked(repair.work_id, "generation_changed_before_followup");
|
|
82
|
+
const work = context.db.get("SELECT status FROM works WHERE project_id=? AND run_id=? AND work_id=?", [row.project_id, row.run_id, repair.work_id]);
|
|
83
|
+
if (work?.status !== "running")
|
|
84
|
+
throw blocked(repair.work_id, "work_changed_before_followup");
|
|
85
|
+
const successor = context.db.get("SELECT status FROM lifecycle_invocations WHERE id=?", [repair.successor_invocation_id]);
|
|
86
|
+
if (successor?.status !== "issued")
|
|
87
|
+
throw blocked(repair.work_id, "successor_changed_before_followup");
|
|
88
|
+
const reserved = context.db.get(`SELECT e.controller_id,e.data_json FROM run_controller_events e JOIN run_controllers c ON c.controller_id=e.controller_id
|
|
89
|
+
WHERE c.project_id=? AND c.run_id=? AND e.event_type='work_repair_reserved' AND json_extract(e.data_json,'$.work_id')=?`, [row.project_id, row.run_id, repair.work_id]);
|
|
90
|
+
if (reserved && (reserved.controller_id !== row.controller_id || reserved.data_json !== JSON.stringify(repair)))
|
|
91
|
+
throw blocked(repair.work_id, "episode_reserved_by_another_dispatch");
|
|
92
|
+
if (!reserved)
|
|
93
|
+
appendEvent(context, row.controller_id, "work_repair_reserved", repair);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
@@ -12,7 +12,7 @@ import { requireProjectByRoot } from "./projects.js";
|
|
|
12
12
|
import { stageAttempt, stageWorkIds } from "./stage-work-graph.js";
|
|
13
13
|
import { runtimeCommand } from "./runtime-command.js";
|
|
14
14
|
import { promptDeliveryInstruction } from "./prompt-delivery.js";
|
|
15
|
-
import {
|
|
15
|
+
import { currentStageFinishAuthority } from "./lifecycle-invocations.js";
|
|
16
16
|
import { acceptedChildWork } from "./controller-result-authority.js";
|
|
17
17
|
function semanticCompletionPrompt(stage, completed, command) {
|
|
18
18
|
const task = stage === "plan-review"
|
|
@@ -72,7 +72,10 @@ export async function nextControllerFanout(context, input) {
|
|
|
72
72
|
throw new AppError("stage_graph_compatibility_required", "The retained graph cannot be dispatched by this controller; use the RUN's pinned engine", 1, { stage: input.stage, run_id: input.runId, graph: fanout });
|
|
73
73
|
if (children.length) {
|
|
74
74
|
const reconciled = await reconcileVnextFanout(context, { ...input, observations });
|
|
75
|
-
|
|
75
|
+
// A known settled child's Work blocker does not consume an independent
|
|
76
|
+
// ready branch. It remains running, so this cannot manufacture Stage success.
|
|
77
|
+
const issues = reconciled.issues.filter(issue => !["child_check_in_progress", "child_capacity_pending"].includes(issue.code)
|
|
78
|
+
&& !(issue.code === "execution_ended_without_work_result" && issue.work_id && input.repairBlockedWorks?.includes(issue.work_id)));
|
|
76
79
|
if (issues.length)
|
|
77
80
|
throw new AppError("fanout_reconciliation_required", "Native child outcome requires reconciliation, not replay", 1, { issues });
|
|
78
81
|
const unsettled = children.filter(child => child.status === "running" || child.status === "unknown");
|
|
@@ -214,10 +217,12 @@ export async function nextControllerFanout(context, input) {
|
|
|
214
217
|
].join("\n\n"));
|
|
215
218
|
}
|
|
216
219
|
}
|
|
217
|
-
const finish =
|
|
220
|
+
const finish = currentStageFinishAuthority(context, input.stage);
|
|
218
221
|
if (!finish)
|
|
219
|
-
throw new AppError("controller_finish_authority_missing", "Settled child Work has no current
|
|
220
|
-
|
|
222
|
+
throw new AppError("controller_finish_authority_missing", "Settled child Work has no current Stage finish authority", 1, { stage: input.stage, reconciliation_required: true });
|
|
223
|
+
if (finish.status !== "issued" || !finish.command)
|
|
224
|
+
throw new AppError("controller_finish_authority_pending", "Observe the original Stage finish before another productive dispatch", 1, { stage: input.stage, invocation_id: finish.id, status: finish.status, reconciliation_required: true });
|
|
225
|
+
return prompt("semantic_completion", semanticCompletionPrompt(input.stage, fanout.works.completed, finish.command));
|
|
221
226
|
}
|
|
222
227
|
/** Shared pinned runtime normalization; preserve structured errors at the TS boundary. */
|
|
223
228
|
export function controllerNativeChildren(receipt, rootSessionId) {
|
|
@@ -108,7 +108,7 @@ export function isRetryableLifecycleRejection(code, details = {}) {
|
|
|
108
108
|
return details.recoverable !== false;
|
|
109
109
|
if (["work_checks_failed", "code_gate_failed", "code_review_gate_failed", "merge_gate_failed"].includes(code))
|
|
110
110
|
return Array.isArray(details.failures) && details.failures.length > 0 && details.failures.every(receipt => checkDiagnosisDisposition(receipt) === "diagnosis_required");
|
|
111
|
-
return details.recoverable === true || ["review_repair_incomplete", "plan_review_incomplete", "semantic_result_invalid",
|
|
111
|
+
return details.recoverable === true || ["work_contract_incomplete", "review_repair_incomplete", "plan_review_incomplete", "semantic_result_invalid",
|
|
112
112
|
"review_evidence_invalid", "evidence_obligation_unknown", "invalid_evidence_ref",
|
|
113
113
|
"evidence_ref_missing", "evidence_line_out_of_range", "document_update_missing",
|
|
114
114
|
"document_update_not_materialized", "review_repair_no_change"].includes(code)
|
|
@@ -765,18 +765,23 @@ function invocationRecoverySnapshot(context, scope) {
|
|
|
765
765
|
}
|
|
766
766
|
/** Resolve authority, never reconstruct it from an old prompt or issue on read. */
|
|
767
767
|
export function currentStageFinishCommand(context, stage) {
|
|
768
|
+
return currentStageFinishAuthority(context, stage)?.command ?? null;
|
|
769
|
+
}
|
|
770
|
+
/** Observation of an existing attempt is not permission to dispatch it again. */
|
|
771
|
+
export function currentStageFinishAuthority(context, stage) {
|
|
768
772
|
const scope = context.env.DD_FLOW_INVOCATION_SCOPE;
|
|
769
773
|
if (!scope || !context.db.get("SELECT 1 FROM sqlite_master WHERE name='lifecycle_invocations'"))
|
|
770
774
|
return null;
|
|
771
|
-
const rows = context.db.all("SELECT * FROM lifecycle_invocations WHERE scope_json = ? AND status
|
|
775
|
+
const rows = context.db.all("SELECT * FROM lifecycle_invocations WHERE scope_json = ? AND status IN ('issued','observed','executing')", [scope])
|
|
772
776
|
.filter(row => {
|
|
773
777
|
const parsed = invocation(row.command);
|
|
774
|
-
return parsed.operation === "stage_finish" && commandOption(parsed, "stage") === stage
|
|
775
|
-
&& (row.deadline === null || row.deadline > Date.now());
|
|
778
|
+
return parsed.operation === "stage_finish" && commandOption(parsed, "stage") === stage;
|
|
776
779
|
});
|
|
777
780
|
if (rows.length > 1)
|
|
778
781
|
throw new AppError("invocation_ambiguous", "Stage has multiple current finish authorities", 1, { stage });
|
|
779
|
-
|
|
782
|
+
const row = rows[0];
|
|
783
|
+
return row ? { id: row.id, status: row.status, command: row.status === "issued" && (row.deadline === null || row.deadline > Date.now())
|
|
784
|
+
? publicInvocationCommand(context, row, row.command) : null } : null;
|
|
780
785
|
}
|
|
781
786
|
export function managedInvocationContext(context, input) {
|
|
782
787
|
const env = { ...context.env, DD_FLOW_CURRENT_INVOCATION: undefined, DD_FLOW_INVOCATION_SCOPE: undefined };
|
|
@@ -1876,7 +1881,8 @@ export function observedLifecycleInvocation(context, command, scopeConstraint, c
|
|
|
1876
1881
|
const retainedId = suppliedId && context.db.get("SELECT id FROM lifecycle_invocations WHERE id = ?", [suppliedId]) ? suppliedId : undefined;
|
|
1877
1882
|
// `issued` is a rendezvous, not authority: the CLI still blocks in
|
|
1878
1883
|
// awaitLifecycleInvocation until the native observer commits the receipt.
|
|
1879
|
-
const
|
|
1884
|
+
const history = context.db.all("SELECT * FROM lifecycle_invocations WHERE status IN ('issued','observed','executing','settled') ORDER BY rowid DESC");
|
|
1885
|
+
const matching = history
|
|
1880
1886
|
.filter(row => {
|
|
1881
1887
|
try {
|
|
1882
1888
|
const explicitId = retainedId;
|
|
@@ -1896,7 +1902,7 @@ export function observedLifecycleInvocation(context, command, scopeConstraint, c
|
|
|
1896
1902
|
}
|
|
1897
1903
|
});
|
|
1898
1904
|
const identity = correlation?.identity;
|
|
1899
|
-
const matches = lifecycleCandidates(matching, identity ? JSON.stringify([identity.daemonId, identity.rootSessionId, identity.sessionId, identity.parentSessionId, identity.toolCallId]) : undefined, true);
|
|
1905
|
+
const matches = lifecycleCandidates(matching, identity ? JSON.stringify([identity.daemonId, identity.rootSessionId, identity.sessionId, identity.parentSessionId, identity.toolCallId]) : undefined, true, history);
|
|
1900
1906
|
if (!matches.length)
|
|
1901
1907
|
return null;
|
|
1902
1908
|
if (matches.length !== 1)
|
|
@@ -1905,8 +1911,9 @@ export function observedLifecycleInvocation(context, command, scopeConstraint, c
|
|
|
1905
1911
|
}
|
|
1906
1912
|
/** A new native call may consume a successor, but never a historical result.
|
|
1907
1913
|
* Exact native delivery replays its immutable assignment. CLI lookup without
|
|
1908
|
-
* native correlation
|
|
1909
|
-
|
|
1914
|
+
* native correlation may use a uniquely bound committed successor, not an
|
|
1915
|
+
* arbitrary newest/active row. Unrelated history remains ambiguous. */
|
|
1916
|
+
function lifecycleCandidates(rows, identity, lookup = false, history = rows) {
|
|
1910
1917
|
if (identity) {
|
|
1911
1918
|
const replay = rows.filter(row => {
|
|
1912
1919
|
if (!row.identity_json)
|
|
@@ -1927,6 +1934,26 @@ function lifecycleCandidates(rows, identity, lookup = false) {
|
|
|
1927
1934
|
return replay;
|
|
1928
1935
|
}
|
|
1929
1936
|
const active = rows.filter(row => row.status !== "settled");
|
|
1937
|
+
if (lookup && !identity && active.length === 1 && (active[0].status === "issued" || active[0].identity_json && active[0].event_key)) {
|
|
1938
|
+
const current = active[0];
|
|
1939
|
+
const leadsToCurrent = (row, seen = new Set()) => {
|
|
1940
|
+
if (row.id === current.id)
|
|
1941
|
+
return true;
|
|
1942
|
+
if (row.status !== "settled" || seen.has(row.id))
|
|
1943
|
+
return false;
|
|
1944
|
+
seen.add(row.id);
|
|
1945
|
+
try {
|
|
1946
|
+
const next = JSON.parse(row.outcome_json ?? "null")?.successor_invocation_id;
|
|
1947
|
+
const successor = history.find(candidate => candidate.id === next && candidate.scope_json === current.scope_json);
|
|
1948
|
+
return Boolean(successor && leadsToCurrent(successor, seen));
|
|
1949
|
+
}
|
|
1950
|
+
catch {
|
|
1951
|
+
return false;
|
|
1952
|
+
}
|
|
1953
|
+
};
|
|
1954
|
+
if (rows.every(row => leadsToCurrent(row)))
|
|
1955
|
+
return active;
|
|
1956
|
+
}
|
|
1930
1957
|
return (!lookup || identity) && active.length ? active : rows;
|
|
1931
1958
|
}
|
|
1932
1959
|
/** Restore private assignment arguments without discarding the provider's
|
|
@@ -12,6 +12,7 @@ import { handleAgyEvent, handleCodexHook, handleDroidEvent, handleOpenCodeEvent,
|
|
|
12
12
|
import { withDaemonProgressObserver } from "../harness-runtime/lib/daemon-observation.mjs";
|
|
13
13
|
const object = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
14
14
|
const sha256 = (value) => crypto.createHash("sha256").update(value).digest("hex");
|
|
15
|
+
const diagnostics = new WeakMap();
|
|
15
16
|
async function regularJson(file) {
|
|
16
17
|
try {
|
|
17
18
|
const handle = await open(file, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
@@ -65,6 +66,23 @@ function payloadCommand(binding) {
|
|
|
65
66
|
* binding from its verified native-delivery map before it becomes a receipt. */
|
|
66
67
|
export async function reconcileRetainedNativeHook(context, input) {
|
|
67
68
|
const { scope, invocationId } = input;
|
|
69
|
+
const diagnostic = (phase, error, binding) => {
|
|
70
|
+
if (!input.onDiagnostic)
|
|
71
|
+
return;
|
|
72
|
+
const supplied = error instanceof Error || object(error) ? error.code : undefined;
|
|
73
|
+
const code = typeof supplied === "string" && /^[A-Za-z][A-Za-z0-9_]{0,95}$/.test(supplied) ? supplied : "native_hook_verification_unavailable";
|
|
74
|
+
// Diagnostics never become execution authority or activity. Keep identifiers
|
|
75
|
+
// and a typed cause, not native messages/configs that can contain credentials.
|
|
76
|
+
const value = { phase, code, invocation_id: invocationId,
|
|
77
|
+
...(typeof binding?.request_id === "string" ? { request_id: binding.request_id } : {}),
|
|
78
|
+
...(typeof binding?.operation_id === "string" ? { operation_id: binding.operation_id } : {}) };
|
|
79
|
+
const seen = diagnostics.get(context) ?? new Set(), key = JSON.stringify(value);
|
|
80
|
+
if (seen.has(key))
|
|
81
|
+
return;
|
|
82
|
+
seen.add(key);
|
|
83
|
+
diagnostics.set(context, seen);
|
|
84
|
+
input.onDiagnostic(value);
|
|
85
|
+
};
|
|
68
86
|
if (!scope.runId || context.env.DD_FLOW_DAEMON_ID !== scope.daemonId)
|
|
69
87
|
return false;
|
|
70
88
|
let owner = null;
|
|
@@ -84,17 +102,22 @@ export async function reconcileRetainedNativeHook(context, input) {
|
|
|
84
102
|
let metadata;
|
|
85
103
|
try {
|
|
86
104
|
const state = readNativeDaemonState(stateDir);
|
|
87
|
-
if ((state.daemon_id ?? state.config?.daemonId) !== scope.daemonId)
|
|
105
|
+
if ((state.daemon_id ?? state.config?.daemonId) !== scope.daemonId) {
|
|
106
|
+
diagnostic("owner", { code: "daemon_owner_mismatch" });
|
|
88
107
|
return false;
|
|
108
|
+
}
|
|
89
109
|
const project = requireProjectByRoot(context, scope.projectRoot);
|
|
90
110
|
owned = requireManagedDaemonBinding(context, { stateDir, projectId: project.id, runId: scope.runId });
|
|
91
111
|
metadata = JSON.parse(owned.process.metadata_json);
|
|
92
112
|
}
|
|
93
|
-
catch {
|
|
113
|
+
catch (error) {
|
|
114
|
+
diagnostic("owner", error);
|
|
94
115
|
return false;
|
|
95
116
|
}
|
|
96
|
-
if (metadata.run_generation !== scope.generation)
|
|
117
|
+
if (metadata.run_generation !== scope.generation) {
|
|
118
|
+
diagnostic("owner", { code: "run_generation_mismatch" });
|
|
97
119
|
return false;
|
|
120
|
+
}
|
|
98
121
|
const operations = path.join(stateDir, "operations");
|
|
99
122
|
if (!await realDirectory(operations))
|
|
100
123
|
return false;
|
|
@@ -151,14 +174,20 @@ export async function reconcileRetainedNativeHook(context, input) {
|
|
|
151
174
|
|| !(resolved.parent_provider_session_id === null || typeof resolved.parent_provider_session_id === "string")
|
|
152
175
|
|| ["request_id", "operation_id", "daemon_id", "turn_generation", "source_harness", "root_provider_session_id", "provider_session_id",
|
|
153
176
|
"tool_call_id", "event", "native_command", "command_sha256", "native_creator", "native_admission"].some(key => resolved[key] !== binding[key])
|
|
154
|
-
|| payloadCommand(resolved) !== binding.native_command)
|
|
177
|
+
|| payloadCommand(resolved) !== binding.native_command) {
|
|
178
|
+
diagnostic("verify", { code: "native_hook_unproven" }, binding);
|
|
155
179
|
continue;
|
|
180
|
+
}
|
|
156
181
|
verified.push(resolved);
|
|
157
182
|
}
|
|
158
183
|
else if (proof.verified === true && JSON.stringify(proof.nativeHookBinding) === JSON.stringify(binding))
|
|
159
184
|
verified.push(binding);
|
|
185
|
+
else
|
|
186
|
+
diagnostic("verify", { code: "native_hook_unproven" }, binding);
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
diagnostic("verify", error, binding);
|
|
160
190
|
}
|
|
161
|
-
catch { /* Legacy/retired daemons cannot attest a retained fact. */ }
|
|
162
191
|
}
|
|
163
192
|
// Exact old tool delivery belongs to its immutable prior attempt. A public
|
|
164
193
|
// command may be identical in a fresh no-effect successor; do not repeatedly
|
|
@@ -79,12 +79,25 @@ async function prepareSpawn(context, row) {
|
|
|
79
79
|
const launch = launchRecord(context, row);
|
|
80
80
|
if (!launch || launch.value.dispatch_ready || !["starting", "running", "recovery_required"].includes(row.status))
|
|
81
81
|
return null;
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
const superseded = new AppError("controller_owner_mismatch", "Controller launch owner changed during physical observation", 1);
|
|
83
|
+
let launcher;
|
|
84
|
+
try {
|
|
85
|
+
launcher = await physicalStartup(context.databaseDeadlineAt).birth(() => {
|
|
86
|
+
const current = requireController(context, row.controller_id);
|
|
87
|
+
if (current.generation !== row.generation)
|
|
88
|
+
throw new AppError("controller_owner_mismatch", "Controller launch generation changed during physical observation", 1);
|
|
89
|
+
if (current.owner_token !== row.owner_token)
|
|
90
|
+
throw superseded;
|
|
91
|
+
assertLaunchAllowed(context, current);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
// Like a lost launch CAS below, a concurrent winner is observed, not
|
|
96
|
+
// retried or replaced. Other ownership/admission errors remain refusals.
|
|
97
|
+
if (error === superseded)
|
|
98
|
+
return null;
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
88
101
|
const resourceContext = { ...context, env: { ...context.env, DD_FLOW_RESOURCE_HOME: JSON.parse(row.manifest_json).resource_home } };
|
|
89
102
|
const ours = launch.value.pid === launcher.pid && launch.value.pid_started_at === launcher.pid_started_at;
|
|
90
103
|
let token = row.owner_token;
|
|
@@ -55,6 +55,8 @@ import { storageSessionId } from "./session-identity.js";
|
|
|
55
55
|
import { RECOVERY_POLICY as CAPACITY_POLICY, nativeItemsSettled, recoveryExhausted as overloadExhausted, recoveryRefusal as refusalObservation, terminalProviderRecovery as terminalProviderOverload, normalizeRecoveryFailure } from "../harness-runtime/lib/provider-overload-policy.mjs";
|
|
56
56
|
import { renderNativeChildFollowupInstruction } from "../harness-runtime/lib/delegation-instructions.mjs";
|
|
57
57
|
import { childCapacityDispatchAllowed, childCapacityRecoveryDecision, closeSuccessfulChildCapacity, reconcileChildCapacity } from "./controller-child-capacity.js";
|
|
58
|
+
import { completedChildRepair, reserveChildRepair } from "./controller-child-repair.js";
|
|
59
|
+
import { getVnextFanoutStatus } from "./vnext-fanout.js";
|
|
58
60
|
import { isObservationLoss } from "../harness-runtime/lib/operation-errors.mjs";
|
|
59
61
|
import { recordTechnicalNote } from "./technical-notes.js";
|
|
60
62
|
import { acceptedChildWork, acceptedChildrenRetired, controllerObligation, controllerObligationAccepted, lateNativeFailure } from "./controller-result-authority.js";
|
|
@@ -673,7 +675,9 @@ async function executeController(context, row, manifest, state, assertOwnership,
|
|
|
673
675
|
return false;
|
|
674
676
|
});
|
|
675
677
|
const pauseDependency = async (session, sourceOperationId) => {
|
|
676
|
-
|
|
678
|
+
// The control intent contains the retained productive failure; it must not
|
|
679
|
+
// require that same failure to disappear before the owner can pause it.
|
|
680
|
+
assertPhysicalOwner();
|
|
677
681
|
note(session, "native_writer_dependency_blocked", "stage_transition", "dependency_blocked");
|
|
678
682
|
const requestId = `technical:${row.controller_id}:${row.generation}:${digest([state.stage, session.state_dir]).slice(0, 20)}`;
|
|
679
683
|
const input = { projectRoot: manifest.request.project_root, runId: row.run_id, requestId, action: "pause" };
|
|
@@ -1394,8 +1398,51 @@ async function executeController(context, row, manifest, state, assertOwnership,
|
|
|
1394
1398
|
continue;
|
|
1395
1399
|
}
|
|
1396
1400
|
}
|
|
1401
|
+
const repairBlockedWorks = [];
|
|
1402
|
+
if (!retainedFailures.length && children.some(child => child.status === "completed" && !acceptedChildWork(context, row, childHarness, session.id, child.session_id))) {
|
|
1403
|
+
const inspected = await call(session, ["session", "inspect", "--session-id", session.id, ...sessionArguments(context, manifest, session)], false);
|
|
1404
|
+
state.last_receipt = inspected;
|
|
1405
|
+
saveState(context, row, state);
|
|
1406
|
+
children = controllerNativeChildren(inspected, session.id).filter(child => !baseline.has(child.session_id));
|
|
1407
|
+
// Do not compete with an active parent/healthy sibling or turn a known
|
|
1408
|
+
// repair into a replacement fanout wave.
|
|
1409
|
+
if (inspected.settled !== true || children.some(child => ["running", "unknown"].includes(child.status))) {
|
|
1410
|
+
setStatus("waiting_for_children");
|
|
1411
|
+
await delay(1000, undefined, { signal: cancellation.signal });
|
|
1412
|
+
continue;
|
|
1413
|
+
}
|
|
1414
|
+
let repair = null;
|
|
1415
|
+
const blockers = [];
|
|
1416
|
+
for (const child of children) {
|
|
1417
|
+
try {
|
|
1418
|
+
repair ??= completedChildRepair(executionContext, row, { stage: next.stage, harness: childHarness, parentId: session.id, children: [child] });
|
|
1419
|
+
}
|
|
1420
|
+
catch (error) {
|
|
1421
|
+
if (!(error instanceof AppError) || error.code !== "child_work_repair_blocked")
|
|
1422
|
+
throw error;
|
|
1423
|
+
blockers.push(error);
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
if (repair) {
|
|
1427
|
+
reserveChildRepair(executionContext, row, repair, assertOwner);
|
|
1428
|
+
await prompt(session, repair.prompt, repair.operation_id, undefined, "work", [repair.work_id]);
|
|
1429
|
+
continue;
|
|
1430
|
+
}
|
|
1431
|
+
if (blockers.length) {
|
|
1432
|
+
const graph = getVnextFanoutStatus(executionContext, { projectRoot: run.project_root, runId: run.id, stage: next.stage }).orchestration;
|
|
1433
|
+
if (!graph?.works.ready.length)
|
|
1434
|
+
throw blockers[0];
|
|
1435
|
+
for (const blocker of blockers) {
|
|
1436
|
+
const workId = String(blocker.details.work_id);
|
|
1437
|
+
repairBlockedWorks.push(workId);
|
|
1438
|
+
recordTechnicalNote(context, row.project_id, row.run_id, { code: "child_work_repair_blocked", message: blocker.message,
|
|
1439
|
+
phase: "work_repair", generation: row.generation, stage: next.stage, work_id: workId, harness: childHarness,
|
|
1440
|
+
resource_identity: workId, consequence: "dependency_blocked", diagnostic_reference: String(blocker.details.reason) });
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1397
1444
|
const memoryFlow = ["mb-init", "mb-upgrade", "mb-fix", "interactive"].includes(run.flow_kind) || isSourceSnapshotFlow(run.flow_kind) || routing.kind === "descriptor" && isDeliveryFlow(run.flow_kind);
|
|
1398
|
-
const fanoutInput = { projectRoot: run.project_root, runId: run.id, stage: next.stage, harness: runtimeHarness(session.profile), sessionId: session.id, children, policy, profiles, dispatchFence: retainedFailures.length > 0 };
|
|
1445
|
+
const fanoutInput = { projectRoot: run.project_root, runId: run.id, stage: next.stage, harness: runtimeHarness(session.profile), sessionId: session.id, children, policy, profiles, dispatchFence: retainedFailures.length > 0, repairBlockedWorks };
|
|
1399
1446
|
const fanout = memoryFlow ? await runControllerMemoryTask(executionContext, { operation: "fanout", input: fanoutInput }, cancellation.signal)
|
|
1400
1447
|
: await nextControllerFanout(executionContext, fanoutInput);
|
|
1401
1448
|
if (memoryFlow)
|
|
@@ -1943,8 +1943,8 @@ export function validateCodeWorkResult(work, value, projectRoot, runHome, runId)
|
|
|
1943
1943
|
const ignored = ignoredWorkspacePaths(projectRoot, changedPaths);
|
|
1944
1944
|
if (ignored.length)
|
|
1945
1945
|
throw new AppError("environment_repair_not_product_change", "CODE Work must record ignored environment repair separately, not as a product changed_path", 2, { work_id: work.work_id, paths: ignored });
|
|
1946
|
-
if ((result.
|
|
1947
|
-
throw new AppError("work_contract_incomplete", "CODE Work cannot complete with unresolved
|
|
1946
|
+
if ((result.blockers?.length ?? 0) > 0)
|
|
1947
|
+
throw new AppError("work_contract_incomplete", "CODE Work cannot complete with unresolved blockers; correct the result or fail this Work with the evidenced blocker", 2, { work_id: work.work_id, blockers: result.blockers ?? [], phase: "prepare", effect: "no_effect", recoverable: true });
|
|
1948
1948
|
const packet = codePacket(work);
|
|
1949
1949
|
if (!packet)
|
|
1950
1950
|
return;
|
|
@@ -2124,7 +2124,7 @@ function renderWorkerPromptBody(context, work, run, dependencies, commands) {
|
|
|
2124
2124
|
: ["Read the failed receipt and its stdout/stderr. Fix project-owned source, migration, test, formatting, or configuration errors in this same Work, then use the returned retry_command with your result. Internal invocation IDs are runtime-owned; do not copy or invent them. Do not invent a cause that does not appear in the retained output."];
|
|
2125
2125
|
const codeContext = packet ? ["<semantic_spine>", JSON.stringify(packet.semantic_spine, null, 2), "</semantic_spine>", "", ...(packet.repair ? ["<repair_context>", JSON.stringify(packet.repair, null, 2), "Read the failed receipt and its linked stdout/stderr before editing. Preserve the accepted origin context and fix only the evidenced failure.", "</repair_context>", ""] : []), "<accepted_requirements>", JSON.stringify(packet.requirements, null, 2), "</accepted_requirements>", "", "<write_boundary_invariant>", "For a mutation guarded by membership, ownership, authorization or parent lifecycle state, preserve that predicate in the write statement or make guard and write one explicit transaction with the needed lock. A prior read may diagnose an error but never proves a later write remains allowed. Apply this to create, update, delete and parent-state mutations.", "</write_boundary_invariant>", "", "<acceptance_context>", "The criteria below are end-to-end context. Complete this Work's semantic contribution and declared checks; another ordered Work may own a different acceptance surface.", JSON.stringify(packet.acceptance, null, 2), "</acceptance_context>", "", "<required_read>", "These are mandatory starting sources, not a read allowlist. Read any additional project files needed to implement the Work correctly.", ...packet.required_read.map((item) => `- ${resolveRunReferences(item, work.run_id, requireRunHome(run))}`), "</required_read>", "", "<discovery_boundary>", "These are likely discovery areas, not a hard boundary. Expand project-local investigation when required and report material additions.", ...packet.discovery_boundary.map((item) => `- ${item}`), "</discovery_boundary>", "", "<planned_write_areas>", "SOFT COORDINATION HINT ONLY. These paths help the coordinator avoid concurrent collisions. They do not grant or deny write permission and do not limit the files needed for this Work. You may create or change any project file under workspace_root that is necessary and in semantic scope; report every actual changed path.", ...(packet.planned_write_areas.length ? packet.planned_write_areas.map((item) => `- ${item}`) : ["- none predicted; derive the necessary files from the task"]), "</planned_write_areas>", "", ...(packet.provides_checks.length ? ["<provided_checks>", ...packet.provides_checks.map((item) => `- ${item.id}: materialize ${item.command}${item.definition ? ` as ${item.definition}` : ""}; it is not usable until this Work finishes.`), "Update the declared project command or alias before Work finish. The CLI verifies materialization at Work finish. Execution occurs at its declared run_at gate, not necessarily in this Work.", "</provided_checks>", ""] : []), "<verification>", ...packet.checks.map((item) => `- ${item.id} at ${item.run_at}: ${item.command} — ${item.purpose}`), "The CLI executes work-scoped checks and retains their receipts. Report semantic evidence only; do not rerun declared checks manually.", "</verification>", "", "<stop_conditions>", ...packet.stop_conditions.map((item) => `- ${item}`), "</stop_conditions>", ""] : [];
|
|
2126
2126
|
if (packet)
|
|
2127
|
-
codeContext.push("<document_updates>", JSON.stringify(packet.document_updates, null, 2), "Materialize every listed update. dd-flow verifies the resulting file against its PLAN-time baseline.", "</document_updates>", "", "<completion_contract>", "Successful completion requires
|
|
2127
|
+
codeContext.push("<document_updates>", JSON.stringify(packet.document_updates, null, 2), "Materialize every listed update. dd-flow verifies the resulting file against its PLAN-time baseline.", "</document_updates>", "", "<completion_contract>", "Successful completion requires no unresolved blockers and closure of every assigned obligation/document update. Record descriptive deviations as notes for review; their presence alone does not reject the Work. A necessary path outside planned_write_areas is normal coordination drift, not a blocker; include it in changed_paths and continue.", "</completion_contract>", "");
|
|
2128
2128
|
if (packet)
|
|
2129
2129
|
codeContext.push("<temporary_services>", "Prefer the declared check launcher: it already owns check resources. If the planned scenario genuinely requires an interactive HTTP service, use the managed supervisor below. This is a template: replace the project service command, port names and readiness path from the plan/project instructions; do not invent a fixed port.", `${command} runtime process start --run ${run.id} --project-root ${JSON.stringify(run.project_root)} --command '<project-service-command>' --ports api --ready-port api --ready-path /health --json --progress-jsonl`, "The service receives DD_FLOW_PORT_API (and equivalent variables for all declared names). The command stays running as its supervisor. Retain its tool handle; wait for the service ready event and read its service.json receipt. Pass those exact ports and the same project environment to reset/seed, API and browser operations.", "A ready receipt proves service readiness only. Record the scenario outcome and real evidence separately. After the scenario, execute the exact stop_command from that receipt, then wait for the supervisor to exit. Never use pkill/killall or stop a sibling's process. If cleanup fails, retain the process id and report the failure; do not claim the resource is free.", "</temporary_services>", "");
|
|
2130
2130
|
return ["<work>", `- work_id: ${work.work_id}`, `- run_id: ${work.run_id}`, `- project_root: ${run.project_root}`, `- workspace_root: ${run.workspace_root}`, `- run_home: ${requireRunHome(run)}`, "</work>", "", "<cli_context>", `- Work commands use the short ID ${shortWorkId(work.work_id)}; dd-flow binds it to this RUN.`, `- @project = ${run.project_root}`, `- @workspace = ${run.workspace_root}`, `- @run = ${requireRunHome(run)}`, "- @ aliases are accepted only by declared dd-flow path parameters. Copy the quoted alias literally; do not add backslashes. Shell tools such as cat and rg require ordinary paths relative to cwd or absolute paths.", "- Lifecycle invocation IDs are internal runtime authority and are intentionally omitted from commands.", "</cli_context>", "", "<hard_write_boundary>", ...writeBoundary, "</hard_write_boundary>", "", ...codeContext, "<dependency_results>", JSON.stringify(dependencies.filter(Boolean), null, 2), "</dependency_results>", "", "<task>", resolveRunReferences(work.task, work.run_id, requireRunHome(run)), "</task>", "", ...(work.result_schema ? ["<result_contract>", `Return JSON matching \`${work.result_schema}\`.`, ...resultSchemaGuidance(work, run.id), `Write the completed JSON to ${resultInputPath}. This is an attempt input, not the canonical result.json receipt. dd-flow validates the file and atomically stores the canonical receipt.`, "</result_contract>", ""] : []), "<completion>", repairPacket ? "Repair Finish validates and publishes the exact private delta before Work settlement. Independent re-review, correction checks and Stage/RUN acceptance remain separate obligations." : packet?.repair?.verification_check_refs?.length ? "Work finish runs its normal work-scoped checks plus the listed causal repair checks. Their original run_at remains an aggregate obligation; this is the additional proof required before accepting this repair." : "Work finish runs only declared run_at=work checks. Stage finish owns readiness/code/merge gates; successful Work completion does not mean those gates have passed.", "A failed receipt means only that the check failed; it is not proof of an engine, harness, dependency, or environment blocker.", ...completionRepair, ...(repairPacket ? [] : [lifecycleRetryInstruction]), repairPacket ? "Use Fail for an evidenced external blocker or assignment conflict, including any required edit outside the exact write_paths allowlist. Never expand that allowlist or edit the shared checkout." : "Use Fail only for a concrete external blocker after deterministic bootstrap or a contradiction with an accepted requirement/non-goal. Never fail merely because a necessary project path was absent from planned_write_areas.", "Finish may run for several minutes. Preserve the shell tool's process/session handle and poll that same invocation until it exits; progress arrives as JSONL on stderr. Never reissue Finish merely because final stdout has not arrived.", `First write only the result JSON to ${resultInputPath}. Then run this standalone command:\n${finishWorkCommand}`, `Fail only for an evidenced external or semantic-contract blocker: ${failWorkCommand}`, "</completion>", ""].join("\n");
|