@ontrails/topography 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.
- package/CHANGELOG.md +543 -0
- package/README.md +196 -0
- package/package.json +33 -0
- package/src/activation-report.ts +385 -0
- package/src/backend-support.ts +11 -0
- package/src/derive.ts +690 -0
- package/src/diff.ts +1088 -0
- package/src/forces.ts +125 -0
- package/src/hash.ts +55 -0
- package/src/index.ts +272 -0
- package/src/internal/topo-snapshots.ts +682 -0
- package/src/internal/topo-store-read.ts +1102 -0
- package/src/internal/topo-store.ts +1651 -0
- package/src/io.ts +280 -0
- package/src/library-projection.ts +133 -0
- package/src/overlays.ts +155 -0
- package/src/permit.ts +19 -0
- package/src/source-fingerprint.ts +103 -0
- package/src/surface-bindings.ts +101 -0
- package/src/topo-store.ts +716 -0
- package/src/types.ts +749 -0
- package/src/versioning.ts +280 -0
- package/src/wayfind/error-facts.ts +363 -0
- package/src/wayfind/filters.ts +430 -0
- package/src/wayfind/loader.ts +443 -0
- package/src/wayfind/navigation.ts +265 -0
- package/src/wayfind/provenance.ts +152 -0
- package/src/wayfind/queries.ts +1656 -0
- package/src/wayfind/relations.ts +344 -0
- package/src/workspace-topos.ts +483 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DETOUR_MAX_ATTEMPTS_CAP,
|
|
3
|
+
TRAIL_VERSION_MARKER_MIN_PREFIX_LENGTH,
|
|
4
|
+
deriveStructuredTrailExamples,
|
|
5
|
+
ValidationError,
|
|
6
|
+
deriveCurrentTrailVersionMarker,
|
|
7
|
+
deriveShortestUnambiguousTrailVersionMarkerPrefix,
|
|
8
|
+
deriveSupportedTrailVersions,
|
|
9
|
+
deriveTrailVersionEntryMarker,
|
|
10
|
+
deriveTrailVersionMarkers,
|
|
11
|
+
getTrailVersionEntryKind,
|
|
12
|
+
resolveTrailVersionMarkerPrefix,
|
|
13
|
+
} from '@ontrails/core';
|
|
14
|
+
import type {
|
|
15
|
+
AnyTrail,
|
|
16
|
+
TrailVersionEntry,
|
|
17
|
+
TrailVersionMarkerBinding,
|
|
18
|
+
} from '@ontrails/core';
|
|
19
|
+
|
|
20
|
+
import type {
|
|
21
|
+
JsonSchema,
|
|
22
|
+
TopoGraphEntry,
|
|
23
|
+
TopoGraphVersionEntry,
|
|
24
|
+
} from './types.js';
|
|
25
|
+
|
|
26
|
+
type SchemaProjector = (schema: unknown) => JsonSchema;
|
|
27
|
+
|
|
28
|
+
const sortPlainRecord = <T extends Record<string, unknown>>(value: T): T => {
|
|
29
|
+
const sorted: Record<string, unknown> = {};
|
|
30
|
+
for (const key of Object.keys(value).toSorted()) {
|
|
31
|
+
sorted[key] = value[key];
|
|
32
|
+
}
|
|
33
|
+
return sorted as T;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const projectVersionDetours = (
|
|
37
|
+
entry: TrailVersionEntry
|
|
38
|
+
): TopoGraphVersionEntry['detours'] => {
|
|
39
|
+
const raw = entry as unknown as Record<string, unknown>;
|
|
40
|
+
const { detours } = raw;
|
|
41
|
+
if (!Array.isArray(detours) || detours.length === 0) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return detours.map((detour) => {
|
|
46
|
+
const candidate = detour as {
|
|
47
|
+
readonly maxAttempts?: number | undefined;
|
|
48
|
+
readonly on?: { readonly name?: string | undefined } | undefined;
|
|
49
|
+
};
|
|
50
|
+
return {
|
|
51
|
+
maxAttempts: Math.max(
|
|
52
|
+
1,
|
|
53
|
+
Math.min(candidate.maxAttempts ?? 1, DETOUR_MAX_ATTEMPTS_CAP)
|
|
54
|
+
),
|
|
55
|
+
on: candidate.on?.name ?? 'Error',
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const projectVersionRuntimeRefs = (
|
|
61
|
+
entry: TrailVersionEntry,
|
|
62
|
+
field: 'composes' | 'resources'
|
|
63
|
+
): readonly string[] | undefined => {
|
|
64
|
+
const raw = entry as unknown as Record<string, unknown>;
|
|
65
|
+
const values = raw[field];
|
|
66
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const refs: string[] = [];
|
|
71
|
+
for (const value of values) {
|
|
72
|
+
if (typeof value === 'string') {
|
|
73
|
+
refs.push(value);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (
|
|
77
|
+
typeof value === 'object' &&
|
|
78
|
+
value !== null &&
|
|
79
|
+
typeof (value as { readonly id?: unknown }).id === 'string'
|
|
80
|
+
) {
|
|
81
|
+
refs.push((value as { readonly id: string }).id);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return refs.toSorted();
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const projectTrailVersionEntry = (
|
|
89
|
+
entry: TrailVersionEntry,
|
|
90
|
+
projectSchema: SchemaProjector
|
|
91
|
+
): TopoGraphVersionEntry => {
|
|
92
|
+
const kind = getTrailVersionEntryKind(entry);
|
|
93
|
+
const examples = deriveStructuredTrailExamples(entry.examples, {
|
|
94
|
+
provenance: { source: 'trail.versions.examples' },
|
|
95
|
+
});
|
|
96
|
+
const projected: Record<string, unknown> = {
|
|
97
|
+
exampleCount: entry.examples?.length ?? 0,
|
|
98
|
+
input: projectSchema(entry.input),
|
|
99
|
+
kind,
|
|
100
|
+
marker: deriveTrailVersionEntryMarker(entry),
|
|
101
|
+
output: projectSchema(entry.output),
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
if (examples !== undefined) {
|
|
105
|
+
projected['examples'] = examples;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (entry.status !== undefined) {
|
|
109
|
+
projected['status'] = sortPlainRecord({ ...entry.status });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (kind === 'fork') {
|
|
113
|
+
const composes = projectVersionRuntimeRefs(entry, 'composes');
|
|
114
|
+
const resources = projectVersionRuntimeRefs(entry, 'resources');
|
|
115
|
+
const detours = projectVersionDetours(entry);
|
|
116
|
+
if (composes !== undefined) {
|
|
117
|
+
projected['composes'] = composes;
|
|
118
|
+
}
|
|
119
|
+
if (resources !== undefined) {
|
|
120
|
+
projected['resources'] = resources;
|
|
121
|
+
}
|
|
122
|
+
if (detours !== undefined) {
|
|
123
|
+
projected['detours'] = detours;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return sortPlainRecord(projected) as unknown as TopoGraphVersionEntry;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export interface TopoGraphVersionMarkerRecord {
|
|
131
|
+
readonly current: boolean;
|
|
132
|
+
readonly displayMarker?: string | undefined;
|
|
133
|
+
readonly marker?: string | undefined;
|
|
134
|
+
readonly version: number;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface TopoGraphVersionMarkerResolution extends TopoGraphVersionMarkerRecord {
|
|
138
|
+
readonly prefix: string;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export const collectTopoGraphVersionMarkers = (
|
|
142
|
+
entry: Pick<TopoGraphEntry, 'marker' | 'version' | 'versions'>
|
|
143
|
+
): readonly TrailVersionMarkerBinding[] => {
|
|
144
|
+
if (entry.version === undefined) {
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const markers: TrailVersionMarkerBinding[] = [];
|
|
149
|
+
if (entry.marker !== undefined) {
|
|
150
|
+
markers.push({ marker: entry.marker, version: entry.version });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
for (const [version, versionEntry] of Object.entries(entry.versions ?? {})) {
|
|
154
|
+
const { marker } = versionEntry as { readonly marker?: unknown };
|
|
155
|
+
if (typeof marker === 'string') {
|
|
156
|
+
markers.push({ marker, version: Number(version) });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return markers.toSorted((left, right) => left.version - right.version);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export const deriveTopoGraphVersionMarkerRecords = (
|
|
164
|
+
entry: Pick<TopoGraphEntry, 'marker' | 'version' | 'versions'>
|
|
165
|
+
): readonly TopoGraphVersionMarkerRecord[] => {
|
|
166
|
+
const markers = collectTopoGraphVersionMarkers(entry);
|
|
167
|
+
const markerValues = markers.map((candidate) => candidate.marker);
|
|
168
|
+
|
|
169
|
+
return markers.map((candidate) => ({
|
|
170
|
+
...candidate,
|
|
171
|
+
current: candidate.version === entry.version,
|
|
172
|
+
displayMarker: deriveShortestUnambiguousTrailVersionMarkerPrefix(
|
|
173
|
+
candidate.marker,
|
|
174
|
+
markerValues
|
|
175
|
+
),
|
|
176
|
+
}));
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const hasTopoGraphVersion = (
|
|
180
|
+
entry: Pick<TopoGraphEntry, 'version' | 'versions'>,
|
|
181
|
+
version: number
|
|
182
|
+
): boolean =>
|
|
183
|
+
entry.version === version || Object.hasOwn(entry.versions ?? {}, version);
|
|
184
|
+
|
|
185
|
+
export const resolveTopoGraphVersionReference = (
|
|
186
|
+
entry: Pick<TopoGraphEntry, 'marker' | 'version' | 'versions'>,
|
|
187
|
+
reference: number | string
|
|
188
|
+
): TopoGraphVersionMarkerResolution => {
|
|
189
|
+
const markers = collectTopoGraphVersionMarkers(entry);
|
|
190
|
+
const markerValues = markers.map((candidate) => candidate.marker);
|
|
191
|
+
|
|
192
|
+
if (typeof reference === 'number') {
|
|
193
|
+
const match = markers.find((candidate) => candidate.version === reference);
|
|
194
|
+
if (match !== undefined) {
|
|
195
|
+
return {
|
|
196
|
+
...match,
|
|
197
|
+
current: match.version === entry.version,
|
|
198
|
+
displayMarker: deriveShortestUnambiguousTrailVersionMarkerPrefix(
|
|
199
|
+
match.marker,
|
|
200
|
+
markerValues
|
|
201
|
+
),
|
|
202
|
+
prefix: String(reference),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
if (!hasTopoGraphVersion(entry, reference)) {
|
|
206
|
+
throw new ValidationError(
|
|
207
|
+
`Trail version ${reference} is not in the TopoGraph`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
current: reference === entry.version,
|
|
213
|
+
prefix: String(reference),
|
|
214
|
+
version: reference,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (/^\d+$/.test(reference)) {
|
|
219
|
+
const version = Number(reference);
|
|
220
|
+
if (hasTopoGraphVersion(entry, version)) {
|
|
221
|
+
return resolveTopoGraphVersionReference(entry, version);
|
|
222
|
+
}
|
|
223
|
+
if (reference.length < TRAIL_VERSION_MARKER_MIN_PREFIX_LENGTH) {
|
|
224
|
+
throw new ValidationError(
|
|
225
|
+
`Trail version ${version} is not in the TopoGraph`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const markerPrefix = reference.startsWith('@')
|
|
231
|
+
? reference.slice(1)
|
|
232
|
+
: reference;
|
|
233
|
+
const resolved = resolveTrailVersionMarkerPrefix(markers, markerPrefix);
|
|
234
|
+
return {
|
|
235
|
+
...resolved,
|
|
236
|
+
current: resolved.version === entry.version,
|
|
237
|
+
displayMarker: deriveShortestUnambiguousTrailVersionMarkerPrefix(
|
|
238
|
+
resolved.marker,
|
|
239
|
+
markerValues
|
|
240
|
+
),
|
|
241
|
+
};
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
export const projectTrailVersions = (
|
|
245
|
+
trail: AnyTrail,
|
|
246
|
+
projectSchema: SchemaProjector
|
|
247
|
+
):
|
|
248
|
+
| {
|
|
249
|
+
readonly marker: string;
|
|
250
|
+
readonly supports: readonly number[];
|
|
251
|
+
readonly version: number;
|
|
252
|
+
readonly versions?: Readonly<Record<string, TopoGraphVersionEntry>>;
|
|
253
|
+
}
|
|
254
|
+
| undefined => {
|
|
255
|
+
if (trail.version === undefined) {
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const projectedEntries: Record<string, TopoGraphVersionEntry> = {};
|
|
260
|
+
for (const [rawVersion, entry] of Object.entries(
|
|
261
|
+
trail.versions ?? {}
|
|
262
|
+
).toSorted(([left], [right]) => Number(left) - Number(right))) {
|
|
263
|
+
projectedEntries[rawVersion] = projectTrailVersionEntry(
|
|
264
|
+
entry,
|
|
265
|
+
projectSchema
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const currentMarker = deriveCurrentTrailVersionMarker(trail);
|
|
270
|
+
deriveTrailVersionMarkers(trail);
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
marker: currentMarker,
|
|
274
|
+
supports: deriveSupportedTrailVersions(trail),
|
|
275
|
+
version: trail.version,
|
|
276
|
+
...(Object.keys(projectedEntries).length > 0
|
|
277
|
+
? { versions: projectedEntries }
|
|
278
|
+
: {}),
|
|
279
|
+
};
|
|
280
|
+
};
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import {
|
|
2
|
+
errorClasses,
|
|
3
|
+
projectErrorClassSurface,
|
|
4
|
+
surfaceNames,
|
|
5
|
+
} from '@ontrails/core';
|
|
6
|
+
import type {
|
|
7
|
+
ErrorCategory,
|
|
8
|
+
ErrorClassRegistryEntry,
|
|
9
|
+
ErrorClassSurfaceProjection,
|
|
10
|
+
SurfaceName,
|
|
11
|
+
} from '@ontrails/core';
|
|
12
|
+
import type {
|
|
13
|
+
TopoGraph,
|
|
14
|
+
TopoGraphEntry,
|
|
15
|
+
TopoGraphVersionEntry,
|
|
16
|
+
} from '../types.js';
|
|
17
|
+
|
|
18
|
+
export type TrailErrorFactKind =
|
|
19
|
+
| 'documented'
|
|
20
|
+
| 'handled'
|
|
21
|
+
| 'inferred'
|
|
22
|
+
| 'observed';
|
|
23
|
+
|
|
24
|
+
export type TrailErrorFactsCompleteness =
|
|
25
|
+
| {
|
|
26
|
+
readonly reason: 'authored-facts-exhausted';
|
|
27
|
+
readonly status: 'complete';
|
|
28
|
+
}
|
|
29
|
+
| {
|
|
30
|
+
readonly reason: 'inferred-facts-supplied' | 'observed-facts-supplied';
|
|
31
|
+
readonly status: 'partial';
|
|
32
|
+
}
|
|
33
|
+
| {
|
|
34
|
+
readonly reason: 'no-exhaustive-emitted-error-contract' | 'not-evaluated';
|
|
35
|
+
readonly status: 'unknown';
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export interface TrailErrorTaxonomyProjection {
|
|
39
|
+
readonly category?: ErrorCategory | undefined;
|
|
40
|
+
readonly dynamicCategory?:
|
|
41
|
+
| {
|
|
42
|
+
readonly inheritsCategoryFrom: 'wrapped-error';
|
|
43
|
+
}
|
|
44
|
+
| undefined;
|
|
45
|
+
readonly known: boolean;
|
|
46
|
+
readonly name: string;
|
|
47
|
+
readonly retryable?: boolean | undefined;
|
|
48
|
+
readonly surfaces: readonly ErrorClassSurfaceProjection[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type TrailErrorFactProvenance =
|
|
52
|
+
| {
|
|
53
|
+
readonly exampleName: string;
|
|
54
|
+
readonly source: 'trail.examples' | 'trail.versions.examples';
|
|
55
|
+
readonly trailId: string;
|
|
56
|
+
readonly version?: number | undefined;
|
|
57
|
+
}
|
|
58
|
+
| {
|
|
59
|
+
readonly detourIndex: number;
|
|
60
|
+
readonly source: 'trail.detours' | 'trail.versions.detours';
|
|
61
|
+
readonly trailId: string;
|
|
62
|
+
readonly version?: number | undefined;
|
|
63
|
+
}
|
|
64
|
+
| {
|
|
65
|
+
readonly detail?: string | undefined;
|
|
66
|
+
readonly source: 'static-inference';
|
|
67
|
+
readonly trailId: string;
|
|
68
|
+
readonly version?: number | undefined;
|
|
69
|
+
}
|
|
70
|
+
| {
|
|
71
|
+
readonly detail?: string | undefined;
|
|
72
|
+
readonly source: 'runtime-observation';
|
|
73
|
+
readonly trailId: string;
|
|
74
|
+
readonly version?: number | undefined;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export interface TrailErrorFact {
|
|
78
|
+
readonly completeness: TrailErrorFactsCompleteness;
|
|
79
|
+
readonly kind: TrailErrorFactKind;
|
|
80
|
+
readonly provenance: TrailErrorFactProvenance;
|
|
81
|
+
readonly taxonomy: TrailErrorTaxonomyProjection;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface TrailErrorFacts {
|
|
85
|
+
readonly completeness: {
|
|
86
|
+
readonly emitted: TrailErrorFactsCompleteness;
|
|
87
|
+
readonly handled: TrailErrorFactsCompleteness;
|
|
88
|
+
readonly documented: TrailErrorFactsCompleteness;
|
|
89
|
+
readonly inferred: TrailErrorFactsCompleteness;
|
|
90
|
+
readonly observed: TrailErrorFactsCompleteness;
|
|
91
|
+
};
|
|
92
|
+
readonly facts: readonly TrailErrorFact[];
|
|
93
|
+
readonly trailId: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface TrailErrorEvidenceInput {
|
|
97
|
+
readonly detail?: string | undefined;
|
|
98
|
+
readonly errorName: string;
|
|
99
|
+
readonly trailId: string;
|
|
100
|
+
readonly version?: number | undefined;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface TrailErrorFactsOptions {
|
|
104
|
+
readonly inferred?: readonly TrailErrorEvidenceInput[] | undefined;
|
|
105
|
+
readonly observed?: readonly TrailErrorEvidenceInput[] | undefined;
|
|
106
|
+
readonly surfaces?: readonly SurfaceName[] | undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const errorClassByName: ReadonlyMap<string, ErrorClassRegistryEntry> = new Map(
|
|
110
|
+
errorClasses.map((entry) => [entry.name, entry])
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const taxonomyProjection = (
|
|
114
|
+
errorName: string,
|
|
115
|
+
surfaces: readonly SurfaceName[]
|
|
116
|
+
): TrailErrorTaxonomyProjection => {
|
|
117
|
+
const registryEntry = errorClassByName.get(errorName);
|
|
118
|
+
if (registryEntry === undefined) {
|
|
119
|
+
return {
|
|
120
|
+
known: false,
|
|
121
|
+
name: errorName,
|
|
122
|
+
surfaces: [],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (registryEntry.category === 'dynamic') {
|
|
127
|
+
return {
|
|
128
|
+
dynamicCategory: {
|
|
129
|
+
inheritsCategoryFrom: registryEntry.inheritsCategoryFrom,
|
|
130
|
+
},
|
|
131
|
+
known: true,
|
|
132
|
+
name: registryEntry.name,
|
|
133
|
+
retryable: registryEntry.retryable,
|
|
134
|
+
surfaces: [],
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
category: registryEntry.category,
|
|
140
|
+
known: true,
|
|
141
|
+
name: registryEntry.name,
|
|
142
|
+
retryable: registryEntry.retryable,
|
|
143
|
+
surfaces: surfaces.flatMap((surface) => {
|
|
144
|
+
const projected = projectErrorClassSurface(surface, errorName);
|
|
145
|
+
return projected === undefined ? [] : [projected];
|
|
146
|
+
}),
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const authoredCompleteness: TrailErrorFactsCompleteness = {
|
|
151
|
+
reason: 'authored-facts-exhausted',
|
|
152
|
+
status: 'complete',
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const emittedCompleteness: TrailErrorFactsCompleteness = {
|
|
156
|
+
reason: 'no-exhaustive-emitted-error-contract',
|
|
157
|
+
status: 'unknown',
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const notEvaluatedCompleteness: TrailErrorFactsCompleteness = {
|
|
161
|
+
reason: 'not-evaluated',
|
|
162
|
+
status: 'unknown',
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const inferredCompleteness: TrailErrorFactsCompleteness = {
|
|
166
|
+
reason: 'inferred-facts-supplied',
|
|
167
|
+
status: 'partial',
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const observedCompleteness: TrailErrorFactsCompleteness = {
|
|
171
|
+
reason: 'observed-facts-supplied',
|
|
172
|
+
status: 'partial',
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const documentedFacts = (
|
|
176
|
+
entry: TopoGraphEntry,
|
|
177
|
+
surfaces: readonly SurfaceName[]
|
|
178
|
+
): TrailErrorFact[] =>
|
|
179
|
+
(entry.examples ?? []).flatMap((example): TrailErrorFact[] => {
|
|
180
|
+
if (example.kind !== 'error' || example.error === undefined) {
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
return [
|
|
184
|
+
{
|
|
185
|
+
completeness: authoredCompleteness,
|
|
186
|
+
kind: 'documented',
|
|
187
|
+
provenance: {
|
|
188
|
+
exampleName: example.name,
|
|
189
|
+
source: 'trail.examples',
|
|
190
|
+
trailId: entry.id,
|
|
191
|
+
},
|
|
192
|
+
taxonomy: taxonomyProjection(example.error, surfaces),
|
|
193
|
+
},
|
|
194
|
+
];
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const handledFacts = (
|
|
198
|
+
entry: TopoGraphEntry,
|
|
199
|
+
surfaces: readonly SurfaceName[]
|
|
200
|
+
): TrailErrorFact[] =>
|
|
201
|
+
(entry.detours ?? []).map(
|
|
202
|
+
(detour, detourIndex): TrailErrorFact => ({
|
|
203
|
+
completeness: authoredCompleteness,
|
|
204
|
+
kind: 'handled',
|
|
205
|
+
provenance: {
|
|
206
|
+
detourIndex,
|
|
207
|
+
source: 'trail.detours',
|
|
208
|
+
trailId: entry.id,
|
|
209
|
+
},
|
|
210
|
+
taxonomy: taxonomyProjection(detour.on, surfaces),
|
|
211
|
+
})
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
const versionDocumentedFacts = (
|
|
215
|
+
trailId: string,
|
|
216
|
+
version: number,
|
|
217
|
+
entry: TopoGraphVersionEntry,
|
|
218
|
+
surfaces: readonly SurfaceName[]
|
|
219
|
+
): TrailErrorFact[] =>
|
|
220
|
+
(entry.examples ?? []).flatMap((example): TrailErrorFact[] => {
|
|
221
|
+
if (example.kind !== 'error' || example.error === undefined) {
|
|
222
|
+
return [];
|
|
223
|
+
}
|
|
224
|
+
return [
|
|
225
|
+
{
|
|
226
|
+
completeness: authoredCompleteness,
|
|
227
|
+
kind: 'documented',
|
|
228
|
+
provenance: {
|
|
229
|
+
exampleName: example.name,
|
|
230
|
+
source: 'trail.versions.examples',
|
|
231
|
+
trailId,
|
|
232
|
+
version,
|
|
233
|
+
},
|
|
234
|
+
taxonomy: taxonomyProjection(example.error, surfaces),
|
|
235
|
+
},
|
|
236
|
+
];
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const versionHandledFacts = (
|
|
240
|
+
trailId: string,
|
|
241
|
+
version: number,
|
|
242
|
+
entry: TopoGraphVersionEntry,
|
|
243
|
+
surfaces: readonly SurfaceName[]
|
|
244
|
+
): TrailErrorFact[] =>
|
|
245
|
+
(entry.detours ?? []).map(
|
|
246
|
+
(detour, detourIndex): TrailErrorFact => ({
|
|
247
|
+
completeness: authoredCompleteness,
|
|
248
|
+
kind: 'handled',
|
|
249
|
+
provenance: {
|
|
250
|
+
detourIndex,
|
|
251
|
+
source: 'trail.versions.detours',
|
|
252
|
+
trailId,
|
|
253
|
+
version,
|
|
254
|
+
},
|
|
255
|
+
taxonomy: taxonomyProjection(detour.on, surfaces),
|
|
256
|
+
})
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
const versionFacts = (
|
|
260
|
+
entry: TopoGraphEntry,
|
|
261
|
+
surfaces: readonly SurfaceName[]
|
|
262
|
+
): TrailErrorFact[] =>
|
|
263
|
+
Object.entries(entry.versions ?? {}).flatMap(([versionKey, versionEntry]) => {
|
|
264
|
+
const version = Number.parseInt(versionKey, 10);
|
|
265
|
+
if (!Number.isFinite(version)) {
|
|
266
|
+
return [];
|
|
267
|
+
}
|
|
268
|
+
return [
|
|
269
|
+
...versionDocumentedFacts(entry.id, version, versionEntry, surfaces),
|
|
270
|
+
...versionHandledFacts(entry.id, version, versionEntry, surfaces),
|
|
271
|
+
];
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
const inputFact = (
|
|
275
|
+
input: TrailErrorEvidenceInput,
|
|
276
|
+
kind: 'inferred' | 'observed',
|
|
277
|
+
surfaces: readonly SurfaceName[]
|
|
278
|
+
): TrailErrorFact => ({
|
|
279
|
+
completeness:
|
|
280
|
+
kind === 'inferred' ? inferredCompleteness : observedCompleteness,
|
|
281
|
+
kind,
|
|
282
|
+
provenance: {
|
|
283
|
+
...(input.detail === undefined ? {} : { detail: input.detail }),
|
|
284
|
+
source: kind === 'inferred' ? 'static-inference' : 'runtime-observation',
|
|
285
|
+
trailId: input.trailId,
|
|
286
|
+
...(input.version === undefined ? {} : { version: input.version }),
|
|
287
|
+
},
|
|
288
|
+
taxonomy: taxonomyProjection(input.errorName, surfaces),
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const byTrail = (
|
|
292
|
+
inputs: readonly TrailErrorEvidenceInput[] | undefined
|
|
293
|
+
): ReadonlyMap<string, readonly TrailErrorEvidenceInput[]> => {
|
|
294
|
+
const map = new Map<string, TrailErrorEvidenceInput[]>();
|
|
295
|
+
for (const input of inputs ?? []) {
|
|
296
|
+
const existing = map.get(input.trailId) ?? [];
|
|
297
|
+
existing.push(input);
|
|
298
|
+
map.set(input.trailId, existing);
|
|
299
|
+
}
|
|
300
|
+
return map;
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const sortFacts = (
|
|
304
|
+
facts: readonly TrailErrorFact[]
|
|
305
|
+
): readonly TrailErrorFact[] =>
|
|
306
|
+
[...facts].toSorted((left, right) => {
|
|
307
|
+
const leftVersion = left.provenance.version ?? 0;
|
|
308
|
+
const rightVersion = right.provenance.version ?? 0;
|
|
309
|
+
return (
|
|
310
|
+
leftVersion - rightVersion ||
|
|
311
|
+
left.kind.localeCompare(right.kind) ||
|
|
312
|
+
left.taxonomy.name.localeCompare(right.taxonomy.name)
|
|
313
|
+
);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
export const deriveTrailErrorFacts = (
|
|
317
|
+
topoGraph: TopoGraph,
|
|
318
|
+
options: TrailErrorFactsOptions = {}
|
|
319
|
+
): readonly TrailErrorFacts[] => {
|
|
320
|
+
const surfaces = options.surfaces ?? surfaceNames;
|
|
321
|
+
const inferredByTrail = byTrail(options.inferred);
|
|
322
|
+
const observedByTrail = byTrail(options.observed);
|
|
323
|
+
|
|
324
|
+
return topoGraph.entries
|
|
325
|
+
.filter(
|
|
326
|
+
(entry): entry is TopoGraphEntry & { readonly kind: 'trail' } =>
|
|
327
|
+
entry.kind === 'trail'
|
|
328
|
+
)
|
|
329
|
+
.map((entry): TrailErrorFacts => {
|
|
330
|
+
const inferredInputs = inferredByTrail.get(entry.id) ?? [];
|
|
331
|
+
const observedInputs = observedByTrail.get(entry.id) ?? [];
|
|
332
|
+
const facts = sortFacts([
|
|
333
|
+
...documentedFacts(entry, surfaces),
|
|
334
|
+
...handledFacts(entry, surfaces),
|
|
335
|
+
...versionFacts(entry, surfaces),
|
|
336
|
+
...inferredInputs.map((input) =>
|
|
337
|
+
inputFact(input, 'inferred', surfaces)
|
|
338
|
+
),
|
|
339
|
+
...observedInputs.map((input) =>
|
|
340
|
+
inputFact(input, 'observed', surfaces)
|
|
341
|
+
),
|
|
342
|
+
]);
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
completeness: {
|
|
346
|
+
documented: authoredCompleteness,
|
|
347
|
+
emitted: emittedCompleteness,
|
|
348
|
+
handled: authoredCompleteness,
|
|
349
|
+
inferred:
|
|
350
|
+
inferredInputs.length > 0
|
|
351
|
+
? inferredCompleteness
|
|
352
|
+
: notEvaluatedCompleteness,
|
|
353
|
+
observed:
|
|
354
|
+
observedInputs.length > 0
|
|
355
|
+
? observedCompleteness
|
|
356
|
+
: notEvaluatedCompleteness,
|
|
357
|
+
},
|
|
358
|
+
facts,
|
|
359
|
+
trailId: entry.id,
|
|
360
|
+
};
|
|
361
|
+
})
|
|
362
|
+
.toSorted((left, right) => left.trailId.localeCompare(right.trailId));
|
|
363
|
+
};
|