@ontrails/topography 1.0.0-beta.41

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.
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Surface-binding consumption for topo graph derivation.
3
+ *
4
+ * Both derivation pipelines — `deriveTopoGraph` and the store-side
5
+ * `buildTopoGraph` — resolve the app-authored `surfaces` overlay's `cli`
6
+ * bindings into per-trail CLI alias inputs and its `mcp` list bindings into
7
+ * projected trailhead entries through this single helper module, so compiled
8
+ * locks and fresh derivations cannot diverge from the runtime surfaces'
9
+ * reading of the same bindings.
10
+ */
11
+
12
+ import {
13
+ deriveMcpTrailheadDescription,
14
+ expandCliSurfaceBindings,
15
+ expandMcpSurfaceBindings,
16
+ resolveSurfaceOverlayBindings,
17
+ } from '@ontrails/core';
18
+ import type { CliSurfaceBindingAliases, Topo } from '@ontrails/core';
19
+
20
+ import { deriveStableHash } from './hash.js';
21
+ import type {
22
+ TopoGraphOverlayRegistration,
23
+ TopoGraphTrailheadEntry,
24
+ } from './types.js';
25
+
26
+ /**
27
+ * Resolve per-trail CLI alias inputs from overlay registrations.
28
+ *
29
+ * Finds the app-authored `surfaces` overlay among the registrations (the
30
+ * provenance gate rejects adapter-derived claimants), takes its `cli`
31
+ * bindings, and expands them against the topo's trail ids: a scalar binding
32
+ * is a synonym command path for exactly one trail, a list binding is a
33
+ * command group whose members get group-prefixed alias routes. Throws a
34
+ * `ValidationError` for bindings that resolve to no trail, scalar bindings
35
+ * that match more than one, or an ill-formed `surfaces` envelope. Returns
36
+ * `undefined` when no registrations carry `cli` bindings.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * import { resolveCliAliasInputsFromOverlays } from './surface-bindings.js';
41
+ *
42
+ * const aliases = resolveCliAliasInputsFromOverlays(app, overlays);
43
+ * // => { 'gear.list': [['gear', 'ls']] } for surfaceOverlay({ cli: { 'gear.ls': 'gear.list' } })
44
+ * ```
45
+ */
46
+ export const resolveCliAliasInputsFromOverlays = (
47
+ topo: Topo,
48
+ registrations: readonly TopoGraphOverlayRegistration[] | undefined
49
+ ): CliSurfaceBindingAliases | undefined => {
50
+ const bindings = resolveSurfaceOverlayBindings(registrations);
51
+ if (bindings?.cli === undefined) {
52
+ return undefined;
53
+ }
54
+ return expandCliSurfaceBindings(bindings.cli, [...topo.trails.keys()]);
55
+ };
56
+
57
+ /**
58
+ * Resolve projected trailhead entries from overlay registrations.
59
+ *
60
+ * Finds the app-authored `surfaces` overlay among the registrations and
61
+ * projects each `mcp` list binding into one `TopoGraphTrailheadEntry`: the
62
+ * binding name becomes the trailhead id, the sorted expanded member trail
63
+ * ids become `memberIds`, the surfaces list is `['mcp']`, and the
64
+ * description is the deterministic derived default shared with the MCP
65
+ * surface. Scalar `mcp` bindings are tool synonyms, not grouped entries, so
66
+ * they project no trailhead. Throws a `ValidationError` for group bindings
67
+ * whose member union is empty or synonym bindings that violate the shared
68
+ * expansion rules. Returns `undefined` when no `mcp` list bindings exist.
69
+ *
70
+ * @example
71
+ * ```ts
72
+ * import { resolveTrailheadEntriesFromOverlays } from './surface-bindings.js';
73
+ *
74
+ * const trailheads = resolveTrailheadEntriesFromOverlays(app, overlays);
75
+ * // => [{ id: 'gear', memberIds: ['gear.create', 'gear.list'], surfaces: ['mcp'], ... }]
76
+ * ```
77
+ */
78
+ export const resolveTrailheadEntriesFromOverlays = (
79
+ topo: Topo,
80
+ registrations: readonly TopoGraphOverlayRegistration[] | undefined
81
+ ): readonly TopoGraphTrailheadEntry[] | undefined => {
82
+ const bindings = resolveSurfaceOverlayBindings(registrations);
83
+ const expansion = expandMcpSurfaceBindings(bindings?.mcp, [
84
+ ...topo.trails.keys(),
85
+ ]);
86
+ if (expansion === undefined) {
87
+ return undefined;
88
+ }
89
+ const entries = Object.entries(expansion.groups)
90
+ .map(
91
+ ([id, memberIds]): TopoGraphTrailheadEntry => ({
92
+ description: deriveMcpTrailheadDescription(memberIds),
93
+ id,
94
+ memberIds,
95
+ memberSetHash: deriveStableHash(memberIds),
96
+ surfaces: ['mcp'],
97
+ })
98
+ )
99
+ .toSorted((a, b) => a.id.localeCompare(b.id));
100
+ return entries.length > 0 ? entries : undefined;
101
+ };