@nifrajs/coding-agent 3.2.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/EXTENSIONS.md +25 -0
- package/PERFORMANCE.md +19 -0
- package/README.md +69 -0
- package/REGISTRY.md +15 -0
- package/SECURITY.md +32 -0
- package/dist/approvals.d.ts +70 -0
- package/dist/approvals.d.ts.map +1 -0
- package/dist/approvals.js +174 -0
- package/dist/approvals.js.map +1 -0
- package/dist/capabilities.d.ts +20 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +49 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/cli.d.ts +24 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +380 -0
- package/dist/cli.js.map +1 -0
- package/dist/context.d.ts +17 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +66 -0
- package/dist/context.js.map +1 -0
- package/dist/deployment-adapters.d.ts +40 -0
- package/dist/deployment-adapters.d.ts.map +1 -0
- package/dist/deployment-adapters.js +147 -0
- package/dist/deployment-adapters.js.map +1 -0
- package/dist/diff.d.ts +17 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +61 -0
- package/dist/diff.js.map +1 -0
- package/dist/errors.d.ts +9 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +35 -0
- package/dist/errors.js.map +1 -0
- package/dist/extensions.d.ts +104 -0
- package/dist/extensions.d.ts.map +1 -0
- package/dist/extensions.js +284 -0
- package/dist/extensions.js.map +1 -0
- package/dist/handoffs.d.ts +97 -0
- package/dist/handoffs.d.ts.map +1 -0
- package/dist/handoffs.js +199 -0
- package/dist/handoffs.js.map +1 -0
- package/dist/healing.d.ts +56 -0
- package/dist/healing.d.ts.map +1 -0
- package/dist/healing.js +54 -0
- package/dist/healing.js.map +1 -0
- package/dist/host.d.ts +61 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +369 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/isolated-worker.d.ts +2 -0
- package/dist/isolated-worker.d.ts.map +1 -0
- package/dist/isolated-worker.js +155 -0
- package/dist/isolated-worker.js.map +1 -0
- package/dist/isolated.d.ts +52 -0
- package/dist/isolated.d.ts.map +1 -0
- package/dist/isolated.js +293 -0
- package/dist/isolated.js.map +1 -0
- package/dist/native.d.ts +92 -0
- package/dist/native.d.ts.map +1 -0
- package/dist/native.js +339 -0
- package/dist/native.js.map +1 -0
- package/dist/orchestration/artifact-port.d.ts +30 -0
- package/dist/orchestration/artifact-port.d.ts.map +1 -0
- package/dist/orchestration/artifact-port.js +59 -0
- package/dist/orchestration/artifact-port.js.map +1 -0
- package/dist/orchestration/catalog.d.ts +71 -0
- package/dist/orchestration/catalog.d.ts.map +1 -0
- package/dist/orchestration/catalog.js +46 -0
- package/dist/orchestration/catalog.js.map +1 -0
- package/dist/orchestration/compile.d.ts +59 -0
- package/dist/orchestration/compile.d.ts.map +1 -0
- package/dist/orchestration/compile.js +268 -0
- package/dist/orchestration/compile.js.map +1 -0
- package/dist/orchestration/dispatch.d.ts +115 -0
- package/dist/orchestration/dispatch.d.ts.map +1 -0
- package/dist/orchestration/dispatch.js +220 -0
- package/dist/orchestration/dispatch.js.map +1 -0
- package/dist/orchestration/durable-jobs.d.ts +35 -0
- package/dist/orchestration/durable-jobs.d.ts.map +1 -0
- package/dist/orchestration/durable-jobs.js +273 -0
- package/dist/orchestration/durable-jobs.js.map +1 -0
- package/dist/orchestration/effect-key.d.ts +23 -0
- package/dist/orchestration/effect-key.d.ts.map +1 -0
- package/dist/orchestration/effect-key.js +24 -0
- package/dist/orchestration/effect-key.js.map +1 -0
- package/dist/orchestration/evidence-store.d.ts +75 -0
- package/dist/orchestration/evidence-store.d.ts.map +1 -0
- package/dist/orchestration/evidence-store.js +183 -0
- package/dist/orchestration/evidence-store.js.map +1 -0
- package/dist/orchestration/hash.d.ts +12 -0
- package/dist/orchestration/hash.d.ts.map +1 -0
- package/dist/orchestration/hash.js +43 -0
- package/dist/orchestration/hash.js.map +1 -0
- package/dist/orchestration/host.d.ts +121 -0
- package/dist/orchestration/host.d.ts.map +1 -0
- package/dist/orchestration/host.js +334 -0
- package/dist/orchestration/host.js.map +1 -0
- package/dist/orchestration/index.d.ts +20 -0
- package/dist/orchestration/index.d.ts.map +1 -0
- package/dist/orchestration/index.js +19 -0
- package/dist/orchestration/index.js.map +1 -0
- package/dist/orchestration/policy.d.ts +63 -0
- package/dist/orchestration/policy.d.ts.map +1 -0
- package/dist/orchestration/policy.js +80 -0
- package/dist/orchestration/policy.js.map +1 -0
- package/dist/orchestration/recovery.d.ts +87 -0
- package/dist/orchestration/recovery.d.ts.map +1 -0
- package/dist/orchestration/recovery.js +197 -0
- package/dist/orchestration/recovery.js.map +1 -0
- package/dist/orchestration/tracer.d.ts +31 -0
- package/dist/orchestration/tracer.d.ts.map +1 -0
- package/dist/orchestration/tracer.js +141 -0
- package/dist/orchestration/tracer.js.map +1 -0
- package/dist/plans.d.ts +35 -0
- package/dist/plans.d.ts.map +1 -0
- package/dist/plans.js +63 -0
- package/dist/plans.js.map +1 -0
- package/dist/presets.d.ts +10 -0
- package/dist/presets.d.ts.map +1 -0
- package/dist/presets.js +70 -0
- package/dist/presets.js.map +1 -0
- package/dist/process.d.ts +6 -0
- package/dist/process.d.ts.map +1 -0
- package/dist/process.js +42 -0
- package/dist/process.js.map +1 -0
- package/dist/registry.d.ts +37 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +51 -0
- package/dist/registry.js.map +1 -0
- package/dist/replay.d.ts +24 -0
- package/dist/replay.d.ts.map +1 -0
- package/dist/replay.js +154 -0
- package/dist/replay.js.map +1 -0
- package/dist/rpc.d.ts +51 -0
- package/dist/rpc.d.ts.map +1 -0
- package/dist/rpc.js +412 -0
- package/dist/rpc.js.map +1 -0
- package/dist/session-migration.d.ts +59 -0
- package/dist/session-migration.d.ts.map +1 -0
- package/dist/session-migration.js +315 -0
- package/dist/session-migration.js.map +1 -0
- package/dist/sessions.d.ts +74 -0
- package/dist/sessions.d.ts.map +1 -0
- package/dist/sessions.js +222 -0
- package/dist/sessions.js.map +1 -0
- package/dist/subagents.d.ts +51 -0
- package/dist/subagents.d.ts.map +1 -0
- package/dist/subagents.js +141 -0
- package/dist/subagents.js.map +1 -0
- package/dist/tools.d.ts +13 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +37 -0
- package/dist/tools.js.map +1 -0
- package/dist/ui.d.ts +42 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +93 -0
- package/dist/ui.js.map +1 -0
- package/dist/verification.d.ts +34 -0
- package/dist/verification.d.ts.map +1 -0
- package/dist/verification.js +91 -0
- package/dist/verification.js.map +1 -0
- package/dist/workflows.d.ts +98 -0
- package/dist/workflows.d.ts.map +1 -0
- package/dist/workflows.js +174 -0
- package/dist/workflows.js.map +1 -0
- package/package.json +84 -0
- package/src/approvals.ts +249 -0
- package/src/capabilities.ts +74 -0
- package/src/cli.ts +405 -0
- package/src/context.ts +81 -0
- package/src/deployment-adapters.ts +181 -0
- package/src/diff.ts +80 -0
- package/src/errors.ts +46 -0
- package/src/extensions.ts +402 -0
- package/src/handoffs.ts +299 -0
- package/src/healing.ts +93 -0
- package/src/host.ts +465 -0
- package/src/index.ts +165 -0
- package/src/isolated-worker.ts +177 -0
- package/src/isolated.ts +344 -0
- package/src/native.ts +478 -0
- package/src/orchestration/artifact-port.ts +75 -0
- package/src/orchestration/catalog.ts +102 -0
- package/src/orchestration/compile.ts +373 -0
- package/src/orchestration/dispatch.ts +383 -0
- package/src/orchestration/durable-jobs.ts +374 -0
- package/src/orchestration/effect-key.ts +35 -0
- package/src/orchestration/evidence-store.ts +233 -0
- package/src/orchestration/hash.ts +43 -0
- package/src/orchestration/host.ts +445 -0
- package/src/orchestration/index.ts +131 -0
- package/src/orchestration/policy.ts +116 -0
- package/src/orchestration/recovery.ts +271 -0
- package/src/orchestration/tracer.ts +177 -0
- package/src/plans.ts +105 -0
- package/src/presets.ts +90 -0
- package/src/process.ts +47 -0
- package/src/registry.ts +80 -0
- package/src/replay.ts +190 -0
- package/src/rpc.ts +598 -0
- package/src/session-migration.ts +401 -0
- package/src/sessions.ts +303 -0
- package/src/subagents.ts +210 -0
- package/src/tools.ts +52 -0
- package/src/ui.ts +137 -0
- package/src/verification.ts +133 -0
- package/src/workflows.ts +279 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evidence-only dispatch contracts for at-least-once run execution.
|
|
3
|
+
*
|
|
4
|
+
* These values deliberately contain no job payload, tenant state, credentials, retention policy,
|
|
5
|
+
* error message, or tool/model data. A durable operated adapter may implement the ports, but the
|
|
6
|
+
* public reference adapters are disposable and keep only identifiers, digests, counters, and
|
|
7
|
+
* lifecycle evidence. Delivery is at-least-once; callers make effects idempotent with the key
|
|
8
|
+
* derived below and prove completion before a recovered worker skips the effect.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { sha256HexOf } from "./hash.ts"
|
|
12
|
+
|
|
13
|
+
export const RUN_DISPATCH_VERSION = 1 as const
|
|
14
|
+
export const DISPATCH_JOB_NAME = "nifra.agent.run-node"
|
|
15
|
+
export const MAX_DISPATCH_STRING = 256
|
|
16
|
+
export const MAX_DISPATCH_ATTEMPTS = 16
|
|
17
|
+
|
|
18
|
+
export type DispatchBoundary = "before-effect" | "after-effect"
|
|
19
|
+
export type DispatchState =
|
|
20
|
+
| "queued"
|
|
21
|
+
| "leased"
|
|
22
|
+
| "checkpointed"
|
|
23
|
+
| "retrying"
|
|
24
|
+
| "succeeded"
|
|
25
|
+
| "failed"
|
|
26
|
+
| "cancelled"
|
|
27
|
+
| "dead-lettered"
|
|
28
|
+
|
|
29
|
+
export type DispatchResultCode =
|
|
30
|
+
| "accepted"
|
|
31
|
+
| "stale_lease"
|
|
32
|
+
| "cancelled"
|
|
33
|
+
| "terminal"
|
|
34
|
+
| "invalid_checkpoint"
|
|
35
|
+
| "idempotency_required"
|
|
36
|
+
|
|
37
|
+
export interface InjectedClock {
|
|
38
|
+
readonly now: () => number
|
|
39
|
+
readonly sleep?: (milliseconds: number) => Promise<void>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const systemClock: InjectedClock = Object.freeze({ now: () => Date.now() })
|
|
43
|
+
|
|
44
|
+
/** A queued node identity. It is safe to persist and safe to show in evidence. */
|
|
45
|
+
export interface RunDispatch {
|
|
46
|
+
readonly version: typeof RUN_DISPATCH_VERSION
|
|
47
|
+
readonly dispatchId: string
|
|
48
|
+
readonly runId: string
|
|
49
|
+
readonly planDigest: string
|
|
50
|
+
readonly nodeId: string
|
|
51
|
+
readonly maxAttempts: number
|
|
52
|
+
readonly notBefore: number
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** A lease with a generation. A completion from any older generation is rejected. */
|
|
56
|
+
export interface RunLease {
|
|
57
|
+
readonly version: typeof RUN_DISPATCH_VERSION
|
|
58
|
+
readonly leaseId: string
|
|
59
|
+
readonly dispatch: RunDispatch
|
|
60
|
+
/** Logical attempt is stable across duplicate delivery while the same attempt is unresolved. */
|
|
61
|
+
readonly attempt: number
|
|
62
|
+
readonly generation: number
|
|
63
|
+
readonly leasedAt: number
|
|
64
|
+
readonly leaseUntil: number
|
|
65
|
+
readonly scheduleToken: string
|
|
66
|
+
readonly idempotencyKey: string
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** A safe boundary checkpoint. It never contains a result or effect payload. */
|
|
70
|
+
export interface RunCheckpoint {
|
|
71
|
+
readonly version: typeof RUN_DISPATCH_VERSION
|
|
72
|
+
readonly dispatchId: string
|
|
73
|
+
readonly runId: string
|
|
74
|
+
readonly nodeId: string
|
|
75
|
+
readonly attempt: number
|
|
76
|
+
readonly generation: number
|
|
77
|
+
readonly boundary: DispatchBoundary
|
|
78
|
+
readonly idempotencyKey: string
|
|
79
|
+
readonly at: number
|
|
80
|
+
readonly scheduleToken: string
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** A terminal or transitional record suitable for a public evidence sink. */
|
|
84
|
+
export interface RunDispatchEvidence {
|
|
85
|
+
readonly version: typeof RUN_DISPATCH_VERSION
|
|
86
|
+
readonly dispatchId: string
|
|
87
|
+
readonly runId: string
|
|
88
|
+
readonly nodeId: string
|
|
89
|
+
readonly state: DispatchState
|
|
90
|
+
readonly attempt: number
|
|
91
|
+
readonly generation: number
|
|
92
|
+
readonly at: number
|
|
93
|
+
readonly scheduleToken: string
|
|
94
|
+
readonly idempotencyKey: string
|
|
95
|
+
readonly code?: string
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface DispatchWriteResult {
|
|
99
|
+
readonly ok: boolean
|
|
100
|
+
readonly code: DispatchResultCode
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface DispatchInspection {
|
|
104
|
+
readonly dispatch: RunDispatch
|
|
105
|
+
readonly state: DispatchState
|
|
106
|
+
readonly attempt: number
|
|
107
|
+
readonly generation: number
|
|
108
|
+
readonly checkpoint?: RunCheckpoint
|
|
109
|
+
readonly evidence: readonly RunDispatchEvidence[]
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface RunDispatchStore {
|
|
113
|
+
enqueue(dispatch: RunDispatch): void | Promise<void>
|
|
114
|
+
lease(now: number, limit: number, leaseMs: number): RunLease[] | Promise<RunLease[]>
|
|
115
|
+
checkpoint(
|
|
116
|
+
lease: RunLease,
|
|
117
|
+
checkpoint: RunCheckpoint,
|
|
118
|
+
): DispatchWriteResult | Promise<DispatchWriteResult>
|
|
119
|
+
complete(
|
|
120
|
+
lease: RunLease,
|
|
121
|
+
evidence?: RunDispatchEvidence,
|
|
122
|
+
): DispatchWriteResult | Promise<DispatchWriteResult>
|
|
123
|
+
retry(
|
|
124
|
+
lease: RunLease,
|
|
125
|
+
runAt: number,
|
|
126
|
+
code: string,
|
|
127
|
+
): DispatchWriteResult | Promise<DispatchWriteResult>
|
|
128
|
+
deadLetter(lease: RunLease, code: string): DispatchWriteResult | Promise<DispatchWriteResult>
|
|
129
|
+
cancel(runId: string, code?: string): number | Promise<number>
|
|
130
|
+
inspect(
|
|
131
|
+
dispatchId: string,
|
|
132
|
+
): DispatchInspection | undefined | Promise<DispatchInspection | undefined>
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Optional operated-depth handoff. The context is intentionally opaque: the public package does not
|
|
137
|
+
* model tenants, rows, RLS policy text, retention periods, credentials, workers, or fleet topology.
|
|
138
|
+
* A private adapter supplies those controls and still implements the evidence-only store port above.
|
|
139
|
+
*/
|
|
140
|
+
export interface DurableDispatchAdapter<OpaqueContext = unknown> extends RunDispatchStore {
|
|
141
|
+
authorize(
|
|
142
|
+
context: OpaqueContext,
|
|
143
|
+
operation: "enqueue" | "lease" | "inspect" | "settle",
|
|
144
|
+
): boolean | Promise<boolean>
|
|
145
|
+
enforceDataLayerPolicy(
|
|
146
|
+
context: OpaqueContext,
|
|
147
|
+
operation: "read" | "write",
|
|
148
|
+
): boolean | Promise<boolean>
|
|
149
|
+
retainEvidence(context: OpaqueContext, evidence: RunDispatchEvidence): void | Promise<void>
|
|
150
|
+
reconcile(context: OpaqueContext, dispatchId: string): void | Promise<void>
|
|
151
|
+
workerHealth(
|
|
152
|
+
context: OpaqueContext,
|
|
153
|
+
): "ready" | "draining" | "unavailable" | Promise<"ready" | "draining" | "unavailable">
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const DIGEST = /^[0-9a-f]{64}$/
|
|
157
|
+
const TOKEN = /^[A-Za-z0-9._:-]{1,256}$/
|
|
158
|
+
const FORBIDDEN = new Set([
|
|
159
|
+
"prompt",
|
|
160
|
+
"message",
|
|
161
|
+
"text",
|
|
162
|
+
"input",
|
|
163
|
+
"output",
|
|
164
|
+
"arguments",
|
|
165
|
+
"body",
|
|
166
|
+
"response",
|
|
167
|
+
"secret",
|
|
168
|
+
"credential",
|
|
169
|
+
"diagnostic",
|
|
170
|
+
"stack",
|
|
171
|
+
"content",
|
|
172
|
+
"transcript",
|
|
173
|
+
"artifact",
|
|
174
|
+
])
|
|
175
|
+
|
|
176
|
+
function record(value: unknown, where: string): Record<string, unknown> {
|
|
177
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
178
|
+
throw new TypeError(`${where} must be an object`)
|
|
179
|
+
return value as Record<string, unknown>
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function rejectKeys(
|
|
183
|
+
value: Record<string, unknown>,
|
|
184
|
+
allowed: ReadonlySet<string>,
|
|
185
|
+
where: string,
|
|
186
|
+
): void {
|
|
187
|
+
for (const key of Object.keys(value)) {
|
|
188
|
+
if (FORBIDDEN.has(key.toLowerCase()))
|
|
189
|
+
throw new TypeError(`${where} contains forbidden field '${key}'`)
|
|
190
|
+
if (!allowed.has(key)) throw new TypeError(`${where} contains unknown field '${key}'`)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function boundedString(value: unknown, name: string): string {
|
|
195
|
+
if (typeof value !== "string" || value.length === 0 || value.length > MAX_DISPATCH_STRING)
|
|
196
|
+
throw new TypeError(`${name} must be a non-empty bounded string`)
|
|
197
|
+
return value
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function digest(value: unknown, name: string): string {
|
|
201
|
+
const text = boundedString(value, name)
|
|
202
|
+
if (!DIGEST.test(text)) throw new TypeError(`${name} must be a lowercase sha256 digest`)
|
|
203
|
+
return text
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function token(value: unknown, name: string): string {
|
|
207
|
+
const text = boundedString(value, name)
|
|
208
|
+
if (!TOKEN.test(text)) throw new TypeError(`${name} must be an opaque schedule token`)
|
|
209
|
+
return text
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function nonNegative(value: unknown, name: string): number {
|
|
213
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
|
|
214
|
+
throw new TypeError(`${name} must be a non-negative safe integer`)
|
|
215
|
+
return value
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function attempts(value: unknown): number {
|
|
219
|
+
const result = nonNegative(value, "maxAttempts")
|
|
220
|
+
if (result < 1 || result > MAX_DISPATCH_ATTEMPTS)
|
|
221
|
+
throw new RangeError(`maxAttempts must be between 1 and ${MAX_DISPATCH_ATTEMPTS}`)
|
|
222
|
+
return result
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const DISPATCH_KEYS = new Set([
|
|
226
|
+
"version",
|
|
227
|
+
"dispatchId",
|
|
228
|
+
"runId",
|
|
229
|
+
"planDigest",
|
|
230
|
+
"nodeId",
|
|
231
|
+
"maxAttempts",
|
|
232
|
+
"notBefore",
|
|
233
|
+
])
|
|
234
|
+
|
|
235
|
+
/** Parse a dispatch identity and reject content or undeclared fields. */
|
|
236
|
+
export function parseRunDispatch(value: unknown): RunDispatch {
|
|
237
|
+
const input = record(value, "run dispatch")
|
|
238
|
+
rejectKeys(input, DISPATCH_KEYS, "run dispatch")
|
|
239
|
+
if (input.version !== RUN_DISPATCH_VERSION)
|
|
240
|
+
throw new TypeError("run dispatch.version is unsupported")
|
|
241
|
+
return Object.freeze({
|
|
242
|
+
version: RUN_DISPATCH_VERSION,
|
|
243
|
+
dispatchId: token(input.dispatchId, "dispatchId"),
|
|
244
|
+
runId: token(input.runId, "runId"),
|
|
245
|
+
planDigest: digest(input.planDigest, "planDigest"),
|
|
246
|
+
nodeId: token(input.nodeId, "nodeId"),
|
|
247
|
+
maxAttempts: attempts(input.maxAttempts),
|
|
248
|
+
notBefore: nonNegative(input.notBefore, "notBefore"),
|
|
249
|
+
})
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const LEASE_KEYS = new Set([
|
|
253
|
+
"version",
|
|
254
|
+
"leaseId",
|
|
255
|
+
"dispatch",
|
|
256
|
+
"attempt",
|
|
257
|
+
"generation",
|
|
258
|
+
"leasedAt",
|
|
259
|
+
"leaseUntil",
|
|
260
|
+
"scheduleToken",
|
|
261
|
+
"idempotencyKey",
|
|
262
|
+
])
|
|
263
|
+
|
|
264
|
+
export function parseRunLease(value: unknown): RunLease {
|
|
265
|
+
const input = record(value, "run lease")
|
|
266
|
+
rejectKeys(input, LEASE_KEYS, "run lease")
|
|
267
|
+
if (input.version !== RUN_DISPATCH_VERSION)
|
|
268
|
+
throw new TypeError("run lease.version is unsupported")
|
|
269
|
+
const attempt = attempts(input.attempt)
|
|
270
|
+
const generation = nonNegative(input.generation, "generation")
|
|
271
|
+
return Object.freeze({
|
|
272
|
+
version: RUN_DISPATCH_VERSION,
|
|
273
|
+
leaseId: token(input.leaseId, "leaseId"),
|
|
274
|
+
dispatch: parseRunDispatch(input.dispatch),
|
|
275
|
+
attempt,
|
|
276
|
+
generation,
|
|
277
|
+
leasedAt: nonNegative(input.leasedAt, "leasedAt"),
|
|
278
|
+
leaseUntil: nonNegative(input.leaseUntil, "leaseUntil"),
|
|
279
|
+
scheduleToken: token(input.scheduleToken, "scheduleToken"),
|
|
280
|
+
idempotencyKey: token(input.idempotencyKey, "idempotencyKey"),
|
|
281
|
+
})
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const CHECKPOINT_KEYS = new Set([
|
|
285
|
+
"version",
|
|
286
|
+
"dispatchId",
|
|
287
|
+
"runId",
|
|
288
|
+
"nodeId",
|
|
289
|
+
"attempt",
|
|
290
|
+
"generation",
|
|
291
|
+
"boundary",
|
|
292
|
+
"idempotencyKey",
|
|
293
|
+
"at",
|
|
294
|
+
"scheduleToken",
|
|
295
|
+
])
|
|
296
|
+
|
|
297
|
+
export function parseRunCheckpoint(value: unknown): RunCheckpoint {
|
|
298
|
+
const input = record(value, "run checkpoint")
|
|
299
|
+
rejectKeys(input, CHECKPOINT_KEYS, "run checkpoint")
|
|
300
|
+
if (input.version !== RUN_DISPATCH_VERSION)
|
|
301
|
+
throw new TypeError("run checkpoint.version is unsupported")
|
|
302
|
+
const boundary = input.boundary
|
|
303
|
+
if (boundary !== "before-effect" && boundary !== "after-effect")
|
|
304
|
+
throw new TypeError("run checkpoint.boundary is invalid")
|
|
305
|
+
return Object.freeze({
|
|
306
|
+
version: RUN_DISPATCH_VERSION,
|
|
307
|
+
dispatchId: token(input.dispatchId, "dispatchId"),
|
|
308
|
+
runId: token(input.runId, "runId"),
|
|
309
|
+
nodeId: token(input.nodeId, "nodeId"),
|
|
310
|
+
attempt: attempts(input.attempt),
|
|
311
|
+
generation: nonNegative(input.generation, "generation"),
|
|
312
|
+
boundary,
|
|
313
|
+
idempotencyKey: token(input.idempotencyKey, "idempotencyKey"),
|
|
314
|
+
at: nonNegative(input.at, "at"),
|
|
315
|
+
scheduleToken: token(input.scheduleToken, "scheduleToken"),
|
|
316
|
+
})
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const EVIDENCE_KEYS = new Set([
|
|
320
|
+
"version",
|
|
321
|
+
"dispatchId",
|
|
322
|
+
"runId",
|
|
323
|
+
"nodeId",
|
|
324
|
+
"state",
|
|
325
|
+
"attempt",
|
|
326
|
+
"generation",
|
|
327
|
+
"at",
|
|
328
|
+
"scheduleToken",
|
|
329
|
+
"idempotencyKey",
|
|
330
|
+
"code",
|
|
331
|
+
])
|
|
332
|
+
|
|
333
|
+
const STATES: ReadonlySet<string> = new Set([
|
|
334
|
+
"queued",
|
|
335
|
+
"leased",
|
|
336
|
+
"checkpointed",
|
|
337
|
+
"retrying",
|
|
338
|
+
"succeeded",
|
|
339
|
+
"failed",
|
|
340
|
+
"cancelled",
|
|
341
|
+
"dead-lettered",
|
|
342
|
+
])
|
|
343
|
+
|
|
344
|
+
export function parseRunDispatchEvidence(value: unknown): RunDispatchEvidence {
|
|
345
|
+
const input = record(value, "run dispatch evidence")
|
|
346
|
+
rejectKeys(input, EVIDENCE_KEYS, "run dispatch evidence")
|
|
347
|
+
if (input.version !== RUN_DISPATCH_VERSION)
|
|
348
|
+
throw new TypeError("run dispatch evidence.version is unsupported")
|
|
349
|
+
if (typeof input.state !== "string" || !STATES.has(input.state))
|
|
350
|
+
throw new TypeError("run dispatch evidence.state is invalid")
|
|
351
|
+
return Object.freeze({
|
|
352
|
+
version: RUN_DISPATCH_VERSION,
|
|
353
|
+
dispatchId: token(input.dispatchId, "dispatchId"),
|
|
354
|
+
runId: token(input.runId, "runId"),
|
|
355
|
+
nodeId: token(input.nodeId, "nodeId"),
|
|
356
|
+
state: input.state as DispatchState,
|
|
357
|
+
attempt: attempts(input.attempt),
|
|
358
|
+
generation: nonNegative(input.generation, "generation"),
|
|
359
|
+
at: nonNegative(input.at, "at"),
|
|
360
|
+
scheduleToken: token(input.scheduleToken, "scheduleToken"),
|
|
361
|
+
idempotencyKey: token(input.idempotencyKey, "idempotencyKey"),
|
|
362
|
+
...(input.code === undefined ? {} : { code: token(input.code, "code") }),
|
|
363
|
+
})
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/** Stable key for one logical node attempt. Duplicate delivery uses the same boundary number. */
|
|
367
|
+
export function deriveRunIdempotencyKey(
|
|
368
|
+
planDigest: string,
|
|
369
|
+
runId: string,
|
|
370
|
+
nodeId: string,
|
|
371
|
+
logicalAttemptBoundary: number,
|
|
372
|
+
): Promise<string> {
|
|
373
|
+
const plan = digest(planDigest, "planDigest")
|
|
374
|
+
const run = token(runId, "runId")
|
|
375
|
+
const node = token(nodeId, "nodeId")
|
|
376
|
+
const attempt = attempts(logicalAttemptBoundary)
|
|
377
|
+
return sha256HexOf(`${plan}\u0000${run}\u0000${node}\u0000${attempt}`)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** Create a parsed dispatch identity before handing it to any adapter. */
|
|
381
|
+
export function createRunDispatch(input: Omit<RunDispatch, "version">): RunDispatch {
|
|
382
|
+
return parseRunDispatch({ version: RUN_DISPATCH_VERSION, ...input })
|
|
383
|
+
}
|