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

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 +69 -0
  2. package/README.md +64 -19
  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
package/src/execute.ts CHANGED
@@ -11,10 +11,20 @@ import type { z } from 'zod';
11
11
  import type { AnyTrail } from './trail.js';
12
12
  import type { Layer } from './layer.js';
13
13
  import type { ResourceOverrideMap } from './resource.js';
14
- import type { TraceContext, TraceRecord } from './internal/tracing.js';
14
+ import type { TraceContext, TraceRecord } from './tracing.js';
15
15
  import type { Topo } from './topo.js';
16
16
 
17
- import { createFireFn } from './fire.js';
17
+ import {
18
+ buildActivationProvenanceTraceAttrs,
19
+ getActivationProvenance,
20
+ } from './activation-provenance.js';
21
+ import {
22
+ createFireFn,
23
+ isFrameworkFireFn,
24
+ waitForPendingFireDispatches,
25
+ withFireDispatchTracking,
26
+ } from './fire.js';
27
+ import type { BasePermit } from './permits.js';
18
28
  import type {
19
29
  CrossBatchOptions,
20
30
  CrossFn,
@@ -31,14 +41,16 @@ import {
31
41
  CancelledError,
32
42
  InternalError,
33
43
  NotFoundError,
44
+ PermitError,
34
45
  RetryExhaustedError,
35
46
  TrailsError,
47
+ ValidationError,
36
48
  } from './errors.js';
37
49
  import {
38
50
  claimNextCrossBatchIndex,
39
51
  createCrossBatchValidationResults,
40
52
  normalizeCrossBatchConcurrency,
41
- } from './internal/cross-batch.js';
53
+ } from './cross-batch.js';
42
54
  import { forkCtx } from './internal/fork-ctx.js';
43
55
  import {
44
56
  TRACE_CONTEXT_KEY,
@@ -48,12 +60,18 @@ import {
48
60
  getTraceSink,
49
61
  isTracingDisabled,
50
62
  writeToSink,
51
- } from './internal/tracing.js';
63
+ } from './tracing.js';
64
+ import {
65
+ OBSERVE_LOGGER_CONTEXT_KEY,
66
+ OBSERVE_LOGGER_METADATA_KEY,
67
+ createObserveLogger,
68
+ } from './observe.js';
52
69
  import { Result } from './result.js';
70
+ import { DETOUR_MAX_ATTEMPTS_CAP } from './detours.js';
53
71
  import { createResourceLookup } from './resource.js';
54
72
  import { createResources } from './resource-config.js';
55
- import { TRAILHEAD_KEY } from './types.js';
56
- import { validateInput } from './validation.js';
73
+ import { LAYER_INPUTS_KEY, SURFACE_KEY } from './types.js';
74
+ import { validateInput, validateOutput } from './validation.js';
57
75
 
