@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
@@ -1,7 +1,26 @@
1
- import type { ResourceManifest } from "@telorun/sdk";
2
- import { isRefEntry, isScopeEntry, isInlineResource } from "./reference-field-map.js";
3
- import type { DefinitionRegistry } from "./definition-registry.js";
1
+ import { inlineStepTargetName, type ResourceDefinition, type ResourceManifest } from "@telorun/sdk";
2
+ import { isTaggedSentinel } from "@telorun/templating";
4
3
  import type { AliasResolver } from "./alias-resolver.js";
4
+ import type { DefinitionRegistry } from "./definition-registry.js";
5
+ import {
6
+ controllerBearingAncestor,
7
+ hasOwnControllerOrTemplate,
8
+ type DefResolver,
9
+ } from "./extends-resolution.js";
10
+ import { isForwardedDeclaration } from "./forwarded-declaration.js";
11
+ import { definitionInScope } from "./module-alias-scope.js";
12
+ import { isInlineResource, isRefEntry } from "./reference-field-map.js";
13
+ import { gatherPropertySchemas } from "./schema-walk.js";
14
+ import {
15
+ declaredScopes,
16
+ outsideScopesOf,
17
+ scopeEncloses,
18
+ scopeMembers,
19
+ type DeclaredScope,
20
+ type OutsideScope,
21
+ } from "./scope-declarations.js";
22
+ import { forEachStep, stepBodiesOf } from "./step-bodies.js";
23
+ import { readStepSlot } from "./step-slot.js";
5
24
 
