@systemfsoftware/trace-spec 0.5.1 → 0.5.2

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,12 @@
1
1
  # @systemfsoftware/trace-spec
2
2
 
3
+ ## 0.5.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies:
8
+ - @systemfsoftware/vitest@0.3.0
9
+
3
10
  ## 0.5.1
4
11
 
5
12
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -1,9 +1,10 @@
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 Result$1 from "effect/Result";
4
5
  import * as HttpClient from "effect/unstable/http/HttpClient";
5
- import * as OtelTracer from "@effect/opentelemetry/OtelTracer";
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";
@@ -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,18 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
2
  import { callFrameOutside, withRaisingFrame } from "@systemfsoftware/vitest/failure";
3
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";
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";
9
+ import * as Result$1 from "effect/Result";
10
10
  import * as HttpClient from "effect/unstable/http/HttpClient";
11
11
  import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
12
12
  import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse";
13
+ import { Blueprint, Handle, Workflow } from "@systemfsoftware/effect-cell-types";
13
14
  import * as OtelTracer from "@effect/opentelemetry/OtelTracer";
14
15
  import * as OtelResource from "@effect/opentelemetry/Resource";
15
- import { Blueprint, Handle } from "@systemfsoftware/effect-cell-types";
16
16
  import { SpanStatusCode } from "@opentelemetry/api";
17
17
  import { AlwaysOnSampler, BasicTracerProvider } from "@opentelemetry/sdk-trace-base";
18
18
  import { Suite, TaskRef } from "@systemfsoftware/effect-spec-runtime";
@@ -46,64 +46,6 @@ var EmptyObservationError = class extends Schema.TaggedError()("EmptyObservation
46
46
  }
47
47
  };
48
48
  //#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
49
  //#region src/TraceGraph.schema.ts
108
50
  const Status = Schema.Literals([
109
51
  "ok",
@@ -159,22 +101,6 @@ const TraceGraph = Schema.Struct({
159
101
  traceId: Schema.String,
160
102
  nodes: Schema.Array(GraphNode)
161
103
  });
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
104
  const byIdImpl = (graph, spec) => graph.nodes.filter((node) => node.name === spec.name);
179
105
  const byId = dual(2, byIdImpl);
180
106
  const childrenImpl = (graph, node) => graph.nodes.filter((candidate) => candidate.parentSpanId === node.spanId);
@@ -204,7 +130,7 @@ const decodeFailure = (spec, record, detail, attribute) => ContractDecodeError.m
204
130
  attribute: attribute ?? "*",
205
131
  detail
206
132
  });
