@abelxiaoxing/cadence 1.1.0 → 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 +11 -1
- package/package.json +1 -1
- package/prompts/abel-design.md +69 -13
- package/prompts/abel-diagnose.md +2 -0
- package/prompts/abel-implement.md +19 -8
- package/skills/abel-workflow/SKILL.md +37 -3
- package/src/contracts.ts +18 -2
- package/src/control-contracts.ts +2 -15
- package/src/delivery-compiler.ts +47 -17
- package/src/design-control.ts +846 -0
- package/src/design-journal.ts +1569 -0
- package/src/index.ts +580 -197
- package/src/run-state.ts +39 -2
- package/src/run-store.ts +81 -9
- package/src/subagent-activity.ts +1 -1
- package/src/verification-capability.ts +43 -15
- package/src/workflow-engine.ts +428 -113
package/src/run-state.ts
CHANGED
|
@@ -47,6 +47,11 @@ export interface DeliveryBindingProjection {
|
|
|
47
47
|
gate: DeliveryGate;
|
|
48
48
|
revision: number;
|
|
49
49
|
receiptHash: string;
|
|
50
|
+
approvalProof?: {
|
|
51
|
+
revision: number;
|
|
52
|
+
contractHash: string;
|
|
53
|
+
recordHash: string;
|
|
54
|
+
};
|
|
50
55
|
}
|
|
51
56
|
|
|
52
57
|
export interface RunProjection {
|
|
@@ -133,6 +138,7 @@ const LEGAL_TRANSITIONS: Readonly<Record<RunState, readonly RunState[]>> = {
|
|
|
133
138
|
"running",
|
|
134
139
|
"approval-needed",
|
|
135
140
|
"recovering",
|
|
141
|
+
"completed",
|
|
136
142
|
"discarded",
|
|
137
143
|
"rejected",
|
|
138
144
|
],
|
|
@@ -263,12 +269,31 @@ function applyVerifiedEvent(
|
|
|
263
269
|
const gate = event.payload.gate;
|
|
264
270
|
const revision = event.payload.revision;
|
|
265
271
|
const receiptHash = event.payload.receiptHash;
|
|
272
|
+
const approvalProof = event.payload.approvalProof;
|
|
266
273
|
if (
|
|
267
274
|
(gate !== "gate-a" && gate !== "gate-b") ||
|
|
268
275
|
!Number.isSafeInteger(revision) ||
|
|
269
276
|
(revision as number) < 1 ||
|
|
270
277
|
typeof receiptHash !== "string" ||
|
|
271
|
-
!SHA256.test(receiptHash)
|
|
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
|
+
)))
|
|
272
297
|
) {
|
|
273
298
|
throw new Error("event-integrity-delivery-binding");
|
|
274
299
|
}
|
|
@@ -276,13 +301,25 @@ function applyVerifiedEvent(
|
|
|
276
301
|
gate: gate as DeliveryGate,
|
|
277
302
|
revision: revision as number,
|
|
278
303
|
receiptHash,
|
|
304
|
+
...(approvalProof
|
|
305
|
+
? {
|
|
306
|
+
approvalProof: structuredClone(
|
|
307
|
+
approvalProof as DeliveryBindingProjection["approvalProof"],
|
|
308
|
+
),
|
|
309
|
+
}
|
|
310
|
+
: {}),
|
|
279
311
|
};
|
|
280
312
|
const existing = projection.deliveryBindings.find(
|
|
281
313
|
(candidate) =>
|
|
282
314
|
candidate.gate === binding.gate &&
|
|
283
315
|
candidate.revision === binding.revision,
|
|
284
316
|
);
|
|
285
|
-
if (
|
|
317
|
+
if (
|
|
318
|
+
existing &&
|
|
319
|
+
(existing.receiptHash !== binding.receiptHash ||
|
|
320
|
+
canonicalJson(existing.approvalProof) !==
|
|
321
|
+
canonicalJson(binding.approvalProof))
|
|
322
|
+
) {
|
|
286
323
|
throw new Error("event-integrity-delivery-conflict");
|
|
287
324
|
}
|
|
288
325
|
const deliveryBindings = existing
|
package/src/run-store.ts
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
StateRootError,
|
|
18
18
|
} from "./state-root.ts";
|
|
19
19
|
|
|
20
|
-
export const RUN_STORE_SCHEMA_VERSION =
|
|
20
|
+
export const RUN_STORE_SCHEMA_VERSION = 4 as const;
|
|
21
21
|
|
|
22
22
|
const CHANGE_NAME = /^[a-z0-9](?:[a-z0-9-]{0,126}[a-z0-9])?$/u;
|
|
23
23
|
const IDENTIFIER = /^[a-z0-9](?:[a-z0-9._:-]{0,126}[a-z0-9])?$/iu;
|
|
@@ -60,6 +60,11 @@ export interface BindDeliveryInput {
|
|
|
60
60
|
gate: DeliveryGate;
|
|
61
61
|
revision: number;
|
|
62
62
|
receiptHash: string;
|
|
63
|
+
approvalProof?: {
|
|
64
|
+
revision: number;
|
|
65
|
+
contractHash: string;
|
|
66
|
+
recordHash: string;
|
|
67
|
+
};
|
|
63
68
|
operationId: string;
|
|
64
69
|
lease?: OperationLease;
|
|
65
70
|
}
|
|
@@ -79,7 +84,7 @@ export interface OperationLeaseStatus {
|
|
|
79
84
|
|
|
80
85
|
const SCHEMA = `
|
|
81
86
|
CREATE TABLE IF NOT EXISTS schema_meta (
|
|
82
|
-
version INTEGER PRIMARY KEY CHECK (version =
|
|
87
|
+
version INTEGER PRIMARY KEY CHECK (version = 4)
|
|
83
88
|
) STRICT;
|
|
84
89
|
|
|
85
90
|
CREATE TABLE IF NOT EXISTS runs (
|
|
@@ -103,6 +108,9 @@ const SCHEMA = `
|
|
|
103
108
|
gate TEXT NOT NULL,
|
|
104
109
|
revision INTEGER NOT NULL,
|
|
105
110
|
receipt_hash TEXT NOT NULL,
|
|
111
|
+
approval_revision INTEGER,
|
|
112
|
+
contract_hash TEXT,
|
|
113
|
+
record_hash TEXT,
|
|
106
114
|
operation_id TEXT NOT NULL,
|
|
107
115
|
PRIMARY KEY (run_id, gate, revision),
|
|
108
116
|
UNIQUE (run_id, operation_id)
|
|
@@ -433,7 +441,7 @@ export class RunStore {
|
|
|
433
441
|
}
|
|
434
442
|
const key = lookupKey(input);
|
|
435
443
|
return this.#transaction(() => {
|
|
436
|
-
|
|
444
|
+
let existing = this.#database
|
|
437
445
|
.prepare(
|
|
438
446
|
`SELECT run_id, projection_json FROM runs
|
|
439
447
|
WHERE root_hash = ? AND stage = ? AND lookup_key = ?`,
|
|
@@ -441,6 +449,18 @@ export class RunStore {
|
|
|
441
449
|
.get(this.stateRoot.consumerRootHash, input.stage, key) as
|
|
442
450
|
| RunRow
|
|
443
451
|
| undefined;
|
|
452
|
+
if (existing) {
|
|
453
|
+
const projection = parseProjection(existing.projection_json);
|
|
454
|
+
if (
|
|
455
|
+
input.stage === "abel-design" &&
|
|
456
|
+
["completed", "discarded", "rejected"].includes(projection.state)
|
|
457
|
+
) {
|
|
458
|
+
this.#database
|
|
459
|
+
.prepare("UPDATE runs SET lookup_key = ? WHERE run_id = ?")
|
|
460
|
+
.run(`terminal:${existing.run_id}`, existing.run_id);
|
|
461
|
+
existing = undefined;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
444
464
|
if (existing) {
|
|
445
465
|
const replay = this.#operationOutcome(
|
|
446
466
|
existing.run_id,
|
|
@@ -561,7 +581,12 @@ export class RunStore {
|
|
|
561
581
|
(input.gate !== "gate-a" && input.gate !== "gate-b") ||
|
|
562
582
|
!Number.isSafeInteger(input.revision) ||
|
|
563
583
|
input.revision < 1 ||
|
|
564
|
-
!SHA256.test(input.receiptHash)
|
|
584
|
+
!SHA256.test(input.receiptHash) ||
|
|
585
|
+
(input.approvalProof !== undefined &&
|
|
586
|
+
(!Number.isSafeInteger(input.approvalProof.revision) ||
|
|
587
|
+
input.approvalProof.revision < 1 ||
|
|
588
|
+
!SHA256.test(input.approvalProof.contractHash) ||
|
|
589
|
+
!SHA256.test(input.approvalProof.recordHash)))
|
|
565
590
|
) {
|
|
566
591
|
throw new Error("invalid-delivery-binding");
|
|
567
592
|
}
|
|
@@ -576,14 +601,27 @@ export class RunStore {
|
|
|
576
601
|
if (replay) return replay;
|
|
577
602
|
const existing = this.#database
|
|
578
603
|
.prepare(
|
|
579
|
-
`SELECT receipt_hash
|
|
604
|
+
`SELECT receipt_hash, approval_revision, contract_hash, record_hash
|
|
605
|
+
FROM delivery_bindings
|
|
580
606
|
WHERE run_id = ? AND gate = ? AND revision = ?`,
|
|
581
607
|
)
|
|
582
608
|
.get(input.runId, input.gate, input.revision) as
|
|
583
|
-
| {
|
|
609
|
+
| {
|
|
610
|
+
receipt_hash: string;
|
|
611
|
+
approval_revision: number | null;
|
|
612
|
+
contract_hash: string | null;
|
|
613
|
+
record_hash: string | null;
|
|
614
|
+
}
|
|
584
615
|
| undefined;
|
|
585
616
|
if (existing) {
|
|
586
|
-
if (
|
|
617
|
+
if (
|
|
618
|
+
existing.receipt_hash !== input.receiptHash ||
|
|
619
|
+
existing.approval_revision !==
|
|
620
|
+
(input.approvalProof?.revision ?? null) ||
|
|
621
|
+
existing.contract_hash !==
|
|
622
|
+
(input.approvalProof?.contractHash ?? null) ||
|
|
623
|
+
existing.record_hash !== (input.approvalProof?.recordHash ?? null)
|
|
624
|
+
) {
|
|
587
625
|
throw new Error("delivery-revision-conflict");
|
|
588
626
|
}
|
|
589
627
|
const projection = this.status(input.runId);
|
|
@@ -599,20 +637,27 @@ export class RunStore {
|
|
|
599
637
|
this.#database
|
|
600
638
|
.prepare(
|
|
601
639
|
`INSERT INTO delivery_bindings(
|
|
602
|
-
run_id, gate, revision, receipt_hash,
|
|
603
|
-
|
|
640
|
+
run_id, gate, revision, receipt_hash, approval_revision,
|
|
641
|
+
contract_hash, record_hash, operation_id
|
|
642
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
604
643
|
)
|
|
605
644
|
.run(
|
|
606
645
|
input.runId,
|
|
607
646
|
input.gate,
|
|
608
647
|
input.revision,
|
|
609
648
|
input.receiptHash,
|
|
649
|
+
input.approvalProof?.revision ?? null,
|
|
650
|
+
input.approvalProof?.contractHash ?? null,
|
|
651
|
+
input.approvalProof?.recordHash ?? null,
|
|
610
652
|
input.operationId,
|
|
611
653
|
);
|
|
612
654
|
const projection = this.#appendEvent(input.runId, "delivery-bound", {
|
|
613
655
|
gate: input.gate,
|
|
614
656
|
revision: input.revision,
|
|
615
657
|
receiptHash: input.receiptHash,
|
|
658
|
+
...(input.approvalProof
|
|
659
|
+
? { approvalProof: structuredClone(input.approvalProof) }
|
|
660
|
+
: {}),
|
|
616
661
|
});
|
|
617
662
|
this.#recordOperation(
|
|
618
663
|
input.runId,
|
|
@@ -643,6 +688,33 @@ export class RunStore {
|
|
|
643
688
|
});
|
|
644
689
|
}
|
|
645
690
|
|
|
691
|
+
bindDeliveriesAtomically(
|
|
692
|
+
inputs: readonly BindDeliveryInput[],
|
|
693
|
+
write: (database: DatabaseSync) => void,
|
|
694
|
+
): RunProjection {
|
|
695
|
+
if (inputs.length === 0) throw new Error("delivery-binding-empty");
|
|
696
|
+
for (const input of inputs) this.#requireDeliveryBinding(input);
|
|
697
|
+
const runId = inputs[0]?.runId;
|
|
698
|
+
if (
|
|
699
|
+
!runId ||
|
|
700
|
+
inputs.some(
|
|
701
|
+
(input) =>
|
|
702
|
+
input.runId !== runId ||
|
|
703
|
+
(input.lease?.token ?? null) !== (inputs[0]?.lease?.token ?? null),
|
|
704
|
+
) ||
|
|
705
|
+
typeof write !== "function"
|
|
706
|
+
) {
|
|
707
|
+
throw new Error("delivery-binding-write-invalid");
|
|
708
|
+
}
|
|
709
|
+
return this.#transaction(() => {
|
|
710
|
+
let projection: RunProjection | undefined;
|
|
711
|
+
for (const input of inputs) projection = this.#bindDelivery(input);
|
|
712
|
+
write(this.#database);
|
|
713
|
+
if (!projection) throw new Error("delivery-binding-empty");
|
|
714
|
+
return projection;
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
|
|
646
718
|
acquireLease(input: {
|
|
647
719
|
runId: string;
|
|
648
720
|
operationId: string;
|
package/src/subagent-activity.ts
CHANGED
|
@@ -1320,7 +1320,7 @@ export function renderActivityCall(
|
|
|
1320
1320
|
context?: ActivityRenderContext,
|
|
1321
1321
|
): Component {
|
|
1322
1322
|
const value = asRecord(args);
|
|
1323
|
-
if (typeof value?.command === "string"
|
|
1323
|
+
if (typeof value?.command === "string") {
|
|
1324
1324
|
if (context?.executionStarted || context?.isError) return new Text("");
|
|
1325
1325
|
const projected = projectWorkflowActivity(value, { state: "queued" }, 0);
|
|
1326
1326
|
const display: ActivitySnapshot = {
|
|
@@ -166,17 +166,39 @@ function localExecutable(
|
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
function pathApi(
|
|
170
|
+
platform: NodeJS.Platform,
|
|
171
|
+
...values: readonly string[]
|
|
172
|
+
): typeof path.posix {
|
|
173
|
+
if (platform !== "win32") return path.posix;
|
|
174
|
+
if (
|
|
175
|
+
process.platform === "win32" ||
|
|
176
|
+
values.some(
|
|
177
|
+
(value) => /^[a-z]:[\\/]/iu.test(value) || value.startsWith("\\\\"),
|
|
178
|
+
)
|
|
179
|
+
) {
|
|
180
|
+
return path.win32;
|
|
181
|
+
}
|
|
182
|
+
// Tests may model Windows executable semantics over a POSIX fixture tree.
|
|
183
|
+
return path.posix;
|
|
184
|
+
}
|
|
185
|
+
|
|
169
186
|
function pathWithin(
|
|
170
187
|
root: string,
|
|
171
188
|
candidate: string,
|
|
172
189
|
platform: NodeJS.Platform,
|
|
173
190
|
): boolean {
|
|
174
|
-
const
|
|
175
|
-
const
|
|
176
|
-
|
|
191
|
+
const platformPath = pathApi(platform, root, candidate);
|
|
192
|
+
const normalizedRoot = platformPath.resolve(root);
|
|
193
|
+
const relative = platformPath.relative(
|
|
194
|
+
normalizedRoot,
|
|
195
|
+
platformPath.resolve(candidate),
|
|
196
|
+
);
|
|
177
197
|
return (
|
|
178
|
-
|
|
179
|
-
|
|
198
|
+
relative === "" ||
|
|
199
|
+
(relative !== ".." &&
|
|
200
|
+
!relative.startsWith(`..${platformPath.sep}`) &&
|
|
201
|
+
!platformPath.isAbsolute(relative))
|
|
180
202
|
);
|
|
181
203
|
}
|
|
182
204
|
|
|
@@ -261,13 +283,24 @@ function within(root: string, candidate: string): boolean {
|
|
|
261
283
|
return candidate === root || candidate.startsWith(`${root}${path.sep}`);
|
|
262
284
|
}
|
|
263
285
|
|
|
264
|
-
function commonDirectory(
|
|
286
|
+
export function commonDirectory(
|
|
265
287
|
left: string,
|
|
266
288
|
right: string,
|
|
267
289
|
platform: NodeJS.Platform = process.platform,
|
|
268
290
|
): string {
|
|
269
|
-
const
|
|
270
|
-
const
|
|
291
|
+
const platformPath = pathApi(platform, left, right);
|
|
292
|
+
const leftResolved = platformPath.resolve(left);
|
|
293
|
+
const rightResolved = platformPath.resolve(right);
|
|
294
|
+
const leftRoot = platformPath.parse(leftResolved).root;
|
|
295
|
+
const rightRoot = platformPath.parse(rightResolved).root;
|
|
296
|
+
const comparable = (value: string) =>
|
|
297
|
+
platform === "win32" ? value.toLowerCase() : value;
|
|
298
|
+
if (comparable(leftRoot) !== comparable(rightRoot)) return leftRoot;
|
|
299
|
+
|
|
300
|
+
const parts = (value: string, root: string) =>
|
|
301
|
+
platformPath.relative(root, value).split(platformPath.sep).filter(Boolean);
|
|
302
|
+
const leftParts = parts(leftResolved, leftRoot);
|
|
303
|
+
const rightParts = parts(rightResolved, rightRoot);
|
|
271
304
|
const common: string[] = [];
|
|
272
305
|
for (
|
|
273
306
|
let index = 0;
|
|
@@ -276,15 +309,10 @@ function commonDirectory(
|
|
|
276
309
|
) {
|
|
277
310
|
const leftPart = leftParts[index] as string;
|
|
278
311
|
const rightPart = rightParts[index] as string;
|
|
279
|
-
if (
|
|
280
|
-
(platform === "win32" ? leftPart.toLowerCase() : leftPart) !==
|
|
281
|
-
(platform === "win32" ? rightPart.toLowerCase() : rightPart)
|
|
282
|
-
) {
|
|
283
|
-
break;
|
|
284
|
-
}
|
|
312
|
+
if (comparable(leftPart) !== comparable(rightPart)) break;
|
|
285
313
|
common.push(leftPart);
|
|
286
314
|
}
|
|
287
|
-
return
|
|
315
|
+
return platformPath.join(leftRoot, ...common);
|
|
288
316
|
}
|
|
289
317
|
|
|
290
318
|
function unsafeMountSource(source: string): boolean {
|