@systemfsoftware/trace-spec 0.2.0 → 0.3.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,27 @@
1
1
  # @systemfsoftware/trace-spec
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Requires `@systemfsoftware/vitest` installed under the `@effect/vitest` name (`"@effect/vitest": "npm:@systemfsoftware/vitest"`) in place of the upstream package. `Case.prop` checks the stimulus it is handed on every run and pins the run's input, so a stimulus that ignores its input fails; the trace dump path is annotated on the failing test.
8
+
9
+ ### Patch Changes
10
+
11
+ - The observation window is built on the `Blueprint` and `Handle` kinds, with unchanged behaviour. The blueprint's `TypeId` is now exported as a value.
12
+
13
+ - Updated dependencies:
14
+ - @systemfsoftware/effect-spec-runtime@0.2.0
15
+
16
+ ## 0.2.1
17
+
18
+ ### Patch Changes
19
+
20
+ - Update peer and runtime dependency on `effect` and companion packages to `4.0.0-rc.117`.
21
+
22
+ - Updated dependencies:
23
+ - @systemfsoftware/trace-taxonomy@0.0.1
24
+
3
25
  ## 0.2.0
4
26
 
5
27
  ### Minor Changes
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ import { Pipeable } from "effect/Pipeable";
3
3
  import { Span, Taxonomy } from "@systemfsoftware/trace-taxonomy";
4
4
  import * as HttpClient from "effect/unstable/http/HttpClient";
5
5
  import * as OtelTracer from "@effect/opentelemetry/OtelTracer";
6
+ import { Blueprint, Handle } from "@systemfsoftware/effect-cell-types";
6
7
  import { BasicTracerProvider, InMemorySpanExporter } from "@opentelemetry/sdk-trace-base";
7
8
  import { Suite } from "@systemfsoftware/effect-spec-runtime";
8
9
  import * as Scope from "effect/Scope";
@@ -320,7 +321,7 @@ declare const check: {
320
321
  <Input, Output, E, R>(self: Contract<Input, Output, E, R>, input: Input, options?: CheckOptions): Effect.Effect<Judgment<Input, Output>, CheckFailure<E>, Services<R>>;
321
322
  };
322
323
  declare namespace RemoteObservation_d_exports {
323
- export { Options, TraceSource, layer };
324
+ export { Options, TraceSource, layer$1 as layer };
324
325
  }
325
326
  /**
326
327
  * The cadence of the poll, the quiet window the observed span set must hold
@@ -348,7 +349,7 @@ type TraceSource<R> = (traceId: string) => Effect.Effect<ReadonlyArray<SpanRecor
348
349
  * the source requires (`Scope.Scope` is supplied by the layer machinery, as
349
350
  * every Effect layer constructor scopes it away).
350
351
  */
