@ontrails/core 1.0.0-beta.3 → 1.0.0-beta.30

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 (245) hide show
  1. package/CHANGELOG.md +305 -8
  2. package/README.md +114 -33
  3. package/package.json +11 -1
  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/compose-batch.ts +69 -0
  11. package/src/compose-schema.ts +36 -0
  12. package/src/context.ts +44 -10
  13. package/src/contour.ts +346 -0
  14. package/src/derive.ts +241 -16
  15. package/src/detours.ts +8 -0
  16. package/src/draft.ts +342 -0
  17. package/src/error-projection.ts +51 -0
  18. package/src/errors.ts +335 -48
  19. package/src/execute.ts +1577 -0
  20. package/src/fire.ts +1169 -0
  21. package/src/index.ts +495 -34
  22. package/src/internal/fork-ctx.ts +69 -0
  23. package/src/layer-projection.ts +193 -0
  24. package/src/layer.ts +43 -6
  25. package/src/observe.ts +361 -0
  26. package/src/path-security.ts +1 -1
  27. package/src/permits.ts +12 -0
  28. package/src/redaction/patterns.ts +6 -3
  29. package/src/resilience.ts +11 -11
  30. package/src/resource-config.ts +792 -0
  31. package/src/resource.ts +194 -0
  32. package/src/result.ts +18 -0
  33. package/src/run.ts +76 -0
  34. package/src/schedule-runtime.ts +689 -0
  35. package/src/schedule.ts +326 -0
  36. package/src/serialization.ts +92 -55
  37. package/src/signal-diagnostics.ts +633 -0
  38. package/src/signal-ref.ts +87 -0
  39. package/src/signal.ts +104 -0
  40. package/src/store/accessor-protocol.ts +56 -0
  41. package/src/store/index.ts +4 -0
  42. package/src/structured-examples.ts +248 -0
  43. package/src/surface-derivation.ts +91 -0
  44. package/src/surface-filter.ts +176 -0
  45. package/src/surface-versioning.ts +42 -0
  46. package/src/topo.ts +769 -67
  47. package/src/tracing.ts +345 -0
  48. package/src/trail.ts +1168 -30
  49. package/src/trails/derive-trail.ts +842 -0
  50. package/src/trails/index.ts +9 -0
  51. package/src/trails/ingest.ts +139 -0
  52. package/src/trails-db.ts +206 -0
  53. package/src/transport-error-map.ts +174 -0
  54. package/src/type-utils.ts +87 -0
  55. package/src/types.ts +254 -12
  56. package/src/validate-established-topo.ts +69 -0
  57. package/src/validate-topo.ts +393 -45
  58. package/src/validation.ts +152 -4
  59. package/src/version-marker.ts +716 -0
  60. package/src/version-resolution.ts +308 -0
  61. package/src/version-runtime.ts +120 -0
  62. package/src/webhook.ts +278 -0
  63. package/src/workspace.ts +1 -1
  64. package/src/zod-wrappers.ts +72 -0
  65. package/.turbo/turbo-build.log +0 -1
  66. package/.turbo/turbo-lint.log +0 -3
  67. package/.turbo/turbo-typecheck.log +0 -1
  68. package/dist/adapters.d.ts +0 -39
  69. package/dist/adapters.d.ts.map +0 -1
  70. package/dist/adapters.js +0 -2
  71. package/dist/adapters.js.map +0 -1
  72. package/dist/blob-ref.d.ts +0 -20
  73. package/dist/blob-ref.d.ts.map +0 -1
  74. package/dist/blob-ref.js +0 -22
  75. package/dist/blob-ref.js.map +0 -1
  76. package/dist/branded.d.ts +0 -36
  77. package/dist/branded.d.ts.map +0 -1
  78. package/dist/branded.js +0 -89
  79. package/dist/branded.js.map +0 -1
  80. package/dist/collections.d.ts +0 -31
  81. package/dist/collections.d.ts.map +0 -1
  82. package/dist/collections.js +0 -60
  83. package/dist/collections.js.map +0 -1
  84. package/dist/context.d.ts +0 -10
  85. package/dist/context.d.ts.map +0 -1
  86. package/dist/context.js +0 -15
  87. package/dist/context.js.map +0 -1
  88. package/dist/derive.d.ts +0 -33
  89. package/dist/derive.d.ts.map +0 -1
  90. package/dist/derive.js +0 -125
  91. package/dist/derive.js.map +0 -1
  92. package/dist/errors.d.ts +0 -83
  93. package/dist/errors.d.ts.map +0 -1
  94. package/dist/errors.js +0 -142
  95. package/dist/errors.js.map +0 -1
  96. package/dist/event.d.ts +0 -45
  97. package/dist/event.d.ts.map +0 -1
  98. package/dist/event.js +0 -17
  99. package/dist/event.js.map +0 -1
  100. package/dist/fetch.d.ts +0 -15
  101. package/dist/fetch.d.ts.map +0 -1
  102. package/dist/fetch.js +0 -102
  103. package/dist/fetch.js.map +0 -1
  104. package/dist/guards.d.ts +0 -17
  105. package/dist/guards.d.ts.map +0 -1
  106. package/dist/guards.js +0 -25
  107. package/dist/guards.js.map +0 -1
  108. package/dist/health.d.ts +0 -18
  109. package/dist/health.d.ts.map +0 -1
  110. package/dist/health.js +0 -5
  111. package/dist/health.js.map +0 -1
  112. package/dist/hike.d.ts +0 -36
  113. package/dist/hike.d.ts.map +0 -1
  114. package/dist/hike.js +0 -20
  115. package/dist/hike.js.map +0 -1
  116. package/dist/index.d.ts +0 -36
  117. package/dist/index.d.ts.map +0 -1
  118. package/dist/index.js +0 -40
  119. package/dist/index.js.map +0 -1
  120. package/dist/job.d.ts +0 -24
  121. package/dist/job.d.ts.map +0 -1
  122. package/dist/job.js +0 -17
  123. package/dist/job.js.map +0 -1
  124. package/dist/layer.d.ts +0 -17
  125. package/dist/layer.d.ts.map +0 -1
  126. package/dist/layer.js +0 -21
  127. package/dist/layer.js.map +0 -1
  128. package/dist/path-security.d.ts +0 -28
  129. package/dist/path-security.d.ts.map +0 -1
  130. package/dist/path-security.js +0 -63
  131. package/dist/path-security.js.map +0 -1
  132. package/dist/patterns/bulk.d.ts +0 -15
  133. package/dist/patterns/bulk.d.ts.map +0 -1
  134. package/dist/patterns/bulk.js +0 -14
  135. package/dist/patterns/bulk.js.map +0 -1
  136. package/dist/patterns/change.d.ts +0 -10
  137. package/dist/patterns/change.d.ts.map +0 -1
  138. package/dist/patterns/change.js +0 -10
  139. package/dist/patterns/change.js.map +0 -1
  140. package/dist/patterns/date-range.d.ts +0 -10
  141. package/dist/patterns/date-range.d.ts.map +0 -1
  142. package/dist/patterns/date-range.js +0 -10
  143. package/dist/patterns/date-range.js.map +0 -1
  144. package/dist/patterns/index.d.ts +0 -9
  145. package/dist/patterns/index.d.ts.map +0 -1
  146. package/dist/patterns/index.js +0 -9
  147. package/dist/patterns/index.js.map +0 -1
  148. package/dist/patterns/pagination.d.ts +0 -18
  149. package/dist/patterns/pagination.d.ts.map +0 -1
  150. package/dist/patterns/pagination.js +0 -18
  151. package/dist/patterns/pagination.js.map +0 -1
  152. package/dist/patterns/progress.d.ts +0 -11
  153. package/dist/patterns/progress.d.ts.map +0 -1
  154. package/dist/patterns/progress.js +0 -11
  155. package/dist/patterns/progress.js.map +0 -1
  156. package/dist/patterns/sorting.d.ts +0 -13
  157. package/dist/patterns/sorting.d.ts.map +0 -1
  158. package/dist/patterns/sorting.js +0 -10
  159. package/dist/patterns/sorting.js.map +0 -1
  160. package/dist/patterns/status.d.ts +0 -15
  161. package/dist/patterns/status.d.ts.map +0 -1
  162. package/dist/patterns/status.js +0 -9
  163. package/dist/patterns/status.js.map +0 -1
  164. package/dist/patterns/timestamps.d.ts +0 -10
  165. package/dist/patterns/timestamps.d.ts.map +0 -1
  166. package/dist/patterns/timestamps.js +0 -10
  167. package/dist/patterns/timestamps.js.map +0 -1
  168. package/dist/redaction/index.d.ts +0 -4
  169. package/dist/redaction/index.d.ts.map +0 -1
  170. package/dist/redaction/index.js +0 -3
  171. package/dist/redaction/index.js.map +0 -1
  172. package/dist/redaction/patterns.d.ts +0 -9
  173. package/dist/redaction/patterns.d.ts.map +0 -1
  174. package/dist/redaction/patterns.js +0 -39
  175. package/dist/redaction/patterns.js.map +0 -1
  176. package/dist/redaction/redactor.d.ts +0 -27
  177. package/dist/redaction/redactor.d.ts.map +0 -1
  178. package/dist/redaction/redactor.js +0 -89
  179. package/dist/redaction/redactor.js.map +0 -1
  180. package/dist/resilience.d.ts +0 -34
  181. package/dist/resilience.d.ts.map +0 -1
  182. package/dist/resilience.js +0 -164
  183. package/dist/resilience.js.map +0 -1
  184. package/dist/result.d.ts +0 -57
  185. package/dist/result.d.ts.map +0 -1
  186. package/dist/result.js +0 -156
  187. package/dist/result.js.map +0 -1
  188. package/dist/serialization.d.ts +0 -27
  189. package/dist/serialization.d.ts.map +0 -1
  190. package/dist/serialization.js +0 -153
  191. package/dist/serialization.js.map +0 -1
  192. package/dist/topo.d.ts +0 -18
  193. package/dist/topo.d.ts.map +0 -1
  194. package/dist/topo.js +0 -80
  195. package/dist/topo.js.map +0 -1
  196. package/dist/trail.d.ts +0 -83
  197. package/dist/trail.d.ts.map +0 -1
  198. package/dist/trail.js +0 -16
  199. package/dist/trail.js.map +0 -1
  200. package/dist/types.d.ts +0 -46
  201. package/dist/types.d.ts.map +0 -1
  202. package/dist/types.js +0 -2
  203. package/dist/types.js.map +0 -1
  204. package/dist/validate-topo.d.ts +0 -24
  205. package/dist/validate-topo.d.ts.map +0 -1
  206. package/dist/validate-topo.js +0 -156
  207. package/dist/validate-topo.js.map +0 -1
  208. package/dist/validation.d.ts +0 -27
  209. package/dist/validation.d.ts.map +0 -1
  210. package/dist/validation.js +0 -134
  211. package/dist/validation.js.map +0 -1
  212. package/dist/workspace.d.ts +0 -25
  213. package/dist/workspace.d.ts.map +0 -1
  214. package/dist/workspace.js +0 -57
  215. package/dist/workspace.js.map +0 -1
  216. package/src/__tests__/blob-ref.test.ts +0 -103
  217. package/src/__tests__/branded.test.ts +0 -148
  218. package/src/__tests__/collections.test.ts +0 -126
  219. package/src/__tests__/context.test.ts +0 -66
  220. package/src/__tests__/derive.test.ts +0 -203
  221. package/src/__tests__/errors.test.ts +0 -309
  222. package/src/__tests__/event.test.ts +0 -82
  223. package/src/__tests__/fetch.test.ts +0 -217
  224. package/src/__tests__/guards.test.ts +0 -102
  225. package/src/__tests__/hike.test.ts +0 -117
  226. package/src/__tests__/job.test.ts +0 -98
  227. package/src/__tests__/layer.test.ts +0 -224
  228. package/src/__tests__/path-security.test.ts +0 -114
  229. package/src/__tests__/patterns.test.ts +0 -273
  230. package/src/__tests__/redaction.test.ts +0 -244
  231. package/src/__tests__/resilience.test.ts +0 -246
  232. package/src/__tests__/result.test.ts +0 -155
  233. package/src/__tests__/serialization.test.ts +0 -401
  234. package/src/__tests__/topo.test.ts +0 -220
  235. package/src/__tests__/trail.test.ts +0 -179
  236. package/src/__tests__/validate-topo.test.ts +0 -280
  237. package/src/__tests__/validation.test.ts +0 -283
  238. package/src/__tests__/workspace.test.ts +0 -183
  239. package/src/adapters.ts +0 -68
  240. package/src/event.ts +0 -77
  241. package/src/health.ts +0 -23
  242. package/src/hike.ts +0 -77
  243. package/src/job.ts +0 -20
  244. package/tsconfig.json +0 -9
  245. package/tsconfig.tsbuildinfo +0 -1
