@telorun/kernel 0.46.0 → 0.48.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.
- package/dist/application-env.d.ts +26 -11
- package/dist/application-env.d.ts.map +1 -1
- package/dist/application-env.js +36 -12
- package/dist/application-env.js.map +1 -1
- package/dist/bundle/module-manifest.d.ts +6 -0
- package/dist/bundle/module-manifest.d.ts.map +1 -1
- package/dist/bundle/module-manifest.js +4 -0
- package/dist/bundle/module-manifest.js.map +1 -1
- package/dist/controller-loaders/npm-loader.d.ts +2 -16
- package/dist/controller-loaders/npm-loader.d.ts.map +1 -1
- package/dist/controller-loaders/npm-loader.js +42 -1
- package/dist/controller-loaders/npm-loader.js.map +1 -1
- package/dist/controller-registry.d.ts +6 -6
- package/dist/controller-registry.d.ts.map +1 -1
- package/dist/controller-registry.js +14 -10
- package/dist/controller-registry.js.map +1 -1
- package/dist/controllers/module/import-controller.d.ts +0 -46
- package/dist/controllers/module/import-controller.d.ts.map +1 -1
- package/dist/controllers/module/import-controller.js +10 -30
- package/dist/controllers/module/import-controller.js.map +1 -1
- package/dist/controllers/resource-definition/abstract-controller.d.ts +0 -4
- package/dist/controllers/resource-definition/abstract-controller.d.ts.map +1 -1
- package/dist/controllers/resource-definition/abstract-controller.js +0 -4
- package/dist/controllers/resource-definition/abstract-controller.js.map +1 -1
- package/dist/controllers/resource-definition/resource-definition-controller.d.ts +0 -4
- package/dist/controllers/resource-definition/resource-definition-controller.d.ts.map +1 -1
- package/dist/controllers/resource-definition/resource-definition-controller.js +0 -4
- package/dist/controllers/resource-definition/resource-definition-controller.js.map +1 -1
- package/dist/controllers/resource-definition/resource-inherited-controller.d.ts.map +1 -1
- package/dist/controllers/resource-definition/resource-inherited-controller.js +0 -1
- package/dist/controllers/resource-definition/resource-inherited-controller.js.map +1 -1
- package/dist/controllers/resource-definition/resource-template-controller.d.ts.map +1 -1
- package/dist/controllers/resource-definition/resource-template-controller.js +0 -1
- package/dist/controllers/resource-definition/resource-template-controller.js.map +1 -1
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +19 -12
- package/dist/kernel.js.map +1 -1
- package/dist/module-context.d.ts +25 -4
- package/dist/module-context.d.ts.map +1 -1
- package/dist/module-context.js +28 -5
- package/dist/module-context.js.map +1 -1
- package/dist/resource-context.d.ts +3 -1
- package/dist/resource-context.d.ts.map +1 -1
- package/dist/resource-context.js +2 -0
- package/dist/resource-context.js.map +1 -1
- package/dist/transports/oci/oci-client.d.ts +5 -0
- package/dist/transports/oci/oci-client.d.ts.map +1 -1
- package/dist/transports/oci/oci-client.js.map +1 -1
- package/dist/transports/oci/oci-transport.d.ts +18 -0
- package/dist/transports/oci/oci-transport.d.ts.map +1 -1
- package/dist/transports/oci/oci-transport.js +41 -8
- package/dist/transports/oci/oci-transport.js.map +1 -1
- package/dist/transports/registry-transport.d.ts +7 -0
- package/dist/transports/registry-transport.d.ts.map +1 -1
- package/dist/transports/registry-transport.js +29 -10
- package/dist/transports/registry-transport.js.map +1 -1
- package/dist/transports/transport.d.ts +19 -0
- package/dist/transports/transport.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/application-env.ts +36 -11
- package/src/bundle/module-manifest.ts +10 -0
- package/src/controller-loaders/npm-loader.ts +47 -1
- package/src/controller-registry.ts +14 -10
- package/src/controllers/module/import-controller.ts +11 -30
- package/src/controllers/resource-definition/abstract-controller.ts +0 -4
- package/src/controllers/resource-definition/resource-definition-controller.ts +0 -4
- package/src/controllers/resource-definition/resource-inherited-controller.ts +0 -2
- package/src/controllers/resource-definition/resource-template-controller.ts +0 -2
- package/src/kernel.ts +21 -12
- package/src/module-context.ts +30 -6
- package/src/resource-context.ts +3 -1
- package/src/transports/oci/oci-client.ts +5 -0
- package/src/transports/oci/oci-transport.ts +46 -10
- package/src/transports/registry-transport.ts +32 -10
- package/src/transports/transport.ts +20 -0
|
@@ -86,6 +86,25 @@ export interface Transport {
|
|
|
86
86
|
* re-pushed to different bytes — so the discovery tracker records this
|
|
87
87
|
* digest per version and re-checks it on every track. */
|
|
88
88
|
digest(ref: string): Promise<string | null>;
|
|
89
|
+
/** Telo's inline integrity hash (`sha256-<base64url>`) for the `telo.yaml`
|
|
90
|
+
* `ref` resolves to — the value written as a `#sha256-…` pin by `telo
|
|
91
|
+
* publish` and re-pinned by `telo upgrade`. Throws when the ref does not
|
|
92
|
+
* resolve; callers decide whether that is fatal (`--frozen`) or best-effort.
|
|
93
|
+
*
|
|
94
|
+
* This is on the interface, not computed by the caller, because *what gets
|
|
95
|
+
* hashed* is transport-specific and must match exactly what that transport's
|
|
96
|
+
* own `source.read()` verifies — otherwise a pin written at publish fails
|
|
97
|
+
* verification at import. HTTP/registry hash the raw response bytes;
|
|
98
|
+
* OCI hashes the UTF-8 encoding of the `telo.yaml` extracted from the tar
|
|
99
|
+
* layer. A caller cannot know which, so a caller-side scheme branch silently
|
|
100
|
+
* degrades the moment a transport is added — which is exactly how `oci://`
|
|
101
|
+
* refs came to be published unpinned.
|
|
102
|
+
*
|
|
103
|
+
* Distinct from `digest()`: that is an opaque transport-native content id for
|
|
104
|
+
* change detection, never written into a manifest or compared across
|
|
105
|
+
* transports. This is the portable, cross-transport hash Telo itself
|
|
106
|
+
* verifies. */
|
|
107
|
+
manifestHash(ref: string): Promise<string>;
|
|
89
108
|
/** Push `bundle` to `destination` (a base ref / repo whose scheme this
|
|
90
109
|
* transport owns), pinning the payload and writing the transport-native
|
|
91
110
|
* artifact shape. Throws on failure. Used by `telo publish`. */
|
|
@@ -1 +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;;;;;;;8DAO0D;IAC1D,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAE5C;;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"}
|
|
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;;;;;;;8DAO0D;IAC1D,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAE5C;;;;;;;;;;;;;;;;;oBAiBgB;IAChB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE3C;;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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/kernel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.48.0",
|
|
4
4
|
"description": "Telo Runtime - A lightweight, polyglot execution host.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"telo",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@marcbachmann/cel-js": "^7.6.1",
|
|
49
49
|
"@sinclair/typebox": "^0.34.48",
|
|
50
|
-
"@telorun/analyzer": "0.
|
|
50
|
+
"@telorun/analyzer": "0.38.0",
|
|
51
51
|
"@telorun/glob": "0.2.0",
|
|
52
52
|
"@telorun/templating": "0.10.1",
|
|
53
53
|
"ajv": "^8.17.1",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"@types/tar-stream": "^3.1.3",
|
|
62
62
|
"typescript": "^5.0.0",
|
|
63
63
|
"vitest": "^2.1.8",
|
|
64
|
-
"@telorun/sdk": "0.
|
|
64
|
+
"@telorun/sdk": "0.48.0"
|
|
65
65
|
},
|
|
66
66
|
"optionalDependencies": {
|
|
67
67
|
"esbuild": "^0.28.1"
|
package/src/application-env.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { residualEntrySchema } from "@telorun/analyzer";
|
|
1
|
+
import { type DefResolver, effectiveAuthorSchema, residualEntrySchema } from "@telorun/analyzer";
|
|
2
|
+
import type { ResourceDefinition } from "@telorun/sdk";
|
|
2
3
|
import { RuntimeError } from "@telorun/sdk";
|
|
3
4
|
import { SchemaValidator } from "./schema-validator.js";
|
|
4
5
|
|
|
@@ -145,24 +146,39 @@ export function precompileApplicationEnvSchemas(
|
|
|
145
146
|
|
|
146
147
|
/**
|
|
147
148
|
* Build-time cache warm for resource-config validators. The runtime
|
|
148
|
-
* `_createInstance` compiles `
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
149
|
+
* `_createInstance` compiles the declaring `Telo.Definition`'s `schema` to
|
|
150
|
+
* validate every resource's config, then validates inputs/outputs against
|
|
151
|
+
* `inputType` / `outputType`. The analyze-only warm pass stops before
|
|
152
|
+
* instantiation, so without this those validators are absent from the
|
|
153
|
+
* `__validators` cache and the runtime recompiles (and, on a read-only image,
|
|
154
|
+
* fails to persist) them on every boot.
|
|
154
155
|
*
|
|
155
156
|
* Compiling each definition's `schema` (plus any inline `inputType` /
|
|
156
157
|
* `outputType` object schemas) here writes them into the same content-addressed
|
|
157
158
|
* cache the runtime reads, keyed identically because the same schema object is
|
|
158
|
-
* fed to the same `validator.compile`.
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
159
|
+
* fed to the same `validator.compile`. Every kind is bakeable now that the
|
|
160
|
+
* manifest is the sole config contract — a controller can no longer supply a
|
|
161
|
+
* schema the warm cannot see. Compile failures are swallowed; a genuinely
|
|
162
|
+
* broken schema surfaces through analysis / runtime, not here.
|
|
163
|
+
*
|
|
164
|
+
* `resolverFor` bakes the INHERITANCE-RESOLVED schema too. A `base:`-less
|
|
165
|
+
* `extends` child is validated at runtime against `merge(parent, own)` — a
|
|
166
|
+
* different object than its raw `schema:`, so a different cache key. Without
|
|
167
|
+
* this the warm bakes a schema the runtime never asks for and every inheriting
|
|
168
|
+
* kind misses on every boot, recompiling (and, on a read-only image, failing to
|
|
169
|
+
* persist) forever. Both forms are compiled — the raw one still backs
|
|
170
|
+
* definitions that don't inherit.
|
|
171
|
+
*
|
|
172
|
+
* It is a factory, not a single resolver, because `extends` aliases are scoped
|
|
173
|
+
* to the DECLARING module — `Cache.Store` reads against that library's import
|
|
174
|
+
* map, `Self.Host` against its own name. A global resolver silently fails to
|
|
175
|
+
* resolve those, yielding the un-merged schema and reintroducing the very miss
|
|
176
|
+
* this exists to prevent.
|
|
162
177
|
*/
|
|
163
178
|
export function precompileDefinitionSchemas(
|
|
164
179
|
manifests: Array<Record<string, any>>,
|
|
165
180
|
validator: SchemaValidator,
|
|
181
|
+
resolverFor?: (def: Record<string, any>) => DefResolver,
|
|
166
182
|
): void {
|
|
167
183
|
const compile = (schema: unknown): void => {
|
|
168
184
|
if (!schema || typeof schema !== "object") return;
|
|
@@ -177,6 +193,15 @@ export function precompileDefinitionSchemas(
|
|
|
177
193
|
compile(m.schema);
|
|
178
194
|
compile(m.inputType);
|
|
179
195
|
compile(m.outputType);
|
|
196
|
+
if (resolverFor && m.extends) {
|
|
197
|
+
// Mirrors the runtime stamp in `resource-definition-controller`; sharing
|
|
198
|
+
// `effectiveAuthorSchema` is what keeps the two keys identical.
|
|
199
|
+
try {
|
|
200
|
+
compile(effectiveAuthorSchema(m as unknown as ResourceDefinition, resolverFor(m)));
|
|
201
|
+
} catch {
|
|
202
|
+
// An unresolvable parent is a diagnostic elsewhere; the warm just skips.
|
|
203
|
+
}
|
|
204
|
+
}
|
|
180
205
|
}
|
|
181
206
|
}
|
|
182
207
|
|
|
@@ -27,6 +27,12 @@ export interface OwnerManifest {
|
|
|
27
27
|
filesIntegrity?: string;
|
|
28
28
|
/** True when the owner doc declares a non-empty `files:` list. */
|
|
29
29
|
declaresFiles: boolean;
|
|
30
|
+
/** Descriptive provenance a transport projects into its backend's metadata
|
|
31
|
+
* (OCI annotations). Never used to address the artifact. */
|
|
32
|
+
description?: string;
|
|
33
|
+
repository?: string;
|
|
34
|
+
license?: string;
|
|
35
|
+
documentation?: string;
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
/** Read the owner doc's identity + payload fields from a manifest, parsing once
|
|
@@ -45,5 +51,9 @@ export function readOwnerManifest(text: string): OwnerManifest {
|
|
|
45
51
|
version: str(md.version),
|
|
46
52
|
filesIntegrity: str(parsed?.filesIntegrity),
|
|
47
53
|
declaresFiles: Array.isArray(parsed?.files) && parsed.files.length > 0,
|
|
54
|
+
description: str(md.description),
|
|
55
|
+
repository: str(md.repository),
|
|
56
|
+
license: str(md.license),
|
|
57
|
+
documentation: str(md.documentation),
|
|
48
58
|
};
|
|
49
59
|
}
|
|
@@ -547,6 +547,52 @@ async function resolveKernelPackageRoot(name: string): Promise<string | null> {
|
|
|
547
547
|
}
|
|
548
548
|
}
|
|
549
549
|
|
|
550
|
+
/**
|
|
551
|
+
* Serialize same-process callers for one install root, in memory, ahead of the
|
|
552
|
+
* filesystem lock.
|
|
553
|
+
*
|
|
554
|
+
* Package installs dedupe per alias, so N distinct controllers are N distinct
|
|
555
|
+
* `withInstallLock` calls against the SAME root. Without this queue they all
|
|
556
|
+
* contend through the filesystem: one wins and the rest poll `fs.open` every
|
|
557
|
+
* {@link LOCK_RETRY_MS} for the whole install, each crossing
|
|
558
|
+
* {@link LOCK_WAIT_NOTICE_MS} and printing the wait notice — a notice whose
|
|
559
|
+
* whole point is "another Telo *process* holds this", which is misleading when
|
|
560
|
+
* the holder is us. (`telo install` fanning 52 controllers out through one
|
|
561
|
+
* `Promise.allSettled` printed 51 of them.)
|
|
562
|
+
*
|
|
563
|
+
* Queuing here means exactly one caller per process reaches the fs lock, so the
|
|
564
|
+
* notice regains its cross-process meaning and the losers do no I/O at all. The
|
|
565
|
+
* fs lock is untouched and still provides the cross-process guarantee.
|
|
566
|
+
*/
|
|
567
|
+
const localInstallQueues = new Map<string, Promise<unknown>>();
|
|
568
|
+
|
|
569
|
+
function withLocalInstallQueue<T>(installRoot: string, fn: () => Promise<T>): Promise<T> {
|
|
570
|
+
const prev = localInstallQueues.get(installRoot) ?? Promise.resolve();
|
|
571
|
+
// Run on both settle paths: one failed install must not wedge the queue.
|
|
572
|
+
const run = prev.then(fn, fn);
|
|
573
|
+
// The stored tail never rejects — a failure neither poisons followers nor
|
|
574
|
+
// surfaces as an unhandled rejection on the chain copy.
|
|
575
|
+
const tail = run.then(
|
|
576
|
+
() => {},
|
|
577
|
+
() => {},
|
|
578
|
+
);
|
|
579
|
+
localInstallQueues.set(installRoot, tail);
|
|
580
|
+
// Drop the entry once nothing further is queued, so a long-lived process that
|
|
581
|
+
// touches many entry dirs doesn't retain a promise per root forever.
|
|
582
|
+
void tail.then(() => {
|
|
583
|
+
if (localInstallQueues.get(installRoot) === tail) localInstallQueues.delete(installRoot);
|
|
584
|
+
});
|
|
585
|
+
return run;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Acquire the install lock for `installRoot` and run `fn` under it: first the
|
|
590
|
+
* in-process queue above, then the cross-process filesystem lock.
|
|
591
|
+
*/
|
|
592
|
+
async function withInstallLock<T>(installRoot: string, fn: () => Promise<T>): Promise<T> {
|
|
593
|
+
return withLocalInstallQueue(installRoot, () => withFileInstallLock(installRoot, fn));
|
|
594
|
+
}
|
|
595
|
+
|
|
550
596
|
/**
|
|
551
597
|
* Acquire a process-portable lock on `<root>/.lock` and execute fn while
|
|
552
598
|
* holding it. `fs.open(path, 'wx')` is atomic on POSIX and Windows, so
|
|
@@ -565,7 +611,7 @@ async function resolveKernelPackageRoot(name: string): Promise<string | null> {
|
|
|
565
611
|
* invocation, and any state-file writes. It does NOT serialize *reads* of
|
|
566
612
|
* already-installed controllers — those run lock-free against a stable tree.
|
|
567
613
|
*/
|
|
568
|
-
async function
|
|
614
|
+
async function withFileInstallLock<T>(installRoot: string, fn: () => Promise<T>): Promise<T> {
|
|
569
615
|
const lockPath = path.join(installRoot, ".lock");
|
|
570
616
|
|
|
571
617
|
await fs.mkdir(installRoot, { recursive: true });
|
|
@@ -111,18 +111,17 @@ export class ControllerRegistry {
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
/**
|
|
114
|
-
* Distinct
|
|
115
|
-
* kind
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
114
|
+
* Distinct resource-config schemas across all registered kinds, taken from
|
|
115
|
+
* each kind's DEFINITION. Used by the build-time validator warm to pre-compile
|
|
116
|
+
* the framework/builtin schemas (`Telo.Import`, `Telo.Definition`, the module
|
|
117
|
+
* controller, …) the runtime validates resources against — module-defined
|
|
118
|
+
* kinds aren't registered here until instantiation, so those are warmed from
|
|
119
|
+
* the static manifests instead.
|
|
120
120
|
*/
|
|
121
121
|
getControllerSchemas(): object[] {
|
|
122
122
|
const schemas: object[] = [];
|
|
123
|
-
for (const
|
|
124
|
-
const
|
|
125
|
-
const schema = controller?.schema;
|
|
123
|
+
for (const kind of this.controllersByKind.keys()) {
|
|
124
|
+
const schema = this.definitionsByKind.get(kind)?.schema;
|
|
126
125
|
if (schema && typeof schema === "object") schemas.push(schema);
|
|
127
126
|
}
|
|
128
127
|
return schemas;
|
|
@@ -142,9 +141,14 @@ export class ControllerRegistry {
|
|
|
142
141
|
throw new Error(`Cannot register controller for kind ${kind} without definition`);
|
|
143
142
|
}
|
|
144
143
|
const definition = this.definitionsByKind.get(kind);
|
|
144
|
+
// The DEFINITION's schema is the resource-config contract, always. A
|
|
145
|
+
// controller module's exports become the controller instance verbatim, so a
|
|
146
|
+
// stray `export const schema` used to silently override the manifest — the
|
|
147
|
+
// analyzer never loads controllers, so those overrides were invisible to
|
|
148
|
+
// `telo check`, unbakeable by the validator warm, and free to drift from the
|
|
149
|
+
// manifest they shadowed.
|
|
145
150
|
const wrappedController: ControllerInstance = {
|
|
146
151
|
...controller,
|
|
147
|
-
schema: controller.schema ?? definition?.schema,
|
|
148
152
|
inputType: controller.inputType,
|
|
149
153
|
outputType: controller.outputType,
|
|
150
154
|
};
|
|
@@ -155,7 +155,7 @@ export async function create(
|
|
|
155
155
|
// A library references its own kinds via `Self.<Kind>` (e.g. when it declares an
|
|
156
156
|
// instance to export). Register `Self` → the library's own module in the child context
|
|
157
157
|
// so those resolve at runtime — ungated, since this is internal use, not an importer.
|
|
158
|
-
childCtx.
|
|
158
|
+
childCtx.registerUngatedAlias("Self", targetModule);
|
|
159
159
|
|
|
160
160
|
// Stamp the resolved controller policy on the child only when the import
|
|
161
161
|
// specifies a `runtime:` field that resolves to something other than the
|
|
@@ -216,8 +216,16 @@ export async function create(
|
|
|
216
216
|
// of an imported library's kind). `parseExportEntry` (shared with the analyzer) yields
|
|
217
217
|
// `{name, alias?}` — `name` is the exported kind suffix, `alias` (when set) names this
|
|
218
218
|
// library's own import it re-exports from.
|
|
219
|
-
|
|
220
|
-
|
|
219
|
+
// A library that declares `exports.kinds` is gated to exactly that list (an empty list
|
|
220
|
+
// exports nothing). One that declares none registers an unrestricted gate (`undefined`)
|
|
221
|
+
// — the legacy permissive default, kept so already-published module versions, whose
|
|
222
|
+
// manifests can no longer gain an `exports.kinds` block, stay importable. Flipping this
|
|
223
|
+
// to a gated `[]` makes kinds private by default and is a breaking change for every such
|
|
224
|
+
// version; it needs the ecosystem republished with explicit exports first.
|
|
225
|
+
const declaredKinds = moduleManifest.exports?.kinds as string[] | undefined;
|
|
226
|
+
const kindEntries = (declaredKinds ?? []).map(parseExportEntry);
|
|
227
|
+
const exportedKindSuffixes =
|
|
228
|
+
declaredKinds === undefined ? undefined : kindEntries.map((k) => k.name);
|
|
221
229
|
// `exports.resources` entries are a bare name (`Db`, a locally-owned export) or a dotted
|
|
222
230
|
// `Alias.Name` (re-export of an imported instance, under name `Name`) — same grammar as
|
|
223
231
|
// `exports.kinds`.
|
|
@@ -324,30 +332,3 @@ function validateRequiredInputs(
|
|
|
324
332
|
}
|
|
325
333
|
}
|
|
326
334
|
|
|
327
|
-
export const schema = {
|
|
328
|
-
type: "object",
|
|
329
|
-
properties: {
|
|
330
|
-
kind: { type: "string" },
|
|
331
|
-
metadata: {
|
|
332
|
-
type: "object",
|
|
333
|
-
properties: {
|
|
334
|
-
name: { type: "string" },
|
|
335
|
-
module: { type: "string" },
|
|
336
|
-
},
|
|
337
|
-
required: ["name"],
|
|
338
|
-
additionalProperties: true,
|
|
339
|
-
},
|
|
340
|
-
source: { type: "string" },
|
|
341
|
-
integrity: { type: "string" },
|
|
342
|
-
variables: { type: "object" },
|
|
343
|
-
secrets: { type: "object" },
|
|
344
|
-
runtime: {
|
|
345
|
-
oneOf: [
|
|
346
|
-
{ type: "string" },
|
|
347
|
-
{ type: "array", items: { type: "string" } },
|
|
348
|
-
],
|
|
349
|
-
},
|
|
350
|
-
},
|
|
351
|
-
required: ["metadata", "source"],
|
|
352
|
-
additionalProperties: false,
|
|
353
|
-
};
|
|
@@ -125,8 +125,6 @@ export function createInheritedController(
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
return {
|
|
128
|
-
schema: authorSchema,
|
|
129
|
-
|
|
130
128
|
create: async (resource: any, ctx: ResourceContext): Promise<ResourceInstance | null> => {
|
|
131
129
|
const self: Record<string, unknown> = { ...resource, name: resource.metadata.name };
|
|
132
130
|
for (const path of refFieldPaths) {
|
|
@@ -61,8 +61,6 @@ export function createTemplateController(definition: {
|
|
|
61
61
|
result?: Record<string, any>;
|
|
62
62
|
}, definingContext: EvaluationContext): ControllerInstance {
|
|
63
63
|
return {
|
|
64
|
-
schema: definition.schema ?? { type: "object", additionalProperties: true },
|
|
65
|
-
|
|
66
64
|
create: async (resource: any, ctx: ResourceContext): Promise<ResourceInstance> => {
|
|
67
65
|
// `self` is read lazily: Phase 5 injection mutates `resource`'s ref slots
|
|
68
66
|
// (e.g. `connection: !ref Db` → the live instance) AFTER create() but before
|
package/src/kernel.ts
CHANGED
|
@@ -276,7 +276,7 @@ export class Kernel implements IKernel {
|
|
|
276
276
|
private async loadBuiltinDefinitions(): Promise<void> {
|
|
277
277
|
// Declare built-in module namespaces upfront so getContext() can distinguish
|
|
278
278
|
// "not yet populated" from a completely unknown module name.
|
|
279
|
-
this.rootContext.
|
|
279
|
+
this.rootContext.registerUngatedAlias("Telo", "Telo"); // built-ins — no import boundary
|
|
280
280
|
|
|
281
281
|
// Register built-in definitions with the controller registry.
|
|
282
282
|
// AnalysisRegistry's underlying DefinitionRegistry already seeds KERNEL_BUILTINS on construction.
|
|
@@ -483,13 +483,17 @@ export class Kernel implements IKernel {
|
|
|
483
483
|
);
|
|
484
484
|
}
|
|
485
485
|
// Bake the per-kind resource-config validators too. `_createInstance`
|
|
486
|
-
// compiles `
|
|
487
|
-
//
|
|
488
|
-
//
|
|
489
|
-
//
|
|
486
|
+
// compiles the definition's `schema` for every resource at runtime — work
|
|
487
|
+
// this analyze-only pass otherwise skips because it stops before
|
|
488
|
+
// instantiation, leaving the runtime to recompile and fail to persist
|
|
489
|
+
// them on a read-only image.
|
|
490
490
|
// Pre-compiling every `Telo.Definition` schema here writes them into the
|
|
491
|
-
// same content-addressed `__validators/` cache the runtime reads.
|
|
492
|
-
|
|
491
|
+
// same content-addressed `__validators/` cache the runtime reads. The
|
|
492
|
+
// resolver lets it also bake each `extends` child's inheritance-resolved
|
|
493
|
+
// schema — the form the runtime actually validates against.
|
|
494
|
+
precompileDefinitionSchemas(staticManifests, this.sharedSchemaValidator, (def) =>
|
|
495
|
+
this.registry.resolverForDefinition(def),
|
|
496
|
+
);
|
|
493
497
|
// Framework/builtin controller schemas (`Telo.Import`, `Telo.Definition`,
|
|
494
498
|
// the module controller, …) aren't in the static manifests but are
|
|
495
499
|
// validated per-resource at runtime just the same. They're registered by
|
|
@@ -1040,12 +1044,17 @@ export class Kernel implements IKernel {
|
|
|
1040
1044
|
`Controller for ${kind} does not implement create method`,
|
|
1041
1045
|
);
|
|
1042
1046
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1047
|
+
// The DEFINITION is the sole resource-config contract — a controller module
|
|
1048
|
+
// cannot supply or override one. Keeping the schema on the manifest is what
|
|
1049
|
+
// lets `telo check` see it, the validator warm bake it, and the editor render
|
|
1050
|
+
// it; a code-side schema was invisible to all three and free to drift.
|
|
1051
|
+
const definition = this.controllers.getDefinition(resolvedKind);
|
|
1052
|
+
const configSchema = definition?.schema as Record<string, unknown> | undefined;
|
|
1053
|
+
if (!configSchema?.type) {
|
|
1054
|
+
throw new Error(`No schema defined for kind ${kind}`);
|
|
1045
1055
|
}
|
|
1046
1056
|
|
|
1047
1057
|
// Resolve eval paths from x-telo-eval annotations in the parent and own schema
|
|
1048
|
-
const definition = this.controllers.getDefinition(resolvedKind);
|
|
1049
1058
|
const parentDef = definition?.capability
|
|
1050
1059
|
? this.controllers.getDefinition(definition.capability)
|
|
1051
1060
|
: undefined;
|
|
@@ -1063,8 +1072,8 @@ export class Kernel implements IKernel {
|
|
|
1063
1072
|
// restoring the pre-CEL string view that the schema expects.
|
|
1064
1073
|
try {
|
|
1065
1074
|
this.sharedSchemaValidator
|
|
1066
|
-
.compile(
|
|
1067
|
-
.validate(stripCompiledValues(resource,
|
|
1075
|
+
.compile(configSchema)
|
|
1076
|
+
.validate(stripCompiledValues(resource, configSchema));
|
|
1068
1077
|
} catch (error) {
|
|
1069
1078
|
throw new RuntimeError(
|
|
1070
1079
|
"ERR_RESOURCE_SCHEMA_VALIDATION_FAILED",
|
package/src/module-context.ts
CHANGED
|
@@ -72,7 +72,13 @@ export class ModuleContext extends EvaluationContext implements IModuleContext {
|
|
|
72
72
|
/** Maps import alias → real module name for kind resolution. */
|
|
73
73
|
private readonly importAliases = new Map<string, string>();
|
|
74
74
|
|
|
75
|
-
/** Maps import alias → allowed kind names.
|
|
75
|
+
/** Maps import alias → allowed kind names, i.e. the target's `exports.kinds` gate.
|
|
76
|
+
* A registered set is authoritative: only listed kinds resolve, and an empty set
|
|
77
|
+
* exports nothing. An absent entry is unrestricted, which today covers two cases —
|
|
78
|
+
* `registerUngatedAlias` (aliases crossing no import boundary: `Self`, the `Telo`
|
|
79
|
+
* built-ins) and an import whose target declares no `exports.kinds` at all (the
|
|
80
|
+
* legacy permissive default, kept so already-published module versions stay
|
|
81
|
+
* importable). Only the latter disappears when kinds go private by default. */
|
|
76
82
|
private readonly importedKinds = new Map<string, Set<string>>();
|
|
77
83
|
|
|
78
84
|
/** Maps import alias → its child context's exported instances. Registered by the
|
|
@@ -191,16 +197,34 @@ export class ModuleContext extends EvaluationContext implements IModuleContext {
|
|
|
191
197
|
}
|
|
192
198
|
|
|
193
199
|
/**
|
|
194
|
-
* Register an imported module under the given alias,
|
|
195
|
-
*
|
|
200
|
+
* Register an imported module under the given alias, gated to the kind names it
|
|
201
|
+
* exports (its `exports.kinds`). Only listed kinds resolve; an empty list exports
|
|
202
|
+
* nothing.
|
|
203
|
+
*
|
|
204
|
+
* `kinds` is `undefined` for exactly one case: the target declares no `exports.kinds`
|
|
205
|
+
* at all, the legacy permissive default that keeps already-published module versions
|
|
206
|
+
* importable. Every such call is a site to delete when kinds go private by default —
|
|
207
|
+
* for an alias that crosses no import boundary use `registerUngatedAlias` instead, so
|
|
208
|
+
* the two never get confused.
|
|
196
209
|
*/
|
|
197
|
-
registerImport(alias: string, targetModule: string, kinds
|
|
210
|
+
registerImport(alias: string, targetModule: string, kinds?: readonly string[]): void {
|
|
198
211
|
this.importAliases.set(alias, targetModule);
|
|
199
|
-
if (kinds
|
|
212
|
+
if (kinds !== undefined) {
|
|
200
213
|
this.importedKinds.set(alias, new Set(kinds));
|
|
201
214
|
}
|
|
202
215
|
}
|
|
203
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Register an alias that crosses no import boundary and is therefore never gated:
|
|
219
|
+
* `Self` (a library resolving its own kinds — `exports.kinds` gates importers, not
|
|
220
|
+
* internal use) and the `Telo` built-in namespace. Distinct from an ungated
|
|
221
|
+
* `registerImport` so that making kinds private by default cannot accidentally gate
|
|
222
|
+
* these to nothing, which would break every built-in kind everywhere.
|
|
223
|
+
*/
|
|
224
|
+
registerUngatedAlias(alias: string, targetModule: string): void {
|
|
225
|
+
this.importAliases.set(alias, targetModule);
|
|
226
|
+
}
|
|
227
|
+
|
|
204
228
|
/** Register an import alias's exported instances for cross-module reference resolution.
|
|
205
229
|
* `names` is the gate (the import's `exports.resources`); `terminal` returns the child
|
|
206
230
|
* context's pre-flattened terminal getter for a name (existing only after the import's
|
|
@@ -398,7 +422,7 @@ export class ModuleContext extends EvaluationContext implements IModuleContext {
|
|
|
398
422
|
// Re-export override: if this import's exported-kind table maps the suffix to a DIFFERENT
|
|
399
423
|
// owning module, the kind is transitively re-exported (`exports.kinds: [Alias.Kind]`) —
|
|
400
424
|
// resolve to its true owner. A local kind maps to `${realModule}.${suffix}` (no override),
|
|
401
|
-
// and a module without `exports.kinds` has an empty table (
|
|
425
|
+
// and a module without `exports.kinds` has an empty table (nothing re-exported). Built
|
|
402
426
|
// deferred, so before the import inits this returns the un-overridden kind, whose controller
|
|
403
427
|
// miss makes the init loop retry until the table is ready.
|
|
404
428
|
const reExported = this.importedKindResolvers.get(prefix)?.(suffix);
|
package/src/resource-context.ts
CHANGED
|
@@ -466,7 +466,9 @@ export class ResourceContextImpl implements ResourceContext {
|
|
|
466
466
|
await this.kernel.emitRuntimeEvent(event, payload);
|
|
467
467
|
}
|
|
468
468
|
|
|
469
|
-
|
|
469
|
+
/** `kinds` is the target's `exports.kinds` gate; omit it for an unrestricted alias
|
|
470
|
+
* (one crossing no import boundary). See `ModuleContext.registerImport`. */
|
|
471
|
+
registerModuleImport(alias: string, targetModule: string, kinds?: readonly string[]): void {
|
|
470
472
|
this.moduleContext.registerImport(alias, targetModule, kinds);
|
|
471
473
|
}
|
|
472
474
|
|
|
@@ -32,6 +32,11 @@ export interface OciManifest {
|
|
|
32
32
|
artifactType?: string;
|
|
33
33
|
config: OciDescriptor;
|
|
34
34
|
layers: OciDescriptor[];
|
|
35
|
+
/** Descriptive key/value metadata on the manifest. Telo projects the module's
|
|
36
|
+
* declared provenance into the standard `org.opencontainers.image.*` keys —
|
|
37
|
+
* the only metadata channel GHCR exposes, since it does not serve the
|
|
38
|
+
* referrers API. */
|
|
39
|
+
annotations?: Record<string, string>;
|
|
35
40
|
}
|
|
36
41
|
|
|
37
42
|
function sha256Hex(bytes: Uint8Array): string {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DEFAULT_MANIFEST_FILENAME,
|
|
3
3
|
IntegrityError,
|
|
4
|
+
sha256Base64Url,
|
|
4
5
|
verifyIntegrity,
|
|
5
6
|
type ManifestSource,
|
|
6
7
|
} from "@telorun/analyzer";
|
|
7
8
|
|
|
8
9
|
import { computeFilesIntegrity, injectFilesIntegrity } from "../../bundle/files-integrity.js";
|
|
9
|
-
import { readOwnerManifest } from "../../bundle/module-manifest.js";
|
|
10
|
+
import { readOwnerManifest, type OwnerManifest } from "../../bundle/module-manifest.js";
|
|
10
11
|
import { makeTarGz, readTarGz, toPayloadFiles } from "../../bundle/tar.js";
|
|
11
12
|
import type {
|
|
12
13
|
FetchedArtifact,
|
|
@@ -134,6 +135,39 @@ export class OciTransport implements Transport {
|
|
|
134
135
|
return pullVerified(ref);
|
|
135
136
|
}
|
|
136
137
|
|
|
138
|
+
/** Hashes the **UTF-8 encoding of the extracted `telo.yaml`**, which is what
|
|
139
|
+
* `pullVerified` checks an inline `#sha256-…` pin against on the read path.
|
|
140
|
+
*
|
|
141
|
+
* Cost note: a module is one tar blob, so there is no way to read `telo.yaml`
|
|
142
|
+
* without pulling the whole artifact — including any `files:` payload — and
|
|
143
|
+
* this path is deliberately uncached (a pin must hash what is published
|
|
144
|
+
* *now*, not a cached copy). Two consequences for callers: pinning N imports
|
|
145
|
+
* costs N full artifact pulls, and `pullVerified` also re-checks the
|
|
146
|
+
* dependency's `filesIntegrity`, so a corrupt *payload* upstream surfaces
|
|
147
|
+
* here as a pinning failure rather than a payload error. Both are acceptable
|
|
148
|
+
* for publish-time pinning, where correctness beats latency and refusing to
|
|
149
|
+
* pin against a corrupt dependency is the right outcome. */
|
|
150
|
+
async manifestHash(ref: string): Promise<string> {
|
|
151
|
+
const { manifest } = await pullVerified(ref);
|
|
152
|
+
return `sha256-${await sha256Base64Url(new TextEncoder().encode(manifest))}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Project a module's declared provenance onto the standard
|
|
156
|
+
* `org.opencontainers.image.*` annotation keys. Descriptive only — nothing
|
|
157
|
+
* addresses the artifact by these. Absent fields are omitted rather than
|
|
158
|
+
* written empty, so the manifest carries only what the module declared. */
|
|
159
|
+
private static annotationsFor(identity: OwnerManifest): Record<string, string> {
|
|
160
|
+
const mapped: Array<[string, string | undefined]> = [
|
|
161
|
+
["org.opencontainers.image.title", identity.name],
|
|
162
|
+
["org.opencontainers.image.version", identity.version],
|
|
163
|
+
["org.opencontainers.image.description", identity.description],
|
|
164
|
+
["org.opencontainers.image.source", identity.repository],
|
|
165
|
+
["org.opencontainers.image.licenses", identity.license],
|
|
166
|
+
["org.opencontainers.image.documentation", identity.documentation],
|
|
167
|
+
];
|
|
168
|
+
return Object.fromEntries(mapped.filter((e): e is [string, string] => Boolean(e[1])));
|
|
169
|
+
}
|
|
170
|
+
|
|
137
171
|
async publish(
|
|
138
172
|
destination: string,
|
|
139
173
|
bundle: PublishBundle,
|
|
@@ -144,19 +178,19 @@ export class OciTransport implements Transport {
|
|
|
144
178
|
throw new Error("OCI publish requires metadata.version (used as the tag).");
|
|
145
179
|
}
|
|
146
180
|
|
|
147
|
-
// Destination
|
|
148
|
-
//
|
|
181
|
+
// Destination must be a full repo (`oci://host/repo`). Identity is the ref,
|
|
182
|
+
// so the repo is never derived from `metadata.namespace`/`name` — a
|
|
183
|
+
// metadata-derived path is wrong whenever the repo differs from the name,
|
|
184
|
+
// and would silently push to a namespace the publisher may not own.
|
|
149
185
|
const afterScheme = destination.replace(/^oci:\/\//, "").replace(/\/+$/, "");
|
|
150
186
|
const slash = afterScheme.indexOf("/");
|
|
151
187
|
const host = slash > 0 ? afterScheme.slice(0, slash) : afterScheme;
|
|
152
|
-
|
|
188
|
+
const repo = slash > 0 ? afterScheme.slice(slash + 1) : "";
|
|
153
189
|
if (!repo) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
`
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
repo = `${identity.namespace}/${identity.name}`;
|
|
190
|
+
throw new Error(
|
|
191
|
+
`OCI publish destination '${destination}' is host-only — it must name a full repository, ` +
|
|
192
|
+
`e.g. 'oci://${host || "ghcr.io"}/<org>/<name>'.`,
|
|
193
|
+
);
|
|
160
194
|
}
|
|
161
195
|
const tag = identity.version;
|
|
162
196
|
|
|
@@ -173,12 +207,14 @@ export class OciTransport implements Transport {
|
|
|
173
207
|
const client = new OciClient(host, repo);
|
|
174
208
|
const layerDigest = await client.pushBlob(tar);
|
|
175
209
|
const config = await client.pushEmptyConfig();
|
|
210
|
+
const annotations = OciTransport.annotationsFor(identity);
|
|
176
211
|
const manifest: OciManifest = {
|
|
177
212
|
schemaVersion: 2,
|
|
178
213
|
mediaType: OCI_MANIFEST_MEDIA_TYPE,
|
|
179
214
|
artifactType: TELO_LAYER_MEDIA_TYPE,
|
|
180
215
|
config,
|
|
181
216
|
layers: [{ mediaType: TELO_LAYER_MEDIA_TYPE, digest: layerDigest, size: tar.length }],
|
|
217
|
+
...(Object.keys(annotations).length > 0 ? { annotations } : {}),
|
|
182
218
|
};
|
|
183
219
|
await client.pushManifest(tag, manifest);
|
|
184
220
|
|