@ontrails/trails 1.0.0-beta.17 → 1.0.0-beta.19

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 (45) hide show
  1. package/CHANGELOG.md +139 -0
  2. package/README.md +7 -10
  3. package/package.json +13 -12
  4. package/src/app.ts +14 -4
  5. package/src/cli.ts +16 -0
  6. package/src/lifecycle-source-io.ts +33 -0
  7. package/src/project-writes.ts +62 -5
  8. package/src/retired-topo-command.ts +36 -0
  9. package/src/run-adapter-check.ts +76 -0
  10. package/src/run-collision.ts +1 -0
  11. package/src/trails/adapter-check.ts +244 -0
  12. package/src/trails/add-surface.ts +18 -18
  13. package/src/trails/add-trail.ts +3 -2
  14. package/src/trails/add-verify.ts +30 -6
  15. package/src/trails/{topo-compile.ts → compile.ts} +16 -8
  16. package/src/trails/completions-complete.ts +1 -1
  17. package/src/trails/create-adapter.ts +1084 -0
  18. package/src/trails/create-scaffold.ts +243 -29
  19. package/src/trails/create.ts +118 -17
  20. package/src/trails/deprecate.ts +59 -0
  21. package/src/trails/dev-clean.ts +2 -2
  22. package/src/trails/dev-reset.ts +2 -2
  23. package/src/trails/dev-stats.ts +1 -1
  24. package/src/trails/doctor.ts +56 -0
  25. package/src/trails/draft-promote.ts +1 -0
  26. package/src/trails/guide.ts +2 -2
  27. package/src/trails/revise.ts +53 -0
  28. package/src/trails/run-example.ts +12 -7
  29. package/src/trails/run-examples.ts +3 -3
  30. package/src/trails/run.ts +7 -4
  31. package/src/trails/survey.ts +332 -25
  32. package/src/trails/topo-history.ts +1 -1
  33. package/src/trails/topo-output-schemas.ts +30 -1
  34. package/src/trails/topo-pin.ts +3 -2
  35. package/src/trails/topo-read-support.ts +49 -8
  36. package/src/trails/topo-reports.ts +39 -22
  37. package/src/trails/topo-store-support.ts +62 -16
  38. package/src/trails/topo-support.ts +1 -1
  39. package/src/trails/topo-unpin.ts +2 -2
  40. package/src/trails/topo.ts +2 -2
  41. package/src/trails/{topo-verify.ts → validate.ts} +7 -7
  42. package/src/trails/version-lifecycle-support.ts +945 -0
  43. package/src/trails/warden-guide.ts +8 -0
  44. package/src/trails/warden.ts +18 -2
  45. package/src/versions.ts +4 -1
@@ -0,0 +1,59 @@
1
+ import { trail } from '@ontrails/core';
2
+ import { z } from 'zod';
3
+
4
+ import {
5
+ parseLifecycleTarget,
6
+ setVersionStatusSource,
7
+ withLifecycleApp,
8
+ } from './version-lifecycle-support.js';
9
+
10
+ export const deprecateTrail = trail('deprecate', {
11
+ args: ['target'],
12
+ blaze: async (input, ctx) =>
13
+ withLifecycleApp(input, ctx.cwd, async (_app, rootDir) => {
14
+ const target = parseLifecycleTarget(input.target);
15
+ if (target.isErr()) {
16
+ return target;
17
+ }
18
+ return setVersionStatusSource(
19
+ rootDir,
20
+ target.value,
21
+ input.archive ? 'archived' : 'deprecated',
22
+ {
23
+ migration: input.migration,
24
+ note: input.note,
25
+ reason: input.reason,
26
+ successor: input.successor,
27
+ }
28
+ );
29
+ }),
30
+ description: 'Mark a historical trail version deprecated or archived',
31
+ input: z.object({
32
+ archive: z
33
+ .boolean()
34
+ .default(false)
35
+ .describe('Archive instead of deprecate'),
36
+ migration: z
37
+ .array(z.string())
38
+ .default([])
39
+ .describe('Migration guidance entries'),
40
+ module: z.string().optional().describe('Path to the app module'),
41
+ note: z.string().optional().describe('Deprecation note'),
42
+ reason: z.string().optional().describe('Archive reason'),
43
+ rootDir: z.string().optional().describe('Workspace root directory'),
44
+ successor: z
45
+ .number()
46
+ .int()
47
+ .positive()
48
+ .optional()
49
+ .describe('Successor version'),
50
+ target: z.string().min(1).describe('Historical version target trail.id@N'),
51
+ }),
52
+ intent: 'write',
53
+ output: z.object({
54
+ file: z.string(),
55
+ trailId: z.string(),
56
+ updated: z.boolean(),
57
+ }),
58
+ permit: { scopes: ['version:write'] },
59
+ });
@@ -20,7 +20,7 @@ export const devCleanTrail = trail('dev.clean', {
20
20
 
21
21
  const rootDirResult = resolveTrailRootDir(input.rootDir, ctx.cwd);
22
22
  if (rootDirResult.isErr()) {
23
- return Result.err(rootDirResult.error);
23
+ return rootDirResult;
24
24
  }
25
25
  const rootDir = rootDirResult.value;
26
26
  return Result.ok(
@@ -78,5 +78,5 @@ export const devCleanTrail = trail('dev.clean', {
78
78
  traces: z.number(),
79
79
  }),
80
80
  }),
81
- permit: { scopes: ['dev:clean'] },
81
+ permit: { scopes: ['dev:write'] },
82
82
  });
