@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.
Files changed (208) hide show
  1. package/dist/analyzer.d.ts.map +1 -1
  2. package/dist/analyzer.js +76 -20
  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 +144 -113
  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/cel-scope-query.d.ts.map +1 -1
  19. package/dist/cel-scope-query.js +32 -11
  20. package/dist/cel-scope.d.ts.map +1 -1
  21. package/dist/cel-scope.js +1 -0
  22. package/dist/eval-paths.d.ts +36 -5
  23. package/dist/eval-paths.d.ts.map +1 -1
  24. package/dist/eval-paths.js +47 -6
  25. package/dist/extends-resolution.d.ts +12 -0
  26. package/dist/extends-resolution.d.ts.map +1 -1
  27. package/dist/extends-resolution.js +23 -0
  28. package/dist/forwarded-declaration.d.ts +26 -0
  29. package/dist/forwarded-declaration.d.ts.map +1 -0
  30. package/dist/forwarded-declaration.js +31 -0
  31. package/dist/index.d.ts +10 -4
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +5 -2
  34. package/dist/kernel-globals.d.ts.map +1 -1
  35. package/dist/kernel-globals.js +21 -5
  36. package/dist/module-alias-scope.d.ts +11 -0
  37. package/dist/module-alias-scope.d.ts.map +1 -1
  38. package/dist/module-alias-scope.js +10 -0
  39. package/dist/module-file-claims.d.ts +17 -1
  40. package/dist/module-file-claims.d.ts.map +1 -1
  41. package/dist/module-file-claims.js +38 -3
  42. package/dist/module-named-files.d.ts +72 -0
  43. package/dist/module-named-files.d.ts.map +1 -0
  44. package/dist/module-named-files.js +115 -0
  45. package/dist/native-entries.d.ts +83 -0
  46. package/dist/native-entries.d.ts.map +1 -0
  47. package/dist/native-entries.js +169 -0
  48. package/dist/nearest-name.d.ts +22 -0
  49. package/dist/nearest-name.d.ts.map +1 -0
  50. package/dist/nearest-name.js +43 -0
  51. package/dist/normalize-inline-resources.d.ts +41 -13
  52. package/dist/normalize-inline-resources.d.ts.map +1 -1
  53. package/dist/normalize-inline-resources.js +236 -54
  54. package/dist/reference-field-map.d.ts +59 -1
  55. package/dist/reference-field-map.d.ts.map +1 -1
  56. package/dist/reference-field-map.js +200 -34
  57. package/dist/release/destinations.d.ts +59 -0
  58. package/dist/release/destinations.d.ts.map +1 -0
  59. package/dist/release/destinations.js +75 -0
  60. package/dist/release/index.d.ts +6 -2
  61. package/dist/release/index.d.ts.map +1 -1
  62. package/dist/release/index.js +3 -1
  63. package/dist/release/ledger.d.ts +20 -11
  64. package/dist/release/ledger.d.ts.map +1 -1
  65. package/dist/release/ledger.js +43 -14
  66. package/dist/release/payload-digest.d.ts +2 -1
  67. package/dist/release/payload-digest.d.ts.map +1 -1
  68. package/dist/release/payload-digest.js +2 -1
  69. package/dist/release/release-plan.d.ts +3 -2
  70. package/dist/release/release-plan.d.ts.map +1 -1
  71. package/dist/release/release-plan.js +12 -19
  72. package/dist/release/workspace-config.d.ts +114 -34
  73. package/dist/release/workspace-config.d.ts.map +1 -1
  74. package/dist/release/workspace-config.js +322 -38
  75. package/dist/release/workspace-schema.d.ts +41 -0
  76. package/dist/release/workspace-schema.d.ts.map +1 -0
  77. package/dist/release/workspace-schema.js +77 -0
  78. package/dist/resolve-ref-sentinels.d.ts.map +1 -1
  79. package/dist/resolve-ref-sentinels.js +8 -5
  80. package/dist/resolve-throws-union.d.ts +25 -4
  81. package/dist/resolve-throws-union.d.ts.map +1 -1
  82. package/dist/resolve-throws-union.js +62 -32
  83. package/dist/resolve-zone-requirements.d.ts.map +1 -1
  84. package/dist/resolve-zone-requirements.js +2 -1
  85. package/dist/rewrite-synthetic-origins.d.ts +6 -1
  86. package/dist/rewrite-synthetic-origins.d.ts.map +1 -1
  87. package/dist/rewrite-synthetic-origins.js +107 -13
  88. package/dist/schema-compat.d.ts.map +1 -1
  89. package/dist/schema-compat.js +18 -5
  90. package/dist/schema-error-report.d.ts +7 -0
  91. package/dist/schema-error-report.d.ts.map +1 -1
  92. package/dist/schema-error-report.js +9 -4
  93. package/dist/schema-walk.d.ts +47 -13
  94. package/dist/schema-walk.d.ts.map +1 -1
  95. package/dist/schema-walk.js +150 -30
  96. package/dist/scope-declarations.d.ts +59 -0
  97. package/dist/scope-declarations.d.ts.map +1 -0
  98. package/dist/scope-declarations.js +45 -0
  99. package/dist/source-entries.d.ts +119 -0
  100. package/dist/source-entries.d.ts.map +1 -0
  101. package/dist/source-entries.js +349 -0
  102. package/dist/step-bodies.d.ts +24 -0
  103. package/dist/step-bodies.d.ts.map +1 -0
  104. package/dist/step-bodies.js +28 -0
  105. package/dist/telo-version.d.ts +1 -1
  106. package/dist/telo-version.js +1 -1
  107. package/dist/template-body.d.ts +10 -0
  108. package/dist/template-body.d.ts.map +1 -1
  109. package/dist/template-body.js +25 -0
  110. package/dist/validate-cel-context.d.ts +23 -8
  111. package/dist/validate-cel-context.d.ts.map +1 -1
  112. package/dist/validate-cel-context.js +51 -18
  113. package/dist/validate-exports.d.ts +28 -0
  114. package/dist/validate-exports.d.ts.map +1 -0
  115. package/dist/validate-exports.js +146 -0
  116. package/dist/validate-extends.d.ts.map +1 -1
  117. package/dist/validate-extends.js +37 -1
  118. package/dist/validate-module-artifact.d.ts +4 -2
  119. package/dist/validate-module-artifact.d.ts.map +1 -1
  120. package/dist/validate-module-artifact.js +43 -4
  121. package/dist/validate-native-entries.d.ts +15 -0
  122. package/dist/validate-native-entries.d.ts.map +1 -0
  123. package/dist/validate-native-entries.js +103 -0
  124. package/dist/validate-observed-state.d.ts +7 -1
  125. package/dist/validate-observed-state.d.ts.map +1 -1
  126. package/dist/validate-observed-state.js +24 -6
  127. package/dist/validate-provider-coherence.d.ts +4 -8
  128. package/dist/validate-provider-coherence.d.ts.map +1 -1
  129. package/dist/validate-provider-coherence.js +12 -123
  130. package/dist/validate-reference-forms.d.ts.map +1 -1
  131. package/dist/validate-reference-forms.js +2 -2
  132. package/dist/validate-references.d.ts.map +1 -1
  133. package/dist/validate-references.js +152 -65
  134. package/dist/validate-scope-reach.d.ts +31 -0
  135. package/dist/validate-scope-reach.d.ts.map +1 -0
  136. package/dist/validate-scope-reach.js +180 -0
  137. package/dist/validate-source-entries.d.ts +16 -0
  138. package/dist/validate-source-entries.d.ts.map +1 -0
  139. package/dist/validate-source-entries.js +90 -0
  140. package/dist/validate-template-body.d.ts +36 -0
  141. package/dist/validate-template-body.d.ts.map +1 -0
  142. package/dist/validate-template-body.js +246 -0
  143. package/dist/validate-throws-coverage.d.ts.map +1 -1
  144. package/dist/validate-throws-coverage.js +21 -39
  145. package/dist/value-type-keyword.d.ts +22 -5
  146. package/dist/value-type-keyword.d.ts.map +1 -1
  147. package/dist/value-type-keyword.js +16 -7
  148. package/package.json +4 -3
  149. package/src/analyzer.ts +97 -20
  150. package/src/artifact-axes.ts +24 -0
  151. package/src/artifact-layer-index.ts +9 -6
  152. package/src/artifact-selector.ts +110 -35
  153. package/src/builtins.ts +160 -113
  154. package/src/call-graph.ts +30 -0
  155. package/src/catch-scope.ts +1 -1
  156. package/src/cel-scope-query.ts +28 -9
  157. package/src/cel-scope.ts +1 -0
  158. package/src/eval-paths.ts +53 -6
  159. package/src/extends-resolution.ts +24 -0
  160. package/src/forwarded-declaration.ts +32 -0
  161. package/src/index.ts +37 -1
  162. package/src/kernel-globals.ts +28 -5
  163. package/src/migrations/CLAUDE.md +35 -0
  164. package/src/module-alias-scope.ts +17 -0
  165. package/src/module-file-claims.ts +63 -4
  166. package/src/module-named-files.ts +162 -0
  167. package/src/native-entries.ts +227 -0
  168. package/src/nearest-name.ts +47 -0
  169. package/src/normalize-inline-resources.ts +326 -70
  170. package/src/reference-field-map.ts +257 -52
  171. package/src/release/CLAUDE.md +32 -0
  172. package/src/release/destinations.ts +105 -0
  173. package/src/release/index.ts +33 -2
  174. package/src/release/ledger.ts +51 -21
  175. package/src/release/payload-digest.ts +2 -1
  176. package/src/release/release-plan.ts +16 -26
  177. package/src/release/workspace-config.ts +483 -47
  178. package/src/release/workspace-schema.ts +99 -0
  179. package/src/resolve-ref-sentinels.ts +7 -6
  180. package/src/resolve-throws-union.ts +87 -32
  181. package/src/resolve-zone-requirements.ts +2 -1
  182. package/src/rewrite-synthetic-origins.ts +107 -16
  183. package/src/schema-compat.ts +18 -4
  184. package/src/schema-error-report.ts +16 -4
  185. package/src/schema-walk.ts +162 -34
  186. package/src/scope-declarations.ts +98 -0
  187. package/src/source-entries.ts +502 -0
  188. package/src/step-bodies.ts +55 -0
  189. package/src/telo-version.ts +1 -1
  190. package/src/template-body.ts +25 -0
  191. package/src/validate-cel-context.ts +60 -18
  192. package/src/validate-exports.ts +185 -0
  193. package/src/validate-extends.ts +43 -0
  194. package/src/validate-module-artifact.ts +46 -3
  195. package/src/validate-native-entries.ts +147 -0
  196. package/src/validate-observed-state.ts +36 -7
  197. package/src/validate-provider-coherence.ts +17 -127
  198. package/src/validate-reference-forms.ts +2 -3
  199. package/src/validate-references.ts +151 -66
  200. package/src/validate-scope-reach.ts +213 -0
  201. package/src/validate-source-entries.ts +120 -0
  202. package/src/validate-template-body.ts +274 -0
  203. package/src/validate-throws-coverage.ts +24 -31
  204. package/src/value-type-keyword.ts +35 -9
  205. package/dist/validate-template-dispatch.d.ts +0 -27
  206. package/dist/validate-template-dispatch.d.ts.map +0 -1
  207. package/dist/validate-template-dispatch.js +0 -95
  208. package/src/validate-template-dispatch.ts +0 -99
