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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/README.md +73 -24
  3. package/package.json +11 -4
  4. package/src/activation-provenance.ts +111 -0
  5. package/src/activation-source-compatibility.ts +430 -0
  6. package/src/activation-source-projection.ts +221 -0
  7. package/src/activation-source.ts +91 -0
  8. package/src/blob-ref.ts +51 -0
  9. package/src/branded.ts +1 -1
  10. package/src/context.ts +28 -4
  11. package/src/contour.ts +344 -0
  12. package/src/cross-batch.ts +69 -0
  13. package/src/cross-schema.ts +36 -0
  14. package/src/detours.ts +8 -0
  15. package/src/draft.ts +60 -52
  16. package/src/error-projection.ts +51 -0
  17. package/src/errors.ts +245 -48
  18. package/src/execute.ts +1250 -40
  19. package/src/fire.ts +1169 -0
  20. package/src/index.ts +383 -49
  21. package/src/internal/fork-ctx.ts +69 -0
  22. package/src/layer-projection.ts +192 -0
  23. package/src/layer.ts +81 -0
  24. package/src/observe.ts +361 -0
  25. package/src/path-security.ts +1 -1
  26. package/src/permits.ts +12 -0
  27. package/src/redaction/patterns.ts +6 -3
  28. package/src/resilience.ts +11 -11
  29. package/src/resource-config.ts +792 -0
  30. package/src/resource.ts +175 -0
  31. package/src/result.ts +18 -0
  32. package/src/run.ts +7 -6
  33. package/src/schedule-runtime.ts +689 -0
  34. package/src/schedule.ts +324 -0
  35. package/src/serialization.ts +90 -55
  36. package/src/signal-diagnostics.ts +633 -0
  37. package/src/signal-ref.ts +87 -0
  38. package/src/signal.ts +37 -0
  39. package/src/store/accessor-protocol.ts +56 -0
  40. package/src/store/index.ts +4 -0
  41. package/src/structured-examples.ts +245 -0
  42. package/src/surface-derivation.ts +91 -0
  43. package/src/surface-filter.ts +176 -0
  44. package/src/topo.ts +710 -59
  45. package/src/tracing.ts +345 -0
  46. package/src/trail.ts +347 -33
  47. package/src/trails/derive-trail.ts +842 -0
  48. package/src/trails/index.ts +9 -0
  49. package/src/trails/ingest.ts +138 -0
  50. package/src/{internal/trails-db.ts → trails-db.ts} +50 -31
  51. package/src/transport-error-map.ts +174 -0
  52. package/src/type-utils.ts +17 -2
  53. package/src/types.ts +208 -17
  54. package/src/validate-established-topo.ts +8 -2
  55. package/src/validate-topo.ts +244 -13
  56. package/src/validation.ts +58 -1
  57. package/src/webhook.ts +276 -0
  58. package/src/workspace.ts +1 -1
  59. package/src/zod-wrappers.ts +72 -0
  60. package/.turbo/turbo-build.log +0 -1
  61. package/.turbo/turbo-lint.log +0 -3
  62. package/.turbo/turbo-typecheck.log +0 -1
  63. package/dist/adapters.d.ts +0 -39
  64. package/dist/adapters.d.ts.map +0 -1
  65. package/dist/adapters.js +0 -2
  66. package/dist/adapters.js.map +0 -1
  67. package/dist/blob-ref.d.ts +0 -20
  68. package/dist/blob-ref.d.ts.map +0 -1
  69. package/dist/blob-ref.js +0 -22
  70. package/dist/blob-ref.js.map +0 -1
  71. package/dist/branded.d.ts +0 -36
  72. package/dist/branded.d.ts.map +0 -1
  73. package/dist/branded.js +0 -89
  74. package/dist/branded.js.map +0 -1
  75. package/dist/collections.d.ts +0 -31
  76. package/dist/collections.d.ts.map +0 -1
  77. package/dist/collections.js +0 -60
  78. package/dist/collections.js.map +0 -1
  79. package/dist/context.d.ts +0 -10
  80. package/dist/context.d.ts.map +0 -1
  81. package/dist/context.js +0 -21
  82. package/dist/context.js.map +0 -1
  83. package/dist/derive.d.ts +0 -44
  84. package/dist/derive.d.ts.map +0 -1
  85. package/dist/derive.js +0 -149
  86. package/dist/derive.js.map +0 -1
  87. package/dist/dispatch.d.ts +0 -27
  88. package/dist/dispatch.d.ts.map +0 -1
  89. package/dist/dispatch.js +0 -34
  90. package/dist/dispatch.js.map +0 -1
  91. package/dist/draft.d.ts +0 -28
  92. package/dist/draft.d.ts.map +0 -1
  93. package/dist/draft.js +0 -156
  94. package/dist/draft.js.map +0 -1
  95. package/dist/errors.d.ts +0 -83
  96. package/dist/errors.d.ts.map +0 -1
  97. package/dist/errors.js +0 -142
  98. package/dist/errors.js.map +0 -1
  99. package/dist/event.d.ts +0 -8
  100. package/dist/event.d.ts.map +0 -1
  101. package/dist/event.js +0 -7
  102. package/dist/event.js.map +0 -1
  103. package/dist/execute.d.ts +0 -35
  104. package/dist/execute.d.ts.map +0 -1
  105. package/dist/execute.js +0 -90
  106. package/dist/execute.js.map +0 -1
  107. package/dist/fetch.d.ts +0 -15
  108. package/dist/fetch.d.ts.map +0 -1
  109. package/dist/fetch.js +0 -102
  110. package/dist/fetch.js.map +0 -1
  111. package/dist/gate.d.ts +0 -17
  112. package/dist/gate.d.ts.map +0 -1
  113. package/dist/gate.js +0 -21
  114. package/dist/gate.js.map +0 -1
  115. package/dist/guards.d.ts +0 -17
  116. package/dist/guards.d.ts.map +0 -1
  117. package/dist/guards.js +0 -25
  118. package/dist/guards.js.map +0 -1
  119. package/dist/health.d.ts +0 -18
  120. package/dist/health.d.ts.map +0 -1
  121. package/dist/health.js +0 -5
  122. package/dist/health.js.map +0 -1
  123. package/dist/index.d.ts +0 -48
  124. package/dist/index.d.ts.map +0 -1
  125. package/dist/index.js +0 -51
  126. package/dist/index.js.map +0 -1
  127. package/dist/internal/topo-saves.d.ts +0 -47
  128. package/dist/internal/topo-saves.d.ts.map +0 -1
  129. package/dist/internal/topo-saves.js +0 -310
  130. package/dist/internal/topo-saves.js.map +0 -1
  131. package/dist/internal/topo-store-read.d.ts +0 -67
  132. package/dist/internal/topo-store-read.d.ts.map +0 -1
  133. package/dist/internal/topo-store-read.js +0 -222
  134. package/dist/internal/topo-store-read.js.map +0 -1
  135. package/dist/internal/topo-store.d.ts +0 -12
  136. package/dist/internal/topo-store.d.ts.map +0 -1
  137. package/dist/internal/topo-store.js +0 -571
  138. package/dist/internal/topo-store.js.map +0 -1
  139. package/dist/internal/trails-db.d.ts +0 -16
  140. package/dist/internal/trails-db.d.ts.map +0 -1
  141. package/dist/internal/trails-db.js +0 -118
  142. package/dist/internal/trails-db.js.map +0 -1
  143. package/dist/job.d.ts +0 -24
  144. package/dist/job.d.ts.map +0 -1
  145. package/dist/job.js +0 -17
  146. package/dist/job.js.map +0 -1
  147. package/dist/layer.d.ts +0 -6
  148. package/dist/layer.d.ts.map +0 -1
  149. package/dist/layer.js +0 -4
  150. package/dist/layer.js.map +0 -1
  151. package/dist/path-security.d.ts +0 -28
  152. package/dist/path-security.d.ts.map +0 -1
  153. package/dist/path-security.js +0 -63
  154. package/dist/path-security.js.map +0 -1
  155. package/dist/patterns/bulk.d.ts +0 -15
  156. package/dist/patterns/bulk.d.ts.map +0 -1
  157. package/dist/patterns/bulk.js +0 -14
  158. package/dist/patterns/bulk.js.map +0 -1
  159. package/dist/patterns/change.d.ts +0 -10
  160. package/dist/patterns/change.d.ts.map +0 -1
  161. package/dist/patterns/change.js +0 -10
  162. package/dist/patterns/change.js.map +0 -1
  163. package/dist/patterns/date-range.d.ts +0 -10
  164. package/dist/patterns/date-range.d.ts.map +0 -1
  165. package/dist/patterns/date-range.js +0 -10
  166. package/dist/patterns/date-range.js.map +0 -1
  167. package/dist/patterns/index.d.ts +0 -9
  168. package/dist/patterns/index.d.ts.map +0 -1
  169. package/dist/patterns/index.js +0 -9
  170. package/dist/patterns/index.js.map +0 -1
  171. package/dist/patterns/pagination.d.ts +0 -18
  172. package/dist/patterns/pagination.d.ts.map +0 -1
  173. package/dist/patterns/pagination.js +0 -18
  174. package/dist/patterns/pagination.js.map +0 -1
  175. package/dist/patterns/progress.d.ts +0 -11
  176. package/dist/patterns/progress.d.ts.map +0 -1
  177. package/dist/patterns/progress.js +0 -11
  178. package/dist/patterns/progress.js.map +0 -1
  179. package/dist/patterns/sorting.d.ts +0 -13
  180. package/dist/patterns/sorting.d.ts.map +0 -1
  181. package/dist/patterns/sorting.js +0 -10
  182. package/dist/patterns/sorting.js.map +0 -1
  183. package/dist/patterns/status.d.ts +0 -15
  184. package/dist/patterns/status.d.ts.map +0 -1
  185. package/dist/patterns/status.js +0 -9
  186. package/dist/patterns/status.js.map +0 -1
  187. package/dist/patterns/timestamps.d.ts +0 -10
  188. package/dist/patterns/timestamps.d.ts.map +0 -1
  189. package/dist/patterns/timestamps.js +0 -10
  190. package/dist/patterns/timestamps.js.map +0 -1
  191. package/dist/provision-config.d.ts +0 -22
  192. package/dist/provision-config.d.ts.map +0 -1
  193. package/dist/provision-config.js +0 -210
  194. package/dist/provision-config.js.map +0 -1
  195. package/dist/provision.d.ts +0 -71
  196. package/dist/provision.d.ts.map +0 -1
  197. package/dist/provision.js +0 -56
  198. package/dist/provision.js.map +0 -1
  199. package/dist/redaction/index.d.ts +0 -4
  200. package/dist/redaction/index.d.ts.map +0 -1
  201. package/dist/redaction/index.js +0 -3
  202. package/dist/redaction/index.js.map +0 -1
  203. package/dist/redaction/patterns.d.ts +0 -9
  204. package/dist/redaction/patterns.d.ts.map +0 -1
  205. package/dist/redaction/patterns.js +0 -39
  206. package/dist/redaction/patterns.js.map +0 -1
  207. package/dist/redaction/redactor.d.ts +0 -27
  208. package/dist/redaction/redactor.d.ts.map +0 -1
  209. package/dist/redaction/redactor.js +0 -89
  210. package/dist/redaction/redactor.js.map +0 -1
  211. package/dist/resilience.d.ts +0 -34
  212. package/dist/resilience.d.ts.map +0 -1
  213. package/dist/resilience.js +0 -164
  214. package/dist/resilience.js.map +0 -1
  215. package/dist/result.d.ts +0 -57
  216. package/dist/result.d.ts.map +0 -1
  217. package/dist/result.js +0 -156
  218. package/dist/result.js.map +0 -1
  219. package/dist/run.d.ts +0 -27
  220. package/dist/run.d.ts.map +0 -1
  221. package/dist/run.js +0 -34
  222. package/dist/run.js.map +0 -1
  223. package/dist/serialization.d.ts +0 -27
  224. package/dist/serialization.d.ts.map +0 -1
  225. package/dist/serialization.js +0 -153
  226. package/dist/serialization.js.map +0 -1
  227. package/dist/service-config.d.ts +0 -22
  228. package/dist/service-config.d.ts.map +0 -1
  229. package/dist/service-config.js +0 -210
  230. package/dist/service-config.js.map +0 -1
  231. package/dist/service.d.ts +0 -71
  232. package/dist/service.d.ts.map +0 -1
  233. package/dist/service.js +0 -56
  234. package/dist/service.js.map +0 -1
  235. package/dist/signal.d.ts +0 -33
  236. package/dist/signal.d.ts.map +0 -1
  237. package/dist/signal.js +0 -17
  238. package/dist/signal.js.map +0 -1
  239. package/dist/topo-store.d.ts +0 -48
  240. package/dist/topo-store.d.ts.map +0 -1
  241. package/dist/topo-store.js +0 -175
  242. package/dist/topo-store.js.map +0 -1
  243. package/dist/topo.d.ts +0 -25
  244. package/dist/topo.d.ts.map +0 -1
  245. package/dist/topo.js +0 -112
  246. package/dist/topo.js.map +0 -1
  247. package/dist/trail.d.ts +0 -96
  248. package/dist/trail.d.ts.map +0 -1
  249. package/dist/trail.js +0 -20
  250. package/dist/trail.js.map +0 -1
  251. package/dist/type-utils.d.ts +0 -24
  252. package/dist/type-utils.d.ts.map +0 -1
  253. package/dist/type-utils.js +0 -12
  254. package/dist/type-utils.js.map +0 -1
  255. package/dist/types.d.ts +0 -71
  256. package/dist/types.d.ts.map +0 -1
  257. package/dist/types.js +0 -3
  258. package/dist/types.js.map +0 -1
  259. package/dist/validate-established-topo.d.ts +0 -76
  260. package/dist/validate-established-topo.d.ts.map +0 -1
  261. package/dist/validate-established-topo.js +0 -43
  262. package/dist/validate-established-topo.js.map +0 -1
  263. package/dist/validate-topo.d.ts +0 -24
  264. package/dist/validate-topo.d.ts.map +0 -1
  265. package/dist/validate-topo.js +0 -176
  266. package/dist/validate-topo.js.map +0 -1
  267. package/dist/validation.d.ts +0 -27
  268. package/dist/validation.d.ts.map +0 -1
  269. package/dist/validation.js +0 -165
  270. package/dist/validation.js.map +0 -1
  271. package/dist/workspace.d.ts +0 -25
  272. package/dist/workspace.d.ts.map +0 -1
  273. package/dist/workspace.js +0 -57
  274. package/dist/workspace.js.map +0 -1
  275. package/src/__tests__/blob-ref.test.ts +0 -103
  276. package/src/__tests__/branded.test.ts +0 -148
  277. package/src/__tests__/collections.test.ts +0 -126
  278. package/src/__tests__/context.test.ts +0 -77
  279. package/src/__tests__/derive.test.ts +0 -260
  280. package/src/__tests__/errors.test.ts +0 -309
  281. package/src/__tests__/execute.test.ts +0 -525
  282. package/src/__tests__/fetch.test.ts +0 -217
  283. package/src/__tests__/gate.test.ts +0 -125
  284. package/src/__tests__/guards.test.ts +0 -102
  285. package/src/__tests__/path-security.test.ts +0 -114
  286. package/src/__tests__/patterns.test.ts +0 -273
  287. package/src/__tests__/redaction.test.ts +0 -244
  288. package/src/__tests__/resilience.test.ts +0 -246
  289. package/src/__tests__/result.test.ts +0 -155
  290. package/src/__tests__/run.test.ts +0 -181
  291. package/src/__tests__/serialization.test.ts +0 -401
  292. package/src/__tests__/service-config.test.ts +0 -228
  293. package/src/__tests__/service.test.ts +0 -197
  294. package/src/__tests__/signal.test.ts +0 -82
  295. package/src/__tests__/topo-store-read.test.ts +0 -251
  296. package/src/__tests__/topo-store.test.ts +0 -469
  297. package/src/__tests__/topo.test.ts +0 -277
  298. package/src/__tests__/trail-permit.test.ts +0 -60
  299. package/src/__tests__/trail.test.ts +0 -261
  300. package/src/__tests__/trails-db.test.ts +0 -191
  301. package/src/__tests__/type-utils.test.ts +0 -90
  302. package/src/__tests__/validate-topo.test.ts +0 -489
  303. package/src/__tests__/validation.test.ts +0 -336
  304. package/src/__tests__/workspace.test.ts +0 -183
  305. package/src/event.ts +0 -15
  306. package/src/gate.ts +0 -44
  307. package/src/internal/topo-saves.ts +0 -429
  308. package/src/internal/topo-store-read.ts +0 -473
  309. package/src/internal/topo-store.ts +0 -1087
  310. package/src/provision-config.ts +0 -373
  311. package/src/provision.ts +0 -148
  312. package/src/topo-store.ts +0 -301
  313. package/tsconfig.json +0 -9
  314. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,9 @@
