@tetsuo-ai/agenc 0.6.0 → 0.7.2
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 +335 -0
- package/lib/activation-lock-identity.d.mts +3 -0
- package/lib/activation-lock-identity.mjs +151 -0
- package/lib/generated-wrapper.d.mts +23 -0
- package/lib/generated-wrapper.mjs +197 -0
- package/lib/runtime-archive.mjs +299 -0
- package/lib/runtime-compatibility.mjs +141 -0
- package/lib/runtime-manager.mjs +1161 -59
- package/lib/runtime-release-contract.d.mts +94 -0
- package/lib/runtime-release-contract.mjs +386 -0
- package/lib/sqlite-lock.d.mts +44 -0
- package/lib/sqlite-lock.mjs +1121 -0
- package/package.json +27 -7
- package/generated/.gitkeep +0 -0
- package/generated/agenc-runtime-manifest.json +0 -63
- package/scripts/build-runtime-tarball.mjs +0 -201
- package/scripts/gen-manifest.mjs +0 -91
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export type RuntimeManifestTrustMode = "official" | "explicitHttps" | "explicitLocal";
|
|
2
|
+
|
|
3
|
+
export interface RuntimeReleaseArtifact {
|
|
4
|
+
readonly platform: string;
|
|
5
|
+
readonly arch: string;
|
|
6
|
+
readonly runtimeVersion: string;
|
|
7
|
+
readonly nodeMajor: number;
|
|
8
|
+
readonly nodeModuleAbi: string;
|
|
9
|
+
readonly nodeApiVersion: string;
|
|
10
|
+
readonly url: string;
|
|
11
|
+
readonly sha256: string;
|
|
12
|
+
readonly bytes: number;
|
|
13
|
+
readonly attestationUrl?: string;
|
|
14
|
+
readonly attestationSha256?: string;
|
|
15
|
+
readonly attestationBytes?: number;
|
|
16
|
+
readonly bins?: { readonly agenc?: string };
|
|
17
|
+
readonly [key: string]: unknown;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface RuntimeReleaseManifest {
|
|
21
|
+
readonly manifestVersion: 2;
|
|
22
|
+
readonly runtimeVersion: string;
|
|
23
|
+
readonly releaseRepository: string;
|
|
24
|
+
readonly releaseTag: string;
|
|
25
|
+
readonly build?: Readonly<Record<string, unknown>>;
|
|
26
|
+
readonly artifacts: readonly RuntimeReleaseArtifact[];
|
|
27
|
+
readonly [key: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const MAX_RUNTIME_MANIFEST_BYTES: number;
|
|
31
|
+
export const MAX_RUNTIME_ARTIFACT_BYTES: number;
|
|
32
|
+
export const MAX_RUNTIME_ATTESTATION_BYTES: number;
|
|
33
|
+
export const OFFICIAL_RELEASE_REPOSITORY: string;
|
|
34
|
+
export const OFFICIAL_SOURCE_REPOSITORY: string;
|
|
35
|
+
export const OFFICIAL_RELEASE_WORKFLOW: string;
|
|
36
|
+
export const RUNTIME_ATTESTATION_POLICY: Readonly<{
|
|
37
|
+
repository: string;
|
|
38
|
+
signerWorkflow: string;
|
|
39
|
+
hostname: string;
|
|
40
|
+
oidcIssuer: string;
|
|
41
|
+
predicateType: string;
|
|
42
|
+
}>;
|
|
43
|
+
export const PINNED_GITHUB_CLI_VERSION: string;
|
|
44
|
+
export const PINNED_GITHUB_CLI_ARTIFACTS: Readonly<Record<string, Readonly<{
|
|
45
|
+
file: string;
|
|
46
|
+
url: string;
|
|
47
|
+
sha256: string;
|
|
48
|
+
bytes: number;
|
|
49
|
+
executable: string;
|
|
50
|
+
}>>>;
|
|
51
|
+
export const RUNTIME_MANIFEST_TRUST_MODES: readonly RuntimeManifestTrustMode[];
|
|
52
|
+
|
|
53
|
+
export function canonicalRuntimeAttestationVerificationArgs(options: {
|
|
54
|
+
readonly subjectPath: string;
|
|
55
|
+
readonly bundlePath: string;
|
|
56
|
+
readonly sourceCommit: string;
|
|
57
|
+
readonly sourceRef: string;
|
|
58
|
+
}): readonly string[];
|
|
59
|
+
|
|
60
|
+
export type LocalFileUrlPlatform =
|
|
61
|
+
| "win"
|
|
62
|
+
| "win32"
|
|
63
|
+
| "linux"
|
|
64
|
+
| "darwin"
|
|
65
|
+
| "freebsd"
|
|
66
|
+
| "openbsd"
|
|
67
|
+
| "sunos"
|
|
68
|
+
| "aix";
|
|
69
|
+
|
|
70
|
+
export function requireRuntimeManifestTrustMode(
|
|
71
|
+
trustMode: unknown,
|
|
72
|
+
): RuntimeManifestTrustMode;
|
|
73
|
+
|
|
74
|
+
export function canonicalRuntimeArtifactName(
|
|
75
|
+
manifest: Pick<RuntimeReleaseManifest, "runtimeVersion">,
|
|
76
|
+
artifact: Pick<
|
|
77
|
+
RuntimeReleaseArtifact,
|
|
78
|
+
"platform" | "arch" | "nodeMajor" | "nodeModuleAbi"
|
|
79
|
+
>,
|
|
80
|
+
): string;
|
|
81
|
+
|
|
82
|
+
export function canonicalLocalFileUrlToPath(
|
|
83
|
+
value: string,
|
|
84
|
+
platform?: LocalFileUrlPlatform,
|
|
85
|
+
label?: string,
|
|
86
|
+
): string;
|
|
87
|
+
|
|
88
|
+
export function validateRuntimeReleaseManifest<T extends RuntimeReleaseManifest>(
|
|
89
|
+
manifest: T,
|
|
90
|
+
options?: {
|
|
91
|
+
readonly trustMode?: RuntimeManifestTrustMode;
|
|
92
|
+
readonly expectedRuntimeVersion?: string;
|
|
93
|
+
},
|
|
94
|
+
): T;
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
// Canonical trust contract for AgenC runtime release manifests and artifacts.
|
|
2
|
+
// Keep this module dependency-free and side-effect-free: the npm launcher,
|
|
3
|
+
// runtime updater, and standalone installer payloads all consume this policy.
|
|
4
|
+
|
|
5
|
+
import { posix, win32 } from "node:path";
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
|
+
|
|
8
|
+
export const MAX_RUNTIME_MANIFEST_BYTES = 1024 * 1024;
|
|
9
|
+
export const MAX_RUNTIME_ARTIFACT_BYTES = 256 * 1024 * 1024;
|
|
10
|
+
export const MAX_RUNTIME_ATTESTATION_BYTES = 4 * 1024 * 1024;
|
|
11
|
+
export const OFFICIAL_RELEASE_REPOSITORY = "tetsuo-ai/agenc-releases";
|
|
12
|
+
export const OFFICIAL_SOURCE_REPOSITORY = "tetsuo-ai/agenc-core";
|
|
13
|
+
export const OFFICIAL_RELEASE_WORKFLOW =
|
|
14
|
+
"tetsuo-ai/agenc-core/.github/workflows/release-runtime.yml";
|
|
15
|
+
export const RUNTIME_ATTESTATION_POLICY = Object.freeze({
|
|
16
|
+
repository: OFFICIAL_SOURCE_REPOSITORY,
|
|
17
|
+
signerWorkflow: OFFICIAL_RELEASE_WORKFLOW,
|
|
18
|
+
hostname: "github.com",
|
|
19
|
+
oidcIssuer: "https://token.actions.githubusercontent.com",
|
|
20
|
+
predicateType: "https://slsa.dev/provenance/v1",
|
|
21
|
+
});
|
|
22
|
+
export const PINNED_GITHUB_CLI_VERSION = "2.96.0";
|
|
23
|
+
export const PINNED_GITHUB_CLI_ARTIFACTS = Object.freeze({
|
|
24
|
+
"linux-x64": Object.freeze({
|
|
25
|
+
file: "gh_2.96.0_linux_amd64.tar.gz",
|
|
26
|
+
url: "https://github.com/cli/cli/releases/download/v2.96.0/gh_2.96.0_linux_amd64.tar.gz",
|
|
27
|
+
sha256: "83d5c2ccad5498f58bf6368acb1ab32588cf43ab3a4b1c301bf36328b1c8bd60",
|
|
28
|
+
bytes: 14652560,
|
|
29
|
+
executable: "gh_2.96.0_linux_amd64/bin/gh",
|
|
30
|
+
}),
|
|
31
|
+
"linux-arm64": Object.freeze({
|
|
32
|
+
file: "gh_2.96.0_linux_arm64.tar.gz",
|
|
33
|
+
url: "https://github.com/cli/cli/releases/download/v2.96.0/gh_2.96.0_linux_arm64.tar.gz",
|
|
34
|
+
sha256: "06f86ec7103d41993b76cd78072f43595c34aaa56506d971d9860e67140bf909",
|
|
35
|
+
bytes: 13321232,
|
|
36
|
+
executable: "gh_2.96.0_linux_arm64/bin/gh",
|
|
37
|
+
}),
|
|
38
|
+
"darwin-x64": Object.freeze({
|
|
39
|
+
file: "gh_2.96.0_macOS_amd64.zip",
|
|
40
|
+
url: "https://github.com/cli/cli/releases/download/v2.96.0/gh_2.96.0_macOS_amd64.zip",
|
|
41
|
+
sha256: "4bd449df9ad639391bc62b8032546f0fe9edcd8526e06682a4f88abd8c5d163c",
|
|
42
|
+
bytes: 15298430,
|
|
43
|
+
executable: "gh_2.96.0_macOS_amd64/bin/gh",
|
|
44
|
+
}),
|
|
45
|
+
"darwin-arm64": Object.freeze({
|
|
46
|
+
file: "gh_2.96.0_macOS_arm64.zip",
|
|
47
|
+
url: "https://github.com/cli/cli/releases/download/v2.96.0/gh_2.96.0_macOS_arm64.zip",
|
|
48
|
+
sha256: "f23a0c37d963aacc3bed703ccbd59b41c5ca22101fab7f00eb2b7cad23aba463",
|
|
49
|
+
bytes: 13950131,
|
|
50
|
+
executable: "gh_2.96.0_macOS_arm64/bin/gh",
|
|
51
|
+
}),
|
|
52
|
+
"win-x64": Object.freeze({
|
|
53
|
+
file: "gh_2.96.0_windows_amd64.zip",
|
|
54
|
+
url: "https://github.com/cli/cli/releases/download/v2.96.0/gh_2.96.0_windows_amd64.zip",
|
|
55
|
+
sha256: "c2d6acc935cd2f00e2144d7e036d5cd82e6b6bd5594e8c75aa75ef2a4ed6aac3",
|
|
56
|
+
bytes: 14821821,
|
|
57
|
+
executable: "gh_2.96.0_windows_amd64/bin/gh.exe",
|
|
58
|
+
}),
|
|
59
|
+
});
|
|
60
|
+
export const RUNTIME_MANIFEST_TRUST_MODES = Object.freeze([
|
|
61
|
+
"official",
|
|
62
|
+
"explicitHttps",
|
|
63
|
+
"explicitLocal",
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
const TRUST_MODES = new Set(RUNTIME_MANIFEST_TRUST_MODES);
|
|
67
|
+
|
|
68
|
+
export function canonicalRuntimeAttestationVerificationArgs({
|
|
69
|
+
subjectPath,
|
|
70
|
+
bundlePath,
|
|
71
|
+
sourceCommit,
|
|
72
|
+
sourceRef,
|
|
73
|
+
}) {
|
|
74
|
+
if (!/^[0-9a-f]{40,64}$/.test(sourceCommit ?? "")) {
|
|
75
|
+
throw new Error("runtime attestation policy requires an exact source commit");
|
|
76
|
+
}
|
|
77
|
+
if (typeof sourceRef !== "string" || !sourceRef.startsWith("refs/tags/agenc-v")) {
|
|
78
|
+
throw new Error("runtime attestation policy requires the canonical release source ref");
|
|
79
|
+
}
|
|
80
|
+
return [
|
|
81
|
+
"attestation",
|
|
82
|
+
"verify",
|
|
83
|
+
subjectPath,
|
|
84
|
+
"--repo",
|
|
85
|
+
RUNTIME_ATTESTATION_POLICY.repository,
|
|
86
|
+
"--bundle",
|
|
87
|
+
bundlePath,
|
|
88
|
+
"--signer-workflow",
|
|
89
|
+
RUNTIME_ATTESTATION_POLICY.signerWorkflow,
|
|
90
|
+
"--signer-digest",
|
|
91
|
+
sourceCommit,
|
|
92
|
+
"--source-digest",
|
|
93
|
+
sourceCommit,
|
|
94
|
+
"--source-ref",
|
|
95
|
+
sourceRef,
|
|
96
|
+
"--hostname",
|
|
97
|
+
RUNTIME_ATTESTATION_POLICY.hostname,
|
|
98
|
+
"--cert-oidc-issuer",
|
|
99
|
+
RUNTIME_ATTESTATION_POLICY.oidcIssuer,
|
|
100
|
+
"--predicate-type",
|
|
101
|
+
RUNTIME_ATTESTATION_POLICY.predicateType,
|
|
102
|
+
"--deny-self-hosted-runners",
|
|
103
|
+
];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function requireRuntimeManifestTrustMode(trustMode) {
|
|
107
|
+
if (!TRUST_MODES.has(trustMode)) {
|
|
108
|
+
throw new Error(`agenc: unsupported runtime manifest trust mode ${String(trustMode)}`);
|
|
109
|
+
}
|
|
110
|
+
return trustMode;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function canonicalRuntimeArtifactName(manifest, artifact) {
|
|
114
|
+
return `agenc-runtime-${manifest.runtimeVersion}-${artifact.platform}-${artifact.arch}` +
|
|
115
|
+
`-node${artifact.nodeMajor}-abi${artifact.nodeModuleAbi}.tar.gz`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function localFileUrlUsesWindowsPaths(platform) {
|
|
119
|
+
if (platform === "win" || platform === "win32") return true;
|
|
120
|
+
if (
|
|
121
|
+
platform === "linux" ||
|
|
122
|
+
platform === "darwin" ||
|
|
123
|
+
platform === "freebsd" ||
|
|
124
|
+
platform === "openbsd" ||
|
|
125
|
+
platform === "sunos" ||
|
|
126
|
+
platform === "aix"
|
|
127
|
+
) return false;
|
|
128
|
+
throw new Error(`agenc: unsupported local file URL platform ${String(platform)}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Convert a canonical, authority-free local file URL into an absolute path.
|
|
133
|
+
*
|
|
134
|
+
* The lexical checks run before host path conversion so a manifest cannot make
|
|
135
|
+
* `file://server/share`, `file://localhost/path`, a Windows device namespace,
|
|
136
|
+
* or a drive-relative path mean different things on different hosts. The
|
|
137
|
+
* platform parameter also makes the same contract testable on every runner.
|
|
138
|
+
*/
|
|
139
|
+
export function canonicalLocalFileUrlToPath(
|
|
140
|
+
value,
|
|
141
|
+
platform = process.platform,
|
|
142
|
+
label = "local runtime artifact URL",
|
|
143
|
+
) {
|
|
144
|
+
if (
|
|
145
|
+
typeof value !== "string" ||
|
|
146
|
+
value !== value.trim() ||
|
|
147
|
+
/[\0\r\n]/.test(value) ||
|
|
148
|
+
!value.startsWith("file:///")
|
|
149
|
+
) {
|
|
150
|
+
throw new Error(`agenc: ${label} must be an authority-free file URL`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let parsed;
|
|
154
|
+
try {
|
|
155
|
+
parsed = new URL(value);
|
|
156
|
+
} catch {
|
|
157
|
+
throw new Error(`agenc: ${label} is invalid`);
|
|
158
|
+
}
|
|
159
|
+
if (
|
|
160
|
+
parsed.protocol !== "file:" ||
|
|
161
|
+
parsed.username !== "" ||
|
|
162
|
+
parsed.password !== "" ||
|
|
163
|
+
parsed.host !== ""
|
|
164
|
+
) {
|
|
165
|
+
throw new Error(`agenc: ${label} must not contain an authority`);
|
|
166
|
+
}
|
|
167
|
+
if (parsed.search !== "" || parsed.hash !== "") {
|
|
168
|
+
throw new Error(`agenc: ${label} must not contain a query or fragment`);
|
|
169
|
+
}
|
|
170
|
+
// URL parsing intentionally normalizes localhost authorities, dot segments,
|
|
171
|
+
// legacy drive separators, and scheme casing. Requiring the original bytes
|
|
172
|
+
// to equal the normalized serialization rejects all of those aliases.
|
|
173
|
+
if (parsed.href !== value) {
|
|
174
|
+
throw new Error(`agenc: ${label} is not canonical`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
let decodedPathname;
|
|
178
|
+
try {
|
|
179
|
+
decodedPathname = decodeURIComponent(parsed.pathname);
|
|
180
|
+
} catch {
|
|
181
|
+
throw new Error(`agenc: ${label} has invalid path encoding`);
|
|
182
|
+
}
|
|
183
|
+
if (decodedPathname.includes("\0")) {
|
|
184
|
+
throw new Error(`agenc: ${label} contains a NUL byte`);
|
|
185
|
+
}
|
|
186
|
+
if (decodedPathname.startsWith("//")) {
|
|
187
|
+
throw new Error(`agenc: ${label} must not use a UNC path`);
|
|
188
|
+
}
|
|
189
|
+
// Reject device namespace spellings even while validating a manifest for a
|
|
190
|
+
// different target OS. Encoded backslashes must not turn a benign-looking
|
|
191
|
+
// URL into \\?\, \\.\, or \??\ after decoding.
|
|
192
|
+
const namespaceProbe = decodedPathname.slice(1).replaceAll("/", "\\");
|
|
193
|
+
if (/^(?:\\\\[?.]\\|\\\?\?\\)/.test(namespaceProbe)) {
|
|
194
|
+
throw new Error(`agenc: ${label} must not use a device namespace`);
|
|
195
|
+
}
|
|
196
|
+
if (/^\/[A-Za-z]:(?:$|[^/])/.test(decodedPathname)) {
|
|
197
|
+
throw new Error(`agenc: ${label} must not use a drive-relative path`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const windows = localFileUrlUsesWindowsPaths(platform);
|
|
201
|
+
let path;
|
|
202
|
+
try {
|
|
203
|
+
path = fileURLToPath(parsed, { windows });
|
|
204
|
+
} catch {
|
|
205
|
+
throw new Error(`agenc: ${label} is invalid for its platform`);
|
|
206
|
+
}
|
|
207
|
+
if (windows) {
|
|
208
|
+
if (!win32.isAbsolute(path) || !/^[A-Za-z]:\\/.test(path) || path.startsWith("\\\\")) {
|
|
209
|
+
throw new Error(`agenc: ${label} must contain an absolute drive path`);
|
|
210
|
+
}
|
|
211
|
+
if (path.slice(2).includes(":")) {
|
|
212
|
+
throw new Error(`agenc: ${label} must not use an alternate data stream`);
|
|
213
|
+
}
|
|
214
|
+
} else if (!posix.isAbsolute(path) || path.startsWith("//")) {
|
|
215
|
+
throw new Error(`agenc: ${label} must contain an absolute POSIX path`);
|
|
216
|
+
}
|
|
217
|
+
if (pathToFileURL(path, { windows }).href !== value) {
|
|
218
|
+
throw new Error(`agenc: ${label} does not round-trip canonically`);
|
|
219
|
+
}
|
|
220
|
+
return path;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function validateArtifactUrl(manifest, artifact, trustMode) {
|
|
224
|
+
let parsed;
|
|
225
|
+
try {
|
|
226
|
+
parsed = new URL(artifact.url);
|
|
227
|
+
} catch {
|
|
228
|
+
throw new Error("agenc: runtime manifest artifact URL is invalid");
|
|
229
|
+
}
|
|
230
|
+
if (trustMode === "explicitLocal") {
|
|
231
|
+
// Local URLs name files on the machine consuming the manifest, not on the
|
|
232
|
+
// platform named by an unselected artifact entry.
|
|
233
|
+
canonicalLocalFileUrlToPath(artifact.url);
|
|
234
|
+
if (
|
|
235
|
+
artifact.attestationUrl !== undefined ||
|
|
236
|
+
artifact.attestationSha256 !== undefined ||
|
|
237
|
+
artifact.attestationBytes !== undefined
|
|
238
|
+
) {
|
|
239
|
+
throw new Error("agenc: explicit local runtime artifacts must not declare remote attestations");
|
|
240
|
+
}
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (parsed.protocol !== "https:") {
|
|
244
|
+
throw new Error("agenc: remote runtime manifests may only use HTTPS artifact URLs");
|
|
245
|
+
}
|
|
246
|
+
const expected =
|
|
247
|
+
`https://github.com/${manifest.releaseRepository}/releases/download/` +
|
|
248
|
+
`${manifest.releaseTag}/${canonicalRuntimeArtifactName(manifest, artifact)}`;
|
|
249
|
+
if (artifact.url !== expected) {
|
|
250
|
+
throw new Error("agenc: runtime manifest artifact URL is not canonical");
|
|
251
|
+
}
|
|
252
|
+
const hasAttestation =
|
|
253
|
+
artifact.attestationUrl !== undefined ||
|
|
254
|
+
artifact.attestationSha256 !== undefined ||
|
|
255
|
+
artifact.attestationBytes !== undefined;
|
|
256
|
+
if (trustMode === "official" || hasAttestation) {
|
|
257
|
+
if (artifact.attestationUrl !== `${artifact.url}.sigstore.json`) {
|
|
258
|
+
throw new Error("agenc: runtime artifact attestation URL is not canonical");
|
|
259
|
+
}
|
|
260
|
+
if (!/^[0-9a-f]{64}$/.test(artifact.attestationSha256 ?? "")) {
|
|
261
|
+
throw new Error("agenc: runtime artifact attestation digest is invalid");
|
|
262
|
+
}
|
|
263
|
+
if (
|
|
264
|
+
!Number.isSafeInteger(artifact.attestationBytes) ||
|
|
265
|
+
artifact.attestationBytes <= 0 ||
|
|
266
|
+
artifact.attestationBytes > MAX_RUNTIME_ATTESTATION_BYTES
|
|
267
|
+
) {
|
|
268
|
+
throw new Error("agenc: runtime artifact attestation size is invalid");
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function validateRuntimeReleaseManifest(
|
|
274
|
+
manifest,
|
|
275
|
+
{
|
|
276
|
+
trustMode = "official",
|
|
277
|
+
expectedRuntimeVersion,
|
|
278
|
+
} = {},
|
|
279
|
+
) {
|
|
280
|
+
requireRuntimeManifestTrustMode(trustMode);
|
|
281
|
+
if (manifest === null || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
282
|
+
throw new Error("agenc: runtime manifest root is invalid");
|
|
283
|
+
}
|
|
284
|
+
if (manifest.manifestVersion !== 2) {
|
|
285
|
+
throw new Error(
|
|
286
|
+
`agenc: unsupported runtime manifest version ${manifest.manifestVersion ?? "missing"}`,
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(manifest.runtimeVersion ?? "")) {
|
|
290
|
+
throw new Error("agenc: runtime manifest has an invalid runtimeVersion");
|
|
291
|
+
}
|
|
292
|
+
if (manifest.releaseTag !== `agenc-v${manifest.runtimeVersion}`) {
|
|
293
|
+
throw new Error("agenc: runtime manifest releaseTag does not match runtimeVersion");
|
|
294
|
+
}
|
|
295
|
+
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(manifest.releaseRepository ?? "")) {
|
|
296
|
+
throw new Error("agenc: runtime manifest releaseRepository is invalid");
|
|
297
|
+
}
|
|
298
|
+
if (
|
|
299
|
+
trustMode === "official" &&
|
|
300
|
+
manifest.releaseRepository !== OFFICIAL_RELEASE_REPOSITORY
|
|
301
|
+
) {
|
|
302
|
+
throw new Error("agenc: runtime manifest releaseRepository is not official");
|
|
303
|
+
}
|
|
304
|
+
if (
|
|
305
|
+
expectedRuntimeVersion !== undefined &&
|
|
306
|
+
manifest.runtimeVersion !== expectedRuntimeVersion
|
|
307
|
+
) {
|
|
308
|
+
if (trustMode === "official") {
|
|
309
|
+
throw new Error(
|
|
310
|
+
`agenc: official runtime manifest version ${manifest.runtimeVersion} ` +
|
|
311
|
+
`does not match launcher ${expectedRuntimeVersion}`,
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
throw new Error(
|
|
315
|
+
`agenc: runtime manifest version ${manifest.runtimeVersion} ` +
|
|
316
|
+
`does not match expected ${expectedRuntimeVersion}`,
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
if (!Array.isArray(manifest.artifacts) || manifest.artifacts.length === 0) {
|
|
320
|
+
throw new Error("agenc: runtime manifest has no artifacts");
|
|
321
|
+
}
|
|
322
|
+
if (manifest.artifacts.length > 128) {
|
|
323
|
+
throw new Error("agenc: runtime manifest has too many artifacts");
|
|
324
|
+
}
|
|
325
|
+
const identities = new Set();
|
|
326
|
+
for (const artifact of manifest.artifacts) {
|
|
327
|
+
if (artifact === null || typeof artifact !== "object" || Array.isArray(artifact)) {
|
|
328
|
+
throw new Error("agenc: runtime manifest artifact is invalid");
|
|
329
|
+
}
|
|
330
|
+
const identity = `${artifact.platform}-${artifact.arch}/abi${artifact.nodeModuleAbi ?? "?"}`;
|
|
331
|
+
if (!/^(linux-(x64|arm64)|darwin-(x64|arm64)|win-x64)\/abi[0-9]+$/.test(identity)) {
|
|
332
|
+
throw new Error(`agenc: runtime manifest artifact identity is invalid (${identity})`);
|
|
333
|
+
}
|
|
334
|
+
if (identities.has(identity)) {
|
|
335
|
+
throw new Error(`agenc: duplicate runtime manifest artifact ${identity}`);
|
|
336
|
+
}
|
|
337
|
+
identities.add(identity);
|
|
338
|
+
if (
|
|
339
|
+
artifact.runtimeVersion !== manifest.runtimeVersion ||
|
|
340
|
+
typeof artifact.url !== "string" ||
|
|
341
|
+
!Number.isSafeInteger(artifact.nodeMajor) ||
|
|
342
|
+
artifact.nodeMajor < 1 ||
|
|
343
|
+
!/^\d+$/.test(artifact.nodeApiVersion ?? "") ||
|
|
344
|
+
!/^[0-9a-f]{64}$/.test(artifact.sha256 ?? "") ||
|
|
345
|
+
!Number.isSafeInteger(artifact.bytes) ||
|
|
346
|
+
artifact.bytes <= 0 ||
|
|
347
|
+
artifact.bytes > MAX_RUNTIME_ARTIFACT_BYTES ||
|
|
348
|
+
artifact.bins?.agenc !== "node_modules/@tetsuo-ai/runtime/bin/agenc"
|
|
349
|
+
) {
|
|
350
|
+
throw new Error(`agenc: runtime manifest artifact identity is invalid (${identity})`);
|
|
351
|
+
}
|
|
352
|
+
validateArtifactUrl(manifest, artifact, trustMode);
|
|
353
|
+
}
|
|
354
|
+
if (trustMode !== "explicitLocal") {
|
|
355
|
+
const build = manifest.build;
|
|
356
|
+
if (
|
|
357
|
+
build === null ||
|
|
358
|
+
typeof build !== "object" ||
|
|
359
|
+
Array.isArray(build) ||
|
|
360
|
+
build.sourceRef !== `refs/tags/${manifest.releaseTag}` ||
|
|
361
|
+
!/^[0-9a-f]{40,64}$/.test(build.sourceCommit ?? "") ||
|
|
362
|
+
!Number.isSafeInteger(build.sourceDateEpoch) ||
|
|
363
|
+
build.sourceDateEpoch < 0 ||
|
|
364
|
+
!/^[0-9a-f]{64}$/.test(build.lockfileSha256 ?? "") ||
|
|
365
|
+
!/^v\d+\.\d+\.\d+$/.test(build.nodeVersion ?? "") ||
|
|
366
|
+
!Number.isSafeInteger(build.nodeMajor) ||
|
|
367
|
+
!/^\d+$/.test(build.nodeModuleAbi ?? "") ||
|
|
368
|
+
!/^\d+$/.test(build.nodeApiVersion ?? "") ||
|
|
369
|
+
!/^\d+\.\d+\.\d+$/.test(build.npmVersion ?? "") ||
|
|
370
|
+
build.artifactProfile !== "release" ||
|
|
371
|
+
Number(build.nodeVersion.slice(1).split(".")[0]) !== build.nodeMajor
|
|
372
|
+
) {
|
|
373
|
+
throw new Error("agenc: runtime manifest build provenance is invalid");
|
|
374
|
+
}
|
|
375
|
+
for (const artifact of manifest.artifacts) {
|
|
376
|
+
if (
|
|
377
|
+
artifact.nodeMajor !== build.nodeMajor ||
|
|
378
|
+
artifact.nodeModuleAbi !== build.nodeModuleAbi ||
|
|
379
|
+
artifact.nodeApiVersion !== build.nodeApiVersion
|
|
380
|
+
) {
|
|
381
|
+
throw new Error("agenc: runtime manifest artifact disagrees with build provenance");
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return manifest;
|
|
386
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
2
|
+
|
|
3
|
+
export interface LocalSqliteLockOptions {
|
|
4
|
+
readonly timeoutMs?: number;
|
|
5
|
+
readonly label?: string;
|
|
6
|
+
readonly deadline?: number;
|
|
7
|
+
/** Permit a root/current-user-owned sticky directory as the validated leaf. */
|
|
8
|
+
readonly allowTrustedStickyLeaf?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type LocalSqliteLockRelease = () => void;
|
|
12
|
+
|
|
13
|
+
export class LocalSqliteLockTimeoutError extends Error {
|
|
14
|
+
readonly code: "AGENC_LOCK_TIMEOUT";
|
|
15
|
+
readonly path: string;
|
|
16
|
+
readonly label: string;
|
|
17
|
+
readonly timeoutMs: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function acquireLocalSqliteLocks(
|
|
21
|
+
requestedPaths: readonly string[],
|
|
22
|
+
options?: LocalSqliteLockOptions,
|
|
23
|
+
): Promise<LocalSqliteLockRelease>;
|
|
24
|
+
|
|
25
|
+
export function acquireLocalSqliteLock(
|
|
26
|
+
path: string,
|
|
27
|
+
options?: LocalSqliteLockOptions,
|
|
28
|
+
): Promise<LocalSqliteLockRelease>;
|
|
29
|
+
|
|
30
|
+
export function assertLocalPrivateDirectory(
|
|
31
|
+
path: string,
|
|
32
|
+
options?: LocalSqliteLockOptions,
|
|
33
|
+
): Promise<string>;
|
|
34
|
+
|
|
35
|
+
export function assertLocalPrivateFile(
|
|
36
|
+
path: string,
|
|
37
|
+
options?: LocalSqliteLockOptions,
|
|
38
|
+
): Promise<string>;
|
|
39
|
+
|
|
40
|
+
export function configureLocalSqliteLockConnection(
|
|
41
|
+
database: DatabaseSync,
|
|
42
|
+
): void;
|
|
43
|
+
|
|
44
|
+
export function isSqliteBusyError(error: unknown): boolean;
|