@defrex/autobuild 0.8.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.
Files changed (251) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +192 -0
  3. package/SPEC.md +2269 -0
  4. package/bin/ab-build-runner.ts +47 -0
  5. package/bin/ab-dev.ts +27 -0
  6. package/bin/ab-dispatch-kernel.ts +92 -0
  7. package/bin/ab-harvest-runner.ts +29 -0
  8. package/bin/ab-init-probe.ts +19 -0
  9. package/bin/ab-process-group-reaper.ts +32 -0
  10. package/bin/ab.ts +6 -0
  11. package/bin/agent/ab +3 -0
  12. package/docs/answering-blocked-builds.md +89 -0
  13. package/docs/architecture.md +726 -0
  14. package/docs/assets/headline-wide.png +0 -0
  15. package/docs/configuration.md +1623 -0
  16. package/docs/hosted-dispatcher-cutover-validation.md +178 -0
  17. package/docs/hosted-dispatcher.md +204 -0
  18. package/docs/mcp.md +119 -0
  19. package/docs/operator-api.md +186 -0
  20. package/docs/remote-store-protocol.md +972 -0
  21. package/docs/remote-ticket-protocol.md +79 -0
  22. package/docs/setup.md +289 -0
  23. package/docs/spec-standard.md +63 -0
  24. package/docs/ticket-dependencies.md +59 -0
  25. package/docs/vercel-sandbox-rollout-probe.md +7 -0
  26. package/package.json +135 -0
  27. package/packages/core/src/cli/args.ts +74 -0
  28. package/packages/core/src/cli/artifact.ts +182 -0
  29. package/packages/core/src/cli/binary.ts +198 -0
  30. package/packages/core/src/cli/build-control.ts +789 -0
  31. package/packages/core/src/cli/build-progress.ts +55 -0
  32. package/packages/core/src/cli/bulk-control.ts +385 -0
  33. package/packages/core/src/cli/context.ts +470 -0
  34. package/packages/core/src/cli/dashboard/actions.ts +59 -0
  35. package/packages/core/src/cli/dashboard/cells.ts +92 -0
  36. package/packages/core/src/cli/dashboard/composer.ts +230 -0
  37. package/packages/core/src/cli/dashboard/detail.ts +100 -0
  38. package/packages/core/src/cli/dashboard/frame-image.ts +373 -0
  39. package/packages/core/src/cli/dashboard/live.ts +152 -0
  40. package/packages/core/src/cli/dashboard/model.ts +1173 -0
  41. package/packages/core/src/cli/dashboard/poll.ts +196 -0
  42. package/packages/core/src/cli/dashboard/render.ts +1568 -0
  43. package/packages/core/src/cli/dashboard/selection.ts +66 -0
  44. package/packages/core/src/cli/dashboard/session-feed.ts +244 -0
  45. package/packages/core/src/cli/dashboard/session-view.ts +506 -0
  46. package/packages/core/src/cli/dashboard/transcript.ts +108 -0
  47. package/packages/core/src/cli/dev.ts +46 -0
  48. package/packages/core/src/cli/dispatch-frontend.ts +1045 -0
  49. package/packages/core/src/cli/dispatch-process.ts +259 -0
  50. package/packages/core/src/cli/dispatch.ts +3692 -0
  51. package/packages/core/src/cli/dotenv.ts +48 -0
  52. package/packages/core/src/cli/env.ts +206 -0
  53. package/packages/core/src/cli/harvest.ts +698 -0
  54. package/packages/core/src/cli/help.ts +713 -0
  55. package/packages/core/src/cli/init-agent.ts +106 -0
  56. package/packages/core/src/cli/init-validation.ts +583 -0
  57. package/packages/core/src/cli/init.ts +756 -0
  58. package/packages/core/src/cli/installation.ts +333 -0
  59. package/packages/core/src/cli/keyboard.ts +261 -0
  60. package/packages/core/src/cli/main.ts +1399 -0
  61. package/packages/core/src/cli/mcp.ts +279 -0
  62. package/packages/core/src/cli/models.ts +65 -0
  63. package/packages/core/src/cli/observe.ts +55 -0
  64. package/packages/core/src/cli/plugin.ts +227 -0
  65. package/packages/core/src/cli/pr-attachments.ts +138 -0
  66. package/packages/core/src/cli/pr-summary.ts +124 -0
  67. package/packages/core/src/cli/publication-settlement.ts +188 -0
  68. package/packages/core/src/cli/repo-state.ts +213 -0
  69. package/packages/core/src/cli/repository-status.ts +80 -0
  70. package/packages/core/src/cli/self-update.ts +443 -0
  71. package/packages/core/src/cli/status.ts +895 -0
  72. package/packages/core/src/cli/store-opening.contract.ts +289 -0
  73. package/packages/core/src/cli/store-opening.ts +167 -0
  74. package/packages/core/src/cli/store-ref.ts +33 -0
  75. package/packages/core/src/cli/terminal-restore.ts +174 -0
  76. package/packages/core/src/cli/terminal.ts +361 -0
  77. package/packages/core/src/cli/terminals.ts +995 -0
  78. package/packages/core/src/cli/testkit.ts +194 -0
  79. package/packages/core/src/cli/ticket.ts +484 -0
  80. package/packages/core/src/cli/upgrade-agent.ts +180 -0
  81. package/packages/core/src/cli/upgrade-commit.ts +468 -0
  82. package/packages/core/src/cli/upgrade-notice.ts +91 -0
  83. package/packages/core/src/cli/upgrade-progress.ts +110 -0
  84. package/packages/core/src/cli/upgrade.ts +888 -0
  85. package/packages/core/src/cli/wait.ts +587 -0
  86. package/packages/core/src/cli/watch.ts +932 -0
  87. package/packages/core/src/config/live.ts +255 -0
  88. package/packages/core/src/config/load.ts +98 -0
  89. package/packages/core/src/config/roles.ts +438 -0
  90. package/packages/core/src/config/schema.ts +774 -0
  91. package/packages/core/src/distribution.ts +24 -0
  92. package/packages/core/src/events/catalog.ts +201 -0
  93. package/packages/core/src/events/envelope.ts +59 -0
  94. package/packages/core/src/events/payloads.ts +618 -0
  95. package/packages/core/src/events/repository.ts +559 -0
  96. package/packages/core/src/events/sessions.ts +180 -0
  97. package/packages/core/src/harvest/schema.ts +147 -0
  98. package/packages/core/src/ids.ts +45 -0
  99. package/packages/core/src/integration/harness.ts +681 -0
  100. package/packages/core/src/kernel/auto-merge.ts +259 -0
  101. package/packages/core/src/kernel/converge.ts +117 -0
  102. package/packages/core/src/kernel/dispatch-settings.ts +60 -0
  103. package/packages/core/src/kernel/dispatch-status.ts +141 -0
  104. package/packages/core/src/kernel/engine.ts +1047 -0
  105. package/packages/core/src/kernel/harvest.ts +823 -0
  106. package/packages/core/src/kernel/origin.ts +69 -0
  107. package/packages/core/src/kernel/phases.ts +196 -0
  108. package/packages/core/src/kernel/plan-verify-selection.ts +105 -0
  109. package/packages/core/src/kernel/pr-attachments.ts +82 -0
  110. package/packages/core/src/kernel/reducer.ts +752 -0
  111. package/packages/core/src/kernel/stall.ts +155 -0
  112. package/packages/core/src/kernel/verify-applicability.ts +52 -0
  113. package/packages/core/src/markdown.ts +67 -0
  114. package/packages/core/src/ontology.ts +288 -0
  115. package/packages/core/src/open-map.ts +203 -0
  116. package/packages/core/src/operator/annotations.ts +187 -0
  117. package/packages/core/src/operator/client.ts +311 -0
  118. package/packages/core/src/operator/control.ts +117 -0
  119. package/packages/core/src/operator/index.ts +62 -0
  120. package/packages/core/src/operator/presentation.ts +22 -0
  121. package/packages/core/src/operator/protocol.ts +98 -0
  122. package/packages/core/src/operator/query.ts +201 -0
  123. package/packages/core/src/operator/registry.ts +1043 -0
  124. package/packages/core/src/operator/requests.ts +138 -0
  125. package/packages/core/src/operator/sandbox.ts +458 -0
  126. package/packages/core/src/operator/server.ts +616 -0
  127. package/packages/core/src/operator/sessions.ts +223 -0
  128. package/packages/core/src/operator/tickets.ts +177 -0
  129. package/packages/core/src/plugin-sdk/index.ts +174 -0
  130. package/packages/core/src/plugins/contract-entry.ts +74 -0
  131. package/packages/core/src/plugins/contract-env.ts +3 -0
  132. package/packages/core/src/plugins/load.ts +259 -0
  133. package/packages/core/src/plugins/manifest.ts +236 -0
  134. package/packages/core/src/plugins/registry.ts +274 -0
  135. package/packages/core/src/plugins/runtimes.ts +75 -0
  136. package/packages/core/src/ports/forge/contract.ts +258 -0
  137. package/packages/core/src/ports/forge/create.ts +74 -0
  138. package/packages/core/src/ports/forge/fake.ts +488 -0
  139. package/packages/core/src/ports/forge/github-pr-attachments.ts +351 -0
  140. package/packages/core/src/ports/forge/github-transport.ts +352 -0
  141. package/packages/core/src/ports/forge/github.ts +1162 -0
  142. package/packages/core/src/ports/forge/local-git.ts +568 -0
  143. package/packages/core/src/ports/runner/claude.ts +873 -0
  144. package/packages/core/src/ports/runner/cli-stream.ts +90 -0
  145. package/packages/core/src/ports/runner/codex.ts +873 -0
  146. package/packages/core/src/ports/runner/contract.ts +492 -0
  147. package/packages/core/src/ports/runner/fake.ts +213 -0
  148. package/packages/core/src/ports/runner/json-record.ts +2 -0
  149. package/packages/core/src/ports/runner/live-contract-fixture.ts +176 -0
  150. package/packages/core/src/ports/runner/one-shot.ts +29 -0
  151. package/packages/core/src/ports/runner/pi-bridge-core.ts +136 -0
  152. package/packages/core/src/ports/runner/pi-bridge.js +6 -0
  153. package/packages/core/src/ports/runner/pi-rpc.ts +454 -0
  154. package/packages/core/src/ports/runner/pi.ts +617 -0
  155. package/packages/core/src/ports/runner/production.ts +74 -0
  156. package/packages/core/src/ports/runner/provider-error.ts +121 -0
  157. package/packages/core/src/ports/runner/routing.ts +234 -0
  158. package/packages/core/src/ports/runner/runtime.ts +214 -0
  159. package/packages/core/src/ports/runner/session-env.ts +34 -0
  160. package/packages/core/src/ports/runner/stream-parts.ts +103 -0
  161. package/packages/core/src/ports/tickets/contract.ts +570 -0
  162. package/packages/core/src/ports/tickets/create.ts +135 -0
  163. package/packages/core/src/ports/tickets/fake.ts +215 -0
  164. package/packages/core/src/ports/tickets/file.ts +559 -0
  165. package/packages/core/src/ports/tickets/linear.ts +811 -0
  166. package/packages/core/src/ports/tickets/operations.ts +161 -0
  167. package/packages/core/src/ports/tickets/remote-index.ts +13 -0
  168. package/packages/core/src/ports/tickets/remote-protocol.ts +80 -0
  169. package/packages/core/src/ports/tickets/remote-server.ts +185 -0
  170. package/packages/core/src/ports/tickets/remote.ts +130 -0
  171. package/packages/core/src/ports/tickets/update.ts +51 -0
  172. package/packages/core/src/ports/types.ts +528 -0
  173. package/packages/core/src/ports/workspace/build-execution.ts +123 -0
  174. package/packages/core/src/ports/workspace/contract.ts +136 -0
  175. package/packages/core/src/ports/workspace/create.ts +132 -0
  176. package/packages/core/src/ports/workspace/distribution-archive.ts +317 -0
  177. package/packages/core/src/ports/workspace/fake.ts +432 -0
  178. package/packages/core/src/ports/workspace/git-worktree.ts +848 -0
  179. package/packages/core/src/ports/workspace/harvest-execution.ts +111 -0
  180. package/packages/core/src/ports/workspace/in-process-build-execution.ts +30 -0
  181. package/packages/core/src/ports/workspace/local-build-execution.ts +88 -0
  182. package/packages/core/src/ports/workspace/operator-sandbox.ts +142 -0
  183. package/packages/core/src/ports/workspace/phase-scratch.ts +156 -0
  184. package/packages/core/src/ports/workspace/vercel-sandbox.ts +2541 -0
  185. package/packages/core/src/processes/build-child-exit.ts +78 -0
  186. package/packages/core/src/processes/build-child.ts +119 -0
  187. package/packages/core/src/processes/build-execution-state.ts +95 -0
  188. package/packages/core/src/processes/build-parent-watch.ts +35 -0
  189. package/packages/core/src/processes/build-runner.ts +2614 -0
  190. package/packages/core/src/processes/dispatcher.ts +2825 -0
  191. package/packages/core/src/processes/execution-settlement.ts +171 -0
  192. package/packages/core/src/processes/harvest-child.ts +115 -0
  193. package/packages/core/src/processes/harvest-execution-state.ts +121 -0
  194. package/packages/core/src/processes/harvest-runner.ts +1656 -0
  195. package/packages/core/src/processes/harvest.ts +595 -0
  196. package/packages/core/src/processes/infrastructure-failure-budget.ts +146 -0
  197. package/packages/core/src/processes/phase-failure-budget.ts +52 -0
  198. package/packages/core/src/processes/process-group-reaper.ts +35 -0
  199. package/packages/core/src/processes/process-group.ts +65 -0
  200. package/packages/core/src/processes/publication-loss.ts +62 -0
  201. package/packages/core/src/processes/publication-state.ts +93 -0
  202. package/packages/core/src/processes/sandbox-state.ts +101 -0
  203. package/packages/core/src/registry.ts +24 -0
  204. package/packages/core/src/sessions/reducer.ts +185 -0
  205. package/packages/core/src/skills.ts +7 -0
  206. package/packages/core/src/spec-standard.ts +47 -0
  207. package/packages/core/src/store/adapter.ts +80 -0
  208. package/packages/core/src/store/build-scope.ts +272 -0
  209. package/packages/core/src/store/contract.ts +2581 -0
  210. package/packages/core/src/store/local/blobs.ts +38 -0
  211. package/packages/core/src/store/local/schema.ts +180 -0
  212. package/packages/core/src/store/local/store.ts +1713 -0
  213. package/packages/core/src/store/memory.ts +1138 -0
  214. package/packages/core/src/store/phase-session.ts +425 -0
  215. package/packages/core/src/store/remote/client.ts +780 -0
  216. package/packages/core/src/store/remote/index.ts +12 -0
  217. package/packages/core/src/store/remote/protocol.ts +348 -0
  218. package/packages/core/src/store/remote/server.ts +923 -0
  219. package/packages/core/src/store/remote/token.ts +137 -0
  220. package/packages/core/src/store/remote/version.ts +10 -0
  221. package/packages/core/src/store/retention.ts +91 -0
  222. package/packages/core/src/store/session-handle.ts +265 -0
  223. package/packages/core/src/store/streams/assemble.ts +79 -0
  224. package/packages/core/src/store/streams/lock.ts +42 -0
  225. package/packages/core/src/store/streams/session-writer.ts +306 -0
  226. package/packages/core/src/store/streams/types.ts +171 -0
  227. package/packages/core/src/store/streams/wait.ts +127 -0
  228. package/packages/core/src/store/subscribe.ts +105 -0
  229. package/packages/core/src/store/types.ts +466 -0
  230. package/packages/core/src/testing/fixed.ts +44 -0
  231. package/packages/core/src/testing/store-failures.ts +32 -0
  232. package/packages/core/src/zod-issues.ts +91 -0
  233. package/skills/code-review/SKILL.md +100 -0
  234. package/skills/finalize/SKILL.md +44 -0
  235. package/skills/guide/SKILL.md +1781 -0
  236. package/skills/guide/references/operator-api.md +164 -0
  237. package/skills/guide/references/plugin-authoring.md +354 -0
  238. package/skills/guide/references/remote-store-protocol.md +972 -0
  239. package/skills/guide/references/remote-ticket-protocol.md +79 -0
  240. package/skills/guide/references/setup.md +289 -0
  241. package/skills/guide/references/spec-standard.md +63 -0
  242. package/skills/guide/references/ticket-dependencies.md +59 -0
  243. package/skills/harvest/SKILL.md +66 -0
  244. package/skills/harvest-review/SKILL.md +63 -0
  245. package/skills/implement/SKILL.md +82 -0
  246. package/skills/plan/SKILL.md +123 -0
  247. package/skills/plan-review/SKILL.md +121 -0
  248. package/skills/reconcile/SKILL.md +65 -0
  249. package/skills/spec/SKILL.md +132 -0
  250. package/skills/tickets/SKILL.md +139 -0
  251. package/templates/autobuild.toml +36 -0
