@defrex/autobuild 0.11.0 → 0.13.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/README.md +3 -1
- package/SPEC.md +335 -31
- package/docs/architecture.md +125 -7
- package/docs/configuration.md +142 -21
- package/docs/hosted-dispatcher.md +17 -2
- package/docs/hosted-service.md +240 -0
- package/docs/mcp.md +8 -2
- package/docs/operator-api.md +97 -7
- package/docs/remote-store-protocol.md +199 -144
- package/docs/remote-ticket-protocol.md +249 -19
- package/docs/setup.md +199 -16
- package/docs/ticket-dependencies.md +1 -1
- package/docs/ticket-migration.md +64 -0
- package/package.json +7 -6
- package/packages/core/src/cli/attention.ts +209 -0
- package/packages/core/src/cli/build-control.ts +12 -1
- package/packages/core/src/cli/configured-store.ts +17 -0
- package/packages/core/src/cli/connect.ts +364 -0
- package/packages/core/src/cli/context.ts +7 -1
- package/packages/core/src/cli/dashboard/model.ts +4 -5
- package/packages/core/src/cli/dashboard/poll.ts +1 -1
- package/packages/core/src/cli/dispatch.ts +336 -41
- package/packages/core/src/cli/harvest.ts +13 -3
- package/packages/core/src/cli/help.ts +51 -16
- package/packages/core/src/cli/init-validation.ts +27 -2
- package/packages/core/src/cli/installation.ts +67 -13
- package/packages/core/src/cli/main.ts +60 -16
- package/packages/core/src/cli/mcp.ts +9 -2
- package/packages/core/src/cli/pr-summary-publication.ts +220 -0
- package/packages/core/src/cli/pr-summary.ts +2 -0
- package/packages/core/src/cli/project-credentials.ts +132 -0
- package/packages/core/src/cli/repo-state.ts +15 -2
- package/packages/core/src/cli/self-update.ts +15 -1
- package/packages/core/src/cli/store-opening.ts +22 -14
- package/packages/core/src/cli/terminals.ts +0 -10
- package/packages/core/src/cli/ticket-assets.ts +7 -2
- package/packages/core/src/cli/ticket-editor.ts +82 -0
- package/packages/core/src/cli/ticket.ts +172 -35
- package/packages/core/src/cli/upgrade-compatibility.ts +80 -0
- package/packages/core/src/cli/wait.ts +84 -17
- package/packages/core/src/cli/watch.ts +197 -60
- package/packages/core/src/config/live.ts +3 -0
- package/packages/core/src/config/schema.ts +90 -11
- package/packages/core/src/connection.ts +86 -0
- package/packages/core/src/events/catalog.ts +11 -2
- package/packages/core/src/events/payloads.ts +50 -0
- package/packages/core/src/events/repository.ts +13 -0
- package/packages/core/src/integration/harness.ts +37 -2
- package/packages/core/src/kernel/__snapshots__/reducer-registry.test.ts.snap +145 -83
- package/packages/core/src/kernel/engine.ts +24 -0
- package/packages/core/src/kernel/generators/build-log.ts +11 -1
- package/packages/core/src/kernel/incremental-fixtures.ts +13 -2
- package/packages/core/src/kernel/log-index.ts +17 -0
- package/packages/core/src/kernel/pr-summary.ts +89 -0
- package/packages/core/src/kernel/reducer-registry.ts +10 -0
- package/packages/core/src/kernel/reducer.ts +8 -0
- package/packages/core/src/ontology.ts +2 -0
- package/packages/core/src/operator/annotations.ts +12 -0
- package/packages/core/src/operator/index.ts +12 -0
- package/packages/core/src/operator/protocol.ts +19 -2
- package/packages/core/src/operator/query.ts +38 -16
- package/packages/core/src/operator/registry.ts +61 -8
- package/packages/core/src/operator/ticket-source-backend.ts +17 -20
- package/packages/core/src/operator/tickets.ts +136 -28
- package/packages/core/src/plugin-sdk/index.ts +7 -0
- package/packages/core/src/plugins/manifest.ts +5 -3
- package/packages/core/src/plugins/runtimes.ts +7 -1
- package/packages/core/src/ports/forge/fake.ts +13 -1
- package/packages/core/src/ports/forge/github-transport.ts +36 -1
- package/packages/core/src/ports/forge/github.ts +29 -5
- package/packages/core/src/ports/forge/local-git.ts +12 -4
- package/packages/core/src/ports/forge/summary-marker.ts +8 -0
- package/packages/core/src/ports/runner/session-env.ts +3 -0
- package/packages/core/src/ports/tickets/contract.ts +66 -1
- package/packages/core/src/ports/tickets/create.ts +54 -10
- package/packages/core/src/ports/tickets/fake.ts +30 -1
- package/packages/core/src/ports/tickets/file-assets.ts +159 -0
- package/packages/core/src/ports/tickets/file-codec.ts +90 -0
- package/packages/core/src/ports/tickets/file.ts +114 -120
- package/packages/core/src/ports/tickets/grants.ts +47 -0
- package/packages/core/src/ports/tickets/hosted-index.ts +17 -3
- package/packages/core/src/ports/tickets/identity.ts +20 -0
- package/packages/core/src/ports/tickets/lifecycle.ts +18 -0
- package/packages/core/src/ports/tickets/linear.ts +74 -9
- package/packages/core/src/ports/tickets/migration.ts +336 -0
- package/packages/core/src/ports/tickets/operations.ts +38 -7
- package/packages/core/src/ports/tickets/portable-assets.ts +54 -0
- package/packages/core/src/ports/tickets/remote-protocol.ts +96 -0
- package/packages/core/src/ports/tickets/remote.ts +61 -3
- package/packages/core/src/ports/tickets/requested-id.ts +46 -0
- package/packages/core/src/ports/tickets/revision.ts +62 -0
- package/packages/core/src/ports/tickets/states.ts +61 -0
- package/packages/core/src/ports/types.ts +40 -3
- package/packages/core/src/ports/workspace/build-execution.ts +6 -1
- package/packages/core/src/ports/workspace/create.ts +15 -2
- package/packages/core/src/ports/workspace/distribution-archive.ts +3 -5
- package/packages/core/src/ports/workspace/harvest-execution.ts +3 -1
- package/packages/core/src/ports/workspace/local-build-execution.ts +8 -1
- package/packages/core/src/ports/workspace/operator-sandbox.ts +2 -0
- package/packages/core/src/ports/workspace/vercel-sandbox.ts +51 -19
- package/packages/core/src/processes/build-execution-state.ts +1 -1
- package/packages/core/src/processes/build-runner.ts +87 -26
- package/packages/core/src/processes/dispatch-failure-budget.ts +83 -0
- package/packages/core/src/processes/dispatcher.ts +168 -41
- package/packages/core/src/processes/execution-no-progress.ts +176 -0
- package/packages/core/src/processes/harvest-child.ts +31 -4
- package/packages/core/src/processes/harvest-execution-state.ts +19 -2
- package/packages/core/src/processes/harvest-runner.ts +166 -104
- package/packages/core/src/processes/harvest.ts +34 -14
- package/packages/core/src/processes/phase-failure-budget.ts +2 -0
- package/packages/core/src/processes/publication-loss.ts +18 -1
- package/packages/core/src/processes/repo-view.ts +35 -0
- package/packages/core/src/store/contract.ts +32 -1
- package/packages/core/src/store/local/store.ts +1 -1
- package/packages/core/src/store/remote/client.ts +1 -1
- package/packages/core/src/store/remote/compatibility.ts +90 -0
- package/packages/core/src/store/remote/credentials.ts +28 -0
- package/packages/core/src/store/remote/index.ts +14 -1
- package/packages/core/src/store/remote/protocol.ts +25 -0
- package/packages/core/src/store/remote/token.ts +74 -13
- package/packages/core/src/store/ticket-asset-snapshots.ts +192 -0
- package/packages/core/src/store/ticket-assets.ts +86 -8
- package/packages/core/src/store/types.ts +1 -1
- package/packages/core/src/testing/index.ts +7 -0
- package/skills/guide/SKILL.md +232 -34
- package/skills/guide/references/operator-api.md +142 -13
- package/skills/guide/references/plugin-authoring.md +10 -2
- package/skills/guide/references/remote-store-protocol.md +199 -144
- package/skills/guide/references/remote-ticket-protocol.md +253 -18
- package/skills/guide/references/setup.md +199 -16
- package/skills/guide/references/ticket-dependencies.md +1 -1
- package/skills/guide/references/ticket-migration.md +64 -0
- package/skills/operate/SKILL.md +126 -36
- package/skills/tickets/SKILL.md +71 -0
- package/templates/hosted-service/package.json +13 -0
- package/templates/hosted-service/vercel.json +7 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { delegateCredential } from '../../store/remote/credentials'
|
|
2
|
+
import type { ExecutionLaunchAuth } from './build-execution'
|
|
1
3
|
import { createHash } from 'node:crypto'
|
|
2
4
|
import { Sandbox, Snapshot, type NetworkPolicy, type SandboxRegion } from '@vercel/sandbox'
|
|
3
5
|
import { displayName, tomlKey, type RuntimeReferenceGroup } from '../../config/roles'
|
|
@@ -638,7 +640,9 @@ function runtimeEnvironment(
|
|
|
638
640
|
env: Record<string, string | undefined>,
|
|
639
641
|
): Record<string, string> {
|
|
640
642
|
return Object.fromEntries(
|
|
641
|
-
config.environmentVariables
|
|
643
|
+
config.environmentVariables
|
|
644
|
+
.filter((name) => !['AB_TOKEN', 'AB_TICKET_URL', 'AB_TICKET_TOKEN'].includes(name))
|
|
645
|
+
.map((name) => [name, requireVercelEnvironmentValue(env, name)]),
|
|
642
646
|
)
|
|
643
647
|
}
|
|
644
648
|
|
|
@@ -942,10 +946,9 @@ export async function validateVercelSandbox(
|
|
|
942
946
|
env: Object.fromEntries([
|
|
943
947
|
['AB_STORE', options.storeRef],
|
|
944
948
|
['AB_TOKEN', options.storeToken],
|
|
945
|
-
...config.environmentVariables
|
|
946
|
-
name,
|
|
947
|
-
requireVercelEnvironmentValue(options.env, name),
|
|
948
|
-
]),
|
|
949
|
+
...config.environmentVariables
|
|
950
|
+
.filter((name) => !['AB_TOKEN', 'AB_TICKET_URL', 'AB_TICKET_TOKEN'].includes(name))
|
|
951
|
+
.map((name) => [name, requireVercelEnvironmentValue(options.env, name)]),
|
|
949
952
|
]),
|
|
950
953
|
}),
|
|
951
954
|
)
|
|
@@ -1106,10 +1109,11 @@ async function effectiveArchive(
|
|
|
1106
1109
|
}
|
|
1107
1110
|
|
|
1108
1111
|
export interface VercelSandboxProviderOptions {
|
|
1112
|
+
delegateCredential?: typeof delegateCredential
|
|
1109
1113
|
config: VercelSandboxConfig
|
|
1110
1114
|
env: Record<string, string | undefined>
|
|
1111
1115
|
storeRef: string
|
|
1112
|
-
storeToken
|
|
1116
|
+
storeToken?: string
|
|
1113
1117
|
/** Dispatcher-local main checkout, used only for origin discovery and verification.
|
|
1114
1118
|
* With the injected `origin`/`remoteBranchHead` seams (checkout-less origin
|
|
1115
1119
|
* mode) it is never touched on the host. */
|
|
@@ -1188,18 +1192,21 @@ export class VercelSandboxProvider implements WorkspaceProvider {
|
|
|
1188
1192
|
}
|
|
1189
1193
|
|
|
1190
1194
|
constructor(private readonly options: VercelSandboxProviderOptions) {
|
|
1191
|
-
if (
|
|
1195
|
+
if (
|
|
1196
|
+
!/^https:\/\//i.test(options.storeRef) ||
|
|
1197
|
+
(!options.storeToken && !options.delegateCredential)
|
|
1198
|
+
) {
|
|
1192
1199
|
throw new Error('vercel-sandbox requires an HTTPS BuildStore and scoped AB_TOKEN authority')
|
|
1193
1200
|
}
|
|
1194
1201
|
this.facade = options.facade ?? createVercelSdkFacade(options.env)
|
|
1195
1202
|
this.exec = options.exec ?? spawnExec
|
|
1196
1203
|
this.observeWaitMs = options.observeWaitMs ?? VERCEL_OBSERVE_WAIT_MS
|
|
1197
1204
|
this.buildExecution = {
|
|
1198
|
-
start: (input) => this.start(input),
|
|
1205
|
+
start: (input, auth) => this.start(input, auth),
|
|
1199
1206
|
observe: (identity) => this.observeExecution(identity),
|
|
1200
1207
|
}
|
|
1201
1208
|
this.harvestExecution = {
|
|
1202
|
-
start: (input) => this.startHarvestExecution(input),
|
|
1209
|
+
start: (input, auth) => this.startHarvestExecution(input, auth),
|
|
1203
1210
|
observe: (identity) => this.observeExecution(identity),
|
|
1204
1211
|
}
|
|
1205
1212
|
this.orchestratorSandbox = {
|
|
@@ -1244,6 +1251,11 @@ export class VercelSandboxProvider implements WorkspaceProvider {
|
|
|
1244
1251
|
revision?: string
|
|
1245
1252
|
generation?: number
|
|
1246
1253
|
}): Promise<WorkspaceProvisionResult> {
|
|
1254
|
+
const scopedToken = await (this.options.delegateCredential ?? delegateCredential)(
|
|
1255
|
+
this.options.storeRef,
|
|
1256
|
+
this.options.storeToken,
|
|
1257
|
+
{ kind: 'build-runner', id: opts.branch.replace(/^ab\//, '') },
|
|
1258
|
+
)
|
|
1247
1259
|
const rawOrigin = await this.origin()
|
|
1248
1260
|
const origin = cleanGithubOrigin(rawOrigin)
|
|
1249
1261
|
const name = sandboxName(origin.url, opts.branch, opts.generation)
|
|
@@ -1385,7 +1397,7 @@ export class VercelSandboxProvider implements WorkspaceProvider {
|
|
|
1385
1397
|
await bootstrapRuntimes(
|
|
1386
1398
|
sandbox,
|
|
1387
1399
|
this.options.config,
|
|
1388
|
-
runtimeEnvironment(this.options.config, this.options.env),
|
|
1400
|
+
{ ...runtimeEnvironment(this.options.config, this.options.env), AB_TOKEN: scopedToken },
|
|
1389
1401
|
currentRuntimeReferences(this.options.runtimeReferences),
|
|
1390
1402
|
true,
|
|
1391
1403
|
)
|
|
@@ -1521,7 +1533,17 @@ export class VercelSandboxProvider implements WorkspaceProvider {
|
|
|
1521
1533
|
}
|
|
1522
1534
|
}
|
|
1523
1535
|
|
|
1524
|
-
private async start(
|
|
1536
|
+
private async start(
|
|
1537
|
+
input: BuildExecutionStart,
|
|
1538
|
+
auth?: ExecutionLaunchAuth,
|
|
1539
|
+
): Promise<BuildExecutionHandle> {
|
|
1540
|
+
const scopedToken =
|
|
1541
|
+
auth?.token ??
|
|
1542
|
+
(await (this.options.delegateCredential ?? delegateCredential)(
|
|
1543
|
+
this.options.storeRef,
|
|
1544
|
+
this.options.storeToken,
|
|
1545
|
+
{ kind: 'build-runner', id: input.slug },
|
|
1546
|
+
))
|
|
1525
1547
|
const ref = input.workspaceRef
|
|
1526
1548
|
if (this.active.has(ref)) throw new Error(`sandbox ${ref} already has a live execution`)
|
|
1527
1549
|
const sandbox = this.sessions.get(ref) ?? (await this.facade.get(ref, this.operationSignal()))
|
|
@@ -1566,21 +1588,22 @@ export class VercelSandboxProvider implements WorkspaceProvider {
|
|
|
1566
1588
|
await bootstrapRuntimes(
|
|
1567
1589
|
sandbox,
|
|
1568
1590
|
this.options.config,
|
|
1569
|
-
runtimeEnvironment(this.options.config, this.options.env),
|
|
1591
|
+
{ ...runtimeEnvironment(this.options.config, this.options.env), AB_TOKEN: scopedToken },
|
|
1570
1592
|
currentRuntimeReferences(this.options.runtimeReferences),
|
|
1571
1593
|
false,
|
|
1572
1594
|
)
|
|
1573
1595
|
this.sessions.set(ref, sandbox)
|
|
1574
1596
|
const env: Record<string, string> = {
|
|
1575
1597
|
AB_STORE: this.options.storeRef,
|
|
1576
|
-
AB_TOKEN:
|
|
1598
|
+
AB_TOKEN: scopedToken,
|
|
1577
1599
|
[BUILD_RUNNER_OPTIONS_ENV]: JSON.stringify({
|
|
1578
1600
|
...input,
|
|
1579
1601
|
supervision: { kind: 'environment' },
|
|
1580
1602
|
}),
|
|
1581
1603
|
}
|
|
1582
1604
|
for (const name of this.options.config.environmentVariables)
|
|
1583
|
-
|
|
1605
|
+
if (!['AB_TOKEN', 'AB_TICKET_URL', 'AB_TICKET_TOKEN'].includes(name))
|
|
1606
|
+
env[name] = requireVercelEnvironmentValue(this.options.env, name)
|
|
1584
1607
|
const command = (await sandbox.runCommand({
|
|
1585
1608
|
cmd: 'sh',
|
|
1586
1609
|
args: [
|
|
@@ -1696,7 +1719,12 @@ export class VercelSandboxProvider implements WorkspaceProvider {
|
|
|
1696
1719
|
* build execution. Provisioning reuses the module-local build chain so a
|
|
1697
1720
|
* harvest guest is identical to a build guest; release is the provider's
|
|
1698
1721
|
* existing reap path (stop, snapshot purge, delete). */
|
|
1699
|
-
private async startHarvestExecution(
|
|
1722
|
+
private async startHarvestExecution(
|
|
1723
|
+
input: HarvestExecutionStart,
|
|
1724
|
+
auth?: ExecutionLaunchAuth,
|
|
1725
|
+
): Promise<BuildExecutionHandle> {
|
|
1726
|
+
if (!auth) throw new Error('harvest launch requires delegated authority')
|
|
1727
|
+
const scopedToken = auth.token
|
|
1700
1728
|
const origin = cleanGithubOrigin(await this.origin())
|
|
1701
1729
|
const name = harvestSandboxName(origin.url)
|
|
1702
1730
|
if (this.active.has(name)) throw new Error(`sandbox ${name} already has a live execution`)
|
|
@@ -1824,7 +1852,7 @@ export class VercelSandboxProvider implements WorkspaceProvider {
|
|
|
1824
1852
|
await bootstrapRuntimes(
|
|
1825
1853
|
sandbox,
|
|
1826
1854
|
this.options.config,
|
|
1827
|
-
runtimeEnvironment(this.options.config, this.options.env),
|
|
1855
|
+
{ ...runtimeEnvironment(this.options.config, this.options.env), AB_TOKEN: scopedToken },
|
|
1828
1856
|
currentRuntimeReferences(this.options.runtimeReferences),
|
|
1829
1857
|
true,
|
|
1830
1858
|
)
|
|
@@ -1861,7 +1889,7 @@ export class VercelSandboxProvider implements WorkspaceProvider {
|
|
|
1861
1889
|
await bootstrapRuntimes(
|
|
1862
1890
|
sandbox,
|
|
1863
1891
|
this.options.config,
|
|
1864
|
-
runtimeEnvironment(this.options.config, this.options.env),
|
|
1892
|
+
{ ...runtimeEnvironment(this.options.config, this.options.env), AB_TOKEN: scopedToken },
|
|
1865
1893
|
currentRuntimeReferences(this.options.runtimeReferences),
|
|
1866
1894
|
false,
|
|
1867
1895
|
)
|
|
@@ -1878,11 +1906,15 @@ export class VercelSandboxProvider implements WorkspaceProvider {
|
|
|
1878
1906
|
}
|
|
1879
1907
|
const env: Record<string, string> = {
|
|
1880
1908
|
AB_STORE: this.options.storeRef,
|
|
1881
|
-
AB_TOKEN:
|
|
1909
|
+
AB_TOKEN: scopedToken,
|
|
1910
|
+
...(auth?.tickets
|
|
1911
|
+
? { AB_TICKET_URL: auth.tickets.url, AB_TICKET_TOKEN: auth.tickets.token }
|
|
1912
|
+
: {}),
|
|
1882
1913
|
[HARVEST_RUNNER_OPTIONS_ENV]: JSON.stringify(envelope),
|
|
1883
1914
|
}
|
|
1884
1915
|
for (const envName of this.options.config.environmentVariables)
|
|
1885
|
-
|
|
1916
|
+
if (!['AB_TOKEN', 'AB_TICKET_URL', 'AB_TICKET_TOKEN'].includes(envName))
|
|
1917
|
+
env[envName] = requireVercelEnvironmentValue(this.options.env, envName)
|
|
1886
1918
|
const command = (await sandbox.runCommand({
|
|
1887
1919
|
cmd: 'sh',
|
|
1888
1920
|
args: [
|
|
@@ -22,7 +22,7 @@ export function effectiveBuildConfigContent(config: Config): string {
|
|
|
22
22
|
const { verify, finalize, ...root } = config
|
|
23
23
|
return JSON.stringify({
|
|
24
24
|
...root,
|
|
25
|
-
verify: { steps: verify.steps, ...verify.stepConfigs },
|
|
25
|
+
verify: { steps: verify.steps, afterReconcile: verify.afterReconcile, ...verify.stepConfigs },
|
|
26
26
|
finalize: { steps: finalize.steps, ...finalize.stepConfigs },
|
|
27
27
|
})
|
|
28
28
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { delegateCredential } from '../store/remote/credentials'
|
|
2
|
+
import { refreshSummaryBoundary } from '../cli/pr-summary-publication'
|
|
1
3
|
/**
|
|
2
4
|
* The build-runner process (SPEC §3.3): one per build, owns one pipeline
|
|
3
5
|
* execution end to end. The kernel's job reduced to a loop (§8): read the
|
|
@@ -170,6 +172,7 @@ export interface BuildRunnerDeps {
|
|
|
170
172
|
/** Extra AB_* the launcher wants in every session env (e.g. AB_TOKEN,
|
|
171
173
|
* AB_STORE for a remote store) — D8. May override AB_STORE; never the
|
|
172
174
|
* per-session identity keys (AB_BUILD/AB_PHASE/AB_SESSION). */
|
|
175
|
+
delegateCredential?: typeof delegateCredential
|
|
173
176
|
sessionEnv?: Record<string, string>
|
|
174
177
|
opts?: BuildRunnerOpts
|
|
175
178
|
}
|
|
@@ -819,6 +822,26 @@ export class BuildRunner {
|
|
|
819
822
|
const events = await store.getEvents(slug)
|
|
820
823
|
const decision = decideNext(events, config)
|
|
821
824
|
switch (decision.kind) {
|
|
825
|
+
case 'capture-reconcile-policy':
|
|
826
|
+
await store.append(slug, {
|
|
827
|
+
actor: KERNEL,
|
|
828
|
+
type: 'verify.reconcile-policy-captured',
|
|
829
|
+
payload: {
|
|
830
|
+
reconcileSeq: decision.reconcileSeq,
|
|
831
|
+
steps: config.verify.steps.map((step) => {
|
|
832
|
+
const override = config.verify.stepConfigs[step]?.afterReconcile
|
|
833
|
+
return {
|
|
834
|
+
step,
|
|
835
|
+
enabled: override ?? config.verify.afterReconcile,
|
|
836
|
+
source:
|
|
837
|
+
override === undefined
|
|
838
|
+
? 'verify.afterReconcile'
|
|
839
|
+
: `verify.${step}.afterReconcile`,
|
|
840
|
+
}
|
|
841
|
+
}),
|
|
842
|
+
},
|
|
843
|
+
})
|
|
844
|
+
return decision
|
|
822
845
|
case 'wait':
|
|
823
846
|
return decision
|
|
824
847
|
case 'acknowledge':
|
|
@@ -866,12 +889,15 @@ export class BuildRunner {
|
|
|
866
889
|
}
|
|
867
890
|
}
|
|
868
891
|
try {
|
|
892
|
+
if (await this.refreshSummary())
|
|
893
|
+
return reduceBuild(await this.deps.store.getEvents(this.deps.slug))
|
|
869
894
|
if (publicationPending(await this.deps.store.getEvents(this.deps.slug))) {
|
|
870
895
|
return reduceBuild(await this.deps.store.getEvents(this.deps.slug))
|
|
871
896
|
}
|
|
872
897
|
for (;;) {
|
|
873
898
|
await this.ensureLease()
|
|
874
899
|
const decision = await this.step()
|
|
900
|
+
if (await this.refreshSummary()) break
|
|
875
901
|
if (decision.kind === 'wait') break
|
|
876
902
|
if (publicationPending(await this.deps.store.getEvents(this.deps.slug))) break
|
|
877
903
|
}
|
|
@@ -882,6 +908,22 @@ export class BuildRunner {
|
|
|
882
908
|
}
|
|
883
909
|
}
|
|
884
910
|
|
|
911
|
+
private async refreshSummary(): Promise<boolean> {
|
|
912
|
+
try {
|
|
913
|
+
await this.ensureLease()
|
|
914
|
+
} catch {
|
|
915
|
+
return false
|
|
916
|
+
}
|
|
917
|
+
return refreshSummaryBoundary({
|
|
918
|
+
store: this.deps.store,
|
|
919
|
+
forge: this.deps.forge,
|
|
920
|
+
slug: this.deps.slug,
|
|
921
|
+
workspacePath: this.deps.workspacePath,
|
|
922
|
+
storeRef: this.deps.sessionEnv?.AB_STORE ?? 'local',
|
|
923
|
+
clock: this.deps.clock,
|
|
924
|
+
})
|
|
925
|
+
}
|
|
926
|
+
|
|
885
927
|
/**
|
|
886
928
|
* §7.4 exclusivity, holder side: a lapsed lease means the sweep may already
|
|
887
929
|
* have launched a replacement, so this runner must re-claim before doing
|
|
@@ -1146,6 +1188,7 @@ export class BuildRunner {
|
|
|
1146
1188
|
attempt: decision.attempt,
|
|
1147
1189
|
outcome: 'skipped',
|
|
1148
1190
|
reason: decision.reason,
|
|
1191
|
+
...(decision.skipCause !== undefined ? { skipCause: decision.skipCause } : {}),
|
|
1149
1192
|
},
|
|
1150
1193
|
} satisfies EventWrite<'verify.completed'>)
|
|
1151
1194
|
}
|
|
@@ -1254,6 +1297,11 @@ export class BuildRunner {
|
|
|
1254
1297
|
events: AbEvent[],
|
|
1255
1298
|
): Promise<void> {
|
|
1256
1299
|
const { step, action } = decision
|
|
1300
|
+
await this.deps.store.append(this.deps.slug, {
|
|
1301
|
+
actor: KERNEL,
|
|
1302
|
+
type: 'finalize.step-started',
|
|
1303
|
+
payload: { step },
|
|
1304
|
+
})
|
|
1257
1305
|
let actor: Actor = KERNEL
|
|
1258
1306
|
let durableHead: string | undefined
|
|
1259
1307
|
let failureNote: string | undefined
|
|
@@ -1383,7 +1431,7 @@ export class BuildRunner {
|
|
|
1383
1431
|
const turn = await this.runBudgetedTurn(
|
|
1384
1432
|
started.seq,
|
|
1385
1433
|
resolved.sessionBudgetSeconds,
|
|
1386
|
-
(signal) =>
|
|
1434
|
+
async (signal) =>
|
|
1387
1435
|
target.runner.start({
|
|
1388
1436
|
skill,
|
|
1389
1437
|
invocation: slug,
|
|
@@ -1391,7 +1439,7 @@ export class BuildRunner {
|
|
|
1391
1439
|
workspacePath,
|
|
1392
1440
|
...(target.model !== undefined ? { model: target.model } : {}),
|
|
1393
1441
|
...(target.args !== undefined ? { args: target.args } : {}),
|
|
1394
|
-
env: this.sessionEnvFor('finalize@1', session),
|
|
1442
|
+
env: await this.sessionEnvFor('finalize@1', session),
|
|
1395
1443
|
signal,
|
|
1396
1444
|
...(stream !== undefined ? { stream } : {}),
|
|
1397
1445
|
}),
|
|
@@ -1523,7 +1571,7 @@ export class BuildRunner {
|
|
|
1523
1571
|
|
|
1524
1572
|
let handle: AgentSessionHandle | undefined
|
|
1525
1573
|
let failureNote: string | undefined
|
|
1526
|
-
const turn = await this.runBudgetedTurn(started.seq, sessionBudgetSeconds, (signal) =>
|
|
1574
|
+
const turn = await this.runBudgetedTurn(started.seq, sessionBudgetSeconds, async (signal) =>
|
|
1527
1575
|
runner.start({
|
|
1528
1576
|
skill,
|
|
1529
1577
|
invocation: slug,
|
|
@@ -1531,7 +1579,7 @@ export class BuildRunner {
|
|
|
1531
1579
|
workspacePath,
|
|
1532
1580
|
...(model !== undefined ? { model } : {}),
|
|
1533
1581
|
...(args !== undefined ? { args } : {}),
|
|
1534
|
-
env: this.sessionEnvFor('finalize@1', session),
|
|
1582
|
+
env: await this.sessionEnvFor('finalize@1', session),
|
|
1535
1583
|
signal,
|
|
1536
1584
|
...(stream !== undefined ? { stream } : {}),
|
|
1537
1585
|
}),
|
|
@@ -1907,26 +1955,29 @@ export class BuildRunner {
|
|
|
1907
1955
|
let handle: AgentSessionHandle | undefined = live?.handle
|
|
1908
1956
|
let result: AgentTurnResult | undefined
|
|
1909
1957
|
let turnError: unknown
|
|
1910
|
-
const turn = await this.runBudgetedTurn(
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1958
|
+
const turn = await this.runBudgetedTurn(
|
|
1959
|
+
preSeq,
|
|
1960
|
+
primary.sessionBudgetSeconds,
|
|
1961
|
+
async (signal) =>
|
|
1962
|
+
live !== undefined
|
|
1963
|
+
? live.runner
|
|
1964
|
+
.continue(live.handle, continueMessage(spec), {
|
|
1965
|
+
env: await this.sessionEnvFor(spec.abPhase, session),
|
|
1966
|
+
signal,
|
|
1967
|
+
...(stream !== undefined ? { stream } : {}),
|
|
1968
|
+
})
|
|
1969
|
+
.then((continued) => ({ session: live.handle, result: continued }))
|
|
1970
|
+
: target.runner.start({
|
|
1971
|
+
skill: spec.skill,
|
|
1972
|
+
invocation: slug,
|
|
1973
|
+
buildSlug: slug,
|
|
1974
|
+
workspacePath,
|
|
1975
|
+
...(target.model !== undefined ? { model: target.model } : {}),
|
|
1976
|
+
...(target.args !== undefined ? { args: target.args } : {}),
|
|
1977
|
+
env: await this.sessionEnvFor(spec.abPhase, session),
|
|
1915
1978
|
signal,
|
|
1916
1979
|
...(stream !== undefined ? { stream } : {}),
|
|
1917
|
-
})
|
|
1918
|
-
.then((continued) => ({ session: live.handle, result: continued }))
|
|
1919
|
-
: target.runner.start({
|
|
1920
|
-
skill: spec.skill,
|
|
1921
|
-
invocation: slug,
|
|
1922
|
-
buildSlug: slug,
|
|
1923
|
-
workspacePath,
|
|
1924
|
-
...(target.model !== undefined ? { model: target.model } : {}),
|
|
1925
|
-
...(target.args !== undefined ? { args: target.args } : {}),
|
|
1926
|
-
env: this.sessionEnvFor(spec.abPhase, session),
|
|
1927
|
-
signal,
|
|
1928
|
-
...(stream !== undefined ? { stream } : {}),
|
|
1929
|
-
}),
|
|
1980
|
+
}),
|
|
1930
1981
|
)
|
|
1931
1982
|
if (turn.settlement.kind === 'value') {
|
|
1932
1983
|
handle = turn.settlement.value.session
|
|
@@ -2192,7 +2243,7 @@ export class BuildRunner {
|
|
|
2192
2243
|
let handle: AgentSessionHandle | undefined = live?.handle
|
|
2193
2244
|
let result: AgentTurnResult | undefined
|
|
2194
2245
|
let turnError: unknown
|
|
2195
|
-
const turn = await this.runBudgetedTurn(preSeq, sessionBudgetSeconds, (signal) => {
|
|
2246
|
+
const turn = await this.runBudgetedTurn(preSeq, sessionBudgetSeconds, async (signal) => {
|
|
2196
2247
|
if (live !== undefined) {
|
|
2197
2248
|
// §10: the producer continues its session; the feedback message
|
|
2198
2249
|
// points at the .ab/ inputs — `ab context` is the real carrier. The
|
|
@@ -2202,7 +2253,7 @@ export class BuildRunner {
|
|
|
2202
2253
|
// 1's second terminal (§8.4 D5).
|
|
2203
2254
|
return live.runner
|
|
2204
2255
|
.continue(live.handle, continueMessage(spec), {
|
|
2205
|
-
env: this.sessionEnvFor(spec.abPhase, session),
|
|
2256
|
+
env: await this.sessionEnvFor(spec.abPhase, session),
|
|
2206
2257
|
signal,
|
|
2207
2258
|
...(stream !== undefined ? { stream } : {}),
|
|
2208
2259
|
})
|
|
@@ -2215,7 +2266,7 @@ export class BuildRunner {
|
|
|
2215
2266
|
workspacePath,
|
|
2216
2267
|
...(model !== undefined ? { model } : {}),
|
|
2217
2268
|
...(args !== undefined ? { args } : {}),
|
|
2218
|
-
env: this.sessionEnvFor(spec.abPhase, session),
|
|
2269
|
+
env: await this.sessionEnvFor(spec.abPhase, session),
|
|
2219
2270
|
signal,
|
|
2220
2271
|
...(stream !== undefined ? { stream } : {}),
|
|
2221
2272
|
})
|
|
@@ -2560,10 +2611,20 @@ export class BuildRunner {
|
|
|
2560
2611
|
|
|
2561
2612
|
/** D8 ambient auth: sessionEnv may override AB_STORE (remote stores) and
|
|
2562
2613
|
* add AB_TOKEN, but never the per-session identity keys. */
|
|
2563
|
-
private sessionEnvFor(abPhase: string, session: string): Record<string, string
|
|
2614
|
+
private async sessionEnvFor(abPhase: string, session: string): Promise<Record<string, string>> {
|
|
2615
|
+
const parent = this.deps.sessionEnv
|
|
2616
|
+
const token =
|
|
2617
|
+
parent?.AB_STORE && /^https?:/i.test(parent.AB_STORE)
|
|
2618
|
+
? await (this.deps.delegateCredential ?? delegateCredential)(
|
|
2619
|
+
parent.AB_STORE,
|
|
2620
|
+
parent.AB_TOKEN,
|
|
2621
|
+
{ kind: 'build-phase', id: this.deps.slug, session },
|
|
2622
|
+
)
|
|
2623
|
+
: undefined
|
|
2564
2624
|
return {
|
|
2565
2625
|
AB_STORE: 'local',
|
|
2566
2626
|
...this.deps.sessionEnv,
|
|
2627
|
+
...(token ? { AB_TOKEN: token } : {}),
|
|
2567
2628
|
AB_BUILD: this.deps.slug,
|
|
2568
2629
|
AB_PHASE: abPhase,
|
|
2569
2630
|
AB_SESSION: session,
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { AbEvent } from '../events/catalog'
|
|
2
|
+
import { DISPATCHER } from '../events/envelope'
|
|
3
|
+
import type { IdSource } from '../ids'
|
|
4
|
+
import { defineReducer } from '../kernel/incremental'
|
|
5
|
+
import type { BuildStore } from '../store/types'
|
|
6
|
+
|
|
7
|
+
interface DispatchBudgetAccumulator {
|
|
8
|
+
failures: Extract<AbEvent, { type: 'dispatch.failed' }>[]
|
|
9
|
+
open: string[]
|
|
10
|
+
aborted: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function dispatchFailureBudgetReducer(infrastructureOwnsWorkspace: boolean) {
|
|
14
|
+
return defineReducer({
|
|
15
|
+
version: 1,
|
|
16
|
+
initial: (): DispatchBudgetAccumulator => ({ failures: [], open: [], aborted: false }),
|
|
17
|
+
fold(acc, events: readonly AbEvent[]) {
|
|
18
|
+
for (const event of events) {
|
|
19
|
+
if (event.type === 'runner.attached') {
|
|
20
|
+
acc.failures = []
|
|
21
|
+
} else if (
|
|
22
|
+
event.type === 'escalation.raised' &&
|
|
23
|
+
event.payload.source === 'policy' &&
|
|
24
|
+
event.payload.policyCause === 'dispatch-failure-limit'
|
|
25
|
+
) {
|
|
26
|
+
if (!acc.open.includes(event.payload.id)) acc.open.push(event.payload.id)
|
|
27
|
+
} else if (event.type === 'escalation.answered' && acc.open.includes(event.payload.id)) {
|
|
28
|
+
acc.open = acc.open.filter((id) => id !== event.payload.id)
|
|
29
|
+
if (event.payload.resolution !== 'abort') acc.failures = []
|
|
30
|
+
else acc.aborted = true
|
|
31
|
+
} else if (
|
|
32
|
+
event.type === 'dispatch.failed' &&
|
|
33
|
+
!(infrastructureOwnsWorkspace && event.payload.stage === 'workspace')
|
|
34
|
+
) {
|
|
35
|
+
acc.failures.push(event)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
finish: (acc) => ({
|
|
40
|
+
count: acc.failures.length,
|
|
41
|
+
latest: acc.failures.at(-1),
|
|
42
|
+
parked: acc.aborted || acc.open.length > 0,
|
|
43
|
+
}),
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Lifetime diagnostic attempt numbers are deliberately independent of this epoch. */
|
|
48
|
+
export function dispatchFailureBudget(
|
|
49
|
+
events: readonly AbEvent[],
|
|
50
|
+
infrastructureOwnsWorkspace: boolean,
|
|
51
|
+
) {
|
|
52
|
+
return dispatchFailureBudgetReducer(infrastructureOwnsWorkspace).reduce(events)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Repair the failure/raise crash gap before allowing another provider call. */
|
|
56
|
+
export async function ensureDispatchBudget(
|
|
57
|
+
store: Pick<BuildStore, 'getEvents' | 'appendIfCurrent'>,
|
|
58
|
+
ids: IdSource,
|
|
59
|
+
slug: string,
|
|
60
|
+
maxAttempts: number,
|
|
61
|
+
infrastructureOwnsWorkspace: boolean,
|
|
62
|
+
): Promise<boolean> {
|
|
63
|
+
for (;;) {
|
|
64
|
+
const events = await store.getEvents(slug)
|
|
65
|
+
const budget = dispatchFailureBudget(events, infrastructureOwnsWorkspace)
|
|
66
|
+
if (budget.parked) return false
|
|
67
|
+
if (budget.count < maxAttempts || budget.latest === undefined) return true
|
|
68
|
+
const failure = budget.latest.payload
|
|
69
|
+
const raised = await store.appendIfCurrent(slug, events.at(-1)?.seq ?? 0, {
|
|
70
|
+
actor: DISPATCHER,
|
|
71
|
+
type: 'escalation.raised',
|
|
72
|
+
payload: {
|
|
73
|
+
id: ids('esc'),
|
|
74
|
+
phase: 'setup',
|
|
75
|
+
source: 'policy',
|
|
76
|
+
policyCause: 'dispatch-failure-limit',
|
|
77
|
+
question: `maxDispatchAttempts (${maxAttempts}) exhausted during ${failure.stage}: ${failure.error}`,
|
|
78
|
+
refs: [],
|
|
79
|
+
},
|
|
80
|
+
})
|
|
81
|
+
if (raised !== null) return false
|
|
82
|
+
}
|
|
83
|
+
}
|