1
+ export { deriveTrail } from './derive-trail.js';
2
+ export type {
3
+ DeriveTrailInput,
4
+ DeriveTrailOperation,
5
+ DeriveTrailOutput,
6
+ DeriveTrailSpec,
7
+ } from './derive-trail.js';
8
+ export { ingest } from './ingest.js';
9
+ export type { IngestOptions, IngestTransform } from './ingest.js';
@@ -0,0 +1,138 @@
1
+ import { z } from 'zod';
2
+
3
+ import { InternalError } from '../errors.js';
4
+ import { composeLayers } from '../layer.js';
5
+ import type { Layer } from '../layer.js';
6
+ import { Result } from '../result.js';
7
+ import type { Signal } from '../signal.js';
8
+ import { trail } from '../trail.js';
9
+ import type { Trail, TrailExample, TrailSpec } from '../trail.js';
10
+ import type { TrailContext } from '../types.js';
11
+
12
+ type SchemaValue<TSchema extends z.ZodType> = z.output<TSchema>;
13
+
14
+ type ExampleBearingSchema<TSchema extends z.ZodType> = TSchema & {
15
+ readonly examples?: readonly Partial<SchemaValue<TSchema>>[] | undefined;
16
+ };
17
+
18
+ interface IngestBaseOptions<TSchema extends z.ZodType, TSignal> extends Omit<
19
+ TrailSpec<SchemaValue<TSchema>, void>,
20
+ 'blaze' | 'examples' | 'fires' | 'input' | 'intent' | 'output' | 'pattern'
21
+ > {
22
+ /** Override the derived trail id. Defaults to `${signal}.ingest`. */
23
+ readonly id?: string | undefined;
24
+ /** Validated external payload shape. */
25
+ readonly schema: TSchema;
26
+ /** Signal to fire after verification and optional transformation. */
27
+ readonly signal: Signal<TSignal>;
28
+ /** Optional per-trail verification layer, e.g. HMAC signature checks. */
29
+ readonly verify?: Layer | undefined;
30
+ }
31
+
32
+ export type IngestTransform<TInput, TSignal> = (
33
+ payload: TInput,
34
+ ctx: TrailContext
35
+ ) => TSignal | Promise<TSignal>;
36
+
37
+ export interface IngestOptions<
38
+ TSchema extends z.ZodType,
39
+ TSignal,
40
+ > extends IngestBaseOptions<TSchema, TSignal> {
41
+ readonly transform?:
42
+ | IngestTransform<SchemaValue<TSchema>, TSignal>
43
+ | undefined;
44
+ }
45
+
46
+ const deriveExampleName = (signalId: string, index: number): string =>
47
+ `Ingest ${signalId} ${index + 1}`;
48
+
49
+ const deriveExamples = <TSchema extends z.ZodType>(
50
+ schema: ExampleBearingSchema<TSchema>,
51
+ signalId: string
52
+ ): readonly TrailExample<SchemaValue<TSchema>, void>[] | undefined => {
53
+ const { examples } = schema;
54
+ if (examples === undefined || examples.length === 0) {
55
+ return undefined;
56
+ }
57
+
58
+ return Object.freeze(
59
+ examples.map((example, index) => ({
60
+ input: example,
61
+ name: deriveExampleName(signalId, index),
62
+ }))
63
+ );
64
+ };
65
+
66
+ const createIngestBlaze =
67
+ <TSchema extends z.ZodType, TSignal>(
68
+ signalRef: Signal<TSignal>,
69
+ signalId: string,
70
+ trailId: string,
71
+ transform: IngestTransform<SchemaValue<TSchema>, TSignal> | undefined
72
+ ) =>
73
+ async (
74
+ input: SchemaValue<TSchema>,
75
+ ctx: TrailContext
76
+ ): Promise<Result<void, Error>> => {
77
+ if (ctx.fire === undefined) {
78
+ return Result.err(
79
+ new InternalError(
80
+ `ingest("${trailId}") requires topo-backed execution to fire "${signalId}"`
81
+ )
82
+ );
83
+ }
84
+
85
+ try {
86
+ const payload =
87
+ transform === undefined
88
+ ? (input as TSignal)
89
+ : await transform(input, ctx);
90
+ await ctx.fire(signalRef, payload);
91
+ return Result.ok();
92
+ } catch (error) {
93
+ const message = `ingest("${trailId}"): ${error instanceof Error ? error.message : String(error)}`;
94
+ return Result.err(
95
+ error instanceof Error
96
+ ? new InternalError(message, { cause: error })
97
+ : new InternalError(message)
98
+ );
99
+ }
100
+ };
101
+
102
+ export const ingest = <
103
+ TSchema extends z.ZodType,
104
+ TSignal = SchemaValue<TSchema>,
105
+ >(
106
+ options: IngestOptions<TSchema, TSignal>
107
+ ): Trail<SchemaValue<TSchema>, void> => {
108
+ const signalId = options.signal.id;
109
+ const id = options.id ?? `${signalId}.ingest`;
110
+ const { id: _id, schema, signal, transform, verify, ...trailSpec } = options;
111
+ const baseBlaze = createIngestBlaze<TSchema, TSignal>(
112
+ signal,
113
+ signalId,
114
+ id,
115
+ transform
116
+ );
117
+ const baseTrail = trail(id, {
118
+ ...trailSpec,
119
+ blaze: baseBlaze,
120
+ examples: deriveExamples(schema as ExampleBearingSchema<TSchema>, signalId),
121
+ fires: [signal],
122
+ input: schema as z.ZodType<SchemaValue<TSchema>>,
123
+ intent: 'write',
124
+ output: z.void(),
125
+ pattern: 'ingest',
126
+ }) as Trail<SchemaValue<TSchema>, void>;
127
+
128
+ if (verify === undefined) {
129
+ return baseTrail;
130
+ }
131
+
132
+ // Verification is a per-factory concern, so compose it locally instead of
133
+ // mutating runner-wide layer configuration.
134
+ return Object.freeze({
135
+ ...baseTrail,
136
+ blaze: composeLayers([verify], baseTrail, baseTrail.blaze),
137
+ }) as Trail<SchemaValue<TSchema>, void>;
138
+ };
@@ -2,28 +2,41 @@ import { Database } from 'bun:sqlite';
2
2
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
3
  import { dirname, join, resolve } from 'node:path';
