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

db

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Sep 16, 2026 License: GPL-3.0 Imports: 30 Imported by: 0

Documentation

Overview

Package db is the only package that speaks PostgreSQL.

One engine, supported properly. There is no store interface here pretending that two database engines are interchangeable, because that abstraction costs more than it saves and hides the differences that matter — COPY for bulk lap ingest, partial indexes for the reference lookup, binary parameters for a compressed trace blob. pgx speaks the native protocol, sqlc turns the plain SQL in queries/ into typed methods, and goose applies the migrations in migrations/ at startup from inside the binary, so there is no migration command for an operator to remember.

The dependency rule is enforced by the linter: nothing outside this package may import pgx, goose or database/sql. A caller that needs a new query adds SQL to queries/, regenerates, and calls a typed method.

Setup is decided here

One row, in setup_state, says whether the wizard has already run. It is written in the same transaction as the first administrator, and its primary key is a single fixed value so that two concurrent attempts contend and exactly one wins. See Store.CompleteSetup and Store.SetupState.

Index

Constants

View Source
const (
	// ActionSettingsIdentity is a change to what discovery publishes about
	// the organisation.
	ActionSettingsIdentity = "settings.identity"
	// ActionSettingsVoice is a change to the voice service. Nothing writes it
	// any more — the voice left the core with its vendor — and it is kept
	// because an operator's audit log still holds rows that say it, and a
	// reader that did not know the name would show them as nothing.
	ActionSettingsVoice = "settings.voice"
	// ActionSettingsCoaching is a change to the language-model settings.
	ActionSettingsCoaching = "settings.coaching"
	// ActionSettingsLimits is a change to the published limits.
	ActionSettingsLimits = "settings.limits"
	// ActionSettingsKeyRemoved is an operator clearing a stored API key. Like
	// [ActionSettingsVoice] nothing writes it now: the core stores no
	// credential, and clearing a plugin's is recorded against the plugin.
	ActionSettingsKeyRemoved = "settings.key_removed"
	// ActionVoiceTested was the Test voice button, which left with the voice.
	// Kept so an operator's older audit rows still read as what they were.
	ActionVoiceTested = "settings.voice_tested"
	// ActionDataKeyRegenerated is the data key being replaced.
	ActionDataKeyRegenerated = "danger.data_key_regenerated"
	// ActionDevicesRevoked is every device token being revoked at once.
	ActionDevicesRevoked = "danger.devices_revoked"
	// ActionDeviceRevoked is one machine being signed out from the devices
	// page.
	ActionDeviceRevoked = "devices.revoked"
	// ActionDriverDevicesRevoked is every machine of one driver being signed
	// out at once — the lost-laptop action.
	ActionDriverDevicesRevoked = "devices.revoked_for_driver"
	// ActionRetentionChanged is a change to how long traces are kept.
	ActionRetentionChanged = "data.retention"
	// ActionTracesPruned is a retention prune being started.
	ActionTracesPruned = "data.traces_pruned"
	// ActionClientBuilt is one Windows client being built for an address.
	ActionClientBuilt = "clients.built"
	// ActionCertificateStored is an operator giving this server a code-signing
	// certificate, and ActionCertificateRemoved is taking it away again. The
	// subject is the certificate's own subject, which is public: it is printed
	// inside every client signed with it.
	ActionCertificateStored  = "clients.certificate_stored"
	ActionCertificateRemoved = "clients.certificate_removed"
	// ActionDriverSignedOut is a driver's browser session being ended by the
	// operator. It is separate from revoking their machines: a machine's token
	// uploads laps and a browser session reads them, and ending one is not
	// ending the other.
	ActionDriverSignedOut = "drivers.signed_out"
)

The audit actions this server writes. They are constants rather than strings at the call site so that a query for "who changed the settings" is a query for a value and not a guess at the spelling somebody used.

View Source
const (
	SettingOrganisation = "organisation"
	SettingPublicHost   = "public_host"
	SettingTLSMode      = "tls_mode"
	SettingShortName    = "short_name"
	SettingAccent       = "accent"
	SettingLimits       = "limits"
	SettingRetention    = "retention"
)

The keys of the settings table. They are spelled out rather than derived from the struct so that renaming a Go field cannot silently orphan a stored row.

View Source
const AcquireTimeout = 5 * time.Second

AcquireTimeout bounds how long a caller waits for a connection from the pool before giving up. It is separate from the statement's own deadline: waiting five seconds for a free connection and then being given the caller's full time to run is the behaviour a busy server wants.

View Source
const FirstLapCursor = -1

FirstLapCursor is the cursor that starts at a stint's first lap. Lap numbers begin at zero, so the value below every real one is -1.

View Source
const IdempotencyTTL = 24 * time.Hour

IdempotencyTTL is how long a stored response is replayed for. Twenty-four hours covers a client that was offline for a session and drained its queue the next morning, which is the case the table exists for.

View Source
const IdleInTransactionTimeout = 60 * time.Second

IdleInTransactionTimeout kills a transaction that was opened and then abandoned, which is what a crashed request looks like from the database's side. Without it one such request holds its locks until the process restarts.

View Source
const MinConns = 4

MinConns is the floor on pool size, so a single-core machine still has room for a request, a background job and a health check at once.

View Source
const MinServerVersion = "15"

MinServerVersion is MinServerVersionNum as an operator reads it.

View Source
const MinServerVersionNum = 150000

MinServerVersionNum is the oldest PostgreSQL this server runs against, in the server_version_num spelling: 150000 is PostgreSQL 15. It is stated in the wizard and checked before the operator is allowed to continue (D-4).

The floor is 15 rather than 14 for a reason that has nothing to do with SQL: PostgreSQL 14 leaves support in November 2026, and a server shipped now with a floor on a version about to stop receiving security fixes would be telling operators to run something nobody is patching.

Two things follow from it that the plugin boundary relies on. PostgreSQL 15 stopped granting CREATE on the public schema to PUBLIC, so a plugin role cannot create tables beside core's; and security_invoker exists, so a view's read-as-owner semantics — which is what lets a plugin be granted a view without being granted the table behind it — is stated in the migration rather than depending on a default.

Not 16. PostgreSQL 15 is supported until November 2027 and is on every managed service; 16's stricter CREATEROLE would be welcome but is not worth refusing a year of otherwise fine installations.

View Source
const PageSize = 25

PageSize is how many rows the admin panel's lists ask for at a time. It is one constant because every list on those pages is read the same way and an operator scrolling one should not find another paged differently.

View Source
const PairingTTL = 10 * time.Minute

PairingTTL is how long a device code stays good, and it is the value POST /pair/start reports as expires_in_s. Ten minutes is long enough for a driver to find the operator and short enough that an abandoned code is not sitting there tomorrow.

View Source
const PluginRolePrefix = "plugin_"

PluginRolePrefix is what a plugin's role and schema are called. It is a prefix rather than a bare name so that a plugin can never be granted a role the operator made for something else by choosing its name carefully.

View Source
const PruneBatch = 500

PruneBatch is how many laps one step of a retention prune clears. It is small enough that each statement is short — a long UPDATE holds its locks and its snapshot for as long as it runs — and large enough that a hundred thousand laps is a few hundred statements rather than a few hundred thousand.

View Source
const StatementTimeout = 30 * time.Second

StatementTimeout is set on every connection, which is where D-4's "statement_timeout on the role" is actually implemented. Setting it as a runtime parameter rather than with ALTER ROLE means it applies whether or not the operator's database user may alter itself, and it cannot be forgotten when someone creates a second user.

View Source
const UUIDLen = 16

UUIDLen is the length of a UUID in bytes.

Variables

View Source
var ErrAdminEmailTaken = errors.New("db: that email address already has an account")

ErrAdminEmailTaken is returned when the administrator's email address is already in use. During setup it cannot happen — there are no accounts yet — but the same query creates accounts later, so the case is named.

View Source
var ErrBadUUID = errors.New("db: that is not a UUID")

ErrBadUUID reports an identifier that is not a UUID. Stint identifiers are generated by the client, so this is an ordinary answer to a request rather than a fault: the caller turns it into the contract's "invalid".

View Source
var ErrIdempotencyConflict = errors.New("db: that idempotency key was used with a different request")

ErrIdempotencyConflict reports an idempotency key presented a second time with a different request. The contract calls that a bug in the caller: it is never retried and the stored response is not touched.