@@ -0,0 +1,425 @@
1
+ /**
2
+ * Local ambient phase-session wrapper for a BuildStore.
3
+ *
4
+ * This module's "session" is the CLI's validated ambient phase/Harvest
5
+ * identity (agent-session attribution): it scopes a local store handle to the
6
+ * exact build or repository resource and rejects agent-attributed event
7
+ * writes for any other `AB_SESSION`. It is unrelated to the operator-session
8
+ * scope handles in `session-handle.ts` (hosted-only, AUT-339), which scope a
9
+ * store to a single operator session's record.
10
+ */
11
+ import type { EventType } from '../events/payloads'
12
+ import type { EventWrite } from '../events/catalog'
13
+ import type { RepositoryEventType, RepositoryEventWrite } from '../events/repository'
14
+ import type { AbEvent, EventEnvelope } from '../events/catalog'
15
+ import type { RepositoryEvent, RepositoryEventEnvelope } from '../events/repository'
16
+ import type {
17
+ SessionEvent,
18
+ SessionEventEnvelope,
19
+ SessionEventType,
20
+ SessionEventWrite,
21
+ } from '../events/sessions'
22
+ import type {
23
+ Artifact,
24
+ ArtifactInput,
25
+ ArtifactMeta,
26
+ BuildRecord,
27
+ BuildStore,
28
+ NewBuildInput,
29
+ NewSessionInput,
30
+ RepositoryArtifact,
31
+ RepositoryArtifactMeta,
32
+ RepositoryRecord,
33
+ SessionArtifactMeta,
34
+ SessionRecord,
35
+ SubscribeOptions,
36
+ Unsubscribe,
37
+ } from './types'
38
+ import type {
39
+ StreamChunk,
40
+ StreamOutcome,
41
+ StreamPart,
42
+ StreamRead,
43
+ StreamRecord,
44
+ StreamScope,
45
+ } from './streams/types'
46
+
47
+ export type PhaseSessionScope =
48
+ | { kind: 'build'; id: string; session: string }
49
+ | { kind: 'repo'; id: string; session: string }
50
+
51
+ /** An in-process authority failure for a local phase-session store handle. */
52
+ export class PhaseSessionError extends Error {
53
+ constructor(
54
+ readonly scope: PhaseSessionScope,
55
+ readonly operation: string,
56
+ readonly target?: { kind: 'build' | 'repo' | 'admin'; id?: string },
57
+ message?: string,
58
+ ) {
59
+ const authority = `${scope.kind} ${JSON.stringify(scope.id)} and session ${JSON.stringify(scope.session)}`
60
+ const addressed =
61
+ target === undefined
62
+ ? operation
63
+ : target.kind === 'admin'
64
+ ? `${operation} (admin)`
65
+ : `${operation} targeting ${target.kind} ${JSON.stringify(target.id)}`
66
+ super(message ?? `local phase-session store scoped to ${authority} forbids ${addressed}`)
67
+ this.name = 'PhaseSessionError'
68
+ }
69
+ }
70
+
71
+ type BuildPhaseSessionScope = Extract<PhaseSessionScope, { kind: 'build' }>
72
+ type RepositoryPhaseSessionScope = Extract<PhaseSessionScope, { kind: 'repo' }>
73
+
74
+ /** Nested same-build scoping is idempotent while a foreign scope can never
75
+ * widen this ambient-session handle. */
76
+ export interface BuildPhaseSessionStore extends BuildStore {
77
+ readonly phaseSession: BuildPhaseSessionScope
78
+ readonly buildScope: string
79
+ scopeBuild(slug: string): BuildPhaseSessionStore
80
+ }
81
+
82
+ export interface RepositoryPhaseSessionStore extends BuildStore {
83
+ readonly phaseSession: RepositoryPhaseSessionScope
84
+ scopeBuild(slug: string): never
85
+ }
86
+
87
+ export type PhaseSessionStore = BuildPhaseSessionStore | RepositoryPhaseSessionStore
88
+
89
+ function actorSession(actor: unknown): string | null {
90
+ if (typeof actor !== 'object' || actor === null) return null
91
+ const { kind, session } = actor as { kind?: unknown; session?: unknown }
92
+ return kind === 'agent' && typeof session === 'string' ? session : null
93
+ }
94
+
95
+ /**
96
+ * Restrict a local Store handle to the exact resource and session supplied by
97
+ * a validated ambient agent identity. Resource checks apply to every method;
98
+ * the session dimension applies only to agent-attributed event writes so the
99
+ * CLI's trusted KERNEL plumbing remains usable.
100
+ */
101
+ export function scopeLocalStoreToPhaseSession(
102
+ store: BuildStore,
103
+ scope: BuildPhaseSessionScope,
104
+ ): BuildPhaseSessionStore
105
+ export function scopeLocalStoreToPhaseSession(
106
+ store: BuildStore,
107
+ scope: RepositoryPhaseSessionScope,
108
+ ): RepositoryPhaseSessionStore
109
+ export function scopeLocalStoreToPhaseSession(
110
+ store: BuildStore,
111
+ scope: PhaseSessionScope,
112
+ ): PhaseSessionStore
113
+ export function scopeLocalStoreToPhaseSession(
114
+ store: BuildStore,
115
+ scope: PhaseSessionScope,
116
+ ): PhaseSessionStore {
117
+ const target = (kind: 'build' | 'repo', id: string) => ({ kind, id }) as const
118
+ const own = (operation: string, kind: 'build' | 'repo', id: string): void => {
119
+ if (scope.kind !== kind || scope.id !== id) {
120
+ throw new PhaseSessionError(scope, operation, target(kind, id))
121
+ }
122
+ }
123
+ const admin = (operation: string): never => {
124
+ throw new PhaseSessionError(scope, operation, { kind: 'admin' })
125
+ }
126
+ /** Streams carry no actor, so the session dimension does not gate them —
127
+ * only the exact-resource guard does. Addressed operations resolve the
128
+ * target's scope from its record; an unknown id delegates so the backing
129
+ * store's `unknown stream` feedback survives. */
130
+ const ownStreamScope = (record: StreamRecord): void => {
131
+ const matches =
132
+ scope.kind === 'build'
133
+ ? record.scope.kind === 'build' && record.scope.build === scope.id
134
+ : record.scope.kind === 'repo' && record.scope.repo === scope.id
135
+ if (!matches) {
136
+ throw new PhaseSessionError(
137
+ scope,
138
+ 'stream',
139
+ record.scope.kind === 'build'
140
+ ? ({ kind: 'build', id: record.scope.build } as const)
141
+ : record.scope.kind === 'repo'
142
+ ? ({ kind: 'repo', id: record.scope.repo } as const)
143
+ : // Session-scoped streams are hosted-only; an ambient phase
144
+ // session never owns one.
145
+ ({ kind: 'admin' } as const),
146
+ )
147
+ }
148
+ }
149
+ const ownStream = async (streamId: string): Promise<void> => {
150
+ const record = await store.getStream(streamId)
151
+ if (record) ownStreamScope(record)
152
+ }
153
+ const streamScope = (): StreamScope =>
154
+ scope.kind === 'build' ? { kind: 'build', build: scope.id } : { kind: 'repo', repo: scope.id }
155
+ const ownStreamScopeArg = (operation: string, candidate: StreamScope): void => {
156
+ const expected = streamScope()
157
+ if (JSON.stringify(candidate) !== JSON.stringify(expected)) {
158
+ throw new PhaseSessionError(
159
+ scope,
160
+ operation,
161
+ candidate.kind === 'build'
162
+ ? ({ kind: 'build', id: candidate.build } as const)
163
+ : candidate.kind === 'repo'
164
+ ? ({ kind: 'repo', id: candidate.repo } as const)
165
+ : ({ kind: 'admin' } as const),
166
+ )
167
+ }
168
+ }
169
+ const authorizeActor = (operation: string, actor: unknown): void => {
170
+ if (
171
+ typeof actor !== 'object' ||
172
+ actor === null ||
173
+ (actor as { kind?: unknown }).kind !== 'agent'
174
+ ) {
175
+ return
176
+ }
177
+ const session = actorSession(actor)
178
+ if (session !== scope.session) {
179
+ throw new PhaseSessionError(
180
+ scope,
181
+ operation,
182
+ undefined,
183
+ `local phase-session store scoped to session ${JSON.stringify(scope.session)} may not write events attributed to ${
184
+ session === null
185
+ ? 'an agent without a valid session'
186
+ : `session ${JSON.stringify(session)}`
187
+ }`,
188
+ )
189
+ }
190
+ }
191
+
192
+ const scoped = {
193
+ phaseSession: scope,
194
+ ...(scope.kind === 'build' ? { buildScope: scope.id } : {}),
195
+
196
+ scopeBuild(slug: string): BuildPhaseSessionStore {
197
+ own('scopeBuild', 'build', slug)
198
+ return scoped as BuildPhaseSessionStore
199
+ },
200
+
201
+ async createBuild(_input: NewBuildInput): Promise<BuildRecord> {
202
+ return admin('createBuild')
203
+ },
204
+ async getBuild(slug: string): Promise<BuildRecord | null> {
205
+ own('getBuild', 'build', slug)
206
+ return store.getBuild(slug)
207
+ },
208
+ async listBuilds(): Promise<BuildRecord[]> {
209
+ return admin('listBuilds')
210
+ },
211
+ async append<T extends EventType>(
212
+ slug: string,
213
+ event: EventWrite<T>,
214
+ ): Promise<EventEnvelope<T>> {
215
+ own('append', 'build', slug)
216
+ authorizeActor('append', event.actor)
217
+ return store.append(slug, event)
218
+ },
219
+ async appendIfCurrent<T extends EventType>(
220
+ slug: string,
221
+ expectedSeq: number,
222
+ event: EventWrite<T>,
223
+ ): Promise<EventEnvelope<T> | null> {
224
+ own('appendIfCurrent', 'build', slug)
225
+ authorizeActor('appendIfCurrent', event.actor)
226
+ return store.appendIfCurrent(slug, expectedSeq, event)
227
+ },
228
+ async appendWithArtifacts<T extends EventType>(
229
+ slug: string,
230
+ artifacts: ArtifactInput[],
231
+ makeEvent: (deposited: ArtifactMeta[]) => EventWrite<T>,
232
+ ): Promise<{ event: EventEnvelope<T>; artifacts: ArtifactMeta[] }> {
233
+ own('appendWithArtifacts', 'build', slug)
234
+ return store.appendWithArtifacts(slug, artifacts, (deposited) => {
235
+ const event = makeEvent(deposited)
236
+ authorizeActor('appendWithArtifacts', event.actor)
237
+ return event
238
+ })
239
+ },
240
+ async getEvents(
241
+ slug: string,
242
+ sinceSeq?: number,
243
+ opts?: { waitSeconds?: number; signal?: AbortSignal },
244
+ ): Promise<AbEvent[]> {
245
+ own('getEvents', 'build', slug)
246
+ return store.getEvents(slug, sinceSeq, opts)
247
+ },
248
+ async putArtifact(slug: string, artifact: ArtifactInput): Promise<ArtifactMeta> {
249
+ own('putArtifact', 'build', slug)
250
+ return store.putArtifact(slug, artifact)
251
+ },
252
+ async getArtifact(slug: string, kind: string, rev?: number): Promise<Artifact | null> {
253
+ own('getArtifact', 'build', slug)
254
+ return store.getArtifact(slug, kind, rev)
255
+ },
256
+ async listArtifacts(slug: string, kind?: string): Promise<ArtifactMeta[]> {
257
+ own('listArtifacts', 'build', slug)
258
+ return store.listArtifacts(slug, kind)
259
+ },
260
+ async claimLease(slug: string, holder: string, ttlMs: number): Promise<boolean> {
261
+ own('claimLease', 'build', slug)
262
+ return store.claimLease(slug, holder, ttlMs)
263
+ },
264
+ async heartbeat(slug: string, holder: string): Promise<boolean> {
265
+ own('heartbeat', 'build', slug)
266
+ return store.heartbeat(slug, holder)
267
+ },
268
+ async releaseLease(slug: string, holder: string): Promise<void> {
269
+ own('releaseLease', 'build', slug)
270
+ return store.releaseLease(slug, holder)
271
+ },
272
+ subscribe(
273
+ slug: string,
274
+ opts: SubscribeOptions,
275
+ onEvent: (event: AbEvent) => void,
276
+ ): Unsubscribe {
277
+ own('subscribe', 'build', slug)
278
+ return store.subscribe(slug, opts, onEvent)
279
+ },
280
+
281
+ async ensureRepo(_repo: string): Promise<RepositoryRecord> {
282
+ return admin('ensureRepo')
283
+ },
284
+ async getRepo(repo: string): Promise<RepositoryRecord | null> {
285
+ own('getRepo', 'repo', repo)
286
+ return store.getRepo(repo)
287
+ },
288
+ async appendRepo<T extends RepositoryEventType>(
289
+ repo: string,
290
+ event: RepositoryEventWrite<T>,
291
+ ): Promise<RepositoryEventEnvelope<T>> {
292
+ own('appendRepo', 'repo', repo)
293
+ authorizeActor('appendRepo', event.actor)
294
+ return store.appendRepo(repo, event)
295
+ },
296
+ async appendRepoWithArtifacts<T extends RepositoryEventType>(
297
+ repo: string,
298
+ artifacts: ArtifactInput[],
299
+ makeEvent: (deposited: RepositoryArtifactMeta[]) => RepositoryEventWrite<T>,
300
+ ): Promise<{
301
+ event: RepositoryEventEnvelope<T>
302
+ artifacts: RepositoryArtifactMeta[]
303
+ }> {
304
+ own('appendRepoWithArtifacts', 'repo', repo)
305
+ return store.appendRepoWithArtifacts(repo, artifacts, (deposited) => {
306
+ const event = makeEvent(deposited)
307
+ authorizeActor('appendRepoWithArtifacts', event.actor)
308
+ return event
309
+ })
310
+ },
311
+ async getRepoEvents(
312
+ repo: string,
313
+ sinceSeq?: number,
314
+ opts?: { waitSeconds?: number; signal?: AbortSignal },
315
+ ): Promise<RepositoryEvent[]> {
316
+ own('getRepoEvents', 'repo', repo)
317
+ return store.getRepoEvents(repo, sinceSeq, opts)
318
+ },
319
+ async putRepoArtifact(repo: string, artifact: ArtifactInput): Promise<RepositoryArtifactMeta> {
320
+ own('putRepoArtifact', 'repo', repo)
321
+ return store.putRepoArtifact(repo, artifact)
322
+ },
323
+ async getRepoArtifact(
324
+ repo: string,
325
+ kind: string,
326
+ rev?: number,
327
+ ): Promise<RepositoryArtifact | null> {
328
+ own('getRepoArtifact', 'repo', repo)
329
+ return store.getRepoArtifact(repo, kind, rev)
330
+ },
331
+ async listRepoArtifacts(repo: string, kind?: string): Promise<RepositoryArtifactMeta[]> {
332
+ own('listRepoArtifacts', 'repo', repo)
333
+ return store.listRepoArtifacts(repo, kind)
334
+ },
335
+ async claimRepoLease(repo: string, holder: string, ttlMs: number): Promise<boolean> {
336
+ own('claimRepoLease', 'repo', repo)
337
+ return store.claimRepoLease(repo, holder, ttlMs)
338
+ },
339
+ async heartbeatRepo(repo: string, holder: string): Promise<boolean> {
340
+ own('heartbeatRepo', 'repo', repo)
341
+ return store.heartbeatRepo(repo, holder)
342
+ },
343
+ async releaseRepoLease(repo: string, holder: string): Promise<void> {
344
+ own('releaseRepoLease', 'repo', repo)
345
+ return store.releaseRepoLease(repo, holder)
346
+ },
347
+
348
+ async createStream(candidate: StreamScope, label: string): Promise<StreamRecord> {
349
+ ownStreamScopeArg('createStream', candidate)
350
+ return store.createStream(candidate, label)
351
+ },
352
+ async appendStreamParts(streamId: string, parts: StreamPart[]): Promise<StreamChunk> {
353
+ await ownStream(streamId)
354
+ return store.appendStreamParts(streamId, parts)
355
+ },
356
+ async readStream(
357
+ streamId: string,
358
+ opts?: { since?: number; waitSeconds?: number },
359
+ ): Promise<StreamRead> {
360
+ await ownStream(streamId)
361
+ return store.readStream(streamId, opts)
362
+ },
363
+ async closeStream(streamId: string, outcome: StreamOutcome): Promise<StreamRecord> {
364
+ await ownStream(streamId)
365
+ return store.closeStream(streamId, outcome)
366
+ },
367
+ async getStream(streamId: string): Promise<StreamRecord | null> {
368
+ await ownStream(streamId)
369
+ return store.getStream(streamId)
370
+ },
371
+ async listStreams(candidate: StreamScope): Promise<StreamRecord[]> {
372
+ ownStreamScopeArg('listStreams', candidate)
373
+ return store.listStreams(candidate)
374
+ },
375
+
376
+ // Operator sessions are hosted-only: an ambient phase-session handle —
377
+ // a local construct — never creates or touches one.
378
+ createSession(_input: NewSessionInput): Promise<SessionRecord> {
379
+ return admin('createSession')
380
+ },
381
+ getSession(_id: string): Promise<SessionRecord | null> {
382
+ return admin('getSession')
383
+ },
384
+ listSessions(_repo: string): Promise<SessionRecord[]> {
385
+ return admin('listSessions')
386
+ },
387
+ appendSessionEvent<T extends SessionEventType>(
388
+ _id: string,
389
+ _event: SessionEventWrite<T>,
390
+ ): Promise<SessionEventEnvelope<T>> {
391
+ return admin('appendSessionEvent')
392
+ },
393
+ getSessionEvents(_id: string, _sinceSeq?: number): Promise<SessionEvent[]> {
394
+ return admin('getSessionEvents')
395
+ },
396
+ appendSessionWithArtifacts<T extends SessionEventType>(
397
+ _id: string,
398
+ _artifacts: ArtifactInput[],
399
+ _makeEvent: (deposited: SessionArtifactMeta[]) => SessionEventWrite<T>,
400
+ ): Promise<{ event: SessionEventEnvelope<T>; artifacts: SessionArtifactMeta[] }> {
401
+ return admin('appendSessionWithArtifacts')
402
+ },
403
+ putSessionArtifact(_id: string, _artifact: ArtifactInput): Promise<SessionArtifactMeta> {
404
+ return admin('putSessionArtifact')
405
+ },
406
+ getSessionArtifact(_id: string, _kind: string, _rev?: number): Promise<null> {
407
+ return admin('getSessionArtifact')
408
+ },
409
+ listSessionArtifacts(_id: string, _kind?: string): Promise<SessionArtifactMeta[]> {
410
+ return admin('listSessionArtifacts')
411
+ },
412
+ scopeSession(_id: string): never {
413
+ return admin('scopeSession')
414
+ },
415
+
416
+ close(): Promise<void> {
417
+ return store.close()
418
+ },
419
+ } satisfies BuildStore & {
420
+ readonly phaseSession: PhaseSessionScope
421
+ readonly buildScope?: string
422
+ scopeBuild(slug: string): BuildPhaseSessionStore
423
+ }
424
+ return scoped as PhaseSessionStore
425
+ }