@le-space/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +4 -0
  2. package/index.d.ts +522 -0
  3. package/index.js +1636 -0
  4. package/package.json +21 -0
package/index.js ADDED
@@ -0,0 +1,1636 @@
1
+ // src/constants.ts
2
+ var DEFAULT_ALEPH_CHANNEL = "TEST";
3
+
4
+ // src/manifests.ts
5
+ var ITEM_HASH_RE = /^[a-fA-F0-9]{64}$/;
6
+ var DEFAULT_ALEPH_API_HOST = "https://api2.aleph.im";
7
+ var DEFAULT_IPFS_GATEWAY_BASE_URL = "https://ipfs.aleph.cloud/ipfs/";
8
+ function isValidDateString(value) {
9
+ return value.trim().length > 0 && !Number.isNaN(new Date(value).getTime());
10
+ }
11
+ function validateRootfsManifest(manifest) {
12
+ const errors = [];
13
+ if (!manifest) {
14
+ return { manifest, valid: false, errors: ["Rootfs manifest is missing."] };
15
+ }
16
+ if (!manifest.version || !manifest.version.trim()) {
17
+ errors.push("Rootfs manifest version is missing.");
18
+ }
19
+ if (manifest.rootfsInstallStrategy != null && manifest.rootfsInstallStrategy !== "thin" && manifest.rootfsInstallStrategy !== "prebaked") {
20
+ errors.push('Rootfs install strategy must be "thin" or "prebaked" when provided.');
21
+ }
22
+ if (manifest.requiresBootstrapNetwork != null && typeof manifest.requiresBootstrapNetwork !== "boolean") {
23
+ errors.push("Rootfs bootstrap network flag must be a boolean when provided.");
24
+ }
25
+ if (manifest.bootstrapSummary != null && (typeof manifest.bootstrapSummary !== "string" || !manifest.bootstrapSummary.trim())) {
26
+ errors.push("Rootfs bootstrap summary must be non-empty when provided.");
27
+ }
28
+ if (manifest.requiredPortForwards != null) {
29
+ if (!Array.isArray(manifest.requiredPortForwards)) {
30
+ errors.push("Rootfs required port forwards must be an array when provided.");
31
+ } else {
32
+ manifest.requiredPortForwards.forEach((entry, index) => {
33
+ if (!entry || typeof entry !== "object") {
34
+ errors.push(`Rootfs required port forward #${index + 1} must be an object.`);
35
+ return;
36
+ }
37
+ if (!Number.isInteger(entry.port) || entry.port < 1 || entry.port > 65535) {
38
+ errors.push(`Rootfs required port forward #${index + 1} must use a TCP/UDP port between 1 and 65535.`);
39
+ }
40
+ if (entry.tcp !== true && entry.udp !== true) {
41
+ errors.push(`Rootfs required port forward #${index + 1} must enable TCP or UDP.`);
42
+ }
43
+ if (entry.purpose != null && (typeof entry.purpose !== "string" || !entry.purpose.trim())) {
44
+ errors.push(`Rootfs required port forward #${index + 1} purpose must be non-empty when provided.`);
45
+ }
46
+ });
47
+ }
48
+ }
49
+ if (manifest.rootfsItemHash != null && !ITEM_HASH_RE.test(manifest.rootfsItemHash)) {
50
+ errors.push("Rootfs ItemHash must be a 64 character hex value when provided.");
51
+ }
52
+ if (manifest.rootfsCid != null && (typeof manifest.rootfsCid !== "string" || !manifest.rootfsCid.trim())) {
53
+ errors.push("Rootfs CID must be a non-empty string when provided.");
54
+ }
55
+ if (!Number.isInteger(manifest.rootfsSizeMiB) || manifest.rootfsSizeMiB <= 0) {
56
+ errors.push("Rootfs size must be a positive MiB integer.");
57
+ }
58
+ if (manifest.rootfsSourceSizeBytes != null && (!Number.isInteger(manifest.rootfsSourceSizeBytes) || manifest.rootfsSourceSizeBytes <= 0)) {
59
+ errors.push("Rootfs source size must be a positive byte integer when provided.");
60
+ }
61
+ if (!isValidDateString(manifest.createdAt)) {
62
+ errors.push("Rootfs creation date is missing or invalid.");
63
+ }
64
+ return { manifest, valid: errors.length === 0, errors };
65
+ }
66
+ function normalizeStatus(status) {
67
+ if (typeof status !== "string") return "unknown";
68
+ const normalized = status.toLowerCase();
69
+ if (normalized === "processed" || normalized === "pending" || normalized === "rejected") {
70
+ return normalized;
71
+ }
72
+ return "unknown";
73
+ }
74
+ function parseCidFromPayload(payload) {
75
+ const firstMessage = Array.isArray(payload.messages) && payload.messages[0] && typeof payload.messages[0] === "object" ? payload.messages[0] : null;
76
+ const directContent = firstMessage?.content && typeof firstMessage.content === "object" ? firstMessage.content : null;
77
+ if (typeof directContent?.item_hash === "string") {
78
+ return directContent.item_hash;
79
+ }
80
+ if (typeof firstMessage?.item_content === "string") {
81
+ try {
82
+ const itemContent = JSON.parse(firstMessage.item_content);
83
+ if (typeof itemContent.item_hash === "string") {
84
+ return itemContent.item_hash;
85
+ }
86
+ } catch {
87
+ return null;
88
+ }
89
+ }
90
+ return null;
91
+ }
92
+ function parseRejectionReason(payload) {
93
+ const errorCode = typeof payload.error_code === "number" ? payload.error_code : null;
94
+ const details = payload.details && typeof payload.details === "object" ? payload.details : null;
95
+ const rawErrors = Array.isArray(details?.errors) ? details.errors : [];
96
+ const firstError = rawErrors[0] && typeof rawErrors[0] === "object" ? rawErrors[0] : null;
97
+ if (firstError) {
98
+ const accountBalance = Number(firstError.account_balance);
99
+ const requiredBalance = Number(firstError.required_balance);
100
+ if (Number.isFinite(accountBalance) && Number.isFinite(requiredBalance)) {
101
+ const shortfall = requiredBalance - accountBalance;
102
+ return {
103
+ rejectionErrorCode: errorCode,
104
+ rejectionReason: shortfall > 0 ? `Rejected by Aleph for insufficient hold balance: ${accountBalance.toFixed(3)} available, ${requiredBalance.toFixed(3)} required, ${shortfall.toFixed(3)} short.` : `Rejected by Aleph for insufficient hold balance: ${accountBalance.toFixed(3)} available, ${requiredBalance.toFixed(3)} required.`
105
+ };
106
+ }
107
+ }
108
+ return {
109
+ rejectionErrorCode: errorCode,
110
+ rejectionReason: errorCode != null ? `Rejected by Aleph (error code ${errorCode}).` : null
111
+ };
112
+ }
113
+ async function verifyRootfsExists(itemHash, options) {
114
+ if (!ITEM_HASH_RE.test(itemHash)) return false;
115
+ const apiHost = options.apiHost ?? DEFAULT_ALEPH_API_HOST;
116
+ const response = await options.fetch(`${apiHost}/api/v0/messages/${itemHash}`, {
117
+ method: "GET",
118
+ cache: "no-cache"
119
+ });
120
+ if (response.status === 404) return false;
121
+ if (!response.ok) throw new Error(`Rootfs lookup failed: ${response.status}`);
122
+ const payload = await response.json();
123
+ const firstMessage = Array.isArray(payload.messages) ? payload.messages[0] : void 0;
124
+ const type = String(payload.type || payload.message?.type || firstMessage?.type || "").toUpperCase();
125
+ return type === "STORE";
126
+ }
127
+ async function probeRootfsGateway(cid, options) {
128
+ const gatewayUrl = new URL(cid, options.gatewayBaseUrl ?? DEFAULT_IPFS_GATEWAY_BASE_URL).toString();
129
+ try {
130
+ const response = await options.fetch(gatewayUrl, { method: "HEAD", cache: "no-store" });
131
+ return {
132
+ gatewayUrl,
133
+ gatewayStatus: response.ok ? "reachable" : "error",
134
+ gatewayError: response.ok ? null : `Gateway responded with ${response.status}.`
135
+ };
136
+ } catch (error) {
137
+ const message = error instanceof Error ? error.message : String(error);
138
+ return {
139
+ gatewayUrl,
140
+ gatewayStatus: message.includes("timed out") ? "timeout" : "unavailable",
141
+ gatewayError: message
142
+ };
143
+ }
144
+ }
145
+ async function resolveRootfsReference(itemHash, options) {
146
+ if (!ITEM_HASH_RE.test(itemHash)) return null;
147
+ const apiHost = options.apiHost ?? DEFAULT_ALEPH_API_HOST;
148
+ const response = await options.fetch(`${apiHost}/api/v0/messages/${itemHash}`, {
149
+ method: "GET",
150
+ cache: "no-cache"
151
+ });
152
+ if (response.status === 404) return null;
153
+ if (!response.ok) throw new Error(`Rootfs lookup failed: ${response.status}`);
154
+ const payload = await response.json();
155
+ const firstMessage = Array.isArray(payload.messages) && payload.messages[0] && typeof payload.messages[0] === "object" ? payload.messages[0] : null;
156
+ const messageObject = payload.message && typeof payload.message === "object" ? payload.message : null;
157
+ const cid = parseCidFromPayload(payload);
158
+ const rejection = normalizeStatus(payload.status) === "rejected" ? parseRejectionReason(payload) : { rejectionErrorCode: null, rejectionReason: null };
159
+ const gateway = cid ? await probeRootfsGateway(cid, {
160
+ gatewayBaseUrl: options.gatewayBaseUrl,
161
+ fetch: options.fetch
162
+ }) : { gatewayUrl: null, gatewayStatus: "unknown", gatewayError: null };
163
+ return {
164
+ itemHash,
165
+ messageStatus: normalizeStatus(payload.status),
166
+ messageType: String(payload.type || messageObject?.type || firstMessage?.type || "").toUpperCase() || null,
167
+ cid,
168
+ receptionTime: typeof payload.reception_time === "string" ? payload.reception_time : null,
169
+ rejectionErrorCode: rejection.rejectionErrorCode,
170
+ rejectionReason: rejection.rejectionReason,
171
+ gatewayUrl: gateway.gatewayUrl,
172
+ gatewayStatus: gateway.gatewayStatus,
173
+ gatewayError: gateway.gatewayError
174
+ };
175
+ }
176
+
177
+ // src/crns.ts
178
+ var DEFAULT_CRN_LIST_URL = "https://crns-list.aleph.sh/crns.json";
179
+ var DEFAULT_COUNTRY_LOOKUP_BASE_URL = "https://api.country.is";
180
+ var DEFAULT_DNS_RESOLVE_URL = "https://dns.google/resolve";
181
+ function asString(value) {
182
+ return typeof value === "string" && value.trim() ? value.trim() : null;
183
+ }
184
+ function normalizeCountryCode(value) {
185
+ const normalized = asString(value)?.toUpperCase() ?? null;
186
+ return normalized && /^[A-Z]{2}$/.test(normalized) ? normalized : null;
187
+ }
188
+ function lookupHost(address) {
189
+ try {
190
+ return new URL(address ?? "").hostname.trim().toLowerCase();
191
+ } catch {
192
+ return String(address ?? "").trim().toLowerCase().replace(/^https?:\/\//, "").replace(/:\d+$/, "");
193
+ }
194
+ }
195
+ function isIpAddress(host) {
196
+ return /^\d{1,3}(?:\.\d{1,3}){3}$/.test(host) || host.includes(":");
197
+ }
198
+ function countryNameFromCode(value) {
199
+ if (!value) return null;
200
+ try {
201
+ const displayNames = new Intl.DisplayNames(["en"], { type: "region" });
202
+ return displayNames.of(value) ?? value;
203
+ } catch {
204
+ return value;
205
+ }
206
+ }
207
+ function compatibleCrns(crns, excludedHashes = []) {
208
+ const excluded = new Set(excludedHashes.filter(Boolean));
209
+ return [...crns].filter((crn) => {
210
+ if (!crn?.hash || excluded.has(crn.hash)) return false;
211
+ if (crn.qemu_support === false) return false;
212
+ if (crn.system_usage?.active === false) return false;
213
+ return true;
214
+ });
215
+ }
216
+ function scoreSortedCrns(crns) {
217
+ return [...crns].sort((left, right) => {
218
+ const rightScore = typeof right.score === "number" ? right.score : Number(right.score ?? Number.NEGATIVE_INFINITY);
219
+ const leftScore = typeof left.score === "number" ? left.score : Number(left.score ?? Number.NEGATIVE_INFINITY);
220
+ if (rightScore !== leftScore) return rightScore - leftScore;
221
+ const leftName = (left.name || left.address || left.hash).toLowerCase();
222
+ const rightName = (right.name || right.address || right.hash).toLowerCase();
223
+ return leftName.localeCompare(rightName);
224
+ });
225
+ }
226
+ async function resolveHostIp(host, options) {
227
+ if (!host) return null;
228
+ if (isIpAddress(host)) return host;
229
+ for (const type of ["A", "AAAA"]) {
230
+ const url = new URL(options.dnsResolveUrl ?? DEFAULT_DNS_RESOLVE_URL);
231
+ url.searchParams.set("name", host);
232
+ url.searchParams.set("type", type);
233
+ url.searchParams.set("edns_client_subnet", "0.0.0.0/0");
234
+ const response = await options.fetch(url.toString(), {
235
+ cache: "no-cache"
236
+ });
237
+ if (!response.ok) continue;
238
+ const payload = await response.json();
239
+ const record = Array.isArray(payload?.Answer) ? payload.Answer.find((entry) => typeof entry?.data === "string" && entry.data.trim()) : null;
240
+ if (typeof record?.data === "string" && record.data.trim()) {
241
+ return record.data.trim();
242
+ }
243
+ }
244
+ return null;
245
+ }
246
+ async function lookupIpLocation(ip, options) {
247
+ const url = new URL(`${options.countryLookupBaseUrl ?? DEFAULT_COUNTRY_LOOKUP_BASE_URL}/${encodeURIComponent(ip)}`);
248
+ url.searchParams.set("fields", "city,subdivision");
249
+ const response = await options.fetch(url.toString(), {
250
+ cache: "no-cache"
251
+ });
252
+ if (!response.ok) {
253
+ return {
254
+ resolved_ip: ip,
255
+ city: null,
256
+ region: null,
257
+ country: null,
258
+ country_code: null,
259
+ geo_source: null
260
+ };
261
+ }
262
+ const payload = await response.json();
263
+ const countryCode = normalizeCountryCode(payload?.country);
264
+ return {
265
+ resolved_ip: asString(payload?.ip) ?? ip,
266
+ city: asString(payload?.city),
267
+ region: asString(payload?.subdivision),
268
+ country: countryNameFromCode(countryCode),
269
+ country_code: countryCode,
270
+ geo_source: "country.is"
271
+ };
272
+ }
273
+ async function fetchCrns(options) {
274
+ const requestUrl = new URL(options.url ?? DEFAULT_CRN_LIST_URL);
275
+ requestUrl.searchParams.set("filter_inactive", "true");
276
+ const response = await options.fetch(requestUrl.toString(), {
277
+ cache: "no-cache"
278
+ });
279
+ if (!response.ok) {
280
+ throw new Error(`CRN list request failed: ${response.status}`);
281
+ }
282
+ const payload = await response.json();
283
+ return Array.isArray(payload?.crns) ? payload.crns : [];
284
+ }
285
+ async function enrichCrnsWithGeo(crns, options) {
286
+ return Promise.all(
287
+ crns.map(async (crn) => {
288
+ if (crn.city || crn.region || crn.country || crn.country_code) {
289
+ return crn;
290
+ }
291
+ try {
292
+ const host = lookupHost(crn.address);
293
+ const ip = await resolveHostIp(host, {
294
+ fetch: options.fetch,
295
+ dnsResolveUrl: options.dnsResolveUrl
296
+ });
297
+ if (!ip) return crn;
298
+ return {
299
+ ...crn,
300
+ ...await lookupIpLocation(ip, {
301
+ fetch: options.fetch,
302
+ countryLookupBaseUrl: options.countryLookupBaseUrl
303
+ })
304
+ };
305
+ } catch {
306
+ return crn;
307
+ }
308
+ })
309
+ );
310
+ }
311
+ async function listGeocodedCrns(options) {
312
+ const sortedCrns = scoreSortedCrns(compatibleCrns(await fetchCrns({
313
+ url: options.url,
314
+ fetch: options.fetch
315
+ })));
316
+ const geocodedCrns = await enrichCrnsWithGeo(
317
+ sortedCrns.slice(0, Math.max(1, Number(options.limit) || 30)),
318
+ options
319
+ );
320
+ return geocodedCrns.filter((crn) => Boolean(crn.city || crn.region || crn.country || crn.country_code)).sort((left, right) => {
321
+ const leftLabel = `${left.country ?? ""}/${left.region ?? ""}/${left.city ?? ""}/${left.name ?? left.hash}`.toLowerCase();
322
+ const rightLabel = `${right.country ?? ""}/${right.region ?? ""}/${right.city ?? ""}/${right.name ?? right.hash}`.toLowerCase();
323
+ return leftLabel.localeCompare(rightLabel);
324
+ });
325
+ }
326
+ async function rankCandidateCrns(crns, options) {
327
+ const preferredCountryCode = normalizeCountryCode(options.preferredCountryCode);
328
+ const geoLimit = Math.max(1, Number(options.geoLimit) || 30);
329
+ const sortedCrns = scoreSortedCrns(compatibleCrns(crns, options.excludedHashes));
330
+ if (!preferredCountryCode || sortedCrns.length === 0) {
331
+ return sortedCrns;
332
+ }
333
+ const enrichedTopCrns = await enrichCrnsWithGeo(sortedCrns.slice(0, geoLimit), options);
334
+ const mergedByHash = new Map(enrichedTopCrns.map((crn) => [crn.hash, crn]));
335
+ const mergedCrns = sortedCrns.map((crn) => mergedByHash.get(crn.hash) ?? crn);
336
+ const originalIndex = new Map(mergedCrns.map((crn, index) => [crn.hash, index]));
337
+ return [...mergedCrns].sort((left, right) => {
338
+ const leftPreferred = normalizeCountryCode(left.country_code) === preferredCountryCode ? 1 : 0;
339
+ const rightPreferred = normalizeCountryCode(right.country_code) === preferredCountryCode ? 1 : 0;
340
+ if (leftPreferred !== rightPreferred) {
341
+ return rightPreferred - leftPreferred;
342
+ }
343
+ return (originalIndex.get(left.hash) ?? Number.MAX_SAFE_INTEGER) - (originalIndex.get(right.hash) ?? Number.MAX_SAFE_INTEGER);
344
+ });
345
+ }
346
+ async function selectPreferredCrn(crns, options) {
347
+ return (await rankCandidateCrns(crns, options))[0] ?? null;
348
+ }
349
+
350
+ // src/port-forwarding.ts
351
+ var DEFAULT_INSTANCE_PORT_FORWARDS = [
352
+ { port: 22, tcp: true, udp: false, purpose: "SSH" }
353
+ ];
354
+ function normalizeRequestedPort(entry) {
355
+ return {
356
+ port: entry.port,
357
+ tcp: entry.tcp === true,
358
+ udp: entry.udp === true,
359
+ purpose: entry.purpose?.trim() || void 0
360
+ };
361
+ }
362
+ function normalizePortFlags(value) {
363
+ if (!value || typeof value !== "object") return null;
364
+ const candidate = value;
365
+ return {
366
+ tcp: candidate.tcp === true,
367
+ udp: candidate.udp === true
368
+ };
369
+ }
370
+ function normalizeExistingPortForwardEntry(entry) {
371
+ if (!entry?.ports || typeof entry.ports !== "object") return {};
372
+ return Object.fromEntries(
373
+ Object.entries(entry.ports).map(([port, flags]) => [port, normalizePortFlags(flags)]).filter((item) => item[1] != null)
374
+ );
375
+ }
376
+ function requestedPortFlags(portForwards) {
377
+ return Object.fromEntries(
378
+ portForwards.map((entry) => [
379
+ String(entry.port),
380
+ {
381
+ tcp: entry.tcp === true,
382
+ udp: entry.udp === true
383
+ }
384
+ ])
385
+ );
386
+ }
387
+ function mergePortFlagMaps(existing, requested) {
388
+ const merged = /* @__PURE__ */ new Map();
389
+ for (const [port, flags] of Object.entries(existing)) {
390
+ merged.set(port, {
391
+ tcp: flags.tcp === true,
392
+ udp: flags.udp === true
393
+ });
394
+ }
395
+ for (const [port, flags] of Object.entries(requested)) {
396
+ const current = merged.get(port);
397
+ merged.set(port, {
398
+ tcp: current?.tcp === true || flags.tcp === true,
399
+ udp: current?.udp === true || flags.udp === true
400
+ });
401
+ }
402
+ return Object.fromEntries([...merged.entries()].sort((left, right) => Number(left[0]) - Number(right[0])));
403
+ }
404
+ function mergeRequiredPortForwards(...groups) {
405
+ const merged = /* @__PURE__ */ new Map();
406
+ for (const group of groups) {
407
+ for (const entry of group ?? []) {
408
+ const normalized = normalizeRequestedPort(entry);
409
+ const current = merged.get(normalized.port);
410
+ merged.set(normalized.port, {
411
+ port: normalized.port,
412
+ tcp: current?.tcp === true || normalized.tcp === true,
413
+ udp: current?.udp === true || normalized.udp === true,
414
+ purpose: current?.purpose ?? normalized.purpose
415
+ });
416
+ }
417
+ }
418
+ return [...merged.values()].sort((left, right) => left.port - right.port);
419
+ }
420
+ function requiredInstancePortForwards(manifest) {
421
+ return mergeRequiredPortForwards(DEFAULT_INSTANCE_PORT_FORWARDS, manifest?.requiredPortForwards);
422
+ }
423
+ function portForwardLabel(entry) {
424
+ const protocols = [entry.tcp === true ? "TCP" : null, entry.udp === true ? "UDP" : null].filter((value) => Boolean(value)).join("/");
425
+ return `${entry.port}/${protocols}`;
426
+ }
427
+ async function fetchPortForwardAggregate(address, options) {
428
+ const requestUrl = new URL(`/api/v0/aggregates/${address}.json`, options.apiHost ?? DEFAULT_ALEPH_API_HOST);
429
+ requestUrl.searchParams.set("keys", "port-forwarding");
430
+ const response = await options.fetch(requestUrl.toString(), { cache: "no-cache" });
431
+ if (response.status === 404) return {};
432
+ if (!response.ok) {
433
+ throw new Error(`Port-forward aggregate request failed: ${response.status}`);
434
+ }
435
+ const payload = await response.json();
436
+ const aggregate = payload.data?.["port-forwarding"];
437
+ if (!aggregate || typeof aggregate !== "object" || Array.isArray(aggregate)) return {};
438
+ return aggregate;
439
+ }
440
+
441
+ // src/aleph-normalizers.ts
442
+ function asString2(value) {
443
+ return typeof value === "string" && value.trim() ? value : null;
444
+ }
445
+ function asNumber(value) {
446
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
447
+ }
448
+ function normalizeMessageStatus(status) {
449
+ if (typeof status !== "string") return "unknown";
450
+ const normalized = status.toLowerCase();
451
+ if (normalized === "processed" || normalized === "pending" || normalized === "rejected") {
452
+ return normalized;
453
+ }
454
+ return "unknown";
455
+ }
456
+ function normalizeProxyUrl(value) {
457
+ const stringValue = asString2(value);
458
+ if (!stringValue) return null;
459
+ if (/^https?:\/\//i.test(stringValue)) return stringValue;
460
+ return `https://${stringValue}`;
461
+ }
462
+ function messageTypeFromEnvelope(payload) {
463
+ if (!payload) return null;
464
+ const type = payload.type ?? payload.message?.type ?? (Array.isArray(payload.messages) ? payload.messages[0]?.type : void 0);
465
+ return typeof type === "string" ? type.toUpperCase() : null;
466
+ }
467
+ function extractReferenceHashes(details) {
468
+ if (!details || typeof details !== "object" || !("errors" in details)) return [];
469
+ const errors = details.errors;
470
+ if (!Array.isArray(errors)) return [];
471
+ return errors.filter((value) => typeof value === "string");
472
+ }
473
+ function describeRejectedDeployment(payload, references, rootfsRef) {
474
+ const errorCode = typeof payload.error_code === "number" ? payload.error_code : null;
475
+ const pendingReferences = references.filter((reference) => reference.status === "pending");
476
+ const missingReferences = references.filter((reference) => reference.status === "missing");
477
+ const rootfsReference = references.find((reference) => reference.itemHash === rootfsRef);
478
+ if (rootfsReference?.status === "pending") {
479
+ return `Aleph rejected this deployment because the referenced rootfs STORE message ${rootfsReference.itemHash} is still pending and cannot yet be used by an instance. Wait for that STORE message to process, then deploy again.`;
480
+ }
481
+ if (pendingReferences.length > 0) {
482
+ return `Aleph rejected this deployment because referenced message(s) are still pending: ${pendingReferences.map((reference) => reference.itemHash).join(", ")}.`;
483
+ }
484
+ if (missingReferences.length > 0) {
485
+ return `Aleph rejected this deployment because referenced message(s) were not found on Aleph: ${missingReferences.map((reference) => reference.itemHash).join(", ")}.`;
486
+ }
487
+ const referencedHashes = extractReferenceHashes(payload.details);
488
+ if (referencedHashes.length > 0) {
489
+ return `Aleph rejected this deployment${errorCode ? ` (error ${errorCode})` : ""}. Referenced message(s): ${referencedHashes.join(", ")}.`;
490
+ }
491
+ return `Aleph rejected this deployment${errorCode ? ` (error ${errorCode})` : ""}.`;
492
+ }
493
+ function extractProxyUrl(item, networking) {
494
+ const networkingCandidates = [
495
+ networking?.proxy_url,
496
+ networking?.proxyUrl,
497
+ networking?.web_access_url,
498
+ networking?.webAccessUrl,
499
+ networking?.proxy_hostname,
500
+ networking?.proxyHostname,
501
+ networking?.domain,
502
+ networking?.hostname
503
+ ];
504
+ for (const candidate of networkingCandidates) {
505
+ const normalized = normalizeProxyUrl(candidate);
506
+ if (normalized) return normalized;
507
+ }
508
+ const webAccessCandidates = [item.web_access, item.webAccess];
509
+ for (const entry of webAccessCandidates) {
510
+ const normalized = normalizeProxyUrl(entry?.url) ?? normalizeProxyUrl(entry?.proxy_url) ?? normalizeProxyUrl(entry?.hostname) ?? normalizeProxyUrl(entry?.domain);
511
+ if (normalized) return normalized;
512
+ }
513
+ return null;
514
+ }
515
+ function normalizeExecution(item, crnUrl) {
516
+ const networking = item.networking ?? null;
517
+ const mappedPorts = networking && "mapped_ports" in networking && networking.mapped_ports && typeof networking.mapped_ports === "object" ? Object.fromEntries(
518
+ Object.entries(networking.mapped_ports).map(([port, mapping]) => [
519
+ port,
520
+ {
521
+ host: asNumber(mapping?.host) ?? void 0,
522
+ tcp: typeof mapping?.tcp === "boolean" ? mapping.tcp : void 0,
523
+ udp: typeof mapping?.udp === "boolean" ? mapping.udp : void 0
524
+ }
525
+ ])
526
+ ) : void 0;
527
+ if (networking && ("host_ipv4" in networking || "ipv6_ip" in networking || "ipv4_network" in networking)) {
528
+ const v2Item = item;
529
+ return {
530
+ crnUrl,
531
+ version: "v2",
532
+ running: typeof v2Item.running === "boolean" ? v2Item.running : void 0,
533
+ networking: {
534
+ ipv4_network: asString2(networking.ipv4_network),
535
+ host_ipv4: asString2(networking.host_ipv4),
536
+ ipv6_network: asString2(networking.ipv6_network),
537
+ ipv6_ip: asString2(networking.ipv6_ip),
538
+ ipv4_ip: asString2(networking.ipv4_ip),
539
+ proxy_url: extractProxyUrl(v2Item, networking),
540
+ mapped_ports: mappedPorts
541
+ },
542
+ status: v2Item.status ? {
543
+ defined_at: asString2(v2Item.status.defined_at),
544
+ preparing_at: asString2(v2Item.status.preparing_at),
545
+ prepared_at: asString2(v2Item.status.prepared_at),
546
+ starting_at: asString2(v2Item.status.starting_at),
547
+ started_at: asString2(v2Item.status.started_at),
548
+ stopping_at: asString2(v2Item.status.stopping_at),
549
+ stopped_at: asString2(v2Item.status.stopped_at)
550
+ } : null
551
+ };
552
+ }
553
+ return {
554
+ crnUrl,
555
+ version: "v1",
556
+ networking: {
557
+ ipv4: networking && "ipv4" in networking ? asString2(networking.ipv4) : null,
558
+ ipv6: networking && "ipv6" in networking ? asString2(networking.ipv6) : null
559
+ },
560
+ status: null
561
+ };
562
+ }
563
+
564
+ // src/deployment-inspection.ts
565
+ function insufficientBalanceMessage(details) {
566
+ const firstError = details && Array.isArray(details.errors) && details.errors[0] && typeof details.errors[0] === "object" ? details.errors[0] : null;
567
+ const accountBalance = firstError?.account_balance;
568
+ const requiredBalance = firstError?.required_balance;
569
+ if (accountBalance != null && requiredBalance != null) {
570
+ return `insufficient Aleph balance: account has ${accountBalance}, required is ${requiredBalance}`;
571
+ }
572
+ return null;
573
+ }
574
+ async function fetchMessageEnvelope(itemHash, options) {
575
+ const response = await options.fetch(`${options.apiHost ?? DEFAULT_ALEPH_API_HOST}/api/v0/messages/${itemHash}`, {
576
+ cache: "no-cache"
577
+ });
578
+ if (response.status === 404) return null;
579
+ if (!response.ok) throw new Error(`Message lookup failed: ${response.status}`);
580
+ return await response.json();
581
+ }
582
+ async function inspectMessageResult(itemHash, options) {
583
+ const label = options.label ?? "Message";
584
+ const payload = await fetchMessageEnvelope(itemHash, options);
585
+ if (!payload) {
586
+ return {
587
+ status: "unknown",
588
+ errorCode: null,
589
+ details: null,
590
+ rejectionReason: `${label} ${itemHash} was not found on Aleph.`
591
+ };
592
+ }
593
+ const status = normalizeMessageStatus(payload.status);
594
+ const errorCode = typeof payload.error_code === "number" ? payload.error_code : null;
595
+ const details = payload.details && typeof payload.details === "object" ? payload.details : null;
596
+ let rejectionReason = null;
597
+ if (status === "rejected") {
598
+ const balanceMessage = errorCode === 5 ? insufficientBalanceMessage(details) : null;
599
+ rejectionReason = balanceMessage ? `${label} ${itemHash} was rejected by Aleph due to ${balanceMessage}.` : `${label} ${itemHash} was rejected by Aleph${errorCode ? ` (error ${errorCode})` : ""}.`;
600
+ }
601
+ return {
602
+ status,
603
+ errorCode,
604
+ details,
605
+ rejectionReason
606
+ };
607
+ }
608
+ async function fetchReference(itemHash, options) {
609
+ const payload = await fetchMessageEnvelope(itemHash, options);
610
+ if (!payload) {
611
+ return {
612
+ itemHash,
613
+ status: "missing",
614
+ type: null
615
+ };
616
+ }
617
+ return {
618
+ itemHash,
619
+ status: normalizeMessageStatus(payload.status),
620
+ type: messageTypeFromEnvelope(payload)
621
+ };
622
+ }
623
+ async function inspectDeploymentResult(itemHash, options) {
624
+ const payload = await fetchMessageEnvelope(itemHash, options);
625
+ if (!payload) {
626
+ return {
627
+ status: "unknown",
628
+ errorCode: null,
629
+ details: null,
630
+ rejectionReason: `Deployment message ${itemHash} was not found on Aleph.`,
631
+ references: []
632
+ };
633
+ }
634
+ const relatedHashes = new Set(options.rootfsRef ? [options.rootfsRef] : []);
635
+ for (const referenceHash of extractReferenceHashes(payload.details)) {
636
+ relatedHashes.add(referenceHash);
637
+ }
638
+ const references = await Promise.all(
639
+ Array.from(relatedHashes).map((hash) => fetchReference(hash, options))
640
+ );
641
+ const status = normalizeMessageStatus(payload.status);
642
+ const errorCode = typeof payload.error_code === "number" ? payload.error_code : null;
643
+ const details = payload.details && typeof payload.details === "object" ? payload.details : null;
644
+ return {
645
+ status,
646
+ errorCode,
647
+ details,
648
+ rejectionReason: status === "rejected" ? describeRejectedDeployment(payload, references, options.rootfsRef) : null,
649
+ references
650
+ };
651
+ }
652
+ async function waitForDeploymentResult(itemHash, options) {
653
+ const attempts = Math.max(1, Number(options.attempts ?? 15));
654
+ const delayMs = Math.max(0, Number(options.delayMs ?? 2e3));
655
+ const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
656
+ let lastResult = await inspectDeploymentResult(itemHash, options);
657
+ for (let attempt = 1; attempt < attempts; attempt += 1) {
658
+ if (lastResult.status === "processed" || lastResult.status === "rejected") {
659
+ return lastResult;
660
+ }
661
+ await sleep(delayMs);
662
+ lastResult = await inspectDeploymentResult(itemHash, options);
663
+ }
664
+ return lastResult;
665
+ }
666
+
667
+ // src/broadcast.ts
668
+ function signaturePayload(message) {
669
+ return [message.chain, message.sender, message.type, message.item_hash].join("\n");
670
+ }
671
+ async function signAlephMessage(unsignedMessage, signer) {
672
+ const signature = await signer(unsignedMessage.sender, signaturePayload(unsignedMessage));
673
+ return {
674
+ ...unsignedMessage,
675
+ signature: signature.startsWith("0x") ? signature : `0x${signature}`
676
+ };
677
+ }
678
+ function normalizeBroadcastStatus(httpStatus, responseStatus) {
679
+ if (httpStatus === 202) return "pending";
680
+ return normalizeMessageStatus(responseStatus);
681
+ }
682
+ function isInvalidMessageFormatResponse(response, payload) {
683
+ if (response.status !== 422) return false;
684
+ const details = payload?.details;
685
+ if (typeof details === "string" && details.includes("InvalidMessageFormat")) return true;
686
+ if (details && typeof details === "object") {
687
+ const detailMessage = details.message;
688
+ if (typeof detailMessage === "string" && detailMessage.includes("InvalidMessageFormat")) return true;
689
+ }
690
+ return false;
691
+ }
692
+ function isRetryableBroadcastFailure(response, payload) {
693
+ if (response.status >= 500) return true;
694
+ const publicationStatus = payload?.publication_status?.status;
695
+ if (typeof publicationStatus === "string" && publicationStatus.toLowerCase() === "error") {
696
+ return true;
697
+ }
698
+ return false;
699
+ }
700
+ async function postBroadcastPayload(body, options) {
701
+ const rawResponse = await options.fetch(`${options.apiHost ?? DEFAULT_ALEPH_API_HOST}/api/v0/messages`, {
702
+ method: "POST",
703
+ headers: { "content-type": "application/json" },
704
+ body: JSON.stringify(body)
705
+ });
706
+ const response = await rawResponse.json().catch(() => ({}));
707
+ return {
708
+ response,
709
+ httpStatus: rawResponse.status
710
+ };
711
+ }
712
+ async function broadcastAlephMessage(message, options) {
713
+ const attempts = [
714
+ { sync: options.sync ?? false, message },
715
+ { ...message, sync: options.sync ?? false },
716
+ { ...message }
717
+ ];
718
+ for (let index = 0; index < attempts.length; index += 1) {
719
+ const result = await postBroadcastPayload(attempts[index], {
720
+ apiHost: options.apiHost,
721
+ fetch: options.fetch
722
+ });
723
+ if (result.httpStatus === 202 || normalizeBroadcastStatus(result.httpStatus, result.response?.message_status) !== "unknown" || result.httpStatus >= 200 && result.httpStatus < 300) {
724
+ return result;
725
+ }
726
+ const canRetry = index < attempts.length - 1 && (isInvalidMessageFormatResponse({ status: result.httpStatus }, result.response) || isRetryableBroadcastFailure({ status: result.httpStatus }, result.response));
727
+ if (!canRetry) {
728
+ throw new Error(`Broadcast failed: ${result.httpStatus} ${JSON.stringify(result.response ?? {})}`);
729
+ }
730
+ }
731
+ throw new Error("Broadcast failed: no compatible request format was accepted");
732
+ }
733
+
734
+ // src/forget.ts
735
+ function asOptionalReason(value) {
736
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
737
+ }
738
+ async function createUnsignedForgetMessage(args) {
739
+ const content = {
740
+ address: args.sender,
741
+ time: args.now ?? Date.now() / 1e3,
742
+ hashes: [...new Set((args.hashes ?? []).filter(Boolean))],
743
+ aggregates: [...new Set((args.aggregates ?? []).filter(Boolean))],
744
+ reason: asOptionalReason(args.reason)
745
+ };
746
+ if (content.hashes.length === 0 && content.aggregates.length === 0) {
747
+ throw new Error("FORGET message requires at least one hash or aggregate key.");
748
+ }
749
+ const itemContent = JSON.stringify(content);
750
+ const itemHash = await args.hasher(itemContent);
751
+ return {
752
+ sender: args.sender,
753
+ chain: "ETH",
754
+ type: "FORGET",
755
+ item_hash: itemHash,
756
+ item_type: "inline",
757
+ item_content: itemContent,
758
+ time: args.now ?? Date.now() / 1e3,
759
+ channel: args.channel ?? DEFAULT_ALEPH_CHANNEL
760
+ };
761
+ }
762
+ async function forgetAlephMessages(args) {
763
+ const unsignedMessage = await createUnsignedForgetMessage({
764
+ sender: args.sender,
765
+ hashes: args.hashes,
766
+ aggregates: args.aggregates,
767
+ reason: args.reason,
768
+ hasher: args.hasher,
769
+ channel: args.channel,
770
+ now: args.now
771
+ });
772
+ const message = await signAlephMessage(unsignedMessage, args.signer);
773
+ const { response, httpStatus } = await broadcastAlephMessage(message, {
774
+ apiHost: args.apiHost,
775
+ sync: args.sync,
776
+ fetch: args.fetch
777
+ });
778
+ return {
779
+ sender: args.sender,
780
+ itemHash: message.item_hash,
781
+ response,
782
+ httpStatus,
783
+ status: normalizeBroadcastStatus(httpStatus, response?.message_status)
784
+ };
785
+ }
786
+ async function cleanupFailedDeployment(args) {
787
+ try {
788
+ return await forgetAlephMessages({
789
+ sender: args.sender,
790
+ hashes: [args.instanceItemHash],
791
+ reason: args.reason ?? "Discard failed deployment attempt",
792
+ signer: args.signer,
793
+ hasher: args.hasher,
794
+ fetch: args.fetch,
795
+ channel: args.channel,
796
+ apiHost: args.apiHost
797
+ });
798
+ } catch (error) {
799
+ return {
800
+ error: error instanceof Error ? error.message : String(error)
801
+ };
802
+ }
803
+ }
804
+
805
+ // src/retention.ts
806
+ var SUCCESSFUL_DEPLOYMENTS_AGGREGATE_KEY = "uc-go-peer-successful-deployments";
807
+ function asString3(value) {
808
+ return typeof value === "string" && value.trim() ? value.trim() : null;
809
+ }
810
+ function normalizeRetentionRecord(record) {
811
+ if (!record || typeof record !== "object" || Array.isArray(record)) return null;
812
+ const candidate = record;
813
+ const instanceItemHash = asString3(candidate.instance_item_hash);
814
+ if (!instanceItemHash) return null;
815
+ return {
816
+ instance_item_hash: instanceItemHash,
817
+ rootfs_item_hash: asString3(candidate.rootfs_item_hash) ?? "",
818
+ site_item_hash: asString3(candidate.site_item_hash) ?? "",
819
+ rootfs_cid: asString3(candidate.rootfs_cid) ?? "",
820
+ site_url: asString3(candidate.site_url) ?? "",
821
+ relay_peer_id: asString3(candidate.relay_peer_id) ?? "",
822
+ rootfs_version: asString3(candidate.rootfs_version) ?? "",
823
+ deployed_at: asString3(candidate.deployed_at) ?? (/* @__PURE__ */ new Date()).toISOString(),
824
+ vm_name: asString3(candidate.vm_name) ?? ""
825
+ };
826
+ }
827
+ function normalizeRetentionLedger(value) {
828
+ if (Array.isArray(value)) {
829
+ return value.map(normalizeRetentionRecord).filter((entry) => entry != null);
830
+ }
831
+ if (value && typeof value === "object" && Array.isArray(value.deployments)) {
832
+ return value.deployments.map(normalizeRetentionRecord).filter((entry) => entry != null);
833
+ }
834
+ return [];
835
+ }
836
+ function retentionRecordId(record) {
837
+ return [record.instance_item_hash, record.rootfs_item_hash, record.site_item_hash].join(":");
838
+ }
839
+ function hashesFromRetentionRecord(record) {
840
+ return [record.instance_item_hash, record.rootfs_item_hash, record.site_item_hash].filter(Boolean);
841
+ }
842
+ async function fetchAggregateKey(args) {
843
+ const requestUrl = new URL(`/api/v0/aggregates/${args.address}.json`, args.apiHost ?? "https://api2.aleph.im");
844
+ requestUrl.searchParams.set("keys", args.key);
845
+ const response = await args.fetch(requestUrl.toString(), { cache: "no-cache" });
846
+ if (response.status === 404) return {};
847
+ if (!response.ok) {
848
+ throw new Error(`Aggregate request failed for key ${args.key}: ${response.status}`);
849
+ }
850
+ const payload = await response.json();
851
+ return payload?.data?.[args.key] ?? {};
852
+ }
853
+ async function createUnsignedAggregateMessage(args) {
854
+ const itemContent = JSON.stringify(args.content);
855
+ const itemHash = await args.hasher(itemContent);
856
+ return {
857
+ sender: args.sender,
858
+ chain: "ETH",
859
+ type: "AGGREGATE",
860
+ item_hash: itemHash,
861
+ item_type: "inline",
862
+ item_content: itemContent,
863
+ time: args.now ?? Date.now() / 1e3,
864
+ channel: args.channel ?? DEFAULT_ALEPH_CHANNEL
865
+ };
866
+ }
867
+ async function publishAggregateKey(args) {
868
+ const aggregateContent = {
869
+ address: args.sender,
870
+ key: args.key,
871
+ content: args.content,
872
+ time: args.now ?? Date.now() / 1e3
873
+ };
874
+ const unsignedMessage = await createUnsignedAggregateMessage({
875
+ sender: args.sender,
876
+ content: aggregateContent,
877
+ hasher: args.hasher,
878
+ channel: args.channel,
879
+ now: args.now
880
+ });
881
+ const message = await signAlephMessage(unsignedMessage, args.signer);
882
+ const { response, httpStatus } = await broadcastAlephMessage(message, {
883
+ apiHost: args.apiHost,
884
+ sync: true,
885
+ fetch: args.fetch
886
+ });
887
+ return {
888
+ itemHash: message.item_hash,
889
+ status: normalizeBroadcastStatus(httpStatus, response?.message_status),
890
+ response,
891
+ httpStatus
892
+ };
893
+ }
894
+ async function retainSuccessfulDeployments(args) {
895
+ const keepCount = Math.max(0, Number.parseInt(String(args.keepCount ?? 0), 10) || 0);
896
+ const aggregateKey = asString3(args.aggregateKey) ?? SUCCESSFUL_DEPLOYMENTS_AGGREGATE_KEY;
897
+ const currentRecord = normalizeRetentionRecord(args.currentRecord);
898
+ if (!currentRecord) {
899
+ throw new Error("retainSuccessfulDeployments requires a current deployment record with instance_item_hash.");
900
+ }
901
+ const existingValue = await fetchAggregateKey({
902
+ address: args.sender,
903
+ key: aggregateKey,
904
+ fetch: args.fetch,
905
+ apiHost: args.apiHost
906
+ });
907
+ const existingRecords = normalizeRetentionLedger(existingValue);
908
+ const mergedRecords = [currentRecord, ...existingRecords];
909
+ const uniqueRecords = [];
910
+ const seenIds = /* @__PURE__ */ new Set();
911
+ for (const record of mergedRecords) {
912
+ const id = retentionRecordId(record);
913
+ if (seenIds.has(id)) continue;
914
+ seenIds.add(id);
915
+ uniqueRecords.push(record);
916
+ }
917
+ const retainedRecords = keepCount > 0 ? uniqueRecords.slice(0, keepCount) : [];
918
+ const prunedRecords = keepCount > 0 ? uniqueRecords.slice(keepCount) : uniqueRecords;
919
+ const retainedHashes = new Set(retainedRecords.flatMap(hashesFromRetentionRecord));
920
+ const extraForgetHashes = [...new Set((args.extraForgetHashes ?? []).filter(Boolean))];
921
+ const forgetHashes = [.../* @__PURE__ */ new Set([...prunedRecords.flatMap(hashesFromRetentionRecord), ...extraForgetHashes])].filter(
922
+ (hash) => !retainedHashes.has(hash)
923
+ );
924
+ const aggregateContent = {
925
+ keep: keepCount,
926
+ updated_at: (/* @__PURE__ */ new Date()).toISOString(),
927
+ deployments: retainedRecords
928
+ };
929
+ const aggregatePublication = await publishAggregateKey({
930
+ sender: args.sender,
931
+ key: aggregateKey,
932
+ content: aggregateContent,
933
+ signer: args.signer,
934
+ hasher: args.hasher,
935
+ fetch: args.fetch,
936
+ channel: args.channel,
937
+ apiHost: args.apiHost,
938
+ now: args.now
939
+ });
940
+ let forgetResult = null;
941
+ if (forgetHashes.length > 0) {
942
+ forgetResult = await forgetAlephMessages({
943
+ sender: args.sender,
944
+ hashes: forgetHashes,
945
+ reason: args.reason ?? `Prune successful deployments beyond retention limit ${keepCount}`,
946
+ signer: args.signer,
947
+ hasher: args.hasher,
948
+ fetch: args.fetch,
949
+ channel: args.channel,
950
+ apiHost: args.apiHost,
951
+ now: args.now
952
+ });
953
+ }
954
+ return {
955
+ sender: args.sender,
956
+ aggregateKey,
957
+ keepCount,
958
+ aggregatePublication,
959
+ retainedRecords,
960
+ prunedRecords,
961
+ forgetHashes,
962
+ forgetResult
963
+ };
964
+ }
965
+
966
+ // src/aggregate-publication.ts
967
+ function createPortForwardAggregateContent(args) {
968
+ const existingPorts = normalizeExistingPortForwardEntry(args.existingAggregate?.[args.instanceItemHash]);
969
+ const mergedPorts = mergePortFlagMaps(existingPorts, requestedPortFlags(args.requestedPorts));
970
+ return {
971
+ address: args.sender,
972
+ key: "port-forwarding",
973
+ content: {
974
+ [args.instanceItemHash]: {
975
+ ports: mergedPorts
976
+ }
977
+ },
978
+ time: args.now ?? Date.now() / 1e3
979
+ };
980
+ }
981
+ async function createUnsignedAggregateMessage2(args) {
982
+ const itemContent = JSON.stringify(args.content);
983
+ const itemHash = await args.hasher(itemContent);
984
+ return {
985
+ sender: args.sender,
986
+ chain: "ETH",
987
+ type: "AGGREGATE",
988
+ item_hash: itemHash,
989
+ item_type: "inline",
990
+ item_content: itemContent,
991
+ time: args.now ?? Date.now() / 1e3,
992
+ channel: args.channel ?? DEFAULT_ALEPH_CHANNEL
993
+ };
994
+ }
995
+ async function ensureInstancePortForwards(args) {
996
+ const requestedPorts = requiredInstancePortForwards(args.manifest);
997
+ const aggregate = await fetchPortForwardAggregate(args.sender, {
998
+ apiHost: args.apiHost,
999
+ fetch: args.fetch
1000
+ });
1001
+ const content = createPortForwardAggregateContent({
1002
+ sender: args.sender,
1003
+ instanceItemHash: args.instanceItemHash,
1004
+ requestedPorts,
1005
+ existingAggregate: aggregate
1006
+ });
1007
+ const unsignedMessage = await createUnsignedAggregateMessage2({
1008
+ sender: args.sender,
1009
+ content,
1010
+ hasher: args.hasher,
1011
+ channel: args.channel
1012
+ });
1013
+ const message = await signAlephMessage(unsignedMessage, args.signer);
1014
+ const { response, httpStatus } = await broadcastAlephMessage(message, {
1015
+ apiHost: args.apiHost,
1016
+ sync: args.sync,
1017
+ fetch: args.fetch
1018
+ });
1019
+ const aggregateStatus = normalizeBroadcastStatus(httpStatus, response.message_status);
1020
+ if (aggregateStatus === "rejected") {
1021
+ throw new Error(`Port-forward aggregate was rejected by Aleph: ${JSON.stringify(response.details ?? response)}`);
1022
+ }
1023
+ return {
1024
+ aggregateItemHash: message.item_hash,
1025
+ aggregateStatus,
1026
+ requestedPorts
1027
+ };
1028
+ }
1029
+
1030
+ // src/instance-deployment.ts
1031
+ var SSH_PUBLIC_KEY_PATTERN = /^(ssh-rsa|ssh-ed25519|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521|sk-ssh-ed25519@openssh\.com|sk-ecdsa-sha2-nistp256@openssh\.com)\s+[A-Za-z0-9+/]+={0,3}(?:\s+.+)?$/;
1032
+ function normalizeSshPublicKey(value) {
1033
+ return value.split(/\r?\n/g).map((line) => line.trim()).filter(Boolean).join(" ").replace(/\s+/g, " ").trim();
1034
+ }
1035
+ function isValidSshPublicKey(value) {
1036
+ return SSH_PUBLIC_KEY_PATTERN.test(normalizeSshPublicKey(value));
1037
+ }
1038
+ function createReleaseMetadata(name, rootfsVersion, deployer = "shared-aleph-tooling") {
1039
+ return {
1040
+ name,
1041
+ rootfs_version: rootfsVersion,
1042
+ deployer
1043
+ };
1044
+ }
1045
+ function createInstanceContent(args) {
1046
+ const sshKey = normalizeSshPublicKey(args.sshPublicKey);
1047
+ if (!sshKey) {
1048
+ throw new Error("An SSH public key is required.");
1049
+ }
1050
+ if (!isValidSshPublicKey(sshKey)) {
1051
+ throw new Error("SSH public key must be a single valid .pub line.");
1052
+ }
1053
+ if (!args.rootfsItemHash || !/^[a-fA-F0-9]{64}$/.test(args.rootfsItemHash)) {
1054
+ throw new Error("rootfsItemHash must be a 64-character Aleph item hash.");
1055
+ }
1056
+ return {
1057
+ address: args.address,
1058
+ time: args.now ?? Date.now() / 1e3,
1059
+ allow_amend: false,
1060
+ metadata: createReleaseMetadata(args.name.trim(), args.rootfsVersion ?? "custom-rootfs", args.deployer),
1061
+ authorized_keys: [sshKey],
1062
+ environment: {
1063
+ internet: true,
1064
+ aleph_api: true,
1065
+ hypervisor: "qemu",
1066
+ reproducible: false,
1067
+ shared_cache: false
1068
+ },
1069
+ resources: {
1070
+ vcpus: Number(args.vcpus),
1071
+ memory: Number(args.memoryMiB),
1072
+ seconds: Number(args.seconds ?? 30)
1073
+ },
1074
+ payment: {
1075
+ type: "credit"
1076
+ },
1077
+ requirements: args.crnHash ? {
1078
+ node: {
1079
+ node_hash: args.crnHash
1080
+ }
1081
+ } : void 0,
1082
+ volumes: [],
1083
+ rootfs: {
1084
+ parent: {
1085
+ ref: args.rootfsItemHash,
1086
+ use_latest: true
1087
+ },
1088
+ persistence: "host",
1089
+ size_mib: Number(args.rootfsSizeMiB)
1090
+ }
1091
+ };
1092
+ }
1093
+ async function createUnsignedInstanceMessage(args) {
1094
+ const itemContent = JSON.stringify(args.content);
1095
+ const itemHash = await args.hasher(itemContent);
1096
+ return {
1097
+ sender: args.sender,
1098
+ chain: "ETH",
1099
+ type: "INSTANCE",
1100
+ item_hash: itemHash,
1101
+ item_type: "inline",
1102
+ item_content: itemContent,
1103
+ time: args.now ?? Date.now() / 1e3,
1104
+ channel: args.channel ?? DEFAULT_ALEPH_CHANNEL
1105
+ };
1106
+ }
1107
+ async function deployInstance(args) {
1108
+ const unsignedMessage = await createUnsignedInstanceMessage({
1109
+ sender: args.sender,
1110
+ content: args.content,
1111
+ hasher: args.hasher,
1112
+ channel: args.channel,
1113
+ now: args.now
1114
+ });
1115
+ const signature = await args.signer(unsignedMessage.sender, [unsignedMessage.chain, unsignedMessage.sender, unsignedMessage.type, unsignedMessage.item_hash].join("\n"));
1116
+ const message = {
1117
+ ...unsignedMessage,
1118
+ signature: signature.startsWith("0x") ? signature : `0x${signature}`
1119
+ };
1120
+ const { response, httpStatus } = await broadcastAlephMessage(message, {
1121
+ apiHost: args.apiHost,
1122
+ sync: args.sync,
1123
+ fetch: args.fetch
1124
+ });
1125
+ const status = httpStatus === 202 ? "pending" : typeof response.message_status === "string" ? response.message_status : "unknown";
1126
+ return {
1127
+ itemHash: message.item_hash,
1128
+ httpStatus,
1129
+ status,
1130
+ message,
1131
+ response,
1132
+ rejectionReason: status === "rejected" ? String(response.details ?? "Aleph rejected this deployment.") : null
1133
+ };
1134
+ }
1135
+
1136
+ // src/runtime.ts
1137
+ var DEFAULT_SCHEDULER_ALLOCATION_URL = "https://scheduler.api.aleph.cloud/api/v0/allocation";
1138
+ var DEFAULT_TWO_N_SIX_HASH_URL = "https://api.2n6.me/api/hash";
1139
+ function asString4(value) {
1140
+ return typeof value === "string" && value.trim() ? value.trim() : null;
1141
+ }
1142
+ function asNumber2(value) {
1143
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
1144
+ }
1145
+ function findCrnByHash(crns, crnHash) {
1146
+ return crns.find((crn) => crn.hash === crnHash) ?? null;
1147
+ }
1148
+ async function fetchSchedulerAllocation(itemHash, options) {
1149
+ const response = await options.fetch(
1150
+ `${options.schedulerAllocationUrl ?? DEFAULT_SCHEDULER_ALLOCATION_URL}/${itemHash}`,
1151
+ { cache: "no-cache" }
1152
+ );
1153
+ if (response.status === 404) return null;
1154
+ if (!response.ok) {
1155
+ throw new Error(`Scheduler allocation request failed: ${response.status}`);
1156
+ }
1157
+ const payload = await response.json();
1158
+ const node = payload?.node;
1159
+ return {
1160
+ source: "scheduler",
1161
+ crnHash: asString4(node?.node_id),
1162
+ crnUrl: asString4(node?.url),
1163
+ node: node ? {
1164
+ node_id: asString4(node.node_id) ?? void 0,
1165
+ url: asString4(node.url) ?? void 0,
1166
+ ipv6: asString4(node.ipv6),
1167
+ supports_ipv6: typeof node.supports_ipv6 === "boolean" ? node.supports_ipv6 : void 0
1168
+ } : null,
1169
+ vmIpv6: asString4(payload?.vm_ipv6),
1170
+ period: payload?.period ? {
1171
+ start_timestamp: asString4(payload.period.start_timestamp) ?? void 0,
1172
+ duration_seconds: asNumber2(payload.period.duration_seconds) ?? void 0
1173
+ } : null
1174
+ };
1175
+ }
1176
+ async function fetch2n6WebAccessUrl(itemHash, options) {
1177
+ const response = await options.fetch(
1178
+ `${options.twoN6HashUrl ?? DEFAULT_TWO_N_SIX_HASH_URL}/${itemHash}`,
1179
+ { cache: "no-cache" }
1180
+ );
1181
+ if (!response.ok) return null;
1182
+ const payload = await response.json();
1183
+ return {
1184
+ url: normalizeProxyUrl(payload?.url ?? payload?.subdomain),
1185
+ active: typeof payload?.active === "boolean" ? payload.active : null,
1186
+ subdomain: asString4(payload?.subdomain)
1187
+ };
1188
+ }
1189
+ async function fetchCrnExecutionMap(crnUrl, options) {
1190
+ const normalizedCrnUrl = String(crnUrl).replace(/\/+$/, "");
1191
+ for (const [version, suffix] of [
1192
+ ["v2", "/v2/about/executions/list"],
1193
+ ["v1", "/about/executions/list"]
1194
+ ]) {
1195
+ const requestUrl = `${normalizedCrnUrl}${suffix}`;
1196
+ const response = await options.fetch(requestUrl, {
1197
+ cache: "no-cache"
1198
+ });
1199
+ if (response.ok) {
1200
+ const payload = await response.json();
1201
+ return {
1202
+ version,
1203
+ payload: payload && typeof payload === "object" ? payload : null,
1204
+ requestUrl
1205
+ };
1206
+ }
1207
+ if (response.status !== 404) {
1208
+ return {
1209
+ version,
1210
+ payload: null,
1211
+ requestUrl
1212
+ };
1213
+ }
1214
+ }
1215
+ return {
1216
+ version: "v2",
1217
+ payload: null,
1218
+ requestUrl: `${normalizedCrnUrl}/v2/about/executions/list`
1219
+ };
1220
+ }
1221
+ function describeRuntimeAvailability(runtime) {
1222
+ const execution = runtime.execution ?? null;
1223
+ const mappedPorts = runtime.mappedPorts ?? {};
1224
+ const hostIpv4 = runtime.hostIpv4 ?? null;
1225
+ const proxyUrl = runtime.proxyUrl ?? null;
1226
+ const schedulerSource = runtime.allocation?.source ?? null;
1227
+ const webAccessActive = runtime.webAccess?.active ?? null;
1228
+ const mappedPortCount = Object.keys(mappedPorts).length;
1229
+ if (hostIpv4 && mappedPortCount > 0) {
1230
+ return {
1231
+ state: "ready",
1232
+ reason: null,
1233
+ schedulerSource,
1234
+ executionSeen: Boolean(execution),
1235
+ webAccessActive,
1236
+ mappedPortCount,
1237
+ proxyUrl
1238
+ };
1239
+ }
1240
+ if (!execution && proxyUrl && webAccessActive === false) {
1241
+ return {
1242
+ state: "proxy-reserved-inactive",
1243
+ reason: "Aleph reserved a proxy URL for the VM, but it is still inactive and the selected CRN has not exposed execution networking yet.",
1244
+ schedulerSource,
1245
+ executionSeen: false,
1246
+ webAccessActive,
1247
+ mappedPortCount,
1248
+ proxyUrl
1249
+ };
1250
+ }
1251
+ if (!execution && schedulerSource === "manual") {
1252
+ return {
1253
+ state: "crn-execution-missing",
1254
+ reason: "The deployment is pinned to a specific CRN, but that CRN is not exposing this VM in its execution list yet.",
1255
+ schedulerSource,
1256
+ executionSeen: false,
1257
+ webAccessActive,
1258
+ mappedPortCount,
1259
+ proxyUrl
1260
+ };
1261
+ }
1262
+ if (execution && !hostIpv4) {
1263
+ return {
1264
+ state: "execution-missing-host-ipv4",
1265
+ reason: "The CRN exposed an execution record, but it does not include a public host IPv4 yet.",
1266
+ schedulerSource,
1267
+ executionSeen: true,
1268
+ webAccessActive,
1269
+ mappedPortCount,
1270
+ proxyUrl
1271
+ };
1272
+ }
1273
+ if (execution && mappedPortCount === 0) {
1274
+ return {
1275
+ state: "execution-missing-port-mappings",
1276
+ reason: "The CRN exposed an execution record, but mapped ports are still empty.",
1277
+ schedulerSource,
1278
+ executionSeen: true,
1279
+ webAccessActive,
1280
+ mappedPortCount,
1281
+ proxyUrl
1282
+ };
1283
+ }
1284
+ return {
1285
+ state: "runtime-pending",
1286
+ reason: "Aleph has not exposed enough runtime networking details yet.",
1287
+ schedulerSource,
1288
+ executionSeen: Boolean(execution),
1289
+ webAccessActive,
1290
+ mappedPortCount,
1291
+ proxyUrl
1292
+ };
1293
+ }
1294
+ async function fetchVmRuntime(args) {
1295
+ const crns = args.crns ?? await fetchCrns({
1296
+ url: args.crnListUrl ?? DEFAULT_CRN_LIST_URL,
1297
+ fetch: args.fetch
1298
+ });
1299
+ const schedulerAllocation = await fetchSchedulerAllocation(args.itemHash, {
1300
+ fetch: args.fetch,
1301
+ schedulerAllocationUrl: args.schedulerAllocationUrl
1302
+ }).catch(() => null);
1303
+ const selectedCrn = args.crnHash ? findCrnByHash(crns, args.crnHash) : null;
1304
+ const allocation = schedulerAllocation ?? (selectedCrn ? {
1305
+ source: "manual",
1306
+ crnHash: selectedCrn.hash,
1307
+ crnUrl: selectedCrn.address,
1308
+ node: { url: selectedCrn.address },
1309
+ vmIpv6: null,
1310
+ period: null
1311
+ } : null);
1312
+ const webAccess = await fetch2n6WebAccessUrl(args.itemHash, {
1313
+ fetch: args.fetch,
1314
+ twoN6HashUrl: args.twoN6HashUrl
1315
+ }).catch(() => null);
1316
+ const webAccessUrl = webAccess?.url ?? null;
1317
+ let execution = null;
1318
+ let executionLookupBlocked = false;
1319
+ if (allocation?.crnUrl) {
1320
+ const executionLookup = await fetchCrnExecutionMap(allocation.crnUrl, {
1321
+ fetch: args.fetch
1322
+ });
1323
+ const executionPayload = executionLookup.payload?.[args.itemHash];
1324
+ if (executionPayload && typeof executionPayload === "object") {
1325
+ execution = normalizeExecution(executionPayload, allocation.crnUrl);
1326
+ if (!execution.networking.proxy_url && webAccessUrl) {
1327
+ execution.networking.proxy_url = webAccessUrl;
1328
+ }
1329
+ } else if (executionLookup.payload == null) {
1330
+ executionLookupBlocked = true;
1331
+ }
1332
+ }
1333
+ const hostIpv4 = execution?.networking?.host_ipv4 ?? execution?.networking?.ipv4 ?? null;
1334
+ const ipv6 = execution?.networking?.ipv6_ip ?? execution?.networking?.ipv6 ?? allocation?.vmIpv6 ?? null;
1335
+ const mappedPorts = execution?.networking?.mapped_ports ?? {};
1336
+ const sshPort = mappedPorts?.["22"]?.host ?? null;
1337
+ const proxyUrl = execution?.networking?.proxy_url ?? webAccessUrl ?? null;
1338
+ const diagnostics = describeRuntimeAvailability({
1339
+ allocation,
1340
+ execution,
1341
+ webAccess,
1342
+ hostIpv4,
1343
+ ipv6,
1344
+ proxyUrl,
1345
+ mappedPorts
1346
+ });
1347
+ return {
1348
+ allocation,
1349
+ execution,
1350
+ webAccess,
1351
+ webAccessUrl,
1352
+ hostIpv4,
1353
+ ipv6,
1354
+ proxyUrl,
1355
+ mappedPorts,
1356
+ diagnostics,
1357
+ sshCommand: hostIpv4 && sshPort ? `ssh root@${hostIpv4} -p ${sshPort}` : ipv6 ? `ssh root@${ipv6}` : null,
1358
+ selectedCrn,
1359
+ executionLookupBlocked
1360
+ };
1361
+ }
1362
+ async function waitForVmRuntime(args) {
1363
+ const attempts = Math.max(1, Number(args.attempts ?? 20));
1364
+ const delayMs = Math.max(0, Number(args.delayMs ?? 4e3));
1365
+ const sleep = args.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1366
+ let lastRuntime = await fetchVmRuntime(args);
1367
+ for (let attempt = 0; attempt < attempts - 1; attempt += 1) {
1368
+ if (lastRuntime.hostIpv4 && Object.keys(lastRuntime.mappedPorts ?? {}).length > 0) {
1369
+ return lastRuntime;
1370
+ }
1371
+ await sleep(delayMs);
1372
+ lastRuntime = await fetchVmRuntime(args);
1373
+ }
1374
+ if (lastRuntime.diagnostics) {
1375
+ lastRuntime.diagnostics.timedOut = true;
1376
+ }
1377
+ return lastRuntime;
1378
+ }
1379
+
1380
+ // src/guest.ts
1381
+ function proxyHostnameFromUrl(value) {
1382
+ try {
1383
+ return value ? new URL(value).hostname : null;
1384
+ } catch {
1385
+ return null;
1386
+ }
1387
+ }
1388
+ async function defaultHttpProbe(fetch, url, timeoutMs = 1e4) {
1389
+ try {
1390
+ const controller = new AbortController();
1391
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
1392
+ const response = await fetch(url, {
1393
+ method: "GET",
1394
+ redirect: "follow",
1395
+ signal: controller.signal
1396
+ });
1397
+ clearTimeout(timeout);
1398
+ return {
1399
+ ok: response.ok,
1400
+ status: response.status,
1401
+ url: response.url
1402
+ };
1403
+ } catch (error) {
1404
+ return {
1405
+ ok: false,
1406
+ error: error instanceof Error ? error.message : String(error)
1407
+ };
1408
+ }
1409
+ }
1410
+ async function notifyCrnAllocation(args) {
1411
+ const normalizedCrnUrl = typeof args.crnUrl === "string" ? args.crnUrl.trim().replace(/\/+$/, "") : "";
1412
+ if (!normalizedCrnUrl) {
1413
+ return {
1414
+ status: "skipped",
1415
+ reason: "No CRN URL available for allocation notification."
1416
+ };
1417
+ }
1418
+ try {
1419
+ const response = await args.fetch(`${normalizedCrnUrl}/control/allocation/notify`, {
1420
+ method: "POST",
1421
+ headers: {
1422
+ "content-type": "application/json"
1423
+ },
1424
+ body: JSON.stringify({ instance: args.itemHash })
1425
+ });
1426
+ const payload = await response.json().catch(() => null);
1427
+ if (!response.ok) {
1428
+ return {
1429
+ status: "unconfirmed",
1430
+ reason: `CRN allocation notify returned ${response.status}.`,
1431
+ payload
1432
+ };
1433
+ }
1434
+ return {
1435
+ status: "confirmed",
1436
+ payload
1437
+ };
1438
+ } catch (error) {
1439
+ return {
1440
+ status: "unconfirmed",
1441
+ reason: error instanceof Error ? error.message : String(error)
1442
+ };
1443
+ }
1444
+ }
1445
+ async function waitForSetupEndpoint(args) {
1446
+ const attempts = Math.max(1, Number(args.attempts ?? 15));
1447
+ const delayMs = Math.max(0, Number(args.delayMs ?? 4e3));
1448
+ const sleep = args.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1449
+ const url = `http://${args.hostIpv4}:${args.setupPort}/health`;
1450
+ let result = await defaultHttpProbe(args.fetch, url, Number(args.httpTimeoutMs ?? 1e4));
1451
+ for (let attempt = 1; attempt < attempts; attempt += 1) {
1452
+ if (result.ok) return result;
1453
+ await sleep(delayMs);
1454
+ result = await defaultHttpProbe(args.fetch, url, Number(args.httpTimeoutMs ?? 1e4));
1455
+ }
1456
+ return result;
1457
+ }
1458
+ async function configureUcGoPeer(args) {
1459
+ const controller = new AbortController();
1460
+ const timeout = setTimeout(() => controller.abort(), Number(args.timeoutMs ?? 18e4));
1461
+ const payload = {
1462
+ public_ipv4: args.hostIpv4,
1463
+ public_ipv6: args.publicIpv6 ?? void 0,
1464
+ proxy_url: args.proxyUrl ?? void 0,
1465
+ tcp_port: args.tcpPort ?? void 0,
1466
+ ws_port: args.wsPort ?? void 0,
1467
+ udp_port: args.udpPort ?? void 0,
1468
+ quic_port: args.quicPort ?? void 0,
1469
+ webrtc_port: args.webrtcPort ?? void 0
1470
+ };
1471
+ try {
1472
+ const response = await args.fetch(`http://${args.hostIpv4}:${args.setupPort}/configure`, {
1473
+ method: "POST",
1474
+ headers: {
1475
+ "content-type": "application/json"
1476
+ },
1477
+ body: JSON.stringify(payload),
1478
+ signal: controller.signal
1479
+ });
1480
+ const responsePayload = await response.json().catch(() => null);
1481
+ if (!response.ok) {
1482
+ throw new Error(
1483
+ `Relay setup request failed: ${response.status} ${typeof responsePayload === "string" ? responsePayload : JSON.stringify(responsePayload ?? {})}`
1484
+ );
1485
+ }
1486
+ return responsePayload;
1487
+ } finally {
1488
+ clearTimeout(timeout);
1489
+ }
1490
+ }
1491
+ async function fetchUcGoPeerMetadata(args) {
1492
+ const attempts = Math.max(1, Number(args.attempts ?? 60));
1493
+ const delayMs = Math.max(0, Number(args.delayMs ?? 3e3));
1494
+ const sleep = args.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1495
+ let lastPayload = null;
1496
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
1497
+ const controller = new AbortController();
1498
+ const timeout = setTimeout(() => controller.abort(), Number(args.timeoutMs ?? 18e4));
1499
+ try {
1500
+ const response = await args.fetch(`http://${args.hostIpv4}:${args.setupPort}/metadata`, {
1501
+ signal: controller.signal
1502
+ });
1503
+ const payload = await response.json().catch(() => null);
1504
+ lastPayload = payload;
1505
+ if (response.ok && payload && typeof payload === "object" && payload.status === "ready") {
1506
+ return payload;
1507
+ }
1508
+ if (response.status >= 500) {
1509
+ throw new Error(
1510
+ `Relay metadata request failed: ${response.status} ${typeof payload === "string" ? payload : JSON.stringify(payload ?? {})}`
1511
+ );
1512
+ }
1513
+ } finally {
1514
+ clearTimeout(timeout);
1515
+ }
1516
+ if (attempt < attempts - 1) {
1517
+ await sleep(delayMs);
1518
+ }
1519
+ }
1520
+ throw new Error(
1521
+ `Relay metadata did not become ready after ${attempts} attempts: ${typeof lastPayload === "string" ? lastPayload : JSON.stringify(lastPayload ?? {})}`
1522
+ );
1523
+ }
1524
+ async function verifyUcGoPeerReachability(args) {
1525
+ const checks = {};
1526
+ const mappedPorts = args.mappedPorts ?? {};
1527
+ const hostIpv4 = args.hostIpv4;
1528
+ const skippedInternalPorts = new Set((args.skipInternalPorts ?? ["80"]).map((value) => String(value)));
1529
+ const httpProbe = args.httpProbe ?? ((url, timeoutMs) => defaultHttpProbe(args.fetch, url, timeoutMs));
1530
+ if (hostIpv4) {
1531
+ for (const [internalPort, mapping] of Object.entries(mappedPorts)) {
1532
+ if (skippedInternalPorts.has(String(internalPort))) continue;
1533
+ if (mapping?.tcp === true && mapping?.host) {
1534
+ checks[`tcp:${internalPort}`] = {
1535
+ host: hostIpv4,
1536
+ port: mapping.host,
1537
+ ...await args.tcpProbe(hostIpv4, mapping.host, Number(args.tcpTimeoutMs ?? 5e3))
1538
+ };
1539
+ } else if (mapping?.udp === true && mapping?.host) {
1540
+ checks[`udp:${internalPort}`] = {
1541
+ host: hostIpv4,
1542
+ port: mapping.host,
1543
+ ok: null,
1544
+ note: "UDP mapping published; CI does not perform an application-level UDP handshake probe."
1545
+ };
1546
+ }
1547
+ }
1548
+ }
1549
+ if (args.proxyUrl && args.verifyProxyHttp !== false) {
1550
+ checks["https:proxy"] = await httpProbe(args.proxyUrl, Number(args.httpTimeoutMs ?? 1e4));
1551
+ }
1552
+ const proxyHostname = proxyHostnameFromUrl(args.proxyUrl);
1553
+ if (proxyHostname) {
1554
+ checks["tcp:proxy-443"] = await args.tcpProbe(proxyHostname, 443, Number(args.tcpTimeoutMs ?? 5e3));
1555
+ }
1556
+ const failedChecks = Object.entries(checks).filter(([, value]) => value?.ok === false);
1557
+ return {
1558
+ ok: failedChecks.length === 0,
1559
+ checks
1560
+ };
1561
+ }
1562
+ export {
1563
+ DEFAULT_ALEPH_API_HOST,
1564
+ DEFAULT_ALEPH_CHANNEL,
1565
+ DEFAULT_COUNTRY_LOOKUP_BASE_URL,
1566
+ DEFAULT_CRN_LIST_URL,
1567
+ DEFAULT_DNS_RESOLVE_URL,
1568
+ DEFAULT_INSTANCE_PORT_FORWARDS,
1569
+ DEFAULT_IPFS_GATEWAY_BASE_URL,
1570
+ DEFAULT_SCHEDULER_ALLOCATION_URL,
1571
+ DEFAULT_TWO_N_SIX_HASH_URL,
1572
+ ITEM_HASH_RE,
1573
+ SSH_PUBLIC_KEY_PATTERN,
1574
+ SUCCESSFUL_DEPLOYMENTS_AGGREGATE_KEY,
1575
+ broadcastAlephMessage,
1576
+ cleanupFailedDeployment,
1577
+ configureUcGoPeer,
1578
+ createInstanceContent,
1579
+ createPortForwardAggregateContent,
1580
+ createReleaseMetadata,
1581
+ createUnsignedAggregateMessage2 as createUnsignedAggregateMessage,
1582
+ createUnsignedForgetMessage,
1583
+ createUnsignedInstanceMessage,
1584
+ deployInstance,
1585
+ describeRejectedDeployment,
1586
+ describeRuntimeAvailability,
1587
+ enrichCrnsWithGeo,
1588
+ ensureInstancePortForwards,
1589
+ extractProxyUrl,
1590
+ extractReferenceHashes,
1591
+ fetch2n6WebAccessUrl,
1592
+ fetchAggregateKey,
1593
+ fetchCrnExecutionMap,
1594
+ fetchCrns,
1595
+ fetchMessageEnvelope,
1596
+ fetchPortForwardAggregate,
1597
+ fetchSchedulerAllocation,
1598
+ fetchUcGoPeerMetadata,
1599
+ fetchVmRuntime,
1600
+ findCrnByHash,
1601
+ forgetAlephMessages,
1602
+ inspectDeploymentResult,
1603
+ inspectMessageResult,
1604
+ isInvalidMessageFormatResponse,
1605
+ isRetryableBroadcastFailure,
1606
+ isValidSshPublicKey,
1607
+ listGeocodedCrns,
1608
+ mergePortFlagMaps,
1609
+ mergeRequiredPortForwards,
1610
+ messageTypeFromEnvelope,
1611
+ normalizeBroadcastStatus,
1612
+ normalizeExecution,
1613
+ normalizeExistingPortForwardEntry,
1614
+ normalizeMessageStatus,
1615
+ normalizeProxyUrl,
1616
+ normalizeSshPublicKey,
1617
+ notifyCrnAllocation,
1618
+ portForwardLabel,
1619
+ postBroadcastPayload,
1620
+ probeRootfsGateway,
1621
+ publishAggregateKey,
1622
+ rankCandidateCrns,
1623
+ requestedPortFlags,
1624
+ requiredInstancePortForwards,
1625
+ resolveRootfsReference,
1626
+ retainSuccessfulDeployments,
1627
+ selectPreferredCrn,
1628
+ signAlephMessage,
1629
+ signaturePayload,
1630
+ validateRootfsManifest,
1631
+ verifyRootfsExists,
1632
+ verifyUcGoPeerReachability,
1633
+ waitForDeploymentResult,
1634
+ waitForSetupEndpoint,
1635
+ waitForVmRuntime
1636
+ };