@deksden-com/dd-flow-cli 0.9.0-beta.38 → 0.9.0-beta.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/dist/build-info.json +5 -5
  3. package/dist/cli/help.js +42 -1
  4. package/dist/cli/run-cli.js +147 -17
  5. package/dist/harness-runtime/bin/dd-agy.mjs +3 -2
  6. package/dist/harness-runtime/bin/dd-codex.mjs +4 -3
  7. package/dist/harness-runtime/bin/dd-droid.mjs +3 -2
  8. package/dist/harness-runtime/bin/dd-grok.mjs +4 -3
  9. package/dist/harness-runtime/bin/dd-opencode.mjs +4 -3
  10. package/dist/harness-runtime/bin/dd-zcode.mjs +4 -3
  11. package/dist/harness-runtime/lib/daemon-operations.mjs +48 -9
  12. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +49 -12
  13. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +57 -16
  14. package/dist/harness-runtime/lib/dd-codex.mjs +10 -2
  15. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +3 -2
  16. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +12 -6
  17. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +4 -3
  18. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +8 -5
  19. package/dist/harness-runtime/lib/managed-daemon.mjs +125 -6
  20. package/dist/harness-runtime/lib/process-json.mjs +1 -1
  21. package/dist/harness-runtime/lib/session-settlement.mjs +4 -2
  22. package/dist/schemas/run-control-receipt.schema.json +99 -0
  23. package/dist/schemas/run-control-request.schema.json +36 -0
  24. package/dist/services/cleanup.js +7 -4
  25. package/dist/services/cli-operation-classifier.js +3 -1
  26. package/dist/services/code-checks.js +33 -7
  27. package/dist/services/controller-fanout.js +105 -0
  28. package/dist/services/engines.js +2 -0
  29. package/dist/services/eval-snapshots.js +379 -36
  30. package/dist/services/execution-policy.js +122 -6
  31. package/dist/services/external-work-launch.js +33 -12
  32. package/dist/services/harness-adapter.js +49 -2
  33. package/dist/services/hooks.js +5 -5
  34. package/dist/services/managed-daemon-binding.js +38 -0
  35. package/dist/services/managed-processes.js +54 -7
  36. package/dist/services/merge-server.js +53 -30
  37. package/dist/services/native-daemon-history.js +49 -0
  38. package/dist/services/native-session-control.js +31 -0
  39. package/dist/services/protocols.js +9 -0
  40. package/dist/services/recovery-snapshot-database.js +103 -0
  41. package/dist/services/run-control-receipt.js +56 -0
  42. package/dist/services/run-control-worker.js +297 -0
  43. package/dist/services/run-control.js +752 -0
  44. package/dist/services/run-controller-adapter.js +79 -0
  45. package/dist/services/run-controller-capture.js +117 -0
  46. package/dist/services/run-controller-process.js +178 -0
  47. package/dist/services/run-controller-recovery.js +219 -0
  48. package/dist/services/run-controller-state.js +31 -0
  49. package/dist/services/run-controller.js +539 -0
  50. package/dist/services/run-recovery-runtime.js +69 -0
  51. package/dist/services/run-recovery.js +33 -9
  52. package/dist/services/runs.js +65 -13
  53. package/dist/services/runtime-budget.js +362 -0
  54. package/dist/services/runtime-scope-capture.js +50 -0
  55. package/dist/services/runtime-scope-control.js +431 -0
  56. package/dist/services/runtime-scope-resume.js +543 -0
  57. package/dist/services/runtime-scope-stop.js +95 -0
  58. package/dist/services/runtime-scope-worker.js +165 -0
  59. package/dist/services/runtime-service.js +4 -0
  60. package/dist/services/vnext-code-review.js +1 -1
  61. package/dist/services/vnext-code.js +1 -1
  62. package/dist/services/vnext-execution-profile.js +2 -35
  63. package/dist/services/vnext-fanout.js +23 -7
  64. package/dist/services/vnext-plan-review.js +12 -9
  65. package/dist/services/vnext-plan.js +1 -1
  66. package/dist/services/vnext-protocolize.js +10 -4
  67. package/dist/services/vnext-specify.js +12 -5
  68. package/dist/services/work-registry.js +26 -8
  69. package/dist/services/workspace-bootstrap.js +10 -1
  70. package/dist/storage/database.js +160 -13
  71. package/dist/storage/writer-contract.js +76 -0
  72. package/dist/storage/writer-migration.js +102 -0
  73. package/package.json +1 -1
@@ -3,9 +3,57 @@ import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { AppError } from "../shared/errors.js";
5
5
  import { harnessKey } from "./harness-config.js";
6
+ import { vnextStages } from "../domain/stage-catalog.js";
7
+ /** Read once so the frozen policy and its provenance describe the same bytes. */
8
+ export function loadExecutionRoutingInput(file) {
9
+ const source_path = path.resolve(file);
10
+ let bytes;
11
+ let value;
12
+ try {
13
+ bytes = fs.readFileSync(source_path);
14
+ value = JSON.parse(bytes.toString("utf8"));
15
+ }
16
+ catch {
17
+ throw new AppError("execution_profile_invalid", "Execution routing input must be a readable JSON file", 2, { source_path });
18
+ }
19
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
20
+ throw new AppError("execution_profile_invalid", "Execution routing input must be an object", 2, { source_path });
21
+ }
22
+ const input = value;
23
+ if (input.schema_id !== "dd-flow/execution-routing@1"
24
+ || Object.keys(input).some(key => key !== "schema_id" && key !== "execution")
25
+ || !isExecutionRouting(input.execution)) {
26
+ throw new AppError("execution_profile_invalid", "Execution routing input does not match dd-flow/execution-routing@1", 2, { source_path });
27
+ }
28
+ return { execution: input.execution, bytes, source_path, source_checksum: crypto.createHash("sha256").update(bytes).digest("hex") };
29
+ }
30
+ /** One routing validator for project defaults and explicit RUN inputs. */
31
+ export function isExecutionRouting(value) {
32
+ if (!value || typeof value !== "object" || Array.isArray(value))
33
+ return false;
34
+ const { stage_overrides: stages, ...root } = value;
35
+ if (typeof root.agent_profile_id !== "string")
36
+ return false;
37
+ try {
38
+ validateExecutionOverride(root);
39
+ if (stages !== undefined) {
40
+ if (!stages || typeof stages !== "object" || Array.isArray(stages))
41
+ return false;
42
+ for (const [stage, override] of Object.entries(stages)) {
43
+ if (!vnextStages.some(item => item.id === stage))
44
+ return false;
45
+ validateExecutionOverride(override);
46
+ }
47
+ }
48
+ return true;
49
+ }
50
+ catch {
51
+ return false;
52
+ }
53
+ }
6
54
  /** Reads one complete profile. A policy selects profiles; it never field-merges them. */
