@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.
Files changed (197) hide show
  1. package/CHANGELOG.md +208 -0
  2. package/README.md +27 -0
  3. package/package.json +19 -8
  4. package/src/app.ts +17 -7
  5. package/src/clack.ts +1 -1
  6. package/src/cli.ts +304 -10
  7. package/src/completions.ts +240 -0
  8. package/src/load-app-mirror.ts +160 -0
  9. package/src/local-state-io.ts +153 -0
  10. package/src/project-writes.ts +320 -0
  11. package/src/run-collision.ts +125 -0
  12. package/src/run-completions-install.ts +179 -0
  13. package/src/run-example.ts +149 -0
  14. package/src/run-examples.ts +148 -0
  15. package/src/run-quiet.ts +75 -0
  16. package/src/run-trace.ts +273 -0
  17. package/src/run-warden.ts +39 -0
  18. package/src/run-watch.ts +432 -0
  19. package/src/scaffold-versions.generated.ts +12 -0
  20. package/src/trails/add-surface.ts +172 -0
  21. package/src/trails/add-trail.ts +73 -27
  22. package/src/trails/add-verify.ts +68 -23
  23. package/src/trails/completions-complete.ts +165 -0
  24. package/src/trails/completions.ts +47 -0
  25. package/src/trails/create-scaffold.ts +101 -35
  26. package/src/trails/create.ts +87 -74
  27. package/src/trails/dev-clean.ts +31 -22
  28. package/src/trails/dev-reset.ts +9 -3
  29. package/src/trails/dev-stats.ts +28 -20
  30. package/src/trails/dev-support.ts +109 -95
  31. package/src/trails/draft-promote.ts +351 -107
  32. package/src/trails/guide.ts +55 -38
  33. package/src/trails/load-app.ts +712 -38
  34. package/src/trails/root-dir.ts +21 -0
  35. package/src/trails/run-example.ts +482 -0
  36. package/src/trails/run-examples.ts +141 -0
  37. package/src/trails/run.ts +403 -0
  38. package/src/trails/survey.ts +517 -186
  39. package/src/trails/topo-activation.ts +385 -0
  40. package/src/trails/topo-compile.ts +55 -0
  41. package/src/trails/topo-history.ts +14 -11
  42. package/src/trails/topo-output-schemas.ts +175 -0
  43. package/src/trails/topo-pin.ts +25 -16
  44. package/src/trails/topo-read-support.ts +178 -238
  45. package/src/trails/topo-reports.ts +445 -63
  46. package/src/trails/topo-store-support.ts +67 -35
  47. package/src/trails/topo-support.ts +93 -147
  48. package/src/trails/topo-unpin.ts +17 -7
  49. package/src/trails/topo-verify.ts +19 -10
  50. package/src/trails/topo.ts +64 -31
  51. package/src/trails/warden-guide.ts +121 -0
  52. package/src/trails/warden.ts +137 -47
  53. package/src/versions.ts +28 -0
  54. package/.turbo/turbo-build.log +0 -1
  55. package/.turbo/turbo-lint.log +0 -3
  56. package/.turbo/turbo-typecheck.log +0 -1
  57. package/__tests__/examples.test.ts +0 -20
  58. package/dist/bin/trails.d.ts +0 -3
  59. package/dist/bin/trails.d.ts.map +0 -1
  60. package/dist/bin/trails.js +0 -4
  61. package/dist/bin/trails.js.map +0 -1
  62. package/dist/src/app.d.ts +0 -2
  63. package/dist/src/app.d.ts.map +0 -1
  64. package/dist/src/app.js +0 -22
  65. package/dist/src/app.js.map +0 -1
  66. package/dist/src/clack.d.ts +0 -9
  67. package/dist/src/clack.d.ts.map +0 -1
  68. package/dist/src/clack.js +0 -84
  69. package/dist/src/clack.js.map +0 -1
  70. package/dist/src/cli.d.ts +0 -2
  71. package/dist/src/cli.d.ts.map +0 -1
  72. package/dist/src/cli.js +0 -13
  73. package/dist/src/cli.js.map +0 -1
  74. package/dist/src/trails/add-surface.d.ts +0 -13
  75. package/dist/src/trails/add-surface.d.ts.map +0 -1
  76. package/dist/src/trails/add-surface.js +0 -88
  77. package/dist/src/trails/add-surface.js.map +0 -1
  78. package/dist/src/trails/add-trail.d.ts +0 -10
  79. package/dist/src/trails/add-trail.d.ts.map +0 -1
  80. package/dist/src/trails/add-trail.js +0 -77
  81. package/dist/src/trails/add-trail.js.map +0 -1
  82. package/dist/src/trails/add-trailhead.d.ts +0 -13
  83. package/dist/src/trails/add-trailhead.d.ts.map +0 -1
  84. package/dist/src/trails/add-trailhead.js +0 -88
  85. package/dist/src/trails/add-trailhead.js.map +0 -1
  86. package/dist/src/trails/add-verify.d.ts +0 -10
  87. package/dist/src/trails/add-verify.d.ts.map +0 -1
  88. package/dist/src/trails/add-verify.js +0 -67
  89. package/dist/src/trails/add-verify.js.map +0 -1
  90. package/dist/src/trails/create-scaffold.d.ts +0 -15
  91. package/dist/src/trails/create-scaffold.d.ts.map +0 -1
  92. package/dist/src/trails/create-scaffold.js +0 -288
  93. package/dist/src/trails/create-scaffold.js.map +0 -1
  94. package/dist/src/trails/create.d.ts +0 -22
  95. package/dist/src/trails/create.d.ts.map +0 -1
  96. package/dist/src/trails/create.js +0 -121
  97. package/dist/src/trails/create.js.map +0 -1
  98. package/dist/src/trails/dev-clean.d.ts +0 -9
  99. package/dist/src/trails/dev-clean.d.ts.map +0 -1
  100. package/dist/src/trails/dev-clean.js +0 -65
  101. package/dist/src/trails/dev-clean.js.map +0 -1
  102. package/dist/src/trails/dev-reset.d.ts +0 -6
  103. package/dist/src/trails/dev-reset.d.ts.map +0 -1
  104. package/dist/src/trails/dev-reset.js +0 -38
  105. package/dist/src/trails/dev-reset.js.map +0 -1
  106. package/dist/src/trails/dev-stats.d.ts +0 -7
  107. package/dist/src/trails/dev-stats.d.ts.map +0 -1
  108. package/dist/src/trails/dev-stats.js +0 -61
  109. package/dist/src/trails/dev-stats.js.map +0 -1
  110. package/dist/src/trails/dev-support.d.ts +0 -64
  111. package/dist/src/trails/dev-support.d.ts.map +0 -1
  112. package/dist/src/trails/dev-support.js +0 -178
  113. package/dist/src/trails/dev-support.js.map +0 -1
  114. package/dist/src/trails/draft-promote.d.ts +0 -18
  115. package/dist/src/trails/draft-promote.d.ts.map +0 -1
  116. package/dist/src/trails/draft-promote.js +0 -386
  117. package/dist/src/trails/draft-promote.js.map +0 -1
  118. package/dist/src/trails/guide.d.ts +0 -21
  119. package/dist/src/trails/guide.d.ts.map +0 -1
  120. package/dist/src/trails/guide.js +0 -64
  121. package/dist/src/trails/guide.js.map +0 -1
  122. package/dist/src/trails/load-app.d.ts +0 -6
  123. package/dist/src/trails/load-app.d.ts.map +0 -1
  124. package/dist/src/trails/load-app.js +0 -67
  125. package/dist/src/trails/load-app.js.map +0 -1
  126. package/dist/src/trails/project.d.ts +0 -8
  127. package/dist/src/trails/project.d.ts.map +0 -1
  128. package/dist/src/trails/project.js +0 -54
  129. package/dist/src/trails/project.js.map +0 -1
  130. package/dist/src/trails/survey.d.ts +0 -18
  131. package/dist/src/trails/survey.d.ts.map +0 -1
  132. package/dist/src/trails/survey.js +0 -212
  133. package/dist/src/trails/survey.js.map +0 -1
  134. package/dist/src/trails/topo-constants.d.ts +0 -3
  135. package/dist/src/trails/topo-constants.d.ts.map +0 -1
  136. package/dist/src/trails/topo-constants.js +0 -3
  137. package/dist/src/trails/topo-constants.js.map +0 -1
  138. package/dist/src/trails/topo-export.d.ts +0 -18
  139. package/dist/src/trails/topo-export.d.ts.map +0 -1
  140. package/dist/src/trails/topo-export.js +0 -34
  141. package/dist/src/trails/topo-export.js.map +0 -1
  142. package/dist/src/trails/topo-history.d.ts +0 -24
  143. package/dist/src/trails/topo-history.d.ts.map +0 -1
  144. package/dist/src/trails/topo-history.js +0 -33
  145. package/dist/src/trails/topo-history.js.map +0 -1
  146. package/dist/src/trails/topo-pin.d.ts +0 -21
  147. package/dist/src/trails/topo-pin.d.ts.map +0 -1
  148. package/dist/src/trails/topo-pin.js +0 -35
  149. package/dist/src/trails/topo-pin.js.map +0 -1
  150. package/dist/src/trails/topo-read-support.d.ts +0 -54
  151. package/dist/src/trails/topo-read-support.d.ts.map +0 -1
  152. package/dist/src/trails/topo-read-support.js +0 -178
  153. package/dist/src/trails/topo-read-support.js.map +0 -1
  154. package/dist/src/trails/topo-reports.d.ts +0 -50
  155. package/dist/src/trails/topo-reports.d.ts.map +0 -1
  156. package/dist/src/trails/topo-reports.js +0 -122
  157. package/dist/src/trails/topo-reports.js.map +0 -1
  158. package/dist/src/trails/topo-show.d.ts +0 -23
  159. package/dist/src/trails/topo-show.d.ts.map +0 -1
  160. package/dist/src/trails/topo-show.js +0 -53
  161. package/dist/src/trails/topo-show.js.map +0 -1
  162. package/dist/src/trails/topo-store-support.d.ts +0 -13
  163. package/dist/src/trails/topo-store-support.d.ts.map +0 -1
  164. package/dist/src/trails/topo-store-support.js +0 -55
  165. package/dist/src/trails/topo-store-support.js.map +0 -1
  166. package/dist/src/trails/topo-support.d.ts +0 -87
  167. package/dist/src/trails/topo-support.d.ts.map +0 -1
  168. package/dist/src/trails/topo-support.js +0 -165
  169. package/dist/src/trails/topo-support.js.map +0 -1
  170. package/dist/src/trails/topo-unpin.d.ts +0 -15
  171. package/dist/src/trails/topo-unpin.d.ts.map +0 -1
  172. package/dist/src/trails/topo-unpin.js +0 -39
  173. package/dist/src/trails/topo-unpin.js.map +0 -1
  174. package/dist/src/trails/topo-verify.d.ts +0 -5
  175. package/dist/src/trails/topo-verify.d.ts.map +0 -1
  176. package/dist/src/trails/topo-verify.js +0 -28
  177. package/dist/src/trails/topo-verify.js.map +0 -1
  178. package/dist/src/trails/topo.d.ts +0 -5
  179. package/dist/src/trails/topo.d.ts.map +0 -1
  180. package/dist/src/trails/topo.js +0 -67
  181. package/dist/src/trails/topo.js.map +0 -1
  182. package/dist/src/trails/warden.d.ts +0 -19
  183. package/dist/src/trails/warden.d.ts.map +0 -1
  184. package/dist/src/trails/warden.js +0 -89
  185. package/dist/src/trails/warden.js.map +0 -1
  186. package/dist/tsconfig.tsbuildinfo +0 -1
  187. package/src/__tests__/create.test.ts +0 -351
  188. package/src/__tests__/draft-promote.test.ts +0 -144
  189. package/src/__tests__/guide.test.ts +0 -91
  190. package/src/__tests__/load-app.test.ts +0 -58
  191. package/src/__tests__/survey.test.ts +0 -301
  192. package/src/__tests__/topo-dev.test.ts +0 -424
  193. package/src/__tests__/warden.test.ts +0 -74
  194. package/src/trails/add-trailhead.ts +0 -121
  195. package/src/trails/topo-export.ts +0 -39
  196. package/src/trails/topo-show.ts +0 -58
  197. package/tsconfig.json +0 -9