4
4
 
5
- import { NotFoundError } from '../errors.js';
5
+ import { NotFoundError } from './errors.js';
6
6
 
7
7
  const TRAILS_DIR = '.trails';
8
8
  const TRAILS_DB_FILE = 'trails.db';
9
+ const TRAILS_CACHE_DIR = 'cache';
10
+ const TRAILS_STATE_DIR = 'state';
9
11
  const SCHEMA_VERSION_TABLE = 'meta_schema_versions';
10
- const WORKSPACE_SUBDIRS = ['config', 'dev', 'generated'] as const;
11
- const REQUIRED_GITIGNORE_LINES = [
12
+ const WORKSPACE_SUBDIRS = [TRAILS_CACHE_DIR, TRAILS_STATE_DIR] as const;
13
+
14
+ /**
15
+ * The canonical lines written to a freshly-bootstrapped
16
+ * `.trails/.gitignore`. Kept as the source of truth for every consumer that
17
+ * needs to either write the file (scaffold) or audit its content (tests).
18
+ *
19
+ * @see {@link WORKSPACE_GITIGNORE_CONTENT} for the rendered string form.
20
+ */
21
+ export const WORKSPACE_GITIGNORE_LINES = [
12
22
  '# Local config overrides',
13
- 'config/',
23
+ 'config.local.js',
24
+ 'config.local.ts',
14
25
  '',
15
- '# Development state',
16
- 'dev/',
26
+ '# Rebuildable cache',
27
+ 'cache/',
17
28
  '',
18
- '# Generated artifacts',
19
- 'generated/',
29
+ '# Mutable runtime state',
30
+ 'state/',
20
31
  '',
21
- '# Shared Trails database',
22
- 'trails.db',
23
- 'trails.db-shm',
24
- 'trails.db-wal',
25
- '',
26
- ];
32
+ ] as const;
33
+
34
+ /**
35
+ * The canonical rendered `.trails/.gitignore` content. Use this when writing
36
+ * the file eagerly (e.g. during `trails create` scaffolding) or when asserting
37
+ * on the workspace bootstrap output.
38
+ */
39
+ export const WORKSPACE_GITIGNORE_CONTENT = `${WORKSPACE_GITIGNORE_LINES.join('\n').trimEnd()}\n`;
27
40
 
