correlation

package module
v1.1.3 Latest Latest
Warning

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

Go to latest
Published: Oct 7, 2026 License: MIT Imports: 12 Imported by: 0

README

correlation

CI CodeQL Coverage Mutation Documentation Go Reference Release Go License

correlation is the transport-neutral owner of correlation, request, and causation identifiers. It carries those semantics through HTTP, JSON-RPC, queues, scheduled work, webhooks, logs, and OpenTelemetry without creating a global propagator or redefining trace and idempotency concepts.

The module is a stable v1 library. It requires and is tested with Go 1.27.0. Stable releases are selected through the v1 module line.

Install

go get github.com/faustbrian/go-correlation@v1

Semantics

  • CorrelationID groups one logical interaction or workflow.
  • RequestID identifies exactly one transport hop or delivery attempt.
  • CausationID identifies that hop's immediate parent request or message.

These types are deliberately distinct. Trace and span IDs remain owned by OpenTelemetry. Idempotency keys and fingerprints remain owned by idempotency. None of these values is authentication, authorization, tenancy, uniqueness, replay protection, or idempotency evidence.

Quick start

factory, err := correlation.NewFactory(correlation.FactoryOptions{})
if err != nil {
    return err
}

root, err := factory.Start()
if err != nil {
    return err
}
child, err := factory.Next(root)
if err != nil {
    return err
}

The complete compiler-checked released-v1 version is the package Example. Further examples cover Factory.Create and Factory.Next.

Create is the canonical operation for fresh root values. Start remains a compatibility alias with identical values and errors. Scheduled work follows the same boundary: schedule.Adapter.Create and schedule.Adapter.Receive are canonical, while Start and Run remain compatible aliases.

The default factory uses an explicitly owned, bounded entropy buffer around the cryptographic UUIDv4 generator from identifier. A caller-supplied generator remains instance scoped and must return canonical text accepted by the configured policy.

Inbound metadata is never trusted by extraction alone:

inbound, err := codec.Extract(carrier)
if err != nil {
    return err
}
values, err := factory.Accept(inbound, correlation.InboundPolicy{
    TrustCorrelation: true,
    TrustRequestAsCausation: true,
})

The application must establish that trust from an authenticated immediate transport boundary first. Every accepted hop receives a new request ID.

Adapters

  • adapters/http sanitizes inbound headers, applies explicit proxy trust, installs immutable context values, and injects outbound hops.
  • http/requestidbridge explicitly adopts a trusted http-middleware/requestid value without importing its private key.
  • adapters/jsonrpc reads and writes a separate metadata object without altering JSON-RPC envelopes.
  • adapters/queue preserves workflow identity while generating a distinct request ID for every retry or redelivery.
  • adapters/schedule starts independent runs unless metadata is deliberately propagated.
  • webhook gives outbound and inbound webhook hops HTTP semantics.
  • adapters/slog supplies redacted, keyed-hash, or explicitly raw slog attrs.
  • adapters/otel attaches attributes to telemetry-owned links and exposes only fixed-cardinality presence flags to metrics.

The former http, jsonrpc, queue, schedule, log, and telemetry import paths remain deprecated compatibility implementations and identity authorities. New code should use the target-oriented adapters/... paths.

W3C Trace Context and Baggage remain optional application-owned propagation. They may be linked to these values, but correlation IDs never become trace or span IDs.

When to use it

Use this module when work crosses process, protocol, queue, retry, or scheduled boundaries and operators need to relate those attempts without conflating their identities. Do not use it as an authorization, tenancy, uniqueness, deduplication, replay-protection, or idempotency mechanism, and do not replace OpenTelemetry trace or span identity with correlation values.

Construction, errors, and lifecycle

NewFactory and NewCodec validate their complete value configuration. NewPropagator and transport constructors reject missing dependencies and validate the policy options they own. The zero Policy accepts canonical ASCII identifiers up to 128 bytes. Stable sentinel categories such as ErrInvalidID, ErrInvalidFactory, and ErrGeneration are matched with errors.Is; wrapped causes remain available where disclosure is safe.

