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
- Variables
- func Describe(url string) (string, error)
- func MigrationSources() ([]string, error)
- func Probe(ctx context.Context, url string) error
- func ReasonOf(err error) string
- func Slug(name string) string
- type Admin
- type AdminSession
- type AuditEntry
- type ClientBuild
- type ClientBuildCursor
- type ClientBuildQuery
- type ConnectError
- type DataStats
- type Device
- type DeviceCursor
- type DeviceQuery
- type Driver
- type DriverSession
- type DriverStorage
- type LapResult
- type LapRow
- type LapWrite
- type MigrationState
- type NewClientBuild
- type NewSigningCertificate
- type Pairing
- type PanelDevice
- type PanelLap
- type PanelStint
- type PluginDatabaseRow
- type PluginRecord
- type PluginSettingRow
- type PluginUsageWrite
- type Reason
- type ReferenceQuery
- type ReferenceResult
- type Response
- type RosterCursor
- type RosterEntry
- type RosterOrder
- type RosterQuery
- type SetupRequest
- type SetupState
- type SigningCertificate
- type Stats
- type Stint
- type StintCursor
- type StintQuery
- type StintRow
- type StintWrite
- type Store
- func (s *Store) AdminByEmail(ctx context.Context, email string) (Admin, error)
- func (s *Store) AdminByID(ctx context.Context, id int64) (Admin, error)
- func (s *Store) AdminSessionByToken(ctx context.Context, sum []byte) (AdminSession, error)
- func (s *Store) AuditFor(ctx context.Context, action string, limit int) ([]AuditEntry, error)
- func (s *Store) ClearPluginDatabase(ctx context.Context, name string) error
- func (s *Store) ClientBuildByReference(ctx context.Context, reference string) (ClientBuild, error)
- func (s *Store) ClientBuilds(ctx context.Context, q ClientBuildQuery) ([]ClientBuild, error)
- func (s *Store) Close()
- func (s *Store) CompleteSetup(ctx context.Context, req SetupRequest) (time.Time, error)
- func (s *Store) CountAdmins(ctx context.Context) (int64, error)
- func (s *Store) CountClientBuilds(ctx context.Context) (int64, error)
- func (s *Store) CountLapsForStint(ctx context.Context, id UUID) (int64, error)
- func (s *Store) CountLiveDevicesForDriver(ctx context.Context, driverID int64) (int64, error)
- func (s *Store) CreateAdminSession(ctx context.Context, adminID int64, sum []byte, expires time.Time, ...) error
- func (s *Store) CreateDevice(ctx context.Context, driverID int64, sum []byte, prefix, label string) (Device, error)
- func (s *Store) CreateDriver(ctx context.Context, externalID, name, slug, class, avatarURL string) (int64, error)
- func (s *Store) CreateDriverSession(ctx context.Context, driverID int64, sum []byte, expires time.Time, ...) error
- func (s *Store) CreatePairing(ctx context.Context, sum []byte, userCode string, expires time.Time) (Pairing, error)
- func (s *Store) DataTotals(ctx context.Context) (DataStats, error)
- func (s *Store) DecidePairing(ctx context.Context, id int64, status wire.PairStatus, driverID *int64, ...) (bool, error)
- func (s *Store) DeleteAdminSession(ctx context.Context, sum []byte) error
- func (s *Store) DeleteAdminSessionsForAdmin(ctx context.Context, adminID int64) (int64, error)
- func (s *Store) DeleteDriverSession(ctx context.Context, sum []byte) error
- func (s *Store) DeleteDriverSessionByID(ctx context.Context, id int64) (bool, error)
- func (s *Store) DeleteDriverSessionsForDriver(ctx context.Context, driverID int64) (int64, error)
- func (s *Store) DeleteExpiredAdminSessions(ctx context.Context) (int64, error)
- func (s *Store) DeleteExpiredDriverSessions(ctx context.Context) (int64, error)
- func (s *Store) DeleteExpiredIdempotencyKeys(ctx context.Context, before time.Time) (int64, error)
- func (s *Store) DeleteFinishedPairings(ctx context.Context, before time.Time) (int64, error)
- func (s *Store) DeletePlugin(ctx context.Context, name string) error
- func (s *Store) DeletePluginSetting(ctx context.Context, plugin, name string) error
- func (s *Store) DeleteSigningCertificate(ctx context.Context) error
- func (s *Store) DeviceByToken(ctx context.Context, prefix string, sum []byte) (Device, error)
- func (s *Store) DeviceCounts(ctx context.Context) (total, live int64, err error)
- func (s *Store) DriverByID(ctx context.Context, id int64) (Driver, error)
- func (s *Store) DriverBySlug(ctx context.Context, slug string) (Driver, error)
- func (s *Store) DriverSessionByToken(ctx context.Context, sum []byte) (DriverSession, error)
- func (s *Store) DriverSessionsForDriver(ctx context.Context, driverID int64) ([]DriverSession, error)
- func (s *Store) DropPluginDatabase(ctx context.Context, name string) error
- func (s *Store) EnsureDriver(ctx context.Context, name, class string) (Driver, error)
- func (s *Store) ExpirePairing(ctx context.Context, id int64) error
- func (s *Store) Idempotent(ctx context.Context, w Write, fn func(context.Context, *Tx) (Response, error)) (Response, bool, error)
- func (s *Store) IssueDeviceForPairing(ctx context.Context, pairingID int64, sum []byte, prefix, label string) (Device, error)
- func (s *Store) LapsForStint(ctx context.Context, stint UUID, afterNumber, limit int) ([]PanelLap, error)
- func (s *Store) ListDevicesForDriver(ctx context.Context, driverID int64) ([]Device, error)
- func (s *Store) ListDrivers(ctx context.Context) ([]Driver, error)
- func (s *Store) ListPendingPairings(ctx context.Context) ([]Pairing, error)
- func (s *Store) Migrate(ctx context.Context) error
- func (s *Store) MigrationState(ctx context.Context) (MigrationState, error)
- func (s *Store) PairingByDeviceCode(ctx context.Context, sum []byte) (Pairing, error)
- func (s *Store) PanelDeviceByID(ctx context.Context, id int64) (PanelDevice, error)
- func (s *Store) PanelDevices(ctx context.Context, q DeviceQuery) ([]PanelDevice, error)
- func (s *Store) PanelDevicesForDriver(ctx context.Context, driverID int64, limit int) ([]PanelDevice, error)
- func (s *Store) PanelStintByID(ctx context.Context, id UUID) (PanelStint, error)
- func (s *Store) Ping(ctx context.Context) error
- func (s *Store) Plugin(ctx context.Context, name string) (PluginRecord, error)
- func (s *Store) PluginDSN(name, password string) (string, error)
- func (s *Store) PluginDatabase(ctx context.Context, name string) (PluginDatabaseRow, error)
- func (s *Store) PluginRoleName(name string) (string, error)
- func (s *Store) PluginSettings(ctx context.Context, name string) ([]PluginSettingRow, error)
- func (s *Store) PluginTokensSince(ctx context.Context, since time.Time) (TokenUse, error)
- func (s *Store) PluginTokensSinceFor(ctx context.Context, name string, since time.Time) (TokenUse, error)
- func (s *Store) Plugins(ctx context.Context) ([]PluginRecord, error)
- func (s *Store) PoolStats() (acquired, idle, total int32)
- func (s *Store) ProvisionPluginDatabase(ctx context.Context, name, password string) error
- func (s *Store) PruneTracesOlderThan(ctx context.Context, before time.Time, batch int) (int64, error)
- func (s *Store) Queries() *gen.Queries
- func (s *Store) RecentAudit(ctx context.Context, limit int) ([]AuditEntry, error)
- func (s *Store) RecordClientBuild(ctx context.Context, b NewClientBuild) (ClientBuild, error)
- func (s *Store) RecordPluginUsage(ctx context.Context, u PluginUsageWrite) error
- func (s *Store) RecordTokenUsage(ctx context.Context, job, model string, driverID *int64, input, output int64) error
- func (s *Store) ReferenceLap(ctx context.Context, q ReferenceQuery) (ReferenceResult, error)
- func (s *Store) RevokeAllDevices(ctx context.Context) (int64, error)
- func (s *Store) RevokeDevice(ctx context.Context, id int64) (bool, error)
- func (s *Store) RevokeDevicesForDriver(ctx context.Context, driverID int64) (int64, error)
- func (s *Store) Roster(ctx context.Context, q RosterQuery) ([]RosterEntry, error)
- func (s *Store) RosterEntryByID(ctx context.Context, id int64) (RosterEntry, error)
- func (s *Store) SavePlugin(ctx context.Context, p PluginRecord) error
- func (s *Store) SavePluginDeclaredSettings(ctx context.Context, name string, declared []byte) error
- func (s *Store) SavePluginEnabled(ctx context.Context, name string, enabled bool) error
- func (s *Store) SavePluginSecret(ctx context.Context, plugin, name string, sealed []byte) error
- func (s *Store) SavePluginSetting(ctx context.Context, plugin, name, value string) error
- func (s *Store) SavePluginState(ctx context.Context, name, state, lastError, lastOutput string, restarts int) error
- func (s *Store) SaveSettings(ctx context.Context, set config.Settings) error
- func (s *Store) SaveSigningCertificate(ctx context.Context, c NewSigningCertificate) error
- func (s *Store) SchemaInstalled(ctx context.Context) (bool, error)
- func (s *Store) SetAdminPasswordHash(ctx context.Context, id int64, hash string) error
- func (s *Store) SetPluginProvisioned(ctx context.Context, name string, sealed []byte, migration string) error
- func (s *Store) SettingString(ctx context.Context, key string) (string, error)
- func (s *Store) Settings(ctx context.Context) (config.Settings, error)
- func (s *Store) SetupState(ctx context.Context) (SetupState, error)
- func (s *Store) SigningCertificate(ctx context.Context) (SigningCertificate, error)
- func (s *Store) Stats(ctx context.Context) (Stats, error)
- func (s *Store) Stint(ctx context.Context, id UUID, driverID int64) (Stint, error)
- func (s *Store) StintsForDriver(ctx context.Context, q StintQuery) ([]StintRow, error)
- func (s *Store) StorageByDriver(ctx context.Context, limit int) ([]DriverStorage, error)
- func (s *Store) TokensSince(ctx context.Context, since time.Time) (TokenUse, error)
- func (s *Store) TouchAdminLogin(ctx context.Context, id int64) error
- func (s *Store) TouchAdminSession(ctx context.Context, sum []byte, expires time.Time) error
- func (s *Store) TouchDevice(ctx context.Context, id int64) error
- func (s *Store) TouchDriverSession(ctx context.Context, sum []byte, expires time.Time) error
- func (s *Store) TracesOlderThan(ctx context.Context, before time.Time) (TraceSet, error)
- func (s *Store) WriteAudit(ctx context.Context, actor, action, subject string, changed []string) error
- type SummaryWrite
- type TokenUse
- type TraceSet
- type Tx
- type UUID
- type Write
Constants ¶
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.
const ( SettingOrganisation = "organisation" SettingPublicHost = "public_host" SettingTLSMode = "tls_mode" SettingShortName = "short_name" SettingLogo = "logo" 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.
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.
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.
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.
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.
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.
const MinServerVersion = "15"
MinServerVersion is MinServerVersionNum as an operator reads it.
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.
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.
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.
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.
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.
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.
const UUIDLen = 16
UUIDLen is the length of a UUID in bytes.
Variables ¶
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.
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".
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.
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.
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.
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 ¶
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 ¶
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 ¶
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.
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 ¶
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 ¶
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).
type DeviceCursor ¶
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 ¶
DriverStorage is one line of the data page's "who is using the disk" list.
type LapResult ¶
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.
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
AdminByEmail finds an administrator by email address, case-insensitively.
func (*Store) AdminSessionByToken ¶
AdminSessionByToken finds an unexpired session by the digest of its cookie.
func (*Store) ClearPluginDatabase ¶
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 ¶
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 ¶
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 ¶
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 ¶
CountClientBuilds is how many clients have ever been built here.
func (*Store) CountLapsForStint ¶
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 ¶
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 ¶
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 ¶
DeleteAdminSession signs one browser out.
func (*Store) DeleteAdminSessionsForAdmin ¶
DeleteAdminSessionsForAdmin signs every browser of one administrator out, which is what a password change does.
func (*Store) DeleteDriverSession ¶
DeleteDriverSession ends one browser's session, which is a driver signing out.
func (*Store) DeleteDriverSessionByID ¶
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 ¶
DeleteDriverSessionsForDriver signs one driver out of everything. It is the lost-laptop action, for the person rather than for the machine.
func (*Store) DeleteExpiredAdminSessions ¶
DeleteExpiredAdminSessions removes sessions that have run out. Expiry is enforced by the lookup, so this is housekeeping rather than security.
func (*Store) DeleteExpiredDriverSessions ¶
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 ¶
DeleteExpiredIdempotencyKeys removes replay entries that have run out. D-6 gives them twenty-four hours; this is what enforces it.
func (*Store) DeleteFinishedPairings ¶
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 ¶
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 ¶
DeletePluginSetting removes one value.
func (*Store) DeleteSigningCertificate ¶
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 ¶
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 ¶
DeviceCounts reports how many machines have ever been paired and how many are still able to upload.
func (*Store) DriverByID ¶
DriverByID reads one driver.
func (*Store) DriverBySlug ¶
DriverBySlug finds a driver by the slug a plugin names when it signs one in.
func (*Store) DriverSessionByToken ¶
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 ¶
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 ¶
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 ¶
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 ¶
ListDevicesForDriver lists a driver's machines, newest first.
func (*Store) ListDrivers ¶
ListDrivers lists every driver, by name. The admin panel offers them when an operator approves a pairing.
func (*Store) ListPendingPairings ¶
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 ¶
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 ¶
PairingByDeviceCode finds the grant a polling client is asking about.
func (*Store) PanelDeviceByID ¶
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 ¶
PanelStintByID reads one stint for the panel. A stint that is not there is ErrNotFound.
func (*Store) Plugin ¶
Plugin is one of them. A plugin that was never installed is ErrNotFound.
func (*Store) PluginDSN ¶
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 ¶
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 ¶
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 ¶
PluginSettings is what the operator configured for one plugin.
func (*Store) PluginTokensSince ¶
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) ProvisionPluginDatabase ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
SavePluginEnabled records whether the operator wants it running.
func (*Store) SavePluginSecret ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
SettingString reads one string setting, for the places that want a single value without the whole document.
func (*Store) Settings ¶
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 ¶
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 ¶
Stint reads one of a driver's stints outside a transaction, for the reads that check ownership without writing anything.
func (*Store) StintsForDriver ¶
StintsForDriver reads one page of a driver's stints, newest first.
func (*Store) StorageByDriver ¶
StorageByDriver lists what each driver's traces occupy, largest first.
func (*Store) TokensSince ¶
TokensSince sums the usage from an instant until now.
func (*Store) TouchAdminLogin ¶
TouchAdminLogin records a successful sign-in.
func (*Store) TouchAdminSession ¶
TouchAdminSession extends a session that is in use.
func (*Store) TouchDevice ¶
TouchDevice records that a device was used, which the driver sees as "last seen" against the machine in their list.
func (*Store) TouchDriverSession ¶
TouchDriverSession extends a session that is being used.
func (*Store) TracesOlderThan ¶
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.
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.
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.
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 ¶
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 ¶
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 ¶
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 ¶
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.
type Write ¶
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.