@ontrails/trails 1.0.0-beta.15 → 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 +197 -2
- package/README.md +27 -0
- package/package.json +19 -8
- package/src/app.ts +15 -5
- package/src/cli.ts +303 -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 +45 -23
- package/src/trails/add-trail.ts +27 -17
- package/src/trails/add-verify.ts +57 -17
- package/src/trails/completions-complete.ts +165 -0
- package/src/trails/completions.ts +47 -0
- package/src/trails/create-scaffold.ts +86 -33
- package/src/trails/create.ts +11 -3
- package/src/trails/dev-clean.ts +6 -1
- package/src/trails/dev-reset.ts +6 -1
- package/src/trails/dev-stats.ts +6 -1
- package/src/trails/dev-support.ts +29 -17
- package/src/trails/draft-promote.ts +289 -80
- package/src/trails/guide.ts +54 -34
- package/src/trails/load-app.ts +251 -56
- 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 +506 -200
- package/src/trails/topo-activation.ts +385 -0
- package/src/trails/topo-compile.ts +55 -0
- package/src/trails/topo-history.ts +6 -1
- package/src/trails/topo-output-schemas.ts +175 -0
- package/src/trails/topo-pin.ts +19 -6
- package/src/trails/topo-read-support.ts +171 -228
- package/src/trails/topo-reports.ts +400 -25
- package/src/trails/topo-store-support.ts +43 -19
- package/src/trails/topo-support.ts +18 -28
- package/src/trails/topo-unpin.ts +6 -1
- package/src/trails/topo-verify.ts +18 -5
- package/src/trails/topo.ts +60 -23
- package/src/trails/warden-guide.ts +121 -0
- package/src/trails/warden.ts +137 -56
- package/src/versions.ts +3 -18
- 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 -45
- 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 -14
- 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 -110
- package/dist/src/trails/add-surface.js.map +0 -1
- package/dist/src/trails/add-trail.d.ts +0 -12
- package/dist/src/trails/add-trail.d.ts.map +0 -1
- package/dist/src/trails/add-trail.js +0 -104
- 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 -68
- 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 -295
- package/dist/src/trails/create-scaffold.js.map +0 -1
- package/dist/src/trails/create.d.ts +0 -18
- package/dist/src/trails/create.d.ts.map +0 -1
- package/dist/src/trails/create.js +0 -126
- 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 -66
- 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 -39
- 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 -181
- 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 -400
- 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 -61
- package/dist/src/trails/guide.js.map +0 -1
- package/dist/src/trails/load-app.d.ts +0 -12
- package/dist/src/trails/load-app.d.ts.map +0 -1
- package/dist/src/trails/load-app.js +0 -415
- 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 -234
- 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 -19
- package/dist/src/trails/topo-export.d.ts.map +0 -1
- package/dist/src/trails/topo-export.js +0 -31
- package/dist/src/trails/topo-export.js.map +0 -1
- package/dist/src/trails/topo-history.d.ts +0 -20
- package/dist/src/trails/topo-history.d.ts.map +0 -1
- package/dist/src/trails/topo-history.js +0 -32
- package/dist/src/trails/topo-history.js.map +0 -1
- package/dist/src/trails/topo-pin.d.ts +0 -17
- package/dist/src/trails/topo-pin.d.ts.map +0 -1
- package/dist/src/trails/topo-pin.js +0 -31
- package/dist/src/trails/topo-pin.js.map +0 -1
- package/dist/src/trails/topo-read-support.d.ts +0 -58
- package/dist/src/trails/topo-read-support.d.ts.map +0 -1
- package/dist/src/trails/topo-read-support.js +0 -167
- package/dist/src/trails/topo-read-support.js.map +0 -1
- package/dist/src/trails/topo-reports.d.ts +0 -54
- package/dist/src/trails/topo-reports.d.ts.map +0 -1
- package/dist/src/trails/topo-reports.js +0 -128
- 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 -49
- 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 -76
- package/dist/src/trails/topo-support.d.ts.map +0 -1
- package/dist/src/trails/topo-support.js +0 -132
- package/dist/src/trails/topo-support.js.map +0 -1
- package/dist/src/trails/topo-unpin.d.ts +0 -20
- package/dist/src/trails/topo-unpin.d.ts.map +0 -1
- package/dist/src/trails/topo-unpin.js +0 -44
- 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 -24
- 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 -63
- package/dist/src/trails/topo.js.map +0 -1
- package/dist/src/trails/warden.d.ts +0 -20
- package/dist/src/trails/warden.d.ts.map +0 -1
- package/dist/src/trails/warden.js +0 -98
- package/dist/src/trails/warden.js.map +0 -1
- package/dist/src/versions.d.ts +0 -12
- package/dist/src/versions.d.ts.map +0 -1
- package/dist/src/versions.js +0 -23
- package/dist/src/versions.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/src/__tests__/add-trail.test.ts +0 -97
- package/src/__tests__/create.test.ts +0 -415
- package/src/__tests__/draft-promote.test.ts +0 -144
- package/src/__tests__/guide.test.ts +0 -96
- package/src/__tests__/load-app.test.ts +0 -419
- package/src/__tests__/survey.test.ts +0 -377
- package/src/__tests__/topo-dev.test.ts +0 -426
- package/src/__tests__/warden.test.ts +0 -74
- package/src/trails/topo-export.ts +0 -35
- package/src/trails/topo-show.ts +0 -54
- package/tsconfig.json +0 -9
- package/tsconfig.tests.json +0 -10
|
@@ -1,26 +1,84 @@
|
|
|
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;
|
|
@@ -35,21 +93,93 @@ export interface SurveyListReport {
|
|
|
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
140
|
readonly detours:
|
|
43
141
|
| readonly { readonly on: string; readonly maxAttempts: number }[]
|
|
44
142
|
| null;
|
|
45
143
|
readonly examples: readonly unknown[];
|
|
144
|
+
readonly fieldOverrides: readonly TopoGraphFieldOverride[];
|
|
46
145
|
readonly crosses: readonly string[];
|
|
146
|
+
readonly fires: readonly string[];
|
|
147
|
+
readonly governance: Readonly<Record<string, unknown>> | null;
|
|
47
148
|
readonly id: string;
|
|
149
|
+
readonly input: JsonSchema | null;
|
|
48
150
|
readonly intent: 'read' | 'write' | 'destroy';
|
|
49
|
-
readonly kind:
|
|
151
|
+
readonly kind: 'trail';
|
|
152
|
+
readonly layers: readonly TopoGraphLayerReference[];
|
|
153
|
+
readonly on: readonly string[];
|
|
154
|
+
readonly output: JsonSchema | null;
|
|
50
155
|
readonly pattern: string | null;
|
|
51
156
|
readonly safety: string;
|
|
52
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[];
|
|
53
183
|
}
|
|
54
184
|
|
|
55
185
|
const trailHas = (raw: Record<string, unknown>, key: string): boolean => {
|
|
@@ -170,9 +300,40 @@ const formatResourceList = (app: Topo): SurveyListReport['resources'] => {
|
|
|
170
300
|
.toSorted((a, b) => a.id.localeCompare(b.id));
|
|
171
301
|
};
|
|
172
302
|
|
|
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
|
+
|
|
173
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 deriveSurveyList = (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,
|
|
@@ -191,38 +354,250 @@ export const deriveSurveyList = (app: Topo): SurveyListReport => {
|
|
|
191
354
|
});
|
|
192
355
|
|
|
193
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
363
|
resourceCount: resources.length,
|
|
199
364
|
resources,
|
|
365
|
+
signalCount: signals.length,
|
|
366
|
+
signals,
|
|
200
367
|
};
|
|
201
368
|
};
|
|
202
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(),
|
|
439
|
+
};
|
|
440
|
+
};
|
|
441
|
+
|
|
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
|
+
|
|
203
544
|
export const deriveTrailDetail = (
|
|
204
|
-
item:
|
|
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:
|
|
214
|
-
item.detours.length > 0
|
|
215
|
-
? item.detours.map((d) => ({
|
|
216
|
-
maxAttempts: d.maxAttempts ?? 1,
|
|
217
|
-
on: d.on.name,
|
|
218
|
-
}))
|
|
219
|
-
: null,
|
|
585
|
+
detours: formatTrailDetours(item),
|
|
220
586
|
examples: item.examples ?? [],
|
|
587
|
+
fieldOverrides: graphDetail.fieldOverrides,
|
|
588
|
+
fires: activation.fires,
|
|
589
|
+
governance: graphDetail.governance,
|
|
221
590
|
id: item.id,
|
|
591
|
+
input: graphDetail.input,
|
|
222
592
|
intent: item.intent,
|
|
223
|
-
kind:
|
|
593
|
+
kind: 'trail',
|
|
594
|
+
layers: graphDetail.layers,
|
|
595
|
+
on: activation.on,
|
|
596
|
+
output: graphDetail.output,
|
|
224
597
|
pattern: item.pattern ?? null,
|
|
225
598
|
resources: item.resources.map((resource) => resource.id).toSorted(),
|
|
226
599
|
safety,
|
|
600
|
+
surfaceProjections: graphDetail.surfaceProjections,
|
|
601
|
+
surfaces: graphDetail.surfaces,
|
|
227
602
|
};
|
|
228
603
|
};
|
|
@@ -5,19 +5,26 @@
|
|
|
5
5
|
* keeping module boundaries clean.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
import type {
|
|
11
|
-
import {
|
|
12
|
-
createTopoSnapshot,
|
|
13
|
-
getStoredTopoExport,
|
|
14
|
-
} from '@ontrails/core/internal/topo-store';
|
|
8
|
+
import { Database } from 'bun:sqlite';
|
|
9
|
+
|
|
10
|
+
import type { Topo } from '@ontrails/core';
|
|
15
11
|
import {
|
|
16
|
-
openWriteTrailsDb,
|
|
17
12
|
deriveTrailsDir,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
InternalError,
|
|
14
|
+
openWriteTrailsDb,
|
|
15
|
+
Result,
|
|
16
|
+
} from '@ontrails/core';
|
|
17
|
+
import type {
|
|
18
|
+
LockManifest,
|
|
19
|
+
TopoGraph,
|
|
20
|
+
TopoSnapshot,
|
|
21
|
+
} from '@ontrails/topographer';
|
|
22
|
+
import type { StoredTopoExport } from '@ontrails/topographer/backend-support';
|
|
23
|
+
import { writeLockManifest, writeTopoGraph } from '@ontrails/topographer';
|
|
24
|
+
import {
|
|
25
|
+
createStoredTopoSnapshot,
|
|
26
|
+
getStoredTopoExport,
|
|
27
|
+
} from '@ontrails/topographer/backend-support';
|
|
21
28
|
|
|
22
29
|
import type { TopoExportReport } from './topo-support.js';
|
|
23
30
|
import {
|
|
@@ -34,7 +41,7 @@ const persistAndReadStoredExport = (
|
|
|
34
41
|
{ snapshot: TopoSnapshot; storedExport: StoredTopoExport },
|
|
35
42
|
Error
|
|
36
43
|
> => {
|
|
37
|
-
const snapshotResult =
|
|
44
|
+
const snapshotResult = createStoredTopoSnapshot(db, app, {
|
|
38
45
|
...readGitState(rootDir),
|
|
39
46
|
...deriveTopoCounts(app),
|
|
40
47
|
});
|
|
@@ -59,23 +66,40 @@ const persistAndReadStoredExport = (
|
|
|
59
66
|
});
|
|
60
67
|
};
|
|
61
68
|
|
|
69
|
+
export const deriveCurrentTopoExport = (
|
|
70
|
+
app: Topo,
|
|
71
|
+
options?: { readonly rootDir?: string }
|
|
72
|
+
): Result<StoredTopoExport, Error> => {
|
|
73
|
+
const rootDir = deriveRootDir(options?.rootDir);
|
|
74
|
+
const db = new Database(':memory:');
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const projected = persistAndReadStoredExport(app, db, rootDir);
|
|
78
|
+
return projected.isErr()
|
|
79
|
+
? projected
|
|
80
|
+
: Result.ok(projected.value.storedExport);
|
|
81
|
+
} finally {
|
|
82
|
+
db.close();
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
62
86
|
const writeStoredExportArtifacts = async (
|
|
63
87
|
storedExport: StoredTopoExport,
|
|
64
88
|
trailsDir: string
|
|
65
|
-
): Promise<Pick<TopoExportReport, 'hash' | 'lockPath' | '
|
|
66
|
-
const
|
|
67
|
-
JSON.parse(storedExport.
|
|
89
|
+
): Promise<Pick<TopoExportReport, 'hash' | 'lockPath' | 'topoPath'>> => {
|
|
90
|
+
const topoPath = await writeTopoGraph(
|
|
91
|
+
JSON.parse(storedExport.topoGraphJson) as TopoGraph,
|
|
68
92
|
{ dir: trailsDir }
|
|
69
93
|
);
|
|
70
|
-
const lockPath = await
|
|
71
|
-
JSON.parse(storedExport.
|
|
94
|
+
const lockPath = await writeLockManifest(
|
|
95
|
+
JSON.parse(storedExport.lockManifestJson) as LockManifest,
|
|
72
96
|
{ dir: trailsDir }
|
|
73
97
|
);
|
|
74
98
|
|
|
75
99
|
return {
|
|
76
|
-
hash: storedExport.
|
|
100
|
+
hash: storedExport.topoGraphHash,
|
|
77
101
|
lockPath,
|
|
78
|
-
|
|
102
|
+
topoPath,
|
|
79
103
|
};
|
|
80
104
|
};
|
|
81
105
|
|
|
@@ -1,18 +1,23 @@
|
|
|
1
|
-
import { existsSync
|
|
2
|
-
import { tmpdir } from 'node:os';
|
|
3
|
-
import { join } from 'node:path';
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
4
2
|
import { fileURLToPath } from 'node:url';
|
|
5
3
|
|
|
4
|
+
import { deriveTrailsDbPath } from '@ontrails/core';
|
|
5
|
+
import type { Topo } from '@ontrails/core';
|
|
6
6
|
import {
|
|
7
7
|
createTopoSnapshot as persistTopoSnapshot,
|
|
8
8
|
listTopoSnapshots as readTopoSnapshots,
|
|
9
9
|
pinTopoSnapshot,
|
|
10
10
|
unpinTopoSnapshot,
|
|
11
|
-
} from '@ontrails/
|
|
12
|
-
import type {
|
|
13
|
-
import { deriveTrailsDbPath } from '@ontrails/core/internal/trails-db';
|
|
11
|
+
} from '@ontrails/topographer';
|
|
12
|
+
import type { TopoSnapshot } from '@ontrails/topographer';
|
|
14
13
|
import { z } from 'zod';
|
|
15
14
|
|
|
15
|
+
import {
|
|
16
|
+
createIsolatedExampleRoot,
|
|
17
|
+
writeIsolatedExampleAppModule,
|
|
18
|
+
} from '../local-state-io.js';
|
|
19
|
+
|
|
20
|
+
import { requireTrailRootDir } from './root-dir.js';
|
|
16
21
|
import type { BriefReport, SurveyListReport } from './topo-reports.js';
|
|
17
22
|
|
|
18
23
|
/** Output schema for a topo snapshot record. Shared across topo trails. */
|
|
@@ -27,11 +32,13 @@ export const topoSnapshotOutput = z.object({
|
|
|
27
32
|
trailCount: z.number(),
|
|
28
33
|
});
|
|
29
34
|
|
|
30
|
-
export const DEFAULT_APP_MODULE = './src/app.ts';
|
|
31
35
|
export const DEFAULT_TOPO_HISTORY_LIMIT = 10;
|
|
32
36
|
export const LOCK_PATH = '.trails/trails.lock';
|
|
33
37
|
const EXAMPLE_APP_MODULE = fileURLToPath(new URL('../app.ts', import.meta.url));
|
|
34
38
|
|
|
39
|
+
const uniqueExampleRootName = (name: string): string =>
|
|
40
|
+
`${name}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
41
|
+
|
|
35
42
|
export interface TopoSummaryReport {
|
|
36
43
|
readonly app: BriefReport;
|
|
37
44
|
readonly dbPath: string;
|
|
@@ -51,8 +58,8 @@ export interface TopoHistoryReport {
|
|
|
51
58
|
export interface TopoExportReport {
|
|
52
59
|
readonly hash: string;
|
|
53
60
|
readonly lockPath: string;
|
|
54
|
-
readonly mapPath: string;
|
|
55
61
|
readonly snapshot: TopoSnapshot;
|
|
62
|
+
readonly topoPath: string;
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
export interface TopoVerifyReport {
|
|
@@ -62,7 +69,7 @@ export interface TopoVerifyReport {
|
|
|
62
69
|
readonly stale: false;
|
|
63
70
|
}
|
|
64
71
|
|
|
65
|
-
export const deriveRootDir = (cwd?: string): string => cwd
|
|
72
|
+
export const deriveRootDir = (cwd?: string): string => requireTrailRootDir(cwd);
|
|
66
73
|
|
|
67
74
|
const safeGit = (cwd: string, args: readonly string[]): string | undefined => {
|
|
68
75
|
const proc = Bun.spawnSync({
|
|
@@ -137,30 +144,13 @@ const buildSnapshotInput = (
|
|
|
137
144
|
export const createIsolatedExampleInput = (
|
|
138
145
|
name: string
|
|
139
146
|
): { readonly module: string; readonly rootDir: string } => {
|
|
140
|
-
const rootDir =
|
|
141
|
-
rmSync(rootDir, { force: true, recursive: true });
|
|
142
|
-
mkdirSync(rootDir, { recursive: true });
|
|
147
|
+
const rootDir = createIsolatedExampleRoot(uniqueExampleRootName(name));
|
|
143
148
|
return {
|
|
144
|
-
module: EXAMPLE_APP_MODULE,
|
|
149
|
+
module: writeIsolatedExampleAppModule(rootDir, EXAMPLE_APP_MODULE),
|
|
145
150
|
rootDir,
|
|
146
151
|
};
|
|
147
152
|
};
|
|
148
153
|
|
|
149
|
-
export const createCurrentTopoSnapshot = (
|
|
150
|
-
app: Topo,
|
|
151
|
-
options?: { readonly rootDir?: string }
|
|
152
|
-
): TopoSnapshot => {
|
|
153
|
-
const rootDir = deriveRootDir(options?.rootDir);
|
|
154
|
-
const result = persistTopoSnapshot(app, {
|
|
155
|
-
rootDir,
|
|
156
|
-
...buildSnapshotInput(app, rootDir),
|
|
157
|
-
});
|
|
158
|
-
if (result.isErr()) {
|
|
159
|
-
throw result.error;
|
|
160
|
-
}
|
|
161
|
-
return result.value;
|
|
162
|
-
};
|
|
163
|
-
|
|
164
154
|
export const listTopoHistory = (options?: {
|
|
165
155
|
readonly limit?: number;
|
|
166
156
|
readonly rootDir?: string;
|