@ontrails/core 1.0.0-beta.14 → 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 (314) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/README.md +73 -24
  3. package/package.json +11 -4
  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/branded.ts +1 -1
  10. package/src/context.ts +28 -4
  11. package/src/contour.ts +344 -0
  12. package/src/cross-batch.ts +69 -0
  13. package/src/cross-schema.ts +36 -0
  14. package/src/detours.ts +8 -0
  15. package/src/draft.ts +60 -52
  16. package/src/error-projection.ts +51 -0
  17. package/src/errors.ts +245 -48
  18. package/src/execute.ts +1250 -40
  19. package/src/fire.ts +1169 -0
  20. package/src/index.ts +383 -49
  21. package/src/internal/fork-ctx.ts +69 -0
  22. package/src/layer-projection.ts +192 -0
  23. package/src/layer.ts +81 -0
  24. package/src/observe.ts +361 -0
  25. package/src/path-security.ts +1 -1
  26. package/src/permits.ts +12 -0
  27. package/src/redaction/patterns.ts +6 -3
  28. package/src/resilience.ts +11 -11
  29. package/src/resource-config.ts +792 -0
  30. package/src/resource.ts +175 -0
  31. package/src/result.ts +18 -0
  32. package/src/run.ts +7 -6
  33. package/src/schedule-runtime.ts +689 -0
  34. package/src/schedule.ts +324 -0
  35. package/src/serialization.ts +90 -55
  36. package/src/signal-diagnostics.ts +633 -0
  37. package/src/signal-ref.ts +87 -0
  38. package/src/signal.ts +37 -0
  39. package/src/store/accessor-protocol.ts +56 -0
  40. package/src/store/index.ts +4 -0
  41. package/src/structured-examples.ts +245 -0
  42. package/src/surface-derivation.ts +91 -0
  43. package/src/surface-filter.ts +176 -0
  44. package/src/topo.ts +710 -59
  45. package/src/tracing.ts +345 -0
  46. package/src/trail.ts +347 -33
  47. package/src/trails/derive-trail.ts +842 -0
  48. package/src/trails/index.ts +9 -0
  49. package/src/trails/ingest.ts +138 -0
  50. package/src/{internal/trails-db.ts → trails-db.ts} +50 -31
  51. package/src/transport-error-map.ts +174 -0
  52. package/src/type-utils.ts +17 -2
  53. package/src/types.ts +208 -17
  54. package/src/validate-established-topo.ts +8 -2
  55. package/src/validate-topo.ts +244 -13
  56. package/src/validation.ts +58 -1
  57. package/src/webhook.ts +276 -0
  58. package/src/workspace.ts +1 -1
  59. package/src/zod-wrappers.ts +72 -0
  60. package/.turbo/turbo-build.log +0 -1
  61. package/.turbo/turbo-lint.log +0 -3
  62. package/.turbo/turbo-typecheck.log +0 -1
  63. package/dist/adapters.d.ts +0 -39
  64. package/dist/adapters.d.ts.map +0 -1
  65. package/dist/adapters.js +0 -2
  66. package/dist/adapters.js.map +0 -1
  67. package/dist/blob-ref.d.ts +0 -20
  68. package/dist/blob-ref.d.ts.map +0 -1
  69. package/dist/blob-ref.js +0 -22
  70. package/dist/blob-ref.js.map +0 -1
  71. package/dist/branded.d.ts +0 -36
  72. package/dist/branded.d.ts.map +0 -1
  73. package/dist/branded.js +0 -89
  74. package/dist/branded.js.map +0 -1
  75. package/dist/collections.d.ts +0 -31
  76. package/dist/collections.d.ts.map +0 -1
  77. package/dist/collections.js +0 -60
  78. package/dist/collections.js.map +0 -1
  79. package/dist/context.d.ts +0 -10
  80. package/dist/context.d.ts.map +0 -1
  81. package/dist/context.js +0 -21
  82. package/dist/context.js.map +0 -1
  83. package/dist/derive.d.ts +0 -44
  84. package/dist/derive.d.ts.map +0 -1
  85. package/dist/derive.js +0 -149
  86. package/dist/derive.js.map +0 -1
  87. package/dist/dispatch.d.ts +0 -27
  88. package/dist/dispatch.d.ts.map +0 -1
  89. package/dist/dispatch.js +0 -34
  90. package/dist/dispatch.js.map +0 -1
  91. package/dist/draft.d.ts +0 -28
  92. package/dist/draft.d.ts.map +0 -1
  93. package/dist/draft.js +0 -156
  94. package/dist/draft.js.map +0 -1
  95. package/dist/errors.d.ts +0 -83
  96. package/dist/errors.d.ts.map +0 -1
  97. package/dist/errors.js +0 -142
  98. package/dist/errors.js.map +0 -1
  99. package/dist/event.d.ts +0 -8
  100. package/dist/event.d.ts.map +0 -1
  101. package/dist/event.js +0 -7
  102. package/dist/event.js.map +0 -1
  103. package/dist/execute.d.ts +0 -35
  104. package/dist/execute.d.ts.map +0 -1
  105. package/dist/execute.js +0 -90
  106. package/dist/execute.js.map +0 -1
  107. package/dist/fetch.d.ts +0 -15
  108. package/dist/fetch.d.ts.map +0 -1
  109. package/dist/fetch.js +0 -102
  110. package/dist/fetch.js.map +0 -1
  111. package/dist/gate.d.ts +0 -17
  112. package/dist/gate.d.ts.map +0 -1
  113. package/dist/gate.js +0 -21
  114. package/dist/gate.js.map +0 -1
  115. package/dist/guards.d.ts +0 -17
  116. package/dist/guards.d.ts.map +0 -1
  117. package/dist/guards.js +0 -25
  118. package/dist/guards.js.map +0 -1
  119. package/dist/health.d.ts +0 -18
  120. package/dist/health.d.ts.map +0 -1
  121. package/dist/health.js +0 -5
  122. package/dist/health.js.map +0 -1
  123. package/dist/index.d.ts +0 -48
  124. package/dist/index.d.ts.map +0 -1
  125. package/dist/index.js +0 -51
  126. package/dist/index.js.map +0 -1
  127. package/dist/internal/topo-saves.d.ts +0 -47
  128. package/dist/internal/topo-saves.d.ts.map +0 -1
  129. package/dist/internal/topo-saves.js +0 -310
  130. package/dist/internal/topo-saves.js.map +0 -1
  131. package/dist/internal/topo-store-read.d.ts +0 -67
  132. package/dist/internal/topo-store-read.d.ts.map +0 -1
  133. package/dist/internal/topo-store-read.js +0 -222
  134. package/dist/internal/topo-store-read.js.map +0 -1
  135. package/dist/internal/topo-store.d.ts +0 -12
  136. package/dist/internal/topo-store.d.ts.map +0 -1
  137. package/dist/internal/topo-store.js +0 -571
  138. package/dist/internal/topo-store.js.map +0 -1
  139. package/dist/internal/trails-db.d.ts +0 -16
  140. package/dist/internal/trails-db.d.ts.map +0 -1
  141. package/dist/internal/trails-db.js +0 -118
  142. package/dist/internal/trails-db.js.map +0 -1
  143. package/dist/job.d.ts +0 -24
  144. package/dist/job.d.ts.map +0 -1
  145. package/dist/job.js +0 -17
  146. package/dist/job.js.map +0 -1
  147. package/dist/layer.d.ts +0 -6
  148. package/dist/layer.d.ts.map +0 -1
  149. package/dist/layer.js +0 -4
  150. package/dist/layer.js.map +0 -1
  151. package/dist/path-security.d.ts +0 -28
  152. package/dist/path-security.d.ts.map +0 -1
  153. package/dist/path-security.js +0 -63
  154. package/dist/path-security.js.map +0 -1
  155. package/dist/patterns/bulk.d.ts +0 -15
  156. package/dist/patterns/bulk.d.ts.map +0 -1
  157. package/dist/patterns/bulk.js +0 -14
  158. package/dist/patterns/bulk.js.map +0 -1
  159. package/dist/patterns/change.d.ts +0 -10
  160. package/dist/patterns/change.d.ts.map +0 -1
  161. package/dist/patterns/change.js +0 -10
  162. package/dist/patterns/change.js.map +0 -1
  163. package/dist/patterns/date-range.d.ts +0 -10
  164. package/dist/patterns/date-range.d.ts.map +0 -1
  165. package/dist/patterns/date-range.js +0 -10
  166. package/dist/patterns/date-range.js.map +0 -1
  167. package/dist/patterns/index.d.ts +0 -9
  168. package/dist/patterns/index.d.ts.map +0 -1
  169. package/dist/patterns/index.js +0 -9
  170. package/dist/patterns/index.js.map +0 -1
  171. package/dist/patterns/pagination.d.ts +0 -18
  172. package/dist/patterns/pagination.d.ts.map +0 -1
  173. package/dist/patterns/pagination.js +0 -18
  174. package/dist/patterns/pagination.js.map +0 -1
  175. package/dist/patterns/progress.d.ts +0 -11
  176. package/dist/patterns/progress.d.ts.map +0 -1
  177. package/dist/patterns/progress.js +0 -11
  178. package/dist/patterns/progress.js.map +0 -1
  179. package/dist/patterns/sorting.d.ts +0 -13
  180. package/dist/patterns/sorting.d.ts.map +0 -1
  181. package/dist/patterns/sorting.js +0 -10
  182. package/dist/patterns/sorting.js.map +0 -1
  183. package/dist/patterns/status.d.ts +0 -15
  184. package/dist/patterns/status.d.ts.map +0 -1
  185. package/dist/patterns/status.js +0 -9
  186. package/dist/patterns/status.js.map +0 -1
  187. package/dist/patterns/timestamps.d.ts +0 -10
  188. package/dist/patterns/timestamps.d.ts.map +0 -1
  189. package/dist/patterns/timestamps.js +0 -10
  190. package/dist/patterns/timestamps.js.map +0 -1
  191. package/dist/provision-config.d.ts +0 -22
  192. package/dist/provision-config.d.ts.map +0 -1
  193. package/dist/provision-config.js +0 -210
  194. package/dist/provision-config.js.map +0 -1
  195. package/dist/provision.d.ts +0 -71
  196. package/dist/provision.d.ts.map +0 -1
  197. package/dist/provision.js +0 -56
  198. package/dist/provision.js.map +0 -1
  199. package/dist/redaction/index.d.ts +0 -4
  200. package/dist/redaction/index.d.ts.map +0 -1
  201. package/dist/redaction/index.js +0 -3
  202. package/dist/redaction/index.js.map +0 -1
  203. package/dist/redaction/patterns.d.ts +0 -9
  204. package/dist/redaction/patterns.d.ts.map +0 -1
  205. package/dist/redaction/patterns.js +0 -39
  206. package/dist/redaction/patterns.js.map +0 -1
  207. package/dist/redaction/redactor.d.ts +0 -27
  208. package/dist/redaction/redactor.d.ts.map +0 -1
  209. package/dist/redaction/redactor.js +0 -89
  210. package/dist/redaction/redactor.js.map +0 -1
  211. package/dist/resilience.d.ts +0 -34
  212. package/dist/resilience.d.ts.map +0 -1
  213. package/dist/resilience.js +0 -164
  214. package/dist/resilience.js.map +0 -1
  215. package/dist/result.d.ts +0 -57
  216. package/dist/result.d.ts.map +0 -1
  217. package/dist/result.js +0 -156
  218. package/dist/result.js.map +0 -1
  219. package/dist/run.d.ts +0 -27
  220. package/dist/run.d.ts.map +0 -1
  221. package/dist/run.js +0 -34
  222. package/dist/run.js.map +0 -1
  223. package/dist/serialization.d.ts +0 -27
  224. package/dist/serialization.d.ts.map +0 -1
  225. package/dist/serialization.js +0 -153
  226. package/dist/serialization.js.map +0 -1
  227. package/dist/service-config.d.ts +0 -22
  228. package/dist/service-config.d.ts.map +0 -1
  229. package/dist/service-config.js +0 -210
  230. package/dist/service-config.js.map +0 -1
  231. package/dist/service.d.ts +0 -71
  232. package/dist/service.d.ts.map +0 -1
  233. package/dist/service.js +0 -56
  234. package/dist/service.js.map +0 -1
  235. package/dist/signal.d.ts +0 -33
  236. package/dist/signal.d.ts.map +0 -1
  237. package/dist/signal.js +0 -17
  238. package/dist/signal.js.map +0 -1
  239. package/dist/topo-store.d.ts +0 -48
  240. package/dist/topo-store.d.ts.map +0 -1
  241. package/dist/topo-store.js +0 -175
  242. package/dist/topo-store.js.map +0 -1
  243. package/dist/topo.d.ts +0 -25
  244. package/dist/topo.d.ts.map +0 -1
  245. package/dist/topo.js +0 -112
  246. package/dist/topo.js.map +0 -1
  247. package/dist/trail.d.ts +0 -96
  248. package/dist/trail.d.ts.map +0 -1
  249. package/dist/trail.js +0 -20
  250. package/dist/trail.js.map +0 -1
  251. package/dist/type-utils.d.ts +0 -24
  252. package/dist/type-utils.d.ts.map +0 -1
  253. package/dist/type-utils.js +0 -12
  254. package/dist/type-utils.js.map +0 -1
  255. package/dist/types.d.ts +0 -71
  256. package/dist/types.d.ts.map +0 -1
  257. package/dist/types.js +0 -3
  258. package/dist/types.js.map +0 -1
  259. package/dist/validate-established-topo.d.ts +0 -76
  260. package/dist/validate-established-topo.d.ts.map +0 -1
  261. package/dist/validate-established-topo.js +0 -43
  262. package/dist/validate-established-topo.js.map +0 -1
  263. package/dist/validate-topo.d.ts +0 -24
  264. package/dist/validate-topo.d.ts.map +0 -1
  265. package/dist/validate-topo.js +0 -176
  266. package/dist/validate-topo.js.map +0 -1
  267. package/dist/validation.d.ts +0 -27
  268. package/dist/validation.d.ts.map +0 -1
  269. package/dist/validation.js +0 -165
  270. package/dist/validation.js.map +0 -1
  271. package/dist/workspace.d.ts +0 -25
  272. package/dist/workspace.d.ts.map +0 -1
  273. package/dist/workspace.js +0 -57
  274. package/dist/workspace.js.map +0 -1
  275. package/src/__tests__/blob-ref.test.ts +0 -103
  276. package/src/__tests__/branded.test.ts +0 -148
  277. package/src/__tests__/collections.test.ts +0 -126
  278. package/src/__tests__/context.test.ts +0 -77
  279. package/src/__tests__/derive.test.ts +0 -260
  280. package/src/__tests__/errors.test.ts +0 -309
  281. package/src/__tests__/execute.test.ts +0 -525
  282. package/src/__tests__/fetch.test.ts +0 -217
  283. package/src/__tests__/gate.test.ts +0 -125
  284. package/src/__tests__/guards.test.ts +0 -102
  285. package/src/__tests__/path-security.test.ts +0 -114
  286. package/src/__tests__/patterns.test.ts +0 -273
  287. package/src/__tests__/redaction.test.ts +0 -244
  288. package/src/__tests__/resilience.test.ts +0 -246
  289. package/src/__tests__/result.test.ts +0 -155
  290. package/src/__tests__/run.test.ts +0 -181
  291. package/src/__tests__/serialization.test.ts +0 -401
  292. package/src/__tests__/service-config.test.ts +0 -228
  293. package/src/__tests__/service.test.ts +0 -197
  294. package/src/__tests__/signal.test.ts +0 -82
  295. package/src/__tests__/topo-store-read.test.ts +0 -251
  296. package/src/__tests__/topo-store.test.ts +0 -469
  297. package/src/__tests__/topo.test.ts +0 -277
  298. package/src/__tests__/trail-permit.test.ts +0 -60
  299. package/src/__tests__/trail.test.ts +0 -261
  300. package/src/__tests__/trails-db.test.ts +0 -191
  301. package/src/__tests__/type-utils.test.ts +0 -90
  302. package/src/__tests__/validate-topo.test.ts +0 -489
  303. package/src/__tests__/validation.test.ts +0 -336
  304. package/src/__tests__/workspace.test.ts +0 -183
  305. package/src/event.ts +0 -15
  306. package/src/gate.ts +0 -44
  307. package/src/internal/topo-saves.ts +0 -429
  308. package/src/internal/topo-store-read.ts +0 -473
  309. package/src/internal/topo-store.ts +0 -1087
  310. package/src/provision-config.ts +0 -373
  311. package/src/provision.ts +0 -148
  312. package/src/topo-store.ts +0 -301
  313. package/tsconfig.json +0 -9
  314. package/tsconfig.tsbuildinfo +0 -1
