@defrex/autobuild 0.12.0 → 0.14.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 +1 -1
- package/SPEC.md +138 -16
- package/bin/ab-dispatch-kernel.ts +4 -6
- package/docs/architecture.md +155 -73
- package/docs/configuration.md +41 -9
- package/docs/hosted-service.md +31 -5
- package/docs/operator-api.md +8 -1
- package/docs/remote-store-protocol.md +94 -2
- package/docs/remote-ticket-protocol.md +151 -9
- package/docs/setup.md +33 -7
- package/docs/ticket-migration.md +64 -0
- package/package.json +4 -3
- package/packages/core/src/cli/attention.ts +18 -164
- package/packages/core/src/cli/build-control.ts +23 -16
- package/packages/core/src/cli/bulk-control.ts +3 -2
- package/packages/core/src/cli/connect.ts +141 -38
- package/packages/core/src/cli/context.ts +23 -79
- package/packages/core/src/cli/dashboard/model.ts +36 -22
- package/packages/core/src/cli/dashboard/poll.ts +1 -1
- package/packages/core/src/cli/dispatch-frontend.ts +24 -67
- package/packages/core/src/cli/dispatch-process.ts +14 -7
- package/packages/core/src/cli/dispatch-repository-feed.ts +196 -0
- package/packages/core/src/cli/dispatch.ts +304 -82
- package/packages/core/src/cli/dispatcher-liveness.ts +26 -19
- package/packages/core/src/cli/harvest.ts +84 -42
- package/packages/core/src/cli/help.ts +14 -8
- package/packages/core/src/cli/historical-build-state.ts +27 -0
- package/packages/core/src/cli/installation.ts +67 -13
- package/packages/core/src/cli/main.ts +23 -5
- package/packages/core/src/cli/mcp.ts +2 -1
- package/packages/core/src/cli/pr-attachments.ts +10 -13
- package/packages/core/src/cli/pr-summary-publication.ts +27 -60
- package/packages/core/src/cli/pr-summary.ts +43 -13
- package/packages/core/src/cli/publication-settlement.ts +10 -20
- package/packages/core/src/cli/repository-status.ts +38 -2
- package/packages/core/src/cli/self-update.ts +5 -1
- package/packages/core/src/cli/status.ts +62 -32
- package/packages/core/src/cli/terminals.ts +25 -142
- 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 +166 -36
- package/packages/core/src/cli/wait.ts +91 -84
- package/packages/core/src/cli/watch.ts +60 -97
- package/packages/core/src/config/pipeline-source.ts +4 -3
- package/packages/core/src/config/schema.ts +35 -3
- package/packages/core/src/connection.ts +3 -3
- package/packages/core/src/events/catalog.ts +3 -2
- package/packages/core/src/events/payloads.ts +6 -0
- package/packages/core/src/events/repository.ts +7 -3
- package/packages/core/src/integration/harness.ts +38 -4
- package/packages/core/src/kernel/__snapshots__/reducer-registry.test.ts.snap +2444 -9
- package/packages/core/src/kernel/auto-merge.ts +19 -3
- package/packages/core/src/kernel/dispatcher-liveness.ts +10 -4
- package/packages/core/src/kernel/generators/build-log.ts +1 -1
- package/packages/core/src/kernel/generators/repository-journals.ts +14 -3
- package/packages/core/src/kernel/harvest.ts +25 -23
- package/packages/core/src/kernel/incremental-fixtures.ts +13 -2
- package/packages/core/src/kernel/log-index-value.ts +38 -0
- package/packages/core/src/kernel/read-facts.ts +637 -0
- package/packages/core/src/kernel/reducer-registry.ts +139 -3
- package/packages/core/src/kernel/repository-attention.ts +179 -0
- package/packages/core/src/kernel/service-facts.ts +135 -0
- package/packages/core/src/kernel/terminal-guard.ts +133 -0
- package/packages/core/src/ontology.ts +1 -0
- package/packages/core/src/operator/annotations.ts +12 -0
- package/packages/core/src/operator/control.ts +5 -6
- package/packages/core/src/operator/index.ts +7 -0
- package/packages/core/src/operator/protocol.ts +19 -2
- package/packages/core/src/operator/query.ts +37 -20
- package/packages/core/src/operator/read-view.ts +4 -13
- package/packages/core/src/operator/registry.ts +61 -8
- package/packages/core/src/operator/requests.ts +2 -14
- package/packages/core/src/operator/sandbox.ts +10 -9
- package/packages/core/src/operator/ticket-source-backend.ts +17 -21
- package/packages/core/src/operator/tickets.ts +132 -22
- package/packages/core/src/plugin-sdk/index.ts +19 -0
- package/packages/core/src/plugins/runtimes.ts +5 -1
- package/packages/core/src/ports/runner/session-env.ts +2 -0
- package/packages/core/src/ports/tickets/contract.ts +66 -1
- package/packages/core/src/ports/tickets/create.ts +48 -12
- 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 -0
- 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 +95 -0
- package/packages/core/src/ports/tickets/remote.ts +60 -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 +1 -0
- package/packages/core/src/ports/workspace/create.ts +5 -1
- package/packages/core/src/ports/workspace/operator-sandbox.ts +2 -0
- package/packages/core/src/ports/workspace/vercel-sandbox.ts +8 -4
- package/packages/core/src/processes/build-child.ts +6 -6
- package/packages/core/src/processes/build-runner.ts +62 -70
- package/packages/core/src/processes/dispatch-failure-budget.ts +5 -4
- package/packages/core/src/processes/dispatcher.ts +223 -205
- package/packages/core/src/processes/execution-no-progress.ts +176 -0
- package/packages/core/src/processes/harvest-child.ts +30 -4
- package/packages/core/src/processes/harvest-execution-state.ts +15 -9
- package/packages/core/src/processes/harvest-runner.ts +58 -74
- package/packages/core/src/processes/harvest.ts +72 -29
- package/packages/core/src/processes/infrastructure-failure-budget.ts +13 -15
- package/packages/core/src/processes/orchestrator-tick.ts +2 -2
- package/packages/core/src/processes/phase-failure-budget.ts +1 -0
- package/packages/core/src/processes/publication-loss.ts +35 -21
- package/packages/core/src/processes/repo-view.ts +229 -1099
- package/packages/core/src/processes/tick-publication.ts +110 -0
- package/packages/core/src/store/adapter.ts +23 -0
- package/packages/core/src/store/build-scope.ts +17 -0
- package/packages/core/src/store/contract.ts +283 -1
- package/packages/core/src/store/local/schema.ts +1 -0
- package/packages/core/src/store/local/store.ts +180 -55
- package/packages/core/src/store/memory.ts +119 -29
- package/packages/core/src/store/phase-session.ts +17 -0
- package/packages/core/src/store/reduced-reads.ts +239 -0
- package/packages/core/src/store/reducer-value-shapes.json +14056 -0
- package/packages/core/src/store/reducer-value-validation.ts +96 -0
- package/packages/core/src/store/reducer-values.ts +145 -0
- package/packages/core/src/store/remote/client.ts +41 -0
- package/packages/core/src/store/remote/compatibility.ts +2 -0
- package/packages/core/src/store/remote/index.ts +19 -0
- package/packages/core/src/store/remote/protocol.ts +16 -0
- package/packages/core/src/store/remote/version.ts +3 -1
- package/packages/core/src/store/repo-state-events.ts +1 -1
- package/packages/core/src/store/repo-tick.ts +72 -0
- package/packages/core/src/store/session-handle.ts +19 -0
- package/packages/core/src/store/snapshots.ts +10 -0
- 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 +40 -3
- package/packages/core/src/testing/dispatch-read-budget.ts +215 -0
- package/packages/core/src/testing/index.ts +8 -0
- package/packages/core/src/testing/reduced-read-budget.ts +462 -0
- package/skills/guide/SKILL.md +97 -12
- package/skills/guide/references/operator-api.md +8 -1
- package/skills/guide/references/remote-store-protocol.md +94 -2
- package/skills/guide/references/remote-ticket-protocol.md +151 -9
- package/skills/guide/references/setup.md +33 -7
- package/skills/guide/references/ticket-migration.md +64 -0
- package/skills/tickets/SKILL.md +71 -0
- package/templates/hosted-service/package.json +2 -2
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import type { BuildState } from '../kernel/reducer'
|
|
2
|
+
import {
|
|
3
|
+
readBuildFacts,
|
|
4
|
+
readRepositoryFacts,
|
|
5
|
+
readBuildSelector,
|
|
6
|
+
readDispatcherRunFacts,
|
|
7
|
+
} from '../store/reduced-reads'
|
|
8
|
+
import { shouldPublishTickFromFacts } from '../processes/tick-publication'
|
|
9
|
+
import { issueTicketGrant, ticketGrantRequest } from '../ports/tickets/grants'
|
|
1
10
|
import { RemoteBuildStore } from '../store/remote/client'
|
|
2
11
|
import { delegateCredential } from '../store/remote/credentials'
|
|
3
12
|
import { isRemoteStoreRef } from './repo-state'
|
|
@@ -53,17 +62,14 @@ import { loadPlugins, pluginResolutionKind } from '../plugins/load'
|
|
|
53
62
|
import type { PluginRegistry } from '../plugins/registry'
|
|
54
63
|
import { materializePluginRuntimes } from '../plugins/runtimes'
|
|
55
64
|
import { DISPATCHER, KERNEL, humanActor } from '../events/envelope'
|
|
56
|
-
import type { AbEvent } from '../events/catalog'
|
|
57
65
|
import type { RepositoryEventWrite } from '../events/repository'
|
|
58
66
|
import { randomIds, randomUuids, type IdSource, type UuidSource } from '../ids'
|
|
59
|
-
import { reduceDispatchSettings } from '../kernel/dispatch-settings'
|
|
67
|
+
import type { reduceDispatchSettings } from '../kernel/dispatch-settings'
|
|
60
68
|
import {
|
|
61
69
|
DEFAULT_MAX_HARVEST_RECOVERY_ATTEMPTS,
|
|
62
70
|
actionableHarvestRun,
|
|
63
71
|
decideHarvestControl,
|
|
64
|
-
reduceHarvest,
|
|
65
72
|
} from '../kernel/harvest'
|
|
66
|
-
import { reduceBuild } from '../kernel/reducer'
|
|
67
73
|
import { dashboardBuildControl } from './dashboard/actions'
|
|
68
74
|
import {
|
|
69
75
|
buildDashboardFromProjected,
|
|
@@ -126,12 +132,10 @@ import {
|
|
|
126
132
|
parseBuildConfigMetadata,
|
|
127
133
|
parseDiagnostic,
|
|
128
134
|
parseEffectiveBuildConfig,
|
|
129
|
-
selectOpenWorkspace,
|
|
130
135
|
} from '../processes/build-execution-state'
|
|
131
136
|
import { HarvestRunner, type HarvestRunnerResult } from '../processes/harvest-runner'
|
|
132
137
|
import {
|
|
133
138
|
sampleUnclaimedObservationCount,
|
|
134
|
-
readRepoEventsIfRecorded,
|
|
135
139
|
evaluateHarvestPressureFromStore,
|
|
136
140
|
} from '../processes/harvest'
|
|
137
141
|
import { classifyHarvestOutcome } from '../processes/harvest-execution-state'
|
|
@@ -163,7 +167,13 @@ import {
|
|
|
163
167
|
} from './repo-state'
|
|
164
168
|
import { openStoreForRepoState } from './store-opening'
|
|
165
169
|
import { DispatchFrontend } from './dispatch-frontend'
|
|
166
|
-
import {
|
|
170
|
+
import {
|
|
171
|
+
systemClock,
|
|
172
|
+
type BuildStore,
|
|
173
|
+
type Clock,
|
|
174
|
+
type RepoTickCompletion,
|
|
175
|
+
type RepoTickFinish,
|
|
176
|
+
} from '../store/types'
|
|
167
177
|
import { availableRelease } from './self-update'
|
|
168
178
|
import {
|
|
169
179
|
startUpgradeNotice,
|
|
@@ -205,7 +215,8 @@ export function slugNamingPrompt(spec: string): string {
|
|
|
205
215
|
function definedEnv(env: Record<string, string | undefined>): Record<string, string> {
|
|
206
216
|
const defined: Record<string, string> = {}
|
|
207
217
|
for (const [key, value] of Object.entries(env)) {
|
|
208
|
-
if (value !== undefined &&
|
|
218
|
+
if (value !== undefined && !['AB_TOKEN', 'AB_TICKET_TOKEN', 'AB_TICKET_URL'].includes(key))
|
|
219
|
+
defined[key] = value
|
|
209
220
|
}
|
|
210
221
|
return defined
|
|
211
222
|
}
|
|
@@ -646,6 +657,7 @@ class DispatchLoop {
|
|
|
646
657
|
* manufacturing a zero queue measurement or losing diagnostics. */
|
|
647
658
|
private lastTickStatus = {
|
|
648
659
|
queued: 0,
|
|
660
|
+
blockedDiagnostics: [] as string[],
|
|
649
661
|
janitorDiagnostics: [] as string[],
|
|
650
662
|
ticketDiagnostics: [] as string[],
|
|
651
663
|
creationDiagnostics: [] as string[],
|
|
@@ -811,6 +823,7 @@ class DispatchLoop {
|
|
|
811
823
|
forge: wiring.forge,
|
|
812
824
|
config,
|
|
813
825
|
getConfig: () => this.liveConfig.current().config,
|
|
826
|
+
repairBuildConfig: (slug) => this.publishBuildConfig(slug),
|
|
814
827
|
repo: this.repoIdentity,
|
|
815
828
|
// Origin mode: the forge answers remote base-branch questions; no
|
|
816
829
|
// checkout git is probed for the served repository's origin.
|
|
@@ -847,7 +860,7 @@ class DispatchLoop {
|
|
|
847
860
|
private async resolveBuildPipeline(slug: string): Promise<{
|
|
848
861
|
source: Awaited<ReturnType<typeof resolvePipelineSource>>
|
|
849
862
|
failures: PipelineSourceFailure[]
|
|
850
|
-
|
|
863
|
+
observations: BuildState['observations']
|
|
851
864
|
}> {
|
|
852
865
|
const failures: PipelineSourceFailure[] = []
|
|
853
866
|
try {
|
|
@@ -855,12 +868,12 @@ class DispatchLoop {
|
|
|
855
868
|
// the record's `branch` (when present) is the same fact. Using the event
|
|
856
869
|
// keeps this read to the events the caller already needs and avoids an
|
|
857
870
|
// extra build-record lookup on the hot launch path.
|
|
858
|
-
const
|
|
859
|
-
const workspace =
|
|
871
|
+
const facts = (await readBuildFacts(this.repoView, slug)).value
|
|
872
|
+
const workspace = facts.workspace
|
|
860
873
|
const source = await resolvePipelineSource({
|
|
861
874
|
slug,
|
|
862
875
|
record: { ...(workspace !== null ? { branch: workspace.branch } : {}) },
|
|
863
|
-
|
|
876
|
+
recordedBase: await readBuildSelector(this.repoView, slug, 'recordedBaseSha'),
|
|
864
877
|
mode: this.opts.repository !== undefined ? 'origin' : 'checkout',
|
|
865
878
|
forge: this.wiring.forge,
|
|
866
879
|
checkout: this.opts.targetRepo,
|
|
@@ -868,13 +881,13 @@ class DispatchLoop {
|
|
|
868
881
|
...(workspace !== null ? { workspacePath: workspace.path } : {}),
|
|
869
882
|
onFailure: (failure) => failures.push(failure),
|
|
870
883
|
})
|
|
871
|
-
return { source, failures,
|
|
884
|
+
return { source, failures, observations: facts.observations }
|
|
872
885
|
} catch (error) {
|
|
873
886
|
failures.push({
|
|
874
887
|
kind: 'store',
|
|
875
888
|
detail: `build-event read failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
876
889
|
})
|
|
877
|
-
return { source: undefined, failures,
|
|
890
|
+
return { source: undefined, failures, observations: [] }
|
|
878
891
|
}
|
|
879
892
|
}
|
|
880
893
|
|
|
@@ -898,7 +911,7 @@ class DispatchLoop {
|
|
|
898
911
|
* naming the failed attempts; the documented fallbacks (an unpublished
|
|
899
912
|
* branch, an unreadable legacy base) stay quiet. */
|
|
900
913
|
private async publishBuildConfig(slug: string, snapshot = this.currentConfig()): Promise<void> {
|
|
901
|
-
const { source, failures,
|
|
914
|
+
const { source, failures, observations } = await this.resolveBuildPipeline(slug)
|
|
902
915
|
if (source !== undefined) {
|
|
903
916
|
await this.depositBuildConfig(
|
|
904
917
|
slug,
|
|
@@ -928,7 +941,7 @@ class DispatchLoop {
|
|
|
928
941
|
// option left that keeps the launch alive.
|
|
929
942
|
await this.depositBuildConfig(slug, snapshot, snapshot.config)
|
|
930
943
|
}
|
|
931
|
-
await this.filePipelineFallbackObservation(slug, reused, failures,
|
|
944
|
+
await this.filePipelineFallbackObservation(slug, reused, failures, observations)
|
|
932
945
|
}
|
|
933
946
|
|
|
934
947
|
/** File a build-scoped observation when a deposit hit an actionable
|
|
@@ -941,7 +954,7 @@ class DispatchLoop {
|
|
|
941
954
|
slug: string,
|
|
942
955
|
reused: PipelineSourceMeta | undefined,
|
|
943
956
|
failures: PipelineSourceFailure[],
|
|
944
|
-
|
|
957
|
+
observations: BuildState['observations'],
|
|
945
958
|
): Promise<void> {
|
|
946
959
|
const actionable = failures.filter(isActionablePipelineFailure)
|
|
947
960
|
if (actionable.length === 0) return
|
|
@@ -958,11 +971,7 @@ class DispatchLoop {
|
|
|
958
971
|
}`
|
|
959
972
|
// Deduplicated against the build's log: a parked build re-attaches every
|
|
960
973
|
// sweep, and an outage must not harvest one identical diagnostic per tick.
|
|
961
|
-
if (
|
|
962
|
-
events.some(
|
|
963
|
-
(event) => event.type === 'observation.recorded' && event.payload.summary === summary,
|
|
964
|
-
)
|
|
965
|
-
) {
|
|
974
|
+
if (observations.some((observation) => observation.summary === summary)) {
|
|
966
975
|
return
|
|
967
976
|
}
|
|
968
977
|
try {
|
|
@@ -1103,9 +1112,11 @@ class DispatchLoop {
|
|
|
1103
1112
|
// The view's bounded subset (AUT-489): the settings reducer consumes
|
|
1104
1113
|
// durable types only. `refresh()` ran first in the tick, so this is the
|
|
1105
1114
|
// delta-current journal, never a store read.
|
|
1106
|
-
return
|
|
1115
|
+
return this.repoView.repositoryFacts().settings
|
|
1107
1116
|
}
|
|
1108
1117
|
|
|
1118
|
+
private tickStartedAt: string | undefined
|
|
1119
|
+
|
|
1109
1120
|
private dispatcherTick(resumeCurrent: boolean): Promise<Awaited<ReturnType<Dispatcher['tick']>>> {
|
|
1110
1121
|
return this.serialize(async () => {
|
|
1111
1122
|
// Refresh before every watch decision. The owner publishes atomically;
|
|
@@ -1118,11 +1129,12 @@ class DispatchLoop {
|
|
|
1118
1129
|
await this.recordTickYielded()
|
|
1119
1130
|
return emptyTickReport()
|
|
1120
1131
|
}
|
|
1121
|
-
await this.
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
}
|
|
1132
|
+
const startedAt = await this.repoView.beginRepoTick(this.repoIdentity, this.runId)
|
|
1133
|
+
if (startedAt === null) {
|
|
1134
|
+
this.superseded = true
|
|
1135
|
+
return emptyTickReport()
|
|
1136
|
+
}
|
|
1137
|
+
this.tickStartedAt = startedAt
|
|
1126
1138
|
|
|
1127
1139
|
// Unclaimed observations are display-only and sampled once per interactive
|
|
1128
1140
|
// dispatcher tick from the journal and one repo-scoped digest read
|
|
@@ -1171,6 +1183,7 @@ class DispatchLoop {
|
|
|
1171
1183
|
this.queuedCount = publishedReport.queued
|
|
1172
1184
|
this.lastTickStatus = {
|
|
1173
1185
|
queued: publishedReport.queued,
|
|
1186
|
+
blockedDiagnostics: [...publishedReport.blockedDiagnostics],
|
|
1174
1187
|
janitorDiagnostics: [...publishedReport.janitorDiagnostics],
|
|
1175
1188
|
ticketDiagnostics: [...publishedReport.ticketDiagnostics],
|
|
1176
1189
|
creationDiagnostics: [...publishedReport.creationDiagnostics],
|
|
@@ -1607,8 +1620,8 @@ class DispatchLoop {
|
|
|
1607
1620
|
const { store } = this.wiring
|
|
1608
1621
|
const repo = this.repoIdentity
|
|
1609
1622
|
await store.ensureRepo(repo)
|
|
1610
|
-
const events = await store
|
|
1611
|
-
const state =
|
|
1623
|
+
const events = (await readRepositoryFacts(store, repo)).value
|
|
1624
|
+
const state = events.harvest
|
|
1612
1625
|
const projected = projectHarvest(events)
|
|
1613
1626
|
if (expectedRun === undefined || projected === undefined || projected.run !== expectedRun) {
|
|
1614
1627
|
this.say('harvest run action ignored: selected run is no longer active')
|
|
@@ -2248,6 +2261,110 @@ class DispatchLoop {
|
|
|
2248
2261
|
|
|
2249
2262
|
let tracked: Promise<void>
|
|
2250
2263
|
tracked = (async () => {
|
|
2264
|
+
const ticketUrl =
|
|
2265
|
+
harvestDeps.config.tickets.source === 'hosted'
|
|
2266
|
+
? (harvestDeps.config.tickets.url ?? (/^https?:/i.test(storeRef) ? storeRef : undefined))
|
|
2267
|
+
: undefined
|
|
2268
|
+
if (ticketUrl && ticketUrl.replace(/\/+$/, '') !== storeRef.replace(/\/+$/, '')) {
|
|
2269
|
+
const ticketToken = projectToken(ticketUrl, this.repoIdentity, {
|
|
2270
|
+
...this.opts.env,
|
|
2271
|
+
AB_TOKEN: undefined,
|
|
2272
|
+
})
|
|
2273
|
+
if (!ticketToken)
|
|
2274
|
+
throw new Error('Independent hosted harvest requires a ticket-service project credential')
|
|
2275
|
+
const issued = await issueTicketGrant(
|
|
2276
|
+
ticketUrl,
|
|
2277
|
+
ticketToken,
|
|
2278
|
+
this.repoIdentity,
|
|
2279
|
+
harvestDeps.instance,
|
|
2280
|
+
'runner',
|
|
2281
|
+
)
|
|
2282
|
+
let remoteRunnerStore: RemoteBuildStore | undefined
|
|
2283
|
+
let preparedRun: { run: string } | undefined
|
|
2284
|
+
let buildScoped: string | undefined
|
|
2285
|
+
let renew: ReturnType<typeof setInterval> | undefined
|
|
2286
|
+
try {
|
|
2287
|
+
if (/^https?:/i.test(storeRef)) {
|
|
2288
|
+
const prepared = await new HarvestRunner(harvestDeps).run(true)
|
|
2289
|
+
if (prepared.outcome !== 'prepared') {
|
|
2290
|
+
return prepared
|
|
2291
|
+
}
|
|
2292
|
+
preparedRun = { run: prepared.run }
|
|
2293
|
+
await this.repoView.appendRepo(this.repoIdentity, {
|
|
2294
|
+
actor: DISPATCHER,
|
|
2295
|
+
type: 'harvest.execution.authorized',
|
|
2296
|
+
payload: {
|
|
2297
|
+
execution: harvestDeps.instance,
|
|
2298
|
+
run: prepared.run,
|
|
2299
|
+
launch: prepared.launch,
|
|
2300
|
+
leaseHolder: this.repoLeaseHolder ?? harvestDeps.instance,
|
|
2301
|
+
},
|
|
2302
|
+
})
|
|
2303
|
+
buildScoped = await (this.wiring.credentialIssuer ?? delegateCredential)(
|
|
2304
|
+
storeRef,
|
|
2305
|
+
token,
|
|
2306
|
+
{
|
|
2307
|
+
kind: 'harvest-runner',
|
|
2308
|
+
execution: harvestDeps.instance,
|
|
2309
|
+
repo: this.repoIdentity,
|
|
2310
|
+
id: prepared.run,
|
|
2311
|
+
leaseHolder: this.repoLeaseHolder ?? harvestDeps.instance,
|
|
2312
|
+
},
|
|
2313
|
+
)
|
|
2314
|
+
remoteRunnerStore = new RemoteBuildStore({ url: storeRef, token: buildScoped })
|
|
2315
|
+
}
|
|
2316
|
+
const sessionEnv = {
|
|
2317
|
+
...harvestDeps.sessionEnv,
|
|
2318
|
+
...(buildScoped ? { AB_TOKEN: buildScoped } : {}),
|
|
2319
|
+
AB_TICKET_URL: ticketUrl,
|
|
2320
|
+
AB_TICKET_TOKEN: issued.token,
|
|
2321
|
+
}
|
|
2322
|
+
let renewalFailure: unknown
|
|
2323
|
+
renew = setInterval(() => {
|
|
2324
|
+
void ticketGrantRequest(ticketUrl, issued.token, {
|
|
2325
|
+
action: 'renew',
|
|
2326
|
+
repo: this.repoIdentity,
|
|
2327
|
+
id: issued.grant.id,
|
|
2328
|
+
}).catch((error) => {
|
|
2329
|
+
renewalFailure = error
|
|
2330
|
+
})
|
|
2331
|
+
}, 60_000)
|
|
2332
|
+
const runnerTickets = await createTicketSource(
|
|
2333
|
+
harvestDeps.config.tickets,
|
|
2334
|
+
{ ...this.opts.env, ...sessionEnv, AB_TOKEN: undefined },
|
|
2335
|
+
this.opts.targetRepo,
|
|
2336
|
+
undefined,
|
|
2337
|
+
this.wiring.plugins,
|
|
2338
|
+
)
|
|
2339
|
+
const result = await new HarvestRunner({
|
|
2340
|
+
...harvestDeps,
|
|
2341
|
+
...(remoteRunnerStore ? { store: remoteRunnerStore } : {}),
|
|
2342
|
+
...(preparedRun ? { preparedRun } : {}),
|
|
2343
|
+
tickets: runnerTickets,
|
|
2344
|
+
sessionEnv,
|
|
2345
|
+
}).run()
|
|
2346
|
+
if (renewalFailure) throw renewalFailure
|
|
2347
|
+
return result
|
|
2348
|
+
} finally {
|
|
2349
|
+
if (renew) clearInterval(renew)
|
|
2350
|
+
try {
|
|
2351
|
+
if (remoteRunnerStore && preparedRun) {
|
|
2352
|
+
await remoteRunnerStore.appendRepo(this.repoIdentity, {
|
|
2353
|
+
actor: DISPATCHER,
|
|
2354
|
+
type: 'harvest.execution.ended',
|
|
2355
|
+
payload: { execution: harvestDeps.instance, run: preparedRun.run },
|
|
2356
|
+
})
|
|
2357
|
+
await remoteRunnerStore.close()
|
|
2358
|
+
}
|
|
2359
|
+
} finally {
|
|
2360
|
+
await ticketGrantRequest(ticketUrl, ticketToken, {
|
|
2361
|
+
action: 'revoke',
|
|
2362
|
+
repo: this.repoIdentity,
|
|
2363
|
+
id: issued.grant.id,
|
|
2364
|
+
})
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2251
2368
|
if (!/^https?:/i.test(storeRef)) return new HarvestRunner(harvestDeps).run()
|
|
2252
2369
|
const prepared = await new HarvestRunner(harvestDeps).run(true)
|
|
2253
2370
|
if (prepared.outcome !== 'prepared') return prepared
|
|
@@ -2274,7 +2391,12 @@ class DispatchLoop {
|
|
|
2274
2391
|
harvestDeps.config.tickets.source === 'hosted'
|
|
2275
2392
|
? await createTicketSource(
|
|
2276
2393
|
harvestDeps.config.tickets,
|
|
2277
|
-
{
|
|
2394
|
+
{
|
|
2395
|
+
...this.opts.env,
|
|
2396
|
+
AB_STORE: storeRef,
|
|
2397
|
+
AB_TOKEN: scoped,
|
|
2398
|
+
AB_HARVEST: prepared.run,
|
|
2399
|
+
},
|
|
2278
2400
|
this.opts.targetRepo,
|
|
2279
2401
|
resolveRepoStatePaths({
|
|
2280
2402
|
repo: this.repoIdentity,
|
|
@@ -2369,6 +2491,8 @@ class DispatchLoop {
|
|
|
2369
2491
|
const repo = this.repoIdentity
|
|
2370
2492
|
let handle: BuildExecutionHandle | undefined
|
|
2371
2493
|
let startedSeq: number | undefined
|
|
2494
|
+
let ticketAuthority: { url: string; token: string; id: string } | undefined
|
|
2495
|
+
let renewTickets: ReturnType<typeof setInterval> | undefined
|
|
2372
2496
|
// Shared with `stopHostedHarvest` through the `hostedHarvest` entry: a
|
|
2373
2497
|
// detach must stay observable to the completion chain even after the
|
|
2374
2498
|
// entry is cleared, or the chain would classify and reap a live guest.
|
|
@@ -2388,8 +2512,8 @@ class DispatchLoop {
|
|
|
2388
2512
|
// reduces durable types only. Take the journal delta first — guests and
|
|
2389
2513
|
// other dispatchers write harvest facts between ticks.
|
|
2390
2514
|
await this.repoView.refreshJournal()
|
|
2391
|
-
const events = this.repoView.
|
|
2392
|
-
const state =
|
|
2515
|
+
const events = this.repoView.repositoryFacts().harvest
|
|
2516
|
+
const state = events
|
|
2393
2517
|
const control = decideHarvestControl(state, this.maxHarvestRecoveryAttempts)
|
|
2394
2518
|
const resumePending =
|
|
2395
2519
|
actionableHarvestRun(state) !== undefined ||
|
|
@@ -2438,6 +2562,36 @@ class DispatchLoop {
|
|
|
2438
2562
|
),
|
|
2439
2563
|
}
|
|
2440
2564
|
: undefined
|
|
2565
|
+
const ticketConfig = this.currentConfig().config.tickets
|
|
2566
|
+
if (
|
|
2567
|
+
ticketConfig.source === 'hosted' &&
|
|
2568
|
+
ticketConfig.url &&
|
|
2569
|
+
ticketConfig.url.replace(/\/+$/, '') !== storeRef.replace(/\/+$/, '')
|
|
2570
|
+
) {
|
|
2571
|
+
const ticketToken = projectToken(ticketConfig.url, repo, {
|
|
2572
|
+
...this.opts.env,
|
|
2573
|
+
AB_TOKEN: undefined,
|
|
2574
|
+
})
|
|
2575
|
+
if (!ticketToken || !launchAuth)
|
|
2576
|
+
throw new Error('Independent harvest requires scoped build and ticket credentials')
|
|
2577
|
+
const issued = await issueTicketGrant(
|
|
2578
|
+
ticketConfig.url,
|
|
2579
|
+
ticketToken,
|
|
2580
|
+
repo,
|
|
2581
|
+
execution,
|
|
2582
|
+
'runner',
|
|
2583
|
+
)
|
|
2584
|
+
ticketAuthority = { url: ticketConfig.url, token: issued.token, id: issued.grant.id }
|
|
2585
|
+
renewTickets = setInterval(() => {
|
|
2586
|
+
void ticketGrantRequest(ticketConfig.url!, issued.token, {
|
|
2587
|
+
action: 'renew',
|
|
2588
|
+
repo,
|
|
2589
|
+
id: issued.grant.id,
|
|
2590
|
+
}).catch(() => {
|
|
2591
|
+
// Expiry fails subsequent reads closed; the guest cannot regain project authority.
|
|
2592
|
+
})
|
|
2593
|
+
}, 60_000)
|
|
2594
|
+
}
|
|
2441
2595
|
handle = await harvestExecution.start(
|
|
2442
2596
|
{
|
|
2443
2597
|
preparedRun: { run: prepared.run },
|
|
@@ -2447,7 +2601,9 @@ class DispatchLoop {
|
|
|
2447
2601
|
baseBranch: this.currentConfig().config.baseBranch,
|
|
2448
2602
|
...(this.repoLeaseHolder !== undefined ? { leaseHolder: this.repoLeaseHolder } : {}),
|
|
2449
2603
|
},
|
|
2450
|
-
launchAuth
|
|
2604
|
+
launchAuth && ticketAuthority
|
|
2605
|
+
? { ...launchAuth, tickets: { url: ticketAuthority.url, token: ticketAuthority.token } }
|
|
2606
|
+
: launchAuth,
|
|
2451
2607
|
)
|
|
2452
2608
|
const identity = handle.identity
|
|
2453
2609
|
if (
|
|
@@ -2525,6 +2681,13 @@ class DispatchLoop {
|
|
|
2525
2681
|
)
|
|
2526
2682
|
}
|
|
2527
2683
|
} finally {
|
|
2684
|
+
if (renewTickets) clearInterval(renewTickets)
|
|
2685
|
+
if (ticketAuthority && !detached.value)
|
|
2686
|
+
await ticketGrantRequest(ticketAuthority.url, ticketAuthority.token, {
|
|
2687
|
+
action: 'revoke',
|
|
2688
|
+
repo,
|
|
2689
|
+
id: ticketAuthority.id,
|
|
2690
|
+
})
|
|
2528
2691
|
this.hostedHarvest = undefined
|
|
2529
2692
|
}
|
|
2530
2693
|
}
|
|
@@ -2539,7 +2702,7 @@ class DispatchLoop {
|
|
|
2539
2702
|
// Bounded subset (AUT-489): classifyHarvestOutcome reduces harvest facts
|
|
2540
2703
|
// only. A guest wrote the facts through its own handle, so read the delta.
|
|
2541
2704
|
await this.repoView.refreshJournal()
|
|
2542
|
-
const events = this.repoView.
|
|
2705
|
+
const events = this.repoView.repositoryFacts()
|
|
2543
2706
|
const record = await store.getRepo(repo)
|
|
2544
2707
|
return classifyHarvestOutcome(events, {
|
|
2545
2708
|
executionStartedSeq: startedSeq,
|
|
@@ -2678,15 +2841,38 @@ class DispatchLoop {
|
|
|
2678
2841
|
dependencyDiagnostics: _dependencyDiagnostics,
|
|
2679
2842
|
...counters
|
|
2680
2843
|
} = merged
|
|
2681
|
-
|
|
2844
|
+
const completed = {
|
|
2682
2845
|
actor: DISPATCHER,
|
|
2683
|
-
type: 'dispatcher.tick-completed',
|
|
2684
|
-
payload: {
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2846
|
+
type: 'dispatcher.tick-completed' as const,
|
|
2847
|
+
payload: { run: this.runId, ...this.lastTickStatus, counters },
|
|
2848
|
+
}
|
|
2849
|
+
if (settlementOnly) {
|
|
2850
|
+
await this.appendStatus(completed)
|
|
2851
|
+
} else {
|
|
2852
|
+
const startedAt = this.tickStartedAt!
|
|
2853
|
+
const facts = this.repoView.repositoryFacts()
|
|
2854
|
+
const recent = facts.recentRuns.find(([run]) => run === this.runId)?.[1]
|
|
2855
|
+
const runFacts = recent?.start
|
|
2856
|
+
? recent
|
|
2857
|
+
: (await readDispatcherRunFacts(this.repoView, this.repoIdentity, this.runId)).value
|
|
2858
|
+
const publish = shouldPublishTickFromFacts(merged, facts, runFacts, startedAt)
|
|
2859
|
+
const events: RepositoryEventWrite[] = publish
|
|
2860
|
+
? [
|
|
2861
|
+
{
|
|
2862
|
+
actor: DISPATCHER,
|
|
2863
|
+
type: 'dispatcher.tick-started',
|
|
2864
|
+
payload: { run: this.runId, startedAt },
|
|
2865
|
+
},
|
|
2866
|
+
completed,
|
|
2867
|
+
]
|
|
2868
|
+
: []
|
|
2869
|
+
const result = await this.finishTick({ outcome: 'completed', counters }, events)
|
|
2870
|
+
if (result === null) {
|
|
2871
|
+
this.superseded = true
|
|
2872
|
+
throw new Error('repository tick lease lost')
|
|
2873
|
+
}
|
|
2874
|
+
this.tickStartedAt = undefined
|
|
2875
|
+
}
|
|
2690
2876
|
|
|
2691
2877
|
this.pendingHarvest.harvestStarted -= snapshot.harvestStarted
|
|
2692
2878
|
this.pendingHarvest.harvestResumed -= snapshot.harvestResumed
|
|
@@ -2696,10 +2882,72 @@ class DispatchLoop {
|
|
|
2696
2882
|
return merged
|
|
2697
2883
|
}
|
|
2698
2884
|
|
|
2885
|
+
/** A remote finish can commit before its response is lost. Confirm that
|
|
2886
|
+
* exact tick through durable telemetry before acknowledging its counters;
|
|
2887
|
+
* never try to close an already committed boundary as a second failure. */
|
|
2888
|
+
private async finishTick(
|
|
2889
|
+
completion: RepoTickCompletion,
|
|
2890
|
+
events: RepositoryEventWrite[],
|
|
2891
|
+
): Promise<RepoTickFinish | null> {
|
|
2892
|
+
try {
|
|
2893
|
+
return await this.repoView.finishRepoTick(this.repoIdentity, this.runId, completion, events)
|
|
2894
|
+
} catch (error) {
|
|
2895
|
+
const observation = (await this.wiring.store.getRepo(this.repoIdentity).catch(() => null))
|
|
2896
|
+
?.tick
|
|
2897
|
+
const finished = observation?.finished
|
|
2898
|
+
if (
|
|
2899
|
+
observation?.run !== this.runId ||
|
|
2900
|
+
observation.open ||
|
|
2901
|
+
finished?.run !== this.runId ||
|
|
2902
|
+
finished.startedAt !== this.tickStartedAt ||
|
|
2903
|
+
finished.outcome !== completion.outcome ||
|
|
2904
|
+
(finished.outcome === 'failed' &&
|
|
2905
|
+
completion.outcome === 'failed' &&
|
|
2906
|
+
finished.error !== completion.error) ||
|
|
2907
|
+
(finished.outcome === 'completed' &&
|
|
2908
|
+
completion.outcome === 'completed' &&
|
|
2909
|
+
Object.entries(completion.counters).some(
|
|
2910
|
+
([key, value]) => finished.counters[key as keyof typeof finished.counters] !== value,
|
|
2911
|
+
))
|
|
2912
|
+
)
|
|
2913
|
+
throw error
|
|
2914
|
+
// The failed response bypassed the view's own-write fold. Refresh its
|
|
2915
|
+
// cursor from the journal, including any committed boundary pair.
|
|
2916
|
+
await this.repoView.refresh()
|
|
2917
|
+
return { observation, events: [] }
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
|
|
2921
|
+
private async publishTickFailure(error: unknown): Promise<void> {
|
|
2922
|
+
const failed = {
|
|
2923
|
+
actor: DISPATCHER,
|
|
2924
|
+
type: 'dispatcher.tick-failed' as const,
|
|
2925
|
+
payload: { run: this.runId, error: error instanceof Error ? error.message : String(error) },
|
|
2926
|
+
}
|
|
2927
|
+
if (this.tickStartedAt === undefined) {
|
|
2928
|
+
await this.appendStatus(failed)
|
|
2929
|
+
return
|
|
2930
|
+
}
|
|
2931
|
+
const result = await this.finishTick({ outcome: 'failed', error: failed.payload.error }, [
|
|
2932
|
+
{
|
|
2933
|
+
actor: DISPATCHER,
|
|
2934
|
+
type: 'dispatcher.tick-started',
|
|
2935
|
+
payload: { run: this.runId, startedAt: this.tickStartedAt },
|
|
2936
|
+
},
|
|
2937
|
+
failed,
|
|
2938
|
+
])
|
|
2939
|
+
if (result === null) {
|
|
2940
|
+
this.superseded = true
|
|
2941
|
+
return
|
|
2942
|
+
}
|
|
2943
|
+
this.tickStartedAt = undefined
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2699
2946
|
/** Settlement-only completions are serialized after operational ticks. Loop
|
|
2700
2947
|
* so a result arriving during an awaited status append gets its own report. */
|
|
2701
2948
|
private publishSettlementReports(): Promise<TickReport[]> {
|
|
2702
2949
|
return this.serialize(async () => {
|
|
2950
|
+
if (this.superseded) return []
|
|
2703
2951
|
const reports: TickReport[] = []
|
|
2704
2952
|
while (this.hasPendingHarvestResults()) {
|
|
2705
2953
|
const report = await this.publishTickReport(emptyTickReport(), true)
|
|
@@ -2765,7 +3013,7 @@ class DispatchLoop {
|
|
|
2765
3013
|
{ store: this.repoView, ids: this.wiring.ids },
|
|
2766
3014
|
{
|
|
2767
3015
|
slug: input.slug,
|
|
2768
|
-
events: await this.repoView
|
|
3016
|
+
events: (await readBuildFacts(this.repoView, input.slug)).value,
|
|
2769
3017
|
maxAttempts: this.currentConfig().config.policy.maxInfrastructureAttempts,
|
|
2770
3018
|
provider: input.identity?.provider ?? this.wiring.workspaces.name,
|
|
2771
3019
|
workspaceRef: input.workspaceRef,
|
|
@@ -2807,18 +3055,9 @@ class DispatchLoop {
|
|
|
2807
3055
|
this.failureNotice(`build ${slug} already held by another runner — skipped`)
|
|
2808
3056
|
return 'already-active'
|
|
2809
3057
|
}
|
|
2810
|
-
const
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
for (const event of launchEvents) {
|
|
2814
|
-
if (event.type === 'workspace.provisioned') {
|
|
2815
|
-
workspaceRef = event.payload.ref
|
|
2816
|
-
workspaceProvider = event.payload.provider
|
|
2817
|
-
} else if (event.type === 'workspace.released') {
|
|
2818
|
-
workspaceRef = undefined
|
|
2819
|
-
workspaceProvider = undefined
|
|
2820
|
-
}
|
|
2821
|
-
}
|
|
3058
|
+
const workspace = (await readBuildFacts(this.repoView, slug)).value.workspace
|
|
3059
|
+
const workspaceRef = workspace?.ref
|
|
3060
|
+
const workspaceProvider = workspace?.provider
|
|
2822
3061
|
if (workspaceRef === undefined) throw new Error(`build ${slug} has no open workspace`)
|
|
2823
3062
|
// Execute through the runtime that owns the workspace: a build
|
|
2824
3063
|
// provisioned before a provider switch still runs where it lives.
|
|
@@ -2901,7 +3140,7 @@ class DispatchLoop {
|
|
|
2901
3140
|
}
|
|
2902
3141
|
|
|
2903
3142
|
await this.repoView.refreshBuild(slug)
|
|
2904
|
-
const state =
|
|
3143
|
+
const state = (await readBuildFacts(this.repoView, slug)).value.state
|
|
2905
3144
|
if (diagnostic === null && exit.exitCode === 0) {
|
|
2906
3145
|
await this.appendStatus({
|
|
2907
3146
|
actor: DISPATCHER,
|
|
@@ -2968,12 +3207,7 @@ class DispatchLoop {
|
|
|
2968
3207
|
if (leaseClaimed) {
|
|
2969
3208
|
// The runner appended through its own handle: take the delta.
|
|
2970
3209
|
await this.repoView.refreshBuild(slug)
|
|
2971
|
-
const
|
|
2972
|
-
let workspaceRef = ''
|
|
2973
|
-
for (const event of events) {
|
|
2974
|
-
if (event.type === 'workspace.provisioned') workspaceRef = event.payload.ref
|
|
2975
|
-
else if (event.type === 'workspace.released') workspaceRef = ''
|
|
2976
|
-
}
|
|
3210
|
+
const workspaceRef = (await readBuildFacts(this.repoView, slug)).value.workspace?.ref ?? ''
|
|
2977
3211
|
if (workspaceRef !== '') {
|
|
2978
3212
|
await this.recordInfrastructureFailure({
|
|
2979
3213
|
slug,
|
|
@@ -3249,7 +3483,7 @@ class DispatchLoop {
|
|
|
3249
3483
|
// no anchor dependence (anchor analysis recorded; the frontend seed needed one
|
|
3250
3484
|
// only because reduceDispatchStatus folds here too — it does not here).
|
|
3251
3485
|
// Missing journal record stays a read-only empty journal (AUT-524).
|
|
3252
|
-
const repositoryEvents = await
|
|
3486
|
+
const repositoryEvents = (await readRepositoryFacts(this.wiring.store, this.repoIdentity)).value
|
|
3253
3487
|
|
|
3254
3488
|
// Action-triggered and timer refreshes share the cache but may finish their
|
|
3255
3489
|
// repository reads out of order. Never let an older build snapshot replace
|
|
@@ -3649,14 +3883,7 @@ class DispatchLoop {
|
|
|
3649
3883
|
// invocation.
|
|
3650
3884
|
if (this.pastDeadline()) return
|
|
3651
3885
|
const initial = await this.dispatcherTick(true).catch(async (error: unknown) => {
|
|
3652
|
-
await this.
|
|
3653
|
-
actor: DISPATCHER,
|
|
3654
|
-
type: 'dispatcher.tick-failed',
|
|
3655
|
-
payload: {
|
|
3656
|
-
run: this.runId,
|
|
3657
|
-
error: error instanceof Error ? error.message : String(error),
|
|
3658
|
-
},
|
|
3659
|
-
})
|
|
3886
|
+
await this.publishTickFailure(error)
|
|
3660
3887
|
throw error
|
|
3661
3888
|
})
|
|
3662
3889
|
const initialPrinted = this.printReport(initial, false)
|
|
@@ -3703,11 +3930,7 @@ class DispatchLoop {
|
|
|
3703
3930
|
startup = false
|
|
3704
3931
|
} catch (error) {
|
|
3705
3932
|
const message = error instanceof Error ? error.message : String(error)
|
|
3706
|
-
await this.
|
|
3707
|
-
actor: DISPATCHER,
|
|
3708
|
-
type: 'dispatcher.tick-failed',
|
|
3709
|
-
payload: { run: this.runId, error: message },
|
|
3710
|
-
})
|
|
3933
|
+
await this.publishTickFailure(error)
|
|
3711
3934
|
this.warn(`tick failed: ${message}`)
|
|
3712
3935
|
}
|
|
3713
3936
|
if (this.stopped || this.superseded) break
|
|
@@ -4161,11 +4384,10 @@ export async function abDispatch(opts: DispatchOpts): Promise<void> {
|
|
|
4161
4384
|
const loop = new DispatchLoop(liveConfig, wiring, resolvedOpts, view)
|
|
4162
4385
|
try {
|
|
4163
4386
|
await loop.run()
|
|
4164
|
-
const stopRequest =
|
|
4387
|
+
const stopRequest =
|
|
4165
4388
|
resolvedOpts.kernelRunId && resolvedOpts.signal?.aborted
|
|
4166
|
-
? await wiring.store
|
|
4167
|
-
:
|
|
4168
|
-
).find((event) => event.type === 'dispatcher.stop-requested' && event.payload.run === runId)
|
|
4389
|
+
? (await readDispatcherRunFacts(wiring.store, state.repo, runId)).value.stopRequest
|
|
4390
|
+
: undefined
|
|
4169
4391
|
const stoppedSignal =
|
|
4170
4392
|
stopRequest?.type === 'dispatcher.stop-requested'
|
|
4171
4393
|
? stopRequest.payload.signal
|
|
@@ -4203,7 +4425,7 @@ export async function abDispatch(opts: DispatchOpts): Promise<void> {
|
|
|
4203
4425
|
// Every append this invocation made, `run-stopped` included, is in the
|
|
4204
4426
|
// view; leave the reduced state behind so the next cold process reads
|
|
4205
4427
|
// only what is newer than it.
|
|
4206
|
-
|
|
4428
|
+
|
|
4207
4429
|
if (resolvedOpts.wire === undefined) await wiring.store.close()
|
|
4208
4430
|
}
|
|
4209
4431
|
}
|