@systemfsoftware/trace-spec 0.3.0 → 0.5.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 +38 -0
- package/README.md +19 -8
- package/dist/index.d.ts +111 -47
- package/dist/index.mjs +218 -129
- package/package.json +12 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# @systemfsoftware/trace-spec
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- `Rel.Hold`, `Rel.Break` and `ObservationWindow.ObservationWindowSpec` are schema structs instead of classes, with unchanged encoded shapes: build them with `.make(...)` instead of `new`. The `VerdictTypeId` type is removed. `IncompleteObservationError.spanCount` must be a non-negative integer, and span `startMillis` and `durationMillis` must be finite and non-negative.
|
|
8
|
+
|
|
9
|
+
- `Contract.check(expect, input, options?)` takes the test's `expect` and ends in one check: a break fails it with the report as the message. `Contract.judge` returns the `{ verdict: 'Hold' | 'Break', report }` value, and `Contract.verdictCheck(contract, expect, judgment)` turns one into a check. `TraceDisparityError` is removed. `Suite` cases register as generator tests and judge each case once.
|
|
10
|
+
|
|
11
|
+
- The lawful test runner is now a peer dependency under its own name, `@systemfsoftware/vitest`, instead of the `@effect/vitest` alias that pointed at it. Install `@systemfsoftware/vitest` and import `it`, `layer`, `expect`, and the rest from `@systemfsoftware/vitest`. `@systemfsoftware/effect-gherkin-spec` re-exports `it` and `layer` from `@systemfsoftware/vitest`.
|
|
12
|
+
|
|
13
|
+
- A broken contract now fails the check with `BreakFailure`, whose message is the break report, pointing at the line that called the check.
|
|
14
|
+
|
|
15
|
+
- Cases now run on the simulation kernel — the zero-preemption schedule, then the profile's seeded schedules — instead of always on the live clock, and each case draws its own crypto-random salt, so two cases never share a trace id.
|
|
16
|
+
|
|
17
|
+
- `Suite.make({ it, layer })` → `Suite.make({ it })`.
|
|
18
|
+
- A case that must stay live declares a reason on any stage: `.live('...')`, or the new `Suite.live` combinator (`self.pipe(Suite.live(reason))`).
|
|
19
|
+
- `withLayer(shared)` rebuilds the shared layer freshly for every case rather than once per suite.
|
|
20
|
+
- `Stimulus.traceContext` draws its ids through Effect's `Random`, so a seeded run replays the same trace ids.
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- Update peer and runtime dependency on `effect` and companion packages to `4.0.0-rc.117`.
|
|
25
|
+
|
|
26
|
+
- Updated dependencies:
|
|
27
|
+
- @systemfsoftware/trace-taxonomy@0.0.2
|
|
28
|
+
- @systemfsoftware/vitest@0.2.0
|
|
29
|
+
|
|
30
|
+
## 0.4.0
|
|
31
|
+
|
|
32
|
+
### Minor Changes
|
|
33
|
+
|
|
34
|
+
- `Contract.check(expect, input, options?)` takes the test's `expect` and ends in one check: a break fails it with the report as the message. `Contract.judge` returns the `{ verdict: 'Hold' | 'Break', report }` value, and `Contract.verdictCheck(contract, expect, judgment)` turns one into a check. `TraceDisparityError` is removed. `Suite` cases register as generator tests and judge each case once.
|
|
35
|
+
|
|
36
|
+
### Patch Changes
|
|
37
|
+
|
|
38
|
+
- Updated dependencies:
|
|
39
|
+
- @systemfsoftware/vitest@0.1.0
|
|
40
|
+
|
|
3
41
|
## 0.3.0
|
|
4
42
|
|
|
5
43
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ In a system with queues, workers, and background consumers, a `200` is not the o
|
|
|
10
10
|
pnpm add @systemfsoftware/trace-spec @systemfsoftware/trace-taxonomy effect
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
The package exports nine namespaces — `Contract`, `Graph`, `Observation`, `ObservationWindow`, `RemoteObservation`, `Rel`, `Stimulus`, `Suite`, `TempoTraceStore`. Errors
|
|
13
|
+
The package exports nine namespaces — `Contract`, `Graph`, `Observation`, `ObservationWindow`, `RemoteObservation`, `Rel`, `Stimulus`, `Suite`, `TempoTraceStore`. Errors belong to the capability that raises them: `Contract.ContractDecodeError`, `Observation.EmptyObservationError`, `Observation.IncompleteObservationError`, `Observation.TransportObservationError`, `Suite.StimulusFailure`; the verdicts are `Rel.Hold`/`Rel.Break`/`Rel.Verdict`. A break is not an error: it is the check the contract states in the test, and the report is that check's message.
|
|
14
14
|
|
|
15
15
|
## Write a contract
|
|
16
16
|
|
|
@@ -40,11 +40,22 @@ const paymentUnderCheckout = Contract.of(checkout)
|
|
|
40
40
|
|
|
41
41
|
`Contract.judge(contract, input)` stimulates the behaviour under its minted trace, collects that trace's spans, decodes them against the taxonomy (`Graph.decode` answers `Result<TraceGraph, Contract.ContractDecodeError>`), applies the relation, and on a break writes the decoded graph under `artifacts/traces/`. It answers a `Contract.Judgment` — the run, the verdict, and the dump path — so a break is a value, not a failure. Behaviour failures and infrastructure refusals (`Contract.ContractDecodeError` and the three `Observation` failures) stay on the error channel.
|
|
42
42
|
|
|
43
|
-
`Contract.check(contract, input)` is the test edge over `judge
|
|
43
|
+
`Contract.check(contract, expect, input)` is the test edge over `judge`, and takes the `expect` the test body received. It ends in the judgement's one check: a hold is the check passing, and a break fails it with the report — the relation that broke, the conjunct it was evaluating, the spans it inspected, the trace, and where the decoded graph was written. `Contract.verdictCheck(contract, expect, judgment)` states an already judged contract the same way, and `Contract.Report` — `{ verdict, report }`, the report empty on a hold — is what the check asserts.
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { Contract } from '@systemfsoftware/trace-spec'
|
|
47
|
+
import { it } from '@systemfsoftware/vitest'
|
|
48
|
+
|
|
49
|
+
it('a charged settlement satisfies the contract', function*({ expect }) {
|
|
50
|
+
yield* Contract.check(chargeBeneathSettlement, expect, { orderId: 'order-7', charge: true })
|
|
51
|
+
})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Navigation over the decoded graph is standalone: `Graph.byId`, `Graph.children`, and `Graph.descendants`.
|
|
44
55
|
|
|
45
56
|
| Outcome | Failure | Meaning |
|
|
46
57
|
| --------------------------------------------- | ---------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
47
|
-
| The relation broke |
|
|
58
|
+
| The relation broke | the judgement's check | its message is the report: the broken conjunct, the spans inspected, the trace and the dump path |
|
|
48
59
|
| A contracted span lacked a required attribute | `Contract.ContractDecodeError` | names the declaration and the attribute — never reported as a broken relation |
|
|
49
60
|
| Nothing ran under the owned trace | `Observation.EmptyObservationError` | the observation was empty, which is its own outcome, not a break |
|
|
50
61
|
| The trace never finished arriving | `Observation.IncompleteObservationError` | spans were read but kept changing, or the store marked the trace partial; carries the span count |
|
|
@@ -54,11 +65,11 @@ const paymentUnderCheckout = Contract.of(checkout)
|
|
|
54
65
|
|
|
55
66
|
```ts
|
|
56
67
|
import { NodeFileSystem } from '@effect/platform-node'
|
|
57
|
-
import { it, layer } from '@effect/vitest'
|
|
58
68
|
import { Observation, ObservationWindow, Suite } from '@systemfsoftware/trace-spec'
|
|
69
|
+
import { it } from '@systemfsoftware/vitest'
|
|
59
70
|
import { Layer } from 'effect'
|
|
60
71
|
|
|
61
|
-
const TraceSuite = Suite.make({ it
|
|
72
|
+
const TraceSuite = Suite.make({ it })
|
|
62
73
|
const harness = Layer.mergeAll(ObservationWindow.make('checkout').layer, NodeFileSystem.layer, CheckoutDoubles)
|
|
63
74
|
|
|
64
75
|
TraceSuite('checkout.place_order')
|
|
@@ -68,11 +79,11 @@ TraceSuite('checkout.place_order')
|
|
|
68
79
|
})
|
|
69
80
|
```
|
|
70
81
|
|
|
71
|
-
`Suite.make({ it
|
|
82
|
+
`Suite.make({ it })(name)` stages a suite as a Pipeable builder, and `withLayer`/`withScenarioLayer` are `dual` combinators whose terminal `body` registers the cases. The scenario layer is built fresh per case and must provide `Observation.Observation` and a `FileSystem` for failure dumps, plus whatever the stimulated behaviour needs — the requirement is enforced at the type level. A failing case fails with the harness's own error channel — the outcomes above, or `Suite.StimulusFailure` when the behaviour itself failed before its trace could be judged. Cases run on the kernel under the zero-preemption schedule plus the profile's seeded PCT schedules; every case gets its own crypto-random salt, so two cases never share a trace id. A case that must stay on the live clock declares why with `.live(reason)` at any stage before `body` (including the `Suite.live` dual), and the reason appears in the run report.
|
|
72
83
|
|
|
73
|
-
`.withLayer(shared)` registers the suite over a layer
|
|
84
|
+
`.withLayer(shared)` registers the suite over a layer rebuilt fresh for each case for replay; the shared layer then carries the observation harness, and `.withScenarioLayer(scenario)` can follow it for what each case needs fresh.
|
|
74
85
|
|
|
75
|
-
`Case.prop(title, contract, schema)` runs the contract over inputs drawn from the Schema's derived arbitrary through `it.effect.prop` on the suite's runner, so shrinking is the runner's own: a failing draw fails the property and the runner reports the smallest failing input. The dump is named after the case title, so every failing draw overwrites one file and the surviving dump is the reported counterexample's evidence. When a case fails with
|
|
86
|
+
`Case.prop(title, contract, schema)` runs the contract over inputs drawn from the Schema's derived arbitrary through `it.effect.prop` on the suite's runner, so shrinking is the runner's own: a failing draw fails the property and the runner reports the smallest failing input. The dump is named after the case title, so every failing draw overwrites one file and the surviving dump is the reported counterexample's evidence. When a case's relation breaks, its check fails with the report and the test carries a Vitest annotation naming that dump, so the decoded graph is found from the failing test.
|
|
76
87
|
|
|
77
88
|
## Observe in memory
|
|
78
89
|
|
package/dist/index.d.ts
CHANGED
|
@@ -4,10 +4,19 @@ 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
6
|
import { Blueprint, Handle } from "@systemfsoftware/effect-cell-types";
|
|
7
|
-
import { BasicTracerProvider,
|
|
7
|
+
import { BasicTracerProvider, ReadableSpan } from "@opentelemetry/sdk-trace-base";
|
|
8
8
|
import { Suite } from "@systemfsoftware/effect-spec-runtime";
|
|
9
|
+
import { Asserted, Check, Expect } from "@systemfsoftware/vitest";
|
|
9
10
|
import * as Scope from "effect/Scope";
|
|
10
11
|
import * as Arbitrary from "effect/unstable/arbitrary/Arbitrary";
|
|
12
|
+
//#region src/BreakFailure.schema.d.ts
|
|
13
|
+
declare const BreakFailure_base: Schema.Class<BreakFailure, Schema.TaggedStruct<"BreakFailure", {
|
|
14
|
+
readonly report: Schema.String;
|
|
15
|
+
}>, import("effect/Cause").YieldableError>;
|
|
16
|
+
declare class BreakFailure extends BreakFailure_base {
|
|
17
|
+
get message(): string;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
11
20
|
//#region src/ContractDecodeError.schema.d.ts
|
|
12
21
|
declare const ContractDecodeError_base: Schema.Class<ContractDecodeError, Schema.TaggedStruct<"ContractDecodeError", {
|
|
13
22
|
readonly declarationId: Schema.String;
|
|
@@ -16,22 +25,28 @@ declare const ContractDecodeError_base: Schema.Class<ContractDecodeError, Schema
|
|
|
16
25
|
readonly attribute: Schema.String;
|
|
17
26
|
readonly detail: Schema.String;
|
|
18
27
|
}>, import("effect/Cause").YieldableError>;
|
|
19
|
-
declare class ContractDecodeError extends ContractDecodeError_base {
|
|
28
|
+
declare class ContractDecodeError extends ContractDecodeError_base {
|
|
29
|
+
get message(): string;
|
|
30
|
+
}
|
|
20
31
|
//#endregion
|
|
21
32
|
//#region src/EmptyObservationError.schema.d.ts
|
|
22
33
|
declare const EmptyObservationError_base: Schema.Class<EmptyObservationError, Schema.TaggedStruct<"EmptyObservationError", {
|
|
23
34
|
readonly traceId: Schema.String;
|
|
24
35
|
readonly detail: Schema.String;
|
|
25
36
|
}>, import("effect/Cause").YieldableError>;
|
|
26
|
-
declare class EmptyObservationError extends EmptyObservationError_base {
|
|
37
|
+
declare class EmptyObservationError extends EmptyObservationError_base {
|
|
38
|
+
get message(): string;
|
|
39
|
+
}
|
|
27
40
|
//#endregion
|
|
28
41
|
//#region src/IncompleteObservationError.schema.d.ts
|
|
29
42
|
declare const IncompleteObservationError_base: Schema.Class<IncompleteObservationError, Schema.TaggedStruct<"IncompleteObservationError", {
|
|
30
43
|
readonly traceId: Schema.String;
|
|
31
|
-
readonly spanCount: Schema.
|
|
44
|
+
readonly spanCount: Schema.Int;
|
|
32
45
|
readonly detail: Schema.String;
|
|
33
46
|
}>, import("effect/Cause").YieldableError>;
|
|
34
|
-
declare class IncompleteObservationError extends IncompleteObservationError_base {
|
|
47
|
+
declare class IncompleteObservationError extends IncompleteObservationError_base {
|
|
48
|
+
get message(): string;
|
|
49
|
+
}
|
|
35
50
|
//#endregion
|
|
36
51
|
//#region src/TraceGraph.schema.d.ts
|
|
37
52
|
declare const Status: Schema.Literals<readonly ["ok", "unset", "error"]>;
|
|
@@ -144,7 +159,9 @@ declare const TransportObservationError_base: Schema.Class<TransportObservationE
|
|
|
144
159
|
readonly source: Schema.String;
|
|
145
160
|
readonly detail: Schema.String;
|
|
146
161
|
}>, import("effect/Cause").YieldableError>;
|
|
147
|
-
declare class TransportObservationError extends TransportObservationError_base {
|
|
162
|
+
declare class TransportObservationError extends TransportObservationError_base {
|
|
163
|
+
get message(): string;
|
|
164
|
+
}
|
|
148
165
|
declare namespace Observation_service_d_exports {
|
|
149
166
|
export { Collector, EmptyObservationError, IncompleteObservationError, Observation, ObservationFailure, TransportObservationError };
|
|
150
167
|
}
|
|
@@ -156,24 +173,25 @@ declare const Observation_base: Context.ServiceClass<Observation, "@systemfsoftw
|
|
|
156
173
|
declare class Observation extends Observation_base {}
|
|
157
174
|
//#endregion
|
|
158
175
|
//#region src/Verdict.schema.d.ts
|
|
159
|
-
declare const
|
|
160
|
-
type VerdictTypeId = typeof VerdictTypeId;
|
|
161
|
-
declare const Hold_base: Schema.Class<Hold, Schema.TaggedStruct<"Hold", {
|
|
176
|
+
declare const Hold: Schema.TaggedStruct<"Hold", {
|
|
162
177
|
readonly conjunct: Schema.String;
|
|
163
178
|
readonly inspected: Schema.$Array<Schema.String>;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
declare const Break_base: Schema.Class<Break, Schema.TaggedStruct<"Break", {
|
|
179
|
+
}>;
|
|
180
|
+
type Hold = typeof Hold.Type;
|
|
181
|
+
declare const Break: Schema.TaggedStruct<"Break", {
|
|
169
182
|
readonly conjunct: Schema.String;
|
|
170
183
|
readonly inspected: Schema.$Array<Schema.String>;
|
|
171
184
|
readonly detail: Schema.String;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
185
|
+
}>;
|
|
186
|
+
type Break = typeof Break.Type;
|
|
187
|
+
declare const Verdict: Schema.Union<readonly [Schema.TaggedStruct<"Hold", {
|
|
188
|
+
readonly conjunct: Schema.String;
|
|
189
|
+
readonly inspected: Schema.$Array<Schema.String>;
|
|
190
|
+
}>, Schema.TaggedStruct<"Break", {
|
|
191
|
+
readonly conjunct: Schema.String;
|
|
192
|
+
readonly inspected: Schema.$Array<Schema.String>;
|
|
193
|
+
readonly detail: Schema.String;
|
|
194
|
+
}>]>;
|
|
177
195
|
type Verdict = typeof Verdict.Type;
|
|
178
196
|
declare namespace Rel_d_exports {
|
|
179
197
|
export { Break, Hold, Relation, Verdict, absent, all, any, attrs, child, descendant, durationLessThan, errorType, event, exists, forall, fromTaxonomy, not, order, soft, status, unique };
|
|
@@ -261,17 +279,8 @@ declare const make$3: <Input, Output, E, R>(options: {
|
|
|
261
279
|
readonly traceparent: string;
|
|
262
280
|
}) => Effect.Effect<Output, E, R>;
|
|
263
281
|
}) => Stimulus<Input, Output, E, R>;
|
|
264
|
-
//#endregion
|
|
265
|
-
//#region src/TraceDisparityError.schema.d.ts
|
|
266
|
-
declare const TraceDisparityError_base: Schema.Class<TraceDisparityError, Schema.TaggedStruct<"TraceDisparityError", {
|
|
267
|
-
readonly relationId: Schema.String;
|
|
268
|
-
readonly traceId: Schema.String;
|
|
269
|
-
readonly breaks: Schema.$Array<typeof Break>;
|
|
270
|
-
readonly dumpPath: Schema.NullOr<Schema.String>;
|
|
271
|
-
}>, import("effect/Cause").YieldableError>;
|
|
272
|
-
declare class TraceDisparityError extends TraceDisparityError_base {}
|
|
273
282
|
declare namespace Contract_d_exports {
|
|
274
|
-
export {
|
|
283
|
+
export { BreakFailure, CheckOptions, Contract, ContractDecodeError, Declared$1 as Declared, EmptyObservationError, IncompleteObservationError, JudgeFailure, Judgment, Report, Services, Stimulated, TransportObservationError, TypeId$3 as TypeId, check, holds, judge, of, stimulate, verdictCheck };
|
|
275
284
|
}
|
|
276
285
|
declare const TypeId$3: unique symbol;
|
|
277
286
|
type TypeId$3 = typeof TypeId$3;
|
|
@@ -310,15 +319,38 @@ interface CheckOptions {
|
|
|
310
319
|
readonly dumpName?: string | undefined;
|
|
311
320
|
}
|
|
312
321
|
type JudgeFailure<E> = E | ContractDecodeError | ObservationFailure;
|
|
313
|
-
type CheckFailure<E> = JudgeFailure<E> | TraceDisparityError;
|
|
314
322
|
type Services<R> = R | Observation | FileSystem.FileSystem;
|
|
315
323
|
declare const judge: {
|
|
316
324
|
<Input>(input: Input, options?: CheckOptions): <Output, E, R>(self: Contract<Input, Output, E, R>) => Effect.Effect<Judgment<Input, Output>, JudgeFailure<E>, Services<R>>;
|
|
317
325
|
<Input, Output, E, R>(self: Contract<Input, Output, E, R>, input: Input, options?: CheckOptions): Effect.Effect<Judgment<Input, Output>, JudgeFailure<E>, Services<R>>;
|
|
318
326
|
};
|
|
327
|
+
/**
|
|
328
|
+
* What a judged contract answers: the verdict, and — when the relation broke — the report the judgement
|
|
329
|
+
* leaves behind. The report is empty only for a hold, so a check over it asserts the whole judgement.
|
|
330
|
+
*/
|
|
331
|
+
interface Report {
|
|
332
|
+
readonly verdict: 'Hold' | 'Break';
|
|
333
|
+
/**
|
|
334
|
+
* The relation that broke, the conjunct it was evaluating, the spans it inspected, the trace, and where the
|
|
335
|
+
* decoded graph was written.
|
|
336
|
+
*/
|
|
337
|
+
readonly report: string;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* The one check a judgement makes: it passes only when the relation held, and its message is the report, so a
|
|
341
|
+
* break names the conjunct that broke, the spans it inspected and where the evidence was written.
|
|
342
|
+
*/
|
|
343
|
+
declare const verdictCheck: {
|
|
344
|
+
<Input, Output, E, R>(expect: Expect, judgment: Judgment<Input, Output>): (self: Contract<Input, Output, E, R>) => Check;
|
|
345
|
+
<Input, Output, E, R>(self: Contract<Input, Output, E, R>, expect: Expect, judgment: Judgment<Input, Output>): Check;
|
|
346
|
+
};
|
|
347
|
+
/**
|
|
348
|
+
* The test edge over `judge`: the judgement and its one check. A break fails the check with the report;
|
|
349
|
+
* behaviour failures and infrastructure refusals stay on the error channel.
|
|
350
|
+
*/
|
|
319
351
|
declare const check: {
|
|
320
|
-
<Input>(input: Input, options?: CheckOptions): <Output, E, R>(self: Contract<Input, Output, E, R>) => Effect.Effect<
|
|
321
|
-
<Input, Output, E, R>(self: Contract<Input, Output, E, R>, input: Input, options?: CheckOptions): Effect.Effect<
|
|
352
|
+
<Input>(expect: Expect, input: Input, options?: CheckOptions): <Output, E, R>(self: Contract<Input, Output, E, R>) => Effect.Effect<void, JudgeFailure<E>, Asserted | Services<R>>;
|
|
353
|
+
<Input, Output, E, R>(self: Contract<Input, Output, E, R>, expect: Expect, input: Input, options?: CheckOptions): Effect.Effect<void, JudgeFailure<E>, Asserted | Services<R>>;
|
|
322
354
|
};
|
|
323
355
|
declare namespace RemoteObservation_d_exports {
|
|
324
356
|
export { Options, TraceSource, layer$1 as layer };
|
|
@@ -362,26 +394,32 @@ declare const source: (options: {
|
|
|
362
394
|
}) => TraceSource<HttpClient.HttpClient>;
|
|
363
395
|
//#endregion
|
|
364
396
|
//#region src/ObservationWindowSpec.schema.d.ts
|
|
365
|
-
declare const
|
|
397
|
+
declare const ObservationWindowSpec: Schema.Struct<{
|
|
366
398
|
readonly serviceName: Schema.NonEmptyString;
|
|
367
|
-
}
|
|
368
|
-
|
|
399
|
+
}>;
|
|
400
|
+
type ObservationWindowSpec = typeof ObservationWindowSpec.Type;
|
|
369
401
|
declare namespace observation_window_handle_d_exports {
|
|
370
402
|
export { ObservationWindow, TypeId$2 as TypeId, collect, context, isObservationWindow, make$2 as make, shutdown };
|
|
371
403
|
}
|
|
372
404
|
declare const TypeId$2: unique symbol;
|
|
373
405
|
type TypeId$2 = typeof TypeId$2;
|
|
406
|
+
/**
|
|
407
|
+
* The finished-span storage a window reads back through.
|
|
408
|
+
*/
|
|
409
|
+
interface SpanRecordStore {
|
|
410
|
+
readonly getFinishedSpans: () => ReadonlyArray<ReadableSpan>;
|
|
411
|
+
}
|
|
374
412
|
declare const ObservationWindow: Handle.Definition<typeof TypeId$2, {
|
|
375
413
|
readonly serviceName: string;
|
|
376
414
|
}, {
|
|
377
|
-
readonly
|
|
415
|
+
readonly store: SpanRecordStore;
|
|
378
416
|
readonly provider: BasicTracerProvider;
|
|
379
417
|
}, never>;
|
|
380
418
|
type ObservationWindow = Handle.Of<typeof ObservationWindow>;
|
|
381
419
|
declare const isObservationWindow: (u: unknown) => u is Handle.Handle<typeof TypeId$2, {
|
|
382
420
|
readonly serviceName: string;
|
|
383
421
|
}, {
|
|
384
|
-
readonly
|
|
422
|
+
readonly store: SpanRecordStore;
|
|
385
423
|
readonly provider: BasicTracerProvider;
|
|
386
424
|
}, never>;
|
|
387
425
|
declare const make$2: (spec: ObservationWindowSpec) => ObservationWindow;
|
|
@@ -398,15 +436,21 @@ declare const TypeId$1: unique symbol;
|
|
|
398
436
|
type TypeId$1 = typeof TypeId$1;
|
|
399
437
|
declare const scoped: (spec: ObservationWindowSpec) => Effect.Effect<ObservationWindow, never, Scope.Scope>;
|
|
400
438
|
declare const layer: (spec: ObservationWindowSpec) => Layer.Layer<Observation | OtelTracer.OtelTracer>;
|
|
401
|
-
declare const ObservationWindows: Blueprint.Definition<typeof TypeId$1,
|
|
439
|
+
declare const ObservationWindows: Blueprint.Definition<typeof TypeId$1, {
|
|
440
|
+
readonly serviceName: string;
|
|
441
|
+
}, Blueprint.Derived<{
|
|
442
|
+
readonly serviceName: string;
|
|
443
|
+
}, {}, {
|
|
402
444
|
readonly scoped: typeof scoped;
|
|
403
445
|
readonly layer: typeof layer;
|
|
404
|
-
}>,
|
|
446
|
+
}>, {
|
|
447
|
+
readonly serviceName: string;
|
|
448
|
+
}>;
|
|
405
449
|
type ObservationWindowBlueprint = Blueprint.Of<typeof ObservationWindows>;
|
|
406
450
|
declare const make$1: (serviceName: string) => ObservationWindowBlueprint;
|
|
407
451
|
//#endregion
|
|
408
452
|
//#region src/HarnessFailure.schema.d.ts
|
|
409
|
-
declare const HarnessFailure: Schema.Union<readonly [typeof ContractDecodeError, typeof EmptyObservationError, typeof IncompleteObservationError, typeof TransportObservationError
|
|
453
|
+
declare const HarnessFailure: Schema.Union<readonly [typeof ContractDecodeError, typeof EmptyObservationError, typeof IncompleteObservationError, typeof TransportObservationError]>;
|
|
410
454
|
type HarnessFailure = typeof HarnessFailure.Type;
|
|
411
455
|
//#endregion
|
|
412
456
|
//#region src/StimulusFailure.schema.d.ts
|
|
@@ -414,13 +458,21 @@ declare const StimulusFailure_base: Schema.Class<StimulusFailure, Schema.TaggedS
|
|
|
414
458
|
readonly stimulus: Schema.String;
|
|
415
459
|
readonly detail: Schema.String;
|
|
416
460
|
}>, import("effect/Cause").YieldableError>;
|
|
417
|
-
declare class StimulusFailure extends StimulusFailure_base {
|
|
461
|
+
declare class StimulusFailure extends StimulusFailure_base {
|
|
462
|
+
get message(): string;
|
|
463
|
+
}
|
|
418
464
|
declare namespace Suite_d_exports {
|
|
419
|
-
export { ArbitraryInput, CaseFailure, CaseRegistrar, CaseTools, Declared, Harness, Opened, Shared, StimulusFailure, TypeId, make, withLayer, withScenarioLayer };
|
|
465
|
+
export { ArbitraryInput, CaseFailure, CaseRegistrar, CaseTools, Declared, Harness, LiveStage, Opened, Shared, StimulusFailure, TypeId, live, make, withLayer, withScenarioLayer };
|
|
420
466
|
}
|
|
421
467
|
declare const TypeId: unique symbol;
|
|
422
468
|
type TypeId = typeof TypeId;
|
|
423
469
|
type CaseFailure = HarnessFailure | StimulusFailure;
|
|
470
|
+
/**
|
|
471
|
+
* What every case's layers carry around the body: the observation harness and
|
|
472
|
+
* the file system failure dumps are written through. Trace ids draw through
|
|
473
|
+
* the `Random` reference, which the runner salts per case with
|
|
474
|
+
* `Random.withSeed`, so no layer has to provide it.
|
|
475
|
+
*/
|
|
424
476
|
type Harness = Observation | FileSystem.FileSystem;
|
|
425
477
|
type ArbitraryInput<Input> = Schema.Schema<Input> | Arbitrary.Arbitrary<Input>;
|
|
426
478
|
interface CaseRegistrar<Provided> {
|
|
@@ -430,18 +482,26 @@ interface CaseRegistrar<Provided> {
|
|
|
430
482
|
interface CaseTools<Provided> {
|
|
431
483
|
readonly Case: CaseRegistrar<Provided>;
|
|
432
484
|
}
|
|
433
|
-
|
|
485
|
+
/**
|
|
486
|
+
* Every stage that can still reach `body` also declares whether its cases stay
|
|
487
|
+
* on the live clock, and a live declaration carries the reason it stayed there
|
|
488
|
+
* (KTD11). Without one, the stage's cases run on the kernel.
|
|
489
|
+
*/
|
|
490
|
+
interface LiveStage<Self> {
|
|
491
|
+
readonly live: (reason: string) => Self;
|
|
492
|
+
}
|
|
493
|
+
interface Opened<Provided> extends LiveStage<Opened<Provided>> {
|
|
434
494
|
readonly body: (use: (tools: CaseTools<Provided>) => void) => void;
|
|
435
495
|
}
|
|
436
|
-
interface Declared extends Pipeable {
|
|
496
|
+
interface Declared extends Pipeable, LiveStage<Declared> {
|
|
437
497
|
readonly [TypeId]: typeof TypeId;
|
|
438
498
|
readonly withLayer: <SharedProvided>(shared: Layer.Layer<SharedProvided | Harness>) => Shared<SharedProvided>;
|
|
439
499
|
readonly withScenarioLayer: <Provided>(scenario: Layer.Layer<Provided | Harness>) => Opened<Provided | Harness>;
|
|
440
500
|
}
|
|
441
|
-
interface Shared<SharedProvided> extends Pipeable {
|
|
501
|
+
interface Shared<SharedProvided> extends Pipeable, LiveStage<Shared<SharedProvided>> {
|
|
442
502
|
readonly [TypeId]: typeof TypeId;
|
|
443
|
-
readonly withScenarioLayer: <Provided>(scenario: Layer.Layer<Provided | Harness, never, SharedProvided>) => Opened<Provided | SharedProvided>;
|
|
444
|
-
readonly body: (use: (tools: CaseTools<SharedProvided>) => void) => void;
|
|
503
|
+
readonly withScenarioLayer: <Provided>(scenario: Layer.Layer<Provided | Harness, never, SharedProvided>) => Opened<Provided | SharedProvided | Harness>;
|
|
504
|
+
readonly body: (use: (tools: CaseTools<SharedProvided | Harness>) => void) => void;
|
|
445
505
|
}
|
|
446
506
|
declare const withLayer: {
|
|
447
507
|
<SharedProvided>(shared: Layer.Layer<SharedProvided | Harness>): (self: Declared) => Shared<SharedProvided>;
|
|
@@ -451,6 +511,10 @@ declare const withScenarioLayer: {
|
|
|
451
511
|
<Provided>(scenario: Layer.Layer<Provided | Harness>): (self: Declared) => Opened<Provided | Harness>;
|
|
452
512
|
<Provided>(self: Declared, scenario: Layer.Layer<Provided | Harness>): Opened<Provided | Harness>;
|
|
453
513
|
};
|
|
514
|
+
declare const live: {
|
|
515
|
+
(reason: string): <Self>(self: LiveStage<Self>) => Self;
|
|
516
|
+
<Self>(self: LiveStage<Self>, reason: string): Self;
|
|
517
|
+
};
|
|
454
518
|
declare const make: (bindings: Suite.Bindings) => (name: string) => Declared;
|
|
455
519
|
//#endregion
|
|
456
520
|
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,10 +1,12 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
-
import {
|
|
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";
|
|
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
4
|
import { dual } from "effect/Function";
|
|
5
5
|
import { Prototype } from "effect/Pipeable";
|
|
6
6
|
import "effect/PlatformError";
|
|
7
7
|
import "@systemfsoftware/trace-taxonomy";
|
|
8
|
+
import "effect/Array";
|
|
9
|
+
import "effect/Result";
|
|
8
10
|
import * as HttpClient from "effect/unstable/http/HttpClient";
|
|
9
11
|
import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
|
|
10
12
|
import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse";
|
|
@@ -12,8 +14,15 @@ import * as OtelTracer from "@effect/opentelemetry/OtelTracer";
|
|
|
12
14
|
import * as OtelResource from "@effect/opentelemetry/Resource";
|
|
13
15
|
import { Blueprint, Handle } from "@systemfsoftware/effect-cell-types";
|
|
14
16
|
import { SpanStatusCode } from "@opentelemetry/api";
|
|
15
|
-
import { AlwaysOnSampler, BasicTracerProvider
|
|
17
|
+
import { AlwaysOnSampler, BasicTracerProvider } from "@opentelemetry/sdk-trace-base";
|
|
16
18
|
import { Suite, TaskRef } from "@systemfsoftware/effect-spec-runtime";
|
|
19
|
+
//#region src/BreakFailure.schema.ts
|
|
20
|
+
var BreakFailure = class extends Schema.TaggedError()("BreakFailure", { report: Schema.String }) {
|
|
21
|
+
get message() {
|
|
22
|
+
return this.report;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
//#endregion
|
|
17
26
|
//#region src/ContractDecodeError.schema.ts
|
|
18
27
|
var ContractDecodeError = class extends Schema.TaggedError()("ContractDecodeError", {
|
|
19
28
|
declarationId: Schema.String,
|
|
@@ -21,38 +30,41 @@ var ContractDecodeError = class extends Schema.TaggedError()("ContractDecodeErro
|
|
|
21
30
|
spanId: Schema.String,
|
|
22
31
|
attribute: Schema.String,
|
|
23
32
|
detail: Schema.String
|
|
24
|
-
}) {
|
|
33
|
+
}) {
|
|
34
|
+
get message() {
|
|
35
|
+
return `Span "${this.spanName}" (${this.spanId}) does not satisfy contract "${this.declarationId}": ${this.detail}`;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
25
38
|
//#endregion
|
|
26
39
|
//#region src/EmptyObservationError.schema.ts
|
|
27
40
|
var EmptyObservationError = class extends Schema.TaggedError()("EmptyObservationError", {
|
|
28
41
|
traceId: Schema.String,
|
|
29
42
|
detail: Schema.String
|
|
30
|
-
}) {
|
|
43
|
+
}) {
|
|
44
|
+
get message() {
|
|
45
|
+
return `Trace "${this.traceId}" recorded no spans: ${this.detail}`;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
31
48
|
//#endregion
|
|
32
49
|
//#region src/Verdict.schema.ts
|
|
33
|
-
const
|
|
34
|
-
var Hold = class extends Schema.TaggedClass()("Hold", {
|
|
50
|
+
const Hold = Schema.TaggedStruct("Hold", {
|
|
35
51
|
conjunct: Schema.String,
|
|
36
52
|
inspected: Schema.Array(Schema.String)
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
};
|
|
40
|
-
var Break = class extends Schema.TaggedClass()("Break", {
|
|
53
|
+
});
|
|
54
|
+
const Break = Schema.TaggedStruct("Break", {
|
|
41
55
|
conjunct: Schema.String,
|
|
42
56
|
inspected: Schema.Array(Schema.String),
|
|
43
57
|
detail: Schema.String
|
|
44
|
-
})
|
|
45
|
-
[VerdictTypeId] = VerdictTypeId;
|
|
46
|
-
};
|
|
58
|
+
});
|
|
47
59
|
const Verdict = Schema.Union([Hold, Break]);
|
|
48
60
|
//#endregion
|
|
49
61
|
//#region src/FailureDump.ts
|
|
50
62
|
const DEFAULT_DIRECTORY = "artifacts/traces";
|
|
51
|
-
const isBreak$
|
|
63
|
+
const isBreak$1 = Schema.is(Break);
|
|
52
64
|
/**
|
|
53
65
|
* The rendered evidence a break carries; a held verdict leaves nothing to write.
|
|
54
66
|
*/
|
|
55
|
-
const report = (verdict) => Option.map(Option.liftPredicate(verdict, isBreak$
|
|
67
|
+
const report = (verdict) => Option.map(Option.liftPredicate(verdict, isBreak$1), renderBreach);
|
|
56
68
|
const renderValue = (value) => JSON.stringify(value);
|
|
57
69
|
const renderEntries = (entries) => [...entries].map(([key, value]) => `${key}=${renderValue(value)}`).join(" ");
|
|
58
70
|
const indentOf = (depth) => " ".repeat(depth);
|
|
@@ -117,6 +129,7 @@ const SpanLink = Schema.Struct({
|
|
|
117
129
|
traceId: Schema.String,
|
|
118
130
|
spanId: Schema.String
|
|
119
131
|
});
|
|
132
|
+
const SpanMillis = Schema.Finite.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)));
|
|
120
133
|
const SpanRecord = Schema.Struct({
|
|
121
134
|
traceId: Schema.String,
|
|
122
135
|
spanId: Schema.String,
|
|
@@ -124,8 +137,8 @@ const SpanRecord = Schema.Struct({
|
|
|
124
137
|
name: Schema.String,
|
|
125
138
|
status: Status,
|
|
126
139
|
errorType: Schema.NullOr(Schema.String),
|
|
127
|
-
startMillis:
|
|
128
|
-
durationMillis:
|
|
140
|
+
startMillis: SpanMillis,
|
|
141
|
+
durationMillis: SpanMillis,
|
|
129
142
|
attributes: Attributes,
|
|
130
143
|
events: Schema.Array(SpanEvent),
|
|
131
144
|
links: Schema.Array(SpanLink)
|
|
@@ -136,8 +149,8 @@ const GraphNode = Schema.Struct({
|
|
|
136
149
|
name: Schema.String,
|
|
137
150
|
status: Status,
|
|
138
151
|
errorType: Schema.NullOr(Schema.String),
|
|
139
|
-
startMillis:
|
|
140
|
-
durationMillis:
|
|
152
|
+
startMillis: SpanMillis,
|
|
153
|
+
durationMillis: SpanMillis,
|
|
141
154
|
attrs: Attributes,
|
|
142
155
|
events: Schema.Array(SpanEvent),
|
|
143
156
|
links: Schema.Array(SpanLink)
|
|
@@ -227,18 +240,27 @@ const decodeImpl = (traceId, spans, taxonomy) => {
|
|
|
227
240
|
const decode = dual(3, decodeImpl);
|
|
228
241
|
//#endregion
|
|
229
242
|
//#region src/IncompleteObservationError.schema.ts
|
|
243
|
+
const SpanCount = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0, { message: "a span count is a non-negative integer" })));
|
|
230
244
|
var IncompleteObservationError = class extends Schema.TaggedError()("IncompleteObservationError", {
|
|
231
245
|
traceId: Schema.String,
|
|
232
|
-
spanCount:
|
|
246
|
+
spanCount: SpanCount,
|
|
233
247
|
detail: Schema.String
|
|
234
|
-
}) {
|
|
248
|
+
}) {
|
|
249
|
+
get message() {
|
|
250
|
+
return `Trace "${this.traceId}" is incomplete after ${this.spanCount} spans: ${this.detail}`;
|
|
251
|
+
}
|
|
252
|
+
};
|
|
235
253
|
//#endregion
|
|
236
254
|
//#region src/TransportObservationError.schema.ts
|
|
237
255
|
var TransportObservationError = class extends Schema.TaggedError()("TransportObservationError", {
|
|
238
256
|
traceId: Schema.String,
|
|
239
257
|
source: Schema.String,
|
|
240
258
|
detail: Schema.String
|
|
241
|
-
}) {
|
|
259
|
+
}) {
|
|
260
|
+
get message() {
|
|
261
|
+
return `The trace store at "${this.source}" could not serve trace "${this.traceId}": ${this.detail}`;
|
|
262
|
+
}
|
|
263
|
+
};
|
|
242
264
|
//#endregion
|
|
243
265
|
//#region src/Observation.service.ts
|
|
244
266
|
var Observation_service_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -249,28 +271,23 @@ var Observation_service_exports = /* @__PURE__ */ __exportAll({
|
|
|
249
271
|
});
|
|
250
272
|
var Observation = class extends Context.Service()("@systemfsoftware/trace-spec/Observation") {};
|
|
251
273
|
//#endregion
|
|
252
|
-
//#region src/TraceDisparityError.schema.ts
|
|
253
|
-
var TraceDisparityError = class extends Schema.TaggedError()("TraceDisparityError", {
|
|
254
|
-
relationId: Schema.String,
|
|
255
|
-
traceId: Schema.String,
|
|
256
|
-
breaks: Schema.Array(Break),
|
|
257
|
-
dumpPath: Schema.NullOr(Schema.String)
|
|
258
|
-
}) {};
|
|
259
|
-
//#endregion
|
|
260
274
|
//#region src/Contract.ts
|
|
261
275
|
var Contract_exports = /* @__PURE__ */ __exportAll({
|
|
276
|
+
BreakFailure: () => BreakFailure,
|
|
262
277
|
ContractDecodeError: () => ContractDecodeError,
|
|
263
278
|
EmptyObservationError: () => EmptyObservationError,
|
|
264
279
|
IncompleteObservationError: () => IncompleteObservationError,
|
|
265
|
-
TraceDisparityError: () => TraceDisparityError,
|
|
266
280
|
TransportObservationError: () => TransportObservationError,
|
|
267
281
|
TypeId: () => TypeId$3,
|
|
268
282
|
check: () => check,
|
|
269
283
|
holds: () => holds,
|
|
270
284
|
judge: () => judge,
|
|
271
285
|
of: () => of,
|
|
272
|
-
stimulate: () => stimulate
|
|
286
|
+
stimulate: () => stimulate,
|
|
287
|
+
verdictCheck: () => verdictCheck
|
|
273
288
|
});
|
|
289
|
+
const OWN_FRAMES = /\/trace-spec\/(?:src|dist)\//u;
|
|
290
|
+
const callFrame = () => callFrameOutside(OWN_FRAMES);
|
|
274
291
|
const TypeId$3 = Symbol.for("@systemfsoftware/trace-spec/Contract");
|
|
275
292
|
const stimulatedOf = (taxonomy, stimulus) => ({
|
|
276
293
|
[TypeId$3]: TypeId$3,
|
|
@@ -322,13 +339,39 @@ const judgeDual = (self, input, options) => Effect.gen(function* () {
|
|
|
322
339
|
};
|
|
323
340
|
});
|
|
324
341
|
const judge = dual((args) => isContract(args[0]), judgeDual);
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
const
|
|
342
|
+
/** What a hold answers: nothing to report. */
|
|
343
|
+
const held = {
|
|
344
|
+
verdict: "Hold",
|
|
345
|
+
report: ""
|
|
346
|
+
};
|
|
347
|
+
const failureOfReport = (report) => new BreakFailure({ report: report.report });
|
|
348
|
+
const reportOf = (relationId, judgment) => Match.value(judgment.verdict).pipe(Match.tag("Hold", () => held), Match.tag("Break", (breach) => ({
|
|
349
|
+
verdict: "Break",
|
|
350
|
+
report: [
|
|
351
|
+
`trace contract broke: ${relationId}`,
|
|
352
|
+
Option.getOrElse(report(breach), () => ""),
|
|
353
|
+
`trace ${judgment.run.traceId}`,
|
|
354
|
+
`dump ${judgment.dumpPath ?? "(not written)"}`
|
|
355
|
+
].join("\n")
|
|
356
|
+
})), Match.exhaustive);
|
|
357
|
+
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);
|
|
360
|
+
};
|
|
361
|
+
const verdictCheckImpl = (self, expect, judgment) => verdictCheckWith(self, expect, judgment, callFrame());
|
|
362
|
+
/**
|
|
363
|
+
* The one check a judgement makes: it passes only when the relation held, and its message is the report, so a
|
|
364
|
+
* break names the conjunct that broke, the spans it inspected and where the evidence was written.
|
|
365
|
+
*/
|
|
366
|
+
const verdictCheck = dual(3, verdictCheckImpl);
|
|
367
|
+
const checkDual = (self, expect, input, options) => {
|
|
368
|
+
const frame = callFrame();
|
|
369
|
+
return Effect.flatMap(judgeDual(self, input, options), (judgment) => verdictCheckWith(self, expect, judgment, frame));
|
|
370
|
+
};
|
|
371
|
+
/**
|
|
372
|
+
* The test edge over `judge`: the judgement and its one check. A break fails the check with the report;
|
|
373
|
+
* behaviour failures and infrastructure refusals stay on the error channel.
|
|
374
|
+
*/
|
|
332
375
|
const check = dual((args) => isContract(args[0]), checkDual);
|
|
333
376
|
//#endregion
|
|
334
377
|
//#region src/drivers/tempo-trace.schema.ts
|
|
@@ -547,15 +590,40 @@ Schema.encodeEffect(HexId);
|
|
|
547
590
|
//#region src/observation-window.handle.ts
|
|
548
591
|
const TypeId$2 = Symbol.for("~systemfsoftware/trace-spec/ObservationWindow");
|
|
549
592
|
const ObservationWindow = Handle.make()(TypeId$2);
|
|
593
|
+
/**
|
|
594
|
+
* Records every finished span the moment it ends, and answers `shutdown`
|
|
595
|
+
* inline. The OpenTelemetry `InMemorySpanExporter` defers its result callback
|
|
596
|
+
* to a `setTimeout`, a real timer the kernel must fail as an escaped schedule;
|
|
597
|
+
* a window records nothing that waits, so it records synchronously.
|
|
598
|
+
*/
|
|
599
|
+
const spanRecorder = () => {
|
|
600
|
+
const finishedSpans = [];
|
|
601
|
+
let stopped = false;
|
|
602
|
+
return {
|
|
603
|
+
processor: {
|
|
604
|
+
onStart: () => void 0,
|
|
605
|
+
onEnd: (span) => {
|
|
606
|
+
if (!stopped) finishedSpans.push(span);
|
|
607
|
+
},
|
|
608
|
+
shutdown: () => {
|
|
609
|
+
stopped = true;
|
|
610
|
+
finishedSpans.length = 0;
|
|
611
|
+
return Promise.resolve();
|
|
612
|
+
},
|
|
613
|
+
forceFlush: () => Promise.resolve()
|
|
614
|
+
},
|
|
615
|
+
store: { getFinishedSpans: () => finishedSpans }
|
|
616
|
+
};
|
|
617
|
+
};
|
|
550
618
|
const isObservationWindow = ObservationWindow.is;
|
|
551
619
|
const make$3 = (spec) => {
|
|
552
|
-
const
|
|
620
|
+
const { processor, store } = spanRecorder();
|
|
553
621
|
const provider = new BasicTracerProvider({
|
|
554
622
|
sampler: new AlwaysOnSampler(),
|
|
555
|
-
spanProcessors: [
|
|
623
|
+
spanProcessors: [processor]
|
|
556
624
|
});
|
|
557
625
|
return ObservationWindow.make({ serviceName: spec.serviceName }, {
|
|
558
|
-
|
|
626
|
+
store,
|
|
559
627
|
provider
|
|
560
628
|
});
|
|
561
629
|
};
|
|
@@ -614,7 +682,7 @@ const emptyObservation = (traceId) => new EmptyObservationError({
|
|
|
614
682
|
traceId,
|
|
615
683
|
detail: "the observation window closed with no span carrying the stimulated trace id"
|
|
616
684
|
});
|
|
617
|
-
const recordsOf = (self, traceId) => ObservationWindow.slot(self).
|
|
685
|
+
const recordsOf = (self, traceId) => ObservationWindow.slot(self).store.getFinishedSpans().filter((span) => span.spanContext().traceId === traceId).map(spanRecordOf);
|
|
618
686
|
const collect = dual(2, (self, traceId) => Effect.suspend(() => {
|
|
619
687
|
const records = recordsOf(self, traceId);
|
|
620
688
|
return records.length > 0 ? Effect.succeed(records) : Effect.fail(emptyObservation(traceId));
|
|
@@ -622,7 +690,7 @@ const collect = dual(2, (self, traceId) => Effect.suspend(() => {
|
|
|
622
690
|
const context = (self) => Context.make(Observation, { collect: (traceId) => collect(self, traceId) }).pipe(Context.add(OtelTracer.OtelTracerProvider, ObservationWindow.slot(self).provider));
|
|
623
691
|
//#endregion
|
|
624
692
|
//#region src/ObservationWindowSpec.schema.ts
|
|
625
|
-
|
|
693
|
+
const ObservationWindowSpec = Schema.Struct({ serviceName: Schema.NonEmptyString });
|
|
626
694
|
//#endregion
|
|
627
695
|
//#region src/observation-window.blueprint.ts
|
|
628
696
|
var observation_window_blueprint_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -643,7 +711,7 @@ const ObservationWindows = Blueprint.make()(TypeId$1).steps({
|
|
|
643
711
|
layer: layer$1
|
|
644
712
|
}
|
|
645
713
|
});
|
|
646
|
-
const make$2 = (serviceName) => ObservationWindows.of(
|
|
714
|
+
const make$2 = (serviceName) => ObservationWindows.of(ObservationWindowSpec.make({ serviceName }));
|
|
647
715
|
//#endregion
|
|
648
716
|
//#region src/Rel.ts
|
|
649
717
|
var Rel_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -676,7 +744,7 @@ const relation = (parts) => Object.assign((graph) => parts.evaluate(graph), {
|
|
|
676
744
|
const matchedNodes = (graph, spec) => byId(graph, spec);
|
|
677
745
|
const inspectedOf = (nodes) => nodes.map((node) => node.spanId);
|
|
678
746
|
const matchedAll = (items, predicate) => items.length > 0 && items.every(predicate);
|
|
679
|
-
const isBreak
|
|
747
|
+
const isBreak = (verdict) => Schema.is(Break)(verdict);
|
|
680
748
|
const isHold$1 = (verdict) => Schema.is(Hold)(verdict);
|
|
681
749
|
const verdictOf$1 = (options) => options.holds ? Hold.make({
|
|
682
750
|
conjunct: options.id,
|
|
@@ -686,7 +754,7 @@ const verdictOf$1 = (options) => options.holds ? Hold.make({
|
|
|
686
754
|
inspected: options.inspected,
|
|
687
755
|
detail: options.detail
|
|
688
756
|
});
|
|
689
|
-
const declared = (declaration) => relation({
|
|
757
|
+
const declared$1 = (declaration) => relation({
|
|
690
758
|
id: declaration.id,
|
|
691
759
|
soft: false,
|
|
692
760
|
softenable: declaration.softenable,
|
|
@@ -702,21 +770,21 @@ const declared = (declaration) => relation({
|
|
|
702
770
|
});
|
|
703
771
|
const holdsCountOf = (spec, expected) => (graph) => expected(matchedNodes(graph, spec).length);
|
|
704
772
|
const holdsEveryOf = (spec, predicate) => (graph) => matchedAll(matchedNodes(graph, spec), predicate);
|
|
705
|
-
const exists = (spec) => declared({
|
|
773
|
+
const exists = (spec) => declared$1({
|
|
706
774
|
id: `exists(${spec.id})`,
|
|
707
775
|
spans: [spec],
|
|
708
776
|
softenable: false,
|
|
709
777
|
detail: `no ${spec.id} span was emitted`,
|
|
710
778
|
holds: holdsCountOf(spec, (count) => count > 0)
|
|
711
779
|
});
|
|
712
|
-
const absent = (spec) => declared({
|
|
780
|
+
const absent = (spec) => declared$1({
|
|
713
781
|
id: `absent(${spec.id})`,
|
|
714
782
|
spans: [spec],
|
|
715
783
|
softenable: false,
|
|
716
784
|
detail: `a ${spec.id} span was emitted`,
|
|
717
785
|
holds: holdsCountOf(spec, (count) => count === 0)
|
|
718
786
|
});
|
|
719
|
-
const unique = (spec) => declared({
|
|
787
|
+
const unique = (spec) => declared$1({
|
|
720
788
|
id: `unique(${spec.id})`,
|
|
721
789
|
spans: [spec],
|
|
722
790
|
softenable: true,
|
|
@@ -731,7 +799,7 @@ const reachedUnder = (graph, parent, childSpan, reach) => {
|
|
|
731
799
|
const reached = reach(graph, matchedNodes(graph, parent));
|
|
732
800
|
return matchedNodes(graph, childSpan).some((node) => reached.has(node.spanId));
|
|
733
801
|
};
|
|
734
|
-
const childImpl = (parent, childSpan) => declared({
|
|
802
|
+
const childImpl = (parent, childSpan) => declared$1({
|
|
735
803
|
id: `child(${parent.id},${childSpan.id})`,
|
|
736
804
|
spans: [parent, childSpan],
|
|
737
805
|
softenable: true,
|
|
@@ -739,7 +807,7 @@ const childImpl = (parent, childSpan) => declared({
|
|
|
739
807
|
holds: (graph) => reachedUnder(graph, parent, childSpan, childIdsOf)
|
|
740
808
|
});
|
|
741
809
|
const child = dual(2, childImpl);
|
|
742
|
-
const descendantImpl = (parent, childSpan) => declared({
|
|
810
|
+
const descendantImpl = (parent, childSpan) => declared$1({
|
|
743
811
|
id: `descendant(${parent.id},${childSpan.id})`,
|
|
744
812
|
spans: [parent, childSpan],
|
|
745
813
|
softenable: true,
|
|
@@ -747,7 +815,7 @@ const descendantImpl = (parent, childSpan) => declared({
|
|
|
747
815
|
holds: (graph) => reachedUnder(graph, parent, childSpan, descendantIdsOf)
|
|
748
816
|
});
|
|
749
817
|
const descendant = dual(2, descendantImpl);
|
|
750
|
-
const orderImpl = (before, after) => declared({
|
|
818
|
+
const orderImpl = (before, after) => declared$1({
|
|
751
819
|
id: `order(${before.id},${after.id})`,
|
|
752
820
|
spans: [before, after],
|
|
753
821
|
softenable: true,
|
|
@@ -755,7 +823,7 @@ const orderImpl = (before, after) => declared({
|
|
|
755
823
|
holds: (graph) => matchedAll(matchedNodes(graph, after), (node) => startsAfterSome(matchedNodes(graph, before), node))
|
|
756
824
|
});
|
|
757
825
|
const order = dual(2, orderImpl);
|
|
758
|
-
const statusImpl = (spec, expected) => declared({
|
|
826
|
+
const statusImpl = (spec, expected) => declared$1({
|
|
759
827
|
id: `status(${spec.id},${expected})`,
|
|
760
828
|
spans: [spec],
|
|
761
829
|
softenable: true,
|
|
@@ -763,7 +831,7 @@ const statusImpl = (spec, expected) => declared({
|
|
|
763
831
|
holds: holdsEveryOf(spec, (node) => node.status === expected)
|
|
764
832
|
});
|
|
765
833
|
const status = dual(2, statusImpl);
|
|
766
|
-
const errorTypeImpl = (spec, expected) => declared({
|
|
834
|
+
const errorTypeImpl = (spec, expected) => declared$1({
|
|
767
835
|
id: `errorType(${spec.id},${expected})`,
|
|
768
836
|
spans: [spec],
|
|
769
837
|
softenable: true,
|
|
@@ -772,7 +840,7 @@ const errorTypeImpl = (spec, expected) => declared({
|
|
|
772
840
|
});
|
|
773
841
|
const errorType = dual(2, errorTypeImpl);
|
|
774
842
|
const partialMatches = (attributes, partial) => Object.entries(partial).filter(([, value]) => value !== void 0).every(([key, value]) => Equal.equals(attributes[key], value));
|
|
775
|
-
const attrsImpl = (spec, partial) => declared({
|
|
843
|
+
const attrsImpl = (spec, partial) => declared$1({
|
|
776
844
|
id: `attrs(${spec.id})`,
|
|
777
845
|
spans: [spec],
|
|
778
846
|
softenable: true,
|
|
@@ -780,7 +848,7 @@ const attrsImpl = (spec, partial) => declared({
|
|
|
780
848
|
holds: holdsEveryOf(spec, (node) => partialMatches(node.attrs, partial))
|
|
781
849
|
});
|
|
782
850
|
const attrs = dual(2, attrsImpl);
|
|
783
|
-
const durationLessThanImpl = (spec, millis) => declared({
|
|
851
|
+
const durationLessThanImpl = (spec, millis) => declared$1({
|
|
784
852
|
id: `durationLessThan(${spec.id},${millis})`,
|
|
785
853
|
spans: [spec],
|
|
786
854
|
softenable: true,
|
|
@@ -789,7 +857,7 @@ const durationLessThanImpl = (spec, millis) => declared({
|
|
|
789
857
|
});
|
|
790
858
|
const durationLessThan = dual(2, durationLessThanImpl);
|
|
791
859
|
const carriesEvent = (name) => (node) => node.events.some((event) => event.name === name);
|
|
792
|
-
const eventImpl = (spec, name) => declared({
|
|
860
|
+
const eventImpl = (spec, name) => declared$1({
|
|
793
861
|
id: `event(${spec.id},${name})`,
|
|
794
862
|
spans: [spec],
|
|
795
863
|
softenable: true,
|
|
@@ -849,8 +917,8 @@ const collectSoft = (id, breaks) => {
|
|
|
849
917
|
detail: breaks.map((breach) => breach.conjunct).join(", ")
|
|
850
918
|
});
|
|
851
919
|
};
|
|
852
|
-
const firstHardBreak = (relations, graph) => relations.filter((relation) => !relation.soft).map((relation) => relation(graph)).find(isBreak
|
|
853
|
-
const softBreaks = (relations, graph) => relations.filter((relation) => relation.soft).map((relation) => relation(graph)).filter(isBreak
|
|
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);
|
|
854
922
|
const combined = (options) => relation({
|
|
855
923
|
id: options.id,
|
|
856
924
|
soft: options.soft,
|
|
@@ -1038,10 +1106,11 @@ var Stimulus_exports = /* @__PURE__ */ __exportAll({
|
|
|
1038
1106
|
traceContext: () => traceContext
|
|
1039
1107
|
});
|
|
1040
1108
|
const byteHex = (byte) => byte.toString(16).padStart(2, "0");
|
|
1041
|
-
const
|
|
1042
|
-
const
|
|
1043
|
-
|
|
1044
|
-
const
|
|
1109
|
+
const randomByte = Random.nextIntBetween(0, 255);
|
|
1110
|
+
const randomHex = (byteLength) => Effect.map(Effect.all(Array.from({ length: byteLength }, () => randomByte)), (bytes) => bytes.map(byteHex).join(""));
|
|
1111
|
+
const traceContext = Effect.gen(function* () {
|
|
1112
|
+
const traceId = yield* randomHex(16);
|
|
1113
|
+
const spanId = yield* randomHex(8);
|
|
1045
1114
|
return {
|
|
1046
1115
|
traceId,
|
|
1047
1116
|
spanId,
|
|
@@ -1075,25 +1144,33 @@ const HarnessFailure = Schema.Union([
|
|
|
1075
1144
|
ContractDecodeError,
|
|
1076
1145
|
EmptyObservationError,
|
|
1077
1146
|
IncompleteObservationError,
|
|
1078
|
-
TransportObservationError
|
|
1079
|
-
TraceDisparityError
|
|
1147
|
+
TransportObservationError
|
|
1080
1148
|
]);
|
|
1081
1149
|
//#endregion
|
|
1082
|
-
//#region src/
|
|
1083
|
-
const
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
onSome: (record) => Effect.promise(() => Promise.resolve(record(message, "info")))
|
|
1090
|
-
});
|
|
1150
|
+
//#region src/TaskAnnounce.ts
|
|
1151
|
+
const forget = (annotation) => {
|
|
1152
|
+
Promise.resolve(annotation);
|
|
1153
|
+
};
|
|
1154
|
+
const dispatchAnnotate = (annotate, message, type) => Option.match(Option.fromNullishOr(annotate), {
|
|
1155
|
+
onNone: () => Effect.void,
|
|
1156
|
+
onSome: (record) => Effect.sync(() => forget(record(message, type)))
|
|
1091
1157
|
});
|
|
1092
|
-
const
|
|
1093
|
-
const announce = (verdict, dumpPath) => Option.match(dumpMessageOf(verdict, dumpPath), {
|
|
1158
|
+
const announcement = (taskContext, message, type) => Option.match(Option.fromNullishOr(taskContext), {
|
|
1094
1159
|
onNone: () => Effect.void,
|
|
1095
|
-
onSome: (
|
|
1160
|
+
onSome: (context) => dispatchAnnotate(context.annotate, message, type)
|
|
1161
|
+
});
|
|
1162
|
+
const record = (message, type) => Effect.gen(function* () {
|
|
1163
|
+
const taskContext = yield* TaskRef.RawVitestTaskRef;
|
|
1164
|
+
return yield* announcement(taskContext, message, type);
|
|
1096
1165
|
});
|
|
1166
|
+
const dumpMessageOf = (judged) => Option.map(Option.flatMap(Option.liftPredicate(judged.verdict, Schema.is(Break)), () => Option.fromNullishOr(judged.dumpPath)), (dumpPath) => `trace contract failed; observed graph dumped to ${dumpPath}`);
|
|
1167
|
+
const announceDump = (judged) => Option.match(dumpMessageOf(judged), {
|
|
1168
|
+
onNone: () => Effect.void,
|
|
1169
|
+
onSome: (message) => record(message, "info")
|
|
1170
|
+
});
|
|
1171
|
+
//#endregion
|
|
1172
|
+
//#region src/Prop.ts
|
|
1173
|
+
const isHold = Schema.is(Hold);
|
|
1097
1174
|
/**
|
|
1098
1175
|
* The contract with its stimulus replaced by the function the property was handed: the impostor gate
|
|
1099
1176
|
* swaps in a constant fake, so the judgement must reach the stimulus through the argument, never the
|
|
@@ -1108,92 +1185,104 @@ const judgedOver = (contract, subject) => ({
|
|
|
1108
1185
|
* frozen run satisfies every relation about that run, so the input pin is what refutes it.
|
|
1109
1186
|
*/
|
|
1110
1187
|
const consumedInput = (judgment, input) => judgment.run.input === input;
|
|
1111
|
-
const predicateImpl = (title, contract, scenario) => {
|
|
1112
|
-
const
|
|
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)));
|
|
1113
1191
|
return (subject, input) => checked(subject, input);
|
|
1114
1192
|
};
|
|
1115
|
-
const predicate = dual(
|
|
1193
|
+
const predicate = dual(4, predicateImpl);
|
|
1116
1194
|
//#endregion
|
|
1117
1195
|
//#region src/StimulusFailure.schema.ts
|
|
1196
|
+
const firstLine = (text) => text.split("\n")[0] ?? "";
|
|
1118
1197
|
var StimulusFailure = class extends Schema.TaggedError()("StimulusFailure", {
|
|
1119
1198
|
stimulus: Schema.String,
|
|
1120
1199
|
detail: Schema.String
|
|
1121
|
-
}) {
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
onSome: (record) => Effect.promise(() => Promise.resolve(record(message, type)))
|
|
1127
|
-
});
|
|
1128
|
-
const announcement = (taskContext, message, type) => Option.match(Option.fromNullishOr(taskContext), {
|
|
1129
|
-
onNone: () => Effect.void,
|
|
1130
|
-
onSome: (context) => dispatchAnnotate(context.annotate, message, type)
|
|
1131
|
-
});
|
|
1132
|
-
const record = (message, type) => Effect.gen(function* () {
|
|
1133
|
-
const taskContext = yield* TaskRef.RawVitestTaskRef;
|
|
1134
|
-
return yield* announcement(taskContext, message, type);
|
|
1135
|
-
});
|
|
1136
|
-
const announceDump = (error) => Option.match(Option.fromNullishOr(error.dumpPath), {
|
|
1137
|
-
onNone: () => Effect.void,
|
|
1138
|
-
onSome: (dumpPath) => record(`trace contract failed; observed graph dumped to ${dumpPath}`, "info")
|
|
1139
|
-
});
|
|
1200
|
+
}) {
|
|
1201
|
+
get message() {
|
|
1202
|
+
return `The stimulus "${this.stimulus}" failed: ${firstLine(this.detail)}`;
|
|
1203
|
+
}
|
|
1204
|
+
};
|
|
1140
1205
|
//#endregion
|
|
1141
1206
|
//#region src/Suite.ts
|
|
1142
1207
|
var Suite_exports = /* @__PURE__ */ __exportAll({
|
|
1143
1208
|
StimulusFailure: () => StimulusFailure,
|
|
1144
1209
|
TypeId: () => TypeId,
|
|
1210
|
+
live: () => live,
|
|
1145
1211
|
make: () => make,
|
|
1146
1212
|
withLayer: () => withLayer,
|
|
1147
1213
|
withScenarioLayer: () => withScenarioLayer
|
|
1148
1214
|
});
|
|
1149
1215
|
const TypeId = Symbol.for("@systemfsoftware/trace-spec/Suite");
|
|
1150
1216
|
const isHarnessFailure = Schema.is(HarnessFailure);
|
|
1151
|
-
const
|
|
1217
|
+
const SALT_BYTES = 8;
|
|
1218
|
+
/**
|
|
1219
|
+
* Per-case salt for globally unique W3C trace ids: drawn here, at case
|
|
1220
|
+
* registration and outside any kernel run, so a seeded Random inside the run
|
|
1221
|
+
* never replays another case's ids and the raw crypto source never enters an
|
|
1222
|
+
* explored body.
|
|
1223
|
+
*/
|
|
1224
|
+
const saltOf = () => {
|
|
1225
|
+
const bytes = crypto.getRandomValues(new Uint8Array(SALT_BYTES));
|
|
1226
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1227
|
+
};
|
|
1228
|
+
const caseFailureOf = (stimulus, salt) => (failure) => isHarnessFailure(failure) ? failure : new StimulusFailure({
|
|
1152
1229
|
stimulus,
|
|
1153
|
-
detail: Cause.pretty(Cause.fail(failure))
|
|
1230
|
+
detail: `${Cause.pretty(Cause.fail(failure))}\nrandom salt: ${salt}`
|
|
1154
1231
|
});
|
|
1155
|
-
const
|
|
1156
|
-
const
|
|
1157
|
-
const
|
|
1158
|
-
const
|
|
1159
|
-
|
|
1232
|
+
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)));
|
|
1233
|
+
const caseTools = (register, propIt, scenario, shared) => {
|
|
1234
|
+
const Case = (name, contract, input) => {
|
|
1235
|
+
const salt = saltOf();
|
|
1236
|
+
register(name, (expect) => caseBody(expect, contract, input, salt).pipe(Random.withSeed(salt)), "run");
|
|
1237
|
+
};
|
|
1160
1238
|
Case.prop = (name, contract, arbitrary) => {
|
|
1239
|
+
const salt = saltOf();
|
|
1161
1240
|
propIt.effect.prop(name, {
|
|
1162
1241
|
of: [arbitrary],
|
|
1163
1242
|
subject: contract.stimulus
|
|
1164
|
-
}, (subject, values) => predicate(name, contract, scenario)(subject, values[0]));
|
|
1243
|
+
}, (subject, values) => predicate(name, contract, scenario, shared)(subject, values[0]).pipe(Random.withSeed(salt)));
|
|
1165
1244
|
};
|
|
1166
1245
|
return { Case };
|
|
1167
1246
|
};
|
|
1168
|
-
const config = (name) =>
|
|
1247
|
+
const config = (name, live) => live === void 0 ? {
|
|
1248
|
+
name,
|
|
1249
|
+
describe: "describe",
|
|
1250
|
+
options: void 0
|
|
1251
|
+
} : {
|
|
1169
1252
|
name,
|
|
1170
1253
|
describe: "describe",
|
|
1171
1254
|
options: void 0,
|
|
1172
|
-
|
|
1173
|
-
}
|
|
1174
|
-
const
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1255
|
+
live
|
|
1256
|
+
};
|
|
1257
|
+
const openedScenario = (bindings, name, live, scenario) => ({
|
|
1258
|
+
live: (reason) => openedScenario(bindings, name, { reason }, scenario),
|
|
1259
|
+
body: (use) => Suite.openCase(config(name, live), scenario, (register, propIt) => use(caseTools(register, propIt, scenario, Layer.empty)))(bindings)
|
|
1260
|
+
});
|
|
1261
|
+
const openedSharedScenario = (bindings, name, live, shared, scenario) => ({
|
|
1262
|
+
live: (reason) => openedSharedScenario(bindings, name, { reason }, shared, scenario),
|
|
1263
|
+
body: (use) => Suite.openSharedCase(config(name, live), { layer: shared }, scenario, (register, propIt) => use(caseTools(register, propIt, scenario.pipe(Layer.provideMerge(shared)), shared)))(bindings)
|
|
1264
|
+
});
|
|
1265
|
+
const registerSharedCases = (bindings, name, live, shared, use) => Suite.openShared(config(name, live), { layer: shared }, (register, propIt) => use(caseTools(register, propIt, shared, shared)))(bindings);
|
|
1266
|
+
const sharedOver = (bindings, name, live, shared) => ({
|
|
1267
|
+
[TypeId]: TypeId,
|
|
1268
|
+
live: (reason) => sharedOver(bindings, name, { reason }, shared),
|
|
1269
|
+
withScenarioLayer: (scenario) => openedSharedScenario(bindings, name, live, shared, scenario),
|
|
1270
|
+
body: (use) => registerSharedCases(bindings, name, live, shared, use),
|
|
1271
|
+
...Prototype
|
|
1272
|
+
});
|
|
1273
|
+
const declared = (bindings, name, live) => ({
|
|
1180
1274
|
[TypeId]: TypeId,
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
}, (register, propIt) => use(caseTools(register, propIt, shared))),
|
|
1185
|
-
withScenarioLayer: (scenario) => openSharedScenario(bindings, name, shared, scenario),
|
|
1275
|
+
live: (reason) => declared(bindings, name, { reason }),
|
|
1276
|
+
withLayer: (shared) => sharedOver(bindings, name, live, shared),
|
|
1277
|
+
withScenarioLayer: (scenario) => openedScenario(bindings, name, live, scenario),
|
|
1186
1278
|
...Prototype
|
|
1187
1279
|
});
|
|
1188
1280
|
const withLayerDual = (self, shared) => self.withLayer(shared);
|
|
1189
1281
|
const withLayer = dual(2, withLayerDual);
|
|
1190
1282
|
const withScenarioLayerDual = (self, scenario) => self.withScenarioLayer(scenario);
|
|
1191
1283
|
const withScenarioLayer = dual(2, withScenarioLayerDual);
|
|
1192
|
-
const
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
withScenarioLayer: (scenario) => openScenario(bindings, name, scenario),
|
|
1196
|
-
...Prototype
|
|
1197
|
-
});
|
|
1284
|
+
const liveDual = (self, reason) => self.live(reason);
|
|
1285
|
+
const live = dual(2, liveDual);
|
|
1286
|
+
const make = (bindings) => (name) => declared(bindings, name, void 0);
|
|
1198
1287
|
//#endregion
|
|
1199
1288
|
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,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@systemfsoftware/trace-spec",
|
|
3
3
|
"license": "Apache-2.0",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"author": "Ryan Lee <drdgvhbh@gmail.com>",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/systemfsoftware/systemfsoftware.git",
|
|
9
|
-
"directory": "packages/trace-spec"
|
|
9
|
+
"directory": "packages/trace/trace-spec"
|
|
10
10
|
},
|
|
11
|
-
"homepage": "https://github.com/systemfsoftware/systemfsoftware/tree/main/packages/trace-spec#readme",
|
|
11
|
+
"homepage": "https://github.com/systemfsoftware/systemfsoftware/tree/main/packages/trace/trace-spec#readme",
|
|
12
12
|
"bugs": "https://github.com/systemfsoftware/systemfsoftware/issues",
|
|
13
13
|
"description": "Trace contracts as executable specs — own a trace id, observe the finished trace through an in-memory exporter, and relate the decoded span graph to a declared taxonomy.",
|
|
14
14
|
"keywords": [
|
|
@@ -38,18 +38,19 @@
|
|
|
38
38
|
"@opentelemetry/resources": "^2",
|
|
39
39
|
"@opentelemetry/sdk-trace-base": "^2",
|
|
40
40
|
"@opentelemetry/semantic-conventions": "^1.33",
|
|
41
|
-
"@systemfsoftware/effect-cell-types": "^10.
|
|
42
|
-
"@systemfsoftware/effect-spec-runtime": "^0.
|
|
43
|
-
"@systemfsoftware/trace-taxonomy": "^0.0.
|
|
41
|
+
"@systemfsoftware/effect-cell-types": "^10.3.0",
|
|
42
|
+
"@systemfsoftware/effect-spec-runtime": "^0.3.1",
|
|
43
|
+
"@systemfsoftware/trace-taxonomy": "^0.0.2"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@effect/platform-node": "4.0.0-rc.117",
|
|
47
|
-
"@effect/vitest": "npm:@systemfsoftware/vitest@0.0.0",
|
|
48
47
|
"@systemfsoftware/arethetypeswrong-cli": "^4.2.0",
|
|
49
|
-
"@systemfsoftware/
|
|
50
|
-
"@systemfsoftware/
|
|
48
|
+
"@systemfsoftware/conformance-spec": "^0.2.0",
|
|
49
|
+
"@systemfsoftware/effect-gherkin-spec": "^6.0.0",
|
|
50
|
+
"@systemfsoftware/oxlint-config-recommended": "^3.2.0",
|
|
51
51
|
"@systemfsoftware/tsconfig": "^2.0.1",
|
|
52
52
|
"@systemfsoftware/tsdown-config": "^0.1.0",
|
|
53
|
+
"@systemfsoftware/vitest": "^0.2.0",
|
|
53
54
|
"@systemfsoftware/vitest-config": "^0.1.0",
|
|
54
55
|
"@types/node": "^26",
|
|
55
56
|
"effect": "4.0.0-rc.117",
|
|
@@ -60,11 +61,12 @@
|
|
|
60
61
|
"vitest": "^5"
|
|
61
62
|
},
|
|
62
63
|
"peerDependencies": {
|
|
63
|
-
"@
|
|
64
|
+
"@systemfsoftware/vitest": "^0.2.0",
|
|
64
65
|
"effect": "4.0.0-rc.117",
|
|
65
66
|
"vitest": "^5"
|
|
66
67
|
},
|
|
67
68
|
"publishConfig": {
|
|
69
|
+
"access": "public",
|
|
68
70
|
"provenance": true
|
|
69
71
|
},
|
|
70
72
|
"scripts": {
|