@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,78 @@
|
|
|
1
|
+
import type { ProcessGroupReaperOptions } from './process-group-reaper'
|
|
2
|
+
import { launchProcessGroupReaper, processGroupReaperOptions } from './process-group-reaper'
|
|
3
|
+
|
|
4
|
+
interface ParentWatch {
|
|
5
|
+
close(): void
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface BuildChildExitCoordinatorOptions {
|
|
9
|
+
groupId: number
|
|
10
|
+
stopTimeoutMs?: number
|
|
11
|
+
launchReaper?: (options: ProcessGroupReaperOptions) => number
|
|
12
|
+
exit?: (code: number) => void
|
|
13
|
+
retryDelayMs?: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Transfers teardown ownership before a build-child leader exits. Every
|
|
18
|
+
* terminal path shares this coordinator so signal/watchdog races preserve the
|
|
19
|
+
* first exit intent and create exactly one detached group reaper.
|
|
20
|
+
*/
|
|
21
|
+
export class BuildChildExitCoordinator {
|
|
22
|
+
private readonly reaperOptions: ProcessGroupReaperOptions
|
|
23
|
+
private readonly launchReaper: (options: ProcessGroupReaperOptions) => number
|
|
24
|
+
private readonly exit: (code: number) => void
|
|
25
|
+
private readonly retryDelayMs: number
|
|
26
|
+
private parentWatch: ParentWatch | undefined
|
|
27
|
+
private parentWatchClosed = false
|
|
28
|
+
private intendedExitCode: number | undefined
|
|
29
|
+
private retry: ReturnType<typeof setTimeout> | undefined
|
|
30
|
+
private handedOff = false
|
|
31
|
+
|
|
32
|
+
constructor(options: BuildChildExitCoordinatorOptions) {
|
|
33
|
+
this.reaperOptions =
|
|
34
|
+
options.stopTimeoutMs === undefined
|
|
35
|
+
? processGroupReaperOptions(options.groupId)
|
|
36
|
+
: { groupId: options.groupId, stopTimeoutMs: options.stopTimeoutMs }
|
|
37
|
+
this.launchReaper = options.launchReaper ?? launchProcessGroupReaper
|
|
38
|
+
this.exit = options.exit ?? process.exit
|
|
39
|
+
this.retryDelayMs = options.retryDelayMs ?? 10
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
setParentWatch(parentWatch: ParentWatch): void {
|
|
43
|
+
this.parentWatch = parentWatch
|
|
44
|
+
if (this.intendedExitCode !== undefined) this.closeParentWatch()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
terminate(exitCode: number): void {
|
|
48
|
+
if (this.intendedExitCode === undefined) this.intendedExitCode = exitCode
|
|
49
|
+
if (this.handedOff) return
|
|
50
|
+
|
|
51
|
+
this.closeParentWatch()
|
|
52
|
+
this.tryHandoff()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private closeParentWatch(): void {
|
|
56
|
+
if (this.parentWatch === undefined || this.parentWatchClosed) return
|
|
57
|
+
this.parentWatchClosed = true
|
|
58
|
+
this.parentWatch.close()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private tryHandoff(): void {
|
|
62
|
+
if (this.handedOff || this.retry !== undefined) return
|
|
63
|
+
try {
|
|
64
|
+
this.launchReaper(this.reaperOptions)
|
|
65
|
+
} catch {
|
|
66
|
+
// Do not let the leader disappear while detached ownership is absent.
|
|
67
|
+
// A referenced retry keeps natural completion alive until spawn succeeds.
|
|
68
|
+
this.retry = setTimeout(() => {
|
|
69
|
+
this.retry = undefined
|
|
70
|
+
this.tryHandoff()
|
|
71
|
+
}, this.retryDelayMs)
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
this.handedOff = true
|
|
76
|
+
this.exit(this.intendedExitCode!)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { hostname } from 'node:os'
|
|
2
|
+
import { createForge } from '../ports/forge/create'
|
|
3
|
+
import { createProductionRuntimes } from '../ports/runner/production'
|
|
4
|
+
import { spawnExec } from '../ports/workspace/git-worktree'
|
|
5
|
+
import { loadPlugins } from '../plugins/load'
|
|
6
|
+
import { materializePluginRuntimes } from '../plugins/runtimes'
|
|
7
|
+
import { randomIds } from '../ids'
|
|
8
|
+
import { systemClock, type BuildStore } from '../store/types'
|
|
9
|
+
import { openProductionStore } from '../cli/store-opening'
|
|
10
|
+
import { BuildRunner, LeaseHeldError, SetupFailureError } from './build-runner'
|
|
11
|
+
import {
|
|
12
|
+
BUILD_EFFECTIVE_CONFIG_ARTIFACT,
|
|
13
|
+
diagnosticArtifact,
|
|
14
|
+
parseEffectiveBuildConfig,
|
|
15
|
+
selectOpenWorkspace,
|
|
16
|
+
type BuildRunnerDiagnosticOutcome,
|
|
17
|
+
} from './build-execution-state'
|
|
18
|
+
import type { BuildExecutionStart } from '../ports/workspace/build-execution'
|
|
19
|
+
|
|
20
|
+
function message(error: unknown): string {
|
|
21
|
+
return error instanceof Error ? error.message : String(error)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Child-side composition. The launch envelope supplies identity only; all
|
|
25
|
+
* execution location/configuration is discovered through the scoped Store. */
|
|
26
|
+
export async function runBuildChild(
|
|
27
|
+
input: BuildExecutionStart,
|
|
28
|
+
env: Record<string, string | undefined> = process.env,
|
|
29
|
+
openStore: (ref: string, token?: string) => BuildStore = openProductionStore,
|
|
30
|
+
): Promise<void> {
|
|
31
|
+
const fullStore = openStore(input.storeRef, env.AB_TOKEN)
|
|
32
|
+
const store = fullStore.scopeBuild(input.slug)
|
|
33
|
+
try {
|
|
34
|
+
const record = await store.getBuild(input.slug)
|
|
35
|
+
if (record === null) throw new Error(`unknown build ${JSON.stringify(input.slug)}`)
|
|
36
|
+
const workspace = selectOpenWorkspace(await store.getEvents(input.slug))
|
|
37
|
+
if (workspace === null) {
|
|
38
|
+
throw new Error(`build ${JSON.stringify(input.slug)} has no open durable workspace`)
|
|
39
|
+
}
|
|
40
|
+
if (workspace.ref !== input.workspaceRef) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`build ${JSON.stringify(input.slug)} workspace changed from launch ref ` +
|
|
43
|
+
`${JSON.stringify(input.workspaceRef)} to ${JSON.stringify(workspace.ref)}`,
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
const configArtifact = await store.getArtifact(input.slug, BUILD_EFFECTIVE_CONFIG_ARTIFACT)
|
|
47
|
+
if (configArtifact === null) {
|
|
48
|
+
throw new Error(`build ${JSON.stringify(input.slug)} has no effective config artifact`)
|
|
49
|
+
}
|
|
50
|
+
let currentConfig = parseEffectiveBuildConfig(configArtifact)
|
|
51
|
+
const plugins = await loadPlugins(currentConfig.plugins, workspace.path, {
|
|
52
|
+
packageRoot: record.repo,
|
|
53
|
+
})
|
|
54
|
+
const { runtimes: builtins } = createProductionRuntimes()
|
|
55
|
+
const runtimes = await materializePluginRuntimes(builtins, plugins, {
|
|
56
|
+
repoRoot: workspace.path,
|
|
57
|
+
env,
|
|
58
|
+
})
|
|
59
|
+
const forge = await createForge({
|
|
60
|
+
name: currentConfig.forge,
|
|
61
|
+
registry: plugins,
|
|
62
|
+
env,
|
|
63
|
+
repoRoot: workspace.path,
|
|
64
|
+
})
|
|
65
|
+
const getConfig = async () => {
|
|
66
|
+
const latest = await store.getArtifact(input.slug, BUILD_EFFECTIVE_CONFIG_ARTIFACT)
|
|
67
|
+
if (latest === null) return currentConfig
|
|
68
|
+
try {
|
|
69
|
+
currentConfig = parseEffectiveBuildConfig(latest)
|
|
70
|
+
} catch {
|
|
71
|
+
// The dispatcher publishes only validated snapshots. If storage is
|
|
72
|
+
// externally corrupted, preserve the child's last valid boundary.
|
|
73
|
+
}
|
|
74
|
+
return currentConfig
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const runner = new BuildRunner({
|
|
78
|
+
store,
|
|
79
|
+
config: currentConfig,
|
|
80
|
+
getConfig,
|
|
81
|
+
runtimes,
|
|
82
|
+
workspacePath: workspace.path,
|
|
83
|
+
branch: workspace.branch,
|
|
84
|
+
slug: input.slug,
|
|
85
|
+
exec: spawnExec,
|
|
86
|
+
forge,
|
|
87
|
+
ids: randomIds(),
|
|
88
|
+
clock: systemClock,
|
|
89
|
+
instance: input.instance,
|
|
90
|
+
host: hostname(),
|
|
91
|
+
sessionEnv: {
|
|
92
|
+
AB_STORE: input.storeRef,
|
|
93
|
+
...(env.AB_TOKEN !== undefined && env.AB_TOKEN !== '' ? { AB_TOKEN: env.AB_TOKEN } : {}),
|
|
94
|
+
},
|
|
95
|
+
})
|
|
96
|
+
await runner.run()
|
|
97
|
+
} catch (error) {
|
|
98
|
+
let outcome: BuildRunnerDiagnosticOutcome = 'failed'
|
|
99
|
+
if (error instanceof LeaseHeldError) outcome = 'lease-held'
|
|
100
|
+
else if (error instanceof SetupFailureError) outcome = 'setup-failed'
|
|
101
|
+
try {
|
|
102
|
+
await store.putArtifact(
|
|
103
|
+
input.slug,
|
|
104
|
+
diagnosticArtifact({ instance: input.instance, outcome, error: message(error) }),
|
|
105
|
+
)
|
|
106
|
+
} catch {
|
|
107
|
+
// Store reachability failures cannot be diagnosed through that Store.
|
|
108
|
+
}
|
|
109
|
+
throw error
|
|
110
|
+
} finally {
|
|
111
|
+
try {
|
|
112
|
+
await fullStore.close()
|
|
113
|
+
} catch {
|
|
114
|
+
// Process teardown cannot change the already-durable runner outcome. A
|
|
115
|
+
// close failure after a clean park must not turn truthful Store state
|
|
116
|
+
// into a synthetic failed child settlement.
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { Config } from '../config/schema'
|
|
2
|
+
import { configSchema } from '../config/schema'
|
|
3
|
+
import type { AbEvent } from '../events/catalog'
|
|
4
|
+
import type { Artifact, ArtifactInput } from '../store/types'
|
|
5
|
+
|
|
6
|
+
export const BUILD_EFFECTIVE_CONFIG_ARTIFACT = 'build-runner-effective-config'
|
|
7
|
+
export const BUILD_RUNNER_DIAGNOSTIC_ARTIFACT = 'build-runner-diagnostic'
|
|
8
|
+
|
|
9
|
+
export type BuildRunnerDiagnosticOutcome = 'lease-held' | 'setup-failed' | 'failed'
|
|
10
|
+
|
|
11
|
+
export interface BuildRunnerDiagnostic {
|
|
12
|
+
instance: string
|
|
13
|
+
outcome: BuildRunnerDiagnosticOutcome
|
|
14
|
+
error: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** JSON form of the normalized config's strict declarative input shape. */
|
|
18
|
+
export function effectiveBuildConfigContent(config: Config): string {
|
|
19
|
+
const { verify, finalize, ...root } = config
|
|
20
|
+
return JSON.stringify({
|
|
21
|
+
...root,
|
|
22
|
+
verify: { steps: verify.steps, ...verify.stepConfigs },
|
|
23
|
+
finalize: { steps: finalize.steps, ...finalize.stepConfigs },
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function parseEffectiveBuildConfig(artifact: Artifact): Config {
|
|
28
|
+
let value: unknown
|
|
29
|
+
try {
|
|
30
|
+
value = JSON.parse(new TextDecoder().decode(artifact.content))
|
|
31
|
+
} catch (error) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`invalid ${BUILD_EFFECTIVE_CONFIG_ARTIFACT} JSON: ${
|
|
34
|
+
error instanceof Error ? error.message : String(error)
|
|
35
|
+
}`,
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
const result = configSchema.safeParse(value)
|
|
39
|
+
if (!result.success) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`invalid ${BUILD_EFFECTIVE_CONFIG_ARTIFACT}: ${result.error.issues
|
|
42
|
+
.map((issue) => `${issue.path.join('.') || '(root)'}: ${issue.message}`)
|
|
43
|
+
.join('; ')}`,
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
return result.data
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Latest workspace location not followed by release. Historical events use
|
|
50
|
+
* the provider ref as the path compatibility fallback. */
|
|
51
|
+
export function selectOpenWorkspace(events: readonly AbEvent[]): {
|
|
52
|
+
ref: string
|
|
53
|
+
path: string
|
|
54
|
+
branch: string
|
|
55
|
+
} | null {
|
|
56
|
+
let open: { ref: string; path: string; branch: string } | null = null
|
|
57
|
+
for (const event of events) {
|
|
58
|
+
if (event.type === 'workspace.provisioned') {
|
|
59
|
+
open = {
|
|
60
|
+
ref: event.payload.ref,
|
|
61
|
+
path: event.payload.path ?? event.payload.ref,
|
|
62
|
+
branch: event.payload.branch,
|
|
63
|
+
}
|
|
64
|
+
} else if (event.type === 'workspace.released') {
|
|
65
|
+
open = null
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return open
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function diagnosticArtifact(diagnostic: BuildRunnerDiagnostic): ArtifactInput {
|
|
72
|
+
return {
|
|
73
|
+
kind: BUILD_RUNNER_DIAGNOSTIC_ARTIFACT,
|
|
74
|
+
content: JSON.stringify(diagnostic),
|
|
75
|
+
metadata: { instance: diagnostic.instance, outcome: diagnostic.outcome },
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function parseDiagnostic(artifact: Artifact): BuildRunnerDiagnostic | null {
|
|
80
|
+
try {
|
|
81
|
+
const value = JSON.parse(
|
|
82
|
+
new TextDecoder().decode(artifact.content),
|
|
83
|
+
) as Partial<BuildRunnerDiagnostic>
|
|
84
|
+
if (
|
|
85
|
+
typeof value.instance !== 'string' ||
|
|
86
|
+
typeof value.error !== 'string' ||
|
|
87
|
+
!['lease-held', 'setup-failed', 'failed'].includes(value.outcome ?? '')
|
|
88
|
+
) {
|
|
89
|
+
return null
|
|
90
|
+
}
|
|
91
|
+
return value as BuildRunnerDiagnostic
|
|
92
|
+
} catch {
|
|
93
|
+
return null
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Parent-liveness watchdog for a local build child. `expectedParentPid` comes
|
|
2
|
+
* from the immutable launch envelope rather than a possibly already-reparented
|
|
3
|
+
* `process.ppid`, closing the module-startup death window. The child
|
|
4
|
+
* deliberately terminates without releasing its lease; ordinary expiry is the
|
|
5
|
+
* durable recovery mechanism after an abrupt kernel death. */
|
|
6
|
+
export function watchBuildParent(
|
|
7
|
+
expectedParentPid: number,
|
|
8
|
+
onDeath: () => never | void,
|
|
9
|
+
intervalMs = 500,
|
|
10
|
+
): { close(): void } {
|
|
11
|
+
let closed = false
|
|
12
|
+
const check = (): void => {
|
|
13
|
+
if (closed) return
|
|
14
|
+
if (process.ppid !== expectedParentPid || process.ppid === 1) {
|
|
15
|
+
closed = true
|
|
16
|
+
onDeath()
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
process.kill(expectedParentPid, 0)
|
|
21
|
+
} catch {
|
|
22
|
+
closed = true
|
|
23
|
+
onDeath()
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const timer = setInterval(check, intervalMs)
|
|
27
|
+
timer.unref()
|
|
28
|
+
return {
|
|
29
|
+
close() {
|
|
30
|
+
if (closed) return
|
|
31
|
+
closed = true
|
|
32
|
+
clearInterval(timer)
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
}
|