@defrex/autobuild 0.11.0 → 0.12.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 (96) hide show
  1. package/README.md +3 -1
  2. package/SPEC.md +222 -22
  3. package/docs/architecture.md +66 -4
  4. package/docs/configuration.md +105 -16
  5. package/docs/hosted-dispatcher.md +17 -2
  6. package/docs/hosted-service.md +214 -0
  7. package/docs/mcp.md +8 -2
  8. package/docs/operator-api.md +90 -7
  9. package/docs/remote-store-protocol.md +189 -144
  10. package/docs/remote-ticket-protocol.md +107 -19
  11. package/docs/setup.md +173 -16
  12. package/docs/ticket-dependencies.md +1 -1
  13. package/package.json +7 -6
  14. package/packages/core/src/cli/attention.ts +209 -0
  15. package/packages/core/src/cli/configured-store.ts +17 -0
  16. package/packages/core/src/cli/connect.ts +261 -0
  17. package/packages/core/src/cli/dashboard/model.ts +4 -5
  18. package/packages/core/src/cli/dispatch.ts +183 -41
  19. package/packages/core/src/cli/help.ts +39 -10
  20. package/packages/core/src/cli/init-validation.ts +27 -2
  21. package/packages/core/src/cli/main.ts +41 -15
  22. package/packages/core/src/cli/mcp.ts +7 -1
  23. package/packages/core/src/cli/pr-summary-publication.ts +220 -0
  24. package/packages/core/src/cli/pr-summary.ts +2 -0
  25. package/packages/core/src/cli/project-credentials.ts +132 -0
  26. package/packages/core/src/cli/repo-state.ts +15 -2
  27. package/packages/core/src/cli/self-update.ts +10 -0
  28. package/packages/core/src/cli/store-opening.ts +22 -14
  29. package/packages/core/src/cli/terminals.ts +0 -10
  30. package/packages/core/src/cli/ticket.ts +8 -1
  31. package/packages/core/src/cli/upgrade-compatibility.ts +80 -0
  32. package/packages/core/src/cli/wait.ts +84 -17
  33. package/packages/core/src/cli/watch.ts +197 -60
  34. package/packages/core/src/config/live.ts +3 -0
  35. package/packages/core/src/config/schema.ts +56 -9
  36. package/packages/core/src/connection.ts +86 -0
  37. package/packages/core/src/events/catalog.ts +10 -2
  38. package/packages/core/src/events/payloads.ts +44 -0
  39. package/packages/core/src/events/repository.ts +13 -0
  40. package/packages/core/src/kernel/__snapshots__/reducer-registry.test.ts.snap +130 -83
  41. package/packages/core/src/kernel/engine.ts +24 -0
  42. package/packages/core/src/kernel/generators/build-log.ts +10 -0
  43. package/packages/core/src/kernel/log-index.ts +17 -0
  44. package/packages/core/src/kernel/pr-summary.ts +89 -0
  45. package/packages/core/src/kernel/reducer-registry.ts +8 -0
  46. package/packages/core/src/kernel/reducer.ts +8 -0
  47. package/packages/core/src/ontology.ts +1 -0
  48. package/packages/core/src/operator/index.ts +7 -0
  49. package/packages/core/src/operator/query.ts +38 -16
  50. package/packages/core/src/operator/ticket-source-backend.ts +1 -0
  51. package/packages/core/src/operator/tickets.ts +6 -8
  52. package/packages/core/src/plugin-sdk/index.ts +1 -0
  53. package/packages/core/src/plugins/manifest.ts +5 -3
  54. package/packages/core/src/plugins/runtimes.ts +3 -1
  55. package/packages/core/src/ports/forge/fake.ts +13 -1
  56. package/packages/core/src/ports/forge/github-transport.ts +36 -1
  57. package/packages/core/src/ports/forge/github.ts +29 -5
  58. package/packages/core/src/ports/forge/local-git.ts +12 -4
  59. package/packages/core/src/ports/forge/summary-marker.ts +8 -0
  60. package/packages/core/src/ports/runner/session-env.ts +1 -0
  61. package/packages/core/src/ports/tickets/create.ts +11 -3
  62. package/packages/core/src/ports/tickets/hosted-index.ts +0 -3
  63. package/packages/core/src/ports/tickets/remote-protocol.ts +1 -0
  64. package/packages/core/src/ports/tickets/remote.ts +1 -0
  65. package/packages/core/src/ports/workspace/build-execution.ts +5 -1
  66. package/packages/core/src/ports/workspace/create.ts +11 -2
  67. package/packages/core/src/ports/workspace/distribution-archive.ts +3 -5
  68. package/packages/core/src/ports/workspace/harvest-execution.ts +3 -1
  69. package/packages/core/src/ports/workspace/local-build-execution.ts +8 -1
  70. package/packages/core/src/ports/workspace/vercel-sandbox.ts +47 -19
  71. package/packages/core/src/processes/build-execution-state.ts +1 -1
  72. package/packages/core/src/processes/build-runner.ts +87 -26
  73. package/packages/core/src/processes/dispatch-failure-budget.ts +83 -0
  74. package/packages/core/src/processes/dispatcher.ts +59 -10
  75. package/packages/core/src/processes/harvest-child.ts +1 -0
  76. package/packages/core/src/processes/harvest-execution-state.ts +19 -2
  77. package/packages/core/src/processes/harvest-runner.ts +142 -100
  78. package/packages/core/src/processes/phase-failure-budget.ts +1 -0
  79. package/packages/core/src/processes/publication-loss.ts +18 -1
  80. package/packages/core/src/store/remote/client.ts +1 -1
  81. package/packages/core/src/store/remote/compatibility.ts +90 -0
  82. package/packages/core/src/store/remote/credentials.ts +28 -0
  83. package/packages/core/src/store/remote/index.ts +14 -1
  84. package/packages/core/src/store/remote/protocol.ts +25 -0
  85. package/packages/core/src/store/remote/token.ts +74 -13
  86. package/packages/core/src/testing/index.ts +7 -0
  87. package/skills/guide/SKILL.md +144 -26
  88. package/skills/guide/references/operator-api.md +135 -13
  89. package/skills/guide/references/plugin-authoring.md +10 -2
  90. package/skills/guide/references/remote-store-protocol.md +189 -144
  91. package/skills/guide/references/remote-ticket-protocol.md +111 -18
  92. package/skills/guide/references/setup.md +173 -16
  93. package/skills/guide/references/ticket-dependencies.md +1 -1
  94. package/skills/operate/SKILL.md +126 -36
  95. package/templates/hosted-service/package.json +13 -0
  96. package/templates/hosted-service/vercel.json +7 -0
