@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
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Moving a project between ticket sources
|
|
2
|
+
|
|
3
|
+
Export and import use the ordinary file ticket layout. They operate on tickets
|
|
4
|
+
and current attachments through the ticket source; neither command contacts the
|
|
5
|
+
forge. File and hosted sources support import. Linear supports export only.
|
|
6
|
+
|
|
7
|
+
1. Pause intake and stop other ticket writers for the cutover. Import preflight
|
|
8
|
+
does not reserve numbers against concurrent writers.
|
|
9
|
+
2. With the old `[tickets]` configuration, run `ab ticket export /path/to/export`.
|
|
10
|
+
The destination must be absent or empty. Export reads every state, including
|
|
11
|
+
archived Linear tickets and empty discoverable states, and publishes the
|
|
12
|
+
directory only after all reads succeed. Back up the target tracker too.
|
|
13
|
+
3. Configure the new `[tickets]` source, or use `ab connect --tickets URL` for a
|
|
14
|
+
hosted ticket service. Keep the export directory intact.
|
|
15
|
+
4. Run `ab ticket import /path/to/export`. Inspect its printed ticket and state
|
|
16
|
+
mappings and `/path/to/export/.ab-import-map.json`.
|
|
17
|
+
5. Inspect ticket bodies, labels, blockers, comments and attachments in the new
|
|
18
|
+
source. Confirm lifecycle configuration and the ready gate, then resume intake.
|
|
19
|
+
|
|
20
|
+
Both commands also accept
|
|
21
|
+
`--store <ref>` to reach legacy BuildStore attachments. Keep the old store available
|
|
22
|
+
through export when file or Linear tickets have attachments there.
|
|
23
|
+
|
|
24
|
+
State directory names use lowercase letters, digits and hyphens, start with a
|
|
25
|
+
letter and contain at most forty characters. Export maps `In Progress` to
|
|
26
|
+
`in-progress`, `Backlog` to `backlog`, and `Done` to `done`. It retains each ticket's
|
|
27
|
+
state rather than assigning lifecycle roles. Distinct names that collapse to the
|
|
28
|
+
same directory fail before publication. The hidden `.ab-migration.json` records
|
|
29
|
+
original display names and the state map. Import also accepts ordinary legacy
|
|
30
|
+
file layouts: it reuses exact or unique case-insensitive target states before
|
|
31
|
+
normalizing new names. A seeded `done` satisfies `Done`; absent `In Progress`
|
|
32
|
+
creates `in-progress`. Ambiguous matches and colliding state maps fail preflight.
|
|
33
|
+
|
|
34
|
+
File targets retain source ids. Hosted targets keep canonical own-team ids:
|
|
35
|
+
`AUT-706` imported into team `AUT` stays `AUT-706`. Other ids receive numbers above
|
|
36
|
+
all existing and preserved incoming numbers. Known clashes with unrelated tickets
|
|
37
|
+
are reported together before any target state, ticket, comment or asset write.
|
|
38
|
+
Content similarity and stale mapping files never establish identity.
|
|
39
|
+
|
|
40
|
+
Creation keys establish identity. Exports retain existing keys and assign keys to
|
|
41
|
+
legacy tickets. Importing an ordinary file layout first saves a hidden migration
|
|
42
|
+
identity and keys in that input directory. Preserve this file and the input across
|
|
43
|
+
retries. After interruption, rerun the same command: committed creates are adopted
|
|
44
|
+
by key even if their mapping write failed. Adopted bodies and other fields are not
|
|
45
|
+
overwritten. Missing blockers and comments are replayed, and unchanged attachments
|
|
46
|
+
do not create another revision. Comments are deduplicated by exact body, so two
|
|
47
|
+
identical source comment bodies produce one target comment. Original authors and
|
|
48
|
+
timestamps remain in the export; imported comments use the target's metadata.
|
|
49
|
+
|
|
50
|
+
Portable records use TOML frontmatter between `+++` fences and an unchanged
|
|
51
|
+
Markdown body. Alongside `id`, `title`, `labels`, `blockedBy` and `idempotencyKey`,
|
|
52
|
+
optional `comments` contain `body`, `createdAt` and optional `author`. Optional
|
|
53
|
+
`attachments` contain `kind`, `name`, `layout` (`file` or `tree`) and `entries`:
|
|
54
|
+
`{ type = "dir", path = "empty" }` or
|
|
55
|
+
`{ type = "file", path = "image.png", base64 = "…" }`. Common asset limits and
|
|
56
|
+
path validators apply. Only current attachment contents travel, including binary
|
|
57
|
+
files and empty directories; historical revisions do not. The exported directory
|
|
58
|
+
works directly as a file source, without its migration manifest or original store.
|
|
59
|
+
File source revision history stays in its hidden `.ab-assets` directory.
|
|
60
|
+
|
|
61
|
+
Legacy records without `comments` retain their Markdown comment append behavior;
|
|
62
|
+
export never interprets Markdown headings as structured history. New file tickets
|
|
63
|
+
use structured comments and preserve their body when a comment is added. There is
|
|
64
|
+
no continuous sync, assignee migration, state history or build-history migration.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defrex/autobuild",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Agent-driven software delivery from groomed ticket to merged PR",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -114,6 +114,7 @@
|
|
|
114
114
|
"docs"
|
|
115
115
|
],
|
|
116
116
|
"scripts": {
|
|
117
|
+
"ab": "bun bin/ab.ts",
|
|
117
118
|
"dev:cli": "bun --hot --no-clear-screen bin/ab-dev.ts",
|
|
118
119
|
"capture:dashboard": "bun tools/dashboard-capture.ts",
|
|
119
120
|
"capture:web-dashboard": "bun packages/web-dashboard-capture/src/web-dashboard-capture.tsx",
|
|
@@ -143,8 +144,8 @@
|
|
|
143
144
|
},
|
|
144
145
|
"devDependencies": {
|
|
145
146
|
"@biomejs/biome": "2.5.5",
|
|
146
|
-
"@defrex/autobuild-postgres-store": "0.
|
|
147
|
-
"@defrex/autobuild-vercel-sandbox": "0.
|
|
147
|
+
"@defrex/autobuild-postgres-store": "0.14.0",
|
|
148
|
+
"@defrex/autobuild-vercel-sandbox": "0.14.0",
|
|
148
149
|
"@resvg/resvg-js": "2.6.2",
|
|
149
150
|
"@types/bun": "^1.3.14",
|
|
150
151
|
"@types/node": "^24.0.0",
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { AbEvent } from '../events/catalog'
|
|
2
2
|
import type { RepositoryEvent } from '../events/repository'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
currentDeferralObservationReducer,
|
|
5
|
+
mergeAttentionFromFacts,
|
|
6
|
+
type DeferralLedger,
|
|
7
|
+
} from '../kernel/auto-merge'
|
|
4
8
|
import { reduceBuild, type BuildState } from '../kernel/reducer'
|
|
5
9
|
|
|
6
10
|
export interface MergeDecision {
|
|
@@ -17,22 +21,18 @@ export function mergeDecision(
|
|
|
17
21
|
now: Date,
|
|
18
22
|
state = reduceBuild(events),
|
|
19
23
|
): MergeDecision | null {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
pr: state.pr,
|
|
33
|
-
reason: deferral ?? 'Auto-merge is not armed',
|
|
34
|
-
observedAt: now.toISOString(),
|
|
35
|
-
}
|
|
24
|
+
return mergeDecisionFromFacts(state, currentDeferralObservationReducer.reduce(events), now)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function mergeDecisionFromFacts(
|
|
28
|
+
state: BuildState,
|
|
29
|
+
ledger: DeferralLedger,
|
|
30
|
+
now: Date,
|
|
31
|
+
): MergeDecision | null {
|
|
32
|
+
const decision = mergeAttentionFromFacts(state, ledger)
|
|
33
|
+
return decision
|
|
34
|
+
? { type: 'build.merge-decision', ...decision, observedAt: now.toISOString() }
|
|
35
|
+
: null
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
export function buildNeedsAttention(events: AbEvent[], state: BuildState): boolean {
|
|
@@ -60,150 +60,4 @@ export function repositoryNeedsAttention(event: RepositoryEvent): boolean {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
type Failed = Extract<RepositoryEvent, { type: 'dispatcher.tick-failed' }>
|
|
65
|
-
export type PersistenceObservation =
|
|
66
|
-
| {
|
|
67
|
-
type: 'dispatcher.ticket-source-persistent'
|
|
68
|
-
source: string
|
|
69
|
-
cause: string
|
|
70
|
-
message: string
|
|
71
|
-
resetAt?: string
|
|
72
|
-
episodeSeq: number
|
|
73
|
-
observedAt: string
|
|
74
|
-
dependencyLimited: boolean
|
|
75
|
-
}
|
|
76
|
-
| {
|
|
77
|
-
type: 'dispatcher.tick-failure-persistent'
|
|
78
|
-
run: string
|
|
79
|
-
firstFailure: string
|
|
80
|
-
error: string
|
|
81
|
-
episodeSeq: number
|
|
82
|
-
observedAt: string
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** Incremental journal projection. Episode starts survive run anchors and
|
|
86
|
-
* repeated evidence can strengthen, but never postpone, an existing deadline. */
|
|
87
|
-
export class RepositoryAttention {
|
|
88
|
-
private sources = new Map<
|
|
89
|
-
string,
|
|
90
|
-
{
|
|
91
|
-
event: Degraded
|
|
92
|
-
deadline: number
|
|
93
|
-
dependencyLimited: boolean
|
|
94
|
-
requiresRetry: boolean
|
|
95
|
-
retryObserved: boolean
|
|
96
|
-
}
|
|
97
|
-
>()
|
|
98
|
-
private failure: Failed | undefined
|
|
99
|
-
private tickStarts = new Map<string, number>()
|
|
100
|
-
|
|
101
|
-
private sourceDefersTick(now: number): boolean {
|
|
102
|
-
return [...this.sources.values()].some(
|
|
103
|
-
(source) => now < source.deadline || (source.requiresRetry && !source.retryObserved),
|
|
104
|
-
)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/** Crossing resetAt alone does not prove persistence: the dispatcher must
|
|
108
|
-
* finish a tick that could actually retry after the provider cooldown. */
|
|
109
|
-
private observeRetry(run: string): void {
|
|
110
|
-
const startedAt = this.tickStarts.get(run)
|
|
111
|
-
this.tickStarts.delete(run)
|
|
112
|
-
if (startedAt === undefined) return
|
|
113
|
-
for (const source of this.sources.values()) {
|
|
114
|
-
if (startedAt >= source.deadline) source.retryObserved = true
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
fold(event: RepositoryEvent): void {
|
|
118
|
-
switch (event.type) {
|
|
119
|
-
case 'dispatcher.ticket-source-degraded': {
|
|
120
|
-
const existing = this.sources.get(event.payload.source)
|
|
121
|
-
const deadline =
|
|
122
|
-
'resetAt' in event.payload
|
|
123
|
-
? Date.parse(event.payload.resetAt)
|
|
124
|
-
: Date.parse(event.ts) + 15 * 60_000
|
|
125
|
-
if (existing) {
|
|
126
|
-
existing.deadline = Math.min(existing.deadline, deadline)
|
|
127
|
-
existing.requiresRetry ||= 'resetAt' in event.payload
|
|
128
|
-
} else
|
|
129
|
-
this.sources.set(event.payload.source, {
|
|
130
|
-
event,
|
|
131
|
-
deadline,
|
|
132
|
-
dependencyLimited: false,
|
|
133
|
-
requiresRetry: 'resetAt' in event.payload,
|
|
134
|
-
retryObserved: false,
|
|
135
|
-
})
|
|
136
|
-
// Cooldown-driven failures are not an independent dispatcher outage.
|
|
137
|
-
if (this.sourceDefersTick(Date.parse(event.ts))) this.failure = undefined
|
|
138
|
-
break
|
|
139
|
-
}
|
|
140
|
-
case 'dispatcher.ticket-source-dependency-limited': {
|
|
141
|
-
const source = this.sources.get(event.payload.source)
|
|
142
|
-
if (source) source.dependencyLimited = true
|
|
143
|
-
break
|
|
144
|
-
}
|
|
145
|
-
case 'dispatcher.ticket-source-recovered':
|
|
146
|
-
this.sources.delete(event.payload.source)
|
|
147
|
-
break
|
|
148
|
-
case 'dispatcher.run-started':
|
|
149
|
-
this.failure = undefined
|
|
150
|
-
this.tickStarts.clear()
|
|
151
|
-
break
|
|
152
|
-
case 'dispatcher.run-stopped':
|
|
153
|
-
this.tickStarts.delete(event.payload.run)
|
|
154
|
-
if (this.failure?.payload.run === event.payload.run) this.failure = undefined
|
|
155
|
-
break
|
|
156
|
-
case 'dispatcher.tick-started':
|
|
157
|
-
this.tickStarts.set(event.payload.run, Date.parse(event.ts))
|
|
158
|
-
break
|
|
159
|
-
case 'dispatcher.tick-completed':
|
|
160
|
-
this.observeRetry(event.payload.run)
|
|
161
|
-
if (this.failure?.payload.run === event.payload.run) this.failure = undefined
|
|
162
|
-
break
|
|
163
|
-
case 'dispatcher.tick-failed':
|
|
164
|
-
this.observeRetry(event.payload.run)
|
|
165
|
-
if (this.sourceDefersTick(Date.parse(event.ts))) this.failure = undefined
|
|
166
|
-
else if (this.failure?.payload.run !== event.payload.run) this.failure = event
|
|
167
|
-
break
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
pending(now: Date): { identity: string; observation: PersistenceObservation }[] {
|
|
171
|
-
const results: { identity: string; observation: PersistenceObservation }[] = []
|
|
172
|
-
for (const {
|
|
173
|
-
event,
|
|
174
|
-
deadline,
|
|
175
|
-
dependencyLimited,
|
|
176
|
-
requiresRetry,
|
|
177
|
-
retryObserved,
|
|
178
|
-
} of this.sources.values()) {
|
|
179
|
-
if (now.getTime() < deadline || (requiresRetry && !retryObserved)) continue
|
|
180
|
-
results.push({
|
|
181
|
-
identity: `source:${event.seq}`,
|
|
182
|
-
observation: {
|
|
183
|
-
type: 'dispatcher.ticket-source-persistent',
|
|
184
|
-
...event.payload,
|
|
185
|
-
episodeSeq: event.seq,
|
|
186
|
-
observedAt: now.toISOString(),
|
|
187
|
-
dependencyLimited,
|
|
188
|
-
},
|
|
189
|
-
})
|
|
190
|
-
}
|
|
191
|
-
const event = this.failure
|
|
192
|
-
if (
|
|
193
|
-
event &&
|
|
194
|
-
!this.sourceDefersTick(now.getTime()) &&
|
|
195
|
-
now.getTime() >= Date.parse(event.ts) + 5 * 60_000
|
|
196
|
-
)
|
|
197
|
-
results.push({
|
|
198
|
-
identity: `tick:${event.seq}`,
|
|
199
|
-
observation: {
|
|
200
|
-
type: 'dispatcher.tick-failure-persistent',
|
|
201
|
-
...event.payload,
|
|
202
|
-
firstFailure: event.ts,
|
|
203
|
-
episodeSeq: event.seq,
|
|
204
|
-
observedAt: now.toISOString(),
|
|
205
|
-
},
|
|
206
|
-
})
|
|
207
|
-
return results
|
|
208
|
-
}
|
|
209
|
-
}
|
|
63
|
+
export { RepositoryAttention, type PersistenceObservation } from '../kernel/repository-attention'
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readBuildFacts } from '../store/reduced-reads'
|
|
1
2
|
/**
|
|
2
3
|
* Durable operator controls for one build.
|
|
3
4
|
*
|
|
@@ -9,12 +10,7 @@
|
|
|
9
10
|
import type { AbEvent } from '../events/catalog'
|
|
10
11
|
import type { PinnedAsset } from '../events/payloads'
|
|
11
12
|
import { humanActor, KERNEL, type Via } from '../events/envelope'
|
|
12
|
-
import {
|
|
13
|
-
reduceBuild,
|
|
14
|
-
discardInFlight,
|
|
15
|
-
type BuildState,
|
|
16
|
-
type OpenEscalation,
|
|
17
|
-
} from '../kernel/reducer'
|
|
13
|
+
import { discardInFlight, type BuildState, type OpenEscalation } from '../kernel/reducer'
|
|
18
14
|
import type { ArtifactRef, BuildOutcome, BuildStatus, TicketRef } from '../ontology'
|
|
19
15
|
import type { Exec } from '../ports/workspace/git-worktree'
|
|
20
16
|
import { specConformance } from '../spec-standard'
|
|
@@ -276,7 +272,7 @@ async function appendConsentCommand(
|
|
|
276
272
|
via?: Via,
|
|
277
273
|
): Promise<BuildControlResult> {
|
|
278
274
|
while (true) {
|
|
279
|
-
const state =
|
|
275
|
+
const state = (await readBuildFacts(store, slug)).value.state
|
|
280
276
|
activeState(slug, state)
|
|
281
277
|
const command =
|
|
282
278
|
action === 'toggle-auto-merge'
|
|
@@ -464,6 +460,7 @@ export interface ControlBuildOpts {
|
|
|
464
460
|
via?: Via
|
|
465
461
|
action: BuildControlAction
|
|
466
462
|
/** Injected only for --revise-spec-from-ticket. */
|
|
463
|
+
prepareTicketAssets?: (ref: TicketRef, store: BuildStore, repo: string) => Promise<PinnedAsset[]>
|
|
467
464
|
readTicketBody?: (ref: TicketRef) => Promise<string>
|
|
468
465
|
}
|
|
469
466
|
|
|
@@ -483,8 +480,8 @@ export async function controlBuild(opts: ControlBuildOpts): Promise<BuildControl
|
|
|
483
480
|
)
|
|
484
481
|
}
|
|
485
482
|
|
|
486
|
-
const events = await opts.store
|
|
487
|
-
const state =
|
|
483
|
+
const events = (await readBuildFacts(opts.store, opts.slug)).value
|
|
484
|
+
const state = events.state
|
|
488
485
|
const user = opts.user?.trim() || buildControlUser(opts.env ?? {})
|
|
489
486
|
|
|
490
487
|
if (opts.action.kind === 'discard') {
|
|
@@ -494,7 +491,7 @@ export async function controlBuild(opts: ControlBuildOpts): Promise<BuildControl
|
|
|
494
491
|
`build "${opts.slug}" cannot be discarded (status: ${state.status}); discard requires queued`,
|
|
495
492
|
)
|
|
496
493
|
}
|
|
497
|
-
const existing = events.
|
|
494
|
+
const existing = events.latestDiscard
|
|
498
495
|
if (existing !== undefined) {
|
|
499
496
|
return { kind: 'command', slug: opts.slug, command: 'discard', event: existing }
|
|
500
497
|
}
|
|
@@ -503,7 +500,7 @@ export async function controlBuild(opts: ControlBuildOpts): Promise<BuildControl
|
|
|
503
500
|
|
|
504
501
|
if (opts.action.kind === 'abort') {
|
|
505
502
|
if (state.status !== 'queued') activeState(opts.slug, state)
|
|
506
|
-
const existing = events.
|
|
503
|
+
const existing = events.latestAbort
|
|
507
504
|
if (existing !== undefined) {
|
|
508
505
|
return { kind: 'command', slug: opts.slug, command: 'abort', event: existing }
|
|
509
506
|
}
|
|
@@ -624,7 +621,15 @@ export async function controlBuild(opts: ControlBuildOpts): Promise<BuildControl
|
|
|
624
621
|
)
|
|
625
622
|
}
|
|
626
623
|
body = await opts.readTicketBody(record.ticket)
|
|
627
|
-
|
|
624
|
+
if (record.ticket.source === 'hosted' && !opts.prepareTicketAssets) {
|
|
625
|
+
throw new BuildControlError(
|
|
626
|
+
'ticket-unavailable',
|
|
627
|
+
'hosted ticket asset preparation is unavailable',
|
|
628
|
+
)
|
|
629
|
+
}
|
|
630
|
+
assets = opts.prepareTicketAssets
|
|
631
|
+
? await opts.prepareTicketAssets(record.ticket, opts.store, record.repo)
|
|
632
|
+
: await samplePinnedAssets(opts.store, record.repo, record.ticket.id)
|
|
628
633
|
}
|
|
629
634
|
const conformance = specConformance(body)
|
|
630
635
|
if (!conformance.conforms) {
|
|
@@ -649,7 +654,7 @@ export async function controlBuild(opts: ControlBuildOpts): Promise<BuildControl
|
|
|
649
654
|
|
|
650
655
|
// Re-read at the last durable moment. The PR janitor and abort
|
|
651
656
|
// acknowledgement may still write while a blocked build is parked.
|
|
652
|
-
const freshState =
|
|
657
|
+
const freshState = (await readBuildFacts(opts.store, opts.slug)).value.state
|
|
653
658
|
assertStillActive(opts.slug, freshState)
|
|
654
659
|
const decision = decideRevise(opts.slug, freshState, captured)
|
|
655
660
|
if (decision.kind === 'refuse') throwRefusal(decision)
|
|
@@ -692,7 +697,7 @@ export async function controlBuild(opts: ControlBuildOpts): Promise<BuildControl
|
|
|
692
697
|
payload: {},
|
|
693
698
|
})
|
|
694
699
|
}
|
|
695
|
-
const after =
|
|
700
|
+
const after = (await readBuildFacts(opts.store, opts.slug)).value.state
|
|
696
701
|
const signal = terminalSignal(after)
|
|
697
702
|
return {
|
|
698
703
|
kind: 'answered',
|
|
@@ -720,7 +725,7 @@ export async function controlBuild(opts: ControlBuildOpts): Promise<BuildControl
|
|
|
720
725
|
const initial = dismissPartition(state, captured)
|
|
721
726
|
if (initial.eligible.length === 0) throw noCitedFindings(opts.slug, initialOpen)
|
|
722
727
|
|
|
723
|
-
const freshState =
|
|
728
|
+
const freshState = (await readBuildFacts(opts.store, opts.slug)).value.state
|
|
724
729
|
assertStillActive(opts.slug, freshState)
|
|
725
730
|
if (hasPendingAbort(freshState)) throw abortPending(opts.slug)
|
|
726
731
|
const freshOpen = selectedOpen(freshState, captured)
|
|
@@ -748,7 +753,7 @@ export async function controlBuild(opts: ControlBuildOpts): Promise<BuildControl
|
|
|
748
753
|
payload: {},
|
|
749
754
|
})
|
|
750
755
|
}
|
|
751
|
-
const after =
|
|
756
|
+
const after = (await readBuildFacts(opts.store, opts.slug)).value.state
|
|
752
757
|
const signal = terminalSignal(after)
|
|
753
758
|
return {
|
|
754
759
|
kind: 'answered',
|
|
@@ -862,6 +867,7 @@ export interface AbBuildControlOpts {
|
|
|
862
867
|
/** Explicit `--store`; selection remains --store > AB_STORE > repo-local. */
|
|
863
868
|
storeRef?: string
|
|
864
869
|
/** Injected only for --revise-spec-from-ticket. */
|
|
870
|
+
prepareTicketAssets?: (ref: TicketRef, store: BuildStore, repo: string) => Promise<PinnedAsset[]>
|
|
865
871
|
readTicketBody?: (ref: TicketRef) => Promise<string>
|
|
866
872
|
/** Injectable adapter seam for unit tests. */
|
|
867
873
|
openStore?: StoreOpener
|
|
@@ -881,6 +887,7 @@ export async function abBuildControl(opts: AbBuildControlOpts): Promise<BuildCon
|
|
|
881
887
|
env: opts.env,
|
|
882
888
|
action: opts.action,
|
|
883
889
|
...(opts.readTicketBody !== undefined ? { readTicketBody: opts.readTicketBody } : {}),
|
|
890
|
+
...(opts.prepareTicketAssets ? { prepareTicketAssets: opts.prepareTicketAssets } : {}),
|
|
884
891
|
}),
|
|
885
892
|
)
|
|
886
893
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readBuildFacts } from '../store/reduced-reads'
|
|
1
2
|
/**
|
|
2
3
|
* Repository-wide quiescence: one keypress that parks every running build and
|
|
3
4
|
* stops intake, and one that reverses it.
|
|
@@ -59,7 +60,7 @@
|
|
|
59
60
|
* what the operator sees, and a unit test pins that agreement.
|
|
60
61
|
*/
|
|
61
62
|
import { humanActor, type Actor, type Via } from '../events/envelope'
|
|
62
|
-
import {
|
|
63
|
+
import type { BuildState } from '../kernel/reducer'
|
|
63
64
|
import type { Exec } from '../ports/workspace/git-worktree'
|
|
64
65
|
import type { BuildRecord, BuildStore } from '../store/types'
|
|
65
66
|
import { BuildControlError, buildControlUser } from './build-control'
|
|
@@ -132,7 +133,7 @@ async function requestOne(
|
|
|
132
133
|
direction: BulkDirection,
|
|
133
134
|
): Promise<boolean> {
|
|
134
135
|
while (true) {
|
|
135
|
-
const state =
|
|
136
|
+
const state = (await readBuildFacts(store, slug)).value.state
|
|
136
137
|
if (!eligible(state, direction)) return false
|
|
137
138
|
// The two branches are written out rather than selecting the event type
|
|
138
139
|
// into a variable so `appendIfCurrent`'s `T extends EventType` infers a
|