@ontrails/core 1.0.0-beta.15 → 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 +69 -0
- package/README.md +64 -19
- package/package.json +9 -8
- package/src/activation-provenance.ts +111 -0
- package/src/activation-source-compatibility.ts +430 -0
- package/src/activation-source-projection.ts +221 -0
- package/src/activation-source.ts +91 -0
- package/src/blob-ref.ts +51 -0
- package/src/context.ts +4 -0
- package/src/{internal/cross-batch.ts → cross-batch.ts} +3 -11
- package/src/detours.ts +8 -0
- package/src/draft.ts +4 -0
- package/src/error-projection.ts +51 -0
- package/src/errors.ts +192 -39
- package/src/execute.ts +443 -44
- package/src/fire.ts +965 -91
- package/src/index.ts +285 -38
- package/src/layer-projection.ts +192 -0
- package/src/layer.ts +32 -1
- package/src/observe.ts +361 -0
- package/src/permits.ts +12 -0
- package/src/redaction/patterns.ts +6 -3
- package/src/resilience.ts +8 -8
- package/src/resource-config.ts +477 -44
- package/src/resource.ts +21 -1
- package/src/result.ts +18 -0
- package/src/run.ts +2 -2
- package/src/schedule-runtime.ts +689 -0
- package/src/schedule.ts +324 -0
- package/src/serialization.ts +90 -57
- package/src/signal-diagnostics.ts +633 -0
- package/src/{internal/signal-ref.ts → signal-ref.ts} +1 -1
- package/src/signal.ts +37 -0
- package/src/store/accessor-protocol.ts +5 -5
- package/src/structured-examples.ts +245 -0
- package/src/surface-derivation.ts +91 -0
- package/src/surface-filter.ts +7 -13
- package/src/topo.ts +386 -6
- package/src/tracing.ts +345 -0
- package/src/trail.ts +168 -22
- package/src/trails/derive-trail.ts +1 -1
- package/src/trails/ingest.ts +7 -15
- package/src/{internal/trails-db.ts → trails-db.ts} +42 -21
- package/src/transport-error-map.ts +146 -28
- package/src/types.ts +82 -28
- package/src/validate-established-topo.ts +6 -0
- package/src/validate-topo.ts +212 -5
- package/src/validation.ts +58 -1
- package/src/webhook.ts +276 -0
- package/src/{internal/zod-wrappers.ts → zod-wrappers.ts} +0 -6
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-lint.log +0 -3
- package/.turbo/turbo-typecheck.log +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 -22
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -37
- package/dist/context.js.map +0 -1
- package/dist/contour.d.ts +0 -81
- package/dist/contour.d.ts.map +0 -1
- package/dist/contour.js +0 -176
- package/dist/contour.js.map +0 -1
- package/dist/cross-schema.d.ts +0 -18
- package/dist/cross-schema.d.ts.map +0 -1
- package/dist/cross-schema.js +0 -29
- package/dist/cross-schema.js.map +0 -1
- package/dist/derive.d.ts +0 -44
- package/dist/derive.d.ts.map +0 -1
- package/dist/derive.js +0 -149
- package/dist/derive.js.map +0 -1
- package/dist/draft.d.ts +0 -28
- package/dist/draft.d.ts.map +0 -1
- package/dist/draft.js +0 -157
- package/dist/draft.js.map +0 -1
- package/dist/errors.d.ts +0 -141
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -185
- package/dist/errors.js.map +0 -1
- package/dist/execute.d.ts +0 -52
- package/dist/execute.d.ts.map +0 -1
- package/dist/execute.js +0 -511
- package/dist/execute.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/fire.d.ts +0 -43
- package/dist/fire.d.ts.map +0 -1
- package/dist/fire.js +0 -185
- package/dist/fire.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/index.d.ts +0 -59
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -61
- package/dist/index.js.map +0 -1
- package/dist/internal/cross-batch.d.ts +0 -44
- package/dist/internal/cross-batch.d.ts.map +0 -1
- package/dist/internal/cross-batch.js +0 -58
- package/dist/internal/cross-batch.js.map +0 -1
- package/dist/internal/fork-ctx.d.ts +0 -41
- package/dist/internal/fork-ctx.d.ts.map +0 -1
- package/dist/internal/fork-ctx.js +0 -47
- package/dist/internal/fork-ctx.js.map +0 -1
- package/dist/internal/signal-ref.d.ts +0 -15
- package/dist/internal/signal-ref.d.ts.map +0 -1
- package/dist/internal/signal-ref.js +0 -41
- package/dist/internal/signal-ref.js.map +0 -1
- package/dist/internal/topo-saves.d.ts +0 -53
- package/dist/internal/topo-saves.d.ts.map +0 -1
- package/dist/internal/topo-saves.js +0 -449
- package/dist/internal/topo-saves.js.map +0 -1
- package/dist/internal/topo-snapshots.d.ts +0 -57
- package/dist/internal/topo-snapshots.d.ts.map +0 -1
- package/dist/internal/topo-snapshots.js +0 -372
- package/dist/internal/topo-snapshots.js.map +0 -1
- package/dist/internal/topo-store-read.d.ts +0 -70
- package/dist/internal/topo-store-read.d.ts.map +0 -1
- package/dist/internal/topo-store-read.js +0 -221
- package/dist/internal/topo-store-read.js.map +0 -1
- package/dist/internal/topo-store.d.ts +0 -26
- package/dist/internal/topo-store.d.ts.map +0 -1
- package/dist/internal/topo-store.js +0 -621
- package/dist/internal/topo-store.js.map +0 -1
- package/dist/internal/tracing.d.ts +0 -98
- package/dist/internal/tracing.d.ts.map +0 -1
- package/dist/internal/tracing.js +0 -102
- package/dist/internal/tracing.js.map +0 -1
- package/dist/internal/trails-db.d.ts +0 -16
- package/dist/internal/trails-db.d.ts.map +0 -1
- package/dist/internal/trails-db.js +0 -118
- package/dist/internal/trails-db.js.map +0 -1
- package/dist/internal/zod-wrappers.d.ts +0 -40
- package/dist/internal/zod-wrappers.d.ts.map +0 -1
- package/dist/internal/zod-wrappers.js +0 -59
- package/dist/internal/zod-wrappers.js.map +0 -1
- package/dist/layer.d.ts +0 -23
- 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/resource-config.d.ts +0 -22
- package/dist/resource-config.d.ts.map +0 -1
- package/dist/resource-config.js +0 -210
- package/dist/resource-config.js.map +0 -1
- package/dist/resource.d.ts +0 -74
- package/dist/resource.d.ts.map +0 -1
- package/dist/resource.js +0 -61
- package/dist/resource.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/run.d.ts +0 -28
- package/dist/run.d.ts.map +0 -1
- package/dist/run.js +0 -35
- package/dist/run.js.map +0 -1
- package/dist/serialization.d.ts +0 -27
- package/dist/serialization.d.ts.map +0 -1
- package/dist/serialization.js +0 -154
- package/dist/serialization.js.map +0 -1
- package/dist/signal.d.ts +0 -33
- package/dist/signal.d.ts.map +0 -1
- package/dist/signal.js +0 -17
- package/dist/signal.js.map +0 -1
- package/dist/store/accessor-protocol.d.ts +0 -51
- package/dist/store/accessor-protocol.d.ts.map +0 -1
- package/dist/store/accessor-protocol.js +0 -2
- package/dist/store/accessor-protocol.js.map +0 -1
- package/dist/store/index.d.ts +0 -2
- package/dist/store/index.d.ts.map +0 -1
- package/dist/store/index.js +0 -2
- package/dist/store/index.js.map +0 -1
- package/dist/surface-filter.d.ts +0 -13
- package/dist/surface-filter.d.ts.map +0 -1
- package/dist/surface-filter.js +0 -76
- package/dist/surface-filter.js.map +0 -1
- package/dist/topo-store.d.ts +0 -62
- package/dist/topo-store.d.ts.map +0 -1
- package/dist/topo-store.js +0 -319
- package/dist/topo-store.js.map +0 -1
- package/dist/topo.d.ts +0 -39
- package/dist/topo.d.ts.map +0 -1
- package/dist/topo.js +0 -241
- package/dist/topo.js.map +0 -1
- package/dist/trail.d.ts +0 -163
- package/dist/trail.d.ts.map +0 -1
- package/dist/trail.js +0 -75
- package/dist/trail.js.map +0 -1
- package/dist/trails/derive-trail.d.ts +0 -66
- package/dist/trails/derive-trail.d.ts.map +0 -1
- package/dist/trails/derive-trail.js +0 -378
- package/dist/trails/derive-trail.js.map +0 -1
- package/dist/trails/index.d.ts +0 -5
- package/dist/trails/index.d.ts.map +0 -1
- package/dist/trails/index.js +0 -3
- package/dist/trails/index.js.map +0 -1
- package/dist/trails/ingest.d.ts +0 -24
- package/dist/trails/ingest.d.ts.map +0 -1
- package/dist/trails/ingest.js +0 -63
- package/dist/trails/ingest.js.map +0 -1
- package/dist/transport-error-map.d.ts +0 -106
- package/dist/transport-error-map.d.ts.map +0 -1
- package/dist/transport-error-map.js +0 -24
- package/dist/transport-error-map.js.map +0 -1
- package/dist/type-checks.test-d.d.ts +0 -71
- package/dist/type-checks.test-d.d.ts.map +0 -1
- package/dist/type-checks.test-d.js +0 -12
- package/dist/type-checks.test-d.js.map +0 -1
- package/dist/type-utils.d.ts +0 -33
- package/dist/type-utils.d.ts.map +0 -1
- package/dist/type-utils.js +0 -12
- package/dist/type-utils.js.map +0 -1
- package/dist/types.d.ts +0 -177
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -3
- package/dist/types.js.map +0 -1
- package/dist/validate-established-topo.d.ts +0 -76
- package/dist/validate-established-topo.d.ts.map +0 -1
- package/dist/validate-established-topo.js +0 -43
- package/dist/validate-established-topo.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 -193
- 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 -165
- 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 -77
- package/src/__tests__/contour.test.ts +0 -263
- package/src/__tests__/derive-trail.test.ts +0 -668
- package/src/__tests__/derive.test.ts +0 -260
- package/src/__tests__/errors.test.ts +0 -427
- package/src/__tests__/execute.test.ts +0 -1563
- package/src/__tests__/fetch.test.ts +0 -217
- package/src/__tests__/fire.test.ts +0 -1056
- package/src/__tests__/fork-ctx.test.ts +0 -83
- package/src/__tests__/guards.test.ts +0 -102
- package/src/__tests__/ingest.test.ts +0 -191
- package/src/__tests__/layer.test.ts +0 -125
- 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__/resource.test.ts +0 -204
- package/src/__tests__/result.test.ts +0 -155
- package/src/__tests__/run.test.ts +0 -181
- package/src/__tests__/serialization.test.ts +0 -401
- package/src/__tests__/service-config.test.ts +0 -224
- package/src/__tests__/signal.test.ts +0 -82
- package/src/__tests__/surface-filter.test.ts +0 -196
- package/src/__tests__/topo-store-read.test.ts +0 -256
- package/src/__tests__/topo-store.test.ts +0 -869
- package/src/__tests__/topo.test.ts +0 -573
- package/src/__tests__/trail-permit.test.ts +0 -60
- package/src/__tests__/trail.test.ts +0 -426
- package/src/__tests__/trails-db.test.ts +0 -198
- package/src/__tests__/transport-error-map.test.ts +0 -82
- package/src/__tests__/type-utils.test.ts +0 -118
- package/src/__tests__/validate-topo.test.ts +0 -611
- package/src/__tests__/validation.test.ts +0 -336
- package/src/__tests__/workspace.test.ts +0 -183
- package/src/__tests__/zod-wrappers.test.ts +0 -62
- package/src/internal/topo-snapshots.ts +0 -517
- package/src/internal/topo-store-read.ts +0 -481
- package/src/internal/topo-store.ts +0 -1192
- package/src/internal/tracing.ts +0 -187
- package/src/topo-store.ts +0 -510
- package/src/type-checks.test-d.ts +0 -106
- package/tsconfig.json +0 -9
- package/tsconfig.tests.json +0 -10
- package/tsconfig.tsbuildinfo +0 -1
package/src/execute.ts
CHANGED
|
@@ -11,10 +11,20 @@ import type { z } from 'zod';
|
|
|
11
11
|
import type { AnyTrail } from './trail.js';
|
|
12
12
|
import type { Layer } from './layer.js';
|
|
13
13
|
import type { ResourceOverrideMap } from './resource.js';
|
|
14
|
-
import type { TraceContext, TraceRecord } from './
|
|
14
|
+
import type { TraceContext, TraceRecord } from './tracing.js';
|
|
15
15
|
import type { Topo } from './topo.js';
|
|
16
16
|
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
buildActivationProvenanceTraceAttrs,
|
|
19
|
+
getActivationProvenance,
|
|
20
|
+
} from './activation-provenance.js';
|
|
21
|
+
import {
|
|
22
|
+
createFireFn,
|
|
23
|
+
isFrameworkFireFn,
|
|
24
|
+
waitForPendingFireDispatches,
|
|
25
|
+
withFireDispatchTracking,
|
|
26
|
+
} from './fire.js';
|
|
27
|
+
import type { BasePermit } from './permits.js';
|
|
18
28
|
import type {
|
|
19
29
|
CrossBatchOptions,
|
|
20
30
|
CrossFn,
|
|
@@ -31,14 +41,16 @@ import {
|
|
|
31
41
|
CancelledError,
|
|
32
42
|
InternalError,
|
|
33
43
|
NotFoundError,
|
|
44
|
+
PermitError,
|
|
34
45
|
RetryExhaustedError,
|
|
35
46
|
TrailsError,
|
|
47
|
+
ValidationError,
|
|
36
48
|
} from './errors.js';
|
|
37
49
|
import {
|
|
38
50
|
claimNextCrossBatchIndex,
|
|
39
51
|
createCrossBatchValidationResults,
|
|
40
52
|
normalizeCrossBatchConcurrency,
|
|
41
|
-
} from './
|
|
53
|
+
} from './cross-batch.js';
|
|
42
54
|
import { forkCtx } from './internal/fork-ctx.js';
|
|
43
55
|
import {
|
|
44
56
|
TRACE_CONTEXT_KEY,
|
|
@@ -48,12 +60,18 @@ import {
|
|
|
48
60
|
getTraceSink,
|
|
49
61
|
isTracingDisabled,
|
|
50
62
|
writeToSink,
|
|
51
|
-
} from './
|
|
63
|
+
} from './tracing.js';
|
|
64
|
+
import {
|
|
65
|
+
OBSERVE_LOGGER_CONTEXT_KEY,
|
|
66
|
+
OBSERVE_LOGGER_METADATA_KEY,
|
|
67
|
+
createObserveLogger,
|
|
68
|
+
} from './observe.js';
|
|
52
69
|
import { Result } from './result.js';
|
|
70
|
+
import { DETOUR_MAX_ATTEMPTS_CAP } from './detours.js';
|
|
53
71
|
import { createResourceLookup } from './resource.js';
|
|
54
72
|
import { createResources } from './resource-config.js';
|
|
55
|
-
import {
|
|
56
|
-
import { validateInput } from './validation.js';
|
|
73
|
+
import { LAYER_INPUTS_KEY, SURFACE_KEY } from './types.js';
|
|
74
|
+
import { validateInput, validateOutput } from './validation.js';
|
|
57
75
|
|
|
58
76
|
type MutableTrailContext = {
|
|
59
77
|
-readonly [K in keyof TrailContext]: TrailContext[K];
|
|
@@ -69,8 +87,30 @@ export interface ExecuteTrailOptions {
|
|
|
69
87
|
readonly ctx?: Partial<TrailContextInit> | undefined;
|
|
70
88
|
/** AbortSignal override (takes final precedence over ctx and factory). */
|
|
71
89
|
readonly abortSignal?: AbortSignal | undefined;
|
|
72
|
-
/**
|
|
90
|
+
/**
|
|
91
|
+
* Typed layers supplied for this execution.
|
|
92
|
+
*
|
|
93
|
+
* Layers compose around the trail implementation. Layers without `input`
|
|
94
|
+
* schemas are surface-invisible wrappers for concerns such as tenant guards,
|
|
95
|
+
* rate limiting, circuit breaking, or custom audit logging.
|
|
96
|
+
*/
|
|
73
97
|
readonly layers?: readonly Layer[] | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* Typed layers attached at surface scope.
|
|
100
|
+
*
|
|
101
|
+
* Surfaces (CLI, MCP, HTTP) forward their `layers` option here so they
|
|
102
|
+
* compose around every trail dispatched through that surface. The final
|
|
103
|
+
* composition order is `topo → surface → trail → blaze` (outermost-first).
|
|
104
|
+
*/
|
|
105
|
+
readonly surfaceLayers?: readonly Layer[] | undefined;
|
|
106
|
+
/**
|
|
107
|
+
* Typed layers attached at topo scope.
|
|
108
|
+
*
|
|
109
|
+
* The CLI/MCP/HTTP surfaces typically forward `topo.layers` here so the
|
|
110
|
+
* topo's declared layers wrap every trail invocation. The final
|
|
111
|
+
* composition order is `topo → surface → trail → blaze` (outermost-first).
|
|
112
|
+
*/
|
|
113
|
+
readonly topoLayers?: readonly Layer[] | undefined;
|
|
74
114
|
/** Factory that produces a base TrailContext (takes precedence over defaults). */
|
|
75
115
|
readonly createContext?:
|
|
76
116
|
| (() => TrailContextInit | Promise<TrailContextInit>)
|
|
@@ -83,6 +123,36 @@ export interface ExecuteTrailOptions {
|
|
|
83
123
|
| undefined;
|
|
84
124
|
/** Topo used for signal-driven activation; required for `ctx.fire()` to work. */
|
|
85
125
|
readonly topo?: Topo | undefined;
|
|
126
|
+
/**
|
|
127
|
+
* Whether this invocation is a dry run.
|
|
128
|
+
*
|
|
129
|
+
* Sets `ctx.dryRun` for the trail. Defaults to `false`. The framework
|
|
130
|
+
* never short-circuits execution on this field — it only carries the
|
|
131
|
+
* flag through. Trails that read `ctx.dryRun` decide what dry-run means.
|
|
132
|
+
*/
|
|
133
|
+
readonly dryRun?: boolean | undefined;
|
|
134
|
+
/**
|
|
135
|
+
* Permit to overlay onto `ctx.permit` for this invocation.
|
|
136
|
+
*
|
|
137
|
+
* When provided, this overrides any permit supplied via `ctx.permit` on
|
|
138
|
+
* the partial context overrides or via the `createContext` factory. Leave
|
|
139
|
+
* unset to inherit the permit from the resolved context (typically
|
|
140
|
+
* `undefined`). Surfaces parse and validate the permit at their boundary
|
|
141
|
+
* (e.g. CLI `--permit '<json>'`) before passing it here.
|
|
142
|
+
*/
|
|
143
|
+
readonly permit?: BasePermit | undefined;
|
|
144
|
+
/**
|
|
145
|
+
* Per-layer runtime input keyed by `Layer.name`.
|
|
146
|
+
*
|
|
147
|
+
* Surfaces (CLI, MCP, HTTP) parse their native idiom into a per-layer
|
|
148
|
+
* input object — usually projected from each layer's `input` schema —
|
|
149
|
+
* and pass it here. The executor merges these into
|
|
150
|
+
* `ctx.extensions[LAYER_INPUTS_KEY]` so layers can read their own slot
|
|
151
|
+
* via `ctx.extensions?.[LAYER_INPUTS_KEY]?.[layer.name]`.
|
|
152
|
+
*
|
|
153
|
+
* @see TRL-473 for the CLI projection contract.
|
|
154
|
+
*/
|
|
155
|
+
readonly layerInputs?: Readonly<Record<string, unknown>> | undefined;
|
|
86
156
|
/**
|
|
87
157
|
* Override the validation schema used for input validation.
|
|
88
158
|
*
|
|
@@ -114,9 +184,39 @@ const applyContextOverrides = (
|
|
|
114
184
|
}
|
|
115
185
|
: base;
|
|
116
186
|
|
|
117
|
-
|
|
187
|
+
const withAbort = options?.abortSignal
|
|
118
188
|
? { ...withOverrides, abortSignal: options.abortSignal }
|
|
119
189
|
: withOverrides;
|
|
190
|
+
|
|
191
|
+
const withDryRun =
|
|
192
|
+
options?.dryRun === undefined
|
|
193
|
+
? withAbort
|
|
194
|
+
: { ...withAbort, dryRun: options.dryRun };
|
|
195
|
+
|
|
196
|
+
const withPermit =
|
|
197
|
+
options?.permit === undefined
|
|
198
|
+
? withDryRun
|
|
199
|
+
: { ...withDryRun, permit: options.permit };
|
|
200
|
+
|
|
201
|
+
if (options?.layerInputs === undefined) {
|
|
202
|
+
return withPermit;
|
|
203
|
+
}
|
|
204
|
+
// Merge per-layer inputs onto any inherited LAYER_INPUTS_KEY slot so
|
|
205
|
+
// crossed/forked contexts can preserve outer-surface routing.
|
|
206
|
+
const inheritedExtensions = withPermit.extensions ?? {};
|
|
207
|
+
const inheritedLayerInputs = (inheritedExtensions[LAYER_INPUTS_KEY] ?? {}) as
|
|
208
|
+
| Readonly<Record<string, unknown>>
|
|
209
|
+
| Record<string, unknown>;
|
|
210
|
+
return {
|
|
211
|
+
...withPermit,
|
|
212
|
+
extensions: {
|
|
213
|
+
...inheritedExtensions,
|
|
214
|
+
[LAYER_INPUTS_KEY]: {
|
|
215
|
+
...inheritedLayerInputs,
|
|
216
|
+
...options.layerInputs,
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
};
|
|
120
220
|
};
|
|
121
221
|
|
|
122
222
|
const bindResourceLookup = (
|
|
@@ -143,6 +243,11 @@ const bindResourceLookup = (
|
|
|
143
243
|
* 1. Factory (`createContext`) or `createTrailContext()` defaults.
|
|
144
244
|
* 2. Partial `ctx` overrides merged on top.
|
|
145
245
|
* 3. `abortSignal` override takes final precedence.
|
|
246
|
+
* 4. `dryRun` option takes final precedence (defaults to `false` via
|
|
247
|
+
* `createTrailContext` when neither option nor `ctx.dryRun` is provided).
|
|
248
|
+
* 5. `permit` option takes final precedence over any inherited permit when
|
|
249
|
+
* provided; leaving it unset preserves whatever the resolved context
|
|
250
|
+
* already carries.
|
|
146
251
|
*/
|
|
147
252
|
const resolveContext = async (
|
|
148
253
|
options?: ExecuteTrailOptions
|
|
@@ -155,17 +260,110 @@ const resolveContext = async (
|
|
|
155
260
|
return bindResourceLookup(resolved, options);
|
|
156
261
|
};
|
|
157
262
|
|
|
263
|
+
const readObserveLoggerMetadata = (
|
|
264
|
+
ctx: TrailContext
|
|
265
|
+
): Record<string, unknown> => {
|
|
266
|
+
const value = ctx.extensions?.[OBSERVE_LOGGER_METADATA_KEY];
|
|
267
|
+
return value !== null && typeof value === 'object'
|
|
268
|
+
? (value as Record<string, unknown>)
|
|
269
|
+
: {};
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const applyTopoObserveContext = (
|
|
273
|
+
trail: AnyTrail,
|
|
274
|
+
ctx: TrailContext,
|
|
275
|
+
topo: Topo | undefined
|
|
276
|
+
): TrailContext => {
|
|
277
|
+
if (topo?.observe?.log === undefined) {
|
|
278
|
+
return ctx;
|
|
279
|
+
}
|
|
280
|
+
const hasTopoObserveLogger =
|
|
281
|
+
ctx.extensions?.[OBSERVE_LOGGER_CONTEXT_KEY] === true;
|
|
282
|
+
if (ctx.logger !== undefined && !hasTopoObserveLogger) {
|
|
283
|
+
return ctx;
|
|
284
|
+
}
|
|
285
|
+
const { log } = topo.observe;
|
|
286
|
+
// Accumulated metadata (e.g. signal fan-out fields) takes precedence over
|
|
287
|
+
// the previous observe logger's `topo`/`trailId` values, but the new trail's
|
|
288
|
+
// identity wins so log records keep pointing at the trail currently running.
|
|
289
|
+
const accumulated = readObserveLoggerMetadata(ctx);
|
|
290
|
+
return {
|
|
291
|
+
...ctx,
|
|
292
|
+
extensions: {
|
|
293
|
+
...ctx.extensions,
|
|
294
|
+
[OBSERVE_LOGGER_CONTEXT_KEY]: true,
|
|
295
|
+
},
|
|
296
|
+
logger: createObserveLogger(log, trail.id, {
|
|
297
|
+
...accumulated,
|
|
298
|
+
topo: topo.name,
|
|
299
|
+
trailId: trail.id,
|
|
300
|
+
}),
|
|
301
|
+
};
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const findMissingScopes = (
|
|
305
|
+
required: readonly string[],
|
|
306
|
+
held: readonly string[]
|
|
307
|
+
): readonly string[] => required.filter((scope) => !held.includes(scope));
|
|
308
|
+
|
|
309
|
+
const enforcePermitRequirement = (
|
|
310
|
+
trail: AnyTrail,
|
|
311
|
+
ctx: TrailContext
|
|
312
|
+
): Result<TrailContext, Error> => {
|
|
313
|
+
const requirement = trail.permit;
|
|
314
|
+
if (requirement === undefined || requirement === 'public') {
|
|
315
|
+
return Result.ok(ctx);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (ctx.permit === undefined) {
|
|
319
|
+
return Result.err(
|
|
320
|
+
new PermitError('No permit provided', {
|
|
321
|
+
context: { required: requirement.scopes, trailId: trail.id },
|
|
322
|
+
})
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const missing = findMissingScopes(requirement.scopes, ctx.permit.scopes);
|
|
327
|
+
return missing.length === 0
|
|
328
|
+
? Result.ok(ctx)
|
|
329
|
+
: Result.err(
|
|
330
|
+
new PermitError(`Missing scopes: ${missing.join(', ')}`, {
|
|
331
|
+
context: { missing, required: requirement.scopes, trailId: trail.id },
|
|
332
|
+
})
|
|
333
|
+
);
|
|
334
|
+
};
|
|
335
|
+
|
|
158
336
|
const prepareContext = async (
|
|
159
337
|
trail: AnyTrail,
|
|
160
338
|
options?: ExecuteTrailOptions
|
|
161
|
-
): Promise<
|
|
162
|
-
|
|
163
|
-
|
|
339
|
+
): Promise<
|
|
340
|
+
Result<
|
|
341
|
+
{ readonly ctx: TrailContext; readonly releaseResources: () => void },
|
|
342
|
+
Error
|
|
343
|
+
>
|
|
344
|
+
> => {
|
|
345
|
+
const baseCtx = applyTopoObserveContext(
|
|
164
346
|
trail,
|
|
165
|
-
|
|
347
|
+
await resolveContext(options),
|
|
348
|
+
options?.topo
|
|
349
|
+
);
|
|
350
|
+
const permitted = enforcePermitRequirement(trail, baseCtx);
|
|
351
|
+
if (permitted.isErr()) {
|
|
352
|
+
return Result.err(permitted.error);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const resources = await createResources(
|
|
356
|
+
trail,
|
|
357
|
+
permitted.value,
|
|
166
358
|
options?.resources,
|
|
167
359
|
options?.configValues
|
|
168
360
|
);
|
|
361
|
+
return resources.isErr()
|
|
362
|
+
? Result.err(resources.error)
|
|
363
|
+
: Result.ok({
|
|
364
|
+
ctx: resources.value.ctx,
|
|
365
|
+
releaseResources: resources.value.release,
|
|
366
|
+
});
|
|
169
367
|
};
|
|
170
368
|
|
|
171
369
|
// ---------------------------------------------------------------------------
|
|
@@ -278,18 +476,27 @@ const buildTracedContext = (
|
|
|
278
476
|
parentId: parent?.spanId,
|
|
279
477
|
permit: extractPermit(ctx),
|
|
280
478
|
rootId: parent?.rootId,
|
|
479
|
+
sampled: parent?.sampled ?? true,
|
|
480
|
+
surface: ctx.extensions?.[SURFACE_KEY] as TraceRecord['surface'],
|
|
281
481
|
traceId: parent?.traceId,
|
|
282
482
|
trailId: trail.id,
|
|
283
|
-
trailhead: ctx.extensions?.[TRAILHEAD_KEY] as TraceRecord['trailhead'],
|
|
284
483
|
});
|
|
484
|
+
const activation = getActivationProvenance(ctx);
|
|
485
|
+
const recordWithAttrs: TraceRecord =
|
|
486
|
+
activation === undefined
|
|
487
|
+
? record
|
|
488
|
+
: {
|
|
489
|
+
...record,
|
|
490
|
+
attrs: buildActivationProvenanceTraceAttrs(activation),
|
|
491
|
+
};
|
|
285
492
|
|
|
286
493
|
// Root trace context for this trail's span. When inheriting a parent, the
|
|
287
494
|
// traceId/rootId carry forward and only spanId advances to the new record.
|
|
288
495
|
const rootTrace: TraceContext = {
|
|
289
496
|
rootId: parent?.rootId ?? record.id,
|
|
290
|
-
sampled: true,
|
|
291
|
-
spanId:
|
|
292
|
-
traceId:
|
|
497
|
+
sampled: recordWithAttrs.sampled ?? true,
|
|
498
|
+
spanId: recordWithAttrs.id,
|
|
499
|
+
traceId: recordWithAttrs.traceId,
|
|
293
500
|
};
|
|
294
501
|
|
|
295
502
|
const tracedCtx: TrailContext = {
|
|
@@ -301,7 +508,7 @@ const buildTracedContext = (
|
|
|
301
508
|
trace: buildTraceFn(rootTrace, sink),
|
|
302
509
|
};
|
|
303
510
|
|
|
304
|
-
return { record, tracedCtx };
|
|
511
|
+
return { record: recordWithAttrs, tracedCtx };
|
|
305
512
|
};
|
|
306
513
|
|
|
307
514
|
const buildUntracedContext = (ctx: TrailContext): TrailContext => {
|
|
@@ -326,6 +533,7 @@ const runImplWithRootRecord = async (
|
|
|
326
533
|
): Promise<Result<unknown, Error>> => {
|
|
327
534
|
try {
|
|
328
535
|
const result = await impl(input, tracedCtx);
|
|
536
|
+
await waitForPendingFireDispatches(tracedCtx);
|
|
329
537
|
const outcome = deriveOutcome(result);
|
|
330
538
|
await writeToSink(
|
|
331
539
|
sink,
|
|
@@ -333,6 +541,7 @@ const runImplWithRootRecord = async (
|
|
|
333
541
|
);
|
|
334
542
|
return result;
|
|
335
543
|
} catch (error: unknown) {
|
|
544
|
+
await waitForPendingFireDispatches(tracedCtx);
|
|
336
545
|
// Normalize unexpected throws so the root record still reflects the error
|
|
337
546
|
// outcome. The outer executeTrail try/catch converts the thrown value into
|
|
338
547
|
// a Result.err(InternalError) for the caller.
|
|
@@ -390,6 +599,45 @@ const stripInheritedResourceExtensions = (
|
|
|
390
599
|
return Object.fromEntries(entries);
|
|
391
600
|
};
|
|
392
601
|
|
|
602
|
+
const deriveConcurrentBranchObserveMetadata = (
|
|
603
|
+
ctx: TrailContext,
|
|
604
|
+
target: AnyTrail,
|
|
605
|
+
branchIndex: number
|
|
606
|
+
): Record<string, unknown> | undefined => {
|
|
607
|
+
// Only carry observe metadata forward when the parent ctx is using a
|
|
608
|
+
// topo-managed observe logger. Otherwise we'd attach branch fields to
|
|
609
|
+
// caller-supplied loggers that didn't opt into the structured contract.
|
|
610
|
+
if (ctx.extensions?.[OBSERVE_LOGGER_CONTEXT_KEY] !== true) {
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
return {
|
|
614
|
+
...readObserveLoggerMetadata(ctx),
|
|
615
|
+
branchIndex,
|
|
616
|
+
crossedTrailId: target.id,
|
|
617
|
+
};
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
const buildConcurrentBranchExtensions = (
|
|
621
|
+
ctx: TrailContext,
|
|
622
|
+
target: AnyTrail,
|
|
623
|
+
topo: Topo | undefined,
|
|
624
|
+
branchIndex: number
|
|
625
|
+
): Record<string, unknown> => {
|
|
626
|
+
const stripped = stripInheritedResourceExtensions(ctx, target, topo);
|
|
627
|
+
const observeMetadata = deriveConcurrentBranchObserveMetadata(
|
|
628
|
+
ctx,
|
|
629
|
+
target,
|
|
630
|
+
branchIndex
|
|
631
|
+
);
|
|
632
|
+
if (observeMetadata === undefined) {
|
|
633
|
+
return stripped;
|
|
634
|
+
}
|
|
635
|
+
return {
|
|
636
|
+
...stripped,
|
|
637
|
+
[OBSERVE_LOGGER_METADATA_KEY]: observeMetadata,
|
|
638
|
+
};
|
|
639
|
+
};
|
|
640
|
+
|
|
393
641
|
const deriveConcurrentBranchLogger = (
|
|
394
642
|
ctx: TrailContext,
|
|
395
643
|
target: AnyTrail,
|
|
@@ -406,7 +654,7 @@ const deriveConcurrentBranchLogger = (
|
|
|
406
654
|
* Concurrent crossings should not inherit already-resolved resource instances
|
|
407
655
|
* from the parent execution scope. Stripping resource IDs from extensions
|
|
408
656
|
* forces each branch to resolve its own scope while still carrying forward
|
|
409
|
-
* request-scoped values like tracing,
|
|
657
|
+
* request-scoped values like tracing, surface identity, permits, and the
|
|
410
658
|
* shared AbortSignal.
|
|
411
659
|
*
|
|
412
660
|
* `cross`, `fire`, and `resource` are cleared so the child execution can
|
|
@@ -420,7 +668,7 @@ const buildConcurrentBranchContext = (
|
|
|
420
668
|
branchIndex: number
|
|
421
669
|
): TrailContext =>
|
|
422
670
|
forkCtx(ctx, {
|
|
423
|
-
extensions:
|
|
671
|
+
extensions: buildConcurrentBranchExtensions(ctx, target, topo, branchIndex),
|
|
424
672
|
logger: deriveConcurrentBranchLogger(ctx, target, branchIndex),
|
|
425
673
|
});
|
|
426
674
|
|
|
@@ -617,8 +865,18 @@ const bindCrossToCtx = (
|
|
|
617
865
|
const bindFireToCtx = (
|
|
618
866
|
ctx: TrailContext,
|
|
619
867
|
topo: Topo | undefined,
|
|
620
|
-
options: ExecuteTrailOptions | undefined
|
|
868
|
+
options: ExecuteTrailOptions | undefined,
|
|
869
|
+
producerTrailId?: string | undefined
|
|
621
870
|
): TrailContext => {
|
|
871
|
+
// Symmetric with bindCrossToCtx: a caller-supplied ctx.fire (e.g. test
|
|
872
|
+
// helper, scenario harness, or runtime intercepting signal fan-out) is
|
|
873
|
+
// preserved as-is. Without this guard, passing both `topo: app` and a
|
|
874
|
+
// custom `ctx.fire` would silently clobber the injected mock with the
|
|
875
|
+
// topo-backed dispatcher. Framework-created fire functions are replaced
|
|
876
|
+
// because consumer fan-out seeds them before the consumer trace span exists.
|
|
877
|
+
if (ctx.fire !== undefined && !isFrameworkFireFn(ctx.fire)) {
|
|
878
|
+
return ctx;
|
|
879
|
+
}
|
|
622
880
|
if (topo === undefined) {
|
|
623
881
|
return ctx;
|
|
624
882
|
}
|
|
@@ -634,15 +892,20 @@ const bindFireToCtx = (
|
|
|
634
892
|
validationSchema: _omitSchema,
|
|
635
893
|
...forwarded
|
|
636
894
|
} = options ?? {};
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
895
|
+
const trackedCtx = withFireDispatchTracking(ctx);
|
|
896
|
+
const fire = createFireFn(
|
|
897
|
+
topo,
|
|
898
|
+
trackedCtx,
|
|
899
|
+
(consumer, input, consumerCtx) =>
|
|
900
|
+
// eslint-disable-next-line no-use-before-define -- executor closure runs only after executeTrail is defined
|
|
901
|
+
executeTrail(consumer, input, {
|
|
902
|
+
...forwarded,
|
|
903
|
+
ctx: consumerCtx,
|
|
904
|
+
topo,
|
|
905
|
+
}),
|
|
906
|
+
producerTrailId
|
|
644
907
|
);
|
|
645
|
-
return { ...
|
|
908
|
+
return { ...trackedCtx, fire };
|
|
646
909
|
};
|
|
647
910
|
|
|
648
911
|
const bindCrossAtLayerBoundary =
|
|
@@ -656,6 +919,7 @@ const bindCrossAtLayerBoundary =
|
|
|
656
919
|
|
|
657
920
|
const bindFireAtLayerBoundary = <I, O>(
|
|
658
921
|
implementation: Implementation<I, O>,
|
|
922
|
+
trail: AnyTrail,
|
|
659
923
|
topo: Topo | undefined,
|
|
660
924
|
options: ExecuteTrailOptions | undefined
|
|
661
925
|
): Implementation<I, O> => {
|
|
@@ -664,15 +928,13 @@ const bindFireAtLayerBoundary = <I, O>(
|
|
|
664
928
|
}
|
|
665
929
|
|
|
666
930
|
return (input, ctx) =>
|
|
667
|
-
implementation(input, bindFireToCtx(ctx, topo, options));
|
|
931
|
+
implementation(input, bindFireToCtx(ctx, topo, options, trail.id));
|
|
668
932
|
};
|
|
669
933
|
|
|
670
934
|
// ---------------------------------------------------------------------------
|
|
671
935
|
// Detour loop
|
|
672
936
|
// ---------------------------------------------------------------------------
|
|
673
937
|
|
|
674
|
-
const DETOUR_MAX_ATTEMPTS_CAP = 5;
|
|
675
|
-
|
|
676
938
|
/**
|
|
677
939
|
* Find the first detour whose `on` class matches the error via `instanceof`.
|
|
678
940
|
*
|
|
@@ -818,6 +1080,28 @@ const wrapWithDetours = (
|
|
|
818
1080
|
};
|
|
819
1081
|
};
|
|
820
1082
|
|
|
1083
|
+
const wrapWithOutputValidation = (
|
|
1084
|
+
trail: AnyTrail,
|
|
1085
|
+
implementation: Implementation<unknown, unknown>
|
|
1086
|
+
): Implementation<unknown, unknown> => {
|
|
1087
|
+
const { output } = trail;
|
|
1088
|
+
if (output === undefined) {
|
|
1089
|
+
return implementation;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
return async (input, ctx) => {
|
|
1093
|
+
const result = await implementation(input, ctx);
|
|
1094
|
+
if (result.isErr()) {
|
|
1095
|
+
return result;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
const validated = validateOutput(output, result.value);
|
|
1099
|
+
return validated.isErr()
|
|
1100
|
+
? Result.err(validated.error)
|
|
1101
|
+
: Result.ok(validated.value);
|
|
1102
|
+
};
|
|
1103
|
+
};
|
|
1104
|
+
|
|
821
1105
|
const prepareRunImpl = (
|
|
822
1106
|
trail: AnyTrail,
|
|
823
1107
|
ctx: TrailContext,
|
|
@@ -831,7 +1115,8 @@ const prepareRunImpl = (
|
|
|
831
1115
|
const ctxWithIntrinsics = bindFireToCtx(
|
|
832
1116
|
bindCrossToCtx(ctx, topo, options),
|
|
833
1117
|
topo,
|
|
834
|
-
options
|
|
1118
|
+
options,
|
|
1119
|
+
trail.id
|
|
835
1120
|
);
|
|
836
1121
|
// Detour loop wraps the blaze (inside layer stack, closest to blaze)
|
|
837
1122
|
let impl = wrapWithDetours(
|
|
@@ -841,6 +1126,7 @@ const prepareRunImpl = (
|
|
|
841
1126
|
topo,
|
|
842
1127
|
options
|
|
843
1128
|
),
|
|
1129
|
+
trail,
|
|
844
1130
|
topo,
|
|
845
1131
|
options
|
|
846
1132
|
),
|
|
@@ -856,6 +1142,7 @@ const prepareRunImpl = (
|
|
|
856
1142
|
topo,
|
|
857
1143
|
options
|
|
858
1144
|
),
|
|
1145
|
+
trail,
|
|
859
1146
|
topo,
|
|
860
1147
|
options
|
|
861
1148
|
);
|
|
@@ -864,7 +1151,7 @@ const prepareRunImpl = (
|
|
|
864
1151
|
|
|
865
1152
|
return {
|
|
866
1153
|
ctxWithIntrinsics,
|
|
867
|
-
impl,
|
|
1154
|
+
impl: wrapWithOutputValidation(trail, impl),
|
|
868
1155
|
};
|
|
869
1156
|
};
|
|
870
1157
|
|
|
@@ -883,7 +1170,11 @@ const runImplWithoutTracing = async (
|
|
|
883
1170
|
topo,
|
|
884
1171
|
options
|
|
885
1172
|
);
|
|
886
|
-
|
|
1173
|
+
try {
|
|
1174
|
+
return await prepared.impl(input, prepared.ctxWithIntrinsics);
|
|
1175
|
+
} finally {
|
|
1176
|
+
await waitForPendingFireDispatches(prepared.ctxWithIntrinsics);
|
|
1177
|
+
}
|
|
887
1178
|
};
|
|
888
1179
|
|
|
889
1180
|
const runTrailWithTracing = async (
|
|
@@ -927,7 +1218,7 @@ const runTrail = async (
|
|
|
927
1218
|
topo: Topo | undefined,
|
|
928
1219
|
options: ExecuteTrailOptions | undefined
|
|
929
1220
|
): Promise<Result<unknown, Error>> => {
|
|
930
|
-
const sink = getTraceSink();
|
|
1221
|
+
const sink = topo?.observe?.trace ?? getTraceSink();
|
|
931
1222
|
return isTracingDisabled(sink)
|
|
932
1223
|
? await runImplWithoutTracing(trail, input, ctx, layers, topo, options)
|
|
933
1224
|
: await runTrailWithTracing(trail, input, ctx, layers, topo, options, sink);
|
|
@@ -937,6 +1228,102 @@ const runTrail = async (
|
|
|
937
1228
|
// Pipeline
|
|
938
1229
|
// ---------------------------------------------------------------------------
|
|
939
1230
|
|
|
1231
|
+
/**
|
|
1232
|
+
* Compose the typed layers attached at topo, surface, and trail scope.
|
|
1233
|
+
*
|
|
1234
|
+
* Composition order is topo → surface → trail → execution-supplied → blaze
|
|
1235
|
+
* (outermost-first): trail-scope layers run inside surface/topo layers, and
|
|
1236
|
+
* `executeTrail({ layers })` layers wrap closest to the blaze for per-call
|
|
1237
|
+
* behavior.
|
|
1238
|
+
*/
|
|
1239
|
+
const composeAttachedLayers = (
|
|
1240
|
+
trail: AnyTrail,
|
|
1241
|
+
options: ExecuteTrailOptions | undefined
|
|
1242
|
+
): readonly Layer[] => [
|
|
1243
|
+
...(options?.topoLayers ?? []),
|
|
1244
|
+
...(options?.surfaceLayers ?? []),
|
|
1245
|
+
...trail.layers,
|
|
1246
|
+
...(options?.layers ?? []),
|
|
1247
|
+
];
|
|
1248
|
+
|
|
1249
|
+
const isLayerInputMap = (
|
|
1250
|
+
value: unknown
|
|
1251
|
+
): value is Readonly<Record<string, unknown>> =>
|
|
1252
|
+
value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
1253
|
+
|
|
1254
|
+
const readContextLayerInputs = (
|
|
1255
|
+
ctx: TrailContext
|
|
1256
|
+
): Result<Readonly<Record<string, unknown>> | undefined, ValidationError> => {
|
|
1257
|
+
const value = ctx.extensions?.[LAYER_INPUTS_KEY];
|
|
1258
|
+
if (value === undefined) {
|
|
1259
|
+
return Result.ok();
|
|
1260
|
+
}
|
|
1261
|
+
return isLayerInputMap(value)
|
|
1262
|
+
? Result.ok(value)
|
|
1263
|
+
: Result.err(
|
|
1264
|
+
new ValidationError(
|
|
1265
|
+
'Layer inputs must be an object keyed by layer name',
|
|
1266
|
+
{
|
|
1267
|
+
context: { extensionKey: LAYER_INPUTS_KEY },
|
|
1268
|
+
}
|
|
1269
|
+
)
|
|
1270
|
+
);
|
|
1271
|
+
};
|
|
1272
|
+
|
|
1273
|
+
const validateLayerInputs = (
|
|
1274
|
+
layers: readonly Layer[],
|
|
1275
|
+
layerInputs: Readonly<Record<string, unknown>>
|
|
1276
|
+
): Result<Readonly<Record<string, unknown>>, ValidationError> => {
|
|
1277
|
+
const validated: Record<string, unknown> = { ...layerInputs };
|
|
1278
|
+
for (const layer of layers) {
|
|
1279
|
+
if (layer.input === undefined) {
|
|
1280
|
+
continue;
|
|
1281
|
+
}
|
|
1282
|
+
const slot = layerInputs[layer.name];
|
|
1283
|
+
if (slot === undefined) {
|
|
1284
|
+
continue;
|
|
1285
|
+
}
|
|
1286
|
+
const parsed = layer.input.safeParse(slot);
|
|
1287
|
+
if (!parsed.success) {
|
|
1288
|
+
return Result.err(
|
|
1289
|
+
new ValidationError(
|
|
1290
|
+
`Invalid input for layer '${layer.name}': ${parsed.error.message}`,
|
|
1291
|
+
{
|
|
1292
|
+
cause: parsed.error,
|
|
1293
|
+
context: { issues: parsed.error.issues, layerName: layer.name },
|
|
1294
|
+
}
|
|
1295
|
+
)
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
validated[layer.name] = parsed.data;
|
|
1299
|
+
}
|
|
1300
|
+
return Result.ok(validated);
|
|
1301
|
+
};
|
|
1302
|
+
|
|
1303
|
+
const validateContextLayerInputs = (
|
|
1304
|
+
ctx: TrailContext,
|
|
1305
|
+
layers: readonly Layer[]
|
|
1306
|
+
): Result<TrailContext, ValidationError> => {
|
|
1307
|
+
const layerInputs = readContextLayerInputs(ctx);
|
|
1308
|
+
if (layerInputs.isErr()) {
|
|
1309
|
+
return Result.err(layerInputs.error);
|
|
1310
|
+
}
|
|
1311
|
+
if (layerInputs.value === undefined) {
|
|
1312
|
+
return Result.ok(ctx);
|
|
1313
|
+
}
|
|
1314
|
+
const validated = validateLayerInputs(layers, layerInputs.value);
|
|
1315
|
+
if (validated.isErr()) {
|
|
1316
|
+
return Result.err(validated.error);
|
|
1317
|
+
}
|
|
1318
|
+
return Result.ok({
|
|
1319
|
+
...ctx,
|
|
1320
|
+
extensions: {
|
|
1321
|
+
...ctx.extensions,
|
|
1322
|
+
[LAYER_INPUTS_KEY]: validated.value,
|
|
1323
|
+
},
|
|
1324
|
+
});
|
|
1325
|
+
};
|
|
1326
|
+
|
|
940
1327
|
/**
|
|
941
1328
|
* Execute a trail through the standard validate-context-layers-run pipeline.
|
|
942
1329
|
*
|
|
@@ -959,17 +1346,29 @@ export const executeTrail = async (
|
|
|
959
1346
|
|
|
960
1347
|
const resolvedCtx = await prepareContext(trail, options);
|
|
961
1348
|
if (resolvedCtx.isErr()) {
|
|
962
|
-
return resolvedCtx;
|
|
1349
|
+
return Result.err(resolvedCtx.error);
|
|
963
1350
|
}
|
|
964
1351
|
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1352
|
+
const layers = composeAttachedLayers(trail, options);
|
|
1353
|
+
try {
|
|
1354
|
+
const layerCtx = validateContextLayerInputs(
|
|
1355
|
+
resolvedCtx.value.ctx,
|
|
1356
|
+
layers
|
|
1357
|
+
);
|
|
1358
|
+
if (layerCtx.isErr()) {
|
|
1359
|
+
return Result.err(layerCtx.error);
|
|
1360
|
+
}
|
|
1361
|
+
return await runTrail(
|
|
1362
|
+
trail,
|
|
1363
|
+
validated.value,
|
|
1364
|
+
layerCtx.value,
|
|
1365
|
+
layers,
|
|
1366
|
+
options?.topo,
|
|
1367
|
+
options
|
|
1368
|
+
);
|
|
1369
|
+
} finally {
|
|
1370
|
+
resolvedCtx.value.releaseResources();
|
|
1371
|
+
}
|
|
973
1372
|
} catch (error: unknown) {
|
|
974
1373
|
const message = error instanceof Error ? error.message : String(error);
|
|
975
1374
|
return Result.err(new InternalError(message));
|