View Source
var ErrNotFound = errors.New("db: not found")

ErrNotFound is returned when a row that was asked for by key is not there. It is separate from a failure, because "no such session" is an ordinary answer on a request path and a database fault is not.

View Source
var ErrPairingSpent = errors.New("db: that pairing has already issued its token")

ErrPairingSpent is returned when a pairing has already handed its token over. A device-code grant issues exactly once: a second poll is answered "approved" with no token, and a client that has lost its token pairs again.

View Source
var ErrSetupAlreadyComplete = errors.New("db: setup has already been completed")

ErrSetupAlreadyComplete is returned by Store.CompleteSetup when the database already holds a finished setup. It is the answer to the second of two concurrent attempts, and to anyone who deleted a data directory hoping the wizard would come back.

Functions

func Describe

func Describe(url string) (string, error)

Describe renders a connection string as a place, with no credential in it: "pacenote on db.example.com:5432, as the user pacenote". The setup wizard shows it on the confirmation page, where the password must not be.

func MigrationSources

func MigrationSources() ([]string, error)

MigrationSources lists the migrations this binary carries, oldest first. The admin panel shows them; a test asserts the list is not empty, which is the cheapest possible guard against an embed pattern that silently matches nothing.

func Probe

func Probe(ctx context.Context, url string) error

Probe opens a connection to url, checks that the server is new enough and that the user may create the schema, and closes again. It is what the setup wizard calls before it lets the operator move on, and what the admin panel calls to test a changed connection string.

Everything it can diagnose comes back as a *ConnectError with a Reason.

func ReasonOf

func ReasonOf(err error) string

ReasonOf is a label for a failure that is safe to put in a log line: the diagnosis and nothing else. An error this package did not produce is "unknown", because its text has not been checked for secrets.

func Slug

func Slug(name string) string

Slug is the stable, URL-safe spelling of a driver's name: lower case, letters and digits, single hyphens between words. Accented letters keep their base letter, so "Escudería" and "Escuderia" do not become two different people.

Types

type Admin

type Admin struct {
	ID           int64
	Email        string
	PasswordHash string
	CreatedAt    time.Time
	LastLoginAt  *time.Time
}

Admin is an administrator of the panel.

type AdminSession

type AdminSession struct {
	ID         int64
	AdminID    int64
	Email      string
	CreatedAt  time.Time
	LastSeenAt time.Time
	ExpiresAt  time.Time
}

AdminSession is a signed-in browser.

type AuditEntry

type AuditEntry struct {
	// ID is the row's identity, and At is when it was written.
	ID int64
	At time.Time
	// Actor is the administrator's email address.
	Actor string
	// Action is one of the constants above.
	Action string
	// Subject is what was acted on — a settings group, a count of devices.
	Subject string
	// Detail is the JSON document, already encoded.
	Detail []byte
}

AuditEntry is one line of the trail: who did what, to what, and when.

Detail names the fields that changed and never carries their values. A settings change can be a change to an API key, and a trail that recorded the value would be the one place the key was readable.

func (AuditEntry) Fields

func (e AuditEntry) Fields() []string

Fields reads the field names out of AuditEntry.Detail. A detail that does not carry any is no fields rather than an error: the trail is written by several versions of this server over its life.

type ClientBuild

type ClientBuild struct {
	ID int64
	At time.Time
	// Actor is the administrator's email address.
	Actor string
	// Address is the server the built client talks to.
	Address string
	// Signing is which of the three routes was taken, as the build page
	// spells it.
	Signing string
	// Reference names the file on the disk, the download address, and the
	// identifier stamped inside the binary.
	Reference string
	// FileName is what a driver downloads, FileBytes its size, and SHA256 the
	// digest an operator tells their drivers to check.
	FileName  string
	FileBytes int64
	SHA256    string
	// ClientVersion is the version of the prebuilt client it was stamped from.
	ClientVersion string
	// SignedBy is the certificate that signed it, empty for a build that was
	// not signed or was made before this server recorded it.
	SignedBy string
}

ClientBuild is one client an operator built: the address that went into the binary, the file it produced, and who pressed the button.

func (ClientBuild) Cursor

func (b ClientBuild) Cursor() ClientBuildCursor

Cursor is where a page of the history resumes after this row.

type ClientBuildCursor

type ClientBuildCursor struct {
	At time.Time
	ID int64
}

ClientBuildCursor is a position in the build history: newest first, so the next page is everything older than this row.

type ClientBuildQuery

type ClientBuildQuery struct {
	// After is the cursor, or nil for the first page.
	After *ClientBuildCursor
	// Limit is how many rows to read. Zero is [PageSize].
	Limit int
}

ClientBuildQuery asks for one page of the build history.

type ConnectError

type ConnectError struct {
	Reason  Reason
	Message string
	// contains filtered or unexported fields
}

ConnectError is a failure to use a database, diagnosed.

Message is a complete sentence written for the operator: it names what failed and what to do about it, and it never contains the connection string.

func (*ConnectError) Error

func (e *ConnectError) Error() string

Error implements error. The rendering is for a log line; the operator sees Message.

func (*ConnectError) Unwrap

func (e *ConnectError) Unwrap() error

Unwrap returns the driver's own error, for a log line that wants it.

type DataStats

type DataStats struct {
	// DatabaseBytes is what PostgreSQL reports for the whole database,
	// indexes and its own overhead included.
	DatabaseBytes int64
	// TraceBytes is how much of that is compressed lap traces.
	TraceBytes int64
	// Drivers, Stints and Laps are row counts, and Traces is how many of
	// those laps still hold a trace — the two differ once retention has run.
	Drivers, Stints, Laps, Traces int64
	// OldestStintAt and OldestLapAt are the oldest records held, or the zero
	// time when there are none.
	OldestStintAt, OldestLapAt time.Time
}

DataStats is everything the data page says about what is stored. It comes back from one statement, because a page of eight numbers that asked eight questions would be eight round trips for one screen.

type Device

type Device struct {
	ID          int64
	DriverID    int64
	TokenPrefix string
	Label       string
	CreatedAt   time.Time
	LastUsedAt  *time.Time
	RevokedAt   *time.Time
}

Device is one paired machine belonging to a driver. The token it was issued is not here, and cannot be: only its digest was ever stored (D-2).

func (Device) Revoked

func (d Device) Revoked() bool

Revoked reports whether this device has been revoked, which a driver sees as removing a machine from their list.

type DeviceCursor

type DeviceCursor struct {
	CreatedAt time.Time
	ID        int64
}

DeviceCursor is where a page of the devices list resumes.

type DeviceQuery

type DeviceQuery struct {
	// After is the cursor, or nil for the first page.
	After *DeviceCursor
	// Limit is how many rows to read. Zero is [PageSize].
	Limit int
}

DeviceQuery asks for one page of the devices list, newest first.

type Driver

type Driver struct {
	ID         int64
	ExternalID string
	Name       string
	Slug       string
	Class      string
	AvatarURL  string
	CreatedAt  time.Time
}

Driver is one person who drives. In the community edition there is one organisation and no entitlement model, so a driver is an identity and a display name and nothing else.

type DriverSession

type DriverSession struct {
	ID       int64
	DriverID int64
	// Slug and Name are the driver's, so the caller need not read the row.
	Slug string
	Name string
	// CreatedAt, LastSeenAt and ExpiresAt are the session's own life.
	CreatedAt  time.Time
	LastSeenAt time.Time
	ExpiresAt  time.Time
	// UserAgent is what the browser said it was, cut to something a table cell
	// can hold.
	UserAgent string
	// SignedInBy is the plugin that authenticated this driver.
	SignedInBy string
}

DriverSession is one browser signed in as one driver.

type DriverStorage

type DriverStorage struct {
	ID         int64
	Name       string
	Laps       int64
	TraceBytes int64
}

DriverStorage is one line of the data page's "who is using the disk" list.

type LapResult

type LapResult struct {
	Accepted  int
	BestLapMs int
	Conflicts []int
	Stored    []int
}

LapResult is what the ingest did. Accepted counts the laps this call stored, so a lap already held counts once across the two calls and not twice. Conflicts names the lap numbers already held with different content, and a non-empty Conflicts means nothing was stored. Stored names the lap numbers this call actually wrote, in the order they were sent. It is not derivable from Accepted: a batch that repeats a lap already held stores the rest and counts the repeat once, so "the first Accepted of them" is the wrong set. A caller that has to act on each new lap — publishing an event, say — needs to know which.

