@abelxiaoxing/cadence 1.4.0 → 1.4.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.
- package/README.md +58 -4
- 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/package.json +5 -3
- package/prompts/abel-design.md +93 -44
- 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 +896 -0
- package/src/child-model.ts +129 -0
- package/src/child-session.ts +293 -326
- package/src/contracts.ts +184 -76
- package/src/delivery-compiler.ts +103 -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 +210 -0
- package/src/durable-workflow.ts +148 -3570
- package/src/evidence-draft.ts +122 -0
- package/src/implement-graph.ts +3 -0
- package/src/implement-plan.ts +70 -0
- package/src/index.ts +59 -522
- package/src/model-source.ts +22 -0
- package/src/package-candidate.ts +224 -0
- package/src/package-workflow.ts +253 -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 +146 -0
- package/src/plan-draft.ts +493 -0
- package/src/subagent-activity.ts +32 -66
- package/src/submit-tool.ts +20 -59
- package/src/transport-budget.ts +32 -0
- package/src/worker-broker.ts +37 -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-worker.mjs +7 -0
- 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",
|
|
@@ -117,7 +125,6 @@ export const ENVIRONMENT_FAILURE_CODES = [
|
|
|
117
125
|
"checkout-failed",
|
|
118
126
|
"clone-alternates",
|
|
119
127
|
"clone-failed",
|
|
120
|
-
"child-session-create-failed",
|
|
121
128
|
"dependency-path-unsafe",
|
|
122
129
|
"git-apply-check-unavailable",
|
|
123
130
|
"git-apply-failed",
|
|
@@ -125,11 +132,6 @@ export const ENVIRONMENT_FAILURE_CODES = [
|
|
|
125
132
|
"git-ignore-check-failed",
|
|
126
133
|
"git-index-unavailable",
|
|
127
134
|
"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
135
|
"root-unavailable",
|
|
134
136
|
"sandbox-runtime-unavailable",
|
|
135
137
|
] as const;
|
|
@@ -155,6 +157,9 @@ export const VERIFICATION_ADAPTER_CODES = [
|
|
|
155
157
|
"verification-config-unsafe",
|
|
156
158
|
] as const;
|
|
157
159
|
export const CHILD_TRANSPORT_CODES = [
|
|
160
|
+
"first-progress-timeout",
|
|
161
|
+
"stream-idle-timeout",
|
|
162
|
+
"attempt-timeout",
|
|
158
163
|
"child-no-final-assistant",
|
|
159
164
|
"child-provider-stream-aborted",
|
|
160
165
|
"child-provider-stream-error",
|
|
@@ -237,6 +242,10 @@ export type CandidateFailure =
|
|
|
237
242
|
| { kind: "result-limit"; limitBytes: number };
|
|
238
243
|
|
|
239
244
|
export type ChildFailure =
|
|
245
|
+
| {
|
|
246
|
+
kind: "execution-limit";
|
|
247
|
+
code: "child-turn-limit" | "child-context-limit";
|
|
248
|
+
}
|
|
240
249
|
| CandidateFailure
|
|
241
250
|
| {
|
|
242
251
|
kind: "transport";
|
|
@@ -437,6 +446,9 @@ type ImplementGraphDiagnosticDetails = Partial<{
|
|
|
437
446
|
producerTaskId: string;
|
|
438
447
|
producerPhase: ImplementationPhase;
|
|
439
448
|
dependencyTaskId: string;
|
|
449
|
+
field: string;
|
|
450
|
+
expectedPaths: string[];
|
|
451
|
+
actualPaths: string[];
|
|
440
452
|
}>;
|
|
441
453
|
|
|
442
454
|
export type ImplementGraphReadinessDiagnostic =
|
|
@@ -478,6 +490,10 @@ export interface PacketEnvelope {
|
|
|
478
490
|
output: "evidence" | "diff";
|
|
479
491
|
}
|
|
480
492
|
|
|
493
|
+
// JSON-schema counterpart for model-visible path arguments; keep its behavior
|
|
494
|
+
// checked against isValidRelativePath rather than hiding constraints in prose.
|
|
495
|
+
export const RELATIVE_PATH_PATTERN = String.raw`^(?:\.|(?!/)(?![a-zA-Z]:/)(?!\./)(?![\s\S]*\.\.)(?![\s\S]*[\\\u0000])(?![\s\S]*//)(?![\s\S]*/\.(?:/|$))(?![\s\S]*/$)[\s\S]+)$`;
|
|
496
|
+
|
|
481
497
|
export function isValidRelativePath(p: unknown): p is string {
|
|
482
498
|
return (
|
|
483
499
|
typeof p === "string" &&
|
|
@@ -655,8 +671,8 @@ function validateAtomicVerification(
|
|
|
655
671
|
validateExecutableRunner(contract.runner) &&
|
|
656
672
|
validateVitestRunner(executableRunner) &&
|
|
657
673
|
Array.isArray(contract.testFiles) &&
|
|
658
|
-
contract.testFiles.length
|
|
659
|
-
contract.testFiles.length <=
|
|
674
|
+
contract.testFiles.length >= VERIFICATION_LIMITS.minTestFiles &&
|
|
675
|
+
contract.testFiles.length <= VERIFICATION_LIMITS.maxTestFiles &&
|
|
660
676
|
contract.testFiles.every(isValidRelativePath) &&
|
|
661
677
|
new Set(contract.testFiles).size === contract.testFiles.length &&
|
|
662
678
|
validVerificationArgs(contract.args) &&
|
|
@@ -724,8 +740,8 @@ function validateStructuredVerification(
|
|
|
724
740
|
String(contract.classification),
|
|
725
741
|
) ||
|
|
726
742
|
!Array.isArray(contract.steps) ||
|
|
727
|
-
contract.steps.length
|
|
728
|
-
contract.steps.length >
|
|
743
|
+
contract.steps.length < VERIFICATION_LIMITS.minSteps ||
|
|
744
|
+
contract.steps.length > VERIFICATION_LIMITS.maxSteps ||
|
|
729
745
|
!contract.steps.every((step) => validateAtomicVerification(step))
|
|
730
746
|
) {
|
|
731
747
|
return false;
|
|
@@ -1198,6 +1214,9 @@ export type ImplementGraphContractDiagnostic =
|
|
|
1198
1214
|
field?: string;
|
|
1199
1215
|
category?: string;
|
|
1200
1216
|
outputId?: string;
|
|
1217
|
+
path?: string;
|
|
1218
|
+
expectedPaths?: string[];
|
|
1219
|
+
actualPaths?: string[];
|
|
1201
1220
|
}
|
|
1202
1221
|
| {
|
|
1203
1222
|
code: "invalid-output-path";
|
|
@@ -1456,6 +1475,26 @@ function taskBoundaryContractDiagnostic(
|
|
|
1456
1475
|
snapshot,
|
|
1457
1476
|
);
|
|
1458
1477
|
if (impactReason !== null) {
|
|
1478
|
+
if (impactReason === "current-task related test is outside the write set") {
|
|
1479
|
+
const related = (value.impactClosure as ImpactClosureContract)
|
|
1480
|
+
.relatedTests;
|
|
1481
|
+
const index = related.findIndex(
|
|
1482
|
+
(test) =>
|
|
1483
|
+
test.disposition === "current-task" &&
|
|
1484
|
+
!declared.write.includes(test.path),
|
|
1485
|
+
);
|
|
1486
|
+
const test = related[index];
|
|
1487
|
+
if (test)
|
|
1488
|
+
return {
|
|
1489
|
+
...diagnostic(
|
|
1490
|
+
`impactClosure.relatedTests.${index}.disposition`,
|
|
1491
|
+
"current-task-outside-write-set",
|
|
1492
|
+
),
|
|
1493
|
+
path: test.path,
|
|
1494
|
+
expectedPaths: [...declared.write].sort(),
|
|
1495
|
+
actualPaths: [test.path],
|
|
1496
|
+
};
|
|
1497
|
+
}
|
|
1459
1498
|
return diagnostic("impactClosure", impactClosureCategory(impactReason));
|
|
1460
1499
|
}
|
|
1461
1500
|
return validateTaskBoundary(value, snapshot)
|
|
@@ -1469,6 +1508,7 @@ export function validateImplementGraphBoundary(value: unknown):
|
|
|
1469
1508
|
ok: false;
|
|
1470
1509
|
reason: string;
|
|
1471
1510
|
diagnostic: ImplementGraphContractDiagnostic;
|
|
1511
|
+
diagnostics?: ImplementGraphContractDiagnostic[];
|
|
1472
1512
|
} {
|
|
1473
1513
|
if (
|
|
1474
1514
|
!hasExactKeys(value, ["changeId", "tasks", "outputs"]) ||
|
|
@@ -1478,29 +1518,33 @@ export function validateImplementGraphBoundary(value: unknown):
|
|
|
1478
1518
|
value.tasks.length > 128 ||
|
|
1479
1519
|
!Array.isArray(value.outputs) ||
|
|
1480
1520
|
value.outputs.length > 512
|
|
1481
|
-
)
|
|
1521
|
+
)
|
|
1482
1522
|
return {
|
|
1483
1523
|
ok: false,
|
|
1484
1524
|
reason: "invalid Implement graph boundary",
|
|
1485
1525
|
diagnostic: { code: "invalid-implement-graph" },
|
|
1486
1526
|
};
|
|
1487
|
-
}
|
|
1488
1527
|
|
|
1528
|
+
// Collect independent task/output failures without admitting a partial graph.
|
|
1529
|
+
const failures: Array<{
|
|
1530
|
+
reason: string;
|
|
1531
|
+
diagnostic: ImplementGraphContractDiagnostic;
|
|
1532
|
+
}> = [];
|
|
1489
1533
|
for (const candidate of value.tasks) {
|
|
1490
1534
|
if (
|
|
1491
1535
|
!candidate ||
|
|
1492
1536
|
typeof candidate !== "object" ||
|
|
1493
1537
|
Array.isArray(candidate)
|
|
1494
1538
|
) {
|
|
1495
|
-
|
|
1496
|
-
ok: false,
|
|
1539
|
+
failures.push({
|
|
1497
1540
|
reason: "invalid Implement task boundary",
|
|
1498
1541
|
diagnostic: {
|
|
1499
1542
|
code: "invalid-implement-graph",
|
|
1500
1543
|
field: "tasks",
|
|
1501
1544
|
category: "shape",
|
|
1502
1545
|
},
|
|
1503
|
-
};
|
|
1546
|
+
});
|
|
1547
|
+
continue;
|
|
1504
1548
|
}
|
|
1505
1549
|
const task = candidate as Record<string, unknown>;
|
|
1506
1550
|
const diagnostic = Object.hasOwn(task, "changeId")
|
|
@@ -1513,31 +1557,27 @@ export function validateImplementGraphBoundary(value: unknown):
|
|
|
1513
1557
|
{ changeId: value.changeId, ...task },
|
|
1514
1558
|
undefined,
|
|
1515
1559
|
);
|
|
1516
|
-
if (diagnostic)
|
|
1517
|
-
|
|
1518
|
-
ok: false,
|
|
1560
|
+
if (diagnostic)
|
|
1561
|
+
failures.push({
|
|
1519
1562
|
reason: "invalid Implement task boundary",
|
|
1520
1563
|
diagnostic: { code: "invalid-implement-graph", ...diagnostic },
|
|
1521
|
-
};
|
|
1522
|
-
}
|
|
1564
|
+
});
|
|
1523
1565
|
}
|
|
1524
|
-
|
|
1525
1566
|
for (const candidate of value.outputs) {
|
|
1526
1567
|
if (
|
|
1527
1568
|
!candidate ||
|
|
1528
1569
|
typeof candidate !== "object" ||
|
|
1529
1570
|
Array.isArray(candidate)
|
|
1530
1571
|
) {
|
|
1531
|
-
|
|
1532
|
-
ok: false,
|
|
1572
|
+
failures.push({
|
|
1533
1573
|
reason: "invalid Implement graph output",
|
|
1534
1574
|
diagnostic: { code: "invalid-implement-graph" },
|
|
1535
|
-
};
|
|
1575
|
+
});
|
|
1576
|
+
continue;
|
|
1536
1577
|
}
|
|
1537
1578
|
const output = candidate as Record<string, unknown>;
|
|
1538
1579
|
if (!isValidRelativePath(output.path)) {
|
|
1539
|
-
|
|
1540
|
-
ok: false,
|
|
1580
|
+
failures.push({
|
|
1541
1581
|
reason: "invalid Implement graph output path",
|
|
1542
1582
|
diagnostic: {
|
|
1543
1583
|
code: "invalid-output-path",
|
|
@@ -1545,7 +1585,8 @@ export function validateImplementGraphBoundary(value: unknown):
|
|
|
1545
1585
|
field: "outputs.path",
|
|
1546
1586
|
category: "path",
|
|
1547
1587
|
},
|
|
1548
|
-
};
|
|
1588
|
+
});
|
|
1589
|
+
continue;
|
|
1549
1590
|
}
|
|
1550
1591
|
if (
|
|
1551
1592
|
!hasExactKeys(output, ["id", "path", "producer", "postcondition"]) ||
|
|
@@ -1557,8 +1598,7 @@ export function validateImplementGraphBoundary(value: unknown):
|
|
|
1557
1598
|
) ||
|
|
1558
1599
|
output.postcondition !== "regular-file"
|
|
1559
1600
|
) {
|
|
1560
|
-
|
|
1561
|
-
ok: false,
|
|
1601
|
+
failures.push({
|
|
1562
1602
|
reason: "invalid Implement graph output",
|
|
1563
1603
|
diagnostic: {
|
|
1564
1604
|
code: "invalid-implement-graph",
|
|
@@ -1566,10 +1606,18 @@ export function validateImplementGraphBoundary(value: unknown):
|
|
|
1566
1606
|
field: "outputs",
|
|
1567
1607
|
category: "shape",
|
|
1568
1608
|
},
|
|
1569
|
-
};
|
|
1609
|
+
});
|
|
1570
1610
|
}
|
|
1571
1611
|
}
|
|
1572
|
-
|
|
1612
|
+
const first = failures[0];
|
|
1613
|
+
if (first)
|
|
1614
|
+
return {
|
|
1615
|
+
ok: false,
|
|
1616
|
+
...first,
|
|
1617
|
+
...(failures.length > 1
|
|
1618
|
+
? { diagnostics: failures.map((failure) => failure.diagnostic) }
|
|
1619
|
+
: {}),
|
|
1620
|
+
};
|
|
1573
1621
|
return {
|
|
1574
1622
|
ok: true,
|
|
1575
1623
|
value: structuredClone(value) as unknown as ImplementGraphBoundary,
|
|
@@ -1976,6 +2024,24 @@ export interface DiffResult {
|
|
|
1976
2024
|
contractCompliant: boolean;
|
|
1977
2025
|
}
|
|
1978
2026
|
|
|
2027
|
+
// Diagnostics contain only code-owned field names, never unexpected keys or values.
|
|
2028
|
+
function evidenceFieldsFailure(
|
|
2029
|
+
value: unknown,
|
|
2030
|
+
fields: readonly string[],
|
|
2031
|
+
location: string,
|
|
2032
|
+
): { ok: false; reason: string } {
|
|
2033
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
2034
|
+
return { ok: false, reason: `${location}: expected an object` };
|
|
2035
|
+
const missing = fields.filter((field) => !Object.hasOwn(value, field));
|
|
2036
|
+
return {
|
|
2037
|
+
ok: false,
|
|
2038
|
+
reason:
|
|
2039
|
+
missing.length > 0
|
|
2040
|
+
? `${location}: missing fields ${missing.join(", ")}`
|
|
2041
|
+
: `${location}: unexpected fields; allowed fields ${fields.join(", ")}`,
|
|
2042
|
+
};
|
|
2043
|
+
}
|
|
2044
|
+
|
|
1979
2045
|
export function validateEvidenceResult(value: unknown): {
|
|
1980
2046
|
ok: boolean;
|
|
1981
2047
|
reason?: string;
|
|
@@ -1999,7 +2065,7 @@ export function validateEvidenceResult(value: unknown): {
|
|
|
1999
2065
|
"hints",
|
|
2000
2066
|
] as const;
|
|
2001
2067
|
if (!hasExactKeys(r, fields)) {
|
|
2002
|
-
return
|
|
2068
|
+
return evidenceFieldsFailure(r, fields, "result");
|
|
2003
2069
|
}
|
|
2004
2070
|
if (!validIdentifier(r.id) || typeof r.role !== "string")
|
|
2005
2071
|
return { ok: false, reason: "invalid identity" };
|
|
@@ -2019,25 +2085,38 @@ export function validateEvidenceResult(value: unknown): {
|
|
|
2019
2085
|
return { ok: false, reason: `invalid evidence field: ${field}` };
|
|
2020
2086
|
}
|
|
2021
2087
|
}
|
|
2022
|
-
if (
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2088
|
+
if (!Array.isArray(r.citations))
|
|
2089
|
+
return { ok: false, reason: "citations: expected an array" };
|
|
2090
|
+
for (const [index, citation] of r.citations.entries()) {
|
|
2091
|
+
const field = `citations[${index}]`;
|
|
2092
|
+
if (!hasExactKeys(citation, ["path", "lines"]))
|
|
2093
|
+
return evidenceFieldsFailure(citation, ["path", "lines"], field);
|
|
2094
|
+
if (!isValidRelativePath(citation.path))
|
|
2095
|
+
return {
|
|
2096
|
+
ok: false,
|
|
2097
|
+
reason: `${field}.path: expected a canonical relative path`,
|
|
2098
|
+
};
|
|
2099
|
+
if (typeof citation.lines !== "string")
|
|
2100
|
+
return { ok: false, reason: `${field}.lines: expected a string` };
|
|
2101
|
+
}
|
|
2102
|
+
if (!hasExactKeys(r.hints, ["writeSet", "verification", "agentsImpact"]))
|
|
2103
|
+
return evidenceFieldsFailure(
|
|
2104
|
+
r.hints,
|
|
2105
|
+
["writeSet", "verification", "agentsImpact"],
|
|
2106
|
+
"hints",
|
|
2107
|
+
);
|
|
2108
|
+
if (!validatePathSet(r.hints.writeSet))
|
|
2109
|
+
return {
|
|
2110
|
+
ok: false,
|
|
2111
|
+
reason: "hints.writeSet: expected unique canonical relative paths",
|
|
2112
|
+
};
|
|
2113
|
+
if (typeof r.hints.verification !== "string")
|
|
2114
|
+
return { ok: false, reason: "hints.verification: expected a string" };
|
|
2115
|
+
if (!(AGENTS_IMPACTS as readonly unknown[]).includes(r.hints.agentsImpact))
|
|
2116
|
+
return {
|
|
2117
|
+
ok: false,
|
|
2118
|
+
reason: `hints.agentsImpact: expected one of ${AGENTS_IMPACTS.join(", ")}`,
|
|
2119
|
+
};
|
|
2041
2120
|
const serialized = JSON.stringify(r);
|
|
2042
2121
|
if (Buffer.byteLength(serialized, "utf8") > LIMITS.maxCompleteResultBytes) {
|
|
2043
2122
|
return {
|
|
@@ -2078,36 +2157,65 @@ function validateDesignEvidenceResult(r: Record<string, unknown>): {
|
|
|
2078
2157
|
"success_criteria_hints",
|
|
2079
2158
|
] as const;
|
|
2080
2159
|
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" };
|
|
2160
|
+
return evidenceFieldsFailure(r, fields, "result");
|
|
2089
2161
|
}
|
|
2162
|
+
if (!validIdentifier(r.id))
|
|
2163
|
+
return { ok: false, reason: "id: expected a valid identifier" };
|
|
2164
|
+
if (r.packet_id !== r.id)
|
|
2165
|
+
return { ok: false, reason: "packet_id: must equal id" };
|
|
2166
|
+
if (!isValidRelativePath(r.module_name))
|
|
2167
|
+
return {
|
|
2168
|
+
ok: false,
|
|
2169
|
+
reason: "module_name: expected a canonical relative path or module slug",
|
|
2170
|
+
};
|
|
2090
2171
|
if (!validatePathSet(r.scope) || r.scope.length === 0) {
|
|
2091
|
-
return {
|
|
2172
|
+
return {
|
|
2173
|
+
ok: false,
|
|
2174
|
+
reason:
|
|
2175
|
+
"scope: expected a nonempty set of unique canonical relative paths",
|
|
2176
|
+
};
|
|
2092
2177
|
}
|
|
2093
|
-
|
|
2094
|
-
|
|
2178
|
+
for (const field of ["files_read", "write_set_hints"] as const) {
|
|
2179
|
+
if (!validatePathSet(r[field]))
|
|
2180
|
+
return {
|
|
2181
|
+
ok: false,
|
|
2182
|
+
reason: `${field}: expected unique canonical relative paths`,
|
|
2183
|
+
};
|
|
2095
2184
|
}
|
|
2096
|
-
if (
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
entry
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2185
|
+
if (!Array.isArray(r.evidence))
|
|
2186
|
+
return { ok: false, reason: "evidence: expected an array" };
|
|
2187
|
+
for (const [index, entry] of r.evidence.entries()) {
|
|
2188
|
+
const field = `evidence[${index}]`;
|
|
2189
|
+
if (!hasExactKeys(entry, ["claim", "path", "line_start", "line_end"])) {
|
|
2190
|
+
return evidenceFieldsFailure(
|
|
2191
|
+
entry,
|
|
2192
|
+
["claim", "path", "line_start", "line_end"],
|
|
2193
|
+
field,
|
|
2194
|
+
);
|
|
2195
|
+
}
|
|
2196
|
+
if (typeof entry.claim !== "string" || entry.claim.length === 0)
|
|
2197
|
+
return { ok: false, reason: `${field}.claim: expected nonempty text` };
|
|
2198
|
+
if (!isValidRelativePath(entry.path))
|
|
2199
|
+
return {
|
|
2200
|
+
ok: false,
|
|
2201
|
+
reason: `${field}.path: expected a canonical relative path`,
|
|
2202
|
+
};
|
|
2203
|
+
if (
|
|
2204
|
+
!Number.isSafeInteger(entry.line_start) ||
|
|
2205
|
+
(entry.line_start as number) < 1
|
|
2108
2206
|
)
|
|
2109
|
-
|
|
2110
|
-
|
|
2207
|
+
return {
|
|
2208
|
+
ok: false,
|
|
2209
|
+
reason: `${field}.line_start: expected a positive safe integer`,
|
|
2210
|
+
};
|
|
2211
|
+
if (
|
|
2212
|
+
!Number.isSafeInteger(entry.line_end) ||
|
|
2213
|
+
(entry.line_end as number) < (entry.line_start as number)
|
|
2214
|
+
)
|
|
2215
|
+
return {
|
|
2216
|
+
ok: false,
|
|
2217
|
+
reason: `${field}.line_end: expected a safe integer >= line_start`,
|
|
2218
|
+
};
|
|
2111
2219
|
}
|
|
2112
2220
|
for (const field of [
|
|
2113
2221
|
"existing_structures",
|