@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.
- package/CHANGELOG.md +323 -8
- package/README.md +114 -33
- package/package.json +11 -1
- package/src/activation-provenance.ts +111 -0
- package/src/activation-source-compatibility.ts +430 -0
- package/src/activation-source-projection.ts +221 -0
- package/src/activation-source.ts +91 -0
- package/src/blob-ref.ts +51 -0
- package/src/branded.ts +1 -1
- package/src/compose-batch.ts +69 -0
- package/src/compose-schema.ts +36 -0
- package/src/context.ts +44 -10
- package/src/contour.ts +346 -0
- package/src/derive.ts +250 -19
- package/src/detours.ts +8 -0
- package/src/diagnostics.ts +21 -0
- package/src/draft.ts +350 -0
- package/src/error-projection.ts +51 -0
- package/src/errors.ts +335 -48
- package/src/execute.ts +1577 -0
- package/src/fire.ts +1169 -0
- package/src/glob.ts +81 -0
- package/src/index.ts +526 -35
- package/src/internal/fork-ctx.ts +69 -0
- package/src/layer-projection.ts +193 -0
- package/src/layer.ts +43 -6
- package/src/observe.ts +361 -0
- package/src/path-scope.ts +66 -0
- package/src/path-security.ts +1 -1
- package/src/permits.ts +12 -0
- package/src/redaction/patterns.ts +6 -3
- package/src/resilience.ts +11 -11
- package/src/resource-config.ts +792 -0
- package/src/resource.ts +194 -0
- package/src/result.ts +18 -0
- package/src/run.ts +76 -0
- package/src/schedule-runtime.ts +689 -0
- package/src/schedule.ts +326 -0
- package/src/serialization.ts +92 -55
- package/src/signal-diagnostics.ts +633 -0
- package/src/signal-ref.ts +87 -0
- package/src/signal.ts +104 -0
- package/src/store/accessor-protocol.ts +56 -0
- package/src/store/index.ts +4 -0
- package/src/structured-examples.ts +248 -0
- package/src/surface-derivation.ts +91 -0
- package/src/surface-filter.ts +101 -0
- package/src/surface-versioning.ts +42 -0
- package/src/topo.ts +769 -67
- package/src/tracing.ts +345 -0
- package/src/trail-id-glob.ts +15 -0
- package/src/trail.ts +1168 -30
- package/src/trails/derive-trail.ts +842 -0
- package/src/trails/index.ts +9 -0
- package/src/trails/ingest.ts +139 -0
- package/src/trails-db.ts +206 -0
- package/src/transport-error-map.ts +174 -0
- package/src/type-utils.ts +87 -0
- package/src/types.ts +254 -12
- package/src/validate-established-topo.ts +69 -0
- package/src/validate-topo.ts +414 -56
- package/src/validation.ts +152 -4
- package/src/version-marker.ts +716 -0
- package/src/version-resolution.ts +308 -0
- package/src/version-runtime.ts +120 -0
- package/src/webhook.ts +278 -0
- package/src/workspace.ts +162 -1
- package/src/zod-wrappers.ts +72 -0
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-lint.log +0 -3
- package/.turbo/turbo-typecheck.log +0 -1
- package/dist/adapters.d.ts +0 -39
- package/dist/adapters.d.ts.map +0 -1
- package/dist/adapters.js +0 -2
- package/dist/adapters.js.map +0 -1
- package/dist/blob-ref.d.ts +0 -20
- package/dist/blob-ref.d.ts.map +0 -1
- package/dist/blob-ref.js +0 -22
- package/dist/blob-ref.js.map +0 -1
- package/dist/branded.d.ts +0 -36
- package/dist/branded.d.ts.map +0 -1
- package/dist/branded.js +0 -89
- package/dist/branded.js.map +0 -1
- package/dist/collections.d.ts +0 -31
- package/dist/collections.d.ts.map +0 -1
- package/dist/collections.js +0 -60
- package/dist/collections.js.map +0 -1
- package/dist/context.d.ts +0 -10
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -15
- package/dist/context.js.map +0 -1
- package/dist/derive.d.ts +0 -33
- package/dist/derive.d.ts.map +0 -1
- package/dist/derive.js +0 -125
- package/dist/derive.js.map +0 -1
- package/dist/errors.d.ts +0 -83
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -142
- package/dist/errors.js.map +0 -1
- package/dist/event.d.ts +0 -45
- package/dist/event.d.ts.map +0 -1
- package/dist/event.js +0 -17
- package/dist/event.js.map +0 -1
- package/dist/fetch.d.ts +0 -15
- package/dist/fetch.d.ts.map +0 -1
- package/dist/fetch.js +0 -102
- package/dist/fetch.js.map +0 -1
- package/dist/guards.d.ts +0 -17
- package/dist/guards.d.ts.map +0 -1
- package/dist/guards.js +0 -25
- package/dist/guards.js.map +0 -1
- package/dist/health.d.ts +0 -18
- package/dist/health.d.ts.map +0 -1
- package/dist/health.js +0 -5
- package/dist/health.js.map +0 -1
- package/dist/hike.d.ts +0 -36
- package/dist/hike.d.ts.map +0 -1
- package/dist/hike.js +0 -20
- package/dist/hike.js.map +0 -1
- package/dist/index.d.ts +0 -36
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -40
- package/dist/index.js.map +0 -1
- package/dist/job.d.ts +0 -24
- package/dist/job.d.ts.map +0 -1
- package/dist/job.js +0 -17
- package/dist/job.js.map +0 -1
- package/dist/layer.d.ts +0 -17
- package/dist/layer.d.ts.map +0 -1
- package/dist/layer.js +0 -21
- package/dist/layer.js.map +0 -1
- package/dist/path-security.d.ts +0 -28
- package/dist/path-security.d.ts.map +0 -1
- package/dist/path-security.js +0 -63
- package/dist/path-security.js.map +0 -1
- package/dist/patterns/bulk.d.ts +0 -15
- package/dist/patterns/bulk.d.ts.map +0 -1
- package/dist/patterns/bulk.js +0 -14
- package/dist/patterns/bulk.js.map +0 -1
- package/dist/patterns/change.d.ts +0 -10
- package/dist/patterns/change.d.ts.map +0 -1
- package/dist/patterns/change.js +0 -10
- package/dist/patterns/change.js.map +0 -1
- package/dist/patterns/date-range.d.ts +0 -10
- package/dist/patterns/date-range.d.ts.map +0 -1
- package/dist/patterns/date-range.js +0 -10
- package/dist/patterns/date-range.js.map +0 -1
- package/dist/patterns/index.d.ts +0 -9
- package/dist/patterns/index.d.ts.map +0 -1
- package/dist/patterns/index.js +0 -9
- package/dist/patterns/index.js.map +0 -1
- package/dist/patterns/pagination.d.ts +0 -18
- package/dist/patterns/pagination.d.ts.map +0 -1
- package/dist/patterns/pagination.js +0 -18
- package/dist/patterns/pagination.js.map +0 -1
- package/dist/patterns/progress.d.ts +0 -11
- package/dist/patterns/progress.d.ts.map +0 -1
- package/dist/patterns/progress.js +0 -11
- package/dist/patterns/progress.js.map +0 -1
- package/dist/patterns/sorting.d.ts +0 -13
- package/dist/patterns/sorting.d.ts.map +0 -1
- package/dist/patterns/sorting.js +0 -10
- package/dist/patterns/sorting.js.map +0 -1
- package/dist/patterns/status.d.ts +0 -15
- package/dist/patterns/status.d.ts.map +0 -1
- package/dist/patterns/status.js +0 -9
- package/dist/patterns/status.js.map +0 -1
- package/dist/patterns/timestamps.d.ts +0 -10
- package/dist/patterns/timestamps.d.ts.map +0 -1
- package/dist/patterns/timestamps.js +0 -10
- package/dist/patterns/timestamps.js.map +0 -1
- package/dist/redaction/index.d.ts +0 -4
- package/dist/redaction/index.d.ts.map +0 -1
- package/dist/redaction/index.js +0 -3
- package/dist/redaction/index.js.map +0 -1
- package/dist/redaction/patterns.d.ts +0 -9
- package/dist/redaction/patterns.d.ts.map +0 -1
- package/dist/redaction/patterns.js +0 -39
- package/dist/redaction/patterns.js.map +0 -1
- package/dist/redaction/redactor.d.ts +0 -27
- package/dist/redaction/redactor.d.ts.map +0 -1
- package/dist/redaction/redactor.js +0 -89
- package/dist/redaction/redactor.js.map +0 -1
- package/dist/resilience.d.ts +0 -34
- package/dist/resilience.d.ts.map +0 -1
- package/dist/resilience.js +0 -164
- package/dist/resilience.js.map +0 -1
- package/dist/result.d.ts +0 -57
- package/dist/result.d.ts.map +0 -1
- package/dist/result.js +0 -156
- package/dist/result.js.map +0 -1
- package/dist/serialization.d.ts +0 -27
- package/dist/serialization.d.ts.map +0 -1
- package/dist/serialization.js +0 -153
- package/dist/serialization.js.map +0 -1
- package/dist/topo.d.ts +0 -18
- package/dist/topo.d.ts.map +0 -1
- package/dist/topo.js +0 -80
- package/dist/topo.js.map +0 -1
- package/dist/trail.d.ts +0 -83
- package/dist/trail.d.ts.map +0 -1
- package/dist/trail.js +0 -16
- package/dist/trail.js.map +0 -1
- package/dist/types.d.ts +0 -46
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/validate-topo.d.ts +0 -24
- package/dist/validate-topo.d.ts.map +0 -1
- package/dist/validate-topo.js +0 -156
- package/dist/validate-topo.js.map +0 -1
- package/dist/validation.d.ts +0 -27
- package/dist/validation.d.ts.map +0 -1
- package/dist/validation.js +0 -134
- package/dist/validation.js.map +0 -1
- package/dist/workspace.d.ts +0 -25
- package/dist/workspace.d.ts.map +0 -1
- package/dist/workspace.js +0 -57
- package/dist/workspace.js.map +0 -1
- package/src/__tests__/blob-ref.test.ts +0 -103
- package/src/__tests__/branded.test.ts +0 -148
- package/src/__tests__/collections.test.ts +0 -126
- package/src/__tests__/context.test.ts +0 -66
- package/src/__tests__/derive.test.ts +0 -203
- package/src/__tests__/errors.test.ts +0 -309
- package/src/__tests__/event.test.ts +0 -82
- package/src/__tests__/fetch.test.ts +0 -217
- package/src/__tests__/guards.test.ts +0 -102
- package/src/__tests__/hike.test.ts +0 -117
- package/src/__tests__/job.test.ts +0 -98
- package/src/__tests__/layer.test.ts +0 -224
- package/src/__tests__/path-security.test.ts +0 -114
- package/src/__tests__/patterns.test.ts +0 -273
- package/src/__tests__/redaction.test.ts +0 -244
- package/src/__tests__/resilience.test.ts +0 -246
- package/src/__tests__/result.test.ts +0 -155
- package/src/__tests__/serialization.test.ts +0 -401
- package/src/__tests__/topo.test.ts +0 -220
- package/src/__tests__/trail.test.ts +0 -179
- package/src/__tests__/validate-topo.test.ts +0 -280
- package/src/__tests__/validation.test.ts +0 -283
- package/src/__tests__/workspace.test.ts +0 -183
- package/src/adapters.ts +0 -68
- package/src/event.ts +0 -77
- package/src/health.ts +0 -23
- package/src/hike.ts +0 -77
- package/src/job.ts +0 -20
- package/tsconfig.json +0 -9
- 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
|
+
};
|