@telorun/analyzer 0.73.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.
Files changed (138) hide show
  1. package/dist/analyzer.d.ts.map +1 -1
  2. package/dist/analyzer.js +26 -7
  3. package/dist/artifact-axes.d.ts +14 -0
  4. package/dist/artifact-axes.d.ts.map +1 -0
  5. package/dist/artifact-axes.js +12 -0
  6. package/dist/artifact-layer-index.d.ts +2 -2
  7. package/dist/artifact-layer-index.d.ts.map +1 -1
  8. package/dist/artifact-layer-index.js +9 -4
  9. package/dist/artifact-selector.d.ts +47 -21
  10. package/dist/artifact-selector.d.ts.map +1 -1
  11. package/dist/artifact-selector.js +85 -16
  12. package/dist/builtins.d.ts.map +1 -1
  13. package/dist/builtins.js +73 -16
  14. package/dist/call-graph.d.ts +12 -1
  15. package/dist/call-graph.d.ts.map +1 -1
  16. package/dist/call-graph.js +22 -0
  17. package/dist/catch-scope.js +1 -1
  18. package/dist/forwarded-declaration.d.ts +26 -0
  19. package/dist/forwarded-declaration.d.ts.map +1 -0
  20. package/dist/forwarded-declaration.js +31 -0
  21. package/dist/index.d.ts +9 -3
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +4 -1
  24. package/dist/kernel-globals.d.ts.map +1 -1
  25. package/dist/kernel-globals.js +21 -5
  26. package/dist/module-alias-scope.d.ts +11 -0
  27. package/dist/module-alias-scope.d.ts.map +1 -1
  28. package/dist/module-alias-scope.js +10 -0
  29. package/dist/module-file-claims.d.ts +17 -1
  30. package/dist/module-file-claims.d.ts.map +1 -1
  31. package/dist/module-file-claims.js +38 -3
  32. package/dist/module-named-files.d.ts +72 -0
  33. package/dist/module-named-files.d.ts.map +1 -0
  34. package/dist/module-named-files.js +115 -0
  35. package/dist/native-entries.d.ts +83 -0
  36. package/dist/native-entries.d.ts.map +1 -0
  37. package/dist/native-entries.js +169 -0
  38. package/dist/normalize-inline-resources.d.ts +41 -13
  39. package/dist/normalize-inline-resources.d.ts.map +1 -1
  40. package/dist/normalize-inline-resources.js +236 -54
  41. package/dist/reference-field-map.d.ts +59 -1
  42. package/dist/reference-field-map.d.ts.map +1 -1
  43. package/dist/reference-field-map.js +200 -34
  44. package/dist/release/payload-digest.d.ts +2 -1
  45. package/dist/release/payload-digest.d.ts.map +1 -1
  46. package/dist/release/payload-digest.js +2 -1
  47. package/dist/resolve-ref-sentinels.d.ts.map +1 -1
  48. package/dist/resolve-ref-sentinels.js +8 -5
  49. package/dist/resolve-throws-union.d.ts +25 -4
  50. package/dist/resolve-throws-union.d.ts.map +1 -1
  51. package/dist/resolve-throws-union.js +62 -32
  52. package/dist/resolve-zone-requirements.d.ts.map +1 -1
  53. package/dist/resolve-zone-requirements.js +2 -1
  54. package/dist/rewrite-synthetic-origins.d.ts +6 -1
  55. package/dist/rewrite-synthetic-origins.d.ts.map +1 -1
  56. package/dist/rewrite-synthetic-origins.js +107 -13
  57. package/dist/schema-compat.d.ts.map +1 -1
  58. package/dist/schema-compat.js +18 -5
  59. package/dist/schema-walk.d.ts +47 -13
  60. package/dist/schema-walk.d.ts.map +1 -1
  61. package/dist/schema-walk.js +150 -30
  62. package/dist/scope-declarations.d.ts +59 -0
  63. package/dist/scope-declarations.d.ts.map +1 -0
  64. package/dist/scope-declarations.js +45 -0
  65. package/dist/source-entries.d.ts +119 -0
  66. package/dist/source-entries.d.ts.map +1 -0
  67. package/dist/source-entries.js +349 -0
  68. package/dist/step-bodies.d.ts +24 -0
  69. package/dist/step-bodies.d.ts.map +1 -0
  70. package/dist/step-bodies.js +28 -0
  71. package/dist/telo-version.d.ts +1 -1
  72. package/dist/telo-version.js +1 -1
  73. package/dist/validate-cel-context.d.ts.map +1 -1
  74. package/dist/validate-cel-context.js +3 -2
  75. package/dist/validate-module-artifact.d.ts +4 -2
  76. package/dist/validate-module-artifact.d.ts.map +1 -1
  77. package/dist/validate-module-artifact.js +43 -4
  78. package/dist/validate-native-entries.d.ts +15 -0
  79. package/dist/validate-native-entries.d.ts.map +1 -0
  80. package/dist/validate-native-entries.js +103 -0
  81. package/dist/validate-observed-state.d.ts +7 -1
  82. package/dist/validate-observed-state.d.ts.map +1 -1
  83. package/dist/validate-observed-state.js +24 -6
  84. package/dist/validate-reference-forms.d.ts.map +1 -1
  85. package/dist/validate-reference-forms.js +2 -2
  86. package/dist/validate-references.d.ts.map +1 -1
  87. package/dist/validate-references.js +11 -5
  88. package/dist/validate-scope-reach.d.ts +31 -0
  89. package/dist/validate-scope-reach.d.ts.map +1 -0
  90. package/dist/validate-scope-reach.js +180 -0
  91. package/dist/validate-source-entries.d.ts +16 -0
  92. package/dist/validate-source-entries.d.ts.map +1 -0
  93. package/dist/validate-source-entries.js +90 -0
  94. package/dist/validate-throws-coverage.d.ts.map +1 -1
  95. package/dist/validate-throws-coverage.js +21 -39
  96. package/dist/value-type-keyword.d.ts +22 -5
  97. package/dist/value-type-keyword.d.ts.map +1 -1
  98. package/dist/value-type-keyword.js +16 -7
  99. package/package.json +3 -3
  100. package/src/analyzer.ts +33 -9
  101. package/src/artifact-axes.ts +24 -0
  102. package/src/artifact-layer-index.ts +9 -6
  103. package/src/artifact-selector.ts +110 -35
  104. package/src/builtins.ts +75 -16
  105. package/src/call-graph.ts +30 -0
  106. package/src/catch-scope.ts +1 -1
  107. package/src/forwarded-declaration.ts +32 -0
  108. package/src/index.ts +36 -1
  109. package/src/kernel-globals.ts +28 -5
  110. package/src/migrations/CLAUDE.md +35 -0
  111. package/src/module-alias-scope.ts +17 -0
  112. package/src/module-file-claims.ts +63 -4
  113. package/src/module-named-files.ts +162 -0
  114. package/src/native-entries.ts +227 -0
  115. package/src/normalize-inline-resources.ts +326 -70
  116. package/src/reference-field-map.ts +257 -52
  117. package/src/release/CLAUDE.md +32 -0
  118. package/src/release/payload-digest.ts +2 -1
  119. package/src/resolve-ref-sentinels.ts +7 -6
  120. package/src/resolve-throws-union.ts +87 -32
  121. package/src/resolve-zone-requirements.ts +2 -1
  122. package/src/rewrite-synthetic-origins.ts +107 -16
  123. package/src/schema-compat.ts +18 -4
  124. package/src/schema-walk.ts +162 -34
  125. package/src/scope-declarations.ts +98 -0
  126. package/src/source-entries.ts +502 -0
  127. package/src/step-bodies.ts +55 -0
  128. package/src/telo-version.ts +1 -1
  129. package/src/validate-cel-context.ts +3 -2
  130. package/src/validate-module-artifact.ts +46 -3
  131. package/src/validate-native-entries.ts +147 -0
  132. package/src/validate-observed-state.ts +36 -7
  133. package/src/validate-reference-forms.ts +2 -3
  134. package/src/validate-references.ts +10 -6
  135. package/src/validate-scope-reach.ts +213 -0
  136. package/src/validate-source-entries.ts +120 -0
  137. package/src/validate-throws-coverage.ts +24 -31
  138. package/src/value-type-keyword.ts +35 -9
