@ontrails/trails 1.0.0-beta.14 → 1.0.0-beta.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/.turbo/turbo-lint.log +1 -1
  2. package/CHANGELOG.md +13 -0
  3. package/__tests__/examples.test.ts +29 -4
  4. package/dist/src/app.js +2 -2
  5. package/dist/src/app.js.map +1 -1
  6. package/dist/src/clack.d.ts +1 -1
  7. package/dist/src/clack.js +1 -1
  8. package/dist/src/cli.js +4 -3
  9. package/dist/src/cli.js.map +1 -1
  10. package/dist/src/trails/add-surface.d.ts +3 -3
  11. package/dist/src/trails/add-surface.d.ts.map +1 -1
  12. package/dist/src/trails/add-surface.js +46 -24
  13. package/dist/src/trails/add-surface.js.map +1 -1
  14. package/dist/src/trails/add-trail.d.ts +3 -1
  15. package/dist/src/trails/add-trail.d.ts.map +1 -1
  16. package/dist/src/trails/add-trail.js +38 -11
  17. package/dist/src/trails/add-trail.js.map +1 -1
  18. package/dist/src/trails/add-trailhead.d.ts +1 -1
  19. package/dist/src/trails/add-trailhead.d.ts.map +1 -1
  20. package/dist/src/trails/add-trailhead.js +4 -4
  21. package/dist/src/trails/add-verify.d.ts +1 -1
  22. package/dist/src/trails/add-verify.d.ts.map +1 -1
  23. package/dist/src/trails/add-verify.js +7 -6
  24. package/dist/src/trails/add-verify.js.map +1 -1
  25. package/dist/src/trails/create-scaffold.d.ts +1 -1
  26. package/dist/src/trails/create-scaffold.d.ts.map +1 -1
  27. package/dist/src/trails/create-scaffold.js +10 -3
  28. package/dist/src/trails/create-scaffold.js.map +1 -1
  29. package/dist/src/trails/create.d.ts +10 -14
  30. package/dist/src/trails/create.d.ts.map +1 -1
  31. package/dist/src/trails/create.js +42 -37
  32. package/dist/src/trails/create.js.map +1 -1
  33. package/dist/src/trails/dev-clean.d.ts +4 -4
  34. package/dist/src/trails/dev-clean.d.ts.map +1 -1
  35. package/dist/src/trails/dev-clean.js +22 -21
  36. package/dist/src/trails/dev-clean.js.map +1 -1
  37. package/dist/src/trails/dev-reset.d.ts +1 -1
  38. package/dist/src/trails/dev-reset.d.ts.map +1 -1
  39. package/dist/src/trails/dev-reset.js +3 -2
  40. package/dist/src/trails/dev-reset.js.map +1 -1
  41. package/dist/src/trails/dev-stats.d.ts +4 -4
  42. package/dist/src/trails/dev-stats.d.ts.map +1 -1
  43. package/dist/src/trails/dev-stats.js +19 -19
  44. package/dist/src/trails/dev-stats.js.map +1 -1
  45. package/dist/src/trails/dev-support.d.ts +17 -17
  46. package/dist/src/trails/dev-support.d.ts.map +1 -1
  47. package/dist/src/trails/dev-support.js +60 -57
  48. package/dist/src/trails/dev-support.js.map +1 -1
  49. package/dist/src/trails/draft-promote.d.ts +1 -1
  50. package/dist/src/trails/draft-promote.d.ts.map +1 -1
  51. package/dist/src/trails/draft-promote.js +30 -16
  52. package/dist/src/trails/draft-promote.js.map +1 -1
  53. package/dist/src/trails/guide.d.ts +2 -2
  54. package/dist/src/trails/guide.d.ts.map +1 -1
  55. package/dist/src/trails/guide.js +1 -4
  56. package/dist/src/trails/guide.js.map +1 -1
  57. package/dist/src/trails/load-app.d.ts +7 -1
  58. package/dist/src/trails/load-app.d.ts.map +1 -1
  59. package/dist/src/trails/load-app.js +374 -26
  60. package/dist/src/trails/load-app.js.map +1 -1
  61. package/dist/src/trails/survey.d.ts +5 -5
  62. package/dist/src/trails/survey.d.ts.map +1 -1
  63. package/dist/src/trails/survey.js +51 -29
  64. package/dist/src/trails/survey.js.map +1 -1
  65. package/dist/src/trails/topo-export.d.ts +5 -4
  66. package/dist/src/trails/topo-export.d.ts.map +1 -1
  67. package/dist/src/trails/topo-export.js +4 -7
  68. package/dist/src/trails/topo-export.js.map +1 -1
  69. package/dist/src/trails/topo-history.d.ts +6 -10
  70. package/dist/src/trails/topo-history.d.ts.map +1 -1
  71. package/dist/src/trails/topo-history.js +7 -8
  72. package/dist/src/trails/topo-history.js.map +1 -1
  73. package/dist/src/trails/topo-pin.d.ts +5 -9
  74. package/dist/src/trails/topo-pin.d.ts.map +1 -1
  75. package/dist/src/trails/topo-pin.js +5 -9
  76. package/dist/src/trails/topo-pin.js.map +1 -1
  77. package/dist/src/trails/topo-read-support.d.ts +12 -8
  78. package/dist/src/trails/topo-read-support.d.ts.map +1 -1
  79. package/dist/src/trails/topo-read-support.js +71 -82
  80. package/dist/src/trails/topo-read-support.js.map +1 -1
  81. package/dist/src/trails/topo-reports.d.ts +15 -11
  82. package/dist/src/trails/topo-reports.d.ts.map +1 -1
  83. package/dist/src/trails/topo-reports.js +40 -34
  84. package/dist/src/trails/topo-reports.js.map +1 -1
  85. package/dist/src/trails/topo-show.d.ts +4 -4
  86. package/dist/src/trails/topo-show.d.ts.map +1 -1
  87. package/dist/src/trails/topo-show.js +8 -12
  88. package/dist/src/trails/topo-show.js.map +1 -1
  89. package/dist/src/trails/topo-store-support.d.ts +2 -2
  90. package/dist/src/trails/topo-store-support.d.ts.map +1 -1
  91. package/dist/src/trails/topo-store-support.js +22 -22
  92. package/dist/src/trails/topo-store-support.js.map +1 -1
  93. package/dist/src/trails/topo-support.d.ts +19 -30
  94. package/dist/src/trails/topo-support.d.ts.map +1 -1
  95. package/dist/src/trails/topo-support.js +63 -96
  96. package/dist/src/trails/topo-support.js.map +1 -1
  97. package/dist/src/trails/topo-unpin.d.ts +9 -4
  98. package/dist/src/trails/topo-unpin.d.ts.map +1 -1
  99. package/dist/src/trails/topo-unpin.js +9 -4
  100. package/dist/src/trails/topo-unpin.js.map +1 -1
  101. package/dist/src/trails/topo-verify.d.ts +2 -2
  102. package/dist/src/trails/topo-verify.d.ts.map +1 -1
  103. package/dist/src/trails/topo-verify.js +1 -5
  104. package/dist/src/trails/topo-verify.js.map +1 -1
  105. package/dist/src/trails/topo.d.ts +2 -2
  106. package/dist/src/trails/topo.d.ts.map +1 -1
  107. package/dist/src/trails/topo.js +6 -10
  108. package/dist/src/trails/topo.js.map +1 -1
  109. package/dist/src/trails/warden.d.ts +3 -2
  110. package/dist/src/trails/warden.d.ts.map +1 -1
  111. package/dist/src/trails/warden.js +11 -2
  112. package/dist/src/trails/warden.js.map +1 -1
  113. package/dist/src/versions.d.ts +12 -0
  114. package/dist/src/versions.d.ts.map +1 -0
  115. package/dist/src/versions.js +23 -0
  116. package/dist/src/versions.js.map +1 -0
  117. package/dist/tsconfig.tsbuildinfo +1 -1
  118. package/package.json +8 -8
  119. package/src/__tests__/add-trail.test.ts +97 -0
  120. package/src/__tests__/create.test.ts +91 -27
  121. package/src/__tests__/guide.test.ts +10 -5
  122. package/src/__tests__/load-app.test.ts +365 -4
  123. package/src/__tests__/survey.test.ts +143 -67
  124. package/src/__tests__/topo-dev.test.ts +48 -46
  125. package/src/app.ts +2 -2
  126. package/src/clack.ts +1 -1
  127. package/src/cli.ts +4 -3
  128. package/src/trails/add-surface.ts +150 -0
  129. package/src/trails/add-trail.ts +46 -10
  130. package/src/trails/add-verify.ts +11 -6
  131. package/src/trails/create-scaffold.ts +16 -3
  132. package/src/trails/create.ts +76 -71
  133. package/src/trails/dev-clean.ts +25 -21
  134. package/src/trails/dev-reset.ts +3 -2
  135. package/src/trails/dev-stats.ts +22 -19
  136. package/src/trails/dev-support.ts +88 -86
  137. package/src/trails/draft-promote.ts +70 -35
  138. package/src/trails/guide.ts +1 -4
  139. package/src/trails/load-app.ts +511 -32
  140. package/src/trails/survey.ts +60 -35
  141. package/src/trails/topo-export.ts +5 -9
  142. package/src/trails/topo-history.ts +8 -10
  143. package/src/trails/topo-pin.ts +9 -13
  144. package/src/trails/topo-read-support.ts +103 -106
  145. package/src/trails/topo-reports.ts +54 -47
  146. package/src/trails/topo-show.ts +8 -12
  147. package/src/trails/topo-store-support.ts +36 -28
  148. package/src/trails/topo-support.ts +90 -134
  149. package/src/trails/topo-unpin.ts +11 -6
  150. package/src/trails/topo-verify.ts +1 -5
  151. package/src/trails/topo.ts +6 -10
  152. package/src/trails/warden.ts +12 -3
  153. package/src/versions.ts +43 -0
  154. package/tsconfig.tests.json +10 -0
  155. package/src/trails/add-trailhead.ts +0 -121