351
- declare const layer: {
352
+ declare const layer$1: {
352
353
  (options: Options): <R>(source: TraceSource<R>) => Layer.Layer<Observation, never, Exclude<R, Scope.Scope>>;
353
354
  <R>(source: TraceSource<R>, options: Options): Layer.Layer<Observation, never, Exclude<R, Scope.Scope>>;
354
355
  };
@@ -370,15 +371,19 @@ declare namespace observation_window_handle_d_exports {
370
371
  }
371
372
  declare const TypeId$2: unique symbol;
372
373
  type TypeId$2 = typeof TypeId$2;
373
- declare const ExporterId: unique symbol;
374
- declare const ProviderId: unique symbol;
375
- interface ObservationWindow extends Pipeable {
376
- readonly [TypeId$2]: typeof TypeId$2;
377
- readonly [ExporterId]: InMemorySpanExporter;
378
- readonly [ProviderId]: BasicTracerProvider;
374
+ declare const ObservationWindow: Handle.Definition<typeof TypeId$2, {
379
375
  readonly serviceName: string;
380
- }
381
- declare const isObservationWindow: (u: unknown) => u is ObservationWindow;
376
+ }, {
377
+ readonly exporter: InMemorySpanExporter;
378
+ readonly provider: BasicTracerProvider;
379
+ }, never>;
380
+ type ObservationWindow = Handle.Of<typeof ObservationWindow>;
381
+ declare const isObservationWindow: (u: unknown) => u is Handle.Handle<typeof TypeId$2, {
382
+ readonly serviceName: string;
383
+ }, {
384
+ readonly exporter: InMemorySpanExporter;
385
+ readonly provider: BasicTracerProvider;
386
+ }, never>;
382
387
  declare const make$2: (spec: ObservationWindowSpec) => ObservationWindow;
383
388
  declare const shutdown: (self: ObservationWindow) => Effect.Effect<void>;
384
389
  declare const collect: {
@@ -386,18 +391,19 @@ declare const collect: {
386
391
  (self: ObservationWindow, traceId: string): Effect.Effect<ReadonlyArray<SpanRecord>, EmptyObservationError>;
387
392
  };
388
393
  declare const context: (self: ObservationWindow) => Context.Context<Observation | OtelTracer.OtelTracerProvider>;
389
- declare namespace observation_window_resource_d_exports {
390
- export { ObservationWindow, ObservationWindowResource, ObservationWindowSpec, TypeId$2 as ObservationWindowTypeId, TypeId$1 as TypeId, collect, isObservationWindow, make$1 as make };
394
+ declare namespace observation_window_blueprint_d_exports {
395
+ export { ObservationWindow, ObservationWindowBlueprint, ObservationWindowSpec, TypeId$2 as ObservationWindowTypeId, TypeId$1 as TypeId, collect, isObservationWindow, make$1 as make };
391
396
  }
392
397
  declare const TypeId$1: unique symbol;
393
398
  type TypeId$1 = typeof TypeId$1;
394
- interface ObservationWindowResource extends Pipeable {
395
- readonly [TypeId$1]: typeof TypeId$1;
396
- readonly spec: ObservationWindowSpec;
397
- readonly scoped: Effect.Effect<ObservationWindow, never, Scope.Scope>;
398
- readonly layer: Layer.Layer<Observation | OtelTracer.OtelTracer>;
399
- }
400
- declare const make$1: (serviceName: string) => ObservationWindowResource;
399
+ declare const scoped: (spec: ObservationWindowSpec) => Effect.Effect<ObservationWindow, never, Scope.Scope>;
400
+ declare const layer: (spec: ObservationWindowSpec) => Layer.Layer<Observation | OtelTracer.OtelTracer>;
401
+ declare const ObservationWindows: Blueprint.Definition<typeof TypeId$1, ObservationWindowSpec, Blueprint.Derived<ObservationWindowSpec, {}, {
402
+ readonly scoped: typeof scoped;
403
+ readonly layer: typeof layer;
404
+ }>, ObservationWindowSpec>;
405
+ type ObservationWindowBlueprint = Blueprint.Of<typeof ObservationWindows>;
406
+ declare const make$1: (serviceName: string) => ObservationWindowBlueprint;
401
407
  //#endregion
402
408
  //#region src/HarnessFailure.schema.d.ts
403
409
  declare const HarnessFailure: Schema.Union<readonly [typeof ContractDecodeError, typeof EmptyObservationError, typeof IncompleteObservationError, typeof TransportObservationError, typeof TraceDisparityError]>;
@@ -447,4 +453,4 @@ declare const withScenarioLayer: {
447
453
  };
448
454
  declare const make: (bindings: Suite.Bindings) => (name: string) => Declared;
449
455
  //#endregion
450
- export { Contract_d_exports as Contract, Graph_d_exports as Graph, Observation_service_d_exports as Observation, observation_window_resource_d_exports as ObservationWindow, Rel_d_exports as Rel, RemoteObservation_d_exports as RemoteObservation, Stimulus_d_exports as Stimulus, Suite_d_exports as Suite, tempo_trace_store_d_exports as TempoTraceStore };
456
+ export { Contract_d_exports as Contract, Graph_d_exports as Graph, Observation_service_d_exports as Observation, observation_window_blueprint_d_exports as ObservationWindow, Rel_d_exports as Rel, RemoteObservation_d_exports as RemoteObservation, Stimulus_d_exports as Stimulus, Suite_d_exports as Suite, tempo_trace_store_d_exports as TempoTraceStore };
package/dist/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { recordAssertion } from "@effect/vitest";
2
3
  import { Array as Array$1, Cause, Clock, Context, Duration, Effect, Encoding, Equal, FileSystem, Layer, Match, Option, Predicate, Ref, Result, Schema, SchemaGetter, SchemaTransformation } from "effect";
3
4
  import { dual } from "effect/Function";
4
5
  import { Prototype } from "effect/Pipeable";
@@ -9,6 +10,7 @@ import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
9
10
  import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse";
10
11
  import * as OtelTracer from "@effect/opentelemetry/OtelTracer";
11
12
  import * as OtelResource from "@effect/opentelemetry/Resource";
13
+ import { Blueprint, Handle } from "@systemfsoftware/effect-cell-types";
12
14
  import { SpanStatusCode } from "@opentelemetry/api";
13
15
  import { AlwaysOnSampler, BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
14
16
  import { Suite, TaskRef } from "@systemfsoftware/effect-spec-runtime";
@@ -326,7 +328,7 @@ const refuseBreak = (relationId, judgment) => Match.value(judgment.verdict).pipe
326
328
  breaks: [breach],
327
329
  dumpPath: judgment.dumpPath
328
330
  }))), Match.tag("Hold", () => Effect.succeed(judgment)), Match.exhaustive);
