@ontrails/core 1.0.0-beta.41 → 1.0.0-beta.43
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 +24 -0
- package/README.md +3 -3
- package/package.json +1 -1
- package/src/{activation-source-projection.ts → activation-source-derivation.ts} +9 -9
- package/src/blob-ref.ts +3 -3
- package/src/derive.ts +27 -27
- package/src/detours.ts +1 -1
- package/src/{error-projection.ts → error-rendering.ts} +3 -3
- package/src/errors.ts +1 -1
- package/src/execute.ts +3 -3
- package/src/fire.ts +1 -1
- package/src/index.ts +23 -23
- package/src/{layer-projection.ts → layer-field-rendering.ts} +30 -30
- package/src/layer.ts +6 -6
- package/src/observe.ts +6 -6
- package/src/resource.ts +1 -1
- package/src/schedule-runtime.ts +1 -1
- package/src/serialization.ts +1 -1
- package/src/structured-examples.ts +27 -27
- package/src/surface-derivation.ts +1 -1
- package/src/surface-overlay.ts +5 -5
- package/src/surface-versioning.ts +3 -3
- package/src/tracing.ts +3 -3
- package/src/trail.ts +9 -9
- package/src/trails/derive-trail.ts +1 -1
- package/src/transport-error-map.ts +14 -14
- package/src/types.ts +2 -2
- package/src/validate-established-topo.ts +6 -6
- package/src/validate-topo.ts +1 -1
- package/src/validation.ts +2 -2
- package/src/version-marker.ts +17 -17
package/src/observe.ts
CHANGED
|
@@ -210,10 +210,10 @@ const stringifyDefaultConsoleRecord = (record: LogRecord): string => {
|
|
|
210
210
|
};
|
|
211
211
|
|
|
212
212
|
/**
|
|
213
|
-
* In-core mirror of `@ontrails/
|
|
213
|
+
* In-core mirror of `@ontrails/observability`'s `createConsoleSink` shape, kept
|
|
214
214
|
* minimal and private to avoid a reverse dependency from `@ontrails/core`
|
|
215
|
-
* onto `@ontrails/
|
|
216
|
-
* `packages/
|
|
215
|
+
* onto `@ontrails/observability`. It mirrors the console level mapping in
|
|
216
|
+
* `packages/observability/src/sinks.ts:50` and emits each record as a single-line
|
|
217
217
|
* JSON object written to the matching `console.{debug|info|warn|error}` method.
|
|
218
218
|
*
|
|
219
219
|
* @remarks
|
|
@@ -231,7 +231,7 @@ const createDefaultConsoleSink = (): LogSink => ({
|
|
|
231
231
|
return;
|
|
232
232
|
}
|
|
233
233
|
const payload = stringifyDefaultConsoleRecord(record);
|
|
234
|
-
// oxlint-disable-next-line trails-local/no-console-in-packages -- ADR 0041 mandates a default console logger in core; this is the single sanctioned console boundary, mirroring `@ontrails/
|
|
234
|
+
// oxlint-disable-next-line trails-local/no-console-in-packages -- ADR 0041 mandates a default console logger in core; this is the single sanctioned console boundary, mirroring `@ontrails/observability`'s `createConsoleSink`.
|
|
235
235
|
console[method](payload);
|
|
236
236
|
},
|
|
237
237
|
});
|
|
@@ -251,8 +251,8 @@ export const normalizeObserve = (
|
|
|
251
251
|
if (observe === undefined) {
|
|
252
252
|
// ADR 0041 promises a non-null `ctx.logger` with zero configuration.
|
|
253
253
|
// Returning the default config here lets the existing topo → adapter
|
|
254
|
-
// path
|
|
255
|
-
// resolution point or a reverse dependency on `@ontrails/
|
|
254
|
+
// path renders this log sink into `ctx.logger` without a second
|
|
255
|
+
// resolution point or a reverse dependency on `@ontrails/observability`.
|
|
256
256
|
return DEFAULT_OBSERVE_CONFIG;
|
|
257
257
|
}
|
|
258
258
|
|
package/src/resource.ts
CHANGED
|
@@ -52,7 +52,7 @@ export interface ResourceSpec<T, C = unknown> {
|
|
|
52
52
|
readonly description?: string | undefined;
|
|
53
53
|
/** Arbitrary meta for tooling and filtering. */
|
|
54
54
|
readonly meta?: Readonly<Record<string, unknown>> | undefined;
|
|
55
|
-
/** Signals
|
|
55
|
+
/** Signals derived or owned by this resource. */
|
|
56
56
|
readonly signals?: readonly AnySignal[] | undefined;
|
|
57
57
|
/** Reserved for future resource-specific design; trail versioning is trail-only. */
|
|
58
58
|
readonly version?: never;
|
package/src/schedule-runtime.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ActivationEntry } from './activation-source.js';
|
|
2
|
-
import { activationSourceKey } from './activation-source-
|
|
2
|
+
import { activationSourceKey } from './activation-source-derivation.js';
|
|
3
3
|
import {
|
|
4
4
|
buildActivationProvenanceTraceAttrs,
|
|
5
5
|
withActivationProvenance,
|
package/src/serialization.ts
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
redactErrorContext,
|
|
27
27
|
redactErrorStack,
|
|
28
28
|
redactErrorString,
|
|
29
|
-
} from './error-
|
|
29
|
+
} from './error-rendering.js';
|
|
30
30
|
import { Result } from './result.js';
|
|
31
31
|
|
|
32
32
|
// ---------------------------------------------------------------------------
|
|
@@ -40,7 +40,7 @@ export interface StructuredSignalExample {
|
|
|
40
40
|
// `Date`, `RegExp`, `Map`, and `Set` are objects (`typeof === 'object'`),
|
|
41
41
|
// so they would pass the structural walk and reach `JSON.stringify`, which
|
|
42
42
|
// silently coerces them: a `Date` becomes its ISO string, a `RegExp` and
|
|
43
|
-
// any `Map`/`Set` become `{}`. Either way the
|
|
43
|
+
// any `Map`/`Set` become `{}`. Either way the derived shape diverges
|
|
44
44
|
// from the example author's declared input. Treat them as non-serializable
|
|
45
45
|
// leaves so the example is dropped rather than misrepresented to MCP
|
|
46
46
|
// clients.
|
|
@@ -107,7 +107,7 @@ const signalIdFromAssertion = (
|
|
|
107
107
|
: undefined;
|
|
108
108
|
};
|
|
109
109
|
|
|
110
|
-
const
|
|
110
|
+
const deriveSignalAssertion = (
|
|
111
111
|
assertion: TrailExampleSignalAssertion
|
|
112
112
|
): StructuredTrailExampleSignalAssertion | undefined => {
|
|
113
113
|
const signalId = signalIdFromAssertion(assertion);
|
|
@@ -115,43 +115,43 @@ const projectSignalAssertion = (
|
|
|
115
115
|
return undefined;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
const
|
|
118
|
+
const derived: Record<string, unknown> = { signalId };
|
|
119
119
|
if (assertion.payload !== undefined) {
|
|
120
120
|
const payload = toJsonSerializable(assertion.payload);
|
|
121
121
|
if (payload === undefined) {
|
|
122
122
|
return undefined;
|
|
123
123
|
}
|
|
124
|
-
|
|
124
|
+
derived['payload'] = payload;
|
|
125
125
|
}
|
|
126
126
|
if (assertion.payloadMatch !== undefined) {
|
|
127
127
|
const payloadMatch = toJsonSerializable(assertion.payloadMatch);
|
|
128
128
|
if (payloadMatch === undefined) {
|
|
129
129
|
return undefined;
|
|
130
130
|
}
|
|
131
|
-
|
|
131
|
+
derived['payloadMatch'] = payloadMatch;
|
|
132
132
|
}
|
|
133
133
|
if (assertion.times !== undefined) {
|
|
134
|
-
|
|
134
|
+
derived['times'] = assertion.times;
|
|
135
135
|
}
|
|
136
136
|
return Object.freeze(
|
|
137
|
-
|
|
137
|
+
derived
|
|
138
138
|
) as unknown as StructuredTrailExampleSignalAssertion;
|
|
139
139
|
};
|
|
140
140
|
|
|
141
|
-
const
|
|
141
|
+
const deriveSignalAssertions = (
|
|
142
142
|
assertions: readonly TrailExampleSignalAssertion[] | undefined
|
|
143
143
|
): readonly StructuredTrailExampleSignalAssertion[] | undefined => {
|
|
144
144
|
if (assertions === undefined) {
|
|
145
145
|
return undefined;
|
|
146
146
|
}
|
|
147
|
-
const
|
|
148
|
-
if (
|
|
147
|
+
const derived = assertions.map(deriveSignalAssertion);
|
|
148
|
+
if (derived.some((assertion) => assertion === undefined)) {
|
|
149
149
|
return undefined;
|
|
150
150
|
}
|
|
151
|
-
return Object.freeze(
|
|
151
|
+
return Object.freeze(derived as StructuredTrailExampleSignalAssertion[]);
|
|
152
152
|
};
|
|
153
153
|
|
|
154
|
-
const
|
|
154
|
+
const deriveExample = (
|
|
155
155
|
example: TrailExample<unknown, unknown>,
|
|
156
156
|
provenance: StructuredTrailExampleProvenance
|
|
157
157
|
): StructuredTrailExample | undefined => {
|
|
@@ -160,7 +160,7 @@ const projectExample = (
|
|
|
160
160
|
return undefined;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
const
|
|
163
|
+
const derived: Record<string, unknown> = {
|
|
164
164
|
input,
|
|
165
165
|
kind: example.error === undefined ? 'success' : 'error',
|
|
166
166
|
name: example.name,
|
|
@@ -168,37 +168,37 @@ const projectExample = (
|
|
|
168
168
|
};
|
|
169
169
|
|
|
170
170
|
if (example.description !== undefined) {
|
|
171
|
-
|
|
171
|
+
derived['description'] = example.description;
|
|
172
172
|
}
|
|
173
173
|
if (example.expected !== undefined) {
|
|
174
174
|
const expected = toJsonSerializable(example.expected);
|
|
175
175
|
if (expected === undefined) {
|
|
176
176
|
return undefined;
|
|
177
177
|
}
|
|
178
|
-
|
|
178
|
+
derived['expected'] = expected;
|
|
179
179
|
}
|
|
180
180
|
if (example.expectedMatch !== undefined) {
|
|
181
181
|
const expectedMatch = toJsonSerializable(example.expectedMatch);
|
|
182
182
|
if (expectedMatch === undefined) {
|
|
183
183
|
return undefined;
|
|
184
184
|
}
|
|
185
|
-
|
|
185
|
+
derived['expectedMatch'] = expectedMatch;
|
|
186
186
|
}
|
|
187
187
|
if (example.error !== undefined) {
|
|
188
|
-
|
|
188
|
+
derived['error'] = example.error;
|
|
189
189
|
}
|
|
190
190
|
if (example.signals !== undefined) {
|
|
191
|
-
const signals =
|
|
191
|
+
const signals = deriveSignalAssertions(example.signals);
|
|
192
192
|
if (signals === undefined) {
|
|
193
193
|
return undefined;
|
|
194
194
|
}
|
|
195
|
-
|
|
195
|
+
derived['signals'] = signals;
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
return Object.freeze(
|
|
198
|
+
return Object.freeze(derived) as unknown as StructuredTrailExample;
|
|
199
199
|
};
|
|
200
200
|
|
|
201
|
-
const
|
|
201
|
+
const deriveSignalExample = (
|
|
202
202
|
payload: unknown
|
|
203
203
|
): StructuredSignalExample | undefined => {
|
|
204
204
|
const serializablePayload = toJsonSerializable(payload);
|
|
@@ -222,13 +222,13 @@ export const deriveStructuredTrailExamples = (
|
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
const provenance = options?.provenance ?? { source: 'trail.examples' };
|
|
225
|
-
const
|
|
226
|
-
.map((example) =>
|
|
225
|
+
const derived = examples
|
|
226
|
+
.map((example) => deriveExample(example, provenance))
|
|
227
227
|
.filter(
|
|
228
228
|
(example): example is StructuredTrailExample => example !== undefined
|
|
229
229
|
);
|
|
230
230
|
|
|
231
|
-
return
|
|
231
|
+
return derived.length > 0 ? Object.freeze(derived) : undefined;
|
|
232
232
|
};
|
|
233
233
|
|
|
234
234
|
export const deriveStructuredSignalExamples = (
|
|
@@ -238,11 +238,11 @@ export const deriveStructuredSignalExamples = (
|
|
|
238
238
|
return undefined;
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
const
|
|
242
|
-
.map(
|
|
241
|
+
const derived = examples
|
|
242
|
+
.map(deriveSignalExample)
|
|
243
243
|
.filter(
|
|
244
244
|
(example): example is StructuredSignalExample => example !== undefined
|
|
245
245
|
);
|
|
246
246
|
|
|
247
|
-
return
|
|
247
|
+
return derived.length > 0 ? Object.freeze(derived) : undefined;
|
|
248
248
|
};
|
|
@@ -21,7 +21,7 @@ export interface SurfaceSelectionOptions {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export interface SurfaceValidationOptions {
|
|
24
|
-
/** Set to `false` to skip established-topo validation during
|
|
24
|
+
/** Set to `false` to skip established-topo validation during derivation. */
|
|
25
25
|
readonly validate?: boolean | undefined;
|
|
26
26
|
}
|
|
27
27
|
|
package/src/surface-overlay.ts
CHANGED
|
@@ -33,7 +33,7 @@ export const SURFACES_OVERLAY_NAMESPACE = 'surfaces' as const;
|
|
|
33
33
|
/**
|
|
34
34
|
* Who authored an overlay envelope.
|
|
35
35
|
*
|
|
36
|
-
* `'adapter-derived'` marks facts an adapter
|
|
36
|
+
* `'adapter-derived'` marks facts an adapter renders from the topo;
|
|
37
37
|
* `'app-authored'` marks bindings the app wrote by hand. Surfaces obey
|
|
38
38
|
* app-authored overlays only — adapters contribute facts, never bindings.
|
|
39
39
|
*
|
|
@@ -242,7 +242,7 @@ export interface SurfaceOverlay {
|
|
|
242
242
|
/** The bindings schema, enforced again on the compile path. */
|
|
243
243
|
readonly schema: z.ZodType;
|
|
244
244
|
/**
|
|
245
|
-
*
|
|
245
|
+
* Derive the overlay facts. Ignores the topo — bindings are authored —
|
|
246
246
|
* so the parameter is optional; the signature stays structurally
|
|
247
247
|
* assignable to the adapter-kit `Overlay` contract's `(topo) => unknown`.
|
|
248
248
|
*/
|
|
@@ -313,7 +313,7 @@ export interface OverlayEnvelopeLike {
|
|
|
313
313
|
readonly provenance?: OverlayProvenance | undefined;
|
|
314
314
|
/** The envelope's fact schema. */
|
|
315
315
|
readonly schema: z.ZodType;
|
|
316
|
-
/**
|
|
316
|
+
/** Derive the envelope's facts. */
|
|
317
317
|
derive(topo?: Topo): unknown;
|
|
318
318
|
/**
|
|
319
319
|
* Static surface bindings, when the envelope carries them directly.
|
|
@@ -575,11 +575,11 @@ const assertMcpSynonymBindingName = (name: string): void => {
|
|
|
575
575
|
|
|
576
576
|
/**
|
|
577
577
|
* Derive the deterministic default description for an MCP grouped entry
|
|
578
|
-
*
|
|
578
|
+
* rendered from the `surfaces` overlay.
|
|
579
579
|
*
|
|
580
580
|
* Both the MCP surface (the runtime tool description) and Topography (the
|
|
581
581
|
* lock's trailhead entry description) read this helper, so the authored
|
|
582
|
-
* binding
|
|
582
|
+
* binding renders one description everywhere.
|
|
583
583
|
*
|
|
584
584
|
* @example
|
|
585
585
|
* ```ts
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from './trail.js';
|
|
6
6
|
import { deriveTrailVersionMarkers } from './version-marker.js';
|
|
7
7
|
|
|
8
|
-
export interface
|
|
8
|
+
export interface SurfaceTrailVersionRendering {
|
|
9
9
|
readonly current: boolean;
|
|
10
10
|
readonly deprecated: boolean;
|
|
11
11
|
readonly marker?: string | undefined;
|
|
@@ -13,9 +13,9 @@ export interface SurfaceTrailVersionProjection {
|
|
|
13
13
|
readonly version: number;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export const
|
|
16
|
+
export const deriveSurfaceTrailVersionRenderings = (
|
|
17
17
|
trail: AnyTrail
|
|
18
|
-
): readonly
|
|
18
|
+
): readonly SurfaceTrailVersionRendering[] | undefined => {
|
|
19
19
|
if (trail.version === undefined) {
|
|
20
20
|
return undefined;
|
|
21
21
|
}
|
package/src/tracing.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* This module is the home for the trace record type, the sink interface,
|
|
5
5
|
* the sink registry, and the helpers `executeTrail` uses to create root
|
|
6
6
|
* trace records and child spans. Core keeps this minimal contract public so
|
|
7
|
-
* `@ontrails/
|
|
7
|
+
* `@ontrails/observability`, adapters, and tests share the
|
|
8
8
|
* same intrinsic execution record shape.
|
|
9
9
|
*
|
|
10
10
|
* Tracing is intrinsic: every `executeTrail` call automatically produces a
|
|
@@ -58,8 +58,8 @@ export interface TraceRecord {
|
|
|
58
58
|
/**
|
|
59
59
|
* Minimal shape a tracing sink must satisfy.
|
|
60
60
|
*
|
|
61
|
-
* Kept intentionally tiny so adapters in `@ontrails/
|
|
62
|
-
* `@ontrails/
|
|
61
|
+
* Kept intentionally tiny so adapters in `@ontrails/observability`,
|
|
62
|
+
* `@ontrails/observability`, and user code can all satisfy it without
|
|
63
63
|
* additional dependencies.
|
|
64
64
|
*/
|
|
65
65
|
export interface TraceSink {
|
package/src/trail.ts
CHANGED
|
@@ -15,7 +15,7 @@ import type { AnyEntity } from './entity.js';
|
|
|
15
15
|
import type {
|
|
16
16
|
FieldOverride,
|
|
17
17
|
CliCommandPathInput,
|
|
18
|
-
|
|
18
|
+
TrailCliRendering,
|
|
19
19
|
} from './derive.js';
|
|
20
20
|
import type { Layer } from './layer.js';
|
|
21
21
|
import type { Result } from './result.js';
|
|
@@ -504,13 +504,13 @@ export interface TrailSpec<
|
|
|
504
504
|
* `topo → surface → trail → implementation` (outermost-first).
|
|
505
505
|
*
|
|
506
506
|
* Layers are typed and inspectable. Omit `input` for surface-invisible
|
|
507
|
-
* wrappers that do not
|
|
507
|
+
* wrappers that do not render any fields.
|
|
508
508
|
*/
|
|
509
509
|
readonly layers?: readonly Layer[] | undefined;
|
|
510
510
|
/** Per-field overrides for deriveFields() (labels, hints, options) */
|
|
511
511
|
readonly fields?: Readonly<Record<string, FieldOverride>> | undefined;
|
|
512
|
-
/** CLI
|
|
513
|
-
readonly cli?: CliCommandPathInput |
|
|
512
|
+
/** CLI rendering metadata for canonical command path overrides and aliases. */
|
|
513
|
+
readonly cli?: CliCommandPathInput | TrailCliRendering | undefined;
|
|
514
514
|
/** Entities this trail operates on. */
|
|
515
515
|
readonly entities?: readonly AnyEntity[] | undefined;
|
|
516
516
|
/** IDs or trail objects of downstream trails this trail may invoke via ctx.compose() */
|
|
@@ -548,13 +548,13 @@ export interface TrailSpec<
|
|
|
548
548
|
| undefined;
|
|
549
549
|
/** Auth requirement: scopes object, 'public', or omitted (undeclared) */
|
|
550
550
|
readonly permit?: PermitRequirement | undefined;
|
|
551
|
-
/** Primary input fields and their order. CLI
|
|
551
|
+
/** Primary input fields and their order. CLI derives as positional args. */
|
|
552
552
|
readonly args?: readonly string[] | false | undefined;
|
|
553
553
|
/** Current trail version number. Omit for current-only unversioned trails. */
|
|
554
554
|
readonly version?: number | undefined;
|
|
555
555
|
/** Explicit historical trail versions. Current stays top-level. */
|
|
556
556
|
readonly versions?: TrailVersions<I, O> | undefined;
|
|
557
|
-
/** Version markers are
|
|
557
|
+
/** Version markers are derived into the resolved graph, not authored. */
|
|
558
558
|
readonly marker?: never;
|
|
559
559
|
}
|
|
560
560
|
|
|
@@ -976,12 +976,12 @@ const normalizeVersionEntry = <CurrentInput, CurrentOutput>(
|
|
|
976
976
|
|
|
977
977
|
if (hasOwn(raw, 'kind')) {
|
|
978
978
|
throw new ValidationError(
|
|
979
|
-
`Trail "${trailId}" version ${version} must not author kind; it is
|
|
979
|
+
`Trail "${trailId}" version ${version} must not author kind; it is derived`
|
|
980
980
|
);
|
|
981
981
|
}
|
|
982
982
|
if (hasOwn(raw, 'marker')) {
|
|
983
983
|
throw new ValidationError(
|
|
984
|
-
`Trail "${trailId}" version ${version} must not author marker; it is
|
|
984
|
+
`Trail "${trailId}" version ${version} must not author marker; it is derived`
|
|
985
985
|
);
|
|
986
986
|
}
|
|
987
987
|
|
|
@@ -1286,7 +1286,7 @@ export function trail<
|
|
|
1286
1286
|
|
|
1287
1287
|
if (hasOwn(rawSpec, 'marker')) {
|
|
1288
1288
|
throw new ValidationError(
|
|
1289
|
-
`Trail "${resolved.id}" must not author marker; it is
|
|
1289
|
+
`Trail "${resolved.id}" must not author marker; it is derived`
|
|
1290
1290
|
);
|
|
1291
1291
|
}
|
|
1292
1292
|
|
|
@@ -760,7 +760,7 @@ const synthesizeDefaultImplementation = <
|
|
|
760
760
|
};
|
|
761
761
|
|
|
762
762
|
/**
|
|
763
|
-
* Mechanically
|
|
763
|
+
* Mechanically derive one CRUD-shaped trail from a entity declaration.
|
|
764
764
|
*
|
|
765
765
|
* When `spec.implementation` is omitted and the call declares a single resource, the
|
|
766
766
|
* helper derives a default implementation that dispatches to the resource accessor
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
INTERNAL_ERROR_PUBLIC_MESSAGE,
|
|
18
18
|
redactErrorString,
|
|
19
|
-
} from './error-
|
|
19
|
+
} from './error-rendering.js';
|
|
20
20
|
|
|
21
21
|
export const surfaceNames = ['cli', 'http', 'jsonRpc', 'mcp'] as const;
|
|
22
22
|
|
|
@@ -48,7 +48,7 @@ export type SurfaceErrorMappings<T> = Record<ErrorCategory, T>;
|
|
|
48
48
|
export type SurfaceErrorCode =
|
|
49
49
|
(typeof codesByCategory)[ErrorCategory][(typeof surfaceCodeKeys)[SurfaceName]];
|
|
50
50
|
|
|
51
|
-
export interface
|
|
51
|
+
export interface SurfaceErrorRendering {
|
|
52
52
|
readonly category: ErrorCategory;
|
|
53
53
|
readonly code: SurfaceErrorCode;
|
|
54
54
|
readonly message: string;
|
|
@@ -57,7 +57,7 @@ export interface SurfaceErrorProjection {
|
|
|
57
57
|
readonly surface: SurfaceName;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
export interface
|
|
60
|
+
export interface ErrorClassSurfaceRendering {
|
|
61
61
|
readonly category: ErrorCategory;
|
|
62
62
|
readonly code: SurfaceErrorCode;
|
|
63
63
|
readonly name: string;
|
|
@@ -102,10 +102,10 @@ export const mapSurfaceError = (
|
|
|
102
102
|
): SurfaceErrorCode =>
|
|
103
103
|
codesByCategory[error.category][surfaceCodeKeys[surface]];
|
|
104
104
|
|
|
105
|
-
export const
|
|
105
|
+
export const renderSurfaceError = (
|
|
106
106
|
surface: SurfaceName,
|
|
107
107
|
error: TrailsError
|
|
108
|
-
):
|
|
108
|
+
): SurfaceErrorRendering => ({
|
|
109
109
|
category: error.category,
|
|
110
110
|
code: mapSurfaceError(surface, error),
|
|
111
111
|
message: error.message,
|
|
@@ -114,18 +114,18 @@ export const projectSurfaceError = (
|
|
|
114
114
|
surface,
|
|
115
115
|
});
|
|
116
116
|
|
|
117
|
-
export const
|
|
117
|
+
export const renderPublicSurfaceError = (
|
|
118
118
|
surface: SurfaceName,
|
|
119
119
|
error: Error
|
|
120
|
-
):
|
|
120
|
+
): SurfaceErrorRendering => {
|
|
121
121
|
if (isTrailsError(error)) {
|
|
122
|
-
const
|
|
122
|
+
const rendering = renderSurfaceError(surface, error);
|
|
123
123
|
return {
|
|
124
|
-
...
|
|
124
|
+
...rendering,
|
|
125
125
|
message:
|
|
126
|
-
|
|
126
|
+
rendering.category === 'internal'
|
|
127
127
|
? INTERNAL_ERROR_PUBLIC_MESSAGE
|
|
128
|
-
: redactErrorString(
|
|
128
|
+
: redactErrorString(rendering.message),
|
|
129
129
|
};
|
|
130
130
|
}
|
|
131
131
|
|
|
@@ -151,15 +151,15 @@ const fixedErrorClassByName: ReadonlyMap<string, FixedErrorClassRegistryEntry> =
|
|
|
151
151
|
);
|
|
152
152
|
|
|
153
153
|
/**
|
|
154
|
-
*
|
|
154
|
+
* Render a known error class name onto a surface without constructing it.
|
|
155
155
|
*
|
|
156
156
|
* Dynamic-category errors such as `RetryExhaustedError` return `undefined`
|
|
157
157
|
* because their surface code depends on the wrapped runtime error.
|
|
158
158
|
*/
|
|
159
|
-
export const
|
|
159
|
+
export const renderErrorClassSurface = (
|
|
160
160
|
surface: SurfaceName,
|
|
161
161
|
errorName: string
|
|
162
|
-
):
|
|
162
|
+
): ErrorClassSurfaceRendering | undefined => {
|
|
163
163
|
const entry = fixedErrorClassByName.get(errorName);
|
|
164
164
|
if (entry === undefined) {
|
|
165
165
|
return undefined;
|
package/src/types.ts
CHANGED
|
@@ -215,13 +215,13 @@ export const SURFACE_LAYER_NAMES_KEY = '__trails_surface_layer_names' as const;
|
|
|
215
215
|
/**
|
|
216
216
|
* Context extension key carrying per-layer runtime input.
|
|
217
217
|
*
|
|
218
|
-
* Surfaces (CLI, MCP, HTTP)
|
|
218
|
+
* Surfaces (CLI, MCP, HTTP) render each typed layer's `input` schema onto
|
|
219
219
|
* their native idioms (flags, tool params, query strings). At execute time
|
|
220
220
|
* the parsed values are partitioned per layer and stored under this key as
|
|
221
221
|
* `Record<layerName, unknown>`. Layers that need runtime input read their
|
|
222
222
|
* own slot via `ctx.extensions?.[LAYER_INPUTS_KEY]?.[layer.name]`.
|
|
223
223
|
*
|
|
224
|
-
* @see TRL-473 for the CLI
|
|
224
|
+
* @see TRL-473 for the CLI derivation contract.
|
|
225
225
|
*/
|
|
226
226
|
export const LAYER_INPUTS_KEY = '__trails_layer_inputs' as const;
|
|
227
227
|
|
|
@@ -5,7 +5,7 @@ import { validateDraftFreeTopo } from './draft.js';
|
|
|
5
5
|
import type { TopoDiagnostic } from './validate-topo.js';
|
|
6
6
|
import { validateTopo } from './validate-topo.js';
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
const DERIVATION_BLOCKING_RULES = new Set([
|
|
9
9
|
'compose-cycle',
|
|
10
10
|
'compose-exists',
|
|
11
11
|
'no-self-compose',
|
|
@@ -20,12 +20,12 @@ const PROJECTION_BLOCKING_RULES = new Set([
|
|
|
20
20
|
'signal-origin-exists',
|
|
21
21
|
]);
|
|
22
22
|
|
|
23
|
-
const
|
|
24
|
-
|
|
23
|
+
const isDerivationBlockingIssue = (issue: TopoDiagnostic): boolean =>
|
|
24
|
+
DERIVATION_BLOCKING_RULES.has(issue.rule) ||
|
|
25
25
|
(issue.rule === 'activation-source-input-compatible' &&
|
|
26
26
|
issue.sourceKind === 'queue');
|
|
27
27
|
|
|
28
|
-
const
|
|
28
|
+
const keepDerivationBlockingIssues = (
|
|
29
29
|
result: ReturnType<typeof validateTopo>
|
|
30
30
|
) => {
|
|
31
31
|
if (result.isOk()) {
|
|
@@ -35,7 +35,7 @@ const keepProjectionBlockingIssues = (
|
|
|
35
35
|
const issues = (
|
|
36
36
|
result.error.context as { issues?: readonly TopoDiagnostic[] } | undefined
|
|
37
37
|
)?.issues;
|
|
38
|
-
const remainingIssues = issues?.filter(
|
|
38
|
+
const remainingIssues = issues?.filter(isDerivationBlockingIssue);
|
|
39
39
|
|
|
40
40
|
if (remainingIssues === undefined || remainingIssues.length === 0) {
|
|
41
41
|
return Result.ok();
|
|
@@ -59,7 +59,7 @@ const keepProjectionBlockingIssues = (
|
|
|
59
59
|
* valid, and they must also reject any remaining draft state.
|
|
60
60
|
*/
|
|
61
61
|
export const validateEstablishedTopo = (topo: Topo) => {
|
|
62
|
-
const structural =
|
|
62
|
+
const structural = keepDerivationBlockingIssues(validateTopo(topo));
|
|
63
63
|
if (structural.isErr()) {
|
|
64
64
|
return structural;
|
|
65
65
|
}
|
package/src/validate-topo.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { getActivationSourceInputCompatibilityIssues } from './activation-source
|
|
|
11
11
|
import {
|
|
12
12
|
activationSourceDeclarationSignature,
|
|
13
13
|
activationSourceKey,
|
|
14
|
-
} from './activation-source-
|
|
14
|
+
} from './activation-source-derivation.js';
|
|
15
15
|
import type { AnyEntity } from './entity.js';
|
|
16
16
|
import { getEntityReferences } from './entity.js';
|
|
17
17
|
import { ValidationError } from './errors.js';
|
package/src/validation.ts
CHANGED
|
@@ -84,9 +84,9 @@ const getSchemaJsonSchemaOverride = (
|
|
|
84
84
|
};
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
|
-
* Whether a schema has a deterministic JSON-schema override
|
|
87
|
+
* Whether a schema has a deterministic JSON-schema override derivation (for
|
|
88
88
|
* example `blobRefSchema`, a `z.custom(...)` carrying the descriptor metadata).
|
|
89
|
-
* Such schemas
|
|
89
|
+
* Such schemas derive to a canonical descriptor regardless of their underlying
|
|
90
90
|
* Zod internals, so marker derivation can treat them as supported.
|
|
91
91
|
*/
|
|
92
92
|
export const schemaHasJsonSchemaOverride = (schema: z.ZodType): boolean =>
|
package/src/version-marker.ts
CHANGED
|
@@ -131,7 +131,7 @@ const wrappedMarkerSchemaTypes = new Set([
|
|
|
131
131
|
'readonly',
|
|
132
132
|
]);
|
|
133
133
|
|
|
134
|
-
// Schemas with a deterministic JSON-schema override (e.g. blobRefSchema)
|
|
134
|
+
// Schemas with a deterministic JSON-schema override (e.g. blobRefSchema) derive
|
|
135
135
|
// to a canonical descriptor, so the preflight accepts them without inspecting
|
|
136
136
|
// the underlying custom Zod internals once runtime-only checks have been ruled
|
|
137
137
|
// out.
|
|
@@ -176,7 +176,7 @@ const assertMarkerLiteralSupported = (
|
|
|
176
176
|
def: Readonly<Record<string, unknown>>,
|
|
177
177
|
path: readonly string[]
|
|
178
178
|
): void => {
|
|
179
|
-
// The JSON-schema
|
|
179
|
+
// The JSON-schema derivation only emits the first literal value, so a
|
|
180
180
|
// multi-value literal (z.literal(['a', 'b'])) would hash identically to a
|
|
181
181
|
// single-value literal and silently collide.
|
|
182
182
|
const { values } = def;
|
|
@@ -345,7 +345,7 @@ const assertMarkerContentSupported = (
|
|
|
345
345
|
const keys = Object.keys(record);
|
|
346
346
|
if (keys.length === 0 && path.at(-1) !== 'properties') {
|
|
347
347
|
throw new ValidationError(
|
|
348
|
-
`Trail version marker content at ${markerValuePath(path)} contains an unsupported empty schema
|
|
348
|
+
`Trail version marker content at ${markerValuePath(path)} contains an unsupported empty schema derivation`
|
|
349
349
|
);
|
|
350
350
|
}
|
|
351
351
|
|
|
@@ -428,14 +428,14 @@ export const deriveTrailVersionMarker = (content: unknown): string => {
|
|
|
428
428
|
return hasher.digest('hex').slice(0, TRAIL_VERSION_MARKER_LENGTH);
|
|
429
429
|
};
|
|
430
430
|
|
|
431
|
-
const
|
|
431
|
+
const deriveSchema = (schema: unknown, path: readonly string[]): unknown => {
|
|
432
432
|
assertMarkerSchemaSupported(schema, path);
|
|
433
433
|
return canonicalizeTrailVersionMarkerContent(
|
|
434
434
|
zodToJsonSchema(schema as never)
|
|
435
435
|
);
|
|
436
436
|
};
|
|
437
437
|
|
|
438
|
-
const
|
|
438
|
+
const deriveVersionDetours = (
|
|
439
439
|
entry: unknown
|
|
440
440
|
): readonly Record<string, unknown>[] | undefined => {
|
|
441
441
|
const raw = entry as unknown as Record<string, unknown>;
|
|
@@ -459,7 +459,7 @@ const projectVersionDetours = (
|
|
|
459
459
|
});
|
|
460
460
|
};
|
|
461
461
|
|
|
462
|
-
const
|
|
462
|
+
const deriveVersionRuntimeRefs = (
|
|
463
463
|
entry: unknown,
|
|
464
464
|
field: 'composes' | 'resources'
|
|
465
465
|
): readonly string[] | undefined => {
|
|
@@ -494,16 +494,16 @@ export const deriveCurrentTrailVersionMarkerContent = (
|
|
|
494
494
|
>
|
|
495
495
|
): Readonly<Record<string, unknown>> => {
|
|
496
496
|
const content: Record<string, unknown> = {
|
|
497
|
-
input:
|
|
497
|
+
input: deriveSchema(trail.input, ['input']),
|
|
498
498
|
kind: 'current',
|
|
499
499
|
...(trail.output === undefined
|
|
500
500
|
? {}
|
|
501
|
-
: { output:
|
|
501
|
+
: { output: deriveSchema(trail.output, ['output']) }),
|
|
502
502
|
};
|
|
503
503
|
|
|
504
|
-
const composes =
|
|
505
|
-
const resources =
|
|
506
|
-
const detours =
|
|
504
|
+
const composes = deriveVersionRuntimeRefs(trail, 'composes');
|
|
505
|
+
const resources = deriveVersionRuntimeRefs(trail, 'resources');
|
|
506
|
+
const detours = deriveVersionDetours(trail);
|
|
507
507
|
if (composes !== undefined) {
|
|
508
508
|
content['composes'] = composes;
|
|
509
509
|
}
|
|
@@ -522,9 +522,9 @@ export const deriveTrailVersionEntryMarkerContent = (
|
|
|
522
522
|
): Readonly<Record<string, unknown>> => {
|
|
523
523
|
const kind = getTrailVersionEntryKind(entry);
|
|
524
524
|
const content: Record<string, unknown> = {
|
|
525
|
-
input:
|
|
525
|
+
input: deriveSchema(entry.input, ['input']),
|
|
526
526
|
kind,
|
|
527
|
-
output:
|
|
527
|
+
output: deriveSchema(entry.output, ['output']),
|
|
528
528
|
};
|
|
529
529
|
|
|
530
530
|
if (kind === 'revision' && entry.transpose !== undefined) {
|
|
@@ -532,9 +532,9 @@ export const deriveTrailVersionEntryMarkerContent = (
|
|
|
532
532
|
}
|
|
533
533
|
|
|
534
534
|
if (kind === 'fork') {
|
|
535
|
-
const composes =
|
|
536
|
-
const resources =
|
|
537
|
-
const detours =
|
|
535
|
+
const composes = deriveVersionRuntimeRefs(entry, 'composes');
|
|
536
|
+
const resources = deriveVersionRuntimeRefs(entry, 'resources');
|
|
537
|
+
const detours = deriveVersionDetours(entry);
|
|
538
538
|
if (composes !== undefined) {
|
|
539
539
|
content['composes'] = composes;
|
|
540
540
|
}
|
|
@@ -599,7 +599,7 @@ export const assertUniqueTrailVersionMarkers = (
|
|
|
599
599
|
for (const [marker, versions] of byMarker) {
|
|
600
600
|
if (versions.length > 1) {
|
|
601
601
|
throw new ValidationError(
|
|
602
|
-
`Trail "${trailId}" versions ${versions.join(', ')}
|
|
602
|
+
`Trail "${trailId}" versions ${versions.join(', ')} derive the same marker ${marker}`
|
|
603
603
|
);
|
|
604
604
|
}
|
|
605
605
|
}
|