writer

package
v0.16.0 Latest Latest
Warning

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

Go to latest
Published: Oct 6, 2026 License: MIT Imports: 25 Imported by: 0

Documentation

Overview

Package writer is the audit writer as a library: the thing that takes records, splits them into the copies their profiles keep, pseudonymises, and puts them into the locked archive.

This is what the audit-writer binary runs: that binary is built on this package, and the promises are the writer's rather than the binary's. Nothing is acknowledged before it is in the archive, a record the writer cannot take is dead-lettered and never dropped, and the writer keeps an account of itself in the archive it writes.

It is exported because the binary needs it and a test may. It is not a way to deploy: a writer inside an application puts the archive's credentials in the application's pods and makes every fix an application release, which docs/decisions/0011-one-installation-per-service-or-product.md rules out.

w, err := writer.Open(ctx, writer.Config{
	Archive:  archive,  // an s3store.Store on the Object-Locked bucket
	Profiles: profiles, // preset.ParseDeployment(doc) then Compose
	Keys:     provider, // keys.NewTransit or keys.NewLocal
})
defer w.Close(ctx)

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	// Archive is where the copies go: the Object-Locked bucket.
	Archive store.Store
	// Profiles are the deployment's profiles, composed from the presets —
	// preset.ParseDeployment and Compose read the same document the chart
	// renders.
	Profiles map[string]*preset.Profile
	// Keys pseudonymise identifiers. With a provider that can also seal
	// (keys.Transit, keys.Local), the identity behind each pseudonym is kept
	// sealed under the same key, for resolve; see ForgetIdentities.
	//
	// Optional, and nil is the default a deployment should have to argue
	// itself out of rather than into: most trails are of an organisation's
	// own staff, who are kept in clear because that is what accountability
	// is for. Open refuses nil only when a composed profile would actually
	// pseudonymise, which is what GuardKeys decides.
	Keys keys.Provider
	// Catalogues are the application's own. The common catalogue, which
	// describes the writer's own actions, is always registered.
	Catalogues []*catalogue.Catalogue

	// Database, when given, is the deduplication table shared by every
	// replica, and where catalogues registered through the registry service
	// are read from. The writer's role needs those tables and none of the
	// index: observe indexes, by following the bucket. Without a database
	// the writer deduplicates in process, and so may run as one instance
	// only.
	Database *pgxpool.Pool
	// Dedupe, when given, is the deduplication store, in place of the one
	// Database provides: for a deployment that has no database, such as the
	// writer on a function platform, where it is a DynamoDB table
	// (dedupe/dynamodbdedupe). It is shared by every replica, so it lifts the
	// one-instance limit as Database does. Give it or Database, not both.
	Dedupe Dedupe
	// Replicas is how many writers share one stream of records. Above one it
	// needs Database, and keys every replica sees the same way.
	Replicas int

	// Self is this writer's own name as an observer, stamped on records that
	// reach it in process — an embedding application's emitter is the
	// application itself, so this names it. A record that arrives over HTTP
	// carries the caller the auth middleware verified instead.
	Self string
	// ForgetIdentities keeps no sealed identities, which makes resolve
	// impossible for everything this writer writes.
	ForgetIdentities bool
	// Instance names this writer in its own records. It is not in any object
	// key: the key's ULID is what keeps two writers apart.
	// Default: the host name and process id.
	Instance string
	// RollInterval is how long an object stays open within a batch. Default
	// 5 minutes; every batch is flushed before it is acknowledged regardless.
	RollInterval time.Duration
	// Version names this build in the writer's own records.
	Version string
	// Evidence says, in the writer's start-up record, which configuration it
	// ran under: digests of the files it read. Empty says nothing.
	Evidence Evidence
	// FromStream says this writer consumes a stream its own installation's
	// receivers publish to, so a record arriving already stamped keeps that
	// stamp. A consumer reading messages has no caller to verify, and
	// re-stamping would replace the identity the receiver checked with
	// nothing. It has no effect on records reaching the sink's own port.
	FromStream bool

	// Logger, default slog.Default().
	Logger *slog.Logger
	// Meter is where the writer's counters go, default the global provider.
	Meter metric.MeterProvider
}

Config is what a writer needs. Archive and Profiles are required; everything else has a default that is safe for one instance.

type Dedupe added in v0.7.0

type Dedupe = inner.Dedupe

Dedupe is the writer's record of what it has written: Seen asks and marks nothing, Mark is called once the copies are durable, Purge forgets. See dedupe/dynamodbdedupe and index/postgres for the shared ones.

type Evidence added in v0.11.0

type Evidence = inner.Evidence

Evidence is what a writer reports of its configuration in its start-up record (audit.writer.started).

type Writer

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

Writer is an open writer. It is a sink.Sink: hand it to an emitter, or serve it over HTTP with Handler.

func Open

func Open(ctx context.Context, c Config) (*Writer, error)

Open checks the configuration, reads the legal holds, records each profile's composition, and returns a writer ready to take records.

It refuses rather than degrades: a writer that could not read the holds, or could not record a change of profile, or whose replicas would pseudonymise the same person differently, does not start.

func (*Writer) Close

func (w *Writer) Close(ctx context.Context) error

Close records that the writer stopped, writes whatever is still gathered, and stops watching the legal holds. Nothing is taken after it.

func (*Writer) Guarantees added in v0.6.0

func (w *Writer) Guarantees() sink.Durability

Guarantees implements sink.Guarantor.

func (*Writer) Handler

func (w *Writer) Handler(a auth.Authenticator) (string, http.Handler)

Handler serves the writer as the sink's Connect service, for emitters in other processes. With an authenticator, every caller must present a token it verifies, and the verified subject is stamped as the record's observer; nil accepts anybody, under no name.

func (*Writer) RefreshHolds added in v0.7.0

func (w *Writer) RefreshHolds(ctx context.Context) error

RefreshHolds reads the legal holds now. The writer refreshes them every minute in the background, which a process that is frozen between invocations does not get: its clock keeps running while it is stopped, and the first write after a thaw could act on a list that is older than it looks. A function calls this at the start of each invocation. An error leaves the last answer in force, the way a failed background refresh does.

func (*Writer) Write

func (w *Writer) Write(ctx context.Context, req *sink.Request) (*sink.Result, error)

Write implements sink.Sink. It returns only once every record in the batch is in the archive, seen before, or dead-lettered, and reports Archived.

Jump to

Keyboard shortcuts

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