@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
@@ -10,8 +10,8 @@
10
10
  * allowing the derivation helper to call accessors by convention.
11
11
  *
12
12
  * Required methods (`get`, `list`, `upsert`, `remove`) match the
13
- * connector-agnostic write contract every bound store must expose. Optional
14
- * methods (`insert`, `update`) are declared by tabular connectors that
13
+ * backend-agnostic write contract every bound store must expose. Optional
14
+ * methods (`insert`, `update`) are declared by tabular adapters that
15
15
  * distinguish create-only and patch-only operations from the generalized
16
16
  * `upsert` contract.
17
17
  */
@@ -25,7 +25,7 @@ export interface StoreAccessorProtocol<
25
25
  get(id: TId): Promise<TEntity | null>;
26
26
  /** List entities, optionally filtered. Returns `[]` when no rows match. */
27
27
  list(filters?: TFilters): Promise<readonly TEntity[]>;
28
- /** Create-or-replace one entity using the connector-agnostic contract. */
28
+ /** Create-or-replace one entity using the backend-agnostic contract. */
29
29
  upsert(input: TInput): Promise<TEntity>;
30
30
  /**
31
31
  * Remove an entity by identity. Returns `{ deleted: true }` when the row
@@ -34,13 +34,13 @@ export interface StoreAccessorProtocol<
34
34
  */
35
35
  remove(id: TId): Promise<{ readonly deleted: boolean }>;
36
36
  /**
37
- * Optional insert — available on tabular connectors that distinguish
37
+ * Optional insert — available on tabular adapters that distinguish
38
38
  * create from update. When absent, synthesized blazes fall back to
39
39
  * `upsert`.
40
40
  */
41
41
  insert?(input: TInput): Promise<TEntity>;
42
42
  /**
43
- * Optional patch-by-identity — available on tabular connectors. Returns
43
+ * Optional patch-by-identity — available on tabular adapters. Returns
44
44
  * `null` when no row with the given identity exists. When absent,
45
45
  * synthesized update blazes fall back to `get` + merge + `upsert`.
46
46
  */