@@ -51,6 +51,11 @@ export const allowedActorKinds: Record<EventType, readonly ActorKind[]> = {
51
51
  'execution.ended': ['dispatcher'],
52
52
  'infrastructure.failed': ['dispatcher'],
53
53
  'infrastructure.cleanup-attempted': ['dispatcher'],
54
+ 'pr-summary.requested': ['kernel'],
55
+ 'pr-summary.attempted': ['kernel'],
56
+ 'pr-summary.result': ['kernel'],
57
+ 'pr-summary.settled': ['kernel'],
58
+ 'finalize.step-started': ['kernel'],
54
59
  'publication.requested': ['agent', 'kernel'],
55
60
  // Only the dispatcher releases workspaces and settles publications (§15.7);
56
61
  // the kernel never destroys one, so it never records a loss.
@@ -88,6 +93,7 @@ export const allowedActorKinds: Record<EventType, readonly ActorKind[]> = {
88
93
  'code-review.started': ['kernel'],
89
94
  'code-review.verdict': ['agent'],
90
95
 
96
+ 'verify.reconcile-policy-captured': ['kernel'],
91
97
  'verify.started': ['kernel'],
92
98
  'verify.completed': ['kernel', 'agent'],
93
99
  'finalize.started': ['kernel'],
@@ -177,10 +183,12 @@ export function validateEventWrite(input: {
177
183
  const escalation = payloadResult.data as EventPayload<'escalation.raised'>
178
184
  if (
179
185
  actor.kind === 'dispatcher' &&
180
- (escalation.source !== 'policy' || escalation.policyCause !== 'infrastructure-failure-limit')
186
+ (escalation.source !== 'policy' ||
187
+ (escalation.policyCause !== 'infrastructure-failure-limit' &&
188
+ escalation.policyCause !== 'dispatch-failure-limit'))
181
189
  ) {
182
190
  throw new EventValidationError(
183
- 'invalid dispatcher escalation: only policy/infrastructure-failure-limit may be raised outside the kernel',
191
+ 'invalid dispatcher escalation: only policy/infrastructure-failure-limit or policy/dispatch-failure-limit may be raised outside the kernel',
184
192
  )
185
193
  }
186
194
  if (escalation.source === 'policy' && escalation.policyCause === undefined) {
@@ -172,6 +172,7 @@ const verifyCompletedPayloadSchema = z.union([
172
172
  z.strictObject({
173
173
  ...verifyCompletionBase,
174
174
  outcome: z.literal('skipped'),
175
+ skipCause: z.literal('post-reconcile-policy').optional(),
175
176
  reason: z.string().trim().min(1, 'a skipped verification requires a non-blank reason'),
176
177
  }),
177
178
  ]),
@@ -336,6 +337,26 @@ export const eventPayloadSchemas = {
336
337
  })
337
338
  .optional(),
338
339
  }),
340
+ /** Optional summary publication identities and durable write budgets. */
341
+ 'pr-summary.requested': z.strictObject({
342
+ occurrence: z.number().int().positive(),
343
+ boundary: z.number().int().positive(),
344
+ }),
345
+ 'pr-summary.attempted': z.strictObject({
346
+ occurrence: z.number().int().positive(),
347
+ boundary: z.number().int().positive(),
348
+ pr: z.number().int().positive(),
349
+ }),
350
+ 'pr-summary.result': z.strictObject({
351
+ attempt: z.number().int().positive(),
352
+ outcome: z.enum(['success', 'failure', 'rate-limited']),
353
+ eligibleAfter: z.string().datetime().optional(),
354
+ }),
355
+ 'pr-summary.settled': z.strictObject({
356
+ request: z.number().int().positive(),
357
+ disposition: z.enum(['attempted', 'skipped', 'deferred']),
358
+ }),
359
+ 'finalize.step-started': z.strictObject({ step: z.string().min(1) }),
339
360
  /** Remote phases deposit their output and park before trusted publication.
340
361
  * The dispatcher settles the request only after the VM session and execution
341
362
  * lease are gone, then appends the ordinary phase completion fact. */
@@ -515,6 +536,27 @@ export const eventPayloadSchemas = {
515
536
  * starts. */
516
537
  feedback: feedbackSchema.optional(),
517
538
  }),
539
+ 'verify.reconcile-policy-captured': z.strictObject({
540
+ reconcileSeq: z.number().int().positive(),
541
+ steps: z
542
+ .array(
543
+ z.strictObject({
544
+ step: z.string().min(1),
545
+ enabled: z.boolean(),
546
+ source: z.string().min(1),
547
+ }),
548
+ )
549
+ .superRefine((steps, ctx) => {
550
+ if (new Set(steps.map((s) => s.step)).size !== steps.length)
551
+ ctx.addIssue({ code: 'custom', message: 'duplicate policy step' })
552
+ for (const s of steps)
553
+ if (
554
+ s.source !== 'verify.afterReconcile' &&
555
+ s.source !== `verify.${s.step}.afterReconcile`
556
+ )
557
+ ctx.addIssue({ code: 'custom', message: 'invalid policy source' })
558
+ }),
559
+ }),
518
560
  'verify.completed': verifyCompletedPayloadSchema,
519
561
  'finalize.started': empty,
520
562
  'finalize.completed': z.strictObject({
@@ -630,6 +672,7 @@ export interface NormalizedVerifyCompletion {
630
672
  outcome: VerifyOutcome
631
673
  report?: ArtifactRef
632
674
  reason?: string
675
+ skipCause?: 'post-reconcile-policy'
633
676
  }
634
677
 
635
678
  /**
@@ -653,6 +696,7 @@ export function normalizeVerifyCompletion(
653
696
  attempt: payload.attempt,
654
697
  outcome: 'skipped',
655
698
  reason: payload.reason,
699
+ ...(payload.skipCause !== undefined ? { skipCause: payload.skipCause } : {}),
656
700
  }
657
701
  }
658
702
  return {
@@ -90,6 +90,7 @@ const tickCountersSchema = z.strictObject({
90
90
  const restartRequiredConfigPathSchema = z.enum([
91
91
  'forge',
92
92
  'plugins',
93
+ 'store',
93
94
  'workspace.provider',
94
95
  'workspace.config',
95
96
  'tickets.source',
@@ -154,6 +155,16 @@ export const harvestEventPayloadSchemas = {
154
155
  * command supervising the guest runner. Written as soon as the launch
155
156
  * succeeds so a later process can re-observe the execution without
156
157
  * process memory. */
158
+ 'harvest.execution.authorized': z.strictObject({
159
+ execution: z.string().min(1),
160
+ run: z.string().min(1),
161
+ launch: z.enum(['started', 'resumed']),
162
+ leaseHolder: z.string().min(1),
163
+ }),
164
+ 'harvest.execution.ended': z.strictObject({
165
+ execution: z.string().min(1),
166
+ run: z.string().min(1),
167
+ }),
157
168
  'harvest.execution.started': z.strictObject({
158
169
  execution: z.string().min(1),
159
170
  provider: z.string().min(1),
@@ -557,6 +568,8 @@ const allowedActorKinds: Record<RepositoryEventType, readonly ActorKind[]> = {
557
568
  'harvest.recovery-requested': ['kernel'],
558
569
  'harvest.recovery-exhausted': ['kernel'],
559
570
  'harvest.started': ['dispatcher', 'kernel'],
571
+ 'harvest.execution.authorized': ['dispatcher'],
572
+ 'harvest.execution.ended': ['dispatcher'],
560
573
  'harvest.execution.started': ['dispatcher'],
561
574
  'harvest.execution.released': ['dispatcher'],
562
575
  'harvest.step.started': ['kernel'],
@@ -138,27 +138,28 @@ exports[`reducer registry accumulator shapes 1`] = `
138
138
  "$.lastEvent.actor.user: string",
139
139
  "$.lastEvent.actor: object",
140
140
  "$.lastEvent.build: string",
141
+ "$.lastEvent.payload.answer: string",
141
142
  "$.lastEvent.payload.artifact.kind: string",
142
143
  "$.lastEvent.payload.artifact.rev: number",
143
144
  "$.lastEvent.payload.artifact: object",
144
- "$.lastEvent.payload.asset.assetId: number",
145
- "$.lastEvent.payload.asset.provider: string",
146
- "$.lastEvent.payload.asset.releaseId: number",
147
- "$.lastEvent.payload.asset.repository: string",
148
- "$.lastEvent.payload.asset.url: string",
149
- "$.lastEvent.payload.asset: object",
145
+ "$.lastEvent.payload.assets: array",
146
+ "$.lastEvent.payload.assets[].dirCount: number",
147
+ "$.lastEvent.payload.assets[].fileCount: number",
148
+ "$.lastEvent.payload.assets[].kind: string",
149
+ "$.lastEvent.payload.assets[].layout: string",
150
+ "$.lastEvent.payload.assets[].name: string",
151
+ "$.lastEvent.payload.assets[].revision: number",
152
+ "$.lastEvent.payload.assets[].size: number",
153
+ "$.lastEvent.payload.assets[]: object",
150
154
  "$.lastEvent.payload.attempt: number",
151
155
  "$.lastEvent.payload.base.sha: string",
152
156
  "$.lastEvent.payload.base.source: string",
153
157
  "$.lastEvent.payload.base: object",
158
+ "$.lastEvent.payload.base: string",
159
+ "$.lastEvent.payload.baseSha: string",
160
+ "$.lastEvent.payload.boundary: number",
154
161
  "$.lastEvent.payload.branch: string",
155
162
  "$.lastEvent.payload.cause: string",
156
- "$.lastEvent.payload.cleanupPending: boolean",
157
- "$.lastEvent.payload.command: string",
158
- "$.lastEvent.payload.commandSeq: number",
159
- "$.lastEvent.payload.commits.base: string",
160
- "$.lastEvent.payload.commits.head: string",
161
- "$.lastEvent.payload.commits: object",
162
163
  "$.lastEvent.payload.counters.claimed: number",
163
164
  "$.lastEvent.payload.counters.closed: number",
164
165
  "$.lastEvent.payload.counters.conflicted: number",
@@ -174,7 +175,6 @@ exports[`reducer registry accumulator shapes 1`] = `
174
175
  "$.lastEvent.payload.decision: string",
175
176
  "$.lastEvent.payload.defaultSeq: number",
176
177
  "$.lastEvent.payload.dependencyDiagnostics: array",
177
- "$.lastEvent.payload.designationSeq: number",
178
178
  "$.lastEvent.payload.dispositions: array",
179
179
  "$.lastEvent.payload.dispositions[].action: string",
180
180
  "$.lastEvent.payload.dispositions[].occurrence.build: string",
@@ -185,15 +185,15 @@ exports[`reducer registry accumulator shapes 1`] = `
185
185
  "$.lastEvent.payload.effectiveConfig.kind: string",
186
186
  "$.lastEvent.payload.effectiveConfig.rev: number",
187
187
  "$.lastEvent.payload.effectiveConfig: object",
188
+ "$.lastEvent.payload.eligibleAfter: string",
188
189
  "$.lastEvent.payload.enabled: boolean",
189
190
  "$.lastEvent.payload.environmentId: string",
190
191
  "$.lastEvent.payload.error: string",
191
192
  "$.lastEvent.payload.escalation: number",
192
193
  "$.lastEvent.payload.execution: string",
193
- "$.lastEvent.payload.exitStatus: number",
194
194
  "$.lastEvent.payload.filename: string",
195
- "$.lastEvent.payload.files: array",
196
195
  "$.lastEvent.payload.findings: array",
196
+ "$.lastEvent.payload.generation: number",
197
197
  "$.lastEvent.payload.globs: array",
198
198
  "$.lastEvent.payload.globs[]: string",
199
199
  "$.lastEvent.payload.host: string",
@@ -207,37 +207,36 @@ exports[`reducer registry accumulator shapes 1`] = `
207
207
  "$.lastEvent.payload.kind: string",
208
208
  "$.lastEvent.payload.level: string",
209
209
  "$.lastEvent.payload.mediaType: string",
210
+ "$.lastEvent.payload.mergeCommit: string",
210
211
  "$.lastEvent.payload.message: string",
211
212
  "$.lastEvent.payload.observations: array",
212
213
  "$.lastEvent.payload.observations[].build: string",
213
214
  "$.lastEvent.payload.observations[].seq: number",
214
215
  "$.lastEvent.payload.observations[]: object",
216
+ "$.lastEvent.payload.occurrence: number",
215
217
  "$.lastEvent.payload.ok: boolean",
216
218
  "$.lastEvent.payload.operation: string",
217
219
  "$.lastEvent.payload.operator: string",
218
220
  "$.lastEvent.payload.outcome: string",
219
- "$.lastEvent.payload.output: string",
220
221
  "$.lastEvent.payload.phase: string",
221
222
  "$.lastEvent.payload.pid: number",
222
- "$.lastEvent.payload.policyCause: string",
223
223
  "$.lastEvent.payload.pr.headSha: string",
224
224
  "$.lastEvent.payload.pr.number: number",
225
225
  "$.lastEvent.payload.pr.url: string",
226
+ "$.lastEvent.payload.pr: number",
226
227
  "$.lastEvent.payload.pr: object",
227
228
  "$.lastEvent.payload.provider: string",
228
229
  "$.lastEvent.payload.question: string",
229
230
  "$.lastEvent.payload.queued: number",
230
231
  "$.lastEvent.payload.reason: string",
231
232
  "$.lastEvent.payload.ref: string",
232
- "$.lastEvent.payload.refs: array",
233
- "$.lastEvent.payload.refs[]: string",
234
233
  "$.lastEvent.payload.remote: boolean",
235
234
  "$.lastEvent.payload.report.kind: string",
236
235
  "$.lastEvent.payload.report.rev: number",
237
236
  "$.lastEvent.payload.report: object",
238
237
  "$.lastEvent.payload.request: number",
239
238
  "$.lastEvent.payload.resetAt: string",
240
- "$.lastEvent.payload.retryable: boolean",
239
+ "$.lastEvent.payload.resolution: string",
241
240
  "$.lastEvent.payload.role: string",
242
241
  "$.lastEvent.payload.roleWarnings: array",
243
242
  "$.lastEvent.payload.roleWarnings[]: string",
@@ -253,10 +252,8 @@ exports[`reducer registry accumulator shapes 1`] = `
253
252
  "$.lastEvent.payload.snapshots.outcome: string",
254
253
  "$.lastEvent.payload.snapshots: object",
255
254
  "$.lastEvent.payload.source: string",
256
- "$.lastEvent.payload.stage: string",
257
255
  "$.lastEvent.payload.step: string",
258
256
  "$.lastEvent.payload.stream: string",
259
- "$.lastEvent.payload.summary: string",
260
257
  "$.lastEvent.payload.text: string",
261
258
  "$.lastEvent.payload.ticket.id: string",
262
259
  "$.lastEvent.payload.ticket.source: string",
@@ -324,7 +321,6 @@ exports[`reducer registry accumulator shapes 1`] = `
324
321
  "$.pending.abort[].actor.user: string",
325
322
  "$.pending.abort[].actor: object",
326
323
  "$.pending.abort[].command: string",
327
- "$.pending.abort[].reason: string",
328
324
  "$.pending.abort[].reason: undefined",
329
325
  "$.pending.abort[].seq: number",
330
326
  "$.pending.abort[]: object",
@@ -393,7 +389,6 @@ exports[`reducer registry accumulator shapes 1`] = `
393
389
  "$.terminal: string",
394
390
  "$.terminal: undefined",
395
391
  "$.verify.currentStep: string",
396
- "$.verify.currentStep: undefined",
397
392
  "$.verify.cycleSince: number",
398
393
  "$.verify.maxAttemptSeen: number",
399
394
  "$.verify.results: array",
@@ -520,19 +515,11 @@ exports[`reducer registry accumulator shapes 1`] = `
520
515
  "$.intervals.reconcile.open: object",
521
516
  "$.intervals.reconcile: object",
522
517
  "$.intervals.verify:types.closed: array",
523
- "$.intervals.verify:types.closed[].end: number",
524
- "$.intervals.verify:types.closed[].start: number",
525
- "$.intervals.verify:types.closed[].startSeq: number",
526
- "$.intervals.verify:types.closed[]: object",
527
518
  "$.intervals.verify:types.open.start: number",
528
519
  "$.intervals.verify:types.open.startSeq: number",
529
520
  "$.intervals.verify:types.open: object",
530
521
  "$.intervals.verify:types: object",
531
522
  "$.intervals.verify:unit.closed: array",
532
- "$.intervals.verify:unit.closed[].end: number",
533
- "$.intervals.verify:unit.closed[].start: number",
534
- "$.intervals.verify:unit.closed[].startSeq: number",
535
- "$.intervals.verify:unit.closed[]: object",
536
523
  "$.intervals.verify:unit.open.start: number",
537
524
  "$.intervals.verify:unit.open.startSeq: number",
538
525
  "$.intervals.verify:unit.open: object",
@@ -587,6 +574,23 @@ exports[`reducer registry accumulator shapes 1`] = `
587
574
  "$.workspaceReleased: number",
588
575
  "$: object",
589
576
  ],
577
+ "dispatchFailureBudget": [
578
+ "$.aborted: boolean",
579
+ "$.failures: array",
580
+ "$.failures[].actor.kind: string",
581
+ "$.failures[].actor: object",
582
+ "$.failures[].build: string",
583
+ "$.failures[].payload.attempt: number",
584
+ "$.failures[].payload.error: string",
585
+ "$.failures[].payload.stage: string",
586
+ "$.failures[].payload: object",
587
+ "$.failures[].seq: number",
588
+ "$.failures[].ts: string",
589
+ "$.failures[].type: string",
590
+ "$.failures[]: object",
591
+ "$.open: array",
592
+ "$: object",
593
+ ],
590
594
  "dispatchSettings": [
591
595
  "$.autoMergeSeq: number",
592
596
  "$.defaultAutoMerge: boolean",
@@ -719,20 +723,6 @@ exports[`reducer registry accumulator shapes 1`] = `
719
723
  "$.byKind.screenshot.type: string",
720
724
  "$.byKind.screenshot: object",
721
725
  "$.byKind: object",
722
- "$.hostedByDesignation.1.actor.kind: string",
723
- "$.hostedByDesignation.1.actor: object",
724
- "$.hostedByDesignation.1.build: string",
725
- "$.hostedByDesignation.1.payload.asset.assetId: number",
726
- "$.hostedByDesignation.1.payload.asset.provider: string",
727
- "$.hostedByDesignation.1.payload.asset.releaseId: number",
728
- "$.hostedByDesignation.1.payload.asset.repository: string",
729
- "$.hostedByDesignation.1.payload.asset.url: string",
730
- "$.hostedByDesignation.1.payload.asset: object",
731
- "$.hostedByDesignation.1.payload.designationSeq: number",
732
- "$.hostedByDesignation.1.payload: object",
733
- "$.hostedByDesignation.1.seq: number",
734
- "$.hostedByDesignation.1.ts: string",
735
- "$.hostedByDesignation.1.type: string",
736
726
  "$.hostedByDesignation.12.actor.kind: string",
737
727
  "$.hostedByDesignation.12.actor: object",
738
728
  "$.hostedByDesignation.12.build: string",
@@ -748,37 +738,36 @@ exports[`reducer registry accumulator shapes 1`] = `
748
738
  "$.hostedByDesignation.12.ts: string",
749
739
  "$.hostedByDesignation.12.type: string",
750
740
  "$.hostedByDesignation.12: object",
751
- "$.hostedByDesignation.15.actor.kind: string",
752
- "$.hostedByDesignation.15.actor: object",
753
- "$.hostedByDesignation.15.build: string",
754
- "$.hostedByDesignation.15.payload.asset.assetId: number",
755
- "$.hostedByDesignation.15.payload.asset.provider: string",
756
- "$.hostedByDesignation.15.payload.asset.releaseId: number",
757
- "$.hostedByDesignation.15.payload.asset.repository: string",
758
- "$.hostedByDesignation.15.payload.asset.url: string",
759
- "$.hostedByDesignation.15.payload.asset: object",
760
- "$.hostedByDesignation.15.payload.designationSeq: number",
761
- "$.hostedByDesignation.15.payload: object",
762
- "$.hostedByDesignation.15.seq: number",
763
- "$.hostedByDesignation.15.ts: string",
764
- "$.hostedByDesignation.15.type: string",
765
- "$.hostedByDesignation.15: object",
766
- "$.hostedByDesignation.1: object",
767
- "$.hostedByDesignation.2.actor.kind: string",
768
- "$.hostedByDesignation.2.actor: object",
769
- "$.hostedByDesignation.2.build: string",
770
- "$.hostedByDesignation.2.payload.asset.assetId: number",
771
- "$.hostedByDesignation.2.payload.asset.provider: string",
772
- "$.hostedByDesignation.2.payload.asset.releaseId: number",
773
- "$.hostedByDesignation.2.payload.asset.repository: string",
774
- "$.hostedByDesignation.2.payload.asset.url: string",
775
- "$.hostedByDesignation.2.payload.asset: object",
776
- "$.hostedByDesignation.2.payload.designationSeq: number",
777
- "$.hostedByDesignation.2.payload: object",
778
- "$.hostedByDesignation.2.seq: number",
779
- "$.hostedByDesignation.2.ts: string",
780
- "$.hostedByDesignation.2.type: string",
781
- "$.hostedByDesignation.2: object",
741
+ "$.hostedByDesignation.13.actor.kind: string",
742
+ "$.hostedByDesignation.13.actor: object",
743
+ "$.hostedByDesignation.13.build: string",
744
+ "$.hostedByDesignation.13.payload.asset.assetId: number",
745
+ "$.hostedByDesignation.13.payload.asset.provider: string",
746
+ "$.hostedByDesignation.13.payload.asset.releaseId: number",
747
+ "$.hostedByDesignation.13.payload.asset.repository: string",
748
+ "$.hostedByDesignation.13.payload.asset.url: string",
749
+ "$.hostedByDesignation.13.payload.asset: object",
750
+ "$.hostedByDesignation.13.payload.designationSeq: number",
751
+ "$.hostedByDesignation.13.payload: object",
752
+ "$.hostedByDesignation.13.seq: number",
753
+ "$.hostedByDesignation.13.ts: string",
754
+ "$.hostedByDesignation.13.type: string",
755
+ "$.hostedByDesignation.13: object",
756
+ "$.hostedByDesignation.19.actor.kind: string",
757
+ "$.hostedByDesignation.19.actor: object",
758
+ "$.hostedByDesignation.19.build: string",
759
+ "$.hostedByDesignation.19.payload.asset.assetId: number",
760
+ "$.hostedByDesignation.19.payload.asset.provider: string",
761
+ "$.hostedByDesignation.19.payload.asset.releaseId: number",
762
+ "$.hostedByDesignation.19.payload.asset.repository: string",
763
+ "$.hostedByDesignation.19.payload.asset.url: string",
764
+ "$.hostedByDesignation.19.payload.asset: object",
765
+ "$.hostedByDesignation.19.payload.designationSeq: number",
766
+ "$.hostedByDesignation.19.payload: object",
767
+ "$.hostedByDesignation.19.seq: number",
768
+ "$.hostedByDesignation.19.ts: string",
769
+ "$.hostedByDesignation.19.type: string",
770
+ "$.hostedByDesignation.19: object",
782
771
  "$.hostedByDesignation.3.actor.kind: string",
783
772
  "$.hostedByDesignation.3.actor: object",
784
773
  "$.hostedByDesignation.3.build: string",
@@ -809,6 +798,36 @@ exports[`reducer registry accumulator shapes 1`] = `
809
798
  "$.hostedByDesignation.4.ts: string",
810
799
  "$.hostedByDesignation.4.type: string",
811
800
  "$.hostedByDesignation.4: object",
801
+ "$.hostedByDesignation.5.actor.kind: string",
802
+ "$.hostedByDesignation.5.actor: object",
803
+ "$.hostedByDesignation.5.build: string",
804
+ "$.hostedByDesignation.5.payload.asset.assetId: number",
805
+ "$.hostedByDesignation.5.payload.asset.provider: string",
806
+ "$.hostedByDesignation.5.payload.asset.releaseId: number",
807
+ "$.hostedByDesignation.5.payload.asset.repository: string",
808
+ "$.hostedByDesignation.5.payload.asset.url: string",
809
+ "$.hostedByDesignation.5.payload.asset: object",
810
+ "$.hostedByDesignation.5.payload.designationSeq: number",
811
+ "$.hostedByDesignation.5.payload: object",
812
+ "$.hostedByDesignation.5.seq: number",
813
+ "$.hostedByDesignation.5.ts: string",
814
+ "$.hostedByDesignation.5.type: string",
815
+ "$.hostedByDesignation.5: object",
816
+ "$.hostedByDesignation.6.actor.kind: string",
817
+ "$.hostedByDesignation.6.actor: object",
818
+ "$.hostedByDesignation.6.build: string",
819
+ "$.hostedByDesignation.6.payload.asset.assetId: number",
820
+ "$.hostedByDesignation.6.payload.asset.provider: string",
821
+ "$.hostedByDesignation.6.payload.asset.releaseId: number",
822
+ "$.hostedByDesignation.6.payload.asset.repository: string",
823
+ "$.hostedByDesignation.6.payload.asset.url: string",
824
+ "$.hostedByDesignation.6.payload.asset: object",
825
+ "$.hostedByDesignation.6.payload.designationSeq: number",
826
+ "$.hostedByDesignation.6.payload: object",
827
+ "$.hostedByDesignation.6.seq: number",
828
+ "$.hostedByDesignation.6.ts: string",
829
+ "$.hostedByDesignation.6.type: string",
830
+ "$.hostedByDesignation.6: object",
812
831
  "$.hostedByDesignation.8.actor.kind: string",
813
832
  "$.hostedByDesignation.8.actor: object",
814
833
  "$.hostedByDesignation.8.build: string",
@@ -1025,16 +1044,15 @@ exports[`reducer registry accumulator shapes 1`] = `
1025
1044
  "$.hosted[].type: string",
1026
1045
  "$.hosted[]: object",
1027
1046
  "$.reclaimedAt.10: number",
1028
- "$.reclaimedAt.11: number",
1029
- "$.reclaimedAt.15: number",
1047
+ "$.reclaimedAt.12: number",
1030
1048
  "$.reclaimedAt.16: number",
1031
- "$.reclaimedAt.18: number",
1032
- "$.reclaimedAt.1: number",
1049
+ "$.reclaimedAt.17: number",
1050
+ "$.reclaimedAt.19: number",
1033
1051
  "$.reclaimedAt.20: number",
1034
- "$.reclaimedAt.3: number",
1052
+ "$.reclaimedAt.2: number",
1035
1053
  "$.reclaimedAt.6: number",
1036
- "$.reclaimedAt.7: number",
1037
1054
  "$.reclaimedAt.8: number",
1055
+ "$.reclaimedAt.9: number",
1038
1056
  "$.reclaimedAt: object",
1039
1057
  "$: object",
1040
1058
  ],
@@ -1164,6 +1182,7 @@ exports[`reducer registry accumulator shapes 1`] = `
1164
1182
  "$.retained[].payload.base: string",
1165
1183
  "$.retained[].payload.baseBranch: string",
1166
1184
  "$.retained[].payload.baseSha: string",
1185
+ "$.retained[].payload.boundary: number",
1167
1186
  "$.retained[].payload.branch: string",
1168
1187
  "$.retained[].payload.cause: string",
1169
1188
  "$.retained[].payload.cleanupPending: boolean",
@@ -1188,6 +1207,7 @@ exports[`reducer registry accumulator shapes 1`] = `
1188
1207
  "$.retained[].payload.defaultSeq: number",
1189
1208
  "$.retained[].payload.dependencyDiagnostics: array",
1190
1209
  "$.retained[].payload.designationSeq: number",
1210
+ "$.retained[].payload.disposition: string",
1191
1211
  "$.retained[].payload.dispositions: array",
1192
1212
  "$.retained[].payload.dispositions[].action: string",
1193
1213
  "$.retained[].payload.dispositions[].occurrence.build: string",
@@ -1198,6 +1218,7 @@ exports[`reducer registry accumulator shapes 1`] = `
1198
1218
  "$.retained[].payload.effectiveConfig.kind: string",
1199
1219
  "$.retained[].payload.effectiveConfig.rev: number",
1200
1220
  "$.retained[].payload.effectiveConfig: object",
1221
+ "$.retained[].payload.eligibleAfter: string",
1201
1222
  "$.retained[].payload.enabled: boolean",
1202
1223
  "$.retained[].payload.environmentId: string",
1203
1224
  "$.retained[].payload.error: string",
@@ -1227,6 +1248,7 @@ exports[`reducer registry accumulator shapes 1`] = `
1227
1248
  "$.retained[].payload.observations[].build: string",
1228
1249
  "$.retained[].payload.observations[].seq: number",
1229
1250
  "$.retained[].payload.observations[]: object",
1251
+ "$.retained[].payload.occurrence: number",
1230
1252
  "$.retained[].payload.ok: boolean",
1231
1253
  "$.retained[].payload.operation: string",
1232
1254
  "$.retained[].payload.operator: string",
@@ -1239,6 +1261,7 @@ exports[`reducer registry accumulator shapes 1`] = `
1239
1261
  "$.retained[].payload.pr.headSha: string",
1240
1262
  "$.retained[].payload.pr.number: number",
1241
1263
  "$.retained[].payload.pr.url: string",
1264
+ "$.retained[].payload.pr: number",
1242
1265
  "$.retained[].payload.pr: object",
1243
1266
  "$.retained[].payload.provider: string",
1244
1267
  "$.retained[].payload.question: string",
@@ -1368,6 +1391,30 @@ exports[`reducer registry accumulator shapes 1`] = `
1368
1391
  "$.failures[]: number",
1369
1392
  "$: object",
1370
1393
  ],
1394
+ "summaryBoundary": [
1395
+ "$.boundary: number",
1396
+ "$.occurrence: number",
1397
+ "$.sessions.s_a: boolean",
1398
+ "$.sessions.s_b: boolean",
1399
+ "$.sessions: object",
1400
+ "$: object",
1401
+ ],
1402
+ "summaryRequests": [
1403
+ "$.requests: array",
1404
+ "$.requests[].actor.kind: string",
1405
+ "$.requests[].actor: object",
1406
+ "$.requests[].build: string",
1407
+ "$.requests[].payload.boundary: number",
1408
+ "$.requests[].payload.occurrence: number",
1409
+ "$.requests[].payload: object",
1410
+ "$.requests[].seq: number",
1411
+ "$.requests[].ts: string",
1412
+ "$.requests[].type: string",
1413
+ "$.requests[]: object",
1414
+ "$.settled: array",
1415
+ "$.settled[]: number",
1416
+ "$: object",
1417
+ ],
1371
1418
  "ticketSourceHealth": [
1372
1419
  "$.active.cause: string",
1373
1420
  "$.active.message: string",
@@ -55,6 +55,7 @@ export type VerifyAction =
55
55
  export type FinalizeAction = { kind: 'check'; command: string } | { kind: 'agent'; skill: string }
56
56
 
57
57
  export type Decision =
58
+ | { kind: 'capture-reconcile-policy'; reconcileSeq: number }
58
59
  | { kind: 'wait'; reason: WaitReason }
59
60
  | { kind: 'acknowledge'; command: 'pause' | 'resume' | 'abort' }
60
61
  | {
@@ -80,6 +81,7 @@ export type Decision =
80
81
  step: string
81
82
  attempt: number
82
83
  reason: string
84
+ skipCause?: 'post-reconcile-policy'
83
85
  }
84
86
  | { kind: 'run-finalize-step'; step: string; action: FinalizeAction }
85
87
  | {
@@ -313,6 +315,18 @@ export function decideNextFromFacts(state: BuildState, log: LogIndex, config: Co
313
315
  const cycleAttempts = [...cycleResults, ...cycleStarted].map((v) => v.attempt)
314
316
  const attempt =
315
317
  cycleAttempts.length > 0 ? Math.max(...cycleAttempts) : log.maxVerifyAttemptEver + 1
318
+ // Freeze only a fresh reconcile cycle. A newer code-review approval restores
319
+ // ordinary verification. Already-started historical cycles have no snapshot:
320
+ // preserve their rerun-all policy, including after a crashed verify start.
321
+ const reconcileCycle =
322
+ log.lastReconcileCompletedSeq > Math.max(log.code.latestApproveSeq, log.restartSeq)
323
+ const policy =
324
+ reconcileCycle && log.reconcilePolicy?.reconcileSeq === cycleBoundary
325
+ ? log.reconcilePolicy
326
+ : undefined
327
+ if (reconcileCycle && policy === undefined && cycleAttempts.length === 0) {
328
+ return { kind: 'capture-reconcile-policy', reconcileSeq: cycleBoundary }
329
+ }
316
330
  const approvedPlan = log.plan.approvedPlan
317
331
  const approvedSelection =
318
332
  approvedPlan?.verifySteps === undefined
@@ -333,6 +347,16 @@ export function decideNextFromFacts(state: BuildState, log: LogIndex, config: Co
333
347
  const stepConfig = config.verify.stepConfigs[step]
334
348
  if (stepConfig === undefined) continue // unreachable: configSchema cross-validates (§16.1)
335
349
 
350
+ const exclusion = policy?.steps.find((s) => s.step === step && !s.enabled)
351
+ if (exclusion !== undefined)
352
+ return {
353
+ kind: 'skip-verify',
354
+ step,
355
+ attempt,
356
+ skipCause: 'post-reconcile-policy',
357
+ reason: `skipped by post-reconcile policy: ${exclusion.source} = false`,
358
+ }
359
+
336
360
  // Missing selection data is the historical default-all behavior. For a
337
361
  // current explicit selection, exclusion precedes path evaluation and any
338
362
  // verifier construction. Mandatory gates remain selected defensively even
@@ -207,6 +207,16 @@ function randomWrite(rand: () => number): EventWrite {
207
207
  },
208
208
  }),
209
209
  () => w('pr-attachment.reclaimed', { hostedSeq: 1 + Math.floor(rand() * 20) }),
210
+ () => w('pr-summary.requested', { occurrence: round, boundary: round + 1 }),
211
+ () => w('pr-summary.settled', { request: round, disposition: 'attempted' }),
212
+ () => w('pr-summary.attempted', { occurrence: round, boundary: round + 1, pr: 7 }),
213
+ () =>
214
+ w('pr-summary.result', {
215
+ attempt: round,
216
+ outcome: 'rate-limited',
217
+ eligibleAfter: '2026-10-15T00:01:00.000Z',
218
+ }),
219
+ () => w('finalize.step-started', { step: 'notes' }),
210
220
  () => w('build.auto-merge-default-observed', { defaultSeq: round }),
211
221
  () =>
212
222
  w('observation.recorded', {