Help us shape the future of Go! Take the 2026 Go Developer Survey

audittest

package
v0.50.1 Latest Latest
Warning

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

Go to latest
Published: Aug 9, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

Documentation

Overview

Package audittest holds the canonical list of audit emissions the core must produce, plus the recording sink the contract tests drive it with.

Why a shared package instead of one list per test: the audit seam (ext.AuditSink) is the one thing an embedding distribution relies on to know WHO read WHICH historical rows. Before #945 the wiring covered the CLI's query/recover and the MCP tools only, while ext/audit.go's own contract named the shim, reconstruct and the console — and no test pinned any of it, so a refactor could drop an emission with CI green. Required below is that contract, machine-checked.

The list is split by Owner rather than exercised from one place because each surface is only drivable from inside its own package (unexported handlers, in-package fakes) or needs a live MySQL. Every owner's contract test calls CheckCoverage with the pairs it actually observed, so a dropped emission fails that owner's test and an emission nobody exercises fails as uncovered.

This package is imported by tests only; it is never linked into a binary.

Index

Constants

This section is empty.

Variables

View Source
var Required = []Requirement{
	{
		Pair:  Pair{Surface: "cli", Action: "query.run"},
		Owner: OwnerCLI,
		Why:   "reading indexed row images (before/after) from the command line",
	},
	{
		Pair:  Pair{Surface: "cli", Action: "recover.generate"},
		Owner: OwnerCLI,
		Why:   "producing a reversal script — the mutation artifact recover exists to record",
	},
	{
		Pair:  Pair{Surface: "cli", Action: "recover.cascade"},
		Owner: OwnerCLI,
		Why:   "producing a reversal script that also SYNTHESIZES cascade-deleted child rows",
	},
	{
		Pair:  Pair{Surface: "cli", Action: "reconstruct.run"},
		Owner: OwnerCLI,
		Why:   "reading a baseline snapshot's rows and folding deltas onto them (point-in-time row state)",
	},
	{
		Pair:  Pair{Surface: "cli", Action: "verify.explain"},
		Owner: OwnerCLI,
		Why:   "the only verify output that prints row-level data (the differing rows of a mismatch)",
	},
	{
		Pair:  Pair{Surface: "cli", Action: "drill.run"},
		Owner: OwnerCLI,
		Why:   "materializing historical row state into an operator-provided scratch server (a rehearsed restore)",
	},
	{
		Pair:  Pair{Surface: "mcp", Action: "query.run"},
		Owner: OwnerMCP,
		Why:   "an LLM client reading indexed row images",
	},
	{
		Pair:  Pair{Surface: "mcp", Action: "recover.generate"},
		Owner: OwnerMCP,
		Why:   "an LLM client generating a reversal script",
	},
	{
		Pair:  Pair{Surface: "mcp", Action: "recover.cascade"},
		Owner: OwnerMCP,
		Why:   "an LLM client generating a cascade reversal script with SYNTHESIZED child rows",
	},
	{
		Pair:  Pair{Surface: "mcp", Action: "reconstruct.row"},
		Owner: OwnerMCPIntegration,
		Why:   "an LLM client reading point-in-time row state (baseline + deltas)",
	},
	{
		Pair:  Pair{Surface: "console", Action: "reconstruct.row"},
		Owner: OwnerMCPIntegration,
		Why:   "the console's /mcp endpoint mounts the same reconstruct handler with Surface console",
	},
	{
		Pair:  Pair{Surface: "shim", Action: "timetravel.query"},
		Owner: OwnerShim,
		Why:   "network time-travel reads (_flashback/_snapshot/_diff) by an authenticated tenant",
	},
	{

		Pair:  Pair{Surface: "shim", Action: "timetravel.query"},
		Owner: OwnerPGShim,
		Why:   "the PostgreSQL wire front-end serves the same row images outside HandleQuery",
	},
	{
		Pair:  Pair{Surface: "console", Action: "query.run"},
		Owner: OwnerConsoleUnit,
		Why:   "reading indexed row images from the web console",
	},
	{
		Pair:  Pair{Surface: "console", Action: "recover.generate"},
		Owner: OwnerConsoleUnit,
		Why:   "generating a reversal script from the web console",
	},
	{
		Pair:  Pair{Surface: "console", Action: "verify.explain"},
		Owner: OwnerConsoleUnit,
		Why:   "the console's row-level mismatch drill-down",
	},
	{
		Pair:  Pair{Surface: "console", Action: "authz.denied"},
		Owner: OwnerConsoleUnit,
		Why:   "an authorization refusal — one of the two console events that are not data reads",
	},
	{
		Pair:  Pair{Surface: "console", Action: "profile.denied"},
		Owner: OwnerConsoleUnit,
		Why:   "a data-profile refusal: an unknown profile, or a surface that cannot honor redaction",
	},
	{
		Pair:  Pair{Surface: "console", Action: "reconstruct.run"},
		Owner: OwnerConsoleIntegration,
		Why:   "point-in-time row state served by the console's Time-travel tab",
	},
	{
		Pair:  Pair{Surface: "console", Action: "recover.cascade"},
		Owner: OwnerConsoleIntegration,
		Why:   "cascade reversal (with synthesized child rows) served by the console",
	},
	{
		Pair:  Pair{Surface: "console", Action: "sql.run"},
		Owner: OwnerConsoleUnit,
		Why:   "a free-form SQL statement executed over the archive/baseline Parquet by the console's SQL panel (#1177)",
	},
}

