@deksden-com/dd-flow-cli 0.9.0-beta.145 → 0.9.0-beta.149

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 (101) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/dist/build-info.json +7 -7
  3. package/dist/cli/command-inputs.js +6 -2
  4. package/dist/cli/command-metadata.js +18 -2
  5. package/dist/cli/diagnostic-invocation.js +46 -0
  6. package/dist/cli/diagnostic.js +120 -0
  7. package/dist/cli/help.js +35 -0
  8. package/dist/cli/memory-flow-admission.js +4 -2
  9. package/dist/cli/memory-init.js +5 -2
  10. package/dist/cli/run-cli.js +16 -15
  11. package/dist/cli.js +6 -0
  12. package/dist/domain/diagnostic-aspects.js +103 -0
  13. package/dist/domain/diagnostic-flows.js +8 -0
  14. package/dist/harness-runtime/lib/agy-native-inspection.mjs +216 -0
  15. package/dist/harness-runtime/lib/agy-native_pb.js +117 -0
  16. package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
  17. package/dist/harness-runtime/lib/daemon-operations.mjs +84 -10
  18. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +306 -34
  19. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +27 -13
  20. package/dist/harness-runtime/lib/dd-codex.mjs +4 -0
  21. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +4 -3
  22. package/dist/harness-runtime/lib/dd-droid.mjs +70 -28
  23. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +3 -3
  24. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +66 -20
  25. package/dist/harness-runtime/lib/dd-opencode.mjs +30 -3
  26. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +7 -6
  27. package/dist/harness-runtime/lib/driver-recovery.mjs +9 -8
  28. package/dist/harness-runtime/lib/managed-daemon.d.mts +1 -1
  29. package/dist/harness-runtime/lib/managed-daemon.mjs +11 -3
  30. package/dist/harness-runtime/lib/native-children.d.mts +8 -1
  31. package/dist/harness-runtime/lib/native-children.mjs +114 -8
  32. package/dist/harness-runtime/lib/native-request-proof.mjs +1 -1
  33. package/dist/harness-runtime/lib/observation-clock.d.mts +2 -2
  34. package/dist/harness-runtime/lib/observation-clock.mjs +7 -4
  35. package/dist/harness-runtime/lib/provider-overload-policy.d.mts +1 -1
  36. package/dist/harness-runtime/lib/provider-overload-policy.mjs +1 -1
  37. package/dist/harness-runtime/lib/tool-observations.mjs +1 -1
  38. package/dist/schemas/diagnostic-stage-result.schema.json +354 -0
  39. package/dist/schemas/diagnostic-work-result.schema.json +296 -0
  40. package/dist/schemas/execution-bundle.schema.json +7 -1
  41. package/dist/schemas/project-flow-pack-manifest.schema.json +1 -1
  42. package/dist/services/check-environment.js +21 -0
  43. package/dist/services/check-runner-evidence.js +267 -15
  44. package/dist/services/code-checks.js +23 -15
  45. package/dist/services/controller-execution.js +10 -4
  46. package/dist/services/controller-fanout.js +13 -6
  47. package/dist/services/controller-memory-worker.js +3 -2
  48. package/dist/services/dashboard.js +2 -2
  49. package/dist/services/descriptor-run-settlement.js +5 -3
  50. package/dist/services/descriptor-run-start.js +22 -3
  51. package/dist/services/descriptor-stage-coordinator.js +37 -4
  52. package/dist/services/descriptor-stage-settlement.js +2 -1
  53. package/dist/services/diagnostic-evidence.js +73 -0
  54. package/dist/services/diagnostic-report-publication.js +72 -0
  55. package/dist/services/diagnostic-report.js +67 -0
  56. package/dist/services/diagnostic-run-bundle.js +74 -0
  57. package/dist/services/diagnostic-snapshot.js +159 -0
  58. package/dist/services/diagnostic-stage-handlers.js +108 -0
  59. package/dist/services/diagnostic-stage-results.js +132 -0
  60. package/dist/services/diagnostic-start.js +84 -0
  61. package/dist/services/diagnostic-work-batch.js +34 -0
  62. package/dist/services/diagnostic-work-collection.js +97 -0
  63. package/dist/services/diagnostic-work-registration.js +101 -0
  64. package/dist/services/diagnostic-work-results.js +93 -0
  65. package/dist/services/eval-snapshots.js +85 -29
  66. package/dist/services/execution-bundles.js +19 -7
  67. package/dist/services/flow-catalog.js +6 -1
  68. package/dist/services/frozen-engine.js +37 -2
  69. package/dist/services/hybrid-contracts.js +2 -0
  70. package/dist/services/installed-flow-source.js +25 -3
  71. package/dist/services/lifecycle-invocations.js +147 -34
  72. package/dist/services/memory-init-contexts.js +16 -0
  73. package/dist/services/memory-init-controller-start.js +12 -4
  74. package/dist/services/memory-init-lint-capability.js +5 -5
  75. package/dist/services/memory-init-native-capacity.js +4 -4
  76. package/dist/services/memory-init-preflight.js +10 -16
  77. package/dist/services/memory-init-worktrunk-capability.js +3 -7
  78. package/dist/services/memory-layer.js +4 -3
  79. package/dist/services/memory-tool-probe.js +25 -0
  80. package/dist/services/memory-verification-checks.js +15 -4
  81. package/dist/services/native-daemon-history.js +2 -1
  82. package/dist/services/provider-capacity.js +43 -38
  83. package/dist/services/release-cache.js +8 -0
  84. package/dist/services/run-control.js +2 -1
  85. package/dist/services/run-controller-adapter.js +2 -1
  86. package/dist/services/run-controller-process.js +2 -1
  87. package/dist/services/run-controller-recovery.js +2 -1
  88. package/dist/services/run-controller.js +85 -15
  89. package/dist/services/run-fork.js +1 -1
  90. package/dist/services/runs.js +6 -1
  91. package/dist/services/runtime-scope-resume.js +12 -6
  92. package/dist/services/runtime-scope-stop.js +3 -2
  93. package/dist/services/runtime-service.js +2 -1
  94. package/dist/services/stage-handlers.js +16 -3
  95. package/dist/services/stage-pause.js +4 -1
  96. package/dist/services/version-status.js +1 -1
  97. package/dist/services/vnext-code.js +2 -2
  98. package/dist/services/vnext-fanout.js +31 -1
  99. package/dist/services/work-registry.js +37 -6
  100. package/dist/services/workspace-bootstrap.js +2 -1
  101. package/package.json +6 -3
