span

package
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Oct 22, 2026 License: MIT Imports: 3 Imported by: 0

Documentation

Overview

Package span implements confirmed-span collection, deduplication, merging, precedence resolution, ordered release, and the redacting writer used by the streaming engine. See docs/DESIGN.md, section "internal/span", for the frozen contract this package implements.

The package never retains input bytes: Span and Collector deal purely in absolute byte offsets.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Collector

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

Collector accumulates spans out of order, deduplicating and merging them eagerly, and releases them in Start order once the caller guarantees no further span can touch them.

The zero value is a ready-to-use, empty Collector.

Merge/precedence rules (deterministic — see docs/DESIGN.md):

  1. Identical spans (same Start/End) dedupe: keep the highest Confidence, then the lowest Rule index.
  2. Overlapping or exactly adjacent (a.End == b.Start) spans merge into their union. Attribution (Rule/Confidence of the merged result) goes to the higher-Confidence contributor; ties break to the contributor with the earlier original Start, then to the lower Rule index.
  3. Merging is transitive: a chain of overlapping/adjacent spans collapses into a single span.

entries is always kept sorted by start and, because merged spans are non-overlapping by construction, is therefore also sorted by end.

func (*Collector) Add

func (c *Collector) Add(s Span)

Add inserts s into the collector, merging it with any spans it overlaps or is adjacent to (transitively) and resolving attribution per the precedence rules documented on Collector.

Add panics if s.Start >= s.End: this indicates a programmer error in the caller (e.g. a validator producing an empty or inverted range), not a data condition the collector can recover from.

func (*Collector) Earliest

func (c *Collector) Earliest() (Span, bool)

Earliest returns a snapshot of the earliest held merged span — the one with the smallest Start — and true, or the zero Span and false when the collector holds nothing.

The streaming engine uses the snapshot's Start to cap its emission frontier (plain bytes at or past a held span's Start cannot be emitted yet) and its End to prove that a straddling span covers buffer bytes it wants to discard under buffer pressure. The snapshot reflects the merge state at call time; a held span may still grow through later Adds.

func (*Collector) Pending

func (c *Collector) Pending() bool

Pending reports whether any span is still held by the collector (i.e. has not yet been released).

func (*Collector) Release

func (c *Collector) Release(dst []Span, limit int64) []Span

Release appends to dst every merged span whose End is <= limit, in Start order, and removes them from the collector. The returned spans are mutually non-overlapping (they always are, by construction).

The engine's contract with Release is: once Release(limit) has been called, the engine will never subsequently Add a span with Start <= limit. Note the non-strict bound: adjacent spans merge (a.End == b.Start, merge rule 2 above), so a merged span with End == limit could still merge with a future span whose Start == limit — forbidding only Start < limit would not rule that out. Under the Start <= limit contract, any merged span with End <= limit can never grow further (a span that could still merge with it would need a Start <= its End <= limit), so it is safe to release. Spans with End > limit — including ones that overlap limit — stay held and are never split.

Release is idempotent in the sense that calling it again with the same or a smaller limit returns no additional spans (dst is returned unchanged, aside from the append no-op).

func (*Collector) Reset

func (c *Collector) Reset()

Reset clears all held state so the Collector can be reused, retaining its backing array's capacity.

type MaskStrategy

type MaskStrategy uint8

MaskStrategy selects how a redacted span's bytes are replaced in the Writer's output.

const (
	// MaskFixedMarker writes exactly one copy of the fixed marker per
	// span, regardless of the span's length. This is the default.
	MaskFixedMarker MaskStrategy = iota

	// MaskLengthPreserving writes one '*' per redacted input byte, so
	// output length always equals input length.
	MaskLengthPreserving

	// MaskFormatPreserving writes one byte per redacted input byte,
	// preserving its coarse character class: uppercase letters become
	// 'X', lowercase letters 'x', digits '9'; the separator bytes
	// - _ . : / + = @ pass through verbatim; every other byte becomes
	// '*'. Output length equals input length and token shapes (JWT
	// dots, UUID dashes, base64 padding) survive. Note the preserved
	// separators and class shape ARE information about the original
	// secret; this strategy trades that disclosure for format fidelity
	// and must be an explicit caller choice.
	MaskFormatPreserving
)

