@ontrails/core 1.0.0-beta.4 → 1.0.0-beta.41

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 (247) hide show
  1. package/CHANGELOG.md +412 -12
  2. package/README.md +112 -28
  3. package/package.json +11 -1
  4. package/src/activation-provenance.ts +116 -0
  5. package/src/activation-source-compatibility.ts +430 -0
  6. package/src/activation-source-projection.ts +227 -0
  7. package/src/activation-source.ts +93 -0
  8. package/src/blob-ref.ts +51 -0
  9. package/src/branded.ts +1 -1
  10. package/src/compose-batch.ts +69 -0
  11. package/src/compose-schema.ts +36 -0
  12. package/src/context.ts +59 -11
  13. package/src/derive.ts +271 -19
  14. package/src/detours.ts +8 -0
  15. package/src/diagnostics.ts +21 -0
  16. package/src/draft.ts +350 -0
  17. package/src/entity.ts +346 -0
  18. package/src/error-projection.ts +51 -0
  19. package/src/errors.ts +335 -48
  20. package/src/execute.ts +1577 -0
  21. package/src/fire.ts +1172 -0
  22. package/src/glob.ts +81 -0
  23. package/src/index.ts +568 -32
  24. package/src/internal/fork-ctx.ts +69 -0
  25. package/src/layer-projection.ts +193 -0
  26. package/src/layer.ts +43 -6
  27. package/src/observe.ts +361 -0
  28. package/src/path-scope.ts +66 -0
  29. package/src/path-security.ts +11 -3
  30. package/src/permits.ts +12 -0
  31. package/src/queue.ts +163 -0
  32. package/src/redaction/patterns.ts +6 -3
  33. package/src/resilience.ts +11 -11
  34. package/src/resource-config.ts +804 -0
  35. package/src/resource.ts +194 -0
  36. package/src/result.ts +18 -0
  37. package/src/run.ts +76 -0
  38. package/src/runtime-builtins.ts +69 -0
  39. package/src/schedule-runtime.ts +689 -0
  40. package/src/schedule.ts +326 -0
  41. package/src/serialization.ts +92 -55
  42. package/src/sha256.ts +136 -0
  43. package/src/signal-diagnostics.ts +633 -0
  44. package/src/signal-ref.ts +111 -0
  45. package/src/signal.ts +104 -0
  46. package/src/store/accessor-protocol.ts +56 -0
  47. package/src/store/index.ts +4 -0
  48. package/src/structured-examples.ts +248 -0
  49. package/src/surface-derivation.ts +91 -0
  50. package/src/surface-filter.ts +101 -0
  51. package/src/surface-overlay.ts +694 -0
  52. package/src/surface-versioning.ts +42 -0
  53. package/src/topo.ts +762 -38
  54. package/src/tracing.ts +346 -0
  55. package/src/trail-id-glob.ts +15 -0
  56. package/src/trail.ts +1244 -34
  57. package/src/trails/derive-trail.ts +835 -0
  58. package/src/trails/index.ts +9 -0
  59. package/src/trails/ingest.ts +152 -0
  60. package/src/trails-db.ts +212 -0
  61. package/src/transport-error-map.ts +174 -0
  62. package/src/type-utils.ts +87 -0
  63. package/src/types.ts +254 -12
  64. package/src/validate-established-topo.ts +73 -0
  65. package/src/validate-topo.ts +569 -55
  66. package/src/validation.ts +152 -4
  67. package/src/version-marker.ts +716 -0
  68. package/src/version-resolution.ts +308 -0
  69. package/src/version-runtime.ts +120 -0
  70. package/src/webhook.ts +461 -0
  71. package/src/workspace.ts +183 -16
  72. package/src/zod-wrappers.ts +72 -0
  73. package/.turbo/turbo-build.log +0 -1
  74. package/.turbo/turbo-lint.log +0 -3
  75. package/.turbo/turbo-typecheck.log +0 -1
  76. package/dist/adapters.d.ts +0 -39
  77. package/dist/adapters.d.ts.map +0 -1
  78. package/dist/adapters.js +0 -2
  79. package/dist/adapters.js.map +0 -1
  80. package/dist/blob-ref.d.ts +0 -20
  81. package/dist/blob-ref.d.ts.map +0 -1
  82. package/dist/blob-ref.js +0 -22
  83. package/dist/blob-ref.js.map +0 -1
  84. package/dist/branded.d.ts +0 -36
  85. package/dist/branded.d.ts.map +0 -1
  86. package/dist/branded.js +0 -89
  87. package/dist/branded.js.map +0 -1
  88. package/dist/collections.d.ts +0 -31
  89. package/dist/collections.d.ts.map +0 -1
  90. package/dist/collections.js +0 -60
  91. package/dist/collections.js.map +0 -1
  92. package/dist/context.d.ts +0 -10
  93. package/dist/context.d.ts.map +0 -1
  94. package/dist/context.js +0 -15
  95. package/dist/context.js.map +0 -1
  96. package/dist/derive.d.ts +0 -33
  97. package/dist/derive.d.ts.map +0 -1
  98. package/dist/derive.js +0 -125
  99. package/dist/derive.js.map +0 -1
  100. package/dist/errors.d.ts +0 -83
  101. package/dist/errors.d.ts.map +0 -1
  102. package/dist/errors.js +0 -142
  103. package/dist/errors.js.map +0 -1
  104. package/dist/event.d.ts +0 -45
  105. package/dist/event.d.ts.map +0 -1
  106. package/dist/event.js +0 -17
  107. package/dist/event.js.map +0 -1
  108. package/dist/fetch.d.ts +0 -15
  109. package/dist/fetch.d.ts.map +0 -1
  110. package/dist/fetch.js +0 -102
  111. package/dist/fetch.js.map +0 -1
  112. package/dist/guards.d.ts +0 -17
  113. package/dist/guards.d.ts.map +0 -1
  114. package/dist/guards.js +0 -25
  115. package/dist/guards.js.map +0 -1
  116. package/dist/health.d.ts +0 -18
  117. package/dist/health.d.ts.map +0 -1
  118. package/dist/health.js +0 -5
  119. package/dist/health.js.map +0 -1
  120. package/dist/index.d.ts +0 -34
  121. package/dist/index.d.ts.map +0 -1
  122. package/dist/index.js +0 -38
  123. package/dist/index.js.map +0 -1
  124. package/dist/job.d.ts +0 -24
  125. package/dist/job.d.ts.map +0 -1
  126. package/dist/job.js +0 -17
  127. package/dist/job.js.map +0 -1
  128. package/dist/layer.d.ts +0 -17
  129. package/dist/layer.d.ts.map +0 -1
  130. package/dist/layer.js +0 -21
  131. package/dist/layer.js.map +0 -1
  132. package/dist/path-security.d.ts +0 -28
  133. package/dist/path-security.d.ts.map +0 -1
  134. package/dist/path-security.js +0 -63
  135. package/dist/path-security.js.map +0 -1
  136. package/dist/patterns/bulk.d.ts +0 -15
  137. package/dist/patterns/bulk.d.ts.map +0 -1
  138. package/dist/patterns/bulk.js +0 -14
  139. package/dist/patterns/bulk.js.map +0 -1
  140. package/dist/patterns/change.d.ts +0 -10
  141. package/dist/patterns/change.d.ts.map +0 -1
  142. package/dist/patterns/change.js +0 -10
  143. package/dist/patterns/change.js.map +0 -1
  144. package/dist/patterns/date-range.d.ts +0 -10
  145. package/dist/patterns/date-range.d.ts.map +0 -1
  146. package/dist/patterns/date-range.js +0 -10
  147. package/dist/patterns/date-range.js.map +0 -1
  148. package/dist/patterns/index.d.ts +0 -9
  149. package/dist/patterns/index.d.ts.map +0 -1
  150. package/dist/patterns/index.js +0 -9
  151. package/dist/patterns/index.js.map +0 -1
  152. package/dist/patterns/pagination.d.ts +0 -18
  153. package/dist/patterns/pagination.d.ts.map +0 -1
  154. package/dist/patterns/pagination.js +0 -18
  155. package/dist/patterns/pagination.js.map +0 -1
  156. package/dist/patterns/progress.d.ts +0 -11
  157. package/dist/patterns/progress.d.ts.map +0 -1
  158. package/dist/patterns/progress.js +0 -11
  159. package/dist/patterns/progress.js.map +0 -1
  160. package/dist/patterns/sorting.d.ts +0 -13
  161. package/dist/patterns/sorting.d.ts.map +0 -1
  162. package/dist/patterns/sorting.js +0 -10
  163. package/dist/patterns/sorting.js.map +0 -1
  164. package/dist/patterns/status.d.ts +0 -15
  165. package/dist/patterns/status.d.ts.map +0 -1
  166. package/dist/patterns/status.js +0 -9
  167. package/dist/patterns/status.js.map +0 -1
  168. package/dist/patterns/timestamps.d.ts +0 -10
  169. package/dist/patterns/timestamps.d.ts.map +0 -1
  170. package/dist/patterns/timestamps.js +0 -10
  171. package/dist/patterns/timestamps.js.map +0 -1
  172. package/dist/redaction/index.d.ts +0 -4
  173. package/dist/redaction/index.d.ts.map +0 -1
  174. package/dist/redaction/index.js +0 -3
  175. package/dist/redaction/index.js.map +0 -1
  176. package/dist/redaction/patterns.d.ts +0 -9
  177. package/dist/redaction/patterns.d.ts.map +0 -1
  178. package/dist/redaction/patterns.js +0 -39
  179. package/dist/redaction/patterns.js.map +0 -1
  180. package/dist/redaction/redactor.d.ts +0 -27
  181. package/dist/redaction/redactor.d.ts.map +0 -1
  182. package/dist/redaction/redactor.js +0 -89
  183. package/dist/redaction/redactor.js.map +0 -1
  184. package/dist/resilience.d.ts +0 -34
  185. package/dist/resilience.d.ts.map +0 -1
  186. package/dist/resilience.js +0 -164
  187. package/dist/resilience.js.map +0 -1
  188. package/dist/result.d.ts +0 -57
  189. package/dist/result.d.ts.map +0 -1
  190. package/dist/result.js +0 -156
  191. package/dist/result.js.map +0 -1
  192. package/dist/serialization.d.ts +0 -27
  193. package/dist/serialization.d.ts.map +0 -1
  194. package/dist/serialization.js +0 -153
  195. package/dist/serialization.js.map +0 -1
  196. package/dist/topo.d.ts +0 -16
  197. package/dist/topo.d.ts.map +0 -1
  198. package/dist/topo.js +0 -66
  199. package/dist/topo.js.map +0 -1
  200. package/dist/trail.d.ts +0 -89
  201. package/dist/trail.d.ts.map +0 -1
  202. package/dist/trail.js +0 -18
  203. package/dist/trail.js.map +0 -1
  204. package/dist/types.d.ts +0 -46
  205. package/dist/types.d.ts.map +0 -1
  206. package/dist/types.js +0 -2
  207. package/dist/types.js.map +0 -1
  208. package/dist/validate-topo.d.ts +0 -24
  209. package/dist/validate-topo.d.ts.map +0 -1
  210. package/dist/validate-topo.js +0 -158
  211. package/dist/validate-topo.js.map +0 -1
  212. package/dist/validation.d.ts +0 -27
  213. package/dist/validation.d.ts.map +0 -1
  214. package/dist/validation.js +0 -134
  215. package/dist/validation.js.map +0 -1
  216. package/dist/workspace.d.ts +0 -25
  217. package/dist/workspace.d.ts.map +0 -1
  218. package/dist/workspace.js +0 -57
  219. package/dist/workspace.js.map +0 -1
  220. package/src/__tests__/blob-ref.test.ts +0 -103
  221. package/src/__tests__/branded.test.ts +0 -148
  222. package/src/__tests__/collections.test.ts +0 -126
  223. package/src/__tests__/context.test.ts +0 -66
  224. package/src/__tests__/derive.test.ts +0 -203
  225. package/src/__tests__/errors.test.ts +0 -309
  226. package/src/__tests__/event.test.ts +0 -82
  227. package/src/__tests__/fetch.test.ts +0 -217
  228. package/src/__tests__/guards.test.ts +0 -102
  229. package/src/__tests__/job.test.ts +0 -98
  230. package/src/__tests__/layer.test.ts +0 -212
  231. package/src/__tests__/path-security.test.ts +0 -114
  232. package/src/__tests__/patterns.test.ts +0 -273
  233. package/src/__tests__/redaction.test.ts +0 -244
  234. package/src/__tests__/resilience.test.ts +0 -246
  235. package/src/__tests__/result.test.ts +0 -155
  236. package/src/__tests__/serialization.test.ts +0 -401
  237. package/src/__tests__/topo.test.ts +0 -181
  238. package/src/__tests__/trail.test.ts +0 -217
  239. package/src/__tests__/validate-topo.test.ts +0 -278
  240. package/src/__tests__/validation.test.ts +0 -283
  241. package/src/__tests__/workspace.test.ts +0 -183
  242. package/src/adapters.ts +0 -68
  243. package/src/event.ts +0 -77
  244. package/src/health.ts +0 -23
  245. package/src/job.ts +0 -20
  246. package/tsconfig.json +0 -9
  247. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,51 @@
