@telorun/analyzer 0.62.0 → 0.63.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 (146) hide show
  1. package/dist/analysis-registry.d.ts.map +1 -1
  2. package/dist/analysis-registry.js +6 -0
  3. package/dist/analyzer.d.ts +1 -1
  4. package/dist/analyzer.d.ts.map +1 -1
  5. package/dist/analyzer.js +343 -13
  6. package/dist/call-graph.d.ts.map +1 -1
  7. package/dist/call-graph.js +2 -7
  8. package/dist/flatten-for-analyzer.js +1 -1
  9. package/dist/import-resolution-diagnostics.d.ts +9 -2
  10. package/dist/import-resolution-diagnostics.d.ts.map +1 -1
  11. package/dist/import-resolution-diagnostics.js +26 -7
  12. package/dist/index.d.ts +15 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +13 -1
  15. package/dist/invocation-contract.d.ts +6 -1
  16. package/dist/invocation-contract.d.ts.map +1 -1
  17. package/dist/invocation-contract.js +27 -4
  18. package/dist/loaded-types.d.ts +12 -3
  19. package/dist/loaded-types.d.ts.map +1 -1
  20. package/dist/manifest-loader.d.ts +0 -1
  21. package/dist/manifest-loader.d.ts.map +1 -1
  22. package/dist/manifest-loader.js +62 -41
  23. package/dist/manifest-schemas.d.ts +699 -0
  24. package/dist/manifest-schemas.d.ts.map +1 -1
  25. package/dist/manifest-schemas.js +306 -14
  26. package/dist/module-compatibility.d.ts +40 -0
  27. package/dist/module-compatibility.d.ts.map +1 -0
  28. package/dist/module-compatibility.js +52 -0
  29. package/dist/ref-slot-reading.d.ts +30 -0
  30. package/dist/ref-slot-reading.d.ts.map +1 -0
  31. package/dist/ref-slot-reading.js +139 -0
  32. package/dist/referrer-rule.d.ts +67 -0
  33. package/dist/referrer-rule.d.ts.map +1 -0
  34. package/dist/referrer-rule.js +96 -0
  35. package/dist/release/index.d.ts +1 -1
  36. package/dist/release/index.d.ts.map +1 -1
  37. package/dist/release/index.js +1 -1
  38. package/dist/release/payload-digest.d.ts +7 -3
  39. package/dist/release/payload-digest.d.ts.map +1 -1
  40. package/dist/release/payload-digest.js +7 -3
  41. package/dist/release/version-stamp.d.ts +16 -0
  42. package/dist/release/version-stamp.d.ts.map +1 -1
  43. package/dist/release/version-stamp.js +52 -0
  44. package/dist/release/workspace-config.d.ts +10 -6
  45. package/dist/release/workspace-config.d.ts.map +1 -1
  46. package/dist/release/workspace-config.js +10 -6
  47. package/dist/resolve-schema-ref-kinds.d.ts +4 -3
  48. package/dist/resolve-schema-ref-kinds.d.ts.map +1 -1
  49. package/dist/resolve-schema-ref-kinds.js +16 -0
  50. package/dist/resolve-throws-union.d.ts.map +1 -1
  51. package/dist/resolve-throws-union.js +4 -3
  52. package/dist/resolve-zone-containment.d.ts +99 -0
  53. package/dist/resolve-zone-containment.d.ts.map +1 -0
  54. package/dist/resolve-zone-containment.js +212 -0
  55. package/dist/resolve-zone-requirements.d.ts +5 -0
  56. package/dist/resolve-zone-requirements.d.ts.map +1 -1
  57. package/dist/resolve-zone-requirements.js +48 -7
  58. package/dist/resource-rule.d.ts +106 -0
  59. package/dist/resource-rule.d.ts.map +1 -0
  60. package/dist/resource-rule.js +236 -0
  61. package/dist/rule-condition.d.ts +50 -0
  62. package/dist/rule-condition.d.ts.map +1 -0
  63. package/dist/rule-condition.js +108 -0
  64. package/dist/schema-keywords.d.ts.map +1 -1
  65. package/dist/schema-keywords.js +20 -0
  66. package/dist/schema-projection.d.ts +171 -0
  67. package/dist/schema-projection.d.ts.map +1 -0
  68. package/dist/schema-projection.js +317 -0
  69. package/dist/step-slot.d.ts +39 -0
  70. package/dist/step-slot.d.ts.map +1 -0
  71. package/dist/step-slot.js +49 -0
  72. package/dist/telo-version.d.ts +1 -1
  73. package/dist/telo-version.js +1 -1
  74. package/dist/validate-cel-context.d.ts.map +1 -1
  75. package/dist/validate-cel-context.js +17 -3
  76. package/dist/validate-durable-regions.d.ts +15 -0
  77. package/dist/validate-durable-regions.d.ts.map +1 -0
  78. package/dist/validate-durable-regions.js +336 -0
  79. package/dist/validate-references.d.ts.map +1 -1
  80. package/dist/validate-references.js +44 -2
  81. package/dist/validate-referrer-rules.d.ts +120 -0
  82. package/dist/validate-referrer-rules.d.ts.map +1 -0
  83. package/dist/validate-referrer-rules.js +255 -0
  84. package/dist/validate-resource-rules.d.ts +114 -0
  85. package/dist/validate-resource-rules.d.ts.map +1 -0
  86. package/dist/validate-resource-rules.js +283 -0
  87. package/dist/validate-schema-projection.d.ts +32 -0
  88. package/dist/validate-schema-projection.d.ts.map +1 -0
  89. package/dist/validate-schema-projection.js +110 -0
  90. package/dist/validate-step-inputs.d.ts +2 -1
  91. package/dist/validate-step-inputs.d.ts.map +1 -1
  92. package/dist/validate-step-inputs.js +5 -3
  93. package/dist/validate-throws-coverage.d.ts.map +1 -1
  94. package/dist/validate-throws-coverage.js +11 -5
  95. package/dist/validate-zone-slots.d.ts +2 -2
  96. package/dist/validate-zone-slots.d.ts.map +1 -1
  97. package/dist/validate-zone-slots.js +202 -6
  98. package/dist/validate-zone-violations.d.ts +12 -0
  99. package/dist/validate-zone-violations.d.ts.map +1 -0
  100. package/dist/validate-zone-violations.js +169 -0
  101. package/dist/value-type-keyword.d.ts +1 -1
  102. package/dist/value-type-keyword.d.ts.map +1 -1
  103. package/dist/value-type-keyword.js +4 -0
  104. package/dist/zone-slot.d.ts +61 -0
  105. package/dist/zone-slot.d.ts.map +1 -1
  106. package/dist/zone-slot.js +86 -5
  107. package/package.json +2 -2
  108. package/src/analysis-registry.ts +6 -0
  109. package/src/analyzer.ts +395 -14
  110. package/src/call-graph.ts +2 -8
  111. package/src/flatten-for-analyzer.ts +1 -1
  112. package/src/import-resolution-diagnostics.ts +25 -7
  113. package/src/index.ts +73 -0
  114. package/src/invocation-contract.ts +45 -2
  115. package/src/loaded-types.ts +12 -3
  116. package/src/manifest-loader.ts +87 -55
  117. package/src/manifest-schemas.ts +328 -14
  118. package/src/module-compatibility.ts +72 -0
  119. package/src/ref-slot-reading.ts +156 -0
  120. package/src/referrer-rule.ts +116 -0
  121. package/src/release/index.ts +1 -0
  122. package/src/release/payload-digest.ts +7 -3
  123. package/src/release/version-stamp.ts +58 -0
  124. package/src/release/workspace-config.ts +10 -6
  125. package/src/resolve-schema-ref-kinds.ts +28 -4
  126. package/src/resolve-throws-union.ts +4 -3
  127. package/src/resolve-zone-containment.ts +329 -0
  128. package/src/resolve-zone-requirements.ts +67 -7
  129. package/src/resource-rule.ts +252 -0
  130. package/src/rule-condition.ts +126 -0
  131. package/src/schema-keywords.ts +24 -0
  132. package/src/schema-projection.ts +403 -0
  133. package/src/step-slot.ts +64 -0
  134. package/src/telo-version.ts +1 -1
  135. package/src/validate-cel-context.ts +17 -3
  136. package/src/validate-durable-regions.ts +401 -0
  137. package/src/validate-references.ts +57 -2
  138. package/src/validate-referrer-rules.ts +373 -0
  139. package/src/validate-resource-rules.ts +401 -0
  140. package/src/validate-schema-projection.ts +184 -0
  141. package/src/validate-step-inputs.ts +5 -3
  142. package/src/validate-throws-coverage.ts +12 -5
  143. package/src/validate-zone-slots.ts +192 -8
  144. package/src/validate-zone-violations.ts +190 -0
  145. package/src/value-type-keyword.ts +4 -0
  146. package/src/zone-slot.ts +114 -5
