@telorun/analyzer 0.58.0 → 0.60.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 (89) hide show
  1. package/dist/analyzer.d.ts +9 -2
  2. package/dist/analyzer.d.ts.map +1 -1
  3. package/dist/analyzer.js +38 -10
  4. package/dist/builtins.d.ts.map +1 -1
  5. package/dist/builtins.js +16 -42
  6. package/dist/cel-environment.d.ts.map +1 -1
  7. package/dist/cel-environment.js +35 -0
  8. package/dist/flatten-for-analyzer.d.ts +4 -0
  9. package/dist/flatten-for-analyzer.d.ts.map +1 -1
  10. package/dist/flatten-for-analyzer.js +8 -0
  11. package/dist/index.d.ts +6 -3
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +8 -2
  14. package/dist/invocation-contract.d.ts +37 -0
  15. package/dist/invocation-contract.d.ts.map +1 -1
  16. package/dist/invocation-contract.js +81 -1
  17. package/dist/kernel-globals.d.ts +1 -1
  18. package/dist/kernel-globals.d.ts.map +1 -1
  19. package/dist/kernel-globals.js +15 -1
  20. package/dist/manifest-schemas.d.ts +407 -0
  21. package/dist/manifest-schemas.d.ts.map +1 -0
  22. package/dist/manifest-schemas.js +396 -0
  23. package/dist/module-file-claims.d.ts +6 -0
  24. package/dist/module-file-claims.d.ts.map +1 -1
  25. package/dist/module-file-claims.js +4 -0
  26. package/dist/module-metadata-scope.d.ts +44 -0
  27. package/dist/module-metadata-scope.d.ts.map +1 -0
  28. package/dist/module-metadata-scope.js +82 -0
  29. package/dist/parse-loaded-file.d.ts.map +1 -1
  30. package/dist/parse-loaded-file.js +16 -0
  31. package/dist/release/bump-level.d.ts +46 -0
  32. package/dist/release/bump-level.d.ts.map +1 -0
  33. package/dist/release/bump-level.js +80 -0
  34. package/dist/release/changelog.d.ts +36 -0
  35. package/dist/release/changelog.d.ts.map +1 -0
  36. package/dist/release/changelog.js +53 -0
  37. package/dist/release/fragment.d.ts +48 -0
  38. package/dist/release/fragment.d.ts.map +1 -0
  39. package/dist/release/fragment.js +84 -0
  40. package/dist/release/index.d.ts +26 -0
  41. package/dist/release/index.d.ts.map +1 -0
  42. package/dist/release/index.js +18 -0
  43. package/dist/release/ledger.d.ts +53 -0
  44. package/dist/release/ledger.d.ts.map +1 -0
  45. package/dist/release/ledger.js +109 -0
  46. package/dist/release/payload-digest.d.ts +63 -0
  47. package/dist/release/payload-digest.d.ts.map +1 -0
  48. package/dist/release/payload-digest.js +65 -0
  49. package/dist/release/release-plan.d.ts +128 -0
  50. package/dist/release/release-plan.d.ts.map +1 -0
  51. package/dist/release/release-plan.js +277 -0
  52. package/dist/release/version-stamp.d.ts +50 -0
  53. package/dist/release/version-stamp.d.ts.map +1 -0
  54. package/dist/release/version-stamp.js +119 -0
  55. package/dist/release/workspace-config.d.ts +41 -0
  56. package/dist/release/workspace-config.d.ts.map +1 -0
  57. package/dist/release/workspace-config.js +60 -0
  58. package/dist/schema-compat.d.ts +18 -0
  59. package/dist/schema-compat.d.ts.map +1 -1
  60. package/dist/schema-compat.js +71 -7
  61. package/dist/validate-cel-context.d.ts.map +1 -1
  62. package/dist/validate-cel-context.js +69 -13
  63. package/dist/validate-step-inputs.d.ts +1 -1
  64. package/dist/validate-step-inputs.d.ts.map +1 -1
  65. package/dist/validate-step-inputs.js +99 -8
  66. package/package.json +3 -3
  67. package/src/analyzer.ts +41 -10
  68. package/src/builtins.ts +16 -42
  69. package/src/cel-environment.ts +37 -0
  70. package/src/flatten-for-analyzer.ts +12 -0
  71. package/src/index.ts +18 -3
  72. package/src/invocation-contract.ts +92 -1
  73. package/src/kernel-globals.ts +23 -1
  74. package/src/manifest-schemas.ts +408 -0
  75. package/src/module-file-claims.ts +10 -0
  76. package/src/module-metadata-scope.ts +88 -0
  77. package/src/parse-loaded-file.ts +16 -0
  78. package/src/release/bump-level.ts +95 -0
  79. package/src/release/changelog.ts +62 -0
  80. package/src/release/fragment.ts +130 -0
  81. package/src/release/index.ts +66 -0
  82. package/src/release/ledger.ts +142 -0
  83. package/src/release/payload-digest.ts +83 -0
  84. package/src/release/release-plan.ts +392 -0
  85. package/src/release/version-stamp.ts +142 -0
  86. package/src/release/workspace-config.ts +81 -0
  87. package/src/schema-compat.ts +69 -5
  88. package/src/validate-cel-context.ts +82 -13
  89. package/src/validate-step-inputs.ts +109 -9
