@deksden-com/dd-flow-cli 0.9.0-beta.136 → 0.9.0-beta.138
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 +12 -0
- package/README.md +9 -0
- package/dist/build-info.json +6 -6
- package/dist/cli/argument-values.js +5 -5
- package/dist/cli/command-inputs.js +89 -20
- package/dist/cli/command-metadata.js +56 -3
- package/dist/cli/command-option-descriptions.js +5 -0
- package/dist/cli/executor-onboarding.js +8 -3
- package/dist/cli/flows.js +29 -0
- package/dist/cli/help.js +240 -6
- package/dist/cli/input-preparation.js +54 -5
- package/dist/cli/memory-flow-admission.js +44 -0
- package/dist/cli/memory-init-invocation.js +78 -0
- package/dist/cli/memory-init.js +240 -0
- package/dist/cli/memory-storage-admission.js +53 -0
- package/dist/cli/native-hook-ingress.js +1 -1
- package/dist/cli/response-file.js +2 -0
- package/dist/cli/run-cli.js +283 -160
- package/dist/cli/setup.js +9 -7
- package/dist/cli.js +54 -2
- package/dist/domain/stage-handler-identities.js +20 -0
- package/dist/harness-runtime/DD-ZCODE.md +13 -0
- package/dist/harness-runtime/bin/dd-opencode.mjs +3 -3
- package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +4 -3
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +4 -2
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +11 -5
- package/dist/harness-runtime/lib/dd-codex.mjs +22 -10
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +2 -1
- package/dist/harness-runtime/lib/dd-droid.mjs +2 -1
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +4 -2
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +20 -5
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +45 -10
- package/dist/harness-runtime/lib/dd-zcode.mjs +7 -3
- package/dist/harness-runtime/lib/delegation-instructions.mjs +5 -3
- package/dist/harness-runtime/lib/lifecycle-command.mjs +1 -0
- package/dist/harness-runtime/lib/managed-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +50 -15
- package/dist/harness-runtime/lib/managed-flow-env.mjs +15 -0
- package/dist/harness-runtime/lib/native-hook-command.mjs +1 -1
- package/dist/harness-runtime/lib/native-request-proof.d.mts +2 -0
- package/dist/harness-runtime/lib/native-request-proof.mjs +65 -0
- package/dist/harness-runtime/lib/opencode-plugin.mjs +5 -1
- package/dist/harness-runtime/lib/operation-progress.mjs +3 -1
- package/dist/runtime/context.js +1 -1
- package/dist/schemas/discovery-result.schema.json +184 -0
- package/dist/schemas/execution-bundle.schema.json +205 -0
- package/dist/schemas/execution-settings.schema.json +162 -0
- package/dist/schemas/flow-descriptor.schema.json +54 -0
- package/dist/schemas/flow-run.schema.json +22 -0
- package/dist/schemas/harness-catalog.schema.json +80 -0
- package/dist/schemas/mb-init-compose-result.schema.json +457 -0
- package/dist/schemas/mb-init-discover-result.schema.json +500 -0
- package/dist/schemas/mb-init-finish-result.schema.json +422 -0
- package/dist/schemas/mb-init-preflight-result.schema.json +367 -0
- package/dist/schemas/mb-init-review-result.schema.json +396 -0
- package/dist/schemas/mb-init-verify-result.schema.json +391 -0
- package/dist/schemas/memory-bootstrap-receipt-v2.schema.json +67 -0
- package/dist/schemas/memory-bootstrap-receipt.schema.json +354 -0
- package/dist/schemas/memory-composition-work-result.schema.json +38 -0
- package/dist/schemas/memory-repair-work-result.schema.json +49 -0
- package/dist/schemas/memory-review-correction-plan.schema.json +30 -0
- package/dist/schemas/memory-review-work-result.schema.json +43 -0
- package/dist/schemas/model-catalog.schema.json +153 -0
- package/dist/schemas/profile-settings.schema.json +122 -0
- package/dist/schemas/release-manifest.schema.json +291 -0
- package/dist/schemas/run-engine-binding.schema.json +1 -1
- package/dist/services/check-runner-evidence.js +51 -4
- package/dist/services/cli-operation-classifier.js +8 -47
- package/dist/services/code-checks.js +387 -34
- package/dist/services/controller-child-observer.js +99 -0
- package/dist/services/controller-execution.js +88 -0
- package/dist/services/controller-fanout.js +79 -13
- package/dist/services/dashboard.js +8 -2
- package/dist/services/descriptor-coordinator-session.js +35 -0
- package/dist/services/descriptor-root-work.js +53 -0
- package/dist/services/descriptor-run-settlement.js +105 -0
- package/dist/services/descriptor-run-start.js +449 -0
- package/dist/services/descriptor-stage-coordinator.js +197 -0
- package/dist/services/descriptor-stage-settlement.js +222 -0
- package/dist/services/descriptor-stage-start.js +139 -0
- package/dist/services/engines.js +39 -22
- package/dist/services/eval-snapshots.js +83 -25
- package/dist/services/execution-bundles.js +263 -0
- package/dist/services/execution-policy.js +20 -16
- package/dist/services/external-work-launch.js +91 -11
- package/dist/services/flow-catalog.js +21 -0
- package/dist/services/flow-descriptors.js +82 -0
- package/dist/services/frozen-engine.js +242 -0
- package/dist/services/generic-execution.js +164 -0
- package/dist/services/harness-adapter.js +28 -2
- package/dist/services/hooks.js +24 -6
- package/dist/services/hybrid-contracts.js +31 -3
- package/dist/services/ids.js +29 -1
- package/dist/services/installation-lock.js +7 -4
- package/dist/services/installed-flow-source.js +183 -0
- package/dist/services/lifecycle-invocations.js +257 -78
- package/dist/services/memory-accepted-composition.js +77 -0
- package/dist/services/memory-baseline-preservation.js +65 -0
- package/dist/services/memory-completed-correction-wave.js +320 -0
- package/dist/services/memory-compose-handler.js +258 -0
- package/dist/services/memory-composition-acceptance.js +199 -0
- package/dist/services/memory-composition-graph.js +134 -0
- package/dist/services/memory-composition-registration.js +146 -0
- package/dist/services/memory-composition-work-custody.js +151 -0
- package/dist/services/memory-composition-work-results.js +96 -0
- package/dist/services/memory-corrected-review-acceptance.js +129 -0
- package/dist/services/memory-corrected-review-batch.js +30 -0
- package/dist/services/memory-corrected-review-findings.js +106 -0
- package/dist/services/memory-corrected-review-inputs.js +210 -0
- package/dist/services/memory-corrected-review-projection.js +78 -0
- package/dist/services/memory-corrected-review-receipt.js +170 -0
- package/dist/services/memory-corrected-review-registration.js +191 -0
- package/dist/services/memory-correction-check-receipt.js +219 -0
- package/dist/services/memory-correction-input-owner.js +6 -0
- package/dist/services/memory-discover-handler.js +104 -0
- package/dist/services/memory-finish-evidence.js +435 -0
- package/dist/services/memory-finish-handler.js +215 -0
- package/dist/services/memory-finish-integration.js +280 -0
- package/dist/services/memory-finish-readiness.js +122 -0
- package/dist/services/memory-git-integration.js +151 -0
- package/dist/services/memory-historical-corrected-review.js +206 -0
- package/dist/services/memory-historical-original-review.js +130 -0
- package/dist/services/memory-historical-repair-registration.js +99 -0
- package/dist/services/memory-historical-second-review.js +128 -0
- package/dist/services/memory-init-bootstrap-readiness.js +210 -0
- package/dist/services/memory-init-contexts.js +89 -0
- package/dist/services/memory-init-controller-start.js +84 -0
- package/dist/services/memory-init-engine-release.js +223 -0
- package/dist/services/memory-init-git-policy.js +136 -0
- package/dist/services/memory-init-lint-capability.js +185 -0
- package/dist/services/memory-init-native-capacity.js +241 -0
- package/dist/services/memory-init-preflight.js +312 -0
- package/dist/services/memory-init-project-bootstrap.js +599 -0
- package/dist/services/memory-init-request-candidate.js +157 -0
- package/dist/services/memory-init-request-input.js +66 -0
- package/dist/services/memory-init-resource-storage.js +125 -0
- package/dist/services/memory-init-run-allocation.js +81 -0
- package/dist/services/memory-init-run-bundle.js +98 -0
- package/dist/services/memory-init-start.js +175 -0
- package/dist/services/memory-init-startup-custody.js +80 -0
- package/dist/services/memory-init-startup-input.js +240 -0
- package/dist/services/memory-init-storage.js +81 -0
- package/dist/services/memory-init-stores.js +359 -0
- package/dist/services/memory-init-worktrunk-capability.js +172 -0
- package/dist/services/memory-layer-publication.js +611 -0
- package/dist/services/memory-layer.js +156 -0
- package/dist/services/memory-lint-config.js +85 -0
- package/dist/services/memory-native-review-copy.js +165 -0
- package/dist/services/memory-normal-flow-entry.js +75 -0
- package/dist/services/memory-ordinary-defaults.js +112 -0
- package/dist/services/memory-preflight-bootstrap-custody.js +115 -0
- package/dist/services/memory-preflight-handler.js +259 -0
- package/dist/services/memory-repair-current-view.js +299 -0
- package/dist/services/memory-repair-document-delta.js +451 -0
- package/dist/services/memory-repair-history.js +491 -0
- package/dist/services/memory-repair-physical-binding.js +312 -0
- package/dist/services/memory-repair-publication-files.js +152 -0
- package/dist/services/memory-repair-publication-intent.js +27 -0
- package/dist/services/memory-repair-publication-plan.js +160 -0
- package/dist/services/memory-repair-publication-source.js +225 -0
- package/dist/services/memory-repair-publication.js +794 -0
- package/dist/services/memory-repair-registration.js +239 -0
- package/dist/services/memory-repair-work-baseline.js +413 -0
- package/dist/services/memory-repair-work-completion.js +97 -0
- package/dist/services/memory-repair-work-custody.js +73 -0
- package/dist/services/memory-repair-work-results.js +89 -0
- package/dist/services/memory-repair-workspace.js +252 -0
- package/dist/services/memory-repair-writer-copy.js +735 -0
- package/dist/services/memory-result-binding.js +173 -0
- package/dist/services/memory-retained-review-authority.js +129 -0
- package/dist/services/memory-retained-review-results.js +70 -0
- package/dist/services/memory-retained-second-baseline.js +102 -0
- package/dist/services/memory-retained-second-repairs.js +156 -0
- package/dist/services/memory-review-acceptance.js +214 -0
- package/dist/services/memory-review-correction-plan.js +171 -0
- package/dist/services/memory-review-handler.js +585 -0
- package/dist/services/memory-review-inputs.js +200 -0
- package/dist/services/memory-review-receipt.js +180 -0
- package/dist/services/memory-review-registration.js +108 -0
- package/dist/services/memory-review-work-custody.js +124 -0
- package/dist/services/memory-review-work-input.js +72 -0
- package/dist/services/memory-review-work-results.js +73 -0
- package/dist/services/memory-second-corrected-review-inputs.js +165 -0
- package/dist/services/memory-second-correction-plan.js +76 -0
- package/dist/services/memory-second-repair-baseline.js +91 -0
- package/dist/services/memory-second-repair-launch.js +614 -0
- package/dist/services/memory-second-repair-registration.js +88 -0
- package/dist/services/memory-second-repair-source.js +187 -0
- package/dist/services/memory-second-review-acceptance.js +115 -0
- package/dist/services/memory-source-observations.js +117 -0
- package/dist/services/memory-stage-dispatch.js +28 -0
- package/dist/services/memory-stage-results.js +199 -0
- package/dist/services/memory-verification-acceptance.js +135 -0
- package/dist/services/memory-verification-checks.js +252 -0
- package/dist/services/memory-verification-receipt.js +159 -0
- package/dist/services/memory-verify-handler.js +135 -0
- package/dist/services/memory-work-result-preparation.js +63 -0
- package/dist/services/projects.js +17 -2
- package/dist/services/recovery-snapshot-database.js +416 -1
- package/dist/services/repair-intents.js +1 -1
- package/dist/services/review-copy.js +10 -2
- package/dist/services/run-control.js +80 -9
- package/dist/services/run-controller-capture.js +10 -3
- package/dist/services/run-controller-process.js +21 -0
- package/dist/services/run-controller-recovery.js +3 -1
- package/dist/services/run-controller-state.js +1 -1
- package/dist/services/run-controller.js +184 -48
- package/dist/services/run-recovery-guard.js +2 -0
- package/dist/services/run-recovery.js +145 -5
- package/dist/services/run-start-custody.js +177 -0
- package/dist/services/runs.js +319 -25
- package/dist/services/stage-context.js +9 -1
- package/dist/services/stage-handlers.js +74 -0
- package/dist/services/stage-lifecycle.js +2 -1
- package/dist/services/stage-report-renderer.js +76 -0
- package/dist/services/stage-work-graph.js +17 -0
- package/dist/services/vnext-code.js +3 -3
- package/dist/services/vnext-execution-profile.js +2 -2
- package/dist/services/vnext-fanout.js +136 -10
- package/dist/services/vnext-merge.js +8 -3
- package/dist/services/vnext-plan-review.js +6 -4
- package/dist/services/vnext-protocolize.js +60 -2
- package/dist/services/work-registry.js +616 -71
- package/dist/services/workspace-files.js +1 -1
- package/dist/storage/database.js +84 -6
- package/dist/storage/writer-contract.js +11 -7
- package/package.json +13 -3
- package/tools/create-canon-release.mjs +33 -0
- package/tools/produce-hybrid-release.mjs +41 -0
|
@@ -292,6 +292,34 @@ async function reconcileOwnedSessions(context, input, interrupt, boundary) {
|
|
|
292
292
|
const nativeVersions = new Map();
|
|
293
293
|
for (const [directory, { state, records }] of daemonInventories) {
|
|
294
294
|
let failedStart = null;
|
|
295
|
+
// Explicit stop can reconcile a retired owner whose last bookkeeping ACK
|
|
296
|
+
// failed. This is physical retirement, not a fabricated clean shutdown.
|
|
297
|
+
if (control?.action === "stop" && state.shutdown_state === "cleanup_failed")
|
|
298
|
+
try {
|
|
299
|
+
const daemonId = state.daemon_id ?? state.config?.daemonId;
|
|
300
|
+
const daemon = records.filter(record => record.kind.endsWith("-daemon"));
|
|
301
|
+
const provider = records.filter(record => record.kind.endsWith("-provider"));
|
|
302
|
+
const operations = retainedDaemonOperations(directory);
|
|
303
|
+
const owner = state.config?.runtime_owner;
|
|
304
|
+
if (daemonId && !conflicts.has(directory) && !unsafeDaemons.has(canonicalPath(directory, false))
|
|
305
|
+
&& state.active_tree === false && !state.active_operation && state.shutdown?.daemon_id === daemonId
|
|
306
|
+
&& state.shutdown.phases?.tree === true && state.shutdown.phases.provider === true
|
|
307
|
+
&& daemon.length === 1 && provider.length === 1 && records.length === 2
|
|
308
|
+
&& daemon[0].id === state.resource_process?.id && daemon[0].lease_token === state.resource_process.lease_token && daemon[0].pid === state.pid
|
|
309
|
+
&& provider[0].id === state.provider_process?.id && provider[0].lease_token === state.provider_process.lease_token && provider[0].pid === state.provider_pid
|
|
310
|
+
&& JSON.parse(daemon[0].metadata_json).dispatch_barrier === "run-generation@1"
|
|
311
|
+
&& records.every(record => record.pid && record.pid_started_at && ["stopped", "failed"].includes(record.state) && record.finished_at && !processTreeIsAlive(record))
|
|
312
|
+
&& operations.length > 0 && operations.every(operation => ["completed", "failed", "interrupted"].includes(String(operation.state))
|
|
313
|
+
&& (operation.operation === "daemon.start" ? operation.daemon_id === null && operation.launch?.runtime_owner === JSON.stringify(owner)
|
|
314
|
+
: operation.daemon_id === daemonId && ["daemon.stop", "daemon.status", "session.create", "session.prompt", "session.start", "session.fork", "session.resume", "session.cancel", "session.inspect"].includes(String(operation.operation))
|
|
315
|
+
&& (operation.session_id == null || state.sessions.some(item => (typeof item === "string" ? item : adapterSessionId(item)) === operation.session_id))))) {
|
|
316
|
+
nativeVersions.set(directory, nativeInventoryVersion(directory, operations));
|
|
317
|
+
closedDaemons.set(directory, { source: "observed_owned_retirement", daemon_id: daemonId, stopped: true, settled: true,
|
|
318
|
+
session_ids: state.sessions.map(item => typeof item === "string" ? item : adapterSessionId(item)),
|
|
319
|
+
processes: records.map(record => ({ process_id: record.id, pid: record.pid, pid_started_at: record.pid_started_at })) });
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
catch { /* Invalid or unfinished evidence remains on the existing recovery path. */ }
|
|
295
323
|
if (!interrupt) {
|
|
296
324
|
try {
|
|
297
325
|
const nativeOperations = retainedDaemonOperations(directory);
|
|
@@ -309,16 +337,48 @@ async function reconcileOwnedSessions(context, input, interrupt, boundary) {
|
|
|
309
337
|
const saved = JSON.parse(operation.input_json);
|
|
310
338
|
return saved.owner === owner?.owner_id && saved.executable === directoryAdapters.get(directory) && saved.args?.[0] === "daemon" && saved.args?.[1] === "start" && saved.args.includes("--state-dir") && saved.args[saved.args.indexOf("--state-dir") + 1] === directory;
|
|
311
339
|
});
|
|
340
|
+
const cleanup = start?.error?.details;
|
|
341
|
+
const proof = cleanup?.startup_child_cleanup;
|
|
342
|
+
let ownedChildGone = false;
|
|
343
|
+
if (proof && !cleanup?.provider_cleanup_failed && !cleanup?.cleanup_failed && provider.length === 0 && !state.provider_process && state.provider_pid == null
|
|
344
|
+
&& daemon.length === 1 && daemon[0].pid === null && daemon[0].pid_started_at === null
|
|
345
|
+
&& proof.settled === true && proof.physical_exit === "ESRCH" && proof.daemon_id === state.daemon_id
|
|
346
|
+
&& proof.process_id === daemon[0].id
|
|
347
|
+
&& proof.pid === state.pid && Number.isSafeInteger(proof.pid) && Number(proof.pid) > 0
|
|
348
|
+
&& proof.pgid === (process.platform === "win32" ? null : proof.pid)
|
|
349
|
+
&& typeof proof.observed_at === "string" && Number.isFinite(Date.parse(proof.observed_at))
|
|
350
|
+
&& proof.owner_id === owner?.owner_id && proof.project_id === project.id && proof.run_id === input.runId
|
|
351
|
+
&& proof.operation_id === start?.operation_id && proof.operation_id === owner?.operation_id && proof.state_dir === directory) {
|
|
352
|
+
let leaderGone = false;
|
|
353
|
+
try {
|
|
354
|
+
process.kill(Number(proof.pid), 0);
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
leaderGone = error.code === "ESRCH";
|
|
358
|
+
}
|
|
359
|
+
if (leaderGone) {
|
|
360
|
+
if (process.platform === "win32")
|
|
361
|
+
ownedChildGone = true;
|
|
362
|
+
else {
|
|
363
|
+
try {
|
|
364
|
+
process.kill(-Number(proof.pgid), 0);
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
ownedChildGone = error.code === "ESRCH";
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
312
372
|
if (state.shutdown_state === "starting" && state.sessions?.length === 0 && state.active_tree !== true && !state.active_operation
|
|
313
373
|
&& start?.operation === "daemon.start" && start.state === "failed" && start.predecessor_daemon_id === null && start.daemon_id === null
|
|
314
374
|
&& typeof state.daemon_id === "string" && controllerStarts.length === 1 && starts.length === 1 && starts[0].operation_id === start.operation_id
|
|
315
375
|
&& launch?.runtime_owner === JSON.stringify(owner)
|
|
316
376
|
&& daemon.length === 1 && provider.length <= 1 && records.length === daemon.length + provider.length
|
|
317
377
|
&& daemon[0].id === state.resource_process?.id && daemon[0].lease_token === state.resource_process.lease_token
|
|
318
|
-
&& (state.pid == null || state.pid === daemon[0].pid)
|
|
378
|
+
&& (state.pid == null || state.pid === daemon[0].pid || ownedChildGone)
|
|
319
379
|
&& (!state.provider_process && state.provider_pid == null && provider.length === 0 || provider.length === 1 && provider[0].id === state.provider_process?.id && provider[0].lease_token === state.provider_process.lease_token && state.provider_pid === provider[0].pid)
|
|
320
|
-
&& records.every(record => record.pid && record.pid_started_at && !processTreeIsAlive(record) && ["stopped", "failed"].includes(record.state) && Boolean(record.finished_at))) {
|
|
321
|
-
failedStart = { source: "failed_start_without_create", daemon_id: state.daemon_id, operation_id: start.operation_id, session_ids: [], processes: records.map(record => ({ process_id: record.id, pid: record.pid, pid_started_at: record.pid_started_at })) };
|
|
380
|
+
&& records.every(record => (record.pid && record.pid_started_at && !processTreeIsAlive(record) || ownedChildGone && record === daemon[0]) && ["stopped", "failed"].includes(record.state) && Boolean(record.finished_at))) {
|
|
381
|
+
failedStart = { source: "failed_start_without_create", daemon_id: state.daemon_id, operation_id: start.operation_id, session_ids: [], processes: records.map(record => ({ process_id: record.id, pid: record.pid, pid_started_at: record.pid_started_at })), ...(ownedChildGone ? { startup_child_cleanup: proof } : {}) };
|
|
322
382
|
closedDaemons.set(directory, failedStart);
|
|
323
383
|
}
|
|
324
384
|
for (const operation of nativeOperations)
|
|
@@ -331,6 +391,8 @@ async function reconcileOwnedSessions(context, input, interrupt, boundary) {
|
|
|
331
391
|
unresolved.push({ state_dir: directory, status: "pending", reason: "daemon_operation_inventory_invalid" });
|
|
332
392
|
}
|
|
333
393
|
}
|
|
394
|
+
if (closedDaemons.get(directory)?.source === "observed_owned_retirement")
|
|
395
|
+
continue;
|
|
334
396
|
if (state.shutdown_state !== "clean") {
|
|
335
397
|
if (state.shutdown_state === "unclean")
|
|
336
398
|
unresolved.push({ state_dir: directory, status: "pending", reason: "native_shutdown_unclean" });
|
|
@@ -634,11 +696,17 @@ async function reconcileOwnedSessions(context, input, interrupt, boundary) {
|
|
|
634
696
|
const nativeOperations = retainedDaemonOperations(directory);
|
|
635
697
|
const targetList = [...targets.values()];
|
|
636
698
|
const sessionsSettled = ids.every(id => nodes.some((node, index) => targetList[index]?.session.state_dir === directory && targetList[index]?.session.id === id && node.status === "completed"));
|
|
637
|
-
// A confirmed
|
|
638
|
-
//
|
|
699
|
+
// A confirmed failure may retry the same incarnation's settled tree or
|
|
700
|
+
// remaining idempotent resource finish. Unknown outcomes keep their ID.
|
|
639
701
|
if (sessionsSettled) {
|
|
702
|
+
const remainingFinish = state.shutdown_state === "cleanup_failed" && state.active_tree === false
|
|
703
|
+
&& state.shutdown?.daemon_id === daemonId && state.shutdown.phases?.tree === true
|
|
704
|
+
&& state.shutdown.required_phases?.includes("daemon_resource")
|
|
705
|
+
&& state.shutdown.phases.daemon_resource !== true
|
|
706
|
+
&& state.shutdown.required_phases.every(phase => phase === "daemon_resource" || state.shutdown.phases[phase] === true);
|
|
640
707
|
while (nativeOperations.some(operation => operation.operation_id === operationId && operation.daemon_id === daemonId && operation.operation === "daemon.stop"
|
|
641
|
-
&& operation.state === "failed" && operation.error?.code === "tree_not_settled"
|
|
708
|
+
&& operation.state === "failed" && (operation.error?.code === "tree_not_settled"
|
|
709
|
+
|| remainingFinish && operation.error?.code === "process_maintenance_timeout")))
|
|
642
710
|
operationId += ":finalize";
|
|
643
711
|
}
|
|
644
712
|
assertCurrent();
|
|
@@ -697,11 +765,13 @@ async function reconcileOwnedSessions(context, input, interrupt, boundary) {
|
|
|
697
765
|
const executable = directoryAdapters.get(directory);
|
|
698
766
|
if (!inventory)
|
|
699
767
|
continue;
|
|
768
|
+
const pending = retainedDaemonOperations(directory).filter(operation => operation.daemon_id === daemonId && ["running", "awaiting_provider"].includes(String(operation.state)) && ["session.create", "session.prompt", "session.start", "session.fork", "session.resume"].includes(String(operation.operation)) && typeof operation.operation_id === "string");
|
|
769
|
+
if (!pending.length)
|
|
770
|
+
continue;
|
|
700
771
|
if (!daemonId || !executable || !node.operation_id) {
|
|
701
772
|
terminalizedOperations.push({ state_dir: directory, status: "pending", reason: "operation_terminalization_binding_unknown" });
|
|
702
773
|
continue;
|
|
703
774
|
}
|
|
704
|
-
const pending = retainedDaemonOperations(directory).filter(operation => operation.daemon_id === daemonId && ["running", "awaiting_provider"].includes(String(operation.state)) && ["session.create", "session.prompt", "session.start", "session.fork", "session.resume"].includes(String(operation.operation)) && typeof operation.operation_id === "string");
|
|
705
775
|
terminalizedOperations.push(...await Promise.all(pending.map(async (operation) => {
|
|
706
776
|
try {
|
|
707
777
|
const operationId = operation.operation_id, sessionId = typeof operation.session_id === "string" ? operation.session_id : null;
|
|
@@ -795,7 +865,8 @@ async function reconcileOwnedSessions(context, input, interrupt, boundary) {
|
|
|
795
865
|
verifyInventory();
|
|
796
866
|
}
|
|
797
867
|
catch (error) {
|
|
798
|
-
unresolved.push({ status: "pending", reason: error instanceof AppError && error.code === "owned_inventory_changed" ? error.code : "owned_inventory_recheck_failed"
|
|
868
|
+
unresolved.push({ status: "pending", reason: error instanceof AppError && error.code === "owned_inventory_changed" ? error.code : "owned_inventory_recheck_failed",
|
|
869
|
+
...(error instanceof AppError && error.code === "owned_inventory_changed" ? { components: error.details.components } : {}) });
|
|
799
870
|
}
|
|
800
871
|
}
|
|
801
872
|
assertCurrent();
|
|
@@ -818,7 +889,7 @@ async function reconcileOwnedSessions(context, input, interrupt, boundary) {
|
|
|
818
889
|
}
|
|
819
890
|
export function nativeInventoryVersion(directory, operations = retainedDaemonOperations(directory)) {
|
|
820
891
|
const state = JSON.parse(fs.readFileSync(path.join(directory, "daemon.json"), "utf8"));
|
|
821
|
-
const keys = ["schema_id", "daemon_id", "config", "pid", "provider_pid", "shutdown_state", "active_operation", "active_tree", "sessions", "descendants", "root_session_id", "resource_process", "provider_process", "owned_processes"];
|
|
892
|
+
const keys = ["schema_id", "daemon_id", "config", "pid", "provider_pid", "shutdown_state", "shutdown", "active_operation", "active_tree", "sessions", "descendants", "root_session_id", "resource_process", "provider_process", "owned_processes"];
|
|
822
893
|
// Only these native verbs are read-only. Unknown verbs remain guarded.
|
|
823
894
|
const productive = operations.filter(operation => !isObservationOperation(operation.operation));
|
|
824
895
|
return crypto.createHash("sha256").update(JSON.stringify([Object.fromEntries(keys.map(key => [key, state[key]])), productive])).digest("hex");
|
|
@@ -6,7 +6,7 @@ import { createEvalRunSnapshot, verifyControllerBoundarySnapshot } from "./eval-
|
|
|
6
6
|
import { observeControllerBoundary } from "./run-control.js";
|
|
7
7
|
import { assertRunMutationAllowed, recoveryGuard } from "./run-recovery.js";
|
|
8
8
|
import { appendEvent, digest, errorRecord, requireController, requireRun } from "./run-controller-state.js";
|
|
9
|
-
import { getFlowRunStatus } from "./runs.js";
|
|
9
|
+
import { getFlowRunStatus, readFlowRunExecutionBundle } from "./runs.js";
|
|
10
10
|
import { recoveryObservationBudget, withRecoveryObservation } from "./recovery-observation-budget.js";
|
|
11
11
|
import { getResourceDatabase } from "../storage/database.js";
|
|
12
12
|
import { managedProcessBinding, resourceHome } from "./managed-processes.js";
|
|
@@ -60,6 +60,7 @@ export async function captureControllerBoundary(context, row, manifest, stage, n
|
|
|
60
60
|
throw error;
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
+
const preparedBundle = readFlowRunExecutionBundle(context, scope);
|
|
63
64
|
const assertStartOwner = prepareControllerPublication(context, row, assertPhysicalOwner);
|
|
64
65
|
context.db.writeTransaction(() => {
|
|
65
66
|
assertStartOwner();
|
|
@@ -82,7 +83,7 @@ export async function captureControllerBoundary(context, row, manifest, stage, n
|
|
|
82
83
|
if (!current || !["done", "skipped"].includes(current.status) || digest(identity(current)) !== boundaryKey)
|
|
83
84
|
throw new AppError("controller_boundary_changed", "Stage identity changed after its capture fence", 1);
|
|
84
85
|
if (physical) {
|
|
85
|
-
const continuation = getFlowRunStatus(context, scope).continuation;
|
|
86
|
+
const continuation = getFlowRunStatus(context, scope, preparedBundle ?? undefined).continuation;
|
|
86
87
|
const stoppedAtTarget = nextStage === null && manifest.request.stop_after === stage.stage && continuation.kind === "start_stage";
|
|
87
88
|
if (nextStage ? continuation.kind !== "start_stage" || continuation.stage !== nextStage : continuation.kind !== "terminal" && !stoppedAtTarget)
|
|
88
89
|
throw new AppError("controller_boundary_changed", "Stage successor changed after its capture fence", 1);
|
|
@@ -123,7 +124,13 @@ export async function captureControllerBoundary(context, row, manifest, stage, n
|
|
|
123
124
|
assertCurrent();
|
|
124
125
|
context.db.run("UPDATE run_controller_operations SET receipt_json = json_set(COALESCE(receipt_json, '{}'), '$.recovery_observation', json(?)), updated_at = ? WHERE operation_id = ?", [JSON.stringify(observationBudget.state()), context.now(), operationId]);
|
|
125
126
|
let verify = () => { throw new AppError("controller_boundary_unsettled", "Boundary has no stable writer observation", 1); };
|
|
126
|
-
const observation = await withRecoveryObservation(observationBudget, () => observeControllerBoundary(context, { ...scope, controllerId: row.controller_id, operationId, assertCurrent, retainVerification: value => {
|
|
127
|
+
const observation = await withRecoveryObservation(observationBudget, () => observeControllerBoundary(context, { ...scope, controllerId: row.controller_id, operationId, assertCurrent, retainVerification: value => {
|
|
128
|
+
verify = () => {
|
|
129
|
+
value();
|
|
130
|
+
if (readFlowRunExecutionBundle(context, scope)?.sha256 !== preparedBundle?.sha256)
|
|
131
|
+
throw new AppError("controller_boundary_changed", "Frozen bundle changed before snapshot publication", 1);
|
|
132
|
+
};
|
|
133
|
+
} }));
|
|
127
134
|
if (observation.settled === true) {
|
|
128
135
|
try {
|
|
129
136
|
createEvalRunSnapshot(context, { ...scope, output, ...(nextStage ? { stageEntry: nextStage } : { candidate: true }), boundary: { controllerId: row.controller_id, operationId, boundaryKey, observation, verify } });
|
|
@@ -48,6 +48,22 @@ export function assertControllerRecoveryAllowed(context, row) {
|
|
|
48
48
|
|| !context.db.get("SELECT 1 FROM run_controls WHERE control_id = ? AND recovery_id = ? AND status = 'resuming'", [state.recovery.control_id, state.recovery.recovery_id]))
|
|
49
49
|
throw new AppError("run_control_superseded", "Controller launch no longer owns the accepted recovery", 1);
|
|
50
50
|
}
|
|
51
|
+
/** An admitted physical owner may renew while closing a terminal RUN. This
|
|
52
|
+
* does not admit a new owner or release a recovery/generation fence. */
|
|
53
|
+
export function assertControllerLeaseAllowed(context, row) {
|
|
54
|
+
const current = requireController(context, row.controller_id);
|
|
55
|
+
if (current.owner_token !== row.owner_token || current.generation !== row.generation
|
|
56
|
+
|| current.owner_pid !== process.pid || current.process_id !== row.process_id
|
|
57
|
+
|| current.process_lease_token !== row.process_lease_token || !current.process_id || !current.process_lease_token
|
|
58
|
+
|| !["running", "capturing", "waiting_for_user", "waiting_for_context", "waiting_for_children", "waiting_for_capacity", "waiting_for_merge"].includes(current.status))
|
|
59
|
+
throw new AppError("controller_owner_mismatch", "Lease renewal no longer owns this physical controller", 1);
|
|
60
|
+
const run = requireRun(context, current.project_id, current.run_id);
|
|
61
|
+
if ((["done", "failed", "cancelled"].includes(run.status) || current.status === "capturing") && launchRecord(context, current)?.value.dispatch_ready === true) {
|
|
62
|
+
assertControllerRecoveryAllowed(context, current);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
assertLaunchAllowed(context, current);
|
|
66
|
+
}
|
|
51
67
|
function retire(context, row, identity) {
|
|
52
68
|
const record = managedProcessStatus(context).find(item => item.id === identity.id);
|
|
53
69
|
if (!record || record.kind !== "run-controller" || record.owner_id !== row.controller_id || record.project_id !== row.project_id || record.run_id !== row.run_id
|
|
@@ -123,6 +139,10 @@ export function claimControllerProcess(context, row) {
|
|
|
123
139
|
throw new AppError("controller_owner_mismatch", "Managed registration is not authorized", 1);
|
|
124
140
|
const owner = birth();
|
|
125
141
|
context.db.writeTransaction(() => {
|
|
142
|
+
// Recovery fences are reconciled by serve's retained failure boundary;
|
|
143
|
+
// only terminal admission must reject before claiming a physical owner.
|
|
144
|
+
if (["done", "failed", "cancelled"].includes(requireRun(context, row.project_id, row.run_id).status))
|
|
145
|
+
throw new AppError("invalid_run_state", "A terminal RUN cannot start a physical controller", 1);
|
|
126
146
|
const claimed = context.db.run("UPDATE run_controllers SET owner_pid = ?, status = 'running', updated_at = ? WHERE controller_id = ? AND owner_token = ? AND status = 'starting' AND owner_pid IS NULL", [process.pid, context.now(), row.controller_id, row.owner_token]);
|
|
127
147
|
if (claimed.changes !== 1)
|
|
128
148
|
throw new AppError("controller_already_owned", "Managed owner has already claimed this launch", 1);
|
|
@@ -142,6 +162,7 @@ export function claimControllerProcess(context, row) {
|
|
|
142
162
|
}
|
|
143
163
|
}
|
|
144
164
|
export function markControllerDispatchReady(context, row) {
|
|
165
|
+
assertLaunchAllowed(context, row);
|
|
145
166
|
context.db.run("UPDATE run_controller_operations SET receipt_json = json_set(receipt_json, '$.dispatch_ready', json('true')), updated_at = ? WHERE operation_id = ?", [context.now(), launchId(row)]);
|
|
146
167
|
}
|
|
147
168
|
export async function spawnController(context, row) {
|
|
@@ -10,6 +10,7 @@ import { verifyRecoverySnapshot } from "./eval-snapshots.js";
|
|
|
10
10
|
import { recoveryGuard, resumeRunRecovery } from "./run-recovery.js";
|
|
11
11
|
import { prepareRunRecoveryDispatch, retainRecoveryCapacityIntent } from "./run-recovery-runtime.js";
|
|
12
12
|
import { runtimeHarness } from "./execution-policy.js";
|
|
13
|
+
import { selectControllerExecution } from "./controller-execution.js";
|
|
13
14
|
import { managedProcessBinding, managedProcessRuntimeHome, managedProcessStatus, processIsAlive, processTreeIsAlive, processStartedAt } from "./managed-processes.js";
|
|
14
15
|
import { requireRun, requireController, controllerReceipt, appendEvent, saveState, digest, errorRecord } from "./run-controller-state.js";
|
|
15
16
|
import { controllerAdapter, controllerEnvironment, sessionArguments } from "./run-controller-adapter.js";
|
|
@@ -74,7 +75,8 @@ export function prepareControllerResume(context, input) {
|
|
|
74
75
|
if (!guard || guard.recovery_id !== control?.recovery_id || guard.status !== "sealed" || control.status !== "settled" || !guard.capture_path || JSON.parse(guard.settlement_json ?? "{}").settled !== true)
|
|
75
76
|
throw new AppError("run_control_not_resumable", "Resume requires the current settled control and its immutable recovery capture", 1);
|
|
76
77
|
const manifest = JSON.parse(row.manifest_json), state = JSON.parse(row.state_json);
|
|
77
|
-
|
|
78
|
+
selectControllerExecution(context, run, manifest);
|
|
79
|
+
if (!path.isAbsolute(manifest.resource_home) || !path.isAbsolute(manifest.flow_executable))
|
|
78
80
|
throw new AppError("controller_manifest_invalid", "Resume requires the original frozen controller manifest", 1);
|
|
79
81
|
if (state.current_session === null || !state.sessions[state.current_session]?.id)
|
|
80
82
|
throw new AppError("recovery_owner_missing", "Resume requires the retained coordinator Session", 1);
|
|
@@ -3,7 +3,7 @@ import { AppError, errorRecord as structuredErrorRecord } from "../shared/errors
|
|
|
3
3
|
import { runtimeHarness } from "./execution-policy.js";
|
|
4
4
|
import { managedProcessRuntimeHome, managedProcessStatus, processTreeIsAlive } from "./managed-processes.js";
|
|
5
5
|
export function requireRun(context, projectId, runId) {
|
|
6
|
-
const run = context.db.get("SELECT id, project_id, project_root, workspace_root, run_root, status, index_json FROM runs WHERE project_id = ? AND id = ?", [projectId, runId]);
|
|
6
|
+
const run = context.db.get("SELECT id, project_id, flow_kind, project_root, workspace_root, run_root, status, index_json FROM runs WHERE project_id = ? AND id = ?", [projectId, runId]);
|
|
7
7
|
if (!run?.run_root)
|
|
8
8
|
throw new AppError("not_found", "Managed RUN has no materialized root", 1, { run_id: runId });
|
|
9
9
|
return run;
|