@telorun/analyzer 0.47.0 → 0.49.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 (58) hide show
  1. package/dist/analysis-registry.d.ts +22 -11
  2. package/dist/analysis-registry.d.ts.map +1 -1
  3. package/dist/analysis-registry.js +36 -39
  4. package/dist/analyzer.d.ts +38 -1
  5. package/dist/analyzer.d.ts.map +1 -1
  6. package/dist/analyzer.js +121 -83
  7. package/dist/artifact-layer-index.d.ts +55 -0
  8. package/dist/artifact-layer-index.d.ts.map +1 -0
  9. package/dist/artifact-layer-index.js +116 -0
  10. package/dist/artifact-selector.d.ts +81 -0
  11. package/dist/artifact-selector.d.ts.map +1 -0
  12. package/dist/artifact-selector.js +122 -0
  13. package/dist/builtins.d.ts.map +1 -1
  14. package/dist/builtins.js +130 -20
  15. package/dist/extends-resolution.d.ts +41 -0
  16. package/dist/extends-resolution.d.ts.map +1 -1
  17. package/dist/extends-resolution.js +68 -0
  18. package/dist/index.d.ts +9 -2
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +5 -1
  21. package/dist/invocation-contract.d.ts +100 -0
  22. package/dist/invocation-contract.d.ts.map +1 -0
  23. package/dist/invocation-contract.js +208 -0
  24. package/dist/schema-compat.d.ts +12 -4
  25. package/dist/schema-compat.d.ts.map +1 -1
  26. package/dist/schema-compat.js +185 -9
  27. package/dist/validate-base-mapping.js +11 -1
  28. package/dist/validate-cel-context.d.ts +0 -6
  29. package/dist/validate-cel-context.d.ts.map +1 -1
  30. package/dist/validate-cel-context.js +51 -4
  31. package/dist/validate-invocation-contract.d.ts +30 -0
  32. package/dist/validate-invocation-contract.d.ts.map +1 -0
  33. package/dist/validate-invocation-contract.js +394 -0
  34. package/dist/validate-module-artifact.d.ts +27 -0
  35. package/dist/validate-module-artifact.d.ts.map +1 -0
  36. package/dist/validate-module-artifact.js +131 -0
  37. package/dist/validate-step-inputs.d.ts +24 -0
  38. package/dist/validate-step-inputs.d.ts.map +1 -0
  39. package/dist/validate-step-inputs.js +87 -0
  40. package/dist/validate-throws-coverage.d.ts +1 -1
  41. package/dist/validate-throws-coverage.d.ts.map +1 -1
  42. package/dist/validate-throws-coverage.js +9 -1
  43. package/package.json +2 -2
  44. package/src/analysis-registry.ts +44 -34
  45. package/src/analyzer.ts +177 -100
  46. package/src/artifact-layer-index.ts +162 -0
  47. package/src/artifact-selector.ts +171 -0
  48. package/src/builtins.ts +135 -20
  49. package/src/extends-resolution.ts +86 -0
  50. package/src/index.ts +38 -1
  51. package/src/invocation-contract.ts +275 -0
  52. package/src/schema-compat.ts +191 -8
  53. package/src/validate-base-mapping.ts +14 -1
  54. package/src/validate-cel-context.ts +49 -4
  55. package/src/validate-invocation-contract.ts +450 -0
  56. package/src/validate-module-artifact.ts +141 -0
  57. package/src/validate-step-inputs.ts +117 -0
  58. package/src/validate-throws-coverage.ts +12 -2
