@telorun/kernel 0.84.0 → 0.85.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 (44) hide show
  1. package/dist/bundle/module-artifact.d.ts +1 -1
  2. package/dist/bundle/module-artifact.d.ts.map +1 -1
  3. package/dist/bundle/module-artifact.js +3 -3
  4. package/dist/bundle/module-artifact.js.map +1 -1
  5. package/dist/controller-loaders/bundle-loader.d.ts +5 -5
  6. package/dist/controller-loaders/bundle-loader.js +7 -7
  7. package/dist/controller-loaders/bundle-loader.js.map +1 -1
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +1 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/kernel.d.ts +0 -5
  13. package/dist/kernel.d.ts.map +1 -1
  14. package/dist/kernel.js +5 -7
  15. package/dist/kernel.js.map +1 -1
  16. package/dist/manifest-sources/local-manifest-cache-source.d.ts +6 -6
  17. package/dist/manifest-sources/local-manifest-cache-source.d.ts.map +1 -1
  18. package/dist/manifest-sources/local-manifest-cache-source.js +11 -12
  19. package/dist/manifest-sources/local-manifest-cache-source.js.map +1 -1
  20. package/dist/runtime-seam.d.ts.map +1 -1
  21. package/dist/runtime-seam.js +1 -2
  22. package/dist/runtime-seam.js.map +1 -1
  23. package/dist/transports/http-transport.d.ts +37 -0
  24. package/dist/transports/http-transport.d.ts.map +1 -0
  25. package/dist/transports/http-transport.js +168 -0
  26. package/dist/transports/http-transport.js.map +1 -0
  27. package/dist/transports/transport-registry.d.ts +13 -14
  28. package/dist/transports/transport-registry.d.ts.map +1 -1
  29. package/dist/transports/transport-registry.js +17 -24
  30. package/dist/transports/transport-registry.js.map +1 -1
  31. package/package.json +2 -2
  32. package/src/bundle/module-artifact.ts +2 -3
  33. package/src/controller-loaders/bundle-loader.ts +7 -7
  34. package/src/index.ts +1 -1
  35. package/src/kernel.ts +4 -11
  36. package/src/manifest-sources/local-manifest-cache-source.ts +9 -16
  37. package/src/runtime-seam.ts +1 -2
  38. package/src/transports/http-transport.ts +209 -0
  39. package/src/transports/transport-registry.ts +17 -24
  40. package/dist/transports/registry-transport.d.ts +0 -41
  41. package/dist/transports/registry-transport.d.ts.map +0 -1
  42. package/dist/transports/registry-transport.js +0 -282
  43. package/dist/transports/registry-transport.js.map +0 -1
  44. package/src/transports/registry-transport.ts +0 -339
