@ontrails/core 1.0.0-beta.15 → 1.0.0-beta.16

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 (348) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +61 -16
  3. package/package.json +9 -8
  4. package/src/activation-provenance.ts +111 -0
  5. package/src/activation-source-compatibility.ts +430 -0
  6. package/src/activation-source-projection.ts +221 -0
  7. package/src/activation-source.ts +91 -0
  8. package/src/blob-ref.ts +51 -0
  9. package/src/context.ts +4 -0
  10. package/src/{internal/cross-batch.ts → cross-batch.ts} +3 -11
  11. package/src/detours.ts +8 -0
  12. package/src/draft.ts +4 -0
  13. package/src/error-projection.ts +51 -0
  14. package/src/errors.ts +192 -39
  15. package/src/execute.ts +443 -44
  16. package/src/fire.ts +965 -91
  17. package/src/index.ts +285 -38
  18. package/src/layer-projection.ts +192 -0
  19. package/src/layer.ts +32 -1
  20. package/src/observe.ts +361 -0
  21. package/src/permits.ts +12 -0
  22. package/src/redaction/patterns.ts +6 -3
  23. package/src/resilience.ts +8 -8
  24. package/src/resource-config.ts +477 -44
  25. package/src/resource.ts +21 -1
  26. package/src/result.ts +18 -0
  27. package/src/run.ts +2 -2
  28. package/src/schedule-runtime.ts +689 -0
  29. package/src/schedule.ts +324 -0
  30. package/src/serialization.ts +90 -57
  31. package/src/signal-diagnostics.ts +633 -0
  32. package/src/{internal/signal-ref.ts → signal-ref.ts} +1 -1
  33. package/src/signal.ts +37 -0
  34. package/src/store/accessor-protocol.ts +5 -5
  35. package/src/structured-examples.ts +245 -0
  36. package/src/surface-derivation.ts +91 -0
  37. package/src/surface-filter.ts +7 -13
  38. package/src/topo.ts +386 -6
  39. package/src/tracing.ts +345 -0
  40. package/src/trail.ts +168 -22
  41. package/src/trails/derive-trail.ts +1 -1
  42. package/src/trails/ingest.ts +7 -15
  43. package/src/{internal/trails-db.ts → trails-db.ts} +42 -21
  44. package/src/transport-error-map.ts +146 -28
  45. package/src/types.ts +82 -28
  46. package/src/validate-established-topo.ts +6 -0
  47. package/src/validate-topo.ts +212 -5
  48. package/src/validation.ts +58 -1
  49. package/src/webhook.ts +276 -0
  50. package/src/{internal/zod-wrappers.ts → zod-wrappers.ts} +0 -6
  51. package/.turbo/turbo-build.log +0 -1
  52. package/.turbo/turbo-lint.log +0 -3
  53. package/.turbo/turbo-typecheck.log +0 -1
  54. package/dist/blob-ref.d.ts +0 -20
  55. package/dist/blob-ref.d.ts.map +0 -1
  56. package/dist/blob-ref.js +0 -22
  57. package/dist/blob-ref.js.map +0 -1
  58. package/dist/branded.d.ts +0 -36
  59. package/dist/branded.d.ts.map +0 -1
  60. package/dist/branded.js +0 -89
  61. package/dist/branded.js.map +0 -1
  62. package/dist/collections.d.ts +0 -31
  63. package/dist/collections.d.ts.map +0 -1
  64. package/dist/collections.js +0 -60
  65. package/dist/collections.js.map +0 -1
  66. package/dist/context.d.ts +0 -22
  67. package/dist/context.d.ts.map +0 -1
  68. package/dist/context.js +0 -37
  69. package/dist/context.js.map +0 -1
  70. package/dist/contour.d.ts +0 -81
  71. package/dist/contour.d.ts.map +0 -1
  72. package/dist/contour.js +0 -176
  73. package/dist/contour.js.map +0 -1
  74. package/dist/cross-schema.d.ts +0 -18
  75. package/dist/cross-schema.d.ts.map +0 -1
  76. package/dist/cross-schema.js +0 -29
  77. package/dist/cross-schema.js.map +0 -1
  78. package/dist/derive.d.ts +0 -44
  79. package/dist/derive.d.ts.map +0 -1
  80. package/dist/derive.js +0 -149
  81. package/dist/derive.js.map +0 -1
  82. package/dist/draft.d.ts +0 -28
  83. package/dist/draft.d.ts.map +0 -1
  84. package/dist/draft.js +0 -157
  85. package/dist/draft.js.map +0 -1
  86. package/dist/errors.d.ts +0 -141
  87. package/dist/errors.d.ts.map +0 -1
  88. package/dist/errors.js +0 -185
  89. package/dist/errors.js.map +0 -1
  90. package/dist/execute.d.ts +0 -52
  91. package/dist/execute.d.ts.map +0 -1
  92. package/dist/execute.js +0 -511
  93. package/dist/execute.js.map +0 -1
  94. package/dist/fetch.d.ts +0 -15
  95. package/dist/fetch.d.ts.map +0 -1
  96. package/dist/fetch.js +0 -102
  97. package/dist/fetch.js.map +0 -1
  98. package/dist/fire.d.ts +0 -43
  99. package/dist/fire.d.ts.map +0 -1
  100. package/dist/fire.js +0 -185
  101. package/dist/fire.js.map +0 -1
  102. package/dist/guards.d.ts +0 -17
  103. package/dist/guards.d.ts.map +0 -1
  104. package/dist/guards.js +0 -25
  105. package/dist/guards.js.map +0 -1
  106. package/dist/index.d.ts +0 -59
  107. package/dist/index.d.ts.map +0 -1
  108. package/dist/index.js +0 -61
  109. package/dist/index.js.map +0 -1
  110. package/dist/internal/cross-batch.d.ts +0 -44
  111. package/dist/internal/cross-batch.d.ts.map +0 -1
  112. package/dist/internal/cross-batch.js +0 -58
  113. package/dist/internal/cross-batch.js.map +0 -1
  114. package/dist/internal/fork-ctx.d.ts +0 -41
  115. package/dist/internal/fork-ctx.d.ts.map +0 -1
  116. package/dist/internal/fork-ctx.js +0 -47
  117. package/dist/internal/fork-ctx.js.map +0 -1
  118. package/dist/internal/signal-ref.d.ts +0 -15
  119. package/dist/internal/signal-ref.d.ts.map +0 -1
  120. package/dist/internal/signal-ref.js +0 -41
  121. package/dist/internal/signal-ref.js.map +0 -1
  122. package/dist/internal/topo-saves.d.ts +0 -53
  123. package/dist/internal/topo-saves.d.ts.map +0 -1
  124. package/dist/internal/topo-saves.js +0 -449
  125. package/dist/internal/topo-saves.js.map +0 -1
  126. package/dist/internal/topo-snapshots.d.ts +0 -57
  127. package/dist/internal/topo-snapshots.d.ts.map +0 -1
  128. package/dist/internal/topo-snapshots.js +0 -372
  129. package/dist/internal/topo-snapshots.js.map +0 -1
  130. package/dist/internal/topo-store-read.d.ts +0 -70
  131. package/dist/internal/topo-store-read.d.ts.map +0 -1
  132. package/dist/internal/topo-store-read.js +0 -221
  133. package/dist/internal/topo-store-read.js.map +0 -1
  134. package/dist/internal/topo-store.d.ts +0 -26
  135. package/dist/internal/topo-store.d.ts.map +0 -1
  136. package/dist/internal/topo-store.js +0 -621
  137. package/dist/internal/topo-store.js.map +0 -1
  138. package/dist/internal/tracing.d.ts +0 -98
  139. package/dist/internal/tracing.d.ts.map +0 -1
  140. package/dist/internal/tracing.js +0 -102
  141. package/dist/internal/tracing.js.map +0 -1
  142. package/dist/internal/trails-db.d.ts +0 -16
  143. package/dist/internal/trails-db.d.ts.map +0 -1
  144. package/dist/internal/trails-db.js +0 -118
  145. package/dist/internal/trails-db.js.map +0 -1
  146. package/dist/internal/zod-wrappers.d.ts +0 -40
  147. package/dist/internal/zod-wrappers.d.ts.map +0 -1
  148. package/dist/internal/zod-wrappers.js +0 -59
  149. package/dist/internal/zod-wrappers.js.map +0 -1
  150. package/dist/layer.d.ts +0 -23
  151. package/dist/layer.d.ts.map +0 -1
  152. package/dist/layer.js +0 -21
  153. package/dist/layer.js.map +0 -1
  154. package/dist/path-security.d.ts +0 -28
  155. package/dist/path-security.d.ts.map +0 -1
  156. package/dist/path-security.js +0 -63
  157. package/dist/path-security.js.map +0 -1
  158. package/dist/patterns/bulk.d.ts +0 -15
  159. package/dist/patterns/bulk.d.ts.map +0 -1
  160. package/dist/patterns/bulk.js +0 -14
  161. package/dist/patterns/bulk.js.map +0 -1
  162. package/dist/patterns/change.d.ts +0 -10
  163. package/dist/patterns/change.d.ts.map +0 -1
  164. package/dist/patterns/change.js +0 -10
  165. package/dist/patterns/change.js.map +0 -1
  166. package/dist/patterns/date-range.d.ts +0 -10
  167. package/dist/patterns/date-range.d.ts.map +0 -1
  168. package/dist/patterns/date-range.js +0 -10
  169. package/dist/patterns/date-range.js.map +0 -1
  170. package/dist/patterns/index.d.ts +0 -9
  171. package/dist/patterns/index.d.ts.map +0 -1
  172. package/dist/patterns/index.js +0 -9
  173. package/dist/patterns/index.js.map +0 -1
  174. package/dist/patterns/pagination.d.ts +0 -18
  175. package/dist/patterns/pagination.d.ts.map +0 -1
  176. package/dist/patterns/pagination.js +0 -18
  177. package/dist/patterns/pagination.js.map +0 -1
  178. package/dist/patterns/progress.d.ts +0 -11
  179. package/dist/patterns/progress.d.ts.map +0 -1
  180. package/dist/patterns/progress.js +0 -11
  181. package/dist/patterns/progress.js.map +0 -1
  182. package/dist/patterns/sorting.d.ts +0 -13
  183. package/dist/patterns/sorting.d.ts.map +0 -1
  184. package/dist/patterns/sorting.js +0 -10
  185. package/dist/patterns/sorting.js.map +0 -1
  186. package/dist/patterns/status.d.ts +0 -15
  187. package/dist/patterns/status.d.ts.map +0 -1
  188. package/dist/patterns/status.js +0 -9
  189. package/dist/patterns/status.js.map +0 -1
  190. package/dist/patterns/timestamps.d.ts +0 -10
  191. package/dist/patterns/timestamps.d.ts.map +0 -1
  192. package/dist/patterns/timestamps.js +0 -10
  193. package/dist/patterns/timestamps.js.map +0 -1
  194. package/dist/redaction/index.d.ts +0 -4
  195. package/dist/redaction/index.d.ts.map +0 -1
  196. package/dist/redaction/index.js +0 -3
  197. package/dist/redaction/index.js.map +0 -1
  198. package/dist/redaction/patterns.d.ts +0 -9
  199. package/dist/redaction/patterns.d.ts.map +0 -1
  200. package/dist/redaction/patterns.js +0 -39
  201. package/dist/redaction/patterns.js.map +0 -1
  202. package/dist/redaction/redactor.d.ts +0 -27
  203. package/dist/redaction/redactor.d.ts.map +0 -1
  204. package/dist/redaction/redactor.js +0 -89
  205. package/dist/redaction/redactor.js.map +0 -1
  206. package/dist/resilience.d.ts +0 -34
  207. package/dist/resilience.d.ts.map +0 -1
  208. package/dist/resilience.js +0 -164
  209. package/dist/resilience.js.map +0 -1
  210. package/dist/resource-config.d.ts +0 -22
  211. package/dist/resource-config.d.ts.map +0 -1
  212. package/dist/resource-config.js +0 -210
  213. package/dist/resource-config.js.map +0 -1
  214. package/dist/resource.d.ts +0 -74
  215. package/dist/resource.d.ts.map +0 -1
  216. package/dist/resource.js +0 -61
  217. package/dist/resource.js.map +0 -1
  218. package/dist/result.d.ts +0 -57
  219. package/dist/result.d.ts.map +0 -1
  220. package/dist/result.js +0 -156
  221. package/dist/result.js.map +0 -1
  222. package/dist/run.d.ts +0 -28
  223. package/dist/run.d.ts.map +0 -1
  224. package/dist/run.js +0 -35
  225. package/dist/run.js.map +0 -1
  226. package/dist/serialization.d.ts +0 -27
  227. package/dist/serialization.d.ts.map +0 -1
  228. package/dist/serialization.js +0 -154
  229. package/dist/serialization.js.map +0 -1
  230. package/dist/signal.d.ts +0 -33
  231. package/dist/signal.d.ts.map +0 -1
  232. package/dist/signal.js +0 -17
  233. package/dist/signal.js.map +0 -1
  234. package/dist/store/accessor-protocol.d.ts +0 -51
  235. package/dist/store/accessor-protocol.d.ts.map +0 -1
  236. package/dist/store/accessor-protocol.js +0 -2
  237. package/dist/store/accessor-protocol.js.map +0 -1
  238. package/dist/store/index.d.ts +0 -2
  239. package/dist/store/index.d.ts.map +0 -1
  240. package/dist/store/index.js +0 -2
  241. package/dist/store/index.js.map +0 -1
  242. package/dist/surface-filter.d.ts +0 -13
  243. package/dist/surface-filter.d.ts.map +0 -1
  244. package/dist/surface-filter.js +0 -76
  245. package/dist/surface-filter.js.map +0 -1
  246. package/dist/topo-store.d.ts +0 -62
  247. package/dist/topo-store.d.ts.map +0 -1
  248. package/dist/topo-store.js +0 -319
  249. package/dist/topo-store.js.map +0 -1
  250. package/dist/topo.d.ts +0 -39
  251. package/dist/topo.d.ts.map +0 -1
  252. package/dist/topo.js +0 -241
  253. package/dist/topo.js.map +0 -1
  254. package/dist/trail.d.ts +0 -163
  255. package/dist/trail.d.ts.map +0 -1
  256. package/dist/trail.js +0 -75
  257. package/dist/trail.js.map +0 -1
  258. package/dist/trails/derive-trail.d.ts +0 -66
  259. package/dist/trails/derive-trail.d.ts.map +0 -1
  260. package/dist/trails/derive-trail.js +0 -378
  261. package/dist/trails/derive-trail.js.map +0 -1
  262. package/dist/trails/index.d.ts +0 -5
  263. package/dist/trails/index.d.ts.map +0 -1
  264. package/dist/trails/index.js +0 -3
  265. package/dist/trails/index.js.map +0 -1
  266. package/dist/trails/ingest.d.ts +0 -24
  267. package/dist/trails/ingest.d.ts.map +0 -1
  268. package/dist/trails/ingest.js +0 -63
  269. package/dist/trails/ingest.js.map +0 -1
  270. package/dist/transport-error-map.d.ts +0 -106
  271. package/dist/transport-error-map.d.ts.map +0 -1
  272. package/dist/transport-error-map.js +0 -24
  273. package/dist/transport-error-map.js.map +0 -1
  274. package/dist/type-checks.test-d.d.ts +0 -71
  275. package/dist/type-checks.test-d.d.ts.map +0 -1
  276. package/dist/type-checks.test-d.js +0 -12
  277. package/dist/type-checks.test-d.js.map +0 -1
  278. package/dist/type-utils.d.ts +0 -33
  279. package/dist/type-utils.d.ts.map +0 -1
  280. package/dist/type-utils.js +0 -12
  281. package/dist/type-utils.js.map +0 -1
  282. package/dist/types.d.ts +0 -177
  283. package/dist/types.d.ts.map +0 -1
  284. package/dist/types.js +0 -3
  285. package/dist/types.js.map +0 -1
  286. package/dist/validate-established-topo.d.ts +0 -76
  287. package/dist/validate-established-topo.d.ts.map +0 -1
  288. package/dist/validate-established-topo.js +0 -43
  289. package/dist/validate-established-topo.js.map +0 -1
  290. package/dist/validate-topo.d.ts +0 -24
  291. package/dist/validate-topo.d.ts.map +0 -1
  292. package/dist/validate-topo.js +0 -193
  293. package/dist/validate-topo.js.map +0 -1
  294. package/dist/validation.d.ts +0 -27
  295. package/dist/validation.d.ts.map +0 -1
  296. package/dist/validation.js +0 -165
  297. package/dist/validation.js.map +0 -1
  298. package/dist/workspace.d.ts +0 -25
  299. package/dist/workspace.d.ts.map +0 -1
  300. package/dist/workspace.js +0 -57
  301. package/dist/workspace.js.map +0 -1
  302. package/src/__tests__/blob-ref.test.ts +0 -103
  303. package/src/__tests__/branded.test.ts +0 -148
  304. package/src/__tests__/collections.test.ts +0 -126
  305. package/src/__tests__/context.test.ts +0 -77
  306. package/src/__tests__/contour.test.ts +0 -263
  307. package/src/__tests__/derive-trail.test.ts +0 -668
  308. package/src/__tests__/derive.test.ts +0 -260
  309. package/src/__tests__/errors.test.ts +0 -427
  310. package/src/__tests__/execute.test.ts +0 -1563
  311. package/src/__tests__/fetch.test.ts +0 -217
  312. package/src/__tests__/fire.test.ts +0 -1056
  313. package/src/__tests__/fork-ctx.test.ts +0 -83
  314. package/src/__tests__/guards.test.ts +0 -102
  315. package/src/__tests__/ingest.test.ts +0 -191
  316. package/src/__tests__/layer.test.ts +0 -125
  317. package/src/__tests__/path-security.test.ts +0 -114
  318. package/src/__tests__/patterns.test.ts +0 -273
  319. package/src/__tests__/redaction.test.ts +0 -244
  320. package/src/__tests__/resilience.test.ts +0 -246
  321. package/src/__tests__/resource.test.ts +0 -204
  322. package/src/__tests__/result.test.ts +0 -155
  323. package/src/__tests__/run.test.ts +0 -181
  324. package/src/__tests__/serialization.test.ts +0 -401
  325. package/src/__tests__/service-config.test.ts +0 -224
  326. package/src/__tests__/signal.test.ts +0 -82
  327. package/src/__tests__/surface-filter.test.ts +0 -196
  328. package/src/__tests__/topo-store-read.test.ts +0 -256
  329. package/src/__tests__/topo-store.test.ts +0 -869
  330. package/src/__tests__/topo.test.ts +0 -573
  331. package/src/__tests__/trail-permit.test.ts +0 -60
  332. package/src/__tests__/trail.test.ts +0 -426
  333. package/src/__tests__/trails-db.test.ts +0 -198
  334. package/src/__tests__/transport-error-map.test.ts +0 -82
  335. package/src/__tests__/type-utils.test.ts +0 -118
  336. package/src/__tests__/validate-topo.test.ts +0 -611
  337. package/src/__tests__/validation.test.ts +0 -336
  338. package/src/__tests__/workspace.test.ts +0 -183
  339. package/src/__tests__/zod-wrappers.test.ts +0 -62
  340. package/src/internal/topo-snapshots.ts +0 -517
  341. package/src/internal/topo-store-read.ts +0 -481
  342. package/src/internal/topo-store.ts +0 -1192
  343. package/src/internal/tracing.ts +0 -187
  344. package/src/topo-store.ts +0 -510
  345. package/src/type-checks.test-d.ts +0 -106
  346. package/tsconfig.json +0 -9
  347. package/tsconfig.tests.json +0 -10
  348. package/tsconfig.tsbuildinfo +0 -1