@@ -1,65 +1,69 @@
1
1
  /**
2
2
  * Read-only topo store consumer helpers.
3
3
  *
4
- * Extracted from topo-support.ts so this branch (trl-132) owns its own file,
5
- * keeping absorb routing clean across the stack.
4
+ * Extracted from topo-support.ts to isolate read-only store consumer helpers,
5
+ * keeping module boundaries clean.
6
6
  */
7
7
 
8
8
  import { existsSync } from 'node:fs';
9
9
  import { join } from 'node:path';
10
10
 
11
- import type { Topo } from '@ontrails/core';
12
11
  import {
13
12
  ConflictError,
14
13
  createTopoStore,
15
14
  InternalError,
15
+ listTopoSnapshots,
16
16
  NotFoundError,
17
17
  Result,
18
18
  } from '@ontrails/core';
19
- import type { TopoSaveRecord } from '@ontrails/core/internal/topo-saves';
20
- import { listTopoSaves } from '@ontrails/core/internal/topo-saves';
19
+ import type { Topo, TopoSnapshot } from '@ontrails/core';
21
20
  import {
22
- openReadTrailsDb,
23
- resolveTrailsDbPath,
24
- resolveTrailsDir,
21
+ deriveTrailsDbPath,
22
+ deriveTrailsDir,
25
23
  } from '@ontrails/core/internal/trails-db';
