@defrex/autobuild 0.13.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 (94) hide show
  1. package/README.md +1 -1
  2. package/SPEC.md +19 -1
  3. package/bin/ab-dispatch-kernel.ts +4 -6
  4. package/docs/architecture.md +96 -70
  5. package/docs/hosted-service.md +2 -2
  6. package/docs/operator-api.md +1 -1
  7. package/docs/remote-store-protocol.md +84 -2
  8. package/package.json +4 -3
  9. package/packages/core/src/cli/attention.ts +18 -164
  10. package/packages/core/src/cli/build-control.ts +11 -15
  11. package/packages/core/src/cli/bulk-control.ts +3 -2
  12. package/packages/core/src/cli/context.ts +16 -78
  13. package/packages/core/src/cli/dashboard/model.ts +36 -22
  14. package/packages/core/src/cli/dispatch-frontend.ts +24 -67
  15. package/packages/core/src/cli/dispatch-process.ts +14 -7
  16. package/packages/core/src/cli/dispatch-repository-feed.ts +196 -0
  17. package/packages/core/src/cli/dispatch.ts +148 -79
  18. package/packages/core/src/cli/dispatcher-liveness.ts +26 -19
  19. package/packages/core/src/cli/harvest.ts +71 -39
  20. package/packages/core/src/cli/historical-build-state.ts +27 -0
  21. package/packages/core/src/cli/main.ts +3 -3
  22. package/packages/core/src/cli/pr-attachments.ts +10 -13
  23. package/packages/core/src/cli/pr-summary-publication.ts +27 -60
  24. package/packages/core/src/cli/pr-summary.ts +43 -13
  25. package/packages/core/src/cli/publication-settlement.ts +10 -20
  26. package/packages/core/src/cli/repository-status.ts +38 -2
  27. package/packages/core/src/cli/status.ts +62 -32
  28. package/packages/core/src/cli/terminals.ts +25 -142
  29. package/packages/core/src/cli/ticket.ts +2 -2
  30. package/packages/core/src/cli/wait.ts +91 -84
  31. package/packages/core/src/cli/watch.ts +60 -97
  32. package/packages/core/src/config/pipeline-source.ts +4 -3
  33. package/packages/core/src/events/repository.ts +7 -3
  34. package/packages/core/src/integration/harness.ts +1 -2
  35. package/packages/core/src/kernel/__snapshots__/reducer-registry.test.ts.snap +2429 -9
  36. package/packages/core/src/kernel/auto-merge.ts +19 -3
  37. package/packages/core/src/kernel/dispatcher-liveness.ts +10 -4
  38. package/packages/core/src/kernel/generators/repository-journals.ts +14 -3
  39. package/packages/core/src/kernel/harvest.ts +25 -23
  40. package/packages/core/src/kernel/log-index-value.ts +38 -0
  41. package/packages/core/src/kernel/read-facts.ts +637 -0
  42. package/packages/core/src/kernel/reducer-registry.ts +137 -3
  43. package/packages/core/src/kernel/repository-attention.ts +179 -0
  44. package/packages/core/src/kernel/service-facts.ts +135 -0
  45. package/packages/core/src/kernel/terminal-guard.ts +133 -0
  46. package/packages/core/src/operator/control.ts +5 -6
  47. package/packages/core/src/operator/index.ts +2 -0
  48. package/packages/core/src/operator/query.ts +37 -20
  49. package/packages/core/src/operator/read-view.ts +4 -13
  50. package/packages/core/src/operator/requests.ts +2 -14
  51. package/packages/core/src/operator/sandbox.ts +10 -9
  52. package/packages/core/src/operator/tickets.ts +2 -2
  53. package/packages/core/src/plugin-sdk/index.ts +13 -0
  54. package/packages/core/src/processes/build-child.ts +6 -6
  55. package/packages/core/src/processes/build-runner.ts +62 -70
  56. package/packages/core/src/processes/dispatch-failure-budget.ts +5 -4
  57. package/packages/core/src/processes/dispatcher.ts +113 -173
  58. package/packages/core/src/processes/harvest-execution-state.ts +15 -9
  59. package/packages/core/src/processes/harvest-runner.ts +34 -70
  60. package/packages/core/src/processes/harvest.ts +38 -15
  61. package/packages/core/src/processes/infrastructure-failure-budget.ts +13 -15
  62. package/packages/core/src/processes/orchestrator-tick.ts +2 -2
  63. package/packages/core/src/processes/publication-loss.ts +35 -21
  64. package/packages/core/src/processes/repo-view.ts +229 -1134
  65. package/packages/core/src/processes/tick-publication.ts +110 -0
  66. package/packages/core/src/store/adapter.ts +23 -0
  67. package/packages/core/src/store/build-scope.ts +17 -0
  68. package/packages/core/src/store/contract.ts +251 -0
  69. package/packages/core/src/store/local/schema.ts +1 -0
  70. package/packages/core/src/store/local/store.ts +179 -54
  71. package/packages/core/src/store/memory.ts +119 -29
  72. package/packages/core/src/store/phase-session.ts +17 -0
  73. package/packages/core/src/store/reduced-reads.ts +239 -0
  74. package/packages/core/src/store/reducer-value-shapes.json +14056 -0
  75. package/packages/core/src/store/reducer-value-validation.ts +96 -0
  76. package/packages/core/src/store/reducer-values.ts +145 -0
  77. package/packages/core/src/store/remote/client.ts +41 -0
  78. package/packages/core/src/store/remote/compatibility.ts +2 -0
  79. package/packages/core/src/store/remote/index.ts +19 -0
  80. package/packages/core/src/store/remote/protocol.ts +16 -0
  81. package/packages/core/src/store/remote/version.ts +3 -1
  82. package/packages/core/src/store/repo-state-events.ts +1 -1
  83. package/packages/core/src/store/repo-tick.ts +72 -0
  84. package/packages/core/src/store/session-handle.ts +19 -0
  85. package/packages/core/src/store/snapshots.ts +10 -0
  86. package/packages/core/src/store/ticket-assets.ts +4 -4
  87. package/packages/core/src/store/types.ts +39 -2
  88. package/packages/core/src/testing/dispatch-read-budget.ts +215 -0
  89. package/packages/core/src/testing/index.ts +8 -0
  90. package/packages/core/src/testing/reduced-read-budget.ts +462 -0
  91. package/skills/guide/SKILL.md +6 -1
  92. package/skills/guide/references/operator-api.md +1 -1
  93. package/skills/guide/references/remote-store-protocol.md +84 -2
  94. 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 }
@@ -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
 
@@ -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
+ }