@systemfsoftware/trace-spec 0.0.0 → 0.2.0
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 +19 -0
- package/README.md +31 -8
- package/dist/index.d.ts +129 -23
- package/dist/index.mjs +411 -51
- package/package.json +10 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @systemfsoftware/trace-spec
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Judge contracts against traces produced in another process. `RemoteObservation.layer(source, { interval, settle, timeout })` provides `Observation` by reading a trace store until no read has added a span for `settle`. `TempoTraceStore.source({ baseUrl })` reads Grafana Tempo's `/api/v2/traces/<traceId>` through the `HttpClient` you provide; put auth and tenant headers on that client.
|
|
8
|
+
|
|
9
|
+
New failures `Observation.IncompleteObservationError` (spans never settled, or the store marked the trace partial) and `Observation.TransportObservationError` (the store could not be read) join `Collector.collect`, `Contract.JudgeFailure`, and `Suite.CaseFailure`. This is a breaking type change: code that matches every case of these unions, or restates them by hand, must handle both.
|
|
10
|
+
|
|
11
|
+
## 0.1.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- `Rel`, `Graph`, `FailureDump` and `Prop` functions that took their subject first can now also be called data-last inside `pipe`. Existing calls keep working.
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- Updated dependencies:
|
|
20
|
+
- @systemfsoftware/effect-spec-runtime@0.1.0
|
|
21
|
+
|
|
3
22
|
## 0.0.0
|
|
4
23
|
|
|
5
24
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ In a system with queues, workers, and background consumers, a `200` is not the o
|
|
|
10
10
|
pnpm add @systemfsoftware/trace-spec @systemfsoftware/trace-taxonomy effect
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
The package exports
|
|
13
|
+
The package exports nine namespaces — `Contract`, `Graph`, `Observation`, `ObservationWindow`, `RemoteObservation`, `Rel`, `Stimulus`, `Suite`, `TempoTraceStore`. Errors and verdicts belong to the capability that raises them: `Contract.ContractDecodeError`, `Contract.TraceDisparityError`, `Observation.EmptyObservationError`, `Observation.IncompleteObservationError`, `Observation.TransportObservationError`, `Suite.StimulusFailure`, and `Rel.Hold`/`Rel.Break`/`Rel.Verdict`.
|
|
14
14
|
|
|
15
15
|
## Write a contract
|
|
16
16
|
|
|
@@ -38,15 +38,17 @@ const paymentUnderCheckout = Contract.of(checkout)
|
|
|
38
38
|
|
|
39
39
|
## Run the contract
|
|
40
40
|
|
|
41
|
-
`Contract.judge(contract, input)` stimulates the behaviour under its minted trace, collects that trace's spans, decodes them against the taxonomy (`Graph.decode` answers `Result<TraceGraph, Contract.ContractDecodeError>`), applies the relation, and on a break writes the decoded graph under `artifacts/traces/`. It answers a `Contract.Judgment` — the run, the verdict, and the dump path — so a break is a value, not a failure. Behaviour failures and infrastructure refusals (`Contract.ContractDecodeError
|
|
41
|
+
`Contract.judge(contract, input)` stimulates the behaviour under its minted trace, collects that trace's spans, decodes them against the taxonomy (`Graph.decode` answers `Result<TraceGraph, Contract.ContractDecodeError>`), applies the relation, and on a break writes the decoded graph under `artifacts/traces/`. It answers a `Contract.Judgment` — the run, the verdict, and the dump path — so a break is a value, not a failure. Behaviour failures and infrastructure refusals (`Contract.ContractDecodeError` and the three `Observation` failures) stay on the error channel.
|
|
42
42
|
|
|
43
43
|
`Contract.check(contract, input)` is the test edge over `judge`: a break fails with `Contract.TraceDisparityError`. Navigation over the decoded graph is standalone: `Graph.byId`, `Graph.children`, and `Graph.descendants`.
|
|
44
44
|
|
|
45
|
-
| Outcome | Failure
|
|
46
|
-
| --------------------------------------------- |
|
|
47
|
-
| The relation broke | `Contract.TraceDisparityError`
|
|
48
|
-
| A contracted span lacked a required attribute | `Contract.ContractDecodeError`
|
|
49
|
-
| Nothing ran under the owned trace | `Observation.EmptyObservationError`
|
|
45
|
+
| Outcome | Failure | Meaning |
|
|
46
|
+
| --------------------------------------------- | ---------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
47
|
+
| The relation broke | `Contract.TraceDisparityError` | names the broken conjunct, the spans inspected, and where the decoded graph was written |
|
|
48
|
+
| A contracted span lacked a required attribute | `Contract.ContractDecodeError` | names the declaration and the attribute — never reported as a broken relation |
|
|
49
|
+
| Nothing ran under the owned trace | `Observation.EmptyObservationError` | the observation was empty, which is its own outcome, not a break |
|
|
50
|
+
| The trace never finished arriving | `Observation.IncompleteObservationError` | spans were read but kept changing, or the store marked the trace partial; carries the span count |
|
|
51
|
+
| The trace store could not be read | `Observation.TransportObservationError` | the store refused, was unreachable, or answered a body that does not decode; names the request URL |
|
|
50
52
|
|
|
51
53
|
## Register cases
|
|
52
54
|
|
|
@@ -66,7 +68,7 @@ TraceSuite('checkout.place_order')
|
|
|
66
68
|
})
|
|
67
69
|
```
|
|
68
70
|
|
|
69
|
-
`Suite.make({ it, layer })(name)` stages a suite as a Pipeable builder, and `withLayer`/`withScenarioLayer` are `dual` combinators whose terminal `body` registers the cases. The scenario layer is built fresh per case and must provide `Observation.Observation` and a `FileSystem` for failure dumps, plus whatever the stimulated behaviour needs — the requirement is enforced at the type level. A failing case fails with the harness's own error channel — the
|
|
71
|
+
`Suite.make({ it, layer })(name)` stages a suite as a Pipeable builder, and `withLayer`/`withScenarioLayer` are `dual` combinators whose terminal `body` registers the cases. The scenario layer is built fresh per case and must provide `Observation.Observation` and a `FileSystem` for failure dumps, plus whatever the stimulated behaviour needs — the requirement is enforced at the type level. A failing case fails with the harness's own error channel — the outcomes above, or `Suite.StimulusFailure` when the behaviour itself failed before its trace could be judged. Cases run on the live clock.
|
|
70
72
|
|
|
71
73
|
`.withLayer(shared)` registers the suite over a layer built once for the whole suite; the shared layer then carries the observation harness, and `.withScenarioLayer(scenario)` can follow it for what each case needs fresh.
|
|
72
74
|
|
|
@@ -76,6 +78,27 @@ TraceSuite('checkout.place_order')
|
|
|
76
78
|
|
|
77
79
|
`ObservationWindow.make(serviceName)` is the cold resource; the service name is required. `.scoped` acquires an `ObservationWindow` handle for one scope — an OpenTelemetry in-memory exporter behind a simple span processor and an always-on sampler, shut down when the scope closes — and `ObservationWindow.collect(window, traceId)` reads one trace back. `.layer` binds a window as `Observation.Observation` plus the Effect tracer, one window per layer build, so two acquisitions never see each other's spans. Only the handle module imports the OpenTelemetry SDK. The export happens outside any test-clock boundary.
|
|
78
80
|
|
|
81
|
+
## Observe a remote store
|
|
82
|
+
|
|
83
|
+
When the behaviour runs in another process — a CLI binary, a worker, a container — its spans never reach an in-memory window. Export them to a trace store and read them back with `RemoteObservation.layer`, which provides `Observation.Observation` over a trace source:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { RemoteObservation, TempoTraceStore } from '@systemfsoftware/trace-spec'
|
|
87
|
+
import { Layer } from 'effect'
|
|
88
|
+
import { FetchHttpClient } from 'effect/unstable/http'
|
|
89
|
+
|
|
90
|
+
const observation = RemoteObservation.layer(
|
|
91
|
+
TempoTraceStore.source({ baseUrl: 'http://127.0.0.1:3200' }),
|
|
92
|
+
{ interval: '50 millis', settle: '500 millis', timeout: '10 seconds' },
|
|
93
|
+
).pipe(Layer.provide(FetchHttpClient.layer))
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`collect(traceId)` reads the source every `interval` and keeps every span it has seen, keyed by span id. It answers once no read has added a span for `settle`, because no trace store says when a trace is complete: a trace read too early looks finished but is missing spans still in flight. A read that returns fewer spans than an earlier one never drops a span. The whole read runs under `timeout`, and a read still in flight at the deadline is cancelled: nothing read by then fails with `Observation.EmptyObservationError`, spans that never settled fail with `Observation.IncompleteObservationError`. A source failure fails at once, without retrying. Every duration must be positive.
|
|
97
|
+
|
|
98
|
+
`TempoTraceStore.source({ baseUrl })` reads Grafana Tempo's `GET /api/v2/traces/<traceId>` once. It needs only an `HttpClient`, so auth and tenant headers go on the client you provide: `HttpClient.mapRequest(client, HttpClientRequest.setHeader('X-Scope-OrgID', tenant))`. A trace Tempo marks `PARTIAL` fails with `Observation.IncompleteObservationError`; a non-2xx answer, including `404`, or a body that does not decode fails with `Observation.TransportObservationError`.
|
|
99
|
+
|
|
100
|
+
Another store needs only another source: a `RemoteObservation.TraceSource<R>` is one read that answers the spans the store holds for a trace id now — empty when it holds none — or fails with `Observation.TransportObservationError` or `Observation.IncompleteObservationError`. It never polls, waits, or retries; the layer owns the cadence.
|
|
101
|
+
|
|
79
102
|
## Relations
|
|
80
103
|
|
|
81
104
|
`Rel.exists`, `Rel.absent`, `Rel.unique`, `Rel.child`, `Rel.descendant`, `Rel.status`, `Rel.errorType`, `Rel.attrs`, `Rel.durationLessThan`, `Rel.forall`, `Rel.event`, `Rel.order`, `Rel.any`, `Rel.not`, `Rel.soft`, `Rel.all`, `Rel.fromTaxonomy`.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { Context, Effect, FileSystem, Layer, Result, Schema } from "effect";
|
|
1
|
+
import { Context, Duration, Effect, FileSystem, Layer, Result, Schema } from "effect";
|
|
2
2
|
import { Pipeable } from "effect/Pipeable";
|
|
3
3
|
import { Span, Taxonomy } from "@systemfsoftware/trace-taxonomy";
|
|
4
|
+
import * as HttpClient from "effect/unstable/http/HttpClient";
|
|
4
5
|
import * as OtelTracer from "@effect/opentelemetry/OtelTracer";
|
|
5
6
|
import { BasicTracerProvider, InMemorySpanExporter } from "@opentelemetry/sdk-trace-base";
|
|
6
7
|
import { Suite } from "@systemfsoftware/effect-spec-runtime";
|
|
@@ -23,6 +24,14 @@ declare const EmptyObservationError_base: Schema.Class<EmptyObservationError, Sc
|
|
|
23
24
|
}>, import("effect/Cause").YieldableError>;
|
|
24
25
|
declare class EmptyObservationError extends EmptyObservationError_base {}
|
|
25
26
|
//#endregion
|
|
27
|
+
//#region src/IncompleteObservationError.schema.d.ts
|
|
28
|
+
declare const IncompleteObservationError_base: Schema.Class<IncompleteObservationError, Schema.TaggedStruct<"IncompleteObservationError", {
|
|
29
|
+
readonly traceId: Schema.String;
|
|
30
|
+
readonly spanCount: Schema.Finite;
|
|
31
|
+
readonly detail: Schema.String;
|
|
32
|
+
}>, import("effect/Cause").YieldableError>;
|
|
33
|
+
declare class IncompleteObservationError extends IncompleteObservationError_base {}
|
|
34
|
+
//#endregion
|
|
26
35
|
//#region src/TraceGraph.schema.d.ts
|
|
27
36
|
declare const Status: Schema.Literals<readonly ["ok", "unset", "error"]>;
|
|
28
37
|
type Status = typeof Status.Type;
|
|
@@ -104,9 +113,18 @@ type TraceGraph = typeof TraceGraph.Type;
|
|
|
104
113
|
declare namespace Graph_d_exports {
|
|
105
114
|
export { AttributeValue, Attributes, GraphNode, SpanEvent, SpanLink, SpanRecord, Status, TraceGraph, byId, children, decode, descendants };
|
|
106
115
|
}
|
|
107
|
-
declare const byId:
|
|
108
|
-
|
|
109
|
-
|
|
116
|
+
declare const byId: {
|
|
117
|
+
(spec: Span.Span): (graph: TraceGraph) => ReadonlyArray<GraphNode>;
|
|
118
|
+
(graph: TraceGraph, spec: Span.Span): ReadonlyArray<GraphNode>;
|
|
119
|
+
};
|
|
120
|
+
declare const children: {
|
|
121
|
+
(node: GraphNode): (graph: TraceGraph) => ReadonlyArray<GraphNode>;
|
|
122
|
+
(graph: TraceGraph, node: GraphNode): ReadonlyArray<GraphNode>;
|
|
123
|
+
};
|
|
124
|
+
declare const descendants: {
|
|
125
|
+
(node: GraphNode): (graph: TraceGraph) => ReadonlyArray<GraphNode>;
|
|
126
|
+
(graph: TraceGraph, node: GraphNode): ReadonlyArray<GraphNode>;
|
|
127
|
+
};
|
|
110
128
|
/**
|
|
111
129
|
* Decodes the recorded spans of one trace against a taxonomy, purely: a declared span
|
|
112
130
|
* whose required attributes do not decode refuses with {@link ContractDecodeError}
|
|
@@ -114,12 +132,24 @@ declare const descendants: (graph: TraceGraph, node: GraphNode) => ReadonlyArray
|
|
|
114
132
|
* the graph as recorded. Failure is a `Result`, so the phase that reads the trace cannot
|
|
115
133
|
* fail the pipeline by accident.
|
|
116
134
|
*/
|
|
117
|
-
declare const decode:
|
|
135
|
+
declare const decode: {
|
|
136
|
+
(spans: ReadonlyArray<SpanRecord>, taxonomy: Taxonomy.Taxonomy): (traceId: string) => Result.Result<TraceGraph, ContractDecodeError>;
|
|
137
|
+
(traceId: string, spans: ReadonlyArray<SpanRecord>, taxonomy: Taxonomy.Taxonomy): Result.Result<TraceGraph, ContractDecodeError>;
|
|
138
|
+
};
|
|
139
|
+
//#endregion
|
|
140
|
+
//#region src/TransportObservationError.schema.d.ts
|
|
141
|
+
declare const TransportObservationError_base: Schema.Class<TransportObservationError, Schema.TaggedStruct<"TransportObservationError", {
|
|
142
|
+
readonly traceId: Schema.String;
|
|
143
|
+
readonly source: Schema.String;
|
|
144
|
+
readonly detail: Schema.String;
|
|
145
|
+
}>, import("effect/Cause").YieldableError>;
|
|
146
|
+
declare class TransportObservationError extends TransportObservationError_base {}
|
|
118
147
|
declare namespace Observation_service_d_exports {
|
|
119
|
-
export { Collector, EmptyObservationError, Observation };
|
|
148
|
+
export { Collector, EmptyObservationError, IncompleteObservationError, Observation, ObservationFailure, TransportObservationError };
|
|
120
149
|
}
|
|
150
|
+
type ObservationFailure = EmptyObservationError | IncompleteObservationError | TransportObservationError;
|
|
121
151
|
interface Collector {
|
|
122
|
-
readonly collect: (traceId: string) => Effect.Effect<ReadonlyArray<SpanRecord>,
|
|
152
|
+
readonly collect: (traceId: string) => Effect.Effect<ReadonlyArray<SpanRecord>, ObservationFailure>;
|
|
123
153
|
}
|
|
124
154
|
declare const Observation_base: Context.ServiceClass<Observation, "@systemfsoftware/trace-spec/Observation", Collector>;
|
|
125
155
|
declare class Observation extends Observation_base {}
|
|
@@ -156,22 +186,54 @@ interface Relation {
|
|
|
156
186
|
declare const exists: <S extends Span.Span>(spec: S) => Relation;
|
|
157
187
|
declare const absent: <S extends Span.Span>(spec: S) => Relation;
|
|
158
188
|
declare const unique: <S extends Span.Span>(spec: S) => Relation;
|
|
159
|
-
declare const child:
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
declare const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
declare const
|
|
189
|
+
declare const child: {
|
|
190
|
+
(childSpan: Span.Span): (parent: Span.Span) => Relation;
|
|
191
|
+
(parent: Span.Span, childSpan: Span.Span): Relation;
|
|
192
|
+
};
|
|
193
|
+
declare const descendant: {
|
|
194
|
+
(childSpan: Span.Span): (parent: Span.Span) => Relation;
|
|
195
|
+
(parent: Span.Span, childSpan: Span.Span): Relation;
|
|
196
|
+
};
|
|
197
|
+
declare const order: {
|
|
198
|
+
(after: Span.Span): (before: Span.Span) => Relation;
|
|
199
|
+
(before: Span.Span, after: Span.Span): Relation;
|
|
200
|
+
};
|
|
201
|
+
declare const status: {
|
|
202
|
+
(expected: Status): (spec: Span.Span) => Relation;
|
|
203
|
+
(spec: Span.Span, expected: Status): Relation;
|
|
204
|
+
};
|
|
205
|
+
declare const errorType: {
|
|
206
|
+
(expected: string): (spec: Span.Span) => Relation;
|
|
207
|
+
(spec: Span.Span, expected: string): Relation;
|
|
208
|
+
};
|
|
209
|
+
declare const attrs: {
|
|
210
|
+
<S extends Span.Span>(partial: Partial<Span.AttrsOf<S>>): (spec: S) => Relation;
|
|
211
|
+
<S extends Span.Span>(spec: S, partial: Partial<Span.AttrsOf<S>>): Relation;
|
|
212
|
+
};
|
|
213
|
+
declare const durationLessThan: {
|
|
214
|
+
(millis: number): (spec: Span.Span) => Relation;
|
|
215
|
+
(spec: Span.Span, millis: number): Relation;
|
|
216
|
+
};
|
|
217
|
+
declare const event: {
|
|
218
|
+
(name: string): (spec: Span.Span) => Relation;
|
|
219
|
+
(spec: Span.Span, name: string): Relation;
|
|
220
|
+
};
|
|
221
|
+
declare const forall: {
|
|
222
|
+
(predicate: (node: GraphNode) => boolean, detail: string): (spec: Span.Span) => Relation;
|
|
223
|
+
(spec: Span.Span, predicate: (node: GraphNode) => boolean, detail: string): Relation;
|
|
224
|
+
};
|
|
168
225
|
declare const soft: (self: Relation) => Relation;
|
|
169
226
|
declare const not: (self: Relation) => Relation;
|
|
170
227
|
declare const all: (...relations: ReadonlyArray<Relation>) => Relation;
|
|
171
228
|
declare const any: (...relations: ReadonlyArray<Relation>) => Relation;
|
|
172
|
-
declare const fromTaxonomy:
|
|
173
|
-
|
|
174
|
-
|
|
229
|
+
declare const fromTaxonomy: {
|
|
230
|
+
(options: {
|
|
231
|
+
readonly path: string;
|
|
232
|
+
}): (taxonomy: Taxonomy.Taxonomy) => Relation;
|
|
233
|
+
(taxonomy: Taxonomy.Taxonomy, options: {
|
|
234
|
+
readonly path: string;
|
|
235
|
+
}): Relation;
|
|
236
|
+
};
|
|
175
237
|
declare namespace Stimulus_d_exports {
|
|
176
238
|
export { Run, Stimulus, TraceContext, make$3 as make, traceContext };
|
|
177
239
|
}
|
|
@@ -208,7 +270,7 @@ declare const TraceDisparityError_base: Schema.Class<TraceDisparityError, Schema
|
|
|
208
270
|
}>, import("effect/Cause").YieldableError>;
|
|
209
271
|
declare class TraceDisparityError extends TraceDisparityError_base {}
|
|
210
272
|
declare namespace Contract_d_exports {
|
|
211
|
-
export { CheckFailure, CheckOptions, Contract, ContractDecodeError, Declared$1 as Declared, EmptyObservationError, JudgeFailure, Judgment, Services, Stimulated, TraceDisparityError, TypeId$3 as TypeId, check, holds, judge, of, stimulate };
|
|
273
|
+
export { CheckFailure, CheckOptions, Contract, ContractDecodeError, Declared$1 as Declared, EmptyObservationError, IncompleteObservationError, JudgeFailure, Judgment, Services, Stimulated, TraceDisparityError, TransportObservationError, TypeId$3 as TypeId, check, holds, judge, of, stimulate };
|
|
212
274
|
}
|
|
213
275
|
declare const TypeId$3: unique symbol;
|
|
214
276
|
type TypeId$3 = typeof TypeId$3;
|
|
@@ -246,7 +308,7 @@ interface Judgment<Input, Output> {
|
|
|
246
308
|
interface CheckOptions {
|
|
247
309
|
readonly dumpName?: string | undefined;
|
|
248
310
|
}
|
|
249
|
-
type JudgeFailure<E> = E | ContractDecodeError |
|
|
311
|
+
type JudgeFailure<E> = E | ContractDecodeError | ObservationFailure;
|
|
250
312
|
type CheckFailure<E> = JudgeFailure<E> | TraceDisparityError;
|
|
251
313
|
type Services<R> = R | Observation | FileSystem.FileSystem;
|
|
252
314
|
declare const judge: {
|
|
@@ -257,6 +319,46 @@ declare const check: {
|
|
|
257
319
|
<Input>(input: Input, options?: CheckOptions): <Output, E, R>(self: Contract<Input, Output, E, R>) => Effect.Effect<Judgment<Input, Output>, CheckFailure<E>, Services<R>>;
|
|
258
320
|
<Input, Output, E, R>(self: Contract<Input, Output, E, R>, input: Input, options?: CheckOptions): Effect.Effect<Judgment<Input, Output>, CheckFailure<E>, Services<R>>;
|
|
259
321
|
};
|
|
322
|
+
declare namespace RemoteObservation_d_exports {
|
|
323
|
+
export { Options, TraceSource, layer };
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* The cadence of the poll, the quiet window the observed span set must hold
|
|
327
|
+
* before the read is trusted, and the deadline the whole poll runs under.
|
|
328
|
+
* Durations are refused unless they are positive, at layer construction.
|
|
329
|
+
*/
|
|
330
|
+
interface Options {
|
|
331
|
+
readonly interval: Duration.Input;
|
|
332
|
+
readonly settle: Duration.Input;
|
|
333
|
+
readonly timeout: Duration.Input;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* One read against one store: the spans that store currently holds for a trace
|
|
337
|
+
* id, or the store's own transport or unfinished answer. A source never polls,
|
|
338
|
+
* waits, or retries — the cadence belongs to {@link layer}.
|
|
339
|
+
*/
|
|
340
|
+
type TraceSource<R> = (traceId: string) => Effect.Effect<ReadonlyArray<SpanRecord>, IncompleteObservationError | TransportObservationError, R>;
|
|
341
|
+
/**
|
|
342
|
+
* The `Observation` service over one trace source. `collect` reads the source at
|
|
343
|
+
* `options.interval`, keeps every span it has seen keyed by span id, and answers
|
|
344
|
+
* when that union has stayed unchanged for `options.settle`; a trace the store
|
|
345
|
+
* never served is `EmptyObservationError`, one that never settled is
|
|
346
|
+
* `IncompleteObservationError`, and the source's own failure ends the poll
|
|
347
|
+
* immediately, unchanged. Provides `Observation` alone and requires exactly what
|
|
348
|
+
* the source requires (`Scope.Scope` is supplied by the layer machinery, as
|
|
349
|
+
* every Effect layer constructor scopes it away).
|
|
350
|
+
*/
|
|
351
|
+
declare const layer: {
|
|
352
|
+
(options: Options): <R>(source: TraceSource<R>) => Layer.Layer<Observation, never, Exclude<R, Scope.Scope>>;
|
|
353
|
+
<R>(source: TraceSource<R>, options: Options): Layer.Layer<Observation, never, Exclude<R, Scope.Scope>>;
|
|
354
|
+
};
|
|
355
|
+
declare namespace tempo_trace_store_d_exports {
|
|
356
|
+
export { source };
|
|
357
|
+
}
|
|
358
|
+
/** One read against one Tempo endpoint. Its only requirement is the caller's HTTP client. */
|
|
359
|
+
declare const source: (options: {
|
|
360
|
+
readonly baseUrl: string;
|
|
361
|
+
}) => TraceSource<HttpClient.HttpClient>;
|
|
260
362
|
//#endregion
|
|
261
363
|
//#region src/ObservationWindowSpec.schema.d.ts
|
|
262
364
|
declare const ObservationWindowSpec_base: Schema.Class<ObservationWindowSpec, Schema.Struct<{
|
|
@@ -297,6 +399,10 @@ interface ObservationWindowResource extends Pipeable {
|
|
|
297
399
|
}
|
|
298
400
|
declare const make$1: (serviceName: string) => ObservationWindowResource;
|
|
299
401
|
//#endregion
|
|
402
|
+
//#region src/HarnessFailure.schema.d.ts
|
|
403
|
+
declare const HarnessFailure: Schema.Union<readonly [typeof ContractDecodeError, typeof EmptyObservationError, typeof IncompleteObservationError, typeof TransportObservationError, typeof TraceDisparityError]>;
|
|
404
|
+
type HarnessFailure = typeof HarnessFailure.Type;
|
|
405
|
+
//#endregion
|
|
300
406
|
//#region src/StimulusFailure.schema.d.ts
|
|
301
407
|
declare const StimulusFailure_base: Schema.Class<StimulusFailure, Schema.TaggedStruct<"StimulusFailure", {
|
|
302
408
|
readonly stimulus: Schema.String;
|
|
@@ -308,7 +414,7 @@ declare namespace Suite_d_exports {
|
|
|
308
414
|
}
|
|
309
415
|
declare const TypeId: unique symbol;
|
|
310
416
|
type TypeId = typeof TypeId;
|
|
311
|
-
type CaseFailure =
|
|
417
|
+
type CaseFailure = HarnessFailure | StimulusFailure;
|
|
312
418
|
type Harness = Observation | FileSystem.FileSystem;
|
|
313
419
|
type ArbitraryInput<Input> = Schema.Schema<Input> | Arbitrary.Arbitrary<Input>;
|
|
314
420
|
interface CaseRegistrar<Provided> {
|
|
@@ -341,4 +447,4 @@ declare const withScenarioLayer: {
|
|
|
341
447
|
};
|
|
342
448
|
declare const make: (bindings: Suite.Bindings) => (name: string) => Declared;
|
|
343
449
|
//#endregion
|
|
344
|
-
export { Contract_d_exports as Contract, Graph_d_exports as Graph, Observation_service_d_exports as Observation, observation_window_resource_d_exports as ObservationWindow, Rel_d_exports as Rel, Stimulus_d_exports as Stimulus, Suite_d_exports as Suite };
|
|
450
|
+
export { Contract_d_exports as Contract, Graph_d_exports as Graph, Observation_service_d_exports as Observation, observation_window_resource_d_exports as ObservationWindow, Rel_d_exports as Rel, RemoteObservation_d_exports as RemoteObservation, Stimulus_d_exports as Stimulus, Suite_d_exports as Suite, tempo_trace_store_d_exports as TempoTraceStore };
|
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
-
import { Cause, Context, Effect, Equal, FileSystem, Layer, Match, Option, Predicate, Result, Schema } from "effect";
|
|
2
|
+
import { Array as Array$1, Cause, Clock, Context, Duration, Effect, Encoding, Equal, FileSystem, Layer, Match, Option, Predicate, Ref, Result, Schema, SchemaGetter, SchemaTransformation } from "effect";
|
|
3
3
|
import { dual } from "effect/Function";
|
|
4
4
|
import { Prototype } from "effect/Pipeable";
|
|
5
5
|
import "effect/PlatformError";
|
|
6
6
|
import "@systemfsoftware/trace-taxonomy";
|
|
7
|
+
import * as HttpClient from "effect/unstable/http/HttpClient";
|
|
8
|
+
import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
|
|
9
|
+
import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse";
|
|
7
10
|
import * as OtelTracer from "@effect/opentelemetry/OtelTracer";
|
|
8
11
|
import * as OtelResource from "@effect/opentelemetry/Resource";
|
|
9
12
|
import { SpanStatusCode } from "@opentelemetry/api";
|
|
@@ -61,15 +64,16 @@ const renderNode = (spans, node, depth) => {
|
|
|
61
64
|
...childrenOf(spans, node.spanId).map((child) => renderNode(spans, child, depth + 1))
|
|
62
65
|
]].join("\n");
|
|
63
66
|
};
|
|
64
|
-
|
|
65
|
-
* The dump document: the trace it belongs to, the break that refused it, and the recorded
|
|
66
|
-
* spans as the observation window saw them.
|
|
67
|
-
*/
|
|
68
|
-
const document = (observed, report) => [
|
|
67
|
+
const documentImpl = (observed, report) => [
|
|
69
68
|
`trace ${observed.traceId}`,
|
|
70
69
|
report,
|
|
71
70
|
...observed.spans.map((node) => renderNode(observed.spans, node, 0))
|
|
72
71
|
].join("\n");
|
|
72
|
+
/**
|
|
73
|
+
* The dump document: the trace it belongs to, the break that refused it, and the recorded
|
|
74
|
+
* spans as the observation window saw them.
|
|
75
|
+
*/
|
|
76
|
+
const document = dual(2, documentImpl);
|
|
73
77
|
const sanitize = (value) => value.replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
74
78
|
const fileNameOf = (request) => `${sanitize(request.name ?? request.traceId)}.${sanitize(request.conjunct)}.txt`;
|
|
75
79
|
/**
|
|
@@ -156,12 +160,15 @@ var Graph_exports = /* @__PURE__ */ __exportAll({
|
|
|
156
160
|
decode: () => decode,
|
|
157
161
|
descendants: () => descendants
|
|
158
162
|
});
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
const
|
|
163
|
+
const byIdImpl = (graph, spec) => graph.nodes.filter((node) => node.name === spec.name);
|
|
164
|
+
const byId = dual(2, byIdImpl);
|
|
165
|
+
const childrenImpl = (graph, node) => graph.nodes.filter((candidate) => candidate.parentSpanId === node.spanId);
|
|
166
|
+
const children = dual(2, childrenImpl);
|
|
167
|
+
const descendantsImpl = (graph, node) => {
|
|
162
168
|
const direct = children(graph, node);
|
|
163
169
|
return [...direct, ...direct.flatMap((child) => descendants(graph, child))];
|
|
164
170
|
};
|
|
171
|
+
const descendants = dual(2, descendantsImpl);
|
|
165
172
|
const indexDeclarations = (taxonomy) => new Map(taxonomy.spans.map((span) => [span.name, span]));
|
|
166
173
|
const keyOfPath = (path) => {
|
|
167
174
|
const last = path.at(-1);
|
|
@@ -204,6 +211,10 @@ const graphOf = (traceId, nodes) => ({
|
|
|
204
211
|
traceId,
|
|
205
212
|
nodes
|
|
206
213
|
});
|
|
214
|
+
const decodeImpl = (traceId, spans, taxonomy) => {
|
|
215
|
+
const declarations = indexDeclarations(taxonomy);
|
|
216
|
+
return Result.map(Result.all(spans.map((record) => declaredNodeOf(record, declarations))), (nodes) => graphOf(traceId, nodes));
|
|
217
|
+
};
|
|
207
218
|
/**
|
|
208
219
|
* Decodes the recorded spans of one trace against a taxonomy, purely: a declared span
|
|
209
220
|
* whose required attributes do not decode refuses with {@link ContractDecodeError}
|
|
@@ -211,15 +222,28 @@ const graphOf = (traceId, nodes) => ({
|
|
|
211
222
|
* the graph as recorded. Failure is a `Result`, so the phase that reads the trace cannot
|
|
212
223
|
* fail the pipeline by accident.
|
|
213
224
|
*/
|
|
214
|
-
const decode = (
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
225
|
+
const decode = dual(3, decodeImpl);
|
|
226
|
+
//#endregion
|
|
227
|
+
//#region src/IncompleteObservationError.schema.ts
|
|
228
|
+
var IncompleteObservationError = class extends Schema.TaggedError()("IncompleteObservationError", {
|
|
229
|
+
traceId: Schema.String,
|
|
230
|
+
spanCount: Schema.Finite,
|
|
231
|
+
detail: Schema.String
|
|
232
|
+
}) {};
|
|
233
|
+
//#endregion
|
|
234
|
+
//#region src/TransportObservationError.schema.ts
|
|
235
|
+
var TransportObservationError = class extends Schema.TaggedError()("TransportObservationError", {
|
|
236
|
+
traceId: Schema.String,
|
|
237
|
+
source: Schema.String,
|
|
238
|
+
detail: Schema.String
|
|
239
|
+
}) {};
|
|
218
240
|
//#endregion
|
|
219
241
|
//#region src/Observation.service.ts
|
|
220
242
|
var Observation_service_exports = /* @__PURE__ */ __exportAll({
|
|
221
243
|
EmptyObservationError: () => EmptyObservationError,
|
|
222
|
-
|
|
244
|
+
IncompleteObservationError: () => IncompleteObservationError,
|
|
245
|
+
Observation: () => Observation,
|
|
246
|
+
TransportObservationError: () => TransportObservationError
|
|
223
247
|
});
|
|
224
248
|
var Observation = class extends Context.Service()("@systemfsoftware/trace-spec/Observation") {};
|
|
225
249
|
//#endregion
|
|
@@ -235,7 +259,9 @@ var TraceDisparityError = class extends Schema.TaggedError()("TraceDisparityErro
|
|
|
235
259
|
var Contract_exports = /* @__PURE__ */ __exportAll({
|
|
236
260
|
ContractDecodeError: () => ContractDecodeError,
|
|
237
261
|
EmptyObservationError: () => EmptyObservationError,
|
|
262
|
+
IncompleteObservationError: () => IncompleteObservationError,
|
|
238
263
|
TraceDisparityError: () => TraceDisparityError,
|
|
264
|
+
TransportObservationError: () => TransportObservationError,
|
|
239
265
|
TypeId: () => TypeId$3,
|
|
240
266
|
check: () => check,
|
|
241
267
|
holds: () => holds,
|
|
@@ -303,6 +329,219 @@ const refuseBreak = (relationId, judgment) => Match.value(judgment.verdict).pipe
|
|
|
303
329
|
const checkDual = (self, input, options) => Effect.flatMap(judgeDual(self, input, options), (judgment) => refuseBreak(self.relation.id, judgment));
|
|
304
330
|
const check = dual((args) => isContract(args[0]), checkDual);
|
|
305
331
|
//#endregion
|
|
332
|
+
//#region src/drivers/tempo-trace.schema.ts
|
|
333
|
+
/** Proto bytes (base64 on the wire) as the lowercase hex ids traces are addressed by. */
|
|
334
|
+
const HexId = Schema.Uint8ArrayFromBase64.pipe(Schema.decodeTo(Schema.String, {
|
|
335
|
+
decode: SchemaGetter.transform(Encoding.encodeHex),
|
|
336
|
+
encode: SchemaGetter.decodeHex()
|
|
337
|
+
}));
|
|
338
|
+
const secondsAndNanos = (nanos) => {
|
|
339
|
+
const text = nanos.padStart(10, "0");
|
|
340
|
+
return [Number(text.slice(0, -9)), Number(text.slice(-9))];
|
|
341
|
+
};
|
|
342
|
+
const millisOf$2 = (nanos) => {
|
|
343
|
+
const [seconds, subMillis] = secondsAndNanos(nanos);
|
|
344
|
+
return seconds * 1e3 + subMillis / 1e6;
|
|
345
|
+
};
|
|
346
|
+
const MillisFromNanos = Schema.String.pipe(Schema.decodeTo(Schema.Finite, SchemaTransformation.transform({
|
|
347
|
+
decode: millisOf$2,
|
|
348
|
+
encode: (millis) => String(Math.round(millis * 1e6))
|
|
349
|
+
})));
|
|
350
|
+
const unsetParent = (parent) => parent === "" || /^0+$/.test(parent);
|
|
351
|
+
const heldParent = (parent) => unsetParent(parent) ? null : parent;
|
|
352
|
+
const parentOf$1 = (parent) => heldParent(parent ?? "");
|
|
353
|
+
const EMPTY_STATUS = {};
|
|
354
|
+
const codeOf = (status) => status.code ?? "STATUS_CODE_UNSET";
|
|
355
|
+
const STATUS_BY_CODE$1 = {
|
|
356
|
+
STATUS_CODE_UNSET: "unset",
|
|
357
|
+
STATUS_CODE_OK: "ok",
|
|
358
|
+
STATUS_CODE_ERROR: "error"
|
|
359
|
+
};
|
|
360
|
+
const statusOf$1 = (status) => STATUS_BY_CODE$1[codeOf(status ?? EMPTY_STATUS)];
|
|
361
|
+
const stringValueOf = (value) => value.stringValue ?? null;
|
|
362
|
+
const numberOf = (value) => value.doubleValue ?? intToNumber(value.intValue);
|
|
363
|
+
const intToNumber = (text) => text === void 0 ? null : Number(text);
|
|
364
|
+
const boolOrNumberOf = (value) => value.boolValue ?? numberOf(value);
|
|
365
|
+
const scalarOf = (value) => stringValueOf(value) ?? boolOrNumberOf(value);
|
|
366
|
+
const isScalarMember = (member) => member !== null;
|
|
367
|
+
const arrayValuesOf = (value) => value.arrayValue?.values;
|
|
368
|
+
const scalarMembersOf = (values) => values.map(scalarOf).filter(isScalarMember);
|
|
369
|
+
const arrayValueOf = (values) => values === void 0 ? null : scalarMembersOf(values);
|
|
370
|
+
const presentValueOf = (value) => scalarOf(value) ?? arrayValueOf(arrayValuesOf(value));
|
|
371
|
+
const isPresent$1 = (entry) => entry[1] !== null;
|
|
372
|
+
const entryOf = (attribute) => [attribute.key, attributeValueOf(attribute.value)];
|
|
373
|
+
const attributeValueOf = (value) => value === void 0 ? null : presentValueOf(value);
|
|
374
|
+
const attributesOf = (attributes) => Object.fromEntries((attributes ?? []).map(entryOf).filter(isPresent$1));
|
|
375
|
+
const errorTypeOf$1 = (attributes) => {
|
|
376
|
+
const value = attributes["error.type"];
|
|
377
|
+
return typeof value === "string" ? value : null;
|
|
378
|
+
};
|
|
379
|
+
const eventOf = (event) => ({
|
|
380
|
+
name: event.name,
|
|
381
|
+
attributes: attributesOf(event.attributes)
|
|
382
|
+
});
|
|
383
|
+
const linkIdOf = (id) => id ?? "";
|
|
384
|
+
const linkOf = (link) => ({
|
|
385
|
+
traceId: linkIdOf(link.traceId),
|
|
386
|
+
spanId: linkIdOf(link.spanId)
|
|
387
|
+
});
|
|
388
|
+
const eventsOf$1 = (span) => (span.events ?? []).map(eventOf);
|
|
389
|
+
const linksOf$1 = (span) => (span.links ?? []).map(linkOf);
|
|
390
|
+
const spanRecordOf$1 = (span) => {
|
|
391
|
+
const attributes = attributesOf(span.attributes);
|
|
392
|
+
return {
|
|
393
|
+
traceId: span.traceId,
|
|
394
|
+
spanId: span.spanId,
|
|
395
|
+
parentSpanId: parentOf$1(span.parentSpanId),
|
|
396
|
+
name: span.name,
|
|
397
|
+
status: statusOf$1(span.status),
|
|
398
|
+
errorType: errorTypeOf$1(attributes),
|
|
399
|
+
startMillis: span.startTimeUnixNano,
|
|
400
|
+
durationMillis: span.endTimeUnixNano - span.startTimeUnixNano,
|
|
401
|
+
attributes,
|
|
402
|
+
events: eventsOf$1(span),
|
|
403
|
+
links: linksOf$1(span)
|
|
404
|
+
};
|
|
405
|
+
};
|
|
406
|
+
const wireTextOf = (value) => typeof value === "string" ? { stringValue: value } : void 0;
|
|
407
|
+
const wireBoolOf = (value) => typeof value === "boolean" ? { boolValue: value } : void 0;
|
|
408
|
+
const isValueArray = (value) => Array.isArray(value);
|
|
409
|
+
const wireArrayOf = (value) => ({ arrayValue: { values: isValueArray(value) ? value.map(wireValueOf) : [] } });
|
|
410
|
+
const wireNumberOrArrayOf = (value) => typeof value === "number" ? { doubleValue: value } : wireArrayOf(value);
|
|
411
|
+
const wireBoolOrNumberOf = (value) => wireBoolOf(value) ?? wireNumberOrArrayOf(value);
|
|
412
|
+
const wireValueOf = (value) => wireTextOf(value) ?? wireBoolOrNumberOf(value);
|
|
413
|
+
const wireAttributesOf = (attributes) => Object.entries(attributes).map(([key, value]) => ({
|
|
414
|
+
key,
|
|
415
|
+
value: wireValueOf(value)
|
|
416
|
+
}));
|
|
417
|
+
const STATUS_CODE_BY_STATUS = {
|
|
418
|
+
unset: "STATUS_CODE_UNSET",
|
|
419
|
+
ok: "STATUS_CODE_OK",
|
|
420
|
+
error: "STATUS_CODE_ERROR"
|
|
421
|
+
};
|
|
422
|
+
const wireStatusOf = (status) => ({ code: STATUS_CODE_BY_STATUS[status] });
|
|
423
|
+
const wireSpanOf = (record) => ({
|
|
424
|
+
traceId: record.traceId,
|
|
425
|
+
spanId: record.spanId,
|
|
426
|
+
parentSpanId: record.parentSpanId ?? void 0,
|
|
427
|
+
name: record.name,
|
|
428
|
+
startTimeUnixNano: record.startMillis,
|
|
429
|
+
endTimeUnixNano: record.startMillis + record.durationMillis,
|
|
430
|
+
attributes: wireAttributesOf(record.attributes),
|
|
431
|
+
events: record.events.map((event) => ({
|
|
432
|
+
name: event.name,
|
|
433
|
+
attributes: wireAttributesOf(event.attributes)
|
|
434
|
+
})),
|
|
435
|
+
links: record.links.map((link) => ({
|
|
436
|
+
traceId: link.traceId,
|
|
437
|
+
spanId: link.spanId
|
|
438
|
+
})),
|
|
439
|
+
status: wireStatusOf(record.status)
|
|
440
|
+
});
|
|
441
|
+
const WireAnyValue = Schema.Struct({
|
|
442
|
+
stringValue: Schema.optional(Schema.String),
|
|
443
|
+
boolValue: Schema.optional(Schema.Boolean),
|
|
444
|
+
intValue: Schema.optional(Schema.String),
|
|
445
|
+
doubleValue: Schema.optional(Schema.Finite),
|
|
446
|
+
arrayValue: Schema.optional(Schema.Struct({ values: Schema.optional(Schema.Array(Schema.suspend(() => WireAnyValue))) }))
|
|
447
|
+
});
|
|
448
|
+
const WireAttribute = Schema.Struct({
|
|
449
|
+
key: Schema.String,
|
|
450
|
+
value: Schema.optional(WireAnyValue)
|
|
451
|
+
});
|
|
452
|
+
const WireStatus = Schema.Struct({
|
|
453
|
+
message: Schema.optional(Schema.String),
|
|
454
|
+
code: Schema.optional(Schema.Literals([
|
|
455
|
+
"STATUS_CODE_UNSET",
|
|
456
|
+
"STATUS_CODE_OK",
|
|
457
|
+
"STATUS_CODE_ERROR"
|
|
458
|
+
]))
|
|
459
|
+
});
|
|
460
|
+
const WireEvent = Schema.Struct({
|
|
461
|
+
name: Schema.String,
|
|
462
|
+
timeUnixNano: Schema.optional(MillisFromNanos),
|
|
463
|
+
attributes: Schema.optional(Schema.Array(WireAttribute))
|
|
464
|
+
});
|
|
465
|
+
const WireLink = Schema.Struct({
|
|
466
|
+
traceId: Schema.optional(HexId),
|
|
467
|
+
spanId: Schema.optional(HexId)
|
|
468
|
+
});
|
|
469
|
+
const WireSpan = Schema.Struct({
|
|
470
|
+
traceId: HexId,
|
|
471
|
+
spanId: HexId,
|
|
472
|
+
parentSpanId: Schema.optional(HexId),
|
|
473
|
+
name: Schema.String,
|
|
474
|
+
kind: Schema.optional(Schema.String),
|
|
475
|
+
startTimeUnixNano: MillisFromNanos,
|
|
476
|
+
endTimeUnixNano: MillisFromNanos,
|
|
477
|
+
attributes: Schema.optional(Schema.Array(WireAttribute)),
|
|
478
|
+
events: Schema.optional(Schema.Array(WireEvent)),
|
|
479
|
+
links: Schema.optional(Schema.Array(WireLink)),
|
|
480
|
+
status: Schema.optional(WireStatus),
|
|
481
|
+
traceState: Schema.optional(Schema.String),
|
|
482
|
+
flags: Schema.optional(Schema.Finite)
|
|
483
|
+
}).pipe(Schema.decodeTo(SpanRecord, SchemaTransformation.transform({
|
|
484
|
+
decode: spanRecordOf$1,
|
|
485
|
+
encode: wireSpanOf
|
|
486
|
+
})));
|
|
487
|
+
const WireScopeSpans = Schema.Struct({ spans: Schema.optional(Schema.Array(WireSpan)) });
|
|
488
|
+
const WireResourceSpans = Schema.Struct({ scopeSpans: Schema.optional(Schema.Array(WireScopeSpans)) });
|
|
489
|
+
/** One trace-by-id answer: the spans, plus the store's completeness status when it is not complete. */
|
|
490
|
+
const TempoV2TraceResponse = Schema.Struct({
|
|
491
|
+
trace: Schema.Struct({ resourceSpans: Schema.optional(Schema.Array(WireResourceSpans)) }),
|
|
492
|
+
status: Schema.optional(Schema.String),
|
|
493
|
+
message: Schema.optional(Schema.String)
|
|
494
|
+
});
|
|
495
|
+
//#endregion
|
|
496
|
+
//#region src/drivers/tempo-trace-store.ts
|
|
497
|
+
var tempo_trace_store_exports = /* @__PURE__ */ __exportAll({ source: () => source });
|
|
498
|
+
/**
|
|
499
|
+
* The Grafana Tempo trace source: one read of one trace by id against one
|
|
500
|
+
* store endpoint. It never polls, waits, or retries — completeness belongs
|
|
501
|
+
* to the remote observation layer. Any refusal (unreachable endpoint,
|
|
502
|
+
* non-2xx answer, undecodable body) fails with the transport failure whose
|
|
503
|
+
* `source` is the request URL; an answer the store marked incomplete fails
|
|
504
|
+
* with the incomplete failure, since waiting cannot complete it.
|
|
505
|
+
*/
|
|
506
|
+
const parseBody = Schema.decodeResult(Schema.fromJsonString(TempoV2TraceResponse));
|
|
507
|
+
const orEmpty = (values) => values ?? [];
|
|
508
|
+
const resourceSpansOf = (response) => orEmpty(response.trace.resourceSpans);
|
|
509
|
+
const scopeSpansOf = (resourceSpans) => orEmpty(resourceSpans.scopeSpans);
|
|
510
|
+
const spansOfScope = (scopeSpans) => orEmpty(scopeSpans.spans);
|
|
511
|
+
const spansOf = (response) => resourceSpansOf(response).flatMap((resourceSpans) => scopeSpansOf(resourceSpans).flatMap(spansOfScope));
|
|
512
|
+
const transportFailureOf = (traceId, url, cause) => new TransportObservationError({
|
|
513
|
+
traceId,
|
|
514
|
+
source: url,
|
|
515
|
+
detail: cause
|
|
516
|
+
});
|
|
517
|
+
const refusedOf = (traceId, url, error) => transportFailureOf(traceId, url, error.message);
|
|
518
|
+
const undecodableOf = (traceId, url, error) => transportFailureOf(traceId, url, `undecodable trace body: ${error.message}`);
|
|
519
|
+
const incompleteOf = (traceId, status, spanCount) => new IncompleteObservationError({
|
|
520
|
+
traceId,
|
|
521
|
+
spanCount,
|
|
522
|
+
detail: `the store answered ${status}: waiting cannot complete the trace`
|
|
523
|
+
});
|
|
524
|
+
const isComplete = (status) => status === void 0 || status === "COMPLETE";
|
|
525
|
+
const markedOf = (response) => response.status ?? "INCOMPLETE";
|
|
526
|
+
const recordsOf$1 = (traceId, response) => {
|
|
527
|
+
const spans = spansOf(response);
|
|
528
|
+
return isComplete(response.status) ? Effect.succeed(spans) : Effect.fail(incompleteOf(traceId, markedOf(response), spans.length));
|
|
529
|
+
};
|
|
530
|
+
const readTrace = (traceId, url, body) => {
|
|
531
|
+
const decoded = parseBody(body);
|
|
532
|
+
return Result.isFailure(decoded) ? Effect.fail(undecodableOf(traceId, url, decoded.failure)) : recordsOf$1(traceId, decoded.success);
|
|
533
|
+
};
|
|
534
|
+
/** One read against one Tempo endpoint. Its only requirement is the caller's HTTP client. */
|
|
535
|
+
const source = (options) => {
|
|
536
|
+
const urlOf = (traceId) => `${options.baseUrl}/api/v2/traces/${traceId}`;
|
|
537
|
+
return (traceId) => {
|
|
538
|
+
const url = urlOf(traceId);
|
|
539
|
+
return HttpClient.execute(HttpClientRequest.get(url).pipe(HttpClientRequest.acceptJson)).pipe(Effect.flatMap(HttpClientResponse.filterStatusOk), Effect.flatMap((response) => response.text), Effect.mapError((error) => refusedOf(traceId, url, error)), Effect.flatMap((body) => readTrace(traceId, url, body)));
|
|
540
|
+
};
|
|
541
|
+
};
|
|
542
|
+
Schema.decodeEffect(HexId);
|
|
543
|
+
Schema.encodeEffect(HexId);
|
|
544
|
+
//#endregion
|
|
306
545
|
//#region src/observation-window.handle.ts
|
|
307
546
|
const TypeId$2 = Symbol.for("~systemfsoftware/trace-spec/ObservationWindow");
|
|
308
547
|
const ExporterId = Symbol.for("~systemfsoftware/trace-spec/ObservationWindow/exporter");
|
|
@@ -356,7 +595,7 @@ const linksOf = (span) => span.links.map((link) => ({
|
|
|
356
595
|
traceId: link.context.traceId,
|
|
357
596
|
spanId: link.context.spanId
|
|
358
597
|
}));
|
|
359
|
-
const millisOf = (duration) => duration[0] * 1e3 + duration[1] / 1e6;
|
|
598
|
+
const millisOf$1 = (duration) => duration[0] * 1e3 + duration[1] / 1e6;
|
|
360
599
|
const spanRecordOf = (span) => {
|
|
361
600
|
const attributes = attributeRecordOf(span.attributes);
|
|
362
601
|
return {
|
|
@@ -366,8 +605,8 @@ const spanRecordOf = (span) => {
|
|
|
366
605
|
name: span.name,
|
|
367
606
|
status: statusOf(span.status.code),
|
|
368
607
|
errorType: errorTypeOf(attributes),
|
|
369
|
-
startMillis: millisOf(span.startTime),
|
|
370
|
-
durationMillis: millisOf(span.duration),
|
|
608
|
+
startMillis: millisOf$1(span.startTime),
|
|
609
|
+
durationMillis: millisOf$1(span.duration),
|
|
371
610
|
attributes,
|
|
372
611
|
events: eventsOf(span),
|
|
373
612
|
links: linksOf(span)
|
|
@@ -397,7 +636,7 @@ var observation_window_resource_exports = /* @__PURE__ */ __exportAll({
|
|
|
397
636
|
});
|
|
398
637
|
const TypeId$1 = Symbol.for("~systemfsoftware/trace-spec/ObservationWindowResource");
|
|
399
638
|
const scoped = (spec) => Effect.acquireRelease(Effect.sync(() => make$3(spec)), shutdown);
|
|
400
|
-
const layer = (spec) => OtelTracer.layer.pipe(Layer.provideMerge(OtelResource.layer({ serviceName: spec.serviceName })), Layer.provideMerge(Layer.effectContext(Effect.map(scoped(spec), context))));
|
|
639
|
+
const layer$1 = (spec) => OtelTracer.layer.pipe(Layer.provideMerge(OtelResource.layer({ serviceName: spec.serviceName })), Layer.provideMerge(Layer.effectContext(Effect.map(scoped(spec), context))));
|
|
401
640
|
const makeProto = (spec) => ({
|
|
402
641
|
[TypeId$1]: TypeId$1,
|
|
403
642
|
spec,
|
|
@@ -405,7 +644,7 @@ const makeProto = (spec) => ({
|
|
|
405
644
|
return scoped(spec);
|
|
406
645
|
},
|
|
407
646
|
get layer() {
|
|
408
|
-
return layer(spec);
|
|
647
|
+
return layer$1(spec);
|
|
409
648
|
},
|
|
410
649
|
...Prototype
|
|
411
650
|
});
|
|
@@ -444,7 +683,7 @@ const inspectedOf = (nodes) => nodes.map((node) => node.spanId);
|
|
|
444
683
|
const matchedAll = (items, predicate) => items.length > 0 && items.every(predicate);
|
|
445
684
|
const isBreak$1 = (verdict) => Schema.is(Break)(verdict);
|
|
446
685
|
const isHold$1 = (verdict) => Schema.is(Hold)(verdict);
|
|
447
|
-
const verdictOf = (options) => options.holds ? Hold.make({
|
|
686
|
+
const verdictOf$1 = (options) => options.holds ? Hold.make({
|
|
448
687
|
conjunct: options.id,
|
|
449
688
|
inspected: options.inspected
|
|
450
689
|
}) : Break.make({
|
|
@@ -458,7 +697,7 @@ const declared = (declaration) => relation({
|
|
|
458
697
|
softenable: declaration.softenable,
|
|
459
698
|
evaluate: (graph) => {
|
|
460
699
|
const inspected = declaration.spans.flatMap((span) => inspectedOf(byId(graph, span)));
|
|
461
|
-
return verdictOf({
|
|
700
|
+
return verdictOf$1({
|
|
462
701
|
id: declaration.id,
|
|
463
702
|
inspected,
|
|
464
703
|
holds: declaration.holds(graph),
|
|
@@ -497,65 +736,73 @@ const reachedUnder = (graph, parent, childSpan, reach) => {
|
|
|
497
736
|
const reached = reach(graph, matchedNodes(graph, parent));
|
|
498
737
|
return matchedNodes(graph, childSpan).some((node) => reached.has(node.spanId));
|
|
499
738
|
};
|
|
500
|
-
const
|
|
739
|
+
const childImpl = (parent, childSpan) => declared({
|
|
501
740
|
id: `child(${parent.id},${childSpan.id})`,
|
|
502
741
|
spans: [parent, childSpan],
|
|
503
742
|
softenable: true,
|
|
504
743
|
detail: `no ${childSpan.id} span is a direct child of a ${parent.id} span`,
|
|
505
744
|
holds: (graph) => reachedUnder(graph, parent, childSpan, childIdsOf)
|
|
506
745
|
});
|
|
507
|
-
const
|
|
746
|
+
const child = dual(2, childImpl);
|
|
747
|
+
const descendantImpl = (parent, childSpan) => declared({
|
|
508
748
|
id: `descendant(${parent.id},${childSpan.id})`,
|
|
509
749
|
spans: [parent, childSpan],
|
|
510
750
|
softenable: true,
|
|
511
751
|
detail: `no ${childSpan.id} span descends from a ${parent.id} span`,
|
|
512
752
|
holds: (graph) => reachedUnder(graph, parent, childSpan, descendantIdsOf)
|
|
513
753
|
});
|
|
514
|
-
const
|
|
754
|
+
const descendant = dual(2, descendantImpl);
|
|
755
|
+
const orderImpl = (before, after) => declared({
|
|
515
756
|
id: `order(${before.id},${after.id})`,
|
|
516
757
|
spans: [before, after],
|
|
517
758
|
softenable: true,
|
|
518
759
|
detail: `a ${after.id} span starts before every ${before.id} span`,
|
|
519
760
|
holds: (graph) => matchedAll(matchedNodes(graph, after), (node) => startsAfterSome(matchedNodes(graph, before), node))
|
|
520
761
|
});
|
|
521
|
-
const
|
|
762
|
+
const order = dual(2, orderImpl);
|
|
763
|
+
const statusImpl = (spec, expected) => declared({
|
|
522
764
|
id: `status(${spec.id},${expected})`,
|
|
523
765
|
spans: [spec],
|
|
524
766
|
softenable: true,
|
|
525
767
|
detail: `a ${spec.id} span is not ${expected}`,
|
|
526
768
|
holds: holdsEveryOf(spec, (node) => node.status === expected)
|
|
527
769
|
});
|
|
528
|
-
const
|
|
770
|
+
const status = dual(2, statusImpl);
|
|
771
|
+
const errorTypeImpl = (spec, expected) => declared({
|
|
529
772
|
id: `errorType(${spec.id},${expected})`,
|
|
530
773
|
spans: [spec],
|
|
531
774
|
softenable: true,
|
|
532
775
|
detail: `a ${spec.id} span carries a different error.type`,
|
|
533
776
|
holds: holdsEveryOf(spec, (node) => node.errorType === expected)
|
|
534
777
|
});
|
|
778
|
+
const errorType = dual(2, errorTypeImpl);
|
|
535
779
|
const partialMatches = (attributes, partial) => Object.entries(partial).filter(([, value]) => value !== void 0).every(([key, value]) => Equal.equals(attributes[key], value));
|
|
536
|
-
const
|
|
780
|
+
const attrsImpl = (spec, partial) => declared({
|
|
537
781
|
id: `attrs(${spec.id})`,
|
|
538
782
|
spans: [spec],
|
|
539
783
|
softenable: true,
|
|
540
784
|
detail: `a ${spec.id} span carries a different declared attribute`,
|
|
541
785
|
holds: holdsEveryOf(spec, (node) => partialMatches(node.attrs, partial))
|
|
542
786
|
});
|
|
543
|
-
const
|
|
787
|
+
const attrs = dual(2, attrsImpl);
|
|
788
|
+
const durationLessThanImpl = (spec, millis) => declared({
|
|
544
789
|
id: `durationLessThan(${spec.id},${millis})`,
|
|
545
790
|
spans: [spec],
|
|
546
791
|
softenable: true,
|
|
547
792
|
detail: `a ${spec.id} span exceeded ${millis}ms`,
|
|
548
793
|
holds: holdsEveryOf(spec, (node) => node.durationMillis < millis)
|
|
549
794
|
});
|
|
795
|
+
const durationLessThan = dual(2, durationLessThanImpl);
|
|
550
796
|
const carriesEvent = (name) => (node) => node.events.some((event) => event.name === name);
|
|
551
|
-
const
|
|
797
|
+
const eventImpl = (spec, name) => declared({
|
|
552
798
|
id: `event(${spec.id},${name})`,
|
|
553
799
|
spans: [spec],
|
|
554
800
|
softenable: true,
|
|
555
801
|
detail: `a ${spec.id} span carries no ${name} event`,
|
|
556
802
|
holds: holdsEveryOf(spec, carriesEvent(name))
|
|
557
803
|
});
|
|
558
|
-
const
|
|
804
|
+
const event = dual(2, eventImpl);
|
|
805
|
+
const forallImpl = (spec, predicate, detail) => {
|
|
559
806
|
const id = `forall(${spec.id})`;
|
|
560
807
|
return relation({
|
|
561
808
|
id,
|
|
@@ -563,7 +810,7 @@ const forall = (spec, predicate, detail) => {
|
|
|
563
810
|
softenable: true,
|
|
564
811
|
evaluate: (graph) => {
|
|
565
812
|
const nodes = matchedNodes(graph, spec);
|
|
566
|
-
return verdictOf({
|
|
813
|
+
return verdictOf$1({
|
|
567
814
|
id,
|
|
568
815
|
inspected: inspectedOf(nodes),
|
|
569
816
|
holds: matchedAll(nodes, predicate),
|
|
@@ -572,6 +819,7 @@ const forall = (spec, predicate, detail) => {
|
|
|
572
819
|
}
|
|
573
820
|
});
|
|
574
821
|
};
|
|
822
|
+
const forall = dual(3, forallImpl);
|
|
575
823
|
const soft = (self) => self.softenable ? relation({
|
|
576
824
|
id: self.id,
|
|
577
825
|
soft: true,
|
|
@@ -653,7 +901,7 @@ const placementOf = (edge) => {
|
|
|
653
901
|
evaluate: (graph) => {
|
|
654
902
|
const placed = matchedNodes(graph, edge.child);
|
|
655
903
|
const reached = REACH_BY_RELATION[edge.relation](graph, matchedNodes(graph, edge.parent));
|
|
656
|
-
return verdictOf({
|
|
904
|
+
return verdictOf$1({
|
|
657
905
|
id,
|
|
658
906
|
inspected: inspectedOf(placed),
|
|
659
907
|
holds: placed.every((node) => reached.has(node.spanId)),
|
|
@@ -667,12 +915,127 @@ const forbidsAt = (entry, path) => Option.match(entry.unless, {
|
|
|
667
915
|
onSome: (tag) => tag !== path
|
|
668
916
|
});
|
|
669
917
|
const forbiddenOn = (path) => (entry) => forbidsAt(entry, path) ? [absent(entry.span)] : [];
|
|
670
|
-
const
|
|
918
|
+
const fromTaxonomyImpl = (taxonomy, options) => combined({
|
|
671
919
|
id: `fromTaxonomy(${taxonomy.id},${options.path})`,
|
|
672
920
|
relations: [...taxonomy.edges.map(placementOf), ...taxonomy.forbidden.flatMap(forbiddenOn(options.path))],
|
|
673
921
|
soft: false,
|
|
674
922
|
softenable: false
|
|
675
923
|
});
|
|
924
|
+
const fromTaxonomy = dual(2, fromTaxonomyImpl);
|
|
925
|
+
//#endregion
|
|
926
|
+
//#region src/RemoteObservation.ts
|
|
927
|
+
var RemoteObservation_exports = /* @__PURE__ */ __exportAll({ layer: () => layer });
|
|
928
|
+
const NEVER_READ = {
|
|
929
|
+
spans: [],
|
|
930
|
+
addedAtMillis: 0
|
|
931
|
+
};
|
|
932
|
+
const idOf = (span) => span.spanId;
|
|
933
|
+
const sameId = (left, right) => left.spanId === right.spanId;
|
|
934
|
+
const idsOf = (spans) => new Set(spans.map(idOf));
|
|
935
|
+
const firstOfEachId = (read) => Array$1.dedupeWith(read, sameId);
|
|
936
|
+
const freshOf = (settlement, read) => {
|
|
937
|
+
const known = idsOf(settlement.spans);
|
|
938
|
+
return firstOfEachId(read).filter((span) => !known.has(span.spanId));
|
|
939
|
+
};
|
|
940
|
+
const absorbed = (settlement, read, elapsedMillis) => {
|
|
941
|
+
const fresh = freshOf(settlement, read);
|
|
942
|
+
return fresh.length === 0 ? settlement : {
|
|
943
|
+
spans: [...settlement.spans, ...fresh],
|
|
944
|
+
addedAtMillis: elapsedMillis
|
|
945
|
+
};
|
|
946
|
+
};
|
|
947
|
+
const quietForLongEnough = (settlement, elapsedMillis, windows) => settlement.spans.length > 0 && elapsedMillis - settlement.addedAtMillis >= windows.settleMillis;
|
|
948
|
+
const pastDeadline = (elapsedMillis, windows) => elapsedMillis >= windows.timeoutMillis;
|
|
949
|
+
const settledVerdict = (spans) => Option.some(Result.succeed(spans));
|
|
950
|
+
const refusedVerdict = (refusal) => Option.some(Result.fail(refusal));
|
|
951
|
+
const verdictOf = (settlement, elapsedMillis, windows) => Match.value({
|
|
952
|
+
quiet: quietForLongEnough(settlement, elapsedMillis, windows),
|
|
953
|
+
vacant: settlement.spans.length === 0,
|
|
954
|
+
late: pastDeadline(elapsedMillis, windows)
|
|
955
|
+
}).pipe(Match.when({ quiet: true }, () => settledVerdict(settlement.spans)), Match.when({
|
|
956
|
+
vacant: true,
|
|
957
|
+
late: true
|
|
958
|
+
}, () => refusedVerdict("absent")), Match.when({ late: true }, () => refusedVerdict("unfinished")), Match.orElse(() => Option.none()));
|
|
959
|
+
const settleStep = (settlement, read, elapsedMillis, windows) => {
|
|
960
|
+
const advanced = absorbed(settlement, read, elapsedMillis);
|
|
961
|
+
return {
|
|
962
|
+
settlement: advanced,
|
|
963
|
+
verdict: verdictOf(advanced, elapsedMillis, windows)
|
|
964
|
+
};
|
|
965
|
+
};
|
|
966
|
+
const lateRefusal = (settlement) => settlement.spans.length === 0 ? "absent" : "unfinished";
|
|
967
|
+
const deadlineVerdict = (settlement, elapsedMillis, windows) => pastDeadline(elapsedMillis, windows) ? refusedVerdict(lateRefusal(settlement)) : Option.none();
|
|
968
|
+
const millisOf = (input) => Duration.toMillis(input);
|
|
969
|
+
const windowsOf = (options) => ({
|
|
970
|
+
settleMillis: millisOf(options.settle),
|
|
971
|
+
timeoutMillis: millisOf(options.timeout)
|
|
972
|
+
});
|
|
973
|
+
const nonPositive = (options) => [
|
|
974
|
+
options.interval,
|
|
975
|
+
options.settle,
|
|
976
|
+
options.timeout
|
|
977
|
+
].some((input) => millisOf(input) <= 0);
|
|
978
|
+
const requirePositive = (options) => nonPositive(options) ? Effect.die(/* @__PURE__ */ new Error(`remote observation durations must be positive milliseconds: interval=${millisOf(options.interval)}, settle=${millisOf(options.settle)}, timeout=${millisOf(options.timeout)}`)) : Effect.void;
|
|
979
|
+
const absence = (traceId, windows) => new EmptyObservationError({
|
|
980
|
+
traceId,
|
|
981
|
+
detail: `the remote trace store served no span for trace ${traceId} before the ${windows.timeoutMillis}ms timeout`
|
|
982
|
+
});
|
|
983
|
+
const incompleteness = (traceId, spans, windows) => new IncompleteObservationError({
|
|
984
|
+
traceId,
|
|
985
|
+
spanCount: spans.length,
|
|
986
|
+
detail: `the remote trace store served ${spans.length} span(s) for trace ${traceId}, which never stayed unchanged for ${windows.settleMillis}ms before the ${windows.timeoutMillis}ms timeout`
|
|
987
|
+
});
|
|
988
|
+
const answer = (verdict, settlement, traceId, windows) => Result.match(verdict, {
|
|
989
|
+
onSuccess: (spans) => Effect.succeed(spans),
|
|
990
|
+
onFailure: (refusal) => refusal === "absent" ? Effect.fail(absence(traceId, windows)) : Effect.fail(incompleteness(traceId, settlement.spans, windows))
|
|
991
|
+
});
|
|
992
|
+
const watching = (windows) => Effect.gen(function* () {
|
|
993
|
+
const startedAtMillis = yield* Clock.currentTimeMillis;
|
|
994
|
+
return {
|
|
995
|
+
settlement: yield* Ref.make(NEVER_READ),
|
|
996
|
+
windows,
|
|
997
|
+
startedAtMillis
|
|
998
|
+
};
|
|
999
|
+
});
|
|
1000
|
+
const advanceAt = (watch, read) => Effect.gen(function* () {
|
|
1001
|
+
const elapsedMillis = (yield* Clock.currentTimeMillis) - watch.startedAtMillis;
|
|
1002
|
+
return settleStep(yield* Ref.get(watch.settlement), read, elapsedMillis, watch.windows);
|
|
1003
|
+
});
|
|
1004
|
+
const elapsedFor = (watch) => Effect.map(Clock.currentTimeMillis, (now) => now - watch.startedAtMillis);
|
|
1005
|
+
const poll = (source, traceId, options, watch) => Effect.gen(function* () {
|
|
1006
|
+
const settlement = yield* Ref.get(watch.settlement);
|
|
1007
|
+
const elapsedMillis = yield* elapsedFor(watch);
|
|
1008
|
+
return yield* Option.match(deadlineVerdict(settlement, elapsedMillis, watch.windows), {
|
|
1009
|
+
onNone: () => readOnce(source, traceId, options, watch, watch.windows.timeoutMillis - elapsedMillis),
|
|
1010
|
+
onSome: (reached) => answer(reached, settlement, traceId, watch.windows)
|
|
1011
|
+
});
|
|
1012
|
+
});
|
|
1013
|
+
const overdue = (traceId, watch) => Effect.flatMap(Ref.get(watch.settlement), (settlement) => answer(Result.fail(lateRefusal(settlement)), settlement, traceId, watch.windows));
|
|
1014
|
+
const readOnce = (source, traceId, options, watch, remainingMillis) => Effect.flatMap(Effect.timeoutOption(source(traceId), remainingMillis), (answered) => Option.match(answered, {
|
|
1015
|
+
onNone: () => overdue(traceId, watch),
|
|
1016
|
+
onSome: (read) => judgeRead(source, traceId, options, watch, read)
|
|
1017
|
+
}));
|
|
1018
|
+
const judgeRead = (source, traceId, options, watch, read) => Effect.flatMap(advanceAt(watch, read), (advance) => Option.match(advance.verdict, {
|
|
1019
|
+
onNone: () => keepPolling(source, traceId, options, watch, advance),
|
|
1020
|
+
onSome: (verdict) => answer(verdict, advance.settlement, traceId, watch.windows)
|
|
1021
|
+
}));
|
|
1022
|
+
const keepPolling = (source, traceId, options, watch, advance) => Effect.gen(function* () {
|
|
1023
|
+
yield* Ref.set(watch.settlement, advance.settlement);
|
|
1024
|
+
yield* Effect.sleep(options.interval);
|
|
1025
|
+
return yield* Effect.suspend(() => poll(source, traceId, options, watch));
|
|
1026
|
+
});
|
|
1027
|
+
const collector = (source, options, context) => ({ collect: (traceId) => Effect.flatMap(watching(windowsOf(options)), (watch) => poll(source, traceId, options, watch)).pipe(Effect.provideContext(context)) });
|
|
1028
|
+
/**
|
|
1029
|
+
* The `Observation` service over one trace source. `collect` reads the source at
|
|
1030
|
+
* `options.interval`, keeps every span it has seen keyed by span id, and answers
|
|
1031
|
+
* when that union has stayed unchanged for `options.settle`; a trace the store
|
|
1032
|
+
* never served is `EmptyObservationError`, one that never settled is
|
|
1033
|
+
* `IncompleteObservationError`, and the source's own failure ends the poll
|
|
1034
|
+
* immediately, unchanged. Provides `Observation` alone and requires exactly what
|
|
1035
|
+
* the source requires (`Scope.Scope` is supplied by the layer machinery, as
|
|
1036
|
+
* every Effect layer constructor scopes it away).
|
|
1037
|
+
*/
|
|
1038
|
+
const layer = dual(2, (source, options) => Layer.effectContext(Effect.map(Effect.andThen(requirePositive(options), Effect.context()), (context) => Context.make(Observation, collector(source, options, context)))));
|
|
676
1039
|
//#endregion
|
|
677
1040
|
//#region src/Stimulus.ts
|
|
678
1041
|
var Stimulus_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -712,6 +1075,15 @@ const make$1 = (options) => {
|
|
|
712
1075
|
return stimulus;
|
|
713
1076
|
};
|
|
714
1077
|
//#endregion
|
|
1078
|
+
//#region src/HarnessFailure.schema.ts
|
|
1079
|
+
const HarnessFailure = Schema.Union([
|
|
1080
|
+
ContractDecodeError,
|
|
1081
|
+
EmptyObservationError,
|
|
1082
|
+
IncompleteObservationError,
|
|
1083
|
+
TransportObservationError,
|
|
1084
|
+
TraceDisparityError
|
|
1085
|
+
]);
|
|
1086
|
+
//#endregion
|
|
715
1087
|
//#region src/Prop.ts
|
|
716
1088
|
const isHold = Schema.is(Hold);
|
|
717
1089
|
const isBreak = Schema.is(Break);
|
|
@@ -724,19 +1096,11 @@ const announce = (context, verdict, dumpPath) => Option.match(dumpMessageOf(verd
|
|
|
724
1096
|
onNone: () => Effect.void,
|
|
725
1097
|
onSome: (message) => annotate(context, message)
|
|
726
1098
|
});
|
|
727
|
-
|
|
728
|
-
* The generated-case predicate for one case: a factory that runs the contract's cell with
|
|
729
|
-
* the dump named after the case — so every failing draw overwrites one file and the last
|
|
730
|
-
* failing draw is the shrunk counterexample the runner reports — and answers the verdict as
|
|
731
|
-
* the boolean: `true` while the relation held, `false` on a break, falsifying the property.
|
|
732
|
-
* Only infrastructure refusals — a failing behaviour, an undecodable span, an empty
|
|
733
|
-
* observation — stay on the error channel. The scenario layer is built fresh per draw, so
|
|
734
|
-
* each draw owns its observation window.
|
|
735
|
-
*/
|
|
736
|
-
function predicate(title, contract, scenario) {
|
|
1099
|
+
const predicateImpl = (title, contract, scenario) => {
|
|
737
1100
|
const checked = (input, context) => judge(contract, input, { dumpName: title }).pipe(Effect.tap((judgment) => announce(context, judgment.verdict, judgment.dumpPath)), Effect.map((judgment) => isHold(judgment.verdict)), Effect.provide(Layer.fresh(scenario)));
|
|
738
1101
|
return (input, context) => checked(input, context);
|
|
739
|
-
}
|
|
1102
|
+
};
|
|
1103
|
+
const predicate = dual(3, predicateImpl);
|
|
740
1104
|
//#endregion
|
|
741
1105
|
//#region src/StimulusFailure.schema.ts
|
|
742
1106
|
var StimulusFailure = class extends Schema.TaggedError()("StimulusFailure", {
|
|
@@ -771,12 +1135,8 @@ var Suite_exports = /* @__PURE__ */ __exportAll({
|
|
|
771
1135
|
withScenarioLayer: () => withScenarioLayer
|
|
772
1136
|
});
|
|
773
1137
|
const TypeId = Symbol.for("@systemfsoftware/trace-spec/Suite");
|
|
774
|
-
const
|
|
775
|
-
|
|
776
|
-
EmptyObservationError,
|
|
777
|
-
TraceDisparityError
|
|
778
|
-
]));
|
|
779
|
-
const caseFailureOf = (stimulus) => (failure) => isCheckFailure(failure) ? failure : new StimulusFailure({
|
|
1138
|
+
const isHarnessFailure = Schema.is(HarnessFailure);
|
|
1139
|
+
const caseFailureOf = (stimulus) => (failure) => isHarnessFailure(failure) ? failure : new StimulusFailure({
|
|
780
1140
|
stimulus,
|
|
781
1141
|
detail: Cause.pretty(Cause.fail(failure))
|
|
782
1142
|
});
|
|
@@ -821,4 +1181,4 @@ const make = (bindings) => (name) => ({
|
|
|
821
1181
|
...Prototype
|
|
822
1182
|
});
|
|
823
1183
|
//#endregion
|
|
824
|
-
export { Contract_exports as Contract, Graph_exports as Graph, Observation_service_exports as Observation, observation_window_resource_exports as ObservationWindow, Rel_exports as Rel, Stimulus_exports as Stimulus, Suite_exports as Suite };
|
|
1184
|
+
export { Contract_exports as Contract, Graph_exports as Graph, Observation_service_exports as Observation, observation_window_resource_exports as ObservationWindow, Rel_exports as Rel, RemoteObservation_exports as RemoteObservation, Stimulus_exports as Stimulus, Suite_exports as Suite, tempo_trace_store_exports as TempoTraceStore };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@systemfsoftware/trace-spec",
|
|
3
3
|
"license": "Apache-2.0",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"author": "Ryan Lee <drdgvhbh@gmail.com>",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -38,25 +38,25 @@
|
|
|
38
38
|
"@opentelemetry/resources": "^2",
|
|
39
39
|
"@opentelemetry/sdk-trace-base": "^2",
|
|
40
40
|
"@opentelemetry/semantic-conventions": "^1.33",
|
|
41
|
-
"@systemfsoftware/
|
|
42
|
-
"@systemfsoftware/
|
|
41
|
+
"@systemfsoftware/effect-spec-runtime": "^0.1.0",
|
|
42
|
+
"@systemfsoftware/trace-taxonomy": "^0.0.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@effect/platform-node": "4.0.0-rc.116",
|
|
46
46
|
"@effect/vitest": "4.0.0-rc.116",
|
|
47
47
|
"@systemfsoftware/arethetypeswrong-cli": "^4.2.0",
|
|
48
|
+
"@systemfsoftware/effect-gherkin-spec": "^4.3.0",
|
|
49
|
+
"@systemfsoftware/oxlint-config-recommended": "^2.0.0",
|
|
50
|
+
"@systemfsoftware/tsconfig": "^2.0.0",
|
|
51
|
+
"@systemfsoftware/tsdown-config": "^0.1.0",
|
|
52
|
+
"@systemfsoftware/vitest-config": "^0.1.0",
|
|
48
53
|
"@types/node": "^26",
|
|
49
54
|
"effect": "4.0.0-rc.116",
|
|
50
55
|
"oxlint": "~1.82.0",
|
|
51
56
|
"rimraf": "^6.1.3",
|
|
52
57
|
"tsdown": "^0.23.0",
|
|
53
58
|
"tstyche": "^7.2.5",
|
|
54
|
-
"vitest": "^5"
|
|
55
|
-
"@systemfsoftware/effect-gherkin-spec": "^4.2.3",
|
|
56
|
-
"@systemfsoftware/oxlint-config-recommended": "^1.1.1",
|
|
57
|
-
"@systemfsoftware/tsconfig": "^1.3.6",
|
|
58
|
-
"@systemfsoftware/tsdown-config": "^0.1.0",
|
|
59
|
-
"@systemfsoftware/vitest-config": "^0.1.0"
|
|
59
|
+
"vitest": "^5"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
62
|
"@effect/vitest": "4.0.0-rc.116",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"scripts": {
|
|
70
70
|
"clean": "rimraf dist",
|
|
71
71
|
"build": "tsdown -l warn",
|
|
72
|
-
"typecheck": "tsc
|
|
72
|
+
"typecheck": "tsc -b",
|
|
73
73
|
"test": "vitest run",
|
|
74
74
|
"test:types": "TSTYCHE_TYPESCRIPT_MODULE=tstyche-typescript tstyche",
|
|
75
75
|
"lint": "f=${OXLINT_FORMAT:-${AGENT:+agent}}; oxlint . --format=${f:-default}",
|