329
- const checkDual = (self, input, options) => Effect.flatMap(judgeDual(self, input, options), (judgment) => refuseBreak(self.relation.id, judgment));
331
+ const checkDual = (self, input, options) => Effect.flatMap(judgeDual(self, input, options), (judgment) => Effect.andThen(Effect.sync(recordAssertion), refuseBreak(self.relation.id, judgment)));
330
332
  const check = dual((args) => isContract(args[0]), checkDual);
331
333
  //#endregion
332
334
  //#region src/drivers/tempo-trace.schema.ts
@@ -544,24 +546,20 @@ Schema.encodeEffect(HexId);
544
546
  //#endregion
545
547
  //#region src/observation-window.handle.ts
546
548
  const TypeId$2 = Symbol.for("~systemfsoftware/trace-spec/ObservationWindow");
547
- const ExporterId = Symbol.for("~systemfsoftware/trace-spec/ObservationWindow/exporter");
548
- const ProviderId = Symbol.for("~systemfsoftware/trace-spec/ObservationWindow/provider");
549
- const isObservationWindow = (u) => Predicate.hasProperty(u, TypeId$2);
549
+ const ObservationWindow = Handle.make()(TypeId$2);
550
+ const isObservationWindow = ObservationWindow.is;
550
551
  const make$3 = (spec) => {
551
552
  const exporter = new InMemorySpanExporter();
552
553
  const provider = new BasicTracerProvider({
553
554
  sampler: new AlwaysOnSampler(),
554
555
  spanProcessors: [new SimpleSpanProcessor(exporter)]
555
556
  });
556
- return {
557
- [TypeId$2]: TypeId$2,
558
- [ExporterId]: exporter,
559
- [ProviderId]: provider,
560
- serviceName: spec.serviceName,
561
- ...Prototype
562
- };
557
+ return ObservationWindow.make({ serviceName: spec.serviceName }, {
558
+ exporter,
559
+ provider
560
+ });
563
561
  };
