@abelxiaoxing/cadence 1.0.14 → 1.1.1

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 (48) hide show
  1. package/README.md +39 -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 +206 -228
  10. package/prompts/abel-diagnose.md +39 -18
  11. package/prompts/abel-implement.md +122 -178
  12. package/prompts/abel-init.md +36 -33
  13. package/skills/abel-workflow/SKILL.md +190 -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 +121 -696
  18. package/src/control-contracts.ts +246 -0
  19. package/src/delivery-compiler.ts +1118 -0
  20. package/src/design-control.ts +846 -0
  21. package/src/design-journal.ts +1569 -0
  22. package/src/file-snapshot.ts +5 -0
  23. package/src/implement-graph.ts +11 -4
  24. package/src/index.ts +1833 -118
  25. package/src/isolation-backend.ts +269 -0
  26. package/src/packet-runtime.ts +533 -0
  27. package/src/parent-provider.ts +23 -4
  28. package/src/route-policy.ts +483 -0
  29. package/src/run-state.ts +430 -0
  30. package/src/run-store.ts +990 -0
  31. package/src/safe-path.ts +11 -0
  32. package/src/scoped-tools.ts +36 -0
  33. package/src/state-root.ts +215 -0
  34. package/src/subagent-activity.ts +526 -27
  35. package/src/submit-tool.ts +274 -0
  36. package/src/task-ledger.ts +1380 -0
  37. package/src/verification-capability.ts +560 -45
  38. package/src/worker-broker.ts +703 -0
  39. package/src/workflow-engine.ts +7706 -0
  40. package/src/workspace-store.ts +544 -0
  41. package/src/candidate-preflight.ts +0 -1618
  42. package/src/drain.ts +0 -20
  43. package/src/patch.ts +0 -505
  44. package/src/result-store.ts +0 -312
  45. package/src/runtime.ts +0 -2436
  46. package/src/scheduler.ts +0 -384
  47. package/src/subagent-endpoint.ts +0 -158
  48. package/src/worker.ts +0 -204
