@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
|
@@ -18,58 +18,80 @@ import type {
|
|
|
18
18
|
BuildExecutionIdentity,
|
|
19
19
|
ExecutionObservation,
|
|
20
20
|
} from '../ports/workspace/build-execution'
|
|
21
|
+
import { defineReducer } from '../kernel/incremental'
|
|
21
22
|
import type { BuildStore } from '../store/types'
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
* `execution.ended`, or null when no execution is open. */
|
|
25
|
-
export function openExecution(events: AbEvent[]): {
|
|
24
|
+
export interface OpenExecution {
|
|
26
25
|
instance: string
|
|
27
26
|
workspaceRef: string
|
|
28
27
|
provider: string
|
|
29
28
|
environmentId?: string
|
|
30
29
|
sessionId?: string
|
|
31
30
|
commandId?: string
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const OPEN_EXECUTION_REDUCER_VERSION = 1
|
|
34
|
+
|
|
35
|
+
/** The latest `execution.started` not followed by an instance-matched
|
|
36
|
+
* `execution.ended`, or null when no execution is open. */
|
|
37
|
+
export const openExecutionReducer = defineReducer<
|
|
38
|
+
{ open: OpenExecution | null },
|
|
39
|
+
AbEvent,
|
|
40
|
+
OpenExecution | null
|
|
41
|
+
>({
|
|
42
|
+
version: OPEN_EXECUTION_REDUCER_VERSION,
|
|
43
|
+
initial: () => ({ open: null }),
|
|
44
|
+
fold(acc, events) {
|
|
45
|
+
for (const event of events) {
|
|
46
|
+
if (event.type === 'execution.started') {
|
|
47
|
+
const payload = event.payload
|
|
48
|
+
acc.open = {
|
|
49
|
+
instance: payload.instance,
|
|
50
|
+
workspaceRef: payload.workspaceRef,
|
|
51
|
+
provider: payload.provider,
|
|
52
|
+
...(payload.environmentId !== undefined ? { environmentId: payload.environmentId } : {}),
|
|
53
|
+
...(payload.sessionId !== undefined ? { sessionId: payload.sessionId } : {}),
|
|
54
|
+
...(payload.commandId !== undefined ? { commandId: payload.commandId } : {}),
|
|
55
|
+
}
|
|
56
|
+
} else if (event.type === 'execution.ended' && acc.open !== null) {
|
|
57
|
+
if (event.payload.instance === acc.open.instance) acc.open = null
|
|
51
58
|
}
|
|
52
|
-
} else if (event.type === 'execution.ended' && open !== null) {
|
|
53
|
-
if (event.payload.instance === open.instance) open = null
|
|
54
59
|
}
|
|
55
|
-
}
|
|
56
|
-
|
|
60
|
+
},
|
|
61
|
+
finish: (acc) => (acc.open === null ? null : { ...acc.open }),
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
export function openExecution(events: AbEvent[]): OpenExecution | null {
|
|
65
|
+
return openExecutionReducer.reduce(events)
|
|
57
66
|
}
|
|
58
67
|
|
|
68
|
+
export type ExecutionOutcome = 'open' | 'completed' | 'stopped' | 'lost' | 'none'
|
|
69
|
+
|
|
70
|
+
export const LAST_EXECUTION_OUTCOME_REDUCER_VERSION = 1
|
|
71
|
+
|
|
59
72
|
/** The lifecycle state of the latest recorded execution: `open` (no matching
|
|
60
73
|
* end yet), `completed`/`stopped`/`lost` (its recorded end), or `none`. */
|
|
61
|
-
export
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
export const lastExecutionOutcomeReducer = defineReducer<
|
|
75
|
+
{ state: ExecutionOutcome },
|
|
76
|
+
AbEvent,
|
|
77
|
+
ExecutionOutcome
|
|
78
|
+
>({
|
|
79
|
+
version: LAST_EXECUTION_OUTCOME_REDUCER_VERSION,
|
|
80
|
+
initial: () => ({ state: 'none' }),
|
|
81
|
+
fold(acc, events) {
|
|
82
|
+
for (const event of events) {
|
|
83
|
+
if (event.type === 'execution.started') {
|
|
84
|
+
acc.state = 'open'
|
|
85
|
+
} else if (event.type === 'execution.ended' && acc.state === 'open') {
|
|
86
|
+
acc.state = event.payload.outcome
|
|
87
|
+
}
|
|
70
88
|
}
|
|
71
|
-
}
|
|
72
|
-
|
|
89
|
+
},
|
|
90
|
+
finish: (acc) => acc.state,
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
export function lastExecutionOutcome(events: AbEvent[]): ExecutionOutcome {
|
|
94
|
+
return lastExecutionOutcomeReducer.reduce(events)
|
|
73
95
|
}
|
|
74
96
|
|
|
75
97
|
function observationIdentity(open: {
|
|
@@ -124,12 +146,23 @@ async function releaseSettledLease(
|
|
|
124
146
|
* Observation errors are contained and read as `running` (conservative: never
|
|
125
147
|
* reap on an unknown); the next tick retries.
|
|
126
148
|
*/
|
|
127
|
-
export
|
|
149
|
+
export function settleExecution(
|
|
128
150
|
deps: ExecutionSettlementDeps,
|
|
129
151
|
slug: string,
|
|
130
152
|
events: AbEvent[],
|
|
131
153
|
): Promise<ExecutionSettlement> {
|
|
132
|
-
|
|
154
|
+
return settleOpenExecution(deps, slug, openExecution(events))
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** `settleExecution` over an already-derived open execution, for callers that
|
|
158
|
+
* hold the projection (a reducer snapshot advanced to the log tail) and so
|
|
159
|
+
* never need the event array. A `running` result or an observation error
|
|
160
|
+
* touches no event rows. */
|
|
161
|
+
export async function settleOpenExecution(
|
|
162
|
+
deps: ExecutionSettlementDeps,
|
|
163
|
+
slug: string,
|
|
164
|
+
open: OpenExecution | null,
|
|
165
|
+
): Promise<ExecutionSettlement> {
|
|
133
166
|
if (open === null) return 'settled'
|
|
134
167
|
if (open.commandId === undefined || deps.execution.observe === undefined) return 'running'
|
|
135
168
|
let observation: ExecutionObservation
|
|
@@ -61,8 +61,13 @@ export async function runHarvestChild(
|
|
|
61
61
|
// Full build-child composition parity, in the same order: plugins load
|
|
62
62
|
// first or materializePluginRuntimes silently no-ops and leaves any
|
|
63
63
|
// plugin-provided runtime unrouted in the guest.
|
|
64
|
+
// Guest tolerance (AUT-517): a harvest runner launch is always
|
|
65
|
+
// environment-supervised — the process is structurally a guest and never
|
|
66
|
+
// constructs a workspace provider — so a configured provider plugin the
|
|
67
|
+
// guest cannot resolve is skipped with a notice instead of failing.
|
|
64
68
|
const plugins = await loadPlugins(config.plugins, workspacePath, {
|
|
65
69
|
packageRoot: input.repo,
|
|
70
|
+
...(input.supervision.kind === 'environment' ? { guest: true } : {}),
|
|
66
71
|
})
|
|
67
72
|
const { runtimes: builtins } = createProductionRuntimes()
|
|
68
73
|
const runtimes = await materializePluginRuntimes(builtins, plugins, {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* supervisor died.
|
|
9
9
|
*/
|
|
10
10
|
import type { RepositoryEvent } from '../events/repository'
|
|
11
|
+
import { defineReducer } from '../kernel/incremental'
|
|
11
12
|
import {
|
|
12
13
|
DEFAULT_MAX_HARVEST_RECOVERY_ATTEMPTS,
|
|
13
14
|
actionableHarvestRun,
|
|
@@ -16,42 +17,53 @@ import {
|
|
|
16
17
|
} from '../kernel/harvest'
|
|
17
18
|
import type { HarvestRunnerResult } from './harvest-runner'
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
* a matching `harvest.execution.released`, in journal order. */
|
|
21
|
-
export function openHarvestExecutions(events: readonly RepositoryEvent[]): Array<{
|
|
20
|
+
export interface OpenHarvestExecution {
|
|
22
21
|
execution: string
|
|
23
22
|
provider: string
|
|
24
23
|
environmentId: string
|
|
25
24
|
sessionId?: string
|
|
26
25
|
commandId?: string
|
|
27
26
|
seq: number
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const OPEN_HARVEST_EXECUTIONS_REDUCER_VERSION = 1
|
|
30
|
+
|
|
31
|
+
/** Open harvest executions: every `harvest.execution.started` not followed by
|
|
32
|
+
* a matching `harvest.execution.released`, in journal order. The accumulator is
|
|
33
|
+
* an ordered array: a re-started execution keeps its original position, as it
|
|
34
|
+
* did in the `Map` this replaces. */
|
|
35
|
+
export const openHarvestExecutionsReducer = defineReducer<
|
|
36
|
+
{ open: OpenHarvestExecution[] },
|
|
37
|
+
RepositoryEvent,
|
|
38
|
+
OpenHarvestExecution[]
|
|
39
|
+
>({
|
|
40
|
+
version: OPEN_HARVEST_EXECUTIONS_REDUCER_VERSION,
|
|
41
|
+
initial: () => ({ open: [] }),
|
|
42
|
+
fold(acc, events) {
|
|
43
|
+
for (const event of events) {
|
|
44
|
+
if (event.type === 'harvest.execution.started') {
|
|
45
|
+
const entry: OpenHarvestExecution = {
|
|
46
|
+
execution: event.payload.execution,
|
|
47
|
+
provider: event.payload.provider,
|
|
48
|
+
environmentId: event.payload.environmentId,
|
|
49
|
+
...(event.payload.sessionId !== undefined ? { sessionId: event.payload.sessionId } : {}),
|
|
50
|
+
...(event.payload.commandId !== undefined ? { commandId: event.payload.commandId } : {}),
|
|
51
|
+
seq: event.seq,
|
|
52
|
+
}
|
|
53
|
+
const index = acc.open.findIndex((item) => item.execution === entry.execution)
|
|
54
|
+
if (index === -1) acc.open.push(entry)
|
|
55
|
+
else acc.open[index] = entry
|
|
56
|
+
} else if (event.type === 'harvest.execution.released') {
|
|
57
|
+
const index = acc.open.findIndex((item) => item.execution === event.payload.execution)
|
|
58
|
+
if (index !== -1) acc.open.splice(index, 1)
|
|
59
|
+
}
|
|
52
60
|
}
|
|
53
|
-
}
|
|
54
|
-
|
|
61
|
+
},
|
|
62
|
+
finish: (acc) => acc.open.map((item) => ({ ...item })),
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
export function openHarvestExecutions(events: readonly RepositoryEvent[]): OpenHarvestExecution[] {
|
|
66
|
+
return openHarvestExecutionsReducer.reduce(events)
|
|
55
67
|
}
|
|
56
68
|
|
|
57
69
|
export interface HarvestExecutionClassificationInput {
|
|
@@ -46,6 +46,7 @@ import { defaultProposalState } from './dispatcher'
|
|
|
46
46
|
import {
|
|
47
47
|
artifactRef,
|
|
48
48
|
evaluateHarvestPressure,
|
|
49
|
+
evaluateHarvestPressureFromDigests,
|
|
49
50
|
HARVEST_REPORT_ARTIFACT,
|
|
50
51
|
HARVEST_SCAN_ARTIFACT,
|
|
51
52
|
HARVEST_TRANSCRIPT_ARTIFACT,
|
|
@@ -230,7 +231,9 @@ export class HarvestRunner {
|
|
|
230
231
|
try {
|
|
231
232
|
await this.startHeartbeat()
|
|
232
233
|
await this.ensureLease()
|
|
233
|
-
|
|
234
|
+
// Bounded read (AUT-489): reduceHarvest and the verdict/failure filters
|
|
235
|
+
// below consume durable types only, so the subset is replay-equivalent.
|
|
236
|
+
let events = await store.getRepoStateEvents(repo)
|
|
234
237
|
let state = reduceHarvest(events)
|
|
235
238
|
run = actionableHarvestRun(state)
|
|
236
239
|
|
|
@@ -238,19 +241,48 @@ export class HarvestRunner {
|
|
|
238
241
|
// selected run comes from the full journal, so a shadowed failure or
|
|
239
242
|
// exhaustion barrier is recovered/reported before any later open run.
|
|
240
243
|
await this.controlBoundary(run?.run)
|
|
241
|
-
events = await store.
|
|
244
|
+
events = await store.getRepoStateEvents(repo)
|
|
242
245
|
state = reduceHarvest(events)
|
|
243
246
|
run = openHarvestRun(state)
|
|
244
247
|
|
|
245
248
|
if (!run) {
|
|
246
249
|
await this.controlBoundary()
|
|
247
|
-
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
//
|
|
250
|
+
// Flat-cost gate (AUT-521): the pressure trigger is evaluated from
|
|
251
|
+
// the journal and one repo-scoped digest batch read — no per-build
|
|
252
|
+
// history reads — so the idle evaluation no longer grows with
|
|
253
|
+
// accumulated finished builds. Only a tripped gate pays for the full
|
|
254
|
+
// unclaimed-observation scan, because the packet artifact needs the
|
|
255
|
+
// observation payloads no bounded source carries.
|
|
256
|
+
// Bounded read (AUT-489): the gate's journal input is consumed only
|
|
257
|
+
// through reduceHarvest, so the subset is replay-equivalent here too.
|
|
258
|
+
const [gateEvents, digests] = await Promise.all([
|
|
259
|
+
store.getRepoStateEvents(repo),
|
|
260
|
+
store.getRepoBuildDigests(repo),
|
|
261
|
+
])
|
|
262
|
+
const pressure = evaluateHarvestPressureFromDigests({
|
|
263
|
+
digests,
|
|
264
|
+
harvestEvents: gateEvents,
|
|
265
|
+
policy: this.currentConfig().policy,
|
|
266
|
+
})
|
|
267
|
+
if (pressure.trigger === undefined) {
|
|
268
|
+
// Treat the read's completion as a boundary even when the gate did
|
|
269
|
+
// not trip, so a request arriving during the read is acknowledged
|
|
270
|
+
// before this runner returns idle.
|
|
271
|
+
await this.controlBoundary()
|
|
272
|
+
return { outcome: 'idle' }
|
|
273
|
+
}
|
|
251
274
|
await this.controlBoundary()
|
|
252
|
-
const
|
|
253
|
-
|
|
275
|
+
const scan = await scanUnclaimedObservations(store, repo)
|
|
276
|
+
// The trigger came from an earlier read than this scan, and a claim
|
|
277
|
+
// may have landed in the window. Re-confirm the gate against the
|
|
278
|
+
// fresh scan (pure, free): an emptied window returns idle exactly as
|
|
279
|
+
// if the gate had not tripped, so a packet is only ever built from a
|
|
280
|
+
// scan that itself triggered.
|
|
281
|
+
const confirmed = evaluateHarvestPressure(scan, this.currentConfig().policy)
|
|
282
|
+
if (confirmed.trigger === undefined) {
|
|
283
|
+
await this.controlBoundary()
|
|
284
|
+
return { outcome: 'idle' }
|
|
285
|
+
}
|
|
254
286
|
const runId = this.deps.ids('harvest')
|
|
255
287
|
const packet = await makeHarvestScanPacket({
|
|
256
288
|
store,
|
|
@@ -280,14 +312,15 @@ export class HarvestRunner {
|
|
|
280
312
|
run: runId,
|
|
281
313
|
observations: packet.observations.map((item) => item.occurrence),
|
|
282
314
|
scan: artifactRef(deposited[0]!),
|
|
283
|
-
trigger:
|
|
315
|
+
trigger: confirmed.trigger,
|
|
284
316
|
...(this.deps.environment !== undefined
|
|
285
317
|
? { environment: this.deps.environment }
|
|
286
318
|
: {}),
|
|
287
319
|
},
|
|
288
320
|
}),
|
|
289
321
|
)
|
|
290
|
-
|
|
322
|
+
// Bounded read (AUT-489): reduceHarvest consumes durable types only.
|
|
323
|
+
events = await store.getRepoStateEvents(repo)
|
|
291
324
|
run = reduceHarvest(events).runs.find((candidate) => candidate.run === runId)
|
|
292
325
|
if (!run) throw new Error(`harvest.started did not reduce run "${runId}"`)
|
|
293
326
|
// A request racing the claim parks this exact run; its snapshot stays
|
|
@@ -448,7 +481,9 @@ export class HarvestRunner {
|
|
|
448
481
|
existing.verdict,
|
|
449
482
|
existing.artifact,
|
|
450
483
|
)
|
|
451
|
-
|
|
484
|
+
// Bounded read (AUT-489): only harvest.review.verdict facts are
|
|
485
|
+
// filtered here, and those are durable.
|
|
486
|
+
const event = (await this.deps.store.getRepoStateEvents(this.deps.repo)).find(
|
|
452
487
|
(
|
|
453
488
|
candidate,
|
|
454
489
|
): candidate is Extract<RepositoryEvent, { type: 'harvest.review.verdict' }> =>
|
|
@@ -515,7 +550,9 @@ export class HarvestRunner {
|
|
|
515
550
|
event.actor.kind === 'agent' &&
|
|
516
551
|
event.actor.session === session,
|
|
517
552
|
})
|
|
518
|
-
|
|
553
|
+
// Bounded read (AUT-489): only harvest.review.verdict facts are filtered
|
|
554
|
+
// here, and those are durable.
|
|
555
|
+
const events = await this.deps.store.getRepoStateEvents(this.deps.repo)
|
|
519
556
|
const verdict = [...events]
|
|
520
557
|
.reverse()
|
|
521
558
|
.find(
|
|
@@ -565,7 +602,9 @@ export class HarvestRunner {
|
|
|
565
602
|
): Promise<void> {
|
|
566
603
|
const { store, repo, ids, workspacePath } = this.deps
|
|
567
604
|
await this.ensureLease()
|
|
568
|
-
|
|
605
|
+
// Bounded read (AUT-489): reduceHarvest and the harvest.failed filter
|
|
606
|
+
// consume durable types only.
|
|
607
|
+
const events = await store.getRepoStateEvents(repo)
|
|
569
608
|
const matchingFailures = events.filter(
|
|
570
609
|
(event): event is Extract<RepositoryEvent, { type: 'harvest.failed' }> =>
|
|
571
610
|
event.type === 'harvest.failed' &&
|
|
@@ -823,7 +862,9 @@ export class HarvestRunner {
|
|
|
823
862
|
}): Promise<void> {
|
|
824
863
|
const { store, repo, ids, workspacePath } = this.deps
|
|
825
864
|
await this.ensureLease()
|
|
826
|
-
|
|
865
|
+
// Bounded read (AUT-489): reduceHarvest and the harvest.failed filter
|
|
866
|
+
// consume durable types only.
|
|
867
|
+
const events = await store.getRepoStateEvents(repo)
|
|
827
868
|
const matchingFailures = events.filter(
|
|
828
869
|
(event): event is Extract<RepositoryEvent, { type: 'harvest.failed' }> =>
|
|
829
870
|
event.type === 'harvest.failed' &&
|
|
@@ -1296,7 +1337,9 @@ export class HarvestRunner {
|
|
|
1296
1337
|
|
|
1297
1338
|
private async recordWorkflowFailure(run: string, error: unknown): Promise<void> {
|
|
1298
1339
|
await this.ensureLease()
|
|
1299
|
-
|
|
1340
|
+
// Bounded read (AUT-489): reduceHarvest and the harvest.failed filter
|
|
1341
|
+
// consume durable types only.
|
|
1342
|
+
const events = await this.deps.store.getRepoStateEvents(this.deps.repo)
|
|
1300
1343
|
const state = reduceHarvest(events)
|
|
1301
1344
|
const current = state.runs.find((candidate) => candidate.run === run)
|
|
1302
1345
|
if (current?.status !== 'running') return
|
|
@@ -1349,7 +1392,8 @@ export class HarvestRunner {
|
|
|
1349
1392
|
* outer budget and therefore remain recoverable. */
|
|
1350
1393
|
private async settleRecoveryExhaustion(run: string): Promise<void> {
|
|
1351
1394
|
await this.ensureLease()
|
|
1352
|
-
|
|
1395
|
+
// Bounded read (AUT-489): reduceHarvest consumes durable types only.
|
|
1396
|
+
const state = reduceHarvest(await this.deps.store.getRepoStateEvents(this.deps.repo))
|
|
1353
1397
|
const decision = decideHarvestControl(state, this.maxRecoveryAttempts)
|
|
1354
1398
|
if (decision.kind !== 'exhaust-recovery' || decision.run !== run) return
|
|
1355
1399
|
await this.finalizeRecoveryExhaustion(decision.run, decision.attempts, decision.limit)
|
|
@@ -1374,7 +1418,8 @@ export class HarvestRunner {
|
|
|
1374
1418
|
})
|
|
1375
1419
|
|
|
1376
1420
|
await this.ensureLease()
|
|
1377
|
-
|
|
1421
|
+
// Bounded read (AUT-489): reduceHarvest consumes durable types only.
|
|
1422
|
+
const state = reduceHarvest(await this.deps.store.getRepoStateEvents(this.deps.repo))
|
|
1378
1423
|
const decision = decideHarvestControl(state, this.maxRecoveryAttempts)
|
|
1379
1424
|
if (
|
|
1380
1425
|
decision.kind !== 'exhaust-recovery' ||
|
|
@@ -1410,7 +1455,8 @@ export class HarvestRunner {
|
|
|
1410
1455
|
private async controlBoundary(run?: string): Promise<void> {
|
|
1411
1456
|
while (true) {
|
|
1412
1457
|
await this.ensureLease()
|
|
1413
|
-
|
|
1458
|
+
// Bounded read (AUT-489): reduceHarvest consumes durable types only.
|
|
1459
|
+
const state = reduceHarvest(await this.deps.store.getRepoStateEvents(this.deps.repo))
|
|
1414
1460
|
const decision = decideHarvestControl(state, this.maxRecoveryAttempts)
|
|
1415
1461
|
if (decision.kind === 'proceed') return
|
|
1416
1462
|
if (decision.kind === 'park') {
|
|
@@ -1453,7 +1499,8 @@ export class HarvestRunner {
|
|
|
1453
1499
|
}
|
|
1454
1500
|
|
|
1455
1501
|
private async refreshRun(run: string): Promise<HarvestRunState> {
|
|
1456
|
-
|
|
1502
|
+
// Bounded read (AUT-489): reduceHarvest consumes durable types only.
|
|
1503
|
+
const state = reduceHarvest(await this.deps.store.getRepoStateEvents(this.deps.repo))
|
|
1457
1504
|
const found = state.runs.find((candidate) => candidate.run === run)
|
|
1458
1505
|
if (!found) throw new Error(`unknown harvest run "${run}"`)
|
|
1459
1506
|
return found
|