@abelxiaoxing/cadence 1.0.12 → 1.0.13
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/package.json +1 -1
- package/prompts/abel-design.md +19 -10
- package/prompts/abel-implement.md +19 -6
- package/skills/abel-workflow/SKILL.md +32 -10
- package/src/candidate-preflight.ts +9 -6
- package/src/child-session.ts +75 -25
- package/src/contracts.ts +438 -230
- package/src/file-snapshot.ts +11 -20
- package/src/implement-graph.ts +571 -0
- package/src/patch.ts +103 -21
- package/src/runtime.ts +445 -119
- package/src/safe-path.ts +66 -0
- package/src/submit-tool.ts +26 -13
- package/src/verification-capability.ts +52 -67
- package/src/worker.ts +49 -0
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
|
-
| {
|
|
183
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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: "
|
|
366
|
-
|
|
367
|
-
|
|
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: "
|
|
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
|
-
|
|
644
|
+
!validScriptCommand(value.command)
|
|
532
645
|
) {
|
|
533
646
|
return false;
|
|
534
647
|
}
|
|
535
|
-
return
|
|
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
|
|
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
|
|
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
|
|
609
|
-
validateVitestRunner(executableRunner
|
|
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
|
-
|
|
726
|
+
command: contract.command,
|
|
633
727
|
};
|
|
634
728
|
return (
|
|
635
|
-
hasExactKeys(
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 === "
|
|
1607
|
+
if (request.kind === "admit-graph") {
|
|
1608
|
+
const state = request.state as Record<string, unknown>;
|
|
1433
1609
|
if (
|
|
1434
|
-
request
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
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
|
|
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
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1634
|
+
[...completedTasks, ...blockedTasks].some(
|
|
1635
|
+
(taskId) => !taskIds.has(taskId),
|
|
1636
|
+
)
|
|
1445
1637
|
) {
|
|
1446
|
-
return { ok: false, reason: "
|
|
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: "
|
|
1468
|
-
|
|
1469
|
-
|
|
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 === "
|
|
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
|
|
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
|
|
1677
|
+
return validateNonImplementRequestEnvelope(value);
|
|
1500
1678
|
}
|
|
1501
1679
|
|
|
1502
|
-
function
|
|
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 {
|
|
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) {
|