@telora/daemon 0.22.146 → 0.22.169
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/build-info.json +15 -3
- package/dist/audit/audit-phase.d.ts +11 -1
- package/dist/audit/audit-phase.d.ts.map +1 -1
- package/dist/audit/audit-phase.js +2 -14
- package/dist/audit/audit-phase.js.map +1 -1
- package/dist/audit/audit-prompt.d.ts +7 -4
- package/dist/audit/audit-prompt.d.ts.map +1 -1
- package/dist/audit/audit-prompt.js +34 -23
- package/dist/audit/audit-prompt.js.map +1 -1
- package/dist/backends/codex/codex-mcp-proxy.d.ts +4 -6
- package/dist/backends/codex/codex-mcp-proxy.d.ts.map +1 -1
- package/dist/backends/codex/codex-mcp-proxy.js.map +1 -1
- package/dist/backends/codex/mcp-preflight.d.ts +2 -2
- package/dist/backends/codex/mcp-preflight.d.ts.map +1 -1
- package/dist/backends/codex/mcp-preflight.js.map +1 -1
- package/dist/completion/advancement-stall-watchdog.d.ts +12 -0
- package/dist/completion/advancement-stall-watchdog.d.ts.map +1 -0
- package/dist/completion/advancement-stall-watchdog.js +79 -0
- package/dist/completion/advancement-stall-watchdog.js.map +1 -0
- package/dist/completion/arm-visible-progress-inputs.d.ts +51 -0
- package/dist/completion/arm-visible-progress-inputs.d.ts.map +1 -0
- package/dist/completion/arm-visible-progress-inputs.js +19 -0
- package/dist/completion/arm-visible-progress-inputs.js.map +1 -0
- package/dist/completion/completion-phase-registry.d.ts +1 -0
- package/dist/completion/completion-phase-registry.d.ts.map +1 -1
- package/dist/completion/completion-phase-registry.js +7 -5
- package/dist/completion/completion-phase-registry.js.map +1 -1
- package/dist/completion/delivery-advancement-guard.d.ts.map +1 -1
- package/dist/completion/delivery-advancement-guard.js +1 -0
- package/dist/completion/delivery-advancement-guard.js.map +1 -1
- package/dist/completion/escalation-provenance.d.ts.map +1 -1
- package/dist/completion/escalation-provenance.js +5 -2
- package/dist/completion/escalation-provenance.js.map +1 -1
- package/dist/completion/event-entry-phases.d.ts.map +1 -1
- package/dist/completion/event-entry-phases.js +9 -2
- package/dist/completion/event-entry-phases.js.map +1 -1
- package/dist/completion/event-phases.d.ts +15 -8
- package/dist/completion/event-phases.d.ts.map +1 -1
- package/dist/completion/event-phases.js +26 -64
- package/dist/completion/event-phases.js.map +1 -1
- package/dist/completion/event.d.ts.map +1 -1
- package/dist/completion/event.js +7 -1
- package/dist/completion/event.js.map +1 -1
- package/dist/completion/exit-classification.d.ts +1 -1
- package/dist/completion/exit-classification.d.ts.map +1 -1
- package/dist/completion/exit-classification.js +7 -1
- package/dist/completion/exit-classification.js.map +1 -1
- package/dist/completion/review-cycle-cap.d.ts +81 -35
- package/dist/completion/review-cycle-cap.d.ts.map +1 -1
- package/dist/completion/review-cycle-cap.js +60 -132
- package/dist/completion/review-cycle-cap.js.map +1 -1
- package/dist/completion/review-exit-deps.d.ts +3 -1
- package/dist/completion/review-exit-deps.d.ts.map +1 -1
- package/dist/completion/review-exit-deps.js +3 -1
- package/dist/completion/review-exit-deps.js.map +1 -1
- package/dist/completion/review-exit-phase.d.ts.map +1 -1
- package/dist/completion/review-exit-phase.js +5 -4
- package/dist/completion/review-exit-phase.js.map +1 -1
- package/dist/completion/shutdown-attribution.d.ts +107 -0
- package/dist/completion/shutdown-attribution.d.ts.map +1 -0
- package/dist/completion/shutdown-attribution.js +106 -0
- package/dist/completion/shutdown-attribution.js.map +1 -0
- package/dist/completion/state-cascade.d.ts.map +1 -1
- package/dist/completion/state-cascade.js +3 -7
- package/dist/completion/state-cascade.js.map +1 -1
- package/dist/completion/team-lifecycle.d.ts.map +1 -1
- package/dist/completion/team-lifecycle.js +16 -0
- package/dist/completion/team-lifecycle.js.map +1 -1
- package/dist/delivery/delivery-lifecycle.d.ts.map +1 -1
- package/dist/delivery/delivery-lifecycle.js +1 -0
- package/dist/delivery/delivery-lifecycle.js.map +1 -1
- package/dist/directive/directive-assembly.d.ts +19 -1
- package/dist/directive/directive-assembly.d.ts.map +1 -1
- package/dist/directive/directive-assembly.js +15 -3
- package/dist/directive/directive-assembly.js.map +1 -1
- package/dist/directive/directive-dispatch.d.ts +67 -2
- package/dist/directive/directive-dispatch.d.ts.map +1 -1
- package/dist/directive/directive-dispatch.js +188 -45
- package/dist/directive/directive-dispatch.js.map +1 -1
- package/dist/directive/directive-executor.d.ts +2 -2
- package/dist/directive/directive-executor.d.ts.map +1 -1
- package/dist/directive/directive-executor.js +49 -12
- package/dist/directive/directive-executor.js.map +1 -1
- package/dist/directive/directive-queue.d.ts.map +1 -1
- package/dist/directive/directive-queue.js +1 -0
- package/dist/directive/directive-queue.js.map +1 -1
- package/dist/directive/stage-tracker.d.ts +20 -0
- package/dist/directive/stage-tracker.d.ts.map +1 -1
- package/dist/directive/stage-tracker.js +29 -1
- package/dist/directive/stage-tracker.js.map +1 -1
- package/dist/focus/focus-engine.d.ts +16 -0
- package/dist/focus/focus-engine.d.ts.map +1 -1
- package/dist/focus/focus-engine.js +42 -18
- package/dist/focus/focus-engine.js.map +1 -1
- package/dist/focus/focus-executor.d.ts.map +1 -1
- package/dist/focus/focus-executor.js +21 -3
- package/dist/focus/focus-executor.js.map +1 -1
- package/dist/focus/focus-phase.d.ts +11 -1
- package/dist/focus/focus-phase.d.ts.map +1 -1
- package/dist/focus/focus-phase.js +12 -2
- package/dist/focus/focus-phase.js.map +1 -1
- package/dist/focus/focus-spawn-decision.d.ts +5 -3
- package/dist/focus/focus-spawn-decision.d.ts.map +1 -1
- package/dist/focus/focus-spawn-decision.js.map +1 -1
- package/dist/focus/focus-team-state.js +1 -1
- package/dist/focus/listener-auto-advance.d.ts.map +1 -1
- package/dist/focus/listener-auto-advance.js +1 -0
- package/dist/focus/listener-auto-advance.js.map +1 -1
- package/dist/focus/listener-spawn.d.ts.map +1 -1
- package/dist/focus/listener-spawn.js +37 -13
- package/dist/focus/listener-spawn.js.map +1 -1
- package/dist/focus/listener.d.ts +15 -0
- package/dist/focus/listener.d.ts.map +1 -1
- package/dist/focus/listener.js +79 -17
- package/dist/focus/listener.js.map +1 -1
- package/dist/focus/live-arm-facts.d.ts +10 -0
- package/dist/focus/live-arm-facts.d.ts.map +1 -0
- package/dist/focus/live-arm-facts.js +47 -0
- package/dist/focus/live-arm-facts.js.map +1 -0
- package/dist/focus/recorded-new-intent.d.ts +81 -0
- package/dist/focus/recorded-new-intent.d.ts.map +1 -0
- package/dist/focus/recorded-new-intent.js +147 -0
- package/dist/focus/recorded-new-intent.js.map +1 -0
- package/dist/focus/review-cycle-cap-hold.d.ts +15 -94
- package/dist/focus/review-cycle-cap-hold.d.ts.map +1 -1
- package/dist/focus/review-cycle-cap-hold.js +54 -112
- package/dist/focus/review-cycle-cap-hold.js.map +1 -1
- package/dist/focus/session-health.d.ts +22 -1
- package/dist/focus/session-health.d.ts.map +1 -1
- package/dist/focus/session-health.js +28 -3
- package/dist/focus/session-health.js.map +1 -1
- package/dist/focus/session-process-reap.d.ts +3 -1
- package/dist/focus/session-process-reap.d.ts.map +1 -1
- package/dist/focus/session-process-reap.js +20 -0
- package/dist/focus/session-process-reap.js.map +1 -1
- package/dist/focus/stage-watermark-seeding.d.ts +39 -0
- package/dist/focus/stage-watermark-seeding.d.ts.map +1 -0
- package/dist/focus/stage-watermark-seeding.js +60 -0
- package/dist/focus/stage-watermark-seeding.js.map +1 -0
- package/dist/focus-stage-lifecycle/index.d.ts.map +1 -1
- package/dist/focus-stage-lifecycle/index.js +1 -0
- package/dist/focus-stage-lifecycle/index.js.map +1 -1
- package/dist/focus-stage-lifecycle/wedge-watchdog.d.ts +29 -1
- package/dist/focus-stage-lifecycle/wedge-watchdog.d.ts.map +1 -1
- package/dist/focus-stage-lifecycle/wedge-watchdog.js +46 -6
- package/dist/focus-stage-lifecycle/wedge-watchdog.js.map +1 -1
- package/dist/git/focus-integration-sync.d.ts +42 -3
- package/dist/git/focus-integration-sync.d.ts.map +1 -1
- package/dist/git/focus-integration-sync.js +38 -2
- package/dist/git/focus-integration-sync.js.map +1 -1
- package/dist/merge/abandoned-merge-recovery.d.ts.map +1 -1
- package/dist/merge/abandoned-merge-recovery.js +10 -0
- package/dist/merge/abandoned-merge-recovery.js.map +1 -1
- package/dist/merge/merge-back-loop.d.ts +1 -0
- package/dist/merge/merge-back-loop.d.ts.map +1 -1
- package/dist/merge/merge-back-loop.js +3 -3
- package/dist/merge/merge-back-loop.js.map +1 -1
- package/dist/prompt/focus-prompt-builder.d.ts +2 -1
- package/dist/prompt/focus-prompt-builder.d.ts.map +1 -1
- package/dist/prompt/focus-prompt-builder.js +10 -1
- package/dist/prompt/focus-prompt-builder.js.map +1 -1
- package/dist/prompt/team-prompt-base.d.ts +2 -1
- package/dist/prompt/team-prompt-base.d.ts.map +1 -1
- package/dist/prompt/team-prompt-base.js +8 -20
- package/dist/prompt/team-prompt-base.js.map +1 -1
- package/dist/prompt-sections/execution-sections.d.ts +22 -1
- package/dist/prompt-sections/execution-sections.d.ts.map +1 -1
- package/dist/prompt-sections/execution-sections.js +23 -2
- package/dist/prompt-sections/execution-sections.js.map +1 -1
- package/dist/prompt-sections/integration-reconcile-section.d.ts +45 -0
- package/dist/prompt-sections/integration-reconcile-section.d.ts.map +1 -0
- package/dist/prompt-sections/integration-reconcile-section.js +69 -0
- package/dist/prompt-sections/integration-reconcile-section.js.map +1 -0
- package/dist/prompt-sections/persona-sections.d.ts +0 -20
- package/dist/prompt-sections/persona-sections.d.ts.map +1 -1
- package/dist/prompt-sections/persona-sections.js +4 -49
- package/dist/prompt-sections/persona-sections.js.map +1 -1
- package/dist/prompt-sections/shared.d.ts +9 -0
- package/dist/prompt-sections/shared.d.ts.map +1 -1
- package/dist/prompt-sections/shared.js +2 -1
- package/dist/prompt-sections/shared.js.map +1 -1
- package/dist/prompt-sections/testing-standard.d.ts +51 -0
- package/dist/prompt-sections/testing-standard.d.ts.map +1 -0
- package/dist/prompt-sections/testing-standard.js +132 -0
- package/dist/prompt-sections/testing-standard.js.map +1 -0
- package/dist/queries/agent-resolvable-promotion.d.ts.map +1 -1
- package/dist/queries/agent-resolvable-promotion.js +7 -5
- package/dist/queries/agent-resolvable-promotion.js.map +1 -1
- package/dist/queries/arm-covered-targets.d.ts +84 -0
- package/dist/queries/arm-covered-targets.d.ts.map +1 -0
- package/dist/queries/arm-covered-targets.js +129 -0
- package/dist/queries/arm-covered-targets.js.map +1 -0
- package/dist/queries/deliveries.d.ts +3 -0
- package/dist/queries/deliveries.d.ts.map +1 -1
- package/dist/queries/deliveries.js +10 -3
- package/dist/queries/deliveries.js.map +1 -1
- package/dist/queries/delivery-gate-state.d.ts +10 -0
- package/dist/queries/delivery-gate-state.d.ts.map +1 -1
- package/dist/queries/delivery-gate-state.js +1 -1
- package/dist/queries/delivery-gate-state.js.map +1 -1
- package/dist/queries/delivery-write-discipline.d.ts.map +1 -1
- package/dist/queries/delivery-write-discipline.js +2 -1
- package/dist/queries/delivery-write-discipline.js.map +1 -1
- package/dist/queries/directive-undelivered.d.ts +104 -0
- package/dist/queries/directive-undelivered.d.ts.map +1 -0
- package/dist/queries/directive-undelivered.js +162 -0
- package/dist/queries/directive-undelivered.js.map +1 -0
- package/dist/queries/ended-arm.d.ts +47 -0
- package/dist/queries/ended-arm.d.ts.map +1 -0
- package/dist/queries/ended-arm.js +35 -0
- package/dist/queries/ended-arm.js.map +1 -0
- package/dist/queries/escalation-consumption.d.ts.map +1 -1
- package/dist/queries/escalation-consumption.js +0 -1
- package/dist/queries/escalation-consumption.js.map +1 -1
- package/dist/queries/focus-discovery.d.ts +30 -0
- package/dist/queries/focus-discovery.d.ts.map +1 -1
- package/dist/queries/focus-discovery.js +4 -0
- package/dist/queries/focus-discovery.js.map +1 -1
- package/dist/queries/focus-integration-conflict.d.ts +78 -0
- package/dist/queries/focus-integration-conflict.d.ts.map +1 -0
- package/dist/queries/focus-integration-conflict.js +136 -0
- package/dist/queries/focus-integration-conflict.js.map +1 -0
- package/dist/queries/focuses.d.ts +6 -3
- package/dist/queries/focuses.d.ts.map +1 -1
- package/dist/queries/focuses.js +14 -5
- package/dist/queries/focuses.js.map +1 -1
- package/dist/queries/incident-correlation.d.ts +1 -2
- package/dist/queries/incident-correlation.d.ts.map +1 -1
- package/dist/queries/incident-correlation.js +1 -3
- package/dist/queries/incident-correlation.js.map +1 -1
- package/dist/queries/issues.d.ts.map +1 -1
- package/dist/queries/issues.js +1 -0
- package/dist/queries/issues.js.map +1 -1
- package/dist/queries/local-models.js +1 -1
- package/dist/queries/local-models.js.map +1 -1
- package/dist/queries/recorded-targets-record.d.ts +46 -0
- package/dist/queries/recorded-targets-record.d.ts.map +1 -0
- package/dist/queries/recorded-targets-record.js +29 -0
- package/dist/queries/recorded-targets-record.js.map +1 -0
- package/dist/queries/schemas-local-model.d.ts +47 -0
- package/dist/queries/schemas-local-model.d.ts.map +1 -0
- package/dist/queries/schemas-local-model.js +57 -0
- package/dist/queries/schemas-local-model.js.map +1 -0
- package/dist/queries/schemas.d.ts +14 -32
- package/dist/queries/schemas.d.ts.map +1 -1
- package/dist/queries/schemas.js +24 -41
- package/dist/queries/schemas.js.map +1 -1
- package/dist/queries/sessions.d.ts +120 -12
- package/dist/queries/sessions.d.ts.map +1 -1
- package/dist/queries/sessions.js +104 -11
- package/dist/queries/sessions.js.map +1 -1
- package/dist/queries/shared.d.ts +1 -0
- package/dist/queries/shared.d.ts.map +1 -1
- package/dist/queries/shared.js +1 -1
- package/dist/queries/shared.js.map +1 -1
- package/dist/queries/system-session.d.ts.map +1 -1
- package/dist/queries/system-session.js +3 -1
- package/dist/queries/system-session.js.map +1 -1
- package/dist/queries/verification.js +1 -1
- package/dist/queries/workflows.d.ts.map +1 -1
- package/dist/queries/workflows.js +1 -0
- package/dist/queries/workflows.js.map +1 -1
- package/dist/resolvers/assembly-engine.d.ts +33 -1
- package/dist/resolvers/assembly-engine.d.ts.map +1 -1
- package/dist/resolvers/assembly-engine.js +74 -23
- package/dist/resolvers/assembly-engine.js.map +1 -1
- package/dist/resolvers/compose/loop-delivery-index-compose.d.ts +23 -3
- package/dist/resolvers/compose/loop-delivery-index-compose.d.ts.map +1 -1
- package/dist/resolvers/compose/loop-delivery-index-compose.js +90 -43
- package/dist/resolvers/compose/loop-delivery-index-compose.js.map +1 -1
- package/dist/resolvers/delivery-listing.d.ts +27 -0
- package/dist/resolvers/delivery-listing.d.ts.map +1 -1
- package/dist/resolvers/delivery-listing.js +52 -36
- package/dist/resolvers/delivery-listing.js.map +1 -1
- package/dist/resolvers/loop-delivery-index.d.ts +8 -0
- package/dist/resolvers/loop-delivery-index.d.ts.map +1 -1
- package/dist/resolvers/loop-delivery-index.js +68 -7
- package/dist/resolvers/loop-delivery-index.js.map +1 -1
- package/dist/resolvers/shared/loop.d.ts +7 -0
- package/dist/resolvers/shared/loop.d.ts.map +1 -1
- package/dist/resolvers/shared/loop.js.map +1 -1
- package/dist/resolvers/wiki-topic.d.ts +6 -5
- package/dist/resolvers/wiki-topic.d.ts.map +1 -1
- package/dist/resolvers/wiki-topic.js +43 -27
- package/dist/resolvers/wiki-topic.js.map +1 -1
- package/dist/session/session-lineage.d.ts.map +1 -1
- package/dist/session/session-lineage.js +1 -0
- package/dist/session/session-lineage.js.map +1 -1
- package/dist/shell/crash-recovery.d.ts +4 -2
- package/dist/shell/crash-recovery.d.ts.map +1 -1
- package/dist/shell/crash-recovery.js +15 -14
- package/dist/shell/crash-recovery.js.map +1 -1
- package/dist/shell/daemon-instance.d.ts +176 -0
- package/dist/shell/daemon-instance.d.ts.map +1 -0
- package/dist/shell/daemon-instance.js +281 -0
- package/dist/shell/daemon-instance.js.map +1 -0
- package/dist/shell/graceful-shutdown.d.ts +12 -1
- package/dist/shell/graceful-shutdown.d.ts.map +1 -1
- package/dist/shell/graceful-shutdown.js +7 -4
- package/dist/shell/graceful-shutdown.js.map +1 -1
- package/dist/shell/shutdown-ownership.d.ts +34 -0
- package/dist/shell/shutdown-ownership.d.ts.map +1 -0
- package/dist/shell/shutdown-ownership.js +51 -0
- package/dist/shell/shutdown-ownership.js.map +1 -0
- package/dist/shell/unified-shell.d.ts.map +1 -1
- package/dist/shell/unified-shell.js +12 -5
- package/dist/shell/unified-shell.js.map +1 -1
- package/dist/shell/worker-pidfile.d.ts +127 -11
- package/dist/shell/worker-pidfile.d.ts.map +1 -1
- package/dist/shell/worker-pidfile.js +349 -58
- package/dist/shell/worker-pidfile.js.map +1 -1
- package/dist/spawn/helpers.d.ts.map +1 -1
- package/dist/spawn/helpers.js +4 -2
- package/dist/spawn/helpers.js.map +1 -1
- package/dist/spawn/lead-continuation.d.ts.map +1 -1
- package/dist/spawn/lead-continuation.js +8 -2
- package/dist/spawn/lead-continuation.js.map +1 -1
- package/dist/spawn/lead-launch.d.ts +17 -1
- package/dist/spawn/lead-launch.d.ts.map +1 -1
- package/dist/spawn/lead-launch.js +56 -7
- package/dist/spawn/lead-launch.js.map +1 -1
- package/dist/spawn/lead-process-ownership.d.ts +5 -4
- package/dist/spawn/lead-process-ownership.d.ts.map +1 -1
- package/dist/spawn/lead-process-ownership.js +7 -4
- package/dist/spawn/lead-process-ownership.js.map +1 -1
- package/dist/spawn/lead-send.d.ts +97 -0
- package/dist/spawn/lead-send.d.ts.map +1 -0
- package/dist/spawn/lead-send.js +80 -0
- package/dist/spawn/lead-send.js.map +1 -0
- package/dist/spawn/lifecycle.d.ts +0 -12
- package/dist/spawn/lifecycle.d.ts.map +1 -1
- package/dist/spawn/lifecycle.js +76 -19
- package/dist/spawn/lifecycle.js.map +1 -1
- package/dist/spawn/liveness.d.ts.map +1 -1
- package/dist/spawn/liveness.js +3 -1
- package/dist/spawn/liveness.js.map +1 -1
- package/dist/spawn/one-shot-process-ownership.d.ts +46 -2
- package/dist/spawn/one-shot-process-ownership.d.ts.map +1 -1
- package/dist/spawn/one-shot-process-ownership.js +78 -3
- package/dist/spawn/one-shot-process-ownership.js.map +1 -1
- package/dist/spawn/process-group-kill.d.ts.map +1 -1
- package/dist/spawn/process-group-kill.js +15 -2
- package/dist/spawn/process-group-kill.js.map +1 -1
- package/dist/spawn/process-spawn.d.ts +5 -15
- package/dist/spawn/process-spawn.d.ts.map +1 -1
- package/dist/spawn/process-spawn.js +8 -1
- package/dist/spawn/process-spawn.js.map +1 -1
- package/dist/spawn/prompt-assembly.d.ts +35 -2
- package/dist/spawn/prompt-assembly.d.ts.map +1 -1
- package/dist/spawn/prompt-assembly.js +67 -44
- package/dist/spawn/prompt-assembly.js.map +1 -1
- package/dist/spawn/spawn-decision.d.ts +25 -1
- package/dist/spawn/spawn-decision.d.ts.map +1 -1
- package/dist/spawn/spawn-decision.js +19 -3
- package/dist/spawn/spawn-decision.js.map +1 -1
- package/dist/spawn/worktree-session.d.ts +49 -2
- package/dist/spawn/worktree-session.d.ts.map +1 -1
- package/dist/spawn/worktree-session.js +59 -5
- package/dist/spawn/worktree-session.js.map +1 -1
- package/dist/testing/fixtures/conflicting-repo.d.ts +30 -0
- package/dist/testing/fixtures/conflicting-repo.d.ts.map +1 -0
- package/dist/testing/fixtures/conflicting-repo.js +89 -0
- package/dist/testing/fixtures/conflicting-repo.js.map +1 -0
- package/dist/testing/fixtures/index.d.ts.map +1 -1
- package/dist/testing/fixtures/index.js +2 -1
- package/dist/testing/fixtures/index.js.map +1 -1
- package/dist/testing/fixtures/pending-spawn-world.js +2 -2
- package/dist/testing/fixtures/pending-spawn-world.js.map +1 -1
- package/dist/types/exit-category.d.ts +59 -0
- package/dist/types/exit-category.d.ts.map +1 -0
- package/dist/types/exit-category.js +133 -0
- package/dist/types/exit-category.js.map +1 -0
- package/dist/types/focus.d.ts +19 -0
- package/dist/types/focus.d.ts.map +1 -1
- package/dist/types/index.js +1 -1
- package/dist/types/session.d.ts +5 -1
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/session.js +1 -1
- package/dist/types/session.js.map +1 -1
- package/dist/verify/injection-stamp.d.ts +30 -0
- package/dist/verify/injection-stamp.d.ts.map +1 -1
- package/dist/verify/injection-stamp.js +37 -1
- package/dist/verify/injection-stamp.js.map +1 -1
- package/dist/verify/recorded-targets.d.ts +103 -0
- package/dist/verify/recorded-targets.d.ts.map +1 -0
- package/dist/verify/recorded-targets.js +159 -0
- package/dist/verify/recorded-targets.js.map +1 -0
- package/dist/verify/stall-detector-config.d.ts +5 -1
- package/dist/verify/stall-detector-config.d.ts.map +1 -1
- package/dist/verify/stall-detector-config.js +5 -1
- package/dist/verify/stall-detector-config.js.map +1 -1
- package/dist/verify/verification-deps-default.d.ts.map +1 -1
- package/dist/verify/verification-deps-default.js +15 -2
- package/dist/verify/verification-deps-default.js.map +1 -1
- package/dist/verify/verification-deps.d.ts +17 -1
- package/dist/verify/verification-deps.d.ts.map +1 -1
- package/dist/verify/verification-engine.d.ts.map +1 -1
- package/dist/verify/verification-engine.js +51 -47
- package/dist/verify/verification-engine.js.map +1 -1
- package/dist/verify/verify-restatement.d.ts +27 -33
- package/dist/verify/verify-restatement.d.ts.map +1 -1
- package/dist/verify/verify-restatement.js +74 -1
- package/dist/verify/verify-restatement.js.map +1 -1
- package/dist/worktree/state.d.ts +75 -1
- package/dist/worktree/state.d.ts.map +1 -1
- package/dist/worktree/state.js +89 -17
- package/dist/worktree/state.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import type { AgentSession } from '../types/index.js';
|
|
2
|
+
/** The parts of a parsed instance id. */
|
|
3
|
+
export interface DaemonInstance {
|
|
4
|
+
hostname: string;
|
|
5
|
+
pid: number;
|
|
6
|
+
/** Kernel start time of the daemon pid; null when the platform cannot supply one. */
|
|
7
|
+
startIdentity: string | null;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* This daemon instance's id. Stamped onto every session it creates, and the
|
|
11
|
+
* identity its own crash recovery compares against.
|
|
12
|
+
*/
|
|
13
|
+
export declare function daemonInstanceId(): string;
|
|
14
|
+
/** Reset the memo. Test seam only -- a daemon's identity never changes in life. */
|
|
15
|
+
export declare function resetDaemonInstanceIdCache(): void;
|
|
16
|
+
/**
|
|
17
|
+
* Parse an instance id, or null when it is absent or malformed. A row written
|
|
18
|
+
* by a daemon predating this column parses to null and is treated as
|
|
19
|
+
* unattributable -- never as "gone".
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseDaemonInstanceId(id: string | null | undefined): DaemonInstance | null;
|
|
22
|
+
/**
|
|
23
|
+
* What a session row's owning instance is doing now.
|
|
24
|
+
*
|
|
25
|
+
* - `self` -- this daemon owns it. Never reap: the team is ours and live.
|
|
26
|
+
* - `peer_alive` -- another daemon instance on this host is still running. Its
|
|
27
|
+
* sessions are its business; reaping them is the defect.
|
|
28
|
+
* - `gone` -- the owning instance is not running. Its teams stopped with
|
|
29
|
+
* it, so its rows are safe to close.
|
|
30
|
+
* - `unknown` -- no id, a malformed id, or an id from another host whose pid
|
|
31
|
+
* we cannot probe. FAILS SAFE: not reaped.
|
|
32
|
+
*/
|
|
33
|
+
export type InstanceLiveness = 'self' | 'peer_alive' | 'gone' | 'unknown';
|
|
34
|
+
/** Probes `classifyOwningInstance` reaches, injectable for tests. */
|
|
35
|
+
export interface InstanceProbeDeps {
|
|
36
|
+
selfId: string;
|
|
37
|
+
selfHostname: string;
|
|
38
|
+
isProcessAlive: (pid: number) => boolean;
|
|
39
|
+
readStartIdentity: (pid: number) => string | null;
|
|
40
|
+
}
|
|
41
|
+
/** Real probes against this host. */
|
|
42
|
+
export declare function defaultInstanceProbes(): InstanceProbeDeps;
|
|
43
|
+
/**
|
|
44
|
+
* Classify the instance that owns a session row.
|
|
45
|
+
*
|
|
46
|
+
* Every branch that is not `gone` leaves the row alone, because only `gone` is
|
|
47
|
+
* positive evidence that the team the row represents has stopped. Recovery
|
|
48
|
+
* would rather leave a stale row than falsify a live one: a stale `running` row
|
|
49
|
+
* is visible and self-correcting on the next sweep, whereas a live session
|
|
50
|
+
* marked `failed` silently tells every reader -- including "is this worktree
|
|
51
|
+
* being written?" -- that a running team is not there.
|
|
52
|
+
*/
|
|
53
|
+
export declare function classifyOwningInstance(instanceId: string | null | undefined, deps: InstanceProbeDeps): InstanceLiveness;
|
|
54
|
+
/**
|
|
55
|
+
* What the SESSION's own process is doing -- the second proof, about the team
|
|
56
|
+
* rather than the daemon that spawned it.
|
|
57
|
+
*
|
|
58
|
+
* - `alive` -- this session's recorded lead is still running.
|
|
59
|
+
* - `stopped` -- it is not: gone, or its pid was recycled onto an
|
|
60
|
+
* unrelated process.
|
|
61
|
+
* - `no_process_recorded` -- the host's lead record is readable and names no
|
|
62
|
+
* lead for this session, so none was ever spawned.
|
|
63
|
+
* - `unprovable` -- nothing can be shown: the record could not be read,
|
|
64
|
+
* or it names a process whose identity cannot be
|
|
65
|
+
* verified (a reservation, a pid with no recorded or
|
|
66
|
+
* no readable start identity). An anonymous live pid
|
|
67
|
+
* is NOT an alive team.
|
|
68
|
+
*/
|
|
69
|
+
export type SessionProcessLiveness = 'alive' | 'stopped' | 'no_process_recorded' | 'unprovable';
|
|
70
|
+
/** Process probes for the session-liveness check, injectable for tests. */
|
|
71
|
+
export interface SessionProcessProbes {
|
|
72
|
+
isProcessAlive: (pid: number) => boolean;
|
|
73
|
+
isProcessGroupAlive: (pgid: number) => boolean;
|
|
74
|
+
readStartIdentity: (pid: number) => string | null;
|
|
75
|
+
/**
|
|
76
|
+
* The host's durable lead records by session id, or null when they cannot be
|
|
77
|
+
* read. Null is UNKNOWN, never "nothing is running".
|
|
78
|
+
*/
|
|
79
|
+
leadRecordsBySession: () => Map<string, {
|
|
80
|
+
pid: number;
|
|
81
|
+
startIdentity?: string;
|
|
82
|
+
pending?: true;
|
|
83
|
+
}> | null;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Real probes against this host. The lead-record read is memoized PER PROBE
|
|
87
|
+
* OBJECT: a sweep classifying many rows reads the file once, and every row in
|
|
88
|
+
* that sweep is judged against the same snapshot rather than a record that may
|
|
89
|
+
* change between rows.
|
|
90
|
+
*/
|
|
91
|
+
export declare function defaultSessionProcessProbes(): SessionProcessProbes;
|
|
92
|
+
/**
|
|
93
|
+
* What this record's process is doing: running, gone, or not decidable.
|
|
94
|
+
*
|
|
95
|
+
* The third answer is the one that was missing. A live pid ALONE does not
|
|
96
|
+
* identify our lead, so treating "alive" as proof let an identity-less record
|
|
97
|
+
* -- or one whose identity could not be read -- nominate whatever process
|
|
98
|
+
* happens to hold that number as the session's writer. That is how a stale row
|
|
99
|
+
* keeps reading `running` and invents a writer for a quiescent worktree.
|
|
100
|
+
*
|
|
101
|
+
* So: alive AND verifiably ours -> `alive`. Provably absent -> `gone`. Anything
|
|
102
|
+
* in between -- no recorded identity, an unreadable live identity, a pid we
|
|
103
|
+
* cannot probe -- is `indeterminate`, and each caller applies its OWN safe
|
|
104
|
+
* direction to it rather than inheriting one that only suits recovery.
|
|
105
|
+
*/
|
|
106
|
+
export type RecordedProcessState = 'alive' | 'gone' | 'indeterminate';
|
|
107
|
+
export declare function classifyRecordedProcess(record: {
|
|
108
|
+
pid: number;
|
|
109
|
+
startIdentity?: string;
|
|
110
|
+
pending?: true;
|
|
111
|
+
}, probes: SessionProcessProbes): RecordedProcessState;
|
|
112
|
+
/**
|
|
113
|
+
* Classify the session's own process, from the host's DURABLE LEAD RECORD rather
|
|
114
|
+
* than from the session row's pid.
|
|
115
|
+
*
|
|
116
|
+
* The row's pid cannot answer this, for two reasons found in review:
|
|
117
|
+
*
|
|
118
|
+
* 1. It arrives TOO LATE. `launchLeadProcess` spawns the detached lead and
|
|
119
|
+
* records it, and only afterwards does `spawnTeamProcess` persist
|
|
120
|
+
* `{ status: 'running', pid }`. A hard kill or a failed API write in that
|
|
121
|
+
* window leaves a `starting` row with a null pid AND a live team. Reading
|
|
122
|
+
* that as "no process was spawned" closes a row whose team is running.
|
|
123
|
+
*
|
|
124
|
+
* 2. It is UNVERIFIABLE. A bare pid says nothing about WHICH process holds it
|
|
125
|
+
* now, so an unrelated process that recycled the number would keep a stale
|
|
126
|
+
* row `running` -- and `isWorktreeBeingWritten` would report a writer that
|
|
127
|
+
* does not exist.
|
|
128
|
+
*
|
|
129
|
+
* The lead record fixes both: it is written BEFORE the row's pid, and it carries
|
|
130
|
+
* the spawn-time start identity. Its absence is only evidence when the record
|
|
131
|
+
* itself is readable; an unreadable record is `unprovable`, never `stopped`.
|
|
132
|
+
*/
|
|
133
|
+
export declare function classifySessionProcess(sessionId: string, probes: SessionProcessProbes): SessionProcessLiveness;
|
|
134
|
+
/**
|
|
135
|
+
* Split the scoped candidates into the ones recovery may close and the ones it
|
|
136
|
+
* must leave alone, with the reason it spared each group.
|
|
137
|
+
*
|
|
138
|
+
* TWO proofs are required, and a row is closed only when both agree.
|
|
139
|
+
*
|
|
140
|
+
* 1. Its OWNER is gone. The host scope answers "whose machine", but a machine
|
|
141
|
+
* is not an owner: two daemon instances share a host. Our own live session,
|
|
142
|
+
* a peer instance still running, an unattributable or remote id -- all spared.
|
|
143
|
+
*
|
|
144
|
+
* 2. Its own PROCESS is gone. A dead owner does NOT imply a stopped team: team
|
|
145
|
+
* leads are spawned detached and survive a hard-killed daemon, and the boot
|
|
146
|
+
* reap that ends them is best-effort by design (a missing or malformed
|
|
147
|
+
* pidfile yields no entries; a failed kill is swallowed). So a live team
|
|
148
|
+
* whose daemon was `kill -9`ed reaches this pass, and closing its row is
|
|
149
|
+
* exactly the falsification the criterion forbids.
|
|
150
|
+
*
|
|
151
|
+
* The asymmetry is deliberate. A stale `running` row is visible and the next
|
|
152
|
+
* sweep re-examines it. A LIVE session marked `failed` is invisible and wrong:
|
|
153
|
+
* it tells every reader -- the dashboard, and `isWorktreeBeingWritten` -- that a
|
|
154
|
+
* running team is not there, which is how a worktree under active write comes to
|
|
155
|
+
* read as quiescent. This pass never kills anything to make its own proof true;
|
|
156
|
+
* ending a surviving orphan belongs to the identity-verified reap paths
|
|
157
|
+
* (`reapAndClearWorkerPidfile`, the untracked-lead sweep), and once they do, the
|
|
158
|
+
* next sweep reads the process as stopped and closes the row.
|
|
159
|
+
*/
|
|
160
|
+
export declare function partitionReapableSessions(sessions: readonly AgentSession[], probes: InstanceProbeDeps, processProbes?: SessionProcessProbes): {
|
|
161
|
+
reapable: AgentSession[];
|
|
162
|
+
spared: Map<string, string[]>;
|
|
163
|
+
};
|
|
164
|
+
/** Where the two probe families come from; real host probes when unsupplied. */
|
|
165
|
+
export interface RecoveryProbeSource {
|
|
166
|
+
instanceProbes?: () => InstanceProbeDeps;
|
|
167
|
+
sessionProcessProbes?: () => SessionProcessProbes;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* The subset of `sessions` crash recovery may close, logging what it spared and
|
|
171
|
+
* why. The one call recovery makes: everything it returns has a provably
|
|
172
|
+
* stopped owner, and everything it withheld is named in the log rather than
|
|
173
|
+
* silently dropped.
|
|
174
|
+
*/
|
|
175
|
+
export declare function reconcileOwnedSessions(sessions: readonly AgentSession[], probeSource?: RecoveryProbeSource): AgentSession[];
|
|
176
|
+
//# sourceMappingURL=daemon-instance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon-instance.d.ts","sourceRoot":"","sources":["../../src/shell/daemon-instance.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
// PINNED-BY-BEGIN (generated by gen:pinned-by -- do not hand-edit; checked by lint:pinned-by)
|
|
2
|
+
// Pinned by: packages/daemon/src/queries/session-create-ownership.test.ts,
|
|
3
|
+
// packages/daemon/src/shell/crash-recovery-session-scope.test.ts,
|
|
4
|
+
// packages/daemon/src/shell/daemon-instance.test.ts, packages/daemon/src/worktree/worktree-state.test.ts
|
|
5
|
+
// PINNED-BY-END
|
|
6
|
+
/**
|
|
7
|
+
* Daemon INSTANCE identity -- which running daemon owns a session row.
|
|
8
|
+
*
|
|
9
|
+
* `daemon_hostname` answers "which machine", which is enough to keep one host's
|
|
10
|
+
* crash recovery off another host's live work. It is NOT enough to separate two
|
|
11
|
+
* daemon instances on the SAME host and product: to that scope they are the
|
|
12
|
+
* same owner, so one's recovery sweep would mark the other's running sessions
|
|
13
|
+
* failed. It is also not enough to answer the question recovery actually has to
|
|
14
|
+
* answer before it writes `failed` -- has the team this row represents actually
|
|
15
|
+
* stopped? -- because a hostname says nothing about liveness.
|
|
16
|
+
*
|
|
17
|
+
* An instance id is `<hostname>:<pid>:<startIdentity>`:
|
|
18
|
+
*
|
|
19
|
+
* - `pid` is the daemon process, comparable on its own host.
|
|
20
|
+
* - `startIdentity` is the kernel's start time for that pid (/proc stat field
|
|
21
|
+
* 22 on Linux). It makes the id PID-REUSE SAFE: a new process that happens
|
|
22
|
+
* to land on a dead daemon's pid has a different start time, so it is not
|
|
23
|
+
* mistaken for the instance that owned those rows.
|
|
24
|
+
*
|
|
25
|
+
* The id is therefore the thing crash recovery can probe: "is the daemon that
|
|
26
|
+
* wrote this row still running?" A row whose owner is alive, or whose owner
|
|
27
|
+
* cannot be determined, is left alone.
|
|
28
|
+
*
|
|
29
|
+
* A dead owner is NOT on its own proof that the row's team stopped. Team leads
|
|
30
|
+
* are spawned DETACHED, so they survive a hard-killed daemon; the boot reap
|
|
31
|
+
* that ends them (`reapAndClearWorkerPidfile`, unified-shell) is explicitly
|
|
32
|
+
* best-effort -- a missing or malformed pidfile yields no entries and a failed
|
|
33
|
+
* kill is swallowed. So the destructive pass needs a SECOND proof, about the
|
|
34
|
+
* session's own process: `classifySessionProcess`. Both must agree before a row
|
|
35
|
+
* is closed.
|
|
36
|
+
*
|
|
37
|
+
* @module shell/daemon-instance
|
|
38
|
+
*/
|
|
39
|
+
import { hostname } from 'node:os';
|
|
40
|
+
import { createLogger } from './log.js';
|
|
41
|
+
import { isProcessAlive } from '../spawn/liveness.js';
|
|
42
|
+
import { readProcessStartIdentity, isProcessGroupAlive } from '../spawn/process-group-kill.js';
|
|
43
|
+
import { readLeadRecordsBySession } from './worker-pidfile.js';
|
|
44
|
+
const log = createLogger({ module: 'daemon-instance' });
|
|
45
|
+
/** Separator between instance-id parts. Hostnames cannot contain ':'. */
|
|
46
|
+
const SEP = ':';
|
|
47
|
+
/** Memoized so every read within one daemon run returns the same identity. */
|
|
48
|
+
let cachedId = null;
|
|
49
|
+
/**
|
|
50
|
+
* This daemon instance's id. Stamped onto every session it creates, and the
|
|
51
|
+
* identity its own crash recovery compares against.
|
|
52
|
+
*/
|
|
53
|
+
export function daemonInstanceId() {
|
|
54
|
+
if (cachedId === null) {
|
|
55
|
+
const start = readProcessStartIdentity(process.pid);
|
|
56
|
+
cachedId = [hostname(), String(process.pid), start ?? 'unknown'].join(SEP);
|
|
57
|
+
}
|
|
58
|
+
return cachedId;
|
|
59
|
+
}
|
|
60
|
+
/** Reset the memo. Test seam only -- a daemon's identity never changes in life. */
|
|
61
|
+
export function resetDaemonInstanceIdCache() {
|
|
62
|
+
cachedId = null;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Parse an instance id, or null when it is absent or malformed. A row written
|
|
66
|
+
* by a daemon predating this column parses to null and is treated as
|
|
67
|
+
* unattributable -- never as "gone".
|
|
68
|
+
*/
|
|
69
|
+
export function parseDaemonInstanceId(id) {
|
|
70
|
+
if (typeof id !== 'string' || id.length === 0)
|
|
71
|
+
return null;
|
|
72
|
+
const parts = id.split(SEP);
|
|
73
|
+
if (parts.length !== 3)
|
|
74
|
+
return null;
|
|
75
|
+
const [host, pidText, start] = parts;
|
|
76
|
+
if (!host || !pidText)
|
|
77
|
+
return null;
|
|
78
|
+
// STRICT: Number.parseInt('200junk') is 200, which would have us probe an
|
|
79
|
+
// unintended pid on the strength of a malformed id. Require all digits.
|
|
80
|
+
if (!/^[0-9]+$/.test(pidText))
|
|
81
|
+
return null;
|
|
82
|
+
const pid = Number.parseInt(pidText, 10);
|
|
83
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
84
|
+
return null;
|
|
85
|
+
return { hostname: host, pid, startIdentity: start === 'unknown' ? null : (start ?? null) };
|
|
86
|
+
}
|
|
87
|
+
/** Real probes against this host. */
|
|
88
|
+
export function defaultInstanceProbes() {
|
|
89
|
+
return {
|
|
90
|
+
selfId: daemonInstanceId(),
|
|
91
|
+
selfHostname: hostname(),
|
|
92
|
+
isProcessAlive,
|
|
93
|
+
readStartIdentity: readProcessStartIdentity,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Classify the instance that owns a session row.
|
|
98
|
+
*
|
|
99
|
+
* Every branch that is not `gone` leaves the row alone, because only `gone` is
|
|
100
|
+
* positive evidence that the team the row represents has stopped. Recovery
|
|
101
|
+
* would rather leave a stale row than falsify a live one: a stale `running` row
|
|
102
|
+
* is visible and self-correcting on the next sweep, whereas a live session
|
|
103
|
+
* marked `failed` silently tells every reader -- including "is this worktree
|
|
104
|
+
* being written?" -- that a running team is not there.
|
|
105
|
+
*/
|
|
106
|
+
export function classifyOwningInstance(instanceId, deps) {
|
|
107
|
+
if (instanceId === deps.selfId)
|
|
108
|
+
return 'self';
|
|
109
|
+
const parsed = parseDaemonInstanceId(instanceId);
|
|
110
|
+
if (parsed === null)
|
|
111
|
+
return 'unknown';
|
|
112
|
+
// A pid from another machine is not a pid we can probe. The host scope should
|
|
113
|
+
// already have excluded it; if one arrives anyway, it is unknown, not gone.
|
|
114
|
+
if (parsed.hostname !== deps.selfHostname)
|
|
115
|
+
return 'unknown';
|
|
116
|
+
if (!deps.isProcessAlive(parsed.pid))
|
|
117
|
+
return 'gone';
|
|
118
|
+
// The pid is alive -- but is it the SAME process, or a reused pid? A start
|
|
119
|
+
// identity that disagrees means the original instance died and its pid was
|
|
120
|
+
// recycled, so the owner IS gone.
|
|
121
|
+
const liveStart = deps.readStartIdentity(parsed.pid);
|
|
122
|
+
if (parsed.startIdentity !== null && liveStart !== null && parsed.startIdentity !== liveStart) {
|
|
123
|
+
return 'gone';
|
|
124
|
+
}
|
|
125
|
+
// Alive, and nothing contradicts that it is the owner. Leave it.
|
|
126
|
+
return 'peer_alive';
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Real probes against this host. The lead-record read is memoized PER PROBE
|
|
130
|
+
* OBJECT: a sweep classifying many rows reads the file once, and every row in
|
|
131
|
+
* that sweep is judged against the same snapshot rather than a record that may
|
|
132
|
+
* change between rows.
|
|
133
|
+
*/
|
|
134
|
+
export function defaultSessionProcessProbes() {
|
|
135
|
+
let cached;
|
|
136
|
+
return {
|
|
137
|
+
isProcessAlive,
|
|
138
|
+
isProcessGroupAlive,
|
|
139
|
+
readStartIdentity: readProcessStartIdentity,
|
|
140
|
+
leadRecordsBySession: () => {
|
|
141
|
+
if (cached === undefined)
|
|
142
|
+
cached = readLeadRecordsBySession();
|
|
143
|
+
return cached;
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
export function classifyRecordedProcess(record, probes) {
|
|
148
|
+
// A RESERVATION: the record exists because a spawn was about to happen. It
|
|
149
|
+
// names no pid yet, so nothing about it is decidable.
|
|
150
|
+
if (record.pending)
|
|
151
|
+
return 'indeterminate';
|
|
152
|
+
const { pid } = record;
|
|
153
|
+
if (!Number.isInteger(pid) || pid <= 1)
|
|
154
|
+
return 'indeterminate';
|
|
155
|
+
// A lead may be detached (its pid is a pgid) or the in-process teammate (not a
|
|
156
|
+
// group leader, only the bare pid answers) -- the same ambiguity
|
|
157
|
+
// spawn/lifecycle.ts handles when it signals a worker. Ask both.
|
|
158
|
+
if (!probes.isProcessGroupAlive(pid) && !probes.isProcessAlive(pid))
|
|
159
|
+
return 'gone';
|
|
160
|
+
// Alive -- but is it the SAME process? Only a matching start identity settles
|
|
161
|
+
// that. Without one recorded, or without one readable now, the pid is alive
|
|
162
|
+
// and anonymous: it may be our lead or may be anything the OS put there.
|
|
163
|
+
if (!record.startIdentity)
|
|
164
|
+
return 'indeterminate';
|
|
165
|
+
const live = probes.readStartIdentity(pid);
|
|
166
|
+
if (live === null)
|
|
167
|
+
return 'indeterminate';
|
|
168
|
+
return live === record.startIdentity ? 'alive' : 'gone';
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Classify the session's own process, from the host's DURABLE LEAD RECORD rather
|
|
172
|
+
* than from the session row's pid.
|
|
173
|
+
*
|
|
174
|
+
* The row's pid cannot answer this, for two reasons found in review:
|
|
175
|
+
*
|
|
176
|
+
* 1. It arrives TOO LATE. `launchLeadProcess` spawns the detached lead and
|
|
177
|
+
* records it, and only afterwards does `spawnTeamProcess` persist
|
|
178
|
+
* `{ status: 'running', pid }`. A hard kill or a failed API write in that
|
|
179
|
+
* window leaves a `starting` row with a null pid AND a live team. Reading
|
|
180
|
+
* that as "no process was spawned" closes a row whose team is running.
|
|
181
|
+
*
|
|
182
|
+
* 2. It is UNVERIFIABLE. A bare pid says nothing about WHICH process holds it
|
|
183
|
+
* now, so an unrelated process that recycled the number would keep a stale
|
|
184
|
+
* row `running` -- and `isWorktreeBeingWritten` would report a writer that
|
|
185
|
+
* does not exist.
|
|
186
|
+
*
|
|
187
|
+
* The lead record fixes both: it is written BEFORE the row's pid, and it carries
|
|
188
|
+
* the spawn-time start identity. Its absence is only evidence when the record
|
|
189
|
+
* itself is readable; an unreadable record is `unprovable`, never `stopped`.
|
|
190
|
+
*/
|
|
191
|
+
export function classifySessionProcess(sessionId, probes) {
|
|
192
|
+
const records = probes.leadRecordsBySession();
|
|
193
|
+
if (records === null)
|
|
194
|
+
return 'unprovable';
|
|
195
|
+
const record = records.get(sessionId);
|
|
196
|
+
if (record === undefined)
|
|
197
|
+
return 'no_process_recorded';
|
|
198
|
+
switch (classifyRecordedProcess(record, probes)) {
|
|
199
|
+
case 'alive': return 'alive';
|
|
200
|
+
case 'gone': return 'stopped';
|
|
201
|
+
default: return 'unprovable';
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Split the scoped candidates into the ones recovery may close and the ones it
|
|
206
|
+
* must leave alone, with the reason it spared each group.
|
|
207
|
+
*
|
|
208
|
+
* TWO proofs are required, and a row is closed only when both agree.
|
|
209
|
+
*
|
|
210
|
+
* 1. Its OWNER is gone. The host scope answers "whose machine", but a machine
|
|
211
|
+
* is not an owner: two daemon instances share a host. Our own live session,
|
|
212
|
+
* a peer instance still running, an unattributable or remote id -- all spared.
|
|
213
|
+
*
|
|
214
|
+
* 2. Its own PROCESS is gone. A dead owner does NOT imply a stopped team: team
|
|
215
|
+
* leads are spawned detached and survive a hard-killed daemon, and the boot
|
|
216
|
+
* reap that ends them is best-effort by design (a missing or malformed
|
|
217
|
+
* pidfile yields no entries; a failed kill is swallowed). So a live team
|
|
218
|
+
* whose daemon was `kill -9`ed reaches this pass, and closing its row is
|
|
219
|
+
* exactly the falsification the criterion forbids.
|
|
220
|
+
*
|
|
221
|
+
* The asymmetry is deliberate. A stale `running` row is visible and the next
|
|
222
|
+
* sweep re-examines it. A LIVE session marked `failed` is invisible and wrong:
|
|
223
|
+
* it tells every reader -- the dashboard, and `isWorktreeBeingWritten` -- that a
|
|
224
|
+
* running team is not there, which is how a worktree under active write comes to
|
|
225
|
+
* read as quiescent. This pass never kills anything to make its own proof true;
|
|
226
|
+
* ending a surviving orphan belongs to the identity-verified reap paths
|
|
227
|
+
* (`reapAndClearWorkerPidfile`, the untracked-lead sweep), and once they do, the
|
|
228
|
+
* next sweep reads the process as stopped and closes the row.
|
|
229
|
+
*/
|
|
230
|
+
export function partitionReapableSessions(sessions, probes, processProbes = defaultSessionProcessProbes()) {
|
|
231
|
+
const reapable = [];
|
|
232
|
+
const spared = new Map();
|
|
233
|
+
const spare = (reason, id) => {
|
|
234
|
+
const ids = spared.get(reason) ?? [];
|
|
235
|
+
ids.push(id);
|
|
236
|
+
spared.set(reason, ids);
|
|
237
|
+
};
|
|
238
|
+
for (const session of sessions) {
|
|
239
|
+
const owner = classifyOwningInstance(session.daemon_instance_id, probes);
|
|
240
|
+
if (owner === 'self') {
|
|
241
|
+
spare('owned by THIS running daemon', session.id);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (owner === 'peer_alive') {
|
|
245
|
+
spare('owned by another daemon instance still running on this host', session.id);
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (owner === 'unknown') {
|
|
249
|
+
spare('owning daemon instance not identifiable -- cannot prove it stopped', session.id);
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
// owner === 'gone'. Now the second proof: did its team stop with it?
|
|
253
|
+
switch (classifySessionProcess(session.id, processProbes)) {
|
|
254
|
+
case 'stopped':
|
|
255
|
+
case 'no_process_recorded':
|
|
256
|
+
reapable.push(session);
|
|
257
|
+
break;
|
|
258
|
+
case 'alive':
|
|
259
|
+
spare("the session's own lead is still running -- a detached team outlived its daemon", session.id);
|
|
260
|
+
break;
|
|
261
|
+
case 'unprovable':
|
|
262
|
+
spare('the host lead record is unreadable -- cannot prove the team stopped', session.id);
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return { reapable, spared };
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* The subset of `sessions` crash recovery may close, logging what it spared and
|
|
270
|
+
* why. The one call recovery makes: everything it returns has a provably
|
|
271
|
+
* stopped owner, and everything it withheld is named in the log rather than
|
|
272
|
+
* silently dropped.
|
|
273
|
+
*/
|
|
274
|
+
export function reconcileOwnedSessions(sessions, probeSource = {}) {
|
|
275
|
+
const { reapable, spared } = partitionReapableSessions(sessions, (probeSource.instanceProbes ?? defaultInstanceProbes)(), (probeSource.sessionProcessProbes ?? defaultSessionProcessProbes)());
|
|
276
|
+
for (const [why, ids] of spared) {
|
|
277
|
+
log.info(`[crash-recovery] Leaving ${ids.length} session(s) alone (${why}): ${ids.join(', ')}`);
|
|
278
|
+
}
|
|
279
|
+
return reapable;
|
|
280
|
+
}
|
|
281
|
+
//# sourceMappingURL=daemon-instance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon-instance.js","sourceRoot":"","sources":["../../src/shell/daemon-instance.ts"],"names":[],"mappings":"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"}
|
|
@@ -19,7 +19,18 @@ export interface GracefulShutdownDeps {
|
|
|
19
19
|
/** Floor for the reap budget when the engine drain overran the stop budget. */
|
|
20
20
|
reapMinBudgetMs: number;
|
|
21
21
|
reapWorkerFleet: (budgetMs: number) => Promise<unknown>;
|
|
22
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Finalize this host's worker-ownership records: drop what is CONFIRMED GONE,
|
|
24
|
+
* keep the rest. The reap above is bounded and best-effort, so a survivor must
|
|
25
|
+
* keep its record for the next boot reap rather than being erased here.
|
|
26
|
+
*
|
|
27
|
+
* OPTIONAL, and that is the point: the shell used to pass this, which made
|
|
28
|
+
* "which function the shutdown clears with" a fact about the shell's source
|
|
29
|
+
* rather than about any run. The default is the production boundary
|
|
30
|
+
* (shell/shutdown-ownership.ts), so no caller chooses it and a test that omits
|
|
31
|
+
* it drives exactly what a real SIGTERM drives.
|
|
32
|
+
*/
|
|
33
|
+
finalizeWorkerOwnership?: () => unknown;
|
|
23
34
|
stopCircuitWedgeWatcher: () => void;
|
|
24
35
|
stopOtlpReceiver: () => Promise<void>;
|
|
25
36
|
stopTelemetryWriter: () => Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graceful-shutdown.d.ts","sourceRoot":"","sources":["../../src/shell/graceful-shutdown.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"graceful-shutdown.d.ts","sourceRoot":"","sources":["../../src/shell/graceful-shutdown.ts"],"names":[],"mappings":"AAoCA,OAAO,EAAuD,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAM1G,0CAA0C;AAC1C,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,IAAI,IAAI,CAAC;IACb,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED,oFAAoF;AACpF,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IACvC,QAAQ,EAAE;QAAE,QAAQ,IAAI,IAAI,CAAA;KAAE,CAAC;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,wGAAwG;IACxG,aAAa,EAAE,MAAM,OAAO,CAAC;IAC7B,sFAAsF;IACtF,iBAAiB,EAAE,MAAM,CAAC;IAC1B,+EAA+E;IAC/E,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxD;;;;;;;;;;OAUG;IACH,uBAAuB,CAAC,EAAE,MAAM,OAAO,CAAC;IACxC,uBAAuB,EAAE,MAAM,IAAI,CAAC;IACpC,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,mBAAmB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,iBAAiB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,aAAa,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAC;IACnC,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,cAAc,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,kDAAkD;IAClD,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B;AAMD,oEAAoE;AACpE,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,oBAAoB,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAqFpG;AAED,+CAA+C;AAC/C,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,mFAAmF;IACnF,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9D,eAAe,CAAC,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC;IAC3C,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;CACxE;AAED,wEAAwE;AACxE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,CA0B9D"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// PINNED-BY-BEGIN (generated by gen:pinned-by -- do not hand-edit; checked by lint:pinned-by)
|
|
2
|
-
// Pinned by: packages/daemon/src/shell/graceful-shutdown.test.ts
|
|
2
|
+
// Pinned by: packages/daemon/src/shell/graceful-shutdown.test.ts,
|
|
3
|
+
// packages/daemon/src/shell/shutdown-ownership-wiring.test.ts
|
|
3
4
|
// PINNED-BY-END
|
|
4
5
|
/**
|
|
5
6
|
* The unified daemon's graceful shutdown and the stop signals that run it.
|
|
@@ -33,6 +34,7 @@ import { daemonExitCode, isDaemonFailStopRequested } from './fail-stop.js';
|
|
|
33
34
|
import { setDaemonShuttingDown } from './shutdown-state.js';
|
|
34
35
|
import { clearStopRequest, decideStopAction, readStopRequest } from './stop-request.js';
|
|
35
36
|
import { createLogger } from './log.js';
|
|
37
|
+
import { finalizeWorkerOwnership } from './shutdown-ownership.js';
|
|
36
38
|
const log = createLogger({ module: 'telemetry' });
|
|
37
39
|
function message(err) {
|
|
38
40
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -93,9 +95,10 @@ export function createGracefulShutdown(deps) {
|
|
|
93
95
|
catch (err) {
|
|
94
96
|
log.warn(`[reap] Worker-fleet reap failed: ${message(err)}`);
|
|
95
97
|
}
|
|
96
|
-
// The fleet is reaped --
|
|
97
|
-
// nor the next boot acts on
|
|
98
|
-
|
|
98
|
+
// The fleet is reaped -- drop the record of every worker CONFIRMED gone, so
|
|
99
|
+
// neither the CLI nor the next boot acts on a dead pid, and KEEP the record
|
|
100
|
+
// of anything that outlived the bounded reap so the next boot still can.
|
|
101
|
+
(deps.finalizeWorkerOwnership ?? (() => finalizeWorkerOwnership('graceful shutdown')))();
|
|
99
102
|
// Phase 2.5: Stop telemetry capture (flush remaining buffered rows, close
|
|
100
103
|
// the OTLP receiver socket) and tear down the Lacuna sidecar (SIGTERM).
|
|
101
104
|
deps.stopCircuitWedgeWatcher();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graceful-shutdown.js","sourceRoot":"","sources":["../../src/shell/graceful-shutdown.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,
|
|
1
|
+
{"version":3,"file":"graceful-shutdown.js","sourceRoot":"","sources":["../../src/shell/graceful-shutdown.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE place a shutdown finalizes this host's worker-ownership records.
|
|
3
|
+
*
|
|
4
|
+
* The daemon has two shutdowns. The graceful one (shell/graceful-shutdown.ts)
|
|
5
|
+
* runs on SIGINT/SIGTERM; the auto-update one (`shutdownForUpdate` in
|
|
6
|
+
* shell/unified-shell.ts) runs before the re-exec. Both must leave the pidfile in
|
|
7
|
+
* the same state, and for the same reason: `reapWorkerFleet` is BOUNDED and
|
|
8
|
+
* best-effort, so a worker that outlived SIGKILL, one that could not be
|
|
9
|
+
* signalled, and a reservation whose spawn is in flight all survive it. Erasing
|
|
10
|
+
* the file here erases the record of exactly those -- right before the next boot
|
|
11
|
+
* reap, or the re-exec'd daemon, would have acted on them.
|
|
12
|
+
*
|
|
13
|
+
* Both paths used to reach for that themselves, and each could reach for the
|
|
14
|
+
* WRONG function: they called the unconditional `clearWorkerPidfile`, and the
|
|
15
|
+
* fix -- passing `clearWorkerPidfileIfAllGone` instead -- left the choice in
|
|
16
|
+
* place at two call sites, testable only by reading the source to see which
|
|
17
|
+
* name was written there. So the choice is gone: this is the only ownership
|
|
18
|
+
* finalization either path can perform, the graceful sequence takes it by
|
|
19
|
+
* default rather than as an injected dep, and worker-pidfile.ts no longer
|
|
20
|
+
* exports an unconditional clear for a call site to pick up.
|
|
21
|
+
*
|
|
22
|
+
* @module shell/shutdown-ownership
|
|
23
|
+
*/
|
|
24
|
+
import { type WorkerPidEntry } from './worker-pidfile.js';
|
|
25
|
+
/**
|
|
26
|
+
* Drop the ownership records of every worker CONFIRMED gone, keep the rest.
|
|
27
|
+
*
|
|
28
|
+
* `context` names the shutdown for the log. Returns the survivors kept on disk,
|
|
29
|
+
* so a caller (and a test) can see what this host is still claiming. Never
|
|
30
|
+
* throws: a shutdown step that cannot finalize ownership must not prevent the
|
|
31
|
+
* rest of the teardown, and an unreadable record is kept, not declared empty.
|
|
32
|
+
*/
|
|
33
|
+
export declare function finalizeWorkerOwnership(context: string): WorkerPidEntry[];
|
|
34
|
+
//# sourceMappingURL=shutdown-ownership.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shutdown-ownership.d.ts","sourceRoot":"","sources":["../../src/shell/shutdown-ownership.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAA+B,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAKvF;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE,CAWzE"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// PINNED-BY-BEGIN (generated by gen:pinned-by -- do not hand-edit; checked by lint:pinned-by)
|
|
2
|
+
// Pinned by: packages/daemon/src/shell/shutdown-ownership-wiring.test.ts
|
|
3
|
+
// PINNED-BY-END
|
|
4
|
+
/**
|
|
5
|
+
* The ONE place a shutdown finalizes this host's worker-ownership records.
|
|
6
|
+
*
|
|
7
|
+
* The daemon has two shutdowns. The graceful one (shell/graceful-shutdown.ts)
|
|
8
|
+
* runs on SIGINT/SIGTERM; the auto-update one (`shutdownForUpdate` in
|
|
9
|
+
* shell/unified-shell.ts) runs before the re-exec. Both must leave the pidfile in
|
|
10
|
+
* the same state, and for the same reason: `reapWorkerFleet` is BOUNDED and
|
|
11
|
+
* best-effort, so a worker that outlived SIGKILL, one that could not be
|
|
12
|
+
* signalled, and a reservation whose spawn is in flight all survive it. Erasing
|
|
13
|
+
* the file here erases the record of exactly those -- right before the next boot
|
|
14
|
+
* reap, or the re-exec'd daemon, would have acted on them.
|
|
15
|
+
*
|
|
16
|
+
* Both paths used to reach for that themselves, and each could reach for the
|
|
17
|
+
* WRONG function: they called the unconditional `clearWorkerPidfile`, and the
|
|
18
|
+
* fix -- passing `clearWorkerPidfileIfAllGone` instead -- left the choice in
|
|
19
|
+
* place at two call sites, testable only by reading the source to see which
|
|
20
|
+
* name was written there. So the choice is gone: this is the only ownership
|
|
21
|
+
* finalization either path can perform, the graceful sequence takes it by
|
|
22
|
+
* default rather than as an injected dep, and worker-pidfile.ts no longer
|
|
23
|
+
* exports an unconditional clear for a call site to pick up.
|
|
24
|
+
*
|
|
25
|
+
* @module shell/shutdown-ownership
|
|
26
|
+
*/
|
|
27
|
+
import { clearWorkerPidfileIfAllGone } from './worker-pidfile.js';
|
|
28
|
+
import { createLogger } from './log.js';
|
|
29
|
+
const log = createLogger({ module: 'shutdown-ownership' });
|
|
30
|
+
/**
|
|
31
|
+
* Drop the ownership records of every worker CONFIRMED gone, keep the rest.
|
|
32
|
+
*
|
|
33
|
+
* `context` names the shutdown for the log. Returns the survivors kept on disk,
|
|
34
|
+
* so a caller (and a test) can see what this host is still claiming. Never
|
|
35
|
+
* throws: a shutdown step that cannot finalize ownership must not prevent the
|
|
36
|
+
* rest of the teardown, and an unreadable record is kept, not declared empty.
|
|
37
|
+
*/
|
|
38
|
+
export function finalizeWorkerOwnership(context) {
|
|
39
|
+
try {
|
|
40
|
+
const survivors = clearWorkerPidfileIfAllGone();
|
|
41
|
+
if (survivors.length > 0) {
|
|
42
|
+
log.info(`[${context}] ${survivors.length} worker record(s) kept for the next boot reap`);
|
|
43
|
+
}
|
|
44
|
+
return survivors;
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
log.warn(`[${context}] could not finalize worker ownership: ${err?.message ?? String(err)}`);
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=shutdown-ownership.js.map
|