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

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 (280) hide show
  1. package/.turbo/turbo-lint.log +1 -1
  2. package/CHANGELOG.md +11 -0
  3. package/README.md +17 -13
  4. package/dist/branded.d.ts +1 -1
  5. package/dist/branded.d.ts.map +1 -1
  6. package/dist/branded.js +1 -1
  7. package/dist/branded.js.map +1 -1
  8. package/dist/context.d.ts +13 -1
  9. package/dist/context.d.ts.map +1 -1
  10. package/dist/context.js +19 -3
  11. package/dist/context.js.map +1 -1
  12. package/dist/contour.d.ts +81 -0
  13. package/dist/contour.d.ts.map +1 -0
  14. package/dist/contour.js +176 -0
  15. package/dist/contour.js.map +1 -0
  16. package/dist/cross-schema.d.ts +18 -0
  17. package/dist/cross-schema.d.ts.map +1 -0
  18. package/dist/cross-schema.js +29 -0
  19. package/dist/cross-schema.js.map +1 -0
  20. package/dist/draft.d.ts +5 -5
  21. package/dist/draft.d.ts.map +1 -1
  22. package/dist/draft.js +28 -27
  23. package/dist/draft.js.map +1 -1
  24. package/dist/errors.d.ts +62 -4
  25. package/dist/errors.d.ts.map +1 -1
  26. package/dist/errors.js +44 -1
  27. package/dist/errors.js.map +1 -1
  28. package/dist/execute.d.ts +26 -9
  29. package/dist/execute.d.ts.map +1 -1
  30. package/dist/execute.js +441 -20
  31. package/dist/execute.js.map +1 -1
  32. package/dist/fire.d.ts +43 -0
  33. package/dist/fire.d.ts.map +1 -0
  34. package/dist/fire.js +185 -0
  35. package/dist/fire.js.map +1 -0
  36. package/dist/index.d.ts +31 -20
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +23 -13
  39. package/dist/index.js.map +1 -1
  40. package/dist/internal/cross-batch.d.ts +44 -0
  41. package/dist/internal/cross-batch.d.ts.map +1 -0
  42. package/dist/internal/cross-batch.js +58 -0
  43. package/dist/internal/cross-batch.js.map +1 -0
  44. package/dist/internal/fork-ctx.d.ts +41 -0
  45. package/dist/internal/fork-ctx.d.ts.map +1 -0
  46. package/dist/internal/fork-ctx.js +47 -0
  47. package/dist/internal/fork-ctx.js.map +1 -0
  48. package/dist/internal/signal-ref.d.ts +15 -0
  49. package/dist/internal/signal-ref.d.ts.map +1 -0
  50. package/dist/internal/signal-ref.js +41 -0
  51. package/dist/internal/signal-ref.js.map +1 -0
  52. package/dist/internal/topo-saves.d.ts +8 -2
  53. package/dist/internal/topo-saves.d.ts.map +1 -1
  54. package/dist/internal/topo-saves.js +162 -23
  55. package/dist/internal/topo-saves.js.map +1 -1
  56. package/dist/internal/topo-snapshots.d.ts +57 -0
  57. package/dist/internal/topo-snapshots.d.ts.map +1 -0
  58. package/dist/internal/topo-snapshots.js +372 -0
  59. package/dist/internal/topo-snapshots.js.map +1 -0
  60. package/dist/internal/topo-store-read.d.ts +23 -20
  61. package/dist/internal/topo-store-read.d.ts.map +1 -1
  62. package/dist/internal/topo-store-read.js +82 -83
  63. package/dist/internal/topo-store-read.js.map +1 -1
  64. package/dist/internal/topo-store.d.ts +19 -5
  65. package/dist/internal/topo-store.d.ts.map +1 -1
  66. package/dist/internal/topo-store.js +174 -124
  67. package/dist/internal/topo-store.js.map +1 -1
  68. package/dist/internal/tracing.d.ts +98 -0
  69. package/dist/internal/tracing.d.ts.map +1 -0
  70. package/dist/internal/tracing.js +102 -0
  71. package/dist/internal/tracing.js.map +1 -0
  72. package/dist/internal/trails-db.d.ts +2 -2
  73. package/dist/internal/trails-db.d.ts.map +1 -1
  74. package/dist/internal/trails-db.js +8 -8
  75. package/dist/internal/trails-db.js.map +1 -1
  76. package/dist/internal/zod-wrappers.d.ts +40 -0
  77. package/dist/internal/zod-wrappers.d.ts.map +1 -0
  78. package/dist/internal/zod-wrappers.js +59 -0
  79. package/dist/internal/zod-wrappers.js.map +1 -0
  80. package/dist/layer.d.ts +22 -5
  81. package/dist/layer.d.ts.map +1 -1
  82. package/dist/layer.js +20 -3
  83. package/dist/layer.js.map +1 -1
  84. package/dist/path-security.d.ts +1 -1
  85. package/dist/path-security.d.ts.map +1 -1
  86. package/dist/path-security.js +1 -1
  87. package/dist/path-security.js.map +1 -1
  88. package/dist/resilience.d.ts +1 -1
  89. package/dist/resilience.d.ts.map +1 -1
  90. package/dist/resilience.js +3 -3
  91. package/dist/resilience.js.map +1 -1
  92. package/dist/resource-config.d.ts +22 -0
  93. package/dist/resource-config.d.ts.map +1 -0
  94. package/dist/resource-config.js +210 -0
  95. package/dist/resource-config.js.map +1 -0
  96. package/dist/resource.d.ts +74 -0
  97. package/dist/resource.d.ts.map +1 -0
  98. package/dist/resource.js +61 -0
  99. package/dist/resource.js.map +1 -0
  100. package/dist/run.d.ts +4 -3
  101. package/dist/run.d.ts.map +1 -1
  102. package/dist/run.js +5 -4
  103. package/dist/run.js.map +1 -1
  104. package/dist/serialization.d.ts.map +1 -1
  105. package/dist/serialization.js +2 -1
  106. package/dist/serialization.js.map +1 -1
  107. package/dist/store/accessor-protocol.d.ts +51 -0
  108. package/dist/store/accessor-protocol.d.ts.map +1 -0
  109. package/dist/store/accessor-protocol.js +2 -0
  110. package/dist/store/accessor-protocol.js.map +1 -0
  111. package/dist/store/index.d.ts +2 -0
  112. package/dist/store/index.d.ts.map +1 -0
  113. package/dist/store/index.js +2 -0
  114. package/dist/store/index.js.map +1 -0
  115. package/dist/surface-filter.d.ts +13 -0
  116. package/dist/surface-filter.d.ts.map +1 -0
  117. package/dist/surface-filter.js +76 -0
  118. package/dist/surface-filter.js.map +1 -0
  119. package/dist/topo-store.d.ts +36 -22
  120. package/dist/topo-store.d.ts.map +1 -1
  121. package/dist/topo-store.js +215 -71
  122. package/dist/topo-store.js.map +1 -1
  123. package/dist/topo.d.ts +23 -9
  124. package/dist/topo.d.ts.map +1 -1
  125. package/dist/topo.js +175 -46
  126. package/dist/topo.js.map +1 -1
  127. package/dist/trail.d.ts +86 -19
  128. package/dist/trail.d.ts.map +1 -1
  129. package/dist/trail.js +59 -4
  130. package/dist/trail.js.map +1 -1
  131. package/dist/trails/derive-trail.d.ts +66 -0
  132. package/dist/trails/derive-trail.d.ts.map +1 -0
  133. package/dist/trails/derive-trail.js +378 -0
  134. package/dist/trails/derive-trail.js.map +1 -0
  135. package/dist/trails/index.d.ts +5 -0
  136. package/dist/trails/index.d.ts.map +1 -0
  137. package/dist/trails/index.js +3 -0
  138. package/dist/trails/index.js.map +1 -0
  139. package/dist/trails/ingest.d.ts +24 -0
  140. package/dist/trails/ingest.d.ts.map +1 -0
  141. package/dist/trails/ingest.js +63 -0
  142. package/dist/trails/ingest.js.map +1 -0
  143. package/dist/transport-error-map.d.ts +106 -0
  144. package/dist/transport-error-map.d.ts.map +1 -0
  145. package/dist/transport-error-map.js +24 -0
  146. package/dist/transport-error-map.js.map +1 -0
  147. package/dist/type-checks.test-d.d.ts +71 -0
  148. package/dist/type-checks.test-d.d.ts.map +1 -0
  149. package/dist/type-checks.test-d.js +12 -0
  150. package/dist/type-checks.test-d.js.map +1 -0
  151. package/dist/type-utils.d.ts +11 -2
  152. package/dist/type-utils.d.ts.map +1 -1
  153. package/dist/type-utils.js.map +1 -1
  154. package/dist/types.d.ts +113 -7
  155. package/dist/types.d.ts.map +1 -1
  156. package/dist/types.js.map +1 -1
  157. package/dist/validate-established-topo.js +2 -2
  158. package/dist/validate-established-topo.js.map +1 -1
  159. package/dist/validate-topo.d.ts.map +1 -1
  160. package/dist/validate-topo.js +24 -7
  161. package/dist/validate-topo.js.map +1 -1
  162. package/dist/workspace.d.ts +1 -1
  163. package/dist/workspace.d.ts.map +1 -1
  164. package/dist/workspace.js +1 -1
  165. package/dist/workspace.js.map +1 -1
  166. package/package.json +8 -2
  167. package/src/__tests__/branded.test.ts +5 -5
  168. package/src/__tests__/context.test.ts +3 -3
  169. package/src/__tests__/contour.test.ts +263 -0
  170. package/src/__tests__/derive-trail.test.ts +668 -0
  171. package/src/__tests__/errors.test.ts +118 -0
  172. package/src/__tests__/execute.test.ts +1179 -141
  173. package/src/__tests__/fire.test.ts +1056 -0
  174. package/src/__tests__/fork-ctx.test.ts +83 -0
  175. package/src/__tests__/ingest.test.ts +191 -0
  176. package/src/__tests__/{gate.test.ts → layer.test.ts} +21 -21
  177. package/src/__tests__/path-security.test.ts +7 -7
  178. package/src/__tests__/resilience.test.ts +8 -8
  179. package/src/__tests__/resource.test.ts +204 -0
  180. package/src/__tests__/run.test.ts +13 -13
  181. package/src/__tests__/service-config.test.ts +28 -32
  182. package/src/__tests__/surface-filter.test.ts +196 -0
  183. package/src/__tests__/topo-store-read.test.ts +41 -36
  184. package/src/__tests__/topo-store.test.ts +493 -93
  185. package/src/__tests__/topo.test.ts +350 -54
  186. package/src/__tests__/trail.test.ts +189 -24
  187. package/src/__tests__/trails-db.test.ts +32 -25
  188. package/src/__tests__/transport-error-map.test.ts +82 -0
  189. package/src/__tests__/type-utils.test.ts +29 -1
  190. package/src/__tests__/validate-topo.test.ts +147 -25
  191. package/src/__tests__/workspace.test.ts +6 -6
  192. package/src/__tests__/zod-wrappers.test.ts +62 -0
  193. package/src/branded.ts +1 -1
  194. package/src/context.ts +24 -4
  195. package/src/contour.ts +344 -0
  196. package/src/cross-schema.ts +36 -0
  197. package/src/draft.ts +56 -52
  198. package/src/errors.ts +62 -18
  199. package/src/execute.ts +840 -29
  200. package/src/fire.ts +295 -0
  201. package/src/index.ts +116 -29
  202. package/src/internal/cross-batch.ts +77 -0
  203. package/src/internal/fork-ctx.ts +69 -0
  204. package/src/internal/signal-ref.ts +87 -0
  205. package/src/internal/topo-snapshots.ts +517 -0
  206. package/src/internal/topo-store-read.ts +140 -132
  207. package/src/internal/topo-store.ts +291 -186
  208. package/src/internal/tracing.ts +187 -0
  209. package/src/internal/trails-db.ts +9 -11
  210. package/src/internal/zod-wrappers.ts +78 -0
  211. package/src/layer.ts +50 -0
  212. package/src/path-security.ts +1 -1
  213. package/src/resilience.ts +3 -3
  214. package/src/{provision-config.ts → resource-config.ts} +109 -123
  215. package/src/resource.ts +155 -0
  216. package/src/run.ts +5 -4
  217. package/src/serialization.ts +2 -0
  218. package/src/store/accessor-protocol.ts +56 -0
  219. package/src/store/index.ts +4 -0
  220. package/src/surface-filter.ts +182 -0
  221. package/src/topo-store.ts +313 -104
  222. package/src/topo.ts +328 -57
  223. package/src/trail.ts +199 -31
  224. package/src/trails/derive-trail.ts +842 -0
  225. package/src/trails/index.ts +9 -0
  226. package/src/trails/ingest.ts +146 -0
  227. package/src/transport-error-map.ts +56 -0
  228. package/src/type-checks.test-d.ts +106 -0
  229. package/src/type-utils.ts +17 -2
  230. package/src/types.ts +148 -11
  231. package/src/validate-established-topo.ts +2 -2
  232. package/src/validate-topo.ts +32 -8
  233. package/src/workspace.ts +1 -1
  234. package/tsconfig.tests.json +10 -0
  235. package/tsconfig.tsbuildinfo +1 -1
  236. package/dist/adapters.d.ts +0 -39
  237. package/dist/adapters.d.ts.map +0 -1
  238. package/dist/adapters.js +0 -2
  239. package/dist/adapters.js.map +0 -1
  240. package/dist/dispatch.d.ts +0 -27
  241. package/dist/dispatch.d.ts.map +0 -1
  242. package/dist/dispatch.js +0 -34
  243. package/dist/dispatch.js.map +0 -1
  244. package/dist/event.d.ts +0 -8
  245. package/dist/event.d.ts.map +0 -1
  246. package/dist/event.js +0 -7
  247. package/dist/event.js.map +0 -1
  248. package/dist/gate.d.ts +0 -17
  249. package/dist/gate.d.ts.map +0 -1
  250. package/dist/gate.js +0 -21
  251. package/dist/gate.js.map +0 -1
  252. package/dist/health.d.ts +0 -18
  253. package/dist/health.d.ts.map +0 -1
  254. package/dist/health.js +0 -5
  255. package/dist/health.js.map +0 -1
  256. package/dist/job.d.ts +0 -24
  257. package/dist/job.d.ts.map +0 -1
  258. package/dist/job.js +0 -17
  259. package/dist/job.js.map +0 -1
  260. package/dist/provision-config.d.ts +0 -22
  261. package/dist/provision-config.d.ts.map +0 -1
  262. package/dist/provision-config.js +0 -210
  263. package/dist/provision-config.js.map +0 -1
  264. package/dist/provision.d.ts +0 -71
  265. package/dist/provision.d.ts.map +0 -1
  266. package/dist/provision.js +0 -56
  267. package/dist/provision.js.map +0 -1
  268. package/dist/service-config.d.ts +0 -22
  269. package/dist/service-config.d.ts.map +0 -1
  270. package/dist/service-config.js +0 -210
  271. package/dist/service-config.js.map +0 -1
  272. package/dist/service.d.ts +0 -71
  273. package/dist/service.d.ts.map +0 -1
  274. package/dist/service.js +0 -56
  275. package/dist/service.js.map +0 -1
  276. package/src/__tests__/service.test.ts +0 -197
  277. package/src/event.ts +0 -15
  278. package/src/gate.ts +0 -44
  279. package/src/internal/topo-saves.ts +0 -429
  280. package/src/provision.ts +0 -148