Required is the exact set of audit emissions the core guarantees, keyed by (pair, owner) — one pair may appear under two owners when two independent serving layers emit it (shim/timetravel.query: the MySQL command loop and the PostgreSQL front-end). Adding a surface means adding a row here AND exercising it in that owner's contract test; removing one means deleting the row and correcting the ext/audit.go docstring, which enumerates the same set in prose.

Functions

func CheckCoverage

func CheckCoverage(t *testing.T, owner Owner, observed []Pair)

CheckCoverage fails t unless the observed pairs cover the pairs owner is required to emit: every requirement seen at least once, and no observed pair that Required does not list anywhere.

Know exactly what that proves (#1123). At-least-once catches a DELETED emission for a pair; it is structurally blind to a NEW unaudited mode of a command that already has an audited one (reconstruct --baseline-only slipped through exactly this way). And the undeclared arm fails only on an undeclared emission FROM AN EXERCISED PATH — an emission on a handler no contract case drives, or a pair declared for a different owner, is invisible here. TestAuditRecordCallSitesAccounted (callsites_test.go) is the source-level backstop for the call-site half of that blindness; a new MODE reusing an existing call site still needs a hand-added emission and contract case.

Types

type Owner

type Owner string

Owner names the contract test responsible for exercising a requirement. It is the test's location, not a taxonomy of its own — the surface vocabulary lives in ext.AuditEvent.Surface.

const (
	// OwnerCLI: internal/cli/audit_contract_integration_test.go. The CLI
	// commands connect through a real DSN (config.Connect), so their contract
	// test needs the integration MySQL — which CI runs on every pull request
	// as a required check, not only on tagged refs.
	OwnerCLI Owner = "internal/cli (integration)"
	// OwnerConsoleUnit: internal/console/audit_contract_test.go — the HTTP
	// handlers that a sqlmock index or a stub controller can serve.
	OwnerConsoleUnit Owner = "internal/console (unit)"
	// OwnerConsoleIntegration: internal/console/audit_contract_integration_test.go
	// — the endpoints that need a real index plus a baseline snapshot.
	OwnerConsoleIntegration Owner = "internal/console (integration)"
	// OwnerShim: internal/shim/audit_contract_test.go — Handler.HandleQuery
	// driven with the package's injected fetcher/resolver fakes.
	OwnerShim Owner = "internal/shim (unit)"
	// OwnerMCP: internal/mcptools/audit_contract_test.go — the tool handlers
	// over a sqlmock index.
	OwnerMCP Owner = "internal/mcptools (unit)"
	// OwnerMCPIntegration: internal/mcptools/reconstruct_integration_test.go —
	// the reconstruct tool needs a real index plus a baseline Parquet, so its
	// contract case lives in the integration tier (which CI runs on every
	// pull request as a required check).
	OwnerMCPIntegration Owner = "internal/mcptools (integration)"
	// OwnerPGShim: internal/pgshim/audit_contract_integration_test.go — the
	// PostgreSQL wire front-end drives a real pgx client against a seeded
	// index, the same real-code-path posture as the other owners.
	OwnerPGShim Owner = "internal/pgshim (integration)"
)

type Pair

type Pair struct {
	Surface string
	Action  string
}

Pair identifies one audit emission: the ext.AuditEvent Surface and Action an operator (or an embedding distribution's sink) can rely on seeing.

func RequiredFor

func RequiredFor(owner Owner) []Pair

RequiredFor returns the pairs owner must exercise.

func (Pair) String

func (p Pair) String() string

type Recorder

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

Recorder is an ext.AuditSink that keeps every event for assertions. Safe for concurrent use, as the AuditSink contract demands.

func Install

func Install(t *testing.T) *Recorder

Install registers a Recorder as the process-wide audit sink and removes it when the test ends.

The sink is process-wide, so a test that installs one must NOT call t.Parallel() — two parallel tests would observe each other's events. The swap itself is atomic (ext keeps the sink behind an atomic.Pointer), so driving a surface from another goroutine is race-safe.

func (*Recorder) Events

func (r *Recorder) Events() []ext.AuditEvent

Events returns a copy of everything recorded so far.

func (*Recorder) Pairs

func (r *Recorder) Pairs() []Pair

Pairs returns the (surface, action) pairs recorded so far, in order.

func (*Recorder) Record

func (r *Recorder) Record(_ context.Context, ev ext.AuditEvent)

Record implements ext.AuditSink.

func (*Recorder) Reset

func (r *Recorder) Reset()

Reset drops recorded events so one recorder can serve a whole table of subtests.

type Requirement

type Requirement struct {
	Pair
	Owner Owner
	// Why states what the operator loses if this emission disappears.
	Why string
}

Requirement is one emission the core must make, and where it is pinned.

Jump to

Keyboard shortcuts

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