@@ -1,20 +1,30 @@
1
1
  /**
2
2
  * Structural validation for a Topo graph.
3
3
  *
4
- * Checks trail crossing references, example input validity, event origin
5
- * references, and output schema completeness. Returns a Result with all
6
- * issues collected into a single ValidationError.
4
+ * Checks trail crossing references, example input validity, signal origin
5
+ * references, activation source kinds, and output schema completeness. Returns
6
+ * a Result with all issues collected into a single ValidationError.
7
7
  */
8
8
 
9
+ import type { ActivationSchemaIssue } from './activation-source-compatibility.js';
10
+ import { getActivationSourceInputCompatibilityIssues } from './activation-source-compatibility.js';
11
+ import {
12
+ activationSourceDeclarationSignature,
13
+ activationSourceKey,
14
+ } from './activation-source-projection.js';
9
15
  import type { AnyContour } from './contour.js';
10
16
  import { getContourReferences } from './contour.js';
11
17
  import { ValidationError } from './errors.js';
18
+ import type { ActivationEntry } from './activation-source.js';
19
+ import { isKnownActivationSourceKind } from './activation-source.js';
12
20
  import { isDraftId } from './draft.js';
13
21
  import type { AnySignal } from './signal.js';
22
+ import { validateScheduleSource } from './schedule.js';
14
23
  import { Result } from './result.js';
