@abelxiaoxing/cadence 1.0.14 → 1.1.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 (46) hide show
  1. package/README.md +29 -13
  2. package/agents/contract-reviewer.md +2 -2
  3. package/agents/design-explorer.md +1 -1
  4. package/agents/diagnosis-worker.md +6 -3
  5. package/agents/implementation-worker.md +14 -9
  6. package/config/.env.example +5 -17
  7. package/config/routes.example.json +42 -0
  8. package/package.json +3 -3
  9. package/prompts/abel-design.md +150 -228
  10. package/prompts/abel-diagnose.md +37 -18
  11. package/prompts/abel-implement.md +111 -178
  12. package/prompts/abel-init.md +36 -33
  13. package/skills/abel-workflow/SKILL.md +156 -168
  14. package/src/apply-transaction.ts +1427 -0
  15. package/src/artifact-store.ts +204 -0
  16. package/src/child-session.ts +136 -17
  17. package/src/contracts.ts +105 -696
  18. package/src/control-contracts.ts +259 -0
  19. package/src/delivery-compiler.ts +1088 -0
  20. package/src/file-snapshot.ts +5 -0
  21. package/src/implement-graph.ts +11 -4
  22. package/src/index.ts +1465 -133
  23. package/src/isolation-backend.ts +269 -0
  24. package/src/packet-runtime.ts +533 -0
  25. package/src/parent-provider.ts +23 -4
  26. package/src/route-policy.ts +483 -0
  27. package/src/run-state.ts +393 -0
  28. package/src/run-store.ts +918 -0
  29. package/src/safe-path.ts +11 -0
  30. package/src/scoped-tools.ts +36 -0
  31. package/src/state-root.ts +215 -0
  32. package/src/subagent-activity.ts +526 -27
  33. package/src/submit-tool.ts +274 -0
  34. package/src/task-ledger.ts +1380 -0
  35. package/src/verification-capability.ts +529 -42
  36. package/src/worker-broker.ts +703 -0
  37. package/src/workflow-engine.ts +7391 -0
  38. package/src/workspace-store.ts +544 -0
  39. package/src/candidate-preflight.ts +0 -1618
  40. package/src/drain.ts +0 -20
  41. package/src/patch.ts +0 -505
  42. package/src/result-store.ts +0 -312
  43. package/src/runtime.ts +0 -2436
  44. package/src/scheduler.ts +0 -384
  45. package/src/subagent-endpoint.ts +0 -158
  46. package/src/worker.ts +0 -204
package/src/contracts.ts CHANGED
@@ -1,5 +1,21 @@
1
1
  // Strict request/result contracts for the private orchestration kernel.
2
2
  // Structural validation only; no runtime dependency on a schema library.