@@ -1,23 +1,31 @@
1
1
  import type { Database, SQLQueryBindings } from 'bun:sqlite';
2
2
 
3
+ import { getContourReferences } from '../contour.js';
3
4
  import { deriveCliPath } from '../derive.js';
4
5
  import { Result } from '../result.js';
5
- import type { AnyProvision } from '../provision.js';
6
+ import type { AnyContour } from '../contour.js';
7
+ import type { AnyResource } from '../resource.js';
6
8
  import type { AnySignal } from '../signal.js';
7
9
  import type { Topo } from '../topo.js';
8
10
  import type { AnyTrail } from '../trail.js';
9
11
  import { validateEstablishedTopo } from '../validate-established-topo.js';
10
12
  import { zodToJsonSchema } from '../validation.js';
11
- import type { CreateTopoSaveInput, TopoSaveRecord } from './topo-saves.js';
12
- import { ensureTopoHistorySchema, insertTopoSaveRecord } from './topo-saves.js';
13
-
14
- type TrailheadMapEntryRecord = Readonly<Record<string, unknown>> & {
13
+ import type {
14
+ CreateTopoSnapshotInput,
15
+ TopoSnapshot,
16
+ } from './topo-snapshots.js';
17
+ import {
18
+ ensureTopoSnapshotSchema,
19
+ insertTopoSnapshotRecord,
20
+ } from './topo-snapshots.js';
21
+
22
+ type SurfaceMapEntryRecord = Readonly<Record<string, unknown>> & {
15
23
  readonly id: string;
16
- readonly kind: 'provision' | 'signal' | 'trail';
24
+ readonly kind: 'contour' | 'resource' | 'signal' | 'trail';
17
25
  };
18
26
 
19
- type TrailheadMapRecord = Readonly<{
20
- readonly entries: readonly TrailheadMapEntryRecord[];
27
+ type SurfaceMapRecord = Readonly<{
28
+ readonly entries: readonly SurfaceMapEntryRecord[];
21
29
  readonly generatedAt: string;
22
30
  readonly version: '1.0';
23
31
  }>;
@@ -34,57 +42,71 @@ interface TopoTrailRow {
34
42
  readonly idempotent: number;
35
43
  readonly intent: string;
36
44
  readonly meta: string | null;
37
- readonly saveId: string;
45
+ readonly pattern: string | null;
46
+ readonly snapshotId: string;
38
47
  }
39
48
 
40
49
  interface TopoCrossingRow {
41
- readonly saveId: string;
50
+ readonly snapshotId: string;
42
51
  readonly sourceId: string;
43
52
  readonly targetId: string;
44
53
  }
45
54
 
46
- interface TopoTrailProvisionRow {
47
- readonly provisionId: string;
48
- readonly saveId: string;
55
+ interface TopoFiresRow {
56
+ readonly snapshotId: string;
57
+ readonly signalId: string;
58
+ readonly trailId: string;
59
+ }
60
+
61
+ interface TopoOnRow {
62
+ readonly snapshotId: string;
63
+ readonly signalId: string;
49
64
  readonly trailId: string;
50
65
  }
51
66
 
52
- interface TopoProvisionRow {
67
+ interface TopoTrailResourceRow {
68
+ readonly resourceId: string;
69
+ readonly snapshotId: string;
70
+ readonly trailId: string;
71
+ }
72
+
73
+ interface TopoResourceRow {
53
74
  readonly hasHealth: number;
54
75
  readonly hasMock: number;
55
76
  readonly id: string;
56
- readonly saveId: string;
77
+ readonly snapshotId: string;
57
78
  }
58
79
 
59
80
  interface TopoSignalRow {
60
81
  readonly description: string | null;
61
82
  readonly id: string;
62
- readonly saveId: string;
83
+ readonly snapshotId: string;
63
84
  }
64
85
 
65
86
  interface TopoTrailSignalRow {
66
- readonly saveId: string;
87
+ readonly snapshotId: string;
67
88
  readonly signalId: string;
68
89
  readonly trailId: string;
69
90
  }
70
91
 
71
- interface TopoTrailheadRow {
92
+ interface TopoSurfaceRow {
72
93
  readonly derivedName: string;
73
94
  readonly method: string | null;
74
- readonly saveId: string;
95
+ readonly snapshotId: string;
75
96
  readonly trailId: string;
76
- readonly trailhead: string;
97
+ readonly surface: string;
77
98
  }
78
99
 
79
100
  interface TopoExampleRow {
80
101
  readonly description: string | null;
81
102
  readonly error: string | null;
82
103
  readonly expected: string | null;
104
+ readonly expectedMatch: string | null;
83
105
  readonly id: string;
84
106
  readonly input: string;
85
107
  readonly name: string;
86
108
  readonly ordinal: number;
87
- readonly saveId: string;
109
+ readonly snapshotId: string;
88
110
  readonly trailId: string;
89
111
  }
90
112
 
@@ -92,28 +114,28 @@ interface TopoSchemaRow {
92
114
  readonly jsonSchema: string;
93
115
  readonly ownerId: string;
94
116
  readonly ownerKind: 'signal' | 'trail';
95
- readonly saveId: string;
117
+ readonly snapshotId: string;
96
118
  readonly schemaKind: 'input' | 'output' | 'payload';
97
119
  readonly zodHash: string;
98
120
  }
99
121
 
100
122
  interface StoredTopoExportRow {
101
- readonly saveId: string;
123
+ readonly snapshotId: string;
102
124
  readonly serializedLock: string;
103
- readonly trailheadHash: string;
104
- readonly trailheadMap: string;
125
+ readonly surfaceHash: string;
126
+ readonly surfaceMap: string;
105
127
  }
106
128
 
107
129
  interface StoredTopoExportDbRow {
108
130
  readonly serialized_lock: string;
109
- readonly trailhead_hash: string;
110
- readonly trailhead_map: string;
131
+ readonly surface_hash: string;
132
+ readonly surface_map: string;
111
133
  }
112
134
 
113
135
  export interface StoredTopoExport {
114
136
  readonly lockContent: string;
115
- readonly trailheadHash: string;
116
- readonly trailheadMapJson: string;
137
+ readonly surfaceHash: string;
138
+ readonly surfaceMapJson: string;
117
139
  }
118
140
 
119
141
  interface MaterializedSchemas {
@@ -136,10 +158,12 @@ interface MaterializedTopoArtifacts {
136
158
  interface NormalizedTopoProjection {
137
159
  readonly crossings: readonly TopoCrossingRow[];
138
160
  readonly examples: readonly TopoExampleRow[];
139
- readonly provisions: readonly TopoProvisionRow[];
161
+ readonly fires: readonly TopoFiresRow[];
162
+ readonly on: readonly TopoOnRow[];
163
+ readonly resources: readonly TopoResourceRow[];
140
164
  readonly signals: readonly TopoSignalRow[];
141
- readonly trailheads: readonly TopoTrailheadRow[];
142
- readonly trailProvisions: readonly TopoTrailProvisionRow[];
165
+ readonly surfaces: readonly TopoSurfaceRow[];
166
+ readonly trailResources: readonly TopoTrailResourceRow[];
143
167
  readonly trailSignals: readonly TopoTrailSignalRow[];
144
168
  readonly trails: readonly TopoTrailRow[];
145
169
  }
@@ -262,7 +286,7 @@ const sortKeys = <T extends Record<string, unknown>>(value: T): T => {
262
286
 
263
287
  const normalizeTrailRows = (
264
288
  trails: readonly AnyTrail[],
265
- saveId: string
289
+ snapshotId: string
266
290
  ): readonly TopoTrailRow[] =>
267
291
  trails.map((trail) => ({
268
292
  description: trail.description ?? null,
@@ -273,97 +297,122 @@ const normalizeTrailRows = (
273
297
  idempotent: trail.idempotent === true ? 1 : 0,
274
298
  intent: trail.intent,
275
299
  meta: trail.meta === undefined ? null : stableJson(trail.meta),
276
- saveId,
300
+ pattern: trail.pattern ?? null,
301
+ snapshotId,
277
302
  }));
278
303
 
279
304
  const normalizeCrossingRows = (
280
305
  trails: readonly AnyTrail[],
281
- saveId: string
306
+ snapshotId: string
282
307
  ): readonly TopoCrossingRow[] =>
283
308
  trails.flatMap((trail) =>
284
309
  [...new Set(trail.crosses)].toSorted().map((targetId) => ({
285
- saveId,
310
+ snapshotId,
286
311
  sourceId: trail.id,
287
312
  targetId,
288
313
  }))
289
314
  );
290
315
 
291
- const normalizeTrailProvisionRows = (
316
+ export const normalizeFiresRows = (
317
+ trails: readonly AnyTrail[],
318
+ snapshotId: string
319
+ ): readonly TopoFiresRow[] =>
320
+ trails.flatMap((trail) =>
321
+ [...new Set(trail.fires)].toSorted().map((signalId) => ({
322
+ signalId,
323
+ snapshotId,
324
+ trailId: trail.id,
325
+ }))
326
+ );
327
+
328
+ export const normalizeOnRows = (
292
329
  trails: readonly AnyTrail[],
293
- saveId: string
294
- ): readonly TopoTrailProvisionRow[] =>
330
+ snapshotId: string
331
+ ): readonly TopoOnRow[] =>
295
332
  trails.flatMap((trail) =>
296
- [...new Set(trail.provisions.map((provision) => provision.id))]
333
+ [...new Set(trail.on)].toSorted().map((signalId) => ({
334
+ signalId,
335
+ snapshotId,
336
+ trailId: trail.id,
337
+ }))
338
+ );
339
+
340
+ const normalizeTrailResourceRows = (
341
+ trails: readonly AnyTrail[],
342
+ snapshotId: string
343
+ ): readonly TopoTrailResourceRow[] =>
344
+ trails.flatMap((trail) =>
345
+ [...new Set(trail.resources.map((resource) => resource.id))]
297
346
  .toSorted()
298
- .map((provisionId) => ({
299
- provisionId,
300
- saveId,
347
+ .map((resourceId) => ({
348
+ resourceId,
349
+ snapshotId,
301
350
  trailId: trail.id,
302
351
  }))
303
352
  );
304
353
 
305
- const normalizeProvisionRows = (
306
- provisions: readonly AnyProvision[],
307
- saveId: string
308
- ): readonly TopoProvisionRow[] =>
309
- provisions.map((provision) => ({
310
- hasHealth: provision.health === undefined ? 0 : 1,
311
- hasMock: provision.mock === undefined ? 0 : 1,
312
- id: provision.id,
313
- saveId,
354
+ const normalizeResourceRows = (
355
+ resources: readonly AnyResource[],
356
+ snapshotId: string
357
+ ): readonly TopoResourceRow[] =>
358
+ resources.map((resource) => ({
359
+ hasHealth: resource.health === undefined ? 0 : 1,
360
+ hasMock: resource.mock === undefined ? 0 : 1,
361
+ id: resource.id,
362
+ snapshotId,
314
363
  }));
315
364
 
316
365
  const normalizeSignalRows = (
317
366
  signals: readonly AnySignal[],
318
- saveId: string
367
+ snapshotId: string
319
368
  ): readonly TopoSignalRow[] =>
320
369
  signals.map((signal) => ({
321
370
  description: signal.description ?? null,
322
371
  id: signal.id,
323
- saveId,
372
+ snapshotId,
324
373
  }));
325
374
 
326
375
  const normalizeTrailSignalRows = (
327
376
  signals: readonly AnySignal[],
328
- saveId: string
377
+ snapshotId: string
329
378
  ): readonly TopoTrailSignalRow[] =>
330
379
  signals.flatMap((signal) =>
331
380
  [...new Set(signal.from)].toSorted().map((trailId) => ({
332
- saveId,
333
381
  signalId: signal.id,
382
+ snapshotId,
334
383
  trailId,
335
384
  }))
336
385
  );
337
386
 
338
387
  /**
339
- * Project trailhead rows for stored topo.
388
+ * Project surface rows for stored topo.
340
389
  *
341
- * Currently records only CLI-derived rows. MCP, HTTP, and other trailhead
390
+ * Currently records only CLI-derived rows. MCP, HTTP, and other surface
342
391
  * projections are intentionally deferred until the topo-store schema supports
343
- * multi-trailhead representation. The JSON export (`trailhead_map` in
392
+ * multi-surface representation. The JSON export (`surface_map` in
344
393
  * `topo_exports`) is more faithful for now. See ADR-0015 for the target shape.
345
394
  */
346
- const normalizeTrailheadRows = (
395
+ const normalizeSurfaceRows = (
347
396
  trails: readonly AnyTrail[],
348
- saveId: string
349
- ): readonly TopoTrailheadRow[] =>
397
+ snapshotId: string
398
+ ): readonly TopoSurfaceRow[] =>
350
399
  trails.map((trail) => ({
351
400
  derivedName: deriveCliPath(trail.id).join(' '),
352
401
  method: null,
353
- saveId,
402
+ snapshotId,
403
+ surface: 'cli',
354
404
  trailId: trail.id,
355
- trailhead: 'cli',
356
405
  }));
357
406
 
358
407
  const buildExampleId = (
359
- saveId: string,
408
+ snapshotId: string,
360
409
  trailId: string,
361
410
  ordinal: number
362
- ): string => `${saveId}:${trailId}:${ordinal.toString().padStart(4, '0')}`;
411
+ ): string => `${snapshotId}:${trailId}:${ordinal.toString().padStart(4, '0')}`;
363
412
 
364
413
  const normalizeExampleRows = (
365
414
  trails: readonly AnyTrail[],
366
- saveId: string
415
+ snapshotId: string
367
416
  ): readonly TopoExampleRow[] =>
368
417
  trails.flatMap((trail) =>
369
418
  (trail.examples ?? []).map((example, index) => ({
@@ -371,46 +420,52 @@ const normalizeExampleRows = (
371
420
  error: example.error ?? null,
372
421
  expected:
373
422
  example.expected === undefined ? null : stableJson(example.expected),
374
- id: buildExampleId(saveId, trail.id, index),
423
+ expectedMatch:
424
+ example.expectedMatch === undefined
425
+ ? null
426
+ : stableJson(example.expectedMatch),
427
+ id: buildExampleId(snapshotId, trail.id, index),
375
428
  input: stableJson(example.input),
376
429
  name: example.name,
377
430
  ordinal: index,
378
- saveId,
431
+ snapshotId,
379
432
  trailId: trail.id,
380
433
  }))
381
434
  );
382
435
 
383
436
  const normalizeTopoProjection = (
384
437
  topo: Topo,
385
- saveId: string
438
+ snapshotId: string
386
439
  ): NormalizedTopoProjection => {
387
440
  const trails = topo.list().toSorted((a, b) => a.id.localeCompare(b.id));
388
- const provisions = topo
389
- .listProvisions()
441
+ const resources = topo
442
+ .listResources()
390
443
  .toSorted((a, b) => a.id.localeCompare(b.id));
391
444
  const signals = topo
392
445
  .listSignals()
393
446
  .toSorted((a, b) => a.id.localeCompare(b.id));
394
447
 
395
448
  return {
396
- crossings: normalizeCrossingRows(trails, saveId),
397
- examples: normalizeExampleRows(trails, saveId),
398
- provisions: normalizeProvisionRows(provisions, saveId),
399
- signals: normalizeSignalRows(signals, saveId),
400
- trailProvisions: normalizeTrailProvisionRows(trails, saveId),
401
- trailSignals: normalizeTrailSignalRows(signals, saveId),
402
- trailheads: normalizeTrailheadRows(trails, saveId),
403
- trails: normalizeTrailRows(trails, saveId),
449
+ crossings: normalizeCrossingRows(trails, snapshotId),
450
+ examples: normalizeExampleRows(trails, snapshotId),
451
+ fires: normalizeFiresRows(trails, snapshotId),
452
+ on: normalizeOnRows(trails, snapshotId),
453
+ resources: normalizeResourceRows(resources, snapshotId),
454
+ signals: normalizeSignalRows(signals, snapshotId),
455
+ surfaces: normalizeSurfaceRows(trails, snapshotId),
456
+ trailResources: normalizeTrailResourceRows(trails, snapshotId),
457
+ trailSignals: normalizeTrailSignalRows(signals, snapshotId),
458
+ trails: normalizeTrailRows(trails, snapshotId),
404
459
  };
405
460
  };
406
461
 
407
462
  /**
408
463
  * Look up a cached JSON schema by content hash.
409
464
  *
410
- * The query matches on `zod_hash` without filtering by `save_id` because the
411
- * cache is intentionally cross-save: if the Zod schema definition has not
465
+ * The query matches on `zod_hash` without filtering by `snapshot_id` because the
466
+ * cache is intentionally cross-snapshot: if the Zod schema definition has not
412
467
  * changed (same hash), the serialized JSON Schema is reused regardless of
413
- * which save produced it. This is safe as long as `zodToJsonSchema` is
468
+ * which snapshot produced it. This is safe as long as `zodToJsonSchema` is
414
469
  * deterministic for a given `_def` hash — the `schemaDefinitionHash` pipeline
415
470
  * guarantees that structurally identical schemas produce the same hash.
416
471
  */
@@ -445,7 +500,7 @@ const resolveSchemaRow = (
445
500
  db: Database,
446
501
  ownerId: string,
447
502
  ownerKind: TopoSchemaRow['ownerKind'],
448
- saveId: string,
503
+ snapshotId: string,
449
504
  schemaKind: TopoSchemaRow['schemaKind'],
450
505
  schema: ZodSchemaInput
451
506
  ): {
@@ -467,8 +522,8 @@ const resolveSchemaRow = (
467
522
  jsonSchema: cachedJson,
468
523
  ownerId,
469
524
  ownerKind,
470
- saveId,
471
525
  schemaKind,
526
+ snapshotId,
472
527
  zodHash,
473
528
  },
474
529
  value: parseJsonRecord(cachedJson),
@@ -481,8 +536,8 @@ const resolveSchemaRow = (
481
536
  jsonSchema: generated.json,
482
537
  ownerId,
483
538
  ownerKind,
484
- saveId,
485
539
  schemaKind,
540
+ snapshotId,
486
541
  zodHash,
487
542
  },
488
543
  value: generated.value,
@@ -491,7 +546,7 @@ const resolveSchemaRow = (
491
546
 
492
547
  const materializeTrailSchema = (
493
548
  db: Database,
494
- saveId: string,
549
+ snapshotId: string,
495
550
  trail: AnyTrail
496
551
  ): {
497
552
  readonly rows: readonly TopoSchemaRow[];
@@ -504,7 +559,7 @@ const materializeTrailSchema = (
504
559
  db,
505
560
  trail.id,
506
561
  'trail',
507
- saveId,
562
+ snapshotId,
508
563
  'input',
509
564
  trail.input as ZodSchemaInput
510
565
  );
@@ -522,7 +577,7 @@ const materializeTrailSchema = (
522
577
  db,
523
578
  trail.id,
524
579
  'trail',
525
- saveId,
580
+ snapshotId,
526
581
  'output',
527
582
  trail.output as ZodSchemaInput
528
583
  );
@@ -538,7 +593,7 @@ const materializeTrailSchema = (
538
593
 
539
594
  const materializeTrailSchemas = (
540
595
  db: Database,
541
- saveId: string,
596
+ snapshotId: string,
542
597
  trails: readonly AnyTrail[]
543
598
  ): Pick<MaterializedSchemas, 'rows' | 'trailSchemas'> => {
544
599
  const rows: TopoSchemaRow[] = [];
@@ -551,7 +606,7 @@ const materializeTrailSchemas = (
551
606
  >();
552
607
 
553
608
  for (const trail of trails) {
554
- const materialized = materializeTrailSchema(db, saveId, trail);
609
+ const materialized = materializeTrailSchema(db, snapshotId, trail);
555
610
  rows.push(...materialized.rows);
556
611
  trailSchemas.set(trail.id, materialized.value);
557
612
  }
@@ -564,7 +619,7 @@ const materializeTrailSchemas = (
564
619
 
565
620
  const materializeSignalSchemas = (
566
621
  db: Database,
567
- saveId: string,
622
+ snapshotId: string,
568
623
  signals: readonly AnySignal[]
569
624
  ): Pick<MaterializedSchemas, 'rows' | 'signalPayloads'> => {
570
625
  const rows: TopoSchemaRow[] = [];
@@ -575,7 +630,7 @@ const materializeSignalSchemas = (
575
630
  db,
576
631
  signal.id,
577
632
  'signal',
578
- saveId,
633
+ snapshotId,
579
634
  'payload',
580
635
  signal.payload as ZodSchemaInput
581
636
  );
@@ -591,12 +646,12 @@ const materializeSignalSchemas = (
591
646
 
592
647
  const materializeSchemas = (
593
648
  db: Database,
594
- saveId: string,
649
+ snapshotId: string,
595
650
  signals: readonly AnySignal[],
596
651
  trails: readonly AnyTrail[]
597
652
  ): MaterializedSchemas => {
598
- const trailMaterial = materializeTrailSchemas(db, saveId, trails);
599
- const signalMaterial = materializeSignalSchemas(db, saveId, signals);
653
+ const trailMaterial = materializeTrailSchemas(db, snapshotId, trails);
654
+ const signalMaterial = materializeSignalSchemas(db, snapshotId, signals);
600
655
 
601
656
  return {
602
657
  rows: [...trailMaterial.rows, ...signalMaterial.rows],
@@ -636,12 +691,11 @@ const addExtendedMetadata = (
636
691
  entry['replacedBy'] = raw['replacedBy'];
637
692
  }
638
693
 
639
- if (trail.detours !== undefined) {
640
- const detours: Record<string, readonly string[]> = {};
641
- for (const key of Object.keys(trail.detours).toSorted()) {
642
- detours[key] = (trail.detours[key] ?? []).toSorted();
643
- }
644
- entry['detours'] = detours;
694
+ if (trail.detours.length > 0) {
695
+ entry['detours'] = trail.detours.map((d) => ({
696
+ maxAttempts: Math.max(1, Math.min(d.maxAttempts ?? 1, 5)),
697
+ on: d.on.name,
698
+ }));
645
699
  }
646
700
  };
647
701
 
@@ -653,9 +707,15 @@ const addTrailRelations = (
653
707
  entry['crosses'] = trail.crosses.toSorted();
654
708
  }
655
709
 
656
- if (trail.provisions.length > 0) {
657
- entry['provisions'] = trail.provisions
658
- .map((provision) => provision.id)
710
+ if (trail.contours.length > 0) {
711
+ entry['contours'] = trail.contours
712
+ .map((contour) => contour.name)
713
+ .toSorted();
714
+ }
715
+
716
+ if (trail.resources.length > 0) {
717
+ entry['resources'] = trail.resources
718
+ .map((resource) => resource.id)
659
719
  .toSorted();
660
720
  }
661
721
  };
@@ -688,6 +748,10 @@ const buildTrailEntryBase = (
688
748
  entry['description'] = trail.description;
689
749
  }
690
750
 
751
+ if (trail.pattern !== undefined) {
752
+ entry['pattern'] = trail.pattern;
753
+ }
754
+
691
755
  return {
692
756
  entry,
693
757
  raw,
@@ -700,18 +764,18 @@ const trailToEntryRecord = (
700
764
  readonly input: JsonRecord;
701
765
  readonly output?: JsonRecord;
702
766
  }>
703
- ): TrailheadMapEntryRecord => {
767
+ ): SurfaceMapEntryRecord => {
704
768
  const { entry, raw } = buildTrailEntryBase(trail, trailSchema);
705
769
  addSafetyMarkers(entry, trail);
706
770
  addExtendedMetadata(entry, raw, trail);
707
771
  addTrailRelations(entry, trail);
708
- return sortKeys(entry) as TrailheadMapEntryRecord;
772
+ return sortKeys(entry) as SurfaceMapEntryRecord;
709
773
  };
710
774
 
711
775
  const signalToEntryRecord = (
712
776
  signal: AnySignal,
713
777
  payloadSchema: JsonRecord
714
- ): TrailheadMapEntryRecord => {
778
+ ): SurfaceMapEntryRecord => {
715
779
  const raw = signal as unknown as Record<string, unknown>;
716
780
  const entry: Record<string, unknown> = {
717
781
  exampleCount: 0,
@@ -733,28 +797,47 @@ const signalToEntryRecord = (
733
797
  entry['replacedBy'] = raw['replacedBy'];
734
798
  }
735
799
 
736
- return sortKeys(entry) as TrailheadMapEntryRecord;
800
+ return sortKeys(entry) as SurfaceMapEntryRecord;
737
801
  };
738
802
 
739
- const provisionToEntryRecord = (
740
- provision: AnyProvision
741
- ): TrailheadMapEntryRecord => {
803
+ const resourceToEntryRecord = (
804
+ resource: AnyResource
805
+ ): SurfaceMapEntryRecord => {
742
806
  const entry: Record<string, unknown> = {
743
807
  exampleCount: 0,
744
- id: provision.id,
745
- kind: 'provision',
808
+ id: resource.id,
809
+ kind: 'resource',
746
810
  trailheads: [],
747
811
  };
748
812
 
749
- if (provision.description !== undefined) {
750
- entry['description'] = provision.description;
813
+ if (resource.description !== undefined) {
814
+ entry['description'] = resource.description;
751
815
  }
752
816
 
753
- if (provision.health !== undefined) {
817
+ if (resource.health !== undefined) {
754
818
  entry['healthcheck'] = true;
755
819
  }
756
820
 
757
- return sortKeys(entry) as TrailheadMapEntryRecord;
821
+ return sortKeys(entry) as SurfaceMapEntryRecord;
822
+ };
823
+
824
+ const contourToEntryRecord = (contour: AnyContour): SurfaceMapEntryRecord => {
825
+ const schema = sortedJsonSchema(contour);
826
+ const entry: Record<string, unknown> = {
827
+ exampleCount: contour.examples?.length ?? 0,
828
+ id: contour.name,
829
+ identity: contour.identity,
830
+ kind: 'contour',
831
+ schema: schema.value,
832
+ trailheads: [],
833
+ };
834
+
835
+ const references = getContourReferences(contour);
836
+ if (references.length > 0) {
837
+ entry['references'] = references;
838
+ }
839
+
840
+ return sortKeys(entry) as SurfaceMapEntryRecord;
758
841
  };
759
842
 
760
843
  const requireTrailSchema = (
@@ -782,9 +865,10 @@ const requireSignalPayload = (
782
865
  return payload;
783
866
  };
784
867
 
785
- const buildTrailheadMap = (
868
+ const buildSurfaceMap = (
869
+ contours: readonly AnyContour[],
786
870
  generatedAt: string,
787
- provisions: readonly AnyProvision[],
871
+ resources: readonly AnyResource[],
788
872
  signalPayloads: ReadonlyMap<string, JsonRecord>,
789
873
  signals: readonly AnySignal[],
790
874
  trailSchemas: ReadonlyMap<
@@ -795,8 +879,9 @@ const buildTrailheadMap = (
795
879
  }>
796
880
  >,
797
881
  trails: readonly AnyTrail[]
798
- ): TrailheadMapRecord => {
882
+ ): SurfaceMapRecord => {
799
883
  const entries = [
884
+ ...contours.map((contour) => contourToEntryRecord(contour)),
800
885
  ...trails.map((trail) =>
801
886
  trailToEntryRecord(trail, requireTrailSchema(trailSchemas, trail.id))
802
887
  ),
@@ -806,7 +891,7 @@ const buildTrailheadMap = (
806
891
  requireSignalPayload(signalPayloads, signal.id)
807
892
  )
808
893
  ),
809
- ...provisions.map((provision) => provisionToEntryRecord(provision)),
894
+ ...resources.map((resource) => resourceToEntryRecord(resource)),
810
895
  ].toSorted((a, b) => a.id.localeCompare(b.id));
811
896
 
812
897
  return {
@@ -816,21 +901,21 @@ const buildTrailheadMap = (
816
901
  };
817
902
  };
818
903
 
819
- const hashTrailheadMapRecord = (trailheadMap: TrailheadMapRecord): string => {
820
- const { generatedAt: _unused, ...rest } = trailheadMap;
904
+ const hashSurfaceMapRecord = (surfaceMap: SurfaceMapRecord): string => {
905
+ const { generatedAt: _unused, ...rest } = surfaceMap;
821
906
  return hashValue(rest);
822
907
  };
823
908
 
824
909
  const entryPayload = (
825
- entry: TrailheadMapEntryRecord
910
+ entry: SurfaceMapEntryRecord
826
911
  ): Readonly<Record<string, unknown>> => {
827
912
  const { id: _unusedId, kind: _unusedKind, ...rest } = entry;
828
913
  return sortKeys(rest);
829
914
  };
830
915
 
831
916
  const entriesForKind = (
832
- entries: readonly TrailheadMapEntryRecord[],
833
- kind: TrailheadMapEntryRecord['kind']
917
+ entries: readonly SurfaceMapEntryRecord[],
918
+ kind: SurfaceMapEntryRecord['kind']
834
919
  ): Readonly<Record<string, Readonly<Record<string, unknown>>>> =>
835
920
  Object.fromEntries(
836
921
  entries
@@ -841,55 +926,60 @@ const entriesForKind = (
841
926
  const buildSerializedLock = (
842
927
  hash: string,
843
928
  topo: Topo,
844
- trailheadMap: TrailheadMapRecord
929
+ surfaceMap: SurfaceMapRecord
845
930
  ): Readonly<Record<string, unknown>> =>
846
931
  sortKeys({
847
932
  apps: sortKeys({
848
933
  [topo.name]: sortKeys({
849
- provisions: entriesForKind(trailheadMap.entries, 'provision'),
850
- signals: entriesForKind(trailheadMap.entries, 'signal'),
851
- trails: entriesForKind(trailheadMap.entries, 'trail'),
934
+ contours: entriesForKind(surfaceMap.entries, 'contour'),
935
+ resources: entriesForKind(surfaceMap.entries, 'resource'),
936
+ signals: entriesForKind(surfaceMap.entries, 'signal'),
937
+ trails: entriesForKind(surfaceMap.entries, 'trail'),
852
938
  }),
853
939
  }),
854
- generatedAt: trailheadMap.generatedAt,
940
+ generatedAt: surfaceMap.generatedAt,
855
941
  hash,
856
942
  version: 1,
857
943
  });
858
944
 
859
945
  const buildStoredTopoExport = (
860
946
  db: Database,
861
- save: TopoSaveRecord,
947
+ snapshot: TopoSnapshot,
862
948
  topo: Topo
863
949
  ): MaterializedTopoArtifacts => {
950
+ const contours = topo
951
+ .listContours()
952
+ .toSorted((a, b) => a.name.localeCompare(b.name));
864
953
  const trails = topo.list().toSorted((a, b) => a.id.localeCompare(b.id));
865
- const provisions = topo
866
- .listProvisions()
954
+ const resources = topo
955
+ .listResources()
867
956
  .toSorted((a, b) => a.id.localeCompare(b.id));
868
957
  const signals = topo
869
958
  .listSignals()
870
959
  .toSorted((a, b) => a.id.localeCompare(b.id));
871
- const schemas = materializeSchemas(db, save.id, signals, trails);
872
- const trailheadMap = buildTrailheadMap(
873
- save.createdAt,
874
- provisions,
960
+ const schemas = materializeSchemas(db, snapshot.id, signals, trails);
961
+ const surfaceMap = buildSurfaceMap(
962
+ contours,
963
+ snapshot.createdAt,
964
+ resources,
875
965
  schemas.signalPayloads,
876
966
  signals,
877
967
  schemas.trailSchemas,
878
968
  trails
879
969
  );
880
- const trailheadHash = hashTrailheadMapRecord(trailheadMap);
970
+ const surfaceHash = hashSurfaceMapRecord(surfaceMap);
881
971
  const serializedLock = `${JSON.stringify(
882
- buildSerializedLock(trailheadHash, topo, trailheadMap),
972
+ buildSerializedLock(surfaceHash, topo, surfaceMap),
883
973
  null,
884
974
  2
885
975
  )}\n`;
886
976
 
887
977
  return {
888
978
  exportRow: {
889
- saveId: save.id,
890
979
  serializedLock,
891
- trailheadHash,
892
- trailheadMap: `${JSON.stringify(trailheadMap, null, 2)}\n`,
980
+ snapshotId: snapshot.id,
981
+ surfaceHash,
982
+ surfaceMap: `${JSON.stringify(surfaceMap, null, 2)}\n`,
893
983
  },
894
984
  schemaRows: schemas.rows,
895
985
  };
@@ -914,8 +1004,8 @@ const insertProjectedRows = (
914
1004
  db,
915
1005
  projection.trails,
916
1006
  `INSERT INTO topo_trails (
917
- id, intent, idempotent, has_output, has_examples, example_count, description, meta, save_id
918
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1007
+ id, intent, idempotent, has_output, has_examples, example_count, description, pattern, meta, snapshot_id
1008
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
919
1009
  (row) => [
920
1010
  row.id,
921
1011
  row.intent,
@@ -924,61 +1014,76 @@ const insertProjectedRows = (
924
1014
  row.hasExamples,
925
1015
  row.exampleCount,
926
1016
  row.description,
1017
+ row.pattern,
927
1018
  row.meta,
928
- row.saveId,
1019
+ row.snapshotId,
929
1020
  ]
930
1021
  );
931
1022
  insertRows(
932
1023
  db,
933
1024
  projection.crossings,
934
- 'INSERT INTO topo_crossings (source_id, target_id, save_id) VALUES (?, ?, ?)',
935
- (row) => [row.sourceId, row.targetId, row.saveId]
1025
+ 'INSERT INTO topo_crossings (source_id, target_id, snapshot_id) VALUES (?, ?, ?)',
1026
+ (row) => [row.sourceId, row.targetId, row.snapshotId]
936
1027
  );
937
1028
  insertRows(
938
1029
  db,
939
- projection.trailProvisions,
940
- `INSERT INTO topo_trail_provisions (trail_id, provision_id, save_id)
1030
+ projection.trailResources,
1031
+ `INSERT INTO topo_trail_resources (trail_id, resource_id, snapshot_id)
941
1032
  VALUES (?, ?, ?)`,
942
- (row) => [row.trailId, row.provisionId, row.saveId]
1033
+ (row) => [row.trailId, row.resourceId, row.snapshotId]
943
1034
  );
944
1035
  insertRows(
945
1036
  db,
946
- projection.provisions,
947
- `INSERT INTO topo_provisions (id, has_mock, has_health, save_id)
1037
+ projection.resources,
1038
+ `INSERT INTO topo_resources (id, has_mock, has_health, snapshot_id)
948
1039
  VALUES (?, ?, ?, ?)`,
949
- (row) => [row.id, row.hasMock, row.hasHealth, row.saveId]
1040
+ (row) => [row.id, row.hasMock, row.hasHealth, row.snapshotId]
950
1041
  );
951
1042
  insertRows(
952
1043
  db,
953
1044
  projection.signals,
954
- 'INSERT INTO topo_signals (id, description, save_id) VALUES (?, ?, ?)',
955
- (row) => [row.id, row.description, row.saveId]
1045
+ 'INSERT INTO topo_signals (id, description, snapshot_id) VALUES (?, ?, ?)',
1046
+ (row) => [row.id, row.description, row.snapshotId]
956
1047
  );
957
1048
  insertRows(
958
1049
  db,
959
1050
  projection.trailSignals,
960
- `INSERT INTO topo_trail_signals (trail_id, signal_id, save_id)
1051
+ `INSERT INTO topo_trail_signals (trail_id, signal_id, snapshot_id)
1052
+ VALUES (?, ?, ?)`,
1053
+ (row) => [row.trailId, row.signalId, row.snapshotId]
1054
+ );
1055
+ insertRows(
1056
+ db,
1057
+ projection.fires,
1058
+ `INSERT INTO topo_trail_fires (trail_id, signal_id, snapshot_id)
1059
+ VALUES (?, ?, ?)`,
1060
+ (row) => [row.trailId, row.signalId, row.snapshotId]
1061
+ );
1062
+ insertRows(
1063
+ db,
1064
+ projection.on,
1065
+ `INSERT INTO topo_trail_on (trail_id, signal_id, snapshot_id)
961
1066
  VALUES (?, ?, ?)`,
962
- (row) => [row.trailId, row.signalId, row.saveId]
1067
+ (row) => [row.trailId, row.signalId, row.snapshotId]
963
1068
  );
964
1069
  insertRows(
965
1070
  db,
966
- projection.trailheads,
967
- `INSERT INTO topo_trailheads (trail_id, trailhead, derived_name, method, save_id)
1071
+ projection.surfaces,
1072
+ `INSERT INTO topo_surfaces (trail_id, surface, derived_name, method, snapshot_id)
968
1073
  VALUES (?, ?, ?, ?, ?)`,
969
1074
  (row) => [
970
1075
  row.trailId,
971
- row.trailhead,
1076
+ row.surface,
972
1077
  row.derivedName,
973
1078
  row.method,
974
- row.saveId,
1079
+ row.snapshotId,
975
1080
  ]
976
1081
  );
977
1082
  insertRows(
978
1083
  db,
979
1084
  projection.examples,
980
1085
  `INSERT INTO topo_examples (
981
- id, trail_id, ordinal, name, description, input, expected, error, save_id
1086
+ id, trail_id, ordinal, name, description, input, expected, error, snapshot_id
982
1087
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
983
1088
  (row) => [
984
1089
  row.id,
@@ -989,7 +1094,7 @@ const insertProjectedRows = (
989
1094
  row.input,
990
1095
  row.expected,
991
1096
  row.error,
992
- row.saveId,
1097
+ row.snapshotId,
993
1098
  ]
994
1099
  );
995
1100
  };
@@ -1002,7 +1107,7 @@ const insertSchemaRows = (
1002
1107
  db,
1003
1108
  rows,
1004
1109
  `INSERT INTO topo_schemas (
1005
- owner_id, owner_kind, schema_kind, zod_hash, json_schema, save_id
1110
+ owner_id, owner_kind, schema_kind, zod_hash, json_schema, snapshot_id
1006
1111
  ) VALUES (?, ?, ?, ?, ?, ?)`,
1007
1112
  (row) => [
1008
1113
  row.ownerId,
@@ -1010,7 +1115,7 @@ const insertSchemaRows = (
1010
1115
  row.schemaKind,
1011
1116
  row.zodHash,
1012
1117
  row.jsonSchema,
1013
- row.saveId,
1118
+ row.snapshotId,
1014
1119
  ]
1015
1120
  );
1016
1121
  };
@@ -1021,12 +1126,12 @@ const insertStoredExport = (
1021
1126
  ): void => {
1022
1127
  db.run(
1023
1128
  `INSERT INTO topo_exports (
1024
- save_id, trailhead_map, trailhead_hash, serialized_lock
1129
+ snapshot_id, surface_map, surface_hash, serialized_lock
1025
1130
  ) VALUES (?, ?, ?, ?)`,
1026
1131
  [
1027
- exportRow.saveId,
1028
- exportRow.trailheadMap,
1029
- exportRow.trailheadHash,
1132
+ exportRow.snapshotId,
1133
+ exportRow.surfaceMap,
1134
+ exportRow.surfaceHash,
1030
1135
  exportRow.serializedLock,
1031
1136
  ]
1032
1137
  );
@@ -1034,15 +1139,15 @@ const insertStoredExport = (
1034
1139
 
1035
1140
  export const getStoredTopoExport = (
1036
1141
  db: Database,
1037
- saveId: string
1142
+ snapshotId: string
1038
1143
  ): StoredTopoExport | undefined => {
1039
1144
  const row = db
1040
1145
  .query<StoredTopoExportDbRow, [string]>(
1041
- `SELECT trailhead_map, trailhead_hash, serialized_lock
1146
+ `SELECT surface_map, surface_hash, serialized_lock
1042
1147
  FROM topo_exports
1043
- WHERE save_id = ?`
1148
+ WHERE snapshot_id = ?`
1044
1149
  )
1045
- .get(saveId);
1150
+ .get(snapshotId);
1046
1151
 
1047
1152
  if (row === undefined || row === null) {
1048
1153
  return undefined;
@@ -1050,32 +1155,32 @@ export const getStoredTopoExport = (
1050
1155
 
1051
1156
  return {
1052
1157
  lockContent: row.serialized_lock,
1053
- trailheadHash: row.trailhead_hash,
1054
- trailheadMapJson: row.trailhead_map,
1158
+ surfaceHash: row.surface_hash,
1159
+ surfaceMapJson: row.surface_map,
1055
1160
  };
1056
1161
  };
1057
1162
 
1058
- export const persistEstablishedTopoSave = (
1163
+ export const createTopoSnapshot = (
1059
1164
  db: Database,
1060
1165
  topo: Topo,
1061
- input?: CreateTopoSaveInput
1062
- ): Result<TopoSaveRecord, Error> => {
1166
+ input?: CreateTopoSnapshotInput
1167
+ ): Result<TopoSnapshot, Error> => {
1063
1168
  const validated = validateEstablishedTopo(topo);
1064
1169
  if (validated.isErr()) {
1065
1170
  return Result.err(validated.error);
1066
1171
  }
1067
1172
 
1068
- ensureTopoHistorySchema(db);
1173
+ ensureTopoSnapshotSchema(db);
1069
1174
 
1070
- const saveInput: CreateTopoSaveInput = {
1175
+ const snapshotInput: CreateTopoSnapshotInput = {
1071
1176
  ...input,
1072
- provisionCount: input?.provisionCount ?? topo.provisions.size,
1177
+ resourceCount: input?.resourceCount ?? topo.resources.size,
1073
1178
  signalCount: input?.signalCount ?? topo.signals.size,
1074
1179
  trailCount: input?.trailCount ?? topo.trails.size,
1075
1180
  };
1076
1181
 
1077
- const save = db.transaction(() => {
1078
- const record = insertTopoSaveRecord(db, saveInput);
1182
+ const snapshot = db.transaction(() => {
1183
+ const record = insertTopoSnapshotRecord(db, snapshotInput);
1079
1184
  const artifacts = buildStoredTopoExport(db, record, topo);
1080
1185
  insertProjectedRows(db, normalizeTopoProjection(topo, record.id));
1081
1186
  insertSchemaRows(db, artifacts.schemaRows);
@@ -1083,5 +1188,5 @@ export const persistEstablishedTopoSave = (
1083
1188
  return record;
1084
1189
  })();
1085
1190
 
1086
- return Result.ok(save);
1191
+ return Result.ok(snapshot);
1087
1192
  };