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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # trails
2
2
 
3
+ ## 1.0.0-beta.17
4
+
5
+ ### Patch Changes
6
+
7
+ - 41276d2: Expose a shipped surface projection inventory through survey output and trail detail reports.
8
+ - Updated dependencies [3dc8254]
9
+ - Updated dependencies [61497c5]
10
+ - @ontrails/core@1.0.0-beta.17
11
+ - @ontrails/cli@1.0.0-beta.17
12
+ - @ontrails/commander@1.0.0-beta.17
13
+ - @ontrails/http@1.0.0-beta.17
14
+ - @ontrails/mcp@1.0.0-beta.17
15
+ - @ontrails/observe@1.0.0-beta.17
16
+ - @ontrails/permits@1.0.0-beta.17
17
+ - @ontrails/topographer@1.0.0-beta.17
18
+ - @ontrails/tracing@1.0.0-beta.17
19
+ - @ontrails/warden@1.0.0-beta.17
20
+
3
21
  ## 1.0.0-beta.16
4
22
 
5
23
  ### Major Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ontrails/trails",
3
- "version": "1.0.0-beta.16",
3
+ "version": "1.0.0-beta.17",
4
4
  "bin": {
5
5
  "trails": "./bin/trails.ts"
6
6
  },
@@ -23,18 +23,20 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "@clack/prompts": "^1.1.0",
26
- "@ontrails/cli": "^1.0.0-beta.15",
27
- "@ontrails/commander": "^1.0.0-beta.15",
28
- "@ontrails/core": "^1.0.0-beta.15",
29
- "@ontrails/observe": "^1.0.0-beta.15",
30
- "@ontrails/permits": "^1.0.0-beta.15",
31
- "@ontrails/topographer": "^1.0.0-beta.15",
32
- "@ontrails/tracing": "^1.0.0-beta.15",
33
- "@ontrails/warden": "^1.0.0-beta.15",
26
+ "@ontrails/cli": "^1.0.0-beta.16",
27
+ "@ontrails/commander": "^1.0.0-beta.16",
28
+ "@ontrails/core": "^1.0.0-beta.16",
29
+ "@ontrails/http": "^1.0.0-beta.16",
30
+ "@ontrails/mcp": "^1.0.0-beta.16",
31
+ "@ontrails/observe": "^1.0.0-beta.16",
32
+ "@ontrails/permits": "^1.0.0-beta.16",
33
+ "@ontrails/topographer": "^1.0.0-beta.16",
34
+ "@ontrails/tracing": "^1.0.0-beta.16",
35
+ "@ontrails/warden": "^1.0.0-beta.16",
34
36
  "commander": "^14.0.3",
35
37
  "zod": "^4.3.5"
36
38
  },
37
39
  "devDependencies": {
38
- "@ontrails/testing": "^1.0.0-beta.15"
40
+ "@ontrails/testing": "^1.0.0-beta.16"
39
41
  }
40
42
  }