3
+
4
+ export type {
5
+ ControlCommand,
6
+ ControlCommandName,
7
+ ControlCommandValidation,
8
+ ControlStage,
9
+ } from "./control-contracts.ts";
10
+ export {
11
+ assertControlCommand,
12
+ CONTROL_COMMANDS,
13
+ CONTROL_PROTOCOL_VERSION,
14
+ CONTROL_STAGES,
15
+ controlRunKey,
16
+ validateControlCommand,
17
+ } from "./control-contracts.ts";
18
+
3
19
  export const STAGES = [
4
20
  "abel-design",
5
21
  "abel-implement",
@@ -11,7 +27,6 @@ export const ROLES = [
11
27
  "implementation-worker",
12
28
  "diagnosis-worker",
13
29
  ] as const;
14
- export const ACTIONS = ["run", "apply", "discard", "cancel", "finish"] as const;
15
30
  export const OUTPUT_KINDS = ["evidence", "diff"] as const;
16
31
  export const PHASES = [
17
32
  "evidence",
@@ -40,7 +55,6 @@ export type ImpactSurface = (typeof IMPACT_SURFACES)[number];
40
55
 
41
56
  export const LIMITS = {
42
57
  maxActiveChildSessions: 4,
43
- maxRequestsPerBatch: 8,
44
58
  maxEnvelopeBytes: 64 * 1024,
45
59
  phaseTimeoutMs: 20 * 60 * 1000,
46
60
  maxCompleteResultBytes: 64 * 1024,
@@ -159,7 +173,6 @@ export const FAILURE_STAGES = [
159
173
  "structural-submit",
160
174
  "candidate-retention",
161
175
  "candidate-diff",
162
- "candidate-preflight",
163
176
  "parent-review",
164
177
  "candidate-apply",
165
178
  "agents-checkpoint",
@@ -205,12 +218,6 @@ export interface SafeFailureDetails {
205
218
  verificationId?: string;
206
219
  }
207
220
 
208
- export interface SafeFailureDiagnostic {
209
- code: ArtifactFailureCode | StaleFailureCode | ChildTransportCode;
210
- stage: FailureStage;
211
- details?: SafeFailureDetails;
212
- }
213
-
214
221
  export type EnvironmentFailure = {
215
222
  kind: "environment";
216
223
  code: EnvironmentFailureCode;
@@ -241,48 +248,7 @@ export type ChildFailure =
241
248
  details?: SafeFailureDetails;
242
249
  };
243
250
 
244
- export type CandidateRejection =
245
- | {
246
- kind: "artifact";
247
- code: "parent-review-rejected";
248
- stage: "parent-review";
249
- }
250
- | { kind: "approval-boundary"; code: ApprovalBoundaryCode };
251
-
252
- export type TaskFailure =
253
- | { kind: "approval-boundary"; code: ApprovalBoundaryCode }
254
- | { kind: "verification-adapter"; code: VerificationAdapterCode }
255
- | {
256
- kind: "graph-readiness";
257
- diagnostic: ImplementGraphReadinessDiagnostic;
258
- }
259
- | {
260
- kind: "attempts-exhausted";
261
- cause: "artifact" | "stale" | "transport";
262
- attemptsUsed: 2;
263
- lastFailure: SafeFailureDiagnostic;
264
- }
265
- | {
266
- kind: "checkpoint-attempts-exhausted";
267
- cause: "artifact" | "stale";
268
- attemptsUsed: 2;
269
- lastFailure: SafeFailureDiagnostic;
270
- }
271
- | EnvironmentFailure
272
- | { kind: "result-limit"; limitBytes: number };
273
-
274
- export interface CandidateApplyEvidence {
275
- targets: string[];
276
- checkExitCode: 0;
277
- applyExitCode: 0;
278
- }
279
-
280
- export type ApplyCandidateResult =
281
- | { ok: true; result: CandidateApplyEvidence }
282
- | { ok: false; failure: CandidateFailure };
283
-
284
251
  const NONCANONICAL = /(^|\/)\.\.(\/|$)|(^|\/)\/|^\//;
285
- const SNAPSHOT_SHA256 = /^[a-f0-9]{64}$/;
286
252
 
287
253
  export type VerificationClassification =
288
254
  | "expected-red"
@@ -371,6 +337,7 @@ export type VerificationInputBinding =
371
337
  export interface PhaseBoundary {
372
338
  read: string[];
373
339
  write: string[];
340
+ delete: string[];
374
341
  verification: VerificationContract;
375
342
  verificationInputs: VerificationInputBinding[];
376
343
  verificationLock?: string;
@@ -469,32 +436,9 @@ export type ImplementGraphReadinessDiagnostic =
469
436
  verificationId?: string;
470
437
  } & ImplementGraphDiagnosticDetails);
471
438
 
472
- export interface PhaseAttempt {
473
- changeId: string;
474
- taskId: string;
475
- requestId: string;
476
- phase: ImplementationPhase;
477
- snapshot: unknown;
478
- }
479
-
480
- export type ImplementRunRequest =
481
- | {
482
- stage: "abel-implement";
483
- kind: "admit-graph";
484
- graph: ImplementGraphBoundary;
485
- graphHash: string;
486
- state: { completedTasks: string[]; blockedTasks: string[] };
487
- }
488
- | {
489
- stage: "abel-implement";
490
- kind: "task-attempt";
491
- attempt: PhaseAttempt;
492
- };
493
-
494
- export interface RequestEnvelope {
495
- stage: string;
496
- role: string;
497
- taskId?: string;
439
+ export interface PacketEnvelope {
440
+ stage: "abel-design" | "abel-diagnose";
441
+ role: "design-explorer" | "diagnosis-worker";
498
442
  id: string;
499
443
  phase: string;
500
444
  objective: string;
@@ -507,44 +451,7 @@ export interface RequestEnvelope {
507
451
  resources: string[];
508
452
  verificationLock?: string;
509
453
  };
510
- output: string;
511
- agentsImpact?: AgentsImpact;
512
- agentsTarget?: string;
513
- agentsManagedOnly?: true;
514
- approvedDependencies?: string[];
515
- impactClosure?: ImpactClosureContract;
516
- verification?: VerificationContract;
517
- snapshot?: unknown;
518
- }
519
-
520
- export type RunRequest = RequestEnvelope | ImplementRunRequest;
521
-
522
- export interface AgentsCheckpointRequest {
523
- stage: "abel-implement";
524
- taskId: string;
525
- agentsImpact: Exclude<AgentsImpact, "none">;
526
- agentsTarget: string;
527
- agentsManagedOnly: true;
528
- stableCheckpoint: true;
529
- snapshot: unknown;
530
- diff: string;
531
- }
532
-
533
- export interface AgentsCheckpointAttempt {
534
- changeId: string;
535
- taskId: string;
536
- requestId: string;
537
- snapshot: unknown;
538
- diff: string;
539
- }
540
-
541
- export interface ImplementApplyOperation {
542
- resultId: string;
543
- requestId: string;
544
- }
545
-
546
- export interface ImplementDiscardOperation extends ImplementApplyOperation {
547
- rejection: CandidateRejection;
454
+ output: "evidence" | "diff";
548
455
  }
549
456
 
550
457
  export function isValidRelativePath(p: unknown): p is string {
@@ -572,6 +479,7 @@ export function isAgentsPath(p: unknown): p is string {
572
479
  const UNSAFE_VERIFICATION_TOKEN = /[;&|`$<>\n\r\0]/u;
573
480
  const VERIFICATION_NAME = /^[a-z0-9][a-z0-9._:@/-]*$/iu;
574
481
  const EXECUTABLE_NAME = /^[a-z0-9][a-z0-9._-]*$/iu;
482
+ const IDENTIFIER = /^[a-z0-9](?:[a-z0-9._:-]{0,126}[a-z0-9])?$/iu;
575
483
 
576
484
  function validVerificationToken(value: unknown): value is string {
577
485
  const optionValue =
@@ -615,9 +523,7 @@ function validScriptCommand(value: unknown): value is string {
615
523
 
616
524
  function validVerificationIdentity(contract: Record<string, unknown>): boolean {
617
525
  if (
618
- typeof contract.id !== "string" ||
619
- contract.id.length === 0 ||
620
- contract.id.length > 128 ||
526
+ !validIdentifier(contract.id) ||
621
527
  !["expected-red", "expected-green", "expected-refactor"].includes(
622
528
  String(contract.classification),
623
529
  )
@@ -773,9 +679,7 @@ function validateStructuredVerification(
773
679
  }
774
680
  if (
775
681
  !hasExactKeys(contract, ["kind", "id", "classification", "steps"]) ||
776
- typeof contract.id !== "string" ||
777
- contract.id.length === 0 ||
778
- contract.id.length > 128 ||
682
+ !validIdentifier(contract.id) ||
779
683
  !["expected-red", "expected-green", "expected-refactor"].includes(
780
684
  String(contract.classification),
781
685
  ) ||
@@ -852,77 +756,6 @@ export function cloneVerificationContract(
852
756
  return structuredClone(normalized.value);
853
757
  }
854
758
 
855
- function validateImplementationSnapshot(
856
- snapshot: unknown,
857
- paths: string[],
858
- ): string | null {
859
- if (
860
- snapshot === undefined ||
861
- snapshot === null ||
862
- typeof snapshot !== "object" ||
863
- Array.isArray(snapshot)
864
- ) {
865
- return "invalid snapshot";
866
- }
867
- const bounds = snapshot as Record<string, unknown>;
868
- for (const [path, value] of Object.entries(bounds)) {
869
- if (
870
- !isValidRelativePath(path) ||
871
- value === null ||
872
- typeof value !== "object" ||
873
- Array.isArray(value)
874
- ) {
875
- return "invalid snapshot";
876
- }
877
- const entry = value as Record<string, unknown>;
878
- if (entry.kind === "file") {
879
- if (
880
- !hasExactKeys(entry, ["kind", "sha256", "bytes"]) ||
881
- typeof entry.sha256 !== "string" ||
882
- !SNAPSHOT_SHA256.test(entry.sha256) ||
883
- typeof entry.bytes !== "number" ||
884
- !Number.isSafeInteger(entry.bytes) ||
885
- entry.bytes < 0
886
- ) {
887
- return "invalid snapshot";
888
- }
889
- } else if (entry.kind === "dir") {
890
- if (
891
- !hasExactKeys(entry, ["kind", "manifest"]) ||
892
- typeof entry.manifest !== "string" ||
893
- !SNAPSHOT_SHA256.test(entry.manifest)
894
- ) {
895
- return "invalid snapshot";
896
- }
897
- } else if (entry.kind === "absent") {
898
- if (!hasExactKeys(entry, ["kind", "absent"]) || entry.absent !== true) {
899
- return "invalid snapshot";
900
- }
901
- } else {
902
- return "invalid snapshot";
903
- }
904
- }
905
- if ([...new Set(paths)].some((path) => !Object.hasOwn(bounds, path))) {
906
- return "snapshot does not cover declared paths";
907
- }
908
- return null;
909
- }
910
-
911
- function validateAgentsContract(env: Record<string, unknown>): string | null {
912
- if (!(AGENTS_IMPACTS as readonly unknown[]).includes(env.agentsImpact)) {
913
- return "invalid AGENTS impact";
914
- }
915
- if (env.agentsManagedOnly !== true) return "AGENTS must be managed-only";
916
- if (env.agentsImpact === "none") {
917
- return env.agentsTarget === undefined
918
- ? null
919
- : "none AGENTS impact cannot declare a target";
920
- }
921
- return isAgentsPath(env.agentsTarget)
922
- ? null
923
- : "AGENTS impact requires an explicit AGENTS target";
924
- }
925
-
926
759
  function validateApprovedDependencies(value: unknown): string | null {
927
760
  if (
928
761
  !Array.isArray(value) ||
@@ -1053,140 +886,6 @@ function isTestPath(path: string): boolean {
1053
886
  return /^(?:test|tests)\//u.test(path);
1054
887
  }
1055
888
 
1056
- export function validateAgentsCheckpointRequest(
1057
- value: unknown,
1058
- ):
1059
- | { ok: true; value: AgentsCheckpointRequest }
1060
- | { ok: false; reason: string } {
1061
- if (!value || typeof value !== "object" || Array.isArray(value)) {
1062
- return { ok: false, reason: "missing AGENTS checkpoint request" };
1063
- }
1064
- const request = value as Record<string, unknown>;
1065
- if (
1066
- request.stage !== "abel-implement" ||
1067
- typeof request.taskId !== "string" ||
1068
- request.taskId.length === 0 ||
1069
- request.taskId.length > 128 ||
1070
- request.agentsImpact === "none" ||
1071
- !(AGENTS_IMPACTS as readonly unknown[]).includes(request.agentsImpact) ||
1072
- !isAgentsPath(request.agentsTarget) ||
1073
- request.agentsManagedOnly !== true ||
1074
- request.stableCheckpoint !== true ||
1075
- typeof request.diff !== "string" ||
1076
- request.diff.length === 0 ||
1077
- request.diff.length > LIMITS.maxCompleteResultBytes
1078
- ) {
1079
- return { ok: false, reason: "invalid AGENTS checkpoint contract" };
1080
- }
1081
- const snapshotReason = validateImplementationSnapshot(request.snapshot, [
1082
- request.agentsTarget,
1083
- ] as string[]);
1084
- if (snapshotReason !== null) {
1085
- return { ok: false, reason: snapshotReason };
1086
- }
1087
- return {
1088
- ok: true,
1089
- value: request as unknown as AgentsCheckpointRequest,
1090
- };
1091
- }
1092
-
1093
- export function validateAgentsCheckpointAttempt(
1094
- value: unknown,
1095
- ):
1096
- | { ok: true; value: AgentsCheckpointAttempt }
1097
- | { ok: false; reason: string } {
1098
- if (
1099
- !hasExactKeys(value, [
1100
- "changeId",
1101
- "taskId",
1102
- "requestId",
1103
- "snapshot",
1104
- "diff",
1105
- ]) ||
1106
- !validIdentifier(value.changeId) ||
1107
- !validIdentifier(value.taskId) ||
1108
- !validIdentifier(value.requestId) ||
1109
- typeof value.diff !== "string" ||
1110
- value.diff.length === 0 ||
1111
- value.diff.length > LIMITS.maxCompleteResultBytes ||
1112
- !value.snapshot ||
1113
- typeof value.snapshot !== "object" ||
1114
- Array.isArray(value.snapshot) ||
1115
- Object.keys(value.snapshot).length === 0 ||
1116
- validateImplementationSnapshot(
1117
- value.snapshot,
1118
- Object.keys(value.snapshot as Record<string, unknown>),
1119
- ) !== null
1120
- ) {
1121
- return { ok: false, reason: "invalid AGENTS checkpoint attempt" };
1122
- }
1123
- return {
1124
- ok: true,
1125
- value: structuredClone(value) as unknown as AgentsCheckpointAttempt,
1126
- };
1127
- }
1128
-
1129
- export function validateImplementApplyOperation(
1130
- value: unknown,
1131
- ):
1132
- | { ok: true; value: ImplementApplyOperation }
1133
- | { ok: false; reason: string } {
1134
- if (
1135
- !hasExactKeys(value, ["resultId", "requestId"]) ||
1136
- !validIdentifier(value.resultId) ||
1137
- !validIdentifier(value.requestId)
1138
- ) {
1139
- return { ok: false, reason: "invalid Implement apply operation" };
1140
- }
1141
- return {
1142
- ok: true,
1143
- value: { resultId: value.resultId, requestId: value.requestId },
1144
- };
1145
- }
1146
-
1147
- function validateCandidateRejection(
1148
- value: unknown,
1149
- ): value is CandidateRejection {
1150
- if (!value || typeof value !== "object" || Array.isArray(value)) {
1151
- return false;
1152
- }
1153
- const rejection = value as Record<string, unknown>;
1154
- if (rejection.kind === "approval-boundary") {
1155
- return (
1156
- hasExactKeys(rejection, ["kind", "code"]) &&
1157
- (APPROVAL_BOUNDARY_CODES as readonly unknown[]).includes(rejection.code)
1158
- );
1159
- }
1160
- if (
1161
- rejection.kind !== "artifact" ||
1162
- rejection.code !== "parent-review-rejected" ||
1163
- rejection.stage !== "parent-review" ||
1164
- !hasExactKeys(rejection, ["kind", "code", "stage"])
1165
- ) {
1166
- return false;
1167
- }
1168
- return true;
1169
- }
1170
-
1171
- export function validateImplementDiscardOperation(
1172
- value: unknown,
1173
- ):
1174
- | { ok: true; value: ImplementDiscardOperation }
1175
- | { ok: false; reason: string } {
1176
- if (
1177
- !hasExactKeys(value, ["resultId", "requestId", "rejection"]) ||
1178
- !validIdentifier(value.resultId) ||
1179
- !validIdentifier(value.requestId) ||
1180
- !validateCandidateRejection(value.rejection)
1181
- ) {
1182
- return { ok: false, reason: "invalid Implement discard operation" };
1183
- }
1184
- return {
1185
- ok: true,
1186
- value: structuredClone(value) as unknown as ImplementDiscardOperation,
1187
- };
1188
- }
1189
-
1190
889
  function hasExactKeys(
1191
890
  value: unknown,
1192
891
  required: readonly string[],
@@ -1202,7 +901,7 @@ function hasExactKeys(
1202
901
  }
1203
902
 
1204
903
  function validIdentifier(value: unknown): value is string {
1205
- return typeof value === "string" && value.length > 0 && value.length <= 128;
904
+ return typeof value === "string" && IDENTIFIER.test(value);
1206
905
  }
1207
906
 
1208
907
  function validatePathSet(value: unknown): value is string[] {
@@ -1286,7 +985,7 @@ function validatePhaseBoundary(
1286
985
  if (
1287
986
  !hasExactKeys(
1288
987
  value,
1289
- ["read", "write", "verification", "verificationInputs"],
988
+ ["read", "write", "delete", "verification", "verificationInputs"],
1290
989
  ["verificationLock"],
1291
990
  )
1292
991
  ) {
@@ -1295,8 +994,11 @@ function validatePhaseBoundary(
1295
994
  if (
1296
995
  !validatePathSet(value.read) ||
1297
996
  !validatePathSet(value.write) ||
997
+ !validatePathSet(value.delete) ||
1298
998
  !validateVerificationInputBindings(value.verificationInputs) ||
1299
999
  value.write.some(isAgentsPath) ||
1000
+ value.delete.some(isAgentsPath) ||
1001
+ value.write.some((path) => (value.delete as string[]).includes(path)) ||
1300
1002
  (value.verificationLock !== undefined &&
1301
1003
  !isValidRelativePath(value.verificationLock))
1302
1004
  ) {
@@ -1375,6 +1077,7 @@ function validateTaskBoundary(
1375
1077
  [
1376
1078
  ...phase.read,
1377
1079
  ...phase.write,
1080
+ ...phase.delete,
1378
1081
  ...verificationInputPaths(phase.verification),
1379
1082
  ].some((path) => !isWithinRoots(path, roots)),
1380
1083
  )
@@ -1394,7 +1097,9 @@ function validateTaskBoundary(
1394
1097
  }
1395
1098
  const declared = {
1396
1099
  read: [...new Set(phases.flatMap((entry) => entry.read))],
1397
- write: [...new Set(phases.flatMap((entry) => entry.write))],
1100
+ write: [
1101
+ ...new Set(phases.flatMap((entry) => [...entry.write, ...entry.delete])),
1102
+ ],
1398
1103
  };
1399
1104
  return (
1400
1105
  validateImpactClosure(value.impactClosure, declared, snapshot) === null
@@ -1518,173 +1223,14 @@ export function validateImplementGraphBoundary(value: unknown):
1518
1223
  };
1519
1224
  }
1520
1225
 
1521
- function validatePhaseAttemptShape(value: unknown): value is PhaseAttempt {
1522
- if (
1523
- !hasExactKeys(value, [
1524
- "changeId",
1525
- "taskId",
1526
- "requestId",
1527
- "phase",
1528
- "snapshot",
1529
- ]) ||
1530
- !validIdentifier(value.changeId) ||
1531
- !validIdentifier(value.taskId) ||
1532
- !validIdentifier(value.requestId) ||
1533
- !["red", "green", "refactor"].includes(String(value.phase))
1534
- ) {
1535
- return false;
1536
- }
1537
- if (
1538
- !value.snapshot ||
1539
- typeof value.snapshot !== "object" ||
1540
- Array.isArray(value.snapshot)
1541
- ) {
1542
- return false;
1543
- }
1544
- return (
1545
- validateImplementationSnapshot(
1546
- value.snapshot,
1547
- Object.keys(value.snapshot as Record<string, unknown>),
1548
- ) === null
1549
- );
1550
- }
1551
-
1552
- export function validatePhaseAttemptAgainstBoundary(
1553
- boundary: TaskBoundary,
1554
- attempt: PhaseAttempt,
1555
- ): string | null {
1556
- if (
1557
- attempt.changeId !== boundary.changeId ||
1558
- attempt.taskId !== boundary.taskId
1559
- ) {
1560
- return "task attempt identity mismatch";
1561
- }
1562
- const phase = boundary.phases[attempt.phase];
1563
- if (!phase) return "task attempt phase is not declared";
1564
- const snapshotReason = validateImplementationSnapshot(attempt.snapshot, [
1565
- ...phase.read,
1566
- ...phase.write,
1567
- ]);
1568
- if (snapshotReason !== null) return snapshotReason;
1569
- const bounds = attempt.snapshot as Record<string, { kind?: unknown }>;
1570
- if (
1571
- phase.write.some(
1572
- (path) =>
1573
- bounds[path]?.kind !== "file" && bounds[path]?.kind !== "absent",
1574
- )
1575
- ) {
1576
- return "write snapshot must bind a regular file or absent path";
1577
- }
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
- );
1591
- }
1592
-
1593
- function validateImplementRunRequest(
1594
- value: unknown,
1595
- ): { ok: true; value: ImplementRunRequest } | { ok: false; reason: string } {
1596
- const serialized = JSON.stringify(value);
1597
- if (serialized.length > LIMITS.maxEnvelopeBytes) {
1598
- return {
1599
- ok: false,
1600
- reason: `request envelope exceeds ${LIMITS.maxEnvelopeBytes / 1024} KiB`,
1601
- };
1602
- }
1603
- if (!value || typeof value !== "object" || Array.isArray(value)) {
1604
- return { ok: false, reason: "missing request envelope" };
1605
- }
1606
- const request = value as Record<string, unknown>;
1607
- if (request.kind === "admit-graph") {
1608
- const state = request.state as Record<string, unknown>;
1609
- if (
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)
1622
- ) {
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" };
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));
1633
- if (
1634
- [...completedTasks, ...blockedTasks].some(
1635
- (taskId) => !taskIds.has(taskId),
1636
- )
1637
- ) {
1638
- return { ok: false, reason: "unknown Implement graph task state" };
1639
- }
1640
- return {
1641
- ok: true,
1642
- value: {
1643
- stage: "abel-implement",
1644
- kind: "admit-graph",
1645
- graph: graph.value,
1646
- graphHash: request.graphHash,
1647
- state: structuredClone({ completedTasks, blockedTasks }),
1648
- },
1649
- };
1650
- }
1651
- if (request.kind === "task-attempt") {
1652
- if (
1653
- !hasExactKeys(request, ["stage", "kind", "attempt"]) ||
1654
- !validatePhaseAttemptShape(request.attempt)
1655
- ) {
1656
- return { ok: false, reason: "invalid task attempt contract" };
1657
- }
1658
- return {
1659
- ok: true,
1660
- value: structuredClone(request) as ImplementRunRequest,
1661
- };
1662
- }
1663
- return { ok: false, reason: "invalid Implement run kind" };
1664
- }
1665
-
1666
- export function validateRequestEnvelope(
1667
- value: unknown,
1668
- ): { ok: true; value: RunRequest } | { ok: false; reason: string } {
1669
- if (
1670
- value &&
1671
- typeof value === "object" &&
1672
- !Array.isArray(value) &&
1673
- (value as Record<string, unknown>).stage === "abel-implement"
1674
- ) {
1675
- return validateImplementRunRequest(value);
1676
- }
1677
- return validateNonImplementRequestEnvelope(value);
1678
- }
1679
-
1680
- function validateNonImplementRequestEnvelope(
1226
+ export function validatePacketEnvelope(
1681
1227
  value: unknown,
1682
- ): { ok: true; value: RequestEnvelope } | { ok: false; reason: string } {
1683
- if (value === null || typeof value !== "object") {
1228
+ ): { ok: true; value: PacketEnvelope } | { ok: false; reason: string } {
1229
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
1684
1230
  return { ok: false, reason: "missing request envelope" };
1685
1231
  }
1686
- const env = value as Record<string, unknown>;
1687
- for (const field of [
1232
+ const packet = value as Record<string, unknown>;
1233
+ const required = [
1688
1234
  "stage",
1689
1235
  "role",
1690
1236
  "id",
@@ -1694,48 +1240,54 @@ function validateNonImplementRequestEnvelope(
1694
1240
  "context",
1695
1241
  "declared",
1696
1242
  "output",
1697
- ]) {
1698
- if (env[field] === undefined) {
1243
+ ] as const;
1244
+ for (const field of required) {
1245
+ if (packet[field] === undefined) {
1699
1246
  return { ok: false, reason: `missing required field: ${field}` };
1700
1247
  }
1701
1248
  }
1702
- if (!(STAGES as readonly string[]).includes(env.stage as string)) {
1703
- return { ok: false, reason: `unknown stage: ${String(env.stage)}` };
1249
+ if (!hasExactKeys(packet, required)) {
1250
+ return { ok: false, reason: "invalid packet fields" };
1704
1251
  }
1705
- if (!(ROLES as readonly string[]).includes(env.role as string)) {
1706
- return { ok: false, reason: `unknown role: ${String(env.role)}` };
1252
+
1253
+ if (packet.stage !== "abel-design" && packet.stage !== "abel-diagnose") {
1254
+ return {
1255
+ ok: false,
1256
+ reason: `unsupported packet stage: ${String(packet.stage)}`,
1257
+ };
1707
1258
  }
1708
1259
  if (
1709
- typeof env.id !== "string" ||
1710
- env.id.length === 0 ||
1711
- env.id.length > 128
1260
+ (packet.stage === "abel-design" && packet.role !== "design-explorer") ||
1261
+ (packet.stage === "abel-diagnose" && packet.role !== "diagnosis-worker")
1712
1262
  ) {
1713
- return { ok: false, reason: "invalid request id" };
1263
+ return { ok: false, reason: "packet role does not match stage" };
1714
1264
  }
1715
1265
  if (
1716
- env.taskId !== undefined &&
1717
- (typeof env.taskId !== "string" ||
1718
- env.taskId.length === 0 ||
1719
- env.taskId.length > 128)
1266
+ typeof packet.id !== "string" ||
1267
+ packet.id.length === 0 ||
1268
+ packet.id.length > 128
1720
1269
  ) {
1721
- return { ok: false, reason: "invalid task id" };
1270
+ return { ok: false, reason: "invalid request id" };
1722
1271
  }
1723
- // Implementation phases use a phase-local request id and carry their stable
1724
- // task identity explicitly. Other packets keep id as task id.
1725
1272
  if (
1726
- env.stage === "abel-implement" &&
1727
- env.output === "diff" &&
1728
- env.taskId === undefined
1273
+ typeof packet.phase !== "string" ||
1274
+ !(PHASES as readonly string[]).includes(packet.phase)
1729
1275
  ) {
1730
- return { ok: false, reason: "missing required field: taskId" };
1276
+ return { ok: false, reason: `invalid phase: ${String(packet.phase)}` };
1731
1277
  }
1732
1278
  if (
1733
- typeof env.phase !== "string" ||
1734
- !(PHASES as readonly string[]).includes(env.phase)
1279
+ typeof packet.objective !== "string" ||
1280
+ packet.objective.length === 0 ||
1281
+ packet.objective.length > 4096
1735
1282
  ) {
1736
- return { ok: false, reason: `invalid phase: ${String(env.phase)}` };
1283
+ return { ok: false, reason: "invalid objective" };
1284
+ }
1285
+ let serialized: string;
1286
+ try {
1287
+ serialized = JSON.stringify(packet);
1288
+ } catch {
1289
+ return { ok: false, reason: "request envelope is not serializable" };
1737
1290
  }
1738
- const serialized = JSON.stringify(env);
1739
1291
  if (serialized.length > LIMITS.maxEnvelopeBytes) {
1740
1292
  return {
1741
1293
  ok: false,
@@ -1743,112 +1295,66 @@ function validateNonImplementRequestEnvelope(
1743
1295
  };
1744
1296
  }
1745
1297
  if (
1746
- typeof env.objective !== "string" ||
1747
- env.objective.length === 0 ||
1748
- env.objective.length > 4096
1749
- ) {
1750
- return { ok: false, reason: "invalid objective" };
1751
- }
1752
- if (
1753
- !Array.isArray(env.roots) ||
1754
- env.roots.length === 0 ||
1755
- !env.roots.every(isValidRelativePath)
1298
+ !Array.isArray(packet.roots) ||
1299
+ packet.roots.length === 0 ||
1300
+ !packet.roots.every(isValidRelativePath)
1756
1301
  ) {
1757
1302
  return { ok: false, reason: "invalid path roots" };
1758
1303
  }
1759
- const ctx = env.context as Record<string, unknown>;
1760
1304
  if (
1761
- ctx === null ||
1762
- typeof ctx !== "object" ||
1763
- typeof ctx.agents !== "string" ||
1764
- typeof ctx.contract !== "string"
1305
+ !hasExactKeys(packet.context, ["agents", "contract"]) ||
1306
+ typeof packet.context.agents !== "string" ||
1307
+ typeof packet.context.contract !== "string"
1765
1308
  ) {
1766
1309
  return { ok: false, reason: "invalid context" };
1767
1310
  }
1768
- const decl = env.declared as Record<string, unknown>;
1769
- if (decl === null || typeof decl !== "object") {
1311
+ if (
1312
+ !hasExactKeys(
1313
+ packet.declared,
1314
+ ["read", "write", "conflicts", "resources"],
1315
+ ["verificationLock"],
1316
+ )
1317
+ ) {
1770
1318
  return { ok: false, reason: "invalid declared sets" };
1771
1319
  }
1772
- for (const key of ["read", "write", "conflicts", "resources"]) {
1320
+ for (const key of ["read", "write", "conflicts", "resources"] as const) {
1321
+ const entries = packet.declared[key];
1773
1322
  if (
1774
- !Array.isArray(decl[key]) ||
1775
- !(decl[key] as unknown[]).every(isValidRelativePath)
1323
+ !Array.isArray(entries) ||
1324
+ !entries.every(isValidRelativePath) ||
1325
+ new Set(entries).size !== entries.length
1776
1326
  ) {
1777
1327
  return { ok: false, reason: `invalid declared ${key} set` };
1778
1328
  }
1779
1329
  }
1780
- if ((decl.write as unknown[]).some(isAgentsPath)) {
1330
+ if ((packet.declared.write as unknown[]).some(isAgentsPath)) {
1781
1331
  return { ok: false, reason: "subagents cannot declare AGENTS writes" };
1782
1332
  }
1783
1333
  if (
1784
- decl.verificationLock !== undefined &&
1785
- !isValidRelativePath(decl.verificationLock)
1334
+ packet.declared.verificationLock !== undefined &&
1335
+ !isValidRelativePath(packet.declared.verificationLock)
1786
1336
  ) {
1787
1337
  return { ok: false, reason: "invalid verification lock" };
1788
1338
  }
1789
- if (!(OUTPUT_KINDS as readonly string[]).includes(env.output as string)) {
1790
- return { ok: false, reason: `invalid output kind: ${String(env.output)}` };
1791
- }
1792
- if (env.verification !== undefined) {
1793
- const verification = validateVerificationContract(env.verification);
1794
- if (!verification.ok) {
1795
- return {
1796
- ok: false,
1797
- reason: verification.reason,
1798
- };
1799
- }
1800
- }
1801
- if (env.stage === "abel-implement" && env.output === "diff") {
1802
- const agentsReason = validateAgentsContract(env);
1803
- if (agentsReason !== null) return { ok: false, reason: agentsReason };
1804
- const dependenciesReason = validateApprovedDependencies(
1805
- env.approvedDependencies,
1806
- );
1807
- if (dependenciesReason !== null) {
1808
- return { ok: false, reason: dependenciesReason };
1809
- }
1810
- const closureReason = validateImpactClosure(
1811
- env.impactClosure,
1812
- decl,
1813
- env.snapshot,
1814
- );
1815
- if (closureReason !== null) return { ok: false, reason: closureReason };
1816
- if (env.verification === undefined) {
1817
- return { ok: false, reason: "missing required field: verification" };
1818
- }
1819
- const expectedClassification = {
1820
- red: "expected-red",
1821
- green: "expected-green",
1822
- refactor: "expected-refactor",
1823
- }[String(env.phase)];
1824
- if (
1825
- expectedClassification === undefined ||
1826
- (env.verification as VerificationContract).classification !==
1827
- expectedClassification
1828
- ) {
1829
- return {
1830
- ok: false,
1831
- reason:
1832
- "verification classification does not match implementation phase",
1833
- };
1834
- }
1835
- }
1836
- if (env.stage === "abel-implement" && env.output === "diff") {
1837
- const snapshotReason = validateImplementationSnapshot(env.snapshot, [
1838
- ...(decl.read as string[]),
1839
- ...(decl.write as string[]),
1840
- ]);
1841
- if (snapshotReason !== null) {
1842
- return { ok: false, reason: snapshotReason };
1843
- }
1339
+ if (!(OUTPUT_KINDS as readonly unknown[]).includes(packet.output)) {
1340
+ return {
1341
+ ok: false,
1342
+ reason: `invalid output kind: ${String(packet.output)}`,
1343
+ };
1844
1344
  }
1845
1345
  if (
1846
- env.snapshot !== undefined &&
1847
- (env.snapshot === null || typeof env.snapshot !== "object")
1346
+ packet.stage === "abel-design" &&
1347
+ (packet.phase !== "evidence" ||
1348
+ packet.output !== "evidence" ||
1349
+ (packet.declared.write as unknown[]).length !== 0)
1848
1350
  ) {
1849
- return { ok: false, reason: "invalid snapshot" };
1351
+ return { ok: false, reason: "invalid Design packet contract" };
1850
1352
  }
1851
- return { ok: true, value: env as unknown as RequestEnvelope };
1353
+
1354
+ return {
1355
+ ok: true,
1356
+ value: structuredClone(packet) as unknown as PacketEnvelope,
1357
+ };
1852
1358
  }
1853
1359
 
1854
1360
  /** Extract proposed write paths from ordinary unified diff header pairs. */
@@ -2107,103 +1613,6 @@ export interface DiffResult {
2107
1613
  contractCompliant: boolean;
2108
1614
  }
2109
1615
 
2110
- export interface ApplyResult {
2111
- targets: string[];
2112
- checkExitCode: 0;
2113
- applyExitCode: 0;
2114
- sequence?: number;
2115
- }
2116
-
2117
- export interface AgentsCheckpointResult {
2118
- target: string;
2119
- agentsImpact: Exclude<TaskBoundary["agents"]["impact"], "none">;
2120
- checkExitCode: 0;
2121
- applyExitCode: 0;
2122
- sequence?: number;
2123
- }
2124
-
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
- }
2146
- | {
2147
- kind: "deferred";
2148
- requestId: string;
2149
- taskId: string;
2150
- reason: "task-conflict";
2151
- }
2152
- | {
2153
- kind: "candidate";
2154
- requestId: string;
2155
- taskId: string;
2156
- phase: ImplementationPhase;
2157
- resultId: string;
2158
- result: DiffResult;
2159
- }
2160
- | {
2161
- kind: "applied";
2162
- requestId: string;
2163
- taskId: string;
2164
- phase: "red" | "green";
2165
- readyPhase: "green" | "refactor";
2166
- result: ApplyResult;
2167
- }
2168
- | {
2169
- kind: "checkpoint-required";
2170
- requestId: string;
2171
- taskId: string;
2172
- finalPhase: "green" | "refactor";
2173
- result: ApplyResult;
2174
- }
2175
- | {
2176
- kind: "retry";
2177
- requestId: string;
2178
- taskId: string;
2179
- scope: "worker" | "checkpoint";
2180
- phase: ImplementationPhase;
2181
- cause: "artifact" | "stale";
2182
- remainingAttempts: 1;
2183
- lastFailure: SafeFailureDiagnostic;
2184
- }
2185
- | {
2186
- kind: "completed";
2187
- requestId: string;
2188
- taskId: string;
2189
- finalPhase: "green" | "refactor";
2190
- result?: ApplyResult | AgentsCheckpointResult;
2191
- }
2192
- | {
2193
- kind: "blocked";
2194
- requestId: string;
2195
- taskId: string;
2196
- phase: ImplementationPhase;
2197
- failure: TaskFailure;
2198
- result?: ApplyResult;
2199
- }
2200
- | {
2201
- kind: "cancelled";
2202
- requestId: string;
2203
- taskId: string;
2204
- phase: ImplementationPhase;
2205
- };
2206
-
2207
1616
  export function validateEvidenceResult(value: unknown): {
2208
1617
  ok: boolean;
2209
1618
  reason?: string;