@ontrails/core 1.0.0-beta.13 → 1.0.0-beta.15
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/.turbo/turbo-lint.log +1 -1
- package/CHANGELOG.md +17 -0
- package/README.md +19 -14
- package/dist/branded.d.ts +1 -1
- package/dist/branded.d.ts.map +1 -1
- package/dist/branded.js +1 -1
- package/dist/branded.js.map +1 -1
- package/dist/context.d.ts +13 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +19 -3
- package/dist/context.js.map +1 -1
- package/dist/contour.d.ts +81 -0
- package/dist/contour.d.ts.map +1 -0
- package/dist/contour.js +176 -0
- package/dist/contour.js.map +1 -0
- package/dist/cross-schema.d.ts +18 -0
- package/dist/cross-schema.d.ts.map +1 -0
- package/dist/cross-schema.js +29 -0
- package/dist/cross-schema.js.map +1 -0
- package/dist/derive.d.ts +6 -0
- package/dist/derive.d.ts.map +1 -1
- package/dist/derive.js +29 -5
- package/dist/derive.js.map +1 -1
- package/dist/draft.d.ts +28 -0
- package/dist/draft.d.ts.map +1 -0
- package/dist/draft.js +157 -0
- package/dist/draft.js.map +1 -0
- package/dist/errors.d.ts +62 -4
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +44 -1
- package/dist/errors.js.map +1 -1
- package/dist/execute.d.ts +26 -9
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +441 -20
- package/dist/execute.js.map +1 -1
- package/dist/fire.d.ts +43 -0
- package/dist/fire.d.ts.map +1 -0
- package/dist/fire.js +185 -0
- package/dist/fire.js.map +1 -0
- package/dist/index.d.ts +33 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -12
- package/dist/index.js.map +1 -1
- package/dist/internal/cross-batch.d.ts +44 -0
- package/dist/internal/cross-batch.d.ts.map +1 -0
- package/dist/internal/cross-batch.js +58 -0
- package/dist/internal/cross-batch.js.map +1 -0
- package/dist/internal/fork-ctx.d.ts +41 -0
- package/dist/internal/fork-ctx.d.ts.map +1 -0
- package/dist/internal/fork-ctx.js +47 -0
- package/dist/internal/fork-ctx.js.map +1 -0
- package/dist/internal/signal-ref.d.ts +15 -0
- package/dist/internal/signal-ref.d.ts.map +1 -0
- package/dist/internal/signal-ref.js +41 -0
- package/dist/internal/signal-ref.js.map +1 -0
- package/dist/internal/topo-saves.d.ts +53 -0
- package/dist/internal/topo-saves.d.ts.map +1 -0
- package/dist/internal/topo-saves.js +449 -0
- package/dist/internal/topo-saves.js.map +1 -0
- package/dist/internal/topo-snapshots.d.ts +57 -0
- package/dist/internal/topo-snapshots.d.ts.map +1 -0
- package/dist/internal/topo-snapshots.js +372 -0
- package/dist/internal/topo-snapshots.js.map +1 -0
- package/dist/internal/topo-store-read.d.ts +70 -0
- package/dist/internal/topo-store-read.d.ts.map +1 -0
- package/dist/internal/topo-store-read.js +221 -0
- package/dist/internal/topo-store-read.js.map +1 -0
- package/dist/internal/topo-store.d.ts +26 -0
- package/dist/internal/topo-store.d.ts.map +1 -0
- package/dist/internal/topo-store.js +621 -0
- package/dist/internal/topo-store.js.map +1 -0
- package/dist/internal/tracing.d.ts +98 -0
- package/dist/internal/tracing.d.ts.map +1 -0
- package/dist/internal/tracing.js +102 -0
- package/dist/internal/tracing.js.map +1 -0
- package/dist/internal/trails-db.d.ts +16 -0
- package/dist/internal/trails-db.d.ts.map +1 -0
- package/dist/internal/trails-db.js +118 -0
- package/dist/internal/trails-db.js.map +1 -0
- package/dist/internal/zod-wrappers.d.ts +40 -0
- package/dist/internal/zod-wrappers.d.ts.map +1 -0
- package/dist/internal/zod-wrappers.js +59 -0
- package/dist/internal/zod-wrappers.js.map +1 -0
- package/dist/layer.d.ts +22 -5
- package/dist/layer.d.ts.map +1 -1
- package/dist/layer.js +20 -3
- package/dist/layer.js.map +1 -1
- package/dist/path-security.d.ts +1 -1
- package/dist/path-security.d.ts.map +1 -1
- package/dist/path-security.js +1 -1
- package/dist/path-security.js.map +1 -1
- package/dist/resilience.d.ts +1 -1
- package/dist/resilience.d.ts.map +1 -1
- package/dist/resilience.js +3 -3
- package/dist/resilience.js.map +1 -1
- package/dist/resource-config.d.ts +22 -0
- package/dist/resource-config.d.ts.map +1 -0
- package/dist/resource-config.js +210 -0
- package/dist/resource-config.js.map +1 -0
- package/dist/resource.d.ts +74 -0
- package/dist/resource.d.ts.map +1 -0
- package/dist/resource.js +61 -0
- package/dist/resource.js.map +1 -0
- package/dist/run.d.ts +4 -3
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +5 -4
- package/dist/run.js.map +1 -1
- package/dist/serialization.d.ts.map +1 -1
- package/dist/serialization.js +2 -1
- package/dist/serialization.js.map +1 -1
- package/dist/store/accessor-protocol.d.ts +51 -0
- package/dist/store/accessor-protocol.d.ts.map +1 -0
- package/dist/store/accessor-protocol.js +2 -0
- package/dist/store/accessor-protocol.js.map +1 -0
- package/dist/store/index.d.ts +2 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +2 -0
- package/dist/store/index.js.map +1 -0
- package/dist/surface-filter.d.ts +13 -0
- package/dist/surface-filter.d.ts.map +1 -0
- package/dist/surface-filter.js +76 -0
- package/dist/surface-filter.js.map +1 -0
- package/dist/topo-store.d.ts +62 -0
- package/dist/topo-store.d.ts.map +1 -0
- package/dist/topo-store.js +319 -0
- package/dist/topo-store.js.map +1 -0
- package/dist/topo.d.ts +23 -9
- package/dist/topo.d.ts.map +1 -1
- package/dist/topo.js +175 -46
- package/dist/topo.js.map +1 -1
- package/dist/trail.d.ts +86 -19
- package/dist/trail.d.ts.map +1 -1
- package/dist/trail.js +59 -4
- package/dist/trail.js.map +1 -1
- package/dist/trails/derive-trail.d.ts +66 -0
- package/dist/trails/derive-trail.d.ts.map +1 -0
- package/dist/trails/derive-trail.js +378 -0
- package/dist/trails/derive-trail.js.map +1 -0
- package/dist/trails/index.d.ts +5 -0
- package/dist/trails/index.d.ts.map +1 -0
- package/dist/trails/index.js +3 -0
- package/dist/trails/index.js.map +1 -0
- package/dist/trails/ingest.d.ts +24 -0
- package/dist/trails/ingest.d.ts.map +1 -0
- package/dist/trails/ingest.js +63 -0
- package/dist/trails/ingest.js.map +1 -0
- package/dist/transport-error-map.d.ts +106 -0
- package/dist/transport-error-map.d.ts.map +1 -0
- package/dist/transport-error-map.js +24 -0
- package/dist/transport-error-map.js.map +1 -0
- package/dist/type-checks.test-d.d.ts +71 -0
- package/dist/type-checks.test-d.d.ts.map +1 -0
- package/dist/type-checks.test-d.js +12 -0
- package/dist/type-checks.test-d.js.map +1 -0
- package/dist/type-utils.d.ts +11 -2
- package/dist/type-utils.d.ts.map +1 -1
- package/dist/type-utils.js.map +1 -1
- package/dist/types.d.ts +113 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/validate-established-topo.d.ts +76 -0
- package/dist/validate-established-topo.d.ts.map +1 -0
- package/dist/validate-established-topo.js +43 -0
- package/dist/validate-established-topo.js.map +1 -0
- package/dist/validate-topo.d.ts.map +1 -1
- package/dist/validate-topo.js +27 -8
- package/dist/validate-topo.js.map +1 -1
- package/dist/workspace.d.ts +1 -1
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +1 -1
- package/dist/workspace.js.map +1 -1
- package/package.json +10 -1
- package/src/__tests__/branded.test.ts +5 -5
- package/src/__tests__/context.test.ts +3 -3
- package/src/__tests__/contour.test.ts +263 -0
- package/src/__tests__/derive-trail.test.ts +668 -0
- package/src/__tests__/derive.test.ts +58 -1
- package/src/__tests__/errors.test.ts +118 -0
- package/src/__tests__/execute.test.ts +1179 -141
- package/src/__tests__/fire.test.ts +1056 -0
- package/src/__tests__/fork-ctx.test.ts +83 -0
- package/src/__tests__/ingest.test.ts +191 -0
- package/src/__tests__/{gate.test.ts → layer.test.ts} +21 -21
- package/src/__tests__/path-security.test.ts +7 -7
- package/src/__tests__/resilience.test.ts +8 -8
- package/src/__tests__/resource.test.ts +204 -0
- package/src/__tests__/run.test.ts +13 -13
- package/src/__tests__/service-config.test.ts +28 -32
- package/src/__tests__/surface-filter.test.ts +196 -0
- package/src/__tests__/topo-store-read.test.ts +256 -0
- package/src/__tests__/topo-store.test.ts +869 -0
- package/src/__tests__/topo.test.ts +350 -54
- package/src/__tests__/trail.test.ts +189 -24
- package/src/__tests__/trails-db.test.ts +198 -0
- package/src/__tests__/transport-error-map.test.ts +82 -0
- package/src/__tests__/type-utils.test.ts +29 -1
- package/src/__tests__/validate-topo.test.ts +305 -16
- package/src/__tests__/workspace.test.ts +6 -6
- package/src/__tests__/zod-wrappers.test.ts +62 -0
- package/src/branded.ts +1 -1
- package/src/context.ts +24 -4
- package/src/contour.ts +344 -0
- package/src/cross-schema.ts +36 -0
- package/src/derive.ts +39 -8
- package/src/draft.ts +338 -0
- package/src/errors.ts +62 -18
- package/src/execute.ts +840 -29
- package/src/fire.ts +295 -0
- package/src/index.ts +142 -26
- package/src/internal/cross-batch.ts +77 -0
- package/src/internal/fork-ctx.ts +69 -0
- package/src/internal/signal-ref.ts +87 -0
- package/src/internal/topo-snapshots.ts +517 -0
- package/src/internal/topo-store-read.ts +481 -0
- package/src/internal/topo-store.ts +1192 -0
- package/src/internal/tracing.ts +187 -0
- package/src/internal/trails-db.ts +187 -0
- package/src/internal/zod-wrappers.ts +78 -0
- package/src/layer.ts +50 -0
- package/src/path-security.ts +1 -1
- package/src/resilience.ts +3 -3
- package/src/{provision-config.ts → resource-config.ts} +109 -123
- package/src/resource.ts +155 -0
- package/src/run.ts +5 -4
- package/src/serialization.ts +2 -0
- package/src/store/accessor-protocol.ts +56 -0
- package/src/store/index.ts +4 -0
- package/src/surface-filter.ts +182 -0
- package/src/topo-store.ts +510 -0
- package/src/topo.ts +328 -57
- package/src/trail.ts +199 -31
- package/src/trails/derive-trail.ts +842 -0
- package/src/trails/index.ts +9 -0
- package/src/trails/ingest.ts +146 -0
- package/src/transport-error-map.ts +56 -0
- package/src/type-checks.test-d.ts +106 -0
- package/src/type-utils.ts +17 -2
- package/src/types.ts +148 -11
- package/src/validate-established-topo.ts +63 -0
- package/src/validate-topo.ts +37 -9
- package/src/workspace.ts +1 -1
- package/tsconfig.tests.json +10 -0
- package/tsconfig.tsbuildinfo +1 -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/dispatch.d.ts +0 -27
- package/dist/dispatch.d.ts.map +0 -1
- package/dist/dispatch.js +0 -34
- package/dist/dispatch.js.map +0 -1
- package/dist/event.d.ts +0 -8
- package/dist/event.d.ts.map +0 -1
- package/dist/event.js +0 -7
- package/dist/event.js.map +0 -1
- package/dist/gate.d.ts +0 -17
- package/dist/gate.d.ts.map +0 -1
- package/dist/gate.js +0 -21
- package/dist/gate.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/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/provision-config.d.ts +0 -22
- package/dist/provision-config.d.ts.map +0 -1
- package/dist/provision-config.js +0 -210
- package/dist/provision-config.js.map +0 -1
- package/dist/provision.d.ts +0 -71
- package/dist/provision.d.ts.map +0 -1
- package/dist/provision.js +0 -56
- package/dist/provision.js.map +0 -1
- package/dist/service-config.d.ts +0 -22
- package/dist/service-config.d.ts.map +0 -1
- package/dist/service-config.js +0 -210
- package/dist/service-config.js.map +0 -1
- package/dist/service.d.ts +0 -71
- package/dist/service.d.ts.map +0 -1
- package/dist/service.js +0 -56
- package/dist/service.js.map +0 -1
- package/src/__tests__/service.test.ts +0 -197
- package/src/event.ts +0 -15
- package/src/gate.ts +0 -44
- package/src/provision.ts +0 -148
package/src/draft.ts
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
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
|
+
| 'cross'
|
|
14
|
+
| 'contour'
|
|
15
|
+
| 'resource'
|
|
16
|
+
| 'replaced-by'
|
|
17
|
+
| 'schema-reference'
|
|
18
|
+
| 'signal-from';
|
|
19
|
+
|
|
20
|
+
export interface DraftDependency {
|
|
21
|
+
readonly fromId: string;
|
|
22
|
+
readonly kind: DraftDependencyKind;
|
|
23
|
+
readonly toId: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface DraftFinding {
|
|
27
|
+
readonly id: string;
|
|
28
|
+
readonly kind: 'contour' | 'resource' | 'signal' | 'trail' | 'unknown';
|
|
29
|
+
readonly message: string;
|
|
30
|
+
readonly rule: 'draft-contamination' | 'draft-id';
|
|
31
|
+
readonly via?: DraftDependencyKind | undefined;
|
|
32
|
+
readonly dependsOn?: string | undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface DraftReport {
|
|
36
|
+
readonly contaminatedIds: ReadonlySet<string>;
|
|
37
|
+
readonly declaredDraftIds: ReadonlySet<string>;
|
|
38
|
+
readonly dependencies: readonly DraftDependency[];
|
|
39
|
+
readonly findings: readonly DraftFinding[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface DraftReason {
|
|
43
|
+
readonly dependsOn: string;
|
|
44
|
+
readonly via: DraftDependencyKind;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type TopoNode = AnyContour | AnyResource | AnySignal | AnyTrail;
|
|
48
|
+
|
|
49
|
+
const replacedByTarget = (value: TopoNode): string | undefined => {
|
|
50
|
+
const raw = value as unknown as { replacedBy?: unknown };
|
|
51
|
+
return typeof raw.replacedBy === 'string' ? raw.replacedBy : undefined;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const isDraftId = (id: string): boolean =>
|
|
55
|
+
id.startsWith(DRAFT_ID_PREFIX);
|
|
56
|
+
|
|
57
|
+
const dependenciesFromIds = (
|
|
58
|
+
fromId: string,
|
|
59
|
+
toIds: readonly string[],
|
|
60
|
+
kind: DraftDependencyKind
|
|
61
|
+
): DraftDependency[] =>
|
|
62
|
+
toIds.map((toId) => ({
|
|
63
|
+
fromId,
|
|
64
|
+
kind,
|
|
65
|
+
toId,
|
|
66
|
+
}));
|
|
67
|
+
|
|
68
|
+
const dependencyFromTarget = (
|
|
69
|
+
fromId: string,
|
|
70
|
+
toId: string | undefined,
|
|
71
|
+
kind: DraftDependencyKind
|
|
72
|
+
): DraftDependency[] =>
|
|
73
|
+
toId === undefined
|
|
74
|
+
? []
|
|
75
|
+
: [
|
|
76
|
+
{
|
|
77
|
+
fromId,
|
|
78
|
+
kind,
|
|
79
|
+
toId,
|
|
80
|
+
},
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
const trailDependencies = (trail: AnyTrail): DraftDependency[] => [
|
|
84
|
+
...dependenciesFromIds(
|
|
85
|
+
trail.id,
|
|
86
|
+
(trail.contours ?? []).map((contour) => contour.name),
|
|
87
|
+
'contour'
|
|
88
|
+
),
|
|
89
|
+
...dependenciesFromIds(trail.id, trail.crosses, 'cross'),
|
|
90
|
+
...dependenciesFromIds(
|
|
91
|
+
trail.id,
|
|
92
|
+
trail.resources.map(({ id }) => id),
|
|
93
|
+
'resource'
|
|
94
|
+
),
|
|
95
|
+
...dependencyFromTarget(trail.id, replacedByTarget(trail), 'replaced-by'),
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
const contourDependencies = (contour: AnyContour): DraftDependency[] =>
|
|
99
|
+
dependenciesFromIds(
|
|
100
|
+
contour.name,
|
|
101
|
+
getContourReferences(contour).map((reference) => reference.contour),
|
|
102
|
+
'schema-reference'
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const signalDependencies = (signal: AnySignal): DraftDependency[] => [
|
|
106
|
+
...dependenciesFromIds(signal.id, signal.from ?? [], 'signal-from'),
|
|
107
|
+
...dependencyFromTarget(signal.id, replacedByTarget(signal), 'replaced-by'),
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
const resourceDependencies = (resource: AnyResource): DraftDependency[] =>
|
|
111
|
+
dependencyFromTarget(resource.id, replacedByTarget(resource), 'replaced-by');
|
|
112
|
+
|
|
113
|
+
const nodeKind = (
|
|
114
|
+
id: string,
|
|
115
|
+
contours: ReadonlyMap<string, AnyContour>,
|
|
116
|
+
trails: ReadonlyMap<string, AnyTrail>,
|
|
117
|
+
signals: ReadonlyMap<string, AnySignal>,
|
|
118
|
+
resources: ReadonlyMap<string, AnyResource>
|
|
119
|
+
): DraftFinding['kind'] => {
|
|
120
|
+
if (contours.has(id)) {
|
|
121
|
+
return 'contour';
|
|
122
|
+
}
|
|
123
|
+
if (trails.has(id)) {
|
|
124
|
+
return 'trail';
|
|
125
|
+
}
|
|
126
|
+
if (signals.has(id)) {
|
|
127
|
+
return 'signal';
|
|
128
|
+
}
|
|
129
|
+
if (resources.has(id)) {
|
|
130
|
+
return 'resource';
|
|
131
|
+
}
|
|
132
|
+
return 'unknown';
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const displayKind = (kind: DraftFinding['kind']): string =>
|
|
136
|
+
kind === 'unknown' ? 'Node' : kind[0]?.toUpperCase() + kind.slice(1);
|
|
137
|
+
|
|
138
|
+
const draftIdsFromKeys = (keys: Iterable<string>): string[] =>
|
|
139
|
+
[...keys].filter(isDraftId);
|
|
140
|
+
|
|
141
|
+
const collectDeclaredDraftIds = (topo: Topo): ReadonlySet<string> =>
|
|
142
|
+
new Set([
|
|
143
|
+
...draftIdsFromKeys(topo.contours.keys()),
|
|
144
|
+
...draftIdsFromKeys(topo.trails.keys()),
|
|
145
|
+
...draftIdsFromKeys(topo.signals.keys()),
|
|
146
|
+
...draftIdsFromKeys(topo.resources.keys()),
|
|
147
|
+
]);
|
|
148
|
+
|
|
149
|
+
const findingForDraftId = (
|
|
150
|
+
id: string,
|
|
151
|
+
kind: DraftFinding['kind']
|
|
152
|
+
): DraftFinding => ({
|
|
153
|
+
id,
|
|
154
|
+
kind,
|
|
155
|
+
message: `${displayKind(id ? kind : 'unknown')} "${id}" is draft and cannot appear in the established graph.`,
|
|
156
|
+
rule: 'draft-id',
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const findingForContamination = (
|
|
160
|
+
id: string,
|
|
161
|
+
kind: DraftFinding['kind'],
|
|
162
|
+
reason: DraftReason
|
|
163
|
+
): DraftFinding => {
|
|
164
|
+
const dependencyLabel = isDraftId(reason.dependsOn)
|
|
165
|
+
? `draft "${reason.dependsOn}"`
|
|
166
|
+
: `draft-contaminated "${reason.dependsOn}"`;
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
dependsOn: reason.dependsOn,
|
|
170
|
+
id,
|
|
171
|
+
kind,
|
|
172
|
+
message:
|
|
173
|
+
`Established ${kind} "${id}" depends on ${dependencyLabel} ` +
|
|
174
|
+
`via ${reason.via} and cannot appear in the established graph.`,
|
|
175
|
+
rule: 'draft-contamination',
|
|
176
|
+
via: reason.via,
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const contaminationReason = (
|
|
181
|
+
dependency: DraftDependency,
|
|
182
|
+
contaminatedIds: ReadonlySet<string>
|
|
183
|
+
): DraftReason | undefined => {
|
|
184
|
+
if (contaminatedIds.has(dependency.fromId)) {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!isDraftId(dependency.toId) && !contaminatedIds.has(dependency.toId)) {
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
dependsOn: dependency.toId,
|
|
194
|
+
via: dependency.kind,
|
|
195
|
+
};
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const markContaminatedDependency = (
|
|
199
|
+
dependency: DraftDependency,
|
|
200
|
+
contaminatedIds: Set<string>,
|
|
201
|
+
reasons: Map<string, DraftReason>
|
|
202
|
+
): boolean => {
|
|
203
|
+
const reason = contaminationReason(dependency, contaminatedIds);
|
|
204
|
+
|
|
205
|
+
if (reason === undefined) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
contaminatedIds.add(dependency.fromId);
|
|
210
|
+
reasons.set(dependency.fromId, reason);
|
|
211
|
+
return true;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const propagateContaminatedIds = (
|
|
215
|
+
declaredDraftIds: ReadonlySet<string>,
|
|
216
|
+
dependencies: readonly DraftDependency[]
|
|
217
|
+
): { contaminatedIds: Set<string>; reasons: Map<string, DraftReason> } => {
|
|
218
|
+
const contaminatedIds = new Set<string>(declaredDraftIds);
|
|
219
|
+
const reasons = new Map<string, DraftReason>();
|
|
220
|
+
|
|
221
|
+
const visit = (): void => {
|
|
222
|
+
if (
|
|
223
|
+
dependencies.some((dependency) =>
|
|
224
|
+
markContaminatedDependency(dependency, contaminatedIds, reasons)
|
|
225
|
+
)
|
|
226
|
+
) {
|
|
227
|
+
visit();
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
visit();
|
|
232
|
+
return { contaminatedIds, reasons };
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const contaminationFindingForId = (
|
|
236
|
+
id: string,
|
|
237
|
+
declaredDraftIds: ReadonlySet<string>,
|
|
238
|
+
reasons: ReadonlyMap<string, DraftReason>,
|
|
239
|
+
contours: ReadonlyMap<string, AnyContour>,
|
|
240
|
+
trails: ReadonlyMap<string, AnyTrail>,
|
|
241
|
+
signals: ReadonlyMap<string, AnySignal>,
|
|
242
|
+
resources: ReadonlyMap<string, AnyResource>
|
|
243
|
+
): DraftFinding | undefined => {
|
|
244
|
+
if (declaredDraftIds.has(id)) {
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const reason = reasons.get(id);
|
|
249
|
+
if (reason === undefined) {
|
|
250
|
+
return undefined;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return findingForContamination(
|
|
254
|
+
id,
|
|
255
|
+
nodeKind(id, contours, trails, signals, resources),
|
|
256
|
+
reason
|
|
257
|
+
);
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const collectFindings = (
|
|
261
|
+
declaredDraftIds: ReadonlySet<string>,
|
|
262
|
+
contaminatedIds: ReadonlySet<string>,
|
|
263
|
+
reasons: ReadonlyMap<string, DraftReason>,
|
|
264
|
+
contours: ReadonlyMap<string, AnyContour>,
|
|
265
|
+
trails: ReadonlyMap<string, AnyTrail>,
|
|
266
|
+
signals: ReadonlyMap<string, AnySignal>,
|
|
267
|
+
resources: ReadonlyMap<string, AnyResource>
|
|
268
|
+
): DraftFinding[] => [
|
|
269
|
+
...[...declaredDraftIds]
|
|
270
|
+
.toSorted()
|
|
271
|
+
.map((id) =>
|
|
272
|
+
findingForDraftId(id, nodeKind(id, contours, trails, signals, resources))
|
|
273
|
+
),
|
|
274
|
+
...[...contaminatedIds].toSorted().flatMap((id) => {
|
|
275
|
+
const finding = contaminationFindingForId(
|
|
276
|
+
id,
|
|
277
|
+
declaredDraftIds,
|
|
278
|
+
reasons,
|
|
279
|
+
contours,
|
|
280
|
+
trails,
|
|
281
|
+
signals,
|
|
282
|
+
resources
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
return finding === undefined ? [] : [finding];
|
|
286
|
+
}),
|
|
287
|
+
];
|
|
288
|
+
|
|
289
|
+
const collectDependencies = (topo: Topo): DraftDependency[] => [
|
|
290
|
+
...[...topo.contours.values()].flatMap(contourDependencies),
|
|
291
|
+
...[...topo.trails.values()].flatMap(trailDependencies),
|
|
292
|
+
...[...topo.signals.values()].flatMap(signalDependencies),
|
|
293
|
+
...[...topo.resources.values()].flatMap(resourceDependencies),
|
|
294
|
+
];
|
|
295
|
+
|
|
296
|
+
export const deriveDraftReport = (topo: Topo): DraftReport => {
|
|
297
|
+
const declaredDraftIds = collectDeclaredDraftIds(topo);
|
|
298
|
+
const dependencies = collectDependencies(topo);
|
|
299
|
+
const { contaminatedIds, reasons } = propagateContaminatedIds(
|
|
300
|
+
declaredDraftIds,
|
|
301
|
+
dependencies
|
|
302
|
+
);
|
|
303
|
+
const findings = collectFindings(
|
|
304
|
+
declaredDraftIds,
|
|
305
|
+
contaminatedIds,
|
|
306
|
+
reasons,
|
|
307
|
+
topo.contours,
|
|
308
|
+
topo.trails,
|
|
309
|
+
topo.signals,
|
|
310
|
+
topo.resources
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
contaminatedIds,
|
|
315
|
+
declaredDraftIds,
|
|
316
|
+
dependencies,
|
|
317
|
+
findings,
|
|
318
|
+
};
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
export const validateDraftFreeTopo = (
|
|
322
|
+
topo: Topo
|
|
323
|
+
): Result<void, ValidationError> => {
|
|
324
|
+
const analysis = deriveDraftReport(topo);
|
|
325
|
+
|
|
326
|
+
if (analysis.findings.length === 0) {
|
|
327
|
+
return Result.ok();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return Result.err(
|
|
331
|
+
new ValidationError(
|
|
332
|
+
`Established topo validation failed with ${analysis.findings.length} draft issue(s)`,
|
|
333
|
+
{
|
|
334
|
+
context: { issues: analysis.findings },
|
|
335
|
+
}
|
|
336
|
+
)
|
|
337
|
+
);
|
|
338
|
+
};
|
package/src/errors.ts
CHANGED
|
@@ -10,17 +10,20 @@
|
|
|
10
10
|
// Category
|
|
11
11
|
// ---------------------------------------------------------------------------
|
|
12
12
|
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
13
|
+
export const errorCategories = [
|
|
14
|
+
'validation',
|
|
15
|
+
'not_found',
|
|
16
|
+
'conflict',
|
|
17
|
+
'permission',
|
|
18
|
+
'timeout',
|
|
19
|
+
'rate_limit',
|
|
20
|
+
'network',
|
|
21
|
+
'internal',
|
|
22
|
+
'auth',
|
|
23
|
+
'cancelled',
|
|
24
|
+
] as const;
|
|
25
|
+
|
|
26
|
+
export type ErrorCategory = (typeof errorCategories)[number];
|
|
24
27
|
|
|
25
28
|
// ---------------------------------------------------------------------------
|
|
26
29
|
// Base class
|
|
@@ -109,6 +112,11 @@ export class NetworkError extends TrailsError {
|
|
|
109
112
|
}
|
|
110
113
|
|
|
111
114
|
export class InternalError extends TrailsError {
|
|
115
|
+
readonly category: ErrorCategory = 'internal';
|
|
116
|
+
readonly retryable = false as const;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export class DerivationError extends TrailsError {
|
|
112
120
|
readonly category = 'internal' as const;
|
|
113
121
|
readonly retryable = false as const;
|
|
114
122
|
}
|
|
@@ -123,11 +131,47 @@ export class CancelledError extends TrailsError {
|
|
|
123
131
|
readonly retryable = false as const;
|
|
124
132
|
}
|
|
125
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Returned when a detour exhausts all recovery attempts.
|
|
136
|
+
*
|
|
137
|
+
* Inherits the wrapped error's category for trailhead mapping (e.g. a
|
|
138
|
+
* `RetryExhaustedError<ConflictError>` maps to HTTP 409), but always
|
|
139
|
+
* sets `retryable = false` to prevent amplification across `ctx.cross()`
|
|
140
|
+
* boundaries or stacked layers.
|
|
141
|
+
*/
|
|
142
|
+
export class RetryExhaustedError<
|
|
143
|
+
TErr extends TrailsError = TrailsError,
|
|
144
|
+
> extends InternalError {
|
|
145
|
+
readonly category: ErrorCategory;
|
|
146
|
+
readonly cause: TErr;
|
|
147
|
+
|
|
148
|
+
/** Number of recovery attempts made before exhaustion. */
|
|
149
|
+
readonly attempts: number;
|
|
150
|
+
|
|
151
|
+
/** Name of the detour whose recovery was exhausted. */
|
|
152
|
+
readonly detour: string;
|
|
153
|
+
|
|
154
|
+
constructor(
|
|
155
|
+
wrapped: TErr,
|
|
156
|
+
metadata: { readonly attempts: number; readonly detour: string }
|
|
157
|
+
) {
|
|
158
|
+
super(
|
|
159
|
+
`Recovery exhausted after ${metadata.attempts} attempts: ${wrapped.message}`,
|
|
160
|
+
{ cause: wrapped }
|
|
161
|
+
);
|
|
162
|
+
this.cause = wrapped;
|
|
163
|
+
this.attempts = metadata.attempts;
|
|
164
|
+
this.detour = metadata.detour;
|
|
165
|
+
// Dynamic — inherited from wrapped error at construction time.
|
|
166
|
+
this.category = wrapped.category;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
126
170
|
// ---------------------------------------------------------------------------
|
|
127
171
|
// Taxonomy maps
|
|
128
172
|
// ---------------------------------------------------------------------------
|
|
129
173
|
|
|
130
|
-
export const exitCodeMap
|
|
174
|
+
export const exitCodeMap = {
|
|
131
175
|
auth: 9,
|
|
132
176
|
cancelled: 130,
|
|
133
177
|
conflict: 3,
|
|
@@ -138,9 +182,9 @@ export const exitCodeMap: Record<ErrorCategory, number> = {
|
|
|
138
182
|
rate_limit: 6,
|
|
139
183
|
timeout: 5,
|
|
140
184
|
validation: 1,
|
|
141
|
-
} as const
|
|
185
|
+
} as const satisfies Record<ErrorCategory, number>;
|
|
142
186
|
|
|
143
|
-
export const statusCodeMap
|
|
187
|
+
export const statusCodeMap = {
|
|
144
188
|
auth: 401,
|
|
145
189
|
cancelled: 499,
|
|
146
190
|
conflict: 409,
|
|
@@ -151,9 +195,9 @@ export const statusCodeMap: Record<ErrorCategory, number> = {
|
|
|
151
195
|
rate_limit: 429,
|
|
152
196
|
timeout: 504,
|
|
153
197
|
validation: 400,
|
|
154
|
-
} as const
|
|
198
|
+
} as const satisfies Record<ErrorCategory, number>;
|
|
155
199
|
|
|
156
|
-
export const jsonRpcCodeMap
|
|
200
|
+
export const jsonRpcCodeMap = {
|
|
157
201
|
auth: -32_600,
|
|
158
202
|
cancelled: -32_603,
|
|
159
203
|
conflict: -32_603,
|
|
@@ -164,7 +208,7 @@ export const jsonRpcCodeMap: Record<ErrorCategory, number> = {
|
|
|
164
208
|
rate_limit: -32_603,
|
|
165
209
|
timeout: -32_603,
|
|
166
210
|
validation: -32_602,
|
|
167
|
-
} as const
|
|
211
|
+
} as const satisfies Record<ErrorCategory, number>;
|
|
168
212
|
|
|
169
213
|
export const retryableMap: Record<ErrorCategory, boolean> = {
|
|
170
214
|
auth: false,
|
|
@@ -190,7 +234,7 @@ export const isTrailsError = (error?: unknown): error is TrailsError =>
|
|
|
190
234
|
/** Returns true if the error is retryable (TrailsError with retryable category). */
|
|
191
235
|
export const isRetryable = (error: Error): boolean => {
|
|
192
236
|
if (isTrailsError(error)) {
|
|
193
|
-
return
|
|
237
|
+
return error.retryable;
|
|
194
238
|
}
|
|
195
239
|
return false;
|
|
196
240
|
};
|