@abelxiaoxing/cadence 1.3.0 → 1.4.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 +115 -10
- package/package.json +5 -3
- package/prompts/abel-design.md +71 -17
- package/prompts/abel-diagnose.md +10 -1
- package/prompts/abel-implement.md +118 -20
- package/prompts/abel-init.md +9 -2
- package/src/apply-transaction.ts +16 -14
- package/src/artifact-store.ts +17 -0
- package/src/candidate-patch.ts +2 -1
- package/src/canonical.ts +16 -0
- package/src/change-contract.ts +268 -0
- package/src/child-session.ts +4 -3
- package/src/contracts.ts +141 -33
- package/src/control-contracts.ts +51 -1
- package/src/delivery-compiler.ts +148 -31
- package/src/design-control.ts +69 -11
- package/src/design-journal.ts +216 -72
- package/src/durable-workflow.ts +4385 -0
- package/src/implement-graph.ts +11 -16
- package/src/index.ts +380 -981
- package/src/isolation-backend.ts +84 -23
- package/src/package-delivery.ts +486 -0
- package/src/package-verification.ts +509 -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 +14 -1
- package/src/submit-tool.ts +20 -2
- package/src/task-ledger.ts +14 -55
- 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 +97 -34
- package/src/workflow-engine.ts +28 -8285
- package/src/workflow-policy.ts +1427 -0
- package/src/workflow-state-machine.ts +4478 -0
- package/src/workspace-io-entry.mjs +58 -0
- package/src/workspace-io-worker.mjs +984 -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/task-ledger.ts
CHANGED
|
@@ -2,14 +2,16 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { chmodSync, lstatSync, mkdirSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { DatabaseSync } from "node:sqlite";
|
|
5
|
-
|
|
6
5
|
import type { ArtifactStore } from "./artifact-store.ts";
|
|
6
|
+
import { compareCanonicalStrings } from "./canonical.ts";
|
|
7
7
|
import {
|
|
8
8
|
diffWritePaths,
|
|
9
9
|
IMPLEMENTATION_PHASES,
|
|
10
10
|
type ImplementationPhase,
|
|
11
11
|
isValidRelativePath,
|
|
12
12
|
} from "./contracts.ts";
|
|
13
|
+
import { configureSqlite, ensureSqliteSchema } from "./sqlite-schema.ts";
|
|
14
|
+
import { TASK_SCHEMA } from "./storage-schema.ts";
|
|
13
15
|
|
|
14
16
|
export const TASK_LEDGER_LIMITS = {
|
|
15
17
|
maxSegmentBytes: 128 * 1024,
|
|
@@ -413,7 +415,9 @@ function normalizeContextRefs(value: unknown): TaskContextReference[] {
|
|
|
413
415
|
if (new Set(identities).size !== identities.length) {
|
|
414
416
|
throw new Error("ledger-context-refs-invalid");
|
|
415
417
|
}
|
|
416
|
-
return refs.sort((left, right) =>
|
|
418
|
+
return refs.sort((left, right) =>
|
|
419
|
+
compareCanonicalStrings(left.path, right.path),
|
|
420
|
+
);
|
|
417
421
|
}
|
|
418
422
|
|
|
419
423
|
function safeText(value: unknown, code: string, maximum = 256): string {
|
|
@@ -694,57 +698,8 @@ export class TaskLedger {
|
|
|
694
698
|
throw new Error("task-ledger-database-invalid");
|
|
695
699
|
}
|
|
696
700
|
this.#database = new DatabaseSync(databasePath);
|
|
697
|
-
this.#database
|
|
698
|
-
|
|
699
|
-
);
|
|
700
|
-
this.#database.exec(`
|
|
701
|
-
CREATE TABLE IF NOT EXISTS candidates (
|
|
702
|
-
candidate_id TEXT PRIMARY KEY,
|
|
703
|
-
identity_json TEXT NOT NULL,
|
|
704
|
-
state TEXT NOT NULL,
|
|
705
|
-
next_sequence INTEGER NOT NULL,
|
|
706
|
-
total_bytes INTEGER NOT NULL,
|
|
707
|
-
artifact_hash TEXT,
|
|
708
|
-
candidate_hash TEXT,
|
|
709
|
-
segment_count INTEGER,
|
|
710
|
-
paths_json TEXT,
|
|
711
|
-
pause_code TEXT
|
|
712
|
-
) STRICT;
|
|
713
|
-
CREATE TABLE IF NOT EXISTS candidate_segments (
|
|
714
|
-
candidate_id TEXT NOT NULL REFERENCES candidates(candidate_id) ON DELETE CASCADE,
|
|
715
|
-
sequence INTEGER NOT NULL,
|
|
716
|
-
artifact_hash TEXT NOT NULL,
|
|
717
|
-
size_bytes INTEGER NOT NULL,
|
|
718
|
-
segment_hash TEXT NOT NULL,
|
|
719
|
-
PRIMARY KEY (candidate_id, sequence)
|
|
720
|
-
) STRICT;
|
|
721
|
-
CREATE TABLE IF NOT EXISTS task_ledger (
|
|
722
|
-
run_id TEXT NOT NULL,
|
|
723
|
-
task_id TEXT NOT NULL,
|
|
724
|
-
delivery_revision INTEGER NOT NULL,
|
|
725
|
-
boundary_hash TEXT NOT NULL,
|
|
726
|
-
objective TEXT NOT NULL,
|
|
727
|
-
context_refs_json TEXT NOT NULL,
|
|
728
|
-
initial_phase TEXT NOT NULL,
|
|
729
|
-
PRIMARY KEY (run_id, task_id)
|
|
730
|
-
) STRICT;
|
|
731
|
-
CREATE TABLE IF NOT EXISTS task_events (
|
|
732
|
-
run_id TEXT NOT NULL,
|
|
733
|
-
task_id TEXT NOT NULL,
|
|
734
|
-
ordinal INTEGER NOT NULL,
|
|
735
|
-
event_id TEXT NOT NULL,
|
|
736
|
-
event_json TEXT NOT NULL,
|
|
737
|
-
prior_hash TEXT NOT NULL,
|
|
738
|
-
event_hash TEXT NOT NULL,
|
|
739
|
-
PRIMARY KEY (run_id, task_id, ordinal),
|
|
740
|
-
UNIQUE (run_id, task_id, event_id),
|
|
741
|
-
FOREIGN KEY (run_id, task_id) REFERENCES task_ledger(run_id, task_id) ON DELETE CASCADE
|
|
742
|
-
) STRICT;
|
|
743
|
-
CREATE TABLE IF NOT EXISTS durable_facts (
|
|
744
|
-
fact_key TEXT PRIMARY KEY,
|
|
745
|
-
fact_json TEXT NOT NULL
|
|
746
|
-
) STRICT;
|
|
747
|
-
`);
|
|
701
|
+
configureSqlite(this.#database);
|
|
702
|
+
ensureSqliteSchema(this.#database, TASK_SCHEMA);
|
|
748
703
|
chmodSync(databasePath, 0o600);
|
|
749
704
|
}
|
|
750
705
|
|
|
@@ -1173,7 +1128,7 @@ export class TaskLedger {
|
|
|
1173
1128
|
commitVerifiedEvent(input: VerifiedTaskEvent) {
|
|
1174
1129
|
this.#assertOpen();
|
|
1175
1130
|
const normalized = normalizeVerifiedEvent(input);
|
|
1176
|
-
this.#task(normalized.runId, normalized.taskId);
|
|
1131
|
+
const task = this.#task(normalized.runId, normalized.taskId);
|
|
1177
1132
|
const rows = this.#events(normalized.runId, normalized.taskId);
|
|
1178
1133
|
const existing = rows.find((row) => row.event_id === normalized.eventId);
|
|
1179
1134
|
const json = JSON.stringify(normalized);
|
|
@@ -1186,6 +1141,8 @@ export class TaskLedger {
|
|
|
1186
1141
|
throw new Error("ledger-event-capacity-exceeded");
|
|
1187
1142
|
}
|
|
1188
1143
|
if (normalized.kind === "phase-verified") {
|
|
1144
|
+
if (task.initial_phase === "green" && normalized.phase === "red")
|
|
1145
|
+
throw new Error("ledger-phase-order-invalid");
|
|
1189
1146
|
const completed = rows
|
|
1190
1147
|
.map((row) => JSON.parse(row.event_json) as VerifiedTaskEvent)
|
|
1191
1148
|
.filter(
|
|
@@ -1195,7 +1152,9 @@ export class TaskLedger {
|
|
|
1195
1152
|
.map((event) => event.phase);
|
|
1196
1153
|
const required =
|
|
1197
1154
|
normalized.phase === "green"
|
|
1198
|
-
? "
|
|
1155
|
+
? task.initial_phase === "green"
|
|
1156
|
+
? undefined
|
|
1157
|
+
: "red"
|
|
1199
1158
|
: normalized.phase === "refactor"
|
|
1200
1159
|
? "green"
|
|
1201
1160
|
: undefined;
|
|
@@ -9,13 +9,21 @@ import path from "node:path";
|
|
|
9
9
|
import {
|
|
10
10
|
type AtomicVerificationContract,
|
|
11
11
|
type StructuredVerificationContract,
|
|
12
|
+
VERIFICATION_CONFIGURATION_PATHS,
|
|
12
13
|
type VerificationAdapterCode,
|
|
13
14
|
validateVerificationContract,
|
|
15
|
+
verificationBoundInputPaths,
|
|
14
16
|
verificationInputPaths,
|
|
15
17
|
verificationSteps,
|
|
16
18
|
} from "./contracts.ts";
|
|
17
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
type Bound,
|
|
21
|
+
isCurrent,
|
|
22
|
+
snapshotFile,
|
|
23
|
+
snapshotFiles,
|
|
24
|
+
} from "./file-snapshot.ts";
|
|
18
25
|
import { isSafeRegularFile, observeSafePath } from "./safe-path.ts";
|
|
26
|
+
import { verificationEnvironmentDigest } from "./verification-identity.ts";
|
|
19
27
|
|
|
20
28
|
export type VerificationCapabilityDiagnostic =
|
|
21
29
|
| {
|
|
@@ -43,6 +51,7 @@ export interface CurrentVerificationCapability {
|
|
|
43
51
|
verificationId: string;
|
|
44
52
|
runnerBindings: VerificationRunnerBinding[];
|
|
45
53
|
inputSnapshot: Bound;
|
|
54
|
+
runnerSnapshot: { paths: string[]; digest: string };
|
|
46
55
|
}
|
|
47
56
|
|
|
48
57
|
export type CurrentVerificationCapabilityResult =
|
|
@@ -64,14 +73,12 @@ export interface VerificationRunnerEnvironment {
|
|
|
64
73
|
}
|
|
65
74
|
|
|
66
75
|
export interface VerificationCapabilityOptions {
|
|
76
|
+
/** Trusted plan write/delete authority, supplied only for isolated execution. */
|
|
77
|
+
executionWritePaths?: readonly string[];
|
|
67
78
|
dependencyOwner?: string;
|
|
68
79
|
runnerEnvironment?: VerificationRunnerEnvironment;
|
|
69
80
|
}
|
|
70
81
|
|
|
71
|
-
const SHELL_OPERATOR = /[;&|`$<>\n\r\0]/u;
|
|
72
|
-
const SIMPLE_TOKEN =
|
|
73
|
-
/^(?:[a-z0-9][a-z0-9._:@/=-]*|--?[a-z0-9][a-z0-9._:@/=-]*)$/iu;
|
|
74
|
-
const NETWORKING_RUNNERS = new Set(["bunx", "pnpx"]);
|
|
75
82
|
const WINDOWS_EXECUTABLE_EXTENSIONS = new Set([".exe", ".cmd"]);
|
|
76
83
|
const DEFAULT_WINDOWS_PATH_EXT = ".COM;.EXE;.BAT;.CMD";
|
|
77
84
|
const SYSTEM_MOUNT_ROOTS = ["/usr", "/bin", "/lib", "/lib64", "/etc"];
|
|
@@ -633,95 +640,13 @@ function packageManifest(root: string): Record<string, unknown> | null {
|
|
|
633
640
|
}
|
|
634
641
|
}
|
|
635
642
|
|
|
636
|
-
function validateScriptCommand(
|
|
637
|
-
root: string,
|
|
638
|
-
verificationId: string,
|
|
639
|
-
command: string,
|
|
640
|
-
dependencyOwner: string,
|
|
641
|
-
runnerEnvironment: VerificationRunnerEnvironment,
|
|
642
|
-
): VerificationCapabilityResult {
|
|
643
|
-
if (
|
|
644
|
-
command.length === 0 ||
|
|
645
|
-
command.trim() !== command ||
|
|
646
|
-
SHELL_OPERATOR.test(command)
|
|
647
|
-
) {
|
|
648
|
-
return adapterFailure(
|
|
649
|
-
verificationId,
|
|
650
|
-
"script-unsafe",
|
|
651
|
-
"approved package script contains unsupported shell syntax",
|
|
652
|
-
);
|
|
653
|
-
}
|
|
654
|
-
const tokens = command.split(/\s+/u);
|
|
655
|
-
if (!SIMPLE_TOKEN.test(tokens[0] ?? "")) {
|
|
656
|
-
return adapterFailure(
|
|
657
|
-
verificationId,
|
|
658
|
-
"script-unsafe",
|
|
659
|
-
"approved package script cannot be represented as safe tokens",
|
|
660
|
-
);
|
|
661
|
-
}
|
|
662
|
-
const executable = tokens[0] ?? "";
|
|
663
|
-
const subcommands = new Set(tokens.slice(1));
|
|
664
|
-
if (
|
|
665
|
-
NETWORKING_RUNNERS.has(executable) ||
|
|
666
|
-
(executable === "bun" && subcommands.has("x")) ||
|
|
667
|
-
(executable === "npm" &&
|
|
668
|
-
(subcommands.has("exec") || subcommands.has("x"))) ||
|
|
669
|
-
(executable === "pnpm" && subcommands.has("dlx")) ||
|
|
670
|
-
(executable === "yarn" && subcommands.has("dlx"))
|
|
671
|
-
) {
|
|
672
|
-
return adapterFailure(
|
|
673
|
-
verificationId,
|
|
674
|
-
"script-unsafe",
|
|
675
|
-
"package script uses a runner that can download implicitly",
|
|
676
|
-
);
|
|
677
|
-
}
|
|
678
|
-
if (executable === "npx") {
|
|
679
|
-
const local = tokens[1] === "--no-install" ? tokens[2] : undefined;
|
|
680
|
-
if (
|
|
681
|
-
!local ||
|
|
682
|
-
!localExecutable(root, local, dependencyOwner, runnerEnvironment)
|
|
683
|
-
) {
|
|
684
|
-
return adapterFailure(
|
|
685
|
-
verificationId,
|
|
686
|
-
"local-executable-missing",
|
|
687
|
-
"npx requires --no-install and an installed local executable",
|
|
688
|
-
);
|
|
689
|
-
}
|
|
690
|
-
const runner = resolveVerificationRunner("npx", runnerEnvironment);
|
|
691
|
-
return runner
|
|
692
|
-
? capabilitySuccess(verificationId, [runner])
|
|
693
|
-
: adapterFailure(
|
|
694
|
-
verificationId,
|
|
695
|
-
"runner-missing",
|
|
696
|
-
"npx is unavailable or cannot be mounted safely",
|
|
697
|
-
);
|
|
698
|
-
}
|
|
699
|
-
if (["node", "bun"].includes(executable)) {
|
|
700
|
-
const runner = resolveVerificationRunner(executable, runnerEnvironment);
|
|
701
|
-
return runner
|
|
702
|
-
? capabilitySuccess(verificationId, [runner])
|
|
703
|
-
: adapterFailure(
|
|
704
|
-
verificationId,
|
|
705
|
-
"runner-missing",
|
|
706
|
-
`approved runner ${executable} is unavailable or cannot be mounted safely`,
|
|
707
|
-
);
|
|
708
|
-
}
|
|
709
|
-
return localExecutable(root, executable, dependencyOwner, runnerEnvironment)
|
|
710
|
-
? capabilitySuccess(verificationId)
|
|
711
|
-
: adapterFailure(
|
|
712
|
-
verificationId,
|
|
713
|
-
"local-executable-missing",
|
|
714
|
-
`local executable ${executable} is unavailable`,
|
|
715
|
-
);
|
|
716
|
-
}
|
|
717
|
-
|
|
718
643
|
function validatePackageScript(
|
|
719
644
|
root: string,
|
|
720
645
|
verificationId: string,
|
|
721
646
|
packageManager: string,
|
|
722
647
|
script: string,
|
|
723
648
|
approvedCommand: string,
|
|
724
|
-
|
|
649
|
+
_dependencyOwner = root,
|
|
725
650
|
runnerEnvironment: VerificationRunnerEnvironment = {},
|
|
726
651
|
): VerificationCapabilityResult {
|
|
727
652
|
const packageRunner = resolveVerificationRunner(
|
|
@@ -755,19 +680,91 @@ function validatePackageScript(
|
|
|
755
680
|
`package.json script ${script} differs from the Gate B contract`,
|
|
756
681
|
);
|
|
757
682
|
}
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
683
|
+
// Approved scripts are opaque shell programs and may invoke nested scripts
|
|
684
|
+
// or hooks. Supply the available supported runtimes without parsing shell
|
|
685
|
+
// syntax or rejecting composition; isolation still owns their execution.
|
|
686
|
+
const runtimes = ["node", "bun", "npm", "npx", "pnpm", "yarn"].flatMap(
|
|
687
|
+
(command) => {
|
|
688
|
+
const binding = resolveVerificationRunner(command, runnerEnvironment);
|
|
689
|
+
return binding ? [binding] : [];
|
|
690
|
+
},
|
|
764
691
|
);
|
|
765
|
-
return
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
692
|
+
return capabilitySuccess(verificationId, [packageRunner, ...runtimes]);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/** Inspect active directives, never comments or arbitrary words in values. */
|
|
696
|
+
function unsupportedExecutionConfiguration(source: string): boolean {
|
|
697
|
+
const restricted = new Set([
|
|
698
|
+
"scriptshell",
|
|
699
|
+
"userconfig",
|
|
700
|
+
"globalconfig",
|
|
701
|
+
"yarnpath",
|
|
702
|
+
"plugins",
|
|
703
|
+
"auth",
|
|
704
|
+
"authtoken",
|
|
705
|
+
"password",
|
|
706
|
+
"token",
|
|
707
|
+
"npmauthtoken",
|
|
708
|
+
"npmauthident",
|
|
709
|
+
]);
|
|
710
|
+
for (const line of source.split(/\r?\n/u)) {
|
|
711
|
+
let quote = "";
|
|
712
|
+
let escaped = false;
|
|
713
|
+
let active = "";
|
|
714
|
+
const parts: string[] = [];
|
|
715
|
+
let flowDepth = 0;
|
|
716
|
+
for (const character of line) {
|
|
717
|
+
if (escaped) {
|
|
718
|
+
active += character;
|
|
719
|
+
escaped = false;
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
if (character === "\\" && quote === '"') {
|
|
723
|
+
active += character;
|
|
724
|
+
escaped = true;
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
if (quote) {
|
|
728
|
+
active += character;
|
|
729
|
+
if (character === quote) quote = "";
|
|
730
|
+
} else if (character === '"' || character === "'") {
|
|
731
|
+
quote = character;
|
|
732
|
+
active += character;
|
|
733
|
+
} else if (
|
|
734
|
+
character === "#" ||
|
|
735
|
+
(character === ";" && active.trim() === "")
|
|
736
|
+
) {
|
|
737
|
+
break;
|
|
738
|
+
} else if (
|
|
739
|
+
character === "{" ||
|
|
740
|
+
character === "}" ||
|
|
741
|
+
(character === "," && flowDepth > 0)
|
|
742
|
+
) {
|
|
743
|
+
if (character === "{") flowDepth++;
|
|
744
|
+
if (character === "}") flowDepth = Math.max(0, flowDepth - 1);
|
|
745
|
+
parts.push(active);
|
|
746
|
+
active = "";
|
|
747
|
+
} else active += character;
|
|
748
|
+
}
|
|
749
|
+
parts.push(active);
|
|
750
|
+
for (const part of parts) {
|
|
751
|
+
if (part.includes("${")) return true;
|
|
752
|
+
const directive = part.match(
|
|
753
|
+
/^\s*(?:"((?:\\.|[^"\\])*)"|'([^']*)'|([^\s=]+))\s*(.*)$/u,
|
|
754
|
+
);
|
|
755
|
+
if (!directive) continue;
|
|
756
|
+
const rawKey = directive[1] ?? directive[2] ?? directive[3];
|
|
757
|
+
if (!rawKey.endsWith(":") && !directive[4]) continue;
|
|
758
|
+
const key = rawKey
|
|
759
|
+
.replace(/:$/u, "")
|
|
760
|
+
.split(":")
|
|
761
|
+
.at(-1)
|
|
762
|
+
?.replace(/[-_]/gu, "")
|
|
763
|
+
.toLowerCase();
|
|
764
|
+
if (key && restricted.has(key)) return true;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
return false;
|
|
771
768
|
}
|
|
772
769
|
|
|
773
770
|
function validateAtomicCapability(
|
|
@@ -775,7 +772,73 @@ function validateAtomicCapability(
|
|
|
775
772
|
step: AtomicVerificationContract,
|
|
776
773
|
dependencyOwner: string,
|
|
777
774
|
runnerEnvironment: VerificationRunnerEnvironment,
|
|
775
|
+
executionWritePaths: readonly string[],
|
|
778
776
|
): VerificationCapabilityResult {
|
|
777
|
+
if (
|
|
778
|
+
step.kind === "vitest" &&
|
|
779
|
+
[
|
|
780
|
+
...step.args,
|
|
781
|
+
...(step.runner.kind === "package-script"
|
|
782
|
+
? step.runner.command.split(/\s+/u)
|
|
783
|
+
: []),
|
|
784
|
+
].some((arg) => /^--(?:reporters?|outputFile)(?:[.=]|$)/u.test(arg))
|
|
785
|
+
) {
|
|
786
|
+
return adapterFailure(
|
|
787
|
+
step.id,
|
|
788
|
+
"report-arguments-conflict",
|
|
789
|
+
"Vitest report arguments are owned by the adapter",
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
if (step.executionBindings) {
|
|
793
|
+
for (const [relative, expected] of Object.entries(step.executionBindings)) {
|
|
794
|
+
if (executionWritePaths.includes(relative)) continue;
|
|
795
|
+
const observation = observeSafePath(root, relative);
|
|
796
|
+
if (
|
|
797
|
+
(expected === null && observation.kind !== "absent") ||
|
|
798
|
+
(expected !== null && snapshotFile(root, relative)?.sha256 !== expected)
|
|
799
|
+
) {
|
|
800
|
+
return adapterFailure(
|
|
801
|
+
step.id,
|
|
802
|
+
"verification-config-mismatch",
|
|
803
|
+
"Bound execution inputs changed; recompile the verification contract",
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
for (const relative of verificationBoundInputPaths(step).filter((file) =>
|
|
809
|
+
VERIFICATION_CONFIGURATION_PATHS.includes(file),
|
|
810
|
+
)) {
|
|
811
|
+
const observation = observeSafePath(root, relative);
|
|
812
|
+
if (!["file", "absent"].includes(observation.kind))
|
|
813
|
+
return adapterFailure(
|
|
814
|
+
step.id,
|
|
815
|
+
"verification-config-unsafe",
|
|
816
|
+
"Execution configuration must be a regular file",
|
|
817
|
+
);
|
|
818
|
+
if (
|
|
819
|
+
observation.kind !== "file" ||
|
|
820
|
+
![
|
|
821
|
+
".npmrc",
|
|
822
|
+
".yarnrc",
|
|
823
|
+
".yarnrc.yml",
|
|
824
|
+
"bunfig.toml",
|
|
825
|
+
"pnpm-workspace.yaml",
|
|
826
|
+
].includes(relative)
|
|
827
|
+
)
|
|
828
|
+
continue;
|
|
829
|
+
if (
|
|
830
|
+
lstatSync(path.join(root, relative)).size > 64 * 1024 ||
|
|
831
|
+
unsupportedExecutionConfiguration(
|
|
832
|
+
readFileSync(path.join(root, relative), "utf8"),
|
|
833
|
+
)
|
|
834
|
+
) {
|
|
835
|
+
return adapterFailure(
|
|
836
|
+
step.id,
|
|
837
|
+
"verification-config-unsafe",
|
|
838
|
+
"Execution configuration requires unsupported credentials or host configuration",
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
779
842
|
if (step.kind === "package-script") {
|
|
780
843
|
return validatePackageScript(
|
|
781
844
|
root,
|
|
@@ -862,6 +925,57 @@ function validateAtomicCapability(
|
|
|
862
925
|
return capabilitySuccess(step.id);
|
|
863
926
|
}
|
|
864
927
|
|
|
928
|
+
/** Enrich only a new design draft, never rewrite proof-bound canonical delivery bytes. */
|
|
929
|
+
export function bindDraftVerificationInputs(
|
|
930
|
+
root: string,
|
|
931
|
+
draft: unknown,
|
|
932
|
+
): unknown {
|
|
933
|
+
const visit = (value: unknown): unknown => {
|
|
934
|
+
if (Array.isArray(value)) return value.map(visit);
|
|
935
|
+
if (!value || typeof value !== "object") return value;
|
|
936
|
+
const item = Object.fromEntries(
|
|
937
|
+
Object.entries(value).map(([key, child]) => [
|
|
938
|
+
key,
|
|
939
|
+
// Gate A is immutable approval authority, not an execution contract.
|
|
940
|
+
key === "changeContract" ? structuredClone(child) : visit(child),
|
|
941
|
+
]),
|
|
942
|
+
);
|
|
943
|
+
if (
|
|
944
|
+
item.kind === "package-script" &&
|
|
945
|
+
item.command === undefined &&
|
|
946
|
+
typeof item.script === "string"
|
|
947
|
+
) {
|
|
948
|
+
const scripts = packageManifest(root)?.scripts as
|
|
949
|
+
| Record<string, unknown>
|
|
950
|
+
| undefined;
|
|
951
|
+
item.command = scripts?.[item.script];
|
|
952
|
+
}
|
|
953
|
+
if (
|
|
954
|
+
["vitest", "package-script", "static-check"].includes(
|
|
955
|
+
String(item.kind),
|
|
956
|
+
) &&
|
|
957
|
+
typeof item.id === "string"
|
|
958
|
+
) {
|
|
959
|
+
const validation = validateVerificationContract(item);
|
|
960
|
+
if (
|
|
961
|
+
validation.ok &&
|
|
962
|
+
verificationBoundInputPaths(validation.value).some((file) =>
|
|
963
|
+
VERIFICATION_CONFIGURATION_PATHS.includes(file),
|
|
964
|
+
)
|
|
965
|
+
) {
|
|
966
|
+
item.executionBindings = Object.fromEntries(
|
|
967
|
+
VERIFICATION_CONFIGURATION_PATHS.map((relative) => [
|
|
968
|
+
relative,
|
|
969
|
+
snapshotFile(root, relative)?.sha256 ?? null,
|
|
970
|
+
]),
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
return item;
|
|
975
|
+
};
|
|
976
|
+
return visit(draft);
|
|
977
|
+
}
|
|
978
|
+
|
|
865
979
|
export function validateVerificationCapability(
|
|
866
980
|
root: string,
|
|
867
981
|
value: unknown,
|
|
@@ -882,6 +996,26 @@ export function bindCurrentVerificationCapability(
|
|
|
882
996
|
if (!validation.ok) {
|
|
883
997
|
throw new Error("verification-capability-invariant");
|
|
884
998
|
}
|
|
999
|
+
let paths: string[];
|
|
1000
|
+
let runnerDigest: string;
|
|
1001
|
+
try {
|
|
1002
|
+
paths = verificationRunnerFiles(
|
|
1003
|
+
options.dependencyOwner ?? root,
|
|
1004
|
+
validation.value,
|
|
1005
|
+
capability.runnerBindings,
|
|
1006
|
+
);
|
|
1007
|
+
runnerDigest = verificationEnvironmentDigest(paths);
|
|
1008
|
+
} catch {
|
|
1009
|
+
return {
|
|
1010
|
+
ok: false,
|
|
1011
|
+
diagnostic: {
|
|
1012
|
+
kind: "verification-adapter",
|
|
1013
|
+
verificationId: capability.verificationId,
|
|
1014
|
+
code: "runner-missing",
|
|
1015
|
+
message: "Installed verification runner changed or is unavailable",
|
|
1016
|
+
},
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
885
1019
|
return {
|
|
886
1020
|
ok: true,
|
|
887
1021
|
value: {
|
|
@@ -892,8 +1026,9 @@ export function bindCurrentVerificationCapability(
|
|
|
892
1026
|
})),
|
|
893
1027
|
inputSnapshot: snapshotFiles(
|
|
894
1028
|
root,
|
|
895
|
-
|
|
1029
|
+
verificationBoundInputPaths(validation.value),
|
|
896
1030
|
),
|
|
1031
|
+
runnerSnapshot: { paths, digest: runnerDigest },
|
|
897
1032
|
},
|
|
898
1033
|
};
|
|
899
1034
|
}
|
|
@@ -902,7 +1037,65 @@ export function isVerificationCapabilityCurrent(
|
|
|
902
1037
|
root: string,
|
|
903
1038
|
capability: CurrentVerificationCapability,
|
|
904
1039
|
): boolean {
|
|
905
|
-
|
|
1040
|
+
try {
|
|
1041
|
+
return (
|
|
1042
|
+
isCurrent(root, capability.inputSnapshot) &&
|
|
1043
|
+
verificationEnvironmentDigest(capability.runnerSnapshot.paths) ===
|
|
1044
|
+
capability.runnerSnapshot.digest
|
|
1045
|
+
);
|
|
1046
|
+
} catch {
|
|
1047
|
+
return false;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
export function verificationRunnerFiles(
|
|
1052
|
+
dependencyOwner: string,
|
|
1053
|
+
verification: StructuredVerificationContract,
|
|
1054
|
+
bindings: readonly VerificationRunnerBinding[],
|
|
1055
|
+
): string[] {
|
|
1056
|
+
const files = bindings.flatMap((binding) => [
|
|
1057
|
+
binding.executablePath,
|
|
1058
|
+
...(binding.fixedArgs ?? []).filter((arg) => path.isAbsolute(arg)),
|
|
1059
|
+
]);
|
|
1060
|
+
for (const file of [...new Set(files)]) {
|
|
1061
|
+
// Keep the launcher identity and explicitly include its resolved bytes:
|
|
1062
|
+
// the digest intentionally does not follow arbitrary dependency symlinks.
|
|
1063
|
+
const resolved = realpathSync(file);
|
|
1064
|
+
files.push(resolved);
|
|
1065
|
+
// System runners have no mountSource. Bind their installed package too,
|
|
1066
|
+
// including implementation modules and bundled dependencies beyond bin/.
|
|
1067
|
+
for (
|
|
1068
|
+
let directory = path.dirname(resolved);
|
|
1069
|
+
directory !== path.dirname(directory);
|
|
1070
|
+
directory = path.dirname(directory)
|
|
1071
|
+
) {
|
|
1072
|
+
if (
|
|
1073
|
+
lstatSync(path.join(directory, "package.json"), {
|
|
1074
|
+
throwIfNoEntry: false,
|
|
1075
|
+
})?.isFile()
|
|
1076
|
+
) {
|
|
1077
|
+
files.push(directory);
|
|
1078
|
+
break;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
for (const step of verificationSteps(verification)) {
|
|
1083
|
+
if (
|
|
1084
|
+
!("runner" in step) ||
|
|
1085
|
+
step.runner.kind === "node" ||
|
|
1086
|
+
step.runner.kind === "package-script"
|
|
1087
|
+
)
|
|
1088
|
+
continue;
|
|
1089
|
+
const executable =
|
|
1090
|
+
step.kind === "vitest" ? "vitest" : step.runner.executable;
|
|
1091
|
+
const file = path.join(dependencyOwner, "node_modules/.bin", executable);
|
|
1092
|
+
try {
|
|
1093
|
+
files.push(file, realpathSync(file));
|
|
1094
|
+
} catch {
|
|
1095
|
+
/* Admission owns missing runner diagnostics. */
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
return [...new Set(files)];
|
|
906
1099
|
}
|
|
907
1100
|
|
|
908
1101
|
export function validateVerificationAdapterCapability(
|
|
@@ -945,6 +1138,7 @@ function validateCapability(
|
|
|
945
1138
|
dependencyOwner,
|
|
946
1139
|
options.runnerEnvironment ?? {},
|
|
947
1140
|
requireInputAvailability,
|
|
1141
|
+
options.executionWritePaths ?? [],
|
|
948
1142
|
);
|
|
949
1143
|
}
|
|
950
1144
|
|
|
@@ -954,6 +1148,7 @@ function validateAcceptedVerificationCapability(
|
|
|
954
1148
|
dependencyOwner: string,
|
|
955
1149
|
runnerEnvironment: VerificationRunnerEnvironment,
|
|
956
1150
|
requireInputAvailability: boolean,
|
|
1151
|
+
executionWritePaths: readonly string[],
|
|
957
1152
|
): VerificationCapabilityResult {
|
|
958
1153
|
if (requireInputAvailability) {
|
|
959
1154
|
for (const input of verificationInputPaths(verification)) {
|
|
@@ -973,6 +1168,7 @@ function validateAcceptedVerificationCapability(
|
|
|
973
1168
|
step,
|
|
974
1169
|
dependencyOwner,
|
|
975
1170
|
runnerEnvironment,
|
|
1171
|
+
executionWritePaths,
|
|
976
1172
|
);
|
|
977
1173
|
if (!capability.ok) return capability;
|
|
978
1174
|
runnerBindings.push(...capability.runnerBindings);
|