7
55
  export function loadAgentProfile(ddFlowHome, id) {
8
- if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(id))
56
+ if (typeof id !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(id))
9
57
  throw new AppError("agent_profile_invalid", "Agent profile id is invalid", 2, { id });
10
58
  const file = path.join(ddFlowHome, "agent-profiles", `${id}.json`);
11
59
  let value;
@@ -19,26 +67,57 @@ export function loadAgentProfile(ddFlowHome, id) {
19
67
  throw new AppError("agent_profile_invalid", "Agent profile must be an object", 2, { id, file });
20
68
  const profile = value;
21
69
  if (profile.schema_id !== "dd-flow/agent-profile@1" || profile.id !== id
70
+ || Object.keys(profile).some(key => !["schema_id", "id", "harness", "provider", "model", "reasoning", "mode", "permission"].includes(key))
22
71
  || !["codex", "zcode", "agy", "opencode", "grok", "droid"].includes(String(profile.harness))
23
72
  || !nonempty(profile.provider) || !nonempty(profile.model) || !nonempty(profile.reasoning) || !nonempty(profile.mode)
24
73
  || (profile.permission !== "allow" && profile.permission !== "deny")) {
25
74
  throw new AppError("agent_profile_invalid", "Agent profile does not match dd-flow/agent-profile@1", 2, { id, file });
26
75
  }
27
- return profile;
76
+ // Only execution settings enter RUN evidence; never copy arbitrary profile fields.
77
+ return { schema_id: profile.schema_id, id, harness: profile.harness, provider: profile.provider, model: profile.model, reasoning: profile.reasoning, mode: profile.mode, permission: profile.permission };
78
+ }
79
+ export function snapshotAgentProfiles(ddFlowHome, policy) {
80
+ if (!isExecutionRouting(policy))
81
+ throw new AppError("execution_profile_invalid", "Execution routing contains an invalid stage, profile or delegation policy", 2);
82
+ const profiles = Object.create(null);
83
+ for (const override of [policy, ...Object.values(policy.stage_overrides ?? {})]) {
84
+ for (const id of [override.agent_profile_id, override.delegation?.agent_profile_id]) {
85
+ if (id !== undefined && !Object.hasOwn(profiles, id))
86
+ profiles[id] = loadAgentProfile(ddFlowHome, id);
87
+ }
88
+ }
89
+ // Validate every declared route now, including stages not reached by this run.
90
+ for (const stage of [undefined, ...Object.keys(policy.stage_overrides ?? {})]) {
91
+ resolveExecutionPolicy({ ddFlowHome, policy, profiles, ...(stage ? { stage } : {}) });
92
+ }
93
+ return profiles;
28
94
  }
29
95
  /** Resolves a frozen policy with explicit topology. Same profiles do not turn external work into native work. */
