@defrex/autobuild 0.8.0 → 0.10.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 +323 -26
- package/bin/ab-build-runner.ts +8 -2
- package/docs/architecture.md +166 -14
- package/docs/assets/headline-wide.png +0 -0
- package/docs/configuration.md +175 -28
- package/docs/hosted-dispatcher.md +69 -13
- package/docs/operator-api.md +2 -2
- package/docs/remote-store-protocol.md +204 -16
- package/docs/remote-ticket-protocol.md +1 -1
- package/docs/setup.md +12 -9
- package/docs/website.md +14 -0
- package/package.json +45 -23
- package/packages/core/src/cli/artifact.ts +130 -6
- package/packages/core/src/cli/binary.ts +9 -1
- package/packages/core/src/cli/build-control.ts +110 -13
- package/packages/core/src/cli/context.ts +46 -0
- package/packages/core/src/cli/dashboard/facts.ts +215 -0
- package/packages/core/src/cli/dashboard/frame-parse.ts +223 -0
- package/packages/core/src/cli/dashboard/model.ts +141 -153
- package/packages/core/src/cli/dashboard/poll.ts +76 -12
- package/packages/core/src/cli/dashboard/render.ts +34 -5
- package/packages/core/src/cli/dispatch-frontend.ts +54 -5
- package/packages/core/src/cli/dispatch-process.ts +1 -13
- package/packages/core/src/cli/dispatch.ts +392 -75
- package/packages/core/src/cli/harvest.ts +122 -8
- package/packages/core/src/cli/help.ts +62 -9
- package/packages/core/src/cli/init-readiness-remote.ts +110 -0
- package/packages/core/src/cli/init-readiness-shared.ts +72 -0
- package/packages/core/src/cli/init-validation.ts +266 -222
- package/packages/core/src/cli/installation.ts +50 -8
- package/packages/core/src/cli/main.ts +70 -11
- package/packages/core/src/cli/mcp.ts +23 -1
- package/packages/core/src/cli/plugin.ts +7 -4
- package/packages/core/src/cli/remote-poll.ts +277 -0
- package/packages/core/src/cli/repo-state.ts +22 -0
- package/packages/core/src/cli/repository-status.ts +39 -1
- package/packages/core/src/cli/self-update.ts +77 -2
- package/packages/core/src/cli/status.ts +150 -28
- package/packages/core/src/cli/terminals.ts +2 -1
- package/packages/core/src/cli/ticket-assets.ts +247 -0
- package/packages/core/src/cli/ticket.ts +453 -8
- package/packages/core/src/cli/wait.ts +157 -41
- package/packages/core/src/cli/watch.ts +138 -168
- package/packages/core/src/config/live.ts +23 -0
- package/packages/core/src/config/pipeline-source.ts +388 -0
- package/packages/core/src/config/schema.ts +251 -19
- package/packages/core/src/events/catalog.ts +1 -0
- package/packages/core/src/events/globs.ts +47 -0
- package/packages/core/src/events/payloads.ts +52 -2
- package/packages/core/src/events/repository.ts +52 -1
- package/packages/core/src/events/sessions.ts +69 -10
- package/packages/core/src/events/workspace-remote.ts +12 -0
- package/packages/core/src/integration/harness.ts +115 -2
- package/packages/core/src/kernel/__snapshots__/reducer-registry.test.ts.snap +1369 -0
- package/packages/core/src/kernel/auto-merge-default.ts +157 -0
- package/packages/core/src/kernel/auto-merge.ts +99 -22
- package/packages/core/src/kernel/dispatch-settings.ts +42 -17
- package/packages/core/src/kernel/dispatch-status.ts +23 -0
- package/packages/core/src/kernel/engine.ts +9 -438
- package/packages/core/src/kernel/generators/build-log.ts +259 -0
- package/packages/core/src/kernel/generators/repository-journals.ts +206 -0
- package/packages/core/src/kernel/generators/session-log.ts +72 -0
- package/packages/core/src/kernel/harvest.ts +62 -18
- package/packages/core/src/kernel/incremental-contract.ts +75 -0
- package/packages/core/src/kernel/incremental-fixtures.ts +211 -0
- package/packages/core/src/kernel/incremental.ts +53 -0
- package/packages/core/src/kernel/log-index.ts +620 -0
- package/packages/core/src/kernel/phases.ts +16 -2
- package/packages/core/src/kernel/pr-attachments.ts +164 -46
- package/packages/core/src/kernel/reducer-registry.ts +164 -0
- package/packages/core/src/kernel/reducer.ts +254 -73
- package/packages/core/src/operator/annotations.ts +6 -0
- package/packages/core/src/operator/control.ts +19 -5
- package/packages/core/src/operator/index.ts +71 -6
- package/packages/core/src/operator/query.ts +253 -24
- package/packages/core/src/operator/read-view.ts +98 -0
- package/packages/core/src/operator/registry.ts +55 -10
- package/packages/core/src/operator/requests.ts +9 -6
- package/packages/core/src/operator/sandbox-host.ts +140 -0
- package/packages/core/src/operator/sandbox.ts +247 -14
- package/packages/core/src/operator/ticket-source-backend.ts +47 -0
- package/packages/core/src/orchestrator/approvals.ts +115 -0
- package/packages/core/src/orchestrator/conversation.ts +167 -0
- package/packages/core/src/orchestrator/model.ts +86 -0
- package/packages/core/src/orchestrator/prompt.ts +61 -0
- package/packages/core/src/orchestrator/tools.ts +117 -0
- package/packages/core/src/orchestrator/turn-runner.ts +457 -0
- package/packages/core/src/plugin-sdk/index.ts +51 -1
- package/packages/core/src/plugins/load.ts +135 -7
- package/packages/core/src/plugins/manifest.ts +183 -10
- package/packages/core/src/plugins/registry.ts +73 -4
- package/packages/core/src/ports/forge/create.ts +1 -1
- package/packages/core/src/ports/forge/fake.ts +31 -0
- package/packages/core/src/ports/forge/github-transport.ts +13 -2
- package/packages/core/src/ports/forge/github.ts +13 -2
- package/packages/core/src/ports/forge/local-git.ts +31 -1
- package/packages/core/src/ports/runner/claude.ts +2 -2
- package/packages/core/src/ports/runner/codex.ts +6 -2
- package/packages/core/src/ports/runner/fake.ts +7 -1
- package/packages/core/src/ports/runner/pi.ts +8 -6
- package/packages/core/src/ports/runner/provider-error.ts +13 -3
- package/packages/core/src/ports/tickets/fake.ts +5 -1
- package/packages/core/src/ports/tickets/file.ts +169 -48
- package/packages/core/src/ports/tickets/{remote-index.ts → hosted-index.ts} +3 -2
- package/packages/core/src/ports/tickets/linear.ts +33 -1
- package/packages/core/src/ports/tickets/operations.ts +1 -1
- package/packages/core/src/ports/tickets/remote.ts +8 -1
- package/packages/core/src/ports/types.ts +49 -3
- package/packages/core/src/ports/workspace/builtin-capabilities.ts +114 -0
- package/packages/core/src/ports/workspace/create.ts +74 -39
- package/packages/core/src/ports/workspace/distribution-archive.ts +171 -12
- package/packages/core/src/ports/workspace/fake.ts +169 -4
- package/packages/core/src/ports/workspace/git-worktree.ts +246 -26
- package/packages/core/src/ports/workspace/github-origin.ts +36 -0
- package/packages/core/src/ports/workspace/operator-sandbox.ts +7 -1
- package/packages/core/src/ports/workspace/provider-capabilities.ts +171 -0
- package/packages/core/src/ports/workspace/vercel-capabilities.ts +110 -0
- package/packages/core/src/ports/workspace/vercel-sandbox.ts +142 -114
- package/packages/core/src/processes/build-child.ts +17 -2
- package/packages/core/src/processes/build-execution-state.ts +68 -15
- package/packages/core/src/processes/build-runner.ts +238 -93
- package/packages/core/src/processes/dispatcher-selectors.ts +123 -0
- package/packages/core/src/processes/dispatcher.ts +501 -201
- package/packages/core/src/processes/execution-settlement.ts +72 -39
- package/packages/core/src/processes/harvest-child.ts +5 -0
- package/packages/core/src/processes/harvest-execution-state.ts +41 -29
- package/packages/core/src/processes/harvest-runner.ts +66 -19
- package/packages/core/src/processes/harvest.ts +196 -24
- package/packages/core/src/processes/infrastructure-failure-budget.ts +31 -18
- package/packages/core/src/processes/orchestrator-tick.ts +360 -0
- package/packages/core/src/processes/publication-state.ts +164 -52
- package/packages/core/src/processes/repo-view.ts +1436 -0
- package/packages/core/src/processes/sandbox-state.ts +36 -2
- package/packages/core/src/processes/tick-state.ts +33 -0
- package/packages/core/src/store/adapter.ts +41 -0
- package/packages/core/src/store/build-scope.ts +90 -0
- package/packages/core/src/store/contract.ts +1205 -0
- package/packages/core/src/store/digest.ts +102 -0
- package/packages/core/src/store/local/schema.ts +46 -2
- package/packages/core/src/store/local/store.ts +445 -4
- package/packages/core/src/store/memory.ts +246 -0
- package/packages/core/src/store/new-build.ts +17 -0
- package/packages/core/src/store/phase-session.ts +94 -0
- package/packages/core/src/store/remote/client.ts +310 -2
- package/packages/core/src/store/remote/index.ts +14 -2
- package/packages/core/src/store/remote/protocol.ts +115 -12
- package/packages/core/src/store/remote/version.ts +6 -2
- package/packages/core/src/store/repo-state-events.ts +205 -0
- package/packages/core/src/store/retention.ts +7 -0
- package/packages/core/src/store/session-handle.ts +94 -0
- package/packages/core/src/{sessions/reducer.ts → store/session-reducer.ts} +79 -19
- package/packages/core/src/store/snapshots.ts +88 -0
- package/packages/core/src/store/streams/session-writer.ts +42 -2
- package/packages/core/src/store/streams/wait.ts +4 -5
- package/packages/core/src/store/ticket-assets.ts +458 -0
- package/packages/core/src/store/types.ts +160 -1
- package/packages/core/src/testing/index.ts +59 -0
- package/skills/code-review/SKILL.md +5 -1
- package/skills/guide/SKILL.md +152 -40
- package/skills/guide/references/operator-api.md +2 -2
- package/skills/guide/references/plugin-authoring.md +17 -0
- package/skills/guide/references/remote-store-protocol.md +204 -16
- package/skills/guide/references/remote-ticket-protocol.md +1 -1
- package/skills/guide/references/setup.md +12 -9
- package/skills/implement/SKILL.md +3 -0
- package/skills/operate/SKILL.md +126 -0
- package/skills/plan/SKILL.md +4 -0
- package/skills/plan-review/SKILL.md +5 -1
- package/skills/spec/SKILL.md +46 -3
- package/skills/tickets/SKILL.md +57 -5
- package/packages/core/src/cli/dashboard/frame-image.ts +0 -373
- package/packages/core/src/cli/store-opening.contract.ts +0 -289
- package/packages/core/src/cli/testkit.ts +0 -194
- package/packages/core/src/markdown.ts +0 -67
- package/packages/core/src/operator/client.ts +0 -311
- package/packages/core/src/operator/server.ts +0 -616
- package/packages/core/src/operator/sessions.ts +0 -223
- package/packages/core/src/ports/runner/live-contract-fixture.ts +0 -176
- package/packages/core/src/ports/tickets/remote-server.ts +0 -185
- package/packages/core/src/store/remote/server.ts +0 -923
- package/packages/core/src/testing/store-failures.ts +0 -32
|
@@ -24,24 +24,62 @@ const positiveInt = z.number().int().positive()
|
|
|
24
24
|
|
|
25
25
|
const empty = z.strictObject({})
|
|
26
26
|
|
|
27
|
-
/** The trigger that started a turn: a human message or a wake event.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
/** The trigger that started a turn: a human message or a wake event. A wake
|
|
28
|
+
* trigger names exactly one source — a build (the build-log attention scan)
|
|
29
|
+
* or the repository journal — enforced by the schema's `.check()` below even
|
|
30
|
+
* though the static type (one object with optional fields) cannot express it:
|
|
31
|
+
* a two-member union or a nested discriminated union is not a valid
|
|
32
|
+
* discriminated-union option on zod 4 (both constructions throw at first
|
|
33
|
+
* parse), so the wake variant stays a single object and call sites branch on
|
|
34
|
+
* `journal === true`. */
|
|
35
|
+
const turnWakeTriggerSchema = z
|
|
36
|
+
.strictObject({
|
|
31
37
|
kind: z.literal('wake'),
|
|
32
|
-
build: z.string().min(1),
|
|
38
|
+
build: z.string().min(1).optional(),
|
|
39
|
+
journal: z.literal(true).optional(),
|
|
33
40
|
seq: positiveInt,
|
|
34
41
|
type: z.string().min(1),
|
|
35
|
-
})
|
|
42
|
+
})
|
|
43
|
+
.check((ctx) => {
|
|
44
|
+
const hasBuild = ctx.value.build !== undefined
|
|
45
|
+
const hasJournal = ctx.value.journal === true
|
|
46
|
+
if (hasBuild === hasJournal) {
|
|
47
|
+
ctx.issues.push({
|
|
48
|
+
code: 'custom',
|
|
49
|
+
input: ctx.value,
|
|
50
|
+
message: 'a wake trigger names exactly one of build or journal',
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const turnTriggerSchema = z.discriminatedUnion('kind', [
|
|
56
|
+
z.strictObject({ kind: z.literal('message'), messageSeq: positiveInt }),
|
|
57
|
+
turnWakeTriggerSchema,
|
|
36
58
|
])
|
|
37
59
|
|
|
38
|
-
/** Matches the transcript usage shape (harvest.session.ended, §15.3).
|
|
60
|
+
/** Matches the transcript usage shape (harvest.session.ended, §15.3). The
|
|
61
|
+
* turn runner records the AI SDK's totalUsage token counts plus the step
|
|
62
|
+
* count. Old `turn.completed` events without `steps` still reduce. */
|
|
39
63
|
const usageSchema = z.strictObject({
|
|
40
64
|
inputTokens: z.number().int().nonnegative(),
|
|
41
65
|
outputTokens: z.number().int().nonnegative(),
|
|
66
|
+
steps: z.number().int().nonnegative(),
|
|
42
67
|
turns: z.number().int().nonnegative().optional(),
|
|
43
68
|
})
|
|
44
69
|
|
|
70
|
+
/** The typed model-failure vocabulary (AUT-342): provider availability,
|
|
71
|
+
* exhaustion, credentials, and configuration are distinguished so a failed
|
|
72
|
+
* turn's class is visible in the session's reduced state and nothing is ever
|
|
73
|
+
* retried unboundedly. `internal` covers non-model failures — the dispatcher
|
|
74
|
+
* tick's crash reaper among them. */
|
|
75
|
+
const turnFailureKindSchema = z.enum([
|
|
76
|
+
'provider-unavailable',
|
|
77
|
+
'exhausted',
|
|
78
|
+
'credentials',
|
|
79
|
+
'configuration',
|
|
80
|
+
'internal',
|
|
81
|
+
])
|
|
82
|
+
|
|
45
83
|
export const sessionEventPayloadSchemas = {
|
|
46
84
|
/** The fact of creation; the session record carries the same title. */
|
|
47
85
|
'session.created': z.strictObject({ title: z.string().min(1).optional() }),
|
|
@@ -52,11 +90,27 @@ export const sessionEventPayloadSchemas = {
|
|
|
52
90
|
'session.wake-set': z.strictObject({
|
|
53
91
|
globs: z.array(z.string().min(1)).max(100),
|
|
54
92
|
}),
|
|
55
|
-
/** The (later
|
|
93
|
+
/** The (later turn runner's) start of one turn on its stream. A wake
|
|
94
|
+
* turn additionally carries the delivered input — the attention event
|
|
95
|
+
* record and, for a build wake, the build's reduced state, frozen at wake
|
|
96
|
+
* time — so a later invocation reconstructs the identical user message
|
|
97
|
+
* from durable state alone (live build state keeps moving; the frozen
|
|
98
|
+
* snapshot does not). A journal wake delivers the event record only. */
|
|
56
99
|
'turn.started': z.strictObject({
|
|
57
100
|
turn: z.string().min(1),
|
|
58
101
|
stream: z.string().min(1),
|
|
59
102
|
trigger: turnTriggerSchema,
|
|
103
|
+
wake: z
|
|
104
|
+
.strictObject({
|
|
105
|
+
event: z.strictObject({
|
|
106
|
+
seq: positiveInt,
|
|
107
|
+
ts: z.string().min(1),
|
|
108
|
+
type: z.string().min(1),
|
|
109
|
+
payload: z.record(z.string(), z.unknown()),
|
|
110
|
+
}),
|
|
111
|
+
buildState: z.record(z.string(), z.unknown()).optional(),
|
|
112
|
+
})
|
|
113
|
+
.optional(),
|
|
60
114
|
}),
|
|
61
115
|
/** A turn was suspended for budget or for an approval. */
|
|
62
116
|
'turn.suspended': z.strictObject({
|
|
@@ -80,8 +134,13 @@ export const sessionEventPayloadSchemas = {
|
|
|
80
134
|
}),
|
|
81
135
|
/** One turn finished. */
|
|
82
136
|
'turn.completed': z.strictObject({ turn: z.string().min(1), usage: usageSchema }),
|
|
83
|
-
/** One turn failed
|
|
84
|
-
|
|
137
|
+
/** One turn failed: `kind` is the typed failure class, `error` the
|
|
138
|
+
* human-readable message surfaced in the session's reduced state. */
|
|
139
|
+
'turn.failed': z.strictObject({
|
|
140
|
+
turn: z.string().min(1),
|
|
141
|
+
kind: turnFailureKindSchema,
|
|
142
|
+
error: z.string().min(1),
|
|
143
|
+
}),
|
|
85
144
|
/** Terminal. The session is read-only afterwards. */
|
|
86
145
|
'session.archived': empty,
|
|
87
146
|
} as const
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { EventPayload } from './payloads'
|
|
2
|
+
|
|
3
|
+
/** Whether the workspace this payload records publishes remotely, per the
|
|
4
|
+
* marker recorded at provision. Journals written before the marker existed
|
|
5
|
+
* fall back to the legacy provider-name reading — the only name comparison
|
|
6
|
+
* among the remote/local decisions (removed in AUT-505). */
|
|
7
|
+
export function isRemoteWorkspace(
|
|
8
|
+
payload: Pick<EventPayload<'workspace.provisioned'>, 'provider' | 'remote'>,
|
|
9
|
+
): boolean {
|
|
10
|
+
if (payload.remote !== undefined) return payload.remote
|
|
11
|
+
return payload.provider === 'vercel-sandbox'
|
|
12
|
+
}
|
|
@@ -59,6 +59,13 @@ import { InProcessBuildExecution } from '../ports/workspace/in-process-build-exe
|
|
|
59
59
|
import { BuildRunner, LeaseHeldError, SetupFailureError } from '../processes/build-runner'
|
|
60
60
|
import { diagnosticArtifact } from '../processes/build-execution-state'
|
|
61
61
|
import { Dispatcher, type LaunchRunnerResult } from '../processes/dispatcher'
|
|
62
|
+
import {
|
|
63
|
+
getHarvestStatus,
|
|
64
|
+
getOperatorDashboard,
|
|
65
|
+
getRepositoryStatus,
|
|
66
|
+
listOperatorBuilds,
|
|
67
|
+
} from '../operator/query'
|
|
68
|
+
import { RepoViewStore } from '../processes/repo-view'
|
|
62
69
|
import { MemoryBuildStore } from '../store/memory'
|
|
63
70
|
import type { BuildStore } from '../store/types'
|
|
64
71
|
import { steppingClock } from '../testing/fixed'
|
|
@@ -279,6 +286,106 @@ export interface E2eHarness {
|
|
|
279
286
|
cleanup(): Promise<void>
|
|
280
287
|
}
|
|
281
288
|
|
|
289
|
+
/** The store with every reducer snapshot deleted: reads miss, writes no-op. */
|
|
290
|
+
export function withoutSnapshots<T extends BuildStore>(store: T): T {
|
|
291
|
+
return new Proxy(store, {
|
|
292
|
+
get(target, key) {
|
|
293
|
+
if (key === 'getReducerSnapshot') return async () => null
|
|
294
|
+
if (key === 'putReducerSnapshot') return async () => false
|
|
295
|
+
const value = Reflect.get(target, key, target)
|
|
296
|
+
return typeof value === 'function' ? value.bind(target) : value
|
|
297
|
+
},
|
|
298
|
+
})
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** A build's facts with the publication view (closures) reduced to its answers. */
|
|
302
|
+
async function plainFacts(view: RepoViewStore, slug: string) {
|
|
303
|
+
const { publication, ...rest } = await view.buildFacts(slug)
|
|
304
|
+
return {
|
|
305
|
+
...rest,
|
|
306
|
+
publication: {
|
|
307
|
+
pending: publication.pending(),
|
|
308
|
+
abandoned: publication.abandonedPending(),
|
|
309
|
+
latest: publication.latestUncompletedRequest(),
|
|
310
|
+
},
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** JSON with sorted keys, so two structurally equal values compare equal. */
|
|
315
|
+
function canonical(value: unknown): string {
|
|
316
|
+
return JSON.stringify(value, (_key, item) =>
|
|
317
|
+
item !== null && typeof item === 'object' && !Array.isArray(item)
|
|
318
|
+
? Object.fromEntries(Object.entries(item).sort(([a], [b]) => a.localeCompare(b)))
|
|
319
|
+
: item,
|
|
320
|
+
)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async function repositoryReading(store: BuildStore, repo: string) {
|
|
324
|
+
const view = new RepoViewStore(store, { repo })
|
|
325
|
+
await view.refresh()
|
|
326
|
+
const slugs = (await view.listBuilds()).map((record) => record.slug).sort()
|
|
327
|
+
const builds: Record<string, unknown> = {}
|
|
328
|
+
for (const slug of slugs) {
|
|
329
|
+
builds[slug] = { facts: await plainFacts(view, slug), state: await view.buildState(slug) }
|
|
330
|
+
}
|
|
331
|
+
return {
|
|
332
|
+
journal: view.recordedJournal(),
|
|
333
|
+
digests: [...(await view.getRepoBuildDigests(repo)).entries()].sort(([a], [b]) =>
|
|
334
|
+
a.localeCompare(b),
|
|
335
|
+
),
|
|
336
|
+
builds,
|
|
337
|
+
view,
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** Reducer snapshots are a cache the log regenerates (SPEC §7): whatever a
|
|
342
|
+
* scenario's dispatcher left behind, a cold view restored from it must read
|
|
343
|
+
* exactly what a cold view over a store with every snapshot deleted reads — and
|
|
344
|
+
* so must one restored from snapshots written at the final cursor. Run for
|
|
345
|
+
* every scenario at cleanup. */
|
|
346
|
+
export async function assertSnapshotEquivalence(store: BuildStore, repo: string): Promise<void> {
|
|
347
|
+
const replayed = await repositoryReading(withoutSnapshots(store), repo)
|
|
348
|
+
for (const round of ['as left by the scenario', 'written at the final cursor']) {
|
|
349
|
+
const restored = await repositoryReading(store, repo)
|
|
350
|
+
const { view: restoredView, ...got } = restored
|
|
351
|
+
const { view: _replayedView, ...want } = replayed
|
|
352
|
+
if (canonical(got) !== canonical(want)) {
|
|
353
|
+
throw new Error(`snapshot-restored reading differs from full replay (${round})`)
|
|
354
|
+
}
|
|
355
|
+
await restoredView.persistSnapshots({ force: true })
|
|
356
|
+
}
|
|
357
|
+
// The operator routes derive from the same view: each response with
|
|
358
|
+
// snapshots present equals the response with every snapshot deleted.
|
|
359
|
+
const at = new Date('2026-01-01T00:00:00.000Z')
|
|
360
|
+
const routes = async (target: BuildStore) => {
|
|
361
|
+
const attempt = async (read: () => Promise<unknown>): Promise<unknown> => {
|
|
362
|
+
try {
|
|
363
|
+
return await read()
|
|
364
|
+
} catch (error) {
|
|
365
|
+
return { error: error instanceof Error ? error.message : String(error) }
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return {
|
|
369
|
+
dashboard: await attempt(() =>
|
|
370
|
+
getOperatorDashboard({ store: target, repo, clock: () => at }),
|
|
371
|
+
),
|
|
372
|
+
status: await attempt(() => getRepositoryStatus(target, repo)),
|
|
373
|
+
harvest: await attempt(() => getHarvestStatus(target, repo)),
|
|
374
|
+
lists: await Promise.all(
|
|
375
|
+
(['active', 'queued', 'all'] as const).map((scope) =>
|
|
376
|
+
attempt(() => listOperatorBuilds({ store: target, repo, scope, now: at })),
|
|
377
|
+
),
|
|
378
|
+
),
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
const replayedRoutes = canonical(await routes(withoutSnapshots(store)))
|
|
382
|
+
for (const round of ['as left', 'warmed by a first poll']) {
|
|
383
|
+
if (canonical(await routes(store)) !== replayedRoutes) {
|
|
384
|
+
throw new Error(`snapshot-backed operator routes differ from full replay (${round})`)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
282
389
|
export async function makeHarness(opts: {
|
|
283
390
|
handlers: SkillHandlers
|
|
284
391
|
tickets?: Ticket[]
|
|
@@ -336,7 +443,11 @@ export async function makeHarness(opts: {
|
|
|
336
443
|
const clock = steppingClock()
|
|
337
444
|
const ids = sequentialIds()
|
|
338
445
|
const uuids = sequentialUuids()
|
|
339
|
-
|
|
446
|
+
// `AB_E2E_SNAPSHOTS=off` runs every scenario with every snapshot deleted, the
|
|
447
|
+
// other arm of the "snapshots change no observable output" proof.
|
|
448
|
+
const snapshotsOff = process.env.AB_E2E_SNAPSHOTS === 'off'
|
|
449
|
+
const memory = new MemoryBuildStore({ clock })
|
|
450
|
+
const store = snapshotsOff ? withoutSnapshots(memory) : memory
|
|
340
451
|
const adapted = await opts.storeAdapter?.(store)
|
|
341
452
|
const activeStore = adapted?.store ?? store
|
|
342
453
|
const config = parseConfig(configToml, 'e2e autobuild.toml')
|
|
@@ -347,7 +458,8 @@ export async function makeHarness(opts: {
|
|
|
347
458
|
gatePresence: opts.gatePresence ?? 'present',
|
|
348
459
|
...(opts.pluginForge?.prAttachments === true ? { prAttachments: true } : {}),
|
|
349
460
|
})
|
|
350
|
-
let selectedForge: Forge =
|
|
461
|
+
let selectedForge: Forge =
|
|
462
|
+
opts.localGitForge === true ? new LocalGitForge({ repoRoot: origin }) : forge
|
|
351
463
|
let plugins: PluginRegistry | undefined
|
|
352
464
|
if (opts.pluginForge !== undefined) {
|
|
353
465
|
plugins = createPluginRegistry()
|
|
@@ -611,6 +723,7 @@ export async function makeHarness(opts: {
|
|
|
611
723
|
},
|
|
612
724
|
events: (slug) => store.getEvents(slug),
|
|
613
725
|
cleanup: async () => {
|
|
726
|
+
if (!snapshotsOff) await assertSnapshotEquivalence(store, origin)
|
|
614
727
|
await activeStore.close()
|
|
615
728
|
if (activeStore !== store) await store.close()
|
|
616
729
|
await adapted?.cleanup?.()
|