@@ -9,8 +9,9 @@
9
9
  * scope rule is consumed by the IDE, which must not pull the pass in behind it.
10
10
  */
11
11
  import { MANIFEST_SCHEMA_URI, ManifestRootSchema } from "./manifest-schemas.js";
12
- import { readRefSlot, type RefSlot } from "./ref-slot.js";
13
- import { readStepSlot, type StepSlot } from "./step-slot.js";
12
+ import type { RefSlot } from "./ref-slot.js";
13
+ import type { StepSlot } from "./step-slot.js";
14
+ import { buildDrivenSlotMap, refSlotOfEntry, type DrivenSlots } from "./reference-field-map.js";
14
15
 
15
16
  /** Resolve a local `$ref` (only `#/$defs/<name>` form) against the root schema.
16
17
  * Non-refs and unresolved refs pass through unchanged. */
@@ -144,57 +145,184 @@ export function walkStepArray(
144
145
  }
145
146
  });
146
147
  }
147
- /** A slot through which a resource drives another. */
148
+ /** A slot a kind's schema declares through which its resources drive another.
149
+ * `path` is the field-map form (`routes[].handler`, `content.{}.encoder`);
150
+ * `slots` holds every slot any branch declares there. */
151
+ export type DeclaredSlot =
152
+ | { kind: "step"; slots: StepSlot[]; path: string }
153
+ | { kind: "ref"; slots: RefSlot[]; path: string };
154
+
155
+ /** A reference slot at a site, with the declaration it came from — which is
156
+ * what a case-map selector's schema default is read against. */
157
+ export interface DrivenRef {
158
+ slot: RefSlot;
159
+ fieldPath: string;
160
+ }
161
+
162
+ /** The slots at one concrete site of one resource (`routes[0].handler`): every
163
+ * slot any branch or recursion route declares there. A consumer reads them as
164
+ * a union — a branch that does not apply to this resource can only add what
165
+ * counts, never remove it. */
148
166
  export type DrivenSlot =