Factory, codec, and adapter operations perform no network I/O, retry, or detached work. WithValues, FromContext, and HTTP middleware use only the caller-owned context passed to that call; they do not retain it. The module starts no goroutines, opens no resources, and owns no Close or Shutdown operation. Applications retain ownership of carriers, headers, maps, contexts, telemetry providers, and supplied callbacks or generators.

The built-in factory and immutable strategies are safe for concurrent use. A caller-supplied generator and HTTP Trust callback are borrowed for the owning object's lifetime, so callers must keep them valid and provide any synchronization they require. Extraction and receive operations only read carriers. Send, inject, and HTTP wrap operations update the carrier or headers supplied for that call; callers must not concurrently reuse those mutable values without their own synchronization.

Shared construction, ownership, lifecycle, and composition expectations are in the versioned Golib ecosystem index and its foundations package guidance.

Deterministic correlation

NewDeterministic is an explicit opt-in for stable business workflows. It uses HMAC-SHA-256, a versioned domain, length-delimited input, and bounded output. Use a secret key when the input is private or comes from a small input space. Deterministic correlation is linkable and is never the factory default.

Security defaults

Identifiers use the canonical [A-Za-z0-9_-] alphabet and a default maximum of 128 bytes. Carriers reject empty, oversized, malformed, control-bearing, Unicode, and conflicting values. Injection refuses a populated target field for each non-empty identifier it would write. A direct partial Codec.Inject leaves fields corresponding to empty identifiers unchanged, so callers must clear any unrelated stale fields first. Observability output is redacted unless disclosure is explicitly enabled, and metrics never contain identifier values.

Verification

Run the local release-equivalent gate:

make inventory
make cohesion
make check
make ci

It verifies formatting, module tidiness, vet, unit and integration tests, the race detector, 100% production statement coverage in every package, fuzz smoke tests, mutation tests, allocation benchmarks, documentation, API compatibility, linting, Staticcheck, vulnerability analysis, and NilAway.

See the documentation index, security policy, and changelog. Operational limits and performance caveats are in the operations guide; symptom-driven recovery steps are in troubleshooting. Start with the FAQ and support guide for adoption questions and reproducible non-security defects. Follow the security policy for private vulnerability reports.

Documentation

Overview

Package correlation provides transport-neutral correlation, request, and causation identifiers. Identifiers are diagnostic metadata only; they are never authentication, authorization, tenancy, replay, or idempotency proof.

Example
package main

import (
	"fmt"

	correlation "github.com/faustbrian/go-correlation"
)

func main() {
	factory, err := correlation.NewFactory(correlation.FactoryOptions{})
	if err != nil {
		fmt.Println("factory error")
		return
	}
	root, err := factory.Start()
	if err != nil {
		fmt.Println("root error")
		return
	}
	child, err := factory.Next(root)
	if err != nil {
		fmt.Println("child error")
		return
	}
	fmt.Println(
		root.CorrelationID != "",
		root.RequestID != "",
		child.CorrelationID == root.CorrelationID,
		child.CausationID.String() == root.RequestID.String(),
		child.RequestID != root.RequestID,
	)
}
Output:
true true true true true

Index

Examples

Constants

View Source
const (
	// DefaultCorrelationField is the transport-neutral correlation key.
	DefaultCorrelationField = "correlation_id"
	// DefaultRequestField is the transport-neutral request key.
	DefaultRequestField = "request_id"
	// DefaultCausationField is the transport-neutral causation key.
	DefaultCausationField = "causation_id"
)

Variables

View Source
var (
	// ErrInvalidCarrier reports malformed, unbounded, or unsupported metadata.
	ErrInvalidCarrier = errors.New("correlation: invalid carrier")
	// ErrConflictingCarrier reports more than one distinct value for a field.
	ErrConflictingCarrier = errors.New("correlation: conflicting carrier values")
	// ErrCarrierOverwrite reports injection into an already populated field.
	ErrCarrierOverwrite = errors.New("correlation: carrier overwrite")
)
View Source
var (
	// ErrInvalidFactory reports invalid factory configuration.
	ErrInvalidFactory = errors.New("correlation: invalid factory")
	// ErrGeneration reports a generator failure or invalid generated value.
	ErrGeneration = errors.New("correlation: generation failed")
)
View Source
var ErrInvalidDerivation = errors.New("correlation: invalid deterministic derivation")

