@abelxiaoxing/cadence 1.6.0 → 1.7.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/README.md +18 -1
- package/config/plan-draft.example.json +3 -0
- package/config/plan-draft.multiple-tasks.example.json +30 -0
- package/package.json +1 -1
- package/prompts/abel-design.md +33 -3
- package/prompts/abel-implement.md +36 -4
- package/src/change-verification.ts +758 -65
- package/src/contracts.ts +8 -0
- package/src/delivery-compiler.ts +452 -9
- package/src/design-control.ts +2 -2
- package/src/design-diagnostics.ts +6 -4
- package/src/durable-contracts.ts +19 -3
- package/src/durable-workflow.ts +47 -38
- package/src/implement-continuation.ts +559 -0
- package/src/implement-graph.ts +114 -0
- package/src/implement-plan.ts +2 -0
- package/src/index.ts +151 -1
- package/src/operator-cli.mjs +12 -7
- package/src/package-verification.ts +17 -1
- package/src/package-workflow.ts +3 -0
- package/src/phase-execution.ts +7 -1
- package/src/plan-draft-summary.ts +385 -7
- package/src/plan-draft.ts +9 -0
- package/src/subagent-activity.ts +3 -1
- package/src/verification-capability.ts +17 -5
- package/src/verification-environment.ts +20 -2
- package/src/workflow-policy.ts +296 -5
- package/src/workflow-recovery-policy.ts +2 -0
- package/src/workflow-state-machine.ts +314 -80
- package/src/workflow-status.ts +251 -59
package/src/contracts.ts
CHANGED
|
@@ -148,6 +148,7 @@ export const APPROVAL_BOUNDARY_CODES = [
|
|
|
148
148
|
] as const;
|
|
149
149
|
export const VERIFICATION_ADAPTER_CODES = [
|
|
150
150
|
"input-missing",
|
|
151
|
+
"input-unsafe",
|
|
151
152
|
"local-executable-missing",
|
|
152
153
|
"runner-missing",
|
|
153
154
|
"script-command-mismatch",
|
|
@@ -298,9 +299,16 @@ export type VerificationObservation =
|
|
|
298
299
|
code: string;
|
|
299
300
|
verificationId: string;
|
|
300
301
|
diagnostic?: VerificationFailureSummary;
|
|
302
|
+
inputObservation?: VerificationInputObservation;
|
|
301
303
|
}
|
|
302
304
|
| { kind: "cancelled" };
|
|
303
305
|
|
|
306
|
+
/** Parent adapter observation, never a Worker assertion or an error-text guess. */
|
|
307
|
+
export interface VerificationInputObservation {
|
|
308
|
+
path: string;
|
|
309
|
+
kind: "absent" | "unsafe";
|
|
310
|
+
}
|
|
311
|
+
|
|
304
312
|
interface VerificationBase {
|
|
305
313
|
id: string;
|
|
306
314
|
classification: VerificationClassification;
|
package/src/delivery-compiler.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
2
4
|
import { compareCanonicalStrings } from "./canonical.ts";
|
|
3
5
|
import {
|
|
4
6
|
assertPlanWithinChangeContract,
|
|
@@ -14,6 +16,8 @@ import {
|
|
|
14
16
|
type StructuredVerificationContract,
|
|
15
17
|
validateImplementGraphBoundary,
|
|
16
18
|
validateVerificationContract,
|
|
19
|
+
verificationInputPaths,
|
|
20
|
+
verificationSteps,
|
|
17
21
|
} from "./contracts.ts";
|
|
18
22
|
import {
|
|
19
23
|
type DesignPlanDiagnostic,
|
|
@@ -33,6 +37,7 @@ import type {
|
|
|
33
37
|
PlanVerification,
|
|
34
38
|
} from "./implement-plan.ts";
|
|
35
39
|
import { expandPlanDraft, preparePlanDraft } from "./plan-draft.ts";
|
|
40
|
+
import { isSafeRegularFile, observeSafePath } from "./safe-path.ts";
|
|
36
41
|
import { expandSingleTaskDraft } from "./single-task-draft.ts";
|
|
37
42
|
import {
|
|
38
43
|
bindDraftVerificationInputs,
|
|
@@ -682,7 +687,13 @@ function normalizePhase(value: unknown, readOnly = false): PhaseBoundary {
|
|
|
682
687
|
};
|
|
683
688
|
}
|
|
684
689
|
|
|
685
|
-
function normalizeTask(
|
|
690
|
+
function normalizeTask(
|
|
691
|
+
value: unknown,
|
|
692
|
+
options: {
|
|
693
|
+
consumerRoot?: string;
|
|
694
|
+
preserveMissingBaseline?: boolean;
|
|
695
|
+
} = {},
|
|
696
|
+
): PlanTaskDraft {
|
|
686
697
|
const taskId =
|
|
687
698
|
isRecord(value) && typeof value.taskId === "string"
|
|
688
699
|
? value.taskId
|
|
@@ -720,7 +731,41 @@ function normalizeTask(value: unknown): PlanTaskDraft {
|
|
|
720
731
|
field: "repairVerification",
|
|
721
732
|
});
|
|
722
733
|
}
|
|
734
|
+
let baselineVerification: StructuredVerificationContract | undefined;
|
|
735
|
+
if (value.baselineVerification !== undefined) {
|
|
736
|
+
try {
|
|
737
|
+
baselineVerification = normalizeVerification(
|
|
738
|
+
value.baselineVerification,
|
|
739
|
+
);
|
|
740
|
+
} catch (error) {
|
|
741
|
+
throw asPlanValidationError(error, {
|
|
742
|
+
...context,
|
|
743
|
+
field: "baselineVerification",
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
} else if (!options.preserveMissingBaseline) {
|
|
747
|
+
const unavailable = verificationInputPaths(affectedVerification).filter(
|
|
748
|
+
(input) =>
|
|
749
|
+
!options.consumerRoot ||
|
|
750
|
+
!isSafeRegularFile(options.consumerRoot, input),
|
|
751
|
+
);
|
|
752
|
+
if (unavailable.length > 0) {
|
|
753
|
+
throw new DesignPlanValidationError(
|
|
754
|
+
"delivery-task-baseline-unavailable",
|
|
755
|
+
unavailable.map((input) => ({
|
|
756
|
+
code: "workspace-input-unavailable",
|
|
757
|
+
...context,
|
|
758
|
+
field: "baselineVerification",
|
|
759
|
+
verificationId: affectedVerification.id,
|
|
760
|
+
path: input,
|
|
761
|
+
})),
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
baselineVerification = structuredClone(affectedVerification);
|
|
765
|
+
}
|
|
723
766
|
if (
|
|
767
|
+
(baselineVerification &&
|
|
768
|
+
baselineVerification.classification !== "expected-green") ||
|
|
724
769
|
affectedVerification.classification !== "expected-green" ||
|
|
725
770
|
repairVerification.classification !== "expected-green"
|
|
726
771
|
) {
|
|
@@ -761,6 +806,7 @@ function normalizeTask(value: unknown): PlanTaskDraft {
|
|
|
761
806
|
resources: sortStrings(cloned.scheduling.resources),
|
|
762
807
|
},
|
|
763
808
|
approvedDependencies: sortStrings(cloned.approvedDependencies),
|
|
809
|
+
...(baselineVerification ? { baselineVerification } : {}),
|
|
764
810
|
affectedVerification,
|
|
765
811
|
repairVerification,
|
|
766
812
|
impactClosure: {
|
|
@@ -796,6 +842,7 @@ function graphFromPlan(plan: ImplementPlan): ImplementGraphBoundary {
|
|
|
796
842
|
changeId: plan.changeId,
|
|
797
843
|
tasks: plan.tasks.map((task) => {
|
|
798
844
|
const {
|
|
845
|
+
baselineVerification: _baselineVerification,
|
|
799
846
|
affectedVerification: _affectedVerification,
|
|
800
847
|
repairVerification: _repairVerification,
|
|
801
848
|
...boundary
|
|
@@ -815,6 +862,375 @@ function graphFromPlan(plan: ImplementPlan): ImplementGraphBoundary {
|
|
|
815
862
|
};
|
|
816
863
|
}
|
|
817
864
|
|
|
865
|
+
type PathOperation = {
|
|
866
|
+
taskId: string;
|
|
867
|
+
phase: "red" | "green" | "refactor";
|
|
868
|
+
kind: "write" | "delete";
|
|
869
|
+
};
|
|
870
|
+
|
|
871
|
+
const PLAN_PHASE_INDEX = { red: 0, green: 1, refactor: 2 } as const;
|
|
872
|
+
|
|
873
|
+
function taskAncestors(
|
|
874
|
+
taskId: string,
|
|
875
|
+
tasks: ReadonlyMap<string, PlanTaskDraft>,
|
|
876
|
+
): Set<string> {
|
|
877
|
+
const ancestors = new Set<string>();
|
|
878
|
+
const pending = [...(tasks.get(taskId)?.dependsOn ?? [])];
|
|
879
|
+
while (pending.length > 0) {
|
|
880
|
+
const dependency = pending.pop() as string;
|
|
881
|
+
if (ancestors.has(dependency)) continue;
|
|
882
|
+
ancestors.add(dependency);
|
|
883
|
+
pending.push(...(tasks.get(dependency)?.dependsOn ?? []));
|
|
884
|
+
}
|
|
885
|
+
return ancestors;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
function pathOperations(plan: ImplementPlan, input: string): PathOperation[] {
|
|
889
|
+
return plan.tasks.flatMap((task) =>
|
|
890
|
+
(["red", "green", "refactor"] as const).flatMap((phase) => {
|
|
891
|
+
const boundary = task.phases[phase];
|
|
892
|
+
if (!boundary) return [];
|
|
893
|
+
return [
|
|
894
|
+
...(boundary.write.includes(input)
|
|
895
|
+
? [{ taskId: task.taskId, phase, kind: "write" as const }]
|
|
896
|
+
: []),
|
|
897
|
+
...(boundary.delete.includes(input)
|
|
898
|
+
? [{ taskId: task.taskId, phase, kind: "delete" as const }]
|
|
899
|
+
: []),
|
|
900
|
+
];
|
|
901
|
+
}),
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
function operationBefore(
|
|
906
|
+
left: PathOperation,
|
|
907
|
+
right: PathOperation,
|
|
908
|
+
ancestors: ReadonlyMap<string, ReadonlySet<string>>,
|
|
909
|
+
): boolean {
|
|
910
|
+
return left.taskId === right.taskId
|
|
911
|
+
? PLAN_PHASE_INDEX[left.phase] < PLAN_PHASE_INDEX[right.phase]
|
|
912
|
+
: (ancestors.get(right.taskId)?.has(left.taskId) ?? false);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
function pathSurvives(
|
|
916
|
+
operations: readonly PathOperation[],
|
|
917
|
+
ancestors: ReadonlyMap<string, ReadonlySet<string>>,
|
|
918
|
+
): boolean {
|
|
919
|
+
return operations
|
|
920
|
+
.filter((operation) => operation.kind === "delete")
|
|
921
|
+
.every((deletion) =>
|
|
922
|
+
operations.some(
|
|
923
|
+
(operation) =>
|
|
924
|
+
operation.kind === "write" &&
|
|
925
|
+
operationBefore(deletion, operation, ancestors),
|
|
926
|
+
),
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* File-level acceptance entrypoints named directly by a verification contract.
|
|
932
|
+
* Vitest identifies its test files and a Node static check identifies its
|
|
933
|
+
* executable script. Package scripts do not expose selected files, and an
|
|
934
|
+
* unrelated helper or data output is not promoted merely because of its path.
|
|
935
|
+
*/
|
|
936
|
+
function verificationAcceptanceEntrypoints(
|
|
937
|
+
verification: StructuredVerificationContract,
|
|
938
|
+
): string[] {
|
|
939
|
+
return verificationSteps(verification).flatMap((step) => {
|
|
940
|
+
if (step.kind === "vitest") return step.testFiles;
|
|
941
|
+
if (step.kind === "static-check" && step.runner.kind === "node") {
|
|
942
|
+
return [step.runner.script];
|
|
943
|
+
}
|
|
944
|
+
return [];
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
function agentsTargetAvailable(
|
|
949
|
+
root: string,
|
|
950
|
+
operation: PlanAgentsCheckpointOperation,
|
|
951
|
+
): boolean {
|
|
952
|
+
if (operation.impact === "create-index") {
|
|
953
|
+
return ["absent", "file"].includes(
|
|
954
|
+
observeSafePath(root, operation.target).kind,
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
if (!isSafeRegularFile(root, operation.target)) return false;
|
|
958
|
+
if (operation.impact === "update-existing") return true;
|
|
959
|
+
try {
|
|
960
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(
|
|
961
|
+
readFileSync(path.join(root, operation.target)),
|
|
962
|
+
);
|
|
963
|
+
const start = text.indexOf(AGENTS_START);
|
|
964
|
+
const end = text.indexOf(AGENTS_END);
|
|
965
|
+
if (
|
|
966
|
+
start < 0 ||
|
|
967
|
+
end <= start ||
|
|
968
|
+
text.indexOf(AGENTS_START, start + AGENTS_START.length) >= 0 ||
|
|
969
|
+
text.indexOf(AGENTS_END, end + AGENTS_END.length) >= 0
|
|
970
|
+
) {
|
|
971
|
+
return false;
|
|
972
|
+
}
|
|
973
|
+
return (
|
|
974
|
+
`${text.slice(0, start)}${text.slice(end + AGENTS_END.length)}`.trim()
|
|
975
|
+
.length > 0
|
|
976
|
+
);
|
|
977
|
+
} catch {
|
|
978
|
+
return false;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
function inputTimingDiagnostics(
|
|
983
|
+
plan: ImplementPlan,
|
|
984
|
+
consumerRoot: string,
|
|
985
|
+
): DesignPlanDiagnostic[] {
|
|
986
|
+
const diagnostics: DesignPlanDiagnostic[] = [];
|
|
987
|
+
const tasks = new Map(plan.tasks.map((task) => [task.taskId, task]));
|
|
988
|
+
const ancestors = new Map(
|
|
989
|
+
plan.tasks.map((task) => [task.taskId, taskAncestors(task.taskId, tasks)]),
|
|
990
|
+
);
|
|
991
|
+
const outputByPath = new Map(
|
|
992
|
+
plan.outputs.map((output) => [output.path, output]),
|
|
993
|
+
);
|
|
994
|
+
const agentsOperations = new Map(
|
|
995
|
+
plan.verification.agentsCheckpoint.operations.map((operation) => [
|
|
996
|
+
operation.target,
|
|
997
|
+
operation,
|
|
998
|
+
]),
|
|
999
|
+
);
|
|
1000
|
+
const unavailable = (input: {
|
|
1001
|
+
field: string;
|
|
1002
|
+
verification: StructuredVerificationContract;
|
|
1003
|
+
path: string;
|
|
1004
|
+
task?: PlanTaskDraft;
|
|
1005
|
+
code: "workspace-input-unavailable" | "producer-output-unavailable";
|
|
1006
|
+
}) => {
|
|
1007
|
+
const output = outputByPath.get(input.path);
|
|
1008
|
+
diagnostics.push({
|
|
1009
|
+
code: input.code,
|
|
1010
|
+
...(input.task ? { taskId: input.task.taskId } : {}),
|
|
1011
|
+
field: input.field,
|
|
1012
|
+
verificationId: input.verification.id,
|
|
1013
|
+
path: input.path,
|
|
1014
|
+
...(output
|
|
1015
|
+
? {
|
|
1016
|
+
outputId: output.id,
|
|
1017
|
+
producerTaskId: output.producer.taskId,
|
|
1018
|
+
producerPhase: output.producer.phase,
|
|
1019
|
+
}
|
|
1020
|
+
: {}),
|
|
1021
|
+
});
|
|
1022
|
+
};
|
|
1023
|
+
|
|
1024
|
+
const requireOriginalInputs = (
|
|
1025
|
+
field: string,
|
|
1026
|
+
verification: StructuredVerificationContract,
|
|
1027
|
+
task?: PlanTaskDraft,
|
|
1028
|
+
) => {
|
|
1029
|
+
for (const input of verificationInputPaths(verification)) {
|
|
1030
|
+
if (!isSafeRegularFile(consumerRoot, input)) {
|
|
1031
|
+
unavailable({
|
|
1032
|
+
field,
|
|
1033
|
+
verification,
|
|
1034
|
+
path: input,
|
|
1035
|
+
task,
|
|
1036
|
+
code: "workspace-input-unavailable",
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
const requireInput = (
|
|
1042
|
+
field: string,
|
|
1043
|
+
verification: StructuredVerificationContract,
|
|
1044
|
+
input: string,
|
|
1045
|
+
task?: PlanTaskDraft,
|
|
1046
|
+
) => {
|
|
1047
|
+
const actualPaths = verificationInputPaths(verification).sort(
|
|
1048
|
+
compareCanonicalStrings,
|
|
1049
|
+
);
|
|
1050
|
+
if (actualPaths.includes(input)) return;
|
|
1051
|
+
diagnostics.push({
|
|
1052
|
+
code: "verification-input-binding-mismatch",
|
|
1053
|
+
...(task ? { taskId: task.taskId } : {}),
|
|
1054
|
+
field,
|
|
1055
|
+
verificationId: verification.id,
|
|
1056
|
+
path: input,
|
|
1057
|
+
expectedPaths: [...new Set([...actualPaths, input])].sort(
|
|
1058
|
+
compareCanonicalStrings,
|
|
1059
|
+
),
|
|
1060
|
+
actualPaths,
|
|
1061
|
+
});
|
|
1062
|
+
};
|
|
1063
|
+
|
|
1064
|
+
for (const task of plan.tasks) {
|
|
1065
|
+
if (task.baselineVerification) {
|
|
1066
|
+
requireOriginalInputs(
|
|
1067
|
+
"baselineVerification",
|
|
1068
|
+
task.baselineVerification,
|
|
1069
|
+
task,
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
const permittedTasks = new Set([
|
|
1073
|
+
task.taskId,
|
|
1074
|
+
...(ancestors.get(task.taskId) ?? []),
|
|
1075
|
+
]);
|
|
1076
|
+
for (const [field, verification] of [
|
|
1077
|
+
["affectedVerification", task.affectedVerification],
|
|
1078
|
+
["repairVerification", task.repairVerification],
|
|
1079
|
+
] as const) {
|
|
1080
|
+
for (const input of verificationInputPaths(verification)) {
|
|
1081
|
+
const output = outputByPath.get(input);
|
|
1082
|
+
if (output && !permittedTasks.has(output.producer.taskId)) {
|
|
1083
|
+
diagnostics.push({
|
|
1084
|
+
code: "producer-not-dependency",
|
|
1085
|
+
taskId: task.taskId,
|
|
1086
|
+
field,
|
|
1087
|
+
verificationId: verification.id,
|
|
1088
|
+
path: input,
|
|
1089
|
+
outputId: output.id,
|
|
1090
|
+
producerTaskId: output.producer.taskId,
|
|
1091
|
+
producerPhase: output.producer.phase,
|
|
1092
|
+
});
|
|
1093
|
+
continue;
|
|
1094
|
+
}
|
|
1095
|
+
const operations = pathOperations(plan, input).filter((operation) =>
|
|
1096
|
+
permittedTasks.has(operation.taskId),
|
|
1097
|
+
);
|
|
1098
|
+
if (
|
|
1099
|
+
(!output && !isSafeRegularFile(consumerRoot, input)) ||
|
|
1100
|
+
!pathSurvives(operations, ancestors)
|
|
1101
|
+
) {
|
|
1102
|
+
unavailable({
|
|
1103
|
+
field,
|
|
1104
|
+
verification,
|
|
1105
|
+
path: input,
|
|
1106
|
+
task,
|
|
1107
|
+
code: output
|
|
1108
|
+
? "producer-output-unavailable"
|
|
1109
|
+
: "workspace-input-unavailable",
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
requireOriginalInputs(
|
|
1117
|
+
"verification.baseline.fullSuite",
|
|
1118
|
+
plan.verification.baseline.fullSuite,
|
|
1119
|
+
);
|
|
1120
|
+
const deliveryAcceptanceEntrypoints = new Set([
|
|
1121
|
+
...verificationAcceptanceEntrypoints(plan.verification.change.fullSuite),
|
|
1122
|
+
...verificationAcceptanceEntrypoints(plan.verification.change.postApply),
|
|
1123
|
+
...(plan.verification.agentsCheckpoint.verification
|
|
1124
|
+
? verificationAcceptanceEntrypoints(
|
|
1125
|
+
plan.verification.agentsCheckpoint.verification,
|
|
1126
|
+
)
|
|
1127
|
+
: []),
|
|
1128
|
+
]);
|
|
1129
|
+
for (const output of plan.outputs) {
|
|
1130
|
+
const producer = tasks.get(output.producer.taskId);
|
|
1131
|
+
const target = producer?.phases[output.producer.phase];
|
|
1132
|
+
if (
|
|
1133
|
+
!producer ||
|
|
1134
|
+
!target ||
|
|
1135
|
+
observeSafePath(consumerRoot, output.path).kind !== "absent"
|
|
1136
|
+
) {
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
1139
|
+
const declaredTests = new Set([
|
|
1140
|
+
...producer.impactClosure.relatedTests.map((test) => test.path),
|
|
1141
|
+
...producer.impactClosure.affectedSuite,
|
|
1142
|
+
...(["red", "green", "refactor"] as const).flatMap((phase) => {
|
|
1143
|
+
const boundary = producer.phases[phase];
|
|
1144
|
+
return boundary
|
|
1145
|
+
? verificationAcceptanceEntrypoints(boundary.verification)
|
|
1146
|
+
: [];
|
|
1147
|
+
}),
|
|
1148
|
+
...verificationAcceptanceEntrypoints(producer.affectedVerification),
|
|
1149
|
+
...verificationAcceptanceEntrypoints(producer.repairVerification),
|
|
1150
|
+
...deliveryAcceptanceEntrypoints,
|
|
1151
|
+
]);
|
|
1152
|
+
if (!declaredTests.has(output.path)) continue;
|
|
1153
|
+
for (const phase of ["red", "green", "refactor"] as const) {
|
|
1154
|
+
const boundary = producer.phases[phase];
|
|
1155
|
+
if (
|
|
1156
|
+
!boundary ||
|
|
1157
|
+
PLAN_PHASE_INDEX[phase] < PLAN_PHASE_INDEX[output.producer.phase]
|
|
1158
|
+
) {
|
|
1159
|
+
continue;
|
|
1160
|
+
}
|
|
1161
|
+
requireInput(
|
|
1162
|
+
`phases.${phase}.verification`,
|
|
1163
|
+
boundary.verification,
|
|
1164
|
+
output.path,
|
|
1165
|
+
producer,
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
requireInput(
|
|
1169
|
+
"affectedVerification",
|
|
1170
|
+
producer.affectedVerification,
|
|
1171
|
+
output.path,
|
|
1172
|
+
producer,
|
|
1173
|
+
);
|
|
1174
|
+
requireInput(
|
|
1175
|
+
"repairVerification",
|
|
1176
|
+
producer.repairVerification,
|
|
1177
|
+
output.path,
|
|
1178
|
+
producer,
|
|
1179
|
+
);
|
|
1180
|
+
requireInput(
|
|
1181
|
+
"verification.change.fullSuite",
|
|
1182
|
+
plan.verification.change.fullSuite,
|
|
1183
|
+
output.path,
|
|
1184
|
+
);
|
|
1185
|
+
requireInput(
|
|
1186
|
+
"verification.change.postApply",
|
|
1187
|
+
plan.verification.change.postApply,
|
|
1188
|
+
output.path,
|
|
1189
|
+
);
|
|
1190
|
+
}
|
|
1191
|
+
for (const [field, verification, afterAgents] of [
|
|
1192
|
+
[
|
|
1193
|
+
"verification.change.fullSuite",
|
|
1194
|
+
plan.verification.change.fullSuite,
|
|
1195
|
+
false,
|
|
1196
|
+
],
|
|
1197
|
+
["verification.change.postApply", plan.verification.change.postApply, true],
|
|
1198
|
+
...(plan.verification.agentsCheckpoint.verification
|
|
1199
|
+
? ([
|
|
1200
|
+
[
|
|
1201
|
+
"verification.agentsCheckpoint.verification",
|
|
1202
|
+
plan.verification.agentsCheckpoint.verification,
|
|
1203
|
+
true,
|
|
1204
|
+
],
|
|
1205
|
+
] as const)
|
|
1206
|
+
: []),
|
|
1207
|
+
] as const) {
|
|
1208
|
+
for (const input of verificationInputPaths(verification)) {
|
|
1209
|
+
const output = outputByPath.get(input);
|
|
1210
|
+
const operations = pathOperations(plan, input);
|
|
1211
|
+
const agentsOperation = afterAgents
|
|
1212
|
+
? agentsOperations.get(input)
|
|
1213
|
+
: undefined;
|
|
1214
|
+
if (
|
|
1215
|
+
agentsOperation
|
|
1216
|
+
? !agentsTargetAvailable(consumerRoot, agentsOperation)
|
|
1217
|
+
: (!output && !isSafeRegularFile(consumerRoot, input)) ||
|
|
1218
|
+
!pathSurvives(operations, ancestors)
|
|
1219
|
+
) {
|
|
1220
|
+
unavailable({
|
|
1221
|
+
field,
|
|
1222
|
+
verification,
|
|
1223
|
+
path: input,
|
|
1224
|
+
code: output
|
|
1225
|
+
? "producer-output-unavailable"
|
|
1226
|
+
: "workspace-input-unavailable",
|
|
1227
|
+
});
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
return diagnostics;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
818
1234
|
function deliveryVerificationDiagnostics(
|
|
819
1235
|
plan: ImplementPlan,
|
|
820
1236
|
consumerRoot: string,
|
|
@@ -823,6 +1239,14 @@ function deliveryVerificationDiagnostics(
|
|
|
823
1239
|
owner: string;
|
|
824
1240
|
verification: StructuredVerificationContract;
|
|
825
1241
|
}> = plan.tasks.flatMap((task) => [
|
|
1242
|
+
...(task.baselineVerification
|
|
1243
|
+
? [
|
|
1244
|
+
{
|
|
1245
|
+
owner: `task:${task.taskId}:baseline`,
|
|
1246
|
+
verification: task.baselineVerification,
|
|
1247
|
+
},
|
|
1248
|
+
]
|
|
1249
|
+
: []),
|
|
826
1250
|
{
|
|
827
1251
|
owner: `task:${task.taskId}:affected`,
|
|
828
1252
|
verification: task.affectedVerification,
|
|
@@ -1084,7 +1508,13 @@ function normalizeTracking(value: unknown): PlanTracking {
|
|
|
1084
1508
|
};
|
|
1085
1509
|
}
|
|
1086
1510
|
|
|
1087
|
-
function normalizeDraft(
|
|
1511
|
+
function normalizeDraft(
|
|
1512
|
+
value: unknown,
|
|
1513
|
+
options: {
|
|
1514
|
+
consumerRoot?: string;
|
|
1515
|
+
preserveMissingTaskBaselines?: boolean;
|
|
1516
|
+
} = {},
|
|
1517
|
+
): ImplementPlan {
|
|
1088
1518
|
value = structuredClone(value);
|
|
1089
1519
|
if (
|
|
1090
1520
|
!isRecord(value) ||
|
|
@@ -1125,6 +1555,7 @@ function normalizeDraft(value: unknown): ImplementPlan {
|
|
|
1125
1555
|
tasks: taskDrafts.map((candidate) => {
|
|
1126
1556
|
if (!isRecord(candidate)) return candidate;
|
|
1127
1557
|
const {
|
|
1558
|
+
baselineVerification: _baselineVerification,
|
|
1128
1559
|
affectedVerification: _affectedVerification,
|
|
1129
1560
|
repairVerification: _repairVerification,
|
|
1130
1561
|
...boundary
|
|
@@ -1145,7 +1576,12 @@ function normalizeDraft(value: unknown): ImplementPlan {
|
|
|
1145
1576
|
: { changeContract: normalizeChangeContract(value.changeContract) }),
|
|
1146
1577
|
changeId: value.changeId,
|
|
1147
1578
|
tasks: taskDrafts
|
|
1148
|
-
.map(
|
|
1579
|
+
.map((task) =>
|
|
1580
|
+
normalizeTask(task, {
|
|
1581
|
+
consumerRoot: options.consumerRoot,
|
|
1582
|
+
preserveMissingBaseline: options.preserveMissingTaskBaselines,
|
|
1583
|
+
}),
|
|
1584
|
+
)
|
|
1149
1585
|
.sort((left, right) =>
|
|
1150
1586
|
compareCanonicalStrings(left.taskId, right.taskId),
|
|
1151
1587
|
),
|
|
@@ -1279,11 +1715,15 @@ export function compileImplementPlan(
|
|
|
1279
1715
|
if (options.bindExecutionInputs)
|
|
1280
1716
|
draft = bindDraftVerificationInputs(options.consumerRoot, draft);
|
|
1281
1717
|
draft = preparePlanDraft(draft);
|
|
1282
|
-
const
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1718
|
+
const retainedLegacy =
|
|
1719
|
+
isRecord(draft) && legacyPlans.get(draft) === JSON.stringify(draft);
|
|
1720
|
+
const normalized = normalizeDraft(draft, {
|
|
1721
|
+
consumerRoot: options.consumerRoot,
|
|
1722
|
+
preserveMissingTaskBaselines: retainedLegacy,
|
|
1723
|
+
});
|
|
1724
|
+
const plan = retainedLegacy
|
|
1725
|
+
? (draft as unknown as ImplementPlan)
|
|
1726
|
+
: normalized;
|
|
1287
1727
|
const graph = graphFromPlan(plan);
|
|
1288
1728
|
const readiness = assessImplementGraphReadiness(
|
|
1289
1729
|
options.consumerRoot,
|
|
@@ -1296,6 +1736,7 @@ export function compileImplementPlan(
|
|
|
1296
1736
|
);
|
|
1297
1737
|
const diagnostics = [
|
|
1298
1738
|
...readiness.closure.diagnostics,
|
|
1739
|
+
...inputTimingDiagnostics(plan, options.consumerRoot),
|
|
1299
1740
|
...deliveryVerificationDiagnostics(plan, options.consumerRoot),
|
|
1300
1741
|
] as DesignPlanDiagnostic[];
|
|
1301
1742
|
if (diagnostics.length > 0) {
|
|
@@ -1347,7 +1788,9 @@ export function parseImplementPlan(
|
|
|
1347
1788
|
} catch {
|
|
1348
1789
|
throw new Error("delivery-plan-json-invalid");
|
|
1349
1790
|
}
|
|
1350
|
-
const normalized = normalizeDraft(parsed
|
|
1791
|
+
const normalized = normalizeDraft(parsed, {
|
|
1792
|
+
preserveMissingTaskBaselines: true,
|
|
1793
|
+
});
|
|
1351
1794
|
if (`${canonicalJson(normalized)}\n` === text) return normalized;
|
|
1352
1795
|
if (
|
|
1353
1796
|
!options.allowLegacyOrder ||
|
package/src/design-control.ts
CHANGED
|
@@ -688,7 +688,7 @@ export class DesignController {
|
|
|
688
688
|
? {
|
|
689
689
|
operation: "approve-gate",
|
|
690
690
|
instruction:
|
|
691
|
-
"Read applicable instructions, the affected source and existing tests, and the verification manifest together. Resolve
|
|
691
|
+
"Read applicable instructions, the affected source and existing tests, and the verification manifest together. Resolve remaining substantive choices in Design; use existing same-task approval. Include the goal, acceptance, constraints, and scope for likely implementation repairs in the accepted structured ChangeContract. The parent owns later technical choices within that policy; do not defer user decisions to Implement.",
|
|
692
692
|
}
|
|
693
693
|
: !design.change
|
|
694
694
|
? {
|
|
@@ -699,7 +699,7 @@ export class DesignController {
|
|
|
699
699
|
? {
|
|
700
700
|
operation: "write-artifact",
|
|
701
701
|
instruction:
|
|
702
|
-
"Write proposal, design, specs, author task checkboxes with exact Scenario references, and plan-draft.json. Use the installed example named in the tool description; omit derived identities. Preflight
|
|
702
|
+
"Write proposal, design, specs, author task checkboxes with exact Scenario references, and plan-draft.json. Use the installed example named in the tool description; omit derived identities. Preflight and inspect summary.implementation for the accepted recovery policy and summary.parallelism for independent tasks and serialization causes. Resolve known execution prerequisites in Design. Compile to supply verification bindings and Gate B.",
|
|
703
703
|
}
|
|
704
704
|
: {
|
|
705
705
|
operation: "finalize-delivery",
|
|
@@ -61,13 +61,17 @@ const HINTS = new Map<string, string>(
|
|
|
61
61
|
"verification-reference-invalid":
|
|
62
62
|
"A verification reference contains only use and optional Red expectedFailure. Put executable fields in its definition.",
|
|
63
63
|
"verification-definition-invalid":
|
|
64
|
-
"Define one
|
|
64
|
+
"Define one supported atomic verifier without id, classification, executionBindings or references.",
|
|
65
65
|
"current-task-outside-write-set":
|
|
66
66
|
"Omit disposition to derive the test owner from task writes. Keep path and evidence; do not widen writes to satisfy this label.",
|
|
67
67
|
"related-test-owner-ambiguous":
|
|
68
68
|
"Several other tasks edit this test. Choose the intended regression-task owner explicitly, or resolve overlapping task ownership.",
|
|
69
69
|
"verification-input-binding-mismatch":
|
|
70
|
-
"
|
|
70
|
+
"For a phase binding, omit verificationInputs to derive exact bindings from the contract and outputs. For affected, repair or full-suite fields, retain declared new tests as executable inputs. Keep supporting source files in read.",
|
|
71
|
+
"workspace-input-unavailable":
|
|
72
|
+
"Bind original baselineVerification and baseline.fullSuite to safe files in the original snapshot. For later consumers, declare an available producer and preserve its output through consumption. Do not create future tests in the original baseline.",
|
|
73
|
+
"producer-output-unavailable":
|
|
74
|
+
"Keep the declared producer output available at this consumer, including after intermediate deletes and AGENTS checkpoints. Preserve the accepted verification coverage.",
|
|
71
75
|
"workspace-input-has-producer":
|
|
72
76
|
"Omit verificationInputs or bind this path to its declared outputId instead of workspace.",
|
|
73
77
|
"multiple-output-producers":
|
|
@@ -78,8 +82,6 @@ const HINTS = new Map<string, string>(
|
|
|
78
82
|
"This verification needs an output from a later phase. Correct the producer or phase order; input inference cannot make that output available earlier.",
|
|
79
83
|
"verification-input-not-declared":
|
|
80
84
|
"The verification contract requires a path outside this phase's read/write declarations. Correct the contract or explicitly declare the approved input path.",
|
|
81
|
-
"workspace-input-unavailable":
|
|
82
|
-
"The required workspace input is missing or not a safe regular file. Correct its path or declare its planned producer.",
|
|
83
85
|
}),
|
|
84
86
|
);
|
|
85
87
|
function safePath(value: unknown): value is string {
|