@telorun/analyzer 0.72.0 → 0.74.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/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +76 -20
- package/dist/artifact-axes.d.ts +14 -0
- package/dist/artifact-axes.d.ts.map +1 -0
- package/dist/artifact-axes.js +12 -0
- package/dist/artifact-layer-index.d.ts +2 -2
- package/dist/artifact-layer-index.d.ts.map +1 -1
- package/dist/artifact-layer-index.js +9 -4
- package/dist/artifact-selector.d.ts +47 -21
- package/dist/artifact-selector.d.ts.map +1 -1
- package/dist/artifact-selector.js +85 -16
- package/dist/builtins.d.ts.map +1 -1
- package/dist/builtins.js +144 -113
- package/dist/call-graph.d.ts +12 -1
- package/dist/call-graph.d.ts.map +1 -1
- package/dist/call-graph.js +22 -0
- package/dist/catch-scope.js +1 -1
- package/dist/cel-scope-query.d.ts.map +1 -1
- package/dist/cel-scope-query.js +32 -11
- package/dist/cel-scope.d.ts.map +1 -1
- package/dist/cel-scope.js +1 -0
- package/dist/eval-paths.d.ts +36 -5
- package/dist/eval-paths.d.ts.map +1 -1
- package/dist/eval-paths.js +47 -6
- package/dist/extends-resolution.d.ts +12 -0
- package/dist/extends-resolution.d.ts.map +1 -1
- package/dist/extends-resolution.js +23 -0
- package/dist/forwarded-declaration.d.ts +26 -0
- package/dist/forwarded-declaration.d.ts.map +1 -0
- package/dist/forwarded-declaration.js +31 -0
- package/dist/index.d.ts +10 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/kernel-globals.d.ts.map +1 -1
- package/dist/kernel-globals.js +21 -5
- package/dist/module-alias-scope.d.ts +11 -0
- package/dist/module-alias-scope.d.ts.map +1 -1
- package/dist/module-alias-scope.js +10 -0
- package/dist/module-file-claims.d.ts +17 -1
- package/dist/module-file-claims.d.ts.map +1 -1
- package/dist/module-file-claims.js +38 -3
- package/dist/module-named-files.d.ts +72 -0
- package/dist/module-named-files.d.ts.map +1 -0
- package/dist/module-named-files.js +115 -0
- package/dist/native-entries.d.ts +83 -0
- package/dist/native-entries.d.ts.map +1 -0
- package/dist/native-entries.js +169 -0
- package/dist/nearest-name.d.ts +22 -0
- package/dist/nearest-name.d.ts.map +1 -0
- package/dist/nearest-name.js +43 -0
- package/dist/normalize-inline-resources.d.ts +41 -13
- package/dist/normalize-inline-resources.d.ts.map +1 -1
- package/dist/normalize-inline-resources.js +236 -54
- package/dist/reference-field-map.d.ts +59 -1
- package/dist/reference-field-map.d.ts.map +1 -1
- package/dist/reference-field-map.js +200 -34
- package/dist/release/destinations.d.ts +59 -0
- package/dist/release/destinations.d.ts.map +1 -0
- package/dist/release/destinations.js +75 -0
- package/dist/release/index.d.ts +6 -2
- package/dist/release/index.d.ts.map +1 -1
- package/dist/release/index.js +3 -1
- package/dist/release/ledger.d.ts +20 -11
- package/dist/release/ledger.d.ts.map +1 -1
- package/dist/release/ledger.js +43 -14
- package/dist/release/payload-digest.d.ts +2 -1
- package/dist/release/payload-digest.d.ts.map +1 -1
- package/dist/release/payload-digest.js +2 -1
- package/dist/release/release-plan.d.ts +3 -2
- package/dist/release/release-plan.d.ts.map +1 -1
- package/dist/release/release-plan.js +12 -19
- package/dist/release/workspace-config.d.ts +114 -34
- package/dist/release/workspace-config.d.ts.map +1 -1
- package/dist/release/workspace-config.js +322 -38
- package/dist/release/workspace-schema.d.ts +41 -0
- package/dist/release/workspace-schema.d.ts.map +1 -0
- package/dist/release/workspace-schema.js +77 -0
- package/dist/resolve-ref-sentinels.d.ts.map +1 -1
- package/dist/resolve-ref-sentinels.js +8 -5
- package/dist/resolve-throws-union.d.ts +25 -4
- package/dist/resolve-throws-union.d.ts.map +1 -1
- package/dist/resolve-throws-union.js +62 -32
- package/dist/resolve-zone-requirements.d.ts.map +1 -1
- package/dist/resolve-zone-requirements.js +2 -1
- package/dist/rewrite-synthetic-origins.d.ts +6 -1
- package/dist/rewrite-synthetic-origins.d.ts.map +1 -1
- package/dist/rewrite-synthetic-origins.js +107 -13
- package/dist/schema-compat.d.ts.map +1 -1
- package/dist/schema-compat.js +18 -5
- package/dist/schema-error-report.d.ts +7 -0
- package/dist/schema-error-report.d.ts.map +1 -1
- package/dist/schema-error-report.js +9 -4
- package/dist/schema-walk.d.ts +47 -13
- package/dist/schema-walk.d.ts.map +1 -1
- package/dist/schema-walk.js +150 -30
- package/dist/scope-declarations.d.ts +59 -0
- package/dist/scope-declarations.d.ts.map +1 -0
- package/dist/scope-declarations.js +45 -0
- package/dist/source-entries.d.ts +119 -0
- package/dist/source-entries.d.ts.map +1 -0
- package/dist/source-entries.js +349 -0
- package/dist/step-bodies.d.ts +24 -0
- package/dist/step-bodies.d.ts.map +1 -0
- package/dist/step-bodies.js +28 -0
- package/dist/telo-version.d.ts +1 -1
- package/dist/telo-version.js +1 -1
- package/dist/template-body.d.ts +10 -0
- package/dist/template-body.d.ts.map +1 -1
- package/dist/template-body.js +25 -0
- package/dist/validate-cel-context.d.ts +23 -8
- package/dist/validate-cel-context.d.ts.map +1 -1
- package/dist/validate-cel-context.js +51 -18
- package/dist/validate-exports.d.ts +28 -0
- package/dist/validate-exports.d.ts.map +1 -0
- package/dist/validate-exports.js +146 -0
- package/dist/validate-extends.d.ts.map +1 -1
- package/dist/validate-extends.js +37 -1
- package/dist/validate-module-artifact.d.ts +4 -2
- package/dist/validate-module-artifact.d.ts.map +1 -1
- package/dist/validate-module-artifact.js +43 -4
- package/dist/validate-native-entries.d.ts +15 -0
- package/dist/validate-native-entries.d.ts.map +1 -0
- package/dist/validate-native-entries.js +103 -0
- package/dist/validate-observed-state.d.ts +7 -1
- package/dist/validate-observed-state.d.ts.map +1 -1
- package/dist/validate-observed-state.js +24 -6
- package/dist/validate-provider-coherence.d.ts +4 -8
- package/dist/validate-provider-coherence.d.ts.map +1 -1
- package/dist/validate-provider-coherence.js +12 -123
- package/dist/validate-reference-forms.d.ts.map +1 -1
- package/dist/validate-reference-forms.js +2 -2
- package/dist/validate-references.d.ts.map +1 -1
- package/dist/validate-references.js +152 -65
- package/dist/validate-scope-reach.d.ts +31 -0
- package/dist/validate-scope-reach.d.ts.map +1 -0
- package/dist/validate-scope-reach.js +180 -0
- package/dist/validate-source-entries.d.ts +16 -0
- package/dist/validate-source-entries.d.ts.map +1 -0
- package/dist/validate-source-entries.js +90 -0
- package/dist/validate-template-body.d.ts +36 -0
- package/dist/validate-template-body.d.ts.map +1 -0
- package/dist/validate-template-body.js +246 -0
- package/dist/validate-throws-coverage.d.ts.map +1 -1
- package/dist/validate-throws-coverage.js +21 -39
- package/dist/value-type-keyword.d.ts +22 -5
- package/dist/value-type-keyword.d.ts.map +1 -1
- package/dist/value-type-keyword.js +16 -7
- package/package.json +4 -3
- package/src/analyzer.ts +97 -20
- package/src/artifact-axes.ts +24 -0
- package/src/artifact-layer-index.ts +9 -6
- package/src/artifact-selector.ts +110 -35
- package/src/builtins.ts +160 -113
- package/src/call-graph.ts +30 -0
- package/src/catch-scope.ts +1 -1
- package/src/cel-scope-query.ts +28 -9
- package/src/cel-scope.ts +1 -0
- package/src/eval-paths.ts +53 -6
- package/src/extends-resolution.ts +24 -0
- package/src/forwarded-declaration.ts +32 -0
- package/src/index.ts +37 -1
- package/src/kernel-globals.ts +28 -5
- package/src/migrations/CLAUDE.md +35 -0
- package/src/module-alias-scope.ts +17 -0
- package/src/module-file-claims.ts +63 -4
- package/src/module-named-files.ts +162 -0
- package/src/native-entries.ts +227 -0
- package/src/nearest-name.ts +47 -0
- package/src/normalize-inline-resources.ts +326 -70
- package/src/reference-field-map.ts +257 -52
- package/src/release/CLAUDE.md +32 -0
- package/src/release/destinations.ts +105 -0
- package/src/release/index.ts +33 -2
- package/src/release/ledger.ts +51 -21
- package/src/release/payload-digest.ts +2 -1
- package/src/release/release-plan.ts +16 -26
- package/src/release/workspace-config.ts +483 -47
- package/src/release/workspace-schema.ts +99 -0
- package/src/resolve-ref-sentinels.ts +7 -6
- package/src/resolve-throws-union.ts +87 -32
- package/src/resolve-zone-requirements.ts +2 -1
- package/src/rewrite-synthetic-origins.ts +107 -16
- package/src/schema-compat.ts +18 -4
- package/src/schema-error-report.ts +16 -4
- package/src/schema-walk.ts +162 -34
- package/src/scope-declarations.ts +98 -0
- package/src/source-entries.ts +502 -0
- package/src/step-bodies.ts +55 -0
- package/src/telo-version.ts +1 -1
- package/src/template-body.ts +25 -0
- package/src/validate-cel-context.ts +60 -18
- package/src/validate-exports.ts +185 -0
- package/src/validate-extends.ts +43 -0
- package/src/validate-module-artifact.ts +46 -3
- package/src/validate-native-entries.ts +147 -0
- package/src/validate-observed-state.ts +36 -7
- package/src/validate-provider-coherence.ts +17 -127
- package/src/validate-reference-forms.ts +2 -3
- package/src/validate-references.ts +151 -66
- package/src/validate-scope-reach.ts +213 -0
- package/src/validate-source-entries.ts +120 -0
- package/src/validate-template-body.ts +274 -0
- package/src/validate-throws-coverage.ts +24 -31
- package/src/value-type-keyword.ts +35 -9
- package/dist/validate-template-dispatch.d.ts +0 -27
- package/dist/validate-template-dispatch.d.ts.map +0 -1
- package/dist/validate-template-dispatch.js +0 -95
- package/src/validate-template-dispatch.ts +0 -99
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { isTaggedSentinel } from "@telorun/templating";
|
|
2
2
|
import { AMBIENT_CONTRACT_ERROR_CODES, isAmbientContractErrorCode, } from "@telorun/sdk";
|
|
3
3
|
import { scopeResolverForModule } from "./alias-resolver.js";
|
|
4
|
-
import { createResolveCtx, resolveScopeUnion, resolveThrowsUnion, } from "./resolve-throws-union.js";
|
|
4
|
+
import { createResolveCtx, handsFailureBack, resolveScopeUnion, resolveThrowsUnion, throwsUses, } from "./resolve-throws-union.js";
|
|
5
|
+
import { forEachDeclaredSlot } from "./schema-walk.js";
|
|
5
6
|
import { buildEnclosers, collectScopedManifests, enclosingCoverage, } from "./catch-scope.js";
|
|
6
7
|
import { DiagnosticSeverity } from "./types.js";
|
|
7
8
|
import { extractAccessChains, validateChainAgainstSchema } from "./validate-cel-context.js";
|
|
8
|
-
import { isStepSlot } from "./step-slot.js";
|
|
9
9
|
const SOURCE = "telo-analyzer";
|
|
10
10
|
const TEMPLATE_REGEX = /\$\{\{\s*([^}]+?)\s*\}\}/g;
|
|
11
11
|
/** Walk `definition.schema` and `data` in tandem, invoking `onOutcome` each
|
|
@@ -401,12 +401,6 @@ function checkCelChainAgainstDataSchema(entry, dataSchema, resource, filePath, e
|
|
|
401
401
|
}
|
|
402
402
|
return diagnostics;
|
|
403
403
|
}
|
|
404
|
-
/** Rule 8 extension: `inherit: true` only makes sense on a definition whose
|
|
405
|
-
* schema declares at least one STEP BODY — an array whose items point at the
|
|
406
|
-
* shared grammar, or, for a module published before that fragment existed, one
|
|
407
|
-
* carrying the legacy `x-telo-step-context` annotation. That is what drives the
|
|
408
|
-
* resolver's generic step traversal; a definition with `inherit: true` and no
|
|
409
|
-
* such array has no invocables to inherit from. */
|
|
410
404
|
/** The capabilities whose lifecycle includes a dispatch a caller can catch. On
|
|
411
405
|
* every other one a thrown error is a boot-time failure, not a structured
|
|
412
406
|
* runtime error for a downstream caller — a provider resolves configuration, a
|
|
@@ -466,10 +460,14 @@ function validateThrowsDeclarations(manifests) {
|
|
|
466
460
|
severity: DiagnosticSeverity.Error,
|
|
467
461
|
code: "INHERIT_WITHOUT_STEP_CONTEXT",
|
|
468
462
|
source: SOURCE,
|
|
469
|
-
message: `Telo.Definition '${name}' declares throws.inherit: true but its schema
|
|
470
|
-
`
|
|
471
|
-
`
|
|
472
|
-
`x-telo-step-context
|
|
463
|
+
message: `Telo.Definition '${name}' declares throws.inherit: true but its schema dispatches nothing ` +
|
|
464
|
+
`a failure can come back through. inherit makes the kind's union the union of what it ` +
|
|
465
|
+
`dispatches, so the schema needs a step body (an array field with ` +
|
|
466
|
+
`'items: { $ref: "telo://manifest#/$defs/Step" }', or the legacy x-telo-step-context ` +
|
|
467
|
+
`annotation) or a reference slot whose use includes 'call' or 'trigger.consumer' ` +
|
|
468
|
+
`(for a use case map, in any case; a slot declaring no use counts as 'call'). ` +
|
|
469
|
+
`'detached' and 'trigger.inbound' run where no caller awaits them, and ` +
|
|
470
|
+
`'dependency' / 'schema' dispatch nothing.`,
|
|
473
471
|
data: { resource: { kind: m.kind, name }, filePath, path: "throws.inherit" },
|
|
474
472
|
});
|
|
475
473
|
}
|
|
@@ -477,35 +475,19 @@ function validateThrowsDeclarations(manifests) {
|
|
|
477
475
|
}
|
|
478
476
|
return diagnostics;
|
|
479
477
|
}
|
|
478
|
+
/** Rule 8 extension: `inherit: true` needs something to inherit from — a step
|
|
479
|
+
* body, or a reference slot whose use (any case of a case map) hands a failure
|
|
480
|
+
* back. The schema-only mode of the walk the resolver runs per instance, so the
|
|
481
|
+
* check and the union reach the same slots. */
|
|
480
482
|
function schemaDrivesInvocables(schema) {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
if (props && typeof props === "object") {
|
|
487
|
-
for (const v of Object.values(props)) {
|
|
488
|
-
if (schemaDrivesInvocables(v))
|
|
489
|
-
return true;
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
if (schema.items && schemaDrivesInvocables(schema.items))
|
|
493
|
-
return true;
|
|
494
|
-
for (const key of ["oneOf", "anyOf", "allOf"]) {
|
|
495
|
-
const arr = schema[key];
|
|
496
|
-
if (Array.isArray(arr)) {
|
|
497
|
-
for (const sub of arr)
|
|
498
|
-
if (schemaDrivesInvocables(sub))
|
|
499
|
-
return true;
|
|
483
|
+
let drives = false;
|
|
484
|
+
forEachDeclaredSlot(schema, (declared) => {
|
|
485
|
+
if (declared.kind === "step" ||
|
|
486
|
+
declared.slots.some((slot) => throwsUses(slot).some(handsFailureBack))) {
|
|
487
|
+
drives = true;
|
|
500
488
|
}
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
for (const v of Object.values(schema.$defs)) {
|
|
504
|
-
if (schemaDrivesInvocables(v))
|
|
505
|
-
return true;
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
return false;
|
|
489
|
+
});
|
|
490
|
+
return drives;
|
|
509
491
|
}
|
|
510
492
|
/** Entry point — invoked once per analyze() run. */
|
|
511
493
|
export function validateThrowsCoverage(manifests, defs, aliases, env, aliasesByModule = new Map(), rootModules = new Set(),
|
|
@@ -29,6 +29,20 @@ import type { KeywordDefinition } from "ajv";
|
|
|
29
29
|
* `x-telo-type` is deliberately absent — it is the one that emits code.
|
|
30
30
|
*/
|
|
31
31
|
export declare const ANNOTATION_KEYWORDS: readonly ["x-telo-bindings-from", "x-telo-catches-for", "x-telo-context", "x-telo-context-collection-from", "x-telo-context-element-from", "x-telo-context-from", "x-telo-context-from-ref-kind", "x-telo-context-from-root", "x-telo-context-ref-from", "x-telo-error-context", "x-telo-eval", "x-telo-inline", "x-telo-outcome-list", "x-telo-provides-zone", "x-telo-ref", "x-telo-requires-zone", "x-telo-resource-rules", "x-telo-schema-from", "x-telo-schema-map", "x-telo-schema-projection", "x-telo-schema-projection-from", "x-telo-scope", "x-telo-sensitive", "x-telo-step-context", "x-telo-topology-role", "x-telo-value-schema-from", "x-telo-widget"];
|
|
32
|
+
/** How an AJV instance treats a `live` value type. */
|
|
33
|
+
export interface TeloKeywordOptions {
|
|
34
|
+
/**
|
|
35
|
+
* Assert a `live` type as an instance too, so a literal at such a slot is
|
|
36
|
+
* refused — a value written in a manifest can never be a live instance.
|
|
37
|
+
*
|
|
38
|
+
* STATIC analysis only, where a CEL leaf's stand-in is an instance
|
|
39
|
+
* (`celPlaceholderForSchema`). The kernel's instances leave it off: at
|
|
40
|
+
* dispatch a live value is exempt from validation, and the stage that pulls
|
|
41
|
+
* from it refuses a non-stream itself — so the static half refuses a strict
|
|
42
|
+
* subset of what the runtime refuses, and the two cannot disagree.
|
|
43
|
+
*/
|
|
44
|
+
readonly assertLive?: boolean;
|
|
45
|
+
}
|
|
32
46
|
/**
|
|
33
47
|
* The `x-telo-type` keyword.
|
|
34
48
|
*
|
|
@@ -37,8 +51,10 @@ export declare const ANNOTATION_KEYWORDS: readonly ["x-telo-bindings-from", "x-t
|
|
|
37
51
|
* - a `json` representation validates through its own declared schema, so the
|
|
38
52
|
* keyword emits nothing — the name carries nominal identity for static wiring
|
|
39
53
|
* and has no runtime existence at all;
|
|
40
|
-
* - a `live` instance is EXEMPT: its value is never traversed,
|
|
41
|
-
* a stream to check it is precisely what the exemption is
|
|
54
|
+
* - a `live` instance is EXEMPT at dispatch: its value is never traversed,
|
|
55
|
+
* because iterating a stream to check it is precisely what the exemption is
|
|
56
|
+
* for. Statically ({@link TeloKeywordOptions.assertLive}) it is asserted like
|
|
57
|
+
* any other instance, which checks what the value IS and never iterates it;
|
|
42
58
|
* - every other instance is ASSERTED against the constructor its binding names.
|
|
43
59
|
*
|
|
44
60
|
* An unknown name emits nothing here. It is a hard diagnostic in the analyzer
|
|
@@ -46,15 +62,16 @@ export declare const ANNOTATION_KEYWORDS: readonly ["x-telo-bindings-from", "x-t
|
|
|
46
62
|
* manifest that wrote it; failing compilation instead would take out every
|
|
47
63
|
* validator in a module for one typo in one slot.
|
|
48
64
|
*/
|
|
49
|
-
export declare function valueTypeKeyword(): KeywordDefinition;
|
|
65
|
+
export declare function valueTypeKeyword(options?: TeloKeywordOptions): KeywordDefinition;
|
|
50
66
|
/**
|
|
51
67
|
* Register every Telo keyword on an AJV instance: the annotations as no-ops and
|
|
52
68
|
* `x-telo-type` as the one that checks.
|
|
53
69
|
*
|
|
54
70
|
* Every AJV instance in the runtime and the analyzer goes through this, so a
|
|
55
|
-
* schema means the same thing wherever it is validated
|
|
71
|
+
* schema means the same thing wherever it is validated — apart from the one
|
|
72
|
+
* posture {@link TeloKeywordOptions} names, which only static analysis takes.
|
|
56
73
|
*/
|
|
57
74
|
export declare function registerTeloKeywords(ajv: {
|
|
58
75
|
addKeyword: (keyword: any, definition?: any) => unknown;
|
|
59
|
-
}): void;
|
|
76
|
+
}, options?: TeloKeywordOptions): void;
|
|
60
77
|
//# sourceMappingURL=value-type-keyword.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"value-type-keyword.d.ts","sourceRoot":"","sources":["../src/value-type-keyword.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;+EAqB+E;AAG/E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,KAAK,CAAC;AAiB7C;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,woBA4BtB,CAAC;AAEX
|
|
1
|
+
{"version":3,"file":"value-type-keyword.d.ts","sourceRoot":"","sources":["../src/value-type-keyword.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;+EAqB+E;AAG/E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,KAAK,CAAC;AAiB7C;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,woBA4BtB,CAAC;AAEX,sDAAsD;AACtD,MAAM,WAAW,kBAAkB;IACjC;;;;;;;;;OASG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,kBAAuB,GAAG,iBAAiB,CAsCpF;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE;IAAE,UAAU,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,UAAU,CAAC,EAAE,GAAG,KAAK,OAAO,CAAA;CAAE,EAChE,OAAO,GAAE,kBAAuB,GAC/B,IAAI,CAGN"}
|
|
@@ -68,8 +68,10 @@ export const ANNOTATION_KEYWORDS = [
|
|
|
68
68
|
* - a `json` representation validates through its own declared schema, so the
|
|
69
69
|
* keyword emits nothing — the name carries nominal identity for static wiring
|
|
70
70
|
* and has no runtime existence at all;
|
|
71
|
-
* - a `live` instance is EXEMPT: its value is never traversed,
|
|
72
|
-
* a stream to check it is precisely what the exemption is
|
|
71
|
+
* - a `live` instance is EXEMPT at dispatch: its value is never traversed,
|
|
72
|
+
* because iterating a stream to check it is precisely what the exemption is
|
|
73
|
+
* for. Statically ({@link TeloKeywordOptions.assertLive}) it is asserted like
|
|
74
|
+
* any other instance, which checks what the value IS and never iterates it;
|
|
73
75
|
* - every other instance is ASSERTED against the constructor its binding names.
|
|
74
76
|
*
|
|
75
77
|
* An unknown name emits nothing here. It is a hard diagnostic in the analyzer
|
|
@@ -77,7 +79,7 @@ export const ANNOTATION_KEYWORDS = [
|
|
|
77
79
|
* manifest that wrote it; failing compilation instead would take out every
|
|
78
80
|
* validator in a module for one typo in one slot.
|
|
79
81
|
*/
|
|
80
|
-
export function valueTypeKeyword() {
|
|
82
|
+
export function valueTypeKeyword(options = {}) {
|
|
81
83
|
return {
|
|
82
84
|
keyword: X_TELO_TYPE,
|
|
83
85
|
// Both spellings: a bare name, or the object form carrying type arguments.
|
|
@@ -86,7 +88,9 @@ export function valueTypeKeyword() {
|
|
|
86
88
|
const entry = readValueTypeSlot({
|
|
87
89
|
[X_TELO_TYPE]: cxt.schema,
|
|
88
90
|
})?.entry;
|
|
89
|
-
if (!entry || entry.representation !== "instance"
|
|
91
|
+
if (!entry || entry.representation !== "instance")
|
|
92
|
+
return;
|
|
93
|
+
if (entry.live && !options.assertLive)
|
|
90
94
|
return;
|
|
91
95
|
const binding = VALUE_TYPE_BINDINGS[entry.binding];
|
|
92
96
|
if (!binding)
|
|
@@ -104,6 +108,10 @@ export function valueTypeKeyword() {
|
|
|
104
108
|
error: {
|
|
105
109
|
message: (cxt) => {
|
|
106
110
|
const entry = readValueTypeSlot({ [X_TELO_TYPE]: cxt.schema })?.entry;
|
|
111
|
+
if (entry?.live) {
|
|
112
|
+
return (`must be a live ${entry.name} — a value written in the manifest can never be one; ` +
|
|
113
|
+
"pass the result of a step that produces it, with a !cel expression");
|
|
114
|
+
}
|
|
107
115
|
return entry?.binding === "bytes"
|
|
108
116
|
? "must be raw bytes (a Uint8Array) — bytes cannot be written inline in a manifest"
|
|
109
117
|
: `must be a ${entry?.name ?? "declared value type"} — this value is not writable inline in a manifest`;
|
|
@@ -116,10 +124,11 @@ export function valueTypeKeyword() {
|
|
|
116
124
|
* `x-telo-type` as the one that checks.
|
|
117
125
|
*
|
|
118
126
|
* Every AJV instance in the runtime and the analyzer goes through this, so a
|
|
119
|
-
* schema means the same thing wherever it is validated
|
|
127
|
+
* schema means the same thing wherever it is validated — apart from the one
|
|
128
|
+
* posture {@link TeloKeywordOptions} names, which only static analysis takes.
|
|
120
129
|
*/
|
|
121
|
-
export function registerTeloKeywords(ajv) {
|
|
130
|
+
export function registerTeloKeywords(ajv, options = {}) {
|
|
122
131
|
for (const keyword of ANNOTATION_KEYWORDS)
|
|
123
132
|
ajv.addKeyword(keyword);
|
|
124
|
-
ajv.addKeyword(valueTypeKeyword());
|
|
133
|
+
ajv.addKeyword(valueTypeKeyword(options));
|
|
125
134
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/analyzer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.74.0",
|
|
4
4
|
"description": "Telo Analyzer - Static manifest validator for Telo manifests.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"telo",
|
|
@@ -43,13 +43,14 @@
|
|
|
43
43
|
"jsonpath-plus": "^10.3.0",
|
|
44
44
|
"packageurl-js": "^2.0.1",
|
|
45
45
|
"yaml": "^2.8.3",
|
|
46
|
-
"@telorun/templating": "0.
|
|
46
|
+
"@telorun/templating": "0.20.1"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/node": "^20.0.0",
|
|
50
50
|
"typescript": "^5.0.0",
|
|
51
51
|
"vitest": "^2.1.8",
|
|
52
|
-
"@telorun/
|
|
52
|
+
"@telorun/glob": "0.3.0",
|
|
53
|
+
"@telorun/sdk": "0.90.0"
|
|
53
54
|
},
|
|
54
55
|
"peerDependencies": {
|
|
55
56
|
"@telorun/sdk": "*"
|
package/src/analyzer.ts
CHANGED
|
@@ -25,7 +25,13 @@ import {
|
|
|
25
25
|
} from "./cel-environment.js";
|
|
26
26
|
import { DefinitionRegistry } from "./definition-registry.js";
|
|
27
27
|
import { readDeprecation } from "./deprecation.js";
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
type ContractDirection,
|
|
30
|
+
type DefResolver,
|
|
31
|
+
effectiveAuthorSchema,
|
|
32
|
+
inheritedCapability,
|
|
33
|
+
inheritedRequiredFields,
|
|
34
|
+
} from "./extends-resolution.js";
|
|
29
35
|
import {
|
|
30
36
|
analyzerContractScope,
|
|
31
37
|
type ContractScope,
|
|
@@ -43,6 +49,7 @@ import {
|
|
|
43
49
|
buildObservedStateIndex,
|
|
44
50
|
buildObservedStateResourcesSchema,
|
|
45
51
|
collectRunReachableNames,
|
|
52
|
+
forwardedResourceKey,
|
|
46
53
|
observedStateRead,
|
|
47
54
|
validateObservedStateDeclarations,
|
|
48
55
|
} from "./validate-observed-state.js";
|
|
@@ -142,6 +149,7 @@ import {
|
|
|
142
149
|
import {
|
|
143
150
|
celEvalModeAt,
|
|
144
151
|
celEvalSites,
|
|
152
|
+
implicitEvalSites,
|
|
145
153
|
mergeCelEvalSites,
|
|
146
154
|
NO_CEL_EVAL_SITES,
|
|
147
155
|
type CelEvalSites,
|
|
@@ -160,6 +168,8 @@ import { validateIdentifierNames } from "./validate-identifier-names.js";
|
|
|
160
168
|
import { validateExtends } from "./validate-extends.js";
|
|
161
169
|
import { validateLogging } from "./validate-logging.js";
|
|
162
170
|
import { validateModuleArtifact } from "./validate-module-artifact.js";
|
|
171
|
+
import { validateNativeEntries } from "./validate-native-entries.js";
|
|
172
|
+
import { validateSourceEntries } from "./validate-source-entries.js";
|
|
163
173
|
import { validateIncludePlacement } from "./validate-include-placement.js";
|
|
164
174
|
import { validateModuleMetadata } from "./validate-module-metadata.js";
|
|
165
175
|
import { validateRequires } from "./validate-requires.js";
|
|
@@ -172,8 +182,11 @@ import { kindSatisfies, validateReferences } from "./validate-references.js";
|
|
|
172
182
|
import { validateReferenceForms } from "./validate-reference-forms.js";
|
|
173
183
|
import { isInjectedDeclaration } from "./resource-input.js";
|
|
174
184
|
import { validateResourceInputs } from "./validate-resource-inputs.js";
|
|
175
|
-
import {
|
|
185
|
+
import { validateExports } from "./validate-exports.js";
|
|
186
|
+
import { validateTemplateBody } from "./validate-template-body.js";
|
|
176
187
|
import { validateUnusedDeclarations } from "./validate-unused-declarations.js";
|
|
188
|
+
import { validateScopedNameReach } from "./validate-scope-reach.js";
|
|
189
|
+
import { isForwardedDeclaration } from "./forwarded-declaration.js";
|
|
177
190
|
import { validateThrowsCoverage } from "./validate-throws-coverage.js";
|
|
178
191
|
import { readStepSlot } from "./step-slot.js";
|
|
179
192
|
|
|
@@ -398,9 +411,9 @@ function validateStepInvokeReferences(
|
|
|
398
411
|
if (typeof m === "string") loadedModules.add(m);
|
|
399
412
|
continue;
|
|
400
413
|
}
|
|
401
|
-
const meta = r.metadata as { name?: unknown; module?: unknown
|
|
414
|
+
const meta = r.metadata as { name?: unknown; module?: unknown };
|
|
402
415
|
if (typeof meta?.name !== "string" || REF_VALIDATION_SKIP_KINDS.has(r.kind)) continue;
|
|
403
|
-
if (
|
|
416
|
+
if (isForwardedDeclaration(r)) {
|
|
404
417
|
if (typeof meta.module === "string") loadedModules.add(meta.module);
|
|
405
418
|
continue;
|
|
406
419
|
}
|
|
@@ -468,11 +481,11 @@ function validateStepInvokeReferences(
|
|
|
468
481
|
};
|
|
469
482
|
|
|
470
483
|
for (const m of allManifests) {
|
|
471
|
-
const meta = m.metadata as { name?: unknown; source?: unknown
|
|
484
|
+
const meta = m.metadata as { name?: unknown; source?: unknown };
|
|
472
485
|
if (
|
|
473
486
|
typeof meta?.name !== "string" ||
|
|
474
487
|
REF_VALIDATION_SKIP_KINDS.has(m.kind) ||
|
|
475
|
-
|
|
488
|
+
isForwardedDeclaration(m)
|
|
476
489
|
)
|
|
477
490
|
continue;
|
|
478
491
|
const def = defs.resolve(aliases.resolveKind(m.kind) ?? m.kind);
|
|
@@ -1526,6 +1539,11 @@ export class StaticAnalyzer {
|
|
|
1526
1539
|
// would otherwise fail on a consumer's machine — or, for a mistyped platform
|
|
1527
1540
|
// axis, silently offer one platform's binary to every host.
|
|
1528
1541
|
diagnostics.push(...validateModuleArtifact(allManifests));
|
|
1542
|
+
// The module doc's `native:` block: each rule decides whether a platform's
|
|
1543
|
+
// file can ever match a host, or whether two layers would write one path.
|
|
1544
|
+
diagnostics.push(...validateNativeEntries(allManifests, rootModules));
|
|
1545
|
+
// The module doc's `sources:` block: where each staged file comes from.
|
|
1546
|
+
diagnostics.push(...validateSourceEntries(allManifests, rootModules));
|
|
1529
1547
|
// The descriptive `metadata:` surface. Nothing in the kernel branches on
|
|
1530
1548
|
// these fields, which is precisely why they need a check: a mistyped one
|
|
1531
1549
|
// has no runtime failure mode that would ever surface it.
|
|
@@ -1807,9 +1825,7 @@ export class StaticAnalyzer {
|
|
|
1807
1825
|
// `kind`/CEL are authored in that module's scope (e.g. `Self.X` → that module, not the
|
|
1808
1826
|
// consumer). Re-validating against the consumer's scope yields false UNDEFINED_KIND /
|
|
1809
1827
|
// scope-mismatch errors, so skip — they participate here only as resolution targets.
|
|
1810
|
-
if ((m
|
|
1811
|
-
continue;
|
|
1812
|
-
}
|
|
1828
|
+
if (isForwardedDeclaration(m)) continue;
|
|
1813
1829
|
|
|
1814
1830
|
// A kind-only stand-in for a `resources:` entry is a DECLARATION, not an
|
|
1815
1831
|
// instantiation: its kind is routinely an abstract and its configuration
|
|
@@ -2029,12 +2045,41 @@ export class StaticAnalyzer {
|
|
|
2029
2045
|
allManifests as Record<string, any>[],
|
|
2030
2046
|
);
|
|
2031
2047
|
const issues = [...ajvIssues, ...valueSchemaIssues];
|
|
2048
|
+
// WHY A FIELD THIS KIND EXISTS TO SUPPLY IS STILL REQUIRED OF ITS
|
|
2049
|
+
// CONSUMER. A child that `extends` and declares no `base:` is authored
|
|
2050
|
+
// against merge(parent, own), so the parent's `required` stays on the
|
|
2051
|
+
// CHILD's surface — and a kind written to wire that field internally
|
|
2052
|
+
// then demands it from the consumer anyway. The `base:` mapping is what
|
|
2053
|
+
// narrows (with it the surface is the child's own schema alone), and
|
|
2054
|
+
// nothing in "is missing required property" points there, which is what
|
|
2055
|
+
// turns one mistake into a three-step dead end.
|
|
2056
|
+
//
|
|
2057
|
+
// Matched against names DERIVED from the parent, never by parsing the
|
|
2058
|
+
// message: which fields are inherited is a fact about the definition,
|
|
2059
|
+
// and a check that reads a validator's prose breaks when the prose does.
|
|
2060
|
+
const inheritedRequired = inheritedRequiredFields(definition, (k) =>
|
|
2061
|
+
defs.resolve(aliases.resolveKind(k) ?? k) ?? defs.resolve(k),
|
|
2062
|
+
);
|
|
2063
|
+
const parentKind = (definition as { extends?: string } | undefined)?.extends;
|
|
2032
2064
|
for (const issue of issues) {
|
|
2065
|
+
// Keyed on the STRUCTURED failure, never on the sentence: the field
|
|
2066
|
+
// names are derived from the parent, and matching them against the
|
|
2067
|
+
// validator's prose would break when the prose changes and mis-fire on
|
|
2068
|
+
// any other issue quoting the same name (an `additionalProperties`
|
|
2069
|
+
// rejection, an `enum` listing).
|
|
2070
|
+
const inherited =
|
|
2071
|
+
issue.keyword === "required" && issue.missingProperty !== undefined
|
|
2072
|
+
? inheritedRequired.filter((f) => f === issue.missingProperty)
|
|
2073
|
+
: [];
|
|
2074
|
+
const hint =
|
|
2075
|
+
inherited.length > 0 && parentKind
|
|
2076
|
+
? ` — ${inherited.map((f) => `'${f}'`).join(", ")} ${inherited.length > 1 ? "come" : "comes"} from '${parentKind}', which this kind extends without a 'base:' mapping, so the parent's required fields stay on this kind's author surface. Add 'base:' to set them internally; that also narrows the surface to this kind's own schema.`
|
|
2077
|
+
: "";
|
|
2033
2078
|
diagnostics.push({
|
|
2034
2079
|
severity: DiagnosticSeverity.Error,
|
|
2035
2080
|
code: "SCHEMA_VIOLATION",
|
|
2036
2081
|
source: SOURCE,
|
|
2037
|
-
message: `${m.kind}/${resource.name}: ${issue.message}`,
|
|
2082
|
+
message: `${m.kind}/${resource.name}: ${issue.message}${hint}`,
|
|
2038
2083
|
data: { resource, filePath, path: issue.path },
|
|
2039
2084
|
});
|
|
2040
2085
|
}
|
|
@@ -2472,8 +2517,15 @@ export class StaticAnalyzer {
|
|
|
2472
2517
|
|
|
2473
2518
|
// The non-eval-field check only applies to runtime resource instances:
|
|
2474
2519
|
// structural / templating kinds (capability `Telo.Template`, or no
|
|
2475
|
-
// definition) carry CEL the kernel evaluates by other rules.
|
|
2476
|
-
|
|
2520
|
+
// definition) carry CEL the kernel evaluates by other rules. The
|
|
2521
|
+
// capability is INHERITED along `extends` — an inheritance kind
|
|
2522
|
+
// writes none of its own — and reading the declared one left the
|
|
2523
|
+
// rule off for every such kind, and its expressions untyped.
|
|
2524
|
+
const resolveDef: DefResolver = (k) =>
|
|
2525
|
+
defs.resolve(aliases.resolveKind(k) ?? k) ?? defs.resolve(k);
|
|
2526
|
+
const capability = e.definition
|
|
2527
|
+
? inheritedCapability(e.definition as unknown as ResourceDefinition, resolveDef)
|
|
2528
|
+
: undefined;
|
|
2477
2529
|
celRuleApplies =
|
|
2478
2530
|
!!e.definition?.schema && capability !== undefined && capability !== "Telo.Template";
|
|
2479
2531
|
if (celRuleApplies) {
|
|
@@ -2487,15 +2539,18 @@ export class StaticAnalyzer {
|
|
|
2487
2539
|
// disagreeing about what the manifest means.
|
|
2488
2540
|
const ownSchema = effectiveAuthorSchema(
|
|
2489
2541
|
e.definition as unknown as ResourceDefinition,
|
|
2490
|
-
|
|
2542
|
+
resolveDef,
|
|
2491
2543
|
) as Record<string, any>;
|
|
2492
2544
|
const capabilityDef = capability ? defs.resolve(capability) : undefined;
|
|
2493
2545
|
// A `Telo.Provider`'s fields are implicitly compile-eval — the
|
|
2494
2546
|
// capability abstract carries the root annotation — so its reads are
|
|
2495
|
-
// covered here without the provider restating anything.
|
|
2547
|
+
// covered here without the provider restating anything. A base-form
|
|
2548
|
+
// child's own fields are too (`implicitEvalSites`): `base:` reads
|
|
2549
|
+
// them once at create().
|
|
2496
2550
|
celSites = mergeCelEvalSites(
|
|
2497
2551
|
celEvalSites(ownSchema),
|
|
2498
2552
|
celEvalSites(capabilityDef?.schema as Record<string, any> | undefined),
|
|
2553
|
+
implicitEvalSites(e.definition as { base?: unknown }),
|
|
2499
2554
|
);
|
|
2500
2555
|
} else {
|
|
2501
2556
|
celSites = NO_CEL_EVAL_SITES;
|
|
@@ -2539,9 +2594,17 @@ export class StaticAnalyzer {
|
|
|
2539
2594
|
if (!read) continue;
|
|
2540
2595
|
// An import's exported instance is indexed under `<Alias>.<name>`,
|
|
2541
2596
|
// the two-level shape it publishes under, so a cross-module read
|
|
2542
|
-
// is checked exactly like a local one.
|
|
2597
|
+
// is checked exactly like a local one. A bare name read inside a
|
|
2598
|
+
// dependency's own manifest names that dependency's resource.
|
|
2599
|
+
const readerModule = isForwardedDeclaration(m)
|
|
2600
|
+
? (m.metadata?.module as string | undefined)
|
|
2601
|
+
: undefined;
|
|
2543
2602
|
const reported = observedState.get(
|
|
2544
|
-
read.alias
|
|
2603
|
+
read.alias
|
|
2604
|
+
? `${read.alias}.${read.name}`
|
|
2605
|
+
: readerModule !== undefined
|
|
2606
|
+
? forwardedResourceKey(readerModule, read.name)
|
|
2607
|
+
: read.name,
|
|
2545
2608
|
);
|
|
2546
2609
|
|
|
2547
2610
|
if (celRuleApplies && celEvalModeAt(celSites, path) === "compile") {
|
|
@@ -2801,10 +2864,16 @@ export class StaticAnalyzer {
|
|
|
2801
2864
|
// Warn about declared variables / secrets / ports that no CEL references.
|
|
2802
2865
|
diagnostics.push(...validateUnusedDeclarations(allManifests, this.celEnv));
|
|
2803
2866
|
|
|
2804
|
-
// A
|
|
2805
|
-
// entry —
|
|
2806
|
-
//
|
|
2807
|
-
diagnostics.push(
|
|
2867
|
+
// A template body's reference surface — its entry names, its dispatch slots
|
|
2868
|
+
// and the ref slots inside each entry — which no reference pass reaches,
|
|
2869
|
+
// since a `Telo.Definition` is in both skip sets.
|
|
2870
|
+
diagnostics.push(
|
|
2871
|
+
...validateTemplateBody(allManifests, defs, aliases, aliasesByModule, rootModules),
|
|
2872
|
+
);
|
|
2873
|
+
|
|
2874
|
+
// A library's export list, resolved against what it declares — otherwise a
|
|
2875
|
+
// listed name that exists nowhere fails in the consumer's file.
|
|
2876
|
+
diagnostics.push(...validateExports(allManifests, defs, aliases, rootModules));
|
|
2808
2877
|
|
|
2809
2878
|
// A library's declared resource inputs, and every import that supplies them.
|
|
2810
2879
|
diagnostics.push(
|
|
@@ -2818,6 +2887,14 @@ export class StaticAnalyzer {
|
|
|
2818
2887
|
),
|
|
2819
2888
|
);
|
|
2820
2889
|
|
|
2890
|
+
// An inline declaration referencing a name declared by a scope it was
|
|
2891
|
+
// written inside but is created outside of.
|
|
2892
|
+
diagnostics.push(
|
|
2893
|
+
...validateScopedNameReach(allManifests, defs, aliases, aliasesByModule, rootModules, (expr) =>
|
|
2894
|
+
celAccessChains(this.celEnv, expr),
|
|
2895
|
+
),
|
|
2896
|
+
);
|
|
2897
|
+
|
|
2821
2898
|
// Reroute diagnostics on synthetic (inline-extracted) resources back to
|
|
2822
2899
|
// the chain root so position-index lookups land on the parent doc.
|
|
2823
2900
|
return rewriteSyntheticOrigins(
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// GENERATED by scripts/generate-artifact-axes.mjs from analyzer/artifact-axes/axes.json — do not edit.
|
|
2
|
+
|
|
3
|
+
/** The selector platform axes, in canonical order. Closed as a set of axis
|
|
4
|
+
* names; the set of values stays open. */
|
|
5
|
+
export const PLATFORM_AXES = ["os", "arch", "libc", "abi"] as const;
|
|
6
|
+
|
|
7
|
+
export type PlatformAxis = (typeof PLATFORM_AXES)[number];
|
|
8
|
+
|
|
9
|
+
/** A form an axis value must take beyond the shared token grammar. */
|
|
10
|
+
export interface AxisValueForm {
|
|
11
|
+
readonly pattern: RegExp;
|
|
12
|
+
/** The form as a reader writes it, e.g. `<family>-<version>`. */
|
|
13
|
+
readonly form: string;
|
|
14
|
+
readonly examples: readonly string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** The axes whose values carry a form of their own. */
|
|
18
|
+
export const AXIS_VALUE_FORMS: Readonly<Partial<Record<PlatformAxis, AxisValueForm>>> = {
|
|
19
|
+
abi: {
|
|
20
|
+
pattern: new RegExp("^[a-z][a-z0-9_]*-[0-9]+(\\.[0-9]+)*$"),
|
|
21
|
+
form: "<family>-<version>",
|
|
22
|
+
examples: ["node-137", "telo-2"],
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
31
|
import {
|
|
32
|
-
LAYER_ROLES,
|
|
33
32
|
isLayerRole,
|
|
34
33
|
normalizeSelector,
|
|
35
34
|
roleCarriesSelector,
|
|
@@ -48,7 +47,7 @@ const CONTENT_DIGEST = /^sha256-[A-Za-z0-9_-]{43}$/;
|
|
|
48
47
|
|
|
49
48
|
export interface ArtifactLayer {
|
|
50
49
|
role: LayerRole;
|
|
51
|
-
/** Present on the
|
|
50
|
+
/** Present on the selector-keyed roles (`controller`, `library`, `native`) only. */
|
|
52
51
|
selector?: ArtifactSelector;
|
|
53
52
|
/** OCI blob digest — addresses the layer and verifies the transfer. */
|
|
54
53
|
blob: string;
|
|
@@ -101,10 +100,12 @@ export function parseLayerIndex(value: unknown, describe = "layers"): ArtifactLa
|
|
|
101
100
|
const entry = raw as Record<string, unknown>;
|
|
102
101
|
if (typeof entry.role !== "string" || entry.role === "") {
|
|
103
102
|
throw new LayerIndexError(
|
|
104
|
-
`${where}: role is required and must be
|
|
103
|
+
`${where}: role is required and must be a non-empty string; ` +
|
|
105
104
|
`got ${entry.role === undefined ? "nothing" : `'${String(entry.role)}'`}.`,
|
|
106
105
|
);
|
|
107
106
|
}
|
|
107
|
+
const blob = digest("blob", entry.blob, where);
|
|
108
|
+
const integrity = digest("integrity", entry.integrity, where);
|
|
108
109
|
// A role this runtime does not know is SKIPPED, never rejected. Roles are
|
|
109
110
|
// added over time, and a runtime that cannot name one cannot need it — while
|
|
110
111
|
// throwing would make the whole manifest unreadable, so a module gaining a
|
|
@@ -120,6 +121,8 @@ export function parseLayerIndex(value: unknown, describe = "layers"): ArtifactLa
|
|
|
120
121
|
throw new LayerIndexError(`${where}: a ${role} layer must declare a selector.`);
|
|
121
122
|
}
|
|
122
123
|
selector = normalizeSelector(entry.selector, where);
|
|
124
|
+
// An unknown axis skips the entry as an unknown role does (spec §3.1).
|
|
125
|
+
if (selector === undefined) return;
|
|
123
126
|
// Scoped by role: a module's `js` controller layer and its `js` library
|
|
124
127
|
// layer are different layers with the same selector, and only a collision
|
|
125
128
|
// *within* one role means two layers claim one address.
|
|
@@ -146,8 +149,8 @@ export function parseLayerIndex(value: unknown, describe = "layers"): ArtifactLa
|
|
|
146
149
|
layers.push({
|
|
147
150
|
role,
|
|
148
151
|
...(selector ? { selector } : {}),
|
|
149
|
-
blob
|
|
150
|
-
integrity
|
|
152
|
+
blob,
|
|
153
|
+
integrity,
|
|
151
154
|
});
|
|
152
155
|
});
|
|
153
156
|
|
|
@@ -157,7 +160,7 @@ export function parseLayerIndex(value: unknown, describe = "layers"): ArtifactLa
|
|
|
157
160
|
/** The singleton layer for a role, or undefined when the artifact has none. */
|
|
158
161
|
export function singletonLayer(
|
|
159
162
|
layers: readonly ArtifactLayer[],
|
|
160
|
-
role: Exclude<LayerRole, "controller" | "library">,
|
|
163
|
+
role: Exclude<LayerRole, "controller" | "library" | "native">,
|
|
161
164
|
): ArtifactLayer | undefined {
|
|
162
165
|
return layers.find((l) => l.role === role);
|
|
163
166
|
}
|