6
25
  const SYSTEM_KINDS = new Set([
7
26
  "Telo.Definition",
@@ -46,23 +65,88 @@ function cloneForMutation(value: unknown): unknown {
46
65
  return out;
47
66
  }
48
67
 
68
+ type NamedManifest = ResourceManifest & { metadata: { name: string } };
69
+
70
+ /** What an extraction inherits from the declaration it was written in: the
71
+ * module its kinds are written in, whether it is a dependency's code, and the
72
+ * declaring module's config contract its CEL is typed against. */
73
+ interface Provenance {
74
+ module: string | undefined;
75
+ forwarded: boolean;
76
+ moduleGlobals: unknown;
77
+ }
78
+
79
+ /** A declaration waiting to have its inline slots extracted. */
80
+ interface Declaration extends Provenance {
81
+ manifest: NamedManifest;
82
+ /** The set it is declared in — the module's top level (undefined), or the
83
+ * `x-telo-scope` array holding it. What it declares inline joins the same set
84
+ * unless the slot lies in a region of a scope it declares itself. */
85
+ home: unknown[] | undefined;
86
+ /** Scopes it is lexically inside and not created in — see {@link OutsideScope}.
87
+ * Whatever it declares inline is created where it is, so inherits them. */
88
+ outside: OutsideScope[];
89
+ }
90
+
91
+ /** What `metadata.xTeloOrigin` records: where an extracted declaration was
92
+ * written, so a diagnostic about it anchors in its author's document. */
93
+ interface Origin {
94
+ parentKind: string;
95
+ parentName: string;
96
+ /** Concrete dotted path with `[N]` indices, matching `buildPositionIndex` keys
97
+ * (e.g. `routes[0].handler`). */
98
+ pathFromParent: string;
99
+ /** Set on a step's dispatch target, whose identity is its path-derived name
100
+ * alone — see `inlineStepTargetName`. */
101
+ stepTarget?: true;
102
+ outsideScopes?: OutsideScope[];
103
+ }
104
+
49
105
  /**
50
106
  * Phase 2 — Inline resource normalization.
51
107
  *
52
- * After all manifests and definitions are loaded, walks every non-system resource's
53
- * x-telo-ref slots. For each inline resource value (has keys beyond kind/name/metadata),
54
- * assigns a deterministic name, extracts it as a first-class manifest, and replaces
55
- * the inline value in-place with `{kind, name}`. Newly extracted resources are enqueued
56
- * so nested inlines are resolved in the same pass.
108
+ * Every inline declaration (a `{kind, …config}` with no `name`) is extracted as a
109
+ * first-class manifest under a deterministic name and replaced in place with
110
+ * `{kind, name}`. Two kinds of slot carry one:
111
+ *
112
+ * - a REFERENCE slot, found through the kind's field map, named
113
+ * `{parentName}_{pathSegment}[_{itemName|index}]_{fieldName}`
114
+ * (`TestBasicAddition_steps_AddTwoNumbers_invoke`);
115
+ * - a STEP's dispatch target, at any nesting depth of a step body, named by
116
+ * `inlineStepTargetName` — the name the step engine has always registered it
117
+ * under, which is durable identity and so must not move with the extraction.
118
+ *
119
+ * Step slots are found through the kind's step-body annotation and walked with
120
+ * `walkStepArray`, never through the field map: that map is the kernel's Phase-5
121
+ * injection surface, and a step's target resolves at dispatch.
122
+ *
123
+ * Every extracted manifest is queued in turn, so an inline declaration nested in
124
+ * another's slots — a handler inside an inline step target — is extracted too.
125
+ * So is every member of an `x-telo-scope` array, which is a declaration of its
126
+ * own.
127
+ *
128
+ * WHERE an extraction is created:
129
+ *
130
+ * - a reference slot lying in a region of a scope its owner declares (a
131
+ * sequence's `targets:`) resolves against that scope, so its declaration
132
+ * joins the scope's array and is created per scope run;
133
+ * - a step target is created where its owner is — the module's top level, or
134
+ * the scope array holding a scope member — and never in a scope the owner
135
+ * declares, because that would make every step target of a sequence with a
136
+ * `with:` block a scoped resource and move its durable identity;
137
+ * - any other extraction is created where its owner is.
57
138
  *
58
- * Naming scheme:
59
- * {parentName}_{pathSegment}[_{itemName|index}]_{fieldName}
60
- * e.g. TestBasicAddition_steps_AddTwoNumbers_invoke
61
- * TestBasicAddition_steps_0_invoke (when step has no name)
139
+ * A declaration written inside a scope's region and created outside it records
140
+ * the scope (`xTeloOrigin.outsideScopes`), and `validateScopedNameReach` reports
141
+ * a reference from it to a name that scope declares.
142
+ *
143
+ * An extraction out of a dependency's forwarded manifest is that dependency's
144
+ * code: it is stamped `forwardedInternal` (see `isForwardedDeclaration`) and
145
+ * carries the declaring module's `moduleGlobals`, as its parent does.
62
146
  *
63
147
  * Returns a new array of deep-cloned manifests (the rewrites land on the clones)
64
- * plus all extracted manifests. The caller's manifests — array and elements — are
65
- * never mutated; this is the analyzer's immutability boundary.
148
+ * plus every top-level extraction. The caller's manifests — array and elements —
149
+ * are never mutated; this is the analyzer's immutability boundary.
66
150
  */
67
151
  export function normalizeInlineResources(
68
152
  resources: ResourceManifest[],
@@ -71,97 +155,258 @@ export function normalizeInlineResources(
71
155
  aliasesByModule?: Map<string, AliasResolver>,
72
156
  ): ResourceManifest[] {
73
157
  // Deep-clone the input so this pass — and `resolveRefSentinels`, which runs on
74
- // this output — never mutate caller-owned manifests. `extractInlinesAtPath`
75
- // rewrites inline ref slots in place, so without cloning we'd corrupt shared
76
- // state such as the editor's `LoadedFile.manifests` parse cache (a reused
77
- // sentinel would be rewritten to `{kind, name}`, later misread as an authored
78
- // reference form). The clone is the analyzer's immutability boundary.
158
+ // this output — never mutate caller-owned manifests. The extractions rewrite
159
+ // slots in place, so without cloning we'd corrupt shared state such as the
160
+ // editor's `LoadedFile.manifests` parse cache (a reused sentinel would be
161
+ // rewritten to `{kind, name}`, later misread as an authored reference form).
79
162
  const result = resources.map(cloneForMutation) as ResourceManifest[];
80
163
 
81
- // Queue: all non-system resources with a name. Extracted resources are appended.
82
- // Filter the CLONES (not the originals) so traversal mutates copies.
83
164
  // System kinds join the queue too: their inline-accepting slots are filtered
84
165
  // per entry below, so a system document is walked but only its opted-in slots
85
166
  // are extracted from.
86
- const queue = result.filter(
87
- (r): r is ResourceManifest & { metadata: { name: string } } =>
88
- typeof r.metadata?.name === "string" && !!r.kind,
89
- );
167
+ const queue: Declaration[] = result
168
+ .filter((r): r is NamedManifest => typeof r.metadata?.name === "string" && !!r.kind)
169
+ .map((manifest) => ({
170
+ manifest,
171
+ home: undefined,
172
+ outside: outsideScopesOf(manifest),
173
+ ...provenanceOf(manifest, undefined),
174
+ }));
90
175
 
91
- let i = 0;
92
- while (i < queue.length) {
93
- const resource = queue[i++];
176
+ const place = (manifest: ResourceManifest, into: unknown[] | undefined, module?: string) => {
177
+ if (into) into.push(manifest);
178
+ else result.push(manifest);
179
+ queue.push({
180
+ manifest: manifest as NamedManifest,
181
+ home: into,
182
+ outside: outsideScopesOf(manifest),
183
+ ...provenanceOf(manifest, module),
184
+ });
185
+ };
186
+
187
+ for (let i = 0; i < queue.length; i++) {
188
+ const current = queue[i];
189
+ const { manifest: resource, home, outside } = current;
190
+ // The registry reads a kind's alias table off `metadata.module`; a scope
191
+ // member is looked up through a view carrying its owner's.
192
+ const view =
193
+ current.module !== undefined && moduleOf(resource) === undefined
194
+ ? ({ ...resource, metadata: { ...resource.metadata, module: current.module } } as NamedManifest)
195
+ : resource;
94
196
  // When aliasesByModule is available, use the expanded map so inline refs
95
197
  // hidden behind an x-telo-schema-from indirection (e.g. an encoder inside
96
198
  // an HttpDispatch.Outcomes/$defs/Returns sub-schema) reach extraction.
97
199
  const fieldMap =
98
200
  aliases && aliasesByModule
99
- ? registry.expandedFieldMapForResource(resource, aliases, aliasesByModule)
201
+ ? registry.expandedFieldMapForResource(view, aliases, aliasesByModule)
100
202
  : registry.getFieldMapForKind(resource.kind, aliases);
101
203
  if (!fieldMap) continue;
102
204
 
103
- const parentName = resource.metadata.name as string;
104
- const parentModule = resource.metadata.module as string | undefined;
205
+ const parentName = resource.metadata.name;
206
+ const parentModule = moduleOf(view);
207
+ const inherit: Provenance = {
208
+ module: parentModule,
209
+ forwarded: current.forwarded,
210
+ moduleGlobals: current.moduleGlobals,
211
+ };
105
212
 
106
- // Collect scope visibility prefixes so we can route extracted resources correctly.
107
- const scopePrefixes: string[] = [];
108
- for (const [, entry] of fieldMap) {
109
- if (!isScopeEntry(entry)) continue;
110
- const paths = Array.isArray(entry.scope) ? entry.scope : [entry.scope];
111
- for (const p of paths) {
112
- scopePrefixes.push(p.replace(/^\//, "").replace(/\//g, "."));
213
+ const scopes = declaredScopes(resource, fieldMap);
214
+ // Queued BEFORE anything is extracted into these arrays: an extraction
215
+ // queues itself, and must not be walked twice. A member is created inside
216
+ // this resource's scope, which is itself created where this resource is.
217
+ for (const scope of scopes) {
218
+ for (const member of scopeMembers(scope)) {
219
+ queue.push({ manifest: member, home: scope.declarations, outside, ...inherit });
113
220
  }
114
221
  }
222
+ const outsideAt = (slotPath: string, createdIn?: DeclaredScope): OutsideScope[] => [
223
+ ...outside,
224
+ ...scopes
225
+ .filter((scope) => scope !== createdIn && scopeEncloses(scope, slotPath))
226
+ .map((scope) => ({
227
+ ownerKind: resource.kind,
228
+ ownerName: parentName,
229
+ field: scope.path,
230
+ names: scopeMembers(scope).map((member) => member.metadata.name),
231
+ }))
232
+ .filter((scope) => scope.names.length > 0),
233
+ ];
115
234
 
116
235
  for (const [fieldPath, entry] of fieldMap) {
117
236
  if (!isRefEntry(entry)) continue;
118
237
  if (!acceptsInline(resource.kind, entry)) continue;
119
-
120
- const inScope = scopePrefixes.some(
121
- (prefix) =>
122
- fieldPath === prefix ||
123
- fieldPath.startsWith(prefix + ".") ||
124
- fieldPath.startsWith(prefix + "["),
125
- );
126
-
127
- const invocationContext = isRefEntry(entry) ? entry.context : undefined;
128
- const extracted = extractInlinesAtPath(
238
+ const scope = scopes.find((s) => scopeEncloses(s, fieldPath));
239
+ for (const manifest of extractInlinesAtPath(
129
240
  resource,
130
241
  fieldPath,
131
242
  parentName,
132
243
  resource.kind,
133
- parentModule,
134
- invocationContext,
135
- );
136
- for (const manifest of extracted) {
137
- result.push(manifest);
138
- queue.push(manifest as ResourceManifest & { metadata: { name: string } });
139
- // TODO Phase 5: when inScope, add to parent's scope array instead of outer set
140
- void inScope;
244
+ inherit,
245
+ outsideAt(fieldPath, scope),
246
+ entry.context,
247
+ )) {
248
+ place(manifest, scope?.declarations ?? home, parentModule);
141
249
  }
142
250
  }
251
+
252
+ if (SYSTEM_KINDS.has(resource.kind)) continue;
253
+ extractStepTargets(resource, view, registry, aliases, aliasesByModule, inherit, outsideAt, (m) =>
254
+ place(m, home, parentModule),
255
+ );
143
256
  }
144
257
 
145
258
  return result;
146
259
  }
147
260
 
261
+ const moduleOf = (manifest: ResourceManifest): string | undefined => {
262
+ const module = (manifest.metadata as { module?: unknown } | undefined)?.module;
263
+ return typeof module === "string" ? module : undefined;
264
+ };
265
+
266
+ function provenanceOf(manifest: ResourceManifest, module: string | undefined): Provenance {
267
+ return {
268
+ module: moduleOf(manifest) ?? module,
269
+ forwarded: isForwardedDeclaration(manifest),
270
+ moduleGlobals: (manifest.metadata as { moduleGlobals?: unknown } | undefined)?.moduleGlobals,
271
+ };
272
+ }
273
+
274
+ /**
275
+ * Extract every inline dispatch target of every step in `resource`'s step
276
+ * bodies, at any nesting depth, into a named manifest.
277
+ *
278
+ * The name is the step engine's, so it names what the RUNTIME owner passes it:
279
+ *
280
+ * - the owner kind is the kind whose CONTROLLER runs the body — the kind
281
+ * itself, or for an `extends` child that inherits its controller, the
282
+ * ancestor that supplies it;
283
+ * - the path's first segment is the field the controller reads its body from.
284
+ * For a `base:`-form child that is the PARENT's field the body is mapped onto,
285
+ * which is taken from a pure `self.<field>` mapping; a body `base:` reshapes any
286
+ * other way is left inline, for the runtime to name exactly as it always has;
287
+ * - an inline declaration stating its own `metadata.name` keeps it, as
288
+ * `ensureKindRef` always has.
289
+ */
290
+ function extractStepTargets(
291
+ resource: NamedManifest,
292
+ view: NamedManifest,
293
+ registry: DefinitionRegistry,
294
+ aliases: AliasResolver | undefined,
295
+ aliasesByModule: Map<string, AliasResolver> | undefined,
296
+ inherit: Provenance,
297
+ outsideAt: (slotPath: string) => OutsideScope[],
298
+ emit: (manifest: ResourceManifest) => void,
299
+ ): void {
300
+ const definition = definitionInScope<ResourceDefinition>(
301
+ registry,
302
+ view.kind,
303
+ view.metadata,
304
+ aliases,
305
+ aliasesByModule,
306
+ );
307
+ if (!definition) return;
308
+ const schema = registry.effectiveSchemaOf(definition) as Record<string, any> | undefined;
309
+ if (!schema) return;
310
+ const resolveDef: DefResolver = (kind, from) =>
311
+ definitionInScope<ResourceDefinition>(registry, kind, from?.metadata, aliases, aliasesByModule);
312
+ const runner = hasOwnControllerOrTemplate(definition)
313
+ ? definition
314
+ : (controllerBearingAncestor(definition, resolveDef) ?? definition);
315
+ const owner = { kind: runner.metadata.name, resourceName: resource.metadata.name };
316
+ const base = runner !== definition ? (definition as { base?: unknown }).base : undefined;
317
+
318
+ for (const body of stepBodiesOf(resource, schema)) {
319
+ const root = base === undefined ? body.field : mappedStepField(base, body.field, runner, registry);
320
+ if (root === undefined) continue;
321
+ const outsideScopes = outsideAt(body.field);
322
+
323
+ forEachStep(body, schema, (step, stepPath) => {
324
+ const target = step[body.invokeField];
325
+ if (!target || typeof target !== "object" || Array.isArray(target)) return;
326
+ if (!isInlineResource(target as Record<string, unknown>)) return;
327
+ // The step grammar requires a name, and a target is named after it; a step
328
+ // without one is reported by schema validation and left as written.
329
+ if (typeof step.name !== "string") return;
330
+ const inline = target as Record<string, unknown>;
331
+ const declared = (inline.metadata as { name?: unknown } | undefined)?.name;
332
+ const name =
333
+ typeof declared === "string"
334
+ ? declared
335
+ : inlineStepTargetName(owner, [root, ...stepPathSegments(stepPath).slice(1)], step.name);
336
+ step[body.invokeField] = { kind: inline.kind, name };
337
+ emit(
338
+ buildManifest(
339
+ inline,
340
+ name,
341
+ {
342
+ parentKind: resource.kind,
343
+ parentName: resource.metadata.name,
344
+ pathFromParent: `${stepPath}.${body.invokeField}`,
345
+ stepTarget: true,
346
+ ...(outsideScopes.length > 0 ? { outsideScopes } : {}),
347
+ },
348
+ inherit,
349
+ ),
350
+ );
351
+ });
352
+ }
353
+ }
354
+
355
+ /** The runner's step field a `base:` mapping forwards `field` into, when it does
356
+ * so verbatim (`steps: !cel "self.<field>"`); undefined otherwise. */
357
+ function mappedStepField(
358
+ base: unknown,
359
+ field: string,
360
+ runner: ResourceDefinition,
361
+ registry: DefinitionRegistry,
362
+ ): string | undefined {
363
+ if (!base || typeof base !== "object" || Array.isArray(base)) return undefined;
364
+ const runnerSchema = registry.effectiveSchemaOf(runner) as Record<string, any> | undefined;
365
+ if (!runnerSchema) return undefined;
366
+ const runnerSteps = new Set(
367
+ gatherPropertySchemas(runnerSchema)
368
+ .filter(([, schema]) => readStepSlot(schema) !== undefined)
369
+ .map(([key]) => key),
370
+ );
371
+ for (const [target, value] of Object.entries(base as Record<string, unknown>)) {
372
+ if (runnerSteps.has(target) && pureSelfField(value) === field) return target;
373
+ }
374
+ return undefined;
375
+ }
376
+
377
+ /** `f` for a CEL value that is exactly `self.f`, in any spelling it arrives in. */
378
+ function pureSelfField(value: unknown): string | undefined {
379
+ let source: string | undefined;
380
+ if (isTaggedSentinel(value)) {
381
+ if (value.engine === "cel") source = value.source;
382
+ } else if (value && typeof value === "object" && (value as { __compiled?: unknown }).__compiled) {
383
+ const compiled = (value as { source?: unknown }).source;
384
+ if (typeof compiled === "string") source = compiled;
385
+ } else if (typeof value === "string") {
386
+ source = /^\s*\$\{\{([\s\S]*)\}\}\s*$/.exec(value)?.[1];
387
+ }
388
+ return source === undefined ? undefined : /^self\.([A-Za-z_$][\w$]*)$/.exec(source.trim())?.[1];
389
+ }
390
+
391
+ /** `steps[1].cases.a[0]` → `["steps", "1", "cases", "a", "0"]`. Exact even for a
392
+ * case key holding punctuation: the name keeps only alphanumeric runs, so
393
+ * splitting inside a key cannot change it. */
394
+ function stepPathSegments(path: string): string[] {
395
+ return path.split(/[.[\]]/).filter((segment) => segment.length > 0);
396
+ }
397
+
148
398
  /**
149
399
  * Walks `resource` following `fieldPath` (dot notation, `[]` = array traversal).
150
400
  * Mutates the resource in-place: replaces each inline value with `{kind, name}`.
151
401
  * Returns the extracted manifests.
152
- *
153
- * Each extracted manifest carries `metadata.xTeloOrigin` so downstream
154
- * diagnostics can be rerouted back to the parent doc's YAML position:
155
- * - `parentKind` / `parentName` — the resource that owned the inline slot
156
- * - `pathFromParent` — concrete dotted path with `[N]` indices, matching
157
- * `buildPositionIndex` keys (e.g. `routes[0].handler`)
158
402
  */
159
403
  function extractInlinesAtPath(
160
404
  resource: ResourceManifest,
161
405
  fieldPath: string,
162
406
  parentName: string,
163
407
  parentKind: string,
164
- parentModule: string | undefined,
408
+ inherit: Provenance,
409
+ outsideScopes: OutsideScope[],
165
410
  invocationContext?: Record<string, any>,
166
411
  ): ResourceManifest[] {
167
412
  const extracted: ResourceManifest[] = [];
@@ -174,7 +419,18 @@ function extractInlinesAtPath(
174
419
  ): string {
175
420
  const name = sanitizeName([parentName, ...nameSegments].join("_"));
176
421
  extracted.push(
177
- buildManifest(inline, name, parentKind, parentName, concretePath, parentModule, invocationContext),
422
+ buildManifest(
423
+ inline,
424
+ name,
425
+ {
426
+ parentKind,
427
+ parentName,
428
+ pathFromParent: concretePath,
429
+ ...(outsideScopes.length > 0 ? { outsideScopes } : {}),
430
+ },
431
+ inherit,
432
+ invocationContext,
433
+ ),
178
434
  );
179
435
  return name;
180
436
  }
@@ -259,10 +515,8 @@ function extractInlinesAtPath(
259
515
  function buildManifest(
260
516
  inline: Record<string, unknown>,
261
517
  name: string,
262
- parentKind: string,
263
- parentName: string,
264
- pathFromParent: string,
265
- parentModule: string | undefined,
518
+ origin: Origin,
519
+ inherit: Provenance,
266
520
  invocationContext?: Record<string, any>,
267
521
  ): ResourceManifest {
268
522
  const existingMeta =
@@ -275,9 +529,11 @@ function buildManifest(
275
529
  ...existingMeta,
276
530
  name,
277
531
  // Inherit parent module only if the inline doesn't already declare one
278
- ...(parentModule && !existingMeta.module ? { module: parentModule } : {}),
532
+ ...(inherit.module && !existingMeta.module ? { module: inherit.module } : {}),
533
+ ...(inherit.forwarded ? { forwardedInternal: true } : {}),
534
+ ...(inherit.moduleGlobals !== undefined ? { moduleGlobals: inherit.moduleGlobals } : {}),
279
535
  ...(invocationContext ? { xTeloInvocationContext: invocationContext } : {}),
280
- xTeloOrigin: { parentKind, parentName, pathFromParent },
536
+ xTeloOrigin: origin,
281
537
  },
282
538
  } as unknown as ResourceManifest;
283
539
  }