30
96
  export function resolveExecutionPolicy(input) {
97
+ if (input.work !== undefined)
98
+ validateExecutionOverride(input.work);
99
+ const profile = (id) => {
100
+ if (!input.profiles)
101
+ return loadAgentProfile(input.ddFlowHome, id);
102
+ if (!Object.hasOwn(input.profiles, id))
103
+ throw new AppError("execution_profile_not_frozen", "Profile is not in this RUN's frozen execution snapshot", 1, { id });
104
+ return input.profiles[id];
105
+ };
31
106
  const stage = input.stage ? input.policy.stage_overrides?.[input.stage] : undefined;
32
- const coordinatorId = input.work?.agent_profile_id ?? stage?.agent_profile_id ?? input.policy.agent_profile_id;
107
+ const coordinatorId = stage?.agent_profile_id ?? input.policy.agent_profile_id;
33
108
  if (!coordinatorId)
34
109
  throw new AppError("execution_profile_missing", "Execution policy needs agent_profile_id before external dispatch", 1, { stage: input.stage ?? null });
35
- const coordinator = loadAgentProfile(input.ddFlowHome, coordinatorId);
110
+ const coordinator = profile(coordinatorId);
36
111
  const delegation = input.work?.delegation ?? stage?.delegation ?? input.policy.delegation ?? { mode: "native" };
37
112
  validateDelegation(delegation);
38
- const worker = loadAgentProfile(input.ddFlowHome, delegation.agent_profile_id ?? coordinator.id);
113
+ if (input.work?.agent_profile_id && input.work.delegation?.agent_profile_id && input.work.agent_profile_id !== input.work.delegation.agent_profile_id)
114
+ throw new AppError("execution_profile_invalid", "Work selects conflicting worker profiles", 2);
115
+ const worker = profile(input.work?.agent_profile_id ?? delegation.agent_profile_id ?? coordinator.id);
39
116
  if (delegation.mode === "native" && harnessKey(worker.harness) !== harnessKey(coordinator.harness)) {
40
117
  throw new AppError("native_harness_mismatch", "Native delegation cannot change harness", 2, { coordinator_harness: coordinator.harness, worker_harness: worker.harness });
41
118
  }
119
+ if (delegation.mode === "native" && !sameSessionProfile(coordinator, worker))
120
+ throw new AppError("native_profile_override_unqualified", "Native worker profile overrides require qualified adapter support; select external delegation", 2, { coordinator_profile_id: coordinator.id, worker_profile_id: worker.id });
42
121
  const resolved = {
43
122
  coordinator,
44
123
  worker,
@@ -47,15 +126,52 @@ export function resolveExecutionPolicy(input) {
47
126
  return { ...resolved, profile_hash: digest(resolved) };
48
127
  }
49
128
  export function runtimeHarness(profile) { return harnessKey(profile.harness); }
129
+ /** Profile labels are not execution identity; every session-affecting value is. */
130
+ export function sameSessionProfile(left, right) {
131
+ const settings = (profile) => ({ harness: runtimeHarness(profile), provider: profile.provider, model: profile.model, reasoning: profile.reasoning, mode: profile.mode, permission: profile.permission });
132
+ return digest(settings(left)) === digest(settings(right));
133
+ }
134
+ export function assertSameSessionMerge(input) {
135
+ const merge = resolveExecutionPolicy({ ...input, stage: "merge" }).coordinator;
136
+ for (const stage of input.previousStages) {
137
+ const previous = resolveExecutionPolicy({ ...input, stage }).coordinator;
138
+ if (!sameSessionProfile(previous, merge))
139
+ throw new AppError("merge_profile_session_conflict", "Same-session MERGE cannot switch execution profiles; select a compatible profile or server MERGE", 2, { previous_stage: stage, previous_profile_id: previous.id, merge_profile_id: merge.id });
140
+ }
141
+ }
142
+ export function resolveStageTransition(input) {
143
+ const target = resolveExecutionPolicy(input);
144
+ const previous = input.fromStage ? resolveExecutionPolicy({ ...input, stage: input.fromStage }).coordinator : null;
145
+ const compatible = !previous || sameSessionProfile(previous, target.coordinator);
146
+ if (input.stage === "merge" && input.mergeMode === "same_session") {
147
+ if (!compatible)
148
+ throw new AppError("merge_profile_session_conflict", "Same-session MERGE cannot replace its current coordinator", 2);
149
+ return { ...target, session_mode: "same_session" };
150
+ }
151
+ return { ...target, session_mode: !compatible || (input.stage === "merge" && input.mergeMode === "server") ? "new_session" : input.stageSessionMode };
152
+ }
50
153
  function validateDelegation(value) {
154
+ if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some(key => !["mode", "agent_profile_id", "max_parallel"].includes(key)))
155
+ throw new AppError("execution_profile_invalid", "Delegation has unsupported fields", 2);
51
156
  if (value.mode !== "native" && value.mode !== "external")
52
157
  throw new AppError("execution_profile_invalid", "Delegation mode must be native or external", 2);
53
- if (value.agent_profile_id !== undefined && !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(value.agent_profile_id))
158
+ if (value.agent_profile_id !== undefined && (typeof value.agent_profile_id !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(value.agent_profile_id)))
54
159
  throw new AppError("execution_profile_invalid", "Delegation profile id is invalid", 2);
55
160
  if (value.max_parallel !== undefined && (!Number.isInteger(value.max_parallel) || value.max_parallel < 1))
56
161
  throw new AppError("execution_profile_invalid", "External max_parallel must be a positive integer", 2);
57
162
  if (value.mode === "native" && value.max_parallel !== undefined)
58
163
  throw new AppError("execution_profile_invalid", "Native delegation does not accept max_parallel", 2);
59
164
  }
165
+ export function validateExecutionOverride(value) {
166
+ if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some(key => !["agent_profile_id", "delegation"].includes(key)))
167
+ throw new AppError("execution_profile_invalid", "Work execution override has unsupported fields", 2);
168
+ const override = value;
169
+ if (override.agent_profile_id === undefined && override.delegation === undefined)
170
+ throw new AppError("execution_profile_invalid", "Work execution override is empty", 2);
171
+ if (override.agent_profile_id !== undefined && (typeof override.agent_profile_id !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(override.agent_profile_id)))
172
+ throw new AppError("execution_profile_invalid", "Work profile id is invalid", 2);
173
+ if (override.delegation !== undefined)
174
+ validateDelegation(override.delegation);
175
+ }
60
176
  function nonempty(value) { return typeof value === "string" && Boolean(value.trim()); }
61
177
  function digest(value) { return crypto.createHash("sha256").update(JSON.stringify(value)).digest("hex"); }
@@ -3,11 +3,12 @@ import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { ensureWorkLaunchStorage } from "../storage/database.js";
5
5
  import { AppError } from "../shared/errors.js";
6
- import { adapterSessionId, runHarnessAdapter } from "./harness-adapter.js";
6
+ import { runtimeBudgetForRun } from "./runtime-budget.js";
7
+ import { adapterSessionId, prepareHarnessFlowExecutable, runHarnessAdapter } from "./harness-adapter.js";
7
8
  import { harnessRuntimeArguments, resolveHarnessCommand } from "./harness-config.js";
8
- import { resolveExecutionPolicy, runtimeHarness } from "./execution-policy.js";
9
+ import { resolveExecutionPolicy, runtimeHarness, validateExecutionOverride } from "./execution-policy.js";
9
10
  import { assertRunMutationAllowed } from "./run-recovery.js";
