@systemfsoftware/trace-spec 1.0.1 → 1.2.4

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,123 +1,5 @@
1
1
  # @systemfsoftware/trace-spec
2
2
 
3
- ## 1.0.1
3
+ # @systemfsoftware/trace-spec@1.2.4
4
4
 
5
- ### Patch Changes
6
-
7
- - Updated dependencies:
8
- - @systemfsoftware/vitest@2.0.0
9
-
10
- ## 1.0.0
11
-
12
- ### Major Changes
13
-
14
- - Require `effect` `^4` (stable 4.0.0). Release candidates are no longer supported: imports follow 4.0.0's module layout (`effect/http`, `effect/rpc`, `effect/http-api`, `effect/cluster`, `effect/process`, `effect/socket`, `effect/persistence`, `effect/reactivity`, `effect/encoding`, `effect/Arbitrary`), which does not exist in any 4.0.0 RC.
15
-
16
- ### Patch Changes
17
-
18
- - Updated dependencies:
19
- - @systemfsoftware/effect-cell-types@12.0.0
20
- - @systemfsoftware/effect-spec-runtime@1.0.0
21
- - @systemfsoftware/trace-taxonomy@1.0.0
22
- - @systemfsoftware/vitest@1.0.0
23
-
24
- ## 0.5.2
25
-
26
- ### Patch Changes
27
-
28
- - Updated dependencies:
29
- - @systemfsoftware/vitest@0.3.0
30
-
31
- ## 0.5.1
32
-
33
- ### Patch Changes
34
-
35
- - Updated dependencies:
36
- - @systemfsoftware/effect-cell-types@11.0.0
37
-
38
- ## 0.5.0
39
-
40
- ### Minor Changes
41
-
42
- - `Rel.Hold`, `Rel.Break` and `ObservationWindow.ObservationWindowSpec` are schema structs instead of classes, with unchanged encoded shapes: build them with `.make(...)` instead of `new`. The `VerdictTypeId` type is removed. `IncompleteObservationError.spanCount` must be a non-negative integer, and span `startMillis` and `durationMillis` must be finite and non-negative.
43
-
44
- - `Contract.check(expect, input, options?)` takes the test's `expect` and ends in one check: a break fails it with the report as the message. `Contract.judge` returns the `{ verdict: 'Hold' | 'Break', report }` value, and `Contract.verdictCheck(contract, expect, judgment)` turns one into a check. `TraceDisparityError` is removed. `Suite` cases register as generator tests and judge each case once.
45
-
46
- - The lawful test runner is now a peer dependency under its own name, `@systemfsoftware/vitest`, instead of the `@effect/vitest` alias that pointed at it. Install `@systemfsoftware/vitest` and import `it`, `layer`, `expect`, and the rest from `@systemfsoftware/vitest`. `@systemfsoftware/effect-gherkin-spec` re-exports `it` and `layer` from `@systemfsoftware/vitest`.
47
-
48
- - A broken contract now fails the check with `BreakFailure`, whose message is the break report, pointing at the line that called the check.
49
-
50
- - Cases now run on the simulation kernel — the zero-preemption schedule, then the profile's seeded schedules — instead of always on the live clock, and each case draws its own crypto-random salt, so two cases never share a trace id.
51
-
52
- - `Suite.make({ it, layer })` → `Suite.make({ it })`.
53
- - A case that must stay live declares a reason on any stage: `.live('...')`, or the new `Suite.live` combinator (`self.pipe(Suite.live(reason))`).
54
- - `withLayer(shared)` rebuilds the shared layer freshly for every case rather than once per suite.
55
- - `Stimulus.traceContext` draws its ids through Effect's `Random`, so a seeded run replays the same trace ids.
56
-
57
- ### Patch Changes
58
-
59
- - Update peer and runtime dependency on `effect` and companion packages to `4.0.0-rc.117`.
60
-
61
- - Updated dependencies:
62
- - @systemfsoftware/trace-taxonomy@0.0.2
63
- - @systemfsoftware/vitest@0.2.0
64
-
65
- ## 0.4.0
66
-
67
- ### Minor Changes
68
-
69
- - `Contract.check(expect, input, options?)` takes the test's `expect` and ends in one check: a break fails it with the report as the message. `Contract.judge` returns the `{ verdict: 'Hold' | 'Break', report }` value, and `Contract.verdictCheck(contract, expect, judgment)` turns one into a check. `TraceDisparityError` is removed. `Suite` cases register as generator tests and judge each case once.
70
-
71
- ### Patch Changes
72
-
73
- - Updated dependencies:
74
- - @systemfsoftware/vitest@0.1.0
75
-
76
- ## 0.3.0
77
-
78
- ### Minor Changes
79
-
80
- - Requires `@systemfsoftware/vitest` installed under the `@effect/vitest` name (`"@effect/vitest": "npm:@systemfsoftware/vitest"`) in place of the upstream package. `Case.prop` checks the stimulus it is handed on every run and pins the run's input, so a stimulus that ignores its input fails; the trace dump path is annotated on the failing test.
81
-
82
- ### Patch Changes
83
-
84
- - The observation window is built on the `Blueprint` and `Handle` kinds, with unchanged behaviour. The blueprint's `TypeId` is now exported as a value.
85
-
86
- - Updated dependencies:
87
- - @systemfsoftware/effect-spec-runtime@0.2.0
88
-
89
- ## 0.2.1
90
-
91
- ### Patch Changes
92
-
93
- - Update peer and runtime dependency on `effect` and companion packages to `4.0.0-rc.117`.
94
-
95
- - Updated dependencies:
96
- - @systemfsoftware/trace-taxonomy@0.0.1
97
-
98
- ## 0.2.0
99
-
100
- ### Minor Changes
101
-
102
- - 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.
103
-
104
- 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.
105
-
106
- ## 0.1.0
107
-
108
- ### Minor Changes
109
-
110
- - `Rel`, `Graph`, `FailureDump` and `Prop` functions that took their subject first can now also be called data-last inside `pipe`. Existing calls keep working.
111
-
112
- ### Patch Changes
113
-
114
- - Updated dependencies:
115
- - @systemfsoftware/effect-spec-runtime@0.1.0
116
-
117
- ## 0.0.0
118
-
119
- ### Minor Changes
120
-
121
- - New package: hold a behaviour to the trace it produced. `Contract.of(taxonomy).stimulate(stimulus).holds(relation)` composes a spec; `Contract.judge(contract, input)` runs the behaviour under a trace id it mints, decodes the finished trace against the taxonomy, and answers the verdict with the dump path of a break; `Contract.check` fails a break with `Contract.TraceDisparityError`. `Graph.decode` is pure, answering `Result<TraceGraph, ContractDecodeError>`.
122
-
123
- `ObservationWindow.make(serviceName)` is the in-memory observation resource: `.scoped` acquires a window handle read with `ObservationWindow.collect`, and `.layer` provides `Observation | OtelTracer.OtelTracer`, one window per acquisition. `Suite.make({ it, layer })` registers a `Case` per input or a `Case.prop` per Schema, shrinking through `it.effect.prop` to one dump of the minimal failing input.
5
+ Published declaration files no longer carry the rolldown-plugin-dts patch; they come from rolldown-plugin-dts 0.28.8, which includes the same fix upstream.
package/dist/index.d.ts CHANGED
@@ -2,18 +2,20 @@ import { Context, Duration, Effect, FileSystem, Layer, Option, Schema } from "ef
2
2
  import { Pipeable } from "effect/Pipeable";
3
3
  import { Span, Taxonomy } from "@systemfsoftware/trace-taxonomy";
4
4
  import * as Result$1 from "effect/Result";
5
- import * as HttpClient from "effect/http/HttpClient";
6
- import { Blueprint, Handle } from "@systemfsoftware/effect-cell-types";
7
5
  import * as OtelTracer from "@effect/opentelemetry/OtelTracer";
6
+ import { Blueprint, Handle } from "@systemfsoftware/effect-cell-types";
8
7
  import { BasicTracerProvider, ReadableSpan } from "@opentelemetry/sdk-trace-base";
9
8
  import { Suite } from "@systemfsoftware/effect-spec-runtime";
9
+ import * as HttpClient from "effect/http/HttpClient";
10
10
  import { Asserted, Check, Expect } from "@systemfsoftware/vitest";
11
11
  import * as Scope from "effect/Scope";
12
12
  import * as Arbitrary from "effect/Arbitrary";
13
13
  //#region src/BreakFailure.schema.d.ts
14
14
  declare const BreakFailure_base: Schema.Class<BreakFailure, Schema.TaggedStruct<"BreakFailure", {
15
15
  readonly report: Schema.String;
16
- }>, import("effect/Cause").YieldableError>;
16
+ }>, import("effect/Cause").YieldableError & {
17
+ readonly brand: unique symbol;
18
+ }>;
17
19
  declare class BreakFailure extends BreakFailure_base {
18
20
  get message(): string;
19
21
  }
@@ -25,7 +27,9 @@ declare const ContractDecodeError_base: Schema.Class<ContractDecodeError, Schema
25
27
  readonly spanId: Schema.String;
26
28
  readonly attribute: Schema.String;
