@systemfsoftware/trace-spec 0.1.0 → 0.2.1

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 CHANGED
@@ -1,5 +1,22 @@
1
1
  # @systemfsoftware/trace-spec
2
2
 
3
+ ## 0.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Update peer and runtime dependency on `effect` and companion packages to `4.0.0-rc.117`.
8
+
9
+ - Updated dependencies:
10
+ - @systemfsoftware/trace-taxonomy@0.0.1
11
+
12
+ ## 0.2.0
13
+
14
+ ### Minor Changes
15
+
16
+ - 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.
17
+
18
+ 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.
19
+
3
20
  ## 0.1.0
4
21
 
5
22
  ### 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 seven namespaces — `Contract`, `Graph`, `Observation`, `ObservationWindow`, `Rel`, `Stimulus`, `Suite`. Errors and verdicts belong to the capability that raises them: `Contract.ContractDecodeError`, `Contract.TraceDisparityError`, `Observation.EmptyObservationError`, `Suite.StimulusFailure`, and `Rel.Hold`/`Rel.Break`/`Rel.Verdict`.
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`, `Observation.EmptyObservationError`) stay on the error channel.
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 | 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 |
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 three outcomes above, or `Suite.StimulusFailure` when the behaviour itself failed before its trace could be judged. Cases run on the live clock.
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;
@@ -127,11 +136,20 @@ declare const decode: {
127
136
  (spans: ReadonlyArray<SpanRecord>, taxonomy: Taxonomy.Taxonomy): (traceId: string) => Result.Result<TraceGraph, ContractDecodeError>;
128
137
  (traceId: string, spans: ReadonlyArray<SpanRecord>, taxonomy: Taxonomy.Taxonomy): Result.Result<TraceGraph, ContractDecodeError>;
129
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 {}
130
147
  declare namespace Observation_service_d_exports {
131
- export { Collector, EmptyObservationError, Observation };
148
+ export { Collector, EmptyObservationError, IncompleteObservationError, Observation, ObservationFailure, TransportObservationError };
132
149
  }
150
+ type ObservationFailure = EmptyObservationError | IncompleteObservationError | TransportObservationError;
133
151
  interface Collector {
134
- readonly collect: (traceId: string) => Effect.Effect<ReadonlyArray<SpanRecord>, EmptyObservationError>;
152
+ readonly collect: (traceId: string) => Effect.Effect<ReadonlyArray<SpanRecord>, ObservationFailure>;
135
153
  }
136
154
  declare const Observation_base: Context.ServiceClass<Observation, "@systemfsoftware/trace-spec/Observation", Collector>;
137
155
  declare class Observation extends Observation_base {}
@@ -252,7 +270,7 @@ declare const TraceDisparityError_base: Schema.Class<TraceDisparityError, Schema
252
270
  }>, import("effect/Cause").YieldableError>;
253
271
  declare class TraceDisparityError extends TraceDisparityError_base {}
254
272
  declare namespace Contract_d_exports {
255
- 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 };
256
274
  }
257
275
  declare const TypeId$3: unique symbol;
258
276
  type TypeId$3 = typeof TypeId$3;
@@ -290,7 +308,7 @@ interface Judgment<Input, Output> {
290
308
  interface CheckOptions {
291
309
  readonly dumpName?: string | undefined;
292
310
  }
293
- type JudgeFailure<E> = E | ContractDecodeError | EmptyObservationError;
311
+ type JudgeFailure<E> = E | ContractDecodeError | ObservationFailure;
294
312
  type CheckFailure<E> = JudgeFailure<E> | TraceDisparityError;
295
313
  type Services<R> = R | Observation | FileSystem.FileSystem;
296
314
  declare const judge: {
@@ -301,6 +319,46 @@ declare const check: {
301
319
  <Input>(input: Input, options?: CheckOptions): <Output, E, R>(self: Contract<Input, Output, E, R>) => Effect.Effect<Judgment<Input, Output>, CheckFailure<E>, Services<R>>;
302
320
  <Input, Output, E, R>(self: Contract<Input, Output, E, R>, input: Input, options?: CheckOptions): Effect.Effect<Judgment<Input, Output>, CheckFailure<E>, Services<R>>;
303
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>;
304
362
  //#endregion
305
363
  //#region src/ObservationWindowSpec.schema.d.ts
306
364
  declare const ObservationWindowSpec_base: Schema.Class<ObservationWindowSpec, Schema.Struct<{
@@ -341,6 +399,10 @@ interface ObservationWindowResource extends Pipeable {
341
399
  }
342
400
  declare const make$1: (serviceName: string) => ObservationWindowResource;
343
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
344
406
  //#region src/StimulusFailure.schema.d.ts
345
407
  declare const StimulusFailure_base: Schema.Class<StimulusFailure, Schema.TaggedStruct<"StimulusFailure", {
346
408
  readonly stimulus: Schema.String;
@@ -352,7 +414,7 @@ declare namespace Suite_d_exports {
352
414
  }
353
415
  declare const TypeId: unique symbol;
354
416
  type TypeId = typeof TypeId;
355
- type CaseFailure = ContractDecodeError | EmptyObservationError | TraceDisparityError | StimulusFailure;
417
+ type CaseFailure = HarnessFailure | StimulusFailure;
356
418
  type Harness = Observation | FileSystem.FileSystem;
357
419
  type ArbitraryInput<Input> = Schema.Schema<Input> | Arbitrary.Arbitrary<Input>;
358
420
  interface CaseRegistrar<Provided> {
@@ -385,4 +447,4 @@ declare const withScenarioLayer: {
385
447
  };
386
448
  declare const make: (bindings: Suite.Bindings) => (name: string) => Declared;
387
449
  //#endregion
388
- 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";
@@ -221,10 +224,26 @@ const decodeImpl = (traceId, spans, taxonomy) => {
221
224
  */
222
225
  const decode = dual(3, decodeImpl);
223
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
+ }) {};
240
+ //#endregion
224
241
  //#region src/Observation.service.ts
225
242
  var Observation_service_exports = /* @__PURE__ */ __exportAll({
226
243
  EmptyObservationError: () => EmptyObservationError,
227
- Observation: () => Observation
244
+ IncompleteObservationError: () => IncompleteObservationError,
245
+ Observation: () => Observation,
246
+ TransportObservationError: () => TransportObservationError
228
247
  });
229
248
  var Observation = class extends Context.Service()("@systemfsoftware/trace-spec/Observation") {};
230
249
  //#endregion
@@ -240,7 +259,9 @@ var TraceDisparityError = class extends Schema.TaggedError()("TraceDisparityErro
240
259
  var Contract_exports = /* @__PURE__ */ __exportAll({
241
260
  ContractDecodeError: () => ContractDecodeError,
242
261
  EmptyObservationError: () => EmptyObservationError,
262
+ IncompleteObservationError: () => IncompleteObservationError,
243
263
  TraceDisparityError: () => TraceDisparityError,
264
+ TransportObservationError: () => TransportObservationError,
244
265
  TypeId: () => TypeId$3,
245
266
  check: () => check,
246
267
  holds: () => holds,
@@ -308,6 +329,219 @@ const refuseBreak = (relationId, judgment) => Match.value(judgment.verdict).pipe
308
329
  const checkDual = (self, input, options) => Effect.flatMap(judgeDual(self, input, options), (judgment) => refuseBreak(self.relation.id, judgment));
309
330
  const check = dual((args) => isContract(args[0]), checkDual);
310
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
311
545
  //#region src/observation-window.handle.ts
312
546
  const TypeId$2 = Symbol.for("~systemfsoftware/trace-spec/ObservationWindow");
313
547
  const ExporterId = Symbol.for("~systemfsoftware/trace-spec/ObservationWindow/exporter");
@@ -361,7 +595,7 @@ const linksOf = (span) => span.links.map((link) => ({
361
595
  traceId: link.context.traceId,
362
596
  spanId: link.context.spanId
363
597
  }));
364
- const millisOf = (duration) => duration[0] * 1e3 + duration[1] / 1e6;
598
+ const millisOf$1 = (duration) => duration[0] * 1e3 + duration[1] / 1e6;
365
599
  const spanRecordOf = (span) => {
366
600
  const attributes = attributeRecordOf(span.attributes);
367
601
  return {
@@ -371,8 +605,8 @@ const spanRecordOf = (span) => {
371
605
  name: span.name,
372
606
  status: statusOf(span.status.code),
373
607
  errorType: errorTypeOf(attributes),
374
- startMillis: millisOf(span.startTime),
375
- durationMillis: millisOf(span.duration),
608
+ startMillis: millisOf$1(span.startTime),
609
+ durationMillis: millisOf$1(span.duration),
376
610
  attributes,
377
611
  events: eventsOf(span),
378
612
  links: linksOf(span)
@@ -402,7 +636,7 @@ var observation_window_resource_exports = /* @__PURE__ */ __exportAll({
402
636
  });
403
637
  const TypeId$1 = Symbol.for("~systemfsoftware/trace-spec/ObservationWindowResource");
404
638
  const scoped = (spec) => Effect.acquireRelease(Effect.sync(() => make$3(spec)), shutdown);
405
- 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))));
406
640
  const makeProto = (spec) => ({
407
641
  [TypeId$1]: TypeId$1,
408
642
  spec,
@@ -410,7 +644,7 @@ const makeProto = (spec) => ({
410
644
  return scoped(spec);
411
645
  },
412
646
  get layer() {
413
- return layer(spec);
647
+ return layer$1(spec);
414
648
  },
415
649
  ...Prototype
416
650
  });
@@ -449,7 +683,7 @@ const inspectedOf = (nodes) => nodes.map((node) => node.spanId);
449
683
  const matchedAll = (items, predicate) => items.length > 0 && items.every(predicate);
450
684
  const isBreak$1 = (verdict) => Schema.is(Break)(verdict);
451
685
  const isHold$1 = (verdict) => Schema.is(Hold)(verdict);
452
- const verdictOf = (options) => options.holds ? Hold.make({
686
+ const verdictOf$1 = (options) => options.holds ? Hold.make({
453
687
  conjunct: options.id,
454
688
  inspected: options.inspected
455
689
  }) : Break.make({
@@ -463,7 +697,7 @@ const declared = (declaration) => relation({
463
697
  softenable: declaration.softenable,
464
698
  evaluate: (graph) => {
465
699
  const inspected = declaration.spans.flatMap((span) => inspectedOf(byId(graph, span)));
466
- return verdictOf({
700
+ return verdictOf$1({
467
701
  id: declaration.id,
468
702
  inspected,
469
703
  holds: declaration.holds(graph),
@@ -576,7 +810,7 @@ const forallImpl = (spec, predicate, detail) => {
576
810
  softenable: true,
577
811
  evaluate: (graph) => {
578
812
  const nodes = matchedNodes(graph, spec);
579
- return verdictOf({
813
+ return verdictOf$1({
580
814
  id,
581
815
  inspected: inspectedOf(nodes),
582
816
  holds: matchedAll(nodes, predicate),
@@ -667,7 +901,7 @@ const placementOf = (edge) => {
667
901
  evaluate: (graph) => {
668
902
  const placed = matchedNodes(graph, edge.child);
669
903
  const reached = REACH_BY_RELATION[edge.relation](graph, matchedNodes(graph, edge.parent));
670
- return verdictOf({
904
+ return verdictOf$1({
671
905
  id,
672
906
  inspected: inspectedOf(placed),
673
907
  holds: placed.every((node) => reached.has(node.spanId)),
@@ -689,6 +923,120 @@ const fromTaxonomyImpl = (taxonomy, options) => combined({
689
923
  });
690
924
  const fromTaxonomy = dual(2, fromTaxonomyImpl);
691
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)))));
1039
+ //#endregion
692
1040
  //#region src/Stimulus.ts
693
1041
  var Stimulus_exports = /* @__PURE__ */ __exportAll({
694
1042
  make: () => make$1,
@@ -727,6 +1075,15 @@ const make$1 = (options) => {
727
1075
  return stimulus;
728
1076
  };
729
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
730
1087
  //#region src/Prop.ts
731
1088
  const isHold = Schema.is(Hold);
732
1089
  const isBreak = Schema.is(Break);
@@ -778,12 +1135,8 @@ var Suite_exports = /* @__PURE__ */ __exportAll({
778
1135
  withScenarioLayer: () => withScenarioLayer
779
1136
  });
780
1137
  const TypeId = Symbol.for("@systemfsoftware/trace-spec/Suite");
781
- const isCheckFailure = Schema.is(Schema.Union([
782
- ContractDecodeError,
783
- EmptyObservationError,
784
- TraceDisparityError
785
- ]));
786
- 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({
787
1140
  stimulus,
788
1141
  detail: Cause.pretty(Cause.fail(failure))
789
1142
  });
@@ -828,4 +1181,4 @@ const make = (bindings) => (name) => ({
828
1181
  ...Prototype
829
1182
  });
830
1183
  //#endregion
831
- 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.1.0",
4
+ "version": "0.2.1",
5
5
  "author": "Ryan Lee <drdgvhbh@gmail.com>",
6
6
  "repository": {
7
7
  "type": "git",
@@ -33,25 +33,25 @@
33
33
  "dist"
34
34
  ],
35
35
  "dependencies": {
36
- "@effect/opentelemetry": "4.0.0-rc.116",
36
+ "@effect/opentelemetry": "4.0.0-rc.117",
37
37
  "@opentelemetry/api": "^1.9.0",
38
38
  "@opentelemetry/resources": "^2",
39
39
  "@opentelemetry/sdk-trace-base": "^2",
40
40
  "@opentelemetry/semantic-conventions": "^1.33",
41
- "@systemfsoftware/effect-spec-runtime": "^0.1.0",
42
- "@systemfsoftware/trace-taxonomy": "^0.0.0"
41
+ "@systemfsoftware/effect-spec-runtime": "^0.1.1",
42
+ "@systemfsoftware/trace-taxonomy": "^0.0.1"
43
43
  },
44
44
  "devDependencies": {
45
- "@effect/platform-node": "4.0.0-rc.116",
46
- "@effect/vitest": "4.0.0-rc.116",
45
+ "@effect/platform-node": "4.0.0-rc.117",
46
+ "@effect/vitest": "4.0.0-rc.117",
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",
48
+ "@systemfsoftware/effect-gherkin-spec": "^4.3.1",
49
+ "@systemfsoftware/oxlint-config-recommended": "^2.0.2",
50
+ "@systemfsoftware/tsconfig": "^2.0.1",
51
51
  "@systemfsoftware/tsdown-config": "^0.1.0",
52
52
  "@systemfsoftware/vitest-config": "^0.1.0",
53
53
  "@types/node": "^26",
54
- "effect": "4.0.0-rc.116",
54
+ "effect": "4.0.0-rc.117",
55
55
  "oxlint": "~1.82.0",
56
56
  "rimraf": "^6.1.3",
57
57
  "tsdown": "^0.23.0",
@@ -59,8 +59,8 @@
59
59
  "vitest": "^5"
60
60
  },
61
61
  "peerDependencies": {
62
- "@effect/vitest": "4.0.0-rc.116",
63
- "effect": "4.0.0-rc.116",
62
+ "@effect/vitest": "4.0.0-rc.117",
63
+ "effect": "4.0.0-rc.117",
64
64
  "vitest": "^5"
65
65
  },
66
66
  "publishConfig": {