1
+ import type { ErrorCategory } from './errors.js';
2
+ import { isTrailsError } from './errors.js';
3
+ import { createRedactor } from './redaction/index.js';
4
+
5
+ const errorRedactor = createRedactor();
6
+
7
+ export const INTERNAL_ERROR_PUBLIC_MESSAGE = 'Internal server error';
8
+
9
+ export interface ErrorDiagnosticsProjection {
10
+ readonly category?: ErrorCategory | undefined;
11
+ readonly context?: Record<string, unknown> | undefined;
12
+ readonly message: string;
13
+ readonly name: string;
14
+ readonly retryable?: boolean | undefined;
15
+ readonly stack?: string | undefined;
16
+ }
17
+
18
+ export const redactErrorString = (value: string): string =>
19
+ errorRedactor.redact(value);
20
+
21
+ export const redactErrorContext = (
22
+ context: Record<string, unknown> | undefined
23
+ ): Record<string, unknown> | undefined =>
24
+ context === undefined ? undefined : errorRedactor.redactObject(context);
25
+
26
+ export const redactErrorStack = (
27
+ stack: string | undefined
28
+ ): string | undefined =>
29
+ stack === undefined ? undefined : redactErrorString(stack);
30
+
31
+ export const projectErrorDiagnostics = (
32
+ error: Error
33
+ ): ErrorDiagnosticsProjection => {
34
+ const context = isTrailsError(error)
35
+ ? redactErrorContext(error.context)
36
+ : undefined;
37
+ const stack = redactErrorStack(error.stack);
38
+
39
+ return {
40
+ ...(isTrailsError(error)
41
+ ? {
42
+ category: error.category,
43
+ retryable: error.retryable,
44
+ }
45
+ : {}),
46
+ ...(context === undefined ? {} : { context }),
47
+ message: redactErrorString(error.message),
48
+ name: error.name || error.constructor.name || 'Error',
49
+ ...(stack === undefined ? {} : { stack }),
50
+ };
51
+ };
package/src/errors.ts CHANGED
@@ -10,17 +10,21 @@
10
10
  // Category