27
29
  readonly detail: Schema.String;
28
- }>, import("effect/Cause").YieldableError>;
30
+ }>, import("effect/Cause").YieldableError & {
31
+ readonly brand: unique symbol;
32
+ }>;
29
33
  declare class ContractDecodeError extends ContractDecodeError_base {
30
34
  get message(): string;
31
35
  }
@@ -34,7 +38,9 @@ declare class ContractDecodeError extends ContractDecodeError_base {
34
38
  declare const EmptyObservationError_base: Schema.Class<EmptyObservationError, Schema.TaggedStruct<"EmptyObservationError", {
35
39
  readonly traceId: Schema.String;
36
40
  readonly detail: Schema.String;
37
- }>, import("effect/Cause").YieldableError>;
41
+ }>, import("effect/Cause").YieldableError & {
42
+ readonly brand: unique symbol;
43
+ }>;
38
44
  declare class EmptyObservationError extends EmptyObservationError_base {
39
45
  get message(): string;
40
46
  }
@@ -44,7 +50,9 @@ declare const IncompleteObservationError_base: Schema.Class<IncompleteObservatio
44
50
  readonly traceId: Schema.String;
45
51
  readonly spanCount: Schema.Int;
46
52
  readonly detail: Schema.String;
47
- }>, import("effect/Cause").YieldableError>;
53
+ }>, import("effect/Cause").YieldableError & {
54
+ readonly brand: unique symbol;
55
+ }>;
48
56
  declare class IncompleteObservationError extends IncompleteObservationError_base {
49
57
  get message(): string;
50
58
  }
@@ -160,7 +168,9 @@ declare const TransportObservationError_base: Schema.Class<TransportObservationE
160
168
  readonly traceId: Schema.String;
161
169
  readonly source: Schema.String;
162
170
  readonly detail: Schema.String;
163
- }>, import("effect/Cause").YieldableError>;
171
+ }>, import("effect/Cause").YieldableError & {
172
+ readonly brand: unique symbol;
173
+ }>;
164
174
  declare class TransportObservationError extends TransportObservationError_base {
165
175
  get message(): string;
166
176
  }
@@ -256,7 +266,7 @@ declare const fromTaxonomy: {
256
266
  }): Relation;
257
267
  };
258
268
  declare namespace Stimulus_d_exports {
259
- export { Run, Stimulus, TraceContext, make$3 as make, traceContext };
269
+ export { Run, Stimulus, TraceContext, make$2 as make, traceContext };
260
270
  }
