@abelxiaoxing/cadence 1.6.0 → 1.8.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 +25 -4
- package/config/.env.example +6 -0
- package/config/plan-draft.example.json +3 -0
- package/config/plan-draft.multiple-tasks.example.json +30 -0
- package/package.json +1 -1
- package/prompts/abel-design.md +33 -3
- package/prompts/abel-implement.md +36 -4
- package/src/apply-transaction.ts +28 -8
- package/src/build-windows-job.mjs +95 -0
- package/src/candidate-workspace.ts +6 -0
- package/src/change-verification.ts +764 -67
- package/src/contracts.ts +8 -0
- package/src/delivery-compiler.ts +452 -9
- package/src/design-control.ts +2 -2
- package/src/design-diagnostics.ts +6 -4
- package/src/durable-contracts.ts +21 -3
- package/src/durable-workflow.ts +55 -40
- package/src/execution-profile.ts +28 -2
- package/src/execution-retention.ts +77 -0
- package/src/implement-continuation.ts +559 -0
- package/src/implement-graph.ts +114 -0
- package/src/implement-plan.ts +2 -0
- package/src/index.ts +151 -1
- package/src/isolation-backend.ts +2 -1
- package/src/operator-cli.mjs +664 -104
- package/src/operator-tools.ts +7 -1
- package/src/package-state.ts +11 -0
- package/src/package-verification.ts +66 -18
- package/src/package-workflow.ts +6 -0
- package/src/phase-execution.ts +18 -7
- package/src/plan-draft-summary.ts +385 -7
- package/src/plan-draft.ts +9 -0
- package/src/remove-path.ts +32 -0
- package/src/subagent-activity.ts +3 -1
- package/src/verification-capability.ts +18 -6
- package/src/verification-environment-io.ts +123 -14
- package/src/verification-environment.ts +50 -3
- package/src/windows-job-backend.ts +419 -0
- package/src/windows-job.c +149 -0
- package/src/workflow-policy.ts +296 -5
- package/src/workflow-recovery-policy.ts +2 -0
- package/src/workflow-state-machine.ts +314 -80
- package/src/workflow-status.ts +251 -59
- package/src/workspace-io-entry.mjs +2 -2
- package/src/workspace-io-worker.mjs +207 -110
package/src/operator-tools.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { resolveOpenSpecInvocation } from "./openspec-cli.ts";
|
|
|
7
7
|
import { resolveStateRoot } from "./state-root.ts";
|
|
8
8
|
import { requestBounds } from "./transport-budget.ts";
|
|
9
9
|
import { validateVerificationAdapterCapability } from "./verification-capability.ts";
|
|
10
|
+
import { probeWindowsJob } from "./windows-job-backend.ts";
|
|
10
11
|
|
|
11
12
|
export interface DoctorCheck {
|
|
12
13
|
name: string;
|
|
@@ -121,6 +122,11 @@ export function inspectConsumer(root: string) {
|
|
|
121
122
|
"execution",
|
|
122
123
|
() => {
|
|
123
124
|
const profile = executionProfile();
|
|
125
|
+
if (profile.mode === "host-trusted") {
|
|
126
|
+
if (!probeWindowsJob(profile.windowsHelperPath ?? ""))
|
|
127
|
+
throw new Error("windows-job-backend-unavailable");
|
|
128
|
+
return "host-trusted: Windows x64 Job probe passed; host files/network accessible";
|
|
129
|
+
}
|
|
124
130
|
if (process.platform !== "linux")
|
|
125
131
|
throw new Error("linux-pid-isolation-required");
|
|
126
132
|
|
|
@@ -139,7 +145,7 @@ export function inspectConsumer(root: string) {
|
|
|
139
145
|
if (result.status !== 0) throw new Error("bubblewrap-probe-failed");
|
|
140
146
|
return `${profile.mode}: Bubblewrap PID namespace probe passed`;
|
|
141
147
|
},
|
|
142
|
-
"On Linux install/enable Bubblewrap; set ABEL_BWRAP_PATH for a custom install. Trusted Linux projects may explicitly set ABEL_EXECUTION_MODE=local-trusted; Windows
|
|
148
|
+
"On Linux install/enable Bubblewrap; set ABEL_BWRAP_PATH for a custom install. Trusted Linux projects may explicitly set ABEL_EXECUTION_MODE=local-trusted; Windows x64 trusted projects may explicitly select ABEL_EXECUTION_MODE=host-trusted and install the matching native Job helper (ABEL_WINDOWS_JOB_HELPER).",
|
|
143
149
|
);
|
|
144
150
|
check(
|
|
145
151
|
"request-budgets",
|
package/src/package-state.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import path from "node:path";
|
|
16
16
|
import { DatabaseSync } from "node:sqlite";
|
|
17
17
|
import { syncDirectory } from "./directory-sync.ts";
|
|
18
|
+
import { assertExecutionsSettled } from "./execution-retention.ts";
|
|
18
19
|
import { RunStore } from "./run-store.ts";
|
|
19
20
|
import { prepareStateRoot, type ResolvedStateRoot } from "./state-root.ts";
|
|
20
21
|
|
|
@@ -160,6 +161,16 @@ export function acquirePackageState(
|
|
|
160
161
|
): { notice?: PackageStateNotice; close(): void } {
|
|
161
162
|
versionParts(version);
|
|
162
163
|
const state = prepareStateRoot(unresolved);
|
|
164
|
+
try {
|
|
165
|
+
assertExecutionsSettled(state.rootDir);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
if (
|
|
168
|
+
error instanceof Error &&
|
|
169
|
+
error.message === "isolation-termination-unconfirmed"
|
|
170
|
+
)
|
|
171
|
+
fail(error.message);
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
163
174
|
for (const suffix of ["-wal", "-shm", "-journal"])
|
|
164
175
|
regular(state.databasePath + suffix);
|
|
165
176
|
const coordinator = path.join(
|
|
@@ -4,10 +4,12 @@ import path from "node:path";
|
|
|
4
4
|
import {
|
|
5
5
|
type AtomicVerificationContract,
|
|
6
6
|
type StructuredVerificationContract,
|
|
7
|
+
type VerificationInputObservation,
|
|
7
8
|
type VerificationObservation,
|
|
8
9
|
verificationSteps,
|
|
9
10
|
} from "./contracts.ts";
|
|
10
11
|
import { executionProfile } from "./execution-profile.ts";
|
|
12
|
+
import { retainExecution } from "./execution-retention.ts";
|
|
11
13
|
import { canonicalJson } from "./implement-graph.ts";
|
|
12
14
|
import { BubblewrapIsolationBackend } from "./isolation-backend.ts";
|
|
13
15
|
import { observeSafePath } from "./safe-path.ts";
|
|
@@ -18,6 +20,7 @@ import {
|
|
|
18
20
|
} from "./verification-capability.ts";
|
|
19
21
|
import { diagnosticText } from "./verification-diagnostics.ts";
|
|
20
22
|
import { prepareVerificationEnvironment } from "./verification-environment.ts";
|
|
23
|
+
import { WindowsJobBackend } from "./windows-job-backend.ts";
|
|
21
24
|
|
|
22
25
|
function sha256(value: Uint8Array | string): string {
|
|
23
26
|
return createHash("sha256").update(value).digest("hex");
|
|
@@ -310,6 +313,7 @@ function readReport(root: string, relative: string, maximum: number): string {
|
|
|
310
313
|
export async function executePackageVerification(input: {
|
|
311
314
|
root: string;
|
|
312
315
|
dependencyOwner: string;
|
|
316
|
+
executionOwnerRoot?: string;
|
|
313
317
|
verification: StructuredVerificationContract;
|
|
314
318
|
signal: AbortSignal;
|
|
315
319
|
maxReportBytes?: number;
|
|
@@ -321,11 +325,13 @@ export async function executePackageVerification(input: {
|
|
|
321
325
|
const unavailable = (
|
|
322
326
|
code: string,
|
|
323
327
|
category: "environment" | "adapter" | "resource" = "adapter",
|
|
328
|
+
inputObservation?: VerificationInputObservation,
|
|
324
329
|
): VerificationObservation => ({
|
|
325
330
|
kind: "unavailable",
|
|
326
331
|
category,
|
|
327
332
|
code,
|
|
328
333
|
verificationId: input.verification.id,
|
|
334
|
+
...(inputObservation ? { inputObservation } : {}),
|
|
329
335
|
...(executionDiagnostic
|
|
330
336
|
? {
|
|
331
337
|
diagnostic: {
|
|
@@ -349,7 +355,14 @@ export async function executePackageVerification(input: {
|
|
|
349
355
|
executionWritePaths: input.executionWritePaths,
|
|
350
356
|
},
|
|
351
357
|
);
|
|
352
|
-
if (!capability.ok)
|
|
358
|
+
if (!capability.ok)
|
|
359
|
+
return unavailable(
|
|
360
|
+
capability.diagnostic.code,
|
|
361
|
+
"adapter",
|
|
362
|
+
capability.diagnostic.kind === "verification-adapter"
|
|
363
|
+
? capability.diagnostic.inputObservation
|
|
364
|
+
: undefined,
|
|
365
|
+
);
|
|
353
366
|
let environment: Awaited<ReturnType<typeof prepareVerificationEnvironment>>;
|
|
354
367
|
try {
|
|
355
368
|
environment = await prepareVerificationEnvironment(
|
|
@@ -366,6 +379,7 @@ export async function executePackageVerification(input: {
|
|
|
366
379
|
"workspace-dependency-missing",
|
|
367
380
|
"dependency-path-unsafe",
|
|
368
381
|
"execution-mode-invalid",
|
|
382
|
+
"windows-job-helper-path-invalid",
|
|
369
383
|
"verification-timeout-invalid",
|
|
370
384
|
"verification-environment-names-invalid",
|
|
371
385
|
"verification-environment-requires-trusted-mode",
|
|
@@ -374,13 +388,20 @@ export async function executePackageVerification(input: {
|
|
|
374
388
|
: "verification-environment-unavailable";
|
|
375
389
|
return unavailable(code, "environment");
|
|
376
390
|
}
|
|
391
|
+
let retained = false;
|
|
377
392
|
try {
|
|
378
393
|
const profile = executionProfile();
|
|
379
|
-
const isolation =
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
394
|
+
const isolation =
|
|
395
|
+
profile.mode === "host-trusted"
|
|
396
|
+
? new WindowsJobBackend({
|
|
397
|
+
helperPath: profile.windowsHelperPath ?? "",
|
|
398
|
+
timeoutMs: profile.timeoutMs,
|
|
399
|
+
})
|
|
400
|
+
: new BubblewrapIsolationBackend({
|
|
401
|
+
bwrapPath: profile.bwrapPath,
|
|
402
|
+
timeoutMs: profile.timeoutMs,
|
|
403
|
+
localTrusted: profile.mode === "local-trusted",
|
|
404
|
+
});
|
|
384
405
|
let final: VerificationObservation | undefined;
|
|
385
406
|
let sequence = 0;
|
|
386
407
|
for (const step of verificationSteps(input.verification)) {
|
|
@@ -391,7 +412,7 @@ export async function executePackageVerification(input: {
|
|
|
391
412
|
`${environment.reportRoot}/${relative}`,
|
|
392
413
|
);
|
|
393
414
|
if (!invocation) return unavailable("runner-missing");
|
|
394
|
-
if (profile.mode
|
|
415
|
+
if (profile.mode !== "isolated") {
|
|
395
416
|
const translate = (value: string) =>
|
|
396
417
|
value.startsWith("/workspace/")
|
|
397
418
|
? path.join(input.root, value.slice(11))
|
|
@@ -399,16 +420,37 @@ export async function executePackageVerification(input: {
|
|
|
399
420
|
invocation.executable = translate(invocation.executable);
|
|
400
421
|
invocation.args = invocation.args.map(translate);
|
|
401
422
|
}
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
423
|
+
const lease =
|
|
424
|
+
profile.mode === "host-trusted"
|
|
425
|
+
? retainExecution(
|
|
426
|
+
input.executionOwnerRoot ?? path.dirname(input.root),
|
|
427
|
+
[input.root, environment.privateRoot],
|
|
428
|
+
)
|
|
429
|
+
: undefined;
|
|
430
|
+
let executed: Awaited<ReturnType<typeof isolation.run>>;
|
|
431
|
+
try {
|
|
432
|
+
executed = await isolation.run({
|
|
433
|
+
root: input.root,
|
|
434
|
+
...invocation,
|
|
435
|
+
mounts: profile.mode === "host-trusted" ? [] : environment.mounts,
|
|
436
|
+
environment: environment.environment,
|
|
437
|
+
...(step.kind !== "vitest" && step.classification === "expected-red"
|
|
438
|
+
? { outputWitness: step.expectedFailure }
|
|
439
|
+
: {}),
|
|
440
|
+
signal: input.signal,
|
|
441
|
+
});
|
|
442
|
+
if (
|
|
443
|
+
!executed.ok &&
|
|
444
|
+
executed.code === "isolation-termination-unconfirmed"
|
|
445
|
+
) {
|
|
446
|
+
retained = true;
|
|
447
|
+
lease?.uncertain();
|
|
448
|
+
} else lease?.settled();
|
|
449
|
+
} catch (error) {
|
|
450
|
+
retained = !!lease;
|
|
451
|
+
lease?.uncertain();
|
|
452
|
+
throw error;
|
|
453
|
+
}
|
|
412
454
|
if (!executed.ok) {
|
|
413
455
|
if (executed.state === "cancelled") return { kind: "cancelled" };
|
|
414
456
|
return unavailable(
|
|
@@ -524,7 +566,7 @@ export async function executePackageVerification(input: {
|
|
|
524
566
|
}
|
|
525
567
|
return final ?? unavailable("verification-contract-unsupported");
|
|
526
568
|
} finally {
|
|
527
|
-
await environment.cleanup();
|
|
569
|
+
if (!retained) await environment.cleanup();
|
|
528
570
|
}
|
|
529
571
|
}
|
|
530
572
|
|
|
@@ -552,6 +594,9 @@ export function phaseVerificationResult(result: VerificationObservation) {
|
|
|
552
594
|
kind: "paused" as const,
|
|
553
595
|
code: result.code,
|
|
554
596
|
category: result.category,
|
|
597
|
+
...(result.inputObservation
|
|
598
|
+
? { inputObservation: result.inputObservation }
|
|
599
|
+
: {}),
|
|
555
600
|
};
|
|
556
601
|
}
|
|
557
602
|
|
|
@@ -583,5 +628,8 @@ export function changeVerificationResult(result: VerificationObservation) {
|
|
|
583
628
|
? ("verification-adapter" as const)
|
|
584
629
|
: ("environment" as const),
|
|
585
630
|
code: result.code,
|
|
631
|
+
...(result.inputObservation
|
|
632
|
+
? { inputObservation: result.inputObservation }
|
|
633
|
+
: {}),
|
|
586
634
|
};
|
|
587
635
|
}
|
package/src/package-workflow.ts
CHANGED
|
@@ -160,6 +160,7 @@ function openPreparedPackageWorkflowService(
|
|
|
160
160
|
const workHardLimit = hostLimit("ABEL_WORK_MAX_UNITS", 512);
|
|
161
161
|
const engine = openDurableWorkflowEngine({
|
|
162
162
|
workHardLimit,
|
|
163
|
+
awaitPostApplySettlement: true,
|
|
163
164
|
verificationPolicy: `report-file-v5:${JSON.stringify(executionProfile())}`,
|
|
164
165
|
verificationEnvironment: coalesceVerificationScans(
|
|
165
166
|
(plan: import("./implement-plan.ts").ImplementPlan, signal) =>
|
|
@@ -171,6 +172,9 @@ function openPreparedPackageWorkflowService(
|
|
|
171
172
|
(phase) => phase.verification,
|
|
172
173
|
),
|
|
173
174
|
task.affectedVerification,
|
|
175
|
+
...(task.baselineVerification
|
|
176
|
+
? [task.baselineVerification]
|
|
177
|
+
: []),
|
|
174
178
|
task.repairVerification,
|
|
175
179
|
]),
|
|
176
180
|
plan.verification.baseline.fullSuite,
|
|
@@ -207,6 +211,7 @@ function openPreparedPackageWorkflowService(
|
|
|
207
211
|
executionWritePaths: input.executionWritePaths,
|
|
208
212
|
root: input.root,
|
|
209
213
|
dependencyOwner: consumerRoot,
|
|
214
|
+
executionOwnerRoot: stateRoot.rootDir,
|
|
210
215
|
verification: input.verification,
|
|
211
216
|
signal: input.signal,
|
|
212
217
|
}),
|
|
@@ -227,6 +232,7 @@ function openPreparedPackageWorkflowService(
|
|
|
227
232
|
),
|
|
228
233
|
root: input.root,
|
|
229
234
|
dependencyOwner: consumerRoot,
|
|
235
|
+
executionOwnerRoot: stateRoot.rootDir,
|
|
230
236
|
verification,
|
|
231
237
|
signal: input.signal,
|
|
232
238
|
});
|
package/src/phase-execution.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { mkdtempSync
|
|
2
|
+
import { mkdtempSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import {
|
|
5
5
|
inspectCandidateArtifact,
|
|
6
6
|
sealCandidateArtifact,
|
|
7
7
|
} from "./candidate-artifact.ts";
|
|
8
8
|
import { revisionChanges, runGitApply } from "./candidate-workspace.ts";
|
|
9
|
-
|
|
10
9
|
import type { StructuredVerificationContract } from "./contracts.ts";
|
|
11
10
|
import type { PlanTaskDraft } from "./delivery-compiler.ts";
|
|
12
11
|
import type {
|
|
@@ -20,6 +19,8 @@ import type {
|
|
|
20
19
|
DurableWorkflowEngineOptions,
|
|
21
20
|
PhaseResources,
|
|
22
21
|
} from "./durable-contracts.ts";
|
|
22
|
+
import { isExecutionRetained } from "./execution-retention.ts";
|
|
23
|
+
import { removePathSync } from "./remove-path.ts";
|
|
23
24
|
import type {
|
|
24
25
|
BeginCandidateInput,
|
|
25
26
|
TaskLedger,
|
|
@@ -43,6 +44,7 @@ import {
|
|
|
43
44
|
LOCKFILES,
|
|
44
45
|
SHA256,
|
|
45
46
|
semanticRouteFailure,
|
|
47
|
+
taskExecutionContract,
|
|
46
48
|
type WorkflowAttemptOutcome,
|
|
47
49
|
type WorkflowRouteFacts,
|
|
48
50
|
type WorkflowWorker,
|
|
@@ -60,6 +62,7 @@ export interface PhaseExecutionServices {
|
|
|
60
62
|
resources: PhaseResources;
|
|
61
63
|
ledger: TaskLedger;
|
|
62
64
|
signal: AbortSignal;
|
|
65
|
+
taskId?: string;
|
|
63
66
|
}): Promise<DurableBaselineResult>;
|
|
64
67
|
verifyTaskAffected(input: {
|
|
65
68
|
resources: PhaseResources;
|
|
@@ -197,7 +200,10 @@ export class PhaseExecution {
|
|
|
197
200
|
runId: input.runId,
|
|
198
201
|
deliveryRevision: input.deliveryRevision,
|
|
199
202
|
taskId: input.taskId,
|
|
200
|
-
boundaryHash: hash(
|
|
203
|
+
boundaryHash: hash(
|
|
204
|
+
"durable-task-boundary",
|
|
205
|
+
JSON.stringify(taskExecutionContract(input.task)),
|
|
206
|
+
),
|
|
201
207
|
objective: input.task.objective,
|
|
202
208
|
contextRefs,
|
|
203
209
|
initialPhase:
|
|
@@ -303,7 +309,8 @@ export class PhaseExecution {
|
|
|
303
309
|
policy: this.#options.verificationPolicy ?? "legacy",
|
|
304
310
|
});
|
|
305
311
|
} finally {
|
|
306
|
-
|
|
312
|
+
if (!isExecutionRetained(root))
|
|
313
|
+
removePathSync(root, { recursive: true, force: true });
|
|
307
314
|
}
|
|
308
315
|
}
|
|
309
316
|
return undefined;
|
|
@@ -975,7 +982,8 @@ export class PhaseExecution {
|
|
|
975
982
|
},
|
|
976
983
|
};
|
|
977
984
|
} finally {
|
|
978
|
-
|
|
985
|
+
if (!isExecutionRetained(proposalRoot))
|
|
986
|
+
removePathSync(proposalRoot, { recursive: true, force: true });
|
|
979
987
|
}
|
|
980
988
|
}
|
|
981
989
|
|
|
@@ -1160,7 +1168,8 @@ export class PhaseExecution {
|
|
|
1160
1168
|
signal: request.signal,
|
|
1161
1169
|
});
|
|
1162
1170
|
} finally {
|
|
1163
|
-
|
|
1171
|
+
if (!isExecutionRetained(root))
|
|
1172
|
+
removePathSync(root, { recursive: true, force: true });
|
|
1164
1173
|
}
|
|
1165
1174
|
};
|
|
1166
1175
|
emitWorkflowActivity(request.onActivity, { state: "verifying" });
|
|
@@ -1562,6 +1571,7 @@ export class PhaseExecution {
|
|
|
1562
1571
|
resources,
|
|
1563
1572
|
ledger,
|
|
1564
1573
|
signal: input.signal,
|
|
1574
|
+
taskId: input.taskId,
|
|
1565
1575
|
});
|
|
1566
1576
|
if (!captured.ok) {
|
|
1567
1577
|
const outcome = captured.outcome;
|
|
@@ -2242,7 +2252,8 @@ export class PhaseExecution {
|
|
|
2242
2252
|
baselineRevisionId: resources.baselineRevisionId,
|
|
2243
2253
|
});
|
|
2244
2254
|
} finally {
|
|
2245
|
-
|
|
2255
|
+
if (!isExecutionRetained(proposalRoot))
|
|
2256
|
+
removePathSync(proposalRoot, { recursive: true, force: true });
|
|
2246
2257
|
}
|
|
2247
2258
|
}
|
|
2248
2259
|
}
|