runrecord

package
v1.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 13, 2026 License: MIT Imports: 9 Imported by: 0

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

View Source
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 Attr

type Attr struct {
	Key   string
	Value *commonpb.AnyValue
}

Attr is one span or resource attribute.

func Bool

func Bool(k string, v bool) Attr

func Float

func Float(k string, v float64) Attr

func Int

func Int(k string, v int64) Attr

func String

func String(k, v string) Attr

String, Int, Bool and Float build attributes. An empty string value is dropped by attrs, so an absent property is absent rather than present-and-empty.

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 New

func New(opts Options) *Recorder

New starts a recorder for one run.

func (*Recorder) Add

func (r *Recorder) Add(s Span) SpanID

Add records a span and returns its id, for use as a parent.

func (*Recorder) MarshalOTLPJSON

func (r *Recorder) MarshalOTLPJSON() ([]byte, error)

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.

func (*Recorder) TraceID

func (r *Recorder) TraceID() string

TraceID returns the run's trace id as hex, for a log line that ties a run to what a collector received.

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.

type SpanID

type SpanID [8]byte

SpanID identifies a span within the run. The zero value means "no parent", which is how the root span is written.

func (SpanID) IsZero

func (s SpanID) IsZero() bool

IsZero reports whether the id is unset.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL