@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
|
@@ -26,12 +26,34 @@ import {
|
|
|
26
26
|
} from '../events/sessions'
|
|
27
27
|
import { humanActor } from '../events/envelope'
|
|
28
28
|
import { createBuildScopedStore } from './build-scope'
|
|
29
|
+
import {
|
|
30
|
+
type ReducerSnapshot,
|
|
31
|
+
type SnapshotScope,
|
|
32
|
+
snapshotScopeKey,
|
|
33
|
+
snapshotSupersedes,
|
|
34
|
+
} from './snapshots'
|
|
35
|
+
import { validateCreatedEvent } from './new-build'
|
|
29
36
|
import { createSessionScopedStore } from './session-handle'
|
|
37
|
+
import { reduceBuildDigest } from './digest'
|
|
38
|
+
import { projectRepositoryStateEvents } from './repo-state-events'
|
|
30
39
|
import {
|
|
31
40
|
DEFAULT_ARTIFACT_RETENTION_MAX_REVISIONS,
|
|
32
41
|
isRetentionManagedKind,
|
|
33
42
|
revisionsToPrune,
|
|
34
43
|
} from './retention'
|
|
44
|
+
import {
|
|
45
|
+
DEFAULT_TICKET_ASSET_LIMITS,
|
|
46
|
+
loadTicketAsset,
|
|
47
|
+
resolvePinnedAsset,
|
|
48
|
+
storeTicketAssetBlobs,
|
|
49
|
+
summarizeTicketAsset,
|
|
50
|
+
validateTicketAssetInput,
|
|
51
|
+
type TicketAsset,
|
|
52
|
+
type TicketAssetInput,
|
|
53
|
+
type TicketAssetLimits,
|
|
54
|
+
type TicketAssetMeta,
|
|
55
|
+
type TicketAssetSummary,
|
|
56
|
+
} from './ticket-assets'
|
|
35
57
|
import { pollingSubscribe } from './subscribe'
|
|
36
58
|
import { StreamLocks } from './streams/lock'
|
|
37
59
|
import { assembleUIMessageDocument } from './streams/assemble'
|
|
@@ -62,6 +84,7 @@ import {
|
|
|
62
84
|
type ArtifactMeta,
|
|
63
85
|
type BlobStore,
|
|
64
86
|
type BuildRecord,
|
|
87
|
+
type BuildDigest,
|
|
65
88
|
type BuildScopedStore,
|
|
66
89
|
type BuildStore,
|
|
67
90
|
type Clock,
|
|
@@ -146,6 +169,11 @@ export class MemoryBuildStore implements BuildStore {
|
|
|
146
169
|
* stream's close against its appends inside this process (AUT-348). */
|
|
147
170
|
private readonly streamLocks = new StreamLocks()
|
|
148
171
|
private readonly sessions = new Map<string, SessionState>()
|
|
172
|
+
/** Reducer snapshots (store/snapshots.ts), keyed by scope and reducer name. */
|
|
173
|
+
private readonly snapshots = new Map<string, ReducerSnapshot>()
|
|
174
|
+
/** `[repo, ticketId, kind, name]` → rows in revision order. A removal is a
|
|
175
|
+
* tombstone row (`removed`, empty manifest) taking the next revision. */
|
|
176
|
+
private readonly ticketAssets = new Map<string, { meta: TicketAssetMeta; removed: boolean }[]>()
|
|
149
177
|
private readonly clock: Clock
|
|
150
178
|
private readonly maxRevisions: number
|
|
151
179
|
readonly blobs: BlobStore
|
|
@@ -244,6 +272,7 @@ export class MemoryBuildStore implements BuildStore {
|
|
|
244
272
|
}
|
|
245
273
|
|
|
246
274
|
async createBuild(input: NewBuildInput): Promise<BuildRecord> {
|
|
275
|
+
const created = validateCreatedEvent(input)
|
|
247
276
|
if (this.builds.has(input.slug)) {
|
|
248
277
|
throw new Error(`build "${input.slug}" already exists`)
|
|
249
278
|
}
|
|
@@ -261,6 +290,18 @@ export class MemoryBuildStore implements BuildStore {
|
|
|
261
290
|
events: [],
|
|
262
291
|
artifacts: new Map(),
|
|
263
292
|
}
|
|
293
|
+
if (created !== undefined) {
|
|
294
|
+
state.events.push(
|
|
295
|
+
structuredClone({
|
|
296
|
+
build: input.slug,
|
|
297
|
+
seq: 1,
|
|
298
|
+
ts,
|
|
299
|
+
actor: created.actor,
|
|
300
|
+
type: created.type,
|
|
301
|
+
payload: created.payload,
|
|
302
|
+
}) as AbEvent,
|
|
303
|
+
)
|
|
304
|
+
}
|
|
264
305
|
this.builds.set(input.slug, state)
|
|
265
306
|
return this.snapshot(state)
|
|
266
307
|
}
|
|
@@ -274,6 +315,37 @@ export class MemoryBuildStore implements BuildStore {
|
|
|
274
315
|
return [...this.builds.values()].map((state) => this.snapshot(state))
|
|
275
316
|
}
|
|
276
317
|
|
|
318
|
+
async getRepoBuildDigests(repo: string): Promise<Map<string, BuildDigest>> {
|
|
319
|
+
// Insertion order is creation order (the listSessions tiebreak note); the
|
|
320
|
+
// digest map is returned in slug order so every adapter answers the same
|
|
321
|
+
// shape for the same store contents.
|
|
322
|
+
const slugs = [...this.builds.entries()]
|
|
323
|
+
.filter(([, state]) => state.record.repo === repo)
|
|
324
|
+
.map(([slug]) => slug)
|
|
325
|
+
.sort()
|
|
326
|
+
const digests = new Map<string, BuildDigest>()
|
|
327
|
+
for (const slug of slugs) {
|
|
328
|
+
const state = this.builds.get(slug)!
|
|
329
|
+
digests.set(slug, {
|
|
330
|
+
slug,
|
|
331
|
+
...reduceBuildDigest(
|
|
332
|
+
state.events.map((event) => ({
|
|
333
|
+
type: event.type,
|
|
334
|
+
seq: event.seq,
|
|
335
|
+
ts: event.ts,
|
|
336
|
+
...(event.type === 'pr-attachment.reclaimed'
|
|
337
|
+
? { hostedSeq: event.payload.hostedSeq }
|
|
338
|
+
: {}),
|
|
339
|
+
...(event.type === 'execution.started' || event.type === 'execution.ended'
|
|
340
|
+
? { instance: event.payload.instance }
|
|
341
|
+
: {}),
|
|
342
|
+
})),
|
|
343
|
+
),
|
|
344
|
+
})
|
|
345
|
+
}
|
|
346
|
+
return digests
|
|
347
|
+
}
|
|
348
|
+
|
|
277
349
|
async append<T extends EventType>(slug: string, event: EventWrite<T>): Promise<EventEnvelope<T>> {
|
|
278
350
|
const state = this.state(slug)
|
|
279
351
|
const validated = validateEventWrite(event)
|
|
@@ -629,6 +701,58 @@ export class MemoryBuildStore implements BuildStore {
|
|
|
629
701
|
})
|
|
630
702
|
}
|
|
631
703
|
|
|
704
|
+
async getRepoHighWater(repo: string): Promise<number> {
|
|
705
|
+
return this.repoState(repo).events.at(-1)?.seq ?? 0
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/** The scope's log tail, or `undefined` for a scope that does not exist. */
|
|
709
|
+
private snapshotTail(scope: SnapshotScope): number | undefined {
|
|
710
|
+
const events =
|
|
711
|
+
scope.kind === 'build'
|
|
712
|
+
? this.builds.get(scope.slug)?.events
|
|
713
|
+
: this.repos.get(scope.repo)?.events
|
|
714
|
+
return events === undefined ? undefined : (events.at(-1)?.seq ?? 0)
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
async getReducerSnapshot(
|
|
718
|
+
scope: SnapshotScope,
|
|
719
|
+
reducer: string,
|
|
720
|
+
version: number,
|
|
721
|
+
): Promise<ReducerSnapshot | null> {
|
|
722
|
+
const tail = this.snapshotTail(scope)
|
|
723
|
+
if (tail === undefined) return null
|
|
724
|
+
const stored = this.snapshots.get(`${snapshotScopeKey(scope)}\u0000${reducer}`)
|
|
725
|
+
if (stored === undefined || stored.version !== version || stored.cursor > tail) return null
|
|
726
|
+
return { version: stored.version, cursor: stored.cursor, state: structuredClone(stored.state) }
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
async putReducerSnapshot(
|
|
730
|
+
scope: SnapshotScope,
|
|
731
|
+
reducer: string,
|
|
732
|
+
snapshot: ReducerSnapshot,
|
|
733
|
+
): Promise<boolean> {
|
|
734
|
+
const tail = this.snapshotTail(scope)
|
|
735
|
+
if (tail === undefined || snapshot.cursor > tail) return false
|
|
736
|
+
const key = `${snapshotScopeKey(scope)}\u0000${reducer}`
|
|
737
|
+
if (!snapshotSupersedes(snapshot, this.snapshots.get(key))) return false
|
|
738
|
+
// The JSON round trip is the stored form every adapter shares, so callers
|
|
739
|
+
// cannot alias what the store holds.
|
|
740
|
+
this.snapshots.set(key, {
|
|
741
|
+
version: snapshot.version,
|
|
742
|
+
cursor: snapshot.cursor,
|
|
743
|
+
state: JSON.parse(JSON.stringify(snapshot.state ?? null)),
|
|
744
|
+
})
|
|
745
|
+
return true
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
async getRepoStateEvents(repo: string): Promise<RepositoryEvent[]> {
|
|
749
|
+
// The oracle runs on the in-memory journal itself, which keeps the pure
|
|
750
|
+
// derivation honest against real adapter data.
|
|
751
|
+
return structuredClone(
|
|
752
|
+
projectRepositoryStateEvents(this.repoState(repo).events as RepositoryEvent[]),
|
|
753
|
+
)
|
|
754
|
+
}
|
|
755
|
+
|
|
632
756
|
async putRepoArtifact(repo: string, artifact: ArtifactInput): Promise<RepositoryArtifactMeta> {
|
|
633
757
|
const state = this.repoState(repo)
|
|
634
758
|
if (!artifact.kind) throw new Error('artifact kind is required')
|
|
@@ -676,6 +800,102 @@ export class MemoryBuildStore implements BuildStore {
|
|
|
676
800
|
)
|
|
677
801
|
}
|
|
678
802
|
|
|
803
|
+
async ticketAssetLimits(_repo: string): Promise<TicketAssetLimits> {
|
|
804
|
+
return { ...DEFAULT_TICKET_ASSET_LIMITS }
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
async putTicketAsset(
|
|
808
|
+
repo: string,
|
|
809
|
+
ticketId: string,
|
|
810
|
+
asset: TicketAssetInput,
|
|
811
|
+
): Promise<TicketAssetMeta> {
|
|
812
|
+
const size = validateTicketAssetInput(asset)
|
|
813
|
+
const entries = await storeTicketAssetBlobs(this.blobs, asset.entries)
|
|
814
|
+
const key = JSON.stringify([repo, ticketId, asset.kind, asset.name])
|
|
815
|
+
const rows = this.ticketAssets.get(key) ?? []
|
|
816
|
+
const meta: TicketAssetMeta = {
|
|
817
|
+
repo,
|
|
818
|
+
ticketId,
|
|
819
|
+
kind: asset.kind,
|
|
820
|
+
name: asset.name,
|
|
821
|
+
revision: MemoryBuildStore.nextRevision(rows.map((row) => row.meta)),
|
|
822
|
+
layout: asset.layout,
|
|
823
|
+
size,
|
|
824
|
+
entries,
|
|
825
|
+
createdAt: this.now(),
|
|
826
|
+
}
|
|
827
|
+
rows.push({ meta, removed: false })
|
|
828
|
+
this.ticketAssets.set(key, rows)
|
|
829
|
+
return structuredClone(meta)
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
async getTicketAsset(
|
|
833
|
+
repo: string,
|
|
834
|
+
ticketId: string,
|
|
835
|
+
kind: string,
|
|
836
|
+
name: string,
|
|
837
|
+
rev?: number,
|
|
838
|
+
): Promise<TicketAsset | null> {
|
|
839
|
+
const rows = this.ticketAssets.get(JSON.stringify([repo, ticketId, kind, name]))
|
|
840
|
+
const row =
|
|
841
|
+
rev === undefined ? rows?.at(-1) : rows?.find((candidate) => candidate.meta.revision === rev)
|
|
842
|
+
if (!row || row.removed) return null
|
|
843
|
+
return loadTicketAsset(this.blobs, row.meta)
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
async getPinnedTicketAsset(
|
|
847
|
+
slug: string,
|
|
848
|
+
kind: string,
|
|
849
|
+
name: string,
|
|
850
|
+
rev?: number,
|
|
851
|
+
): Promise<TicketAsset | null> {
|
|
852
|
+
return resolvePinnedAsset(this, slug, kind, name, rev)
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
async listTicketAssets(
|
|
856
|
+
repo: string,
|
|
857
|
+
ticketId: string,
|
|
858
|
+
opts?: { revisions?: boolean },
|
|
859
|
+
): Promise<TicketAssetSummary[]> {
|
|
860
|
+
const out: TicketAssetSummary[] = []
|
|
861
|
+
for (const rows of this.ticketAssets.values()) {
|
|
862
|
+
const first = rows[0]
|
|
863
|
+
if (!first || first.meta.repo !== repo || first.meta.ticketId !== ticketId) continue
|
|
864
|
+
if (opts?.revisions) {
|
|
865
|
+
for (const row of rows) if (!row.removed) out.push(summarizeTicketAsset(row.meta))
|
|
866
|
+
} else {
|
|
867
|
+
const latest = rows.at(-1)
|
|
868
|
+
if (latest && !latest.removed) out.push(summarizeTicketAsset(latest.meta))
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
return out.sort(
|
|
872
|
+
(a, b) =>
|
|
873
|
+
a.kind.localeCompare(b.kind) || a.name.localeCompare(b.name) || a.revision - b.revision,
|
|
874
|
+
)
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
async removeTicketAsset(
|
|
878
|
+
repo: string,
|
|
879
|
+
ticketId: string,
|
|
880
|
+
kind: string,
|
|
881
|
+
name: string,
|
|
882
|
+
): Promise<TicketAssetMeta | null> {
|
|
883
|
+
const rows = this.ticketAssets.get(JSON.stringify([repo, ticketId, kind, name]))
|
|
884
|
+
const latest = rows?.at(-1)
|
|
885
|
+
if (!rows || !latest || latest.removed) return null
|
|
886
|
+
rows.push({
|
|
887
|
+
meta: {
|
|
888
|
+
...latest.meta,
|
|
889
|
+
revision: latest.meta.revision + 1,
|
|
890
|
+
size: 0,
|
|
891
|
+
entries: [],
|
|
892
|
+
createdAt: this.now(),
|
|
893
|
+
},
|
|
894
|
+
removed: true,
|
|
895
|
+
})
|
|
896
|
+
return structuredClone(latest.meta)
|
|
897
|
+
}
|
|
898
|
+
|
|
679
899
|
async claimRepoLease(repo: string, holder: string, ttlMs: number): Promise<boolean> {
|
|
680
900
|
const state = this.repoState(repo)
|
|
681
901
|
const now = this.clock().getTime()
|
|
@@ -791,6 +1011,32 @@ export class MemoryBuildStore implements BuildStore {
|
|
|
791
1011
|
return envelope
|
|
792
1012
|
}
|
|
793
1013
|
|
|
1014
|
+
async appendSessionEventIfCurrent<T extends SessionEventType>(
|
|
1015
|
+
id: string,
|
|
1016
|
+
expectedSeq: number,
|
|
1017
|
+
event: SessionEventWrite<T>,
|
|
1018
|
+
): Promise<SessionEventEnvelope<T> | null> {
|
|
1019
|
+
const state = this.sessionState(id)
|
|
1020
|
+
validateExpectedSeq(expectedSeq)
|
|
1021
|
+
const validated = validateSessionEventWrite(event)
|
|
1022
|
+
|
|
1023
|
+
// No await occurs between comparison and mutation. JavaScript's run-to-
|
|
1024
|
+
// completion semantics make this one atomic critical section even when
|
|
1025
|
+
// concurrent remote requests interleave elsewhere in the adapter.
|
|
1026
|
+
if (state.events.length !== expectedSeq) return null
|
|
1027
|
+
const envelope = {
|
|
1028
|
+
session: id,
|
|
1029
|
+
seq: expectedSeq + 1,
|
|
1030
|
+
ts: this.now(),
|
|
1031
|
+
actor: validated.actor,
|
|
1032
|
+
type: validated.type,
|
|
1033
|
+
payload: validated.payload,
|
|
1034
|
+
} as SessionEventEnvelope<T>
|
|
1035
|
+
state.events.push(structuredClone(envelope) as SessionEvent)
|
|
1036
|
+
state.record.updatedAt = envelope.ts
|
|
1037
|
+
return envelope
|
|
1038
|
+
}
|
|
1039
|
+
|
|
794
1040
|
async getSessionEvents(
|
|
795
1041
|
id: string,
|
|
796
1042
|
sinceSeq = 0,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { EventValidationError, validateEventWrite, type EventWrite } from '../events/catalog'
|
|
2
|
+
import type { NewBuildInput } from './types'
|
|
3
|
+
|
|
4
|
+
/** Validate `input.created` before an adapter opens its create transaction, so
|
|
5
|
+
* an invalid initial event creates nothing. Returns the validated write. */
|
|
6
|
+
export function validateCreatedEvent(
|
|
7
|
+
input: NewBuildInput,
|
|
8
|
+
): EventWrite<'build.created'> | undefined {
|
|
9
|
+
if (input.created === undefined) return undefined
|
|
10
|
+
const validated = validateEventWrite(input.created)
|
|
11
|
+
if (validated.type !== 'build.created') {
|
|
12
|
+
throw new EventValidationError(
|
|
13
|
+
`createBuild's initial event must be "build.created", got "${validated.type}"`,
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
return validated as EventWrite<'build.created'>
|
|
17
|
+
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* scope handles in `session-handle.ts` (hosted-only, AUT-339), which scope a
|
|
9
9
|
* store to a single operator session's record.
|
|
10
10
|
*/
|
|
11
|
+
import type { ReducerSnapshot, SnapshotScope } from './snapshots'
|
|
11
12
|
import type { EventType } from '../events/payloads'
|
|
12
13
|
import type { EventWrite } from '../events/catalog'
|
|
13
14
|
import type { RepositoryEventType, RepositoryEventWrite } from '../events/repository'
|
|
@@ -23,6 +24,7 @@ import type {
|
|
|
23
24
|
Artifact,
|
|
24
25
|
ArtifactInput,
|
|
25
26
|
ArtifactMeta,
|
|
27
|
+
BuildDigest,
|
|
26
28
|
BuildRecord,
|
|
27
29
|
BuildStore,
|
|
28
30
|
NewBuildInput,
|
|
@@ -35,6 +37,13 @@ import type {
|
|
|
35
37
|
SubscribeOptions,
|
|
36
38
|
Unsubscribe,
|
|
37
39
|
} from './types'
|
|
40
|
+
import type {
|
|
41
|
+
TicketAsset,
|
|
42
|
+
TicketAssetInput,
|
|
43
|
+
TicketAssetLimits,
|
|
44
|
+
TicketAssetMeta,
|
|
45
|
+
TicketAssetSummary,
|
|
46
|
+
} from './ticket-assets'
|
|
38
47
|
import type {
|
|
39
48
|
StreamChunk,
|
|
40
49
|
StreamOutcome,
|
|
@@ -316,6 +325,36 @@ export function scopeLocalStoreToPhaseSession(
|
|
|
316
325
|
own('getRepoEvents', 'repo', repo)
|
|
317
326
|
return store.getRepoEvents(repo, sinceSeq, opts)
|
|
318
327
|
},
|
|
328
|
+
async getRepoStateEvents(repo: string): Promise<RepositoryEvent[]> {
|
|
329
|
+
own('getRepoStateEvents', 'repo', repo)
|
|
330
|
+
return store.getRepoStateEvents(repo)
|
|
331
|
+
},
|
|
332
|
+
async getRepoHighWater(repo: string): Promise<number> {
|
|
333
|
+
own('getRepoHighWater', 'repo', repo)
|
|
334
|
+
return store.getRepoHighWater(repo)
|
|
335
|
+
},
|
|
336
|
+
async getReducerSnapshot(
|
|
337
|
+
target: SnapshotScope,
|
|
338
|
+
reducer: string,
|
|
339
|
+
version: number,
|
|
340
|
+
): Promise<ReducerSnapshot | null> {
|
|
341
|
+
if (target.kind === 'build') own('getReducerSnapshot', 'build', target.slug)
|
|
342
|
+
else own('getReducerSnapshot', 'repo', target.repo)
|
|
343
|
+
return store.getReducerSnapshot(target, reducer, version)
|
|
344
|
+
},
|
|
345
|
+
async putReducerSnapshot(
|
|
346
|
+
target: SnapshotScope,
|
|
347
|
+
reducer: string,
|
|
348
|
+
snapshot: ReducerSnapshot,
|
|
349
|
+
): Promise<boolean> {
|
|
350
|
+
if (target.kind === 'build') own('putReducerSnapshot', 'build', target.slug)
|
|
351
|
+
else own('putReducerSnapshot', 'repo', target.repo)
|
|
352
|
+
return store.putReducerSnapshot(target, reducer, snapshot)
|
|
353
|
+
},
|
|
354
|
+
async getRepoBuildDigests(repo: string): Promise<Map<string, BuildDigest>> {
|
|
355
|
+
own('getRepoBuildDigests', 'repo', repo)
|
|
356
|
+
return store.getRepoBuildDigests(repo)
|
|
357
|
+
},
|
|
319
358
|
async putRepoArtifact(repo: string, artifact: ArtifactInput): Promise<RepositoryArtifactMeta> {
|
|
320
359
|
own('putRepoArtifact', 'repo', repo)
|
|
321
360
|
return store.putRepoArtifact(repo, artifact)
|
|
@@ -332,6 +371,54 @@ export function scopeLocalStoreToPhaseSession(
|
|
|
332
371
|
own('listRepoArtifacts', 'repo', repo)
|
|
333
372
|
return store.listRepoArtifacts(repo, kind)
|
|
334
373
|
},
|
|
374
|
+
async ticketAssetLimits(repo: string): Promise<TicketAssetLimits> {
|
|
375
|
+
own('ticketAssetLimits', 'repo', repo)
|
|
376
|
+
return store.ticketAssetLimits(repo)
|
|
377
|
+
},
|
|
378
|
+
async putTicketAsset(
|
|
379
|
+
repo: string,
|
|
380
|
+
ticketId: string,
|
|
381
|
+
asset: TicketAssetInput,
|
|
382
|
+
): Promise<TicketAssetMeta> {
|
|
383
|
+
own('putTicketAsset', 'repo', repo)
|
|
384
|
+
return store.putTicketAsset(repo, ticketId, asset)
|
|
385
|
+
},
|
|
386
|
+
async getTicketAsset(
|
|
387
|
+
repo: string,
|
|
388
|
+
ticketId: string,
|
|
389
|
+
kind: string,
|
|
390
|
+
name: string,
|
|
391
|
+
rev?: number,
|
|
392
|
+
): Promise<TicketAsset | null> {
|
|
393
|
+
own('getTicketAsset', 'repo', repo)
|
|
394
|
+
return store.getTicketAsset(repo, ticketId, kind, name, rev)
|
|
395
|
+
},
|
|
396
|
+
async getPinnedTicketAsset(
|
|
397
|
+
slug: string,
|
|
398
|
+
kind: string,
|
|
399
|
+
name: string,
|
|
400
|
+
rev?: number,
|
|
401
|
+
): Promise<TicketAsset | null> {
|
|
402
|
+
own('getPinnedTicketAsset', 'build', slug)
|
|
403
|
+
return store.getPinnedTicketAsset(slug, kind, name, rev)
|
|
404
|
+
},
|
|
405
|
+
async listTicketAssets(
|
|
406
|
+
repo: string,
|
|
407
|
+
ticketId: string,
|
|
408
|
+
opts?: { revisions?: boolean },
|
|
409
|
+
): Promise<TicketAssetSummary[]> {
|
|
410
|
+
own('listTicketAssets', 'repo', repo)
|
|
411
|
+
return store.listTicketAssets(repo, ticketId, opts)
|
|
412
|
+
},
|
|
413
|
+
async removeTicketAsset(
|
|
414
|
+
repo: string,
|
|
415
|
+
ticketId: string,
|
|
416
|
+
kind: string,
|
|
417
|
+
name: string,
|
|
418
|
+
): Promise<TicketAssetMeta | null> {
|
|
419
|
+
own('removeTicketAsset', 'repo', repo)
|
|
420
|
+
return store.removeTicketAsset(repo, ticketId, kind, name)
|
|
421
|
+
},
|
|
335
422
|
async claimRepoLease(repo: string, holder: string, ttlMs: number): Promise<boolean> {
|
|
336
423
|
own('claimRepoLease', 'repo', repo)
|
|
337
424
|
return store.claimRepoLease(repo, holder, ttlMs)
|
|
@@ -390,6 +477,13 @@ export function scopeLocalStoreToPhaseSession(
|
|
|
390
477
|
): Promise<SessionEventEnvelope<T>> {
|
|
391
478
|
return admin('appendSessionEvent')
|
|
392
479
|
},
|
|
480
|
+
appendSessionEventIfCurrent<T extends SessionEventType>(
|
|
481
|
+
_id: string,
|
|
482
|
+
_expectedSeq: number,
|
|
483
|
+
_event: SessionEventWrite<T>,
|
|
484
|
+
): Promise<SessionEventEnvelope<T> | null> {
|
|
485
|
+
return admin('appendSessionEventIfCurrent')
|
|
486
|
+
},
|
|
393
487
|
getSessionEvents(_id: string, _sinceSeq?: number): Promise<SessionEvent[]> {
|
|
394
488
|
return admin('getSessionEvents')
|
|
395
489
|
},
|