ErrInvalidDerivation reports unsafe deterministic derivation input or configuration.

View Source
var ErrInvalidDisclosure = errors.New("correlation: invalid disclosure policy")

ErrInvalidDisclosure reports unsafe disclosure configuration.

View Source
var ErrInvalidExternalID = errors.New("correlation: invalid external identifier")

ErrInvalidExternalID reports invalid external identifier metadata.

View Source
var ErrInvalidID = errors.New("correlation: invalid identifier")

ErrInvalidID reports an identifier that violates its validation policy.

View Source
var ErrInvalidPropagator = errors.New("correlation: invalid propagator")

ErrInvalidPropagator reports missing explicit propagation dependencies.

Functions

func Disclose

func Disclose(label, value string, policy DisclosurePolicy) (string, error)

Disclose renders an identifier according to an explicit observability policy. label domain-separates correlation, request, and causation hashes.

func WithValues

func WithValues(ctx context.Context, values Values) context.Context

WithValues returns a derived context carrying a copy of values.

Types

type Carrier

type Carrier interface {
	Values(key string) []string
	Set(key, value string)
}

Carrier is an explicit transport metadata boundary. Values must return a copy or immutable view. Set replaces one field with exactly one value.

type CausationID

type CausationID string

CausationID identifies the immediate parent request, message, or event.

func MustCausationID

func MustCausationID(value string, policy Policy) CausationID

MustCausationID is ParseCausationID for static configuration and tests.

func ParseCausationID

func ParseCausationID(value string, policy Policy) (CausationID, error)

ParseCausationID validates and returns a causation identifier.

func (CausationID) String

func (id CausationID) String() string

type Codec

type Codec struct {
	// contains filtered or unexported fields
}

Codec injects and extracts typed values without assigning trust.

func NewCodec

func NewCodec(options CodecOptions) (*Codec, error)

NewCodec validates and copies carrier configuration.

func (*Codec) Extract

func (codec *Codec) Extract(carrier Carrier) (Values, error)

Extract parses values but deliberately does not decide whether to trust them. Pass the result to Factory.Accept with an explicit InboundPolicy.

func (*Codec) Inject

func (codec *Codec) Inject(carrier Carrier, values Values) error

Inject installs non-empty values and refuses to overwrite any populated field, even when the existing value is malformed.

type CodecOptions

type CodecOptions struct {
	Policy           Policy
	CorrelationField string
	RequestField     string
	CausationField   string
}

CodecOptions configure immutable carrier field names and validation.

type CorrelationID

type CorrelationID string

CorrelationID groups work in one logical interaction or workflow.

func MustCorrelationID

func MustCorrelationID(value string, policy Policy) CorrelationID

MustCorrelationID is ParseCorrelationID for static configuration and tests.

func ParseCorrelationID

func ParseCorrelationID(value string, policy Policy) (CorrelationID, error)

ParseCorrelationID validates and returns a correlation identifier.

func (CorrelationID) String

func (id CorrelationID) String() string

type Deterministic

type Deterministic struct {
	// contains filtered or unexported fields
}

Deterministic derives linkable IDs for an explicitly stable workflow. It is never selected by Factory defaults and should be keyed for private inputs.

func NewDeterministic

func NewDeterministic(options DeterministicOptions) (*Deterministic, error)

NewDeterministic validates and copies deterministic strategy configuration.

func (*Deterministic) Derive

func (strategy *Deterministic) Derive(input []byte) (CorrelationID, error)

Derive hashes input with length-delimited, versioned domain separation.

type DeterministicOptions

type DeterministicOptions struct {
	Domain  string
	Version uint32
	Key     []byte
	Length  int
}

DeterministicOptions configure an explicitly opted-in stable strategy.

type DisclosureMode

type DisclosureMode uint8

DisclosureMode controls identifier disclosure to logs and telemetry.