@@ -1,5 +1,5 @@
1
1
  import { parseLayerIndex, LayerIndexError } from "./artifact-layer-index.js";
2
- import { ArtifactSelectorError, PLATFORM_AXES, selectorFromQualifiers, selectorKey, } from "./artifact-selector.js";
2
+ import { ArtifactSelectorError, PLATFORM_AXES, selectorContradictions, selectorFromQualifiers, selectorKey, } from "./artifact-selector.js";
3
3
  import { readLibraryCandidates } from "./module-library.js";
4
4
  import { DiagnosticSeverity } from "./types.js";
5
5
  const SOURCE = "telo-analyzer";
@@ -17,10 +17,12 @@ const SOURCE = "telo-analyzer";
17
17
  * selector (spec §1), which is what every module with two `js` controllers relies
18
18
  * on.
19
19
  *
20
- * 1. **Controller selector qualifiers.** `os` / `arch` / `libc` / `siblings` are
20
+ * 1. **Controller selector qualifiers.** The platform axes and `siblings` are
21
21
  * authored surface. An unknown qualifier is reported rather than ignored, since
22
22
  * ignoring is what makes a typo invisible; an invalid value is reported here
23
- * instead of throwing from the loader later.
23
+ * instead of throwing from the loader later, and so is a combination no host
24
+ * can match (`selectorContradictions`, shared with `native:` and
25
+ * `exports.code:`).
24
26
  * 2. **The published layer index.** The owner doc's JSON Schema covers shape; the
25
27
  * semantic rules — controller-requires-selector, singletons carry none, no
