@koed-labs/server 0.0.0-bootstrap.0 → 0.10.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/LICENSE +201 -0
- package/README.md +65 -0
- package/bin/koed.js +7 -0
- package/package.json +22 -4
- package/third-party-notices.json +92 -0
- package/vendor/chunk-2BWTLF7A.js +13883 -0
- package/vendor/chunk-2DA7EFZV.js +924 -0
- package/vendor/chunk-2S3OGPUC.js +58 -0
- package/vendor/chunk-473OA62O.js +26827 -0
- package/vendor/chunk-6JRWGOQG.js +236 -0
- package/vendor/chunk-7NOQSX4T.js +14 -0
- package/vendor/chunk-7NXKPDF3.js +1586 -0
- package/vendor/chunk-A4NKL2PK.js +153 -0
- package/vendor/chunk-A6WMU2GG.js +48 -0
- package/vendor/chunk-AFLKCQHF.js +73 -0
- package/vendor/chunk-BTS4ZTF3.js +43 -0
- package/vendor/chunk-C3N4N5HR.js +108 -0
- package/vendor/chunk-DXTAWHY3.js +25 -0
- package/vendor/chunk-G4IIVVJO.js +56 -0
- package/vendor/chunk-GELDEJJ2.js +4337 -0
- package/vendor/chunk-HFM6ROJL.js +188 -0
- package/vendor/chunk-HK2H3MVE.js +328 -0
- package/vendor/chunk-HZJK5RWC.js +5088 -0
- package/vendor/chunk-J2L726BC.js +2497 -0
- package/vendor/chunk-JBGD4JLR.js +3779 -0
- package/vendor/chunk-JJNLH5GA.js +2470 -0
- package/vendor/chunk-JKOG52PE.js +128 -0
- package/vendor/chunk-K2EIFEQF.js +89 -0
- package/vendor/chunk-KUIWH33N.js +16 -0
- package/vendor/chunk-NEWIOOM6.js +34 -0
- package/vendor/chunk-NM5NXCVM.js +2404 -0
- package/vendor/chunk-PT3AUPGE.js +459 -0
- package/vendor/chunk-PY5LYLV7.js +143 -0
- package/vendor/chunk-QIIKMG3M.js +103 -0
- package/vendor/chunk-QT24SAW6.js +110 -0
- package/vendor/chunk-RVYJKKWA.js +22 -0
- package/vendor/chunk-SKVYP7NY.js +1647 -0
- package/vendor/chunk-TSRPHKAC.js +780 -0
- package/vendor/chunk-UI5RLBKQ.js +30 -0
- package/vendor/chunk-VPC5NLZG.js +259 -0
- package/vendor/chunk-W2HNI6AA.js +75 -0
- package/vendor/chunk-X6M525ZJ.js +20 -0
- package/vendor/chunk-XIZVLPHZ.js +18 -0
- package/vendor/chunk-XNFVSVBS.js +242 -0
- package/vendor/chunk-YCDEMYY6.js +2076 -0
- package/vendor/chunk-YJ6XUZT6.js +292 -0
- package/vendor/chunk-YJWREXN6.js +26020 -0
- package/vendor/chunk-YK4F4VY2.js +173 -0
- package/vendor/chunk-YOFI4PCH.js +36 -0
- package/vendor/chunk-ZUGNCQZE.js +247 -0
- package/vendor/chunk-ZXX4ZNJY.js +21 -0
- package/vendor/cli.js +7 -0
- package/vendor/component-manifest-catalog.js +8 -0
- package/vendor/control-plane.js +23 -0
- package/vendor/package.json +1 -0
- package/vendor/privacy-service-bootstrap.js +6 -0
|
@@ -0,0 +1,2404 @@
|
|
|
1
|
+
import { createRequire as createKoedRequire } from "node:module"; const require = createKoedRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
isRuntimeGenerationState
|
|
4
|
+
} from "./chunk-G4IIVVJO.js";
|
|
5
|
+
import {
|
|
6
|
+
lookupManifestPin,
|
|
7
|
+
productionComponentManifestCatalog,
|
|
8
|
+
validateManifestCatalog
|
|
9
|
+
} from "./chunk-QIIKMG3M.js";
|
|
10
|
+
import {
|
|
11
|
+
canonicalArtifactBytes,
|
|
12
|
+
canonicalArtifactJson,
|
|
13
|
+
extractVerifiedPackageArchive,
|
|
14
|
+
isVersionDowngrade
|
|
15
|
+
} from "./chunk-2DA7EFZV.js";
|
|
16
|
+
|
|
17
|
+
// packages/koed-server/dist/component-runtime-identity.js
|
|
18
|
+
import process2 from "node:process";
|
|
19
|
+
var isVersion = (value) => typeof value === "string" && /^(?:0|[1-9]\d*)(?:\.(?:0|[1-9]\d*)){1,2}$/.test(value) && value.split(".").every((part) => Number.isSafeInteger(Number(part)));
|
|
20
|
+
function discoverRuntimeIdentityFromProcess(source) {
|
|
21
|
+
const platform = source.platform === "darwin" ? "macos" : source.platform === "linux" ? "linux" : void 0;
|
|
22
|
+
const architecture = source.arch === "arm64" || source.arch === "x64" ? source.arch : void 0;
|
|
23
|
+
const runtimeVersion = source.versions.electron ?? source.versions.node;
|
|
24
|
+
const nodeVersion = source.versions.node;
|
|
25
|
+
const modulesAbi = source.versions.modules;
|
|
26
|
+
const napiVersion = Number(source.versions.napi ?? "0");
|
|
27
|
+
let libcVersion;
|
|
28
|
+
if (platform === "linux") {
|
|
29
|
+
try {
|
|
30
|
+
libcVersion = source.report?.getReport().header?.glibcVersionRuntime;
|
|
31
|
+
} catch {
|
|
32
|
+
throw new Error("actual runtime identity is unsupported or incomplete");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (!platform || !architecture || !isVersion(runtimeVersion) || !isVersion(nodeVersion) || typeof modulesAbi !== "string" || !/^\d+$/.test(modulesAbi) || !Number.isSafeInteger(napiVersion) || napiVersion < 0 || platform === "linux" && !isVersion(libcVersion))
|
|
36
|
+
throw new Error("actual runtime identity is unsupported or incomplete");
|
|
37
|
+
return {
|
|
38
|
+
kind: source.versions.electron === void 0 ? "node" : "electron",
|
|
39
|
+
version: runtimeVersion,
|
|
40
|
+
nodeVersion,
|
|
41
|
+
modulesAbi,
|
|
42
|
+
napiVersion,
|
|
43
|
+
platform,
|
|
44
|
+
architecture,
|
|
45
|
+
...libcVersion === void 0 ? {} : { libcVersion }
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function discoverActualRuntimeIdentity() {
|
|
49
|
+
return discoverRuntimeIdentityFromProcess(process2);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// packages/koed-server/dist/config.js
|
|
53
|
+
import { existsSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
54
|
+
import { randomUUID } from "node:crypto";
|
|
55
|
+
import { dirname } from "node:path";
|
|
56
|
+
import { mkdirSync } from "node:fs";
|
|
57
|
+
var defaultKoedServerConfig = {
|
|
58
|
+
runtimeMode: "local-personal",
|
|
59
|
+
dependencyMode: "bundled-local",
|
|
60
|
+
codexTranscriptWatcherEnabled: true,
|
|
61
|
+
claudeTranscriptWatcherEnabled: true,
|
|
62
|
+
piTranscriptWatcherEnabled: true,
|
|
63
|
+
codexGlobalMemoryGuidanceEnabled: true,
|
|
64
|
+
hardwareAcceleration: "auto"
|
|
65
|
+
};
|
|
66
|
+
var trim = (value) => {
|
|
67
|
+
const trimmed = value?.trim();
|
|
68
|
+
return trimmed ? trimmed : void 0;
|
|
69
|
+
};
|
|
70
|
+
var runtimeMode = (value) => value === "local-personal" || value === "external" || value === "developer" ? value : void 0;
|
|
71
|
+
var dependencyMode = (value) => value === "external" || value === "bundled-local" ? value : void 0;
|
|
72
|
+
var codexTranscriptWatcherSetting = (value) => {
|
|
73
|
+
if (value === true || value === "true")
|
|
74
|
+
return true;
|
|
75
|
+
if (value === false || value === "false")
|
|
76
|
+
return false;
|
|
77
|
+
return void 0;
|
|
78
|
+
};
|
|
79
|
+
var claudeTranscriptWatcherSetting = codexTranscriptWatcherSetting;
|
|
80
|
+
var piTranscriptWatcherSetting = codexTranscriptWatcherSetting;
|
|
81
|
+
var booleanSetting = codexTranscriptWatcherSetting;
|
|
82
|
+
var hardwareAccelerationPreference = (value, source) => value === void 0 ? void 0 : value === "auto" || value === "cpu" ? value : (() => {
|
|
83
|
+
throw new Error(`${source} must be auto or cpu`);
|
|
84
|
+
})();
|
|
85
|
+
var readConfig = (paths, deps) => {
|
|
86
|
+
if (!deps.existsSync(paths.serverConfigPath)) {
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const parsed = JSON.parse(deps.readFileSync(paths.serverConfigPath, "utf8"));
|
|
91
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
92
|
+
throw new Error("expected a JSON object");
|
|
93
|
+
}
|
|
94
|
+
const record = parsed;
|
|
95
|
+
if (!deps.strict)
|
|
96
|
+
return record;
|
|
97
|
+
const stringFields = ["runtimeMode", "dependencyMode"];
|
|
98
|
+
const booleanFields = [
|
|
99
|
+
"codexTranscriptWatcherEnabled",
|
|
100
|
+
"claudeTranscriptWatcherEnabled",
|
|
101
|
+
"piTranscriptWatcherEnabled",
|
|
102
|
+
"codexGlobalMemoryGuidanceEnabled"
|
|
103
|
+
];
|
|
104
|
+
for (const field of stringFields) {
|
|
105
|
+
if (record[field] !== void 0 && typeof record[field] !== "string") {
|
|
106
|
+
throw new Error(`${field} must be a string`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (record.runtimeMode !== void 0 && !runtimeMode(record.runtimeMode)) {
|
|
110
|
+
throw new Error("runtimeMode must be local-personal, external, or developer");
|
|
111
|
+
}
|
|
112
|
+
if (record.dependencyMode !== void 0 && !dependencyMode(record.dependencyMode)) {
|
|
113
|
+
throw new Error("dependencyMode must be bundled-local or external");
|
|
114
|
+
}
|
|
115
|
+
for (const field of booleanFields) {
|
|
116
|
+
if (record[field] !== void 0 && typeof record[field] !== "boolean") {
|
|
117
|
+
throw new Error(`${field} must be a boolean`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (record.hardwareAcceleration !== void 0) {
|
|
121
|
+
hardwareAccelerationPreference(record.hardwareAcceleration, "server.json hardwareAcceleration");
|
|
122
|
+
}
|
|
123
|
+
if (record.external !== void 0 && (!record.external || typeof record.external !== "object" || Array.isArray(record.external))) {
|
|
124
|
+
throw new Error("external must be an object");
|
|
125
|
+
}
|
|
126
|
+
if (record.external && typeof record.external === "object") {
|
|
127
|
+
const external = record.external;
|
|
128
|
+
const supportedExternalFields = [
|
|
129
|
+
"databaseUrl",
|
|
130
|
+
"redisUrl",
|
|
131
|
+
"embeddingServiceUrl",
|
|
132
|
+
"privacyServiceUrl"
|
|
133
|
+
];
|
|
134
|
+
for (const [field, value] of Object.entries(external)) {
|
|
135
|
+
if (!supportedExternalFields.includes(field)) {
|
|
136
|
+
throw new Error(`external.${field} is not supported`);
|
|
137
|
+
}
|
|
138
|
+
if (value !== void 0 && typeof value !== "string") {
|
|
139
|
+
throw new Error(`external.${field} must be a string`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return record;
|
|
144
|
+
} catch (error) {
|
|
145
|
+
if (!deps.strict)
|
|
146
|
+
return {};
|
|
147
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
148
|
+
throw new Error(`Cannot read ${paths.serverConfigPath}: ${message}`, {
|
|
149
|
+
cause: error
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
var resolveKoedServerConfig = (paths, environment = process.env, deps = {}) => {
|
|
154
|
+
const file = readConfig(paths, {
|
|
155
|
+
existsSync: deps.existsSync ?? existsSync,
|
|
156
|
+
readFileSync: deps.readFileSync ?? readFileSync,
|
|
157
|
+
strict: deps.strict
|
|
158
|
+
});
|
|
159
|
+
if (deps.strict) {
|
|
160
|
+
if (environment.KOED_RUNTIME_MODE !== void 0 && !runtimeMode(environment.KOED_RUNTIME_MODE)) {
|
|
161
|
+
throw new Error("KOED_RUNTIME_MODE must be local-personal, external, or developer");
|
|
162
|
+
}
|
|
163
|
+
if (environment.KOED_DEPENDENCY_MODE !== void 0 && !dependencyMode(environment.KOED_DEPENDENCY_MODE)) {
|
|
164
|
+
throw new Error("KOED_DEPENDENCY_MODE must be bundled-local or external");
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const resolvedRuntimeMode = runtimeMode(environment.KOED_RUNTIME_MODE) ?? runtimeMode(file.runtimeMode) ?? defaultKoedServerConfig.runtimeMode;
|
|
168
|
+
const resolvedTranscriptWatcherSetting = codexTranscriptWatcherSetting(environment.MEMORY_CODEX_TRANSCRIPT_WATCHER_ENABLED) ?? codexTranscriptWatcherSetting(file.codexTranscriptWatcherEnabled);
|
|
169
|
+
const resolvedClaudeTranscriptWatcherSetting = claudeTranscriptWatcherSetting(environment.MEMORY_CLAUDE_TRANSCRIPT_WATCHER_ENABLED) ?? claudeTranscriptWatcherSetting(file.claudeTranscriptWatcherEnabled);
|
|
170
|
+
const resolvedPiTranscriptWatcherSetting = piTranscriptWatcherSetting(environment.MEMORY_PI_TRANSCRIPT_WATCHER_ENABLED) ?? piTranscriptWatcherSetting(file.piTranscriptWatcherEnabled);
|
|
171
|
+
if (resolvedRuntimeMode === "external" && (resolvedTranscriptWatcherSetting === true || resolvedClaudeTranscriptWatcherSetting === true || resolvedPiTranscriptWatcherSetting === true)) {
|
|
172
|
+
throw new Error("Transcript Watchers cannot run in external runtime mode; run capture through a local-personal koed-server.");
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
runtimeMode: resolvedRuntimeMode,
|
|
176
|
+
dependencyMode: dependencyMode(environment.KOED_DEPENDENCY_MODE) ?? dependencyMode(file.dependencyMode) ?? (resolvedRuntimeMode === "local-personal" ? "bundled-local" : "external"),
|
|
177
|
+
codexTranscriptWatcherEnabled: resolvedTranscriptWatcherSetting ?? resolvedRuntimeMode !== "external",
|
|
178
|
+
claudeTranscriptWatcherEnabled: resolvedClaudeTranscriptWatcherSetting ?? resolvedRuntimeMode !== "external",
|
|
179
|
+
piTranscriptWatcherEnabled: resolvedPiTranscriptWatcherSetting ?? resolvedRuntimeMode !== "external",
|
|
180
|
+
codexGlobalMemoryGuidanceEnabled: booleanSetting(environment.KOED_CODEX_GLOBAL_MEMORY_GUIDANCE_ENABLED) ?? booleanSetting(file.codexGlobalMemoryGuidanceEnabled) ?? defaultKoedServerConfig.codexGlobalMemoryGuidanceEnabled,
|
|
181
|
+
hardwareAcceleration: hardwareAccelerationPreference(environment.KOED_HARDWARE_ACCELERATION, "KOED_HARDWARE_ACCELERATION") ?? hardwareAccelerationPreference(file.hardwareAcceleration, "server.json hardwareAcceleration") ?? defaultKoedServerConfig.hardwareAcceleration,
|
|
182
|
+
external: {
|
|
183
|
+
databaseUrl: trim(environment.KOED_EXTERNAL_DATABASE_URL) ?? trim(environment.DATABASE_URL) ?? trim(file.external?.databaseUrl),
|
|
184
|
+
redisUrl: trim(environment.KOED_EXTERNAL_REDIS_URL) ?? trim(environment.REDIS_URL) ?? trim(file.external?.redisUrl),
|
|
185
|
+
embeddingServiceUrl: trim(environment.KOED_EXTERNAL_EMBEDDING_SERVICE_URL) ?? trim(environment.EMBEDDING_SERVICE_URL) ?? trim(file.external?.embeddingServiceUrl),
|
|
186
|
+
privacyServiceUrl: trim(environment.KOED_EXTERNAL_PRIVACY_SERVICE_URL) ?? trim(environment.PRIVACY_SERVICE_URL) ?? trim(file.external?.privacyServiceUrl)
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
};
|
|
190
|
+
var writeServerConfigAtomically = (paths, config, deps = {}) => {
|
|
191
|
+
mkdirSync(dirname(paths.serverConfigPath), { recursive: true, mode: 448 });
|
|
192
|
+
const temporary = `${paths.serverConfigPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
193
|
+
try {
|
|
194
|
+
(deps.writeFileSync ?? writeFileSync)(temporary, `${JSON.stringify(config, null, 2)}
|
|
195
|
+
`, { mode: 384 });
|
|
196
|
+
(deps.renameSync ?? renameSync)(temporary, paths.serverConfigPath);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
(deps.rmSync ?? rmSync)(temporary, { force: true });
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
var writeCodexGlobalMemoryGuidancePreference = (paths, enabled, deps = {}) => {
|
|
203
|
+
const fileExists = deps.existsSync ?? existsSync;
|
|
204
|
+
const read = deps.readFileSync ?? readFileSync;
|
|
205
|
+
let existing = {};
|
|
206
|
+
if (fileExists(paths.serverConfigPath)) {
|
|
207
|
+
try {
|
|
208
|
+
const parsed = JSON.parse(read(paths.serverConfigPath, "utf8"));
|
|
209
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
210
|
+
throw new Error("expected a JSON object");
|
|
211
|
+
}
|
|
212
|
+
existing = parsed;
|
|
213
|
+
} catch (error) {
|
|
214
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
215
|
+
throw new Error(`Cannot update Codex memory guidance preference because ${paths.serverConfigPath} is malformed: ${message}.`, { cause: error });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
writeServerConfigAtomically(paths, { ...existing, codexGlobalMemoryGuidanceEnabled: enabled }, deps);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// packages/koed-server/dist/component-store.js
|
|
222
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
223
|
+
import { chmodSync, closeSync as closeSync3, constants as constants3, copyFileSync, createWriteStream, existsSync as existsSync2, fstatSync as fstatSync2, lstatSync as lstatSync3, openSync as openSync3, mkdirSync as mkdirSync3, mkdtempSync, readFileSync as readFileSync3, readdirSync as readdirSync3, renameSync as renameSync2, rmSync as rmSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
224
|
+
import { basename as basename2, dirname as dirname2, isAbsolute as isAbsolute3, parse, relative as relative3, resolve as resolve3, sep as sep3 } from "node:path";
|
|
225
|
+
import { Readable as Readable2, Transform } from "node:stream";
|
|
226
|
+
import { pipeline } from "node:stream/promises";
|
|
227
|
+
|
|
228
|
+
// packages/koed-server/dist/directory-lock.js
|
|
229
|
+
import { mkdirSync as mkdirSync2, rmSync as rmSync2 } from "node:fs";
|
|
230
|
+
var acquireDirectoryLock = (path) => {
|
|
231
|
+
try {
|
|
232
|
+
mkdirSync2(path, { mode: 448 });
|
|
233
|
+
} catch (error) {
|
|
234
|
+
if (error.code !== "EEXIST")
|
|
235
|
+
throw error;
|
|
236
|
+
throw Object.assign(new Error("lock is already held"), { code: "ELOCKED" });
|
|
237
|
+
}
|
|
238
|
+
let released = false;
|
|
239
|
+
return () => {
|
|
240
|
+
if (released)
|
|
241
|
+
return;
|
|
242
|
+
released = true;
|
|
243
|
+
rmSync2(path, { recursive: true, force: true });
|
|
244
|
+
};
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// packages/koed-server/dist/desktop-runtime-capability.js
|
|
248
|
+
import { createHash } from "node:crypto";
|
|
249
|
+
import { closeSync, constants, lstatSync, openSync, readFileSync as readFileSync2, readlinkSync, readdirSync, realpathSync } from "node:fs";
|
|
250
|
+
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
251
|
+
var issuedCapabilities = /* @__PURE__ */ new WeakSet();
|
|
252
|
+
var hash = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
253
|
+
function validateDesktopRuntimeManifest(value) {
|
|
254
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
255
|
+
throw new Error("Desktop runtime bundle manifest is invalid");
|
|
256
|
+
const record = value;
|
|
257
|
+
const target = record.target;
|
|
258
|
+
if (record.schemaVersion !== 1 || record.component !== "base" || typeof record.productVersion !== "string" || !target || typeof target !== "object" || Array.isArray(target) || target.platform !== "macos" && target.platform !== "linux" || target.architecture !== "arm64" && target.architecture !== "x64" || !Array.isArray(record.files))
|
|
259
|
+
throw new Error("Desktop runtime bundle manifest is invalid");
|
|
260
|
+
for (const item of record.files) {
|
|
261
|
+
if (!item || typeof item !== "object" || Array.isArray(item))
|
|
262
|
+
throw new Error("Desktop runtime bundle manifest is invalid");
|
|
263
|
+
const entry = item;
|
|
264
|
+
if (typeof entry.path !== "string" || typeof entry.sha256 !== "string" || entry.kind !== "file" && entry.kind !== "symlink")
|
|
265
|
+
throw new Error("Desktop runtime bundle manifest is invalid");
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
var regularFile = (path) => {
|
|
269
|
+
if (constants.O_NOFOLLOW === void 0)
|
|
270
|
+
throw new Error("Desktop bundle no-follow access is unavailable");
|
|
271
|
+
const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
272
|
+
try {
|
|
273
|
+
if (!lstatSync(path).isFile())
|
|
274
|
+
throw new Error("Desktop runtime bundle entry is not a regular file");
|
|
275
|
+
return readFileSync2(fd);
|
|
276
|
+
} finally {
|
|
277
|
+
closeSync(fd);
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
function verifyDesktopRuntimeBundle(resourcesPath) {
|
|
281
|
+
const actualResources = realpathSync.native(resolve(resourcesPath));
|
|
282
|
+
const runtimeRoot = resolve(actualResources, "koed-runtime");
|
|
283
|
+
const manifestPath = resolve(runtimeRoot, "desktop-bundle-manifest.json");
|
|
284
|
+
const manifestBytes = regularFile(manifestPath);
|
|
285
|
+
const manifest = JSON.parse(manifestBytes.toString("utf8"));
|
|
286
|
+
validateDesktopRuntimeManifest(manifest);
|
|
287
|
+
const record = manifest;
|
|
288
|
+
const seen = /* @__PURE__ */ new Set();
|
|
289
|
+
for (const entry of record.files) {
|
|
290
|
+
const path = entry.path;
|
|
291
|
+
const absolute = resolve(runtimeRoot, path);
|
|
292
|
+
const rel = relative(runtimeRoot, absolute);
|
|
293
|
+
if (!path || !/^[a-f0-9]{64}$/.test(entry.sha256) || path.split("/").some((part) => !part || part === "." || part === "..") || path.startsWith("privacy-service/") || isAbsolute(path) || rel === ".." || rel.startsWith(`..${sep}`) || seen.has(path)) {
|
|
294
|
+
throw new Error("Desktop runtime bundle manifest path is invalid");
|
|
295
|
+
}
|
|
296
|
+
seen.add(path);
|
|
297
|
+
const actualDigest = entry.kind === "file" ? hash(regularFile(absolute)) : hash(readlinkSync(absolute));
|
|
298
|
+
if (actualDigest !== entry.sha256) {
|
|
299
|
+
throw new Error("Desktop runtime bundle file digest mismatch");
|
|
300
|
+
}
|
|
301
|
+
if (entry.kind === "symlink") {
|
|
302
|
+
const target = realpathSync.native(absolute);
|
|
303
|
+
const targetRelative = relative(runtimeRoot, target);
|
|
304
|
+
if (targetRelative === ".." || targetRelative.startsWith(`..${sep}`) || isAbsolute(targetRelative)) {
|
|
305
|
+
throw new Error("Desktop runtime bundle symlink escaped resources");
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const actualFiles = [];
|
|
310
|
+
const collectFiles = (directory, prefix = "") => {
|
|
311
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
312
|
+
const path = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
313
|
+
if (entry.isDirectory())
|
|
314
|
+
collectFiles(resolve(directory, entry.name), path);
|
|
315
|
+
else if (entry.isFile() || entry.isSymbolicLink()) {
|
|
316
|
+
if (path !== "desktop-bundle-manifest.json")
|
|
317
|
+
actualFiles.push(path);
|
|
318
|
+
} else
|
|
319
|
+
throw new Error("Desktop runtime bundle contains an unsupported entry");
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
collectFiles(runtimeRoot);
|
|
323
|
+
if (actualFiles.some((path) => !seen.has(path)) || actualFiles.length !== seen.size) {
|
|
324
|
+
throw new Error("Desktop runtime bundle contains an unlisted file");
|
|
325
|
+
}
|
|
326
|
+
const verifiedManifest = Object.freeze({
|
|
327
|
+
...record,
|
|
328
|
+
target: Object.freeze({ ...record.target }),
|
|
329
|
+
files: Object.freeze(record.files.map((entry) => Object.freeze({ ...entry })))
|
|
330
|
+
});
|
|
331
|
+
const capability = Object.freeze({
|
|
332
|
+
resourcesPath: actualResources,
|
|
333
|
+
productVersion: record.productVersion,
|
|
334
|
+
bundleDigest: hash(canonicalArtifactJson(record)),
|
|
335
|
+
manifest: verifiedManifest,
|
|
336
|
+
target: verifiedManifest.target
|
|
337
|
+
});
|
|
338
|
+
issuedCapabilities.add(capability);
|
|
339
|
+
return capability;
|
|
340
|
+
}
|
|
341
|
+
function validateDesktopRuntimeCapability(capability) {
|
|
342
|
+
return typeof capability === "object" && capability !== null && issuedCapabilities.has(capability);
|
|
343
|
+
}
|
|
344
|
+
function deriveDesktopRuntimeOwner(koedHome, stableInstallationIdentity) {
|
|
345
|
+
return {
|
|
346
|
+
kind: "desktop",
|
|
347
|
+
installationId: hash(`${resolve(koedHome)}
|
|
348
|
+
${resolve(stableInstallationIdentity)}`).slice(0, 32)
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
function resolveDesktopRuntimeOwner(capability, koedHome) {
|
|
352
|
+
if (!validateDesktopRuntimeCapability(capability)) {
|
|
353
|
+
throw new Error("validated private Desktop runtime capability is required");
|
|
354
|
+
}
|
|
355
|
+
return deriveDesktopRuntimeOwner(koedHome, capability.resourcesPath);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// packages/koed-server/dist/component-verification.js
|
|
359
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
360
|
+
import { closeSync as closeSync2, constants as constants2, fstatSync, lstatSync as lstatSync2, openSync as openSync2, readdirSync as readdirSync2, readSync } from "node:fs";
|
|
361
|
+
import { Readable } from "node:stream";
|
|
362
|
+
import { basename, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
|
|
363
|
+
import { createGunzip } from "node:zlib";
|
|
364
|
+
|
|
365
|
+
// packages/koed-server/dist/component-artifact-identity.mjs
|
|
366
|
+
var componentArchiveName = ({ component, productVersion, target, variant }) => `koed-${component}-${productVersion}-${target.platform}-${target.architecture}${variant === void 0 ? "" : `-${variant}`}.tar.gz`;
|
|
367
|
+
var validateComponentVariantIdentity = (value) => {
|
|
368
|
+
if (value.schemaVersion === 1 && !Object.hasOwn(value, "variant"))
|
|
369
|
+
return;
|
|
370
|
+
if (value.schemaVersion === 2 && value.component === "privacy" && value.target?.platform === "linux" && value.target.architecture === "x64" && ["cpu", "cuda"].includes(value.variant))
|
|
371
|
+
return;
|
|
372
|
+
throw new Error("Unsupported component variant identity; update to a compatible control plane.");
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
// packages/koed-server/dist/component-verification.js
|
|
376
|
+
var MAX_ARCHIVE_BYTES = 2 * 1024 * 1024 * 1024;
|
|
377
|
+
var MAX_MANIFEST_BYTES = 64 * 1024 * 1024;
|
|
378
|
+
var MAX_EXPANDED_BYTES = 4 * 1024 * 1024 * 1024;
|
|
379
|
+
var MAX_FILE_BYTES = 1024 * 1024 * 1024;
|
|
380
|
+
var MAX_FILES = 1e5;
|
|
381
|
+
var MAX_DEPTH = 128;
|
|
382
|
+
var MAX_DIRECTORIES = 1e5;
|
|
383
|
+
var MAX_TAR_BYTES = MAX_EXPANDED_BYTES + MAX_FILES * 1024 + 1024;
|
|
384
|
+
var canonicalComponentManifestBytes = (manifest) => canonicalArtifactBytes(manifest);
|
|
385
|
+
function assertRecord(value, name, keys) {
|
|
386
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
387
|
+
throw new Error(`${name} must be an object`);
|
|
388
|
+
const actual = Object.keys(value);
|
|
389
|
+
if (actual.length !== keys.length || keys.some((key) => !actual.includes(key)))
|
|
390
|
+
throw new Error(`${name} has invalid fields`);
|
|
391
|
+
}
|
|
392
|
+
function safeRelativePath(path, label) {
|
|
393
|
+
if (typeof path !== "string" || !path || path.includes("\\") || path.includes("\0") || isAbsolute2(path) || path.split("/").some((part) => part === ".." || part === "." || !part)) {
|
|
394
|
+
throw new Error(`${label} is unsafe`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
function validateComponentManifest(value) {
|
|
398
|
+
assertRecord(value, "component manifest", [
|
|
399
|
+
"schemaVersion",
|
|
400
|
+
"productVersion",
|
|
401
|
+
"component",
|
|
402
|
+
"target",
|
|
403
|
+
"runtimes",
|
|
404
|
+
"archive",
|
|
405
|
+
"requiredFiles",
|
|
406
|
+
"files",
|
|
407
|
+
...value && typeof value === "object" && Object.hasOwn(value, "variant") ? ["variant"] : []
|
|
408
|
+
]);
|
|
409
|
+
const manifest = value;
|
|
410
|
+
if (![1, 2].includes(manifest.schemaVersion) || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(manifest.productVersion) || !["base", "privacy"].includes(manifest.component))
|
|
411
|
+
throw new Error("component manifest identity is invalid");
|
|
412
|
+
validateComponentVariantIdentity(manifest);
|
|
413
|
+
assertRecord(manifest.target, "component target", manifest.target.libc ? ["platform", "architecture", "libc"] : ["platform", "architecture"]);
|
|
414
|
+
if (!["macos", "linux"].includes(manifest.target.platform) || !["arm64", "x64"].includes(manifest.target.architecture))
|
|
415
|
+
throw new Error("unsupported component target");
|
|
416
|
+
if (manifest.target.libc) {
|
|
417
|
+
assertRecord(manifest.target.libc, "component libc target", [
|
|
418
|
+
"family",
|
|
419
|
+
"minimumVersion"
|
|
420
|
+
]);
|
|
421
|
+
if (manifest.target.platform !== "linux" || manifest.target.libc.family !== "glibc" || !isVersion2(manifest.target.libc.minimumVersion))
|
|
422
|
+
throw new Error("component libc target is invalid");
|
|
423
|
+
}
|
|
424
|
+
assertRecord(manifest.archive, "component archive", [
|
|
425
|
+
"name",
|
|
426
|
+
"bytes",
|
|
427
|
+
"sha256"
|
|
428
|
+
]);
|
|
429
|
+
if (typeof manifest.archive.name !== "string" || basename(manifest.archive.name) !== manifest.archive.name || !Number.isSafeInteger(manifest.archive.bytes) || manifest.archive.bytes < 1 || !isHash(manifest.archive.sha256))
|
|
430
|
+
throw new Error("component archive metadata is invalid");
|
|
431
|
+
if (manifest.variant !== void 0 && manifest.archive.name !== componentArchiveName(manifest))
|
|
432
|
+
throw new Error("Component archive variant identity mismatch.");
|
|
433
|
+
if (!Array.isArray(manifest.runtimes) || manifest.runtimes.length === 0 || !Array.isArray(manifest.requiredFiles) || manifest.requiredFiles.length > MAX_FILES || !Array.isArray(manifest.files) || manifest.files.length > MAX_FILES)
|
|
434
|
+
throw new Error("component manifest lists are invalid");
|
|
435
|
+
for (const runtime of manifest.runtimes) {
|
|
436
|
+
if (!runtime || typeof runtime !== "object" || Array.isArray(runtime))
|
|
437
|
+
throw new Error("component runtime compatibility is invalid");
|
|
438
|
+
const runtimeRecord = runtime;
|
|
439
|
+
const runtimeKeys = [
|
|
440
|
+
"kind",
|
|
441
|
+
"runtimeRange",
|
|
442
|
+
"nodeRange",
|
|
443
|
+
...runtimeRecord.modulesAbi === void 0 ? [] : ["modulesAbi"],
|
|
444
|
+
...runtimeRecord.minimumNapi === void 0 ? [] : ["minimumNapi"]
|
|
445
|
+
];
|
|
446
|
+
assertRecord(runtime, "component runtime compatibility", runtimeKeys);
|
|
447
|
+
if (typeof runtimeRecord.kind !== "string" || !["node", "electron"].includes(runtimeRecord.kind) || !validRange(runtime.runtimeRange) || !validRange(runtime.nodeRange) || runtimeRecord.modulesAbi !== void 0 && (typeof runtimeRecord.modulesAbi !== "string" || !/^\d+$/.test(runtimeRecord.modulesAbi)) || runtimeRecord.minimumNapi !== void 0 && (typeof runtimeRecord.minimumNapi !== "number" || !Number.isSafeInteger(runtimeRecord.minimumNapi) || runtimeRecord.minimumNapi < 1))
|
|
448
|
+
throw new Error("component runtime compatibility is invalid");
|
|
449
|
+
if (runtime.modulesAbi === void 0 && runtime.minimumNapi === void 0)
|
|
450
|
+
throw new Error("component runtime compatibility must bind ABI or N-API");
|
|
451
|
+
}
|
|
452
|
+
const paths = /* @__PURE__ */ new Set();
|
|
453
|
+
for (const item of manifest.files) {
|
|
454
|
+
assertRecord(item, "component file entry", ["path", "sha256"]);
|
|
455
|
+
safeRelativePath(item.path, "component file path");
|
|
456
|
+
if (!isHash(item.sha256) || paths.has(item.path))
|
|
457
|
+
throw new Error("component file inventory is invalid or duplicate");
|
|
458
|
+
paths.add(item.path);
|
|
459
|
+
}
|
|
460
|
+
const required = /* @__PURE__ */ new Set();
|
|
461
|
+
for (const path of manifest.requiredFiles) {
|
|
462
|
+
safeRelativePath(path, "required component path");
|
|
463
|
+
if (required.has(path) || !paths.has(path))
|
|
464
|
+
throw new Error("required component file is duplicate or missing from inventory");
|
|
465
|
+
required.add(path);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
var isHash = (value) => typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
|
|
469
|
+
var isVersion2 = (value) => typeof value === "string" && /^(?:0|[1-9]\d*)(?:\.(?:0|[1-9]\d*)){1,2}$/.test(value) && value.split(".").every((part) => Number.isSafeInteger(Number(part)));
|
|
470
|
+
function versionParts(value) {
|
|
471
|
+
if (typeof value !== "string" || !/^(?:0|[1-9]\d*)(?:\.(?:0|[1-9]\d*)){0,2}$/.test(value))
|
|
472
|
+
return;
|
|
473
|
+
const parts = value.split(".").map(Number);
|
|
474
|
+
if (parts.some((part) => !Number.isSafeInteger(part)))
|
|
475
|
+
return;
|
|
476
|
+
return [...parts, 0, 0].slice(0, 3);
|
|
477
|
+
}
|
|
478
|
+
function compareVersions(a, b) {
|
|
479
|
+
const left = versionParts(a);
|
|
480
|
+
const right = versionParts(b);
|
|
481
|
+
if (!left || !right)
|
|
482
|
+
return;
|
|
483
|
+
for (let i = 0; i < 3; i++)
|
|
484
|
+
if (left[i] !== right[i])
|
|
485
|
+
return left[i] - right[i];
|
|
486
|
+
return 0;
|
|
487
|
+
}
|
|
488
|
+
function validRange(range) {
|
|
489
|
+
return typeof range === "string" && range.trim().length > 0 && range.split(/\s+/).every((part) => {
|
|
490
|
+
const match = /^(?:>=|>|<=|<|=)?((?:0|[1-9]\d*)(?:\.(?:0|[1-9]\d*)){0,2})$/.exec(part);
|
|
491
|
+
return Boolean(match && versionParts(match[1]));
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
function satisfies(version, range) {
|
|
495
|
+
return range.split(/\s+/).every((part) => {
|
|
496
|
+
const match = /^(>=|>|<=|<|=)?((?:0|[1-9]\d*)(?:\.(?:0|[1-9]\d*)){0,2})$/.exec(part);
|
|
497
|
+
if (!match)
|
|
498
|
+
return false;
|
|
499
|
+
const comparison = compareVersions(version, match[2]);
|
|
500
|
+
if (comparison === void 0)
|
|
501
|
+
return false;
|
|
502
|
+
switch (match[1]) {
|
|
503
|
+
case ">=":
|
|
504
|
+
return comparison >= 0;
|
|
505
|
+
case ">":
|
|
506
|
+
return comparison > 0;
|
|
507
|
+
case "<=":
|
|
508
|
+
return comparison <= 0;
|
|
509
|
+
case "<":
|
|
510
|
+
return comparison < 0;
|
|
511
|
+
default:
|
|
512
|
+
return comparison === 0;
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
function verifyCompatibility(manifest, input) {
|
|
517
|
+
if (manifest.component !== input.expectedComponent)
|
|
518
|
+
throw new Error("component mismatch");
|
|
519
|
+
if (manifest.variant !== input.expectedVariant)
|
|
520
|
+
throw new Error("component variant mismatch");
|
|
521
|
+
if (manifest.productVersion !== input.expectedVersion)
|
|
522
|
+
throw new Error("product version mismatch");
|
|
523
|
+
if (canonicalArtifactJson(manifest.target) !== canonicalArtifactJson(input.target))
|
|
524
|
+
throw new Error("component target mismatch");
|
|
525
|
+
if (manifest.target.platform !== input.runtime.platform || manifest.target.architecture !== input.runtime.architecture)
|
|
526
|
+
throw new Error("runtime target mismatch");
|
|
527
|
+
if (!isVersion2(input.runtime.version) || !isVersion2(input.runtime.nodeVersion) || typeof input.runtime.modulesAbi !== "string" || !/^\d+$/.test(input.runtime.modulesAbi) || !Number.isSafeInteger(input.runtime.napiVersion) || input.runtime.napiVersion < 0)
|
|
528
|
+
throw new Error("runtime identity is invalid");
|
|
529
|
+
const versionMatches = manifest.runtimes.filter((item) => item.kind === input.runtime.kind && satisfies(input.runtime.version, item.runtimeRange) && satisfies(input.runtime.nodeVersion, item.nodeRange));
|
|
530
|
+
if (!versionMatches.length)
|
|
531
|
+
throw new Error("runtime version is incompatible");
|
|
532
|
+
const abiMatches = versionMatches.filter((item) => item.modulesAbi === void 0 || item.modulesAbi === input.runtime.modulesAbi);
|
|
533
|
+
if (!abiMatches.length)
|
|
534
|
+
throw new Error("runtime ABI mismatch");
|
|
535
|
+
const compatibility = abiMatches.find((item) => item.minimumNapi === void 0 || input.runtime.napiVersion >= item.minimumNapi);
|
|
536
|
+
if (!compatibility)
|
|
537
|
+
throw new Error("runtime N-API version is incompatible");
|
|
538
|
+
if (manifest.target.libc) {
|
|
539
|
+
const comparison = isVersion2(input.runtime.libcVersion) ? compareVersions(input.runtime.libcVersion, manifest.target.libc.minimumVersion) : void 0;
|
|
540
|
+
if (comparison === void 0 || comparison < 0)
|
|
541
|
+
throw new Error("runtime libc version is incompatible");
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
function tarString(header, offset, length) {
|
|
545
|
+
const value = header.subarray(offset, offset + length);
|
|
546
|
+
const end = value.indexOf(0);
|
|
547
|
+
return value.subarray(0, end < 0 ? value.length : end).toString("utf8");
|
|
548
|
+
}
|
|
549
|
+
function tarNumber(header, offset, length) {
|
|
550
|
+
const text = tarString(header, offset, length).trim();
|
|
551
|
+
if (!/^[0-7]+$/.test(text))
|
|
552
|
+
throw new Error("malformed component tar numeric field");
|
|
553
|
+
const number = Number.parseInt(text, 8);
|
|
554
|
+
if (!Number.isSafeInteger(number))
|
|
555
|
+
throw new Error("component tar field exceeds safe integer range");
|
|
556
|
+
return number;
|
|
557
|
+
}
|
|
558
|
+
function* archiveChunks(archiveFd) {
|
|
559
|
+
let position = 0;
|
|
560
|
+
while (true) {
|
|
561
|
+
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
562
|
+
const bytesRead = readSync(archiveFd, buffer, 0, buffer.length, position);
|
|
563
|
+
if (bytesRead === 0)
|
|
564
|
+
return;
|
|
565
|
+
position += bytesRead;
|
|
566
|
+
yield buffer.subarray(0, bytesRead);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
async function* tarBlocks(stream) {
|
|
570
|
+
let pending = Buffer.alloc(0);
|
|
571
|
+
let totalBytes = 0;
|
|
572
|
+
for await (const chunk of stream) {
|
|
573
|
+
pending = pending.length ? Buffer.concat([pending, chunk]) : chunk;
|
|
574
|
+
while (pending.length >= 512) {
|
|
575
|
+
totalBytes += 512;
|
|
576
|
+
if (totalBytes > MAX_TAR_BYTES)
|
|
577
|
+
throw new Error("component archive exceeds expanded size limit");
|
|
578
|
+
yield pending.subarray(0, 512);
|
|
579
|
+
pending = pending.subarray(512);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
if (pending.length)
|
|
583
|
+
throw new Error("component archive has partial tar block");
|
|
584
|
+
}
|
|
585
|
+
async function verifyTarArchive(archiveFd, manifest) {
|
|
586
|
+
const contents = /* @__PURE__ */ new Map();
|
|
587
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
588
|
+
let expandedBytes = 0;
|
|
589
|
+
let zeroBlocks = 0;
|
|
590
|
+
let ended = false;
|
|
591
|
+
const gunzip = createGunzip();
|
|
592
|
+
const archiveSource = Readable.from(archiveChunks(archiveFd));
|
|
593
|
+
const blocks = tarBlocks(archiveSource.pipe(gunzip));
|
|
594
|
+
try {
|
|
595
|
+
for await (const header of blocks) {
|
|
596
|
+
if (ended) {
|
|
597
|
+
if (!header.every((byte) => byte === 0))
|
|
598
|
+
throw new Error("component archive contains data after tar terminator");
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
if (header.every((byte) => byte === 0)) {
|
|
602
|
+
zeroBlocks++;
|
|
603
|
+
if (zeroBlocks === 2)
|
|
604
|
+
ended = true;
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
if (zeroBlocks)
|
|
608
|
+
throw new Error("component archive has incomplete tar terminator");
|
|
609
|
+
const storedChecksum = tarNumber(header, 148, 8);
|
|
610
|
+
const check = Buffer.from(header);
|
|
611
|
+
check.fill(32, 148, 156);
|
|
612
|
+
if (storedChecksum !== check.reduce((sum, byte) => sum + byte, 0))
|
|
613
|
+
throw new Error("malformed component tar header checksum");
|
|
614
|
+
const type = tarString(header, 156, 1) || "0";
|
|
615
|
+
if (type === "1")
|
|
616
|
+
throw new Error("component archives must not contain hard links");
|
|
617
|
+
if (type === "2")
|
|
618
|
+
throw new Error("component archives must not contain symbolic links");
|
|
619
|
+
if (type !== "0" && type !== "5")
|
|
620
|
+
throw new Error(`unsupported component tar entry type: ${type}`);
|
|
621
|
+
const name = tarString(header, 0, 100);
|
|
622
|
+
const prefix = tarString(header, 345, 155);
|
|
623
|
+
const rawPath = prefix ? `${prefix}/${name}` : name;
|
|
624
|
+
const path = rawPath.replace(/\/$/, "");
|
|
625
|
+
safeRelativePath(path, "component archive path");
|
|
626
|
+
const size = tarNumber(header, 124, 12);
|
|
627
|
+
if (type === "5" && size !== 0)
|
|
628
|
+
throw new Error("component directory tar entry must be empty");
|
|
629
|
+
if (size > MAX_FILE_BYTES)
|
|
630
|
+
throw new Error("component archive entry exceeds individual file limit");
|
|
631
|
+
expandedBytes += size;
|
|
632
|
+
if (expandedBytes > MAX_EXPANDED_BYTES)
|
|
633
|
+
throw new Error("component archive exceeds expanded size limit");
|
|
634
|
+
if (seenPaths.has(path))
|
|
635
|
+
throw new Error(`duplicate component archive path: ${path}`);
|
|
636
|
+
seenPaths.add(path);
|
|
637
|
+
if (seenPaths.size > MAX_FILES)
|
|
638
|
+
throw new Error("component archive exceeds file count limit");
|
|
639
|
+
if (type === "5")
|
|
640
|
+
continue;
|
|
641
|
+
const digest = createHash2("sha256");
|
|
642
|
+
let remaining = size;
|
|
643
|
+
while (remaining > 0) {
|
|
644
|
+
const data = await blocks.next();
|
|
645
|
+
if (data.done)
|
|
646
|
+
throw new Error("component archive is truncated");
|
|
647
|
+
const count = Math.min(remaining, 512);
|
|
648
|
+
digest.update(data.value.subarray(0, count));
|
|
649
|
+
remaining -= count;
|
|
650
|
+
}
|
|
651
|
+
contents.set(path, digest.digest("hex"));
|
|
652
|
+
}
|
|
653
|
+
} finally {
|
|
654
|
+
archiveSource.destroy();
|
|
655
|
+
gunzip.destroy();
|
|
656
|
+
}
|
|
657
|
+
if (!ended)
|
|
658
|
+
throw new Error("component archive is missing tar terminator");
|
|
659
|
+
for (const path of contents.keys()) {
|
|
660
|
+
const parents = path.split("/").slice(0, -1);
|
|
661
|
+
for (let index = 1; index <= parents.length; index++) {
|
|
662
|
+
const parentPath = parents.slice(0, index).join("/");
|
|
663
|
+
if (contents.has(parentPath))
|
|
664
|
+
throw new Error(`component archive file path conflict: ${parentPath}`);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
const expected = new Map(manifest.files.map((file) => [file.path, file.sha256]));
|
|
668
|
+
if (contents.size !== expected.size)
|
|
669
|
+
throw new Error("component archive file inventory mismatch");
|
|
670
|
+
for (const [path, digest] of contents)
|
|
671
|
+
if (!expected.has(path) || digest !== expected.get(path))
|
|
672
|
+
throw new Error(`component archive file hash mismatch: ${path}`);
|
|
673
|
+
for (const required of manifest.requiredFiles)
|
|
674
|
+
if (!contents.has(required))
|
|
675
|
+
throw new Error(`required component file is missing: ${required}`);
|
|
676
|
+
}
|
|
677
|
+
function verifyComponentManifestPin(input) {
|
|
678
|
+
if (input.manifestBytes.length > MAX_MANIFEST_BYTES)
|
|
679
|
+
throw new Error("component manifest exceeds size limit");
|
|
680
|
+
let parsed;
|
|
681
|
+
try {
|
|
682
|
+
parsed = JSON.parse(input.manifestBytes.toString("utf8"));
|
|
683
|
+
} catch {
|
|
684
|
+
throw new Error("component manifest JSON is invalid");
|
|
685
|
+
}
|
|
686
|
+
validateComponentManifest(parsed);
|
|
687
|
+
const manifest = parsed;
|
|
688
|
+
const canonical = canonicalComponentManifestBytes(manifest);
|
|
689
|
+
if (!canonical.equals(input.manifestBytes))
|
|
690
|
+
throw new Error("component manifest is not canonical");
|
|
691
|
+
const pinnedDigest = lookupManifestPin(input.manifestCatalog, {
|
|
692
|
+
component: input.expectedComponent,
|
|
693
|
+
...input.expectedVariant === void 0 ? {} : { variant: input.expectedVariant },
|
|
694
|
+
productVersion: input.expectedVersion,
|
|
695
|
+
target: input.target
|
|
696
|
+
});
|
|
697
|
+
if (createHash2("sha256").update(canonical).digest("hex") !== pinnedDigest)
|
|
698
|
+
throw new Error("component manifest pin mismatch");
|
|
699
|
+
if (manifest.component !== input.expectedComponent)
|
|
700
|
+
throw new Error("component mismatch");
|
|
701
|
+
if (manifest.variant !== input.expectedVariant)
|
|
702
|
+
throw new Error("component variant mismatch");
|
|
703
|
+
if (manifest.productVersion !== input.expectedVersion)
|
|
704
|
+
throw new Error("product version mismatch");
|
|
705
|
+
if (canonicalArtifactJson(manifest.target) !== canonicalArtifactJson(input.target))
|
|
706
|
+
throw new Error("component target mismatch");
|
|
707
|
+
return manifest;
|
|
708
|
+
}
|
|
709
|
+
async function verifyComponentSync(input) {
|
|
710
|
+
const manifest = verifyComponentManifestPin(input);
|
|
711
|
+
verifyCompatibility(manifest, input);
|
|
712
|
+
const stat = lstatSync2(input.archivePath);
|
|
713
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_ARCHIVE_BYTES)
|
|
714
|
+
throw new Error("component archive is not a bounded regular file");
|
|
715
|
+
if (basename(input.archivePath) !== manifest.archive.name)
|
|
716
|
+
throw new Error("component archive name mismatch");
|
|
717
|
+
if (stat.size !== manifest.archive.bytes)
|
|
718
|
+
throw new Error("component archive size mismatch");
|
|
719
|
+
if (constants2.O_NOFOLLOW === void 0)
|
|
720
|
+
throw new Error("component archive no-follow access is unavailable");
|
|
721
|
+
const archiveFd = openSync2(input.archivePath, constants2.O_RDONLY | constants2.O_NOFOLLOW);
|
|
722
|
+
try {
|
|
723
|
+
const opened = fstatSync(archiveFd);
|
|
724
|
+
if (!opened.isFile() || !sameObject(stat, opened))
|
|
725
|
+
throw new Error("component archive changed during verification");
|
|
726
|
+
const archiveHash = createHash2("sha256");
|
|
727
|
+
for (const chunk of archiveChunks(archiveFd))
|
|
728
|
+
archiveHash.update(chunk);
|
|
729
|
+
if (!sameArchiveState(opened, fstatSync(archiveFd)))
|
|
730
|
+
throw new Error("component archive changed during verification");
|
|
731
|
+
if (archiveHash.digest("hex") !== manifest.archive.sha256)
|
|
732
|
+
throw new Error("component archive SHA-256 mismatch");
|
|
733
|
+
await verifyTarArchive(archiveFd, manifest);
|
|
734
|
+
if (!sameArchiveState(opened, fstatSync(archiveFd)))
|
|
735
|
+
throw new Error("component archive changed during verification");
|
|
736
|
+
} finally {
|
|
737
|
+
closeSync2(archiveFd);
|
|
738
|
+
}
|
|
739
|
+
return manifest;
|
|
740
|
+
}
|
|
741
|
+
var verifyComponent = (input) => verifyComponentSync(input);
|
|
742
|
+
function hashFileDescriptor(fd) {
|
|
743
|
+
const digest = createHash2("sha256");
|
|
744
|
+
let position = 0;
|
|
745
|
+
while (true) {
|
|
746
|
+
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
747
|
+
const bytesRead = readSync(fd, buffer, 0, buffer.length, position);
|
|
748
|
+
if (bytesRead === 0)
|
|
749
|
+
return digest.digest("hex");
|
|
750
|
+
digest.update(buffer.subarray(0, bytesRead));
|
|
751
|
+
position += bytesRead;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
function sameObject(left, right) {
|
|
755
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
756
|
+
}
|
|
757
|
+
function sameArchiveState(left, right) {
|
|
758
|
+
return sameObject(left, right) && left.size === right.size && left.mtimeMs === right.mtimeMs && left.ctimeMs === right.ctimeMs;
|
|
759
|
+
}
|
|
760
|
+
var EXTRACTED_TREE_LIMITS = {
|
|
761
|
+
maxFileBytes: MAX_FILE_BYTES,
|
|
762
|
+
maxExpandedBytes: MAX_EXPANDED_BYTES,
|
|
763
|
+
maxFiles: MAX_FILES,
|
|
764
|
+
maxDirectories: MAX_DIRECTORIES,
|
|
765
|
+
maxDepth: MAX_DEPTH,
|
|
766
|
+
maxEntries: MAX_FILES + MAX_DIRECTORIES
|
|
767
|
+
};
|
|
768
|
+
function verifyExtractedComponentSync(root, manifest, seams = {}) {
|
|
769
|
+
validateComponentManifest(manifest);
|
|
770
|
+
const canonicalRoot = resolve2(root);
|
|
771
|
+
const rootStat = lstatSync2(canonicalRoot);
|
|
772
|
+
if (rootStat.isSymbolicLink() || !rootStat.isDirectory())
|
|
773
|
+
throw new Error("component root is not a regular directory");
|
|
774
|
+
const observed = /* @__PURE__ */ new Set();
|
|
775
|
+
const expectedFiles = new Map(manifest.files.map((file) => [file.path, file.sha256]));
|
|
776
|
+
const limits = { ...EXTRACTED_TREE_LIMITS, ...seams.limits };
|
|
777
|
+
const stack = [canonicalRoot];
|
|
778
|
+
let directories = 0;
|
|
779
|
+
let entries = 0;
|
|
780
|
+
let totalBytes = 0;
|
|
781
|
+
while (stack.length) {
|
|
782
|
+
const current = stack.pop();
|
|
783
|
+
const beforeDirectory = lstatSync2(current);
|
|
784
|
+
if (!beforeDirectory.isDirectory() || beforeDirectory.isSymbolicLink())
|
|
785
|
+
throw new Error("component directory ancestry changed during verification");
|
|
786
|
+
seams.beforeReadDirectory?.(current);
|
|
787
|
+
const directoryEntries = readdirSync2(current, { withFileTypes: true });
|
|
788
|
+
for (const entry of directoryEntries) {
|
|
789
|
+
if (++entries > limits.maxEntries)
|
|
790
|
+
throw new Error("component tree exceeds entry count limit");
|
|
791
|
+
const path = resolve2(current, entry.name);
|
|
792
|
+
const relativePath = relative2(canonicalRoot, path).split(sep2).join("/");
|
|
793
|
+
safeRelativePath(relativePath, "extracted component path");
|
|
794
|
+
const stat = lstatSync2(path);
|
|
795
|
+
if (stat.isSymbolicLink() || !stat.isFile() && !stat.isDirectory())
|
|
796
|
+
throw new Error("component files must not contain links or special files");
|
|
797
|
+
if (stat.isDirectory()) {
|
|
798
|
+
if (++directories > limits.maxDirectories)
|
|
799
|
+
throw new Error("component tree exceeds directory count limit");
|
|
800
|
+
const depth = relativePath.split("/").length;
|
|
801
|
+
if (depth > limits.maxDepth)
|
|
802
|
+
throw new Error("component tree exceeds directory depth limit");
|
|
803
|
+
stack.push(path);
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
if (stat.nlink > 1)
|
|
807
|
+
throw new Error("component files must not contain links or special files");
|
|
808
|
+
if (stat.size > limits.maxFileBytes)
|
|
809
|
+
throw new Error("component file exceeds individual file limit");
|
|
810
|
+
totalBytes += stat.size;
|
|
811
|
+
if (totalBytes > limits.maxExpandedBytes)
|
|
812
|
+
throw new Error("component tree exceeds expanded size limit");
|
|
813
|
+
if (observed.size >= limits.maxFiles)
|
|
814
|
+
throw new Error("component tree exceeds file count limit");
|
|
815
|
+
observed.add(relativePath);
|
|
816
|
+
const expectedHash = expectedFiles.get(relativePath);
|
|
817
|
+
seams.beforeOpenFile?.(path);
|
|
818
|
+
if (constants2.O_NOFOLLOW === void 0)
|
|
819
|
+
throw new Error("component file no-follow access is unavailable");
|
|
820
|
+
const fd = openSync2(path, constants2.O_RDONLY | constants2.O_NOFOLLOW);
|
|
821
|
+
try {
|
|
822
|
+
const opened = fstatSync(fd);
|
|
823
|
+
if (!opened.isFile() || !sameObject(stat, opened))
|
|
824
|
+
throw new Error("component file changed during verification");
|
|
825
|
+
const fileHash = hashFileDescriptor(fd);
|
|
826
|
+
const after = fstatSync(fd);
|
|
827
|
+
const afterPath = lstatSync2(path);
|
|
828
|
+
if (!sameArchiveState(opened, after) || !sameArchiveState(after, afterPath))
|
|
829
|
+
throw new Error("component file changed during verification");
|
|
830
|
+
if (!expectedHash || fileHash !== expectedHash)
|
|
831
|
+
throw new Error(`component file hash mismatch: ${relativePath}`);
|
|
832
|
+
} finally {
|
|
833
|
+
closeSync2(fd);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
if (!sameObject(beforeDirectory, lstatSync2(current)))
|
|
837
|
+
throw new Error("component directory ancestry changed during verification");
|
|
838
|
+
}
|
|
839
|
+
for (const file of manifest.files)
|
|
840
|
+
if (!observed.has(file.path))
|
|
841
|
+
throw new Error(`required component file is missing or incomplete: ${file.path}`);
|
|
842
|
+
for (const file of manifest.requiredFiles)
|
|
843
|
+
if (!observed.has(file))
|
|
844
|
+
throw new Error(`required component file is missing: ${file}`);
|
|
845
|
+
}
|
|
846
|
+
var verifyExtractedComponent = (root, manifest) => Promise.resolve().then(() => verifyExtractedComponentSync(root, manifest));
|
|
847
|
+
|
|
848
|
+
// packages/koed-server/dist/component-store.js
|
|
849
|
+
var MAX_MANIFEST_BYTES2 = 64 * 1024 * 1024;
|
|
850
|
+
var MAX_ARCHIVE_BYTES2 = 2 * 1024 * 1024 * 1024;
|
|
851
|
+
var MAX_REDIRECTS = 5;
|
|
852
|
+
var MAX_GENERATION_RECORD_BYTES = 2 * MAX_MANIFEST_BYTES2 + 1024 * 1024;
|
|
853
|
+
var REQUEST_TIMEOUT_MS = 3e4;
|
|
854
|
+
var hash2 = (value) => createHash3("sha256").update(value).digest("hex");
|
|
855
|
+
var isPlainRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
856
|
+
var aborted = (signal) => {
|
|
857
|
+
if (signal?.aborted)
|
|
858
|
+
throw new Error("component installation cancelled");
|
|
859
|
+
};
|
|
860
|
+
var cancelBody = async (response) => {
|
|
861
|
+
try {
|
|
862
|
+
await response.body?.cancel();
|
|
863
|
+
} catch {
|
|
864
|
+
}
|
|
865
|
+
};
|
|
866
|
+
var exactKeys = (value, keys) => Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
|
|
867
|
+
var assertInside = (base, path) => {
|
|
868
|
+
const rel = relative3(resolve3(base), resolve3(path));
|
|
869
|
+
if (rel.startsWith("..") || isAbsolute3(rel))
|
|
870
|
+
throw new Error("component store path escaped KOED_HOME");
|
|
871
|
+
};
|
|
872
|
+
var assertDirectoryChain = (base, path) => {
|
|
873
|
+
assertInside(base, path);
|
|
874
|
+
const absolute = resolve3(path);
|
|
875
|
+
let cursor = parse(absolute).root;
|
|
876
|
+
for (const part of absolute.slice(cursor.length).split(sep3).filter(Boolean)) {
|
|
877
|
+
cursor = resolve3(cursor, part);
|
|
878
|
+
let stat;
|
|
879
|
+
try {
|
|
880
|
+
stat = lstatSync3(cursor);
|
|
881
|
+
} catch (error) {
|
|
882
|
+
if (error.code === "ENOENT")
|
|
883
|
+
return;
|
|
884
|
+
throw error;
|
|
885
|
+
}
|
|
886
|
+
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
887
|
+
throw new Error("component store path contains a non-directory or symbolic link");
|
|
888
|
+
}
|
|
889
|
+
};
|
|
890
|
+
var ensureSecureDirectory = (home, directory) => {
|
|
891
|
+
assertDirectoryChain(home, directory);
|
|
892
|
+
mkdirSync3(directory, { recursive: true, mode: 448 });
|
|
893
|
+
assertDirectoryChain(home, directory);
|
|
894
|
+
};
|
|
895
|
+
var privateDirectory = (path) => {
|
|
896
|
+
mkdirSync3(path, { recursive: true, mode: 448 });
|
|
897
|
+
const stat = lstatSync3(path);
|
|
898
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
899
|
+
throw new Error("component store directory is not a regular directory");
|
|
900
|
+
chmodSync(path, 448);
|
|
901
|
+
};
|
|
902
|
+
var runtimeMatches = (given, actual) => canonicalArtifactJson(given) === canonicalArtifactJson(actual);
|
|
903
|
+
var readProductionManifestPinCatalog = () => productionComponentManifestCatalog === null ? null : validateManifestCatalog(productionComponentManifestCatalog);
|
|
904
|
+
var compareNumericVersions = (left, right) => {
|
|
905
|
+
const parse3 = (value) => {
|
|
906
|
+
if (!/^(?:0|[1-9]\d*)(?:\.(?:0|[1-9]\d*)){0,2}$/.test(value))
|
|
907
|
+
return void 0;
|
|
908
|
+
const parts = value.split(".").map(Number);
|
|
909
|
+
if (parts.some((part) => !Number.isSafeInteger(part)))
|
|
910
|
+
return void 0;
|
|
911
|
+
return [...parts, 0, 0].slice(0, 3);
|
|
912
|
+
};
|
|
913
|
+
const leftParts = parse3(left);
|
|
914
|
+
const rightParts = parse3(right);
|
|
915
|
+
if (!leftParts || !rightParts)
|
|
916
|
+
return void 0;
|
|
917
|
+
for (let index = 0; index < 3; index++)
|
|
918
|
+
if (leftParts[index] !== rightParts[index])
|
|
919
|
+
return leftParts[index] - rightParts[index];
|
|
920
|
+
return 0;
|
|
921
|
+
};
|
|
922
|
+
var resolvePinnedTarget = (catalog, component, productVersion, target, libcVersion, variant) => {
|
|
923
|
+
if (!catalog) {
|
|
924
|
+
lookupManifestPin(null, { component, productVersion, target, variant });
|
|
925
|
+
throw new Error("Component manifest pin catalog is unavailable");
|
|
926
|
+
}
|
|
927
|
+
if (catalog.productVersion !== productVersion)
|
|
928
|
+
lookupManifestPin(catalog, { component, productVersion, target, variant });
|
|
929
|
+
if (target.libc)
|
|
930
|
+
return target;
|
|
931
|
+
const matches = catalog.entries.filter((entry) => entry.component === component && entry.variant === variant && entry.target.platform === target.platform && entry.target.architecture === target.architecture);
|
|
932
|
+
if (matches.length === 1)
|
|
933
|
+
return matches[0].target;
|
|
934
|
+
const compatibleLibcTargets = matches.filter((entry) => libcVersion !== void 0 && entry.target.libc !== void 0 && (compareNumericVersions(libcVersion, entry.target.libc.minimumVersion) ?? -1) >= 0).sort((left, right) => compareNumericVersions(right.target.libc.minimumVersion, left.target.libc.minimumVersion) ?? 0);
|
|
935
|
+
if (compatibleLibcTargets.length > 0)
|
|
936
|
+
return compatibleLibcTargets[0].target;
|
|
937
|
+
const platformTargets = matches.filter((entry) => !entry.target.libc);
|
|
938
|
+
if (platformTargets.length === 1)
|
|
939
|
+
return platformTargets[0].target;
|
|
940
|
+
if (matches.length > 1)
|
|
941
|
+
throw new Error("Component manifest pin target is ambiguous; exact libc target is required.");
|
|
942
|
+
lookupManifestPin(catalog, { component, productVersion, target, variant });
|
|
943
|
+
throw new Error("Required component manifest pin is missing.");
|
|
944
|
+
};
|
|
945
|
+
var resolveComponentManifestPinTarget = (catalog, input) => {
|
|
946
|
+
const target = resolvePinnedTarget(catalog, input.component, input.productVersion, input.target, input.runtime.libcVersion, input.variant);
|
|
947
|
+
lookupManifestPin(catalog, {
|
|
948
|
+
component: input.component,
|
|
949
|
+
variant: input.variant,
|
|
950
|
+
productVersion: input.productVersion,
|
|
951
|
+
target
|
|
952
|
+
});
|
|
953
|
+
return target;
|
|
954
|
+
};
|
|
955
|
+
var parseJsonFile = (path, maxBytes, requireCanonical = false) => {
|
|
956
|
+
if (constants3.O_NOFOLLOW === void 0)
|
|
957
|
+
throw new Error("component metadata no-follow access is unavailable");
|
|
958
|
+
const descriptor = openSync3(path, constants3.O_RDONLY | constants3.O_NOFOLLOW);
|
|
959
|
+
try {
|
|
960
|
+
const stat = fstatSync2(descriptor);
|
|
961
|
+
if (!stat.isFile() || stat.size > maxBytes)
|
|
962
|
+
throw new Error("component metadata is not a bounded regular file");
|
|
963
|
+
const bytes = readFileSync3(descriptor, "utf8");
|
|
964
|
+
const value = JSON.parse(bytes);
|
|
965
|
+
if (requireCanonical && canonicalArtifactJson(value) !== bytes)
|
|
966
|
+
throw new Error("component metadata is not canonical JSON");
|
|
967
|
+
return value;
|
|
968
|
+
} finally {
|
|
969
|
+
closeSync3(descriptor);
|
|
970
|
+
}
|
|
971
|
+
};
|
|
972
|
+
var readOfflineSource = (source) => {
|
|
973
|
+
const archivePath = resolve3(source.archivePath);
|
|
974
|
+
const archiveStat = lstatSync3(archivePath);
|
|
975
|
+
if (!archiveStat.isFile() || archiveStat.isSymbolicLink() || archiveStat.size > MAX_ARCHIVE_BYTES2)
|
|
976
|
+
throw new Error("component archive is not a bounded regular file");
|
|
977
|
+
return {
|
|
978
|
+
archivePath,
|
|
979
|
+
manifestBytes: readBoundedFile(source.manifestPath, MAX_MANIFEST_BYTES2)
|
|
980
|
+
};
|
|
981
|
+
};
|
|
982
|
+
var readBoundedFile = (path, limit) => {
|
|
983
|
+
const stat = lstatSync3(path);
|
|
984
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > limit)
|
|
985
|
+
throw new Error("component metadata is not a bounded regular file");
|
|
986
|
+
return readFileSync3(path);
|
|
987
|
+
};
|
|
988
|
+
async function fetchBounded(urlText, destination, limit, signal, progress, collectBytes = true, attempt = 0) {
|
|
989
|
+
let url;
|
|
990
|
+
try {
|
|
991
|
+
url = new URL(urlText);
|
|
992
|
+
} catch {
|
|
993
|
+
throw new Error("component source URL is invalid");
|
|
994
|
+
}
|
|
995
|
+
if (url.protocol !== "https:" || url.username || url.password)
|
|
996
|
+
throw new Error("component downloads require safe HTTPS URLs");
|
|
997
|
+
for (let redirects = 0; ; redirects++) {
|
|
998
|
+
aborted(signal);
|
|
999
|
+
let response;
|
|
1000
|
+
const timeoutController = new AbortController();
|
|
1001
|
+
const requestSignal = signal ? AbortSignal.any([signal, timeoutController.signal]) : timeoutController.signal;
|
|
1002
|
+
let timeout = setTimeout(() => timeoutController.abort(), REQUEST_TIMEOUT_MS);
|
|
1003
|
+
const resetIdleTimeout = () => {
|
|
1004
|
+
clearTimeout(timeout);
|
|
1005
|
+
timeout = setTimeout(() => timeoutController.abort(), REQUEST_TIMEOUT_MS);
|
|
1006
|
+
};
|
|
1007
|
+
try {
|
|
1008
|
+
response = await fetch(url, {
|
|
1009
|
+
redirect: "manual",
|
|
1010
|
+
signal: requestSignal
|
|
1011
|
+
});
|
|
1012
|
+
} catch (error) {
|
|
1013
|
+
if (signal?.aborted)
|
|
1014
|
+
throw new Error("component installation cancelled", { cause: error });
|
|
1015
|
+
if (attempt < 2 && error instanceof TypeError) {
|
|
1016
|
+
await new Promise((resolveRetry) => setTimeout(resolveRetry, 100 * (attempt + 1)));
|
|
1017
|
+
return fetchBounded(url.href, destination, limit, signal, progress, collectBytes, attempt + 1);
|
|
1018
|
+
}
|
|
1019
|
+
throw error;
|
|
1020
|
+
} finally {
|
|
1021
|
+
clearTimeout(timeout);
|
|
1022
|
+
}
|
|
1023
|
+
if (response.status >= 500 && attempt < 2) {
|
|
1024
|
+
await cancelBody(response);
|
|
1025
|
+
await new Promise((resolveRetry) => setTimeout(resolveRetry, 100 * (attempt + 1)));
|
|
1026
|
+
return fetchBounded(url.href, destination, limit, signal, progress, collectBytes, attempt + 1);
|
|
1027
|
+
}
|
|
1028
|
+
if ([301, 302, 303, 307, 308].includes(response.status)) {
|
|
1029
|
+
if (redirects >= MAX_REDIRECTS) {
|
|
1030
|
+
await cancelBody(response);
|
|
1031
|
+
throw new Error("too many component redirects");
|
|
1032
|
+
}
|
|
1033
|
+
const location = response.headers.get("location");
|
|
1034
|
+
if (!location) {
|
|
1035
|
+
await cancelBody(response);
|
|
1036
|
+
throw new Error("component redirect has no location");
|
|
1037
|
+
}
|
|
1038
|
+
try {
|
|
1039
|
+
url = new URL(location, url);
|
|
1040
|
+
} catch {
|
|
1041
|
+
await cancelBody(response);
|
|
1042
|
+
throw new Error("component redirect URL is invalid");
|
|
1043
|
+
}
|
|
1044
|
+
if (url.protocol !== "https:" || url.username || url.password) {
|
|
1045
|
+
await cancelBody(response);
|
|
1046
|
+
throw new Error("component redirect requires safe HTTPS");
|
|
1047
|
+
}
|
|
1048
|
+
await cancelBody(response);
|
|
1049
|
+
continue;
|
|
1050
|
+
}
|
|
1051
|
+
if (!response.ok || !response.body) {
|
|
1052
|
+
await cancelBody(response);
|
|
1053
|
+
throw new Error(`component download failed with HTTP ${response.status}`);
|
|
1054
|
+
}
|
|
1055
|
+
const length = Number(response.headers.get("content-length"));
|
|
1056
|
+
if (Number.isFinite(length) && length > limit) {
|
|
1057
|
+
await cancelBody(response);
|
|
1058
|
+
throw new Error("component download exceeds size limit");
|
|
1059
|
+
}
|
|
1060
|
+
let transferredBytes = 0;
|
|
1061
|
+
const chunks = [];
|
|
1062
|
+
const limiter = new Transform({
|
|
1063
|
+
transform(chunk, _encoding, callback) {
|
|
1064
|
+
resetIdleTimeout();
|
|
1065
|
+
transferredBytes += chunk.length;
|
|
1066
|
+
if (transferredBytes > limit) {
|
|
1067
|
+
callback(new Error("component download exceeds size limit"));
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
progress?.({
|
|
1071
|
+
phase: basename2(destination),
|
|
1072
|
+
transferredBytes,
|
|
1073
|
+
...Number.isFinite(length) && length > 0 ? { totalBytes: length } : {}
|
|
1074
|
+
});
|
|
1075
|
+
if (collectBytes)
|
|
1076
|
+
chunks.push(Buffer.from(chunk));
|
|
1077
|
+
callback(null, chunk);
|
|
1078
|
+
}
|
|
1079
|
+
});
|
|
1080
|
+
const output = createWriteStream(destination, { flags: "wx", mode: 384 });
|
|
1081
|
+
resetIdleTimeout();
|
|
1082
|
+
try {
|
|
1083
|
+
await pipeline(Readable2.fromWeb(response.body), limiter, output, { signal: requestSignal });
|
|
1084
|
+
} catch (error) {
|
|
1085
|
+
rmSync3(destination, { force: true });
|
|
1086
|
+
if (signal?.aborted)
|
|
1087
|
+
throw new Error("component installation cancelled", { cause: error });
|
|
1088
|
+
if (attempt < 2 && error instanceof TypeError) {
|
|
1089
|
+
await new Promise((resolveRetry) => setTimeout(resolveRetry, 100 * (attempt + 1)));
|
|
1090
|
+
return fetchBounded(url.href, destination, limit, signal, progress, collectBytes, attempt + 1);
|
|
1091
|
+
}
|
|
1092
|
+
throw error;
|
|
1093
|
+
} finally {
|
|
1094
|
+
clearTimeout(timeout);
|
|
1095
|
+
}
|
|
1096
|
+
return collectBytes ? Buffer.concat(chunks) : void 0;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
async function acquireLock(home, root, signal, fn) {
|
|
1100
|
+
ensureSecureDirectory(home, root);
|
|
1101
|
+
let release;
|
|
1102
|
+
for (let attempt = 0; !release; attempt++) {
|
|
1103
|
+
aborted(signal);
|
|
1104
|
+
assertDirectoryChain(home, root);
|
|
1105
|
+
try {
|
|
1106
|
+
release = acquireDirectoryLock(`${root}.lock`);
|
|
1107
|
+
} catch (error) {
|
|
1108
|
+
if (error.code !== "ELOCKED" || attempt >= 20)
|
|
1109
|
+
throw error;
|
|
1110
|
+
await new Promise((resolveDelay, reject) => {
|
|
1111
|
+
const cleanup = () => signal?.removeEventListener("abort", cancel);
|
|
1112
|
+
const timer = setTimeout(() => {
|
|
1113
|
+
cleanup();
|
|
1114
|
+
resolveDelay();
|
|
1115
|
+
}, 25);
|
|
1116
|
+
const cancel = () => {
|
|
1117
|
+
clearTimeout(timer);
|
|
1118
|
+
cleanup();
|
|
1119
|
+
reject(new Error("component installation cancelled"));
|
|
1120
|
+
};
|
|
1121
|
+
signal?.addEventListener("abort", cancel, { once: true });
|
|
1122
|
+
if (signal?.aborted)
|
|
1123
|
+
cancel();
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
try {
|
|
1128
|
+
aborted(signal);
|
|
1129
|
+
assertDirectoryChain(home, root);
|
|
1130
|
+
return await fn();
|
|
1131
|
+
} finally {
|
|
1132
|
+
release();
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
function targetKey(manifest) {
|
|
1136
|
+
return hash2(canonicalArtifactJson({
|
|
1137
|
+
component: manifest.component,
|
|
1138
|
+
...manifest.variant === void 0 ? {} : { variant: manifest.variant },
|
|
1139
|
+
productVersion: manifest.productVersion,
|
|
1140
|
+
target: manifest.target
|
|
1141
|
+
})).slice(0, 24);
|
|
1142
|
+
}
|
|
1143
|
+
function validateOwner(owner) {
|
|
1144
|
+
if (!isPlainRecord(owner) || !exactKeys(owner, ["kind", "installationId"]) || !["cli", "desktop", "standalone"].includes(String(owner.kind)) || typeof owner.installationId !== "string" || !/^[A-Za-z0-9._-]{1,128}$/.test(owner.installationId))
|
|
1145
|
+
throw new Error("runtime owner is invalid");
|
|
1146
|
+
}
|
|
1147
|
+
function writeIndex(home, indexPath, manifestDigest, tempRoot, signal) {
|
|
1148
|
+
const temporaryIndex = resolve3(tempRoot, `index-${manifestDigest}.json`);
|
|
1149
|
+
writeFileSync2(temporaryIndex, JSON.stringify({ manifestDigest }), {
|
|
1150
|
+
flag: "wx",
|
|
1151
|
+
mode: 384
|
|
1152
|
+
});
|
|
1153
|
+
aborted(signal);
|
|
1154
|
+
assertDirectoryChain(home, dirname2(indexPath));
|
|
1155
|
+
renameSync2(temporaryIndex, indexPath);
|
|
1156
|
+
}
|
|
1157
|
+
function componentRecordPath(root) {
|
|
1158
|
+
return resolve3(root, "component.json");
|
|
1159
|
+
}
|
|
1160
|
+
function componentPayloadPath(root) {
|
|
1161
|
+
return resolve3(root, "payload");
|
|
1162
|
+
}
|
|
1163
|
+
function readComponentRecord(path) {
|
|
1164
|
+
const value = parseJsonFile(path, MAX_MANIFEST_BYTES2 + 32768);
|
|
1165
|
+
if (isPlainRecord(value) && value.schemaVersion === 1)
|
|
1166
|
+
throw new Error("Stored component predates manifest pin admission; reinstall components using a trusted versioned control plane.");
|
|
1167
|
+
if (!isPlainRecord(value) || !exactKeys(value, [
|
|
1168
|
+
"schemaVersion",
|
|
1169
|
+
"manifest",
|
|
1170
|
+
"manifestDigest",
|
|
1171
|
+
"archivePath"
|
|
1172
|
+
]) || value.schemaVersion !== 2 || !isPlainRecord(value.manifest) || typeof value.manifestDigest !== "string" || typeof value.archivePath !== "string")
|
|
1173
|
+
throw new Error("stored component record is invalid");
|
|
1174
|
+
return {
|
|
1175
|
+
schemaVersion: 2,
|
|
1176
|
+
manifest: value.manifest,
|
|
1177
|
+
manifestDigest: value.manifestDigest,
|
|
1178
|
+
archivePath: value.archivePath
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
async function verifyStoredComponent(home, root, expected) {
|
|
1182
|
+
assertDirectoryChain(home, root);
|
|
1183
|
+
const record = readComponentRecord(componentRecordPath(root));
|
|
1184
|
+
validateComponentManifest(record.manifest);
|
|
1185
|
+
const manifestBytes = Buffer.from(canonicalArtifactJson(record.manifest));
|
|
1186
|
+
if (hash2(manifestBytes) !== record.manifestDigest)
|
|
1187
|
+
throw new Error("stored component manifest digest mismatch");
|
|
1188
|
+
const catalog = readProductionManifestPinCatalog();
|
|
1189
|
+
const productVersion = expected.productVersion ?? catalog?.productVersion;
|
|
1190
|
+
if (!productVersion)
|
|
1191
|
+
throw new Error("Component manifest pin catalog is unavailable");
|
|
1192
|
+
const target = resolveComponentManifestPinTarget(catalog, {
|
|
1193
|
+
component: expected.component,
|
|
1194
|
+
variant: record.manifest.variant,
|
|
1195
|
+
productVersion,
|
|
1196
|
+
target: expected.target ?? {
|
|
1197
|
+
platform: expected.runtime.platform,
|
|
1198
|
+
architecture: expected.runtime.architecture
|
|
1199
|
+
},
|
|
1200
|
+
runtime: expected.runtime
|
|
1201
|
+
});
|
|
1202
|
+
const archivePath = resolve3(root, record.archivePath);
|
|
1203
|
+
assertInside(root, archivePath);
|
|
1204
|
+
const manifest = await verifyComponent({
|
|
1205
|
+
manifestBytes,
|
|
1206
|
+
manifestCatalog: catalog,
|
|
1207
|
+
archivePath,
|
|
1208
|
+
expectedComponent: expected.component,
|
|
1209
|
+
expectedVariant: record.manifest.variant,
|
|
1210
|
+
expectedVersion: productVersion,
|
|
1211
|
+
target,
|
|
1212
|
+
runtime: expected.runtime
|
|
1213
|
+
});
|
|
1214
|
+
if (archivePath !== resolve3(root, manifest.archive.name))
|
|
1215
|
+
throw new Error("stored component archive path is invalid");
|
|
1216
|
+
const archiveStat = lstatSync3(archivePath);
|
|
1217
|
+
if (!archiveStat.isFile() || archiveStat.isSymbolicLink())
|
|
1218
|
+
throw new Error("stored component archive is not a regular file");
|
|
1219
|
+
const payload = componentPayloadPath(root);
|
|
1220
|
+
assertDirectoryChain(root, payload);
|
|
1221
|
+
await verifyExtractedComponent(payload, manifest);
|
|
1222
|
+
return {
|
|
1223
|
+
kind: "manifest-pinned-component",
|
|
1224
|
+
manifest,
|
|
1225
|
+
root: payload,
|
|
1226
|
+
manifestDigest: record.manifestDigest
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
async function stageComponentInternal(paths, source, context, runtime) {
|
|
1230
|
+
aborted(context.signal);
|
|
1231
|
+
const storeRoot = resolve3(paths.componentsDir);
|
|
1232
|
+
assertInside(paths.koedHome, storeRoot);
|
|
1233
|
+
assertDirectoryChain(paths.koedHome, storeRoot);
|
|
1234
|
+
ensureSecureDirectory(paths.koedHome, storeRoot);
|
|
1235
|
+
const tempRoot = mkdtempSync(resolve3(storeRoot, ".stage-"));
|
|
1236
|
+
chmodSync(tempRoot, 448);
|
|
1237
|
+
try {
|
|
1238
|
+
const catalog = readProductionManifestPinCatalog();
|
|
1239
|
+
let manifestBytes;
|
|
1240
|
+
if (source.kind === "offline") {
|
|
1241
|
+
manifestBytes = readOfflineSource(source).manifestBytes;
|
|
1242
|
+
} else {
|
|
1243
|
+
manifestBytes = await fetchBounded(source.manifestUrl, resolve3(tempRoot, "manifest.json"), MAX_MANIFEST_BYTES2, context.signal, context.progress);
|
|
1244
|
+
}
|
|
1245
|
+
if (context.target.platform !== runtime.platform || context.target.architecture !== runtime.architecture)
|
|
1246
|
+
throw new Error("component target does not match actual runtime");
|
|
1247
|
+
const target = resolveComponentManifestPinTarget(catalog, {
|
|
1248
|
+
component: context.expectedComponent,
|
|
1249
|
+
variant: context.expectedVariant,
|
|
1250
|
+
productVersion: context.expectedVersion,
|
|
1251
|
+
target: context.target,
|
|
1252
|
+
runtime
|
|
1253
|
+
});
|
|
1254
|
+
const manifest = verifyComponentManifestPin({
|
|
1255
|
+
manifestBytes,
|
|
1256
|
+
manifestCatalog: catalog,
|
|
1257
|
+
expectedComponent: context.expectedComponent,
|
|
1258
|
+
expectedVariant: context.expectedVariant,
|
|
1259
|
+
expectedVersion: context.expectedVersion,
|
|
1260
|
+
target
|
|
1261
|
+
});
|
|
1262
|
+
const archivePath = resolve3(tempRoot, manifest.archive.name);
|
|
1263
|
+
if (source.kind === "offline") {
|
|
1264
|
+
copyFileSync(resolve3(source.archivePath), archivePath, 1);
|
|
1265
|
+
chmodSync(archivePath, 384);
|
|
1266
|
+
} else {
|
|
1267
|
+
await fetchBounded(source.archiveUrl, archivePath, MAX_ARCHIVE_BYTES2, context.signal, context.progress, false);
|
|
1268
|
+
}
|
|
1269
|
+
aborted(context.signal);
|
|
1270
|
+
context.progress?.({ phase: "verify", transferredBytes: 0 });
|
|
1271
|
+
await verifyComponent({
|
|
1272
|
+
manifestBytes,
|
|
1273
|
+
manifestCatalog: catalog,
|
|
1274
|
+
archivePath,
|
|
1275
|
+
expectedComponent: context.expectedComponent,
|
|
1276
|
+
expectedVariant: context.expectedVariant,
|
|
1277
|
+
expectedVersion: context.expectedVersion,
|
|
1278
|
+
target,
|
|
1279
|
+
runtime
|
|
1280
|
+
});
|
|
1281
|
+
aborted(context.signal);
|
|
1282
|
+
const manifestDigest = hash2(manifestBytes);
|
|
1283
|
+
const componentDir = resolve3(storeRoot, manifest.component);
|
|
1284
|
+
assertDirectoryChain(paths.koedHome, componentDir);
|
|
1285
|
+
const indexPath = resolve3(componentDir, `${targetKey(manifest)}.json`);
|
|
1286
|
+
const versionRoot = resolve3(componentDir, manifest.productVersion, manifestDigest);
|
|
1287
|
+
const stagedRoot = resolve3(tempRoot, "verified");
|
|
1288
|
+
const completeRoot = resolve3(tempRoot, "complete");
|
|
1289
|
+
aborted(context.signal);
|
|
1290
|
+
context.progress?.({ phase: "extract", transferredBytes: 0 });
|
|
1291
|
+
await extractVerifiedPackageArchive(archivePath, stagedRoot);
|
|
1292
|
+
aborted(context.signal);
|
|
1293
|
+
await verifyExtractedComponent(stagedRoot, manifest);
|
|
1294
|
+
aborted(context.signal);
|
|
1295
|
+
privateDirectory(completeRoot);
|
|
1296
|
+
renameSync2(stagedRoot, resolve3(completeRoot, "payload"));
|
|
1297
|
+
renameSync2(archivePath, resolve3(completeRoot, manifest.archive.name));
|
|
1298
|
+
writeFileSync2(componentRecordPath(completeRoot), JSON.stringify({
|
|
1299
|
+
schemaVersion: 2,
|
|
1300
|
+
manifest,
|
|
1301
|
+
manifestDigest,
|
|
1302
|
+
archivePath: resolve3(versionRoot, manifest.archive.name)
|
|
1303
|
+
}), { flag: "wx", mode: 384 });
|
|
1304
|
+
return await acquireLock(paths.koedHome, storeRoot, context.signal, async () => {
|
|
1305
|
+
ensureSecureDirectory(paths.koedHome, componentDir);
|
|
1306
|
+
assertDirectoryChain(paths.koedHome, versionRoot);
|
|
1307
|
+
assertDirectoryChain(paths.koedHome, dirname2(indexPath));
|
|
1308
|
+
if (existsSync2(indexPath)) {
|
|
1309
|
+
const index = parseJsonFile(indexPath, 4096);
|
|
1310
|
+
if (!isPlainRecord(index) || index.manifestDigest !== manifestDigest)
|
|
1311
|
+
throw new Error("component version already installed with different digest");
|
|
1312
|
+
}
|
|
1313
|
+
if (existsSync2(versionRoot)) {
|
|
1314
|
+
const cached = await verifyStoredComponent(paths.koedHome, versionRoot, {
|
|
1315
|
+
component: manifest.component,
|
|
1316
|
+
productVersion: manifest.productVersion,
|
|
1317
|
+
target: manifest.target,
|
|
1318
|
+
runtime
|
|
1319
|
+
});
|
|
1320
|
+
aborted(context.signal);
|
|
1321
|
+
if (cached.manifestDigest !== manifestDigest)
|
|
1322
|
+
throw new Error("component version already installed with different digest");
|
|
1323
|
+
if (!existsSync2(indexPath)) {
|
|
1324
|
+
aborted(context.signal);
|
|
1325
|
+
assertDirectoryChain(paths.koedHome, dirname2(indexPath));
|
|
1326
|
+
writeIndex(paths.koedHome, indexPath, manifestDigest, tempRoot, context.signal);
|
|
1327
|
+
}
|
|
1328
|
+
return cached;
|
|
1329
|
+
}
|
|
1330
|
+
if (existsSync2(indexPath))
|
|
1331
|
+
throw new Error("component cache record is missing");
|
|
1332
|
+
ensureSecureDirectory(paths.koedHome, dirname2(versionRoot));
|
|
1333
|
+
let publishedRoot;
|
|
1334
|
+
let committed = false;
|
|
1335
|
+
try {
|
|
1336
|
+
aborted(context.signal);
|
|
1337
|
+
assertDirectoryChain(paths.koedHome, dirname2(versionRoot));
|
|
1338
|
+
renameSync2(completeRoot, versionRoot);
|
|
1339
|
+
publishedRoot = versionRoot;
|
|
1340
|
+
aborted(context.signal);
|
|
1341
|
+
assertDirectoryChain(paths.koedHome, dirname2(indexPath));
|
|
1342
|
+
writeIndex(paths.koedHome, indexPath, manifestDigest, tempRoot, context.signal);
|
|
1343
|
+
committed = true;
|
|
1344
|
+
return {
|
|
1345
|
+
kind: "manifest-pinned-component",
|
|
1346
|
+
manifest,
|
|
1347
|
+
root: componentPayloadPath(versionRoot),
|
|
1348
|
+
manifestDigest
|
|
1349
|
+
};
|
|
1350
|
+
} finally {
|
|
1351
|
+
if (publishedRoot && !committed)
|
|
1352
|
+
rmSync3(publishedRoot, { recursive: true, force: true });
|
|
1353
|
+
}
|
|
1354
|
+
});
|
|
1355
|
+
} finally {
|
|
1356
|
+
rmSync3(tempRoot, { recursive: true, force: true });
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
async function stageComponent(paths, input, context) {
|
|
1360
|
+
const actualRuntime = discoverActualRuntimeIdentity();
|
|
1361
|
+
if (!runtimeMatches(context.runtime, actualRuntime))
|
|
1362
|
+
throw new Error("caller runtime identity does not match actual runtime");
|
|
1363
|
+
return stageComponentInternal(paths, input, context, actualRuntime);
|
|
1364
|
+
}
|
|
1365
|
+
async function stageGeneration(paths, input) {
|
|
1366
|
+
const actualRuntime = discoverActualRuntimeIdentity();
|
|
1367
|
+
validateOwner(input.owner);
|
|
1368
|
+
const { base, privacy } = input;
|
|
1369
|
+
assertDirectoryChain(paths.koedHome, paths.componentsDir);
|
|
1370
|
+
assertDirectoryChain(paths.componentsDir, dirname2(base.root));
|
|
1371
|
+
const baseVerified = await verifyStoredComponent(paths.koedHome, dirname2(base.root), {
|
|
1372
|
+
component: "base",
|
|
1373
|
+
runtime: actualRuntime
|
|
1374
|
+
});
|
|
1375
|
+
if (baseVerified.manifestDigest !== base.manifestDigest || resolve3(baseVerified.root) !== resolve3(base.root))
|
|
1376
|
+
throw new Error("base component is not a verified store entry");
|
|
1377
|
+
let privacyVerified;
|
|
1378
|
+
if (privacy) {
|
|
1379
|
+
assertDirectoryChain(paths.componentsDir, dirname2(privacy.root));
|
|
1380
|
+
privacyVerified = await verifyStoredComponent(paths.koedHome, dirname2(privacy.root), {
|
|
1381
|
+
component: "privacy",
|
|
1382
|
+
runtime: actualRuntime
|
|
1383
|
+
});
|
|
1384
|
+
if (privacyVerified.manifestDigest !== privacy.manifestDigest || resolve3(privacyVerified.root) !== resolve3(privacy.root))
|
|
1385
|
+
throw new Error("privacy component is not a verified store entry");
|
|
1386
|
+
}
|
|
1387
|
+
if (privacyVerified && baseVerified.manifest.productVersion !== privacyVerified.manifest.productVersion)
|
|
1388
|
+
throw new Error("mixed component versions");
|
|
1389
|
+
if (privacyVerified && canonicalArtifactJson(baseVerified.manifest.target) !== canonicalArtifactJson(privacyVerified.manifest.target))
|
|
1390
|
+
throw new Error("mixed component targets");
|
|
1391
|
+
const identity = {
|
|
1392
|
+
schemaVersion: 1,
|
|
1393
|
+
productVersion: baseVerified.manifest.productVersion,
|
|
1394
|
+
owner: input.owner,
|
|
1395
|
+
base: {
|
|
1396
|
+
manifestDigest: baseVerified.manifestDigest,
|
|
1397
|
+
root: relative3(paths.koedHome, baseVerified.root)
|
|
1398
|
+
},
|
|
1399
|
+
...privacyVerified ? {
|
|
1400
|
+
privacy: {
|
|
1401
|
+
manifestDigest: privacyVerified.manifestDigest,
|
|
1402
|
+
root: relative3(paths.koedHome, privacyVerified.root)
|
|
1403
|
+
}
|
|
1404
|
+
} : {}
|
|
1405
|
+
};
|
|
1406
|
+
const id = hash2(canonicalArtifactJson(identity));
|
|
1407
|
+
const generationRoot = resolve3(paths.generationsDir, id);
|
|
1408
|
+
assertDirectoryChain(paths.koedHome, generationRoot);
|
|
1409
|
+
assertDirectoryChain(paths.koedHome, paths.generationsDir);
|
|
1410
|
+
return acquireLock(paths.koedHome, paths.generationsDir, void 0, async () => {
|
|
1411
|
+
if (existsSync2(generationRoot))
|
|
1412
|
+
return readStagedGeneration(paths, id);
|
|
1413
|
+
ensureSecureDirectory(paths.koedHome, paths.generationsDir);
|
|
1414
|
+
const tempRoot = mkdtempSync(resolve3(paths.generationsDir, ".stage-"));
|
|
1415
|
+
chmodSync(tempRoot, 448);
|
|
1416
|
+
try {
|
|
1417
|
+
const freshBase = await verifyStoredComponent(paths.koedHome, dirname2(baseVerified.root), {
|
|
1418
|
+
component: "base",
|
|
1419
|
+
runtime: actualRuntime
|
|
1420
|
+
});
|
|
1421
|
+
const freshPrivacy = privacyVerified ? await verifyStoredComponent(paths.koedHome, dirname2(privacyVerified.root), {
|
|
1422
|
+
component: "privacy",
|
|
1423
|
+
runtime: actualRuntime
|
|
1424
|
+
}) : void 0;
|
|
1425
|
+
const record = {
|
|
1426
|
+
...identity,
|
|
1427
|
+
id,
|
|
1428
|
+
components: {
|
|
1429
|
+
base: {
|
|
1430
|
+
manifest: freshBase.manifest,
|
|
1431
|
+
manifestDigest: freshBase.manifestDigest
|
|
1432
|
+
},
|
|
1433
|
+
...privacyVerified ? {
|
|
1434
|
+
privacy: {
|
|
1435
|
+
manifest: freshPrivacy.manifest,
|
|
1436
|
+
manifestDigest: freshPrivacy.manifestDigest
|
|
1437
|
+
}
|
|
1438
|
+
} : {}
|
|
1439
|
+
}
|
|
1440
|
+
};
|
|
1441
|
+
const recordBytes = Buffer.from(canonicalArtifactJson(record));
|
|
1442
|
+
if (recordBytes.length > MAX_GENERATION_RECORD_BYTES)
|
|
1443
|
+
throw new Error("generation record exceeds size limit");
|
|
1444
|
+
writeFileSync2(resolve3(tempRoot, "generation.json"), recordBytes, {
|
|
1445
|
+
flag: "wx",
|
|
1446
|
+
mode: 384
|
|
1447
|
+
});
|
|
1448
|
+
assertDirectoryChain(paths.koedHome, paths.generationsDir);
|
|
1449
|
+
assertDirectoryChain(paths.generationsDir, generationRoot);
|
|
1450
|
+
renameSync2(tempRoot, generationRoot);
|
|
1451
|
+
return {
|
|
1452
|
+
id,
|
|
1453
|
+
productVersion: identity.productVersion,
|
|
1454
|
+
base: baseVerified,
|
|
1455
|
+
...privacyVerified ? { privacy: privacyVerified } : {},
|
|
1456
|
+
owner: input.owner
|
|
1457
|
+
};
|
|
1458
|
+
} finally {
|
|
1459
|
+
if (existsSync2(tempRoot))
|
|
1460
|
+
rmSync3(tempRoot, { recursive: true, force: true });
|
|
1461
|
+
}
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1464
|
+
async function readStoredComponents(paths, runtime) {
|
|
1465
|
+
const components = [];
|
|
1466
|
+
for (const component of ["base", "privacy"]) {
|
|
1467
|
+
const componentRoot = resolve3(paths.componentsDir, component);
|
|
1468
|
+
if (!existsSync2(componentRoot))
|
|
1469
|
+
continue;
|
|
1470
|
+
assertDirectoryChain(paths.koedHome, componentRoot);
|
|
1471
|
+
for (const versionEntry of readdirSync3(componentRoot, {
|
|
1472
|
+
withFileTypes: true
|
|
1473
|
+
})) {
|
|
1474
|
+
if (!versionEntry.isDirectory() || !/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:\.(?:0|[1-9]\d*))?(?:-[0-9A-Za-z.-]+)?$/.test(versionEntry.name))
|
|
1475
|
+
continue;
|
|
1476
|
+
const versionRoot = resolve3(componentRoot, versionEntry.name);
|
|
1477
|
+
for (const digestEntry of readdirSync3(versionRoot, {
|
|
1478
|
+
withFileTypes: true
|
|
1479
|
+
})) {
|
|
1480
|
+
if (!digestEntry.isDirectory() || !/^[a-f0-9]{64}$/.test(digestEntry.name))
|
|
1481
|
+
continue;
|
|
1482
|
+
try {
|
|
1483
|
+
components.push(await verifyStoredComponent(paths.koedHome, resolve3(versionRoot, digestEntry.name), {
|
|
1484
|
+
component,
|
|
1485
|
+
productVersion: versionEntry.name,
|
|
1486
|
+
runtime
|
|
1487
|
+
}));
|
|
1488
|
+
} catch (error) {
|
|
1489
|
+
if (error instanceof Error && error.message.startsWith("Stored component predates manifest pin admission;"))
|
|
1490
|
+
throw error;
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
return components;
|
|
1496
|
+
}
|
|
1497
|
+
var readDesktopPrivacyGeneration = async (paths, id, record) => {
|
|
1498
|
+
if (!exactKeys(record, [
|
|
1499
|
+
"schemaVersion",
|
|
1500
|
+
"kind",
|
|
1501
|
+
"id",
|
|
1502
|
+
"productVersion",
|
|
1503
|
+
"owner",
|
|
1504
|
+
"resourcesPath",
|
|
1505
|
+
"bundleDigest",
|
|
1506
|
+
"privacy"
|
|
1507
|
+
]) || record.schemaVersion !== 1 || record.kind !== "desktop-bundle-with-privacy" || record.id !== id || typeof record.productVersion !== "string" || typeof record.resourcesPath !== "string" || typeof record.bundleDigest !== "string" || !/^[a-f0-9]{64}$/.test(record.bundleDigest) || !isPlainRecord(record.privacy) || !exactKeys(record.privacy, ["root", "manifestDigest"]) || typeof record.privacy.root !== "string" || typeof record.privacy.manifestDigest !== "string")
|
|
1508
|
+
throw new Error("stored Desktop privacy generation record is invalid");
|
|
1509
|
+
validateOwner(record.owner);
|
|
1510
|
+
const bundle = verifyDesktopRuntimeBundle(record.resourcesPath);
|
|
1511
|
+
if (bundle.productVersion !== record.productVersion || bundle.bundleDigest !== record.bundleDigest)
|
|
1512
|
+
throw new Error("Desktop runtime bundle changed after generation staging");
|
|
1513
|
+
const runtime = discoverActualRuntimeIdentity();
|
|
1514
|
+
const privacyRoot = resolve3(paths.koedHome, record.privacy.root);
|
|
1515
|
+
assertInside(paths.koedHome, privacyRoot);
|
|
1516
|
+
const privacy = await verifyStoredComponent(paths.koedHome, dirname2(privacyRoot), {
|
|
1517
|
+
component: "privacy",
|
|
1518
|
+
productVersion: bundle.productVersion,
|
|
1519
|
+
target: bundle.target,
|
|
1520
|
+
runtime
|
|
1521
|
+
});
|
|
1522
|
+
if (privacy.root !== privacyRoot || privacy.manifestDigest !== record.privacy.manifestDigest)
|
|
1523
|
+
throw new Error("Desktop privacy generation differs from verified component");
|
|
1524
|
+
const identity = {
|
|
1525
|
+
schemaVersion: 1,
|
|
1526
|
+
kind: "desktop-bundle-with-privacy",
|
|
1527
|
+
productVersion: bundle.productVersion,
|
|
1528
|
+
owner: record.owner,
|
|
1529
|
+
resourcesPath: bundle.resourcesPath,
|
|
1530
|
+
bundleDigest: bundle.bundleDigest,
|
|
1531
|
+
privacy: {
|
|
1532
|
+
root: relative3(paths.koedHome, privacy.root),
|
|
1533
|
+
manifestDigest: privacy.manifestDigest
|
|
1534
|
+
}
|
|
1535
|
+
};
|
|
1536
|
+
if (hash2(canonicalArtifactJson(identity)) !== id)
|
|
1537
|
+
throw new Error("Desktop privacy generation digest mismatch");
|
|
1538
|
+
return {
|
|
1539
|
+
id,
|
|
1540
|
+
productVersion: bundle.productVersion,
|
|
1541
|
+
base: {
|
|
1542
|
+
kind: "desktop-bundle",
|
|
1543
|
+
manifest: bundle.manifest,
|
|
1544
|
+
root: resolve3(bundle.resourcesPath, "koed-runtime"),
|
|
1545
|
+
manifestDigest: bundle.bundleDigest
|
|
1546
|
+
},
|
|
1547
|
+
privacy,
|
|
1548
|
+
owner: record.owner
|
|
1549
|
+
};
|
|
1550
|
+
};
|
|
1551
|
+
function readGenerationRecord(paths, id) {
|
|
1552
|
+
if (!/^[a-f0-9]{64}$/.test(id))
|
|
1553
|
+
throw new Error("generation id is invalid");
|
|
1554
|
+
assertDirectoryChain(paths.koedHome, paths.generationsDir);
|
|
1555
|
+
const root = resolve3(paths.generationsDir, id);
|
|
1556
|
+
assertDirectoryChain(paths.generationsDir, root);
|
|
1557
|
+
return parseJsonFile(resolve3(root, "generation.json"), MAX_GENERATION_RECORD_BYTES, true);
|
|
1558
|
+
}
|
|
1559
|
+
function assertOrderingStoreMetadata(root, manifest, digest) {
|
|
1560
|
+
const stored = readComponentRecord(componentRecordPath(root));
|
|
1561
|
+
if (stored.manifestDigest !== digest || canonicalArtifactJson(stored.manifest) !== canonicalArtifactJson(manifest) || stored.archivePath !== resolve3(root, manifest.archive.name))
|
|
1562
|
+
throw new Error("generation component metadata differs from store");
|
|
1563
|
+
}
|
|
1564
|
+
function readOrderingComponent(paths, component, version, reference, embedded) {
|
|
1565
|
+
if (!isPlainRecord(reference) || !exactKeys(reference, ["root", "manifestDigest"]) || typeof reference.root !== "string" || !isPlainRecord(embedded) || !exactKeys(embedded, ["manifest", "manifestDigest"]))
|
|
1566
|
+
throw new Error("stored generation component reference is invalid");
|
|
1567
|
+
validateComponentManifest(embedded.manifest);
|
|
1568
|
+
const manifest = embedded.manifest;
|
|
1569
|
+
const digest = hash2(canonicalArtifactJson(manifest));
|
|
1570
|
+
if (manifest.component !== component || manifest.productVersion !== version || reference.manifestDigest !== digest || embedded.manifestDigest !== digest)
|
|
1571
|
+
throw new Error("generation component identity mismatch");
|
|
1572
|
+
const componentRoot = resolve3(paths.componentsDir, component, version, digest);
|
|
1573
|
+
const payload = componentPayloadPath(componentRoot);
|
|
1574
|
+
assertInside(paths.koedHome, componentRoot);
|
|
1575
|
+
if (reference.root !== relative3(paths.koedHome, payload))
|
|
1576
|
+
throw new Error("stored generation root is invalid");
|
|
1577
|
+
assertDirectoryChain(paths.koedHome, payload);
|
|
1578
|
+
assertOrderingStoreMetadata(componentRoot, manifest, digest);
|
|
1579
|
+
return manifest;
|
|
1580
|
+
}
|
|
1581
|
+
function parseOrderingRecord(record, id) {
|
|
1582
|
+
if (!isPlainRecord(record) || !exactKeys(record, [
|
|
1583
|
+
"schemaVersion",
|
|
1584
|
+
"productVersion",
|
|
1585
|
+
"owner",
|
|
1586
|
+
"base",
|
|
1587
|
+
"components",
|
|
1588
|
+
"id",
|
|
1589
|
+
...Object.hasOwn(record, "privacy") ? ["privacy"] : []
|
|
1590
|
+
]) || record.schemaVersion !== 1 || record.id !== id || typeof record.productVersion !== "string" || !isPlainRecord(record.components) || !exactKeys(record.components, [
|
|
1591
|
+
"base",
|
|
1592
|
+
...Object.hasOwn(record, "privacy") ? ["privacy"] : []
|
|
1593
|
+
]))
|
|
1594
|
+
throw new Error("stored generation record is invalid");
|
|
1595
|
+
validateOwner(record.owner);
|
|
1596
|
+
return {
|
|
1597
|
+
...record,
|
|
1598
|
+
productVersion: record.productVersion,
|
|
1599
|
+
owner: record.owner,
|
|
1600
|
+
components: record.components
|
|
1601
|
+
};
|
|
1602
|
+
}
|
|
1603
|
+
function readConsistentOrderingRecord(paths, id) {
|
|
1604
|
+
const record = parseOrderingRecord(readGenerationRecord(paths, id), id);
|
|
1605
|
+
const base = readOrderingComponent(paths, "base", record.productVersion, record.base, record.components.base);
|
|
1606
|
+
if (Object.hasOwn(record, "privacy")) {
|
|
1607
|
+
const privacy = readOrderingComponent(paths, "privacy", record.productVersion, record.privacy, record.components.privacy);
|
|
1608
|
+
if (canonicalArtifactJson(privacy.target) !== canonicalArtifactJson(base.target))
|
|
1609
|
+
throw new Error("generation component target mismatch");
|
|
1610
|
+
}
|
|
1611
|
+
const identity = {
|
|
1612
|
+
schemaVersion: 1,
|
|
1613
|
+
productVersion: record.productVersion,
|
|
1614
|
+
owner: record.owner,
|
|
1615
|
+
base: record.base,
|
|
1616
|
+
...Object.hasOwn(record, "privacy") ? { privacy: record.privacy } : {}
|
|
1617
|
+
};
|
|
1618
|
+
if (hash2(canonicalArtifactJson(identity)) !== id)
|
|
1619
|
+
throw new Error("generation digest mismatch");
|
|
1620
|
+
return record;
|
|
1621
|
+
}
|
|
1622
|
+
function readGenerationComponentPresence(paths, id) {
|
|
1623
|
+
const record = readConsistentOrderingRecord(paths, id);
|
|
1624
|
+
return Object.freeze(Object.hasOwn(record, "privacy") ? ["base", "privacy"] : ["base"]);
|
|
1625
|
+
}
|
|
1626
|
+
function readGenerationOrderingEvidence(paths, id) {
|
|
1627
|
+
const record = readConsistentOrderingRecord(paths, id);
|
|
1628
|
+
return Object.freeze({
|
|
1629
|
+
kind: "generation-ordering-evidence",
|
|
1630
|
+
id,
|
|
1631
|
+
productVersion: record.productVersion,
|
|
1632
|
+
owner: Object.freeze({ ...record.owner })
|
|
1633
|
+
});
|
|
1634
|
+
}
|
|
1635
|
+
async function readStagedGeneration(paths, id) {
|
|
1636
|
+
const record = readGenerationRecord(paths, id);
|
|
1637
|
+
if (isPlainRecord(record) && record.kind === "desktop-bundle-with-privacy")
|
|
1638
|
+
return readDesktopPrivacyGeneration(paths, id, record);
|
|
1639
|
+
if (!isPlainRecord(record) || !exactKeys(record, [
|
|
1640
|
+
"schemaVersion",
|
|
1641
|
+
"productVersion",
|
|
1642
|
+
"owner",
|
|
1643
|
+
"base",
|
|
1644
|
+
"components",
|
|
1645
|
+
"id",
|
|
1646
|
+
...Object.hasOwn(record, "privacy") ? ["privacy"] : []
|
|
1647
|
+
]) || record.schemaVersion !== 1 || record.id !== id || typeof record.productVersion !== "string" || !isPlainRecord(record.base) || !exactKeys(record.base, ["manifestDigest", "root"]) || !isPlainRecord(record.components) || Object.hasOwn(record, "privacy") !== Object.hasOwn(record.components, "privacy") || !exactKeys(record.components, [
|
|
1648
|
+
"base",
|
|
1649
|
+
...Object.hasOwn(record.components, "privacy") ? ["privacy"] : []
|
|
1650
|
+
]) || Object.hasOwn(record, "privacy") && !isPlainRecord(record.privacy))
|
|
1651
|
+
throw new Error("stored generation record is invalid");
|
|
1652
|
+
validateOwner(record.owner);
|
|
1653
|
+
const runtime = discoverActualRuntimeIdentity();
|
|
1654
|
+
const baseRelative = record.base.root;
|
|
1655
|
+
if (typeof baseRelative !== "string")
|
|
1656
|
+
throw new Error("stored generation root is invalid");
|
|
1657
|
+
const baseRoot = resolve3(paths.koedHome, baseRelative);
|
|
1658
|
+
assertInside(paths.koedHome, baseRoot);
|
|
1659
|
+
const base = await verifyStoredComponent(paths.koedHome, dirname2(baseRoot), {
|
|
1660
|
+
component: "base",
|
|
1661
|
+
runtime
|
|
1662
|
+
});
|
|
1663
|
+
if (base.root !== baseRoot || base.manifestDigest !== record.base.manifestDigest || !isPlainRecord(record.components.base) || !exactKeys(record.components.base, ["manifest", "manifestDigest"]) || record.components.base.manifestDigest !== base.manifestDigest || canonicalArtifactJson(record.components.base.manifest) !== canonicalArtifactJson(base.manifest))
|
|
1664
|
+
throw new Error("generation base reference differs from verified component");
|
|
1665
|
+
let privacy;
|
|
1666
|
+
if (Object.hasOwn(record, "privacy")) {
|
|
1667
|
+
const reference = record.privacy;
|
|
1668
|
+
if (!isPlainRecord(reference) || !exactKeys(reference, ["manifestDigest", "root"]) || typeof reference.root !== "string")
|
|
1669
|
+
throw new Error("stored generation privacy reference is invalid");
|
|
1670
|
+
const privacyRoot = resolve3(paths.koedHome, reference.root);
|
|
1671
|
+
assertInside(paths.koedHome, privacyRoot);
|
|
1672
|
+
privacy = await verifyStoredComponent(paths.koedHome, dirname2(privacyRoot), {
|
|
1673
|
+
component: "privacy",
|
|
1674
|
+
runtime
|
|
1675
|
+
});
|
|
1676
|
+
if (privacy.root !== privacyRoot || privacy.manifestDigest !== reference.manifestDigest || !isPlainRecord(record.components.privacy) || !exactKeys(record.components.privacy, ["manifest", "manifestDigest"]) || record.components.privacy.manifestDigest !== privacy.manifestDigest || canonicalArtifactJson(record.components.privacy.manifest) !== canonicalArtifactJson(privacy.manifest) || privacy.manifest.productVersion !== base.manifest.productVersion || canonicalArtifactJson(privacy.manifest.target) !== canonicalArtifactJson(base.manifest.target))
|
|
1677
|
+
throw new Error("generation privacy reference differs from verified component");
|
|
1678
|
+
}
|
|
1679
|
+
if (record.productVersion !== base.manifest.productVersion)
|
|
1680
|
+
throw new Error("generation product version differs from verified component");
|
|
1681
|
+
const identity = {
|
|
1682
|
+
schemaVersion: 1,
|
|
1683
|
+
productVersion: base.manifest.productVersion,
|
|
1684
|
+
owner: record.owner,
|
|
1685
|
+
base: {
|
|
1686
|
+
manifestDigest: base.manifestDigest,
|
|
1687
|
+
root: relative3(paths.koedHome, base.root)
|
|
1688
|
+
},
|
|
1689
|
+
...privacy ? {
|
|
1690
|
+
privacy: {
|
|
1691
|
+
manifestDigest: privacy.manifestDigest,
|
|
1692
|
+
root: relative3(paths.koedHome, privacy.root)
|
|
1693
|
+
}
|
|
1694
|
+
} : {}
|
|
1695
|
+
};
|
|
1696
|
+
if (hash2(canonicalArtifactJson(identity)) !== id)
|
|
1697
|
+
throw new Error("generation digest mismatch");
|
|
1698
|
+
return {
|
|
1699
|
+
id,
|
|
1700
|
+
productVersion: base.manifest.productVersion,
|
|
1701
|
+
base,
|
|
1702
|
+
...privacy ? { privacy } : {},
|
|
1703
|
+
owner: record.owner
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
// packages/koed-server/dist/generation-lifecycle.js
|
|
1708
|
+
import { createHash as createHash4, randomBytes } from "node:crypto";
|
|
1709
|
+
import { existsSync as existsSync3, closeSync as closeSync5, constants as constants4, fstatSync as fstatSync3, openSync as openSync5, readSync as readSync2, lstatSync as lstatSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync5, readdirSync as readdirSync4, renameSync as renameSync4, rmSync as rmSync5, statSync, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1710
|
+
import { dirname as dirname3, isAbsolute as isAbsolute4, parse as parse2, relative as relative4, resolve as resolve5, sep as sep4 } from "node:path";
|
|
1711
|
+
|
|
1712
|
+
// packages/koed-server/dist/supervisor-lock.js
|
|
1713
|
+
import { closeSync as closeSync4, mkdirSync as mkdirSync4, openSync as openSync4, readFileSync as readFileSync4, rmSync as rmSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1714
|
+
import { spawnSync } from "node:child_process";
|
|
1715
|
+
import { hostname } from "node:os";
|
|
1716
|
+
import { resolve as resolve4 } from "node:path";
|
|
1717
|
+
var processIsRunning = (pid) => {
|
|
1718
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
1719
|
+
return false;
|
|
1720
|
+
try {
|
|
1721
|
+
process.kill(pid, 0);
|
|
1722
|
+
return true;
|
|
1723
|
+
} catch (error) {
|
|
1724
|
+
const code = error.code;
|
|
1725
|
+
return code !== "ESRCH";
|
|
1726
|
+
}
|
|
1727
|
+
};
|
|
1728
|
+
var resolveProcessIdentity = (pid) => {
|
|
1729
|
+
if (!processIsRunning(pid))
|
|
1730
|
+
return null;
|
|
1731
|
+
try {
|
|
1732
|
+
if (process.platform === "linux") {
|
|
1733
|
+
const stat = readFileSync4(`/proc/${pid}/stat`, "utf8");
|
|
1734
|
+
const commandEnd = stat.lastIndexOf(")");
|
|
1735
|
+
if (commandEnd < 0)
|
|
1736
|
+
return null;
|
|
1737
|
+
const fields = stat.slice(commandEnd + 2).trim().split(/\s+/);
|
|
1738
|
+
const startTime = fields[19];
|
|
1739
|
+
if (!startTime)
|
|
1740
|
+
return null;
|
|
1741
|
+
const bootId = readFileSync4("/proc/sys/kernel/random/boot_id", "utf8").trim();
|
|
1742
|
+
return `${hostname()}:linux:${bootId}:${startTime}`;
|
|
1743
|
+
}
|
|
1744
|
+
if (process.platform === "win32") {
|
|
1745
|
+
const result2 = spawnSync("powershell.exe", [
|
|
1746
|
+
"-NoProfile",
|
|
1747
|
+
"-NonInteractive",
|
|
1748
|
+
"-Command",
|
|
1749
|
+
`(Get-Process -Id ${pid} -ErrorAction Stop).StartTime.ToUniversalTime().Ticks`
|
|
1750
|
+
], { encoding: "utf8", windowsHide: true, timeout: 2e3 });
|
|
1751
|
+
const startedAt2 = result2.status === 0 ? result2.stdout.trim() : "";
|
|
1752
|
+
return startedAt2 ? `${hostname()}:win32:${startedAt2}` : null;
|
|
1753
|
+
}
|
|
1754
|
+
const result = spawnSync("ps", ["-o", "lstart=", "-p", String(pid)], {
|
|
1755
|
+
encoding: "utf8",
|
|
1756
|
+
timeout: 2e3
|
|
1757
|
+
});
|
|
1758
|
+
const startedAt = result.status === 0 ? result.stdout.trim() : "";
|
|
1759
|
+
return startedAt ? `${hostname()}:${process.platform}:${startedAt}` : null;
|
|
1760
|
+
} catch {
|
|
1761
|
+
return null;
|
|
1762
|
+
}
|
|
1763
|
+
};
|
|
1764
|
+
var readSupervisorLock = (lockPath2) => {
|
|
1765
|
+
try {
|
|
1766
|
+
const value = JSON.parse(readFileSync4(lockPath2, "utf8"));
|
|
1767
|
+
return Number.isInteger(value.pid) && Number(value.pid) > 0 ? {
|
|
1768
|
+
pid: Number(value.pid),
|
|
1769
|
+
acquiredAt: String(value.acquiredAt ?? "unknown"),
|
|
1770
|
+
...typeof value.processIdentity === "string" && value.processIdentity.length > 0 ? { processIdentity: value.processIdentity } : {}
|
|
1771
|
+
} : null;
|
|
1772
|
+
} catch {
|
|
1773
|
+
return null;
|
|
1774
|
+
}
|
|
1775
|
+
};
|
|
1776
|
+
var acquireKoedServerSupervisorLock = (paths, options = {}) => {
|
|
1777
|
+
const pid = options.pid ?? process.pid;
|
|
1778
|
+
const isRunning = options.isProcessRunning ?? processIsRunning;
|
|
1779
|
+
const identify = options.resolveProcessIdentity ?? resolveProcessIdentity;
|
|
1780
|
+
const processIdentity = identify(pid);
|
|
1781
|
+
if (!processIdentity) {
|
|
1782
|
+
throw new Error(`Could not resolve process identity for PID ${pid}.`);
|
|
1783
|
+
}
|
|
1784
|
+
const lockPath2 = resolve4(paths.runDir, "koed-server.lock");
|
|
1785
|
+
mkdirSync4(paths.runDir, { recursive: true, mode: 448 });
|
|
1786
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
1787
|
+
try {
|
|
1788
|
+
const descriptor = openSync4(lockPath2, "wx", 384);
|
|
1789
|
+
try {
|
|
1790
|
+
writeFileSync3(descriptor, `${JSON.stringify({
|
|
1791
|
+
pid,
|
|
1792
|
+
acquiredAt: (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
1793
|
+
processIdentity
|
|
1794
|
+
}, null, 2)}
|
|
1795
|
+
`);
|
|
1796
|
+
} finally {
|
|
1797
|
+
closeSync4(descriptor);
|
|
1798
|
+
}
|
|
1799
|
+
return { acquired: true, lockPath: lockPath2, ownerPid: pid, processIdentity };
|
|
1800
|
+
} catch (error) {
|
|
1801
|
+
if (error.code !== "EEXIST")
|
|
1802
|
+
throw error;
|
|
1803
|
+
const owner2 = readSupervisorLock(lockPath2);
|
|
1804
|
+
if (!owner2)
|
|
1805
|
+
return { acquired: false, lockPath: lockPath2 };
|
|
1806
|
+
if (isRunning(owner2.pid)) {
|
|
1807
|
+
const ownerIdentity = identify(owner2.pid);
|
|
1808
|
+
if (!ownerIdentity && isRunning(owner2.pid)) {
|
|
1809
|
+
return { acquired: false, lockPath: lockPath2, ownerPid: owner2.pid };
|
|
1810
|
+
}
|
|
1811
|
+
if (ownerIdentity) {
|
|
1812
|
+
if (!owner2.processIdentity && owner2.pid !== pid) {
|
|
1813
|
+
return { acquired: false, lockPath: lockPath2, ownerPid: owner2.pid };
|
|
1814
|
+
}
|
|
1815
|
+
if (owner2.processIdentity && ownerIdentity === owner2.processIdentity) {
|
|
1816
|
+
return { acquired: false, lockPath: lockPath2, ownerPid: owner2.pid };
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
rmSync4(lockPath2, { force: true });
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
const owner = readSupervisorLock(lockPath2);
|
|
1824
|
+
return {
|
|
1825
|
+
acquired: false,
|
|
1826
|
+
lockPath: lockPath2,
|
|
1827
|
+
...owner ? { ownerPid: owner.pid } : {}
|
|
1828
|
+
};
|
|
1829
|
+
};
|
|
1830
|
+
var releaseKoedServerSupervisorLock = (lock, pid = process.pid) => {
|
|
1831
|
+
const owner = readSupervisorLock(lock.lockPath);
|
|
1832
|
+
if (!lock.acquired || owner?.pid !== pid || !lock.processIdentity || owner.processIdentity !== lock.processIdentity)
|
|
1833
|
+
return false;
|
|
1834
|
+
rmSync4(lock.lockPath, { force: true });
|
|
1835
|
+
return true;
|
|
1836
|
+
};
|
|
1837
|
+
|
|
1838
|
+
// packages/koed-server/dist/generation-lifecycle-filesystem.js
|
|
1839
|
+
import { renameSync as renameSync3 } from "node:fs";
|
|
1840
|
+
var renameAtomically = (source, destination) => renameSync3(source, destination);
|
|
1841
|
+
|
|
1842
|
+
// packages/koed-server/dist/generation-lifecycle.js
|
|
1843
|
+
var generationStatePath = (paths) => paths.generationStatePath ?? resolve5(paths.runDir, "runtime-generation.json");
|
|
1844
|
+
var lockPath = (paths) => resolve5(paths.runDir, "generation-lifecycle.lock");
|
|
1845
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1846
|
+
var isRuntimeOwnerRecord = (value) => isRecord(value) && Object.keys(value).length === 2 && ["cli", "desktop", "standalone"].includes(String(value.kind)) && typeof value.installationId === "string" && /^[A-Za-z0-9._-]{1,128}$/.test(value.installationId);
|
|
1847
|
+
var readRegularFile = (path) => {
|
|
1848
|
+
const stat = lstatSync4(path);
|
|
1849
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
1850
|
+
throw new Error("runtime lifecycle state is not a regular file");
|
|
1851
|
+
return readFileSync5(path, "utf8");
|
|
1852
|
+
};
|
|
1853
|
+
var sameOwner = (left, right) => left.kind === right.kind && left.installationId === right.installationId;
|
|
1854
|
+
var assertDirectoryChain2 = (base, target) => {
|
|
1855
|
+
const relativePath = relative4(resolve5(base), resolve5(target));
|
|
1856
|
+
if (relativePath.startsWith("..") || isAbsolute4(relativePath))
|
|
1857
|
+
throw new Error("runtime lifecycle path escaped KOED_HOME");
|
|
1858
|
+
const absolute = resolve5(target);
|
|
1859
|
+
let cursor = parse2(absolute).root;
|
|
1860
|
+
for (const part of absolute.slice(cursor.length).split(sep4).filter(Boolean)) {
|
|
1861
|
+
cursor = resolve5(cursor, part);
|
|
1862
|
+
try {
|
|
1863
|
+
const stat = lstatSync4(cursor);
|
|
1864
|
+
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
1865
|
+
throw new Error("runtime lifecycle path contains a symbolic link or non-directory");
|
|
1866
|
+
} catch (error) {
|
|
1867
|
+
if (error.code === "ENOENT")
|
|
1868
|
+
return;
|
|
1869
|
+
throw error;
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
};
|
|
1873
|
+
var processIsRunning2 = (pid) => {
|
|
1874
|
+
try {
|
|
1875
|
+
process.kill(pid, 0);
|
|
1876
|
+
return true;
|
|
1877
|
+
} catch (error) {
|
|
1878
|
+
return error.code !== "ESRCH";
|
|
1879
|
+
}
|
|
1880
|
+
};
|
|
1881
|
+
var readLockRecord = (path) => {
|
|
1882
|
+
try {
|
|
1883
|
+
const parsed = JSON.parse(readRegularFile(resolve5(path, "owner.json")));
|
|
1884
|
+
if (!isRecord(parsed))
|
|
1885
|
+
return null;
|
|
1886
|
+
const record = parsed;
|
|
1887
|
+
if (Object.keys(record).length !== 3 || typeof record.pid !== "number" || !Number.isInteger(record.pid) || record.pid <= 0 || typeof record.processIdentity !== "string" || record.processIdentity.length === 0 || typeof record.token !== "string" || !/^[a-f0-9]{64}$/.test(record.token))
|
|
1888
|
+
return null;
|
|
1889
|
+
return {
|
|
1890
|
+
pid: Number(record.pid),
|
|
1891
|
+
processIdentity: record.processIdentity,
|
|
1892
|
+
token: record.token
|
|
1893
|
+
};
|
|
1894
|
+
} catch {
|
|
1895
|
+
return null;
|
|
1896
|
+
}
|
|
1897
|
+
};
|
|
1898
|
+
var acquireLifecycleLock = (paths) => {
|
|
1899
|
+
assertDirectoryChain2(paths.koedHome, paths.runDir);
|
|
1900
|
+
mkdirSync5(paths.runDir, { recursive: true, mode: 448 });
|
|
1901
|
+
assertDirectoryChain2(paths.koedHome, paths.runDir);
|
|
1902
|
+
const path = lockPath(paths);
|
|
1903
|
+
assertDirectoryChain2(paths.koedHome, path);
|
|
1904
|
+
const identity = resolveProcessIdentity(process.pid);
|
|
1905
|
+
if (!identity)
|
|
1906
|
+
throw new Error("runtime process identity is unavailable");
|
|
1907
|
+
const record = {
|
|
1908
|
+
pid: process.pid,
|
|
1909
|
+
processIdentity: identity,
|
|
1910
|
+
token: randomBytes(32).toString("hex")
|
|
1911
|
+
};
|
|
1912
|
+
try {
|
|
1913
|
+
mkdirSync5(path, { mode: 448 });
|
|
1914
|
+
assertDirectoryChain2(paths.koedHome, path);
|
|
1915
|
+
writeFileSync4(resolve5(path, "owner.json"), `${JSON.stringify(record)}
|
|
1916
|
+
`, {
|
|
1917
|
+
flag: "wx",
|
|
1918
|
+
mode: 384
|
|
1919
|
+
});
|
|
1920
|
+
return record;
|
|
1921
|
+
} catch (error) {
|
|
1922
|
+
if (error.code !== "EEXIST")
|
|
1923
|
+
throw error;
|
|
1924
|
+
throw new Error("runtime lifecycle is locked; stale locks require stopped-runtime operator recovery", { cause: error });
|
|
1925
|
+
}
|
|
1926
|
+
};
|
|
1927
|
+
var releaseLifecycleLock = (paths, expected) => {
|
|
1928
|
+
const path = lockPath(paths);
|
|
1929
|
+
const actual = readLockRecord(path);
|
|
1930
|
+
if (!actual || actual.pid !== expected.pid || actual.token !== expected.token || actual.processIdentity !== expected.processIdentity)
|
|
1931
|
+
return;
|
|
1932
|
+
rmSync5(path, { recursive: true, force: true });
|
|
1933
|
+
};
|
|
1934
|
+
var withStoreLock = async (paths, action) => {
|
|
1935
|
+
assertDirectoryChain2(paths.koedHome, paths.componentsDir);
|
|
1936
|
+
mkdirSync5(paths.componentsDir, { recursive: true, mode: 448 });
|
|
1937
|
+
assertDirectoryChain2(paths.koedHome, paths.componentsDir);
|
|
1938
|
+
const lockPath2 = `${paths.componentsDir}.lock`;
|
|
1939
|
+
assertDirectoryChain2(paths.koedHome, lockPath2);
|
|
1940
|
+
const release = acquireDirectoryLock(lockPath2);
|
|
1941
|
+
try {
|
|
1942
|
+
return await action();
|
|
1943
|
+
} finally {
|
|
1944
|
+
release();
|
|
1945
|
+
}
|
|
1946
|
+
};
|
|
1947
|
+
var readCurrentId = (paths) => {
|
|
1948
|
+
assertDirectoryChain2(paths.koedHome, paths.componentsDir);
|
|
1949
|
+
const pointerPath = resolve5(paths.componentsDir, "current.json");
|
|
1950
|
+
if (!existsSync3(pointerPath))
|
|
1951
|
+
return null;
|
|
1952
|
+
const value = JSON.parse(readRegularFile(pointerPath));
|
|
1953
|
+
if (!isRecord(value))
|
|
1954
|
+
throw new Error("current generation pointer is invalid");
|
|
1955
|
+
const pointer = value;
|
|
1956
|
+
if (Object.keys(pointer).length !== 2 || pointer.schemaVersion !== 1 || typeof pointer.generationId !== "string" || !/^[a-f0-9]{64}$/.test(pointer.generationId))
|
|
1957
|
+
throw new Error("current generation pointer is invalid");
|
|
1958
|
+
return pointer.generationId;
|
|
1959
|
+
};
|
|
1960
|
+
var writeCurrentPointer = (paths, id) => {
|
|
1961
|
+
const pointer = resolve5(paths.componentsDir, "current.json");
|
|
1962
|
+
const temporary = resolve5(dirname3(pointer), `.current-${randomBytes(8).toString("hex")}.tmp`);
|
|
1963
|
+
writeFileSync4(temporary, `${JSON.stringify({ schemaVersion: 1, generationId: id })}
|
|
1964
|
+
`, { flag: "wx", mode: 384 });
|
|
1965
|
+
try {
|
|
1966
|
+
renameAtomically(temporary, pointer);
|
|
1967
|
+
} catch (error) {
|
|
1968
|
+
rmSync5(temporary, { force: true });
|
|
1969
|
+
throw error;
|
|
1970
|
+
}
|
|
1971
|
+
};
|
|
1972
|
+
var readPinState = (paths) => {
|
|
1973
|
+
const path = generationStatePath(paths);
|
|
1974
|
+
assertDirectoryChain2(paths.koedHome, dirname3(path));
|
|
1975
|
+
if (!existsSync3(path))
|
|
1976
|
+
return null;
|
|
1977
|
+
const parsed = JSON.parse(readRegularFile(path));
|
|
1978
|
+
if (!isRuntimeGenerationState(parsed))
|
|
1979
|
+
throw new Error("runtime generation state is invalid");
|
|
1980
|
+
return parsed;
|
|
1981
|
+
};
|
|
1982
|
+
var assertOwner = (generation, requester) => {
|
|
1983
|
+
if (!sameOwner(generation.owner, requester))
|
|
1984
|
+
throw new Error("runtime owner mismatch");
|
|
1985
|
+
};
|
|
1986
|
+
var assertNoActivePin = (paths, allowCurrentSupervisor = false) => {
|
|
1987
|
+
const pin = readPinState(paths);
|
|
1988
|
+
if (pin) {
|
|
1989
|
+
const identity2 = resolveProcessIdentity(pin.pid);
|
|
1990
|
+
if (identity2 === pin.processIdentity || !identity2 && processIsRunning2(pin.pid))
|
|
1991
|
+
throw new Error("runtime generation is running or pinned");
|
|
1992
|
+
rmSync5(generationStatePath(paths), { force: true });
|
|
1993
|
+
}
|
|
1994
|
+
const supervisorPath = resolve5(paths.runDir, "koed-server.lock");
|
|
1995
|
+
if (!existsSync3(supervisorPath))
|
|
1996
|
+
return;
|
|
1997
|
+
const supervisor = readSupervisorLock(supervisorPath);
|
|
1998
|
+
if (!supervisor)
|
|
1999
|
+
throw new Error("supervisor liveness is uncertain");
|
|
2000
|
+
const identity = resolveProcessIdentity(supervisor.pid);
|
|
2001
|
+
if (allowCurrentSupervisor && supervisor.pid === process.pid && identity !== null && identity === supervisor.processIdentity)
|
|
2002
|
+
return;
|
|
2003
|
+
if (identity === supervisor.processIdentity || !identity && processIsRunning2(supervisor.pid) || !supervisor.processIdentity && processIsRunning2(supervisor.pid))
|
|
2004
|
+
throw new Error("runtime generation is running or pinned");
|
|
2005
|
+
};
|
|
2006
|
+
function readCurrentGenerationOrderingEvidence(paths) {
|
|
2007
|
+
const id = readCurrentId(paths);
|
|
2008
|
+
if (!id)
|
|
2009
|
+
throw new Error("no active runtime generation");
|
|
2010
|
+
return readGenerationOrderingEvidence(paths, id);
|
|
2011
|
+
}
|
|
2012
|
+
async function readCurrentGeneration(paths) {
|
|
2013
|
+
const id = readCurrentId(paths);
|
|
2014
|
+
if (!id)
|
|
2015
|
+
throw new Error("no active runtime generation");
|
|
2016
|
+
return readStagedGeneration(paths, id);
|
|
2017
|
+
}
|
|
2018
|
+
var desktopBundleRecordPath = (paths, id) => resolve5(paths.generationsDir, id, "desktop-bundle.json");
|
|
2019
|
+
var persistDesktopBundleRecord = (paths, generation, capability) => {
|
|
2020
|
+
const root = resolve5(paths.generationsDir, generation.id);
|
|
2021
|
+
assertDirectoryChain2(paths.koedHome, root);
|
|
2022
|
+
mkdirSync5(root, { recursive: true, mode: 448 });
|
|
2023
|
+
const path = desktopBundleRecordPath(paths, generation.id);
|
|
2024
|
+
const record = {
|
|
2025
|
+
schemaVersion: 1,
|
|
2026
|
+
kind: "desktop-bundle",
|
|
2027
|
+
id: generation.id,
|
|
2028
|
+
productVersion: generation.productVersion,
|
|
2029
|
+
owner: generation.owner,
|
|
2030
|
+
bundleDigest: capability.bundleDigest,
|
|
2031
|
+
resourcesPath: capability.resourcesPath
|
|
2032
|
+
};
|
|
2033
|
+
try {
|
|
2034
|
+
writeFileSync4(path, `${JSON.stringify(record)}
|
|
2035
|
+
`, {
|
|
2036
|
+
flag: "wx",
|
|
2037
|
+
mode: 384
|
|
2038
|
+
});
|
|
2039
|
+
} catch (error) {
|
|
2040
|
+
if (error.code !== "EEXIST")
|
|
2041
|
+
throw error;
|
|
2042
|
+
const existing = JSON.parse(readRegularFile(path));
|
|
2043
|
+
if (!isRecord(existing) || existing.schemaVersion !== record.schemaVersion || existing.kind !== record.kind || existing.id !== record.id || existing.productVersion !== record.productVersion || existing.bundleDigest !== record.bundleDigest || existing.resourcesPath !== record.resourcesPath || !isRecord(existing.owner) || existing.owner.kind !== generation.owner.kind || existing.owner.installationId !== generation.owner.installationId)
|
|
2044
|
+
throw new Error("private Desktop bundle generation record is invalid", {
|
|
2045
|
+
cause: error
|
|
2046
|
+
});
|
|
2047
|
+
}
|
|
2048
|
+
};
|
|
2049
|
+
var readDesktopBundleGenerationById = async (paths, id, capability) => {
|
|
2050
|
+
const generation = await readDesktopBundleGeneration(paths, capability);
|
|
2051
|
+
if (generation.id !== id)
|
|
2052
|
+
throw new Error("runtime owner mismatch");
|
|
2053
|
+
const record = JSON.parse(readRegularFile(desktopBundleRecordPath(paths, id)));
|
|
2054
|
+
if (!isRecord(record) || record.schemaVersion !== 1 || record.kind !== "desktop-bundle" || record.id !== generation.id || record.productVersion !== generation.productVersion || record.bundleDigest !== capability.bundleDigest || record.resourcesPath !== capability.resourcesPath || !isRecord(record.owner) || record.owner.kind !== generation.owner.kind || record.owner.installationId !== generation.owner.installationId)
|
|
2055
|
+
throw new Error("private Desktop bundle generation record is invalid");
|
|
2056
|
+
return generation;
|
|
2057
|
+
};
|
|
2058
|
+
async function readDesktopGenerationForCapability(paths, id, capability) {
|
|
2059
|
+
if (!validateDesktopRuntimeCapability(capability))
|
|
2060
|
+
throw new Error("validated private Desktop runtime capability is required");
|
|
2061
|
+
try {
|
|
2062
|
+
return await readDesktopBundleGenerationById(paths, id, capability);
|
|
2063
|
+
} catch (error) {
|
|
2064
|
+
if (error instanceof Error && error.message === "runtime owner mismatch")
|
|
2065
|
+
return readStagedGeneration(paths, id);
|
|
2066
|
+
throw error;
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
async function readCurrentDesktopGeneration(paths, capability) {
|
|
2070
|
+
if (!validateDesktopRuntimeCapability(capability))
|
|
2071
|
+
throw new Error("validated private Desktop runtime capability is required");
|
|
2072
|
+
const id = readCurrentId(paths);
|
|
2073
|
+
if (!id)
|
|
2074
|
+
throw new Error("no active runtime generation");
|
|
2075
|
+
const generation = await readDesktopGenerationForCapability(paths, id, capability);
|
|
2076
|
+
assertOwner(generation, resolveDesktopRuntimeOwner(capability, paths.koedHome));
|
|
2077
|
+
return generation;
|
|
2078
|
+
}
|
|
2079
|
+
async function readCurrentGenerationForOwner(paths, requester) {
|
|
2080
|
+
const generation = await readCurrentGeneration(paths);
|
|
2081
|
+
assertOwner(generation, requester);
|
|
2082
|
+
return generation;
|
|
2083
|
+
}
|
|
2084
|
+
var readOrderingJournalFile = (path) => {
|
|
2085
|
+
if (constants4.O_NOFOLLOW === void 0)
|
|
2086
|
+
throw new Error("component metadata no-follow access is unavailable");
|
|
2087
|
+
const descriptor = openSync5(path, constants4.O_RDONLY | constants4.O_NOFOLLOW | constants4.O_NONBLOCK);
|
|
2088
|
+
try {
|
|
2089
|
+
const stat = fstatSync3(descriptor);
|
|
2090
|
+
if (!stat.isFile() || stat.size > 1024 * 1024)
|
|
2091
|
+
throw new Error("generation journal is not a bounded regular file");
|
|
2092
|
+
const bytes = Buffer.alloc(stat.size + 1);
|
|
2093
|
+
let length = 0;
|
|
2094
|
+
while (length < bytes.length) {
|
|
2095
|
+
const count = readSync2(descriptor, bytes, length, bytes.length - length, null);
|
|
2096
|
+
if (count === 0)
|
|
2097
|
+
break;
|
|
2098
|
+
length += count;
|
|
2099
|
+
}
|
|
2100
|
+
if (length > stat.size)
|
|
2101
|
+
throw new Error("generation journal changed while reading");
|
|
2102
|
+
return bytes.subarray(0, length).toString("utf8");
|
|
2103
|
+
} finally {
|
|
2104
|
+
closeSync5(descriptor);
|
|
2105
|
+
}
|
|
2106
|
+
};
|
|
2107
|
+
var readDesktopOrderingJournal = (paths, id) => {
|
|
2108
|
+
const bundlePath = desktopBundleRecordPath(paths, id);
|
|
2109
|
+
const hasBundle = existsSync3(bundlePath);
|
|
2110
|
+
if (hasBundle)
|
|
2111
|
+
assertDirectoryChain2(paths.koedHome, dirname3(bundlePath));
|
|
2112
|
+
const record = hasBundle ? JSON.parse(readOrderingJournalFile(bundlePath)) : readGenerationRecord(paths, id);
|
|
2113
|
+
if (!isRecord(record))
|
|
2114
|
+
throw new Error("generation record is invalid");
|
|
2115
|
+
if (record.kind !== "desktop-bundle" && record.kind !== "desktop-bundle-with-privacy")
|
|
2116
|
+
return null;
|
|
2117
|
+
validateDesktopOrderingJournal(record, id);
|
|
2118
|
+
return Object.freeze({
|
|
2119
|
+
kind: "desktop-generation-ordering-evidence",
|
|
2120
|
+
id,
|
|
2121
|
+
productVersion: record.productVersion,
|
|
2122
|
+
owner: Object.freeze({ ...record.owner }),
|
|
2123
|
+
bundleDigest: record.bundleDigest,
|
|
2124
|
+
resourcesPath: record.resourcesPath,
|
|
2125
|
+
components: Object.freeze(record.kind === "desktop-bundle" ? ["base"] : ["base", "privacy"])
|
|
2126
|
+
});
|
|
2127
|
+
};
|
|
2128
|
+
function validateDesktopOrderingJournal(record, id) {
|
|
2129
|
+
const privacy = record.kind === "desktop-bundle-with-privacy";
|
|
2130
|
+
const keys = [
|
|
2131
|
+
"schemaVersion",
|
|
2132
|
+
"kind",
|
|
2133
|
+
"id",
|
|
2134
|
+
"productVersion",
|
|
2135
|
+
"owner",
|
|
2136
|
+
"bundleDigest",
|
|
2137
|
+
"resourcesPath",
|
|
2138
|
+
...privacy ? ["privacy"] : []
|
|
2139
|
+
];
|
|
2140
|
+
if (Object.keys(record).length !== keys.length || !keys.every((key) => Object.hasOwn(record, key)) || record.schemaVersion !== 1 || record.id !== id || typeof record.productVersion !== "string" || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(record.productVersion) || !isRuntimeOwnerRecord(record.owner) || record.owner.kind !== "desktop" || typeof record.resourcesPath !== "string" || !isAbsolute4(record.resourcesPath) || typeof record.bundleDigest !== "string" || !/^[a-f0-9]{64}$/.test(record.bundleDigest))
|
|
2141
|
+
throw new Error("private Desktop bundle generation record is invalid");
|
|
2142
|
+
if (privacy)
|
|
2143
|
+
validateDesktopPrivacyJournal(record, id);
|
|
2144
|
+
else if (createHash4("sha256").update(`${record.owner.installationId}
|
|
2145
|
+
${record.bundleDigest}`).digest("hex") !== id)
|
|
2146
|
+
throw new Error("Desktop bundle generation identity mismatch");
|
|
2147
|
+
}
|
|
2148
|
+
function validateDesktopPrivacyJournal(record, id) {
|
|
2149
|
+
if (!isRecord(record.privacy) || Object.keys(record.privacy).length !== 2 || typeof record.privacy.root !== "string" || isAbsolute4(record.privacy.root) || record.privacy.root.split(/[\\/]/).includes("..") || typeof record.privacy.manifestDigest !== "string" || !/^[a-f0-9]{64}$/.test(record.privacy.manifestDigest))
|
|
2150
|
+
throw new Error("private Desktop privacy journal is invalid");
|
|
2151
|
+
const identity = { ...record };
|
|
2152
|
+
delete identity.id;
|
|
2153
|
+
if (createHash4("sha256").update(canonicalArtifactJson(identity)).digest("hex") !== id)
|
|
2154
|
+
throw new Error("Desktop privacy generation digest mismatch");
|
|
2155
|
+
}
|
|
2156
|
+
function readCurrentDesktopGenerationOrderingEvidence(paths, capability) {
|
|
2157
|
+
if (!validateDesktopRuntimeCapability(capability))
|
|
2158
|
+
throw new Error("validated private Desktop runtime capability is required");
|
|
2159
|
+
const id = readCurrentId(paths);
|
|
2160
|
+
if (!id)
|
|
2161
|
+
throw new Error("no active runtime generation");
|
|
2162
|
+
const evidence = readDesktopOrderingJournal(paths, id) ?? readGenerationOrderingEvidence(paths, id);
|
|
2163
|
+
if (evidence.kind === "desktop-generation-ordering-evidence" && evidence.resourcesPath !== capability.resourcesPath)
|
|
2164
|
+
throw new Error("runtime owner mismatch");
|
|
2165
|
+
assertOwner(evidence, resolveDesktopRuntimeOwner(capability, paths.koedHome));
|
|
2166
|
+
return evidence;
|
|
2167
|
+
}
|
|
2168
|
+
async function readCurrentRuntimeOwner(paths) {
|
|
2169
|
+
if (!existsSync3(resolve5(paths.componentsDir, "current.json")))
|
|
2170
|
+
return null;
|
|
2171
|
+
const id = readCurrentId(paths);
|
|
2172
|
+
if (!id)
|
|
2173
|
+
return null;
|
|
2174
|
+
return (await Promise.resolve(readDesktopOrderingJournal(paths, id) ?? readGenerationOrderingEvidence(paths, id))).owner;
|
|
2175
|
+
}
|
|
2176
|
+
var persistPinState = (paths, generation, requester, lock, source = "manifest-pinned-store") => {
|
|
2177
|
+
const processIdentity = resolveProcessIdentity(process.pid);
|
|
2178
|
+
if (!processIdentity)
|
|
2179
|
+
throw new Error("runtime process identity is unavailable");
|
|
2180
|
+
const state = {
|
|
2181
|
+
schemaVersion: 1,
|
|
2182
|
+
generationId: generation.id,
|
|
2183
|
+
pid: process.pid,
|
|
2184
|
+
processIdentity,
|
|
2185
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2186
|
+
owner: requester,
|
|
2187
|
+
pinToken: lock.token,
|
|
2188
|
+
...source === "desktop-bundle" ? { source } : {}
|
|
2189
|
+
};
|
|
2190
|
+
const path = generationStatePath(paths);
|
|
2191
|
+
mkdirSync5(dirname3(path), { recursive: true, mode: 448 });
|
|
2192
|
+
const temporary = `${path}.${lock.token}.tmp`;
|
|
2193
|
+
writeFileSync4(temporary, `${JSON.stringify(state)}
|
|
2194
|
+
`, {
|
|
2195
|
+
flag: "wx",
|
|
2196
|
+
mode: 384
|
|
2197
|
+
});
|
|
2198
|
+
renameSync4(temporary, path);
|
|
2199
|
+
return { path, processIdentity };
|
|
2200
|
+
};
|
|
2201
|
+
var createPinRelease = (paths, lock, pinPath, processIdentity) => {
|
|
2202
|
+
let released = false;
|
|
2203
|
+
return () => Promise.resolve().then(() => {
|
|
2204
|
+
if (released)
|
|
2205
|
+
return;
|
|
2206
|
+
released = true;
|
|
2207
|
+
try {
|
|
2208
|
+
const current = readPinState(paths);
|
|
2209
|
+
if (current?.pinToken === lock.token && current.processIdentity === processIdentity)
|
|
2210
|
+
rmSync5(pinPath, { force: true });
|
|
2211
|
+
} finally {
|
|
2212
|
+
releaseLifecycleLock(paths, lock);
|
|
2213
|
+
}
|
|
2214
|
+
});
|
|
2215
|
+
};
|
|
2216
|
+
function readDesktopBundleGeneration(paths, capability) {
|
|
2217
|
+
if (!validateDesktopRuntimeCapability(capability))
|
|
2218
|
+
throw new Error("validated private Desktop runtime capability is required");
|
|
2219
|
+
const currentCapability = verifyDesktopRuntimeBundle(capability.resourcesPath);
|
|
2220
|
+
if (currentCapability.productVersion !== capability.productVersion || currentCapability.bundleDigest !== capability.bundleDigest)
|
|
2221
|
+
throw new Error("Desktop runtime bundle changed after verification");
|
|
2222
|
+
const owner = resolveDesktopRuntimeOwner(capability, paths.koedHome);
|
|
2223
|
+
const generation = desktopBundleGeneration(currentCapability, owner);
|
|
2224
|
+
return Promise.resolve(generation);
|
|
2225
|
+
}
|
|
2226
|
+
async function pinDesktopBundleGenerationForStart(paths, capability) {
|
|
2227
|
+
if (!validateDesktopRuntimeCapability(capability))
|
|
2228
|
+
throw new Error("validated private Desktop runtime capability is required");
|
|
2229
|
+
const requester = resolveDesktopRuntimeOwner(capability, paths.koedHome);
|
|
2230
|
+
const lock = acquireLifecycleLock(paths);
|
|
2231
|
+
try {
|
|
2232
|
+
return await withStoreLock(paths, async () => {
|
|
2233
|
+
assertNoActivePin(paths, true);
|
|
2234
|
+
const bundled = await readDesktopBundleGeneration(paths, capability);
|
|
2235
|
+
const activeId = readCurrentId(paths);
|
|
2236
|
+
let active = null;
|
|
2237
|
+
if (activeId) {
|
|
2238
|
+
const prior = readCurrentDesktopGenerationOrderingEvidence(paths, capability);
|
|
2239
|
+
if (isVersionDowngrade(prior.productVersion, bundled.productVersion))
|
|
2240
|
+
throw new Error("Desktop generation downgrades are not supported.");
|
|
2241
|
+
if (prior.kind === "desktop-generation-ordering-evidence" ? prior.bundleDigest === capability.bundleDigest : prior.productVersion === bundled.productVersion) {
|
|
2242
|
+
active = await readDesktopGenerationForCapability(paths, activeId, capability);
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
if (active)
|
|
2246
|
+
assertOwner(active, requester);
|
|
2247
|
+
persistDesktopBundleRecord(paths, bundled, capability);
|
|
2248
|
+
const generation = active && sameOwner(active.owner, requester) && active.productVersion === bundled.productVersion && active.base.manifestDigest === bundled.base.manifestDigest ? active : bundled;
|
|
2249
|
+
if (activeId !== generation.id)
|
|
2250
|
+
writeCurrentPointer(paths, generation.id);
|
|
2251
|
+
const pin = persistPinState(paths, generation, requester, lock, "desktop-bundle");
|
|
2252
|
+
return {
|
|
2253
|
+
generation,
|
|
2254
|
+
release: createPinRelease(paths, lock, pin.path, pin.processIdentity)
|
|
2255
|
+
};
|
|
2256
|
+
});
|
|
2257
|
+
} catch (error) {
|
|
2258
|
+
releaseLifecycleLock(paths, lock);
|
|
2259
|
+
throw error;
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
var desktopBundleGeneration = (capability, owner) => {
|
|
2263
|
+
const manifest = capability.manifest;
|
|
2264
|
+
const id = createHash4("sha256").update(`${owner.installationId}
|
|
2265
|
+
${capability.bundleDigest}`).digest("hex");
|
|
2266
|
+
return {
|
|
2267
|
+
id,
|
|
2268
|
+
productVersion: capability.productVersion,
|
|
2269
|
+
base: {
|
|
2270
|
+
kind: "desktop-bundle",
|
|
2271
|
+
manifest,
|
|
2272
|
+
root: resolve5(capability.resourcesPath, "koed-runtime"),
|
|
2273
|
+
manifestDigest: capability.bundleDigest
|
|
2274
|
+
},
|
|
2275
|
+
owner
|
|
2276
|
+
};
|
|
2277
|
+
};
|
|
2278
|
+
async function pinGenerationForStart(paths, requester) {
|
|
2279
|
+
const lock = acquireLifecycleLock(paths);
|
|
2280
|
+
try {
|
|
2281
|
+
return await withStoreLock(paths, async () => {
|
|
2282
|
+
assertNoActivePin(paths, true);
|
|
2283
|
+
const generation = await readCurrentGeneration(paths);
|
|
2284
|
+
assertOwner(generation, requester);
|
|
2285
|
+
const pin = persistPinState(paths, generation, requester, lock);
|
|
2286
|
+
return {
|
|
2287
|
+
generation,
|
|
2288
|
+
release: createPinRelease(paths, lock, pin.path, pin.processIdentity)
|
|
2289
|
+
};
|
|
2290
|
+
});
|
|
2291
|
+
} catch (error) {
|
|
2292
|
+
releaseLifecycleLock(paths, lock);
|
|
2293
|
+
throw error;
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
async function activateGeneration(paths, id, requester, capability, selection) {
|
|
2297
|
+
const lock = acquireLifecycleLock(paths);
|
|
2298
|
+
try {
|
|
2299
|
+
return await withStoreLock(paths, async () => {
|
|
2300
|
+
assertNoActivePin(paths);
|
|
2301
|
+
const currentId = readCurrentId(paths);
|
|
2302
|
+
if (selection && currentId !== selection.expectedCurrentGeneration)
|
|
2303
|
+
throw new Error("Runtime selection changed during Privacy installation. Retry from the current selection; no generation was replaced.");
|
|
2304
|
+
const candidate = capability ? await readDesktopGenerationForCapability(paths, id, capability) : await readStagedGeneration(paths, id);
|
|
2305
|
+
assertOwner(candidate, requester);
|
|
2306
|
+
if (currentId) {
|
|
2307
|
+
const current = capability ? await readDesktopGenerationForCapability(paths, currentId, capability) : readGenerationOrderingEvidence(paths, currentId);
|
|
2308
|
+
assertOwner(current, requester);
|
|
2309
|
+
if (isVersionDowngrade(current.productVersion, candidate.productVersion))
|
|
2310
|
+
throw new Error(`Cannot activate generation ${candidate.productVersion} over active ${current.productVersion}: generation downgrades are not supported.`);
|
|
2311
|
+
}
|
|
2312
|
+
writeCurrentPointer(paths, candidate.id);
|
|
2313
|
+
return candidate;
|
|
2314
|
+
});
|
|
2315
|
+
} finally {
|
|
2316
|
+
releaseLifecycleLock(paths, lock);
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
var listOwnedGenerations = async (paths, requester) => {
|
|
2320
|
+
assertDirectoryChain2(paths.koedHome, paths.generationsDir);
|
|
2321
|
+
let entries;
|
|
2322
|
+
try {
|
|
2323
|
+
entries = readdirSync4(paths.generationsDir, { withFileTypes: true });
|
|
2324
|
+
} catch (error) {
|
|
2325
|
+
if (error.code === "ENOENT")
|
|
2326
|
+
return [];
|
|
2327
|
+
throw error;
|
|
2328
|
+
}
|
|
2329
|
+
const candidates = entries.filter((entry) => entry.isDirectory() && /^[a-f0-9]{64}$/.test(entry.name)).map((entry) => ({
|
|
2330
|
+
id: entry.name,
|
|
2331
|
+
path: resolve5(paths.generationsDir, entry.name),
|
|
2332
|
+
mtime: statSync(resolve5(paths.generationsDir, entry.name)).mtimeMs
|
|
2333
|
+
})).sort((left, right) => right.mtime - left.mtime || left.id.localeCompare(right.id));
|
|
2334
|
+
const verified = [];
|
|
2335
|
+
for (const entry of candidates) {
|
|
2336
|
+
try {
|
|
2337
|
+
const generation = await readStagedGeneration(paths, entry.id);
|
|
2338
|
+
if (sameOwner(generation.owner, requester))
|
|
2339
|
+
verified.push(entry);
|
|
2340
|
+
} catch {
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
return verified;
|
|
2344
|
+
};
|
|
2345
|
+
var removeUnretainedGenerations = (generations, retain, currentId) => {
|
|
2346
|
+
const kept = new Set(generations.slice(0, retain).map((entry) => entry.id));
|
|
2347
|
+
if (currentId)
|
|
2348
|
+
kept.add(currentId);
|
|
2349
|
+
const removed = [];
|
|
2350
|
+
for (const entry of generations) {
|
|
2351
|
+
if (kept.has(entry.id))
|
|
2352
|
+
continue;
|
|
2353
|
+
rmSync5(entry.path, { recursive: true, force: true });
|
|
2354
|
+
removed.push(entry.id);
|
|
2355
|
+
}
|
|
2356
|
+
return removed;
|
|
2357
|
+
};
|
|
2358
|
+
async function cleanupGenerations(paths, retain, requester) {
|
|
2359
|
+
if (!Number.isInteger(retain) || retain < 1)
|
|
2360
|
+
throw new Error("generation retention must be a positive integer");
|
|
2361
|
+
const lock = acquireLifecycleLock(paths);
|
|
2362
|
+
try {
|
|
2363
|
+
return await withStoreLock(paths, async () => {
|
|
2364
|
+
assertNoActivePin(paths);
|
|
2365
|
+
const currentId = readCurrentId(paths);
|
|
2366
|
+
const current = currentId ? await readStagedGeneration(paths, currentId) : null;
|
|
2367
|
+
if (current)
|
|
2368
|
+
assertOwner(current, requester);
|
|
2369
|
+
const generations = await listOwnedGenerations(paths, requester);
|
|
2370
|
+
return removeUnretainedGenerations(generations, retain, currentId);
|
|
2371
|
+
});
|
|
2372
|
+
} finally {
|
|
2373
|
+
releaseLifecycleLock(paths, lock);
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
export {
|
|
2378
|
+
resolveKoedServerConfig,
|
|
2379
|
+
writeCodexGlobalMemoryGuidancePreference,
|
|
2380
|
+
validateDesktopRuntimeCapability,
|
|
2381
|
+
resolveDesktopRuntimeOwner,
|
|
2382
|
+
discoverActualRuntimeIdentity,
|
|
2383
|
+
resolveComponentManifestPinTarget,
|
|
2384
|
+
stageComponent,
|
|
2385
|
+
stageGeneration,
|
|
2386
|
+
readStoredComponents,
|
|
2387
|
+
readGenerationComponentPresence,
|
|
2388
|
+
readStagedGeneration,
|
|
2389
|
+
resolveProcessIdentity,
|
|
2390
|
+
readSupervisorLock,
|
|
2391
|
+
acquireKoedServerSupervisorLock,
|
|
2392
|
+
releaseKoedServerSupervisorLock,
|
|
2393
|
+
readCurrentGenerationOrderingEvidence,
|
|
2394
|
+
readCurrentGeneration,
|
|
2395
|
+
readCurrentDesktopGeneration,
|
|
2396
|
+
readCurrentGenerationForOwner,
|
|
2397
|
+
readCurrentDesktopGenerationOrderingEvidence,
|
|
2398
|
+
readCurrentRuntimeOwner,
|
|
2399
|
+
readDesktopBundleGeneration,
|
|
2400
|
+
pinDesktopBundleGenerationForStart,
|
|
2401
|
+
pinGenerationForStart,
|
|
2402
|
+
activateGeneration,
|
|
2403
|
+
cleanupGenerations
|
|
2404
|
+
};
|