@telorun/analyzer 0.52.0 → 0.54.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 (80) hide show
  1. package/dist/analysis-registry.d.ts +8 -0
  2. package/dist/analysis-registry.d.ts.map +1 -1
  3. package/dist/analysis-registry.js +21 -3
  4. package/dist/analyzer.d.ts +3 -2
  5. package/dist/analyzer.d.ts.map +1 -1
  6. package/dist/analyzer.js +193 -26
  7. package/dist/builtins.d.ts.map +1 -1
  8. package/dist/builtins.js +32 -12
  9. package/dist/call-graph.d.ts +189 -0
  10. package/dist/call-graph.d.ts.map +1 -0
  11. package/dist/call-graph.js +617 -0
  12. package/dist/dependency-graph.d.ts +17 -7
  13. package/dist/dependency-graph.d.ts.map +1 -1
  14. package/dist/dependency-graph.js +36 -65
  15. package/dist/flatten-for-analyzer.d.ts +8 -0
  16. package/dist/flatten-for-analyzer.d.ts.map +1 -1
  17. package/dist/flatten-for-analyzer.js +32 -0
  18. package/dist/index.d.ts +15 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +11 -1
  21. package/dist/manifest-navigation.d.ts +32 -0
  22. package/dist/manifest-navigation.d.ts.map +1 -0
  23. package/dist/manifest-navigation.js +91 -0
  24. package/dist/manifest-visitor.js +1 -1
  25. package/dist/ref-slot.d.ts +125 -0
  26. package/dist/ref-slot.d.ts.map +1 -0
  27. package/dist/ref-slot.js +226 -0
  28. package/dist/reference-field-map.d.ts +15 -1
  29. package/dist/reference-field-map.d.ts.map +1 -1
  30. package/dist/reference-field-map.js +29 -35
  31. package/dist/resolve-schema-ref-kinds.d.ts +4 -0
  32. package/dist/resolve-schema-ref-kinds.d.ts.map +1 -1
  33. package/dist/resolve-schema-ref-kinds.js +31 -8
  34. package/dist/resolve-zone-requirements.d.ts +110 -0
  35. package/dist/resolve-zone-requirements.d.ts.map +1 -0
  36. package/dist/resolve-zone-requirements.js +541 -0
  37. package/dist/types.d.ts +8 -0
  38. package/dist/types.d.ts.map +1 -1
  39. package/dist/validate-module-metadata.d.ts +38 -0
  40. package/dist/validate-module-metadata.d.ts.map +1 -0
  41. package/dist/validate-module-metadata.js +256 -0
  42. package/dist/validate-observed-state.d.ts +14 -13
  43. package/dist/validate-observed-state.d.ts.map +1 -1
  44. package/dist/validate-observed-state.js +21 -88
  45. package/dist/validate-ref-slots.d.ts +48 -0
  46. package/dist/validate-ref-slots.d.ts.map +1 -0
  47. package/dist/validate-ref-slots.js +219 -0
  48. package/dist/validate-references.d.ts.map +1 -1
  49. package/dist/validate-references.js +8 -1
  50. package/dist/validate-zone-slots.d.ts +39 -0
  51. package/dist/validate-zone-slots.d.ts.map +1 -0
  52. package/dist/validate-zone-slots.js +114 -0
  53. package/dist/zone-module-documents.d.ts +27 -0
  54. package/dist/zone-module-documents.d.ts.map +1 -0
  55. package/dist/zone-module-documents.js +1 -0
  56. package/dist/zone-slot.d.ts +61 -0
  57. package/dist/zone-slot.d.ts.map +1 -0
  58. package/dist/zone-slot.js +91 -0
  59. package/package.json +3 -3
  60. package/src/analysis-registry.ts +20 -2
  61. package/src/analyzer.ts +211 -24
  62. package/src/builtins.ts +32 -12
  63. package/src/call-graph.ts +827 -0
  64. package/src/dependency-graph.ts +34 -68
  65. package/src/flatten-for-analyzer.ts +32 -0
  66. package/src/index.ts +51 -0
  67. package/src/manifest-navigation.ts +91 -0
  68. package/src/manifest-visitor.ts +1 -1
  69. package/src/ref-slot.ts +273 -0
  70. package/src/reference-field-map.ts +39 -36
  71. package/src/resolve-schema-ref-kinds.ts +34 -7
  72. package/src/resolve-zone-requirements.ts +781 -0
  73. package/src/types.ts +8 -0
  74. package/src/validate-module-metadata.ts +335 -0
  75. package/src/validate-observed-state.ts +26 -92
  76. package/src/validate-ref-slots.ts +293 -0
  77. package/src/validate-references.ts +8 -1
  78. package/src/validate-zone-slots.ts +175 -0
  79. package/src/zone-module-documents.ts +27 -0
  80. package/src/zone-slot.ts +116 -0
