store

package
v0.9.2 Latest Latest
Warning

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

Go to latest
Published: Oct 16, 2026 License: Apache-2.0 Imports: 33 Imported by: 0

Documentation

Overview

The acknowledged audit-delivery checkpoint (#1513): the durable per-destination cursor the leader-only webhook delivery loop (internal/audit/sinks) reads the already-masked audit trail from, and the status its metrics report. Methods live on PG, not Store: like the federation cursor (store_devices.go) they serve one background loop, so a test double that implements Store need never grow them.

Governance profiles and their subject assignments (migration 0052).

This file is DUMB on purpose: it round-trips rows and owns exactly one piece of logic, ResolveGovernanceProfile's ORDER BY, so precedence is a property of the single indexed read, not a caller that could forget it. Every write is validated at the API boundary (internal/api/governance.go).

Governance changes (migration 0126): a governance write held for a second human. The store owns the parts that must be one transaction: proposing (expire a lapsed change at the same target, then insert) and deciding (lock the change row, require it pending and unexpired, run the target's apply, move the state, commit). It knows nothing about any target: the apply a caller hands DecideGovernanceChange is that target's own code, run on the decision transaction.

Member drives: a person's own drive, a user_drives row whose owner_subject names them. Ownership is the authority, so there are no grant rows. owner_subject is written once, on INSERT; no statement here updates it.

Package store provides typed CRUD over the Wardyn schema using pgx/v5. Callers supply contexts with deadlines. InsertAuditEvent stays a free function taking the pool explicitly since it predates a Store value in the audit.Recorder wiring.

Naming: Create* inserts and returns the hydrated row; Get* fetches by primary key (ErrNotFound when absent); List* returns a slice, empty never nil; Update*/Decide* are point mutations with explicit optimistic guards.

The Azure DevOps personal access tokens Wardyn creates for `minted_pat` runs (migration 0102, #1428): the record that makes a token revocable after a crash. A row holds no secret; the token value lives in daemon memory only.

The console's reads of ado_run_pats (#1428): every token a run held, and the newest token created for a person on a row. Like the table, neither carries a secret.

The Azure DevOps sign-in end counter (migration 0129): per person, how many times their sign-in was disconnected or erased and whether one is running now, so a run token created on any replica across one of those ends revokes itself. The row holds no secret.

Per-user API tokens. Kept out of store.go on purpose (lint size boundary), mirroring store_sshkeys.go's split.

SECURITY: the raw token never reaches SQL — every method here hashes it with hashToken before touching the table, so token_sha256 is the only form that exists at rest.

The reads the attention projection and GET /me/attention need beyond what GET /runs already scoped: every PENDING approval on a batch of runs, in one query, so paging runs costs one extra round trip regardless of how many are held; and the two scoped PENDING counts /me/attention answers, counted in the database rather than by listing every row.

The attach lease (migration 0129): the one writer slot of a run's shared terminal, in Postgres so every replica agrees who holds it. The database's clock decides when a lease lapses.

Console branding (migration 0091_branding). One row or none; the request is validated at the API boundary (internal/api/branding.go).

Capability grants and the per-kind enforcement switch (migration 0042). Kept out of store.go on purpose, mirroring store_sshkeys.go's split.

Custom components (migration 0136_components) and the snapshot of the ones a run launched with (migration 0137_run_components). Round-trips rows; a definition is validated at the API boundary. See ComponentStore (iface.go).

Registered portals and the delegated tokens they are handed. Like store_apitokens.go and store_devices.go, every method hashes the RAW credential with hashToken before it reaches SQL.

Hybrid enrolment and audit federation (migration 0066): the organisation-side device inventory (devices, device_enrolment_tokens) and the laptop side's durable forwarder cursor (org_federation). Kept out of store.go (already at a lint size boundary), mirroring store_apitokens.go/store_sshkeys.go's split.

SECURITY: hashToken (store_ephemeral.go) is reused for both credential tables — the raw device bearer and raw enrolment token never reach a row, only hex(sha256(raw)).

Short-lived control-plane handoff row (migration 0026): single-use WS attach tickets, durable and shared across every control plane — an in-process map can't survive a restart or be seen by a second control plane. Consume-once is a single DELETE ... RETURNING, atomic under concurrency and across processes.

The sign-in gate and the owner guard: how a suspended identity is refused at issuance and how a credential minted during a suspension is refused at its insert (migration 0113, 0127).

Launch presets (migration 0087_launch_presets). Round-trips rows; the request body is validated at the API boundary (internal/api/presets.go).

The store half of leaver deprovisioning: the SCIM projection on an identity row, the suspension and reactivation transactions, and the durable step ledger (migration 0127).

The identity row every successful sign-in leaves (migration 0113).

Per-person preferences (migration 0080_user_view_type).

A held push's complete path list (migration 0085_push_content_paths).

Remote-hosted runners (migration 0173): the server's own Docker host or Kubernetes cluster, one row per configured substrate name, with a display name and the "Available to people's pools" policy. Every write takes the row FOR UPDATE and raises its revision; a person's add into their own user pool reads it FOR SHARE at the revision whose policy was checked (AddRemoteRunnerToUserPool).

Console-managed role mappings (migration 0051): the store half of internal/auth/oidc's RoleMappingSource. Kept out of store.go on purpose, mirroring store_capabilities.go's split.

The run lease (long-holds design rev 4; migration 0073): the reads and the conditional writes the lease sweep needs. Kept out of store.go for the same size reason as store_watcher.go.

Lost-run surface (long-holds design rev 4, RL-9; migration 0083): token stamp, lapsed-token read, and lost claim. Split out of store.go for size, like store_run_lease.go.

The live exec tail (migration 0129): the masked bytes a run's dispatching replica has printed so far, readable from any replica. One final row per run is run_outputs' (0122); these chunks are what stands in for it until that row is written, and what a recovery falls back to when the substrate cannot be re-read.

The persisted run output (migration 0119): one masked final row per run, the pending row that records a capture is owed, and the erasure tombstones that keep an erased run's output erased. Kept out of store.go for the same size reason as store_watcher.go.

Pause and resume (long-holds design rev 4, §3, RL-7; migration 0097): the presence clock, the reads the pause sweep needs and its conditional writes. Kept out of store.go for the same size reason as store_run_lease.go.

A run's sealed proxy config (#1176, migration 0093): what a revive rebuilds the run's proxy from, so the proxy container never holds it at rest.

Tightened limits reach live runs (long-holds design rev 4, §2.2, RL-8; migration 0095): the read and the conditional write the re-clamp sweep needs. Kept out of store.go for the same size reason as store_run_lease.go.

Revive and restart with current limits (long-holds design rev 4, RL-10 and RL-11; migration 0084): the claim a revive makes before it replaces a run's proxy, and the read the admin version-window listing makes.

Run templates (migration 0165_run_templates): the catalogue row, its immutable revisions, the group-management grants and the group labels. A document is validated at the API boundary and stored as the door returned it. See TemplateStore (iface.go).

The runner pool catalogue (migrations 0152, 0168, 0173): pools, their members, the use policy of an organization pool and the organisation's default. Every write that changes a pool, its members or its use policy holds the pool row and raises its revision by one in the same transaction, so a run admitted against a revision can tell that the pool moved. Whether a caller may use a pool is the API's rule, never this file's.

Registered runners and the credential delivery policy (migration 0139); pending actions are in store_runner_actions.go. Registration tokens and the routes that mint and consume them are a later lane's; this file is the rows the stream, the claim and the pending-action queue read and write.

Landing-page GET /runs scoping: RunFilter, the filtered list read, and hidden-row counts. Attention projection lives elsewhere; this file only scopes, windows and orders the bare AgentRun rows.

Sandbox ref -> substrate routing rows (migration 0021): the Postgres RefStore, letting a control-plane restart rehydrate lifecycle routing in multi-substrate deployments.

SSH gateway key registry. Kept out of store.go on purpose (lint size boundary), mirroring store_sandbox_ref.go's split.

Per-user run-detail cockpit widget-layout persistence (migration 0037). Kept out of store.go/iface.go on purpose — see RunLayoutStore's doc below.

User types (migration 0071_user_types). Round-trips rows; every write is validated at the API boundary (internal/api/user_types.go).

The run-watcher lease (migration 0027): two writes that make "who is responsible for finishing this run" a fact in Postgres, not a goroutine in one process. Kept out of store.go (lint size); shares run columns with store.go's CreateRun/GetRun and pagination.go's ListRunsPage.

The workspaces table (tier 3 of the three-tier split): JSONB parameter helpers, the wsCols column list, scanWorkspace, and the CRUD and scoped-writer methods PG exposes over a workspace row. store_sources.go is the read/hydrate half of the pair (tiers 1-2 plus s.hydrated).

User drives and their subject grants (migration 0054), split out of store.go like governance.go. Kept DUMB: round-trips rows, owns only ResolveUserDrive's ORDER BY (precedence must live in the one indexed read, not a caller). Writes are validated at the API boundary (types.ValidateUserDrive, types.ValidateUserDriveGrant).

Index

Constants

View Source
const (
	AuditOriginDevice       = "device"
	AuditOriginOrganisation = "organisation"
)

The two AuditFilter.Origin values: rows a device forwarded, and rows the organisation wrote itself (FederatedDeviceID decides which is which).

View Source
const (
	RetentionNotOldest      = "audit_retention_not_oldest"
	RetentionNotClosed      = "audit_retention_not_closed"
	RetentionInsideWindow   = "audit_retention_inside_window"
	RetentionLiveRun        = "audit_retention_live_run"
	RetentionDigestMismatch = "audit_retention_digest_mismatch"
)

The refusal reasons audit_retention_drop (migration 0123) raises, one SQLSTATE each. They are also the wire reasons of POST /audit/retention/drop, registered in internal/authz.

View Source
const (
	SplitNotOwner       = "audit_split_not_migrator"
	SplitNotPartitioned = "audit_split_not_partitioned"
	SplitNoLegacy       = "audit_split_no_legacy_partition"
	SplitEmpty          = "audit_split_legacy_empty"
	SplitChainBroken    = "audit_split_chain_broken"
	SplitNameTaken      = "audit_split_name_taken"
)

The refusal reasons of SplitLegacyAudit. Each is a state the split must not run in; nothing was changed.

View Source
const (
	JobKindSuspend = "suspend"
	// JobStepCutoff is step 1 of a suspension: the cutoff, the deactivation and the epoch bump.
	JobStepCutoff = "cutoff"
)

Job kinds and the one step name every kind shares.

View Source
const (
	SearchExternalID = "externalId"
	SearchUserName   = "userName"
	SearchEmail      = "emails.value"
)

SearchIdentities attrs, as SCIM spells them.

View Source
const (
	JobKindPurge            = "purge"
	JobStepAuditDeprovision = "audit_deprovision"
)

JobKindPurge is the ledger kind of a purge; JobStepAuditDeprovision is the step it ends on, and the one MarkIdentityPurged records pending so a purge that dies right after the mark is still on the ledger.

View Source
const (
	PeopleDirectoryDefaultLimit = 50
	PeopleDirectoryMaxLimit     = 200
)

A Limit of zero is PeopleDirectoryDefaultLimit; one above PeopleDirectoryMaxLimit is clamped to it.

View Source
const (
	TemplatesMaxPerPerson = 200
	TemplatesMaxPerGroup  = 200
	TemplatesMaxOrg       = 500
)

The most templates one audience holds: each person's own, each group's, and the organisation's. Counted and inserted under templateAudienceLockSQL, so two creates racing for the last slot cannot both land.

View Source
const (
	RunnerPoolsMax          = 200
	RunnerPoolsPerOwnerMax  = 10
	RunnerPoolsOversightMax = 1000
)

RunnerPoolsMax bounds the organisation's live pools and RunnerPoolsPerOwnerMax one person's, so a person's catalogue read is bounded and no person's pools can crowd the organisation's out of it. RunnerPoolsOversightMax bounds the oversight read, which lists the organisation's pools and then the caller's own before anyone else's, so neither can drop out of it.

View Source
const (
	JobKindGroupRemove = "group_remove"

	GroupStepSessions = "sessions"
	GroupStepTokens   = "tokens"
	GroupStepAudit    = "audit"
)

The group removal ledger (migration 0127's group_remove kind): one set of rows per person and group, the target of every step being the group's id.

View Source
const ADOSignInEndsStaleAfter = 10 * time.Minute

ADOSignInEndsStaleAfter is how long an end that began and never finished (its replica died) still counts as running. Past it the count settles, so a person's mints stop revoking themselves; an end finishes in seconds.

View Source
const AuditTargetTruncatedMarker = "…[truncated]"

AuditTargetTruncatedMarker is appended to a capped target so the row says what happened to it. Without it a truncated path reads as a real one, and an operator matching `target = '/api/v1/policies/aaa…'` would believe it.

View Source
const FederatedClaimHashSQL = `` /* 334-byte string literal not displayed */

FederatedClaimHashSQL recomputes, from one stored federated row aliased e, the hash the device claimed for it: equals e.data->'device_origin'->>'row_hash' for every row IngestDeviceAudit accepts. Claimed actor/source_ip/prev_hash live in device_origin; the claimed object is stored data minus that key; a data-less claim is named by device_origin.data_null ("json" for JSON null, "sql" for absent).

View Source
const HoldsSandboxSQL = `(lost_at IS NULL OR (lost_reason = '` + string(types.LostOutage) + `' AND agent_stopped_at IS NULL))`

HoldsSandboxSQL is TRUE when the agent_runs row's agent may still be running: the run is not kept, or it was kept after a control-plane outage and no stop of its agent is recorded (agent_stopped_at, written by MarkRunAgentStopped once the lease sweep stopped it past its end). A failed stop records nothing, so the run holds its slot past its end until the stop is confirmed. A run kept after its end or a reboot, or an outage-kept run whose agent was stopped, has no agent, even if an extension has since moved its end ahead. It is the one rule for a live run under WARDYN_MAX_CONCURRENT_RUNS, in the fleet capacity view and under WARDYN_RUN_MAX_AGE.

View Source
const MaxAuditTargetLen = 512

MaxAuditTargetLen bounds the audit row's `target` column (plain TEXT, unbounded). An authenticated caller looping a huge path via authz.denied (unrate-limited, unlike auth.fail) could otherwise write megabytes per second into a table whose Postgres trigger forbids DELETE; 512 bytes holds every real target this tree writes with an order of magnitude to spare.

View Source
const PeopleDirectoryActiveSessionWindow = 24 * time.Hour

PeopleDirectoryActiveSessionWindow is how recent a sign-in must be to count as a live session. Sessions are stateless signed cookies the server keeps no row for, so the directory knows only when a person last signed in, not how many cookies are out: it counts one when that sign-in is inside this window and no revoke cutoff has passed it.

View Source
const ReauthHoldMax = 1800 * time.Second

ReauthHoldMax is the longest a credential re-auth hold keeps the agent's connection parked (WARDYN_CREDENTIAL_REAUTH_TIMEOUT's ceiling). A re-auth request counts toward a waiting pause only once it is older than this: until then the agent is still inside the connection that asked, and freezing it would cut a sign-in the person may be finishing.

View Source
const SearchDisplayName = "displayName"

SearchDisplayName is the Groups filter attribute that SearchExternalID shares the rest of with Users.

View Source
const SystemAuditActor = "wardynd"

SystemAuditActor is the actor an unattended retention drop (WARDYN_AUDIT_RETENTION_AUTODROP) records: audit_retention_drop treats exactly this name as the system and marks the chained event unattested.

Variables

View Source
var (
	// ErrTemplateNameTaken: another template of the same audience has the name,
	// compared case-insensitively.
	ErrTemplateNameTaken = errors.New("store: a template available to the same people already uses this name")
	// ErrTemplateGrantGone: a group write found no grant for the writer when it
	// re-read it inside its own transaction.
	ErrTemplateGrantGone = errors.New("store: the template group grant is gone")
)
View Source
var (
	// ErrRunnerPoolStale: the caller's revision is not the pool's.
	ErrRunnerPoolStale = errors.New("store: runner pool revision is stale")
	// ErrRunnerPoolGoverned: a detach that was decided as direct met a use policy under the locks.
	ErrRunnerPoolGoverned = errors.New("store: runner pool now has a use policy, so the detach must be held")
	// ErrRunnerPoolLimit: the organisation is at RunnerPoolsMax pools, or the owner at RunnerPoolsPerOwnerMax.
	ErrRunnerPoolLimit = errors.New("store: too many runner pools")
	// ErrRunnerPoolScope: a self-hosted runner was offered to an organization pool, which takes
	// remote-hosted runners only.
	ErrRunnerPoolScope = errors.New("store: organization pools take remote-hosted runners only")
	// ErrRunnerPoolHasPolicy: a pool with a use policy is not deleted, because the delete would clear
	// the policy outside the second-human hold; clear the policy first.
	ErrRunnerPoolHasPolicy = errors.New("store: the runner pool still has a use policy")
)
View Source
var AuditChainPageSize int32 = 1000

AuditChainPageSize is how many rows one page of the verify sweep reads — bounds memory to a page, never the table. Large enough that the per-page round trip is noise against re-hashing a thousand rows, small enough to stay a few hundred kilobytes. A var only so the parity test can shrink it and cross page boundaries.

View Source
var ErrAlreadyDecided = types.ErrApprovalAlreadyDecided

ErrAlreadyDecided is returned when DecideApproval targets an approval that has already left PENDING. Fail closed: a second decision never overwrites the first.

View Source
var ErrAuditPartitionOpen = errors.New("store: audit partition is still open")

ErrAuditPartitionOpen is a partition that can still receive rows: it has no digest yet.

View Source
var ErrConflict = errors.New("store: conflict")

ErrConflict reports a fenced write losing its race: a source scan slot already claimed, or the requirements merge hitting the key cap.

View Source
var ErrDeviceRevoked = errors.New("store: device is revoked")

ErrDeviceRevoked is IngestDeviceAudit's answer for a device revoked after its request was authenticated. Distinct from ErrConflict so the caller answers it as revocation, never a broken chain.

View Source
var ErrDriveAllocated = fmt.Errorf(
	"%w: this user drive gained an allocation while the write was being prepared", ErrConflict)

ErrDriveAllocated is returned by UpsertUserDrive when the caller asked for the write to apply only while the drive has no allocations (refuseIfAllocated) and the row has some. It is the statement-level half of the API's re-home guard: since a plain read-then-write would let a grant created in between get silently re-homed, the precondition rides the writing statement under a FOR UPDATE on the drive row in the same transaction, making it race-free.

View Source
var ErrDriveHomeNamespaceConflict = fmt.Errorf(
	"%w: another host_path drive on this host_root derives home directory names by a different rule", ErrConflict)

ErrDriveHomeNamespaceConflict is returned by UpsertUserDrive when a host_path drive would share a host_root with another host_path drive that derives home directory NAMES by a different rule. Wraps ErrConflict (as do ErrDriveSlugConflict and ErrDriveAllocated below) so callers asking only "is this a 409?" keep working, while the remedy stays distinguishable from a plain UNIQUE(name) collision.

View Source
var ErrDriveSlugConflict = fmt.Errorf(
	"%w: another user drive's name folds to the same storage-object name", ErrConflict)

ErrDriveSlugConflict is returned by UpsertUserDrive when a drive's name folds to a storage-object slug another drive already holds — "Corp NAS" against an existing "corp nas", or "Corp NAS (eng)" against "corp-nas-eng". Distinct from UNIQUE(name): the name itself is free, but types.DriveSlug(name) — the fragment every minted object name is built from — collides, so the remedy is a name differing by more than case or punctuation.

View Source
var ErrDuplicatePending = types.ErrDuplicatePendingApproval

ErrDuplicatePending is returned by CreateApproval when a partial unique index rejects a second open PENDING approval for the same dedup key (a concurrent raise lost the race). Callers treat it as a dedup signal — re-read and return the existing PENDING row — not a hard failure.

View Source
var ErrFederatedOrgRun = errors.New("store: federated row names an organisation run")

ErrFederatedOrgRun refuses a batch where a row's run_id names one of this organisation's own runs: phase one federates audit rows, never run records, so a laptop's row can't legitimately belong to one.

View Source
var ErrFederatedRowInvalid = errors.New("store: federated row cannot be stored as claimed")

ErrFederatedRowInvalid refuses a batch holding a row this organisation can't store (FederatedRowProblem) or a value Postgres can't represent (SQLSTATE class 22); the caller answers 4xx so the forwarder stops rather than retrying a 5xx forever.

View Source
var ErrGovernanceChangeStale = errors.New("store: the governance change is stale")

ErrGovernanceChangeStale is what a decision's apply returns when the target is no longer what the proposal reviewed. DecideGovernanceChange moves the change to stale and commits that, and returns this error.

View Source
var ErrIdentityBindingMismatch = errors.New("store: identity bound to another principal")

ErrIdentityBindingMismatch is a sign-in's refusal when the identity it names is already bound to another principal: issuing its session under the new one would leave it outside the identity's deactivation and epoch checks. Nothing is written.

View Source
var ErrIdentityDeactivated = errors.New("store: identity deactivated")

ErrIdentityDeactivated is the refusal of anything that would give a deactivated, purged or stale-epoch identity a new session, credential or run.

View Source
var ErrIdentityNotRebindable = errors.New("store: identity has no object id and cannot be re-bound")

ErrIdentityNotRebindable is UnbindIdentity's refusal of a row with no object id: such a row is found only by its principal, so an unbound one is never reached again and the next sign-in would create a second row without this one's epoch or SCIM linkage.

View Source
var ErrIdentityPurged = errors.New("store: identity purged")

ErrIdentityPurged is a reactivation's refusal: a purged identity is a permanent tombstone.

View Source
var ErrIdentityRebound = errors.New("store: the identity was bound since the suspension was planned")

ErrIdentityRebound is a SuspendPlan made before a sign-in bound the identity row: its forms miss the newly bound principal, so the caller reads the row again and makes a new plan.

View Source
var ErrInvitationCap = errors.New("store: too many pending invitations for the target")

ErrInvitationCap is CreateInvitation's refusal when the target already has the maximum pending.

View Source
var ErrInvitationNotPending = errors.New("store: invitation is not pending")

ErrInvitationNotPending is ClaimInvitation's and RevokeInvitation's refusal of an invitation that is claimed, revoked or expired, or was before a concurrent writer got there.

View Source
var ErrLoginLockNoCapacity = db.ErrAdvisoryLockNoCapacity

ErrLoginLockNoCapacity is the one LockLoginSupersede error a caller may proceed unlocked on (the pool can't spare a connection); any other error means the caller should refuse.

View Source
var ErrMemberDriveBusy = fmt.Errorf("%w: this drive is already being deleted", ErrConflict)

ErrMemberDriveBusy is returned by a delete Begin when another delete of the same drive started less than a minute ago.

View Source
var ErrMemberDriveLimit = errors.New("store: the most drives one person may hold already exist")

ErrMemberDriveLimit is returned by CreateMemberDrive when the owner already holds the most drives allowed. Rows being deleted still count.

View Source
var ErrMemberDriveNameTaken = fmt.Errorf("%w: you already have a drive with this name", ErrConflict)

ErrMemberDriveNameTaken is returned by CreateMemberDrive when the owner already has a drive whose name folds to the same slug.

View Source
var ErrMoveTargetUnknown = errors.New("store: no such service identity")

ErrMoveTargetUnknown is MoveAPIToken's refusal of a service identity that does not exist. A retired one is ErrServiceIdentityInactive.

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

ErrNotFound is returned when a Get* call finds no row.

View Source
var ErrPushPathListCap = errors.New("store: this run has stored its limit of push path lists")

ErrPushPathListCap is RecordPushPathList's refusal: the run already has its limit of lists.

View Source
var ErrRecordingOutputErased = errors.New("store: recording-derived output was erased")

ErrRecordingOutputErased refuses a new copy of an erased recording while leaving independently captured stdout and pane snapshots alone.

View Source
var ErrRecordingOutputUncovered = errors.New("store: recording output lost masking coverage")

ErrRecordingOutputUncovered refuses a historical read whose masking manifest was fenced or removed before the recovered row could commit.

View Source
var ErrRunCapReached = errors.New("store: deployment run cap reached")

ErrRunCapReached is CreateRunUnderCap's refusal: the deployment already holds its cap of non-terminal runs.

View Source
var ErrRunOutputErased = errors.New("store: the run's output was erased")

ErrRunOutputErased is every writer's and reader's answer for a run whose output was erased: the tombstone is checked in the same transaction as the write or read, under the run's lock, so no replica can recreate or serve it.

View Source
var ErrRunQueueFull = errors.New("store: run queue is full for this owner")

ErrRunQueueFull is CreateQueuedRun's refusal: the owner already has the most runs waiting for the pool.

View Source
var ErrRunnerActionRefused = errors.New("runner pending action refused")

ErrRunnerActionRefused covers a pending action or action_result whose runner, owner, run, ref, generation or loss does not match the org's current record.

View Source
var ErrRunnerAtCapacity = errors.New("store: runner is running its limit of runs at once")

ErrRunnerAtCapacity is the refusal when a runner has no room for the run: it already holds its Runs at once, a total would be passed, the run is past its per-run limit, or it declares no limits.

View Source
var ErrRunnerClaimMismatch = errors.New("store: runner claim does not match the owner or key fingerprint")

ErrRunnerClaimMismatch is ClaimRunner's refusal when the runner is unclaimed but the caller is not its owner or the fingerprint differs: the API answers runner_claim_mismatch.

View Source
var ErrRunnerExecutionRefused = errors.New("runner execution refused")

ErrRunnerExecutionRefused covers foreign, stale and incomplete incarnations.

View Source
var ErrRunnerPoolFull = errors.New("store: runner pool is full")

ErrRunnerPoolFull is a revive's refusal when its pool is at its concurrent-run cap or has runs waiting: a waiting run is first in line, so a revive never jumps it (pool-queue.md section 10).

View Source
var ErrRunnerReconciliationRequired = errors.New("runner lifecycle reconciliation required")

ErrRunnerReconciliationRequired refuses READY without issuing an RPC from the checked STATE handler; page intake commits any containment action first.

View Source
var ErrRunnerSpoolRefused = errors.New("runner audit replay refused")

ErrRunnerSpoolRefused does not distinguish an unknown run from a foreign or revoked runner. A failed checked stream event must not receive an ACK.

View Source
var ErrScimGroupExists = errors.New("store: scim group exists")

ErrScimGroupExists is a create whose externalId already names a group.

View Source
var ErrServiceIdentityInactive = errors.New("store: the service identity is not active")

ErrServiceIdentityInactive is the refusal of a write to a service identity that is retired, or, for a token mint, suspended or sponsored by someone else than the minter.

View Source
var ErrServiceNameTaken = errors.New("store: that service identity name is already in use")

ErrServiceNameTaken is CreateServiceIdentity's refusal of a name a principal-keyed table already holds (or a service identity already carries), so no new identity inherits another's grants or history.

View Source
var ErrServiceSponsorInvalid = errors.New("store: the sponsor is not a bound, active identity")

ErrServiceSponsorInvalid is the refusal of a sponsor that is not exactly the principal of a bound, non-deactivated identity.

View Source
var ErrServiceTokenCap = errors.New("store: the service identity holds the maximum number of live tokens")

ErrServiceTokenCap is CreateServiceToken's refusal when the identity already holds the most live tokens.

View Source
var ErrServiceUserTypeUnknown = errors.New("store: no such user type")

ErrServiceUserTypeUnknown is CreateServiceIdentity's refusal for a user type with no user_types row.

View Source
var ErrTokenNotMovable = errors.New("store: the token is not a live admin-minted token")

ErrTokenNotMovable is MoveAPIToken's refusal of a token that is revoked or expired, or that no admin minted for someone else.

View Source
var ErrUserDriveGrantChanged = errors.New("store: the allocation changed while it was being written")

ErrUserDriveGrantChanged is UpsertUserDriveGrantWithinTier's answer when an allocation for the subject appeared while the write was judged against there being none.

GroupRemovalSteps are the steps StartGroupRemoval opens, in the order they run.

Functions

func AddRunnerPoolExecutorQ added in v0.9.0

func AddRunnerPoolExecutorQ(ctx context.Context, q Querier, id uuid.UUID, executor string) (m types.RunnerPoolMember, added bool, rev int64, err error)

AddRunnerPoolExecutorQ is AddRunnerPoolExecutor's statement on q (a pgx.Tx), which a held change applies through inside the decision transaction.

func CapAuditTarget added in v0.7.4

func CapAuditTarget(target string) string

CapAuditTarget bounds one audit target. A target already within the cap is returned BYTE-IDENTICAL: audit queries match this column exactly, so the cap must be invisible to every row that is not abusive.

func CreateServiceIdentityQ added in v0.9.0

func CreateServiceIdentityQ(ctx context.Context, tx Querier, si types.ServiceIdentity) (types.ServiceIdentity, error)

CreateServiceIdentityQ is CreateServiceIdentity's statements on tx (a pgx.Tx), which a held governance change applies inside the decision transaction.

func DeleteCapabilityGrantQ added in v0.8.6

func DeleteCapabilityGrantQ(ctx context.Context, q Querier, id uuid.UUID) error

DeleteCapabilityGrantQ is DeleteCapabilityGrant's statement on q.

func DeleteGovernanceAssignmentQ added in v0.8.6

func DeleteGovernanceAssignmentQ(ctx context.Context, q Querier, id uuid.UUID) error

DeleteGovernanceAssignmentQ is DeleteGovernanceAssignment's statement on q.

func DeleteGovernanceProfileQ added in v0.8.6

func DeleteGovernanceProfileQ(ctx context.Context, q Querier, id uuid.UUID) error

DeleteGovernanceProfileQ is DeleteGovernanceProfile's body on a caller's transaction (q must be a pgx.Tx: it takes the transaction-scoped graph lock) and answers the same errors.

func DeleteRoleMappingQ added in v0.8.6

func DeleteRoleMappingQ(ctx context.Context, q Querier, id uuid.UUID) error

DeleteRoleMappingQ is DeleteRoleMapping's statement on q.

func DeleteRunnerPoolUsePolicyQ added in v0.9.0

func DeleteRunnerPoolUsePolicyQ(ctx context.Context, q Querier, id uuid.UUID) error

DeleteRunnerPoolUsePolicyQ is DeleteRunnerPoolUsePolicy's statement on q.

func ExecRevocationCut added in v0.9.0

func ExecRevocationCut(ctx context.Context, q batcher, sql string, args ...any) error

ExecRevocationCut runs one cut write (an upsert into oidc_session_revocations or oidc_session_cuts) under the exclusive revocation-order lock, as a single pipelined transaction.

func FederatedActor added in v0.8.0

func FederatedActor(deviceID uuid.UUID, claimed string) string

FederatedActor is the actor a federated row is stored under: the forwarding device's own actor (device:<id>), a slash, then the claimed principal, so an org-principal filter never matches a laptop's claim.

func FederatedDeviceID added in v0.8.0

func FederatedDeviceID(ev types.AuditEvent) *uuid.UUID

FederatedDeviceID reports which device forwarded ev, or nil for the organisation's own row. TRUST BOUNDARY: federated only when both marks agree — data.device_origin.device_id and the FederatedActor prefix naming the same device — so no writer controlling only one can make its own row read as a device's. federatedRowSQL is the same predicate in SQL.

func FederatedRowProblem added in v0.8.0

func FederatedRowProblem(r types.FederatedAuditEvent) string

FederatedRowProblem says why r cannot be stored so its claim re-checks from the stored row, or "" when it can. data must be a JSON object without a top-level device_origin key (this organisation's marker), JSON null, or absent; target must be one CapAuditTarget leaves unchanged; device_id is this organisation's to derive, never the device's to claim. Exported so the API refuses with a 400 before any database work.

func GetCapabilityEnforcementQ added in v0.8.6

func GetCapabilityEnforcementQ(ctx context.Context, q Querier) (map[string]bool, error)

GetCapabilityEnforcementQ is GetCapabilityEnforcement on q.

func GetCapabilityGrantByKeyQ added in v0.8.6

func GetCapabilityGrantByKeyQ(ctx context.Context, q Querier, subjectType, subject, capability, value string, forUpdate bool) (types.CapabilityGrant, error)

GetCapabilityGrantByKeyQ reads the grant at one natural key on q, ErrNotFound when none; forUpdate locks the row.

func GetCapabilityGrantQ added in v0.8.6

func GetCapabilityGrantQ(ctx context.Context, q Querier, id uuid.UUID, forUpdate bool) (types.CapabilityGrant, error)

GetCapabilityGrantQ reads one grant by id on q, ErrNotFound when none; forUpdate locks the row.

func GetCredentialDeliveryPolicyQ added in v0.9.0

func GetCredentialDeliveryPolicyQ(ctx context.Context, q Querier) (types.CredentialDeliveryPolicyDoc, error)

GetCredentialDeliveryPolicyQ is GetCredentialDeliveryPolicy on q (a governance decision's transaction).

func GetGovernanceAssignmentByKeyQ added in v0.8.6

func GetGovernanceAssignmentByKeyQ(ctx context.Context, q Querier, subjectType, subject string, forUpdate bool) (types.GovernanceAssignment, error)

GetGovernanceAssignmentByKeyQ reads the assignment at one natural key on q, ErrNotFound when none.

func GetGovernanceAssignmentQ added in v0.8.6

func GetGovernanceAssignmentQ(ctx context.Context, q Querier, id uuid.UUID, forUpdate bool) (types.GovernanceAssignment, error)

GetGovernanceAssignmentQ reads one assignment by id on q, ErrNotFound when none; forUpdate locks the row for the transaction.

func GetGovernanceProfileQ added in v0.8.6

func GetGovernanceProfileQ(ctx context.Context, q Querier, id uuid.UUID, forUpdate bool) (types.GovernanceProfile, error)

GetGovernanceProfileQ reads one profile row (raw, as GetGovernanceProfile does) on q.

func GetLiveAPITokenQ added in v0.8.6

func GetLiveAPITokenQ(ctx context.Context, q Querier, id uuid.UUID) (types.APIToken, error)

GetLiveAPITokenQ re-reads one token by id on q (a caller's transaction), and only while it can still authenticate: revoked and expired rows are ErrNotFound, as in GetAPITokenByRaw. A decision that must still hold the authority of the token it was made with reads it again through this, not through the context the request was authenticated with.

func GetRemoteRunnerQ added in v0.9.0

func GetRemoteRunnerQ(ctx context.Context, q Querier, id string, forUpdate bool) (types.RemoteRunner, error)

GetRemoteRunnerQ reads one remote runner on q, locked FOR UPDATE when forUpdate.

func GetRunnerPoolUsePolicyQ added in v0.9.0

func GetRunnerPoolUsePolicyQ(ctx context.Context, q Querier, id uuid.UUID) (types.RunnerPoolUsePolicy, error)

GetRunnerPoolUsePolicyQ reads one pool's use policy on q, ErrNotFound when it has none.

func GetServiceIdentityForUpdateQ added in v0.9.0

func GetServiceIdentityForUpdateQ(ctx context.Context, tx Querier, name string) (types.ServiceIdentity, error)

GetServiceIdentityForUpdateQ reads name FOR UPDATE on tx (a pgx.Tx), retired or not; ErrNotFound when missing.

func GetSiteConfigQ added in v0.9.0

func GetSiteConfigQ(ctx context.Context, q Querier) (types.SiteConfig, error)

GetSiteConfigQ is GetSiteConfig on q.

func GetUserTypeQ added in v0.8.6

func GetUserTypeQ(ctx context.Context, q Querier, id string, forUpdate bool) (types.UserType, error)

GetUserTypeQ is GetUserType on q; forUpdate locks the row for the transaction.

func IdentityBlockedQ added in v0.8.9

func IdentityBlockedQ(ctx context.Context, q Querier, principal string, epoch int64) (bool, error)

IdentityBlockedQ checks identity admission on q, including an existing transaction.

func InsertAuditEvent

func InsertAuditEvent(ctx context.Context, pool *pgxpool.Pool, ev *types.AuditEvent) error

InsertAuditEvent appends a single audit event. Implements audit.Recorder. The Postgres trigger blocks UPDATE/DELETE; this function only ever INSERTs.

ev is taken by POINTER so the hash chain can be handed back: on success ev.PrevHash/ev.RowHash carry the values Postgres computed, and ev.RowHash IS the chain head at that instant, which fanoutRecorder emits to the audit sinks so an external SIEM ends up holding a head hash Wardyn cannot later disown.

Runs in a transaction because pg_advisory_xact_lock must be held across the append: this caller's seq allocation and head read must not interleave with another writer's, keeping seq order and chain order identical. audit_append takes the same lock itself (the only way a row enters the table since 0111); the lock here is re-entrant and free, and keeps the bounded wait below in front of it.

func InsertAuditEventTx added in v0.8.6

func InsertAuditEventTx(ctx context.Context, tx pgx.Tx, ev *types.AuditEvent) error

InsertAuditEventTx is InsertAuditEvent's body on a caller-supplied transaction: the same target cap, pinned lock-wait bound and advisory lock on the chain, so a row written this way links exactly as every other row does.

func LiftComponentRestrictionQ added in v0.8.9

func LiftComponentRestrictionQ(ctx context.Context, q Querier, id uuid.UUID, _ string) error

LiftComponentRestrictionQ is LiftComponentRestriction's statements on q, for a caller already in a transaction (a held change's apply). The availability lock is taken again, which is free inside one transaction, so the existence check and the delete are ordered against a component delete either way.

func ListCapabilityGrantsQ added in v0.8.6

func ListCapabilityGrantsQ(ctx context.Context, q Querier) ([]types.CapabilityGrant, error)

ListCapabilityGrantsQ is ListCapabilityGrants on q.

func ListCapabilityRestrictionsQ added in v0.8.6

func ListCapabilityRestrictionsQ(ctx context.Context, q Querier) (map[string]map[string]bool, error)

ListCapabilityRestrictionsQ is ListCapabilityRestrictions on q.

func ListGovernanceProfilesQ added in v0.8.6

func ListGovernanceProfilesQ(ctx context.Context, q Querier) ([]types.GovernanceProfile, error)

ListGovernanceProfilesQ reads every profile by name on q.

func ListRoleMappingsQ added in v0.8.6

func ListRoleMappingsQ(ctx context.Context, q Querier) ([]types.RoleMapping, error)

ListRoleMappingsQ is ListRoleMappings on q.

func ListUserTypesQ added in v0.8.6

func ListUserTypesQ(ctx context.Context, q Querier) ([]types.UserType, error)

ListUserTypesQ is ListUserTypes on q.

func LockGovernanceAssignmentKey added in v0.8.6

func LockGovernanceAssignmentKey(ctx context.Context, q Querier, subjectType, subject string) error

LockGovernanceAssignmentKey takes the transaction-scoped lock keyed to one assignment's natural key, so a write to that key and the approval of a held change to it serialize. q must be a pgx.Tx.

func LockGovernanceGraph added in v0.8.6

func LockGovernanceGraph(ctx context.Context, q Querier) error

LockGovernanceGraph takes the transaction-scoped lock every write to the governance profile graph holds, so one write at a time sees and changes it. q must be a pgx.Tx.

func LockGovernanceTarget added in v0.8.6

func LockGovernanceTarget(ctx context.Context, q Querier, kind string, parts ...string) error

LockGovernanceTarget takes the transaction-scoped lock for one target of a governance change: the direct write to it and the approval of a held change to it serialize on it. parts are the target's natural key. q must be a pgx.Tx.

func LockLivePoolQ added in v0.9.0

func LockLivePoolQ(ctx context.Context, q Querier, id uuid.UUID) error

LockLivePoolQ takes the pool row lock of a live pool on q, ErrNotFound when there is none. A held change takes it right after its target lock, so every member write waits for the decision.

func LockRevocationOrder added in v0.9.0

func LockRevocationOrder(ctx context.Context, tx Querier) error

LockRevocationOrder takes the exclusive revocation-order lock for the rest of tx, which must contain the cut write it protects. tx must be a pgx.Tx.

func LockSiteConfig added in v0.9.0

func LockSiteConfig(ctx context.Context, q Querier) error

LockSiteConfig takes the transaction-scoped form of the lock the site-config writers hold (db.SiteConfigLockClass, the class's single document lock), so a held change applied inside the decision transaction cannot interleave with a PUT /site-config read-modify-write. q must be a pgx.Tx.

func PutCapabilityEnforcementQ added in v0.8.6

func PutCapabilityEnforcementQ(ctx context.Context, q Querier, enabled map[string]bool) (map[string]bool, error)

PutCapabilityEnforcementQ is PutCapabilityEnforcement's statements on q. A held governance change applies through it inside the decision transaction.

func PutCredentialDeliveryPolicyQ added in v0.9.0

func PutCredentialDeliveryPolicyQ(ctx context.Context, q Querier, policy json.RawMessage, by string, now time.Time) (types.CredentialDeliveryPolicyDoc, error)

PutCredentialDeliveryPolicyQ is PutCredentialDeliveryPolicy on q.

func PutRunnerPoolUsePolicyQ added in v0.9.0

func PutRunnerPoolUsePolicyQ(ctx context.Context, q Querier, id uuid.UUID, revision *int64, subjects []types.RunnerPoolSubject, by string) (types.RunnerPoolUsePolicy, error)

PutRunnerPoolUsePolicyQ is PutRunnerPoolUsePolicy's statement on q (a pgx.Tx, the pool row locked here). ErrNotFound: no such live organization pool.

func PutSiteConfigQ added in v0.9.0

func PutSiteConfigQ(ctx context.Context, q Querier, cfg types.SiteConfig) (types.SiteConfig, error)

PutSiteConfigQ is PutSiteConfig on q. A held governance change applies through it inside the decision transaction.

func ReadRevocationSeq added in v0.9.0

func ReadRevocationSeq(ctx context.Context, q batcher) (int64, error)

ReadRevocationSeq is the ordering value a credential issued now records. Always at least 1 once migration 0176 has run; the caller stores it as the credential's rev_seq.

func RemoveRunnerPoolExecutorQ added in v0.9.0

func RemoveRunnerPoolExecutorQ(ctx context.Context, q Querier, id uuid.UUID, executor string) (bool, int64, error)

RemoveRunnerPoolExecutorQ is RemoveRunnerPoolExecutor's statement on q (a pgx.Tx), which a held detach applies through inside the decision transaction.

func RunnerPoolExecutorIDsQ added in v0.9.0

func RunnerPoolExecutorIDsQ(ctx context.Context, q Querier, id uuid.UUID) ([]string, error)

RunnerPoolExecutorIDsQ lists a pool's remote-hosted members, sorted, never nil.

func SetCapabilityRestrictionQ added in v0.8.6

func SetCapabilityRestrictionQ(ctx context.Context, q Querier, capability, value string, restricted bool, by string) error

SetCapabilityRestrictionQ is SetCapabilityRestriction's statement on q.

func SetRemoteRunnerPeopleQ added in v0.9.0

func SetRemoteRunnerPeopleQ(ctx context.Context, q Querier, id string, revision *int64, pools types.RemoteRunnerPeoplePools, subjects []types.RunnerPoolSubject, by string) (types.RemoteRunner, error)

SetRemoteRunnerPeopleQ writes a remote runner's people policy on q (a pgx.Tx holding the governance target lock): pools off clears the list, listed stores subjects. A non-nil revision must be the runner's (ErrRunnerPoolStale). ErrNotFound: no such runner.

func TemplateLimit added in v0.9.0

func TemplateLimit(owner types.TemplateOwner) int

TemplateLimit is the most templates owner's audience may hold.

func UpdateUserTypeQ added in v0.8.6

func UpdateUserTypeQ(ctx context.Context, q Querier, t types.UserType) (types.UserType, error)

UpdateUserTypeQ is UpdateUserType's statement on q. A held governance change applies through it inside the decision transaction.

func UpsertCapabilityGrantQ added in v0.8.6

func UpsertCapabilityGrantQ(ctx context.Context, q Querier, g types.CapabilityGrant) (types.CapabilityGrant, error)

UpsertCapabilityGrantQ is UpsertCapabilityGrant's statement on q. A held governance change applies through it inside the decision transaction.

func UpsertGovernanceAssignmentQ added in v0.8.6

func UpsertGovernanceAssignmentQ(ctx context.Context, q Querier, a types.GovernanceAssignment) (types.GovernanceAssignment, error)

UpsertGovernanceAssignmentQ is UpsertGovernanceAssignment's statement on q.

func UpsertRoleMappingQ added in v0.8.6

func UpsertRoleMappingQ(ctx context.Context, q Querier, m types.RoleMapping) (types.RoleMapping, error)

UpsertRoleMappingQ is UpsertRoleMapping's statement on q (the doc above is its contract). A held governance change applies through it inside the decision transaction.

func WithIdentityGuard added in v0.8.6

func WithIdentityGuard(ctx context.Context, g IdentityGuard) context.Context

WithIdentityGuard makes the insert writers that honour it (CreateRun, CreateRunUnderCap, CreateAPIToken, AddSSHKey) read the owner's identity rows FOR SHARE in their own transaction. Suspension takes the same rows FOR UPDATE, so a write either commits before the suspension's first step, where the sweep after it finds it, or reads the deactivation and is refused.

func WriteGovernanceProfileQ added in v0.8.6

func WriteGovernanceProfileQ(ctx context.Context, q Querier, id uuid.UUID, build GovernanceProfileBuild) (types.GovernanceProfile, error)

WriteGovernanceProfileQ is WriteGovernanceProfile's body on a caller's transaction: q must be a pgx.Tx, because the graph lock it takes is transaction-scoped. A held governance change applies through it inside the decision transaction.

Types

type ADOSignInEndState added in v0.8.6

type ADOSignInEndState struct {
	Gen     int64
	Running bool // an end began and has not finished, within ADOSignInEndsStaleAfter
	Reason  string
}

ADOSignInEndState is one person's count.

type ADOSignInEndStore added in v0.8.6

type ADOSignInEndStore interface {
	// BeginADOSignInEnd marks the start of an end of owner's sign-in.
	BeginADOSignInEnd(ctx context.Context, owner, reason string) error
	// FinishADOSignInEnd marks its finish.
	FinishADOSignInEnd(ctx context.Context, owner string) error
	// ADOSignInEnds is owner's count now; a person with no row has gen 0.
	ADOSignInEnds(ctx context.Context, owner string) (ADOSignInEndState, error)
}

ADOSignInEndStore is the sign-in end counter. Optional like RunOutputStore: the api layer type-asserts, and a store without it counts in the process.

type APITokensByIdentityLister added in v0.9.0

type APITokensByIdentityLister interface {
	ListAPITokensByIdentity(ctx context.Context, name string) ([]types.APIToken, error)
}

APITokensByIdentityLister is the indexed read behind a per-person revocation: the tokens one name owns under either form. An absent implementation falls back to ListAPITokensByPrincipal plus a scan of ListAPITokens (see ListAPITokensByIdentity for the one place the two differ).

type APITokensByPrincipalPager added in v0.8.0

type APITokensByPrincipalPager interface {
	ListAPITokensByPrincipalPage(ctx context.Context, principal string, p Page) ([]types.APIToken, error)
}

APITokensByPrincipalPager is the scoped analogue of Pager for GET /me/tokens, same safe-fallback posture as GrantsByRunPager (ListAPITokensByPrincipal is already WHERE principal=$1).

type AWSSSOSpentTokenStore added in v0.8.0

type AWSSSOSpentTokenStore interface {
	// MarkAWSSSOTokenSpent upserts one spent-token row, idempotent: re-marking
	// an already-spent fingerprint is a no-op, so row age stays "since first
	// spent" for the prune below.
	MarkAWSSSOTokenSpent(ctx context.Context, fingerprint, owner string, markedAt time.Time) error
	// AWSSSOTokenSpent reports whether fingerprint is already known dead; its
	// one caller memoizes the answer, so a miss costs at most one query per
	// fingerprint per process lifetime.
	AWSSSOTokenSpent(ctx context.Context, fingerprint string) (bool, error)
	// PruneAWSSSOSpentTokens deletes rows marked before cutoff, reporting the
	// count removed. Called from the reaper's existing per-tick advisory lock.
	PruneAWSSSOSpentTokens(ctx context.Context, cutoff time.Time) (int, error)
}

AWSSSOSpentTokenStore persists the AWS SSO refresh-token "spent" mark (internal/api/awssso_refresh.go's ssoRefreshSpent map) so it survives a daemon restart, through Store/PG rather than the secret/blob store since the mark is written precisely because that blob store failed.

type ActiveRunsAtPathReader added in v0.7.0

type ActiveRunsAtPathReader interface {
	ActiveRunsAtWorkspacePath(ctx context.Context, workspacePath string) ([]types.AgentRun, error)
}

ActiveRunsAtPathReader answers which OTHER non-terminal runs already operate on one host workspace path, checked on every run create. Its absence falls back to the unbounded ListRuns + an in-Go filter, SAFE (same answer, merely slower) since the warning is advisory and never blocks a launch.

type ActiveRunsByCreatorReader added in v0.7.5

type ActiveRunsByCreatorReader interface {
	ActiveRunsByCreator(ctx context.Context, createdBy, task, agent string) ([]types.AgentRun, error)
}

ActiveRunsByCreatorReader answers which of a person's runs of one task+agent are still non-terminal, so a new sign-in's supersede (supersedeCallerLoginRuns) can end them. Its absence has no fallback; PG implements it in production. task and agent, not "provider": a run row carries no provider column (it lives in the harness.login.start audit datum).

type ApprovalNotifyReader added in v0.8.6

type ApprovalNotifyReader interface {
	// ApprovalEscalations reads, in one query, the escalation state of every approval in ids that has
	// outbox rows. Approvals with none are absent from the map.
	ApprovalEscalations(ctx context.Context, ids []uuid.UUID, now time.Time) (map[uuid.UUID]types.ApprovalEscalation, error)
	// ApprovalNotifyChannelStats returns one row per channel that has outbox rows.
	ApprovalNotifyChannelStats(ctx context.Context, now time.Time) ([]types.ApprovalNotifyChannelStat, error)
}

ApprovalNotifyReader is the read side of the approval notification outbox that the console needs: where each pending approval sits on its escalation schedule, and each channel's delivery history. A capability interface like ApprovalsForRunsPager: an absent implementation leaves the projection and the status endpoint empty, never an error, since both only describe notifications.

type ApprovalsByRunCreatorPager added in v0.5.0

type ApprovalsByRunCreatorPager interface {
	ListApprovalsPageByRunCreator(ctx context.Context, createdBy string, stateFilter types.ApprovalState, p Page) ([]types.ApprovalRequest, error)
}

ApprovalsByRunCreatorPager is the ownership-scoped analogue of Pager.ListApprovalsPage: a member's GET /approvals (no ?run_id=) is scoped to approvals on runs THEY created, via a JOIN on agent_runs (approvals carry no created_by of their own). TRUST BOUNDARY: same fail-closed contract as RunsByCreatorPager — an absent implementation must never fall back to the unscoped list.

type ApprovalsByRunPager added in v0.7.0

type ApprovalsByRunPager interface {
	ListApprovalsPageByRun(ctx context.Context, runID uuid.UUID, stateFilter types.ApprovalState, p Page) ([]types.ApprovalRequest, error)
}

ApprovalsByRunPager is Pager.ListApprovalsPage narrowed to ONE run, the ?run_id= shape of GET /api/v1/approvals. Without it, a run-scoped poll falls back to fetching every approval the deployment has ever written and filtering in Go, a cost growing with deployment age since decided rows are never deleted.

Fail-safe like Pager (not fail-closed like ApprovalsByRunCreatorPager): ownership scoping happens before this is reached (getRunAuthorized), so the fallback is a performance question, not a privilege one.

type ApprovalsForRunsPager added in v0.8.0

type ApprovalsForRunsPager interface {
	// ListPendingApprovalsForRuns returns every state=PENDING approval whose
	// run_id is in runIDs — no other state, since only PENDING can be held or
	// contribute to Pending's count.
	ListPendingApprovalsForRuns(ctx context.Context, runIDs []uuid.UUID) ([]types.ApprovalRequest, error)
	// CountPendingApprovals returns how many approvals in the whole deployment
	// are state=PENDING — GET /me/attention's admin-view pending_approvals,
	// counted in the database rather than by listing every row.
	CountPendingApprovals(ctx context.Context) (int, error)
	// CountPendingApprovalsByRunCreator returns how many PENDING approvals are
	// raised on runs createdBy owns — GET /me/attention's user-view
	// pending_approvals, counted rather than listed.
	CountPendingApprovalsByRunCreator(ctx context.Context, createdBy string) (int, error)
}

ApprovalsForRunsPager is the attention projection's read surface — kept out of Store like every capability interface in this package, since widening Store would silently route a test double's unoverridden method to the unscoped behaviour. SECURITY: an absent implementation fails CLOSED at the api-layer call site (attention projection refuses rather than silently omitting `attention` from every run).

type AttachLease added in v0.8.6

type AttachLease struct {
	RunID     uuid.UUID
	HolderID  uuid.UUID
	Replica   string // the replica serving the holder's socket
	Principal string
	Source    string // "web" or "ssh"
	Since     time.Time
	Epoch     int64 // moves at each takeover of the row
	ExpiresAt time.Time
	Live      bool // read-only: ExpiresAt was ahead of the database's clock
}

AttachLease is one row of run_attach_leases. HolderID is the lease's identity: whatever writes into the terminal must still be the holder.

type AttachLeaseStore added in v0.8.6

type AttachLeaseStore interface {
	// AcquireAttachLease takes runID's lease for l.HolderID when it is free, has lapsed, or is
	// held by from (uuid.Nil: nobody in particular), for ttl. It reports whether it did.
	AcquireAttachLease(ctx context.Context, l AttachLease, from uuid.UUID, ttl time.Duration) (bool, error)
	// ReacquireAttachLease takes runID's lease back for l.HolderID, a holder that had it, when
	// the row is gone, has lapsed or is still l.HolderID's, for ttl. It never takes a live row of
	// another holder, a reservation for a take-over's taker included. It reports whether it did.
	ReacquireAttachLease(ctx context.Context, l AttachLease, ttl time.Duration) (bool, error)
	// RenewAttachLease extends holderID's lease by ttl, reporting whether holderID still held it.
	RenewAttachLease(ctx context.Context, runID, holderID uuid.UUID, ttl time.Duration) (bool, error)
	// HoldsAttachLease reports whether holderID holds runID's lease right now, unlapsed.
	HoldsAttachLease(ctx context.Context, runID, holderID uuid.UUID) (bool, error)
	// ReleaseAttachLease deletes the lease if holderID holds it, reporting whether it did.
	ReleaseAttachLease(ctx context.Context, runID, holderID uuid.UUID) (bool, error)
	// ReserveAttachLease is a take-over: it replaces the live lease held by from (uuid.Nil:
	// whoever holds it) with a reservation for taker, which only taker's own client may take, for
	// ttl, so no queued client of someone else gets the slot the take-over freed. It returns the
	// lease it replaced and the reservation's holder id; found is false when no live lease was
	// held by from.
	ReserveAttachLease(ctx context.Context, runID, from uuid.UUID, taker, replica string, ttl time.Duration) (prev AttachLease, reservation uuid.UUID, found bool, err error)
	// GetAttachLease returns runID's lease, lapsed or not (Live says which).
	GetAttachLease(ctx context.Context, runID uuid.UUID) (AttachLease, bool, error)
}

AttachLeaseStore is the attach lease record. Optional like RunOutputStore: the api layer type-asserts, and a store without it keeps the writer slot in the process.

type AttachTicket added in v0.5.0

type AttachTicket struct {
	RunID     uuid.UUID
	ActorType types.ActorType
	Principal string
	Role      string
	// Via is the delegated lane the ticket was minted on (#1142), nil
	// otherwise; the ticket lane replays it onto the rows it writes.
	Via *types.DelegationVia
	// AuthorizedAt is when the minting request was admitted, and Email the
	// verified email a session revoke may name ("" on the admin-token and local
	// lanes). Redemption checks both against the revoke cutoff (#1474). A zero
	// AuthorizedAt is a row written before the column existed: never exempt.
	AuthorizedAt time.Time
	Email        string
	// RevSeq is the revocation sequence the minting credential recorded (ReadRevocationSeq); 0 is a
	// row from before it existed, ordered on AuthorizedAt.
	RevSeq int64
}

AttachTicket is what one redeemed single-use WS attach ticket carries: the run it's bound to, the minting principal (audit attribution), and that principal's role at mint time. The ?ticket= WS lane bypasses humanOrAdminAuth entirely, so this stamped role is the only signal available to re-check owner-or-admin at consume time.

type AuditChainStatus added in v0.6.0

type AuditChainStatus struct {
	// OK is true when every chained row re-hashed to its stored row_hash and
	// linked to its predecessor.
	OK bool `json:"ok"`
	// Checked is how many chained rows the sweep walked.
	Checked int64 `json:"checked"`
	// Legacy is how many rows carry NO hash and sit BELOW the first chained
	// row (pre-migration-0047) — expected, not a failure. A hashless row
	// ABOVE that prefix is a break instead (auditChainWalk.step).
	Legacy int64 `json:"legacy"`
	// FirstSeq/HeadSeq bound the chained range (both 0 when Checked is 0).
	FirstSeq int64 `json:"first_seq"`
	HeadSeq  int64 `json:"head_seq"`
	// HeadHash is the row_hash of the newest chained row: the value to compare
	// against what a SIEM recorded off the sink stream.
	HeadHash string `json:"head_hash,omitempty"`
	// AnchorSeq is the last seq of the newest attested retention drop the sweep started from: the first
	// retained row must chain to that drop's recorded tail. 0 when no partition was ever dropped.
	AnchorSeq int64 `json:"anchor_seq,omitempty"`
	// BrokenSeq/Reason are set only when OK is false.
	BrokenSeq int64  `json:"broken_seq,omitempty"`
	Reason    string `json:"reason,omitempty"`
}

AuditChainStatus is one verification sweep's verdict (migration 0047). OK is the only field an alert should key on; BrokenSeq/Reason name the FIRST row that failed, not every row after it, since one edited row makes every later link mismatch too and listing them all buries the edit.

func VerifyAuditChainTx added in v0.9.0

func VerifyAuditChainTx(ctx context.Context, tx pgx.Tx) (AuditChainStatus, error)

VerifyAuditChainTx is VerifyAuditChain on a transaction the caller owns, for a caller that has one connection and no pool to give it (wardynd -recovery-rehearse). The caller begins it repeatable-read and read-only.

type AuditChainVerifier added in v0.6.0

type AuditChainVerifier interface {
	VerifyAuditChain(ctx context.Context) (AuditChainStatus, error)
}

AuditChainVerifier is the OPTIONAL store capability behind GET /api/v1/audit/chain/verify: a test fake or non-Postgres store has no chain, and the handler answers 501 rather than reporting a chain it never wrote as clean.

Operator-invoked, never run at boot (O(whole audit log)). Not a substitute for comparing HeadHash against an off-box copy: an actor who can rewrite one row can usually rewrite and re-chain the tail, which then verifies clean. See docs/OPERATIONS.md.

type AuditDeliveryStatus added in v0.9.0

type AuditDeliveryStatus struct {
	Destination       string
	HeadSeq           int64
	AckedSeq          int64
	Resets            int64
	AckedAt           *time.Time
	OldestUndelivered *time.Time
	LastError         string
	Halted            bool
}

AuditDeliveryStatus is one destination's checkpoint plus the head it lags behind, the whole input the delivery metrics emit. AckedAt and OldestUndelivered are nil when nothing has been acknowledged yet and when the checkpoint is caught up.

type AuditFilter added in v0.4.4

type AuditFilter struct {
	Since        time.Time       // events at or after this instant
	Until        time.Time       // events strictly before this instant
	Action       string          // exact action, e.g. "run.kill"
	ActionPrefix string          // action family, e.g. "credential." or "egress."
	Actor        string          // exact principal, e.g. "alice@corp.example" (human evidence)
	ActorType    types.ActorType // human / agent / system
	Outcome      string          // success / failure / warn
	Origin       string          // AuditOriginDevice or AuditOriginOrganisation
	// ActorAlt is a second stored actor that also matches Actor: the "subject:<id>"
	// a row holds for the person Actor names under WARDYN_AUDIT_SEAL=full. Rows
	// written before it was turned on keep the name, so both must match.
	ActorAlt string
	// Target is the exact target column, e.g. a person's principal for a person.*
	// row. TargetAlt is its second stored form, as ActorAlt is Actor's: the
	// "subject:<id>" a person-target row holds under WARDYN_AUDIT_SEAL=full.
	Target    string
	TargetAlt string
	// DataContains is a JSON object the row's data must contain (Postgres
	// `data @> $n::jsonb`): every key present with an equal value, objects
	// recursively. A string, not a map, so the filter stays comparable (IsZero).
	DataContains string
}

AuditFilter narrows the audit feed to answer the questions a bigger window never can — "what happened between 14:00 and 16:00", "every secret.write this quarter", "every failure" — where the answer is 40 events inside 200k. The zero value matches everything, so an unfiltered read keeps taking the plain QueryRecentAuditEventsPage / QueryAuditEventsPage path unchanged.

It renders TWO ways from one declaration — SQL (where) for the Pager path and Go (Matches) for the fetch-all fallback — because a filter applied on only one of them returns silently UNFILTERED events on the other, which in a governance product is a quiet wrong answer. Keep the two in step; they are deliberately adjacent.

Actor IS filterable: for a HUMAN event it is the operator/member principal (e.g. "alice@corp.example"), so ?actor= answers "everything developer X did" — the per-principal evidence a vendor/compliance question needs and that no wider window yields. (An earlier note dismissed actor as filterable because for an AGENT event it is the per-run SPIFFE ID and would merely restate ?run_id= — but that reasoning only ever considered agent events; a human's approvals, kills, and policy writes all carry their principal here, and that is exactly what the finding asks for.) The agent-side "everything under X's runs" view still needs the agent_runs.created_by join, which does not fit this table-local filter.

func (AuditFilter) IsZero added in v0.4.4

func (f AuditFilter) IsZero() bool

IsZero reports whether the filter narrows nothing.

func (AuditFilter) Keep added in v0.4.4

func (f AuditFilter) Keep(events []types.AuditEvent) []types.AuditEvent

Keep returns the events matching f, in order. A zero filter returns events unchanged (no copy).

func (AuditFilter) Matches added in v0.4.4

func (f AuditFilter) Matches(ev types.AuditEvent) bool

Matches reports whether ev satisfies every set predicate.

type AuditPartitionExporter added in v0.8.6

type AuditPartitionExporter interface {
	// ExportAuditPartition reads one closed partition in seq order from ONE snapshot. header is called
	// once, before any row, with the manifest of that snapshot; row once per row. ErrNotFound when name
	// is not a partition of the audit log, ErrAuditPartitionOpen when it can still receive rows; in
	// both cases neither callback ran. An error from either callback stops the read and is returned.
	ExportAuditPartition(ctx context.Context, name string, header func(PartitionManifest) error, row func(AuditPartitionRow) error) error
}

AuditPartitionExporter is the OPTIONAL store capability behind GET /audit/export?partition=. A test fake or non-Postgres store has no partitions.

type AuditPartitionRow added in v0.8.6

type AuditPartitionRow struct {
	Seq        int64
	RecordedUS int64
	TimeUS     int64
	ID         uuid.UUID
	RunID      *uuid.UUID
	ActorType  string
	Actor      string
	Action     string
	Target     string
	Outcome    string
	SourceIP   string
	// Data is the jsonb's own text form, the exact bytes audit_row_hash embeds; nil for SQL NULL.
	Data *string
	// PrevHash and RowHash are nil for a row from before the chain began.
	PrevHash *string
	RowHash  *string
	// FoldHash is what the digest folds: RowHash, or for a hashless row the hash it would have had.
	FoldHash string
}

AuditPartitionRow is one stored row of a partition, exactly as the table holds it: the columns audit_row_hash covers, the hashes, and the position.

type AuditRetention added in v0.8.6

type AuditRetention interface {
	AuditRetentionStatus(ctx context.Context) (AuditRetentionStatus, error)
	// DropAuditPartition calls audit_retention_drop; actorType is the type the chained event is recorded
	// under (the sweeper's name is always the system actor). A refusal is *AuditRetentionRefused; a name that is
	// not a partition of the audit log is ErrNotFound.
	DropAuditPartition(ctx context.Context, partition, digest, actor string, actorType types.ActorType) (AuditRetentionDrop, error)
	// AutodropAuditPartition drops the oldest partition if it is eligible, computing its digest in the
	// database, as the system actor. ok is false (and nothing was dropped) when it is not.
	AutodropAuditPartition(ctx context.Context) (drop AuditRetentionDrop, ok bool, err error)
	// SetAuditRetentionPolicy calls audit_retention_set_policy.
	SetAuditRetentionPolicy(ctx context.Context, days int) (AuditRetentionPolicyChange, error)
	// EnsureAuditPartitions calls audit_ensure_partitions and returns how many months it created.
	EnsureAuditPartitions(ctx context.Context, months int) (int, error)
	// AuditPartitionsAhead is how many months past the current one already have a partition.
	AuditPartitionsAhead(ctx context.Context) (int, error)
}

AuditRetention is the OPTIONAL store capability behind the retention endpoints, the daily partition ensure and the leader sweeper's autodrop: a test fake or non-Postgres store has no partitions.

type AuditRetentionDrop added in v0.8.6

type AuditRetentionDrop = types.AuditRetentionDrop

The retention wire types live in internal/types, shared with pkg/client; these are their store-side names.

type AuditRetentionPartition added in v0.8.6

type AuditRetentionPartition = types.AuditRetentionPartition

The retention wire types live in internal/types, shared with pkg/client; these are their store-side names.

type AuditRetentionPolicy added in v0.8.6

type AuditRetentionPolicy = types.AuditRetentionPolicy

The retention wire types live in internal/types, shared with pkg/client; these are their store-side names.

type AuditRetentionPolicyChange added in v0.8.6

type AuditRetentionPolicyChange = types.AuditRetentionPolicyChange

The retention wire types live in internal/types, shared with pkg/client; these are their store-side names.

type AuditRetentionRefused added in v0.8.6

type AuditRetentionRefused struct {
	Reason    string
	Partition string
}

AuditRetentionRefused is a drop the database refused. Reason is one of the Retention* constants.

func (*AuditRetentionRefused) Error added in v0.8.6

func (e *AuditRetentionRefused) Error() string

type AuditRetentionStatus added in v0.8.6

type AuditRetentionStatus = types.AuditRetentionStatus

The retention wire types live in internal/types, shared with pkg/client; these are their store-side names.

type AuditSplitRange added in v0.8.6

type AuditSplitRange struct {
	Name  string
	Rows  int64
	SeqLo int64
	SeqHi int64
	// RecordedLo and RecordedHi are the smallest and largest recorded_at the range's rows now carry.
	RecordedLo time.Time
	RecordedHi time.Time
	// Digest is the range's partition digest (audit_partition_digest), equal to the fold over its source rows.
	Digest string
}

AuditSplitRange is one seq-contiguous range the legacy partition was split into.

type AuditSplitRefused added in v0.8.6

type AuditSplitRefused struct {
	Reason string
	Detail string
}

AuditSplitRefused is a legacy split that was not attempted, or was undone, for a named reason.

func (*AuditSplitRefused) Error added in v0.8.6

func (e *AuditSplitRefused) Error() string

type AuditSplitResult added in v0.8.6

type AuditSplitResult struct {
	Rows    int64
	Checked int64
	Legacy  int64
	Ranges  []AuditSplitRange
}

AuditSplitResult is what one split did. Checked and Legacy are the chain verifier's counts, which were the same before the split and after it: the split removed no row from the chain.

func SplitLegacyAudit added in v0.8.6

func SplitLegacyAudit(ctx context.Context, tx pgx.Tx) (AuditSplitResult, error)

SplitLegacyAudit splits the legacy partition, which holds all of the audit history from before 0.8.6 under one recorded_at, into seq-contiguous ranges that ordinary retention can drop one at a time. It is the one-shot offline tool behind `wardynd -audit-split-legacy`.

It runs on tx, a read-write transaction on the migrator's connection that the caller commits only when this returns nil. The caller has already made sure no writer is running; this takes db.AuditPartitionLockKey and then db.AuditChainLockKey for the transaction (the order the retention drop takes them), so a writer that slipped in is held out, not interleaved.

A range is not a month of the rows' own time. The boundaries are where the running maximum of "time" first crosses a month, walking the rows in seq order, and each row's new recorded_at is that running maximum (never past the cutover). A spool replay writes an old "time" late, so splitting by "time" would scatter a month through the chain and leave an interior link behind when its range was dropped; the running maximum cannot go backwards, so every range is a contiguous prefix of chain order, and its largest recorded_at is the largest "time" it holds. row_hash does not cover recorded_at, so no hash changes.

Every range is copied to a new table, attached, and proved against its source (the same fold audit_partition_digest computes, and a row-for-row comparison), the legacy table is dropped, the ranges are written to the expected manifest and a 'split' anchor each, and the chain is verified again. The verification must see exactly the rows it saw before.

type BrandingStore added in v0.8.0

type BrandingStore interface {
	// GetBranding returns the record, or ErrNotFound for an unbranded console.
	GetBranding(ctx context.Context) (types.Branding, error)
	// PutBranding replaces the record. keepLogo leaves the stored logo as it
	// is and ignores b.Logo/b.LogoType.
	PutBranding(ctx context.Context, b types.Branding, keepLogo bool) (types.Branding, error)
	// DeleteBranding removes the record; false when there was none.
	DeleteBranding(ctx context.Context) (bool, error)
	// empty logo removes it. false when there is no record to attach it to.
	SetBrandingLogo(ctx context.Context, logo []byte, logoType string, fromFile bool, updatedBy string) (bool, error)
}

BrandingStore is the OPTIONAL store capability behind console branding, optional for DeviceStore's reason: a fake without it answers the public read as unbranded and refuses a write, never a silent no-op.

type CapabilityGrantsForPager added in v0.8.0

type CapabilityGrantsForPager interface {
	ListCapabilityGrantsForPage(ctx context.Context, users, groups []string, userType string, p Page) ([]types.CapabilityGrant, error)
}

CapabilityGrantsForPager is the scoped analogue of Pager for GET /me/capabilities, same safe-fallback posture as GrantsByRunPager; exists for the uniform list-route contract (?limit=&offset=+X-Wardyn-Truncated), not because grant lists routinely truncate.

type ComponentRestrictionLifter added in v0.8.9

type ComponentRestrictionLifter interface {
	// LiftComponentRestriction deletes the restriction row of the component id, in one transaction
	// under the availability lock, after checking that an org row (owner "") has that id. ErrNotFound
	// when none does, and then nothing is written: lifting a deleted id's restriction would open an id
	// that must stay closed.
	LiftComponentRestriction(ctx context.Context, id uuid.UUID, by string) error
}

ComponentRestrictionLifter lifts an organisation component's restriction only while the component exists. Optional beside ComponentStore: a store without it lifts like any other kind.

type ComponentStore added in v0.8.9

type ComponentStore interface {
	// CreateComponent inserts c at version 1 under the id the CALLER chose, and never replaces a
	// row: ErrConflict when the id exists or the owner already has a component of that name. The
	// caller choosing the id is what lets it record who may use an org component before the row
	// exists, so no reader can find the row first. Timestamps are the database's.
	CreateComponent(ctx context.Context, c types.Component) (types.Component, error)
	// UpdateComponent replaces the name and definition of the row with c's id AND owner and moves
	// its version by one. ErrNotFound when that owner has no such row; ErrConflict on a name the
	// owner already uses.
	UpdateComponent(ctx context.Context, c types.Component) (types.Component, error)
	// DeleteComponent removes owner's row and returns it, or ErrNotFound. Snapshots of it on
	// runs already launched are kept.
	DeleteComponent(ctx context.Context, id uuid.UUID, owner string) (types.Component, error)
	GetComponent(ctx context.Context, id uuid.UUID, owner string) (types.Component, error)
	// ListComponents returns owner's rows by name.
	ListComponents(ctx context.Context, owner string) ([]types.Component, error)
	// ListComponentsByIDs returns the rows among ids that are the organisation's or owner's own.
	// An id it does not return is absent or another person's; the two are not told apart.
	ListComponentsByIDs(ctx context.Context, owner string, ids []uuid.UUID) ([]types.Component, error)
	CountComponents(ctx context.Context, owner string) (int, error)
	// PutRunComponents writes run runID's snapshot, once, in one transaction; each row's position
	// in comps is its ordinal, and its RunID, Ordinal and Erased fields are not read. ErrNotFound
	// when the run does not exist; ErrConflict when it already has a snapshot, which is never
	// replaced — its rows are the run's authorization record. An empty comps is refused: a run
	// launched without components writes no snapshot, so the caller does not call this.
	PutRunComponents(ctx context.Context, runID uuid.UUID, comps []types.RunComponent) error
	// ListRunComponents returns a run's snapshot in request order, erased rows included (Erased,
	// with only SelfDefined left beside the key); empty for a run launched without components.
	// It is what revive re-checks: an org row (SelfDefined false) needs its ComponentID's grant,
	// any SelfDefined row the custom-component feature.
	ListRunComponents(ctx context.Context, runID uuid.UUID) ([]types.RunComponent, error)
	// DeleteComponentsByOwner and EraseRunComponentsByOwner are a person's erasure. The first
	// deletes the components they saved. The second clears the descriptive content of the
	// snapshot rows of what they defined, across every run, and KEEPS each row as that run's
	// authorization tombstone. Each returns how many rows it changed and refuses owner "" — the
	// organisation's rows are no person's.
	DeleteComponentsByOwner(ctx context.Context, owner string) (int, error)
	EraseRunComponentsByOwner(ctx context.Context, owner string) (int, error)
	// EraseRunComponentsOfRun clears, on run runID only, what EraseRunComponentsByOwner clears:
	// a create that finds a saved component gone after its snapshot write leaves no content
	// behind. It returns how many rows it changed and refuses owner "".
	EraseRunComponentsOfRun(ctx context.Context, runID uuid.UUID, owner string) (int, error)
}

ComponentStore persists custom components (migration 0136_components) and the snapshot of the ones a run launched with (migration 0137_run_components); bodies in store_components.go. Optional like RunLayoutStore, so the doubles that implement Store need not grow: callers type-assert. A caller that finds it absent must REFUSE a request that names a component, never drop the component and carry on.

Owner "" is the organisation's rows; any other owner is one person's principal. Every read and write of a component is scoped to an owner, so another person's row answers exactly as an absent one.

type DecidedAlwaysEgressPager added in v0.9.0

type DecidedAlwaysEgressPager interface {
	// ListDecidedAlwaysEgressPage returns up to limit egress_domain approvals in
	// state whose decision scope is `always`, newest request first, strictly
	// after the keyset (afterRequestedAt, afterID); a nil afterID starts at the
	// newest row. Each row carries only ID, RunID, Kind, RequestedScope, State,
	// RequestedAt, DecidedAt and DecisionScope: the reason and the other decided
	// columns are never read.
	ListDecidedAlwaysEgressPage(ctx context.Context, state types.ApprovalState, afterRequestedAt time.Time, afterID uuid.UUID, limit int) ([]types.ApprovalRequest, error)
}

DecidedAlwaysEgressPager is the read behind the leader-term heal of `always` egress verdicts (api.ReconcileWorkspaceEgressDecisions). A capability interface like ApprovalsForRunsPager, asserted on the approval service.

type DelegateReader added in v0.9.0

type DelegateReader interface {
	GetDelegateByID(ctx context.Context, id uuid.UUID) (types.Delegate, error)
}

DelegateReader reads one portal by id whatever its state, so the pool queue's start can tell a portal that was revoked while a run waited from one that still stands. Separate from DelegateStore for the same reason that interface is optional: a test double that implements one need not the other.

type DelegateStore added in v0.8.0

type DelegateStore interface {
	CreateDelegate(ctx context.Context, d types.Delegate, raw string) (types.Delegate, error)
	GetDelegateByRaw(ctx context.Context, raw string) (types.Delegate, error)
	ListDelegates(ctx context.Context) ([]types.Delegate, error)
	RevokeDelegate(ctx context.Context, id uuid.UUID, now time.Time) (types.Delegate, error)
	MintDelegatedToken(ctx context.Context, t types.DelegatedToken, raw string, now time.Time) (types.DelegatedToken, error)
	GetDelegatedTokenByRaw(ctx context.Context, raw string, now time.Time) (types.DelegatedToken, error)
	GetDelegatedTokenByID(ctx context.Context, id uuid.UUID, now time.Time) (types.DelegatedToken, error)
}

DelegateStore is optional, type-asserted like DeviceStore: without it the exchange and delegated lane fail closed instead of silently no-op.

Authenticating reads filter revoked/expired rows in their WHERE, so revoked, expired, unknown, and mismatched credentials all answer ErrNotFound — the boundary gives no oracle signal.

type DeprovisionFailure added in v0.8.6

type DeprovisionFailure struct {
	IdentityID uuid.UUID
	Kind       string
	Step       string
	LastError  string
}

DeprovisionFailure is a ledger row of a suspension or purge that is still pending and has failed at least once: the step, and the error its last attempt ended on. The console's SCIM card lists them.

type DeprovisionJob added in v0.8.6

type DeprovisionJob struct {
	IdentityID uuid.UUID
	Kind       string
	Step       string
	Target     string
	Done       bool
	Attempts   int
	LastError  string
	Detail     map[string]int
}

DeprovisionJob is one row of the step ledger.

type DeviceIngestResult added in v0.8.0

type DeviceIngestResult struct {
	Accepted int
	Reset    bool
}

DeviceIngestResult reports what one IngestDeviceAudit call did. Accepted is how many rows were newly appended: 0 on any refusal (whole batch is one transaction) and also on an idempotent retry. Reset is true when accepted rows began with a genesis (empty-PrevHash) row while the device already had a recorded chain — a "device chain reset", accepted rather than refused.

type DeviceStore added in v0.8.0

type DeviceStore interface {
	MintEnrolmentToken(ctx context.Context, token string, t types.DeviceEnrolmentToken) (types.DeviceEnrolmentToken, error)
	ConsumeEnrolmentToken(ctx context.Context, token string, now time.Time) (types.DeviceEnrolmentToken, bool, error)
	ListEnrolmentTokens(ctx context.Context, now time.Time) ([]types.DeviceEnrolmentToken, error)
	RevokeEnrolmentToken(ctx context.Context, id uuid.UUID, now time.Time) (types.DeviceEnrolmentToken, error)
	CreateDevice(ctx context.Context, d types.Device, raw string) (types.Device, error)
	GetDeviceByRaw(ctx context.Context, raw string) (types.Device, error)
	TouchDevice(ctx context.Context, id uuid.UUID, now time.Time) error
	ListDevices(ctx context.Context) ([]types.Device, error)
	RevokeDevice(ctx context.Context, id uuid.UUID, now time.Time) (types.Device, error)
	IngestDeviceAudit(ctx context.Context, deviceID uuid.UUID, peer string, rows []types.FederatedAuditEvent) (DeviceIngestResult, error)
	ListAuditEventsAfterSeq(ctx context.Context, seq int64, limit int) ([]types.FederatedAuditEvent, error)
	GetFederationCursor(ctx context.Context) (seq int64, rowHash string, err error)
	SetFederationCursor(ctx context.Context, seq int64, rowHash string) error
}

DeviceStore is the optional store capability behind hybrid enrolment and audit federation (docs/design/0.8/PLAN.md, epic #78). Optional like Pager and AuditChainVerifier: a test fake or future non-Postgres store implements neither, so mounting routes must type-assert and answer 501/401 rather than silently no-op. SECURITY (fail-closed): nothing here has a default no-op on Store, so a skipped assertion is a compile error and a failed one has an explicit absent branch.

GetDeviceByRaw's WHERE carries `revoked_at IS NULL` so a revoked credential is not an oracle.

type DueIdentity added in v0.9.0

type DueIdentity struct {
	ID        uuid.UUID
	Suspended bool
}

DueIdentity is one identity PurgeDueIdentities lists. Suspended says the identity's current deactivation is its own suspension's (ownSuspension). One without that proof was deactivated alongside another identity's suspension, or by a version that did not record it, and the sweeper does not purge it.

type EndedKept added in v0.8.0

type EndedKept struct {
	LostAt, KeptAfter, Now time.Time
}

EndedKept is the condition a write on a run kept by its OWN end (LostEnded) lands under during its files grace: the row must still carry the exact ended mark decided on (LostAt), after KeptAfter (Now less the ended-run grace). A grace that runs out between check and write refuses the write; nothing here moves the mark, so no write renews the grace. A revive also needs the run's end to be after Now.

type ErrGovernanceChangeNotPending added in v0.8.6

type ErrGovernanceChangeNotPending struct {
	State  string
	Lapsed bool
}

ErrGovernanceChangeNotPending is a decision's refusal when the change is no longer pending. State is what it is now; Lapsed is true when this very decision found it past expires_at and moved it to expired.

func (*ErrGovernanceChangeNotPending) Error added in v0.8.6

type ErrGovernanceChangePending added in v0.8.6

type ErrGovernanceChangePending struct{ ID uuid.UUID }

ErrGovernanceChangePending is a proposal's refusal when a live change already holds the target: ID is that change.

func (*ErrGovernanceChangePending) Error added in v0.8.6

type ErrProfileHasChildren added in v0.8.6

type ErrProfileHasChildren struct{ Names []string }

ErrProfileHasChildren is DeleteGovernanceProfile's refusal when other profiles still compose on the row: Names are those children, sorted.

func (*ErrProfileHasChildren) Error added in v0.8.6

func (e *ErrProfileHasChildren) Error() string

type ExpiredGovernanceChange added in v0.8.6

type ExpiredGovernanceChange struct {
	ID        uuid.UUID
	TargetKey string
}

ExpiredGovernanceChange is a lapsed change a proposal moved to expired: its id and its own target key, which differs from the proposal's when it was a create of the same name.

type GovernanceDecideFunc added in v0.8.6

type GovernanceDecideFunc func(q Querier, ch types.GovernanceChange) error

GovernanceDecideFunc runs inside the decision transaction with the change as it was locked. A nil error commits the decision; ErrGovernanceChangeStale commits the change as stale; any other error rolls everything back, the change still pending.

type GovernanceDecision added in v0.8.6

type GovernanceDecision struct {
	// To is types.GovernanceChangeApplied or types.GovernanceChangeRejected.
	To      string
	By      string
	ByEmail string
	Reason  string
}

GovernanceDecision is what a decision records on the change row.

type GovernanceProfileBuild added in v0.8.6

type GovernanceProfileBuild func(all []types.GovernanceProfile) (types.GovernanceProfile, error)

GovernanceProfileBuild produces the row a graph write stores, given every profile as the write transaction sees it under the graph lock. A refusal it returns aborts the write unchanged.

type GrantsByRunPager added in v0.8.0

type GrantsByRunPager interface {
	ListGrantsByRunPage(ctx context.Context, runID uuid.UUID, p Page) ([]types.CredentialGrant, error)
}

GrantsByRunPager is the scoped analogue of Pager for GET /runs/{id}/grants: ListGrantsByRun is already WHERE run_id=$1, so an absent implementation is safe (not fail-closed) — the fallback windows the same scoped rows in Go.

type IdentityGuard added in v0.8.6

type IdentityGuard struct {
	Principal string
	Epoch     int64
}

IdentityGuard names the owner a write acts for and the authority epoch its caller was admitted under. Epoch < 0 means the caller carries no epoch (an API token, a request-free caller): only a deactivation refuses.

type IdentityUnbinder added in v0.9.0

type IdentityUnbinder interface {
	GetIdentity(ctx context.Context, id uuid.UUID) (PrincipalIdentity, error)
	// UnbindIdentity clears the principal of identity id, so the next sign-in binds the row afresh, and
	// cuts the released principal's sessions as a suspension does. principal is the one the caller read the
	// row bound to: a row bound to another by the time it is locked is ErrIdentityRebound. Refused, with
	// nothing written: a deactivated or purged row (ErrIdentityDeactivated), a row with no principal
	// (ErrConflict), a row with no object id (ErrIdentityNotRebindable), a principal that still holds an API
	// token, SSH key or non-terminal run, sponsors a non-retired service identity or is the target of a
	// pending invitation (*PrincipalInUseError), a missing row (ErrNotFound). Once the row is
	// re-bound, deactivation and the kill sweep reach only the new principal, so the old one must hold
	// nothing. The epoch, the deactivation columns and the SCIM linkage are untouched.
	UnbindIdentity(ctx context.Context, id uuid.UUID, principal string) error
}

IdentityUnbinder is the optional store seam behind POST /admin/identities/{id}/unbind.

type IdentityUpdate added in v0.8.6

type IdentityUpdate struct {
	ExternalID string
	UserName   string
	Emails     []string
	// Reactivate clears the deactivation and the purge schedule on the identity and on every row
	// bound to the same principal. A purged identity is ErrIdentityPurged.
	Reactivate bool
	// PeoplePrincipals are the people rows whose deactivation a reactivation clears.
	PeoplePrincipals []string
}

IdentityUpdate is one SCIM write to an identity row. A projection update never touches a binding column; ExternalID is set only when the caller has already checked it against the binding.

type InvitationClaim added in v0.9.0

type InvitationClaim struct {
	ID     uuid.UUID
	Target string
	Now    time.Time
	Token  types.APIToken
	Raw    string
}

InvitationClaim is one claim: the invitation, the only principal that may claim it, the clock that decides expiry, and the token to mint with its plaintext.

type InvitationClaimed added in v0.9.0

type InvitationClaimed struct {
	Invitation types.Invitation
	Token      types.APIToken
	Retired    *types.APIToken
}

InvitationClaimed is what a claim committed. Retired is the admin-minted token the invitation replaced, nil when it had none or that token was no longer live.

type InvitationStore added in v0.9.0

type InvitationStore interface {
	// CreateInvitation inserts inv, or returns ErrInvitationCap when maxPending invitations are
	// already pending for inv.Target at now.
	CreateInvitation(ctx context.Context, inv types.Invitation, maxPending int, now time.Time) (types.Invitation, error)
	// GetInvitation is the row with exactly id, or ErrNotFound. A non-empty target also has to match:
	// the claim reads by id AND target, so another person's id is ErrNotFound.
	GetInvitation(ctx context.Context, id uuid.UUID, target string) (types.Invitation, error)
	// ListInvitations returns invitations newest first, narrowed to target and state when set.
	ListInvitations(ctx context.Context, target string, state types.InvitationState, now time.Time, p Page) ([]types.Invitation, error)
	// RevokeInvitation revokes a pending invitation: ErrNotFound for no such id, ErrInvitationNotPending otherwise.
	RevokeInvitation(ctx context.Context, id uuid.UUID, by string, now time.Time) (types.Invitation, error)
	// ClaimInvitation, in one transaction, inserts c.Token, marks the invitation claimed by it and
	// revokes the token the invitation replaces. ErrInvitationNotPending leaves nothing behind.
	ClaimInvitation(ctx context.Context, c InvitationClaim) (InvitationClaimed, error)
}

InvitationStore holds person invitations (migration 0157, #1508). Optional, like PersonStore: the API answers 501 on a store without it.

type JobKey added in v0.8.6

type JobKey struct{ Step, Target string }

JobKey names a ledger row within one identity and kind.

type LaunchIntentRecord added in v0.9.0

type LaunchIntentRecord struct {
	RunID     uuid.UUID
	Title     string
	Shape     string
	CreatedAt time.Time
	Document  json.RawMessage
}

LaunchIntentRecord is one kept intent. Title is the run's title now; Shape is the document's intent.shape. Document is set by GetLaunchIntent only.

type LaunchIntentStore added in v0.9.0

type LaunchIntentStore interface {
	// ListLaunchIntents returns createdBy's intents, newest first, at most limit, without documents.
	ListLaunchIntents(ctx context.Context, createdBy string, limit int) ([]LaunchIntentRecord, error)
	// GetLaunchIntent returns the run's intent when createdBy created the run; ErrNotFound otherwise.
	GetLaunchIntent(ctx context.Context, runID uuid.UUID, createdBy string) (LaunchIntentRecord, error)
	// DeleteLaunchIntentsCreatedBy erases createdBy's intents and returns how many. It waits for every
	// launch of theirs that is writing one, and a launch that reaches the write after it began keeps none.
	DeleteLaunchIntentsCreatedBy(ctx context.Context, createdBy string) (int, error)
}

LaunchIntentStore reads and erases launch intents (migration 0171_run_launch_intents): the setup a person launched a run with, kept as a kind Run document for Run again. It has no write method: CreateRun and CreateRunUnderCap insert AgentRun.LaunchIntent in the run row's own transaction, so a run and its intent are written together or not at all. Optional like ComponentStore: callers type-assert.

type LeaverStore added in v0.8.6

type LeaverStore interface {
	GetIdentity(ctx context.Context, id uuid.UUID) (PrincipalIdentity, error)
	SearchIdentities(ctx context.Context, issuer, attr, value string, limit int) ([]PrincipalIdentity, error)
	IdentitiesByAlias(ctx context.Context, issuer, valueLower string) ([]PrincipalIdentity, error)
	IdentityAliasValues(ctx context.Context, id uuid.UUID) ([]string, error)
	CreateScimIdentity(ctx context.Context, issuer, tenantID, objectID string, u IdentityUpdate, now time.Time) (PrincipalIdentity, bool, error)
	ApplyIdentityUpdate(ctx context.Context, id uuid.UUID, u IdentityUpdate, now time.Time) (PrincipalIdentity, error)
	SuspendIdentity(ctx context.Context, p SuspendPlan) (SuspendResult, error)
	ListNonTerminalRunsBy(ctx context.Context, createdBy string) ([]types.AgentRun, error)
	EnsureDeprovisionJobs(ctx context.Context, identityID uuid.UUID, kind string, keys []JobKey) error
	ListDeprovisionJobs(ctx context.Context, identityID uuid.UUID, kind string) ([]DeprovisionJob, error)
	FinishDeprovisionJob(ctx context.Context, identityID uuid.UUID, kind string, k JobKey, detail map[string]int) error
	FailDeprovisionJob(ctx context.Context, identityID uuid.UUID, kind string, k JobKey, cause error) error
	ReopenDeprovisionJob(ctx context.Context, identityID uuid.UUID, kind string, k JobKey) error
	MarkIdentityPurged(ctx context.Context, id uuid.UUID, requireDue bool) (bool, error)
	PurgeDueIdentities(ctx context.Context, limit int) ([]DueIdentity, error)
	PendingLeavers(ctx context.Context, idleFor time.Duration, limit int) ([]PendingLeaver, error)
	DeleteUserSubjectRows(ctx context.Context, id uuid.UUID, principals, emails []string) (grants, assignments, kept int64, err error)
	HeldEmails(ctx context.Context, id uuid.UUID, principals, emails []string) ([]string, error)
	ListDeactivatedIdentities(ctx context.Context, limit int) ([]PrincipalIdentity, error)
	ListDeprovisionFailures(ctx context.Context, limit int) ([]DeprovisionFailure, error)
}

LeaverStore is optional, like PrincipalIdentityStore: callers type-assert it.

type LoginIdentity added in v0.8.6

type LoginIdentity struct {
	Principal string
	Issuer    string
	TenantID  string
	ObjectID  string
	Email     string
}

LoginIdentity is what an approved sign-in tells the store.

type LoginLocker added in v0.8.0

type LoginLocker interface {
	LockLoginSupersede(ctx context.Context, actor string) (release func(), err error)
}

LoginLocker serializes one person's sign-in launches, and the credential capture that follows one, against every other replica — the piece the deterministic tie-break in api.supersedeOlderLoginRuns couldn't supply. Its absence falls back UNLOCKED (as serialized as 0.7.8 was); PG implements it in production.

Keyed by ACTOR, not credential scope: under the `shared` roster every sign-in resolves to the same empty scope owner, so a scope-keyed lock would serialize the whole deployment without serializing what it needs to.

type MemRunPATs added in v0.8.2

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

MemRunPATs is RunPATStore in memory, for the tests of every lane that creates or revokes a token and has no database.

func NewMemRunPATs added in v0.8.2

func NewMemRunPATs() *MemRunPATs

NewMemRunPATs returns an empty MemRunPATs.

func (*MemRunPATs) InsertRunPAT added in v0.8.2

func (m *MemRunPATs) InsertRunPAT(_ context.Context, p RunPAT) error

func (*MemRunPATs) LastRunPAT added in v0.8.2

func (m *MemRunPATs) LastRunPAT(_ context.Context, owner, providerRowID string) (RunPAT, bool, error)

func (*MemRunPATs) ListRunPATs added in v0.8.2

func (m *MemRunPATs) ListRunPATs(_ context.Context, runID uuid.UUID) ([]RunPAT, error)

func (*MemRunPATs) ListUnrevokedRunPATs added in v0.8.2

func (m *MemRunPATs) ListUnrevokedRunPATs(_ context.Context, f RunPATFilter) ([]RunPAT, error)

func (*MemRunPATs) MarkRunPATRevoked added in v0.8.2

func (m *MemRunPATs) MarkRunPATRevoked(_ context.Context, runID, authorizationID uuid.UUID, reason, lastError string) (bool, error)

func (*MemRunPATs) NoteRunPATRevokeFailed added in v0.8.2

func (m *MemRunPATs) NoteRunPATRevokeFailed(_ context.Context, runID, authorizationID uuid.UUID, lastError string) error

type MemberDriveStore added in v0.9.0

type MemberDriveStore interface {
	// CreateMemberDrive inserts d for d.OwnerSubject (non-empty) unless the owner
	// already holds limit rows: ErrMemberDriveLimit, or ErrMemberDriveNameTaken
	// when the owner has a drive whose name folds to the same slug.
	CreateMemberDrive(ctx context.Context, d types.UserDrive, limit int) (types.UserDrive, error)
	// ListMemberDrivesOf returns owner's drives, tombstoned ones included.
	ListMemberDrivesOf(ctx context.Context, owner string) ([]types.UserDrive, error)
	// GetMemberDriveOf returns drive id only if owner owns it, else ErrNotFound.
	GetMemberDriveOf(ctx context.Context, owner string, id uuid.UUID) (types.UserDrive, error)
	// BeginMemberDriveDelete tombstones owner's drive and returns the tombstone:
	// ErrNotFound when not owned, ErrMemberDriveBusy while another delete holds it.
	BeginMemberDriveDelete(ctx context.Context, owner string, id uuid.UUID) (types.UserDrive, time.Time, error)
	// BeginMemberDriveDeleteAdmin is BeginMemberDriveDelete for any owner's drive.
	BeginMemberDriveDeleteAdmin(ctx context.Context, id uuid.UUID) (types.UserDrive, time.Time, error)
	// AbortMemberDriveDelete clears only the tombstone started names.
	AbortMemberDriveDelete(ctx context.Context, id uuid.UUID, started time.Time) error
	// FinishMemberDriveDelete removes the row only while it carries started.
	FinishMemberDriveDelete(ctx context.Context, id uuid.UUID, started time.Time) error
	// ListMemberDrives returns one page of every person's drives and the total.
	ListMemberDrives(ctx context.Context, p Page) ([]types.UserDrive, int, error)
}

MemberDriveStore persists member drives: a person's own user_drives row, owned by its owner_subject, with no grant rows. Bodies in member_drives.go. Optional like RunnerStore: callers type-assert.

type PG

type PG struct {
	Pool *pgxpool.Pool

	// Now is THE APP CLOCK, letting a test run one that disagrees with the
	// database's. Nil means time.Now. Every stamp this store writes belongs on
	// the DATABASE's clock; the app clock only measures an ELAPSED TIME (a
	// difference of two readings, carrying no skew) measured after a successful DB-clock read. See db.CaptureAppClock.
	Now func() time.Time

	// SIEM, when set, receives the audit rows this store writes in its own transaction (the retention
	// policy change, the partition drop, the reauth resolve), which no recorder chain sees. Nil sends them
	// nowhere.
	SIEM audit.Sink
}

PG is the Postgres-backed Store: its methods (defined in store.go) hold the query bodies directly, so there is exactly one implementation of each query.

func NewPG

func NewPG(pool *pgxpool.Pool) PG

NewPG returns a PG Store over pool.

func (PG) ADOSignInEnds added in v0.8.6

func (s PG) ADOSignInEnds(ctx context.Context, owner string) (ADOSignInEndState, error)

ADOSignInEnds — see ADOSignInEndStore.

func (PG) AWSSSOTokenSpent added in v0.8.0

func (s PG) AWSSSOTokenSpent(ctx context.Context, fingerprint string) (bool, error)

AWSSSOTokenSpent reports whether fingerprint has a persisted spent-token row.

func (PG) AbortMemberDriveDelete added in v0.9.0

func (s PG) AbortMemberDriveDelete(ctx context.Context, id uuid.UUID, started time.Time) error

AbortMemberDriveDelete clears the tombstone started set, and only that one. ErrNotFound when it is gone (finished, or taken over).

func (PG) AcceptRunnerExit added in v0.9.0

func (s PG) AcceptRunnerExit(ctx context.Context, runnerID, runID uuid.UUID, exit runnerwire.AgentExit, prepare func(context.Context, types.AuditEvent) (types.AuditEvent, error)) (bool, error)

AcceptRunnerExit atomically commits the generation-bound terminal transition, audit and receipt. It deliberately does not perform terminal side effects; RetryRunnerExitTail owns their serialized, retryable completion.

func (PG) AcquireAttachLease added in v0.8.6

func (s PG) AcquireAttachLease(ctx context.Context, l AttachLease, from uuid.UUID, ttl time.Duration) (bool, error)

AcquireAttachLease — see AttachLeaseStore.

func (PG) ActiveRunsAtWorkspacePath added in v0.7.0

func (s PG) ActiveRunsAtWorkspacePath(ctx context.Context, workspacePath string) ([]types.AgentRun, error)

ActiveRunsAtWorkspacePath returns the slot-holding runs bound to one host workspace path (a QUEUED run has no sandbox on it yet). Uses the POSITIVE state list, same reasoning as ActiveRunsByCreator: a forgotten new state merely under-warns rather than mis-treating it as active.

ponytail: no new index. workspace_path is selective and the state filter is a cheap check over matching rows; a composite (workspace_path, state) index is the upgrade if a deployment ever has enough runs on ONE path.

func (PG) ActiveRunsByCreator added in v0.7.5

func (s PG) ActiveRunsByCreator(ctx context.Context, createdBy, task, agent string) ([]types.AgentRun, error)

ActiveRunsByCreator returns createdBy's non-terminal runs of one task+agent. Uses the POSITIVE state list (types.NonTerminalRunStates): a state added to the enum and forgotten here merely misses a supersede, whereas `NOT IN (terminal)` would hand a new terminal state to the kill cascade.

ponytail: no new index. agent_runs_created_by_idx already indexes the selective column and one person owns few runs; upgrade to a composite if a deployment ever has enough history to notice.

func (PG) AddOwnRunnerToPool added in v0.9.0

func (s PG) AddOwnRunnerToPool(ctx context.Context, id, runnerID uuid.UUID, owner, orgURLSHA256 string) (types.RunnerPoolMember, bool, int64, error)

AddOwnRunnerToPool adds a self-hosted runner to a live user pool only when owner owns both the pool and the runner, the runner is claimed and bound to this organisation's URL, all decided in the insert itself so a revocation or a transfer cannot slip between a check and the write. An organization pool is ErrRunnerPoolScope: it takes remote-hosted runners only. ErrNotFound covers every other case alike: an unknown pool or runner, and a pool or a runner that is not the owner's.

func (PG) AddRemoteRunnerToUserPool added in v0.9.0

func (s PG) AddRemoteRunnerToUserPool(ctx context.Context, id uuid.UUID, remoteRunner, owner string, peopleRevision int64) (types.RunnerPoolMember, bool, int64, error)

AddRemoteRunnerToUserPool adds a remote-hosted runner to owner's own live user pool. The caller checked the runner's people policy at peopleRevision; the runner row is read FOR SHARE here, so a policy write (which takes it FOR UPDATE and raises its revision) cannot slip between that check and this insert: a moved revision is ErrRunnerPoolStale. ErrNotFound: the pool is not owner's live user pool, or the runner has no row.

func (PG) AddRunnerPoolExecutor added in v0.9.0

func (s PG) AddRunnerPoolExecutor(ctx context.Context, id uuid.UUID, executor string) (types.RunnerPoolMember, bool, int64, error)

AddRunnerPoolExecutor adds a remote-hosted runner (executor is its id) to a live organization pool; added is false when it was already a member (no revision change). revision is the pool's revision after the call, read under the pool lock. ErrNotFound: no such live organization pool, or no such remote-hosted runner row.

func (PG) AddSSHKey added in v0.5.0

func (s PG) AddSSHKey(ctx context.Context, k types.SSHPublicKey) (types.SSHPublicKey, error)

AddSSHKey inserts a new registered key. Returns ErrConflict when the fingerprint (PK) already exists — a 23505 here means that exact key material is already registered.

func (PG) AddScimGroupMembers added in v0.8.6

func (s PG) AddScimGroupMembers(ctx context.Context, id uuid.UUID, identities []uuid.UUID, now time.Time) error

AddScimGroupMembers records each identity as a member, leaving one already recorded as it is and skipping an id that names no identity row.

func (PG) AddUserDriveGrant added in v0.9.0

func (s PG) AddUserDriveGrant(ctx context.Context, g types.UserDriveGrant, homeOverrideStated bool) (types.UserDriveGrant, error)

AddUserDriveGrant allocates one drive to one subject BESIDE the subject's other drives, keyed on UNIQUE(subject_type, subject, drive_id) (migration 0169): writing the same subject and drive again updates that row and returns its id; another drive is another row. The guards and error mapping are UpsertUserDriveGrant's.

func (PG) AddWorkspaceEgressDecision added in v0.5.0

func (s PG) AddWorkspaceEgressDecision(ctx context.Context, id uuid.UUID, host string, allow bool, maxApprovedEgress int) (types.Workspace, error)

AddWorkspaceEgressDecision records one `always`-scoped egress decision for host: on allow it is added to approved_egress (capped, deduped) and removed from denied_egress; on deny the mirror. Returns ErrConflict, not a bare "not found", when id exists but the cap guard refused the write.

func (PG) AppendRunOutputChunk added in v0.8.6

func (s PG) AppendRunOutputChunk(ctx context.Context, runID uuid.UUID, replica string, b []byte, keep int) (bool, error)

AppendRunOutputChunk — see RunOutputChunkStore.

func (PG) ApplyIdentityUpdate added in v0.8.6

func (s PG) ApplyIdentityUpdate(ctx context.Context, id uuid.UUID, u IdentityUpdate, now time.Time) (PrincipalIdentity, error)

ApplyIdentityUpdate applies u to the identity in one transaction: the projection, then the reactivation when asked. A purged identity refuses a reactivation and nothing else changes.

func (PG) ApprovalEscalations added in v0.8.6

func (s PG) ApprovalEscalations(ctx context.Context, ids []uuid.UUID, now time.Time) (map[uuid.UUID]types.ApprovalEscalation, error)

ApprovalEscalations — see ApprovalNotifyReader.

func (PG) ApprovalNotifyChannelStats added in v0.8.6

func (s PG) ApprovalNotifyChannelStats(ctx context.Context, now time.Time) ([]types.ApprovalNotifyChannelStat, error)

ApprovalNotifyChannelStats — see ApprovalNotifyReader.

func (PG) AuditDeliveryStatuses added in v0.9.0

func (s PG) AuditDeliveryStatuses(ctx context.Context) ([]AuditDeliveryStatus, error)

AuditDeliveryStatuses returns one row per checkpoint destination, ordered by destination, with the audit table's head and the recorded_at of the oldest event past the checkpoint. One statement, so a lag reading cannot straddle two queries; the subqueries read the append-only table, so it answers on any replica, not just the leader.

func (PG) AuditHeadSeq added in v0.8.0

func (s PG) AuditHeadSeq(ctx context.Context) (int64, error)

AuditHeadSeq is this deployment's newest audit_events seq, 0 on an empty table: the head the forwarder measures lag against, read every tick whether or not a push succeeds.

func (PG) AuditKeyDestroyedAt added in v0.8.6

func (s PG) AuditKeyDestroyedAt(ctx context.Context, owner string) (time.Time, bool, error)

AuditKeyDestroyedAt is when the latest generation of owner's audit-seal key was destroyed. ok is false while none has been.

func (PG) AuditPartitionsAhead added in v0.8.6

func (s PG) AuditPartitionsAhead(ctx context.Context) (int, error)

func (PG) AuditPrincipalOf added in v0.8.6

func (s PG) AuditPrincipalOf(ctx context.Context, id string) (string, bool, error)

AuditPrincipalOf is the principal a subject id names. ok is false for an id that is not a uuid or that no bound identity row carries.

func (PG) AuditRetentionStatus added in v0.8.6

func (s PG) AuditRetentionStatus(ctx context.Context) (AuditRetentionStatus, error)

func (PG) AuditSubjectFor added in v0.8.6

func (s PG) AuditSubjectFor(ctx context.Context, principal string) (string, bool, error)

AuditSubjectFor is principal's subject id. ok is false when the person has no identity row.

func (PG) AutodropAuditPartition added in v0.8.6

func (s PG) AutodropAuditPartition(ctx context.Context) (AuditRetentionDrop, bool, error)

func (PG) BeginADOSignInEnd added in v0.8.6

func (s PG) BeginADOSignInEnd(ctx context.Context, owner, reason string) error

BeginADOSignInEnd — see ADOSignInEndStore.

func (PG) BeginMemberDriveDelete added in v0.9.0

func (s PG) BeginMemberDriveDelete(ctx context.Context, owner string, id uuid.UUID) (types.UserDrive, time.Time, error)

BeginMemberDriveDelete tombstones owner's drive id. ErrNotFound when owner does not own it; ErrMemberDriveBusy when another delete holds it.

func (PG) BeginMemberDriveDeleteAdmin added in v0.9.0

func (s PG) BeginMemberDriveDeleteAdmin(ctx context.Context, id uuid.UUID) (types.UserDrive, time.Time, error)

BeginMemberDriveDeleteAdmin is BeginMemberDriveDelete for any owner.

func (PG) BeginRunnerExecution added in v0.9.0

func (s PG) BeginRunnerExecution(ctx context.Context, runnerID, runID, previous, generation uuid.UUID, operation string) (RunnerExecution, error)

BeginRunnerExecution persists the H3-selected generation before LEASE and Exec/Start. Retries retain generation; a new incarnation supplies the previous generation as a CAS. The existing run-operation lock serializes its entire state phases with exit teardown. H3 releases this phase before waiting for remote Exec/Start, then reacquires through Bind with the exact generation.

func (PG) BeginRunnerInventory added in v0.9.0

func (s PG) BeginRunnerInventory(ctx context.Context, runnerID uuid.UUID, sessionID string) error

BeginRunnerInventory captures original candidates before fresh PENDING/STATE. The authenticated Hub invokes it under its per-runner session admission lock. Repeating the same session never resets a partially consumed inventory.

func (PG) BindRunnerExecution added in v0.9.0

func (s PG) BindRunnerExecution(ctx context.Context, runnerID, runID, generation uuid.UUID, execID string) error

BindRunnerExecution accepts only H3's correlated Exec/Start result or its explicit generation-bound reconciliation. It must never be called with a first-seen agent_exit handle as proof of which execution the org dispatched.

func (PG) BlankRunTasks added in v0.8.6

func (s PG) BlankRunTasks(ctx context.Context, createdBy string) (int, error)

func (PG) BootstrapRunnerPools added in v0.9.0

func (s PG) BootstrapRunnerPools(ctx context.Context, executors []string, name string) (*types.RunnerPool, *types.RunnerPoolDefaults, error)

BootstrapRunnerPools is the one-time upgrade step: when this deployment has configured remote-hosted runners and no organization pool has ever existed (a deleted one still counts, so an administrator's deletion sticks), it creates one organization pool named name holding exactly those runners and makes it the organisation's default pool, so a launch that names no pool still resolves. Defaults the organisation saved before any pool existed keep their barrier. It gives nobody a runner they lacked (no use policy, and no user pool is ever made); it is still a placement change, since a run may then land on any member that advertises its class. With no runners or a catalogue that has existed it does nothing. Each runner's row is EnsureRemoteRunners'. Returns the pool it created and the organisation defaults it left, both nil when it did nothing.

func (PG) ClaimInvitation added in v0.9.0

func (s PG) ClaimInvitation(ctx context.Context, c InvitationClaim) (InvitationClaimed, error)

func (PG) ClaimRecordingRunOutput added in v0.8.9

func (s PG) ClaimRecordingRunOutput(ctx context.Context, runID uuid.UUID, retention, staleAfter time.Duration) (RecordingOutputClaim, bool, error)

ClaimRecordingRunOutput — see RunOutputStore.

func (PG) ClaimRunner added in v0.9.0

func (s PG) ClaimRunner(ctx context.Context, id uuid.UUID, owner, fingerprint string, now time.Time) (types.Runner, error)

ClaimRunner moves an unclaimed runner to claimed in one conditional UPDATE, only for its owner and the fingerprint it was registered with. ErrNotFound: no such runner. ErrConflict: it is not unclaimed (claimed twice, revoked). ErrRunnerClaimMismatch: wrong owner or fingerprint.

func (PG) ClaimSourceActiveRun added in v0.5.0

func (s PG) ClaimSourceActiveRun(ctx context.Context, id, runID uuid.UUID) error

ClaimSourceActiveRun fences a source's in-flight scan run, mirroring ClaimWorkspaceActiveRun. Returns ErrConflict when another run holds the slot.

func (PG) ClaimStaleRunWatchers added in v0.5.0

func (s PG) ClaimStaleRunWatchers(ctx context.Context, owner string, staleAfter time.Duration) ([]types.AgentRun, error)

ClaimStaleRunWatchers atomically takes the watcher lease, for owner, on every non-terminal run with a sandbox whose lease has been silent longer than staleAfter, returning the runs claimed for re-adoption.

The conditional UPDATE ... RETURNING is the mutual exclusion: replicas sweeping at once re-evaluate WHERE against the row version they blocked on, so the loser sees the winner's fresh heartbeat and gets no row — no advisory lock needed (one per in-flight run via db.TryAdvisoryLock would exhaust the pool, which holds a connection for the whole lock).

Two predicates carry the sweep's safety: nonTerminalRunStates matches agent_runs_watcher_sweep_idx's predicate verbatim (complement of types.RunState.IsTerminal, so a finished run is never re-adopted); and a non-empty sandbox_ref limits it to runs with something to watch — a row exists through the whole pre-dispatch window (grant writes, then a multi-minute build) and would otherwise look abandoned, so cleanup there stays boot-only.

A KEPT run (lost_at set, migration 0073) is also never claimed: its agent is stopped on purpose, and a watcher would read that as exit and finalize the run, tearing down files it's kept for. updated_at is deliberately not touched: it's the idle reaper's activity signal, and a lease write isn't activity.

func (PG) ClaimWorkspaceActiveRun

func (s PG) ClaimWorkspaceActiveRun(ctx context.Context, id, runID uuid.UUID, expected *uuid.UUID) (types.Workspace, bool, error)

ClaimWorkspaceActiveRun compare-and-sets active_run_id from expected (possibly nil) to runID, the serial-import-step gate: of two concurrent launches racing the same slot, the loser gets applied=false and must NOT launch.

func (PG) ClearPauseSettle added in v0.8.6

func (s PG) ClearPauseSettle(ctx context.Context, id uuid.UUID, due time.Time) error

ClearPauseSettle — see PauseSettler.

func (PG) ClearRunContainmentError added in v0.8.0

func (s PG) ClearRunContainmentError(ctx context.Context, id uuid.UUID) (bool, error)

ClearRunContainmentError — see RunLeaser.

func (PG) ClearRunPaused added in v0.8.0

func (s PG) ClearRunPaused(ctx context.Context, id uuid.UUID) (bool, error)

ClearRunPaused — see RunPauser.

func (PG) ClearSourceActiveRun added in v0.5.0

func (s PG) ClearSourceActiveRun(ctx context.Context, id, runID uuid.UUID) error

ClearSourceActiveRun releases the fence (idempotent; only the named run's claim is cleared, so a stale clear can't stomp a newer claim).

func (PG) ClearWorkspaceActiveRun

func (s PG) ClearWorkspaceActiveRun(ctx context.Context, id, runID uuid.UUID) (bool, error)

ClearWorkspaceActiveRun clears active_run_id ONLY while it still points at runID, so a terminal run's cleanup can never clobber a step that was concurrently launched and now owns the pointer.

func (PG) ConsumeAttachTicket added in v0.5.0

func (s PG) ConsumeAttachTicket(ctx context.Context, token string, now time.Time) (AttachTicket, bool, error)

ConsumeAttachTicket redeems token exactly once, returning the ticket it stood for. The DELETE ... RETURNING is the single-use guarantee: two racing redemptions can only have one return a row.

The run-id binding is checked by the CALLER, not here, so a redemption against the wrong run still BURNS the ticket (a leaked ticket probed against a guessed run must not survive the probe). A miss and an expired row are both (ok=false), indistinguishable to the caller.

func (PG) ConsumeEnrolmentToken added in v0.8.0

func (s PG) ConsumeEnrolmentToken(ctx context.Context, token string, now time.Time) (types.DeviceEnrolmentToken, bool, error)

ConsumeEnrolmentToken redeems token exactly once: a single-row conditional UPDATE...RETURNING, the same atomic consume-once shape as ConsumeAttachTicket, adapted to an UPDATE since the row is worth keeping after redemption for CreateDevice. TRUST BOUNDARY: an unknown, already-consumed, and expired token all answer (zero, false, nil) alike — no oracle, matching ConsumeAttachTicket.

func (PG) ConsumeRunnerRegistrationToken added in v0.9.0

func (s PG) ConsumeRunnerRegistrationToken(ctx context.Context, raw, orgHash string, now time.Time) (types.RunnerRegistrationToken, bool, error)

ConsumeRunnerRegistrationToken atomically spends a token bound to this org URL. Unknown, expired, consumed and differently bound tokens share the same refusal.

func (PG) CountActiveRunsBy added in v0.7.0

func (s PG) CountActiveRunsBy(ctx context.Context, createdBy string) (int, error)

CountActiveRunsBy counts createdBy's runs that have not ended yet — the governance quota's one read (GovernanceLimits.MaxConcurrentRuns).

The state predicate is a POSITIVE list (types.NonTerminalRunStates), not `NOT IN (terminal)`: a state added to the enum and forgotten here merely undercounts, whereas the negated form would treat a new terminal state as active and wedge a capped member with nothing to stop it. Pinned against RunState.IsTerminal by a test in internal/types.

ponytail: no new index — agent_runs_created_by_idx already covers the selective column and the state filter is cheap over one member's rows.

func (PG) CountApprovalsForRun added in v0.7.2

func (s PG) CountApprovalsForRun(ctx context.Context, runID uuid.UUID) (int, error)

CountApprovalsForRun returns how many approvals a run has raised, in ANY state — the per-run DoS bound behind handleInternalRequestApproval (maxApprovalsPerRun), since a sandbox picks the hosts it asks about and could otherwise raise rows without limit. Counted in the database rather than List + filter in Go, which would read every approval row in the deployment.

func (PG) CountComponents added in v0.8.9

func (s PG) CountComponents(ctx context.Context, owner string) (int, error)

CountComponents — see ComponentStore.

func (PG) CountHiddenRuns added in v0.8.0

func (s PG) CountHiddenRuns(ctx context.Context, f RunFilter) (olderHidden, killedHidden int, err error)

CountHiddenRuns — see RunsFilteredPager.

func (PG) CountNonTerminalRuns added in v0.8.6

func (s PG) CountNonTerminalRuns(ctx context.Context, except uuid.UUID) (int, error)

CountNonTerminalRuns is the number of run rows that hold a slot: a slot-holding state (types.SlotHoldingRunStates, never QUEUED) whose agent still runs (HoldsSandboxSQL), the quantity CreateRunUnderCap holds under the cap. except is a run never counted (uuid.Nil for none): a revive counts the OTHER runs, as markRunRevivedUnderCap does. It takes no lock: a pre-flight read for a refusal that must come before an identity is minted, never the authority.

func (PG) CountPendingApprovals added in v0.8.0

func (s PG) CountPendingApprovals(ctx context.Context) (int, error)

CountPendingApprovals — see ApprovalsForRunsPager. Backed by the partial approvals_state_idx: an index-only count, not a table scan.

func (PG) CountPendingApprovalsByRunCreator added in v0.8.0

func (s PG) CountPendingApprovalsByRunCreator(ctx context.Context, createdBy string) (int, error)

CountPendingApprovalsByRunCreator — see ApprovalsForRunsPager. Same semi-join ListApprovalsPageByRunCreator's own query uses (approvals has no created_by column of its own), counted instead of listed.

func (PG) CountPushPathLists added in v0.8.0

func (s PG) CountPushPathLists(ctx context.Context, runID uuid.UUID) (int, error)

func (PG) CountQueuedRunsByOwner added in v0.9.0

func (s PG) CountQueuedRunsByOwner(ctx context.Context, poolID uuid.UUID, owner string) (int, error)

CountQueuedRunsByOwner is how many runs owner has waiting for poolID: the flood limit's one read.

func (PG) CountRunnerPoolSlotHolders added in v0.9.0

func (s PG) CountRunnerPoolSlotHolders(ctx context.Context, poolID uuid.UUID) (int, error)

CountRunnerPoolSlotHolders is countPoolSlotHolders without the lock, a read for a display; the authority is the in-transaction count.

func (PG) CreateAPIToken added in v0.6.0

func (s PG) CreateAPIToken(ctx context.Context, t types.APIToken, raw string) (types.APIToken, error)

CreateAPIToken inserts one token row. raw is the PLAINTEXT credential; only its hash is stored, and the caller must return the plaintext to its creator exactly once (unrecoverable afterwards). t.Token is ignored — passing the secret twice would be the one way to accidentally persist it.

A unique_violation (23505) on token_sha256 is ErrConflict: a raw collision in a 256-bit space means the caller reused a token value rather than minting a fresh one.

SECURITY: groups_truncated binds as SQL NULL when nil. NULL means "minted before anything recorded this", and consumers read that as TRUNCATED, not false. A plain bool or DEFAULT FALSE would assert "complete" for every legacy row — fail OPEN, the exact thing the marker exists to prevent.

func (PG) CreateApproval

func (s PG) CreateApproval(ctx context.Context, a types.ApprovalRequest) (types.ApprovalRequest, error)

CreateApproval inserts a new approval request. With approval notifications configured it also inserts the approval's outbox rows in the same transaction, so no crash can leave an approval nobody was told about; without them the INSERT is the only write.

func (PG) CreateComponent added in v0.8.9

func (s PG) CreateComponent(ctx context.Context, c types.Component) (types.Component, error)

CreateComponent — see ComponentStore.

func (PG) CreateDelegate added in v0.8.0

func (s PG) CreateDelegate(ctx context.Context, d types.Delegate, raw string) (types.Delegate, error)

CreateDelegate registers one portal. raw is the PLAINTEXT credential; only its hash is stored. Returns ErrConflict on a credential_sha256 collision (a reused credential in a 256-bit space).

func (PG) CreateDevice added in v0.8.0

func (s PG) CreateDevice(ctx context.Context, d types.Device, raw string) (types.Device, error)

CreateDevice inserts one device row, following CreateAPIToken's shape: raw is the plaintext device credential minted at enrolment. SECURITY: only its hash is stored, and d.CredentialSHA256 is ignored, so the caller can never accidentally persist the secret twice; raw is returned to the device exactly once, unrecoverable afterwards. ErrConflict on a credential_sha256 collision, same 256-bit space as MintEnrolmentToken.

func (PG) CreateGrant

func (s PG) CreateGrant(ctx context.Context, g types.CredentialGrant) (types.CredentialGrant, error)

CreateGrant inserts a credential grant (eligibility record) and returns it.

func (PG) CreateInvitation added in v0.9.0

func (s PG) CreateInvitation(ctx context.Context, inv types.Invitation, maxPending int, now time.Time) (types.Invitation, error)

func (PG) CreateMemberDrive added in v0.9.0

func (s PG) CreateMemberDrive(ctx context.Context, d types.UserDrive, limit int) (types.UserDrive, error)

CreateMemberDrive inserts d as owner d.OwnerSubject's drive unless the owner already holds limit rows. The count and the insert run in one READ COMMITTED transaction under the owner's MemberDriveLockClass lock. The row's managed fields are fixed here: no host root, the hash home, the id object scheme, reclaim delete, writable.

func (PG) CreatePerson added in v0.8.0

func (s PG) CreatePerson(ctx context.Context, p types.Person) (types.Person, bool, error)

func (PG) CreatePolicy

func (s PG) CreatePolicy(ctx context.Context, p types.RunPolicy) (types.RunPolicy, error)

CreatePolicy inserts a policy and returns the persisted row. Returns ErrConflict when the name's UNIQUE constraint (run_policies.name) rejects a duplicate — the caller maps that to 409, never the raw driver error.

func (PG) CreateQueuedRun added in v0.9.0

func (s PG) CreateQueuedRun(ctx context.Context, r types.AgentRun, e RunQueueEntry, maxPerOwner int) (types.AgentRun, error)

CreateQueuedRun inserts r (state QUEUED, bound to a pool), its launch intent when it carries one, and its queue row in one transaction that holds the pool row FOR NO KEY UPDATE, the lock promotion takes first, so the owner's waiting count and the insert cannot interleave with a promotion. maxPerOwner <= 0 is unlimited; otherwise an owner with that many runs already waiting for the pool gets ErrRunQueueFull and nothing is written. The queue row's pool and owner are the run's own.

func (PG) CreateRestrictedComponent added in v0.8.9

func (s PG) CreateRestrictedComponent(ctx context.Context, c types.Component, capability, by string) (types.Component, error)

CreateRestrictedComponent — see RestrictedComponentCreator.

func (PG) CreateRun

func (s PG) CreateRun(ctx context.Context, r types.AgentRun) (types.AgentRun, error)

CreateRun inserts a new run and returns the persisted row. A run bound to a runner pool is inserted only while that pool is still active at the recorded revision, else ErrRunnerPoolStale. A run that carries a LaunchIntent is inserted with it in one transaction (store_launch_intents.go); the returned run carries it only when it was kept.

func (PG) CreateRunUnderCap added in v0.8.6

func (s PG) CreateRunUnderCap(ctx context.Context, r types.AgentRun, limit int) (types.AgentRun, error)

CreateRunUnderCap is CreateRun with a deployment-wide ceiling on non-terminal runs (WARDYN_MAX_CONCURRENT_RUNS). The count and the insert share one transaction-scoped advisory lock, so two replicas racing at the cap admit exactly the cap. limit <= 0 is unlimited and takes no lock. Rows are counted, not kept in a counter, so a crashed path cannot drift it. A run bound to a runner of either kind is also held to that runner's limits (Runs at once, its totals and its per-run limit, what it reserves counted) under its row lock, cap or none (RunnerAtCapacityError).

func (PG) CreateRunner added in v0.9.0

func (s PG) CreateRunner(ctx context.Context, r types.Runner) (types.Runner, error)

CreateRunner inserts an unclaimed runner: state, claim and posture on r are ignored, since every registration starts unclaimed. ErrConflict on a key fingerprint already registered. r.Limits, when set, is the runner's limit set and its Runs at once; without it the runner declares no limits and r.MaxRuns 0 is types.RunnerMaxRunsDefault.

func (PG) CreateRunnerPool added in v0.9.0

func (s PG) CreateRunnerPool(ctx context.Context, p types.RunnerPool, by string) (types.RunnerPool, error)

CreateRunnerPool inserts an active pool at revision 1, owned by p.Owner when it is set. ErrConflict: a live pool of the same owner (or of the organisation) has the name. ErrRunnerPoolLimit: the organisation, or the owner, is at its cap.

func (PG) CreateScimGroup added in v0.8.6

func (s PG) CreateScimGroup(ctx context.Context, externalID, displayName string, now time.Time) (ScimGroup, error)

CreateScimGroup stores a group. An externalId any group already holds, in any case, is ErrScimGroupExists.

func (PG) CreateScimIdentity added in v0.8.6

func (s PG) CreateScimIdentity(ctx context.Context, issuer, tenantID, objectID string, u IdentityUpdate, now time.Time) (PrincipalIdentity, bool, error)

CreateScimIdentity records a SCIM user as an identity row with no principal, keyed by the object id, or finds the row already holding it (created=false). The first sign-in binds the principal.

func (PG) CreateServiceIdentity added in v0.9.0

func (s PG) CreateServiceIdentity(ctx context.Context, si types.ServiceIdentity) (types.ServiceIdentity, error)

func (PG) CreateServiceToken added in v0.9.0

func (s PG) CreateServiceToken(ctx context.Context, name, sponsor string, maxLive int, t types.APIToken, raw string) (types.APIToken, error)

func (PG) CreateTemplate added in v0.9.0

func (s PG) CreateTemplate(ctx context.Context, t types.RunTemplate, grant *TemplateGrantCheck) (types.RunTemplate, error)

CreateTemplate — see TemplateStore.

func (PG) CreateUserType added in v0.8.0

func (s PG) CreateUserType(ctx context.Context, t types.UserType) (types.UserType, error)

CreateUserType inserts a custom type (built_in is never set here). ErrConflict when the id or name is taken, or the id is still named by an orphaned subject row.

func (PG) CreateWorkspace

func (s PG) CreateWorkspace(ctx context.Context, ws types.Workspace) (types.Workspace, error)

CreateWorkspace inserts an onboarded workspace and returns the persisted row.

func (PG) DecideApproval

func (s PG) DecideApproval(ctx context.Context, id uuid.UUID, decision types.ApprovalDecision) (types.ApprovalRequest, error)

DecideApproval transitions an approval from PENDING to decision.State. Returns ErrAlreadyDecided if the approval is not PENDING (fail-closed), via a single UPDATE with WHERE state='PENDING' to prevent TOCTOU races. SET is kept separate from RETURNING: updating only RETURNING would echo an un-updated row back as a green result over a silent no-op.

func (PG) DecideGovernanceChange added in v0.8.6

func (s PG) DecideGovernanceChange(ctx context.Context, id uuid.UUID, d GovernanceDecision, fn GovernanceDecideFunc) (types.GovernanceChange, error)

DecideGovernanceChange decides one change in one transaction:

  1. lock the change row;
  2. require it pending and unexpired. A lapsed pending row is moved to expired here and that is committed, and the caller gets *ErrGovernanceChangeNotPending with Lapsed set;
  3. run fn on the transaction (the target's checks and apply);
  4. move the row to d.To, stamping the decider and the time;
  5. commit.

fn returning ErrGovernanceChangeStale moves the row to stale instead and commits that. Any other error from fn, or from the state move, rolls the whole transaction back: a target written by fn is then unwritten and the change is still pending. ErrNotFound when no such change.

func (PG) DeleteBaseImage added in v0.5.0

func (s PG) DeleteBaseImage(ctx context.Context, id uuid.UUID, detach bool) error

DeleteBaseImage removes a catalog row; detach=true drops every workspace reference first (falling back to the derived recommended build — NULL is the marker, so "detach" is honest, not destructive). detach=false mirrors DeleteSource's residual race (STORE-2): a concurrent commit between the NOT EXISTS check and delete is instead caught by the base_image_id FK (0031:61), surfacing as 23503 — mapped to ErrConflict below.

func (PG) DeleteBranding added in v0.8.0

func (s PG) DeleteBranding(ctx context.Context) (bool, error)

DeleteBranding implements BrandingStore.

func (PG) DeleteCapabilityGrant added in v0.6.0

func (s PG) DeleteCapabilityGrant(ctx context.Context, id uuid.UUID) error

DeleteCapabilityGrant removes one grant by id. Returns ErrNotFound when no row matched — this is an admin-only surface, so there is no principal to scope the delete to and no existence oracle to worry about.

func (PG) DeleteComponent added in v0.8.9

func (s PG) DeleteComponent(ctx context.Context, id uuid.UUID, owner string) (types.Component, error)

DeleteComponent — see ComponentStore.

func (PG) DeleteComponentsByOwner added in v0.8.9

func (s PG) DeleteComponentsByOwner(ctx context.Context, owner string) (int, error)

DeleteComponentsByOwner — see ComponentStore.

func (PG) DeleteGovernanceAssignment added in v0.7.0

func (s PG) DeleteGovernanceAssignment(ctx context.Context, id uuid.UUID) error

DeleteGovernanceAssignment removes one assignment by id, ErrNotFound when no row matched. Unassigning is the SUPPORTED way to widen someone back to the deployment ceiling.

func (PG) DeleteGovernanceProfile added in v0.7.0

func (s PG) DeleteGovernanceProfile(ctx context.Context, id uuid.UUID) error

DeleteGovernanceProfile removes one profile by id. ErrNotFound when no row matched.

ErrConflict when the row is still ASSIGNED: governance_assignments' ON DELETE RESTRICT refuses (23503) rather than cascading, since cascading would silently widen every assigned member back to the deployment ceiling. Translated into a sentinel so the route answers a caller-fixable 409 instead of a 500. *ErrProfileHasChildren when other profiles still compose on it (named under the graph lock, so the answer matches what the delete saw).

func (PG) DeleteLaunchIntentsCreatedBy added in v0.9.0

func (s PG) DeleteLaunchIntentsCreatedBy(ctx context.Context, createdBy string) (int, error)

DeleteLaunchIntentsCreatedBy — see LaunchIntentStore.

func (PG) DeleteLaunchPreset added in v0.8.0

func (s PG) DeleteLaunchPreset(ctx context.Context, name string) (types.LaunchPreset, error)

DeleteLaunchPreset removes a preset by name, returning the removed row, or ErrNotFound. Runs launched from it keep their preset/preset_version stamp.

func (PG) DeletePolicy

func (s PG) DeletePolicy(ctx context.Context, id uuid.UUID) error

DeletePolicy removes a policy by id. Returns ErrNotFound when no row matched. agent_runs.policy_id has no foreign key, so referencing runs keep a dangling policy_id; their authorization envelope survives via the run.policy.resolve audit event dispatch already recorded.

func (PG) DeleteRef added in v0.3.1

func (s PG) DeleteRef(ctx context.Context, ref string) error

DeleteRef removes the row for ref. Idempotent: deleting a missing ref is nil.

func (PG) DeleteRestrictedComponent added in v0.8.9

func (s PG) DeleteRestrictedComponent(ctx context.Context, id uuid.UUID, capability, by string) (types.Component, int, bool, error)

DeleteRestrictedComponent — see RestrictedComponentDeleter.

func (PG) DeleteRoleMapping added in v0.7.0

func (s PG) DeleteRoleMapping(ctx context.Context, id uuid.UUID) error

DeleteRoleMapping removes one row by id. Returns ErrNotFound when no row matched — admin-only surface, so there is no principal to scope the delete to and no existence oracle to worry about, exactly DeleteCapabilityGrant.

func (PG) DeleteRunOutputsOlderThan added in v0.8.6

func (s PG) DeleteRunOutputsOlderThan(ctx context.Context, age time.Duration) (int, error)

DeleteRunOutputsOlderThan — see RunOutputStore.

func (PG) DeleteRunProxyConfig added in v0.8.0

func (s PG) DeleteRunProxyConfig(ctx context.Context, id uuid.UUID) error

DeleteRunProxyConfig — see RunProxyConfigs.

func (PG) DeleteRunnerPool added in v0.9.0

func (s PG) DeleteRunnerPool(ctx context.Context, id uuid.UUID) (RunnerPoolDeletion, error)

DeleteRunnerPool tombstones a live pool, ends the runs waiting for it (KILLED, runner_pool_removed, their queue rows with them), drops its members, and clears the organisation default slots that name it, all in one transaction: ErrNotFound for an unknown or already deleted one, ErrRunnerPoolHasPolicy while it has a use policy. The row stays, so a run bound to it keeps its history.

func (PG) DeleteRunnerPoolUsePolicy added in v0.9.0

func (s PG) DeleteRunnerPoolUsePolicy(ctx context.Context, id uuid.UUID) error

DeleteRunnerPoolUsePolicy removes a pool's use policy, which widens the pool back to everyone who may launch remote runs, and raises the pool's revision. ErrNotFound: the pool has none (or is gone).

func (PG) DeleteRunnerPoolsByOwner added in v0.9.0

func (s PG) DeleteRunnerPoolsByOwner(ctx context.Context, owner string) ([]RunnerPoolDeletion, error)

DeleteRunnerPoolsByOwner is a person's erasure of their pools: each live pool owner holds is deleted as DeleteRunnerPool deletes one, and then every pool of theirs, the tombstones included, forgets them (owner and created_by cleared). It holds the lock every create takes, so a pool the person creates meanwhile is either deleted here or made after it. Returns the deletions, in pool id order.

func (PG) DeleteSSHKey added in v0.5.0

func (s PG) DeleteSSHKey(ctx context.Context, fingerprint, principal string) error

DeleteSSHKey removes fingerprint, scoped to principal so a human can only delete their OWN key. Returns ErrNotFound whether the fingerprint doesn't exist or belongs to someone else — indistinguishable on purpose, no existence leak across principals.

func (PG) DeleteSSHKeys added in v0.8.0

func (s PG) DeleteSSHKeys(ctx context.Context, principal string) (int, error)

DeleteSSHKeys removes a principal's registered keys, or every key when principal is empty. The count comes from the DELETE, not a prior snapshot.

func (PG) DeleteScimGroup added in v0.8.6

func (s PG) DeleteScimGroup(ctx context.Context, id uuid.UUID) error

DeleteScimGroup removes the group row and, by cascade, its members. Its ledger rows stay.

func (PG) DeleteSource added in v0.5.0

func (s PG) DeleteSource(ctx context.Context, id uuid.UUID, detach bool) error

DeleteSource removes a library source. detach=true strips every workspace attachment referencing it (?force=1) UNLESS that would leave a workspace with zero attachments (workspacesOrphanedBySource) — '[]' must stay unreachable (STORE-1). The check and the detach are two statements, so this only narrows, never closes (STORE-2), the race against a concurrent attach; same residual race as the detach=false path below.

func (PG) DeleteTemplate added in v0.9.0

func (s PG) DeleteTemplate(ctx context.Context, id uuid.UUID, grant *TemplateGrantCheck) (types.RunTemplate, error)

DeleteTemplate — see TemplateStore.

func (PG) DeleteTemplateGroupAdmin added in v0.9.0

func (s PG) DeleteTemplateGroupAdmin(ctx context.Context, group, person string) (bool, error)

DeleteTemplateGroupAdmin — see TemplateStore.

func (PG) DeleteUserDrive added in v0.7.0

func (s PG) DeleteUserDrive(ctx context.Context, id uuid.UUID) error

DeleteUserDrive removes one admin drive by id. ErrNotFound if no row matched, including a member drive, which only the member-drive delete removes; ErrConflict if it's still allocated (user_drive_grants.drive_id is ON DELETE RESTRICT — Postgres refuses rather than cascading away grants whose directories may still hold someone's work).

func (PG) DeleteUserDriveGrant added in v0.7.0

func (s PG) DeleteUserDriveGrant(ctx context.Context, id uuid.UUID) (types.UserDriveGrant, error)

DeleteUserDriveGrant removes one grant by id and returns it (ErrNotFound if no match) — removing only the binding, never the volume/claim/directory. RETURNING lets the caller audit-log what was removed without a prior scan.

func (PG) DeleteUserSubjectRows added in v0.8.6

func (s PG) DeleteUserSubjectRows(ctx context.Context, id uuid.UUID, principals, emails []string) (grants, assignments, kept int64, err error)

DeleteUserSubjectRows deletes the user-subject capability grants and governance assignments of the purged identity id, matched case-insensitively, and says how many of each; the same user subjects are stripped from every runner pool use policy. A direct delete, never the GOV4 apply path. Rows keyed by one of principals go: the subject is a purged identity that can never authenticate again. Rows keyed by one of emails go only while no other principal holds that address, because an address can be recycled and the new holder's deny rows are theirs: another identity that is neither deactivated nor purged, or an API token or people row under a principal that is not in principals (or the address itself, a form of the leaver). Rows kept for that reason are counted in kept.

func (PG) DeleteUserType added in v0.8.0

func (s PG) DeleteUserType(ctx context.Context, id string) error

DeleteUserType removes a custom type that nothing names. ErrNotFound when no row has the id; ErrConflict when it is built in, still referenced, or held by a foreign key (role_mappings.user_type is ON DELETE RESTRICT). The reference check rides the DELETE itself, so a row written between the caller's check and this statement still refuses; a service identity created concurrently is ordered by userTypeWriteLock.

func (PG) DeleteWorkspace

func (s PG) DeleteWorkspace(ctx context.Context, id uuid.UUID) error

DeleteWorkspace removes a workspace by id. Returns ErrNotFound when no row matched.

func (PG) DropAuditPartition added in v0.8.6

func (s PG) DropAuditPartition(ctx context.Context, partition, digest, actor string, actorType types.ActorType) (AuditRetentionDrop, error)

func (PG) DryRunGovernance added in v0.8.6

func (s PG) DryRunGovernance(ctx context.Context, fn func(q Querier) error) error

DryRunGovernance runs fn on a transaction that is always rolled back, and returns fn's error. It is how a proposal learns whether a write would be accepted (and what it would produce) by running the very code that applies it, with nothing left behind.

func (PG) DuePauseSettles added in v0.8.6

func (s PG) DuePauseSettles(ctx context.Context) ([]PauseSettle, error)

DuePauseSettles — see PauseSettler.

func (PG) EnsureAuditDeliveryCursor added in v0.9.0

func (s PG) EnsureAuditDeliveryCursor(ctx context.Context, dest string) error

EnsureAuditDeliveryCursor creates dest's checkpoint at the audit table's CURRENT head, so the first enable sends only new events, never the whole history. ON CONFLICT DO NOTHING keeps the row an existing checkpoint already holds, so a restart (or a second replica's call) never rewinds delivery.

func (PG) EnsureAuditPartitions added in v0.8.6

func (s PG) EnsureAuditPartitions(ctx context.Context, months int) (int, error)

func (PG) EnsureDeprovisionJobs added in v0.8.6

func (s PG) EnsureDeprovisionJobs(ctx context.Context, identityID uuid.UUID, kind string, keys []JobKey) error

EnsureDeprovisionJobs adds each key as a pending ledger row, leaving rows that exist as they are.

func (PG) EnsureRemoteRunners added in v0.9.0

func (s PG) EnsureRemoteRunners(ctx context.Context, ids []string, proposals map[string]types.RunnerLimits) (made, proposed []types.RemoteRunner, err error)

func (PG) EraseGovernanceChangePersonalFields added in v0.8.6

func (s PG) EraseGovernanceChangePersonalFields(ctx context.Context, principals, emails []string) (int, error)

EraseGovernanceChangePersonalFields clears the proposer and decider (their emails too, and the reason a decider typed) of every change whose recorded principal is one of principals or whose recorded email is one of emails (case folded), and returns how many rows it touched. A principal is never matched against the email column: that column holds the claim the proposer signed in with, which can spell another person's principal. A pending change whose proposer is erased leaves pending in the same statement (to expired), so a change with no recorded proposer can never be approved. Idempotent: a row already cleared matches nothing.

func (PG) EraseInvitationsOf added in v0.9.0

func (s PG) EraseInvitationsOf(ctx context.Context, person string) (deleted, cleared int, err error)

EraseInvitationsOf removes person from person_invitations in one transaction. Every invitation that targets them is deleted, whatever its state, since a target is never blank. Where they created or revoked another person's, their name is cleared and the row stays: a claimed one is what tells that person's replaced token apart (TokenDispositionFacts), and a pending one can still be claimed.

func (PG) EraseRecordingRunOutputs added in v0.8.9

func (s PG) EraseRecordingRunOutputs(ctx context.Context, runIDs []uuid.UUID) (int, error)

EraseRecordingRunOutputs — see RunOutputStore.

func (PG) EraseRunComponentsByOwner added in v0.8.9

func (s PG) EraseRunComponentsByOwner(ctx context.Context, owner string) (int, error)

EraseRunComponentsByOwner — see ComponentStore. It clears content and keeps the row: the row is the run's authorization tombstone.

func (PG) EraseRunComponentsOfRun added in v0.8.9

func (s PG) EraseRunComponentsOfRun(ctx context.Context, runID uuid.UUID, owner string) (int, error)

EraseRunComponentsOfRun — see ComponentStore. The same clear as EraseRunComponentsByOwner, on one run.

func (PG) EraseRunOutputs added in v0.8.6

func (s PG) EraseRunOutputs(ctx context.Context, runIDs []uuid.UUID) error

EraseRunOutputs — see RunOutputStore. The locks are taken in key order, so two overlapping erasures cannot each hold what the other waits for.

func (PG) EraseRunSetupsOf added in v0.9.0

func (s PG) EraseRunSetupsOf(ctx context.Context, person string) (RunSetupsErased, error)

EraseRunSetupsOf — see TemplateStore.

func (PG) EraseServiceIdentityAttributionOf added in v0.9.0

func (s PG) EraseServiceIdentityAttributionOf(ctx context.Context, person string) (cleared int, sponsored []string, err error)

EraseServiceIdentityAttributionOf clears person as the creator, suspender and retirer of every service identity, and returns how many rows that changed and, by name, the identities that are not retired and that person still sponsors. A sponsor is never cleared: tokens and runs are checked against it, so such an identity is handed to another sponsor or retired first. A retired identity keeps its last sponsor.

func (PG) ExitQueuedRun added in v0.9.0

func (s PG) ExitQueuedRun(ctx context.Context, id uuid.UUID, to types.RunState, detail, hint string) (bool, error)

ExitQueuedRun moves a QUEUED run to a terminal state through LeaveRunQueue and, in the same statement, writes the reason the queue ended it: status_detail ("queue: <token>") and the sentence for failure_hint. false means another exit won first.

func (PG) ExpireUnclaimedRunners added in v0.9.0

func (s PG) ExpireUnclaimedRunners(ctx context.Context, createdBefore time.Time) (int, error)

ExpireUnclaimedRunners deletes unclaimed runners registered before createdBefore (an unclaimed runner lives 24h) and returns how many.

func (PG) ExpiredQueueEntries added in v0.9.0

func (s PG) ExpiredQueueEntries(ctx context.Context, now time.Time, limit int) ([]RunQueueEntry, error)

ExpiredQueueEntries are the waiting runs whose deadline is at or before now, oldest deadline first.

func (PG) ExportAuditPartition added in v0.8.6

func (s PG) ExportAuditPartition(ctx context.Context, name string, header func(PartitionManifest) error, row func(AuditPartitionRow) error) error

func (PG) FailDeprovisionJob added in v0.8.6

func (s PG) FailDeprovisionJob(ctx context.Context, identityID uuid.UUID, kind string, k JobKey, cause error) error

FailDeprovisionJob records a failed attempt and leaves the step pending.

func (PG) FederationRevoked added in v0.8.0

func (s PG) FederationRevoked(ctx context.Context) (bool, error)

FederationRevoked reports whether the organisation revoked this laptop's device credential (MarkFederationRevoked), false when no row exists.

func (PG) FinishADOSignInEnd added in v0.8.6

func (s PG) FinishADOSignInEnd(ctx context.Context, owner string) error

FinishADOSignInEnd — see ADOSignInEndStore.

func (PG) FinishDeprovisionJob added in v0.8.6

func (s PG) FinishDeprovisionJob(ctx context.Context, identityID uuid.UUID, kind string, k JobKey, detail map[string]int) error

FinishDeprovisionJob marks a step done, storing the counts it produced.

func (PG) FinishMemberDriveDelete added in v0.9.0

func (s PG) FinishMemberDriveDelete(ctx context.Context, id uuid.UUID, started time.Time) error

FinishMemberDriveDelete removes the row while it still carries the tombstone started set. ErrNotFound when it does not.

func (PG) GetAPITokenByRaw added in v0.6.0

func (s PG) GetAPITokenByRaw(ctx context.Context, raw string) (types.APIToken, error)

GetAPITokenByRaw is the auth-time lookup: given a bearer string, resolve the LIVE token it stands for. Deliberately UNSCOPED by principal — this call is what authenticates the caller.

SECURITY: `revoked_at IS NULL` and the not-expired test (apiTokenLive) are in the WHERE, not checked separately — a revoked, expired, unknown, or non-matching token all fail IDENTICALLY with ErrNotFound, so the boundary is never an oracle for "this token once existed".

func (PG) GetApproval

func (s PG) GetApproval(ctx context.Context, id uuid.UUID) (types.ApprovalRequest, error)

GetApproval returns the approval for id, or ErrNotFound.

func (PG) GetAttachLease added in v0.8.6

func (s PG) GetAttachLease(ctx context.Context, runID uuid.UUID) (AttachLease, bool, error)

GetAttachLease — see AttachLeaseStore.

func (PG) GetAuditDeliveryCursor added in v0.9.0

func (s PG) GetAuditDeliveryCursor(ctx context.Context, dest string) (int64, string, error)

GetAuditDeliveryCursor returns dest's acknowledged position and the row_hash that seq carried, (0, "") while no checkpoint row exists — the same zero-value-on-no-row contract as GetFederationCursor, so a cursor loaded before Ensure reads as "deliver from the beginning".

func (PG) GetBaseImage added in v0.5.0

func (s PG) GetBaseImage(ctx context.Context, id uuid.UUID) (types.BaseImageEntry, error)

GetBaseImage returns the catalog row for id, or ErrNotFound. Not on the Store interface (no handler reads one image by id); kept as a PG method because store_hydrate_pg_test.go round-trips through it.

func (PG) GetBaseImagesByIDs added in v0.5.0

func (s PG) GetBaseImagesByIDs(ctx context.Context, ids []uuid.UUID) (map[uuid.UUID]types.BaseImageEntry, error)

GetBaseImagesByIDs is hydrateAll's own batched read, not a Store-interface method — its only caller is inside this package. Returns the base images for ids in ONE query, keyed by id, mirroring GetSourcesByIDs. Missing ids are simply absent (hydrateWorkspace leaves BaseImage nil for them).

func (PG) GetBranding added in v0.8.0

func (s PG) GetBranding(ctx context.Context) (types.Branding, error)

GetBranding implements BrandingStore.

func (PG) GetCapabilityEnforcement added in v0.6.0

func (s PG) GetCapabilityEnforcement(ctx context.Context) (map[string]bool, error)

GetCapabilityEnforcement returns the per-kind switch map. An ABSENT row means NOT ENFORCED, so the returned map is sparse by design and a missing key reads as false — that default is the whole zero-config back-compat story (see the migration). Never nil.

func (PG) GetComponent added in v0.8.9

func (s PG) GetComponent(ctx context.Context, id uuid.UUID, owner string) (types.Component, error)

GetComponent — see ComponentStore.

func (PG) GetCredentialDeliveryPolicy added in v0.9.0

func (s PG) GetCredentialDeliveryPolicy(ctx context.Context) (types.CredentialDeliveryPolicyDoc, error)

GetCredentialDeliveryPolicy reads the local_credential_delivery document: an empty one (every class refuses) when none was written.

func (PG) GetDelegateByID added in v0.9.0

func (s PG) GetDelegateByID(ctx context.Context, id uuid.UUID) (types.Delegate, error)

GetDelegateByID returns the portal with id, revoked or not (ErrNotFound only for an id that was never registered). The authenticating reads above filter revoked rows; this one exists to see them.

func (PG) GetDelegateByRaw added in v0.8.0

func (s PG) GetDelegateByRaw(ctx context.Context, raw string) (types.Delegate, error)

GetDelegateByRaw resolves the LIVE portal a credential stands for.

func (PG) GetDelegatedTokenByID added in v0.8.5

func (s PG) GetDelegatedTokenByID(ctx context.Context, id uuid.UUID, now time.Time) (types.DelegatedToken, error)

GetDelegatedTokenByID is GetDelegatedTokenByRaw by the grant's id: live while unexpired at now AND its portal still registered. A UI session opened through a portal re-asks it, so the session ends with the grant (#1475). Expired rows are swept on any mint, so ErrNotFound means ended.

func (PG) GetDelegatedTokenByRaw added in v0.8.0

func (s PG) GetDelegatedTokenByRaw(ctx context.Context, raw string, now time.Time) (types.DelegatedToken, error)

GetDelegatedTokenByRaw is the delegated lane's auth lookup: the token must be unexpired at now AND its portal still registered — the join is what makes revoking a portal kill all its tokens on their next request.

func (PG) GetDeviceByRaw added in v0.8.0

func (s PG) GetDeviceByRaw(ctx context.Context, raw string) (types.Device, error)

GetDeviceByRaw is the device auth-time lookup: given the bearer a device presented, resolve the live row it stands for. TRUST BOUNDARY: `revoked_at IS NULL` is in the WHERE (mirrors GetAPITokenByRaw), so a revoked/unknown/mismatched credential all fail identically with ErrNotFound.

func (PG) GetFederationCursor added in v0.8.0

func (s PG) GetFederationCursor(ctx context.Context) (int64, string, error)

GetFederationCursor returns how far this deployment's forwarder has pushed audit_events upward, and the row_hash the organisation acknowledged at that seq — 0 and "" when nothing forwarded yet (matching GetSiteConfig's zero-value-on-no-row contract).

func (PG) GetGovernanceChange added in v0.8.6

func (s PG) GetGovernanceChange(ctx context.Context, id uuid.UUID) (types.GovernanceChange, error)

GetGovernanceChange returns one change, ErrNotFound when none; a pending row past its expiry reads as expired.

func (PG) GetGovernanceProfile added in v0.7.0

func (s PG) GetGovernanceProfile(ctx context.Context, id uuid.UUID) (types.GovernanceProfile, error)

GetGovernanceProfile returns one profile by id, or ErrNotFound. The RAW row: a composed profile carries no authority here (see GetGovernanceProfileChain).

func (PG) GetGovernanceProfileChain added in v0.8.6

func (s PG) GetGovernanceProfileChain(ctx context.Context, id uuid.UUID) ([]types.GovernanceProfile, error)

GetGovernanceProfileChain reads one profile and its ancestors in ONE statement, leaf first, bounded at depth 3 (the profile, its base, that base's base). ErrNotFound when the leaf is gone. It returns whatever the bound reached, cycle or overflow included: the caller refuses a chain whose last row still names a base or whose ids repeat, and never composes a partial one.

func (PG) GetIdentity added in v0.8.6

func (s PG) GetIdentity(ctx context.Context, id uuid.UUID) (PrincipalIdentity, error)

GetIdentity is the row stored under id, or ErrNotFound.

func (PG) GetIdentityByObject added in v0.8.6

func (s PG) GetIdentityByObject(ctx context.Context, issuer, tenantID, objectID string) (PrincipalIdentity, error)

func (PG) GetInvitation added in v0.9.0

func (s PG) GetInvitation(ctx context.Context, id uuid.UUID, target string) (types.Invitation, error)

func (PG) GetLaunchIntent added in v0.9.0

func (s PG) GetLaunchIntent(ctx context.Context, runID uuid.UUID, createdBy string) (LaunchIntentRecord, error)

GetLaunchIntent — see LaunchIntentStore.

func (PG) GetLaunchPreset added in v0.8.0

func (s PG) GetLaunchPreset(ctx context.Context, name string) (types.LaunchPreset, error)

GetLaunchPreset returns one preset by name, or ErrNotFound.

func (PG) GetMemberDriveOf added in v0.9.0

func (s PG) GetMemberDriveOf(ctx context.Context, owner string, id uuid.UUID) (types.UserDrive, error)

GetMemberDriveOf returns drive id if owner owns it, else ErrNotFound.

func (PG) GetPerson added in v0.8.0

func (s PG) GetPerson(ctx context.Context, principal string) (types.Person, error)

func (PG) GetPolicy

func (s PG) GetPolicy(ctx context.Context, id uuid.UUID) (types.RunPolicy, error)

GetPolicy returns the policy for id, or ErrNotFound.

func (PG) GetPrincipalPref added in v0.8.0

func (s PG) GetPrincipalPref(ctx context.Context, principal, key string) (json.RawMessage, error)

func (PG) GetPushPathList added in v0.8.0

func (s PG) GetPushPathList(ctx context.Context, approvalID uuid.UUID) (types.PushPathList, error)

func (PG) GetRef added in v0.3.1

func (s PG) GetRef(ctx context.Context, ref string) (string, bool, error)

GetRef returns the substrate name for ref. A missing row is (found=false, nil error) — pre-migration and unknown refs are expected, not errors.

func (PG) GetRemoteRunner added in v0.9.0

func (s PG) GetRemoteRunner(ctx context.Context, id string) (RemoteRunnerRow, error)

func (PG) GetRun

func (s PG) GetRun(ctx context.Context, id uuid.UUID) (types.AgentRun, error)

GetRun returns the run for id, or ErrNotFound.

func (PG) GetRunLayout added in v0.5.0

func (s PG) GetRunLayout(ctx context.Context, principal, preset string) (types.RunLayout, error)

GetRunLayout reads the one (principal, preset) row scoped to principal — a layout is never looked up by anything but its owner.

func (PG) GetRunOutput added in v0.8.6

func (s PG) GetRunOutput(ctx context.Context, runID uuid.UUID) (RunOutput, bool, error)

GetRunOutput — see RunOutputStore.

func (PG) GetRunProxyConfig added in v0.8.0

func (s PG) GetRunProxyConfig(ctx context.Context, id uuid.UUID) ([]byte, error)

GetRunProxyConfig — see RunProxyConfigs.

func (PG) GetRunQueueEntry added in v0.9.0

func (s PG) GetRunQueueEntry(ctx context.Context, id uuid.UUID) (RunQueueEntry, error)

GetRunQueueEntry is one waiting run's queue row, or ErrNotFound once the run has left the queue.

func (PG) GetRunner added in v0.9.0

func (s PG) GetRunner(ctx context.Context, id uuid.UUID) (types.Runner, error)

func (PG) GetRunnerPool added in v0.9.0

func (s PG) GetRunnerPool(ctx context.Context, id uuid.UUID) (types.RunnerPool, error)

func (PG) GetRunnerPoolOrgDefaults added in v0.9.0

func (s PG) GetRunnerPoolOrgDefaults(ctx context.Context) (types.RunnerPoolDefaults, error)

GetRunnerPoolOrgDefaults reads the organisation's defaults; the zero value when none were written.

func (PG) GetSSHKeyByFingerprint added in v0.5.0

func (s PG) GetSSHKeyByFingerprint(ctx context.Context, fingerprint string) (types.SSHPublicKey, error)

GetSSHKeyByFingerprint is the gateway's pre-auth lookup: resolves which principal (if any) registered the offered fingerprint, and with which role, the admin-override signal. Deliberately UNSCOPED by principal — the caller has not authenticated yet; this call is what authenticates them. Returns ErrNotFound when unregistered.

func (PG) GetScimGroup added in v0.8.6

func (s PG) GetScimGroup(ctx context.Context, id uuid.UUID) (ScimGroup, error)

GetScimGroup is the group stored under id, or ErrNotFound.

func (PG) GetServiceIdentity added in v0.9.0

func (s PG) GetServiceIdentity(ctx context.Context, name string) (types.ServiceIdentity, error)

func (PG) GetSiteConfig

func (s PG) GetSiteConfig(ctx context.Context) (types.SiteConfig, error)

GetSiteConfig returns the operator-wide site config, or a ZERO-VALUE SiteConfig (not an error) when no row has been written yet — first boot has no config, and "unconfigured" is a valid, common state rather than a failure the caller must special-case.

func (PG) GetSource added in v0.5.0

func (s PG) GetSource(ctx context.Context, id uuid.UUID) (types.Source, error)

GetSource returns the source for id, or ErrNotFound.

func (PG) GetSourcesByIDs added in v0.5.0

func (s PG) GetSourcesByIDs(ctx context.Context, ids []uuid.UUID) (map[uuid.UUID]types.Source, error)

GetSourcesByIDs returns the sources for ids in ONE query, keyed by id — the hydrate pass's bulk read. Missing ids are simply absent (a dangling attachment contributes nothing; fold and mount-gate each handle it).

func (PG) GetTemplate added in v0.9.0

func (s PG) GetTemplate(ctx context.Context, id uuid.UUID) (types.RunTemplate, error)

GetTemplate — see TemplateStore.

func (PG) GetTemplateRevision added in v0.9.0

func (s PG) GetTemplateRevision(ctx context.Context, id uuid.UUID, revision int) (types.RunTemplate, error)

GetTemplateRevision — see TemplateStore.

func (PG) GetUserDrive added in v0.7.0

func (s PG) GetUserDrive(ctx context.Context, id uuid.UUID) (types.UserDrive, error)

GetUserDrive returns one drive by id, or ErrNotFound.

func (PG) GetUserType added in v0.8.0

func (s PG) GetUserType(ctx context.Context, id string) (types.UserType, error)

GetUserType returns one type by id, or ErrNotFound.

func (PG) GetWorkspace

func (s PG) GetWorkspace(ctx context.Context, id uuid.UUID) (types.Workspace, error)

GetWorkspace returns the workspace for id, or ErrNotFound.

func (PG) GroupRemovalFailures added in v0.8.6

func (s PG) GroupRemovalFailures(ctx context.Context, limit int) ([]DeprovisionFailure, error)

GroupRemovalFailures is ListDeprovisionFailures for the pendingGroupRemovalWhere rows with a recorded error: one row per identity and step, with the error of the target touched last.

func (PG) GroupRemovalIdentities added in v0.8.6

func (s PG) GroupRemovalIdentities(ctx context.Context, groupID uuid.UUID) ([]uuid.UUID, error)

GroupRemovalIdentities is every identity a group's deletion must remove as a mover: the recorded members, and anyone whose removal from this group is still pending.

func (PG) HasGroupTierAssignments added in v0.7.0

func (s PG) HasGroupTierAssignments(ctx context.Context) (bool, error)

HasGroupTierAssignments reports whether ANY group-tier assignment exists: the gate on the stale-snapshot refusal (see hasGroupTierRows).

func (PG) HasGroupTierDriveGrants added in v0.7.0

func (s PG) HasGroupTierDriveGrants(ctx context.Context) (bool, error)

HasGroupTierDriveGrants reports whether ANY group-tier grant exists: the gate on the stale-snapshot refusal (see hasGroupTierRows).

func (PG) HasRunAuditEvent added in v0.8.0

func (s PG) HasRunAuditEvent(ctx context.Context, runID uuid.UUID, f AuditFilter) (bool, error)

HasRunAuditEvent reports whether runID has an audit row matching f.

func (PG) HeartbeatRunWatcher added in v0.5.0

func (s PG) HeartbeatRunWatcher(ctx context.Context, id uuid.UUID, owner string) error

HeartbeatRunWatcher refreshes the lease on id for owner: the "still watching" write every watcher goroutine repeats while alive; its silence lets another replica's ClaimStaleRunWatchers take over.

Unconditional on current owner, since a watcher can't abort its blocking Runner.Wait anyway and two watchers are harmless (the terminal transition is a CAS). Doesn't bump updated_at either, like the claim: a heartbeat there would make every run look forever-active and disable idle auto-stop. A missing row isn't an error; the lease is advisory.

func (PG) HeldEmails added in v0.8.6

func (s PG) HeldEmails(ctx context.Context, id uuid.UUID, principals, emails []string) ([]string, error)

HeldEmails is the emails another principal holds, by DeleteUserSubjectRows' rule: another identity than id that is neither deactivated nor purged, or a live API token (unrevoked, unexpired) or people row (not deactivated) under a principal that is neither one of principals nor the address itself and whose identity is neither deactivated nor purged. A previous holder that is gone holds nothing. Matched case-insensitively; the answer is lower-cased.

func (PG) HoldsAttachLease added in v0.8.6

func (s PG) HoldsAttachLease(ctx context.Context, runID, holderID uuid.UUID) (bool, error)

HoldsAttachLease — see AttachLeaseStore.

func (PG) IdentitiesByAlias added in v0.8.6

func (s PG) IdentitiesByAlias(ctx context.Context, issuer, valueLower string) ([]PrincipalIdentity, error)

IdentitiesByAlias is the rows under issuer that have no object id and were seen under valueLower as an email or a userName: the removal-only link a SCIM user with no object-id match falls to.

func (PG) IdentitiesByPrincipal added in v0.8.6

func (s PG) IdentitiesByPrincipal(ctx context.Context, principal string) ([]PrincipalIdentity, error)

func (PG) IdentityAliasValues added in v0.8.6

func (s PG) IdentityAliasValues(ctx context.Context, id uuid.UUID) ([]string, error)

IdentityAliasValues is every alias value (email and userName) the identity has been seen under, sorted.

func (PG) IdentityBlocked added in v0.8.6

func (s PG) IdentityBlocked(ctx context.Context, principal string, epoch int64) (bool, error)

IdentityBlocked reports whether principal's identity rows refuse a credential admitted under epoch (epoch < 0: only a deactivation refuses). It is the read the cookie, API-token and SSH-key lanes make at authentication, as one statement.

func (PG) IdentityRefused added in v0.8.6

func (s PG) IdentityRefused(ctx context.Context, issuer, tenantID, objectID, principal string) (bool, error)

IdentityRefused is the admission half of the sign-in gate, read-only: whether the identity a sign-in names is deactivated or purged. An Entra sign-in is named by (issuer, tenant, object id), and every sign-in by its resolved principal; either match refuses.

func (PG) IngestDeviceAudit added in v0.8.0

func (s PG) IngestDeviceAudit(ctx context.Context, deviceID uuid.UUID, peer string, rows []types.FederatedAuditEvent) (DeviceIngestResult, error)

IngestDeviceAudit appends one device's forwarded batch to this organisation's own audit_events, as the organisation's own chained rows ("one chain per writer"). A federated row keeps the device's claimed actor_type, action, target and outcome; what marks it forwarded is the device's provenance folded into `data` (mergeDeviceOrigin), not a new audit action, so no filter/SIEM rule keyed on `action` has to learn about it. TRUST BOUNDARY: three fields are never the device's to choose — actor is FederatedActor (claim kept in data.device_origin.actor); source_ip is peer (claim kept in data.device_origin.source_ip); and a run_id naming one of this organisation's own runs refuses the batch (ErrFederatedOrgRun).

Order of work, chosen so a device can make the org's own audit writers wait for at most its inserts:

  1. Every row must be storable (FederatedRowProblem) and representable by Postgres, or ErrFederatedRowInvalid.
  2. Every claimed RowHash is recomputed in SQL before any transaction or lock, in ONE statement (verifyClaimedHashes), against its own claimed PrevHash. A refused or replayed batch never touches the chain lock.
  3. The device row is locked FOR UPDATE, serializing this device's concurrent pushes before either reaches the chain lock (deadlock-free: no transaction takes the chain lock then a device row). A revoked device answers ErrDeviceRevoked.
  4. Idempotency rides the cursor and the hash, not audit_events.id: rows at or before LastSeq that are the rows held here (heldPrefix) are skipped — the forwarder's cursor can lag what was committed. A reused seq is not a re-send.
  5. The first new row's claimed PrevHash is either empty (genesis, accepted — a chain reset if one was already recorded) or equal to LastRowHash with seq past LastSeq; every later row's claimed PrevHash equals the preceding row's claimed RowHash. Otherwise ErrConflict.
  6. Only then the chain lock (db.AuditChainLockKey), under the lock timeout, and the inserts: field for field as claimed except actor, source_ip and merged data. Cursor advances in the same transaction.

Any refusal refuses the entire batch, never a verified prefix: it's the retry unit, and a partial accept would leave the cursor mid-batch with no way to tell the caller which rows to resend.

func (PG) IngestRunnerDecision added in v0.9.0

func (s PG) IngestRunnerDecision(ctx context.Context, runnerID uuid.UUID, row runnerspool.Record, prepare func(context.Context, types.AuditEvent) (types.AuditEvent, error)) (bool, error)

IngestRunnerDecision atomically appends audit history and advances its replay receipt. Terminal-run acceptance is audit only: this transaction never touches lifecycle, idle clocks, credentials, grants, approvals or token renewal.

func (PG) InsertPendingRunOutput added in v0.8.6

func (s PG) InsertPendingRunOutput(ctx context.Context, runID uuid.UUID) error

InsertPendingRunOutput — see RunOutputStore.

func (PG) InsertRunPAT added in v0.8.2

func (s PG) InsertRunPAT(ctx context.Context, p RunPAT) error

InsertRunPAT — see RunPATStore.

func (PG) IntakeRunnerStatePage added in v0.9.0

func (s PG) IntakeRunnerStatePage(ctx context.Context, runnerID uuid.UUID, sessionID string, page runnerwire.StatePage) (RunnerInventoryResult, error)

IntakeRunnerStatePage commits a bounded page and its cursor together. A partial page never checks omissions. This path performs no wire call, credential renewal, lifecycle transition or accepted-exit acknowledgement.

func (PG) IssueLoginIdentity added in v0.8.6

func (s PG) IssueLoginIdentity(ctx context.Context, in LoginIdentity, now time.Time) (epoch int64, err error)

IssueLoginIdentity is the issuance half of the sign-in gate: in one transaction it upserts and binds the identity row (a failure refuses the sign-in), re-checks deactivation on every row bound to the principal and reads the authority epoch the session cookie will carry. The rows stay locked until the commit, so a suspension either precedes it, and the sign-in is refused with ErrIdentityDeactivated, or follows it, and bumps the epoch past the one returned. A sign-in whose identity is bound to another principal is ErrIdentityBindingMismatch: its session would carry neither that identity's deactivation nor its epoch. A refused sign-in writes nothing.

func (PG) KillRunAtLifetimeIf added in v0.9.0

func (s PG) KillRunAtLifetimeIf(ctx context.Context, id uuid.UUID, reason types.RunEndReason, detail string, now time.Time) (bool, error)

KillRunAtLifetimeIf moves a RUNNING run to KILLED and writes its status_detail in the one statement, but only while the reason still holds on the row as it stands at now: the lease sweep decides from a row it listed earlier, and a person may have moved the end, or another replica kept the run at its end, since. The conditions are the reason's own, as MarkRunEnded's are for the keep:

  • "end_reached": a Background run whose end is at or before now and before its lifetime cap (an end at the cap is the maximum);
  • "max_lifetime_reached": the lifetime cap is at or before now, and the run is not a Background run whose end came first (that run is end_reached).

Both refuse a run its own end already kept (lost_reason 'ended'): a kept run keeps its files grace. False with a nil error is a no-op for the caller: no kill, no audit row. detail is what status_detail becomes ("lifetime: <reason>").

func (PG) LastRunPAT added in v0.8.2

func (s PG) LastRunPAT(ctx context.Context, owner, providerRowID string) (RunPAT, bool, error)

LastRunPAT — see RunPATReader.

func (PG) LatestAuditEventByAction

func (s PG) LatestAuditEventByAction(ctx context.Context, action string) (types.AuditEvent, error)

LatestAuditEventByAction returns the most recent audit event whose action equals the given action, or ErrNotFound when none exists. Used by /healthz to find the latest kernel.sensor.ping driving the eBPF health state.

Most recent by `time`, picked from a seq-ordered window: the audit spool replays at-least-once while keeping each event's original ev.Time, so a beat spooled through a DB blip could carry the highest seq but an hours-old time and falsely mark the sensor degraded. The window orders by seq to keep using the purpose-built (action, seq DESC) index; ordering by time directly would sort the whole action's history on every probe. Twenty is a window, not a proof — a longer replay backlog can push the fresh row out, bounding the staleness to one heartbeat interval against /healthz's own TTL.

func (PG) LeaveRunQueue added in v0.9.0

func (s PG) LeaveRunQueue(ctx context.Context, id uuid.UUID, to types.RunState, admittedAt *time.Time) (bool, error)

LeaveRunQueue is the one way a run leaves QUEUED: one statement moves it to `to` (only while it is still QUEUED) and deletes its run_queue row, so no exit can keep the row or drop it without the state. admittedAt is promotion's stamp (QUEUED to PENDING) and nil for every other exit. false with a nil error means another exit won first. UpdateRunStateIf from QUEUED is this call with no stamp.

func (PG) LiftComponentRestriction added in v0.8.9

func (s PG) LiftComponentRestriction(ctx context.Context, id uuid.UUID, by string) error

LiftComponentRestriction — see ComponentRestrictionLifter.

func (PG) ListAPITokens added in v0.6.0

func (s PG) ListAPITokens(ctx context.Context) ([]types.APIToken, error)

ListAPITokens returns every token in the deployment, newest first (admin GET /tokens); revoked rows included, same reason as the self-service list.

func (PG) ListAPITokensByIdentity added in v0.9.0

func (s PG) ListAPITokensByIdentity(ctx context.Context, name string) ([]types.APIToken, error)

ListAPITokensByIdentity returns the tokens whose principal is name or whose email equals it ignoring case (the match IsSessionRevoked's email arm makes), revoked rows included. The principal matches come first, newest first, then the rows only the email names. It reads api_tokens_principal_idx and api_tokens_lower_email_idx (migration 0176), not the whole table. The email arm follows the session-cut rule (lower(sub) = lower($2)), so for non-ASCII case pairs PG lower() can differ from the scan fallback's strings.EqualFold. name must not be empty: "" is the revoke-all arm, which lists everything.

func (PG) ListAPITokensByPrincipal added in v0.6.0

func (s PG) ListAPITokensByPrincipal(ctx context.Context, principal string) ([]types.APIToken, error)

ListAPITokensByPrincipal returns principal's own tokens, newest first (self-service GET /me/tokens). Revoked rows are INCLUDED so a human can confirm a retired token is retired; the row carries no usable credential either way.

func (PG) ListAPITokensByPrincipalPage added in v0.8.0

func (s PG) ListAPITokensByPrincipalPage(ctx context.Context, principal string, p Page) ([]types.APIToken, error)

ListAPITokensByPrincipalPage is ListAPITokensByPrincipal bounded by p.

func (PG) ListApprovals

func (s PG) ListApprovals(ctx context.Context, stateFilter types.ApprovalState) ([]types.ApprovalRequest, error)

ListApprovals returns approvals filtered by state. Pass empty string to list all.

func (PG) ListApprovalsPage added in v0.3.1

func (s PG) ListApprovalsPage(ctx context.Context, stateFilter types.ApprovalState, p Page) ([]types.ApprovalRequest, error)

ListApprovalsPage returns approvals filtered by state (empty = all) in reverse request order, bounded by p. The all-state feed rides approvals_requested_at_idx; a single-state filter rides approvals_state_requested_at_idx, serving both WHERE and ORDER BY without a sort.

func (PG) ListApprovalsPageByRun added in v0.7.0

func (s PG) ListApprovalsPageByRun(ctx context.Context, runID uuid.UUID, stateFilter types.ApprovalState, p Page) ([]types.ApprovalRequest, error)

ListApprovalsPageByRun is ListApprovalsPage narrowed to one run, same state filter and ordering.

ponytail: no new index. approvals_run_idx already makes this an index scan, and one run's approvals are few enough that sorting by requested_at is free — upgrade to a composite (run_id, requested_at DESC) if that changes.

func (PG) ListApprovalsPageByRunCreator added in v0.5.0

func (s PG) ListApprovalsPageByRunCreator(ctx context.Context, createdBy string, stateFilter types.ApprovalState, p Page) ([]types.ApprovalRequest, error)

ListApprovalsPageByRunCreator is ListApprovalsPage narrowed to approvals on runs createdBy owns, via a JOIN on agent_runs (approvals has no created_by of its own). Same state filter and ordering as ListApprovalsPage.

func (PG) ListAuditEventsAfterSeq added in v0.8.0

func (s PG) ListAuditEventsAfterSeq(ctx context.Context, seq int64, limit int) ([]types.FederatedAuditEvent, error)

ListAuditEventsAfterSeq returns this deployment's own audit_events strictly after seq, oldest first — the forwarder's read of its local chain to build the next batch IngestDeviceAudit expects. limit<=0 defaults to 1000, matching QueryAuditEvents/QueryRecentAuditEvents.

func (PG) ListBaseImages added in v0.5.0

func (s PG) ListBaseImages(ctx context.Context) ([]types.BaseImageEntry, error)

ListBaseImages returns the whole catalog, newest first.

func (PG) ListCapabilityGrants added in v0.6.0

func (s PG) ListCapabilityGrants(ctx context.Context) ([]types.CapabilityGrant, error)

ListCapabilityGrants returns every grant, oldest first — the admin Permissions screen's whole table in one read.

func (PG) ListCapabilityGrantsFor added in v0.6.0

func (s PG) ListCapabilityGrantsFor(ctx context.Context, users, groups []string, userType string) ([]types.CapabilityGrant, error)

ListCapabilityGrantsFor returns every grant that could apply to one caller: `all` rows, `user` rows naming any of users (the caller's lowercased sub AND email), `group` rows naming any of groups, and `user_type` rows naming the caller's one type. An empty userType matches no row, since the write boundary refuses an empty subject.

Deliberately NOT filtered by capability: the resolver needs one kind and GET /me/capabilities needs all four, and one round trip serving both beats two indexes and two queries. Per-kind/per-value matching happens in Go.

ponytail: no cache. This runs per request, on the (subject_type, subject) index; a process-local cache is the HA blocker OPERATIONS already names for other state, and a stale permission cache is a security bug, not a slow page. Add one only behind a shared invalidation channel.

func (PG) ListCapabilityGrantsForPage added in v0.8.0

func (s PG) ListCapabilityGrantsForPage(ctx context.Context, users, groups []string, userType string, p Page) ([]types.CapabilityGrant, error)

ListCapabilityGrantsForPage is ListCapabilityGrantsFor bounded by p: same four subject arms, so a page never drops a `user_type` grant the unpaged read returns.

func (PG) ListCapabilityRestrictions added in v0.8.0

func (s PG) ListCapabilityRestrictions(ctx context.Context) (map[string]map[string]bool, error)

ListCapabilityRestrictions returns every restricted value, kind -> set. The table holds one row per restricted admin-configured resource, so one read per resolution answers every value it asks about. Never nil.

func (PG) ListComponents added in v0.8.9

func (s PG) ListComponents(ctx context.Context, owner string) ([]types.Component, error)

ListComponents — see ComponentStore.

func (PG) ListComponentsByIDs added in v0.8.9

func (s PG) ListComponentsByIDs(ctx context.Context, owner string, ids []uuid.UUID) ([]types.Component, error)

ListComponentsByIDs — see ComponentStore.

func (PG) ListDeactivatedIdentities added in v0.8.6

func (s PG) ListDeactivatedIdentities(ctx context.Context, limit int) ([]PrincipalIdentity, error)

ListDeactivatedIdentities is the identities deactivated and not purged, most recently deactivated first, at most limit.

func (PG) ListDecidedAlwaysEgressPage added in v0.9.0

func (s PG) ListDecidedAlwaysEgressPage(ctx context.Context, state types.ApprovalState, afterRequestedAt time.Time, afterID uuid.UUID, limit int) ([]types.ApprovalRequest, error)

ListDecidedAlwaysEgressPage — see DecidedAlwaysEgressPager.

func (PG) ListDelegates added in v0.8.0

func (s PG) ListDelegates(ctx context.Context) ([]types.Delegate, error)

ListDelegates returns every portal, newest first, including revoked — admins need to see that a retired portal is retired.

func (PG) ListDeprovisionFailures added in v0.8.6

func (s PG) ListDeprovisionFailures(ctx context.Context, limit int) ([]DeprovisionFailure, error)

ListDeprovisionFailures is the suspend and purge ledger rows that are pending with a recorded error for an identity still deactivated or purged (the sweeper's scope: a reactivated person's leftover rows are not unfinished work), oldest identity first, at most limit. One row per identity, kind and step: a step that fails for several targets is listed once, with the error of the one touched last.

func (PG) ListDeprovisionJobs added in v0.8.6

func (s PG) ListDeprovisionJobs(ctx context.Context, identityID uuid.UUID, kind string) ([]DeprovisionJob, error)

ListDeprovisionJobs is the ledger of one identity and kind, oldest first.

func (PG) ListDevices added in v0.8.0

func (s PG) ListDevices(ctx context.Context) ([]types.Device, error)

ListDevices returns every enrolled device, newest first, revoked rows included (an admin needs to see a retired device is retired). Phase one has no console surface for this (CLI/API only).

func (PG) ListEnrolmentTokens added in v0.8.0

func (s PG) ListEnrolmentTokens(ctx context.Context, now time.Time) ([]types.DeviceEnrolmentToken, error)

ListEnrolmentTokens returns every token still redeemable at now (unconsumed, unexpired), newest first — the ones an admin may still cancel; a redeemed or expired token is never listed.

func (PG) ListGovernanceAssignments added in v0.7.0

func (s PG) ListGovernanceAssignments(ctx context.Context) ([]types.GovernanceAssignment, error)

ListGovernanceAssignments returns every assignment in the order the resolver itself would rank them (tier, then priority, then subject), so the console's table reads top-down as the precedence rule.

func (PG) ListGovernanceChanges added in v0.8.6

func (s PG) ListGovernanceChanges(ctx context.Context, state string) ([]types.GovernanceChange, error)

ListGovernanceChanges returns changes newest first. state narrows to one state ("" is every state); a pending row past its expiry reads as expired.

func (PG) ListGovernanceProfiles added in v0.7.0

func (s PG) ListGovernanceProfiles(ctx context.Context) ([]types.GovernanceProfile, error)

ListGovernanceProfiles returns every profile by name (the handle an admin looks for), unlike the creation order other List* calls use. Deployment- wide config, so there's no per-caller scoping to filter on.

func (PG) ListGrantsByRun

func (s PG) ListGrantsByRun(ctx context.Context, runID uuid.UUID) ([]types.CredentialGrant, error)

ListGrantsByRun returns all grants for a run.

func (PG) ListGrantsByRunPage added in v0.8.0

func (s PG) ListGrantsByRunPage(ctx context.Context, runID uuid.UUID, p Page) ([]types.CredentialGrant, error)

ListGrantsByRunPage is ListGrantsByRun bounded by p.

func (PG) ListGroupDenyGrants added in v0.7.0

func (s PG) ListGroupDenyGrants(ctx context.Context, capability string) ([]types.CapabilityGrant, error)

ListGroupDenyGrants returns the GROUP-subject DENY rows of one capability kind — the only rows internal/api's unresolvable-group-deny check can match.

It exists because that check runs on the FAIL-CLOSED path every caller with an unanswerable group snapshot takes (every pre-0.7 API token, every request): answering it with ListCapabilityGrants would let one such token force an unbounded full-table read per checked value — an authorization question whose cost scales with table size is an availability surface, not just a slow page. The predicate is what internal/api applied in Go afterwards, moved into SQL; value-overlap matching stays in Go (TestCapUnresolvableGroupDenyMatchesFullScan pins the two together). The (subject_type, subject) index already serves it.

ponytail: no EXISTS fast path in front of it. It would be a second round trip to save a query that already returns nothing on the deployments the fast path would help.

func (PG) ListInvitations added in v0.9.0

func (s PG) ListInvitations(ctx context.Context, target string, state types.InvitationState, now time.Time, p Page) ([]types.Invitation, error)

func (PG) ListLapsedTokenRuns added in v0.8.0

func (s PG) ListLapsedTokenRuns(ctx context.Context, life time.Duration) ([]types.AgentRun, error)

ListLapsedTokenRuns — see RunLoser. Both sides use the database's clock, so replica clock skew cannot make a live run look lapsed.

func (PG) ListLaunchIntents added in v0.9.0

func (s PG) ListLaunchIntents(ctx context.Context, createdBy string, limit int) ([]LaunchIntentRecord, error)

ListLaunchIntents — see LaunchIntentStore.

func (PG) ListLaunchPresets added in v0.8.0

func (s PG) ListLaunchPresets(ctx context.Context) ([]types.LaunchPreset, error)

ListLaunchPresets returns every preset by name.

func (PG) ListLeasedRuns added in v0.8.0

func (s PG) ListLeasedRuns(ctx context.Context) ([]types.AgentRun, error)

ListLeasedRuns reads the runs the lease sweep acts on. RUNNING only: a run still starting reaches its end on the first sweep after it is up, and a terminal run has nothing left to end.

func (PG) ListLiveRunnerRuns added in v0.9.0

func (s PG) ListLiveRunnerRuns(ctx context.Context, runnerID uuid.UUID) ([]types.AgentRun, error)

ListLiveRunnerRuns is every run bound to the runner that is starting, running or held for a confirmation and not lost: the runs a runner's new session must be leased again.

func (PG) ListMemberDrives added in v0.9.0

func (s PG) ListMemberDrives(ctx context.Context, p Page) ([]types.UserDrive, int, error)

ListMemberDrives returns one page of every person's drives, by owner then name, and the total count.

func (PG) ListMemberDrivesOf added in v0.9.0

func (s PG) ListMemberDrivesOf(ctx context.Context, owner string) ([]types.UserDrive, error)

ListMemberDrivesOf returns owner's drives by name, tombstoned ones included.

func (PG) ListNonTerminalRunsBy added in v0.8.6

func (s PG) ListNonTerminalRunsBy(ctx context.Context, createdBy string) ([]types.AgentRun, error)

ListNonTerminalRunsBy is createdBy's runs that have not ended.

func (PG) ListPauseCandidates added in v0.8.0

func (s PG) ListPauseCandidates(ctx context.Context) ([]PauseCandidate, time.Time, error)

ListPauseCandidates — see RunPauser.

func (PG) ListPendingApprovalsForRuns added in v0.8.0

func (s PG) ListPendingApprovalsForRuns(ctx context.Context, runIDs []uuid.UUID) ([]types.ApprovalRequest, error)

ListPendingApprovalsForRuns — see ApprovalsForRunsPager. Backed by approvals_run_idx and the partial approvals_state_idx: an index scan over run_id = ANY($1) intersected with the partial index, not a table scan.

func (PG) ListPendingRecordingRunOutputs added in v0.8.9

func (s PG) ListPendingRecordingRunOutputs(ctx context.Context, staleAfter time.Duration, limit int) ([]uuid.UUID, error)

ListPendingRecordingRunOutputs — see RunOutputStore.

func (PG) ListPendingRunnerActions added in v0.9.0

func (s PG) ListPendingRunnerActions(ctx context.Context, runnerID uuid.UUID) ([]types.RunnerPendingAction, bool, error)

ListPendingRunnerActions is the runner's PENDING payload: at most one page of its oldest open bound actions, and whether more remain. A row from before the binding is never delivered; it keeps fencing its run instead.

func (PG) ListPeople added in v0.8.0

func (s PG) ListPeople(ctx context.Context) ([]types.Person, error)

func (PG) ListPeopleDirectory added in v0.8.6

func (s PG) ListPeopleDirectory(ctx context.Context, f PeopleDirectoryFilter) (PeopleDirectoryPage, error)

func (PG) ListPolicies

func (s PG) ListPolicies(ctx context.Context) ([]types.RunPolicy, error)

ListPolicies returns all policies in reverse creation order. The slice is empty (never nil) when no policies exist.

func (PG) ListPoliciesPage added in v0.3.1

func (s PG) ListPoliciesPage(ctx context.Context, p Page) ([]types.RunPolicy, error)

ListPoliciesPage returns policies in reverse creation order, bounded by p (run_policies_created_at_idx covers the ORDER BY).

func (PG) ListProfiledLiveRuns added in v0.8.0

func (s PG) ListProfiledLiveRuns(ctx context.Context) ([]types.AgentRun, error)

ListProfiledLiveRuns — see RunLimitsReclamper.

func (PG) ListRemoteRunners added in v0.9.0

func (s PG) ListRemoteRunners(ctx context.Context) ([]RemoteRunnerRow, error)

func (PG) ListRoleMappings added in v0.7.0

func (s PG) ListRoleMappings(ctx context.Context) ([]types.RoleMapping, error)

ListRoleMappings returns every row, oldest first — both the console's People screen and internal/auth/oidc's login-time merge (mergeRoleMaps) read the whole table in one call; there is no per-caller scoping to filter on, this is deployment-wide config.

func (PG) ListRunComponents added in v0.8.9

func (s PG) ListRunComponents(ctx context.Context, runID uuid.UUID) ([]types.RunComponent, error)

ListRunComponents — see ComponentStore.

func (PG) ListRunHarnesses added in v0.9.0

func (s PG) ListRunHarnesses(ctx context.Context, runID uuid.UUID) ([]types.RunHarness, error)

ListRunHarnesses — see RunHarnessStore.

func (PG) ListRunPATs added in v0.8.2

func (s PG) ListRunPATs(ctx context.Context, runID uuid.UUID) ([]RunPAT, error)

ListRunPATs — see RunPATReader. The primary key's run_id prefix serves it.

func (PG) ListRunProxyReleases added in v0.8.0

func (s PG) ListRunProxyReleases(ctx context.Context) ([]RunProxyRelease, error)

ListRunProxyReleases — see RunReviver.

func (PG) ListRunnerExitWork added in v0.9.0

func (s PG) ListRunnerExitWork(ctx context.Context, after uuid.UUID) ([]RunnerExitWork, error)

ListRunnerExitWork pages by run UUID. Generation cannot advance while one incomplete receipt exists, so a run has at most one item in this work list.

func (PG) ListRunnerLapsedRuns added in v0.9.0

func (s PG) ListRunnerLapsedRuns(ctx context.Context, runnerID, after uuid.UUID) ([]types.AgentRun, error)

ListRunnerLapsedRuns is a bounded page for the offline callback. The loss claim rechecks all authority and DB lapse in MarkRunnerRunLost; this list is only a candidate scan and grants no authority to mutate a run.

func (PG) ListRunnerPoolCatalogue added in v0.9.0

func (s PG) ListRunnerPoolCatalogue(ctx context.Context, owner string, all bool) ([]RunnerPoolCatalogueRow, error)

ListRunnerPoolCatalogue returns the live organization pools and owner's own live user pools (with all, every person's: the organisation's first, then owner's own), by name, each with its use policy, remote-hosted members, the count of owner's own claimed runners it holds and the ids of owner's runners in it.

func (PG) ListRunnerPoolMembers added in v0.9.0

func (s PG) ListRunnerPoolMembers(ctx context.Context, id uuid.UUID) ([]types.RunnerPoolMember, error)

func (PG) ListRunnerPoolRunners added in v0.9.0

func (s PG) ListRunnerPoolRunners(ctx context.Context, poolID uuid.UUID) ([]types.Runner, error)

ListRunnerPoolRunners is a Self-Hosted pool's member runners, newest first: only runners its owner holds, so a pool never shows anyone else's runner. A Remote Provided pool has none.

func (PG) ListRunners added in v0.9.0

func (s PG) ListRunners(ctx context.Context) ([]types.Runner, error)

ListRunners returns every runner, newest first, revoked included.

func (PG) ListRunnersByOwner added in v0.9.0

func (s PG) ListRunnersByOwner(ctx context.Context, owner string) ([]types.Runner, error)

func (PG) ListRunnersPage added in v0.9.0

func (s PG) ListRunnersPage(ctx context.Context, filter types.RunnerFilter, now time.Time, p Page) ([]types.Runner, error)

ListRunnersPage — see RunnerInventoryStore.

func (PG) ListRuns

func (s PG) ListRuns(ctx context.Context) ([]types.AgentRun, error)

ListRuns returns all runs in reverse creation order (unbounded).

func (PG) ListRunsFiltered added in v0.8.0

func (s PG) ListRunsFiltered(ctx context.Context, f RunFilter, p Page) ([]types.AgentRun, error)

ListRunsFiltered is ListRunsPage narrowed and reordered per f. Owner- scoped reads ride agent_runs_created_by_idx (bitmap scan + in-memory sort); an org-wide read is a parallel seq scan with top-N heapsort — both linear in the caller's history, which the spec accepted (no ended_at index backs this CASE-based order/window).

func (PG) ListRunsPage added in v0.3.1

func (s PG) ListRunsPage(ctx context.Context, p Page) ([]types.AgentRun, error)

ListRunsPage returns runs in reverse creation order, bounded by p. agent_runs_created_at_idx makes the ORDER BY + LIMIT an index scan.

func (PG) ListRunsPageByCreator added in v0.5.0

func (s PG) ListRunsPageByCreator(ctx context.Context, createdBy string, p Page) ([]types.AgentRun, error)

ListRunsPageByCreator is ListRunsPage narrowed to one creator, same order. ponytail: no dedicated (created_by, created_at) index yet — agent_runs is small enough per-operator that the existing created_at index plus a filter scan is fine; add one if a member's run list ever gets slow.

func (PG) ListRunsPageByCreatorOrSponsor added in v0.9.0

func (s PG) ListRunsPageByCreatorOrSponsor(ctx context.Context, principal string, p Page) ([]types.AgentRun, error)

ListRunsPageByCreatorOrSponsor is ListRunsPageByCreator plus the runs of the service identities principal sponsors, same order.

func (PG) ListSSHKeysByPrincipal added in v0.5.0

func (s PG) ListSSHKeysByPrincipal(ctx context.Context, principal string) ([]types.SSHPublicKey, error)

ListSSHKeysByPrincipal returns principal's own registered keys, newest first — the self-service GET /me/ssh-keys list.

func (PG) ListSSHKeysByPrincipalPage added in v0.8.0

func (s PG) ListSSHKeysByPrincipalPage(ctx context.Context, principal string, p Page) ([]types.SSHPublicKey, error)

ListSSHKeysByPrincipalPage is ListSSHKeysByPrincipal bounded by p.

func (PG) ListServiceIdentities added in v0.9.0

func (s PG) ListServiceIdentities(ctx context.Context, sponsor string, p Page) ([]types.ServiceIdentity, error)

func (PG) ListSources added in v0.5.0

func (s PG) ListSources(ctx context.Context) ([]types.Source, error)

ListSources returns the whole library, newest first.

func (PG) ListStalePendingRunOutputs added in v0.8.6

func (s PG) ListStalePendingRunOutputs(ctx context.Context, age time.Duration, limit int) ([]uuid.UUID, error)

ListStalePendingRunOutputs — see RunOutputStore.

func (PG) ListTemplateGroupAdmins added in v0.9.0

func (s PG) ListTemplateGroupAdmins(ctx context.Context) ([]types.TemplateGroupAdmin, error)

ListTemplateGroupAdmins — see TemplateStore.

func (PG) ListTemplatesVisible added in v0.9.0

func (s PG) ListTemplatesVisible(ctx context.Context, person string, groups []string) ([]types.RunTemplate, error)

ListTemplatesVisible — see TemplateStore.

func (PG) ListUnrevokedRunPATs added in v0.8.2

func (s PG) ListUnrevokedRunPATs(ctx context.Context, f RunPATFilter) ([]RunPAT, error)

ListUnrevokedRunPATs — see RunPATStore.

func (PG) ListUnusedRunnerRegistrationTokensPage added in v0.9.0

func (s PG) ListUnusedRunnerRegistrationTokensPage(ctx context.Context, now time.Time, owner string, p Page) ([]types.RunnerRegistrationToken, error)

ListUnusedRunnerRegistrationTokensPage — see RunnerInventoryStore. Never a token value: the table holds only its hash, which the wire type does not serialize.

func (PG) ListUserDriveGrants added in v0.7.0

func (s PG) ListUserDriveGrants(ctx context.Context) ([]types.UserDriveGrant, error)

ListUserDriveGrants returns every grant in the order the resolver itself would rank them (tier, then priority, then subject) so the console's table reads top-down as the precedence rule, not insertion order.

func (PG) ListUserDriveGrantsPage added in v0.7.0

func (s PG) ListUserDriveGrantsPage(ctx context.Context, p Page) ([]types.UserDriveGrant, error)

ListUserDriveGrantsPage is ListUserDriveGrants bounded to one window. The LIMIT changes the plan, not just the size: this ORDER BY has no serving index, so unbounded it's a Seq Scan + full sort (50k rows, PG17: 149.7ms disk sort); LIMIT turns it into a top-N heapsort (37.7ms, in memory). The Seq Scan itself needs an index (a migration) to remove.

func (PG) ListUserDrives added in v0.7.0

func (s PG) ListUserDrives(ctx context.Context) ([]types.UserDriveListItem, error)

ListUserDrives returns every drive by name (the handle an admin looks for) with its grant count, in one read — the console's whole table. The count rides along so the delete affordance is honest (a drive with grants answers 409, visible before clicking); a grouped subquery, not a JOIN, keeps the drive row single. Member drives are listed too, for the recovery rehearsal; the admin handler filters them out.

func (PG) ListUserTypes added in v0.8.0

func (s PG) ListUserTypes(ctx context.Context) ([]types.UserType, error)

ListUserTypes returns every type: the built-in first, then by priority (highest first) and name.

func (PG) ListWorkspaces

func (s PG) ListWorkspaces(ctx context.Context) ([]types.Workspace, error)

ListWorkspaces returns all workspaces in reverse creation order. The slice is empty (never nil) when no workspaces exist.

func (PG) ListWorkspacesPage added in v0.3.1

func (s PG) ListWorkspacesPage(ctx context.Context, p Page) ([]types.Workspace, error)

ListWorkspacesPage returns workspaces in reverse creation order, bounded by p (workspaces_created_at_idx covers the ORDER BY). The id breaks a tie, so the order is total: a run's fold reads this list twice and credits a source two workspaces carry from it.

func (PG) ListWorkspacesPageForOwner added in v0.6.0

func (s PG) ListWorkspacesPageForOwner(ctx context.Context, owner string, p Page) ([]types.Workspace, error)

ListWorkspacesPageForOwner is ListWorkspacesPage narrowed to what one member may see: their own owned rows plus every operator-owned row (”, every pre-0.6 workspace). workspaces_owned_by_idx covers the IN.

func (PG) LockLoginSupersede added in v0.8.0

func (s PG) LockLoginSupersede(ctx context.Context, actor string) (func(), error)

LockLoginSupersede holds db.LoginSupersedeLockClass keyed to actor for up to db.LoginSupersedeLockWait. Session-scoped, not transaction-scoped: it guards several independent statements, including a later read-modify-write on the capture path. Borrows one connection from this pool for the hold's duration; see ErrLoginLockNoCapacity for the one error a caller may proceed on.

func (PG) Locker added in v0.8.6

func (s PG) Locker() db.Locker

Locker is the cross-replica locker over this store's database (db.LockerFor): every PG over one pool shares it. The api package takes its ordered locks through it when the store offers one, and falls back to an in-process locker for a store with no database (nil here, for a PG with no pool).

func (PG) MarkAWSSSOTokenSpent added in v0.8.0

func (s PG) MarkAWSSSOTokenSpent(ctx context.Context, fingerprint, owner string, markedAt time.Time) error

MarkAWSSSOTokenSpent upserts idempotently: ON CONFLICT DO NOTHING keeps row age "since first spent" even on a second failed persist or invalid_grant.

func (PG) MarkFederationRevoked added in v0.8.0

func (s PG) MarkFederationRevoked(ctx context.Context) error

MarkFederationRevoked durably records that the organisation revoked this laptop, so a restart comes back still refusing new runs.

func (PG) MarkIdentityPurged added in v0.8.6

func (s PG) MarkIdentityPurged(ctx context.Context, id uuid.UUID, requireDue bool) (bool, error)

MarkIdentityPurged turns the identity into a permanent tombstone (purged_at set) and records the purge's last step as pending, in one transaction that holds the row FOR UPDATE. It reports whether this call marked it: false when the row was already purged and, with requireDue, when it is not (still) deactivated by its own suspension (ownSuspension) with a purge_after that has passed. The re-check under the lock is what lets a reactivation that committed first win; once the mark commits a reactivation is ErrIdentityPurged.

func (PG) MarkPersonSignedIn added in v0.8.0

func (s PG) MarkPersonSignedIn(ctx context.Context, principal string, now time.Time) error

func (PG) MarkRunAgentStopped added in v0.9.0

func (s PG) MarkRunAgentStopped(ctx context.Context, id uuid.UUID, lostAt time.Time) error

MarkRunAgentStopped — see RunLeaser.

func (PG) MarkRunEnded added in v0.8.0

func (s PG) MarkRunEnded(ctx context.Context, id uuid.UUID, now time.Time) (bool, error)

MarkRunEnded — see RunLeaser.

func (PG) MarkRunEndingSoon added in v0.8.0

func (s PG) MarkRunEndingSoon(ctx context.Context, id uuid.UUID, endsAt time.Time, thresholdSec int) (bool, error)

MarkRunEndingSoon — see RunLeaser. Keyed on the end itself, so moving the end re-arms every warning without anyone having to reset a counter.

func (PG) MarkRunLost added in v0.8.0

func (s PG) MarkRunLost(ctx context.Context, id uuid.UUID, reason types.LostReason, now time.Time, tokenLife time.Duration) (bool, error)

MarkRunLost — see RunLoser.

func (PG) MarkRunOutputIncomplete added in v0.8.6

func (s PG) MarkRunOutputIncomplete(ctx context.Context, runID uuid.UUID) (bool, error)

MarkRunOutputIncomplete — see RunOutputStore.

func (PG) MarkRunPATRevoked added in v0.8.2

func (s PG) MarkRunPATRevoked(ctx context.Context, runID, authorizationID uuid.UUID, reason, lastError string) (bool, error)

MarkRunPATRevoked — see RunPATStore.

func (PG) MarkRunPaused added in v0.8.0

func (s PG) MarkRunPaused(ctx context.Context, id uuid.UUID, reason types.PauseReason, activeAt *time.Time) (bool, error)

MarkRunPaused — see RunPauser.

func (PG) MarkRunRevived added in v0.8.0

func (s PG) MarkRunRevived(ctx context.Context, id uuid.UUID, from types.LostReason, ended *EndedKept, limit int, startsAgent bool) (bool, error)

MarkRunRevived — see RunReviver.

func (PG) MarkRunnerRunLost added in v0.9.0

func (s PG) MarkRunnerRunLost(ctx context.Context, runnerID, runID uuid.UUID, expectedRef string, at time.Time) (bool, error)

MarkRunnerRunLost uses the existing loss CAS, with authenticated ownership checked in the same committing transaction. A fresh drop never claims loss.

func (PG) MergeWorkspaceRequirements added in v0.5.0

func (s PG) MergeWorkspaceRequirements(ctx context.Context, id uuid.UUID, add map[string]types.WorkspaceRequirement) (types.Workspace, error)

MergeWorkspaceRequirements ADDS rows to a workspace's requirements overlay atomically (`jsonb ||`), so a concurrent edit can never be clobbered by read-modify-write. The WHERE clause enforces the same key-count cap the PUT endpoint does, evaluated on the POST-merge total so a multi-key merge can't overshoot it in one jump; at the cap it returns ErrConflict.

func (PG) MintAttachTicket added in v0.5.0

func (s PG) MintAttachTicket(ctx context.Context, token string, t AttachTicket, now, expiresAt time.Time) error

MintAttachTicket records one outstanding ticket, expiring at expiresAt, and sweeps already-expired rows in the same statement. The sweep compares against the caller's clock (now), NOT now(), so a DB clock running ahead can't silently delete live tickets.

ponytail: the sweep rides the mint instead of a background worker — the table holds only unredeemed tickets inside a 30s TTL, i.e. a handful of rows. Add a sweeper (or an expires_at index) only if mint volume ever makes that false.

func (PG) MintDelegatedToken added in v0.8.0

func (s PG) MintDelegatedToken(ctx context.Context, t types.DelegatedToken, raw string, now time.Time) (types.DelegatedToken, error)

MintDelegatedToken stores one token (raw is plaintext; only its hash persists), stamps the portal's last use, and sweeps expired tokens. created_at is written on the DB clock, back-dated by t.CreatedAt's age like CreateAPIToken, since it's compared against a session cutoff that Postgres stamps.

ponytail: the sweep rides the mint, like MintAttachTicket — rows live only ten minutes. Add a real sweeper if mint volume ever breaks that.

func (PG) MintEnrolmentToken added in v0.8.0

func (s PG) MintEnrolmentToken(ctx context.Context, token string, t types.DeviceEnrolmentToken) (types.DeviceEnrolmentToken, error)

MintEnrolmentToken inserts one single-use enrolment token. ErrConflict on a unique_violation (23505) against token_sha256 — a raw collision in a 256-bit random space, same as CreateAPIToken's.

func (PG) MintRunnerRegistrationToken added in v0.9.0

func (s PG) MintRunnerRegistrationToken(ctx context.Context, raw string, token types.RunnerRegistrationToken) (types.RunnerRegistrationToken, error)

MintRunnerRegistrationToken never stores the raw bearer.

func (PG) MoveAPIToken added in v0.9.0

func (s PG) MoveAPIToken(ctx context.Context, id uuid.UUID, serviceIdentity, by string, now time.Time) (types.TokenMove, error)

func (PG) NotePauseSettle added in v0.8.6

func (s PG) NotePauseSettle(ctx context.Context, id uuid.UUID, after time.Duration) (time.Time, error)

NotePauseSettle — see PauseSettler.

func (PG) NoteRunPATRevokeFailed added in v0.8.2

func (s PG) NoteRunPATRevokeFailed(ctx context.Context, runID, authorizationID uuid.UUID, lastError string) error

NoteRunPATRevokeFailed — see RunPATStore.

func (PG) PendingGroupRemovals added in v0.8.6

func (s PG) PendingGroupRemovals(ctx context.Context, idleFor time.Duration, limit int) ([]PendingGroupRemoval, error)

PendingGroupRemovals is the pendingGroupRemovalWhere removals that no step of the same removal has touched for idleFor (a request still working on it is not one the sweeper may race), at most limit.

func (PG) PendingLeavers added in v0.8.6

func (s PG) PendingLeavers(ctx context.Context, idleFor time.Duration, limit int) ([]PendingLeaver, error)

PendingLeavers is the identities with a suspend or purge ledger row still pending that no step has touched for idleFor (a request still working on it is not one the sweeper may race), at most limit. An identity that is neither deactivated nor purged has a stale ledger and is not listed.

func (PG) Ping added in v0.6.0

func (s PG) Ping(ctx context.Context) error

Ping proves the pool can actually reach Postgres (a live query round-trip, not just a constructed pool).

func (PG) PoolSlotsUsed added in v0.9.0

func (s PG) PoolSlotsUsed(ctx context.Context) (map[uuid.UUID]int, error)

PoolSlotsUsed is the number of slot-holding runs per pool that has any, the count promotion judges a pool's cap by.

func (PG) PrincipalAliases added in v0.8.6

func (s PG) PrincipalAliases(ctx context.Context, principal string) ([]string, error)

func (PG) PrincipalEntraNames added in v0.9.0

func (s PG) PrincipalEntraNames(ctx context.Context, principal string) ([]string, error)

func (PG) PrincipalForName added in v0.8.6

func (s PG) PrincipalForName(ctx context.Context, name string) (string, error)

PrincipalForName maps a name an event carries (a principal, an entra:<tenant>:<object> form, an email the person signed in with) to the person's principal through PrincipalsNamed, the one answer erasure resolves by. A name that resolves to none, or to more than one, is returned as it is. The audit sealer keys rows by it.

func (PG) PrincipalsNamed added in v0.9.0

func (s PG) PrincipalsNamed(ctx context.Context, name string) ([]string, error)

func (PG) PrincipalsSharingObject added in v0.9.0

func (s PG) PrincipalsSharingObject(ctx context.Context, principal string) ([]string, error)

func (PG) PromoteRunQueue added in v0.9.0

func (s PG) PromoteRunQueue(ctx context.Context, pass PromotePass, admit QueueAdmit) ([]PromotedRun, []QueueHeld, error)

PromoteRunQueue moves waiting runs of one pool to PENDING, oldest first, while the pool has a free slot. It judges first, holding no lock: the pool's room and each candidate's verdict are read as they stand. Then one transaction takes the pool row (FOR NO KEY UPDATE, the lock an enqueue takes) and the deployment cap's advisory lock, always in that order, counts the slot-holders again, and moves the admitted runs by a state CAS while there is room. A run the verdict will not admit keeps its place and the pass judges the next, so one run that cannot be placed never blocks another's. An error with promoted runs is a judging cut short (a page read failed, or its time ran out) after those were admitted: they are promoted all the same, and the caller starts them.

func (PG) ProposeGovernanceChange added in v0.8.6

ProposeGovernanceChange stores ch as pending, expiring ttl from now on the database's clock. In the same transaction it first moves any lapsed pending change for the same target to expired (returned, for the audit rows), so a live one is the only thing the one-pending-per-target index can refuse: that is *ErrGovernanceChangePending naming it. A create, and an update that renames a profile, also hold the name they take (claimCreateName).

func (PG) PruneAWSSSOSpentTokens added in v0.8.0

func (s PG) PruneAWSSSOSpentTokens(ctx context.Context, cutoff time.Time) (int, error)

PruneAWSSSOSpentTokens deletes spent-token rows older than cutoff (aws_sso_spent_tokens_marked_at_idx backs the WHERE clause).

func (PG) PurgeAssertedRunnerSubjects added in v0.9.0

func (s PG) PurgeAssertedRunnerSubjects(ctx context.Context) (int64, error)

PurgeAssertedRunnerSubjects deletes every action row of an asserted subject whose quarantine succeeded more than RunnerAssertedEvidenceTTL ago. The runner deletes its own evidence for that run on the same clock.

func (PG) PurgeDueIdentities added in v0.8.6

func (s PG) PurgeDueIdentities(ctx context.Context, limit int) ([]DueIdentity, error)

PurgeDueIdentities is the identities deactivated and not yet purged whose purge_after has passed, those deactivated by their own suspension first, then oldest first, at most limit. It is only a candidate read: MarkIdentityPurged re-checks under the lock.

func (PG) PurgeTerminalRunProxyConfigs added in v0.8.0

func (s PG) PurgeTerminalRunProxyConfigs(ctx context.Context) (int64, error)

PurgeTerminalRunProxyConfigs — see RunProxyConfigs. The terminal test is types.RunState.IsTerminal itself, not a copy of its set in SQL.

func (PG) PutBranding added in v0.8.0

func (s PG) PutBranding(ctx context.Context, b types.Branding, keepLogo bool) (types.Branding, error)

PutBranding implements BrandingStore.

func (PG) PutCapabilityEnforcement added in v0.6.0

func (s PG) PutCapabilityEnforcement(ctx context.Context, enabled map[string]bool) (map[string]bool, error)

PutCapabilityEnforcement replaces the WHOLE switch map: any capability not named in enabled loses its row (absent == not enforced). One statement, not a transaction: the DELETE and INSERT run against the same CTE snapshot over disjoint capability sets, so the pair is already atomic.

func (PG) PutCredentialDeliveryPolicy added in v0.9.0

func (s PG) PutCredentialDeliveryPolicy(ctx context.Context, policy json.RawMessage, by string, now time.Time) (types.CredentialDeliveryPolicyDoc, error)

PutCredentialDeliveryPolicy upserts the document. The caller has validated it (placement.DeliveryPolicy.Validate).

func (PG) PutLaunchPreset added in v0.8.0

func (s PG) PutLaunchPreset(ctx context.Context, p types.LaunchPreset) (types.LaunchPreset, PresetWrite, error)

PutLaunchPreset creates p at version 1, or replaces the named row and moves its version by one. A write identical to the stored row changes nothing, not even the version, so re-applying an unchanged document is a no-op. The comparison and the bump are one statement, so two concurrent writers cannot both land on the same version.

func (PG) PutPrincipalPref added in v0.8.0

func (s PG) PutPrincipalPref(ctx context.Context, principal, key string, value json.RawMessage) error

func (PG) PutRef added in v0.3.1

func (s PG) PutRef(ctx context.Context, ref, substrateName string) error

PutRef upserts the ref -> substrate-name row (ref is the primary key).

func (PG) PutRunComponents added in v0.8.9

func (s PG) PutRunComponents(ctx context.Context, runID uuid.UUID, comps []types.RunComponent) error

PutRunComponents — see ComponentStore.

func (PG) PutRunHarnesses added in v0.9.0

func (s PG) PutRunHarnesses(ctx context.Context, runID uuid.UUID, hs []types.RunHarness) error

PutRunHarnesses — see RunHarnessStore.

func (PG) PutRunLayout added in v0.5.0

func (s PG) PutRunLayout(ctx context.Context, principal, preset string, layout []types.RunLayoutWidget) (types.RunLayout, error)

PutRunLayout upserts principal's layout for preset. ON CONFLICT DO UPDATE makes this one idempotent write with no read-then-decide round trip and no lost-update race between two tabs saving the same preset back to back.

func (PG) PutRunProxyConfig added in v0.8.0

func (s PG) PutRunProxyConfig(ctx context.Context, id uuid.UUID, sealed []byte) error

PutRunProxyConfig — see RunProxyConfigs.

func (PG) PutRunnerPoolOrgDefaults added in v0.9.0

func (s PG) PutRunnerPoolOrgDefaults(ctx context.Context, d types.RunnerPoolDefaults, by string) error

PutRunnerPoolOrgDefaults replaces the organisation's defaults. The caller has checked the pool; a default only names one, and the resolver refuses a name that has since gone or been switched off.

func (PG) PutRunnerPoolUsePolicy added in v0.9.0

func (s PG) PutRunnerPoolUsePolicy(ctx context.Context, id uuid.UUID, revision *int64, subjects []types.RunnerPoolSubject, by string) (types.RunnerPoolUsePolicy, error)

PutRunnerPoolUsePolicy sets a live organization pool's use policy and raises the pool's revision; a non-nil revision must be the pool's, checked under the pool lock (ErrRunnerPoolStale). It takes the governance target lock an approval of a held change takes too.

func (PG) PutSiteConfig

func (s PG) PutSiteConfig(ctx context.Context, cfg types.SiteConfig) (types.SiteConfig, error)

PutSiteConfig upserts the single operator-wide site config row. The `singleton` primary key (CHECKed true) makes a second row impossible at the schema level. A write replaces every key types.SiteConfig declares (no partial merge; the API validates the full document first) but keeps any key it doesn't, which only a newer wardynd could have written (declaredJSONKeys).

func (PG) PutTemplateGroupAdmin added in v0.9.0

func (s PG) PutTemplateGroupAdmin(ctx context.Context, g types.TemplateGroupAdmin) (types.TemplateGroupAdmin, error)

PutTemplateGroupAdmin — see TemplateStore.

func (PG) QueryAuditEvents

func (s PG) QueryAuditEvents(ctx context.Context, runID uuid.UUID, limit int) ([]types.AuditEvent, error)

QueryAuditEvents returns audit events for a run in time order. limit <= 0 means no explicit limit (returns up to 1000).

func (PG) QueryAuditEventsFilteredPage added in v0.4.4

func (s PG) QueryAuditEventsFilteredPage(ctx context.Context, runID *uuid.UUID, f AuditFilter, p Page) ([]types.AuditEvent, error)

QueryAuditEventsFilteredPage returns audit events narrowed by f, bounded by p. A non-nil runID scopes to one run and keeps that trail's chronological seq ASC order (the per-run contract in docs/sdk.md); nil is the newest-first global feed. Only a request that actually sets a predicate takes this path — an unfiltered read stays on the two plain queries above, whose plans are unchanged.

No new index: 0001 covers (time) and 0017 (action, seq DESC); a bounded LIMIT query index-scans the pkey backward and filters, which is what the caps are for. DataContains adds a per-row `data @>` test with no GIN index behind it; the planner walks the pkey or the (time) index and filters, and the LIMIT plus a Since window bound it (about 0.1 s over 2M rows in the #1449 review).

func (PG) QueryAuditEventsPage added in v0.3.1

func (s PG) QueryAuditEventsPage(ctx context.Context, runID uuid.UUID, p Page) ([]types.AuditEvent, error)

QueryAuditEventsPage returns a run's audit events in seq (chronological) order, bounded by p. audit_events_run_seq_idx makes WHERE run_id + ORDER BY seq an indexed range scan with no sort; OFFSET pages forward without flipping to DESC, so the per-run trail stays ASC (docs/sdk.md's exit-code contract) and a caller pages to the newest events with ?offset=.

func (PG) QueryRecentAuditEvents

func (s PG) QueryRecentAuditEvents(ctx context.Context, limit int) ([]types.AuditEvent, error)

QueryRecentAuditEvents returns the most-recent audit events across ALL runs, newest first — the global SIEM-style feed the Audit view renders, unlike the chronological per-run QueryAuditEvents.

func (PG) QueryRecentAuditEventsPage added in v0.3.1

func (s PG) QueryRecentAuditEventsPage(ctx context.Context, p Page) ([]types.AuditEvent, error)

QueryRecentAuditEventsPage returns the newest-first global audit feed, bounded by p. seq is the audit_events PRIMARY KEY, so ORDER BY seq DESC + LIMIT is an index-scan-backward with no added index.

func (PG) QueueDepth added in v0.9.0

func (s PG) QueueDepth(ctx context.Context) (map[uuid.UUID]int, error)

QueueDepth is the number of waiting runs per pool that has any.

func (PG) QueueEntriesOfRevokedRunners added in v0.9.0

func (s PG) QueueEntriesOfRevokedRunners(ctx context.Context, limit int) ([]RunQueueEntry, error)

QueueEntriesOfRevokedRunners are the waiting runs whose launch names a runner (its request's runner_id) that is revoked, oldest first: no promotion can ever place them.

func (PG) QueueOwnerEntries added in v0.9.0

func (s PG) QueueOwnerEntries(ctx context.Context, owner string) ([]RunQueueEntry, error)

QueueOwnerEntries are the waiting runs of one owner, the leaver purge's and an erasure's work list.

func (PG) QueuePoolIDs added in v0.9.0

func (s PG) QueuePoolIDs(ctx context.Context) ([]uuid.UUID, error)

QueuePoolIDs are the pools that have waiting runs: the sweep's and a kick's work list.

func (PG) QueueRecordingRunOutput added in v0.8.9

func (s PG) QueueRecordingRunOutput(ctx context.Context, runID uuid.UUID, retention, retryAfter time.Duration, newRecording bool) error

QueueRecordingRunOutput — see RunOutputStore.

func (PG) QueueRunnerAction added in v0.9.0

func (s PG) QueueRunnerAction(ctx context.Context, a types.RunnerPendingAction) (types.RunnerPendingAction, bool, error)

QueueRunnerAction durably queues a containment action (kill, end, stop_proxy) for a's claimed runner, bound to the identity the org holds at commit: the runner's owner, the run's exact ref and the expected execution generation (uuid.Nil asserts none was issued), and for stop_proxy the exact kept outage loss in LostAt. Any mismatch is ErrRunnerActionRefused and writes nothing. A replayed action ID, an open action of the same kind for the run, or a deferred stop_proxy for the same loss is returned with queued=false only when every binding matches; otherwise ErrConflict. A runner that is not claimed, or a run that is gone, is ErrNotFound: the runner row is locked FOR SHARE, so a queue meeting a revoke waits for it and then refuses. Recovery, quarantine and retirement need explicit org authority that this queue does not establish, so they refuse.

func (PG) QueueRunnerTeardown added in v0.9.0

func (s PG) QueueRunnerTeardown(ctx context.Context, runnerID uuid.UUID, kind types.RunnerActionKind, ref string) (types.RunnerPendingAction, bool, error)

QueueRunnerTeardown queues the org's own offline teardown of ref (the remote substrate's Queue seam): it binds the run's identity as the org holds it under the same locks, then queues through the exact-binding path. A stop_proxy binds the current kept outage loss and refuses without one.

func (PG) ReacquireAttachLease added in v0.9.0

func (s PG) ReacquireAttachLease(ctx context.Context, l AttachLease, ttl time.Duration) (bool, error)

ReacquireAttachLease — see AttachLeaseStore. AcquireAttachLease's rule without its two hand-over arms: no from, and no reservation taken by its taker's principal.

func (PG) ReadRunOutputChunks added in v0.8.6

func (s PG) ReadRunOutputChunks(ctx context.Context, runID uuid.UUID, limit int) (RunOutputChunks, error)

ReadRunOutputChunks — see RunOutputChunkStore.

func (PG) ReclampRunLimits added in v0.8.0

func (s PG) ReclampRunLimits(ctx context.Context, from types.AgentRun, toLimits types.RunLimits, toEnd *time.Time, toWait int, endTightenedAt *time.Time) (bool, error)

ReclampRunLimits — see RunLimitsReclamper. The compare on the limits read is what keeps a PATCH decided against the old gate from landing after it, and this write from landing over a PATCH's.

func (PG) RecordPushPathList added in v0.8.0

func (s PG) RecordPushPathList(ctx context.Context, approvalID, runID uuid.UUID, l types.PushPathList, limit int) (bool, error)

func (PG) RecordRunnerActionResult added in v0.9.0

func (s PG) RecordRunnerActionResult(ctx context.Context, runnerID, runID, actionID uuid.UUID, outcome, reason string, observedAt time.Time,
	prepare func(context.Context, types.AuditEvent) (types.AuditEvent, error)) (types.RunnerPendingAction, bool, error)

RecordRunnerActionResult records an authenticated action_result: runnerID is the stream's stored identity and runID the event's run; both must be the action's own. The outcomes are success, deferred and refused; only refused carries a reason, and its first receipt closes the action and writes runner.action.refuse in the same transaction. observedAt is the runner's assertion and applied_at is the org clock. The first receipt rechecks the owner, ref and execution generation the action was bound to; a stop_proxy success on the still-kept loss records late containment in the same transaction, so the receipt and its audit commit or roll back together. An identical repeat returns first=false with no further effect; a changed one is ErrConflict.

func (PG) RecordRunnerContainmentTx added in v0.9.0

func (s PG) RecordRunnerContainmentTx(ctx context.Context, tx pgx.Tx, proof RunnerContainmentProof, prepare func(context.Context, types.AuditEvent) (types.AuditEvent, error)) error

RecordRunnerContainmentTx is called exactly once after H3's first successful exact action-result CAS, inside that same READ COMMITTED transaction. A duplicate CAS must skip this hook. H3 owns pending clearance and commits only if this hook succeeds. No lifecycle or credential state is restored here.

func (PG) RecordRunnerPosture added in v0.9.0

func (s PG) RecordRunnerPosture(ctx context.Context, id uuid.UUID, p types.RunnerPosture, now time.Time) (bool, error)

RecordRunnerPosture stores a posture report from a claimed runner and says whether it differs from the one held, so only a change writes runner.posture.record. A runner that is not claimed is ErrNotFound.

func (PG) RefreshAPITokenIdentity added in v0.8.0

func (s PG) RefreshAPITokenIdentity(ctx context.Context, principal, role, userType string, groups []string, truncated bool) error

RefreshAPITokenIdentity re-stamps role, user type, and the group snapshot (with its completeness bit) on EVERY token principal holds — the api-token twin of RefreshSSHKeyRoles; OnLogin fires both so one login bounds both frozen credentials.

SECURITY: a token's identity is frozen at mint with nothing else to refresh it — without this, demoting an admin leaves their wdn_ tokens authenticating AS AN ADMIN until explicitly revoked, and a token keeps authorizing against stale group memberships until a fresh mint.

truncated is bound EXACTLY as passed, never defaulted or inferred: it must come from the login's own session-completeness signal, the same bit a fresh mint stamps. A NULL groups_truncated already reads as TRUNCATED downstream (fail closed); silently defaulting this to false would assert "these are all their groups" for a snapshot that isn't — the wrong direction for an authorization decision.

identity_stamped_at moves with the role in the SAME statement, so a login that stamps a token's role stamps its age too, and a failed statement leaves that principal's tokens stale rather than half-updated. WARDYN_ROLE_STAMP_TTL reads it; `revoked_at IS NULL` stays in the WHERE, so a login never revives a revoked token (an expired one keeps its own expiry, which this never writes).

Still bounded-stale, not live: the ceiling is the owner's next login (see docs/SSH.md §Bounds for the key lane's equivalent). No error when the principal holds no tokens — a zero-row UPDATE is the ordinary case.

SECURITY: a token an admin minted FOR this principal (minted_by set) is revoked instead of re-stamped when the login's role differs from its stamp — its role was derived before the person's own groups were known and the minter saw its plaintext, so re-stamping upward would hand the minter's tier to whoever holds that credential. SET reads the row's OLD role, so the comparison is against the stamp.

func (PG) RefreshRunOutputClaim added in v0.8.6

func (s PG) RefreshRunOutputClaim(ctx context.Context, runID uuid.UUID) error

RefreshRunOutputClaim — see RunOutputStore.

func (PG) RefreshSSHKeyRoles added in v0.6.0

func (s PG) RefreshSSHKeyRoles(ctx context.Context, principal, role string, checkedAt time.Time) error

RefreshSSHKeyRoles re-stamps role and role_checked_at for every key owned by principal, from the OIDC OnLogin hook, with that login's freshly-derived role. This bounds the admin-override stamp to at most WARDYN_SSH_ROLE_TTL stale: it overwrites role too, not only the timestamp, so a demoted or promoted human's keys take effect on their very next login, with no delete-then-re-register needed. No registered keys is a normal, silent no-op.

A CAPPED key (registered in the user view) keeps its user role — the login refreshes only its timestamp, never promotes it.

func (PG) ReissueDeferredAssertedRunnerActions added in v0.9.0

func (s PG) ReissueDeferredAssertedRunnerActions(ctx context.Context, runnerID uuid.UUID, sessionID string) (int, error)

ReissueDeferredAssertedRunnerActions is the reconciler for an asserted subject's deferred kill or quarantine: a deferred receipt is final for its action, so the obligation continues in a fresh successor with the identical binding and proof. It runs off the read loop as a fresh session is admitted (before its inventory begins), and only when the runner's previous session completed its inventory, so each obligation gets at most one successor per completed inventory. It returns how many successors it queued.

func (PG) ReleaseAttachLease added in v0.8.6

func (s PG) ReleaseAttachLease(ctx context.Context, runID, holderID uuid.UUID) (bool, error)

ReleaseAttachLease — see AttachLeaseStore.

func (PG) RemoveOwnRunnerFromPool added in v0.9.0

func (s PG) RemoveOwnRunnerFromPool(ctx context.Context, id, runnerID uuid.UUID, owner string) (bool, int64, error)

RemoveOwnRunnerFromPool removes owner's own runner (in any state, so a revoked one can be tidied away) from a pool; removed is false when it was not a member or not owner's.

func (PG) RemoveRemoteRunnerFromUserPool added in v0.9.0

func (s PG) RemoveRemoteRunnerFromUserPool(ctx context.Context, id uuid.UUID, remoteRunner, owner string) (bool, int64, error)

RemoveRemoteRunnerFromUserPool takes a remote-hosted runner out of owner's own user pool; removed is false when it was not a member. ErrNotFound: no such live pool of owner's.

func (PG) RemoveRunnerPoolExecutor added in v0.9.0

func (s PG) RemoveRunnerPoolExecutor(ctx context.Context, id uuid.UUID, executor string, refuseGoverned bool) (removed bool, rev int64, err error)

RemoveRunnerPoolExecutor removes a remote-hosted member of an organization pool; removed is false when it was not one. The caller decided on a catalogue read taken before the locks whether the detach is held; refuseGoverned says it decided "not held", and ErrRunnerPoolGoverned answers a use policy that was approved in between.

func (PG) RenameScimGroup added in v0.8.6

func (s PG) RenameScimGroup(ctx context.Context, id uuid.UUID, displayName string) error

RenameScimGroup changes a group's display name; its externalId never changes.

func (PG) RenewAttachLease added in v0.8.6

func (s PG) RenewAttachLease(ctx context.Context, runID, holderID uuid.UUID, ttl time.Duration) (bool, error)

RenewAttachLease — see AttachLeaseStore.

func (PG) ReopenDeprovisionJob added in v0.8.6

func (s PG) ReopenDeprovisionJob(ctx context.Context, identityID uuid.UUID, kind string, k JobKey) error

ReopenDeprovisionJob makes a finished step pending again: a run the ledger counted done that is found live once more.

func (PG) ReplaceUserDriveGrant added in v0.9.0

func (s PG) ReplaceUserDriveGrant(ctx context.Context, g types.UserDriveGrant, homeOverrideStated bool) (types.UserDriveGrant, []uuid.UUID, error)

ReplaceUserDriveGrant is UpsertUserDriveGrant that also returns the drives the subject lost: the drive ids of the rows it removed and of the kept row's drive before the repoint.

func (PG) ReserveAttachLease added in v0.8.6

func (s PG) ReserveAttachLease(ctx context.Context, runID, from uuid.UUID, taker, replica string, ttl time.Duration) (AttachLease, uuid.UUID, bool, error)

ReserveAttachLease — see AttachLeaseStore.

func (PG) ResetFederation added in v0.8.0

func (s PG) ResetFederation(ctx context.Context) error

ResetFederation is a (re-)enrolment: a new device identity starts an empty chain at the organisation, resetting the cursor to 0 and clearing the revoked mark. The only writer that clears it.

func (PG) ResolveGovernanceProfile added in v0.7.0

func (s PG) ResolveGovernanceProfile(ctx context.Context, userSubjects, groups []string, userType string) (*types.GovernanceProfile, types.CapabilitySubjectType, error)

ResolveGovernanceProfile returns THE ONE profile that applies to a caller, or ErrNotFound when no assignment matches (which the caller reads as "fall through to the deployment ceiling", the absent-row back-compat rule).

The whole precedence rule is the ORDER BY, deliberately: one indexed read on the UNIQUE(subject_type, subject) btree, so there's no second implementation in Go to skip, mis-order, or forget. subjectMatch and subjectPrecedence are the same fragments ResolveUserDrive splices, so the ceiling and the drive can't disagree about "most specific".

SECURITY: users/groups are normalized from nil to empty for the same reason ListCapabilityGrantsFor does: a nil Go slice binds as SQL NULL and `x = ANY(NULL)` is NULL, not false. Fails closed either way, but a predicate whose behavior hinges on a driver detail has no place at an authorization boundary.

userType is the caller's one type id; "" matches no row.

func (PG) ResolveReauthApproval added in v0.7.6

func (s PG) ResolveReauthApproval(ctx context.Context, id uuid.UUID, decision types.ApprovalDecision, ev types.AuditEvent) (types.ApprovalRequest, error)

ResolveReauthApproval moves a credential_reauth approval to APPROVED and writes its credential.reauth.resolve audit row in ONE transaction: the state change unblocks a HELD credential request, and the audit row is the only durable record that a human acted behind it. Committed separately, a crash between them would leave a run resuming with nothing in the trail saying who resolved it (invariant I7). If either write fails the approval stays PENDING and the idempotent reconcile-on-read tries again.

It is NOT part of the Store interface (see iface.go on transactional surfaces): internal/api type-asserts for it.

The CAS is DecideApproval's own: WHERE state='PENDING', so a concurrent resolver loses cleanly with ErrAlreadyDecided and writes no second audit row.

func (PG) ResolveUserDrive added in v0.7.0

func (s PG) ResolveUserDrive(ctx context.Context, userSubjects, groups []string, userType string) (
	*types.UserDrive, *types.UserDriveGrant, types.CapabilitySubjectType, error)

ResolveUserDrive returns THE ONE drive that applies to a caller, the winning grant, and its tier — or ErrNotFound. The whole precedence rule lives in the ORDER BY (subjectMatch/subjectPrecedence, shared with ResolveGovernanceProfile since both rank the same subject vocabulary): one indexed read on UNIQUE(subject_type, subject), so there's no second Go implementation to mis-order.

Disabled grants stay in the query; the winner's own `enabled` decides PAUSED vs MOUNTED. Excluding them in the WHERE would let a paused row fall through to a wider `all`-tier drive no admin chose for this member — silently, since the only visible signal would be a mount appearing rather than one stopping (DESIGN §2.2).

userSubjects/groups are normalized from nil to empty: a nil Go slice binds as SQL NULL, and `x = ANY(NULL)` is NULL rather than false — same reasoning as ResolveGovernanceProfile.

userType is the caller's one type id; "" matches no row.

func (PG) ResolveUserDrives added in v0.9.0

func (s PG) ResolveUserDrives(ctx context.Context, userSubjects, groups []string, userType string) ([]UserDriveWinner, error)

ResolveUserDrives is ResolveUserDrive per drive: DISTINCT ON keeps each drive's top-ranked row, then the outer ORDER BY ranks the winners. Both orders are subjectPrecedence, so the per-drive pick and the single-drive pick cannot rank differently.

func (PG) ResumeGroupRemoval added in v0.8.6

func (s PG) ResumeGroupRemoval(ctx context.Context, groupID, identityID uuid.UUID) (bool, error)

ResumeGroupRemoval says whether the sweeper may go on with a removal it selected earlier: true only when the pair's ledger still has a pending step and the person is not recorded a member again. It never opens a removal or deletes a membership, so a candidate the identity provider added back since it was selected is dropped, not removed again. The identity row is locked FOR UPDATE first, in a statement of its own: a membership insert's foreign-key check holds that row, so the lock waits for an add in flight to commit or roll back. The membership is then read by a second statement, whose snapshot is taken after the lock and so sees that add; an add that starts later waits for this transaction, and is ordered after the answer.

func (PG) RetireServiceIdentity added in v0.9.0

func (s PG) RetireServiceIdentity(ctx context.Context, name, by string) (ServiceIdentityChanged, error)

func (PG) RetryRunnerExitTail added in v0.9.0

func (s PG) RetryRunnerExitTail(ctx context.Context, runnerID, runID, generation uuid.UUID, tail func(context.Context, types.AgentRun, runnerwire.AgentExit) error) error

RetryRunnerExitTail holds the run-operation lock over validation, destructive effects and receipt completion. BeginRunnerExecution uses the same barrier; a completed old receipt never touches a newer incarnation.

func (PG) RevokeAPIToken added in v0.6.0

func (s PG) RevokeAPIToken(ctx context.Context, id uuid.UUID, principal string, now time.Time) (types.APIToken, error)

RevokeAPIToken marks id revoked. principal scopes the UPDATE when non-empty (self-service: a human may only revoke their OWN token). SECURITY: someone else's id is ErrNotFound, not a distinguishable 403 — no existence leak across principals. An EMPTY principal is the ADMIN path and revokes anyone's.

Already-revoked is ErrNotFound too (`revoked_at IS NULL` in the WHERE): revoke is idempotent, so a second call must not emit a second token.revoke audit row for an act that didn't happen.

func (PG) RevokeDelegate added in v0.8.0

func (s PG) RevokeDelegate(ctx context.Context, id uuid.UUID, now time.Time) (types.Delegate, error)

RevokeDelegate marks id revoked. Unknown or already-revoked is ErrNotFound, so a second revoke writes no second audit row. Outstanding delegated tokens need no write of their own: GetDelegatedTokenByRaw joins on this column.

func (PG) RevokeDevice added in v0.8.0

func (s PG) RevokeDevice(ctx context.Context, id uuid.UUID, now time.Time) (types.Device, error)

RevokeDevice marks id revoked. Already-revoked is ErrNotFound too (`revoked_at IS NULL` in the WHERE): idempotent, so a second call emits no second revoke audit row.

func (PG) RevokeEnrolmentToken added in v0.8.0

func (s PG) RevokeEnrolmentToken(ctx context.Context, id uuid.UUID, now time.Time) (types.DeviceEnrolmentToken, error)

RevokeEnrolmentToken cancels a redeemable token by setting consumed_at (the column ConsumeEnrolmentToken's WHERE requires NULL), so a revoke can't race a redemption. Already-redeemed/revoked/expired/unknown is ErrNotFound.

func (PG) RevokeInvitation added in v0.9.0

func (s PG) RevokeInvitation(ctx context.Context, id uuid.UUID, by string, now time.Time) (types.Invitation, error)

func (PG) RevokeRunner added in v0.9.0

func (s PG) RevokeRunner(ctx context.Context, id uuid.UUID, now time.Time, by, reason string) (RunnerRevocation, error)

RevokeRunner revokes a runner in one transaction: the runner row with who revoked it and why, every run holding a slot on it ended KILLED, and every unsettled pending action of it settled 'revoked'. A QUEUED run holds no slot and is not ended here: the caller ends the ones waiting for this runner alone. Rows are kept as evidence. The fence lifts only because the run is terminal in the same commit, never before. Already revoked or unknown is ErrNotFound, so a second call ends nothing. Lock order is runner, then runs by id, then actions, as everywhere else.

func (PG) RevokeRunnerRegistrationToken added in v0.9.0

func (s PG) RevokeRunnerRegistrationToken(ctx context.Context, id uuid.UUID, now time.Time) (types.RunnerRegistrationToken, error)

RevokeRunnerRegistrationToken spends an unused token by setting the column ConsumeRunnerRegistrationToken requires NULL, in one conditional UPDATE: a revoke and a redemption of the same token take the same row lock, so exactly one of them wins. A token that was redeemed, revoked, expired or never existed is ErrNotFound.

func (PG) RunAgentStopped added in v0.9.0

func (s PG) RunAgentStopped(ctx context.Context, id uuid.UUID) (bool, error)

RunAgentStopped reports whether MarkRunAgentStopped persisted a stop for id: its agent_stopped_at is set.

func (PG) RunCapacity added in v0.8.6

func (s PG) RunCapacity(ctx context.Context, o RunCapacityOpts) (RunCapacity, error)

RunCapacity reads every non-terminal row in one query (the positive types.NonTerminalRunStates list, never NOT IN terminal, so a new terminal state cannot be counted as active) and folds it per the state table in the 0.8.6 fleet design: STARTING, RUNNING and WAITING_FOR_CONFIRMATION rows with lost_at NULL hold capacity, paused ones included; kept rows (lost_at set) count as Kept; PENDING and QUEUED rows appear only in States (a QUEUED run holds no slot and no reservation, but wardyn_runs_active still reports it). A kept row whose agent may still run (HoldsSandboxSQL: kept after an outage, no agent stop recorded) also holds its agent's reservation, without the proxy that was stopped. A run bound to a placement outside this deployment (a registered runner, or an executor it does not run) is counted in States, Kept and Paused only: its sizing, whatever runner_kind it was stamped with and even when unstamped, is not this deployment's.

func (PG) RunHasOpenRequest added in v0.8.0

func (s PG) RunHasOpenRequest(ctx context.Context, id uuid.UUID) (bool, error)

RunHasOpenRequest — see RunPauser.

func (PG) RunHasPendingRunnerAction added in v0.9.0

func (s PG) RunHasPendingRunnerAction(ctx context.Context, runID uuid.UUID) (bool, error)

RunHasPendingRunnerAction is the fence: a run with an open action, or a deferred one, gets no relay, call, generation or revival.

func (PG) RunHeldContainedOnRunner added in v0.9.0

func (s PG) RunHeldContainedOnRunner(ctx context.Context, runID uuid.UUID) (bool, error)

RunHeldContainedOnRunner reports whether the run's runner holds it contained: a kill, end, stop_proxy or quarantine was queued for it and no later recover_containment succeeded. Such a runner refuses any lease for the run, and a refused lease ends its session.

func (PG) RunIDsCreatedBy added in v0.8.6

func (s PG) RunIDsCreatedBy(ctx context.Context, createdBy string) ([]uuid.UUID, error)

func (PG) RunQueueFacts added in v0.9.0

func (s PG) RunQueueFacts(ctx context.Context, ids []uuid.UUID) (map[uuid.UUID]RunQueueFact, error)

RunQueueFacts reads the place of each of ids that is still waiting, in one read; a run that is not QUEUED has no entry. Only the pools of the asked runs are ranked.

func (PG) RunQueuePosition added in v0.9.0

func (s PG) RunQueuePosition(ctx context.Context, id uuid.UUID) (int, error)

RunQueuePosition is how many runs wait ahead of run id in its pool (a count, never whose), or ErrNotFound when the run is not waiting.

func (PG) RunTokenRenewedAt added in v0.9.0

func (s PG) RunTokenRenewedAt(ctx context.Context, runID uuid.UUID) (time.Time, error)

RunTokenRenewedAt is the run's token renewal stamp, the one the lapsed-token sweep reads.

func (PG) RunWatcherFresh added in v0.5.0

func (s PG) RunWatcherFresh(ctx context.Context, id uuid.UUID, staleAfter time.Duration) (bool, error)

RunWatcherFresh reports whether id's watcher lease is younger than staleAfter — mirrors ClaimStaleRunWatchers' own staleness predicate (a live watcher owns it). A missing row → (false, nil): nothing is watching it, so the reaper may proceed.

func (PG) RunnerArchCounts added in v0.9.1

func (s PG) RunnerArchCounts(ctx context.Context) (map[string]int, error)

RunnerArchCounts counts the non-revoked runners per reported CPU architecture. A runner that has not reported one is not counted.

func (PG) RunnerExecutionOf added in v0.9.0

func (s PG) RunnerExecutionOf(ctx context.Context, runID uuid.UUID) (RunnerExecution, error)

RunnerExecutionOf is the run's persisted execution intent, ErrNotFound while it has none. It is the check an issued generation is verified against, never a source for one.

func (PG) RunnerPoolSlotCounts added in v0.9.0

func (s PG) RunnerPoolSlotCounts(ctx context.Context, poolIDs []uuid.UUID) (map[uuid.UUID]PoolCounts, error)

RunnerPoolSlotCounts is every listed pool's PoolCounts in one grouped read, taken without any lock: advisory, since a launch counts again under the pool row's lock. Used counts what countPoolSlotHolders counts. A pool with neither is present with zeros.

func (PG) RunnerPoolsOf added in v0.9.0

func (s PG) RunnerPoolsOf(ctx context.Context, ids []uuid.UUID) (map[uuid.UUID][]types.RunnerPoolRef, error)

RunnerPoolsOf — see RunnerInventoryStore. Only a pool whose owner is the runner's owner is named, so a runner never lists a pool of anyone else's, in one read for the whole page.

func (PG) RunnerSlotHolders added in v0.9.0

func (s PG) RunnerSlotHolders(ctx context.Context, ids []uuid.UUID) (map[uuid.UUID]int, error)

RunnerSlotHolders is the number of sandbox-holding runs on each of ids (RunnerUsage's runs). A runner with none is absent.

func (PG) RunnerUsage added in v0.9.0

func (s PG) RunnerUsage(ctx context.Context, runners []uuid.UUID, remotes []string, except uuid.UUID) (map[string]types.RunnerTally, error)

RunnerUsage is the In use of each runner named, by key, without a lock: what every read shows and placement chooses by, never the authority. except is a run never counted (uuid.Nil for none): a promoted run's start does not count the run's own place. A runner with nothing on it is absent.

func (PG) SaveChunkedRunOutput added in v0.8.6

func (s PG) SaveChunkedRunOutput(ctx context.Context, runID uuid.UUID, maskScope string, limit int) (bool, error)

SaveChunkedRunOutput — see RunOutputChunkStore.

func (PG) SaveFinalRunOutput added in v0.8.6

func (s PG) SaveFinalRunOutput(ctx context.Context, o RunOutput) error

SaveFinalRunOutput — see RunOutputStore.

func (PG) SaveGapRunOutput added in v0.8.6

func (s PG) SaveGapRunOutput(ctx context.Context, runID uuid.UUID) (bool, error)

SaveGapRunOutput — see RunOutputStore.

func (PG) SaveRecordingRunOutput added in v0.8.9

func (s PG) SaveRecordingRunOutput(ctx context.Context, claim RecordingOutputClaim, o RunOutput, retention time.Duration) (bool, error)

SaveRecordingRunOutput — see RunOutputStore.

func (PG) ScimGroupMembers added in v0.8.6

func (s PG) ScimGroupMembers(ctx context.Context, id uuid.UUID) ([]uuid.UUID, error)

ScimGroupMembers is the identity ids recorded as members of the group, oldest first.

func (PG) SearchIdentities added in v0.8.6

func (s PG) SearchIdentities(ctx context.Context, issuer, attr, value string, limit int) ([]PrincipalIdentity, error)

SearchIdentities is the identity rows a SCIM filter `attr eq value` selects under issuer, at most limit. externalId matches the SCIM projection or the bound object id; userName and emails.value match the projection, the login email and every alias the identity has been seen under.

func (PG) SearchScimGroups added in v0.8.6

func (s PG) SearchScimGroups(ctx context.Context, attr, value string, limit int) ([]ScimGroup, error)

SearchScimGroups is the groups a SCIM filter `attr eq value` selects, at most limit. Both attributes match case-insensitively.

func (PG) ServiceStillSponsored added in v0.9.0

func (s PG) ServiceStillSponsored(ctx context.Context, name, sponsor string, openedAt time.Time) (bool, error)

func (PG) SetAuditDeliveryCursor added in v0.9.0

func (s PG) SetAuditDeliveryCursor(ctx context.Context, dest string, seq int64, rowHash string) error

SetAuditDeliveryCursor advances dest's checkpoint after the collector accepted a batch. The zero position is written only after a cursor reset (the acknowledged row is gone or its hash changed), which is why seq 0 counts one reset and leaves acked_at alone: an acceptance that delivered nothing is not an acceptance at all.

func (PG) SetAuditDeliveryError added in v0.9.0

func (s PG) SetAuditDeliveryError(ctx context.Context, dest, msg string, halted bool) error

SetAuditDeliveryError records the collector's last answer and whether it halted delivery until wardynd restarts or another replica takes over delivery (a terminal 4xx). An empty msg with halted false clears both.

func (PG) SetAuditRetentionPolicy added in v0.8.6

func (s PG) SetAuditRetentionPolicy(ctx context.Context, days int) (AuditRetentionPolicyChange, error)
func (s PG) SetBrandingLogo(ctx context.Context, logo []byte, logoType string, fromFile bool, updatedBy string) (bool, error)

SetBrandingLogo implements BrandingStore.

func (PG) SetCapabilityRestriction added in v0.8.0

func (s PG) SetCapabilityRestriction(ctx context.Context, capability, value string, restricted bool, by string) error

SetCapabilityRestriction restricts one value (inserts its row, keeping the first writer's provenance) or lifts the restriction (deletes it).

func (PG) SetFederationCursor added in v0.8.0

func (s PG) SetFederationCursor(ctx context.Context, seq int64, rowHash string) error

SetFederationCursor durably advances the forwarder's cursor (seq and the local row's row_hash, so a later tick can tell it from a table-reset row at the same seq), upserting the singleton row. Called after a batch is accepted upstream, so a crash between accept and this write only costs an idempotent retry (see IngestDeviceAudit), never a gap.

func (PG) SetPromotedStatusDetail added in v0.9.0

func (s PG) SetPromotedStatusDetail(ctx context.Context, id uuid.UUID, detail string) error

SetPromotedStatusDetail writes the status_detail of a promoted run that has not been dispatched, so the reason a start refused it lands before the FAILED transition and a reader never sees a failed queue run without its token. It is a no-op once the run has left PENDING.

func (PG) SetQueueWait added in v0.9.0

func (s PG) SetQueueWait(ctx context.Context, id uuid.UUID, detail string) error

SetQueueWait records why a waiting run keeps its place: its "queue: <token>" status_detail, written only when it differs from what the run carries and only while the run is still QUEUED.

func (PG) SetRemoteRunnerPeople added in v0.9.0

func (s PG) SetRemoteRunnerPeople(ctx context.Context, id string, revision *int64, pools types.RemoteRunnerPeoplePools, subjects []types.RunnerPoolSubject, by string) (before, after types.RemoteRunner, err error)

func (PG) SetRunAgentExecID added in v0.3.1

func (s PG) SetRunAgentExecID(ctx context.Context, id uuid.UUID, execID string) error

SetRunAgentExecID scoped-writes ONLY agent_exec_id, once the driver execs the agent. The crash reconciler reads it to observe agent liveness across a restart.

func (PG) SetRunContainmentError added in v0.8.0

func (s PG) SetRunContainmentError(ctx context.Context, id uuid.UUID, msg string, now time.Time) error

SetRunContainmentError — see RunLeaser.

func (PG) SetRunDiskMiB added in v0.8.0

func (s PG) SetRunDiskMiB(ctx context.Context, id uuid.UUID, mib int) error

SetRunDiskMiB scoped-writes ONLY the resolved ephemeral-disk-cap column. Called once applyEphemeralDisk resolves the effective disk_mib, since that needs dispatch-time inputs unavailable at CreateRun.

func (PG) SetRunEndAndWait added in v0.8.0

func (s PG) SetRunEndAndWait(ctx context.Context, id uuid.UUID, fromLimits types.RunLimits, fromEnd *time.Time, fromWait int, toEnd *time.Time, toWait int, ended *EndedKept) (bool, error)

SetRunEndAndWait — see RunLeaser. The compare on the values the caller read is what makes the caller's clamp and gate decision the one that lands: a concurrent change, the lease sweep ending the run, or a re-clamp tightening the limits the gate was read from turns this into a no-op.

func (PG) SetRunFailureHint added in v0.6.0

func (s PG) SetRunFailureHint(ctx context.Context, id uuid.UUID, hint string) error

SetRunFailureHint scoped-writes ONLY failure_hint — the one-line operator reason a run FAILED before its agent started, known only at the failure site (failAndRevoke). Best-effort at the call site; ErrNotFound when no row matched.

func (PG) SetRunImage added in v0.2.0

func (s PG) SetRunImage(ctx context.Context, id uuid.UUID, image string) error

SetRunImage scoped-writes ONLY the resolved-image provenance column, once the image resolves after the row is inserted.

func (PG) SetRunProxyRelease added in v0.8.0

func (s PG) SetRunProxyRelease(ctx context.Context, id uuid.UUID, release string) error

SetRunProxyRelease — see RunReviver.

func (PG) SetRunSizing added in v0.8.6

func (s PG) SetRunSizing(ctx context.Context, id uuid.UUID, z RunSizing) error

SetRunSizing scoped-writes ONLY the dispatch-time sizing columns. It does not bump updated_at: the record is not activity, and the idle clock reads updated_at.

func (PG) SetRunStatusDetail added in v0.7.6

func (s PG) SetRunStatusDetail(ctx context.Context, id uuid.UUID, detail string) error

SetRunStatusDetail scoped-writes ONLY status_detail — what the substrate says a STARTING run is waiting on, or the control plane's own `image: Building` line on a PENDING run (a lifetime end reason rides KillRunAtLifetimeIf). Fed by runner.SandboxSpec.OnWaiting, once per CHANGE of reason. Deliberately does NOT bump updated_at: that column is also what the idle reaper and the killed-run tail-upload grace measure by, and a diagnostic line a kubelet triggers must never buy a run more life or hold a terminal run's upload door open.

func (PG) SetRunTitle added in v0.8.0

func (s PG) SetRunTitle(ctx context.Context, id uuid.UUID, title string) error

SetRunTitle — see RunTitler.

func (PG) SetRunnerArch added in v0.9.1

func (s PG) SetRunnerArch(ctx context.Context, id uuid.UUID, arch string) error

SetRunnerArch records the CPU architecture a runner reported on connect, the latest kept; a revoked runner keeps the one it had. The caller has checked it with types.ValidArch.

func (PG) SetRunnerMachineName added in v0.9.0

func (s PG) SetRunnerMachineName(ctx context.Context, id uuid.UUID, name string) error

SetRunnerMachineName records the host name a runner reported on connect, the latest kept; a revoked runner keeps the one it had. The caller has checked it with types.ValidMachineName.

func (PG) SetSandboxRef

func (s PG) SetSandboxRef(ctx context.Context, id uuid.UUID, ref string) error

SetSandboxRef records the runner reference (container ID / pod name). A non-empty ref also records this release as the one that started the run's proxy (proxy_release, migration 0084).

func (PG) SetSourceScanResult added in v0.5.0

func (s PG) SetSourceScanResult(ctx context.Context, id uuid.UUID, profile []byte, status types.WorkspaceStatus, runID uuid.UUID, seed map[string]types.WorkspaceRequirement) (types.Source, error)

SetSourceScanResult persists a scan outcome FENCED on the claiming run: only the run holding active_run_id may write, so a stale upload from a superseded run can never clobber a fresher result. `seed` rebuilds the scan_seeded subset of requirements (provenance-aware, see sourceScanRebuild); NULL (failed scan) leaves the contract untouched, while a non-nil empty seed still rebuilds — see sourceRequirementsParam.

func (PG) SetSourceScanResultUnfenced added in v0.5.0

func (s PG) SetSourceScanResultUnfenced(ctx context.Context, id uuid.UUID, profile []byte, status types.WorkspaceStatus, seed map[string]types.WorkspaceRequirement) (types.Source, error)

SetSourceScanResultUnfenced persists a SYNCHRONOUS (inline local_dir) scan, which never claimed a run slot to fence against. Same provenance-aware rebuild semantics as the fenced writer (sourceScanRebuild).

func (PG) SetWorkspaceApprovedEgress

func (s PG) SetWorkspaceApprovedEgress(ctx context.Context, id uuid.UUID, domains []string) (types.Workspace, error)

SetWorkspaceApprovedEgress replaces ONLY the approved-egress column (plus updated_at and egress_edited_at), scoped so it can never clobber a concurrently-persisted async scan. egress_edited_at always bumps, even for an identical list, since ReconcileWorkspaceEgressDecisions treats it as "the operator confirmed this at this moment".

func (PG) SetWorkspaceBuiltImage

func (s PG) SetWorkspaceBuiltImage(ctx context.Context, id uuid.UUID, observedRef, imageRef, builtHash string, rec *types.ImageBuild) (bool, error)

SetWorkspaceBuiltImage scoped-writes ONLY the image cache columns and the build record (build-once/reuse-many) in one statement. It is a compare-and-swap on the image_ref the caller observed: that is what fences a run-launch build against a concurrent build (design env-build-record §3.1). applied=false means another build moved image_ref first (or the workspace is gone); the caller keeps its own image and must not reclaim the old tag.

func (PG) SetWorkspaceDeniedEgress added in v0.5.0

func (s PG) SetWorkspaceDeniedEgress(ctx context.Context, id uuid.UUID, domains []string) (types.Workspace, error)

SetWorkspaceDeniedEgress is denied_egress's mirror of SetWorkspaceApprovedEgress: same anti-clobber scoping, same full-list replace, same egress_edited_at stamp (removing a permanent DENY is an operator override the boot heal must not undo).

func (PG) SetWorkspaceImportState

func (s PG) SetWorkspaceImportState(ctx context.Context, id uuid.UUID,
	status types.WorkspaceStatus, activeRunID *uuid.UUID, expectedActive *uuid.UUID) (types.Workspace, bool, error)

SetWorkspaceImportState is the scoped writer the scan orchestrator uses to advance status + the in-flight run pointer. Fenced on expectedActive (the active_run_id observed in the read the decision came from; nil means "no in-flight run"), so a caller deciding from a STALE read cannot land it. applied=false means the slot moved under the caller, which must re-read rather than retry blindly.

func (PG) SetWorkspaceLLMCred added in v0.4.0

func (s PG) SetWorkspaceLLMCred(ctx context.Context, id uuid.UUID, cred *types.WorkspaceLLMCred) (types.Workspace, error)

SetWorkspaceLLMCred replaces ONLY the model/harness cred binding column (plus updated_at), scoped so it can never clobber a concurrently-persisted async scan. Pass nil to clear the binding.

func (PG) SetWorkspaceOwner added in v0.6.0

func (s PG) SetWorkspaceOwner(ctx context.Context, id uuid.UUID, owner string) (types.Workspace, error)

SetWorkspaceOwner replaces ONLY the owned_by column (plus updated_at) — the offboarding reassign (decision O6). Deliberately the ONLY writer of the column after CreateWorkspace: UpdateWorkspace omits owned_by, so an ordinary edit can never move ownership.

func (PG) SetWorkspaceRecordResult

func (s PG) SetWorkspaceRecordResult(ctx context.Context, id uuid.UUID,
	taskKey string, result json.RawMessage, onlyIfStatus string) (types.Workspace, bool, error)

SetWorkspaceRecordResult atomically upserts ONE task's entry in the Record Mode record_results map (jsonb merge, so concurrent writers of DIFFERENT tasks never lose each other's entries). When onlyIfStatus is non-empty the write is a compare-and-set against the task's stored status. Returns applied=false (no error) on a guard miss.

func (PG) SetWorkspaceRequirements added in v0.5.0

func (s PG) SetWorkspaceRequirements(ctx context.Context, id uuid.UUID, reqs map[string]types.WorkspaceRequirement) (types.Workspace, error)

SetWorkspaceRequirements replaces ONLY the requirements-contract column (plus updated_at), scoped like SetWorkspaceApprovedEgress. Pass the FULL desired map — this replaces rather than merges.

func (PG) SpendApprovalOnce added in v0.7.10

func (s PG) SpendApprovalOnce(ctx context.Context, id uuid.UUID, jti string) (bool, error)

SpendApprovalOnce spends a `once` Azure DevOps capability approval: it writes jti into the row's minted_jti if, and only if, nothing has spent it yet. spent=false means another request already did (or the row is not an approved, once-scoped escalation) and the caller must not forward. The empty-minted_jti condition is the whole exactly-once argument: two concurrent re-resolves both reach this UPDATE, Postgres serialises them on the row, and the second finds the column already written.

Not part of the Store interface (see ResolveReauthApproval): the api package type-asserts for it.

func (PG) StageRunnerExit added in v0.9.0

func (s PG) StageRunnerExit(ctx context.Context, runnerID, runID uuid.UUID, exit runnerwire.AgentExit) error

StageRunnerExit is the checked read-loop path. It stores one immutable, bounded candidate for the issued generation without binding its asserted handle or changing lifecycle/audit. The durable stage is sufficient for ACK.

func (PG) StalePromotedRuns added in v0.9.0

func (s PG) StalePromotedRuns(ctx context.Context, staleAfter time.Duration) ([]types.AgentRun, error)

StalePromotedRuns are the runs that were promoted out of the queue, are still PENDING with no sandbox, and whose start has not touched its lease for staleAfter: the process that promoted them is gone. Only promotion writes admitted_at, so a plain run in its image build, whose heartbeat is its insert time, is never listed.

func (PG) StampRunActive added in v0.8.0

func (s PG) StampRunActive(ctx context.Context, id uuid.UUID) (bool, error)

StampRunActive — see RunPauser.

func (PG) StampRunTokenRenewed added in v0.8.0

func (s PG) StampRunTokenRenewed(ctx context.Context, id uuid.UUID) (bool, error)

StampRunTokenRenewed — see RunLoser.

func (PG) StampServerRun added in v0.9.0

func (s PG) StampServerRun(ctx context.Context, id uuid.UUID, remote string, reserved types.RunnerAmounts) error

StampServerRun records the remote-hosted runner a run Wardyn launched itself (a sign-in, a probe, a source scan) is dispatched to, and what it reserves there, so it counts in that runner's In use. Such a run is not placed through a pool and is not held to the runner's limits; it only counts. A run already bound to a runner of either kind is left as it is.

func (PG) StartGroupRemoval added in v0.8.6

func (s PG) StartGroupRemoval(ctx context.Context, groupID, identityID uuid.UUID) error

StartGroupRemoval opens the ledger of one person leaving one group, in one transaction with the removal of the membership row. The three steps join as pending when the person was recorded a member (a new removal, so an earlier finished one is cleared) or when no removal of this pair was ever recorded (a member whose add never reached Wardyn is still removed). A pair whose last removal finished and who has not been added since is a repeat and changes nothing, so an identity provider that repeats the request costs nothing, and a removal still pending is resumed.

func (PG) StartQueuedRun added in v0.9.0

func (s PG) StartQueuedRun(ctx context.Context, id uuid.UUID, st QueueStart) (run types.AgentRun, ok bool, err error)

StartQueuedRun writes the start's columns on a run that was promoted (PENDING, admitted_at set) and has not been dispatched, and returns the row. ok is false when the run is no longer in that state, so a kill that won leaves nothing written. A run bound to a runner of either kind is held to that runner's limits at the size it starts with (Reserved, written to the row in the same statement), the run itself not counted, under the runner rows' locks: RunnerID when it has room (the promotion found it room there), else the first of RunnerFallbacks that has, else RunnerAtCapacityError; a remote-hosted run, RunnerPoolExecutor and then RemoteFallbacks, alike.

func (PG) StopKeptRunIf added in v0.8.0

func (s PG) StopKeptRunIf(ctx context.Context, id uuid.UUID, to types.RunState, lostAt *time.Time, lostReason types.LostReason, endsAt *time.Time) (bool, error)

StopKeptRunIf — see RunLeaser.

func (PG) SubjectKeyDestroyedSince added in v0.8.6

func (s PG) SubjectKeyDestroyedSince(ctx context.Context, owner, purpose string, since time.Time) (bool, error)

func (PG) SuspendIdentity added in v0.8.6

func (s PG) SuspendIdentity(ctx context.Context, p SuspendPlan) (SuspendResult, error)

SuspendIdentity is step 1 of a suspension, one transaction. It takes every row it will change FOR UPDATE, in id order (so a sign-in holding one of them finishes first or is refused), writes the session cutoff for every sub and email form, deactivates the identity rows and bumps their authority epoch, stamps the people rows, records the step done and the plan's pending steps. A suspension of an active identity first clears the ledger of any earlier one.

func (PG) TemplateGroupAdminGroups added in v0.9.0

func (s PG) TemplateGroupAdminGroups(ctx context.Context, persons []string) ([]string, error)

TemplateGroupAdminGroups — see TemplateStore.

func (PG) TemplateGroupLabels added in v0.9.0

func (s PG) TemplateGroupLabels(ctx context.Context, groups []string) (map[string]string, error)

TemplateGroupLabels — see TemplateStore.

func (PG) TokenDispositionFacts added in v0.9.0

func (s PG) TokenDispositionFacts(ctx context.Context, now time.Time) (TokenDispositionFacts, error)

func (PG) TouchAPIToken added in v0.6.0

func (s PG) TouchAPIToken(ctx context.Context, id uuid.UUID, now time.Time) error

TouchAPIToken records that id was just used. BEST EFFORT: the auth branch ignores the error, since failing to record a touch must never fail an otherwise-valid request.

ponytail: one UPDATE per authenticated request; add a throttle only if a hot token ever makes that write volume a problem.

func (PG) TouchDevice added in v0.8.0

func (s PG) TouchDevice(ctx context.Context, id uuid.UUID, now time.Time) error

TouchDevice records that id was just used. Best effort, like TouchAPIToken: the request path must never fail an otherwise-valid request over a lost keepalive write.

func (PG) TouchRun

func (s PG) TouchRun(ctx context.Context, id uuid.UUID) error

TouchRun bumps a run's updated_at to now() without changing any other field — the activity keepalive interactive-attach calls so the idle reaper doesn't stop a run a human is attached to. The TERMINAL-run guard lives HERE, in one WHERE clause, rather than at each of the four callers: updated_at is also the clock the killed-run tail-upload grace is measured from, so an authenticated caller touching a killed run's row on a cadence could hold that upload door open indefinitely. State predicate is the positive list, for the reason given on CountActiveRunsBy.

func (PG) TouchRunner added in v0.9.0

func (s PG) TouchRunner(ctx context.Context, id uuid.UUID, version string, now time.Time) error

TouchRunner records an authenticated session: last_seen_at and the runner's version. Best effort in the caller, like TouchDevice.

func (PG) TouchRunnerActivity added in v0.9.0

func (s PG) TouchRunnerActivity(ctx context.Context, runnerID, runID uuid.UUID) error

TouchRunnerActivity is only for H2's current-connection, nonreplayed signal. The committing transaction rechecks runner ownership and lapse; timestamps come from the database. Neither audit ingestion nor replay calls this method.

func (PG) UnbindIdentity added in v0.9.0

func (s PG) UnbindIdentity(ctx context.Context, id uuid.UUID, principal string) error

func (PG) UpdateBaseImageName added in v0.5.0

func (s PG) UpdateBaseImageName(ctx context.Context, id uuid.UUID, name string) (types.BaseImageEntry, error)

UpdateBaseImageName renames a catalog base-image row — the explicit, scoped rename path, mirroring UpdateSourceConfig above. handleCreateBaseImage is the only caller, on an identity hit where the REQUEST carried an explicit name (never via UpsertBaseImage's own conflict clause — see its doc).

func (PG) UpdateComponent added in v0.8.9

func (s PG) UpdateComponent(ctx context.Context, c types.Component) (types.Component, error)

UpdateComponent — see ComponentStore.

func (PG) UpdatePolicy

func (s PG) UpdatePolicy(ctx context.Context, id uuid.UUID, name string, spec types.RunPolicySpec) (types.RunPolicy, error)

UpdatePolicy replaces a policy's name and spec and bumps updated_at. Returns ErrNotFound when no policy has the given id, and ErrConflict when the rename collides with run_policies.name's UNIQUE constraint — same mapping as CreatePolicy, since a rename onto a taken name is the same caller-fixable mistake as an insert under one. The caller validates the spec before calling.

func (PG) UpdateRemoteRunner added in v0.9.0

func (s PG) UpdateRemoteRunner(ctx context.Context, id string, revision int64, name *string, limits *types.RunnerLimits, by string) (before, after types.RemoteRunner, err error)

func (PG) UpdateRunStateIf

func (s PG) UpdateRunStateIf(ctx context.Context, id uuid.UUID, fromState, toState types.RunState) (bool, error)

UpdateRunStateIf conditionally transitions a run from fromState to toState in a single UPDATE ... WHERE id=$ AND state=$from, returning whether it applied. TOCTOU-safe: a concurrent kill/stop that already moved the run to a terminal state is never clobbered. From QUEUED it is LeaveRunQueue, so the run's queue row goes in the same statement. False with a nil error means someone else won the transition (or the run doesn't exist); the caller does nothing.

func (PG) UpdateRunStateIfCreatedBefore added in v0.8.6

func (s PG) UpdateRunStateIfCreatedBefore(ctx context.Context, id uuid.UUID, fromState, toState types.RunState, createdNotAfter time.Time) (bool, error)

UpdateRunStateIfCreatedBefore transitions a run only when still in fromState, its age anchor (admitted_at when it waited in the pool queue, else created_at) is at or before createdNotAfter, and its agent still runs (HoldsSandboxSQL: not kept, or kept after an outage with no agent stop recorded; a run kept with its agent stopped is left to its files grace). The max-age stop's own predicate on the run's age: it carries neither the idleness guard nor the open-request guard of UpdateRunStateIfIdle, because an absolute age cap exists to end a run that is not going to finish by itself.

func (PG) UpdateRunStateIfIdle

func (s PG) UpdateRunStateIfIdle(ctx context.Context, id uuid.UUID, fromState, toState types.RunState, notAfter time.Time) (bool, error)

UpdateRunStateIfIdle is UpdateRunStateIf plus an idleness guard: it transitions a run only when still in fromState, updated_at has not advanced past notAfter (the caller's snapshot), it has no lost/end mark, and it has no open hold-aware request within its wait. Closes the reaper's idleness TOCTOU — an active `wardyn run attach` TouchRun can bump updated_at between snapshot and UPDATE, so guarding on state alone could stop a now-active run; notAfter makes a touched-since-snapshot run no-op instead. The hold-aware clause enforces "idle stop never fires while a request is open within its wait" inside the same atomic CAS, since a request raised in that window is visible only to a check inside the statement.

func (PG) UpdateRunnerLimits added in v0.9.0

func (s PG) UpdateRunnerLimits(ctx context.Context, id uuid.UUID, owner string, limits types.RunnerLimits) (before *types.RunnerLimits, after types.Runner, err error)

UpdateRunnerLimits declares the limits of a runner its owner holds, unclaimed or claimed, and returns the set it had (nil for none) and the row as it is now. Another person's runner, a revoked one and an absent id are ErrNotFound alike. Lowering a limit ends no run: the runs over it keep running, and no new run is placed there until the runner is under it.

func (PG) UpdateRunnerPool added in v0.9.0

func (s PG) UpdateRunnerPool(ctx context.Context, id uuid.UUID, revision int64, name *string, state *types.RunnerPoolState, limits *types.RunnerPoolLimits) (types.RunnerPool, error)

UpdateRunnerPool renames, switches or re-limits a pool, only at the revision the caller read: ErrRunnerPoolStale otherwise, ErrNotFound for an unknown or deleted pool, ErrConflict when the name is taken. A non-nil limits replaces the stored block whole; nil leaves it. A change that moves nothing, limits compared as canonical JSON, writes nothing and keeps the revision.

func (PG) UpdateServiceIdentity added in v0.9.0

func (s PG) UpdateServiceIdentity(ctx context.Context, name string, ch ServiceIdentityChange) (ServiceIdentityChanged, error)

func (PG) UpdateSourceConfig added in v0.5.0

func (s PG) UpdateSourceConfig(ctx context.Context, id uuid.UUID, name string, reqs map[string]types.WorkspaceRequirement) (types.Source, error)

UpdateSourceConfig replaces a source's operator-editable fields (name, requirements) — scoped, never touching scan-owned columns.

func (PG) UpdateTemplate added in v0.9.0

func (s PG) UpdateTemplate(ctx context.Context, t types.RunTemplate, expected int, grant *TemplateGrantCheck) (types.RunTemplate, error)

UpdateTemplate — see TemplateStore.

func (PG) UpdateUserType added in v0.8.0

func (s PG) UpdateUserType(ctx context.Context, t types.UserType) (types.UserType, error)

UpdateUserType rewrites a type's name, description and priority; id and built_in never change. ErrNotFound when missing, ErrConflict on name clash.

func (PG) UpdateUserTypeMetadata added in v0.8.6

func (s PG) UpdateUserTypeMetadata(ctx context.Context, t types.UserType) (types.UserType, error)

UpdateUserTypeMetadata rewrites a type's name and description and never its priority, so an edit that was judged metadata-only cannot overwrite a priority change approved after it was read. ErrNotFound when missing, ErrConflict on name clash.

func (PG) UpdateWorkspace

func (s PG) UpdateWorkspace(ctx context.Context, id uuid.UUID, ws types.Workspace, stampEgressEdit bool) (types.Workspace, error)

UpdateWorkspace replaces a workspace's editable composition via a FULL-column write of every scan-owned field (profile, image_ref, built_profile_hash, status, etc.), WITH ONE DELIBERATE EXCEPTION: denied_egress is NOT in this SET clause (see Workspace.DeniedEgress) — a permanent deny survives a composition edit, unlike ApprovedEgress. Callers must round-trip the fetched row. Returns ErrNotFound for an unknown id.

egress_edited_at is passed through unless stampEgressEdit marks this write as the operator action the column records: unconditional stamping would suppress the boot heal for a decision nobody undid, while plain omission let this writer clear approved_egress without moving the stamp, so ReconcileWorkspaceEgressDecisions re-widened the list on restart. The stamp is now() from the database, not wardynd's clock, since it's compared against approvals.decided_at and clock skew there fails OPEN.

func (PG) UpsertBaseImage added in v0.5.0

func (s PG) UpsertBaseImage(ctx context.Context, b types.BaseImageEntry) (types.BaseImageEntry, error)

UpsertBaseImage inserts a catalog image or returns the row with the same identity (kind, image, steps) — the identity index is the dedupe rule. The CHECK constraint refuses 'recommended' structurally: that build is derived per-workspace and has no catalog identity.

An identity hit does NOT touch name, deliberately: this upsert is also the PASSTHROUGH path a workspace/run resolves its base-image spec through, which always derives an auto-placeholder name — applying EXCLUDED.name unconditionally would silently rename an operator's custom-named row back to that placeholder. See UpdateBaseImageName for the rename path.

func (PG) UpsertCapabilityGrant added in v0.6.0

func (s PG) UpsertCapabilityGrant(ctx context.Context, g types.CapabilityGrant) (types.CapabilityGrant, error)

UpsertCapabilityGrant writes one grant, keyed on the natural (subject_type, subject, capability, value) UNIQUE: re-granting the same triple FLIPS the effect in place rather than leaving two contradictory rows (which would resolve as a permanent deny). The returned row carries the EXISTING id on a conflict, not g.ID, so a re-submit gets the id the DELETE route needs.

func (PG) UpsertGovernanceAssignment added in v0.7.0

func (s PG) UpsertGovernanceAssignment(ctx context.Context, a types.GovernanceAssignment) (types.GovernanceAssignment, error)

UpsertGovernanceAssignment binds one subject to one profile, keyed on the natural UNIQUE (subject_type, subject): re-assigning a subject REPOINTS its single row rather than leaving a second one behind (two rows for one subject would make "which profile does Bob get" a tie-break instead of the admin's last write).

The returned row carries the EXISTING id on conflict, not a.ID, so the caller gets the id the DELETE route needs.

Returns ErrNotFound when profile_id names no profile (FK 23503 → 404).

func (PG) UpsertGovernanceProfile added in v0.7.0

func (s PG) UpsertGovernanceProfile(ctx context.Context, p types.GovernanceProfile) (types.GovernanceProfile, error)

UpsertGovernanceProfile writes one profile exactly as given. It is WriteGovernanceProfile with nothing to decide, so it takes the same graph lock and a write can never skip it.

func (PG) UpsertLoginIdentity added in v0.8.6

func (s PG) UpsertLoginIdentity(ctx context.Context, in LoginIdentity, now time.Time) (PrincipalIdentity, error)

func (PG) UpsertRoleMapping added in v0.7.0

func (s PG) UpsertRoleMapping(ctx context.Context, m types.RoleMapping) (types.RoleMapping, error)

UpsertRoleMapping writes one row, keyed on the natural UNIQUE (value): re-adding an already-mapped value FLIPS its role in place rather than leaving a second row behind — the same "re-granting the same key updates it" contract UpsertCapabilityGrant follows, for the same reason (two rows for one value cannot happen; the UNIQUE index already forbids it, this just makes the natural retry path a clean update instead of a 409).

The returned row carries the row's real id, which on a conflict is the EXISTING one, not m.ID — the caller needs the id the DELETE route will be given, and an admin re-submitting the same value must not be handed an id that names no row.

A user_type naming no user_types row fails the foreign key; that is ErrNotFound, so the caller can tell a type deleted between its check and this write from a store fault.

The conflict path updates role and user_type only, deliberately NOT created_by — creation provenance (who ADDED this mapping) stays with the original creator across a later role flip by a different admin, the same way created_at is untouched on conflict (no SET at all, so Postgres leaves it).

The conflict path also forces migrated_from_member to false, UNCONDITIONALLY — on a flip to admin/security_admin too, not only a real type pick (never EXCLUDED.migrated_from_member, which the caller never sets to true anyway — see the type's own doc comment). Decision, not an oversight: the marker means "a person still needs to look at this row", and an admin choosing ANYTHING for it — a type or a tier — is that look. The alternative (clear only on a type pick) would let the marker survive a flip to admin and ride along if the row is later flipped BACK to user with no type named — a "migrated" chip on a row an admin, in the interim, explicitly decided was an admin mapping, which is a worse answer than "the row was touched, the flag's job is done" for a marker the design itself frames as a one-time prompt, never a permanent history bit. TestPG_RoleMappings_UpsertClearsMigratedFromMemberOnAdminFlip pins this call.

func (PG) UpsertSource added in v0.5.0

func (s PG) UpsertSource(ctx context.Context, src types.Source) (types.Source, error)

UpsertSource inserts a library source or returns the existing row with the same identity (kind, locator, ref) — ONE statement, no read-then-write race: the UNIQUE constraint IS the dedupe rule. Identity fields must arrive CANONICALIZED (the api layer owns that) — the store stores what it is given.

func (PG) UpsertUserDrive added in v0.7.0

func (s PG) UpsertUserDrive(ctx context.Context, d types.UserDrive, refuseIfAllocated bool) (types.UserDrive, error)

UpsertUserDrive writes one drive keyed on ID: INSERT if absent, else UPDATE in place — renaming is allowed while grants still point at it, since ON DELETE RESTRICT rules out delete-and-recreate for an allocated drive. A rename moves the PVC's name; existing members bind an object that no longer exists until they re-mount (docs/OPERATIONS.md "User drives on Kubernetes"). Returns ErrConflict on UNIQUE(name).

An identity-affecting PUT on an already-allocated drive is refused 409 (ErrDriveAllocated) unless ?confirm=rehome; the precondition is re-asserted in this statement, under the row lock UpsertUserDrive takes, so a grant created between the guard's read and this write can't be silently re-homed.

SECURITY: two host_path shares over the same root must derive home names the same way (namespace is the host_root, not the drive — see shareBackend). Different home_templates (`sub` vs `email_local`) could otherwise fold two principals onto one directory name, breaking migration 0059's one-home-per-principal invariant on a backend its index can't reach; same-template drives on one root stay legal. Enforced in the statement itself, not a prior read — not a hard constraint under READ COMMITTED, but the case that actually happens (one admin registering a second view) can't race itself.

created_by/created_at are untouched on update: provenance stays with whoever registered the drive.

func (PG) UpsertUserDriveGrant added in v0.7.0

func (s PG) UpsertUserDriveGrant(ctx context.Context, g types.UserDriveGrant, homeOverrideStated bool) (types.UserDriveGrant, error)

UpsertUserDriveGrant allocates one drive to one subject and REPLACES whatever the subject was allocated before: the subject ends with exactly this drive, and an existing row is repointed rather than a second left beside it, so the admin's last write decides which drive the subject gets. Since migration 0169 a subject may hold several rows (AddUserDriveGrant); this keeps one, repoints it, and removes the others, in one transaction. On a repoint the returned id is the EXISTING row's, not g.ID. Returns ErrNotFound when drive_id names no drive (FK, 23503).

SECURITY: refuses (ErrConflict) if another subject already holds the same home_override in the same object-name NAMESPACE (host_root, not drive — see shareBackend) — one home_override must name one person's directory, or two people end up sharing a minted volume with every bind-time check passing. Guard checks same-drive OR two host_path drives on the same host_root (compared as the stored string; driveHostRootNesting handles the symlink case on the drive-write path). Enforced in the statement itself, one round trip: migration 0059's partial unique index is the race-free floor for the same-drive case; the cross-drive case has none, so here the statement is the whole guard — sufficient for the case that happens, one admin typing one name twice. Residual: a DERIVED (non-override) home can still collide across same-root shares with different templates; not refusable here since a group/all grant doesn't know its principals until sign-in.

enabled is written verbatim, so a zero-value grant is disabled by default — fail-closed at the API boundary, where the omission is still visible.

SECURITY: also guards a silent re-home. Clearing home_override moves that person to the derived name next run, stranding their old directory unnamed. homeOverrideStated is the API's tri-state for "the client said nothing": false refuses the update on a row with a stated override (no row returned, read as ErrConflict); stating it IS the confirmation, no ?confirm= to invent. The zero value is safe — a caller that forgets the argument gets a refusal, not a silent clear.

func (PG) UpsertUserDriveGrantWithinTier added in v0.9.0

func (s PG) UpsertUserDriveGrantWithinTier(ctx context.Context, g types.UserDriveGrant, homeOverrideStated, additional bool) (types.UserDriveGrant, []uuid.UUID, error)

UpsertUserDriveGrantWithinTier is the allocation write of either verb, ReplaceUserDriveGrant or with additional AddUserDriveGrant, judged by types.DriveGrantWidens in the write's own transaction. The drive is read FOR SHARE, then the subject's allocation lock is taken, then the judged row is read FOR UPDATE: with additional the row for this subject and drive, otherwise the row the replace keeps and repoints. A concurrent edit of the drive or of that row therefore lands wholly before the judgement or waits for the write, and nothing is removed before the judgement. A write judged against there being no row is written only while there is still none (ErrUserDriveGrantChanged otherwise). The removed drives are ReplaceUserDriveGrant's. READ COMMITTED is pinned for UpsertUserDrive's reason. ErrNotFound for an unknown drive.

func (PG) UserTypeReferences added in v0.8.0

func (s PG) UserTypeReferences(ctx context.Context, id string) (int, error)

UserTypeReferences counts the capability-grant, governance-assignment, drive-grant and unretired service-identity rows that name the type.

func (PG) UserTypeTokenStamps added in v0.8.0

func (s PG) UserTypeTokenStamps(ctx context.Context, id string) (int, error)

UserTypeTokenStamps counts the unrevoked API tokens stamped with the type.

func (PG) VerifyAuditChain added in v0.6.0

func (s PG) VerifyAuditChain(ctx context.Context) (AuditChainStatus, error)

VerifyAuditChain walks the audit_events hash chain oldest-first, re-hashing every row with migration 0047's audit_row_hash — the SAME function the insert trigger uses, so there's no second implementation to drift.

The sweep is paged as a correctness property, not just a cost one: an unbounded `ORDER BY seq` can plan as Seq Scan -> Sort, buffering the whole table before returning row one — the tamper-investigation endpoint would get slowest over time. Keyset paging (`seq > $1 ORDER BY seq LIMIT n`) is an index scan with a bound instead, so memory is one page and an early break genuinely stops the work.

Paging does not verify less: since migration 0056 the trigger allocates seq while holding the chain lock and releases it at commit, so seq order IS commit order, and a row can never appear below a cursor the walk already passed. Walk state is carried across pages by one auditChainWalk, so a chain longer than a page is one continuous chain.

Everything runs in ONE repeatable-read snapshot, so the chain, the newest attested retention drop, the high-water mark and the partition manifest are the same instant: an append between two reads cannot read as a removed tail. The walk starts from the newest `drop` anchor that dropped rows (row_count above 0: an empty partition leaves nothing to chain to; the first retained row must chain to its recorded tail) and still inspects every retained row. The chain alone cannot see a removed NEWEST tail, so after it the newest row must also match audit_partition_meta's high-water mark, and every partition the expected manifest names must be present unless an anchor accounts for it.

func (PG) WithIdentityShared added in v0.8.6

func (s PG) WithIdentityShared(ctx context.Context, g IdentityGuard, fn func() error) error

WithIdentityShared runs fn while g's identity rows are held FOR SHARE and unchanged: the write a sign-in's captured credential makes lands before a suspension takes the rows, or is refused with ErrIdentityDeactivated. fn may write through another connection (the secret store).

The rows are held on a connection dialled for this call, outside the pool: fn borrows from the pool, and a hold taken from it would wait on itself once the pool is down to that one connection. The dials are bounded like the lock connections: at most db.LockPoolMaxConns at once per pool, process-wide, and a call that finds them all in use waits db.LockPoolAcquireWait and is then refused with db.ErrLockNoCapacity.

func (PG) WorkspacesAttaching added in v0.5.0

func (s PG) WorkspacesAttaching(ctx context.Context, id uuid.UUID) ([]string, error)

WorkspacesAttaching returns the names of workspaces whose attachments reference source id — the loud half of delete-in-use (one GIN probe on workspaces_attachments_gin). DELETE refuses with these names rather than silently orphaning a workspace to a source that no longer exists.

func (PG) WorkspacesUsingBaseImage added in v0.5.0

func (s PG) WorkspacesUsingBaseImage(ctx context.Context, id uuid.UUID) ([]string, error)

WorkspacesUsingBaseImage is the catalog's delete-in-use check.

func (PG) WriteGovernanceProfile added in v0.8.6

func (s PG) WriteGovernanceProfile(ctx context.Context, id uuid.UUID, build GovernanceProfileBuild) (types.GovernanceProfile, error)

WriteGovernanceProfile writes one profile keyed on its PRIMARY KEY: an unminted id INSERTs, an existing one UPDATEs in place (name included, so a profile can be renamed while assignments still point at it, since ON DELETE RESTRICT makes delete-and-recreate impossible for an assigned profile). One statement serves POST (fresh id) and PUT (id from the path).

The whole write is one transaction under a graph-wide advisory lock: build sees every profile as that transaction reads them, decides the row (merging what a PUT left absent, refusing a cycle, a depth overflow or an overlay its base does not permit) and the row is stored before the lock is released, so two concurrent writes cannot together make a cycle or a depth-4 chain.

contact follows GovernanceProfile.ContactSet: written (or cleared, when empty) only when it is true, otherwise the stored value stays. A composed row (Overlay set) binds '{}' for ceiling and limits and sets them outright, since it stores no raw authority; its composition columns are always written from the row as given.

Returns ErrConflict when UNIQUE(name) rejects the write. created_by and created_at are NOT touched on update: creation provenance stays with whoever authored the profile.

type Page added in v0.3.1

type Page struct {
	Limit  int
	Offset int
}

Page bounds a List query to Limit rows after Offset, ordered by the query's own ORDER BY. Limit<=0 means unbounded, used by internal callers needing the whole table; public read handlers always pass an explicit Limit (capped by api.parseListPage).

type Pager added in v0.3.1

type Pager interface {
	ListRunsPage(ctx context.Context, p Page) ([]types.AgentRun, error)
	ListPoliciesPage(ctx context.Context, p Page) ([]types.RunPolicy, error)
	ListWorkspacesPage(ctx context.Context, p Page) ([]types.Workspace, error)
	ListApprovalsPage(ctx context.Context, stateFilter types.ApprovalState, p Page) ([]types.ApprovalRequest, error)
	QueryAuditEventsPage(ctx context.Context, runID uuid.UUID, p Page) ([]types.AuditEvent, error)
	QueryRecentAuditEventsPage(ctx context.Context, p Page) ([]types.AuditEvent, error)
	// QueryAuditEventsFilteredPage serves a narrowed audit read (time range,
	// action, outcome, actor type); see AuditFilter in auditfilter.go.
	QueryAuditEventsFilteredPage(ctx context.Context, runID *uuid.UUID, f AuditFilter, p Page) ([]types.AuditEvent, error)
	// ListUserDriveGrantsPage serves the drives console's allocation table;
	// its ORDER BY has no index, so unbounded it sorts every allocation on
	// every load of the admin screen.
	ListUserDriveGrantsPage(ctx context.Context, p Page) ([]types.UserDriveGrant, error)
}

Pager is the paginated read surface, deliberately excluded from Store: widening Store would silently route a test double's embedded-but-not- overridden methods to it. Handlers type-assert for Pager, falling back to unbounded List* + in-Go windowing when a store doesn't implement it; PG always does.

type PartitionDigest added in v0.8.6

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

PartitionDigest is the streaming fold of a closed partition, in seq order:

d_0 = sha256(Header)            d_i = sha256(d_{i-1} || row_hash_i)

every value lowercase hex text. It holds one 64-byte state however many rows are added, so a partition larger than Postgres's 1 GB field limit digests in constant memory.

func NewPartitionDigest added in v0.8.6

func NewPartitionDigest(m PartitionManifest) *PartitionDigest

NewPartitionDigest starts the fold at the manifest's header.

func (*PartitionDigest) Add added in v0.8.6

func (p *PartitionDigest) Add(rowHash string)

Add folds one row's hash (the stored row_hash; a row from before the chain began folds the hash audit_row_hash(NULL, ...) would have given it) into the digest.

func (*PartitionDigest) Sum added in v0.8.6

func (p *PartitionDigest) Sum() string

Sum is the digest so far, as 64 hex characters.

type PartitionManifest added in v0.8.6

type PartitionManifest struct {
	Partition    string `json:"partition"`
	Rows         int64  `json:"row_count"`
	SeqLo        int64  `json:"seq_lo"`
	SeqHi        int64  `json:"seq_hi"`
	RecordedLoUS int64  `json:"recorded_lo_us"`
	RecordedHiUS int64  `json:"recorded_hi_us"`
}

PartitionManifest is what a partition's digest binds besides its rows: its name, the range of seq and recorded_at it actually holds (recorded_at as integer microseconds since the epoch, the unit audit_row_hash uses) and its row count. An empty partition has Rows 0 and every range zero.

Header is the canonical text the digest starts from. It is the migration 0119 definition, which audit_partition_digest(p_partition) implements in the database: the two are pinned together by a test.

func (PartitionManifest) Header added in v0.8.6

func (m PartitionManifest) Header() string

Header is the JSON array of strings the fold starts from, in jsonb's own text form (a comma and a space between elements), so a manifest the database and Go each render is byte-identical. A range of an empty partition renders as an empty string.

type PauseCandidate added in v0.8.0

type PauseCandidate struct {
	Run types.AgentRun
	// OpenRequest: the run has a PENDING request inside its wait (openHoldSQL).
	OpenRequest bool
	// WaitingRequest: one of those counts toward a waiting pause
	// (waitingHoldSQL).
	WaitingRequest bool
}

PauseCandidate is one live run as the pause sweep sees it.

type PauseSettle added in v0.8.6

type PauseSettle struct {
	RunID uuid.UUID
	DueAt time.Time
}

PauseSettle is one run a pause compensation may have left with its agent container not matching its pause mark (migration 0132).

type PauseSettler added in v0.8.6

type PauseSettler interface {
	// NotePauseSettle records run id as owing a settle from after on (the
	// database's clock), moving a due_at already recorded only forward, and
	// returns the due_at it holds.
	NotePauseSettle(ctx context.Context, id uuid.UUID, after time.Duration) (time.Time, error)
	// DuePauseSettles lists the runs whose settle is due.
	DuePauseSettles(ctx context.Context) ([]PauseSettle, error)
	// ClearPauseSettle deletes run id's record only while it still reads due,
	// so a later note survives.
	ClearPauseSettle(ctx context.Context, id uuid.UUID, due time.Time) error
}

PauseSettler is the durable record of the runs whose container may not match their pause mark. Optional like RunPauser; production is always PG.

type PendingGroupRemoval added in v0.8.6

type PendingGroupRemoval struct {
	GroupID    uuid.UUID
	IdentityID uuid.UUID
}

PendingGroupRemoval is one person's removal from one group with ledger rows still pending.

type PendingLeaver added in v0.8.6

type PendingLeaver struct {
	ID     uuid.UUID
	Purged bool
}

PendingLeaver is an identity with ledger rows still pending: a suspension its identity provider stopped retrying, or a purge begun and not finished.

type PeopleDirectoryFilter added in v0.8.6

type PeopleDirectoryFilter struct {
	// Query keeps people whose principal starts with it (case-sensitive) or whose email starts
	// with it (case-insensitive). Empty keeps everyone.
	Query string
	// State is "active" (not deactivated), "deactivated", or "" for both.
	State string
	// After is the last principal of the previous page; empty starts at the top.
	After string
	Limit int
}

PeopleDirectoryFilter narrows and pages ListPeopleDirectory.

type PeopleDirectoryPage added in v0.8.6

type PeopleDirectoryPage struct {
	People []PersonListing
	Next   string
}

PeopleDirectoryPage is one page; Next is the principal to pass as After, or "" on the last page.

type PeopleDirectoryStore added in v0.8.6

type PeopleDirectoryStore interface {
	// ListPeopleDirectory returns one page of the directory, ordered by principal.
	ListPeopleDirectory(ctx context.Context, f PeopleDirectoryFilter) (PeopleDirectoryPage, error)
}

PeopleDirectoryStore lists the people a deployment knows: every identity that has signed in (principal_identities, migration 0113) and every person an admin set up beforehand (people, migration 0090). Optional, like PersonStore.

type PersonErasureStore added in v0.8.6

type PersonErasureStore interface {
	// RunIDsCreatedBy lists every run created_by createdBy.
	RunIDsCreatedBy(ctx context.Context, createdBy string) ([]uuid.UUID, error)
	// BlankRunTasks empties the task text of every run created_by createdBy and
	// returns how many it changed. It never touches updated_at: the idle reaper
	// reads it.
	BlankRunTasks(ctx context.Context, createdBy string) (int, error)
	// PrincipalAliases lists the names the identity directory knows principal
	// by: the emails it signed in with (lowercased) and its entra:<tenant>:<object>
	// form. A row sealed under a name the directory did not yet know is under
	// that name's own key, so an erasure destroys these too.
	// Its email values are the claim text a sign-in sent, never checked for an @, so they can
	// spell another person's principal or entra form: match on them, never destroy by them.
	PrincipalAliases(ctx context.Context, principal string) ([]string, error)
	// PrincipalEntraNames lists the entra:<tenant>:<object> forms of principal's own sign-in
	// rows, built from their tenant and object ids and never from an alias value.
	PrincipalEntraNames(ctx context.Context, principal string) ([]string, error)
	// PrincipalsSharingObject lists the other principals bound to a sign-in with the same tenant and object
	// id as one of principal's own, under another issuer: often the same human, never the same principal.
	PrincipalsSharingObject(ctx context.Context, principal string) ([]string, error)
	// EraseGovernanceChangePersonalFields clears the proposer and decider (and the decider's reason) of
	// every governance change whose recorded principal is one of principals or whose recorded email is
	// one of emails (case-folded), and moves a pending change whose proposer is one of them to expired.
	// A principal is never matched against the email column, where an email claim can spell it. It
	// returns the rows it touched.
	EraseGovernanceChangePersonalFields(ctx context.Context, principals, emails []string) (int, error)
	// SubjectKeyDestroyedSince reports whether any generation of owner's key
	// for purpose has a destroyed_at after since.
	SubjectKeyDestroyedSince(ctx context.Context, owner, purpose string, since time.Time) (bool, error)
}

PersonErasureStore is what erasing a person reads and writes beyond the stores that own a scope: the person's runs, and the identity directory that maps the names an audit row carries onto one principal. Optional like PrincipalIdentityStore: callers type-assert and refuse a scope the store cannot serve rather than report it erased.

type PersonListing added in v0.8.6

type PersonListing struct {
	Principal string
	Email     string
	// Entra is true for a person keyed by Entra tenant and object id.
	Entra      bool
	PreCreated bool
	// FirstSignInAt and LastSignInAt are nil for a person who has never signed in.
	FirstSignInAt *time.Time
	LastSignInAt  *time.Time
	DeactivatedAt *time.Time
	// ActiveSessions is 0 or 1, see PeopleDirectoryActiveSessionWindow.
	ActiveSessions int
	APITokens      int
	SSHKeys        int
	Credentials    int
	ActiveRuns     int
	// Groups, GroupsTruncated and GroupsVerified are the person's last verified login groups
	// (key_domain_login_groups, stamped at every SSO sign-in). GroupsVerified is false when no
	// snapshot exists; GroupsTruncated is the login's own bit that its groups claim was cut short.
	Groups          []string
	GroupsTruncated bool
	GroupsVerified  bool
}

PersonListing is one directory row.

type PersonStore added in v0.8.0

type PersonStore interface {
	// CreatePerson inserts p, or returns the row already stored under
	// p.Principal with created=false. A different principal holding the same
	// email (case-insensitively) is ErrConflict.
	CreatePerson(ctx context.Context, p types.Person) (out types.Person, created bool, err error)
	// GetPerson is the row stored under exactly principal, or ErrNotFound.
	GetPerson(ctx context.Context, principal string) (types.Person, error)
	// ListPeople returns every row, oldest first.
	ListPeople(ctx context.Context) ([]types.Person, error)
	// MarkPersonSignedIn stamps first_signed_in_at on principal's row the first
	// time they sign in. No row is not an error: most people were never
	// pre-created.
	MarkPersonSignedIn(ctx context.Context, principal string, now time.Time) error
}

PersonStore holds the identities an admin created or confirmed before their first sign-in (migration 0090, #1157; the Entra object-id key, 0099, #1195). Optional, like DeviceStore: the API answers 501 on a store without it.

type PoolCounts added in v0.9.0

type PoolCounts struct {
	Used    int
	Waiting int
}

PoolCounts is how busy one runner pool is: runs holding one of its slots, and runs waiting for one. Counts only; no run, owner or executor is named.

type PresetWrite added in v0.8.0

type PresetWrite string

PresetWrite is what PutLaunchPreset did: created the row, replaced it (and moved its version), or found it already identical and wrote nothing.

const (
	PresetCreated   PresetWrite = "created"
	PresetUpdated   PresetWrite = "updated"
	PresetUnchanged PresetWrite = "unchanged"
)

type PrincipalDirectoryStore added in v0.9.0

type PrincipalDirectoryStore interface {
	// PrincipalsNamed returns the distinct principals name stands for, a person created by
	// object id who has signed in under a pairwise subject counting as that subject. The
	// first of these that matches decides: the exact principal, the
	// entra:<tenant>:<object> form built from tenant and object ids, the principal
	// case-folded, then, for a name with an @ only, an email (case-folded) that a
	// sign-in, an API token or a pre-created person carries. A principal, a people
	// row an admin created included, therefore always wins over an email claim that
	// spells it. The one exception is a typed entra:<tenant>:<object> name that matches
	// only a people row no sign-in follows and no principal carries: the tenant and object id match
	// under any issuer is added to the answer, so a person who signed in under another issuer is ambiguous. More than one is ambiguous; none is a name nobody is recorded under.
	PrincipalsNamed(ctx context.Context, name string) ([]string, error)
}

PrincipalDirectoryStore answers who a name an admin typed is, from every place a principal is recorded: the identity directory (principal_identities and its aliases), the people an admin pre-created, API tokens and workspace owners. Every route that erases, deletes or assigns by a person's name resolves through it, and the audit sealer keys rows by it. Optional like PersonErasureStore.

type PrincipalIdentity added in v0.8.6

type PrincipalIdentity struct {
	ID         uuid.UUID
	Principal  string
	Issuer     string
	TenantID   string
	ObjectID   string
	EmailLower string
	// ScimExternalID and ScimUserName are the SCIM projection, not binding columns.
	ScimExternalID string
	ScimUserName   string
	DeactivatedAt  *time.Time
	AuthorityEpoch int64
	PurgeAfter     *time.Time
	PurgedAt       *time.Time
	CreatedAt      time.Time
	LastLoginAt    *time.Time
}

PrincipalIdentity is one human identity Wardyn has seen sign in. An Entra identity is (Issuer, TenantID, ObjectID); any other issuer has an empty TenantID and ObjectID and is keyed by (Issuer, Principal). Principal is "" on a row written before the person's first sign-in.

type PrincipalIdentityStore added in v0.8.6

type PrincipalIdentityStore interface {
	// UpsertLoginIdentity records an approved sign-in: it creates the identity row, or finds the
	// one for the same (issuer, tenant, object id) on Entra and (issuer, principal) elsewhere, binds
	// its principal if it has none, stamps the sign-in time and keeps the email as an alias. A row's
	// principal, issuer, tenant and object id are never changed once bound; a sign-in that would
	// change one writes nothing: ErrIdentityBindingMismatch when its row is bound to another
	// principal, ErrConflict when it names no row at all.
	UpsertLoginIdentity(ctx context.Context, in LoginIdentity, now time.Time) (PrincipalIdentity, error)
	// GetIdentityByObject is the row for exactly (issuer, tenantID, objectID), or ErrNotFound. An
	// empty objectID is always ErrNotFound.
	GetIdentityByObject(ctx context.Context, issuer, tenantID, objectID string) (PrincipalIdentity, error)
	// IdentitiesByPrincipal is every row bound to principal, oldest first (one per issuer it has
	// signed in on); empty when it has none.
	IdentitiesByPrincipal(ctx context.Context, principal string) ([]PrincipalIdentity, error)
}

PrincipalIdentityStore is optional, like PersonStore: it is not part of Store, so callers type-assert and treat its absence as "nothing recorded".

type PrincipalInUseError added in v0.9.0

type PrincipalInUseError struct{ APITokens, SSHKeys, ActiveRuns, Sponsored, PendingInvitations int }

PrincipalInUseError is UnbindIdentity's refusal while the principal it would release still holds something the leaver flow reaches only through the row's principal.

func (*PrincipalInUseError) Error added in v0.9.0

func (e *PrincipalInUseError) Error() string

type PrincipalPrefStore added in v0.8.0

type PrincipalPrefStore interface {
	// GetPrincipalPref returns principal's value for key, or ErrNotFound.
	GetPrincipalPref(ctx context.Context, principal, key string) (json.RawMessage, error)
	// PutPrincipalPref upserts principal's value for key.
	PutPrincipalPref(ctx context.Context, principal, key string, value json.RawMessage) error
}

PrincipalPrefStore persists per-person preferences. Like RunLayoutStore it is not part of Store, so the api test doubles need not grow it; the api type-asserts and treats its absence as "nothing remembered".

type PromotePass added in v0.9.0

type PromotePass struct {
	PoolID uuid.UUID
	// DeploymentCap is WARDYN_MAX_CONCURRENT_RUNS, 0 for none. The pass counts the deployment's
	// slot-holders under the same advisory lock a create takes, so it never exceeds the cap.
	DeploymentCap int
	// ScanLimit is how many waiting runs the pass reads at a time; it reads on past runs it holds while it
	// still has room to fill. 0 reads them all at once.
	ScanLimit int
	// WatcherOwner is the lease holder written on each promoted run: the promoting process.
	WatcherOwner string
	// AtCapacityDetail is the status_detail of a run waiting behind the pool's cap. A pass that finds the
	// pool full writes it on every waiting run of the pool that says something else, so a run that waited
	// for a runner goes back to waiting for a slot. "" writes nothing.
	AtCapacityDetail string
}

PromotePass is one promotion pass over one pool.

type PromotedRun added in v0.9.0

type PromotedRun struct {
	Run   types.AgentRun
	Entry RunQueueEntry
}

PromotedRun is a run the pass moved to PENDING, with the queue row it carried.

type PushPathListStore added in v0.8.0

type PushPathListStore interface {
	// RecordPushPathList stores approvalID's list once, for runID. A second
	// call for the same approval stores nothing and reports false; a list
	// past the run's limit returns ErrPushPathListCap. Count and insert are
	// one step, so concurrent calls for a run serialize.
	RecordPushPathList(ctx context.Context, approvalID, runID uuid.UUID, l types.PushPathList, limit int) (bool, error)
	// GetPushPathList returns approvalID's list, or ErrNotFound.
	GetPushPathList(ctx context.Context, approvalID uuid.UUID) (types.PushPathList, error)
	// CountPushPathLists counts the lists stored for runID's approvals.
	CountPushPathLists(ctx context.Context, runID uuid.UUID) (int, error)
}

PushPathListStore keeps each push_content approval's types.PushPathList. Like PrincipalPrefStore it is not part of Store, so the api test doubles need not grow it; the api type-asserts it.

type Querier added in v0.8.6

type Querier interface {
	Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error)
	Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)
	QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
}

Querier is the statement surface a store write needs: satisfied by *pgxpool.Pool (one statement, its own implicit transaction) and by pgx.Tx (a statement inside a caller's transaction). A write written against Querier runs the same SQL either way, which is what lets a held governance change apply inside the decision transaction.

A write that relies on a transaction-scoped lock (an advisory xact lock) must be handed a pgx.Tx: on a pool the lock is released as soon as its own statement ends.

type QueueAdmit added in v0.9.0

type QueueAdmit func(ctx context.Context, pool types.RunnerPool, e RunQueueEntry) (QueueVerdict, error)

QueueAdmit judges one waiting run before the pass takes any lock (pool-queue.md section 7 step 4), so it may read the store and ask the substrate. It must not write to the queue. An error leaves that run in its place and the pass judges the next.

type QueueHeld added in v0.9.0

type QueueHeld struct {
	RunID uuid.UUID
	Wait  string
}

QueueHeld is a waiting run the pass left in its place and why.

type QueueStart added in v0.9.0

type QueueStart struct {
	SPIFFEID            string
	ConfinementClass    types.ConfinementClass
	ModelProviderID     string
	AutonomyLevel       types.AutonomyLevel
	EndsAt              *time.Time
	WaitBudgetSec       int
	RunLimits           types.RunLimits
	GovernanceProfileID *uuid.UUID
	LifetimeCapAt       *time.Time
	LifetimeCapSource   types.LimitSource
	Placement           types.Placement
	PlacementFilled     bool
	RunnerID            *uuid.UUID
	// RunnerFallbacks are the run's other runners, in order: when RunnerID has no room under its lock the
	// start takes the first of these that has, so a run is never ended for one full runner while another
	// can take it.
	RunnerFallbacks    []uuid.UUID
	EvidenceSource     types.RunEvidenceSource
	RunnerPoolExecutor string
	// RemoteFallbacks are the run's other remote-hosted runners, RunnerFallbacks' counterpart.
	RemoteFallbacks []string
	// Reserved is what the run holds on its runner as it starts: the start folds the run afresh, so
	// this is the size it starts with, never the one it joined the queue with.
	Reserved types.RunnerAmounts
}

QueueStart is the columns the start of a promoted run writes: everything a QUEUED run left empty (the identity, the end and its cap, the governance capture) and the placement the start settled.

type QueueVerdict added in v0.9.0

type QueueVerdict struct {
	// Admit promotes the run. Wait is the reason it keeps its place otherwise, "" for none to say.
	Admit bool
	Wait  string
	// Stop says no run can be admitted this pass (the host has no room), so the rest are not judged.
	Stop bool
	// Owner says a held run's reason is its owner's (none of their runners can take a run), so their
	// later runs are passed over this pass. A run held for a reason of its own holds only itself.
	Owner bool
	// Runners are the registered runners an admitted run may be placed on, each with room as judged;
	// Remotes the remote-hosted runners. Both empty for a run placed on none. The promotion binds the
	// run to the self-hosted runner with the most room left that still fits it under the runner rows'
	// locks, else to such a remote-hosted runner, and leaves the run waiting when none has room.
	Runners []uuid.UUID
	Remotes []string
	// Needs is the size the run would start with on each of those runners, by runner key: the promotion
	// holds that size to the runner under its lock and writes it as the run's reservation. A runner
	// with none is held to the size the run joined the queue with.
	Needs map[string]types.RunnerAmounts
}

QueueVerdict is the pass's answer for one waiting run.

type Recorder

type Recorder struct {
	Pool *pgxpool.Pool
}

Recorder wraps a pool and implements audit.Recorder via InsertAuditEvent.

func (Recorder) Record

func (rec Recorder) Record(ctx context.Context, ev types.AuditEvent) error

Record appends ev to the append-only audit_events table. The chain hash InsertAuditEvent fills in is dropped here since ev is taken by value; a caller that wants the head hash calls InsertAuditEvent directly with &ev.

type RecordingOutputClaim added in v0.8.9

type RecordingOutputClaim struct {
	RunID      uuid.UUID
	Generation int64
	Token      uuid.UUID
}

RecordingOutputClaim identifies one read of a committed recording. Generation invalidates reads predating a new upload; Token also fences stale-lease owners.

type RemoteRunnerRow added in v0.9.0

type RemoteRunnerRow struct {
	Runner types.RemoteRunner
	Pools  []types.RemoteRunnerPoolRef
}

RemoteRunnerRow is a remote-hosted runner with every live pool it is in, user pools included.

type RemoteRunnerStore added in v0.9.0

type RemoteRunnerStore interface {
	// EnsureRemoteRunners inserts a row, with its default name and the limits proposals holds for it,
	// for each id that has none, and returns the rows it inserted (made). A row that already exists and
	// declares no limits gets its proposal (proposed); one that declares limits keeps them, since they
	// are an administrator's from the first boot on.
	EnsureRemoteRunners(ctx context.Context, ids []string, proposals map[string]types.RunnerLimits) (made, proposed []types.RemoteRunner, err error)
	ListRemoteRunners(ctx context.Context) ([]RemoteRunnerRow, error)
	GetRemoteRunner(ctx context.Context, id string) (RemoteRunnerRow, error)
	// UpdateRemoteRunner changes the display name, the limits or both at the revision the caller read,
	// and returns the runner before and after.
	UpdateRemoteRunner(ctx context.Context, id string, revision int64, name *string, limits *types.RunnerLimits, by string) (before, after types.RemoteRunner, err error)
	// SetRemoteRunnerPeople writes the people policy under the governance target lock a held change's
	// decision takes, and returns the runner before and after.
	SetRemoteRunnerPeople(ctx context.Context, id string, revision *int64, pools types.RemoteRunnerPeoplePools, subjects []types.RunnerPoolSubject, by string) (before, after types.RemoteRunner, err error)
}

RemoteRunnerStore is the optional store capability behind the remote-hosted runner routes.

type RestrictedComponentCreator added in v0.8.9

type RestrictedComponentCreator interface {
	// CreateRestrictedComponent inserts the org row c (Owner "") at version 1 under its caller-chosen
	// id and restricts (capability, c.ID) in the SAME transaction, so no reader can find the row
	// while it is open to every member, and a failed insert leaves no restriction behind. Errors as
	// CreateComponent; ErrConflict also rolls the restriction back.
	CreateRestrictedComponent(ctx context.Context, c types.Component, capability, by string) (types.Component, error)
}

RestrictedComponentCreator creates an organisation component together with the capability restriction that keeps it attachable by nobody until an admin names who may. Optional beside ComponentStore, for the same reason: a store without it must make the caller refuse, never create the row unrestricted.

type RestrictedComponentDeleter added in v0.8.9

type RestrictedComponentDeleter interface {
	// DeleteRestrictedComponent deletes the org row id, restricts (capability, id) if it is not
	// restricted already, and deletes every grant of capability whose value is id, in ONE
	// transaction: the id ends restricted with no grant naming it, or nothing changed. It returns
	// the deleted row, how many grants went, and whether the restriction insert wrote a row (false
	// when the id was still restricted, true when a lift had opened it and this put it back).
	// ErrNotFound when no org row has the id, and then nothing is written. A wildcard grant names no
	// id and is kept.
	DeleteRestrictedComponent(ctx context.Context, id uuid.UUID, capability, by string) (types.Component, int, bool, error)
}

RestrictedComponentDeleter deletes an organisation component without ever leaving its id open. Optional for the reason RestrictedComponentCreator is: a store without it must make the caller refuse, never delete the row and leave the rest undone.

type RunAuditMatcher added in v0.8.0

type RunAuditMatcher interface {
	HasRunAuditEvent(ctx context.Context, runID uuid.UUID, f AuditFilter) (bool, error)
}

RunAuditMatcher answers "does this run have ANY audit row matching f?" as an EXISTS, so the answer does not depend on how many other rows the run has or where the match sits in its trail — which a bounded, oldest-first QueryAuditEvents window cannot promise. A capability interface for the reason Pager gives.

type RunCapacity added in v0.8.6

type RunCapacity struct {
	States             map[string]int               `json:"states"`
	Paused             int                          `json:"paused"`
	Kept               int                          `json:"kept"`
	Totals             RunCapacityRunner            `json:"totals"`
	AgeBuckets         []RunCapacityAge             `json:"age_buckets"`
	ByRunner           map[string]RunCapacityRunner `json:"by_runner"`
	ByOwner            []RunCapacityOwner           `json:"by_owner"`
	ByOwnerTruncated   bool                         `json:"by_owner_truncated"`
	Unschedulable      []RunCapacityUnschedulable   `json:"unschedulable"`
	UnschedulableTotal int                          `json:"unschedulable_total"`
	// OldestActiveSeconds is the age since created_at of the oldest holding run (0 when none).
	OldestActiveSeconds int64 `json:"oldest_active_seconds"`
}

RunCapacity is the fleet's configured reservations, computed from the non-terminal rows.

type RunCapacityAge added in v0.8.6

type RunCapacityAge struct {
	Bucket string `json:"bucket"`
	Count  int    `json:"count"`
}

RunCapacityAge is one age bucket (since created_at) of holding runs.

type RunCapacityOpts added in v0.8.6

type RunCapacityOpts struct {
	Now                 time.Time
	CurrentKind         string
	UnschedulableReason func(detail string) string
	LocalExecutor       func(executor string) bool
}

RunCapacityOpts parameterises RunCapacity. CurrentKind is the deployment's runner kind; Totals carries only that kind's entry; other kinds appear only under ByRunner. UnschedulableReason maps a status_detail to its capacity-blocker reason, or "" when it is not one. LocalExecutor says whether a run's bound executor is one of this deployment's own substrates; nil treats every executor as one.

type RunCapacityOwner added in v0.8.6

type RunCapacityOwner struct {
	Owner   string                     `json:"owner"`
	Holding int                        `json:"holding"`
	Runners map[string]RunCapacitySums `json:"by_runner"`
}

RunCapacityOwner is one owner's holding runs, summed per runner kind (never across kinds). Ordering uses the deployment's current kind.

type RunCapacityRunner added in v0.8.6

type RunCapacityRunner struct {
	Basis string `json:"basis"`
	RunCapacitySums
}

RunCapacityRunner is one runner kind's sums. Basis says what they mean: Kubernetes sums requests, Docker has no reservation and reports caps.

type RunCapacitySums added in v0.8.6

type RunCapacitySums struct {
	Holding               int   `json:"holding"`
	Unknown               int   `json:"unknown"`
	AgentCPURequestMillis int64 `json:"agent_cpu_request_millis"`
	AgentCPULimitMillis   int64 `json:"agent_cpu_limit_millis"`
	AgentMemoryRequestMiB int64 `json:"agent_memory_request_mib"`
	AgentMemoryLimitMiB   int64 `json:"agent_memory_limit_mib"`
	ProxyCPUMillis        int64 `json:"proxy_cpu_millis"`
	ProxyMemoryMiB        int64 `json:"proxy_memory_mib"`
	// ProxyCPUUncapped counts recorded rows whose proxy had no CPU cap (Docker).
	ProxyCPUUncapped int   `json:"proxy_cpu_uncapped"`
	HeldCPUMillis    int64 `json:"held_cpu_millis"`
	HeldMemoryMiB    int64 `json:"held_memory_mib"`
}

RunCapacitySums is one set of configured-reservation sums. Holding counts the rows that hold capacity; Unknown counts the holding rows whose reservation was never recorded (runner_kind NULL), which add to no sum. A run's held figure is its agent request plus its proxy.

type RunCapacityUnschedulable added in v0.8.6

type RunCapacityUnschedulable struct {
	ID                    uuid.UUID `json:"id"`
	Owner                 string    `json:"owner"`
	WaitedSeconds         int64     `json:"waited_seconds"`
	Reason                string    `json:"reason"`
	RunnerKind            *string   `json:"runner_kind"`
	AgentCPURequestMillis *int64    `json:"agent_cpu_request_millis"`
	AgentMemoryRequestMiB *int64    `json:"agent_memory_request_mib"`
	ProxyCPUMillis        *int64    `json:"proxy_cpu_millis"`
	ProxyMemoryMiB        *int64    `json:"proxy_memory_mib"`
}

RunCapacityUnschedulable is one STARTING run waiting for room, with its recorded reservation (nil where unrecorded).

type RunFilter added in v0.8.0

type RunFilter struct {
	// Owner exact-matches created_by; empty = every creator. The api layer
	// resolves ?owner=me|all before calling down.
	Owner string
	// WithSponsored widens Owner to the runs of every service identity Owner sponsors (#1508): a
	// sponsor reads those runs. Ignored with no Owner.
	WithSponsored bool
	// Statuses is a subset of "active"/"ended"/"failed"/"killed", OR'd
	// together; empty = no filter. "needs" is rejected at the api layer.
	Statuses []string
	// EndedWithin bounds ended rows' age; <= 0 = unbounded. Live rows are
	// never windowed by this.
	EndedWithin time.Duration
	// IncludeKilled disables killedVisibleFor's default hide.
	IncludeKilled bool
	// NoEnd keeps only live runs with no end (ends_at NULL): the ones nothing will
	// stop but a person, an idle stop or a kill (#1319).
	NoEnd bool
	// Workspace exact-matches COALESCE(NULLIF(repo,”), workspace_path).
	Workspace string
	// Query ILIKE-searches title/task/repo/created_by. Raw — the store
	// escapes '%', '_' and '\' itself.
	Query string
}

RunFilter narrows ListRunsFiltered/CountHiddenRuns to the landing page's opt-in contract. Zero value matches every row (killed rows still subject to killedVisibleFor).

type RunHarnessStore added in v0.9.0

type RunHarnessStore interface {
	// PutRunHarnesses writes run runID's rows, once, in one transaction; each row's position in
	// hs is its ordinal. ErrNotFound when the run does not exist; ErrConflict when the run
	// already has rows, a harness repeats, or more than one row starts. An empty hs is refused: a
	// run launched without harnesses writes nothing, so the caller does not call this.
	PutRunHarnesses(ctx context.Context, runID uuid.UUID, hs []types.RunHarness) error
	// ListRunHarnesses returns a run's rows in request order; empty for a run launched without
	// any.
	ListRunHarnesses(ctx context.Context, runID uuid.UUID) ([]types.RunHarness, error)
}

RunHarnessStore persists the harnesses a run was launched with and the model provider each was granted (migration run_harnesses). The rows are the run's authorisation tombstone; they hold configuration ids only, so erasure never clears them.

type RunLayoutStore added in v0.5.0

type RunLayoutStore interface {
	// GetRunLayout returns ErrNotFound when nothing has been saved yet; the
	// api layer treats that as the default shape, not an error.
	GetRunLayout(ctx context.Context, principal, preset string) (types.RunLayout, error)
	// PutRunLayout upserts principal's layout for preset and returns the
	// stored row, including the server-assigned updated_at.
	PutRunLayout(ctx context.Context, principal, preset string, layout []types.RunLayoutWidget) (types.RunLayout, error)
}

RunLayoutStore is the run-cockpit widget-layout persistence surface. Deliberately NOT part of the Store interface: ~30 test doubles embed store.Store, and widening it would break every one for a feature they have nothing to do with. The api layer type-asserts s.cfg.Store to RunLayoutStore and degrades when absent (GET returns the default shape, PUT 501s); production is always PG, which has it.

type RunLeaser added in v0.8.0

type RunLeaser interface {
	// ListLeasedRuns returns every RUNNING run that has an end, a lifetime cap or is kept.
	ListLeasedRuns(ctx context.Context) ([]types.AgentRun, error)
	// MarkRunEnded marks run id kept-and-ended at now, only while still
	// RUNNING, not already kept, and its end is at or before now; clears a
	// pause. The conditional UPDATE is the cross-replica mutual exclusion:
	// exactly one caller sees true and runs the end.
	MarkRunEnded(ctx context.Context, id uuid.UUID, now time.Time) (bool, error)
	// MarkRunEndingSoon records that the thresholdSec-before-endsAt warning
	// went out, reporting whether this call recorded it: false when that
	// warning (or a closer one) already went for this end, or the end changed.
	MarkRunEndingSoon(ctx context.Context, id uuid.UUID, endsAt time.Time, thresholdSec int) (bool, error)
	// SetRunEndAndWait moves run id's end/wait from (fromEnd, fromWait) to
	// (toEnd, toWait), only while the run still has those values, limits
	// fromLimits, and isn't terminal. A run lost to a reboot/outage may still
	// move its end — extending it is how it becomes revivable again. A run
	// kept by its OWN end (LostEnded) moves only with ended set, exactly as
	// EndedKept describes. false means the run changed since the read (end,
	// wait, or a re-clamped profile); nil ends mean "no end". Moving the end
	// clears end_tightened_at.
	SetRunEndAndWait(ctx context.Context, id uuid.UUID, fromLimits types.RunLimits, fromEnd *time.Time, fromWait int, toEnd *time.Time, toWait int, ended *EndedKept) (bool, error)
	// StopKeptRunIf makes a kept run (RUNNING with a lost/end mark) terminal,
	// only while the row still carries the EXACT kept mark read (lostAt,
	// lostReason, endsAt), checked atomically with the state guard in one
	// UPDATE. Closes the same TOCTOU class UpdateRunStateIfIdle closes for the
	// idle reaper: a stale lease-sweep row must never win the destructive
	// RUNNING->terminal transition and tear down a run it no longer describes.
	// false means the mark or state changed; the caller's re-assert next sweep
	// pass makes dropping that safe.
	StopKeptRunIf(ctx context.Context, id uuid.UUID, to types.RunState, lostAt *time.Time, lostReason types.LostReason, endsAt *time.Time) (bool, error)
	// SetRunContainmentError records that a kept run's stop (proxy or agent)
	// failed with msg, only while RUNNING and kept. Message refreshes on every
	// call; containment_error_at keeps the first failure's time.
	SetRunContainmentError(ctx context.Context, id uuid.UUID, msg string, now time.Time) error
	// ClearRunContainmentError clears it once the stop is confirmed; true only
	// for the call that cleared a recorded error, so resolution is audited
	// once across replicas.
	ClearRunContainmentError(ctx context.Context, id uuid.UUID) (bool, error)
	// MarkRunAgentStopped records that the agent of id, a run kept after an
	// outage (lostAt is its mark), was stopped, so it holds no sandbox whatever
	// an extension later does to its end (HoldsSandboxSQL). A no-op unless the
	// row is RUNNING and still carries that outage mark with no stop recorded;
	// a revive clears it.
	MarkRunAgentStopped(ctx context.Context, id uuid.UUID, lostAt time.Time) error
}

RunLeaser is the lease surface. Optional like RunWatcherLeaser and for the same reason: the ~30 test doubles that embed store.Store would route these calls to a nil interface. The api layer type-asserts and runs no lease sweep when a store lacks it; production is always PG.

type RunLimitsReclamper added in v0.8.0

type RunLimitsReclamper interface {
	// ListProfiledLiveRuns returns every non-terminal run that captured a
	// governance profile at create and is not kept by its own end (LostEnded).
	// A run lost to a reboot or a control-plane outage is still profiled-live:
	// tightening its limits must still land, the same way extending its end
	// still does (F1, long-holds design rev 4 §2.3).
	ListProfiledLiveRuns(ctx context.Context) ([]types.AgentRun, error)
	// ReclampRunLimits writes run id's tightened limits, end and wait, but only
	// while the run still has the limits, end and wait in from (the run as the
	// caller read it), is not terminal and is not kept by its own end
	// (LostEnded). A non-nil endTightenedAt is stamped as end_tightened_at.
	// false means the run changed since it was read; the next sweep reads it
	// again.
	ReclampRunLimits(ctx context.Context, from types.AgentRun, toLimits types.RunLimits, toEnd *time.Time, toWait int, endTightenedAt *time.Time) (bool, error)
}

RunLimitsReclamper is the re-clamp surface. Optional like RunLeaser and for the same reason; the api layer runs no re-clamp when a store lacks it.

type RunLoser added in v0.8.0

type RunLoser interface {
	// StampRunTokenRenewed records id's token renewal at the database's clock.
	// false means the run is already kept (lost/ended), so the renew must be
	// refused — a lost run's identity must never carry forward.
	StampRunTokenRenewed(ctx context.Context, id uuid.UUID) (bool, error)
	// ListLapsedTokenRuns returns every RUNNING, not-kept run whose token was
	// last renewed more than life ago, by the database's clock.
	ListLapsedTokenRuns(ctx context.Context, life time.Duration) ([]types.AgentRun, error)
	// MarkRunLost marks id kept and lost for reason at now, only while RUNNING
	// and not already kept; with tokenLife>0 it re-requires the token still
	// lapsed by that much, so a race with a renew resolves to the renew.
	// Exactly one caller sees true. It clears a pause only for reboot — the
	// one reason whose container is truly gone — since an outage's agent
	// keeps running (frozen or not) and clearing here would make resume skip
	// a thaw a still-frozen agent still needs.
	MarkRunLost(ctx context.Context, id uuid.UUID, reason types.LostReason, now time.Time, tokenLife time.Duration) (bool, error)
}

RunLoser is the lost-run surface, kept optional because test doubles that embed store.Store would otherwise route these calls to a nil interface; the api layer type-asserts, and production is always PG.

type RunOutput added in v0.8.6

type RunOutput struct {
	RunID      uuid.UUID
	Output     []byte
	Truncated  bool
	Incomplete bool
	CaptureGap bool
	Source     string // "stdout", "pane_snapshot" or "recording"
	MaskScope  string // "run", "globals_only" or ""
	CapturedAt *time.Time
	ClaimedAt  time.Time
	// RecordingErased is independent of found: its durable source fence remains
	// after the derived row is deleted, without hiding unrelated stdout/panes.
	RecordingErased bool
}

RunOutput is one row of run_outputs. CapturedAt is nil while the row is pending (a capture is owed); MaskScope is "" when the row carries none.

type RunOutputChunkStore added in v0.8.6

type RunOutputChunkStore interface {
	// AppendRunOutputChunk adds b, which the caller's masker has already passed, as runID's next
	// chunk, then deletes the oldest chunks until what is left is the fewest that still hold
	// keep bytes. It reports false, and writes nothing, once the run's final row exists: a
	// writer that was slower than the finisher never recreates what the final row replaced.
	AppendRunOutputChunk(ctx context.Context, runID uuid.UUID, replica string, b []byte, keep int) (bool, error)
	// ReadRunOutputChunks returns the last limit bytes of runID's chunks.
	ReadRunOutputChunks(ctx context.Context, runID uuid.UUID, limit int) (RunOutputChunks, error)
	// SaveChunkedRunOutput writes runID's row from its chunks, as an incomplete capture gap (what
	// the replica that wrote them had not flushed is lost), and deletes them, in one transaction.
	// It reports false, writing nothing, when the run has no chunks or already has a final row.
	SaveChunkedRunOutput(ctx context.Context, runID uuid.UUID, maskScope string, limit int) (bool, error)
}

RunOutputChunkStore is the live-chunk record. Optional like RunOutputStore: the api layer type-asserts, and a store without it keeps the tail in the writing process only. Every method checks the run's erasure tombstone in its own transaction and answers ErrRunOutputErased.

type RunOutputChunks added in v0.8.6

type RunOutputChunks struct {
	Bytes     []byte
	Truncated bool
	Found     bool
}

RunOutputChunks is a run's live tail as stored: the chunks' bytes in order, ending at the newest. Found is false when no chunk is held. Truncated says bytes older than Bytes were printed: chunks were trimmed, or the read cut the front.

type RunOutputStore added in v0.8.6

type RunOutputStore interface {
	// InsertPendingRunOutput records that a capture is owed for runID. A row
	// that already exists is left as it is.
	InsertPendingRunOutput(ctx context.Context, runID uuid.UUID) error
	// RefreshRunOutputClaim stamps a pending row's claimed_at: the final write
	// is being retried, so the sweeper must not resolve it yet.
	RefreshRunOutputClaim(ctx context.Context, runID uuid.UUID) error
	// SaveFinalRunOutput writes the run's final row (output, flags and
	// mask_scope as given), replacing a pending or stdout capture-gap row. The
	// database stamps captured_at.
	SaveFinalRunOutput(ctx context.Context, o RunOutput) error
	// SaveGapRunOutput writes a capture-gap row with no bytes, only over a
	// pending row or none: a final row already committed is never overwritten.
	// It reports whether it wrote.
	SaveGapRunOutput(ctx context.Context, runID uuid.UUID) (bool, error)
	// MarkRunOutputIncomplete sets incomplete on runID's final row, reporting
	// whether a final row was there to mark.
	MarkRunOutputIncomplete(ctx context.Context, runID uuid.UUID) (bool, error)
	// GetRunOutput returns runID's row, pending or final. found is false when
	// there is none.
	GetRunOutput(ctx context.Context, runID uuid.UUID) (o RunOutput, found bool, err error)
	// EraseRunOutputs writes a tombstone for each run and deletes its rows, in
	// one transaction: all of it or none of it.
	EraseRunOutputs(ctx context.Context, runIDs []uuid.UUID) error
	// DeleteRunOutputsOlderThan deletes final rows captured longer ago than age
	// (the database's clock), returning how many went.
	DeleteRunOutputsOlderThan(ctx context.Context, age time.Duration) (int, error)
	// ListStalePendingRunOutputs returns the runs whose pending row was claimed
	// longer ago than age and which are terminal, oldest first, at most limit.
	ListStalePendingRunOutputs(ctx context.Context, age time.Duration, limit int) ([]uuid.UUID, error)
	// QueueRecordingRunOutput records recovery work after a committed upload.
	// newRecording also invalidates a running claim; false is bounded read repair.
	QueueRecordingRunOutput(ctx context.Context, runID uuid.UUID, retention, retryAfter time.Duration, newRecording bool) error
	// ClaimRecordingRunOutput reserves one eligible terminal run; source reads
	// happen after this transaction releases its connection.
	ClaimRecordingRunOutput(ctx context.Context, runID uuid.UUID, retention, staleAfter time.Duration) (RecordingOutputClaim, bool, error)
	// SaveRecordingRunOutput accepts only the current claim/generation and never
	// replaces final direct stdout or a pane snapshot. It consumes the claim.
	SaveRecordingRunOutput(ctx context.Context, claim RecordingOutputClaim, o RunOutput, retention time.Duration) (bool, error)
	// ListPendingRecordingRunOutputs bounds one elected recovery pass.
	ListPendingRecordingRunOutputs(ctx context.Context, staleAfter time.Duration, limit int) ([]uuid.UUID, error)
	// EraseRecordingRunOutputs fences future recording-derived rows and deletes
	// only that source, independently of the currently configured recording store.
	EraseRecordingRunOutputs(ctx context.Context, runIDs []uuid.UUID) (int, error)
}

RunOutputStore is the run-output record. Optional like RunWatcherLeaser: the ~30 test doubles that embed Store would route these to a nil interface, so the api layer type-asserts and keeps output in memory only when a store lacks it. Production is always PG. Capture and read transactions check the general erasure tombstone; erasure and housekeeping remain permitted afterwards.

type RunPAT added in v0.8.2

type RunPAT struct {
	RunID           uuid.UUID
	AuthorizationID uuid.UUID
	Owner           string // whose token: the run's creator
	ProviderRowID   string // the provider row the run was dispatched on
	Org             string // the Azure DevOps organisation the token is scoped to
	Scope           string // the space-joined scope string it was created with
	ValidTo         time.Time
	CreatedAt       time.Time  // stamped by the store on insert; ignored on the way in
	RevokedAt       *time.Time // nil while Wardyn believes the token is live
	RevokeReason    string
	LastError       string // why the last revoke attempt failed; "" otherwise
}

RunPAT is one row of ado_run_pats.

type RunPATFilter added in v0.8.2

type RunPATFilter struct {
	RunID         uuid.UUID
	Owner         string
	ProviderRowID string
}

RunPATFilter narrows ListUnrevokedRunPATs; a zero field matches everything.

type RunPATReader added in v0.8.2

type RunPATReader interface {
	// ListRunPATs returns every token recorded for runID, revoked or not,
	// oldest first. Empty, never nil, when there is none.
	ListRunPATs(ctx context.Context, runID uuid.UUID) ([]RunPAT, error)
	// LastRunPAT returns the newest token recorded for owner on
	// providerRowID; found=false when there is none.
	LastRunPAT(ctx context.Context, owner, providerRowID string) (p RunPAT, found bool, err error)
}

RunPATReader reads the run-token record for display. Optional like RunPATStore: a store without it has no tokens to show.

type RunPATStore added in v0.8.2

type RunPATStore interface {
	// InsertRunPAT records a token BEFORE its first use. ErrConflict when that
	// (run, authorization id) is already recorded. CreatedAt is the store's.
	InsertRunPAT(ctx context.Context, p RunPAT) error
	// MarkRunPATRevoked closes a token's record: it was revoked, or its valid_to
	// has passed. reason is required (one of api's adoPATRevoke* values). It
	// reports whether it closed the row: false when the row is unknown or
	// already closed, so two revokers race harmlessly. lastError, if non-empty,
	// keeps why the last revoke attempt failed.
	MarkRunPATRevoked(ctx context.Context, runID, authorizationID uuid.UUID, reason, lastError string) (bool, error)
	// NoteRunPATRevokeFailed records why a revoke attempt failed and leaves the
	// row OPEN, so the sweep retries and the setup check can list a token Wardyn
	// could not revoke while its valid_to is ahead. A closed or unknown row is
	// left as it is.
	NoteRunPATRevokeFailed(ctx context.Context, runID, authorizationID uuid.UUID, lastError string) error
	// ListUnrevokedRunPATs returns the tokens still recorded live that match f,
	// oldest first.
	ListUnrevokedRunPATs(ctx context.Context, f RunPATFilter) ([]RunPAT, error)
}

RunPATStore is the run-token record. Optional like RunPauser: it is not part of Store because the test doubles that embed Store would route these to a nil interface; the api layer type-asserts. Production is always PG; MemRunPATs is the same contract without a database. Unlike RunPauser, a caller that finds no RunPATStore must REFUSE TO MINT: a token is never created unrecorded.

type RunPauser added in v0.8.0

type RunPauser interface {
	// ListPauseCandidates returns every RUNNING, not-kept run with a sandbox,
	// paused or not, and the store's own clock read with them.
	ListPauseCandidates(ctx context.Context) ([]PauseCandidate, time.Time, error)
	// StampRunActive moves run id's presence clock to now, and reports whether
	// the run is paused. A terminal or missing run is not stamped (false, nil).
	StampRunActive(ctx context.Context, id uuid.UUID) (paused bool, err error)
	// MarkRunPaused marks run id paused for reason, but only while it is still
	// RUNNING, not kept, not paused, its presence clock still reads activeAt
	// (the snapshot the caller judged idle; nil is "never stamped"), and — for
	// a waiting pause — it still has a request the waiting pause counts, or —
	// for an idle pause — it has no open request. The conditional UPDATE is
	// what makes a keystroke, or a request closing or opening, between the
	// caller's read and this write win: false means leave the run running.
	MarkRunPaused(ctx context.Context, id uuid.UUID, reason types.PauseReason, activeAt *time.Time) (bool, error)
	// ClearRunPaused clears run id's pause, and reports whether it was paused.
	// Clearing one restarts the run's presence clock in the same write: a
	// resume is the newest thing that happened to the run, so the sweep counts
	// its quiet period from there and not from the stamp the pause was judged on.
	ClearRunPaused(ctx context.Context, id uuid.UUID) (bool, error)
	// RunHasOpenRequest reports whether run id has a PENDING request inside
	// its wait.
	RunHasOpenRequest(ctx context.Context, id uuid.UUID) (bool, error)
}

RunPauser is the pause surface. Optional like RunLeaser and for the same reason; the api layer runs no pause sweep, and stamps no presence, when a store lacks it. Production is always PG.

type RunProxyConfigs added in v0.8.0

type RunProxyConfigs interface {
	// PutRunProxyConfig writes run id's sealed config, replacing any.
	PutRunProxyConfig(ctx context.Context, id uuid.UUID, sealed []byte) error
	// GetRunProxyConfig reads it; ErrNotFound when there is none.
	GetRunProxyConfig(ctx context.Context, id uuid.UUID) ([]byte, error)
	// DeleteRunProxyConfig removes it. Idempotent.
	DeleteRunProxyConfig(ctx context.Context, id uuid.UUID) error
	// PurgeTerminalRunProxyConfigs removes every row whose run is terminal,
	// the backstop for a delete that failed, and returns how many it removed.
	PurgeTerminalRunProxyConfigs(ctx context.Context) (int64, error)
}

RunProxyConfigs stores each run's sealed proxy config. The caller seals and opens it; the store never sees it in the clear. Optional for RunLeaser's reason: the api layer type-asserts, and production is always PG.

type RunProxyRelease added in v0.8.0

type RunProxyRelease struct {
	RunID      uuid.UUID        `json:"run_id"`
	CreatedBy  string           `json:"created_by"`
	State      types.RunState   `json:"state"`
	LostReason types.LostReason `json:"lost_reason,omitempty"`
	Release    string           `json:"proxy_release"`
}

RunProxyRelease is one row of the version-window listing.

type RunQueueEntry added in v0.9.0

type RunQueueEntry struct {
	RunID      uuid.UUID
	PoolID     uuid.UUID
	Owner      string
	QueuedAt   time.Time
	DeadlineAt time.Time
	PolicyRef  json.RawMessage
	Authority  json.RawMessage
	Launch     json.RawMessage
}

RunQueueEntry is one waiting run's queue row. PolicyRef, Authority and Launch are JSON documents the enqueue path owns; Authority and Launch are personal data and go with the row.

func ListRunQueueForUpdate added in v0.9.0

func ListRunQueueForUpdate(ctx context.Context, q Querier, poolID uuid.UUID, limit int) ([]RunQueueEntry, error)

ListRunQueueForUpdate is the promoter's scan: poolID's waiting runs oldest first (queued_at, then run_id), each row locked FOR UPDATE SKIP LOCKED so a second promoter passes over what the first holds. q must be a transaction that already holds the pool row's lock. limit <= 0 is every row.

type RunQueueFact added in v0.9.0

type RunQueueFact struct {
	PoolID     uuid.UUID
	QueuedAt   time.Time
	DeadlineAt time.Time
	Ahead      int
}

RunQueueFact is a waiting run's place in its pool's queue: Ahead is the number of waiting runs before it (queued_at, then run_id), a count and never whose.

type RunReviver added in v0.8.0

type RunReviver interface {
	// MarkRunRevived claims run id for a new proxy: while it is RUNNING and still as the revive read
	// it, live (from "") or lost to an outage or a reboot (from that reason), it clears the lost
	// mark (and any unresolved containment error — the old proxy it was about is replaced), stamps
	// the token as just renewed (the revive mints a fresh one, and the lapsed-token sweep must not
	// read the old stamp), and refreshes the watcher lease (a stopped agent starts only after the
	// new proxy, and the watcher sweep must not probe it before then).
	//
	// A run kept by its own end (from LostEnded) is claimed only with ended set: its exact mark,
	// its files grace still live, and its end after ended.Now. It also clears a pause ONLY when
	// from is a reboot: that revive restarts the agent container (docker start), so a stale pause
	// mark would 409 the run's files/resources reads over an agent that's actually running. A live
	// restart (from "") or an outage revive never touch the agent's own process, frozen or not, so
	// their pause mark, if any, must survive for the thaw an eventual resume still needs to
	// perform (a lease end already cleared the pause of a run kept by its own end).
	//
	// false means the run went terminal, ended, was lost or revived since, or its grace or end ran
	// out (an outage-kept run's end too, as revive admission requires) — it gets no proxy.
	//
	// startsAgent is a revive that starts the run's stopped agent (a reboot, an end, or an
	// outage whose agent was stopped). With limit > 0 its claim takes a slot under the
	// deployment cap like a create does: under CreateRunUnderCap's lock,
	// ErrRunCapReached when every OTHER run that holds a sandbox already reaches limit,
	// the run left as it was. The row's own count is not trusted for this: an outage-kept
	// run whose end was moved later reads as holding a sandbox though its agent is
	// stopped. A revive that starts no agent (a live restart, an outage revive whose agent
	// still runs) adds nothing and is never refused.
	MarkRunRevived(ctx context.Context, id uuid.UUID, from types.LostReason, ended *EndedKept, limit int, startsAgent bool) (bool, error)
	// SetRunProxyRelease records release as the one that started run id's
	// proxy, once a revive's new proxy runs.
	SetRunProxyRelease(ctx context.Context, id uuid.UUID, release string) error
	// ListRunProxyReleases returns every non-terminal run that has a sandbox,
	// with the release that started its proxy ("" before migration 0084).
	ListRunProxyReleases(ctx context.Context) ([]RunProxyRelease, error)
}

RunReviver is the revive surface. Optional for RunLeaser's reason: the test doubles that embed store.Store would route these calls to a nil interface. The api layer type-asserts; production is always PG.

type RunSetupsErased added in v0.9.0

type RunSetupsErased struct {
	Templates         int `json:"templates"`
	Grants            int `json:"grants"`
	AuthorshipCleared int `json:"authorship_cleared"`
	// LaunchIntents is set by the caller that also erases the person's launch intents (LaunchIntentStore).
	LaunchIntents int `json:"launch_intents"`
}

RunSetupsErased is what EraseRunSetupsOf removed and cleared.

type RunSizing added in v0.8.6

type RunSizing struct {
	RunnerKind            string
	AgentCPURequestMillis int64
	AgentCPULimitMillis   int64
	AgentMemoryRequestMiB int64
	AgentMemoryLimitMiB   int64
	ProxyCPUMillis        *int64
	ProxyMemoryMiB        int64
}

RunSizing is the configured reservation recorded at dispatch (migration 0117). A nil ProxyCPUMillis means the proxy had no CPU cap.

type RunTitler added in v0.8.0

type RunTitler interface {
	// SetRunTitle overwrites run id's title unconditionally — a rename is
	// allowed in any run state (design.md §3.4), unlike the lease's end/wait,
	// so there is no state guard and no compare-and-swap here.
	SetRunTitle(ctx context.Context, id uuid.UUID, title string) error
}

RunTitler is the run-rename surface (#1197 L2, design.md §3.4). Optional like RunLeaser and for the same reason: the ~30 hand-written test doubles that implement Store directly would otherwise need a mechanical update for a route most of them never exercise. The api layer type-asserts; production is always PG.

type RunWatcherLeaser added in v0.5.0

type RunWatcherLeaser interface {
	ClaimStaleRunWatchers(ctx context.Context, owner string, staleAfter time.Duration) ([]types.AgentRun, error)
	HeartbeatRunWatcher(ctx context.Context, id uuid.UUID, owner string) error
	// RunWatcherFresh reports whether id's lease heartbeat is younger than
	// staleAfter (a live process owns it). The undispatched-run reaper checks
	// this so it never false-fails a RUNNING run whose sandbox_ref write was
	// lost but whose watcher still holds the lease. Missing row = not fresh (reap it).
	RunWatcherFresh(ctx context.Context, id uuid.UUID, staleAfter time.Duration) (bool, error)
}

RunWatcherLeaser is deliberately not part of Store (like Pager): ~30 test doubles embed store.Store overriding only a few methods, so widening Store would route lease writes to the embedded nil interface inside the watcher goroutine, whose recover() would swallow the panic and kill it silently. The api layer type-asserts and skips leasing when a store lacks this; PG always has it.

type RunnerAtCapacityError added in v0.9.0

type RunnerAtCapacityError struct {
	Runner    string
	Max       int
	NeverFits bool
}

RunnerAtCapacityError names the runner and its Runs at once for the sentence; it is ErrRunnerAtCapacity. NeverFits says the run could not go there however empty the runner is: it is past the per-run limit, or the runner declares no limits.

func (*RunnerAtCapacityError) Error added in v0.9.0

func (e *RunnerAtCapacityError) Error() string

func (*RunnerAtCapacityError) Unwrap added in v0.9.0

func (e *RunnerAtCapacityError) Unwrap() error

type RunnerContainment added in v0.9.0

type RunnerContainment struct {
	RunnerID, RunID, Generation uuid.UUID
	Ref                         string
	LostAt                      time.Time
}

RunnerContainment identifies the exact kept incarnation requiring H3's durable stop_proxy queue. Only an exact action_result may settle that action.

type RunnerContainmentProof added in v0.9.0

type RunnerContainmentProof struct {
	RunnerContainment
	ActionID   uuid.UUID
	ObservedAt time.Time
}

RunnerContainmentProof contains org-persisted action bindings, not values selected by a runner's action_result or inferred from the latest run. H3 stores LostAt/Generation with the queued action and supplies them unchanged. Nil Generation explicitly means no execution had been issued; the committing transaction must still find no execution row, never infer a latest generation.

type RunnerExecution added in v0.9.0

type RunnerExecution struct {
	RunID      uuid.UUID
	RunnerID   uuid.UUID
	Generation uuid.UUID
	Operation  string
	ExecID     string
}

RunnerExecution is an org-issued execution intent. An empty ExecID requires explicit reconciliation of the ambiguous command result before exit ingestion.

type RunnerExitWork added in v0.9.0

type RunnerExitWork struct {
	RunnerID uuid.UUID
	RunID    uuid.UUID
	Exit     runnerwire.AgentExit
}

RunnerExitWork is durable staged or accepted work. Only a bound matching handle can be applied; ambiguous results wait for H3 reconciliation.

type RunnerInventoryResult added in v0.9.0

type RunnerInventoryResult struct {
	Complete            bool
	NeedsReconciliation bool
	// AssertedCapHeld reports a page that met a new asserted subject past
	// the runner's cap: nothing was queued and the runner stays not-ready.
	AssertedCapHeld bool
}

RunnerInventoryResult describes committed intake, not permission to dispatch. Reconciliation requirements are durable even if the caller loses its reply.

type RunnerInventoryStore added in v0.9.0

type RunnerInventoryStore interface {
	RunnerStore
	// ListRunnersPage lists the runners the filter names, newest first, never an unclaimed one that
	// has outlived its wait at now (the sweeper may not have deleted the row yet).
	ListRunnersPage(ctx context.Context, filter types.RunnerFilter, now time.Time, p Page) ([]types.Runner, error)
	// ListUnusedRunnerRegistrationTokensPage lists tokens redeemable at now, newest first, for one
	// owner or, with owner "", every owner.
	ListUnusedRunnerRegistrationTokensPage(ctx context.Context, now time.Time, owner string, p Page) ([]types.RunnerRegistrationToken, error)
	RevokeRunnerRegistrationToken(ctx context.Context, id uuid.UUID, now time.Time) (types.RunnerRegistrationToken, error)
	// RunnerSlotHolders counts the runs holding a sandbox on each of ids, the count max_runs bounds.
	RunnerSlotHolders(ctx context.Context, ids []uuid.UUID) (map[uuid.UUID]int, error)
	// RunnerUsage is each named runner's In use, by key (a self-hosted runner's id as text, or a
	// remote-hosted runner's id): the figures admission counts, read without a lock.
	RunnerUsage(ctx context.Context, runners []uuid.UUID, remotes []string, except uuid.UUID) (map[string]types.RunnerTally, error)
	// RunnerPoolsOf names, for each of ids, its owner's live Self-Hosted pools it is a member of, by
	// name; a runner in none is absent.
	RunnerPoolsOf(ctx context.Context, ids []uuid.UUID) (map[uuid.UUID][]types.RunnerPoolRef, error)
	// UpdateRunnerLimits is the owner's declaration of their own live runner's limits.
	UpdateRunnerLimits(ctx context.Context, id uuid.UUID, owner string, limits types.RunnerLimits) (before *types.RunnerLimits, after types.Runner, err error)
}

RunnerInventoryStore is the optional capability behind the runners management routes: a page of the inventory, the unused registration tokens and the count of runs on each runner. Revoking a runner is the revoke door's, not this one's.

type RunnerKilledRun added in v0.9.0

type RunnerKilledRun struct {
	ID   uuid.UUID
	From types.RunState
}

RunnerKilledRun is one run a runner revocation ended KILLED.

type RunnerPoolCatalogueRow added in v0.9.0

type RunnerPoolCatalogueRow struct {
	Pool         types.RunnerPool
	Policy       *types.RunnerPoolUsePolicy
	ExecutorIDs  []string
	OwnRunners   int
	OwnRunnerIDs []uuid.UUID
}

RunnerPoolCatalogueRow is one live pool with what the API needs to decide who may use it and whether it can take a run right now: its use policy, its remote-hosted members (ExecutorIDs, their ids), how many of the caller's own claimed runners it holds, and which of the caller's own runners (in any state) are its members.

type RunnerPoolDeletion added in v0.9.0

type RunnerPoolDeletion struct {
	Pool              types.RunnerPool
	ExecutorIDs       []string
	RunnerCount       int
	OrgDefaultsBefore *types.RunnerPoolDefaults
	OrgDefaultsAfter  *types.RunnerPoolDefaults
	// CancelledRuns are the runs that were waiting for the pool and were ended KILLED with it.
	CancelledRuns []uuid.UUID
}

RunnerPoolDeletion is what a delete removed, for its audit row: the pool as it was, the executors and the count of runners it held, and the organisation defaults before and after when the delete cleared a slot that named it.

type RunnerPoolFullError added in v0.9.0

type RunnerPoolFullError struct {
	Pool string
	Max  int
}

RunnerPoolFullError names the pool and its cap for the sentence; it is ErrRunnerPoolFull.

func (*RunnerPoolFullError) Error added in v0.9.0

func (e *RunnerPoolFullError) Error() string

func (*RunnerPoolFullError) Unwrap added in v0.9.0

func (e *RunnerPoolFullError) Unwrap() error

type RunnerPoolStore added in v0.9.0

type RunnerPoolStore interface {
	// ListRunnerPoolCatalogue is the live organization pools and owner's own user pools; with all,
	// every person's user pools too, for oversight.
	ListRunnerPoolCatalogue(ctx context.Context, owner string, all bool) ([]RunnerPoolCatalogueRow, error)
	GetRunnerPool(ctx context.Context, id uuid.UUID) (types.RunnerPool, error)
	// ListRunnerPoolMembers is every member of a pool: only the admission fold reads it, and only through
	// runnerpool.OwnRunnerCandidates.
	ListRunnerPoolMembers(ctx context.Context, id uuid.UUID) ([]types.RunnerPoolMember, error)
	CreateRunnerPool(ctx context.Context, p types.RunnerPool, by string) (types.RunnerPool, error)
	UpdateRunnerPool(ctx context.Context, id uuid.UUID, revision int64, name *string, state *types.RunnerPoolState, limits *types.RunnerPoolLimits) (types.RunnerPool, error)
	DeleteRunnerPool(ctx context.Context, id uuid.UUID) (RunnerPoolDeletion, error)
	DeleteRunnerPoolsByOwner(ctx context.Context, owner string) ([]RunnerPoolDeletion, error)

	AddRunnerPoolExecutor(ctx context.Context, id uuid.UUID, executor string) (m types.RunnerPoolMember, added bool, revision int64, err error)
	RemoveRunnerPoolExecutor(ctx context.Context, id uuid.UUID, executor string, refuseGoverned bool) (removed bool, revision int64, err error)
	AddOwnRunnerToPool(ctx context.Context, id, runnerID uuid.UUID, owner, orgURLSHA256 string) (m types.RunnerPoolMember, added bool, revision int64, err error)
	RemoveOwnRunnerFromPool(ctx context.Context, id, runnerID uuid.UUID, owner string) (removed bool, revision int64, err error)
	// AddRemoteRunnerToUserPool puts a remote-hosted runner in owner's own live user pool, only while
	// the runner's revision is still peopleRevision, the one whose people policy the caller checked.
	AddRemoteRunnerToUserPool(ctx context.Context, id uuid.UUID, remoteRunner, owner string, peopleRevision int64) (m types.RunnerPoolMember, added bool, revision int64, err error)
	RemoveRemoteRunnerFromUserPool(ctx context.Context, id uuid.UUID, remoteRunner, owner string) (removed bool, revision int64, err error)

	PutRunnerPoolUsePolicy(ctx context.Context, id uuid.UUID, revision *int64, subjects []types.RunnerPoolSubject, by string) (types.RunnerPoolUsePolicy, error)
	DeleteRunnerPoolUsePolicy(ctx context.Context, id uuid.UUID) error

	GetRunnerPoolOrgDefaults(ctx context.Context) (types.RunnerPoolDefaults, error)
	PutRunnerPoolOrgDefaults(ctx context.Context, d types.RunnerPoolDefaults, by string) error
	BootstrapRunnerPools(ctx context.Context, executors []string, name string) (*types.RunnerPool, *types.RunnerPoolDefaults, error)
}

RunnerPoolStore is the optional store capability behind the runner pool routes: a store without it answers them 501, as RunnerStore does.

type RunnerRegistrationStore added in v0.9.0

type RunnerRegistrationStore interface {
	RunnerStore
	MintRunnerRegistrationToken(context.Context, string, types.RunnerRegistrationToken) (types.RunnerRegistrationToken, error)
	ConsumeRunnerRegistrationToken(context.Context, string, string, time.Time) (types.RunnerRegistrationToken, bool, error)
}

RunnerRegistrationStore is the optional capability behind the registration doors. A store without it cannot mint or redeem registration tokens.

type RunnerRevocation added in v0.9.0

type RunnerRevocation struct {
	Runner types.Runner
	Killed []RunnerKilledRun
}

RunnerRevocation is what one committed revoke did: the revoked runner and the runs it ended KILLED (RN-Q29), each with the state it was killed from.

type RunnerStore added in v0.9.0

type RunnerStore interface {
	CreateRunner(ctx context.Context, r types.Runner) (types.Runner, error)
	GetRunner(ctx context.Context, id uuid.UUID) (types.Runner, error)
	ListRunners(ctx context.Context) ([]types.Runner, error)
	ListRunnersByOwner(ctx context.Context, owner string) ([]types.Runner, error)
	ClaimRunner(ctx context.Context, id uuid.UUID, owner, fingerprint string, now time.Time) (types.Runner, error)
	TouchRunner(ctx context.Context, id uuid.UUID, version string, now time.Time) error
	RecordRunnerPosture(ctx context.Context, id uuid.UUID, p types.RunnerPosture, now time.Time) (changed bool, err error)
	RevokeRunner(ctx context.Context, id uuid.UUID, now time.Time, by, reason string) (RunnerRevocation, error)
	ExpireUnclaimedRunners(ctx context.Context, createdBefore time.Time) (int, error)

	QueueRunnerAction(ctx context.Context, a types.RunnerPendingAction) (action types.RunnerPendingAction, queued bool, err error)
	ListPendingRunnerActions(ctx context.Context, runnerID uuid.UUID) (page []types.RunnerPendingAction, more bool, err error)
	RunHasPendingRunnerAction(ctx context.Context, runID uuid.UUID) (bool, error)
	RecordRunnerActionResult(ctx context.Context, runnerID, runID, actionID uuid.UUID, outcome, reason string, observedAt time.Time, prepare func(context.Context, types.AuditEvent) (types.AuditEvent, error)) (action types.RunnerPendingAction, first bool, err error)

	GetCredentialDeliveryPolicy(ctx context.Context) (types.CredentialDeliveryPolicyDoc, error)
	PutCredentialDeliveryPolicy(ctx context.Context, policy json.RawMessage, by string, now time.Time) (types.CredentialDeliveryPolicyDoc, error)
}

RunnerStore is the optional store capability behind client mode, optional like DeviceStore: a fake or a non-Postgres store implements it or the runner routes answer 501.

type RunsByCreatorOrSponsorPager added in v0.9.0

type RunsByCreatorOrSponsorPager interface {
	ListRunsPageByCreatorOrSponsor(ctx context.Context, principal string, p Page) ([]types.AgentRun, error)
}

RunsByCreatorOrSponsorPager is RunsByCreatorPager widened to the runs of the service identities the caller sponsors (#1508): a sponsor reads those runs. Optional; a store without it lists a sponsor's own runs only, never more.

type RunsByCreatorPager added in v0.5.0

type RunsByCreatorPager interface {
	ListRunsPageByCreator(ctx context.Context, createdBy string, p Page) ([]types.AgentRun, error)
}

RunsByCreatorPager is the ownership-scoped analogue of Pager.ListRunsPage: a member's GET /runs is scoped to created_by = the caller. TRUST BOUNDARY: unlike Pager, an absent implementation must fail closed (500), not fall back to the unscoped list — an unscoped list IS the vulnerability here.

type RunsFilteredPager added in v0.8.0

type RunsFilteredPager interface {
	ListRunsFiltered(ctx context.Context, f RunFilter, p Page) ([]types.AgentRun, error)
	// CountHiddenRuns answers the X-Wardyn-Hidden-* headers: rows baseWhere
	// matches but ended_within excludes (olderHidden), plus rows
	// killedVisibleFor additionally excludes (killedHidden) — one query.
	CountHiddenRuns(ctx context.Context, f RunFilter) (olderHidden, killedHidden int, err error)
}

RunsFilteredPager is the landing-page scoping surface for GET /runs. Kept out of Pager/Store: widening either would silently route a test double's embedded method to the wrong (unscoped) behaviour. An absent implementation fails CLOSED at the api-layer call site, same as RunsByCreatorPager — a silent unscoped fallback would leak every run to a caller the filter was meant to narrow.

type SSHKeysByPrincipalPager added in v0.8.0

type SSHKeysByPrincipalPager interface {
	ListSSHKeysByPrincipalPage(ctx context.Context, principal string, p Page) ([]types.SSHPublicKey, error)
}

SSHKeysByPrincipalPager is the scoped analogue of Pager for GET /me/ssh-keys, same safe-fallback posture as GrantsByRunPager (ListSSHKeysByPrincipal is already WHERE principal=$1).

type ScimGroup added in v0.8.6

type ScimGroup struct {
	ID          uuid.UUID
	ExternalID  string
	DisplayName string
	CreatedAt   time.Time
}

ScimGroup is a group the identity provider reported. ExternalID is the provider's id for it.

type ScimGroupStore added in v0.8.6

type ScimGroupStore interface {
	CreateScimGroup(ctx context.Context, externalID, displayName string, now time.Time) (ScimGroup, error)
	GetScimGroup(ctx context.Context, id uuid.UUID) (ScimGroup, error)
	SearchScimGroups(ctx context.Context, attr, value string, limit int) ([]ScimGroup, error)
	RenameScimGroup(ctx context.Context, id uuid.UUID, displayName string) error
	ScimGroupMembers(ctx context.Context, id uuid.UUID) ([]uuid.UUID, error)
	// AddScimGroupMembers records identities as members, skipping any id that names no identity.
	AddScimGroupMembers(ctx context.Context, id uuid.UUID, identities []uuid.UUID, now time.Time) error
	StartGroupRemoval(ctx context.Context, groupID, identityID uuid.UUID) error
	ResumeGroupRemoval(ctx context.Context, groupID, identityID uuid.UUID) (bool, error)
	GroupRemovalIdentities(ctx context.Context, groupID uuid.UUID) ([]uuid.UUID, error)
	DeleteScimGroup(ctx context.Context, id uuid.UUID) error
	PendingGroupRemovals(ctx context.Context, idleFor time.Duration, limit int) ([]PendingGroupRemoval, error)
	GroupRemovalFailures(ctx context.Context, limit int) ([]DeprovisionFailure, error)
}

ScimGroupStore is optional, like LeaverStore: callers type-assert it.

type ServiceIdentityChange added in v0.9.0

type ServiceIdentityChange struct {
	Description *string
	Sponsor     *string
	Suspended   *bool
	By          string
}

ServiceIdentityChange is a PATCH: the fields that are not nil change.

type ServiceIdentityChanged added in v0.9.0

type ServiceIdentityChanged struct {
	Before, After types.ServiceIdentity
	TokensRevoked int
}

ServiceIdentityChanged is what a committed change did.

func UpdateServiceIdentityQ added in v0.9.0

func UpdateServiceIdentityQ(ctx context.Context, tx Querier, name string, ch ServiceIdentityChange) (ServiceIdentityChanged, error)

UpdateServiceIdentityQ is UpdateServiceIdentity's statements on tx (a pgx.Tx), which a held governance change applies inside the decision transaction.

type ServiceIdentityStore added in v0.9.0

type ServiceIdentityStore interface {
	// CreateServiceIdentity inserts si, or refuses with ErrServiceNameTaken or ErrServiceSponsorInvalid.
	CreateServiceIdentity(ctx context.Context, si types.ServiceIdentity) (types.ServiceIdentity, error)
	// GetServiceIdentity is the row named name, or ErrNotFound.
	GetServiceIdentity(ctx context.Context, name string) (types.ServiceIdentity, error)
	// ListServiceIdentities returns identities by name, narrowed to sponsor when it is not empty.
	ListServiceIdentities(ctx context.Context, sponsor string, p Page) ([]types.ServiceIdentity, error)
	// UpdateServiceIdentity applies ch to a non-retired identity. A sponsor change sets sponsor_since and
	// revokes every live token of the identity in the same transaction. ErrNotFound, ErrServiceIdentityInactive
	// (retired) and ErrServiceSponsorInvalid are the refusals.
	UpdateServiceIdentity(ctx context.Context, name string, ch ServiceIdentityChange) (ServiceIdentityChanged, error)
	// RetireServiceIdentity retires a non-retired identity and revokes its live tokens in one transaction.
	RetireServiceIdentity(ctx context.Context, name, by string) (ServiceIdentityChanged, error)
	// CreateServiceToken inserts t, a token of service:<name>, only while the identity is active, its
	// sponsor is still sponsor and it holds fewer than maxLive live tokens, counted in the same
	// transaction under a row lock that serialises concurrent mints: ErrNotFound for no such identity,
	// ErrServiceIdentityInactive, or ErrServiceTokenCap.
	CreateServiceToken(ctx context.Context, name, sponsor string, maxLive int, t types.APIToken, raw string) (types.APIToken, error)
	// ServiceStillSponsored reports whether name is usable, still sponsored by sponsor, and not re-sponsored
	// since openedAt, an application-clock instant that is translated to the database clock sponsor_since
	// is written on.
	ServiceStillSponsored(ctx context.Context, name, sponsor string, openedAt time.Time) (bool, error)
}

ServiceIdentityStore holds service identities (migration 0157, #1508). Optional, like PersonStore: the API answers 501 on a store without it.

type Store

type Store interface {
	// AgentRun.
	CreateRun(ctx context.Context, r types.AgentRun) (types.AgentRun, error)
	GetRun(ctx context.Context, id uuid.UUID) (types.AgentRun, error)
	ListRuns(ctx context.Context) ([]types.AgentRun, error)
	// CountActiveRunsBy counts one creator's non-terminal runs — the governance quota's read
	// (GovernanceLimits.MaxConcurrentRuns). Called only when an assigned profile sets a cap.
	CountActiveRunsBy(ctx context.Context, createdBy string) (int, error)
	UpdateRunStateIf(ctx context.Context, id uuid.UUID, fromState, toState types.RunState) (bool, error)
	UpdateRunStateIfIdle(ctx context.Context, id uuid.UUID, fromState, toState types.RunState, notAfter time.Time) (bool, error)
	SetSandboxRef(ctx context.Context, id uuid.UUID, ref string) error
	SetRunImage(ctx context.Context, id uuid.UUID, image string) error
	SetRunDiskMiB(ctx context.Context, id uuid.UUID, mib int) error
	SetRunAgentExecID(ctx context.Context, id uuid.UUID, execID string) error
	TouchRun(ctx context.Context, id uuid.UUID) error

	// RunPolicy.
	CreatePolicy(ctx context.Context, p types.RunPolicy) (types.RunPolicy, error)
	GetPolicy(ctx context.Context, id uuid.UUID) (types.RunPolicy, error)
	ListPolicies(ctx context.Context) ([]types.RunPolicy, error)
	UpdatePolicy(ctx context.Context, id uuid.UUID, name string, spec types.RunPolicySpec) (types.RunPolicy, error)
	DeletePolicy(ctx context.Context, id uuid.UUID) error

	// Workspace.
	CreateWorkspace(ctx context.Context, ws types.Workspace) (types.Workspace, error)
	GetWorkspace(ctx context.Context, id uuid.UUID) (types.Workspace, error)
	ListWorkspaces(ctx context.Context) ([]types.Workspace, error)
	// UpdateWorkspace writes the full column set, CARRYING ws.EgressEditedAt rather than stamping
	// it, or ReconcileWorkspaceEgressDecisions could re-widen a list the operator just cleared.
	// stampEgressEdit lets the ONE caller that clears the list say so, stamped by the DATABASE like
	// every other writer of that column.
	UpdateWorkspace(ctx context.Context, id uuid.UUID, ws types.Workspace, stampEgressEdit bool) (types.Workspace, error)
	// SetWorkspaceApprovedEgress replaces the approved-egress list and stamps EgressEditedAt: the
	// documented undo for an `always` decision, stopping the boot heal from re-adding a removed host.
	SetWorkspaceApprovedEgress(ctx context.Context, id uuid.UUID, domains []string) (types.Workspace, error)
	// AddWorkspaceEgressDecision records one `always`-scoped decision: on allow, host moves into
	// approved_egress (capped, deduped) and out of denied_egress; on deny the mirror. ErrConflict
	// (not ErrNotFound) when the cap refuses the write. Does NOT stamp EgressEditedAt — a decision
	// isn't an operator override of itself.
	AddWorkspaceEgressDecision(ctx context.Context, id uuid.UUID, host string, allow bool, maxApprovedEgress int) (types.Workspace, error)
	// SetWorkspaceDeniedEgress mirrors SetWorkspaceApprovedEgress for denied-egress: pass the FULL
	// desired list, replacing rather than merging.
	SetWorkspaceDeniedEgress(ctx context.Context, id uuid.UUID, domains []string) (types.Workspace, error)
	SetWorkspaceLLMCred(ctx context.Context, id uuid.UUID, cred *types.WorkspaceLLMCred) (types.Workspace, error)
	// SetWorkspaceOwner replaces ONLY owned_by (offboarding: reassign a departed member's workspace
	// to the operator via owner ""). Scoped and separate from UpdateWorkspace so no ordinary edit
	// can move ownership or race a concurrent async scan.
	SetWorkspaceOwner(ctx context.Context, id uuid.UUID, owner string) (types.Workspace, error)
	SetWorkspaceRequirements(ctx context.Context, id uuid.UUID, reqs map[string]types.WorkspaceRequirement) (types.Workspace, error)
	SetWorkspaceRecordResult(ctx context.Context, id uuid.UUID, taskKey string, result json.RawMessage, onlyIfStatus string) (types.Workspace, bool, error)
	ClaimWorkspaceActiveRun(ctx context.Context, id, runID uuid.UUID, expected *uuid.UUID) (types.Workspace, bool, error)
	ClearWorkspaceActiveRun(ctx context.Context, id, runID uuid.UUID) (bool, error)
	// SetWorkspaceBuiltImage writes image_ref, built_profile_hash and the build record by one compare-and-swap
	// on observedRef; applied=false means another build moved image_ref first.
	SetWorkspaceBuiltImage(ctx context.Context, id uuid.UUID, observedRef, imageRef, builtHash string, rec *types.ImageBuild) (applied bool, err error)
	// SetWorkspaceImportState advances the scan pipeline. FENCED: applies only while the import-step
	// slot still holds expectedActive; applied=false means the caller must re-read, not retry blindly.
	SetWorkspaceImportState(ctx context.Context, id uuid.UUID, status types.WorkspaceStatus, activeRunID *uuid.UUID, expectedActive *uuid.UUID) (types.Workspace, bool, error)
	// MergeWorkspaceRequirements ADDS overlay rows atomically (jsonb ||), safe against concurrent
	// edits the full-replace SetWorkspaceRequirements would race. ErrConflict at the key cap.
	MergeWorkspaceRequirements(ctx context.Context, id uuid.UUID, add map[string]types.WorkspaceRequirement) (types.Workspace, error)
	DeleteWorkspace(ctx context.Context, id uuid.UUID) error

	// Source library (tier 1) — a repo/dir configured once, attached to many workspaces. Upsert
	// dedupes on (kind, locator, ref).
	UpsertSource(ctx context.Context, src types.Source) (types.Source, error)
	GetSource(ctx context.Context, id uuid.UUID) (types.Source, error)
	GetSourcesByIDs(ctx context.Context, ids []uuid.UUID) (map[uuid.UUID]types.Source, error)
	ListSources(ctx context.Context) ([]types.Source, error)
	UpdateSourceConfig(ctx context.Context, id uuid.UUID, name string, reqs map[string]types.WorkspaceRequirement) (types.Source, error)
	WorkspacesAttaching(ctx context.Context, id uuid.UUID) ([]string, error)
	DeleteSource(ctx context.Context, id uuid.UUID, detach bool) error
	ClaimSourceActiveRun(ctx context.Context, id, runID uuid.UUID) error
	ClearSourceActiveRun(ctx context.Context, id, runID uuid.UUID) error
	SetSourceScanResult(ctx context.Context, id uuid.UUID, profile []byte, status types.WorkspaceStatus, runID uuid.UUID, seed map[string]types.WorkspaceRequirement) (types.Source, error)
	SetSourceScanResultUnfenced(ctx context.Context, id uuid.UUID, profile []byte, status types.WorkspaceStatus, seed map[string]types.WorkspaceRequirement) (types.Source, error)

	// Base-image catalog (tier 2). Upsert dedupes on (kind, image, steps); "recommended" is
	// structurally excluded (CHECK) — it is a per-workspace derived build, never a catalog row.
	UpsertBaseImage(ctx context.Context, b types.BaseImageEntry) (types.BaseImageEntry, error)
	// UpdateBaseImageName renames a catalog row — the operator-editable counterpart to
	// UpdateSourceConfig above, deliberately NOT folded into UpsertBaseImage's identity-hit dedupe.
	UpdateBaseImageName(ctx context.Context, id uuid.UUID, name string) (types.BaseImageEntry, error)
	ListBaseImages(ctx context.Context) ([]types.BaseImageEntry, error)
	WorkspacesUsingBaseImage(ctx context.Context, id uuid.UUID) ([]string, error)
	DeleteBaseImage(ctx context.Context, id uuid.UUID, detach bool) error

	// CredentialGrant.
	CreateGrant(ctx context.Context, g types.CredentialGrant) (types.CredentialGrant, error)
	ListGrantsByRun(ctx context.Context, runID uuid.UUID) ([]types.CredentialGrant, error)

	// ApprovalRequest.
	CreateApproval(ctx context.Context, a types.ApprovalRequest) (types.ApprovalRequest, error)
	GetApproval(ctx context.Context, id uuid.UUID) (types.ApprovalRequest, error)
	ListApprovals(ctx context.Context, stateFilter types.ApprovalState) ([]types.ApprovalRequest, error)
	// DecideApproval transitions state from PENDING to decision.State; returns
	// ErrAlreadyDecided if the approval is not PENDING. See types.ApprovalDecision.
	DecideApproval(ctx context.Context, id uuid.UUID, decision types.ApprovalDecision) (types.ApprovalRequest, error)

	// AuditEvent.
	QueryAuditEvents(ctx context.Context, runID uuid.UUID, limit int) ([]types.AuditEvent, error)
	QueryRecentAuditEvents(ctx context.Context, limit int) ([]types.AuditEvent, error)
	LatestAuditEventByAction(ctx context.Context, action string) (types.AuditEvent, error)

	// SiteConfig.
	GetSiteConfig(ctx context.Context) (types.SiteConfig, error)
	PutSiteConfig(ctx context.Context, cfg types.SiteConfig) (types.SiteConfig, error)

	// Sandbox ref -> substrate routing (the orchestrator's RefStore seam; see
	// store_sandbox_ref.go). GetRef reports a missing row as found=false, nil.
	PutRef(ctx context.Context, ref, substrateName string) error
	GetRef(ctx context.Context, ref string) (substrateName string, found bool, err error)
	DeleteRef(ctx context.Context, ref string) error

	// Short-lived cross-process handoff row (see store_ephemeral.go): single-use WS attach tickets.
	// Consume-once, and the consumer is a single DELETE ... RETURNING, so two racing redemptions —
	// on one control plane or two — can only have one win.
	MintAttachTicket(ctx context.Context, token string, t AttachTicket, now, expiresAt time.Time) error
	ConsumeAttachTicket(ctx context.Context, token string, now time.Time) (AttachTicket, bool, error)

	// SSH gateway key registry (migration 0033, self-service via /api/v1/me/ssh-keys). AddSSHKey
	// returns ErrConflict on a taken fingerprint (the PK; one key maps to one owner). DeleteSSHKey is
	// scoped to principal (someone else's key is ErrNotFound, no existence leak). GetSSHKeyByFingerprint
	// is the gateway's unscoped auth-time lookup. RefreshSSHKeyRoles re-stamps role+role_checked_at
	// (migration 0046) from the OIDC OnLogin hook, bounding the admin-override stamp's staleness.
	AddSSHKey(ctx context.Context, k types.SSHPublicKey) (types.SSHPublicKey, error)
	ListSSHKeysByPrincipal(ctx context.Context, principal string) ([]types.SSHPublicKey, error)
	GetSSHKeyByFingerprint(ctx context.Context, fingerprint string) (types.SSHPublicKey, error)
	DeleteSSHKey(ctx context.Context, fingerprint, principal string) error
	// DeleteSSHKeys removes one principal's keys, or all keys when principal is empty.
	DeleteSSHKeys(ctx context.Context, principal string) (int, error)
	RefreshSSHKeyRoles(ctx context.Context, principal, role string, checkedAt time.Time) error
	// RefreshAPITokenIdentity re-stamps role, user type and the group snapshot (plus its completeness
	// bit) on every unrevoked api_tokens row a principal holds, from the same OnLogin hook as
	// RefreshSSHKeyRoles — neither credential had a way to learn about a demotion or group change.
	RefreshAPITokenIdentity(ctx context.Context, principal, role, userType string, groups []string, truncated bool) error

	// Per-user API tokens (migration 0045). SECURITY: GetAPITokenByRaw is the
	// third auth branch on every authenticated route.
	//
	// CreateAPIToken/GetAPITokenByRaw hash the plaintext token internally — the raw value never
	// reaches SQL. GetAPITokenByRaw returns ErrNotFound for unknown, mismatched, revoked AND expired
	// tokens alike, so it's not an existence oracle. RevokeAPIToken is principal-scoped when non-empty
	// (self-service; someone else's id is ErrNotFound) and revokes ANY token when empty (admin).
	// TouchAPIToken is best effort — its error must never fail a request.
	CreateAPIToken(ctx context.Context, t types.APIToken, raw string) (types.APIToken, error)
	GetAPITokenByRaw(ctx context.Context, raw string) (types.APIToken, error)
	TouchAPIToken(ctx context.Context, id uuid.UUID, now time.Time) error
	ListAPITokensByPrincipal(ctx context.Context, principal string) ([]types.APIToken, error)
	ListAPITokens(ctx context.Context) ([]types.APIToken, error)
	RevokeAPIToken(ctx context.Context, id uuid.UUID, principal string, now time.Time) (types.APIToken, error)

	// Capability grants and the per-kind enforcement switch (migration 0042).
	// SECURITY: the resolver runs on the request path; the fail mode is a
	// fail-open authz gate hiding in an untyped test double.
	UpsertCapabilityGrant(ctx context.Context, g types.CapabilityGrant) (types.CapabilityGrant, error)
	DeleteCapabilityGrant(ctx context.Context, id uuid.UUID) error
	ListCapabilityGrants(ctx context.Context) ([]types.CapabilityGrant, error)
	// ListGroupDenyGrants returns the group-subject DENY rows of one kind, the only rows the
	// unresolvable-group-deny refusal can match — separate from ListCapabilityGrants so that
	// request-path check doesn't cost scale with table size.
	ListGroupDenyGrants(ctx context.Context, capability string) ([]types.CapabilityGrant, error)
	// ListCapabilityGrantsFor returns the grants that could apply to one caller: `all` rows plus
	// `user` rows naming any of users (sub AND email) plus `group` rows naming any of groups plus
	// `user_type` rows naming userType. Not filtered by capability — see the implementation.
	ListCapabilityGrantsFor(ctx context.Context, users, groups []string, userType string) ([]types.CapabilityGrant, error)
	// GetCapabilityEnforcement returns the sparse per-kind switch map; an absent key means NOT
	// enforced, the zero-config back-compat default.
	GetCapabilityEnforcement(ctx context.Context) (map[string]bool, error)
	// PutCapabilityEnforcement replaces the WHOLE map (a capability the caller omits loses its row)
	// and returns the stored result.
	PutCapabilityEnforcement(ctx context.Context, enabled map[string]bool) (map[string]bool, error)
	// ListCapabilityRestrictions returns the restricted values ("Available to: Only...", migration
	// 0081) as kind -> set of values; an absent value is not restricted. Never nil.
	ListCapabilityRestrictions(ctx context.Context) (map[string]map[string]bool, error)
	// SetCapabilityRestriction turns one value's restriction on or off (idempotent either way).
	SetCapabilityRestriction(ctx context.Context, capability, value string, restricted bool, by string) error

	// Console-managed role mappings (migration 0051): the store half of internal/auth/oidc's
	// RoleMappingSource, read once per login and merged with the chart's WARDYN_OIDC_ROLE_MAP.
	// SECURITY: on the OIDC login path; the fail mode is silently falling back
	// to env-only, WIDENING who derives admin under WARDYN_OIDC_DEFAULT_ROLE=admin.
	//
	// UpsertRoleMapping keys on the natural UNIQUE (value): re-adding an already-mapped value flips
	// its role in place, returning the EXISTING row's id on a conflict (never the candidate's).
	UpsertRoleMapping(ctx context.Context, m types.RoleMapping) (types.RoleMapping, error)
	DeleteRoleMapping(ctx context.Context, id uuid.UUID) error
	// ListRoleMappings returns every row, oldest first — the console's People
	// screen and the OIDC login-time merge's whole data need.
	ListRoleMappings(ctx context.Context) ([]types.RoleMapping, error)

	// User types (migration 0071_user_types). CreateUserType and
	// UpdateUserType return ErrConflict on a taken id or name; DeleteUserType
	// returns ErrConflict while the type is built in or still named by a
	// subject row (UserTypeReferences counts those).
	ListUserTypes(ctx context.Context) ([]types.UserType, error)
	GetUserType(ctx context.Context, id string) (types.UserType, error)
	CreateUserType(ctx context.Context, t types.UserType) (types.UserType, error)
	UpdateUserType(ctx context.Context, t types.UserType) (types.UserType, error)
	// UpdateUserTypeMetadata is UpdateUserType without the priority column.
	UpdateUserTypeMetadata(ctx context.Context, t types.UserType) (types.UserType, error)
	UserTypeReferences(ctx context.Context, id string) (int, error)
	// UserTypeTokenStamps counts the unrevoked API tokens stamped with the type.
	UserTypeTokenStamps(ctx context.Context, id string) (int, error)
	DeleteUserType(ctx context.Context, id string) error

	// Launch presets (migration 0087, store_launch_presets.go). PutLaunchPreset
	// is an upsert by name that moves the version only when the row changes.
	ListLaunchPresets(ctx context.Context) ([]types.LaunchPreset, error)
	GetLaunchPreset(ctx context.Context, name string) (types.LaunchPreset, error)
	PutLaunchPreset(ctx context.Context, p types.LaunchPreset) (types.LaunchPreset, PresetWrite, error)
	DeleteLaunchPreset(ctx context.Context, name string) (types.LaunchPreset, error)

	// Governance profiles and their subject assignments (migration 0052): the assignable ceiling
	// that replaces Config.DefaultPolicy for a principal an admin has named.
	// SECURITY: on the run-creation request path; the fail mode is falling
	// back to the deployment-wide ceiling, past the floor an admin set.
	//
	// UpsertGovernanceProfile keys on the PRIMARY KEY (a fresh id inserts, an existing one updates
	// in place, rename included) and returns ErrConflict when UNIQUE(name) rejects the write.
	// DeleteGovernanceProfile returns ErrConflict when the profile is still ASSIGNED — the ON DELETE
	// RESTRICT, so deleting a profile can never silently widen its members.
	UpsertGovernanceProfile(ctx context.Context, p types.GovernanceProfile) (types.GovernanceProfile, error)
	// WriteGovernanceProfile is the graph-aware write: one transaction under the profile-graph
	// lock, where build decides the stored row from every profile as that transaction reads them
	// (migration 0125: a profile may be composed on another). DeleteGovernanceProfile also
	// returns *ErrProfileHasChildren when another profile composes on the row.
	WriteGovernanceProfile(ctx context.Context, id uuid.UUID, build GovernanceProfileBuild) (types.GovernanceProfile, error)
	DeleteGovernanceProfile(ctx context.Context, id uuid.UUID) error
	// ListGovernanceProfiles and GetGovernanceProfileChain return RAW rows: a composed row
	// carries no authority of its own. Only internal/api/governance_compose.go calls them for
	// authority (a source guard fails any other caller); GetGovernanceProfileChain is one
	// statement, leaf first, bounded at depth 3, and ErrNotFound when the leaf is gone.
	ListGovernanceProfiles(ctx context.Context) ([]types.GovernanceProfile, error)
	GetGovernanceProfileChain(ctx context.Context, id uuid.UUID) ([]types.GovernanceProfile, error)
	// UpsertGovernanceAssignment keys on the natural UNIQUE (subject_type,
	// subject): re-assigning a subject REPOINTS its one row, returning the
	// EXISTING row's id on a conflict. ErrNotFound when profile_id names no
	// profile (the FK rejects it).
	UpsertGovernanceAssignment(ctx context.Context, a types.GovernanceAssignment) (types.GovernanceAssignment, error)
	DeleteGovernanceAssignment(ctx context.Context, id uuid.UUID) error
	ListGovernanceAssignments(ctx context.Context) ([]types.GovernanceAssignment, error)
	// Governance changes (migration 0126): a governance write held for a second human. Part of
	// this interface, not an optional seam, because a store without them would let a covered write
	// through unreviewed. See governance_changes.go for each one's contract.
	ProposeGovernanceChange(ctx context.Context, ch types.GovernanceChange, ttl time.Duration) (types.GovernanceChange, []ExpiredGovernanceChange, error)
	ListGovernanceChanges(ctx context.Context, state string) ([]types.GovernanceChange, error)
	GetGovernanceChange(ctx context.Context, id uuid.UUID) (types.GovernanceChange, error)
	DecideGovernanceChange(ctx context.Context, id uuid.UUID, d GovernanceDecision, fn GovernanceDecideFunc) (types.GovernanceChange, error)
	// DryRunGovernance runs fn on a transaction that is always rolled back.
	DryRunGovernance(ctx context.Context, fn func(q Querier) error) error
	// ResolveGovernanceProfile returns THE ONE profile that applies to a caller — user > group >
	// user_type > all, sub over email within the user tier, then priority DESC and name ASC — as a
	// single indexed read whose ORDER BY is the whole precedence rule. ErrNotFound means "no
	// assignment matched", read as the deployment ceiling. userSubjects must arrive in the caller's
	// own precedence order since the query matches by POSITION.
	//
	// Also returns WHICH TIER matched, load-bearing not informational: on a
	// stale/truncated group snapshot the caller must tell a user-tier winner
	// (serve it) from an all-tier one (refuse), indistinguishable from the
	// profile alone. HasGroupTierAssignments below is that separate read.
	ResolveGovernanceProfile(ctx context.Context, userSubjects, groups []string, userType string) (*types.GovernanceProfile, types.CapabilitySubjectType, error)
	// HasGroupTierAssignments gates the stale/truncated group-snapshot refusal:
	// with no group-tier row there is nothing an unknown group could have
	// matched, so a nil or truncated snapshot must fall through rather than
	// refuse (see the implementation).
	HasGroupTierAssignments(ctx context.Context) (bool, error)

	// User drives and their subject grants (migration 0054): admin-registered per-user storage a
	// member may mount into a run, and the rows allocating one to a user, group, or everyone.
	// SECURITY: same reason as the governance resolver above.
	//
	// UpsertUserDrive keys on the PRIMARY KEY (fresh id inserts, existing updates in place, rename
	// included) and returns ErrConflict when UNIQUE(name) rejects the write. DeleteUserDrive returns
	// ErrConflict while the drive is still ALLOCATED (ON DELETE RESTRICT).
	//
	// refuseIfAllocated is a PRECONDITION: the write applies only while the
	// drive has no allocations (ErrDriveAllocated otherwise) — the API's
	// re-home guard re-asserts, inside the writing statement, the read
	// decision that permitted the write.
	UpsertUserDrive(ctx context.Context, d types.UserDrive, refuseIfAllocated bool) (types.UserDrive, error)
	GetUserDrive(ctx context.Context, id uuid.UUID) (types.UserDrive, error)
	DeleteUserDrive(ctx context.Context, id uuid.UUID) error
	// ListUserDrives returns every drive by name WITH its grant count — what makes the console's
	// delete affordance honest, since a drive with grants answers 409.
	ListUserDrives(ctx context.Context) ([]types.UserDriveListItem, error)
	// UpsertUserDriveGrant keys on the natural UNIQUE (subject_type, subject, drive_id): writing a
	// subject's grant on a drive again updates that row, returning the EXISTING row's id on a
	// conflict; another drive is another row. ErrNotFound when
	// drive_id names no drive (FK). ErrConflict when ANOTHER subject already holds this drive with
	// the same home_override — a directory name is one person's, and on a managed drive an override
	// is the last way left to point two people at one object Wardyn creates.
	//
	// homeOverrideStated is the REQUEST's tri-state, not the row's: false means the caller never
	// mentioned home_override, and an update that never mentioned it must not CLEAR one (see the
	// guard in PG's statement) — that same ErrConflict, distinguished from the uniqueness one purely
	// by this argument (a stated override disables the re-home guard; an unstated one is empty).
	UpsertUserDriveGrant(ctx context.Context, g types.UserDriveGrant, homeOverrideStated bool) (types.UserDriveGrant, error)
	// DeleteUserDriveGrant RETURNS the row it removed (ErrNotFound when none
	// matched): the delete's own audit row has to name the subject that was
	// de-allocated, and by then it is gone.
	DeleteUserDriveGrant(ctx context.Context, id uuid.UUID) (types.UserDriveGrant, error)
	ListUserDriveGrants(ctx context.Context) ([]types.UserDriveGrant, error)
	// ResolveUserDrive mirrors ResolveGovernanceProfile's precedence and
	// stale-snapshot reasoning. DISABLED grants stay IN the query: one that
	// wins its tier comes back Enabled false, rendered PAUSED rather than
	// falling through (a pause must never widen a member onto an unchosen
	// drive). Returns the winning GRANT beside the drive since every override
	// lives on the binding row. ErrNotFound means "no drive".
	ResolveUserDrive(ctx context.Context, userSubjects, groups []string, userType string) (
		*types.UserDrive, *types.UserDriveGrant, types.CapabilitySubjectType, error)
	// HasGroupTierDriveGrants gates the stale/truncated group-snapshot refusal:
	// with no group-tier row there is nothing an unknown group could have
	// matched, so a nil or truncated snapshot must fall through rather than
	// refuse (see the implementation).
	HasGroupTierDriveGrants(ctx context.Context) (bool, error)

	// Ping proves the store is actually reachable, not just constructed — the
	// /readyz readiness probe's one call. A live TCP connect with no working
	// query would otherwise read as healthy forever.
	Ping(ctx context.Context) error
}

Store is the abstract persistence seam the control plane talks to. The default Postgres implementation is PG (query bodies in store.go); a future pure-Go SQLite backend satisfies this same interface unchanged.

Out of scope on purpose: transactional surfaces (broker mint FOR UPDATE, identity revocation) need a real transaction and stay on the pool directly.

SECURITY: several method groups below are deliberately part of this interface, not a type-asserted optional seam, because they run on a request-path authz decision — a test double that doesn't implement them would be a compile error instead of a silent fail-open degrade. Each such group is marked SECURITY below with its specific failure mode.

type SuspendPlan added in v0.8.6

type SuspendPlan struct {
	IdentityID uuid.UUID
	// Principal is the principal IdentityID's row bound when the plan was made ("" for none). A row that
	// binds another under the suspension's lock is ErrIdentityRebound, and nothing is written.
	Principal string
	// Principals are the principals whose identity rows are deactivated with IdentityID's and whose
	// people rows get deactivated_at.
	Principals []string
	// CutoffSubs are every sub and email form a session cutoff is written for.
	CutoffSubs []string
	// PurgeAfter schedules the automatic purge that long after the deactivation. Zero schedules
	// none; an earlier schedule is kept.
	PurgeAfter time.Duration
	// PurgePrincipals are the principals whose identity rows the purge is scheduled for with IdentityID's:
	// the person's own. A row of Principals not among them is deactivated and never scheduled.
	PurgePrincipals []string
	// PendingJobs are added as pending ledger rows in the same transaction, so a crash after the
	// commit still leaves the rest of the suspension for the sweeper. Existing rows are kept.
	PendingJobs []JobKey
}

SuspendPlan is what step 1 of a suspension writes, all in one transaction.

type SuspendResult added in v0.8.6

type SuspendResult struct {
	WasActive bool
	Epoch     int64
}

SuspendResult is step 1's outcome. WasActive says the identity was active before it, so this was a new suspension and not the repair of an old one.

type TemplateGrantCheck added in v0.9.0

type TemplateGrantCheck struct {
	Group   string
	Persons []string
}

TemplateGrantCheck asks a group write to re-read the writer's grant inside its own transaction. Persons are every subject the writer is known by (an administrator may have granted either their sub or their email).

type TemplateLimitError added in v0.9.0

type TemplateLimitError struct{ Limit int }

TemplateLimitError: the audience already holds Limit templates.

func (*TemplateLimitError) Error added in v0.9.0

func (e *TemplateLimitError) Error() string

type TemplateRevisionConflictError added in v0.9.0

type TemplateRevisionConflictError struct{ Current int }

TemplateRevisionConflictError: the template is at Current, not the revision the update expected. Nothing was written.

func (*TemplateRevisionConflictError) Error added in v0.9.0

type TemplateStore added in v0.9.0

type TemplateStore interface {
	// ListTemplatesVisible returns the current revision of every organisation
	// template, of person's own, and of the groups' templates, by name.
	ListTemplatesVisible(ctx context.Context, person string, groups []string) ([]types.RunTemplate, error)
	// GetTemplate returns the current revision; ErrNotFound when absent.
	GetTemplate(ctx context.Context, id uuid.UUID) (types.RunTemplate, error)
	// GetTemplateRevision returns one saved revision, with that revision's own
	// saver and time as UpdatedBy and UpdatedAt; ErrNotFound when the template or
	// that revision is absent.
	GetTemplateRevision(ctx context.Context, id uuid.UUID, revision int) (types.RunTemplate, error)
	// CreateTemplate inserts t at revision 1 under the id the caller chose.
	// *TemplateLimitError when the audience is full, ErrTemplateNameTaken on a
	// name the audience already uses, ErrTemplateGrantGone when grant finds no row.
	CreateTemplate(ctx context.Context, t types.RunTemplate, grant *TemplateGrantCheck) (types.RunTemplate, error)
	// UpdateTemplate saves t's name, description and document as the next
	// revision when the row is at expected; *TemplateRevisionConflictError (and
	// nothing written) otherwise. ErrNotFound, ErrTemplateNameTaken and
	// ErrTemplateGrantGone as for create.
	UpdateTemplate(ctx context.Context, t types.RunTemplate, expected int, grant *TemplateGrantCheck) (types.RunTemplate, error)
	// DeleteTemplate removes the template and every revision, and returns the
	// row it removed (without a document).
	DeleteTemplate(ctx context.Context, id uuid.UUID, grant *TemplateGrantCheck) (types.RunTemplate, error)
	// The group-management grants. Put stores the grant as given, which the
	// caller has made canonical, and a non-empty Label as the group's label.
	ListTemplateGroupAdmins(ctx context.Context) ([]types.TemplateGroupAdmin, error)
	PutTemplateGroupAdmin(ctx context.Context, g types.TemplateGroupAdmin) (types.TemplateGroupAdmin, error)
	DeleteTemplateGroupAdmin(ctx context.Context, group, person string) (bool, error)
	// TemplateGroupAdminGroups lists the groups any of persons holds a grant on.
	TemplateGroupAdminGroups(ctx context.Context, persons []string) ([]string, error)
	// TemplateGroupLabels returns the labels of those of groups that have one.
	TemplateGroupLabels(ctx context.Context, groups []string) (map[string]string, error)
	// EraseRunSetupsOf is the run_setups erasure scope: person's own templates
	// (with their revisions) and grants go, and their name is blanked wherever
	// it is the author of a shared template, a revision or a grant.
	EraseRunSetupsOf(ctx context.Context, person string) (RunSetupsErased, error)
}

TemplateStore persists run templates (migration 0165_run_templates); bodies in store_run_templates.go. Optional like ComponentStore: callers type-assert, and a caller that finds it absent answers 501 templates_unavailable rather than pretending to store anything.

The store does not authorise: the API checks types.TemplateAuthorize first. What the store holds for it is what must sit in the write's own transaction: a group write's grant re-read (TemplateGrantCheck, FOR SHARE), the audience limit counted under a named lock, and the revision compare-and-set.

type TokenDispositionFacts added in v0.9.0

type TokenDispositionFacts struct {
	Replacements map[uuid.UUID][]TokenReplacement
	Moves        map[uuid.UUID]types.TokenMove
}

TokenDispositionFacts is everything a disposition is derived from beside the token row itself.

type TokenMoveStore added in v0.9.0

type TokenMoveStore interface {
	// MoveAPIToken links a live admin-minted token to a non-retired service identity, replacing an earlier
	// link. It never revokes. ErrNotFound is no such token; ErrTokenNotMovable, ErrMoveTargetUnknown and
	// ErrServiceIdentityInactive (retired) are the refusals.
	MoveAPIToken(ctx context.Context, id uuid.UUID, serviceIdentity, by string, now time.Time) (types.TokenMove, error)
	// TokenDispositionFacts reads the invitations that replace tokens and the move rows, at now.
	TokenDispositionFacts(ctx context.Context, now time.Time) (TokenDispositionFacts, error)
}

TokenMoveStore holds the links between admin-minted tokens and the service identities that replace them (api_token_moves, migration 0157, #1508). Optional, like InvitationStore.

type TokenReplacement added in v0.9.0

type TokenReplacement struct {
	TokenID   uuid.UUID
	Target    string
	ClaimedAt *time.Time // nil while pending
}

TokenReplacement is an invitation that names a token as the one its claim replaces, only while pending or once claimed: a revoked or expired one decides nothing about the token.

type UserDriveCatalogueStore added in v0.9.0

type UserDriveCatalogueStore interface {
	// ResolveUserDrives returns, for each drive any of the caller's grants
	// names, the grant that wins THAT drive, ranked across drives by the same
	// precedence ResolveUserDrive uses, so its first entry is ResolveUserDrive's
	// answer. Disabled grants stay in: a disabled winner comes back Enabled
	// false and never falls through to a wider grant on the same drive. Empty,
	// never ErrNotFound, when nothing matches.
	ResolveUserDrives(ctx context.Context, userSubjects, groups []string, userType string) ([]UserDriveWinner, error)
}

UserDriveCatalogueStore resolves every drive a caller reaches. Like PrincipalPrefStore it is not part of Store, so the api test doubles need not grow it; the api type-asserts and treats its absence as "no drives".

type UserDriveGrantTierWriter added in v0.9.0

type UserDriveGrantTierWriter interface {
	UpsertUserDriveGrantWithinTier(ctx context.Context, g types.UserDriveGrant, homeOverrideStated, additional bool) (types.UserDriveGrant, []uuid.UUID, error)
}

UserDriveGrantTierWriter is the allocation write for a caller whose authority stops at the drive's own posture. Optional like PersonStore; a caller without it must refuse the write.

type UserDriveGrantWidensError added in v0.9.0

type UserDriveGrantWidensError struct{ Field string }

UserDriveGrantWidensError is UpsertUserDriveGrantWithinTier's refusal: Field is what the write would widen (types.DriveGrantWidens). Nothing was written.

func (*UserDriveGrantWidensError) Error added in v0.9.0

func (e *UserDriveGrantWidensError) Error() string

type UserDriveGrantWriter added in v0.9.0

type UserDriveGrantWriter interface {
	AddUserDriveGrant(ctx context.Context, g types.UserDriveGrant, homeOverrideStated bool) (types.UserDriveGrant, error)
	ReplaceUserDriveGrant(ctx context.Context, g types.UserDriveGrant, homeOverrideStated bool) (types.UserDriveGrant, []uuid.UUID, error)
}

UserDriveGrantWriter is POST /drives/grants' two verbs: AddUserDriveGrant allocates a drive beside a subject's others (additional), and ReplaceUserDriveGrant is UpsertUserDriveGrant returning the drives the subject lost, for the audit row. Optional like UserDriveCatalogueStore.

type UserDriveWinner added in v0.9.0

type UserDriveWinner struct {
	Drive types.UserDrive
	Grant types.UserDriveGrant
}

UserDriveWinner is one drive a caller reaches and the grant that wins it. The tier is Grant.SubjectType.

type WorkspacesByOwnerPager added in v0.6.0

type WorkspacesByOwnerPager interface {
	ListWorkspacesPageForOwner(ctx context.Context, owner string, p Page) ([]types.Workspace, error)
}

WorkspacesByOwnerPager is the ownership-scoped analogue of Pager.ListWorkspacesPage: a member's GET /workspaces sees their own owned rows plus the operator-owned ones (owned_by = ”), never another member's. Unlike RunsByCreatorPager, an absent implementation is not fail-closed: the api-layer fallback applies the same owned_by filter in Go before windowing, so scoping still holds.

Source Files

Jump to

Keyboard shortcuts

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