28
41
  export interface TrailsDbLocationOptions {
29
42
  readonly path?: string;
@@ -40,31 +53,27 @@ interface SchemaVersionRow {
40
53
  readonly version: number;
41
54
  }
42
55
 
43
- const resolveRootDir = (rootDir?: string): string =>
56
+ const deriveRootDir = (rootDir?: string): string =>
44
57
  resolve(rootDir ?? process.cwd());
45
58
 
46
- export const resolveTrailsDir = (options?: TrailsDbLocationOptions): string =>
47
- join(resolveRootDir(options?.rootDir), TRAILS_DIR);
59
+ export const deriveTrailsDir = (options?: TrailsDbLocationOptions): string =>
60
+ join(deriveRootDir(options?.rootDir), TRAILS_DIR);
48
61
 
49
- export const resolveTrailsDbPath = (
50
- options?: TrailsDbLocationOptions
51
- ): string =>
62
+ export const deriveTrailsDbPath = (options?: TrailsDbLocationOptions): string =>
52
63
  options?.path
53
64
  ? resolve(options.path)
54
- : join(resolveTrailsDir(options), TRAILS_DB_FILE);
65
+ : join(deriveTrailsDir(options), TRAILS_STATE_DIR, TRAILS_DB_FILE);
55
66
 
56
67
  const ensureDbParentDir = (dbPath: string): void => {
57
68
  mkdirSync(dirname(dbPath), { recursive: true });
58
69
  };
59
70
 
60
- const GITIGNORE_TEMPLATE = `${REQUIRED_GITIGNORE_LINES.join('\n').trimEnd()}\n`;
61
-
62
71
  const appendMissingGitignoreLines = (
63
72
  gitignorePath: string,
64
73
  content: string
65
74
  ): void => {
66
75
  const existingLines = new Set(content.split('\n').map((l) => l.trim()));
67
- const missing = REQUIRED_GITIGNORE_LINES.filter(
76
+ const missing = WORKSPACE_GITIGNORE_LINES.filter(
68
77
  (line) => line !== '' && !existingLines.has(line)
69
78
  );
70
79
 
@@ -80,7 +89,7 @@ const ensureWorkspaceGitignore = (trailsDir: string): void => {
80
89
  const gitignorePath = join(trailsDir, '.gitignore');
81
90
 
82
91
  if (!existsSync(gitignorePath)) {
83
- writeFileSync(gitignorePath, GITIGNORE_TEMPLATE);
92
+ writeFileSync(gitignorePath, WORKSPACE_GITIGNORE_CONTENT);
84
93
  return;
85
94
  }
86
95
 
@@ -90,8 +99,18 @@ const ensureWorkspaceGitignore = (trailsDir: string): void => {
90
99
  );
91
100
  };
92
101
 
93
- const ensureWorkspaceDir = (rootDir: string): void => {
94
- const trailsDir = resolveTrailsDir({ rootDir });
102
+ /**
103
+ * Bootstrap the `.trails/` workspace at `rootDir`.
104
+ *
105
+ * Creates the workspace directory plus the canonical `cache/` and `state/`
106
+ * subdirectories, then either writes a fresh `.gitignore` matching
107
+ * {@link WORKSPACE_GITIGNORE_CONTENT} or appends any missing canonical lines
108
+ * to an existing one. Safe to call repeatedly. This is the single canonical
109
+ * source of truth for workspace layout — scaffolding, configuration loading,
110
+ * and runtime DB initialization all flow through here.
111
+ */
112
+ export const ensureTrailsWorkspace = (rootDir: string): void => {
113
+ const trailsDir = deriveTrailsDir({ rootDir });
95
114
  mkdirSync(trailsDir, { recursive: true });
96
115
  for (const subdir of WORKSPACE_SUBDIRS) {
97
116
  mkdirSync(join(trailsDir, subdir), { recursive: true });
@@ -140,13 +159,13 @@ const writeSubsystemVersion = (
140
159
  export const openWriteTrailsDb = (
141
160
  options?: TrailsDbLocationOptions
142
161
  ): Database => {
143
- const rootDir = resolveRootDir(options?.rootDir);
144
- const dbPath = resolveTrailsDbPath(
162
+ const rootDir = deriveRootDir(options?.rootDir);
163
+ const dbPath = deriveTrailsDbPath(
145
164
  options?.path ? { path: options.path, rootDir } : { rootDir }
146
165
  );
147
166
 
148
167
  if (options?.path === undefined) {
149
- ensureWorkspaceDir(rootDir);
168
+ ensureTrailsWorkspace(rootDir);
150
169
  } else {
151
170
  ensureDbParentDir(dbPath);
152
171
  }
@@ -160,7 +179,7 @@ export const openWriteTrailsDb = (
160
179
  export const openReadTrailsDb = (
161
180
  options?: TrailsDbLocationOptions
162
181
  ): Database => {
163
- const dbPath = resolveTrailsDbPath(options);
182
+ const dbPath = deriveTrailsDbPath(options);
164
183
  if (!existsSync(dbPath)) {
165
184
  throw new NotFoundError(
166
185
  `Trails database not found at "${dbPath}". Run a write operation first to initialize it.`
@@ -0,0 +1,174 @@
1
+ import type {
2
+ ErrorCategory,
3
+ ErrorCategoryCodes,
4
+ ErrorClassRegistryEntry,
5
+ FixedErrorClassRegistryEntry,
6
+ TrailsError,
7
+ } from './errors.js';
8
+ import {
9
+ codesByCategory,
10
+ errorClasses,
11
+ exitCodeMap,
12
+ isTrailsError,
13
+ jsonRpcCodeMap,
14
+ statusCodeMap,
15
+ } from './errors.js';
16
+ import {
17
+ INTERNAL_ERROR_PUBLIC_MESSAGE,
18
+ redactErrorString,
19
+ } from './error-projection.js';
20
+
21
+ export const surfaceNames = ['cli', 'http', 'jsonRpc', 'mcp'] as const;
22
+
23
+ export type SurfaceName = (typeof surfaceNames)[number];
24
+
25
+ const surfaceCodeKeys = {
26
+ cli: 'exit',
27
+ http: 'http',
28
+ jsonRpc: 'jsonRpc',
29
+ mcp: 'jsonRpc',
30
+ } as const satisfies Record<SurfaceName, keyof ErrorCategoryCodes>;
31
+
32
+ export type SurfaceErrorMapper<T> = (error: TrailsError) => T;
33
+
34
+ export type SurfaceErrorMappings<T> = Record<ErrorCategory, T>;
35
+
36
+ /**
37
+ * Union of every surface-specific error code emitted by {@link surfaceErrorMap}.
38
+ *
39
+ * @remarks
40
+ * Previously parameterized by surface (`SurfaceErrorCode<'cli'>` etc.), but
41
+ * the generic collapsed to `number` because the underlying maps were typed as
42
+ * `Record<ErrorCategory, number>`. With `as const satisfies` on the maps the
43
+ * per-surface literals are now observable, but TypeScript cannot narrow
44
+ * `surfaceErrorMap[surface][error.category]` through a generic `TSurface`
45
+ * without an unsound cast. The non-generic union honestly reflects what
46
+ * `mapSurfaceError` returns at the call site.
47
+ */
48
+ export type SurfaceErrorCode =
49
+ (typeof codesByCategory)[ErrorCategory][(typeof surfaceCodeKeys)[SurfaceName]];
50
+
51
+ export interface SurfaceErrorProjection {
52
+ readonly category: ErrorCategory;
53
+ readonly code: SurfaceErrorCode;
54
+ readonly message: string;
55
+ readonly name: string;
56
+ readonly retryable: boolean;
57
+ readonly surface: SurfaceName;
58
+ }
59
+
60
+ export interface ErrorClassSurfaceProjection {
61
+ readonly category: ErrorCategory;
62
+ readonly code: SurfaceErrorCode;
63
+ readonly name: string;
64
+ readonly retryable: boolean;
65
+ readonly surface: SurfaceName;
66
+ }
67
+
68
+ export const createSurfaceErrorMapper =
69
+ <T>(mappings: SurfaceErrorMappings<T>): SurfaceErrorMapper<T> =>
70
+ (error) =>
71
+ mappings[error.category];
72
+
73
+ export const surfaceErrorMap = {
74
+ cli: exitCodeMap,
75
+ http: statusCodeMap,
76
+ jsonRpc: jsonRpcCodeMap,
77
+ mcp: jsonRpcCodeMap,
78
+ } as const satisfies Record<SurfaceName, SurfaceErrorMappings<number>>;
79
+
80
+ export const surfaceErrorRegistry = {
81
+ cli: {
82
+ map: createSurfaceErrorMapper(surfaceErrorMap.cli),
83
+ values: surfaceErrorMap.cli,
84
+ },
85
+ http: {
86
+ map: createSurfaceErrorMapper(surfaceErrorMap.http),
87
+ values: surfaceErrorMap.http,
88
+ },
89
+ jsonRpc: {
90
+ map: createSurfaceErrorMapper(surfaceErrorMap.jsonRpc),
91
+ values: surfaceErrorMap.jsonRpc,
92
+ },
93
+ mcp: {
94
+ map: createSurfaceErrorMapper(surfaceErrorMap.mcp),
95
+ values: surfaceErrorMap.mcp,
96
+ },
97
+ } as const;
98
+
99
+ export const mapSurfaceError = (
100
+ surface: SurfaceName,
101
+ error: TrailsError
102
+ ): SurfaceErrorCode =>
103
+ codesByCategory[error.category][surfaceCodeKeys[surface]];
104
+
105
+ export const projectSurfaceError = (
106
+ surface: SurfaceName,
107
+ error: TrailsError
108
+ ): SurfaceErrorProjection => ({
109
+ category: error.category,
110
+ code: mapSurfaceError(surface, error),
111
+ message: error.message,
112
+ name: error.name,
113
+ retryable: error.retryable,
114
+ surface,
115
+ });
116
+
117
+ export const projectPublicSurfaceError = (
118
+ surface: SurfaceName,
119
+ error: Error
120
+ ): SurfaceErrorProjection => {
121
+ if (isTrailsError(error)) {
122
+ const projection = projectSurfaceError(surface, error);
123
+ return {
124
+ ...projection,
125
+ message:
126
+ projection.category === 'internal'
127
+ ? INTERNAL_ERROR_PUBLIC_MESSAGE
128
+ : redactErrorString(projection.message),
129
+ };
130
+ }
131
+
132
+ return {
133
+ category: 'internal',
134
+ code: codesByCategory.internal[surfaceCodeKeys[surface]],
135
+ message: INTERNAL_ERROR_PUBLIC_MESSAGE,
136
+ name: 'InternalError',
137
+ retryable: false,
138
+ surface,
139
+ };
140
+ };
141
+
142
+ const isFixedErrorClassEntry = (
143
+ entry: ErrorClassRegistryEntry
144
+ ): entry is FixedErrorClassRegistryEntry => entry.category !== 'dynamic';
145
+
146
+ const fixedErrorClassByName: ReadonlyMap<string, FixedErrorClassRegistryEntry> =
147
+ new Map(
148
+ errorClasses.flatMap((entry): [string, FixedErrorClassRegistryEntry][] =>
149
+ isFixedErrorClassEntry(entry) ? [[entry.name, entry]] : []
150
+ )
151
+ );
152
+
153
+ /**
154
+ * Project a known error class name onto a surface without constructing it.
155
+ *
156
+ * Dynamic-category errors such as `RetryExhaustedError` return `undefined`
157
+ * because their surface code depends on the wrapped runtime error.
158
+ */
159
+ export const projectErrorClassSurface = (
160
+ surface: SurfaceName,
161
+ errorName: string
162
+ ): ErrorClassSurfaceProjection | undefined => {
163
+ const entry = fixedErrorClassByName.get(errorName);
164
+ if (entry === undefined) {
165
+ return undefined;
166
+ }
167
+ return {
168
+ category: entry.category,
169
+ code: codesByCategory[entry.category][surfaceCodeKeys[surface]],
170
+ name: entry.name,
171
+ retryable: entry.retryable,
172
+ surface,
173
+ };
174
+ };
package/src/type-utils.ts CHANGED
@@ -13,11 +13,26 @@ import type { AnyTrail, Trail } from './trail.js';
13
13
 
14
14
  /** Extract the input type from a Trail. */
15
15
  export type TrailInput<T extends AnyTrail> =
16
- T extends Trail<infer I, any> ? I : never;
16
+ T extends Trail<infer I, any, any> ? I : never;
17
17
 
18
18
  /** Extract the output type from a Trail. */
19
19
  export type TrailOutput<T extends AnyTrail> =
20
- T extends Trail<any, infer O> ? O : never;
20
+ T extends Trail<any, infer O, any> ? O : never;
21
+
22
+ /**
23
+ * Extract the cross-callable input type from a trail.
24
+ *
25
+ * When a trail declares `crossInput`, callers via `ctx.cross()` must pass
26
+ * both the public input fields and the composition-only fields. This type
27
+ * merges both schemas so the compiler enforces the full shape at the call
28
+ * site. Falls back to plain `TrailInput<T>` when no `crossInput` exists.
29
+ */
30
+ export type CrossInput<T extends AnyTrail> =
31
+ T extends Trail<infer I, any, infer CI>
32
+ ? [CI] extends [never]
33
+ ? I
34
+ : I & CI
35
+ : never;
21
36
 
22
37
  /**
23
38
  * Extracts the full `Result<Output, Error>` type from a trail definition.