type LapRow

type LapRow struct {
	Number     int
	LapMs      int
	Kind       string
	StartedAt  time.Time
	ContentSum []byte
	TraceCodec int
	Trace      []byte
	// Corners is the client's own corner analysis, already encoded as the JSON
	// document the column holds. Nothing here looks inside it — it is written
	// with the lap and read back whole — so it is carried as bytes. Nil is a lap
	// with nothing to say about its corners and is stored as an empty array:
	// the column is NOT NULL, and one spelling of "no corners" is less to get
	// wrong than two.
	Corners []byte
}

LapRow is one completed lap ready to store. ContentSum fingerprints the lap as the client sent it and is what makes a repeat of lap 7 either the same lap 7 or a conflict; Trace is already compressed by the protocol codec.

type LapWrite

type LapWrite struct {
	StintID  UUID
	DriverID int64
	Sim      string
	TrackID  string
	Car      string
	CarClass string
	Laps     []LapRow
}

LapWrite is one POST /stints/{id}/laps, ready to store. The simulator, track, car and class are copied down from the stint onto every lap, which is the one denormalisation the reference lookup rests on.

Sim is part of that identity and not a label beside it. A track_id is a slug the client derives from the name its simulator prints, so two simulators arrive at the same slug for the same circuit and mean two different circuits — different physics, different lap times — and a lap of one is never the reference for a lap of the other.

type MigrationState

type MigrationState struct {
	// Version is the migration the database is at.
	Version int64
	// Target is the newest migration this binary carries.
	Target int64
	// Applied and Pending count the migrations either side of the line.
	Applied, Pending int
	// LastAppliedAt is when the newest applied migration ran, or the zero time
	// if nothing has been applied.
	LastAppliedAt time.Time
}

MigrationState is what the admin panel's overview shows about the schema.

func (MigrationState) UpToDate

func (m MigrationState) UpToDate() bool

UpToDate reports whether the database carries every migration this binary knows about.

type NewClientBuild

type NewClientBuild struct {
	Actor         string
	Address       string
	Signing       string
	Reference     string
	FileName      string
	FileBytes     int64
	SHA256        string
	ClientVersion string
	SignedBy      string
}

NewClientBuild is a build to record. It is the page's own vocabulary rather than the table's, so that a caller cannot put the digest in the address by getting two string arguments the wrong way round.

type NewSigningCertificate

type NewSigningCertificate struct {
	PfxSealed           []byte
	PasswordSealed      []byte
	Subject, Issuer     string
	NotBefore, NotAfter time.Time
	Thumbprint          string
	Algorithm           string
	SelfSigned          bool
	CodeSigning         bool
	UploadedBy          string
}

NewSigningCertificate is a certificate to store. It is a separate type from the one that is read back so that a caller cannot accidentally set the fields this package fills in.

type Pairing

type Pairing struct {
	ID        int64
	UserCode  string
	Status    wire.PairStatus
	DriverID  *int64
	DeviceID  *int64
	CreatedAt time.Time
	ExpiresAt time.Time
}

Pairing is one device-code grant in progress.

func (Pairing) Expired

func (p Pairing) Expired(now time.Time) bool

Expired reports whether the code has run out, whatever the stored status says. The status is corrected lazily, by the poll that notices.

type PanelDevice

type PanelDevice struct {
	ID          int64
	DriverID    int64
	DriverName  string
	TokenPrefix string
	Label       string
	CreatedAt   time.Time
	LastUsedAt  *time.Time
	RevokedAt   *time.Time
}

PanelDevice is one paired machine with the driver it belongs to.

The token is not here and cannot be: only its digest was ever stored (D-2). TokenPrefix is the short clear-text head of it, which is enough for an operator to tell two of a driver's machines apart and useless to anyone else.

func (PanelDevice) Cursor

func (d PanelDevice) Cursor() DeviceCursor

Cursor is where a page resumes after this row.

func (PanelDevice) Revoked

func (d PanelDevice) Revoked() bool

Revoked reports whether this machine has been signed out.

type PanelLap

type PanelLap struct {
	ID        int64
	Number    int
	LapMs     int
	Kind      string
	StartedAt time.Time
	CreatedAt time.Time
	// TraceBytes is the size of the stored trace, and zero means there is
	// none — either the lap arrived without one or retention has cleared it.
	TraceBytes int
}

PanelLap is one lap as the stint page lists it. The trace itself is never read here: the page shows how big it is, and a page that fetched two hundred compressed blobs to print their sizes would be the one slow page in the panel.

type PanelStint

type PanelStint struct {
	StintRow
	// DriverID and DriverName are who drove it.
	DriverID   int64
	DriverName string
	// Sim and TrackID are the identifiers the client reported.
	Sim, TrackID string
}

PanelStint is one stint with the driver it belongs to, for the page that opens a stint and lists its laps.

type PluginDatabaseRow

type PluginDatabaseRow struct {
	PasswordSealed []byte
	Provisioned    bool
	Migration      string
}

PluginDatabaseRow is what the server remembers about a plugin's own database: the sealed password for its role, whether the role and schema exist, and the last migration file it applied.

type PluginRecord

type PluginRecord struct {
	// Name is the key everywhere: the directory, the settings, the metering.
	Name string
	// Version, Author and Description are what the manifest said, kept so the
	// panel can describe a plugin whose files have since gone.
	Version     string
	Author      string
	Description string
	// InterfaceVersion is the contract version the plugin was built against.
	InterfaceVersion int
	// Capabilities and DeclaredSettings are the JSON documents the manifest
	// and the plugin produced, stored whole.
	Capabilities     []byte
	DeclaredSettings []byte
	// Directory is where it was found, relative to the plugin directory.
	Directory string
	// Enabled is whether the operator wants it running. It is separate from
	// State because disabled is a choice and stopped is a consequence.
	Enabled bool
	// State is what it is doing, as internal/plugins spells it.
	State string
	// LastError is why it is not running and LastOutput the last of what it
	// printed. Both are scrubbed of credentials before they get here.
	LastError  string
	LastOutput string
	// Restarts is how many times it has been restarted since it last ran
	// cleanly, so an operator can see a plugin flapping rather than running.
	Restarts int
	// FirstSeenAt and UpdatedAt are when it appeared and when this row last
	// changed.
	FirstSeenAt time.Time
	UpdatedAt   time.Time
}

PluginRecord is an installed plugin as the database holds it.

type PluginSettingRow

type PluginSettingRow struct {
	Name   string
	Value  string
	Sealed []byte
}

PluginSettingRow is one configured value. Exactly one of Value and Sealed is set: a credential is sealed with the data key and a plain setting is not, and a value that is both would be a credential anyone with the database can read.

type PluginUsageWrite

type PluginUsageWrite struct {
	// Plugin is the plugin's name. It is not a foreign key: removing a plugin
	// must not erase what it already spent.
	Plugin string
	// Job is what the call was for, Model what it was spent on.
	Job   string
	Model string
	// DriverID is nil for work that was not on any one driver's behalf.
	DriverID *int64
	// Input and Output are tokens, counted the way the vendor counts them.
	Input, Output int64
	// Cached reports that the plugin answered from its own cache.
	Cached bool
}

PluginUsageWrite is what one plugin call cost.

type Reason

type Reason string

Reason says what went wrong with a database, in the terms the setup wizard has to explain it in. It exists so that the wizard can name the failure rather than showing the driver's error text, and so that a test can assert on the diagnosis rather than on a sentence.

const (
	// ReasonBadConnectionString means the string is not a connection string at
	// all — nothing was attempted.
	ReasonBadConnectionString Reason = "bad_connection_string"
	// ReasonUnreachable means nothing answered: wrong host, wrong port, a
	// firewall, or PostgreSQL not running.
	ReasonUnreachable Reason = "unreachable"
	// ReasonCredentials means PostgreSQL answered and refused the user name or
	// password.
	ReasonCredentials Reason = "credentials"
	// ReasonDatabaseMissing means the server is there and the credentials are
	// good, but the named database does not exist.
	ReasonDatabaseMissing Reason = "database_missing"
	// ReasonPermission means the user connected but may not do what the server
	// needs — usually create tables.
	ReasonPermission Reason = "permission"
	// ReasonVersionTooOld means the server is older than [MinServerVersion].
	ReasonVersionTooOld Reason = "version_too_old"
	// ReasonBusy means the server refused because it has no connection slots
	// left.
	ReasonBusy Reason = "busy"
	// ReasonUnknown is everything else. The message carries the database's own
	// words, because inventing a diagnosis would be worse.
	ReasonUnknown Reason = "unknown"
)

