@telorun/kernel 0.41.0 → 0.43.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 (74) hide show
  1. package/dist/bundle/files-integrity.d.ts +25 -0
  2. package/dist/bundle/files-integrity.d.ts.map +1 -0
  3. package/dist/bundle/files-integrity.js +40 -0
  4. package/dist/bundle/files-integrity.js.map +1 -0
  5. package/dist/bundle/module-manifest.d.ts +22 -0
  6. package/dist/bundle/module-manifest.d.ts.map +1 -0
  7. package/dist/bundle/module-manifest.js +33 -0
  8. package/dist/bundle/module-manifest.js.map +1 -0
  9. package/dist/bundle/tar.d.ts +20 -0
  10. package/dist/bundle/tar.d.ts.map +1 -0
  11. package/dist/bundle/tar.js +63 -0
  12. package/dist/bundle/tar.js.map +1 -0
  13. package/dist/controller-loaders/bundle-builder.d.ts.map +1 -1
  14. package/dist/controller-loaders/bundle-builder.js +13 -2
  15. package/dist/controller-loaders/bundle-builder.js.map +1 -1
  16. package/dist/controllers/module/import-controller.d.ts +3 -0
  17. package/dist/controllers/module/import-controller.d.ts.map +1 -1
  18. package/dist/controllers/module/import-controller.js +7 -2
  19. package/dist/controllers/module/import-controller.js.map +1 -1
  20. package/dist/index.d.ts +9 -0
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +7 -0
  23. package/dist/index.js.map +1 -1
  24. package/dist/kernel.d.ts.map +1 -1
  25. package/dist/kernel.js +8 -2
  26. package/dist/kernel.js.map +1 -1
  27. package/dist/manifest-sources/local-manifest-cache-source.d.ts +2 -2
  28. package/dist/manifest-sources/local-manifest-cache-source.d.ts.map +1 -1
  29. package/dist/manifest-sources/local-manifest-cache-source.js +24 -90
  30. package/dist/manifest-sources/local-manifest-cache-source.js.map +1 -1
  31. package/dist/transports/oci/docker-credentials.d.ts +10 -0
  32. package/dist/transports/oci/docker-credentials.d.ts.map +1 -0
  33. package/dist/transports/oci/docker-credentials.js +75 -0
  34. package/dist/transports/oci/docker-credentials.js.map +1 -0
  35. package/dist/transports/oci/oci-client.d.ts +51 -0
  36. package/dist/transports/oci/oci-client.d.ts.map +1 -0
  37. package/dist/transports/oci/oci-client.js +170 -0
  38. package/dist/transports/oci/oci-client.js.map +1 -0
  39. package/dist/transports/oci/oci-ref.d.ts +21 -0
  40. package/dist/transports/oci/oci-ref.d.ts.map +1 -0
  41. package/dist/transports/oci/oci-ref.js +36 -0
  42. package/dist/transports/oci/oci-ref.js.map +1 -0
  43. package/dist/transports/oci/oci-transport.d.ts +29 -0
  44. package/dist/transports/oci/oci-transport.d.ts.map +1 -0
  45. package/dist/transports/oci/oci-transport.js +142 -0
  46. package/dist/transports/oci/oci-transport.js.map +1 -0
  47. package/dist/transports/registry-transport.d.ts +23 -0
  48. package/dist/transports/registry-transport.d.ts.map +1 -0
  49. package/dist/transports/registry-transport.js +225 -0
  50. package/dist/transports/registry-transport.js.map +1 -0
  51. package/dist/transports/transport-registry.d.ts +40 -0
  52. package/dist/transports/transport-registry.d.ts.map +1 -0
  53. package/dist/transports/transport-registry.js +70 -0
  54. package/dist/transports/transport-registry.js.map +1 -0
  55. package/dist/transports/transport.d.ts +92 -0
  56. package/dist/transports/transport.d.ts.map +1 -0
  57. package/dist/transports/transport.js +2 -0
  58. package/dist/transports/transport.js.map +1 -0
  59. package/package.json +6 -3
  60. package/src/bundle/files-integrity.ts +46 -0
  61. package/src/bundle/module-manifest.ts +49 -0
  62. package/src/bundle/tar.ts +78 -0
  63. package/src/controller-loaders/bundle-builder.ts +14 -2
  64. package/src/controllers/module/import-controller.ts +7 -2
  65. package/src/index.ts +22 -0
  66. package/src/kernel.ts +7 -2
  67. package/src/manifest-sources/local-manifest-cache-source.ts +25 -98
  68. package/src/transports/oci/docker-credentials.ts +85 -0
  69. package/src/transports/oci/oci-client.ts +237 -0
  70. package/src/transports/oci/oci-ref.ts +53 -0
  71. package/src/transports/oci/oci-transport.ts +192 -0
  72. package/src/transports/registry-transport.ts +281 -0
  73. package/src/transports/transport-registry.ts +91 -0
  74. package/src/transports/transport.ts +112 -0