@@ -0,0 +1,169 @@
1
+ /**
2
+ * `ZONE_ATTRIBUTE_VIOLATED` — a resource inside a region whose guarantee it
3
+ * declares it cannot honour.
4
+ *
5
+ * The third leg of the zone-attribute mechanism, and the one that makes the
6
+ * other two enforceable. `x-telo-provides-zone` says what a region guarantees
7
+ * about its contents and `x-telo-requires-zone` says what a resource needs of
8
+ * the region around it — but neither says *this resource breaks that
9
+ * guarantee*, so until now an attribute could be declared, resolved and read
10
+ * with nothing able to report the one thing it exists to prevent.
11
+ *
12
+ * **Generic over the whole vocabulary, and it names no kind.** The rule is one
13
+ * sentence — *a region declaring attribute A must not contain a resource
14
+ * declaring it violates A* — and it is run once per attribute in
15
+ * `sdk/zone-attributes/`. So `noSuspend` (a parking kind inside a lease) and any
16
+ * attribute added later are covered by the same traversal, and `modules/durable`
17
+ * stays outside the analyzer's surface exactly as the topology-driven constraint
18
+ * requires.
19
+ *
20
+ * **Both sentences are their authors' own.** The diagnostic prints the region's
21
+ * declared reason and the violating kind's declared reason side by side, because
22
+ * the useful message is the collision between two written claims — "this
23
+ * transaction holds a connection a parked run would lose" against "this waits
24
+ * for a delivery that may be days away" — and neither half is anything the
25
+ * analyzer could generate.
26
+ *
27
+ * **Enforced at runtime, warned early**, like every other containment rule: the
28
+ * walk under-approximates (an edge the call graph cannot see is invisible here),
29
+ * and the runtime check in the violating controller is the real enforcement. It
30
+ * is an ERROR rather than a warning nonetheless: where the walk *can* see the
31
+ * path, the placement is decidably wrong — the region's promise and the
32
+ * resource's rebuttal are both declarations, so nothing about the runtime can
33
+ * reconcile them.
34
+ *
35
+ * Browser-safe: no Node built-ins.
36
+ */
37
+ import { SUSPENDING_BACKOFF_MS, ZONE_ATTRIBUTES, retryBackoffMs, } from "@telorun/sdk";
38
+ import { findZoneRegions } from "./resolve-zone-containment.js";
39
+ import { DiagnosticSeverity } from "./types.js";
40
+ import { readViolatesZone } from "./zone-slot.js";
41
+ const SOURCE = "telo";
42
+ export function validateZoneViolations(args) {
43
+ const { graph, resolveDef, reportModules } = args;
44
+ const diagnostics = [];
45
+ const reported = new Set();
46
+ // What each kind in the graph declares it cannot honour, resolved once. A
47
+ // graph holds many instances of few kinds, and this is a schema read.
48
+ const violationsOf = new Map();
49
+ const violations = (kind, module) => {
50
+ const key = `${module ?? ""}\0${kind}`;
51
+ let found = violationsOf.get(key);
52
+ if (!found) {
53
+ const def = resolveDef(kind, module);
54
+ found = readViolatesZone(def?.schema);
55
+ violationsOf.set(key, found);
56
+ }
57
+ return found;
58
+ };
59
+ for (const attribute of ZONE_ATTRIBUTES.keys()) {
60
+ for (const region of findZoneRegions(graph, resolveDef, attribute)) {
61
+ for (const contained of region.contents.values()) {
62
+ // The one step-level rule, and it is separate because it is a property
63
+ // of the STEP rather than of its target: a retry whose backoff is long
64
+ // enough to park suspends the run from inside a region that promised
65
+ // nothing in it would. Only `noSuspend` has such a rule, because only
66
+ // `noSuspend` is broken by waiting.
67
+ if (attribute === "noSuspend" && contained.node.type === "step") {
68
+ const ownerNode = graph.nodes.get(contained.node.owner);
69
+ const owner = ownerNode?.type === "resource" ? ownerNode : undefined;
70
+ if (owner &&
71
+ owned(owner.manifest, reportModules) &&
72
+ suspendingBackoff(contained.node.step.retry)) {
73
+ const dedupe = `retry\0${contained.node.id}`;
74
+ if (!reported.has(dedupe)) {
75
+ reported.add(dedupe);
76
+ diagnostics.push({
77
+ severity: DiagnosticSeverity.Error,
78
+ code: "ZONE_ATTRIBUTE_VIOLATED",
79
+ source: SOURCE,
80
+ message: `Step '${contained.node.name ?? contained.node.path}' declares a retry ` +
81
+ `whose last backoff reaches ${SUSPENDING_BACKOFF_MS}ms, at which point a ` +
82
+ `re-attempt waits by PARKING the run rather than by sleeping — but it is ` +
83
+ `inside a ${region.provider.kind} ` +
84
+ `'${region.provider.name}' region that declares 'noSuspend' ` +
85
+ `(${region.reason}). Shorten the backoff, or move the retry outside the ` +
86
+ `region so it re-attempts the region as a whole.`,
87
+ data: {
88
+ resource: { kind: owner.kind, name: owner.name },
89
+ filePath: owner.manifest.metadata?.source,
90
+ path: contained.node.path,
91
+ },
92
+ });
93
+ }
94
+ }
95
+ }
96
+ // A step node is a dispatch site, not a resource — whatever it reaches
97
+ // is in `contents` in its own right, so judging the step too would
98
+ // report one placement twice.
99
+ if (contained.node.type !== "resource")
100
+ continue;
101
+ const manifest = contained.node.manifest;
102
+ const module = manifest.metadata?.module;
103
+ const rebuttal = violations(contained.node.kind, module)[attribute];
104
+ if (!rebuttal)
105
+ continue;
106
+ if (!owned(manifest, reportModules))
107
+ continue;
108
+ const dedupe = `${region.provider.id}\0${region.slot}\0${contained.node.id}\0${attribute}`;
109
+ if (reported.has(dedupe))
110
+ continue;
111
+ reported.add(dedupe);
112
+ const path = [`${region.provider.name}.${region.slot}`, ...contained.via].join(" → ");
113
+ diagnostics.push({
114
+ severity: DiagnosticSeverity.Error,
115
+ code: "ZONE_ATTRIBUTE_VIOLATED",
116
+ source: SOURCE,
117
+ message: `${contained.node.kind} '${contained.node.name}' is inside a ` +
118
+ `${region.provider.kind} '${region.provider.name}' region that declares ` +
119
+ `'${attribute}' (${region.reason}), but ${contained.node.kind} cannot honour ` +
120
+ `it: ${rebuttal}. Reached by ${path}. Move it outside the region, or use a ` +
121
+ `region that does not make that promise.`,
122
+ data: {
123
+ resource: { kind: contained.node.kind, name: contained.node.name },
124
+ filePath: manifest.metadata?.source,
125
+ },
126
+ });
127
+ }
128
+ }
129
+ }
130
+ return diagnostics;
131
+ }
132
+ /**
133
+ * Does this declared retry policy back off far enough to park?
134
+ *
135
+ * EXACT, not an estimate, and it is the runtime's own arithmetic
136
+ * (`retryBackoffMs`) rather than a second copy of it — a static rule that
137
+ * re-derives what it describes drifts from the behaviour the first time either
138
+ * side gains a knob. It is exact for a second reason too: the runtime branches
139
+ * on the UN-JITTERED backoff, so there is no coin flip for this to
140
+ * approximate. The last attempt has the largest backoff, so it decides.
141
+ *
142
+ * Only a STATICALLY KNOWN policy is judged — the `LIVE_VALUE_RETRIED` posture,
143
+ * and for its reason: a CEL budget says nothing, and guessing would report a
144
+ * conflict against a manifest that may never wait that long. An unreadable
145
+ * policy is left to the runtime check, which sees the real number.
146
+ */
147
+ function suspendingBackoff(raw) {
148
+ if (!raw || typeof raw !== "object")
149
+ return false;
150
+ const policy = raw;
151
+ if (!Object.values(policy).every(isStaticValue))
152
+ return false;
153
+ const attempts = policy.attempts;
154
+ if (typeof attempts !== "number" || attempts <= 0)
155
+ return false;
156
+ return retryBackoffMs(policy, attempts - 1) >= SUSPENDING_BACKOFF_MS;
157
+ }
158
+ /** A CEL leaf reaches here as a compiled value or a tagged sentinel, and either
159
+ * way the policy is not statically known. Refusing the whole policy rather than
160
+ * the one field is the conservative direction: `maxDelay` alone decides the
161
+ * answer, so reading around an unresolved one would report a bound the manifest
162
+ * may never reach. */
163
+ function isStaticValue(value) {
164
+ return value === undefined || typeof value === "number" || typeof value === "string";
165
+ }
166
+ function owned(manifest, reportModules) {
167
+ const module = manifest.metadata?.module;
168
+ return !module || reportModules.size === 0 || reportModules.has(module);
169
+ }
@@ -28,7 +28,7 @@ import type { KeywordDefinition } from "ajv";
28
28
  *
