@abelxiaoxing/cadence 1.0.12 → 1.0.14

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/src/contracts.ts CHANGED
@@ -75,6 +75,7 @@ export const ARTIFACT_FAILURE_CODES = [
75
75
  "invalid-snapshot",
76
76
  "invalid-structural-result",
77
77
  "child-no-structural-submit",
78
+ "structural-identity-mismatch",
78
79
  "invalid-verification-contract",
79
80
  "lockfile-bound-mismatch",
80
81
  "noncanonical-path",
@@ -83,6 +84,7 @@ export const ARTIFACT_FAILURE_CODES = [
83
84
  "package-bound-mismatch",
84
85
  "package-manifest-invalid",
85
86
  "parent-review-rejected",
87
+ "producer-output-unavailable",
86
88
  "red-not-witnessed",
87
89
  "submodule",
88
90
  "target-baseline-missing",
@@ -150,11 +152,37 @@ export const CHILD_TRANSPORT_CODES = [
150
152
  "transport-failure",
151
153
  ] as const;
152
154
  export const FAILURE_STAGES = [
153
- "phase-runtime",
154
155
  "child-session-create",
155
156
  "child-provider-stream",
156
- "child-finalization",
157
157
  "child-timeout",
158
+ "child-finalization",
159
+ "structural-submit",
160
+ "candidate-retention",
161
+ "candidate-diff",
162
+ "candidate-preflight",
163
+ "parent-review",
164
+ "candidate-apply",
165
+ "agents-checkpoint",
166
+ "phase-runtime",
167
+ ] as const;
168
+
169
+ export const SUBMIT_FINAL_CATEGORIES = [
170
+ "no-final-assistant",
171
+ "text-only",
172
+ "mixed",
173
+ "multiple-submit",
174
+ "single-submit-only",
175
+ ] as const;
176
+ export const SUBMIT_SCHEMA_STATES = [
177
+ "not-submitted",
178
+ "valid",
179
+ "invalid",
180
+ ] as const;
181
+ export const IDENTITY_DIMENSIONS = [
182
+ "request",
183
+ "role",
184
+ "task",
185
+ "phase",
158
186
  ] as const;
159
187
 
160
188
  export type ArtifactFailureCode = (typeof ARTIFACT_FAILURE_CODES)[number];
@@ -165,10 +193,22 @@ export type VerificationAdapterCode =
165
193
  (typeof VERIFICATION_ADAPTER_CODES)[number];
166
194
  export type ChildTransportCode = (typeof CHILD_TRANSPORT_CODES)[number];
167
195
  export type FailureStage = (typeof FAILURE_STAGES)[number];
196
+ export type SubmitFinalCategory = (typeof SUBMIT_FINAL_CATEGORIES)[number];
197
+ export type SubmitSchemaState = (typeof SUBMIT_SCHEMA_STATES)[number];
198
+ export type IdentityDimension = (typeof IDENTITY_DIMENSIONS)[number];
199
+
200
+ export interface SafeFailureDetails {
201
+ finalCategory?: SubmitFinalCategory;
202
+ submitAttempts?: number;
203
+ schema?: SubmitSchemaState;
204
+ identityMismatch?: IdentityDimension[];
205
+ verificationId?: string;
206
+ }
168
207
 
169
208
  export interface SafeFailureDiagnostic {
170
- code: ChildTransportCode;
209
+ code: ArtifactFailureCode | StaleFailureCode | ChildTransportCode;
171
210
  stage: FailureStage;
211
+ details?: SafeFailureDetails;
172
212
  }
173
213
 
174
214
  export type EnvironmentFailure = {
@@ -179,8 +219,13 @@ export type EnvironmentFailure = {
179
219
  };
180
220
 
181
221
  export type CandidateFailure =
182
- | { kind: "artifact"; code: ArtifactFailureCode; evidence?: string[] }
183
- | { kind: "stale"; code: StaleFailureCode }
222
+ | {
223
+ kind: "artifact";
224
+ code: ArtifactFailureCode;
225
+ stage: FailureStage;
226
+ details?: SafeFailureDetails;
227
+ }
228
+ | { kind: "stale"; code: StaleFailureCode; stage: FailureStage }
184
229
  | EnvironmentFailure
185
230
  | { kind: "verification-adapter"; code: VerificationAdapterCode }
186
231
  | { kind: "approval-boundary"; code: ApprovalBoundaryCode }
@@ -192,28 +237,36 @@ export type ChildFailure =
192
237
  | {
193
238
  kind: "transport";
194
239
  code: ChildTransportCode;
195
- stage?: FailureStage;
240
+ stage: FailureStage;
241
+ details?: SafeFailureDetails;
196
242
  };
197
243
 
198
244
  export type CandidateRejection =
199
245
  | {
200
246
  kind: "artifact";
201
247
  code: "parent-review-rejected";
202
- evidence?: string[];
248
+ stage: "parent-review";
203
249
  }
204
250
  | { kind: "approval-boundary"; code: ApprovalBoundaryCode };
205
251
 
206
252
  export type TaskFailure =
207
253
  | { kind: "approval-boundary"; code: ApprovalBoundaryCode }
208
254
  | { kind: "verification-adapter"; code: VerificationAdapterCode }
255
+ | {
256
+ kind: "graph-readiness";
257
+ diagnostic: ImplementGraphReadinessDiagnostic;
258
+ }
209
259
  | {
210
260
  kind: "attempts-exhausted";
211
261
  cause: "artifact" | "stale" | "transport";
212
- lastFailure?: SafeFailureDiagnostic;
262
+ attemptsUsed: 2;
263
+ lastFailure: SafeFailureDiagnostic;
213
264
  }
214
265
  | {
215
266
  kind: "checkpoint-attempts-exhausted";
216
267
  cause: "artifact" | "stale";
268
+ attemptsUsed: 2;
269
+ lastFailure: SafeFailureDiagnostic;
217
270
  }
218
271
  | EnvironmentFailure
219
272
  | { kind: "result-limit"; limitBytes: number };
@@ -240,8 +293,6 @@ interface VerificationBase {
240
293
  id: string;
241
294
  classification: VerificationClassification;
242
295
  expectedFailure?: string;
243
- /** Internal marker retained only for normalized 1.0.x argv compatibility. */
244
- legacy?: true;
245
296
  }
246
297
 
247
298
  export type PackageManager = "bun" | "npm" | "pnpm" | "yarn";
@@ -250,8 +301,7 @@ export interface PackageScriptRunner {
250
301
  kind: "package-script";
251
302
  packageManager: PackageManager;
252
303
  script: string;
253
- /** Exact Gate-B-approved package.json script value. Omitted only for legacy input. */
254
- command?: string;
304
+ command: string;
255
305
  }
256
306
 
257
307
  export type ExecutableRunner =
@@ -271,8 +321,7 @@ export interface PackageScriptVerificationContract extends VerificationBase {
271
321
  kind: "package-script";
272
322
  packageManager: PackageManager;
273
323
  script: string;
274
- /** Exact Gate-B-approved package.json script value. Omitted only for legacy input. */
275
- command?: string;
324
+ command: string;
276
325
  args: string[];
277
326
  }
278
327
 
@@ -294,20 +343,11 @@ export interface VerificationStepsContract {
294
343
  steps: AtomicVerificationContract[];
295
344
  }
296
345
 
297
- /** @deprecated Accepted only as 1.0.x compatibility input and normalized immediately. */
298
- export interface LegacyVerificationContract extends VerificationBase {
299
- argv: string[];
300
- minTests: number;
301
- kind?: never;
302
- }
303
-
304
346
  export type StructuredVerificationContract =
305
347
  | AtomicVerificationContract
306
348
  | VerificationStepsContract;
307
349
 
308
- export type VerificationContract =
309
- | StructuredVerificationContract
310
- | LegacyVerificationContract;
350
+ export type VerificationContract = StructuredVerificationContract;
311
351
 
312
352
  export interface ImpactClosureContract {
313
353
  changedSurfaces: ImpactSurface[];
@@ -321,18 +361,25 @@ export interface ImpactClosureContract {
321
361
  affectedSuite: string[];
322
362
  }
323
363
 
324
- export type ImplementationPhase = "red" | "green" | "refactor";
364
+ export const IMPLEMENTATION_PHASES = ["red", "green", "refactor"] as const;
365
+ export type ImplementationPhase = (typeof IMPLEMENTATION_PHASES)[number];
366
+
367
+ export type VerificationInputBinding =
368
+ | { kind: "workspace"; path: string }
369
+ | { kind: "output"; outputId: string };
325
370
 
326
371
  export interface PhaseBoundary {
327
372
  read: string[];
328
373
  write: string[];
329
374
  verification: VerificationContract;
375
+ verificationInputs: VerificationInputBinding[];
330
376
  verificationLock?: string;
331
377
  }
332
378
 
333
379
  export interface TaskBoundary {
334
380
  changeId: string;
335
381
  taskId: string;
382
+ dependsOn: string[];
336
383
  objective: string;
337
384
  context: { agents: string; contract: string };
338
385
  roots: string[];
@@ -351,6 +398,77 @@ export interface TaskBoundary {
351
398
  impactClosure: ImpactClosureContract;
352
399
  }
353
400
 
401
+ export interface ImplementGraphOutput {
402
+ id: string;
403
+ path: string;
404
+ producer: { taskId: string; phase: ImplementationPhase };
405
+ postcondition: "regular-file";
406
+ }
407
+
408
+ export type ImplementTaskBoundary = Omit<TaskBoundary, "changeId">;
409
+
410
+ export interface ImplementGraphBoundary {
411
+ changeId: string;
412
+ tasks: ImplementTaskBoundary[];
413
+ outputs: ImplementGraphOutput[];
414
+ }
415
+
416
+ export const IMPLEMENT_GRAPH_READINESS_CODES = [
417
+ "dependency-blocked",
418
+ "dependency-cycle",
419
+ "duplicate-output-id",
420
+ "duplicate-task-id",
421
+ "graph-hash-mismatch",
422
+ "invalid-implement-graph",
423
+ "invalid-output-path",
424
+ "multiple-output-producers",
425
+ "output-not-declared",
426
+ "output-producer-invalid",
427
+ "output-write-not-approved",
428
+ "phase-blocked",
429
+ "producer-not-dependency",
430
+ "producer-output-unavailable",
431
+ "producer-phase-after-consumer",
432
+ "unknown-dependency",
433
+ "verification-input-binding-mismatch",
434
+ "workspace-input-has-producer",
435
+ "workspace-input-unavailable",
436
+ ] as const;
437
+
438
+ export type ImplementGraphReadinessCode =
439
+ (typeof IMPLEMENT_GRAPH_READINESS_CODES)[number];
440
+
441
+ type ImplementGraphDiagnosticDetails = Partial<{
442
+ taskId: string;
443
+ phase: ImplementationPhase;
444
+ verificationId: string;
445
+ outputId: string;
446
+ path: string;
447
+ producerTaskId: string;
448
+ producerPhase: ImplementationPhase;
449
+ dependencyTaskId: string;
450
+ }>;
451
+
452
+ export type ImplementGraphReadinessDiagnostic =
453
+ | ({
454
+ kind: "graph-readiness";
455
+ code: ImplementGraphReadinessCode;
456
+ } & ImplementGraphDiagnosticDetails)
457
+ | ({
458
+ kind: "verification-adapter";
459
+ code: VerificationAdapterCode;
460
+ taskId: string;
461
+ phase: ImplementationPhase;
462
+ verificationId: string;
463
+ } & ImplementGraphDiagnosticDetails)
464
+ | ({
465
+ kind: "design-readiness";
466
+ code: "verification-contract-unsupported";
467
+ taskId: string;
468
+ phase: ImplementationPhase;
469
+ verificationId?: string;
470
+ } & ImplementGraphDiagnosticDetails);
471
+
354
472
  export interface PhaseAttempt {
355
473
  changeId: string;
356
474
  taskId: string;
@@ -362,13 +480,14 @@ export interface PhaseAttempt {
362
480
  export type ImplementRunRequest =
363
481
  | {
364
482
  stage: "abel-implement";
365
- kind: "open-task";
366
- boundary: TaskBoundary;
367
- attempt: PhaseAttempt;
483
+ kind: "admit-graph";
484
+ graph: ImplementGraphBoundary;
485
+ graphHash: string;
486
+ state: { completedTasks: string[]; blockedTasks: string[] };
368
487
  }
369
488
  | {
370
489
  stage: "abel-implement";
371
- kind: "phase-attempt";
490
+ kind: "task-attempt";
372
491
  attempt: PhaseAttempt;
373
492
  };
374
493
 
@@ -435,6 +554,7 @@ export function isValidRelativePath(p: unknown): p is string {
435
554
  p.length <= 512 &&
436
555
  !NONCANONICAL.test(p) &&
437
556
  !p.startsWith("/") &&
557
+ !/^[a-z]:\//iu.test(p) &&
438
558
  (p === "." ||
439
559
  (!p.startsWith("./") &&
440
560
  !p.endsWith("/") &&
@@ -513,36 +633,26 @@ function validVerificationIdentity(contract: Record<string, unknown>): boolean {
513
633
 
514
634
  function validatePackageScriptRunner(
515
635
  value: unknown,
516
- requireCommand: boolean,
517
636
  ): value is PackageScriptRunner {
518
637
  if (
519
- !hasExactKeys(
520
- value,
521
- requireCommand
522
- ? ["kind", "packageManager", "script", "command"]
523
- : ["kind", "packageManager", "script"],
524
- requireCommand ? [] : ["command"],
525
- ) ||
638
+ !hasExactKeys(value, ["kind", "packageManager", "script", "command"]) ||
526
639
  value.kind !== "package-script" ||
527
640
  !["bun", "npm", "pnpm", "yarn"].includes(String(value.packageManager)) ||
528
641
  typeof value.script !== "string" ||
529
642
  !VERIFICATION_NAME.test(value.script) ||
530
643
  value.script.includes("/") ||
531
- (value.command !== undefined && !validScriptCommand(value.command))
644
+ !validScriptCommand(value.command)
532
645
  ) {
533
646
  return false;
534
647
  }
535
- return !requireCommand || value.command !== undefined;
648
+ return true;
536
649
  }
537
650
 
538
- function validateExecutableRunner(
539
- value: unknown,
540
- requirePackageCommand: boolean,
541
- ): value is ExecutableRunner {
651
+ function validateExecutableRunner(value: unknown): value is ExecutableRunner {
542
652
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
543
653
  const runner = value as Record<string, unknown>;
544
654
  if (runner.kind === "package-script") {
545
- return validatePackageScriptRunner(runner, requirePackageCommand);
655
+ return validatePackageScriptRunner(runner);
546
656
  }
547
657
  if (
548
658
  runner.kind === "local-binary" &&
@@ -562,13 +672,9 @@ function validateExecutableRunner(
562
672
  );
563
673
  }
564
674
 
565
- function validateVitestRunner(
566
- value: ExecutableRunner,
567
- legacy: boolean,
568
- ): boolean {
569
- if (legacy) return true;
675
+ function validateVitestRunner(value: ExecutableRunner): boolean {
570
676
  if (value.kind === "package-script") {
571
- const tokens = value.command?.split(/\s+/u) ?? [];
677
+ const tokens = value.command.split(/\s+/u);
572
678
  return (
573
679
  tokens[0] === "vitest" ||
574
680
  (tokens[0] === "npx" &&
@@ -581,16 +687,12 @@ function validateVitestRunner(
581
687
 
582
688
  function validateAtomicVerification(
583
689
  value: unknown,
584
- requirePackageCommand: boolean,
585
690
  ): value is AtomicVerificationContract {
586
691
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
587
692
  const contract = value as Record<string, unknown>;
588
- const legacy = contract.legacy === true;
589
- const packageCommandRequired = requirePackageCommand && !legacy;
590
693
  const expectedFailure =
591
694
  contract.classification === "expected-red" ? ["expectedFailure"] : [];
592
695
  if (contract.kind === "vitest") {
593
- const legacyRunner = contract.runner as Record<string, unknown>;
594
696
  const executableRunner = contract.runner as ExecutableRunner;
595
697
  return (
596
698
  hasExactKeys(contract, [
@@ -602,11 +704,10 @@ function validateAtomicVerification(
602
704
  "classification",
603
705
  ...expectedFailure,
604
706
  "minTests",
605
- ...(legacy ? ["legacy"] : []),
606
707
  ]) &&
607
708
  validVerificationIdentity(contract) &&
608
- validateExecutableRunner(contract.runner, packageCommandRequired) &&
609
- validateVitestRunner(executableRunner, legacy) &&
709
+ validateExecutableRunner(contract.runner) &&
710
+ validateVitestRunner(executableRunner) &&
610
711
  Array.isArray(contract.testFiles) &&
611
712
  contract.testFiles.length > 0 &&
612
713
  contract.testFiles.length <= 64 &&
@@ -614,14 +715,7 @@ function validateAtomicVerification(
614
715
  new Set(contract.testFiles).size === contract.testFiles.length &&
615
716
  validVerificationArgs(contract.args) &&
616
717
  Number.isSafeInteger(contract.minTests) &&
617
- (contract.minTests as number) >= 1 &&
618
- (!legacy ||
619
- (hasExactKeys(legacyRunner, ["kind", "packageManager", "script"]) &&
620
- legacyRunner.kind === "package-script" &&
621
- legacyRunner.packageManager === "bun" &&
622
- legacyRunner.script === "test:target" &&
623
- contract.testFiles.every((part) => part.startsWith("test/")) &&
624
- contract.args.length === 0))
718
+ (contract.minTests as number) >= 1
625
719
  );
626
720
  }
627
721
  if (contract.kind === "package-script") {
@@ -629,38 +723,28 @@ function validateAtomicVerification(
629
723
  kind: "package-script",
630
724
  packageManager: contract.packageManager,
631
725
  script: contract.script,
632
- ...(contract.command === undefined ? {} : { command: contract.command }),
726
+ command: contract.command,
633
727
  };
634
728
  return (
635
- hasExactKeys(
636
- contract,
637
- [
638
- "kind",
639
- "id",
640
- "packageManager",
641
- "script",
642
- ...(packageCommandRequired ? ["command"] : []),
643
- "args",
644
- "classification",
645
- ...expectedFailure,
646
- ...(legacy ? ["legacy"] : []),
647
- ],
648
- packageCommandRequired ? [] : ["command"],
649
- ) &&
729
+ hasExactKeys(contract, [
730
+ "kind",
731
+ "id",
732
+ "packageManager",
733
+ "script",
734
+ "command",
735
+ "args",
736
+ "classification",
737
+ ...expectedFailure,
738
+ ]) &&
650
739
  validVerificationIdentity(contract) &&
651
- validatePackageScriptRunner(runner, packageCommandRequired) &&
652
- validVerificationArgs(contract.args) &&
653
- (!legacy ||
654
- (contract.packageManager === "bun" &&
655
- contract.script === "check" &&
656
- contract.command === undefined &&
657
- contract.args.length === 0))
740
+ validatePackageScriptRunner(runner) &&
741
+ validVerificationArgs(contract.args)
658
742
  );
659
743
  }
660
744
  if (contract.kind !== "static-check") return false;
661
745
  const runner = contract.runner as Record<string, unknown> | undefined;
662
746
  const validRunner =
663
- validateExecutableRunner(runner, requirePackageCommand) ||
747
+ validateExecutableRunner(runner) ||
664
748
  (hasExactKeys(runner, ["kind", "script"]) &&
665
749
  runner.kind === "node" &&
666
750
  isValidRelativePath(runner.script));
@@ -685,7 +769,7 @@ function validateStructuredVerification(
685
769
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
686
770
  const contract = value as Record<string, unknown>;
687
771
  if (contract.kind !== "steps") {
688
- return validateAtomicVerification(contract, true);
772
+ return validateAtomicVerification(contract);
689
773
  }
690
774
  if (
691
775
  !hasExactKeys(contract, ["kind", "id", "classification", "steps"]) ||
@@ -698,7 +782,7 @@ function validateStructuredVerification(
698
782
  !Array.isArray(contract.steps) ||
699
783
  contract.steps.length === 0 ||
700
784
  contract.steps.length > 8 ||
701
- !contract.steps.every((step) => validateAtomicVerification(step, true))
785
+ !contract.steps.every((step) => validateAtomicVerification(step))
702
786
  ) {
703
787
  return false;
704
788
  }
@@ -712,72 +796,6 @@ function validateStructuredVerification(
712
796
  );
713
797
  }
714
798
 
715
- function normalizeLegacyVerification(
716
- contract: Record<string, unknown>,
717
- ): StructuredVerificationContract | null {
718
- if (
719
- !hasExactKeys(
720
- contract,
721
- ["id", "argv", "classification", "minTests"],
722
- ["expectedFailure"],
723
- ) ||
724
- !validVerificationIdentity(contract) ||
725
- !Number.isSafeInteger(contract.minTests) ||
726
- (contract.minTests as number) < 1 ||
727
- !Array.isArray(contract.argv) ||
728
- !contract.argv.every(validVerificationToken)
729
- ) {
730
- return null;
731
- }
732
- const argv = contract.argv as string[];
733
- const base = {
734
- id: contract.id as string,
735
- classification: contract.classification as VerificationClassification,
736
- ...(contract.expectedFailure === undefined
737
- ? {}
738
- : { expectedFailure: contract.expectedFailure as string }),
739
- };
740
- if (
741
- argv.length >= 4 &&
742
- argv[0] === "bun" &&
743
- argv[1] === "run" &&
744
- argv[2] === "test:target" &&
745
- argv
746
- .slice(3)
747
- .every((part) => part.startsWith("test/") && isValidRelativePath(part))
748
- ) {
749
- return {
750
- ...base,
751
- kind: "vitest",
752
- runner: {
753
- kind: "package-script",
754
- packageManager: "bun",
755
- script: "test:target",
756
- },
757
- testFiles: argv.slice(3),
758
- args: [],
759
- minTests: contract.minTests as number,
760
- legacy: true,
761
- };
762
- }
763
- if (
764
- argv.length === 3 &&
765
- argv[0] === "bun" &&
766
- argv[1] === "run" &&
767
- argv[2] === "check"
768
- ) {
769
- return {
770
- ...base,
771
- kind: "package-script",
772
- packageManager: "bun",
773
- script: "check",
774
- args: [],
775
- legacy: true,
776
- };
777
- }
778
- return null;
779
- }
780
-
781
799
  export function validateVerificationContract(
782
800
  value: unknown,
783
801
  ):
@@ -786,12 +804,6 @@ export function validateVerificationContract(
786
804
  if (validateStructuredVerification(value)) {
787
805
  return { ok: true, value: structuredClone(value) };
788
806
  }
789
- if (value && typeof value === "object" && !Array.isArray(value)) {
790
- const legacy = normalizeLegacyVerification(
791
- value as Record<string, unknown>,
792
- );
793
- if (legacy) return { ok: true, value: legacy };
794
- }
795
807
  return {
796
808
  ok: false,
797
809
  reason: "verification contract is unsupported by the Implement runtime",
@@ -813,13 +825,21 @@ export function verificationInputPaths(
813
825
  ): string[] {
814
826
  return [
815
827
  ...new Set(
816
- verificationSteps(verification).flatMap((step) =>
817
- step.kind === "vitest"
818
- ? step.testFiles
819
- : step.kind === "static-check" && step.runner.kind === "node"
820
- ? [step.runner.script]
821
- : [],
822
- ),
828
+ verificationSteps(verification).flatMap((step) => {
829
+ const paths =
830
+ step.kind === "vitest"
831
+ ? [...step.testFiles]
832
+ : step.kind === "static-check" && step.runner.kind === "node"
833
+ ? [step.runner.script]
834
+ : [];
835
+ if (
836
+ step.kind === "package-script" ||
837
+ ("runner" in step && step.runner.kind === "package-script")
838
+ ) {
839
+ paths.push("package.json");
840
+ }
841
+ return paths;
842
+ }),
823
843
  ),
824
844
  ];
825
845
  }
@@ -1019,8 +1039,11 @@ function validateImpactClosure(
1019
1039
  ) {
1020
1040
  return "public impact closure is incomplete";
1021
1041
  }
1022
- const bounds = snapshot as Record<string, { kind?: unknown }>;
1023
- if (!affectedSuite.some((test) => bounds?.[test]?.kind === "file")) {
1042
+ const bounds = snapshot as Record<string, { kind?: unknown }> | undefined;
1043
+ if (
1044
+ bounds !== undefined &&
1045
+ !affectedSuite.some((test) => bounds[test]?.kind === "file")
1046
+ ) {
1024
1047
  return "affected suite contains no existing test evidence";
1025
1048
  }
1026
1049
  return null;
@@ -1137,19 +1160,12 @@ function validateCandidateRejection(
1137
1160
  if (
1138
1161
  rejection.kind !== "artifact" ||
1139
1162
  rejection.code !== "parent-review-rejected" ||
1140
- !hasExactKeys(rejection, ["kind", "code"], ["evidence"])
1163
+ rejection.stage !== "parent-review" ||
1164
+ !hasExactKeys(rejection, ["kind", "code", "stage"])
1141
1165
  ) {
1142
1166
  return false;
1143
1167
  }
1144
- return (
1145
- rejection.evidence === undefined ||
1146
- (Array.isArray(rejection.evidence) &&
1147
- rejection.evidence.length <= 8 &&
1148
- rejection.evidence.every(
1149
- (item) =>
1150
- typeof item === "string" && item.length > 0 && item.length <= 512,
1151
- ))
1152
- );
1168
+ return true;
1153
1169
  }
1154
1170
 
1155
1171
  export function validateImplementDiscardOperation(
@@ -1197,6 +1213,49 @@ function validatePathSet(value: unknown): value is string[] {
1197
1213
  );
1198
1214
  }
1199
1215
 
1216
+ function validateIdentifierSet(value: unknown): value is string[] {
1217
+ return (
1218
+ Array.isArray(value) &&
1219
+ value.every(validIdentifier) &&
1220
+ new Set(value).size === value.length
1221
+ );
1222
+ }
1223
+
1224
+ function validateVerificationInputBindings(
1225
+ value: unknown,
1226
+ ): value is VerificationInputBinding[] {
1227
+ if (!Array.isArray(value)) return false;
1228
+ const keys = new Set<string>();
1229
+ for (const binding of value) {
1230
+ if (
1231
+ binding &&
1232
+ typeof binding === "object" &&
1233
+ !Array.isArray(binding) &&
1234
+ (binding as { kind?: unknown }).kind === "workspace" &&
1235
+ hasExactKeys(binding, ["kind", "path"]) &&
1236
+ isValidRelativePath(binding.path)
1237
+ ) {
1238
+ if (keys.has(`workspace:${binding.path}`)) return false;
1239
+ keys.add(`workspace:${binding.path}`);
1240
+ continue;
1241
+ }
1242
+ if (
1243
+ binding &&
1244
+ typeof binding === "object" &&
1245
+ !Array.isArray(binding) &&
1246
+ (binding as { kind?: unknown }).kind === "output" &&
1247
+ hasExactKeys(binding, ["kind", "outputId"]) &&
1248
+ validIdentifier(binding.outputId)
1249
+ ) {
1250
+ if (keys.has(`output:${binding.outputId}`)) return false;
1251
+ keys.add(`output:${binding.outputId}`);
1252
+ continue;
1253
+ }
1254
+ return false;
1255
+ }
1256
+ return true;
1257
+ }
1258
+
1200
1259
  function validateRoots(value: unknown): value is string[] {
1201
1260
  if (!validatePathSet(value) || value.length === 0) return false;
1202
1261
  if (
@@ -1227,7 +1286,7 @@ function validatePhaseBoundary(
1227
1286
  if (
1228
1287
  !hasExactKeys(
1229
1288
  value,
1230
- ["read", "write", "verification"],
1289
+ ["read", "write", "verification", "verificationInputs"],
1231
1290
  ["verificationLock"],
1232
1291
  )
1233
1292
  ) {
@@ -1236,6 +1295,7 @@ function validatePhaseBoundary(
1236
1295
  if (
1237
1296
  !validatePathSet(value.read) ||
1238
1297
  !validatePathSet(value.write) ||
1298
+ !validateVerificationInputBindings(value.verificationInputs) ||
1239
1299
  value.write.some(isAgentsPath) ||
1240
1300
  (value.verificationLock !== undefined &&
1241
1301
  !isValidRelativePath(value.verificationLock))
@@ -1260,6 +1320,7 @@ function validateTaskBoundary(
1260
1320
  !hasExactKeys(value, [
1261
1321
  "changeId",
1262
1322
  "taskId",
1323
+ "dependsOn",
1263
1324
  "objective",
1264
1325
  "context",
1265
1326
  "roots",
@@ -1271,6 +1332,7 @@ function validateTaskBoundary(
1271
1332
  ]) ||
1272
1333
  !validIdentifier(value.changeId) ||
1273
1334
  !validIdentifier(value.taskId) ||
1335
+ !validateIdentifierSet(value.dependsOn) ||
1274
1336
  typeof value.objective !== "string" ||
1275
1337
  value.objective.length === 0 ||
1276
1338
  value.objective.length > 4096 ||
@@ -1339,20 +1401,121 @@ function validateTaskBoundary(
1339
1401
  );
1340
1402
  }
1341
1403
 
1342
- function normalizedTaskBoundary(value: TaskBoundary): TaskBoundary {
1343
- const clone = structuredClone(value);
1344
- clone.phases.red.verification = cloneVerificationContract(
1345
- clone.phases.red.verification,
1346
- );
1347
- clone.phases.green.verification = cloneVerificationContract(
1348
- clone.phases.green.verification,
1349
- );
1350
- if (clone.phases.refactor) {
1351
- clone.phases.refactor.verification = cloneVerificationContract(
1352
- clone.phases.refactor.verification,
1353
- );
1404
+ export type ImplementGraphContractDiagnostic =
1405
+ | { code: "invalid-implement-graph" }
1406
+ | { code: "invalid-output-path"; outputId?: string };
1407
+
1408
+ export function validateImplementGraphBoundary(value: unknown):
1409
+ | { ok: true; value: ImplementGraphBoundary }
1410
+ | {
1411
+ ok: false;
1412
+ reason: string;
1413
+ diagnostic: ImplementGraphContractDiagnostic;
1414
+ } {
1415
+ if (
1416
+ !hasExactKeys(value, ["changeId", "tasks", "outputs"]) ||
1417
+ !validIdentifier(value.changeId) ||
1418
+ !Array.isArray(value.tasks) ||
1419
+ value.tasks.length === 0 ||
1420
+ value.tasks.length > 128 ||
1421
+ !Array.isArray(value.outputs) ||
1422
+ value.outputs.length > 512
1423
+ ) {
1424
+ return {
1425
+ ok: false,
1426
+ reason: "invalid Implement graph boundary",
1427
+ diagnostic: { code: "invalid-implement-graph" },
1428
+ };
1429
+ }
1430
+
1431
+ for (const candidate of value.tasks) {
1432
+ if (
1433
+ !candidate ||
1434
+ typeof candidate !== "object" ||
1435
+ Array.isArray(candidate)
1436
+ ) {
1437
+ return {
1438
+ ok: false,
1439
+ reason: "invalid Implement task boundary",
1440
+ diagnostic: { code: "invalid-implement-graph" },
1441
+ };
1442
+ }
1443
+ const task = candidate as Record<string, unknown>;
1444
+ const phases = task.phases as Record<string, unknown> | undefined;
1445
+ if (
1446
+ Object.hasOwn(task, "changeId") ||
1447
+ !Object.hasOwn(task, "dependsOn") ||
1448
+ !validateIdentifierSet(task.dependsOn) ||
1449
+ !phases ||
1450
+ !["red", "green"].every((phase) => {
1451
+ const boundary = phases[phase];
1452
+ return (
1453
+ boundary !== null &&
1454
+ typeof boundary === "object" &&
1455
+ !Array.isArray(boundary) &&
1456
+ Object.hasOwn(boundary, "verificationInputs")
1457
+ );
1458
+ }) ||
1459
+ (phases.refactor !== undefined &&
1460
+ (!phases.refactor ||
1461
+ typeof phases.refactor !== "object" ||
1462
+ Array.isArray(phases.refactor) ||
1463
+ !Object.hasOwn(phases.refactor, "verificationInputs"))) ||
1464
+ !validateTaskBoundary({ changeId: value.changeId, ...task }, undefined)
1465
+ ) {
1466
+ return {
1467
+ ok: false,
1468
+ reason: "invalid Implement task boundary",
1469
+ diagnostic: { code: "invalid-implement-graph" },
1470
+ };
1471
+ }
1472
+ }
1473
+
1474
+ for (const candidate of value.outputs) {
1475
+ if (
1476
+ !candidate ||
1477
+ typeof candidate !== "object" ||
1478
+ Array.isArray(candidate)
1479
+ ) {
1480
+ return {
1481
+ ok: false,
1482
+ reason: "invalid Implement graph output",
1483
+ diagnostic: { code: "invalid-implement-graph" },
1484
+ };
1485
+ }
1486
+ const output = candidate as Record<string, unknown>;
1487
+ if (!isValidRelativePath(output.path)) {
1488
+ return {
1489
+ ok: false,
1490
+ reason: "invalid Implement graph output path",
1491
+ diagnostic: {
1492
+ code: "invalid-output-path",
1493
+ ...(validIdentifier(output.id) ? { outputId: output.id } : {}),
1494
+ },
1495
+ };
1496
+ }
1497
+ if (
1498
+ !hasExactKeys(output, ["id", "path", "producer", "postcondition"]) ||
1499
+ !validIdentifier(output.id) ||
1500
+ !hasExactKeys(output.producer, ["taskId", "phase"]) ||
1501
+ !validIdentifier(output.producer.taskId) ||
1502
+ !(IMPLEMENTATION_PHASES as readonly unknown[]).includes(
1503
+ output.producer.phase,
1504
+ ) ||
1505
+ output.postcondition !== "regular-file"
1506
+ ) {
1507
+ return {
1508
+ ok: false,
1509
+ reason: "invalid Implement graph output",
1510
+ diagnostic: { code: "invalid-implement-graph" },
1511
+ };
1512
+ }
1354
1513
  }
1355
- return clone;
1514
+
1515
+ return {
1516
+ ok: true,
1517
+ value: structuredClone(value) as unknown as ImplementGraphBoundary,
1518
+ };
1356
1519
  }
1357
1520
 
1358
1521
  function validatePhaseAttemptShape(value: unknown): value is PhaseAttempt {
@@ -1412,7 +1575,19 @@ export function validatePhaseAttemptAgainstBoundary(
1412
1575
  ) {
1413
1576
  return "write snapshot must bind a regular file or absent path";
1414
1577
  }
1415
- return null;
1578
+ const phases = [
1579
+ boundary.phases.red,
1580
+ boundary.phases.green,
1581
+ ...(boundary.phases.refactor ? [boundary.phases.refactor] : []),
1582
+ ];
1583
+ return validateImpactClosure(
1584
+ boundary.impactClosure,
1585
+ {
1586
+ read: [...new Set(phases.flatMap((entry) => entry.read))],
1587
+ write: [...new Set(phases.flatMap((entry) => entry.write))],
1588
+ },
1589
+ attempt.snapshot,
1590
+ );
1416
1591
  }
1417
1592
 
1418
1593
  function validateImplementRunRequest(
@@ -1429,53 +1604,56 @@ function validateImplementRunRequest(
1429
1604
  return { ok: false, reason: "missing request envelope" };
1430
1605
  }
1431
1606
  const request = value as Record<string, unknown>;
1432
- if (request.kind === "open-task") {
1607
+ if (request.kind === "admit-graph") {
1608
+ const state = request.state as Record<string, unknown>;
1433
1609
  if (
1434
- request.attempt &&
1435
- typeof request.attempt === "object" &&
1436
- !Array.isArray(request.attempt) &&
1437
- !Object.hasOwn(request.attempt, "snapshot")
1610
+ !hasExactKeys(request, [
1611
+ "stage",
1612
+ "kind",
1613
+ "graph",
1614
+ "graphHash",
1615
+ "state",
1616
+ ]) ||
1617
+ typeof request.graphHash !== "string" ||
1618
+ !SNAPSHOT_SHA256.test(request.graphHash) ||
1619
+ !hasExactKeys(state, ["completedTasks", "blockedTasks"]) ||
1620
+ !validateIdentifierSet(state.completedTasks) ||
1621
+ !validateIdentifierSet(state.blockedTasks)
1438
1622
  ) {
1439
- return { ok: false, reason: "invalid snapshot" };
1623
+ return { ok: false, reason: "invalid Implement graph admission" };
1624
+ }
1625
+ const completedTasks = state.completedTasks as string[];
1626
+ const blockedTasks = state.blockedTasks as string[];
1627
+ if (completedTasks.some((taskId) => blockedTasks.includes(taskId))) {
1628
+ return { ok: false, reason: "invalid Implement graph admission" };
1440
1629
  }
1630
+ const graph = validateImplementGraphBoundary(request.graph);
1631
+ if (!graph.ok) return { ok: false, reason: graph.reason };
1632
+ const taskIds = new Set(graph.value.tasks.map((task) => task.taskId));
1441
1633
  if (
1442
- !hasExactKeys(request, ["stage", "kind", "boundary", "attempt"]) ||
1443
- !validatePhaseAttemptShape(request.attempt) ||
1444
- !validateTaskBoundary(request.boundary, request.attempt.snapshot)
1634
+ [...completedTasks, ...blockedTasks].some(
1635
+ (taskId) => !taskIds.has(taskId),
1636
+ )
1445
1637
  ) {
1446
- return { ok: false, reason: "invalid task open contract" };
1447
- }
1448
- const boundary = request.boundary as TaskBoundary;
1449
- const attempt = request.attempt as PhaseAttempt;
1450
- const attemptReason = validatePhaseAttemptAgainstBoundary(
1451
- boundary,
1452
- attempt,
1453
- );
1454
- if (attempt.phase !== "red" || attemptReason !== null) {
1455
- return {
1456
- ok: false,
1457
- reason:
1458
- attempt.phase !== "red"
1459
- ? "task open must begin with Red"
1460
- : (attemptReason ?? "invalid phase attempt"),
1461
- };
1638
+ return { ok: false, reason: "unknown Implement graph task state" };
1462
1639
  }
1463
1640
  return {
1464
1641
  ok: true,
1465
1642
  value: {
1466
1643
  stage: "abel-implement",
1467
- kind: "open-task",
1468
- boundary: normalizedTaskBoundary(boundary),
1469
- attempt: structuredClone(attempt),
1644
+ kind: "admit-graph",
1645
+ graph: graph.value,
1646
+ graphHash: request.graphHash,
1647
+ state: structuredClone({ completedTasks, blockedTasks }),
1470
1648
  },
1471
1649
  };
1472
1650
  }
1473
- if (request.kind === "phase-attempt") {
1651
+ if (request.kind === "task-attempt") {
1474
1652
  if (
1475
1653
  !hasExactKeys(request, ["stage", "kind", "attempt"]) ||
1476
1654
  !validatePhaseAttemptShape(request.attempt)
1477
1655
  ) {
1478
- return { ok: false, reason: "invalid phase attempt contract" };
1656
+ return { ok: false, reason: "invalid task attempt contract" };
1479
1657
  }
1480
1658
  return {
1481
1659
  ok: true,
@@ -1496,10 +1674,10 @@ export function validateRequestEnvelope(
1496
1674
  ) {
1497
1675
  return validateImplementRunRequest(value);
1498
1676
  }
1499
- return validateLegacyRequestEnvelope(value);
1677
+ return validateNonImplementRequestEnvelope(value);
1500
1678
  }
1501
1679
 
1502
- function validateLegacyRequestEnvelope(
1680
+ function validateNonImplementRequestEnvelope(
1503
1681
  value: unknown,
1504
1682
  ): { ok: true; value: RequestEnvelope } | { ok: false; reason: string } {
1505
1683
  if (value === null || typeof value !== "object") {
@@ -1945,6 +2123,26 @@ export interface AgentsCheckpointResult {
1945
2123
  }
1946
2124
 
1947
2125
  export type ImplementOutcome =
2126
+ | {
2127
+ kind: "graph-admitted";
2128
+ changeId: string;
2129
+ graphHash: string;
2130
+ readyTasks: string[];
2131
+ blockedTasks: string[];
2132
+ }
2133
+ | {
2134
+ kind: "graph-rejected";
2135
+ changeId: string;
2136
+ graphHash: string;
2137
+ diagnostics: ImplementGraphReadinessDiagnostic[];
2138
+ }
2139
+ | {
2140
+ kind: "dependency-blocked";
2141
+ requestId: string;
2142
+ taskId: string;
2143
+ phase: ImplementationPhase;
2144
+ diagnostics: ImplementGraphReadinessDiagnostic[];
2145
+ }
1948
2146
  | {
1949
2147
  kind: "deferred";
1950
2148
  requestId: string;
@@ -1982,6 +2180,7 @@ export type ImplementOutcome =
1982
2180
  phase: ImplementationPhase;
1983
2181
  cause: "artifact" | "stale";
1984
2182
  remainingAttempts: 1;
2183
+ lastFailure: SafeFailureDiagnostic;
1985
2184
  }
1986
2185
  | {
1987
2186
  kind: "completed";
@@ -1996,6 +2195,7 @@ export type ImplementOutcome =
1996
2195
  taskId: string;
1997
2196
  phase: ImplementationPhase;
1998
2197
  failure: TaskFailure;
2198
+ result?: ApplyResult;
1999
2199
  }
2000
2200
  | {
2001
2201
  kind: "cancelled";
@@ -2240,7 +2440,15 @@ export function validateDiffResult(value: unknown): {
2240
2440
  try {
2241
2441
  paths = diffWritePaths(r.diff as string).paths;
2242
2442
  } catch (err) {
2243
- return { ok: false, reason: `invalid diff: ${(err as Error).message}` };
2443
+ return {
2444
+ ok: false,
2445
+ reason: `invalid diff: ${(err as Error).message}`,
2446
+ failure: {
2447
+ kind: "artifact",
2448
+ code: "invalid-diff",
2449
+ stage: "candidate-diff",
2450
+ },
2451
+ };
2244
2452
  }
2245
2453
  const serialized = JSON.stringify(r);
2246
2454
  if (Buffer.byteLength(serialized, "utf8") > LIMITS.maxCompleteResultBytes) {