The ways connecting to a database fails.

type ReferenceQuery

type ReferenceQuery struct {
	DriverID   int64
	Sim        string
	TrackID    string
	Car        string
	CarClass   string
	Preference []wire.Scope
}

ReferenceQuery asks for the lap a driver's own is compared against.

Sim is required and is matched exactly. A lap is only ever compared against a lap from the same simulator: two simulators arrive at the same slug for the same circuit and the same name for the same car, and mean two different things by both, so serving one for the other would tell a driver they are seconds off a time nobody in their simulator has ever set.

Preference is the scopes to consider, best first. The caller builds it from the scope the client asked for and the ones narrower than it, so a server with no class data still answers with the driver's own best rather than nothing at all.

type ReferenceResult

type ReferenceResult struct {
	Scope      wire.Scope
	LapMs      int
	DriverID   int64
	DriverName string
	TraceCodec int
	Trace      []byte
}

ReferenceResult is the lap that was found, and the scope it actually came from, which may be narrower than the one asked for.

type Response

type Response struct {
	Status int
	Body   []byte
}

Response is what a write answered with: the status and the exact bytes. Both are stored, so a replay is byte-for-byte the first answer rather than a second answer that happens to look similar.

type RosterCursor

type RosterCursor struct {
	// ID is the identifier of the last row of the previous page.
	ID int64
	// Name, Seen and Laps are that row's key under each ordering.
	Name string
	Seen time.Time
	Laps int64
}

RosterCursor is where a page of the roster resumes: the sort key of the last row of the page before it, and that row's identifier to break a tie between two rows that share a key.

Only the field belonging to the current ordering is read. The others are carried because the cursor travels through a URL, where the ordering is a separate parameter and either can be edited.

type RosterEntry

type RosterEntry struct {
	// ID, Name, Slug and Class are the driver.
	ID    int64
	Name  string
	Slug  string
	Class string
	// CreatedAt is when the driver was first paired.
	CreatedAt time.Time
	// Laps and Stints are what they have recorded.
	Laps, Stints int64
	// TraceBytes is what their traces occupy, compressed.
	TraceBytes int64
	// Devices is how many of their machines are still paired, and
	// RevokedDevices how many have been signed out.
	Devices, RevokedDevices int64
	// LastUploadAt is when their newest lap arrived, or nil if they have never
	// uploaded one.
	LastUploadAt *time.Time
	// LastSeenAt is when any of their machines last made an authenticated
	// request, or nil if none ever has. It is the "is their client working"
	// answer, and it moves without a lap being uploaded.
	LastSeenAt *time.Time
}

RosterEntry is one driver as the roster page shows them: who they are, and every number the page prints beside them.

The numbers come from one grouped query over each source table rather than a lookup per driver, so a page of twenty-five drivers costs the same whether each of them has ten laps or ten thousand.

func (RosterEntry) Cursor

func (e RosterEntry) Cursor() RosterCursor

Cursor is where a page resumes after this row.

type RosterOrder

type RosterOrder string

RosterOrder is how the drivers page is sorted. The three orderings are separate queries rather than one query with a sort parameter, because the keyset predicate has to agree with the ORDER BY about direction and about the tie-break, and a mismatch there skips rows instead of failing.

const (
	// RosterByName is alphabetical, which is how an operator looks somebody up.
	RosterByName RosterOrder = "name"
	// RosterByLastSeen is the most recently connected machine first, which is
	// how an operator answers "is anybody's client broken".
	RosterByLastSeen RosterOrder = "seen"
	// RosterByLaps is the busiest driver first.
	RosterByLaps RosterOrder = "laps"
)

The orderings the roster offers.

func (RosterOrder) Valid

func (o RosterOrder) Valid() bool

Valid reports whether o is one of the three orderings.

type RosterQuery

type RosterQuery struct {
	// Order is the sort. An invalid one is [RosterByName].
	Order RosterOrder
	// After is the cursor, or nil for the first page.
	After *RosterCursor
	// Limit is how many rows to read. Zero is [PageSize].
	Limit int
}

RosterQuery asks for one page of the roster.

type SetupRequest

type SetupRequest struct {
	// AdminEmail is the administrator's address, as typed.
	AdminEmail string
	// AdminPasswordHash is the argon2id hash. The plain password never
	// reaches this package.
	AdminPasswordHash string
	// Settings are the organisation's own settings, including the sealed
	// settings the wizard collected. No vendor credential is among them: a
	// fresh installation stores none, and a plugin asks for its own.
	Settings config.Settings
	// ServerVersion is the build running the wizard, recorded for diagnostics.
	ServerVersion string
}

SetupRequest is everything the wizard collected, ready to be committed.

type SetupState

type SetupState struct {
	// Installed reports whether the schema has been created at all.
	Installed bool
	// Complete reports whether setup has finished. When it is true the wizard
	// must not run, whatever is or is not on disk.
	Complete bool
	// CompletedAt and CompletedBy are the moment it finished and the email
	// address of the administrator it created.
	CompletedAt time.Time
	CompletedBy string
	// ServerVersion is the build that ran the setup.
	ServerVersion string
}

SetupState is the answer to the only question that matters at startup: may the setup wizard run?

The rule is that the database decides. A data directory is a cache of a connection string and can be deleted or mounted on a volume that does not survive a restart; if its absence reopened the wizard, anyone who could delete it would be handed the administrator account of a database that already has one.

type SigningCertificate

type SigningCertificate struct {
	// PfxSealed is the PKCS#12 file the operator uploaded, sealed.
	PfxSealed []byte
	// PasswordSealed is the password that opens it, sealed. Empty is a file
	// with no password, which is unusual and allowed.
	PasswordSealed []byte
	// Subject and Issuer are who the certificate says signed, and who vouched.
	Subject, Issuer string
	// NotBefore and NotAfter are the window a signature is made inside.
	NotBefore, NotAfter time.Time
	// Thumbprint is the certificate's own SHA-256.
	Thumbprint string
	// Algorithm is the key in words.
	Algorithm string
	// SelfSigned and CodeSigning are what decide what a driver will see.
	SelfSigned, CodeSigning bool
	// UploadedAt and UploadedBy are when it arrived and which administrator
	// put it there.
	UploadedAt time.Time
	UploadedBy string
}

SigningCertificate is the stored certificate, as this package holds it. The sealed fields are ciphertext and stay that way here — opening them needs the data key, which this package never sees.

type Stats

type Stats struct {
	// DatabaseBytes is what PostgreSQL reports for the whole database,
	// including its indexes and its own overhead.
	DatabaseBytes int64
	// TraceBytes is how much of that is compressed lap traces. They are the
	// bulk of the data and an operator should be able to see that plainly.
	TraceBytes int64
	// Drivers, Devices, Stints and Laps are row counts. Devices counts only
	// the ones that have not been revoked.
	Drivers, Devices, Stints, Laps int64
}

Stats is what the admin panel's overview page shows about the data.

type Stint

type Stint struct {
	ID         UUID
	DriverID   int64
	Sim        string
	TrackID    string
	Car        string
	CarClass   string
	StartedAt  time.Time
	FinishedAt *time.Time
	// Setup is the car's setup as the client sent it, still encoded, or nil
	// when the client sent none. It is carried as bytes because nothing here
	// looks inside it: the API decodes it once, on the one path that turns it
	// into the facts a plugin is given.
	Setup []byte
}

Stint is one continuous capture, as the database holds it. The four denormalised columns are here because the laps written against this stint copy them down, which is what lets the reference lookup be one index entry.

Sim is one of them, and it is the one that keeps the others honest: two simulators agree about the slug of a circuit and the name of a car and agree about nothing underneath it, so a lap is only ever compared against a lap from the same simulator.

type StintCursor

type StintCursor struct {
	StartedAt time.Time
	ID        UUID
}

StintCursor is where a page of a driver's stints resumes.

type StintQuery