package/src/analyzer.ts CHANGED
@@ -22,6 +22,7 @@ import {
22
22
  PERMISSIVE_CONTRACT,
23
23
  resolveContract,
24
24
  } from "./invocation-contract.js";
25
+ import { buildCallGraph } from "./call-graph.js";
25
26
  import { buildDependencyGraph, formatCycle } from "./dependency-graph.js";
26
27
  import {
27
28
  buildKernelGlobalsSchema,
@@ -42,6 +43,13 @@ import { normalizeInlineResources } from "./normalize-inline-resources.js";
42
43
  import { REF_VALIDATION_SKIP_KINDS } from "./system-kinds.js";
43
44
  import { resolveRefSentinels } from "./resolve-ref-sentinels.js";
44
45
  import { resolveSchemaRefKinds, type RefConstraintIssue } from "./resolve-schema-ref-kinds.js";
46
+ import { runZoneAnalysis, type ZoneExportCache } from "./resolve-zone-requirements.js";
47
+ import { validateZoneSlotDeclarations, type ZoneSlotIssue } from "./validate-zone-slots.js";
48
+ import {
49
+ validateDynamicSelectors,
50
+ validateRefSlotDeclarations,
51
+ type RefSlotIssue,
52
+ } from "./validate-ref-slots.js";
45
53
  import { resolveSchemaTypeRefs } from "./resolve-schema-type-refs.js";
46
54
  import { validateSchemaTypeRefs } from "./validate-schema-type-refs.js";
47
55
  import { rewriteSyntheticOrigins } from "./rewrite-synthetic-origins.js";
@@ -76,6 +84,7 @@ import {
76
84
  import { validateExtends } from "./validate-extends.js";
77
85
  import { validateLogging } from "./validate-logging.js";
78
86
  import { validateModuleArtifact } from "./validate-module-artifact.js";
87
+ import { validateModuleMetadata } from "./validate-module-metadata.js";
79
88
  import { validateBaseMapping } from "./validate-base-mapping.js";
80
89
  import { validateInvocationContract } from "./validate-invocation-contract.js";
81
90
  import { collectStepInputIssues } from "./validate-step-inputs.js";
@@ -520,24 +529,76 @@ function buildStepContextSchema(
520
529
  return undefined;
521
530
  }
522
531
 
523
- /**
524
- * Capabilities whose instances structurally expose no `invoke`/`run` method, so
525
- * a step `invoke` of one always fails at runtime with ERR_RESOURCE_NOT_INVOKABLE
526
- * (kernel dispatch checks method presence, not capability — evaluation-context.ts).
527
- * `Telo.Service` is intentionally absent: some services are invocable (a function
528
- * handler dispatched directly, e.g. `Lambda.Function`), so it can't be rejected
529
- * statically without false positives. This is the sound subset of the runtime rule.
530
- */
531
532
  /** The built-in namespace: globally resolvable, crossing no import boundary. */
532
533
  const TELO_BUILTIN_MODULE = "Telo";
533
534
 
534
- const NON_INVOKABLE_CAPABILITIES = new Set([
535
- "Telo.Provider",
535
+ /** One mapping from a ref-slot issue to a diagnostic — the shape is identical
536
+ * for the schema-level and the manifest-level (dynamic selector) checks. */
537
+ function refSlotIssueDiagnostic(issue: RefSlotIssue): AnalysisDiagnostic {
538
+ return {
539
+ severity: DiagnosticSeverity.Error,
540
+ code: issue.code,
541
+ source: SOURCE,
542
+ message: issue.message,
543
+ data: {
544
+ resource: { kind: issue.manifest.kind, name: issue.manifest.metadata?.name as string },
545
+ filePath: (issue.manifest.metadata as { source?: string } | undefined)?.source,
546
+ path: issue.path,
547
+ },
548
+ };
549
+ }
550
+
551
+ /**
552
+ * Is this kind acceptable in a slot that transfers control?
553
+ *
554
+ * The executable side is a POSITIVE test against the `Telo.Executable`
555
+ * hierarchy: a capability that is, or `extends`, `Telo.Executable` is
556
+ * executable, so a future executable capability opts in with one `extends:`
557
+ * edge — no name list here to keep in step. This replaced
558
+ * `NON_INVOKABLE_CAPABILITIES`, a maintained set of capabilities the analyzer
559
+ * believed could never be invoked, which was unsound in the direction that
560
+ * rejects working manifests: it listed `Telo.Provider`, and `Ai.Model` is a
561
+ * Provider the agent controller invokes directly — the divergence being
562
+ * structural versus nominal, since the kernel tests method presence at dispatch
563
+ * while any static test can only read a declared capability.
564
+ *
565
+ * Outside the hierarchy nothing is guessed: `Telo.Provider` (entry points by
566
+ * convention) and `Telo.Service` (some services are invocable) pass, an unknown
567
+ * capability passes (rejecting it would be the wrong polarity for third-party
568
+ * extensions), and only the kernel-owned capabilities whose CONTRACT has no
569
+ * entry point — `Telo.Type`, `Telo.Mount`, `Telo.Template`, `Telo.Sink` (a sink
570
+ * is written to through a direct contract on the controller instance, never
571
+ * dispatched) — are rejected. That last set is the capabilities' own
572
+ * definition, not a belief about controllers.
573
+ */
574
+ function isExecutableKind(kind: string, defs: DefinitionRegistry, aliases: AliasResolver): boolean {
575
+ const canonical = aliases.resolveKind(kind) ?? kind;
576
+ const def = defs.resolve(canonical);
577
+ if (!def) return true;
578
+ const capability = def.capability as string | undefined;
579
+ if (!capability) return true;
580
+ if (capabilityExtendsExecutable(capability, defs)) return true;
581
+ return !NO_ENTRY_POINT_CAPABILITIES.has(capability);
582
+ }
583
+
584
+ /** Does this capability name `Telo.Executable` or extend it, transitively?
585
+ * Derived from the abstract hierarchy at call time, never from a name list. */
586
+ function capabilityExtendsExecutable(capability: string, defs: DefinitionRegistry): boolean {
587
+ let current: string | undefined = capability;
588
+ const seen = new Set<string>();
589
+ while (current && !seen.has(current)) {
590
+ if (current === "Telo.Executable") return true;
591
+ seen.add(current);
592
+ current = defs.resolve(current)?.extends as string | undefined;
593
+ }
594
+ return false;
595
+ }
596
+
597
+ /** Kernel-owned capabilities whose contract declares no entry point. */
598
+ const NO_ENTRY_POINT_CAPABILITIES = new Set([
536
599
  "Telo.Mount",
537
600
  "Telo.Type",
538
601
  "Telo.Template",
539
- // A sink is written to through a direct contract on the controller instance,
540
- // never dispatched — so invoking one is statically wrong.
541
602
  "Telo.Sink",
542
603
  ]);
543
604
 
@@ -556,9 +617,10 @@ const NON_INVOKABLE_CAPABILITIES = new Set([
556
617
  * *kind* instead of an exported instance (`!ref Stream.Of`) — is a still-a-
557
618
  * sentinel after Phase 2.5 resolution → `UNRESOLVED_REFERENCE` (runtime
558
619
  * `ERR_RESOURCE_NOT_FOUND`).
559
- * - Invokability: a resolved instance whose capability structurally has no
560
- * invoke/run method (`NON_INVOKABLE_CAPABILITIES`) `REFERENCE_KIND_MISMATCH`
561
- * (runtime `ERR_RESOURCE_NOT_INVOKABLE`).
620
+ * - Invokability: a resolved instance whose kind fails `isExecutableKind`
621
+ * outside the `Telo.Executable` hierarchy AND declaring a capability whose
622
+ * contract has no entry point → `REFERENCE_KIND_MISMATCH` (runtime
623
+ * `ERR_RESOURCE_NOT_INVOKABLE`).
562
624
  *
563
625
  * Generic and topology-driven — it walks steps via the same `x-telo-step-context`
564
626
  * / `x-telo-topology-role` annotations `buildStepContextSchema` uses (through the
@@ -655,15 +717,13 @@ function validateStepInvokeReferences(
655
717
  // Resolved `{kind, name}` (or an inline `{kind, …}` definition) — the
656
718
  // instance exists. Mirror the kernel's ERR_RESOURCE_NOT_INVOKABLE, which
657
719
  // fires when the instance has neither an `invoke` nor a `run` method
658
- // (evaluation-context.ts). That is a per-instance property, not a pure
659
- // capability, so only capabilities that STRUCTURALLY expose no such method
660
- // are rejected statically — Service is intentionally excluded, since some
661
- // services are invocable (e.g. a function handler dispatched directly).
720
+ // (evaluation-context.ts). That is a per-instance property, so the static
721
+ // test only rejects a kind whose declared capability names no entry point.
662
722
  if (!value || typeof value !== "object" || Array.isArray(value)) return;
663
723
  const kind = (value as Record<string, unknown>).kind;
664
724
  if (typeof kind !== "string") return;
665
- const capability = defs.resolve(aliases.resolveKind(kind) ?? kind)?.capability;
666
- if (typeof capability === "string" && NON_INVOKABLE_CAPABILITIES.has(capability)) {
725
+ if (!isExecutableKind(kind, defs, aliases)) {
726
+ const capability = defs.resolve(aliases.resolveKind(kind) ?? kind)?.capability;
667
727
  diagnostics.push({
668
728
  severity: DiagnosticSeverity.Error,
669
729
  code: "REFERENCE_KIND_MISMATCH",
@@ -985,6 +1045,7 @@ export class StaticAnalyzer {
985
1045
  manifests: ResourceManifest[],
986
1046
  options?: AnalysisOptions,
987
1047
  registry?: AnalysisRegistry,
1048
+ zoneExportCache?: ZoneExportCache,
988
1049
  ): AnalysisDiagnostic[] {
989
1050
  assertManifestPositions(manifests);
990
1051
  const diagnostics: AnalysisDiagnostic[] = [];
@@ -1163,6 +1224,8 @@ export class StaticAnalyzer {
1163
1224
  // of alias choices. `capability` covers the legacy implements-this-abstract overload;
1164
1225
  // `extends` is the canonical first-class form.
1165
1226
  const refConstraintIssues: RefConstraintIssue[] = [];
1227
+ const refSlotIssues: RefSlotIssue[] = [];
1228
+ const zoneSlotIssues: ZoneSlotIssue[] = [];
1166
1229
  for (const m of manifests) {
1167
1230
  if (m.kind !== "Telo.Definition" && m.kind !== "Telo.Abstract") continue;
1168
1231
  const def = m as unknown as ResourceDefinition;
@@ -1179,10 +1242,38 @@ export class StaticAnalyzer {
1179
1242
  // still on the deprecated form — or with a constraint that no longer
1180
1243
  // resolves — is not the consumer's to fix, and every import would
1181
1244
  // otherwise flood `telo check` with unactionable noise.
1182
- if (!ownModule || rootModules.has(ownModule)) refConstraintIssues.push(...issues);
1245
+ if (!ownModule || rootModules.has(ownModule)) {
1246
+ refConstraintIssues.push(...issues);
1247
+ refSlotIssues.push(...validateRefSlotDeclarations(m as unknown as ResourceManifest));
1248
+ zoneSlotIssues.push(...validateZoneSlotDeclarations(m as unknown as ResourceManifest));
1249
+ }
1183
1250
  const resolvedCapability = def.capability
1184
1251
  ? (scopeResolver.resolveKind(def.capability) ?? def.capability)
1185
1252
  : def.capability;
1253
+ // `Telo.Executable` is a slot constraint — the x-telo-ref parent of
1254
+ // Invocable and Runnable — and names no lifecycle role. The kernel
1255
+ // rejects it at load; without this the analyzer would report "no issues"
1256
+ // on a manifest that cannot boot.
1257
+ if (
1258
+ resolvedCapability === "Telo.Executable" &&
1259
+ (!ownModule || rootModules.has(ownModule))
1260
+ ) {
1261
+ diagnostics.push({
1262
+ severity: DiagnosticSeverity.Error,
1263
+ code: "CAPABILITY_NOT_DECLARABLE",
1264
+ source: SOURCE,
1265
+ message:
1266
+ `'${def.metadata?.name}' declares capability: Telo.Executable, which is an ` +
1267
+ `x-telo-ref slot constraint (the parent Telo.Invocable and Telo.Runnable extend), ` +
1268
+ `not a declarable lifecycle role. Declare 'Telo.Invocable' (invoke) or ` +
1269
+ `'Telo.Runnable' (run) instead.`,
1270
+ data: {
1271
+ resource: { kind: m.kind, name: m.metadata?.name as string },
1272
+ filePath: (m.metadata as { source?: string } | undefined)?.source,
1273
+ path: "capability",
1274
+ },
1275
+ });
1276
+ }
1186
1277
  const resolvedExtends = def.extends
1187
1278
  ? (scopeResolver.resolveKind(def.extends) ?? def.extends)
1188
1279
  : def.extends;
@@ -1212,6 +1303,24 @@ export class StaticAnalyzer {
1212
1303
  };
1213
1304
  const filePath = (issue.manifest.metadata as { source?: string } | undefined)?.source;
1214
1305
  const data = { resource, filePath, path: issue.path };
1306
+ if (issue.annotation === "zone") {
1307
+ // Mirrors X_TELO_REF_UNRESOLVED: an unresolvable provider kind would
1308
+ // leave the requirement silently unenforced — no provider ever
1309
+ // matches a kind that does not exist.
1310
+ diagnostics.push({
1311
+ severity: DiagnosticSeverity.Error,
1312
+ code: "ZONE_PROVIDER_UNRESOLVED",
1313
+ source: SOURCE,
1314
+ message:
1315
+ `x-telo-requires-zone '${issue.ref}' at '${issue.path}' names no kind. The prefix ` +
1316
+ `must be an import alias declared in this file's 'imports:' map, 'Self' for a kind ` +
1317
+ `in this library, or 'Telo' for a built-in. An unresolvable zone would leave the ` +
1318
+ `requirement silently unenforced. Known aliases: ` +
1319
+ `${issue.knownAliases?.join(", ") || "(none)"}.`,
1320
+ data,
1321
+ });
1322
+ continue;
1323
+ }
1215
1324
  if (issue.reason === "legacy") {
1216
1325
  diagnostics.push({
1217
1326
  severity: DiagnosticSeverity.Warning,
@@ -1252,6 +1361,30 @@ export class StaticAnalyzer {
1252
1361
  }
1253
1362
  }
1254
1363
  diagnostics.push(...validateReferenceForms(manifests, defs, aliases, aliasesByModule));
1364
+ // The x-telo-ref annotation's own validity — the strict half of the
1365
+ // accessor split; `readRefSlot` stays lenient so surfaces keep working
1366
+ // mid-migration, and this reports what leniency would silently absorb.
1367
+ for (const issue of refSlotIssues) diagnostics.push(refSlotIssueDiagnostic(issue));
1368
+ // Same split for the two zone annotations. Unreadable ones fail in
1369
+ // OPPOSITE directions — a dropped requirement is silently unenforced, a
1370
+ // dropped provision invents failures — so neither can be left to
1371
+ // leniency.
1372
+ for (const issue of zoneSlotIssues) {
1373
+ diagnostics.push({
1374
+ severity: DiagnosticSeverity.Error,
1375
+ code: issue.code,
1376
+ source: SOURCE,
1377
+ message: issue.message,
1378
+ data: {
1379
+ resource: {
1380
+ kind: issue.manifest.kind,
1381
+ name: issue.manifest.metadata?.name as string,
1382
+ },
1383
+ filePath: (issue.manifest.metadata as { source?: string } | undefined)?.source,
1384
+ path: issue.path,
1385
+ },
1386
+ });
1387
+ }
1255
1388
  }
1256
1389
 
1257
1390
  // Phase 2: extract inline resources from x-telo-ref slots into first-class manifests
@@ -1263,6 +1396,55 @@ export class StaticAnalyzer {
1263
1396
  // original and inline-extracted manifests have their sentinels resolved.
1264
1397
  resolveRefSentinels(allManifests, aliases, aliasesByModule, [], defs);
1265
1398
 
1399
+ // ONE typed reference graph per analysis. Both graph consumers in this
1400
+ // pass (the dynamic-selector check and run-reachability) read the same
1401
+ // build — constructing it per consumer is strictly more work than the
1402
+ // walks it replaced, and performance is a core goal. Lazy, so a
1403
+ // skipValidation pass with no observed state builds nothing.
1404
+ let callGraphMemo: ReturnType<typeof buildCallGraph> | undefined;
1405
+ const getCallGraph = () =>
1406
+ (callGraphMemo ??= buildCallGraph(allManifests as unknown as ResourceManifest[], defs, {
1407
+ aliases,
1408
+ aliasesByModule,
1409
+ }));
1410
+
1411
+ // A `use` case map's selector written in CEL is a hard diagnostic — a call
1412
+ // graph known only at runtime is not statically analyzable, and no fallback
1413
+ // is conservative for every consumer. Scoped to the entry's own modules:
1414
+ // a published dependency's manifest is not the consumer's to fix.
1415
+ if (!options?.skipValidation) {
1416
+ for (const issue of validateDynamicSelectors(
1417
+ allManifests as unknown as ResourceManifest[],
1418
+ defs,
1419
+ aliases,
1420
+ aliasesByModule,
1421
+ getCallGraph(),
1422
+ )) {
1423
+ const ownModule = (issue.manifest.metadata as { module?: string } | undefined)?.module;
1424
+ if (ownModule && !rootModules.has(ownModule)) continue;
1425
+ diagnostics.push(refSlotIssueDiagnostic(issue));
1426
+ }
1427
+
1428
+ // Zone requirements — a projection over the same graph: propagate along
1429
+ // `call` edges, discharge at providing slots under correlation, fire at
1430
+ // terminating edges and boot. Imported libraries' export contracts are
1431
+ // derived over their full documents (options.moduleDocuments — the
1432
+ // flattened view no longer holds their internal dispatch chains), cached
1433
+ // per library in the host-lifetime `zoneExportCache`.
1434
+ diagnostics.push(
1435
+ ...runZoneAnalysis({
1436
+ manifests: allManifests as unknown as ResourceManifest[],
1437
+ graph: getCallGraph(),
1438
+ defs,
1439
+ aliases,
1440
+ aliasesByModule,
1441
+ rootModules,
1442
+ moduleDocuments: options?.moduleDocuments,
1443
+ cache: zoneExportCache,
1444
+ }),
1445
+ );
1446
+ }
1447
+
1266
1448
  // Phase 2.6: register each named `Telo.Type` resource's schema under its
1267
1449
  // canonical module-scoped id (`telo://<module>/<name>`), validate
1268
1450
  // `telo://Self|Alias/Type` schema refs resolve to one, then rewrite those
@@ -1315,6 +1497,10 @@ export class StaticAnalyzer {
1315
1497
  // would otherwise fail on a consumer's machine — or, for a mistyped platform
1316
1498
  // axis, silently offer one platform's binary to every host.
1317
1499
  diagnostics.push(...validateModuleArtifact(allManifests));
1500
+ // The descriptive `metadata:` surface. Nothing in the kernel branches on
1501
+ // these fields, which is precisely why they need a check: a mistyped one
1502
+ // has no runtime failure mode that would ever surface it.
1503
+ diagnostics.push(...validateModuleMetadata(allManifests, defs, aliases));
1318
1504
  }
1319
1505
  resolveSchemaTypeRefs(allManifests, aliases, aliasesByModule);
1320
1506
 
@@ -1422,7 +1608,7 @@ export class StaticAnalyzer {
1422
1608
  const observedState = buildObservedStateIndex(allManifests, defs, aliases, moduleScopes);
1423
1609
  const reportsObservedState = [...observedState.values()].some((r) => r.status);
1424
1610
  const runReachable = reportsObservedState
1425
- ? collectRunReachableNames(allManifests, defs, aliases)
1611
+ ? collectRunReachableNames(getCallGraph())
1426
1612
  : new Set<string>();
1427
1613
 
1428
1614
  // Build typed kernel globals schema so x-telo-context chain validation
@@ -2153,8 +2339,9 @@ export class StaticAnalyzer {
2153
2339
  manifests: ResourceManifest[],
2154
2340
  options?: AnalysisOptions,
2155
2341
  registry?: AnalysisRegistry,
2342
+ zoneExportCache?: ZoneExportCache,
2156
2343
  ): AnalysisDiagnostic[] {
2157
- return this.analyze(manifests, options, registry).filter(
2344
+ return this.analyze(manifests, options, registry, zoneExportCache).filter(
2158
2345
  (d) => d.severity === DiagnosticSeverity.Error,
2159
2346
  );
2160
2347
  }
package/src/builtins.ts CHANGED
@@ -145,7 +145,9 @@ const ROOT_LOGGING_SCHEMA = {
145
145
  type: "array",
146
146
  items: {
147
147
  type: "object",
148
- "x-telo-ref": "Telo.LogSink",
148
+ // A sink is written to directly by the logging pipeline, never through
149
+ // `ctx.invoke` — so from the Application's side it is held, not called.
150
+ "x-telo-ref": { kind: "Telo.LogSink", use: "dependency" },
149
151
  "x-telo-inline": true,
150
152
  },
151
153
  },
@@ -155,9 +157,22 @@ const ROOT_LOGGING_SCHEMA = {
155
157
 
156
158
  export const KERNEL_BUILTINS: ResourceDefinition[] = [
157
159
  { kind: "Telo.Abstract", metadata: { name: "Template", module: "Telo" } },
158
- { kind: "Telo.Abstract", metadata: { name: "Runnable", module: "Telo" } },
160
+ // "Control can be transferred to this" the parent of Invocable and Runnable,
161
+ // and the only thing a slot that accepts either needs to say. It is a SLOT
162
+ // CONSTRAINT, never a lifecycle role: `capability: Telo.Executable` is rejected
163
+ // because it is absent from the kernel's `KNOWN_CAPABILITIES` enum, which is
164
+ // what keeps "what a resource is" and "what a slot does with it" separate.
165
+ //
166
+ // `Telo.Service` is deliberately NOT under it. A Service's `run()` is a
167
+ // lifecycle start the kernel dispatches differently (no ambient scope, so
168
+ // inbound work roots its own trace), and admitting it here would make every
169
+ // step's `invoke:` slot accept a service. Boot-target slots that genuinely take
170
+ // `Runnable | Service` stay kind lists — the honest shape for a heterogeneous
171
+ // set.
172
+ { kind: "Telo.Abstract", metadata: { name: "Executable", module: "Telo" } },
173
+ { kind: "Telo.Abstract", metadata: { name: "Runnable", module: "Telo" }, extends: "Telo.Executable" },
159
174
  { kind: "Telo.Abstract", metadata: { name: "Service", module: "Telo" } },
160
- { kind: "Telo.Abstract", metadata: { name: "Invocable", module: "Telo" } },
175
+ { kind: "Telo.Abstract", metadata: { name: "Invocable", module: "Telo" }, extends: "Telo.Executable" },
161
176
  { kind: "Telo.Abstract", metadata: { name: "Mount", module: "Telo" } },
162
177
  { kind: "Telo.Abstract", metadata: { name: "Type", module: "Telo" } },
163
178
  {
@@ -558,12 +573,16 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
558
573
  "x-telo-step-context": { invoke: "invoke", outputType: "outputType" },
559
574
  type: "array",
560
575
  items: {
576
+ // A genuinely heterogeneous set stays a kind list: `Telo.Service` is
577
+ // deliberately outside `Telo.Executable`, since a service's `run()`
578
+ // is a lifecycle start the kernel dispatches without an ambient
579
+ // scope.
580
+ "x-telo-ref": { kind: ["Telo.Runnable", "Telo.Service"], use: "call" },
561
581
  anyOf: [
562
- { type: "string", "x-telo-ref": "Telo.Runnable" },
563
- { type: "string", "x-telo-ref": "Telo.Service" },
582
+ { type: "string" },
564
583
  // Post-resolution shape that `resolveRefSentinels`
565
584
  // substitutes a `!ref <name>` sentinel into. The
566
- // adjacent `x-telo-ref` constraints govern the kind
585
+ // adjacent `x-telo-ref` constraint governs the kind
567
586
  // check; this branch only admits the structural form so
568
587
  // AJV doesn't reject a resolved ref.
569
588
  {
@@ -583,9 +602,9 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
583
602
  required: ["ref"],
584
603
  properties: {
585
604
  ref: {
605
+ "x-telo-ref": { kind: ["Telo.Runnable", "Telo.Service"], use: "call" },
586
606
  anyOf: [
587
- { type: "string", "x-telo-ref": "Telo.Runnable" },
588
- { type: "string", "x-telo-ref": "Telo.Service" },
607
+ { type: "string" },
589
608
  {
590
609
  type: "object",
591
610
  required: ["kind", "name"],
@@ -617,6 +636,11 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
617
636
  name: { type: "string" },
618
637
  invoke: {
619
638
  "x-telo-topology-role": "invoke",
639
+ "x-telo-ref": {
640
+ kind: "Telo.Executable",
641
+ use: "call",
642
+ inputs: "/inputs",
643
+ },
620
644
  type: "object",
621
645
  required: ["kind", "name"],
622
646
  properties: {
@@ -624,10 +648,6 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
624
648
  name: { type: "string" },
625
649
  },
626
650
  additionalProperties: true,
627
- anyOf: [
628
- { "x-telo-ref": "Telo.Invocable" },
629
- { "x-telo-ref": "Telo.Runnable" },
630
- ],
631
651
  },
632
652
  inputs: {
633
653
  // Same annotation Run.Sequence steps carry: it is what makes