@abelxiaoxing/cadence 1.2.1 → 1.2.3
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/README.md +7 -1
- package/agents/diagnosis-worker.md +6 -2
- package/agents/implementation-worker.md +9 -2
- package/config/routes.example.json +0 -1
- package/package.json +1 -1
- package/prompts/abel-design.md +15 -4
- package/skills/abel-workflow/SKILL.md +5 -3
- package/src/apply-transaction.ts +1 -5
- package/src/child-session.ts +37 -46
- package/src/contracts.ts +278 -32
- package/src/control-contracts.ts +62 -0
- package/src/delivery-compiler.ts +239 -124
- package/src/design-control.ts +72 -27
- package/src/design-journal.ts +2 -2
- package/src/file-snapshot.ts +1 -1
- package/src/index.ts +295 -58
- package/src/packet-runtime.ts +79 -21
- package/src/parent-provider.ts +63 -30
- package/src/route-policy.ts +58 -14
- package/src/run-store.ts +115 -23
- package/src/subagent-activity.ts +82 -6
- package/src/submit-tool.ts +431 -39
- package/src/task-ledger.ts +1 -2
- package/src/worker-broker.ts +72 -13
- package/src/workflow-engine.ts +782 -54
- package/src/workspace-store.ts +2 -10
package/src/contracts.ts
CHANGED
|
@@ -1102,8 +1102,258 @@ function validateTaskBoundary(
|
|
|
1102
1102
|
}
|
|
1103
1103
|
|
|
1104
1104
|
export type ImplementGraphContractDiagnostic =
|
|
1105
|
-
| {
|
|
1106
|
-
|
|
1105
|
+
| {
|
|
1106
|
+
code: "invalid-implement-graph";
|
|
1107
|
+
taskId?: string;
|
|
1108
|
+
phase?: ImplementationPhase;
|
|
1109
|
+
field?: string;
|
|
1110
|
+
category?: string;
|
|
1111
|
+
outputId?: string;
|
|
1112
|
+
}
|
|
1113
|
+
| {
|
|
1114
|
+
code: "invalid-output-path";
|
|
1115
|
+
outputId?: string;
|
|
1116
|
+
field: "outputs.path";
|
|
1117
|
+
category: "path";
|
|
1118
|
+
};
|
|
1119
|
+
|
|
1120
|
+
function phaseBoundaryContractDiagnostic(
|
|
1121
|
+
value: unknown,
|
|
1122
|
+
phase: ImplementationPhase,
|
|
1123
|
+
): Omit<
|
|
1124
|
+
Extract<
|
|
1125
|
+
ImplementGraphContractDiagnostic,
|
|
1126
|
+
{ code: "invalid-implement-graph" }
|
|
1127
|
+
>,
|
|
1128
|
+
"code"
|
|
1129
|
+
> | null {
|
|
1130
|
+
const prefix = `phases.${phase}`;
|
|
1131
|
+
if (
|
|
1132
|
+
!hasExactKeys(
|
|
1133
|
+
value,
|
|
1134
|
+
["read", "write", "delete", "verification", "verificationInputs"],
|
|
1135
|
+
["verificationLock"],
|
|
1136
|
+
)
|
|
1137
|
+
) {
|
|
1138
|
+
return { phase, field: prefix, category: "shape" };
|
|
1139
|
+
}
|
|
1140
|
+
if (!validatePathSet(value.read)) {
|
|
1141
|
+
return { phase, field: `${prefix}.read`, category: "path-set" };
|
|
1142
|
+
}
|
|
1143
|
+
if (!validatePathSet(value.write) || value.write.some(isAgentsPath)) {
|
|
1144
|
+
return { phase, field: `${prefix}.write`, category: "path-set" };
|
|
1145
|
+
}
|
|
1146
|
+
if (!validatePathSet(value.delete) || value.delete.some(isAgentsPath)) {
|
|
1147
|
+
return { phase, field: `${prefix}.delete`, category: "path-set" };
|
|
1148
|
+
}
|
|
1149
|
+
const deletions = value.delete as string[];
|
|
1150
|
+
if (value.write.some((candidate) => deletions.includes(candidate))) {
|
|
1151
|
+
return { phase, field: prefix, category: "write-delete-overlap" };
|
|
1152
|
+
}
|
|
1153
|
+
if (!validateVerificationInputBindings(value.verificationInputs)) {
|
|
1154
|
+
return {
|
|
1155
|
+
phase,
|
|
1156
|
+
field: `${prefix}.verificationInputs`,
|
|
1157
|
+
category: "verification-input-binding",
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
if (
|
|
1161
|
+
value.verificationLock !== undefined &&
|
|
1162
|
+
!isValidRelativePath(value.verificationLock)
|
|
1163
|
+
) {
|
|
1164
|
+
return {
|
|
1165
|
+
phase,
|
|
1166
|
+
field: `${prefix}.verificationLock`,
|
|
1167
|
+
category: "path",
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
const verification = validateVerificationContract(value.verification);
|
|
1171
|
+
if (!verification.ok) {
|
|
1172
|
+
return {
|
|
1173
|
+
phase,
|
|
1174
|
+
field: `${prefix}.verification`,
|
|
1175
|
+
category: "verification-contract",
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
const classification = {
|
|
1179
|
+
red: "expected-red",
|
|
1180
|
+
green: "expected-green",
|
|
1181
|
+
refactor: "expected-refactor",
|
|
1182
|
+
} as const;
|
|
1183
|
+
if (verification.value.classification !== classification[phase]) {
|
|
1184
|
+
return {
|
|
1185
|
+
phase,
|
|
1186
|
+
field: `${prefix}.verification.classification`,
|
|
1187
|
+
category: "verification-classification",
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
return null;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
function impactClosureCategory(reason: string): string {
|
|
1194
|
+
const categories: Record<string, string> = {
|
|
1195
|
+
"invalid impact closure": "shape",
|
|
1196
|
+
"invalid impact closure related test": "related-test",
|
|
1197
|
+
"current-task related test is outside the write set":
|
|
1198
|
+
"current-task-outside-write-set",
|
|
1199
|
+
"regression task identity is required": "regression-task-identity",
|
|
1200
|
+
"unexpected regression task identity": "regression-task-identity",
|
|
1201
|
+
"public impact closure is incomplete": "public-impact-incomplete",
|
|
1202
|
+
"affected suite contains no existing test evidence":
|
|
1203
|
+
"existing-test-evidence",
|
|
1204
|
+
};
|
|
1205
|
+
return categories[reason] ?? "contract";
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
function taskBoundaryContractDiagnostic(
|
|
1209
|
+
value: Record<string, unknown>,
|
|
1210
|
+
snapshot: unknown,
|
|
1211
|
+
): Omit<
|
|
1212
|
+
Extract<
|
|
1213
|
+
ImplementGraphContractDiagnostic,
|
|
1214
|
+
{ code: "invalid-implement-graph" }
|
|
1215
|
+
>,
|
|
1216
|
+
"code"
|
|
1217
|
+
> | null {
|
|
1218
|
+
const taskId = validIdentifier(value.taskId) ? value.taskId : undefined;
|
|
1219
|
+
const diagnostic = (
|
|
1220
|
+
field: string,
|
|
1221
|
+
category: string,
|
|
1222
|
+
phase?: ImplementationPhase,
|
|
1223
|
+
) => ({
|
|
1224
|
+
...(taskId ? { taskId } : {}),
|
|
1225
|
+
...(phase ? { phase } : {}),
|
|
1226
|
+
field,
|
|
1227
|
+
category,
|
|
1228
|
+
});
|
|
1229
|
+
if (
|
|
1230
|
+
!hasExactKeys(value, [
|
|
1231
|
+
"changeId",
|
|
1232
|
+
"taskId",
|
|
1233
|
+
"dependsOn",
|
|
1234
|
+
"objective",
|
|
1235
|
+
"context",
|
|
1236
|
+
"roots",
|
|
1237
|
+
"phases",
|
|
1238
|
+
"scheduling",
|
|
1239
|
+
"agents",
|
|
1240
|
+
"approvedDependencies",
|
|
1241
|
+
"impactClosure",
|
|
1242
|
+
])
|
|
1243
|
+
) {
|
|
1244
|
+
return diagnostic("task", "shape");
|
|
1245
|
+
}
|
|
1246
|
+
if (!validIdentifier(value.changeId)) {
|
|
1247
|
+
return diagnostic("changeId", "identifier");
|
|
1248
|
+
}
|
|
1249
|
+
if (!validIdentifier(value.taskId)) {
|
|
1250
|
+
return diagnostic("taskId", "identifier");
|
|
1251
|
+
}
|
|
1252
|
+
if (!validateIdentifierSet(value.dependsOn)) {
|
|
1253
|
+
return diagnostic("dependsOn", "identifier-set");
|
|
1254
|
+
}
|
|
1255
|
+
if (
|
|
1256
|
+
typeof value.objective !== "string" ||
|
|
1257
|
+
value.objective.length === 0 ||
|
|
1258
|
+
value.objective.length > 4096
|
|
1259
|
+
) {
|
|
1260
|
+
return diagnostic("objective", "text-boundary");
|
|
1261
|
+
}
|
|
1262
|
+
if (
|
|
1263
|
+
!hasExactKeys(value.context, ["agents", "contract"]) ||
|
|
1264
|
+
typeof value.context.agents !== "string" ||
|
|
1265
|
+
typeof value.context.contract !== "string"
|
|
1266
|
+
) {
|
|
1267
|
+
return diagnostic("context", "shape");
|
|
1268
|
+
}
|
|
1269
|
+
if (!validateRoots(value.roots)) {
|
|
1270
|
+
return diagnostic("roots", "path-set");
|
|
1271
|
+
}
|
|
1272
|
+
if (!hasExactKeys(value.phases, ["red", "green"], ["refactor"])) {
|
|
1273
|
+
return diagnostic("phases", "shape");
|
|
1274
|
+
}
|
|
1275
|
+
for (const phase of IMPLEMENTATION_PHASES) {
|
|
1276
|
+
const boundary = value.phases[phase];
|
|
1277
|
+
if (phase === "refactor" && boundary === undefined) continue;
|
|
1278
|
+
const phaseDiagnostic = phaseBoundaryContractDiagnostic(boundary, phase);
|
|
1279
|
+
if (phaseDiagnostic)
|
|
1280
|
+
return { ...diagnostic("phases", "contract"), ...phaseDiagnostic };
|
|
1281
|
+
}
|
|
1282
|
+
if (
|
|
1283
|
+
!hasExactKeys(value.scheduling, ["conflicts", "resources"]) ||
|
|
1284
|
+
!validatePathSet(value.scheduling.conflicts) ||
|
|
1285
|
+
!validatePathSet(value.scheduling.resources)
|
|
1286
|
+
) {
|
|
1287
|
+
return diagnostic("scheduling", "path-set");
|
|
1288
|
+
}
|
|
1289
|
+
if (
|
|
1290
|
+
!hasExactKeys(value.agents, ["impact", "managedOnly"], ["target"]) ||
|
|
1291
|
+
!(AGENTS_IMPACTS as readonly unknown[]).includes(value.agents.impact) ||
|
|
1292
|
+
value.agents.managedOnly !== true ||
|
|
1293
|
+
(value.agents.impact === "none"
|
|
1294
|
+
? value.agents.target !== undefined
|
|
1295
|
+
: !isAgentsPath(value.agents.target))
|
|
1296
|
+
) {
|
|
1297
|
+
return diagnostic("agents", "contract");
|
|
1298
|
+
}
|
|
1299
|
+
if (validateApprovedDependencies(value.approvedDependencies) !== null) {
|
|
1300
|
+
return diagnostic("approvedDependencies", "dependency-set");
|
|
1301
|
+
}
|
|
1302
|
+
const roots = value.roots as string[];
|
|
1303
|
+
for (const phase of IMPLEMENTATION_PHASES) {
|
|
1304
|
+
const boundary = value.phases[phase] as PhaseBoundary | undefined;
|
|
1305
|
+
if (!boundary) continue;
|
|
1306
|
+
for (const [field, candidates] of [
|
|
1307
|
+
["read", boundary.read],
|
|
1308
|
+
["write", boundary.write],
|
|
1309
|
+
["delete", boundary.delete],
|
|
1310
|
+
["verification", verificationInputPaths(boundary.verification)],
|
|
1311
|
+
] as const) {
|
|
1312
|
+
if (candidates.some((candidate) => !isWithinRoots(candidate, roots))) {
|
|
1313
|
+
return diagnostic(`phases.${phase}.${field}`, "outside-roots", phase);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
if (boundary.verification.kind !== undefined) {
|
|
1317
|
+
const declared = new Set([...boundary.read, ...boundary.write]);
|
|
1318
|
+
if (
|
|
1319
|
+
verificationInputPaths(boundary.verification).some(
|
|
1320
|
+
(input) => !declared.has(input),
|
|
1321
|
+
)
|
|
1322
|
+
) {
|
|
1323
|
+
return diagnostic(
|
|
1324
|
+
`phases.${phase}.verification`,
|
|
1325
|
+
"verification-input-not-declared",
|
|
1326
|
+
phase,
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
const taskPhases = value.phases as Record<
|
|
1332
|
+
ImplementationPhase,
|
|
1333
|
+
PhaseBoundary | undefined
|
|
1334
|
+
>;
|
|
1335
|
+
const phases = IMPLEMENTATION_PHASES.flatMap((phase) => {
|
|
1336
|
+
const boundary = taskPhases[phase];
|
|
1337
|
+
return boundary ? [boundary] : [];
|
|
1338
|
+
});
|
|
1339
|
+
const declared = {
|
|
1340
|
+
read: [...new Set(phases.flatMap((entry) => entry.read))],
|
|
1341
|
+
write: [
|
|
1342
|
+
...new Set(phases.flatMap((entry) => [...entry.write, ...entry.delete])),
|
|
1343
|
+
],
|
|
1344
|
+
};
|
|
1345
|
+
const impactReason = validateImpactClosure(
|
|
1346
|
+
value.impactClosure,
|
|
1347
|
+
declared,
|
|
1348
|
+
snapshot,
|
|
1349
|
+
);
|
|
1350
|
+
if (impactReason !== null) {
|
|
1351
|
+
return diagnostic("impactClosure", impactClosureCategory(impactReason));
|
|
1352
|
+
}
|
|
1353
|
+
return validateTaskBoundary(value, snapshot)
|
|
1354
|
+
? null
|
|
1355
|
+
: diagnostic("task", "contract");
|
|
1356
|
+
}
|
|
1107
1357
|
|
|
1108
1358
|
export function validateImplementGraphBoundary(value: unknown):
|
|
1109
1359
|
| { ok: true; value: ImplementGraphBoundary }
|
|
@@ -1137,36 +1387,29 @@ export function validateImplementGraphBoundary(value: unknown):
|
|
|
1137
1387
|
return {
|
|
1138
1388
|
ok: false,
|
|
1139
1389
|
reason: "invalid Implement task boundary",
|
|
1140
|
-
diagnostic: {
|
|
1390
|
+
diagnostic: {
|
|
1391
|
+
code: "invalid-implement-graph",
|
|
1392
|
+
field: "tasks",
|
|
1393
|
+
category: "shape",
|
|
1394
|
+
},
|
|
1141
1395
|
};
|
|
1142
1396
|
}
|
|
1143
1397
|
const task = candidate as Record<string, unknown>;
|
|
1144
|
-
const
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
boundary !== null &&
|
|
1154
|
-
typeof boundary === "object" &&
|
|
1155
|
-
!Array.isArray(boundary) &&
|
|
1156
|
-
Object.hasOwn(boundary, "verificationInputs")
|
|
1398
|
+
const diagnostic = Object.hasOwn(task, "changeId")
|
|
1399
|
+
? {
|
|
1400
|
+
...(validIdentifier(task.taskId) ? { taskId: task.taskId } : {}),
|
|
1401
|
+
field: "changeId",
|
|
1402
|
+
category: "unexpected-field",
|
|
1403
|
+
}
|
|
1404
|
+
: taskBoundaryContractDiagnostic(
|
|
1405
|
+
{ changeId: value.changeId, ...task },
|
|
1406
|
+
undefined,
|
|
1157
1407
|
);
|
|
1158
|
-
|
|
1159
|
-
(phases.refactor !== undefined &&
|
|
1160
|
-
(!phases.refactor ||
|
|
1161
|
-
typeof phases.refactor !== "object" ||
|
|
1162
|
-
Array.isArray(phases.refactor) ||
|
|
1163
|
-
!Object.hasOwn(phases.refactor, "verificationInputs"))) ||
|
|
1164
|
-
!validateTaskBoundary({ changeId: value.changeId, ...task }, undefined)
|
|
1165
|
-
) {
|
|
1408
|
+
if (diagnostic) {
|
|
1166
1409
|
return {
|
|
1167
1410
|
ok: false,
|
|
1168
1411
|
reason: "invalid Implement task boundary",
|
|
1169
|
-
diagnostic: { code: "invalid-implement-graph" },
|
|
1412
|
+
diagnostic: { code: "invalid-implement-graph", ...diagnostic },
|
|
1170
1413
|
};
|
|
1171
1414
|
}
|
|
1172
1415
|
}
|
|
@@ -1191,6 +1434,8 @@ export function validateImplementGraphBoundary(value: unknown):
|
|
|
1191
1434
|
diagnostic: {
|
|
1192
1435
|
code: "invalid-output-path",
|
|
1193
1436
|
...(validIdentifier(output.id) ? { outputId: output.id } : {}),
|
|
1437
|
+
field: "outputs.path",
|
|
1438
|
+
category: "path",
|
|
1194
1439
|
},
|
|
1195
1440
|
};
|
|
1196
1441
|
}
|
|
@@ -1207,7 +1452,12 @@ export function validateImplementGraphBoundary(value: unknown):
|
|
|
1207
1452
|
return {
|
|
1208
1453
|
ok: false,
|
|
1209
1454
|
reason: "invalid Implement graph output",
|
|
1210
|
-
diagnostic: {
|
|
1455
|
+
diagnostic: {
|
|
1456
|
+
code: "invalid-implement-graph",
|
|
1457
|
+
...(validIdentifier(output.id) ? { outputId: output.id } : {}),
|
|
1458
|
+
field: "outputs",
|
|
1459
|
+
category: "shape",
|
|
1460
|
+
},
|
|
1211
1461
|
};
|
|
1212
1462
|
}
|
|
1213
1463
|
}
|
|
@@ -1271,11 +1521,7 @@ export function validatePacketEnvelope(
|
|
|
1271
1521
|
) {
|
|
1272
1522
|
return { ok: false, reason: "invalid Design run identity" };
|
|
1273
1523
|
}
|
|
1274
|
-
if (
|
|
1275
|
-
typeof packet.id !== "string" ||
|
|
1276
|
-
packet.id.length === 0 ||
|
|
1277
|
-
packet.id.length > 128
|
|
1278
|
-
) {
|
|
1524
|
+
if (!validIdentifier(packet.id)) {
|
|
1279
1525
|
return { ok: false, reason: "invalid request id" };
|
|
1280
1526
|
}
|
|
1281
1527
|
if (
|
|
@@ -1729,7 +1975,7 @@ function validateDesignEvidenceResult(r: Record<string, unknown>): {
|
|
|
1729
1975
|
if (
|
|
1730
1976
|
!validIdentifier(r.id) ||
|
|
1731
1977
|
r.packet_id !== r.id ||
|
|
1732
|
-
!
|
|
1978
|
+
!isValidRelativePath(r.module_name)
|
|
1733
1979
|
) {
|
|
1734
1980
|
return { ok: false, reason: "invalid Design evidence identity" };
|
|
1735
1981
|
}
|
package/src/control-contracts.ts
CHANGED
|
@@ -13,6 +13,68 @@ export const CONTROL_COMMANDS = [
|
|
|
13
13
|
|
|
14
14
|
export const CONTROL_STAGES = ["abel-implement"] as const;
|
|
15
15
|
|
|
16
|
+
const CONTROL_SCHEMA_PROPERTIES = {
|
|
17
|
+
command: {
|
|
18
|
+
type: "string",
|
|
19
|
+
enum: ["start", "status", "resume", "rebind", "cancel", "discard"],
|
|
20
|
+
},
|
|
21
|
+
stage: { type: "string", enum: ["abel-implement"] },
|
|
22
|
+
change: { type: "string" },
|
|
23
|
+
operationId: { type: "string" },
|
|
24
|
+
deliveryRevision: { type: "integer", minimum: 1 },
|
|
25
|
+
receiptHash: { type: "string" },
|
|
26
|
+
routeId: { type: "string" },
|
|
27
|
+
} as const;
|
|
28
|
+
|
|
29
|
+
type ControlSchemaField = Exclude<
|
|
30
|
+
keyof typeof CONTROL_SCHEMA_PROPERTIES,
|
|
31
|
+
"command"
|
|
32
|
+
>;
|
|
33
|
+
|
|
34
|
+
const commandSchema = (
|
|
35
|
+
command: ControlCommandName,
|
|
36
|
+
fields: readonly ControlSchemaField[],
|
|
37
|
+
) => ({
|
|
38
|
+
type: "object" as const,
|
|
39
|
+
properties: {
|
|
40
|
+
command: { type: "string" as const, enum: [command] },
|
|
41
|
+
...Object.fromEntries(
|
|
42
|
+
fields.map((field) => [field, CONTROL_SCHEMA_PROPERTIES[field]]),
|
|
43
|
+
),
|
|
44
|
+
},
|
|
45
|
+
required: ["command", ...fields],
|
|
46
|
+
additionalProperties: false,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Provider-facing schema for the closed Implement command union.
|
|
51
|
+
*
|
|
52
|
+
* The anyOf branches express command-specific required fields, including the
|
|
53
|
+
* paired optional receipt arguments on resume. Exact keys and provider-added
|
|
54
|
+
* nullable padding remain enforced by the canonicalizer and validator below.
|
|
55
|
+
*/
|
|
56
|
+
export const CONTROL_COMMAND_PARAMETERS = {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: CONTROL_SCHEMA_PROPERTIES,
|
|
59
|
+
required: ["command", "stage", "change"],
|
|
60
|
+
anyOf: [
|
|
61
|
+
commandSchema("start", ["stage", "change", "operationId"]),
|
|
62
|
+
commandSchema("status", ["stage", "change"]),
|
|
63
|
+
commandSchema("resume", ["stage", "change", "operationId"]),
|
|
64
|
+
commandSchema("resume", [
|
|
65
|
+
"stage",
|
|
66
|
+
"change",
|
|
67
|
+
"operationId",
|
|
68
|
+
"deliveryRevision",
|
|
69
|
+
"receiptHash",
|
|
70
|
+
]),
|
|
71
|
+
commandSchema("rebind", ["stage", "change", "operationId", "routeId"]),
|
|
72
|
+
commandSchema("cancel", ["stage", "change", "operationId"]),
|
|
73
|
+
commandSchema("discard", ["stage", "change", "operationId"]),
|
|
74
|
+
],
|
|
75
|
+
additionalProperties: false,
|
|
76
|
+
} as const;
|
|
77
|
+
|
|
16
78
|
export type ControlCommandName = (typeof CONTROL_COMMANDS)[number];
|
|
17
79
|
export type ControlStage = (typeof CONTROL_STAGES)[number];
|
|
18
80
|
|