@@ -0,0 +1,55 @@
1
+ /**
2
+ * The **layer index** of `kernel/specs/module-artifact.md` — the `layers:` block
3
+ * a published `telo.yaml` carries, listing every layer of the module artifact
4
+ * except the manifest layer itself.
5
+ *
6
+ * Why it lives in `telo.yaml` rather than in the OCI manifest, which has layers
7
+ * natively: a Telo import is pinned to a hash of `telo.yaml` and nothing else.
8
+ * The OCI manifest sits one level up, is fetched by a reference that is usually
9
+ * a mutable tag, and is never hashed by Telo — so digests held only there would
10
+ * leave the pin proving nothing about the payload. Pinning the OCI manifest
11
+ * instead is circular: `telo.yaml` is one of its layers.
12
+ *
13
+ * The manifest layer therefore has no entry — a hash of `telo.yaml` cannot sit
14
+ * inside `telo.yaml`. It is pinned by the importer's `#sha256-...` instead, so
15
+ * the chain reads `import pin -> telo.yaml -> blob digest -> layer contents`.
16
+ *
17
+ * Each entry carries two digests, answering different questions:
18
+ * - `blob` — the OCI blob digest over the pushed bytes. It *addresses* the
19
+ * layer, so a client pulls by digest and never reads the OCI layer list, and
20
+ * it verifies the transfer. Publish pushes payload blobs first and injects
21
+ * their digests here, then pushes the manifest blob, so nothing is circular.
22
+ * - `integrity` — the content digest (`computeFilesIntegrity`) over that
23
+ * layer's own files, independent of tar/gzip framing. It verifies what is
24
+ * already extracted on disk and can be re-derived from it without re-tarring,
25
+ * which is what makes a per-layer cache marker checkable.
26
+ *
27
+ * Browser-safe: `telo check`, the editor and the hub validate an index through
28
+ * this module; only the kernel fetches and extracts.
29
+ */
30
+ import { type ArtifactSelector, type LayerRole, type PlatformTarget } from "./artifact-selector.js";
31
+ export interface ArtifactLayer {
32
+ role: LayerRole;
33
+ /** Present on `controller` layers only. */
34
+ selector?: ArtifactSelector;
35
+ /** OCI blob digest — addresses the layer and verifies the transfer. */
36
+ blob: string;
37
+ /** Content digest over the layer's files — verifies what is on disk. */
38
+ integrity: string;
39
+ }
40
+ export declare class LayerIndexError extends Error {
41
+ readonly code = "INVALID_LAYER_INDEX";
42
+ constructor(detail: string);
43
+ }
44
+ /**
45
+ * Parse and validate a `layers:` value off an owner document. Order is
46
+ * preserved — when several controller layers match a target, precedence is
47
+ * declaration order, so the author controls it.
48
+ */
49
+ export declare function parseLayerIndex(value: unknown, describe?: string): ArtifactLayer[];
50
+ /** The singleton layer for a role, or undefined when the artifact has none. */
51
+ export declare function singletonLayer(layers: readonly ArtifactLayer[], role: Exclude<LayerRole, "controller">): ArtifactLayer | undefined;
52
+ /** Every controller layer matching `target`, in declaration order. Used by
53
+ * `telo install` to warm a cache for one platform. */
54
+ export declare function matchControllerLayers(layers: readonly ArtifactLayer[], target: PlatformTarget): ArtifactLayer[];
55
+ //# sourceMappingURL=artifact-layer-index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifact-layer-index.d.ts","sourceRoot":"","sources":["../src/artifact-layer-index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAKL,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,cAAc,EACpB,MAAM,wBAAwB,CAAC;AAQhC,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,SAAS,CAAC;IAChB,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,IAAI,yBAAyB;gBAE1B,MAAM,EAAE,MAAM;CAI3B;AAiBD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,SAAW,GAAG,aAAa,EAAE,CAyDpF;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAC5B,MAAM,EAAE,SAAS,aAAa,EAAE,EAChC,IAAI,EAAE,OAAO,CAAC,SAAS,EAAE,YAAY,CAAC,GACrC,aAAa,GAAG,SAAS,CAE3B;AAED;uDACuD;AACvD,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,SAAS,aAAa,EAAE,EAChC,MAAM,EAAE,cAAc,GACrB,aAAa,EAAE,CAIjB"}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * The **layer index** of `kernel/specs/module-artifact.md` — the `layers:` block
3
+ * a published `telo.yaml` carries, listing every layer of the module artifact
4
+ * except the manifest layer itself.
5
+ *
6
+ * Why it lives in `telo.yaml` rather than in the OCI manifest, which has layers
7
+ * natively: a Telo import is pinned to a hash of `telo.yaml` and nothing else.
8
+ * The OCI manifest sits one level up, is fetched by a reference that is usually
9
+ * a mutable tag, and is never hashed by Telo — so digests held only there would
10
+ * leave the pin proving nothing about the payload. Pinning the OCI manifest
11
+ * instead is circular: `telo.yaml` is one of its layers.
12
+ *
13
+ * The manifest layer therefore has no entry — a hash of `telo.yaml` cannot sit
14
+ * inside `telo.yaml`. It is pinned by the importer's `#sha256-...` instead, so
15
+ * the chain reads `import pin -> telo.yaml -> blob digest -> layer contents`.
16
+ *
17
+ * Each entry carries two digests, answering different questions:
18
+ * - `blob` — the OCI blob digest over the pushed bytes. It *addresses* the
19
+ * layer, so a client pulls by digest and never reads the OCI layer list, and
20
+ * it verifies the transfer. Publish pushes payload blobs first and injects
21
+ * their digests here, then pushes the manifest blob, so nothing is circular.
22
+ * - `integrity` — the content digest (`computeFilesIntegrity`) over that
23
+ * layer's own files, independent of tar/gzip framing. It verifies what is
24
+ * already extracted on disk and can be re-derived from it without re-tarring,
25
+ * which is what makes a per-layer cache marker checkable.
26
+ *
27
+ * Browser-safe: `telo check`, the editor and the hub validate an index through
28
+ * this module; only the kernel fetches and extracts.
29
+ */
30
+ import { isLayerRole, normalizeSelector, selectorKey, selectorMatches, } from "./artifact-selector.js";
31
+ /** OCI content digest: `sha256:` + 64 lowercase hex. */
32
+ const BLOB_DIGEST = /^sha256:[0-9a-f]{64}$/;
33
+ /** Telo content digest: `sha256-` + unpadded base64url of 32 bytes. */
34
+ const CONTENT_DIGEST = /^sha256-[A-Za-z0-9_-]{43}$/;
35
+ export class LayerIndexError extends Error {
36
+ code = "INVALID_LAYER_INDEX";
37
+ constructor(detail) {
38
+ super(detail);
39
+ this.name = "LayerIndexError";
40
+ }
41
+ }
42
+ function digest(field, raw, describe) {
43
+ if (typeof raw !== "string" || raw === "") {
44
+ throw new LayerIndexError(`${describe}: ${field} is required and must be a string.`);
45
+ }
46
+ const pattern = field === "blob" ? BLOB_DIGEST : CONTENT_DIGEST;
47
+ if (!pattern.test(raw)) {
48
+ throw new LayerIndexError(field === "blob"
49
+ ? `${describe}: blob '${raw}' is not an OCI digest (expected 'sha256:' + 64 hex characters).`
50
+ : `${describe}: integrity '${raw}' is not a content digest (expected 'sha256-' + 43 base64url characters).`);
51
+ }
52
+ return raw;
53
+ }
54
+ /**
55
+ * Parse and validate a `layers:` value off an owner document. Order is
56
+ * preserved — when several controller layers match a target, precedence is
57
+ * declaration order, so the author controls it.
58
+ */
59
+ export function parseLayerIndex(value, describe = "layers") {
60
+ if (!Array.isArray(value)) {
61
+ throw new LayerIndexError(`${describe}: expected an array of layer entries.`);
62
+ }
63
+ const layers = [];
64
+ const seenSelectors = new Set();
65
+ const seenSingletons = new Set();
66
+ value.forEach((raw, index) => {
67
+ const where = `${describe}[${index}]`;
68
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
69
+ throw new LayerIndexError(`${where}: expected an object.`);
70
+ }
71
+ const entry = raw;
72
+ if (!isLayerRole(entry.role)) {
73
+ throw new LayerIndexError(`${where}: role must be one of 'controller', 'assets', 'common'; got ` +
74
+ `${entry.role === undefined ? "nothing" : `'${String(entry.role)}'`}.`);
75
+ }
76
+ const role = entry.role;
77
+ let selector;
78
+ if (role === "controller") {
79
+ if (entry.selector === undefined) {
80
+ throw new LayerIndexError(`${where}: a controller layer must declare a selector.`);
81
+ }
82
+ selector = normalizeSelector(entry.selector, where);
83
+ const key = selectorKey(selector);
84
+ if (seenSelectors.has(key)) {
85
+ throw new LayerIndexError(`${where}: a second controller layer claims the selector ${key}. ` +
86
+ `Each selector addresses exactly one layer.`);
87
+ }
88
+ seenSelectors.add(key);
89
+ }
90
+ else {
91
+ if (entry.selector !== undefined) {
92
+ throw new LayerIndexError(`${where}: a '${role}' layer must not declare a selector — it is a singleton.`);
93
+ }
94
+ if (seenSingletons.has(role)) {
95
+ throw new LayerIndexError(`${where}: a second '${role}' layer is declared.`);
96
+ }
97
+ seenSingletons.add(role);
98
+ }
99
+ layers.push({
100
+ role,
101
+ ...(selector ? { selector } : {}),
102
+ blob: digest("blob", entry.blob, where),
103
+ integrity: digest("integrity", entry.integrity, where),
104
+ });
105
+ });
106
+ return layers;
107
+ }
108
+ /** The singleton layer for a role, or undefined when the artifact has none. */
109
+ export function singletonLayer(layers, role) {
110
+ return layers.find((l) => l.role === role);
111
+ }
112
+ /** Every controller layer matching `target`, in declaration order. Used by
113
+ * `telo install` to warm a cache for one platform. */
114
+ export function matchControllerLayers(layers, target) {
115
+ return layers.filter((l) => l.role === "controller" && l.selector !== undefined && selectorMatches(l.selector, target));
116
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The **selector** of `kernel/specs/module-artifact.md` — the tuple a bundled
3
+ * controller candidate is chosen by, and the key a controller layer of a module
4
+ * artifact is stored under.
5
+ *
6
+ * A selector is `format` plus the optional platform axes `os` / `arch` / `libc`.
7
+ * Matching is one rule, applied per axis: an axis the selector omits accepts
8
+ * anything, an axis it states must be equal. That is what lets a `js` controller
9
+ * be platform-neutral and a `napi` controller be pinned to one triple, with no
10
+ * special case for either.
11
+ *
12
+ * Browser-safe and dependency-free by construction. Three consumers must agree
13
+ * on this grammar or a published artifact stops loading: `telo publish`
14
+ * (partitioning files into layers), `telo install --platform` (deciding which
15
+ * layers to pre-fetch), and the kernel's bundle controller loader (matching a
16
+ * candidate against the host). Keeping it here — beside the redaction path
17
+ * parser, for the same reason — means one implementation rather than three that
18
+ * drift.
19
+ *
20
+ * PURL *syntax* is deliberately not parsed here. Callers hand in the format and
21
+ * an already-decoded qualifier map, so this module owns selector semantics while
22
+ * the caller owns its own package-URL library. The Node vocabulary is likewise
23
+ * not known here: `process.platform` / `process.arch` are mapped to the
24
+ * canonical OCI/GOOS names at the kernel boundary, since these values are
25
+ * published into OCI descriptors.
26
+ */
27
+ /** The role a layer plays in a module artifact. `controller` layers carry a
28
+ * selector; `assets` and `common` are singletons and carry none. */
29
+ export type LayerRole = "controller" | "assets" | "common";
30
+ export declare const LAYER_ROLES: readonly LayerRole[];
31
+ export declare function isLayerRole(value: unknown): value is LayerRole;
32
+ /** The platform axes, in canonical order. Not a closed vocabulary of *values* —
33
+ * new architectures appear without a Telo release — only of axis names. */
34
+ export declare const PLATFORM_AXES: readonly ["os", "arch", "libc"];
35
+ export type PlatformAxis = (typeof PLATFORM_AXES)[number];
36
+ export interface ArtifactSelector {
37
+ /** Bundled controller format: the PURL name segment (`js`, `napi`, `wasm`, …). */
38
+ format: string;
39
+ os?: string;
40
+ arch?: string;
41
+ libc?: string;
42
+ }
43
+ /** What a selector is matched against: the host the kernel runs on, or the
44
+ * target `telo install --platform` is warming a cache for. An axis left
45
+ * undetermined (a host whose libc cannot be detected) matches no selector that
46
+ * constrains it — refusing to load is the safe direction for a native binary. */
47
+ export interface PlatformTarget {
48
+ format?: string;
49
+ os?: string;
50
+ arch?: string;
51
+ libc?: string;
52
+ }
53
+ export declare class ArtifactSelectorError extends Error {
54
+ readonly code = "INVALID_ARTIFACT_SELECTOR";
55
+ constructor(detail: string);
56
+ }
57
+ /**
58
+ * Build a selector from a controller candidate's format and qualifier map.
59
+ * Qualifier keys other than the platform axes are ignored — `path` and the
60
+ * sibling list live in the same map and are not part of the selector.
61
+ */
62
+ export declare function selectorFromQualifiers(format: unknown, qualifiers: Readonly<Record<string, unknown>> | undefined, describe?: string): ArtifactSelector;
63
+ /** Validate and normalize a selector read off a published layer index. */
64
+ export declare function normalizeSelector(value: unknown, describe?: string): ArtifactSelector;
65
+ /**
66
+ * The canonical stable key for a selector: sorted `axis=value` pairs joined by
67
+ * `;`. Used to group entry points into layers at publish time and to detect two
68
+ * layers claiming the same selector. Sorted and fully qualified so no two
69
+ * distinct selectors can collide and no one selector has two spellings.
70
+ */
71
+ export declare function selectorKey(selector: ArtifactSelector): string;
72
+ /** Human-facing rendering for diagnostics and the publish partition printout. */
73
+ export declare function describeSelector(selector: ArtifactSelector): string;
74
+ /**
75
+ * The matching rule: every axis the selector states must equal the target's;
76
+ * every axis it omits accepts anything. A target axis left undetermined matches
77
+ * only a selector that does not constrain it — a host whose libc is unknown must
78
+ * not be handed a `libc=gnu` binary on the assumption it will run.
79
+ */
80
+ export declare function selectorMatches(selector: ArtifactSelector, target: PlatformTarget): boolean;
81
+ //# sourceMappingURL=artifact-selector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifact-selector.d.ts","sourceRoot":"","sources":["../src/artifact-selector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH;qEACqE;AACrE,MAAM,MAAM,SAAS,GAAG,YAAY,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE3D,eAAO,MAAM,WAAW,EAAE,SAAS,SAAS,EAAuC,CAAC;AAEpF,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAE9D;AAED;4EAC4E;AAC5E,eAAO,MAAM,aAAa,iCAAkC,CAAC;AAE7D,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1D,MAAM,WAAW,gBAAgB;IAC/B,kFAAkF;IAClF,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;kFAGkF;AAClF,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,IAAI,+BAA+B;gBAEhC,MAAM,EAAE,MAAM;CAI3B;AAsBD;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,OAAO,EACf,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,EACzD,QAAQ,SAAwB,GAC/B,gBAAgB,CAUlB;AAED,0EAA0E;AAC1E,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,OAAO,EACd,QAAQ,SAAmB,GAC1B,gBAAgB,CAelB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,gBAAgB,GAAG,MAAM,CAO9D;AAED,iFAAiF;AACjF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,MAAM,CAKnE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,gBAAgB,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAQ3F"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * The **selector** of `kernel/specs/module-artifact.md` — the tuple a bundled
3
+ * controller candidate is chosen by, and the key a controller layer of a module
4
+ * artifact is stored under.
5
+ *
6
+ * A selector is `format` plus the optional platform axes `os` / `arch` / `libc`.
7
+ * Matching is one rule, applied per axis: an axis the selector omits accepts
8
+ * anything, an axis it states must be equal. That is what lets a `js` controller
9
+ * be platform-neutral and a `napi` controller be pinned to one triple, with no
10
+ * special case for either.
11
+ *
12
+ * Browser-safe and dependency-free by construction. Three consumers must agree
13
+ * on this grammar or a published artifact stops loading: `telo publish`
14
+ * (partitioning files into layers), `telo install --platform` (deciding which
15
+ * layers to pre-fetch), and the kernel's bundle controller loader (matching a
16
+ * candidate against the host). Keeping it here — beside the redaction path
17
+ * parser, for the same reason — means one implementation rather than three that
18
+ * drift.
19
+ *
20
+ * PURL *syntax* is deliberately not parsed here. Callers hand in the format and
21
+ * an already-decoded qualifier map, so this module owns selector semantics while
22
+ * the caller owns its own package-URL library. The Node vocabulary is likewise
23
+ * not known here: `process.platform` / `process.arch` are mapped to the
24
+ * canonical OCI/GOOS names at the kernel boundary, since these values are
25
+ * published into OCI descriptors.
26
+ */
27
+ export const LAYER_ROLES = ["controller", "assets", "common"];
28
+ export function isLayerRole(value) {
29
+ return typeof value === "string" && LAYER_ROLES.includes(value);
30
+ }
31
+ /** The platform axes, in canonical order. Not a closed vocabulary of *values* —
32
+ * new architectures appear without a Telo release — only of axis names. */
33
+ export const PLATFORM_AXES = ["os", "arch", "libc"];
34
+ export class ArtifactSelectorError extends Error {
35
+ code = "INVALID_ARTIFACT_SELECTOR";
36
+ constructor(detail) {
37
+ super(detail);
38
+ this.name = "ArtifactSelectorError";
39
+ }
40
+ }
41
+ /** Canonical token shape for every selector value. Lowercase, so the same
42
+ * platform written two ways is one layer rather than two. */
43
+ const TOKEN = /^[a-z0-9][a-z0-9_.-]*$/;
44
+ function normalizeToken(axis, raw, describe) {
45
+ if (typeof raw !== "string") {
46
+ throw new ArtifactSelectorError(`${describe}: ${axis} must be a string, got ${raw === null ? "null" : typeof raw}.`);
47
+ }
48
+ const value = raw.trim().toLowerCase();
49
+ if (!TOKEN.test(value)) {
50
+ throw new ArtifactSelectorError(`${describe}: ${axis} value '${raw}' is not a canonical token. ` +
51
+ `Use lowercase letters, digits, '.', '-' or '_', starting with a letter or digit.`);
52
+ }
53
+ return value;
54
+ }
55
+ /**
56
+ * Build a selector from a controller candidate's format and qualifier map.
57
+ * Qualifier keys other than the platform axes are ignored — `path` and the
58
+ * sibling list live in the same map and are not part of the selector.
59
+ */
60
+ export function selectorFromQualifiers(format, qualifiers, describe = "controller selector") {
61
+ const selector = {
62
+ format: normalizeToken("format", format, describe),
63
+ };
64
+ for (const axis of PLATFORM_AXES) {
65
+ const raw = qualifiers?.[axis];
66
+ if (raw === undefined || raw === "")
67
+ continue;
68
+ selector[axis] = normalizeToken(axis, raw, describe);
69
+ }
70
+ return selector;
71
+ }
72
+ /** Validate and normalize a selector read off a published layer index. */
73
+ export function normalizeSelector(value, describe = "layer selector") {
74
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
75
+ throw new ArtifactSelectorError(`${describe}: expected an object of selector axes.`);
76
+ }
77
+ const record = value;
78
+ const unknown = Object.keys(record).filter((k) => k !== "format" && !PLATFORM_AXES.includes(k));
79
+ if (unknown.length > 0) {
80
+ throw new ArtifactSelectorError(`${describe}: unknown selector ${unknown.length === 1 ? "axis" : "axes"} ` +
81
+ `${unknown.map((k) => `'${k}'`).join(", ")}. Known axes: format, ${PLATFORM_AXES.join(", ")}.`);
82
+ }
83
+ return selectorFromQualifiers(record.format, record, describe);
84
+ }
85
+ /**
86
+ * The canonical stable key for a selector: sorted `axis=value` pairs joined by
87
+ * `;`. Used to group entry points into layers at publish time and to detect two
88
+ * layers claiming the same selector. Sorted and fully qualified so no two
89
+ * distinct selectors can collide and no one selector has two spellings.
90
+ */
91
+ export function selectorKey(selector) {
92
+ const pairs = [`format=${selector.format}`];
93
+ for (const axis of PLATFORM_AXES) {
94
+ const value = selector[axis];
95
+ if (value !== undefined)
96
+ pairs.push(`${axis}=${value}`);
97
+ }
98
+ return pairs.sort().join(";");
99
+ }
100
+ /** Human-facing rendering for diagnostics and the publish partition printout. */
101
+ export function describeSelector(selector) {
102
+ const platform = PLATFORM_AXES.map((axis) => selector[axis]).filter((v) => v !== undefined);
103
+ return platform.length === 0 ? selector.format : `${selector.format} (${platform.join("/")})`;
104
+ }
105
+ /**
106
+ * The matching rule: every axis the selector states must equal the target's;
107
+ * every axis it omits accepts anything. A target axis left undetermined matches
108
+ * only a selector that does not constrain it — a host whose libc is unknown must
109
+ * not be handed a `libc=gnu` binary on the assumption it will run.
110
+ */
111
+ export function selectorMatches(selector, target) {
112
+ if (target.format !== undefined && selector.format !== target.format)
113
+ return false;
114
+ for (const axis of PLATFORM_AXES) {
115
+ const constraint = selector[axis];
116
+ if (constraint === undefined)
117
+ continue;
118
+ if (target[axis] !== constraint)
119
+ return false;
120
+ }
121
+ return true;
122
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../src/builtins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AA4GvD,eAAO,MAAM,eAAe,EAAE,kBAAkB,EA+nB/C,CAAC"}
1
+ {"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../src/builtins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AA2JvD,eAAO,MAAM,eAAe,EAAE,kBAAkB,EAmsB/C,CAAC"}
package/dist/builtins.js CHANGED
@@ -20,6 +20,50 @@ const PROVENANCE_METADATA = {
20
20
  license: { type: "string" },
21
21
  documentation: { type: "string" },
22
22
  };
23
+ /** Author-declared subset of `files:` that ships in the artifact's lazily
24
+ * materialized `assets` layer. Optional: an unclaimed file joins the `common`
25
+ * layer, which is pulled alongside any controller layer, so omitting this costs
26
+ * laziness rather than correctness. See kernel/specs/module-artifact.md. */
27
+ const ASSETS_FILES_SCHEMA = {
28
+ type: "array",
29
+ items: { type: "string" },
30
+ };
31
+ /** The published layer index, written by `telo publish` (never hand-authored).
32
+ * One entry per layer except the manifest layer, which cannot list its own hash
33
+ * inside itself and is pinned by the importer's `#sha256-...` instead. Shape and
34
+ * matching rules are normative in kernel/specs/module-artifact.md; the parser
35
+ * that enforces them is `artifact-layer-index.ts`. */
36
+ const LAYER_INDEX_SCHEMA = {
37
+ type: "array",
38
+ items: {
39
+ type: "object",
40
+ required: ["role", "blob", "integrity"],
41
+ properties: {
42
+ role: { type: "string", enum: ["controller", "assets", "common"] },
43
+ selector: {
44
+ type: "object",
45
+ required: ["format"],
46
+ properties: {
47
+ format: { type: "string" },
48
+ os: { type: "string" },
49
+ arch: { type: "string" },
50
+ libc: { type: "string" },
51
+ },
52
+ additionalProperties: false,
53
+ },
54
+ blob: { type: "string", pattern: "^sha256:[0-9a-f]{64}$" },
55
+ integrity: { type: "string", pattern: "^sha256-[A-Za-z0-9_-]{43}$" },
56
+ },
57
+ additionalProperties: false,
58
+ },
59
+ };
60
+ /** The pre-layers payload digest, superseded by the per-layer `integrity` values
61
+ * in `layers:`. Accepted and ignored, for one reason only: a module published in
62
+ * the old single-blob shape must reach the *actionable* failure — the controller
63
+ * loader's "republish the module" error — instead of dying earlier on
64
+ * `must NOT have additional properties`, which tells an author nothing. Nothing
65
+ * reads this field. */
66
+ const LEGACY_FILES_INTEGRITY_SCHEMA = { type: "string" };
23
67
  /** The six named levels of `kernel/specs/logging.md` §5.1. The full 1–24 OTel
24
68
  * range stays valid on the wire; only these are nameable in a manifest. */
25
69
  const LOG_LEVEL_ENUM = ["trace", "debug", "info", "warn", "error", "fatal"];
@@ -162,6 +206,64 @@ export const KERNEL_BUILTINS = [
162
206
  additionalProperties: false,
163
207
  },
164
208
  },