type StintQuery struct {
	// DriverID is whose stints to read.
	DriverID int64
	// After is the cursor, or nil for the first page.
	After *StintCursor
	// Limit is how many rows to read. Zero is [PageSize].
	Limit int
}

StintQuery asks for one page of a driver's stints, newest first.

type StintRow

type StintRow struct {
	// ID is the identifier the client generated.
	ID UUID
	// Track, Car, CarClass and SessionType are what was being driven.
	Track, Car, CarClass, SessionType string
	// StartedAt is when it began, and FinishedAt when it was closed, or nil
	// for one that never was.
	StartedAt  time.Time
	FinishedAt *time.Time
	// Laps is how many laps it holds and BestLapMs the quickest clean one, or
	// zero when it holds no clean lap.
	Laps      int64
	BestLapMs int
	// TraceBytes is what its traces occupy, compressed.
	TraceBytes int64
}

StintRow is one stint as the driver's page lists it.

func (StintRow) Cursor

func (r StintRow) Cursor() StintCursor

Cursor is where a page resumes after this row.

type StintWrite

type StintWrite struct {
	ID          UUID
	DriverID    int64
	Sim         string
	Track       string
	TrackID     string
	Car         string
	CarClass    string
	SessionType string
	Sectors     []float64
	StartedAt   time.Time
	// Setup is the encoded car setup, or nil. Nil clears any setup the stint
	// already had, because the client is the only thing that knows what the
	// car was set to and a client that has stopped seeing one is saying so.
	Setup []byte
}

StintWrite is the body of PUT /stints/{id}, ready to store.

type Store

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

Store is the server's database. It owns a pgx pool and exposes typed methods; the generated sqlc queries are reached through it and not directly, so that every call site goes through one place that knows about timeouts.

func Open

func Open(ctx context.Context, url string, log *slog.Logger) (*Store, error)

Open connects to the database at url and returns a ready Store. It does not run migrations: Store.Migrate does, and setup decides when.

The pool is sized at four connections per core, per D-4, with a floor of MinConns.

func (*Store) AdminByEmail

func (s *Store) AdminByEmail(ctx context.Context, email string) (Admin, error)

AdminByEmail finds an administrator by email address, case-insensitively.

func (*Store) AdminByID

func (s *Store) AdminByID(ctx context.Context, id int64) (Admin, error)

AdminByID finds an administrator by primary key.

func (*Store) AdminSessionByToken

func (s *Store) AdminSessionByToken(ctx context.Context, sum []byte) (AdminSession, error)

AdminSessionByToken finds an unexpired session by the digest of its cookie.

func (*Store) AuditFor

func (s *Store) AuditFor(ctx context.Context, action string, limit int) ([]AuditEntry, error)

AuditFor reads the newest entries for one action.

func (*Store) ClearPluginDatabase

func (s *Store) ClearPluginDatabase(ctx context.Context, name string) error

ClearPluginDatabase forgets a plugin's database. The caller has already dropped the role and the schema; this removes the password that reached them, which must not outlive them.

func (*Store) ClientBuildByReference

func (s *Store) ClientBuildByReference(ctx context.Context, reference string) (ClientBuild, error)

ClientBuildByReference reads one build by the reference in its download address. A reference nothing was built under is ErrNotFound.

func (*Store) ClientBuilds

func (s *Store) ClientBuilds(ctx context.Context, q ClientBuildQuery) ([]ClientBuild, error)

ClientBuilds reads one page of the build history, newest first.

func (*Store) Close

func (s *Store) Close()

Close returns every connection to the operating system. It blocks until the connections in use have been released.

func (*Store) CompleteSetup

func (s *Store) CompleteSetup(ctx context.Context, req SetupRequest) (time.Time, error)

CompleteSetup writes the completion marker, the first administrator and the organisation's settings in one transaction. Either all of it lands or none of it does, so a setup interrupted halfway leaves the server in setup mode with no administrator rather than half-configured.

The marker's primary key is a single fixed value, so two requests arriving together contend on it: one commits, and the other is ErrSetupAlreadyComplete with its administrator rolled back. That is the guarantee that exactly one account is ever created this way.

func (*Store) CountAdmins

func (s *Store) CountAdmins(ctx context.Context) (int64, error)

CountAdmins reports how many administrator accounts exist. It is a diagnostic, not a gate: whether setup may run is Store.SetupState's answer.

func (*Store) CountClientBuilds

func (s *Store) CountClientBuilds(ctx context.Context) (int64, error)

CountClientBuilds is how many clients have ever been built here.

func (*Store) CountLapsForStint

func (s *Store) CountLapsForStint(ctx context.Context, id UUID) (int64, error)

CountLapsForStint reports how many laps a stint holds. The admin panel and the tests both want it; nothing on the request path does.

func (*Store) CountLiveDevicesForDriver

func (s *Store) CountLiveDevicesForDriver(ctx context.Context, driverID int64) (int64, error)

CountLiveDevicesForDriver counts a driver's machines that have not been revoked. The confirmation page names this number before anything happens.

func (*Store) CreateAdminSession

func (s *Store) CreateAdminSession(ctx context.Context, adminID int64, sum []byte, expires time.Time, userAgent string) error

CreateAdminSession stores a session. sum is the SHA-256 of the cookie value; the cookie value itself is never stored, so a database dump cannot be used to sign in as anyone.

func (*Store) CreateDevice

func (s *Store) CreateDevice(ctx context.Context, driverID int64, sum []byte, prefix, label string) (Device, error)

CreateDevice stores a paired machine. sum and prefix come from auth.SplitDeviceToken; the token itself is returned to the client once by the pairing endpoint and never stored.

func (*Store) CreateDriver

func (s *Store) CreateDriver(ctx context.Context, externalID, name, slug, class, avatarURL string) (int64, error)

CreateDriver adds a driver. The roster page and the pairing flow both need it; neither is built yet, and the devices above are useless without it.

func (*Store) CreateDriverSession

func (s *Store) CreateDriverSession(ctx context.Context, driverID int64, sum []byte,
	expires time.Time, userAgent, by string,
) error

CreateDriverSession records a signed-in browser.

func (*Store) CreatePairing

func (s *Store) CreatePairing(ctx context.Context, sum []byte, userCode string, expires time.Time) (Pairing, error)

CreatePairing opens a device-code grant. sum is the SHA-256 of the device code; the code itself is returned to the client once and never stored.

func (*Store) DataTotals

func (s *Store) DataTotals(ctx context.Context) (DataStats, error)

DataTotals reads the data page's figures.

func (*Store) DecidePairing

func (s *Store) DecidePairing(ctx context.Context, id int64, status wire.PairStatus, driverID *int64, by string) (bool, error)

DecidePairing records an operator's approval or denial. It reports whether a pending, unexpired grant was actually changed, so approving one twice — two tabs, or a double click — is not an error and does not undo anything.

func (*Store) DeleteAdminSession

func (s *Store) DeleteAdminSession(ctx context.Context, sum []byte) error

DeleteAdminSession signs one browser out.

func (*Store) DeleteAdminSessionsForAdmin

func (s *Store) DeleteAdminSessionsForAdmin(ctx context.Context, adminID int64) (int64, error)

DeleteAdminSessionsForAdmin signs every browser of one administrator out, which is what a password change does.

func (*Store) DeleteDriverSession

func (s *Store) DeleteDriverSession(ctx context.Context, sum []byte) error

DeleteDriverSession ends one browser's session, which is a driver signing out.

func (*Store) DeleteDriverSessionByID

func (s *Store) DeleteDriverSessionByID(ctx context.Context, id int64) (bool, error)

DeleteDriverSessionByID ends one session the operator picked off a page. It reports whether there was one, so pressing the button twice is not an error the second time.

func (*Store) DeleteDriverSessionsForDriver

func (s *Store) DeleteDriverSessionsForDriver(ctx context.Context, driverID int64) (int64, error)

DeleteDriverSessionsForDriver signs one driver out of everything. It is the lost-laptop action, for the person rather than for the machine.

func (*Store) DeleteExpiredAdminSessions

func (s *Store) DeleteExpiredAdminSessions(ctx context.Context) (int64, error)

DeleteExpiredAdminSessions removes sessions that have run out. Expiry is enforced by the lookup, so this is housekeeping rather than security.

func (*Store) DeleteExpiredDriverSessions

func (s *Store) DeleteExpiredDriverSessions(ctx context.Context) (int64, error)

