@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defrex/autobuild",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Agent-driven software delivery from groomed ticket to merged PR",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -37,10 +37,15 @@
|
|
|
37
37
|
"import": "./packages/core/src/store/remote/index.ts",
|
|
38
38
|
"default": "./packages/core/src/store/remote/index.ts"
|
|
39
39
|
},
|
|
40
|
-
"./
|
|
41
|
-
"types": "./packages/core/src/
|
|
42
|
-
"import": "./packages/core/src/
|
|
43
|
-
"default": "./packages/core/src/
|
|
40
|
+
"./hosted-tickets": {
|
|
41
|
+
"types": "./packages/core/src/ports/tickets/hosted-index.ts",
|
|
42
|
+
"import": "./packages/core/src/ports/tickets/hosted-index.ts",
|
|
43
|
+
"default": "./packages/core/src/ports/tickets/hosted-index.ts"
|
|
44
|
+
},
|
|
45
|
+
"./testing": {
|
|
46
|
+
"types": "./packages/core/src/testing/index.ts",
|
|
47
|
+
"import": "./packages/core/src/testing/index.ts",
|
|
48
|
+
"default": "./packages/core/src/testing/index.ts"
|
|
44
49
|
},
|
|
45
50
|
"./operator-presentation": {
|
|
46
51
|
"types": "./packages/core/src/operator/presentation.ts",
|
|
@@ -52,11 +57,6 @@
|
|
|
52
57
|
"import": "./packages/core/src/ports/tickets/update.ts",
|
|
53
58
|
"default": "./packages/core/src/ports/tickets/update.ts"
|
|
54
59
|
},
|
|
55
|
-
"./remote-tickets": {
|
|
56
|
-
"types": "./packages/core/src/ports/tickets/remote-index.ts",
|
|
57
|
-
"import": "./packages/core/src/ports/tickets/remote-index.ts",
|
|
58
|
-
"default": "./packages/core/src/ports/tickets/remote-index.ts"
|
|
59
|
-
},
|
|
60
60
|
"./origin": {
|
|
61
61
|
"types": "./packages/core/src/kernel/origin.ts",
|
|
62
62
|
"import": "./packages/core/src/kernel/origin.ts",
|
|
@@ -76,12 +76,32 @@
|
|
|
76
76
|
"types": "./packages/core/src/ports/workspace/distribution-archive.ts",
|
|
77
77
|
"import": "./packages/core/src/ports/workspace/distribution-archive.ts",
|
|
78
78
|
"default": "./packages/core/src/ports/workspace/distribution-archive.ts"
|
|
79
|
+
},
|
|
80
|
+
"./operator": {
|
|
81
|
+
"types": "./packages/core/src/operator/index.ts",
|
|
82
|
+
"import": "./packages/core/src/operator/index.ts",
|
|
83
|
+
"default": "./packages/core/src/operator/index.ts"
|
|
84
|
+
},
|
|
85
|
+
"./operator-sandbox-host": {
|
|
86
|
+
"types": "./packages/core/src/operator/sandbox-host.ts",
|
|
87
|
+
"import": "./packages/core/src/operator/sandbox-host.ts",
|
|
88
|
+
"default": "./packages/core/src/operator/sandbox-host.ts"
|
|
79
89
|
}
|
|
80
90
|
},
|
|
81
91
|
"files": [
|
|
82
92
|
"bin",
|
|
83
93
|
"packages/core/src",
|
|
84
94
|
"!packages/core/src/**/*.test.ts",
|
|
95
|
+
"!packages/core/src/**/*.test.tsx",
|
|
96
|
+
"!packages/core/src/**/*.spec.ts",
|
|
97
|
+
"!packages/core/src/**/*.spec.tsx",
|
|
98
|
+
"!packages/core/src/testing/store-failures.ts",
|
|
99
|
+
"!packages/core/src/testing/packed-install.ts",
|
|
100
|
+
"!packages/core/src/cli/testkit.ts",
|
|
101
|
+
"!packages/core/src/cli/store-opening.contract.ts",
|
|
102
|
+
"!packages/core/src/ports/runner/live-contract-fixture.ts",
|
|
103
|
+
"!packages/core/src/cli/dashboard/frame-image.ts",
|
|
104
|
+
"!packages/core/src/markdown.ts",
|
|
85
105
|
"skills",
|
|
86
106
|
"templates",
|
|
87
107
|
"LICENSE",
|
|
@@ -90,24 +110,25 @@
|
|
|
90
110
|
"docs"
|
|
91
111
|
],
|
|
92
112
|
"scripts": {
|
|
93
|
-
"dev": "bun run --bun next dev",
|
|
94
113
|
"dev:cli": "bun --hot --no-clear-screen bin/ab-dev.ts",
|
|
95
|
-
"build": "bun run --bun next build",
|
|
96
|
-
"deploy:build": "bun packages/hosted-store-service/src/bin.ts pack-distribution && bun run postgres:migrate && bun run build && bun tools/ship-packed-distribution.ts",
|
|
97
|
-
"start": "bun run --bun next start",
|
|
98
|
-
"hosted-store": "bun server.ts",
|
|
99
114
|
"capture:dashboard": "bun tools/dashboard-capture.ts",
|
|
100
|
-
"capture:web-dashboard": "bun
|
|
115
|
+
"capture:web-dashboard": "bun packages/web-dashboard-capture/src/web-dashboard-capture.tsx",
|
|
116
|
+
"capture:website": "bun tools/website-capture.ts",
|
|
117
|
+
"website:dev": "bun packages/website/src/dev.ts",
|
|
118
|
+
"website:build": "bun packages/website/src/build.ts",
|
|
101
119
|
"capture:readme-headline": "bun tools/readme-headline.ts",
|
|
120
|
+
"capture:website-hero": "bun tools/website-hero.ts",
|
|
121
|
+
"capture:website-og": "bun tools/website-og.ts",
|
|
102
122
|
"release": "bun tools/release.ts",
|
|
103
123
|
"postgres:migrate": "bun packages/postgres-store/src/bin.ts migrate",
|
|
104
|
-
"check": "bun tools/biome-check.ts && bun tools/product-name-check.ts && bun tools/docs-asset-check.ts && bun tools/readme-headline.ts --check && bun tools/workspace-manifest-check.ts",
|
|
124
|
+
"check": "bun tools/biome-check.ts && bun tools/product-name-check.ts && bun tools/docs-asset-check.ts && bun tools/skill-docs-asset-check.ts && bun tools/readme-headline.ts --check && bun tools/website-hero.ts --check && bun tools/workspace-manifest-check.ts && bun tools/package-boundary-check.ts && bun tools/bin-mode-check.ts && bun tools/publish-contents-check.ts && bun tools/publish-imports-check.ts",
|
|
105
125
|
"format:staged": "git-format-staged --formatter './node_modules/.bin/biome format --stdin-file-path {}' '*.js' '*.jsx' '*.ts' '*.tsx' '*.json' '*.jsonc' '*.css' '*.graphql' '*.gql'",
|
|
106
126
|
"prepare": "husky",
|
|
107
|
-
"test": "bun test packages/*/src tools bin app",
|
|
127
|
+
"test": "bun test packages/*/src tools bin packages/hosted-store-service/app",
|
|
108
128
|
"typecheck": "tsc --noEmit"
|
|
109
129
|
},
|
|
110
130
|
"dependencies": {
|
|
131
|
+
"@ai-sdk/gateway": "4.0.82",
|
|
111
132
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
112
133
|
"@vercel/sandbox": "^3.1.0",
|
|
113
134
|
"ai": "^7.0.101",
|
|
@@ -118,18 +139,19 @@
|
|
|
118
139
|
},
|
|
119
140
|
"devDependencies": {
|
|
120
141
|
"@biomejs/biome": "2.5.5",
|
|
142
|
+
"@defrex/autobuild-postgres-store": "0.9.0",
|
|
143
|
+
"@defrex/autobuild-vercel-sandbox": "0.9.0",
|
|
121
144
|
"@resvg/resvg-js": "2.6.2",
|
|
122
145
|
"@types/bun": "^1.3.14",
|
|
123
146
|
"@types/node": "^24.0.0",
|
|
124
|
-
"@types/pg": "8.16.0",
|
|
125
|
-
"@types/react": "19.2.14",
|
|
126
|
-
"@types/react-dom": "19.2.3",
|
|
127
147
|
"@types/semver": "^7.7.1",
|
|
128
148
|
"dejavu-fonts-ttf": "2.37.0",
|
|
129
149
|
"drizzle-kit": "^0.31.10",
|
|
130
150
|
"git-format-staged": "4.0.1",
|
|
131
151
|
"husky": "9.1.7",
|
|
132
|
-
"typescript": "5.9.3"
|
|
133
|
-
|
|
152
|
+
"typescript": "5.9.3"
|
|
153
|
+
},
|
|
154
|
+
"patchedDependencies": {
|
|
155
|
+
"better-auth@1.4.18": "patches/better-auth@1.4.18.patch"
|
|
134
156
|
}
|
|
135
157
|
}
|
|
@@ -9,10 +9,11 @@ import { basename, dirname, resolve } from 'node:path'
|
|
|
9
9
|
import { agentActor } from '../events/envelope'
|
|
10
10
|
import { prAttachmentSchema } from '../ontology'
|
|
11
11
|
import type { Exec } from '../ports/workspace/git-worktree'
|
|
12
|
-
import type { Artifact, ArtifactMeta, BuildStore } from '../store/types'
|
|
12
|
+
import type { Artifact, ArtifactMeta, BuildStore, RepositoryArtifact } from '../store/types'
|
|
13
13
|
import type { CliEnv } from './env'
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
14
|
+
import { resolveAmbientReadSession } from './env'
|
|
15
|
+
import { buildInRepository, streamInRepository } from './repo-state'
|
|
16
|
+
import { withAmbientReadStore, withSessionlessStore, type StoreOpener } from './store-opening'
|
|
16
17
|
|
|
17
18
|
export interface ArtifactDeps {
|
|
18
19
|
store: BuildStore
|
|
@@ -174,9 +175,132 @@ export async function artifactDownload(
|
|
|
174
175
|
)
|
|
175
176
|
}
|
|
176
177
|
|
|
177
|
-
const outputPath =
|
|
178
|
-
await mkdir(dirname(outputPath), { recursive: true })
|
|
179
|
-
await writeFile(outputPath, artifact.content)
|
|
178
|
+
const outputPath = await writeArtifactBytes(opts.targetRepo, opts.outputPath, artifact.content)
|
|
180
179
|
return { artifact, outputPath }
|
|
181
180
|
})
|
|
182
181
|
}
|
|
182
|
+
|
|
183
|
+
/** Shared download tail: exact bytes under the resolved output path (parent
|
|
184
|
+
* directories created), used by both the build-scoped and stream-addressed
|
|
185
|
+
* forms so neither can drift on byte handling. */
|
|
186
|
+
async function writeArtifactBytes(
|
|
187
|
+
targetRepo: string,
|
|
188
|
+
outputPath: string,
|
|
189
|
+
content: Uint8Array,
|
|
190
|
+
): Promise<string> {
|
|
191
|
+
const resolved = resolve(targetRepo, outputPath)
|
|
192
|
+
await mkdir(dirname(resolved), { recursive: true })
|
|
193
|
+
await writeFile(resolved, content)
|
|
194
|
+
return resolved
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export interface ArtifactDownloadStreamOpts {
|
|
198
|
+
/** Current checkout; repository identity resolves to its main worktree. */
|
|
199
|
+
targetRepo: string
|
|
200
|
+
env: Record<string, string | undefined>
|
|
201
|
+
exec: Exec
|
|
202
|
+
/** `stream:<id>` or `stream:<id>@<rev>`; rev pins the finalized artifact. */
|
|
203
|
+
spec: string
|
|
204
|
+
outputPath: string
|
|
205
|
+
/** Explicit --store; precedence remains flag > AB_STORE > local default. */
|
|
206
|
+
storeRef?: string
|
|
207
|
+
/** Adapter seam for local/remote selection tests. */
|
|
208
|
+
openStore?: StoreOpener
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export interface ArtifactDownloadStreamResult {
|
|
212
|
+
artifact: RepositoryArtifact
|
|
213
|
+
outputPath: string
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Operator retrieval of a finalized, repo-scoped stream artifact (the harvest
|
|
217
|
+
* session analog of the build-scoped form). A stream id is store-assigned and
|
|
218
|
+
* globally unique, so no build or repo argument is needed: the command reads
|
|
219
|
+
* the stream record, checks its scope, then fetches the close-deposited
|
|
220
|
+
* repository artifact.
|
|
221
|
+
*
|
|
222
|
+
* Authorization is the operator-wide read rule only (§8.2): this form requires
|
|
223
|
+
* a sessionless invocation and fails closed under any complete ambient phase
|
|
224
|
+
* identity — repository-scoped targets are never reachable by ambient
|
|
225
|
+
* own-build or own-harvest authority. The check runs here, before any store
|
|
226
|
+
* call, because local harvest-ambient handles would permit own-repo
|
|
227
|
+
* `getRepoArtifact` while remote handles rely on the core to enforce scope. */
|
|
228
|
+
export async function artifactDownloadStream(
|
|
229
|
+
opts: ArtifactDownloadStreamOpts,
|
|
230
|
+
): Promise<ArtifactDownloadStreamResult> {
|
|
231
|
+
const ambient = resolveAmbientReadSession(opts.env)
|
|
232
|
+
if (ambient !== undefined) {
|
|
233
|
+
throw new Error(
|
|
234
|
+
'repo-scoped stream downloads are operator reads (§8.2): ambient phase ' +
|
|
235
|
+
'identity (build or Harvest) never extends to repository-scoped targets — ' +
|
|
236
|
+
'run this command without agent identity markers',
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
const { kind, rev } = parseArtifactSpec(opts.spec)
|
|
240
|
+
const streamId = kind.startsWith('stream:') ? kind.slice('stream:'.length) : ''
|
|
241
|
+
if (streamId.trim() === '') {
|
|
242
|
+
throw new Error(
|
|
243
|
+
`invalid stream ref "${opts.spec}" — expected 'stream:<id>' or 'stream:<id>@<rev>'`,
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
return withSessionlessStore(
|
|
247
|
+
{
|
|
248
|
+
targetRepo: opts.targetRepo,
|
|
249
|
+
env: opts.env,
|
|
250
|
+
exec: opts.exec,
|
|
251
|
+
...(opts.storeRef !== undefined ? { storeRef: opts.storeRef } : {}),
|
|
252
|
+
...(opts.openStore !== undefined ? { openStore: opts.openStore } : {}),
|
|
253
|
+
},
|
|
254
|
+
async (context) => {
|
|
255
|
+
const record = await context.store.getStream(streamId)
|
|
256
|
+
if (record === null) {
|
|
257
|
+
throw new Error(
|
|
258
|
+
`no stream "${streamId}" in this store — run 'ab harvest status' to find ` +
|
|
259
|
+
'session stream ids, or pass --store <ref>',
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
if (record.scope.kind === 'build') {
|
|
263
|
+
throw new Error(
|
|
264
|
+
`stream "${streamId}" is build-scoped — download it with ` +
|
|
265
|
+
`'ab artifact download ${record.scope.build} stream:${streamId}'`,
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
if (record.scope.kind === 'session') {
|
|
269
|
+
throw new Error(
|
|
270
|
+
`stream "${streamId}" is session-scoped — read it through the operator ` +
|
|
271
|
+
`session stream surface (GET /sessions/${record.scope.session}/streams/${streamId})`,
|
|
272
|
+
)
|
|
273
|
+
}
|
|
274
|
+
// Origin-aware membership, mirroring the build-scoped path above.
|
|
275
|
+
// Unlike a BuildRecord, StreamScope carries no `repoOrigin`, so a legacy
|
|
276
|
+
// physical-path scope.repo stays rejected here (see
|
|
277
|
+
// `streamInRepository` for the documented AUT-314-class divergence).
|
|
278
|
+
if (!streamInRepository(record.scope.repo, context.repo)) {
|
|
279
|
+
throw new Error(
|
|
280
|
+
`stream "${streamId}" belongs to repository "${record.scope.repo}", not "${context.repo}"`,
|
|
281
|
+
)
|
|
282
|
+
}
|
|
283
|
+
if (record.status === 'open') {
|
|
284
|
+
throw new Error(
|
|
285
|
+
`stream "${streamId}" is open — its finalized artifact exists only after ` +
|
|
286
|
+
'the stream closes (§7.6)',
|
|
287
|
+
)
|
|
288
|
+
}
|
|
289
|
+
const artifact = await context.store.getRepoArtifact(record.scope.repo, kind, rev)
|
|
290
|
+
if (artifact === null) {
|
|
291
|
+
const available = await context.store.listRepoArtifacts(record.scope.repo, kind)
|
|
292
|
+
const refs = available.map((meta) => `${meta.kind}@${meta.revision}`)
|
|
293
|
+
throw new Error(
|
|
294
|
+
`no "${kind}" artifact${rev !== undefined ? ` at rev ${rev}` : ''} in ` +
|
|
295
|
+
`repository "${record.scope.repo}" — available refs: ${refs.join(', ') || '(none)'}`,
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
const outputPath = await writeArtifactBytes(
|
|
299
|
+
opts.targetRepo,
|
|
300
|
+
opts.outputPath,
|
|
301
|
+
artifact.content,
|
|
302
|
+
)
|
|
303
|
+
return { artifact, outputPath }
|
|
304
|
+
},
|
|
305
|
+
)
|
|
306
|
+
}
|
|
@@ -165,12 +165,20 @@ export async function runBinary(
|
|
|
165
165
|
// A scoped phase process is composed from the build worktree's immutable
|
|
166
166
|
// configuration just like dispatch. Load and select the forge before opening
|
|
167
167
|
// the store so plugin failures cannot partially execute terminal plumbing.
|
|
168
|
+
// Guest tolerance (AUT-517), always on here: in checkout mode `ab dispatch`
|
|
169
|
+
// already failed startup if a configured plugin did not resolve, and
|
|
170
|
+
// origin-mode phase processes only run inside guests — so an unresolvable
|
|
171
|
+
// bare specifier here can only be the provider-plugin case, which a phase
|
|
172
|
+
// process (never constructing workspace providers) may skip.
|
|
168
173
|
let forge: Awaited<ReturnType<typeof createForge>>
|
|
169
174
|
try {
|
|
170
175
|
const repoRoot = process.cwd()
|
|
171
176
|
const packageRoot = await resolveMainRepo(repoRoot, spawnExec)
|
|
172
177
|
const config = await loadConfig(join(repoRoot, 'autobuild.toml'))
|
|
173
|
-
const plugins = await loadPlugins(config.plugins, repoRoot, {
|
|
178
|
+
const plugins = await loadPlugins(config.plugins, repoRoot, {
|
|
179
|
+
packageRoot,
|
|
180
|
+
guest: true,
|
|
181
|
+
})
|
|
174
182
|
forge = await createForge({
|
|
175
183
|
name: config.forge,
|
|
176
184
|
registry: plugins,
|
|
@@ -7,11 +7,18 @@
|
|
|
7
7
|
* normal runner and dispatcher consume them.
|
|
8
8
|
*/
|
|
9
9
|
import type { AbEvent } from '../events/catalog'
|
|
10
|
+
import type { PinnedAsset } from '../events/payloads'
|
|
10
11
|
import { humanActor, KERNEL, type Via } from '../events/envelope'
|
|
11
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
reduceBuild,
|
|
14
|
+
discardInFlight,
|
|
15
|
+
type BuildState,
|
|
16
|
+
type OpenEscalation,
|
|
17
|
+
} from '../kernel/reducer'
|
|
12
18
|
import type { ArtifactRef, BuildOutcome, BuildStatus, TicketRef } from '../ontology'
|
|
13
19
|
import type { Exec } from '../ports/workspace/git-worktree'
|
|
14
20
|
import { specConformance } from '../spec-standard'
|
|
21
|
+
import { samplePinnedAssets } from '../store/ticket-assets'
|
|
15
22
|
import type { BuildStore } from '../store/types'
|
|
16
23
|
import { withSessionlessStore, type StoreOpener } from './store-opening'
|
|
17
24
|
|
|
@@ -107,6 +114,7 @@ export type BuildControlErrorCode =
|
|
|
107
114
|
| 'missing-authorization'
|
|
108
115
|
| 'no-longer-active'
|
|
109
116
|
| 'abort-pending'
|
|
117
|
+
| 'discard-pending'
|
|
110
118
|
| 'review-round-ceiling-unavailable'
|
|
111
119
|
| 'incompatible-answer-options'
|
|
112
120
|
|
|
@@ -226,6 +234,78 @@ function hasPendingAbort(state: BuildState): boolean {
|
|
|
226
234
|
return state.pendingCommands.some((command) => command.command === 'abort')
|
|
227
235
|
}
|
|
228
236
|
|
|
237
|
+
function discardPending(slug: string): BuildControlError {
|
|
238
|
+
return new BuildControlError(
|
|
239
|
+
'discard-pending',
|
|
240
|
+
`build "${slug}" has an outstanding discard request; auto-merge consent was not recorded. ` +
|
|
241
|
+
'The discard settles only when the build terminalizes — nothing was appended. ' +
|
|
242
|
+
'If the discard raced with runner attachment and you still want the change, ' +
|
|
243
|
+
'merge the PR manually.',
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Record or withdraw per-build auto-merge consent — the write path for
|
|
248
|
+
* `auto-merge-on`, `auto-merge-off`, and `toggle-auto-merge`.
|
|
249
|
+
*
|
|
250
|
+
* Ruling (AUT-445): recording consent on a build with an outstanding discard
|
|
251
|
+
* request is rejected — the fan-out's discard exclusion
|
|
252
|
+
* (`autoMergeDefaultEligible`'s discard clause, AUT-418) applied to explicit
|
|
253
|
+
* consent. Both controls call the same `discardInFlight` predicate from
|
|
254
|
+
* kernel/reducer.ts, so they cannot diverge silently. Withdrawing consent (OFF)
|
|
255
|
+
* is deliberately still allowed: `checkPr` honors revocation before anything
|
|
256
|
+
* else, so an OFF strictly shrinks the merge set, and it is the only way to
|
|
257
|
+
* withdraw consent recorded before the discard (a fan-out or claim-time seed) —
|
|
258
|
+
* refusing it would leave that discard-contradicting consent driving the merge
|
|
259
|
+
* anyway. The fan-out is direction-blind because it is a queue cleaner that
|
|
260
|
+
* withdraws nothing that survives the settlement; this guard is directional on
|
|
261
|
+
* purpose, and the divergence is stated here rather than left accidental.
|
|
262
|
+
*
|
|
263
|
+
* The guard runs inside a compare-and-append loop (the `appendIfCurrent` idiom
|
|
264
|
+
* the fan-out's dispatcher loop and `bulkControlRepository` already use), so a
|
|
265
|
+
* discard request that lands between the guard's read and this append cannot
|
|
266
|
+
* slip past it: the CAS misses, the loop re-reduces the log, and the guard
|
|
267
|
+
* fires on the fresh state. The toggle's direction is re-resolved from the
|
|
268
|
+
* re-read state each attempt. `activeState` runs before the guard on every
|
|
269
|
+
* attempt: a queued build (where the discard normally sits) still reports the
|
|
270
|
+
* existing `inactive` error. */
|
|
271
|
+
async function appendConsentCommand(
|
|
272
|
+
store: BuildStore,
|
|
273
|
+
slug: string,
|
|
274
|
+
user: string,
|
|
275
|
+
action: 'auto-merge-on' | 'auto-merge-off' | 'toggle-auto-merge',
|
|
276
|
+
via?: Via,
|
|
277
|
+
): Promise<BuildControlResult> {
|
|
278
|
+
while (true) {
|
|
279
|
+
const state = reduceBuild(await store.getEvents(slug))
|
|
280
|
+
activeState(slug, state)
|
|
281
|
+
const command =
|
|
282
|
+
action === 'toggle-auto-merge'
|
|
283
|
+
? state.autoMerge.requested
|
|
284
|
+
? 'auto-merge-off'
|
|
285
|
+
: 'auto-merge-on'
|
|
286
|
+
: action
|
|
287
|
+
if (command === 'auto-merge-on' && discardInFlight(state)) throw discardPending(slug)
|
|
288
|
+
const actor = humanActor(user, via)
|
|
289
|
+
// Written out rather than selected into a variable so `appendIfCurrent`'s
|
|
290
|
+
// `T extends EventType` infers a concrete payload type in each branch.
|
|
291
|
+
const appended =
|
|
292
|
+
command === 'auto-merge-on'
|
|
293
|
+
? await store.appendIfCurrent(slug, state.lastSeq, {
|
|
294
|
+
actor,
|
|
295
|
+
type: 'build.auto-merge-requested',
|
|
296
|
+
payload: {},
|
|
297
|
+
})
|
|
298
|
+
: await store.appendIfCurrent(slug, state.lastSeq, {
|
|
299
|
+
actor,
|
|
300
|
+
type: 'build.auto-merge-cancelled',
|
|
301
|
+
payload: {},
|
|
302
|
+
})
|
|
303
|
+
if (appended !== null) return { kind: 'command', slug, command, event: appended }
|
|
304
|
+
// The stream advanced under us (a racing discard, a fan-out write, a
|
|
305
|
+
// runner acknowledgement): loop to re-read and re-check before writing.
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
229
309
|
function abortPending(slug: string): BuildControlError {
|
|
230
310
|
return new BuildControlError(
|
|
231
311
|
'abort-pending',
|
|
@@ -254,6 +334,9 @@ export type ReviseDecision =
|
|
|
254
334
|
| {
|
|
255
335
|
kind: 'complete'
|
|
256
336
|
artifact: ArtifactRef
|
|
337
|
+
/** The assets sampled with the authorization; absent for a supplied body
|
|
338
|
+
* and for authorizations recorded before assets were pinned. */
|
|
339
|
+
assets?: PinnedAsset[]
|
|
257
340
|
open: OpenEscalation[]
|
|
258
341
|
escalationSeq: number
|
|
259
342
|
}
|
|
@@ -289,7 +372,13 @@ export function decideRevise(
|
|
|
289
372
|
}
|
|
290
373
|
}
|
|
291
374
|
const escalationSeq = Math.max(...[...open, ...pending].map((item) => item.seq))
|
|
292
|
-
return {
|
|
375
|
+
return {
|
|
376
|
+
kind: 'complete',
|
|
377
|
+
artifact: authorized.artifact,
|
|
378
|
+
...(authorized.assets !== undefined ? { assets: authorized.assets } : {}),
|
|
379
|
+
open,
|
|
380
|
+
escalationSeq,
|
|
381
|
+
}
|
|
293
382
|
}
|
|
294
383
|
if (open.length === 0) {
|
|
295
384
|
const paused =
|
|
@@ -425,9 +514,14 @@ export async function controlBuild(opts: ControlBuildOpts): Promise<BuildControl
|
|
|
425
514
|
switch (opts.action.kind) {
|
|
426
515
|
case 'pause':
|
|
427
516
|
case 'resume':
|
|
517
|
+
return appendCommand(opts.store, opts.slug, user, opts.action.kind, opts.via)
|
|
518
|
+
|
|
428
519
|
case 'auto-merge-on':
|
|
429
520
|
case 'auto-merge-off':
|
|
430
|
-
|
|
521
|
+
case 'toggle-auto-merge':
|
|
522
|
+
// Consent has its own guarded, compare-and-append write path; the
|
|
523
|
+
// outer `activeState` above only short-circuits the common inactive case.
|
|
524
|
+
return appendConsentCommand(opts.store, opts.slug, user, opts.action.kind, opts.via)
|
|
431
525
|
|
|
432
526
|
case 'dashboard-pause': {
|
|
433
527
|
const pendingPause = state.pendingCommands.some((command) => command.command === 'pause')
|
|
@@ -464,15 +558,6 @@ export async function controlBuild(opts: ControlBuildOpts): Promise<BuildControl
|
|
|
464
558
|
return appendCommand(opts.store, opts.slug, user, 'resume', opts.via)
|
|
465
559
|
}
|
|
466
560
|
|
|
467
|
-
case 'toggle-auto-merge':
|
|
468
|
-
return appendCommand(
|
|
469
|
-
opts.store,
|
|
470
|
-
opts.slug,
|
|
471
|
-
user,
|
|
472
|
-
state.autoMerge.requested ? 'auto-merge-off' : 'auto-merge-on',
|
|
473
|
-
opts.via,
|
|
474
|
-
)
|
|
475
|
-
|
|
476
561
|
case 'answer': {
|
|
477
562
|
const captured =
|
|
478
563
|
opts.action.escalationIds === undefined ? undefined : new Set(opts.action.escalationIds)
|
|
@@ -510,11 +595,16 @@ export async function controlBuild(opts: ControlBuildOpts): Promise<BuildControl
|
|
|
510
595
|
if (initial.kind === 'refuse') throwRefusal(initial)
|
|
511
596
|
|
|
512
597
|
let artifact: ArtifactRef
|
|
598
|
+
// From-ticket revisions re-pin the ticket's assets; a supplied body
|
|
599
|
+
// leaves the pinned set alone (`undefined`). Sampled once, with the
|
|
600
|
+
// body, and persisted on the answers so a retry reuses it.
|
|
601
|
+
let assets: PinnedAsset[] | undefined
|
|
513
602
|
let authorizedEarlier = initial.kind === 'complete'
|
|
514
603
|
if (initial.kind === 'complete') {
|
|
515
604
|
// Completion rule: recorded authorization wins before the caller's
|
|
516
605
|
// lazy body source is opened, regardless of remaining blockers.
|
|
517
606
|
artifact = initial.artifact
|
|
607
|
+
assets = initial.assets
|
|
518
608
|
} else {
|
|
519
609
|
let body: string
|
|
520
610
|
const source = opts.action.resolve.body
|
|
@@ -534,6 +624,7 @@ export async function controlBuild(opts: ControlBuildOpts): Promise<BuildControl
|
|
|
534
624
|
)
|
|
535
625
|
}
|
|
536
626
|
body = await opts.readTicketBody(record.ticket)
|
|
627
|
+
assets = await samplePinnedAssets(opts.store, record.repo, record.ticket.id)
|
|
537
628
|
}
|
|
538
629
|
const conformance = specConformance(body)
|
|
539
630
|
if (!conformance.conforms) {
|
|
@@ -564,6 +655,7 @@ export async function controlBuild(opts: ControlBuildOpts): Promise<BuildControl
|
|
|
564
655
|
if (decision.kind === 'refuse') throwRefusal(decision)
|
|
565
656
|
if (decision.kind === 'complete') {
|
|
566
657
|
artifact = decision.artifact
|
|
658
|
+
assets = decision.assets
|
|
567
659
|
authorizedEarlier = true
|
|
568
660
|
}
|
|
569
661
|
const answer = guidance === '' ? SPEC_REVISION_ANSWER : guidance
|
|
@@ -580,13 +672,18 @@ export async function controlBuild(opts: ControlBuildOpts): Promise<BuildControl
|
|
|
580
672
|
answer,
|
|
581
673
|
resolution: 'revise-spec',
|
|
582
674
|
artifact,
|
|
675
|
+
...(assets !== undefined ? { assets } : {}),
|
|
583
676
|
},
|
|
584
677
|
})
|
|
585
678
|
}
|
|
586
679
|
await opts.store.append(opts.slug, {
|
|
587
680
|
actor: KERNEL,
|
|
588
681
|
type: 'spec.revised',
|
|
589
|
-
payload: {
|
|
682
|
+
payload: {
|
|
683
|
+
artifact,
|
|
684
|
+
escalation: decision.escalationSeq,
|
|
685
|
+
...(assets !== undefined ? { assets } : {}),
|
|
686
|
+
},
|
|
590
687
|
})
|
|
591
688
|
if (alsoPaused) {
|
|
592
689
|
await opts.store.append(opts.slug, {
|
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
} from '../ports/workspace/phase-scratch'
|
|
45
45
|
import type { Exec } from '../ports/workspace/git-worktree'
|
|
46
46
|
import type { BuildStore } from '../store/types'
|
|
47
|
+
import { writeAssetTo } from './ticket-assets'
|
|
47
48
|
import type { CliEnv } from './env'
|
|
48
49
|
|
|
49
50
|
export interface ContextDeps {
|
|
@@ -81,6 +82,16 @@ export interface ContextManifest {
|
|
|
81
82
|
notesPath?: string
|
|
82
83
|
/** relPath (under .ab/) → source artifact ref, or 'derived'. */
|
|
83
84
|
materialized: Record<string, MaterializedEntry>
|
|
85
|
+
/** The ticket assets this build froze (SPEC §6.3), each written to `path`
|
|
86
|
+
* (relative to the workspace). Omitted when the build pinned none. */
|
|
87
|
+
assets?: {
|
|
88
|
+
kind: string
|
|
89
|
+
name: string
|
|
90
|
+
revision: number
|
|
91
|
+
layout: 'file' | 'tree'
|
|
92
|
+
size: number
|
|
93
|
+
path: string
|
|
94
|
+
}[]
|
|
84
95
|
/** From the latest `implement.completed` (§8.3 code-review/verify inputs). */
|
|
85
96
|
commitRange?: CommitRange
|
|
86
97
|
/** Reconcile: `{baseSha}` freshly recorded by this attempt's phase start (§15.7). */
|
|
@@ -276,6 +287,41 @@ export async function buildContext(deps: ContextDeps): Promise<ContextManifest>
|
|
|
276
287
|
}
|
|
277
288
|
}
|
|
278
289
|
|
|
290
|
+
if (inputs.assets === true && state.pinnedAssets.length > 0) {
|
|
291
|
+
// The pin, not the ticket's current state: the revisions `build.created`
|
|
292
|
+
// (or the latest re-pinning `spec.revised`) recorded, fetched by explicit
|
|
293
|
+
// revision so a later attach/replace/remove cannot change what a phase sees.
|
|
294
|
+
const root = locate('assets')
|
|
295
|
+
manifest.assets = []
|
|
296
|
+
for (const pinned of state.pinnedAssets) {
|
|
297
|
+
const asset = await store.getPinnedTicketAsset(
|
|
298
|
+
build,
|
|
299
|
+
pinned.kind,
|
|
300
|
+
pinned.name,
|
|
301
|
+
pinned.revision,
|
|
302
|
+
)
|
|
303
|
+
if (asset === null) {
|
|
304
|
+
throw new Error(
|
|
305
|
+
`pinned ticket asset ${pinned.kind}/${pinned.name} revision ${pinned.revision} is missing from the store`,
|
|
306
|
+
)
|
|
307
|
+
}
|
|
308
|
+
const relPath = `${root}/${pinned.kind}/${pinned.name}`
|
|
309
|
+
const target = join(abDir, relPath)
|
|
310
|
+
// A file asset lands inside its directory as `<filename>`; a tree asset
|
|
311
|
+
// keeps its relative tree, empty directories included.
|
|
312
|
+
await mkdir(target, { recursive: true })
|
|
313
|
+
await writeAssetTo(asset, target)
|
|
314
|
+
manifest.assets.push({
|
|
315
|
+
kind: pinned.kind,
|
|
316
|
+
name: pinned.name,
|
|
317
|
+
revision: pinned.revision,
|
|
318
|
+
layout: pinned.layout,
|
|
319
|
+
size: pinned.size,
|
|
320
|
+
path: `.ab/${relPath}`,
|
|
321
|
+
})
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
279
325
|
if (inputs.spec === true) {
|
|
280
326
|
// §6.3 immutability: the spec a phase sees is the rev the spec.* events
|
|
281
327
|
// anchor (spec.imported/authored/revised → state.specRev), never the
|