@ontrails/trails 1.0.0-beta.14 → 1.0.0-beta.16
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 +208 -0
- package/README.md +27 -0
- package/package.json +19 -8
- package/src/app.ts +17 -7
- package/src/clack.ts +1 -1
- package/src/cli.ts +304 -10
- package/src/completions.ts +240 -0
- package/src/load-app-mirror.ts +160 -0
- package/src/local-state-io.ts +153 -0
- package/src/project-writes.ts +320 -0
- package/src/run-collision.ts +125 -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-trace.ts +273 -0
- package/src/run-warden.ts +39 -0
- package/src/run-watch.ts +432 -0
- package/src/scaffold-versions.generated.ts +12 -0
- package/src/trails/add-surface.ts +172 -0
- package/src/trails/add-trail.ts +73 -27
- package/src/trails/add-verify.ts +68 -23
- package/src/trails/completions-complete.ts +165 -0
- package/src/trails/completions.ts +47 -0
- package/src/trails/create-scaffold.ts +101 -35
- package/src/trails/create.ts +87 -74
- package/src/trails/dev-clean.ts +31 -22
- package/src/trails/dev-reset.ts +9 -3
- package/src/trails/dev-stats.ts +28 -20
- package/src/trails/dev-support.ts +109 -95
- package/src/trails/draft-promote.ts +351 -107
- package/src/trails/guide.ts +55 -38
- package/src/trails/load-app.ts +712 -38
- package/src/trails/root-dir.ts +21 -0
- package/src/trails/run-example.ts +482 -0
- package/src/trails/run-examples.ts +141 -0
- package/src/trails/run.ts +403 -0
- package/src/trails/survey.ts +517 -186
- package/src/trails/topo-activation.ts +385 -0
- package/src/trails/topo-compile.ts +55 -0
- package/src/trails/topo-history.ts +14 -11
- package/src/trails/topo-output-schemas.ts +175 -0
- package/src/trails/topo-pin.ts +25 -16
- package/src/trails/topo-read-support.ts +178 -238
- package/src/trails/topo-reports.ts +445 -63
- package/src/trails/topo-store-support.ts +67 -35
- package/src/trails/topo-support.ts +93 -147
- package/src/trails/topo-unpin.ts +17 -7
- package/src/trails/topo-verify.ts +19 -10
- package/src/trails/topo.ts +64 -31
- package/src/trails/warden-guide.ts +121 -0
- package/src/trails/warden.ts +137 -47
- package/src/versions.ts +28 -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 -20
- 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 -22
- 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 -84
- 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 -10
- package/dist/src/trails/add-trail.d.ts.map +0 -1
- package/dist/src/trails/add-trail.js +0 -77
- package/dist/src/trails/add-trail.js.map +0 -1
- package/dist/src/trails/add-trailhead.d.ts +0 -13
- package/dist/src/trails/add-trailhead.d.ts.map +0 -1
- package/dist/src/trails/add-trailhead.js +0 -88
- package/dist/src/trails/add-trailhead.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/dev-clean.d.ts +0 -9
- package/dist/src/trails/dev-clean.d.ts.map +0 -1
- package/dist/src/trails/dev-clean.js +0 -65
- package/dist/src/trails/dev-clean.js.map +0 -1
- package/dist/src/trails/dev-reset.d.ts +0 -6
- package/dist/src/trails/dev-reset.d.ts.map +0 -1
- package/dist/src/trails/dev-reset.js +0 -38
- package/dist/src/trails/dev-reset.js.map +0 -1
- package/dist/src/trails/dev-stats.d.ts +0 -7
- package/dist/src/trails/dev-stats.d.ts.map +0 -1
- package/dist/src/trails/dev-stats.js +0 -61
- package/dist/src/trails/dev-stats.js.map +0 -1
- package/dist/src/trails/dev-support.d.ts +0 -64
- package/dist/src/trails/dev-support.d.ts.map +0 -1
- package/dist/src/trails/dev-support.js +0 -178
- package/dist/src/trails/dev-support.js.map +0 -1
- package/dist/src/trails/draft-promote.d.ts +0 -18
- package/dist/src/trails/draft-promote.d.ts.map +0 -1
- package/dist/src/trails/draft-promote.js +0 -386
- package/dist/src/trails/draft-promote.js.map +0 -1
- package/dist/src/trails/guide.d.ts +0 -21
- package/dist/src/trails/guide.d.ts.map +0 -1
- package/dist/src/trails/guide.js +0 -64
- package/dist/src/trails/guide.js.map +0 -1
- package/dist/src/trails/load-app.d.ts +0 -6
- package/dist/src/trails/load-app.d.ts.map +0 -1
- package/dist/src/trails/load-app.js +0 -67
- 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 -54
- package/dist/src/trails/project.js.map +0 -1
- package/dist/src/trails/survey.d.ts +0 -18
- package/dist/src/trails/survey.d.ts.map +0 -1
- package/dist/src/trails/survey.js +0 -212
- package/dist/src/trails/survey.js.map +0 -1
- package/dist/src/trails/topo-constants.d.ts +0 -3
- package/dist/src/trails/topo-constants.d.ts.map +0 -1
- package/dist/src/trails/topo-constants.js +0 -3
- package/dist/src/trails/topo-constants.js.map +0 -1
- package/dist/src/trails/topo-export.d.ts +0 -18
- package/dist/src/trails/topo-export.d.ts.map +0 -1
- package/dist/src/trails/topo-export.js +0 -34
- package/dist/src/trails/topo-export.js.map +0 -1
- package/dist/src/trails/topo-history.d.ts +0 -24
- package/dist/src/trails/topo-history.d.ts.map +0 -1
- package/dist/src/trails/topo-history.js +0 -33
- package/dist/src/trails/topo-history.js.map +0 -1
- package/dist/src/trails/topo-pin.d.ts +0 -21
- package/dist/src/trails/topo-pin.d.ts.map +0 -1
- package/dist/src/trails/topo-pin.js +0 -35
- package/dist/src/trails/topo-pin.js.map +0 -1
- package/dist/src/trails/topo-read-support.d.ts +0 -54
- package/dist/src/trails/topo-read-support.d.ts.map +0 -1
- package/dist/src/trails/topo-read-support.js +0 -178
- package/dist/src/trails/topo-read-support.js.map +0 -1
- package/dist/src/trails/topo-reports.d.ts +0 -50
- package/dist/src/trails/topo-reports.d.ts.map +0 -1
- package/dist/src/trails/topo-reports.js +0 -122
- package/dist/src/trails/topo-reports.js.map +0 -1
- package/dist/src/trails/topo-show.d.ts +0 -23
- package/dist/src/trails/topo-show.d.ts.map +0 -1
- package/dist/src/trails/topo-show.js +0 -53
- package/dist/src/trails/topo-show.js.map +0 -1
- package/dist/src/trails/topo-store-support.d.ts +0 -13
- package/dist/src/trails/topo-store-support.d.ts.map +0 -1
- package/dist/src/trails/topo-store-support.js +0 -55
- package/dist/src/trails/topo-store-support.js.map +0 -1
- package/dist/src/trails/topo-support.d.ts +0 -87
- package/dist/src/trails/topo-support.d.ts.map +0 -1
- package/dist/src/trails/topo-support.js +0 -165
- package/dist/src/trails/topo-support.js.map +0 -1
- package/dist/src/trails/topo-unpin.d.ts +0 -15
- package/dist/src/trails/topo-unpin.d.ts.map +0 -1
- package/dist/src/trails/topo-unpin.js +0 -39
- package/dist/src/trails/topo-unpin.js.map +0 -1
- package/dist/src/trails/topo-verify.d.ts +0 -5
- package/dist/src/trails/topo-verify.d.ts.map +0 -1
- package/dist/src/trails/topo-verify.js +0 -28
- package/dist/src/trails/topo-verify.js.map +0 -1
- package/dist/src/trails/topo.d.ts +0 -5
- package/dist/src/trails/topo.d.ts.map +0 -1
- package/dist/src/trails/topo.js +0 -67
- package/dist/src/trails/topo.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 -89
- package/dist/src/trails/warden.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/src/__tests__/create.test.ts +0 -351
- package/src/__tests__/draft-promote.test.ts +0 -144
- package/src/__tests__/guide.test.ts +0 -91
- package/src/__tests__/load-app.test.ts +0 -58
- package/src/__tests__/survey.test.ts +0 -301
- package/src/__tests__/topo-dev.test.ts +0 -424
- package/src/__tests__/warden.test.ts +0 -74
- package/src/trails/add-trailhead.ts +0 -121
- package/src/trails/topo-export.ts +0 -39
- package/src/trails/topo-show.ts +0 -58
- package/tsconfig.json +0 -9
|
@@ -1,56 +1,189 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { DETOUR_MAX_ATTEMPTS_CAP, zodToJsonSchema } from '@ontrails/core';
|
|
2
|
+
import type { AnyTrail, Signal, Topo } from '@ontrails/core';
|
|
3
|
+
import { deriveTopoGraph } from '@ontrails/topographer';
|
|
4
|
+
import type {
|
|
5
|
+
JsonSchema,
|
|
6
|
+
TopoGraph,
|
|
7
|
+
TopoGraphActivationEdge,
|
|
8
|
+
TopoGraphEntry,
|
|
9
|
+
TopoGraphFieldOverride,
|
|
10
|
+
TopoGraphLayerReference,
|
|
11
|
+
} from '@ontrails/topographer';
|
|
12
|
+
import { z } from 'zod';
|
|
2
13
|
|
|
14
|
+
import type {
|
|
15
|
+
ActivationChainReport,
|
|
16
|
+
ActivationEdgeReport,
|
|
17
|
+
ActivationGraphReport,
|
|
18
|
+
ActivationOverviewReport,
|
|
19
|
+
ActivationSourceReport,
|
|
20
|
+
SignalActivationRelations,
|
|
21
|
+
} from './topo-activation.js';
|
|
22
|
+
import {
|
|
23
|
+
deriveActivationGraph,
|
|
24
|
+
deriveDeclaredTrailActivation,
|
|
25
|
+
deriveSignalActivationRelations,
|
|
26
|
+
} from './topo-activation.js';
|
|
3
27
|
import { REPORT_CONTRACT_VERSION, REPORT_VERSION } from './topo-constants.js';
|
|
4
28
|
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
29
|
+
export type {
|
|
30
|
+
ActivationChainReport,
|
|
31
|
+
ActivationEdgeReport,
|
|
32
|
+
ActivationGraphReport,
|
|
33
|
+
ActivationOverviewReport,
|
|
34
|
+
ActivationSourceReport,
|
|
35
|
+
SignalActivationRelations,
|
|
36
|
+
TrailActivationReport,
|
|
37
|
+
} from './topo-activation.js';
|
|
38
|
+
|
|
39
|
+
export const briefReportSchema = z.object({
|
|
40
|
+
contractVersion: z.string(),
|
|
41
|
+
features: z.object({
|
|
42
|
+
detours: z.boolean(),
|
|
43
|
+
examples: z.boolean(),
|
|
44
|
+
outputSchemas: z.boolean(),
|
|
45
|
+
resources: z.boolean(),
|
|
46
|
+
signals: z.boolean(),
|
|
47
|
+
}),
|
|
48
|
+
name: z.string(),
|
|
49
|
+
resources: z.number(),
|
|
50
|
+
signals: z.number(),
|
|
51
|
+
trails: z.number(),
|
|
52
|
+
version: z.string(),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
type BriefReportShape = z.infer<typeof briefReportSchema>;
|
|
56
|
+
|
|
57
|
+
export type BriefReport = Readonly<
|
|
58
|
+
Omit<BriefReportShape, 'features'> & {
|
|
59
|
+
readonly features: Readonly<BriefReportShape['features']>;
|
|
60
|
+
}
|
|
61
|
+
>;
|
|
62
|
+
|
|
63
|
+
export type SurfaceLayerKey = 'cli' | 'http' | 'mcp';
|
|
64
|
+
|
|
65
|
+
export type SurfaceLayerNames = Readonly<
|
|
66
|
+
Record<SurfaceLayerKey, readonly string[]>
|
|
67
|
+
>;
|
|
68
|
+
|
|
69
|
+
type TopoGraphContourEntry = TopoGraphEntry & { readonly kind: 'contour' };
|
|
70
|
+
|
|
71
|
+
export interface TrailDetailOptions {
|
|
72
|
+
readonly surfaceLayerNames?: Partial<SurfaceLayerNames> | undefined;
|
|
73
|
+
readonly topoGraph?: TopoGraph | undefined;
|
|
19
74
|
}
|
|
20
75
|
|
|
21
76
|
export interface SurveyListReport {
|
|
77
|
+
readonly activation: ActivationOverviewReport;
|
|
22
78
|
readonly count: number;
|
|
23
79
|
readonly entries: readonly {
|
|
80
|
+
readonly activatedBy: readonly string[];
|
|
81
|
+
readonly activates: readonly string[];
|
|
24
82
|
readonly examples: number;
|
|
25
83
|
readonly id: string;
|
|
26
84
|
readonly kind: string;
|
|
27
85
|
readonly safety: string;
|
|
28
86
|
}[];
|
|
29
|
-
readonly
|
|
30
|
-
readonly
|
|
87
|
+
readonly resourceCount: number;
|
|
88
|
+
readonly resources: readonly {
|
|
31
89
|
readonly description: string | null;
|
|
32
90
|
readonly health: 'available' | 'none';
|
|
33
91
|
readonly id: string;
|
|
34
|
-
readonly kind: '
|
|
92
|
+
readonly kind: 'resource';
|
|
35
93
|
readonly lifetime: 'singleton';
|
|
36
94
|
readonly usedBy: readonly string[];
|
|
37
95
|
}[];
|
|
96
|
+
readonly signalCount: number;
|
|
97
|
+
readonly signals: readonly {
|
|
98
|
+
readonly consumers: readonly string[];
|
|
99
|
+
readonly description: string | null;
|
|
100
|
+
readonly examples: number;
|
|
101
|
+
readonly from: readonly string[];
|
|
102
|
+
readonly id: string;
|
|
103
|
+
readonly kind: 'signal';
|
|
104
|
+
readonly payloadSchema: boolean;
|
|
105
|
+
readonly producers: readonly string[];
|
|
106
|
+
}[];
|
|
38
107
|
}
|
|
39
108
|
|
|
40
109
|
export interface TrailDetailReport {
|
|
110
|
+
readonly activatedBy: readonly string[];
|
|
111
|
+
readonly activates: readonly string[];
|
|
112
|
+
readonly activationChains: readonly ActivationChainReport[];
|
|
113
|
+
readonly activationContext: {
|
|
114
|
+
readonly edgeCount: number;
|
|
115
|
+
readonly sourceCount: number;
|
|
116
|
+
readonly sourceKeys: readonly string[];
|
|
117
|
+
readonly trailIds: readonly string[];
|
|
118
|
+
};
|
|
119
|
+
readonly activationEdges: readonly ActivationEdgeReport[];
|
|
120
|
+
readonly activationSources: readonly ActivationSourceReport[];
|
|
121
|
+
readonly cli: {
|
|
122
|
+
readonly path: readonly string[];
|
|
123
|
+
} | null;
|
|
124
|
+
/**
|
|
125
|
+
* Composed layer names visible at the survey boundary.
|
|
126
|
+
*
|
|
127
|
+
* Reports the names of typed layers that wrap this trail at execution time,
|
|
128
|
+
* in the framework's composition order: `topo → surface → trail`
|
|
129
|
+
* (outermost-first). Surface-scope layers are keyed by surface because
|
|
130
|
+
* each surface owns its own attachment set.
|
|
131
|
+
*/
|
|
132
|
+
readonly composedLayers: {
|
|
133
|
+
readonly topo: readonly string[];
|
|
134
|
+
readonly surface: SurfaceLayerNames;
|
|
135
|
+
readonly trail: readonly string[];
|
|
136
|
+
};
|
|
137
|
+
readonly contourDetails: readonly TopoGraphContourEntry[];
|
|
138
|
+
readonly contours: readonly string[];
|
|
41
139
|
readonly description: string | null;
|
|
42
|
-
readonly detours:
|
|
140
|
+
readonly detours:
|
|
141
|
+
| readonly { readonly on: string; readonly maxAttempts: number }[]
|
|
142
|
+
| null;
|
|
43
143
|
readonly examples: readonly unknown[];
|
|
144
|
+
readonly fieldOverrides: readonly TopoGraphFieldOverride[];
|
|
44
145
|
readonly crosses: readonly string[];
|
|
146
|
+
readonly fires: readonly string[];
|
|
147
|
+
readonly governance: Readonly<Record<string, unknown>> | null;
|
|
45
148
|
readonly id: string;
|
|
149
|
+
readonly input: JsonSchema | null;
|
|
46
150
|
readonly intent: 'read' | 'write' | 'destroy';
|
|
47
|
-
readonly kind:
|
|
151
|
+
readonly kind: 'trail';
|
|
152
|
+
readonly layers: readonly TopoGraphLayerReference[];
|
|
153
|
+
readonly on: readonly string[];
|
|
154
|
+
readonly output: JsonSchema | null;
|
|
155
|
+
readonly pattern: string | null;
|
|
48
156
|
readonly safety: string;
|
|
49
|
-
readonly
|
|
157
|
+
readonly resources: readonly string[];
|
|
158
|
+
readonly surfaceProjections: readonly {
|
|
159
|
+
readonly derivedName: string;
|
|
160
|
+
readonly method: string | null;
|
|
161
|
+
readonly surface: string;
|
|
162
|
+
readonly trailId: string;
|
|
163
|
+
}[];
|
|
164
|
+
readonly surfaces: readonly string[];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface SignalDetailReport {
|
|
168
|
+
readonly consumers: readonly string[];
|
|
169
|
+
readonly description: string | null;
|
|
170
|
+
readonly examples: readonly unknown[];
|
|
171
|
+
readonly from: readonly string[];
|
|
172
|
+
readonly id: string;
|
|
173
|
+
readonly kind: 'signal';
|
|
174
|
+
/**
|
|
175
|
+
* The signal's payload schema (JSON Schema object), or `null` when the
|
|
176
|
+
* surface-map entry is missing for this signal. `null` is meaningful:
|
|
177
|
+
* it matches the list view's `payloadSchema: false` flag and lets
|
|
178
|
+
* consumers distinguish "schema not found" from "schema accepts any
|
|
179
|
+
* value" (the latter would be an empty object `{}`).
|
|
180
|
+
*/
|
|
181
|
+
readonly payload: Readonly<Record<string, unknown>> | null;
|
|
182
|
+
readonly producers: readonly string[];
|
|
50
183
|
}
|
|
51
184
|
|
|
52
185
|
const trailHas = (raw: Record<string, unknown>, key: string): boolean => {
|
|
53
|
-
if (key === 'examples') {
|
|
186
|
+
if (key === 'examples' || key === 'detours') {
|
|
54
187
|
return Array.isArray(raw[key]) && (raw[key] as unknown[]).length > 0;
|
|
55
188
|
}
|
|
56
189
|
return Boolean(raw[key]);
|
|
@@ -62,7 +195,7 @@ const detectFeatures = (
|
|
|
62
195
|
hasDetours: boolean;
|
|
63
196
|
hasExamples: boolean;
|
|
64
197
|
hasOutputSchemas: boolean;
|
|
65
|
-
|
|
198
|
+
hasResources: boolean;
|
|
66
199
|
} => {
|
|
67
200
|
const trails = [...app.trails.values()].map(
|
|
68
201
|
(item) => item as unknown as Record<string, unknown>
|
|
@@ -71,16 +204,16 @@ const detectFeatures = (
|
|
|
71
204
|
hasDetours: trails.some((r) => trailHas(r, 'detours')),
|
|
72
205
|
hasExamples: trails.some((r) => trailHas(r, 'examples')),
|
|
73
206
|
hasOutputSchemas: trails.some((r) => trailHas(r, 'output')),
|
|
74
|
-
|
|
207
|
+
hasResources: trails.some(
|
|
75
208
|
(r) =>
|
|
76
|
-
Array.isArray(r['
|
|
77
|
-
(r['
|
|
209
|
+
Array.isArray(r['resources']) &&
|
|
210
|
+
(r['resources'] as unknown[]).length > 0
|
|
78
211
|
),
|
|
79
212
|
};
|
|
80
213
|
};
|
|
81
214
|
|
|
82
|
-
export const
|
|
83
|
-
const { hasDetours, hasExamples, hasOutputSchemas,
|
|
215
|
+
export const deriveBriefReport = (app: Topo): BriefReport => {
|
|
216
|
+
const { hasDetours, hasExamples, hasOutputSchemas, hasResources } =
|
|
84
217
|
detectFeatures(app);
|
|
85
218
|
|
|
86
219
|
return {
|
|
@@ -89,11 +222,11 @@ export const generateBriefReport = (app: Topo): BriefReport => {
|
|
|
89
222
|
detours: hasDetours,
|
|
90
223
|
examples: hasExamples,
|
|
91
224
|
outputSchemas: hasOutputSchemas,
|
|
92
|
-
|
|
225
|
+
resources: hasResources,
|
|
93
226
|
signals: app.signals.size > 0,
|
|
94
227
|
},
|
|
95
228
|
name: app.name,
|
|
96
|
-
|
|
229
|
+
resources: app.resources.size,
|
|
97
230
|
signals: app.signals.size,
|
|
98
231
|
trails: app.trails.size,
|
|
99
232
|
version: REPORT_VERSION,
|
|
@@ -115,16 +248,16 @@ const safetyLabel = (entry: {
|
|
|
115
248
|
return '-';
|
|
116
249
|
};
|
|
117
250
|
|
|
118
|
-
const
|
|
251
|
+
const buildResourceUsage = (
|
|
119
252
|
app: Topo
|
|
120
253
|
): ReadonlyMap<string, readonly string[]> => {
|
|
121
254
|
const usage = new Map<string, string[]>();
|
|
122
255
|
|
|
123
256
|
for (const trailDef of app.list()) {
|
|
124
|
-
for (const
|
|
125
|
-
const users = usage.get(
|
|
257
|
+
for (const declaredResource of trailDef.resources) {
|
|
258
|
+
const users = usage.get(declaredResource.id) ?? [];
|
|
126
259
|
users.push(trailDef.id);
|
|
127
|
-
usage.set(
|
|
260
|
+
usage.set(declaredResource.id, users);
|
|
128
261
|
}
|
|
129
262
|
}
|
|
130
263
|
|
|
@@ -133,46 +266,74 @@ const buildProvisionUsage = (
|
|
|
133
266
|
);
|
|
134
267
|
};
|
|
135
268
|
|
|
136
|
-
const
|
|
269
|
+
const resourceHealthStatus = (resource: {
|
|
137
270
|
health?: unknown;
|
|
138
271
|
}): 'available' | 'none' =>
|
|
139
|
-
|
|
272
|
+
resource.health === undefined ? 'none' : 'available';
|
|
140
273
|
|
|
141
|
-
export const
|
|
142
|
-
app
|
|
143
|
-
|
|
144
|
-
): object => {
|
|
145
|
-
const item = app.getProvision(provisionId);
|
|
146
|
-
const usedBy = buildProvisionUsage(app).get(provisionId) ?? [];
|
|
274
|
+
export const deriveResourceDetail = (app: Topo, resourceId: string): object => {
|
|
275
|
+
const item = app.getResource(resourceId);
|
|
276
|
+
const usedBy = buildResourceUsage(app).get(resourceId) ?? [];
|
|
147
277
|
|
|
148
278
|
return {
|
|
149
279
|
description: item?.description ?? null,
|
|
150
|
-
health: item ?
|
|
151
|
-
id:
|
|
152
|
-
kind: '
|
|
280
|
+
health: item ? resourceHealthStatus(item) : 'none',
|
|
281
|
+
id: resourceId,
|
|
282
|
+
kind: 'resource',
|
|
153
283
|
lifetime: 'singleton',
|
|
154
284
|
usedBy,
|
|
155
285
|
};
|
|
156
286
|
};
|
|
157
287
|
|
|
158
|
-
const
|
|
159
|
-
const usage =
|
|
288
|
+
const formatResourceList = (app: Topo): SurveyListReport['resources'] => {
|
|
289
|
+
const usage = buildResourceUsage(app);
|
|
160
290
|
return app
|
|
161
|
-
.
|
|
162
|
-
.map((
|
|
163
|
-
description:
|
|
164
|
-
health:
|
|
165
|
-
id:
|
|
166
|
-
kind:
|
|
291
|
+
.listResources()
|
|
292
|
+
.map((resource) => ({
|
|
293
|
+
description: resource.description ?? null,
|
|
294
|
+
health: resourceHealthStatus(resource),
|
|
295
|
+
id: resource.id,
|
|
296
|
+
kind: resource.kind,
|
|
167
297
|
lifetime: 'singleton' as const,
|
|
168
|
-
usedBy: usage.get(
|
|
298
|
+
usedBy: usage.get(resource.id) ?? [],
|
|
169
299
|
}))
|
|
170
300
|
.toSorted((a, b) => a.id.localeCompare(b.id));
|
|
171
301
|
};
|
|
172
302
|
|
|
173
|
-
|
|
303
|
+
const formatSignalList = (
|
|
304
|
+
app: Topo,
|
|
305
|
+
relations: ReadonlyMap<string, SignalActivationRelations>
|
|
306
|
+
): SurveyListReport['signals'] =>
|
|
307
|
+
app
|
|
308
|
+
.listSignals()
|
|
309
|
+
.map((signalDef) => {
|
|
310
|
+
const related = relations.get(signalDef.id);
|
|
311
|
+
const consumers = related?.consumers ?? [];
|
|
312
|
+
const producers = related?.producers ?? [];
|
|
313
|
+
return {
|
|
314
|
+
consumers,
|
|
315
|
+
description: signalDef.description ?? null,
|
|
316
|
+
examples: signalDef.examples?.length ?? 0,
|
|
317
|
+
from: signalDef.from?.toSorted() ?? [],
|
|
318
|
+
id: signalDef.id,
|
|
319
|
+
kind: signalDef.kind,
|
|
320
|
+
// Mirror the store path (`mapSignalRow` in `topo-store-read.ts`) which
|
|
321
|
+
// derives this from the surface-map entry. SignalSpec<T> requires
|
|
322
|
+
// `payload` so this is `true` in practice today; the explicit check
|
|
323
|
+
// keeps the in-memory and store reports self-consistent if a future
|
|
324
|
+
// SignalSpec variant ever omits `payload`.
|
|
325
|
+
payloadSchema: signalDef.payload !== undefined,
|
|
326
|
+
producers,
|
|
327
|
+
};
|
|
328
|
+
})
|
|
329
|
+
.toSorted((a, b) => a.id.localeCompare(b.id));
|
|
330
|
+
|
|
331
|
+
export const deriveSurveyList = (app: Topo): SurveyListReport => {
|
|
174
332
|
const items = app.list();
|
|
333
|
+
const activation = deriveActivationGraph(app);
|
|
175
334
|
const entries = items.map((item) => {
|
|
335
|
+
const trailActivation =
|
|
336
|
+
activation.trails.get(item.id) ?? deriveDeclaredTrailActivation(item);
|
|
176
337
|
const safety = safetyLabel(
|
|
177
338
|
item as unknown as { intent?: 'read' | 'write' | 'destroy' }
|
|
178
339
|
);
|
|
@@ -183,6 +344,8 @@ export const generateSurveyList = (app: Topo): SurveyListReport => {
|
|
|
183
344
|
: 0;
|
|
184
345
|
|
|
185
346
|
return {
|
|
347
|
+
activatedBy: trailActivation.activatedBy,
|
|
348
|
+
activates: trailActivation.activates,
|
|
186
349
|
examples,
|
|
187
350
|
id: item.id,
|
|
188
351
|
kind: item.kind,
|
|
@@ -190,32 +353,251 @@ export const generateSurveyList = (app: Topo): SurveyListReport => {
|
|
|
190
353
|
};
|
|
191
354
|
});
|
|
192
355
|
|
|
193
|
-
const
|
|
356
|
+
const resources = formatResourceList(app);
|
|
357
|
+
const signals = formatSignalList(app, activation.signals);
|
|
194
358
|
|
|
195
359
|
return {
|
|
360
|
+
activation: activation.overview,
|
|
196
361
|
count: items.length,
|
|
197
362
|
entries,
|
|
198
|
-
|
|
199
|
-
|
|
363
|
+
resourceCount: resources.length,
|
|
364
|
+
resources,
|
|
365
|
+
signalCount: signals.length,
|
|
366
|
+
signals,
|
|
367
|
+
};
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
export const deriveSignalDetail = (
|
|
371
|
+
app: Topo,
|
|
372
|
+
signalId: string,
|
|
373
|
+
activationGraph?: ActivationGraphReport | undefined
|
|
374
|
+
): SignalDetailReport | undefined => {
|
|
375
|
+
const signalDef = app.signals.get(signalId) as Signal<unknown> | undefined;
|
|
376
|
+
if (signalDef === undefined) {
|
|
377
|
+
return undefined;
|
|
378
|
+
}
|
|
379
|
+
const related =
|
|
380
|
+
activationGraph?.signals.get(signalId) ??
|
|
381
|
+
deriveSignalActivationRelations(app, signalId);
|
|
382
|
+
|
|
383
|
+
return {
|
|
384
|
+
consumers: [...related.consumers],
|
|
385
|
+
description: signalDef.description ?? null,
|
|
386
|
+
examples: [...(signalDef.examples ?? [])],
|
|
387
|
+
from: signalDef.from?.toSorted() ?? [],
|
|
388
|
+
id: signalDef.id,
|
|
389
|
+
kind: 'signal',
|
|
390
|
+
payload: zodToJsonSchema(signalDef.payload),
|
|
391
|
+
producers: [...related.producers],
|
|
392
|
+
};
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
const emptySurfaceLayerNames = (): SurfaceLayerNames => ({
|
|
396
|
+
cli: [],
|
|
397
|
+
http: [],
|
|
398
|
+
mcp: [],
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
const normalizeSurfaceLayerNames = (
|
|
402
|
+
names?: Partial<SurfaceLayerNames> | undefined
|
|
403
|
+
): SurfaceLayerNames => {
|
|
404
|
+
const base = emptySurfaceLayerNames();
|
|
405
|
+
if (names === undefined) {
|
|
406
|
+
return base;
|
|
407
|
+
}
|
|
408
|
+
return {
|
|
409
|
+
cli: names.cli ?? base.cli,
|
|
410
|
+
http: names.http ?? base.http,
|
|
411
|
+
mcp: names.mcp ?? base.mcp,
|
|
412
|
+
};
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
const emptyActivationContext = (): TrailDetailReport['activationContext'] => ({
|
|
416
|
+
edgeCount: 0,
|
|
417
|
+
sourceCount: 0,
|
|
418
|
+
sourceKeys: [],
|
|
419
|
+
trailIds: [],
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
const activationContextFromTopoGraph = (
|
|
423
|
+
topoGraph: TopoGraph | undefined,
|
|
424
|
+
trailId: string,
|
|
425
|
+
fallbackEdges: readonly TopoGraphActivationEdge[]
|
|
426
|
+
): TrailDetailReport['activationContext'] => {
|
|
427
|
+
const edges =
|
|
428
|
+
topoGraph?.activationGraph.edges.filter(
|
|
429
|
+
(edge) => edge.trailId === trailId
|
|
430
|
+
) ?? fallbackEdges;
|
|
431
|
+
if (edges.length === 0) {
|
|
432
|
+
return emptyActivationContext();
|
|
433
|
+
}
|
|
434
|
+
return {
|
|
435
|
+
edgeCount: edges.length,
|
|
436
|
+
sourceCount: new Set(edges.map((edge) => edge.sourceKey)).size,
|
|
437
|
+
sourceKeys: [...new Set(edges.map((edge) => edge.sourceKey))].toSorted(),
|
|
438
|
+
trailIds: [...new Set(edges.map((edge) => edge.trailId))].toSorted(),
|
|
200
439
|
};
|
|
201
440
|
};
|
|
202
441
|
|
|
203
|
-
|
|
204
|
-
|
|
442
|
+
const findTopoEntry = (
|
|
443
|
+
topoGraph: TopoGraph | undefined,
|
|
444
|
+
id: string,
|
|
445
|
+
kind: TopoGraphEntry['kind']
|
|
446
|
+
): TopoGraphEntry | undefined =>
|
|
447
|
+
topoGraph?.entries.find((entry) => entry.id === id && entry.kind === kind);
|
|
448
|
+
|
|
449
|
+
const trailActivationEdgesFromTopoGraph = (
|
|
450
|
+
topoGraph: TopoGraph | undefined,
|
|
451
|
+
trailId: string,
|
|
452
|
+
fallback: readonly TopoGraphActivationEdge[]
|
|
453
|
+
): readonly TopoGraphActivationEdge[] =>
|
|
454
|
+
topoGraph?.activationGraph.edges.filter((edge) => edge.trailId === trailId) ??
|
|
455
|
+
fallback;
|
|
456
|
+
|
|
457
|
+
const deriveSurfaceProjections = (
|
|
458
|
+
entry: TopoGraphEntry | undefined
|
|
459
|
+
): TrailDetailReport['surfaceProjections'] => {
|
|
460
|
+
if (entry === undefined) {
|
|
461
|
+
return [];
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const cliProjection =
|
|
465
|
+
entry.cli === undefined
|
|
466
|
+
? []
|
|
467
|
+
: [
|
|
468
|
+
{
|
|
469
|
+
derivedName: entry.cli.path.join(' '),
|
|
470
|
+
method: null,
|
|
471
|
+
surface: 'cli',
|
|
472
|
+
trailId: entry.id,
|
|
473
|
+
},
|
|
474
|
+
];
|
|
475
|
+
return cliProjection.toSorted((a, b) => a.surface.localeCompare(b.surface));
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
const deriveResolvedTrailGraphDetail = (
|
|
479
|
+
app: Topo | undefined,
|
|
480
|
+
trailId: string,
|
|
481
|
+
fallbackActivationEdges: readonly TopoGraphActivationEdge[],
|
|
482
|
+
topoGraphOverride?: TopoGraph | undefined
|
|
483
|
+
): Pick<
|
|
484
|
+
TrailDetailReport,
|
|
485
|
+
| 'activationContext'
|
|
486
|
+
| 'activationEdges'
|
|
487
|
+
| 'cli'
|
|
488
|
+
| 'contourDetails'
|
|
489
|
+
| 'contours'
|
|
490
|
+
| 'fieldOverrides'
|
|
491
|
+
| 'governance'
|
|
492
|
+
| 'input'
|
|
493
|
+
| 'layers'
|
|
494
|
+
| 'output'
|
|
495
|
+
| 'surfaceProjections'
|
|
496
|
+
| 'surfaces'
|
|
497
|
+
> => {
|
|
498
|
+
const topoGraph =
|
|
499
|
+
topoGraphOverride ?? (app === undefined ? undefined : deriveTopoGraph(app));
|
|
500
|
+
const topoEntry = findTopoEntry(topoGraph, trailId, 'trail');
|
|
501
|
+
const contours = topoEntry?.contours ?? [];
|
|
502
|
+
const contourDetails = contours
|
|
503
|
+
.map((contourId) => findTopoEntry(topoGraph, contourId, 'contour'))
|
|
504
|
+
.filter(
|
|
505
|
+
(entry): entry is TopoGraphContourEntry =>
|
|
506
|
+
entry !== undefined && entry.kind === 'contour'
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
return {
|
|
510
|
+
activationContext: activationContextFromTopoGraph(
|
|
511
|
+
topoGraph,
|
|
512
|
+
trailId,
|
|
513
|
+
fallbackActivationEdges
|
|
514
|
+
),
|
|
515
|
+
activationEdges: trailActivationEdgesFromTopoGraph(
|
|
516
|
+
topoGraph,
|
|
517
|
+
trailId,
|
|
518
|
+
fallbackActivationEdges
|
|
519
|
+
),
|
|
520
|
+
cli: topoEntry?.cli ?? null,
|
|
521
|
+
contourDetails,
|
|
522
|
+
contours,
|
|
523
|
+
fieldOverrides: topoEntry?.fieldOverrides ?? [],
|
|
524
|
+
governance: topoEntry?.governance ?? null,
|
|
525
|
+
input: topoEntry?.input ?? null,
|
|
526
|
+
layers: topoEntry?.layers ?? [],
|
|
527
|
+
output: topoEntry?.output ?? null,
|
|
528
|
+
surfaceProjections: deriveSurfaceProjections(topoEntry),
|
|
529
|
+
surfaces: topoEntry?.surfaces ?? [],
|
|
530
|
+
};
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
const formatTrailDetours = (item: AnyTrail): TrailDetailReport['detours'] =>
|
|
534
|
+
item.detours.length > 0
|
|
535
|
+
? item.detours.map((d) => ({
|
|
536
|
+
maxAttempts: Math.max(
|
|
537
|
+
1,
|
|
538
|
+
Math.min(d.maxAttempts ?? 1, DETOUR_MAX_ATTEMPTS_CAP)
|
|
539
|
+
),
|
|
540
|
+
on: d.on.name,
|
|
541
|
+
}))
|
|
542
|
+
: null;
|
|
543
|
+
|
|
544
|
+
export const deriveTrailDetail = (
|
|
545
|
+
item: AnyTrail,
|
|
546
|
+
app?: Topo | undefined,
|
|
547
|
+
activationGraph?: ActivationGraphReport | undefined,
|
|
548
|
+
options: TrailDetailOptions = {}
|
|
205
549
|
): TrailDetailReport => {
|
|
550
|
+
const activation =
|
|
551
|
+
app === undefined
|
|
552
|
+
? deriveDeclaredTrailActivation(item)
|
|
553
|
+
: ((activationGraph ?? deriveActivationGraph(app)).trails.get(item.id) ??
|
|
554
|
+
deriveDeclaredTrailActivation(item));
|
|
206
555
|
const safety = safetyLabel(
|
|
207
556
|
item as unknown as { intent?: 'read' | 'write' | 'destroy' }
|
|
208
557
|
);
|
|
209
558
|
|
|
559
|
+
const trailLayerNames = item.layers.map((layer) => layer.name);
|
|
560
|
+
const topoLayerNames = (app?.layers ?? []).map((layer) => layer.name);
|
|
561
|
+
const graphDetail = deriveResolvedTrailGraphDetail(
|
|
562
|
+
app,
|
|
563
|
+
item.id,
|
|
564
|
+
activation.edges,
|
|
565
|
+
options.topoGraph
|
|
566
|
+
);
|
|
567
|
+
|
|
210
568
|
return {
|
|
569
|
+
activatedBy: activation.activatedBy,
|
|
570
|
+
activates: activation.activates,
|
|
571
|
+
activationChains: activation.chains,
|
|
572
|
+
activationContext: graphDetail.activationContext,
|
|
573
|
+
activationEdges: graphDetail.activationEdges,
|
|
574
|
+
activationSources: activation.sources,
|
|
575
|
+
cli: graphDetail.cli,
|
|
576
|
+
composedLayers: {
|
|
577
|
+
surface: normalizeSurfaceLayerNames(options.surfaceLayerNames),
|
|
578
|
+
topo: topoLayerNames,
|
|
579
|
+
trail: trailLayerNames,
|
|
580
|
+
},
|
|
581
|
+
contourDetails: graphDetail.contourDetails,
|
|
582
|
+
contours: graphDetail.contours,
|
|
211
583
|
crosses: item.crosses.toSorted(),
|
|
212
584
|
description: item.description ?? null,
|
|
213
|
-
detours: item
|
|
585
|
+
detours: formatTrailDetours(item),
|
|
214
586
|
examples: item.examples ?? [],
|
|
587
|
+
fieldOverrides: graphDetail.fieldOverrides,
|
|
588
|
+
fires: activation.fires,
|
|
589
|
+
governance: graphDetail.governance,
|
|
215
590
|
id: item.id,
|
|
591
|
+
input: graphDetail.input,
|
|
216
592
|
intent: item.intent,
|
|
217
|
-
kind:
|
|
218
|
-
|
|
593
|
+
kind: 'trail',
|
|
594
|
+
layers: graphDetail.layers,
|
|
595
|
+
on: activation.on,
|
|
596
|
+
output: graphDetail.output,
|
|
597
|
+
pattern: item.pattern ?? null,
|
|
598
|
+
resources: item.resources.map((resource) => resource.id).toSorted(),
|
|
219
599
|
safety,
|
|
600
|
+
surfaceProjections: graphDetail.surfaceProjections,
|
|
601
|
+
surfaces: graphDetail.surfaces,
|
|
220
602
|
};
|
|
221
603
|
};
|