29
29
  * `x-telo-type` is deliberately absent — it is the one that emits code.
30
30
  */
31
- export declare const ANNOTATION_KEYWORDS: readonly ["x-telo-bindings-from", "x-telo-catches-for", "x-telo-context", "x-telo-context-collection-from", "x-telo-context-element-from", "x-telo-context-from", "x-telo-context-from-ref-kind", "x-telo-context-from-root", "x-telo-context-ref-from", "x-telo-error-context", "x-telo-eval", "x-telo-inline", "x-telo-outcome-list", "x-telo-provides-zone", "x-telo-ref", "x-telo-requires-zone", "x-telo-schema-from", "x-telo-scope", "x-telo-step-context", "x-telo-topology-role", "x-telo-value-schema-from", "x-telo-widget"];
31
+ export declare const ANNOTATION_KEYWORDS: readonly ["x-telo-bindings-from", "x-telo-catches-for", "x-telo-context", "x-telo-context-collection-from", "x-telo-context-element-from", "x-telo-context-from", "x-telo-context-from-ref-kind", "x-telo-context-from-root", "x-telo-context-ref-from", "x-telo-error-context", "x-telo-eval", "x-telo-inline", "x-telo-outcome-list", "x-telo-provides-zone", "x-telo-ref", "x-telo-requires-zone", "x-telo-resource-rules", "x-telo-schema-from", "x-telo-schema-map", "x-telo-schema-projection", "x-telo-schema-projection-from", "x-telo-scope", "x-telo-step-context", "x-telo-topology-role", "x-telo-value-schema-from", "x-telo-widget"];
32
32
  /**
33
33
  * The `x-telo-type` keyword.
34
34
  *
@@ -1 +1 @@
1
- {"version":3,"file":"value-type-keyword.d.ts","sourceRoot":"","sources":["../src/value-type-keyword.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;+EAqB+E;AAG/E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,KAAK,CAAC;AAiB7C;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,ygBAuBtB,CAAC;AAEX;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,IAAI,iBAAiB,CA+BpD;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE;IACxC,UAAU,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,UAAU,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC;CACzD,GAAG,IAAI,CAGP"}
1
+ {"version":3,"file":"value-type-keyword.d.ts","sourceRoot":"","sources":["../src/value-type-keyword.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;+EAqB+E;AAG/E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,KAAK,CAAC;AAiB7C;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,onBA2BtB,CAAC;AAEX;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,IAAI,iBAAiB,CA+BpD;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE;IACxC,UAAU,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,UAAU,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC;CACzD,GAAG,IAAI,CAGP"}
@@ -48,7 +48,11 @@ export const ANNOTATION_KEYWORDS = [
48
48
  "x-telo-provides-zone",
49
49
  "x-telo-ref",
50
50
  "x-telo-requires-zone",
51
+ "x-telo-resource-rules",
51
52
  "x-telo-schema-from",
53
+ "x-telo-schema-map",
54
+ "x-telo-schema-projection",
55
+ "x-telo-schema-projection-from",
52
56
  "x-telo-scope",
53
57
  "x-telo-step-context",
54
58
  "x-telo-topology-role",
@@ -10,6 +10,13 @@
10
10
  *
11
11
  * x-telo-provides-zone: true # uncorrelated — the zone is the kind
12
12
  * x-telo-provides-zone: /connection # correlation-key pointer (own field)
13
+ * x-telo-provides-zone: # correlation key + zone attributes
14
+ * key: /connection
15
+ * atomic: a rollback erases writes a journal recorded as done
16
+ * noSuspend: the transaction holds a connection a parked run would lose
17
+ *
18
+ * x-telo-violates-zone: # what this kind cannot honour
19
+ * noSuspend: this waits for a delivery that may be days away
13
20
  *
14
21
  * x-telo-requires-zone: Self.Transaction # uncorrelated string form
15
22
  * x-telo-requires-zone: # object form
@@ -17,6 +24,7 @@
17
24
  * key: [/connection, /transaction/connection] # ordered, first hit wins
18
25
  * reason: the statement would execute outside any transaction
19
26
  */
