@defrex/autobuild 0.8.0
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/LICENSE +201 -0
- package/README.md +192 -0
- package/SPEC.md +2269 -0
- package/bin/ab-build-runner.ts +47 -0
- package/bin/ab-dev.ts +27 -0
- package/bin/ab-dispatch-kernel.ts +92 -0
- package/bin/ab-harvest-runner.ts +29 -0
- package/bin/ab-init-probe.ts +19 -0
- package/bin/ab-process-group-reaper.ts +32 -0
- package/bin/ab.ts +6 -0
- package/bin/agent/ab +3 -0
- package/docs/answering-blocked-builds.md +89 -0
- package/docs/architecture.md +726 -0
- package/docs/assets/headline-wide.png +0 -0
- package/docs/configuration.md +1623 -0
- package/docs/hosted-dispatcher-cutover-validation.md +178 -0
- package/docs/hosted-dispatcher.md +204 -0
- package/docs/mcp.md +119 -0
- package/docs/operator-api.md +186 -0
- package/docs/remote-store-protocol.md +972 -0
- package/docs/remote-ticket-protocol.md +79 -0
- package/docs/setup.md +289 -0
- package/docs/spec-standard.md +63 -0
- package/docs/ticket-dependencies.md +59 -0
- package/docs/vercel-sandbox-rollout-probe.md +7 -0
- package/package.json +135 -0
- package/packages/core/src/cli/args.ts +74 -0
- package/packages/core/src/cli/artifact.ts +182 -0
- package/packages/core/src/cli/binary.ts +198 -0
- package/packages/core/src/cli/build-control.ts +789 -0
- package/packages/core/src/cli/build-progress.ts +55 -0
- package/packages/core/src/cli/bulk-control.ts +385 -0
- package/packages/core/src/cli/context.ts +470 -0
- package/packages/core/src/cli/dashboard/actions.ts +59 -0
- package/packages/core/src/cli/dashboard/cells.ts +92 -0
- package/packages/core/src/cli/dashboard/composer.ts +230 -0
- package/packages/core/src/cli/dashboard/detail.ts +100 -0
- package/packages/core/src/cli/dashboard/frame-image.ts +373 -0
- package/packages/core/src/cli/dashboard/live.ts +152 -0
- package/packages/core/src/cli/dashboard/model.ts +1173 -0
- package/packages/core/src/cli/dashboard/poll.ts +196 -0
- package/packages/core/src/cli/dashboard/render.ts +1568 -0
- package/packages/core/src/cli/dashboard/selection.ts +66 -0
- package/packages/core/src/cli/dashboard/session-feed.ts +244 -0
- package/packages/core/src/cli/dashboard/session-view.ts +506 -0
- package/packages/core/src/cli/dashboard/transcript.ts +108 -0
- package/packages/core/src/cli/dev.ts +46 -0
- package/packages/core/src/cli/dispatch-frontend.ts +1045 -0
- package/packages/core/src/cli/dispatch-process.ts +259 -0
- package/packages/core/src/cli/dispatch.ts +3692 -0
- package/packages/core/src/cli/dotenv.ts +48 -0
- package/packages/core/src/cli/env.ts +206 -0
- package/packages/core/src/cli/harvest.ts +698 -0
- package/packages/core/src/cli/help.ts +713 -0
- package/packages/core/src/cli/init-agent.ts +106 -0
- package/packages/core/src/cli/init-validation.ts +583 -0
- package/packages/core/src/cli/init.ts +756 -0
- package/packages/core/src/cli/installation.ts +333 -0
- package/packages/core/src/cli/keyboard.ts +261 -0
- package/packages/core/src/cli/main.ts +1399 -0
- package/packages/core/src/cli/mcp.ts +279 -0
- package/packages/core/src/cli/models.ts +65 -0
- package/packages/core/src/cli/observe.ts +55 -0
- package/packages/core/src/cli/plugin.ts +227 -0
- package/packages/core/src/cli/pr-attachments.ts +138 -0
- package/packages/core/src/cli/pr-summary.ts +124 -0
- package/packages/core/src/cli/publication-settlement.ts +188 -0
- package/packages/core/src/cli/repo-state.ts +213 -0
- package/packages/core/src/cli/repository-status.ts +80 -0
- package/packages/core/src/cli/self-update.ts +443 -0
- package/packages/core/src/cli/status.ts +895 -0
- package/packages/core/src/cli/store-opening.contract.ts +289 -0
- package/packages/core/src/cli/store-opening.ts +167 -0
- package/packages/core/src/cli/store-ref.ts +33 -0
- package/packages/core/src/cli/terminal-restore.ts +174 -0
- package/packages/core/src/cli/terminal.ts +361 -0
- package/packages/core/src/cli/terminals.ts +995 -0
- package/packages/core/src/cli/testkit.ts +194 -0
- package/packages/core/src/cli/ticket.ts +484 -0
- package/packages/core/src/cli/upgrade-agent.ts +180 -0
- package/packages/core/src/cli/upgrade-commit.ts +468 -0
- package/packages/core/src/cli/upgrade-notice.ts +91 -0
- package/packages/core/src/cli/upgrade-progress.ts +110 -0
- package/packages/core/src/cli/upgrade.ts +888 -0
- package/packages/core/src/cli/wait.ts +587 -0
- package/packages/core/src/cli/watch.ts +932 -0
- package/packages/core/src/config/live.ts +255 -0
- package/packages/core/src/config/load.ts +98 -0
- package/packages/core/src/config/roles.ts +438 -0
- package/packages/core/src/config/schema.ts +774 -0
- package/packages/core/src/distribution.ts +24 -0
- package/packages/core/src/events/catalog.ts +201 -0
- package/packages/core/src/events/envelope.ts +59 -0
- package/packages/core/src/events/payloads.ts +618 -0
- package/packages/core/src/events/repository.ts +559 -0
- package/packages/core/src/events/sessions.ts +180 -0
- package/packages/core/src/harvest/schema.ts +147 -0
- package/packages/core/src/ids.ts +45 -0
- package/packages/core/src/integration/harness.ts +681 -0
- package/packages/core/src/kernel/auto-merge.ts +259 -0
- package/packages/core/src/kernel/converge.ts +117 -0
- package/packages/core/src/kernel/dispatch-settings.ts +60 -0
- package/packages/core/src/kernel/dispatch-status.ts +141 -0
- package/packages/core/src/kernel/engine.ts +1047 -0
- package/packages/core/src/kernel/harvest.ts +823 -0
- package/packages/core/src/kernel/origin.ts +69 -0
- package/packages/core/src/kernel/phases.ts +196 -0
- package/packages/core/src/kernel/plan-verify-selection.ts +105 -0
- package/packages/core/src/kernel/pr-attachments.ts +82 -0
- package/packages/core/src/kernel/reducer.ts +752 -0
- package/packages/core/src/kernel/stall.ts +155 -0
- package/packages/core/src/kernel/verify-applicability.ts +52 -0
- package/packages/core/src/markdown.ts +67 -0
- package/packages/core/src/ontology.ts +288 -0
- package/packages/core/src/open-map.ts +203 -0
- package/packages/core/src/operator/annotations.ts +187 -0
- package/packages/core/src/operator/client.ts +311 -0
- package/packages/core/src/operator/control.ts +117 -0
- package/packages/core/src/operator/index.ts +62 -0
- package/packages/core/src/operator/presentation.ts +22 -0
- package/packages/core/src/operator/protocol.ts +98 -0
- package/packages/core/src/operator/query.ts +201 -0
- package/packages/core/src/operator/registry.ts +1043 -0
- package/packages/core/src/operator/requests.ts +138 -0
- package/packages/core/src/operator/sandbox.ts +458 -0
- package/packages/core/src/operator/server.ts +616 -0
- package/packages/core/src/operator/sessions.ts +223 -0
- package/packages/core/src/operator/tickets.ts +177 -0
- package/packages/core/src/plugin-sdk/index.ts +174 -0
- package/packages/core/src/plugins/contract-entry.ts +74 -0
- package/packages/core/src/plugins/contract-env.ts +3 -0
- package/packages/core/src/plugins/load.ts +259 -0
- package/packages/core/src/plugins/manifest.ts +236 -0
- package/packages/core/src/plugins/registry.ts +274 -0
- package/packages/core/src/plugins/runtimes.ts +75 -0
- package/packages/core/src/ports/forge/contract.ts +258 -0
- package/packages/core/src/ports/forge/create.ts +74 -0
- package/packages/core/src/ports/forge/fake.ts +488 -0
- package/packages/core/src/ports/forge/github-pr-attachments.ts +351 -0
- package/packages/core/src/ports/forge/github-transport.ts +352 -0
- package/packages/core/src/ports/forge/github.ts +1162 -0
- package/packages/core/src/ports/forge/local-git.ts +568 -0
- package/packages/core/src/ports/runner/claude.ts +873 -0
- package/packages/core/src/ports/runner/cli-stream.ts +90 -0
- package/packages/core/src/ports/runner/codex.ts +873 -0
- package/packages/core/src/ports/runner/contract.ts +492 -0
- package/packages/core/src/ports/runner/fake.ts +213 -0
- package/packages/core/src/ports/runner/json-record.ts +2 -0
- package/packages/core/src/ports/runner/live-contract-fixture.ts +176 -0
- package/packages/core/src/ports/runner/one-shot.ts +29 -0
- package/packages/core/src/ports/runner/pi-bridge-core.ts +136 -0
- package/packages/core/src/ports/runner/pi-bridge.js +6 -0
- package/packages/core/src/ports/runner/pi-rpc.ts +454 -0
- package/packages/core/src/ports/runner/pi.ts +617 -0
- package/packages/core/src/ports/runner/production.ts +74 -0
- package/packages/core/src/ports/runner/provider-error.ts +121 -0
- package/packages/core/src/ports/runner/routing.ts +234 -0
- package/packages/core/src/ports/runner/runtime.ts +214 -0
- package/packages/core/src/ports/runner/session-env.ts +34 -0
- package/packages/core/src/ports/runner/stream-parts.ts +103 -0
- package/packages/core/src/ports/tickets/contract.ts +570 -0
- package/packages/core/src/ports/tickets/create.ts +135 -0
- package/packages/core/src/ports/tickets/fake.ts +215 -0
- package/packages/core/src/ports/tickets/file.ts +559 -0
- package/packages/core/src/ports/tickets/linear.ts +811 -0
- package/packages/core/src/ports/tickets/operations.ts +161 -0
- package/packages/core/src/ports/tickets/remote-index.ts +13 -0
- package/packages/core/src/ports/tickets/remote-protocol.ts +80 -0
- package/packages/core/src/ports/tickets/remote-server.ts +185 -0
- package/packages/core/src/ports/tickets/remote.ts +130 -0
- package/packages/core/src/ports/tickets/update.ts +51 -0
- package/packages/core/src/ports/types.ts +528 -0
- package/packages/core/src/ports/workspace/build-execution.ts +123 -0
- package/packages/core/src/ports/workspace/contract.ts +136 -0
- package/packages/core/src/ports/workspace/create.ts +132 -0
- package/packages/core/src/ports/workspace/distribution-archive.ts +317 -0
- package/packages/core/src/ports/workspace/fake.ts +432 -0
- package/packages/core/src/ports/workspace/git-worktree.ts +848 -0
- package/packages/core/src/ports/workspace/harvest-execution.ts +111 -0
- package/packages/core/src/ports/workspace/in-process-build-execution.ts +30 -0
- package/packages/core/src/ports/workspace/local-build-execution.ts +88 -0
- package/packages/core/src/ports/workspace/operator-sandbox.ts +142 -0
- package/packages/core/src/ports/workspace/phase-scratch.ts +156 -0
- package/packages/core/src/ports/workspace/vercel-sandbox.ts +2541 -0
- package/packages/core/src/processes/build-child-exit.ts +78 -0
- package/packages/core/src/processes/build-child.ts +119 -0
- package/packages/core/src/processes/build-execution-state.ts +95 -0
- package/packages/core/src/processes/build-parent-watch.ts +35 -0
- package/packages/core/src/processes/build-runner.ts +2614 -0
- package/packages/core/src/processes/dispatcher.ts +2825 -0
- package/packages/core/src/processes/execution-settlement.ts +171 -0
- package/packages/core/src/processes/harvest-child.ts +115 -0
- package/packages/core/src/processes/harvest-execution-state.ts +121 -0
- package/packages/core/src/processes/harvest-runner.ts +1656 -0
- package/packages/core/src/processes/harvest.ts +595 -0
- package/packages/core/src/processes/infrastructure-failure-budget.ts +146 -0
- package/packages/core/src/processes/phase-failure-budget.ts +52 -0
- package/packages/core/src/processes/process-group-reaper.ts +35 -0
- package/packages/core/src/processes/process-group.ts +65 -0
- package/packages/core/src/processes/publication-loss.ts +62 -0
- package/packages/core/src/processes/publication-state.ts +93 -0
- package/packages/core/src/processes/sandbox-state.ts +101 -0
- package/packages/core/src/registry.ts +24 -0
- package/packages/core/src/sessions/reducer.ts +185 -0
- package/packages/core/src/skills.ts +7 -0
- package/packages/core/src/spec-standard.ts +47 -0
- package/packages/core/src/store/adapter.ts +80 -0
- package/packages/core/src/store/build-scope.ts +272 -0
- package/packages/core/src/store/contract.ts +2581 -0
- package/packages/core/src/store/local/blobs.ts +38 -0
- package/packages/core/src/store/local/schema.ts +180 -0
- package/packages/core/src/store/local/store.ts +1713 -0
- package/packages/core/src/store/memory.ts +1138 -0
- package/packages/core/src/store/phase-session.ts +425 -0
- package/packages/core/src/store/remote/client.ts +780 -0
- package/packages/core/src/store/remote/index.ts +12 -0
- package/packages/core/src/store/remote/protocol.ts +348 -0
- package/packages/core/src/store/remote/server.ts +923 -0
- package/packages/core/src/store/remote/token.ts +137 -0
- package/packages/core/src/store/remote/version.ts +10 -0
- package/packages/core/src/store/retention.ts +91 -0
- package/packages/core/src/store/session-handle.ts +265 -0
- package/packages/core/src/store/streams/assemble.ts +79 -0
- package/packages/core/src/store/streams/lock.ts +42 -0
- package/packages/core/src/store/streams/session-writer.ts +306 -0
- package/packages/core/src/store/streams/types.ts +171 -0
- package/packages/core/src/store/streams/wait.ts +127 -0
- package/packages/core/src/store/subscribe.ts +105 -0
- package/packages/core/src/store/types.ts +466 -0
- package/packages/core/src/testing/fixed.ts +44 -0
- package/packages/core/src/testing/store-failures.ts +32 -0
- package/packages/core/src/zod-issues.ts +91 -0
- package/skills/code-review/SKILL.md +100 -0
- package/skills/finalize/SKILL.md +44 -0
- package/skills/guide/SKILL.md +1781 -0
- package/skills/guide/references/operator-api.md +164 -0
- package/skills/guide/references/plugin-authoring.md +354 -0
- package/skills/guide/references/remote-store-protocol.md +972 -0
- package/skills/guide/references/remote-ticket-protocol.md +79 -0
- package/skills/guide/references/setup.md +289 -0
- package/skills/guide/references/spec-standard.md +63 -0
- package/skills/guide/references/ticket-dependencies.md +59 -0
- package/skills/harvest/SKILL.md +66 -0
- package/skills/harvest-review/SKILL.md +63 -0
- package/skills/implement/SKILL.md +82 -0
- package/skills/plan/SKILL.md +123 -0
- package/skills/plan-review/SKILL.md +121 -0
- package/skills/reconcile/SKILL.md +65 -0
- package/skills/spec/SKILL.md +132 -0
- package/skills/tickets/SKILL.md +139 -0
- package/templates/autobuild.toml +36 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { distributionPath } from '../distribution'
|
|
2
|
+
import { DISPATCHER } from '../events/envelope'
|
|
3
|
+
import type { RepositoryEvent } from '../events/repository'
|
|
4
|
+
import type { BuildStore } from '../store/types'
|
|
5
|
+
|
|
6
|
+
export const DISPATCH_CHILD_OPTIONS_ENV = 'AB_DISPATCH_CHILD_OPTIONS'
|
|
7
|
+
const DEFAULT_STOP_TIMEOUT_MS = 5_000
|
|
8
|
+
|
|
9
|
+
export interface DispatchChildOptions {
|
|
10
|
+
targetRepo: string
|
|
11
|
+
storeRef: string
|
|
12
|
+
run: string
|
|
13
|
+
once: boolean
|
|
14
|
+
intervalMs?: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type DispatchChildOutcome = 'normal' | 'abnormal' | 'forced'
|
|
18
|
+
|
|
19
|
+
export interface DispatchChildResult {
|
|
20
|
+
outcome: DispatchChildOutcome
|
|
21
|
+
exitCode: number
|
|
22
|
+
signal?: string
|
|
23
|
+
error?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface DispatchChildHandle {
|
|
27
|
+
completed: Promise<DispatchChildResult>
|
|
28
|
+
stop(): Promise<void>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface DispatchSubprocess {
|
|
32
|
+
exited: Promise<number>
|
|
33
|
+
exitCode: number | null
|
|
34
|
+
signalCode: string | number | null
|
|
35
|
+
kill(signal: 'SIGINT' | 'SIGKILL'): void
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface DispatchProcessHook {
|
|
39
|
+
close(): void
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const DISPATCH_KERNEL_SIGNALS = [
|
|
43
|
+
'SIGINT',
|
|
44
|
+
'SIGHUP',
|
|
45
|
+
'SIGQUIT',
|
|
46
|
+
'SIGTERM',
|
|
47
|
+
] as const satisfies readonly NodeJS.Signals[]
|
|
48
|
+
|
|
49
|
+
type DispatchKernelSignal = (typeof DISPATCH_KERNEL_SIGNALS)[number]
|
|
50
|
+
|
|
51
|
+
export interface DispatchKernelSignalBoundary {
|
|
52
|
+
on(signal: DispatchKernelSignal, listener: () => void): unknown
|
|
53
|
+
removeListener(signal: DispatchKernelSignal, listener: () => void): unknown
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Keep graceful handlers active until the kernel has actually drained. Using
|
|
57
|
+
* one-shot handlers would expose the child to the default disposition on a
|
|
58
|
+
* repeated Ctrl-C while it is still crossing an unsafe claim boundary. */
|
|
59
|
+
export function installDispatchKernelSignalHandlers(
|
|
60
|
+
onStop: () => void,
|
|
61
|
+
boundary: DispatchKernelSignalBoundary = process,
|
|
62
|
+
): DispatchProcessHook {
|
|
63
|
+
for (const signal of DISPATCH_KERNEL_SIGNALS) boundary.on(signal, onStop)
|
|
64
|
+
let closed = false
|
|
65
|
+
return {
|
|
66
|
+
close(): void {
|
|
67
|
+
if (closed) return
|
|
68
|
+
closed = true
|
|
69
|
+
for (const signal of DISPATCH_KERNEL_SIGNALS) boundary.removeListener(signal, onStop)
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface DispatchParentWatchDeps {
|
|
75
|
+
intervalMs?: number
|
|
76
|
+
currentParentPid?: () => number
|
|
77
|
+
isAlive?: (pid: number) => boolean
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** A private kernel must never become a detached daemon if its terminal-owning
|
|
81
|
+
* parent is killed directly rather than through the foreground process group.
|
|
82
|
+
* Polling is deliberately local process liveness, not an operational IPC
|
|
83
|
+
* channel; shutdown still converges through the normal AbortSignal boundary. */
|
|
84
|
+
export function watchDispatchParent(
|
|
85
|
+
parentPid: number,
|
|
86
|
+
onParentExit: () => void,
|
|
87
|
+
deps: DispatchParentWatchDeps = {},
|
|
88
|
+
): DispatchProcessHook {
|
|
89
|
+
const currentParentPid = deps.currentParentPid ?? (() => process.ppid)
|
|
90
|
+
const isAlive =
|
|
91
|
+
deps.isAlive ??
|
|
92
|
+
((pid: number): boolean => {
|
|
93
|
+
try {
|
|
94
|
+
process.kill(pid, 0)
|
|
95
|
+
return true
|
|
96
|
+
} catch {
|
|
97
|
+
return false
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
let closed = false
|
|
101
|
+
let timer: ReturnType<typeof setInterval>
|
|
102
|
+
const close = (): void => {
|
|
103
|
+
if (closed) return
|
|
104
|
+
closed = true
|
|
105
|
+
clearInterval(timer)
|
|
106
|
+
}
|
|
107
|
+
timer = setInterval(() => {
|
|
108
|
+
if (currentParentPid() === parentPid && isAlive(parentPid)) return
|
|
109
|
+
close()
|
|
110
|
+
onParentExit()
|
|
111
|
+
}, deps.intervalMs ?? 100)
|
|
112
|
+
timer.unref?.()
|
|
113
|
+
return { close }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface DispatchChildSupervisorDeps {
|
|
117
|
+
store: BuildStore
|
|
118
|
+
/** Repository identity (§12) — the Store journal key. */
|
|
119
|
+
repo: string
|
|
120
|
+
/** Process cwd for the spawned kernel; defaults to `repo` (checkout mode). */
|
|
121
|
+
cwd?: string
|
|
122
|
+
run: string
|
|
123
|
+
env: Record<string, string | undefined>
|
|
124
|
+
options: DispatchChildOptions
|
|
125
|
+
stopTimeoutMs?: number
|
|
126
|
+
entrypoint?: string
|
|
127
|
+
/** Injectable process seam for lifecycle and timeout tests. */
|
|
128
|
+
spawn?: (input: {
|
|
129
|
+
entrypoint: string
|
|
130
|
+
cwd: string
|
|
131
|
+
env: Record<string, string>
|
|
132
|
+
}) => DispatchSubprocess
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function hasOpenTick(events: readonly RepositoryEvent[], run: string): boolean {
|
|
136
|
+
const boundary = events.findLast(
|
|
137
|
+
(event) =>
|
|
138
|
+
'run' in event.payload &&
|
|
139
|
+
event.payload.run === run &&
|
|
140
|
+
(event.type === 'dispatcher.tick-started' ||
|
|
141
|
+
event.type === 'dispatcher.tick-completed' ||
|
|
142
|
+
event.type === 'dispatcher.tick-failed'),
|
|
143
|
+
)
|
|
144
|
+
return boundary?.type === 'dispatcher.tick-started'
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function defaultSpawn(input: {
|
|
148
|
+
entrypoint: string
|
|
149
|
+
cwd: string
|
|
150
|
+
env: Record<string, string>
|
|
151
|
+
}): DispatchSubprocess {
|
|
152
|
+
return Bun.spawn([process.execPath, input.entrypoint], {
|
|
153
|
+
cwd: input.cwd,
|
|
154
|
+
env: input.env,
|
|
155
|
+
stdin: 'ignore',
|
|
156
|
+
stdout: 'ignore',
|
|
157
|
+
stderr: 'ignore',
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Spawn and reap the private kernel. Changing operational state never crosses
|
|
162
|
+
* this boundary: options are immutable launch identity and all live facts and
|
|
163
|
+
* commands use BuildStore. */
|
|
164
|
+
export function superviseDispatchChild(deps: DispatchChildSupervisorDeps): DispatchChildHandle {
|
|
165
|
+
const entrypoint = deps.entrypoint ?? distributionPath('bin', 'ab-dispatch-kernel.ts')
|
|
166
|
+
const env: Record<string, string> = {}
|
|
167
|
+
for (const [key, value] of Object.entries(deps.env)) if (value !== undefined) env[key] = value
|
|
168
|
+
env[DISPATCH_CHILD_OPTIONS_ENV] = JSON.stringify(deps.options)
|
|
169
|
+
const child = (deps.spawn ?? defaultSpawn)({ entrypoint, cwd: deps.cwd ?? deps.repo, env })
|
|
170
|
+
|
|
171
|
+
let stopping = false
|
|
172
|
+
let forced = false
|
|
173
|
+
let stopPromise: Promise<void> | undefined
|
|
174
|
+
// This is the sole supervisor writer of a missing stop fact. `stop()` only
|
|
175
|
+
// chooses/awaits process termination, avoiding check-then-append races.
|
|
176
|
+
const completed = child.exited.then(async (exitCode): Promise<DispatchChildResult> => {
|
|
177
|
+
const processSignal = child.signalCode === null ? undefined : String(child.signalCode)
|
|
178
|
+
const events = await deps.store.getRepoEvents(deps.repo)
|
|
179
|
+
const recorded = events.findLast(
|
|
180
|
+
(event) => event.type === 'dispatcher.run-stopped' && event.payload.run === deps.run,
|
|
181
|
+
)
|
|
182
|
+
if (recorded?.type === 'dispatcher.run-stopped') {
|
|
183
|
+
const signal = processSignal ?? recorded.payload.signal
|
|
184
|
+
return {
|
|
185
|
+
outcome: recorded.payload.outcome,
|
|
186
|
+
exitCode,
|
|
187
|
+
...(signal !== undefined ? { signal } : {}),
|
|
188
|
+
...(recorded.payload.error !== undefined ? { error: recorded.payload.error } : {}),
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const outcome: DispatchChildOutcome = forced
|
|
193
|
+
? 'forced'
|
|
194
|
+
: stopping && exitCode === 0 && processSignal === undefined
|
|
195
|
+
? 'normal'
|
|
196
|
+
: 'abnormal'
|
|
197
|
+
const error =
|
|
198
|
+
outcome === 'abnormal'
|
|
199
|
+
? `kernel process exited with status ${exitCode}${
|
|
200
|
+
processSignal !== undefined ? ` (signal ${processSignal})` : ''
|
|
201
|
+
}`
|
|
202
|
+
: undefined
|
|
203
|
+
await deps.store.appendRepo(deps.repo, {
|
|
204
|
+
actor: DISPATCHER,
|
|
205
|
+
type: 'dispatcher.run-stopped',
|
|
206
|
+
payload: {
|
|
207
|
+
run: deps.run,
|
|
208
|
+
outcome,
|
|
209
|
+
exitCode,
|
|
210
|
+
...(processSignal !== undefined ? { signal: processSignal } : {}),
|
|
211
|
+
...(error !== undefined ? { error } : {}),
|
|
212
|
+
},
|
|
213
|
+
})
|
|
214
|
+
return {
|
|
215
|
+
outcome,
|
|
216
|
+
exitCode,
|
|
217
|
+
...(processSignal !== undefined ? { signal: processSignal } : {}),
|
|
218
|
+
...(error !== undefined ? { error } : {}),
|
|
219
|
+
}
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
completed,
|
|
224
|
+
stop(): Promise<void> {
|
|
225
|
+
if (stopPromise !== undefined) return stopPromise
|
|
226
|
+
stopPromise = (async () => {
|
|
227
|
+
if (child.exitCode !== null) return
|
|
228
|
+
stopping = true
|
|
229
|
+
child.kill('SIGINT')
|
|
230
|
+
const timeout = deps.stopTimeoutMs ?? DEFAULT_STOP_TIMEOUT_MS
|
|
231
|
+
const stopped = await Promise.race([
|
|
232
|
+
child.exited.then(() => true),
|
|
233
|
+
new Promise<false>((resolve) => setTimeout(() => resolve(false), timeout)),
|
|
234
|
+
])
|
|
235
|
+
if (stopped || child.exitCode !== null) return
|
|
236
|
+
|
|
237
|
+
// A dispatcher tick may hold an external ticket claim before its build
|
|
238
|
+
// record exists. Killing that turn can make the ticket disappear from
|
|
239
|
+
// Ready with no durable build to recover. Let an open tick reach its
|
|
240
|
+
// normal boundary; all non-tick work is lease-backed/recoverable and a
|
|
241
|
+
// child stuck there is safe to terminate.
|
|
242
|
+
const events = await deps.store.getRepoEvents(deps.repo)
|
|
243
|
+
// The child may have exited while the asynchronous journal read was in
|
|
244
|
+
// flight. Re-check at the force boundary so evidence never claims a
|
|
245
|
+
// SIGKILL that was not actually sent.
|
|
246
|
+
if (child.exitCode !== null) return
|
|
247
|
+
if (hasOpenTick(events, deps.run)) {
|
|
248
|
+
await child.exited
|
|
249
|
+
return
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
forced = true
|
|
253
|
+
child.kill('SIGKILL')
|
|
254
|
+
await child.exited
|
|
255
|
+
})()
|
|
256
|
+
return stopPromise
|
|
257
|
+
},
|
|
258
|
+
}
|
|
259
|
+
}
|