@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
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { DesignPlanValidationError } from "./design-diagnostics.ts";
|
|
2
|
+
import type {
|
|
3
|
+
DraftVerification,
|
|
4
|
+
PlanDraft,
|
|
5
|
+
PlanTaskInput,
|
|
6
|
+
} from "./plan-draft.ts";
|
|
7
|
+
|
|
8
|
+
/** Small author input. Evidence, scope and the complete suite remain explicit. */
|
|
9
|
+
export interface SingleTaskDraft {
|
|
10
|
+
changeId: string;
|
|
11
|
+
changeContract?: PlanDraft["changeContract"];
|
|
12
|
+
singleTask: {
|
|
13
|
+
taskId: string;
|
|
14
|
+
objective: string;
|
|
15
|
+
context: PlanTaskInput["context"];
|
|
16
|
+
roots: string[];
|
|
17
|
+
read: string[];
|
|
18
|
+
greenWrite: string[];
|
|
19
|
+
redWrite?: string[];
|
|
20
|
+
expectedFailure?: string;
|
|
21
|
+
verificationMode?: "behavior" | "mechanical" | "refactor";
|
|
22
|
+
verification: DraftVerification;
|
|
23
|
+
fullSuite: DraftVerification;
|
|
24
|
+
impactClosure: PlanTaskInput["impactClosure"];
|
|
25
|
+
agents: PlanTaskInput["agents"];
|
|
26
|
+
recovery?: { artifactAttempts: number; repairAttempts: number };
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function invalid(code: string, field = "singleTask"): never {
|
|
31
|
+
throw new DesignPlanValidationError(code, [{ code, field }]);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function record(value: unknown): value is Record<string, unknown> {
|
|
35
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Mechanical expansion only; the ordinary compiler validates all resulting fields. */
|
|
39
|
+
export function expandSingleTaskDraft(value: unknown): unknown {
|
|
40
|
+
if (!record(value) || !Object.hasOwn(value, "singleTask")) return value;
|
|
41
|
+
if (
|
|
42
|
+
Object.keys(value).some(
|
|
43
|
+
(key) => !["changeId", "changeContract", "singleTask"].includes(key),
|
|
44
|
+
) ||
|
|
45
|
+
!record(value.singleTask)
|
|
46
|
+
)
|
|
47
|
+
invalid("single-task-draft-invalid");
|
|
48
|
+
const input = value.singleTask;
|
|
49
|
+
const required = [
|
|
50
|
+
"taskId",
|
|
51
|
+
"objective",
|
|
52
|
+
"context",
|
|
53
|
+
"roots",
|
|
54
|
+
"read",
|
|
55
|
+
"greenWrite",
|
|
56
|
+
"verification",
|
|
57
|
+
"fullSuite",
|
|
58
|
+
"impactClosure",
|
|
59
|
+
"agents",
|
|
60
|
+
];
|
|
61
|
+
const optional = [
|
|
62
|
+
"redWrite",
|
|
63
|
+
"expectedFailure",
|
|
64
|
+
"verificationMode",
|
|
65
|
+
"recovery",
|
|
66
|
+
];
|
|
67
|
+
if (
|
|
68
|
+
required.some((key) => !Object.hasOwn(input, key)) ||
|
|
69
|
+
Object.keys(input).some((key) => ![...required, ...optional].includes(key))
|
|
70
|
+
)
|
|
71
|
+
invalid("single-task-draft-invalid");
|
|
72
|
+
const task = structuredClone(
|
|
73
|
+
input,
|
|
74
|
+
) as unknown as SingleTaskDraft["singleTask"];
|
|
75
|
+
const mode = task.verificationMode ?? "behavior";
|
|
76
|
+
if (mode === "behavior" && (!task.redWrite?.length || !task.expectedFailure))
|
|
77
|
+
invalid("single-task-red-required");
|
|
78
|
+
if (
|
|
79
|
+
mode !== "behavior" &&
|
|
80
|
+
(task.redWrite !== undefined || task.expectedFailure !== undefined)
|
|
81
|
+
)
|
|
82
|
+
invalid("single-task-red-unexpected");
|
|
83
|
+
if (
|
|
84
|
+
Object.hasOwn(input, "recovery") &&
|
|
85
|
+
(!record(task.recovery) ||
|
|
86
|
+
!Object.hasOwn(task.recovery, "artifactAttempts") ||
|
|
87
|
+
!Object.hasOwn(task.recovery, "repairAttempts") ||
|
|
88
|
+
Object.keys(task.recovery).some(
|
|
89
|
+
(key) => !["artifactAttempts", "repairAttempts"].includes(key),
|
|
90
|
+
))
|
|
91
|
+
)
|
|
92
|
+
invalid("single-task-recovery-invalid");
|
|
93
|
+
return {
|
|
94
|
+
changeId: value.changeId,
|
|
95
|
+
...(value.changeContract === undefined
|
|
96
|
+
? {}
|
|
97
|
+
: { changeContract: value.changeContract }),
|
|
98
|
+
tasks: [
|
|
99
|
+
{
|
|
100
|
+
taskId: task.taskId,
|
|
101
|
+
objective: task.objective,
|
|
102
|
+
context: task.context,
|
|
103
|
+
roots: task.roots,
|
|
104
|
+
read: task.read,
|
|
105
|
+
dependsOn: [],
|
|
106
|
+
verificationMode: mode,
|
|
107
|
+
phases: {
|
|
108
|
+
...(mode === "behavior"
|
|
109
|
+
? {
|
|
110
|
+
red: {
|
|
111
|
+
write: task.redWrite,
|
|
112
|
+
delete: [],
|
|
113
|
+
verification: {
|
|
114
|
+
...task.verification,
|
|
115
|
+
expectedFailure: task.expectedFailure,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
: {}),
|
|
120
|
+
green: {
|
|
121
|
+
write: task.greenWrite,
|
|
122
|
+
delete: [],
|
|
123
|
+
verification: task.verification,
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
affectedVerification: task.fullSuite,
|
|
127
|
+
repairVerification: task.fullSuite,
|
|
128
|
+
scheduling: { conflicts: [], resources: [] },
|
|
129
|
+
agents: task.agents,
|
|
130
|
+
approvedDependencies: [],
|
|
131
|
+
impactClosure: task.impactClosure,
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
outputs: [],
|
|
135
|
+
verification: {
|
|
136
|
+
baseline: { fullSuite: task.fullSuite },
|
|
137
|
+
change: { fullSuite: task.fullSuite, postApply: task.fullSuite },
|
|
138
|
+
artifactCorrection: { maxAttempts: task.recovery?.artifactAttempts ?? 2 },
|
|
139
|
+
repair: { maxAttempts: task.recovery?.repairAttempts ?? 1 },
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
package/src/subagent-activity.ts
CHANGED
|
@@ -12,8 +12,11 @@ import {
|
|
|
12
12
|
import type {
|
|
13
13
|
PacketActivityEvent,
|
|
14
14
|
PacketActivityObserver,
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
} from "./activity-contracts.ts";
|
|
16
|
+
import {
|
|
17
|
+
projectDesignDiagnostic,
|
|
18
|
+
type SafeDesignDiagnostic,
|
|
19
|
+
} from "./design-diagnostics.ts";
|
|
17
20
|
|
|
18
21
|
export const ACTIVITY_DETAILS_KEY = "activityDisplay" as const;
|
|
19
22
|
export const ACTIVITY_WIDGET_KEY = "abel-subagents" as const;
|
|
@@ -22,51 +25,20 @@ export const ACTIVITY_REFRESH_MS = 100;
|
|
|
22
25
|
export const ACTIVITY_WIDGET_MAX_LINES = 12;
|
|
23
26
|
export const ACTIVITY_WIDGET_MIN_WIDTH = 3;
|
|
24
27
|
|
|
25
|
-
export
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"rejected",
|
|
40
|
-
"completed",
|
|
41
|
-
] as const;
|
|
42
|
-
|
|
43
|
-
export type WorkflowActivityState = (typeof WORKFLOW_ACTIVITY_STATES)[number];
|
|
44
|
-
export type ActivityState = PacketActivityState | WorkflowActivityState;
|
|
45
|
-
export type TerminalActivityState =
|
|
46
|
-
| Exclude<
|
|
47
|
-
PacketActivityState,
|
|
48
|
-
| "queued"
|
|
49
|
-
| "connecting"
|
|
50
|
-
| "waiting-first-response"
|
|
51
|
-
| "running"
|
|
52
|
-
| "retrying"
|
|
53
|
-
>
|
|
54
|
-
| WorkflowActivityState;
|
|
55
|
-
|
|
56
|
-
export interface WorkflowActivityUpdate {
|
|
57
|
-
state: WorkflowActivityState;
|
|
58
|
-
stage?: "abel-design" | "abel-implement";
|
|
59
|
-
runId?: string;
|
|
60
|
-
change?: string;
|
|
61
|
-
taskId?: string;
|
|
62
|
-
phase?: string;
|
|
63
|
-
objective?: string;
|
|
64
|
-
code?: string;
|
|
65
|
-
attempt?: number;
|
|
66
|
-
maxAttempts?: number;
|
|
67
|
-
wait?: string;
|
|
68
|
-
legalCommands?: string[];
|
|
69
|
-
}
|
|
28
|
+
export type {
|
|
29
|
+
ActivityState,
|
|
30
|
+
TerminalActivityState,
|
|
31
|
+
WorkflowActivityState,
|
|
32
|
+
WorkflowActivityUpdate,
|
|
33
|
+
} from "./activity-contracts.ts";
|
|
34
|
+
export { WORKFLOW_ACTIVITY_STATES } from "./activity-contracts.ts";
|
|
35
|
+
|
|
36
|
+
import type {
|
|
37
|
+
ActivityState,
|
|
38
|
+
TerminalActivityState,
|
|
39
|
+
WorkflowActivityState,
|
|
40
|
+
WorkflowActivityUpdate,
|
|
41
|
+
} from "./activity-contracts.ts";
|
|
70
42
|
|
|
71
43
|
export interface EvidenceActivitySummary {
|
|
72
44
|
kind: "evidence";
|
|
@@ -224,7 +196,7 @@ export function sanitizeFailureReason(value: unknown, maxLength = 240): string {
|
|
|
224
196
|
function safeDesignFailure(value: unknown): {
|
|
225
197
|
operation: string;
|
|
226
198
|
code: string;
|
|
227
|
-
diagnostics:
|
|
199
|
+
diagnostics: SafeDesignDiagnostic[];
|
|
228
200
|
} | null {
|
|
229
201
|
const failure = asRecord(value);
|
|
230
202
|
if (
|
|
@@ -237,23 +209,9 @@ function safeDesignFailure(value: unknown): {
|
|
|
237
209
|
) {
|
|
238
210
|
return null;
|
|
239
211
|
}
|
|
240
|
-
const diagnostics = failure.diagnostics.flatMap((candidate) => {
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
!record ||
|
|
244
|
-
typeof record.code !== "string" ||
|
|
245
|
-
!SAFE_DESIGN_DETAIL_VALUE.test(record.code)
|
|
246
|
-
) {
|
|
247
|
-
return [];
|
|
248
|
-
}
|
|
249
|
-
return [
|
|
250
|
-
Object.fromEntries(
|
|
251
|
-
Object.entries(record).filter(
|
|
252
|
-
([, entry]) =>
|
|
253
|
-
typeof entry === "string" && SAFE_DESIGN_DETAIL_VALUE.test(entry),
|
|
254
|
-
),
|
|
255
|
-
) as Record<string, string>,
|
|
256
|
-
];
|
|
212
|
+
const diagnostics = failure.diagnostics.slice(0, 64).flatMap((candidate) => {
|
|
213
|
+
const diagnostic = projectDesignDiagnostic(candidate);
|
|
214
|
+
return diagnostic ? [diagnostic] : [];
|
|
257
215
|
});
|
|
258
216
|
return {
|
|
259
217
|
operation: failure.operation,
|
|
@@ -286,7 +244,10 @@ function renderDesignFailure(
|
|
|
286
244
|
const lines = failure.diagnostics.map((diagnostic) => {
|
|
287
245
|
const details = Object.entries(diagnostic)
|
|
288
246
|
.filter(([key]) => key !== "code")
|
|
289
|
-
.map(
|
|
247
|
+
.map(
|
|
248
|
+
([key, value]) =>
|
|
249
|
+
`${key}=${Array.isArray(value) ? JSON.stringify(value) : value}`,
|
|
250
|
+
)
|
|
290
251
|
.join(" · ");
|
|
291
252
|
return ` - ${diagnostic.code}${details ? ` · ${details}` : ""}`;
|
|
292
253
|
});
|
|
@@ -315,6 +276,7 @@ function stateGlyph(state: ActivityState, spinnerFrame = 0): string {
|
|
|
315
276
|
switch (state) {
|
|
316
277
|
case "queued":
|
|
317
278
|
return "…";
|
|
279
|
+
case "preparing":
|
|
318
280
|
case "connecting":
|
|
319
281
|
case "waiting-first-response":
|
|
320
282
|
case "running":
|
|
@@ -563,6 +525,8 @@ function workflowStateOf(
|
|
|
563
525
|
case "ready":
|
|
564
526
|
case "queued":
|
|
565
527
|
return "queued";
|
|
528
|
+
case "preparing":
|
|
529
|
+
return "preparing";
|
|
566
530
|
case "connecting":
|
|
567
531
|
return "connecting";
|
|
568
532
|
case "waiting-first-response":
|
|
@@ -681,6 +645,7 @@ function workflowWait(
|
|
|
681
645
|
) {
|
|
682
646
|
return approval.gate;
|
|
683
647
|
}
|
|
648
|
+
if (state === "preparing") return "worker-setup";
|
|
684
649
|
if (state === "connecting") return "connection";
|
|
685
650
|
if (state === "waiting-first-response") return "first-response";
|
|
686
651
|
if (state === "retrying") return "bounded-policy";
|
|
@@ -867,6 +832,7 @@ function terminalState(
|
|
|
867
832
|
} {
|
|
868
833
|
return ![
|
|
869
834
|
"queued",
|
|
835
|
+
"preparing",
|
|
870
836
|
"connecting",
|
|
871
837
|
"waiting-first-response",
|
|
872
838
|
"running",
|
package/src/submit-tool.ts
CHANGED
|
@@ -17,65 +17,16 @@ import {
|
|
|
17
17
|
validateDiffResult,
|
|
18
18
|
validateEvidenceResult,
|
|
19
19
|
} from "./contracts.ts";
|
|
20
|
+
import {
|
|
21
|
+
completeEvidenceDraft,
|
|
22
|
+
evidenceDraftSchema,
|
|
23
|
+
} from "./evidence-draft.ts";
|
|
20
24
|
import {
|
|
21
25
|
type BeginCandidateInput,
|
|
22
26
|
TASK_LEDGER_LIMITS,
|
|
23
27
|
type TaskLedger,
|
|
24
28
|
} from "./task-ledger.ts";
|
|
25
29
|
|
|
26
|
-
const compactEvidenceSchema = Type.Object({
|
|
27
|
-
id: Type.String(),
|
|
28
|
-
role: Type.String(),
|
|
29
|
-
kind: Type.Literal("evidence"),
|
|
30
|
-
conclusions: Type.Array(Type.String()),
|
|
31
|
-
citations: Type.Array(
|
|
32
|
-
Type.Object({ path: Type.String(), lines: Type.String() }),
|
|
33
|
-
),
|
|
34
|
-
constraints: Type.Array(Type.String()),
|
|
35
|
-
dependencies: Type.Array(Type.String()),
|
|
36
|
-
risks: Type.Array(Type.String()),
|
|
37
|
-
blockingQuestions: Type.Array(Type.String()),
|
|
38
|
-
hints: Type.Object({
|
|
39
|
-
writeSet: Type.Array(Type.String()),
|
|
40
|
-
verification: Type.String(),
|
|
41
|
-
agentsImpact: Type.String(),
|
|
42
|
-
}),
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const designEvidenceSchema = Type.Object(
|
|
46
|
-
{
|
|
47
|
-
id: Type.String(),
|
|
48
|
-
role: Type.Literal("design-explorer"),
|
|
49
|
-
kind: Type.Literal("evidence"),
|
|
50
|
-
packet_id: Type.String(),
|
|
51
|
-
module_name: Type.String(),
|
|
52
|
-
scope: Type.Array(Type.String()),
|
|
53
|
-
files_read: Type.Array(Type.String()),
|
|
54
|
-
evidence: Type.Array(
|
|
55
|
-
Type.Object(
|
|
56
|
-
{
|
|
57
|
-
claim: Type.String(),
|
|
58
|
-
path: Type.String(),
|
|
59
|
-
line_start: Type.Integer({ minimum: 1 }),
|
|
60
|
-
line_end: Type.Integer({ minimum: 1 }),
|
|
61
|
-
},
|
|
62
|
-
{ additionalProperties: false },
|
|
63
|
-
),
|
|
64
|
-
),
|
|
65
|
-
existing_structures: Type.Array(Type.String()),
|
|
66
|
-
existing_conventions: Type.Array(Type.String()),
|
|
67
|
-
constraints_discovered: Type.Array(Type.String()),
|
|
68
|
-
open_questions: Type.Array(Type.String()),
|
|
69
|
-
dependencies: Type.Array(Type.String()),
|
|
70
|
-
write_set_hints: Type.Array(Type.String()),
|
|
71
|
-
validation_hints: Type.Array(Type.String()),
|
|
72
|
-
agents_impact_hints: Type.Array(Type.String()),
|
|
73
|
-
risks: Type.Array(Type.String()),
|
|
74
|
-
success_criteria_hints: Type.Array(Type.String()),
|
|
75
|
-
},
|
|
76
|
-
{ additionalProperties: false },
|
|
77
|
-
);
|
|
78
|
-
|
|
79
30
|
const diffSchema = Type.Object({
|
|
80
31
|
id: Type.String(),
|
|
81
32
|
role: Type.String(),
|
|
@@ -881,13 +832,11 @@ export function createSubmitTool(input: {
|
|
|
881
832
|
name: "abel_submit_result",
|
|
882
833
|
label: "Submit Abel Result",
|
|
883
834
|
description:
|
|
884
|
-
"Submit
|
|
835
|
+
"Submit one accepted final result. Evidence identity and omitted advisory fields are completed by this tool; keep citations, constraints, risks, and open questions explicit. After a rejection, correct once in this same session. Brief accompanying text is harmless.",
|
|
885
836
|
executionMode: "sequential",
|
|
886
837
|
parameters:
|
|
887
838
|
input.output === "evidence"
|
|
888
|
-
? input.role
|
|
889
|
-
? designEvidenceSchema
|
|
890
|
-
: compactEvidenceSchema
|
|
839
|
+
? evidenceDraftSchema(input.requestId, input.role)
|
|
891
840
|
: diffSchema,
|
|
892
841
|
async execute(_toolCallId, params) {
|
|
893
842
|
attempts++;
|
|
@@ -916,7 +865,11 @@ export function createSubmitTool(input: {
|
|
|
916
865
|
phase: true,
|
|
917
866
|
};
|
|
918
867
|
failure = undefined;
|
|
919
|
-
const value =
|
|
868
|
+
const value = (
|
|
869
|
+
input.output === "evidence"
|
|
870
|
+
? completeEvidenceDraft(params, input.requestId, input.role)
|
|
871
|
+
: params
|
|
872
|
+
) as Record<string, unknown>;
|
|
920
873
|
if (value === null || typeof value !== "object") {
|
|
921
874
|
identity.request = false;
|
|
922
875
|
identity.role = false;
|
|
@@ -963,7 +916,15 @@ export function createSubmitTool(input: {
|
|
|
963
916
|
stage: "structural-submit",
|
|
964
917
|
});
|
|
965
918
|
throw new Error(
|
|
966
|
-
|
|
919
|
+
JSON.stringify({
|
|
920
|
+
code: "code" in failure ? failure.code : failure.kind,
|
|
921
|
+
reason:
|
|
922
|
+
validation.reason ?? "submitted result identity does not match",
|
|
923
|
+
identityMismatch: Object.entries(identity)
|
|
924
|
+
.filter(([, matches]) => !matches)
|
|
925
|
+
.map(([field]) => field),
|
|
926
|
+
remainingCorrections: Math.max(0, 2 - attempts),
|
|
927
|
+
}),
|
|
967
928
|
);
|
|
968
929
|
}
|
|
969
930
|
submitted = structuredClone(value) as unknown as
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** Request budgets describe observable model progress, never TCP/TLS events. */
|
|
2
|
+
export const REQUEST_BOUNDS = Object.freeze({
|
|
3
|
+
firstProgressMs: 90_000,
|
|
4
|
+
streamIdleMs: 180_000,
|
|
5
|
+
cancellationDrainMs: 250,
|
|
6
|
+
});
|
|
7
|
+
export type TransportTimeoutCode =
|
|
8
|
+
| "first-progress-timeout"
|
|
9
|
+
| "stream-idle-timeout"
|
|
10
|
+
| "attempt-timeout";
|
|
11
|
+
export class TransportTimeout extends Error {
|
|
12
|
+
readonly scope: "request" | "attempt";
|
|
13
|
+
constructor(readonly code: TransportTimeoutCode) {
|
|
14
|
+
super(code);
|
|
15
|
+
this.name = "TransportTimeout";
|
|
16
|
+
this.scope = code === "attempt-timeout" ? "attempt" : "request";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function isTransportTimeoutCode(
|
|
20
|
+
code: string,
|
|
21
|
+
): code is TransportTimeoutCode {
|
|
22
|
+
return [
|
|
23
|
+
"first-progress-timeout",
|
|
24
|
+
"stream-idle-timeout",
|
|
25
|
+
"attempt-timeout",
|
|
26
|
+
].includes(code);
|
|
27
|
+
}
|
|
28
|
+
export function transportFailureError(code: string): Error {
|
|
29
|
+
return isTransportTimeoutCode(code)
|
|
30
|
+
? new TransportTimeout(code)
|
|
31
|
+
: new Error(
|
|
32
|
+
code === "child-provider-rate-limited" ? code : "transport-failure",
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Snapshot at the start of each request; Workers cannot override these values. */
|
|
37
|
+
export function requestBounds(env: NodeJS.ProcessEnv = process.env) {
|
|
38
|
+
const read = (name: string, fallback: number) => {
|
|
39
|
+
const value = Number(env[name] ?? fallback);
|
|
40
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > 1_200_000)
|
|
41
|
+
throw new Error("request-budget-invalid");
|
|
42
|
+
return value;
|
|
43
|
+
};
|
|
44
|
+
return {
|
|
45
|
+
...REQUEST_BOUNDS,
|
|
46
|
+
firstProgressMs: read(
|
|
47
|
+
"ABEL_FIRST_PROGRESS_MS",
|
|
48
|
+
REQUEST_BOUNDS.firstProgressMs,
|
|
49
|
+
),
|
|
50
|
+
streamIdleMs: read("ABEL_STREAM_IDLE_MS", REQUEST_BOUNDS.streamIdleMs),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
verificationInputPaths,
|
|
17
17
|
verificationSteps,
|
|
18
18
|
} from "./contracts.ts";
|
|
19
|
+
import { executionProfile } from "./execution-profile.ts";
|
|
19
20
|
import {
|
|
20
21
|
type Bound,
|
|
21
22
|
isCurrent,
|
|
@@ -828,9 +829,10 @@ function validateAtomicCapability(
|
|
|
828
829
|
continue;
|
|
829
830
|
if (
|
|
830
831
|
lstatSync(path.join(root, relative)).size > 64 * 1024 ||
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
832
|
+
(executionProfile().mode !== "local-trusted" &&
|
|
833
|
+
unsupportedExecutionConfiguration(
|
|
834
|
+
readFileSync(path.join(root, relative), "utf8"),
|
|
835
|
+
))
|
|
834
836
|
) {
|
|
835
837
|
return adapterFailure(
|
|
836
838
|
step.id,
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
VerificationFailureSummary,
|
|
3
|
+
VerificationObservation,
|
|
4
|
+
} from "./contracts.ts";
|
|
5
|
+
|
|
6
|
+
/** Diagnostic text is evidence for repair, never a failure identity or authority. */
|
|
7
|
+
|
|
8
|
+
export function diagnosticText(value: string, maximum = 4096): string {
|
|
9
|
+
const clean = value
|
|
10
|
+
.replace(
|
|
11
|
+
new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*[A-Za-z]`, "gu"),
|
|
12
|
+
"",
|
|
13
|
+
)
|
|
14
|
+
.replace(/(Bearer\s+)\S+/giu, "$1[redacted]")
|
|
15
|
+
.replace(
|
|
16
|
+
/((?:api[_-]?key|token|password|secret)\s*[:=]\s*)[^\s,;]+/giu,
|
|
17
|
+
"$1[redacted]",
|
|
18
|
+
)
|
|
19
|
+
.replace(/(https?:\/\/)[^\s/@]+:[^\s/@]+@/giu, "$1[redacted]@");
|
|
20
|
+
return clean.length <= maximum
|
|
21
|
+
? clean
|
|
22
|
+
: `${clean.slice(0, Math.floor(maximum / 2))}\n…[truncated]…\n${clean.slice(-Math.floor(maximum / 2))}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Bounded operation-local feedback; no raw transcripts or global cross-run cache. */
|
|
26
|
+
export class VerificationFeedback {
|
|
27
|
+
readonly #entries = new Map<string, VerificationFailureSummary>();
|
|
28
|
+
observe(result: VerificationObservation): void {
|
|
29
|
+
if (result.kind === "cancelled") return;
|
|
30
|
+
const id =
|
|
31
|
+
result.kind === "unavailable"
|
|
32
|
+
? result.verificationId
|
|
33
|
+
: result.evidence.id;
|
|
34
|
+
this.#entries.delete(id);
|
|
35
|
+
if (result.kind === "accepted") return;
|
|
36
|
+
const summary = result.diagnostic ?? {
|
|
37
|
+
verificationId: id,
|
|
38
|
+
code: result.code,
|
|
39
|
+
failures: [],
|
|
40
|
+
stdout: "",
|
|
41
|
+
stderr: "",
|
|
42
|
+
truncated: false,
|
|
43
|
+
nextStep:
|
|
44
|
+
"Repair the verification environment or adapter before changing product code.",
|
|
45
|
+
};
|
|
46
|
+
this.#entries.set(id, structuredClone(summary));
|
|
47
|
+
const oldest = this.#entries.keys().next().value;
|
|
48
|
+
if (this.#entries.size > 32 && oldest !== undefined)
|
|
49
|
+
this.#entries.delete(oldest);
|
|
50
|
+
}
|
|
51
|
+
current(): VerificationFailureSummary[] {
|
|
52
|
+
const result: VerificationFailureSummary[] = [];
|
|
53
|
+
let bytes = 0;
|
|
54
|
+
for (const entry of [...this.#entries.values()].reverse()) {
|
|
55
|
+
const summary = {
|
|
56
|
+
...entry,
|
|
57
|
+
failures: entry.failures
|
|
58
|
+
.slice(0, 4)
|
|
59
|
+
.map((text) => diagnosticText(text, 256)),
|
|
60
|
+
stdout: diagnosticText(entry.stdout, 512),
|
|
61
|
+
stderr: diagnosticText(entry.stderr, 1024),
|
|
62
|
+
truncated:
|
|
63
|
+
entry.truncated ||
|
|
64
|
+
entry.failures.length > 4 ||
|
|
65
|
+
entry.failures.some((text) => text.length > 256) ||
|
|
66
|
+
entry.stdout.length > 512 ||
|
|
67
|
+
entry.stderr.length > 1024,
|
|
68
|
+
};
|
|
69
|
+
const size = Buffer.byteLength(JSON.stringify(summary), "utf8");
|
|
70
|
+
if (bytes + size > 6000) break;
|
|
71
|
+
result.push(summary);
|
|
72
|
+
bytes += size;
|
|
73
|
+
}
|
|
74
|
+
return structuredClone(result.reverse());
|
|
75
|
+
}
|
|
76
|
+
}
|