Documentation
¶
Overview ¶
Package runrecord builds the OpenTelemetry trace that records a journey run, and serialises it as OTLP/JSON.
A run is a trace: a root span for the journey, a child per step, and a child of that per assertion or evidence capture. The requirement it serves is narrow and hard — after a run, someone who was not watching must be able to establish what was asserted, what was actually observed, and whether that matched. A rendered text log does not carry that: it says an assertion failed, not what was on screen instead.
Three decisions are load-bearing, and each is written up in docs/journey-evidence.md:
- The spans are built directly as OTLP protocol buffers rather than through the OTel SDK. The SDK has no file exporter, and routing evidence through a TracerProvider would put it behind a sampler — a run record holding a statistical subset of its own steps is not evidence.
- protojson output is deliberately unstable: it injects randomised whitespace to discourage byte comparison. The artifact is SHA-256'd into a bundle manifest and compared on verification, so it is canonicalised before it is written (see MarshalOTLPJSON).
- Trace and span ids are random, so two runs of one document produce different files. That is correct: the run differs. The stable identity is the journey document's hash and the compiled plan id, both of which are span attributes.
The package holds no Windows or MCP types, so the whole serialisation is testable on any platform.
Index ¶
Constants ¶
const ScopeName = "windows-mcp-server/journeys"
ScopeName is the instrumentation scope every journey span is recorded under.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Options ¶
type Options struct {
ServiceName string
ServiceVersion string
SessionID string
HostName string
// Rand supplies trace and span ids. Nil uses crypto/rand; a test supplies a
// deterministic reader so a recorded document can be compared.
Rand io.Reader
}
Options configures a recorder's resource attributes.
SessionID is the one that matters and the one that has never reached OTLP: it is the stamp that names the session's audit file and its recording, so without it a trace cannot be tied to the chain it belongs to.
type Recorder ¶
type Recorder struct {
// contains filtered or unexported fields
}
Recorder accumulates the spans of one run.
func (*Recorder) MarshalOTLPJSON ¶
MarshalOTLPJSON renders the run as OTLP/JSON — the standard ResourceSpans encoding, so a collector, otel-cli, a Jaeger import or any OTLP-aware tool reads it with no bespoke parser.
The output is canonicalised. protojson randomises its whitespace on purpose to discourage byte comparison of its output, and this artifact is hashed into an evidence manifest and compared on verification — so it is decoded into a generic value and re-encoded through encoding/json, which sorts object keys and emits stable separators. Without this a file hashes differently every time it is written and verification fails for no visible reason.
func (*Recorder) Open ¶
func (r *Recorder) Open(s Span) (SpanID, func(end time.Time, failed bool, message string, extra ...Attr))
Open records a span whose end is not yet known — the run itself — returning its id, so children can be parented to it, and a function that closes it.
The alternative, adding the root last, does not work: a child needs its parent's id before it can be recorded, and a run's outcome is not known until every child has been.
type Span ¶
type Span struct {
Name string
Parent SpanID
Start time.Time
End time.Time
Attrs []Attr
// Failed sets the span's status to Error. Status is set explicitly either way:
// a span with no status is indistinguishable from one nobody decided about, and
// the point of recording every assertion — not only the failures — is to be able
// to answer "was this ever checked?".
Failed bool
// Message is the status description, which for an assertion carries the
// comparison: expected "EXP-[0-9]{6}", observed "EXP-4471".
Message string
}
Span is one recorded operation.