@ontrails/core 1.0.0-beta.4 → 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.
- package/CHANGELOG.md +412 -12
- package/README.md +112 -28
- package/package.json +11 -1
- package/src/activation-provenance.ts +116 -0
- package/src/activation-source-compatibility.ts +430 -0
- package/src/activation-source-projection.ts +227 -0
- package/src/activation-source.ts +93 -0
- package/src/blob-ref.ts +51 -0
- package/src/branded.ts +1 -1
- package/src/compose-batch.ts +69 -0
- package/src/compose-schema.ts +36 -0
- package/src/context.ts +59 -11
- package/src/derive.ts +271 -19
- package/src/detours.ts +8 -0
- package/src/diagnostics.ts +21 -0
- package/src/draft.ts +350 -0
- package/src/entity.ts +346 -0
- package/src/error-projection.ts +51 -0
- package/src/errors.ts +335 -48
- package/src/execute.ts +1577 -0
- package/src/fire.ts +1172 -0
- package/src/glob.ts +81 -0
- package/src/index.ts +568 -32
- package/src/internal/fork-ctx.ts +69 -0
- package/src/layer-projection.ts +193 -0
- package/src/layer.ts +43 -6
- package/src/observe.ts +361 -0
- package/src/path-scope.ts +66 -0
- package/src/path-security.ts +11 -3
- package/src/permits.ts +12 -0
- package/src/queue.ts +163 -0
- package/src/redaction/patterns.ts +6 -3
- package/src/resilience.ts +11 -11
- package/src/resource-config.ts +804 -0
- package/src/resource.ts +194 -0
- package/src/result.ts +18 -0
- package/src/run.ts +76 -0
- package/src/runtime-builtins.ts +69 -0
- package/src/schedule-runtime.ts +689 -0
- package/src/schedule.ts +326 -0
- package/src/serialization.ts +92 -55
- package/src/sha256.ts +136 -0
- package/src/signal-diagnostics.ts +633 -0
- package/src/signal-ref.ts +111 -0
- package/src/signal.ts +104 -0
- package/src/store/accessor-protocol.ts +56 -0
- package/src/store/index.ts +4 -0
- package/src/structured-examples.ts +248 -0
- package/src/surface-derivation.ts +91 -0
- package/src/surface-filter.ts +101 -0
- package/src/surface-overlay.ts +694 -0
- package/src/surface-versioning.ts +42 -0
- package/src/topo.ts +762 -38
- package/src/tracing.ts +346 -0
- package/src/trail-id-glob.ts +15 -0
- package/src/trail.ts +1244 -34
- package/src/trails/derive-trail.ts +835 -0
- package/src/trails/index.ts +9 -0
- package/src/trails/ingest.ts +152 -0
- package/src/trails-db.ts +212 -0
- package/src/transport-error-map.ts +174 -0
- package/src/type-utils.ts +87 -0
- package/src/types.ts +254 -12
- package/src/validate-established-topo.ts +73 -0
- package/src/validate-topo.ts +569 -55
- package/src/validation.ts +152 -4
- package/src/version-marker.ts +716 -0
- package/src/version-resolution.ts +308 -0
- package/src/version-runtime.ts +120 -0
- package/src/webhook.ts +461 -0
- package/src/workspace.ts +183 -16
- package/src/zod-wrappers.ts +72 -0
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-lint.log +0 -3
- package/.turbo/turbo-typecheck.log +0 -1
- package/dist/adapters.d.ts +0 -39
- package/dist/adapters.d.ts.map +0 -1
- package/dist/adapters.js +0 -2
- package/dist/adapters.js.map +0 -1
- package/dist/blob-ref.d.ts +0 -20
- package/dist/blob-ref.d.ts.map +0 -1
- package/dist/blob-ref.js +0 -22
- package/dist/blob-ref.js.map +0 -1
- package/dist/branded.d.ts +0 -36
- package/dist/branded.d.ts.map +0 -1
- package/dist/branded.js +0 -89
- package/dist/branded.js.map +0 -1
- package/dist/collections.d.ts +0 -31
- package/dist/collections.d.ts.map +0 -1
- package/dist/collections.js +0 -60
- package/dist/collections.js.map +0 -1
- package/dist/context.d.ts +0 -10
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -15
- package/dist/context.js.map +0 -1
- package/dist/derive.d.ts +0 -33
- package/dist/derive.d.ts.map +0 -1
- package/dist/derive.js +0 -125
- package/dist/derive.js.map +0 -1
- package/dist/errors.d.ts +0 -83
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -142
- package/dist/errors.js.map +0 -1
- package/dist/event.d.ts +0 -45
- package/dist/event.d.ts.map +0 -1
- package/dist/event.js +0 -17
- package/dist/event.js.map +0 -1
- package/dist/fetch.d.ts +0 -15
- package/dist/fetch.d.ts.map +0 -1
- package/dist/fetch.js +0 -102
- package/dist/fetch.js.map +0 -1
- package/dist/guards.d.ts +0 -17
- package/dist/guards.d.ts.map +0 -1
- package/dist/guards.js +0 -25
- package/dist/guards.js.map +0 -1
- package/dist/health.d.ts +0 -18
- package/dist/health.d.ts.map +0 -1
- package/dist/health.js +0 -5
- package/dist/health.js.map +0 -1
- package/dist/index.d.ts +0 -34
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -38
- package/dist/index.js.map +0 -1
- package/dist/job.d.ts +0 -24
- package/dist/job.d.ts.map +0 -1
- package/dist/job.js +0 -17
- package/dist/job.js.map +0 -1
- package/dist/layer.d.ts +0 -17
- package/dist/layer.d.ts.map +0 -1
- package/dist/layer.js +0 -21
- package/dist/layer.js.map +0 -1
- package/dist/path-security.d.ts +0 -28
- package/dist/path-security.d.ts.map +0 -1
- package/dist/path-security.js +0 -63
- package/dist/path-security.js.map +0 -1
- package/dist/patterns/bulk.d.ts +0 -15
- package/dist/patterns/bulk.d.ts.map +0 -1
- package/dist/patterns/bulk.js +0 -14
- package/dist/patterns/bulk.js.map +0 -1
- package/dist/patterns/change.d.ts +0 -10
- package/dist/patterns/change.d.ts.map +0 -1
- package/dist/patterns/change.js +0 -10
- package/dist/patterns/change.js.map +0 -1
- package/dist/patterns/date-range.d.ts +0 -10
- package/dist/patterns/date-range.d.ts.map +0 -1
- package/dist/patterns/date-range.js +0 -10
- package/dist/patterns/date-range.js.map +0 -1
- package/dist/patterns/index.d.ts +0 -9
- package/dist/patterns/index.d.ts.map +0 -1
- package/dist/patterns/index.js +0 -9
- package/dist/patterns/index.js.map +0 -1
- package/dist/patterns/pagination.d.ts +0 -18
- package/dist/patterns/pagination.d.ts.map +0 -1
- package/dist/patterns/pagination.js +0 -18
- package/dist/patterns/pagination.js.map +0 -1
- package/dist/patterns/progress.d.ts +0 -11
- package/dist/patterns/progress.d.ts.map +0 -1
- package/dist/patterns/progress.js +0 -11
- package/dist/patterns/progress.js.map +0 -1
- package/dist/patterns/sorting.d.ts +0 -13
- package/dist/patterns/sorting.d.ts.map +0 -1
- package/dist/patterns/sorting.js +0 -10
- package/dist/patterns/sorting.js.map +0 -1
- package/dist/patterns/status.d.ts +0 -15
- package/dist/patterns/status.d.ts.map +0 -1
- package/dist/patterns/status.js +0 -9
- package/dist/patterns/status.js.map +0 -1
- package/dist/patterns/timestamps.d.ts +0 -10
- package/dist/patterns/timestamps.d.ts.map +0 -1
- package/dist/patterns/timestamps.js +0 -10
- package/dist/patterns/timestamps.js.map +0 -1
- package/dist/redaction/index.d.ts +0 -4
- package/dist/redaction/index.d.ts.map +0 -1
- package/dist/redaction/index.js +0 -3
- package/dist/redaction/index.js.map +0 -1
- package/dist/redaction/patterns.d.ts +0 -9
- package/dist/redaction/patterns.d.ts.map +0 -1
- package/dist/redaction/patterns.js +0 -39
- package/dist/redaction/patterns.js.map +0 -1
- package/dist/redaction/redactor.d.ts +0 -27
- package/dist/redaction/redactor.d.ts.map +0 -1
- package/dist/redaction/redactor.js +0 -89
- package/dist/redaction/redactor.js.map +0 -1
- package/dist/resilience.d.ts +0 -34
- package/dist/resilience.d.ts.map +0 -1
- package/dist/resilience.js +0 -164
- package/dist/resilience.js.map +0 -1
- package/dist/result.d.ts +0 -57
- package/dist/result.d.ts.map +0 -1
- package/dist/result.js +0 -156
- package/dist/result.js.map +0 -1
- package/dist/serialization.d.ts +0 -27
- package/dist/serialization.d.ts.map +0 -1
- package/dist/serialization.js +0 -153
- package/dist/serialization.js.map +0 -1
- package/dist/topo.d.ts +0 -16
- package/dist/topo.d.ts.map +0 -1
- package/dist/topo.js +0 -66
- package/dist/topo.js.map +0 -1
- package/dist/trail.d.ts +0 -89
- package/dist/trail.d.ts.map +0 -1
- package/dist/trail.js +0 -18
- package/dist/trail.js.map +0 -1
- package/dist/types.d.ts +0 -46
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/validate-topo.d.ts +0 -24
- package/dist/validate-topo.d.ts.map +0 -1
- package/dist/validate-topo.js +0 -158
- package/dist/validate-topo.js.map +0 -1
- package/dist/validation.d.ts +0 -27
- package/dist/validation.d.ts.map +0 -1
- package/dist/validation.js +0 -134
- package/dist/validation.js.map +0 -1
- package/dist/workspace.d.ts +0 -25
- package/dist/workspace.d.ts.map +0 -1
- package/dist/workspace.js +0 -57
- package/dist/workspace.js.map +0 -1
- package/src/__tests__/blob-ref.test.ts +0 -103
- package/src/__tests__/branded.test.ts +0 -148
- package/src/__tests__/collections.test.ts +0 -126
- package/src/__tests__/context.test.ts +0 -66
- package/src/__tests__/derive.test.ts +0 -203
- package/src/__tests__/errors.test.ts +0 -309
- package/src/__tests__/event.test.ts +0 -82
- package/src/__tests__/fetch.test.ts +0 -217
- package/src/__tests__/guards.test.ts +0 -102
- package/src/__tests__/job.test.ts +0 -98
- package/src/__tests__/layer.test.ts +0 -212
- package/src/__tests__/path-security.test.ts +0 -114
- package/src/__tests__/patterns.test.ts +0 -273
- package/src/__tests__/redaction.test.ts +0 -244
- package/src/__tests__/resilience.test.ts +0 -246
- package/src/__tests__/result.test.ts +0 -155
- package/src/__tests__/serialization.test.ts +0 -401
- package/src/__tests__/topo.test.ts +0 -181
- package/src/__tests__/trail.test.ts +0 -217
- package/src/__tests__/validate-topo.test.ts +0 -278
- package/src/__tests__/validation.test.ts +0 -283
- package/src/__tests__/workspace.test.ts +0 -183
- package/src/adapters.ts +0 -68
- package/src/event.ts +0 -77
- package/src/health.ts +0 -23
- package/src/job.ts +0 -20
- package/tsconfig.json +0 -9
- package/tsconfig.tsbuildinfo +0 -1
package/src/derive.ts
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Schema-driven field derivation for @ontrails/core
|
|
3
3
|
*
|
|
4
|
-
* Introspects Zod v4 schemas to produce a
|
|
5
|
-
* that UI
|
|
4
|
+
* Introspects Zod v4 schemas to produce a runtime-agnostic Field[] descriptor
|
|
5
|
+
* that UI consumers (CLI prompts, web forms, etc.) can consume.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { z } from 'zod';
|
|
9
9
|
|
|
10
|
+
import { ValidationError } from './errors.js';
|
|
11
|
+
|
|
10
12
|
// ---------------------------------------------------------------------------
|
|
11
13
|
// Public types
|
|
12
14
|
// ---------------------------------------------------------------------------
|
|
13
15
|
|
|
14
|
-
/** A
|
|
16
|
+
/** A runtime-agnostic field descriptor derived from a Zod schema. */
|
|
15
17
|
export interface Field {
|
|
16
18
|
readonly name: string;
|
|
17
19
|
readonly type:
|
|
@@ -48,6 +50,57 @@ export interface FieldOverride {
|
|
|
48
50
|
| undefined;
|
|
49
51
|
}
|
|
50
52
|
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// CLI command route projection
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
/** Authored CLI command path shape. Strings are split on whitespace. */
|
|
58
|
+
export type CliCommandPathInput = string | readonly string[];
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Authored CLI command alias shape.
|
|
62
|
+
*
|
|
63
|
+
* String aliases are sibling leaf aliases. Array aliases are absolute command
|
|
64
|
+
* paths.
|
|
65
|
+
*/
|
|
66
|
+
export type CliCommandAliasInput = string | readonly string[];
|
|
67
|
+
|
|
68
|
+
/** Source that produced a resolved CLI command route. */
|
|
69
|
+
export type CliCommandRouteSource = 'derived' | 'trail' | 'surface';
|
|
70
|
+
|
|
71
|
+
/** Whether a resolved CLI command route is canonical or an alias. */
|
|
72
|
+
export type CliCommandRouteKind = 'alias' | 'canonical';
|
|
73
|
+
|
|
74
|
+
/** Trail-authored CLI projection metadata. */
|
|
75
|
+
export interface TrailCliProjection {
|
|
76
|
+
readonly aliases?: readonly CliCommandAliasInput[] | undefined;
|
|
77
|
+
readonly path?: CliCommandPathInput | undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** A resolved command path accepted by a CLI surface for one trail. */
|
|
81
|
+
export interface CliCommandRoute {
|
|
82
|
+
readonly kind: CliCommandRouteKind;
|
|
83
|
+
readonly path: readonly string[];
|
|
84
|
+
readonly source: CliCommandRouteSource;
|
|
85
|
+
readonly target: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Resolved CLI projection for one trail. */
|
|
89
|
+
export interface TrailCliCommandProjection {
|
|
90
|
+
readonly path: readonly string[];
|
|
91
|
+
readonly routes: readonly CliCommandRoute[];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
interface TrailCliProjectionInput {
|
|
95
|
+
readonly cli?: CliCommandPathInput | TrailCliProjection | undefined;
|
|
96
|
+
readonly id: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface DeriveTrailCliCommandProjectionOptions {
|
|
100
|
+
readonly aliases?: readonly CliCommandAliasInput[] | undefined;
|
|
101
|
+
readonly aliasSource?: Extract<CliCommandRouteSource, 'surface' | 'trail'>;
|
|
102
|
+
}
|
|
103
|
+
|
|
51
104
|
// ---------------------------------------------------------------------------
|
|
52
105
|
// Zod v4 internals accessor
|
|
53
106
|
// ---------------------------------------------------------------------------
|
|
@@ -92,7 +145,7 @@ const propagateDescription = (
|
|
|
92
145
|
}
|
|
93
146
|
};
|
|
94
147
|
|
|
95
|
-
/** Step one level of
|
|
148
|
+
/** Step one level of transparent wrapper unwrapping. Returns null if not a wrapper type. */
|
|
96
149
|
const unwrapStep = (
|
|
97
150
|
current: ZodInternals,
|
|
98
151
|
state: {
|
|
@@ -102,10 +155,16 @@ const unwrapStep = (
|
|
|
102
155
|
}
|
|
103
156
|
): ZodInternals | null => {
|
|
104
157
|
const defType = current._zod.def['type'] as string;
|
|
105
|
-
if (
|
|
158
|
+
if (
|
|
159
|
+
defType !== 'optional' &&
|
|
160
|
+
defType !== 'default' &&
|
|
161
|
+
defType !== 'readonly'
|
|
162
|
+
) {
|
|
106
163
|
return null;
|
|
107
164
|
}
|
|
108
|
-
|
|
165
|
+
if (defType !== 'readonly') {
|
|
166
|
+
state.required = false;
|
|
167
|
+
}
|
|
109
168
|
if (defType === 'default') {
|
|
110
169
|
state.defaultValue = current._zod.def['defaultValue'];
|
|
111
170
|
}
|
|
@@ -140,14 +199,21 @@ interface DerivedFieldType {
|
|
|
140
199
|
type: Field['type'];
|
|
141
200
|
}
|
|
142
201
|
|
|
143
|
-
const fieldTypeByDef: Record<
|
|
202
|
+
const fieldTypeByDef: Record<
|
|
203
|
+
string,
|
|
204
|
+
(s: ZodInternals) => DerivedFieldType | null
|
|
205
|
+
> = {
|
|
144
206
|
array: (s) => {
|
|
145
207
|
const element = s._zod.def['element'] as unknown as ZodInternals;
|
|
146
|
-
const
|
|
208
|
+
const { inner } = unwrap(element);
|
|
209
|
+
const elementType = inner._zod.def['type'] as string;
|
|
147
210
|
if (elementType === 'enum') {
|
|
148
|
-
const entries =
|
|
211
|
+
const entries = inner._zod.def['entries'] as Record<string, string>;
|
|
149
212
|
return { options: Object.values(entries), type: 'multiselect' };
|
|
150
213
|
}
|
|
214
|
+
if (elementType !== 'number' && elementType !== 'string') {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
151
217
|
return {
|
|
152
218
|
options: undefined,
|
|
153
219
|
type: elementType === 'number' ? 'number[]' : 'string[]',
|
|
@@ -163,10 +229,10 @@ const fieldTypeByDef: Record<string, (s: ZodInternals) => DerivedFieldType> = {
|
|
|
163
229
|
};
|
|
164
230
|
|
|
165
231
|
/** Derive field type and raw options from the unwrapped Zod def. */
|
|
166
|
-
const deriveFieldType = (s: ZodInternals): DerivedFieldType => {
|
|
232
|
+
const deriveFieldType = (s: ZodInternals): DerivedFieldType | null => {
|
|
167
233
|
const defType = s._zod.def['type'] as string;
|
|
168
234
|
const derive = fieldTypeByDef[defType];
|
|
169
|
-
return derive ? derive(s) :
|
|
235
|
+
return derive ? derive(s) : null;
|
|
170
236
|
};
|
|
171
237
|
|
|
172
238
|
/** Build options array, merging with overrides when present. */
|
|
@@ -189,29 +255,213 @@ const buildOptions = (
|
|
|
189
255
|
});
|
|
190
256
|
};
|
|
191
257
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
258
|
+
/**
|
|
259
|
+
* Derive the canonical ordered CLI path from a trail ID.
|
|
260
|
+
*
|
|
261
|
+
* @throws {ValidationError} if the trail ID contains empty segments (e.g. consecutive dots).
|
|
262
|
+
*/
|
|
263
|
+
export const deriveCliPath = (trailId: string): string[] => {
|
|
264
|
+
const segments = trailId.split('.');
|
|
265
|
+
const emptyIndex = segments.findIndex((s) => s.length === 0);
|
|
266
|
+
if (emptyIndex !== -1) {
|
|
267
|
+
throw new ValidationError(
|
|
268
|
+
`Trail ID "${trailId}" contains an empty segment at position ${emptyIndex}`
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
return segments;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const hasWhitespace = (value: string): boolean => /\s/.test(value);
|
|
275
|
+
|
|
276
|
+
const validateCliSegment = (segment: string, context: string): string => {
|
|
277
|
+
const normalized = segment.trim();
|
|
278
|
+
if (normalized.length === 0) {
|
|
279
|
+
throw new ValidationError(`${context} cannot contain empty segments`);
|
|
280
|
+
}
|
|
281
|
+
if (hasWhitespace(normalized)) {
|
|
282
|
+
throw new ValidationError(
|
|
283
|
+
`${context} segment "${segment}" cannot contain whitespace`
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
return normalized;
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const splitCliPathString = (value: string, context: string): string[] => {
|
|
290
|
+
const segments = value
|
|
291
|
+
.trim()
|
|
292
|
+
.split(/\s+/)
|
|
293
|
+
.filter((segment) => segment.length > 0);
|
|
294
|
+
if (segments.length === 0) {
|
|
295
|
+
throw new ValidationError(`${context} cannot be empty`);
|
|
296
|
+
}
|
|
297
|
+
return segments.map((segment) => validateCliSegment(segment, context));
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
/** Normalize an authored CLI command path. */
|
|
301
|
+
export const normalizeCliCommandPath = (
|
|
302
|
+
value: CliCommandPathInput,
|
|
303
|
+
context = 'CLI command path'
|
|
304
|
+
): readonly string[] =>
|
|
305
|
+
typeof value === 'string'
|
|
306
|
+
? splitCliPathString(value, context)
|
|
307
|
+
: value.map((segment) => validateCliSegment(segment, context));
|
|
308
|
+
|
|
309
|
+
const isTrailCliProjection = (
|
|
310
|
+
value: CliCommandPathInput | TrailCliProjection
|
|
311
|
+
): value is TrailCliProjection =>
|
|
312
|
+
typeof value !== 'string' &&
|
|
313
|
+
!Array.isArray(value) &&
|
|
314
|
+
value !== null &&
|
|
315
|
+
typeof value === 'object';
|
|
316
|
+
|
|
317
|
+
const trailCliProjectionFor = (
|
|
318
|
+
trail: TrailCliProjectionInput
|
|
319
|
+
): TrailCliProjection | undefined => {
|
|
320
|
+
if (trail.cli === undefined) {
|
|
321
|
+
return undefined;
|
|
322
|
+
}
|
|
323
|
+
return isTrailCliProjection(trail.cli) ? trail.cli : { path: trail.cli };
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
const deriveCanonicalCliRoute = (
|
|
327
|
+
trail: TrailCliProjectionInput
|
|
328
|
+
): CliCommandRoute => {
|
|
329
|
+
const projection = trailCliProjectionFor(trail);
|
|
330
|
+
const path =
|
|
331
|
+
projection?.path === undefined
|
|
332
|
+
? deriveCliPath(trail.id)
|
|
333
|
+
: normalizeCliCommandPath(
|
|
334
|
+
projection.path,
|
|
335
|
+
`CLI command path for trail "${trail.id}"`
|
|
336
|
+
);
|
|
337
|
+
return {
|
|
338
|
+
kind: 'canonical',
|
|
339
|
+
path,
|
|
340
|
+
source: projection?.path === undefined ? 'derived' : 'trail',
|
|
341
|
+
target: trail.id,
|
|
342
|
+
};
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const normalizeCliAlias = ({
|
|
346
|
+
alias,
|
|
347
|
+
canonicalPath,
|
|
348
|
+
source,
|
|
349
|
+
target,
|
|
350
|
+
}: {
|
|
351
|
+
readonly alias: CliCommandAliasInput;
|
|
352
|
+
readonly canonicalPath: readonly string[];
|
|
353
|
+
readonly source: Extract<CliCommandRouteSource, 'surface' | 'trail'>;
|
|
354
|
+
readonly target: string;
|
|
355
|
+
}): CliCommandRoute => {
|
|
356
|
+
const context = `CLI command alias for trail "${target}"`;
|
|
357
|
+
if (typeof alias === 'string') {
|
|
358
|
+
const segment = alias.trim();
|
|
359
|
+
if (segment.length === 0) {
|
|
360
|
+
throw new ValidationError(`${context} cannot be empty`);
|
|
361
|
+
}
|
|
362
|
+
if (hasWhitespace(segment)) {
|
|
363
|
+
throw new ValidationError(
|
|
364
|
+
`${context} must be a single command segment; use a string array for absolute paths`
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
kind: 'alias',
|
|
369
|
+
path: [
|
|
370
|
+
...canonicalPath.slice(0, -1),
|
|
371
|
+
validateCliSegment(segment, context),
|
|
372
|
+
],
|
|
373
|
+
source,
|
|
374
|
+
target,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
return {
|
|
378
|
+
kind: 'alias',
|
|
379
|
+
path: normalizeCliCommandPath(alias, context),
|
|
380
|
+
source,
|
|
381
|
+
target,
|
|
382
|
+
};
|
|
383
|
+
};
|
|
195
384
|
|
|
196
385
|
/**
|
|
197
|
-
*
|
|
386
|
+
* Convert app name + trail ID to an MCP-safe tool name.
|
|
198
387
|
*
|
|
199
|
-
*
|
|
388
|
+
* MCP tool names must be `[a-z0-9_]+`: the app name prefixes the trail id,
|
|
389
|
+
* dots and hyphens collapse to underscores, and everything lowercases. This
|
|
390
|
+
* is the one owner for the projection — the MCP surface renders tools with
|
|
391
|
+
* it and Warden checks binding-name collisions against it, so the two
|
|
392
|
+
* readers cannot drift.
|
|
393
|
+
*
|
|
394
|
+
* @example
|
|
395
|
+
* ```ts
|
|
396
|
+
* deriveMcpToolName('myapp', 'entity.show'); // "myapp_entity_show"
|
|
397
|
+
* deriveMcpToolName('dispatch', 'patch.search'); // "dispatch_patch_search"
|
|
398
|
+
* ```
|
|
200
399
|
*/
|
|
400
|
+
export const deriveMcpToolName = (appName: string, trailId: string): string => {
|
|
401
|
+
const prefix = appName.toLowerCase().replaceAll(/[.-]/g, '_');
|
|
402
|
+
const suffix = trailId.toLowerCase().replaceAll(/[.-]/g, '_');
|
|
403
|
+
return `${prefix}_${suffix}`;
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
/** Derive resolved CLI command routes for one trail. */
|
|
407
|
+
export const deriveTrailCliCommandProjection = (
|
|
408
|
+
trail: TrailCliProjectionInput,
|
|
409
|
+
options?: DeriveTrailCliCommandProjectionOptions
|
|
410
|
+
): TrailCliCommandProjection => {
|
|
411
|
+
const canonical = deriveCanonicalCliRoute(trail);
|
|
412
|
+
const projection = trailCliProjectionFor(trail);
|
|
413
|
+
const trailAliases =
|
|
414
|
+
projection?.aliases?.map((alias) =>
|
|
415
|
+
normalizeCliAlias({
|
|
416
|
+
alias,
|
|
417
|
+
canonicalPath: canonical.path,
|
|
418
|
+
source: 'trail',
|
|
419
|
+
target: trail.id,
|
|
420
|
+
})
|
|
421
|
+
) ?? [];
|
|
422
|
+
const surfaceAliases =
|
|
423
|
+
options?.aliases?.map((alias) =>
|
|
424
|
+
normalizeCliAlias({
|
|
425
|
+
alias,
|
|
426
|
+
canonicalPath: canonical.path,
|
|
427
|
+
source: options.aliasSource ?? 'surface',
|
|
428
|
+
target: trail.id,
|
|
429
|
+
})
|
|
430
|
+
) ?? [];
|
|
431
|
+
|
|
432
|
+
return {
|
|
433
|
+
path: canonical.path,
|
|
434
|
+
routes: [canonical, ...trailAliases, ...surfaceAliases],
|
|
435
|
+
};
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
// ---------------------------------------------------------------------------
|
|
439
|
+
// Public API
|
|
440
|
+
// ---------------------------------------------------------------------------
|
|
441
|
+
|
|
201
442
|
/** Derive a single field from a shape entry. */
|
|
202
443
|
const deriveField = (
|
|
203
444
|
key: string,
|
|
204
445
|
value: ZodInternals,
|
|
205
446
|
overrides?: Record<string, FieldOverride>
|
|
206
|
-
): Field => {
|
|
447
|
+
): Field | null => {
|
|
207
448
|
const { inner, required, defaultValue, description } = unwrap(value);
|
|
208
|
-
const
|
|
449
|
+
const derived = deriveFieldType(inner);
|
|
450
|
+
if (!derived) {
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
const { type, options: rawOptions } = derived;
|
|
209
454
|
const override = overrides?.[key];
|
|
210
455
|
const label = override?.label ?? description ?? humanize(key);
|
|
211
456
|
const options = buildOptions(rawOptions, override?.options);
|
|
212
457
|
return { default: defaultValue, label, name: key, options, required, type };
|
|
213
458
|
};
|
|
214
459
|
|
|
460
|
+
/**
|
|
461
|
+
* Derive a runtime-agnostic Field[] from a Zod object schema.
|
|
462
|
+
*
|
|
463
|
+
* Uses Zod v4's `_zod.def` for introspection. Returns fields sorted by name.
|
|
464
|
+
*/
|
|
215
465
|
export const deriveFields = (
|
|
216
466
|
schema: z.ZodType,
|
|
217
467
|
overrides?: Record<string, FieldOverride>
|
|
@@ -229,5 +479,7 @@ export const deriveFields = (
|
|
|
229
479
|
const fields = Object.entries(shape).map(([key, value]) =>
|
|
230
480
|
deriveField(key, value, overrides)
|
|
231
481
|
);
|
|
232
|
-
return fields
|
|
482
|
+
return fields
|
|
483
|
+
.filter((field): field is Field => field !== null)
|
|
484
|
+
.toSorted((a, b) => a.name.localeCompare(b.name));
|
|
233
485
|
};
|
package/src/detours.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hard upper bound for detour recovery attempts.
|
|
3
|
+
*
|
|
4
|
+
* Execution and derived surface/topo projections both clamp declared detour
|
|
5
|
+
* attempts to this value so runtime behavior and inspectable contracts stay in
|
|
6
|
+
* lockstep.
|
|
7
|
+
*/
|
|
8
|
+
export const DETOUR_MAX_ATTEMPTS_CAP = 5;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared diagnostic vocabulary for governance-style findings.
|
|
3
|
+
*
|
|
4
|
+
* Runtime side-channel records and field-state reports can still define their
|
|
5
|
+
* own shapes. This base exists for tools that report rule or check failures.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type DiagnosticSeverity = 'error' | 'warn';
|
|
9
|
+
|
|
10
|
+
export interface DiagnosticBase<TCode extends string = string> {
|
|
11
|
+
readonly code?: TCode | undefined;
|
|
12
|
+
readonly message: string;
|
|
13
|
+
readonly severity: DiagnosticSeverity;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface RuleDiagnosticBase<
|
|
17
|
+
TCode extends string = string,
|
|
18
|
+
TRule extends string = string,
|
|
19
|
+
> extends DiagnosticBase<TCode> {
|
|
20
|
+
readonly rule: TRule;
|
|
21
|
+
}
|