@tetsuo-ai/agenc 0.10.0 → 0.13.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/generated/agenc-runtime-manifest-v2.json +152 -119
- package/lib/generated-wrapper.d.mts +2 -0
- package/lib/generated-wrapper.mjs +113 -8
- package/lib/runtime-archive.d.mts +58 -0
- package/lib/runtime-archive.mjs +250 -0
- package/lib/runtime-manager.mjs +121 -32
- package/lib/runtime-release-contract.d.mts +55 -1
- package/lib/runtime-release-contract.mjs +199 -5
- package/package.json +3 -2
- package/src/launcher.mjs +104 -12
|
@@ -13,7 +13,14 @@ export interface RuntimeReleaseArtifact {
|
|
|
13
13
|
readonly attestationUrl?: string;
|
|
14
14
|
readonly attestationSha256?: string;
|
|
15
15
|
readonly attestationBytes?: number;
|
|
16
|
-
readonly
|
|
16
|
+
readonly buildProvenanceUrl?: string;
|
|
17
|
+
readonly buildProvenanceSha256?: string;
|
|
18
|
+
readonly buildProvenanceBytes?: number;
|
|
19
|
+
readonly bins?: {
|
|
20
|
+
readonly agenc?: string;
|
|
21
|
+
readonly node?: string;
|
|
22
|
+
readonly nodeLibrary?: string;
|
|
23
|
+
};
|
|
17
24
|
readonly [key: string]: unknown;
|
|
18
25
|
}
|
|
19
26
|
|
|
@@ -32,6 +39,10 @@ export const MAX_RUNTIME_ARTIFACT_BYTES: number;
|
|
|
32
39
|
export const MAX_RUNTIME_ATTESTATION_BYTES: number;
|
|
33
40
|
export const OFFICIAL_RELEASE_REPOSITORY: string;
|
|
34
41
|
export const OFFICIAL_SOURCE_REPOSITORY: string;
|
|
42
|
+
export const FROZEN_LEGACY_RUNTIME_VERSION: "0.7.2";
|
|
43
|
+
export const MINIMUM_PRIVATE_NODE_RUNTIME_VERSION: "0.11.2";
|
|
44
|
+
export const MINIMUM_DUAL_PROVENANCE_RUNTIME_VERSION: "0.13.0";
|
|
45
|
+
export const PRIVATE_NODE_BOOTSTRAP_RELEASE_TAG: "agenc-v0.11.2";
|
|
35
46
|
export const OFFICIAL_RELEASE_WORKFLOW: string;
|
|
36
47
|
export const RUNTIME_ATTESTATION_POLICY: Readonly<{
|
|
37
48
|
repository: string;
|
|
@@ -40,6 +51,14 @@ export const RUNTIME_ATTESTATION_POLICY: Readonly<{
|
|
|
40
51
|
oidcIssuer: string;
|
|
41
52
|
predicateType: string;
|
|
42
53
|
}>;
|
|
54
|
+
export const RUNTIME_BUILD_PROVENANCE_POLICY: Readonly<{
|
|
55
|
+
repository: string;
|
|
56
|
+
signerWorkflow: string;
|
|
57
|
+
hostname: string;
|
|
58
|
+
oidcIssuer: string;
|
|
59
|
+
predicateType: string;
|
|
60
|
+
sourceRef: "refs/heads/main";
|
|
61
|
+
}>;
|
|
43
62
|
export const PINNED_GITHUB_CLI_VERSION: string;
|
|
44
63
|
export const PINNED_GITHUB_CLI_ARTIFACTS: Readonly<Record<string, Readonly<{
|
|
45
64
|
file: string;
|
|
@@ -47,6 +66,8 @@ export const PINNED_GITHUB_CLI_ARTIFACTS: Readonly<Record<string, Readonly<{
|
|
|
47
66
|
sha256: string;
|
|
48
67
|
bytes: number;
|
|
49
68
|
executable: string;
|
|
69
|
+
executableSha256: string;
|
|
70
|
+
executableBytes: number;
|
|
50
71
|
}>>>;
|
|
51
72
|
export const RUNTIME_MANIFEST_TRUST_MODES: readonly RuntimeManifestTrustMode[];
|
|
52
73
|
|
|
@@ -57,6 +78,13 @@ export function canonicalRuntimeAttestationVerificationArgs(options: {
|
|
|
57
78
|
readonly sourceRef: string;
|
|
58
79
|
}): readonly string[];
|
|
59
80
|
|
|
81
|
+
export function canonicalRuntimeBuildProvenanceVerificationArgs(options: {
|
|
82
|
+
readonly subjectPath: string;
|
|
83
|
+
readonly bundlePath: string;
|
|
84
|
+
readonly sourceCommit: string;
|
|
85
|
+
readonly sourceRef?: "refs/heads/main";
|
|
86
|
+
}): readonly string[];
|
|
87
|
+
|
|
60
88
|
export type LocalFileUrlPlatform =
|
|
61
89
|
| "win"
|
|
62
90
|
| "win32"
|
|
@@ -79,6 +107,32 @@ export function canonicalRuntimeArtifactName(
|
|
|
79
107
|
>,
|
|
80
108
|
): string;
|
|
81
109
|
|
|
110
|
+
export function canonicalRuntimeNodeBin(platform: string): string;
|
|
111
|
+
|
|
112
|
+
export function canonicalRuntimeNodeLibrary(platform: string): string | undefined;
|
|
113
|
+
|
|
114
|
+
export function requireSupportedRuntimeVersion(version: string): string;
|
|
115
|
+
|
|
116
|
+
export function runtimeVersionRequiresDualProvenance(version: string): boolean;
|
|
117
|
+
|
|
118
|
+
export interface RuntimeReleaseCandidateIdentity {
|
|
119
|
+
readonly workflow: "release-runtime.yml";
|
|
120
|
+
readonly runId: number;
|
|
121
|
+
readonly runAttempt: number;
|
|
122
|
+
readonly runUrl: string;
|
|
123
|
+
readonly phase: "candidate";
|
|
124
|
+
readonly sourceRef: "refs/heads/main";
|
|
125
|
+
readonly evidenceSha256: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function validateRuntimeReleaseCandidateIdentity<
|
|
129
|
+
T extends RuntimeReleaseCandidateIdentity,
|
|
130
|
+
>(
|
|
131
|
+
value: T | undefined,
|
|
132
|
+
sourceCommit: unknown,
|
|
133
|
+
options?: { readonly required?: boolean },
|
|
134
|
+
): T | undefined;
|
|
135
|
+
|
|
82
136
|
export function canonicalLocalFileUrlToPath(
|
|
83
137
|
value: string,
|
|
84
138
|
platform?: LocalFileUrlPlatform,
|
|
@@ -10,6 +10,11 @@ export const MAX_RUNTIME_ARTIFACT_BYTES = 256 * 1024 * 1024;
|
|
|
10
10
|
export const MAX_RUNTIME_ATTESTATION_BYTES = 4 * 1024 * 1024;
|
|
11
11
|
export const OFFICIAL_RELEASE_REPOSITORY = "tetsuo-ai/agenc-releases";
|
|
12
12
|
export const OFFICIAL_SOURCE_REPOSITORY = "tetsuo-ai/agenc-core";
|
|
13
|
+
export const FROZEN_LEGACY_RUNTIME_VERSION = "0.7.2";
|
|
14
|
+
export const MINIMUM_PRIVATE_NODE_RUNTIME_VERSION = "0.11.2";
|
|
15
|
+
export const MINIMUM_DUAL_PROVENANCE_RUNTIME_VERSION = "0.13.0";
|
|
16
|
+
export const PRIVATE_NODE_BOOTSTRAP_RELEASE_TAG =
|
|
17
|
+
`agenc-v${MINIMUM_PRIVATE_NODE_RUNTIME_VERSION}`;
|
|
13
18
|
export const OFFICIAL_RELEASE_WORKFLOW =
|
|
14
19
|
"tetsuo-ai/agenc-core/.github/workflows/release-runtime.yml";
|
|
15
20
|
export const RUNTIME_ATTESTATION_POLICY = Object.freeze({
|
|
@@ -19,6 +24,10 @@ export const RUNTIME_ATTESTATION_POLICY = Object.freeze({
|
|
|
19
24
|
oidcIssuer: "https://token.actions.githubusercontent.com",
|
|
20
25
|
predicateType: "https://slsa.dev/provenance/v1",
|
|
21
26
|
});
|
|
27
|
+
export const RUNTIME_BUILD_PROVENANCE_POLICY = Object.freeze({
|
|
28
|
+
...RUNTIME_ATTESTATION_POLICY,
|
|
29
|
+
sourceRef: "refs/heads/main",
|
|
30
|
+
});
|
|
22
31
|
export const PINNED_GITHUB_CLI_VERSION = "2.96.0";
|
|
23
32
|
export const PINNED_GITHUB_CLI_ARTIFACTS = Object.freeze({
|
|
24
33
|
"linux-x64": Object.freeze({
|
|
@@ -27,6 +36,8 @@ export const PINNED_GITHUB_CLI_ARTIFACTS = Object.freeze({
|
|
|
27
36
|
sha256: "83d5c2ccad5498f58bf6368acb1ab32588cf43ab3a4b1c301bf36328b1c8bd60",
|
|
28
37
|
bytes: 14652560,
|
|
29
38
|
executable: "gh_2.96.0_linux_amd64/bin/gh",
|
|
39
|
+
executableSha256: "56b8bbbb27b066ecb33dbef9a256dc9d1314adaeff0908a752feba6c34053b40",
|
|
40
|
+
executableBytes: 40722594,
|
|
30
41
|
}),
|
|
31
42
|
"linux-arm64": Object.freeze({
|
|
32
43
|
file: "gh_2.96.0_linux_arm64.tar.gz",
|
|
@@ -34,6 +45,8 @@ export const PINNED_GITHUB_CLI_ARTIFACTS = Object.freeze({
|
|
|
34
45
|
sha256: "06f86ec7103d41993b76cd78072f43595c34aaa56506d971d9860e67140bf909",
|
|
35
46
|
bytes: 13321232,
|
|
36
47
|
executable: "gh_2.96.0_linux_arm64/bin/gh",
|
|
48
|
+
executableSha256: "f903d2fa04ae78ee8f8df0186364d0b92f078d2720053941a47e0a3beaef54f0",
|
|
49
|
+
executableBytes: 37879970,
|
|
37
50
|
}),
|
|
38
51
|
"darwin-x64": Object.freeze({
|
|
39
52
|
file: "gh_2.96.0_macOS_amd64.zip",
|
|
@@ -41,6 +54,8 @@ export const PINNED_GITHUB_CLI_ARTIFACTS = Object.freeze({
|
|
|
41
54
|
sha256: "4bd449df9ad639391bc62b8032546f0fe9edcd8526e06682a4f88abd8c5d163c",
|
|
42
55
|
bytes: 15298430,
|
|
43
56
|
executable: "gh_2.96.0_macOS_amd64/bin/gh",
|
|
57
|
+
executableSha256: "49380b19e758c14ce5509afe0ced58777fe45cfbcd5989941f7314d5f7e468c8",
|
|
58
|
+
executableBytes: 41773632,
|
|
44
59
|
}),
|
|
45
60
|
"darwin-arm64": Object.freeze({
|
|
46
61
|
file: "gh_2.96.0_macOS_arm64.zip",
|
|
@@ -48,6 +63,8 @@ export const PINNED_GITHUB_CLI_ARTIFACTS = Object.freeze({
|
|
|
48
63
|
sha256: "f23a0c37d963aacc3bed703ccbd59b41c5ca22101fab7f00eb2b7cad23aba463",
|
|
49
64
|
bytes: 13950131,
|
|
50
65
|
executable: "gh_2.96.0_macOS_arm64/bin/gh",
|
|
66
|
+
executableSha256: "b1d6c442fde99ca27c04e1e74d624895abe37785f4a3e9e9b684bf7586ce4bc8",
|
|
67
|
+
executableBytes: 38817216,
|
|
51
68
|
}),
|
|
52
69
|
"win-x64": Object.freeze({
|
|
53
70
|
file: "gh_2.96.0_windows_amd64.zip",
|
|
@@ -55,6 +72,8 @@ export const PINNED_GITHUB_CLI_ARTIFACTS = Object.freeze({
|
|
|
55
72
|
sha256: "c2d6acc935cd2f00e2144d7e036d5cd82e6b6bd5594e8c75aa75ef2a4ed6aac3",
|
|
56
73
|
bytes: 14821821,
|
|
57
74
|
executable: "gh_2.96.0_windows_amd64/bin/gh.exe",
|
|
75
|
+
executableSha256: "cd79f16203f1fbe56937c4c96e2b6eadd10549418dcb241d91576ac77af0ac8b",
|
|
76
|
+
executableBytes: 41504056,
|
|
58
77
|
}),
|
|
59
78
|
});
|
|
60
79
|
export const RUNTIME_MANIFEST_TRUST_MODES = Object.freeze([
|
|
@@ -65,17 +84,19 @@ export const RUNTIME_MANIFEST_TRUST_MODES = Object.freeze([
|
|
|
65
84
|
|
|
66
85
|
const TRUST_MODES = new Set(RUNTIME_MANIFEST_TRUST_MODES);
|
|
67
86
|
|
|
68
|
-
|
|
87
|
+
function canonicalRuntimeProvenanceVerificationArgs({
|
|
69
88
|
subjectPath,
|
|
70
89
|
bundlePath,
|
|
71
90
|
sourceCommit,
|
|
72
91
|
sourceRef,
|
|
92
|
+
sourceRefPattern,
|
|
93
|
+
sourceRefError,
|
|
73
94
|
}) {
|
|
74
95
|
if (!/^[0-9a-f]{40,64}$/.test(sourceCommit ?? "")) {
|
|
75
96
|
throw new Error("runtime attestation policy requires an exact source commit");
|
|
76
97
|
}
|
|
77
|
-
if (typeof sourceRef !== "string" || !
|
|
78
|
-
throw new Error(
|
|
98
|
+
if (typeof sourceRef !== "string" || !sourceRefPattern.test(sourceRef)) {
|
|
99
|
+
throw new Error(sourceRefError);
|
|
79
100
|
}
|
|
80
101
|
return [
|
|
81
102
|
"attestation",
|
|
@@ -103,6 +124,40 @@ export function canonicalRuntimeAttestationVerificationArgs({
|
|
|
103
124
|
];
|
|
104
125
|
}
|
|
105
126
|
|
|
127
|
+
export function canonicalRuntimeAttestationVerificationArgs({
|
|
128
|
+
subjectPath,
|
|
129
|
+
bundlePath,
|
|
130
|
+
sourceCommit,
|
|
131
|
+
sourceRef,
|
|
132
|
+
}) {
|
|
133
|
+
return canonicalRuntimeProvenanceVerificationArgs({
|
|
134
|
+
subjectPath,
|
|
135
|
+
bundlePath,
|
|
136
|
+
sourceCommit,
|
|
137
|
+
sourceRef,
|
|
138
|
+
sourceRefPattern: /^refs\/tags\/agenc-v[0-9]+\.[0-9]+\.[0-9]+$/,
|
|
139
|
+
sourceRefError:
|
|
140
|
+
"runtime attestation policy requires the canonical release source ref",
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function canonicalRuntimeBuildProvenanceVerificationArgs({
|
|
145
|
+
subjectPath,
|
|
146
|
+
bundlePath,
|
|
147
|
+
sourceCommit,
|
|
148
|
+
sourceRef = RUNTIME_BUILD_PROVENANCE_POLICY.sourceRef,
|
|
149
|
+
}) {
|
|
150
|
+
return canonicalRuntimeProvenanceVerificationArgs({
|
|
151
|
+
subjectPath,
|
|
152
|
+
bundlePath,
|
|
153
|
+
sourceCommit,
|
|
154
|
+
sourceRef,
|
|
155
|
+
sourceRefPattern: /^refs\/heads\/main$/,
|
|
156
|
+
sourceRefError:
|
|
157
|
+
"runtime build provenance policy requires the canonical main source ref",
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
106
161
|
export function requireRuntimeManifestTrustMode(trustMode) {
|
|
107
162
|
if (!TRUST_MODES.has(trustMode)) {
|
|
108
163
|
throw new Error(`agenc: unsupported runtime manifest trust mode ${String(trustMode)}`);
|
|
@@ -115,6 +170,100 @@ export function canonicalRuntimeArtifactName(manifest, artifact) {
|
|
|
115
170
|
`-node${artifact.nodeMajor}-abi${artifact.nodeModuleAbi}.tar.gz`;
|
|
116
171
|
}
|
|
117
172
|
|
|
173
|
+
export function canonicalRuntimeNodeBin(platform) {
|
|
174
|
+
if (platform === "win" || platform === "win32") {
|
|
175
|
+
return "node_modules/.agenc-node/node.exe";
|
|
176
|
+
}
|
|
177
|
+
if (platform === "linux" || platform === "darwin") {
|
|
178
|
+
return "node_modules/.agenc-node/bin/node";
|
|
179
|
+
}
|
|
180
|
+
throw new Error(`agenc: unsupported runtime Node platform ${String(platform)}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function canonicalRuntimeNodeLibrary(platform) {
|
|
184
|
+
if (platform === "linux") {
|
|
185
|
+
return "node_modules/.agenc-node/lib";
|
|
186
|
+
}
|
|
187
|
+
if (platform === "darwin" || platform === "win" || platform === "win32") {
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
throw new Error(`agenc: unsupported runtime Node platform ${String(platform)}`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function requireSupportedRuntimeVersion(version) {
|
|
194
|
+
if (version === FROZEN_LEGACY_RUNTIME_VERSION) {
|
|
195
|
+
return version;
|
|
196
|
+
}
|
|
197
|
+
const match = /^(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?$/.exec(version ?? "");
|
|
198
|
+
if (match === null) {
|
|
199
|
+
throw new Error("agenc: runtime version is not a canonical semantic version");
|
|
200
|
+
}
|
|
201
|
+
const actual = match.slice(1, 4).map(Number);
|
|
202
|
+
const minimum = MINIMUM_PRIVATE_NODE_RUNTIME_VERSION.split(".").map(Number);
|
|
203
|
+
for (let index = 0; index < minimum.length; index += 1) {
|
|
204
|
+
if (actual[index] > minimum[index]) return version;
|
|
205
|
+
if (actual[index] < minimum[index]) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
`agenc: runtime ${version} has no supported standalone activation contract; ` +
|
|
208
|
+
`use the frozen ${FROZEN_LEGACY_RUNTIME_VERSION} bridge with host Node 25.9, ` +
|
|
209
|
+
`or ${MINIMUM_PRIVATE_NODE_RUNTIME_VERSION} and newer with private Node`,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return version;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function runtimeVersionRequiresDualProvenance(version) {
|
|
217
|
+
requireSupportedRuntimeVersion(version);
|
|
218
|
+
if (version === FROZEN_LEGACY_RUNTIME_VERSION) return false;
|
|
219
|
+
const actual = version.split("-", 1)[0].split(".").map(Number);
|
|
220
|
+
const minimum = MINIMUM_DUAL_PROVENANCE_RUNTIME_VERSION.split(".").map(Number);
|
|
221
|
+
for (let index = 0; index < minimum.length; index += 1) {
|
|
222
|
+
if (actual[index] > minimum[index]) return true;
|
|
223
|
+
if (actual[index] < minimum[index]) return false;
|
|
224
|
+
}
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const RUNTIME_RELEASE_CANDIDATE_KEYS = Object.freeze([
|
|
229
|
+
"workflow",
|
|
230
|
+
"runId",
|
|
231
|
+
"runAttempt",
|
|
232
|
+
"runUrl",
|
|
233
|
+
"phase",
|
|
234
|
+
"sourceRef",
|
|
235
|
+
"evidenceSha256",
|
|
236
|
+
]);
|
|
237
|
+
|
|
238
|
+
export function validateRuntimeReleaseCandidateIdentity(
|
|
239
|
+
value,
|
|
240
|
+
sourceCommit,
|
|
241
|
+
{ required = false } = {},
|
|
242
|
+
) {
|
|
243
|
+
if (value === undefined && !required) return undefined;
|
|
244
|
+
if (
|
|
245
|
+
value === null ||
|
|
246
|
+
typeof value !== "object" ||
|
|
247
|
+
Array.isArray(value) ||
|
|
248
|
+
Object.keys(value).sort().join("\0") !==
|
|
249
|
+
[...RUNTIME_RELEASE_CANDIDATE_KEYS].sort().join("\0") ||
|
|
250
|
+
value.workflow !== "release-runtime.yml" ||
|
|
251
|
+
!Number.isSafeInteger(value.runId) ||
|
|
252
|
+
value.runId <= 0 ||
|
|
253
|
+
!Number.isSafeInteger(value.runAttempt) ||
|
|
254
|
+
value.runAttempt <= 0 ||
|
|
255
|
+
value.runUrl !==
|
|
256
|
+
`https://github.com/tetsuo-ai/agenc-core/actions/runs/${value.runId}` ||
|
|
257
|
+
value.phase !== "candidate" ||
|
|
258
|
+
value.sourceRef !== RUNTIME_BUILD_PROVENANCE_POLICY.sourceRef ||
|
|
259
|
+
!/^[0-9a-f]{64}$/.test(value.evidenceSha256 ?? "") ||
|
|
260
|
+
!/^[0-9a-f]{40}$/.test(sourceCommit ?? "")
|
|
261
|
+
) {
|
|
262
|
+
throw new Error("agenc: runtime release candidate identity is invalid");
|
|
263
|
+
}
|
|
264
|
+
return value;
|
|
265
|
+
}
|
|
266
|
+
|
|
118
267
|
function localFileUrlUsesWindowsPaths(platform) {
|
|
119
268
|
if (platform === "win" || platform === "win32") return true;
|
|
120
269
|
if (
|
|
@@ -234,7 +383,10 @@ function validateArtifactUrl(manifest, artifact, trustMode) {
|
|
|
234
383
|
if (
|
|
235
384
|
artifact.attestationUrl !== undefined ||
|
|
236
385
|
artifact.attestationSha256 !== undefined ||
|
|
237
|
-
artifact.attestationBytes !== undefined
|
|
386
|
+
artifact.attestationBytes !== undefined ||
|
|
387
|
+
artifact.buildProvenanceUrl !== undefined ||
|
|
388
|
+
artifact.buildProvenanceSha256 !== undefined ||
|
|
389
|
+
artifact.buildProvenanceBytes !== undefined
|
|
238
390
|
) {
|
|
239
391
|
throw new Error("agenc: explicit local runtime artifacts must not declare remote attestations");
|
|
240
392
|
}
|
|
@@ -268,6 +420,31 @@ function validateArtifactUrl(manifest, artifact, trustMode) {
|
|
|
268
420
|
throw new Error("agenc: runtime artifact attestation size is invalid");
|
|
269
421
|
}
|
|
270
422
|
}
|
|
423
|
+
const hasBuildProvenance =
|
|
424
|
+
artifact.buildProvenanceUrl !== undefined ||
|
|
425
|
+
artifact.buildProvenanceSha256 !== undefined ||
|
|
426
|
+
artifact.buildProvenanceBytes !== undefined;
|
|
427
|
+
if (
|
|
428
|
+
(
|
|
429
|
+
trustMode === "official" &&
|
|
430
|
+
runtimeVersionRequiresDualProvenance(manifest.runtimeVersion)
|
|
431
|
+
) ||
|
|
432
|
+
hasBuildProvenance
|
|
433
|
+
) {
|
|
434
|
+
if (artifact.buildProvenanceUrl !== `${artifact.url}.build.sigstore.json`) {
|
|
435
|
+
throw new Error("agenc: runtime artifact build provenance URL is not canonical");
|
|
436
|
+
}
|
|
437
|
+
if (!/^[0-9a-f]{64}$/.test(artifact.buildProvenanceSha256 ?? "")) {
|
|
438
|
+
throw new Error("agenc: runtime artifact build provenance digest is invalid");
|
|
439
|
+
}
|
|
440
|
+
if (
|
|
441
|
+
!Number.isSafeInteger(artifact.buildProvenanceBytes) ||
|
|
442
|
+
artifact.buildProvenanceBytes <= 0 ||
|
|
443
|
+
artifact.buildProvenanceBytes > MAX_RUNTIME_ATTESTATION_BYTES
|
|
444
|
+
) {
|
|
445
|
+
throw new Error("agenc: runtime artifact build provenance size is invalid");
|
|
446
|
+
}
|
|
447
|
+
}
|
|
271
448
|
}
|
|
272
449
|
|
|
273
450
|
export function validateRuntimeReleaseManifest(
|
|
@@ -289,6 +466,7 @@ export function validateRuntimeReleaseManifest(
|
|
|
289
466
|
if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(manifest.runtimeVersion ?? "")) {
|
|
290
467
|
throw new Error("agenc: runtime manifest has an invalid runtimeVersion");
|
|
291
468
|
}
|
|
469
|
+
requireSupportedRuntimeVersion(manifest.runtimeVersion);
|
|
292
470
|
if (manifest.releaseTag !== `agenc-v${manifest.runtimeVersion}`) {
|
|
293
471
|
throw new Error("agenc: runtime manifest releaseTag does not match runtimeVersion");
|
|
294
472
|
}
|
|
@@ -345,7 +523,14 @@ export function validateRuntimeReleaseManifest(
|
|
|
345
523
|
!Number.isSafeInteger(artifact.bytes) ||
|
|
346
524
|
artifact.bytes <= 0 ||
|
|
347
525
|
artifact.bytes > MAX_RUNTIME_ARTIFACT_BYTES ||
|
|
348
|
-
artifact.bins?.agenc !== "node_modules/@tetsuo-ai/runtime/bin/agenc"
|
|
526
|
+
artifact.bins?.agenc !== "node_modules/@tetsuo-ai/runtime/bin/agenc" ||
|
|
527
|
+
(
|
|
528
|
+
manifest.runtimeVersion !== FROZEN_LEGACY_RUNTIME_VERSION &&
|
|
529
|
+
(
|
|
530
|
+
artifact.bins?.node !== canonicalRuntimeNodeBin(artifact.platform) ||
|
|
531
|
+
artifact.bins?.nodeLibrary !== canonicalRuntimeNodeLibrary(artifact.platform)
|
|
532
|
+
)
|
|
533
|
+
)
|
|
349
534
|
) {
|
|
350
535
|
throw new Error(`agenc: runtime manifest artifact identity is invalid (${identity})`);
|
|
351
536
|
}
|
|
@@ -372,6 +557,15 @@ export function validateRuntimeReleaseManifest(
|
|
|
372
557
|
) {
|
|
373
558
|
throw new Error("agenc: runtime manifest build provenance is invalid");
|
|
374
559
|
}
|
|
560
|
+
validateRuntimeReleaseCandidateIdentity(
|
|
561
|
+
build.releaseCandidate,
|
|
562
|
+
build.sourceCommit,
|
|
563
|
+
{
|
|
564
|
+
required:
|
|
565
|
+
trustMode === "official" &&
|
|
566
|
+
runtimeVersionRequiresDualProvenance(manifest.runtimeVersion),
|
|
567
|
+
},
|
|
568
|
+
);
|
|
375
569
|
for (const artifact of manifest.artifacts) {
|
|
376
570
|
if (
|
|
377
571
|
artifact.nodeMajor !== build.nodeMajor ||
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tetsuo-ai/agenc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Public AgenC CLI launcher package",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"lib/activation-lock-identity.mjs",
|
|
24
24
|
"lib/generated-wrapper.d.mts",
|
|
25
25
|
"lib/generated-wrapper.mjs",
|
|
26
|
+
"lib/runtime-archive.d.mts",
|
|
26
27
|
"lib/runtime-archive.mjs",
|
|
27
28
|
"lib/runtime-compatibility.mjs",
|
|
28
29
|
"lib/runtime-manager.mjs",
|
|
@@ -47,6 +48,6 @@
|
|
|
47
48
|
"@tetsuo-ai/runtime": "file:../../runtime"
|
|
48
49
|
},
|
|
49
50
|
"engines": {
|
|
50
|
-
"node": ">=
|
|
51
|
+
"node": ">=26.5.0 <27.0.0"
|
|
51
52
|
}
|
|
52
53
|
}
|
package/src/launcher.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
2
3
|
import { readFile } from "node:fs/promises";
|
|
3
4
|
import { createRequire } from "node:module";
|
|
4
5
|
import { homedir } from "node:os";
|
|
5
|
-
import { dirname, join, resolve } from "node:path";
|
|
6
|
+
import { delimiter, dirname, join, resolve } from "node:path";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
7
8
|
|
|
8
9
|
const DEFAULT_READY_TIMEOUT_MS = 2000;
|
|
@@ -74,11 +75,17 @@ export function isDaemonCommand(argv) {
|
|
|
74
75
|
export function resolveRuntimeBin(requireFn = requireFromLauncher) {
|
|
75
76
|
try {
|
|
76
77
|
const runtimeEntry = requireFn.resolve("@tetsuo-ai/runtime");
|
|
78
|
+
const runtimeRoot = realpathSync(resolve(dirname(runtimeEntry), ".."));
|
|
79
|
+
const repositoryRuntimeRoot = realpathSync(
|
|
80
|
+
resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "runtime"),
|
|
81
|
+
);
|
|
82
|
+
if (runtimeRoot !== repositoryRuntimeRoot) return null;
|
|
77
83
|
return resolve(dirname(runtimeEntry), "../bin/agenc");
|
|
78
84
|
} catch (error) {
|
|
79
85
|
if (
|
|
80
86
|
error?.code === "MODULE_NOT_FOUND" ||
|
|
81
|
-
error?.code === "ERR_MODULE_NOT_FOUND"
|
|
87
|
+
error?.code === "ERR_MODULE_NOT_FOUND" ||
|
|
88
|
+
error?.code === "ENOENT"
|
|
82
89
|
) {
|
|
83
90
|
return null;
|
|
84
91
|
}
|
|
@@ -86,8 +93,41 @@ export function resolveRuntimeBin(requireFn = requireFromLauncher) {
|
|
|
86
93
|
}
|
|
87
94
|
}
|
|
88
95
|
|
|
89
|
-
|
|
90
|
-
|
|
96
|
+
function hostRuntimeLaunch(runtimeBin) {
|
|
97
|
+
return { runtimeBin, nodeBin: process.execPath };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function requireRuntimeLaunch(value) {
|
|
101
|
+
if (
|
|
102
|
+
value === null ||
|
|
103
|
+
typeof value !== "object" ||
|
|
104
|
+
typeof value.runtimeBin !== "string" ||
|
|
105
|
+
typeof value.nodeBin !== "string" ||
|
|
106
|
+
(
|
|
107
|
+
value.nodeLibraryPath !== undefined &&
|
|
108
|
+
typeof value.nodeLibraryPath !== "string"
|
|
109
|
+
)
|
|
110
|
+
) {
|
|
111
|
+
throw new Error("agenc: runtime manager returned an invalid launch contract");
|
|
112
|
+
}
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Resolve every path needed to execute the runtime. Development file: links
|
|
117
|
+
// intentionally use the current Node process; published artifacts always
|
|
118
|
+
// return their embedded Node (and the Linux compatibility-library directory).
|
|
119
|
+
export async function resolveRuntimeLaunchAsync({
|
|
120
|
+
requireFn = requireFromLauncher,
|
|
121
|
+
ensureFn,
|
|
122
|
+
} = {}) {
|
|
123
|
+
const dev = resolveRuntimeBin(requireFn);
|
|
124
|
+
if (dev !== null) return hostRuntimeLaunch(dev);
|
|
125
|
+
const ensureRuntimeLaunch =
|
|
126
|
+
ensureFn ?? (await import("../lib/runtime-manager.mjs")).ensureRuntimeLaunch;
|
|
127
|
+
return requireRuntimeLaunch(await ensureRuntimeLaunch());
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Retained for callers that only need the script path.
|
|
91
131
|
export async function resolveRuntimeBinAsync({
|
|
92
132
|
requireFn = requireFromLauncher,
|
|
93
133
|
ensureFn,
|
|
@@ -96,7 +136,10 @@ export async function resolveRuntimeBinAsync({
|
|
|
96
136
|
if (dev !== null) return dev;
|
|
97
137
|
const ensureRuntime =
|
|
98
138
|
ensureFn ?? (await import("../lib/runtime-manager.mjs")).ensureRuntime;
|
|
99
|
-
|
|
139
|
+
const ensured = await ensureRuntime();
|
|
140
|
+
return typeof ensured === "string"
|
|
141
|
+
? ensured
|
|
142
|
+
: requireRuntimeLaunch(ensured).runtimeBin;
|
|
100
143
|
}
|
|
101
144
|
|
|
102
145
|
export async function spawnNodeScript(
|
|
@@ -107,12 +150,27 @@ export async function spawnNodeScript(
|
|
|
107
150
|
cwd = process.cwd(),
|
|
108
151
|
stdio = "inherit",
|
|
109
152
|
spawnFn = spawn,
|
|
153
|
+
nodeBin = process.execPath,
|
|
154
|
+
nodeLibraryPath,
|
|
110
155
|
} = {},
|
|
111
156
|
) {
|
|
157
|
+
const childEnv = { ...env };
|
|
158
|
+
const pathKey =
|
|
159
|
+
Object.keys(childEnv).find((key) => key.toLowerCase() === "path") ?? "PATH";
|
|
160
|
+
const nodeDir = dirname(nodeBin);
|
|
161
|
+
childEnv[pathKey] = childEnv[pathKey]
|
|
162
|
+
? `${nodeDir}${delimiter}${childEnv[pathKey]}`
|
|
163
|
+
: nodeDir;
|
|
164
|
+
if (nodeLibraryPath !== undefined) {
|
|
165
|
+
// The embedded Linux Node requires the artifact's pinned libatomic. Do not
|
|
166
|
+
// search an operator-controlled ambient library path before that exact
|
|
167
|
+
// directory.
|
|
168
|
+
childEnv.LD_LIBRARY_PATH = nodeLibraryPath;
|
|
169
|
+
}
|
|
112
170
|
return new Promise((resolveExit, reject) => {
|
|
113
|
-
const child = spawnFn(
|
|
171
|
+
const child = spawnFn(nodeBin, [scriptPath, ...args], {
|
|
114
172
|
cwd,
|
|
115
|
-
env,
|
|
173
|
+
env: childEnv,
|
|
116
174
|
stdio,
|
|
117
175
|
});
|
|
118
176
|
child.once("error", reject);
|
|
@@ -174,6 +232,8 @@ export async function ensureDaemonForLaunch({
|
|
|
174
232
|
env = process.env,
|
|
175
233
|
cwd = process.cwd(),
|
|
176
234
|
runtimeBin = resolveRuntimeBin(),
|
|
235
|
+
runtimeNodeBin = process.execPath,
|
|
236
|
+
runtimeNodeLibraryPath,
|
|
177
237
|
userHome = homedir(),
|
|
178
238
|
readText = readFile,
|
|
179
239
|
signalPid = process.kill,
|
|
@@ -197,7 +257,12 @@ export async function ensureDaemonForLaunch({
|
|
|
197
257
|
return { status: "already-running" };
|
|
198
258
|
}
|
|
199
259
|
|
|
200
|
-
await spawnDaemonFn(runtimeBin, {
|
|
260
|
+
await spawnDaemonFn(runtimeBin, {
|
|
261
|
+
env,
|
|
262
|
+
cwd,
|
|
263
|
+
nodeBin: runtimeNodeBin,
|
|
264
|
+
nodeLibraryPath: runtimeNodeLibraryPath,
|
|
265
|
+
});
|
|
201
266
|
const ready = await waitForReadyFn({ env, userHome, readText, signalPid });
|
|
202
267
|
if (!ready) {
|
|
203
268
|
throw new Error("AgenC daemon did not become ready before timeout");
|
|
@@ -210,16 +275,29 @@ export async function main(
|
|
|
210
275
|
{
|
|
211
276
|
env = process.env,
|
|
212
277
|
cwd = process.cwd(),
|
|
278
|
+
runtimeLaunch,
|
|
213
279
|
runtimeBin,
|
|
280
|
+
runtimeNodeBin = process.execPath,
|
|
281
|
+
runtimeNodeLibraryPath,
|
|
214
282
|
userHome = homedir(),
|
|
215
283
|
} = {},
|
|
216
284
|
) {
|
|
217
285
|
// Resolve (and, in a published install, download + verify) the runtime before
|
|
218
286
|
// anything tries to spawn it. Sync default is avoided: it returns null when
|
|
219
287
|
// runtime isn't an npm dep, which is the normal published case.
|
|
220
|
-
let
|
|
288
|
+
let resolvedLaunch = runtimeLaunch ??
|
|
289
|
+
(runtimeBin === undefined
|
|
290
|
+
? undefined
|
|
291
|
+
: {
|
|
292
|
+
runtimeBin,
|
|
293
|
+
nodeBin: runtimeNodeBin,
|
|
294
|
+
...(runtimeNodeLibraryPath === undefined
|
|
295
|
+
? {}
|
|
296
|
+
: { nodeLibraryPath: runtimeNodeLibraryPath }),
|
|
297
|
+
});
|
|
221
298
|
try {
|
|
222
|
-
|
|
299
|
+
resolvedLaunch ??= await resolveRuntimeLaunchAsync();
|
|
300
|
+
resolvedLaunch = requireRuntimeLaunch(resolvedLaunch);
|
|
223
301
|
} catch (error) {
|
|
224
302
|
process.stderr.write(
|
|
225
303
|
`agenc: could not obtain runtime: ${
|
|
@@ -229,7 +307,15 @@ export async function main(
|
|
|
229
307
|
return 1;
|
|
230
308
|
}
|
|
231
309
|
try {
|
|
232
|
-
await ensureDaemonForLaunch({
|
|
310
|
+
await ensureDaemonForLaunch({
|
|
311
|
+
argv,
|
|
312
|
+
env,
|
|
313
|
+
cwd,
|
|
314
|
+
runtimeBin: resolvedLaunch.runtimeBin,
|
|
315
|
+
runtimeNodeBin: resolvedLaunch.nodeBin,
|
|
316
|
+
runtimeNodeLibraryPath: resolvedLaunch.nodeLibraryPath,
|
|
317
|
+
userHome,
|
|
318
|
+
});
|
|
233
319
|
} catch (error) {
|
|
234
320
|
// The launcher is transport, not policy: a daemon that cannot start must
|
|
235
321
|
// not block daemon-independent commands. `agenc update` is the canonical
|
|
@@ -245,7 +331,13 @@ export async function main(
|
|
|
245
331
|
"agenc: continuing without the daemon; commands that require it will report the failure\n",
|
|
246
332
|
);
|
|
247
333
|
}
|
|
248
|
-
return spawnNodeScript(
|
|
334
|
+
return spawnNodeScript(resolvedLaunch.runtimeBin, argv, {
|
|
335
|
+
env,
|
|
336
|
+
cwd,
|
|
337
|
+
stdio: "inherit",
|
|
338
|
+
nodeBin: resolvedLaunch.nodeBin,
|
|
339
|
+
nodeLibraryPath: resolvedLaunch.nodeLibraryPath,
|
|
340
|
+
});
|
|
249
341
|
}
|
|
250
342
|
|
|
251
343
|
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|