207
- const decodeAttrs = (spec, record) => Result.mapError(Schema.decodeResult(Schema.toType(spec.attrs))(record.attributes), (error) => decodeFailure(spec, record, error.message, attributeNameOf(error.issue)));
133
+ 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
134
  const nodeOf = (record, attrs) => ({
209
135
  spanId: record.spanId,
210
136
  parentSpanId: record.parentSpanId,
@@ -217,10 +143,10 @@ const nodeOf = (record, attrs) => ({
217
143
  events: record.events,
218
144
  links: record.links
219
145
  });
220
- const decodedNodeOf = (record, spec) => Result.map(decodeAttrs(spec, record), (attrs) => nodeOf(record, attrs));
146
+ const decodedNodeOf = (record, spec) => Result$1.map(decodeAttrs(spec, record), (attrs) => nodeOf(record, attrs));
221
147
  const declaredNodeOf = (record, declarations) => {
222
148
  const spec = declarations.get(record.name);
223
- return spec === void 0 ? Result.succeed(nodeOf(record, record.attributes)) : decodedNodeOf(record, spec);
149
+ return spec === void 0 ? Result$1.succeed(nodeOf(record, record.attributes)) : decodedNodeOf(record, spec);
224
150
  };
225
151
  const graphOf = (traceId, nodes) => ({
226
152
  traceId,
@@ -228,7 +154,7 @@ const graphOf = (traceId, nodes) => ({
228
154
  });
229
155
  const decodeImpl = (traceId, spans, taxonomy) => {
230
156
  const declarations = indexDeclarations(taxonomy);
231
- return Result.map(Result.all(spans.map((record) => declaredNodeOf(record, declarations))), (nodes) => graphOf(traceId, nodes));
157
+ return Result$1.map(Result$1.all(spans.map((record) => declaredNodeOf(record, declarations))), (nodes) => graphOf(traceId, nodes));
232
158
  };
233
159
  /**
234
160
  * Decodes the recorded spans of one trace against a taxonomy, purely: a declared span
@@ -238,6 +164,56 @@ const decodeImpl = (traceId, spans, taxonomy) => {
238
164
  * fail the pipeline by accident.
239
165
  */
240
166
  const decode = dual(3, decodeImpl);
167
+ Schema.Struct({
168
+ traceId: Schema.String,
169
+ spans: Schema.Array(SpanRecord)
170
+ });
171
+ const renderValue = (value) => JSON.stringify(value);
172
+ const renderEntries = (entries) => [...entries].map(([key, value]) => `${key}=${renderValue(value)}`).join(" ");
173
+ const indentOf$1 = (depth) => " ".repeat(depth);
174
+ const childrenOf = (spans, spanId) => spans.filter((span) => span.parentSpanId === spanId);
175
+ const renderNode = (spans, node, depth) => {
176
+ return [`${indentOf$1(depth)}${node.name} (${node.spanId}) status=${node.status} error.type=${String(node.errorType)} duration=${node.durationMillis}ms`, ...[
177
+ `${indentOf$1(depth)} attrs: ${renderEntries(Object.entries(node.attributes))}`,
178
+ ...node.events.map((event) => `${indentOf$1(depth)} event ${event.name} ${renderEntries(Object.entries(event.attributes))}`),
179
+ ...node.links.map((link) => `${indentOf$1(depth)} link ${link.traceId}/${link.spanId}`),
180
+ ...childrenOf(spans, node.spanId).map((child) => renderNode(spans, child, depth + 1))
181
+ ]].join("\n");
182
+ };
183
+ const documentImpl = (observed, report) => [
184
+ `trace ${observed.traceId}`,
185
+ report,
186
+ ...observed.spans.map((node) => renderNode(observed.spans, node, 0))
187
+ ].join("\n");
188
+ const document = dual(2, documentImpl);
189
+ //#endregion
190
+ //#region src/FailureDump.ts
191
+ const DEFAULT_DIRECTORY = "artifacts/traces";
192
+ const sanitize = (value) => value.replace(/[^a-zA-Z0-9._-]+/g, "-");
193
+ const fileNameOf = (request) => `${sanitize(request.name ?? request.traceId)}.${sanitize(request.conjunct)}.txt`;
194
+ const write = (request) => Effect.gen(function* () {
195
+ const fs = yield* FileSystem.FileSystem;
196
+ yield* fs.makeDirectory(DEFAULT_DIRECTORY, { recursive: true });
197
+ const path = `${DEFAULT_DIRECTORY}/${fileNameOf(request)}`;
198
+ yield* fs.writeFileString(path, document(request, request.report));
199
+ return path;
200
+ });
201
+ //#endregion
202
+ //#region src/Graph.ts
203
+ var Graph_exports = /* @__PURE__ */ __exportAll({
204
+ AttributeValue: () => AttributeValue,
205
+ Attributes: () => Attributes,
206
+ GraphNode: () => GraphNode,
207
+ SpanEvent: () => SpanEvent,
208
+ SpanLink: () => SpanLink,
209
+ SpanRecord: () => SpanRecord,
210
+ Status: () => Status,
211
+ TraceGraph: () => TraceGraph,
212
+ byId: () => byId,
213
+ children: () => children,
214
+ decode: () => decode,
215
+ descendants: () => descendants
216
+ });
241
217
  //#endregion
242
218
  //#region src/IncompleteObservationError.schema.ts
243
219
  const SpanCount = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0, { message: "a span count is a non-negative integer" })));
@@ -271,6 +247,22 @@ var Observation_service_exports = /* @__PURE__ */ __exportAll({
271
247
  });
272
248
  var Observation = class extends Context.Service()("@systemfsoftware/trace-spec/Observation") {};
273
249
  //#endregion
250
+ //#region src/Verdict.schema.ts
251
+ const Hold = Schema.TaggedStruct("Hold", {
252
+ conjunct: Schema.String,
253
+ inspected: Schema.Array(Schema.String)
254
+ });
255
+ const Break = Schema.TaggedStruct("Break", {
256
+ conjunct: Schema.String,
257
+ inspected: Schema.Array(Schema.String),
258
+ detail: Schema.String
259
+ });
260
+ const Verdict = Schema.Union([Hold, Break]);
261
+ const isBreak = Schema.is(Break);
262
+ const indentOf = (depth) => " ".repeat(depth);
263
+ const renderBreach = (breach) => `break ${breach.conjunct} inspected=[${breach.inspected.join(", ")}]\n${indentOf(1)}${breach.detail}`;
264
+ const report = (verdict) => Option.map(Option.liftPredicate(verdict, isBreak), renderBreach);
265
+ //#endregion
274
266
  //#region src/Contract.ts
275
267
  var Contract_exports = /* @__PURE__ */ __exportAll({
276
268
  BreakFailure: () => BreakFailure,
@@ -280,7 +272,7 @@ var Contract_exports = /* @__PURE__ */ __exportAll({
280
272
  TransportObservationError: () => TransportObservationError,
281
273
  TypeId: () => TypeId$3,
282
274
  check: () => check,
283
- holds: () => holds,
275
+ holds: () => holds$1,
284
276
  judge: () => judge,
285
277
  of: () => of,
286
278
  stimulate: () => stimulate,
@@ -312,14 +304,14 @@ const of = (taxonomy) => ({
312
304
  const stimulateDual = (self, stimulus) => stimulatedOf(self.taxonomy, stimulus);
313
305
  const stimulate = dual(2, stimulateDual);
314
306
  const holdsDual = (self, relation) => contractOf(self.taxonomy, self.stimulus, relation);
315
- const holds = dual(2, holdsDual);
307
+ const holds$1 = dual(2, holdsDual);
316
308
  const isContract = (value) => Predicate.hasProperty(value, TypeId$3);
317
309
  const dumpOf = (observed, verdict, options) => Option.match(report(verdict), {
318
310
  onNone: () => Effect.succeed(null),
319
- onSome: (report) => Effect.map(Effect.option(write({
311
+ onSome: (rendered) => Effect.map(Effect.option(write({
320
312
  ...observed,
321
313
  conjunct: verdict.conjunct,
322
- report,
314
+ report: rendered,
323
315
  name: options?.dumpName
324
316
  })), Option.getOrNull)
325
317
  });
@@ -355,8 +347,8 @@ const reportOf = (relationId, judgment) => Match.value(judgment.verdict).pipe(Ma
355
347
  ].join("\n")
356
348
  })), Match.exhaustive);
357
349
  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);
350
+ const finding = reportOf(self.relation.id, judgment);
351
+ return finding.verdict === "Break" ? Effect.die(withRaisingFrame(failureOfReport(finding), frame)) : expect(finding, finding.report).toEqual(held);
360
352
  };
361
353
  const verdictCheckImpl = (self, expect, judgment) => verdictCheckWith(self, expect, judgment, callFrame());
362
354
  /**
@@ -374,6 +366,50 @@ const checkDual = (self, expect, input, options) => {
374
366
  */
375
367
  const check = dual((args) => isContract(args[0]), checkDual);
376
368
  //#endregion
369
+ //#region src/drivers/judge-tempo-answer.workflow.ts
370
+ const JudgeTempoAnswerTypeId = Symbol.for("@systemfsoftware/trace-spec/JudgeTempoAnswer");
371
+ var TempoBodyDecoded = class extends Schema.TaggedClass()("TempoBodyDecoded", {
372
+ spans: Schema.Array(SpanRecord),
373
+ status: Schema.optional(Schema.String)
374
+ }) {};
375
+ var TempoBodyUndecodable = class extends Schema.TaggedClass()("TempoBodyUndecodable", { detail: Schema.String }) {};
376
+ const TempoBody = Schema.Union([TempoBodyDecoded, TempoBodyUndecodable]);
377
+ var TempoAnswerComplete = class extends Schema.TaggedClass()("TempoAnswerComplete", { spans: Schema.Array(SpanRecord) }) {
378
+ [JudgeTempoAnswerTypeId] = JudgeTempoAnswerTypeId;
379
+ };
380
+ var TempoAnswerIncomplete = class extends Schema.TaggedClass()("TempoAnswerIncomplete", {
381
+ status: Schema.String,
382
+ spanCount: Schema.Int
383
+ }) {
384
+ [JudgeTempoAnswerTypeId] = JudgeTempoAnswerTypeId;
385
+ };
386
+ var TempoAnswerUndecodable = class extends Schema.TaggedClass()("TempoAnswerUndecodable", { detail: Schema.String }) {
387
+ [JudgeTempoAnswerTypeId] = JudgeTempoAnswerTypeId;
388
+ };
389
+ const TempoAnswerDecision = Schema.Union([
390
+ TempoAnswerComplete,
391
+ TempoAnswerIncomplete,
392
+ TempoAnswerUndecodable
393
+ ]);
394
+ var JudgeTempoAnswer = class extends Schema.TaggedClass()("JudgeTempoAnswer", { body: TempoBody }) {
395
+ static [Workflow.InstrumentationBrand] = {};
396
+ };
397
+ const isComplete = (status) => Option.match(status, {
398
+ onNone: () => true,
399
+ onSome: (marked) => marked === "COMPLETE"
400
+ });
401
+ const classify$1 = (spans, status) => Match.value(isComplete(status)).pipe(Match.when(true, () => new TempoAnswerComplete({ spans })), Match.when(false, () => new TempoAnswerIncomplete({
402
+ status: Option.getOrElse(status, () => "INCOMPLETE"),
403
+ spanCount: spans.length
404
+ })), Match.exhaustive);
405
+ 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));
406
+ const judgeTempoAnswer = Workflow.make({
407
+ command: JudgeTempoAnswer,
408
+ decision: TempoAnswerDecision,
409
+ error: Schema.Never,
410
+ decide: decide$5
411
+ });
412
+ //#endregion
377
413
  //#region src/drivers/tempo-trace.schema.ts
378
414
  /** Proto bytes (base64 on the wire) as the lowercase hex ids traces are addressed by. */
379
415
  const HexId = Schema.Uint8ArrayFromBase64.pipe(Schema.decodeTo(Schema.String, {
@@ -550,32 +586,27 @@ var tempo_trace_store_exports = /* @__PURE__ */ __exportAll({ source: () => sour
550
586
  */
551
587
  const parseBody = Schema.decodeResult(Schema.fromJsonString(TempoV2TraceResponse));
552
588
  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));
589
+ const spansOf = (response) => orEmpty(response.trace.resourceSpans).flatMap((resourceSpans) => orEmpty(resourceSpans.scopeSpans).flatMap((scopeSpans) => orEmpty(scopeSpans.spans)));
590
+ const bodyOf = (decoded) => Result.match(decoded, {
591
+ onFailure: (error) => new TempoBodyUndecodable({ detail: error.message }),
592
+ onSuccess: (response) => new TempoBodyDecoded({
593
+ spans: spansOf(response),
594
+ status: response.status
595
+ })
596
+ });
557
597
  const transportFailureOf = (traceId, url, cause) => new TransportObservationError({
558
598
  traceId,
559
599
  source: url,
560
600
  detail: cause
561
601
  });
562
602
  const refusedOf = (traceId, url, error) => transportFailureOf(traceId, url, error.message);
563
- const undecodableOf = (traceId, url, error) => transportFailureOf(traceId, url, `undecodable trace body: ${error.message}`);
603
+ const undecodableOf = (traceId, url, detail) => transportFailureOf(traceId, url, `undecodable trace body: ${detail}`);
564
604
  const incompleteOf = (traceId, status, spanCount) => new IncompleteObservationError({
565
605
  traceId,
566
606
  spanCount,
567
607
  detail: `the store answered ${status}: waiting cannot complete the trace`
568
608
  });
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
- };
609
+ 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
610
  /** One read against one Tempo endpoint. Its only requirement is the caller's HTTP client. */
580
611
  const source = (options) => {
581
612
  const urlOf = (traceId) => `${options.baseUrl}/api/v2/traces/${traceId}`;
@@ -713,6 +744,199 @@ const ObservationWindows = Blueprint.make()(TypeId$1).steps({
713
744
  });
714
745
  const make$2 = (serviceName) => ObservationWindows.of(ObservationWindowSpec.make({ serviceName }));
715
746
  //#endregion
747
+ //#region src/combine-any-relations.workflow.ts
748
+ const CombineAnyRelationsTypeId = Symbol.for("@systemfsoftware/trace-spec/CombineAnyRelations");
749
+ /** At least one relation held, so the disjunction holds over every inspected span. */
750
+ var AnyRelationHeld = class extends Schema.TaggedClass()("AnyRelationHeld", {
751
+ conjunct: Schema.String,
752
+ inspected: Schema.Array(Schema.String)
753
+ }) {
754
+ [CombineAnyRelationsTypeId] = CombineAnyRelationsTypeId;
755
+ };
756
+ /** No relation held: every conjunct is named in the break. */
757
+ var NoRelationHeld = class extends Schema.TaggedClass()("NoRelationHeld", {
758
+ conjunct: Schema.String,
759
+ inspected: Schema.Array(Schema.String),
760
+ detail: Schema.String
761
+ }) {
762
+ [CombineAnyRelationsTypeId] = CombineAnyRelationsTypeId;
763
+ };
764
+ const CombineAnyRelationsDecision = Schema.Union([AnyRelationHeld, NoRelationHeld]);
765
+ var CombineAnyRelations = class extends Schema.TaggedClass()("CombineAnyRelations", {
766
+ conjunct: Schema.String,
767
+ verdicts: Schema.Array(Verdict)
768
+ }) {
769
+ static [Workflow.InstrumentationBrand] = {};
770
+ };
771
+ const holds = (verdict) => Match.value(verdict).pipe(Match.tag("Hold", () => true), Match.tag("Break", () => false), Match.exhaustive);
772
+ const inspectedOf$1 = (verdicts) => Array$1.flatMap(verdicts, (verdict) => verdict.inspected);
773
+ const namedConjuncts = (verdicts) => Array$1.join(Array$1.map(verdicts, (verdict) => verdict.conjunct), ", ");
774
+ const decide$4 = (command) => Result$1.succeed(Match.value(Array$1.some(command.verdicts, holds)).pipe(Match.when(true, () => new AnyRelationHeld({
775
+ conjunct: command.conjunct,
776
+ inspected: inspectedOf$1(command.verdicts)
777
+ })), Match.when(false, () => new NoRelationHeld({
778
+ conjunct: command.conjunct,
779
+ inspected: inspectedOf$1(command.verdicts),
780
+ detail: namedConjuncts(command.verdicts)
781
+ })), Match.exhaustive));
782
+ /** `any(...)`: holds as soon as one relation holds, and otherwise breaks naming every conjunct. */
783
+ const combineAnyRelations = Workflow.make({
784
+ command: CombineAnyRelations,
785
+ decision: CombineAnyRelationsDecision,
786
+ error: Schema.Never,
787
+ decide: decide$4
788
+ });
789
+ //#endregion
790
+ //#region src/combine-relation-verdicts.workflow.ts
791
+ const CombineRelationVerdictsTypeId = Symbol.for("@systemfsoftware/trace-spec/CombineRelationVerdicts");
792
+ const EvaluatedRelation = Schema.Struct({
793
+ verdict: Verdict,
794
+ soft: Schema.Boolean
795
+ });
796
+ var FirstHardBreak = class extends Schema.TaggedClass()("FirstHardBreak", {
797
+ conjunct: Schema.String,
798
+ inspected: Schema.Array(Schema.String),
799
+ detail: Schema.String
800
+ }) {
801
+ [CombineRelationVerdictsTypeId] = CombineRelationVerdictsTypeId;
802
+ };
803
+ var AggregatedSoftBreak = class extends Schema.TaggedClass()("AggregatedSoftBreak", {
804
+ conjunct: Schema.String,
805
+ inspected: Schema.Array(Schema.String),
806
+ detail: Schema.String
807
+ }) {
808
+ [CombineRelationVerdictsTypeId] = CombineRelationVerdictsTypeId;
809
+ };
810
+ var EveryRelationHeld = class extends Schema.TaggedClass()("EveryRelationHeld", {
811
+ conjunct: Schema.String,
812
+ inspected: Schema.Array(Schema.String)
813
+ }) {
814
+ [CombineRelationVerdictsTypeId] = CombineRelationVerdictsTypeId;
815
+ };
816
+ const CombineRelationVerdictsDecision = Schema.Union([
817
+ FirstHardBreak,
818
+ AggregatedSoftBreak,
819
+ EveryRelationHeld
820
+ ]);
821
+ var CombineRelationVerdicts = class extends Schema.TaggedClass()("CombineRelationVerdicts", {
822
+ conjunct: Schema.String,
823
+ verdicts: Schema.Array(EvaluatedRelation)
824
+ }) {
825
+ static [Workflow.InstrumentationBrand] = {};
826
+ };
827
+ const asBreak = (entry) => Match.value(entry.verdict).pipe(Match.tag("Break", (breach) => Option.some(breach)), Match.tag("Hold", () => Option.none()), Match.exhaustive);
828
+ const breaksIn = (entries) => Array$1.flatMap(entries, (entry) => Option.match(asBreak(entry), {
829
+ onNone: () => [],
830
+ onSome: (breach) => [breach]
831
+ }));
832
+ const firstHardBreak = (verdicts) => Array$1.head(breaksIn(Array$1.filter(verdicts, (entry) => !entry.soft)));
833
+ const softBreaks = (verdicts) => breaksIn(Array$1.filter(verdicts, (entry) => entry.soft));
834
+ const aggregateSoft = (command) => {
835
+ const breaks = softBreaks(command.verdicts);
836
+ return Option.match(Array$1.head(breaks), {
837
+ onNone: () => new EveryRelationHeld({
838
+ conjunct: command.conjunct,
839
+ inspected: []
840
+ }),
841
+ onSome: () => new AggregatedSoftBreak({
842
+ conjunct: command.conjunct,
843
+ inspected: Array$1.flatMap(breaks, (breach) => breach.inspected),
844
+ detail: Array$1.join(Array$1.map(breaks, (breach) => breach.conjunct), ", ")
845
+ })
846
+ });
847
+ };
848
+ const decide$3 = (command) => Result$1.succeed(Option.match(firstHardBreak(command.verdicts), {
849
+ onNone: () => aggregateSoft(command),
850
+ onSome: (winner) => new FirstHardBreak({
851
+ conjunct: winner.conjunct,
852
+ inspected: winner.inspected,
853
+ detail: winner.detail
854
+ })
855
+ }));
856
+ const combineRelationVerdicts = Workflow.make({
857
+ command: CombineRelationVerdicts,
858
+ decision: CombineRelationVerdictsDecision,
859
+ error: Schema.Never,
860
+ decide: decide$3
861
+ });
862
+ //#endregion
863
+ //#region src/judge-edge-placement.workflow.ts
864
+ const JudgeEdgePlacementTypeId = Symbol.for("@systemfsoftware/trace-spec/JudgeEdgePlacement");
865
+ var EdgePlacementHeld = class extends Schema.TaggedClass()("EdgePlacementHeld", {
866
+ conjunct: Schema.String,
867
+ inspected: Schema.Array(Schema.String)
868
+ }) {
869
+ [JudgeEdgePlacementTypeId] = JudgeEdgePlacementTypeId;
870
+ };
871
+ var EdgePlacementBroken = class extends Schema.TaggedClass()("EdgePlacementBroken", {
872
+ conjunct: Schema.String,
873
+ inspected: Schema.Array(Schema.String),
874
+ detail: Schema.String
875
+ }) {
876
+ [JudgeEdgePlacementTypeId] = JudgeEdgePlacementTypeId;
877
+ };
878
+ const JudgeEdgePlacementDecision = Schema.Union([EdgePlacementHeld, EdgePlacementBroken]);
879
+ var JudgeEdgePlacement = class extends Schema.TaggedClass()("JudgeEdgePlacement", {
880
+ conjunct: Schema.String,
881
+ placed: Schema.Array(Schema.String),
882
+ reached: Schema.Array(Schema.String),
883
+ detail: Schema.String
884
+ }) {
885
+ static [Workflow.InstrumentationBrand] = {};
886
+ };
887
+ const everyPlaced = (placed, reached) => Array$1.every(placed, (spanId) => Array$1.some(reached, (candidate) => candidate === spanId));
888
+ const decide$2 = (command) => Result$1.succeed(Match.value(everyPlaced(command.placed, command.reached)).pipe(Match.when(true, () => new EdgePlacementHeld({
889
+ conjunct: command.conjunct,
890
+ inspected: command.placed
891
+ })), Match.when(false, () => new EdgePlacementBroken({
892
+ conjunct: command.conjunct,
893
+ inspected: command.placed,
894
+ detail: command.detail
895
+ })), Match.exhaustive));
896
+ const judgeEdgePlacement = Workflow.make({
897
+ command: JudgeEdgePlacement,
898
+ decision: JudgeEdgePlacementDecision,
899
+ error: Schema.Never,
900
+ decide: decide$2
901
+ });
902
+ //#endregion
903
+ //#region src/negate-relation-verdict.workflow.ts
904
+ const NegateRelationVerdictTypeId = Symbol.for("@systemfsoftware/trace-spec/NegateRelationVerdict");
905
+ var HoldNegated = class extends Schema.TaggedClass()("HoldNegated", {
906
+ conjunct: Schema.String,
907
+ inspected: Schema.Array(Schema.String),
908
+ detail: Schema.String
909
+ }) {
910
+ [NegateRelationVerdictTypeId] = NegateRelationVerdictTypeId;
911
+ };
912
+ var BreakNegated = class extends Schema.TaggedClass()("BreakNegated", {
913
+ conjunct: Schema.String,
914
+ inspected: Schema.Array(Schema.String)
915
+ }) {
916
+ [NegateRelationVerdictTypeId] = NegateRelationVerdictTypeId;
917
+ };
918
+ const NegateRelationVerdictDecision = Schema.Union([HoldNegated, BreakNegated]);
919
+ var NegateRelationVerdict = class extends Schema.TaggedClass()("NegateRelationVerdict", {
920
+ conjunct: Schema.String,
921
+ verdict: Verdict
922
+ }) {
923
+ static [Workflow.InstrumentationBrand] = {};
924
+ };
925
+ const decide$1 = (command) => Result$1.succeed(Match.value(command.verdict).pipe(Match.tag("Hold", (held) => new HoldNegated({
926
+ conjunct: command.conjunct,
927
+ inspected: held.inspected,
928
+ detail: `${held.conjunct} held`
929
+ })), Match.tag("Break", (breach) => new BreakNegated({
930
+ conjunct: command.conjunct,
931
+ inspected: breach.inspected
932
+ })), Match.exhaustive));
933
+ const negateRelationVerdict = Workflow.make({
934
+ command: NegateRelationVerdict,
935
+ decision: NegateRelationVerdictDecision,
936
+ error: Schema.Never,
937
+ decide: decide$1
938
+ });
939
+ //#endregion
716
940
  //#region src/Rel.ts
717
941
  var Rel_exports = /* @__PURE__ */ __exportAll({
718
942
  Break: () => Break,
@@ -736,6 +960,10 @@ var Rel_exports = /* @__PURE__ */ __exportAll({
736
960
  status: () => status,
737
961
  unique: () => unique
738
962
  });
963
+ const decisionOf = (result) => Result.match(result, {
964
+ onFailure: (unreachable) => absurd(unreachable),
965
+ onSuccess: (decision) => decision
966
+ });
739
967
  const relation = (parts) => Object.assign((graph) => parts.evaluate(graph), {
740
968
  id: parts.id,
741
969
  soft: parts.soft,
@@ -744,9 +972,7 @@ const relation = (parts) => Object.assign((graph) => parts.evaluate(graph), {
744
972
  const matchedNodes = (graph, spec) => byId(graph, spec);
745
973
  const inspectedOf = (nodes) => nodes.map((node) => node.spanId);
746
974
  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({
975
+ const verdictOf = (options) => options.holds ? Hold.make({
750
976
  conjunct: options.id,
751
977
  inspected: options.inspected
752
978
  }) : Break.make({
@@ -760,7 +986,7 @@ const declared$1 = (declaration) => relation({
760
986
  softenable: declaration.softenable,
761
987
  evaluate: (graph) => {
762
988
  const inspected = declaration.spans.flatMap((span) => inspectedOf(byId(graph, span)));
763
- return verdictOf$1({
989
+ return verdictOf({
764
990
  id: declaration.id,
765
991
  inspected,
766
992
  holds: declaration.holds(graph),
@@ -873,7 +1099,7 @@ const forallImpl = (spec, predicate, detail) => {
873
1099
  softenable: true,
874
1100
  evaluate: (graph) => {
875
1101
  const nodes = matchedNodes(graph, spec);
876
- return verdictOf$1({
1102
+ return verdictOf({
877
1103
  id,
878
1104
  inspected: inspectedOf(nodes),
879
1105
  holds: matchedAll(nodes, predicate),
@@ -889,13 +1115,16 @@ const soft = (self) => self.softenable ? relation({
889
1115
  softenable: true,
890
1116
  evaluate: (graph) => self(graph)
891
1117
  }) : self;
892
- const negatedVerdict = (id, inner) => Match.value(inner).pipe(Match.tag("Hold", (held) => Break.make({
1118
+ const negatedVerdict = (id, inner) => Match.value(decisionOf(negateRelationVerdict(new NegateRelationVerdict({
893
1119
  conjunct: id,
1120
+ verdict: inner
1121
+ })))).pipe(Match.tag("HoldNegated", (held) => Break.make({
1122
+ conjunct: held.conjunct,
894
1123
  inspected: held.inspected,
895
- detail: `${held.conjunct} held`
896
- })), Match.tag("Break", (breach) => Hold.make({
897
- conjunct: id,
898
- inspected: breach.inspected
1124
+ detail: held.detail
1125
+ })), Match.tag("BreakNegated", (broken) => Hold.make({
1126
+ conjunct: broken.conjunct,
1127
+ inspected: broken.inspected
899
1128
  })), Match.exhaustive);
900
1129
  const not = (self) => {
901
1130
  const id = `not(${self.id})`;
@@ -906,24 +1135,29 @@ const not = (self) => {
906
1135
  evaluate: (graph) => negatedVerdict(id, self(graph))
907
1136
  });
908
1137
  };
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);
1138
+ const combinationOf = (conjunct, relations, graph) => Match.value(decisionOf(combineRelationVerdicts(new CombineRelationVerdicts({
1139
+ conjunct,
1140
+ verdicts: relations.map((relation) => ({
1141
+ verdict: relation(graph),
1142
+ soft: relation.soft
1143
+ }))
1144
+ })))).pipe(Match.tag("FirstHardBreak", (winner) => Break.make({
1145
+ conjunct: winner.conjunct,
1146
+ inspected: winner.inspected,
1147
+ detail: winner.detail
1148
+ })), Match.tag("AggregatedSoftBreak", (aggregated) => Break.make({
1149
+ conjunct: aggregated.conjunct,
1150
+ inspected: aggregated.inspected,
1151
+ detail: aggregated.detail
1152
+ })), Match.tag("EveryRelationHeld", (held) => Hold.make({
1153
+ conjunct: held.conjunct,
1154
+ inspected: held.inspected
1155
+ })), Match.exhaustive);
922
1156
  const combined = (options) => relation({
923
1157
  id: options.id,
924
1158
  soft: options.soft,
925
1159
  softenable: options.softenable,
926
- evaluate: (graph) => firstHardBreak(options.relations, graph) ?? collectSoft(options.id, softBreaks(options.relations, graph))
1160
+ evaluate: (graph) => combinationOf(options.id, options.relations, graph)
927
1161
  });
928
1162
  const all = (...relations) => combined({
929
1163
  id: `all(${relations.map((relation) => relation.id).join(", ")})`,
@@ -931,30 +1165,43 @@ const all = (...relations) => combined({
931
1165
  soft: relations.every((relation) => relation.soft),
932
1166
  softenable: true
933
1167
  });
1168
+ const disjunctionOf = (conjunct, verdicts) => Match.value(decisionOf(combineAnyRelations(new CombineAnyRelations({
1169
+ conjunct,
1170
+ verdicts
1171
+ })))).pipe(Match.tag("AnyRelationHeld", (held) => Hold.make({
1172
+ conjunct: held.conjunct,
1173
+ inspected: held.inspected
1174
+ })), Match.tag("NoRelationHeld", (broken) => Break.make({
1175
+ conjunct: broken.conjunct,
1176
+ inspected: broken.inspected,
1177
+ detail: broken.detail
1178
+ })), Match.exhaustive);
934
1179
  const any = (...relations) => {
935
1180
  const id = `any(${relations.map((relation) => relation.id).join(", ")})`;
936
1181
  return relation({
937
1182
  id,
938
1183
  soft: false,
939
1184
  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
- }
1185
+ evaluate: (graph) => disjunctionOf(id, relations.map((relation) => relation(graph)))
952
1186
  });
953
1187
  };
954
1188
  const REACH_BY_RELATION = {
955
1189
  child: childIdsOf,
956
1190
  descendant: descendantIdsOf
957
1191
  };
1192
+ const placementVerdictOf = (conjunct, placed, reached, detail) => Match.value(decisionOf(judgeEdgePlacement(new JudgeEdgePlacement({
1193
+ conjunct,
1194
+ placed,
1195
+ reached: [...reached],
1196
+ detail
1197
+ })))).pipe(Match.tag("EdgePlacementHeld", (held) => Hold.make({
1198
+ conjunct: held.conjunct,
1199
+ inspected: held.inspected
1200
+ })), Match.tag("EdgePlacementBroken", (broken) => Break.make({
1201
+ conjunct: broken.conjunct,
1202
+ inspected: broken.inspected,
1203
+ detail: broken.detail
1204
+ })), Match.exhaustive);
958
1205
  const placementOf = (edge) => {
959
1206
  const id = `placement(${edge.relation}:${edge.parent.id},${edge.child.id})`;
960
1207
  return relation({
@@ -962,14 +1209,9 @@ const placementOf = (edge) => {
962
1209
  soft: false,
963
1210
  softenable: false,
964
1211
  evaluate: (graph) => {
965
- const placed = matchedNodes(graph, edge.child);
1212
+ const placed = inspectedOf(matchedNodes(graph, edge.child));
966
1213
  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
- });
1214
+ return placementVerdictOf(id, placed, reached, `a ${edge.child.id} span is not a ${edge.relation} of any ${edge.parent.id} span`);
973
1215
  }
974
1216
  });
975
1217
  };
@@ -986,44 +1228,87 @@ const fromTaxonomyImpl = (taxonomy, options) => combined({
986
1228
  });
987
1229
  const fromTaxonomy = dual(2, fromTaxonomyImpl);
988
1230
  //#endregion
1231
+ //#region src/settle-remote-read.workflow.ts
1232
+ const SettleRemoteReadTypeId = Symbol.for("@systemfsoftware/trace-spec/SettleRemoteRead");
1233
+ const ObservedSpans = Schema.Struct({
1234
+ spans: Schema.Array(SpanRecord),
1235
+ addedAtMillis: Schema.Finite
1236
+ });
1237
+ var ReadSettled = class extends Schema.TaggedClass()("ReadSettled", { observation: ObservedSpans }) {
1238
+ [SettleRemoteReadTypeId] = SettleRemoteReadTypeId;
1239
+ };
1240
+ var ReadAbsent = class extends Schema.TaggedClass()("ReadAbsent", { observation: ObservedSpans }) {
1241
+ [SettleRemoteReadTypeId] = SettleRemoteReadTypeId;
1242
+ };
1243
+ var ReadUnfinished = class extends Schema.TaggedClass()("ReadUnfinished", { observation: ObservedSpans }) {
1244
+ [SettleRemoteReadTypeId] = SettleRemoteReadTypeId;
1245
+ };
1246
+ var ReadStillGrowing = class extends Schema.TaggedClass()("ReadStillGrowing", { observation: ObservedSpans }) {
1247
+ [SettleRemoteReadTypeId] = SettleRemoteReadTypeId;
1248
+ };
1249
+ const SettleRemoteReadDecision = Schema.Union([
1250
+ ReadSettled,
1251
+ ReadAbsent,
1252
+ ReadUnfinished,
1253
+ ReadStillGrowing
1254
+ ]);
1255
+ var SettleRemoteRead = class extends Schema.TaggedClass()("SettleRemoteRead", {
1256
+ observed: ObservedSpans,
1257
+ read: Schema.Array(SpanRecord),
1258
+ elapsedMillis: Schema.Finite,
1259
+ settleMillis: Schema.Finite,
1260
+ timeoutMillis: Schema.Finite
1261
+ }) {
1262
+ static [Workflow.InstrumentationBrand] = {};
1263
+ };
1264
+ const sameId = (left, right) => left.spanId === right.spanId;
1265
+ const firstOfEachId = (read) => Array$1.dedupeWith(read, sameId);
1266
+ const freshOf = (observed, read) => Array$1.filter(firstOfEachId(read), (span) => Array$1.every(observed.spans, (seen) => seen.spanId !== span.spanId));
1267
+ const absorbed = (observed, read, elapsedMillis) => {
1268
+ const fresh = freshOf(observed, read);
1269
+ return Option.match(Array$1.head(fresh), {
1270
+ onNone: () => observed,
1271
+ onSome: () => ({
1272
+ spans: [...observed.spans, ...fresh],
1273
+ addedAtMillis: elapsedMillis
1274
+ })
1275
+ });
1276
+ };
1277
+ const quietForLongEnough = (observed, elapsedMillis, settleMillis) => Array$1.every([observed.spans.length > 0, elapsedMillis - observed.addedAtMillis >= settleMillis], Boolean);
1278
+ 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);
1279
+ const decide = (command) => Result$1.succeed(classify(absorbed(command.observed, command.read, command.elapsedMillis), command.elapsedMillis, command.settleMillis, command.timeoutMillis));
1280
+ const settleRemoteRead = Workflow.make({
1281
+ command: SettleRemoteRead,
1282
+ decision: SettleRemoteReadDecision,
1283
+ error: Schema.Never,
1284
+ decide
1285
+ });
1286
+ //#endregion
989
1287
  //#region src/RemoteObservation.ts
990
1288
  var RemoteObservation_exports = /* @__PURE__ */ __exportAll({ layer: () => layer });
991
1289
  const NEVER_READ = {
992
1290
  spans: [],
993
1291
  addedAtMillis: 0
994
1292
  };
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;
1293
+ const settlementOf = (command) => Result.match(settleRemoteRead(command), {
1294
+ onFailure: (unreachable) => absurd(unreachable),
1295
+ onSuccess: (decision) => decision
1296
+ });
1011
1297
  const pastDeadline = (elapsedMillis, windows) => elapsedMillis >= windows.timeoutMillis;
1012
1298
  const settledVerdict = (spans) => Option.some(Result.succeed(spans));
1013
1299
  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()));
1300
+ 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
1301
  const settleStep = (settlement, read, elapsedMillis, windows) => {
1023
- const advanced = absorbed(settlement, read, elapsedMillis);
1302
+ const outcome = settlementOf(new SettleRemoteRead({
1303
+ observed: settlement,
1304
+ read,
1305
+ elapsedMillis,
1306
+ settleMillis: windows.settleMillis,
1307
+ timeoutMillis: windows.timeoutMillis
1308
+ }));
1024
1309
  return {
1025
- settlement: advanced,
1026
- verdict: verdictOf(advanced, elapsedMillis, windows)
1310
+ settlement: outcome.observation,
1311
+ verdict: verdictOfOutcome(outcome)
1027
1312
  };
1028
1313
  };
1029
1314
  const lateRefusal = (settlement) => settlement.spans.length === 0 ? "absent" : "unfinished";
@@ -1147,6 +1432,17 @@ const HarnessFailure = Schema.Union([
1147
1432
  TransportObservationError
1148
1433
  ]);
1149
1434
  //#endregion
1435
+ //#region src/StimulusFailure.schema.ts
1436
+ const firstLine = (text) => text.split("\n")[0] ?? "";
1437
+ var StimulusFailure = class extends Schema.TaggedError()("StimulusFailure", {
1438
+ stimulus: Schema.String,
1439
+ detail: Schema.String
1440
+ }) {
1441
+ get message() {
1442
+ return `The stimulus "${this.stimulus}" failed: ${firstLine(this.detail)}`;
1443
+ }
1444
+ };
1445
+ //#endregion
1150
1446
  //#region src/TaskAnnounce.ts
1151
1447
  const forget = (annotation) => {
1152
1448
  Promise.resolve(annotation);
@@ -1169,40 +1465,6 @@ const announceDump = (judged) => Option.match(dumpMessageOf(judged), {
1169
1465
  onSome: (message) => record(message, "info")
1170
1466
  });
1171
1467
  //#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
1468
  //#region src/Suite.ts
1207
1469
  var Suite_exports = /* @__PURE__ */ __exportAll({
1208
1470
  StimulusFailure: () => StimulusFailure,
@@ -1230,6 +1492,17 @@ const caseFailureOf = (stimulus, salt) => (failure) => isHarnessFailure(failure)
1230
1492
  detail: `${Cause.pretty(Cause.fail(failure))}\nrandom salt: ${salt}`
1231
1493
  });
1232
1494
  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)));
1495
+ const isHold = Schema.is(Hold);
1496
+ const judgedOver = (contract, subject) => ({
1497
+ ...contract,
1498
+ stimulus: subject
1499
+ });
1500
+ const consumedInput = (judgment, input) => judgment.run.input === input;
1501
+ const casePredicate = (title, contract, scenario, shared) => {
1502
+ const provided = scenario.pipe(Layer.provideMerge(shared));
1503
+ 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)));
1504
+ return (subject, input) => checked(subject, input);
1505
+ };
1233
1506
  const caseTools = (register, propIt, scenario, shared) => {
1234
1507
  const Case = (name, contract, input) => {
1235
1508
  const salt = saltOf();
@@ -1240,7 +1513,7 @@ const caseTools = (register, propIt, scenario, shared) => {
1240
1513
  propIt.effect.prop(name, {
1241
1514
  of: [arbitrary],
1242
1515
  subject: contract.stimulus
1243
- }, (subject, values) => predicate(name, contract, scenario, shared)(subject, values[0]).pipe(Random.withSeed(salt)));
1516
+ }, (subject, values) => casePredicate(name, contract, scenario, shared)(subject, values[0]).pipe(Random.withSeed(salt)));
1244
1517
  };
1245
1518
  return { Case };
1246
1519
  };
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": "0.5.2",
5
5
  "author": "Ryan Lee <drdgvhbh@gmail.com>",
6
6
  "repository": {
7
7
  "type": "git",
@@ -39,18 +39,25 @@
39
39
  "@opentelemetry/sdk-trace-base": "^2",
40
40
  "@opentelemetry/semantic-conventions": "^1.33",
41
41
  "@systemfsoftware/effect-cell-types": "^11.0.0",
42
- "@systemfsoftware/effect-spec-runtime": "^0.3.1",
42
+ "@systemfsoftware/effect-spec-runtime": "^0.3.2",
43
43
  "@systemfsoftware/trace-taxonomy": "^0.0.2"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@effect/platform-node": "4.0.0-rc.117",
47
47
  "@systemfsoftware/arethetypeswrong-cli": "^4.2.0",
48
48
  "@systemfsoftware/conformance-spec": "^0.2.0",
49
- "@systemfsoftware/effect-gherkin-spec": "^6.0.0",
50
- "@systemfsoftware/oxlint-config-recommended": "^3.2.0",
49
+ "@systemfsoftware/effect-gherkin-spec": "^6.0.1",
50
+ "@systemfsoftware/oxlint-config-recommended": "^3.3.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": "^0.3.0",
54
61
  "@systemfsoftware/vitest-config": "^0.1.0",
55
62
  "@types/node": "^26",
56
63
  "effect": "4.0.0-rc.117",
@@ -61,7 +68,7 @@
61
68
  "vitest": "^5"
62
69
  },
63
70
  "peerDependencies": {
64
- "@systemfsoftware/vitest": "^0.2.0",
71
+ "@systemfsoftware/vitest": "^0.3.0",
65
72
  "effect": "4.0.0-rc.117",
66
73
  "vitest": "^5"
67
74
  },
@@ -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 ."