const (
	// RedactDisclosure is the safe default and emits only a marker.
	RedactDisclosure DisclosureMode = iota
	// HashDisclosure emits a keyed, domain-separated stable token.
	HashDisclosure
	// ExposeDisclosure emits validated raw identifier text.
	ExposeDisclosure
)

type DisclosurePolicy

type DisclosurePolicy struct {
	Mode DisclosureMode
	Key  []byte
}

DisclosurePolicy must explicitly opt into linkable or raw output.

type ExternalID

type ExternalID struct {
	// contains filtered or unexported fields
}

ExternalID is an optional typed external identifier with explicit source and trust metadata. It is not implicitly promoted to any correlation ID.

func NewExternalID

func NewExternalID(options ExternalIDOptions) (ExternalID, error)

NewExternalID validates and copies external metadata.

func (ExternalID) Kind

func (external ExternalID) Kind() string

Kind returns the external identifier's declared semantic kind.

func (ExternalID) Source

func (external ExternalID) Source() string

Source returns the declared transport or system source.

func (ExternalID) Trusted

func (external ExternalID) Trusted() bool

Trusted reports the caller's explicit trust decision.

func (ExternalID) Value

func (external ExternalID) Value() string

Value returns the validated external identifier text.

type ExternalIDOptions

type ExternalIDOptions struct {
	Kind    string
	Value   string
	Source  string
	Trusted bool
	Policy  Policy
}

ExternalIDOptions require the caller to state type, source, and trust.

type Factory

type Factory struct {
	// contains filtered or unexported fields
}

Factory creates fresh hop identifiers without global mutable state.

func NewFactory

func NewFactory(options FactoryOptions) (*Factory, error)

NewFactory constructs a factory. The default generator uses crypto/rand.

func (*Factory) Accept

func (factory *Factory) Accept(inbound Values, policy InboundPolicy) (Values, error)

Accept starts a receiving hop. No inbound value is used unless its exact semantic trust is enabled.

func (*Factory) Create added in v1.1.0

func (factory *Factory) Create() (Values, error)

Create creates a new correlation and request identifier.

Example
generator := &sequenceGenerator{values: []string{"workflow", "root-request"}}
factory, err := correlation.NewFactory(correlation.FactoryOptions{Generator: generator})
if err != nil {
	fmt.Println("factory error")
	return
}
root, err := factory.Create()
if err != nil {
	fmt.Println("create error")
	return
}
fmt.Println(root.CorrelationID, root.RequestID, root.CausationID)
Output:
workflow root-request

func (*Factory) Next

func (factory *Factory) Next(parent Values) (Values, error)

Next preserves correlation, creates a request ID, and makes the prior request the immediate cause.

Example
generator := &sequenceGenerator{values: []string{"child-request"}}
factory, err := correlation.NewFactory(correlation.FactoryOptions{Generator: generator})
if err != nil {
	fmt.Println("factory error")
	return
}
parent := correlation.Values{
	CorrelationID: correlation.MustCorrelationID("workflow", correlation.Policy{}),
	RequestID:     correlation.MustRequestID("parent-request", correlation.Policy{}),
}
child, err := factory.Next(parent)
if err != nil {
	fmt.Println("next error")
	return
}
fmt.Println(child.CorrelationID, child.RequestID, child.CausationID)
Output:
workflow child-request parent-request

func (*Factory) Start deprecated

func (factory *Factory) Start() (Values, error)

Start creates a new correlation and request identifier.

Deprecated: use Create.

type FactoryOptions

type FactoryOptions struct {
	Policy    Policy
	Generator Generator
}

FactoryOptions configure an immutable Factory.

type Generator

type Generator interface {
	New() (string, error)
}

Generator supplies canonical random identifier text. It is structurally compatible with identifier.Generator[string] from identifier.

type GeneratorFunc

type GeneratorFunc func() (string, error)

GeneratorFunc adapts a function to Generator.

func (GeneratorFunc) New

func (function GeneratorFunc) New() (string, error)

New calls the wrapped function.

type InboundPolicy

type InboundPolicy struct {
	TrustCorrelation        bool
	TrustRequestAsCausation bool
}

