@abelxiaoxing/cadence 1.6.0 → 1.8.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.
Files changed (45) hide show
  1. package/README.md +25 -4
  2. package/config/.env.example +6 -0
  3. package/config/plan-draft.example.json +3 -0
  4. package/config/plan-draft.multiple-tasks.example.json +30 -0
  5. package/package.json +1 -1
  6. package/prompts/abel-design.md +33 -3
  7. package/prompts/abel-implement.md +36 -4
  8. package/src/apply-transaction.ts +28 -8
  9. package/src/build-windows-job.mjs +95 -0
  10. package/src/candidate-workspace.ts +6 -0
  11. package/src/change-verification.ts +764 -67
  12. package/src/contracts.ts +8 -0
  13. package/src/delivery-compiler.ts +452 -9
  14. package/src/design-control.ts +2 -2
  15. package/src/design-diagnostics.ts +6 -4
  16. package/src/durable-contracts.ts +21 -3
  17. package/src/durable-workflow.ts +55 -40
  18. package/src/execution-profile.ts +28 -2
  19. package/src/execution-retention.ts +77 -0
  20. package/src/implement-continuation.ts +559 -0
  21. package/src/implement-graph.ts +114 -0
  22. package/src/implement-plan.ts +2 -0
  23. package/src/index.ts +151 -1
  24. package/src/isolation-backend.ts +2 -1
  25. package/src/operator-cli.mjs +664 -104
  26. package/src/operator-tools.ts +7 -1
  27. package/src/package-state.ts +11 -0
  28. package/src/package-verification.ts +66 -18
  29. package/src/package-workflow.ts +6 -0
  30. package/src/phase-execution.ts +18 -7
  31. package/src/plan-draft-summary.ts +385 -7
  32. package/src/plan-draft.ts +9 -0
  33. package/src/remove-path.ts +32 -0
  34. package/src/subagent-activity.ts +3 -1
  35. package/src/verification-capability.ts +18 -6
  36. package/src/verification-environment-io.ts +123 -14
  37. package/src/verification-environment.ts +50 -3
  38. package/src/windows-job-backend.ts +419 -0
  39. package/src/windows-job.c +149 -0
  40. package/src/workflow-policy.ts +296 -5
  41. package/src/workflow-recovery-policy.ts +2 -0
  42. package/src/workflow-state-machine.ts +314 -80
  43. package/src/workflow-status.ts +251 -59
  44. package/src/workspace-io-entry.mjs +2 -2
  45. package/src/workspace-io-worker.mjs +207 -110
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;
@@ -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(value: unknown): PlanTaskDraft {
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(value: unknown): ImplementPlan {
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(normalizeTask)
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 normalized = normalizeDraft(draft);
1283
- const plan =
1284
- isRecord(draft) && legacyPlans.get(draft) === JSON.stringify(draft)
1285
- ? (draft as unknown as ImplementPlan)
1286
- : normalized;
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 ||
@@ -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 only remaining substantive choices; use existing same-task approval. Submit the accepted structured ChangeContract, not a prose placeholder.",
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 then compile; compilation supplies verification bindings and Gate B.",
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 existing atomic verifier without id, classification, executionBindings or references.",
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
- "Omit verificationInputs to derive exact bindings from the verification contract and declared outputs. Keep supporting source files in read, not in verificationInputs.",
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 {