DeleteExpiredDriverSessions clears out what has run out. Nothing depends on it having run — the lookup refuses an expired session either way — so this is housekeeping and not a check.

func (*Store) DeleteExpiredIdempotencyKeys

func (s *Store) DeleteExpiredIdempotencyKeys(ctx context.Context, before time.Time) (int64, error)

DeleteExpiredIdempotencyKeys removes replay entries that have run out. D-6 gives them twenty-four hours; this is what enforces it.

func (*Store) DeleteFinishedPairings

func (s *Store) DeleteFinishedPairings(ctx context.Context, before time.Time) (int64, error)

DeleteFinishedPairings removes grants whose codes ran out before the given instant, whatever they were decided. It is housekeeping: expiry is enforced by the lookup, so nothing depends on this having run.

func (*Store) DeletePlugin

func (s *Store) DeletePlugin(ctx context.Context, name string) error

DeletePlugin forgets a plugin, its settings and the credentials they hold. What it spent stays, because the money was the organisation's.

func (*Store) DeletePluginSetting

func (s *Store) DeletePluginSetting(ctx context.Context, plugin, name string) error

DeletePluginSetting removes one value.

func (*Store) DeleteSigningCertificate

func (s *Store) DeleteSigningCertificate(ctx context.Context) error

DeleteSigningCertificate removes it. Removing one that is not there is not an error: the operator asked for a server with no certificate and that is what they have.

func (*Store) DeviceByToken

func (s *Store) DeviceByToken(ctx context.Context, prefix string, sum []byte) (Device, error)

DeviceByToken finds the live device a presented token belongs to.

The lookup is by the clear-text prefix, which is indexed, and the decision is made by comparing digests in constant time. That is the whole reason the prefix column exists: without it the query would be a scan, and with the prefix as the decision it would be a 48-bit credential.

func (*Store) DeviceCounts

func (s *Store) DeviceCounts(ctx context.Context) (total, live int64, err error)

DeviceCounts reports how many machines have ever been paired and how many are still able to upload.

func (*Store) DriverByID

func (s *Store) DriverByID(ctx context.Context, id int64) (Driver, error)

DriverByID reads one driver.

func (*Store) DriverBySlug

func (s *Store) DriverBySlug(ctx context.Context, slug string) (Driver, error)

DriverBySlug finds a driver by the slug a plugin names when it signs one in.

func (*Store) DriverSessionByToken

func (s *Store) DriverSessionByToken(ctx context.Context, sum []byte) (DriverSession, error)

DriverSessionByToken finds a live session by its token's digest, with the driver it belongs to. An expired one is ErrNotFound: the query enforces that, so nothing downstream has to remember to check.

func (*Store) DriverSessionsForDriver

func (s *Store) DriverSessionsForDriver(ctx context.Context, driverID int64) ([]DriverSession, error)

DriverSessionsForDriver is every browser this driver is signed in to, newest use first.

func (*Store) DropPluginDatabase

func (s *Store) DropPluginDatabase(ctx context.Context, name string) error

DropPluginDatabase removes a plugin's schema, its tables, every grant it holds and the role itself.

It is two statements and not one, and the order matters. DROP SCHEMA CASCADE alone leaves the role holding USAGE on core_read and SELECT on its views, and a role that still holds a grant cannot be dropped — PostgreSQL refuses with "cannot be dropped because some objects depend on it". DROP OWNED BY revokes what it was granted and drops what it owns in this database, which is the schema and everything in it.

It tolerates a role that is not there, because uninstalling a plugin that was never provisioned must not be an error.

func (*Store) EnsureDriver

func (s *Store) EnsureDriver(ctx context.Context, name, class string) (Driver, error)

EnsureDriver finds the driver with this name's slug, or creates one. It is what approving a pairing calls: an operator types a name, and typing the same name twice attaches the second machine to the same person rather than making a second person with the same name.

func (*Store) ExpirePairing

func (s *Store) ExpirePairing(ctx context.Context, id int64) error

ExpirePairing marks a grant expired, which the poll that noticed does so that the admin panel stops offering a decision on a code nobody can use.

func (*Store) Idempotent

func (s *Store) Idempotent(ctx context.Context, w Write, fn func(context.Context, *Tx) (Response, error)) (Response, bool, error)

Idempotent runs fn exactly once for this device and key and returns what it answered, replaying the stored answer on every repeat.

The claim and the work are one transaction, which is what makes a concurrent double submit correct rather than merely unlikely: the second request's insert blocks on the primary key until the first commits, and then reads the committed response and replays it. Neither request ever sees a half-written state, and the work happens once.

fn returns a Response for anything the client should be told, including a refusal — a 409 for a lap that disagrees with one already stored is an answer and is itself replayed. It returns an error only for a fault, which rolls the whole transaction back, claim included, so the client's retry can succeed.

The second result reports whether the answer was replayed rather than produced.

func (*Store) IssueDeviceForPairing

func (s *Store) IssueDeviceForPairing(ctx context.Context, pairingID int64, sum []byte, prefix, label string) (Device, error)

IssueDeviceForPairing mints the device a successful poll returns, once.

The row is locked for the length of the transaction, so two polls arriving together cannot both mint one: the second waits, sees the device already attached, and is answered ErrPairingSpent. That is the whole reason this is a transaction and not three statements.

func (*Store) LapsForStint

func (s *Store) LapsForStint(ctx context.Context, stint UUID, afterNumber, limit int) ([]PanelLap, error)

LapsForStint reads one page of a stint's laps, in lap order, resuming after the lap number given. FirstLapCursor starts at the beginning.

func (*Store) ListDevicesForDriver

func (s *Store) ListDevicesForDriver(ctx context.Context, driverID int64) ([]Device, error)

ListDevicesForDriver lists a driver's machines, newest first.

func (*Store) ListDrivers

func (s *Store) ListDrivers(ctx context.Context) ([]Driver, error)

ListDrivers lists every driver, by name. The admin panel offers them when an operator approves a pairing.

func (*Store) ListPendingPairings

func (s *Store) ListPendingPairings(ctx context.Context) ([]Pairing, error)

ListPendingPairings is what the admin panel shows: the grants waiting for a decision, oldest first, with the user code an operator matches against what the driver read out.

func (*Store) Migrate

func (s *Store) Migrate(ctx context.Context) error

Migrate applies every migration the binary carries that the database has not already run. It is safe to call on every startup and safe to call from two instances at once: a PostgreSQL advisory lock means the second waits rather than racing.

func (*Store) MigrationState

func (s *Store) MigrationState(ctx context.Context) (MigrationState, error)

MigrationState reports where the database's schema sits relative to this binary's.

func (*Store) PairingByDeviceCode

func (s *Store) PairingByDeviceCode(ctx context.Context, sum []byte) (Pairing, error)

PairingByDeviceCode finds the grant a polling client is asking about.

func (*Store) PanelDeviceByID

func (s *Store) PanelDeviceByID(ctx context.Context, id int64) (PanelDevice, error)

PanelDeviceByID reads one machine, so that the page naming what a revoke will sign out is naming the machine it is about to sign out.

func (*Store) PanelDevices

func (s *Store) PanelDevices(ctx context.Context, q DeviceQuery) ([]PanelDevice, error)

PanelDevices reads one page of every paired machine, newest first.

func (*Store) PanelDevicesForDriver

func (s *Store) PanelDevicesForDriver(ctx context.Context, driverID int64, limit int) ([]PanelDevice, error)

PanelDevicesForDriver reads a driver's machines, newest first, for the driver's own page.

func (*Store) PanelStintByID

func (s *Store) PanelStintByID(ctx context.Context, id UUID) (PanelStint, error)

PanelStintByID reads one stint for the panel. A stint that is not there is ErrNotFound.

func (*Store) Ping

func (s *Store) Ping(ctx context.Context) error

Ping reports whether the database is answering.

func (*Store) Plugin

func (s *Store) Plugin(ctx context.Context, name string) (PluginRecord, error)

Plugin is one of them. A plugin that was never installed is ErrNotFound.

func (*Store) PluginDSN

func (s *Store) PluginDSN(name, password string) (string, error)

PluginDSN is the connection string a plugin is given.

It is built from this server's own connection rather than by rewriting a string, so that it is right whichever spelling the operator configured — a URL or the keyword/value form — and so that a change to how the server connects, to TLS in particular, reaches plugins without anybody remembering to make it.

