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

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