generation.bend

bend-trace-context@0.2.0.0 · 107 lines · 11 definitions

1# Generation on the host's cryptographic source
2# =============================================
3#
4# Generated identifiers and contexts on the host's cryptographic source:
5# WebCrypto in JavaScript and the operating system's generator natively. They
6# apply the rules of trace_context.bend: four words per trace ID candidate and
7# two per span ID candidate, most significant first, eight candidates per
8# identifier, no all-zero identifier, no reuse of an identifier to exclude,
9# such as the parent's span ID or a received trace ID, and an immediate stop
10# at the first source error. There is no time, counter or other fallback.
11# TC.TraceId.generate_with, TC.Context.root_with and their siblings take a
12# caller's source instead.
13#
14# The generation machine itself is pure: section 6 of LAWS.bend states its
15# behavior over replayed tapes. This file only connects it, and the native
16# HTTP shortcuts of native_http.bend, to the host source of entropy.bend.
17#
18# Every operation here relies on entropy.bend's foreign effect, so a check of
19# a file that imports this module reports them and answers SOME PROOFS FAIL.
20# No law imports it: the laws state the same operations on a caller's
21# source, which is how PROOF.bend proves them without host code.
22
23import Base
24import bend-trace-context@0.2.0.0/trace_context.bend as TC
25import bend-trace-context@0.2.0.0/native_http.bend as Adapter
26import bend-trace-context@0.2.0.0/entropy.bend as Entropy
27
28# The host source as the machine reads it: each read returns one word result
29# from Entropy.read_u32, and the state is Unit because the host keeps none.
30def Source.host(state: Unit) -> IO(Unit & Result<&1, &1, U32 & String, U32>):
31  IO.bind(Result<&1, &1, U32 & String, U32>, Unit & Result<&1, &1, U32 & String, U32>,
32    Entropy.read_u32(), word => IO.pure(Unit & Result<&1, &1, U32 & String, U32>, (state, word)))
33
34# The host source has no state to hand back.
35def on_host(-A: Type, action: IO(Unit & A)) -> IO(A):
36  IO.bind(Unit & A, A, action, done => IO.pure(A, Pair.snd(Unit, A, done)))
37
38# A trace ID alone, as an OpenTelemetry SDK generates one before its sampler
39# decides on it (TC.TraceId.generate_with). It asserts random-trace-id and is
40# never `excluded`, when there is one.
41def TraceId.generate(excluded: Maybe<&2, TC.TraceId>) -> IO(Result<&2, &2, TC.GenerationError, TC.TraceId>):
42  on_host(Result<&2, &2, TC.GenerationError, TC.TraceId>,
43    TC.TraceId.generate_with(~Unit, ~Source.host, Unit{}, excluded))
44
45# A span ID alone, as an SDK generates one once its sampler has decided, or
46# for a child span with its parent's span ID excluded
47# (TC.SpanId.generate_with).
48def SpanId.generate(excluded: Maybe<&2, TC.SpanId>) -> IO(Result<&2, &2, TC.GenerationError, TC.SpanId>):
49  on_host(Result<&2, &2, TC.GenerationError, TC.SpanId>,
50    TC.SpanId.generate_with(~Unit, ~Source.host, Unit{}, excluded))
51
52# Start a trace: a trace ID, then a span ID, as TC.Context.root_with composes
53# them, with the sampled indication `sampled`, as it is given. The trace ID
54# asserts random-trace-id, so the root is emitted with flags 03 when it is
55# sampled and 02 when it is not: False gives an unsampled root, as
56# Context.continue_or_start starts one by default.
57def Context.root(sampled: Bool) -> IO(Result<&2, &2, TC.GenerationError, TC.LocalContext>):
58  on_host(Result<&2, &2, TC.GenerationError, TC.LocalContext>,
59    TC.Context.root_with(~Unit, ~Source.host, Unit{}, sampled))
60
61# Continue a remote or local parent with a new operation.
62def Context.child(parent: TC.Parent, sampling: TC.Sampling) -> IO(Result<&2, &2, TC.GenerationError, TC.LocalContext>):
63  on_host(Result<&2, &2, TC.GenerationError, TC.LocalContext>,
64    TC.Context.child_with(~Unit, ~Source.host, Unit{}, parent, sampling))
65
66# Start a new trace instead of continuing a received context, with the
67# sampled indication `sampled`, as a root takes it.
68def Context.restart(previous: TC.RemoteContext, sampled: Bool) ->
69  IO(Result<&2, &2, TC.GenerationError, TC.LocalContext>):
70  on_host(Result<&2, &2, TC.GenerationError, TC.LocalContext>,
71    TC.Context.restart_with(~Unit, ~Source.host, Unit{}, previous, sampled))
72
73# Give a service its own operation for a received message: a child of the
74# context that extraction keeps, the message's or the base, a root without
75# one, or a new trace in its place at a trust boundary
76# (TC.Context.continue_or_start_with).
77def Context.continue_or_start(+extraction: TC.Extraction, reception: TC.Reception, sampling: TC.Sampling,
78  +policy: TC.FailurePolicy) -> IO(TC.FailurePolicy.result(policy, TC.Service)):
79  on_host(TC.FailurePolicy.result(policy, TC.Service),
80    TC.Context.continue_or_start_with(~Unit, ~Source.host, Unit{}, extraction, reception, sampling, policy))
81
82# Give one message that the service sends a new child of the service's
83# operation, or the fallback when none can be generated
84# (TC.Context.send_with).
85def Context.send(+limits: TC.Limits, service: TC.Service, sampling: TC.Sampling, +policy: TC.FailurePolicy,
86  +carrier: List<&2, TC.Header>) -> IO(TC.FailurePolicy.result(policy, TC.Sent)):
87  on_host(TC.FailurePolicy.result(policy, TC.Sent),
88    TC.Context.send_with(~Unit, ~Source.host, Unit{}, limits, service, sampling, policy, carrier))
89
90# The service's own operation for a received request, from the header map of
91# the request, on the host's cryptographic source: native_http.bend's
92# continue_or_start_with on Source.host. `base` is the context to keep when
93# the request carries no usable traceparent.
94def NativeHttp.continue_or_start(+limits: TC.Limits, headers: Adapter.HeaderMap(), base: Maybe<&2, TC.BaseContext>,
95  reception: TC.Reception, sampling: TC.Sampling, +policy: TC.FailurePolicy) ->
96  IO(TC.FailurePolicy.result(policy, TC.Service)):
97  on_host(TC.FailurePolicy.result(policy, TC.Service),
98    Adapter.continue_or_start_with(~Unit, ~Source.host, Unit{}, limits, headers, base, reception, sampling, policy))
99
100# Give one request that the service sends the context of a new operation, on
101# the host's cryptographic source: native_http.bend's send_with on
102# Source.host, which replaces the context fields of `headers`, the request's
103# own header map.
104def NativeHttp.send(+limits: TC.Limits, service: TC.Service, sampling: TC.Sampling, +policy: TC.FailurePolicy,
105  headers: Adapter.HeaderMap()) -> IO(TC.FailurePolicy.result(policy, Adapter.Outbound)):
106  on_host(TC.FailurePolicy.result(policy, Adapter.Outbound),
107    Adapter.send_with(~Unit, ~Source.host, Unit{}, limits, service, sampling, policy, headers))