@telorun/analyzer 0.67.0 → 0.69.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 (96) hide show
  1. package/dist/analysis-registry.d.ts +12 -0
  2. package/dist/analysis-registry.d.ts.map +1 -1
  3. package/dist/analysis-registry.js +26 -0
  4. package/dist/analyzer.d.ts.map +1 -1
  5. package/dist/analyzer.js +23 -0
  6. package/dist/call-graph.d.ts +80 -1
  7. package/dist/call-graph.d.ts.map +1 -1
  8. package/dist/call-graph.js +145 -12
  9. package/dist/extends-resolution.d.ts +20 -0
  10. package/dist/extends-resolution.d.ts.map +1 -1
  11. package/dist/extends-resolution.js +29 -0
  12. package/dist/import-resolution-diagnostics.d.ts.map +1 -1
  13. package/dist/import-resolution-diagnostics.js +22 -8
  14. package/dist/index.d.ts +8 -7
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +6 -6
  17. package/dist/invocation-contract.d.ts +20 -0
  18. package/dist/invocation-contract.d.ts.map +1 -1
  19. package/dist/invocation-contract.js +74 -0
  20. package/dist/loaded-types.d.ts +4 -4
  21. package/dist/loaded-types.d.ts.map +1 -1
  22. package/dist/manifest-analysis.d.ts +19 -0
  23. package/dist/manifest-analysis.d.ts.map +1 -1
  24. package/dist/manifest-analysis.js +27 -0
  25. package/dist/manifest-loader.d.ts +3 -4
  26. package/dist/manifest-loader.d.ts.map +1 -1
  27. package/dist/manifest-loader.js +4 -5
  28. package/dist/manifest-schemas.d.ts +2 -0
  29. package/dist/manifest-schemas.d.ts.map +1 -1
  30. package/dist/manifest-schemas.js +4 -0
  31. package/dist/module-graph.d.ts +500 -0
  32. package/dist/module-graph.d.ts.map +1 -0
  33. package/dist/module-graph.js +1411 -0
  34. package/dist/reconcile-module-versions.d.ts.map +1 -1
  35. package/dist/reconcile-module-versions.js +10 -11
  36. package/dist/release/release-plan.d.ts +1 -1
  37. package/dist/resolve-zone-containment.d.ts +9 -1
  38. package/dist/resolve-zone-containment.d.ts.map +1 -1
  39. package/dist/resolve-zone-containment.js +34 -6
  40. package/dist/resolve-zone-requirements.d.ts.map +1 -1
  41. package/dist/resolve-zone-requirements.js +4 -2
  42. package/dist/schema-keywords.d.ts +16 -1
  43. package/dist/schema-keywords.d.ts.map +1 -1
  44. package/dist/schema-keywords.js +20 -0
  45. package/dist/sources/default-sources.d.ts +6 -6
  46. package/dist/sources/default-sources.d.ts.map +1 -1
  47. package/dist/sources/default-sources.js +7 -8
  48. package/dist/sources/integrity.d.ts +3 -2
  49. package/dist/sources/integrity.d.ts.map +1 -1
  50. package/dist/sources/integrity.js +26 -3
  51. package/dist/sources/versioned-ref.d.ts +17 -12
  52. package/dist/sources/versioned-ref.d.ts.map +1 -1
  53. package/dist/sources/versioned-ref.js +22 -24
  54. package/dist/telo-version.d.ts +1 -1
  55. package/dist/telo-version.js +1 -1
  56. package/dist/validate-extends.d.ts.map +1 -1
  57. package/dist/validate-extends.js +42 -1
  58. package/dist/validate-sensitive-slots.d.ts +28 -0
  59. package/dist/validate-sensitive-slots.d.ts.map +1 -0
  60. package/dist/validate-sensitive-slots.js +75 -0
  61. package/dist/value-type-keyword.d.ts +1 -1
  62. package/dist/value-type-keyword.d.ts.map +1 -1
  63. package/dist/value-type-keyword.js +1 -0
  64. package/package.json +1 -1
  65. package/src/analysis-registry.ts +37 -0
  66. package/src/analyzer.ts +26 -0
  67. package/src/call-graph.ts +207 -14
  68. package/src/extends-resolution.ts +33 -0
  69. package/src/import-resolution-diagnostics.ts +24 -7
  70. package/src/index.ts +42 -5
  71. package/src/invocation-contract.ts +71 -0
  72. package/src/loaded-types.ts +4 -4
  73. package/src/manifest-analysis.ts +39 -0
  74. package/src/manifest-loader.ts +4 -5
  75. package/src/manifest-schemas.ts +4 -0
  76. package/src/module-graph.ts +2005 -0
  77. package/src/reconcile-module-versions.ts +10 -11
  78. package/src/release/release-plan.ts +1 -1
  79. package/src/resolve-zone-containment.ts +49 -9
  80. package/src/resolve-zone-requirements.ts +7 -2
  81. package/src/schema-keywords.ts +33 -1
  82. package/src/sources/default-sources.ts +7 -8
  83. package/src/sources/integrity.ts +28 -3
  84. package/src/sources/versioned-ref.ts +26 -28
  85. package/src/telo-version.ts +1 -1
  86. package/src/validate-extends.ts +51 -1
  87. package/src/validate-sensitive-slots.ts +109 -0
  88. package/src/value-type-keyword.ts +1 -0
  89. package/dist/sources/module-ref.d.ts +0 -21
  90. package/dist/sources/module-ref.d.ts.map +0 -1
  91. package/dist/sources/module-ref.js +0 -36
  92. package/dist/sources/registry-source.d.ts +0 -14
  93. package/dist/sources/registry-source.d.ts.map +0 -1
  94. package/dist/sources/registry-source.js +0 -45
  95. package/src/sources/module-ref.ts +0 -49
  96. package/src/sources/registry-source.ts +0 -52
