replay

package
v1.2.0 Latest Latest
Warning

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

Go to latest
Published: Jul 15, 2026 License: AGPL-3.0 Imports: 22 Imported by: 0

Documentation

Overview

Package replay issues baseline and variant HTTP requests and collects responses. It owns all network I/O for possession; mutators are pure.

Engine guarantees (D15, D11, D12, D10):

  • Per-host token-bucket rate limit + bounded concurrency
  • Adaptive backoff on 429/503 honoring Retry-After
  • Body capped at MaxBody; Truncated flagged when cap hit
  • Tier-1 refresh hooks fire once per identity, before that identity's variants; refresh failure aborts that identity (variants Inconclusive) but does not abort the run

Index

Constants

View Source
const DefaultMaxBody = 5 * 1024 * 1024

DefaultMaxBody is the per-response body cap (5 MB, D12).

View Source
const DefaultMaxVariants = 10_000

DefaultMaxVariants is the cap when --max-variants is not set (D11).

Variables

This section is empty.

Functions

func DottedPath

func DottedPath(expr string, doc any) (any, error)

DottedPath evaluates a minimal dotted-path selector against a decoded JSON document (map[string]any / []any / scalar) and returns the value at that path or an error.

Grammar (D13):

path     = "$" segment*
segment  = "." name | "[" index "]"
name     = [A-Za-z0-9_-]+
index    = digits

No wildcards, no filters, no recursive descent — keep the surface area small. The hostile case is silently returning the wrong value, which a permissive parser invites; we are strict on purpose.

func IsTransientFailure added in v1.2.0

func IsTransientFailure(resp *model.Response) bool

IsTransientFailure reports whether a response failed for a reason a single re-issue could plausibly fix: a transport error (resp.Err set with no usable status) or a transient server-side rejection (HTTP 429 or any 5xx).

It deliberately returns false for responses the engine marked Inconclusive, because that flag is set only for refresh/flow short-circuits (D10) — a per-identity setup failure that a single variant retry cannot repair and that would only burn another request. Those stay inconclusive.

A nil response is treated as a transient failure (no response at all is the strongest signal that the request never completed).

Types

type Engine

type Engine struct {
	HTTP        *http.Client
	Limiter     *hostLimiter
	Concurrency int
	MaxBody     int64
	UserAgent   string
	Stderr      io.Writer

	// OnResponse, when non-nil, is invoked exactly once for each completed
	// response as soon as it is collected (ROADMAP v1.1 "resume on
	// interrupt"). It runs on worker goroutines and may be called
	// concurrently, so an implementation must be safe for concurrent use.
	// Run still returns the full plan-ordered slice regardless; the hook is
	// purely additive so a nil hook preserves the previous behaviour exactly.
	OnResponse func(resp model.Response, baseline bool)
	// contains filtered or unexported fields
}

Engine drives a single scan: refresh, replay, collect.

func New

func New(s model.RunSettings, userAgent string, stderr io.Writer) *Engine

New constructs an Engine from settings. Pass settings.MaxBody=0 to use DefaultMaxBody.

func (*Engine) PrepareFlows added in v1.1.0

func (e *Engine) PrepareFlows(ctx context.Context, matrix *model.RoleMatrix)

PrepareFlows executes named flows for every identity that references one (P7). Results are cached; a flow failure marks that identity's variants inconclusive (D10 policy, consistent with Tier-1 refresh).

func (*Engine) PrepareRefresh

func (e *Engine) PrepareRefresh(ctx context.Context, matrix *model.RoleMatrix)

PrepareRefresh fires the refresh hook for every identity that has one (D3). Results are cached on the engine; subsequent calls are no-ops. Refresh failures are recorded on the cache entry so per-variant code can short-circuit to Inconclusive (D10) and log the loud warning once.

func (*Engine) RetryInconclusive added in v1.2.0

func (e *Engine) RetryInconclusive(ctx context.Context, plan Plan, responses []model.Response) (out []model.Response, retried, improved int)

RetryInconclusive re-issues, exactly once, every variant in plan whose corresponding response in responses is a transient failure (per IsTransientFailure). It returns a new plan-ordered slice: a retried slot holds the retry's response only when that retry is itself no longer a transient failure; otherwise the original response is preserved (a flaky target should never make a result worse than the first attempt). Slots that were not retried are copied through unchanged.

The retry honors the same rate limiter, concurrency, refresh injections, and body caps as the original run — it goes through the standard fire path via a sub-plan. The OnResponse hook fires for each successful (non-transient) retry so --resume/--record see the improved response. retried is the count of variants re-issued; improved is how many of those produced a usable result.

func (*Engine) Run

func (e *Engine) Run(ctx context.Context, plan Plan) []model.Response

Run replays a Plan and returns one Response per Variant, paired by slice position. Bounded concurrency per Engine.Concurrency; results are re-sorted into plan order before return so the output is deterministic regardless of which worker finished first.

Run treats the plan as a variant plan for the OnResponse hook. Use RunWithKind to fire a baseline plan so checkpointed responses are routed to the baseline set.

func (*Engine) RunWithKind added in v1.2.0

func (e *Engine) RunWithKind(ctx context.Context, plan Plan, baseline bool) []model.Response

RunWithKind is Run with an explicit baseline flag for the OnResponse hook. baseline=true marks every completed response as an owner-baseline response so resume checkpointing can route it to the baseline set; the network behaviour is identical to Run.

type Plan

type Plan struct {
	Variants    []model.Variant
	Capped      bool
	TotalBefore int // count generated before the cap kicked in (for warn-and-proceed)
}

Plan is the deterministic, ordered list of Variants for a scan.

func Generate

func Generate(endpoints []*model.Endpoint, matrix *model.RoleMatrix, reg *mutate.Registry, max int) Plan

Generate produces the canonical variant plan for endpoints under matrix using reg. Ordering (D11):

  1. Endpoints sorted by (method, pathTemplate)
  2. One representative sample per endpoint — the CapturedRequest with the lexicographically smallest stable ID.
  3. Mutators in registry declaration order
  4. Identities within a mutator sorted by (rank, name) — owned by the mutator itself.

Each Variant gets a deterministic 16-hex-char ID:

sha256(endpoint_key + "|" + mutator + "|" + identity_name + "|" + canonical_detail_json)[:16]

max <= 0 ⇒ DefaultMaxVariants. On cap, generation stops and Plan.Capped is set; caller is expected to warn and proceed with the partial plan.

Jump to

Keyboard shortcuts

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