@@ -41,23 +41,30 @@ import {
41
41
  import {
42
42
  activationOverviewOutput,
43
43
  resourceDetailOutput,
44
+ shippedSurfaceInventoryOutput,
44
45
  signalDetailOutput,
45
46
  trailDetailOutput,
46
47
  } from './topo-output-schemas.js';
47
48
  import { createIsolatedExampleInput } from './topo-support.js';
48
- import { briefReportSchema } from './topo-reports.js';
49
+ import {
50
+ briefReportSchema,
51
+ deriveShippedSurfaceProjectionInventory,
52
+ } from './topo-reports.js';
49
53
  import type { SurfaceLayerNames } from './topo-reports.js';
50
54
 
51
55
  export {
52
56
  briefReportSchema,
53
57
  deriveBriefReport,
54
58
  deriveResourceDetail,
59
+ deriveShippedSurfaceProjectionInventory,
55
60
  deriveSignalDetail,
56
61
  deriveSurveyList,
57
62
  deriveTrailDetail,
58
63
  } from './topo-reports.js';
59
64
  export type {
60
65
  BriefReport,
66
+ ShippedSurfaceInventoryReport,
67
+ ShippedSurfaceProjection,
61
68
  SignalDetailReport,
62
69
  SurfaceLayerNames,
63
70
  SurveyListReport,
@@ -278,6 +285,9 @@ const buildSurveySignalDetail = (
278
285
  : Result.ok(detail);
279
286
  };
280
287
 
288
+ const buildSurveySurfaceInventory = (app: Topo): Result<object, Error> =>
289
+ Result.ok(deriveShippedSurfaceProjectionInventory(app));
290
+
281
291
  interface SurveyInput {
282
292
  id?: string | undefined;
283
293
  module?: string | undefined;
@@ -506,6 +516,24 @@ export const surveyBriefTrail = trail('survey.brief', {
506
516
  output: briefReportSchema,
507
517
  });
508
518
 
519
+ export const surveySurfacesTrail = trail('survey.surfaces', {
520
+ blaze: async (input, ctx) =>
521
+ withResolvedSurveyApp(input, ctx.cwd, (app) =>
522
+ buildSurveySurfaceInventory(app)
523
+ ),
524
+ description: 'Inventory shipped surface projections',
525
+ examples: [
526
+ {
527
+ description: 'Show CLI, MCP, and HTTP projections for public trails',
528
+ input: createIsolatedExampleInput('survey-surfaces'),
529
+ name: 'Shipped surface inventory',
530
+ },
531
+ ],
532
+ input: moduleInputSchema,
533
+ intent: 'read',
534
+ output: shippedSurfaceInventoryOutput,
535
+ });
536
+
509
537
  export const surveyDiffTrail = trail('survey.diff', {
510
538
  blaze: async (input, ctx) =>
511
539
  withResolvedSurveyApp(input, ctx.cwd, (app, rootDir) =>
@@ -86,11 +86,55 @@ const contourDetailOutput = z.object({
86
86
  surfaces: z.array(z.string()).readonly(),
87
87
  });
88
88
 
89
- const surfaceProjectionOutput = z.object({
89
+ const surfaceProjectionBaseOutput = {
90
90
  derivedName: z.string(),
91
- method: z.string().nullable(),
92
- surface: z.string(),
91
+ source: z.enum(['authored', 'default-derived']),
93
92
  trailId: z.string(),
93
+ } as const;
94
+
95
+ export const surfaceProjectionOutput = z.discriminatedUnion('surface', [
96
+ z.object({
97
+ ...surfaceProjectionBaseOutput,
98
+ commandPath: z.array(z.string()).readonly(),
99
+ method: z.null(),
100
+ surface: z.literal('cli'),
101
+ }),
102
+ z.object({
103
+ ...surfaceProjectionBaseOutput,
104
+ method: z.null(),
105
+ surface: z.literal('mcp'),
106
+ toolName: z.string(),
107
+ }),
108
+ z.object({
109
+ ...surfaceProjectionBaseOutput,
110
+ method: z.string(),
111
+ path: z.string(),
112
+ surface: z.literal('http'),
113
+ }),
114
+ ]);
115
+
116
+ export const shippedSurfaceInventoryOutput = z.object({
117
+ count: z.number(),
118
+ excludedSurfaces: z
119
+ .array(
120
+ z.object({
121
+ reason: z.string(),
122
+ status: z.literal('planned'),
123
+ surface: z.literal('websocket'),
124
+ })
125
+ )
126
+ .readonly(),
127
+ projections: z.array(surfaceProjectionOutput).readonly(),
128
+ shippedSurfaces: z.array(z.enum(['cli', 'mcp', 'http'])).readonly(),
129
+ trails: z
130
+ .array(
131
+ z.object({
132
+ explicitSurfaces: z.array(z.string()).readonly(),
133
+ projections: z.array(surfaceProjectionOutput).readonly(),
134
+ trailId: z.string(),
135
+ })
136
+ )
137
+ .readonly(),
94
138
  });
95
139
 
96
140
  export const trailDetailOutput = z.object({
@@ -1,5 +1,13 @@
1
- import { DETOUR_MAX_ATTEMPTS_CAP, zodToJsonSchema } from '@ontrails/core';
1
+ import {
2
+ DETOUR_MAX_ATTEMPTS_CAP,
3
+ deriveCliPath,
4
+ filterSurfaceTrails,
5
+ zodToJsonSchema,
6
+ } from '@ontrails/core';
2
7
  import type { AnyTrail, Signal, Topo } from '@ontrails/core';
8
+ import { deriveHttpMethod } from '@ontrails/http';
9
+ import type { HttpMethod } from '@ontrails/http';
10
+ import { deriveToolName } from '@ontrails/mcp';
3
11
  import { deriveTopoGraph } from '@ontrails/topographer';
4
12
  import type {
5
13
  JsonSchema,
@@ -66,6 +74,52 @@ export type SurfaceLayerNames = Readonly<
66
74
  Record<SurfaceLayerKey, readonly string[]>
67
75
  >;
68
76
 
77
+ export type ShippedSurfaceKey = 'cli' | 'mcp' | 'http';
78
+
79
+ export type SurfaceProjectionSource = 'authored' | 'default-derived';
80
+
81
+ export type ShippedSurfaceProjection =
82
+ | {
83
+ readonly commandPath: readonly string[];
84
+ readonly derivedName: string;
85
+ readonly method: null;
86
+ readonly source: SurfaceProjectionSource;
87
+ readonly surface: 'cli';
88
+ readonly trailId: string;
89
+ }
90
+ | {
91
+ readonly derivedName: string;
92
+ readonly method: null;
93
+ readonly source: SurfaceProjectionSource;
94
+ readonly surface: 'mcp';
95
+ readonly toolName: string;
96
+ readonly trailId: string;
97
+ }
98
+ | {
99
+ readonly derivedName: string;
100
+ readonly method: HttpMethod;
101
+ readonly path: string;
102
+ readonly source: SurfaceProjectionSource;
103
+ readonly surface: 'http';
104
+ readonly trailId: string;
105
+ };
106
+
107
+ export interface ShippedSurfaceInventoryReport {
108
+ readonly count: number;
109
+ readonly excludedSurfaces: readonly {
110
+ readonly reason: string;
111
+ readonly status: 'planned';
112
+ readonly surface: 'websocket';
113
+ }[];
114
+ readonly projections: readonly ShippedSurfaceProjection[];
115
+ readonly shippedSurfaces: readonly ShippedSurfaceKey[];
116
+ readonly trails: readonly {
117
+ readonly explicitSurfaces: readonly string[];
118
+ readonly projections: readonly ShippedSurfaceProjection[];
119
+ readonly trailId: string;
120
+ }[];
121
+ }
122
+
69
123
  type TopoGraphContourEntry = TopoGraphEntry & { readonly kind: 'contour' };
70
124
 
71
125
  export interface TrailDetailOptions {
@@ -155,12 +209,7 @@ export interface TrailDetailReport {
155
209
  readonly pattern: string | null;
156
210
  readonly safety: string;
157
211
  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
- }[];
212
+ readonly surfaceProjections: readonly ShippedSurfaceProjection[];
164
213
  readonly surfaces: readonly string[];
165
214
  }
166
215
 
@@ -454,25 +503,158 @@ const trailActivationEdgesFromTopoGraph = (
454
503
  topoGraph?.activationGraph.edges.filter((edge) => edge.trailId === trailId) ??
455
504
  fallback;
456
505
 
457
- const deriveSurfaceProjections = (
506
+ const SHIPPED_SURFACES = ['cli', 'mcp', 'http'] as const;
507
+
508
+ const PLANNED_SURFACE_EXCLUSIONS = [
509
+ {
510
+ reason: 'WebSocket is planned, but no public package or API ships yet.',
511
+ status: 'planned' as const,
512
+ surface: 'websocket' as const,
513
+ },
514
+ ] as const;
515
+
516
+ const surfaceOrder = (surface: ShippedSurfaceKey): number =>
517
+ SHIPPED_SURFACES.indexOf(surface);
518
+
519
+ const sortSurfaceProjections = (
520
+ projections: readonly ShippedSurfaceProjection[]
521
+ ): readonly ShippedSurfaceProjection[] =>
522
+ projections.toSorted(
523
+ (a, b) =>
524
+ a.trailId.localeCompare(b.trailId) ||
525
+ surfaceOrder(a.surface) - surfaceOrder(b.surface)
526
+ );
527
+
528
+ const explicitSurfacesForEntry = (
458
529
  entry: TopoGraphEntry | undefined
459
- ): TrailDetailReport['surfaceProjections'] => {
460
- if (entry === undefined) {
530
+ ): readonly string[] => entry?.surfaces ?? [];
531
+
532
+ const projectionSource = (
533
+ entry: TopoGraphEntry | undefined,
534
+ surface: ShippedSurfaceKey
535
+ ): SurfaceProjectionSource =>
536
+ explicitSurfacesForEntry(entry).includes(surface)
537
+ ? 'authored'
538
+ : 'default-derived';
539
+
540
+ const deriveHttpPath = (trailId: string): string =>
541
+ `/${trailId.replaceAll('.', '/')}`;
542
+
543
+ const isSurfaceEligibleTrail = (app: Topo, trail: AnyTrail): boolean =>
544
+ filterSurfaceTrails([trail]).length > 0 &&
545
+ app.trails.get(trail.id) !== undefined;
546
+
547
+ export const deriveShippedSurfaceProjectionsForTrail = (
548
+ app: Topo,
549
+ trail: AnyTrail,
550
+ topoGraph?: TopoGraph | undefined
551
+ ): readonly ShippedSurfaceProjection[] => {
552
+ if (!isSurfaceEligibleTrail(app, trail)) {
461
553
  return [];
462
554
  }
463
555
 
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));
556
+ const entry = findTopoEntry(
557
+ topoGraph ?? deriveTopoGraph(app),
558
+ trail.id,
559
+ 'trail'
560
+ );
561
+ const commandPath = deriveCliPath(trail.id);
562
+ const httpMethod = deriveHttpMethod(trail.intent);
563
+ const httpPath = deriveHttpPath(trail.id);
564
+ const mcpToolName = deriveToolName(app.name, trail.id);
565
+
566
+ return sortSurfaceProjections([
567
+ {
568
+ commandPath,
569
+ derivedName: commandPath.join(' '),
570
+ method: null,
571
+ source: projectionSource(entry, 'cli'),
572
+ surface: 'cli',
573
+ trailId: trail.id,
574
+ },
575
+ {
576
+ derivedName: mcpToolName,
577
+ method: null,
578
+ source: projectionSource(entry, 'mcp'),
579
+ surface: 'mcp',
580
+ toolName: mcpToolName,
581
+ trailId: trail.id,
582
+ },
583
+ {
584
+ derivedName: httpPath,
585
+ method: httpMethod,
586
+ path: httpPath,
587
+ source: projectionSource(entry, 'http'),
588
+ surface: 'http',
589
+ trailId: trail.id,
590
+ },
591
+ ]);
592
+ };
593
+
594
+ const deriveFallbackSurfaceProjections = (
595
+ entry: TopoGraphEntry | undefined
596
+ ): readonly ShippedSurfaceProjection[] => {
597
+ if (entry?.cli === undefined) {
598
+ return [];
599
+ }
600
+
601
+ return [
602
+ {
603
+ commandPath: entry.cli.path,
604
+ derivedName: entry.cli.path.join(' '),
605
+ method: null,
606
+ source: projectionSource(entry, 'cli'),
607
+ surface: 'cli',
608
+ trailId: entry.id,
609
+ },
610
+ ];
611
+ };
612
+
613
+ export const deriveShippedSurfaceProjectionInventory = (
614
+ app: Topo
615
+ ): ShippedSurfaceInventoryReport => {
616
+ const topoGraph = deriveTopoGraph(app);
617
+ const trails = filterSurfaceTrails(app.list()).map((trail) => {
618
+ const entry = findTopoEntry(topoGraph, trail.id, 'trail');
619
+ const projections = deriveShippedSurfaceProjectionsForTrail(
620
+ app,
621
+ trail,
622
+ topoGraph
623
+ );
624
+
625
+ return {
626
+ explicitSurfaces: explicitSurfacesForEntry(entry),
627
+ projections,
628
+ trailId: trail.id,
629
+ };
630
+ });
631
+ const projections = sortSurfaceProjections(
632
+ trails.flatMap((trail) => trail.projections)
633
+ );
634
+
635
+ return {
636
+ count: trails.length,
637
+ excludedSurfaces: PLANNED_SURFACE_EXCLUSIONS,
638
+ projections,
639
+ shippedSurfaces: SHIPPED_SURFACES,
640
+ trails: trails.toSorted((a, b) => a.trailId.localeCompare(b.trailId)),
641
+ };
642
+ };
643
+
644
+ const deriveResolvedSurfaceProjections = (
645
+ app: Topo | undefined,
646
+ trailId: string,
647
+ topoEntry: TopoGraphEntry | undefined,
648
+ topoGraph: TopoGraph | undefined
649
+ ): readonly ShippedSurfaceProjection[] => {
650
+ if (app === undefined) {
651
+ return deriveFallbackSurfaceProjections(topoEntry);
652
+ }
653
+
654
+ const trail = app.trails.get(trailId);
655
+ return trail === undefined
656
+ ? deriveFallbackSurfaceProjections(topoEntry)
657
+ : deriveShippedSurfaceProjectionsForTrail(app, trail, topoGraph);
476
658
  };
477
659
 
478
660
  const deriveResolvedTrailGraphDetail = (
@@ -525,7 +707,12 @@ const deriveResolvedTrailGraphDetail = (
525
707
  input: topoEntry?.input ?? null,
526
708
  layers: topoEntry?.layers ?? [],
527
709
  output: topoEntry?.output ?? null,
528
- surfaceProjections: deriveSurfaceProjections(topoEntry),
710
+ surfaceProjections: deriveResolvedSurfaceProjections(
711
+ app,
712
+ trailId,
713
+ topoEntry,
714
+ topoGraph
715
+ ),
529
716
  surfaces: topoEntry?.surfaces ?? [],
530
717
  };
531
718
  };