Only the credentials differ. Same host, same port, same database: a plugin joins its tables to the core_read views in one query, which is only possible because it is the same database.

func (*Store) PluginDatabase

func (s *Store) PluginDatabase(ctx context.Context, name string) (PluginDatabaseRow, error)

PluginDatabase reads what is known about a plugin's database. A plugin that has none reads back zero, which is not an error: most plugins have none.

func (*Store) PluginRoleName

func (s *Store) PluginRoleName(name string) (string, error)

PluginRoleName is the role and schema this store gives a plugin. It is exported because a caller that wants to look at a plugin's schema — a support query, a test — cannot work it out without knowing which database it is for.

func (*Store) PluginSettings

func (s *Store) PluginSettings(ctx context.Context, name string) ([]PluginSettingRow, error)

PluginSettings is what the operator configured for one plugin.

func (*Store) PluginTokensSince

func (s *Store) PluginTokensSince(ctx context.Context, since time.Time) (TokenUse, error)

PluginTokensSince is what every plugin has spent since an instant. It is what the daily cap is measured against.

func (*Store) PluginTokensSinceFor

func (s *Store) PluginTokensSinceFor(ctx context.Context, name string, since time.Time) (TokenUse, error)

PluginTokensSinceFor is the same for one plugin, which is what the panel shows per plugin.

func (*Store) Plugins

func (s *Store) Plugins(ctx context.Context) ([]PluginRecord, error)

Plugins is every plugin this server has seen, by name.

func (*Store) PoolStats

func (s *Store) PoolStats() (acquired, idle, total int32)

PoolStats is what the pool is doing right now, for the metrics port.

func (*Store) ProvisionPluginDatabase

func (s *Store) ProvisionPluginDatabase(ctx context.Context, name, password string) error

ProvisionPluginDatabase creates the role and schema a plugin owns, and grants it read access to the core_read views and nothing else.

It is idempotent, and has to be: a server that crashed between creating the role and recording that it had must be able to run this again and arrive somewhere correct. Every statement either tolerates the object already existing or is safe to repeat, and the password is set on every call so that a role left over from a half-finished install is reachable again rather than stranded.

The caller holds the password and is responsible for sealing it. This function never logs it, and the statement that carries it is the only place it appears.

func (*Store) PruneTracesOlderThan

func (s *Store) PruneTracesOlderThan(ctx context.Context, before time.Time, batch int) (int64, error)

PruneTracesOlderThan clears one batch of traces older than the cutoff and reports how many laps it cleared. Zero means there is nothing left to do.

The lap row and its time survive: only the blob is emptied, so a driver's record of having driven that lap in that time is untouched and only the shape of the lap is gone. It is one batch rather than the whole set on purpose — the caller loops, so a prune of a hundred thousand laps is a few hundred short statements rather than one that holds its locks for minutes.

func (*Store) Queries

func (s *Store) Queries() *gen.Queries

Queries is the generated query set, bound to this pool. It exists for the packages inside internal/db that need it; everything outside calls the typed methods on Store.

func (*Store) RecentAudit

func (s *Store) RecentAudit(ctx context.Context, limit int) ([]AuditEntry, error)

RecentAudit reads the newest entries, for the panel and for the tests that assert a change was recorded.

func (*Store) RecordClientBuild

func (s *Store) RecordClientBuild(ctx context.Context, b NewClientBuild) (ClientBuild, error)

RecordClientBuild writes one row of the build history and returns it.

func (*Store) RecordPluginUsage

func (s *Store) RecordPluginUsage(ctx context.Context, u PluginUsageWrite) error

RecordPluginUsage stores what one plugin call cost. A call that is not recorded is a call the operator cannot see and the cap cannot count.

func (*Store) RecordTokenUsage

func (s *Store) RecordTokenUsage(ctx context.Context, job, model string, driverID *int64, input, output int64) error

RecordTokenUsage stores what one language-model call cost. It is what makes the operator's daily cap enforceable and the figures on the settings page true; a call that is not recorded is a call the operator cannot see.

driverID is nil for work that was not on any one driver's behalf.

func (*Store) ReferenceLap

func (s *Store) ReferenceLap(ctx context.Context, q ReferenceQuery) (ReferenceResult, error)

ReferenceLap finds the best lap in the narrowest scope the server has, within the caller's preference. It is ErrNotFound when there is none, which is the contract's 404 and an ordinary answer for a track nobody has driven yet.

It is one statement: three lookups against the two partial unique indexes on reference_laps, then two primary-key joins to fetch the winner's trace and the name to show beside it. Nothing here scans laps.

func (*Store) RevokeAllDevices

func (s *Store) RevokeAllDevices(ctx context.Context) (int64, error)

RevokeAllDevices revokes every live device token and reports how many it revoked. It is the danger-zone action an operator reaches for when a token has leaked: every paired machine stops being able to upload, and every driver pairs again.

Nothing is deleted. The devices stay, with the moment they were revoked on them, because "when did we cut everyone off" is a question somebody asks afterwards.

func (*Store) RevokeDevice

func (s *Store) RevokeDevice(ctx context.Context, id int64) (bool, error)

RevokeDevice stops a device from uploading. It reports whether a live device was revoked, so revoking one twice is not an error.

func (*Store) RevokeDevicesForDriver

func (s *Store) RevokeDevicesForDriver(ctx context.Context, driverID int64) (int64, error)

RevokeDevicesForDriver signs out every one of one driver's machines and reports how many it signed out. No other driver is touched, which is what separates it from the danger zone's Store.RevokeAllDevices.

func (*Store) Roster

func (s *Store) Roster(ctx context.Context, q RosterQuery) ([]RosterEntry, error)

Roster reads one page of the drivers page's list.

func (*Store) RosterEntryByID

func (s *Store) RosterEntryByID(ctx context.Context, id int64) (RosterEntry, error)

RosterEntryByID reads one driver with the same numbers the list shows, for the detail page.

func (*Store) SavePlugin

func (s *Store) SavePlugin(ctx context.Context, p PluginRecord) error

SavePlugin records a plugin that was found on disk, or updates what is known about one that was already there. It does not touch the settings declaration, which arrives later — a plugin has to start before it can say what it needs.

func (*Store) SavePluginDeclaredSettings

func (s *Store) SavePluginDeclaredSettings(ctx context.Context, name string, declared []byte) error

SavePluginDeclaredSettings stores what a plugin said it needs configured, so that the panel can render the form whether or not the plugin is running. That matters most for a plugin that will not start until a credential is filled in: without this the operator would have no way to fill it in.

func (*Store) SavePluginEnabled

func (s *Store) SavePluginEnabled(ctx context.Context, name string, enabled bool) error

SavePluginEnabled records whether the operator wants it running.

func (*Store) SavePluginSecret

func (s *Store) SavePluginSecret(ctx context.Context, plugin, name string, sealed []byte) error

SavePluginSecret stores one sealed credential. The caller seals it: this package never sees a plaintext key and never holds the data key.

func (*Store) SavePluginSetting

func (s *Store) SavePluginSetting(ctx context.Context, plugin, name, value string) error

SavePluginSetting stores one plain value. It clears any sealed value under the same name, because a setting is one thing or the other.

func (*Store) SavePluginState

func (s *Store) SavePluginState(ctx context.Context, name, state, lastError, lastOutput string, restarts int) error

SavePluginState records what a plugin is doing and why.

func (*Store) SaveSettings

func (s *Store) SaveSettings(ctx context.Context, set config.Settings) error

SaveSettings replaces the organisation's settings.

func (*Store) SaveSigningCertificate

func (s *Store) SaveSigningCertificate(ctx context.Context, c NewSigningCertificate) error

SaveSigningCertificate stores the operator's certificate, replacing whatever was there. A server signs with one certificate, so uploading is replacing.

func (*Store) SchemaInstalled

func (s *Store) SchemaInstalled(ctx context.Context) (bool, error)

SchemaInstalled reports whether the schema has been created at all. It is the question that has to be answerable before setup_state can be read, so it is asked with to_regclass rather than by selecting from a table that may not exist — a failed SELECT would abort the surrounding transaction.

func (*Store) SetAdminPasswordHash

func (s *Store) SetAdminPasswordHash(ctx context.Context, id int64, hash string) error