@@ -1,56 +1,189 @@
1
- import type { Topo, Trail } from '@ontrails/core';
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 interface BriefReport {
6
- readonly name: string;
7
- readonly version: string;
8
- readonly contractVersion: string;
9
- readonly features: {
10
- readonly provisions: boolean;
11
- readonly outputSchemas: boolean;
12
- readonly examples: boolean;
13
- readonly detours: boolean;
14
- readonly signals: boolean;
15
- };
16
- readonly trails: number;
17
- readonly signals: number;
18
- readonly provisions: number;
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 provisionCount: number;
30
- readonly provisions: 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: 'provision';
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: Trail<unknown, unknown>['detours'] | null;
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: string;
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 provisions: readonly string[];
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
- hasProvisions: boolean;
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
- hasProvisions: trails.some(
207
+ hasResources: trails.some(
75
208
  (r) =>
76
- Array.isArray(r['provisions']) &&
77
- (r['provisions'] as unknown[]).length > 0
209
+ Array.isArray(r['resources']) &&
210
+ (r['resources'] as unknown[]).length > 0
78
211
  ),
79
212
  };
80
213
  };
81
214
 
82
- export const generateBriefReport = (app: Topo): BriefReport => {
83
- const { hasDetours, hasExamples, hasOutputSchemas, hasProvisions } =
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
- provisions: hasProvisions,
225
+ resources: hasResources,
93
226
  signals: app.signals.size > 0,
94
227
  },