@@ -0,0 +1,396 @@
1
+ /**
2
+ * Shared JSON-Schema fragments every module's `telo.yaml` may point at with
3
+ * `$ref: "telo://manifest#/$defs/<Name>"`.
4
+ *
5
+ * WHY THE ANALYZER OWNS THEM. These describe manifest structure, which is what
6
+ * this package exists to read — and the layering forces the choice anyway: the
7
+ * editor validates in a browser through `@telorun/analyzer`, and the analyzer
8
+ * must not depend on the kernel. A fragment in the kernel would exist only at
9
+ * runtime, so `telo check` and the editor could not see a step's shape at all.
10
+ * The kernel re-exports these from its own `manifest-schemas` surface, and both
11
+ * halves register the same root document with AJV, so a `$ref` resolves
12
+ * identically in the editor, in `telo check` and at dispatch.
13
+ *
14
+ * A fragment here is STRUCTURE, not a named user type. The two are different
15
+ * mechanisms and both exist:
16
+ *
17
+ * - `#/$defs/<Name>` is private to the declaring kind's own schema — Run's
18
+ * `WhileStep` is Run's business and nothing outside it can name one.
19
+ * - `telo://manifest#/$defs/<Name>` is this set: shapes the kernel defines, so
20
+ * several unrelated documents can agree on one. `builtins.ts` is not a module
21
+ * document and has no `$defs` any module could reach, which is why a shared
22
+ * shape cannot live in one of the modules that use it.
23
+ * - `telo:<module>/<Type>` names a `Telo.JsonSchema` resource a MODULE
24
+ * declared, resolved through the type registry and carrying its owner so two
25
+ * libraries may both declare a `Filter`.
26
+ *
27
+ * Adding a fragment means putting it under `$defs` in {@link ManifestRootSchema}
28
+ * and `$ref`-ing it from module schemas. Browser-safe: no Node built-ins.
29
+ */
30
+ export const MANIFEST_SCHEMA_URI = "telo://manifest";
31
+ /** `$ref` to a fragment in this set, as a module schema writes it. */
32
+ export function manifestFragmentRef(name) {
33
+ return `${MANIFEST_SCHEMA_URI}#/$defs/${name}`;
34
+ }
35
+ /** Schema fragment for a resource-reference slot. The only form a manifest
36
+ * author writes is the `!ref <name>` (or `!ref <Alias>.<name>`) YAML tag,
37
+ * which parses to a `TaggedSentinel` (engine "ref") whose `source` is the
38
+ * bare resource name. In practice module schemas mark a ref slot with a bare
39
+ * `x-telo-ref` annotation (plus, where the slot only ever holds a reference,
40
+ * `type: object` to reject a stray scalar); the analyzer's reference walker
41
+ * reads `x-telo-ref` to look the name up against that constraint, independent
42
+ * of this fragment. A slot opts into this fragment only when it wants this
43
+ * exact two-branch shape enforced at the AJV layer too — it is not required,
44
+ * and slots that also accept an inline value (e.g. `inputType` / `outputType`)
45
+ * deliberately do not use it (an inline JSON Schema has no `kind`).
46
+ *
47
+ * Two `anyOf` branches because the value's shape depends on the phase at
48
+ * which it is validated:
49
+ *
50
+ * 1. The raw `!ref` sentinel — what survives to AJV when a cross-module
51
+ * reference can't be resolved in standalone single-file analysis (the
52
+ * imported module isn't loaded). `substituteCelFields` deliberately
53
+ * keeps the sentinel so this branch matches.
54
+ * 2. A resolved reference object — `{kind, name, alias?}` substituted in
55
+ * place of a sentinel (or an inline definition `{kind, ...config}`
56
+ * reached through a local `$ref` that escapes extraction). Both the
57
+ * kernel and the analyzer validate ref slots *after* sentinel
58
+ * resolution, so this is the shape AJV usually sees.
59
+ *
60
+ * The object-form `{kind, name}` reference a user could once type directly
61
+ * is gone: a plain object at a ref slot is only ever an inline definition
62
+ * or the resolver's own substitution, never an author-written reference.
63
+ * That removal is enforced by the analyzer (it rejects an author-written
64
+ * `{kind, name}` before normalization), not by this schema — branch 2
65
+ * cannot distinguish an author's `{kind, name}` from the resolver's. */
66
+ export const ResourceRefSchema = {
67
+ title: "Resource reference",
68
+ anyOf: [
69
+ {
70
+ type: "object",
71
+ required: ["__tagged", "engine", "source"],
72
+ properties: {
73
+ __tagged: { const: true },
74
+ engine: { const: "ref" },
75
+ source: { type: "string", minLength: 1 },
76
+ },
77
+ additionalProperties: false,
78
+ },
79
+ {
80
+ type: "object",
81
+ required: ["kind"],
82
+ properties: { kind: { type: "string" } },
83
+ additionalProperties: true,
84
+ },
85
+ ],
86
+ };
87
+ /**
88
+ * How a dispatch is re-attempted.
89
+ *
90
+ * ONE shape, because a retry policy is a cross-cutting primitive rather than
91
+ * each composer's own idea: it was written out six times across `run` and
92
+ * `http-client` and had already drifted — one copy declared no defaults, another
93
+ * carried a field the others lacked. Defaults live here, so the Node step leaf
94
+ * and a second-language one read the same numbers instead of each re-deriving
95
+ * them from the other's source.
96
+ *
97
+ * `delay` survives as the older duration-string spelling because published
98
+ * manifests carry it, and it cannot be migrated away: a migration entry writes a
99
+ * SCALAR, and `"250ms"` → `250` is a computation the vocabulary deliberately
100
+ * cannot express.
101
+ */
102
+ export const RetryPolicySchema = {
103
+ title: "Retry policy",
104
+ description: "Re-attempts a failed dispatch with exponential backoff. A domain failure is " +
105
+ "retried; a cancellation and a contract violation are not — the latter is a " +
106
+ "property of the manifest and would fail identically every time.",
107
+ type: "object",
108
+ additionalProperties: false,
109
+ properties: {
110
+ attempts: {
111
+ title: "Attempts",
112
+ description: "Re-attempts after the first try. 0 disables retrying.",
113
+ type: "integer",
114
+ minimum: 0,
115
+ default: 0,
116
+ },
117
+ initialDelay: {
118
+ title: "Initial delay",
119
+ description: "Milliseconds to wait before the first re-attempt.",
120
+ type: "integer",
121
+ minimum: 0,
122
+ default: 250,
123
+ },
124
+ factor: {
125
+ title: "Factor",
126
+ description: "Multiplier applied to the delay after each re-attempt.",
127
+ type: "number",
128
+ minimum: 1,
129
+ default: 2,
130
+ },
131
+ maxDelay: {
132
+ title: "Max delay",
133
+ description: "Ceiling on the delay between re-attempts, in milliseconds.",
134
+ type: "integer",
135
+ minimum: 0,
136
+ default: 32000,
137
+ },
138
+ jitter: {
139
+ title: "Jitter",
140
+ description: "`full` picks each delay uniformly from [0, delay], which is what stops " +
141
+ "work that failed together from re-attempting together.",
142
+ type: "string",
143
+ enum: ["none", "full"],
144
+ default: "full",
145
+ },
146
+ delay: {
147
+ title: "Delay",
148
+ description: "DEPRECATED duration string (`250ms`, `1s`) — read as `initialDelay` when " +
149
+ "that is absent. The pattern is what makes a typo a `telo check` failure " +
150
+ "instead of a silently different backoff.",
151
+ type: "string",
152
+ pattern: "^[0-9]+(\\.[0-9]+)?\\s*(ms|s|m|h)$",
153
+ },
154
+ },
155
+ };
156
+ /**
157
+ * The bare-count spelling of a re-attempt — `Http.Request.retries`, deprecated
158
+ * but carried by every manifest published before `retry:` existed.
159
+ *
160
+ * Its own fragment rather than a special case in the reader: what a consumer
161
+ * needs to know is WHERE the budget is, and pointing at this shape says "the
162
+ * value itself" as precisely as pointing at a policy says "its `attempts`". It
163
+ * cannot be migrated to the policy form — a migration entry writes a scalar, and
164
+ * wrapping one in an object is not something the vocabulary can express.
165
+ */
166
+ export const RetryAttemptsSchema = {
167
+ title: "Retry attempts",
168
+ description: "Re-attempts after the first try. 0 disables retrying.",
169
+ type: "integer",
170
+ minimum: 0,
171
+ default: 0,
172
+ };
173
+ /**
174
+ * A DISPATCH SITE: name a target, pass it arguments, optionally guard it,
175
+ * optionally re-attempt it.
176
+ *
177
+ * The runtime has always had exactly one of these — `InvokeStep` in the SDK, run
178
+ * by `executeInvokeStep`, which every dispatch passes through. What did not exist
179
+ * was the schema half: the shape was hand-restated by each composer (four times
180
+ * in `run`, once in `builtins.ts`) and they drifted, which is why `retry:` worked
181
+ * in a sequence step and was a schema error one line away in `targets:` — not a
182
+ * decision anyone made about boot, just a copy that never grew the field.
183
+ *
184
+ * Owning it here is also what retired `x-telo-retry` for a step: the analyzer
185
+ * reads `step.retry.attempts` because that is what a step IS, rather than
186
+ * discovering a retry-bearing field through a marker the kind had to remember to
187
+ * write.
188
+ *
189
+ * `name` is optional: a boot target only needs one to publish
190
+ * `steps.<name>.result`, and a composer that requires one says so in its own
191
+ * schema. Closed, so a misspelled key is rejected wherever a dispatch is written.
192
+ */
193
+ export const InvokeStepSchema = {
194
+ title: "Invoke step",
195
+ description: "Transfers control to a resource: what to call, what to pass it, whether to " +
196
+ "call it, and how to re-attempt it.",
197
+ type: "object",
198
+ required: ["invoke"],
199
+ additionalProperties: false,
200
+ properties: {
201
+ name: {
202
+ title: "Name",
203
+ description: "Publishes this dispatch's result as `steps.<name>.result`.",
204
+ type: "string",
205
+ },
206
+ invoke: {
207
+ title: "Invoke",
208
+ description: "Resource to invoke.",
209
+ "x-telo-topology-role": "invoke",
210
+ // A reference is always an object (a `!ref` sentinel or its resolved
211
+ // `{kind, name}`); requiring an object rejects a bare-string ref — which
212
+ // `validateReferenceForms` cannot catch at this nested slot — at
213
+ // `telo check` instead of as an obscure runtime failure.
214
+ type: "object",
215
+ "x-telo-ref": {
216
+ kind: "Telo.Executable",
217
+ use: "call",
218
+ inputs: "/inputs",
219
+ },
220
+ },
221
+ inputs: {
222
+ title: "Inputs",
223
+ description: "Values passed to the invoked resource.",
224
+ "x-telo-topology-role": "inputs",
225
+ type: "object",
226
+ additionalProperties: true,
227
+ },
228
+ when: {
229
+ title: "When",
230
+ description: "CEL guard — the dispatch is skipped when it evaluates false.",
231
+ type: "string",
232
+ },
233
+ retry: {
234
+ title: "Retry",
235
+ $ref: `${MANIFEST_SCHEMA_URI}#/$defs/RetryPolicy`,
236
+ },
237
+ },
238
+ };
239
+ /** Recursively freeze, so the fragment set cannot be edited through any of the
240
+ * references handed out. `fragmentFor` clones precisely because downstream
241
+ * passes rewrite schemas in place — `resolveSchemaRefKinds` rewrites the very
242
+ * `x-telo-ref` node `InvokeStep` carries — and a consumer that embedded a
243
+ * fragment WITHOUT cloning would corrupt every later expansion process-wide, in
244
+ * a host that outlives one load (the editor, the LSP). Freezing turns that from
245
+ * a rule someone has to remember into a throw at the write. */
246
+ function deepFreeze(value) {
247
+ if (value && typeof value === "object" && !Object.isFrozen(value)) {
248
+ Object.freeze(value);
249
+ for (const child of Object.values(value))
250
+ deepFreeze(child);
251
+ }
252
+ return value;
253
+ }
254
+ /** Root schema registered with AJV under {@link MANIFEST_SCHEMA_URI}. Carries
255
+ * `$defs` only — it isn't validated against directly. */
256
+ export const ManifestRootSchema = {
257
+ $id: MANIFEST_SCHEMA_URI,
258
+ $defs: {
259
+ ResourceRef: ResourceRefSchema,
260
+ RetryPolicy: RetryPolicySchema,
261
+ RetryAttempts: RetryAttemptsSchema,
262
+ InvokeStep: InvokeStepSchema,
263
+ },
264
+ };
265
+ deepFreeze(ManifestRootSchema);
266
+ /** A private, expanded copy of a fragment, for a consumer that must EMBED one
267
+ * rather than `$ref` it — `builtins.ts` is not a manifest and never passes
268
+ * through the loader, so its dispatch site has to arrive already resolved and
269
+ * already stamped. Cloned for the reason {@link deepFreeze} explains. */
270
+ export function manifestFragment(name) {
271
+ const fragment = ManifestRootSchema.$defs[name];
272
+ if (!fragment || typeof fragment !== "object") {
273
+ throw new Error(`Unknown manifest fragment '${name}'`);
274
+ }
275
+ const copy = structuredClone(fragment);
276
+ expandManifestFragments(copy);
277
+ copy[X_TELO_FRAGMENT] = name;
278
+ return copy;
279
+ }
280
+ const FRAGMENT_PREFIX = `${MANIFEST_SCHEMA_URI}#/$defs/`;
281
+ /**
282
+ * Replace every `telo://manifest#/$defs/<Name>` reference with the fragment
283
+ * itself, in place, throughout a parsed manifest.
284
+ *
285
+ * EXPANDED rather than left as a reference, which is the opposite of what
286
+ * `resolveSchemaTypeRefs` does for a named user type — and for the opposite
287
+ * reasons. A user type must stay a reference because it can recurse and because
288
+ * the compiled-validator cache is keyed on schema identity. These fragments are a
289
+ * closed, non-recursive set the analyzer itself owns, and expanding them is what
290
+ * keeps a composer that points at a shared shape legible to walks that never
291
+ * resolved anything: the CEL-placeholder substitution, the eval-path collector,
292
+ * the editor's field walk. Teaching each of those to follow a reference is the
293
+ * same fix applied N times, and the failure mode when one is missed is silent —
294
+ * a role-driven lookup finds nothing and the check it feeds simply stops
295
+ * reporting.
296
+ *
297
+ * Runs in the shared loader, so both kernels' Node halves and every consumer of a
298
+ * loaded manifest see the same expanded shape.
299
+ */
300
+ export function expandManifestFragments(node, seen = new Set()) {
301
+ if (!node || typeof node !== "object")
302
+ return;
303
+ if (seen.has(node))
304
+ return;
305
+ seen.add(node);
306
+ if (Array.isArray(node)) {
307
+ for (let i = 0; i < node.length; i++) {
308
+ const fragment = fragmentFor(node[i]);
309
+ if (fragment)
310
+ node[i] = fragment;
311
+ else
312
+ expandManifestFragments(node[i], seen);
313
+ }
314
+ return;
315
+ }
316
+ const obj = node;
317
+ for (const [key, value] of Object.entries(obj)) {
318
+ const fragment = fragmentFor(value);
319
+ if (fragment)
320
+ obj[key] = fragment;
321
+ else
322
+ expandManifestFragments(value, seen);
323
+ }
324
+ }
325
+ /**
326
+ * The fragment a node references, expanded and merged with whatever the node
327
+ * declared beside the `$ref`, or undefined when it references none.
328
+ *
329
+ * SIBLINGS ARE MERGED, which draft-07 would ignore — `$ref` is exclusive there,
330
+ * so `{ $ref, properties: {...} }` silently drops the properties and `allOf` is
331
+ * the only standard alternative. `allOf` cannot preserve
332
+ * `additionalProperties: false`, because a branch only ever sees its own
333
+ * `properties`; a kind extending the shared retry policy with one HTTP-specific
334
+ * field would have had to give up a closed schema to do it. Merging at expansion
335
+ * gives the 2019-09 reading — `$ref` composes rather than replaces — on the
336
+ * draft the validators actually run.
337
+ *
338
+ * The node's own keys WIN, and `properties` merge key-wise, so an extension adds
339
+ * fields without restating the shared ones.
340
+ *
341
+ * The result is STAMPED with the fragment it came from. That stamp is what
342
+ * replaced `x-telo-retry`: a consumer asking "does this field declare a
343
+ * re-attempt, and where is the budget" reads which shape the author pointed at,
344
+ * rather than a marker the author had to remember to write beside it. Derived,
345
+ * never authored — the same standing as `metadata.exportedKinds` — and stripped
346
+ * before AJV like every other `x-telo-*` key, so it cannot affect validation.
347
+ *
348
+ * Deep-copied because downstream passes (`resolveSchemaRefKinds`, migrations)
349
+ * rewrite schemas in place, and a shared object would let one manifest's rewrite
350
+ * reach every other manifest that pointed at the same shape.
351
+ */
352
+ function fragmentFor(value) {
353
+ if (!value || typeof value !== "object" || Array.isArray(value))
354
+ return undefined;
355
+ const node = value;
356
+ const ref = node.$ref;
357
+ if (typeof ref !== "string" || !ref.startsWith(FRAGMENT_PREFIX))
358
+ return undefined;
359
+ const name = ref.slice(FRAGMENT_PREFIX.length);
360
+ const fragment = ManifestRootSchema.$defs[name];
361
+ if (!fragment || typeof fragment !== "object")
362
+ return undefined;
363
+ const expanded = structuredClone(fragment);
364
+ // A fragment may reference another (InvokeStep holds a RetryPolicy); the copy
365
+ // is expanded too, so one pass leaves no reference behind.
366
+ expandManifestFragments(expanded);
367
+ for (const [key, own] of Object.entries(node)) {
368
+ if (key === "$ref")
369
+ continue;
370
+ if (key === "properties" && isPlainObject(own) && isPlainObject(expanded.properties)) {
371
+ expanded.properties = { ...expanded.properties, ...own };
372
+ continue;
373
+ }
374
+ if (key === "required" && Array.isArray(own) && Array.isArray(expanded.required)) {
375
+ expanded.required = [...new Set([...expanded.required, ...own])];
376
+ continue;
377
+ }
378
+ expanded[key] = own;
379
+ }
380
+ expanded[X_TELO_FRAGMENT] = name;
381
+ return expanded;
382
+ }
383
+ function isPlainObject(value) {
384
+ return !!value && typeof value === "object" && !Array.isArray(value);
385
+ }
386
+ /** Stamped by {@link expandManifestFragments} with the name of the shared
387
+ * fragment a slot pointed at. Derived, never author-written. */
388
+ export const X_TELO_FRAGMENT = "x-telo-fragment";
389
+ /** The shared fragment a schema node was expanded from, or undefined. The one
390
+ * accessor every consumer reads the stamp through. */
391
+ export function manifestFragmentOf(schema) {
392
+ if (!schema || typeof schema !== "object")
393
+ return undefined;
394
+ const name = schema[X_TELO_FRAGMENT];
395
+ return typeof name === "string" ? name : undefined;
396
+ }
@@ -48,6 +48,12 @@ export type ModuleFileClaim = (ClaimBase & {
48
48
  * `.gitignore`-style globs over the selected files, matched by the
49
49
  * caller, which is the side that knows what was selected. */
50
50
  readonly siblings: readonly string[];
51
+ /** The source `path` was built from (`local_path=`), when the candidate
52
+ * names one. The release path builds the entry point rather than reading
53
+ * a prebuilt file, so it needs the source — and re-deriving it by parsing
54
+ * `origin` would put PURL knowledge back into the consumer, which is
55
+ * exactly what this module exists to hold. */
56
+ readonly localPath?: string;
51
57
  }) | (ClaimBase & {
52
58
  readonly role: "assets";
53
59
  });