564
- const shutdown = (self) => Effect.promise(() => self[ProviderId].shutdown());
562
+ const shutdown = (self) => Effect.promise(() => ObservationWindow.slot(self).provider.shutdown());
565
563
  const SCALAR_TYPES = {
566
564
  string: true,
567
565
  number: true,
@@ -616,39 +614,36 @@ const emptyObservation = (traceId) => new EmptyObservationError({
616
614
  traceId,
617
615
  detail: "the observation window closed with no span carrying the stimulated trace id"
618
616
  });
619
- const recordsOf = (self, traceId) => self[ExporterId].getFinishedSpans().filter((span) => span.spanContext().traceId === traceId).map(spanRecordOf);
617
+ const recordsOf = (self, traceId) => ObservationWindow.slot(self).exporter.getFinishedSpans().filter((span) => span.spanContext().traceId === traceId).map(spanRecordOf);
620
618
  const collect = dual(2, (self, traceId) => Effect.suspend(() => {
621
619
  const records = recordsOf(self, traceId);
622
620
  return records.length > 0 ? Effect.succeed(records) : Effect.fail(emptyObservation(traceId));
623
621
  }));
624
- const context = (self) => Context.make(Observation, { collect: (traceId) => collect(self, traceId) }).pipe(Context.add(OtelTracer.OtelTracerProvider, self[ProviderId]));
622
+ const context = (self) => Context.make(Observation, { collect: (traceId) => collect(self, traceId) }).pipe(Context.add(OtelTracer.OtelTracerProvider, ObservationWindow.slot(self).provider));
625
623
  //#endregion
626
624
  //#region src/ObservationWindowSpec.schema.ts
627
625
  var ObservationWindowSpec = class extends Schema.Class("ObservationWindowSpec")({ serviceName: Schema.NonEmptyString }) {};
628
626
  //#endregion
629
- //#region src/observation-window.resource.ts
630
- var observation_window_resource_exports = /* @__PURE__ */ __exportAll({
627
+ //#region src/observation-window.blueprint.ts
628
+ var observation_window_blueprint_exports = /* @__PURE__ */ __exportAll({
631
629
  ObservationWindowSpec: () => ObservationWindowSpec,
632
630
  ObservationWindowTypeId: () => TypeId$2,
631
+ TypeId: () => TypeId$1,
633
632
  collect: () => collect,
634
633
  isObservationWindow: () => isObservationWindow,
635
634
  make: () => make$2
636
635
  });
637
- const TypeId$1 = Symbol.for("~systemfsoftware/trace-spec/ObservationWindowResource");
636
+ const TypeId$1 = Symbol.for("~systemfsoftware/trace-spec/ObservationWindowBlueprint");
638
637
  const scoped = (spec) => Effect.acquireRelease(Effect.sync(() => make$3(spec)), shutdown);
639
638
  const layer$1 = (spec) => OtelTracer.layer.pipe(Layer.provideMerge(OtelResource.layer({ serviceName: spec.serviceName })), Layer.provideMerge(Layer.effectContext(Effect.map(scoped(spec), context))));
640
- const makeProto = (spec) => ({
641
- [TypeId$1]: TypeId$1,
642
- spec,
643
- get scoped() {
644
- return scoped(spec);
645
- },
646
- get layer() {
647
- return layer$1(spec);
648
- },
649
- ...Prototype
639
+ const ObservationWindows = Blueprint.make()(TypeId$1).steps({
640
+ steps: {},
641
+ targets: {
642
+ scoped,
643
+ layer: layer$1
644
+ }
650
645
  });
651
- const make$2 = (serviceName) => makeProto(new ObservationWindowSpec({ serviceName }));
646
+ const make$2 = (serviceName) => ObservationWindows.of(new ObservationWindowSpec({ serviceName }));
652
647
  //#endregion
653
648
  //#region src/Rel.ts
654
649
  var Rel_exports = /* @__PURE__ */ __exportAll({
@@ -1087,18 +1082,35 @@ const HarnessFailure = Schema.Union([
1087
1082
  //#region src/Prop.ts
1088
1083
  const isHold = Schema.is(Hold);
1089
1084
  const isBreak = Schema.is(Break);
1090
- const annotate = (context, message) => Option.match(Option.flatMap(Option.fromNullishOr(context), (task) => Option.fromNullishOr(task.annotate)), {
1091
- onNone: () => Effect.void,
1092
- onSome: (record) => Effect.promise(() => Promise.resolve(record(message, "info")))
1085
+ const annotate = (message) => Effect.gen(function* () {
1086
+ const task = yield* TaskRef.RawVitestTaskRef;
1087
+ return yield* Option.match(Option.fromNullishOr(task?.annotate), {
1088
+ onNone: () => Effect.void,
1089
+ onSome: (record) => Effect.promise(() => Promise.resolve(record(message, "info")))
1090
+ });
1093
1091
  });
1094
1092
  const dumpMessageOf = (verdict, dumpPath) => Option.map(Option.flatMap(Option.liftPredicate(verdict, isBreak), () => Option.fromNullishOr(dumpPath)), (path) => `trace contract failed; observed graph dumped to ${path}`);
1095
- const announce = (context, verdict, dumpPath) => Option.match(dumpMessageOf(verdict, dumpPath), {
1093
+ const announce = (verdict, dumpPath) => Option.match(dumpMessageOf(verdict, dumpPath), {
1096
1094
  onNone: () => Effect.void,
1097
- onSome: (message) => annotate(context, message)
1095
+ onSome: (message) => annotate(message)
1096
+ });
1097
+ /**
1098
+ * The contract with its stimulus replaced by the function the property was handed: the impostor gate
1099
+ * swaps in a constant fake, so the judgement must reach the stimulus through the argument, never the
1100
+ * closure.
1101
+ */
1102
+ const judgedOver = (contract, subject) => ({
1103
+ ...contract,
1104
+ stimulus: subject
1098
1105
  });
1106
+ /**
1107
+ * The run must be the run of the generated input: a stimulus that ignores its input and answers with one
1108
+ * frozen run satisfies every relation about that run, so the input pin is what refutes it.
1109
+ */
1110
+ const consumedInput = (judgment, input) => judgment.run.input === input;
1099
1111
  const predicateImpl = (title, contract, scenario) => {
1100
- const checked = (input, context) => judge(contract, input, { dumpName: title }).pipe(Effect.tap((judgment) => announce(context, judgment.verdict, judgment.dumpPath)), Effect.map((judgment) => isHold(judgment.verdict)), Effect.provide(Layer.fresh(scenario)));
1101
- return (input, context) => checked(input, context);
1112
+ const checked = (subject, input) => judge(judgedOver(contract, subject), input, { dumpName: title }).pipe(Effect.tap((judgment) => announce(judgment.verdict, judgment.dumpPath)), Effect.map((judgment) => isHold(judgment.verdict) && consumedInput(judgment, input)), Effect.provide(Layer.fresh(scenario)));
1113
+ return (subject, input) => checked(subject, input);
1102
1114
  };
1103
1115
  const predicate = dual(3, predicateImpl);
1104
1116
  //#endregion
@@ -1146,7 +1158,10 @@ const caseBody = (contract, input) => check(contract, input).pipe(Effect.catchIf
1146
1158
  const caseTools = (register, propIt, scenario) => {
1147
1159
  const Case = (name, contract, input) => register(name, caseBody(contract, input), "run");
1148
1160
  Case.prop = (name, contract, arbitrary) => {
1149
- propIt.effect.prop(name, [arbitrary], (values, context) => predicate(name, contract, scenario)(values[0], context));
1161
+ propIt.effect.prop(name, {
1162
+ of: [arbitrary],
1163
+ subject: contract.stimulus
1164
+ }, (subject, values) => predicate(name, contract, scenario)(subject, values[0]));
1150
1165
  };
1151
1166
  return { Case };
1152
1167
  };
@@ -1181,4 +1196,4 @@ const make = (bindings) => (name) => ({
1181
1196
  ...Prototype
1182
1197
  });
1183
1198
  //#endregion
1184
- export { Contract_exports as Contract, Graph_exports as Graph, Observation_service_exports as Observation, observation_window_resource_exports as ObservationWindow, Rel_exports as Rel, RemoteObservation_exports as RemoteObservation, Stimulus_exports as Stimulus, Suite_exports as Suite, tempo_trace_store_exports as TempoTraceStore };
1199
+ export { Contract_exports as Contract, Graph_exports as Graph, Observation_service_exports as Observation, observation_window_blueprint_exports as ObservationWindow, Rel_exports as Rel, RemoteObservation_exports as RemoteObservation, Stimulus_exports as Stimulus, Suite_exports as Suite, tempo_trace_store_exports as TempoTraceStore };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@systemfsoftware/trace-spec",
3
3
  "license": "Apache-2.0",
4
- "version": "0.2.0",
4
+ "version": "0.3.0",
5
5
  "author": "Ryan Lee <drdgvhbh@gmail.com>",
6
6
  "repository": {
7
7
  "type": "git",
@@ -33,25 +33,26 @@
33
33
  "dist"
34
34
  ],
35
35
  "dependencies": {
36
- "@effect/opentelemetry": "4.0.0-rc.116",
36
+ "@effect/opentelemetry": "4.0.0-rc.117",
37
37
  "@opentelemetry/api": "^1.9.0",
38
38
  "@opentelemetry/resources": "^2",
39
39
  "@opentelemetry/sdk-trace-base": "^2",
40
40
  "@opentelemetry/semantic-conventions": "^1.33",
41
- "@systemfsoftware/effect-spec-runtime": "^0.1.0",
42
- "@systemfsoftware/trace-taxonomy": "^0.0.0"
41
+ "@systemfsoftware/effect-cell-types": "^10.2.0",
42
+ "@systemfsoftware/effect-spec-runtime": "^0.2.0",
43
+ "@systemfsoftware/trace-taxonomy": "^0.0.1"
43
44
  },
44
45
  "devDependencies": {
45
- "@effect/platform-node": "4.0.0-rc.116",
46
- "@effect/vitest": "4.0.0-rc.116",
46
+ "@effect/platform-node": "4.0.0-rc.117",
47
+ "@effect/vitest": "npm:@systemfsoftware/vitest@0.0.0",
47
48
  "@systemfsoftware/arethetypeswrong-cli": "^4.2.0",
48
- "@systemfsoftware/effect-gherkin-spec": "^4.3.0",
49
- "@systemfsoftware/oxlint-config-recommended": "^2.0.0",
50
- "@systemfsoftware/tsconfig": "^2.0.0",
49
+ "@systemfsoftware/effect-gherkin-spec": "^4.4.0",
50
+ "@systemfsoftware/oxlint-config-recommended": "^3.0.0",
51
+ "@systemfsoftware/tsconfig": "^2.0.1",
51
52
  "@systemfsoftware/tsdown-config": "^0.1.0",
52
53
  "@systemfsoftware/vitest-config": "^0.1.0",
53
54
  "@types/node": "^26",
54
- "effect": "4.0.0-rc.116",
55
+ "effect": "4.0.0-rc.117",
55
56
  "oxlint": "~1.82.0",
56
57
  "rimraf": "^6.1.3",
57
58
  "tsdown": "^0.23.0",
@@ -59,8 +60,8 @@
59
60
  "vitest": "^5"
60
61
  },
61
62
  "peerDependencies": {
62
- "@effect/vitest": "4.0.0-rc.116",
63
- "effect": "4.0.0-rc.116",
63
+ "@effect/vitest": "npm:@systemfsoftware/vitest@*",
64
+ "effect": "4.0.0-rc.117",
64
65
  "vitest": "^5"
65
66
  },
66
67
  "publishConfig": {