type Span

type Span struct {
	Start, End int64 // absolute input offsets, half-open, Start < End
	Rule       int   // index into the active rule set
	Confidence uint8
}

Span is a confirmed redaction candidate expressed as an absolute, half-open byte range [Start, End) within the input stream.

Rule is the index of the detection rule that produced the span, into whatever rule set the caller is using. Confidence is an opaque ordering value: higher means "more confident this is a true positive".

type Writer

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

Writer emits input bytes to an underlying io.Writer, replacing each redacted span's bytes according to a MaskStrategy: with exactly one copy of a fixed marker (MaskFixedMarker), or byte for byte with masked replacements (MaskLengthPreserving, MaskFormatPreserving).

Writer tracks absolute input offsets across a sequence of Emit calls. The caller (the engine) must feed it consecutive, non-overlapping, gap-free input regions in ascending order — that is, the region passed to one Emit call must start exactly where the previous one ended (the very first call may start at any offset, e.g. 0). Each Emit call also carries the spans (already merged, sorted by Start, non-overlapping — as produced by Collector.Release) whose Start falls within that call's region.

Straddling spans

A merged span may be longer than a single Emit call's region: it can start in one call and end in a later one. The contract for this case is:

  • A span is reported to Emit exactly once, in the call whose region contains the span's Start. Its End may be beyond that region.
  • The caller must NOT pass that span (or any remainder of it) again in later calls; Writer tracks the in-progress redaction internally and automatically consumes input bytes belonging to it across as many subsequent Emit calls as needed, without emitting any further marker for it.
  • Under MaskFixedMarker the marker is written exactly once, at the point the span starts, and the span's remaining bytes are skipped. Under the preserving strategies each Emit call instead writes the masked replacement of the span bytes its region covers, so the replacement accumulates to exactly the span's length across calls.

Discarded span interiors

The engine may discard the interior bytes of a very long merged span under buffer pressure and replay them as zero-filled scratch. Under MaskFixedMarker those bytes are never read. Under the preserving strategies the zero bytes mask to '*' (0x00 is in no preserved class), so length preservation still holds exactly and format preservation degrades to '*' fill for the discarded stretch only.

A span's Start must never be less than the offset of the region currently being processed (i.e. it must not start before or inside a still-pending straddling redaction, and it must not start before the current Emit call's off). Violating this, or feeding non-contiguous regions, is a programmer error and Writer panics.

func NewMaskingWriter

func NewMaskingWriter(w io.Writer, marker []byte, strategy MaskStrategy) *Writer

NewMaskingWriter returns a Writer that writes to w, replacing redacted span bytes according to strategy. marker is used only by MaskFixedMarker; it is copied by reference and must not be mutated by the caller afterward.

func NewWriter

func NewWriter(w io.Writer, marker []byte) *Writer

NewWriter returns a Writer that writes to w, replacing redacted span bytes with exactly one marker per span (MaskFixedMarker). marker is copied by reference and must not be mutated by the caller afterward.

func (*Writer) BytesWritten

func (w *Writer) BytesWritten() int64

BytesWritten returns the total number of bytes written to the underlying io.Writer so far: plain input bytes plus, per redacted span, one marker's worth of bytes (MaskFixedMarker) or one masked byte per span byte (the preserving strategies, under which BytesWritten tracks input length exactly).

func (*Writer) Emit

func (w *Writer) Emit(off int64, data []byte, spans []Span) error

Emit writes data — which corresponds to the absolute input range [off, off+len(data)) — to the underlying writer, replacing the bytes covered by spans with the marker. See the Writer doc comment for the exact straddling-span contract.

Emit always consumes the entirety of data; there is no partial consumption to report. It returns any error from the underlying io.Writer's Write, unmodified. Once Emit returns an error, the Writer must not be used again: its internal offset bookkeeping may not have advanced past the failed write.

func (*Writer) RedactedBytes

func (w *Writer) RedactedBytes() int64

RedactedBytes returns the total number of original input bytes that have been replaced by markers so far (summed across all spans, and across all the Emit calls a straddling span was spread over).

Jump to

Keyboard shortcuts

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