10
- import { workStartCommand } from "./work-registry.js";
11
+ import { assertWorkLaunchReady, workStartCommand } from "./work-registry.js";
11
12
  /**
12
13
  * Creates an external provider root for one already-created Work. The launch
13
14
  * is not a Work claim: only the worker's trusted `work start` hook binds it.
@@ -17,18 +18,27 @@ export async function launchExternalWork(context, input) {
17
18
  const work = context.db.get("SELECT work_id, project_id, run_id, status, payload_json FROM works WHERE work_id = ?", [input.workId]);
18
19
  if (!work)
19
20
  throw new AppError("not_found", "Work was not found", 1, { work_id: input.workId });
20
- if (work.status !== "created")
21
- throw new AppError("invalid_work_state", "Only created Work can be launched externally", 2, { work_id: work.work_id, status: work.status });
22
- const run = context.db.get("SELECT run_root, workspace_root, index_json FROM runs WHERE project_id = ? AND id = ?", [work.project_id, work.run_id]);
21
+ const run = context.db.get("SELECT run_root, project_root, workspace_root, index_json, status FROM runs WHERE project_id = ? AND id = ?", [work.project_id, work.run_id]);
23
22
  if (!run?.run_root)
24
23
  throw new AppError("runtime_missing", "Work has no materialized RUN root", 1, { work_id: work.work_id });
25
24
  const existing = context.db.get("SELECT operation_id, status, provider_session_id, profile_hash, journal_path FROM work_launches WHERE work_id = ?", [work.work_id]);
26
25
  if (existing)
27
26
  return { ok: true, work_id: work.work_id, operation_id: existing.operation_id, status: existing.status, provider_session_id: existing.provider_session_id, profile_hash: existing.profile_hash, journal_path: existing.journal_path, idempotent: true };
27
+ assertWorkLaunchReady(context, work.work_id);
28
+ if (run.status !== "running")
29
+ throw new AppError("invalid_run_state", "External Work dispatch requires a running RUN", 2, { run_id: work.run_id, status: run.status });
28
30
  const generation = assertRunMutationAllowed(context, work.project_id, work.run_id);
29
31
  const snapshot = JSON.parse(run.index_json);
32
+ if (!snapshot.execution_profile?.agent_profiles)
33
+ throw new AppError("execution_profile_not_frozen", "External dispatch requires a RUN with frozen agent profiles; recreate the RUN with the current engine", 1, { work_id: work.work_id });
30
34
  const override = workOverride(work.payload_json);
31
- const resolved = resolveExecutionPolicy({ ddFlowHome: context.ddFlowHome, policy: snapshot.execution_profile?.settings?.execution ?? {}, ...(input.stage ? { stage: input.stage } : {}), ...(override ? { work: override } : {}) });
35
+ const policy = snapshot.execution_profile?.settings?.execution ?? {};
36
+ const stage = snapshot.current_stage;
37
+ if (input.stage !== undefined && input.stage !== stage)
38
+ throw new AppError("execution_stage_mismatch", "Requested stage does not match the RUN's current stage", 2, { requested_stage: input.stage, current_stage: stage ?? null });
39
+ if (!stage && Object.keys(policy.stage_overrides ?? {}).length)
40
+ throw new AppError("execution_stage_missing", "Stage overrides require an authoritative RUN stage", 1, { run_id: work.run_id });
41
+ const resolved = resolveExecutionPolicy({ ddFlowHome: context.ddFlowHome, policy, profiles: snapshot.execution_profile.agent_profiles, ...(stage ? { stage } : {}), ...(override ? { work: override } : {}) });
32
42
  if (resolved.delegation.mode !== "external")
33
43
  throw new AppError("external_launch_not_selected", "This Work's frozen policy selects native delegation", 2, { work_id: work.work_id });
34
44
  const operationId = `OPR-${crypto.randomUUID()}`;
@@ -36,19 +46,27 @@ export async function launchExternalWork(context, input) {
36
46
  const journal = path.join(root, "adapter.events.jsonl");
37
47
  fs.mkdirSync(root, { recursive: true });
38
48
  const now = context.now();
39
- context.db.run("INSERT INTO work_launches (operation_id, work_id, profile_json, profile_hash, status, state_dir, journal_path, provider_session_id, adapter_receipt_json, created_at, updated_at) VALUES (?, ?, ?, ?, 'launching', ?, ?, NULL, NULL, ?, ?)", [operationId, work.work_id, JSON.stringify(resolved), resolved.profile_hash, root, journal, now, now]);
49
+ const flowExecutable = prepareHarnessFlowExecutable(context, root);
50
+ const runtimeContext = { ...context, env: { ...context.env, DD_FLOW_BIN: flowExecutable } };
51
+ const runtimeOwner = { schema_id: "dd-flow/runtime-owner@1", owner_id: `run:${work.run_id}:work:${work.work_id}`, role: "worker", project_id: work.project_id, run_id: work.run_id, work_id: work.work_id, operation_id: operationId, state_dir: root, dd_flow_home: context.ddFlowHome, dd_flow_bin: flowExecutable, resource_home: context.env.DD_FLOW_RESOURCE_HOME ?? context.ddFlowHome, budget: runtimeBudgetForRun(context, work.project_id, work.run_id) };
40
52
  const executable = resolveHarnessCommand(context.ddFlowHome, runtimeHarness(resolved.worker), "adapter");
41
- const common = ["--state-dir", root, "--journal", journal, "--cwd", run.workspace_root, "--provider", resolved.worker.provider, "--model", resolved.worker.model, "--reasoning", resolved.worker.reasoning, "--mode", resolved.worker.mode, "--permission", resolved.worker.permission, "--project-root", run.workspace_root, "--dd-flow-home", context.ddFlowHome, ...harnessRuntimeArguments(context.ddFlowHome, runtimeHarness(resolved.worker)), "--json"];
53
+ context.db.run("INSERT INTO work_launches (operation_id, work_id, profile_json, profile_hash, status, state_dir, journal_path, provider_session_id, adapter_receipt_json, created_at, updated_at) VALUES (?, ?, ?, ?, 'launching', ?, ?, NULL, NULL, ?, ?)", [operationId, work.work_id, JSON.stringify({ ...resolved, runtime_adapter: { executable, owner: runtimeOwner } }), resolved.profile_hash, root, journal, now, now]);
54
+ const common = ["--dd-flow-bin", flowExecutable, "--state-dir", root, "--journal", journal, "--cwd", run.workspace_root, "--provider", resolved.worker.provider, "--model", resolved.worker.model, "--reasoning", resolved.worker.reasoning, "--mode", resolved.worker.mode, "--permission", resolved.worker.permission, "--project-root", run.project_root, "--dd-flow-home", context.ddFlowHome, ...harnessRuntimeArguments(context.ddFlowHome, runtimeHarness(resolved.worker)), "--json"];
42
55
  const evidence = (value) => fs.appendFileSync(journal, `${JSON.stringify({ type: "adapter_call", at: context.now(), ...value })}\n`);
43
56
  try {
44
- const call = (args, timeoutMs) => runHarnessAdapter({ executable, args, env: context.env, ...(timeoutMs ? { timeoutMs } : {}), ...(input.progress ? { progress: input.progress } : {}), onEvidence: evidence });
57
+ const call = (args, timeoutMs) => runHarnessAdapter({ executable, args, env: runtimeContext.env, operationId: `${operationId}:${args[0]}.${args[1]}`, runtimeOwner, ...(timeoutMs ? { timeoutMs } : {}), ...(input.progress ? { progress: input.progress } : {}), onEvidence: evidence });
45
58
  await call(["daemon", "start", ...common]);
59
+ assertRunMutationAllowed(context, work.project_id, work.run_id, generation);
46
60
  const created = await call(["session", "create", ...common]);
47
61
  const sessionId = adapterSessionId(created);
48
62
  if (!sessionId)
49
63
  throw new AppError("harness_adapter_invalid", "Harness adapter did not return a typed Session id", 1, { work_id: work.work_id, harness: runtimeHarness(resolved.worker) });
64
+ // Retain the physical root before any subsequent guard or prompt can fail.
65
+ context.db.run("UPDATE work_launches SET provider_session_id = ?, adapter_receipt_json = ?, updated_at = ? WHERE operation_id = ?", [sessionId, JSON.stringify({ create: created }), context.now(), operationId]);
66
+ assertRunMutationAllowed(context, work.project_id, work.run_id, generation);
67
+ assertWorkLaunchReady(context, work.work_id);
50
68
  const promptFile = path.join(root, "launch.md");
51
- fs.writeFileSync(promptFile, `Start the assigned Work now. Your first tool call must be this exact standalone command:\n\n${workStartCommand(context, work)}\n\nUse the packet returned by that command. Do not claim another Work or run this command more than once.\n`);
69
+ 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`);
52
70
  const prompted = await call(["session", "prompt", "--session-id", sessionId, "--prompt-file", promptFile, ...common], 45 * 60_000);
53
71
  assertRunMutationAllowed(context, work.project_id, work.run_id, generation);
54
72
  context.db.run("UPDATE work_launches SET status = 'start_requested', provider_session_id = ?, adapter_receipt_json = ?, updated_at = ? WHERE operation_id = ?", [sessionId, JSON.stringify({ create: created, prompt: prompted }), context.now(), operationId]);
@@ -72,5 +90,8 @@ function workOverride(payload) {
72
90
  if (!value || typeof value !== "object" || Array.isArray(value))
73
91
  return undefined;
74
92
  const execution = value.execution_override;
75
- return execution && typeof execution === "object" && !Array.isArray(execution) ? execution : undefined;
93
+ if (execution === undefined)
94
+ return undefined;
95
+ validateExecutionOverride(execution);
96
+ return execution;
76
97
  }
@@ -1,12 +1,59 @@
1
1
  import { spawn } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { fileURLToPath, pathToFileURL } from "node:url";
2
5
  import { AppError } from "../shared/errors.js";
3
6
  const maxAdapterOutputBytes = 1_000_000;
7
+ /** Native hooks need a real executable, not a PATH-dependent shell expression. */
8
+ export function prepareHarnessFlowExecutable(context, stateDir) {
9
+ const configured = context.env.DD_FLOW_BIN;
10
+ if (configured !== undefined) {
11
+ if (!path.isAbsolute(configured) || !fs.existsSync(configured))
12
+ throw new AppError("harness_runtime_missing", "DD_FLOW_BIN must identify an existing absolute executable", 2);
13
+ if (path.basename(configured) === "dd-flow")
14
+ return configured;
15
+ }
16
+ const isolated = path.join(context.ddFlowHome, "bin", "dd-flow");
17
+ if (!configured && fs.existsSync(isolated))
18
+ return isolated;
19
+ const entry = fileURLToPath(new URL("../cli.js", import.meta.url));
20
+ if (!configured && !fs.existsSync(entry))
21
+ throw new AppError("harness_runtime_unbuilt", "Build the CLI or supply its pinned DD_FLOW_BIN before launching a harness", 1);
22
+ // The trusted lifecycle parser recognizes this basename, including for a
23
+ // custom pinned executable. Do not broaden that parser to arbitrary scripts.
24
+ const executable = path.join(stateDir, "dd-flow");
25
+ const dispatch = configured
26
+ ? `const { spawnSync } = await import("node:child_process");\nconst child = spawnSync(${JSON.stringify(configured)}, process.argv.slice(2), { stdio: "inherit", env: process.env });\nif (child.error) throw child.error;\nif (child.signal) process.kill(process.pid, child.signal);\nprocess.exit(child.status ?? 1);\n`
27
+ : `await import(${JSON.stringify(pathToFileURL(entry).href)});\n`;
28
+ const contents = `#!${process.execPath}\nprocess.env.DD_FLOW_HOME = ${JSON.stringify(context.ddFlowHome)};\n${dispatch}`;
29
+ try {
30
+ fs.writeFileSync(executable, contents, { flag: "wx", mode: 0o700 });
31
+ }
32
+ catch (error) {
33
+ if (error.code !== "EEXIST" || fs.readFileSync(executable, "utf8") !== contents)
34
+ throw new AppError("harness_runtime_binding_conflict", "Launch runtime executable already has different content", 1, { path: executable });
35
+ }
36
+ return executable;
37
+ }
4
38
  /** Runs one harness command with the lifecycle guarantees shared by agent services. */