package/src/index.ts CHANGED
@@ -84,15 +84,19 @@ export {
84
84
  isInheritedDelegation,
85
85
  mappingFieldFor,
86
86
  needsContractMapping,
87
+ publishedOwnFields,
87
88
  resolveParent,
88
89
  } from "./extends-resolution.js";
89
90
  export type { ContractDirection, DefResolver } from "./extends-resolution.js";
91
+ export { validateSensitiveSlots } from "./validate-sensitive-slots.js";
92
+ export type { SensitiveSlotIssue } from "./validate-sensitive-slots.js";
90
93
  export {
91
94
  defaultBearingPaths,
92
95
  declaredScalarPaths,
93
96
  PERMISSIVE_CONTRACT,
94
97
  resolveContract,
95
98
  resolveContractSchema,
99
+ sensitivePaths,
96
100
  withLiveValuesSkipped,
97
101
  } from "./invocation-contract.js";
98
102
  export type {
@@ -108,7 +112,13 @@ export {
108
112
  RedactionPathError,
109
113
  } from "./redaction-path.js";
110
114
  export type { RedactionSegment } from "./redaction-path.js";
111
- export { buildCallGraph, projectToPairs, resourceId } from "./call-graph.js";
115
+ export {
116
+ buildCallGraph,
117
+ nodeIdFor,
118
+ projectToPairs,
119
+ resolveScopedName,
120
+ resourceId,
121
+ } from "./call-graph.js";
112
122
  export type {
113
123
  BuildCallGraphOptions,
114
124
  CallGraph,
@@ -117,6 +127,31 @@ export type {
117
127
  ResourceGraphNode,
118
128
  StepGraphNode,
119
129
  } from "./call-graph.js";
130
+ export {
131
+ AMBIENT_CAPABILITIES,
132
+ buildModuleGraph,
133
+ contentKey,
134
+ edgeClassOf,
135
+ isAmbientCapability,
136
+ isAmbientHold,
137
+ isOrderedRow,
138
+ isUnwired,
139
+ } from "./module-graph.js";
140
+ export type {
141
+ BuildModuleGraphOptions,
142
+ EdgeClass,
143
+ GraphEdge,
144
+ GraphKind,
145
+ GraphNode,
146
+ GraphPort,
147
+ GraphRegion,
148
+ GraphRow,
149
+ ModuleGraph,
150
+ ModuleGraphDeps,
151
+ NodeOwnership,
152
+ PortSlot,
153
+ RowKind,
154
+ } from "./module-graph.js";
120
155
  export {
121
156
  buildReferenceFieldMap,
122
157
  isRefEntry,
@@ -300,7 +335,12 @@ export { validateDynamicSelectors, validateRefSlotDeclarations } from "./validat
300
335
  export type { RefSlotIssue } from "./validate-ref-slots.js";
301
336
  export { validateValueTypeSlots } from "./validate-value-type-slots.js";
302
337
  export type { ValueTypeSlotIssue } from "./validate-value-type-slots.js";
303
- export { checkSchemaCompatibility, resolveRefIn, selectUnionBranch } from "./schema-compat.js";
338
+ export {
339
+ checkSchemaCompatibility,
340
+ collectProperties,
341
+ resolveRefIn,
342
+ selectUnionBranch,
343
+ } from "./schema-compat.js";
304
344
  export type { CompatibilityResult, ExternalSchemaResolver } from "./schema-compat.js";
305
345
  export {
306
346
  ajvErrorToPath,
@@ -358,7 +398,6 @@ export {
358
398
  } from "./position-metadata.js";
359
399
  export type { DocumentPosition } from "./position-metadata.js";
360
400
  export { HttpSource } from "./sources/http-source.js";
361
- export { RegistrySource } from "./sources/registry-source.js";
362
401
  export { defaultSources } from "./sources/default-sources.js";
363
402
  export {
364
403
  splitIntegrity,
@@ -369,8 +408,6 @@ export {
369
408
  sha256Base64Url,
370
409
  IntegrityError,
371
410
  } from "./sources/integrity.js";
372
- export { parseModuleRef, isRegistryRef } from "./sources/module-ref.js";
373
- export type { ParsedModuleRef } from "./sources/module-ref.js";
374
411
  export { OCI_SCHEME, isOciRef, parseOciRef } from "./sources/oci-ref.js";
375
412
  export type { ParsedOciRef } from "./sources/oci-ref.js";
376
413
  export { parseVersionedRef, withRefVersion } from "./sources/versioned-ref.js";
@@ -334,6 +334,77 @@ export function defaultBearingPaths(
334
334
  return out;
335
335
  }
336
336
 
337
+ /** Every path in `schema` the author marked `x-telo-sensitive: true` — the
338
+ * values a trace payload must carry as `[redacted]` rather than verbatim.
339
+ *
340
+ * It exists because making a credential a dispatched `Telo.Invocable` turns its
341
+ * material into an invoke OUTPUT, and invoke inputs and outputs ride the debug
342
+ * wire on every call under `--inspect` — which is every watch session. The
343
+ * kernel's substring scrubbing does not reach them: it has one call site, the
344
+ * resource-Created event's properties.
345
+ *
346
+ * Declared by the kind that OWNS the contract, so the kernel names no kind and
347
+ * any module opts in — the `x-telo-eval` shape. Exempting "an Http.Credential
348
+ * result" directly would be kind-knowledge in the kernel, and would stop at
349
+ * that one kind while the same token surfaces wherever else a contract carries
350
+ * it.
351
+ *
352
+ * Bounded by the schema's declarations exactly as {@link defaultBearingPaths}
353
+ * is: a contract marking nothing walks nothing at dispatch. The key is kept and
354
+ * only the value replaced, per the logging spec §14 — a payload that silently
355
+ * loses a key reads as a value that was never produced. */
356
+ export function sensitivePaths(
357
+ schema: Record<string, any>,
358
+ resolveRef?: (ref: string) => Record<string, any> | undefined,
359
+ ): string[][] {
360
+ const out: string[][] = [];
361
+
362
+ const walk = (node: unknown, path: string[], chain: readonly object[]): void => {
363
+ if (!node || typeof node !== "object") return;
364
+ let s = node as Record<string, any>;
365
+ if (chain.includes(s)) return;
366
+ if (resolveRef && typeof s.$ref === "string") {
367
+ const target = resolveRef(s.$ref);
368
+ if (!target || chain.includes(target)) return;
369
+ s = { ...target, ...s, $ref: undefined };
370
+ }
371
+ const here = [...chain, node as object];
372
+
373
+ // A marked node is redacted WHOLE, so there is nothing below it to mark:
374
+ // descending would emit paths into a value that is already gone. The EMPTY
375
+ // path is legal and means the whole value — a contract whose entire output
376
+ // is the secret is the simplest shape there is, and refusing it silently
377
+ // (which `path.length > 0` did) left exactly that case unredacted.
378
+ if (s["x-telo-sensitive"] === true) {
379
+ out.push(path);
380
+ return;
381
+ }
382
+
383
+ const properties = s.properties as Record<string, any> | undefined;
384
+ if (properties) {
385
+ for (const [key, child] of Object.entries(properties)) walk(child, [...path, key], here);
386
+ }
387
+ // A map whose VALUES are secrets — `additionalProperties: {x-telo-sensitive}`
388
+ // — is the shape a headers or query bag takes, and it carries no property
389
+ // names to walk. `{}` is the wildcard segment, the `[]` precedent one axis
390
+ // over.
391
+ for (const key of ["additionalProperties", "patternProperties"] as const) {
392
+ const node = s[key];
393
+ if (!node || typeof node !== "object") continue;
394
+ if (key === "additionalProperties") walk(node, [...path, "{}"], here);
395
+ else for (const child of Object.values(node)) walk(child, [...path, "{}"], here);
396
+ }
397
+ for (const branch of ["allOf", "anyOf", "oneOf"] as const) {
398
+ const list = s[branch];
399
+ if (Array.isArray(list)) for (const child of list) walk(child, path, here);
400
+ }
401
+ if (s.items) walk(s.items, [...path, "[]"], here);
402
+ };
403
+
404
+ walk(schema, [], []);
405
+ return out;
406
+ }
407
+
337
408
  /** The scalar form a declared node's value takes at runtime. `int64` and
338
409
  * `double` are the two JSON scalars whose runtime representation is NOT decided
339
410
  * by the value that arrives: a CEL integer is a BigInt and a CEL double a JS
@@ -12,9 +12,9 @@ import type { AstDocument } from "./yaml-ast.js";
12
12
  * cache uses `source` — the URL the source adapter's `read()` returned. */
13
13
  export interface LoadedFile {
14
14
  /** Canonical identity. The URL the source adapter's `read()` returned —
15
- * HTTPS for http/registry, an absolute path for local. */
15
+ * HTTPS for a remote module, an absolute path for local. */
16
16
  source: string;
17
- /** The URL the caller supplied (e.g. registry ref `std/javascript@0.3.0`).
17
+ /** The URL the caller supplied (e.g. `oci://ghcr.io/telorun/javascript@0.3.0`).
18
18
  * Differs from `source` only for adapter-resolved URLs. */
19
19
  requestedUrl: string;
20
20
  /** Raw text exactly as `read()` returned it. */
@@ -65,8 +65,8 @@ export interface LoadedModule {
65
65
  export interface ImportEdge {
66
66
  /** Canonical resolved URL of the target — a key into `modules`. */
67
67
  targetSource: string;
68
- /** The import's `source` exactly as authored — a registry ref, an `oci://`
69
- * or `https://` ref, or a relative path. Version reconciliation keys on
68
+ /** The import's `source` exactly as authored — an `oci://` or `https://`
69
+ * ref, or a relative path. Version reconciliation keys on
70
70
  * this (minus its version), since it names the module's location
71
71
  * independently of what the module declares about itself. */
72
72
  targetRef: string;
@@ -27,7 +27,14 @@ import { CelScopeQuery, type CelScopeQueryContext } from "./cel-scope-query.js";
27
27
  import { DefinitionRegistry } from "./definition-registry.js";
28
28
  import type { ContractDirection } from "./extends-resolution.js";
29
29
  import { analyzerContractScope, resolveContract } from "./invocation-contract.js";
30
+ import { buildCallGraph, type CallGraph } from "./call-graph.js";
30
31
  import { findManifest } from "./find-manifest.js";
32
+ import {
33
+ buildModuleGraph,
34
+ type BuildModuleGraphOptions,
35
+ type ModuleGraph,
36
+ type ModuleGraphDeps,
37
+ } from "./module-graph.js";
31
38
  import { isModuleKind } from "./module-kinds.js";
32
39
 
33
40
  /**
@@ -43,6 +50,8 @@ export interface ManifestRef {
43
50
  export class ManifestAnalysis {
44
51
  private readonly scopes: ModuleScopes;
45
52
  private celScopeQuery: CelScopeQuery | undefined;
53
+ private callGraphMemo: CallGraph | undefined;
54
+ private moduleGraphMemo: ModuleGraph | undefined;
46
55
 
47
56
  constructor(
48
57
  readonly manifests: ResourceManifest[],
@@ -62,6 +71,36 @@ export class ManifestAnalysis {
62
71
  return (this.celScopeQuery ??= new CelScopeQuery(this.manifests, this.ctx));
63
72
  }
64
73
 
74
+ /** What calls what. Built on first use and kept, so the projection and any
75
+ * other consumer over this set share one. */
76
+ private get callGraph(): CallGraph {
77
+ return (this.callGraphMemo ??= buildCallGraph(this.manifests, this.ctx.defs, {
78
+ aliases: this.ctx.aliases,
79
+ aliasesByModule: this.ctx.aliasesByModule,
80
+ }));
81
+ }
82
+
83
+ /**
84
+ * The module graph: boxes, ordered rows and classed edges — the projection an
85
+ * editor draws.
86
+ *
87
+ * Here rather than on the registry because it is the registry AND a manifest
88
+ * set, which is the pairing this class exists to name; a fifth
89
+ * `registry.x(manifests, …)` factory is the accretion naming it stopped. It
90
+ * shares this analysis's call graph rather than building one of its own.
91
+ *
92
+ * `deps` is supplied by the registry, which owns capability resolution — this
93
+ * class holds the manifests, not the rules for reading a kind.
94
+ */
95
+ moduleGraph(deps: ModuleGraphDeps, options: BuildModuleGraphOptions = {}): ModuleGraph {
96
+ return (this.moduleGraphMemo ??= buildModuleGraph(
97
+ this.manifests,
98
+ this.callGraph,
99
+ deps,
100
+ options,
101
+ ));
102
+ }
103
+
65
104
  /** The manifest a `(kind, name)` pair addresses. */
66
105
  resourceFor(kind: string | undefined, name: string | undefined): ResourceManifest | undefined {
67
106
  return findManifest(this.manifests, kind, name);
@@ -146,7 +146,7 @@ export class Loader {
146
146
  * when a URL it has already canonicalised is requested again — kernel
147
147
  * load → boot and the import-controller each ask the loader for the same
148
148
  * modules. Without this fast path every duplicate request re-runs the
149
- * source's `read()` (a `fetch` for `RegistrySource`, a disk read for
149
+ * source's `read()` (a `fetch` for `HttpSource`, a disk read for
150
150
  * `LocalFileSource`). */
151
151
  private readonly urlToSource = new Map<string, string>();
152
152
 
@@ -157,8 +157,7 @@ export class Loader {
157
157
  /** Sources are resolved in order — the first whose `supports(url)` matches
158
158
  * wins. The caller (composition root) decides which concrete sources exist
159
159
  * and supplies them; `defaultSources()` bundles the browser-safe built-ins
160
- * (HTTP + registry) for the common case. `register()` prepends a source at
161
- * runtime. */
160
+ * (HTTP) for the common case. `register()` prepends a source at runtime. */
162
161
  constructor(sources: ManifestSource[] = [], options: LoaderInitOptions = {}) {
163
162
  this.sources = [...sources];
164
163
  this.celEnv = buildCelEnvironment(options.celHandlers);
@@ -181,7 +180,7 @@ export class Loader {
181
180
  // entry are populated in `urlToSource` + `fileCache` in one read.
182
181
  // Callers (kernel.load) immediately call `loadGraph(entryUrl)`
183
182
  // afterwards — without this priming, the entry file would be read
184
- // twice (twice over the network for `RegistrySource`).
183
+ // twice (twice over the network for `HttpSource`).
185
184
  const file = await this.loadFile(url);
186
185
  return file.source;
187
186
  }
@@ -469,7 +468,7 @@ export class Loader {
469
468
 
470
469
  /** Resolve an `import` URL against the file it appears in. Relative /
471
470
  * absolute-path forms run through the owning `ManifestSource`'s
472
- * `resolveRelative`; registry refs and full URLs pass through
471
+ * `resolveRelative`; scheme-qualified refs and full URLs pass through
473
472
  * unchanged. Exposed so the import-controller (and any other
474
473
  * caller-side resolver) lands on the *exact same* canonical URL the
475
474
  * loader used when walking the entry graph — divergent resolution
@@ -241,6 +241,10 @@ export const InvokeStepSchema = {
241
241
  when: {
242
242
  title: "When",
243
243
  description: "CEL guard — the dispatch is skipped when it evaluates false.",
244
+ // The same role `if:` and `while:` carry: it is the expression that
245
+ // decides whether the statement runs, and a surface showing what a step
246
+ // is conditional on should not have to know which keyword spelled it.
247
+ "x-telo-topology-role": "predicate",
244
248
  type: "string",
245
249
  },
246
250
  retry: {