@ontrails/trails 1.0.0-beta.2 → 1.0.0-beta.22
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 +647 -0
- package/README.md +26 -0
- package/package.json +28 -7
- package/src/app.ts +86 -2
- package/src/clack.ts +22 -0
- package/src/cli.ts +330 -11
- package/src/completions.ts +240 -0
- package/src/lifecycle-source-io.ts +33 -0
- package/src/load-app-mirror.ts +202 -0
- package/src/local-state-io.ts +153 -0
- package/src/mcp-app.ts +30 -0
- package/src/mcp-options.ts +77 -0
- package/src/mcp.ts +8 -0
- package/src/project-writes.ts +377 -0
- package/src/release/bindings.ts +39 -0
- package/src/release/check.ts +818 -0
- package/src/release/config.ts +63 -0
- package/src/release/contract-facts.ts +425 -0
- package/src/release/index.ts +85 -0
- package/src/release/native-bun-publish.ts +651 -0
- package/src/release/native-bun-registry.ts +350 -0
- package/src/release/packed-artifacts-smoke.ts +236 -0
- package/src/release/smoke.ts +46 -0
- package/src/release/wayfinder-dogfood-smoke.ts +226 -0
- package/src/retired-topo-command.ts +36 -0
- package/src/run-adapter-check.ts +76 -0
- package/src/run-collision.ts +126 -0
- package/src/run-completions-install.ts +179 -0
- package/src/run-example.ts +149 -0
- package/src/run-examples.ts +148 -0
- package/src/run-quiet.ts +75 -0
- package/src/run-release-check.ts +74 -0
- package/src/run-trace.ts +273 -0
- package/src/run-warden.ts +39 -0
- package/src/run-watch.ts +432 -0
- package/src/scaffold-version-sync.ts +183 -0
- package/src/scaffold-versions.generated.ts +12 -0
- package/src/trails/adapter-check.ts +244 -0
- package/src/trails/add-surface.ts +94 -40
- package/src/trails/add-trail.ts +79 -41
- package/src/trails/add-verify.ts +95 -25
- package/src/trails/compile.ts +67 -0
- package/src/trails/completions-complete.ts +165 -0
- package/src/trails/completions.ts +47 -0
- package/src/trails/create-adapter.ts +1084 -0
- package/src/trails/create-scaffold.ts +399 -104
- package/src/trails/create-versions.ts +62 -0
- package/src/trails/create.ts +185 -71
- package/src/trails/deprecate.ts +59 -0
- package/src/trails/dev-clean.ts +82 -0
- package/src/trails/dev-reset.ts +50 -0
- package/src/trails/dev-stats.ts +72 -0
- package/src/trails/dev-support.ts +340 -0
- package/src/trails/doctor.ts +56 -0
- package/src/trails/draft-promote.ts +949 -0
- package/src/trails/guide.ts +74 -68
- package/src/trails/load-app.ts +1143 -15
- package/src/trails/project.ts +17 -3
- package/src/trails/release-check.ts +104 -0
- package/src/trails/release-smoke.ts +48 -0
- package/src/trails/revise.ts +53 -0
- package/src/trails/root-dir.ts +21 -0
- package/src/trails/run-example.ts +491 -0
- package/src/trails/run-examples.ts +145 -0
- package/src/trails/run.ts +410 -0
- package/src/trails/scaffold-json.ts +58 -0
- package/src/trails/survey.ts +881 -226
- package/src/trails/topo-activation.ts +385 -0
- package/src/trails/topo-constants.ts +2 -0
- package/src/trails/topo-history.ts +47 -0
- package/src/trails/topo-output-schemas.ts +248 -0
- package/src/trails/topo-pin.ts +52 -0
- package/src/trails/topo-read-support.ts +313 -0
- package/src/trails/topo-reports.ts +807 -0
- package/src/trails/topo-store-support.ts +174 -0
- package/src/trails/topo-support.ts +220 -0
- package/src/trails/topo-unpin.ts +61 -0
- package/src/trails/topo.ts +106 -0
- package/src/trails/validate.ts +38 -0
- package/src/trails/version-lifecycle-support.ts +945 -0
- package/src/trails/warden-guide.ts +129 -0
- package/src/trails/warden.ts +165 -58
- package/src/versions.ts +31 -0
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-lint.log +0 -3
- package/.turbo/turbo-typecheck.log +0 -1
- package/__tests__/examples.test.ts +0 -6
- package/dist/bin/trails.d.ts +0 -3
- package/dist/bin/trails.d.ts.map +0 -1
- package/dist/bin/trails.js +0 -4
- package/dist/bin/trails.js.map +0 -1
- package/dist/src/app.d.ts +0 -2
- package/dist/src/app.d.ts.map +0 -1
- package/dist/src/app.js +0 -11
- package/dist/src/app.js.map +0 -1
- package/dist/src/clack.d.ts +0 -9
- package/dist/src/clack.d.ts.map +0 -1
- package/dist/src/clack.js +0 -62
- package/dist/src/clack.js.map +0 -1
- package/dist/src/cli.d.ts +0 -2
- package/dist/src/cli.d.ts.map +0 -1
- package/dist/src/cli.js +0 -13
- package/dist/src/cli.js.map +0 -1
- package/dist/src/trails/add-surface.d.ts +0 -13
- package/dist/src/trails/add-surface.d.ts.map +0 -1
- package/dist/src/trails/add-surface.js +0 -88
- package/dist/src/trails/add-surface.js.map +0 -1
- package/dist/src/trails/add-trail.d.ts +0 -11
- package/dist/src/trails/add-trail.d.ts.map +0 -1
- package/dist/src/trails/add-trail.js +0 -85
- package/dist/src/trails/add-trail.js.map +0 -1
- package/dist/src/trails/add-verify.d.ts +0 -10
- package/dist/src/trails/add-verify.d.ts.map +0 -1
- package/dist/src/trails/add-verify.js +0 -67
- package/dist/src/trails/add-verify.js.map +0 -1
- package/dist/src/trails/create-scaffold.d.ts +0 -15
- package/dist/src/trails/create-scaffold.d.ts.map +0 -1
- package/dist/src/trails/create-scaffold.js +0 -288
- package/dist/src/trails/create-scaffold.js.map +0 -1
- package/dist/src/trails/create.d.ts +0 -22
- package/dist/src/trails/create.d.ts.map +0 -1
- package/dist/src/trails/create.js +0 -121
- package/dist/src/trails/create.js.map +0 -1
- package/dist/src/trails/guide.d.ts +0 -11
- package/dist/src/trails/guide.d.ts.map +0 -1
- package/dist/src/trails/guide.js +0 -80
- package/dist/src/trails/guide.js.map +0 -1
- package/dist/src/trails/load-app.d.ts +0 -4
- package/dist/src/trails/load-app.d.ts.map +0 -1
- package/dist/src/trails/load-app.js +0 -24
- package/dist/src/trails/load-app.js.map +0 -1
- package/dist/src/trails/project.d.ts +0 -8
- package/dist/src/trails/project.d.ts.map +0 -1
- package/dist/src/trails/project.js +0 -43
- package/dist/src/trails/project.js.map +0 -1
- package/dist/src/trails/survey.d.ts +0 -33
- package/dist/src/trails/survey.d.ts.map +0 -1
- package/dist/src/trails/survey.js +0 -225
- package/dist/src/trails/survey.js.map +0 -1
- package/dist/src/trails/warden.d.ts +0 -19
- package/dist/src/trails/warden.d.ts.map +0 -1
- package/dist/src/trails/warden.js +0 -88
- package/dist/src/trails/warden.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/src/__tests__/create.test.ts +0 -349
- package/src/__tests__/guide.test.ts +0 -91
- package/src/__tests__/load-app.test.ts +0 -15
- package/src/__tests__/survey.test.ts +0 -161
- package/src/__tests__/warden.test.ts +0 -74
- package/tsconfig.json +0 -9
|
@@ -0,0 +1,807 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DETOUR_MAX_ATTEMPTS_CAP,
|
|
3
|
+
deriveCliPath,
|
|
4
|
+
filterSurfaceTrails,
|
|
5
|
+
isArchivedTrailVersionEntry,
|
|
6
|
+
zodToJsonSchema,
|
|
7
|
+
} from '@ontrails/core';
|
|
8
|
+
import type { AnyTrail, Signal, Topo } from '@ontrails/core';
|
|
9
|
+
import { deriveHttpMethod } from '@ontrails/http';
|
|
10
|
+
import type { HttpMethod } from '@ontrails/http';
|
|
11
|
+
import { deriveToolName } from '@ontrails/mcp';
|
|
12
|
+
import { deriveTopoGraph } from '@ontrails/topographer';
|
|
13
|
+
import type {
|
|
14
|
+
JsonSchema,
|
|
15
|
+
TopoGraph,
|
|
16
|
+
TopoGraphActivationEdge,
|
|
17
|
+
TopoGraphEntry,
|
|
18
|
+
TopoGraphFieldOverride,
|
|
19
|
+
TopoGraphLayerReference,
|
|
20
|
+
TopoGraphVersionEntry,
|
|
21
|
+
} from '@ontrails/topographer';
|
|
22
|
+
import { z } from 'zod';
|
|
23
|
+
|
|
24
|
+
import type {
|
|
25
|
+
ActivationChainReport,
|
|
26
|
+
ActivationEdgeReport,
|
|
27
|
+
ActivationGraphReport,
|
|
28
|
+
ActivationOverviewReport,
|
|
29
|
+
ActivationSourceReport,
|
|
30
|
+
SignalActivationRelations,
|
|
31
|
+
} from './topo-activation.js';
|
|
32
|
+
import {
|
|
33
|
+
deriveActivationGraph,
|
|
34
|
+
deriveDeclaredTrailActivation,
|
|
35
|
+
deriveSignalActivationRelations,
|
|
36
|
+
} from './topo-activation.js';
|
|
37
|
+
import { REPORT_CONTRACT_VERSION, REPORT_VERSION } from './topo-constants.js';
|
|
38
|
+
|
|
39
|
+
export type {
|
|
40
|
+
ActivationChainReport,
|
|
41
|
+
ActivationEdgeReport,
|
|
42
|
+
ActivationGraphReport,
|
|
43
|
+
ActivationOverviewReport,
|
|
44
|
+
ActivationSourceReport,
|
|
45
|
+
SignalActivationRelations,
|
|
46
|
+
TrailActivationReport,
|
|
47
|
+
} from './topo-activation.js';
|
|
48
|
+
|
|
49
|
+
export const briefReportSchema = z.object({
|
|
50
|
+
contractVersion: z.string(),
|
|
51
|
+
features: z.object({
|
|
52
|
+
detours: z.boolean(),
|
|
53
|
+
examples: z.boolean(),
|
|
54
|
+
outputSchemas: z.boolean(),
|
|
55
|
+
resources: z.boolean(),
|
|
56
|
+
signals: z.boolean(),
|
|
57
|
+
}),
|
|
58
|
+
name: z.string(),
|
|
59
|
+
resources: z.number(),
|
|
60
|
+
signals: z.number(),
|
|
61
|
+
trails: z.number(),
|
|
62
|
+
version: z.string(),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
type BriefReportShape = z.infer<typeof briefReportSchema>;
|
|
66
|
+
|
|
67
|
+
export type BriefReport = Readonly<
|
|
68
|
+
Omit<BriefReportShape, 'features'> & {
|
|
69
|
+
readonly features: Readonly<BriefReportShape['features']>;
|
|
70
|
+
}
|
|
71
|
+
>;
|
|
72
|
+
|
|
73
|
+
export type SurfaceLayerKey = 'cli' | 'http' | 'mcp';
|
|
74
|
+
|
|
75
|
+
export type SurfaceLayerNames = Readonly<
|
|
76
|
+
Record<SurfaceLayerKey, readonly string[]>
|
|
77
|
+
>;
|
|
78
|
+
|
|
79
|
+
export type ShippedSurfaceKey = 'cli' | 'mcp' | 'http';
|
|
80
|
+
|
|
81
|
+
export type SurfaceProjectionSource = 'authored' | 'default-derived';
|
|
82
|
+
|
|
83
|
+
export type ShippedSurfaceProjection =
|
|
84
|
+
| {
|
|
85
|
+
readonly commandPath: readonly string[];
|
|
86
|
+
readonly derivedName: string;
|
|
87
|
+
readonly method: null;
|
|
88
|
+
readonly source: SurfaceProjectionSource;
|
|
89
|
+
readonly surface: 'cli';
|
|
90
|
+
readonly trailId: string;
|
|
91
|
+
}
|
|
92
|
+
| {
|
|
93
|
+
readonly derivedName: string;
|
|
94
|
+
readonly method: null;
|
|
95
|
+
readonly source: SurfaceProjectionSource;
|
|
96
|
+
readonly surface: 'mcp';
|
|
97
|
+
readonly toolName: string;
|
|
98
|
+
readonly trailId: string;
|
|
99
|
+
}
|
|
100
|
+
| {
|
|
101
|
+
readonly derivedName: string;
|
|
102
|
+
readonly method: HttpMethod;
|
|
103
|
+
readonly path: string;
|
|
104
|
+
readonly source: SurfaceProjectionSource;
|
|
105
|
+
readonly surface: 'http';
|
|
106
|
+
readonly trailId: string;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export interface ShippedSurfaceInventoryReport {
|
|
110
|
+
readonly count: number;
|
|
111
|
+
readonly excludedSurfaces: readonly {
|
|
112
|
+
readonly reason: string;
|
|
113
|
+
readonly status: 'planned';
|
|
114
|
+
readonly surface: 'websocket';
|
|
115
|
+
}[];
|
|
116
|
+
readonly projections: readonly ShippedSurfaceProjection[];
|
|
117
|
+
readonly shippedSurfaces: readonly ShippedSurfaceKey[];
|
|
118
|
+
readonly trails: readonly {
|
|
119
|
+
readonly explicitSurfaces: readonly string[];
|
|
120
|
+
readonly projections: readonly ShippedSurfaceProjection[];
|
|
121
|
+
readonly trailId: string;
|
|
122
|
+
}[];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
type TopoGraphContourEntry = TopoGraphEntry & { readonly kind: 'contour' };
|
|
126
|
+
|
|
127
|
+
export interface TrailDetailOptions {
|
|
128
|
+
readonly surfaceLayerNames?: Partial<SurfaceLayerNames> | undefined;
|
|
129
|
+
readonly topoGraph?: TopoGraph | undefined;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface SurveyListReport {
|
|
133
|
+
readonly activation: ActivationOverviewReport;
|
|
134
|
+
readonly count: number;
|
|
135
|
+
readonly entries: readonly {
|
|
136
|
+
readonly activatedBy: readonly string[];
|
|
137
|
+
readonly activates: readonly string[];
|
|
138
|
+
readonly examples: number;
|
|
139
|
+
readonly id: string;
|
|
140
|
+
readonly kind: string;
|
|
141
|
+
readonly safety: string;
|
|
142
|
+
}[];
|
|
143
|
+
readonly resourceCount: number;
|
|
144
|
+
readonly resources: readonly {
|
|
145
|
+
readonly description: string | null;
|
|
146
|
+
readonly health: 'available' | 'none';
|
|
147
|
+
readonly id: string;
|
|
148
|
+
readonly kind: 'resource';
|
|
149
|
+
readonly lifetime: 'singleton';
|
|
150
|
+
readonly usedBy: readonly string[];
|
|
151
|
+
}[];
|
|
152
|
+
readonly signalCount: number;
|
|
153
|
+
readonly signals: readonly {
|
|
154
|
+
readonly consumers: readonly string[];
|
|
155
|
+
readonly description: string | null;
|
|
156
|
+
readonly examples: number;
|
|
157
|
+
readonly from: readonly string[];
|
|
158
|
+
readonly id: string;
|
|
159
|
+
readonly kind: 'signal';
|
|
160
|
+
readonly payloadSchema: boolean;
|
|
161
|
+
readonly producers: readonly string[];
|
|
162
|
+
}[];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export interface TrailDetailReport {
|
|
166
|
+
readonly activatedBy: readonly string[];
|
|
167
|
+
readonly activates: readonly string[];
|
|
168
|
+
readonly activationChains: readonly ActivationChainReport[];
|
|
169
|
+
readonly activationContext: {
|
|
170
|
+
readonly edgeCount: number;
|
|
171
|
+
readonly sourceCount: number;
|
|
172
|
+
readonly sourceKeys: readonly string[];
|
|
173
|
+
readonly trailIds: readonly string[];
|
|
174
|
+
};
|
|
175
|
+
readonly activationEdges: readonly ActivationEdgeReport[];
|
|
176
|
+
readonly activationSources: readonly ActivationSourceReport[];
|
|
177
|
+
readonly cli: {
|
|
178
|
+
readonly path: readonly string[];
|
|
179
|
+
} | null;
|
|
180
|
+
/**
|
|
181
|
+
* Composed layer names visible at the survey boundary.
|
|
182
|
+
*
|
|
183
|
+
* Reports the names of typed layers that wrap this trail at execution time,
|
|
184
|
+
* in the framework's composition order: `topo → surface → trail`
|
|
185
|
+
* (outermost-first). Surface-scope layers are keyed by surface because
|
|
186
|
+
* each surface owns its own attachment set.
|
|
187
|
+
*/
|
|
188
|
+
readonly composedLayers: {
|
|
189
|
+
readonly topo: readonly string[];
|
|
190
|
+
readonly surface: SurfaceLayerNames;
|
|
191
|
+
readonly trail: readonly string[];
|
|
192
|
+
};
|
|
193
|
+
readonly contourDetails: readonly TopoGraphContourEntry[];
|
|
194
|
+
readonly contours: readonly string[];
|
|
195
|
+
readonly description: string | null;
|
|
196
|
+
readonly detours:
|
|
197
|
+
| readonly { readonly on: string; readonly maxAttempts: number }[]
|
|
198
|
+
| null;
|
|
199
|
+
readonly examples: readonly unknown[];
|
|
200
|
+
readonly fieldOverrides: readonly TopoGraphFieldOverride[];
|
|
201
|
+
readonly composes: readonly string[];
|
|
202
|
+
readonly fires: readonly string[];
|
|
203
|
+
readonly governance: Readonly<Record<string, unknown>> | null;
|
|
204
|
+
readonly id: string;
|
|
205
|
+
readonly input: JsonSchema | null;
|
|
206
|
+
readonly intent: 'read' | 'write' | 'destroy';
|
|
207
|
+
readonly kind: 'trail';
|
|
208
|
+
readonly layers: readonly TopoGraphLayerReference[];
|
|
209
|
+
readonly on: readonly string[];
|
|
210
|
+
readonly output: JsonSchema | null;
|
|
211
|
+
readonly pattern: string | null;
|
|
212
|
+
readonly safety: string;
|
|
213
|
+
readonly resources: readonly string[];
|
|
214
|
+
readonly surfaceProjections: readonly ShippedSurfaceProjection[];
|
|
215
|
+
readonly surfaces: readonly string[];
|
|
216
|
+
readonly supports: readonly number[];
|
|
217
|
+
readonly version: number | null;
|
|
218
|
+
readonly versions: Readonly<Record<string, TopoGraphVersionEntry>>;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export interface SignalDetailReport {
|
|
222
|
+
readonly consumers: readonly string[];
|
|
223
|
+
readonly description: string | null;
|
|
224
|
+
readonly examples: readonly unknown[];
|
|
225
|
+
readonly from: readonly string[];
|
|
226
|
+
readonly id: string;
|
|
227
|
+
readonly kind: 'signal';
|
|
228
|
+
/**
|
|
229
|
+
* The signal's payload schema (JSON Schema object), or `null` when the
|
|
230
|
+
* surface-map entry is missing for this signal. `null` is meaningful:
|
|
231
|
+
* it matches the list view's `payloadSchema: false` flag and lets
|
|
232
|
+
* consumers distinguish "schema not found" from "schema accepts any
|
|
233
|
+
* value" (the latter would be an empty object `{}`).
|
|
234
|
+
*/
|
|
235
|
+
readonly payload: Readonly<Record<string, unknown>> | null;
|
|
236
|
+
readonly producers: readonly string[];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const countLiveTrailVersionExamples = (trail: AnyTrail): number => {
|
|
240
|
+
let count = 0;
|
|
241
|
+
for (const entry of Object.values(trail.versions ?? {})) {
|
|
242
|
+
if (isArchivedTrailVersionEntry(entry)) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
count += entry.examples?.length ?? 0;
|
|
246
|
+
}
|
|
247
|
+
return count;
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
export const countTrailExamples = (trail: AnyTrail): number =>
|
|
251
|
+
(trail.examples?.length ?? 0) + countLiveTrailVersionExamples(trail);
|
|
252
|
+
|
|
253
|
+
const detectFeatures = (
|
|
254
|
+
app: Topo
|
|
255
|
+
): {
|
|
256
|
+
hasDetours: boolean;
|
|
257
|
+
hasExamples: boolean;
|
|
258
|
+
hasOutputSchemas: boolean;
|
|
259
|
+
hasResources: boolean;
|
|
260
|
+
} => {
|
|
261
|
+
const trails = [...app.trails.values()];
|
|
262
|
+
return {
|
|
263
|
+
hasDetours: trails.some((trail) => trail.detours.length > 0),
|
|
264
|
+
hasExamples: trails.some((trail) => countTrailExamples(trail) > 0),
|
|
265
|
+
hasOutputSchemas: trails.some((trail) => trail.output !== undefined),
|
|
266
|
+
hasResources: trails.some((trail) => trail.resources.length > 0),
|
|
267
|
+
};
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
export const deriveBriefReport = (app: Topo): BriefReport => {
|
|
271
|
+
const { hasDetours, hasExamples, hasOutputSchemas, hasResources } =
|
|
272
|
+
detectFeatures(app);
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
contractVersion: REPORT_CONTRACT_VERSION,
|
|
276
|
+
features: {
|
|
277
|
+
detours: hasDetours,
|
|
278
|
+
examples: hasExamples,
|
|
279
|
+
outputSchemas: hasOutputSchemas,
|
|
280
|
+
resources: hasResources,
|
|
281
|
+
signals: app.signals.size > 0,
|
|
282
|
+
},
|
|
283
|
+
name: app.name,
|
|
284
|
+
resources: app.resources.size,
|
|
285
|
+
signals: app.signals.size,
|
|
286
|
+
trails: app.trails.size,
|
|
287
|
+
version: REPORT_VERSION,
|
|
288
|
+
};
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const safetyLabel = (entry: {
|
|
292
|
+
intent?: 'read' | 'write' | 'destroy';
|
|
293
|
+
}): string => {
|
|
294
|
+
if (entry.intent === 'destroy') {
|
|
295
|
+
return 'destroy';
|
|
296
|
+
}
|
|
297
|
+
if (entry.intent === 'write') {
|
|
298
|
+
return 'write';
|
|
299
|
+
}
|
|
300
|
+
if (entry.intent === 'read') {
|
|
301
|
+
return 'read';
|
|
302
|
+
}
|
|
303
|
+
return '-';
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const buildResourceUsage = (
|
|
307
|
+
app: Topo
|
|
308
|
+
): ReadonlyMap<string, readonly string[]> => {
|
|
309
|
+
const usage = new Map<string, string[]>();
|
|
310
|
+
|
|
311
|
+
for (const trailDef of app.list()) {
|
|
312
|
+
for (const declaredResource of trailDef.resources) {
|
|
313
|
+
const users = usage.get(declaredResource.id) ?? [];
|
|
314
|
+
users.push(trailDef.id);
|
|
315
|
+
usage.set(declaredResource.id, users);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return new Map(
|
|
320
|
+
[...usage.entries()].map(([id, users]) => [id, users.toSorted()] as const)
|
|
321
|
+
);
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const resourceHealthStatus = (resource: {
|
|
325
|
+
health?: unknown;
|
|
326
|
+
}): 'available' | 'none' =>
|
|
327
|
+
resource.health === undefined ? 'none' : 'available';
|
|
328
|
+
|
|
329
|
+
export const deriveResourceDetail = (app: Topo, resourceId: string): object => {
|
|
330
|
+
const item = app.getResource(resourceId);
|
|
331
|
+
const usedBy = buildResourceUsage(app).get(resourceId) ?? [];
|
|
332
|
+
|
|
333
|
+
return {
|
|
334
|
+
description: item?.description ?? null,
|
|
335
|
+
health: item ? resourceHealthStatus(item) : 'none',
|
|
336
|
+
id: resourceId,
|
|
337
|
+
kind: 'resource',
|
|
338
|
+
lifetime: 'singleton',
|
|
339
|
+
usedBy,
|
|
340
|
+
};
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
const formatResourceList = (app: Topo): SurveyListReport['resources'] => {
|
|
344
|
+
const usage = buildResourceUsage(app);
|
|
345
|
+
return app
|
|
346
|
+
.listResources()
|
|
347
|
+
.map((resource) => ({
|
|
348
|
+
description: resource.description ?? null,
|
|
349
|
+
health: resourceHealthStatus(resource),
|
|
350
|
+
id: resource.id,
|
|
351
|
+
kind: resource.kind,
|
|
352
|
+
lifetime: 'singleton' as const,
|
|
353
|
+
usedBy: usage.get(resource.id) ?? [],
|
|
354
|
+
}))
|
|
355
|
+
.toSorted((a, b) => a.id.localeCompare(b.id));
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const formatSignalList = (
|
|
359
|
+
app: Topo,
|
|
360
|
+
relations: ReadonlyMap<string, SignalActivationRelations>
|
|
361
|
+
): SurveyListReport['signals'] =>
|
|
362
|
+
app
|
|
363
|
+
.listSignals()
|
|
364
|
+
.map((signalDef) => {
|
|
365
|
+
const related = relations.get(signalDef.id);
|
|
366
|
+
const consumers = related?.consumers ?? [];
|
|
367
|
+
const producers = related?.producers ?? [];
|
|
368
|
+
return {
|
|
369
|
+
consumers,
|
|
370
|
+
description: signalDef.description ?? null,
|
|
371
|
+
examples: signalDef.examples?.length ?? 0,
|
|
372
|
+
from: signalDef.from?.toSorted() ?? [],
|
|
373
|
+
id: signalDef.id,
|
|
374
|
+
kind: signalDef.kind,
|
|
375
|
+
// Mirror the store path (`mapSignalRow` in `topo-store-read.ts`) which
|
|
376
|
+
// derives this from the surface-map entry. SignalSpec<T> requires
|
|
377
|
+
// `payload` so this is `true` in practice today; the explicit check
|
|
378
|
+
// keeps the in-memory and store reports self-consistent if a future
|
|
379
|
+
// SignalSpec variant ever omits `payload`.
|
|
380
|
+
payloadSchema: signalDef.payload !== undefined,
|
|
381
|
+
producers,
|
|
382
|
+
};
|
|
383
|
+
})
|
|
384
|
+
.toSorted((a, b) => a.id.localeCompare(b.id));
|
|
385
|
+
|
|
386
|
+
export const deriveSurveyList = (app: Topo): SurveyListReport => {
|
|
387
|
+
const items = app.list();
|
|
388
|
+
const activation = deriveActivationGraph(app);
|
|
389
|
+
const entries = items.map((item) => {
|
|
390
|
+
const trailActivation =
|
|
391
|
+
activation.trails.get(item.id) ?? deriveDeclaredTrailActivation(item);
|
|
392
|
+
const safety = safetyLabel(
|
|
393
|
+
item as unknown as { intent?: 'read' | 'write' | 'destroy' }
|
|
394
|
+
);
|
|
395
|
+
const examples = countTrailExamples(item);
|
|
396
|
+
|
|
397
|
+
return {
|
|
398
|
+
activatedBy: trailActivation.activatedBy,
|
|
399
|
+
activates: trailActivation.activates,
|
|
400
|
+
examples,
|
|
401
|
+
id: item.id,
|
|
402
|
+
kind: item.kind,
|
|
403
|
+
safety,
|
|
404
|
+
};
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
const resources = formatResourceList(app);
|
|
408
|
+
const signals = formatSignalList(app, activation.signals);
|
|
409
|
+
|
|
410
|
+
return {
|
|
411
|
+
activation: activation.overview,
|
|
412
|
+
count: items.length,
|
|
413
|
+
entries,
|
|
414
|
+
resourceCount: resources.length,
|
|
415
|
+
resources,
|
|
416
|
+
signalCount: signals.length,
|
|
417
|
+
signals,
|
|
418
|
+
};
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
export const deriveSignalDetail = (
|
|
422
|
+
app: Topo,
|
|
423
|
+
signalId: string,
|
|
424
|
+
activationGraph?: ActivationGraphReport | undefined
|
|
425
|
+
): SignalDetailReport | undefined => {
|
|
426
|
+
const signalDef = app.signals.get(signalId) as Signal<unknown> | undefined;
|
|
427
|
+
if (signalDef === undefined) {
|
|
428
|
+
return undefined;
|
|
429
|
+
}
|
|
430
|
+
const related =
|
|
431
|
+
activationGraph?.signals.get(signalId) ??
|
|
432
|
+
deriveSignalActivationRelations(app, signalId);
|
|
433
|
+
|
|
434
|
+
return {
|
|
435
|
+
consumers: [...related.consumers],
|
|
436
|
+
description: signalDef.description ?? null,
|
|
437
|
+
examples: [...(signalDef.examples ?? [])],
|
|
438
|
+
from: signalDef.from?.toSorted() ?? [],
|
|
439
|
+
id: signalDef.id,
|
|
440
|
+
kind: 'signal',
|
|
441
|
+
payload: zodToJsonSchema(signalDef.payload),
|
|
442
|
+
producers: [...related.producers],
|
|
443
|
+
};
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
const emptySurfaceLayerNames = (): SurfaceLayerNames => ({
|
|
447
|
+
cli: [],
|
|
448
|
+
http: [],
|
|
449
|
+
mcp: [],
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
const normalizeSurfaceLayerNames = (
|
|
453
|
+
names?: Partial<SurfaceLayerNames> | undefined
|
|
454
|
+
): SurfaceLayerNames => {
|
|
455
|
+
const base = emptySurfaceLayerNames();
|
|
456
|
+
if (names === undefined) {
|
|
457
|
+
return base;
|
|
458
|
+
}
|
|
459
|
+
return {
|
|
460
|
+
cli: names.cli ?? base.cli,
|
|
461
|
+
http: names.http ?? base.http,
|
|
462
|
+
mcp: names.mcp ?? base.mcp,
|
|
463
|
+
};
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
const emptyActivationContext = (): TrailDetailReport['activationContext'] => ({
|
|
467
|
+
edgeCount: 0,
|
|
468
|
+
sourceCount: 0,
|
|
469
|
+
sourceKeys: [],
|
|
470
|
+
trailIds: [],
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
const activationContextFromTopoGraph = (
|
|
474
|
+
topoGraph: TopoGraph | undefined,
|
|
475
|
+
trailId: string,
|
|
476
|
+
fallbackEdges: readonly TopoGraphActivationEdge[]
|
|
477
|
+
): TrailDetailReport['activationContext'] => {
|
|
478
|
+
const edges =
|
|
479
|
+
topoGraph?.activationGraph.edges.filter(
|
|
480
|
+
(edge) => edge.trailId === trailId
|
|
481
|
+
) ?? fallbackEdges;
|
|
482
|
+
if (edges.length === 0) {
|
|
483
|
+
return emptyActivationContext();
|
|
484
|
+
}
|
|
485
|
+
return {
|
|
486
|
+
edgeCount: edges.length,
|
|
487
|
+
sourceCount: new Set(edges.map((edge) => edge.sourceKey)).size,
|
|
488
|
+
sourceKeys: [...new Set(edges.map((edge) => edge.sourceKey))].toSorted(),
|
|
489
|
+
trailIds: [...new Set(edges.map((edge) => edge.trailId))].toSorted(),
|
|
490
|
+
};
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
const findTopoEntry = (
|
|
494
|
+
topoGraph: TopoGraph | undefined,
|
|
495
|
+
id: string,
|
|
496
|
+
kind: TopoGraphEntry['kind']
|
|
497
|
+
): TopoGraphEntry | undefined =>
|
|
498
|
+
topoGraph?.entries.find((entry) => entry.id === id && entry.kind === kind);
|
|
499
|
+
|
|
500
|
+
const trailActivationEdgesFromTopoGraph = (
|
|
501
|
+
topoGraph: TopoGraph | undefined,
|
|
502
|
+
trailId: string,
|
|
503
|
+
fallback: readonly TopoGraphActivationEdge[]
|
|
504
|
+
): readonly TopoGraphActivationEdge[] =>
|
|
505
|
+
topoGraph?.activationGraph.edges.filter((edge) => edge.trailId === trailId) ??
|
|
506
|
+
fallback;
|
|
507
|
+
|
|
508
|
+
const SHIPPED_SURFACES = ['cli', 'mcp', 'http'] as const;
|
|
509
|
+
|
|
510
|
+
const PLANNED_SURFACE_EXCLUSIONS = [
|
|
511
|
+
{
|
|
512
|
+
reason: 'WebSocket is planned, but no public package or API ships yet.',
|
|
513
|
+
status: 'planned' as const,
|
|
514
|
+
surface: 'websocket' as const,
|
|
515
|
+
},
|
|
516
|
+
] as const;
|
|
517
|
+
|
|
518
|
+
const surfaceOrder = (surface: ShippedSurfaceKey): number =>
|
|
519
|
+
SHIPPED_SURFACES.indexOf(surface);
|
|
520
|
+
|
|
521
|
+
const sortSurfaceProjections = (
|
|
522
|
+
projections: readonly ShippedSurfaceProjection[]
|
|
523
|
+
): readonly ShippedSurfaceProjection[] =>
|
|
524
|
+
projections.toSorted(
|
|
525
|
+
(a, b) =>
|
|
526
|
+
a.trailId.localeCompare(b.trailId) ||
|
|
527
|
+
surfaceOrder(a.surface) - surfaceOrder(b.surface)
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
const explicitSurfacesForEntry = (
|
|
531
|
+
entry: TopoGraphEntry | undefined
|
|
532
|
+
): readonly string[] => entry?.surfaces ?? [];
|
|
533
|
+
|
|
534
|
+
const projectionSource = (
|
|
535
|
+
entry: TopoGraphEntry | undefined,
|
|
536
|
+
surface: ShippedSurfaceKey
|
|
537
|
+
): SurfaceProjectionSource =>
|
|
538
|
+
explicitSurfacesForEntry(entry).includes(surface)
|
|
539
|
+
? 'authored'
|
|
540
|
+
: 'default-derived';
|
|
541
|
+
|
|
542
|
+
const deriveHttpPath = (trailId: string): string =>
|
|
543
|
+
`/${trailId.replaceAll('.', '/')}`;
|
|
544
|
+
|
|
545
|
+
const isSurfaceEligibleTrail = (app: Topo, trail: AnyTrail): boolean =>
|
|
546
|
+
filterSurfaceTrails([trail]).length > 0 &&
|
|
547
|
+
app.trails.get(trail.id) !== undefined;
|
|
548
|
+
|
|
549
|
+
export const deriveShippedSurfaceProjectionsForTrail = (
|
|
550
|
+
app: Topo,
|
|
551
|
+
trail: AnyTrail,
|
|
552
|
+
topoGraph?: TopoGraph | undefined
|
|
553
|
+
): readonly ShippedSurfaceProjection[] => {
|
|
554
|
+
if (!isSurfaceEligibleTrail(app, trail)) {
|
|
555
|
+
return [];
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const entry = findTopoEntry(
|
|
559
|
+
topoGraph ?? deriveTopoGraph(app),
|
|
560
|
+
trail.id,
|
|
561
|
+
'trail'
|
|
562
|
+
);
|
|
563
|
+
const commandPath = deriveCliPath(trail.id);
|
|
564
|
+
const httpMethod = deriveHttpMethod(trail.intent);
|
|
565
|
+
const httpPath = deriveHttpPath(trail.id);
|
|
566
|
+
const mcpToolName = deriveToolName(app.name, trail.id);
|
|
567
|
+
|
|
568
|
+
return sortSurfaceProjections([
|
|
569
|
+
{
|
|
570
|
+
commandPath,
|
|
571
|
+
derivedName: commandPath.join(' '),
|
|
572
|
+
method: null,
|
|
573
|
+
source: projectionSource(entry, 'cli'),
|
|
574
|
+
surface: 'cli',
|
|
575
|
+
trailId: trail.id,
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
derivedName: mcpToolName,
|
|
579
|
+
method: null,
|
|
580
|
+
source: projectionSource(entry, 'mcp'),
|
|
581
|
+
surface: 'mcp',
|
|
582
|
+
toolName: mcpToolName,
|
|
583
|
+
trailId: trail.id,
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
derivedName: httpPath,
|
|
587
|
+
method: httpMethod,
|
|
588
|
+
path: httpPath,
|
|
589
|
+
source: projectionSource(entry, 'http'),
|
|
590
|
+
surface: 'http',
|
|
591
|
+
trailId: trail.id,
|
|
592
|
+
},
|
|
593
|
+
]);
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
const deriveFallbackSurfaceProjections = (
|
|
597
|
+
entry: TopoGraphEntry | undefined
|
|
598
|
+
): readonly ShippedSurfaceProjection[] => {
|
|
599
|
+
if (entry?.cli === undefined) {
|
|
600
|
+
return [];
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
return [
|
|
604
|
+
{
|
|
605
|
+
commandPath: entry.cli.path,
|
|
606
|
+
derivedName: entry.cli.path.join(' '),
|
|
607
|
+
method: null,
|
|
608
|
+
source: projectionSource(entry, 'cli'),
|
|
609
|
+
surface: 'cli',
|
|
610
|
+
trailId: entry.id,
|
|
611
|
+
},
|
|
612
|
+
];
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
export const deriveShippedSurfaceProjectionInventory = (
|
|
616
|
+
app: Topo
|
|
617
|
+
): ShippedSurfaceInventoryReport => {
|
|
618
|
+
const topoGraph = deriveTopoGraph(app);
|
|
619
|
+
const trails = filterSurfaceTrails(app.list()).map((trail) => {
|
|
620
|
+
const entry = findTopoEntry(topoGraph, trail.id, 'trail');
|
|
621
|
+
const projections = deriveShippedSurfaceProjectionsForTrail(
|
|
622
|
+
app,
|
|
623
|
+
trail,
|
|
624
|
+
topoGraph
|
|
625
|
+
);
|
|
626
|
+
|
|
627
|
+
return {
|
|
628
|
+
explicitSurfaces: explicitSurfacesForEntry(entry),
|
|
629
|
+
projections,
|
|
630
|
+
trailId: trail.id,
|
|
631
|
+
};
|
|
632
|
+
});
|
|
633
|
+
const projections = sortSurfaceProjections(
|
|
634
|
+
trails.flatMap((trail) => trail.projections)
|
|
635
|
+
);
|
|
636
|
+
|
|
637
|
+
return {
|
|
638
|
+
count: trails.length,
|
|
639
|
+
excludedSurfaces: PLANNED_SURFACE_EXCLUSIONS,
|
|
640
|
+
projections,
|
|
641
|
+
shippedSurfaces: SHIPPED_SURFACES,
|
|
642
|
+
trails: trails.toSorted((a, b) => a.trailId.localeCompare(b.trailId)),
|
|
643
|
+
};
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
const deriveResolvedSurfaceProjections = (
|
|
647
|
+
app: Topo | undefined,
|
|
648
|
+
trailId: string,
|
|
649
|
+
topoEntry: TopoGraphEntry | undefined,
|
|
650
|
+
topoGraph: TopoGraph | undefined
|
|
651
|
+
): readonly ShippedSurfaceProjection[] => {
|
|
652
|
+
if (app === undefined) {
|
|
653
|
+
return deriveFallbackSurfaceProjections(topoEntry);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const trail = app.trails.get(trailId);
|
|
657
|
+
return trail === undefined
|
|
658
|
+
? deriveFallbackSurfaceProjections(topoEntry)
|
|
659
|
+
: deriveShippedSurfaceProjectionsForTrail(app, trail, topoGraph);
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
const deriveResolvedTrailVersionDetail = (
|
|
663
|
+
topoEntry: TopoGraphEntry | undefined
|
|
664
|
+
): Pick<TrailDetailReport, 'supports' | 'version' | 'versions'> => ({
|
|
665
|
+
supports: topoEntry?.supports ?? [],
|
|
666
|
+
version: topoEntry?.version ?? null,
|
|
667
|
+
versions: topoEntry?.versions ?? {},
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
const deriveResolvedTrailGraphDetail = (
|
|
671
|
+
app: Topo | undefined,
|
|
672
|
+
trailId: string,
|
|
673
|
+
fallbackActivationEdges: readonly TopoGraphActivationEdge[],
|
|
674
|
+
topoGraphOverride?: TopoGraph | undefined
|
|
675
|
+
): Pick<
|
|
676
|
+
TrailDetailReport,
|
|
677
|
+
| 'activationContext'
|
|
678
|
+
| 'activationEdges'
|
|
679
|
+
| 'cli'
|
|
680
|
+
| 'contourDetails'
|
|
681
|
+
| 'contours'
|
|
682
|
+
| 'fieldOverrides'
|
|
683
|
+
| 'governance'
|
|
684
|
+
| 'input'
|
|
685
|
+
| 'layers'
|
|
686
|
+
| 'output'
|
|
687
|
+
| 'surfaceProjections'
|
|
688
|
+
| 'surfaces'
|
|
689
|
+
| 'supports'
|
|
690
|
+
| 'version'
|
|
691
|
+
| 'versions'
|
|
692
|
+
> => {
|
|
693
|
+
const topoGraph =
|
|
694
|
+
topoGraphOverride ?? (app === undefined ? undefined : deriveTopoGraph(app));
|
|
695
|
+
const topoEntry = findTopoEntry(topoGraph, trailId, 'trail');
|
|
696
|
+
const contours = topoEntry?.contours ?? [];
|
|
697
|
+
const contourDetails = contours
|
|
698
|
+
.map((contourId) => findTopoEntry(topoGraph, contourId, 'contour'))
|
|
699
|
+
.filter(
|
|
700
|
+
(entry): entry is TopoGraphContourEntry =>
|
|
701
|
+
entry !== undefined && entry.kind === 'contour'
|
|
702
|
+
);
|
|
703
|
+
|
|
704
|
+
return {
|
|
705
|
+
activationContext: activationContextFromTopoGraph(
|
|
706
|
+
topoGraph,
|
|
707
|
+
trailId,
|
|
708
|
+
fallbackActivationEdges
|
|
709
|
+
),
|
|
710
|
+
activationEdges: trailActivationEdgesFromTopoGraph(
|
|
711
|
+
topoGraph,
|
|
712
|
+
trailId,
|
|
713
|
+
fallbackActivationEdges
|
|
714
|
+
),
|
|
715
|
+
cli: topoEntry?.cli ?? null,
|
|
716
|
+
contourDetails,
|
|
717
|
+
contours,
|
|
718
|
+
fieldOverrides: topoEntry?.fieldOverrides ?? [],
|
|
719
|
+
governance: topoEntry?.governance ?? null,
|
|
720
|
+
input: topoEntry?.input ?? null,
|
|
721
|
+
layers: topoEntry?.layers ?? [],
|
|
722
|
+
output: topoEntry?.output ?? null,
|
|
723
|
+
surfaceProjections: deriveResolvedSurfaceProjections(
|
|
724
|
+
app,
|
|
725
|
+
trailId,
|
|
726
|
+
topoEntry,
|
|
727
|
+
topoGraph
|
|
728
|
+
),
|
|
729
|
+
surfaces: topoEntry?.surfaces ?? [],
|
|
730
|
+
...deriveResolvedTrailVersionDetail(topoEntry),
|
|
731
|
+
};
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
const formatTrailDetours = (item: AnyTrail): TrailDetailReport['detours'] =>
|
|
735
|
+
item.detours.length > 0
|
|
736
|
+
? item.detours.map((d) => ({
|
|
737
|
+
maxAttempts: Math.max(
|
|
738
|
+
1,
|
|
739
|
+
Math.min(d.maxAttempts ?? 1, DETOUR_MAX_ATTEMPTS_CAP)
|
|
740
|
+
),
|
|
741
|
+
on: d.on.name,
|
|
742
|
+
}))
|
|
743
|
+
: null;
|
|
744
|
+
|
|
745
|
+
export const deriveTrailDetail = (
|
|
746
|
+
item: AnyTrail,
|
|
747
|
+
app?: Topo | undefined,
|
|
748
|
+
activationGraph?: ActivationGraphReport | undefined,
|
|
749
|
+
options: TrailDetailOptions = {}
|
|
750
|
+
): TrailDetailReport => {
|
|
751
|
+
const activation =
|
|
752
|
+
app === undefined
|
|
753
|
+
? deriveDeclaredTrailActivation(item)
|
|
754
|
+
: ((activationGraph ?? deriveActivationGraph(app)).trails.get(item.id) ??
|
|
755
|
+
deriveDeclaredTrailActivation(item));
|
|
756
|
+
const safety = safetyLabel(
|
|
757
|
+
item as unknown as { intent?: 'read' | 'write' | 'destroy' }
|
|
758
|
+
);
|
|
759
|
+
|
|
760
|
+
const trailLayerNames = item.layers.map((layer) => layer.name);
|
|
761
|
+
const topoLayerNames = (app?.layers ?? []).map((layer) => layer.name);
|
|
762
|
+
const graphDetail = deriveResolvedTrailGraphDetail(
|
|
763
|
+
app,
|
|
764
|
+
item.id,
|
|
765
|
+
activation.edges,
|
|
766
|
+
options.topoGraph
|
|
767
|
+
);
|
|
768
|
+
|
|
769
|
+
return {
|
|
770
|
+
activatedBy: activation.activatedBy,
|
|
771
|
+
activates: activation.activates,
|
|
772
|
+
activationChains: activation.chains,
|
|
773
|
+
activationContext: graphDetail.activationContext,
|
|
774
|
+
activationEdges: graphDetail.activationEdges,
|
|
775
|
+
activationSources: activation.sources,
|
|
776
|
+
cli: graphDetail.cli,
|
|
777
|
+
composedLayers: {
|
|
778
|
+
surface: normalizeSurfaceLayerNames(options.surfaceLayerNames),
|
|
779
|
+
topo: topoLayerNames,
|
|
780
|
+
trail: trailLayerNames,
|
|
781
|
+
},
|
|
782
|
+
composes: item.composes.toSorted(),
|
|
783
|
+
contourDetails: graphDetail.contourDetails,
|
|
784
|
+
contours: graphDetail.contours,
|
|
785
|
+
description: item.description ?? null,
|
|
786
|
+
detours: formatTrailDetours(item),
|
|
787
|
+
examples: item.examples ?? [],
|
|
788
|
+
fieldOverrides: graphDetail.fieldOverrides,
|
|
789
|
+
fires: activation.fires,
|
|
790
|
+
governance: graphDetail.governance,
|
|
791
|
+
id: item.id,
|
|
792
|
+
input: graphDetail.input,
|
|
793
|
+
intent: item.intent,
|
|
794
|
+
kind: 'trail',
|
|
795
|
+
layers: graphDetail.layers,
|
|
796
|
+
on: activation.on,
|
|
797
|
+
output: graphDetail.output,
|
|
798
|
+
pattern: item.pattern ?? null,
|
|
799
|
+
resources: item.resources.map((resource) => resource.id).toSorted(),
|
|
800
|
+
safety,
|
|
801
|
+
supports: graphDetail.supports,
|
|
802
|
+
surfaceProjections: graphDetail.surfaceProjections,
|
|
803
|
+
surfaces: graphDetail.surfaces,
|
|
804
|
+
version: graphDetail.version,
|
|
805
|
+
versions: graphDetail.versions,
|
|
806
|
+
};
|
|
807
|
+
};
|