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