@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
@@ -0,0 +1,430 @@
1
+ import type { z } from 'zod';
2
+
3
+ import type { ActivationSource } from './activation-source.js';
4
+ import type { AnySignal } from './signal.js';
5
+ import { zodToJsonSchema } from './validation.js';
6
+
7
+ export interface ActivationSchemaIssue {
8
+ readonly code: string;
9
+ readonly message: string;
10
+ readonly path: readonly (string | number)[];
11
+ }
12
+
13
+ type SourcePayloadContract =
14
+ | {
15
+ readonly schema: z.ZodType<unknown>;
16
+ readonly type: 'schema';
17
+ }
18
+ | {
19
+ readonly type: 'value';
20
+ readonly value: unknown;
21
+ };
22
+
23
+ type JsonSchemaObject = Record<string, unknown>;
24
+
25
+ const EMPTY_SOURCE_INPUT = Object.freeze({});
26
+
27
+ const hasOwn = (value: object, key: string): boolean =>
28
+ Object.hasOwn(value, key);
29
+
30
+ const isObjectRecord = (value: unknown): value is Record<string, unknown> =>
31
+ typeof value === 'object' && value !== null && !Array.isArray(value);
32
+
33
+ const isZodSchema = (value: unknown): value is z.ZodType<unknown> =>
34
+ isObjectRecord(value) &&
35
+ typeof value['safeParse'] === 'function' &&
36
+ isObjectRecord(value['_zod']);
37
+
38
+ const normalizeIssuePath = (
39
+ path: readonly unknown[]
40
+ ): readonly (string | number)[] =>
41
+ Object.freeze(
42
+ path.map((segment) =>
43
+ typeof segment === 'string' || typeof segment === 'number'
44
+ ? segment
45
+ : String(segment)
46
+ )
47
+ );
48
+
49
+ const getActivationPayloadContract = (
50
+ source: ActivationSource,
51
+ signals: ReadonlyMap<string, AnySignal>
52
+ ): SourcePayloadContract | undefined => {
53
+ if (source.kind === 'signal') {
54
+ const signal = signals.get(source.id);
55
+ return signal ? { schema: signal.payload, type: 'schema' } : undefined;
56
+ }
57
+
58
+ if (source.kind === 'schedule' && !hasOwn(source, 'input')) {
59
+ return { type: 'value', value: EMPTY_SOURCE_INPUT };
60
+ }
61
+
62
+ if (isZodSchema(source.parse)) {
63
+ return { schema: source.parse, type: 'schema' };
64
+ }
65
+
66
+ if (isObjectRecord(source.parse) && isZodSchema(source.parse['output'])) {
67
+ return { schema: source.parse['output'], type: 'schema' };
68
+ }
69
+
70
+ if (isZodSchema(source.payload)) {
71
+ return { schema: source.payload, type: 'schema' };
72
+ }
73
+
74
+ if (isZodSchema(source.input)) {
75
+ return { schema: source.input, type: 'schema' };
76
+ }
77
+
78
+ if (hasOwn(source, 'input')) {
79
+ return { type: 'value', value: source.input };
80
+ }
81
+
82
+ return undefined;
83
+ };
84
+
85
+ const zodIssuesToCompatibilityIssues = (
86
+ issues: readonly z.ZodIssue[]
87
+ ): readonly ActivationSchemaIssue[] =>
88
+ Object.freeze(
89
+ issues.map((issue) => ({
90
+ code: issue.code,
91
+ message: issue.message,
92
+ path: normalizeIssuePath(issue.path),
93
+ }))
94
+ );
95
+
96
+ const jsonSchemaFor = (schema: z.ZodType<unknown>): JsonSchemaObject =>
97
+ zodToJsonSchema(schema) as JsonSchemaObject;
98
+
99
+ const jsonObjectAt = (
100
+ schema: JsonSchemaObject,
101
+ key: string
102
+ ): JsonSchemaObject | undefined => {
103
+ const value = schema[key];
104
+ return isObjectRecord(value) ? value : undefined;
105
+ };
106
+
107
+ const jsonObjectEntriesAt = (
108
+ schema: JsonSchemaObject,
109
+ key: string
110
+ ): readonly [string, JsonSchemaObject][] => {
111
+ const value = schema[key];
112
+ if (!isObjectRecord(value)) {
113
+ return [];
114
+ }
115
+ return Object.entries(value).filter(
116
+ (entry): entry is [string, JsonSchemaObject] => isObjectRecord(entry[1])
117
+ );
118
+ };
119
+
120
+ const jsonSchemaArrayAt = (
121
+ schema: JsonSchemaObject,
122
+ key: string
123
+ ): readonly JsonSchemaObject[] => {
124
+ const value = schema[key];
125
+ if (!Array.isArray(value)) {
126
+ return [];
127
+ }
128
+ return value.filter(isObjectRecord);
129
+ };
130
+
131
+ const stringArrayAt = (
132
+ schema: JsonSchemaObject,
133
+ key: string
134
+ ): readonly string[] => {
135
+ const value = schema[key];
136
+ return Array.isArray(value)
137
+ ? value.filter((entry): entry is string => typeof entry === 'string')
138
+ : [];
139
+ };
140
+
141
+ const primitiveTypeOf = (value: unknown): string => {
142
+ if (value === null) {
143
+ return 'null';
144
+ }
145
+ if (Array.isArray(value)) {
146
+ return 'array';
147
+ }
148
+ return typeof value;
149
+ };
150
+
151
+ const schemaTypes = (
152
+ schema: JsonSchemaObject
153
+ ): ReadonlySet<string> | undefined => {
154
+ const { type } = schema;
155
+ if (typeof type === 'string') {
156
+ return new Set([type]);
157
+ }
158
+ if (Array.isArray(type)) {
159
+ const types = type.filter(
160
+ (entry): entry is string => typeof entry === 'string'
161
+ );
162
+ return types.length > 0 ? new Set(types) : undefined;
163
+ }
164
+ if (hasOwn(schema, 'const')) {
165
+ return new Set([primitiveTypeOf(schema['const'])]);
166
+ }
167
+ const enumValues = schema['enum'];
168
+ if (Array.isArray(enumValues) && enumValues.length > 0) {
169
+ return new Set(enumValues.map(primitiveTypeOf));
170
+ }
171
+
172
+ const anyOf = jsonSchemaArrayAt(schema, 'anyOf');
173
+ if (anyOf.length > 0) {
174
+ const allTypes = new Set<string>();
175
+ for (const option of anyOf) {
176
+ const optionTypes = schemaTypes(option);
177
+ if (!optionTypes) {
178
+ return undefined;
179
+ }
180
+ for (const optionType of optionTypes) {
181
+ allTypes.add(optionType);
182
+ }
183
+ }
184
+ return allTypes;
185
+ }
186
+
187
+ return undefined;
188
+ };
189
+
190
+ const setsIntersect = (
191
+ left: ReadonlySet<string>,
192
+ right: ReadonlySet<string>
193
+ ): boolean => {
194
+ for (const entry of left) {
195
+ if (right.has(entry)) {
196
+ return true;
197
+ }
198
+ }
199
+ return false;
200
+ };
201
+
202
+ const describeSchemaTypes = (types: ReadonlySet<string> | undefined): string =>
203
+ types ? [...types].toSorted().join(' | ') : 'unknown';
204
+
205
+ const valueSet = (
206
+ schema: JsonSchemaObject
207
+ ): ReadonlySet<unknown> | undefined => {
208
+ if (hasOwn(schema, 'const')) {
209
+ return new Set([schema['const']]);
210
+ }
211
+ const enumValues = schema['enum'];
212
+ if (Array.isArray(enumValues)) {
213
+ return new Set(enumValues);
214
+ }
215
+
216
+ const anyOf = jsonSchemaArrayAt(schema, 'anyOf');
217
+ if (anyOf.length === 0) {
218
+ return undefined;
219
+ }
220
+
221
+ const values = new Set<unknown>();
222
+ for (const option of anyOf) {
223
+ const optionValues = valueSet(option);
224
+ if (optionValues === undefined) {
225
+ return undefined;
226
+ }
227
+ for (const value of optionValues) {
228
+ values.add(value);
229
+ }
230
+ }
231
+ return values;
232
+ };
233
+
234
+ const allValuesIn = (
235
+ source: ReadonlySet<unknown>,
236
+ target: ReadonlySet<unknown>
237
+ ): boolean => {
238
+ for (const value of source) {
239
+ if (!target.has(value)) {
240
+ return false;
241
+ }
242
+ }
243
+ return true;
244
+ };
245
+
246
+ const compatibilityIssue = (
247
+ code: string,
248
+ path: readonly (string | number)[],
249
+ message: string
250
+ ): ActivationSchemaIssue => ({
251
+ code,
252
+ message,
253
+ path: Object.freeze([...path]),
254
+ });
255
+
256
+ const sourceGuaranteesObjectProperty = (
257
+ key: string,
258
+ required: ReadonlySet<string>,
259
+ properties: ReadonlyMap<string, JsonSchemaObject>
260
+ ): boolean => {
261
+ const property = properties.get(key);
262
+ return (
263
+ property !== undefined && (required.has(key) || hasOwn(property, 'default'))
264
+ );
265
+ };
266
+
267
+ const checkEnumCompatibility = (
268
+ source: JsonSchemaObject,
269
+ target: JsonSchemaObject,
270
+ path: readonly (string | number)[]
271
+ ): readonly ActivationSchemaIssue[] => {
272
+ if (!hasOwn(target, 'const') && !Array.isArray(target['enum'])) {
273
+ return [];
274
+ }
275
+
276
+ const targetValues = valueSet(target);
277
+ const sourceValues = valueSet(source);
278
+ if (targetValues && sourceValues && allValuesIn(sourceValues, targetValues)) {
279
+ return [];
280
+ }
281
+
282
+ return [
283
+ compatibilityIssue(
284
+ hasOwn(target, 'const') ? 'const' : 'enum',
285
+ path,
286
+ 'Source schema can produce values outside the accepted input values'
287
+ ),
288
+ ];
289
+ };
290
+
291
+ const checkTypeCompatibility = (
292
+ source: JsonSchemaObject,
293
+ target: JsonSchemaObject,
294
+ path: readonly (string | number)[]
295
+ ): readonly ActivationSchemaIssue[] => {
296
+ const targetTypes = schemaTypes(target);
297
+ if (!targetTypes) {
298
+ return [];
299
+ }
300
+
301
+ const sourceTypes = schemaTypes(source);
302
+ if (sourceTypes && setsIntersect(sourceTypes, targetTypes)) {
303
+ return [];
304
+ }
305
+
306
+ return [
307
+ compatibilityIssue(
308
+ 'type',
309
+ path,
310
+ `Source schema type ${describeSchemaTypes(sourceTypes)} is not compatible with input schema type ${describeSchemaTypes(targetTypes)}`
311
+ ),
312
+ ];
313
+ };
314
+
315
+ const checkSchemaCompatibility = (
316
+ source: JsonSchemaObject,
317
+ target: JsonSchemaObject,
318
+ path: readonly (string | number)[] = Object.freeze([])
319
+ ): readonly ActivationSchemaIssue[] => {
320
+ if (Object.keys(target).length === 0) {
321
+ return [];
322
+ }
323
+
324
+ const sourceOptions = jsonSchemaArrayAt(source, 'anyOf');
325
+ if (sourceOptions.length > 0) {
326
+ return sourceOptions.flatMap((option) =>
327
+ checkSchemaCompatibility(option, target, path)
328
+ );
329
+ }
330
+
331
+ const targetOptions = jsonSchemaArrayAt(target, 'anyOf');
332
+ if (targetOptions.length > 0) {
333
+ const optionIssues = targetOptions.map((option) =>
334
+ checkSchemaCompatibility(source, option, path)
335
+ );
336
+ return optionIssues.some((issues) => issues.length === 0)
337
+ ? []
338
+ : (optionIssues[0] ?? []);
339
+ }
340
+
341
+ const issues = [
342
+ ...checkEnumCompatibility(source, target, path),
343
+ ...checkTypeCompatibility(source, target, path),
344
+ ];
345
+ if (issues.length > 0) {
346
+ return issues;
347
+ }
348
+
349
+ const objectIssues = (() => {
350
+ if (target['type'] !== 'object') {
351
+ return [];
352
+ }
353
+
354
+ const sourceRequired = new Set(stringArrayAt(source, 'required'));
355
+ const sourceProperties = new Map(jsonObjectEntriesAt(source, 'properties'));
356
+ const targetProperties = new Map(jsonObjectEntriesAt(target, 'properties'));
357
+ const nestedIssues: ActivationSchemaIssue[] = [];
358
+
359
+ for (const requiredKey of stringArrayAt(target, 'required')) {
360
+ if (
361
+ !sourceGuaranteesObjectProperty(
362
+ requiredKey,
363
+ sourceRequired,
364
+ sourceProperties
365
+ )
366
+ ) {
367
+ nestedIssues.push(
368
+ compatibilityIssue(
369
+ 'required',
370
+ [...path, requiredKey],
371
+ `Source schema does not guarantee required input field "${requiredKey}"`
372
+ )
373
+ );
374
+ }
375
+ }
376
+
377
+ for (const [key, targetProperty] of targetProperties) {
378
+ const sourceProperty = sourceProperties.get(key);
379
+ if (!sourceProperty) {
380
+ continue;
381
+ }
382
+ nestedIssues.push(
383
+ ...checkSchemaCompatibility(sourceProperty, targetProperty, [
384
+ ...path,
385
+ key,
386
+ ])
387
+ );
388
+ }
389
+
390
+ return nestedIssues;
391
+ })();
392
+ if (objectIssues.length > 0) {
393
+ return objectIssues;
394
+ }
395
+
396
+ if (target['type'] !== 'array') {
397
+ return [];
398
+ }
399
+
400
+ const sourceItems = jsonObjectAt(source, 'items');
401
+ const targetItems = jsonObjectAt(target, 'items');
402
+ if (!sourceItems || !targetItems) {
403
+ return [];
404
+ }
405
+
406
+ return checkSchemaCompatibility(sourceItems, targetItems, [...path, '*']);
407
+ };
408
+
409
+ export const getActivationSourceInputCompatibilityIssues = (
410
+ inputSchema: z.ZodType<unknown>,
411
+ source: ActivationSource,
412
+ signals: ReadonlyMap<string, AnySignal>
413
+ ): readonly ActivationSchemaIssue[] | undefined => {
414
+ const contract = getActivationPayloadContract(source, signals);
415
+ if (!contract) {
416
+ return undefined;
417
+ }
418
+
419
+ if (contract.type === 'value') {
420
+ const parsed = inputSchema.safeParse(contract.value);
421
+ return parsed.success
422
+ ? []
423
+ : zodIssuesToCompatibilityIssues(parsed.error.issues);
424
+ }
425
+
426
+ return checkSchemaCompatibility(
427
+ jsonSchemaFor(contract.schema),
428
+ jsonSchemaFor(inputSchema)
429
+ );
430
+ };
@@ -0,0 +1,221 @@
1
+ import type { ActivationSource } from './activation-source.js';
2
+ import { zodToJsonSchema } from './validation.js';
3
+
4
+ type ZodSchemaInput = Parameters<typeof zodToJsonSchema>[0];
5
+
6
+ export type ActivationSourceProjection = Readonly<Record<string, unknown>> & {
7
+ readonly id: string;
8
+ readonly key: string;
9
+ readonly kind: string;
10
+ };
11
+
12
+ export const activationSourceKey = (
13
+ source: Pick<ActivationSource, 'id' | 'kind'>
14
+ ): string => `${source.kind}:${source.id}`;
15
+
16
+ const sortKeys = <T extends Record<string, unknown>>(value: T): T => {
17
+ const sorted: Record<string, unknown> = {};
18
+ for (const key of Object.keys(value).toSorted()) {
19
+ sorted[key] = value[key];
20
+ }
21
+ return sorted as T;
22
+ };
23
+
24
+ const deepSortKeys = (value: unknown): unknown => {
25
+ if (Array.isArray(value)) {
26
+ return value.map(deepSortKeys);
27
+ }
28
+ if (value !== null && typeof value === 'object') {
29
+ const sorted: Record<string, unknown> = {};
30
+ for (const key of Object.keys(value).toSorted()) {
31
+ sorted[key] = deepSortKeys((value as Record<string, unknown>)[key]);
32
+ }
33
+ return sorted;
34
+ }
35
+ return value;
36
+ };
37
+
38
+ const canonicalLeaf = (value: unknown): unknown => {
39
+ switch (typeof value) {
40
+ case 'bigint': {
41
+ return value.toString();
42
+ }
43
+ case 'function': {
44
+ return `[Function:${value.name || 'anonymous'}]`;
45
+ }
46
+ case 'symbol': {
47
+ return `[Symbol:${value.description ?? ''}]`;
48
+ }
49
+ case 'undefined': {
50
+ return '[Undefined]';
51
+ }
52
+ default: {
53
+ return value;
54
+ }
55
+ }
56
+ };
57
+
58
+ const canonicalObject = (
59
+ value: Record<string, unknown>,
60
+ visit: (value: unknown) => unknown
61
+ ): Record<string, unknown> => {
62
+ const sorted: Record<string, unknown> = {};
63
+ for (const key of Object.keys(value).toSorted()) {
64
+ const next = value[key];
65
+ sorted[key] = next === undefined ? '[Undefined]' : visit(next);
66
+ }
67
+ return sorted;
68
+ };
69
+
70
+ const canonicalize = (value: unknown): unknown => {
71
+ if (Array.isArray(value)) {
72
+ return value.map(canonicalize);
73
+ }
74
+ if (value instanceof Date) {
75
+ return value.toISOString();
76
+ }
77
+ if (value instanceof RegExp) {
78
+ return value.toString();
79
+ }
80
+ if (value !== null && typeof value === 'object') {
81
+ return canonicalObject(value as Record<string, unknown>, canonicalize);
82
+ }
83
+ return canonicalLeaf(value);
84
+ };
85
+
86
+ const isObjectRecord = (value: unknown): value is Record<string, unknown> =>
87
+ typeof value === 'object' && value !== null && !Array.isArray(value);
88
+
89
+ const isZodSchema = (value: unknown): value is ZodSchemaInput =>
90
+ isObjectRecord(value) &&
91
+ typeof value['safeParse'] === 'function' &&
92
+ isObjectRecord(value['_zod']);
93
+
94
+ const toSortedJsonSchema = (
95
+ schema: ZodSchemaInput
96
+ ): Readonly<Record<string, unknown>> =>
97
+ deepSortKeys(zodToJsonSchema(schema)) as Readonly<Record<string, unknown>>;
98
+
99
+ const parseOutputSchema = (
100
+ source: ActivationSource
101
+ ): ZodSchemaInput | undefined => {
102
+ if (isZodSchema(source.parse)) {
103
+ return source.parse;
104
+ }
105
+ if (isObjectRecord(source.parse) && isZodSchema(source.parse['output'])) {
106
+ return source.parse['output'];
107
+ }
108
+ return undefined;
109
+ };
110
+
111
+ const normalizeWebhookMethod = (method: string | undefined): string =>
112
+ (method ?? 'POST').trim().toUpperCase();
113
+
114
+ const normalizeWebhookPath = (path: string): string => path.trim();
115
+
116
+ export const projectActivationSourceDeclaration = (
117
+ source: ActivationSource
118
+ ): ActivationSourceProjection => {
119
+ const record: Record<string, unknown> = {
120
+ id: source.id,
121
+ key: activationSourceKey(source),
122
+ kind: source.kind,
123
+ };
124
+
125
+ if (source.cron !== undefined) {
126
+ record['cron'] = source.cron;
127
+ }
128
+ if (Object.hasOwn(source, 'input')) {
129
+ if (isZodSchema(source.input)) {
130
+ record['inputSchema'] = toSortedJsonSchema(source.input);
131
+ } else {
132
+ record['input'] = canonicalize(source.input);
133
+ }
134
+ }
135
+ if (source.meta !== undefined) {
136
+ record['meta'] = canonicalize(source.meta);
137
+ }
138
+ if (source.kind === 'webhook') {
139
+ record['method'] = normalizeWebhookMethod(source.method);
140
+ } else if (source.method !== undefined) {
141
+ record['method'] = source.method;
142
+ }
143
+ if (source.parse !== undefined) {
144
+ record['hasParse'] = true;
145
+ const output = parseOutputSchema(source);
146
+ if (output !== undefined) {
147
+ record['parseOutputSchema'] = toSortedJsonSchema(output);
148
+ }
149
+ }
150
+ if (source.path !== undefined) {
151
+ record['path'] =
152
+ source.kind === 'webhook'
153
+ ? normalizeWebhookPath(source.path)
154
+ : source.path;
155
+ }
156
+ if (source.payload !== undefined) {
157
+ record['hasPayloadSchema'] = true;
158
+ if (isZodSchema(source.payload)) {
159
+ record['payloadSchema'] = toSortedJsonSchema(source.payload);
160
+ }
161
+ }
162
+ if (source.timezone !== undefined) {
163
+ record['timezone'] = source.timezone;
164
+ }
165
+ if (source.verify !== undefined) {
166
+ record['hasVerify'] = true;
167
+ }
168
+
169
+ return sortKeys(record) as ActivationSourceProjection;
170
+ };
171
+
172
+ /**
173
+ * Identifies the verifier function attached to an activation source, when one
174
+ * exists. Two declarations sharing the same id/method/path/parse but different
175
+ * `verify` references must be treated as conflicting source options. The
176
+ * returned token captures the function's reference identity so it changes when
177
+ * the verifier function changes.
178
+ *
179
+ * The token is intentionally kept out of {@link projectActivationSourceDeclaration}
180
+ * so that the persisted topo-store projection remains stable and free of
181
+ * nondeterministic function identity. Use this only for in-memory comparisons
182
+ * (validation, conflict detection).
183
+ */
184
+ const verifierIds = new WeakMap<object, number>();
185
+ let verifierIdCounter = 0;
186
+
187
+ const verifierIdToken = (verify: object): string => {
188
+ const existing = verifierIds.get(verify);
189
+ if (existing !== undefined) {
190
+ return `verify#${existing}`;
191
+ }
192
+ verifierIdCounter += 1;
193
+ verifierIds.set(verify, verifierIdCounter);
194
+ return `verify#${verifierIdCounter}`;
195
+ };
196
+
197
+ const verifierIdentityToken = (
198
+ source: ActivationSource
199
+ ): string | undefined => {
200
+ if (source.kind !== 'webhook') {
201
+ return undefined;
202
+ }
203
+ const { verify } = source as { readonly verify?: unknown };
204
+ if (typeof verify !== 'function') {
205
+ return undefined;
206
+ }
207
+ // Use a per-process WeakMap-backed counter so two different function
208
+ // references produce different tokens, even if they share a name.
209
+ return verifierIdToken(verify);
210
+ };
211
+
212
+ export const activationSourceDeclarationSignature = (
213
+ source: ActivationSource
214
+ ): string => {
215
+ const projection = projectActivationSourceDeclaration(source);
216
+ const verifyToken = verifierIdentityToken(source);
217
+ if (verifyToken === undefined) {
218
+ return JSON.stringify(projection);
219
+ }
220
+ return JSON.stringify({ projection, verify: verifyToken });
221
+ };