@defrex/autobuild 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/README.md +1 -1
  2. package/SPEC.md +138 -16
  3. package/bin/ab-dispatch-kernel.ts +4 -6
  4. package/docs/architecture.md +155 -73
  5. package/docs/configuration.md +41 -9
  6. package/docs/hosted-service.md +31 -5
  7. package/docs/operator-api.md +8 -1
  8. package/docs/remote-store-protocol.md +94 -2
  9. package/docs/remote-ticket-protocol.md +151 -9
  10. package/docs/setup.md +33 -7
  11. package/docs/ticket-migration.md +64 -0
  12. package/package.json +4 -3
  13. package/packages/core/src/cli/attention.ts +18 -164
  14. package/packages/core/src/cli/build-control.ts +23 -16
  15. package/packages/core/src/cli/bulk-control.ts +3 -2
  16. package/packages/core/src/cli/connect.ts +141 -38
  17. package/packages/core/src/cli/context.ts +23 -79
  18. package/packages/core/src/cli/dashboard/model.ts +36 -22
  19. package/packages/core/src/cli/dashboard/poll.ts +1 -1
  20. package/packages/core/src/cli/dispatch-frontend.ts +24 -67
  21. package/packages/core/src/cli/dispatch-process.ts +14 -7
  22. package/packages/core/src/cli/dispatch-repository-feed.ts +196 -0
  23. package/packages/core/src/cli/dispatch.ts +304 -82
  24. package/packages/core/src/cli/dispatcher-liveness.ts +26 -19
  25. package/packages/core/src/cli/harvest.ts +84 -42
  26. package/packages/core/src/cli/help.ts +14 -8
  27. package/packages/core/src/cli/historical-build-state.ts +27 -0
  28. package/packages/core/src/cli/installation.ts +67 -13
  29. package/packages/core/src/cli/main.ts +23 -5
  30. package/packages/core/src/cli/mcp.ts +2 -1
  31. package/packages/core/src/cli/pr-attachments.ts +10 -13
  32. package/packages/core/src/cli/pr-summary-publication.ts +27 -60
  33. package/packages/core/src/cli/pr-summary.ts +43 -13
  34. package/packages/core/src/cli/publication-settlement.ts +10 -20
  35. package/packages/core/src/cli/repository-status.ts +38 -2
  36. package/packages/core/src/cli/self-update.ts +5 -1
  37. package/packages/core/src/cli/status.ts +62 -32
  38. package/packages/core/src/cli/terminals.ts +25 -142
  39. package/packages/core/src/cli/ticket-assets.ts +7 -2
  40. package/packages/core/src/cli/ticket-editor.ts +82 -0
  41. package/packages/core/src/cli/ticket.ts +166 -36
  42. package/packages/core/src/cli/wait.ts +91 -84
  43. package/packages/core/src/cli/watch.ts +60 -97
  44. package/packages/core/src/config/pipeline-source.ts +4 -3
  45. package/packages/core/src/config/schema.ts +35 -3
  46. package/packages/core/src/connection.ts +3 -3
  47. package/packages/core/src/events/catalog.ts +3 -2
  48. package/packages/core/src/events/payloads.ts +6 -0
  49. package/packages/core/src/events/repository.ts +7 -3
  50. package/packages/core/src/integration/harness.ts +38 -4
  51. package/packages/core/src/kernel/__snapshots__/reducer-registry.test.ts.snap +2444 -9
  52. package/packages/core/src/kernel/auto-merge.ts +19 -3
  53. package/packages/core/src/kernel/dispatcher-liveness.ts +10 -4
  54. package/packages/core/src/kernel/generators/build-log.ts +1 -1
  55. package/packages/core/src/kernel/generators/repository-journals.ts +14 -3
  56. package/packages/core/src/kernel/harvest.ts +25 -23
  57. package/packages/core/src/kernel/incremental-fixtures.ts +13 -2
  58. package/packages/core/src/kernel/log-index-value.ts +38 -0
  59. package/packages/core/src/kernel/read-facts.ts +637 -0
  60. package/packages/core/src/kernel/reducer-registry.ts +139 -3
  61. package/packages/core/src/kernel/repository-attention.ts +179 -0
  62. package/packages/core/src/kernel/service-facts.ts +135 -0
  63. package/packages/core/src/kernel/terminal-guard.ts +133 -0
  64. package/packages/core/src/ontology.ts +1 -0
  65. package/packages/core/src/operator/annotations.ts +12 -0
  66. package/packages/core/src/operator/control.ts +5 -6
  67. package/packages/core/src/operator/index.ts +7 -0
  68. package/packages/core/src/operator/protocol.ts +19 -2
  69. package/packages/core/src/operator/query.ts +37 -20
  70. package/packages/core/src/operator/read-view.ts +4 -13
  71. package/packages/core/src/operator/registry.ts +61 -8
  72. package/packages/core/src/operator/requests.ts +2 -14
  73. package/packages/core/src/operator/sandbox.ts +10 -9
  74. package/packages/core/src/operator/ticket-source-backend.ts +17 -21
  75. package/packages/core/src/operator/tickets.ts +132 -22
  76. package/packages/core/src/plugin-sdk/index.ts +19 -0
  77. package/packages/core/src/plugins/runtimes.ts +5 -1
  78. package/packages/core/src/ports/runner/session-env.ts +2 -0
  79. package/packages/core/src/ports/tickets/contract.ts +66 -1
  80. package/packages/core/src/ports/tickets/create.ts +48 -12
  81. package/packages/core/src/ports/tickets/fake.ts +30 -1
  82. package/packages/core/src/ports/tickets/file-assets.ts +159 -0
  83. package/packages/core/src/ports/tickets/file-codec.ts +90 -0
  84. package/packages/core/src/ports/tickets/file.ts +114 -120
  85. package/packages/core/src/ports/tickets/grants.ts +47 -0
  86. package/packages/core/src/ports/tickets/hosted-index.ts +17 -0
  87. package/packages/core/src/ports/tickets/identity.ts +20 -0
  88. package/packages/core/src/ports/tickets/lifecycle.ts +18 -0
  89. package/packages/core/src/ports/tickets/linear.ts +74 -9
  90. package/packages/core/src/ports/tickets/migration.ts +336 -0
  91. package/packages/core/src/ports/tickets/operations.ts +38 -7
  92. package/packages/core/src/ports/tickets/portable-assets.ts +54 -0
  93. package/packages/core/src/ports/tickets/remote-protocol.ts +95 -0
  94. package/packages/core/src/ports/tickets/remote.ts +60 -3
  95. package/packages/core/src/ports/tickets/requested-id.ts +46 -0
  96. package/packages/core/src/ports/tickets/revision.ts +62 -0
  97. package/packages/core/src/ports/tickets/states.ts +61 -0
  98. package/packages/core/src/ports/types.ts +40 -3
  99. package/packages/core/src/ports/workspace/build-execution.ts +1 -0
  100. package/packages/core/src/ports/workspace/create.ts +5 -1
  101. package/packages/core/src/ports/workspace/operator-sandbox.ts +2 -0
  102. package/packages/core/src/ports/workspace/vercel-sandbox.ts +8 -4
  103. package/packages/core/src/processes/build-child.ts +6 -6
  104. package/packages/core/src/processes/build-runner.ts +62 -70
  105. package/packages/core/src/processes/dispatch-failure-budget.ts +5 -4
  106. package/packages/core/src/processes/dispatcher.ts +223 -205
  107. package/packages/core/src/processes/execution-no-progress.ts +176 -0
  108. package/packages/core/src/processes/harvest-child.ts +30 -4
  109. package/packages/core/src/processes/harvest-execution-state.ts +15 -9
  110. package/packages/core/src/processes/harvest-runner.ts +58 -74
  111. package/packages/core/src/processes/harvest.ts +72 -29
  112. package/packages/core/src/processes/infrastructure-failure-budget.ts +13 -15
  113. package/packages/core/src/processes/orchestrator-tick.ts +2 -2
  114. package/packages/core/src/processes/phase-failure-budget.ts +1 -0
  115. package/packages/core/src/processes/publication-loss.ts +35 -21
  116. package/packages/core/src/processes/repo-view.ts +229 -1099
  117. package/packages/core/src/processes/tick-publication.ts +110 -0
  118. package/packages/core/src/store/adapter.ts +23 -0
  119. package/packages/core/src/store/build-scope.ts +17 -0
  120. package/packages/core/src/store/contract.ts +283 -1
  121. package/packages/core/src/store/local/schema.ts +1 -0
  122. package/packages/core/src/store/local/store.ts +180 -55
  123. package/packages/core/src/store/memory.ts +119 -29
  124. package/packages/core/src/store/phase-session.ts +17 -0
  125. package/packages/core/src/store/reduced-reads.ts +239 -0
  126. package/packages/core/src/store/reducer-value-shapes.json +14056 -0
  127. package/packages/core/src/store/reducer-value-validation.ts +96 -0
  128. package/packages/core/src/store/reducer-values.ts +145 -0
  129. package/packages/core/src/store/remote/client.ts +41 -0
  130. package/packages/core/src/store/remote/compatibility.ts +2 -0
  131. package/packages/core/src/store/remote/index.ts +19 -0
  132. package/packages/core/src/store/remote/protocol.ts +16 -0
  133. package/packages/core/src/store/remote/version.ts +3 -1
  134. package/packages/core/src/store/repo-state-events.ts +1 -1
  135. package/packages/core/src/store/repo-tick.ts +72 -0
  136. package/packages/core/src/store/session-handle.ts +19 -0
  137. package/packages/core/src/store/snapshots.ts +10 -0
  138. package/packages/core/src/store/ticket-asset-snapshots.ts +192 -0
  139. package/packages/core/src/store/ticket-assets.ts +86 -8
  140. package/packages/core/src/store/types.ts +40 -3
  141. package/packages/core/src/testing/dispatch-read-budget.ts +215 -0
  142. package/packages/core/src/testing/index.ts +8 -0
  143. package/packages/core/src/testing/reduced-read-budget.ts +462 -0
  144. package/skills/guide/SKILL.md +97 -12
  145. package/skills/guide/references/operator-api.md +8 -1
  146. package/skills/guide/references/remote-store-protocol.md +94 -2
  147. package/skills/guide/references/remote-ticket-protocol.md +151 -9
  148. package/skills/guide/references/setup.md +33 -7
  149. package/skills/guide/references/ticket-migration.md +64 -0
  150. package/skills/tickets/SKILL.md +71 -0
  151. package/templates/hosted-service/package.json +2 -2