209
+ {
210
+ // Telo.JsonSchema — the concrete data-shape kind, in the kernel rather than
211
+ // in an installable module for the same reason the mandatory sinks are:
212
+ // declaring a shape is not optional. Every kind with an invocation contract
213
+ // needs one, so requiring an import to write `inputType:` would put a tax on
214
+ // the one thing the contract wants authors to do more of — and a library
215
+ // declaring a contract would have to import a module purely to describe
216
+ // itself. `type.JsonSchema` remains as a deprecated alias of this kind.
217
+ kind: "Telo.Definition",
218
+ metadata: { name: "JsonSchema", module: "Telo" },
219
+ capability: "Telo.Type",
220
+ // Declared so the kind reads as controller-BEARING, which is what lets
221
+ // another definition inherit it by delegation (`extends: Telo.JsonSchema`
222
+ // with no controller of its own). The entry is never loaded from — the
223
+ // kernel registers this controller directly at boot, before any lazy
224
+ // resolution — it states truthfully who provides it.
225
+ controllers: [{ runtime: "kernel", entry: "Telo.JsonSchema" }],
226
+ schema: {
227
+ type: "object",
228
+ properties: {
229
+ schema: {
230
+ title: "Schema",
231
+ description: "JSON Schema definition for the declared data type.",
232
+ type: "object",
233
+ },
234
+ extends: {
235
+ title: "Extends",
236
+ description: "Parent type name or list of parent type names to inherit from.",
237
+ oneOf: [{ type: "string" }, { type: "array", items: { type: "string" } }],
238
+ },
239
+ rules: {
240
+ title: "Rules",
241
+ description: "CEL-based business invariant rules. Each rule's condition must return true for valid data.",
242
+ type: "array",
243
+ items: {
244
+ type: "object",
245
+ properties: {
246
+ condition: {
247
+ type: "string",
248
+ description: "CEL expression evaluated with 'this' bound to the data. Must return true for valid data.",
249
+ },
250
+ code: {
251
+ type: "string",
252
+ description: "Machine-readable error code surfaced on validation failure.",
253
+ },
254
+ message: {
255
+ type: "string",
256
+ description: "Optional human-readable hint for the validation failure.",
257
+ },
258
+ },
259
+ required: ["condition", "code"],
260
+ },
261
+ },
262
+ },
263
+ required: ["schema"],
264
+ additionalProperties: false,
265
+ },
266
+ },
165
267
  {
166
268
  kind: "Telo.Definition",
167
269
  metadata: { name: "Abstract", module: "Telo" },
@@ -435,6 +537,11 @@ export const KERNEL_BUILTINS = [
435
537
  default: "shared",
436
538
  },
437
539
  targets: {
540
+ // Boot targets form a step list: a later target reads an earlier one's
541
+ // result as `steps.<name>.result`, exactly as a sequence step does, so
542
+ // the same annotation types that context and drives the call-site
543
+ // contract check.
544
+ "x-telo-step-context": { invoke: "invoke", outputType: "outputType" },
438
545
  type: "array",
439
546
  items: {
440
547
  anyOf: [
@@ -508,7 +615,15 @@ export const KERNEL_BUILTINS = [
508
615
  { "x-telo-ref": "Telo.Runnable" },
509
616
  ],
510
617
  },
511
- inputs: { type: "object", additionalProperties: true },
618
+ inputs: {
619
+ // Same annotation Run.Sequence steps carry: it is what makes
620
+ // a boot target's inputs visible to the call-site contract
621
+ // check and to the wiring rule. Without it the kernel would
622
+ // validate these at dispatch and nothing before it.
623
+ "x-telo-topology-role": "inputs",
624
+ type: "object",
625
+ additionalProperties: true,
626
+ },
512
627
  when: { type: "string" },
513
628
  },
514
629
  additionalProperties: false,
@@ -520,21 +635,18 @@ export const KERNEL_BUILTINS = [
520
635
  type: "array",
521
636
  items: { type: "string" },
522
637
  },
523
- // Files bundled alongside `telo.yaml` into the module's registry
524
- // artifact (`module.tar.gz`) static assets served by Http.Static,
525
- // templates, etc. Ordered `.gitignore`-style patterns resolved against
526
- // the manifest dir at publish time. Analyzer-only role: accept the
527
- // field (the schema is additionalProperties:false); the analyzer never
528
- // reads the assets. See kernel/nodejs/plans/bundle-controllers.md.
638
+ // Files bundled alongside `telo.yaml` into the module's artifact —
639
+ // controller bundles, static assets served by Http.Static, templates,
640
+ // etc. Ordered `.gitignore`-style patterns resolved against the manifest
641
+ // dir at publish time. Analyzer-only role: accept the field (the schema
642
+ // is additionalProperties:false); the analyzer never reads the payload.
529
643
  files: {
530
644
  type: "array",
531
645
  items: { type: "string" },
532
646
  },
533
- // Integrity hash of the decompressed payload tar (`module.tar.gz`,
534
- // telo.yaml excluded), written by `telo publish`. Pinned transitively
535
- // by the importer's `#sha256-...` hash over this telo.yaml; verified at
536
- // extract time. See plans/federated-registries.md.
537
- filesIntegrity: { type: "string" },
647
+ assets: ASSETS_FILES_SCHEMA,
648
+ layers: LAYER_INDEX_SCHEMA,
649
+ filesIntegrity: LEGACY_FILES_INTEGRITY_SCHEMA,
538
650
  // Inline imports — name-keyed map sugar for separate `Telo.Import`
539
651
  // documents. The key is the PascalCase alias (the import's
540
652
  // `metadata.name`). Each value is either a bare source string
@@ -671,18 +783,16 @@ export const KERNEL_BUILTINS = [
671
783
  type: "array",
672
784
  items: { type: "string" },
673
785
  },
674
- // Files bundled into the module's registry artifact — same semantics as
675
- // the Telo.Application `files` field above (a library may ship bundled
676
- // templates, migrations, seed data).
786
+ // Files bundled into the module's artifact — same semantics as the
787
+ // Telo.Application `files` field above (a library may ship bundled
788
+ // controllers, templates, migrations, seed data).
677
789
  files: {
678
790
  type: "array",
679
791
  items: { type: "string" },
680
792
  },
681
- // Integrity hash of the decompressed payload tar (`module.tar.gz`,
682
- // telo.yaml excluded), written by `telo publish`. Pinned transitively
683
- // by the importer's `#sha256-...` hash over this telo.yaml; verified at
684
- // extract time. See plans/federated-registries.md.
685
- filesIntegrity: { type: "string" },
793
+ assets: ASSETS_FILES_SCHEMA,
794
+ layers: LAYER_INDEX_SCHEMA,
795
+ filesIntegrity: LEGACY_FILES_INTEGRITY_SCHEMA,
686
796
  // Inline imports — same name-keyed map sugar as Telo.Application; the
687
797
  // loader desugars each entry into a synthetic Telo.Import. See the
688
798
  // Application schema above and analyzer/nodejs/src/inline-imports.ts.
@@ -36,6 +36,47 @@ export declare function inheritedCapability(def: ResourceDefinition | undefined,
36
36
  * `mergeTypeSchemas` that `Type.JsonSchema.extends` uses.
37
37
  * - no `extends` → the own schema unchanged. */
38
38
  export declare function effectiveAuthorSchema(def: ResourceDefinition | undefined, resolve: DefResolver): Record<string, any>;
39
+ /** The two directions of a kind's invocation contract. `inputType` is what a
40
+ * caller sends to `invoke()`; `outputType` is what `invoke()` / `provide()`
41
+ * returns. */
42
+ export type ContractDirection = "inputType" | "outputType";
43
+ /**
44
+ * The **nearest declaration** of an invocation contract along the `extends`
45
+ * chain, self first — the raw type-field value, still to be resolved to a schema
46
+ * by the caller (which is what keeps this module free of manifest lookup).
47
+ *
48
+ * Contracts RESOLVE, they never merge. A definition that declares one fully
49
+ * replaces its ancestor's; one that declares none inherits its ancestor's
50
+ * verbatim, at any depth. This is deliberately unlike {@link
51
+ * effectiveAuthorSchema} and {@link effectiveStatusSchema}: construction config
52
+ * and observed state are additive, a call signature is not. Folding a child's
53
+ * required fields into its parent's yields a union no caller can satisfy, and it
54
+ * would reject the very remapping `base:` + `inputs:` exists for — the point of
55
+ * a child declaring a signature is that it accepts something *different*.
56
+ *
57
+ * Substitutability is not weakened by that, because `extends` never carried the
58
+ * dispatch contract: it decides which slots accept a resource. Whether a
59
+ * particular slot may hold a resource whose contract differs from the slot's
60
+ * declared kind is a wiring question, answered per slot by
61
+ * `validate-invocation-contract`'s wiring rule.
62
+ */
63
+ export declare function effectiveContractField(def: ResourceDefinition | undefined, resolve: DefResolver, direction: ContractDirection): unknown;
64
+ /** The definition in the `extends` chain (self first) that actually DECLARES the
65
+ * contract for `direction` — the one whose scope its `telo#Type` references
66
+ * resolve in, and the one a diagnostic should name. Undefined when nothing in
67
+ * the chain declares it. */
68
+ export declare function contractDeclarer(def: ResourceDefinition | undefined, resolve: DefResolver, direction: ContractDirection): ResourceDefinition | undefined;
69
+ /** True when this definition declares its own contract for `direction` while
70
+ * inheriting the controller that will execute it — the case that REQUIRES a
71
+ * bridging mapping (`inputs:` for inputs, `result:` for outputs), because the
72
+ * inherited controller only understands the ancestor's shape. A definition with
73
+ * its own controller or template body is exempt: its controller *is* the
74
+ * implementation of whatever it declares. */
75
+ export declare function needsContractMapping(def: ResourceDefinition | undefined, resolve: DefResolver, direction: ContractDirection): boolean;
76
+ /** The mapping field that bridges a replaced contract back to the inherited
77
+ * controller: `inputs:` maps the child's signature onto the parent's call,
78
+ * `result:` maps the parent's result back to the child's declared output. */
79
+ export declare function mappingFieldFor(direction: ContractDirection): "inputs" | "result";
39
80
  /** The observed state a kind reports (`status:`), folded through `extends`:
40
81
  * - with `base:` present → the **parent's** effective status unchanged; the
41
82
  * child delegates to the parent's controller and *is* a parent instance, so
@@ -1 +1 @@
1
- {"version":3,"file":"extends-resolution.d.ts","sourceRoot":"","sources":["../src/extends-resolution.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAGvD;;;;;;;2DAO2D;AAC3D,MAAM,MAAM,WAAW,GAAG,CACxB,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,kBAAkB,KACtB,kBAAkB,GAAG,SAAS,CAAC;AAsBpC;sDACsD;AACtD,wBAAgB,aAAa,CAC3B,GAAG,EAAE,kBAAkB,GAAG,SAAS,EACnC,OAAO,EAAE,WAAW,GACnB,kBAAkB,GAAG,SAAS,CAIhC;AAED;6EAC6E;AAC7E,wBAAgB,aAAa,CAC3B,GAAG,EAAE,kBAAkB,GAAG,SAAS,EACnC,OAAO,EAAE,WAAW,GACnB,kBAAkB,EAAE,CAUtB;AAED;iFACiF;AACjF,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,kBAAkB,GAAG,SAAS,GAAG,OAAO,CAUvF;AAED;;mFAEmF;AACnF,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,kBAAkB,GAAG,SAAS,EACnC,OAAO,EAAE,WAAW,GACnB,kBAAkB,GAAG,SAAS,CAMhC;AAED;;sCAEsC;AACtC,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,kBAAkB,GAAG,SAAS,EACnC,OAAO,EAAE,WAAW,GACnB,OAAO,CAGT;AAED;2EAC2E;AAC3E,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,kBAAkB,GAAG,SAAS,EACnC,OAAO,EAAE,WAAW,GACnB,MAAM,GAAG,SAAS,CAMpB;AAED;;;;;;iDAMiD;AACjD,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,kBAAkB,GAAG,SAAS,EACnC,OAAO,EAAE,WAAW,GACnB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAOrB;AAED;;;;;;;;;gDASgD;AAChD,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,kBAAkB,GAAG,SAAS,EACnC,OAAO,EAAE,WAAW,GACnB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CASjC"}
1
+ {"version":3,"file":"extends-resolution.d.ts","sourceRoot":"","sources":["../src/extends-resolution.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAGvD;;;;;;;2DAO2D;AAC3D,MAAM,MAAM,WAAW,GAAG,CACxB,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,kBAAkB,KACtB,kBAAkB,GAAG,SAAS,CAAC;AA0BpC;sDACsD;AACtD,wBAAgB,aAAa,CAC3B,GAAG,EAAE,kBAAkB,GAAG,SAAS,EACnC,OAAO,EAAE,WAAW,GACnB,kBAAkB,GAAG,SAAS,CAIhC;AAED;6EAC6E;AAC7E,wBAAgB,aAAa,CAC3B,GAAG,EAAE,kBAAkB,GAAG,SAAS,EACnC,OAAO,EAAE,WAAW,GACnB,kBAAkB,EAAE,CAUtB;AAED;iFACiF;AACjF,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,kBAAkB,GAAG,SAAS,GAAG,OAAO,CAUvF;AAED;;mFAEmF;AACnF,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,kBAAkB,GAAG,SAAS,EACnC,OAAO,EAAE,WAAW,GACnB,kBAAkB,GAAG,SAAS,CAMhC;AAED;;sCAEsC;AACtC,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,kBAAkB,GAAG,SAAS,EACnC,OAAO,EAAE,WAAW,GACnB,OAAO,CAGT;AAED;2EAC2E;AAC3E,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,kBAAkB,GAAG,SAAS,EACnC,OAAO,EAAE,WAAW,GACnB,MAAM,GAAG,SAAS,CAMpB;AAED;;;;;;iDAMiD;AACjD,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,kBAAkB,GAAG,SAAS,EACnC,OAAO,EAAE,WAAW,GACnB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAOrB;AAED;;eAEe;AACf,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,YAAY,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,kBAAkB,GAAG,SAAS,EACnC,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,iBAAiB,GAC3B,OAAO,CAQT;AAED;;;6BAG6B;AAC7B,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,kBAAkB,GAAG,SAAS,EACnC,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,iBAAiB,GAC3B,kBAAkB,GAAG,SAAS,CAShC;AAED;;;;;8CAK8C;AAC9C,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,kBAAkB,GAAG,SAAS,EACnC,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,iBAAiB,GAC3B,OAAO,CAKT;AAED;;8EAE8E;AAC9E,wBAAgB,eAAe,CAAC,SAAS,EAAE,iBAAiB,GAAG,QAAQ,GAAG,QAAQ,CAEjF;AAED;;;;;;;;;gDASgD;AAChD,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,kBAAkB,GAAG,SAAS,EACnC,OAAO,EAAE,WAAW,GACnB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CASjC"}