@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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Deterministic halves of observation harvest: scan, dedup, validation, and
|
|
2
2
|
* proposal rendering. Agent judgment is deliberately absent from this file. */
|
|
3
|
+
import type { AbEvent } from '../events/catalog'
|
|
3
4
|
import type { ArtifactRef } from '../ontology'
|
|
4
5
|
import {
|
|
5
6
|
harvestProposalSetSchema,
|
|
@@ -26,7 +27,14 @@ import {
|
|
|
26
27
|
} from '../kernel/harvest'
|
|
27
28
|
import type { TicketSource } from '../ports/types'
|
|
28
29
|
import { specConformance } from '../spec-standard'
|
|
29
|
-
import {
|
|
30
|
+
import type { RepositoryEvent } from '../events/repository'
|
|
31
|
+
import {
|
|
32
|
+
contentHash,
|
|
33
|
+
toBytes,
|
|
34
|
+
type BuildDigest,
|
|
35
|
+
type BuildRecord,
|
|
36
|
+
type BuildStore,
|
|
37
|
+
} from '../store/types'
|
|
30
38
|
|
|
31
39
|
export const HARVEST_SCAN_ARTIFACT = 'harvest-scan'
|
|
32
40
|
export const HARVEST_PROPOSALS_ARTIFACT = 'harvest-proposals'
|
|
@@ -52,21 +60,33 @@ export interface HarvestPressure {
|
|
|
52
60
|
trigger?: HarvestTrigger
|
|
53
61
|
}
|
|
54
62
|
|
|
55
|
-
/**
|
|
56
|
-
* unclaimed
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
/** The build- and occurrence-shaped facts both pressure evaluations project
|
|
64
|
+
* onto: the unclaimed occurrences with their event timestamps, and the merge
|
|
65
|
+
* facts. Keeping the oldest-observation selection and drift arithmetic in one
|
|
66
|
+
* place makes the scan-based and digest-based evaluations equal by
|
|
67
|
+
* construction rather than by parallel maintenance (AUT-521). */
|
|
68
|
+
interface HarvestPressureFacts {
|
|
69
|
+
unclaimed: { build: string; seq: number; ts: string }[]
|
|
70
|
+
merges: { build: string; ts: string }[]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function evaluateHarvestPressureFacts(
|
|
74
|
+
facts: HarvestPressureFacts,
|
|
59
75
|
policy: { harvestThreshold: number; harvestMaxDrift: number },
|
|
60
76
|
): HarvestPressure {
|
|
61
|
-
const observationCount =
|
|
77
|
+
const observationCount = facts.unclaimed.length
|
|
62
78
|
if (observationCount === 0) return { observationCount, drift: 0 }
|
|
63
79
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
80
|
+
// The oldest unclaimed occurrence: min ts, then build ascending, then seq
|
|
81
|
+
// ascending — exactly what the scan projection's sort-by-(build,seq) then
|
|
82
|
+
// strict-`<`-reduce produced, now stated as one comparator.
|
|
83
|
+
const oldest = [...facts.unclaimed].sort(
|
|
84
|
+
(a, b) =>
|
|
85
|
+
(a.ts < b.ts ? -1 : a.ts > b.ts ? 1 : 0) || a.build.localeCompare(b.build) || a.seq - b.seq,
|
|
86
|
+
)[0]!
|
|
67
87
|
const mergedBuilds = new Set<string>()
|
|
68
|
-
for (const merge of
|
|
69
|
-
if (merge.build === oldest.
|
|
88
|
+
for (const merge of facts.merges) {
|
|
89
|
+
if (merge.build === oldest.build || merge.ts <= oldest.ts) continue
|
|
70
90
|
mergedBuilds.add(merge.build)
|
|
71
91
|
}
|
|
72
92
|
const drift = mergedBuilds.size
|
|
@@ -87,23 +107,94 @@ export function evaluateHarvestPressure(
|
|
|
87
107
|
}
|
|
88
108
|
}
|
|
89
109
|
|
|
90
|
-
/**
|
|
91
|
-
*
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
110
|
+
/** Pure two-dimensional Harvest gate. Drift is measured from the oldest
|
|
111
|
+
* unclaimed observation and excludes that observation's own build. */
|
|
112
|
+
export function evaluateHarvestPressure(
|
|
113
|
+
scan: Pick<HarvestScanResult, 'observations' | 'merges'>,
|
|
114
|
+
policy: { harvestThreshold: number; harvestMaxDrift: number },
|
|
115
|
+
): HarvestPressure {
|
|
116
|
+
return evaluateHarvestPressureFacts(
|
|
117
|
+
{
|
|
118
|
+
unclaimed: scan.observations.map((observation) => ({
|
|
119
|
+
build: observation.occurrence.build,
|
|
120
|
+
seq: observation.occurrence.seq,
|
|
121
|
+
ts: observation.ts,
|
|
122
|
+
})),
|
|
123
|
+
merges: scan.merges,
|
|
124
|
+
},
|
|
125
|
+
policy,
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** The same gate from build digests and the repository journal alone
|
|
130
|
+
* (AUT-521): claimedness comes from the journal, unclaimed occurrences and
|
|
131
|
+
* merge facts from the digests — no per-build history reads. The merge facts
|
|
132
|
+
* use each digest's latest `pr.merged` ts; drift counts a build when any of
|
|
133
|
+
* its merges is strictly after the oldest unclaimed observation, and
|
|
134
|
+
* `max(merge.ts) > oldest.ts` is equivalent to `any(merge.ts) > oldest.ts`,
|
|
135
|
+
* so latest-wins carries exactly the drift fact the scan collects. */
|
|
136
|
+
export function evaluateHarvestPressureFromDigests(input: {
|
|
137
|
+
digests: Map<string, BuildDigest>
|
|
138
|
+
harvestEvents: RepositoryEvent[]
|
|
139
|
+
policy: { harvestThreshold: number; harvestMaxDrift: number }
|
|
140
|
+
}): HarvestPressure {
|
|
141
|
+
const claimed = claimedOccurrenceKeys(reduceHarvest(input.harvestEvents))
|
|
142
|
+
const unclaimed: HarvestPressureFacts['unclaimed'] = []
|
|
143
|
+
const merges: HarvestPressureFacts['merges'] = []
|
|
144
|
+
for (const digest of input.digests.values()) {
|
|
145
|
+
for (const observation of digest.observations) {
|
|
146
|
+
if (claimed.has(occurrenceKey({ build: digest.slug, seq: observation.seq }))) continue
|
|
147
|
+
unclaimed.push({ build: digest.slug, seq: observation.seq, ts: observation.ts })
|
|
148
|
+
}
|
|
149
|
+
if (digest.merged !== undefined) merges.push({ build: digest.slug, ts: digest.merged })
|
|
150
|
+
}
|
|
151
|
+
return evaluateHarvestPressureFacts({ unclaimed, merges }, input.policy)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** The dispatcher harvest-launch gate's read sequence as one named seam
|
|
155
|
+
* (AUT-521): `ensureRepo`, one repo-scoped digest batch read, and the pure
|
|
156
|
+
* digest evaluation with the caller's policy. Per-evaluation cost is one
|
|
157
|
+
* journal read (passed in by the caller, which already holds it) plus one
|
|
158
|
+
* bounded digest read — flat in the number of accumulated finished builds,
|
|
159
|
+
* where the previous scan read every build's full history per evaluation. */
|
|
160
|
+
export async function evaluateHarvestPressureFromStore(input: {
|
|
161
|
+
store: BuildStore
|
|
162
|
+
repo: string
|
|
163
|
+
harvestEvents: RepositoryEvent[]
|
|
164
|
+
policy: { harvestThreshold: number; harvestMaxDrift: number }
|
|
165
|
+
}): Promise<HarvestPressure> {
|
|
166
|
+
await input.store.ensureRepo(input.repo)
|
|
167
|
+
const digests = await input.store.getRepoBuildDigests(input.repo)
|
|
168
|
+
return evaluateHarvestPressureFromDigests({
|
|
169
|
+
digests,
|
|
170
|
+
harvestEvents: input.harvestEvents,
|
|
171
|
+
policy: input.policy,
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** The deterministic core of the unclaimed-observation scan, over
|
|
176
|
+
* already-loaded streams: exactly the reduce/claim/collect/sort the
|
|
177
|
+
* store-reading scan performs, with no store calls. The dashboard consumes
|
|
178
|
+
* this directly with the journal and per-build event arrays it has already
|
|
179
|
+
* fetched (AUT-486), while `scanUnclaimedObservations` keeps the
|
|
180
|
+
* ensure/read/delegate shape its other callers depend on. */
|
|
181
|
+
export function collectUnclaimedObservations(input: {
|
|
182
|
+
repo: string
|
|
183
|
+
records: BuildRecord[]
|
|
184
|
+
eventsByBuild: Map<string, AbEvent[]>
|
|
185
|
+
harvestEvents: RepositoryEvent[]
|
|
186
|
+
}): HarvestScanResult {
|
|
187
|
+
const state = reduceHarvest(input.harvestEvents)
|
|
100
188
|
const claimed = claimedOccurrenceKeys(state)
|
|
101
189
|
const observations: HarvestObservation[] = []
|
|
102
190
|
const merges: HarvestMerge[] = []
|
|
103
191
|
|
|
104
|
-
for (const record of
|
|
105
|
-
if (record.repo !== repo) continue
|
|
106
|
-
const events =
|
|
192
|
+
for (const record of input.records) {
|
|
193
|
+
if (record.repo !== input.repo) continue
|
|
194
|
+
const events = input.eventsByBuild.get(record.slug)
|
|
195
|
+
if (events === undefined) {
|
|
196
|
+
throw new Error(`eventsByBuild is missing an entry for build "${record.slug}"`)
|
|
197
|
+
}
|
|
107
198
|
for (const event of events) {
|
|
108
199
|
if (event.type === 'pr.merged') {
|
|
109
200
|
merges.push({ build: record.slug, ts: event.ts })
|
|
@@ -132,6 +223,87 @@ export async function scanUnclaimedObservations(
|
|
|
132
223
|
return { observations, merges, state }
|
|
133
224
|
}
|
|
134
225
|
|
|
226
|
+
/** The unclaimed-observation count from build digests and the repository
|
|
227
|
+
* journal alone (AUT-487): the same reduce/claim/count the store-reading scan
|
|
228
|
+
* performs, with no per-build history reads. The operator query consumes this
|
|
229
|
+
* directly, and the terminal dashboards consume it through
|
|
230
|
+
* `sampleUnclaimedObservationCount` (AUT-524), while `scanUnclaimedObservations`
|
|
231
|
+
* keeps the full-scan shape its remaining callers depend on (the harvest
|
|
232
|
+
* runner's packet scan — the only source of observation payloads — and its
|
|
233
|
+
* gate re-confirmation; the dispatcher's harvest-launch gate itself evaluates
|
|
234
|
+
* from digests, AUT-521). Occurrences are keyed `{build, seq}`; payload ids
|
|
235
|
+
* are not assumed globally unique. */
|
|
236
|
+
export function unclaimedObservationCount(input: {
|
|
237
|
+
digests: Map<string, BuildDigest>
|
|
238
|
+
harvestEvents: RepositoryEvent[]
|
|
239
|
+
}): number {
|
|
240
|
+
const claimed = claimedOccurrenceKeys(reduceHarvest(input.harvestEvents))
|
|
241
|
+
let count = 0
|
|
242
|
+
for (const digest of input.digests.values()) {
|
|
243
|
+
for (const observation of digest.observations) {
|
|
244
|
+
if (!claimed.has(occurrenceKey({ build: digest.slug, seq: observation.seq }))) count += 1
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return count
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** The shared missing-record journal read (AUT-524): a repository whose
|
|
251
|
+
* journal record does not yet exist has an empty journal by definition, so
|
|
252
|
+
* the probe answers `[]` instead of letting `getRepoStateEvents` reject. The
|
|
253
|
+
* read writes nothing — a display path must not `ensureRepo` — while
|
|
254
|
+
* `scanUnclaimedObservations` keeps its materializing `ensureRepo` for its
|
|
255
|
+
* remaining callers.
|
|
256
|
+
*
|
|
257
|
+
* The journal read is bounded (AUT-489): every consumer — the terminal
|
|
258
|
+
* dashboards' observation-pressure sample, the operator query
|
|
259
|
+
* (operator/query.ts), the dispatcher's repository-wide controls and ready
|
|
260
|
+
* scans, and the operator sandbox service's journal reads — reduces durable
|
|
261
|
+
* event types plus the latest-run tail only, so the subset is
|
|
262
|
+
* replay-equivalent to a full journal replay. */
|
|
263
|
+
export async function readRepoEventsIfRecorded(
|
|
264
|
+
store: BuildStore,
|
|
265
|
+
repo: string,
|
|
266
|
+
): Promise<RepositoryEvent[]> {
|
|
267
|
+
return (await store.getRepo(repo)) === null ? [] : store.getRepoStateEvents(repo)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** The terminal dashboards' observation-pressure sample (AUT-487): build
|
|
271
|
+
* digests plus the repository journal, reduced to the unclaimed-observation
|
|
272
|
+
* count. Store traffic is flat in the finished-build count — one journal read
|
|
273
|
+
* plus one repo-scoped digest read, plus the journal-record probe inside
|
|
274
|
+
* `readRepoEventsIfRecorded`. Its missing-record treatment (AUT-524) is the
|
|
275
|
+
* shared helper's, above. */
|
|
276
|
+
export async function sampleUnclaimedObservationCount(
|
|
277
|
+
store: BuildStore,
|
|
278
|
+
repo: string,
|
|
279
|
+
): Promise<number> {
|
|
280
|
+
const [digests, harvestEvents] = await Promise.all([
|
|
281
|
+
store.getRepoBuildDigests(repo),
|
|
282
|
+
readRepoEventsIfRecorded(store, repo),
|
|
283
|
+
])
|
|
284
|
+
return unclaimedObservationCount({ digests, harvestEvents })
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** Raw structured `observation.recorded` envelopes across this repository.
|
|
288
|
+
* The pair `{build, seq}` is the occurrence key; payload ids are not assumed
|
|
289
|
+
* globally unique. */
|
|
290
|
+
export async function scanUnclaimedObservations(
|
|
291
|
+
store: BuildStore,
|
|
292
|
+
repo: string,
|
|
293
|
+
): Promise<HarvestScanResult> {
|
|
294
|
+
await store.ensureRepo(repo)
|
|
295
|
+
// Bounded read (AUT-489): only harvest facts are consumed here; the
|
|
296
|
+
// per-build history reads stay as AUT-487 left them.
|
|
297
|
+
const harvestEvents = await store.getRepoStateEvents(repo)
|
|
298
|
+
const records = await store.listBuilds()
|
|
299
|
+
const eventsByBuild = new Map<string, AbEvent[]>()
|
|
300
|
+
for (const record of records) {
|
|
301
|
+
if (record.repo !== repo) continue
|
|
302
|
+
eventsByBuild.set(record.slug, await store.getEvents(record.slug))
|
|
303
|
+
}
|
|
304
|
+
return collectUnclaimedObservations({ repo, records, eventsByBuild, harvestEvents })
|
|
305
|
+
}
|
|
306
|
+
|
|
135
307
|
/** Distinct previously filed/joined proposal tickets, reconciled through the
|
|
136
308
|
* TicketSource's native lifecycle semantics. Resolved and missing entries stay
|
|
137
309
|
* in the packet as tombstones rather than disappearing and being re-filed. */
|
|
@@ -2,6 +2,7 @@ import type { AbEvent } from '../events/catalog'
|
|
|
2
2
|
import { DISPATCHER } from '../events/envelope'
|
|
3
3
|
import type { EventPayload } from '../events/payloads'
|
|
4
4
|
import type { IdSource } from '../ids'
|
|
5
|
+
import { defineReducer } from '../kernel/incremental'
|
|
5
6
|
import type { BuildStore } from '../store/types'
|
|
6
7
|
|
|
7
8
|
export type InfrastructureOperation = EventPayload<'infrastructure.failed'>['operation']
|
|
@@ -26,27 +27,39 @@ export interface InfrastructureFailureDependencies {
|
|
|
26
27
|
ids: IdSource
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
export const INFRASTRUCTURE_FAILURE_RESET_REDUCER_VERSION = 1
|
|
31
|
+
|
|
29
32
|
/** Infrastructure retries are re-armed only by a confirmed execution boundary
|
|
30
33
|
* or a retry answer to this policy's own previously-raised exhaustion escalation. */
|
|
31
|
-
export
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
34
|
+
export const infrastructureFailureResetReducer = defineReducer<
|
|
35
|
+
{ escalations: string[]; resetSeq: number },
|
|
36
|
+
AbEvent,
|
|
37
|
+
number
|
|
38
|
+
>({
|
|
39
|
+
version: INFRASTRUCTURE_FAILURE_RESET_REDUCER_VERSION,
|
|
40
|
+
initial: () => ({ escalations: [], resetSeq: 0 }),
|
|
41
|
+
fold(acc, events) {
|
|
42
|
+
for (const event of events) {
|
|
43
|
+
if (
|
|
44
|
+
event.type === 'escalation.raised' &&
|
|
45
|
+
event.payload.policyCause === 'infrastructure-failure-limit'
|
|
46
|
+
) {
|
|
47
|
+
if (!acc.escalations.includes(event.payload.id)) acc.escalations.push(event.payload.id)
|
|
48
|
+
} else if (
|
|
49
|
+
event.type === 'execution.ended' ||
|
|
50
|
+
(event.type === 'escalation.answered' &&
|
|
51
|
+
event.payload.resolution === 'retry' &&
|
|
52
|
+
acc.escalations.includes(event.payload.id))
|
|
53
|
+
) {
|
|
54
|
+
acc.resetSeq = event.seq
|
|
55
|
+
}
|
|
47
56
|
}
|
|
48
|
-
}
|
|
49
|
-
|
|
57
|
+
},
|
|
58
|
+
finish: (acc) => acc.resetSeq,
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
export function infrastructureFailureResetSeq(events: readonly AbEvent[]): number {
|
|
62
|
+
return infrastructureFailureResetReducer.reduce(events)
|
|
50
63
|
}
|
|
51
64
|
|
|
52
65
|
export function normalizeInfrastructureError(error: unknown): string {
|