@@ -1 +1 @@
1
- {"version":3,"file":"module-file-claims.d.ts","sourceRoot":"","sources":["../src/module-file-claims.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,wBAAwB,EAC9B,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAuC,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAEpG;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,UAAU,SAAS;IACjB;;;uEAGmE;IACnE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;6CACyC;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GACvB,CAAC,SAAS,GAAG;IACX,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC;;kEAE8D;IAC9D,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC,CAAC,GACF,CAAC,SAAS,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC,CAAC;AAiF9C;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,MAAM,EACpB,QAAQ,GAAE,wBAA4C,GACrD,eAAe,EAAE,CAanB"}
1
+ {"version":3,"file":"module-file-claims.d.ts","sourceRoot":"","sources":["../src/module-file-claims.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,wBAAwB,EAC9B,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAuC,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAEpG;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,UAAU,SAAS;IACjB;;;uEAGmE;IACnE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;6CACyC;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GACvB,CAAC,SAAS,GAAG;IACX,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC;;kEAE8D;IAC9D,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC;;;;mDAI+C;IAC/C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC,GACF,CAAC,SAAS,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC,CAAC;AAqF9C;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,MAAM,EACpB,QAAQ,GAAE,wBAA4C,GACrD,eAAe,EAAE,CAanB"}
@@ -40,6 +40,7 @@ function controllerClaims(json) {
40
40
  const entry = parsed.qualifiers?.path;
41
41
  if (typeof entry !== "string" || entry === "")
42
42
  continue;
43
+ const localPath = parsed.qualifiers?.local_path;
43
44
  claims.push({
44
45
  role: "controller",
45
46
  path: normalizeRelative(entry),
@@ -48,6 +49,9 @@ function controllerClaims(json) {
48
49
  .split(",")
49
50
  .map((p) => p.trim())
50
51
  .filter((p) => p !== ""),
52
+ ...(typeof localPath === "string" && localPath !== ""
53
+ ? { localPath: normalizeRelative(localPath) }
54
+ : {}),
51
55
  origin: candidate,
52
56
  });
53
57
  }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * What `module.<field>` may read: the metadata an AUTHOR wrote, never the
3
+ * loader's own stamps.
4
+ *
5
+ * A manifest reaching its own `metadata` is a small binding with one sharp edge.
6
+ * By the time analysis or the runtime sees a module doc, its `metadata` also
7
+ * carries fields nothing authored — `source` and `sourceLine` (the loader's
8
+ * provenance), `module`, `moduleGlobals`, `exportedKinds`, `reExportedKinds`,
9
+ * `forwardedExport` (derived indices the analyzer stamps). Exposing those would
10
+ * publish loader internals as a manifest surface, where they would be read,
11
+ * depended on, and then unchangeable.
12
+ *
13
+ * A DENYLIST rather than an allowlist, because the metadata vocabulary is
14
+ * deliberately open: a module may declare a field the standard library has never
15
+ * heard of, and an allowlist would silently hide it. The stamps, by contrast,
16
+ * are a closed set this repo controls — so the thing that can be enumerated is
17
+ * the thing enumerated.
18
+ */
19
+ /** Fields written by the loader or the analyzer, not by the module's author. */
20
+ export declare const DERIVED_METADATA_FIELDS: ReadonlySet<string>;
21
+ /** The author-written half of a module doc's `metadata`. */
22
+ export declare function authoredModuleMetadata(metadata: Record<string, unknown> | undefined | null): Record<string, unknown>;
23
+ /**
24
+ * The `module` namespace as a JSON Schema, or `undefined` when there is nothing
25
+ * to type it from.
26
+ *
27
+ * **One derivation, two consumers.** `cel-environment.ts` needs CEL type strings
28
+ * and `kernel-globals.ts` needs JSON Schema, and they used to reach the same
29
+ * conclusion through two hand-written ternary chains that had to agree forever
30
+ * about which values are open and which are closed. The schema is the richer of
31
+ * the two shapes, so it is what is derived; the CEL side converts with
32
+ * `jsonSchemaToCelType`, which every other namespace already goes through.
33
+ *
34
+ * Typed from the VALUES because a module doc's metadata is literals, not a
35
+ * schema map — the module a resource belongs to is fixed, so there is nothing to
36
+ * resolve.
37
+ *
38
+ * `undefined` means **open**, and the distinction matters in the rejecting
39
+ * direction: a set with no module doc must leave `module.*` unconstrained rather
40
+ * than close it over whatever metadata happened to be at hand, or a valid
41
+ * `module.version` becomes a hard error nobody can act on.
42
+ */
43
+ export declare function moduleMetadataSchema(metadata: Record<string, unknown> | undefined | null): Record<string, any> | undefined;
44
+ //# sourceMappingURL=module-metadata-scope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module-metadata-scope.d.ts","sourceRoot":"","sources":["../src/module-metadata-scope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,gFAAgF;AAChF,eAAO,MAAM,uBAAuB,EAAE,WAAW,CAAC,MAAM,CAQtD,CAAC;AAEH,4DAA4D;AAC5D,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,IAAI,GACnD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQzB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,IAAI,GACnD,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAsBjC"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * What `module.<field>` may read: the metadata an AUTHOR wrote, never the
3
+ * loader's own stamps.
4
+ *
5
+ * A manifest reaching its own `metadata` is a small binding with one sharp edge.
6
+ * By the time analysis or the runtime sees a module doc, its `metadata` also
7
+ * carries fields nothing authored — `source` and `sourceLine` (the loader's
8
+ * provenance), `module`, `moduleGlobals`, `exportedKinds`, `reExportedKinds`,
9
+ * `forwardedExport` (derived indices the analyzer stamps). Exposing those would
10
+ * publish loader internals as a manifest surface, where they would be read,
11
+ * depended on, and then unchangeable.
12
+ *
13
+ * A DENYLIST rather than an allowlist, because the metadata vocabulary is
14
+ * deliberately open: a module may declare a field the standard library has never
15
+ * heard of, and an allowlist would silently hide it. The stamps, by contrast,
16
+ * are a closed set this repo controls — so the thing that can be enumerated is
17
+ * the thing enumerated.
18
+ */
19
+ /** Fields written by the loader or the analyzer, not by the module's author. */
20
+ export const DERIVED_METADATA_FIELDS = new Set([
21
+ "source",
22
+ "sourceLine",
23
+ "module",
24
+ "moduleGlobals",
25
+ "exportedKinds",
26
+ "reExportedKinds",
27
+ "forwardedExport",
28
+ ]);
29
+ /** The author-written half of a module doc's `metadata`. */
30
+ export function authoredModuleMetadata(metadata) {
31
+ if (!metadata || typeof metadata !== "object" || Array.isArray(metadata))
32
+ return {};
33
+ const authored = {};
34
+ for (const [key, value] of Object.entries(metadata)) {
35
+ if (DERIVED_METADATA_FIELDS.has(key))
36
+ continue;
37
+ authored[key] = value;
38
+ }
39
+ return authored;
40
+ }
41
+ /**
42
+ * The `module` namespace as a JSON Schema, or `undefined` when there is nothing
43
+ * to type it from.
44
+ *
45
+ * **One derivation, two consumers.** `cel-environment.ts` needs CEL type strings
46
+ * and `kernel-globals.ts` needs JSON Schema, and they used to reach the same
47
+ * conclusion through two hand-written ternary chains that had to agree forever
48
+ * about which values are open and which are closed. The schema is the richer of
49
+ * the two shapes, so it is what is derived; the CEL side converts with
50
+ * `jsonSchemaToCelType`, which every other namespace already goes through.
51
+ *
52
+ * Typed from the VALUES because a module doc's metadata is literals, not a
53
+ * schema map — the module a resource belongs to is fixed, so there is nothing to
54
+ * resolve.
55
+ *
56
+ * `undefined` means **open**, and the distinction matters in the rejecting
57
+ * direction: a set with no module doc must leave `module.*` unconstrained rather
58
+ * than close it over whatever metadata happened to be at hand, or a valid
59
+ * `module.version` becomes a hard error nobody can act on.
60
+ */
61
+ export function moduleMetadataSchema(metadata) {
62
+ const authored = authoredModuleMetadata(metadata);
63
+ const keys = Object.keys(authored);
64
+ if (keys.length === 0)
65
+ return undefined;
66
+ const properties = {};
67
+ for (const key of keys) {
68
+ const value = authored[key];
69
+ properties[key] = Array.isArray(value)
70
+ ? { type: "array" }
71
+ : value !== null && typeof value === "object"
72
+ ? { type: "object", additionalProperties: true }
73
+ : {
74
+ type: typeof value === "number"
75
+ ? "number"
76
+ : typeof value === "boolean"
77
+ ? "boolean"
78
+ : "string",
79
+ };
80
+ }
81
+ return { type: "object", properties, additionalProperties: false };
82
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"parse-loaded-file.d.ts","sourceRoot":"","sources":["../src/parse-loaded-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAKxD,OAAO,KAAK,EAAE,UAAU,EAAc,MAAM,mBAAmB,CAAC;AAEhE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAK5D,MAAM,WAAW,YAAY;IAC3B;4EACwE;IACxE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;0DAGsD;IACtD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,uEAAuE;IACvE,UAAU,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;CACxC;AA4BD,oEAAoE;AACpE,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,YAAY,GACrB,UAAU,CA2DZ"}
1
+ {"version":3,"file":"parse-loaded-file.d.ts","sourceRoot":"","sources":["../src/parse-loaded-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAKxD,OAAO,KAAK,EAAE,UAAU,EAAc,MAAM,mBAAmB,CAAC;AAEhE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAM5D,MAAM,WAAW,YAAY;IAC3B;4EACwE;IACxE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;0DAGsD;IACtD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,uEAAuE;IACvE,UAAU,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;CACxC;AA4BD,oEAAoE;AACpE,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,YAAY,GACrB,UAAU,CA0EZ"}
@@ -3,6 +3,7 @@ import { parseAllDocuments } from "yaml";
3
3
  import { buildCelEnvironment } from "./cel-environment.js";
4
4
  import { migrateManifests, NO_MIGRATIONS } from "./migrations/driver.js";
5
5
  import { buildDocumentPositions } from "./position-metadata.js";
6
+ import { expandManifestFragments } from "./manifest-schemas.js";
6
7
  import { precompileDoc } from "./precompile.js";
7
8
  import { documentToAst } from "./yaml-ast.js";
8
9
  /** Append an actionable hint to raw yaml-parser messages that are otherwise
@@ -57,6 +58,21 @@ export function parseLoadedFile(source, requestedUrl, text, options) {
57
58
  const migrations = options?.migrate
58
59
  ? migrateManifests({ source, manifests, entries: options.migrations })
59
60
  : NO_MIGRATIONS;
61
+ // Shared structural fragments (`telo://manifest#/$defs/InvokeStep`) are
62
+ // expanded for EVERY consumer, deliberately ungated.
63
+ //
64
+ // They are not authoring sugar the way `imports:` or a `!ref` tag is — they are
65
+ // the analyzer's own closed set, and nothing downstream is equipped to meet one
66
+ // unresolved: the editor's schema resolver handles document-local `#/` refs
67
+ // only and THROWS on anything else, so a gated expansion took every canvas that
68
+ // renders a `Run` step down. Round-tripping is unaffected because a save writes
69
+ // the YAML documents the editor holds separately (`saveModuleFromDocuments`),
70
+ // never a manifest object — so unlike `migrate`, no expansion here can reach an
71
+ // author's file.
72
+ for (const manifest of manifests) {
73
+ if (manifest)
74
+ expandManifestFragments(manifest);
75
+ }
60
76
  let env;
61
77
  if (options?.compile) {
62
78
  for (let i = 0; i < manifests.length; i++) {
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The two vocabularies a release speaks, and the arithmetic between them.
3
+ *
4
+ * A **kind** is what an author writes in a fragment (`Added`, `Fixed`, …). It is
5
+ * changie's vocabulary, kept because it drives two things at once: the semantic
6
+ * level of the bump, and the heading the entry lands under in the changelog. A
7
+ * **level** is what the version arithmetic consumes.
8
+ *
9
+ * The mapping is the only place the two meet, and it is deliberately total: an
10
+ * unrecognized kind is refused rather than defaulted, because a typo that
11
+ * degraded to `patch` would silently under-release a breaking change.
12
+ */
13
+ /** Semantic level of a version move. */
14
+ export type BumpLevel = "major" | "minor" | "patch";
15
+ export declare function maxLevel(a: BumpLevel, b: BumpLevel): BumpLevel;
16
+ /**
17
+ * Fragment kinds, in changelog section order, each with the level it induces.
18
+ *
19
+ * `Changed` / `Removed` induce `major`, which the pre-1.0 guard then rejects —
20
+ * they are kept in the vocabulary rather than dropped so the rejection can name
21
+ * what was written and say why, instead of reporting an unknown kind.
22
+ */
23
+ export declare const FRAGMENT_KINDS: {
24
+ readonly Added: "minor";
25
+ readonly Changed: "major";
26
+ readonly Deprecated: "minor";
27
+ readonly Removed: "major";
28
+ readonly Fixed: "patch";
29
+ readonly Security: "patch";
30
+ };
31
+ export type FragmentKind = keyof typeof FRAGMENT_KINDS;
32
+ /** Declaration order, which is also the order sections appear in a changelog
33
+ * release block. */
34
+ export declare const FRAGMENT_KIND_ORDER: readonly FragmentKind[];
35
+ export declare function isFragmentKind(value: unknown): value is FragmentKind;
36
+ export declare function levelOfKind(kind: FragmentKind): BumpLevel;
37
+ export declare function isReleaseVersion(value: unknown): value is string;
38
+ /** Apply `level` to `version`. Throws on a version this system cannot represent,
39
+ * rather than returning something plausible — every caller has already read the
40
+ * value out of a manifest it is about to rewrite. */
41
+ export declare function applyBump(version: string, level: BumpLevel): string;
42
+ /** Numeric comparison, so `0.10.0` sorts after `0.9.0`. Returns a negative
43
+ * number when `a` precedes `b`. An unparseable version sorts last rather than
44
+ * throwing — this is used for display ordering, not for decisions. */
45
+ export declare function compareVersions(a: string, b: string): number;
46
+ //# sourceMappingURL=bump-level.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bump-level.d.ts","sourceRoot":"","sources":["../../src/release/bump-level.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,wCAAwC;AACxC,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAOpD,wBAAgB,QAAQ,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,GAAG,SAAS,CAE9D;AAED;;;;;;GAMG;AACH,eAAO,MAAM,cAAc;;;;;;;CAOmB,CAAC;AAE/C,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,cAAc,CAAC;AAEvD;qBACqB;AACrB,eAAO,MAAM,mBAAmB,EAAE,SAAS,YAAY,EAEpC,CAAC;AAEpB,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAEpE;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,SAAS,CAEzD;AAOD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAEhE;AAED;;sDAEsD;AACtD,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,CAWnE;AAED;;uEAEuE;AACvE,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAS5D"}