95
228
  name: app.name,
96
- provisions: app.provisions.size,
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 buildProvisionUsage = (
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 declaredProvision of trailDef.provisions) {
125
- const users = usage.get(declaredProvision.id) ?? [];
257
+ for (const declaredResource of trailDef.resources) {
258
+ const users = usage.get(declaredResource.id) ?? [];
126
259
  users.push(trailDef.id);
127
- usage.set(declaredProvision.id, users);
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 provisionHealthStatus = (provision: {
269
+ const resourceHealthStatus = (resource: {
137
270
  health?: unknown;
138
271
  }): 'available' | 'none' =>
139
- provision.health === undefined ? 'none' : 'available';
272
+ resource.health === undefined ? 'none' : 'available';
140
273
 
141
- export const formatProvisionDetail = (
142
- app: Topo,
143
- provisionId: string
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 ? provisionHealthStatus(item) : 'none',
151
- id: provisionId,
152
- kind: 'provision',
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 formatProvisionList = (app: Topo): SurveyListReport['provisions'] => {
159
- const usage = buildProvisionUsage(app);
288
+ const formatResourceList = (app: Topo): SurveyListReport['resources'] => {
289
+ const usage = buildResourceUsage(app);
160
290
  return app
161
- .listProvisions()
162
- .map((provision) => ({
163
- description: provision.description ?? null,
164
- health: provisionHealthStatus(provision),
165
- id: provision.id,
166
- kind: provision.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(provision.id) ?? [],
298
+ usedBy: usage.get(resource.id) ?? [],
169
299
  }))
170
300
  .toSorted((a, b) => a.id.localeCompare(b.id));
171
301
  };
172
302
 
173
- export const generateSurveyList = (app: Topo): SurveyListReport => {
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 provisions = formatProvisionList(app);
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
- provisionCount: provisions.length,
199
- provisions,
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
- export const generateTrailDetail = (
204
- item: Trail<unknown, unknown>
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.detours ?? null,
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: item.kind,
218
- provisions: item.provisions.map((provision) => provision.id).toSorted(),
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
  };