26
- import { readTrailheadLockData } from '@ontrails/schema';
24
+ import { readSurfaceLockData } from '@ontrails/schema';
27
25
 
28
26
  import type { BriefReport, SurveyListReport } from './topo-reports.js';
29
27
  import type { TopoSummaryReport, TopoVerifyReport } from './topo-support.js';
30
28
  import { REPORT_CONTRACT_VERSION, REPORT_VERSION } from './topo-constants.js';
31
29
  import {
32
- createCurrentTopoSave,
30
+ createCurrentTopoSnapshot,
31
+ deriveRootDir,
33
32
  LOCK_PATH,
34
- resolveRootDir,
35
33
  } from './topo-support.js';
36
34
 
37
35
  // ---------------------------------------------------------------------------
38
36
  // Internal types
39
37
  // ---------------------------------------------------------------------------
40
38
 
41
- interface StoredTrailheadMapEntry {
42
- readonly detours?: Readonly<Record<string, readonly string[]>>;
43
- readonly kind: 'provision' | 'signal' | 'trail';
39
+ interface StoredSurfaceMapEntry {
40
+ readonly detours?: readonly {
41
+ readonly on: string;
42
+ readonly maxAttempts: number;
43
+ }[];
44
+ readonly kind: 'resource' | 'signal' | 'trail';
44
45
  }
45
46
 
46
47
  interface CurrentTrailDetail {
47
48
  readonly crosses: string[];
48
49
  readonly description: string | null;
49
- readonly detours: Readonly<Record<string, readonly string[]>> | null;
50
+ readonly detours:
51
+ | readonly { readonly on: string; readonly maxAttempts: number }[]
52
+ | null;
50
53
  readonly examples: unknown[];
51
54
  readonly id: string;
52
55
  readonly intent: 'destroy' | 'read' | 'write';
53
56
  readonly kind: string;
54
- readonly provisions: string[];
57
+ readonly pattern: string | null;
58
+ readonly resources: string[];
55
59
  readonly safety: string;
56
60
  }
57
61
 
58
- interface CurrentProvisionDetail {
62
+ interface CurrentResourceDetail {
59
63
  readonly description: string | null;
60
64
  readonly health: 'available' | 'none';
61
65
  readonly id: string;
62
- readonly kind: 'provision';
66
+ readonly kind: 'resource';
63
67
  readonly lifetime: 'singleton';
64
68
  readonly usedBy: string[];
65
69
  }
@@ -68,18 +72,17 @@ interface CurrentProvisionDetail {
68
72
  // Store helpers
69
73
  // ---------------------------------------------------------------------------
70
74
 
71
- const topoStoreRef = (saveId: string) => ({ saveId }) as const;
75
+ const topoStoreRef = (snapshotId: string) => ({ snapshotId }) as const;
72
76
 
73
77
  const hasCommittedLock = (trailsDir: string): boolean =>
74
- existsSync(join(trailsDir, 'trails.lock')) ||
75
- existsSync(join(trailsDir, 'trailhead.lock'));
78
+ existsSync(join(trailsDir, 'trails.lock'));
76
79
 
77
- const readTrailheadEntries = (
78
- trailheadMapJson: string
79
- ): readonly StoredTrailheadMapEntry[] =>
80
+ const readSurfaceEntries = (
81
+ surfaceMapJson: string
82
+ ): readonly StoredSurfaceMapEntry[] =>
80
83
  (
81
- JSON.parse(trailheadMapJson) as {
82
- readonly entries: readonly StoredTrailheadMapEntry[];
84
+ JSON.parse(surfaceMapJson) as {
85
+ readonly entries: readonly StoredSurfaceMapEntry[];
83
86
  }
84
87
  ).entries;
85
88
 
@@ -87,32 +90,30 @@ const buildBriefReportFromStore = (
87
90
  app: Topo,
88
91
  store: ReturnType<typeof createTopoStore>,
89
92
  ref: ReturnType<typeof topoStoreRef>,
90
- save: TopoSaveRecord
93
+ snapshot: TopoSnapshot
91
94
  ): BriefReport => {
92
- const trails = store.trails.list({ save: ref });
95
+ const trails = store.trails.list({ snapshot: ref });
93
96
  const exportRecord = store.exports.get(ref);
94
97
  const trailEntries =
95
98
  exportRecord === undefined
96
99
  ? []
97
- : readTrailheadEntries(exportRecord.trailheadMapJson).filter(
100
+ : readSurfaceEntries(exportRecord.surfaceMapJson).filter(
98
101
  (entry) => entry.kind === 'trail'
99
102
  );
100
103
 
101
104
  return {
102
105
  contractVersion: REPORT_CONTRACT_VERSION,
103
106
  features: {
104
- detours: trailEntries.some(
105
- (entry) => (Object.keys(entry.detours ?? {}).length ?? 0) > 0
106
- ),
107
+ detours: trailEntries.some((entry) => (entry.detours ?? []).length > 0),
107
108
  examples: trails.some((trail) => trail.hasExamples),
108
109
  outputSchemas: trails.some((trail) => trail.hasOutput),
109
- provisions: save.provisionCount > 0,
110
- signals: save.signalCount > 0,
110
+ resources: snapshot.resourceCount > 0,
111
+ signals: snapshot.signalCount > 0,
111
112
  },
112
113
  name: app.name,
113
- provisions: save.provisionCount,
114
- signals: save.signalCount,
115
- trails: save.trailCount,
114
+ resources: snapshot.resourceCount,
115
+ signals: snapshot.signalCount,
116
+ trails: snapshot.trailCount,
116
117
  version: REPORT_VERSION,
117
118
  };
118
119
  };
@@ -121,8 +122,8 @@ const buildSurveyListFromStore = (
121
122
  store: ReturnType<typeof createTopoStore>,
122
123
  ref: ReturnType<typeof topoStoreRef>
123
124
  ): SurveyListReport => {
124
- const trails = store.trails.list({ save: ref });
125
- const provisions = store.provisions.list({ save: ref });
125
+ const trails = store.trails.list({ snapshot: ref });
126
+ const resources = store.resources.list({ snapshot: ref });
126
127
 
127
128
  return {
128
129
  count: trails.length,
@@ -132,43 +133,46 @@ const buildSurveyListFromStore = (
132
133
  kind: trail.kind,
133
134
  safety: trail.safety,
134
135
  })),
135
- provisionCount: provisions.length,
136
- provisions: provisions.map((provision) => ({
137
- description: provision.description,
138
- health: provision.health,
139
- id: provision.id,
140
- kind: provision.kind,
141
- lifetime: provision.lifetime,
142
- usedBy: provision.usedBy,
136
+ resourceCount: resources.length,
137
+ resources: resources.map((resource) => ({
138
+ description: resource.description,
139
+ health: resource.health,
140
+ id: resource.id,
141
+ kind: resource.kind,
142
+ lifetime: resource.lifetime,
143
+ usedBy: resource.usedBy,
143
144
  })),
144
145
  };
145
146
  };
146
147
 
147
148
  const buildTrailDetailFromStore = (
148
- detail: ReturnType<ReturnType<typeof createTopoStore>['trails']['get']>
149
+ detail: NonNullable<
150
+ ReturnType<ReturnType<typeof createTopoStore>['trails']['get']>
151
+ >
149
152
  ): CurrentTrailDetail => ({
150
- crosses: [...(detail?.crosses ?? [])],
151
- description: detail?.description ?? null,
152
- detours: detail?.detours ?? null,
153
- examples: [...(detail?.examples ?? [])],
154
- id: detail?.id ?? '',
155
- intent: detail?.intent ?? 'write',
156
- kind: detail?.kind ?? 'trail',
157
- provisions: [...(detail?.provisions ?? [])],
158
- safety: detail?.safety ?? '-',
153
+ crosses: [...detail.crosses],
154
+ description: detail.description,
155
+ detours: detail.detours,
156
+ examples: [...detail.examples],
157
+ id: detail.id,
158
+ intent: detail.intent,
159
+ kind: detail.kind,
160
+ pattern: detail.pattern,
161
+ resources: [...detail.resources],
162
+ safety: detail.safety,
159
163
  });
160
164
 
161
- const buildProvisionDetailFromStore = (
162
- provision: NonNullable<
163
- ReturnType<ReturnType<typeof createTopoStore>['provisions']['get']>
165
+ const buildResourceDetailFromStore = (
166
+ resource: NonNullable<
167
+ ReturnType<ReturnType<typeof createTopoStore>['resources']['get']>
164
168
  >
165
- ): CurrentProvisionDetail => ({
166
- description: provision.description,
167
- health: provision.health,
168
- id: provision.id,
169
- kind: provision.kind,
170
- lifetime: provision.lifetime,
171
- usedBy: [...provision.usedBy],
169
+ ): CurrentResourceDetail => ({
170
+ description: resource.description,
171
+ health: resource.health,
172
+ id: resource.id,
173
+ kind: resource.kind,
174
+ lifetime: resource.lifetime,
175
+ usedBy: [...resource.usedBy],
172
176
  });
173
177
 
174
178
  // ---------------------------------------------------------------------------
@@ -178,8 +182,9 @@ const buildProvisionDetailFromStore = (
178
182
  /**
179
183
  * Run a read callback against the latest topo store state.
180
184
  *
181
- * Uses the most recent existing save when available, only creating a new save
182
- * when no prior save exists. This avoids unbounded save accumulation from
185
+ * Uses the most recent existing snapshot when available, only creating a new
186
+ * snapshot when no prior snapshot exists. This avoids unbounded snapshot
187
+ * accumulation from
183
188
  * read-only operations like survey, guide, and show.
184
189
  */
185
190
  const withCurrentTopoStore = <T>(
@@ -188,24 +193,14 @@ const withCurrentTopoStore = <T>(
188
193
  read: (
189
194
  store: ReturnType<typeof createTopoStore>,
190
195
  ref: ReturnType<typeof topoStoreRef>,
191
- save: TopoSaveRecord
196
+ snapshot: TopoSnapshot
192
197
  ) => T
193
198
  ): T => {
194
- const dbPath = resolveTrailsDbPath({ rootDir });
195
- const existingSave = existsSync(dbPath)
196
- ? (() => {
197
- const db = openReadTrailsDb({ rootDir });
198
- try {
199
- return listTopoSaves(db)[0];
200
- } finally {
201
- db.close();
202
- }
203
- })()
204
- : undefined;
205
-
206
- const save = existingSave ?? createCurrentTopoSave(app, { rootDir });
199
+ const [existingSnapshot] = listTopoSnapshots({ limit: 1, rootDir });
200
+ const snapshot =
201
+ existingSnapshot ?? createCurrentTopoSnapshot(app, { rootDir });
207
202
  const store = createTopoStore({ rootDir });
208
- return read(store, topoStoreRef(save.id), save);
203
+ return read(store, topoStoreRef(snapshot.id), snapshot);
209
204
  };
210
205
 
211
206
  // ---------------------------------------------------------------------------
@@ -216,11 +211,11 @@ export const buildTopoSummary = (
216
211
  app: Topo,
217
212
  options?: { readonly rootDir?: string }
218
213
  ): TopoSummaryReport => {
219
- const rootDir = resolveRootDir(options?.rootDir);
220
- const trailsDir = resolveTrailsDir({ rootDir });
221
- return withCurrentTopoStore(app, rootDir, (store, ref, save) => ({
222
- app: buildBriefReportFromStore(app, store, ref, save),
223
- dbPath: resolveTrailsDbPath({ rootDir }),
214
+ const rootDir = deriveRootDir(options?.rootDir);
215
+ const trailsDir = deriveTrailsDir({ rootDir });
216
+ return withCurrentTopoStore(app, rootDir, (store, ref, snapshot) => ({
217
+ app: buildBriefReportFromStore(app, store, ref, snapshot),
218
+ dbPath: deriveTrailsDbPath({ rootDir }),
224
219
  list: buildSurveyListFromStore(store, ref),
225
220
  lockExists: hasCommittedLock(trailsDir),
226
221
  lockPath: LOCK_PATH,
@@ -231,9 +226,9 @@ export const buildCurrentTopoBrief = (
231
226
  app: Topo,
232
227
  options?: { readonly rootDir?: string }
233
228
  ): BriefReport => {
234
- const rootDir = resolveRootDir(options?.rootDir);
235
- return withCurrentTopoStore(app, rootDir, (store, ref, save) =>
236
- buildBriefReportFromStore(app, store, ref, save)
229
+ const rootDir = deriveRootDir(options?.rootDir);
230
+ return withCurrentTopoStore(app, rootDir, (store, ref, snapshot) =>
231
+ buildBriefReportFromStore(app, store, ref, snapshot)
237
232
  );
238
233
  };
239
234
 
@@ -241,7 +236,7 @@ export const buildCurrentTopoList = (
241
236
  app: Topo,
242
237
  options?: { readonly rootDir?: string }
243
238
  ): SurveyListReport => {
244
- const rootDir = resolveRootDir(options?.rootDir);
239
+ const rootDir = deriveRootDir(options?.rootDir);
245
240
  return withCurrentTopoStore(app, rootDir, (store, ref) =>
246
241
  buildSurveyListFromStore(store, ref)
247
242
  );
@@ -256,9 +251,9 @@ export const buildCurrentGuideEntries = (
256
251
  readonly id: string;
257
252
  readonly kind: string;
258
253
  }[] => {
259
- const rootDir = resolveRootDir(options?.rootDir);
254
+ const rootDir = deriveRootDir(options?.rootDir);
260
255
  return withCurrentTopoStore(app, rootDir, (store, ref) =>
261
- store.trails.list({ save: ref }).map((trail) => ({
256
+ store.trails.list({ snapshot: ref }).map((trail) => ({
262
257
  description: trail.description ?? '(no description)',
263
258
  exampleCount: trail.exampleCount,
264
259
  id: trail.id,
@@ -271,18 +266,18 @@ export const buildCurrentTopoDetail = (
271
266
  app: Topo,
272
267
  id: string,
273
268
  options?: { readonly rootDir?: string }
274
- ): CurrentProvisionDetail | CurrentTrailDetail | undefined => {
275
- const rootDir = resolveRootDir(options?.rootDir);
269
+ ): CurrentResourceDetail | CurrentTrailDetail | undefined => {
270
+ const rootDir = deriveRootDir(options?.rootDir);
276
271
  return withCurrentTopoStore(app, rootDir, (store, ref) => {
277
- const trail = store.trails.get(id, { save: ref });
272
+ const trail = store.trails.get(id, { snapshot: ref });
278
273
  if (trail !== undefined) {
279
274
  return buildTrailDetailFromStore(trail);
280
275
  }
281
276
 
282
- const provision = store.provisions.get(id, { save: ref });
283
- return provision === undefined
277
+ const resource = store.resources.get(id, { snapshot: ref });
278
+ return resource === undefined
284
279
  ? undefined
285
- : buildProvisionDetailFromStore(provision);
280
+ : buildResourceDetailFromStore(resource);
286
281
  });
287
282
  };
288
283
 
@@ -290,9 +285,9 @@ export const verifyCurrentTopo = async (
290
285
  app: Topo,
291
286
  options?: { readonly rootDir?: string }
292
287
  ): Promise<Result<TopoVerifyReport, Error>> => {
293
- const rootDir = resolveRootDir(options?.rootDir);
294
- const committedLock = await readTrailheadLockData({
295
- dir: resolveTrailsDir({ rootDir }),
288
+ const rootDir = deriveRootDir(options?.rootDir);
289
+ const committedLock = await readSurfaceLockData({
290
+ dir: deriveTrailsDir({ rootDir }),
296
291
  });
297
292
 
298
293
  if (committedLock === null) {
@@ -306,12 +301,14 @@ export const verifyCurrentTopo = async (
306
301
  const currentHash = withCurrentTopoStore(
307
302
  app,
308
303
  rootDir,
309
- (store, ref) => store.exports.get(ref)?.trailheadHash
304
+ (store, ref) => store.exports.get(ref)?.surfaceHash
310
305
  );
311
306
 
312
307
  if (currentHash === undefined) {
313
308
  return Result.err(
314
- new InternalError('No stored topo export found for the current topo save')
309
+ new InternalError(
310
+ 'No stored topo export found for the current topo snapshot'
311
+ )
315
312
  );
316
313
  }
317
314
 
@@ -7,7 +7,7 @@ export interface BriefReport {
7
7
  readonly version: string;
8
8
  readonly contractVersion: string;
9
9
  readonly features: {
10
- readonly provisions: boolean;
10
+ readonly resources: boolean;
11
11
  readonly outputSchemas: boolean;
12
12
  readonly examples: boolean;
13
13
  readonly detours: boolean;
@@ -15,7 +15,7 @@ export interface BriefReport {
15
15
  };
16
16
  readonly trails: number;
17
17
  readonly signals: number;
18
- readonly provisions: number;
18
+ readonly resources: number;
19
19
  }
20
20
 
21
21
  export interface SurveyListReport {
@@ -26,12 +26,12 @@ export interface SurveyListReport {
26
26
  readonly kind: string;
27
27
  readonly safety: string;
28
28
  }[];
29
- readonly provisionCount: number;
30
- readonly provisions: readonly {
29
+ readonly resourceCount: number;
30
+ readonly resources: readonly {
31
31
  readonly description: string | null;
32
32
  readonly health: 'available' | 'none';
33
33
  readonly id: string;
34
- readonly kind: 'provision';
34
+ readonly kind: 'resource';
35
35
  readonly lifetime: 'singleton';
36
36
  readonly usedBy: readonly string[];
37
37
  }[];
@@ -39,18 +39,21 @@ export interface SurveyListReport {
39
39
 
40
40
  export interface TrailDetailReport {
41
41
  readonly description: string | null;
42
- readonly detours: Trail<unknown, unknown>['detours'] | null;
42
+ readonly detours:
43
+ | readonly { readonly on: string; readonly maxAttempts: number }[]
44
+ | null;
43
45
  readonly examples: readonly unknown[];
44
46
  readonly crosses: readonly string[];
45
47
  readonly id: string;
46
48
  readonly intent: 'read' | 'write' | 'destroy';
47
49
  readonly kind: string;
50
+ readonly pattern: string | null;
48
51
  readonly safety: string;
49
- readonly provisions: readonly string[];
52
+ readonly resources: readonly string[];
50
53
  }
51
54
 
52
55
  const trailHas = (raw: Record<string, unknown>, key: string): boolean => {
53
- if (key === 'examples') {
56
+ if (key === 'examples' || key === 'detours') {
54
57
  return Array.isArray(raw[key]) && (raw[key] as unknown[]).length > 0;
55
58
  }
56
59
  return Boolean(raw[key]);
@@ -62,7 +65,7 @@ const detectFeatures = (
62
65
  hasDetours: boolean;
63
66
  hasExamples: boolean;
64
67
  hasOutputSchemas: boolean;
65
- hasProvisions: boolean;
68
+ hasResources: boolean;
66
69
  } => {
67
70
  const trails = [...app.trails.values()].map(
68
71
  (item) => item as unknown as Record<string, unknown>
@@ -71,16 +74,16 @@ const detectFeatures = (
71
74
  hasDetours: trails.some((r) => trailHas(r, 'detours')),
72
75
  hasExamples: trails.some((r) => trailHas(r, 'examples')),
73
76
  hasOutputSchemas: trails.some((r) => trailHas(r, 'output')),
74
- hasProvisions: trails.some(
77
+ hasResources: trails.some(
75
78
  (r) =>
76
- Array.isArray(r['provisions']) &&
77
- (r['provisions'] as unknown[]).length > 0
79
+ Array.isArray(r['resources']) &&
80
+ (r['resources'] as unknown[]).length > 0
78
81
  ),
79
82
  };
80
83
  };
81
84
 
82
- export const generateBriefReport = (app: Topo): BriefReport => {
83
- const { hasDetours, hasExamples, hasOutputSchemas, hasProvisions } =
85
+ export const deriveBriefReport = (app: Topo): BriefReport => {
86
+ const { hasDetours, hasExamples, hasOutputSchemas, hasResources } =
84
87
  detectFeatures(app);
85
88
 
86
89
  return {
@@ -89,11 +92,11 @@ export const generateBriefReport = (app: Topo): BriefReport => {
89
92
  detours: hasDetours,
90
93
  examples: hasExamples,
91
94
  outputSchemas: hasOutputSchemas,
92
- provisions: hasProvisions,
95
+ resources: hasResources,
93
96
  signals: app.signals.size > 0,
94
97
  },
95
98
  name: app.name,
96
- provisions: app.provisions.size,
99
+ resources: app.resources.size,
97
100
  signals: app.signals.size,
98
101
  trails: app.trails.size,
99
102
  version: REPORT_VERSION,
@@ -115,16 +118,16 @@ const safetyLabel = (entry: {
115
118
  return '-';
116
119
  };
117
120
 
118
- const buildProvisionUsage = (
121
+ const buildResourceUsage = (
119
122
  app: Topo
120
123
  ): ReadonlyMap<string, readonly string[]> => {
121
124
  const usage = new Map<string, string[]>();
122
125
 
123
126
  for (const trailDef of app.list()) {
124
- for (const declaredProvision of trailDef.provisions) {
125
- const users = usage.get(declaredProvision.id) ?? [];
127
+ for (const declaredResource of trailDef.resources) {
128
+ const users = usage.get(declaredResource.id) ?? [];
126
129
  users.push(trailDef.id);
127
- usage.set(declaredProvision.id, users);
130
+ usage.set(declaredResource.id, users);
128
131
  }
129
132
  }
130
133
 
@@ -133,44 +136,41 @@ const buildProvisionUsage = (
133
136
  );
134
137
  };
135
138
 
136
- const provisionHealthStatus = (provision: {
139
+ const resourceHealthStatus = (resource: {
137
140
  health?: unknown;
138
141
  }): 'available' | 'none' =>
139
- provision.health === undefined ? 'none' : 'available';
142
+ resource.health === undefined ? 'none' : 'available';
140
143
 
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) ?? [];
144
+ export const deriveResourceDetail = (app: Topo, resourceId: string): object => {
145
+ const item = app.getResource(resourceId);
146
+ const usedBy = buildResourceUsage(app).get(resourceId) ?? [];
147
147
 
148
148
  return {
149
149
  description: item?.description ?? null,
150
- health: item ? provisionHealthStatus(item) : 'none',
151
- id: provisionId,
152
- kind: 'provision',
150
+ health: item ? resourceHealthStatus(item) : 'none',
151
+ id: resourceId,
152
+ kind: 'resource',
153
153
  lifetime: 'singleton',
154
154
  usedBy,
155
155
  };
156
156
  };
157
157
 
158
- const formatProvisionList = (app: Topo): SurveyListReport['provisions'] => {
159
- const usage = buildProvisionUsage(app);
158
+ const formatResourceList = (app: Topo): SurveyListReport['resources'] => {
159
+ const usage = buildResourceUsage(app);
160
160
  return app
161
- .listProvisions()
162
- .map((provision) => ({
163
- description: provision.description ?? null,
164
- health: provisionHealthStatus(provision),
165
- id: provision.id,
166
- kind: provision.kind,
161
+ .listResources()
162
+ .map((resource) => ({
163
+ description: resource.description ?? null,
164
+ health: resourceHealthStatus(resource),
165
+ id: resource.id,
166
+ kind: resource.kind,
167
167
  lifetime: 'singleton' as const,
168
- usedBy: usage.get(provision.id) ?? [],
168
+ usedBy: usage.get(resource.id) ?? [],
169
169
  }))
170
170
  .toSorted((a, b) => a.id.localeCompare(b.id));
171
171
  };
172
172
 
173
- export const generateSurveyList = (app: Topo): SurveyListReport => {
173
+ export const deriveSurveyList = (app: Topo): SurveyListReport => {
174
174
  const items = app.list();
175
175
  const entries = items.map((item) => {
176
176
  const safety = safetyLabel(
@@ -190,17 +190,17 @@ export const generateSurveyList = (app: Topo): SurveyListReport => {
190
190
  };
191
191
  });
192
192
 
193
- const provisions = formatProvisionList(app);
193
+ const resources = formatResourceList(app);
194
194
 
195
195
  return {
196
196
  count: items.length,
197
197
  entries,
198
- provisionCount: provisions.length,
199
- provisions,
198
+ resourceCount: resources.length,
199
+ resources,
200
200
  };
201
201
  };
202
202
 
203
- export const generateTrailDetail = (
203
+ export const deriveTrailDetail = (
204
204
  item: Trail<unknown, unknown>
205
205
  ): TrailDetailReport => {
206
206
  const safety = safetyLabel(
@@ -210,12 +210,19 @@ export const generateTrailDetail = (
210
210
  return {
211
211
  crosses: item.crosses.toSorted(),
212
212
  description: item.description ?? null,
213
- detours: item.detours ?? 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,
214
220
  examples: item.examples ?? [],
215
221
  id: item.id,
216
222
  intent: item.intent,
217
223
  kind: item.kind,
218
- provisions: item.provisions.map((provision) => provision.id).toSorted(),
224
+ pattern: item.pattern ?? null,
225
+ resources: item.resources.map((resource) => resource.id).toSorted(),
219
226
  safety,
220
227
  };
221
228
  };
@@ -3,7 +3,6 @@ import { z } from 'zod';
3
3
 
4
4
  import { loadApp } from './load-app.js';
5
5
  import { buildCurrentTopoDetail } from './topo-read-support.js';
6
- import { DEFAULT_APP_MODULE } from './topo-support.js';
7
6
 
8
7
  const trailDetailOutput = z.object({
9
8
  crosses: z.array(z.string()),
@@ -13,15 +12,15 @@ const trailDetailOutput = z.object({
13
12
  id: z.string(),
14
13
  intent: z.enum(['read', 'write', 'destroy']),
15
14
  kind: z.string(),
16
- provisions: z.array(z.string()),
15
+ resources: z.array(z.string()),
17
16
  safety: z.string(),
18
17
  });
19
18
 
20
- const provisionDetailOutput = z.object({
19
+ const resourceDetailOutput = z.object({
21
20
  description: z.string().nullable(),
22
21
  health: z.enum(['available', 'none']),
23
22
  id: z.string(),
24
- kind: z.literal('provision'),
23
+ kind: z.literal('resource'),
25
24
  lifetime: z.literal('singleton'),
26
25
  usedBy: z.array(z.string()),
27
26
  });
@@ -35,10 +34,10 @@ export const topoShowTrail = trail('topo.show', {
35
34
  return Result.ok(detail);
36
35
  }
37
36
  return Result.err(
38
- new NotFoundError(`Trail or provision not found: ${input.id}`)
37
+ new NotFoundError(`Trail or resource not found: ${input.id}`)
39
38
  );
40
39
  },
41
- description: 'Show detail for a current trail or provision',
40
+ description: 'Show detail for a current trail or resource',
42
41
  examples: [
43
42
  {
44
43
  input: { id: 'topo' },
@@ -46,13 +45,10 @@ export const topoShowTrail = trail('topo.show', {
46
45
  },
47
46
  ],
48
47
  input: z.object({
49
- id: z.string().describe('Trail or provision ID to inspect'),
50
- module: z
51
- .string()
52
- .default(DEFAULT_APP_MODULE)
53
- .describe('Path to the app module'),
48
+ id: z.string().describe('Trail or resource ID to inspect'),
49
+ module: z.string().optional().describe('Path to the app module'),
54
50
  rootDir: z.string().optional().describe('Workspace root directory'),
55
51
  }),
56
52
  intent: 'read',
57
- output: z.union([trailDetailOutput, provisionDetailOutput]),
53
+ output: z.union([trailDetailOutput, resourceDetailOutput]),
58
54
  });