5
39
  export function runHarnessAdapter(input) {
6
40
  const timeoutMs = input.timeoutMs ?? 120_000;
41
+ if (!input.operationId.trim())
42
+ throw new AppError("operation_id_required", "Harness calls require a scoped operation id", 2);
43
+ // The current adapter transport uses this name; its identity is runtime-owned,
44
+ // never the operation or trusted daemon identity of the calling coordinator.
45
+ const env = { ...input.env, DD_EVAL_OPERATION_ID: input.operationId };
46
+ delete env.DD_FLOW_DAEMON_ID;
47
+ delete env.DD_FLOW_SCOPE_CONTROL_TOKEN;
48
+ delete env.DD_FLOW_RUNTIME_OWNER;
49
+ if (input.runtimeOwner) {
50
+ if (!path.isAbsolute(input.executable) || input.runtimeOwner.adapter_executable !== undefined && input.runtimeOwner.adapter_executable !== input.executable)
51
+ throw new AppError("harness_runtime_binding_conflict", "Owned harness call must use its frozen absolute adapter executable", 1);
52
+ env.DD_FLOW_RUNTIME_OWNER = JSON.stringify({ ...input.runtimeOwner, adapter_executable: input.executable });
53
+ }
7
54
  return new Promise((resolve, reject) => {
8
55
  const detached = process.platform !== "win32";
9
- const child = spawn(input.executable, input.args, { env: input.env, detached, stdio: ["ignore", "pipe", "pipe"] });
56
+ const child = spawn(input.executable, input.args, { env, ...(input.cwd ? { cwd: input.cwd } : {}), detached, stdio: ["ignore", "pipe", "pipe"] });
10
57
  let stdout = "";
11
58
  let stderr = "";
12
59
  let outputTooLarge = false;
@@ -21,7 +68,7 @@ export function runHarnessAdapter(input) {
21
68
  catch { /* The process already settled. */ } };
22
69
  const heartbeat = input.progress && input.progressMessage ? setInterval(() => input.progress?.(`${input.progressMessage}; waiting for adapter completion`), 15_000) : undefined;
23
70
  const timer = setTimeout(() => { timedOut = true; terminate("SIGTERM"); killTimer = setTimeout(() => terminate("SIGKILL"), 5_000); }, timeoutMs);
24
- const evidence = (value) => input.onEvidence?.({ executable: input.executable, args: input.args, ...value, at: new Date().toISOString() });
71
+ const evidence = (value) => input.onEvidence?.({ operation_id: input.operationId, executable: input.executable, args: input.args, ...value, at: new Date().toISOString() });
25
72
  const append = (current, chunk) => {
26
73
  if (Buffer.byteLength(current) + Buffer.byteLength(chunk) > maxAdapterOutputBytes) {
27
74
  outputTooLarge = true;
@@ -14,7 +14,7 @@ import { registerProject, requireProjectByRoot } from "./projects.js";
14
14
  import { activeFlowSessionsForProject, bindObservedFlowSession, flowSessionPayloadFromRegisterCommand, recordFlowSessionObservation } from "./sessions.js";
15
15
  const defaultProfileName = "default";
16
16
  const maxSanitizedSummaryLength = 1600;
17
- const sharedEntries = [
17
+ export const codexHomeSharedEntries = [
18
18
  "auth.json",
19
19
  "auth.bk",
20
20
  "sessions",
@@ -51,7 +51,7 @@ export function planCodexHome(context, input) {
51
51
  config_path: paths.configPath,
52
52
  hooks_path: paths.hooksPath,
53
53
  owned_files: ["config.toml", "hooks.json", ".dd-flow-home.json", "log/", ".tmp/", "tmp/"],
54
- shared_entries: sharedEntries,
54
+ shared_entries: codexHomeSharedEntries,
55
55
  exists: fs.existsSync(paths.targetHome),
56
56
  codex_env: { CODEX_HOME: paths.targetHome }
57
57
  };
@@ -1118,7 +1118,7 @@ function objectTable(value) {
1118
1118
  function createSharedLinks(sourceHome, targetHome) {
1119
1119
  const linked = [];
1120
1120
  const skipped = [];
1121
- for (const entry of sharedEntries) {
1121
+ for (const entry of codexHomeSharedEntries) {
1122
1122
  const source = path.join(sourceHome, entry);
1123
1123
  const target = path.join(targetHome, entry);
1124
1124
  if (!fs.existsSync(source)) {
@@ -1141,7 +1141,7 @@ function createSharedLinks(sourceHome, targetHome) {
1141
1141
  return { linked, skipped };
1142
1142
  }
1143
1143
  function removeSharedSymlinks(targetHome) {
1144
- for (const entry of sharedEntries) {
1144
+ for (const entry of codexHomeSharedEntries) {
1145
1145
  const target = path.join(targetHome, entry);
1146
1146
  if (fs.existsSync(target) && fs.lstatSync(target).isSymbolicLink()) {
1147
1147
  fs.rmSync(target);
@@ -1154,7 +1154,7 @@ function removeDirIfEmpty(dir) {
1154
1154
  }
1155
1155
  }
1156
1156
  function sharedEntryStatus(sourceHome, targetHome) {
1157
- return sharedEntries.map((entry) => {
1157
+ return codexHomeSharedEntries.map((entry) => {
1158
1158
  const source = path.join(sourceHome, entry);
1159
1159
  const target = path.join(targetHome, entry);
1160
1160
  return {
@@ -0,0 +1,38 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { AppError } from "../shared/errors.js";
4
+ import { managedProcessRuntimeHome, managedProcessStatus } from "./managed-processes.js";
5
+ export function readNativeDaemonState(stateDir) {
6
+ if (!path.isAbsolute(stateDir))
7
+ throw new AppError("daemon_owner_mismatch", "Native state directory must be absolute", 1);
8
+ return JSON.parse(fs.readFileSync(path.join(stateDir, "daemon.json"), "utf8"));
9
+ }
10
+ /** A shared directory or logical owner is not a physical provider binding. */
11
+ export function managedProviderBelongsToDaemon(provider, daemon, state) {
12
+ const child = JSON.parse(provider.metadata_json), parent = JSON.parse(daemon.metadata_json);
13
+ return provider.kind === daemon.kind.replace(/-daemon$/, "-provider")
14
+ && state.provider_process?.id === provider.id && state.provider_process.lease_token === provider.lease_token
15
+ && Boolean(provider.pid && provider.pid_started_at && daemon.pid && daemon.pid_started_at)
16
+ && state.provider_pid === provider.pid && child.owner_pid === daemon.pid && child.owner_pid_started_at === daemon.pid_started_at
17
+ && provider.owner_id === daemon.owner_id && provider.project_id === daemon.project_id && provider.run_id === daemon.run_id && provider.work_id === daemon.work_id
18
+ && child.state_dir === parent.state_dir && child.role === parent.role && child.dd_flow_home === parent.dd_flow_home && child.adapter_executable === parent.adapter_executable
19
+ && child.dispatch_barrier === "run-generation@1" && child.budget?.scope_id === parent.budget?.scope_id;
20
+ }
21
+ /** Identity proof only. Liveness and clean shutdown are separate caller decisions. */
22
+ export function requireManagedDaemonBinding(context, input) {
23
+ const state = readNativeDaemonState(input.stateDir);
24
+ const owner = state.config?.runtime_owner;
25
+ const record = managedProcessStatus(context).find(record => record.id === state.resource_process?.id);
26
+ const metadata = record && JSON.parse(record.metadata_json);
27
+ if (!record || !owner || !record.kind.endsWith("-daemon")
28
+ || record.lease_token !== state.resource_process?.lease_token
29
+ || !record.pid || record.pid !== state.pid || !record.pid_started_at
30
+ || record.owner_id !== owner.owner_id || input.ownerId !== undefined && record.owner_id !== input.ownerId
31
+ || record.project_id !== input.projectId || owner.project_id !== input.projectId
32
+ || record.run_id !== input.runId || owner.run_id !== input.runId
33
+ || metadata?.state_dir !== input.stateDir || owner.state_dir !== input.stateDir
34
+ || owner.dd_flow_home !== context.ddFlowHome || managedProcessRuntimeHome(record, context) !== context.ddFlowHome) {
35
+ throw new AppError("daemon_owner_mismatch", "Native daemon has no proven physical ownership in this RUN and runtime home", 1);
36
+ }
37
+ return { state, process: record };
38
+ }
@@ -1,12 +1,47 @@
1
1
  import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
2
4
  import { spawnSync } from "node:child_process";
3
5
  import net from "node:net";
4
6
  import { getResourceDatabase } from "../storage/database.js";
7
+ import { AppError } from "../shared/errors.js";
8
+ export function managedProcessBinding(record, home) {
9
+ return { resource_home: home, process_id: record.id, registration_sha256: crypto.createHash("sha256").update(JSON.stringify([home, record.id, record.kind, record.owner_id, record.lease_token, record.pid, record.pid_started_at, record.project_id, record.run_id, record.work_id, record.check_id, record.operation_id, record.metadata_json])).digest("hex") };
10
+ }
5
11
  const defaultLeaseMs = 15 * 60_000;
6
12
  export function resourceHome(context) {
7
13
  return context.env?.DD_FLOW_RESOURCE_HOME ?? context.ddFlowHome ?? "/tmp/dd-flow-runtime";
8
14
  }
15
+ /** Runtime IDs are home-local; a shared host registry needs this extra scope. */
16
+ export function managedProcessRuntimeHome(record, authority) {
17
+ const metadata = parseMetadata(record.metadata_json);
18
+ if (metadata.dd_flow_home !== undefined)
19
+ return typeof metadata.dd_flow_home === "string" && path.isAbsolute(metadata.dd_flow_home) ? metadata.dd_flow_home : null;
20
+ // Older native registrations can still prove their home from the retained
21
+ // exact daemon owner; PID or project/RUN labels alone never establish it.
22
+ if (typeof metadata.state_dir === "string" && path.isAbsolute(metadata.state_dir)) {
23
+ try {
24
+ const state = JSON.parse(fs.readFileSync(path.join(metadata.state_dir, "daemon.json"), "utf8"));
25
+ const owner = state.config?.runtime_owner;
26
+ if (owner?.owner_id === record.owner_id && owner.project_id === record.project_id && owner.run_id === record.run_id && owner.state_dir === metadata.state_dir && typeof owner.dd_flow_home === "string" && path.isAbsolute(owner.dd_flow_home))
27
+ return owner.dd_flow_home;
28
+ }
29
+ catch { /* No retained native scope proof. */ }
30
+ }
31
+ if (record.kind === "run-controller" && authority.db.get("SELECT 1 FROM sqlite_schema WHERE name = 'run_controllers'") && authority.db.get("SELECT 1 FROM run_controllers WHERE controller_id = ? AND process_id = ? AND process_lease_token = ? AND project_id = ? AND run_id = ?", [record.owner_id, record.id, record.lease_token, record.project_id, record.run_id]))
32
+ return authority.ddFlowHome;
33
+ if (record.kind === "run-control" && authority.db.get("SELECT 1 FROM sqlite_schema WHERE name = 'run_control_workers'") && authority.db.get("SELECT 1 FROM run_control_workers w JOIN run_controls c ON c.control_id = w.control_id WHERE w.control_id = ? AND w.process_id = ? AND w.process_lease_token = ? AND c.project_id = ? AND c.run_id = ?", [record.owner_id, record.id, record.lease_token, record.project_id, record.run_id]))
34
+ return authority.ddFlowHome;
35
+ return null;
36
+ }
9
37
  export function registerManagedProcess(context, input) {
38
+ const observer = input.kind === "eval-observer";
39
+ const observerBirth = observer && input.pid ? processStartedAt(input.pid) : null;
40
+ if (observer) {
41
+ if (!Number.isSafeInteger(input.pid) || !input.pid || input.pid < 1 || !observerBirth || input.ownerPid !== undefined && input.ownerPid !== input.pid || input.processGroupId !== undefined && input.processGroupId !== input.pid)
42
+ throw new AppError("process_ownership_unknown", "EVAL observer registration requires its live self-owned process identity", 1);
43
+ input = { ...input, ownerPid: input.pid, uniqueActiveOperation: true };
44
+ }
10
45
  if (!input.uniqueActiveOperation)
11
46
  return insertManagedProcess(context, input);
12
47
  if (!input.operationId)
@@ -16,8 +51,13 @@ export function registerManagedProcess(context, input) {
16
51
  try {
17
52
  const existing = db.get("SELECT * FROM managed_processes WHERE operation_id = ? AND state IN ('starting','running','stopping','orphaned')", [input.operationId]);
18
53
  if (existing)
19
- throw Object.assign(new Error("The operation already owns a managed process; observe it instead of spawning a duplicate"), { code: "managed_operation_in_progress", details: { process_id: existing.id, operation_id: input.operationId } });
20
- const result = insertManagedProcess(context, input);
54
+ throw new AppError("managed_operation_in_progress", "The operation already owns a managed process; observe it instead of spawning a duplicate", 1, { process_id: existing.id, operation_id: input.operationId });
55
+ let result = insertManagedProcess(context, input);
56
+ if (observer) {
57
+ result = confirmManagedProcess(context, { id: result.id, leaseToken: result.lease_token, pid: input.pid, ...(input.processGroupId ? { processGroupId: input.processGroupId } : {}) });
58
+ if (result.pid_started_at !== observerBirth || !processIsAlive(result))
59
+ throw new AppError("process_ownership_unknown", "EVAL observer changed before registration committed", 1);
60
+ }
21
61
  db.exec("COMMIT");
22
62
  return result;
23
63
  }
@@ -33,7 +73,7 @@ function insertManagedProcess(context, input) {
33
73
  const token = crypto.randomUUID();
34
74
  db.run(`INSERT INTO managed_processes
35
75
  (id, kind, pid, pid_started_at, owner_id, lease_token, lease_expires_at, project_id, run_id, work_id, check_id, operation_id, stdout_path, stderr_path, state, started_at, updated_at, finished_at, termination_reason, metadata_json)
36
- VALUES (?, ?, NULL, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'starting', ?, ?, NULL, NULL, ?)`, [id, input.kind, input.ownerId, token, leaseExpiry(now, input.leaseMs), input.projectId ?? null, input.runId ?? null, input.workId ?? null, input.checkId ?? null, input.operationId ?? null, input.stdoutPath ?? null, input.stderrPath ?? null, now, now, JSON.stringify({ ...input.metadata, ...(input.ownerPid ? { owner_pid: input.ownerPid, owner_pid_started_at: processStartedAt(input.ownerPid) } : {}) })]);
76
+ VALUES (?, ?, NULL, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'starting', ?, ?, NULL, NULL, ?)`, [id, input.kind, input.ownerId, token, leaseExpiry(now, input.leaseMs), input.projectId ?? null, input.runId ?? null, input.workId ?? null, input.checkId ?? null, input.operationId ?? null, input.stdoutPath ?? null, input.stderrPath ?? null, now, now, JSON.stringify({ ...input.metadata, dd_flow_home: context.ddFlowHome, ...(input.ownerPid ? { owner_pid: input.ownerPid, owner_pid_started_at: processStartedAt(input.ownerPid) } : {}) })]);
37
77
  return requireProcess(db, id);
38
78
  }
39
79
  export function confirmManagedProcess(context, input) {
@@ -67,7 +107,11 @@ export function finishManagedProcess(context, input) {
67
107
  const updated = db.run("UPDATE managed_processes SET state = ?, termination_reason = ?, finished_at = ?, updated_at = ?, lease_expires_at = ? WHERE id = ? AND lease_token = ? AND state IN ('starting','running','stopping','orphaned')", [input.state, input.reason ?? null, now, now, now, input.id, input.leaseToken]).changes === 1;
68
108
  if (updated)
69
109
  db.run("DELETE FROM managed_resources WHERE process_id = ?", [input.id]);
70
- return updated;
110
+ if (updated)
111
+ return true;
112
+ // Another client may have completed the same lease after our initial read.
113
+ const current = requireProcess(db, input.id);
114
+ return current.lease_token === input.leaseToken && current.state === input.state;
71
115
  }
72
116
  export function processIsAlive(record) {
73
117
  if (!record.pid)
@@ -140,18 +184,21 @@ export async function stopManagedProcess(context, input) {
140
184
  throw new Error("Managed process lease does not match");
141
185
  if (["stopped", "failed"].includes(record.state) && !processTreeIsAlive(record))
142
186
  return record;
187
+ if (!record.pid || !record.pid_started_at)
188
+ throw Object.assign(new Error("Managed process has no confirmed physical identity; retain its pending launch"), { code: "process_identity_unknown" });
143
189
  db.run("UPDATE managed_processes SET state = 'stopping', updated_at = ? WHERE id = ? AND lease_token = ? AND state IN ('starting','running','orphaned')", [context.now(), record.id, record.lease_token]);
144
190
  if (processTreeIsAlive(record)) {
145
191
  terminateOwnedProcess(record, "SIGTERM");
146
192
  await delay(input.graceMs ?? 1_000);
147
- if (processTreeIsAlive(record)) {
193
+ if (input.force !== false && processTreeIsAlive(record)) {
148
194
  terminateOwnedProcess(record, "SIGKILL");
149
195
  await delay(250);
150
196
  }
151
197
  if (processTreeIsAlive(record))
152
198
  throw Object.assign(new Error("Owned process tree could not be safely stopped; resources retained"), { code: "process_tree_not_settled", details: { process_id: record.id } });
153
199
  }
154
- finishManagedProcess(context, { id: record.id, leaseToken: record.lease_token, state: "stopped", reason: input.reason ?? "requested_stop" });
200
+ if (!finishManagedProcess(context, { id: record.id, leaseToken: record.lease_token, state: "stopped", reason: input.reason ?? "requested_stop" }))
201
+ throw Object.assign(new Error("Managed process ownership changed before settlement"), { code: "process_lease_lost" });
155
202
  return requireProcess(db, record.id);
156
203
  }
157
204
  /** Reconcile only records the system owns and has atomically claimed. */
@@ -206,7 +253,7 @@ function registry(context) { return getResourceDatabase(resourceHome(context));
206
253
  function requireProcess(db, id) { const row = db.get("SELECT * FROM managed_processes WHERE id = ?", [id]); if (!row)
207
254
  throw new Error(`Managed process is missing: ${id}`); return row; }
208
255
  function leaseExpiry(now, leaseMs = defaultLeaseMs) { return new Date(Date.parse(now) + leaseMs).toISOString(); }
209
- function processStartedAt(pid) { const result = spawnSync("ps", ["-o", "lstart=", "-p", String(pid)], { encoding: "utf8" }); const value = result.status === 0 ? result.stdout.trim() : ""; return value || null; }
256
+ export function processStartedAt(pid) { const result = spawnSync("ps", ["-o", "lstart=", "-p", String(pid)], { encoding: "utf8" }); const value = result.status === 0 ? result.stdout.trim() : ""; return value || null; }
210
257
  function releasePortClaims(db, claims) { for (const claim of claims)
211
258
  db.run("DELETE FROM managed_resources WHERE resource_kind = 'port' AND resource_key = ? AND lease_token = ?", [claim.key, claim.token]); }
212
259
  function terminateOwnedProcess(record, signal) {