@@ -0,0 +1,225 @@
1
+ import { DEFAULT_MANIFEST_FILENAME, HttpSource, IntegrityError, RegistrySource, isRegistryRef, parseModuleRef, splitIntegrity, } from "@telorun/analyzer";
2
+ import { createHash } from "crypto";
3
+ import { computeFilesIntegrity, injectFilesIntegrity } from "../bundle/files-integrity.js";
4
+ import { readOwnerManifest } from "../bundle/module-manifest.js";
5
+ import { makeTarGz, readTarGz, toPayloadFiles } from "../bundle/tar.js";
6
+ const DEFAULT_REGISTRY_URL = "https://registry.telo.run";
7
+ const HTTP_NAMESPACE = "__http";
8
+ const QUERY_HASH_LENGTH = 12;
9
+ const MAX_PUSH_ATTEMPTS = 4;
10
+ const PUSH_BASE_DELAY_MS = 1000;
11
+ /** Registry / object-storage backends treat 408/425/429/5xx as transient. */
12
+ function isRetryableStatus(status) {
13
+ return status === 408 || status === 425 || status === 429 || status >= 500;
14
+ }
15
+ function sleep(ms) {
16
+ return new Promise((resolve) => setTimeout(resolve, ms));
17
+ }
18
+ /** Mirror `HttpSource.read`'s `fetchUrl` derivation: when the URL does not
19
+ * already point at a YAML file, append `/telo.yaml`, so a raw import URL and
20
+ * the canonical source it resolves to map to the same cache path. */
21
+ function normalizePathname(rawUrl, parsed) {
22
+ let pathname = parsed.pathname;
23
+ if (!rawUrl.includes(".yaml")) {
24
+ pathname = pathname.endsWith("/")
25
+ ? `${pathname}${DEFAULT_MANIFEST_FILENAME}`
26
+ : `${pathname}/${DEFAULT_MANIFEST_FILENAME}`;
27
+ }
28
+ return pathname;
29
+ }
30
+ /** Short hash of `search + hash` so two URLs that differ only in query /
31
+ * fragment do not collide at the same cache path. */
32
+ function disambiguatePath(pathname, search, hash) {
33
+ if (!search && !hash)
34
+ return pathname;
35
+ const digest = createHash("sha256")
36
+ .update(search + hash)
37
+ .digest("hex")
38
+ .slice(0, QUERY_HASH_LENGTH);
39
+ const dotIdx = pathname.lastIndexOf(".");
40
+ const slashIdx = pathname.lastIndexOf("/");
41
+ const ext = dotIdx > slashIdx ? pathname.slice(dotIdx) : "";
42
+ const base = pathname.slice(0, pathname.length - ext.length);
43
+ return `${base}.${digest}${ext}`;
44
+ }
45
+ /** The default HTTP transport: bare `namespace/name@version` registry refs and
46
+ * direct `https://…` URLs, resolving against `registry.telo.run` (or a
47
+ * configured registry). Its resolution `source` composes the browser-safe
48
+ * `RegistrySource` / `HttpSource` from `analyzer`; the Node-only management
49
+ * methods live here. This is the fallback transport for any ref that carries
50
+ * no owning scheme, so `oci://` (or a future `s3://`) never falls through to
51
+ * it — those refs are claimed by their own transport's `supports()`. */
52
+ export class RegistryTransport {
53
+ constructor(registryUrl = DEFAULT_REGISTRY_URL) {
54
+ this.registryUrl = registryUrl;
55
+ this.registrySource = new RegistrySource(registryUrl);
56
+ this.httpSource = new HttpSource();
57
+ const pick = (ref) => this.httpSource.supports(ref) ? this.httpSource : this.registrySource;
58
+ this.source = {
59
+ supports: (url) => this.supports(url),
60
+ read: (url) => pick(url).read(url),
61
+ resolveRelative: (base, relative) => pick(base).resolveRelative(base, relative),
62
+ };
63
+ }
64
+ supports(ref) {
65
+ const { base } = splitIntegrity(ref);
66
+ return base.startsWith("http://") || base.startsWith("https://") || isRegistryRef(ref);
67
+ }
68
+ cacheLocation(ref) {
69
+ const url = splitIntegrity(ref).base;
70
+ const trimmedRegistry = this.registryUrl.replace(/\/+$/, "");
71
+ // 1. Registry ref form: namespace/name@version
72
+ if (isRegistryRef(url)) {
73
+ let parsed;
74
+ try {
75
+ parsed = parseModuleRef(url);
76
+ }
77
+ catch {
78
+ return null;
79
+ }
80
+ return [parsed.modulePath, parsed.version, DEFAULT_MANIFEST_FILENAME];
81
+ }
82
+ // 2. HTTP(S) URL — a direct registry URL or arbitrary external.
83
+ if (url.startsWith("http://") || url.startsWith("https://")) {
84
+ let parsed;
85
+ try {
86
+ parsed = new URL(url);
87
+ }
88
+ catch {
89
+ return null;
90
+ }
91
+ const pathname = normalizePathname(url, parsed);
92
+ // 2a. On the configured registry, no query/fragment: fold into the
93
+ // registry layout so a ref and a direct URL land on the same file.
94
+ const normalizedUrl = `${parsed.protocol}//${parsed.host}${pathname}`;
95
+ if (!parsed.search &&
96
+ !parsed.hash &&
97
+ (normalizedUrl === trimmedRegistry || normalizedUrl.startsWith(`${trimmedRegistry}/`))) {
98
+ const rel = normalizedUrl.slice(trimmedRegistry.length + 1);
99
+ if (!rel)
100
+ return null;
101
+ return rel.split("/");
102
+ }
103
+ // 2b. Arbitrary HTTP(S) → __http subtree, query-hash suffix on collision.
104
+ const cleanPath = pathname.startsWith("/") ? pathname.slice(1) : pathname;
105
+ const disambiguated = disambiguatePath(cleanPath, parsed.search, parsed.hash);
106
+ return [HTTP_NAMESPACE, parsed.host, ...disambiguated.split("/")];
107
+ }
108
+ return null;
109
+ }
110
+ async listVersions(ref) {
111
+ // Only bare registry refs are version-enumerable — a direct `https://` URL
112
+ // has no version-list endpoint.
113
+ if (!isRegistryRef(ref))
114
+ return null;
115
+ const { modulePath } = parseModuleRef(ref);
116
+ const url = `${this.registryUrl.replace(/\/+$/, "")}/${modulePath}`;
117
+ const res = await fetch(url, { headers: { accept: "application/json" } });
118
+ if (res.status === 404)
119
+ return null;
120
+ if (!res.ok) {
121
+ throw new Error(`Registry returned ${res.status} ${res.statusText} for ${modulePath}`);
122
+ }
123
+ const body = (await res.json());
124
+ return Array.isArray(body.versions) ? body.versions : [];
125
+ }
126
+ async fetchArtifact(ref) {
127
+ // `read` verifies the manifest bytes against the inline `#sha256-...` hash.
128
+ const { text: manifest, source } = await this.source.read(ref);
129
+ const meta = readOwnerManifest(manifest);
130
+ if (!meta.declaresFiles)
131
+ return { manifest, files: [] };
132
+ // The payload rides beside the manifest as `module.tar.gz`.
133
+ const tarUrl = source.replace(/\/telo\.yaml$/, "/module.tar.gz");
134
+ const res = await fetch(tarUrl);
135
+ if (!res.ok) {
136
+ throw new Error(`could not fetch bundle ${tarUrl}: ${res.status} ${res.statusText}`);
137
+ }
138
+ const files = toPayloadFiles(await readTarGz(Buffer.from(await res.arrayBuffer())));
139
+ // Verify the payload against the manifest's `filesIntegrity` before handing
140
+ // it back — a mismatch is terminal (a tampered bundle must never be used).
141
+ // The manifest that carries the hash is itself pinned by the inline hash.
142
+ if (meta.filesIntegrity) {
143
+ const actual = await computeFilesIntegrity(files);
144
+ if (actual !== meta.filesIntegrity) {
145
+ throw new IntegrityError(`Integrity check failed for bundle ${tarUrl}: filesIntegrity expected ` +
146
+ `${meta.filesIntegrity}, got ${actual}. The payload does not match the recorded ` +
147
+ `hash — the module may have been tampered with or republished.`);
148
+ }
149
+ }
150
+ return { manifest, files };
151
+ }
152
+ async publish(destination, bundle, opts = {}) {
153
+ // Pin the payload in the manifest before it enters the tarball, then choose
154
+ // the artifact body: a `module.tar.gz` when there are files, else raw YAML.
155
+ let manifest = bundle.manifest;
156
+ let body = manifest;
157
+ let contentType = "text/yaml";
158
+ let urlSuffix = "";
159
+ if (bundle.files.length > 0) {
160
+ manifest = injectFilesIntegrity(manifest, await computeFilesIntegrity(bundle.files));
161
+ const entries = [
162
+ { name: DEFAULT_MANIFEST_FILENAME, content: manifest },
163
+ ...bundle.files.map((f) => ({ name: f.name, content: Buffer.from(f.content) })),
164
+ ];
165
+ body = await makeTarGz(entries);
166
+ contentType = "application/gzip";
167
+ urlSuffix = "/module.tar.gz";
168
+ }
169
+ const { namespace, name, version } = readOwnerManifest(manifest);
170
+ if (!namespace || !name || !version) {
171
+ throw new Error("metadata must include namespace, name, and version.");
172
+ }
173
+ const identity = { namespace, name, version };
174
+ const base = `${destination.replace(/\/+$/, "")}/${identity.namespace}/${identity.name}/${identity.version}`;
175
+ const url = `${base}${urlSuffix}`;
176
+ const label = `${identity.namespace}/${identity.name}@${identity.version}`;
177
+ const headers = { "content-type": contentType };
178
+ if (opts.token)
179
+ headers.authorization = `Bearer ${opts.token}`;
180
+ let res = null;
181
+ let networkErr = null;
182
+ for (let attempt = 1; attempt <= MAX_PUSH_ATTEMPTS; attempt++) {
183
+ networkErr = null;
184
+ try {
185
+ res = await fetch(url, { method: "PUT", headers, body });
186
+ }
187
+ catch (err) {
188
+ networkErr = err;
189
+ res = null;
190
+ }
191
+ const transient = networkErr != null || (res != null && isRetryableStatus(res.status));
192
+ if (!transient)
193
+ break;
194
+ if (attempt === MAX_PUSH_ATTEMPTS)
195
+ break;
196
+ const reason = networkErr
197
+ ? `network error: ${networkErr instanceof Error ? networkErr.message : String(networkErr)}`
198
+ : `HTTP ${res.status}`;
199
+ // Drain the body so the underlying connection can be reused for the retry.
200
+ if (res)
201
+ await res.text().catch(() => { });
202
+ const delayMs = PUSH_BASE_DELAY_MS * 2 ** (attempt - 1) + Math.floor(Math.random() * 250);
203
+ opts.onRetry?.({ reason, attempt, maxAttempts: MAX_PUSH_ATTEMPTS, delayMs });
204
+ await sleep(delayMs);
205
+ }
206
+ if (networkErr) {
207
+ throw new Error(`Network error: ${networkErr instanceof Error ? networkErr.message : String(networkErr)} ` +
208
+ `(after ${MAX_PUSH_ATTEMPTS} attempts)`);
209
+ }
210
+ if (!res.ok) {
211
+ const ct = res.headers.get("content-type") ?? "";
212
+ const errBody = ct.includes("application/json") ? await res.json() : await res.text();
213
+ throw new Error(`Push failed (${res.status}): ${JSON.stringify(errBody)}`);
214
+ }
215
+ return { label, url };
216
+ }
217
+ canonicalizeSiblingRef(_destination, _relativeSource, sibling) {
218
+ // An HTTP registry path defaults to the sibling's own `<namespace>/<name>`.
219
+ if (!sibling.namespace || !sibling.name) {
220
+ throw new Error("a relative import canonicalized to an HTTP registry needs the sibling's metadata.namespace and metadata.name.");
221
+ }
222
+ return `${sibling.namespace}/${sibling.name}@${sibling.version}`;
223
+ }
224
+ }
225
+ //# sourceMappingURL=registry-transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry-transport.js","sourceRoot":"","sources":["../../src/transports/registry-transport.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import type { ManifestSource } from "@telorun/analyzer";
2
+ import type { FetchedArtifact, PublishBundle, PublishOptions, PublishResult, Transport } from "./transport.js";
3
+ /** Dispatches ref-scheme-specific operations to the transport that owns a ref.
4
+ * The loader, cache source, `upgrade`, and `publish` consult this instead of
5
+ * branching on ref shape. `RegistryTransport` is always last so it is the
6
+ * fallback for bare / `https` refs, and a scheme-owning transport (OCI, later
7
+ * S3) claims its refs via `supports()` before the fallback is reached. */
8
+ export declare class TransportRegistry {
9
+ private readonly transports;
10
+ constructor(transports: Transport[]);
11
+ /** The transport that owns `ref`, or `undefined` if none does. */
12
+ forRef(ref: string): Transport | undefined;
13
+ /** The resolution `ManifestSource`s of every registered transport, in order —
14
+ * the browser-safe subset the loader appends to its source chain. */
15
+ sources(): ManifestSource[];
16
+ /** Cache-path segments for `ref`, from its owning transport; `null` when no
17
+ * transport owns it or the ref is not cacheable. */
18
+ cacheLocation(ref: string): string[] | null;
19
+ /** Published versions for `ref` via its owning transport; `null` when the
20
+ * module is unpublished. Throws when no transport owns the ref. */
21
+ listVersions(ref: string): Promise<string[] | null>;
22
+ /** Full artifact for `ref` via its owning transport. Throws when no transport
23
+ * owns the ref. */
24
+ fetchArtifact(ref: string): Promise<FetchedArtifact>;
25
+ /** Publish `bundle` to `destination` via the transport its scheme selects.
26
+ * Throws when no transport owns the destination. */
27
+ publish(destination: string, bundle: PublishBundle, opts?: PublishOptions): Promise<PublishResult>;
28
+ private require;
29
+ }
30
+ /** The default transport set. Scheme-owning transports come first; the
31
+ * `RegistryTransport` is last, the fallback for bare / `https` refs. OCI (and
32
+ * later S3) claim their `oci://` / `s3://` refs before the fallback is reached. */
33
+ export declare function defaultTransports(registryUrl?: string): Transport[];
34
+ /** A `TransportRegistry` seeded with {@link defaultTransports}, memoized per
35
+ * `registryUrl`. The default transports are stateless config (a fresh
36
+ * `OciClient` with its own token cache is created per OCI operation), so one
37
+ * shared instance per registry URL is safe — and avoids re-instantiating the
38
+ * whole set on hot paths like `cachePathForCanonical`. */
39
+ export declare function defaultTransportRegistry(registryUrl?: string): TransportRegistry;
40
+ //# sourceMappingURL=transport-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport-registry.d.ts","sourceRoot":"","sources":["../../src/transports/transport-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAIxD,OAAO,KAAK,EACV,eAAe,EACf,aAAa,EACb,cAAc,EACd,aAAa,EACb,SAAS,EACV,MAAM,gBAAgB,CAAC;AAExB;;;;2EAI2E;AAC3E,qBAAa,iBAAiB;IAChB,OAAO,CAAC,QAAQ,CAAC,UAAU;gBAAV,UAAU,EAAE,SAAS,EAAE;IAEpD,kEAAkE;IAClE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAI1C;0EACsE;IACtE,OAAO,IAAI,cAAc,EAAE;IAI3B;yDACqD;IACrD,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI;IAI3C;wEACoE;IACpE,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC;IAInD;wBACoB;IACpB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAIpD;yDACqD;IACrD,OAAO,CACL,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,aAAa,EACrB,IAAI,CAAC,EAAE,cAAc,GACpB,OAAO,CAAC,aAAa,CAAC;IAIzB,OAAO,CAAC,OAAO;CAOhB;AAED;;oFAEoF;AACpF,wBAAgB,iBAAiB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,EAAE,CAEnE;AAID;;;;2DAI2D;AAC3D,wBAAgB,wBAAwB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAQhF"}
@@ -0,0 +1,70 @@
1
+ import { OciTransport } from "./oci/oci-transport.js";
2
+ import { RegistryTransport } from "./registry-transport.js";
3
+ /** Dispatches ref-scheme-specific operations to the transport that owns a ref.
4
+ * The loader, cache source, `upgrade`, and `publish` consult this instead of
5
+ * branching on ref shape. `RegistryTransport` is always last so it is the
6
+ * fallback for bare / `https` refs, and a scheme-owning transport (OCI, later
7
+ * S3) claims its refs via `supports()` before the fallback is reached. */
8
+ export class TransportRegistry {
9
+ constructor(transports) {
10
+ this.transports = transports;
11
+ }
12
+ /** The transport that owns `ref`, or `undefined` if none does. */
13
+ forRef(ref) {
14
+ return this.transports.find((t) => t.supports(ref));
15
+ }
16
+ /** The resolution `ManifestSource`s of every registered transport, in order —
17
+ * the browser-safe subset the loader appends to its source chain. */
18
+ sources() {
19
+ return this.transports.map((t) => t.source);
20
+ }
21
+ /** Cache-path segments for `ref`, from its owning transport; `null` when no
22
+ * transport owns it or the ref is not cacheable. */
23
+ cacheLocation(ref) {
24
+ return this.forRef(ref)?.cacheLocation(ref) ?? null;
25
+ }
26
+ /** Published versions for `ref` via its owning transport; `null` when the
27
+ * module is unpublished. Throws when no transport owns the ref. */
28
+ listVersions(ref) {
29
+ return this.require(ref).listVersions(ref);
30
+ }
31
+ /** Full artifact for `ref` via its owning transport. Throws when no transport
32
+ * owns the ref. */
33
+ fetchArtifact(ref) {
34
+ return this.require(ref).fetchArtifact(ref);
35
+ }
36
+ /** Publish `bundle` to `destination` via the transport its scheme selects.
37
+ * Throws when no transport owns the destination. */
38
+ publish(destination, bundle, opts) {
39
+ return this.require(destination).publish(destination, bundle, opts);
40
+ }
41
+ require(ref) {
42
+ const transport = this.forRef(ref);
43
+ if (!transport) {
44
+ throw new Error(`no transport owns ref '${ref}'`);
45
+ }
46
+ return transport;
47
+ }
48
+ }
49
+ /** The default transport set. Scheme-owning transports come first; the
50
+ * `RegistryTransport` is last, the fallback for bare / `https` refs. OCI (and
51
+ * later S3) claim their `oci://` / `s3://` refs before the fallback is reached. */
52
+ export function defaultTransports(registryUrl) {
53
+ return [new OciTransport(), new RegistryTransport(registryUrl)];
54
+ }
55
+ const defaultRegistryCache = new Map();
56
+ /** A `TransportRegistry` seeded with {@link defaultTransports}, memoized per
57
+ * `registryUrl`. The default transports are stateless config (a fresh
58
+ * `OciClient` with its own token cache is created per OCI operation), so one
59
+ * shared instance per registry URL is safe — and avoids re-instantiating the
60
+ * whole set on hot paths like `cachePathForCanonical`. */
61
+ export function defaultTransportRegistry(registryUrl) {
62
+ const key = registryUrl ?? "";
63
+ let cached = defaultRegistryCache.get(key);
64
+ if (!cached) {
65
+ cached = new TransportRegistry(defaultTransports(registryUrl));
66
+ defaultRegistryCache.set(key, cached);
67
+ }
68
+ return cached;
69
+ }
70
+ //# sourceMappingURL=transport-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport-registry.js","sourceRoot":"","sources":["../../src/transports/transport-registry.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAS5D;;;;2EAI2E;AAC3E,MAAM,OAAO,iBAAiB;IAC5B,YAA6B,UAAuB;QAAvB,eAAU,GAAV,UAAU,CAAa;IAAG,CAAC;IAExD,kEAAkE;IAClE,MAAM,CAAC,GAAW;QAChB,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;IACtD,CAAC;IAED;0EACsE;IACtE,OAAO;QACL,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAC9C,CAAC;IAED;yDACqD;IACrD,aAAa,CAAC,GAAW;QACvB,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,aAAa,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;IACtD,CAAC;IAED;wEACoE;IACpE,YAAY,CAAC,GAAW;QACtB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;IAC7C,CAAC;IAED;wBACoB;IACpB,aAAa,CAAC,GAAW;QACvB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IAC9C,CAAC;IAED;yDACqD;IACrD,OAAO,CACL,WAAmB,EACnB,MAAqB,EACrB,IAAqB;QAErB,OAAO,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;IACtE,CAAC;IAEO,OAAO,CAAC,GAAW;QACzB,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,0BAA0B,GAAG,GAAG,CAAC,CAAC;QACpD,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;CACF;AAED;;oFAEoF;AACpF,MAAM,UAAU,iBAAiB,CAAC,WAAoB;IACpD,OAAO,CAAC,IAAI,YAAY,EAAE,EAAE,IAAI,iBAAiB,CAAC,WAAW,CAAC,CAAC,CAAC;AAClE,CAAC;AAED,MAAM,oBAAoB,GAAG,IAAI,GAAG,EAA6B,CAAC;AAElE;;;;2DAI2D;AAC3D,MAAM,UAAU,wBAAwB,CAAC,WAAoB;IAC3D,MAAM,GAAG,GAAG,WAAW,IAAI,EAAE,CAAC;IAC9B,IAAI,MAAM,GAAG,oBAAoB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC3C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,GAAG,IAAI,iBAAiB,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC,CAAC;QAC/D,oBAAoB,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACxC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,92 @@
1
+ import type { ManifestSource } from "@telorun/analyzer";
2
+ import type { PayloadFile } from "../bundle/files-integrity.js";
3
+ /** The full module artifact a transport delivers: the `telo.yaml` bytes plus
4
+ * the decompressed `files:` payload (empty for a manifest-only module). The
5
+ * manifest is already verified against the import's inline hash; the payload
6
+ * against the manifest-embedded `filesIntegrity`. */
7
+ export interface FetchedArtifact {
8
+ manifest: string;
9
+ files: PayloadFile[];
10
+ }
11
+ /** The module bundle handed to a transport for publishing: the final,
12
+ * already-analyzed / pinned / canonicalized `telo.yaml` bytes plus the `files:`
13
+ * payload (empty for a manifest-only module). The transport is responsible for
14
+ * pinning the payload (`filesIntegrity`) and for the artifact shape it writes
15
+ * (HTTP: `telo.yaml` + `module.tar.gz`; OCI: a single blob). */
16
+ export interface PublishBundle {
17
+ manifest: string;
18
+ files: PayloadFile[];
19
+ }
20
+ export interface PublishResult {
21
+ /** Human-readable `<ns>/<name>@<version>` label of what was pushed. */
22
+ label: string;
23
+ /** The location the artifact was written to. */
24
+ url: string;
25
+ }
26
+ /** Identity of a sibling library, read from its own manifest, that a relative
27
+ * import canonicalizes to. `version` is always required; `namespace`/`name` are
28
+ * used only by transports whose location is metadata-derived (HTTP registry). */
29
+ export interface SiblingIdentity {
30
+ namespace?: string;
31
+ name?: string;
32
+ version: string;
33
+ }
34
+ export interface PublishOptions {
35
+ /** Bearer token for registries that require auth. */
36
+ token?: string;
37
+ /** Notified before each backoff sleep on a transient push failure, so the
38
+ * caller can surface retry progress. */
39
+ onRetry?: (info: {
40
+ reason: string;
41
+ attempt: number;
42
+ maxAttempts: number;
43
+ delayMs: number;
44
+ }) => void;
45
+ }
46
+ /** A Transport owns everything ref-scheme-specific about a module's lifecycle:
47
+ * resolution (through its `ManifestSource`), cache placement, version
48
+ * enumeration, full-artifact fetch, and publish. Registering a transport is
49
+ * the only thing needed to add a backend — the loader, cache, `upgrade`, and
50
+ * `publish` never branch on scheme again; they ask the {@link TransportRegistry}
51
+ * which transport owns a ref and delegate.
52
+ *
53
+ * A Transport *composes* a resolution `ManifestSource`, it does not extend it:
54
+ * `ManifestSource` is the browser-safe resolution primitive (also implemented
55
+ * by the cache / local / memory sources, which have no versions and nothing to
56
+ * publish), so it stays in `analyzer`, while the Node-only management methods
57
+ * (`cacheLocation` and, in later phases, `listVersions` / `fetchArtifact` /
58
+ * `publish`) live on the Transport here in `kernel`. */
59
+ export interface Transport {
60
+ /** True when this transport owns the given ref (or publish destination). */
61
+ supports(ref: string): boolean;
62
+ /** The resolution primitive: fetch + verify `telo.yaml`, resolve relatives.
63
+ * Browser-safe for a browser-reachable transport (HTTP/registry), so it can
64
+ * live in `analyzer`; a Node-only transport has no browser-safe source. */
65
+ readonly source: ManifestSource;
66
+ /** Deterministic cache-path segments for a ref, joined under the cache root by
67
+ * the cache source. Returns `null` when the ref is not cacheable here
68
+ * (unsupported scheme, malformed ref, or path-traversal in the ref). */
69
+ cacheLocation(ref: string): string[] | null;
70
+ /** The versions published for the module `ref` names, newest-first order not
71
+ * guaranteed (the caller sorts). Returns `null` when the module is not
72
+ * published (e.g. a 404), distinct from `[]` (published, no versions). Used
73
+ * by `telo upgrade`. */
74
+ listVersions(ref: string): Promise<string[] | null>;
75
+ /** Retrieve the full artifact for `ref` — the `telo.yaml` and its `files:`
76
+ * payload — verifying the manifest against the inline hash and the payload
77
+ * against the manifest's `filesIntegrity`. Used by `telo install`; subsumes
78
+ * the out-of-band bundle fetch that used to sit outside the source chain. */
79
+ fetchArtifact(ref: string): Promise<FetchedArtifact>;
80
+ /** Push `bundle` to `destination` (a base ref / repo whose scheme this
81
+ * transport owns), pinning the payload and writing the transport-native
82
+ * artifact shape. Throws on failure. Used by `telo publish`. */
83
+ publish(destination: string, bundle: PublishBundle, opts?: PublishOptions): Promise<PublishResult>;
84
+ /** Canonicalize a relative sibling import (`../lib`) declared in a module
85
+ * being published to `destination` into the absolute ref it will resolve to
86
+ * once published. Owns the scheme-specific "where does a sibling land" rule —
87
+ * OCI derives the repo from the destination, HTTP from the sibling's
88
+ * `<namespace>/<name>` — so `telo publish` delegates instead of branching on
89
+ * transport shape. */
90
+ canonicalizeSiblingRef(destination: string, relativeSource: string, sibling: SiblingIdentity): string;
91
+ }
92
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/transports/transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAEhE;;;sDAGsD;AACtD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AAED;;;;iEAIiE;AACjE,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;kFAEkF;AAClF,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;6CACyC;IACzC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE;QACf,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC;KACjB,KAAK,IAAI,CAAC;CACZ;AAED;;;;;;;;;;;;yDAYyD;AACzD,MAAM,WAAW,SAAS;IACxB,4EAA4E;IAC5E,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAE/B;;gFAE4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAEhC;;6EAEyE;IACzE,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC;IAE5C;;;6BAGyB;IACzB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC;IAEpD;;;kFAG8E;IAC9E,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAErD;;qEAEiE;IACjE,OAAO,CACL,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,aAAa,EACrB,IAAI,CAAC,EAAE,cAAc,GACpB,OAAO,CAAC,aAAa,CAAC,CAAC;IAE1B;;;;;2BAKuB;IACvB,sBAAsB,CACpB,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,eAAe,GACvB,MAAM,CAAC;CACX"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../../src/transports/transport.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telorun/kernel",
3
- "version": "0.41.0",
3
+ "version": "0.43.0",
4
4
  "description": "Telo Runtime - A lightweight, polyglot execution host.",