@@ -0,0 +1,245 @@
1
+ import type { TrailExample, TrailExampleSignalAssertion } from './trail.js';
2
+
3
+ export interface StructuredTrailExampleProvenance {
4
+ readonly source: 'trail.examples';
5
+ }
6
+
7
+ export interface StructuredSignalExampleProvenance {
8
+ readonly source: 'signal.examples';
9
+ }
10
+
11
+ export type StructuredTrailExampleKind = 'success' | 'error';
12
+
13
+ export interface StructuredTrailExample {
14
+ readonly description?: string | undefined;
15
+ readonly error?: string | undefined;
16
+ readonly expected?: unknown | undefined;
17
+ readonly expectedMatch?: unknown | undefined;
18
+ readonly input: unknown;
19
+ readonly kind: StructuredTrailExampleKind;
20
+ readonly name: string;
21
+ readonly provenance: StructuredTrailExampleProvenance;
22
+ readonly signals?:
23
+ | readonly StructuredTrailExampleSignalAssertion[]
24
+ | undefined;
25
+ }
26
+
27
+ export interface StructuredTrailExampleSignalAssertion {
28
+ readonly payload?: unknown | undefined;
29
+ readonly payloadMatch?: unknown | undefined;
30
+ readonly signalId: string;
31
+ readonly times?: number | undefined;
32
+ }
33
+
34
+ export interface StructuredSignalExample {
35
+ readonly kind: 'payload';
36
+ readonly payload: unknown;
37
+ readonly provenance: StructuredSignalExampleProvenance;
38
+ }
39
+
40
+ // `Date`, `RegExp`, `Map`, and `Set` are objects (`typeof === 'object'`),
41
+ // so they would pass the structural walk and reach `JSON.stringify`, which
42
+ // silently coerces them: a `Date` becomes its ISO string, a `RegExp` and
43
+ // any `Map`/`Set` become `{}`. Either way the projected shape diverges
44
+ // from the example author's declared input. Treat them as non-serializable
45
+ // leaves so the example is dropped rather than misrepresented to MCP
46
+ // clients.
47
+ const isNonJsonLeaf = (value: unknown): boolean => {
48
+ const kind = typeof value;
49
+ if (kind === 'function' || kind === 'symbol' || kind === 'bigint') {
50
+ return true;
51
+ }
52
+ return (
53
+ value instanceof Date ||
54
+ value instanceof RegExp ||
55
+ value instanceof Map ||
56
+ value instanceof Set
57
+ );
58
+ };
59
+
60
+ // `JSON.stringify` silently drops function / symbol property values rather than
61
+ // throwing, so we walk the value first and reject any example whose graph
62
+ // contains a non-serializable leaf. Without this, an MCP client consuming
63
+ // `ontrails/examples` would receive structurally incorrect inputs.
64
+ const containsNonSerializableLeaf = (
65
+ value: unknown,
66
+ seen: WeakSet<object>
67
+ ): boolean => {
68
+ if (isNonJsonLeaf(value)) {
69
+ return true;
70
+ }
71
+ if (value === null || typeof value !== 'object') {
72
+ return false;
73
+ }
74
+ const obj = value as object;
75
+ if (seen.has(obj)) {
76
+ return false;
77
+ }
78
+ seen.add(obj);
79
+ if (Array.isArray(value)) {
80
+ return value.some((entry) => containsNonSerializableLeaf(entry, seen));
81
+ }
82
+ return Object.values(obj).some((entry) =>
83
+ containsNonSerializableLeaf(entry, seen)
84
+ );
85
+ };
86
+
87
+ const toJsonSerializable = (value: unknown): unknown | undefined => {
88
+ if (containsNonSerializableLeaf(value, new WeakSet<object>())) {
89
+ return undefined;
90
+ }
91
+ try {
92
+ const encoded = JSON.stringify(value);
93
+ return encoded === undefined ? undefined : JSON.parse(encoded);
94
+ } catch {
95
+ return undefined;
96
+ }
97
+ };
98
+
99
+ const signalIdFromAssertion = (
100
+ assertion: TrailExampleSignalAssertion
101
+ ): string | undefined => {
102
+ if (typeof assertion.signal === 'string') {
103
+ return assertion.signal;
104
+ }
105
+ return typeof assertion.signal.id === 'string'
106
+ ? assertion.signal.id
107
+ : undefined;
108
+ };
109
+
110
+ const projectSignalAssertion = (
111
+ assertion: TrailExampleSignalAssertion
112
+ ): StructuredTrailExampleSignalAssertion | undefined => {
113
+ const signalId = signalIdFromAssertion(assertion);
114
+ if (signalId === undefined) {
115
+ return undefined;
116
+ }
117
+
118
+ const projected: Record<string, unknown> = { signalId };
119
+ if (assertion.payload !== undefined) {
120
+ const payload = toJsonSerializable(assertion.payload);
121
+ if (payload === undefined) {
122
+ return undefined;
123
+ }
124
+ projected['payload'] = payload;
125
+ }
126
+ if (assertion.payloadMatch !== undefined) {
127
+ const payloadMatch = toJsonSerializable(assertion.payloadMatch);
128
+ if (payloadMatch === undefined) {
129
+ return undefined;
130
+ }
131
+ projected['payloadMatch'] = payloadMatch;
132
+ }
133
+ if (assertion.times !== undefined) {
134
+ projected['times'] = assertion.times;
135
+ }
136
+ return Object.freeze(
137
+ projected
138
+ ) as unknown as StructuredTrailExampleSignalAssertion;
139
+ };
140
+
141
+ const projectSignalAssertions = (
142
+ assertions: readonly TrailExampleSignalAssertion[] | undefined
143
+ ): readonly StructuredTrailExampleSignalAssertion[] | undefined => {
144
+ if (assertions === undefined) {
145
+ return undefined;
146
+ }
147
+ const projected = assertions.map(projectSignalAssertion);
148
+ if (projected.some((assertion) => assertion === undefined)) {
149
+ return undefined;
150
+ }
151
+ return Object.freeze(projected as StructuredTrailExampleSignalAssertion[]);
152
+ };
153
+
154
+ const projectExample = (
155
+ example: TrailExample<unknown, unknown>
156
+ ): StructuredTrailExample | undefined => {
157
+ const input = toJsonSerializable(example.input);
158
+ if (input === undefined) {
159
+ return undefined;
160
+ }
161
+
162
+ const projected: Record<string, unknown> = {
163
+ input,
164
+ kind: example.error === undefined ? 'success' : 'error',
165
+ name: example.name,
166
+ provenance: { source: 'trail.examples' },
167
+ };
168
+
169
+ if (example.description !== undefined) {
170
+ projected['description'] = example.description;
171
+ }
172
+ if (example.expected !== undefined) {
173
+ const expected = toJsonSerializable(example.expected);
174
+ if (expected === undefined) {
175
+ return undefined;
176
+ }
177
+ projected['expected'] = expected;
178
+ }
179
+ if (example.expectedMatch !== undefined) {
180
+ const expectedMatch = toJsonSerializable(example.expectedMatch);
181
+ if (expectedMatch === undefined) {
182
+ return undefined;
183
+ }
184
+ projected['expectedMatch'] = expectedMatch;
185
+ }
186
+ if (example.error !== undefined) {
187
+ projected['error'] = example.error;
188
+ }
189
+ if (example.signals !== undefined) {
190
+ const signals = projectSignalAssertions(example.signals);
191
+ if (signals === undefined) {
192
+ return undefined;
193
+ }
194
+ projected['signals'] = signals;
195
+ }
196
+
197
+ return Object.freeze(projected) as unknown as StructuredTrailExample;
198
+ };
199
+
200
+ const projectSignalExample = (
201
+ payload: unknown
202
+ ): StructuredSignalExample | undefined => {
203
+ const serializablePayload = toJsonSerializable(payload);
204
+ if (serializablePayload === undefined) {
205
+ return undefined;
206
+ }
207
+
208
+ return Object.freeze({
209
+ kind: 'payload',
210
+ payload: serializablePayload,
211
+ provenance: { source: 'signal.examples' },
212
+ } satisfies StructuredSignalExample);
213
+ };
214
+
215
+ export const deriveStructuredTrailExamples = (
216
+ examples: readonly TrailExample<unknown, unknown>[] | undefined
217
+ ): readonly StructuredTrailExample[] | undefined => {
218
+ if (examples === undefined || examples.length === 0) {
219
+ return undefined;
220
+ }
221
+
222
+ const projected = examples
223
+ .map(projectExample)
224
+ .filter(
225
+ (example): example is StructuredTrailExample => example !== undefined
226
+ );
227
+
228
+ return projected.length > 0 ? Object.freeze(projected) : undefined;
229
+ };
230
+
231
+ export const deriveStructuredSignalExamples = (
232
+ examples: readonly unknown[] | undefined
233
+ ): readonly StructuredSignalExample[] | undefined => {
234
+ if (examples === undefined || examples.length === 0) {
235
+ return undefined;
236
+ }
237
+
238
+ const projected = examples
239
+ .map(projectSignalExample)
240
+ .filter(
241
+ (example): example is StructuredSignalExample => example !== undefined
242
+ );
243
+
244
+ return projected.length > 0 ? Object.freeze(projected) : undefined;
245
+ };
@@ -0,0 +1,91 @@
1
+ import { Result } from './result.js';
2
+ import type { Layer } from './layer.js';
3
+ import type { Intent } from './trail.js';
4
+ import type { Topo } from './topo.js';
5
+ import type { SurfaceName } from './transport-error-map.js';
6
+ import type { TrailContextInit } from './types.js';
7
+ import { SURFACE_KEY, SURFACE_LAYER_NAMES_KEY } from './types.js';
8
+ import { validateEstablishedTopo } from './validate-established-topo.js';
9
+
10
+ export type SurfaceConfigValues = Readonly<
11
+ Record<string, Record<string, unknown>>
12
+ >;
13
+
14
+ export interface SurfaceSelectionOptions {
15
+ /** Glob patterns that remove matching trail IDs. */
16
+ readonly exclude?: readonly string[] | undefined;
17
+ /** Glob patterns that keep only matching trail IDs when provided. */
18
+ readonly include?: readonly string[] | undefined;
19
+ /** Allowed intents for exposed surfaces. Empty arrays act as no filter. */
20
+ readonly intent?: readonly Intent[] | undefined;
21
+ }
22
+
23
+ export interface SurfaceValidationOptions {
24
+ /** Set to `false` to skip established-topo validation during projection. */
25
+ readonly validate?: boolean | undefined;
26
+ }
27
+
28
+ export interface BaseSurfaceOptions
29
+ extends SurfaceSelectionOptions, SurfaceValidationOptions {
30
+ /** Config values for resources that declare a `config` schema, keyed by resource ID. */
31
+ readonly configValues?: SurfaceConfigValues | undefined;
32
+ }
33
+
34
+ export const shouldValidateSurfaceTopo = (
35
+ options?: SurfaceValidationOptions
36
+ ): boolean => options?.validate !== false;
37
+
38
+ export const validateSurfaceTopo = (
39
+ graph: Topo,
40
+ options?: SurfaceValidationOptions
41
+ ): Result<void, Error> => {
42
+ if (!shouldValidateSurfaceTopo(options)) {
43
+ return Result.ok();
44
+ }
45
+
46
+ const validated = validateEstablishedTopo(graph);
47
+ return validated.isErr() ? Result.err(validated.error) : Result.ok();
48
+ };
49
+
50
+ export type SurfaceMarkedContext = Partial<TrailContextInit> & {
51
+ readonly extensions: Readonly<Record<string, unknown>>;
52
+ };
53
+
54
+ export const withSurfaceMarker = (
55
+ surface: SurfaceName,
56
+ ctx: Partial<TrailContextInit> = {}
57
+ ): SurfaceMarkedContext => ({
58
+ ...ctx,
59
+ extensions: {
60
+ ...ctx.extensions,
61
+ [SURFACE_KEY]: surface,
62
+ },
63
+ });
64
+
65
+ const readSurfaceLayerNameRecord = (
66
+ value: unknown
67
+ ): Record<string, readonly string[]> =>
68
+ value !== null && typeof value === 'object' && !Array.isArray(value)
69
+ ? (value as Record<string, readonly string[]>)
70
+ : {};
71
+
72
+ export const withSurfaceLayerNames = (
73
+ surface: SurfaceName,
74
+ layers: readonly Layer[],
75
+ ctx: Partial<TrailContextInit> = {}
76
+ ): SurfaceMarkedContext => {
77
+ const existing = readSurfaceLayerNameRecord(
78
+ ctx.extensions?.[SURFACE_LAYER_NAMES_KEY]
79
+ );
80
+ return {
81
+ ...ctx,
82
+ extensions: {
83
+ ...ctx.extensions,
84
+ [SURFACE_KEY]: surface,
85
+ [SURFACE_LAYER_NAMES_KEY]: {
86
+ ...existing,
87
+ [surface]: layers.map((layer) => layer.name),
88
+ },
89
+ },
90
+ };
91
+ };
@@ -1,17 +1,11 @@
1
1
  import type { Intent, Trail } from './trail.js';