58
76
  type MutableTrailContext = {
59
77
  -readonly [K in keyof TrailContext]: TrailContext[K];
@@ -69,8 +87,30 @@ export interface ExecuteTrailOptions {
69
87
  readonly ctx?: Partial<TrailContextInit> | undefined;
70
88
  /** AbortSignal override (takes final precedence over ctx and factory). */
71
89
  readonly abortSignal?: AbortSignal | undefined;
72
- /** Layers to compose around the implementation. */
90
+ /**
91
+ * Typed layers supplied for this execution.
92
+ *
93
+ * Layers compose around the trail implementation. Layers without `input`
94
+ * schemas are surface-invisible wrappers for concerns such as tenant guards,
95
+ * rate limiting, circuit breaking, or custom audit logging.
96
+ */
73
97
  readonly layers?: readonly Layer[] | undefined;
98
+ /**
99
+ * Typed layers attached at surface scope.
100
+ *
101
+ * Surfaces (CLI, MCP, HTTP) forward their `layers` option here so they
102
+ * compose around every trail dispatched through that surface. The final
103
+ * composition order is `topo → surface → trail → blaze` (outermost-first).
104
+ */
105
+ readonly surfaceLayers?: readonly Layer[] | undefined;
106
+ /**
107
+ * Typed layers attached at topo scope.
108
+ *
109
+ * The CLI/MCP/HTTP surfaces typically forward `topo.layers` here so the
110
+ * topo's declared layers wrap every trail invocation. The final
111
+ * composition order is `topo → surface → trail → blaze` (outermost-first).
112
+ */
113
+ readonly topoLayers?: readonly Layer[] | undefined;
74
114
  /** Factory that produces a base TrailContext (takes precedence over defaults). */
75
115
  readonly createContext?:
76
116
  | (() => TrailContextInit | Promise<TrailContextInit>)
@@ -83,6 +123,36 @@ export interface ExecuteTrailOptions {
83
123
  | undefined;
84
124
  /** Topo used for signal-driven activation; required for `ctx.fire()` to work. */
85
125
  readonly topo?: Topo | undefined;
126
+ /**
127
+ * Whether this invocation is a dry run.
128
+ *
129
+ * Sets `ctx.dryRun` for the trail. Defaults to `false`. The framework
130
+ * never short-circuits execution on this field — it only carries the
131
+ * flag through. Trails that read `ctx.dryRun` decide what dry-run means.
132
+ */
133
+ readonly dryRun?: boolean | undefined;
134
+ /**
135
+ * Permit to overlay onto `ctx.permit` for this invocation.
136
+ *
137
+ * When provided, this overrides any permit supplied via `ctx.permit` on
138
+ * the partial context overrides or via the `createContext` factory. Leave
139
+ * unset to inherit the permit from the resolved context (typically
140
+ * `undefined`). Surfaces parse and validate the permit at their boundary
141
+ * (e.g. CLI `--permit '<json>'`) before passing it here.
142
+ */
143
+ readonly permit?: BasePermit | undefined;
144
+ /**
145
+ * Per-layer runtime input keyed by `Layer.name`.
146
+ *
147
+ * Surfaces (CLI, MCP, HTTP) parse their native idiom into a per-layer
148
+ * input object — usually projected from each layer's `input` schema —
149
+ * and pass it here. The executor merges these into
150
+ * `ctx.extensions[LAYER_INPUTS_KEY]` so layers can read their own slot
151
+ * via `ctx.extensions?.[LAYER_INPUTS_KEY]?.[layer.name]`.
152
+ *
153
+ * @see TRL-473 for the CLI projection contract.
154
+ */
155
+ readonly layerInputs?: Readonly<Record<string, unknown>> | undefined;
86
156
  /**
87
157
  * Override the validation schema used for input validation.
88
158
  *
@@ -114,9 +184,39 @@ const applyContextOverrides = (
114
184
  }
115
185
  : base;
116
186
 
117
- return options?.abortSignal
187
+ const withAbort = options?.abortSignal
118
188
  ? { ...withOverrides, abortSignal: options.abortSignal }
119
189
  : withOverrides;
190
+
191
+ const withDryRun =
192
+ options?.dryRun === undefined
193
+ ? withAbort
194
+ : { ...withAbort, dryRun: options.dryRun };
195
+
196
+ const withPermit =
197
+ options?.permit === undefined
198
+ ? withDryRun
199
+ : { ...withDryRun, permit: options.permit };
200
+
201
+ if (options?.layerInputs === undefined) {
202
+ return withPermit;
203
+ }
204
+ // Merge per-layer inputs onto any inherited LAYER_INPUTS_KEY slot so
205
+ // crossed/forked contexts can preserve outer-surface routing.
206
+ const inheritedExtensions = withPermit.extensions ?? {};
207
+ const inheritedLayerInputs = (inheritedExtensions[LAYER_INPUTS_KEY] ?? {}) as
208
+ | Readonly<Record<string, unknown>>
209
+ | Record<string, unknown>;
210
+ return {
211
+ ...withPermit,
212
+ extensions: {
213
+ ...inheritedExtensions,
214
+ [LAYER_INPUTS_KEY]: {
215
+ ...inheritedLayerInputs,
216
+ ...options.layerInputs,
217
+ },
218
+ },
219
+ };
120
220
  };
121
221
 
122
222
  const bindResourceLookup = (
@@ -143,6 +243,11 @@ const bindResourceLookup = (
143
243
  * 1. Factory (`createContext`) or `createTrailContext()` defaults.
144
244
  * 2. Partial `ctx` overrides merged on top.
145
245
  * 3. `abortSignal` override takes final precedence.
246
+ * 4. `dryRun` option takes final precedence (defaults to `false` via
247
+ * `createTrailContext` when neither option nor `ctx.dryRun` is provided).
248
+ * 5. `permit` option takes final precedence over any inherited permit when
249
+ * provided; leaving it unset preserves whatever the resolved context
250
+ * already carries.
146
251
  */
147
252
  const resolveContext = async (
148
253
  options?: ExecuteTrailOptions
@@ -155,17 +260,110 @@ const resolveContext = async (
155
260
  return bindResourceLookup(resolved, options);
156
261
  };
157
262
 
263
+ const readObserveLoggerMetadata = (
264
+ ctx: TrailContext
265
+ ): Record<string, unknown> => {
266
+ const value = ctx.extensions?.[OBSERVE_LOGGER_METADATA_KEY];
267
+ return value !== null && typeof value === 'object'
268
+ ? (value as Record<string, unknown>)
269
+ : {};
270
+ };
271
+
272
+ const applyTopoObserveContext = (
273
+ trail: AnyTrail,
274
+ ctx: TrailContext,
275
+ topo: Topo | undefined
276
+ ): TrailContext => {
277
+ if (topo?.observe?.log === undefined) {
278
+ return ctx;
279
+ }
280
+ const hasTopoObserveLogger =
281
+ ctx.extensions?.[OBSERVE_LOGGER_CONTEXT_KEY] === true;
282
+ if (ctx.logger !== undefined && !hasTopoObserveLogger) {
283
+ return ctx;
284
+ }
285
+ const { log } = topo.observe;
286
+ // Accumulated metadata (e.g. signal fan-out fields) takes precedence over
287
+ // the previous observe logger's `topo`/`trailId` values, but the new trail's
288
+ // identity wins so log records keep pointing at the trail currently running.
289
+ const accumulated = readObserveLoggerMetadata(ctx);
290
+ return {
291
+ ...ctx,
292
+ extensions: {
293
+ ...ctx.extensions,
294
+ [OBSERVE_LOGGER_CONTEXT_KEY]: true,
295
+ },
296
+ logger: createObserveLogger(log, trail.id, {
297
+ ...accumulated,
298
+ topo: topo.name,
299
+ trailId: trail.id,
300
+ }),
301
+ };
302
+ };
303
+
304
+ const findMissingScopes = (
305
+ required: readonly string[],
306
+ held: readonly string[]
307
+ ): readonly string[] => required.filter((scope) => !held.includes(scope));
308
+
309
+ const enforcePermitRequirement = (
310
+ trail: AnyTrail,
311
+ ctx: TrailContext
312
+ ): Result<TrailContext, Error> => {
313
+ const requirement = trail.permit;
314
+ if (requirement === undefined || requirement === 'public') {
315
+ return Result.ok(ctx);
316
+ }
317
+
318
+ if (ctx.permit === undefined) {
319
+ return Result.err(
320
+ new PermitError('No permit provided', {
321
+ context: { required: requirement.scopes, trailId: trail.id },
322
+ })
323
+ );
324
+ }
325
+
326
+ const missing = findMissingScopes(requirement.scopes, ctx.permit.scopes);
327
+ return missing.length === 0
328
+ ? Result.ok(ctx)
329
+ : Result.err(
330
+ new PermitError(`Missing scopes: ${missing.join(', ')}`, {
331
+ context: { missing, required: requirement.scopes, trailId: trail.id },
332
+ })
333
+ );
334
+ };
335
+
158
336
  const prepareContext = async (
159
337
  trail: AnyTrail,
160
338
  options?: ExecuteTrailOptions
161
- ): Promise<Result<TrailContext, Error>> => {
162
- const baseCtx = await resolveContext(options);
163
- return await createResources(
339
+ ): Promise<
340
+ Result<
341
+ { readonly ctx: TrailContext; readonly releaseResources: () => void },
342
+ Error
343
+ >
344
+ > => {
345
+ const baseCtx = applyTopoObserveContext(
164
346
  trail,
165
- baseCtx,
347
+ await resolveContext(options),
348
+ options?.topo
349
+ );
350
+ const permitted = enforcePermitRequirement(trail, baseCtx);
351
+ if (permitted.isErr()) {
352
+ return Result.err(permitted.error);
353
+ }
354
+
355
+ const resources = await createResources(
356
+ trail,
357
+ permitted.value,
166
358
  options?.resources,
167
359
  options?.configValues
168
360
  );
361
+ return resources.isErr()
362
+ ? Result.err(resources.error)
363
+ : Result.ok({
364
+ ctx: resources.value.ctx,
365
+ releaseResources: resources.value.release,
366
+ });
169
367
  };
170
368
 
171
369
  // ---------------------------------------------------------------------------
@@ -278,18 +476,27 @@ const buildTracedContext = (
278
476
  parentId: parent?.spanId,
279
477
  permit: extractPermit(ctx),
280
478
  rootId: parent?.rootId,
479
+ sampled: parent?.sampled ?? true,
480
+ surface: ctx.extensions?.[SURFACE_KEY] as TraceRecord['surface'],
281
481
  traceId: parent?.traceId,
282
482
  trailId: trail.id,
283
- trailhead: ctx.extensions?.[TRAILHEAD_KEY] as TraceRecord['trailhead'],
284
483
  });
484
+ const activation = getActivationProvenance(ctx);
485
+ const recordWithAttrs: TraceRecord =
486
+ activation === undefined
487
+ ? record
488
+ : {
489
+ ...record,
490
+ attrs: buildActivationProvenanceTraceAttrs(activation),
491
+ };
285
492
 
286
493
  // Root trace context for this trail's span. When inheriting a parent, the
287
494
  // traceId/rootId carry forward and only spanId advances to the new record.
288
495
  const rootTrace: TraceContext = {
289
496
  rootId: parent?.rootId ?? record.id,
290
- sampled: true,
291
- spanId: record.id,
292
- traceId: record.traceId,
497
+ sampled: recordWithAttrs.sampled ?? true,
498
+ spanId: recordWithAttrs.id,
499
+ traceId: recordWithAttrs.traceId,
293
500
  };
294
501
 
295
502
  const tracedCtx: TrailContext = {
@@ -301,7 +508,7 @@ const buildTracedContext = (
301
508
  trace: buildTraceFn(rootTrace, sink),
302
509
  };
303
510
 
304
- return { record, tracedCtx };
511
+ return { record: recordWithAttrs, tracedCtx };
305
512
  };
306
513
 
307
514
  const buildUntracedContext = (ctx: TrailContext): TrailContext => {
@@ -326,6 +533,7 @@ const runImplWithRootRecord = async (
326
533
  ): Promise<Result<unknown, Error>> => {
327
534
  try {
328
535
  const result = await impl(input, tracedCtx);
536
+ await waitForPendingFireDispatches(tracedCtx);
329
537
  const outcome = deriveOutcome(result);
330
538
  await writeToSink(
331
539
  sink,
@@ -333,6 +541,7 @@ const runImplWithRootRecord = async (
333
541
  );
334
542
  return result;
335
543
  } catch (error: unknown) {
544
+ await waitForPendingFireDispatches(tracedCtx);
336
545
  // Normalize unexpected throws so the root record still reflects the error
337
546
  // outcome. The outer executeTrail try/catch converts the thrown value into
338
547
  // a Result.err(InternalError) for the caller.
@@ -390,6 +599,45 @@ const stripInheritedResourceExtensions = (
390
599
  return Object.fromEntries(entries);
391
600
  };
392
601
 
602
+ const deriveConcurrentBranchObserveMetadata = (
603
+ ctx: TrailContext,
604
+ target: AnyTrail,
605
+ branchIndex: number
606
+ ): Record<string, unknown> | undefined => {
607
+ // Only carry observe metadata forward when the parent ctx is using a
608
+ // topo-managed observe logger. Otherwise we'd attach branch fields to
609
+ // caller-supplied loggers that didn't opt into the structured contract.
610
+ if (ctx.extensions?.[OBSERVE_LOGGER_CONTEXT_KEY] !== true) {
611
+ return;
612
+ }
613
+ return {
614
+ ...readObserveLoggerMetadata(ctx),
615
+ branchIndex,
616
+ crossedTrailId: target.id,
617
+ };
618
+ };
619
+
620
+ const buildConcurrentBranchExtensions = (
621
+ ctx: TrailContext,
622
+ target: AnyTrail,
623
+ topo: Topo | undefined,
624
+ branchIndex: number
625
+ ): Record<string, unknown> => {
626
+ const stripped = stripInheritedResourceExtensions(ctx, target, topo);
627
+ const observeMetadata = deriveConcurrentBranchObserveMetadata(
628
+ ctx,
629
+ target,
630
+ branchIndex
631
+ );
632
+ if (observeMetadata === undefined) {
633
+ return stripped;
634
+ }
635
+ return {
636
+ ...stripped,
637
+ [OBSERVE_LOGGER_METADATA_KEY]: observeMetadata,
638
+ };
639
+ };
640
+
393
641
  const deriveConcurrentBranchLogger = (
394
642
  ctx: TrailContext,
395
643
  target: AnyTrail,
@@ -406,7 +654,7 @@ const deriveConcurrentBranchLogger = (
406
654
  * Concurrent crossings should not inherit already-resolved resource instances
407
655
  * from the parent execution scope. Stripping resource IDs from extensions
408
656
  * forces each branch to resolve its own scope while still carrying forward
409
- * request-scoped values like tracing, trailhead identity, permits, and the
657
+ * request-scoped values like tracing, surface identity, permits, and the
410
658
  * shared AbortSignal.
411
659
  *
412
660
  * `cross`, `fire`, and `resource` are cleared so the child execution can
@@ -420,7 +668,7 @@ const buildConcurrentBranchContext = (
420
668
  branchIndex: number
421
669
  ): TrailContext =>
422
670
  forkCtx(ctx, {
423
- extensions: stripInheritedResourceExtensions(ctx, target, topo),
671
+ extensions: buildConcurrentBranchExtensions(ctx, target, topo, branchIndex),
424
672
  logger: deriveConcurrentBranchLogger(ctx, target, branchIndex),
425
673
  });
426
674
 
@@ -617,8 +865,18 @@ const bindCrossToCtx = (
617
865
  const bindFireToCtx = (
618
866
  ctx: TrailContext,
619
867
  topo: Topo | undefined,
620
- options: ExecuteTrailOptions | undefined
868
+ options: ExecuteTrailOptions | undefined,
869
+ producerTrailId?: string | undefined
621
870
  ): TrailContext => {
871
+ // Symmetric with bindCrossToCtx: a caller-supplied ctx.fire (e.g. test
872
+ // helper, scenario harness, or runtime intercepting signal fan-out) is
873
+ // preserved as-is. Without this guard, passing both `topo: app` and a
874
+ // custom `ctx.fire` would silently clobber the injected mock with the
875
+ // topo-backed dispatcher. Framework-created fire functions are replaced
876
+ // because consumer fan-out seeds them before the consumer trace span exists.
877
+ if (ctx.fire !== undefined && !isFrameworkFireFn(ctx.fire)) {
878
+ return ctx;
879
+ }
622
880
  if (topo === undefined) {
623
881
  return ctx;
624
882
  }
@@ -634,15 +892,20 @@ const bindFireToCtx = (
634
892
  validationSchema: _omitSchema,
635
893
  ...forwarded
636
894
  } = options ?? {};
637
- const fire = createFireFn(topo, ctx, (consumer, input, consumerCtx) =>
638
- // eslint-disable-next-line no-use-before-define -- executor closure runs only after executeTrail is defined
639
- executeTrail(consumer, input, {
640
- ...forwarded,
641
- ctx: consumerCtx,
642
- topo,
643
- })
895
+ const trackedCtx = withFireDispatchTracking(ctx);
896
+ const fire = createFireFn(
897
+ topo,
898
+ trackedCtx,
899
+ (consumer, input, consumerCtx) =>
900
+ // eslint-disable-next-line no-use-before-define -- executor closure runs only after executeTrail is defined
901
+ executeTrail(consumer, input, {
902
+ ...forwarded,
903
+ ctx: consumerCtx,
904
+ topo,
905
+ }),
906
+ producerTrailId
644
907
  );
645
- return { ...ctx, fire };
908
+ return { ...trackedCtx, fire };
646
909
  };
647
910
 
648
911
  const bindCrossAtLayerBoundary =
@@ -656,6 +919,7 @@ const bindCrossAtLayerBoundary =
656
919
 
657
920
  const bindFireAtLayerBoundary = <I, O>(
658
921
  implementation: Implementation<I, O>,
922
+ trail: AnyTrail,
659
923
  topo: Topo | undefined,
660
924
  options: ExecuteTrailOptions | undefined
661
925
  ): Implementation<I, O> => {
@@ -664,15 +928,13 @@ const bindFireAtLayerBoundary = <I, O>(
664
928
  }
665
929
 
666
930
  return (input, ctx) =>
667
- implementation(input, bindFireToCtx(ctx, topo, options));
931
+ implementation(input, bindFireToCtx(ctx, topo, options, trail.id));
668
932
  };
669
933
 
670
934
  // ---------------------------------------------------------------------------
671
935
  // Detour loop
672
936
  // ---------------------------------------------------------------------------
673
937
 
674
- const DETOUR_MAX_ATTEMPTS_CAP = 5;
675
-
676
938
  /**
677
939
  * Find the first detour whose `on` class matches the error via `instanceof`.
678
940
  *
@@ -818,6 +1080,28 @@ const wrapWithDetours = (
818
1080
  };
819
1081
  };
820
1082
 
1083
+ const wrapWithOutputValidation = (
1084
+ trail: AnyTrail,
1085
+ implementation: Implementation<unknown, unknown>
1086
+ ): Implementation<unknown, unknown> => {
1087
+ const { output } = trail;
1088
+ if (output === undefined) {
1089
+ return implementation;
1090
+ }
1091
+
1092
+ return async (input, ctx) => {
1093
+ const result = await implementation(input, ctx);
1094
+ if (result.isErr()) {
1095
+ return result;
1096
+ }
1097
+
1098
+ const validated = validateOutput(output, result.value);
1099
+ return validated.isErr()
1100
+ ? Result.err(validated.error)
1101
+ : Result.ok(validated.value);
1102
+ };
1103
+ };
1104
+
821
1105
  const prepareRunImpl = (
822
1106
  trail: AnyTrail,
823
1107
  ctx: TrailContext,
@@ -831,7 +1115,8 @@ const prepareRunImpl = (
831
1115
  const ctxWithIntrinsics = bindFireToCtx(
832
1116
  bindCrossToCtx(ctx, topo, options),
833
1117
  topo,
834
- options
1118
+ options,
1119
+ trail.id
835
1120
  );
836
1121
  // Detour loop wraps the blaze (inside layer stack, closest to blaze)
837
1122
  let impl = wrapWithDetours(
@@ -841,6 +1126,7 @@ const prepareRunImpl = (
841
1126
  topo,
842
1127
  options
843
1128
  ),
1129
+ trail,
844
1130
  topo,
845
1131
  options
846
1132
  ),
@@ -856,6 +1142,7 @@ const prepareRunImpl = (
856
1142
  topo,
857
1143
  options
858
1144
  ),
1145
+ trail,
859
1146
  topo,
860
1147
  options
861
1148
  );
@@ -864,7 +1151,7 @@ const prepareRunImpl = (
864
1151
 
865
1152
  return {
866
1153
  ctxWithIntrinsics,
867
- impl,
1154
+ impl: wrapWithOutputValidation(trail, impl),
868
1155
  };
869
1156
  };
870
1157
 
@@ -883,7 +1170,11 @@ const runImplWithoutTracing = async (
883
1170
  topo,
884
1171
  options
885
1172
  );
886
- return await prepared.impl(input, prepared.ctxWithIntrinsics);
1173
+ try {
1174
+ return await prepared.impl(input, prepared.ctxWithIntrinsics);
1175
+ } finally {
1176
+ await waitForPendingFireDispatches(prepared.ctxWithIntrinsics);
1177
+ }
887
1178
  };
888
1179
 
889
1180
  const runTrailWithTracing = async (
@@ -927,7 +1218,7 @@ const runTrail = async (
927
1218
  topo: Topo | undefined,
928
1219
  options: ExecuteTrailOptions | undefined
929
1220
  ): Promise<Result<unknown, Error>> => {
930
- const sink = getTraceSink();
1221
+ const sink = topo?.observe?.trace ?? getTraceSink();
931
1222
  return isTracingDisabled(sink)
932
1223
  ? await runImplWithoutTracing(trail, input, ctx, layers, topo, options)
933
1224
  : await runTrailWithTracing(trail, input, ctx, layers, topo, options, sink);
@@ -937,6 +1228,102 @@ const runTrail = async (
937
1228
  // Pipeline
938
1229
  // ---------------------------------------------------------------------------
939
1230
 
1231
+ /**
1232
+ * Compose the typed layers attached at topo, surface, and trail scope.
1233
+ *
1234
+ * Composition order is topo → surface → trail → execution-supplied → blaze
1235
+ * (outermost-first): trail-scope layers run inside surface/topo layers, and
1236
+ * `executeTrail({ layers })` layers wrap closest to the blaze for per-call
1237
+ * behavior.
1238
+ */
1239
+ const composeAttachedLayers = (
1240
+ trail: AnyTrail,
1241
+ options: ExecuteTrailOptions | undefined
1242
+ ): readonly Layer[] => [
1243
+ ...(options?.topoLayers ?? []),
1244
+ ...(options?.surfaceLayers ?? []),
1245
+ ...trail.layers,
1246
+ ...(options?.layers ?? []),
1247
+ ];
1248
+
1249
+ const isLayerInputMap = (
1250
+ value: unknown
1251
+ ): value is Readonly<Record<string, unknown>> =>
1252
+ value !== null && typeof value === 'object' && !Array.isArray(value);
1253
+
1254
+ const readContextLayerInputs = (
1255
+ ctx: TrailContext
1256
+ ): Result<Readonly<Record<string, unknown>> | undefined, ValidationError> => {
1257
+ const value = ctx.extensions?.[LAYER_INPUTS_KEY];
1258
+ if (value === undefined) {
1259
+ return Result.ok();
1260
+ }
1261
+ return isLayerInputMap(value)
1262
+ ? Result.ok(value)
1263
+ : Result.err(
1264
+ new ValidationError(
1265
+ 'Layer inputs must be an object keyed by layer name',
1266
+ {
1267
+ context: { extensionKey: LAYER_INPUTS_KEY },
1268
+ }
1269
+ )
1270
+ );
1271
+ };
1272
+
1273
+ const validateLayerInputs = (
1274
+ layers: readonly Layer[],
1275
+ layerInputs: Readonly<Record<string, unknown>>
1276
+ ): Result<Readonly<Record<string, unknown>>, ValidationError> => {
1277
+ const validated: Record<string, unknown> = { ...layerInputs };
1278
+ for (const layer of layers) {
1279
+ if (layer.input === undefined) {
1280
+ continue;
1281
+ }
1282
+ const slot = layerInputs[layer.name];
1283
+ if (slot === undefined) {
1284
+ continue;
1285
+ }
1286
+ const parsed = layer.input.safeParse(slot);
1287
+ if (!parsed.success) {
1288
+ return Result.err(
1289
+ new ValidationError(
1290
+ `Invalid input for layer '${layer.name}': ${parsed.error.message}`,
1291
+ {
1292
+ cause: parsed.error,
1293
+ context: { issues: parsed.error.issues, layerName: layer.name },
1294
+ }
1295
+ )
1296
+ );
1297
+ }
1298
+ validated[layer.name] = parsed.data;
1299
+ }
1300
+ return Result.ok(validated);
1301
+ };
1302
+
1303
+ const validateContextLayerInputs = (
1304
+ ctx: TrailContext,
1305
+ layers: readonly Layer[]
1306
+ ): Result<TrailContext, ValidationError> => {
1307
+ const layerInputs = readContextLayerInputs(ctx);
1308
+ if (layerInputs.isErr()) {
1309
+ return Result.err(layerInputs.error);
1310
+ }
1311
+ if (layerInputs.value === undefined) {
1312
+ return Result.ok(ctx);
1313
+ }
1314
+ const validated = validateLayerInputs(layers, layerInputs.value);
1315
+ if (validated.isErr()) {
1316
+ return Result.err(validated.error);
1317
+ }
1318
+ return Result.ok({
1319
+ ...ctx,
1320
+ extensions: {
1321
+ ...ctx.extensions,
1322
+ [LAYER_INPUTS_KEY]: validated.value,
1323
+ },
1324
+ });
1325
+ };
1326
+
940
1327
  /**
941
1328
  * Execute a trail through the standard validate-context-layers-run pipeline.
942
1329
  *
@@ -959,17 +1346,29 @@ export const executeTrail = async (
959
1346
 
960
1347
  const resolvedCtx = await prepareContext(trail, options);
961
1348
  if (resolvedCtx.isErr()) {
962
- return resolvedCtx;
1349
+ return Result.err(resolvedCtx.error);
963
1350
  }
964
1351
 
965
- return await runTrail(
966
- trail,
967
- validated.value,
968
- resolvedCtx.value,
969
- options?.layers ?? [],
970
- options?.topo,
971
- options
972
- );
1352
+ const layers = composeAttachedLayers(trail, options);
1353
+ try {
1354
+ const layerCtx = validateContextLayerInputs(
1355
+ resolvedCtx.value.ctx,
1356
+ layers
1357
+ );
1358
+ if (layerCtx.isErr()) {
1359
+ return Result.err(layerCtx.error);
1360
+ }
1361
+ return await runTrail(
1362
+ trail,
1363
+ validated.value,
1364
+ layerCtx.value,
1365
+ layers,
1366
+ options?.topo,
1367
+ options
1368
+ );
1369
+ } finally {
1370
+ resolvedCtx.value.releaseResources();
1371
+ }
973
1372
  } catch (error: unknown) {
974
1373
  const message = error instanceof Error ? error.message : String(error);
975
1374
  return Result.err(new InternalError(message));