package/src/draft.ts ADDED
@@ -0,0 +1,342 @@
1
+ import type { AnyContour } from './contour.js';
2
+ import { getContourReferences } from './contour.js';
3
+ import { ValidationError } from './errors.js';
4
+ import type { AnySignal } from './signal.js';
5
+ import type { AnyResource } from './resource.js';
6
+ import { Result } from './result.js';
7
+ import type { Topo } from './topo.js';
8
+ import type { AnyTrail } from './trail.js';
9
+
10
+ export const DRAFT_ID_PREFIX = '_draft.';
11
+
12
+ export type DraftDependencyKind =
13
+ | 'compose'
14
+ | 'contour'
15
+ | 'resource'
16
+ | 'replaced-by'
17
+ | 'schema-reference'
18
+ | 'signal-fire'
19
+ | 'signal-on'
20
+ | 'signal-from';
21
+
22
+ export interface DraftDependency {
23
+ readonly fromId: string;
24
+ readonly kind: DraftDependencyKind;
25
+ readonly toId: string;
26
+ }
27
+
28
+ export interface DraftFinding {
29
+ readonly id: string;
30
+ readonly kind: 'contour' | 'resource' | 'signal' | 'trail' | 'unknown';
31
+ readonly message: string;
32
+ readonly rule: 'draft-contamination' | 'draft-id';
33
+ readonly via?: DraftDependencyKind | undefined;
34
+ readonly dependsOn?: string | undefined;
35
+ }
36
+
37
+ export interface DraftReport {
38
+ readonly contaminatedIds: ReadonlySet<string>;
39
+ readonly declaredDraftIds: ReadonlySet<string>;
40
+ readonly dependencies: readonly DraftDependency[];
41
+ readonly findings: readonly DraftFinding[];
42
+ }
43
+
44
+ interface DraftReason {
45
+ readonly dependsOn: string;
46
+ readonly via: DraftDependencyKind;
47
+ }
48
+
49
+ type TopoNode = AnyContour | AnyResource | AnySignal | AnyTrail;
50
+
51
+ const replacedByTarget = (value: TopoNode): string | undefined => {
52
+ const raw = value as unknown as { replacedBy?: unknown };
53
+ return typeof raw.replacedBy === 'string' ? raw.replacedBy : undefined;
54
+ };
55
+
56
+ export const isDraftId = (id: string): boolean =>
57
+ id.startsWith(DRAFT_ID_PREFIX);
58
+
59
+ const dependenciesFromIds = (
60
+ fromId: string,
61
+ toIds: readonly string[],
62
+ kind: DraftDependencyKind
63
+ ): DraftDependency[] =>
64
+ toIds.map((toId) => ({
65
+ fromId,
66
+ kind,
67
+ toId,
68
+ }));
69
+
70
+ const dependencyFromTarget = (
71
+ fromId: string,
72
+ toId: string | undefined,
73
+ kind: DraftDependencyKind
74
+ ): DraftDependency[] =>
75
+ toId === undefined
76
+ ? []
77
+ : [
78
+ {
79
+ fromId,
80
+ kind,
81
+ toId,
82
+ },
83
+ ];
84
+
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.composes, 'compose'),
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
+ ];
101
+
102
+ const contourDependencies = (contour: AnyContour): DraftDependency[] =>
103
+ dependenciesFromIds(
104
+ contour.name,
105
+ getContourReferences(contour).map((reference) => reference.contour),
106
+ 'schema-reference'
107
+ );
108
+
109
+ const signalDependencies = (signal: AnySignal): DraftDependency[] => [
110
+ ...dependenciesFromIds(signal.id, signal.from ?? [], 'signal-from'),
111
+ ...dependencyFromTarget(signal.id, replacedByTarget(signal), 'replaced-by'),
112
+ ];
113
+
114
+ const resourceDependencies = (resource: AnyResource): DraftDependency[] =>
115
+ dependencyFromTarget(resource.id, replacedByTarget(resource), 'replaced-by');
116
+
117
+ const nodeKind = (
118
+ id: string,
119
+ contours: ReadonlyMap<string, AnyContour>,
120
+ trails: ReadonlyMap<string, AnyTrail>,
121
+ signals: ReadonlyMap<string, AnySignal>,
122
+ resources: ReadonlyMap<string, AnyResource>
123
+ ): DraftFinding['kind'] => {
124
+ if (contours.has(id)) {
125
+ return 'contour';
126
+ }
127
+ if (trails.has(id)) {
128
+ return 'trail';
129
+ }
130
+ if (signals.has(id)) {
131
+ return 'signal';
132
+ }
133
+ if (resources.has(id)) {
134
+ return 'resource';
135
+ }
136
+ return 'unknown';
137
+ };
138
+
139
+ const displayKind = (kind: DraftFinding['kind']): string =>
140
+ kind === 'unknown' ? 'Node' : kind[0]?.toUpperCase() + kind.slice(1);
141
+
142
+ const draftIdsFromKeys = (keys: Iterable<string>): string[] =>
143
+ [...keys].filter(isDraftId);
144
+
145
+ const collectDeclaredDraftIds = (topo: Topo): ReadonlySet<string> =>
146
+ new Set([
147
+ ...draftIdsFromKeys(topo.contours.keys()),
148
+ ...draftIdsFromKeys(topo.trails.keys()),
149
+ ...draftIdsFromKeys(topo.signals.keys()),
150
+ ...draftIdsFromKeys(topo.resources.keys()),
151
+ ]);
152
+
153
+ const findingForDraftId = (
154
+ id: string,
155
+ kind: DraftFinding['kind']
156
+ ): DraftFinding => ({
157
+ id,
158
+ kind,
159
+ message: `${displayKind(id ? kind : 'unknown')} "${id}" is draft and cannot appear in the established graph.`,
160
+ rule: 'draft-id',
161
+ });
162
+
163
+ const findingForContamination = (
164
+ id: string,
165
+ kind: DraftFinding['kind'],
166
+ reason: DraftReason
167
+ ): DraftFinding => {
168
+ const dependencyLabel = isDraftId(reason.dependsOn)
169
+ ? `draft "${reason.dependsOn}"`
170
+ : `draft-contaminated "${reason.dependsOn}"`;
171
+
172
+ return {
173
+ dependsOn: reason.dependsOn,
174
+ id,
175
+ kind,
176
+ message:
177
+ `Established ${kind} "${id}" depends on ${dependencyLabel} ` +
178
+ `via ${reason.via} and cannot appear in the established graph.`,
179
+ rule: 'draft-contamination',
180
+ via: reason.via,
181
+ };
182
+ };
183
+
184
+ const contaminationReason = (
185
+ dependency: DraftDependency,
186
+ contaminatedIds: ReadonlySet<string>
187
+ ): DraftReason | undefined => {
188
+ if (contaminatedIds.has(dependency.fromId)) {
189
+ return undefined;
190
+ }
191
+
192
+ if (!isDraftId(dependency.toId) && !contaminatedIds.has(dependency.toId)) {
193
+ return undefined;
194
+ }
195
+
196
+ return {
197
+ dependsOn: dependency.toId,
198
+ via: dependency.kind,
199
+ };
200
+ };
201
+
202
+ const markContaminatedDependency = (
203
+ dependency: DraftDependency,
204
+ contaminatedIds: Set<string>,
205
+ reasons: Map<string, DraftReason>
206
+ ): boolean => {
207
+ const reason = contaminationReason(dependency, contaminatedIds);
208
+
209
+ if (reason === undefined) {
210
+ return false;
211
+ }
212
+
213
+ contaminatedIds.add(dependency.fromId);
214
+ reasons.set(dependency.fromId, reason);
215
+ return true;
216
+ };
217
+
218
+ const propagateContaminatedIds = (
219
+ declaredDraftIds: ReadonlySet<string>,
220
+ dependencies: readonly DraftDependency[]
221
+ ): { contaminatedIds: Set<string>; reasons: Map<string, DraftReason> } => {
222
+ const contaminatedIds = new Set<string>(declaredDraftIds);
223
+ const reasons = new Map<string, DraftReason>();
224
+
225
+ const visit = (): void => {
226
+ if (
227
+ dependencies.some((dependency) =>
228
+ markContaminatedDependency(dependency, contaminatedIds, reasons)
229
+ )
230
+ ) {
231
+ visit();
232
+ }
233
+ };
234
+
235
+ visit();
236
+ return { contaminatedIds, reasons };
237
+ };
238
+
239
+ const contaminationFindingForId = (
240
+ id: string,
241
+ declaredDraftIds: ReadonlySet<string>,
242
+ reasons: ReadonlyMap<string, DraftReason>,
243
+ contours: ReadonlyMap<string, AnyContour>,
244
+ trails: ReadonlyMap<string, AnyTrail>,
245
+ signals: ReadonlyMap<string, AnySignal>,
246
+ resources: ReadonlyMap<string, AnyResource>
247
+ ): DraftFinding | undefined => {
248
+ if (declaredDraftIds.has(id)) {
249
+ return undefined;
250
+ }
251
+
252
+ const reason = reasons.get(id);
253
+ if (reason === undefined) {
254
+ return undefined;
255
+ }
256
+
257
+ return findingForContamination(
258
+ id,
259
+ nodeKind(id, contours, trails, signals, resources),
260
+ reason
261
+ );
262
+ };
263
+
264
+ const collectFindings = (
265
+ declaredDraftIds: ReadonlySet<string>,
266
+ contaminatedIds: ReadonlySet<string>,
267
+ reasons: ReadonlyMap<string, DraftReason>,
268
+ contours: ReadonlyMap<string, AnyContour>,
269
+ trails: ReadonlyMap<string, AnyTrail>,
270
+ signals: ReadonlyMap<string, AnySignal>,
271
+ resources: ReadonlyMap<string, AnyResource>
272
+ ): DraftFinding[] => [
273
+ ...[...declaredDraftIds]
274
+ .toSorted()
275
+ .map((id) =>
276
+ findingForDraftId(id, nodeKind(id, contours, trails, signals, resources))
277
+ ),
278
+ ...[...contaminatedIds].toSorted().flatMap((id) => {
279
+ const finding = contaminationFindingForId(
280
+ id,
281
+ declaredDraftIds,
282
+ reasons,
283
+ contours,
284
+ trails,
285
+ signals,
286
+ resources
287
+ );
288
+
289
+ return finding === undefined ? [] : [finding];
290
+ }),
291
+ ];
292
+
293
+ const collectDependencies = (topo: Topo): DraftDependency[] => [
294
+ ...[...topo.contours.values()].flatMap(contourDependencies),
295
+ ...[...topo.trails.values()].flatMap(trailDependencies),
296
+ ...[...topo.signals.values()].flatMap(signalDependencies),
297
+ ...[...topo.resources.values()].flatMap(resourceDependencies),
298
+ ];
299
+
300
+ export const deriveDraftReport = (topo: Topo): DraftReport => {
301
+ const declaredDraftIds = collectDeclaredDraftIds(topo);
302
+ const dependencies = collectDependencies(topo);
303
+ const { contaminatedIds, reasons } = propagateContaminatedIds(
304
+ declaredDraftIds,
305
+ dependencies
306
+ );
307
+ const findings = collectFindings(
308
+ declaredDraftIds,
309
+ contaminatedIds,
310
+ reasons,
311
+ topo.contours,
312
+ topo.trails,
313
+ topo.signals,
314
+ topo.resources
315
+ );
316
+
317
+ return {
318
+ contaminatedIds,
319
+ declaredDraftIds,
320
+ dependencies,
321
+ findings,
322
+ };
323
+ };
324
+
325
+ export const validateDraftFreeTopo = (
326
+ topo: Topo
327
+ ): Result<void, ValidationError> => {
328
+ const analysis = deriveDraftReport(topo);
329
+
330
+ if (analysis.findings.length === 0) {
331
+ return Result.ok();
332
+ }
333
+
334
+ return Result.err(
335
+ new ValidationError(
336
+ `Established topo validation failed with ${analysis.findings.length} draft issue(s)`,
337
+ {
338
+ context: { issues: analysis.findings },
339
+ }
340
+ )
341
+ );
342
+ };
@@ -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
+ };