@@ -17,7 +17,7 @@ export const devResetTrail = trail('dev.reset', {
17
17
 
18
18
  const rootDirResult = resolveTrailRootDir(input.rootDir, ctx.cwd);
19
19
  if (rootDirResult.isErr()) {
20
- return Result.err(rootDirResult.error);
20
+ return rootDirResult;
21
21
  }
22
22
  const rootDir = rootDirResult.value;
23
23
  return Result.ok(resetDevState({ dryRun: input.dryRun, rootDir }));
@@ -46,5 +46,5 @@ export const devResetTrail = trail('dev.reset', {
46
46
  removedCount: z.number(),
47
47
  removedFiles: z.array(z.string()),
48
48
  }),
49
- permit: { scopes: ['dev:reset'] },
49
+ permit: { scopes: ['dev:write'] },
50
50
  });
@@ -12,7 +12,7 @@ export const devStatsTrail = trail('dev.stats', {
12
12
  blaze: (input, ctx) => {
13
13
  const rootDirResult = resolveTrailRootDir(input.rootDir, ctx.cwd);
14
14
  if (rootDirResult.isErr()) {
15
- return Result.err(rootDirResult.error);
15
+ return rootDirResult;
16
16
  }
17
17
  const rootDir = rootDirResult.value;
18
18
  return Result.ok(
@@ -0,0 +1,56 @@
1
+ import { deriveTrailsDir, Result, trail } from '@ontrails/core';
2
+ import { readTopoGraph } from '@ontrails/topographer';
3
+ import { z } from 'zod';
4
+
5
+ import {
6
+ deriveDoctorSummary,
7
+ withLifecycleApp,
8
+ } from './version-lifecycle-support.js';
9
+
10
+ const readDoctorForceGraph = async (
11
+ rootDir: string
12
+ ): Promise<Awaited<ReturnType<typeof readTopoGraph>> | undefined> => {
13
+ try {
14
+ return await readTopoGraph({ dir: deriveTrailsDir({ rootDir }) });
15
+ } catch {
16
+ // Force audit details are supplemental; stale topo locks should not block doctor counts.
17
+ return undefined;
18
+ }
19
+ };
20
+
21
+ export const doctorTrail = trail('doctor', {
22
+ blaze: async (input, ctx) =>
23
+ withLifecycleApp(input, ctx.cwd, async (app, rootDir) => {
24
+ const forceGraph = await readDoctorForceGraph(rootDir);
25
+ return Result.ok(deriveDoctorSummary(app, { forceGraph }));
26
+ }),
27
+ description: 'Diagnose trail versioning lifecycle state',
28
+ input: z.object({
29
+ module: z.string().optional().describe('Path to the app module'),
30
+ rootDir: z.string().optional().describe('Workspace root directory'),
31
+ }),
32
+ intent: 'read',
33
+ output: z.object({
34
+ archived: z.number(),
35
+ deprecated: z.number(),
36
+ forceDetails: z.array(
37
+ z
38
+ .object({
39
+ acceptedAt: z.string(),
40
+ change: z.enum(['modified', 'removed']),
41
+ detail: z.string(),
42
+ id: z.string(),
43
+ kind: z.enum(['contour', 'trail', 'signal', 'resource']),
44
+ reason: z.string().optional(),
45
+ scope: z.enum(['entry', 'graph']),
46
+ severity: z.literal('breaking'),
47
+ source: z.literal('trails compile --force'),
48
+ })
49
+ .strict()
50
+ ),
51
+ forceEvents: z.number(),
52
+ mode: z.literal('doctor'),
53
+ trails: z.number(),
54
+ versioned: z.number(),
55
+ }),
56
+ });
@@ -945,4 +945,5 @@ export const draftPromoteTrail = trail('draft.promote', {
945
945
  ),
946
946
  updatedFiles: z.array(z.string()),
947
947
  }),
948
+ permit: { scopes: ['version:write'] },
948
949
  });
@@ -36,12 +36,12 @@ export const guideTrail = trail('guide', {
36
36
  blaze: async (input, ctx) => {
37
37
  const rootDirResult = resolveTrailRootDir(input.rootDir, ctx.cwd);
38
38
  if (rootDirResult.isErr()) {
39
- return Result.err(rootDirResult.error);
39
+ return rootDirResult;
40
40
  }
41
41
  const rootDir = rootDirResult.value;
42
42
  const leaseResult = await tryLoadFreshAppLease(input.module, rootDir);
43
43
  if (leaseResult.isErr()) {
44
- return Result.err(leaseResult.error);
44
+ return leaseResult;
45
45
  }
46
46
  const lease = leaseResult.value;
47
47
 
@@ -0,0 +1,53 @@
1
+ import { Result, ValidationError, trail } from '@ontrails/core';
2
+ import { z } from 'zod';
3
+
4
+ import {
5
+ findLifecycleTrail,
6
+ forkVersionEntrySource,
7
+ parseLifecycleTarget,
8
+ reviseTrailSource,
9
+ withLifecycleApp,
10
+ } from './version-lifecycle-support.js';
11
+
12
+ export const reviseTrail = trail('revise', {
13
+ args: ['target'],
14
+ blaze: async (input, ctx) =>
15
+ withLifecycleApp(input, ctx.cwd, async (app, rootDir) => {
16
+ const target = parseLifecycleTarget(input.target);
17
+ if (target.isErr()) {
18
+ return target;
19
+ }
20
+ if (target.value.version !== undefined) {
21
+ return input.as === 'fork'
22
+ ? forkVersionEntrySource(rootDir, target.value)
23
+ : Result.err(
24
+ new ValidationError(
25
+ 'Revising a specific historical entry requires --as fork'
26
+ )
27
+ );
28
+ }
29
+ const found = findLifecycleTrail(app, target.value.trailId);
30
+ if (found.isErr()) {
31
+ return found;
32
+ }
33
+ return reviseTrailSource(rootDir, found.value, input.as);
34
+ }),
35
+ description: 'Scaffold the next trail version entry',
36
+ input: z.object({
37
+ as: z
38
+ .enum(['revision', 'fork'])
39
+ .default('revision')
40
+ .describe('Version entry shape to scaffold'),
41
+ module: z.string().optional().describe('Path to the app module'),
42
+ rootDir: z.string().optional().describe('Workspace root directory'),
43
+ target: z.string().min(1).describe('Trail target, optionally trail.id@N'),
44
+ }),
45
+ intent: 'write',
46
+ output: z.object({
47
+ file: z.string(),
48
+ trailId: z.string(),
49
+ updated: z.boolean(),
50
+ warnings: z.array(z.string()).optional(),
51
+ }),
52
+ permit: { scopes: ['version:write'] },
53
+ });
@@ -10,7 +10,7 @@ import {
10
10
  run,
11
11
  trail,
12
12
  } from '@ontrails/core';
13
- import type { StructuredTrailExample, Topo } from '@ontrails/core';
13
+ import type { BasePermit, StructuredTrailExample, Topo } from '@ontrails/core';
14
14
  import { z } from 'zod';
15
15
 
16
16
  import { tryLoadFreshAppLease } from './load-app.js';
@@ -350,7 +350,8 @@ const determineMode = (
350
350
  const buildComparisonEnvelope = async (
351
351
  app: Topo,
352
352
  trailId: string,
353
- exampleName: string
353
+ exampleName: string,
354
+ permit: BasePermit | undefined
354
355
  ): Promise<Result<RunExampleComparison, Error>> => {
355
356
  const exampleResult = findExample(app, trailId, exampleName);
356
357
  if (exampleResult.isErr()) {
@@ -358,7 +359,9 @@ const buildComparisonEnvelope = async (
358
359
  }
359
360
  const example = exampleResult.value;
360
361
  const mode = determineMode(example);
361
- const executed = await run(app, trailId, example.input);
362
+ const executed = await run(app, trailId, example.input, {
363
+ ctx: permit === undefined ? {} : { permit },
364
+ });
362
365
  const actual = projectActual(executed);
363
366
 
364
367
  if (mode === 'error') {
@@ -425,7 +428,7 @@ export const runExampleTrail = trail('run.example', {
425
428
  blaze: async (input, ctx) => {
426
429
  const rootDirResult = resolveTrailRootDir(input.rootDir, ctx.cwd);
427
430
  if (rootDirResult.isErr()) {
428
- return Result.err(rootDirResult.error);
431
+ return rootDirResult;
429
432
  }
430
433
  const rootDir = rootDirResult.value;
431
434
  const moduleResolution = await resolveRunModulePath(
@@ -435,7 +438,7 @@ export const runExampleTrail = trail('run.example', {
435
438
  input.app
436
439
  );
437
440
  if (moduleResolution.isErr()) {
438
- return Result.err(moduleResolution.error);
441
+ return moduleResolution;
439
442
  }
440
443
 
441
444
  const leaseResult = await tryLoadFreshAppLease(
@@ -443,7 +446,7 @@ export const runExampleTrail = trail('run.example', {
443
446
  rootDir
444
447
  );
445
448
  if (leaseResult.isErr()) {
446
- return Result.err(leaseResult.error);
449
+ return leaseResult;
447
450
  }
448
451
  const lease = leaseResult.value;
449
452
 
@@ -451,7 +454,8 @@ export const runExampleTrail = trail('run.example', {
451
454
  return await buildComparisonEnvelope(
452
455
  lease.app,
453
456
  input.id,
454
- input.exampleName
457
+ input.exampleName,
458
+ ctx.permit
455
459
  );
456
460
  } finally {
457
461
  lease.release();
@@ -479,4 +483,5 @@ export const runExampleTrail = trail('run.example', {
479
483
  }),
480
484
  intent: 'write',
481
485
  output: runExampleComparisonSchema,
486
+ permit: { scopes: ['trails:run'] },
482
487
  });
@@ -89,7 +89,7 @@ export const runExamplesTrail = trail('run.examples', {
89
89
  blaze: async (input, ctx) => {
90
90
  const rootDirResult = resolveTrailRootDir(input.rootDir, ctx.cwd);
91
91
  if (rootDirResult.isErr()) {
92
- return Result.err(rootDirResult.error);
92
+ return rootDirResult;
93
93
  }
94
94
  const rootDir = rootDirResult.value;
95
95
  const moduleResolution = await resolveRunModulePath(
@@ -99,7 +99,7 @@ export const runExamplesTrail = trail('run.examples', {
99
99
  input.app
100
100
  );
101
101
  if (moduleResolution.isErr()) {
102
- return Result.err(moduleResolution.error);
102
+ return moduleResolution;
103
103
  }
104
104
 
105
105
  const leaseResult = await tryLoadFreshAppLease(
@@ -107,7 +107,7 @@ export const runExamplesTrail = trail('run.examples', {
107
107
  rootDir
108
108
  );
109
109
  if (leaseResult.isErr()) {
110
- return Result.err(leaseResult.error);
110
+ return leaseResult;
111
111
  }
112
112
  const lease = leaseResult.value;
113
113
 
package/src/trails/run.ts CHANGED
@@ -322,7 +322,7 @@ export const runTrail = trail('run', {
322
322
  blaze: async (input, ctx) => {
323
323
  const rootDirResult = resolveTrailRootDir(input.rootDir, ctx.cwd);
324
324
  if (rootDirResult.isErr()) {
325
- return Result.err(rootDirResult.error);
325
+ return rootDirResult;
326
326
  }
327
327
  const rootDir = rootDirResult.value;
328
328
 
@@ -334,18 +334,20 @@ export const runTrail = trail('run', {
334
334
  input.app
335
335
  );
336
336
  if (moduleResolution.isErr()) {
337
- return Result.err(moduleResolution.error);
337
+ return moduleResolution;
338
338
  }
339
339
  const modulePath = moduleResolution.value;
340
340
 
341
341
  const leaseResult = await tryLoadFreshAppLease(modulePath, rootDir);
342
342
  if (leaseResult.isErr()) {
343
- return Result.err(leaseResult.error);
343
+ return leaseResult;
344
344
  }
345
345
  const lease = leaseResult.value;
346
346
 
347
347
  try {
348
- const result = await run(lease.app, input.id, input.input);
348
+ const result = await run(lease.app, input.id, input.input, {
349
+ ctx: ctx.permit === undefined ? {} : { permit: ctx.permit },
350
+ });
349
351
  if (result.isErr()) {
350
352
  return Result.err(result.error);
351
353
  }
@@ -400,4 +402,5 @@ export const runTrail = trail('run', {
400
402
  }),
401
403
  intent: 'write',
402
404
  output: innerTrailResultSchema,
405
+ permit: { scopes: ['trails:run'] },
403
406
  });