@deksden-com/dd-flow-cli 0.9.0-beta.62 → 0.9.0-beta.63

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 CHANGED
@@ -1,5 +1,12 @@
1
1
  # @deksden-com/dd-flow-cli
2
2
 
3
+ ## 0.9.0-beta.63
4
+
5
+ ### Patch Changes
6
+
7
+ - Accept the qualified retained-subagent ZCode bridge and settle a control only
8
+ when a durable startup rejection proves that it created no native resources.
9
+
3
10
  ## 0.9.0-beta.62
4
11
 
5
12
  ### Patch Changes
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "cli_package": "@deksden-com/dd-flow-cli",
3
- "cli_version": "0.9.0-beta.62",
4
- "cli_commit": "a496c8a3b9781cd1808bd4a49fe314f0ef17c52c",
5
- "built_at": "2026-09-14T08:57:06.311Z",
3
+ "cli_version": "0.9.0-beta.63",
4
+ "cli_commit": "4975ba184bd847fd34db995cd3d9e1feb3db9f9f",
5
+ "built_at": "2026-09-14T10:36:19.793Z",
6
6
  "built_with_canon": {
7
7
  "version": "4.1.1",
8
8
  "commit": "97f811d33c212ae3497020178b1ed825c7c3ebac",
@@ -154,7 +154,7 @@ export async function startDaemon(options) {
154
154
  await removeSocket(paths.socket, paths.dir);
155
155
  const versions = await doctor({ bin: config.bin, zcodePath: config.zcodePath });
156
156
  if (hasFlowContext && versions.compatible !== true) {
157
- throw new DaemonError("zcode_lifecycle_unqualified", "ZCode native event identity is not qualified for managed CLI admission; inspect lifecycle_qualification before starting E2E.", false, { observed_runtime: versions.observed_runtime, lifecycle_qualification: versions.lifecycle_qualification });
157
+ throw new DaemonError("zcode_lifecycle_unqualified", "ZCode native event identity is not qualified for managed CLI admission; inspect lifecycle_qualification before starting E2E.", false, { startup_resources: "not_created", observed_runtime: versions.observed_runtime, lifecycle_qualification: versions.lifecycle_qualification });
158
158
  }
159
159
  const resourceProcess = await registerDaemonProcess(config, {
160
160
  kind: "zcode-daemon", owner: `zcode:${path.basename(paths.dir)}`, operation: `zcode-daemon:${path.basename(paths.dir)}`,
@@ -681,8 +681,9 @@ async function forwardUsage(options, providerSessionId, usage, toolCalls) {
681
681
  export function zcodeLifecycleQualification(sha256, bridgeCommit, harnessContract) {
682
682
  // Behavioural evidence: dd-eval production-invocations probe, 2026-09-13.
683
683
  // Pin both providers of native event identity, not a string in the bundle.
684
+ // 60af0d3 adds retainedSubagents without changing native event forwarding.
684
685
  const qualified = sha256 === "e9f1868c0fdb863537ed910ee3828b9be96b8c2fd805473f63b439e1113266b8"
685
- && bridgeCommit === "e0600fe3c46e215695f257e65aa6f674ced998d8" && harnessContract === ZCODE_HARNESS_CONTRACT;
686
+ && ["e0600fe3c46e215695f257e65aa6f674ced998d8", "60af0d31e13076a313d9770f10aa70f7c94742cf"].includes(bridgeCommit) && harnessContract === ZCODE_HARNESS_CONTRACT;
686
687
  return { status: qualified ? "qualified" : "unqualified", mechanism: "cli-invocation@1", sha256,
687
688
  capabilities: qualified ? ["root", "concurrent_children", "child_continuation"] : [],
688
689
  native_nested_children: false, reason: qualified ? null : "native_event_contract_unqualified" };
@@ -413,10 +413,29 @@ async function reconcileOwnedSessions(context, input, interrupt, boundary) {
413
413
  }
414
414
  unresolved.push({ check_id: check.id, status: "pending", reason });
415
415
  }
416
+ const uncreatedDirectories = new Map();
416
417
  const sessionNodes = Promise.all([...targets.values()].map(async ({ session, executable, harness }) => {
417
418
  const env = { ...context.env, DD_FLOW_HOME: context.ddFlowHome };
418
419
  delete env.DD_FLOW_CONTROLLER_TOKEN;
419
420
  delete env.DD_FLOW_CONTROL_TOKEN;
421
+ // An explicit pre-registration rejection proves there is no Session to
422
+ // cancel. A missing id or an arbitrary startup failure does not.
423
+ if (!session.id && !session.create_operation_id && !daemonInventories.has(session.state_dir) && !unsafeDaemons.has(canonicalPath(session.state_dir, false)) && !fs.existsSync(path.join(session.state_dir, "daemon.json"))) {
424
+ const operations = context.db.all("SELECT o.operation_id, o.input_json FROM run_controller_operations o JOIN run_controllers c ON c.controller_id = o.controller_id WHERE c.project_id = ? AND c.run_id = ? AND json_extract(o.input_json, '$.state_dir') = ?", [project.id, input.runId, session.state_dir]);
425
+ if (operations.length === 1)
426
+ try {
427
+ const operation = operations[0];
428
+ const saved = JSON.parse(operation.input_json);
429
+ const journal = retainedDaemonOperations(session.state_dir);
430
+ const receipt = journal.length === 1 ? journal[0] : null;
431
+ const error = receipt?.error;
432
+ if (saved.executable === executable && saved.owner === session.owner.owner_id && saved.args?.[0] === "daemon" && saved.args?.[1] === "start" && receipt?.operation_id === operation.operation_id && receipt.operation === "daemon.start" && receipt.predecessor_daemon_id === null && receipt.state === "failed" && error?.details?.startup_resources === "not_created" && !registeredProcesses.some(({ record }) => JSON.parse(record.metadata_json).state_dir === session.state_dir)) {
433
+ uncreatedDirectories.set(session.state_dir, JSON.stringify(journal));
434
+ return { state_dir: session.state_dir, status: "completed", receipt: { source: "startup_resources_not_created", operation_id: operation.operation_id, error } };
435
+ }
436
+ }
437
+ catch { /* Incomplete evidence retains the normal unknown outcome. */ }
438
+ }
420
439
  if (!session.id && session.create_operation_id) {
421
440
  try {
422
441
  assertCurrent();
@@ -622,7 +641,7 @@ async function reconcileOwnedSessions(context, input, interrupt, boundary) {
622
641
  };
623
642
  bindings = readBindings();
624
643
  for (const target of targets.values())
625
- if (!daemonInventories.has(target.session.state_dir))
644
+ if (!daemonInventories.has(target.session.state_dir) && !uncreatedDirectories.has(target.session.state_dir))
626
645
  unresolved.push({ state_dir: target.session.state_dir, status: "pending", reason: "runtime_inventory_unverified" });
627
646
  for (const node of nodes)
628
647
  if ("harness" in node && "session_id" in node && typeof node.session_id === "string" && bindings.some(binding => foreign(binding) && underRoot(binding, node.session_id, node.harness ?? null)))
@@ -647,6 +666,9 @@ async function reconcileOwnedSessions(context, input, interrupt, boundary) {
647
666
  const processVersion = (items) => JSON.stringify(items.map(({ home, record }) => [home, record.id, record.kind, record.owner_id, record.project_id, record.run_id, record.work_id, record.check_id, record.operation_id, record.pid, record.pid_started_at, record.lease_token, record.state, record.metadata_json]).sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right))));
648
667
  verifyInventory = () => {
649
668
  assertCurrent();
669
+ for (const [directory, journal] of uncreatedDirectories)
670
+ if (fs.existsSync(path.join(directory, "daemon.json")) || JSON.stringify(retainedDaemonOperations(directory)) !== journal)
671
+ throw new AppError("owned_inventory_changed", "Rejected startup acquired new runtime evidence", 1);
650
672
  const currentProcesses = [...resourceHomes].flatMap(home => managedProcessStatus({ ...context, env: { ...context.env, DD_FLOW_RESOURCE_HOME: home } }).filter(record => record.project_id === project.id && record.run_id === input.runId && [null, context.ddFlowHome].includes(managedProcessRuntimeHome(record, context))).map(record => ({ home, record })));
651
673
  if (initialDatabaseVersion !== databaseVersion() || processVersion(registeredProcesses) !== processVersion(currentProcesses) || [...nativeVersions].some(([directory, version]) => version !== nativeInventoryVersion(directory)) || [...importVersions].some(([file, version]) => crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex") !== version))
652
674
  throw new AppError("owned_inventory_changed", "Owned writers changed after their settlement observation", 1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deksden-com/dd-flow-cli",
3
- "version": "0.9.0-beta.62",
3
+ "version": "0.9.0-beta.63",
4
4
  "description": "Mechanical runtime CLI for dd-flow workflows.",
5
5
  "type": "module",
6
6
  "bin": {