26
28
  * duplicate selector, the token grammar (`os: Linux` passes the schema and
@@ -68,6 +70,15 @@ function validateLibraryCandidates(manifest, out) {
68
70
  // mean a consumer's import resolves by whichever candidate is read first.
69
71
  const seen = new Map();
70
72
  for (const candidate of candidates) {
73
+ for (const contradiction of selectorContradictions(candidate.selector)) {
74
+ out.push({
75
+ severity: DiagnosticSeverity.Error,
76
+ code: `LIBRARY_${contradiction.rule}`,
77
+ source: SOURCE,
78
+ message: `Telo.Library/${metadata?.name ?? "(unnamed)"}: ${candidate.origin}: ${contradiction.detail}`,
79
+ data: { resource, filePath: metadata?.source, path: "exports/code" },
80
+ });
81
+ }
71
82
  const key = selectorKey(candidate.selector);
72
83
  const first = seen.get(key);
73
84
  if (first) {
@@ -143,7 +154,35 @@ function validateControllerSelectors(manifest, out) {
143
154
  // Validate the selector; the value is not otherwise needed here, since
144
155
  // candidates sharing a selector legitimately share a layer.
145
156
  try {
146
- selectorFromQualifiers(parsed.format, parsed.qualifiers, candidate);
157
+ const selector = selectorFromQualifiers(parsed.format, parsed.qualifiers, candidate);
158
+ for (const contradiction of selectorContradictions(selector)) {
159
+ out.push({
160
+ severity: DiagnosticSeverity.Error,
161
+ code: `CONTROLLER_${contradiction.rule}`,
162
+ source: SOURCE,
163
+ message: `${manifest.kind}/${name ?? "(unnamed)"}: controller candidate "${candidate}": ` +
164
+ contradiction.detail,
165
+ data: { resource, filePath, path: `${at}?${contradiction.axis}` },
166
+ });
167
+ }
168
+ // A `dylib` opens over the Rust controller ABI, and a library built
169
+ // against another version of it must never match — so the candidate states
170
+ // which, in the `telo` family.
171
+ if (parsed.format === "dylib" && !selector.abi?.startsWith("telo-")) {
172
+ out.push({
173
+ severity: DiagnosticSeverity.Error,
174
+ code: "CONTROLLER_DYLIB_ABI_MISSING",
175
+ source: SOURCE,
176
+ message: `${manifest.kind}/${name ?? "(unnamed)"}: dylib candidate "${candidate}" ` +
177
+ (selector.abi === undefined
178
+ ? `states no abi. `
179
+ : `states abi=${selector.abi}, which is not a Rust controller ABI. `) +
180
+ `A dylib is built against one version of the Rust controller ABI and must say which — ` +
181
+ `add abi=telo-<version>, e.g. abi=telo-2. Without it the Rust kernel downloads the ` +
182
+ `library on every host and refuses it only when it opens it.`,
183
+ data: { resource, filePath, path: at },
184
+ });
185
+ }
147
186
  }
148
187
  catch (err) {
149
188
  if (!(err instanceof ArtifactSelectorError))
@@ -0,0 +1,15 @@
1
+ import type { ResourceManifest } from "@telorun/sdk";
2
+ import { type AnalysisDiagnostic } from "./types.js";
3
+ /**
4
+ * The strict half of `native-entries.ts`: what `telo check` reports about a
5
+ * module doc's `native:` block.
6
+ *
7
+ * Every rule is decidable from the manifest and would otherwise surface only on
8
+ * a consumer's host, as a native file that never matches or a layer that
9
+ * overwrites another's file. Shape errors (a missing key, an unknown one) are
10
+ * left to the owner doc's JSON Schema, which reports them in the same pass.
11
+ *
12
+ * Entry-module-scoped: a dependency's block is not the consumer's to fix.
13
+ */
14
+ export declare function validateNativeEntries(manifests: ResourceManifest[], entryModules?: ReadonlySet<string>): AnalysisDiagnostic[];
15
+ //# sourceMappingURL=validate-native-entries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-native-entries.d.ts","sourceRoot":"","sources":["../src/validate-native-entries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAMrD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIzE;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,YAAY,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GACjC,kBAAkB,EAAE,CA0HtB"}
@@ -0,0 +1,103 @@
1
+ import { describeSelector, selectorContradictions, selectorKey } from "./artifact-selector.js";
2
+ import { moduleDocumentClaims } from "./module-file-claims.js";
3
+ import { describeClaim, nativeClaimConflicts } from "./module-named-files.js";
4
+ import { readNativeEntries } from "./native-entries.js";
5
+ import { DiagnosticSeverity } from "./types.js";
6
+ const SOURCE = "telo-analyzer";
7
+ /**
8
+ * The strict half of `native-entries.ts`: what `telo check` reports about a
9
+ * module doc's `native:` block.
10
+ *
11
+ * Every rule is decidable from the manifest and would otherwise surface only on
12
+ * a consumer's host, as a native file that never matches or a layer that
13
+ * overwrites another's file. Shape errors (a missing key, an unknown one) are
14
+ * left to the owner doc's JSON Schema, which reports them in the same pass.
15
+ *
16
+ * Entry-module-scoped: a dependency's block is not the consumer's to fix.
17
+ */
18
+ export function validateNativeEntries(manifests, entryModules) {
19
+ const out = [];
20
+ for (const manifest of manifests) {
21
+ if (manifest.kind !== "Telo.Application" && manifest.kind !== "Telo.Library")
22
+ continue;
23
+ const metadata = (manifest.metadata ?? {});
24
+ const name = typeof metadata.name === "string" ? metadata.name : undefined;
25
+ const owned = entryModules === undefined ||
26
+ (typeof metadata.module === "string"
27
+ ? entryModules.has(metadata.module)
28
+ : name === undefined || entryModules.has(name));
29
+ if (!owned)
30
+ continue;
31
+ const label = `${manifest.kind}/${name ?? "(unnamed)"}`;
32
+ const report = (code, path, message) => out.push({
33
+ severity: DiagnosticSeverity.Error,
34
+ code,
35
+ source: SOURCE,
36
+ message: `${label}: ${message}`,
37
+ data: {
38
+ resource: { kind: manifest.kind, name },
39
+ filePath: typeof metadata.source === "string" ? metadata.source : undefined,
40
+ path,
41
+ },
42
+ });
43
+ const { entries, problems } = readNativeEntries(manifest);
44
+ for (const problem of problems) {
45
+ if (problem.kind === "shape")
46
+ continue;
47
+ report(problem.kind === "escape" ? "NATIVE_PATH_ESCAPES_MODULE" : "NATIVE_ENTRY_INVALID", problem.path, problem.message);
48
+ }
49
+ if (entries.length === 0)
50
+ continue;
51
+ const moduleName = typeof metadata.module === "string" ? metadata.module : name;
52
+ for (const { entry, claim } of nativeClaimConflicts(entries, moduleDocumentClaims(manifests, moduleName))) {
53
+ report("NATIVE_PATH_CLAIMED", `native[${entry.index}].path`, `${entry.origin}: path '${entry.path}' is also named by ${describeClaim(claim)}. A native ` +
54
+ `file ships only in its platform's native layer and a file extracts from exactly one ` +
55
+ `layer, so publish refuses it — give the native entry its own file, or drop the other ` +
56
+ `declaration.`);
57
+ }
58
+ const bySelector = new Map();
59
+ const byPath = new Map();
60
+ for (const entry of entries) {
61
+ const at = `native[${entry.index}]`;
62
+ if (entry.selector.format === "node" && entry.selector.abi === undefined) {
63
+ report("NATIVE_NODE_ABI_MISSING", at, `${entry.origin}: a 'node' addon is built against one Node ABI and must state it — ` +
64
+ `add abi: node-<NODE_MODULE_VERSION>, e.g. 'node-137'. Without it the file matches ` +
65
+ `every Node release and fails to load on all but one.`);
66
+ }
67
+ for (const contradiction of selectorContradictions(entry.selector)) {
68
+ report(`NATIVE_${contradiction.rule}`, `${at}.${contradiction.axis}`, `${entry.origin}: ${contradiction.detail}`);
69
+ }
70
+ const key = `${entry.name}\0${selectorKey(entry.selector)}`;
71
+ const twin = bySelector.get(key);
72
+ if (twin) {
73
+ report("NATIVE_ENTRY_DUPLICATE", at, `${entry.origin}: ${twin.origin} already declares '${entry.name}' for ` +
74
+ `${describeSelector(entry.selector)}. A name has one entry per platform tuple, or ` +
75
+ `a lookup by name could resolve to either file.`);
76
+ }
77
+ else {
78
+ bySelector.set(key, entry);
79
+ }
80
+ const sharer = byPath.get(entry.path);
81
+ if (sharer && selectorKey(sharer.selector) !== selectorKey(entry.selector)) {
82
+ report("NATIVE_PATH_SHARED", `${at}.path`, `${entry.origin}: path '${entry.path}' is also declared by ${sharer.origin} for ` +
83
+ `${describeSelector(sharer.selector)}. Every layer of a module extracts into one ` +
84
+ `directory, so each platform tuple needs its own path — e.g. ` +
85
+ `'native/<os>-<arch>/…'.`);
86
+ }
87
+ else if (!sharer) {
88
+ byPath.set(entry.path, entry);
89
+ }
90
+ const through = [...byPath.values()].find((other) => other !== entry &&
91
+ (entry.path.startsWith(`${other.path}/`) || other.path.startsWith(`${entry.path}/`)));
92
+ if (through) {
93
+ const relation = entry.path.startsWith(`${through.path}/`)
94
+ ? `runs through '${through.path}' as a directory`
95
+ : `is a directory that '${through.path}' runs through`;
96
+ report("NATIVE_PATH_NESTED", `${at}.path`, `${entry.origin}: path '${entry.path}' ${relation}, and ${through.origin} declares ` +
97
+ `'${through.path}'. Every layer of a module extracts into one directory, so a path ` +
98
+ `cannot be both a file and a directory — give each entry its own path.`);
99
+ }
100
+ }
101
+ }
102
+ return out;
103
+ }
@@ -70,12 +70,18 @@ export declare function observedStateRead(chain: readonly string[]): ObservedSta
70
70
  export declare function collectRunReachableNames(graph: CallGraph): Set<string>;
71
71
  /** What a resource name resolves to for CEL purposes. `status` is present only
72
72
  * when the kind declares one; `scoped` marks a resource declared inside an
73
- * `x-telo-scope` slot, which resolves only within that scope's regions. */
73
+ * `x-telo-scope` slot, which resolves only within that scope's regions.
74
+ * `forwardedFrom` marks a dependency's declaration, keyed by
75
+ * `forwardedResourceKey`: it is in no consumer's `resources`, but a read inside
76
+ * that dependency's own manifests still resolves to it. */
74
77
  export interface AnalyzedResource {
75
78
  kind: string;
76
79
  status?: Record<string, any>;
77
80
  scoped?: boolean;
81
+ forwardedFrom?: string;
78
82
  }
83
+ /** The index key of a name a dependency declares, as read from inside it. */
84
+ export declare function forwardedResourceKey(module: string, name: string): string;
79
85
  /**
80
86
  * Index every resource a CEL `resources.…` read can name: the module's own
81
87
  * top-level resources, the ones declared inside `x-telo-scope` slots (a
@@ -1 +1 @@
1
- {"version":3,"file":"validate-observed-state.d.ts","sourceRoot":"","sources":["../src/validate-observed-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAIzE,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAgBjD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,qBAAqB;;;CAGjC,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,iCAAiC,CAC/C,SAAS,EAAE,SAAS,gBAAgB,EAAE,GACrC,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAmB3E;AAED,6DAA6D;AAC7D,MAAM,WAAW,iBAAiB;IAChC;+CAC2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,iBAAiB,GAAG,SAAS,CAOzF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,CAQtE;AAGD;;4EAE4E;AAC5E,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,SAAS,gBAAgB,EAAE,EACtC,IAAI,EAAE;IAAE,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAAA;CAAE,EAC/D,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAAC,cAAc,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;CAAE,EAC/G,MAAM,CAAC,EAAE,YAAY,GACpB,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAsC/B;AAyDD;;;gDAGgD;AAChD,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAC,EAC5C,IAAI,EAAE,OAAO,GACZ,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CASrB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC/B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC1B,IAAI,CAmBN"}
1
+ {"version":3,"file":"validate-observed-state.d.ts","sourceRoot":"","sources":["../src/validate-observed-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAKzE,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAgBjD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,qBAAqB;;;CAGjC,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,iCAAiC,CAC/C,SAAS,EAAE,SAAS,gBAAgB,EAAE,GACrC,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAmB3E;AAED,6DAA6D;AAC7D,MAAM,WAAW,iBAAiB;IAChC;+CAC2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,iBAAiB,GAAG,SAAS,CAOzF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,CAQtE;AAGD;;;;;4DAK4D;AAC5D,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,6EAA6E;AAC7E,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzE;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,SAAS,gBAAgB,EAAE,EACtC,IAAI,EAAE;IAAE,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAAA;CAAE,EAC/D,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAAC,cAAc,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;CAAE,EAC/G,MAAM,CAAC,EAAE,YAAY,GACpB,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAyD/B;AAyDD;;;gDAGgD;AAChD,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAC,EAC5C,IAAI,EAAE,OAAO,GACZ,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CASrB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC/B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC1B,IAAI,CAmBN"}
@@ -1,5 +1,6 @@
1
1
  import { OBSERVED_STATE_KEY } from "@telorun/sdk";
2
2
  import { effectiveStatusSchema } from "./extends-resolution.js";
3
+ import { isForwardedDeclaration } from "./forwarded-declaration.js";
3
4
  import { parseExportEntry } from "./flatten-for-analyzer.js";
4
5
  import { moduleScopedDefResolver, } from "./alias-resolver.js";
5
6
  import { buildReferenceFieldMap, isScopeEntry, resolveFieldValues, } from "./reference-field-map.js";
@@ -100,6 +101,10 @@ export function collectRunReachableNames(graph) {
100
101
  names.add(edge.toName);
101
102
  return names;
102
103
  }
104
+ /** The index key of a name a dependency declares, as read from inside it. */
105
+ export function forwardedResourceKey(module, name) {
106
+ return `${module}\0${name}`;
107
+ }
103
108
  /**
104
109
  * Index every resource a CEL `resources.…` read can name: the module's own
105
110
  * top-level resources, the ones declared inside `x-telo-scope` slots (a
@@ -115,17 +120,30 @@ export function buildObservedStateIndex(manifests, defs, aliases, scopes) {
115
120
  /** `module` is the resource's DECLARING module: an exported instance is
116
121
  * written with that library's aliases (`kind: Self.Listener`), which the
117
122
  * consumer's table cannot resolve. */
118
- const record = (kind, key, scoped, module) => {
123
+ const record = (kind, key, scoped, module, forwardedFrom) => {
119
124
  const status = effectiveStatusSchema(resolve.in(kind, module), resolve);
120
- out.set(key, { kind, ...(status ? { status } : {}), ...(scoped ? { scoped } : {}) });
125
+ out.set(key, {
126
+ kind,
127
+ ...(status ? { status } : {}),
128
+ ...(scoped ? { scoped } : {}),
129
+ ...(forwardedFrom !== undefined ? { forwardedFrom } : {}),
130
+ });
121
131
  };
122
132
  for (const manifest of manifests) {
123
133
  const kind = manifest.kind;
124
134
  const name = manifest.metadata?.name;
125
135
  if (!kind || SYSTEM_KINDS.has(kind))
126
136
  continue;
137
+ const module = manifest.metadata?.module;
138
+ // A dependency's declarations publish into ITS module's `resources`, not this
139
+ // one's: they are keyed by that module, and an export is also indexed under
140
+ // `<Alias>.<name>` below.
141
+ const forwardedFrom = isForwardedDeclaration(manifest) ? module : undefined;
142
+ if (forwardedFrom === undefined && isForwardedDeclaration(manifest))
143
+ continue;
144
+ const keyOf = (n) => forwardedFrom === undefined ? n : forwardedResourceKey(forwardedFrom, n);
127
145
  if (name)
128
- record(kind, name, false, manifest.metadata?.module);
146
+ record(kind, keyOf(name), false, module, forwardedFrom);
129
147
  const schema = resolve(kind)?.schema;
130
148
  if (!schema)
131
149
  continue;
@@ -137,7 +155,7 @@ export function buildObservedStateIndex(manifests, defs, aliases, scopes) {
137
155
  const scopedKind = scopedEntry?.kind;
138
156
  const scopedName = scopedEntry?.metadata?.name;
139
157
  if (typeof scopedKind === "string" && typeof scopedName === "string") {
140
- record(scopedKind, scopedName, true);
158
+ record(scopedKind, keyOf(scopedName), true, forwardedFrom, forwardedFrom);
141
159
  }
142
160
  }
143
161
  }
@@ -207,8 +225,8 @@ function* importedExports(manifests, aliases) {
207
225
  * flat field pass exactly as they do today. */
208
226
  export function buildObservedStateResourcesSchema(index, open) {
209
227
  const properties = {};
210
- for (const [key, { status }] of index) {
211
- if (!status)
228
+ for (const [key, { status, forwardedFrom }] of index) {
229
+ if (!status || forwardedFrom !== undefined)
212
230
  continue;
213
231
  applyObservedStateNode(properties, key, status);
214
232
  }
@@ -1 +1 @@
1
- {"version":3,"file":"validate-reference-forms.d.ts","sourceRoot":"","sources":["../src/validate-reference-forms.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAInE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,CAAC,EAAE,aAAa,EACvB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAC3C,kBAAkB,EAAE,CA0EtB"}
1
+ {"version":3,"file":"validate-reference-forms.d.ts","sourceRoot":"","sources":["../src/validate-reference-forms.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAKnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,CAAC,EAAE,aAAa,EACvB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAC3C,kBAAkB,EAAE,CAwEtB"}
@@ -1,4 +1,5 @@
1
1
  import { isTaggedSentinel } from "@telorun/templating";
2
+ import { isForwardedDeclaration } from "./forwarded-declaration.js";
2
3
  import { visitManifest } from "./manifest-visitor.js";
3
4
  import { satisfiesValueBranch } from "./reference-field-map.js";
4
5
  import { REF_VALIDATION_SKIP_KINDS as SYSTEM_KINDS } from "./system-kinds.js";
@@ -40,8 +41,7 @@ export function validateReferenceForms(resources, registry, aliases, aliasesByMo
40
41
  if (!aliases)
41
42
  return [];
42
43
  const diagnostics = [];
43
- const isForeign = (r) => r.metadata?.forwardedExport === true;
44
- const localResources = resources.filter((r) => !isForeign(r));
44
+ const localResources = resources.filter((r) => !isForwardedDeclaration(r));
45
45
  visitManifest(localResources, registry, {
46
46
  onRef: (e) => {
47
47
  const value = e.value;
@@ -1 +1 @@
1
- {"version":3,"file":"validate-references.d.ts","sourceRoot":"","sources":["../src/validate-references.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAarD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAE/F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAyInE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,kBAAkB;AAC5B;8EAC8E;AAC9E,aAAa,UAAQ,GACpB,OAAO,CAwBT;AAgED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,eAAe,GACvB,kBAAkB,EAAE,CAsgBtB"}
1
+ {"version":3,"file":"validate-references.d.ts","sourceRoot":"","sources":["../src/validate-references.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAarD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAE/F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AA2InE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,kBAAkB;AAC5B;8EAC8E;AAC9E,aAAa,UAAQ,GACpB,OAAO,CAwBT;AAgED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,eAAe,GACvB,kBAAkB,EAAE,CAwgBtB"}
@@ -8,6 +8,8 @@ import { DiagnosticSeverity } from "./types.js";
8
8
  import { moduleAliasScope } from "./module-alias-scope.js";
9
9
  import { isModuleKind } from "./module-kinds.js";
10
10
  import { isInjectedDeclaration } from "./resource-input.js";
11
+ import { outsideScopesOf } from "./scope-declarations.js";
12
+ import { isForwardedDeclaration, isForwardedExport } from "./forwarded-declaration.js";
11
13
  const SOURCE = "telo-analyzer";
12
14
  /** What a name IS on one side of a collision. The three shapes share the
13
15
  * kernel's one namespace but read as different things to an author — an alias
@@ -287,10 +289,10 @@ export function validateReferences(resources, context) {
287
289
  // metadata.module that isn't a root module) are resolution TARGETS only: excluded from
288
290
  // duplicate detection and local name resolution, and never walked as ref sources.
289
291
  const moduleOf = (r) => r.metadata?.module;
290
- // Forwarded exports are flagged by flattenForAnalyzer (`metadata.forwardedExport`); they're
291
- // cross-module resolution targets only — excluded from duplicate detection and local name
292
- // resolution, and never walked as ref sources.
293
- const isForeign = (r) => r.metadata?.forwardedExport === true;
292
+ // A dependency's code a forwarded export, or what extraction pulled out of one — is
293
+ // excluded from duplicate detection and local name resolution, and never walked as a
294
+ // ref source. Only an EXPORT is a cross-module resolution target.
295
+ const isForeign = isForwardedDeclaration;
294
296
  // Forwarded exported instances keyed `${module}\0${name}` — the lookup that resolves
295
297
  // whether a cross-module `!ref Alias.name` names a real exported instance.
296
298
  const byModuleName = new Map();
@@ -308,7 +310,7 @@ export function validateReferences(resources, context) {
308
310
  loadedModules.add(m);
309
311
  continue;
310
312
  }
311
- if (!r.metadata?.name || SYSTEM_KINDS.has(r.kind) || !isForeign(r))
313
+ if (!r.metadata?.name || SYSTEM_KINDS.has(r.kind) || !isForwardedExport(r))
312
314
  continue;
313
315
  const m = moduleOf(r);
314
316
  if (!m)
@@ -398,6 +400,10 @@ export function validateReferences(resources, context) {
398
400
  const target = visibleScopeManifests.find((m) => m.metadata?.name === localName) ??
399
401
  byName.get(localName);
400
402
  if (!target) {
403
+ // A scoped name out of this declaration's reach has its own
404
+ // diagnostic, which says why; this one would say only "not found".
405
+ if (outsideScopesOf(r).some((scope) => scope.names.includes(localName)))
406
+ return;
401
407
  diagnostics.push({
402
408
  severity: DiagnosticSeverity.Error,
403
409
  code: "UNRESOLVED_REFERENCE",
@@ -0,0 +1,31 @@
1
+ import type { ResourceManifest } from "@telorun/sdk";
2
+ import type { AliasResolver } from "./alias-resolver.js";
3
+ import type { DefinitionRegistry } from "./definition-registry.js";
4
+ import { type AnalysisDiagnostic } from "./types.js";
5
+ /**
6
+ * `SCOPED_NAME_OUT_OF_REACH` — a declaration written inside a scope's region but
7
+ * created outside the scope (see {@link OutsideScope}) naming one of the scope's
8
+ * resources.
9
+ *
10
+ * Written there, the name reads as reachable — the scope's names shadow the
11
+ * module's in its regions — while at run time it resolves to nothing, or to a
12
+ * module-level resource that merely shares it. Reported at the reference, in
13
+ * each spelling a name can be read in:
14
+ *
15
+ * - a `!ref` sentinel, anywhere — the tag is unambiguous;
16
+ * - a resolved `{kind, name}`, but ONLY where the declaring kind says a
17
+ * reference is: a reference slot of its field map, or a step's dispatch slot.
18
+ * An object that merely has those keys is data;
19
+ * - `resources.<name>` in a CEL expression.
20
+ *
21
+ * Lexical: a scope declared on the way from the out-of-reach declaration down to
22
+ * the reference — found through the kinds' own scope slots — shadows the name
23
+ * again, since that one IS created around it. Entry-module-scoped: a
24
+ * dependency's code is its own analysis's to report.
25
+ *
26
+ * Browser-safe.
27
+ */
28
+ export declare function validateScopedNameReach(manifests: ResourceManifest[], registry: DefinitionRegistry, aliases: AliasResolver, aliasesByModule: Map<string, AliasResolver>, rootModules: ReadonlySet<string>,
29
+ /** Member-access chains of a CEL expression — the analyzer's own parse. */
30
+ accessChains: (expression: string) => string[][]): AnalysisDiagnostic[];
31
+ //# sourceMappingURL=validate-scope-reach.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-scope-reach.d.ts","sourceRoot":"","sources":["../src/validate-scope-reach.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAanE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAYzE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,EACtB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,EAC3C,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC;AAChC,2EAA2E;AAC3E,YAAY,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,EAAE,EAAE,GAC/C,kBAAkB,EAAE,CAyItB"}
@@ -0,0 +1,180 @@
1
+ import { walkCelExpressions } from "@telorun/templating";
2
+ import { isForwardedDeclaration } from "./forwarded-declaration.js";
3
+ import { definitionInScope } from "./module-alias-scope.js";
4
+ import { isRefEntry, resolveFieldEntries } from "./reference-field-map.js";
5
+ import { declaredScopes, isScopeMember, outsideScopesOf, scopeEncloses, } from "./scope-declarations.js";
6
+ import { forEachStep, stepBodiesOf } from "./step-bodies.js";
7
+ import { DiagnosticSeverity } from "./types.js";
8
+ const SOURCE = "telo-analyzer";
9
+ const DECLARATION_KINDS = new Set([
10
+ "Telo.Definition",
11
+ "Telo.Abstract",
12
+ "Telo.Application",
13
+ "Telo.Library",
14
+ "Telo.Import",
15
+ ]);
16
+ /**
17
+ * `SCOPED_NAME_OUT_OF_REACH` — a declaration written inside a scope's region but
18
+ * created outside the scope (see {@link OutsideScope}) naming one of the scope's
19
+ * resources.
20
+ *
21
+ * Written there, the name reads as reachable — the scope's names shadow the
22
+ * module's in its regions — while at run time it resolves to nothing, or to a
23
+ * module-level resource that merely shares it. Reported at the reference, in
24
+ * each spelling a name can be read in:
25
+ *
26
+ * - a `!ref` sentinel, anywhere — the tag is unambiguous;
27
+ * - a resolved `{kind, name}`, but ONLY where the declaring kind says a
28
+ * reference is: a reference slot of its field map, or a step's dispatch slot.
29
+ * An object that merely has those keys is data;
30
+ * - `resources.<name>` in a CEL expression.
31
+ *
32
+ * Lexical: a scope declared on the way from the out-of-reach declaration down to
33
+ * the reference — found through the kinds' own scope slots — shadows the name
34
+ * again, since that one IS created around it. Entry-module-scoped: a
35
+ * dependency's code is its own analysis's to report.
36
+ *
37
+ * Browser-safe.
38
+ */
39
+ export function validateScopedNameReach(manifests, registry, aliases, aliasesByModule, rootModules,
40
+ /** Member-access chains of a CEL expression — the analyzer's own parse. */
41
+ accessChains) {
42
+ const out = [];
43
+ for (const root of manifests) {
44
+ if (!root.kind || DECLARATION_KINDS.has(root.kind) || isForwardedDeclaration(root))
45
+ continue;
46
+ const meta = root.metadata;
47
+ if (typeof meta?.name !== "string")
48
+ continue;
49
+ const module = typeof meta.module === "string" ? meta.module : undefined;
50
+ if (module && !rootModules.has(module))
51
+ continue;
52
+ const resource = { kind: root.kind, name: meta.name };
53
+ const filePath = typeof meta.source === "string" ? meta.source : undefined;
54
+ const report = (path, name, scope) => out.push({
55
+ severity: DiagnosticSeverity.Error,
56
+ code: "SCOPED_NAME_OUT_OF_REACH",
57
+ source: SOURCE,
58
+ message: `${scope.ownerKind}/${scope.ownerName}: '${name}' is declared in '${scope.field}:', but ` +
59
+ `the inline declaration referencing it is created where '${scope.ownerName}' is ` +
60
+ `declared, outside that scope, so the reference cannot reach it. Declare the ` +
61
+ `referencing resource under '${scope.field}:' with a name of its own and point at it ` +
62
+ `with '!ref'.`,
63
+ data: { resource, filePath, path },
64
+ });
65
+ /** A declaration's kind, field map and scopes, read in its module's aliases. */
66
+ const shape = (declaration) => {
67
+ const view = module !== undefined && typeof declaration.metadata?.module !== "string"
68
+ ? { ...declaration, metadata: { ...declaration.metadata, module } }
69
+ : declaration;
70
+ const fieldMap = registry.expandedFieldMapForResource(view, aliases, aliasesByModule);
71
+ const definition = definitionInScope(registry, view.kind, view.metadata, aliases, aliasesByModule);
72
+ return {
73
+ fieldMap,
74
+ scopes: declaredScopes(declaration, fieldMap),
75
+ schema: registry.effectiveSchemaOf(definition),
76
+ };
77
+ };
78
+ const check = (declaration, prefix, reach, shadowed) => {
79
+ const stamped = outsideScopesOf(declaration);
80
+ const nextReach = new Map(reach);
81
+ const nextShadowed = new Set(shadowed);
82
+ for (const scope of stamped) {
83
+ for (const name of scope.names) {
84
+ nextReach.set(name, scope);
85
+ nextShadowed.delete(name);
86
+ }
87
+ }
88
+ const { fieldMap, scopes, schema } = shape(declaration);
89
+ for (const scope of scopes) {
90
+ for (const member of scope.declarations) {
91
+ if (isScopeMember(member))
92
+ nextShadowed.add(member.metadata.name);
93
+ }
94
+ }
95
+ const at = (path) => (prefix ? (path ? `${prefix}.${path}` : prefix) : path);
96
+ const test = (name, path) => {
97
+ if (name === undefined || nextShadowed.has(name))
98
+ return;
99
+ const scope = nextReach.get(name);
100
+ if (scope)
101
+ report(at(path), name, scope);
102
+ };
103
+ const inScopeArray = (path) => scopes.some((scope) => scopeEncloses({ ...scope, regions: [scope.path] }, path));
104
+ // Tagged `!ref`s and CEL, anywhere but the declaration's own metadata and
105
+ // scope arrays (their members are checked below, with their own shadowing).
106
+ walkCelExpressions(declaration, "", (source, path, engine) => {
107
+ if (path === "metadata" || path.startsWith("metadata.") || inScopeArray(path))
108
+ return;
109
+ if (engine === "ref") {
110
+ test(localName(source), path);
111
+ }
112
+ else if (engine === "cel") {
113
+ for (const chain of accessChains(source)) {
114
+ if (chain[0] === "resources" && chain.length > 1)
115
+ test(chain[1], path);
116
+ }
117
+ }
118
+ });
119
+ // A resolved reference, where the kind declares one.
120
+ if (fieldMap) {
121
+ for (const [fieldPath, entry] of fieldMap) {
122
+ if (!isRefEntry(entry))
123
+ continue;
124
+ for (const { value, path } of resolveFieldEntries(declaration, fieldPath)) {
125
+ if (inScopeArray(path))
126
+ continue;
127
+ test(resolvedLocalName(value), path);
128
+ }
129
+ }
130
+ }
131
+ if (schema) {
132
+ for (const body of stepBodiesOf(declaration, schema)) {
133
+ forEachStep(body, schema, (step, stepPath) => test(resolvedLocalName(step[body.invokeField]), `${stepPath}.${body.invokeField}`));
134
+ }
135
+ }
136
+ for (const scope of scopes) {
137
+ scope.declarations.forEach((member, i) => {
138
+ if (isScopeMember(member)) {
139
+ check(member, at(`${scope.path}[${i}]`), nextReach, nextShadowed);
140
+ }
141
+ });
142
+ }
143
+ };
144
+ /** Only a stamped declaration can hold an out-of-reach reference; the rest
145
+ * is searched through the scope arrays that can hold one. */
146
+ const visit = (declaration, prefix) => {
147
+ if (outsideScopesOf(declaration).length > 0) {
148
+ check(declaration, prefix, new Map(), new Set());
149
+ return;
150
+ }
151
+ for (const scope of shape(declaration).scopes) {
152
+ scope.declarations.forEach((member, i) => {
153
+ if (!isScopeMember(member))
154
+ return;
155
+ visit(member, `${prefix ? `${prefix}.` : ""}${scope.path}[${i}]`);
156
+ });
157
+ }
158
+ };
159
+ visit(root, "");
160
+ }
161
+ return out;
162
+ }
163
+ /** The local name a `!ref` source names, or undefined for a cross-module one. */
164
+ function localName(source) {
165
+ const dot = source.indexOf(".");
166
+ if (dot === -1)
167
+ return source;
168
+ return source.slice(0, dot) === "Self" ? source.slice(dot + 1) : undefined;
169
+ }
170
+ /** The local name a resolved `{kind, name, alias?}` reference names. */
171
+ function resolvedLocalName(value) {
172
+ if (!value || typeof value !== "object" || Array.isArray(value))
173
+ return undefined;
174
+ const ref = value;
175
+ if (typeof ref.kind !== "string" || typeof ref.name !== "string")
176
+ return undefined;
177
+ if (typeof ref.alias === "string" && ref.alias !== "Self")
178
+ return undefined;
179
+ return ref.name;
180
+ }
@@ -0,0 +1,16 @@
1
+ import type { ResourceManifest } from "@telorun/sdk";
2
+ import { type AnalysisDiagnostic } from "./types.js";
3
+ /**
4
+ * The strict half of `source-entries.ts`: what `telo check` reports about a
5
+ * module doc's `sources:` block.
6
+ *
7
+ * Every rule the block alone decides is a reader problem, reported here under
8
+ * its own code. This adds the rules that need the rest of the module or that
9
+ * only publish enforces: an entry must stage a file the manifest names, a link
10
+ * ships in its target's layer, and every file entry and every build carries its
11
+ * pin. Shape errors are left to the owner doc's JSON Schema.
12
+ *
13
+ * Entry-module-scoped: a dependency's block is not the consumer's to fix.
14
+ */
15
+ export declare function validateSourceEntries(manifests: ResourceManifest[], entryModules?: ReadonlySet<string>): AnalysisDiagnostic[];
16
+ //# sourceMappingURL=validate-source-entries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-source-entries.d.ts","sourceRoot":"","sources":["../src/validate-source-entries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAUrD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIzE;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,YAAY,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GACjC,kBAAkB,EAAE,CA0FtB"}