@abelxiaoxing/cadence 1.0.14 → 1.1.1
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/README.md +39 -13
- package/agents/contract-reviewer.md +2 -2
- package/agents/design-explorer.md +1 -1
- package/agents/diagnosis-worker.md +6 -3
- package/agents/implementation-worker.md +14 -9
- package/config/.env.example +5 -17
- package/config/routes.example.json +42 -0
- package/package.json +3 -3
- package/prompts/abel-design.md +206 -228
- package/prompts/abel-diagnose.md +39 -18
- package/prompts/abel-implement.md +122 -178
- package/prompts/abel-init.md +36 -33
- package/skills/abel-workflow/SKILL.md +190 -168
- package/src/apply-transaction.ts +1427 -0
- package/src/artifact-store.ts +204 -0
- package/src/child-session.ts +136 -17
- package/src/contracts.ts +121 -696
- package/src/control-contracts.ts +246 -0
- package/src/delivery-compiler.ts +1118 -0
- package/src/design-control.ts +846 -0
- package/src/design-journal.ts +1569 -0
- package/src/file-snapshot.ts +5 -0
- package/src/implement-graph.ts +11 -4
- package/src/index.ts +1833 -118
- package/src/isolation-backend.ts +269 -0
- package/src/packet-runtime.ts +533 -0
- package/src/parent-provider.ts +23 -4
- package/src/route-policy.ts +483 -0
- package/src/run-state.ts +430 -0
- package/src/run-store.ts +990 -0
- package/src/safe-path.ts +11 -0
- package/src/scoped-tools.ts +36 -0
- package/src/state-root.ts +215 -0
- package/src/subagent-activity.ts +526 -27
- package/src/submit-tool.ts +274 -0
- package/src/task-ledger.ts +1380 -0
- package/src/verification-capability.ts +560 -45
- package/src/worker-broker.ts +703 -0
- package/src/workflow-engine.ts +7706 -0
- package/src/workspace-store.ts +544 -0
- package/src/candidate-preflight.ts +0 -1618
- package/src/drain.ts +0 -20
- package/src/patch.ts +0 -505
- package/src/result-store.ts +0 -312
- package/src/runtime.ts +0 -2436
- package/src/scheduler.ts +0 -384
- package/src/subagent-endpoint.ts +0 -158
- package/src/worker.ts +0 -204
package/src/run-state.ts
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import type { ControlStage } from "./control-contracts.ts";
|
|
3
|
+
|
|
4
|
+
export const RUN_STATES = [
|
|
5
|
+
"created",
|
|
6
|
+
"validating-delivery",
|
|
7
|
+
"ready",
|
|
8
|
+
"queued",
|
|
9
|
+
"connecting",
|
|
10
|
+
"running",
|
|
11
|
+
"validating",
|
|
12
|
+
"verifying",
|
|
13
|
+
"retryable",
|
|
14
|
+
"paused",
|
|
15
|
+
"approval-needed",
|
|
16
|
+
"change-verifying",
|
|
17
|
+
"ready-to-apply",
|
|
18
|
+
"applying",
|
|
19
|
+
"recovering",
|
|
20
|
+
"completed",
|
|
21
|
+
"discarded",
|
|
22
|
+
"rejected",
|
|
23
|
+
] as const;
|
|
24
|
+
|
|
25
|
+
export const TASK_STATES = [
|
|
26
|
+
"pending",
|
|
27
|
+
"queued",
|
|
28
|
+
"phase-ready",
|
|
29
|
+
"phase-running",
|
|
30
|
+
"validating",
|
|
31
|
+
"phase-verified",
|
|
32
|
+
"repairable",
|
|
33
|
+
"retryable",
|
|
34
|
+
"paused",
|
|
35
|
+
"approval-needed",
|
|
36
|
+
"verified",
|
|
37
|
+
] as const;
|
|
38
|
+
|
|
39
|
+
export type RunState = (typeof RUN_STATES)[number];
|
|
40
|
+
export type TaskState = (typeof TASK_STATES)[number];
|
|
41
|
+
export type DeliveryGate = "gate-a" | "gate-b";
|
|
42
|
+
|
|
43
|
+
export const ZERO_EVENT_HASH = "0".repeat(64);
|
|
44
|
+
const SHA256 = /^[a-f0-9]{64}$/u;
|
|
45
|
+
|
|
46
|
+
export interface DeliveryBindingProjection {
|
|
47
|
+
gate: DeliveryGate;
|
|
48
|
+
revision: number;
|
|
49
|
+
receiptHash: string;
|
|
50
|
+
approvalProof?: {
|
|
51
|
+
revision: number;
|
|
52
|
+
contractHash: string;
|
|
53
|
+
recordHash: string;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface RunProjection {
|
|
58
|
+
runId: string;
|
|
59
|
+
rootHash: string;
|
|
60
|
+
stage: ControlStage;
|
|
61
|
+
change?: string;
|
|
62
|
+
provisionalKey?: string;
|
|
63
|
+
state: RunState;
|
|
64
|
+
sequence: number;
|
|
65
|
+
eventHash: string;
|
|
66
|
+
deliveryRevision?: number;
|
|
67
|
+
deliveryBindings: DeliveryBindingProjection[];
|
|
68
|
+
pauseCode?: string;
|
|
69
|
+
terminal?: "completed" | "discarded" | "rejected";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type RunEventType =
|
|
73
|
+
| "run-created"
|
|
74
|
+
| "change-bound"
|
|
75
|
+
| "delivery-bound"
|
|
76
|
+
| "state-transitioned"
|
|
77
|
+
| "operation-interrupted";
|
|
78
|
+
|
|
79
|
+
export interface RunEvent {
|
|
80
|
+
sequence: number;
|
|
81
|
+
type: RunEventType;
|
|
82
|
+
payload: Record<string, unknown>;
|
|
83
|
+
priorHash: string;
|
|
84
|
+
hash: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const TERMINAL_STATES = new Set<RunState>([
|
|
88
|
+
"completed",
|
|
89
|
+
"discarded",
|
|
90
|
+
"rejected",
|
|
91
|
+
]);
|
|
92
|
+
|
|
93
|
+
const LEGAL_TRANSITIONS: Readonly<Record<RunState, readonly RunState[]>> = {
|
|
94
|
+
created: ["validating-delivery", "paused", "discarded", "rejected"],
|
|
95
|
+
"validating-delivery": [
|
|
96
|
+
"ready",
|
|
97
|
+
"paused",
|
|
98
|
+
"approval-needed",
|
|
99
|
+
"discarded",
|
|
100
|
+
"rejected",
|
|
101
|
+
],
|
|
102
|
+
ready: ["queued", "running", "paused", "discarded", "rejected"],
|
|
103
|
+
queued: ["connecting", "running", "paused", "discarded"],
|
|
104
|
+
connecting: ["running", "retryable", "paused", "discarded"],
|
|
105
|
+
running: [
|
|
106
|
+
"validating",
|
|
107
|
+
"verifying",
|
|
108
|
+
"queued",
|
|
109
|
+
"retryable",
|
|
110
|
+
"paused",
|
|
111
|
+
"approval-needed",
|
|
112
|
+
"change-verifying",
|
|
113
|
+
"discarded",
|
|
114
|
+
],
|
|
115
|
+
validating: [
|
|
116
|
+
"running",
|
|
117
|
+
"verifying",
|
|
118
|
+
"retryable",
|
|
119
|
+
"paused",
|
|
120
|
+
"approval-needed",
|
|
121
|
+
"discarded",
|
|
122
|
+
],
|
|
123
|
+
verifying: [
|
|
124
|
+
"running",
|
|
125
|
+
"retryable",
|
|
126
|
+
"paused",
|
|
127
|
+
"approval-needed",
|
|
128
|
+
"change-verifying",
|
|
129
|
+
"discarded",
|
|
130
|
+
],
|
|
131
|
+
retryable: ["queued", "connecting", "running", "paused", "discarded"],
|
|
132
|
+
paused: [
|
|
133
|
+
"paused",
|
|
134
|
+
"validating-delivery",
|
|
135
|
+
"ready",
|
|
136
|
+
"queued",
|
|
137
|
+
"connecting",
|
|
138
|
+
"running",
|
|
139
|
+
"approval-needed",
|
|
140
|
+
"recovering",
|
|
141
|
+
"completed",
|
|
142
|
+
"discarded",
|
|
143
|
+
"rejected",
|
|
144
|
+
],
|
|
145
|
+
"approval-needed": ["validating-delivery", "paused", "discarded", "rejected"],
|
|
146
|
+
"change-verifying": [
|
|
147
|
+
"running",
|
|
148
|
+
"retryable",
|
|
149
|
+
"paused",
|
|
150
|
+
"approval-needed",
|
|
151
|
+
"ready-to-apply",
|
|
152
|
+
"discarded",
|
|
153
|
+
],
|
|
154
|
+
"ready-to-apply": ["applying", "paused", "discarded"],
|
|
155
|
+
applying: ["recovering", "completed"],
|
|
156
|
+
recovering: ["applying", "paused", "completed", "discarded", "rejected"],
|
|
157
|
+
completed: [],
|
|
158
|
+
discarded: [],
|
|
159
|
+
rejected: [],
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export function canonicalJson(value: unknown): string {
|
|
163
|
+
if (Array.isArray(value)) {
|
|
164
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
165
|
+
}
|
|
166
|
+
if (value && typeof value === "object") {
|
|
167
|
+
const entries = Object.entries(value as Record<string, unknown>)
|
|
168
|
+
.filter(([, entry]) => entry !== undefined)
|
|
169
|
+
.sort(([left], [right]) => left.localeCompare(right));
|
|
170
|
+
return `{${entries
|
|
171
|
+
.map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`)
|
|
172
|
+
.join(",")}}`;
|
|
173
|
+
}
|
|
174
|
+
return JSON.stringify(value) ?? "null";
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function hashRunEvent(event: Omit<RunEvent, "hash">): string {
|
|
178
|
+
return createHash("sha256").update(canonicalJson(event)).digest("hex");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function createRunEvent(
|
|
182
|
+
sequence: number,
|
|
183
|
+
type: RunEventType,
|
|
184
|
+
payload: Record<string, unknown>,
|
|
185
|
+
priorHash: string,
|
|
186
|
+
): RunEvent {
|
|
187
|
+
if (
|
|
188
|
+
!Number.isSafeInteger(sequence) ||
|
|
189
|
+
sequence < 1 ||
|
|
190
|
+
!SHA256.test(priorHash)
|
|
191
|
+
) {
|
|
192
|
+
throw new Error("event-integrity-invalid-header");
|
|
193
|
+
}
|
|
194
|
+
const unsigned = {
|
|
195
|
+
sequence,
|
|
196
|
+
type,
|
|
197
|
+
payload: structuredClone(payload),
|
|
198
|
+
priorHash,
|
|
199
|
+
};
|
|
200
|
+
return { ...unsigned, hash: hashRunEvent(unsigned) };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function canTransition(from: RunState, to: RunState): boolean {
|
|
204
|
+
return LEGAL_TRANSITIONS[from].includes(to);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function assertRunTransition(from: RunState, to: RunState): void {
|
|
208
|
+
if (!canTransition(from, to)) {
|
|
209
|
+
throw new Error(`invalid-run-transition:${from}:${to}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function requireString(payload: Record<string, unknown>, key: string): string {
|
|
214
|
+
const value = payload[key];
|
|
215
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
216
|
+
throw new Error(`event-integrity-invalid-${key}`);
|
|
217
|
+
}
|
|
218
|
+
return value;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function applyVerifiedEvent(
|
|
222
|
+
projection: RunProjection | undefined,
|
|
223
|
+
event: RunEvent,
|
|
224
|
+
): RunProjection {
|
|
225
|
+
switch (event.type) {
|
|
226
|
+
case "run-created": {
|
|
227
|
+
if (projection || event.sequence !== 1) {
|
|
228
|
+
throw new Error("event-integrity-duplicate-create");
|
|
229
|
+
}
|
|
230
|
+
const stage = event.payload.stage;
|
|
231
|
+
if (stage !== "abel-design" && stage !== "abel-implement") {
|
|
232
|
+
throw new Error("event-integrity-invalid-stage");
|
|
233
|
+
}
|
|
234
|
+
const change = event.payload.change;
|
|
235
|
+
const provisionalKey = event.payload.provisionalKey;
|
|
236
|
+
if (
|
|
237
|
+
(typeof change !== "string" || change.length === 0) ===
|
|
238
|
+
(typeof provisionalKey !== "string" || provisionalKey.length === 0)
|
|
239
|
+
) {
|
|
240
|
+
throw new Error("event-integrity-invalid-run-key");
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
runId: requireString(event.payload, "runId"),
|
|
244
|
+
rootHash: requireString(event.payload, "rootHash"),
|
|
245
|
+
stage,
|
|
246
|
+
...(typeof change === "string" ? { change } : {}),
|
|
247
|
+
...(typeof provisionalKey === "string" ? { provisionalKey } : {}),
|
|
248
|
+
state: "created",
|
|
249
|
+
sequence: event.sequence,
|
|
250
|
+
eventHash: event.hash,
|
|
251
|
+
deliveryBindings: [],
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
case "change-bound": {
|
|
255
|
+
if (projection?.stage !== "abel-design") {
|
|
256
|
+
throw new Error("event-integrity-change-binding");
|
|
257
|
+
}
|
|
258
|
+
const { provisionalKey: _provisionalKey, ...boundProjection } =
|
|
259
|
+
projection;
|
|
260
|
+
return {
|
|
261
|
+
...boundProjection,
|
|
262
|
+
change: requireString(event.payload, "change"),
|
|
263
|
+
sequence: event.sequence,
|
|
264
|
+
eventHash: event.hash,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
case "delivery-bound": {
|
|
268
|
+
if (!projection) throw new Error("event-integrity-missing-run");
|
|
269
|
+
const gate = event.payload.gate;
|
|
270
|
+
const revision = event.payload.revision;
|
|
271
|
+
const receiptHash = event.payload.receiptHash;
|
|
272
|
+
const approvalProof = event.payload.approvalProof;
|
|
273
|
+
if (
|
|
274
|
+
(gate !== "gate-a" && gate !== "gate-b") ||
|
|
275
|
+
!Number.isSafeInteger(revision) ||
|
|
276
|
+
(revision as number) < 1 ||
|
|
277
|
+
typeof receiptHash !== "string" ||
|
|
278
|
+
!SHA256.test(receiptHash) ||
|
|
279
|
+
(approvalProof !== undefined &&
|
|
280
|
+
(!approvalProof ||
|
|
281
|
+
typeof approvalProof !== "object" ||
|
|
282
|
+
Array.isArray(approvalProof) ||
|
|
283
|
+
!Number.isSafeInteger(
|
|
284
|
+
(approvalProof as Record<string, unknown>).revision,
|
|
285
|
+
) ||
|
|
286
|
+
Number((approvalProof as Record<string, unknown>).revision) < 1 ||
|
|
287
|
+
typeof (approvalProof as Record<string, unknown>).contractHash !==
|
|
288
|
+
"string" ||
|
|
289
|
+
!SHA256.test(
|
|
290
|
+
String((approvalProof as Record<string, unknown>).contractHash),
|
|
291
|
+
) ||
|
|
292
|
+
typeof (approvalProof as Record<string, unknown>).recordHash !==
|
|
293
|
+
"string" ||
|
|
294
|
+
!SHA256.test(
|
|
295
|
+
String((approvalProof as Record<string, unknown>).recordHash),
|
|
296
|
+
)))
|
|
297
|
+
) {
|
|
298
|
+
throw new Error("event-integrity-delivery-binding");
|
|
299
|
+
}
|
|
300
|
+
const binding: DeliveryBindingProjection = {
|
|
301
|
+
gate: gate as DeliveryGate,
|
|
302
|
+
revision: revision as number,
|
|
303
|
+
receiptHash,
|
|
304
|
+
...(approvalProof
|
|
305
|
+
? {
|
|
306
|
+
approvalProof: structuredClone(
|
|
307
|
+
approvalProof as DeliveryBindingProjection["approvalProof"],
|
|
308
|
+
),
|
|
309
|
+
}
|
|
310
|
+
: {}),
|
|
311
|
+
};
|
|
312
|
+
const existing = projection.deliveryBindings.find(
|
|
313
|
+
(candidate) =>
|
|
314
|
+
candidate.gate === binding.gate &&
|
|
315
|
+
candidate.revision === binding.revision,
|
|
316
|
+
);
|
|
317
|
+
if (
|
|
318
|
+
existing &&
|
|
319
|
+
(existing.receiptHash !== binding.receiptHash ||
|
|
320
|
+
canonicalJson(existing.approvalProof) !==
|
|
321
|
+
canonicalJson(binding.approvalProof))
|
|
322
|
+
) {
|
|
323
|
+
throw new Error("event-integrity-delivery-conflict");
|
|
324
|
+
}
|
|
325
|
+
const deliveryBindings = existing
|
|
326
|
+
? projection.deliveryBindings
|
|
327
|
+
: [...projection.deliveryBindings, binding].sort(
|
|
328
|
+
(left, right) =>
|
|
329
|
+
left.revision - right.revision ||
|
|
330
|
+
left.gate.localeCompare(right.gate),
|
|
331
|
+
);
|
|
332
|
+
return {
|
|
333
|
+
...projection,
|
|
334
|
+
deliveryBindings,
|
|
335
|
+
deliveryRevision: Math.max(
|
|
336
|
+
projection.deliveryRevision ?? 0,
|
|
337
|
+
binding.revision,
|
|
338
|
+
),
|
|
339
|
+
sequence: event.sequence,
|
|
340
|
+
eventHash: event.hash,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
case "state-transitioned": {
|
|
344
|
+
if (!projection) throw new Error("event-integrity-missing-run");
|
|
345
|
+
const from = event.payload.from;
|
|
346
|
+
const to = event.payload.to;
|
|
347
|
+
if (
|
|
348
|
+
from !== projection.state ||
|
|
349
|
+
!(RUN_STATES as readonly unknown[]).includes(to)
|
|
350
|
+
) {
|
|
351
|
+
throw new Error("event-integrity-state-mismatch");
|
|
352
|
+
}
|
|
353
|
+
assertRunTransition(projection.state, to as RunState);
|
|
354
|
+
const nextState = to as RunState;
|
|
355
|
+
const {
|
|
356
|
+
pauseCode: _pauseCode,
|
|
357
|
+
terminal: _terminal,
|
|
358
|
+
...transitionedProjection
|
|
359
|
+
} = projection;
|
|
360
|
+
return {
|
|
361
|
+
...transitionedProjection,
|
|
362
|
+
state: nextState,
|
|
363
|
+
sequence: event.sequence,
|
|
364
|
+
eventHash: event.hash,
|
|
365
|
+
...(typeof event.payload.code === "string"
|
|
366
|
+
? { pauseCode: event.payload.code }
|
|
367
|
+
: {}),
|
|
368
|
+
...(TERMINAL_STATES.has(nextState)
|
|
369
|
+
? { terminal: nextState as "completed" | "discarded" | "rejected" }
|
|
370
|
+
: {}),
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
case "operation-interrupted": {
|
|
374
|
+
if (!projection || TERMINAL_STATES.has(projection.state)) {
|
|
375
|
+
throw new Error("event-integrity-interruption");
|
|
376
|
+
}
|
|
377
|
+
const nextState =
|
|
378
|
+
projection.state === "applying" ? "recovering" : "paused";
|
|
379
|
+
if (nextState !== projection.state) {
|
|
380
|
+
assertRunTransition(projection.state, nextState);
|
|
381
|
+
}
|
|
382
|
+
return {
|
|
383
|
+
...projection,
|
|
384
|
+
state: nextState,
|
|
385
|
+
pauseCode: "operation-interrupted",
|
|
386
|
+
sequence: event.sequence,
|
|
387
|
+
eventHash: event.hash,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export function reduceRunEvents(events: readonly RunEvent[]): RunProjection {
|
|
394
|
+
if (events.length === 0) throw new Error("event-integrity-empty");
|
|
395
|
+
let projection: RunProjection | undefined;
|
|
396
|
+
let priorHash = ZERO_EVENT_HASH;
|
|
397
|
+
for (let index = 0; index < events.length; index += 1) {
|
|
398
|
+
const event = events[index];
|
|
399
|
+
if (
|
|
400
|
+
event.sequence !== index + 1 ||
|
|
401
|
+
event.priorHash !== priorHash ||
|
|
402
|
+
!SHA256.test(event.hash) ||
|
|
403
|
+
hashRunEvent({
|
|
404
|
+
sequence: event.sequence,
|
|
405
|
+
type: event.type,
|
|
406
|
+
payload: event.payload,
|
|
407
|
+
priorHash: event.priorHash,
|
|
408
|
+
}) !== event.hash
|
|
409
|
+
) {
|
|
410
|
+
throw new Error("event-integrity-hash-chain");
|
|
411
|
+
}
|
|
412
|
+
projection = applyVerifiedEvent(projection, event);
|
|
413
|
+
priorHash = event.hash;
|
|
414
|
+
}
|
|
415
|
+
if (!projection) throw new Error("event-integrity-empty");
|
|
416
|
+
return projection;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export function legalControlCommands(state: RunState): string[] {
|
|
420
|
+
if (TERMINAL_STATES.has(state)) return ["status"];
|
|
421
|
+
if (
|
|
422
|
+
state === "paused" ||
|
|
423
|
+
state === "retryable" ||
|
|
424
|
+
state === "approval-needed"
|
|
425
|
+
) {
|
|
426
|
+
return ["status", "resume", "rebind", "discard"];
|
|
427
|
+
}
|
|
428
|
+
if (state === "recovering") return ["status", "resume", "discard"];
|
|
429
|
+
return ["status", "cancel", "discard"];
|
|
430
|
+
}
|