@abelxiaoxing/cadence 1.4.0 → 1.5.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.
- package/README.md +130 -8
- package/agents/design-explorer.md +17 -11
- package/agents/diagnosis-worker.md +9 -1
- package/agents/implementation-worker.md +3 -1
- package/config/plan-draft.example.json +122 -0
- package/config/plan-draft.multiple-tasks.example.json +345 -0
- package/config/plan-draft.quick.example.json +78 -0
- package/package.json +10 -4
- package/prompts/abel-design.md +101 -44
- package/prompts/abel-implement.md +9 -0
- package/src/activity-contracts.ts +82 -0
- package/src/candidate-artifact.ts +143 -0
- package/src/candidate-patch.ts +31 -11
- package/src/candidate-workspace.ts +74 -0
- package/src/change-contract-schema.ts +191 -0
- package/src/change-contract.ts +5 -0
- package/src/change-verification.ts +920 -0
- package/src/child-budget.ts +58 -0
- package/src/child-model.ts +142 -0
- package/src/child-session.ts +289 -326
- package/src/contracts.ts +200 -76
- package/src/delivery-compiler.ts +105 -120
- package/src/delivery-revision.ts +42 -0
- package/src/design-control.ts +113 -4
- package/src/design-diagnostics.ts +150 -0
- package/src/durable-contracts.ts +213 -0
- package/src/durable-workflow.ts +148 -3570
- package/src/evidence-draft.ts +122 -0
- package/src/execution-profile.ts +38 -0
- package/src/implement-graph.ts +3 -0
- package/src/implement-plan.ts +70 -0
- package/src/index.ts +59 -522
- package/src/isolation-backend.ts +62 -40
- package/src/model-source.ts +22 -0
- package/src/operator-cli-entry.mjs +24 -0
- package/src/operator-cli.mjs +1421 -0
- package/src/operator-tools.ts +249 -0
- package/src/package-candidate.ts +227 -0
- package/src/package-verification.ts +86 -8
- package/src/package-workflow.ts +291 -0
- package/src/packet-runtime.ts +27 -48
- package/src/parent-provider.ts +86 -198
- package/src/phase-execution.ts +2248 -0
- package/src/pi-adapter.ts +19 -0
- package/src/plan-draft-summary.ts +149 -0
- package/src/plan-draft.ts +493 -0
- package/src/single-task-draft.ts +142 -0
- package/src/subagent-activity.ts +32 -66
- package/src/submit-tool.ts +20 -59
- package/src/transport-budget.ts +52 -0
- package/src/verification-capability.ts +5 -3
- package/src/verification-diagnostics.ts +76 -0
- package/src/verification-environment-io.ts +280 -0
- package/src/verification-environment.ts +56 -154
- package/src/verification-scan.ts +64 -0
- package/src/worker-broker.ts +39 -57
- package/src/workflow-policy.ts +14 -53
- package/src/workflow-recovery-policy.ts +95 -0
- package/src/workflow-scheduling.ts +105 -0
- package/src/workflow-state-machine.ts +105 -497
- package/src/workflow-status.ts +384 -0
- package/src/workspace-io-entry.mjs +19 -1
- package/src/workspace-io-worker.mjs +275 -50
- package/src/workspace-io.ts +6 -1
- package/src/empty-resource-loader.ts +0 -38
- package/src/parent-payload-bridge.ts +0 -523
package/src/contracts.ts
CHANGED
|
@@ -52,6 +52,14 @@ export const LIMITS = {
|
|
|
52
52
|
maxCompleteResultBytes: 64 * 1024,
|
|
53
53
|
} as const;
|
|
54
54
|
|
|
55
|
+
/** Shared collection bounds for runtime verification and author-facing schemas. */
|
|
56
|
+
export const VERIFICATION_LIMITS = Object.freeze({
|
|
57
|
+
minSteps: 1,
|
|
58
|
+
maxSteps: 8,
|
|
59
|
+
minTestFiles: 1,
|
|
60
|
+
maxTestFiles: 64,
|
|
61
|
+
});
|
|
62
|
+
|
|
55
63
|
export const ARTIFACT_FAILURE_CODES = [
|
|
56
64
|
"agents-impact-mismatch",
|
|
57
65
|
"agents-target-mismatch",
|
|
@@ -109,6 +117,7 @@ export const STALE_FAILURE_CODES = [
|
|
|
109
117
|
"stale-snapshot",
|
|
110
118
|
] as const;
|
|
111
119
|
export const ENVIRONMENT_FAILURE_CODES = [
|
|
120
|
+
"child-provider-authentication-failed",
|
|
112
121
|
"bubblewrap-launch-failed",
|
|
113
122
|
"bubblewrap-or-dependency-unavailable",
|
|
114
123
|
"bun-executable-unavailable",
|
|
@@ -117,7 +126,6 @@ export const ENVIRONMENT_FAILURE_CODES = [
|
|
|
117
126
|
"checkout-failed",
|
|
118
127
|
"clone-alternates",
|
|
119
128
|
"clone-failed",
|
|
120
|
-
"child-session-create-failed",
|
|
121
129
|
"dependency-path-unsafe",
|
|
122
130
|
"git-apply-check-unavailable",
|
|
123
131
|
"git-apply-failed",
|
|
@@ -125,11 +133,6 @@ export const ENVIRONMENT_FAILURE_CODES = [
|
|
|
125
133
|
"git-ignore-check-failed",
|
|
126
134
|
"git-index-unavailable",
|
|
127
135
|
"invalid-subagent-endpoint",
|
|
128
|
-
"parent-bridge-capture-not-ready",
|
|
129
|
-
"parent-bridge-generation-invalidated",
|
|
130
|
-
"parent-bridge-model-key-mismatch",
|
|
131
|
-
"parent-bridge-provider-not-installed",
|
|
132
|
-
"parent-bridge-session-unavailable",
|
|
133
136
|
"root-unavailable",
|
|
134
137
|
"sandbox-runtime-unavailable",
|
|
135
138
|
] as const;
|
|
@@ -155,6 +158,10 @@ export const VERIFICATION_ADAPTER_CODES = [
|
|
|
155
158
|
"verification-config-unsafe",
|
|
156
159
|
] as const;
|
|
157
160
|
export const CHILD_TRANSPORT_CODES = [
|
|
161
|
+
"child-provider-rate-limited",
|
|
162
|
+
"first-progress-timeout",
|
|
163
|
+
"stream-idle-timeout",
|
|
164
|
+
"attempt-timeout",
|
|
158
165
|
"child-no-final-assistant",
|
|
159
166
|
"child-provider-stream-aborted",
|
|
160
167
|
"child-provider-stream-error",
|
|
@@ -237,6 +244,10 @@ export type CandidateFailure =
|
|
|
237
244
|
| { kind: "result-limit"; limitBytes: number };
|
|
238
245
|
|
|
239
246
|
export type ChildFailure =
|
|
247
|
+
| {
|
|
248
|
+
kind: "execution-limit";
|
|
249
|
+
code: "child-turn-limit" | "child-context-limit";
|
|
250
|
+
}
|
|
240
251
|
| CandidateFailure
|
|
241
252
|
| {
|
|
242
253
|
kind: "transport";
|
|
@@ -252,6 +263,17 @@ export type VerificationClassification =
|
|
|
252
263
|
| "expected-green"
|
|
253
264
|
| "expected-refactor";
|
|
254
265
|
|
|
266
|
+
export interface VerificationFailureSummary {
|
|
267
|
+
verificationId: string;
|
|
268
|
+
code: string;
|
|
269
|
+
exitCode?: number;
|
|
270
|
+
failures: string[];
|
|
271
|
+
stdout: string;
|
|
272
|
+
stderr: string;
|
|
273
|
+
truncated: boolean;
|
|
274
|
+
nextStep: string;
|
|
275
|
+
}
|
|
276
|
+
|
|
255
277
|
/** Runtime-owned evidence; unavailable checks never become product baselines. */
|
|
256
278
|
export interface VerificationEvidence {
|
|
257
279
|
id: string;
|
|
@@ -260,6 +282,7 @@ export interface VerificationEvidence {
|
|
|
260
282
|
tests?: number;
|
|
261
283
|
failureIdentities: string[];
|
|
262
284
|
policy: "report-file-v3";
|
|
285
|
+
attributionReliable?: boolean;
|
|
263
286
|
}
|
|
264
287
|
export type VerificationObservation =
|
|
265
288
|
| { kind: "accepted"; evidence: VerificationEvidence }
|
|
@@ -267,12 +290,14 @@ export type VerificationObservation =
|
|
|
267
290
|
kind: "rejected";
|
|
268
291
|
code: "red-not-witnessed" | "verification-rejected";
|
|
269
292
|
evidence: VerificationEvidence;
|
|
293
|
+
diagnostic?: VerificationFailureSummary;
|
|
270
294
|
}
|
|
271
295
|
| {
|
|
272
296
|
kind: "unavailable";
|
|
273
297
|
category: "environment" | "adapter" | "resource";
|
|
274
298
|
code: string;
|
|
275
299
|
verificationId: string;
|
|
300
|
+
diagnostic?: VerificationFailureSummary;
|
|
276
301
|
}
|
|
277
302
|
| { kind: "cancelled" };
|
|
278
303
|
|
|
@@ -437,6 +462,9 @@ type ImplementGraphDiagnosticDetails = Partial<{
|
|
|
437
462
|
producerTaskId: string;
|
|
438
463
|
producerPhase: ImplementationPhase;
|
|
439
464
|
dependencyTaskId: string;
|
|
465
|
+
field: string;
|
|
466
|
+
expectedPaths: string[];
|
|
467
|
+
actualPaths: string[];
|
|
440
468
|
}>;
|
|
441
469
|
|
|
442
470
|
export type ImplementGraphReadinessDiagnostic =
|
|
@@ -478,6 +506,10 @@ export interface PacketEnvelope {
|
|
|
478
506
|
output: "evidence" | "diff";
|
|
479
507
|
}
|
|
480
508
|
|
|
509
|
+
// JSON-schema counterpart for model-visible path arguments; keep its behavior
|
|
510
|
+
// checked against isValidRelativePath rather than hiding constraints in prose.
|
|
511
|
+
export const RELATIVE_PATH_PATTERN = String.raw`^(?:\.|(?!/)(?![a-zA-Z]:/)(?!\./)(?![\s\S]*\.\.)(?![\s\S]*[\\\u0000])(?![\s\S]*//)(?![\s\S]*/\.(?:/|$))(?![\s\S]*/$)[\s\S]+)$`;
|
|
512
|
+
|
|
481
513
|
export function isValidRelativePath(p: unknown): p is string {
|
|
482
514
|
return (
|
|
483
515
|
typeof p === "string" &&
|
|
@@ -655,8 +687,8 @@ function validateAtomicVerification(
|
|
|
655
687
|
validateExecutableRunner(contract.runner) &&
|
|
656
688
|
validateVitestRunner(executableRunner) &&
|
|
657
689
|
Array.isArray(contract.testFiles) &&
|
|
658
|
-
contract.testFiles.length
|
|
659
|
-
contract.testFiles.length <=
|
|
690
|
+
contract.testFiles.length >= VERIFICATION_LIMITS.minTestFiles &&
|
|
691
|
+
contract.testFiles.length <= VERIFICATION_LIMITS.maxTestFiles &&
|
|
660
692
|
contract.testFiles.every(isValidRelativePath) &&
|
|
661
693
|
new Set(contract.testFiles).size === contract.testFiles.length &&
|
|
662
694
|
validVerificationArgs(contract.args) &&
|
|
@@ -724,8 +756,8 @@ function validateStructuredVerification(
|
|
|
724
756
|
String(contract.classification),
|
|
725
757
|
) ||
|
|
726
758
|
!Array.isArray(contract.steps) ||
|
|
727
|
-
contract.steps.length
|
|
728
|
-
contract.steps.length >
|
|
759
|
+
contract.steps.length < VERIFICATION_LIMITS.minSteps ||
|
|
760
|
+
contract.steps.length > VERIFICATION_LIMITS.maxSteps ||
|
|
729
761
|
!contract.steps.every((step) => validateAtomicVerification(step))
|
|
730
762
|
) {
|
|
731
763
|
return false;
|
|
@@ -1198,6 +1230,9 @@ export type ImplementGraphContractDiagnostic =
|
|
|
1198
1230
|
field?: string;
|
|
1199
1231
|
category?: string;
|
|
1200
1232
|
outputId?: string;
|
|
1233
|
+
path?: string;
|
|
1234
|
+
expectedPaths?: string[];
|
|
1235
|
+
actualPaths?: string[];
|
|
1201
1236
|
}
|
|
1202
1237
|
| {
|
|
1203
1238
|
code: "invalid-output-path";
|
|
@@ -1456,6 +1491,26 @@ function taskBoundaryContractDiagnostic(
|
|
|
1456
1491
|
snapshot,
|
|
1457
1492
|
);
|
|
1458
1493
|
if (impactReason !== null) {
|
|
1494
|
+
if (impactReason === "current-task related test is outside the write set") {
|
|
1495
|
+
const related = (value.impactClosure as ImpactClosureContract)
|
|
1496
|
+
.relatedTests;
|
|
1497
|
+
const index = related.findIndex(
|
|
1498
|
+
(test) =>
|
|
1499
|
+
test.disposition === "current-task" &&
|
|
1500
|
+
!declared.write.includes(test.path),
|
|
1501
|
+
);
|
|
1502
|
+
const test = related[index];
|
|
1503
|
+
if (test)
|
|
1504
|
+
return {
|
|
1505
|
+
...diagnostic(
|
|
1506
|
+
`impactClosure.relatedTests.${index}.disposition`,
|
|
1507
|
+
"current-task-outside-write-set",
|
|
1508
|
+
),
|
|
1509
|
+
path: test.path,
|
|
1510
|
+
expectedPaths: [...declared.write].sort(),
|
|
1511
|
+
actualPaths: [test.path],
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1459
1514
|
return diagnostic("impactClosure", impactClosureCategory(impactReason));
|
|
1460
1515
|
}
|
|
1461
1516
|
return validateTaskBoundary(value, snapshot)
|
|
@@ -1469,6 +1524,7 @@ export function validateImplementGraphBoundary(value: unknown):
|
|
|
1469
1524
|
ok: false;
|
|
1470
1525
|
reason: string;
|
|
1471
1526
|
diagnostic: ImplementGraphContractDiagnostic;
|
|
1527
|
+
diagnostics?: ImplementGraphContractDiagnostic[];
|
|
1472
1528
|
} {
|
|
1473
1529
|
if (
|
|
1474
1530
|
!hasExactKeys(value, ["changeId", "tasks", "outputs"]) ||
|
|
@@ -1478,29 +1534,33 @@ export function validateImplementGraphBoundary(value: unknown):
|
|
|
1478
1534
|
value.tasks.length > 128 ||
|
|
1479
1535
|
!Array.isArray(value.outputs) ||
|
|
1480
1536
|
value.outputs.length > 512
|
|
1481
|
-
)
|
|
1537
|
+
)
|
|
1482
1538
|
return {
|
|
1483
1539
|
ok: false,
|
|
1484
1540
|
reason: "invalid Implement graph boundary",
|
|
1485
1541
|
diagnostic: { code: "invalid-implement-graph" },
|
|
1486
1542
|
};
|
|
1487
|
-
}
|
|
1488
1543
|
|
|
1544
|
+
// Collect independent task/output failures without admitting a partial graph.
|
|
1545
|
+
const failures: Array<{
|
|
1546
|
+
reason: string;
|
|
1547
|
+
diagnostic: ImplementGraphContractDiagnostic;
|
|
1548
|
+
}> = [];
|
|
1489
1549
|
for (const candidate of value.tasks) {
|
|
1490
1550
|
if (
|
|
1491
1551
|
!candidate ||
|
|
1492
1552
|
typeof candidate !== "object" ||
|
|
1493
1553
|
Array.isArray(candidate)
|
|
1494
1554
|
) {
|
|
1495
|
-
|
|
1496
|
-
ok: false,
|
|
1555
|
+
failures.push({
|
|
1497
1556
|
reason: "invalid Implement task boundary",
|
|
1498
1557
|
diagnostic: {
|
|
1499
1558
|
code: "invalid-implement-graph",
|
|
1500
1559
|
field: "tasks",
|
|
1501
1560
|
category: "shape",
|
|
1502
1561
|
},
|
|
1503
|
-
};
|
|
1562
|
+
});
|
|
1563
|
+
continue;
|
|
1504
1564
|
}
|
|
1505
1565
|
const task = candidate as Record<string, unknown>;
|
|
1506
1566
|
const diagnostic = Object.hasOwn(task, "changeId")
|
|
@@ -1513,31 +1573,27 @@ export function validateImplementGraphBoundary(value: unknown):
|
|
|
1513
1573
|
{ changeId: value.changeId, ...task },
|
|
1514
1574
|
undefined,
|
|
1515
1575
|
);
|
|
1516
|
-
if (diagnostic)
|
|
1517
|
-
|
|
1518
|
-
ok: false,
|
|
1576
|
+
if (diagnostic)
|
|
1577
|
+
failures.push({
|
|
1519
1578
|
reason: "invalid Implement task boundary",
|
|
1520
1579
|
diagnostic: { code: "invalid-implement-graph", ...diagnostic },
|
|
1521
|
-
};
|
|
1522
|
-
}
|
|
1580
|
+
});
|
|
1523
1581
|
}
|
|
1524
|
-
|
|
1525
1582
|
for (const candidate of value.outputs) {
|
|
1526
1583
|
if (
|
|
1527
1584
|
!candidate ||
|
|
1528
1585
|
typeof candidate !== "object" ||
|
|
1529
1586
|
Array.isArray(candidate)
|
|
1530
1587
|
) {
|
|
1531
|
-
|
|
1532
|
-
ok: false,
|
|
1588
|
+
failures.push({
|
|
1533
1589
|
reason: "invalid Implement graph output",
|
|
1534
1590
|
diagnostic: { code: "invalid-implement-graph" },
|
|
1535
|
-
};
|
|
1591
|
+
});
|
|
1592
|
+
continue;
|
|
1536
1593
|
}
|
|
1537
1594
|
const output = candidate as Record<string, unknown>;
|
|
1538
1595
|
if (!isValidRelativePath(output.path)) {
|
|
1539
|
-
|
|
1540
|
-
ok: false,
|
|
1596
|
+
failures.push({
|
|
1541
1597
|
reason: "invalid Implement graph output path",
|
|
1542
1598
|
diagnostic: {
|
|
1543
1599
|
code: "invalid-output-path",
|
|
@@ -1545,7 +1601,8 @@ export function validateImplementGraphBoundary(value: unknown):
|
|
|
1545
1601
|
field: "outputs.path",
|
|
1546
1602
|
category: "path",
|
|
1547
1603
|
},
|
|
1548
|
-
};
|
|
1604
|
+
});
|
|
1605
|
+
continue;
|
|
1549
1606
|
}
|
|
1550
1607
|
if (
|
|
1551
1608
|
!hasExactKeys(output, ["id", "path", "producer", "postcondition"]) ||
|
|
@@ -1557,8 +1614,7 @@ export function validateImplementGraphBoundary(value: unknown):
|
|
|
1557
1614
|
) ||
|
|
1558
1615
|
output.postcondition !== "regular-file"
|
|
1559
1616
|
) {
|
|
1560
|
-
|
|
1561
|
-
ok: false,
|
|
1617
|
+
failures.push({
|
|
1562
1618
|
reason: "invalid Implement graph output",
|
|
1563
1619
|
diagnostic: {
|
|
1564
1620
|
code: "invalid-implement-graph",
|
|
@@ -1566,10 +1622,18 @@ export function validateImplementGraphBoundary(value: unknown):
|
|
|
1566
1622
|
field: "outputs",
|
|
1567
1623
|
category: "shape",
|
|
1568
1624
|
},
|
|
1569
|
-
};
|
|
1625
|
+
});
|
|
1570
1626
|
}
|
|
1571
1627
|
}
|
|
1572
|
-
|
|
1628
|
+
const first = failures[0];
|
|
1629
|
+
if (first)
|
|
1630
|
+
return {
|
|
1631
|
+
ok: false,
|
|
1632
|
+
...first,
|
|
1633
|
+
...(failures.length > 1
|
|
1634
|
+
? { diagnostics: failures.map((failure) => failure.diagnostic) }
|
|
1635
|
+
: {}),
|
|
1636
|
+
};
|
|
1573
1637
|
return {
|
|
1574
1638
|
ok: true,
|
|
1575
1639
|
value: structuredClone(value) as unknown as ImplementGraphBoundary,
|
|
@@ -1976,6 +2040,24 @@ export interface DiffResult {
|
|
|
1976
2040
|
contractCompliant: boolean;
|
|
1977
2041
|
}
|
|
1978
2042
|
|
|
2043
|
+
// Diagnostics contain only code-owned field names, never unexpected keys or values.
|
|
2044
|
+
function evidenceFieldsFailure(
|
|
2045
|
+
value: unknown,
|
|
2046
|
+
fields: readonly string[],
|
|
2047
|
+
location: string,
|
|
2048
|
+
): { ok: false; reason: string } {
|
|
2049
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
2050
|
+
return { ok: false, reason: `${location}: expected an object` };
|
|
2051
|
+
const missing = fields.filter((field) => !Object.hasOwn(value, field));
|
|
2052
|
+
return {
|
|
2053
|
+
ok: false,
|
|
2054
|
+
reason:
|
|
2055
|
+
missing.length > 0
|
|
2056
|
+
? `${location}: missing fields ${missing.join(", ")}`
|
|
2057
|
+
: `${location}: unexpected fields; allowed fields ${fields.join(", ")}`,
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
2060
|
+
|
|
1979
2061
|
export function validateEvidenceResult(value: unknown): {
|
|
1980
2062
|
ok: boolean;
|
|
1981
2063
|
reason?: string;
|
|
@@ -1999,7 +2081,7 @@ export function validateEvidenceResult(value: unknown): {
|
|
|
1999
2081
|
"hints",
|
|
2000
2082
|
] as const;
|
|
2001
2083
|
if (!hasExactKeys(r, fields)) {
|
|
2002
|
-
return
|
|
2084
|
+
return evidenceFieldsFailure(r, fields, "result");
|
|
2003
2085
|
}
|
|
2004
2086
|
if (!validIdentifier(r.id) || typeof r.role !== "string")
|
|
2005
2087
|
return { ok: false, reason: "invalid identity" };
|
|
@@ -2019,25 +2101,38 @@ export function validateEvidenceResult(value: unknown): {
|
|
|
2019
2101
|
return { ok: false, reason: `invalid evidence field: ${field}` };
|
|
2020
2102
|
}
|
|
2021
2103
|
}
|
|
2022
|
-
if (
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2104
|
+
if (!Array.isArray(r.citations))
|
|
2105
|
+
return { ok: false, reason: "citations: expected an array" };
|
|
2106
|
+
for (const [index, citation] of r.citations.entries()) {
|
|
2107
|
+
const field = `citations[${index}]`;
|
|
2108
|
+
if (!hasExactKeys(citation, ["path", "lines"]))
|
|
2109
|
+
return evidenceFieldsFailure(citation, ["path", "lines"], field);
|
|
2110
|
+
if (!isValidRelativePath(citation.path))
|
|
2111
|
+
return {
|
|
2112
|
+
ok: false,
|
|
2113
|
+
reason: `${field}.path: expected a canonical relative path`,
|
|
2114
|
+
};
|
|
2115
|
+
if (typeof citation.lines !== "string")
|
|
2116
|
+
return { ok: false, reason: `${field}.lines: expected a string` };
|
|
2117
|
+
}
|
|
2118
|
+
if (!hasExactKeys(r.hints, ["writeSet", "verification", "agentsImpact"]))
|
|
2119
|
+
return evidenceFieldsFailure(
|
|
2120
|
+
r.hints,
|
|
2121
|
+
["writeSet", "verification", "agentsImpact"],
|
|
2122
|
+
"hints",
|
|
2123
|
+
);
|
|
2124
|
+
if (!validatePathSet(r.hints.writeSet))
|
|
2125
|
+
return {
|
|
2126
|
+
ok: false,
|
|
2127
|
+
reason: "hints.writeSet: expected unique canonical relative paths",
|
|
2128
|
+
};
|
|
2129
|
+
if (typeof r.hints.verification !== "string")
|
|
2130
|
+
return { ok: false, reason: "hints.verification: expected a string" };
|
|
2131
|
+
if (!(AGENTS_IMPACTS as readonly unknown[]).includes(r.hints.agentsImpact))
|
|
2132
|
+
return {
|
|
2133
|
+
ok: false,
|
|
2134
|
+
reason: `hints.agentsImpact: expected one of ${AGENTS_IMPACTS.join(", ")}`,
|
|
2135
|
+
};
|
|
2041
2136
|
const serialized = JSON.stringify(r);
|
|
2042
2137
|
if (Buffer.byteLength(serialized, "utf8") > LIMITS.maxCompleteResultBytes) {
|
|
2043
2138
|
return {
|
|
@@ -2078,36 +2173,65 @@ function validateDesignEvidenceResult(r: Record<string, unknown>): {
|
|
|
2078
2173
|
"success_criteria_hints",
|
|
2079
2174
|
] as const;
|
|
2080
2175
|
if (!hasExactKeys(r, fields)) {
|
|
2081
|
-
return
|
|
2082
|
-
}
|
|
2083
|
-
if (
|
|
2084
|
-
!validIdentifier(r.id) ||
|
|
2085
|
-
r.packet_id !== r.id ||
|
|
2086
|
-
!isValidRelativePath(r.module_name)
|
|
2087
|
-
) {
|
|
2088
|
-
return { ok: false, reason: "invalid Design evidence identity" };
|
|
2176
|
+
return evidenceFieldsFailure(r, fields, "result");
|
|
2089
2177
|
}
|
|
2178
|
+
if (!validIdentifier(r.id))
|
|
2179
|
+
return { ok: false, reason: "id: expected a valid identifier" };
|
|
2180
|
+
if (r.packet_id !== r.id)
|
|
2181
|
+
return { ok: false, reason: "packet_id: must equal id" };
|
|
2182
|
+
if (!isValidRelativePath(r.module_name))
|
|
2183
|
+
return {
|
|
2184
|
+
ok: false,
|
|
2185
|
+
reason: "module_name: expected a canonical relative path or module slug",
|
|
2186
|
+
};
|
|
2090
2187
|
if (!validatePathSet(r.scope) || r.scope.length === 0) {
|
|
2091
|
-
return {
|
|
2188
|
+
return {
|
|
2189
|
+
ok: false,
|
|
2190
|
+
reason:
|
|
2191
|
+
"scope: expected a nonempty set of unique canonical relative paths",
|
|
2192
|
+
};
|
|
2092
2193
|
}
|
|
2093
|
-
|
|
2094
|
-
|
|
2194
|
+
for (const field of ["files_read", "write_set_hints"] as const) {
|
|
2195
|
+
if (!validatePathSet(r[field]))
|
|
2196
|
+
return {
|
|
2197
|
+
ok: false,
|
|
2198
|
+
reason: `${field}: expected unique canonical relative paths`,
|
|
2199
|
+
};
|
|
2095
2200
|
}
|
|
2096
|
-
if (
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
entry
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2201
|
+
if (!Array.isArray(r.evidence))
|
|
2202
|
+
return { ok: false, reason: "evidence: expected an array" };
|
|
2203
|
+
for (const [index, entry] of r.evidence.entries()) {
|
|
2204
|
+
const field = `evidence[${index}]`;
|
|
2205
|
+
if (!hasExactKeys(entry, ["claim", "path", "line_start", "line_end"])) {
|
|
2206
|
+
return evidenceFieldsFailure(
|
|
2207
|
+
entry,
|
|
2208
|
+
["claim", "path", "line_start", "line_end"],
|
|
2209
|
+
field,
|
|
2210
|
+
);
|
|
2211
|
+
}
|
|
2212
|
+
if (typeof entry.claim !== "string" || entry.claim.length === 0)
|
|
2213
|
+
return { ok: false, reason: `${field}.claim: expected nonempty text` };
|
|
2214
|
+
if (!isValidRelativePath(entry.path))
|
|
2215
|
+
return {
|
|
2216
|
+
ok: false,
|
|
2217
|
+
reason: `${field}.path: expected a canonical relative path`,
|
|
2218
|
+
};
|
|
2219
|
+
if (
|
|
2220
|
+
!Number.isSafeInteger(entry.line_start) ||
|
|
2221
|
+
(entry.line_start as number) < 1
|
|
2108
2222
|
)
|
|
2109
|
-
|
|
2110
|
-
|
|
2223
|
+
return {
|
|
2224
|
+
ok: false,
|
|
2225
|
+
reason: `${field}.line_start: expected a positive safe integer`,
|
|
2226
|
+
};
|
|
2227
|
+
if (
|
|
2228
|
+
!Number.isSafeInteger(entry.line_end) ||
|
|
2229
|
+
(entry.line_end as number) < (entry.line_start as number)
|
|
2230
|
+
)
|
|
2231
|
+
return {
|
|
2232
|
+
ok: false,
|
|
2233
|
+
reason: `${field}.line_end: expected a safe integer >= line_start`,
|
|
2234
|
+
};
|
|
2111
2235
|
}
|
|
2112
2236
|
for (const field of [
|
|
2113
2237
|
"existing_structures",
|