@@ -1,282 +0,0 @@
1
- import { DEFAULT_MANIFEST_FILENAME, HttpSource, RegistrySource, isRegistryRef, parseModuleRef, parseVersionedRef, sha256Base64Url, withRefVersion, splitIntegrity, } from "@telorun/analyzer";
2
- import { fetchOrThrow } from "@telorun/sdk";
3
- import { createHash } from "crypto";
4
- import { assertPublicEgress } from "./egress-guard.js";
5
- const DEFAULT_REGISTRY_URL = "https://registry.telo.run";
6
- /** Throwaway origin that lets a bare registry ref use URL path resolution. */
7
- const JOIN_ORIGIN = "https://ref.invalid";
8
- /** True for an HTTP(S) destination that names a registry root rather than a
9
- * module within it — no path segments to resolve a sibling beside. */
10
- function isRegistryBase(base) {
11
- if (!base.startsWith("http://") && !base.startsWith("https://"))
12
- return false;
13
- try {
14
- return new URL(base).pathname.replace(/\/+/g, "/").replace(/^\/|\/$/g, "") === "";
15
- }
16
- catch {
17
- return false;
18
- }
19
- }
20
- const QUERY_HASH_LENGTH = 12;
21
- /** Mirror `HttpSource.read`'s `fetchUrl` derivation: when the URL does not
22
- * already point at a YAML file, append `/telo.yaml`, so a raw import URL and
23
- * the canonical source it resolves to map to the same cache path. */
24
- function normalizePathname(rawUrl, parsed) {
25
- let pathname = parsed.pathname;
26
- if (!rawUrl.includes(".yaml")) {
27
- pathname = pathname.endsWith("/")
28
- ? `${pathname}${DEFAULT_MANIFEST_FILENAME}`
29
- : `${pathname}/${DEFAULT_MANIFEST_FILENAME}`;
30
- }
31
- return pathname;
32
- }
33
- /** Short hash of `search + hash` so two URLs that differ only in query /
34
- * fragment do not collide at the same cache path. */
35
- function disambiguatePath(pathname, search, hash) {
36
- if (!search && !hash)
37
- return pathname;
38
- const digest = createHash("sha256")
39
- .update(search + hash)
40
- .digest("hex")
41
- .slice(0, QUERY_HASH_LENGTH);
42
- const dotIdx = pathname.lastIndexOf(".");
43
- const slashIdx = pathname.lastIndexOf("/");
44
- const ext = dotIdx > slashIdx ? pathname.slice(dotIdx) : "";
45
- const base = pathname.slice(0, pathname.length - ext.length);
46
- return `${base}.${digest}${ext}`;
47
- }
48
- /** The default HTTP transport: bare `namespace/name@version` registry refs and
49
- * direct `https://…` URLs, resolving against `registry.telo.run` (or a
50
- * configured registry). Its resolution `source` composes the browser-safe
51
- * `RegistrySource` / `HttpSource` from `analyzer`; the Node-only management
52
- * methods live here. This is the fallback transport for any ref that carries
53
- * no owning scheme, so `oci://` (or a future `s3://`) never falls through to
54
- * it — those refs are claimed by their own transport's `supports()`. */
55
- export class RegistryTransport {
56
- registryUrl;
57
- registrySource;
58
- httpSource;
59
- source;
60
- constructor(registryUrl = DEFAULT_REGISTRY_URL) {
61
- this.registryUrl = registryUrl;
62
- this.registrySource = new RegistrySource(registryUrl);
63
- this.httpSource = new HttpSource();
64
- const pick = (ref) => this.httpSource.supports(ref) ? this.httpSource : this.registrySource;
65
- this.source = {
66
- supports: (url) => this.supports(url),
67
- read: async (url) => {
68
- // The browser-safe sources do the fetch; the Node-side egress policy
69
- // is enforced here, on the host the read will actually hit.
70
- await assertPublicEgress(this.httpSource.supports(url) ? url : this.registryUrl);
71
- return pick(url).read(url);
72
- },
73
- resolveRelative: (base, relative) => pick(base).resolveRelative(base, relative),
74
- };
75
- }
76
- supports(ref) {
77
- const { base } = splitIntegrity(ref);
78
- return base.startsWith("http://") || base.startsWith("https://") || isRegistryRef(ref);
79
- }
80
- cacheCoords(ref) {
81
- const url = splitIntegrity(ref).base;
82
- const trimmedRegistry = this.registryUrl.replace(/\/+$/, "");
83
- const registryHost = this.registryHost();
84
- // 1. Registry ref form: <path>@<version>. Keyed under the registry's host —
85
- // a ref says nothing about which registry serves it, so without the host
86
- // two registries' copies of the same path/version share one cache entry.
87
- if (isRegistryRef(url)) {
88
- if (!registryHost)
89
- return null;
90
- let parsed;
91
- try {
92
- parsed = parseModuleRef(url);
93
- }
94
- catch {
95
- return null;
96
- }
97
- return {
98
- transport: "registry",
99
- host: registryHost,
100
- path: parsed.modulePath,
101
- version: parsed.version,
102
- };
103
- }
104
- // 2. HTTP(S) URL — a direct registry URL or arbitrary external.
105
- if (url.startsWith("http://") || url.startsWith("https://")) {
106
- let parsed;
107
- try {
108
- parsed = new URL(url);
109
- }
110
- catch {
111
- return null;
112
- }
113
- const pathname = normalizePathname(url, parsed);
114
- // 2a. On the configured registry, no query/fragment: fold into the
115
- // registry layout so a ref and a direct URL land on the same file.
116
- const normalizedUrl = `${parsed.protocol}//${parsed.host}${pathname}`;
117
- if (registryHost &&
118
- !parsed.search &&
119
- !parsed.hash &&
120
- normalizedUrl.startsWith(`${trimmedRegistry}/`)) {
121
- // The registry serves `<path…>/<version>/<file>`, where `<file>` is the
122
- // module manifest or one of its `include:` partials.
123
- const segments = normalizedUrl.slice(trimmedRegistry.length + 1).split("/");
124
- const file = segments.pop();
125
- const version = segments.pop();
126
- if (file && version && segments.length > 0) {
127
- return {
128
- transport: "registry",
129
- host: registryHost,
130
- path: segments.join("/"),
131
- version,
132
- file,
133
- };
134
- }
135
- }
136
- // 2b. Arbitrary HTTP(S) → `url` subtree, query-hash suffix on collision.
137
- // No version segment: a URL addresses exactly one file, and the
138
- // version it declares lives inside bytes the cache maps paths without.
139
- const cleanPath = pathname.startsWith("/") ? pathname.slice(1) : pathname;
140
- const segments = disambiguatePath(cleanPath, parsed.search, parsed.hash).split("/");
141
- const file = segments.pop();
142
- if (!file)
143
- return null;
144
- return { transport: "url", host: parsed.host, path: segments.join("/"), file };
145
- }
146
- return null;
147
- }
148
- /** Host of the configured registry, or `null` when the URL is unparseable. */
149
- registryHost() {
150
- try {
151
- return new URL(this.registryUrl).host || null;
152
- }
153
- catch {
154
- return null;
155
- }
156
- }
157
- async listVersions(ref) {
158
- // Only bare registry refs are version-enumerable — a direct `https://` URL
159
- // has no version-list endpoint.
160
- if (!isRegistryRef(ref))
161
- return null;
162
- const { modulePath } = parseModuleRef(ref);
163
- const url = `${this.registryUrl.replace(/\/+$/, "")}/${modulePath}`;
164
- await assertPublicEgress(url);
165
- const res = await fetchOrThrow(url, { headers: { accept: "application/json" } }, { operation: "Registry version list", setting: "--registry / TELO_REGISTRY" });
166
- if (res.status === 404)
167
- return null;
168
- if (!res.ok) {
169
- throw new Error(`Registry returned ${res.status} ${res.statusText} for ${modulePath}`);
170
- }
171
- const body = (await res.json());
172
- return Array.isArray(body.versions) ? body.versions : [];
173
- }
174
- refVersion(ref) {
175
- // Only bare `namespace/name@version` refs carry an upgradeable version — a
176
- // direct `https://` URL has no version segment to bump.
177
- return isRegistryRef(ref) ? (parseVersionedRef(ref)?.version ?? null) : null;
178
- }
179
- withVersion(ref, version) {
180
- return withRefVersion(ref, version);
181
- }
182
- /** Mirrors the sources' fetch-URL derivation: a direct URL points at (or
183
- * contains) the YAML file; a bare registry ref folds into the registry
184
- * layout. `null` when this transport does not own the ref's shape. */
185
- manifestUrl(ref) {
186
- const { base } = splitIntegrity(ref);
187
- if (base.startsWith("http://") || base.startsWith("https://")) {
188
- return base.includes(".yaml") ? base : `${base}/${DEFAULT_MANIFEST_FILENAME}`;
189
- }
190
- if (isRegistryRef(ref)) {
191
- const { modulePath, version } = parseModuleRef(ref);
192
- return `${this.registryUrl.replace(/\/+$/, "")}/${modulePath}/${version}/${DEFAULT_MANIFEST_FILENAME}`;
193
- }
194
- return null;
195
- }
196
- async digest(ref) {
197
- // The digest is Telo's canonical hash over the `telo.yaml` bytes — the same
198
- // value `manifestHash` returns, but absent-is-null rather than a throw.
199
- const fetchUrl = this.manifestUrl(ref);
200
- if (!fetchUrl)
201
- return null;
202
- await assertPublicEgress(fetchUrl);
203
- const res = await fetchOrThrow(fetchUrl, undefined, {
204
- operation: "Registry manifest read",
205
- setting: "--registry / TELO_REGISTRY",
206
- });
207
- if (res.status === 404)
208
- return null;
209
- if (!res.ok) {
210
- throw new Error(`Registry returned ${res.status} ${res.statusText} for ${fetchUrl}`);
211
- }
212
- const bytes = new Uint8Array(await res.arrayBuffer());
213
- return `sha256-${await sha256Base64Url(bytes)}`;
214
- }
215
- /** Hashes the **raw response bytes**, which is exactly what `verifiedFetch`
216
- * checks an inline `#sha256-…` pin against on the read path. */
217
- async manifestHash(ref) {
218
- const fetchUrl = this.manifestUrl(ref);
219
- if (!fetchUrl) {
220
- throw new Error(`cannot hash non-remote import '${ref}'`);
221
- }
222
- await assertPublicEgress(fetchUrl);
223
- const res = await fetchOrThrow(fetchUrl, undefined, {
224
- operation: "Registry manifest hash",
225
- setting: "--registry / TELO_REGISTRY",
226
- });
227
- if (!res.ok) {
228
- throw new Error(`fetch ${fetchUrl}: ${res.status} ${res.statusText}`);
229
- }
230
- const bytes = new Uint8Array(await res.arrayBuffer());
231
- return `sha256-${await sha256Base64Url(bytes)}`;
232
- }
233
- /** Layered artifacts are an OCI concept, and the registry origin is read-only
234
- * — nothing has ever published a payload here, so there is no layer to pull.
235
- * A module reached over the registry is manifest-only, and its controllers
236
- * come from npm. */
237
- async fetchLayer(ref, blobDigest) {
238
- throw new Error(`Cannot fetch layer ${blobDigest} of ${ref}: the Telo registry serves manifests only. ` +
239
- `A module with a bundled payload is published as an OCI artifact (oci://host/repo).`);
240
- }
241
- async layerIndex(layers) {
242
- // Same boundary `fetchLayer` and `publish` draw: the HTTP registry serves
243
- // manifests only, so it frames no layer and can name no blob. An empty set
244
- // is not a payload, so it answers rather than throws — a manifest-only
245
- // module builds its payload through this transport during analysis.
246
- if (layers.every((layer) => layer.files.length === 0))
247
- return [];
248
- throw new Error("The Telo registry serves manifests only, so it cannot index payload layers. " +
249
- "A module with a bundled payload is published as an OCI artifact (oci://host/repo).");
250
- }
251
- async publish(destination, bundle, opts = {}) {
252
- // Publishing to the HTTP Telo registry has been removed — the registry
253
- // origin stays read-only, and new versions publish to OCI. `telo publish`
254
- // rejects a non-OCI destination up front, so this is only a guard for a
255
- // direct programmatic call.
256
- throw new Error("Publishing to the HTTP Telo registry has been removed. Publish to an OCI " +
257
- "registry (oci://host/repo) instead.");
258
- }
259
- canonicalizeSiblingRef(destination, relativeSource, version) {
260
- // The sibling sits beside the destination module: resolve the relative path
261
- // against the destination's own path, then pin the sibling's version. A bare
262
- // registry ref (`std/foo`) is a path, not a URL, so it borrows a throwaway
263
- // origin for the join and drops it again.
264
- const base = splitIntegrity(destination).base.replace(/@[^/@]*$/, "");
265
- // A registry *base* (`https://registry.telo.run`) is not a module location,
266
- // so there is nothing for `../lib` to resolve beside — joining anyway would
267
- // silently produce a ref one path segment short.
268
- if (isRegistryBase(base)) {
269
- throw new Error(`cannot canonicalize the relative import '${relativeSource}': publish destination ` +
270
- `'${destination}' is a registry base, not this module's own location. Pass the ` +
271
- `module's full destination (e.g. '${base.replace(/\/+$/, "")}/<namespace>/<name>').`);
272
- }
273
- const isUrl = base.startsWith("http://") || base.startsWith("https://");
274
- const origin = isUrl ? base : `${JOIN_ORIGIN}/${base.replace(/^\/+/, "")}`;
275
- const resolved = new URL(relativeSource, `${origin.replace(/\/+$/, "")}/`);
276
- const joined = isUrl
277
- ? `${resolved.protocol}//${resolved.host}${resolved.pathname}`
278
- : resolved.pathname.replace(/^\/+/, "");
279
- return `${joined.replace(/\/+$/, "")}@${version}`;
280
- }
281
- }
282
- //# sourceMappingURL=registry-transport.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"registry-transport.js","sourceRoot":"","sources":["../../src/transports/registry-transport.ts"],"names":[],"mappings":"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"}
@@ -1,339 +0,0 @@
1
- import {
2
- DEFAULT_MANIFEST_FILENAME,
3
- HttpSource,
4
- RegistrySource,
5
- isRegistryRef,
6
- parseModuleRef,
7
- parseVersionedRef,
8
- sha256Base64Url,
9
- withRefVersion,
10
- splitIntegrity,
11
- type ArtifactLayer,
12
- type ManifestCacheCoords,
13
- type ManifestSource,
14
- } from "@telorun/analyzer";
15
- import { fetchOrThrow } from "@telorun/sdk";
16
- import { createHash } from "crypto";
17
-
18
- import type { PayloadFile } from "../bundle/files-integrity.js";
19
- import { assertPublicEgress } from "./egress-guard.js";
20
- import type {
21
- PayloadLayer,
22
- PublishBundle,
23
- PublishOptions,
24
- PublishResult,
25
- Transport,
26
- } from "./transport.js";
27
-
28
- const DEFAULT_REGISTRY_URL = "https://registry.telo.run";
29
- /** Throwaway origin that lets a bare registry ref use URL path resolution. */
30
- const JOIN_ORIGIN = "https://ref.invalid";
31
-
32
- /** True for an HTTP(S) destination that names a registry root rather than a
33
- * module within it — no path segments to resolve a sibling beside. */
34
- function isRegistryBase(base: string): boolean {
35
- if (!base.startsWith("http://") && !base.startsWith("https://")) return false;
36
- try {
37
- return new URL(base).pathname.replace(/\/+/g, "/").replace(/^\/|\/$/g, "") === "";
38
- } catch {
39
- return false;
40
- }
41
- }
42
- const QUERY_HASH_LENGTH = 12;
43
-
44
- /** Mirror `HttpSource.read`'s `fetchUrl` derivation: when the URL does not
45
- * already point at a YAML file, append `/telo.yaml`, so a raw import URL and
46
- * the canonical source it resolves to map to the same cache path. */
47
- function normalizePathname(rawUrl: string, parsed: URL): string {
48
- let pathname = parsed.pathname;
49
- if (!rawUrl.includes(".yaml")) {
50
- pathname = pathname.endsWith("/")
51
- ? `${pathname}${DEFAULT_MANIFEST_FILENAME}`
52
- : `${pathname}/${DEFAULT_MANIFEST_FILENAME}`;
53
- }
54
- return pathname;
55
- }
56
-
57
- /** Short hash of `search + hash` so two URLs that differ only in query /
58
- * fragment do not collide at the same cache path. */
59
- function disambiguatePath(pathname: string, search: string, hash: string): string {
60
- if (!search && !hash) return pathname;
61
- const digest = createHash("sha256")
62
- .update(search + hash)
63
- .digest("hex")
64
- .slice(0, QUERY_HASH_LENGTH);
65
- const dotIdx = pathname.lastIndexOf(".");
66
- const slashIdx = pathname.lastIndexOf("/");
67
- const ext = dotIdx > slashIdx ? pathname.slice(dotIdx) : "";
68
- const base = pathname.slice(0, pathname.length - ext.length);
69
- return `${base}.${digest}${ext}`;
70
- }
71
-
72
- interface VersionsResponse {
73
- versions?: string[];
74
- }
75
-
76
- /** The default HTTP transport: bare `namespace/name@version` registry refs and
77
- * direct `https://…` URLs, resolving against `registry.telo.run` (or a
78
- * configured registry). Its resolution `source` composes the browser-safe
79
- * `RegistrySource` / `HttpSource` from `analyzer`; the Node-only management
80
- * methods live here. This is the fallback transport for any ref that carries
81
- * no owning scheme, so `oci://` (or a future `s3://`) never falls through to
82
- * it — those refs are claimed by their own transport's `supports()`. */
83
- export class RegistryTransport implements Transport {
84
- private readonly registrySource: RegistrySource;
85
- private readonly httpSource: HttpSource;
86
- readonly source: ManifestSource;
87
-
88
- constructor(private readonly registryUrl: string = DEFAULT_REGISTRY_URL) {
89
- this.registrySource = new RegistrySource(registryUrl);
90
- this.httpSource = new HttpSource();
91
- const pick = (ref: string): ManifestSource =>
92
- this.httpSource.supports(ref) ? this.httpSource : this.registrySource;
93
- this.source = {
94
- supports: (url) => this.supports(url),
95
- read: async (url) => {
96
- // The browser-safe sources do the fetch; the Node-side egress policy
97
- // is enforced here, on the host the read will actually hit.
98
- await assertPublicEgress(this.httpSource.supports(url) ? url : this.registryUrl);
99
- return pick(url).read(url);
100
- },
101
- resolveRelative: (base, relative) => pick(base).resolveRelative(base, relative),
102
- };
103
- }
104
-
105
- supports(ref: string): boolean {
106
- const { base } = splitIntegrity(ref);
107
- return base.startsWith("http://") || base.startsWith("https://") || isRegistryRef(ref);
108
- }
109
-
110
- cacheCoords(ref: string): ManifestCacheCoords | null {
111
- const url = splitIntegrity(ref).base;
112
- const trimmedRegistry = this.registryUrl.replace(/\/+$/, "");
113
- const registryHost = this.registryHost();
114
-
115
- // 1. Registry ref form: <path>@<version>. Keyed under the registry's host —
116
- // a ref says nothing about which registry serves it, so without the host
117
- // two registries' copies of the same path/version share one cache entry.
118
- if (isRegistryRef(url)) {
119
- if (!registryHost) return null;
120
- let parsed: ReturnType<typeof parseModuleRef>;
121
- try {
122
- parsed = parseModuleRef(url);
123
- } catch {
124
- return null;
125
- }
126
- return {
127
- transport: "registry",
128
- host: registryHost,
129
- path: parsed.modulePath,
130
- version: parsed.version,
131
- };
132
- }
133
-
134
- // 2. HTTP(S) URL — a direct registry URL or arbitrary external.
135
- if (url.startsWith("http://") || url.startsWith("https://")) {
136
- let parsed: URL;
137
- try {
138
- parsed = new URL(url);
139
- } catch {
140
- return null;
141
- }
142
- const pathname = normalizePathname(url, parsed);
143
-
144
- // 2a. On the configured registry, no query/fragment: fold into the
145
- // registry layout so a ref and a direct URL land on the same file.
146
- const normalizedUrl = `${parsed.protocol}//${parsed.host}${pathname}`;
147
- if (
148
- registryHost &&
149
- !parsed.search &&
150
- !parsed.hash &&
151
- normalizedUrl.startsWith(`${trimmedRegistry}/`)
152
- ) {
153
- // The registry serves `<path…>/<version>/<file>`, where `<file>` is the
154
- // module manifest or one of its `include:` partials.
155
- const segments = normalizedUrl.slice(trimmedRegistry.length + 1).split("/");
156
- const file = segments.pop();
157
- const version = segments.pop();
158
- if (file && version && segments.length > 0) {
159
- return {
160
- transport: "registry",
161
- host: registryHost,
162
- path: segments.join("/"),
163
- version,
164
- file,
165
- };
166
- }
167
- }
168
-
169
- // 2b. Arbitrary HTTP(S) → `url` subtree, query-hash suffix on collision.
170
- // No version segment: a URL addresses exactly one file, and the
171
- // version it declares lives inside bytes the cache maps paths without.
172
- const cleanPath = pathname.startsWith("/") ? pathname.slice(1) : pathname;
173
- const segments = disambiguatePath(cleanPath, parsed.search, parsed.hash).split("/");
174
- const file = segments.pop();
175
- if (!file) return null;
176
- return { transport: "url", host: parsed.host, path: segments.join("/"), file };
177
- }
178
-
179
- return null;
180
- }
181
-
182
- /** Host of the configured registry, or `null` when the URL is unparseable. */
183
- private registryHost(): string | null {
184
- try {
185
- return new URL(this.registryUrl).host || null;
186
- } catch {
187
- return null;
188
- }
189
- }
190
-
191
- async listVersions(ref: string): Promise<string[] | null> {
192
- // Only bare registry refs are version-enumerable — a direct `https://` URL
193
- // has no version-list endpoint.
194
- if (!isRegistryRef(ref)) return null;
195
- const { modulePath } = parseModuleRef(ref);
196
- const url = `${this.registryUrl.replace(/\/+$/, "")}/${modulePath}`;
197
- await assertPublicEgress(url);
198
- const res = await fetchOrThrow(
199
- url,
200
- { headers: { accept: "application/json" } },
201
- { operation: "Registry version list", setting: "--registry / TELO_REGISTRY" },
202
- );
203
- if (res.status === 404) return null;
204
- if (!res.ok) {
205
- throw new Error(`Registry returned ${res.status} ${res.statusText} for ${modulePath}`);
206
- }
207
- const body = (await res.json()) as VersionsResponse;
208
- return Array.isArray(body.versions) ? body.versions : [];
209
- }
210
-
211
- refVersion(ref: string): string | null {
212
- // Only bare `namespace/name@version` refs carry an upgradeable version — a
213
- // direct `https://` URL has no version segment to bump.
214
- return isRegistryRef(ref) ? (parseVersionedRef(ref)?.version ?? null) : null;
215
- }
216
-
217
- withVersion(ref: string, version: string): string {
218
- return withRefVersion(ref, version);
219
- }
220
-
221
- /** Mirrors the sources' fetch-URL derivation: a direct URL points at (or
222
- * contains) the YAML file; a bare registry ref folds into the registry
223
- * layout. `null` when this transport does not own the ref's shape. */
224
- private manifestUrl(ref: string): string | null {
225
- const { base } = splitIntegrity(ref);
226
- if (base.startsWith("http://") || base.startsWith("https://")) {
227
- return base.includes(".yaml") ? base : `${base}/${DEFAULT_MANIFEST_FILENAME}`;
228
- }
229
- if (isRegistryRef(ref)) {
230
- const { modulePath, version } = parseModuleRef(ref);
231
- return `${this.registryUrl.replace(/\/+$/, "")}/${modulePath}/${version}/${DEFAULT_MANIFEST_FILENAME}`;
232
- }
233
- return null;
234
- }
235
-
236
- async digest(ref: string): Promise<string | null> {
237
- // The digest is Telo's canonical hash over the `telo.yaml` bytes — the same
238
- // value `manifestHash` returns, but absent-is-null rather than a throw.
239
- const fetchUrl = this.manifestUrl(ref);
240
- if (!fetchUrl) return null;
241
- await assertPublicEgress(fetchUrl);
242
- const res = await fetchOrThrow(fetchUrl, undefined, {
243
- operation: "Registry manifest read",
244
- setting: "--registry / TELO_REGISTRY",
245
- });
246
- if (res.status === 404) return null;
247
- if (!res.ok) {
248
- throw new Error(`Registry returned ${res.status} ${res.statusText} for ${fetchUrl}`);
249
- }
250
- const bytes = new Uint8Array(await res.arrayBuffer());
251
- return `sha256-${await sha256Base64Url(bytes)}`;
252
- }
253
-
254
- /** Hashes the **raw response bytes**, which is exactly what `verifiedFetch`
255
- * checks an inline `#sha256-…` pin against on the read path. */
256
- async manifestHash(ref: string): Promise<string> {
257
- const fetchUrl = this.manifestUrl(ref);
258
- if (!fetchUrl) {
259
- throw new Error(`cannot hash non-remote import '${ref}'`);
260
- }
261
- await assertPublicEgress(fetchUrl);
262
- const res = await fetchOrThrow(fetchUrl, undefined, {
263
- operation: "Registry manifest hash",
264
- setting: "--registry / TELO_REGISTRY",
265
- });
266
- if (!res.ok) {
267
- throw new Error(`fetch ${fetchUrl}: ${res.status} ${res.statusText}`);
268
- }
269
- const bytes = new Uint8Array(await res.arrayBuffer());
270
- return `sha256-${await sha256Base64Url(bytes)}`;
271
- }
272
-
273
- /** Layered artifacts are an OCI concept, and the registry origin is read-only
274
- * — nothing has ever published a payload here, so there is no layer to pull.
275
- * A module reached over the registry is manifest-only, and its controllers
276
- * come from npm. */
277
- async fetchLayer(ref: string, blobDigest: string): Promise<PayloadFile[]> {
278
- throw new Error(
279
- `Cannot fetch layer ${blobDigest} of ${ref}: the Telo registry serves manifests only. ` +
280
- `A module with a bundled payload is published as an OCI artifact (oci://host/repo).`,
281
- );
282
- }
283
-
284
- async layerIndex(layers: readonly PayloadLayer[]): Promise<ArtifactLayer[]> {
285
- // Same boundary `fetchLayer` and `publish` draw: the HTTP registry serves
286
- // manifests only, so it frames no layer and can name no blob. An empty set
287
- // is not a payload, so it answers rather than throws — a manifest-only
288
- // module builds its payload through this transport during analysis.
289
- if (layers.every((layer) => layer.files.length === 0)) return [];
290
- throw new Error(
291
- "The Telo registry serves manifests only, so it cannot index payload layers. " +
292
- "A module with a bundled payload is published as an OCI artifact (oci://host/repo).",
293
- );
294
- }
295
-
296
- async publish(
297
- destination: string,
298
- bundle: PublishBundle,
299
- opts: PublishOptions = {},
300
- ): Promise<PublishResult> {
301
- // Publishing to the HTTP Telo registry has been removed — the registry
302
- // origin stays read-only, and new versions publish to OCI. `telo publish`
303
- // rejects a non-OCI destination up front, so this is only a guard for a
304
- // direct programmatic call.
305
- throw new Error(
306
- "Publishing to the HTTP Telo registry has been removed. Publish to an OCI " +
307
- "registry (oci://host/repo) instead.",
308
- );
309
- }
310
-
311
- canonicalizeSiblingRef(
312
- destination: string,
313
- relativeSource: string,
314
- version: string,
315
- ): string {
316
- // The sibling sits beside the destination module: resolve the relative path
317
- // against the destination's own path, then pin the sibling's version. A bare
318
- // registry ref (`std/foo`) is a path, not a URL, so it borrows a throwaway
319
- // origin for the join and drops it again.
320
- const base = splitIntegrity(destination).base.replace(/@[^/@]*$/, "");
321
- // A registry *base* (`https://registry.telo.run`) is not a module location,
322
- // so there is nothing for `../lib` to resolve beside — joining anyway would
323
- // silently produce a ref one path segment short.
324
- if (isRegistryBase(base)) {
325
- throw new Error(
326
- `cannot canonicalize the relative import '${relativeSource}': publish destination ` +
327
- `'${destination}' is a registry base, not this module's own location. Pass the ` +
328
- `module's full destination (e.g. '${base.replace(/\/+$/, "")}/<namespace>/<name>').`,
329
- );
330
- }
331
- const isUrl = base.startsWith("http://") || base.startsWith("https://");
332
- const origin = isUrl ? base : `${JOIN_ORIGIN}/${base.replace(/^\/+/, "")}`;
333
- const resolved = new URL(relativeSource, `${origin.replace(/\/+$/, "")}/`);
334
- const joined = isUrl
335
- ? `${resolved.protocol}//${resolved.host}${resolved.pathname}`
336
- : resolved.pathname.replace(/^\/+/, "");
337
- return `${joined.replace(/\/+$/, "")}@${version}`;
338
- }
339
- }