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

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 (140) hide show
  1. package/.turbo/turbo-lint.log +1 -1
  2. package/CHANGELOG.md +16 -0
  3. package/__tests__/examples.test.ts +14 -0
  4. package/dist/src/app.d.ts.map +1 -1
  5. package/dist/src/app.js +13 -2
  6. package/dist/src/app.js.map +1 -1
  7. package/dist/src/clack.d.ts +1 -1
  8. package/dist/src/clack.js +1 -1
  9. package/dist/src/cli.js +2 -2
  10. package/dist/src/cli.js.map +1 -1
  11. package/dist/src/trails/add-trail.js +13 -13
  12. package/dist/src/trails/add-trail.js.map +1 -1
  13. package/dist/src/trails/add-trailhead.d.ts +13 -0
  14. package/dist/src/trails/add-trailhead.d.ts.map +1 -0
  15. package/dist/src/trails/add-trailhead.js +88 -0
  16. package/dist/src/trails/add-trailhead.js.map +1 -0
  17. package/dist/src/trails/add-verify.js +10 -10
  18. package/dist/src/trails/add-verify.js.map +1 -1
  19. package/dist/src/trails/create-scaffold.js +26 -26
  20. package/dist/src/trails/create-scaffold.js.map +1 -1
  21. package/dist/src/trails/create.d.ts +6 -6
  22. package/dist/src/trails/create.d.ts.map +1 -1
  23. package/dist/src/trails/create.js +29 -29
  24. package/dist/src/trails/create.js.map +1 -1
  25. package/dist/src/trails/dev-clean.d.ts +9 -0
  26. package/dist/src/trails/dev-clean.d.ts.map +1 -0
  27. package/dist/src/trails/dev-clean.js +65 -0
  28. package/dist/src/trails/dev-clean.js.map +1 -0
  29. package/dist/src/trails/dev-reset.d.ts +6 -0
  30. package/dist/src/trails/dev-reset.d.ts.map +1 -0
  31. package/dist/src/trails/dev-reset.js +38 -0
  32. package/dist/src/trails/dev-reset.js.map +1 -0
  33. package/dist/src/trails/dev-stats.d.ts +7 -0
  34. package/dist/src/trails/dev-stats.d.ts.map +1 -0
  35. package/dist/src/trails/dev-stats.js +61 -0
  36. package/dist/src/trails/dev-stats.js.map +1 -0
  37. package/dist/src/trails/dev-support.d.ts +64 -0
  38. package/dist/src/trails/dev-support.d.ts.map +1 -0
  39. package/dist/src/trails/dev-support.js +178 -0
  40. package/dist/src/trails/dev-support.js.map +1 -0
  41. package/dist/src/trails/draft-promote.d.ts +18 -0
  42. package/dist/src/trails/draft-promote.d.ts.map +1 -0
  43. package/dist/src/trails/draft-promote.js +386 -0
  44. package/dist/src/trails/draft-promote.js.map +1 -0
  45. package/dist/src/trails/guide.d.ts +13 -3
  46. package/dist/src/trails/guide.d.ts.map +1 -1
  47. package/dist/src/trails/guide.js +21 -37
  48. package/dist/src/trails/guide.js.map +1 -1
  49. package/dist/src/trails/load-app.d.ts +3 -1
  50. package/dist/src/trails/load-app.d.ts.map +1 -1
  51. package/dist/src/trails/load-app.js +53 -10
  52. package/dist/src/trails/load-app.js.map +1 -1
  53. package/dist/src/trails/project.d.ts.map +1 -1
  54. package/dist/src/trails/project.js +14 -3
  55. package/dist/src/trails/project.js.map +1 -1
  56. package/dist/src/trails/survey.d.ts +4 -58
  57. package/dist/src/trails/survey.d.ts.map +1 -1
  58. package/dist/src/trails/survey.js +52 -173
  59. package/dist/src/trails/survey.js.map +1 -1
  60. package/dist/src/trails/topo-constants.d.ts +3 -0
  61. package/dist/src/trails/topo-constants.d.ts.map +1 -0
  62. package/dist/src/trails/topo-constants.js +3 -0
  63. package/dist/src/trails/topo-constants.js.map +1 -0
  64. package/dist/src/trails/topo-export.d.ts +18 -0
  65. package/dist/src/trails/topo-export.d.ts.map +1 -0
  66. package/dist/src/trails/topo-export.js +34 -0
  67. package/dist/src/trails/topo-export.js.map +1 -0
  68. package/dist/src/trails/topo-history.d.ts +24 -0
  69. package/dist/src/trails/topo-history.d.ts.map +1 -0
  70. package/dist/src/trails/topo-history.js +33 -0
  71. package/dist/src/trails/topo-history.js.map +1 -0
  72. package/dist/src/trails/topo-pin.d.ts +21 -0
  73. package/dist/src/trails/topo-pin.d.ts.map +1 -0
  74. package/dist/src/trails/topo-pin.js +35 -0
  75. package/dist/src/trails/topo-pin.js.map +1 -0
  76. package/dist/src/trails/topo-read-support.d.ts +54 -0
  77. package/dist/src/trails/topo-read-support.d.ts.map +1 -0
  78. package/dist/src/trails/topo-read-support.js +178 -0
  79. package/dist/src/trails/topo-read-support.js.map +1 -0
  80. package/dist/src/trails/topo-reports.d.ts +50 -0
  81. package/dist/src/trails/topo-reports.d.ts.map +1 -0
  82. package/dist/src/trails/topo-reports.js +122 -0
  83. package/dist/src/trails/topo-reports.js.map +1 -0
  84. package/dist/src/trails/topo-show.d.ts +23 -0
  85. package/dist/src/trails/topo-show.d.ts.map +1 -0
  86. package/dist/src/trails/topo-show.js +53 -0
  87. package/dist/src/trails/topo-show.js.map +1 -0
  88. package/dist/src/trails/topo-store-support.d.ts +13 -0
  89. package/dist/src/trails/topo-store-support.d.ts.map +1 -0
  90. package/dist/src/trails/topo-store-support.js +55 -0
  91. package/dist/src/trails/topo-store-support.js.map +1 -0
  92. package/dist/src/trails/topo-support.d.ts +87 -0
  93. package/dist/src/trails/topo-support.d.ts.map +1 -0
  94. package/dist/src/trails/topo-support.js +165 -0
  95. package/dist/src/trails/topo-support.js.map +1 -0
  96. package/dist/src/trails/topo-unpin.d.ts +15 -0
  97. package/dist/src/trails/topo-unpin.d.ts.map +1 -0
  98. package/dist/src/trails/topo-unpin.js +39 -0
  99. package/dist/src/trails/topo-unpin.js.map +1 -0
  100. package/dist/src/trails/topo-verify.d.ts +5 -0
  101. package/dist/src/trails/topo-verify.d.ts.map +1 -0
  102. package/dist/src/trails/topo-verify.js +28 -0
  103. package/dist/src/trails/topo-verify.js.map +1 -0
  104. package/dist/src/trails/topo.d.ts +5 -0
  105. package/dist/src/trails/topo.d.ts.map +1 -0
  106. package/dist/src/trails/topo.js +67 -0
  107. package/dist/src/trails/topo.js.map +1 -0
  108. package/dist/src/trails/warden.d.ts +1 -1
  109. package/dist/src/trails/warden.d.ts.map +1 -1
  110. package/dist/src/trails/warden.js +28 -27
  111. package/dist/src/trails/warden.js.map +1 -1
  112. package/dist/tsconfig.tsbuildinfo +1 -1
  113. package/package.json +8 -7
  114. package/src/__tests__/draft-promote.test.ts +144 -0
  115. package/src/__tests__/load-app.test.ts +43 -0
  116. package/src/__tests__/survey.test.ts +85 -0
  117. package/src/__tests__/topo-dev.test.ts +424 -0
  118. package/src/app.ts +22 -0
  119. package/src/trails/dev-clean.ts +73 -0
  120. package/src/trails/dev-reset.ts +44 -0
  121. package/src/trails/dev-stats.ts +64 -0
  122. package/src/trails/dev-support.ts +326 -0
  123. package/src/trails/draft-promote.ts +704 -0
  124. package/src/trails/guide.ts +22 -37
  125. package/src/trails/load-app.ts +76 -13
  126. package/src/trails/project.ts +17 -3
  127. package/src/trails/survey.ts +56 -256
  128. package/src/trails/topo-constants.ts +2 -0
  129. package/src/trails/topo-export.ts +39 -0
  130. package/src/trails/topo-history.ts +40 -0
  131. package/src/trails/topo-pin.ts +42 -0
  132. package/src/trails/topo-read-support.ts +332 -0
  133. package/src/trails/topo-reports.ts +221 -0
  134. package/src/trails/topo-show.ts +58 -0
  135. package/src/trails/topo-store-support.ts +96 -0
  136. package/src/trails/topo-support.ts +274 -0
  137. package/src/trails/topo-unpin.ts +51 -0
  138. package/src/trails/topo-verify.ts +29 -0
  139. package/src/trails/topo.ts +73 -0
  140. package/src/trails/warden.ts +1 -0
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Stored-export pipeline for topo persistence.
3
+ *
4
+ * Extracted from topo-support.ts so this branch (trl-131) owns its own file,
5
+ * keeping absorb routing clean across the stack.
6
+ */
7
+
8
+ import type { Topo } from '@ontrails/core';
9
+ import { InternalError, Result } from '@ontrails/core';
10
+ import type { TopoSaveRecord } from '@ontrails/core/internal/topo-saves';
11
+ import type { StoredTopoExport } from '@ontrails/core/internal/topo-store';
12
+ import {
13
+ getStoredTopoExport,
14
+ persistEstablishedTopoSave,
15
+ } from '@ontrails/core/internal/topo-store';
16
+ import {
17
+ openWriteTrailsDb,
18
+ resolveTrailsDir,
19
+ } from '@ontrails/core/internal/trails-db';
20
+ import type { TrailheadLock, TrailheadMap } from '@ontrails/schema';
21
+ import { writeTrailheadLock, writeTrailheadMap } from '@ontrails/schema';
22
+
23
+ import type { TopoExportReport } from './topo-support.js';
24
+ import { currentGitState, resolveRootDir, topoCounts } from './topo-support.js';
25
+
26
+ const persistAndReadStoredExport = (
27
+ app: Topo,
28
+ db: ReturnType<typeof openWriteTrailsDb>,
29
+ rootDir: string
30
+ ): Result<{ save: TopoSaveRecord; storedExport: StoredTopoExport }, Error> => {
31
+ const saveResult = persistEstablishedTopoSave(db, app, {
32
+ ...currentGitState(rootDir),
33
+ ...topoCounts(app),
34
+ });
35
+ if (saveResult.isErr()) {
36
+ return saveResult;
37
+ }
38
+
39
+ const save = saveResult.value;
40
+ const storedExport = getStoredTopoExport(db, save.id);
41
+
42
+ if (storedExport === undefined) {
43
+ return Result.err(
44
+ new InternalError(`Missing stored topo export for save "${save.id}"`)
45
+ );
46
+ }
47
+
48
+ return Result.ok({
49
+ save,
50
+ storedExport,
51
+ });
52
+ };
53
+
54
+ const writeStoredExportArtifacts = async (
55
+ storedExport: StoredTopoExport,
56
+ trailsDir: string
57
+ ): Promise<Pick<TopoExportReport, 'hash' | 'lockPath' | 'mapPath'>> => {
58
+ const mapPath = await writeTrailheadMap(
59
+ JSON.parse(storedExport.trailheadMapJson) as TrailheadMap,
60
+ { dir: trailsDir }
61
+ );
62
+ const lockPath = await writeTrailheadLock(
63
+ JSON.parse(storedExport.lockContent) as TrailheadLock,
64
+ { dir: trailsDir }
65
+ );
66
+
67
+ return {
68
+ hash: storedExport.trailheadHash,
69
+ lockPath,
70
+ mapPath,
71
+ };
72
+ };
73
+
74
+ export const exportCurrentTopo = async (
75
+ app: Topo,
76
+ options?: { readonly rootDir?: string }
77
+ ): Promise<Result<TopoExportReport, Error>> => {
78
+ const rootDir = resolveRootDir(options?.rootDir);
79
+ const db = openWriteTrailsDb({ rootDir });
80
+
81
+ try {
82
+ const persisted = persistAndReadStoredExport(app, db, rootDir);
83
+ if (persisted.isErr()) {
84
+ return persisted;
85
+ }
86
+
87
+ const { save, storedExport } = persisted.value;
88
+ const artifacts = await writeStoredExportArtifacts(
89
+ storedExport,
90
+ resolveTrailsDir({ rootDir })
91
+ );
92
+ return Result.ok({ ...artifacts, save });
93
+ } finally {
94
+ db.close();
95
+ }
96
+ };
@@ -0,0 +1,274 @@
1
+ import { existsSync, mkdirSync, rmSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ import type { Topo } from '@ontrails/core';
7
+ import type {
8
+ TopoPinRecord,
9
+ TopoSaveRecord,
10
+ } from '@ontrails/core/internal/topo-saves';
11
+ import {
12
+ getTopoPin,
13
+ listTopoPins,
14
+ listTopoSaves,
15
+ pinTopoSave,
16
+ unpinTopoSave,
17
+ } from '@ontrails/core/internal/topo-saves';
18
+ import { persistEstablishedTopoSave } from '@ontrails/core/internal/topo-store';
19
+ import {
20
+ openReadTrailsDb,
21
+ openWriteTrailsDb,
22
+ resolveTrailsDbPath,
23
+ } from '@ontrails/core/internal/trails-db';
24
+ import { z } from 'zod';
25
+
26
+ import type { BriefReport, SurveyListReport } from './topo-reports.js';
27
+
28
+ /** Output schema for a topo save record. Shared across topo trails. */
29
+ export const topoSaveOutput = z.object({
30
+ createdAt: z.string(),
31
+ gitDirty: z.boolean(),
32
+ gitSha: z.string().optional(),
33
+ id: z.string(),
34
+ provisionCount: z.number(),
35
+ signalCount: z.number(),
36
+ trailCount: z.number(),
37
+ });
38
+
39
+ /** Output schema for a topo pin record. Shared across topo trails. */
40
+ export const topoPinOutput = z.object({
41
+ createdAt: z.string(),
42
+ name: z.string(),
43
+ saveId: z.string(),
44
+ });
45
+
46
+ export const DEFAULT_APP_MODULE = './src/app.ts';
47
+ export const DEFAULT_TOPO_HISTORY_LIMIT = 10;
48
+ export const LOCK_PATH = '.trails/trails.lock';
49
+ export const LEGACY_LOCK_PATH = '.trails/trailhead.lock';
50
+
51
+ /** Resolve the lockfile path, preferring the current name with legacy fallback. */
52
+ export const resolveLockPath = (trailsDir: string): string => {
53
+ const primary = join(trailsDir, 'trails.lock');
54
+ if (existsSync(primary)) {
55
+ return primary;
56
+ }
57
+ const legacy = join(trailsDir, 'trailhead.lock');
58
+ return existsSync(legacy) ? legacy : primary;
59
+ };
60
+ const EXAMPLE_APP_MODULE = fileURLToPath(new URL('../app.ts', import.meta.url));
61
+
62
+ export interface TopoSummaryReport {
63
+ readonly app: BriefReport;
64
+ readonly dbPath: string;
65
+ readonly list: SurveyListReport;
66
+ readonly lockExists: boolean;
67
+ readonly lockPath: string;
68
+ }
69
+
70
+ export interface TopoHistoryReport {
71
+ readonly dbPath: string;
72
+ readonly limit: number;
73
+ readonly pinCount: number;
74
+ readonly pins: TopoPinRecord[];
75
+ readonly saveCount: number;
76
+ readonly saves: TopoSaveRecord[];
77
+ }
78
+
79
+ export interface TopoExportReport {
80
+ readonly hash: string;
81
+ readonly lockPath: string;
82
+ readonly mapPath: string;
83
+ readonly save: TopoSaveRecord;
84
+ }
85
+
86
+ export interface TopoVerifyReport {
87
+ readonly committedHash: string;
88
+ readonly currentHash: string;
89
+ readonly lockPath: string;
90
+ readonly stale: false;
91
+ }
92
+
93
+ export const resolveRootDir = (cwd?: string): string => cwd ?? process.cwd();
94
+
95
+ const safeGit = (cwd: string, args: readonly string[]): string | undefined => {
96
+ const proc = Bun.spawnSync({
97
+ cmd: ['git', '-C', cwd, ...args],
98
+ stderr: 'ignore',
99
+ stdout: 'pipe',
100
+ });
101
+ if (!proc.success) {
102
+ return undefined;
103
+ }
104
+ const text = Buffer.from(proc.stdout).toString('utf8').trim();
105
+ return text.length === 0 ? undefined : text;
106
+ };
107
+
108
+ export const currentGitState = (
109
+ rootDir: string
110
+ ): { readonly gitDirty: boolean; readonly gitSha?: string } => {
111
+ const gitSha = safeGit(rootDir, ['rev-parse', 'HEAD']);
112
+ const status = safeGit(rootDir, ['status', '--porcelain']);
113
+ return {
114
+ gitDirty: (status?.length ?? 0) > 0,
115
+ ...(gitSha === undefined ? {} : { gitSha }),
116
+ };
117
+ };
118
+
119
+ export const topoCounts = (
120
+ app: Topo
121
+ ): Pick<TopoSaveRecord, 'provisionCount' | 'signalCount' | 'trailCount'> => ({
122
+ provisionCount: app.provisions.size,
123
+ signalCount: app.signals.size,
124
+ trailCount: app.trails.size,
125
+ });
126
+
127
+ const emptyTopoHistory = (
128
+ dbPath: string,
129
+ limit: number
130
+ ): TopoHistoryReport => ({
131
+ dbPath,
132
+ limit,
133
+ pinCount: 0,
134
+ pins: [],
135
+ saveCount: 0,
136
+ saves: [],
137
+ });
138
+
139
+ const collectedTopoHistory = (
140
+ dbPath: string,
141
+ limit: number,
142
+ pins: readonly TopoPinRecord[],
143
+ allSaves: readonly TopoSaveRecord[]
144
+ ): TopoHistoryReport => ({
145
+ dbPath,
146
+ limit,
147
+ pinCount: pins.length,
148
+ pins: [...pins],
149
+ saveCount: allSaves.length,
150
+ saves: allSaves.slice(0, limit),
151
+ });
152
+
153
+ const removeTopoPinWithDb = (
154
+ input: { readonly dryRun: boolean; readonly name: string },
155
+ pin: TopoPinRecord,
156
+ db: Parameters<typeof unpinTopoSave>[0]
157
+ ): {
158
+ readonly dryRun: boolean;
159
+ readonly pin?: TopoPinRecord;
160
+ readonly removed: boolean;
161
+ } =>
162
+ input.dryRun
163
+ ? { dryRun: true, pin, removed: false }
164
+ : { dryRun: false, pin, removed: unpinTopoSave(db, input.name) };
165
+
166
+ export const isolatedExampleInput = (
167
+ name: string
168
+ ): { readonly module: string; readonly rootDir: string } => {
169
+ const rootDir = join(tmpdir(), 'ontrails-trails-examples', name);
170
+ rmSync(rootDir, { force: true, recursive: true });
171
+ mkdirSync(rootDir, { recursive: true });
172
+ return {
173
+ module: EXAMPLE_APP_MODULE,
174
+ rootDir,
175
+ };
176
+ };
177
+
178
+ export const createCurrentTopoSave = (
179
+ app: Topo,
180
+ options?: { readonly rootDir?: string }
181
+ ): TopoSaveRecord => {
182
+ const rootDir = resolveRootDir(options?.rootDir);
183
+ const db = openWriteTrailsDb({ rootDir });
184
+
185
+ try {
186
+ const result = persistEstablishedTopoSave(db, app, {
187
+ ...currentGitState(rootDir),
188
+ ...topoCounts(app),
189
+ });
190
+ if (result.isErr()) {
191
+ throw result.error;
192
+ }
193
+ return result.value;
194
+ } finally {
195
+ db.close();
196
+ }
197
+ };
198
+
199
+ export const listTopoHistory = (options?: {
200
+ readonly limit?: number;
201
+ readonly rootDir?: string;
202
+ }): TopoHistoryReport => {
203
+ const rootDir = resolveRootDir(options?.rootDir);
204
+ const limit = options?.limit ?? DEFAULT_TOPO_HISTORY_LIMIT;
205
+ const dbPath = resolveTrailsDbPath({ rootDir });
206
+ if (!existsSync(dbPath)) {
207
+ return emptyTopoHistory(dbPath, limit);
208
+ }
209
+ const db = openReadTrailsDb({ rootDir });
210
+
211
+ try {
212
+ return collectedTopoHistory(
213
+ dbPath,
214
+ limit,
215
+ listTopoPins(db),
216
+ listTopoSaves(db)
217
+ );
218
+ } finally {
219
+ db.close();
220
+ }
221
+ };
222
+
223
+ export const pinCurrentTopo = (
224
+ app: Topo,
225
+ input: { readonly name: string; readonly rootDir?: string }
226
+ ): { readonly pin: TopoPinRecord; readonly save: TopoSaveRecord } => {
227
+ const rootDir = resolveRootDir(input.rootDir);
228
+ const db = openWriteTrailsDb({ rootDir });
229
+
230
+ try {
231
+ const result = persistEstablishedTopoSave(db, app, {
232
+ ...currentGitState(rootDir),
233
+ ...topoCounts(app),
234
+ });
235
+ if (result.isErr()) {
236
+ throw result.error;
237
+ }
238
+ const pin = pinTopoSave(db, {
239
+ name: input.name,
240
+ saveId: result.value.id,
241
+ });
242
+ return { pin, save: result.value };
243
+ } finally {
244
+ db.close();
245
+ }
246
+ };
247
+
248
+ export const removeTopoPin = (input: {
249
+ readonly dryRun: boolean;
250
+ readonly name: string;
251
+ readonly rootDir?: string;
252
+ }): {
253
+ readonly dryRun: boolean;
254
+ readonly pin?: TopoPinRecord;
255
+ readonly removed: boolean;
256
+ } => {
257
+ const rootDir = resolveRootDir(input.rootDir);
258
+ if (!existsSync(resolveTrailsDbPath({ rootDir }))) {
259
+ return { dryRun: input.dryRun, removed: false };
260
+ }
261
+ const db = input.dryRun
262
+ ? openReadTrailsDb({ rootDir })
263
+ : openWriteTrailsDb({ rootDir });
264
+
265
+ try {
266
+ const pin = getTopoPin(db, input.name);
267
+ if (pin === undefined) {
268
+ return { dryRun: input.dryRun, removed: false };
269
+ }
270
+ return removeTopoPinWithDb(input, pin, db);
271
+ } finally {
272
+ db.close();
273
+ }
274
+ };
@@ -0,0 +1,51 @@
1
+ import { Result, ValidationError, trail } from '@ontrails/core';
2
+ import { z } from 'zod';
3
+
4
+ import {
5
+ isolatedExampleInput,
6
+ removeTopoPin,
7
+ topoPinOutput,
8
+ } from './topo-support.js';
9
+
10
+ export const topoUnpinTrail = trail('topo.unpin', {
11
+ blaze: (input, ctx) => {
12
+ if (input.dryRun !== true && input.yes !== true) {
13
+ return Result.err(
14
+ new ValidationError(
15
+ 'Refusing to remove a pin without `--yes` or `--dry-run`.'
16
+ )
17
+ );
18
+ }
19
+
20
+ const rootDir = input.rootDir ?? ctx.cwd ?? process.cwd();
21
+ return Result.ok(
22
+ removeTopoPin({ dryRun: input.dryRun, name: input.name, rootDir })
23
+ );
24
+ },
25
+ description: 'Remove a named topo pin',
26
+ examples: [
27
+ {
28
+ input: {
29
+ ...isolatedExampleInput('topo-unpin'),
30
+ dryRun: true,
31
+ name: 'before-auth-refactor',
32
+ },
33
+ name: 'Preview pin removal',
34
+ },
35
+ ],
36
+ input: z.object({
37
+ dryRun: z
38
+ .boolean()
39
+ .default(true)
40
+ .describe('Preview the removal without changing state'),
41
+ name: z.string().describe('Pin name'),
42
+ rootDir: z.string().optional().describe('Workspace root directory'),
43
+ yes: z.boolean().default(false).describe('Confirm destructive changes'),
44
+ }),
45
+ intent: 'destroy',
46
+ output: z.object({
47
+ dryRun: z.boolean(),
48
+ pin: topoPinOutput.optional(),
49
+ removed: z.boolean(),
50
+ }),
51
+ });
@@ -0,0 +1,29 @@
1
+ import { trail } from '@ontrails/core';
2
+ import { z } from 'zod';
3
+
4
+ import { loadApp } from './load-app.js';
5
+ import { verifyCurrentTopo } from './topo-read-support.js';
6
+ import { DEFAULT_APP_MODULE } from './topo-support.js';
7
+
8
+ export const topoVerifyTrail = trail('topo.verify', {
9
+ blaze: async (input, ctx) => {
10
+ const rootDir = input.rootDir ?? ctx.cwd ?? process.cwd();
11
+ const app = await loadApp(input.module, rootDir);
12
+ return verifyCurrentTopo(app, { rootDir });
13
+ },
14
+ description: 'Verify that the committed lockfile matches the current topo',
15
+ input: z.object({
16
+ module: z
17
+ .string()
18
+ .default(DEFAULT_APP_MODULE)
19
+ .describe('Path to the app module'),
20
+ rootDir: z.string().optional().describe('Workspace root directory'),
21
+ }),
22
+ intent: 'read',
23
+ output: z.object({
24
+ committedHash: z.string(),
25
+ currentHash: z.string(),
26
+ lockPath: z.string(),
27
+ stale: z.literal(false),
28
+ }),
29
+ });
@@ -0,0 +1,73 @@
1
+ import { Result, trail } from '@ontrails/core';
2
+ import { z } from 'zod';
3
+
4
+ import { loadApp } from './load-app.js';
5
+ import { buildTopoSummary } from './topo-read-support.js';
6
+ import { DEFAULT_APP_MODULE } from './topo-support.js';
7
+
8
+ const summaryOutput = z.object({
9
+ app: z.object({
10
+ contractVersion: z.string(),
11
+ features: z.object({
12
+ detours: z.boolean(),
13
+ examples: z.boolean(),
14
+ outputSchemas: z.boolean(),
15
+ provisions: z.boolean(),
16
+ signals: z.boolean(),
17
+ }),
18
+ name: z.string(),
19
+ provisions: z.number(),
20
+ signals: z.number(),
21
+ trails: z.number(),
22
+ version: z.string(),
23
+ }),
24
+ dbPath: z.string(),
25
+ list: z.object({
26
+ count: z.number(),
27
+ entries: z.array(
28
+ z.object({
29
+ examples: z.number(),
30
+ id: z.string(),
31
+ kind: z.string(),
32
+ safety: z.string(),
33
+ })
34
+ ),
35
+ provisionCount: z.number(),
36
+ provisions: z.array(
37
+ z.object({
38
+ description: z.string().nullable(),
39
+ health: z.enum(['available', 'none']),
40
+ id: z.string(),
41
+ kind: z.literal('provision'),
42
+ lifetime: z.literal('singleton'),
43
+ usedBy: z.array(z.string()),
44
+ })
45
+ ),
46
+ }),
47
+ lockExists: z.boolean(),
48
+ lockPath: z.string(),
49
+ });
50
+
51
+ export const topoTrail = trail('topo', {
52
+ blaze: async (input, ctx) => {
53
+ const rootDir = input.rootDir ?? ctx.cwd ?? process.cwd();
54
+ const app = await loadApp(input.module, rootDir);
55
+ return Result.ok(buildTopoSummary(app, { rootDir }));
56
+ },
57
+ description: 'Show the current topo summary and entry list',
58
+ examples: [
59
+ {
60
+ input: {},
61
+ name: 'Show the current topo summary',
62
+ },
63
+ ],
64
+ input: z.object({
65
+ module: z
66
+ .string()
67
+ .default(DEFAULT_APP_MODULE)
68
+ .describe('Path to the app module'),
69
+ rootDir: z.string().optional().describe('Workspace root directory'),
70
+ }),
71
+ intent: 'read',
72
+ output: summaryOutput,
73
+ });
@@ -91,6 +91,7 @@ export const wardenTrail = trail('warden', {
91
91
  ),
92
92
  drift: z
93
93
  .object({
94
+ blockedReason: z.string().optional(),
94
95
  committedHash: z.string().nullable(),
95
96
  currentHash: z.string(),
96
97
  stale: z.boolean(),