@@ -1,3 +1,4 @@
1
+ import { readDeferralLedger } from '../store/reduced-reads'
1
2
  import type { AbEvent, EventEnvelope, EventWrite } from '../events/catalog'
2
3
  import { KERNEL } from '../events/envelope'
3
4
  import { autoMergeDeferralClasses, type AutoMergeDeferralReason } from '../ports/types'
@@ -322,10 +323,10 @@ export async function recordAutoMergeDeferralObservation(
322
323
  ): Promise<EventEnvelope<'observation.recorded'> | null> {
323
324
  if (autoMergeDeferralClasses[reason.code] === 'pipeline-resolved') return null
324
325
  while (true) {
325
- const events = await store.getEvents(slug)
326
- if (hasAutoMergeDeferralObservation(events, prNumber, commandSeq)) return null
326
+ const current = await readDeferralLedger(store, slug)
327
+ if (deferralObservationFrom(current.value, prNumber, commandSeq)) return null
327
328
 
328
- const expectedSeq = events.at(-1)?.seq ?? 0
329
+ const expectedSeq = current.cursor
329
330
  const appended = await store.appendIfCurrent(
330
331
  slug,
331
332
  expectedSeq,
@@ -334,3 +335,18 @@ export async function recordAutoMergeDeferralObservation(
334
335
  if (appended !== null) return appended
335
336
  }
336
337
  }
338
+
339
+ /** Clock-independent merge attention shared by persisted seeds and CLI output. */
340
+ export function mergeAttentionFromFacts(state: BuildState, ledger: DeferralLedger) {
341
+ if (
342
+ !state.finalizeCompletedSeq ||
343
+ !state.pr ||
344
+ state.prState !== 'open' ||
345
+ state.status === 'done' ||
346
+ state.status === 'aborted'
347
+ )
348
+ return null
349
+ const deferral = currentAutoMergeDeferralFrom(ledger, state)
350
+ if (state.autoMerge.requested && deferral === undefined) return null
351
+ return { pr: state.pr, reason: deferral ?? 'Auto-merge is not armed' }
352
+ }
@@ -18,6 +18,9 @@ export function projectDispatcherLiveness(
18
18
  (e.type === 'dispatcher.tick-completed' || e.type === 'dispatcher.tick-failed') &&
19
19
  e.payload.run === id,
20
20
  )
21
+ const finished = record?.tick?.finished?.run === id ? record.tick.finished : undefined
22
+ const useSample = finished !== undefined && (!tick || finished.completedAt >= tick.ts)
23
+ const lastTickAt = useSample ? finished.completedAt : (tick?.ts ?? null)
21
24
  const heartbeatAt =
22
25
  record?.lease?.holder === id && start && record.heartbeatAt && record.heartbeatAt >= start.ts
23
26
  ? record.heartbeatAt
@@ -30,10 +33,13 @@ export function projectDispatcherLiveness(
30
33
  heartbeatAt,
31
34
  heartbeatAgeMs: heartbeatAt === null ? null : age(heartbeatAt),
32
35
  leaseExpiresAt: record?.lease?.holder === id ? record.lease.expiresAt : null,
33
- lastTickAt: tick?.ts ?? null,
34
- lastTickAgeMs: tick ? age(tick.ts) : null,
35
- lastTick:
36
- tick?.type === 'dispatcher.tick-completed'
36
+ lastTickAt,
37
+ lastTickAgeMs: lastTickAt ? age(lastTickAt) : null,
38
+ lastTick: useSample
39
+ ? finished.outcome === 'completed'
40
+ ? { outcome: 'completed' as const, counters: finished.counters }
41
+ : { outcome: 'failed' as const, error: finished.error }
42
+ : tick?.type === 'dispatcher.tick-completed'
37
43
  ? { outcome: 'completed' as const, counters: tick.payload.counters }
38
44
  : tick?.type === 'dispatcher.tick-failed'
39
45
  ? { outcome: 'failed' as const, error: tick.payload.error }
@@ -151,7 +151,7 @@ function randomWrite(rand: () => number): EventWrite {
151
151
  id,
152
152
  phase: 'plan',
153
153
  source: 'policy',
154
- policyCause: 'infrastructure-failure-limit',
154
+ policyCause: round % 2 === 0 ? 'execution-no-progress' : 'infrastructure-failure-limit',
155
155
  question: 'q',
156
156
  }),
157
157
  () =>
@@ -119,13 +119,24 @@ export function randomSettingsJournal(seed: number, length: number): RepositoryE
119
119
  }
120
120
 
121
121
  const counters = {
122
- claimed: 0,
123
- launched: 0,
124
122
  merged: 0,
125
123
  closed: 0,
126
124
  conflicted: 0,
127
- reconciled: 0,
125
+ abandoned: 0,
126
+ discarded: 0,
127
+ janitorFailed: 0,
128
+ recovered: 0,
129
+ dispatchFailed: 0,
130
+ resumed: 0,
131
+ swept: 0,
132
+ dispatched: 0,
133
+ authored: 0,
134
+ bounced: 0,
135
+ claimRaces: 0,
136
+ invalidTickets: 0,
137
+ dependencyBlocked: 0,
128
138
  harvestStarted: 0,
139
+ harvestResumed: 0,
129
140
  harvestCompleted: 0,
130
141
  harvestEscalated: 0,
131
142
  harvestFailed: 0,
@@ -741,32 +741,34 @@ export function harvestCreationsDuringReadyScan(
741
741
  beforeEvents: readonly RepositoryEvent[],
742
742
  afterEvents: readonly RepositoryEvent[],
743
743
  ): InFlightHarvestCreation[] {
744
- const before = reduceHarvest(beforeEvents)
745
- // Validate the complete second snapshot before using its event suffix.
746
- reduceHarvest(afterEvents)
744
+ return harvestCreationsFromStates(reduceHarvest(beforeEvents), reduceHarvest(afterEvents))
745
+ }
746
+
747
+ export function harvestCreationsFromStates(
748
+ before: HarvestState,
749
+ after: HarvestState,
750
+ ): InFlightHarvestCreation[] {
747
751
  const creations = new Map<string, InFlightHarvestCreation>()
748
- const keyOf = (run: string, proposalKey: string): string => `${run}\0${proposalKey}`
749
- const known = new Set<string>()
750
- for (const run of before.runs) {
752
+ const keyOf = (run: string, proposalKey: string) => `${run}\0${proposalKey}`
753
+ const known = new Set(
754
+ before.runs.flatMap((run) =>
755
+ run.reservations.map((entry) => keyOf(run.run, entry.proposalKey)),
756
+ ),
757
+ )
758
+ for (const creation of inFlightHarvestCreations(before))
759
+ creations.set(keyOf(creation.run, creation.proposalKey), creation)
760
+ for (const run of after.runs)
751
761
  for (const reservation of run.reservations) {
752
- known.add(keyOf(run.run, reservation.proposalKey))
762
+ const key = keyOf(run.run, reservation.proposalKey)
763
+ if (known.has(key) || reservation.seq <= before.lastSeq) continue
764
+ known.add(key)
765
+ creations.set(key, {
766
+ run: run.run,
767
+ proposalKey: reservation.proposalKey,
768
+ creationKey: reservation.id,
769
+ reservationSeq: reservation.seq,
770
+ })
753
771
  }
754
- }
755
- for (const creation of inFlightHarvestCreations(before)) {
756
- creations.set(keyOf(creation.run, creation.proposalKey), creation)
757
- }
758
- for (const event of afterEvents) {
759
- if (event.seq <= before.lastSeq || event.type !== 'harvest.proposal.id-reserved') continue
760
- const key = keyOf(event.payload.run, event.payload.proposalKey)
761
- if (known.has(key)) continue
762
- known.add(key)
763
- creations.set(key, {
764
- run: event.payload.run,
765
- proposalKey: event.payload.proposalKey,
766
- creationKey: event.payload.id,
767
- reservationSeq: event.seq,
768
- })
769
- }
770
772
  return [...creations.values()]
771
773
  }
772
774
 
@@ -99,7 +99,12 @@ export function randomBuildItem(rand: Rand): readonly [string, Record<string, un
99
99
  ],
100
100
  () => [
101
101
  'execution.ended',
102
- { instance, workspaceRef: ref, outcome: pick(rand, ['completed', 'stopped', 'lost']) },
102
+ {
103
+ instance,
104
+ workspaceRef: ref,
105
+ outcome: pick(rand, ['completed', 'stopped', 'lost']),
106
+ ...(rand() < 0.5 ? { exitCode: pick(rand, [0, 1, null]) } : {}),
107
+ },
103
108
  ],
104
109
  () => [
105
110
  'workspace.provisioned',
@@ -132,7 +137,13 @@ export function randomBuildItem(rand: Rand): readonly [string, Record<string, un
132
137
  phase: pick(rand, ['setup', 'plan', 'implement', 'verify:unit']),
133
138
  source: pick(rand, ['policy', 'agent']),
134
139
  ...(rand() < 0.7
135
- ? { policyCause: pick(rand, ['infrastructure-failure-limit', 'setup-failure-limit']) }
140
+ ? {
141
+ policyCause: pick(rand, [
142
+ 'infrastructure-failure-limit',
143
+ 'setup-failure-limit',
144
+ 'execution-no-progress',
145
+ ]),
146
+ }
136
147
  : {}),
137
148
  ...(rand() < 0.5 ? { round: 1 } : {}),
138
149
  question: 'q',
@@ -0,0 +1,38 @@
1
+ import type { LogIndex, LoopIndex, RoundRecord } from './log-index'
2
+ type JsonLoop = Omit<LoopIndex, 'rounds'> & {
3
+ rounds: [number, RoundRecord][]
4
+ }
5
+ export type JsonLogIndex = Omit<LogIndex, 'plan' | 'code' | 'finalizeStepsDone'> & {
6
+ plan: JsonLoop
7
+ code: JsonLoop
8
+ finalizeStepsDone: string[]
9
+ }
10
+ export function encodeLog(log: LogIndex): JsonLogIndex {
11
+ return {
12
+ ...log,
13
+ plan: { ...log.plan, rounds: [...log.plan.rounds] },
14
+ code: { ...log.code, rounds: [...log.code.rounds] },
15
+ finalizeStepsDone: [...log.finalizeStepsDone],
16
+ }
17
+ }
18
+ export function decodeLog(log: JsonLogIndex): LogIndex {
19
+ const loop = (value: JsonLoop): LoopIndex => {
20
+ for (const entry of value.rounds) {
21
+ if (
22
+ entry.length !== 2 ||
23
+ !Number.isSafeInteger(entry[0]) ||
24
+ entry[1] === null ||
25
+ typeof entry[1] !== 'object' ||
26
+ Array.isArray(entry[1])
27
+ )
28
+ throw new Error('invalid persisted log-index round entry')
29
+ }
30
+ return { ...value, rounds: new Map(value.rounds) }
31
+ }
32
+ return {
33
+ ...log,
34
+ plan: loop(log.plan),
35
+ code: loop(log.code),
36
+ finalizeStepsDone: new Set(log.finalizeStepsDone),
37
+ }
38
+ }