149
- | { kind: "step"; slot: StepSlot; data: unknown[]; path: string }
150
- | { kind: "ref"; slot: RefSlot; data: unknown; path: string };
167
+ | { kind: "step"; slots: StepSlot[]; data: unknown[]; path: string }
168
+ | { kind: "ref"; slots: DrivenRef[]; data: unknown; path: string };
169
+
170
+ /**
171
+ * Every slot a kind's schema declares through which its resources drive
172
+ * another — the schema-only mode of {@link forEachDrivenSlot}, read off the
173
+ * same {@link buildDrivenSlotMap}.
174
+ */
175
+ export function forEachDeclaredSlot(schema: unknown, visit: (slot: DeclaredSlot) => void): void {
176
+ if (!schema || typeof schema !== "object") return;
177
+ for (const [path, at] of buildDrivenSlotMap(schema as Record<string, any>).paths) {
178
+ if (at.steps.length > 0) visit({ kind: "step", slots: at.steps, path });
179
+ if (at.refs.length > 0) visit({ kind: "ref", slots: at.refs.map(refSlotOfEntry), path });
180
+ }
181
+ }
151
182
 
152
183
  /**
153
184
  * Every slot of one resource through which it drives another, schema and data in
154
185
  * tandem.
155
186
  *
156
- * One traversal with two consumersthe inherited union here, and the catch
157
- * scope enclosure in `validate-throws-coverage.ts` because both ask the same
158
- * structural question and two copies would eventually disagree about where the
159
- * walk stops. It terminates on the manifest's own depth, and it stops AT a step
160
- * slot (that traversal owns everything below it, `try`/`catch` subtraction
161
- * included) and AT a reference slot (a resolved ref is a leaf, `{kind, name}`,
162
- * with nothing beneath it to visit).
187
+ * One traversal for every throws question a kind's `inherit` union, a scope
188
+ * list's denominator, catch-scope enclosure, and whether `inherit` is legal
189
+ * because they ask the same structural question and two copies would eventually
190
+ * disagree about where the walk stops. The slots are the driven-slot map's, so
191
+ * the reach is the reference field map's plus local `$ref`; a step slot is a
192
+ * stop (that traversal owns everything below it, `try`/`catch` subtraction
193
+ * included) and so is a reference slot (a resolved ref is a leaf).
194
+ *
195
+ * Each concrete site is visited once, with every slot that reaches it. A
196
+ * map-value (`additionalProperties`) slot applies only to keys its schema does
197
+ * not declare, and never to the resource envelope at the root, as JSON Schema
198
+ * applies it. A recursive schema's back-edge is followed as deep as the data
199
+ * goes, guarded against data that aliases one of its own ancestors. A schema
200
+ * whose map holds no step or reference slot is not walked at all.
163
201
  */
