@defrex/autobuild 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +192 -0
- package/SPEC.md +2269 -0
- package/bin/ab-build-runner.ts +47 -0
- package/bin/ab-dev.ts +27 -0
- package/bin/ab-dispatch-kernel.ts +92 -0
- package/bin/ab-harvest-runner.ts +29 -0
- package/bin/ab-init-probe.ts +19 -0
- package/bin/ab-process-group-reaper.ts +32 -0
- package/bin/ab.ts +6 -0
- package/bin/agent/ab +3 -0
- package/docs/answering-blocked-builds.md +89 -0
- package/docs/architecture.md +726 -0
- package/docs/assets/headline-wide.png +0 -0
- package/docs/configuration.md +1623 -0
- package/docs/hosted-dispatcher-cutover-validation.md +178 -0
- package/docs/hosted-dispatcher.md +204 -0
- package/docs/mcp.md +119 -0
- package/docs/operator-api.md +186 -0
- package/docs/remote-store-protocol.md +972 -0
- package/docs/remote-ticket-protocol.md +79 -0
- package/docs/setup.md +289 -0
- package/docs/spec-standard.md +63 -0
- package/docs/ticket-dependencies.md +59 -0
- package/docs/vercel-sandbox-rollout-probe.md +7 -0
- package/package.json +135 -0
- package/packages/core/src/cli/args.ts +74 -0
- package/packages/core/src/cli/artifact.ts +182 -0
- package/packages/core/src/cli/binary.ts +198 -0
- package/packages/core/src/cli/build-control.ts +789 -0
- package/packages/core/src/cli/build-progress.ts +55 -0
- package/packages/core/src/cli/bulk-control.ts +385 -0
- package/packages/core/src/cli/context.ts +470 -0
- package/packages/core/src/cli/dashboard/actions.ts +59 -0
- package/packages/core/src/cli/dashboard/cells.ts +92 -0
- package/packages/core/src/cli/dashboard/composer.ts +230 -0
- package/packages/core/src/cli/dashboard/detail.ts +100 -0
- package/packages/core/src/cli/dashboard/frame-image.ts +373 -0
- package/packages/core/src/cli/dashboard/live.ts +152 -0
- package/packages/core/src/cli/dashboard/model.ts +1173 -0
- package/packages/core/src/cli/dashboard/poll.ts +196 -0
- package/packages/core/src/cli/dashboard/render.ts +1568 -0
- package/packages/core/src/cli/dashboard/selection.ts +66 -0
- package/packages/core/src/cli/dashboard/session-feed.ts +244 -0
- package/packages/core/src/cli/dashboard/session-view.ts +506 -0
- package/packages/core/src/cli/dashboard/transcript.ts +108 -0
- package/packages/core/src/cli/dev.ts +46 -0
- package/packages/core/src/cli/dispatch-frontend.ts +1045 -0
- package/packages/core/src/cli/dispatch-process.ts +259 -0
- package/packages/core/src/cli/dispatch.ts +3692 -0
- package/packages/core/src/cli/dotenv.ts +48 -0
- package/packages/core/src/cli/env.ts +206 -0
- package/packages/core/src/cli/harvest.ts +698 -0
- package/packages/core/src/cli/help.ts +713 -0
- package/packages/core/src/cli/init-agent.ts +106 -0
- package/packages/core/src/cli/init-validation.ts +583 -0
- package/packages/core/src/cli/init.ts +756 -0
- package/packages/core/src/cli/installation.ts +333 -0
- package/packages/core/src/cli/keyboard.ts +261 -0
- package/packages/core/src/cli/main.ts +1399 -0
- package/packages/core/src/cli/mcp.ts +279 -0
- package/packages/core/src/cli/models.ts +65 -0
- package/packages/core/src/cli/observe.ts +55 -0
- package/packages/core/src/cli/plugin.ts +227 -0
- package/packages/core/src/cli/pr-attachments.ts +138 -0
- package/packages/core/src/cli/pr-summary.ts +124 -0
- package/packages/core/src/cli/publication-settlement.ts +188 -0
- package/packages/core/src/cli/repo-state.ts +213 -0
- package/packages/core/src/cli/repository-status.ts +80 -0
- package/packages/core/src/cli/self-update.ts +443 -0
- package/packages/core/src/cli/status.ts +895 -0
- package/packages/core/src/cli/store-opening.contract.ts +289 -0
- package/packages/core/src/cli/store-opening.ts +167 -0
- package/packages/core/src/cli/store-ref.ts +33 -0
- package/packages/core/src/cli/terminal-restore.ts +174 -0
- package/packages/core/src/cli/terminal.ts +361 -0
- package/packages/core/src/cli/terminals.ts +995 -0
- package/packages/core/src/cli/testkit.ts +194 -0
- package/packages/core/src/cli/ticket.ts +484 -0
- package/packages/core/src/cli/upgrade-agent.ts +180 -0
- package/packages/core/src/cli/upgrade-commit.ts +468 -0
- package/packages/core/src/cli/upgrade-notice.ts +91 -0
- package/packages/core/src/cli/upgrade-progress.ts +110 -0
- package/packages/core/src/cli/upgrade.ts +888 -0
- package/packages/core/src/cli/wait.ts +587 -0
- package/packages/core/src/cli/watch.ts +932 -0
- package/packages/core/src/config/live.ts +255 -0
- package/packages/core/src/config/load.ts +98 -0
- package/packages/core/src/config/roles.ts +438 -0
- package/packages/core/src/config/schema.ts +774 -0
- package/packages/core/src/distribution.ts +24 -0
- package/packages/core/src/events/catalog.ts +201 -0
- package/packages/core/src/events/envelope.ts +59 -0
- package/packages/core/src/events/payloads.ts +618 -0
- package/packages/core/src/events/repository.ts +559 -0
- package/packages/core/src/events/sessions.ts +180 -0
- package/packages/core/src/harvest/schema.ts +147 -0
- package/packages/core/src/ids.ts +45 -0
- package/packages/core/src/integration/harness.ts +681 -0
- package/packages/core/src/kernel/auto-merge.ts +259 -0
- package/packages/core/src/kernel/converge.ts +117 -0
- package/packages/core/src/kernel/dispatch-settings.ts +60 -0
- package/packages/core/src/kernel/dispatch-status.ts +141 -0
- package/packages/core/src/kernel/engine.ts +1047 -0
- package/packages/core/src/kernel/harvest.ts +823 -0
- package/packages/core/src/kernel/origin.ts +69 -0
- package/packages/core/src/kernel/phases.ts +196 -0
- package/packages/core/src/kernel/plan-verify-selection.ts +105 -0
- package/packages/core/src/kernel/pr-attachments.ts +82 -0
- package/packages/core/src/kernel/reducer.ts +752 -0
- package/packages/core/src/kernel/stall.ts +155 -0
- package/packages/core/src/kernel/verify-applicability.ts +52 -0
- package/packages/core/src/markdown.ts +67 -0
- package/packages/core/src/ontology.ts +288 -0
- package/packages/core/src/open-map.ts +203 -0
- package/packages/core/src/operator/annotations.ts +187 -0
- package/packages/core/src/operator/client.ts +311 -0
- package/packages/core/src/operator/control.ts +117 -0
- package/packages/core/src/operator/index.ts +62 -0
- package/packages/core/src/operator/presentation.ts +22 -0
- package/packages/core/src/operator/protocol.ts +98 -0
- package/packages/core/src/operator/query.ts +201 -0
- package/packages/core/src/operator/registry.ts +1043 -0
- package/packages/core/src/operator/requests.ts +138 -0
- package/packages/core/src/operator/sandbox.ts +458 -0
- package/packages/core/src/operator/server.ts +616 -0
- package/packages/core/src/operator/sessions.ts +223 -0
- package/packages/core/src/operator/tickets.ts +177 -0
- package/packages/core/src/plugin-sdk/index.ts +174 -0
- package/packages/core/src/plugins/contract-entry.ts +74 -0
- package/packages/core/src/plugins/contract-env.ts +3 -0
- package/packages/core/src/plugins/load.ts +259 -0
- package/packages/core/src/plugins/manifest.ts +236 -0
- package/packages/core/src/plugins/registry.ts +274 -0
- package/packages/core/src/plugins/runtimes.ts +75 -0
- package/packages/core/src/ports/forge/contract.ts +258 -0
- package/packages/core/src/ports/forge/create.ts +74 -0
- package/packages/core/src/ports/forge/fake.ts +488 -0
- package/packages/core/src/ports/forge/github-pr-attachments.ts +351 -0
- package/packages/core/src/ports/forge/github-transport.ts +352 -0
- package/packages/core/src/ports/forge/github.ts +1162 -0
- package/packages/core/src/ports/forge/local-git.ts +568 -0
- package/packages/core/src/ports/runner/claude.ts +873 -0
- package/packages/core/src/ports/runner/cli-stream.ts +90 -0
- package/packages/core/src/ports/runner/codex.ts +873 -0
- package/packages/core/src/ports/runner/contract.ts +492 -0
- package/packages/core/src/ports/runner/fake.ts +213 -0
- package/packages/core/src/ports/runner/json-record.ts +2 -0
- package/packages/core/src/ports/runner/live-contract-fixture.ts +176 -0
- package/packages/core/src/ports/runner/one-shot.ts +29 -0
- package/packages/core/src/ports/runner/pi-bridge-core.ts +136 -0
- package/packages/core/src/ports/runner/pi-bridge.js +6 -0
- package/packages/core/src/ports/runner/pi-rpc.ts +454 -0
- package/packages/core/src/ports/runner/pi.ts +617 -0
- package/packages/core/src/ports/runner/production.ts +74 -0
- package/packages/core/src/ports/runner/provider-error.ts +121 -0
- package/packages/core/src/ports/runner/routing.ts +234 -0
- package/packages/core/src/ports/runner/runtime.ts +214 -0
- package/packages/core/src/ports/runner/session-env.ts +34 -0
- package/packages/core/src/ports/runner/stream-parts.ts +103 -0
- package/packages/core/src/ports/tickets/contract.ts +570 -0
- package/packages/core/src/ports/tickets/create.ts +135 -0
- package/packages/core/src/ports/tickets/fake.ts +215 -0
- package/packages/core/src/ports/tickets/file.ts +559 -0
- package/packages/core/src/ports/tickets/linear.ts +811 -0
- package/packages/core/src/ports/tickets/operations.ts +161 -0
- package/packages/core/src/ports/tickets/remote-index.ts +13 -0
- package/packages/core/src/ports/tickets/remote-protocol.ts +80 -0
- package/packages/core/src/ports/tickets/remote-server.ts +185 -0
- package/packages/core/src/ports/tickets/remote.ts +130 -0
- package/packages/core/src/ports/tickets/update.ts +51 -0
- package/packages/core/src/ports/types.ts +528 -0
- package/packages/core/src/ports/workspace/build-execution.ts +123 -0
- package/packages/core/src/ports/workspace/contract.ts +136 -0
- package/packages/core/src/ports/workspace/create.ts +132 -0
- package/packages/core/src/ports/workspace/distribution-archive.ts +317 -0
- package/packages/core/src/ports/workspace/fake.ts +432 -0
- package/packages/core/src/ports/workspace/git-worktree.ts +848 -0
- package/packages/core/src/ports/workspace/harvest-execution.ts +111 -0
- package/packages/core/src/ports/workspace/in-process-build-execution.ts +30 -0
- package/packages/core/src/ports/workspace/local-build-execution.ts +88 -0
- package/packages/core/src/ports/workspace/operator-sandbox.ts +142 -0
- package/packages/core/src/ports/workspace/phase-scratch.ts +156 -0
- package/packages/core/src/ports/workspace/vercel-sandbox.ts +2541 -0
- package/packages/core/src/processes/build-child-exit.ts +78 -0
- package/packages/core/src/processes/build-child.ts +119 -0
- package/packages/core/src/processes/build-execution-state.ts +95 -0
- package/packages/core/src/processes/build-parent-watch.ts +35 -0
- package/packages/core/src/processes/build-runner.ts +2614 -0
- package/packages/core/src/processes/dispatcher.ts +2825 -0
- package/packages/core/src/processes/execution-settlement.ts +171 -0
- package/packages/core/src/processes/harvest-child.ts +115 -0
- package/packages/core/src/processes/harvest-execution-state.ts +121 -0
- package/packages/core/src/processes/harvest-runner.ts +1656 -0
- package/packages/core/src/processes/harvest.ts +595 -0
- package/packages/core/src/processes/infrastructure-failure-budget.ts +146 -0
- package/packages/core/src/processes/phase-failure-budget.ts +52 -0
- package/packages/core/src/processes/process-group-reaper.ts +35 -0
- package/packages/core/src/processes/process-group.ts +65 -0
- package/packages/core/src/processes/publication-loss.ts +62 -0
- package/packages/core/src/processes/publication-state.ts +93 -0
- package/packages/core/src/processes/sandbox-state.ts +101 -0
- package/packages/core/src/registry.ts +24 -0
- package/packages/core/src/sessions/reducer.ts +185 -0
- package/packages/core/src/skills.ts +7 -0
- package/packages/core/src/spec-standard.ts +47 -0
- package/packages/core/src/store/adapter.ts +80 -0
- package/packages/core/src/store/build-scope.ts +272 -0
- package/packages/core/src/store/contract.ts +2581 -0
- package/packages/core/src/store/local/blobs.ts +38 -0
- package/packages/core/src/store/local/schema.ts +180 -0
- package/packages/core/src/store/local/store.ts +1713 -0
- package/packages/core/src/store/memory.ts +1138 -0
- package/packages/core/src/store/phase-session.ts +425 -0
- package/packages/core/src/store/remote/client.ts +780 -0
- package/packages/core/src/store/remote/index.ts +12 -0
- package/packages/core/src/store/remote/protocol.ts +348 -0
- package/packages/core/src/store/remote/server.ts +923 -0
- package/packages/core/src/store/remote/token.ts +137 -0
- package/packages/core/src/store/remote/version.ts +10 -0
- package/packages/core/src/store/retention.ts +91 -0
- package/packages/core/src/store/session-handle.ts +265 -0
- package/packages/core/src/store/streams/assemble.ts +79 -0
- package/packages/core/src/store/streams/lock.ts +42 -0
- package/packages/core/src/store/streams/session-writer.ts +306 -0
- package/packages/core/src/store/streams/types.ts +171 -0
- package/packages/core/src/store/streams/wait.ts +127 -0
- package/packages/core/src/store/subscribe.ts +105 -0
- package/packages/core/src/store/types.ts +466 -0
- package/packages/core/src/testing/fixed.ts +44 -0
- package/packages/core/src/testing/store-failures.ts +32 -0
- package/packages/core/src/zod-issues.ts +91 -0
- package/skills/code-review/SKILL.md +100 -0
- package/skills/finalize/SKILL.md +44 -0
- package/skills/guide/SKILL.md +1781 -0
- package/skills/guide/references/operator-api.md +164 -0
- package/skills/guide/references/plugin-authoring.md +354 -0
- package/skills/guide/references/remote-store-protocol.md +972 -0
- package/skills/guide/references/remote-ticket-protocol.md +79 -0
- package/skills/guide/references/setup.md +289 -0
- package/skills/guide/references/spec-standard.md +63 -0
- package/skills/guide/references/ticket-dependencies.md +59 -0
- package/skills/harvest/SKILL.md +66 -0
- package/skills/harvest-review/SKILL.md +63 -0
- package/skills/implement/SKILL.md +82 -0
- package/skills/plan/SKILL.md +123 -0
- package/skills/plan-review/SKILL.md +121 -0
- package/skills/reconcile/SKILL.md +65 -0
- package/skills/spec/SKILL.md +132 -0
- package/skills/tickets/SKILL.md +139 -0
- package/templates/autobuild.toml +36 -0
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The event vocabulary (SPEC §15.3), frozen as schemas — this closes open
|
|
3
|
+
* thread §18.1. It is simultaneously the store schema, the kernel's I/O, the
|
|
4
|
+
* UI API, and the resume format, so payloads are strict objects: unknown keys
|
|
5
|
+
* are deposit errors, and schema errors are agent feedback (D6), not build
|
|
6
|
+
* failures.
|
|
7
|
+
*
|
|
8
|
+
* Conventions (SPEC §15.2): closed vocabularies live in type names
|
|
9
|
+
* (`plan.completed`); open ones live in payloads (`verify.completed {step}`).
|
|
10
|
+
* Events carry facts and refs `{kind, rev}` — never derived state, never
|
|
11
|
+
* blobs, never code (D3: code travels through the Forge; events carry SHAs).
|
|
12
|
+
*/
|
|
13
|
+
import { z } from 'zod'
|
|
14
|
+
import {
|
|
15
|
+
artifactRefSchema,
|
|
16
|
+
buildOutcomeSchema,
|
|
17
|
+
commitRangeSchema,
|
|
18
|
+
prAttachmentFilenameSchema,
|
|
19
|
+
prImageHostSchema,
|
|
20
|
+
escalationResolutionSchema,
|
|
21
|
+
escalationSourceSchema,
|
|
22
|
+
escalationTargetSchema,
|
|
23
|
+
feedbackSchema,
|
|
24
|
+
findingSchema,
|
|
25
|
+
hostedPrAttachmentAssetSchema,
|
|
26
|
+
mediaTypeSchema,
|
|
27
|
+
observationKindSchema,
|
|
28
|
+
phaseSchema,
|
|
29
|
+
policyEscalationCauseSchema,
|
|
30
|
+
reviewVerdictKindSchema,
|
|
31
|
+
ticketRefSchema,
|
|
32
|
+
workspaceBaseSchema,
|
|
33
|
+
type ArtifactRef,
|
|
34
|
+
type VerifyOutcome,
|
|
35
|
+
} from '../ontology'
|
|
36
|
+
|
|
37
|
+
const empty = z.strictObject({})
|
|
38
|
+
const reasonOnly = z.strictObject({ reason: z.string().optional() })
|
|
39
|
+
const round = z.number().int().positive()
|
|
40
|
+
const attempt = z.number().int().positive()
|
|
41
|
+
const dispatchStage = z.enum(['create', 'workspace', 'spec', 'comment', 'launch'])
|
|
42
|
+
const infrastructureOperation = z.enum([
|
|
43
|
+
'provision',
|
|
44
|
+
'start',
|
|
45
|
+
'wait',
|
|
46
|
+
'stop',
|
|
47
|
+
'delete',
|
|
48
|
+
'reconcile',
|
|
49
|
+
])
|
|
50
|
+
const infrastructureCause = z.enum([
|
|
51
|
+
'missing',
|
|
52
|
+
'session-expired',
|
|
53
|
+
'timeout',
|
|
54
|
+
'provider-limit',
|
|
55
|
+
'provider-error',
|
|
56
|
+
'unknown-outcome',
|
|
57
|
+
])
|
|
58
|
+
|
|
59
|
+
export const agentFailureCauseSchema = z.enum([
|
|
60
|
+
'availability',
|
|
61
|
+
'exhaustion',
|
|
62
|
+
'credentials',
|
|
63
|
+
'configuration',
|
|
64
|
+
])
|
|
65
|
+
|
|
66
|
+
/** One configured execution target that failed within a single phase attempt. */
|
|
67
|
+
export const providerAttemptSchema = z.strictObject({
|
|
68
|
+
index: z.number().int().nonnegative(),
|
|
69
|
+
session: z.string().min(1),
|
|
70
|
+
runner: z.string().min(1),
|
|
71
|
+
model: z.string().optional(),
|
|
72
|
+
error: z.string().min(1),
|
|
73
|
+
cause: agentFailureCauseSchema.optional(),
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
export const providerAttemptsSchema = z
|
|
77
|
+
.array(providerAttemptSchema)
|
|
78
|
+
.min(1)
|
|
79
|
+
.superRefine((attempts, ctx) => {
|
|
80
|
+
attempts.forEach((attempt, index) => {
|
|
81
|
+
if (attempt.index !== index) {
|
|
82
|
+
ctx.addIssue({
|
|
83
|
+
code: 'custom',
|
|
84
|
+
path: [index, 'index'],
|
|
85
|
+
message: `provider attempt index must be ${index} at this ordered position`,
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
export const providerSubstitutionSchema = z
|
|
92
|
+
.strictObject({
|
|
93
|
+
failed: providerAttemptSchema,
|
|
94
|
+
selectedIndex: z.number().int().positive(),
|
|
95
|
+
})
|
|
96
|
+
.superRefine((value, ctx) => {
|
|
97
|
+
if (value.selectedIndex !== value.failed.index + 1) {
|
|
98
|
+
ctx.addIssue({
|
|
99
|
+
code: 'custom',
|
|
100
|
+
path: ['selectedIndex'],
|
|
101
|
+
message: 'selectedIndex must name the configured entry immediately after failed.index',
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const verifyStepSelectionSchema = z
|
|
107
|
+
.array(
|
|
108
|
+
z
|
|
109
|
+
.string()
|
|
110
|
+
.min(1, 'verify step names must be nonempty')
|
|
111
|
+
.refine((step) => step.trim().length > 0, 'verify step names must not be blank'),
|
|
112
|
+
)
|
|
113
|
+
.superRefine((steps, ctx) => {
|
|
114
|
+
const seen = new Set<string>()
|
|
115
|
+
steps.forEach((step, index) => {
|
|
116
|
+
if (seen.has(step)) {
|
|
117
|
+
ctx.addIssue({
|
|
118
|
+
code: 'custom',
|
|
119
|
+
path: [index],
|
|
120
|
+
message: `duplicate verify step ${JSON.stringify(step)}`,
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
seen.add(step)
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
const verifyCompletionBase = {
|
|
128
|
+
step: z.string().min(1),
|
|
129
|
+
attempt,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* `verify.completed` is a durable protocol. The boolean branch remains
|
|
134
|
+
* readable for historical logs; every current writer uses the canonical,
|
|
135
|
+
* three-outcome branch. Keeping the branches strict prevents a producer from
|
|
136
|
+
* smuggling a skip through `pass: true` or recording contradictory facts.
|
|
137
|
+
*/
|
|
138
|
+
const verifyCompletedPayloadSchema = z.union([
|
|
139
|
+
z.strictObject({
|
|
140
|
+
...verifyCompletionBase,
|
|
141
|
+
pass: z.boolean(),
|
|
142
|
+
report: artifactRefSchema.optional(),
|
|
143
|
+
}),
|
|
144
|
+
z.discriminatedUnion('outcome', [
|
|
145
|
+
z.strictObject({
|
|
146
|
+
...verifyCompletionBase,
|
|
147
|
+
outcome: z.literal('pass'),
|
|
148
|
+
report: artifactRefSchema.optional(),
|
|
149
|
+
}),
|
|
150
|
+
z.strictObject({
|
|
151
|
+
...verifyCompletionBase,
|
|
152
|
+
outcome: z.literal('fail'),
|
|
153
|
+
report: artifactRefSchema.optional(),
|
|
154
|
+
}),
|
|
155
|
+
z.strictObject({
|
|
156
|
+
...verifyCompletionBase,
|
|
157
|
+
outcome: z.literal('skipped'),
|
|
158
|
+
reason: z.string().trim().min(1, 'a skipped verification requires a non-blank reason'),
|
|
159
|
+
}),
|
|
160
|
+
]),
|
|
161
|
+
])
|
|
162
|
+
|
|
163
|
+
/** A pushed head is a success checkpoint, never a claim attached to failure. */
|
|
164
|
+
const finalizeStepCompletedPayloadSchema = z.discriminatedUnion('ok', [
|
|
165
|
+
z.strictObject({
|
|
166
|
+
step: z.string().min(1),
|
|
167
|
+
ok: z.literal(true),
|
|
168
|
+
note: z.string().optional(),
|
|
169
|
+
headSha: z.string().trim().min(1, 'a finalize publication head must be non-blank').optional(),
|
|
170
|
+
}),
|
|
171
|
+
z.strictObject({
|
|
172
|
+
step: z.string().min(1),
|
|
173
|
+
ok: z.literal(false),
|
|
174
|
+
note: z.string().optional(),
|
|
175
|
+
}),
|
|
176
|
+
])
|
|
177
|
+
|
|
178
|
+
/** Shared by `plan-review.verdict` and `code-review.verdict` (symmetric by design). */
|
|
179
|
+
const reviewVerdictPayload = z.strictObject({
|
|
180
|
+
round,
|
|
181
|
+
verdict: reviewVerdictKindSchema,
|
|
182
|
+
findings: z.array(findingSchema),
|
|
183
|
+
artifact: artifactRefSchema,
|
|
184
|
+
/** Present when `verdict` is `escalate`. */
|
|
185
|
+
reason: z.string().optional(),
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
export const eventPayloadSchemas = {
|
|
189
|
+
// ── Build lifecycle ────────────────────────────────────────────────────────
|
|
190
|
+
'build.created': z.strictObject({
|
|
191
|
+
ticket: ticketRefSchema,
|
|
192
|
+
repo: z.string().min(1),
|
|
193
|
+
baseBranch: z.string().min(1),
|
|
194
|
+
/** Claim-time auto-merge intent retained across dispatch recovery until the
|
|
195
|
+
* ordinary human-authored command fact can be materialized. */
|
|
196
|
+
autoMergeRequestedBy: z.string().min(1).optional(),
|
|
197
|
+
/** Frozen at claim time. Historical logs and disabled installs omit it. */
|
|
198
|
+
pr: z
|
|
199
|
+
.strictObject({
|
|
200
|
+
imageHost: prImageHostSchema.optional(),
|
|
201
|
+
})
|
|
202
|
+
.optional(),
|
|
203
|
+
}),
|
|
204
|
+
'build.completed': z.strictObject({ outcome: buildOutcomeSchema }),
|
|
205
|
+
'runner.attached': z.strictObject({
|
|
206
|
+
instance: z.string().min(1),
|
|
207
|
+
host: z.string().min(1),
|
|
208
|
+
resumedFromSeq: z.number().int().nonnegative().optional(),
|
|
209
|
+
}),
|
|
210
|
+
/** Pre-phase setup infrastructure failed. `null` explicitly means the
|
|
211
|
+
* command could not produce an exit status (for example, Exec threw). */
|
|
212
|
+
'runner.setup-failed': z.strictObject({
|
|
213
|
+
command: z.string().min(1),
|
|
214
|
+
attempt,
|
|
215
|
+
exitStatus: z.number().int().nullable(),
|
|
216
|
+
output: z.string(),
|
|
217
|
+
}),
|
|
218
|
+
'workspace.provisioned': z.strictObject({
|
|
219
|
+
provider: z.string().min(1),
|
|
220
|
+
ref: z.string().min(1),
|
|
221
|
+
/** Working-copy path in the provider execution environment. Optional only
|
|
222
|
+
* for historical event replay. */
|
|
223
|
+
path: z.string().min(1).optional(),
|
|
224
|
+
/** Dispatcher-reachable path. Remote providers intentionally omit it. */
|
|
225
|
+
localPath: z.string().min(1).optional(),
|
|
226
|
+
branch: z.string().min(1),
|
|
227
|
+
base: workspaceBaseSchema,
|
|
228
|
+
}),
|
|
229
|
+
'workspace.released': z.union([
|
|
230
|
+
empty,
|
|
231
|
+
z.strictObject({
|
|
232
|
+
ref: z.string().min(1),
|
|
233
|
+
reason: z.enum(['completion', 'abort', 'pause', 'blocked', 'replacement', 'discard']),
|
|
234
|
+
}),
|
|
235
|
+
]),
|
|
236
|
+
/** Durable marker that background provisioning of this build's remote
|
|
237
|
+
* workspace has begun. The marker's liveness is the build's execution lease:
|
|
238
|
+
* a holder heartbeats it while provisioning, so a dead provisioner's marker
|
|
239
|
+
* is adopted within one lease TTL. */
|
|
240
|
+
'workspace.provision-started': z.strictObject({
|
|
241
|
+
provider: z.string().min(1),
|
|
242
|
+
branch: z.string().min(1),
|
|
243
|
+
generation: z.number().int().nonnegative(),
|
|
244
|
+
}),
|
|
245
|
+
/** Dispatcher-owned identity and provider lifecycle evidence. These facts do
|
|
246
|
+
* not carry pipeline progress; they make disposable execution observable. */
|
|
247
|
+
'execution.started': z.strictObject({
|
|
248
|
+
provider: z.string().min(1),
|
|
249
|
+
workspaceRef: z.string().min(1),
|
|
250
|
+
instance: z.string().min(1),
|
|
251
|
+
environmentId: z.string().min(1).optional(),
|
|
252
|
+
sessionId: z.string().min(1).optional(),
|
|
253
|
+
/** Provider-native detached command id, recorded at launch so a later
|
|
254
|
+
* process can re-observe the execution without process memory. Optional
|
|
255
|
+
* only so historical journals replay without migration. */
|
|
256
|
+
commandId: z.string().min(1).optional(),
|
|
257
|
+
}),
|
|
258
|
+
/** Confirmed executor completion is the infrastructure retry-epoch boundary. */
|
|
259
|
+
'execution.ended': z.strictObject({
|
|
260
|
+
instance: z.string().min(1),
|
|
261
|
+
workspaceRef: z.string().min(1),
|
|
262
|
+
outcome: z.enum(['completed', 'stopped', 'lost']),
|
|
263
|
+
exitCode: z.number().int().nullable().optional(),
|
|
264
|
+
}),
|
|
265
|
+
'infrastructure.failed': z.strictObject({
|
|
266
|
+
provider: z.string().min(1),
|
|
267
|
+
workspaceRef: z.string().min(1),
|
|
268
|
+
instance: z.string().min(1),
|
|
269
|
+
environmentId: z.string().min(1).optional(),
|
|
270
|
+
sessionId: z.string().min(1).optional(),
|
|
271
|
+
operation: infrastructureOperation,
|
|
272
|
+
cause: infrastructureCause,
|
|
273
|
+
attempt,
|
|
274
|
+
retryable: z.boolean(),
|
|
275
|
+
cleanupPending: z.boolean(),
|
|
276
|
+
error: z.string().min(1),
|
|
277
|
+
}),
|
|
278
|
+
'infrastructure.cleanup-attempted': z.strictObject({
|
|
279
|
+
provider: z.string().min(1),
|
|
280
|
+
workspaceRef: z.string().min(1),
|
|
281
|
+
operation: z.enum(['stop', 'delete', 'reconcile']),
|
|
282
|
+
attempt,
|
|
283
|
+
outcome: z.enum(['confirmed', 'absent', 'unknown']),
|
|
284
|
+
error: z.string().min(1).optional(),
|
|
285
|
+
/** Snapshot-purge evidence for environments whose provider leaves automatic
|
|
286
|
+
* snapshots behind. Absence is provable from Store events without querying
|
|
287
|
+
* the provider; emitters without snapshots omit the field. */
|
|
288
|
+
snapshots: z
|
|
289
|
+
.strictObject({
|
|
290
|
+
outcome: z.enum(['confirmed', 'absent', 'unknown']),
|
|
291
|
+
deleted: z.number().int().min(0).optional(),
|
|
292
|
+
error: z.string().min(1).optional(),
|
|
293
|
+
})
|
|
294
|
+
.optional(),
|
|
295
|
+
}),
|
|
296
|
+
/** Remote phases deposit their output and park before trusted publication.
|
|
297
|
+
* The dispatcher settles the request only after the VM session and execution
|
|
298
|
+
* lease are gone, then appends the ordinary phase completion fact. */
|
|
299
|
+
'publication.requested': z.discriminatedUnion('operation', [
|
|
300
|
+
z.strictObject({
|
|
301
|
+
operation: z.literal('implement'),
|
|
302
|
+
branch: z.string().min(1),
|
|
303
|
+
sha: z.string().regex(/^[0-9a-f]{40,64}$/i),
|
|
304
|
+
round,
|
|
305
|
+
base: z.string().regex(/^[0-9a-f]{40,64}$/i),
|
|
306
|
+
artifact: artifactRefSchema,
|
|
307
|
+
}),
|
|
308
|
+
z.strictObject({
|
|
309
|
+
operation: z.literal('reconcile'),
|
|
310
|
+
branch: z.string().min(1),
|
|
311
|
+
sha: z.string().regex(/^[0-9a-f]{40,64}$/i),
|
|
312
|
+
artifact: artifactRefSchema,
|
|
313
|
+
}),
|
|
314
|
+
z.strictObject({
|
|
315
|
+
operation: z.literal('finalize'),
|
|
316
|
+
branch: z.string().min(1),
|
|
317
|
+
sha: z.string().regex(/^[0-9a-f]{40,64}$/i),
|
|
318
|
+
description: artifactRefSchema,
|
|
319
|
+
}),
|
|
320
|
+
z.strictObject({
|
|
321
|
+
operation: z.literal('finalize-step'),
|
|
322
|
+
branch: z.string().min(1),
|
|
323
|
+
sha: z.string().regex(/^[0-9a-f]{40,64}$/i),
|
|
324
|
+
step: z.string().min(1),
|
|
325
|
+
}),
|
|
326
|
+
]),
|
|
327
|
+
/** Durable loss record: the dispatcher released (or is about to release) the
|
|
328
|
+
* workspace while the referenced `publication.requested` (named by `request`,
|
|
329
|
+
* its seq) could not be completed. Appended by the last-chance release guard
|
|
330
|
+
* (settlePublicationBeforeRelease) BEFORE `workspace.released`, so an
|
|
331
|
+
* operator reading the log alone can distinguish pending (a request with no
|
|
332
|
+
* settling successor), done (the operation's completion fact), and lost
|
|
333
|
+
* (this event). Deliberately no competing status field — this fact plus the
|
|
334
|
+
* request's seq is the whole surface. Recovery continues through the
|
|
335
|
+
* existing abandoned-publication flow, which re-runs the phase. */
|
|
336
|
+
'publication.lost': z.strictObject({
|
|
337
|
+
/** seq of the lost `publication.requested` event. */
|
|
338
|
+
request: z.number().int().positive(),
|
|
339
|
+
operation: z.enum(['implement', 'reconcile', 'finalize', 'finalize-step']),
|
|
340
|
+
branch: z.string().min(1),
|
|
341
|
+
sha: z.string().regex(/^[0-9a-f]{40,64}$/i),
|
|
342
|
+
reason: z.string().trim().min(1),
|
|
343
|
+
}),
|
|
344
|
+
/** Checkpoints in the dispatcher-owned, retry-safe abort cleanup saga. */
|
|
345
|
+
'abort.remote-branch-deleted': z.strictObject({ branch: z.string().min(1) }),
|
|
346
|
+
'abort.local-branch-deleted': z.strictObject({ branch: z.string().min(1) }),
|
|
347
|
+
'abort.ticket-returned': z.strictObject({
|
|
348
|
+
ticket: ticketRefSchema,
|
|
349
|
+
state: z.string().min(1),
|
|
350
|
+
label: z.string().min(1),
|
|
351
|
+
}),
|
|
352
|
+
/** A post-creation dispatch attempt stopped before runner attachment. */
|
|
353
|
+
'dispatch.failed': z.strictObject({
|
|
354
|
+
stage: dispatchStage,
|
|
355
|
+
attempt,
|
|
356
|
+
error: z.string().min(1),
|
|
357
|
+
}),
|
|
358
|
+
/** Durable boundary preventing duplicate ticket notifications on recovery. */
|
|
359
|
+
'dispatch.comment-posted': empty,
|
|
360
|
+
|
|
361
|
+
// ── Operator commands (D2: commands are events in the same log) ───────────
|
|
362
|
+
'build.pause-requested': reasonOnly,
|
|
363
|
+
'build.resume-requested': reasonOnly,
|
|
364
|
+
'build.abort-requested': reasonOnly,
|
|
365
|
+
'build.discard-requested': empty,
|
|
366
|
+
'build.auto-merge-requested': empty,
|
|
367
|
+
'build.auto-merge-cancelled': empty,
|
|
368
|
+
'build.paused': empty,
|
|
369
|
+
'build.resumed': empty,
|
|
370
|
+
'build.aborted': empty,
|
|
371
|
+
|
|
372
|
+
// ── Spec (SPEC §6.3) ───────────────────────────────────────────────────────
|
|
373
|
+
'spec.imported': z.strictObject({
|
|
374
|
+
artifact: artifactRefSchema,
|
|
375
|
+
ticket: ticketRefSchema,
|
|
376
|
+
}),
|
|
377
|
+
'spec.authored': z.strictObject({
|
|
378
|
+
artifact: artifactRefSchema,
|
|
379
|
+
session: z.string().min(1),
|
|
380
|
+
}),
|
|
381
|
+
'spec.revised': z.strictObject({
|
|
382
|
+
artifact: artifactRefSchema,
|
|
383
|
+
/** seq of the `escalation.raised` event that forced the revision. */
|
|
384
|
+
escalation: z.number().int().positive(),
|
|
385
|
+
}),
|
|
386
|
+
|
|
387
|
+
// ── Sessions (every agent run is bracketed by these) ──────────────────────
|
|
388
|
+
'session.started': z.strictObject({
|
|
389
|
+
session: z.string().min(1),
|
|
390
|
+
role: z.string().min(1),
|
|
391
|
+
runner: z.string().min(1),
|
|
392
|
+
model: z.string().optional(),
|
|
393
|
+
/** Optional only so historical events replay without migration. */
|
|
394
|
+
args: z.array(z.string()).optional(),
|
|
395
|
+
phase: phaseSchema,
|
|
396
|
+
round: round.optional(),
|
|
397
|
+
/** Stream id this session's live view writes to (SPEC §9, session
|
|
398
|
+
* streams). Optional only so historical events replay unchanged. */
|
|
399
|
+
stream: z.string().min(1).optional(),
|
|
400
|
+
/** Present only when this target substituted for the preceding failed one. */
|
|
401
|
+
substitution: providerSubstitutionSchema.optional(),
|
|
402
|
+
}),
|
|
403
|
+
'session.ended': z.union([
|
|
404
|
+
/** Ordinary completion retains the historical transcript-bearing shape. */
|
|
405
|
+
z.strictObject({
|
|
406
|
+
session: z.string().min(1),
|
|
407
|
+
transcript: artifactRefSchema,
|
|
408
|
+
usage: z.strictObject({
|
|
409
|
+
inputTokens: z.number().int().nonnegative(),
|
|
410
|
+
outputTokens: z.number().int().nonnegative(),
|
|
411
|
+
turns: z.number().int().nonnegative(),
|
|
412
|
+
}),
|
|
413
|
+
}),
|
|
414
|
+
/** A recovering runner closes work whose transcript died with its prior
|
|
415
|
+
* instance. Takeover evidence replaces, rather than fabricates, a corpus
|
|
416
|
+
* artifact. */
|
|
417
|
+
z.strictObject({
|
|
418
|
+
session: z.string().min(1),
|
|
419
|
+
outcome: z.literal('reclaimed'),
|
|
420
|
+
reclaimedBy: z.strictObject({
|
|
421
|
+
instance: z.string().min(1),
|
|
422
|
+
resumedFromSeq: z.number().int().nonnegative(),
|
|
423
|
+
}),
|
|
424
|
+
}),
|
|
425
|
+
]),
|
|
426
|
+
|
|
427
|
+
// ── Plan loop / code loop (symmetric by design) ────────────────────────────
|
|
428
|
+
'plan.started': z.strictObject({
|
|
429
|
+
round,
|
|
430
|
+
/** Symmetric with `implement.started` (§15.6-B): guidance answered on a
|
|
431
|
+
* plan-loop escalation must reach the producer even when the runner
|
|
432
|
+
* parked and re-attached with a fresh session. The start is the carrier
|
|
433
|
+
* `ab context` materializes from; a later matching `session.started` is
|
|
434
|
+
* the durable consumption boundary. */
|
|
435
|
+
feedback: feedbackSchema.optional(),
|
|
436
|
+
}),
|
|
437
|
+
'plan.completed': z.strictObject({
|
|
438
|
+
round,
|
|
439
|
+
artifact: artifactRefSchema,
|
|
440
|
+
/** Effective plan-selected verify set in config order. Optional only so
|
|
441
|
+
* historical logs retain their default-all meaning. */
|
|
442
|
+
verifySteps: verifyStepSelectionSchema.optional(),
|
|
443
|
+
}),
|
|
444
|
+
'plan-review.started': z.strictObject({ round }),
|
|
445
|
+
'plan-review.verdict': reviewVerdictPayload,
|
|
446
|
+
'implement.started': z.strictObject({
|
|
447
|
+
round,
|
|
448
|
+
/** Carries routed feedback for this producer occurrence. Guidance remains
|
|
449
|
+
* recoverable until a matching `session.started` records launch. */
|
|
450
|
+
feedback: feedbackSchema.optional(),
|
|
451
|
+
}),
|
|
452
|
+
'implement.completed': z.strictObject({
|
|
453
|
+
round,
|
|
454
|
+
commits: commitRangeSchema,
|
|
455
|
+
artifact: artifactRefSchema,
|
|
456
|
+
}),
|
|
457
|
+
'code-review.started': z.strictObject({ round }),
|
|
458
|
+
'code-review.verdict': reviewVerdictPayload,
|
|
459
|
+
|
|
460
|
+
// ── Verify / finalize ──────────────────────────────────────────────────────
|
|
461
|
+
'verify.started': z.strictObject({
|
|
462
|
+
step: z.string().min(1),
|
|
463
|
+
attempt,
|
|
464
|
+
/** An answered escalation raised by this agent verifier returns to the
|
|
465
|
+
* same step. The start citation is the durable delivery carrier; the
|
|
466
|
+
* subsequent matching `session.started` is the exactly-once consumption
|
|
467
|
+
* boundary. Optional for historical logs and guidance-free check/skip
|
|
468
|
+
* starts. */
|
|
469
|
+
feedback: feedbackSchema.optional(),
|
|
470
|
+
}),
|
|
471
|
+
'verify.completed': verifyCompletedPayloadSchema,
|
|
472
|
+
'finalize.started': empty,
|
|
473
|
+
'finalize.completed': z.strictObject({
|
|
474
|
+
pr: z.strictObject({
|
|
475
|
+
number: z.number().int().positive(),
|
|
476
|
+
url: z.string().min(1),
|
|
477
|
+
headSha: z.string().min(1),
|
|
478
|
+
}),
|
|
479
|
+
}),
|
|
480
|
+
'finalize.step-completed': finalizeStepCompletedPayloadSchema,
|
|
481
|
+
|
|
482
|
+
// Any agent session may explicitly designate an exact deposited revision.
|
|
483
|
+
// Successful external uploads are recorded immediately so retries can adopt
|
|
484
|
+
// them and terminal cleanup never depends on a workspace or current config.
|
|
485
|
+
'pr-attachment.designated': z.strictObject({
|
|
486
|
+
artifact: artifactRefSchema,
|
|
487
|
+
filename: prAttachmentFilenameSchema,
|
|
488
|
+
mediaType: mediaTypeSchema,
|
|
489
|
+
}),
|
|
490
|
+
'pr-attachment.hosted': z.strictObject({
|
|
491
|
+
/** seq of the correlated pr-attachment.designated fact. */
|
|
492
|
+
designationSeq: z.number().int().positive(),
|
|
493
|
+
asset: hostedPrAttachmentAssetSchema,
|
|
494
|
+
}),
|
|
495
|
+
'pr-attachment.reclaimed': z.strictObject({
|
|
496
|
+
/** seq of the correlated pr-attachment.hosted fact. */
|
|
497
|
+
hostedSeq: z.number().int().positive(),
|
|
498
|
+
}),
|
|
499
|
+
'pr-attachment.reclaim-failed': z.strictObject({
|
|
500
|
+
hostedSeq: z.number().int().positive(),
|
|
501
|
+
attempt,
|
|
502
|
+
error: z.string().min(1),
|
|
503
|
+
}),
|
|
504
|
+
|
|
505
|
+
// ── Post-PR (D1: janitor duty of the dispatcher — SPEC §15.7) ─────────────
|
|
506
|
+
'pr.auto-merge-enabled': z.strictObject({
|
|
507
|
+
/** seq of the human auto-merge command this forge mutation applied. */
|
|
508
|
+
commandSeq: z.number().int().positive(),
|
|
509
|
+
}),
|
|
510
|
+
'pr.auto-merge-disabled': z.strictObject({
|
|
511
|
+
/** seq of the human auto-merge command this forge mutation applied. */
|
|
512
|
+
commandSeq: z.number().int().positive(),
|
|
513
|
+
}),
|
|
514
|
+
'pr.merged': z.strictObject({ sha: z.string().min(1) }),
|
|
515
|
+
'pr.closed': empty,
|
|
516
|
+
'pr.conflicted': z.strictObject({ baseSha: z.string().min(1) }),
|
|
517
|
+
'reconcile.progress-checked': z.strictObject({
|
|
518
|
+
/** seq of the repeat pr.conflicted fact this authoritative read answers. */
|
|
519
|
+
conflictSeq: z.number().int().positive(),
|
|
520
|
+
/** Completed reconcile whose merge target is being compared. */
|
|
521
|
+
attempt,
|
|
522
|
+
baseSha: z.string().min(1),
|
|
523
|
+
}),
|
|
524
|
+
'reconcile.started': z.strictObject({ attempt, baseSha: z.string().min(1) }),
|
|
525
|
+
'reconcile.completed': z.strictObject({
|
|
526
|
+
mergeCommit: z.string().min(1),
|
|
527
|
+
artifact: artifactRefSchema,
|
|
528
|
+
}),
|
|
529
|
+
|
|
530
|
+
// ── Cross-cutting ──────────────────────────────────────────────────────────
|
|
531
|
+
'observation.recorded': z.strictObject({
|
|
532
|
+
id: z.string().min(1),
|
|
533
|
+
kind: observationKindSchema,
|
|
534
|
+
summary: z.string().min(1),
|
|
535
|
+
files: z.array(z.string()).optional(),
|
|
536
|
+
refs: z.array(z.string()).optional(),
|
|
537
|
+
}),
|
|
538
|
+
'escalation.raised': z.strictObject({
|
|
539
|
+
id: z.string().min(1),
|
|
540
|
+
phase: escalationTargetSchema,
|
|
541
|
+
round: round.optional(),
|
|
542
|
+
source: escalationSourceSchema,
|
|
543
|
+
/** Optional only so cause-less historical policy facts remain replayable.
|
|
544
|
+
* validateEventWrite requires it on every current policy append. */
|
|
545
|
+
policyCause: policyEscalationCauseSchema.optional(),
|
|
546
|
+
question: z.string().min(1),
|
|
547
|
+
refs: z.array(z.string()).optional(),
|
|
548
|
+
}),
|
|
549
|
+
'escalation.answered': z.strictObject({
|
|
550
|
+
id: z.string().min(1),
|
|
551
|
+
answer: z.string().min(1),
|
|
552
|
+
resolution: escalationResolutionSchema,
|
|
553
|
+
/** Exact replacement spec authorized by a `revise-spec` answer. */
|
|
554
|
+
artifact: artifactRefSchema.optional(),
|
|
555
|
+
/** Absolute per-loop/current-spec review budget selected by the operator. */
|
|
556
|
+
reviewRoundCeiling: z.number().int().positive().optional(),
|
|
557
|
+
}),
|
|
558
|
+
/** Infra failure — distinct from verdicts (a verdict is a fact, not a failure). */
|
|
559
|
+
'phase.failed': z.strictObject({
|
|
560
|
+
phase: phaseSchema,
|
|
561
|
+
round: round.optional(),
|
|
562
|
+
attempt,
|
|
563
|
+
error: z.string().min(1),
|
|
564
|
+
willRetry: z.boolean(),
|
|
565
|
+
/** Ordered configured targets tried inside this one phase attempt. */
|
|
566
|
+
providerAttempts: providerAttemptsSchema.optional(),
|
|
567
|
+
}),
|
|
568
|
+
} as const
|
|
569
|
+
|
|
570
|
+
export type EventType = keyof typeof eventPayloadSchemas
|
|
571
|
+
|
|
572
|
+
export const EVENT_TYPES = Object.keys(eventPayloadSchemas) as EventType[]
|
|
573
|
+
|
|
574
|
+
export type EventPayload<T extends EventType> = z.infer<(typeof eventPayloadSchemas)[T]>
|
|
575
|
+
|
|
576
|
+
/** Canonical read shape shared by the reducer, engine, and query surfaces. */
|
|
577
|
+
export interface NormalizedVerifyCompletion {
|
|
578
|
+
step: string
|
|
579
|
+
attempt: number
|
|
580
|
+
outcome: VerifyOutcome
|
|
581
|
+
report?: ArtifactRef
|
|
582
|
+
reason?: string
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Normalize exactly once at each event-consumer boundary. Legacy booleans keep
|
|
587
|
+
* their historical meaning; `skipped` is never represented as either boolean.
|
|
588
|
+
*/
|
|
589
|
+
export function normalizeVerifyCompletion(
|
|
590
|
+
payload: EventPayload<'verify.completed'>,
|
|
591
|
+
): NormalizedVerifyCompletion {
|
|
592
|
+
if ('pass' in payload) {
|
|
593
|
+
return {
|
|
594
|
+
step: payload.step,
|
|
595
|
+
attempt: payload.attempt,
|
|
596
|
+
outcome: payload.pass ? 'pass' : 'fail',
|
|
597
|
+
...(payload.report !== undefined ? { report: payload.report } : {}),
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
if (payload.outcome === 'skipped') {
|
|
601
|
+
return {
|
|
602
|
+
step: payload.step,
|
|
603
|
+
attempt: payload.attempt,
|
|
604
|
+
outcome: 'skipped',
|
|
605
|
+
reason: payload.reason,
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return {
|
|
609
|
+
step: payload.step,
|
|
610
|
+
attempt: payload.attempt,
|
|
611
|
+
outcome: payload.outcome,
|
|
612
|
+
...(payload.report !== undefined ? { report: payload.report } : {}),
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
export function isEventType(value: string): value is EventType {
|
|
617
|
+
return Object.hasOwn(eventPayloadSchemas, value)
|
|
618
|
+
}
|