2
+ import type { SurfaceSelectionOptions } from './surface-derivation.js';
2
3
 
3
4
  // ---------------------------------------------------------------------------
4
5
  // Public types
5
6
  // ---------------------------------------------------------------------------
6
7
 
7
- export interface SurfaceFilterOptions {
8
- /** Glob patterns that keep only matching trail IDs when provided. */
9
- readonly include?: readonly string[] | undefined;
10
- /** Glob patterns that remove matching trail IDs. */
11
- readonly exclude?: readonly string[] | undefined;
12
- /** Allowed intents for exposed trailheads. Empty arrays act as no filter. */
13
- readonly intent?: readonly Intent[] | undefined;
14
- }
8
+ export type SurfaceFilterOptions = SurfaceSelectionOptions;
15
9
 
16
10
  // ---------------------------------------------------------------------------
17
11
  // Glob matching
@@ -93,7 +87,7 @@ export const matchesTrailPattern = (
93
87
  };
94
88
 
95
89
  // ---------------------------------------------------------------------------
96
- // Trailhead filtering
90
+ // Surface filtering
97
91
  // ---------------------------------------------------------------------------
98
92
 
99
93
  const matchesAnyPattern = (
@@ -114,7 +108,7 @@ const isExplicitInternalInclude = (
114
108
  * Returns `'internal'` when either the explicit `visibility` field is
115
109
  * `'internal'` or the legacy `meta.internal === true` convention is set.
116
110
  * Honoring the legacy flag keeps trails authored before the visibility
117
- * field was introduced (e.g. `meta: { internal: true }`) off trailheads.
111
+ * field was introduced (e.g. `meta: { internal: true }`) off surfaces.
118
112
  *
119
113
  * The runtime always fills in `visibility: 'public'` when the spec did not
120
114
  * declare it, so we cannot distinguish explicit `'public'` from the default.
@@ -132,7 +126,7 @@ const effectiveVisibility = (
132
126
  return trail.meta?.['internal'] === true ? 'internal' : 'public';
133
127
  };
134
128
 
135
- const isVisibleToTrailheads = (
129
+ const isVisibleToSurfaces = (
136
130
  trail: Trail<unknown, unknown, unknown>,
137
131
  include: readonly string[] | undefined
138
132
  ): boolean =>
@@ -157,12 +151,12 @@ export const shouldIncludeTrailForSurface = (
157
151
  trail: Trail<unknown, unknown, unknown>,
158
152
  options: SurfaceFilterOptions = {}
159
153
  ): boolean => {
160
- if (trail.on.length > 0) {
154
+ if (trail.activationSources.length > 0) {
161
155
  return false;
162
156
  }
163
157
 
164
158
  const { exclude, include, intent } = options;
165
- if (!isVisibleToTrailheads(trail, include)) {
159
+ if (!isVisibleToSurfaces(trail, include)) {
166
160
  return false;
167
161
  }
168
162