SetAdminPasswordHash replaces an administrator's password hash. It is called after a password change, and after a successful sign-in against a hash made with weaker parameters than the current ones.

func (*Store) SetPluginProvisioned

func (s *Store) SetPluginProvisioned(ctx context.Context, name string, sealed []byte, migration string) error

SetPluginProvisioned records that a plugin's role and schema exist and stores the sealed password that reaches them. The caller has already created both: this is the bookkeeping, and it is written last so that a crash between the two leaves a server that will provision again rather than one that believes in a role it never made.

func (*Store) SettingString

func (s *Store) SettingString(ctx context.Context, key string) (string, error)

SettingString reads one string setting, for the places that want a single value without the whole document.

func (*Store) Settings

func (s *Store) Settings(ctx context.Context) (config.Settings, error)

Settings reads the organisation's own settings. They live in the database rather than beside the binary because they are what makes this deployment itself: an operator who loses the data directory keeps their organisation name, their public address and how they are reached.

func (*Store) SetupState

func (s *Store) SetupState(ctx context.Context) (SetupState, error)

SetupState reads the completion marker. A database with no schema is Installed false and Complete false, which is a first run; a database with a schema but no marker is a setup that was interrupted, which is also a first run because the transaction that would have created an administrator did not commit.

func (*Store) SigningCertificate

func (s *Store) SigningCertificate(ctx context.Context) (SigningCertificate, error)

SigningCertificate is the operator's certificate, or ErrNotFound when this server has not been given one. That is the ordinary state of a fresh installation rather than a failure, and every caller treats it as such.

func (*Store) Stats

func (s *Store) Stats(ctx context.Context) (Stats, error)

Stats reads the overview counters. Each is a single statement and the page is loaded by someone waiting for it, so they run in order rather than fanning out across the pool.

func (*Store) Stint

func (s *Store) Stint(ctx context.Context, id UUID, driverID int64) (Stint, error)

Stint reads one of a driver's stints outside a transaction, for the reads that check ownership without writing anything.

func (*Store) StintsForDriver

func (s *Store) StintsForDriver(ctx context.Context, q StintQuery) ([]StintRow, error)

StintsForDriver reads one page of a driver's stints, newest first.

func (*Store) StorageByDriver

func (s *Store) StorageByDriver(ctx context.Context, limit int) ([]DriverStorage, error)

StorageByDriver lists what each driver's traces occupy, largest first.

func (*Store) TokensSince

func (s *Store) TokensSince(ctx context.Context, since time.Time) (TokenUse, error)

TokensSince sums the usage from an instant until now.

func (*Store) TouchAdminLogin

func (s *Store) TouchAdminLogin(ctx context.Context, id int64) error

TouchAdminLogin records a successful sign-in.

func (*Store) TouchAdminSession

func (s *Store) TouchAdminSession(ctx context.Context, sum []byte, expires time.Time) error

TouchAdminSession extends a session that is in use.

func (*Store) TouchDevice

func (s *Store) TouchDevice(ctx context.Context, id int64) error

TouchDevice records that a device was used, which the driver sees as "last seen" against the machine in their list.

func (*Store) TouchDriverSession

func (s *Store) TouchDriverSession(ctx context.Context, sum []byte, expires time.Time) error

TouchDriverSession extends a session that is being used.

func (*Store) TracesOlderThan

func (s *Store) TracesOlderThan(ctx context.Context, before time.Time) (TraceSet, error)

TracesOlderThan describes the traces a prune with this cutoff would clear. It reads exactly the set Store.PruneTracesOlderThan writes, so the number the page promises is the number the job does.

func (*Store) WriteAudit

func (s *Store) WriteAudit(ctx context.Context, actor, action, subject string, changed []string) error

WriteAudit records one change. The caller passes the names of what changed, never the values.

type SummaryWrite

type SummaryWrite struct {
	StintID        UUID
	Laps           int
	Incidents      int
	BestLapMs      int
	AvgLapMs       int
	ConsistencyPct int
	TopSpeedKmh    int
	Conditions     []byte
	CarState       []byte
	BestTrace      []byte
	BestTraceCodec int
	FinishedAt     *time.Time
}

SummaryWrite is the body of PUT /stints/{id}/summary, ready to store. Conditions and CarState are already-encoded JSON documents, and BestTrace is already compressed by the protocol codec.

type TokenUse

type TokenUse struct {
	// Input and Output are tokens, counted the way the model counts them.
	Input, Output int64
	// Calls is how many requests those tokens were spread over.
	Calls int64
}

TokenUse is what the language-model features spent over one window.

func (TokenUse) Total

func (u TokenUse) Total() int64

Total is what the daily cap is measured against: every token the operator paid for, in or out.

type TraceSet

type TraceSet struct {
	// Laps is how many laps would lose their trace, and TraceBytes how much
	// would be freed.
	Laps, TraceBytes int64
	// OldestAt and NewestAt bound them, or the zero time when the set is
	// empty.
	OldestAt, NewestAt time.Time
}

TraceSet is a set of laps that still hold a trace, as the retention preview describes it before anything is deleted.

func (TraceSet) Empty

func (t TraceSet) Empty() bool

Empty reports whether there is nothing in the set.

type Tx

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

Tx is one database transaction, handed to the callback of Store.Idempotent. It carries the writes an API request performs and nothing else: pgx stays inside this package, so a handler cannot open a transaction of its own, cannot leave one open, and cannot run a statement this package has not reviewed.

func (*Tx) AppendLaps

func (t *Tx) AppendLaps(ctx context.Context, w LapWrite) (LapResult, error)

AppendLaps stores a batch of laps and maintains the reference table.

Laps are append-only and idempotent on (stint_id, number): a repeated number with identical content is accepted and stored once, and with different content the whole batch is refused and the numbers that disagree are named. Refusing the whole batch rather than the offending lap is deliberate — a client whose lap 7 disagrees with the server's has a bug, and half-applying the rest of its batch would hide it.

The insert is one COPY. Everything else in the path is a single statement against an index, so the cost of a forty-lap batch is four round trips and the bytes of the traces, which is what the fifty milliseconds this path is allowed are spent on.

Two batches that carry the same lap number and arrive at once are serialised by the unique index: one commits and the other fails and is rolled back whole, so the client's retry sees the committed state and is answered normally.

func (*Tx) ReplaceSummary

func (t *Tx) ReplaceSummary(ctx context.Context, w SummaryWrite) error

ReplaceSummary writes the whole summary document, replacing whatever was there. A present FinishedAt also closes the stint, which is what makes the last summary of a session the one that ends it.

func (*Tx) Stint

func (t *Tx) Stint(ctx context.Context, id UUID, driverID int64) (Stint, error)

Stint reads one of this driver's stints. It is the ownership check every write against a stint makes before it touches anything.

func (*Tx) UpsertStint

func (t *Tx) UpsertStint(ctx context.Context, w StintWrite) error

UpsertStint creates the stint or updates its mutable fields, and reports nothing but success: the identifier came from the client and is already known to it.

A stint identifier that already belongs to another driver is ErrNotFound. Two clients cannot collide on a UUIDv7 by accident, so this is the answer to somebody trying one, and "no such stint" is the right thing to tell them rather than "that one is not yours".

type UUID

type UUID [UUIDLen]byte

UUID is the identifier a client generates for a stint. API v1 asks for a UUIDv7, which sorts by creation time; this package carries any syntactically valid UUID, because refusing a v4 would break a client for no benefit the database can name.

func ParseUUID

func ParseUUID(s string) (UUID, error)

ParseUUID reads the canonical 8-4-4-4-12 spelling, in either case. Braces, a "urn:uuid:" prefix and the unhyphenated form are all refused: one spelling on the wire means one spelling in a log line and in the admin panel.

func (UUID) String

func (u UUID) String() string

String renders the canonical lower-case spelling.

func (UUID) Version

func (u UUID) Version() int

Version is the UUID's version nibble: 7 for the time-ordered identifiers API v1 asks clients to generate, 4 for a random one.

type Write

type Write struct {
	DeviceID int64
	Key      string
	Hash     []byte
	Now      time.Time
}

Write is one idempotent request, identified by the device that sent it and the key it carried, and fingerprinted by Hash.

Hash covers the method, the path and the body. Two requests with the same key and the same hash are the same request; the same key with a different hash is ErrIdempotencyConflict.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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