@abelxiaoxing/cadence 1.3.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 +172 -13
- 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 +9 -5
- package/prompts/abel-design.md +141 -38
- package/prompts/abel-diagnose.md +10 -1
- package/prompts/abel-implement.md +118 -20
- package/prompts/abel-init.md +9 -2
- package/src/activity-contracts.ts +82 -0
- package/src/apply-transaction.ts +16 -14
- package/src/artifact-store.ts +17 -0
- package/src/candidate-artifact.ts +143 -0
- package/src/candidate-patch.ts +33 -12
- package/src/candidate-workspace.ts +74 -0
- package/src/canonical.ts +16 -0
- package/src/change-contract-schema.ts +191 -0
- package/src/change-contract.ts +273 -0
- package/src/change-verification.ts +896 -0
- package/src/child-model.ts +129 -0
- package/src/child-session.ts +296 -328
- package/src/contracts.ts +325 -109
- package/src/control-contracts.ts +51 -1
- package/src/delivery-compiler.ts +241 -141
- package/src/delivery-revision.ts +42 -0
- package/src/design-control.ts +182 -15
- package/src/design-diagnostics.ts +150 -0
- package/src/design-journal.ts +216 -72
- package/src/durable-contracts.ts +210 -0
- package/src/durable-workflow.ts +963 -0
- package/src/evidence-draft.ts +122 -0
- package/src/implement-graph.ts +14 -16
- package/src/implement-plan.ts +70 -0
- package/src/index.ts +341 -1405
- package/src/isolation-backend.ts +84 -23
- package/src/model-source.ts +22 -0
- package/src/package-candidate.ts +224 -0
- package/src/package-delivery.ts +486 -0
- package/src/package-verification.ts +509 -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/route-policy.ts +1 -11
- package/src/run-state.ts +4 -16
- package/src/run-store.ts +5 -144
- package/src/scoped-grep-worker.mjs +23 -0
- package/src/scoped-tools.ts +312 -116
- package/src/sqlite-schema.ts +171 -0
- package/src/storage-schema.ts +352 -0
- package/src/subagent-activity.ts +46 -67
- package/src/submit-tool.ts +40 -61
- package/src/task-ledger.ts +14 -55
- package/src/transport-budget.ts +32 -0
- package/src/verification-capability.ts +298 -102
- package/src/verification-environment.ts +213 -0
- package/src/verification-identity.ts +77 -0
- package/src/worker-broker.ts +134 -91
- package/src/workflow-engine.ts +28 -8285
- package/src/workflow-policy.ts +1388 -0
- package/src/workflow-recovery-policy.ts +95 -0
- package/src/workflow-scheduling.ts +105 -0
- package/src/workflow-state-machine.ts +4086 -0
- package/src/workflow-status.ts +384 -0
- package/src/workspace-io-entry.mjs +58 -0
- package/src/workspace-io-worker.mjs +991 -0
- package/src/workspace-io.ts +84 -0
- package/src/workspace-store.ts +125 -19
- package/skills/abel-workflow/SKILL.md +0 -205
- 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;
|
|
@@ -150,8 +152,14 @@ export const VERIFICATION_ADAPTER_CODES = [
|
|
|
150
152
|
"script-command-mismatch",
|
|
151
153
|
"script-missing",
|
|
152
154
|
"script-unsafe",
|
|
155
|
+
"report-arguments-conflict",
|
|
156
|
+
"verification-config-mismatch",
|
|
157
|
+
"verification-config-unsafe",
|
|
153
158
|
] as const;
|
|
154
159
|
export const CHILD_TRANSPORT_CODES = [
|
|
160
|
+
"first-progress-timeout",
|
|
161
|
+
"stream-idle-timeout",
|
|
162
|
+
"attempt-timeout",
|
|
155
163
|
"child-no-final-assistant",
|
|
156
164
|
"child-provider-stream-aborted",
|
|
157
165
|
"child-provider-stream-error",
|
|
@@ -234,6 +242,10 @@ export type CandidateFailure =
|
|
|
234
242
|
| { kind: "result-limit"; limitBytes: number };
|
|
235
243
|
|
|
236
244
|
export type ChildFailure =
|
|
245
|
+
| {
|
|
246
|
+
kind: "execution-limit";
|
|
247
|
+
code: "child-turn-limit" | "child-context-limit";
|
|
248
|
+
}
|
|
237
249
|
| CandidateFailure
|
|
238
250
|
| {
|
|
239
251
|
kind: "transport";
|
|
@@ -249,10 +261,35 @@ export type VerificationClassification =
|
|
|
249
261
|
| "expected-green"
|
|
250
262
|
| "expected-refactor";
|
|
251
263
|
|
|
264
|
+
/** Runtime-owned evidence; unavailable checks never become product baselines. */
|
|
265
|
+
export interface VerificationEvidence {
|
|
266
|
+
id: string;
|
|
267
|
+
exitCode: number;
|
|
268
|
+
classification: VerificationClassification;
|
|
269
|
+
tests?: number;
|
|
270
|
+
failureIdentities: string[];
|
|
271
|
+
policy: "report-file-v3";
|
|
272
|
+
}
|
|
273
|
+
export type VerificationObservation =
|
|
274
|
+
| { kind: "accepted"; evidence: VerificationEvidence }
|
|
275
|
+
| {
|
|
276
|
+
kind: "rejected";
|
|
277
|
+
code: "red-not-witnessed" | "verification-rejected";
|
|
278
|
+
evidence: VerificationEvidence;
|
|
279
|
+
}
|
|
280
|
+
| {
|
|
281
|
+
kind: "unavailable";
|
|
282
|
+
category: "environment" | "adapter" | "resource";
|
|
283
|
+
code: string;
|
|
284
|
+
verificationId: string;
|
|
285
|
+
}
|
|
286
|
+
| { kind: "cancelled" };
|
|
287
|
+
|
|
252
288
|
interface VerificationBase {
|
|
253
289
|
id: string;
|
|
254
290
|
classification: VerificationClassification;
|
|
255
291
|
expectedFailure?: string;
|
|
292
|
+
executionBindings?: Record<string, string | null>;
|
|
256
293
|
}
|
|
257
294
|
|
|
258
295
|
export type PackageManager = "bun" | "npm" | "pnpm" | "yarn";
|
|
@@ -338,6 +375,7 @@ export interface PhaseBoundary {
|
|
|
338
375
|
}
|
|
339
376
|
|
|
340
377
|
export interface TaskBoundary {
|
|
378
|
+
verificationMode?: "behavior" | "mechanical" | "refactor";
|
|
341
379
|
changeId: string;
|
|
342
380
|
taskId: string;
|
|
343
381
|
dependsOn: string[];
|
|
@@ -408,6 +446,9 @@ type ImplementGraphDiagnosticDetails = Partial<{
|
|
|
408
446
|
producerTaskId: string;
|
|
409
447
|
producerPhase: ImplementationPhase;
|
|
410
448
|
dependencyTaskId: string;
|
|
449
|
+
field: string;
|
|
450
|
+
expectedPaths: string[];
|
|
451
|
+
actualPaths: string[];
|
|
411
452
|
}>;
|
|
412
453
|
|
|
413
454
|
export type ImplementGraphReadinessDiagnostic =
|
|
@@ -449,6 +490,10 @@ export interface PacketEnvelope {
|
|
|
449
490
|
output: "evidence" | "diff";
|
|
450
491
|
}
|
|
451
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
|
+
|
|
452
497
|
export function isValidRelativePath(p: unknown): p is string {
|
|
453
498
|
return (
|
|
454
499
|
typeof p === "string" &&
|
|
@@ -512,7 +557,7 @@ function validScriptCommand(value: unknown): value is string {
|
|
|
512
557
|
value.length > 0 &&
|
|
513
558
|
value.length <= 1024 &&
|
|
514
559
|
value.trim() === value &&
|
|
515
|
-
!
|
|
560
|
+
!value.includes("\0")
|
|
516
561
|
);
|
|
517
562
|
}
|
|
518
563
|
|
|
@@ -575,6 +620,7 @@ function validateExecutableRunner(value: unknown): value is ExecutableRunner {
|
|
|
575
620
|
|
|
576
621
|
function validateVitestRunner(value: ExecutableRunner): boolean {
|
|
577
622
|
if (value.kind === "package-script") {
|
|
623
|
+
if (UNSAFE_VERIFICATION_TOKEN.test(value.command)) return false;
|
|
578
624
|
const tokens = value.command.split(/\s+/u);
|
|
579
625
|
return (
|
|
580
626
|
tokens[0] === "vitest" ||
|
|
@@ -591,8 +637,23 @@ function validateAtomicVerification(
|
|
|
591
637
|
): value is AtomicVerificationContract {
|
|
592
638
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
593
639
|
const contract = value as Record<string, unknown>;
|
|
594
|
-
|
|
595
|
-
contract.
|
|
640
|
+
if (
|
|
641
|
+
contract.executionBindings !== undefined &&
|
|
642
|
+
(!contract.executionBindings ||
|
|
643
|
+
typeof contract.executionBindings !== "object" ||
|
|
644
|
+
Array.isArray(contract.executionBindings) ||
|
|
645
|
+
Object.entries(contract.executionBindings).some(
|
|
646
|
+
([key, hash]) =>
|
|
647
|
+
!VERIFICATION_CONFIGURATION_PATHS.includes(key) ||
|
|
648
|
+
(hash !== null &&
|
|
649
|
+
(typeof hash !== "string" || !/^[a-f0-9]{64}$/u.test(hash))),
|
|
650
|
+
))
|
|
651
|
+
)
|
|
652
|
+
return false;
|
|
653
|
+
const expectedFailure = [
|
|
654
|
+
...(contract.classification === "expected-red" ? ["expectedFailure"] : []),
|
|
655
|
+
...(contract.executionBindings === undefined ? [] : ["executionBindings"]),
|
|
656
|
+
];
|
|
596
657
|
if (contract.kind === "vitest") {
|
|
597
658
|
const executableRunner = contract.runner as ExecutableRunner;
|
|
598
659
|
return (
|
|
@@ -610,8 +671,8 @@ function validateAtomicVerification(
|
|
|
610
671
|
validateExecutableRunner(contract.runner) &&
|
|
611
672
|
validateVitestRunner(executableRunner) &&
|
|
612
673
|
Array.isArray(contract.testFiles) &&
|
|
613
|
-
contract.testFiles.length
|
|
614
|
-
contract.testFiles.length <=
|
|
674
|
+
contract.testFiles.length >= VERIFICATION_LIMITS.minTestFiles &&
|
|
675
|
+
contract.testFiles.length <= VERIFICATION_LIMITS.maxTestFiles &&
|
|
615
676
|
contract.testFiles.every(isValidRelativePath) &&
|
|
616
677
|
new Set(contract.testFiles).size === contract.testFiles.length &&
|
|
617
678
|
validVerificationArgs(contract.args) &&
|
|
@@ -679,8 +740,8 @@ function validateStructuredVerification(
|
|
|
679
740
|
String(contract.classification),
|
|
680
741
|
) ||
|
|
681
742
|
!Array.isArray(contract.steps) ||
|
|
682
|
-
contract.steps.length
|
|
683
|
-
contract.steps.length >
|
|
743
|
+
contract.steps.length < VERIFICATION_LIMITS.minSteps ||
|
|
744
|
+
contract.steps.length > VERIFICATION_LIMITS.maxSteps ||
|
|
684
745
|
!contract.steps.every((step) => validateAtomicVerification(step))
|
|
685
746
|
) {
|
|
686
747
|
return false;
|
|
@@ -719,6 +780,38 @@ export function verificationSteps(
|
|
|
719
780
|
: [structuredClone(normalized.value)];
|
|
720
781
|
}
|
|
721
782
|
|
|
783
|
+
export const VERIFICATION_CONFIGURATION_PATHS: readonly string[] = [
|
|
784
|
+
"package.json",
|
|
785
|
+
"package-lock.json",
|
|
786
|
+
"npm-shrinkwrap.json",
|
|
787
|
+
"bun.lock",
|
|
788
|
+
"bun.lockb",
|
|
789
|
+
"pnpm-lock.yaml",
|
|
790
|
+
"yarn.lock",
|
|
791
|
+
".npmrc",
|
|
792
|
+
".yarnrc",
|
|
793
|
+
".yarnrc.yml",
|
|
794
|
+
"bunfig.toml",
|
|
795
|
+
"pnpm-workspace.yaml",
|
|
796
|
+
];
|
|
797
|
+
|
|
798
|
+
export function verificationBoundInputPaths(
|
|
799
|
+
verification: VerificationContract,
|
|
800
|
+
): string[] {
|
|
801
|
+
return [
|
|
802
|
+
...new Set([
|
|
803
|
+
...verificationInputPaths(verification),
|
|
804
|
+
...verificationSteps(verification).flatMap((step) =>
|
|
805
|
+
step.kind === "vitest" ||
|
|
806
|
+
step.kind === "package-script" ||
|
|
807
|
+
("runner" in step && step.runner.kind === "package-script")
|
|
808
|
+
? VERIFICATION_CONFIGURATION_PATHS
|
|
809
|
+
: [],
|
|
810
|
+
),
|
|
811
|
+
]),
|
|
812
|
+
];
|
|
813
|
+
}
|
|
814
|
+
|
|
722
815
|
export function verificationInputPaths(
|
|
723
816
|
verification: VerificationContract,
|
|
724
817
|
): string[] {
|
|
@@ -976,6 +1069,7 @@ function isWithinRoots(path: string, roots: string[]): boolean {
|
|
|
976
1069
|
function validatePhaseBoundary(
|
|
977
1070
|
value: unknown,
|
|
978
1071
|
phase: ImplementationPhase,
|
|
1072
|
+
mode: unknown = "behavior",
|
|
979
1073
|
): value is PhaseBoundary {
|
|
980
1074
|
if (
|
|
981
1075
|
!hasExactKeys(
|
|
@@ -1002,7 +1096,10 @@ function validatePhaseBoundary(
|
|
|
1002
1096
|
const verification = validateVerificationContract(value.verification);
|
|
1003
1097
|
if (!verification.ok) return false;
|
|
1004
1098
|
const classification = {
|
|
1005
|
-
red:
|
|
1099
|
+
red:
|
|
1100
|
+
mode !== undefined && mode !== "behavior"
|
|
1101
|
+
? "expected-green"
|
|
1102
|
+
: "expected-red",
|
|
1006
1103
|
green: "expected-green",
|
|
1007
1104
|
refactor: "expected-refactor",
|
|
1008
1105
|
} as const;
|
|
@@ -1014,19 +1111,27 @@ function validateTaskBoundary(
|
|
|
1014
1111
|
snapshot: unknown,
|
|
1015
1112
|
): value is TaskBoundary {
|
|
1016
1113
|
if (
|
|
1017
|
-
!hasExactKeys(
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1114
|
+
!hasExactKeys(
|
|
1115
|
+
value,
|
|
1116
|
+
[
|
|
1117
|
+
"changeId",
|
|
1118
|
+
"taskId",
|
|
1119
|
+
"dependsOn",
|
|
1120
|
+
"objective",
|
|
1121
|
+
"context",
|
|
1122
|
+
"roots",
|
|
1123
|
+
"phases",
|
|
1124
|
+
"scheduling",
|
|
1125
|
+
"agents",
|
|
1126
|
+
"approvedDependencies",
|
|
1127
|
+
"impactClosure",
|
|
1128
|
+
],
|
|
1129
|
+
["verificationMode"],
|
|
1130
|
+
) ||
|
|
1131
|
+
(value.verificationMode !== undefined &&
|
|
1132
|
+
!["behavior", "mechanical", "refactor"].includes(
|
|
1133
|
+
String(value.verificationMode),
|
|
1134
|
+
)) ||
|
|
1030
1135
|
!validIdentifier(value.changeId) ||
|
|
1031
1136
|
!validIdentifier(value.taskId) ||
|
|
1032
1137
|
!validateIdentifierSet(value.dependsOn) ||
|
|
@@ -1038,7 +1143,7 @@ function validateTaskBoundary(
|
|
|
1038
1143
|
typeof value.context.contract !== "string" ||
|
|
1039
1144
|
!validateRoots(value.roots) ||
|
|
1040
1145
|
!hasExactKeys(value.phases, ["red", "green"], ["refactor"]) ||
|
|
1041
|
-
!validatePhaseBoundary(value.phases.red, "red") ||
|
|
1146
|
+
!validatePhaseBoundary(value.phases.red, "red", value.verificationMode) ||
|
|
1042
1147
|
!validatePhaseBoundary(value.phases.green, "green") ||
|
|
1043
1148
|
(value.phases.refactor !== undefined &&
|
|
1044
1149
|
!validatePhaseBoundary(value.phases.refactor, "refactor")) ||
|
|
@@ -1109,6 +1214,9 @@ export type ImplementGraphContractDiagnostic =
|
|
|
1109
1214
|
field?: string;
|
|
1110
1215
|
category?: string;
|
|
1111
1216
|
outputId?: string;
|
|
1217
|
+
path?: string;
|
|
1218
|
+
expectedPaths?: string[];
|
|
1219
|
+
actualPaths?: string[];
|
|
1112
1220
|
}
|
|
1113
1221
|
| {
|
|
1114
1222
|
code: "invalid-output-path";
|
|
@@ -1120,6 +1228,7 @@ export type ImplementGraphContractDiagnostic =
|
|
|
1120
1228
|
function phaseBoundaryContractDiagnostic(
|
|
1121
1229
|
value: unknown,
|
|
1122
1230
|
phase: ImplementationPhase,
|
|
1231
|
+
mode: unknown = "behavior",
|
|
1123
1232
|
): Omit<
|
|
1124
1233
|
Extract<
|
|
1125
1234
|
ImplementGraphContractDiagnostic,
|
|
@@ -1176,7 +1285,10 @@ function phaseBoundaryContractDiagnostic(
|
|
|
1176
1285
|
};
|
|
1177
1286
|
}
|
|
1178
1287
|
const classification = {
|
|
1179
|
-
red:
|
|
1288
|
+
red:
|
|
1289
|
+
mode !== undefined && mode !== "behavior"
|
|
1290
|
+
? "expected-green"
|
|
1291
|
+
: "expected-red",
|
|
1180
1292
|
green: "expected-green",
|
|
1181
1293
|
refactor: "expected-refactor",
|
|
1182
1294
|
} as const;
|
|
@@ -1227,22 +1339,33 @@ function taskBoundaryContractDiagnostic(
|
|
|
1227
1339
|
category,
|
|
1228
1340
|
});
|
|
1229
1341
|
if (
|
|
1230
|
-
!hasExactKeys(
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1342
|
+
!hasExactKeys(
|
|
1343
|
+
value,
|
|
1344
|
+
[
|
|
1345
|
+
"changeId",
|
|
1346
|
+
"taskId",
|
|
1347
|
+
"dependsOn",
|
|
1348
|
+
"objective",
|
|
1349
|
+
"context",
|
|
1350
|
+
"roots",
|
|
1351
|
+
"phases",
|
|
1352
|
+
"scheduling",
|
|
1353
|
+
"agents",
|
|
1354
|
+
"approvedDependencies",
|
|
1355
|
+
"impactClosure",
|
|
1356
|
+
],
|
|
1357
|
+
["verificationMode"],
|
|
1358
|
+
)
|
|
1243
1359
|
) {
|
|
1244
1360
|
return diagnostic("task", "shape");
|
|
1245
1361
|
}
|
|
1362
|
+
if (
|
|
1363
|
+
value.verificationMode !== undefined &&
|
|
1364
|
+
!["behavior", "mechanical", "refactor"].includes(
|
|
1365
|
+
String(value.verificationMode),
|
|
1366
|
+
)
|
|
1367
|
+
)
|
|
1368
|
+
return diagnostic("verificationMode", "shape");
|
|
1246
1369
|
if (!validIdentifier(value.changeId)) {
|
|
1247
1370
|
return diagnostic("changeId", "identifier");
|
|
1248
1371
|
}
|
|
@@ -1275,7 +1398,11 @@ function taskBoundaryContractDiagnostic(
|
|
|
1275
1398
|
for (const phase of IMPLEMENTATION_PHASES) {
|
|
1276
1399
|
const boundary = value.phases[phase];
|
|
1277
1400
|
if (phase === "refactor" && boundary === undefined) continue;
|
|
1278
|
-
const phaseDiagnostic = phaseBoundaryContractDiagnostic(
|
|
1401
|
+
const phaseDiagnostic = phaseBoundaryContractDiagnostic(
|
|
1402
|
+
boundary,
|
|
1403
|
+
phase,
|
|
1404
|
+
value.verificationMode,
|
|
1405
|
+
);
|
|
1279
1406
|
if (phaseDiagnostic)
|
|
1280
1407
|
return { ...diagnostic("phases", "contract"), ...phaseDiagnostic };
|
|
1281
1408
|
}
|
|
@@ -1348,6 +1475,26 @@ function taskBoundaryContractDiagnostic(
|
|
|
1348
1475
|
snapshot,
|
|
1349
1476
|
);
|
|
1350
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
|
+
}
|
|
1351
1498
|
return diagnostic("impactClosure", impactClosureCategory(impactReason));
|
|
1352
1499
|
}
|
|
1353
1500
|
return validateTaskBoundary(value, snapshot)
|
|
@@ -1361,6 +1508,7 @@ export function validateImplementGraphBoundary(value: unknown):
|
|
|
1361
1508
|
ok: false;
|
|
1362
1509
|
reason: string;
|
|
1363
1510
|
diagnostic: ImplementGraphContractDiagnostic;
|
|
1511
|
+
diagnostics?: ImplementGraphContractDiagnostic[];
|
|
1364
1512
|
} {
|
|
1365
1513
|
if (
|
|
1366
1514
|
!hasExactKeys(value, ["changeId", "tasks", "outputs"]) ||
|
|
@@ -1370,29 +1518,33 @@ export function validateImplementGraphBoundary(value: unknown):
|
|
|
1370
1518
|
value.tasks.length > 128 ||
|
|
1371
1519
|
!Array.isArray(value.outputs) ||
|
|
1372
1520
|
value.outputs.length > 512
|
|
1373
|
-
)
|
|
1521
|
+
)
|
|
1374
1522
|
return {
|
|
1375
1523
|
ok: false,
|
|
1376
1524
|
reason: "invalid Implement graph boundary",
|
|
1377
1525
|
diagnostic: { code: "invalid-implement-graph" },
|
|
1378
1526
|
};
|
|
1379
|
-
}
|
|
1380
1527
|
|
|
1528
|
+
// Collect independent task/output failures without admitting a partial graph.
|
|
1529
|
+
const failures: Array<{
|
|
1530
|
+
reason: string;
|
|
1531
|
+
diagnostic: ImplementGraphContractDiagnostic;
|
|
1532
|
+
}> = [];
|
|
1381
1533
|
for (const candidate of value.tasks) {
|
|
1382
1534
|
if (
|
|
1383
1535
|
!candidate ||
|
|
1384
1536
|
typeof candidate !== "object" ||
|
|
1385
1537
|
Array.isArray(candidate)
|
|
1386
1538
|
) {
|
|
1387
|
-
|
|
1388
|
-
ok: false,
|
|
1539
|
+
failures.push({
|
|
1389
1540
|
reason: "invalid Implement task boundary",
|
|
1390
1541
|
diagnostic: {
|
|
1391
1542
|
code: "invalid-implement-graph",
|
|
1392
1543
|
field: "tasks",
|
|
1393
1544
|
category: "shape",
|
|
1394
1545
|
},
|
|
1395
|
-
};
|
|
1546
|
+
});
|
|
1547
|
+
continue;
|
|
1396
1548
|
}
|
|
1397
1549
|
const task = candidate as Record<string, unknown>;
|
|
1398
1550
|
const diagnostic = Object.hasOwn(task, "changeId")
|
|
@@ -1405,31 +1557,27 @@ export function validateImplementGraphBoundary(value: unknown):
|
|
|
1405
1557
|
{ changeId: value.changeId, ...task },
|
|
1406
1558
|
undefined,
|
|
1407
1559
|
);
|
|
1408
|
-
if (diagnostic)
|
|
1409
|
-
|
|
1410
|
-
ok: false,
|
|
1560
|
+
if (diagnostic)
|
|
1561
|
+
failures.push({
|
|
1411
1562
|
reason: "invalid Implement task boundary",
|
|
1412
1563
|
diagnostic: { code: "invalid-implement-graph", ...diagnostic },
|
|
1413
|
-
};
|
|
1414
|
-
}
|
|
1564
|
+
});
|
|
1415
1565
|
}
|
|
1416
|
-
|
|
1417
1566
|
for (const candidate of value.outputs) {
|
|
1418
1567
|
if (
|
|
1419
1568
|
!candidate ||
|
|
1420
1569
|
typeof candidate !== "object" ||
|
|
1421
1570
|
Array.isArray(candidate)
|
|
1422
1571
|
) {
|
|
1423
|
-
|
|
1424
|
-
ok: false,
|
|
1572
|
+
failures.push({
|
|
1425
1573
|
reason: "invalid Implement graph output",
|
|
1426
1574
|
diagnostic: { code: "invalid-implement-graph" },
|
|
1427
|
-
};
|
|
1575
|
+
});
|
|
1576
|
+
continue;
|
|
1428
1577
|
}
|
|
1429
1578
|
const output = candidate as Record<string, unknown>;
|
|
1430
1579
|
if (!isValidRelativePath(output.path)) {
|
|
1431
|
-
|
|
1432
|
-
ok: false,
|
|
1580
|
+
failures.push({
|
|
1433
1581
|
reason: "invalid Implement graph output path",
|
|
1434
1582
|
diagnostic: {
|
|
1435
1583
|
code: "invalid-output-path",
|
|
@@ -1437,7 +1585,8 @@ export function validateImplementGraphBoundary(value: unknown):
|
|
|
1437
1585
|
field: "outputs.path",
|
|
1438
1586
|
category: "path",
|
|
1439
1587
|
},
|
|
1440
|
-
};
|
|
1588
|
+
});
|
|
1589
|
+
continue;
|
|
1441
1590
|
}
|
|
1442
1591
|
if (
|
|
1443
1592
|
!hasExactKeys(output, ["id", "path", "producer", "postcondition"]) ||
|
|
@@ -1449,8 +1598,7 @@ export function validateImplementGraphBoundary(value: unknown):
|
|
|
1449
1598
|
) ||
|
|
1450
1599
|
output.postcondition !== "regular-file"
|
|
1451
1600
|
) {
|
|
1452
|
-
|
|
1453
|
-
ok: false,
|
|
1601
|
+
failures.push({
|
|
1454
1602
|
reason: "invalid Implement graph output",
|
|
1455
1603
|
diagnostic: {
|
|
1456
1604
|
code: "invalid-implement-graph",
|
|
@@ -1458,10 +1606,18 @@ export function validateImplementGraphBoundary(value: unknown):
|
|
|
1458
1606
|
field: "outputs",
|
|
1459
1607
|
category: "shape",
|
|
1460
1608
|
},
|
|
1461
|
-
};
|
|
1609
|
+
});
|
|
1462
1610
|
}
|
|
1463
1611
|
}
|
|
1464
|
-
|
|
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
|
+
};
|
|
1465
1621
|
return {
|
|
1466
1622
|
ok: true,
|
|
1467
1623
|
value: structuredClone(value) as unknown as ImplementGraphBoundary,
|
|
@@ -1868,6 +2024,24 @@ export interface DiffResult {
|
|
|
1868
2024
|
contractCompliant: boolean;
|
|
1869
2025
|
}
|
|
1870
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
|
+
|
|
1871
2045
|
export function validateEvidenceResult(value: unknown): {
|
|
1872
2046
|
ok: boolean;
|
|
1873
2047
|
reason?: string;
|
|
@@ -1891,7 +2065,7 @@ export function validateEvidenceResult(value: unknown): {
|
|
|
1891
2065
|
"hints",
|
|
1892
2066
|
] as const;
|
|
1893
2067
|
if (!hasExactKeys(r, fields)) {
|
|
1894
|
-
return
|
|
2068
|
+
return evidenceFieldsFailure(r, fields, "result");
|
|
1895
2069
|
}
|
|
1896
2070
|
if (!validIdentifier(r.id) || typeof r.role !== "string")
|
|
1897
2071
|
return { ok: false, reason: "invalid identity" };
|
|
@@ -1911,25 +2085,38 @@ export function validateEvidenceResult(value: unknown): {
|
|
|
1911
2085
|
return { ok: false, reason: `invalid evidence field: ${field}` };
|
|
1912
2086
|
}
|
|
1913
2087
|
}
|
|
1914
|
-
if (
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
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
|
+
};
|
|
1933
2120
|
const serialized = JSON.stringify(r);
|
|
1934
2121
|
if (Buffer.byteLength(serialized, "utf8") > LIMITS.maxCompleteResultBytes) {
|
|
1935
2122
|
return {
|
|
@@ -1970,36 +2157,65 @@ function validateDesignEvidenceResult(r: Record<string, unknown>): {
|
|
|
1970
2157
|
"success_criteria_hints",
|
|
1971
2158
|
] as const;
|
|
1972
2159
|
if (!hasExactKeys(r, fields)) {
|
|
1973
|
-
return
|
|
1974
|
-
}
|
|
1975
|
-
if (
|
|
1976
|
-
!validIdentifier(r.id) ||
|
|
1977
|
-
r.packet_id !== r.id ||
|
|
1978
|
-
!isValidRelativePath(r.module_name)
|
|
1979
|
-
) {
|
|
1980
|
-
return { ok: false, reason: "invalid Design evidence identity" };
|
|
2160
|
+
return evidenceFieldsFailure(r, fields, "result");
|
|
1981
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
|
+
};
|
|
1982
2171
|
if (!validatePathSet(r.scope) || r.scope.length === 0) {
|
|
1983
|
-
return {
|
|
2172
|
+
return {
|
|
2173
|
+
ok: false,
|
|
2174
|
+
reason:
|
|
2175
|
+
"scope: expected a nonempty set of unique canonical relative paths",
|
|
2176
|
+
};
|
|
1984
2177
|
}
|
|
1985
|
-
|
|
1986
|
-
|
|
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
|
+
};
|
|
1987
2184
|
}
|
|
1988
|
-
if (
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
entry
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
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
|
|
2000
2206
|
)
|
|
2001
|
-
|
|
2002
|
-
|
|
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
|
+
};
|
|
2003
2219
|
}
|
|
2004
2220
|
for (const field of [
|
|
2005
2221
|
"existing_structures",
|