11
11
  // ---------------------------------------------------------------------------
12
12
 
13
- export type ErrorCategory =
14
- | 'validation'
15
- | 'not_found'
16
- | 'conflict'
17
- | 'permission'
18
- | 'timeout'
19
- | 'rate_limit'
20
- | 'network'
21
- | 'internal'
22
- | 'auth'
23
- | 'cancelled';
13
+ export const errorCategories = [
14
+ 'validation',
15
+ 'not_found',
16
+ 'conflict',
17
+ 'permission',
18
+ 'timeout',
19
+ 'rate_limit',
20
+ 'network',
21
+ 'shift',
22
+ 'internal',
23
+ 'auth',
24
+ 'cancelled',
25
+ ] as const;
26
+
27
+ export type ErrorCategory = (typeof errorCategories)[number];
24
28
 
25
29
  // ---------------------------------------------------------------------------
26
30
  // Base class
@@ -65,6 +69,64 @@ export class NotFoundError extends TrailsError {
65
69
  readonly retryable = false as const;
66
70
  }
67
71
 
72
+ export class VersionNotSupportedError extends NotFoundError {
73
+ readonly reason?: string | undefined;
74
+ readonly requested?: number | string | undefined;
75
+ readonly supported?: readonly number[] | undefined;
76
+ readonly trailId?: string | undefined;
77
+
78
+ constructor(
79
+ message: string,
80
+ options?: { cause?: Error; context?: Record<string, unknown> }
81
+ );
82
+ constructor(
83
+ trailId: string,
84
+ requested: number | string,
85
+ supported: readonly number[],
86
+ reason?: string | undefined
87
+ );
88
+ constructor(
89
+ messageOrTrailId: string,
90
+ optionsOrRequested?:
91
+ | { cause?: Error; context?: Record<string, unknown> }
92
+ | number
93
+ | string,
94
+ supported?: readonly number[],
95
+ reason?: string | undefined
96
+ ) {
97
+ if (supported === undefined) {
98
+ super(
99
+ messageOrTrailId,
100
+ optionsOrRequested as
101
+ | { cause?: Error; context?: Record<string, unknown> }
102
+ | undefined
103
+ );
104
+ this.name = 'VersionNotSupportedError';
105
+ return;
106
+ }
107
+
108
+ const requested = optionsOrRequested as number | string;
109
+ const supportedLabel =
110
+ supported.length === 0 ? 'none' : supported.join(', ');
111
+ super(
112
+ `Trail "${messageOrTrailId}" version ${String(requested)} is not supported (supported: ${supportedLabel})`,
113
+ {
114
+ context: {
115
+ ...(reason === undefined ? {} : { reason }),
116
+ requested,
117
+ supported,
118
+ trailId: messageOrTrailId,
119
+ },
120
+ }
121
+ );
122
+ this.name = 'VersionNotSupportedError';
123
+ this.reason = reason;
124
+ this.requested = requested;
125
+ this.supported = supported;
126
+ this.trailId = messageOrTrailId;
127
+ }
128
+ }
129
+
68
130
  export class AlreadyExistsError extends TrailsError {
69
131
  readonly category = 'conflict' as const;
70
132
  readonly retryable = false as const;
@@ -80,6 +142,16 @@ export class PermissionError extends TrailsError {
80
142
  readonly retryable = false as const;
81
143
  }
82
144
 
145
+ export class PermitError extends PermissionError {
146
+ constructor(
147
+ message: string,
148
+ options?: { cause?: Error; context?: Record<string, unknown> }
149
+ ) {
150
+ super(message, options);
151
+ this.name = 'PermitError';
152
+ }
153
+ }
154
+
83
155
  export class TimeoutError extends TrailsError {
84
156
  readonly category = 'timeout' as const;
85
157
  readonly retryable = true as const;
@@ -108,11 +180,34 @@ export class NetworkError extends TrailsError {
108
180
  readonly retryable = true as const;
109
181
  }
110
182
 
183
+ /**
184
+ * @example
185
+ * ```ts
186
+ * return Result.err(new WorkspaceShiftError('workspace changed during check'));
187
+ * ```
188
+ *
189
+ * Raised when the observed workspace substrate moves during one run.
190
+ *
191
+ * A shift voids the run's verdict, including passes. Callers can retry on
192
+ * stable ground without changing their request.
193
+ */
194
+ export class WorkspaceShiftError extends TrailsError {
195
+ readonly category = 'shift' as const;
196
+ readonly retryable = true as const;
197
+ }
198
+
111
199
  export class InternalError extends TrailsError {
200
+ readonly category: ErrorCategory = 'internal';
201
+ readonly retryable = false as const;
202
+ }
203
+
204
+ export class DerivationError extends TrailsError {
112
205
  readonly category = 'internal' as const;
113
206
  readonly retryable = false as const;
114
207
  }
115
208
 
209
+ export class RecoverableCompletionError extends InternalError {}
210
+
116
211
  export class AuthError extends TrailsError {
117
212
  readonly category = 'auth' as const;
118
213
  readonly retryable = false as const;
@@ -123,48 +218,239 @@ export class CancelledError extends TrailsError {
123
218
  readonly retryable = false as const;
124
219
  }
125
220
 
221
+ /**
222
+ * Returned when a detour exhausts all recovery attempts.
223
+ *
224
+ * Inherits the wrapped error's category for surface mapping (e.g. a
225
+ * `RetryExhaustedError<ConflictError>` maps to HTTP 409), but always
226
+ * sets `retryable = false` to prevent amplification across `ctx.compose()`
227
+ * boundaries or stacked layers.
228
+ */
229
+ export class RetryExhaustedError<
230
+ TErr extends TrailsError = TrailsError,
231
+ > extends InternalError {
232
+ readonly category: ErrorCategory;
233
+ readonly cause: TErr;
234
+
235
+ /** Number of recovery attempts made before exhaustion. */
236
+ readonly attempts: number;
237
+
238
+ /** Name of the detour whose recovery was exhausted. */
239
+ readonly detour: string;
240
+
241
+ constructor(
242
+ wrapped: TErr,
243
+ metadata: { readonly attempts: number; readonly detour: string }
244
+ ) {
245
+ super(
246
+ `Recovery exhausted after ${metadata.attempts} attempts: ${wrapped.message}`,
247
+ { cause: wrapped }
248
+ );
249
+ this.cause = wrapped;
250
+ this.attempts = metadata.attempts;
251
+ this.detour = metadata.detour;
252
+ // Dynamic — inherited from wrapped error at construction time.
253
+ this.category = wrapped.category;
254
+ }
255
+ }
256
+
257
+ // ---------------------------------------------------------------------------
258
+ // Class registry
259
+ // ---------------------------------------------------------------------------
260
+
261
+ export type ErrorClassConstructor = new (...args: never[]) => TrailsError;
262
+
263
+ export interface FixedErrorClassRegistryEntry {
264
+ readonly category: ErrorCategory;
265
+ readonly ctor: ErrorClassConstructor;
266
+ readonly name: string;
267
+ readonly retryable: boolean;
268
+ }
269
+
270
+ export interface DynamicErrorClassRegistryEntry {
271
+ readonly category: 'dynamic';
272
+ readonly ctor: ErrorClassConstructor;
273
+ readonly inheritsCategoryFrom: 'wrapped-error';
274
+ readonly name: string;
275
+ readonly retryable: false;
276
+ }
277
+
278
+ export type ErrorClassRegistryEntry =
279
+ | DynamicErrorClassRegistryEntry
280
+ | FixedErrorClassRegistryEntry;
281
+
282
+ /**
283
+ * Authored registry of concrete TrailsError classes.
284
+ *
285
+ * JavaScript cannot enumerate subclasses at runtime, so rule and projection
286
+ * tooling should walk this owner-held list instead of hardcoding parallel
287
+ * class-name tables. `RetryExhaustedError` is marked dynamic because it
288
+ * inherits its runtime category from the wrapped error rather than always
289
+ * mapping as `internal`.
290
+ */
291
+ export const errorClasses = [
292
+ {
293
+ category: 'validation',
294
+ ctor: ValidationError,
295
+ name: 'ValidationError',
296
+ retryable: false,
297
+ },
298
+ {
299
+ category: 'validation',
300
+ ctor: AmbiguousError,
301
+ name: 'AmbiguousError',
302
+ retryable: false,
303
+ },
304
+ {
305
+ category: 'internal',
306
+ ctor: AssertionError,
307
+ name: 'AssertionError',
308
+ retryable: false,
309
+ },
310
+ {
311
+ category: 'not_found',
312
+ ctor: NotFoundError,
313
+ name: 'NotFoundError',
314
+ retryable: false,
315
+ },
316
+ {
317
+ category: 'not_found',
318
+ ctor: VersionNotSupportedError,
319
+ name: 'VersionNotSupportedError',
320
+ retryable: false,
321
+ },
322
+ {
323
+ category: 'conflict',
324
+ ctor: AlreadyExistsError,
325
+ name: 'AlreadyExistsError',
326
+ retryable: false,
327
+ },
328
+ {
329
+ category: 'conflict',
330
+ ctor: ConflictError,
331
+ name: 'ConflictError',
332
+ retryable: false,
333
+ },
334
+ {
335
+ category: 'permission',
336
+ ctor: PermissionError,
337
+ name: 'PermissionError',
338
+ retryable: false,
339
+ },
340
+ {
341
+ category: 'permission',
342
+ ctor: PermitError,
343
+ name: 'PermitError',
344
+ retryable: false,
345
+ },
346
+ {
347
+ category: 'timeout',
348
+ ctor: TimeoutError,
349
+ name: 'TimeoutError',
350
+ retryable: true,
351
+ },
352
+ {
353
+ category: 'rate_limit',
354
+ ctor: RateLimitError,
355
+ name: 'RateLimitError',
356
+ retryable: true,
357
+ },
358
+ {
359
+ category: 'network',
360
+ ctor: NetworkError,
361
+ name: 'NetworkError',
362
+ retryable: true,
363
+ },
364
+ {
365
+ category: 'shift',
366
+ ctor: WorkspaceShiftError,
367
+ name: 'WorkspaceShiftError',
368
+ retryable: true,
369
+ },
370
+ {
371
+ category: 'internal',
372
+ ctor: InternalError,
373
+ name: 'InternalError',
374
+ retryable: false,
375
+ },
376
+ {
377
+ category: 'internal',
378
+ ctor: DerivationError,
379
+ name: 'DerivationError',
380
+ retryable: false,
381
+ },
382
+ {
383
+ category: 'internal',
384
+ ctor: RecoverableCompletionError,
385
+ name: 'RecoverableCompletionError',
386
+ retryable: false,
387
+ },
388
+ { category: 'auth', ctor: AuthError, name: 'AuthError', retryable: false },
389
+ {
390
+ category: 'cancelled',
391
+ ctor: CancelledError,
392
+ name: 'CancelledError',
393
+ retryable: false,
394
+ },
395
+ {
396
+ category: 'dynamic',
397
+ ctor: RetryExhaustedError,
398
+ inheritsCategoryFrom: 'wrapped-error',
399
+ name: 'RetryExhaustedError',
400
+ retryable: false,
401
+ },
402
+ ] as const satisfies readonly ErrorClassRegistryEntry[];
403
+
126
404
  // ---------------------------------------------------------------------------
127
405
  // Taxonomy maps
128
406
  // ---------------------------------------------------------------------------
129
407
 
130
- export const exitCodeMap: Record<ErrorCategory, number> = {
131
- auth: 9,
132
- cancelled: 130,
133
- conflict: 3,
134
- internal: 8,
135
- network: 7,
136
- not_found: 2,
137
- permission: 4,
138
- rate_limit: 6,
139
- timeout: 5,
140
- validation: 1,
141
- } as const;
408
+ export interface ErrorCategoryCodes {
409
+ readonly exit: number;
410
+ readonly http: number;
411
+ readonly jsonRpc: number;
412
+ }
142
413
 
143
- export const statusCodeMap: Record<ErrorCategory, number> = {
144
- auth: 401,
145
- cancelled: 499,
146
- conflict: 409,
147
- internal: 500,
148
- network: 502,
149
- not_found: 404,
150
- permission: 403,
151
- rate_limit: 429,
152
- timeout: 504,
153
- validation: 400,
154
- } as const;
414
+ export const codesByCategory = {
415
+ auth: { exit: 9, http: 401, jsonRpc: -32_600 },
416
+ cancelled: { exit: 130, http: 499, jsonRpc: -32_603 },
417
+ conflict: { exit: 3, http: 409, jsonRpc: -32_603 },
418
+ internal: { exit: 8, http: 500, jsonRpc: -32_603 },
419
+ network: { exit: 7, http: 502, jsonRpc: -32_603 },
420
+ not_found: { exit: 2, http: 404, jsonRpc: -32_601 },
421
+ permission: { exit: 4, http: 403, jsonRpc: -32_600 },
422
+ rate_limit: { exit: 6, http: 429, jsonRpc: -32_603 },
423
+ shift: { exit: 10, http: 503, jsonRpc: -32_603 },
424
+ timeout: { exit: 5, http: 504, jsonRpc: -32_603 },
425
+ validation: { exit: 1, http: 400, jsonRpc: -32_602 },
426
+ } as const satisfies Record<ErrorCategory, ErrorCategoryCodes>;
155
427
 
156
- export const jsonRpcCodeMap: Record<ErrorCategory, number> = {
157
- auth: -32_600,
158
- cancelled: -32_603,
159
- conflict: -32_603,
160
- internal: -32_603,
161
- network: -32_603,
162
- not_found: -32_601,
163
- permission: -32_600,
164
- rate_limit: -32_603,
165
- timeout: -32_603,
166
- validation: -32_602,
167
- } as const;
428
+ const deriveCodeMap = <TCode extends keyof ErrorCategoryCodes>(
429
+ code: TCode
430
+ ): {
431
+ readonly [TCategory in ErrorCategory]: (typeof codesByCategory)[TCategory][TCode];
432
+ } => ({
433
+ auth: codesByCategory.auth[code],
434
+ cancelled: codesByCategory.cancelled[code],
435
+ conflict: codesByCategory.conflict[code],
436
+ internal: codesByCategory.internal[code],
437
+ network: codesByCategory.network[code],
438
+ not_found: codesByCategory.not_found[code],
439
+ permission: codesByCategory.permission[code],
440
+ rate_limit: codesByCategory.rate_limit[code],
441
+ shift: codesByCategory.shift[code],
442
+ timeout: codesByCategory.timeout[code],
443
+ validation: codesByCategory.validation[code],
444
+ });
445
+
446
+ /** @deprecated Prefer `codesByCategory[category].exit`. */
447
+ export const exitCodeMap = deriveCodeMap('exit');
448
+
449
+ /** @deprecated Prefer `codesByCategory[category].http`. */
450
+ export const statusCodeMap = deriveCodeMap('http');
451
+
452
+ /** @deprecated Prefer `codesByCategory[category].jsonRpc`. */
453
+ export const jsonRpcCodeMap = deriveCodeMap('jsonRpc');
168
454
 
169
455
  export const retryableMap: Record<ErrorCategory, boolean> = {
170
456
  auth: false,
@@ -175,6 +461,7 @@ export const retryableMap: Record<ErrorCategory, boolean> = {
175
461
  not_found: false,
176
462
  permission: false,
177
463
  rate_limit: true,
464
+ shift: true,
178
465
  timeout: true,
179
466
  validation: false,
180
467
  } as const;
@@ -190,7 +477,7 @@ export const isTrailsError = (error?: unknown): error is TrailsError =>
190
477
  /** Returns true if the error is retryable (TrailsError with retryable category). */
191
478
  export const isRetryable = (error: Error): boolean => {
192
479
  if (isTrailsError(error)) {
193
- return retryableMap[error.category];
480
+ return error.retryable;
194
481
  }
195
482
  return false;
196
483
  };