27
+ import { type ZoneAttributes } from "@telorun/sdk";
20
28
  /** A body slot that establishes the declaring kind's zone when dispatched
21
29
  * through. The zone's identity is always the declaring kind — the annotation
22
30
  * never names one, so provision-on-behalf-of is unrepresentable. */
@@ -25,6 +33,16 @@ export interface ProvidesZoneSlot {
25
33
  * reference the zone carries as its correlation payload. Absent =
26
34
  * uncorrelated (`true`). */
27
35
  key?: string;
36
+ /** What this zone declares about everything executed inside it, keyed by the
37
+ * closed vocabulary's bare names with the author's REASON as each value (see
38
+ * `sdk/zone-attributes/`). Empty for the two scalar spellings, which say
39
+ * nothing about their contents.
40
+ *
41
+ * Read here and interpreted nowhere in this file: an attribute's meaning is
42
+ * entirely its consumer's — the containment walk, the step engine's collapse
43
+ * rule, the parking kinds — exactly as `readRefSlot` hands back `use` without
44
+ * acting on it. */
45
+ attributes: ZoneAttributes;
28
46
  }
29
47
  /** A field declaring that its resource must be reached through a zone. */
30
48
  export interface RequiresZoneSlot {
@@ -38,16 +56,59 @@ export interface RequiresZoneSlot {
38
56
  key: string[];
39
57
  /** The runtime consequence, quoted after the path in diagnostics. */
40
58
  reason?: string;
59
+ /**
60
+ * Zone attributes the satisfying zone must DECLARE — what it must guarantee,
61
+ * as opposed to which kind it is.
62
+ *
63
+ * The two are different questions and the kind test alone cannot answer the
64
+ * second. A kind may extend the required abstract, and so satisfy every kind
65
+ * check, while its body slot omits the attribute the requirer actually depends
66
+ * on — a durable workflow whose body does not declare `replayed` is a zone the
67
+ * durable checks never look inside, and a run parking there parks against
68
+ * nothing. Naming the attribute is what makes the requirement say what it
69
+ * means.
70
+ *
71
+ * Names come from the closed vocabulary (`sdk/zone-attributes/`), so the
72
+ * analyzer reads its own words here and no module's kind is named.
73
+ */
74
+ attributes: string[];
41
75
  }
42
76
  /** Reads a schema node's provides-zone declaration, or undefined when it has
43
77
  * none or the value is malformed (`validate-zone-slots` reports those). */
44
78
  export declare function readProvidesZone(node: Record<string, any> | undefined): ProvidesZoneSlot | undefined;
79
+ /** The attributes the zone a slot provides declares, or an empty record when the
80
+ * node provides no zone. The shape every containment consumer wants, so none of
81
+ * them repeats the `readProvidesZone(...)?.attributes ?? {}` dance. */
82
+ export declare function providedZoneAttributes(node: Record<string, any> | undefined): ZoneAttributes;
45
83
  /** True when the node carries `x-telo-provides-zone` in any shape, valid or not
46
84
  * — the recognition test validation needs before it judges the value. */
47
85
  export declare function hasProvidesZone(node: Record<string, any> | undefined): boolean;
48
86
  /** Reads a schema node's requires-zone declaration, or undefined when it has
49
87
  * none or the value is malformed (`validate-zone-slots` reports those). */
50
88
  export declare function readRequiresZone(node: Record<string, any> | undefined): RequiresZoneSlot | undefined;
89
+ /**
90
+ * What this kind CANNOT honour about a region it is placed inside.
91
+ *
92
+ * The third relation, and the one the other two cannot express. `provides`
93
+ * declares what a region guarantees about its contents; `requires` declares what
94
+ * a resource needs of the region around it. Neither says *this resource breaks
95
+ * that guarantee* — and without it a zone attribute has a promise and no way to
96
+ * name what falsifies it: `noSuspend` would be enforced only when a parking kind
97
+ * happened to reach its runtime check, and `Durable.Sleep` would be
98
+ * indistinguishable from `Durable.Value`, which needs the same journal and parks
99
+ * nothing.
100
+ *
101
+ * Declared at the kind's SCHEMA ROOT, because it is a property of the kind
102
+ * rather than of one of its slots — a kind that suspends suspends however it is
103
+ * configured.
104
+ *
105
+ * Values are the author's REASON, exactly as on `provides`: a diagnostic prints
106
+ * the region's promise and this resource's rebuttal side by side, and both are
107
+ * their own authors' words.
108
+ */
109
+ export declare function readViolatesZone(node: Record<string, any> | undefined): ZoneAttributes;
110
+ /** True when the node carries `x-telo-violates-zone` in any shape, valid or not. */
111
+ export declare function hasViolatesZone(node: Record<string, any> | undefined): boolean;
51
112
  /** True when the node carries `x-telo-requires-zone` in any shape. */
52
113
  export declare function hasRequiresZone(node: Record<string, any> | undefined): boolean;
53
114
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"zone-slot.d.ts","sourceRoot":"","sources":["../src/zone-slot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAKH;;qEAEqE;AACrE,MAAM,WAAW,gBAAgB;IAC/B;;iCAE6B;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,0EAA0E;AAC1E,MAAM,WAAW,gBAAgB;IAC/B;;2EAEuE;IACvE,IAAI,EAAE,MAAM,CAAC;IACb;;gCAE4B;IAC5B,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAYD;4EAC4E;AAC5E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,gBAAgB,GAAG,SAAS,CAKpG;AAED;0EAC0E;AAC1E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,OAAO,CAE9E;AAED;4EAC4E;AAC5E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,gBAAgB,GAAG,SAAS,CAWpG;AAED,sEAAsE;AACtE,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,OAAO,CAE9E;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACrC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GACxC,IAAI,CAaN"}
1
+ {"version":3,"file":"zone-slot.d.ts","sourceRoot":"","sources":["../src/zone-slot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAmB,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAMpE;;qEAEqE;AACrE,MAAM,WAAW,gBAAgB;IAC/B;;iCAE6B;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;;;wBAQoB;IACpB,UAAU,EAAE,cAAc,CAAC;CAC5B;AAED,0EAA0E;AAC1E,MAAM,WAAW,gBAAgB;IAC/B;;2EAEuE;IACvE,IAAI,EAAE,MAAM,CAAC;IACb;;gCAE4B;IAC5B,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;;;;;;;OAcG;IACH,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAYD;4EAC4E;AAC5E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,gBAAgB,GAAG,SAAS,CAyBpG;AAED;;wEAEwE;AACxE,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,cAAc,CAE5F;AAED;0EAC0E;AAC1E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,OAAO,CAE9E;AAED;4EAC4E;AAC5E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,gBAAgB,GAAG,SAAS,CAkBpG;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,cAAc,CAYtF;AAED,oFAAoF;AACpF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,OAAO,CAE9E;AAED,sEAAsE;AACtE,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,OAAO,CAE9E;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACrC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GACxC,IAAI,CAaN"}
package/dist/zone-slot.js CHANGED
@@ -10,6 +10,13 @@
10
10
  *
11
11
  * x-telo-provides-zone: true # uncorrelated — the zone is the kind
12
12
  * x-telo-provides-zone: /connection # correlation-key pointer (own field)
13
+ * x-telo-provides-zone: # correlation key + zone attributes
14
+ * key: /connection
15
+ * atomic: a rollback erases writes a journal recorded as done
16
+ * noSuspend: the transaction holds a connection a parked run would lose
17
+ *
18
+ * x-telo-violates-zone: # what this kind cannot honour
19
+ * noSuspend: this waits for a delivery that may be days away
13
20
  *
14
21
  * x-telo-requires-zone: Self.Transaction # uncorrelated string form
15
22
  * x-telo-requires-zone: # object form
@@ -17,8 +24,10 @@
17
24
  * key: [/connection, /transaction/connection] # ordered, first hit wins
18
25
  * reason: the statement would execute outside any transaction
19
26
  */
27
+ import { ZONE_ATTRIBUTES } from "@telorun/sdk";
20
28
  const PROVIDES = "x-telo-provides-zone";
21
29
  const REQUIRES = "x-telo-requires-zone";
30
+ const VIOLATES = "x-telo-violates-zone";
22
31
  /** A self-relative JSON Pointer — the only correlation-key spelling the
23
32
  * analyzer and the kernel read identically. Applied to BOTH the scalar and the
24
33
  * list form: the kernel's walk splits on `/` and drops empty segments, so a
@@ -33,10 +42,38 @@ function isPointer(value) {
33
42
  export function readProvidesZone(node) {
34
43
  const raw = node?.[PROVIDES];
35
44
  if (raw === true)
36
- return {};
45
+ return { attributes: {} };
37
46
  if (isPointer(raw))
38
- return { key: raw };
39
- return undefined;
47
+ return { key: raw, attributes: {} };
48
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
49
+ return undefined;
50
+ const obj = raw;
51
+ // A `key` that is present but unreadable makes the object form MEAN something
52
+ // different to the two halves — the correlation the author wrote is dropped
53
+ // here while the kernel's walk still resolves a bare name — so the whole
54
+ // annotation is refused rather than read as uncorrelated. Absent `key` is the
55
+ // legitimate uncorrelated case.
56
+ if (obj.key !== undefined && !isPointer(obj.key))
57
+ return undefined;
58
+ const attributes = {};
59
+ for (const [name, value] of Object.entries(obj)) {
60
+ if (name === "key")
61
+ continue;
62
+ // Unknown names and wrong-shaped values are reported by
63
+ // `validate-zone-slots`; skipped here so a typo degrades to an attribute
64
+ // this zone does not declare rather than to a consumer reading a name that
65
+ // means nothing to it.
66
+ if (!ZONE_ATTRIBUTES.has(name) || typeof value !== "string" || !value)
67
+ continue;
68
+ attributes[name] = value;
69
+ }
70
+ return { ...(isPointer(obj.key) ? { key: obj.key } : {}), attributes: attributes };
71
+ }
72
+ /** The attributes the zone a slot provides declares, or an empty record when the
73
+ * node provides no zone. The shape every containment consumer wants, so none of
74
+ * them repeats the `readProvidesZone(...)?.attributes ?? {}` dance. */
75
+ export function providedZoneAttributes(node) {
76
+ return readProvidesZone(node)?.attributes ?? {};
40
77
  }
41
78
  /** True when the node carries `x-telo-provides-zone` in any shape, valid or not
42
79
  * — the recognition test validation needs before it judges the value. */
@@ -48,7 +85,7 @@ export function hasProvidesZone(node) {
48
85
  export function readRequiresZone(node) {
49
86
  const raw = node?.[REQUIRES];
50
87
  if (typeof raw === "string" && raw)
51
- return { zone: raw, key: [] };
88
+ return { zone: raw, key: [], attributes: [] };
52
89
  if (!raw || typeof raw !== "object" || Array.isArray(raw))
53
90
  return undefined;
54
91
  const obj = raw;
@@ -56,11 +93,55 @@ export function readRequiresZone(node) {
56
93
  return undefined;
57
94
  // One filter for both spellings — see `isPointer`.
58
95
  const key = (Array.isArray(obj.key) ? obj.key : [obj.key]).filter(isPointer);
59
- const slot = { zone: obj.zone, key };
96
+ // Unknown names are dropped here and reported by `validate-zone-slots`, the
97
+ // lenient-reader / strict-validator split this file has throughout: a typo
98
+ // degrades to a guarantee this requirement does not ask for, never to a
99
+ // consumer matching against a name that means nothing.
100
+ const attributes = (Array.isArray(obj.attributes) ? obj.attributes : []).filter((a) => typeof a === "string" && ZONE_ATTRIBUTES.has(a));
101
+ const slot = { zone: obj.zone, key, attributes };
60
102
  if (typeof obj.reason === "string")
61
103
  slot.reason = obj.reason;
62
104
  return slot;
63
105
  }
106
+ /**
107
+ * What this kind CANNOT honour about a region it is placed inside.
108
+ *
109
+ * The third relation, and the one the other two cannot express. `provides`
110
+ * declares what a region guarantees about its contents; `requires` declares what
111
+ * a resource needs of the region around it. Neither says *this resource breaks
112
+ * that guarantee* — and without it a zone attribute has a promise and no way to
113
+ * name what falsifies it: `noSuspend` would be enforced only when a parking kind
114
+ * happened to reach its runtime check, and `Durable.Sleep` would be
115
+ * indistinguishable from `Durable.Value`, which needs the same journal and parks
116
+ * nothing.
117
+ *
118
+ * Declared at the kind's SCHEMA ROOT, because it is a property of the kind
119
+ * rather than of one of its slots — a kind that suspends suspends however it is
120
+ * configured.
121
+ *
122
+ * Values are the author's REASON, exactly as on `provides`: a diagnostic prints
123
+ * the region's promise and this resource's rebuttal side by side, and both are
124
+ * their own authors' words.
125
+ */
126
+ export function readViolatesZone(node) {
127
+ const raw = node?.[VIOLATES];
128
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
129
+ return {};
130
+ const out = {};
131
+ for (const [name, value] of Object.entries(raw)) {
132
+ // Lenient, like every reader here: `validate-zone-slots` reports a typo, and
133
+ // dropping it degrades to a violation nobody declared rather than to a
134
+ // consumer matching a name that means nothing.
135
+ if (!ZONE_ATTRIBUTES.has(name) || typeof value !== "string" || !value)
136
+ continue;
137
+ out[name] = value;
138
+ }
139
+ return out;
140
+ }
141
+ /** True when the node carries `x-telo-violates-zone` in any shape, valid or not. */
142
+ export function hasViolatesZone(node) {
143
+ return node?.[VIOLATES] !== undefined;
144
+ }
64
145
  /** True when the node carries `x-telo-requires-zone` in any shape. */
65
146
  export function hasRequiresZone(node) {
66
147
  return node?.[REQUIRES] !== undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telorun/analyzer",
3
- "version": "0.62.0",
3
+ "version": "0.63.0",
4
4
  "description": "Telo Analyzer - Static manifest validator for Telo manifests.",
5
5
  "keywords": [
6
6
  "telo",
@@ -49,7 +49,7 @@
49
49
  "@types/node": "^20.0.0",
50
50
  "typescript": "^5.0.0",
51
51
  "vitest": "^2.1.8",
52
- "@telorun/sdk": "0.77.0"
52
+ "@telorun/sdk": "0.79.0"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "@telorun/sdk": "*"
@@ -174,6 +174,12 @@ export class AnalysisRegistry {
174
174
  ): Record<string, unknown> | undefined {
175
175
  const def = this.resolveDefinition(kind);
176
176
  if (!def) return undefined;
177
+ // A KIND-level query: there is no instance, so a DECLARATION-derived slot
178
+ // (`x-telo-schema-projection-from`) has no reference to project from and is
179
+ // left as the kind declared it. That is the honest answer here — the
180
+ // projected shape is a property of one instance, not of the kind — and it is
181
+ // why passing no manifest cannot silently drop a projection: the per-resource
182
+ // analysis pass is what resolves and reports them.
177
183
  return resolveContract(direction, undefined, def, {
178
184
  resolveDefinition: this.scopedDefResolver(),
179
185
  typeManifestsFor: () => [],