164
202
  export function forEachDrivenSlot(
165
203
  schema: unknown,
166
204
  data: unknown,
167
205
  visit: (slot: DrivenSlot) => void,
168
- path = "",
169
206
  ): void {
170
207
  if (!schema || typeof schema !== "object" || data === undefined || data === null) return;
171
- const node = schema as Record<string, any>;
172
-
173
- const stepSlot = readStepSlot(node);
174
- if (stepSlot) {
175
- if (Array.isArray(data)) visit({ kind: "step", slot: stepSlot, data, path });
176
- return;
177
- }
208
+ const driven = buildDrivenSlotMap(schema as Record<string, any>);
209
+ if (!driven.drives) return;
210
+ const sites = new Map<
211
+ string,
212
+ { data: unknown; steps: StepSlot[]; refs: DrivenRef[]; from: Set<object> }
213
+ >();
214
+ const onData = new Set<unknown>([data]);
178
215
 
179
- const refSlot = readRefSlot(node);
180
- if (refSlot) {
181
- visit({ kind: "ref", slot: refSlot, data, path });
182
- return;
183
- }
216
+ const walk = (prefix: string, value: unknown, base: string): void => {
217
+ for (const [fieldPath, at] of driven.paths) {
218
+ const rel = relativeFieldPath(fieldPath, prefix);
219
+ if (rel === undefined) continue;
220
+ for (const found of resolveSites(value, rel, prefix, driven)) {
221
+ const path = joinConcrete(base, found.path);
222
+ if (at.steps.length > 0 || at.refs.length > 0) {
223
+ let site = sites.get(path);
224
+ if (!site) {
225
+ site = { data: found.value, steps: [], refs: [], from: new Set() };
226
+ sites.set(path, site);
227
+ }
228
+ for (const step of at.steps) {
229
+ if (site.from.has(step)) continue;
230
+ site.from.add(step);
231
+ site.steps.push(step);
232
+ }
233
+ for (const entry of at.refs) {
234
+ if (site.from.has(entry)) continue;
235
+ site.from.add(entry);
236
+ site.refs.push({ slot: refSlotOfEntry(entry), fieldPath });
237
+ }
238
+ }
239
+ if (at.recurse.length === 0) continue;
240
+ if (!found.value || typeof found.value !== "object" || onData.has(found.value)) continue;
241
+ onData.add(found.value);
242
+ for (const to of at.recurse) walk(to, found.value, path);
243
+ onData.delete(found.value);
244
+ }
245
+ }
246
+ };
247
+ walk("", data, "");
184
248
 
185
- const props = node.properties as Record<string, any> | undefined;
186
- if (props && typeof data === "object" && !Array.isArray(data)) {
187
- const obj = data as Record<string, unknown>;
188
- for (const [key, propSchema] of Object.entries(props)) {
189
- if (obj[key] === undefined) continue;
190
- forEachDrivenSlot(propSchema, obj[key], visit, path ? `${path}.${key}` : key);
249
+ for (const [path, site] of sites) {
250
+ if (site.steps.length > 0 && Array.isArray(site.data)) {
251
+ visit({ kind: "step", slots: site.steps, data: site.data, path });
252
+ } else if (site.refs.length > 0) {
253
+ visit({ kind: "ref", slots: site.refs, data: site.data, path });
191
254
  }
192
255
  }
256
+ }
257
+
258
+ interface Site {
259
+ value: unknown;
260
+ path: string;
261
+ }
193
262
 
194
- if (node.items && Array.isArray(data)) {
195
- for (const [i, item] of data.entries()) {
196
- forEachDrivenSlot(node.items, item, visit, `${path}[${i}]`);
263
+ /** The values at `rel` below `value`, where `rel` is relative to the field path
264
+ * `prefix`. A `{}` segment skips the keys the driven map records as declared
265
+ * for that map path; each `[]` iterates one array level. */
266
+ function resolveSites(value: unknown, rel: string, prefix: string, driven: DrivenSlots): Site[] {
267
+ let sites: Site[] = [{ value, path: "" }];
268
+ let field = prefix;
269
+ for (const part of rel.split(".")) {
270
+ const next: Site[] = [];
271
+ if (part === "{}") {
272
+ field = joinKey(field, "{}");
273
+ const declared = driven.declaredKeys.get(field);
274
+ for (const site of sites) {
275
+ if (!site.value || typeof site.value !== "object" || Array.isArray(site.value)) continue;
276
+ for (const [key, entry] of Object.entries(site.value as Record<string, unknown>)) {
277
+ if (entry == null || declared?.has(key)) continue;
278
+ next.push({ value: entry, path: joinKey(site.path, key) });
279
+ }
280
+ }
281
+ } else {
282
+ const key = part.replace(/(\[\])+$/, "");
283
+ const depth = (part.length - key.length) / 2;
284
+ field = key ? joinKey(field, part) : `${field}${part}`;
285
+ for (const site of sites) {
286
+ let level: Site[];
287
+ if (key) {
288
+ if (!site.value || typeof site.value !== "object") continue;
289
+ const entry = (site.value as Record<string, unknown>)[key];
290
+ if (entry == null) continue;
291
+ level = [{ value: entry, path: joinKey(site.path, key) }];
292
+ } else {
293
+ level = [site];
294
+ }
295
+ for (let d = 0; d < depth; d++) {
296
+ const items: Site[] = [];
297
+ for (const holder of level) {
298
+ if (!Array.isArray(holder.value)) continue;
299
+ holder.value.forEach((item, i) => {
300
+ if (item != null) items.push({ value: item, path: `${holder.path}[${i}]` });
301
+ });
302
+ }
303
+ level = items;
304
+ }
305
+ next.push(...level);
306
+ }
197
307
  }
308
+ sites = next;
198
309
  }
310
+ return sites;
311
+ }
312
+
313
+ const joinKey = (path: string, key: string): string => (path ? `${path}.${key}` : key);
314
+
315
+ /** `fieldPath` relative to `prefix`, or undefined when it is not strictly
316
+ * below it. A result starting `[]` iterates the value at `prefix` itself. */
317
+ function relativeFieldPath(fieldPath: string, prefix: string): string | undefined {
318
+ if (prefix === "") return fieldPath;
319
+ if (fieldPath.startsWith(`${prefix}.`)) return fieldPath.slice(prefix.length + 1);
320
+ if (fieldPath.startsWith(`${prefix}[]`)) return fieldPath.slice(prefix.length);
321
+ return undefined;
322
+ }
323
+
324
+ function joinConcrete(base: string, rel: string): string {
325
+ if (!base) return rel;
326
+ return rel.startsWith("[") ? `${base}${rel}` : `${base}.${rel}`;
199
327
  }
200
328
 
@@ -0,0 +1,98 @@
1
+ import type { ResourceManifest } from "@telorun/sdk";
2
+ import { isRefSentinel } from "@telorun/templating";
3
+ import { isScopeEntry, resolveFieldEntries, type ReferenceFieldMap } from "./reference-field-map.js";
4
+
5
+ /**
6
+ * The execution scopes ONE resource declares — the `x-telo-scope` arrays it
7
+ * holds — read off its kind's field map, which is the analyzer's single
8
+ * definition of "scope" (the same one `resolve-ref-sentinels` and
9
+ * `manifest-visitor` read). Inferring a scope from shape instead would give one
10
+ * to any kind that happens to carry an array of named declarations.
11
+ *
12
+ * Browser-safe.
13
+ */
14
+
15
+ /** One declared scope: the array a scope run creates, and where its names resolve. */
16
+ export interface DeclaredScope {
17
+ /** Concrete path of the scope slot on its resource (`with`). */
18
+ path: string;
19
+ /** The declaration array itself — what a scope run registers and tears down. */
20
+ declarations: unknown[];
21
+ /** Dot-form prefixes of every region its names resolve in (`steps`, `targets`). */
22
+ regions: string[];
23
+ }
24
+
25
+ export type ScopeMember = ResourceManifest & { metadata: { name: string } };
26
+
27
+ /** Every scope `resource` declares, in field-map order. A scope slot left unset
28
+ * declares nothing: no scope run happens, so nothing is created in one. */
29
+ export function declaredScopes(
30
+ resource: Record<string, unknown>,
31
+ fieldMap: ReferenceFieldMap | undefined,
32
+ ): DeclaredScope[] {
33
+ if (!fieldMap) return [];
34
+ const out: DeclaredScope[] = [];
35
+ for (const [fieldPath, entry] of fieldMap) {
36
+ if (!isScopeEntry(entry)) continue;
37
+ const pointers = Array.isArray(entry.scope) ? entry.scope : [entry.scope];
38
+ const regions = pointers.map((p) => p.replace(/^\//, "").replace(/\//g, "."));
39
+ for (const { value, path } of resolveFieldEntries(resource, fieldPath)) {
40
+ if (Array.isArray(value)) out.push({ path, declarations: value, regions });
41
+ }
42
+ }
43
+ return out;
44
+ }
45
+
46
+ /** True when `path` — a field-map path (`steps[].invoke`) or a concrete one
47
+ * (`steps[0].invoke`) — lies inside one of the scope's regions. */
48
+ export function scopeEncloses(scope: DeclaredScope, path: string): boolean {
49
+ return scope.regions.some(
50
+ (region) => path === region || path.startsWith(`${region}.`) || path.startsWith(`${region}[`),
51
+ );
52
+ }
53
+
54
+ /** A scope's inline resource declarations. A `!ref` entry is a category error
55
+ * (`SCOPE_ENTRY_NOT_INLINE`), not a member. */
56
+ export function scopeMembers(scope: DeclaredScope): ScopeMember[] {
57
+ return scope.declarations.filter(isScopeMember);
58
+ }
59
+
60
+ /** True when a scope array entry is an inline resource declaration. */
61
+ export function isScopeMember(value: unknown): value is ScopeMember {
62
+ if (!value || typeof value !== "object" || Array.isArray(value) || isRefSentinel(value)) {
63
+ return false;
64
+ }
65
+ const entry = value as Record<string, unknown>;
66
+ const name = (entry.metadata as { name?: unknown } | undefined)?.name;
67
+ return typeof entry.kind === "string" && typeof entry.name !== "string" && typeof name === "string";
68
+ }
69
+
70
+ /**
71
+ * A scope an extracted declaration was WRITTEN inside and is NOT created in.
72
+ *
73
+ * An inline declaration is created in the set its owner is declared in, never in
74
+ * a scope its owner declares — moving it there would make every step target of a
75
+ * sequence with a `with:` block a scoped resource, which is a change of durable
76
+ * identity. So a declaration written in `steps:` next to a `with:` block is
77
+ * lexically inside that scope and lives outside it, and the names the scope
78
+ * declares are out of its reach. Recorded at extraction, where both facts are in
79
+ * hand, as `metadata.xTeloOrigin.outsideScopes`.
80
+ */
81
+ export interface OutsideScope {
82
+ /** The resource declaring the scope, as its manifest spells it. */
83
+ ownerKind: string;
84
+ ownerName: string;
85
+ /** The scope slot (`with`). */
86
+ field: string;
87
+ /** Every name the scope declares. */
88
+ names: string[];
89
+ }
90
+
91
+ /** The scopes recorded as out of reach for an extracted declaration. */
92
+ export function outsideScopesOf(manifest: unknown): OutsideScope[] {
93
+ const origin = (
94
+ (manifest as { metadata?: { xTeloOrigin?: { outsideScopes?: unknown } } } | undefined)
95
+ ?.metadata?.xTeloOrigin
96
+ )?.outsideScopes;
97
+ return Array.isArray(origin) ? (origin as OutsideScope[]) : [];
98
+ }