@@ -30,7 +30,7 @@ import {
30
30
  } from "./dd-zcode.mjs";
31
31
 
32
32
  const REQUEST_SCHEMA = "dd-zcode/daemon-request@1";
33
- import { settledNativeTree } from "./native-children.mjs";
33
+ import { settledNativeTree, nativeExecutionReceipt } from "./native-children.mjs";
34
34
  const RESPONSE_SCHEMA = "dd-zcode/daemon-response@1";
35
35
  const STATE_SCHEMA = "dd-zcode/daemon-state@1";
36
36
  // ACP silence starts diagnostics; it is not a provider-failure deadline.
@@ -549,7 +549,7 @@ export class DaemonRuntime {
549
549
  for (const receipt of [previous, closed]) {
550
550
  if (receipt?.close?.closed === true && receipt.settled === true
551
551
  && receipt.topology?.complete === true && receipt.topology.root_session_id === params.sessionId
552
- && Array.isArray(receipt.topology.child_session_ids)) return receipt;
552
+ && Array.isArray(receipt.topology.child_session_ids)) return nativeExecutionReceipt(receipt, { daemonId: this.state.daemon_id, generation: this.controlGeneration ?? 0 });
553
553
  }
554
554
  const known = [...new Set([...topology.child_session_ids, ...(previous?.topology?.child_session_ids ?? [])])];
555
555
  const emptyRoot = this.neverPromptedRootProof(params.sessionId);
@@ -559,7 +559,7 @@ export class DaemonRuntime {
559
559
  this.state.settlement_observations ??= {};
560
560
  this.state.settlement_observations[params.sessionId] = observation;
561
561
  await this.persist();
562
- return observation;
562
+ return nativeExecutionReceipt(observation, { daemonId: this.state.daemon_id, generation: this.controlGeneration ?? 0 });
563
563
  }
564
564
  const adapter = params.adapterSessionId ?? params.sessionId;
