@abelxiaoxing/cadence 1.2.1 → 1.2.3
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 +7 -1
- package/agents/diagnosis-worker.md +6 -2
- package/agents/implementation-worker.md +9 -2
- package/config/routes.example.json +0 -1
- package/package.json +1 -1
- package/prompts/abel-design.md +15 -4
- package/skills/abel-workflow/SKILL.md +5 -3
- package/src/apply-transaction.ts +1 -5
- package/src/child-session.ts +37 -46
- package/src/contracts.ts +278 -32
- package/src/control-contracts.ts +62 -0
- package/src/delivery-compiler.ts +239 -124
- package/src/design-control.ts +72 -27
- package/src/design-journal.ts +2 -2
- package/src/file-snapshot.ts +1 -1
- package/src/index.ts +295 -58
- package/src/packet-runtime.ts +79 -21
- package/src/parent-provider.ts +63 -30
- package/src/route-policy.ts +58 -14
- package/src/run-store.ts +115 -23
- package/src/subagent-activity.ts +82 -6
- package/src/submit-tool.ts +431 -39
- package/src/task-ledger.ts +1 -2
- package/src/worker-broker.ts +72 -13
- package/src/workflow-engine.ts +782 -54
- package/src/workspace-store.ts +2 -10
package/src/delivery-compiler.ts
CHANGED
|
@@ -19,9 +19,7 @@ import {
|
|
|
19
19
|
} from "./implement-graph.ts";
|
|
20
20
|
import { validateVerificationAdapterCapability } from "./verification-capability.ts";
|
|
21
21
|
|
|
22
|
-
export const IMPLEMENT_PLAN_SCHEMA_VERSION = 3 as const;
|
|
23
22
|
export const IMPLEMENT_PLAN_PATH = "implement-plan.json" as const;
|
|
24
|
-
export const DELIVERY_RECEIPT_VERSION = 4 as const;
|
|
25
23
|
export const GATE_A_RECEIPT_PATH = "gate-a.yaml" as const;
|
|
26
24
|
export const READY_RECEIPT_PATH = "ready.yaml" as const;
|
|
27
25
|
|
|
@@ -42,7 +40,6 @@ export interface GateApprovalProof {
|
|
|
42
40
|
}
|
|
43
41
|
|
|
44
42
|
export interface GateAReceipt {
|
|
45
|
-
receiptVersion: typeof DELIVERY_RECEIPT_VERSION;
|
|
46
43
|
change: string;
|
|
47
44
|
schema: string;
|
|
48
45
|
approval: GateApprovalProof;
|
|
@@ -58,7 +55,6 @@ export interface DeliveryTraceability {
|
|
|
58
55
|
}
|
|
59
56
|
|
|
60
57
|
export interface ReadyReceipt {
|
|
61
|
-
receiptVersion: typeof DELIVERY_RECEIPT_VERSION;
|
|
62
58
|
change: string;
|
|
63
59
|
schema: string;
|
|
64
60
|
deliveryRevision: number;
|
|
@@ -98,6 +94,79 @@ export class DeliveryValidationError extends Error {
|
|
|
98
94
|
}
|
|
99
95
|
}
|
|
100
96
|
|
|
97
|
+
export interface DesignPlanDiagnostic {
|
|
98
|
+
code: string;
|
|
99
|
+
taskId?: string;
|
|
100
|
+
phase?: string;
|
|
101
|
+
field?: string;
|
|
102
|
+
category?: string;
|
|
103
|
+
owner?: string;
|
|
104
|
+
verificationId?: string;
|
|
105
|
+
outputId?: string;
|
|
106
|
+
dependencyTaskId?: string;
|
|
107
|
+
producerTaskId?: string;
|
|
108
|
+
producerPhase?: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export class DesignPlanValidationError extends Error {
|
|
112
|
+
readonly diagnostics: readonly DesignPlanDiagnostic[];
|
|
113
|
+
|
|
114
|
+
constructor(message: string, diagnostics: readonly DesignPlanDiagnostic[]) {
|
|
115
|
+
const normalized = [
|
|
116
|
+
...new Map(
|
|
117
|
+
diagnostics
|
|
118
|
+
.map((diagnostic) => structuredClone(diagnostic))
|
|
119
|
+
.sort((left, right) =>
|
|
120
|
+
canonicalJson(left).localeCompare(canonicalJson(right)),
|
|
121
|
+
)
|
|
122
|
+
.map((diagnostic) => [canonicalJson(diagnostic), diagnostic]),
|
|
123
|
+
).values(),
|
|
124
|
+
];
|
|
125
|
+
super(message);
|
|
126
|
+
this.name = "DesignPlanValidationError";
|
|
127
|
+
this.diagnostics = Object.freeze(normalized);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const SAFE_PLAN_ERROR_CODE = /^[a-z0-9](?:[a-z0-9._-]{0,126}[a-z0-9])?$/u;
|
|
132
|
+
|
|
133
|
+
function planErrorCode(error: unknown): string {
|
|
134
|
+
const message = error instanceof Error ? error.message : "";
|
|
135
|
+
return SAFE_PLAN_ERROR_CODE.test(message) ? message : "delivery-plan-invalid";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function planDiagnosticField(code: string): string | undefined {
|
|
139
|
+
const fields: Record<string, string> = {
|
|
140
|
+
"delivery-plan-invalid-task": "task",
|
|
141
|
+
"delivery-plan-invalid-task-phases": "phases",
|
|
142
|
+
"delivery-plan-invalid-phase": "phases",
|
|
143
|
+
"delivery-plan-empty-phase-boundary": "phases",
|
|
144
|
+
"delivery-plan-write-delete-overlap": "phases",
|
|
145
|
+
"delivery-verification-invalid": "verification",
|
|
146
|
+
"delivery-task-verification-invalid": "affectedVerification",
|
|
147
|
+
"delivery-agents-checkpoint-mismatch": "verification.agentsCheckpoint",
|
|
148
|
+
};
|
|
149
|
+
return fields[code];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function asPlanValidationError(
|
|
153
|
+
error: unknown,
|
|
154
|
+
context: Omit<DesignPlanDiagnostic, "code"> = {},
|
|
155
|
+
): DesignPlanValidationError {
|
|
156
|
+
if (error instanceof DesignPlanValidationError) {
|
|
157
|
+
const diagnostics = error.diagnostics.map((diagnostic) => ({
|
|
158
|
+
...context,
|
|
159
|
+
...diagnostic,
|
|
160
|
+
}));
|
|
161
|
+
return new DesignPlanValidationError(error.message, diagnostics);
|
|
162
|
+
}
|
|
163
|
+
const code = planErrorCode(error);
|
|
164
|
+
const field = planDiagnosticField(code);
|
|
165
|
+
return new DesignPlanValidationError(code, [
|
|
166
|
+
{ code, ...context, ...(field ? { field } : {}) },
|
|
167
|
+
]);
|
|
168
|
+
}
|
|
169
|
+
|
|
101
170
|
export function assessDeliveryTraceability(input: {
|
|
102
171
|
tasksMarkdown: string;
|
|
103
172
|
specs: readonly { path: string; text: string }[];
|
|
@@ -197,7 +266,7 @@ export interface PlanVerification {
|
|
|
197
266
|
target: "task-red-contracts";
|
|
198
267
|
affected: "task-affected-contracts";
|
|
199
268
|
fullSuite: StructuredVerificationContract;
|
|
200
|
-
failureIdentity: "normalized
|
|
269
|
+
failureIdentity: "normalized";
|
|
201
270
|
};
|
|
202
271
|
change: {
|
|
203
272
|
affected: "task-affected-contracts";
|
|
@@ -235,7 +304,6 @@ export interface PlanTracking {
|
|
|
235
304
|
}
|
|
236
305
|
|
|
237
306
|
export interface PlanDraft {
|
|
238
|
-
schemaVersion: typeof IMPLEMENT_PLAN_SCHEMA_VERSION;
|
|
239
307
|
changeId: string;
|
|
240
308
|
tasks: PlanTaskDraft[];
|
|
241
309
|
outputs: ImplementGraphOutput[];
|
|
@@ -243,9 +311,7 @@ export interface PlanDraft {
|
|
|
243
311
|
tracking: PlanTracking;
|
|
244
312
|
}
|
|
245
313
|
|
|
246
|
-
export
|
|
247
|
-
schemaVersion: typeof IMPLEMENT_PLAN_SCHEMA_VERSION;
|
|
248
|
-
}
|
|
314
|
+
export type ImplementPlan = PlanDraft;
|
|
249
315
|
|
|
250
316
|
export interface CompiledDelivery {
|
|
251
317
|
plan: ImplementPlan;
|
|
@@ -256,7 +322,6 @@ export interface CompiledDelivery {
|
|
|
256
322
|
receipt: {
|
|
257
323
|
plan: {
|
|
258
324
|
path: typeof IMPLEMENT_PLAN_PATH;
|
|
259
|
-
schemaVersion: typeof IMPLEMENT_PLAN_SCHEMA_VERSION;
|
|
260
325
|
rawSha256: string;
|
|
261
326
|
canonicalHash: string;
|
|
262
327
|
};
|
|
@@ -360,7 +425,6 @@ export function compileGateAReceipt(input: {
|
|
|
360
425
|
}): { receipt: GateAReceipt; bytes: Uint8Array; rawSha256: string } {
|
|
361
426
|
requireReceiptIdentity(input.change, input.schema);
|
|
362
427
|
const receipt: GateAReceipt = {
|
|
363
|
-
receiptVersion: DELIVERY_RECEIPT_VERSION,
|
|
364
428
|
change: input.change,
|
|
365
429
|
schema: input.schema,
|
|
366
430
|
approval: normalizeGateProof(input.approval),
|
|
@@ -381,14 +445,7 @@ export function parseGateAReceipt(bytes: Uint8Array): GateAReceipt {
|
|
|
381
445
|
);
|
|
382
446
|
if (
|
|
383
447
|
!isRecord(receipt) ||
|
|
384
|
-
!exactKeys(receipt, [
|
|
385
|
-
"receiptVersion",
|
|
386
|
-
"change",
|
|
387
|
-
"schema",
|
|
388
|
-
"approval",
|
|
389
|
-
"artifacts",
|
|
390
|
-
]) ||
|
|
391
|
-
receipt.receiptVersion !== DELIVERY_RECEIPT_VERSION ||
|
|
448
|
+
!exactKeys(receipt, ["change", "schema", "approval", "artifacts"]) ||
|
|
392
449
|
!isRecord(receipt.approval) ||
|
|
393
450
|
!exactKeys(receipt.approval, ["revision", "contractHash", "recordHash"])
|
|
394
451
|
) {
|
|
@@ -454,7 +511,6 @@ export function compileReadyReceipt(input: {
|
|
|
454
511
|
throw new Error("delivery-receipt-invalid");
|
|
455
512
|
}
|
|
456
513
|
const receipt: ReadyReceipt = {
|
|
457
|
-
receiptVersion: DELIVERY_RECEIPT_VERSION,
|
|
458
514
|
change: input.change,
|
|
459
515
|
schema: input.schema,
|
|
460
516
|
deliveryRevision: input.deliveryRevision,
|
|
@@ -489,7 +545,6 @@ export function parseReadyReceipt(bytes: Uint8Array): ReadyReceipt {
|
|
|
489
545
|
if (
|
|
490
546
|
!isRecord(receipt) ||
|
|
491
547
|
!exactKeys(receipt, [
|
|
492
|
-
"receiptVersion",
|
|
493
548
|
"change",
|
|
494
549
|
"schema",
|
|
495
550
|
"deliveryRevision",
|
|
@@ -500,7 +555,6 @@ export function parseReadyReceipt(bytes: Uint8Array): ReadyReceipt {
|
|
|
500
555
|
"traceability",
|
|
501
556
|
"openspec",
|
|
502
557
|
]) ||
|
|
503
|
-
receipt.receiptVersion !== DELIVERY_RECEIPT_VERSION ||
|
|
504
558
|
!Number.isSafeInteger(receipt.deliveryRevision) ||
|
|
505
559
|
receipt.deliveryRevision < 1 ||
|
|
506
560
|
!isRecord(receipt.approvals) ||
|
|
@@ -516,14 +570,8 @@ export function parseReadyReceipt(bytes: Uint8Array): ReadyReceipt {
|
|
|
516
570
|
"recordHash",
|
|
517
571
|
]) ||
|
|
518
572
|
!isRecord(receipt.plan) ||
|
|
519
|
-
!exactKeys(receipt.plan, [
|
|
520
|
-
"path",
|
|
521
|
-
"schemaVersion",
|
|
522
|
-
"rawSha256",
|
|
523
|
-
"canonicalHash",
|
|
524
|
-
]) ||
|
|
573
|
+
!exactKeys(receipt.plan, ["path", "rawSha256", "canonicalHash"]) ||
|
|
525
574
|
receipt.plan.path !== IMPLEMENT_PLAN_PATH ||
|
|
526
|
-
receipt.plan.schemaVersion !== IMPLEMENT_PLAN_SCHEMA_VERSION ||
|
|
527
575
|
!SHA256.test(receipt.plan.rawSha256) ||
|
|
528
576
|
!SHA256.test(receipt.plan.canonicalHash) ||
|
|
529
577
|
!isRecord(receipt.verificationClosure) ||
|
|
@@ -603,6 +651,9 @@ function normalizePhase(value: unknown): PlanPhaseDraft {
|
|
|
603
651
|
}
|
|
604
652
|
const write = sortStrings(value.write as string[]);
|
|
605
653
|
const deletions = sortStrings(value.delete as string[]);
|
|
654
|
+
if (write.length + deletions.length === 0) {
|
|
655
|
+
throw new Error("delivery-plan-empty-phase-boundary");
|
|
656
|
+
}
|
|
606
657
|
if (write.some((candidate) => deletions.includes(candidate))) {
|
|
607
658
|
throw new Error("delivery-plan-write-delete-overlap");
|
|
608
659
|
}
|
|
@@ -625,60 +676,97 @@ function normalizePhase(value: unknown): PlanPhaseDraft {
|
|
|
625
676
|
}
|
|
626
677
|
|
|
627
678
|
function normalizeTask(value: unknown): PlanTaskDraft {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
value.
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
679
|
+
const taskId =
|
|
680
|
+
isRecord(value) && typeof value.taskId === "string"
|
|
681
|
+
? value.taskId
|
|
682
|
+
: undefined;
|
|
683
|
+
const context = taskId ? { taskId } : {};
|
|
684
|
+
try {
|
|
685
|
+
if (
|
|
686
|
+
!isRecord(value) ||
|
|
687
|
+
!isRecord(value.phases) ||
|
|
688
|
+
!isRecord(value.affectedVerification) ||
|
|
689
|
+
!isRecord(value.repairVerification)
|
|
690
|
+
) {
|
|
691
|
+
throw new Error("delivery-plan-invalid-task");
|
|
692
|
+
}
|
|
693
|
+
const phases = value.phases;
|
|
694
|
+
if (!Object.hasOwn(phases, "red") || !Object.hasOwn(phases, "green")) {
|
|
695
|
+
throw new Error("delivery-plan-invalid-task-phases");
|
|
696
|
+
}
|
|
697
|
+
const cloned = structuredClone(value) as unknown as PlanTaskDraft;
|
|
698
|
+
let affectedVerification: StructuredVerificationContract;
|
|
699
|
+
try {
|
|
700
|
+
affectedVerification = normalizeVerification(value.affectedVerification);
|
|
701
|
+
} catch (error) {
|
|
702
|
+
throw asPlanValidationError(error, {
|
|
703
|
+
...context,
|
|
704
|
+
field: "affectedVerification",
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
let repairVerification: StructuredVerificationContract;
|
|
708
|
+
try {
|
|
709
|
+
repairVerification = normalizeVerification(value.repairVerification);
|
|
710
|
+
} catch (error) {
|
|
711
|
+
throw asPlanValidationError(error, {
|
|
712
|
+
...context,
|
|
713
|
+
field: "repairVerification",
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
if (
|
|
717
|
+
affectedVerification.classification !== "expected-green" ||
|
|
718
|
+
repairVerification.classification !== "expected-green"
|
|
719
|
+
) {
|
|
720
|
+
throw new Error("delivery-task-verification-invalid");
|
|
721
|
+
}
|
|
722
|
+
const normalizeTaskPhase = (
|
|
723
|
+
phase: "red" | "green" | "refactor",
|
|
724
|
+
candidate: unknown,
|
|
725
|
+
) => {
|
|
726
|
+
try {
|
|
727
|
+
return normalizePhase(candidate);
|
|
728
|
+
} catch (error) {
|
|
729
|
+
throw asPlanValidationError(error, {
|
|
730
|
+
...context,
|
|
731
|
+
phase,
|
|
732
|
+
field: `phases.${phase}`,
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
return {
|
|
737
|
+
...cloned,
|
|
738
|
+
dependsOn: sortStrings(cloned.dependsOn),
|
|
739
|
+
roots: sortStrings(cloned.roots),
|
|
740
|
+
phases: {
|
|
741
|
+
red: normalizeTaskPhase("red", phases.red),
|
|
742
|
+
green: normalizeTaskPhase("green", phases.green),
|
|
743
|
+
...(phases.refactor !== undefined
|
|
744
|
+
? { refactor: normalizeTaskPhase("refactor", phases.refactor) }
|
|
745
|
+
: {}),
|
|
746
|
+
},
|
|
747
|
+
scheduling: {
|
|
748
|
+
conflicts: sortStrings(cloned.scheduling.conflicts),
|
|
749
|
+
resources: sortStrings(cloned.scheduling.resources),
|
|
750
|
+
},
|
|
751
|
+
approvedDependencies: sortStrings(cloned.approvedDependencies),
|
|
752
|
+
affectedVerification,
|
|
753
|
+
repairVerification,
|
|
754
|
+
impactClosure: {
|
|
755
|
+
changedSurfaces: sortStrings(
|
|
756
|
+
cloned.impactClosure.changedSurfaces,
|
|
757
|
+
) as PlanTaskDraft["impactClosure"]["changedSurfaces"],
|
|
758
|
+
searchEvidence: sortStrings(cloned.impactClosure.searchEvidence),
|
|
759
|
+
relatedTests: [...cloned.impactClosure.relatedTests].sort(
|
|
760
|
+
(left, right) =>
|
|
761
|
+
left.path.localeCompare(right.path) ||
|
|
762
|
+
left.disposition.localeCompare(right.disposition),
|
|
763
|
+
),
|
|
764
|
+
affectedSuite: sortStrings(cloned.impactClosure.affectedSuite),
|
|
765
|
+
},
|
|
766
|
+
};
|
|
767
|
+
} catch (error) {
|
|
768
|
+
throw asPlanValidationError(error, context);
|
|
650
769
|
}
|
|
651
|
-
return {
|
|
652
|
-
...cloned,
|
|
653
|
-
dependsOn: sortStrings(cloned.dependsOn),
|
|
654
|
-
roots: sortStrings(cloned.roots),
|
|
655
|
-
phases: {
|
|
656
|
-
red: normalizePhase(phases.red),
|
|
657
|
-
green: normalizePhase(phases.green),
|
|
658
|
-
...(phases.refactor !== undefined
|
|
659
|
-
? { refactor: normalizePhase(phases.refactor) }
|
|
660
|
-
: {}),
|
|
661
|
-
},
|
|
662
|
-
scheduling: {
|
|
663
|
-
conflicts: sortStrings(cloned.scheduling.conflicts),
|
|
664
|
-
resources: sortStrings(cloned.scheduling.resources),
|
|
665
|
-
},
|
|
666
|
-
approvedDependencies: sortStrings(cloned.approvedDependencies),
|
|
667
|
-
affectedVerification,
|
|
668
|
-
repairVerification,
|
|
669
|
-
impactClosure: {
|
|
670
|
-
changedSurfaces: sortStrings(
|
|
671
|
-
cloned.impactClosure.changedSurfaces,
|
|
672
|
-
) as PlanTaskDraft["impactClosure"]["changedSurfaces"],
|
|
673
|
-
searchEvidence: sortStrings(cloned.impactClosure.searchEvidence),
|
|
674
|
-
relatedTests: [...cloned.impactClosure.relatedTests].sort(
|
|
675
|
-
(left, right) =>
|
|
676
|
-
left.path.localeCompare(right.path) ||
|
|
677
|
-
left.disposition.localeCompare(right.disposition),
|
|
678
|
-
),
|
|
679
|
-
affectedSuite: sortStrings(cloned.impactClosure.affectedSuite),
|
|
680
|
-
},
|
|
681
|
-
};
|
|
682
770
|
}
|
|
683
771
|
|
|
684
772
|
function graphFromPlan(plan: ImplementPlan): ImplementGraphBoundary {
|
|
@@ -870,7 +958,7 @@ function normalizeVerificationPlan(value: unknown): PlanVerification {
|
|
|
870
958
|
]) ||
|
|
871
959
|
value.baseline.target !== "task-red-contracts" ||
|
|
872
960
|
value.baseline.affected !== "task-affected-contracts" ||
|
|
873
|
-
value.baseline.failureIdentity !== "normalized
|
|
961
|
+
value.baseline.failureIdentity !== "normalized" ||
|
|
874
962
|
!isRecord(value.change) ||
|
|
875
963
|
!exactKeys(value.change, ["affected", "fullSuite", "postApply"]) ||
|
|
876
964
|
value.change.affected !== "task-affected-contracts" ||
|
|
@@ -919,7 +1007,7 @@ function normalizeVerificationPlan(value: unknown): PlanVerification {
|
|
|
919
1007
|
target: "task-red-contracts",
|
|
920
1008
|
affected: "task-affected-contracts",
|
|
921
1009
|
fullSuite: normalizeExpectedGreenVerification(value.baseline.fullSuite),
|
|
922
|
-
failureIdentity: "normalized
|
|
1010
|
+
failureIdentity: "normalized",
|
|
923
1011
|
},
|
|
924
1012
|
change: {
|
|
925
1013
|
affected: "task-affected-contracts",
|
|
@@ -974,14 +1062,12 @@ function normalizeDraft(value: unknown): ImplementPlan {
|
|
|
974
1062
|
if (
|
|
975
1063
|
!isRecord(value) ||
|
|
976
1064
|
!exactKeys(value, [
|
|
977
|
-
"schemaVersion",
|
|
978
1065
|
"changeId",
|
|
979
1066
|
"tasks",
|
|
980
1067
|
"outputs",
|
|
981
1068
|
"verification",
|
|
982
1069
|
"tracking",
|
|
983
1070
|
]) ||
|
|
984
|
-
value.schemaVersion !== IMPLEMENT_PLAN_SCHEMA_VERSION ||
|
|
985
1071
|
typeof value.changeId !== "string" ||
|
|
986
1072
|
!CHANGE_NAME.test(value.changeId) ||
|
|
987
1073
|
!Array.isArray(value.tasks) ||
|
|
@@ -989,8 +1075,26 @@ function normalizeDraft(value: unknown): ImplementPlan {
|
|
|
989
1075
|
) {
|
|
990
1076
|
throw new Error("delivery-plan-invalid");
|
|
991
1077
|
}
|
|
1078
|
+
const draftGraph = validateImplementGraphBoundary({
|
|
1079
|
+
changeId: value.changeId,
|
|
1080
|
+
tasks: value.tasks.map((candidate) => {
|
|
1081
|
+
if (!isRecord(candidate)) return candidate;
|
|
1082
|
+
const {
|
|
1083
|
+
affectedVerification: _affectedVerification,
|
|
1084
|
+
repairVerification: _repairVerification,
|
|
1085
|
+
...boundary
|
|
1086
|
+
} = candidate;
|
|
1087
|
+
return boundary;
|
|
1088
|
+
}),
|
|
1089
|
+
outputs: value.outputs,
|
|
1090
|
+
});
|
|
1091
|
+
if (!draftGraph.ok) {
|
|
1092
|
+
throw new DesignPlanValidationError(
|
|
1093
|
+
`delivery-plan-invalid:${draftGraph.reason}`,
|
|
1094
|
+
[draftGraph.diagnostic],
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
992
1097
|
const plan: ImplementPlan = {
|
|
993
|
-
schemaVersion: IMPLEMENT_PLAN_SCHEMA_VERSION,
|
|
994
1098
|
changeId: value.changeId,
|
|
995
1099
|
tasks: value.tasks
|
|
996
1100
|
.map(normalizeTask)
|
|
@@ -1039,7 +1143,10 @@ function normalizeDraft(value: unknown): ImplementPlan {
|
|
|
1039
1143
|
}
|
|
1040
1144
|
const graphValidation = validateImplementGraphBoundary(graphFromPlan(plan));
|
|
1041
1145
|
if (!graphValidation.ok) {
|
|
1042
|
-
throw new
|
|
1146
|
+
throw new DesignPlanValidationError(
|
|
1147
|
+
`delivery-plan-invalid:${graphValidation.reason}`,
|
|
1148
|
+
[graphValidation.diagnostic],
|
|
1149
|
+
);
|
|
1043
1150
|
}
|
|
1044
1151
|
return plan;
|
|
1045
1152
|
}
|
|
@@ -1062,44 +1169,52 @@ export function compileImplementPlan(
|
|
|
1062
1169
|
draft: unknown,
|
|
1063
1170
|
options: { consumerRoot: string },
|
|
1064
1171
|
): CompiledDelivery {
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
if (diagnostics.length > 0) {
|
|
1077
|
-
throw new Error(
|
|
1078
|
-
`delivery-plan-not-executable:${canonicalJson(diagnostics)}`,
|
|
1172
|
+
try {
|
|
1173
|
+
const plan = normalizeDraft(draft);
|
|
1174
|
+
const graph = graphFromPlan(plan);
|
|
1175
|
+
const readiness = assessImplementGraphReadiness(
|
|
1176
|
+
options.consumerRoot,
|
|
1177
|
+
graph,
|
|
1178
|
+
{
|
|
1179
|
+
completedTasks: [],
|
|
1180
|
+
blockedTasks: [],
|
|
1181
|
+
appliedPhases: [],
|
|
1182
|
+
},
|
|
1079
1183
|
);
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1184
|
+
const diagnostics = [
|
|
1185
|
+
...readiness.closure.diagnostics,
|
|
1186
|
+
...deliveryVerificationDiagnostics(plan, options.consumerRoot),
|
|
1187
|
+
] as DesignPlanDiagnostic[];
|
|
1188
|
+
if (diagnostics.length > 0) {
|
|
1189
|
+
throw new DesignPlanValidationError(
|
|
1190
|
+
`delivery-plan-not-executable:${canonicalJson(diagnostics)}`,
|
|
1191
|
+
diagnostics,
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
const canonical = canonicalJson(plan);
|
|
1195
|
+
const bytes = new TextEncoder().encode(`${canonical}\n`);
|
|
1196
|
+
const rawSha256 = createHash("sha256").update(bytes).digest("hex");
|
|
1197
|
+
const planHash = hashCanonicalValue(plan);
|
|
1198
|
+
const closure = structuredClone(readiness.closure);
|
|
1199
|
+
return {
|
|
1200
|
+
plan,
|
|
1201
|
+
bytes,
|
|
1202
|
+
rawSha256,
|
|
1203
|
+
planHash,
|
|
1204
|
+
closure,
|
|
1205
|
+
receipt: {
|
|
1206
|
+
plan: {
|
|
1207
|
+
path: IMPLEMENT_PLAN_PATH,
|
|
1208
|
+
rawSha256,
|
|
1209
|
+
canonicalHash: planHash,
|
|
1210
|
+
},
|
|
1211
|
+
verificationClosure: structuredClone(closure),
|
|
1098
1212
|
},
|
|
1099
|
-
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
|
|
1213
|
+
tasksMarkdown: renderTasks(plan),
|
|
1214
|
+
};
|
|
1215
|
+
} catch (error) {
|
|
1216
|
+
throw asPlanValidationError(error);
|
|
1217
|
+
}
|
|
1103
1218
|
}
|
|
1104
1219
|
|
|
1105
1220
|
export function parseImplementPlan(bytes: Uint8Array): ImplementPlan {
|