@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
package/src/draft.ts CHANGED
@@ -1,6 +1,8 @@
1
+ import type { AnyContour } from './contour.js';
2
+ import { getContourReferences } from './contour.js';
1
3
  import { ValidationError } from './errors.js';
2
- import type { AnySignal } from './event.js';
3
- import type { AnyProvision } from './provision.js';
4
+ import type { AnySignal } from './signal.js';
5
+ import type { AnyResource } from './resource.js';
4
6
  import { Result } from './result.js';
5
7
  import type { Topo } from './topo.js';
6
8
  import type { AnyTrail } from './trail.js';
@@ -9,9 +11,12 @@ export const DRAFT_ID_PREFIX = '_draft.';
9
11
 
10
12
  export type DraftDependencyKind =
11
13
  | 'cross'
12
- | 'detour'
13
- | 'provision'
14
+ | 'contour'
15
+ | 'resource'
14
16
  | 'replaced-by'
17
+ | 'schema-reference'
18
+ | 'signal-fire'
19
+ | 'signal-on'
15
20
  | 'signal-from';
16
21
 
17
22
  export interface DraftDependency {
@@ -22,14 +27,14 @@ export interface DraftDependency {
22
27
 
23
28
  export interface DraftFinding {
24
29
  readonly id: string;
25
- readonly kind: 'provision' | 'signal' | 'trail' | 'unknown';
30
+ readonly kind: 'contour' | 'resource' | 'signal' | 'trail' | 'unknown';
26
31
  readonly message: string;
27
32
  readonly rule: 'draft-contamination' | 'draft-id';
28
33
  readonly via?: DraftDependencyKind | undefined;
29
34
  readonly dependsOn?: string | undefined;
30
35
  }
31
36
 
32
- export interface DraftStateAnalysis {
37
+ export interface DraftReport {
33
38
  readonly contaminatedIds: ReadonlySet<string>;
34
39
  readonly declaredDraftIds: ReadonlySet<string>;
35
40
  readonly dependencies: readonly DraftDependency[];
@@ -41,7 +46,7 @@ interface DraftReason {
41
46
  readonly via: DraftDependencyKind;
42
47
  }
43
48
 
44
- type TopoNode = AnyProvision | AnySignal | AnyTrail;
49
+ type TopoNode = AnyContour | AnyResource | AnySignal | AnyTrail;
45
50
 
46
51
  const replacedByTarget = (value: TopoNode): string | undefined => {
47
52
  const raw = value as unknown as { replacedBy?: unknown };
@@ -77,59 +82,56 @@ const dependencyFromTarget = (
77
82
  },
78
83
  ];
79
84
 
80
- const dependenciesFromDetours = (
81
- fromId: string,
82
- detours: Record<string, string[]>
83
- ): DraftDependency[] =>
84
- Object.values(detours).flatMap((targets) =>
85
- dependenciesFromIds(fromId, targets, 'detour')
86
- );
87
-
88
- const trailDependencies = (trail: AnyTrail): DraftDependency[] => {
89
- const { detours } = trail as unknown as {
90
- detours?: Record<string, string[]>;
91
- };
85
+ const trailDependencies = (trail: AnyTrail): DraftDependency[] => [
86
+ ...dependenciesFromIds(
87
+ trail.id,
88
+ (trail.contours ?? []).map((contour) => contour.name),
89
+ 'contour'
90
+ ),
91
+ ...dependenciesFromIds(trail.id, trail.crosses, 'cross'),
92
+ ...dependenciesFromIds(
93
+ trail.id,
94
+ trail.resources.map(({ id }) => id),
95
+ 'resource'
96
+ ),
97
+ ...dependenciesFromIds(trail.id, trail.fires ?? [], 'signal-fire'),
98
+ ...dependenciesFromIds(trail.id, trail.on ?? [], 'signal-on'),
99
+ ...dependencyFromTarget(trail.id, replacedByTarget(trail), 'replaced-by'),
100
+ ];
92
101
 
93
- return [
94
- ...dependenciesFromIds(trail.id, trail.crosses, 'cross'),
95
- ...dependenciesFromIds(
96
- trail.id,
97
- trail.provisions.map(({ id }) => id),
98
- 'provision'
99
- ),
100
- ...(detours === undefined
101
- ? []
102
- : dependenciesFromDetours(trail.id, detours)),
103
- ...dependencyFromTarget(trail.id, replacedByTarget(trail), 'replaced-by'),
104
- ];
105
- };
102
+ const contourDependencies = (contour: AnyContour): DraftDependency[] =>
103
+ dependenciesFromIds(
104
+ contour.name,
105
+ getContourReferences(contour).map((reference) => reference.contour),
106
+ 'schema-reference'
107
+ );
106
108
 
107
109
  const signalDependencies = (signal: AnySignal): DraftDependency[] => [
108
110
  ...dependenciesFromIds(signal.id, signal.from ?? [], 'signal-from'),
109
111
  ...dependencyFromTarget(signal.id, replacedByTarget(signal), 'replaced-by'),
110
112
  ];
111
113
 
112
- const provisionDependencies = (provision: AnyProvision): DraftDependency[] =>
113
- dependencyFromTarget(
114
- provision.id,
115
- replacedByTarget(provision),
116
- 'replaced-by'
117
- );
114
+ const resourceDependencies = (resource: AnyResource): DraftDependency[] =>
115
+ dependencyFromTarget(resource.id, replacedByTarget(resource), 'replaced-by');
118
116
 
119
117
  const nodeKind = (
120
118
  id: string,
119
+ contours: ReadonlyMap<string, AnyContour>,
121
120
  trails: ReadonlyMap<string, AnyTrail>,
122
121
  signals: ReadonlyMap<string, AnySignal>,
123
- provisions: ReadonlyMap<string, AnyProvision>
122
+ resources: ReadonlyMap<string, AnyResource>
124
123
  ): DraftFinding['kind'] => {
124
+ if (contours.has(id)) {
125
+ return 'contour';
126
+ }
125
127
  if (trails.has(id)) {
126
128
  return 'trail';
127
129
  }
128
130
  if (signals.has(id)) {
129
131
  return 'signal';
130
132
  }
131
- if (provisions.has(id)) {
132
- return 'provision';
133
+ if (resources.has(id)) {
134
+ return 'resource';
133
135
  }
134
136
  return 'unknown';
135
137
  };
@@ -142,9 +144,10 @@ const draftIdsFromKeys = (keys: Iterable<string>): string[] =>
142
144
 
143
145
  const collectDeclaredDraftIds = (topo: Topo): ReadonlySet<string> =>
144
146
  new Set([
147
+ ...draftIdsFromKeys(topo.contours.keys()),
145
148
  ...draftIdsFromKeys(topo.trails.keys()),
146
149
  ...draftIdsFromKeys(topo.signals.keys()),
147
- ...draftIdsFromKeys(topo.provisions.keys()),
150
+ ...draftIdsFromKeys(topo.resources.keys()),
148
151
  ]);
149
152
 
150
153
  const findingForDraftId = (
@@ -237,9 +240,10 @@ const contaminationFindingForId = (
237
240
  id: string,
238
241
  declaredDraftIds: ReadonlySet<string>,
239
242
  reasons: ReadonlyMap<string, DraftReason>,
243
+ contours: ReadonlyMap<string, AnyContour>,
240
244
  trails: ReadonlyMap<string, AnyTrail>,
241
245
  signals: ReadonlyMap<string, AnySignal>,
242
- provisions: ReadonlyMap<string, AnyProvision>
246
+ resources: ReadonlyMap<string, AnyResource>
243
247
  ): DraftFinding | undefined => {
244
248
  if (declaredDraftIds.has(id)) {
245
249
  return undefined;
@@ -252,7 +256,7 @@ const contaminationFindingForId = (
252
256
 
253
257
  return findingForContamination(
254
258
  id,
255
- nodeKind(id, trails, signals, provisions),
259
+ nodeKind(id, contours, trails, signals, resources),
256
260
  reason
257
261
  );
258
262
  };
@@ -261,23 +265,25 @@ const collectFindings = (
261
265
  declaredDraftIds: ReadonlySet<string>,
262
266
  contaminatedIds: ReadonlySet<string>,
263
267
  reasons: ReadonlyMap<string, DraftReason>,
268
+ contours: ReadonlyMap<string, AnyContour>,
264
269
  trails: ReadonlyMap<string, AnyTrail>,
265
270
  signals: ReadonlyMap<string, AnySignal>,
266
- provisions: ReadonlyMap<string, AnyProvision>
271
+ resources: ReadonlyMap<string, AnyResource>
267
272
  ): DraftFinding[] => [
268
273
  ...[...declaredDraftIds]
269
274
  .toSorted()
270
275
  .map((id) =>
271
- findingForDraftId(id, nodeKind(id, trails, signals, provisions))
276
+ findingForDraftId(id, nodeKind(id, contours, trails, signals, resources))
272
277
  ),
273
278
  ...[...contaminatedIds].toSorted().flatMap((id) => {
274
279
  const finding = contaminationFindingForId(
275
280
  id,
276
281
  declaredDraftIds,
277
282
  reasons,
283
+ contours,
278
284
  trails,
279
285
  signals,
280
- provisions
286
+ resources
281
287
  );
282
288
 
283
289
  return finding === undefined ? [] : [finding];
@@ -285,12 +291,13 @@ const collectFindings = (
285
291
  ];
286
292
 
287
293
  const collectDependencies = (topo: Topo): DraftDependency[] => [
294
+ ...[...topo.contours.values()].flatMap(contourDependencies),
288
295
  ...[...topo.trails.values()].flatMap(trailDependencies),
289
296
  ...[...topo.signals.values()].flatMap(signalDependencies),
290
- ...[...topo.provisions.values()].flatMap(provisionDependencies),
297
+ ...[...topo.resources.values()].flatMap(resourceDependencies),
291
298
  ];
292
299
 
293
- export const analyzeDraftState = (topo: Topo): DraftStateAnalysis => {
300
+ export const deriveDraftReport = (topo: Topo): DraftReport => {
294
301
  const declaredDraftIds = collectDeclaredDraftIds(topo);
295
302
  const dependencies = collectDependencies(topo);
296
303
  const { contaminatedIds, reasons } = propagateContaminatedIds(
@@ -301,9 +308,10 @@ export const analyzeDraftState = (topo: Topo): DraftStateAnalysis => {
301
308
  declaredDraftIds,
302
309
  contaminatedIds,
303
310
  reasons,
311
+ topo.contours,
304
312
  topo.trails,
305
313
  topo.signals,
306
- topo.provisions
314
+ topo.resources
307
315
  );
308
316
 
309
317
  return {
@@ -314,10 +322,10 @@ export const analyzeDraftState = (topo: Topo): DraftStateAnalysis => {
314
322
  };
315
323
  };
316
324
 
317
- export const validateEstablishedTopo = (
325
+ export const validateDraftFreeTopo = (
318
326
  topo: Topo
319
327
  ): Result<void, ValidationError> => {
320
- const analysis = analyzeDraftState(topo);
328
+ const analysis = deriveDraftReport(topo);
321
329
 
322
330
  if (analysis.findings.length === 0) {
323
331
  return Result.ok();
@@ -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,20 @@
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
+ 'internal',
22
+ 'auth',
23
+ 'cancelled',
24
+ ] as const;
25
+
26
+ export type ErrorCategory = (typeof errorCategories)[number];
24
27
 
25
28
  // ---------------------------------------------------------------------------
26
29
  // Base class
@@ -80,6 +83,16 @@ export class PermissionError extends TrailsError {
80
83
  readonly retryable = false as const;
81
84
  }
82
85
 
86
+ export class PermitError extends PermissionError {
87
+ constructor(
88
+ message: string,
89
+ options?: { cause?: Error; context?: Record<string, unknown> }
90
+ ) {
91
+ super(message, options);
92
+ this.name = 'PermitError';
93
+ }
94
+ }
95
+
83
96
  export class TimeoutError extends TrailsError {
84
97
  readonly category = 'timeout' as const;
85
98
  readonly retryable = true as const;
@@ -109,10 +122,17 @@ export class NetworkError extends TrailsError {
109
122
  }
110
123
 
111
124
  export class InternalError extends TrailsError {
125
+ readonly category: ErrorCategory = 'internal';
126
+ readonly retryable = false as const;
127
+ }
128
+
129
+ export class DerivationError extends TrailsError {
112
130
  readonly category = 'internal' as const;
113
131
  readonly retryable = false as const;
114
132
  }
115
133
 
134
+ export class RecoverableCompletionError extends InternalError {}
135
+
116
136
  export class AuthError extends TrailsError {
117
137
  readonly category = 'auth' as const;
118
138
  readonly retryable = false as const;
@@ -123,48 +143,225 @@ export class CancelledError extends TrailsError {
123
143
  readonly retryable = false as const;
124
144
  }
125
145
 
146
+ /**
147
+ * Returned when a detour exhausts all recovery attempts.
148
+ *
149
+ * Inherits the wrapped error's category for surface mapping (e.g. a
150
+ * `RetryExhaustedError<ConflictError>` maps to HTTP 409), but always
151
+ * sets `retryable = false` to prevent amplification across `ctx.cross()`
152
+ * boundaries or stacked layers.
153
+ */
154
+ export class RetryExhaustedError<
155
+ TErr extends TrailsError = TrailsError,
156
+ > extends InternalError {
157
+ readonly category: ErrorCategory;
158
+ readonly cause: TErr;
159
+
160
+ /** Number of recovery attempts made before exhaustion. */
161
+ readonly attempts: number;
162
+
163
+ /** Name of the detour whose recovery was exhausted. */
164
+ readonly detour: string;
165
+
166
+ constructor(
167
+ wrapped: TErr,
168
+ metadata: { readonly attempts: number; readonly detour: string }
169
+ ) {
170
+ super(
171
+ `Recovery exhausted after ${metadata.attempts} attempts: ${wrapped.message}`,
172
+ { cause: wrapped }
173
+ );
174
+ this.cause = wrapped;
175
+ this.attempts = metadata.attempts;
176
+ this.detour = metadata.detour;
177
+ // Dynamic — inherited from wrapped error at construction time.
178
+ this.category = wrapped.category;
179
+ }
180
+ }
181
+
182
+ // ---------------------------------------------------------------------------
183
+ // Class registry
184
+ // ---------------------------------------------------------------------------
185
+
186
+ export type ErrorClassConstructor = new (...args: never[]) => TrailsError;
187
+
188
+ export interface FixedErrorClassRegistryEntry {
189
+ readonly category: ErrorCategory;
190
+ readonly ctor: ErrorClassConstructor;
191
+ readonly name: string;
192
+ readonly retryable: boolean;
193
+ }
194
+
195
+ export interface DynamicErrorClassRegistryEntry {
196
+ readonly category: 'dynamic';
197
+ readonly ctor: ErrorClassConstructor;
198
+ readonly inheritsCategoryFrom: 'wrapped-error';
199
+ readonly name: string;
200
+ readonly retryable: false;
201
+ }
202
+
203
+ export type ErrorClassRegistryEntry =
204
+ | DynamicErrorClassRegistryEntry
205
+ | FixedErrorClassRegistryEntry;
206
+
207
+ /**
208
+ * Authored registry of concrete TrailsError classes.
209
+ *
210
+ * JavaScript cannot enumerate subclasses at runtime, so rule and projection
211
+ * tooling should walk this owner-held list instead of hardcoding parallel
212
+ * class-name tables. `RetryExhaustedError` is marked dynamic because it
213
+ * inherits its runtime category from the wrapped error rather than always
214
+ * mapping as `internal`.
215
+ */
216
+ export const errorClasses = [
217
+ {
218
+ category: 'validation',
219
+ ctor: ValidationError,
220
+ name: 'ValidationError',
221
+ retryable: false,
222
+ },
223
+ {
224
+ category: 'validation',
225
+ ctor: AmbiguousError,
226
+ name: 'AmbiguousError',
227
+ retryable: false,
228
+ },
229
+ {
230
+ category: 'internal',
231
+ ctor: AssertionError,
232
+ name: 'AssertionError',
233
+ retryable: false,
234
+ },
235
+ {
236
+ category: 'not_found',
237
+ ctor: NotFoundError,
238
+ name: 'NotFoundError',
239
+ retryable: false,
240
+ },
241
+ {
242
+ category: 'conflict',
243
+ ctor: AlreadyExistsError,
244
+ name: 'AlreadyExistsError',
245
+ retryable: false,
246
+ },
247
+ {
248
+ category: 'conflict',
249
+ ctor: ConflictError,
250
+ name: 'ConflictError',
251
+ retryable: false,
252
+ },
253
+ {
254
+ category: 'permission',
255
+ ctor: PermissionError,
256
+ name: 'PermissionError',
257
+ retryable: false,
258
+ },
259
+ {
260
+ category: 'permission',
261
+ ctor: PermitError,
262
+ name: 'PermitError',
263
+ retryable: false,
264
+ },
265
+ {
266
+ category: 'timeout',
267
+ ctor: TimeoutError,
268
+ name: 'TimeoutError',
269
+ retryable: true,
270
+ },
271
+ {
272
+ category: 'rate_limit',
273
+ ctor: RateLimitError,
274
+ name: 'RateLimitError',
275
+ retryable: true,
276
+ },
277
+ {
278
+ category: 'network',
279
+ ctor: NetworkError,
280
+ name: 'NetworkError',
281
+ retryable: true,
282
+ },
283
+ {
284
+ category: 'internal',
285
+ ctor: InternalError,
286
+ name: 'InternalError',
287
+ retryable: false,
288
+ },
289
+ {
290
+ category: 'internal',
291
+ ctor: DerivationError,
292
+ name: 'DerivationError',
293
+ retryable: false,
294
+ },
295
+ {
296
+ category: 'internal',
297
+ ctor: RecoverableCompletionError,
298
+ name: 'RecoverableCompletionError',
299
+ retryable: false,
300
+ },
301
+ { category: 'auth', ctor: AuthError, name: 'AuthError', retryable: false },
302
+ {
303
+ category: 'cancelled',
304
+ ctor: CancelledError,
305
+ name: 'CancelledError',
306
+ retryable: false,
307
+ },
308
+ {
309
+ category: 'dynamic',
310
+ ctor: RetryExhaustedError,
311
+ inheritsCategoryFrom: 'wrapped-error',
312
+ name: 'RetryExhaustedError',
313
+ retryable: false,
314
+ },
315
+ ] as const satisfies readonly ErrorClassRegistryEntry[];
316
+
126
317
  // ---------------------------------------------------------------------------
127
318
  // Taxonomy maps
128
319
  // ---------------------------------------------------------------------------
129
320
 
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;
321
+ export interface ErrorCategoryCodes {
322
+ readonly exit: number;
323
+ readonly http: number;
324
+ readonly jsonRpc: number;
325
+ }
142
326
 
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;
327
+ export const codesByCategory = {
328
+ auth: { exit: 9, http: 401, jsonRpc: -32_600 },
329
+ cancelled: { exit: 130, http: 499, jsonRpc: -32_603 },
330
+ conflict: { exit: 3, http: 409, jsonRpc: -32_603 },
331
+ internal: { exit: 8, http: 500, jsonRpc: -32_603 },
332
+ network: { exit: 7, http: 502, jsonRpc: -32_603 },
333
+ not_found: { exit: 2, http: 404, jsonRpc: -32_601 },
334
+ permission: { exit: 4, http: 403, jsonRpc: -32_600 },
335
+ rate_limit: { exit: 6, http: 429, jsonRpc: -32_603 },
336
+ timeout: { exit: 5, http: 504, jsonRpc: -32_603 },
337
+ validation: { exit: 1, http: 400, jsonRpc: -32_602 },
338
+ } as const satisfies Record<ErrorCategory, ErrorCategoryCodes>;
155
339
 
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;
340
+ const deriveCodeMap = <TCode extends keyof ErrorCategoryCodes>(
341
+ code: TCode
342
+ ): {
343
+ readonly [TCategory in ErrorCategory]: (typeof codesByCategory)[TCategory][TCode];
344
+ } => ({
345
+ auth: codesByCategory.auth[code],
346
+ cancelled: codesByCategory.cancelled[code],
347
+ conflict: codesByCategory.conflict[code],
348
+ internal: codesByCategory.internal[code],
349
+ network: codesByCategory.network[code],
350
+ not_found: codesByCategory.not_found[code],
351
+ permission: codesByCategory.permission[code],
352
+ rate_limit: codesByCategory.rate_limit[code],
353
+ timeout: codesByCategory.timeout[code],
354
+ validation: codesByCategory.validation[code],
355
+ });
356
+
357
+ /** @deprecated Prefer `codesByCategory[category].exit`. */
358
+ export const exitCodeMap = deriveCodeMap('exit');
359
+
360
+ /** @deprecated Prefer `codesByCategory[category].http`. */
361
+ export const statusCodeMap = deriveCodeMap('http');
362
+
363
+ /** @deprecated Prefer `codesByCategory[category].jsonRpc`. */
364
+ export const jsonRpcCodeMap = deriveCodeMap('jsonRpc');
168
365
 
169
366
  export const retryableMap: Record<ErrorCategory, boolean> = {
170
367
  auth: false,
@@ -190,7 +387,7 @@ export const isTrailsError = (error?: unknown): error is TrailsError =>
190
387
  /** Returns true if the error is retryable (TrailsError with retryable category). */
191
388
  export const isRetryable = (error: Error): boolean => {
192
389
  if (isTrailsError(error)) {
193
- return retryableMap[error.category];
390
+ return error.retryable;
194
391
  }
195
392
  return false;
196
393
  };