15
24
  import type { Topo } from './topo.js';
16
25
  import type { AnyTrail } from './trail.js';
17
26
  import { validateInput } from './validation.js';
27
+ import { validateWebhookSource } from './webhook.js';
18
28
 
19
29
  // ---------------------------------------------------------------------------
20
30
  // Issue shape
@@ -24,8 +34,14 @@ export interface TopoIssue {
24
34
  readonly trailId: string;
25
35
  readonly rule: string;
26
36
  readonly message: string;
37
+ readonly inputPath?: readonly (string | number)[];
38
+ readonly schemaIssues?: readonly TopoSchemaIssue[];
39
+ readonly sourceId?: string;
40
+ readonly sourceKind?: string;
27
41
  }
28
42
 
43
+ export type TopoSchemaIssue = ActivationSchemaIssue;
44
+
29
45
  // ---------------------------------------------------------------------------
30
46
  // Validators
31
47
  // ---------------------------------------------------------------------------
@@ -205,6 +221,193 @@ const checkSignalOrigins = (
205
221
  return issues;
206
222
  };
207
223
 
224
+ const checkSignalReferences = (
225
+ trails: ReadonlyMap<string, AnyTrail>,
226
+ signals: ReadonlyMap<string, AnySignal>
227
+ ): TopoIssue[] => {
228
+ const issues: TopoIssue[] = [];
229
+
230
+ for (const [id, trail] of trails) {
231
+ for (const signalId of trail.fires ?? []) {
232
+ if (!signals.has(signalId) && !isDraftId(signalId)) {
233
+ issues.push({
234
+ message: `Trail fires signal "${signalId}" which is not in the topo`,
235
+ rule: 'signal-fire-exists',
236
+ trailId: id,
237
+ });
238
+ }
239
+ }
240
+
241
+ for (const signalId of trail.on ?? []) {
242
+ if (!signals.has(signalId) && !isDraftId(signalId)) {
243
+ issues.push({
244
+ message: `Trail declares on signal "${signalId}" which is not in the topo`,
245
+ rule: 'signal-on-exists',
246
+ trailId: id,
247
+ });
248
+ }
249
+ }
250
+ }
251
+
252
+ return issues;
253
+ };
254
+
255
+ const checkActivationSources = (
256
+ trails: ReadonlyMap<string, AnyTrail>
257
+ ): TopoIssue[] => {
258
+ const issues: TopoIssue[] = [];
259
+ const sourceDeclarations = new Map<
260
+ string,
261
+ {
262
+ readonly signature: string;
263
+ readonly trailId: string;
264
+ }
265
+ >();
266
+ const trailSourceEdges = new Set<string>();
267
+
268
+ for (const [id, trail] of trails) {
269
+ for (const activation of trail.activationSources ?? []) {
270
+ if (!isKnownActivationSourceKind(activation.source.kind)) {
271
+ issues.push({
272
+ message: `Trail declares on source "${activation.source.id}" with unsupported source kind "${activation.source.kind}"`,
273
+ rule: 'activation-source-kind-known',
274
+ trailId: id,
275
+ });
276
+ continue;
277
+ }
278
+
279
+ const sourceKey = activationSourceKey(activation.source);
280
+ const edgeKey = `${id}\0${sourceKey}`;
281
+ if (trailSourceEdges.has(edgeKey)) {
282
+ issues.push({
283
+ message: `Trail declares activation source "${activation.source.id}" (${activation.source.kind}) more than once. Keep one source-to-trail activation edge, or split distinct activation behavior into distinct source ids.`,
284
+ rule: 'activation-source-edge-unique',
285
+ sourceId: activation.source.id,
286
+ sourceKind: activation.source.kind,
287
+ trailId: id,
288
+ });
289
+ } else {
290
+ trailSourceEdges.add(edgeKey);
291
+ }
292
+
293
+ if (!isDraftId(activation.source.id)) {
294
+ const signature = activationSourceDeclarationSignature(
295
+ activation.source
296
+ );
297
+ const previous = sourceDeclarations.get(sourceKey);
298
+ if (previous === undefined) {
299
+ sourceDeclarations.set(sourceKey, { signature, trailId: id });
300
+ } else if (previous.signature !== signature) {
301
+ issues.push({
302
+ message: `Activation source "${activation.source.id}" (${activation.source.kind}) is declared with conflicting source options by trails "${previous.trailId}" and "${id}". Use one canonical source declaration per source id, or give distinct source configurations distinct ids.`,
303
+ rule: 'activation-source-definition-unique',
304
+ sourceId: activation.source.id,
305
+ sourceKind: activation.source.kind,
306
+ trailId: id,
307
+ });
308
+ }
309
+ }
310
+
311
+ const scheduleIssues = validateScheduleSource(activation.source);
312
+ for (const issue of scheduleIssues) {
313
+ issues.push({
314
+ inputPath: [issue.field],
315
+ message: `Trail declares schedule source "${activation.source.id}" with invalid ${issue.field}: ${issue.message}`,
316
+ rule: 'activation-schedule-valid',
317
+ schemaIssues: [
318
+ { code: issue.field, message: issue.message, path: [issue.field] },
319
+ ],
320
+ sourceId: activation.source.id,
321
+ sourceKind: activation.source.kind,
322
+ trailId: id,
323
+ });
324
+ }
325
+
326
+ const webhookIssues = validateWebhookSource(activation.source);
327
+ for (const issue of webhookIssues) {
328
+ issues.push({
329
+ inputPath: [issue.field],
330
+ message: `Trail declares webhook source "${activation.source.id}" with invalid ${issue.field}: ${issue.message}`,
331
+ rule: 'activation-webhook-valid',
332
+ schemaIssues: [
333
+ { code: issue.field, message: issue.message, path: [issue.field] },
334
+ ],
335
+ sourceId: activation.source.id,
336
+ sourceKind: activation.source.kind,
337
+ trailId: id,
338
+ });
339
+ }
340
+ }
341
+ }
342
+
343
+ return issues;
344
+ };
345
+
346
+ const issuePathText = (path: readonly (string | number)[]): string =>
347
+ path.length > 0 ? path.join('.') : '<root>';
348
+
349
+ const createSourceCompatibilityIssue = (
350
+ trailId: string,
351
+ activation: ActivationEntry,
352
+ schemaIssues: readonly TopoSchemaIssue[]
353
+ ): TopoIssue => {
354
+ const [firstIssue] = schemaIssues;
355
+ const inputPath = firstIssue?.path ?? Object.freeze([]);
356
+ return {
357
+ inputPath,
358
+ message: `Activation source "${activation.source.id}" (${activation.source.kind}) does not satisfy trail input at ${issuePathText(inputPath)}: ${firstIssue?.message ?? 'source payload is incompatible with trail input'}`,
359
+ rule: 'activation-source-input-compatible',
360
+ schemaIssues,
361
+ sourceId: activation.source.id,
362
+ sourceKind: activation.source.kind,
363
+ trailId,
364
+ };
365
+ };
366
+
367
+ const checkSourcePayloadCompatibility = (
368
+ trail: AnyTrail,
369
+ activation: ActivationEntry,
370
+ signals: ReadonlyMap<string, AnySignal>
371
+ ): TopoIssue | undefined => {
372
+ if (
373
+ !isKnownActivationSourceKind(activation.source.kind) ||
374
+ isDraftId(activation.source.id)
375
+ ) {
376
+ return undefined;
377
+ }
378
+
379
+ const schemaIssues = getActivationSourceInputCompatibilityIssues(
380
+ trail.input,
381
+ activation.source,
382
+ signals
383
+ );
384
+ if (!schemaIssues) {
385
+ return undefined;
386
+ }
387
+
388
+ return schemaIssues.length > 0
389
+ ? createSourceCompatibilityIssue(trail.id, activation, schemaIssues)
390
+ : undefined;
391
+ };
392
+
393
+ const checkActivationSourceInputCompatibility = (
394
+ trails: ReadonlyMap<string, AnyTrail>,
395
+ signals: ReadonlyMap<string, AnySignal>
396
+ ): TopoIssue[] => {
397
+ const issues: TopoIssue[] = [];
398
+
399
+ for (const trail of trails.values()) {
400
+ for (const activation of trail.activationSources ?? []) {
401
+ const issue = checkSourcePayloadCompatibility(trail, activation, signals);
402
+ if (issue) {
403
+ issues.push(issue);
404
+ }
405
+ }
406
+ }
407
+
408
+ return issues;
409
+ };
410
+
208
411
  const checkContourReferences = (
209
412
  contours: ReadonlyMap<string, AnyContour>,
210
413
  topo: Topo
@@ -233,8 +436,9 @@ const checkContourReferences = (
233
436
  /**
234
437
  * Validate the structural integrity of a Topo graph.
235
438
  *
236
- * Checks crossing references, example inputs, event origins, and output
237
- * schema presence. Returns `Result.ok()` when no issues are found, or
439
+ * Checks crossing references, example inputs, signal origins, activation
440
+ * source kinds, and output schema presence. Returns `Result.ok()` when no
441
+ * issues are found, or
238
442
  * `Result.err(ValidationError)` with all issues in the error context.
239
443
  */
240
444
  export const validateTopo = (topo: Topo): Result<void, ValidationError> => {
@@ -244,6 +448,9 @@ export const validateTopo = (topo: Topo): Result<void, ValidationError> => {
244
448
  ...checkContourReferences(topo.contours, topo),
245
449
  ...checkExamples(topo.trails),
246
450
  ...checkSignalOrigins(topo.signals, topo),
451
+ ...checkSignalReferences(topo.trails, topo.signals),
452
+ ...checkActivationSources(topo.trails),
453
+ ...checkActivationSourceInputCompatibility(topo.trails, topo.signals),
247
454
  ];
248
455
 
249
456
  if (issues.length === 0) {
package/src/validation.ts CHANGED
@@ -7,6 +7,7 @@
7
7
 
8
8
  import type { z } from 'zod';
9
9
 
10
+ import { BLOB_REF_SCHEMA_META_KEY, blobRefJsonSchema } from './blob-ref.js';
10
11
  import { ValidationError } from './errors.js';
11
12
  import { Result } from './result.js';
12
13
 
@@ -31,10 +32,57 @@ type JsonSchemaConverter = (schema: z.ZodType) => JsonSchema;
31
32
  // ---------------------------------------------------------------------------
32
33
 
33
34
  const isOptionalLike = (s: ZodInternals): boolean => {
34
- const defType = s._zod.def['type'] as string;
35
+ let current = s;
36
+ const seen = new Set<ZodInternals>();
37
+ while (
38
+ current._zod.def['type'] === 'readonly' &&
39
+ !seen.has(current) &&
40
+ current._zod.def['innerType'] !== undefined
41
+ ) {
42
+ seen.add(current);
43
+ current = current._zod.def['innerType'] as ZodInternals;
44
+ }
45
+ const defType = current._zod.def['type'] as string;
35
46
  return defType === 'optional' || defType === 'default';
36
47
  };
37
48
 
49
+ const getSchemaMeta = (
50
+ schema: z.ZodType
51
+ ): Readonly<Record<string, unknown>> | undefined => {
52
+ const maybeMeta = (schema as unknown as { meta?: () => unknown }).meta;
53
+ if (typeof maybeMeta !== 'function') {
54
+ return undefined;
55
+ }
56
+ const meta = maybeMeta.call(schema);
57
+ return typeof meta === 'object' && meta !== null
58
+ ? (meta as Readonly<Record<string, unknown>>)
59
+ : undefined;
60
+ };
61
+
62
+ const getSchemaJsonSchemaOverride = (
63
+ schema: z.ZodType
64
+ ): JsonSchema | undefined => {
65
+ const meta = getSchemaMeta(schema);
66
+ if (meta?.[BLOB_REF_SCHEMA_META_KEY] === true) {
67
+ const override: JsonSchema = {
68
+ properties: Object.fromEntries(
69
+ Object.entries(blobRefJsonSchema.properties).map(([key, value]) => [
70
+ key,
71
+ { ...value },
72
+ ])
73
+ ),
74
+ required: [...blobRefJsonSchema.required],
75
+ type: blobRefJsonSchema.type,
76
+ };
77
+ const { description } = schema as unknown as ZodInternals;
78
+ if (description) {
79
+ override['description'] = description;
80
+ }
81
+ return override;
82
+ }
83
+ return undefined;
84
+ };
85
+
38
86
  // ---------------------------------------------------------------------------
39
87
  // Issue formatting
40
88
  // ---------------------------------------------------------------------------
@@ -132,6 +180,11 @@ const resolveDefault = (def: Record<string, unknown>): unknown => {
132
180
  export const zodToJsonSchema: JsonSchemaConverter = (
133
181
  schema: z.ZodType
134
182
  ): JsonSchema => {
183
+ const jsonSchemaOverride = getSchemaJsonSchemaOverride(schema);
184
+ if (jsonSchemaOverride !== undefined) {
185
+ return jsonSchemaOverride;
186
+ }
187
+
135
188
  const s = schema as unknown as ZodInternals;
136
189
 
137
190
  const collectObjectFields = (shape: Record<string, ZodInternals>) => {
@@ -197,6 +250,10 @@ export const zodToJsonSchema: JsonSchemaConverter = (
197
250
  const inner = value._zod.def['innerType'] as unknown as z.ZodType;
198
251
  return zodToJsonSchema(inner);
199
252
  },
253
+ readonly: (value) => {
254
+ const inner = value._zod.def['innerType'] as unknown as z.ZodType;
255
+ return zodToJsonSchema(inner);
256
+ },
200
257
  string: () => ({ type: 'string' }),
201
258
  union: (value) => {
202
259
  const options = value._zod.def['options'] as unknown as z.ZodType[];
package/src/webhook.ts ADDED
@@ -0,0 +1,276 @@
1
+ import type {
2
+ ActivationSource,
3
+ ActivationSourceMeta,
4
+ ActivationSourceParse,
5
+ } from './activation-source.js';
6
+ import { InternalError, ValidationError } from './errors.js';
7
+ import { Result } from './result.js';
8
+
9
+ export const webhookMethods = Object.freeze([
10
+ 'DELETE',
11
+ 'GET',
12
+ 'PATCH',
13
+ 'POST',
14
+ 'PUT',
15
+ ] as const);
16
+
17
+ export type WebhookMethod = (typeof webhookMethods)[number];
18
+ export type WebhookMethodInput = WebhookMethod | Lowercase<WebhookMethod>;
19
+
20
+ export type WebhookVerifyHeaders = Readonly<
21
+ Record<string, readonly string[] | string | undefined>
22
+ >;
23
+
24
+ export interface WebhookVerifyRequest {
25
+ readonly body: ArrayBuffer | Uint8Array | string;
26
+ readonly headers: WebhookVerifyHeaders;
27
+ readonly method: string;
28
+ readonly path: string;
29
+ }
30
+
31
+ export type WebhookVerify = (
32
+ request: WebhookVerifyRequest
33
+ ) => Promise<Result<void, Error>> | Result<void, Error>;
34
+
35
+ export interface WebhookSpec<TOutput = unknown> {
36
+ readonly meta?: ActivationSourceMeta | undefined;
37
+ readonly method?: WebhookMethodInput | undefined;
38
+ readonly parse: ActivationSourceParse<TOutput>;
39
+ readonly path: string;
40
+ readonly payload?: ActivationSource['payload'] | undefined;
41
+ readonly verify?: WebhookVerify | undefined;
42
+ }
43
+
44
+ export interface WebhookSource<TOutput = unknown> extends ActivationSource {
45
+ readonly kind: 'webhook';
46
+ readonly meta?: ActivationSourceMeta | undefined;
47
+ readonly method: WebhookMethod;
48
+ readonly parse: ActivationSourceParse<TOutput>;
49
+ readonly path: string;
50
+ readonly payload?: ActivationSource['payload'] | undefined;
51
+ readonly verify?: WebhookVerify | undefined;
52
+ }
53
+
54
+ export interface WebhookValidationIssue {
55
+ readonly field: 'method' | 'parse' | 'path' | 'verify';
56
+ readonly message: string;
57
+ }
58
+
59
+ const DEFAULT_WEBHOOK_METHOD = 'POST' as const;
60
+
61
+ const normalizeMethod = (
62
+ method: WebhookMethodInput | string | undefined
63
+ ): string => (method ?? DEFAULT_WEBHOOK_METHOD).trim().toUpperCase();
64
+
65
+ const normalizePath = (path: string): string => path.trim();
66
+
67
+ const isWebhookMethod = (method: string): method is WebhookMethod =>
68
+ (webhookMethods as readonly string[]).includes(method);
69
+
70
+ const isObjectRecord = (value: unknown): value is Record<string, unknown> =>
71
+ typeof value === 'object' && value !== null && !Array.isArray(value);
72
+
73
+ const isZodSchema = (value: unknown): boolean =>
74
+ isObjectRecord(value) && typeof value['safeParse'] === 'function';
75
+
76
+ const validateMethod = (
77
+ method: WebhookMethodInput | string | undefined
78
+ ): WebhookValidationIssue[] => {
79
+ const normalized = normalizeMethod(method);
80
+ return isWebhookMethod(normalized)
81
+ ? []
82
+ : [
83
+ {
84
+ field: 'method',
85
+ message: `Webhook method must be one of ${webhookMethods.join(', ')}`,
86
+ },
87
+ ];
88
+ };
89
+
90
+ const validatePath = (path: unknown): WebhookValidationIssue[] => {
91
+ if (typeof path !== 'string' || path.trim().length === 0) {
92
+ return [
93
+ {
94
+ field: 'path',
95
+ message: 'Webhook path must be a non-empty absolute path',
96
+ },
97
+ ];
98
+ }
99
+
100
+ const normalized = normalizePath(path);
101
+ return normalized.startsWith('/')
102
+ ? []
103
+ : [
104
+ {
105
+ field: 'path',
106
+ message: 'Webhook path must start with "/"',
107
+ },
108
+ ];
109
+ };
110
+
111
+ const validateRequiredParse = (parse: unknown): WebhookValidationIssue[] =>
112
+ parse === undefined
113
+ ? [
114
+ {
115
+ field: 'parse',
116
+ message: 'Webhook sources must define parse',
117
+ },
118
+ ]
119
+ : [];
120
+
121
+ const validateParseShape = (parse: unknown): WebhookValidationIssue[] => {
122
+ if (parse === undefined || isZodSchema(parse)) {
123
+ return [];
124
+ }
125
+ if (isObjectRecord(parse) && isZodSchema(parse['output'])) {
126
+ return [];
127
+ }
128
+ return [
129
+ {
130
+ field: 'parse',
131
+ message: 'Webhook parse must be a Zod schema or define parse.output',
132
+ },
133
+ ];
134
+ };
135
+
136
+ const validateVerify = (verify: unknown): WebhookValidationIssue[] =>
137
+ verify === undefined || typeof verify === 'function'
138
+ ? []
139
+ : [
140
+ {
141
+ field: 'verify',
142
+ message: 'Webhook verify must be a function when provided',
143
+ },
144
+ ];
145
+
146
+ const webhookIssuesMessage = (
147
+ id: string,
148
+ issues: readonly WebhookValidationIssue[]
149
+ ): string =>
150
+ `webhook("${id}") is invalid: ${issues.map((issue) => `${issue.field}: ${issue.message}`).join('; ')}`;
151
+
152
+ const assertWebhookSpec = <TOutput>(
153
+ id: string,
154
+ spec: WebhookSpec<TOutput>
155
+ ): {
156
+ readonly method: WebhookMethod;
157
+ readonly path: string;
158
+ } => {
159
+ const issues = [
160
+ ...validateMethod(spec.method),
161
+ ...validatePath(spec.path),
162
+ ...validateRequiredParse(spec.parse),
163
+ ...validateParseShape(spec.parse),
164
+ ...validateVerify(spec.verify),
165
+ ];
166
+
167
+ if (issues.length > 0) {
168
+ throw new ValidationError(webhookIssuesMessage(id, issues), {
169
+ context: { issues },
170
+ });
171
+ }
172
+
173
+ return {
174
+ method: normalizeMethod(spec.method) as WebhookMethod,
175
+ path: normalizePath(spec.path),
176
+ };
177
+ };
178
+
179
+ export const validateWebhookSource = (
180
+ source: ActivationSource
181
+ ): readonly WebhookValidationIssue[] => {
182
+ if (source.kind !== 'webhook') {
183
+ return [];
184
+ }
185
+
186
+ return [
187
+ ...validateMethod(source.method),
188
+ ...validatePath(source.path),
189
+ ...validateRequiredParse(source.parse),
190
+ ...validateParseShape(source.parse),
191
+ ...validateVerify(source.verify),
192
+ ];
193
+ };
194
+
195
+ const errorFromUnknown = (error: unknown): Error =>
196
+ error instanceof Error ? error : new Error(String(error));
197
+
198
+ export const getWebhookHeaders = (
199
+ request: Pick<WebhookVerifyRequest, 'headers'>,
200
+ name: string
201
+ ): readonly string[] => {
202
+ const normalized = name.toLowerCase();
203
+ const matches: string[] = [];
204
+ for (const [headerName, value] of Object.entries(request.headers)) {
205
+ if (headerName.toLowerCase() !== normalized) {
206
+ continue;
207
+ }
208
+ if (value === undefined) {
209
+ continue;
210
+ }
211
+ if (typeof value === 'string') {
212
+ matches.push(value);
213
+ } else {
214
+ matches.push(...value);
215
+ }
216
+ }
217
+ return matches;
218
+ };
219
+
220
+ export const getWebhookHeader = (
221
+ request: Pick<WebhookVerifyRequest, 'headers'>,
222
+ name: string
223
+ ): string | undefined => {
224
+ const [first] = getWebhookHeaders(request, name);
225
+ return first;
226
+ };
227
+
228
+ export const verifyWebhookRequest = async (
229
+ source: Pick<WebhookSource, 'id' | 'verify'>,
230
+ request: WebhookVerifyRequest
231
+ ): Promise<Result<void, Error>> => {
232
+ if (source.verify === undefined) {
233
+ return Result.ok();
234
+ }
235
+
236
+ try {
237
+ return await source.verify(request);
238
+ } catch (error) {
239
+ const cause = errorFromUnknown(error);
240
+ return Result.err(
241
+ new InternalError(`Webhook source "${source.id}" verification threw`, {
242
+ cause,
243
+ })
244
+ );
245
+ }
246
+ };
247
+
248
+ export function webhook<TOutput>(
249
+ id: string,
250
+ spec: WebhookSpec<TOutput>
251
+ ): WebhookSource<TOutput>;
252
+ export function webhook<TOutput>(
253
+ spec: WebhookSpec<TOutput> & { readonly id: string }
254
+ ): WebhookSource<TOutput>;
255
+ export function webhook<TOutput>(
256
+ idOrSpec: string | (WebhookSpec<TOutput> & { readonly id: string }),
257
+ maybeSpec?: WebhookSpec<TOutput>
258
+ ): WebhookSource<TOutput> {
259
+ const id = typeof idOrSpec === 'string' ? idOrSpec : idOrSpec.id;
260
+ // oxlint-disable-next-line no-non-null-assertion -- overload guarantees maybeSpec when idOrSpec is string
261
+ const spec = typeof idOrSpec === 'string' ? maybeSpec! : idOrSpec;
262
+ const normalized = assertWebhookSpec(id, spec);
263
+
264
+ return Object.freeze({
265
+ id,
266
+ kind: 'webhook' as const,
267
+ method: normalized.method,
268
+ parse: spec.parse,
269
+ path: normalized.path,
270
+ ...(spec.meta === undefined
271
+ ? {}
272
+ : { meta: Object.freeze({ ...spec.meta }) }),
273
+ ...(spec.payload === undefined ? {} : { payload: spec.payload }),
274
+ ...(spec.verify === undefined ? {} : { verify: spec.verify }),
275
+ });
276
+ }
@@ -15,8 +15,6 @@
15
15
  * would be redundant at best and would produce an `OptionalOptional<T>` shape
16
16
  * in edge cases at worst. `nullable` wrappers are preserved because nullability
17
17
  * is a semantic constraint that `.partial()` does not reintroduce.
18
- *
19
- * @internal
20
18
  */
21
19
 
22
20
  import type { z } from 'zod';
@@ -35,8 +33,6 @@ const readInnerType = (schema: z.ZodType): z.ZodType =>
35
33
  * (`default`, `optional`, `nullable`), drops defaults, drops `optional` (the
36
34
  * downstream `.partial()` reintroduces it across the whole shape), and
37
35
  * preserves `nullable` so explicit nullability survives.
38
- *
39
- * @internal
40
36
  */
41
37
  export const stripDefaultWrappers = (schema: z.ZodType): z.ZodType => {
42
38
  let current = schema;
@@ -61,8 +57,6 @@ const asObjectSchema = (schema: z.ZodType): AnyObjectSchema =>
61
57
  * Apply {@link stripDefaultWrappers} to every field in a Zod object shape and
62
58
  * return the resulting shape record. Callers typically feed the result to
63
59
  * `.extend()` + `.partial()`.
64
- *
65
- * @internal
66
60
  */
67
61
  export const stripDefaultsFromShape = (
68
62
  schema: z.ZodType
@@ -1 +0,0 @@
1
- $ tsc -b
@@ -1,3 +0,0 @@
1
- $ oxlint ./src
2
- Found 0 warnings and 0 errors.
3
- Finished in 66ms on 99 files with 93 rules using 24 threads.
@@ -1 +0,0 @@
1
- $ tsc --noEmit
@@ -1,20 +0,0 @@
1
- /**
2
- * BlobRef — a frozen reference to binary data for @ontrails/core.
3
- */
4
- /** Immutable reference to a blob of binary data. */
5
- export interface BlobRef {
6
- readonly name: string;
7
- readonly mimeType: string;
8
- readonly size: number;
9
- readonly data: Uint8Array | ReadableStream<Uint8Array>;
10
- }
11
- /** Creates a frozen BlobRef. */
12
- export declare const createBlobRef: (options: {
13
- name: string;
14
- mimeType: string;
15
- size: number;
16
- data: Uint8Array | ReadableStream<Uint8Array>;
17
- }) => BlobRef;
18
- /** Type guard for BlobRef-shaped values. */
19
- export declare const isBlobRef: (value: unknown) => value is BlobRef;
20
- //# sourceMappingURL=blob-ref.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"blob-ref.d.ts","sourceRoot":"","sources":["../src/blob-ref.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,oDAAoD;AACpD,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;CACxD;AAED,gCAAgC;AAChC,eAAO,MAAM,aAAa,GAAI,SAAS;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;CAC/C,KAAG,OAMA,CAAC;AAEL,4CAA4C;AAC5C,eAAO,MAAM,SAAS,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,OAWnD,CAAC"}