565
565
  if (!this.liveAdapters.has(adapter) && this.activeProductive) {
@@ -584,8 +584,8 @@ export class DaemonRuntime {
584
584
  const childIds = [...new Set([...(this.sessions.get(result.provider_session_id)?.known_child_session_ids ?? []), ...zcodeChildSessionIds(nativeChildren)])];
585
585
  const childStates = new Map([...(nativeChildren?.ended?.items ?? nativeChildren?.completed ?? []), ...(nativeChildren?.running ?? []).map(child => ({ ...child, status: "running" }))].map(child => [child.childSessionId ?? child.sessionId, child.status]));
586
586
  const rootSettled = !this.activeProductive && failure?.provider_session_id === result.provider_session_id && (result.evidence ?? result).read?.projection?.status === "idle";
587
- return { ...result, settled, ...settledNativeTree(result.provider_session_id, childIds.map(childSessionId => ({ childSessionId, status: childStates.get(childSessionId) ?? "unknown" })), settled, rootSettled),
588
- ...(failure?.provider_session_id === result.provider_session_id ? { terminal_turn_id: String(failure.request_id) } : {}) };
587
+ return nativeExecutionReceipt({ ...result, settled, ...settledNativeTree(result.provider_session_id, childIds.map(childSessionId => ({ childSessionId, status: childStates.get(childSessionId) ?? "unknown" })), settled, rootSettled),
588
+ ...(failure?.provider_session_id === result.provider_session_id ? { terminal_turn_id: String(failure.request_id) } : {}) }, { daemonId: this.state.daemon_id, generation: this.controlGeneration ?? 0 });
589
589
  }
590
590
  if (operation === "session.cancel") {
591
591
  if (!this.sessions.has(params.sessionId)) throw new DaemonError("session_identity_mismatch", "cancellation Session is not owned by this daemon");
@@ -604,6 +604,7 @@ export class DaemonRuntime {
604
604
  return result;
605
605
  }
606
606
  if (operation === "daemon.stop") return await completeDaemonShutdown(this.paths.dir, this.state, patch => this.persist(patch), () => this.prepareStop(params.cancelTree === true), [
607
+ ["provider", () => this.bridge.close?.()],
607
608
  ["provider_resource", () => finishDaemonProcess(this.state.config, this.state.provider_process)],
608
609
  ["daemon_resource", () => finishDaemonProcess(this.state.config, this.state.resource_process)]
609
610
  ]);
@@ -691,7 +692,7 @@ export class DaemonRuntime {
691
692
  try { if (!bridgeClosed && typeof this.bridge.close === "function") await this.bridge.close(); }
692
693
  catch (error) { throw new DaemonError(error.code ?? "provider_process_stop_incomplete", error.message ?? String(error), false, error.details); }
693
694
  await this.persist({ active_tree: false, shutdown_state: "stopping", active_operation: null, active_request: null, physical_settlement: { confirmed_at: new Date().toISOString(), bridge_pid: this.bridge.child?.pid ?? null } });
694
- return { daemon_id: this.state.daemon_id, stopped: true, clean: true, physical_settlement: this.state.physical_settlement, _shutdown: true };
695
+ return { daemon_id: this.state.daemon_id, stopped: true, clean: true, settled: true, native_settled: true, physical_settlement: this.state.physical_settlement, _shutdown: true };
695
696
  }
696
697
 
697
698
  }
@@ -6,22 +6,23 @@ import { writeJsonAtomic } from "./runner-events.mjs";
6
6
  import { reportedError } from "./operation-errors.mjs";
7
7
  import { ObservationClock } from "./observation-clock.mjs";
8
8
  import { runtimeProcess } from "./managed-daemon.mjs";
9
+ import { nativeShutdownConfirmed } from "./native-children.mjs";
9
10
 
10
- function cleanShutdownConfirmed(state) {
11
+ async function cleanShutdownConfirmed(root, state) {
11
12
  const proof = state.shutdown;
13
+ let fence;
14
+ try { fence = JSON.parse(await readFile(path.join(root, 'operation-fence.json'), 'utf8')); }
15
+ catch (error) { if (error.code !== 'ENOENT') throw error; }
12
16
  return state.shutdown_state === "clean" && state.active_tree === false
13
- && proof?.schema_id === "dd-flow/daemon-shutdown@1" && proof.daemon_id === state.daemon_id
14
- && proof.result?.clean === true && Array.isArray(proof.required_phases)
15
- && proof.required_phases.length >= 3 && proof.required_phases.includes("tree")
16
- && proof.required_phases.includes("daemon_resource")
17
- && proof.required_phases.every(phase => typeof phase === "string" && proof.phases?.[phase] === true);
17
+ && typeof state.daemon_id === 'string' && fence?.daemon_id === state.daemon_id && Number.isSafeInteger(fence.generation) && fence.generation >= 0
18
+ && nativeShutdownConfirmed(proof, { daemonId: state.daemon_id, generation: fence.generation });
18
19
  }
19
20
 
20
21
  /** Explicit same-Session restart retains native stores and the old daemon
21
22
  * receipt; it does not turn a terminal directory into a new execution. */
22
23
  export async function authorizeRetainedDaemonResume(root, previous, sessionId, config) {
23
24
  const sessions = [...(previous.sessions ?? []), ...(previous.root_session_id ? [previous.root_session_id] : [])];
24
- if (!cleanShutdownConfirmed(previous) || typeof sessionId !== "string" || !sessions.some(session => (typeof session === "string" ? session : session.provider_session_id) === sessionId)) throw Object.assign(new Error("terminal daemon restart requires a retained, settled Session identity and complete shutdown proof"), { code: "daemon_state_terminal" });
25
+ if (!await cleanShutdownConfirmed(root, previous) || typeof sessionId !== "string" || !sessions.some(session => (typeof session === "string" ? session : session.provider_session_id) === sessionId)) throw Object.assign(new Error("terminal daemon restart requires a retained, settled Session identity and complete shutdown proof"), { code: "daemon_state_terminal" });
25
26
  for (const key of ["cwd", "projectRoot", "ddFlowHome", "ddFlowBin", "provider", "model", "reasoning", "mode", "variant", "agent", "bin", "noFlow"]) {
26
27
  if ((previous.config?.[key] ?? null) !== (config[key] ?? null)) throw Object.assign(new Error(`retained daemon configuration changed: ${key}`), { code: "daemon_config_mismatch" });
27
28
  }
@@ -53,7 +54,7 @@ export async function recoverySettlement(driversRoot) {
53
54
  }
54
55
  else if (entry.name === "daemon.json") {
55
56
  const state = JSON.parse(await readFile(file, "utf8"));
56
- if (!cleanShutdownConfirmed(state)) throw Object.assign(new Error("provider tree has no complete clean shutdown receipt"), { code: "recovery_settlement_unconfirmed" });
57
+ if (!await cleanShutdownConfirmed(directory, state)) throw Object.assign(new Error("provider tree has no complete clean shutdown receipt"), { code: "recovery_settlement_unconfirmed" });
57
58
  await assertDaemonReplaceable(directory);
58
59
  await reconcileDriverReplies(directory);
59
60
  if (state.config?.resourceHome && state.resource_process?.id) {
@@ -10,7 +10,7 @@ export function heartbeatDaemonProcess(config: RuntimeConfig, record: ProcessBin
10
10
  export function finishDaemonProcess(config: RuntimeConfig, record: ProcessBinding | null | undefined, state?: string, reason?: string): Promise<void>;
11
11
  export function cleanupFailedDaemonStart(config: RuntimeConfig, record: ProcessBinding | null, child: ChildProcess | null, original: Error & { details?: Record<string, unknown> }, readState: () => Promise<Record<string, unknown>>, daemonId: string): Promise<void>;
12
12
  export function completeBudgetOperation(stateDir: string, request: { id: string; operation: string; params?: { sessionId?: string } }, result: unknown, observe?: () => Promise<Record<string, unknown>>): Promise<Record<string, unknown>>;
13
- export function stopProcessGroup(child: ChildProcess | null | undefined, graceMs?: number, options?: { detached?: boolean; settlementMs?: number }): Promise<void>;
13
+ export function stopProcessGroup(child: ChildProcess | { pid: number; started: string } | null | undefined, graceMs?: number, options?: { detached?: boolean; settlementMs?: number; started?: string }): Promise<void>;
14
14
  export function assertStartupChild(child: Pick<ChildProcess, "pid" | "exitCode" | "signalCode">, expected: Record<string, unknown>, observed: Record<string, unknown> | null, log: string): void;
15
15
  export function retainStartupFailure(input: { state: Record<string, unknown>; publish: (state: Record<string, unknown>) => Promise<unknown>; log: string; error: Error & { code?: string; details?: Record<string, unknown> } }): Promise<void>;
16
16
  export function startManagedBridge(input: { config: RuntimeConfig; state: Record<string, unknown>; bridge: { child: ChildProcess | null; options: { onSpawn?: (child: ChildProcess) => Promise<void> }; start: () => Promise<unknown> }; kind: string; publish: (state: Record<string, unknown>) => Promise<unknown>; log?: string }): Promise<unknown>;
@@ -5,6 +5,7 @@ import { commandJson } from "./process-json.mjs";
5
5
  import { errorRecord, reportedError, diagnosticJson } from "./operation-errors.mjs";
6
6
  import { writeJsonAtomic } from "./runner-events.mjs";
7
7
  import { runBoundedCommand } from "./native-hook-command.mjs";
8
+ import { processSnapshot } from "./process-snapshot.mjs";
8
9
  import { createRenewalState, maintenanceRetryable, renewalConfirmed, renewalDelay, renewalFailure, renewalRemaining, RENEWAL_POLICY } from "./lease-renewal.mjs";
9
10
 
10
11
  const leases = new Map();
@@ -598,14 +599,14 @@ export async function closeBridgeProcess(bridge, drain, stopOptions = {}) {
598
599
  finally { bridge.closePending = null; }
599
600
  }
600
601
 
601
- export async function stopProcessGroup(child, graceMs = 1_000, { detached = true, settlementMs = 10_000 } = {}) {
602
+ export async function stopProcessGroup(child, graceMs = 1_000, { detached = true, settlementMs = 10_000, started = child?.started ?? null } = {}) {
602
603
  if (!child?.pid) return;
603
604
  if (processStops.has(child)) return await processStops.get(child);
604
- const pending = stopOwnedChild(child, graceMs, detached, settlementMs);
605
+ const pending = stopOwnedChild(child, graceMs, detached, settlementMs, started);
605
606
  processStops.set(child, pending);
606
607
  try { return await pending; } finally { processStops.delete(child); }
607
608
  }
608
- async function stopOwnedChild(child, graceMs, detached, settlementMs) {
609
+ async function stopOwnedChild(child, graceMs, detached, settlementMs, started) {
609
610
  const target = process.platform === "win32" || !detached ? child.pid : -child.pid;
610
611
  const details = signal => ({ pid: child.pid, pgid: target < 0 ? child.pid : null, signal, platform: process.platform, exit_code: child.exitCode ?? null, signal_code: child.signalCode ?? null, observed_at: new Date().toISOString() });
611
612
  const alive = () => { try { process.kill(target, 0); return true; } catch (error) { if (error.code === "ESRCH") return false; if (error.code === "EPERM") return true; throw error; } };
@@ -615,6 +616,13 @@ async function stopOwnedChild(child, graceMs, detached, settlementMs) {
615
616
  return !alive();
616
617
  };
617
618
  const signal = async name => {
619
+ // A retained raw PID is not a ChildProcess handle. Revalidate its birth at
620
+ // every signal, including escalation after the TERM grace period.
621
+ if (started !== null) {
622
+ if (typeof started !== 'string' || !started) throw Object.assign(new Error('process birth identity is invalid'), { code: 'process_identity_unproven' });
623
+ const observed = (await processSnapshot()).find(item => item.pid === child.pid);
624
+ if (!observed || observed.started !== started || observed.zombie) return false;
625
+ }
618
626
  try { process.kill(target, name); return true; }
619
627
  catch (error) {
620
628
  if (error.code === "ESRCH") return false;
@@ -1,4 +1,11 @@
1
- export const NATIVE_CHILD_CONTRACT_VERSION: "native-children@1";
1
+ export const NATIVE_CHILD_CONTRACT_VERSION: "native-children@2";
2
+ export const NATIVE_TREE_SETTLEMENT_CONTRACT: "native-tree-settlement@2";
3
+ export const NATIVE_SHUTDOWN_CONTRACT: "dd-flow/daemon-shutdown@2";
4
+ export const NATIVE_SHUTDOWN_PHASES: readonly string[];
5
+ export function nativeShutdownConfirmed(receipt: unknown, binding?: { daemonId?: string; generation?: number }): boolean;
6
+ export function nativeExecutionReceipt(receipt: Record<string, unknown>, options?: { daemonId?: string; generation?: number; revision?: number; resources?: Record<string, unknown> }): Record<string, unknown>;
7
+ export type NativeTreeReadiness = "ready" | "owned_children_live" | "inspection_pending" | "unproven";
8
+ export function nativeTreeReadiness(receipt: Record<string, unknown>, binding: { sessionId: string; operationId: string; turnId?: string; requestProof?: unknown; allowDescendants?: boolean }): NativeTreeReadiness;
2
9
  export function settledNativeTree(root: string, children: Record<string, unknown>[], settled: boolean, rootSettled?: boolean): Record<string, unknown>;
3
10
  export function safeNativeFailure(value: unknown): Record<string, unknown> | undefined;
4
11
  export interface NativeChild {
@@ -1,9 +1,104 @@
1
- export const NATIVE_CHILD_CONTRACT_VERSION = "native-children@1";
1
+ export const NATIVE_CHILD_CONTRACT_VERSION = "native-children@2";
2
+ export const NATIVE_TREE_SETTLEMENT_CONTRACT = "native-tree-settlement@2";
3
+ export const NATIVE_SHUTDOWN_CONTRACT = "dd-flow/daemon-shutdown@2";
4
+ export const NATIVE_SHUTDOWN_PHASES = Object.freeze(["execution", "owned_resources", "provider", "daemon_resource"]);
2
5
  import { errorRecord } from "./operation-errors.mjs";
3
6
  import { safeProviderError } from "./provider-overload-policy.mjs";
7
+ import { streamRequestKey } from "./provider-overload-policy.mjs";
8
+ const liveStatus = value => ["active", "running", "inprogress", "pending", "streaming", "busy"].includes(String(typeof value === "object" && value ? value.type : value).toLowerCase());
9
+
10
+ /** One current receipt only. Producers, not status ranks, establish freshness. */
11
+ export function nativeTreeReadiness(receipt, { sessionId, operationId, turnId, requestProof, allowDescendants = true } = {}) {
12
+ if (receipt?.native_settlement?.contract !== NATIVE_TREE_SETTLEMENT_CONTRACT || receipt.native_settlement.scope !== "required_execution") return "unproven";
13
+ if (!receipt || typeof sessionId !== "string" || !sessionId || typeof operationId !== "string" || !operationId || receipt.provider_session_id !== sessionId
14
+ || turnId !== undefined && receipt.terminal_turn_id !== undefined && receipt.terminal_turn_id !== turnId) return "unproven";
15
+ const requestKey = requestProof?.kind === "stream_request" ? streamRequestKey(requestProof) : null;
16
+ if (requestProof?.kind === "stream_request" && (!requestKey || requestProof.operation_id !== operationId
17
+ || streamRequestKey(receipt.terminal_metadata?.request_proof) !== requestKey)) return "unproven";
18
+ const pending = receipt.native_settlement;
19
+ if (pending?.state === "inspection_pending") {
20
+ const clock = pending.observation_clock;
21
+ return receipt.settled === false && pending.confirmed === false && pending.operation_id === operationId
22
+ && pending.provider_session_id === sessionId && requestKey && pending.request_key === requestKey
23
+ && clock?.policy === "operation-progress@1" && Number.isFinite(clock.started_at)
24
+ && Number.isSafeInteger(clock.timeout_ms) && clock.timeout_ms > 0 && clock.timeout_ms <= 2147483647
25
+ && Number.isFinite(clock.elapsed_ms) && clock.elapsed_ms >= 0 && clock.elapsed_ms < clock.timeout_ms
26
+ && Number.isSafeInteger(clock.gaps) && clock.gaps >= 0
27
+ && typeof clock.uncertain === "boolean"
28
+ && Number.isFinite(clock.uncertainty_elapsed_ms) && clock.uncertainty_elapsed_ms >= 0
29
+ && (clock.observed_at === null || Number.isFinite(clock.observed_at))
30
+ && clock.observation_lost === false ? "inspection_pending" : "unproven";
31
+ }
32
+ if (receipt.native_turn_items?.pending === true) return "unproven";
33
+ const ids = receipt.tree_session_ids, observations = receipt.tree_observations, unsettled = receipt.unsettled_sessions;
34
+ if (!Array.isArray(ids) || !ids.length || ids[0] !== sessionId || ids.some(id => typeof id !== "string" || !id)
35
+ || new Set(ids).size !== ids.length || !allowDescendants && ids.length !== 1
36
+ || !Array.isArray(observations) || observations.length !== ids.length
37
+ || !ids.every(id => observations.filter(item => item?.provider_session_id === id).length === 1)
38
+ || observations.some(item => !item || typeof item !== "object" || Array.isArray(item) || typeof item.settled !== "boolean" || item.settled === true && liveStatus(item.status)) || !Array.isArray(unsettled)
39
+ || new Set(unsettled).size !== unsettled.length) return "unproven";
40
+ // A producer's tree list cannot silently omit another positively observed
41
+ // owned child. Normalize the same receipt, not a second historical ledger.
42
+ try {
43
+ const children = normalizeNativeChildren(receipt, sessionId);
44
+ if (children.some(child => !ids.includes(child.session_id) || !ids.includes(child.parent_session_id) || child.parent_session_id === child.session_id)) return "unproven";
45
+ for (const child of children) {
46
+ let parent = child.parent_session_id;
47
+ const visited = new Set([child.session_id]);
48
+ while (parent !== sessionId) {
49
+ if (visited.has(parent)) return "unproven";
50
+ visited.add(parent); parent = children.find(item => item.session_id === parent)?.parent_session_id;
51
+ if (!parent) return "unproven";
52
+ }
53
+ }
54
+ } catch { return "unproven"; }
55
+ const live = observations.filter(item => !item.settled);
56
+ if (unsettled.length !== live.length || !live.every(item => unsettled.includes(item.provider_session_id))) return "unproven";
57
+ const settlement = receipt.settlement;
58
+ const budgetSettled = !settlement || settlement.state === "not_required"
59
+ || settlement.state === "settled" && settlement.operation_ids?.includes(operationId) === true;
60
+ if (receipt.settled === true && receipt.native_settled !== false && pending?.confirmed !== false && !live.length && budgetSettled) return "ready";
61
+ const root = observations.find(item => item.provider_session_id === sessionId);
62
+ const knownLive = live.length > 0 && live.every(item => item.provider_session_id !== sessionId && liveStatus(item.status));
63
+ return allowDescendants && receipt.settled === false && root.settled === true && knownLive
64
+ && (!settlement || settlement.state === "not_required" || settlement.state === "pending" && settlement.reason === "tree_unsettled")
65
+ ? "owned_children_live" : "unproven";
66
+ }
4
67
 
5
68
  function nativeFailure(code, message, exitCode, details) { return Object.assign(new Error(message), { code, exitCode, details }); }
6
69
 
70
+ /** Control settlement is not native settlement; explicit uncertainty wins. */
71
+ export function nativeShutdownConfirmed(receipt, { daemonId, generation } = {}) {
72
+ const proof = receipt?.schema_id === NATIVE_SHUTDOWN_CONTRACT ? receipt : receipt?.shutdown_proof;
73
+ const result = receipt?.schema_id === NATIVE_SHUTDOWN_CONTRACT ? receipt.result : receipt;
74
+ return Boolean(proof?.schema_id === NATIVE_SHUTDOWN_CONTRACT && typeof proof.daemon_id === "string" && proof.daemon_id
75
+ && (daemonId === undefined || proof.daemon_id === daemonId) && Number.isSafeInteger(proof.generation) && proof.generation >= 0
76
+ && (generation === undefined || proof.generation === generation)
77
+ && Array.isArray(proof.required_phases) && new Set(proof.required_phases).size === proof.required_phases.length
78
+ && NATIVE_SHUTDOWN_PHASES.every(phase => proof.required_phases.includes(phase))
79
+ && proof.required_phases.every(phase => typeof phase === "string" && proof.phases?.[phase] === true)
80
+ && result?.daemon_id === proof.daemon_id && result.shutdown_contract === NATIVE_SHUTDOWN_CONTRACT
81
+ && result.clean === true && result.stopped === true && result.physical_stopped === true
82
+ && result.native_settled === true && result.settled === true && result.native_settlement?.confirmed !== false
83
+ && result.resource_retirement?.confirmed !== false);
84
+ }
85
+
86
+ /** Scope metadata does not manufacture readiness or physical cleanup. */
87
+ export function nativeExecutionReceipt(receipt, { daemonId, generation = 0, revision = 0, resources } = {}) {
88
+ if (receipt.native_settlement?.contract !== undefined && receipt.native_settlement.contract !== NATIVE_TREE_SETTLEMENT_CONTRACT
89
+ || receipt.native_settlement?.scope !== undefined && receipt.native_settlement.scope !== "required_execution")
90
+ throw Object.assign(new Error("historical native proof requires fresh execution observation"), { code: "native_contract_mismatch" });
91
+ if ((resources?.entries ?? []).some(item => item.owner_daemon_id !== undefined && item.owner_daemon_id !== daemonId
92
+ || item.generation !== undefined && item.generation !== generation)) throw Object.assign(new Error("native resource belongs to another owner"), { code: "native_resource_owner_mismatch" });
93
+ const owner = { owner_daemon_id: daemonId ?? null, generation, revision };
94
+ return { ...receipt,
95
+ native_settlement: { ...receipt.native_settlement, contract: NATIVE_TREE_SETTLEMENT_CONTRACT, scope: "required_execution", ...owner },
96
+ native_resources: { observed_at: new Date().toISOString(), coverage: "unsupported", ...resources,
97
+ schema_id: "native-resources@1", ...owner,
98
+ entries: (resources?.entries ?? []).map(item => ({ ...item, owner_daemon_id: owner.owner_daemon_id, generation: owner.generation })) }
99
+ };
100
+ }
101
+
7
102
  /** Project a positively settled native tree, never infer it from missing activity. */
8
103
  export function settledNativeTree(root, children, settled, rootSettled = false) {
9
104
  if (settled !== true && rootSettled !== true || typeof root !== "string" || !root || !Array.isArray(children)) return {};
@@ -12,9 +107,9 @@ export function settledNativeTree(root, children, settled, rootSettled = false)
12
107
  const observations = [{ provider_session_id: root, status: "idle", settled: true }, ...children.map((child, index) => {
13
108
  const status = String(child.status ?? child.state ?? "unknown").toLowerCase();
14
109
  const terminal = settled === true || ["completed", "success", "succeeded", "failed", "cancelled", "canceled", "settled_by_root"].includes(status);
15
- return { provider_session_id: ids[index], status: terminal ? "idle" : ["running", "active", "inprogress", "pending"].includes(status) ? "running" : "unknown", settled: terminal };
110
+ return { provider_session_id: ids[index], status: terminal ? "idle" : liveStatus(status) ? "running" : "unknown", settled: terminal };
16
111
  })];
17
- return { tree_session_ids: [root, ...ids], unsettled_sessions: observations.filter(item => !item.settled).map(item => item.provider_session_id), tree_observations: observations };
112
+ return { native_settlement: { contract: NATIVE_TREE_SETTLEMENT_CONTRACT, scope: "required_execution" }, tree_session_ids: [root, ...ids], unsettled_sessions: observations.filter(item => !item.settled).map(item => item.provider_session_id), tree_observations: observations };
18
113
  }
19
114
 
20
115
  /** Retain typed failure facts, never provider headers, request bodies or free-form messages. */
@@ -51,6 +146,8 @@ export function normalizeNativeChildren(receipt, rootSessionId) {
51
146
  .map(value => text(object(value), ["provider_session_id", "session_id"]))
52
147
  .filter(id => id !== null)
53
148
  );
149
+ const unsettledTree = new Set(list(receipt.tree_observations).filter(value => object(value).settled === false)
150
+ .map(value => text(object(value), ["provider_session_id", "session_id"])).filter(Boolean));
54
151
  const add = (raw, source, scopedParent = null, fallback = "unknown") => {
55
152
  const value = object(raw);
56
153
  const id = text(value, ["provider_session_id", "session_id", "sessionId", "childSessionId", "conversation_id", "conversationId", "id", "subagentId"]);
@@ -66,9 +163,12 @@ export function normalizeNativeChildren(receipt, rootSessionId) {
66
163
  // Provider idle means no current activity, not a Work result. Only an
67
164
  // explicit terminal result or a separately proven root settlement may be
68
165
  // terminal here; unknown states stay unknown for reconciliation.
69
- const status = ["completed", "complete", "done", "success", "succeeded"].includes(state) ? "completed" : state === "settled_by_root" ? "settled_by_root" : ["failed", "error"].includes(state) ? "failed" : ["cancelled", "canceled", "interrupted"].includes(state) ? "cancelled" : ["running", "active", "pending"].includes(state) ? "running" : fallback;
166
+ const status = ["completed", "complete", "done", "success", "succeeded"].includes(state) ? "completed" : state === "settled_by_root" ? "settled_by_root" : ["failed", "error"].includes(state) ? "failed" : ["cancelled", "canceled", "interrupted"].includes(state) ? "cancelled" : liveStatus(state) ? "running" : fallback;
70
167
  const rootChild = parent === rootSessionId;
71
168
  const settled = rootChild && status === "unknown" && settledTree.has(id) ? "settled_by_root" : status;
169
+ if ((unsettledTree.has(id) || value.native_turn_items?.pending === true) && ["completed", "failed", "cancelled", "settled_by_root"].includes(settled)
170
+ || settledTree.has(id) && (unsettledTree.has(id) || status === "running" || value.native_turn_items?.pending === true))
171
+ throw nativeFailure("native_child_observation_conflict", "Current child outcome contradicts tree observation", 1, { session_id: id, source });
72
172
  candidates.push({ session_id: id, parent_session_id: parent ?? "", status: settled, source, ...(rootChild ? {} : { provenance: "parent_mismatch" }),
73
173
  ...(text(value, ["terminal_turn_id"]) ? { terminal_turn_id: text(value, ["terminal_turn_id"]) } : {}),
74
174
  ...(text(value, ["terminal_observed_at"]) ? { terminal_observed_at: text(value, ["terminal_observed_at"]) } : {}),
@@ -87,9 +187,12 @@ export function normalizeNativeChildren(receipt, rootSessionId) {
87
187
  add(value, "zcode/session/subagents", rootSessionId);
88
188
  }
89
189
  const unique = new Map();
90
- const rank = { unknown: 0, running: 1, settled_by_root: 2, completed: 3, failed: 3, cancelled: 3 };
190
+ const terminal = value => ["completed", "failed", "cancelled", "settled_by_root"].includes(value);
91
191
  for (const child of candidates) {
92
192
  const previous = unique.get(child.session_id);
193
+ if (previous && (terminal(previous.status) && child.native_turn_items?.pending === true
194
+ || terminal(child.status) && previous.native_turn_items?.pending === true))
195
+ throw nativeFailure("native_child_observation_conflict", "Current child outcome contradicts pending native items", 1, { session_id: child.session_id, observations: [previous, child] });
93
196
  if (previous && previous.parent_session_id !== child.parent_session_id) {
94
197
  // Keep the contradictory parent visible to the existing provenance gate,
95
198
  // independently of input order or which source happened to say success.
@@ -97,17 +200,20 @@ export function normalizeNativeChildren(receipt, rootSessionId) {
97
200
  unique.set(child.session_id, { ...foreign, provenance: "parent_mismatch" });
98
201
  continue;
99
202
  }
100
- if (previous && rank[previous.status] === 3 && rank[child.status] === 3 && previous.status !== child.status) throw nativeFailure("native_child_outcome_conflict", "Native child has contradictory terminal outcomes", 1, { session_id: child.session_id, observations: [previous, child] });
203
+ if (previous && terminal(previous.status) && terminal(child.status) && previous.status !== child.status && ![previous.status, child.status].includes("settled_by_root")) throw nativeFailure("native_child_outcome_conflict", "Native child has contradictory terminal outcomes", 1, { session_id: child.session_id, observations: [previous, child] });
204
+ if (previous && (previous.status === "running" && terminal(child.status) || child.status === "running" && terminal(previous.status)))
205
+ throw nativeFailure("native_child_observation_conflict", "Current child has both active and terminal facts", 1, { session_id: child.session_id, observations: [previous, child] });
101
206
  if (previous && previous.status === child.status && previous.terminal_turn_id && child.terminal_turn_id && previous.terminal_turn_id !== child.terminal_turn_id)
102
207
  throw nativeFailure("native_child_turn_conflict", "Native child has contradictory terminal Turn identities", 1, { session_id: child.session_id, observations: [previous, child] });
103
208
  if (previous && previous.status === child.status) {
104
209
  unique.set(child.session_id, { ...previous, ...child,
105
210
  ...(child.terminal_turn_id ?? previous.terminal_turn_id ? { terminal_turn_id: child.terminal_turn_id ?? previous.terminal_turn_id } : {}),
106
211
  ...(child.provider_error ?? previous.provider_error ? { provider_error: child.provider_error ?? previous.provider_error } : {}),
107
- ...(child.native_turn_items ?? previous.native_turn_items ? { native_turn_items: child.native_turn_items ?? previous.native_turn_items } : {}) });
212
+ ...(child.native_turn_items ?? previous.native_turn_items ? { native_turn_items: { ...previous.native_turn_items, ...child.native_turn_items,
213
+ ...([previous.native_turn_items, child.native_turn_items].some(items => items?.pending === true) ? { pending: true } : {}) } } : {}) });
108
214
  continue;
109
215
  }
110
- if (!previous || rank[child.status] > rank[previous.status]) unique.set(child.session_id, child);
216
+ if (!previous || previous.status === "unknown" || previous.status === "settled_by_root" && terminal(child.status)) unique.set(child.session_id, child);
111
217
  }
112
218
  return [...unique.values()].sort((left, right) => left.session_id.localeCompare(right.session_id));
113
219
  }
@@ -13,7 +13,7 @@ const fail = reason => { throw Object.assign(new Error('Native request proof doe
13
13
  * the adapter, and recomputed by the adapter's shared request builder. */
14
14
  export function nativeCommandFingerprint({ executable, args, cwd, env, operationId }) {
15
15
  return hashJson({ executable, args, cwd, operation_id: operationId,
16
- environment: Object.fromEntries(environmentKeys.map(key => [key, env[key] ?? null])) });
16
+ environment: Object.fromEntries([...environmentKeys, ...(env.PLAYWRIGHT_BROWSERS_PATH === undefined ? [] : ['PLAYWRIGHT_BROWSERS_PATH'])].map(key => [key, env[key] ?? null])) });
17
17
  }
18
18
 
19
19
  /** Called after the adapter's own argument preparation but before its RPC or
@@ -3,12 +3,12 @@ export function validateObservationDuration(value: number, label?: string): numb
3
3
  export interface ObservationState {
4
4
  policy: string; timeout_ms: number; started_at: number; cursor?: unknown;
5
5
  observed_at: number | null; elapsed_ms: number; uncertainty_elapsed_ms: number;
6
- gaps: number; observation_lost: boolean;
6
+ gaps: number; uncertain?: boolean; observation_lost: boolean;
7
7
  }
8
8
  export class ObservationClock {
9
9
  constructor(options: { timeoutMs: number; gapMs?: number; uncertaintyMs?: number;
10
10
  wall?: () => number; monotonic?: () => number; onGap?: (value: Record<string, unknown>) => void;
11
- saved?: ObservationState | null });
11
+ saved?: ObservationState | null; snapshotIsLive?: boolean });
12
12
  observationLost: boolean;
13
13
  sample(progress?: unknown, progressAt?: number): boolean;
14
14
  state(): ObservationState;
@@ -9,7 +9,7 @@ export function validateObservationDuration(value, label = "observation duration
9
9
  * progress or proof of provider failure. Durable snapshots retain that episode. */
10
10
  export class ObservationClock {
11
11
  constructor({ timeoutMs, gapMs = 60_000, uncertaintyMs = 120_000, wall = Date.now,
12
- monotonic = () => performance.now(), onGap = () => {}, saved = null }) {
12
+ monotonic = () => performance.now(), onGap = () => {}, saved = null, snapshotIsLive = false }) {
13
13
  validateObservationDuration(timeoutMs); validateObservationDuration(gapMs, "gap duration");
14
14
  validateObservationDuration(uncertaintyMs, "observation recovery duration");
15
15
  Object.assign(this, { timeoutMs, gapMs, uncertaintyMs, wall, monotonic, onGap });
@@ -21,13 +21,16 @@ export class ObservationClock {
21
21
  || !Number.isFinite(saved.started_at) || !Number.isSafeInteger(saved.gaps) || saved.gaps < 0
22
22
  || saved.observed_at !== null && !Number.isFinite(saved.observed_at)
23
23
  || typeof saved.observation_lost !== "boolean"
24
+ || saved.uncertain !== undefined && typeof saved.uncertain !== "boolean"
24
25
  || !Number.isFinite(saved.elapsed_ms) || saved.elapsed_ms < 0
25
26
  || !Number.isFinite(saved.uncertainty_elapsed_ms) || saved.uncertainty_elapsed_ms < 0)
26
27
  throw new RangeError("invalid retained observation clock");
27
28
  this.startedWall = saved.started_at; this.elapsed = saved.elapsed_ms;
28
29
  this.progress = saved.cursor; this.progressAt = saved.observed_at;
29
- // A reconstructed observer cannot prove silence during its absence.
30
- this.uncertain = true; this.gaps = saved.gaps + 1;
30
+ // Durable reconstruction has an observation gap. Transferring a freshly
31
+ // observed producer clock is not a second restart of that same producer.
32
+ this.uncertain = snapshotIsLive ? saved.uncertain ?? true : true;
33
+ this.gaps = saved.gaps + (snapshotIsLive ? 0 : 1);
31
34
  this.uncertaintyElapsed = saved.uncertainty_elapsed_ms;
32
35
  this.observationLost = saved.observation_lost || this.gaps > 1 || this.uncertaintyElapsed >= uncertaintyMs;
33
36
  }
@@ -77,7 +80,7 @@ export class ObservationClock {
77
80
  return { policy: OBSERVATION_CLOCK_CONTRACT_VERSION, timeout_ms: this.timeoutMs,
78
81
  started_at: this.startedWall, cursor: this.progress, observed_at: this.progressAt,
79
82
  elapsed_ms: this.elapsed, uncertainty_elapsed_ms: this.uncertaintyElapsed,
80
- gaps: this.gaps, observation_lost: this.observationLost };
83
+ gaps: this.gaps, uncertain: this.uncertain, observation_lost: this.observationLost };
81
84
  }
82
85
  }
83
86
 
@@ -1,5 +1,5 @@
1
1
  export const CAPACITY_POLICY: "provider-overload-episode@1";
2
- export const MAX_OVERLOAD_RETRIES: 2;
2
+ export const MAX_OVERLOAD_RETRIES: 3;
3
3
  export const MAX_RETRY_AFTER_MS: 30000;
4
4
  export function classifyProviderError(native: unknown): string;
5
5
  export function safeProviderError(native: unknown): Record<string, unknown>;
@@ -1,6 +1,6 @@
1
1
  /** Pure policy shared by the controller and pinned-engine EVAL consumers. */
2
2
  export const CAPACITY_POLICY = "provider-overload-episode@1";
3
- export const MAX_OVERLOAD_RETRIES = 2;
3
+ export const MAX_OVERLOAD_RETRIES = 3;
4
4
  export const MAX_RETRY_AFTER_MS = 30_000;
5
5
 
6
6
  function policyError(code, message, details = {}) { return Object.assign(new Error(message), { code, details }); }
@@ -156,7 +156,7 @@ export function agyToolObservation(step, rootSessionId) {
156
156
  const sessionId = step.conversation_id ?? rootSessionId;
157
157
  return { session: identity("antigravity-cli", sessionId), parent_session: sessionId !== rootSessionId ? identity("antigravity-cli", rootSessionId) : null,
158
158
  call_id: step.step_id ?? step.stepId ?? step.id ?? step.tool_call_id ?? step.toolCallId ?? (Number.isInteger(step.step_index) ? String(step.step_index) : null),
159
- name: step.tool_name ?? step.tool_info?.name, ...(step.tool_info?.error ? { status: "failed" } : nativeStatus(step.state)) };
159
+ name: step.tool_name ?? step.tool_info?.name, ...(step.tool_info?.error ? { status: "failed" } : nativeStatus(step.state === "ACTIVE" ? "running" : step.state)) };
160
160
  }
161
161
 
162
162
  /** Native AGY JSONL records have planner tool invocations, not matching live