@abelxiaoxing/cadence 1.0.2 → 1.0.3
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/agents/contract-reviewer.md +2 -2
- package/agents/diagnosis-worker.md +3 -2
- package/agents/implementation-worker.md +3 -2
- package/package.json +1 -1
- package/prompts/abel-design.md +13 -3
- package/prompts/abel-implement.md +50 -21
- package/skills/abel-workflow/SKILL.md +57 -25
- package/src/candidate-preflight.ts +361 -56
- package/src/child-session.ts +92 -27
- package/src/contracts.ts +987 -38
- package/src/index.ts +50 -24
- package/src/parent-provider.ts +89 -23
- package/src/patch.ts +269 -41
- package/src/result-store.ts +254 -16
- package/src/runtime.ts +1281 -560
- package/src/scheduler.ts +59 -16
- package/src/subagent-activity.ts +56 -25
- package/src/submit-tool.ts +12 -2
- package/src/worker.ts +114 -195
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from "node:fs";
|
|
16
16
|
import path from "node:path";
|
|
17
17
|
import {
|
|
18
|
+
type CandidateFailure,
|
|
18
19
|
diffWritePaths,
|
|
19
20
|
isValidRelativePath,
|
|
20
21
|
type VerificationContract,
|
|
@@ -42,6 +43,7 @@ export interface CandidatePreflightInput {
|
|
|
42
43
|
root: string;
|
|
43
44
|
diff: Buffer;
|
|
44
45
|
writeSet: string[];
|
|
46
|
+
approvedDependencies: string[];
|
|
45
47
|
snapshot: Bound;
|
|
46
48
|
baseline: BaselineEntry[];
|
|
47
49
|
verification: VerificationContract;
|
|
@@ -51,6 +53,16 @@ export interface CandidatePreflightInput {
|
|
|
51
53
|
signal?: AbortSignal;
|
|
52
54
|
}
|
|
53
55
|
|
|
56
|
+
type PreflightCandidateFailure = Exclude<
|
|
57
|
+
CandidateFailure,
|
|
58
|
+
{ kind: "result-limit" }
|
|
59
|
+
>;
|
|
60
|
+
type PreflightFailureKind = PreflightCandidateFailure["kind"];
|
|
61
|
+
type PreflightFailureFor<Kind extends PreflightFailureKind> = Extract<
|
|
62
|
+
PreflightCandidateFailure,
|
|
63
|
+
{ kind: Kind }
|
|
64
|
+
>;
|
|
65
|
+
|
|
54
66
|
export type CandidatePreflightResult =
|
|
55
67
|
| {
|
|
56
68
|
ok: true;
|
|
@@ -62,17 +74,15 @@ export type CandidatePreflightResult =
|
|
|
62
74
|
identityMatch: boolean;
|
|
63
75
|
checkoutRemoved: true;
|
|
64
76
|
}
|
|
65
|
-
| {
|
|
77
|
+
| (PreflightCandidateFailure & {
|
|
66
78
|
ok: false;
|
|
67
|
-
class: "artifact" | "design" | "environment" | "stale" | "cancelled";
|
|
68
|
-
code: string;
|
|
69
79
|
commandId?: string;
|
|
70
80
|
exitCode?: number;
|
|
71
81
|
testCount?: number;
|
|
72
82
|
identityMatch?: boolean;
|
|
73
83
|
checkoutRemoved: boolean;
|
|
74
84
|
excerpt?: string;
|
|
75
|
-
};
|
|
85
|
+
});
|
|
76
86
|
|
|
77
87
|
export interface CandidatePreflightDependencies {
|
|
78
88
|
mkdtemp: typeof mkdtempSync;
|
|
@@ -113,20 +123,15 @@ interface FailureDetails {
|
|
|
113
123
|
|
|
114
124
|
class PreflightFailure extends Error {
|
|
115
125
|
constructor(
|
|
116
|
-
readonly
|
|
117
|
-
| "artifact"
|
|
118
|
-
| "design"
|
|
119
|
-
| "environment"
|
|
120
|
-
| "stale"
|
|
121
|
-
| "cancelled",
|
|
122
|
-
readonly code: string,
|
|
126
|
+
readonly failure: PreflightCandidateFailure,
|
|
123
127
|
readonly details: FailureDetails = {},
|
|
124
128
|
) {
|
|
125
|
-
super(code);
|
|
129
|
+
super(failure.code);
|
|
126
130
|
}
|
|
127
131
|
}
|
|
128
132
|
|
|
129
133
|
const SHA256 = /^[0-9a-f]{64}$/;
|
|
134
|
+
const PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/iu;
|
|
130
135
|
const LOCKFILES = [
|
|
131
136
|
"bun.lock",
|
|
132
137
|
"bun.lockb",
|
|
@@ -210,27 +215,33 @@ function spawnCommand(
|
|
|
210
215
|
});
|
|
211
216
|
}
|
|
212
217
|
|
|
213
|
-
function
|
|
214
|
-
|
|
215
|
-
code:
|
|
218
|
+
function typedFailure<Kind extends PreflightFailureKind>(
|
|
219
|
+
kind: Kind,
|
|
220
|
+
code: PreflightFailureFor<Kind>["code"],
|
|
221
|
+
): PreflightFailureFor<Kind> {
|
|
222
|
+
return { kind, code } as PreflightFailureFor<Kind>;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function reject<Kind extends PreflightFailureKind>(
|
|
226
|
+
kind: Kind,
|
|
227
|
+
code: PreflightFailureFor<Kind>["code"],
|
|
216
228
|
details: FailureDetails = {},
|
|
217
229
|
): never {
|
|
218
|
-
throw new PreflightFailure(
|
|
230
|
+
throw new PreflightFailure(typedFailure(kind, code), details);
|
|
219
231
|
}
|
|
220
232
|
|
|
221
|
-
function failed(
|
|
222
|
-
|
|
223
|
-
code:
|
|
233
|
+
function failed<Kind extends PreflightFailureKind>(
|
|
234
|
+
kind: Kind,
|
|
235
|
+
code: PreflightFailureFor<Kind>["code"],
|
|
224
236
|
details: FailureDetails = {},
|
|
225
237
|
checkoutRemoved = true,
|
|
226
238
|
): CandidatePreflightResult {
|
|
227
239
|
return {
|
|
228
240
|
ok: false,
|
|
229
|
-
|
|
230
|
-
code,
|
|
241
|
+
...typedFailure(kind, code),
|
|
231
242
|
...details,
|
|
232
243
|
checkoutRemoved,
|
|
233
|
-
};
|
|
244
|
+
} as CandidatePreflightResult;
|
|
234
245
|
}
|
|
235
246
|
|
|
236
247
|
function removeCheckout(
|
|
@@ -329,22 +340,13 @@ async function run(
|
|
|
329
340
|
...(input === undefined ? {} : { input }),
|
|
330
341
|
...(signal === undefined ? {} : { signal }),
|
|
331
342
|
};
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
};
|
|
340
|
-
} catch (error) {
|
|
341
|
-
return {
|
|
342
|
-
status: null,
|
|
343
|
-
stdout: "",
|
|
344
|
-
stderr: "",
|
|
345
|
-
error: error as Error,
|
|
346
|
-
};
|
|
347
|
-
}
|
|
343
|
+
const result = await dependencies.spawn(command, args, options);
|
|
344
|
+
return {
|
|
345
|
+
status: result.status,
|
|
346
|
+
stdout: result.stdout ?? "",
|
|
347
|
+
stderr: result.stderr ?? "",
|
|
348
|
+
...(result.error === undefined ? {} : { error: result.error }),
|
|
349
|
+
};
|
|
348
350
|
}
|
|
349
351
|
|
|
350
352
|
function commandExcerpt(
|
|
@@ -381,6 +383,16 @@ function validateInput(input: CandidatePreflightInput): {
|
|
|
381
383
|
if (!validSnapshot(input.snapshot)) reject("artifact", "invalid-snapshot");
|
|
382
384
|
const verification = validateVerificationContract(input.verification);
|
|
383
385
|
if (!verification.ok) reject("artifact", "invalid-verification-contract");
|
|
386
|
+
if (
|
|
387
|
+
!Array.isArray(input.approvedDependencies) ||
|
|
388
|
+
input.approvedDependencies.some(
|
|
389
|
+
(dependency) => typeof dependency !== "string" || dependency.length === 0,
|
|
390
|
+
) ||
|
|
391
|
+
new Set(input.approvedDependencies).size !==
|
|
392
|
+
input.approvedDependencies.length
|
|
393
|
+
) {
|
|
394
|
+
reject("artifact", "invalid-approved-dependencies");
|
|
395
|
+
}
|
|
384
396
|
|
|
385
397
|
let targets: string[];
|
|
386
398
|
try {
|
|
@@ -477,8 +489,9 @@ function validateInput(input: CandidatePreflightInput): {
|
|
|
477
489
|
}
|
|
478
490
|
if (targets.some((target) => !entries.has(target)))
|
|
479
491
|
reject("artifact", "target-baseline-missing");
|
|
480
|
-
for (
|
|
481
|
-
|
|
492
|
+
for (let index = 3; index < input.verification.argv.length; index++) {
|
|
493
|
+
const testPath = input.verification.argv[index];
|
|
494
|
+
if (testPath === undefined || !entries.has(testPath))
|
|
482
495
|
reject("artifact", "verification-baseline-missing");
|
|
483
496
|
}
|
|
484
497
|
return { targets, lockPath, dependencyPath };
|
|
@@ -786,6 +799,243 @@ function structuredTestReport(reportPath: string): StructuredTestReport {
|
|
|
786
799
|
};
|
|
787
800
|
}
|
|
788
801
|
|
|
802
|
+
interface DependencyContractEntry {
|
|
803
|
+
value: string;
|
|
804
|
+
names: string[];
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
function canonicalJson(value: unknown): string {
|
|
808
|
+
if (Array.isArray(value)) {
|
|
809
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
810
|
+
}
|
|
811
|
+
if (value && typeof value === "object") {
|
|
812
|
+
return `{${Object.entries(value as Record<string, unknown>)
|
|
813
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
814
|
+
.map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`)
|
|
815
|
+
.join(",")}}`;
|
|
816
|
+
}
|
|
817
|
+
return JSON.stringify(value);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
function dependencyName(selector: string): string | null {
|
|
821
|
+
const scoped = /(@[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*)/iu.exec(
|
|
822
|
+
selector,
|
|
823
|
+
)?.[1];
|
|
824
|
+
if (scoped) return scoped;
|
|
825
|
+
const segment = selector.split("/").at(-1) ?? "";
|
|
826
|
+
const name = segment.replace(/@.*$/u, "");
|
|
827
|
+
return /^[a-z0-9][a-z0-9._-]*$/iu.test(name) ? name : null;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
function addResolutionEntries(
|
|
831
|
+
result: Map<string, DependencyContractEntry>,
|
|
832
|
+
section: string,
|
|
833
|
+
value: unknown,
|
|
834
|
+
path: string[] = [],
|
|
835
|
+
names: string[] = [],
|
|
836
|
+
): void {
|
|
837
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
838
|
+
for (const [key, entry] of Object.entries(
|
|
839
|
+
value as Record<string, unknown>,
|
|
840
|
+
)) {
|
|
841
|
+
const name = dependencyName(key);
|
|
842
|
+
addResolutionEntries(
|
|
843
|
+
result,
|
|
844
|
+
section,
|
|
845
|
+
entry,
|
|
846
|
+
[...path, key],
|
|
847
|
+
name && key !== "." ? [...new Set([...names, name])] : names,
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
result.set(`${section}:${path.join(":")}`, {
|
|
853
|
+
value: canonicalJson(value),
|
|
854
|
+
names,
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function addPackageListPolicy(
|
|
859
|
+
result: Map<string, DependencyContractEntry>,
|
|
860
|
+
section: string,
|
|
861
|
+
value: unknown,
|
|
862
|
+
): void {
|
|
863
|
+
if (value === undefined) return;
|
|
864
|
+
if (
|
|
865
|
+
!Array.isArray(value) ||
|
|
866
|
+
value.some(
|
|
867
|
+
(name) => typeof name !== "string" || !PACKAGE_NAME.test(name),
|
|
868
|
+
) ||
|
|
869
|
+
new Set(value).size !== value.length
|
|
870
|
+
) {
|
|
871
|
+
reject("artifact", "package-manifest-invalid");
|
|
872
|
+
}
|
|
873
|
+
result.set(section, {
|
|
874
|
+
value: canonicalJson([...value].sort()),
|
|
875
|
+
names: [],
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
function addPatchedDependencies(
|
|
880
|
+
result: Map<string, DependencyContractEntry>,
|
|
881
|
+
section: string,
|
|
882
|
+
value: unknown,
|
|
883
|
+
): void {
|
|
884
|
+
if (value === undefined) return;
|
|
885
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
886
|
+
reject("artifact", "package-manifest-invalid");
|
|
887
|
+
}
|
|
888
|
+
for (const [selector, patchPath] of Object.entries(value)) {
|
|
889
|
+
if (
|
|
890
|
+
!dependencyName(selector) ||
|
|
891
|
+
typeof patchPath !== "string" ||
|
|
892
|
+
!isValidRelativePath(patchPath)
|
|
893
|
+
) {
|
|
894
|
+
reject("artifact", "package-manifest-invalid");
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
result.set(section, { value: canonicalJson(value), names: [] });
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function dependencyContract(
|
|
901
|
+
manifestPath: string,
|
|
902
|
+
): Map<string, DependencyContractEntry> {
|
|
903
|
+
let manifest: unknown;
|
|
904
|
+
try {
|
|
905
|
+
manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
906
|
+
} catch {
|
|
907
|
+
reject("artifact", "package-manifest-invalid");
|
|
908
|
+
}
|
|
909
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
910
|
+
reject("artifact", "package-manifest-invalid");
|
|
911
|
+
}
|
|
912
|
+
const record = manifest as Record<string, unknown>;
|
|
913
|
+
const result = new Map<string, DependencyContractEntry>();
|
|
914
|
+
for (const section of [
|
|
915
|
+
"dependencies",
|
|
916
|
+
"devDependencies",
|
|
917
|
+
"peerDependencies",
|
|
918
|
+
"optionalDependencies",
|
|
919
|
+
]) {
|
|
920
|
+
const dependencies = record[section];
|
|
921
|
+
if (dependencies === undefined) continue;
|
|
922
|
+
if (
|
|
923
|
+
!dependencies ||
|
|
924
|
+
typeof dependencies !== "object" ||
|
|
925
|
+
Array.isArray(dependencies)
|
|
926
|
+
) {
|
|
927
|
+
reject("artifact", "package-manifest-invalid");
|
|
928
|
+
}
|
|
929
|
+
for (const [name, version] of Object.entries(dependencies)) {
|
|
930
|
+
if (typeof version !== "string")
|
|
931
|
+
reject("artifact", "package-manifest-invalid");
|
|
932
|
+
result.set(`${section}:${name}`, { value: version, names: [name] });
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
for (const section of [
|
|
936
|
+
"overrides",
|
|
937
|
+
"resolutions",
|
|
938
|
+
"dependenciesMeta",
|
|
939
|
+
"peerDependenciesMeta",
|
|
940
|
+
]) {
|
|
941
|
+
if (record[section] !== undefined) {
|
|
942
|
+
addResolutionEntries(result, section, record[section]);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
for (const section of ["workspaces", "catalog", "catalogs"] as const) {
|
|
946
|
+
if (record[section] !== undefined) {
|
|
947
|
+
result.set(section, {
|
|
948
|
+
value: canonicalJson(record[section]),
|
|
949
|
+
names: [],
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
addPackageListPolicy(
|
|
954
|
+
result,
|
|
955
|
+
"trustedDependencies",
|
|
956
|
+
record.trustedDependencies,
|
|
957
|
+
);
|
|
958
|
+
addPackageListPolicy(
|
|
959
|
+
result,
|
|
960
|
+
"blockedDependencies",
|
|
961
|
+
record.blockedDependencies,
|
|
962
|
+
);
|
|
963
|
+
addPackageListPolicy(
|
|
964
|
+
result,
|
|
965
|
+
"bundledDependencies",
|
|
966
|
+
record.bundledDependencies,
|
|
967
|
+
);
|
|
968
|
+
addPackageListPolicy(result, "bundleDependencies", record.bundleDependencies);
|
|
969
|
+
addPatchedDependencies(
|
|
970
|
+
result,
|
|
971
|
+
"patchedDependencies",
|
|
972
|
+
record.patchedDependencies,
|
|
973
|
+
);
|
|
974
|
+
const pnpm = record.pnpm;
|
|
975
|
+
if (pnpm !== undefined) {
|
|
976
|
+
if (!pnpm || typeof pnpm !== "object" || Array.isArray(pnpm)) {
|
|
977
|
+
reject("artifact", "package-manifest-invalid");
|
|
978
|
+
}
|
|
979
|
+
const pnpmRecord = pnpm as Record<string, unknown>;
|
|
980
|
+
const resolutionSections = [
|
|
981
|
+
"overrides",
|
|
982
|
+
"packageExtensions",
|
|
983
|
+
"peerDependencyRules",
|
|
984
|
+
"allowedDeprecatedVersions",
|
|
985
|
+
] as const;
|
|
986
|
+
for (const section of resolutionSections) {
|
|
987
|
+
const value = pnpmRecord[section];
|
|
988
|
+
if (value !== undefined) {
|
|
989
|
+
addResolutionEntries(result, `pnpm.${section}`, value);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
addPatchedDependencies(
|
|
993
|
+
result,
|
|
994
|
+
"pnpm.patchedDependencies",
|
|
995
|
+
pnpmRecord.patchedDependencies,
|
|
996
|
+
);
|
|
997
|
+
const tracked = new Set<string>([
|
|
998
|
+
...resolutionSections,
|
|
999
|
+
"patchedDependencies",
|
|
1000
|
+
]);
|
|
1001
|
+
const policy = Object.fromEntries(
|
|
1002
|
+
Object.entries(pnpmRecord).filter(([key]) => !tracked.has(key)),
|
|
1003
|
+
);
|
|
1004
|
+
if (Object.keys(policy).length > 0) {
|
|
1005
|
+
result.set("pnpm.policy", { value: canonicalJson(policy), names: [] });
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
return result;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
function rejectUnapprovedDependencies(
|
|
1012
|
+
before: Map<string, DependencyContractEntry>,
|
|
1013
|
+
after: Map<string, DependencyContractEntry>,
|
|
1014
|
+
approved: string[],
|
|
1015
|
+
lockfileChanged: boolean,
|
|
1016
|
+
): boolean {
|
|
1017
|
+
const approvedNames = new Set(approved);
|
|
1018
|
+
const keys = new Set([...before.keys(), ...after.keys()]);
|
|
1019
|
+
let manifestChanged = false;
|
|
1020
|
+
for (const key of keys) {
|
|
1021
|
+
const previous = before.get(key);
|
|
1022
|
+
const next = after.get(key);
|
|
1023
|
+
if (previous?.value === next?.value) continue;
|
|
1024
|
+
manifestChanged = true;
|
|
1025
|
+
const names = new Set([...(previous?.names ?? []), ...(next?.names ?? [])]);
|
|
1026
|
+
if (
|
|
1027
|
+
names.size === 0 ||
|
|
1028
|
+
[...names].some((name) => !approvedNames.has(name))
|
|
1029
|
+
) {
|
|
1030
|
+
reject("approval-boundary", "unapproved-dependency-change");
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
if (lockfileChanged) {
|
|
1034
|
+
reject("approval-boundary", "unapproved-dependency-change");
|
|
1035
|
+
}
|
|
1036
|
+
return manifestChanged;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
789
1039
|
function normalizeTarget(
|
|
790
1040
|
input: CandidatePreflightInput,
|
|
791
1041
|
result: CommandResult,
|
|
@@ -829,7 +1079,7 @@ function normalizeTarget(
|
|
|
829
1079
|
enoughTests &&
|
|
830
1080
|
loadable
|
|
831
1081
|
) {
|
|
832
|
-
return failed("
|
|
1082
|
+
return failed("artifact", "red-not-witnessed", {
|
|
833
1083
|
...details,
|
|
834
1084
|
excerpt: commandExcerpt(result, secrets),
|
|
835
1085
|
});
|
|
@@ -890,7 +1140,9 @@ export async function preflightCandidate(
|
|
|
890
1140
|
let temp: string | undefined;
|
|
891
1141
|
let checkout: string | undefined;
|
|
892
1142
|
let targets: string[] = [];
|
|
893
|
-
let outcome: CandidatePreflightResult;
|
|
1143
|
+
let outcome: CandidatePreflightResult | undefined;
|
|
1144
|
+
let unknownError: unknown;
|
|
1145
|
+
let hasUnknownError = false;
|
|
894
1146
|
try {
|
|
895
1147
|
if (input.signal?.aborted) reject("cancelled", "cancelled");
|
|
896
1148
|
if (!validSnapshot(input.snapshot)) reject("artifact", "invalid-snapshot");
|
|
@@ -1005,6 +1257,9 @@ export async function preflightCandidate(
|
|
|
1005
1257
|
reconstructBoundDirectories(input, checkout, validated.dependencyPath);
|
|
1006
1258
|
reconstructBaseline(input, checkout);
|
|
1007
1259
|
scanCheckout(checkout);
|
|
1260
|
+
const dependenciesBefore = dependencyContract(
|
|
1261
|
+
path.join(checkout, "package.json"),
|
|
1262
|
+
);
|
|
1008
1263
|
|
|
1009
1264
|
result = await run(
|
|
1010
1265
|
dependencies,
|
|
@@ -1031,6 +1286,14 @@ export async function preflightCandidate(
|
|
|
1031
1286
|
if (input.signal?.aborted) reject("cancelled", "cancelled");
|
|
1032
1287
|
if (result.error) reject("environment", "git-apply-unavailable");
|
|
1033
1288
|
if (result.status !== 0) reject("artifact", "git-apply-failed");
|
|
1289
|
+
const dependencyManifestChanged = rejectUnapprovedDependencies(
|
|
1290
|
+
dependenciesBefore,
|
|
1291
|
+
dependencyContract(path.join(checkout, "package.json")),
|
|
1292
|
+
input.approvedDependencies,
|
|
1293
|
+
validated.targets.some((target) =>
|
|
1294
|
+
(LOCKFILES as readonly string[]).includes(target),
|
|
1295
|
+
),
|
|
1296
|
+
);
|
|
1034
1297
|
await requireRegularGitModes(dependencies, checkout, input.signal);
|
|
1035
1298
|
scanCheckout(checkout);
|
|
1036
1299
|
|
|
@@ -1073,6 +1336,39 @@ export async function preflightCandidate(
|
|
|
1073
1336
|
excerpt: commandExcerpt(result, [root, checkout]),
|
|
1074
1337
|
});
|
|
1075
1338
|
|
|
1339
|
+
if (dependencyManifestChanged) {
|
|
1340
|
+
const dependencyArgs = sandboxArgs(
|
|
1341
|
+
checkout,
|
|
1342
|
+
dependencySource,
|
|
1343
|
+
input.dependencyTarget.kind === "dir",
|
|
1344
|
+
bunExecutable,
|
|
1345
|
+
[
|
|
1346
|
+
"bun",
|
|
1347
|
+
"install",
|
|
1348
|
+
"--frozen-lockfile",
|
|
1349
|
+
"--dry-run",
|
|
1350
|
+
"--ignore-scripts",
|
|
1351
|
+
"--no-progress",
|
|
1352
|
+
],
|
|
1353
|
+
);
|
|
1354
|
+
result = await run(
|
|
1355
|
+
dependencies,
|
|
1356
|
+
checkout,
|
|
1357
|
+
bwrap,
|
|
1358
|
+
dependencyArgs,
|
|
1359
|
+
undefined,
|
|
1360
|
+
input.signal,
|
|
1361
|
+
);
|
|
1362
|
+
if (input.signal?.aborted) reject("cancelled", "cancelled");
|
|
1363
|
+
if (result.error || result.status === null)
|
|
1364
|
+
reject("environment", "bubblewrap-launch-failed", {
|
|
1365
|
+
excerpt: commandExcerpt(result, [root, checkout]),
|
|
1366
|
+
});
|
|
1367
|
+
if (result.status !== 0) {
|
|
1368
|
+
reject("approval-boundary", "unapproved-dependency-change");
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1076
1372
|
if (!isStaticCheck(input.verification)) {
|
|
1077
1373
|
const checkArgs = sandboxArgs(
|
|
1078
1374
|
checkout,
|
|
@@ -1144,20 +1440,29 @@ export async function preflightCandidate(
|
|
|
1144
1440
|
} catch (error) {
|
|
1145
1441
|
if (input.signal?.aborted) outcome = failed("cancelled", "cancelled");
|
|
1146
1442
|
else if (error instanceof PreflightFailure)
|
|
1147
|
-
outcome =
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1443
|
+
outcome = {
|
|
1444
|
+
ok: false,
|
|
1445
|
+
...error.failure,
|
|
1446
|
+
...error.details,
|
|
1447
|
+
checkoutRemoved: true,
|
|
1448
|
+
} as CandidatePreflightResult;
|
|
1449
|
+
else {
|
|
1450
|
+
unknownError = error;
|
|
1451
|
+
hasUnknownError = true;
|
|
1452
|
+
}
|
|
1152
1453
|
}
|
|
1153
|
-
if (temp
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1454
|
+
if (temp !== undefined) {
|
|
1455
|
+
const cleanup = removeCheckout(temp, dependencies);
|
|
1456
|
+
if (cleanup.failed && !hasUnknownError)
|
|
1457
|
+
return failed(
|
|
1458
|
+
"environment",
|
|
1459
|
+
"checkout-cleanup-failed",
|
|
1460
|
+
{},
|
|
1461
|
+
cleanup.removed,
|
|
1462
|
+
);
|
|
1463
|
+
}
|
|
1464
|
+
if (hasUnknownError) throw unknownError;
|
|
1465
|
+
if (outcome === undefined)
|
|
1466
|
+
throw new Error("candidate preflight outcome unavailable");
|
|
1162
1467
|
return outcome;
|
|
1163
1468
|
}
|