InboundPolicy explicitly identifies which inbound semantics cross a trust boundary. Request IDs are never preserved because each hop gets a new one.

type Policy

type Policy struct {
	MaxLength int
}

Policy bounds and validates an identifier. The zero value accepts the canonical ASCII alphabet [A-Za-z0-9_-] up to 128 bytes.

type Propagator

type Propagator struct {
	// contains filtered or unexported fields
}

Propagator explicitly composes hop generation with a transport codec.

func NewPropagator

func NewPropagator(factory *Factory, codec *Codec) (*Propagator, error)

NewPropagator rejects ambient or incomplete propagation configuration.

func (*Propagator) Receive

func (propagator *Propagator) Receive(carrier Carrier, policy InboundPolicy) (Values, error)

Receive extracts untrusted metadata and applies an explicit trust policy while creating a fresh delivery-attempt request ID.

func (*Propagator) Send

func (propagator *Propagator) Send(carrier Carrier, parent Values) (Values, error)

Send creates the next hop before injecting its values.

type RequestID

type RequestID string

RequestID identifies one transport request or delivery attempt.

func MustRequestID

func MustRequestID(value string, policy Policy) RequestID

MustRequestID is ParseRequestID for static configuration and tests.

func ParseRequestID

func ParseRequestID(value string, policy Policy) (RequestID, error)

ParseRequestID validates and returns a request identifier.

func (RequestID) String

func (id RequestID) String() string

type Values

type Values struct {
	CorrelationID CorrelationID
	RequestID     RequestID
	CausationID   CausationID
}

Values carries the three deliberately distinct identifier semantics.

func FromContext

func FromContext(ctx context.Context) (Values, bool)

FromContext returns values installed by WithValues.

Directories

Path Synopsis
adapters
http
Package httpcorrelation is the target-oriented path for HTTP correlation.
Package httpcorrelation is the target-oriented path for HTTP correlation.
jsonrpc
Package correlationjsonrpc is the target-oriented path for JSON-RPC correlation.
Package correlationjsonrpc is the target-oriented path for JSON-RPC correlation.
otel
Package correlationotel projects correlation metadata into OpenTelemetry attributes and links without treating correlation IDs as trace or span IDs.
Package correlationotel projects correlation metadata into OpenTelemetry attributes and links without treating correlation IDs as trace or span IDs.
queue
Package correlationqueue is the target-oriented path for queue correlation propagation.
Package correlationqueue is the target-oriented path for queue correlation propagation.
schedule
Package correlationschedule is the target-oriented path for scheduled-work correlation.
Package correlationschedule is the target-oriented path for scheduled-work correlation.
slog
Package correlationslog provides bounded slog correlation attributes.
Package correlationslog provides bounded slog correlation attributes.
Package httpcorrelation explicitly propagates correlation metadata over HTTP.
Package httpcorrelation explicitly propagates correlation metadata over HTTP.
requestidbridge
Package requestidbridge integrates explicitly with request ID middleware such as http-middleware/requestid without importing hidden context keys.
Package requestidbridge integrates explicitly with request ID middleware such as http-middleware/requestid without importing hidden context keys.
Package jsonrpc propagates correlation through an explicit JSON-RPC metadata object.
Package jsonrpc propagates correlation through an explicit JSON-RPC metadata object.
Package log provides bounded slog attributes compatible with log's standard log/slog composition API.
Package log provides bounded slog attributes compatible with log's standard log/slog composition API.
Package queue propagates correlation metadata through backend-neutral queue metadata maps.
Package queue propagates correlation metadata through backend-neutral queue metadata maps.
Package schedule provides explicit correlation lifecycle helpers for scheduled work.
Package schedule provides explicit correlation lifecycle helpers for scheduled work.
Package telemetry links correlation metadata to OpenTelemetry without treating correlation IDs as trace or span IDs.
Package telemetry links correlation metadata to OpenTelemetry without treating correlation IDs as trace or span IDs.
Package webhook provides an explicit webhook-named HTTP correlation adapter.
Package webhook provides an explicit webhook-named HTTP correlation adapter.

Jump to

Keyboard shortcuts

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