261
271
  interface TraceContext {
262
272
  readonly traceId: string;
@@ -273,7 +283,7 @@ interface Stimulus<Input, Output, E, R> {
273
283
  readonly name: string;
274
284
  (input: Input): Effect.Effect<Run<Input, Output>, E, R>;
275
285
  }
276
- declare const make$3: <Input, Output, E, R>(options: {
286
+ declare const make$2: <Input, Output, E, R>(options: {
277
287
  readonly name: string;
278
288
  readonly run: (context: {
279
289
  readonly input: Input;
@@ -354,55 +364,14 @@ declare const check: {
354
364
  <Input>(expect: Expect, input: Input, options?: CheckOptions): <Output, E, R>(self: Contract<Input, Output, E, R>) => Effect.Effect<void, JudgeFailure<E>, Asserted | Services<R>>;
355
365
  <Input, Output, E, R>(self: Contract<Input, Output, E, R>, expect: Expect, input: Input, options?: CheckOptions): Effect.Effect<void, JudgeFailure<E>, Asserted | Services<R>>;
356
366
  };
357
- declare namespace RemoteObservation_d_exports {
358
- export { Options, TraceSource, layer$1 as layer };
359
- }
360
- /**
361
- * The cadence of the poll, the quiet window the observed span set must hold
362
- * before the read is trusted, and the deadline the whole poll runs under.
363
- * Durations are refused unless they are positive, at layer construction.
364
- */
365
- interface Options {
366
- readonly interval: Duration.Input;
367
- readonly settle: Duration.Input;
368
- readonly timeout: Duration.Input;
369
- }
370
- /**
371
- * One read against one store: the spans that store currently holds for a trace
372
- * id, or the store's own transport or unfinished answer. A source never polls,
373
- * waits, or retries — the cadence belongs to {@link layer}.
374
- */
375
- type TraceSource<R> = (traceId: string) => Effect.Effect<ReadonlyArray<SpanRecord>, IncompleteObservationError | TransportObservationError, R>;
376
- /**
377
- * The `Observation` service over one trace source. `collect` reads the source at
378
- * `options.interval`, keeps every span it has seen keyed by span id, and answers
379
- * when that union has stayed unchanged for `options.settle`; a trace the store
380
- * never served is `EmptyObservationError`, one that never settled is
381
- * `IncompleteObservationError`, and the source's own failure ends the poll
382
- * immediately, unchanged. Provides `Observation` alone and requires exactly what
383
- * the source requires (`Scope.Scope` is supplied by the layer machinery, as
384
- * every Effect layer constructor scopes it away).
385
- */
386
- declare const layer$1: {
387
- (options: Options): <R>(source: TraceSource<R>) => Layer.Layer<Observation, never, Exclude<R, Scope.Scope>>;
388
- <R>(source: TraceSource<R>, options: Options): Layer.Layer<Observation, never, Exclude<R, Scope.Scope>>;
389
- };
390
- declare namespace tempo_trace_store_d_exports {
391
- export { source };
392
- }
393
- /** One read against one Tempo endpoint. Its only requirement is the caller's HTTP client. */
394
- declare const source: (options: {
395
- readonly baseUrl: string;
396
- }) => TraceSource<HttpClient.HttpClient>;
397
367
  //#endregion
398
368
  //#region src/ObservationWindowSpec.schema.d.ts
399
369
  declare const ObservationWindowSpec: Schema.Struct<{
400
370
  readonly serviceName: Schema.NonEmptyString;
401
371
  }>;
402
372
  type ObservationWindowSpec = typeof ObservationWindowSpec.Type;
403
- declare namespace observation_window_handle_d_exports {
404
- export { ObservationWindow, TypeId$2 as TypeId, collect, context, isObservationWindow, make$2 as make, shutdown };
405
- }
373
+ //#endregion
374
+ //#region src/observation-window.handle.d.ts
406
375
  declare const TypeId$2: unique symbol;
407
376
  type TypeId$2 = typeof TypeId$2;
408
377
  /**
@@ -424,32 +393,62 @@ declare const isObservationWindow: (u: unknown) => u is Handle.Handle<typeof Typ
424
393
  readonly store: SpanRecordStore;
425
394
  readonly provider: BasicTracerProvider;
426
395
  }, never>;
427
- declare const make$2: (spec: ObservationWindowSpec) => ObservationWindow;
428
- declare const shutdown: (self: ObservationWindow) => Effect.Effect<void>;
429
396
  declare const collect: {
430
397
  (traceId: string): (self: ObservationWindow) => Effect.Effect<ReadonlyArray<SpanRecord>, EmptyObservationError>;
431
398
  (self: ObservationWindow, traceId: string): Effect.Effect<ReadonlyArray<SpanRecord>, EmptyObservationError>;
432
399
  };
433
- declare const context: (self: ObservationWindow) => Context.Context<Observation | OtelTracer.OtelTracerProvider>;
434
400
  declare namespace observation_window_blueprint_d_exports {
435
401
  export { ObservationWindow, ObservationWindowBlueprint, ObservationWindowSpec, TypeId$2 as ObservationWindowTypeId, TypeId$1 as TypeId, collect, isObservationWindow, make$1 as make };
436
402
  }
437
403
  declare const TypeId$1: unique symbol;
438
404
  type TypeId$1 = typeof TypeId$1;
439
405
  declare const scoped: (spec: ObservationWindowSpec) => Effect.Effect<ObservationWindow, never, Scope.Scope>;
440
- declare const layer: (spec: ObservationWindowSpec) => Layer.Layer<Observation | OtelTracer.OtelTracer>;
406
+ declare const layer$1: (spec: ObservationWindowSpec) => Layer.Layer<Observation | OtelTracer.OtelTracer>;
441
407
  declare const ObservationWindows: Blueprint.Definition<typeof TypeId$1, {
442
408
  readonly serviceName: string;
443
409
  }, Blueprint.Derived<{
444
410
  readonly serviceName: string;
445
411
  }, {}, {
446
412
  readonly scoped: typeof scoped;
447
- readonly layer: typeof layer;
413
+ readonly layer: typeof layer$1;
448
414
  }>, {
449
415
  readonly serviceName: string;
450
416
  }>;
451
417
  type ObservationWindowBlueprint = Blueprint.Of<typeof ObservationWindows>;
452
418
  declare const make$1: (serviceName: string) => ObservationWindowBlueprint;
419
+ declare namespace RemoteObservation_d_exports {
420
+ export { Options, TraceSource, layer };
421
+ }
422
+ /**
423
+ * The cadence of the poll, the quiet window the observed span set must hold
424
+ * before the read is trusted, and the deadline the whole poll runs under.
425
+ * Durations are refused unless they are positive, at layer construction.
426
+ */
427
+ interface Options {
428
+ readonly interval: Duration.Input;
429
+ readonly settle: Duration.Input;
430
+ readonly timeout: Duration.Input;
431
+ }
432
+ /**
433
+ * One read against one store: the spans that store currently holds for a trace
434
+ * id, or the store's own transport or unfinished answer. A source never polls,
435
+ * waits, or retries — the cadence belongs to {@link layer}.
436
+ */
437
+ type TraceSource<R> = (traceId: string) => Effect.Effect<ReadonlyArray<SpanRecord>, IncompleteObservationError | TransportObservationError, R>;
438
+ /**
439
+ * The `Observation` service over one trace source. `collect` reads the source at
440
+ * `options.interval`, keeps every span it has seen keyed by span id, and answers
441
+ * when that union has stayed unchanged for `options.settle`; a trace the store
442
+ * never served is `EmptyObservationError`, one that never settled is
443
+ * `IncompleteObservationError`, and the source's own failure ends the poll
444
+ * immediately, unchanged. Provides `Observation` alone and requires exactly what
445
+ * the source requires (`Scope.Scope` is supplied by the layer machinery, as
446
+ * every Effect layer constructor scopes it away).
447
+ */
448
+ declare const layer: {
449
+ (options: Options): <R>(source: TraceSource<R>) => Layer.Layer<Observation, never, Exclude<R, Scope.Scope>>;
450
+ <R>(source: TraceSource<R>, options: Options): Layer.Layer<Observation, never, Exclude<R, Scope.Scope>>;
451
+ };
453
452
  //#endregion
454
453
  //#region src/HarnessFailure.schema.d.ts
455
454
  declare const HarnessFailure: Schema.Union<readonly [typeof ContractDecodeError, typeof EmptyObservationError, typeof IncompleteObservationError, typeof TransportObservationError]>;
@@ -459,7 +458,9 @@ type HarnessFailure = typeof HarnessFailure.Type;
459
458
  declare const StimulusFailure_base: Schema.Class<StimulusFailure, Schema.TaggedStruct<"StimulusFailure", {
460
459
  readonly stimulus: Schema.String;
461
460
  readonly detail: Schema.String;
462
- }>, import("effect/Cause").YieldableError>;
461
+ }>, import("effect/Cause").YieldableError & {
462
+ readonly brand: unique symbol;
463
+ }>;
463
464
  declare class StimulusFailure extends StimulusFailure_base {
464
465
  get message(): string;
465
466
  }
@@ -518,5 +519,12 @@ declare const live: {
518
519
  <Self>(self: LiveStage<Self>, reason: string): Self;
519
520
  };
520
521
  declare const make: (bindings: Suite.Bindings) => (name: string) => Declared;
522
+ declare namespace tempo_trace_store_d_exports {
523
+ export { source };
524
+ }
525
+ /** One read against one Tempo endpoint. Its only requirement is the caller's HTTP client. */
526
+ declare const source: (options: {
527
+ readonly baseUrl: string;
528
+ }) => TraceSource<HttpClient.HttpClient>;
521
529
  //#endregion
522
530
  export { Contract_d_exports as Contract, Graph_d_exports as Graph, Observation_service_d_exports as Observation, observation_window_blueprint_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
@@ -7,16 +7,16 @@ import "effect/PlatformError";
7
7
  import "@systemfsoftware/trace-taxonomy";
8
8
  import "effect/Array";
9
9
  import * as Result$1 from "effect/Result";
10
- import * as HttpClient from "effect/http/HttpClient";
11
- import * as HttpClientRequest from "effect/http/HttpClientRequest";
12
- import * as HttpClientResponse from "effect/http/HttpClientResponse";
13
- import { Blueprint, Handle, Workflow } from "@systemfsoftware/effect-cell-types";
14
- import { Hex } from "effect/encoding";
15
10
  import * as OtelTracer from "@effect/opentelemetry/OtelTracer";
16
11
  import * as OtelResource from "@effect/opentelemetry/Resource";
12
+ import { Blueprint, Handle, Workflow } from "@systemfsoftware/effect-cell-types";
17
13
  import { SpanStatusCode } from "@opentelemetry/api";
18
14
  import { AlwaysOnSampler, BasicTracerProvider } from "@opentelemetry/sdk-trace-base";
19
15
  import { Suite, TaskRef } from "@systemfsoftware/effect-spec-runtime";
16
+ import * as HttpClient from "effect/http/HttpClient";
17
+ import * as HttpClientRequest from "effect/http/HttpClientRequest";
18
+ import * as HttpClientResponse from "effect/http/HttpClientResponse";
19
+ import { Hex } from "effect/encoding";
20
20
  //#region src/BreakFailure.schema.ts
21
21
  var BreakFailure = class extends Schema.TaggedError()("BreakFailure", { report: Schema.String }) {
22
22
  get message() {
@@ -367,258 +367,6 @@ const checkDual = (self, expect, input, options) => {
367
367
  */
368
368
  const check = dual((args) => isContract(args[0]), checkDual);
369
369
  //#endregion
370
- //#region src/drivers/judge-tempo-answer.workflow.ts
371
- const JudgeTempoAnswerTypeId = Symbol.for("@systemfsoftware/trace-spec/JudgeTempoAnswer");
372
- var TempoBodyDecoded = class extends Schema.TaggedClass()("TempoBodyDecoded", {
373
- spans: Schema.Array(SpanRecord),
374
- status: Schema.optional(Schema.String)
375
- }) {};
376
- var TempoBodyUndecodable = class extends Schema.TaggedClass()("TempoBodyUndecodable", { detail: Schema.String }) {};
377
- const TempoBody = Schema.Union([TempoBodyDecoded, TempoBodyUndecodable]);
378
- var TempoAnswerComplete = class extends Schema.TaggedClass()("TempoAnswerComplete", { spans: Schema.Array(SpanRecord) }) {
379
- [JudgeTempoAnswerTypeId] = JudgeTempoAnswerTypeId;
380
- };
381
- var TempoAnswerIncomplete = class extends Schema.TaggedClass()("TempoAnswerIncomplete", {
382
- status: Schema.String,
383
- spanCount: Schema.Int
384
- }) {
385
- [JudgeTempoAnswerTypeId] = JudgeTempoAnswerTypeId;
386
- };
387
- var TempoAnswerUndecodable = class extends Schema.TaggedClass()("TempoAnswerUndecodable", { detail: Schema.String }) {
388
- [JudgeTempoAnswerTypeId] = JudgeTempoAnswerTypeId;
389
- };
390
- const TempoAnswerDecision = Schema.Union([
391
- TempoAnswerComplete,
392
- TempoAnswerIncomplete,
393
- TempoAnswerUndecodable
394
- ]);
395
- var JudgeTempoAnswer = class extends Schema.TaggedClass()("JudgeTempoAnswer", { body: TempoBody }) {
396
- static [Workflow.InstrumentationBrand] = {};
397
- };
398
- const isComplete = (status) => Option.match(status, {
399
- onNone: () => true,
400
- onSome: (marked) => marked === "COMPLETE"
401
- });
402
- const classify$1 = (spans, status) => Match.value(isComplete(status)).pipe(Match.when(true, () => new TempoAnswerComplete({ spans })), Match.when(false, () => new TempoAnswerIncomplete({
403
- status: Option.getOrElse(status, () => "INCOMPLETE"),
404
- spanCount: spans.length
405
- })), Match.exhaustive);
406
- const decide$5 = (command) => Result$1.succeed(Match.value(command.body).pipe(Match.tag("TempoBodyDecoded", (decoded) => classify$1(decoded.spans, Option.fromUndefinedOr(decoded.status))), Match.tag("TempoBodyUndecodable", (undecodable) => new TempoAnswerUndecodable({ detail: undecodable.detail })), Match.exhaustive));
407
- const judgeTempoAnswer = Workflow.make({
408
- command: JudgeTempoAnswer,
409
- decision: TempoAnswerDecision,
410
- error: Schema.Never,
411
- decide: decide$5
412
- });
413
- //#endregion
414
- //#region src/drivers/tempo-trace.schema.ts
415
- /** Proto bytes (base64 on the wire) as the lowercase hex ids traces are addressed by. */
416
- const HexId = Schema.Uint8ArrayFromBase64.pipe(Schema.decodeTo(Schema.String, {
417
- decode: SchemaGetter.transform(Hex.encode),
418
- encode: SchemaGetter.decodeHex()
419
- }));
420
- const secondsAndNanos = (nanos) => {
421
- const text = nanos.padStart(10, "0");
422
- return [Number(text.slice(0, -9)), Number(text.slice(-9))];
423
- };
424
- const millisOf$2 = (nanos) => {
425
- const [seconds, subMillis] = secondsAndNanos(nanos);
426
- return seconds * 1e3 + subMillis / 1e6;
427
- };
428
- const MillisFromNanos = Schema.String.pipe(Schema.decodeTo(Schema.Finite, SchemaTransformation.transform({
429
- decode: millisOf$2,
430
- encode: (millis) => String(Math.round(millis * 1e6))
431
- })));
432
- const unsetParent = (parent) => parent === "" || /^0+$/.test(parent);
433
- const heldParent = (parent) => unsetParent(parent) ? null : parent;
434
- const parentOf$1 = (parent) => heldParent(parent ?? "");
435
- const EMPTY_STATUS = {};
436
- const codeOf = (status) => status.code ?? "STATUS_CODE_UNSET";
437
- const STATUS_BY_CODE$1 = {
438
- STATUS_CODE_UNSET: "unset",
439
- STATUS_CODE_OK: "ok",
440
- STATUS_CODE_ERROR: "error"
441
- };
442
- const statusOf$1 = (status) => STATUS_BY_CODE$1[codeOf(status ?? EMPTY_STATUS)];
443
- const stringValueOf = (value) => value.stringValue ?? null;
444
- const numberOf = (value) => value.doubleValue ?? intToNumber(value.intValue);
445
- const intToNumber = (text) => text === void 0 ? null : Number(text);
446
- const boolOrNumberOf = (value) => value.boolValue ?? numberOf(value);
447
- const scalarOf = (value) => stringValueOf(value) ?? boolOrNumberOf(value);
448
- const isScalarMember = (member) => member !== null;
449
- const arrayValuesOf = (value) => value.arrayValue?.values;
450
- const scalarMembersOf = (values) => values.map(scalarOf).filter(isScalarMember);
451
- const arrayValueOf = (values) => values === void 0 ? null : scalarMembersOf(values);
452
- const presentValueOf = (value) => scalarOf(value) ?? arrayValueOf(arrayValuesOf(value));
453
- const isPresent$1 = (entry) => entry[1] !== null;
454
- const entryOf = (attribute) => [attribute.key, attributeValueOf(attribute.value)];
455
- const attributeValueOf = (value) => value === void 0 ? null : presentValueOf(value);
456
- const attributesOf = (attributes) => Object.fromEntries((attributes ?? []).map(entryOf).filter(isPresent$1));
457
- const errorTypeOf$1 = (attributes) => {
458
- const value = attributes["error.type"];
459
- return typeof value === "string" ? value : null;
460
- };
461
- const eventOf = (event) => ({
462
- name: event.name,
463
- attributes: attributesOf(event.attributes)
464
- });
465
- const linkIdOf = (id) => id ?? "";
466
- const linkOf = (link) => ({
467
- traceId: linkIdOf(link.traceId),
468
- spanId: linkIdOf(link.spanId)
469
- });
470
- const eventsOf$1 = (span) => (span.events ?? []).map(eventOf);
471
- const linksOf$1 = (span) => (span.links ?? []).map(linkOf);
472
- const spanRecordOf$1 = (span) => {
473
- const attributes = attributesOf(span.attributes);
474
- return {
475
- traceId: span.traceId,
476
- spanId: span.spanId,
477
- parentSpanId: parentOf$1(span.parentSpanId),
478
- name: span.name,
479
- status: statusOf$1(span.status),
480
- errorType: errorTypeOf$1(attributes),
481
- startMillis: span.startTimeUnixNano,
482
- durationMillis: span.endTimeUnixNano - span.startTimeUnixNano,
483
- attributes,
484
- events: eventsOf$1(span),
485
- links: linksOf$1(span)
486
- };
487
- };
488
- const wireTextOf = (value) => typeof value === "string" ? { stringValue: value } : void 0;
489
- const wireBoolOf = (value) => typeof value === "boolean" ? { boolValue: value } : void 0;
490
- const isValueArray = (value) => Array.isArray(value);
491
- const wireArrayOf = (value) => ({ arrayValue: { values: isValueArray(value) ? value.map(wireValueOf) : [] } });
492
- const wireNumberOrArrayOf = (value) => typeof value === "number" ? { doubleValue: value } : wireArrayOf(value);
493
- const wireBoolOrNumberOf = (value) => wireBoolOf(value) ?? wireNumberOrArrayOf(value);
494
- const wireValueOf = (value) => wireTextOf(value) ?? wireBoolOrNumberOf(value);
495
- const wireAttributesOf = (attributes) => Object.entries(attributes).map(([key, value]) => ({
496
- key,
497
- value: wireValueOf(value)
498
- }));
499
- const STATUS_CODE_BY_STATUS = {
500
- unset: "STATUS_CODE_UNSET",
501
- ok: "STATUS_CODE_OK",
502
- error: "STATUS_CODE_ERROR"
503
- };
504
- const wireStatusOf = (status) => ({ code: STATUS_CODE_BY_STATUS[status] });
505
- const wireSpanOf = (record) => ({
506
- traceId: record.traceId,
507
- spanId: record.spanId,
508
- parentSpanId: record.parentSpanId ?? void 0,
509
- name: record.name,
510
- startTimeUnixNano: record.startMillis,
511
- endTimeUnixNano: record.startMillis + record.durationMillis,
512
- attributes: wireAttributesOf(record.attributes),
513
- events: record.events.map((event) => ({
514
- name: event.name,
515
- attributes: wireAttributesOf(event.attributes)
516
- })),
517
- links: record.links.map((link) => ({
518
- traceId: link.traceId,
519
- spanId: link.spanId
520
- })),
521
- status: wireStatusOf(record.status)
522
- });
523
- const WireAnyValue = Schema.Struct({
524
- stringValue: Schema.optional(Schema.String),
525
- boolValue: Schema.optional(Schema.Boolean),
526
- intValue: Schema.optional(Schema.String),
527
- doubleValue: Schema.optional(Schema.Finite),
528
- arrayValue: Schema.optional(Schema.Struct({ values: Schema.optional(Schema.Array(Schema.suspend(() => WireAnyValue))) }))
529
- });
530
- const WireAttribute = Schema.Struct({
531
- key: Schema.String,
532
- value: Schema.optional(WireAnyValue)
533
- });
534
- const WireStatus = Schema.Struct({
535
- message: Schema.optional(Schema.String),
536
- code: Schema.optional(Schema.Literals([
537
- "STATUS_CODE_UNSET",
538
- "STATUS_CODE_OK",
539
- "STATUS_CODE_ERROR"
540
- ]))
541
- });
542
- const WireEvent = Schema.Struct({
543
- name: Schema.String,
544
- timeUnixNano: Schema.optional(MillisFromNanos),
545
- attributes: Schema.optional(Schema.Array(WireAttribute))
546
- });
547
- const WireLink = Schema.Struct({
548
- traceId: Schema.optional(HexId),
549
- spanId: Schema.optional(HexId)
550
- });
551
- const WireSpan = Schema.Struct({
552
- traceId: HexId,
553
- spanId: HexId,
554
- parentSpanId: Schema.optional(HexId),
555
- name: Schema.String,
556
- kind: Schema.optional(Schema.String),
557
- startTimeUnixNano: MillisFromNanos,
558
- endTimeUnixNano: MillisFromNanos,
559
- attributes: Schema.optional(Schema.Array(WireAttribute)),
560
- events: Schema.optional(Schema.Array(WireEvent)),
561
- links: Schema.optional(Schema.Array(WireLink)),
562
- status: Schema.optional(WireStatus),
563
- traceState: Schema.optional(Schema.String),
564
- flags: Schema.optional(Schema.Finite)
565
- }).pipe(Schema.decodeTo(SpanRecord, SchemaTransformation.transform({
566
- decode: spanRecordOf$1,
567
- encode: wireSpanOf
568
- })));
569
- const WireScopeSpans = Schema.Struct({ spans: Schema.optional(Schema.Array(WireSpan)) });
570
- const WireResourceSpans = Schema.Struct({ scopeSpans: Schema.optional(Schema.Array(WireScopeSpans)) });
571
- /** One trace-by-id answer: the spans, plus the store's completeness status when it is not complete. */
572
- const TempoV2TraceResponse = Schema.Struct({
573
- trace: Schema.Struct({ resourceSpans: Schema.optional(Schema.Array(WireResourceSpans)) }),
574
- status: Schema.optional(Schema.String),
575
- message: Schema.optional(Schema.String)
576
- });
577
- //#endregion
578
- //#region src/drivers/tempo-trace-store.ts
579
- var tempo_trace_store_exports = /* @__PURE__ */ __exportAll({ source: () => source });
580
- /**
581
- * The Grafana Tempo trace source: one read of one trace by id against one
582
- * store endpoint. It never polls, waits, or retries — completeness belongs
583
- * to the remote observation layer. Any refusal (unreachable endpoint,
584
- * non-2xx answer, undecodable body) fails with the transport failure whose
585
- * `source` is the request URL; an answer the store marked incomplete fails
586
- * with the incomplete failure, since waiting cannot complete it.
587
- */
588
- const parseBody = Schema.decodeResult(Schema.fromJsonString(TempoV2TraceResponse));
589
- const orEmpty = (values) => values ?? [];
590
- const spansOf = (response) => orEmpty(response.trace.resourceSpans).flatMap((resourceSpans) => orEmpty(resourceSpans.scopeSpans).flatMap((scopeSpans) => orEmpty(scopeSpans.spans)));
591
- const bodyOf = (decoded) => Result.match(decoded, {
592
- onFailure: (error) => new TempoBodyUndecodable({ detail: error.message }),
593
- onSuccess: (response) => new TempoBodyDecoded({
594
- spans: spansOf(response),
595
- status: response.status
596
- })
597
- });
598
- const transportFailureOf = (traceId, url, cause) => new TransportObservationError({
599
- traceId,
600
- source: url,
601
- detail: cause
602
- });
603
- const refusedOf = (traceId, url, error) => transportFailureOf(traceId, url, error.message);
604
- const undecodableOf = (traceId, url, detail) => transportFailureOf(traceId, url, `undecodable trace body: ${detail}`);
605
- const incompleteOf = (traceId, status, spanCount) => new IncompleteObservationError({
606
- traceId,
607
- spanCount,
608
- detail: `the store answered ${status}: waiting cannot complete the trace`
609
- });
610
- const readTrace = (traceId, url, body) => Effect.flatMap(Effect.fromResult(judgeTempoAnswer(new JudgeTempoAnswer({ body: bodyOf(parseBody(body)) }))), (decision) => Match.value(decision).pipe(Match.tag("TempoAnswerComplete", (complete) => Effect.succeed(complete.spans)), Match.tag("TempoAnswerIncomplete", (incomplete) => Effect.fail(incompleteOf(traceId, incomplete.status, incomplete.spanCount))), Match.tag("TempoAnswerUndecodable", (undecodable) => Effect.fail(undecodableOf(traceId, url, undecodable.detail))), Match.exhaustive));
611
- /** One read against one Tempo endpoint. Its only requirement is the caller's HTTP client. */
612
- const source = (options) => {
613
- const urlOf = (traceId) => `${options.baseUrl}/api/v2/traces/${traceId}`;
614
- return (traceId) => {
615
- const url = urlOf(traceId);
616
- 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)));
617
- };
618
- };
619
- Schema.decodeEffect(HexId);
620
- Schema.encodeEffect(HexId);
621
- //#endregion
622
370
  //#region src/observation-window.handle.ts
623
371
  const TypeId$2 = Symbol.for("~systemfsoftware/trace-spec/ObservationWindow");
624
372
  const ObservationWindow = Handle.make()(TypeId$2);
@@ -669,52 +417,52 @@ const isScalar = (value) => SCALAR_TYPES[typeof value] === true;
669
417
  const scalarArrayOrNull = (value) => Array.isArray(value) ? value.filter(isScalar) : null;
670
418
  const definedOrEmpty = (value) => value ?? [];
671
419
  const attributeValue = (value) => isScalar(value) ? value : scalarArrayOrNull(definedOrEmpty(value));
672
- const isPresent = (entry) => entry[1] !== null;
673
- const attributeRecordOf = (attributes) => Object.fromEntries(Object.entries(attributes).map((entry) => [entry[0], attributeValue(entry[1])]).filter(isPresent));
674
- const STATUS_BY_CODE = {
420
+ const isPresent$1 = (entry) => entry[1] !== null;
421
+ const attributeRecordOf = (attributes) => Object.fromEntries(Object.entries(attributes).map((entry) => [entry[0], attributeValue(entry[1])]).filter(isPresent$1));
422
+ const STATUS_BY_CODE$1 = {
675
423
  [SpanStatusCode.OK]: "ok",
676
424
  [SpanStatusCode.ERROR]: "error",
677
425
  [SpanStatusCode.UNSET]: "unset"
678
426
  };
679
- const statusOf = (code) => STATUS_BY_CODE[code] ?? "unset";
680
- const errorTypeOf = (attributes) => {
427
+ const statusOf$1 = (code) => STATUS_BY_CODE$1[code] ?? "unset";
428
+ const errorTypeOf$1 = (attributes) => {
681
429
  const value = attributes["error.type"];
682
430
  return typeof value === "string" ? value : null;
683
431
  };
684
- const parentOf = (span) => {
432
+ const parentOf$1 = (span) => {
685
433
  const parent = span.parentSpanContext;
686
434
  return parent === void 0 ? null : parent.spanId;
687
435
  };
688
- const eventsOf = (span) => span.events.map((event) => ({
436
+ const eventsOf$1 = (span) => span.events.map((event) => ({
689
437
  name: event.name,
690
438
  attributes: attributeRecordOf(event.attributes ?? {})
691
439
  }));
692
- const linksOf = (span) => span.links.map((link) => ({
440
+ const linksOf$1 = (span) => span.links.map((link) => ({
693
441
  traceId: link.context.traceId,
694
442
  spanId: link.context.spanId
695
443
  }));
696
- const millisOf$1 = (duration) => duration[0] * 1e3 + duration[1] / 1e6;
697
- const spanRecordOf = (span) => {
444
+ const millisOf$2 = (duration) => duration[0] * 1e3 + duration[1] / 1e6;
445
+ const spanRecordOf$1 = (span) => {
698
446
  const attributes = attributeRecordOf(span.attributes);
699
447
  return {
700
448
  traceId: span.spanContext().traceId,
701
449
  spanId: span.spanContext().spanId,
702
- parentSpanId: parentOf(span),
450
+ parentSpanId: parentOf$1(span),
703
451
  name: span.name,
704
- status: statusOf(span.status.code),
705
- errorType: errorTypeOf(attributes),
706
- startMillis: millisOf$1(span.startTime),
707
- durationMillis: millisOf$1(span.duration),
452
+ status: statusOf$1(span.status.code),
453
+ errorType: errorTypeOf$1(attributes),
454
+ startMillis: millisOf$2(span.startTime),
455
+ durationMillis: millisOf$2(span.duration),
708
456
  attributes,
709
- events: eventsOf(span),
710
- links: linksOf(span)
457
+ events: eventsOf$1(span),
458
+ links: linksOf$1(span)
711
459
  };
712
460
  };
713
461
  const emptyObservation = (traceId) => new EmptyObservationError({
714
462
  traceId,
715
463
  detail: "the observation window closed with no span carrying the stimulated trace id"
716
464
  });
717
- const recordsOf = (self, traceId) => ObservationWindow.slot(self).store.getFinishedSpans().filter((span) => span.spanContext().traceId === traceId).map(spanRecordOf);
465
+ const recordsOf = (self, traceId) => ObservationWindow.slot(self).store.getFinishedSpans().filter((span) => span.spanContext().traceId === traceId).map(spanRecordOf$1);
718
466
  const collect = dual(2, (self, traceId) => Effect.suspend(() => {
719
467
  const records = recordsOf(self, traceId);
720
468
  return records.length > 0 ? Effect.succeed(records) : Effect.fail(emptyObservation(traceId));
@@ -772,7 +520,7 @@ var CombineAnyRelations = class extends Schema.TaggedClass()("CombineAnyRelation
772
520
  const holds = (verdict) => Match.value(verdict).pipe(Match.tag("Hold", () => true), Match.tag("Break", () => false), Match.exhaustive);
773
521
  const inspectedOf$1 = (verdicts) => Array$1.flatMap(verdicts, (verdict) => verdict.inspected);
774
522
  const namedConjuncts = (verdicts) => Array$1.join(Array$1.map(verdicts, (verdict) => verdict.conjunct), ", ");
775
- const decide$4 = (command) => Result$1.succeed(Match.value(Array$1.some(command.verdicts, holds)).pipe(Match.when(true, () => new AnyRelationHeld({
523
+ const decide$5 = (command) => Result$1.succeed(Match.value(Array$1.some(command.verdicts, holds)).pipe(Match.when(true, () => new AnyRelationHeld({
776
524
  conjunct: command.conjunct,
777
525
  inspected: inspectedOf$1(command.verdicts)
778
526
  })), Match.when(false, () => new NoRelationHeld({
@@ -785,7 +533,7 @@ const combineAnyRelations = Workflow.make({
785
533
  command: CombineAnyRelations,
786
534
  decision: CombineAnyRelationsDecision,
787
535
  error: Schema.Never,
788
- decide: decide$4
536
+ decide: decide$5
789
537
  });
790
538
  //#endregion
791
539
  //#region src/combine-relation-verdicts.workflow.ts
@@ -846,7 +594,7 @@ const aggregateSoft = (command) => {
846
594
  })
847
595
  });
848
596
  };
849
- const decide$3 = (command) => Result$1.succeed(Option.match(firstHardBreak(command.verdicts), {
597
+ const decide$4 = (command) => Result$1.succeed(Option.match(firstHardBreak(command.verdicts), {
850
598
  onNone: () => aggregateSoft(command),
851
599
  onSome: (winner) => new FirstHardBreak({
852
600
  conjunct: winner.conjunct,
@@ -858,7 +606,7 @@ const combineRelationVerdicts = Workflow.make({
858
606
  command: CombineRelationVerdicts,
859
607
  decision: CombineRelationVerdictsDecision,
860
608
  error: Schema.Never,
861
- decide: decide$3
609
+ decide: decide$4
862
610
  });
863
611
  //#endregion
864
612
  //#region src/judge-edge-placement.workflow.ts
@@ -886,7 +634,7 @@ var JudgeEdgePlacement = class extends Schema.TaggedClass()("JudgeEdgePlacement"
886
634
  static [Workflow.InstrumentationBrand] = {};
887
635
  };
888
636
  const everyPlaced = (placed, reached) => Array$1.every(placed, (spanId) => Array$1.some(reached, (candidate) => candidate === spanId));
889
- const decide$2 = (command) => Result$1.succeed(Match.value(everyPlaced(command.placed, command.reached)).pipe(Match.when(true, () => new EdgePlacementHeld({
637
+ const decide$3 = (command) => Result$1.succeed(Match.value(everyPlaced(command.placed, command.reached)).pipe(Match.when(true, () => new EdgePlacementHeld({
890
638
  conjunct: command.conjunct,
891
639
  inspected: command.placed
892
640
  })), Match.when(false, () => new EdgePlacementBroken({
@@ -898,7 +646,7 @@ const judgeEdgePlacement = Workflow.make({
898
646
  command: JudgeEdgePlacement,
899
647
  decision: JudgeEdgePlacementDecision,
900
648
  error: Schema.Never,
901
- decide: decide$2
649
+ decide: decide$3
902
650
  });
903
651
  //#endregion
904
652
  //#region src/negate-relation-verdict.workflow.ts
@@ -923,7 +671,7 @@ var NegateRelationVerdict = class extends Schema.TaggedClass()("NegateRelationVe
923
671
  }) {
924
672
  static [Workflow.InstrumentationBrand] = {};
925
673
  };
926
- const decide$1 = (command) => Result$1.succeed(Match.value(command.verdict).pipe(Match.tag("Hold", (held) => new HoldNegated({
674
+ const decide$2 = (command) => Result$1.succeed(Match.value(command.verdict).pipe(Match.tag("Hold", (held) => new HoldNegated({
927
675
  conjunct: command.conjunct,
928
676
  inspected: held.inspected,
929
677
  detail: `${held.conjunct} held`
@@ -935,7 +683,7 @@ const negateRelationVerdict = Workflow.make({
935
683
  command: NegateRelationVerdict,
936
684
  decision: NegateRelationVerdictDecision,
937
685
  error: Schema.Never,
938
- decide: decide$1
686
+ decide: decide$2
939
687
  });
940
688
  //#endregion
941
689
  //#region src/Rel.ts
@@ -1276,13 +1024,13 @@ const absorbed = (observed, read, elapsedMillis) => {
1276
1024
  });
1277
1025
  };
1278
1026
  const quietForLongEnough = (observed, elapsedMillis, settleMillis) => Array$1.every([observed.spans.length > 0, elapsedMillis - observed.addedAtMillis >= settleMillis], Boolean);
1279
- const classify = (observation, elapsedMillis, settleMillis, timeoutMillis) => Match.value(quietForLongEnough(observation, elapsedMillis, settleMillis)).pipe(Match.when(true, () => new ReadSettled({ observation })), Match.when(false, () => Match.value(elapsedMillis >= timeoutMillis).pipe(Match.when(true, () => Match.value(observation.spans.length === 0).pipe(Match.when(true, () => new ReadAbsent({ observation })), Match.when(false, () => new ReadUnfinished({ observation })), Match.exhaustive)), Match.when(false, () => new ReadStillGrowing({ observation })), Match.exhaustive)), Match.exhaustive);
1280
- const decide = (command) => Result$1.succeed(classify(absorbed(command.observed, command.read, command.elapsedMillis), command.elapsedMillis, command.settleMillis, command.timeoutMillis));
1027
+ const classify$1 = (observation, elapsedMillis, settleMillis, timeoutMillis) => Match.value(quietForLongEnough(observation, elapsedMillis, settleMillis)).pipe(Match.when(true, () => new ReadSettled({ observation })), Match.when(false, () => Match.value(elapsedMillis >= timeoutMillis).pipe(Match.when(true, () => Match.value(observation.spans.length === 0).pipe(Match.when(true, () => new ReadAbsent({ observation })), Match.when(false, () => new ReadUnfinished({ observation })), Match.exhaustive)), Match.when(false, () => new ReadStillGrowing({ observation })), Match.exhaustive)), Match.exhaustive);
1028
+ const decide$1 = (command) => Result$1.succeed(classify$1(absorbed(command.observed, command.read, command.elapsedMillis), command.elapsedMillis, command.settleMillis, command.timeoutMillis));
1281
1029
  const settleRemoteRead = Workflow.make({
1282
1030
  command: SettleRemoteRead,
1283
1031
  decision: SettleRemoteReadDecision,
1284
1032
  error: Schema.Never,
1285
- decide
1033
+ decide: decide$1
1286
1034
  });
1287
1035
  //#endregion
1288
1036
  //#region src/RemoteObservation.ts
@@ -1314,17 +1062,17 @@ const settleStep = (settlement, read, elapsedMillis, windows) => {
1314
1062
  };
1315
1063
  const lateRefusal = (settlement) => settlement.spans.length === 0 ? "absent" : "unfinished";
1316
1064
  const deadlineVerdict = (settlement, elapsedMillis, windows) => pastDeadline(elapsedMillis, windows) ? refusedVerdict(lateRefusal(settlement)) : Option.none();
1317
- const millisOf = (input) => Duration.toMillis(input);
1065
+ const millisOf$1 = (input) => Duration.toMillis(input);
1318
1066
  const windowsOf = (options) => ({
1319
- settleMillis: millisOf(options.settle),
1320
- timeoutMillis: millisOf(options.timeout)
1067
+ settleMillis: millisOf$1(options.settle),
1068
+ timeoutMillis: millisOf$1(options.timeout)
1321
1069
  });
1322
1070
  const nonPositive = (options) => [
1323
1071
  options.interval,
1324
1072
  options.settle,
1325
1073
  options.timeout
1326
- ].some((input) => millisOf(input) <= 0);
1327
- 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;
1074
+ ].some((input) => millisOf$1(input) <= 0);
1075
+ const requirePositive = (options) => nonPositive(options) ? Effect.die(/* @__PURE__ */ new Error(`remote observation durations must be positive milliseconds: interval=${millisOf$1(options.interval)}, settle=${millisOf$1(options.settle)}, timeout=${millisOf$1(options.timeout)}`)) : Effect.void;
1328
1076
  const absence = (traceId, windows) => new EmptyObservationError({
1329
1077
  traceId,
1330
1078
  detail: `the remote trace store served no span for trace ${traceId} before the ${windows.timeoutMillis}ms timeout`
@@ -1559,4 +1307,256 @@ const liveDual = (self, reason) => self.live(reason);
1559
1307
  const live = dual(2, liveDual);
1560
1308
  const make = (bindings) => (name) => declared(bindings, name, void 0);
1561
1309
  //#endregion
1310
+ //#region src/judge-tempo-answer.workflow.ts
1311
+ const JudgeTempoAnswerTypeId = Symbol.for("@systemfsoftware/trace-spec/JudgeTempoAnswer");
1312
+ var TempoBodyDecoded = class extends Schema.TaggedClass()("TempoBodyDecoded", {
1313
+ spans: Schema.Array(SpanRecord),
1314
+ status: Schema.optional(Schema.String)
1315
+ }) {};
1316
+ var TempoBodyUndecodable = class extends Schema.TaggedClass()("TempoBodyUndecodable", { detail: Schema.String }) {};
1317
+ const TempoBody = Schema.Union([TempoBodyDecoded, TempoBodyUndecodable]);
1318
+ var TempoAnswerComplete = class extends Schema.TaggedClass()("TempoAnswerComplete", { spans: Schema.Array(SpanRecord) }) {
1319
+ [JudgeTempoAnswerTypeId] = JudgeTempoAnswerTypeId;
1320
+ };
1321
+ var TempoAnswerIncomplete = class extends Schema.TaggedClass()("TempoAnswerIncomplete", {
1322
+ status: Schema.String,
1323
+ spanCount: Schema.Int
1324
+ }) {
1325
+ [JudgeTempoAnswerTypeId] = JudgeTempoAnswerTypeId;
1326
+ };
1327
+ var TempoAnswerUndecodable = class extends Schema.TaggedClass()("TempoAnswerUndecodable", { detail: Schema.String }) {
1328
+ [JudgeTempoAnswerTypeId] = JudgeTempoAnswerTypeId;
1329
+ };
1330
+ const TempoAnswerDecision = Schema.Union([
1331
+ TempoAnswerComplete,
1332
+ TempoAnswerIncomplete,
1333
+ TempoAnswerUndecodable
1334
+ ]);
1335
+ var JudgeTempoAnswer = class extends Schema.TaggedClass()("JudgeTempoAnswer", { body: TempoBody }) {
1336
+ static [Workflow.InstrumentationBrand] = {};
1337
+ };
1338
+ const isComplete = (status) => Option.match(status, {
1339
+ onNone: () => true,
1340
+ onSome: (marked) => marked === "COMPLETE"
1341
+ });
1342
+ const classify = (spans, status) => Match.value(isComplete(status)).pipe(Match.when(true, () => new TempoAnswerComplete({ spans })), Match.when(false, () => new TempoAnswerIncomplete({
1343
+ status: Option.getOrElse(status, () => "INCOMPLETE"),
1344
+ spanCount: spans.length
1345
+ })), Match.exhaustive);
1346
+ const decide = (command) => Result$1.succeed(Match.value(command.body).pipe(Match.tag("TempoBodyDecoded", (decoded) => classify(decoded.spans, Option.fromUndefinedOr(decoded.status))), Match.tag("TempoBodyUndecodable", (undecodable) => new TempoAnswerUndecodable({ detail: undecodable.detail })), Match.exhaustive));
1347
+ const judgeTempoAnswer = Workflow.make({
1348
+ command: JudgeTempoAnswer,
1349
+ decision: TempoAnswerDecision,
1350
+ error: Schema.Never,
1351
+ decide
1352
+ });
1353
+ //#endregion
1354
+ //#region src/tempo-trace.schema.ts
1355
+ /** Proto bytes (base64 on the wire) as the lowercase hex ids traces are addressed by. */
1356
+ const HexId = Schema.Uint8ArrayFromBase64.pipe(Schema.decodeTo(Schema.String, {
1357
+ decode: SchemaGetter.transform(Hex.encode),
1358
+ encode: SchemaGetter.decodeHex()
1359
+ }));
1360
+ const secondsAndNanos = (nanos) => {
1361
+ const text = nanos.padStart(10, "0");
1362
+ return [Number(text.slice(0, -9)), Number(text.slice(-9))];
1363
+ };
1364
+ const millisOf = (nanos) => {
1365
+ const [seconds, subMillis] = secondsAndNanos(nanos);
1366
+ return seconds * 1e3 + subMillis / 1e6;
1367
+ };
1368
+ const MillisFromNanos = Schema.String.pipe(Schema.decodeTo(Schema.Finite, SchemaTransformation.transform({
1369
+ decode: millisOf,
1370
+ encode: (millis) => String(Math.round(millis * 1e6))
1371
+ })));
1372
+ const unsetParent = (parent) => parent === "" || /^0+$/.test(parent);
1373
+ const heldParent = (parent) => unsetParent(parent) ? null : parent;
1374
+ const parentOf = (parent) => heldParent(parent ?? "");
1375
+ const EMPTY_STATUS = {};
1376
+ const codeOf = (status) => status.code ?? "STATUS_CODE_UNSET";
1377
+ const STATUS_BY_CODE = {
1378
+ STATUS_CODE_UNSET: "unset",
1379
+ STATUS_CODE_OK: "ok",
1380
+ STATUS_CODE_ERROR: "error"
1381
+ };
1382
+ const statusOf = (status) => STATUS_BY_CODE[codeOf(status ?? EMPTY_STATUS)];
1383
+ const stringValueOf = (value) => value.stringValue ?? null;
1384
+ const numberOf = (value) => value.doubleValue ?? intToNumber(value.intValue);
1385
+ const intToNumber = (text) => text === void 0 ? null : Number(text);
1386
+ const boolOrNumberOf = (value) => value.boolValue ?? numberOf(value);
1387
+ const scalarOf = (value) => stringValueOf(value) ?? boolOrNumberOf(value);
1388
+ const isScalarMember = (member) => member !== null;
1389
+ const arrayValuesOf = (value) => value.arrayValue?.values;
1390
+ const scalarMembersOf = (values) => values.map(scalarOf).filter(isScalarMember);
1391
+ const arrayValueOf = (values) => values === void 0 ? null : scalarMembersOf(values);
1392
+ const presentValueOf = (value) => scalarOf(value) ?? arrayValueOf(arrayValuesOf(value));
1393
+ const isPresent = (entry) => entry[1] !== null;
1394
+ const entryOf = (attribute) => [attribute.key, attributeValueOf(attribute.value)];
1395
+ const attributeValueOf = (value) => value === void 0 ? null : presentValueOf(value);
1396
+ const attributesOf = (attributes) => Object.fromEntries((attributes ?? []).map(entryOf).filter(isPresent));
1397
+ const errorTypeOf = (attributes) => {
1398
+ const value = attributes["error.type"];
1399
+ return typeof value === "string" ? value : null;
1400
+ };
1401
+ const eventOf = (event) => ({
1402
+ name: event.name,
1403
+ attributes: attributesOf(event.attributes)
1404
+ });
1405
+ const linkIdOf = (id) => id ?? "";
1406
+ const linkOf = (link) => ({
1407
+ traceId: linkIdOf(link.traceId),
1408
+ spanId: linkIdOf(link.spanId)
1409
+ });
1410
+ const eventsOf = (span) => (span.events ?? []).map(eventOf);
1411
+ const linksOf = (span) => (span.links ?? []).map(linkOf);
1412
+ const spanRecordOf = (span) => {
1413
+ const attributes = attributesOf(span.attributes);
1414
+ return {
1415
+ traceId: span.traceId,
1416
+ spanId: span.spanId,
1417
+ parentSpanId: parentOf(span.parentSpanId),
1418
+ name: span.name,
1419
+ status: statusOf(span.status),
1420
+ errorType: errorTypeOf(attributes),
1421
+ startMillis: span.startTimeUnixNano,
1422
+ durationMillis: span.endTimeUnixNano - span.startTimeUnixNano,
1423
+ attributes,
1424
+ events: eventsOf(span),
1425
+ links: linksOf(span)
1426
+ };
1427
+ };
1428
+ const wireTextOf = (value) => typeof value === "string" ? { stringValue: value } : void 0;
1429
+ const wireBoolOf = (value) => typeof value === "boolean" ? { boolValue: value } : void 0;
1430
+ const isValueArray = (value) => Array.isArray(value);
1431
+ const wireArrayOf = (value) => ({ arrayValue: { values: isValueArray(value) ? value.map(wireValueOf) : [] } });
1432
+ const wireNumberOrArrayOf = (value) => typeof value === "number" ? { doubleValue: value } : wireArrayOf(value);
1433
+ const wireBoolOrNumberOf = (value) => wireBoolOf(value) ?? wireNumberOrArrayOf(value);
1434
+ const wireValueOf = (value) => wireTextOf(value) ?? wireBoolOrNumberOf(value);
1435
+ const wireAttributesOf = (attributes) => Object.entries(attributes).map(([key, value]) => ({
1436
+ key,
1437
+ value: wireValueOf(value)
1438
+ }));
1439
+ const STATUS_CODE_BY_STATUS = {
1440
+ unset: "STATUS_CODE_UNSET",
1441
+ ok: "STATUS_CODE_OK",
1442
+ error: "STATUS_CODE_ERROR"
1443
+ };
1444
+ const wireStatusOf = (status) => ({ code: STATUS_CODE_BY_STATUS[status] });
1445
+ const wireSpanOf = (record) => ({
1446
+ traceId: record.traceId,
1447
+ spanId: record.spanId,
1448
+ parentSpanId: record.parentSpanId ?? void 0,
1449
+ name: record.name,
1450
+ startTimeUnixNano: record.startMillis,
1451
+ endTimeUnixNano: record.startMillis + record.durationMillis,
1452
+ attributes: wireAttributesOf(record.attributes),
1453
+ events: record.events.map((event) => ({
1454
+ name: event.name,
1455
+ attributes: wireAttributesOf(event.attributes)
1456
+ })),
1457
+ links: record.links.map((link) => ({
1458
+ traceId: link.traceId,
1459
+ spanId: link.spanId
1460
+ })),
1461
+ status: wireStatusOf(record.status)
1462
+ });
1463
+ const WireAnyValue = Schema.Struct({
1464
+ stringValue: Schema.optional(Schema.String),
1465
+ boolValue: Schema.optional(Schema.Boolean),
1466
+ intValue: Schema.optional(Schema.String),
1467
+ doubleValue: Schema.optional(Schema.Finite),
1468
+ arrayValue: Schema.optional(Schema.Struct({ values: Schema.optional(Schema.Array(Schema.suspend(() => WireAnyValue))) }))
1469
+ });
1470
+ const WireAttribute = Schema.Struct({
1471
+ key: Schema.String,
1472
+ value: Schema.optional(WireAnyValue)
1473
+ });
1474
+ const WireStatus = Schema.Struct({
1475
+ message: Schema.optional(Schema.String),
1476
+ code: Schema.optional(Schema.Literals([
1477
+ "STATUS_CODE_UNSET",
1478
+ "STATUS_CODE_OK",
1479
+ "STATUS_CODE_ERROR"
1480
+ ]))
1481
+ });
1482
+ const WireEvent = Schema.Struct({
1483
+ name: Schema.String,
1484
+ timeUnixNano: Schema.optional(MillisFromNanos),
1485
+ attributes: Schema.optional(Schema.Array(WireAttribute))
1486
+ });
1487
+ const WireLink = Schema.Struct({
1488
+ traceId: Schema.optional(HexId),
1489
+ spanId: Schema.optional(HexId)
1490
+ });
1491
+ const WireSpan = Schema.Struct({
1492
+ traceId: HexId,
1493
+ spanId: HexId,
1494
+ parentSpanId: Schema.optional(HexId),
1495
+ name: Schema.String,
1496
+ kind: Schema.optional(Schema.String),
1497
+ startTimeUnixNano: MillisFromNanos,
1498
+ endTimeUnixNano: MillisFromNanos,
1499
+ attributes: Schema.optional(Schema.Array(WireAttribute)),
1500
+ events: Schema.optional(Schema.Array(WireEvent)),
1501
+ links: Schema.optional(Schema.Array(WireLink)),
1502
+ status: Schema.optional(WireStatus),
1503
+ traceState: Schema.optional(Schema.String),
1504
+ flags: Schema.optional(Schema.Finite)
1505
+ }).pipe(Schema.decodeTo(SpanRecord, SchemaTransformation.transform({
1506
+ decode: spanRecordOf,
1507
+ encode: wireSpanOf
1508
+ })));
1509
+ const WireScopeSpans = Schema.Struct({ spans: Schema.optional(Schema.Array(WireSpan)) });
1510
+ const WireResourceSpans = Schema.Struct({ scopeSpans: Schema.optional(Schema.Array(WireScopeSpans)) });
1511
+ /** One trace-by-id answer: the spans, plus the store's completeness status when it is not complete. */
1512
+ const TempoV2TraceResponse = Schema.Struct({
1513
+ trace: Schema.Struct({ resourceSpans: Schema.optional(Schema.Array(WireResourceSpans)) }),
1514
+ status: Schema.optional(Schema.String),
1515
+ message: Schema.optional(Schema.String)
1516
+ });
1517
+ //#endregion
1518
+ //#region src/tempo-trace-store.ts
1519
+ var tempo_trace_store_exports = /* @__PURE__ */ __exportAll({ source: () => source });
1520
+ /**
1521
+ * The Grafana Tempo trace source: one read of one trace by id against one
1522
+ * store endpoint. It never polls, waits, or retries — completeness belongs
1523
+ * to the remote observation layer. Any refusal (unreachable endpoint,
1524
+ * non-2xx answer, undecodable body) fails with the transport failure whose
1525
+ * `source` is the request URL; an answer the store marked incomplete fails
1526
+ * with the incomplete failure, since waiting cannot complete it.
1527
+ */
1528
+ const parseBody = Schema.decodeResult(Schema.fromJsonString(TempoV2TraceResponse));
1529
+ const orEmpty = (values) => values ?? [];
1530
+ const spansOf = (response) => orEmpty(response.trace.resourceSpans).flatMap((resourceSpans) => orEmpty(resourceSpans.scopeSpans).flatMap((scopeSpans) => orEmpty(scopeSpans.spans)));
1531
+ const bodyOf = (decoded) => Result.match(decoded, {
1532
+ onFailure: (error) => new TempoBodyUndecodable({ detail: error.message }),
1533
+ onSuccess: (response) => new TempoBodyDecoded({
1534
+ spans: spansOf(response),
1535
+ status: response.status
1536
+ })
1537
+ });
1538
+ const transportFailureOf = (traceId, url, cause) => new TransportObservationError({
1539
+ traceId,
1540
+ source: url,
1541
+ detail: cause
1542
+ });
1543
+ const refusedOf = (traceId, url, error) => transportFailureOf(traceId, url, error.message);
1544
+ const undecodableOf = (traceId, url, detail) => transportFailureOf(traceId, url, `undecodable trace body: ${detail}`);
1545
+ const incompleteOf = (traceId, status, spanCount) => new IncompleteObservationError({
1546
+ traceId,
1547
+ spanCount,
1548
+ detail: `the store answered ${status}: waiting cannot complete the trace`
1549
+ });
1550
+ const readTrace = (traceId, url, body) => Effect.flatMap(Effect.fromResult(judgeTempoAnswer(new JudgeTempoAnswer({ body: bodyOf(parseBody(body)) }))), (decision) => Match.value(decision).pipe(Match.tag("TempoAnswerComplete", (complete) => Effect.succeed(complete.spans)), Match.tag("TempoAnswerIncomplete", (incomplete) => Effect.fail(incompleteOf(traceId, incomplete.status, incomplete.spanCount))), Match.tag("TempoAnswerUndecodable", (undecodable) => Effect.fail(undecodableOf(traceId, url, undecodable.detail))), Match.exhaustive));
1551
+ /** One read against one Tempo endpoint. Its only requirement is the caller's HTTP client. */
1552
+ const source = (options) => {
1553
+ const urlOf = (traceId) => `${options.baseUrl}/api/v2/traces/${traceId}`;
1554
+ return (traceId) => {
1555
+ const url = urlOf(traceId);
1556
+ 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)));
1557
+ };
1558
+ };
1559
+ Schema.decodeEffect(HexId);
1560
+ Schema.encodeEffect(HexId);
1561
+ //#endregion
1562
1562
  export { Contract_exports as Contract, Graph_exports as Graph, Observation_service_exports as Observation, observation_window_blueprint_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": "1.0.1",
4
+ "version": "1.2.4",
5
5
  "author": "Ryan Lee <drdgvhbh@gmail.com>",
6
6
  "repository": {
7
7
  "type": "git",
@@ -33,44 +33,43 @@
33
33
  "dist"
34
34
  ],
35
35
  "dependencies": {
36
- "@effect/opentelemetry": "^4",
36
+ "@effect/opentelemetry": "^4.1.2",
37
37
  "@opentelemetry/api": "^1.9.0",
38
- "@opentelemetry/resources": "^2",
39
- "@opentelemetry/sdk-trace-base": "^2",
40
- "@opentelemetry/semantic-conventions": "^1.33",
41
- "@systemfsoftware/effect-cell-types": "^12.0.0",
42
- "@systemfsoftware/effect-spec-runtime": "^1.0.1",
43
- "@systemfsoftware/trace-taxonomy": "^1.0.0"
38
+ "@opentelemetry/resources": "^2.12.0",
39
+ "@opentelemetry/sdk-trace-base": "^2.12.0",
40
+ "@opentelemetry/semantic-conventions": "^1.43.0",
41
+ "@systemfsoftware/effect-cell-types": "^12.1.4",
42
+ "@systemfsoftware/effect-spec-runtime": "^1.1.5",
43
+ "@systemfsoftware/trace-taxonomy": "^1.0.6"
44
44
  },
45
45
  "devDependencies": {
46
- "@effect/platform-node": "^4",
46
+ "@effect/platform-node": "^4.1.2",
47
47
  "@systemfsoftware/arethetypeswrong-cli": "^4.2.0",
48
- "@systemfsoftware/conformance-spec": "^1.0.0",
49
- "@systemfsoftware/effect-gherkin-spec": "^7.0.1",
50
- "@systemfsoftware/oxlint-config-recommended": "^4.0.0",
51
- "@systemfsoftware/stryker-config": "^0.1.0",
48
+ "@systemfsoftware/conformance-spec": "^1.1.4",
49
+ "@systemfsoftware/effect-gherkin-spec": "^8.1.4",
50
+ "@systemfsoftware/oxlint-config-recommended": "^6.0.1",
51
+ "@systemfsoftware/stryker-config": "^0.2.1",
52
52
  "@systemfsoftware/stryker-ignorer-effect-schema-declarations": "latest",
53
53
  "@systemfsoftware/stryker-ignorer-in-source-vitest-block": "latest",
54
54
  "@systemfsoftware/stryker-js": "latest",
55
55
  "@systemfsoftware/stryker-js-typescript-checker": "latest",
56
56
  "@systemfsoftware/stryker-js-vitest-runner": "latest",
57
- "@systemfsoftware/stryker-test-contribution": "latest",
58
- "@systemfsoftware/tsconfig": "^2.0.1",
59
- "@systemfsoftware/tsdown-config": "^0.1.0",
60
- "@systemfsoftware/vitest": "^2.0.0",
61
- "@systemfsoftware/vitest-config": "^0.1.0",
62
- "@types/node": "^26",
63
- "effect": "^4",
64
- "oxlint": "~1.82.0",
57
+ "@systemfsoftware/tsconfig": "^2.1.0",
58
+ "@systemfsoftware/tsdown-config": "^0.1.2",
59
+ "@systemfsoftware/vitest": "^4.1.1",
60
+ "@systemfsoftware/vitest-config": "^0.1.3",
61
+ "@types/node": "^26.6.5",
62
+ "effect": "^4.1.2",
63
+ "oxlint": "~1.89.0",
65
64
  "rimraf": "^6.1.3",
66
65
  "tsdown": "^0.23.0",
67
66
  "tstyche": "^7.2.5",
68
- "vitest": "^5"
67
+ "vitest": "^5.1.0"
69
68
  },
70
69
  "peerDependencies": {
71
- "@systemfsoftware/vitest": "^2.0.0",
72
- "effect": "^4",
73
- "vitest": "^5"
70
+ "@systemfsoftware/vitest": "^4.1.1",
71
+ "effect": "^4.1.2",
72
+ "vitest": "^5.1.0"
74
73
  },
75
74
  "publishConfig": {
76
75
  "access": "public",
@@ -82,7 +81,6 @@
82
81
  "typecheck": "tsc -b",
83
82
  "test": "vitest run",
84
83
  "mutation": "stryker run",
85
- "mutation:full": "stryker run --force",
86
84
  "test:types": "TSTYCHE_TYPESCRIPT_MODULE=tstyche-typescript tstyche",
87
85
  "lint": "f=${OXLINT_FORMAT:-${AGENT:+agent}}; oxlint . --format=${f:-default}",
88
86
  "attw": "attw --pack ."