@systemfsoftware/trace-spec 0.5.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # @systemfsoftware/trace-spec
2
2
 
3
+ ## 1.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 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.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies:
12
+ - @systemfsoftware/effect-cell-types@12.0.0
13
+ - @systemfsoftware/effect-spec-runtime@1.0.0
14
+ - @systemfsoftware/trace-taxonomy@1.0.0
15
+ - @systemfsoftware/vitest@1.0.0
16
+
17
+ ## 0.5.2
18
+
19
+ ### Patch Changes
20
+
21
+ - Updated dependencies:
22
+ - @systemfsoftware/vitest@0.3.0
23
+
3
24
  ## 0.5.1
4
25
 
5
26
  ### Patch Changes
package/README.md CHANGED
@@ -96,7 +96,7 @@ When the behaviour runs in another process — a CLI binary, a worker, a contain
96
96
  ```ts
97
97
  import { RemoteObservation, TempoTraceStore } from '@systemfsoftware/trace-spec'
98
98
  import { Layer } from 'effect'
99
- import { FetchHttpClient } from 'effect/unstable/http'
99
+ import { FetchHttpClient } from 'effect/http'
100
100
 
101
101
  const observation = RemoteObservation.layer(
102
102
  TempoTraceStore.source({ baseUrl: 'http://127.0.0.1:3200' }),
package/dist/index.d.ts CHANGED
@@ -1,14 +1,15 @@
1
- import { Context, Duration, Effect, FileSystem, Layer, Result, Schema } from "effect";
1
+ import { Context, Duration, Effect, FileSystem, Layer, Option, 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";
5
- import * as OtelTracer from "@effect/opentelemetry/OtelTracer";
4
+ import * as Result$1 from "effect/Result";
5
+ import * as HttpClient from "effect/http/HttpClient";
6
6
  import { Blueprint, Handle } from "@systemfsoftware/effect-cell-types";
7
+ import * as OtelTracer from "@effect/opentelemetry/OtelTracer";
7
8
  import { BasicTracerProvider, ReadableSpan } from "@opentelemetry/sdk-trace-base";
8
9
  import { Suite } from "@systemfsoftware/effect-spec-runtime";
9
10
  import { Asserted, Check, Expect } from "@systemfsoftware/vitest";
10
11
  import * as Scope from "effect/Scope";
11
- import * as Arbitrary from "effect/unstable/arbitrary/Arbitrary";
12
+ import * as Arbitrary from "effect/Arbitrary";
12
13
  //#region src/BreakFailure.schema.d.ts
13
14
  declare const BreakFailure_base: Schema.Class<BreakFailure, Schema.TaggedStruct<"BreakFailure", {
14
15
  readonly report: Schema.String;
@@ -126,9 +127,7 @@ declare const TraceGraph: Schema.Struct<{
126
127
  }>>;
127
128
  }>;
128
129
  type TraceGraph = typeof TraceGraph.Type;
129
- declare namespace Graph_d_exports {
130
- export { AttributeValue, Attributes, GraphNode, SpanEvent, SpanLink, SpanRecord, Status, TraceGraph, byId, children, decode, descendants };
131
- }
130
+ type GraphDecoded = Result$1.Result<TraceGraph, ContractDecodeError>;
132
131
  declare const byId: {
133
132
  (spec: Span.Span): (graph: TraceGraph) => ReadonlyArray<GraphNode>;
134
133
  (graph: TraceGraph, spec: Span.Span): ReadonlyArray<GraphNode>;
@@ -149,9 +148,12 @@ declare const descendants: {
149
148
  * fail the pipeline by accident.
150
149
  */
151
150
  declare const decode: {
152
- (spans: ReadonlyArray<SpanRecord>, taxonomy: Taxonomy.Taxonomy): (traceId: string) => Result.Result<TraceGraph, ContractDecodeError>;
153
- (traceId: string, spans: ReadonlyArray<SpanRecord>, taxonomy: Taxonomy.Taxonomy): Result.Result<TraceGraph, ContractDecodeError>;
151
+ (spans: ReadonlyArray<SpanRecord>, taxonomy: Taxonomy.Taxonomy): (traceId: string) => GraphDecoded;
152
+ (traceId: string, spans: ReadonlyArray<SpanRecord>, taxonomy: Taxonomy.Taxonomy): GraphDecoded;
154
153
  };
154
+ declare namespace Graph_d_exports {
155
+ export { AttributeValue, Attributes, GraphDecoded, GraphNode, SpanEvent, SpanLink, SpanRecord, Status, TraceGraph, byId, children, decode, descendants };
156
+ }
155
157
  //#endregion
156
158
  //#region src/TransportObservationError.schema.d.ts
157
159
  declare const TransportObservationError_base: Schema.Class<TransportObservationError, Schema.TaggedStruct<"TransportObservationError", {
package/dist/index.mjs CHANGED
@@ -1,18 +1,19 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
2
  import { callFrameOutside, withRaisingFrame } from "@systemfsoftware/vitest/failure";
3
- import { Array as Array$1, Cause, Clock, Context, Duration, Effect, Encoding, Equal, FileSystem, Layer, Match, Option, Predicate, Random, Ref, Result, Schema, SchemaGetter, SchemaTransformation } from "effect";
4
- import { dual } from "effect/Function";
3
+ import { Array as Array$1, Cause, Clock, Context, Duration, Effect, Equal, FileSystem, Layer, Match, Option, Predicate, Random, Ref, Result, Schema, SchemaGetter, SchemaTransformation } from "effect";
4
+ import { absurd, dual } from "effect/Function";
5
5
  import { Prototype } from "effect/Pipeable";
6
6
  import "effect/PlatformError";
7
7
  import "@systemfsoftware/trace-taxonomy";
8
8
  import "effect/Array";
9
- import "effect/Result";
10
- import * as HttpClient from "effect/unstable/http/HttpClient";
11
- import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
12
- import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse";
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";
13
15
  import * as OtelTracer from "@effect/opentelemetry/OtelTracer";
14
16
  import * as OtelResource from "@effect/opentelemetry/Resource";
15
- import { Blueprint, Handle } from "@systemfsoftware/effect-cell-types";
16
17
  import { SpanStatusCode } from "@opentelemetry/api";
17
18
  import { AlwaysOnSampler, BasicTracerProvider } from "@opentelemetry/sdk-trace-base";
18
19
  import { Suite, TaskRef } from "@systemfsoftware/effect-spec-runtime";
@@ -46,64 +47,6 @@ var EmptyObservationError = class extends Schema.TaggedError()("EmptyObservation
46
47
  }
47
48
  };
48
49
  //#endregion
49
- //#region src/Verdict.schema.ts
50
- const Hold = Schema.TaggedStruct("Hold", {
51
- conjunct: Schema.String,
52
- inspected: Schema.Array(Schema.String)
53
- });
54
- const Break = Schema.TaggedStruct("Break", {
55
- conjunct: Schema.String,
56
- inspected: Schema.Array(Schema.String),
57
- detail: Schema.String
58
- });
59
- const Verdict = Schema.Union([Hold, Break]);
60
- //#endregion
61
- //#region src/FailureDump.ts
62
- const DEFAULT_DIRECTORY = "artifacts/traces";
63
- const isBreak$1 = Schema.is(Break);
64
- /**
65
- * The rendered evidence a break carries; a held verdict leaves nothing to write.
66
- */
67
- const report = (verdict) => Option.map(Option.liftPredicate(verdict, isBreak$1), renderBreach);
68
- const renderValue = (value) => JSON.stringify(value);
69
- const renderEntries = (entries) => [...entries].map(([key, value]) => `${key}=${renderValue(value)}`).join(" ");
70
- const indentOf = (depth) => " ".repeat(depth);
71
- const renderBreach = (breach) => `break ${breach.conjunct} inspected=[${breach.inspected.join(", ")}]\n${indentOf(1)}${breach.detail}`;
72
- const childrenOf = (spans, spanId) => spans.filter((span) => span.parentSpanId === spanId);
73
- const renderNode = (spans, node, depth) => {
74
- return [`${indentOf(depth)}${node.name} (${node.spanId}) status=${node.status} error.type=${String(node.errorType)} duration=${node.durationMillis}ms`, ...[
75
- `${indentOf(depth)} attrs: ${renderEntries(Object.entries(node.attributes))}`,
76
- ...node.events.map((event) => `${indentOf(depth)} event ${event.name} ${renderEntries(Object.entries(event.attributes))}`),
77
- ...node.links.map((link) => `${indentOf(depth)} link ${link.traceId}/${link.spanId}`),
78
- ...childrenOf(spans, node.spanId).map((child) => renderNode(spans, child, depth + 1))
79
- ]].join("\n");
80
- };
81
- const documentImpl = (observed, report) => [
82
- `trace ${observed.traceId}`,
83
- report,
84
- ...observed.spans.map((node) => renderNode(observed.spans, node, 0))
85
- ].join("\n");
86
- /**
87
- * The dump document: the trace it belongs to, the break that refused it, and the recorded
88
- * spans as the observation window saw them.
89
- */
90
- const document = dual(2, documentImpl);
91
- const sanitize = (value) => value.replace(/[^a-zA-Z0-9._-]+/g, "-");
92
- const fileNameOf = (request) => `${sanitize(request.name ?? request.traceId)}.${sanitize(request.conjunct)}.txt`;
93
- /**
94
- * Writes the dump under the artifacts directory and answers with its path. The file is
95
- * named after the request's name when one is given — a generated case names its dump after
96
- * itself, so every failing draw overwrites one file and the last write is the reported
97
- * counterexample.
98
- */
99
- const write = (request) => Effect.gen(function* () {
100
- const fs = yield* FileSystem.FileSystem;
101
- yield* fs.makeDirectory(DEFAULT_DIRECTORY, { recursive: true });
102
- const path = `${DEFAULT_DIRECTORY}/${fileNameOf(request)}`;
103
- yield* fs.writeFileString(path, document(request, request.report));
104
- return path;
105
- });
106
- //#endregion
107
50
  //#region src/TraceGraph.schema.ts
108
51
  const Status = Schema.Literals([
109
52
  "ok",
@@ -159,22 +102,6 @@ const TraceGraph = Schema.Struct({
159
102
  traceId: Schema.String,
160
103
  nodes: Schema.Array(GraphNode)
161
104
  });
162
- //#endregion
163
- //#region src/Graph.ts
164
- var Graph_exports = /* @__PURE__ */ __exportAll({
165
- AttributeValue: () => AttributeValue,
166
- Attributes: () => Attributes,
167
- GraphNode: () => GraphNode,
168
- SpanEvent: () => SpanEvent,
169
- SpanLink: () => SpanLink,
170
- SpanRecord: () => SpanRecord,
171
- Status: () => Status,
172
- TraceGraph: () => TraceGraph,
173
- byId: () => byId,
174
- children: () => children,
175
- decode: () => decode,
176
- descendants: () => descendants
177
- });
178
105
  const byIdImpl = (graph, spec) => graph.nodes.filter((node) => node.name === spec.name);
179
106
  const byId = dual(2, byIdImpl);
180
107
  const childrenImpl = (graph, node) => graph.nodes.filter((candidate) => candidate.parentSpanId === node.spanId);
@@ -204,7 +131,7 @@ const decodeFailure = (spec, record, detail, attribute) => ContractDecodeError.m
204
131
  attribute: attribute ?? "*",
205
132
  detail
206
133
  });
207
- const decodeAttrs = (spec, record) => Result.mapError(Schema.decodeResult(Schema.toType(spec.attrs))(record.attributes), (error) => decodeFailure(spec, record, error.message, attributeNameOf(error.issue)));
134
+ const decodeAttrs = (spec, record) => Result$1.mapError(Schema.decodeResult(Schema.toType(spec.attrs))(record.attributes), (error) => decodeFailure(spec, record, error.message, attributeNameOf(error.issue)));
208
135
  const nodeOf = (record, attrs) => ({
209
136
  spanId: record.spanId,
210
137
  parentSpanId: record.parentSpanId,
@@ -217,10 +144,10 @@ const nodeOf = (record, attrs) => ({
217
144
  events: record.events,
218
145
  links: record.links
219
146
  });
220
- const decodedNodeOf = (record, spec) => Result.map(decodeAttrs(spec, record), (attrs) => nodeOf(record, attrs));
147
+ const decodedNodeOf = (record, spec) => Result$1.map(decodeAttrs(spec, record), (attrs) => nodeOf(record, attrs));
221
148
  const declaredNodeOf = (record, declarations) => {
222
149
  const spec = declarations.get(record.name);
223
- return spec === void 0 ? Result.succeed(nodeOf(record, record.attributes)) : decodedNodeOf(record, spec);
150
+ return spec === void 0 ? Result$1.succeed(nodeOf(record, record.attributes)) : decodedNodeOf(record, spec);
224
151
  };
225
152
  const graphOf = (traceId, nodes) => ({
226
153
  traceId,
@@ -228,7 +155,7 @@ const graphOf = (traceId, nodes) => ({
228
155
  });
229
156
  const decodeImpl = (traceId, spans, taxonomy) => {
230
157
  const declarations = indexDeclarations(taxonomy);
231
- return Result.map(Result.all(spans.map((record) => declaredNodeOf(record, declarations))), (nodes) => graphOf(traceId, nodes));
158
+ return Result$1.map(Result$1.all(spans.map((record) => declaredNodeOf(record, declarations))), (nodes) => graphOf(traceId, nodes));
232
159
  };
233
160
  /**
234
161
  * Decodes the recorded spans of one trace against a taxonomy, purely: a declared span
@@ -238,6 +165,56 @@ const decodeImpl = (traceId, spans, taxonomy) => {
238
165
  * fail the pipeline by accident.
239
166
  */
240
167
  const decode = dual(3, decodeImpl);
168
+ Schema.Struct({
169
+ traceId: Schema.String,
170
+ spans: Schema.Array(SpanRecord)
171
+ });
172
+ const renderValue = (value) => JSON.stringify(value);
173
+ const renderEntries = (entries) => [...entries].map(([key, value]) => `${key}=${renderValue(value)}`).join(" ");
174
+ const indentOf$1 = (depth) => " ".repeat(depth);
175
+ const childrenOf = (spans, spanId) => spans.filter((span) => span.parentSpanId === spanId);
176
+ const renderNode = (spans, node, depth) => {
177
+ return [`${indentOf$1(depth)}${node.name} (${node.spanId}) status=${node.status} error.type=${String(node.errorType)} duration=${node.durationMillis}ms`, ...[
178
+ `${indentOf$1(depth)} attrs: ${renderEntries(Object.entries(node.attributes))}`,
179
+ ...node.events.map((event) => `${indentOf$1(depth)} event ${event.name} ${renderEntries(Object.entries(event.attributes))}`),
180
+ ...node.links.map((link) => `${indentOf$1(depth)} link ${link.traceId}/${link.spanId}`),
181
+ ...childrenOf(spans, node.spanId).map((child) => renderNode(spans, child, depth + 1))
182
+ ]].join("\n");
183
+ };
184
+ const documentImpl = (observed, report) => [
185
+ `trace ${observed.traceId}`,
186
+ report,
187
+ ...observed.spans.map((node) => renderNode(observed.spans, node, 0))
188
+ ].join("\n");
189
+ const document = dual(2, documentImpl);
190
+ //#endregion
191
+ //#region src/FailureDump.ts
192
+ const DEFAULT_DIRECTORY = "artifacts/traces";
193
+ const sanitize = (value) => value.replace(/[^a-zA-Z0-9._-]+/g, "-");
194
+ const fileNameOf = (request) => `${sanitize(request.name ?? request.traceId)}.${sanitize(request.conjunct)}.txt`;
195
+ const write = (request) => Effect.gen(function* () {
196
+ const fs = yield* FileSystem.FileSystem;
197
+ yield* fs.makeDirectory(DEFAULT_DIRECTORY, { recursive: true });
198
+ const path = `${DEFAULT_DIRECTORY}/${fileNameOf(request)}`;
199
+ yield* fs.writeFileString(path, document(request, request.report));
200
+ return path;
201
+ });
202
+ //#endregion
203
+ //#region src/Graph.ts
204
+ var Graph_exports = /* @__PURE__ */ __exportAll({
205
+ AttributeValue: () => AttributeValue,
206
+ Attributes: () => Attributes,
207
+ GraphNode: () => GraphNode,
208
+ SpanEvent: () => SpanEvent,
209
+ SpanLink: () => SpanLink,
210
+ SpanRecord: () => SpanRecord,
211
+ Status: () => Status,
212
+ TraceGraph: () => TraceGraph,
213
+ byId: () => byId,
214
+ children: () => children,
215
+ decode: () => decode,
216
+ descendants: () => descendants
217
+ });
241
218
  //#endregion
242
219
  //#region src/IncompleteObservationError.schema.ts
243
220
  const SpanCount = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0, { message: "a span count is a non-negative integer" })));
@@ -271,6 +248,22 @@ var Observation_service_exports = /* @__PURE__ */ __exportAll({
271
248
  });
272
249
  var Observation = class extends Context.Service()("@systemfsoftware/trace-spec/Observation") {};
273
250
  //#endregion
251
+ //#region src/Verdict.schema.ts
252
+ const Hold = Schema.TaggedStruct("Hold", {
253
+ conjunct: Schema.String,
254
+ inspected: Schema.Array(Schema.String)
255
+ });
256
+ const Break = Schema.TaggedStruct("Break", {
257
+ conjunct: Schema.String,
258
+ inspected: Schema.Array(Schema.String),
259
+ detail: Schema.String
260
+ });
261
+ const Verdict = Schema.Union([Hold, Break]);
262
+ const isBreak = Schema.is(Break);
263
+ const indentOf = (depth) => " ".repeat(depth);
264
+ const renderBreach = (breach) => `break ${breach.conjunct} inspected=[${breach.inspected.join(", ")}]\n${indentOf(1)}${breach.detail}`;
265
+ const report = (verdict) => Option.map(Option.liftPredicate(verdict, isBreak), renderBreach);
266
+ //#endregion
274
267
  //#region src/Contract.ts
275
268
  var Contract_exports = /* @__PURE__ */ __exportAll({
276
269
  BreakFailure: () => BreakFailure,
@@ -280,7 +273,7 @@ var Contract_exports = /* @__PURE__ */ __exportAll({
280
273
  TransportObservationError: () => TransportObservationError,
281
274
  TypeId: () => TypeId$3,
282
275
  check: () => check,
283
- holds: () => holds,
276
+ holds: () => holds$1,
284
277
  judge: () => judge,
285
278
  of: () => of,
286
279
  stimulate: () => stimulate,
@@ -312,14 +305,14 @@ const of = (taxonomy) => ({
312
305
  const stimulateDual = (self, stimulus) => stimulatedOf(self.taxonomy, stimulus);
313
306
  const stimulate = dual(2, stimulateDual);
314
307
  const holdsDual = (self, relation) => contractOf(self.taxonomy, self.stimulus, relation);
315
- const holds = dual(2, holdsDual);
308
+ const holds$1 = dual(2, holdsDual);
316
309
  const isContract = (value) => Predicate.hasProperty(value, TypeId$3);
317
310
  const dumpOf = (observed, verdict, options) => Option.match(report(verdict), {
318
311
  onNone: () => Effect.succeed(null),
319
- onSome: (report) => Effect.map(Effect.option(write({
312
+ onSome: (rendered) => Effect.map(Effect.option(write({
320
313
  ...observed,
321
314
  conjunct: verdict.conjunct,
322
- report,
315
+ report: rendered,
323
316
  name: options?.dumpName
324
317
  })), Option.getOrNull)
325
318
  });
@@ -355,8 +348,8 @@ const reportOf = (relationId, judgment) => Match.value(judgment.verdict).pipe(Ma
355
348
  ].join("\n")
356
349
  })), Match.exhaustive);
357
350
  const verdictCheckWith = (self, expect, judgment, frame) => {
358
- const report = reportOf(self.relation.id, judgment);
359
- return report.verdict === "Break" ? Effect.die(withRaisingFrame(failureOfReport(report), frame)) : expect(report, report.report).toEqual(held);
351
+ const finding = reportOf(self.relation.id, judgment);
352
+ return finding.verdict === "Break" ? Effect.die(withRaisingFrame(failureOfReport(finding), frame)) : expect(finding, finding.report).toEqual(held);
360
353
  };
361
354
  const verdictCheckImpl = (self, expect, judgment) => verdictCheckWith(self, expect, judgment, callFrame());
362
355
  /**
@@ -374,10 +367,54 @@ const checkDual = (self, expect, input, options) => {
374
367
  */
375
368
  const check = dual((args) => isContract(args[0]), checkDual);
376
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
377
414
  //#region src/drivers/tempo-trace.schema.ts
378
415
  /** Proto bytes (base64 on the wire) as the lowercase hex ids traces are addressed by. */
379
416
  const HexId = Schema.Uint8ArrayFromBase64.pipe(Schema.decodeTo(Schema.String, {
380
- decode: SchemaGetter.transform(Encoding.encodeHex),
417
+ decode: SchemaGetter.transform(Hex.encode),
381
418
  encode: SchemaGetter.decodeHex()
382
419
  }));
383
420
  const secondsAndNanos = (nanos) => {
@@ -550,32 +587,27 @@ var tempo_trace_store_exports = /* @__PURE__ */ __exportAll({ source: () => sour
550
587
  */
551
588
  const parseBody = Schema.decodeResult(Schema.fromJsonString(TempoV2TraceResponse));
552
589
  const orEmpty = (values) => values ?? [];
553
- const resourceSpansOf = (response) => orEmpty(response.trace.resourceSpans);
554
- const scopeSpansOf = (resourceSpans) => orEmpty(resourceSpans.scopeSpans);
555
- const spansOfScope = (scopeSpans) => orEmpty(scopeSpans.spans);
556
- const spansOf = (response) => resourceSpansOf(response).flatMap((resourceSpans) => scopeSpansOf(resourceSpans).flatMap(spansOfScope));
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
+ });
557
598
  const transportFailureOf = (traceId, url, cause) => new TransportObservationError({
558
599
  traceId,
559
600
  source: url,
560
601
  detail: cause
561
602
  });
562
603
  const refusedOf = (traceId, url, error) => transportFailureOf(traceId, url, error.message);
563
- const undecodableOf = (traceId, url, error) => transportFailureOf(traceId, url, `undecodable trace body: ${error.message}`);
604
+ const undecodableOf = (traceId, url, detail) => transportFailureOf(traceId, url, `undecodable trace body: ${detail}`);
564
605
  const incompleteOf = (traceId, status, spanCount) => new IncompleteObservationError({
565
606
  traceId,
566
607
  spanCount,
567
608
  detail: `the store answered ${status}: waiting cannot complete the trace`
568
609
  });
569
- const isComplete = (status) => status === void 0 || status === "COMPLETE";
570
- const markedOf = (response) => response.status ?? "INCOMPLETE";
571
- const recordsOf$1 = (traceId, response) => {
572
- const spans = spansOf(response);
573
- return isComplete(response.status) ? Effect.succeed(spans) : Effect.fail(incompleteOf(traceId, markedOf(response), spans.length));
574
- };
575
- const readTrace = (traceId, url, body) => {
576
- const decoded = parseBody(body);
577
- return Result.isFailure(decoded) ? Effect.fail(undecodableOf(traceId, url, decoded.failure)) : recordsOf$1(traceId, decoded.success);
578
- };
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));
579
611
  /** One read against one Tempo endpoint. Its only requirement is the caller's HTTP client. */
580
612
  const source = (options) => {
581
613
  const urlOf = (traceId) => `${options.baseUrl}/api/v2/traces/${traceId}`;
@@ -713,6 +745,199 @@ const ObservationWindows = Blueprint.make()(TypeId$1).steps({
713
745
  });
714
746
  const make$2 = (serviceName) => ObservationWindows.of(ObservationWindowSpec.make({ serviceName }));
715
747
  //#endregion
748
+ //#region src/combine-any-relations.workflow.ts
749
+ const CombineAnyRelationsTypeId = Symbol.for("@systemfsoftware/trace-spec/CombineAnyRelations");
750
+ /** At least one relation held, so the disjunction holds over every inspected span. */
751
+ var AnyRelationHeld = class extends Schema.TaggedClass()("AnyRelationHeld", {
752
+ conjunct: Schema.String,
753
+ inspected: Schema.Array(Schema.String)
754
+ }) {
755
+ [CombineAnyRelationsTypeId] = CombineAnyRelationsTypeId;
756
+ };
757
+ /** No relation held: every conjunct is named in the break. */
758
+ var NoRelationHeld = class extends Schema.TaggedClass()("NoRelationHeld", {
759
+ conjunct: Schema.String,
760
+ inspected: Schema.Array(Schema.String),
761
+ detail: Schema.String
762
+ }) {
763
+ [CombineAnyRelationsTypeId] = CombineAnyRelationsTypeId;
764
+ };
765
+ const CombineAnyRelationsDecision = Schema.Union([AnyRelationHeld, NoRelationHeld]);
766
+ var CombineAnyRelations = class extends Schema.TaggedClass()("CombineAnyRelations", {
767
+ conjunct: Schema.String,
768
+ verdicts: Schema.Array(Verdict)
769
+ }) {
770
+ static [Workflow.InstrumentationBrand] = {};
771
+ };
772
+ const holds = (verdict) => Match.value(verdict).pipe(Match.tag("Hold", () => true), Match.tag("Break", () => false), Match.exhaustive);
773
+ const inspectedOf$1 = (verdicts) => Array$1.flatMap(verdicts, (verdict) => verdict.inspected);
774
+ 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({
776
+ conjunct: command.conjunct,
777
+ inspected: inspectedOf$1(command.verdicts)
778
+ })), Match.when(false, () => new NoRelationHeld({
779
+ conjunct: command.conjunct,
780
+ inspected: inspectedOf$1(command.verdicts),
781
+ detail: namedConjuncts(command.verdicts)
782
+ })), Match.exhaustive));
783
+ /** `any(...)`: holds as soon as one relation holds, and otherwise breaks naming every conjunct. */
784
+ const combineAnyRelations = Workflow.make({
785
+ command: CombineAnyRelations,
786
+ decision: CombineAnyRelationsDecision,
787
+ error: Schema.Never,
788
+ decide: decide$4
789
+ });
790
+ //#endregion
791
+ //#region src/combine-relation-verdicts.workflow.ts
792
+ const CombineRelationVerdictsTypeId = Symbol.for("@systemfsoftware/trace-spec/CombineRelationVerdicts");
793
+ const EvaluatedRelation = Schema.Struct({
794
+ verdict: Verdict,
795
+ soft: Schema.Boolean
796
+ });
797
+ var FirstHardBreak = class extends Schema.TaggedClass()("FirstHardBreak", {
798
+ conjunct: Schema.String,
799
+ inspected: Schema.Array(Schema.String),
800
+ detail: Schema.String
801
+ }) {
802
+ [CombineRelationVerdictsTypeId] = CombineRelationVerdictsTypeId;
803
+ };
804
+ var AggregatedSoftBreak = class extends Schema.TaggedClass()("AggregatedSoftBreak", {
805
+ conjunct: Schema.String,
806
+ inspected: Schema.Array(Schema.String),
807
+ detail: Schema.String
808
+ }) {
809
+ [CombineRelationVerdictsTypeId] = CombineRelationVerdictsTypeId;
810
+ };
811
+ var EveryRelationHeld = class extends Schema.TaggedClass()("EveryRelationHeld", {
812
+ conjunct: Schema.String,
813
+ inspected: Schema.Array(Schema.String)
814
+ }) {
815
+ [CombineRelationVerdictsTypeId] = CombineRelationVerdictsTypeId;
816
+ };
817
+ const CombineRelationVerdictsDecision = Schema.Union([
818
+ FirstHardBreak,
819
+ AggregatedSoftBreak,
820
+ EveryRelationHeld
821
+ ]);
822
+ var CombineRelationVerdicts = class extends Schema.TaggedClass()("CombineRelationVerdicts", {
823
+ conjunct: Schema.String,
824
+ verdicts: Schema.Array(EvaluatedRelation)
825
+ }) {
826
+ static [Workflow.InstrumentationBrand] = {};
827
+ };
828
+ const asBreak = (entry) => Match.value(entry.verdict).pipe(Match.tag("Break", (breach) => Option.some(breach)), Match.tag("Hold", () => Option.none()), Match.exhaustive);
829
+ const breaksIn = (entries) => Array$1.flatMap(entries, (entry) => Option.match(asBreak(entry), {
830
+ onNone: () => [],
831
+ onSome: (breach) => [breach]
832
+ }));
833
+ const firstHardBreak = (verdicts) => Array$1.head(breaksIn(Array$1.filter(verdicts, (entry) => !entry.soft)));
834
+ const softBreaks = (verdicts) => breaksIn(Array$1.filter(verdicts, (entry) => entry.soft));
835
+ const aggregateSoft = (command) => {
836
+ const breaks = softBreaks(command.verdicts);
837
+ return Option.match(Array$1.head(breaks), {
838
+ onNone: () => new EveryRelationHeld({
839
+ conjunct: command.conjunct,
840
+ inspected: []
841
+ }),
842
+ onSome: () => new AggregatedSoftBreak({
843
+ conjunct: command.conjunct,
844
+ inspected: Array$1.flatMap(breaks, (breach) => breach.inspected),
845
+ detail: Array$1.join(Array$1.map(breaks, (breach) => breach.conjunct), ", ")
846
+ })
847
+ });
848
+ };
849
+ const decide$3 = (command) => Result$1.succeed(Option.match(firstHardBreak(command.verdicts), {
850
+ onNone: () => aggregateSoft(command),
851
+ onSome: (winner) => new FirstHardBreak({
852
+ conjunct: winner.conjunct,
853
+ inspected: winner.inspected,
854
+ detail: winner.detail
855
+ })
856
+ }));
857
+ const combineRelationVerdicts = Workflow.make({
858
+ command: CombineRelationVerdicts,
859
+ decision: CombineRelationVerdictsDecision,
860
+ error: Schema.Never,
861
+ decide: decide$3
862
+ });
863
+ //#endregion
864
+ //#region src/judge-edge-placement.workflow.ts
865
+ const JudgeEdgePlacementTypeId = Symbol.for("@systemfsoftware/trace-spec/JudgeEdgePlacement");
866
+ var EdgePlacementHeld = class extends Schema.TaggedClass()("EdgePlacementHeld", {
867
+ conjunct: Schema.String,
868
+ inspected: Schema.Array(Schema.String)
869
+ }) {
870
+ [JudgeEdgePlacementTypeId] = JudgeEdgePlacementTypeId;
871
+ };
872
+ var EdgePlacementBroken = class extends Schema.TaggedClass()("EdgePlacementBroken", {
873
+ conjunct: Schema.String,
874
+ inspected: Schema.Array(Schema.String),
875
+ detail: Schema.String
876
+ }) {
877
+ [JudgeEdgePlacementTypeId] = JudgeEdgePlacementTypeId;
878
+ };
879
+ const JudgeEdgePlacementDecision = Schema.Union([EdgePlacementHeld, EdgePlacementBroken]);
880
+ var JudgeEdgePlacement = class extends Schema.TaggedClass()("JudgeEdgePlacement", {
881
+ conjunct: Schema.String,
882
+ placed: Schema.Array(Schema.String),
883
+ reached: Schema.Array(Schema.String),
884
+ detail: Schema.String
885
+ }) {
886
+ static [Workflow.InstrumentationBrand] = {};
887
+ };
888
+ 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({
890
+ conjunct: command.conjunct,
891
+ inspected: command.placed
892
+ })), Match.when(false, () => new EdgePlacementBroken({
893
+ conjunct: command.conjunct,
894
+ inspected: command.placed,
895
+ detail: command.detail
896
+ })), Match.exhaustive));
897
+ const judgeEdgePlacement = Workflow.make({
898
+ command: JudgeEdgePlacement,
899
+ decision: JudgeEdgePlacementDecision,
900
+ error: Schema.Never,
901
+ decide: decide$2
902
+ });
903
+ //#endregion
904
+ //#region src/negate-relation-verdict.workflow.ts
905
+ const NegateRelationVerdictTypeId = Symbol.for("@systemfsoftware/trace-spec/NegateRelationVerdict");
906
+ var HoldNegated = class extends Schema.TaggedClass()("HoldNegated", {
907
+ conjunct: Schema.String,
908
+ inspected: Schema.Array(Schema.String),
909
+ detail: Schema.String
910
+ }) {
911
+ [NegateRelationVerdictTypeId] = NegateRelationVerdictTypeId;
912
+ };
913
+ var BreakNegated = class extends Schema.TaggedClass()("BreakNegated", {
914
+ conjunct: Schema.String,
915
+ inspected: Schema.Array(Schema.String)
916
+ }) {
917
+ [NegateRelationVerdictTypeId] = NegateRelationVerdictTypeId;
918
+ };
919
+ const NegateRelationVerdictDecision = Schema.Union([HoldNegated, BreakNegated]);
920
+ var NegateRelationVerdict = class extends Schema.TaggedClass()("NegateRelationVerdict", {
921
+ conjunct: Schema.String,
922
+ verdict: Verdict
923
+ }) {
924
+ static [Workflow.InstrumentationBrand] = {};
925
+ };
926
+ const decide$1 = (command) => Result$1.succeed(Match.value(command.verdict).pipe(Match.tag("Hold", (held) => new HoldNegated({
927
+ conjunct: command.conjunct,
928
+ inspected: held.inspected,
929
+ detail: `${held.conjunct} held`
930
+ })), Match.tag("Break", (breach) => new BreakNegated({
931
+ conjunct: command.conjunct,
932
+ inspected: breach.inspected
933
+ })), Match.exhaustive));
934
+ const negateRelationVerdict = Workflow.make({
935
+ command: NegateRelationVerdict,
936
+ decision: NegateRelationVerdictDecision,
937
+ error: Schema.Never,
938
+ decide: decide$1
939
+ });
940
+ //#endregion
716
941
  //#region src/Rel.ts
717
942
  var Rel_exports = /* @__PURE__ */ __exportAll({
718
943
  Break: () => Break,
@@ -736,6 +961,10 @@ var Rel_exports = /* @__PURE__ */ __exportAll({
736
961
  status: () => status,
737
962
  unique: () => unique
738
963
  });
964
+ const decisionOf = (result) => Result.match(result, {
965
+ onFailure: (unreachable) => absurd(unreachable),
966
+ onSuccess: (decision) => decision
967
+ });
739
968
  const relation = (parts) => Object.assign((graph) => parts.evaluate(graph), {
740
969
  id: parts.id,
741
970
  soft: parts.soft,
@@ -744,9 +973,7 @@ const relation = (parts) => Object.assign((graph) => parts.evaluate(graph), {
744
973
  const matchedNodes = (graph, spec) => byId(graph, spec);
745
974
  const inspectedOf = (nodes) => nodes.map((node) => node.spanId);
746
975
  const matchedAll = (items, predicate) => items.length > 0 && items.every(predicate);
747
- const isBreak = (verdict) => Schema.is(Break)(verdict);
748
- const isHold$1 = (verdict) => Schema.is(Hold)(verdict);
749
- const verdictOf$1 = (options) => options.holds ? Hold.make({
976
+ const verdictOf = (options) => options.holds ? Hold.make({
750
977
  conjunct: options.id,
751
978
  inspected: options.inspected
752
979
  }) : Break.make({
@@ -760,7 +987,7 @@ const declared$1 = (declaration) => relation({
760
987
  softenable: declaration.softenable,
761
988
  evaluate: (graph) => {
762
989
  const inspected = declaration.spans.flatMap((span) => inspectedOf(byId(graph, span)));
763
- return verdictOf$1({
990
+ return verdictOf({
764
991
  id: declaration.id,
765
992
  inspected,
766
993
  holds: declaration.holds(graph),
@@ -873,7 +1100,7 @@ const forallImpl = (spec, predicate, detail) => {
873
1100
  softenable: true,
874
1101
  evaluate: (graph) => {
875
1102
  const nodes = matchedNodes(graph, spec);
876
- return verdictOf$1({
1103
+ return verdictOf({
877
1104
  id,
878
1105
  inspected: inspectedOf(nodes),
879
1106
  holds: matchedAll(nodes, predicate),
@@ -889,13 +1116,16 @@ const soft = (self) => self.softenable ? relation({
889
1116
  softenable: true,
890
1117
  evaluate: (graph) => self(graph)
891
1118
  }) : self;
892
- const negatedVerdict = (id, inner) => Match.value(inner).pipe(Match.tag("Hold", (held) => Break.make({
1119
+ const negatedVerdict = (id, inner) => Match.value(decisionOf(negateRelationVerdict(new NegateRelationVerdict({
893
1120
  conjunct: id,
1121
+ verdict: inner
1122
+ })))).pipe(Match.tag("HoldNegated", (held) => Break.make({
1123
+ conjunct: held.conjunct,
894
1124
  inspected: held.inspected,
895
- detail: `${held.conjunct} held`
896
- })), Match.tag("Break", (breach) => Hold.make({
897
- conjunct: id,
898
- inspected: breach.inspected
1125
+ detail: held.detail
1126
+ })), Match.tag("BreakNegated", (broken) => Hold.make({
1127
+ conjunct: broken.conjunct,
1128
+ inspected: broken.inspected
899
1129
  })), Match.exhaustive);
900
1130
  const not = (self) => {
901
1131
  const id = `not(${self.id})`;
@@ -906,24 +1136,29 @@ const not = (self) => {
906
1136
  evaluate: (graph) => negatedVerdict(id, self(graph))
907
1137
  });
908
1138
  };
909
- const collectSoft = (id, breaks) => {
910
- const inspected = breaks.flatMap((breach) => breach.inspected);
911
- return breaks.length === 0 ? Hold.make({
912
- conjunct: id,
913
- inspected
914
- }) : Break.make({
915
- conjunct: id,
916
- inspected,
917
- detail: breaks.map((breach) => breach.conjunct).join(", ")
918
- });
919
- };
920
- const firstHardBreak = (relations, graph) => relations.filter((relation) => !relation.soft).map((relation) => relation(graph)).find(isBreak) ?? null;
921
- const softBreaks = (relations, graph) => relations.filter((relation) => relation.soft).map((relation) => relation(graph)).filter(isBreak);
1139
+ const combinationOf = (conjunct, relations, graph) => Match.value(decisionOf(combineRelationVerdicts(new CombineRelationVerdicts({
1140
+ conjunct,
1141
+ verdicts: relations.map((relation) => ({
1142
+ verdict: relation(graph),
1143
+ soft: relation.soft
1144
+ }))
1145
+ })))).pipe(Match.tag("FirstHardBreak", (winner) => Break.make({
1146
+ conjunct: winner.conjunct,
1147
+ inspected: winner.inspected,
1148
+ detail: winner.detail
1149
+ })), Match.tag("AggregatedSoftBreak", (aggregated) => Break.make({
1150
+ conjunct: aggregated.conjunct,
1151
+ inspected: aggregated.inspected,
1152
+ detail: aggregated.detail
1153
+ })), Match.tag("EveryRelationHeld", (held) => Hold.make({
1154
+ conjunct: held.conjunct,
1155
+ inspected: held.inspected
1156
+ })), Match.exhaustive);
922
1157
  const combined = (options) => relation({
923
1158
  id: options.id,
924
1159
  soft: options.soft,
925
1160
  softenable: options.softenable,
926
- evaluate: (graph) => firstHardBreak(options.relations, graph) ?? collectSoft(options.id, softBreaks(options.relations, graph))
1161
+ evaluate: (graph) => combinationOf(options.id, options.relations, graph)
927
1162
  });
928
1163
  const all = (...relations) => combined({
929
1164
  id: `all(${relations.map((relation) => relation.id).join(", ")})`,
@@ -931,30 +1166,43 @@ const all = (...relations) => combined({
931
1166
  soft: relations.every((relation) => relation.soft),
932
1167
  softenable: true
933
1168
  });
1169
+ const disjunctionOf = (conjunct, verdicts) => Match.value(decisionOf(combineAnyRelations(new CombineAnyRelations({
1170
+ conjunct,
1171
+ verdicts
1172
+ })))).pipe(Match.tag("AnyRelationHeld", (held) => Hold.make({
1173
+ conjunct: held.conjunct,
1174
+ inspected: held.inspected
1175
+ })), Match.tag("NoRelationHeld", (broken) => Break.make({
1176
+ conjunct: broken.conjunct,
1177
+ inspected: broken.inspected,
1178
+ detail: broken.detail
1179
+ })), Match.exhaustive);
934
1180
  const any = (...relations) => {
935
1181
  const id = `any(${relations.map((relation) => relation.id).join(", ")})`;
936
1182
  return relation({
937
1183
  id,
938
1184
  soft: false,
939
1185
  softenable: false,
940
- evaluate: (graph) => {
941
- const verdicts = relations.map((relation) => relation(graph));
942
- const inspected = verdicts.flatMap((verdict) => verdict.inspected);
943
- return verdicts.some(isHold$1) ? Hold.make({
944
- conjunct: id,
945
- inspected
946
- }) : Break.make({
947
- conjunct: id,
948
- inspected,
949
- detail: verdicts.map((verdict) => verdict.conjunct).join(", ")
950
- });
951
- }
1186
+ evaluate: (graph) => disjunctionOf(id, relations.map((relation) => relation(graph)))
952
1187
  });
953
1188
  };
954
1189
  const REACH_BY_RELATION = {
955
1190
  child: childIdsOf,
956
1191
  descendant: descendantIdsOf
957
1192
  };
1193
+ const placementVerdictOf = (conjunct, placed, reached, detail) => Match.value(decisionOf(judgeEdgePlacement(new JudgeEdgePlacement({
1194
+ conjunct,
1195
+ placed,
1196
+ reached: [...reached],
1197
+ detail
1198
+ })))).pipe(Match.tag("EdgePlacementHeld", (held) => Hold.make({
1199
+ conjunct: held.conjunct,
1200
+ inspected: held.inspected
1201
+ })), Match.tag("EdgePlacementBroken", (broken) => Break.make({
1202
+ conjunct: broken.conjunct,
1203
+ inspected: broken.inspected,
1204
+ detail: broken.detail
1205
+ })), Match.exhaustive);
958
1206
  const placementOf = (edge) => {
959
1207
  const id = `placement(${edge.relation}:${edge.parent.id},${edge.child.id})`;
960
1208
  return relation({
@@ -962,14 +1210,9 @@ const placementOf = (edge) => {
962
1210
  soft: false,
963
1211
  softenable: false,
964
1212
  evaluate: (graph) => {
965
- const placed = matchedNodes(graph, edge.child);
1213
+ const placed = inspectedOf(matchedNodes(graph, edge.child));
966
1214
  const reached = REACH_BY_RELATION[edge.relation](graph, matchedNodes(graph, edge.parent));
967
- return verdictOf$1({
968
- id,
969
- inspected: inspectedOf(placed),
970
- holds: placed.every((node) => reached.has(node.spanId)),
971
- detail: `a ${edge.child.id} span is not a ${edge.relation} of any ${edge.parent.id} span`
972
- });
1215
+ return placementVerdictOf(id, placed, reached, `a ${edge.child.id} span is not a ${edge.relation} of any ${edge.parent.id} span`);
973
1216
  }
974
1217
  });
975
1218
  };
@@ -986,44 +1229,87 @@ const fromTaxonomyImpl = (taxonomy, options) => combined({
986
1229
  });
987
1230
  const fromTaxonomy = dual(2, fromTaxonomyImpl);
988
1231
  //#endregion
1232
+ //#region src/settle-remote-read.workflow.ts
1233
+ const SettleRemoteReadTypeId = Symbol.for("@systemfsoftware/trace-spec/SettleRemoteRead");
1234
+ const ObservedSpans = Schema.Struct({
1235
+ spans: Schema.Array(SpanRecord),
1236
+ addedAtMillis: Schema.Finite
1237
+ });
1238
+ var ReadSettled = class extends Schema.TaggedClass()("ReadSettled", { observation: ObservedSpans }) {
1239
+ [SettleRemoteReadTypeId] = SettleRemoteReadTypeId;
1240
+ };
1241
+ var ReadAbsent = class extends Schema.TaggedClass()("ReadAbsent", { observation: ObservedSpans }) {
1242
+ [SettleRemoteReadTypeId] = SettleRemoteReadTypeId;
1243
+ };
1244
+ var ReadUnfinished = class extends Schema.TaggedClass()("ReadUnfinished", { observation: ObservedSpans }) {
1245
+ [SettleRemoteReadTypeId] = SettleRemoteReadTypeId;
1246
+ };
1247
+ var ReadStillGrowing = class extends Schema.TaggedClass()("ReadStillGrowing", { observation: ObservedSpans }) {
1248
+ [SettleRemoteReadTypeId] = SettleRemoteReadTypeId;
1249
+ };
1250
+ const SettleRemoteReadDecision = Schema.Union([
1251
+ ReadSettled,
1252
+ ReadAbsent,
1253
+ ReadUnfinished,
1254
+ ReadStillGrowing
1255
+ ]);
1256
+ var SettleRemoteRead = class extends Schema.TaggedClass()("SettleRemoteRead", {
1257
+ observed: ObservedSpans,
1258
+ read: Schema.Array(SpanRecord),
1259
+ elapsedMillis: Schema.Finite,
1260
+ settleMillis: Schema.Finite,
1261
+ timeoutMillis: Schema.Finite
1262
+ }) {
1263
+ static [Workflow.InstrumentationBrand] = {};
1264
+ };
1265
+ const sameId = (left, right) => left.spanId === right.spanId;
1266
+ const firstOfEachId = (read) => Array$1.dedupeWith(read, sameId);
1267
+ const freshOf = (observed, read) => Array$1.filter(firstOfEachId(read), (span) => Array$1.every(observed.spans, (seen) => seen.spanId !== span.spanId));
1268
+ const absorbed = (observed, read, elapsedMillis) => {
1269
+ const fresh = freshOf(observed, read);
1270
+ return Option.match(Array$1.head(fresh), {
1271
+ onNone: () => observed,
1272
+ onSome: () => ({
1273
+ spans: [...observed.spans, ...fresh],
1274
+ addedAtMillis: elapsedMillis
1275
+ })
1276
+ });
1277
+ };
1278
+ 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));
1281
+ const settleRemoteRead = Workflow.make({
1282
+ command: SettleRemoteRead,
1283
+ decision: SettleRemoteReadDecision,
1284
+ error: Schema.Never,
1285
+ decide
1286
+ });
1287
+ //#endregion
989
1288
  //#region src/RemoteObservation.ts
990
1289
  var RemoteObservation_exports = /* @__PURE__ */ __exportAll({ layer: () => layer });
991
1290
  const NEVER_READ = {
992
1291
  spans: [],
993
1292
  addedAtMillis: 0
994
1293
  };
995
- const idOf = (span) => span.spanId;
996
- const sameId = (left, right) => left.spanId === right.spanId;
997
- const idsOf = (spans) => new Set(spans.map(idOf));
998
- const firstOfEachId = (read) => Array$1.dedupeWith(read, sameId);
999
- const freshOf = (settlement, read) => {
1000
- const known = idsOf(settlement.spans);
1001
- return firstOfEachId(read).filter((span) => !known.has(span.spanId));
1002
- };
1003
- const absorbed = (settlement, read, elapsedMillis) => {
1004
- const fresh = freshOf(settlement, read);
1005
- return fresh.length === 0 ? settlement : {
1006
- spans: [...settlement.spans, ...fresh],
1007
- addedAtMillis: elapsedMillis
1008
- };
1009
- };
1010
- const quietForLongEnough = (settlement, elapsedMillis, windows) => settlement.spans.length > 0 && elapsedMillis - settlement.addedAtMillis >= windows.settleMillis;
1294
+ const settlementOf = (command) => Result.match(settleRemoteRead(command), {
1295
+ onFailure: (unreachable) => absurd(unreachable),
1296
+ onSuccess: (decision) => decision
1297
+ });
1011
1298
  const pastDeadline = (elapsedMillis, windows) => elapsedMillis >= windows.timeoutMillis;
1012
1299
  const settledVerdict = (spans) => Option.some(Result.succeed(spans));
1013
1300
  const refusedVerdict = (refusal) => Option.some(Result.fail(refusal));
1014
- const verdictOf = (settlement, elapsedMillis, windows) => Match.value({
1015
- quiet: quietForLongEnough(settlement, elapsedMillis, windows),
1016
- vacant: settlement.spans.length === 0,
1017
- late: pastDeadline(elapsedMillis, windows)
1018
- }).pipe(Match.when({ quiet: true }, () => settledVerdict(settlement.spans)), Match.when({
1019
- vacant: true,
1020
- late: true
1021
- }, () => refusedVerdict("absent")), Match.when({ late: true }, () => refusedVerdict("unfinished")), Match.orElse(() => Option.none()));
1301
+ const verdictOfOutcome = (outcome) => Match.value(outcome).pipe(Match.tag("ReadSettled", (settled) => settledVerdict(settled.observation.spans)), Match.tag("ReadAbsent", () => refusedVerdict("absent")), Match.tag("ReadUnfinished", () => refusedVerdict("unfinished")), Match.tag("ReadStillGrowing", () => Option.none()), Match.exhaustive);
1022
1302
  const settleStep = (settlement, read, elapsedMillis, windows) => {
1023
- const advanced = absorbed(settlement, read, elapsedMillis);
1303
+ const outcome = settlementOf(new SettleRemoteRead({
1304
+ observed: settlement,
1305
+ read,
1306
+ elapsedMillis,
1307
+ settleMillis: windows.settleMillis,
1308
+ timeoutMillis: windows.timeoutMillis
1309
+ }));
1024
1310
  return {
1025
- settlement: advanced,
1026
- verdict: verdictOf(advanced, elapsedMillis, windows)
1311
+ settlement: outcome.observation,
1312
+ verdict: verdictOfOutcome(outcome)
1027
1313
  };
1028
1314
  };
1029
1315
  const lateRefusal = (settlement) => settlement.spans.length === 0 ? "absent" : "unfinished";
@@ -1147,6 +1433,17 @@ const HarnessFailure = Schema.Union([
1147
1433
  TransportObservationError
1148
1434
  ]);
1149
1435
  //#endregion
1436
+ //#region src/StimulusFailure.schema.ts
1437
+ const firstLine = (text) => text.split("\n")[0] ?? "";
1438
+ var StimulusFailure = class extends Schema.TaggedError()("StimulusFailure", {
1439
+ stimulus: Schema.String,
1440
+ detail: Schema.String
1441
+ }) {
1442
+ get message() {
1443
+ return `The stimulus "${this.stimulus}" failed: ${firstLine(this.detail)}`;
1444
+ }
1445
+ };
1446
+ //#endregion
1150
1447
  //#region src/TaskAnnounce.ts
1151
1448
  const forget = (annotation) => {
1152
1449
  Promise.resolve(annotation);
@@ -1169,40 +1466,6 @@ const announceDump = (judged) => Option.match(dumpMessageOf(judged), {
1169
1466
  onSome: (message) => record(message, "info")
1170
1467
  });
1171
1468
  //#endregion
1172
- //#region src/Prop.ts
1173
- const isHold = Schema.is(Hold);
1174
- /**
1175
- * The contract with its stimulus replaced by the function the property was handed: the impostor gate
1176
- * swaps in a constant fake, so the judgement must reach the stimulus through the argument, never the
1177
- * closure.
1178
- */
1179
- const judgedOver = (contract, subject) => ({
1180
- ...contract,
1181
- stimulus: subject
1182
- });
1183
- /**
1184
- * The run must be the run of the generated input: a stimulus that ignores its input and answers with one
1185
- * frozen run satisfies every relation about that run, so the input pin is what refutes it.
1186
- */
1187
- const consumedInput = (judgment, input) => judgment.run.input === input;
1188
- const predicateImpl = (title, contract, scenario, shared) => {
1189
- const provided = scenario.pipe(Layer.provideMerge(shared));
1190
- const checked = (subject, input) => judge(judgedOver(contract, subject), input, { dumpName: title }).pipe(Effect.tap(announceDump), Effect.map((judgment) => isHold(judgment.verdict) && consumedInput(judgment, input)), Effect.provide(Layer.fresh(provided)));
1191
- return (subject, input) => checked(subject, input);
1192
- };
1193
- const predicate = dual(4, predicateImpl);
1194
- //#endregion
1195
- //#region src/StimulusFailure.schema.ts
1196
- const firstLine = (text) => text.split("\n")[0] ?? "";
1197
- var StimulusFailure = class extends Schema.TaggedError()("StimulusFailure", {
1198
- stimulus: Schema.String,
1199
- detail: Schema.String
1200
- }) {
1201
- get message() {
1202
- return `The stimulus "${this.stimulus}" failed: ${firstLine(this.detail)}`;
1203
- }
1204
- };
1205
- //#endregion
1206
1469
  //#region src/Suite.ts
1207
1470
  var Suite_exports = /* @__PURE__ */ __exportAll({
1208
1471
  StimulusFailure: () => StimulusFailure,
@@ -1230,6 +1493,17 @@ const caseFailureOf = (stimulus, salt) => (failure) => isHarnessFailure(failure)
1230
1493
  detail: `${Cause.pretty(Cause.fail(failure))}\nrandom salt: ${salt}`
1231
1494
  });
1232
1495
  const caseBody = (expect, contract, input, salt) => judge(contract, input).pipe(Effect.tap(announceDump), Effect.flatMap((judgment) => verdictCheck(contract, expect, judgment)), Effect.mapError(caseFailureOf(contract.stimulus.name, salt)));
1496
+ const isHold = Schema.is(Hold);
1497
+ const judgedOver = (contract, subject) => ({
1498
+ ...contract,
1499
+ stimulus: subject
1500
+ });
1501
+ const consumedInput = (judgment, input) => judgment.run.input === input;
1502
+ const casePredicate = (title, contract, scenario, shared) => {
1503
+ const provided = scenario.pipe(Layer.provideMerge(shared));
1504
+ const checked = (subject, input) => judge(judgedOver(contract, subject), input, { dumpName: title }).pipe(Effect.tap(announceDump), Effect.map((judgment) => isHold(judgment.verdict) && consumedInput(judgment, input)), Effect.provide(Layer.fresh(provided)));
1505
+ return (subject, input) => checked(subject, input);
1506
+ };
1233
1507
  const caseTools = (register, propIt, scenario, shared) => {
1234
1508
  const Case = (name, contract, input) => {
1235
1509
  const salt = saltOf();
@@ -1240,7 +1514,7 @@ const caseTools = (register, propIt, scenario, shared) => {
1240
1514
  propIt.effect.prop(name, {
1241
1515
  of: [arbitrary],
1242
1516
  subject: contract.stimulus
1243
- }, (subject, values) => predicate(name, contract, scenario, shared)(subject, values[0]).pipe(Random.withSeed(salt)));
1517
+ }, (subject, values) => casePredicate(name, contract, scenario, shared)(subject, values[0]).pipe(Random.withSeed(salt)));
1244
1518
  };
1245
1519
  return { Case };
1246
1520
  };
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.5.1",
4
+ "version": "1.0.0",
5
5
  "author": "Ryan Lee <drdgvhbh@gmail.com>",
6
6
  "repository": {
7
7
  "type": "git",
@@ -33,27 +33,34 @@
33
33
  "dist"
34
34
  ],
35
35
  "dependencies": {
36
- "@effect/opentelemetry": "4.0.0-rc.117",
36
+ "@effect/opentelemetry": "^4",
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-cell-types": "^11.0.0",
42
- "@systemfsoftware/effect-spec-runtime": "^0.3.1",
43
- "@systemfsoftware/trace-taxonomy": "^0.0.2"
41
+ "@systemfsoftware/effect-cell-types": "^12.0.0",
42
+ "@systemfsoftware/effect-spec-runtime": "^1.0.0",
43
+ "@systemfsoftware/trace-taxonomy": "^1.0.0"
44
44
  },
45
45
  "devDependencies": {
46
- "@effect/platform-node": "4.0.0-rc.117",
46
+ "@effect/platform-node": "^4",
47
47
  "@systemfsoftware/arethetypeswrong-cli": "^4.2.0",
48
- "@systemfsoftware/conformance-spec": "^0.2.0",
49
- "@systemfsoftware/effect-gherkin-spec": "^6.0.0",
50
- "@systemfsoftware/oxlint-config-recommended": "^3.2.0",
48
+ "@systemfsoftware/conformance-spec": "^1.0.0",
49
+ "@systemfsoftware/effect-gherkin-spec": "^7.0.0",
50
+ "@systemfsoftware/oxlint-config-recommended": "^4.0.0",
51
+ "@systemfsoftware/stryker-config": "^0.1.0",
52
+ "@systemfsoftware/stryker-ignorer-effect-schema-declarations": "latest",
53
+ "@systemfsoftware/stryker-ignorer-in-source-vitest-block": "latest",
54
+ "@systemfsoftware/stryker-js": "latest",
55
+ "@systemfsoftware/stryker-js-typescript-checker": "latest",
56
+ "@systemfsoftware/stryker-js-vitest-runner": "latest",
57
+ "@systemfsoftware/stryker-test-contribution": "latest",
51
58
  "@systemfsoftware/tsconfig": "^2.0.1",
52
59
  "@systemfsoftware/tsdown-config": "^0.1.0",
53
- "@systemfsoftware/vitest": "^0.2.0",
60
+ "@systemfsoftware/vitest": "^1.0.0",
54
61
  "@systemfsoftware/vitest-config": "^0.1.0",
55
62
  "@types/node": "^26",
56
- "effect": "4.0.0-rc.117",
63
+ "effect": "^4",
57
64
  "oxlint": "~1.82.0",
58
65
  "rimraf": "^6.1.3",
59
66
  "tsdown": "^0.23.0",
@@ -61,8 +68,8 @@
61
68
  "vitest": "^5"
62
69
  },
63
70
  "peerDependencies": {
64
- "@systemfsoftware/vitest": "^0.2.0",
65
- "effect": "4.0.0-rc.117",
71
+ "@systemfsoftware/vitest": "^1.0.0",
72
+ "effect": "^4",
66
73
  "vitest": "^5"
67
74
  },
68
75
  "publishConfig": {
@@ -74,6 +81,8 @@
74
81
  "build": "tsdown -l warn",
75
82
  "typecheck": "tsc -b",
76
83
  "test": "vitest run",
84
+ "mutation": "stryker run",
85
+ "mutation:full": "stryker run --force",
77
86
  "test:types": "TSTYCHE_TYPESCRIPT_MODULE=tstyche-typescript tstyche",
78
87
  "lint": "f=${OXLINT_FORMAT:-${AGENT:+agent}}; oxlint . --format=${f:-default}",
79
88
  "attw": "attw --pack ."