package/src/contracts.ts CHANGED
@@ -1,5 +1,20 @@
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_STAGES,
14
+ controlRunKey,
15
+ validateControlCommand,
16
+ } from "./control-contracts.ts";
17
+
3
18
  export const STAGES = [
4
19
  "abel-design",
5
20
  "abel-implement",
@@ -11,7 +26,6 @@ export const ROLES = [
11
26
  "implementation-worker",
12
27
  "diagnosis-worker",
13
28
  ] as const;
14
- export const ACTIONS = ["run", "apply", "discard", "cancel", "finish"] as const;
15
29
  export const OUTPUT_KINDS = ["evidence", "diff"] as const;
16
30
  export const PHASES = [
17
31
  "evidence",
@@ -40,7 +54,6 @@ export type ImpactSurface = (typeof IMPACT_SURFACES)[number];
40
54
 
41
55
  export const LIMITS = {
42
56
  maxActiveChildSessions: 4,
43
- maxRequestsPerBatch: 8,
44
57
  maxEnvelopeBytes: 64 * 1024,
45
58
  phaseTimeoutMs: 20 * 60 * 1000,
46
59
  maxCompleteResultBytes: 64 * 1024,
@@ -132,8 +145,10 @@ export const APPROVAL_BOUNDARY_CODES = [
132
145
  "behavior-contract-insufficient",
133
146
  "architecture-contract-insufficient",
134
147
  "task-scope-insufficient",
148
+ "conflict-resource-authority-insufficient",
135
149
  "verification-contract-insufficient",
136
150
  "agents-contract-insufficient",
151
+ "irreversible-scope-insufficient",
137
152
  ] as const;
138
153
  export const VERIFICATION_ADAPTER_CODES = [
139
154
  "input-missing",
@@ -159,7 +174,6 @@ export const FAILURE_STAGES = [
159
174
  "structural-submit",
160
175
  "candidate-retention",
161
176
  "candidate-diff",
162
- "candidate-preflight",
163
177
  "parent-review",
164
178
  "candidate-apply",
165
179
  "agents-checkpoint",
@@ -205,12 +219,6 @@ export interface SafeFailureDetails {
205
219
  verificationId?: string;
206
220
  }
207
221
 
208
- export interface SafeFailureDiagnostic {
209
- code: ArtifactFailureCode | StaleFailureCode | ChildTransportCode;
210
- stage: FailureStage;
211
- details?: SafeFailureDetails;
212
- }
213
-
214
222
  export type EnvironmentFailure = {
215
223
  kind: "environment";
216
224
  code: EnvironmentFailureCode;
@@ -241,48 +249,7 @@ export type ChildFailure =
241
249
  details?: SafeFailureDetails;
242
250
  };
243
251
 
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
252
  const NONCANONICAL = /(^|\/)\.\.(\/|$)|(^|\/)\/|^\//;
285
- const SNAPSHOT_SHA256 = /^[a-f0-9]{64}$/;
286
253
 
287
254
  export type VerificationClassification =
288
255
  | "expected-red"
@@ -371,6 +338,7 @@ export type VerificationInputBinding =
371
338
  export interface PhaseBoundary {
372
339
  read: string[];
373
340
  write: string[];
341
+ delete: string[];
374
342
  verification: VerificationContract;
375
343
  verificationInputs: VerificationInputBinding[];
376
344
  verificationLock?: string;
@@ -469,32 +437,10 @@ export type ImplementGraphReadinessDiagnostic =
469
437
  verificationId?: string;
470
438
  } & ImplementGraphDiagnosticDetails);
471
439
 
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;
440
+ export interface PacketEnvelope {
441
+ stage: "abel-design" | "abel-diagnose";
442
+ role: "design-explorer" | "diagnosis-worker";
443
+ runId?: string;
498
444
  id: string;
499
445
  phase: string;
500
446
  objective: string;
@@ -507,44 +453,7 @@ export interface RequestEnvelope {
507
453
  resources: string[];
508
454
  verificationLock?: string;
509
455
  };
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;
456
+ output: "evidence" | "diff";
548
457
  }
549
458
 
550
459
  export function isValidRelativePath(p: unknown): p is string {
@@ -572,6 +481,7 @@ export function isAgentsPath(p: unknown): p is string {
572
481
  const UNSAFE_VERIFICATION_TOKEN = /[;&|`$<>\n\r\0]/u;
573
482
  const VERIFICATION_NAME = /^[a-z0-9][a-z0-9._:@/-]*$/iu;
574
483
  const EXECUTABLE_NAME = /^[a-z0-9][a-z0-9._-]*$/iu;
484
+ const IDENTIFIER = /^[a-z0-9](?:[a-z0-9._:-]{0,126}[a-z0-9])?$/iu;
575
485
 
576
486
  function validVerificationToken(value: unknown): value is string {
577
487
  const optionValue =
@@ -615,9 +525,7 @@ function validScriptCommand(value: unknown): value is string {
615
525
 
616
526
  function validVerificationIdentity(contract: Record<string, unknown>): boolean {
617
527
  if (
618
- typeof contract.id !== "string" ||
619
- contract.id.length === 0 ||
620
- contract.id.length > 128 ||
528
+ !validIdentifier(contract.id) ||
621
529
  !["expected-red", "expected-green", "expected-refactor"].includes(
622
530
  String(contract.classification),
623
531
  )
@@ -773,9 +681,7 @@ function validateStructuredVerification(
773
681
  }
774
682
  if (
775
683
  !hasExactKeys(contract, ["kind", "id", "classification", "steps"]) ||
776
- typeof contract.id !== "string" ||
777
- contract.id.length === 0 ||
778
- contract.id.length > 128 ||
684
+ !validIdentifier(contract.id) ||
779
685
  !["expected-red", "expected-green", "expected-refactor"].includes(
780
686
  String(contract.classification),
781
687
  ) ||
@@ -852,77 +758,6 @@ export function cloneVerificationContract(
852
758
  return structuredClone(normalized.value);
853
759
  }
854
760
 
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
761
  function validateApprovedDependencies(value: unknown): string | null {
927
762
  if (
928
763
  !Array.isArray(value) ||
@@ -1053,140 +888,6 @@ function isTestPath(path: string): boolean {
1053
888
  return /^(?:test|tests)\//u.test(path);
1054
889
  }
1055
890
 
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
891
  function hasExactKeys(
1191
892
  value: unknown,
1192
893
  required: readonly string[],
@@ -1202,7 +903,7 @@ function hasExactKeys(
1202
903
  }
1203
904
 
1204
905
  function validIdentifier(value: unknown): value is string {
1205
- return typeof value === "string" && value.length > 0 && value.length <= 128;
906
+ return typeof value === "string" && IDENTIFIER.test(value);
1206
907
  }
1207
908
 
1208
909
  function validatePathSet(value: unknown): value is string[] {
@@ -1286,7 +987,7 @@ function validatePhaseBoundary(
1286
987
  if (
1287
988
  !hasExactKeys(
1288
989
  value,
1289
- ["read", "write", "verification", "verificationInputs"],
990
+ ["read", "write", "delete", "verification", "verificationInputs"],
1290
991
  ["verificationLock"],
1291
992
  )
1292
993
  ) {
@@ -1295,8 +996,11 @@ function validatePhaseBoundary(
1295
996
  if (
1296
997
  !validatePathSet(value.read) ||
1297
998
  !validatePathSet(value.write) ||
999
+ !validatePathSet(value.delete) ||
1298
1000
  !validateVerificationInputBindings(value.verificationInputs) ||
1299
1001
  value.write.some(isAgentsPath) ||
1002
+ value.delete.some(isAgentsPath) ||
1003
+ value.write.some((path) => (value.delete as string[]).includes(path)) ||
1300
1004
  (value.verificationLock !== undefined &&
1301
1005
  !isValidRelativePath(value.verificationLock))
1302
1006
  ) {
@@ -1375,6 +1079,7 @@ function validateTaskBoundary(
1375
1079
  [
1376
1080
  ...phase.read,
1377
1081
  ...phase.write,
1082
+ ...phase.delete,
1378
1083
  ...verificationInputPaths(phase.verification),
1379
1084
  ].some((path) => !isWithinRoots(path, roots)),
1380
1085
  )
@@ -1394,7 +1099,9 @@ function validateTaskBoundary(
1394
1099
  }
1395
1100
  const declared = {
1396
1101
  read: [...new Set(phases.flatMap((entry) => entry.read))],
1397
- write: [...new Set(phases.flatMap((entry) => entry.write))],
1102
+ write: [
1103
+ ...new Set(phases.flatMap((entry) => [...entry.write, ...entry.delete])),
1104
+ ],
1398
1105
  };
1399
1106
  return (
1400
1107
  validateImpactClosure(value.impactClosure, declared, snapshot) === null
@@ -1518,173 +1225,14 @@ export function validateImplementGraphBoundary(value: unknown):
1518
1225
  };
1519
1226
  }
1520
1227
 
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(
1228
+ export function validatePacketEnvelope(
1594
1229
  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)) {
1230
+ ): { ok: true; value: PacketEnvelope } | { ok: false; reason: string } {
1231
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
1604
1232
  return { ok: false, reason: "missing request envelope" };
1605
1233
  }
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(
1681
- value: unknown,
1682
- ): { ok: true; value: RequestEnvelope } | { ok: false; reason: string } {
1683
- if (value === null || typeof value !== "object") {
1684
- return { ok: false, reason: "missing request envelope" };
1685
- }
1686
- const env = value as Record<string, unknown>;
1687
- for (const field of [
1234
+ const packet = value as Record<string, unknown>;
1235
+ const baseRequired = [
1688
1236
  "stage",
1689
1237
  "role",
1690
1238
  "id",
@@ -1694,48 +1242,68 @@ function validateNonImplementRequestEnvelope(
1694
1242
  "context",
1695
1243
  "declared",
1696
1244
  "output",
1697
- ]) {
1698
- if (env[field] === undefined) {
1245
+ ] as const;
1246
+ const required =
1247
+ packet.stage === "abel-design"
1248
+ ? ([
1249
+ ...baseRequired.slice(0, 2),
1250
+ "runId",
1251
+ ...baseRequired.slice(2),
1252
+ ] as const)
1253
+ : baseRequired;
1254
+ for (const field of required) {
1255
+ if (packet[field] === undefined) {
1699
1256
  return { ok: false, reason: `missing required field: ${field}` };
1700
1257
  }
1701
1258
  }
1702
- if (!(STAGES as readonly string[]).includes(env.stage as string)) {
1703
- return { ok: false, reason: `unknown stage: ${String(env.stage)}` };
1259
+ if (!hasExactKeys(packet, required)) {
1260
+ return { ok: false, reason: "invalid packet fields" };
1704
1261
  }
1705
- if (!(ROLES as readonly string[]).includes(env.role as string)) {
1706
- return { ok: false, reason: `unknown role: ${String(env.role)}` };
1262
+
1263
+ if (packet.stage !== "abel-design" && packet.stage !== "abel-diagnose") {
1264
+ return {
1265
+ ok: false,
1266
+ reason: `unsupported packet stage: ${String(packet.stage)}`,
1267
+ };
1707
1268
  }
1708
1269
  if (
1709
- typeof env.id !== "string" ||
1710
- env.id.length === 0 ||
1711
- env.id.length > 128
1270
+ (packet.stage === "abel-design" && packet.role !== "design-explorer") ||
1271
+ (packet.stage === "abel-diagnose" && packet.role !== "diagnosis-worker")
1712
1272
  ) {
1713
- return { ok: false, reason: "invalid request id" };
1273
+ return { ok: false, reason: "packet role does not match stage" };
1274
+ }
1275
+ if (
1276
+ (packet.stage === "abel-design" && !validIdentifier(packet.runId)) ||
1277
+ (packet.stage === "abel-diagnose" && packet.runId !== undefined)
1278
+ ) {
1279
+ return { ok: false, reason: "invalid Design run identity" };
1714
1280
  }
1715
1281
  if (
1716
- env.taskId !== undefined &&
1717
- (typeof env.taskId !== "string" ||
1718
- env.taskId.length === 0 ||
1719
- env.taskId.length > 128)
1282
+ typeof packet.id !== "string" ||
1283
+ packet.id.length === 0 ||
1284
+ packet.id.length > 128
1720
1285
  ) {
1721
- return { ok: false, reason: "invalid task id" };
1286
+ return { ok: false, reason: "invalid request id" };
1722
1287
  }
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
1288
  if (
1726
- env.stage === "abel-implement" &&
1727
- env.output === "diff" &&
1728
- env.taskId === undefined
1289
+ typeof packet.phase !== "string" ||
1290
+ !(PHASES as readonly string[]).includes(packet.phase)
1729
1291
  ) {
1730
- return { ok: false, reason: "missing required field: taskId" };
1292
+ return { ok: false, reason: `invalid phase: ${String(packet.phase)}` };
1731
1293
  }
1732
1294
  if (
1733
- typeof env.phase !== "string" ||
1734
- !(PHASES as readonly string[]).includes(env.phase)
1295
+ typeof packet.objective !== "string" ||
1296
+ packet.objective.length === 0 ||
1297
+ packet.objective.length > 4096
1735
1298
  ) {
1736
- return { ok: false, reason: `invalid phase: ${String(env.phase)}` };
1299
+ return { ok: false, reason: "invalid objective" };
1300
+ }
1301
+ let serialized: string;
1302
+ try {
1303
+ serialized = JSON.stringify(packet);
1304
+ } catch {
1305
+ return { ok: false, reason: "request envelope is not serializable" };
1737
1306
  }
1738
- const serialized = JSON.stringify(env);
1739
1307
  if (serialized.length > LIMITS.maxEnvelopeBytes) {
1740
1308
  return {
1741
1309
  ok: false,
@@ -1743,112 +1311,66 @@ function validateNonImplementRequestEnvelope(
1743
1311
  };
1744
1312
  }
1745
1313
  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)
1314
+ !Array.isArray(packet.roots) ||
1315
+ packet.roots.length === 0 ||
1316
+ !packet.roots.every(isValidRelativePath)
1756
1317
  ) {
1757
1318
  return { ok: false, reason: "invalid path roots" };
1758
1319
  }
1759
- const ctx = env.context as Record<string, unknown>;
1760
1320
  if (
1761
- ctx === null ||
1762
- typeof ctx !== "object" ||
1763
- typeof ctx.agents !== "string" ||
1764
- typeof ctx.contract !== "string"
1321
+ !hasExactKeys(packet.context, ["agents", "contract"]) ||
1322
+ typeof packet.context.agents !== "string" ||
1323
+ typeof packet.context.contract !== "string"
1765
1324
  ) {
1766
1325
  return { ok: false, reason: "invalid context" };
1767
1326
  }
1768
- const decl = env.declared as Record<string, unknown>;
1769
- if (decl === null || typeof decl !== "object") {
1327
+ if (
1328
+ !hasExactKeys(
1329
+ packet.declared,
1330
+ ["read", "write", "conflicts", "resources"],
1331
+ ["verificationLock"],
1332
+ )
1333
+ ) {
1770
1334
  return { ok: false, reason: "invalid declared sets" };
1771
1335
  }
1772
- for (const key of ["read", "write", "conflicts", "resources"]) {
1336
+ for (const key of ["read", "write", "conflicts", "resources"] as const) {
1337
+ const entries = packet.declared[key];
1773
1338
  if (
1774
- !Array.isArray(decl[key]) ||
1775
- !(decl[key] as unknown[]).every(isValidRelativePath)
1339
+ !Array.isArray(entries) ||
1340
+ !entries.every(isValidRelativePath) ||
1341
+ new Set(entries).size !== entries.length
1776
1342
  ) {
1777
1343
  return { ok: false, reason: `invalid declared ${key} set` };
1778
1344
  }
1779
1345
  }
1780
- if ((decl.write as unknown[]).some(isAgentsPath)) {
1346
+ if ((packet.declared.write as unknown[]).some(isAgentsPath)) {
1781
1347
  return { ok: false, reason: "subagents cannot declare AGENTS writes" };
1782
1348
  }
1783
1349
  if (
1784
- decl.verificationLock !== undefined &&
1785
- !isValidRelativePath(decl.verificationLock)
1350
+ packet.declared.verificationLock !== undefined &&
1351
+ !isValidRelativePath(packet.declared.verificationLock)
1786
1352
  ) {
1787
1353
  return { ok: false, reason: "invalid verification lock" };
1788
1354
  }
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
- }
1355
+ if (!(OUTPUT_KINDS as readonly unknown[]).includes(packet.output)) {
1356
+ return {
1357
+ ok: false,
1358
+ reason: `invalid output kind: ${String(packet.output)}`,
1359
+ };
1844
1360
  }
1845
1361
  if (
1846
- env.snapshot !== undefined &&
1847
- (env.snapshot === null || typeof env.snapshot !== "object")
1362
+ packet.stage === "abel-design" &&
1363
+ (packet.phase !== "evidence" ||
1364
+ packet.output !== "evidence" ||
1365
+ (packet.declared.write as unknown[]).length !== 0)
1848
1366
  ) {
1849
- return { ok: false, reason: "invalid snapshot" };
1367
+ return { ok: false, reason: "invalid Design packet contract" };
1850
1368
  }
1851
- return { ok: true, value: env as unknown as RequestEnvelope };
1369
+
1370
+ return {
1371
+ ok: true,
1372
+ value: structuredClone(packet) as unknown as PacketEnvelope,
1373
+ };
1852
1374
  }
1853
1375
 
1854
1376
  /** Extract proposed write paths from ordinary unified diff header pairs. */
@@ -2107,103 +1629,6 @@ export interface DiffResult {
2107
1629
  contractCompliant: boolean;
2108
1630
  }
2109
1631
 
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
1632
  export function validateEvidenceResult(value: unknown): {
2208
1633
  ok: boolean;
2209
1634
  reason?: string;