5
5
  "keywords": [
6
6
  "telo",
@@ -47,16 +47,18 @@
47
47
  "dependencies": {
48
48
  "@marcbachmann/cel-js": "^7.6.1",
49
49
  "@sinclair/typebox": "^0.34.48",
50
- "@telorun/analyzer": "0.31.0",
50
+ "@telorun/analyzer": "0.33.0",
51
51
  "@telorun/glob": "0.2.0",
52
- "@telorun/templating": "0.10.0",
52
+ "@telorun/templating": "0.10.1",
53
53
  "ajv": "^8.17.1",
54
54
  "ajv-formats": "^3.0.1",
55
55
  "packageurl-js": "^2.0.1",
56
+ "tar-stream": "^3.1.7",
56
57
  "yaml": "^2.8.3"
57
58
  },
58
59
  "devDependencies": {
59
60
  "@types/node": "^20.0.0",
61
+ "@types/tar-stream": "^3.1.3",
60
62
  "typescript": "^5.0.0",
61
63
  "vitest": "^2.1.8",
62
64
  "@telorun/sdk": "0.41.0"
@@ -76,6 +78,7 @@
76
78
  "ajv": "$ajv",
77
79
  "ajv-formats": "$ajv-formats",
78
80
  "packageurl-js": "$packageurl-js",
81
+ "tar-stream": "$tar-stream",
79
82
  "yaml": "$yaml"
80
83
  },
81
84
  "scripts": {
@@ -0,0 +1,46 @@
1
+ import { DEFAULT_MANIFEST_FILENAME, sha256Base64Url } from "@telorun/analyzer";
2
+
3
+ import { findOwnerDoc, parseManifestDocs } from "./module-manifest.js";
4
+
5
+ export interface PayloadFile {
6
+ /** POSIX-relative path inside the bundle. */
7
+ name: string;
8
+ content: Buffer | Uint8Array;
9
+ }
10
+
11
+ /**
12
+ * Canonical per-file content digest of a module's `files:` payload — the value
13
+ * of `filesIntegrity` in a bundle's `telo.yaml`. SHA-256 over the sorted
14
+ * `<path>\0<sha256(content)>` lines of every payload file, `telo.yaml`
15
+ * excluded (the importer's `#sha256-...` hash already covers the manifest, and
16
+ * excluding it breaks the self-reference — the manifest embeds this value).
17
+ *
18
+ * Hashing file *contents* rather than the tar/gzip bytes makes the digest
19
+ * independent of archive framing, so publisher and client compute the same
20
+ * value from the same file set, and it can be re-derived from the extracted
21
+ * files on disk. Returns `sha256-<base64url>`.
22
+ */
23
+ export async function computeFilesIntegrity(files: PayloadFile[]): Promise<string> {
24
+ const lines: string[] = [];
25
+ for (const file of files) {
26
+ if (file.name === DEFAULT_MANIFEST_FILENAME) continue;
27
+ const bytes = file.content instanceof Uint8Array ? file.content : new Uint8Array(file.content);
28
+ lines.push(`${file.name}\0${await sha256Base64Url(bytes)}`);
29
+ }
30
+ lines.sort();
31
+ const canonical = new TextEncoder().encode(lines.join("\n"));
32
+ return `sha256-${await sha256Base64Url(canonical)}`;
33
+ }
34
+
35
+ /** Write `filesIntegrity` onto the manifest's owner doc so the published
36
+ * `telo.yaml` pins its payload — transitively covered by importers'
37
+ * `#sha256-...` hash. The digest excludes `telo.yaml`, so injecting it does
38
+ * not change the digest. Returns the manifest unchanged when it has no owner
39
+ * doc. */
40
+ export function injectFilesIntegrity(manifest: string, hash: string): string {
41
+ const docs = parseManifestDocs(manifest);
42
+ const owner = findOwnerDoc(docs);
43
+ if (!owner) return manifest;
44
+ owner.set("filesIntegrity", hash);
45
+ return docs.map((d) => d.toString()).join("---\n");
46
+ }
@@ -0,0 +1,49 @@
1
+ import { defaultCustomTags } from "@telorun/templating";
2
+ import { parseAllDocuments, type Document } from "yaml";
3
+
4
+ const OWNER_KINDS = new Set(["Telo.Application", "Telo.Library"]);
5
+
6
+ /** Parse a manifest's YAML documents, tolerating its `!cel` / `!ref` tags. */
7
+ export function parseManifestDocs(text: string): Document[] {
8
+ return parseAllDocuments(text, { customTags: defaultCustomTags() });
9
+ }
10
+
11
+ /** The owner document of a manifest — the single `Telo.Application` /
12
+ * `Telo.Library` doc that carries its identity, `files:`, and `filesIntegrity`.
13
+ * The one place that selects it, shared by every reader/writer. */
14
+ export function findOwnerDoc(docs: Document[]): Document | undefined {
15
+ return docs.find((d) => {
16
+ const kind = (d.toJSON() as { kind?: string } | null)?.kind;
17
+ return kind !== undefined && OWNER_KINDS.has(kind);
18
+ });
19
+ }
20
+
21
+ /** The owner-doc fields the transports read: identity for the publish location,
22
+ * and payload markers for artifact fetch. */
23
+ export interface OwnerManifest {
24
+ namespace?: string;
25
+ name?: string;
26
+ version?: string;
27
+ filesIntegrity?: string;
28
+ /** True when the owner doc declares a non-empty `files:` list. */
29
+ declaresFiles: boolean;
30
+ }
31
+
32
+ /** Read the owner doc's identity + payload fields from a manifest, parsing once
33
+ * (never regex-scraping). The single source both transports call for
34
+ * `<namespace>/<name>/<version>`, `filesIntegrity`, and payload detection. */
35
+ export function readOwnerManifest(text: string): OwnerManifest {
36
+ const owner = findOwnerDoc(parseManifestDocs(text));
37
+ const parsed = owner?.toJSON() as
38
+ | { metadata?: Record<string, unknown>; files?: unknown; filesIntegrity?: unknown }
39
+ | undefined;
40
+ const md = parsed?.metadata ?? {};
41
+ const str = (v: unknown): string | undefined => (typeof v === "string" ? v : undefined);
42
+ return {
43
+ namespace: str(md.namespace),
44
+ name: str(md.name),
45
+ version: str(md.version),
46
+ filesIntegrity: str(parsed?.filesIntegrity),
47
+ declaresFiles: Array.isArray(parsed?.files) && parsed.files.length > 0,
48
+ };
49
+ }