@@ -1,489 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
-
3
- import { z } from 'zod';
4
-
5
- import { analyzeDraftState, isDraftId } from '../draft.js';
6
- import { validateEstablishedTopo } from '../validate-established-topo.js';
7
- import { provision } from '../provision.js';
8
- import { Result } from '../result.js';
9
- import { topo } from '../topo.js';
10
- import type { TopoIssue } from '../validate-topo.js';
11
- import { validateTopo } from '../validate-topo.js';
12
-
13
- // ---------------------------------------------------------------------------
14
- // Helpers
15
- // ---------------------------------------------------------------------------
16
-
17
- // oxlint-disable-next-line require-await -- satisfies async interface
18
- const noop = async () => Result.ok();
19
-
20
- const mockTrail = (
21
- id: string,
22
- overrides?: {
23
- crosses?: readonly string[];
24
- examples?: readonly {
25
- name: string;
26
- input: unknown;
27
- expected?: unknown;
28
- error?: string;
29
- }[];
30
- output?: z.ZodType;
31
- provisions?: readonly ReturnType<typeof provision>[];
32
- }
33
- ) => ({
34
- blaze: noop,
35
- crosses: Object.freeze([...(overrides?.crosses ?? [])]),
36
- id,
37
- input: z.object({ name: z.string() }),
38
- kind: 'trail' as const,
39
- provisions: Object.freeze([...(overrides?.provisions ?? [])]),
40
- ...overrides,
41
- });
42
-
43
- const mockProvision = (id: string) =>
44
- provision(id, {
45
- create: () => Result.ok({ id }),
46
- });
47
-
48
- const mockEvent = (id: string, from?: readonly string[]) => ({
49
- from,
50
- id,
51
- kind: 'signal' as const,
52
- payload: z.object({ data: z.string() }),
53
- });
54
-
55
- /** Extract issues from a failed validateTopo result. */
56
- const extractIssues = (result: Result<void, Error>): TopoIssue[] => {
57
- if (result.isOk()) {
58
- return [];
59
- }
60
- const ctx = (result.error as { context?: { issues?: TopoIssue[] } }).context;
61
- return (ctx?.issues ?? []) as TopoIssue[];
62
- };
63
-
64
- // ---------------------------------------------------------------------------
65
- // Tests
66
- // ---------------------------------------------------------------------------
67
-
68
- describe('validateTopo', () => {
69
- test('valid topo passes', () => {
70
- const app = topo('app', {
71
- add: mockTrail('entity.add'),
72
- onboard: mockTrail('entity.onboard', {
73
- crosses: ['entity.add'],
74
- }),
75
- updated: mockEvent('entity.updated', ['entity.add']),
76
- });
77
-
78
- const result = validateTopo(app);
79
- expect(result.isOk()).toBe(true);
80
- });
81
-
82
- describe('trail crossing', () => {
83
- test('draft crossings are allowed in the authored graph', () => {
84
- const app = topo('app', {
85
- exportTrail: mockTrail('entity.export', {
86
- crosses: ['_draft.entity.prepare'],
87
- }),
88
- });
89
-
90
- const result = validateTopo(app);
91
- expect(result.isOk()).toBe(true);
92
- });
93
-
94
- test('trail crossing a non-existent trail fails', () => {
95
- const app = topo('app', {
96
- onboard: mockTrail('entity.onboard', {
97
- crosses: ['entity.missing'],
98
- }),
99
- });
100
-
101
- const result = validateTopo(app);
102
- expect(result.isErr()).toBe(true);
103
-
104
- const issues = extractIssues(result);
105
- expect(issues).toHaveLength(1);
106
- expect(issues[0]?.rule).toBe('cross-exists');
107
- expect(issues[0]?.message).toContain('entity.missing');
108
- });
109
-
110
- test('trail crossing itself fails', () => {
111
- const app = topo('app', {
112
- loop: mockTrail('entity.loop', { crosses: ['entity.loop'] }),
113
- });
114
-
115
- const result = validateTopo(app);
116
- expect(result.isErr()).toBe(true);
117
-
118
- const issues = extractIssues(result);
119
- expect(issues.some((i) => i.rule === 'no-self-cross')).toBe(true);
120
- });
121
-
122
- test('two-node cycle (a→b→a) is detected', () => {
123
- const app = topo('app', {
124
- a: mockTrail('a', { crosses: ['b'] }),
125
- b: mockTrail('b', { crosses: ['a'] }),
126
- });
127
-
128
- const result = validateTopo(app);
129
- expect(result.isErr()).toBe(true);
130
-
131
- const issues = extractIssues(result);
132
- const cycleIssues = issues.filter((i) => i.rule === 'cross-cycle');
133
- expect(cycleIssues.length).toBeGreaterThanOrEqual(1);
134
- expect(cycleIssues[0]?.message).toContain('Cycle detected');
135
- });
136
-
137
- test('three-node cycle (a→b→c→a) is detected', () => {
138
- const app = topo('app', {
139
- a: mockTrail('a', { crosses: ['b'] }),
140
- b: mockTrail('b', { crosses: ['c'] }),
141
- c: mockTrail('c', { crosses: ['a'] }),
142
- });
143
-
144
- const result = validateTopo(app);
145
- expect(result.isErr()).toBe(true);
146
-
147
- const issues = extractIssues(result);
148
- const cycleIssues = issues.filter((i) => i.rule === 'cross-cycle');
149
- expect(cycleIssues.length).toBeGreaterThanOrEqual(1);
150
- expect(cycleIssues[0]?.message).toContain('Cycle detected');
151
- });
152
-
153
- test('valid DAG with shared targets is not flagged', () => {
154
- const app = topo('app', {
155
- a: mockTrail('a', { crosses: ['c'] }),
156
- b: mockTrail('b', { crosses: ['c'] }),
157
- c: mockTrail('c'),
158
- });
159
-
160
- const result = validateTopo(app);
161
- expect(result.isOk()).toBe(true);
162
- });
163
- });
164
-
165
- describe('provision declarations', () => {
166
- test('draft provision references are allowed in the authored graph', () => {
167
- const db = mockProvision('_draft.db.main');
168
- const app = topo('app', {
169
- show: mockTrail('entity.show', {
170
- provisions: [db],
171
- }),
172
- });
173
-
174
- const result = validateTopo(app);
175
- expect(result.isOk()).toBe(true);
176
- });
177
-
178
- test('trail declaring a registered provision passes', () => {
179
- const db = mockProvision('db.main');
180
- const app = topo('app', {
181
- db,
182
- show: mockTrail('entity.show', {
183
- provisions: [db],
184
- }),
185
- });
186
-
187
- const result = validateTopo(app);
188
- expect(result.isOk()).toBe(true);
189
- });
190
-
191
- test('trail declaring a missing provision fails', () => {
192
- const db = mockProvision('db.main');
193
- const app = topo('app', {
194
- show: mockTrail('entity.show', {
195
- provisions: [db],
196
- }),
197
- });
198
-
199
- const result = validateTopo(app);
200
- expect(result.isErr()).toBe(true);
201
-
202
- const issues = extractIssues(result);
203
- expect(issues).toHaveLength(1);
204
- expect(issues[0]?.rule).toBe('provision-exists');
205
- expect(issues[0]?.message).toContain('db.main');
206
- });
207
- });
208
-
209
- describe('example validation', () => {
210
- test('example with invalid input fails', () => {
211
- const app = topo('app', {
212
- show: mockTrail('entity.show', {
213
- examples: [{ input: { name: 123 }, name: 'Bad input' }],
214
- }),
215
- });
216
-
217
- const result = validateTopo(app);
218
- expect(result.isErr()).toBe(true);
219
-
220
- const issues = extractIssues(result);
221
- expect(issues).toHaveLength(1);
222
- expect(issues[0]?.rule).toBe('example-input-valid');
223
- expect(issues[0]?.message).toContain('Bad input');
224
- });
225
-
226
- test('example with expected output but no output schema warns', () => {
227
- const app = topo('app', {
228
- show: mockTrail('entity.show', {
229
- examples: [
230
- {
231
- expected: { result: 'ok' },
232
- input: { name: 'test' },
233
- name: 'Has expected',
234
- },
235
- ],
236
- }),
237
- });
238
-
239
- const result = validateTopo(app);
240
- expect(result.isErr()).toBe(true);
241
-
242
- const issues = extractIssues(result);
243
- expect(issues).toHaveLength(1);
244
- expect(issues[0]?.rule).toBe('output-schema-present');
245
- });
246
-
247
- test('ValidationError example with invalid input is allowed', () => {
248
- const app = topo('app', {
249
- show: mockTrail('entity.show', {
250
- examples: [
251
- {
252
- error: 'ValidationError',
253
- input: { name: 123 },
254
- name: 'Error case',
255
- },
256
- ],
257
- }),
258
- });
259
-
260
- const result = validateTopo(app);
261
- expect(result.isOk()).toBe(true);
262
- });
263
-
264
- test('NotFoundError example with invalid input fails', () => {
265
- const app = topo('app', {
266
- show: mockTrail('entity.show', {
267
- examples: [
268
- {
269
- error: 'NotFoundError',
270
- input: { name: 123 },
271
- name: 'Not found case',
272
- },
273
- ],
274
- }),
275
- });
276
-
277
- const result = validateTopo(app);
278
- expect(result.isErr()).toBe(true);
279
-
280
- const issues = extractIssues(result);
281
- expect(issues).toHaveLength(1);
282
- expect(issues[0]?.rule).toBe('example-input-valid');
283
- });
284
-
285
- test('NotFoundError example with valid input passes', () => {
286
- const app = topo('app', {
287
- show: mockTrail('entity.show', {
288
- examples: [
289
- {
290
- error: 'NotFoundError',
291
- input: { name: 'test' },
292
- name: 'Not found case',
293
- },
294
- ],
295
- }),
296
- });
297
-
298
- const result = validateTopo(app);
299
- expect(result.isOk()).toBe(true);
300
- });
301
- });
302
-
303
- describe('event origins', () => {
304
- test('event with non-existent origin fails', () => {
305
- const app = topo('app', {
306
- updated: mockEvent('entity.updated', ['entity.ghost']),
307
- });
308
-
309
- const result = validateTopo(app);
310
- expect(result.isErr()).toBe(true);
311
-
312
- const issues = extractIssues(result);
313
- expect(issues).toHaveLength(1);
314
- expect(issues[0]?.rule).toBe('signal-origin-exists');
315
- expect(issues[0]?.message).toContain('entity.ghost');
316
- });
317
-
318
- test('event without origins is accepted', () => {
319
- const app = topo('app', {
320
- updated: mockEvent('entity.updated'),
321
- });
322
-
323
- const result = validateTopo(app);
324
- expect(result.isOk()).toBe(true);
325
- });
326
- });
327
-
328
- test('collects multiple issues', () => {
329
- const db = mockProvision('db.main');
330
- const app = topo('app', {
331
- broken: mockTrail('entity.broken', { crosses: ['entity.missing'] }),
332
- missingService: mockTrail('entity.missing-service', {
333
- provisions: [db],
334
- }),
335
- show: mockTrail('entity.show', {
336
- examples: [{ input: { name: 123 }, name: 'Bad' }],
337
- }),
338
- updated: mockEvent('entity.updated', ['entity.ghost']),
339
- });
340
-
341
- const result = validateTopo(app);
342
- expect(result.isErr()).toBe(true);
343
-
344
- const issues = extractIssues(result);
345
- expect(issues).toHaveLength(4);
346
- });
347
-
348
- describe('signal origins', () => {
349
- test('draft signal origins are allowed in the authored graph', () => {
350
- const app = topo('app', {
351
- updated: mockEvent('entity.updated', ['_draft.entity.prepare']),
352
- });
353
-
354
- const result = validateTopo(app);
355
- expect(result.isOk()).toBe(true);
356
- });
357
- });
358
- });
359
-
360
- describe('draft state analysis', () => {
361
- test('detects declared draft ids', () => {
362
- const app = topo('app', {
363
- draftTrail: mockTrail('_draft.entity.prepare'),
364
- });
365
-
366
- const analysis = analyzeDraftState(app);
367
-
368
- expect(analysis.declaredDraftIds.has('_draft.entity.prepare')).toBe(true);
369
- expect(analysis.contaminatedIds.has('_draft.entity.prepare')).toBe(true);
370
- expect(analysis.findings.map((finding) => finding.rule)).toContain(
371
- 'draft-id'
372
- );
373
- });
374
-
375
- test('propagates contamination through dependencies', () => {
376
- const app = topo('app', {
377
- exportTrail: mockTrail('entity.export', {
378
- crosses: ['entity.prepare'],
379
- }),
380
- prepareTrail: mockTrail('entity.prepare', {
381
- crosses: ['_draft.entity.store'],
382
- }),
383
- });
384
-
385
- const analysis = analyzeDraftState(app);
386
- const exportFinding = analysis.findings.find(
387
- (finding) => finding.id === 'entity.export'
388
- );
389
-
390
- expect(analysis.contaminatedIds.has('entity.prepare')).toBe(true);
391
- expect(analysis.contaminatedIds.has('entity.export')).toBe(true);
392
- expect(exportFinding).toBeDefined();
393
- expect(exportFinding?.rule).toBe('draft-contamination');
394
- });
395
- });
396
-
397
- describe('validateEstablishedTopo', () => {
398
- test('passes for an established topo', () => {
399
- const app = topo('app', {
400
- show: mockTrail('entity.show'),
401
- });
402
-
403
- const result = validateEstablishedTopo(app);
404
- expect(result.isOk()).toBe(true);
405
- });
406
-
407
- test('fails when draft declarations remain', () => {
408
- const app = topo('app', {
409
- draftTrail: mockTrail('_draft.entity.prepare'),
410
- });
411
-
412
- const result = validateEstablishedTopo(app);
413
- expect(result.isErr()).toBe(true);
414
-
415
- const issues = extractIssues(result);
416
- expect(issues).toHaveLength(1);
417
- expect(issues[0]?.rule).toBe('draft-id');
418
- });
419
-
420
- test('fails when established trails depend on draft state', () => {
421
- const app = topo('app', {
422
- exportTrail: mockTrail('entity.export', {
423
- crosses: ['_draft.entity.prepare'],
424
- }),
425
- });
426
-
427
- const result = validateEstablishedTopo(app);
428
- expect(result.isErr()).toBe(true);
429
-
430
- const issues = extractIssues(result);
431
- expect(issues).toHaveLength(1);
432
- expect(issues[0]?.rule).toBe('draft-contamination');
433
- expect(issues[0]?.message).toContain('entity.export');
434
- });
435
-
436
- test('fails when authored structural validation still fails', () => {
437
- const app = topo('app', {
438
- exportTrail: mockTrail('entity.export', {
439
- crosses: ['entity.missing'],
440
- }),
441
- });
442
-
443
- const result = validateEstablishedTopo(app);
444
- expect(result.isErr()).toBe(true);
445
-
446
- const issues = extractIssues(result);
447
- expect(issues).toHaveLength(1);
448
- expect(issues[0]?.rule).toBe('cross-exists');
449
- });
450
-
451
- test('fails when provision declarations are not established in the topo', () => {
452
- const app = topo('app', {
453
- show: mockTrail('entity.show', {
454
- provisions: [mockProvision('db.main')],
455
- }),
456
- });
457
-
458
- const result = validateEstablishedTopo(app);
459
- expect(result.isErr()).toBe(true);
460
-
461
- const issues = extractIssues(result);
462
- expect(issues).toHaveLength(1);
463
- expect(issues[0]?.rule).toBe('provision-exists');
464
- });
465
-
466
- test('allows authoring-only example issues outside projection checks', () => {
467
- const app = topo('app', {
468
- show: mockTrail('entity.show', {
469
- examples: [
470
- {
471
- expected: { ok: true },
472
- input: { name: 'test' },
473
- name: 'Has expected',
474
- },
475
- ],
476
- }),
477
- });
478
-
479
- const result = validateEstablishedTopo(app);
480
- expect(result.isOk()).toBe(true);
481
- });
482
- });
483
-
484
- describe('isDraftId', () => {
485
- test('recognizes the reserved draft prefix', () => {
486
- expect(isDraftId('_draft.entity.show')).toBe(true);
487
- expect(isDraftId('entity.show')).toBe(false);
488
- });
489
- });