types

package
v0.9.0 Latest Latest
Warning

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

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

Documentation

Overview

Governance profiles (migration 0052): the wire + store types for an ASSIGNABLE ceiling and the row that binds one to a subject.

Named `governance*` throughout since "profile" already means two other things in this tree (Recording-Mode synthesis, workspace scan profile), and grep ambiguity here is a containment-bug risk.

The rule for what a GROUP SUBJECT is, and what a USER SUBJECT is, in one place: internal/auth/oidc (the login-time match surface), internal/api (the write boundaries) and this package's own validators all call in here rather than restate it, so a change to identity normalisation is made once.

A group subject matches by exact equality against the printable-ASCII login-time snapshot, guarded BEFORE the Unicode fold; anything outside that set can never match anyone — a dead deny the console still renders active, or a phantom group tier.

Here rather than internal/auth/oidc because this package is the leaf every one of those surfaces already imports, while internal/auth/oidc carries the OIDC/JOSE stack that pkg/client and cmd/wardyn must not acquire to canonicalize a string. One owner, not one per dependency direction.

Package types defines Wardyn's core domain vocabulary — AgentRun, RunPolicy, CredentialGrant, ApprovalRequest, and the audit event shape — shared by the control plane, runners, sidecars, and the CRD layer.

User drives (migration 0054): the wire + store types for an admin-registered per-user storage allocation and the row that grants one to a subject.

Named `drive` throughout: a DRIVE is what an admin registers and allocates, MOUNT is what a run does with one. The member's run request carries only `{enabled, read_only}` and NEVER a path — the server derives the home name.

Two kinds fall out of the backend rather than being stored beside it: MANAGED (Wardyn allocates the object) and SHARE (the platform already has the tree and Wardyn binds one per-user subdirectory of it).

Everything here is a CLOSED enum validated at the write boundary, pinned by migration 0054's three CHECKs against internal/db's TestClosedEnumChecksMatchConstants.

Home-segment rules: what a per-person home name may look like per substrate (a Docker volume-name component; a Kubernetes DNS-1123 subdomain), and how each is worded to users.

Index

Constants

View Source
const (
	BrandNamePrefix = "prefix"
	BrandNameSuffix = "suffix"
)

Branding name formats: "<Company> Wardyn" and "Wardyn for <Company>".

View Source
const (
	// ComponentDeliveryHeader: the proxy presents the secret in a request
	// header; the sandbox never holds it.
	ComponentDeliveryHeader = "header"
	// ComponentDeliveryEnv and ComponentDeliveryFile put the secret inside
	// the sandbox for the whole run.
	ComponentDeliveryEnv  = "env"
	ComponentDeliveryFile = "file"
)

How one component secret reaches the run.

View Source
const (
	MaxComponentHosts            = 32
	MaxComponentSecrets          = 8
	MaxComponentConfigKeys       = 32
	MaxComponentConfigValueBytes = 4096
	MaxComponentNameRunes        = 64
	MaxComponentRefs             = 16
	// MaxComponentEnvNameBytes bounds an env var or config key name;
	// MaxComponentHeaderFormatBytes the header value template, which the
	// proxy writes onto every injected request.
	MaxComponentEnvNameBytes      = 128
	MaxComponentHeaderFormatBytes = 512
)

Bounds on one definition and on one run's attachments.

View Source
const (
	ComponentBuiltinGitHub      = "github"
	ComponentBuiltinAzureDevOps = "azure_devops"
)

The built-in Git provider components a ComponentRef may name.

View Source
const (
	ComponentAccessRead  = "read"
	ComponentAccessWrite = "write"
)

Built-in component access levels.

View Source
const (
	MaxComponentBuiltinRepos      = 64
	MaxComponentBuiltinFieldBytes = 256
)

MaxComponentBuiltinRepos bounds the repositories one built-in component lists.

View Source
const (
	CLIKindAzure  = "azure"
	CLIKindAWS    = "aws"
	CLIKindGitHub = "github"
)

CLI kinds a component row can carry. gcloud and kubectl are not built.

View Source
const (
	MaxComponentMCPURLBytes  = 2048
	MaxComponentMCPHarnesses = 8
)

Bounds on an MCP component and on what one reference turns it on for.

View Source
const (
	MaxConfigSchemaBytes      = 64 << 10
	MaxConfigSchemaFields     = 64
	MaxConfigSchemaGroups     = 16
	MaxConfigOptions          = 64
	MaxConfigListItems        = 32
	MaxConfigLabelRunes       = 80
	MaxConfigDescriptionRunes = 500
	MaxConfigOptionValueRunes = 128
)

Bounds on a schema and its values.

View Source
const (
	PATAccessRead  = "read"
	PATAccessWrite = "write"

	PATForgeGeneric         = "generic"
	PATForgeGitLab          = "gitlab"
	PATForgeBitbucketServer = "bitbucket_server"
	PATForgeGitea           = "gitea"
)

Values of a git_pat scope's access and forge axes.

View Source
const (
	GovernanceChangePending  = "pending"
	GovernanceChangeApplied  = "applied"
	GovernanceChangeRejected = "rejected"
	GovernanceChangeExpired  = "expired"
	GovernanceChangeStale    = "stale"
)

Governance change states (migration 0126's CHECK).

View Source
const (
	GovernanceTargetProfile    = "governance_profile"
	GovernanceTargetAssignment = "governance_assignment"

	GovernanceTargetCapabilityGrant        = "capability_grant"
	GovernanceTargetCapabilityEnforcement  = "capability_enforcement"
	GovernanceTargetCapabilityAvailability = "capability_availability"
	GovernanceTargetUserTypePriority       = "user_type_priority"
	GovernanceTargetRoleMapping            = "role_mapping"
	GovernanceTargetKeyDomainAssignment    = "key_domain_assignment"
	GovernanceTargetEgressBaseline         = "egress_baseline"
	GovernanceTargetRunnerPoolUsePolicy    = "runner_pool_use_policy"
	GovernanceTargetRemoteRunnerPeople     = "remote_runner_people_policy"
	GovernanceTargetCredentialDelivery     = "local_credential_delivery"
	GovernanceTargetServiceIdentity        = "service_identity"
)

The target kinds a governance change can hold (governance_changes.target_kind has no CHECK: the closed set is the table in internal/api, and a later lane adds a kind with no DDL).

View Source
const (
	// HoldWindowEgress is how long an egress_domain wait_for_review request
	// holds the sandbox from when it was raised (proxy's defaultHoldTimeout).
	// This under-reports a policy authored above 30s (first_use_hold_seconds,
	// up to maxHoldTimeout) — ported verbatim from the TS rule it replaces;
	// fixing the under-report is a separate proxy change (the proxy would
	// need to stamp its own resolved hold_seconds into the raise scope),
	// not this one.
	HoldWindowEgress = 30 * time.Second
	// HoldWindowADOCapability is how long an Azure DevOps capability
	// escalation (a tool_call with grant_id set) holds the sandbox (proxy's
	// maxCapabilityHoldTimeout).
	HoldWindowADOCapability = 240 * time.Second
	// HoldWindowPush is the proxy's absolute ceiling on a push_content hold
	// (maxHoldTimeout) — the conservative bound isHeld used when no client
	// surface reads a run's own resolved push_rules.hold_seconds.
	HoldWindowPush = 600 * time.Second
)

The three hold windows approval.Hold ports from ui/src/app/lib/types/ approvals.ts's now-retired isHeld — see that function's own history (#725/ F1, #509, #181) for why each window is what it is. Exported here, the one package both internal/approval and internal/egress/proxy already import, so the proxy's own defaultHoldTimeout/maxHoldTimeout/maxCapabilityHoldTimeout point at these instead of carrying a second copy of the same numbers (#1197).

View Source
const (
	ImageBuildLaneGenerated = "generated"
	ImageBuildLaneBYOI      = "byoi"
	ImageBuildLaneRepo      = "repo_devcontainer"

	ImageBuildReasonCacheMiss    = "cache_miss"
	ImageBuildReasonImageMissing = "image_missing"
	ImageBuildReasonRefresh      = "refresh"
)

Build lanes and the reasons a build ran, as the record names them.

View Source
const (
	// MintConflictPending — the approval is still open; approval_id is set and
	// the caller may poll.
	MintConflictPending = "pending"
	// MintConflictDenied — a human denied the approval; denied/reason are set.
	MintConflictDenied = "denied"
	// MintConflictScopeMismatch — the requested scope widens the grant.
	MintConflictScopeMismatch = "scope_mismatch"
	// MintConflictAlreadyMinted — the grant's single-use slot is already spent.
	MintConflictAlreadyMinted = "already_minted"
)

Mint 409-conflict "code" values: the machine-readable discriminator between the four distinct fail-closed conditions that all share HTTP 409 on POST /wardyn/v1/credentials/mint. THIS IS THE ONE HOME: internal/api's mint handler and cmd/wardyn-git-helper's client both read these, and internal/api's tests assert the LITERAL strings (a wire contract).

View Source
const (
	AzureRouteAnthropic = "anthropic"
	AzureRouteOpenAIV1  = "openai_v1"
)

The two inference routes an azure_foundry row serves. One row serves one route, and a route names the one Entra audience its sign-in is captured for.

View Source
const (
	PushPathListMaxPaths = 10_000
	PushPathListMaxBytes = 1 << 20
)

The bounds of a PushPathList: at most PushPathListMaxPaths paths and PushPathListMaxBytes bytes of path text, whichever cuts the list first.

View Source
const (
	PushActsAsGitHubApp = "github_app"
	PushActsAsGitPAT    = "git_pat"
	PushActsAsADOEntra  = "ado_entra"
)

The PushContentScope.ActsAsKind values: which lane's credential a held push authenticates with, so the console can word the card.

View Source
const (
	PublicGitHubHost      = "github.com"
	PublicAzureDevOpsHost = "dev.azure.com"
)

The public host of each provider, which an authored set or override that names no host is for.

View Source
const (
	PostureEvidenceMDMFile = "mdm_file"
	PostureEvidenceOSProbe = "os_probe"
)

RunnerPosture.Evidence values.

View Source
const (
	RunnerActionSuccess  = "success"
	RunnerActionDeferred = "deferred"
	// RunnerActionRefused closes an action the runner can never apply.
	RunnerActionRefused = "refused"
)

Runner action outcomes an action_result may carry. A deferred receipt is final for its action ID but does not settle the obligation it carried.

View Source
const (
	// RunnerMaxRunsCeiling bounds a self-hosted runner's Runs at once (migration 0172's check).
	RunnerMaxRunsCeiling = 64
	// RemoteRunnerMaxRunsCeiling bounds a remote-hosted runner's Runs at once (migration 0174's check).
	RemoteRunnerMaxRunsCeiling = 1024
	// RunnerCPUMillisMin is the least CPU a runner's total or per-run limit may name: one CPU.
	RunnerCPUMillisMin = 1000
	// RunnerDiskStepMiB: a runner's disk limits are whole GiB, as a run's main drive is sized.
	RunnerDiskStepMiB = 1024
	// RunnerMaxRunsDefault is the max_runs of a runner registered before 0172.
	RunnerMaxRunsDefault = 2
)
View Source
const (
	RunnerPoolCPUMillisMax = 1_000_000     // milli-CPU: a thousand CPUs
	RunnerPoolMemoryMiBMax = 16 << 20      // MiB: 16 TiB
	RunnerPoolDiskMiBMax   = 16 << 20      // MiB: 16 TiB, the request contract's disk bound
	RunnerPoolSecondsMax   = math.MaxInt32 // about 68 years
)

The bounds every pool amount and duration is validated against. They are the run request's own bounds (CPU and memory) and the run limits' (seconds), so a pool can never name a value the request contract refuses.

View Source
const (
	// The four AI kinds (anthropic_api_key, anthropic_subscription, bedrock,
	// openai_api_key) are GONE as of 0.8: model access is a model provider, and
	// migration 0100 converted and deleted every stored row of them.
	IntegrationKindGitHubApp = "github_app"
	IntegrationKindGitHost   = "git_host"
)

Integration kinds. An integration is a BASE COMPONENT extended by kind: a connection (secrets + egress), never an installer. The closed set below is every kind with bespoke behavior; any other kind is a GENERIC open slug carrying its own secrets/egress/config. UI grouping DERIVES from kind; there is no stored category.

View Source
const (
	// DeliveryProxyHeader: the proxy presents the secret in an HTTP header;
	// the sandbox never holds it. The default, never-resident lane.
	DeliveryProxyHeader = "proxy_header"
	// DeliveryResidentFile/DeliveryResidentEnv: materialized as a file, or
	// exported as an env var, inside the sandbox — disclosed exceptions.
	DeliveryResidentFile = "resident_file"
	DeliveryResidentEnv  = "resident_env"
)

Integration delivery modes: how one secret reaches the run.

View Source
const (
	OverrideOff      = "off"      // this workspace refuses the requirement outright
	OverrideOptional = "optional" // re-lane: off by default, a run may enable it
	OverrideRequired = "required" // re-lane: rides every run
)

AttachmentOverride values: a workspace's per-attachment stance on one requirement key its source declares.

View Source
const (
	ADOPATMaxHoursDefault = 8   // minted_pat: PATMaxHours 0 reads as this
	ADOPATMaxHoursLimit   = 168 // minted_pat: the longest a token may live, in hours
	ADOPATMaxDaysDefault  = 30  // own_pat: PATMaxDays 0 reads as this
	ADOPATMaxDaysLimit    = 90  // own_pat: the longest a pasted token may live, in days
)

Bounds and defaults for the personal-access-token lifetimes on ADOEntraConfig.

View Source
const ADOEntraAccessTokenSecret = "azure-devops-entra-access-token"

ADOEntraAccessTokenSecret is a SENTINEL secret name (NOT a stored secret) that resolves to an Azure DevOps access token minted from the RUN OWNER's own captured Entra sign-in (internal/api's resolveADOInjection). Nothing is stored under this name: the credential in the store is the person's refresh token, held in the reserved `wardyn-harness-ado-<row>-oauth` blob, and the access token exists only for the moment it is handed to the run's proxy sidecar.

SECURITY: THE TOKEN DOES NOT BOUND THE RUN, and no reader of this name may assume it does — measured against a real tenant, Entra issues an Azure DevOps token carrying EVERY scope the person consented to regardless of what subset is requested, so what holds a run to its granted capabilities is Wardyn's own capability check in front of the resource (the proxy), not the credential. The granted scope string the authority reports is recorded on the audit row because a token is opaque and that string is the only honest evidence of what the credential can do.

Deliberately NOT in sinkReservedSecret, for AWSSSOAccessTokenSecret's reason: being resolved at that sink, host-pinned, is the whole point.

View Source
const AWSSSOAccessTokenSecret = "aws-sso-access-token"

AWSSSOAccessTokenSecret is a SENTINEL secret name (NOT a stored secret) that works like the two OAuth sentinels above at the injection sink (host-pinned, forced header, masked), resolving to the run's OWN captured AWS IAM Identity Center access token — the credential an `aws sso login` captured into wardynd's secret store as a whole blob, not as a value under this name.

This is what makes portal.sso.<region> GetRoleCredentials (`authtype:none`) work: the proxy carries the session as the x-amz-sso_bearer_token HEADER and the sandbox never holds the token. The resolver, internal/api's resolveAWSSSOInjection, also re-derives the credential's scope from the roster and refuses any drift from the snapshot the grant was authored with.

Deliberately NOT in sinkReservedSecret: that guard refuses names an api_key grant must never resolve, but this name's entire purpose is to be resolved through that sink, host-pinned, exactly as bedrock-api-key is (internal/api/secrets.go explains why that one is excluded too). Nothing is stored under it — a secrets-API Put of this name is a value the sentinel arm never reads.

View Source
const CLISecretPrefix = "wardyn-cli-"

CLISecretPrefix starts every person's CLI sign-in capture: wardyn-cli-<component id>-{sso,entra}.

View Source
const ComponentConfigSchemaVersion = 1

ComponentConfigSchemaVersion is the one schema version this build reads.

View Source
const ComponentFileDelivery = true

ComponentFileDelivery says whether this build can deliver a secret as a file. While false, Validate refuses the mode, so no definition can promise a delivery nothing performs. True since the file lane exists (the file_secret grant and its dispatch).

View Source
const ComponentMCPTransportHTTP = "http"

ComponentMCPTransportHTTP is the one MCP transport a component may use: streamable HTTP.

View Source
const ImageBuildSchema = 1

ImageBuildSchema is the record version; a higher number reads as unrecorded.

View Source
const IntegrationCredentialToken = "token"

IntegrationCredentialToken is the secret ROLE a generic header-delivered credential uses; closed kinds keep their own role names ("api_key", "pat", "ssh_key").

View Source
const MachineNameMax = 253

MachineNameMax bounds a reported machine name: the longest DNS host name.

View Source
const ManagedOAuthSecret = "anthropic-managed-oauth"

ManagedOAuthSecret is a SENTINEL secret name (NOT a stored secret) that works exactly like SubscriptionOAuthSecret at the injection sink (host-pinned to api.anthropic.com, forced Authorization: Bearer, value masked), but resolves to the Wardyn-MANAGED subscription token: a long-lived `claude setup-token` OAuth token the operator captured via the container-login flow and Wardyn persisted (internal/api/harnesscred.go). This is what lets a distroless COMPOSE/containerized deployment, with no host ~/.claude to read, credential a subscription run proxy-side without ever making the token resident in the sandbox. Distinct from SubscriptionOAuthSecret only in SOURCE (managed store vs resident host ~/.claude), so the audit trail names which one credentialed a run.

View Source
const MaxCLIAzureSubscriptions = 16

MaxCLIAzureSubscriptions bounds the subscriptions one Azure CLI row pins.

View Source
const MaxMemberDrivesPerPerson = 20

MaxMemberDrivesPerPerson is the ceiling on MemberDrivePolicy.MaxPerPerson.

View Source
const ModelProviderSecretPrefix = "wardyn-provider-"

ModelProviderSecretPrefix starts every per-person credential name: wardyn-provider-<uid>-{key,oauth,sso,entra}.

View Source
const OrgPoolSelfHostedMsg = "Organization pools take remote-hosted runners only. A self-hosted runner can join its owner's user pools."

OrgPoolSelfHostedMsg refuses a self-hosted runner in an organization pool.

View Source
const PushActsAsOperator = "operator"

PushActsAsOperator is the ActsAsLabel of a git_pat whose secret is the operator's shared one rather than the run owner's own: no person owns it.

View Source
const PushContentMaxPaths = 10

PushContentMaxPaths caps the review-matched paths a PushContentScope names. PathsTotal carries the exact count; the complete list rides beside the scope as a PushPathList, stored per approval, never in the scope (the dedup key) or a refusal body.

View Source
const RecordingMaxParts = 2048

RecordingMaxParts bounds how many parts one run's session recording may be stored as (RL-12: wardyn-rec uploads a long run's cast every 24 h, or as soon as half the 64 MiB per-upload cap is waiting). It exists for the reason that cap does: a hostile sandbox must not grow its stored recording without bound, and the Recordings list reads every part of a run.

No existing limit bounds the honest part count: a governance profile may let a run have no end, and the deployment's ephemeral disk ceiling is optional. So this is a named ceiling, sized so an honest run does not reach it: 2048 parts is more than five and a half years of 24 h parts, or 64 GiB of terminal output in 32 MiB parts, and bounds one run's store at 2048 uploads of at most 64 MiB (128 GiB) and a replay or list render at 2048 reads. The control plane refuses a higher part (413), and the proxy never forwards one.

View Source
const RemoteRunnerNameMax = 64

RemoteRunnerNameMax is the longest display name of a remote-hosted runner, in characters.

View Source
const RunnerAssertedEvidenceTTL = 90 * 24 * time.Hour

RunnerAssertedEvidenceTTL is how long quarantined evidence of an asserted (org-erased) run is kept, on the runner and in the org's action rows, after its quarantine succeeded.

View Source
const RunnerAssertedSubjectCap = 100

RunnerAssertedSubjectCap bounds the asserted subjects one runner can have, settled or not. Past it the runner is held not-ready.

View Source
const RunnerPoolDefaultPrefKey = "runner_pool_default.v2"

RunnerPoolDefaultPrefKey is the principal_prefs key of a person's own default pool. A person's document sits under their authenticated principal only.

View Source
const RunnerPoolNameMax = 64

RunnerPoolNameMax is the longest pool name, in characters.

View Source
const RunnerPoolUseSubjectsMax = 64

RunnerPoolUseSubjectsMax bounds a policy's subject list.

View Source
const RunnerPostureSourceRunnerAsserted = "runner_asserted"

RunnerPostureSourceRunnerAsserted is the only posture source in 0.9: the runner said so.

View Source
const RunnerRevokeNotMine = "not_mine"

RunnerRevokeNotMine marks an owner's revoke of a runner they did not register.

View Source
const RunnerUnclaimedTTL = 24 * time.Hour

RunnerUnclaimedTTL is how long an unclaimed runner waits for its owner.

View Source
const ServicePrincipalPrefix = "service:"

ServicePrincipalPrefix begins the principal of a service identity.

View Source
const SubscriptionOAuthSecret = "anthropic-subscription-oauth"

SubscriptionOAuthSecret is a SENTINEL secret name (NOT a stored secret): an api_key grant carrying it resolves at inject time to the operator's LIVE Anthropic subscription OAuth token (resident ~/.claude), never a stored value — so subscription runs are credentialed proxy-side like api-key runs while the sandbox holds only the inert sentinel. Also the durable "this profile uses subscription LLM auth" marker on a recorded profile, since the ~/.claude mount that would otherwise signal it is never synthesized. Shared here so api + recordmode + UI agree on the name.

View Source
const UserTypeStandard = "standard"

UserTypeStandard is the id of the built-in user type the migration seeds: editable, never deletable, and the sign-in tie floor.

Variables

View Source
var (
	// ErrApprovalAlreadyDecided: a decision was attempted on an approval that already left PENDING.
	// Fail closed — never let a second decision overwrite the first.
	ErrApprovalAlreadyDecided = errors.New("approval already decided")
	// ErrDuplicatePendingApproval: a partial unique index rejected a second open PENDING approval for
	// the same dedup key — a dedup signal (re-read the winner), not a hard failure.
	ErrDuplicatePendingApproval = errors.New("duplicate pending approval")
)

These sentinels live here (shared by internal/store and internal/approval) so the FSM can errors.Is instead of matching message text, which silently breaks on rewording.

ApprovalKinds is the closed set, checked by test against the constants above — closedEnumChecks once missed approvals.kind entirely, which would have let a DB-rejected constant pass every gate green.

ApprovalNotifyStates is the closed set, compared with the approval_notifications.state CHECK by internal/db's closedEnumChecks.

ApprovalStates is the closed set, in lifecycle order, so closedEnumChecks compares the DB CHECK against the Go set rather than a hand-typed test list.

CLIGitHubRefused is the closed list of grantable capabilities a GitHub CLI row never carries: each changes a repository's or an organisation's settings, deletes packages, or administers Actions, and the CLI's token acts as the organisation's App rather than as the person.

ClosedADOTokenModes is the closed token-mode set — see ClosedGitLanes.

ClosedCredentialSources is the closed source set — the only values a write may name.

ClosedGitLanes is the closed lane set — see ClosedGitProviderKinds.

ClosedGitProviderKinds is the only kinds a write may name.

ClosedIntegrationKinds is the closed kind set — the kinds with bespoke behavior in code, and as of 0.5 the ONLY kinds a write may name (validateIntegrationWrite).

ClosedModelProviderKinds is the only kind set a write may name.

View Source
var ConfinementClassNames = map[ConfinementClass]string{
	CC1: "Fence",
	CC2: "Wall",
	CC3: "Vault",
}

ConfinementClassNames maps each wire code to its UI display label (mirrors cc-meta.ts CC_META).

DriveBackends is the closed set in the order the admin surface shows them.

DriveObjectSchemes is the closed set, in the order a row can only ever move through (a legacy row stays slug forever; a new row is always id).

DriveReclaims is the closed set in admin-surface order.

HomeTemplates is the closed set in admin-surface order.

View Source
var LegacyGitLanes = []GitLane{GitLaneApp, GitLanePAT, GitLaneSSH}

LegacyGitLanes are the lanes an empty GitProvider.Lanes list admits: not "every lane in ClosedGitLanes" but the three from when this rule was written, frozen forever so growing the closed set never silently opts a stored row into a new lane. It never applies to an Azure DevOps row: an empty list there is refused at the write boundary (the shared pat and ssh lanes it would expand to are retired), and migration 0103 rewrote every stored one.

NonTerminalRunStates is IsTerminal's complement, for the active-run quota query. POSITIVE, never `NOT IN (terminal)`: a forgotten member only undercounts here, but a negation would wedge capped accounts at their limit forever. Pinned against IsTerminal by test.

View Source
var SecretNameRE = regexp.MustCompile(`^[a-z0-9]([a-z0-9._-]{0,126}[a-z0-9])?$`)

SecretNameRE is the shape of a stored secret's name: a safe, predictable identifier, so a name that could never be stored is refused where it is typed rather than found missing at dispatch.

ponytail: internal/api still carries its own secretNameRE and validInjectionFormat with these same rules; point both at this file and delete the copies.

View Source
var SlotHoldingRunStates = []RunState{RunPending, RunStarting, RunRunning, RunWaiting}

SlotHoldingRunStates is NonTerminalRunStates minus RunQueued: the states whose runs hold a pool, deployment or host slot, and the only ones a sandbox reaper may age. A waiting run holds nothing.

View Source
var TemplateRoutes = []TemplateRoute{
	{Route: "GET /api/v1/templates", RouterClass: "member", Person: TemplateTierOwner, Org: TemplateTierMember, Group: TemplateTierGroupMember},
	{Route: "GET /api/v1/templates/{id}", RouterClass: "member", Person: TemplateTierOwner, Org: TemplateTierMember, Group: TemplateTierGroupMember},
	{Route: "GET /api/v1/templates/{id}/revisions/{revision}", RouterClass: "member", Person: TemplateTierOwner, Org: TemplateTierMember, Group: TemplateTierGroupMember},
	{Route: "POST /api/v1/templates", RouterClass: "member", Person: TemplateTierOwner, Org: TemplateTierOrgAdmin, Group: TemplateTierGroupAdmin},
	{Route: "PUT /api/v1/templates/{id}", RouterClass: "member", Person: TemplateTierOwner, Org: TemplateTierOrgAdmin, Group: TemplateTierGroupAdmin},
	{Route: "DELETE /api/v1/templates/{id}", RouterClass: "member", Person: TemplateTierOwner, Org: TemplateTierOrgAdmin, Group: TemplateTierGroupAdmin},
	{Route: "POST /api/v1/templates/{id}/copy", RouterClass: "member", Person: TemplateTierOwner, Org: TemplateTierOrgAdmin, Group: TemplateTierGroupAdmin},
	{Route: "POST /api/v1/templates/import", RouterClass: "member"},
	{Route: "GET /api/v1/admin/template-group-admins", RouterClass: "admin"},
	{Route: "PUT /api/v1/admin/template-group-admins", RouterClass: "admin"},
	{Route: "DELETE /api/v1/admin/template-group-admins", RouterClass: "admin"},
}

TemplateRoutes is the route and authority table for the template surface.

Functions

func ASCIIOnlySubject added in v0.7.0

func ASCIIOnlySubject(s string) bool

ASCIIOnlySubject reports whether s contains no rune above ASCII. It answers exactly one question — can strings.ToLower move a rune across the ASCII boundary — so a control character answers true and this is NOT PrintableASCII's rule.

SECURITY: case-insensitive matching (ToLower/EqualFold) does Unicode case folding, under which a KELVIN SIGN "k" (U+212A) MATCHES ASCII "k" — so guarding the LOWERED value would let a crafted non-ASCII claim fold onto a real operator-authored ASCII entry (RoleMap, LegacyAdminEmails, a group name) and inherit everything bound to it. The guard must run on the RAW value, before the fold, everywhere an ASCII allowlist is compared case-insensitively.

func BarrierLabel added in v0.9.0

func BarrierLabel(c ConfinementClass) string

BarrierLabel is the console's name for a confinement class.

func CLIGitHubCapabilities added in v0.9.0

func CLIGitHubCapabilities(caps []ghscope.Capability) []ghscope.Capability

CLIGitHubCapabilities is caps without anything a GitHub CLI token may not hold: a capability that is not grantable, or one CLIGitHubRefused names.

func CanonicalGroupSubject added in v0.7.0

func CanonicalGroupSubject(s string) (string, bool)

CanonicalGroupSubject canonicalizes an operator-authored group name into the exact string a session snapshot carries for it, or ok=false when no snapshot could ever carry it (empty or non-printable-ASCII).

SECURITY: guards the RAW value before folding — order is the property. strings.ToLower's Unicode case mapping (U+212A folds to 'k') would otherwise let a crafted look-alike fold onto the real ASCII group and inherit its grants.

func CanonicalPushCollection added in v0.9.0

func CanonicalPushCollection(name string) (string, error)

CanonicalPushCollection is an Azure DevOps Server collection's canonical spelling: Unicode NFKC, then lower case, as Azure DevOps Server compares collection names without regard to case. Unlike a hosted organisation it may hold a space and letters outside ASCII. It refuses what Azure DevOps Server refuses in one (its reserved characters, a leading "_" or dot, two dots in a row, a trailing dot, more than 64 characters), a leading or trailing space, any other white space, a control or zero-width character, and the two letters Go and JavaScript lower-case differently (U+0130 and U+03A3), so both file one collection under one key.

func CanonicalPushHost added in v0.9.0

func CanonicalPushHost(provider, host string) (string, error)

CanonicalPushHost is the provider host's canonical spelling: ASCII lower case, no port, no trailing dot. The SSH aliases of the two public hosts are folded into the https host (ssh.github.com, ssh.dev.azure.com), and an Azure DevOps remote's legacy hosts too (*.visualstudio.com, vs-ssh.visualstudio.com become dev.azure.com), because one forge answers to each. Any other ssh. host is its own host. A host outside ASCII is refused before anything is lower-cased or normalised, because Go and JavaScript fold such letters differently. An empty host is the provider's public host.

func CanonicalPushOrg added in v0.9.0

func CanonicalPushOrg(org string) (string, error)

CanonicalPushOrg is the organisation's canonical spelling: Unicode NFKC, then ASCII lower case. It refuses what cannot be one entry's name: anything empty, longer than 100 bytes, holding white space or a control or format character (a zero-width space included), a trailing dot, or any character outside ASCII after the normalisation. GitHub owners and Azure DevOps organisations are ASCII and case-insensitive, so nothing else can name one.

func CanonicalUserSubject added in v0.7.0

func CanonicalUserSubject(s string) string

CanonicalUserSubject canonicalizes an operator-authored USER subject (OIDC `sub` or email) into the stored form.

SECURITY: same guard-before-fold order as CanonicalGroupSubject, to stop a Unicode look-alike (e.g. U+212A -> 'k') folding onto a real human's subject.

Unlike the group rule it does NOT refuse non-ASCII, deliberately: a `sub` is IdP-issued identity, not an operator label, so refusing it would refuse a real person — kept unfolded and unrefused. internal/api's own user-subject boundaries and the caller's subject list (capabilitySubjects) call this same function, so what a caller can BE is exactly what an admin can WRITE. Both arms trim whitespace; a non-ASCII string stays unfolded and matches only that exact string.

func ClosedADOTokenModeList added in v0.7.10

func ClosedADOTokenModeList() []string

ClosedADOTokenModeList is ClosedADOTokenModes in a stable order.

func ClosedCredentialSourceList added in v0.7.2

func ClosedCredentialSourceList() []string

ClosedCredentialSourceList is ClosedCredentialSources in a stable order, for a rejected write's "want one of: …".

func ClosedGitLaneList added in v0.7.2

func ClosedGitLaneList() []string

ClosedGitLaneList is ClosedGitLanes in a stable order.

func ClosedGitProviderKindList added in v0.7.2

func ClosedGitProviderKindList() []string

ClosedGitProviderKindList is ClosedGitProviderKinds in a stable order.

func ClosedIntegrationKindList added in v0.5.0

func ClosedIntegrationKindList() []string

ClosedIntegrationKindList is ClosedIntegrationKinds sorted, for the "want one of: …" half of a rejected write's error.

func ClosedModelProviderKindList added in v0.8.0

func ClosedModelProviderKindList() []string

ClosedModelProviderKindList is ClosedModelProviderKinds in stable order, for a rejected write's error.

func ConfigKeyLooksSecret added in v0.9.0

func ConfigKeyLooksSecret(key string) bool

ConfigKeyLooksSecret reports whether a config key names a secret by one of its words (GITHUB_TOKEN, apiKey, db-password, CLIENT_SECRET, JWT_SECRET, a secret access key). Whole words only: MAX_TOKENS and BYPASS_CACHE are plain settings, and so is SECRET_NAME, which names a stored secret. A config value is plain text by contract, so a key like this is refused by the schema and by a template; a secret travels through a secret_ref field. Only the singular "secret" counts: the plural is in carried field names (add_secrets) that hold names, never values.

func DecodeComponentConfigSchema added in v0.9.0

func DecodeComponentConfigSchema(data []byte) (ComponentConfigSchema, []ConfigIssue)

DecodeComponentConfigSchema reads one schema from JSON: bounded size, one value, no unknown field, and the validation below. Anything else is refused, including an unknown schema version.

func DeniesEveryPlacement added in v0.9.0

func DeniesEveryPlacement(denied []Placement) bool

DeniesEveryPlacement reports whether denied rules out both placements, so the people it binds could run nothing.

func DenyPathSegments added in v0.8.0

func DenyPathSegments(pattern string) ([]string, error)

DenyPathSegments is the one reading of a push_rules.deny_paths entry, shared by write-time validation and the broker's matcher so the two cannot disagree. A leading "/" is dropped (patterns anchor at the root either way); a trailing "/" means everything beneath (the .gitignore/CODEOWNERS spelling), so "infra/" reads as "infra/**". An empty, "." or ".." segment is refused — git never stores such a path, so it would silently match nothing and read as enforcement that isn't there. Invalid UTF-8 and leading/trailing whitespace are refused too (a near-certain typo).

func DriveGrantWidens added in v0.9.0

func DriveGrantWidens(d UserDrive, prev *UserDriveGrant, g UserDriveGrant) string

DriveGrantWidens names the field a write of g would widen past the security-admin tier, judged against drive d and the subject's current allocation prev (nil: none), or "". That tier allocates a drive with its own posture or narrower. Only a super admin newly states a home_override, which names where on the storage the files live and is compared only with other typed names, never with a derived home, or widens writability: true, or dropping a read-only override on a writable drive. An unchanged re-send on the same drive is not new, unless it enables a row that was disabled: a disabled row's home_override or writable exception is not in force, so turning the row on with one puts it in force and is the super admin's to do.

func DriveHomeName added in v0.7.0

func DriveHomeName(d UserDrive, subject, override string) (string, error)

DriveHomeName derives the per-user home segment for one principal.

PRECEDENCE: (1) override (a user-tier grant's home_override) WINS; (2) hash — `d-` + the first 20 hex of sha256(drive id + "\n" + subject), the drive id keeping one member's two drives from colliding; (3) sub / email_local — the CLAIM, lowercased, email_local truncated at the first "@".

EVERY non-hash path is checked against driveHomeSegmentRe; an unusable claim is an ERROR, NEVER A GUESS. The remedy is a home_override.

`subject` is the identity the home is derived FROM and the caller selects it — never the GRANT's subject, since a group grant's subject would give an entire group one home.

func DriveHomeStricterRuleClause added in v0.7.0

func DriveHomeStricterRuleClause(b DriveBackend) string

DriveHomeStricterRuleClause is the extra sentence a backend's home rule needs beyond the frozen DOCKER-rule refusal, said to a MEMBER (or "" when that already covers it) — a server-composed SUFFIX appended after the frozen sentence, never a reworded canon.

func DriveObjectID added in v0.8.0

func DriveObjectID(id uuid.UUID) string

DriveObjectID is the drive's own id, dashless — the fixed-width fragment DriveObjectSchemeID puts where DriveSlug would otherwise go: always 32 lowercase hex characters, unlike DriveSlug whose length depends on caller input.

func DriveObjectName added in v0.7.0

func DriveObjectName(d UserDrive, home string) string

DriveObjectName is what the runner asks the substrate for, given a home already derived by DriveHomeName:

  • docker_volume -> wardyn-drive-<drive-slug>-<home>
  • k8s_pvc[_static] -> wardyn-drive-<drive-slug>-<home>
  • host_path -> <host_root>/<home>, cleaned

EVERY name Wardyn MINTS carries the DRIVE's slug: a home_override is a literal an admin typed on the GRANT, and the grant gets re-pointed between drives, so without the slug a re-point would leave two drives naming one volume. A SHARE keeps its own shape: <host_root> already scopes it.

THE DRIVE HALF IS d.ObjectScheme, beside DriveSlug rather than replacing it (migration 0061's unique index still keys on the slug). DriveObjectSchemeID puts DriveObjectID(d.ID) there instead — 32 hex characters, always, so <home> starts at a FIXED offset no crafted name can shift (issue #163).

func DriveObjectNamedByWardyn added in v0.7.0

func DriveObjectNamedByWardyn(b DriveBackend) bool

DriveObjectNamedByWardyn reports whether WARDYN MINTS this backend's storage object name rather than joining a path somebody else already created.

IT IS NOT THE MANAGED/SHARE SPLIT: DriveKind answers "does Wardyn CREATE the object?"; this answers "does Wardyn NAME it?". k8s_pvc_static is a SHARE by the first question and Wardyn-named by the second. Only host_path is on the other side. The SAME expression DriveObjectName branches on.

func DriveSlug added in v0.7.0

func DriveSlug(name string) string

DriveSlug folds a drive's human name into the DNS-1123 fragment a PVC name carries. ValidateUserDrive refuses a name that folds to nothing.

EXPORTED BECAUSE THE FOLD IS NOT INJECTIVE: "Corp NAS" and "corp nas" fold to one slug, so UNIQUE(name) alone would let two different-looking drive names mint the SAME object. Migration 0061 makes the slug column UNIQUE for every backend Wardyn names; this function decides what that column holds.

func DriveSubjectHash added in v0.7.0

func DriveSubjectHash(subject string) string

DriveSubjectHash fingerprints the principal a drive object was allocated to, in the one shape a substrate LABEL may carry it: the first 20 hex of sha256(subject), folded exactly as DriveHomeName folds it.

NON-PII IS THE WHOLE REQUIREMENT: a label is echoed verbatim by `docker volume inspect`/`kubectl describe` to anybody who can reach the daemon. The drive id is NOT in this digest, unlike DriveHomeName's — the label lives beside `wardyn.drive`, which already carries the id.

An EMPTY subject returns "" rather than the digest of "": the caller then writes no label, avoiding a fingerprint every identity-less caller would share.

func FoldWorkspaceContract added in v0.5.0

func FoldWorkspaceContract(
	attachments []WorkspaceAttachment,
	sources map[uuid.UUID]Source,
	overlay map[string]WorkspaceRequirement,
) map[string]WorkspaceRequirement

FoldWorkspaceContract computes a workspace's effective requirements (the map applyWorkspaceRequirements consumes unchanged) from its attachments, the attached sources' own contracts, and the workspace's overlay rows. Precedence, in order:

  1. Each attachment contributes its source's contract; a dangling SourceID contributes nothing.
  2. An override of "off" drops that source's contribution for that key.
  3. An override lane (optional|required) replaces the source's lane.
  4. SECURITY: a write:<path> key whose path isn't the source's own Locator is dropped, else a shared source could claim write on a path it doesn't own and widen a sibling mount in every attaching workspace.
  5. Collisions merge: Level = strongest, Provenance = weakest — fail-closed since the run-create trust boundary auto-mints only operator_set secrets (ponytail: overlay is the one-row override if too strict).
  6. An overlay row replaces the merged value outright; it can't remove a key ("off" is for that).

Pure: no store, no clock. With no attachments (or all-ephemeral), the result is exactly the overlay.

func IsWildcardHost added in v0.9.0

func IsWildcardHost(host string) bool

IsWildcardHost reports whether an override host is spelled with a wildcard.

func ManagedBackendRejectsTemplate added in v0.7.0

func ManagedBackendRejectsTemplate(backend DriveBackend, tmpl HomeTemplate) bool

ManagedBackendRejectsTemplate is THE managed-backend home-template rule: a managed object's name ENDS in the home segment and is printed by `docker volume ls`/`kubectl get pvc` with no inspect/describe needed, so only `hash` — unique and revealing nothing — may name one. A share backend keeps every template.

Exported because the rule had TWO enforcement points that drifted apart: the write boundary widened from `email_local` to every non-hash template, but the resolver did not, so a legacy `sub` row was refused at authoring and still mounted. One predicate, so a third site cannot diverge again.

func PATRepoCovers added in v0.8.6

func PATRepoCovers(entry, key string) bool

PATRepoCovers reports whether the repos entry key entry covers key, both from PATRepoKey. An entry covers itself, and one trailing "/*" covers exactly one further path segment: "group/*" covers "group/app" and not "group/sub/app". A wildcard entry is covered only by the same wildcard.

func PATRepoKey added in v0.8.6

func PATRepoKey(forge, path string) (key string, ok bool)

PATRepoKey is the one repository-identity function for a git_pat scope. It returns the key a repos entry (or an extracted request path) compares by, and ok=false for a malformed one: empty, an empty segment, ".." anywhere, a "." segment, "?", "#", a backslash, a control character, an empty or repeated trailing "/*", or an unknown forge. An empty forge means generic.

generic returns the path exactly: no case-folding, no ".git" stripping, because git-http-backend serves different repositories for Team/App.git and team/app.git, and for team/foo and team/foo.git. A forge may canonicalise only where the proxy then forwards the canonical form, so that the compared string and the forwarded string are one value. No forge does yet, so every forge keys by the exact path.

func PATRepoSet added in v0.8.6

func PATRepoSet(entries ...string) *[]string

PATRepoSet returns a Repos value holding exactly the given entries (an empty call is the explicit empty list, "none").

func PrintableASCII added in v0.9.0

func PrintableASCII(s string) bool

PrintableASCII reports whether every rune of s is a printable ASCII character (U+0020..U+007E). Exported for internal/auth/oidc's email-domain allowlist, which refuses a claim's domain under the same raw-before-fold rule.

func PushPathsDigest added in v0.8.0

func PushPathsDigest(paths []string) string

PushPathsDigest is PushContentScope.PathsDigest over paths.

func PushRuleEntryOfRemote added in v0.9.0

func PushRuleEntryOfRemote(provider, remote string) (host, org string, err error)

PushRuleEntryOfRemote is how the git route finds the entry a remote belongs to, from the remote's own address and never from a request body. It returns the CANONICAL host and organisation (the pair PushRuleKey files rules under). The host is the remote's host with the ssh. alias of a public host folded; the organisation is:

  • GitHub and GitHub Enterprise Server: the first path segment (https://host/org/repo.git, git@host:org/repo.git, ssh://git@host/org/repo).
  • Azure DevOps: https://dev.azure.com/org/project/_git/repo (a user@ prefix is ignored), https://org.visualstudio.com/project/_git/repo (host becomes dev.azure.com), and, on the SSH hosts only, git@ssh.dev.azure.com:v3/org/project/repo and git@vs-ssh.visualstudio.com:v3/org/project/repo.
  • Azure DevOps Server (any other host): the collection, in [basePath/]Collection[/Project]/_git/Repo. Without the provider row's base path only Collection/Project/_git/Repo is read; a server under a prefix such as /tfs needs it (PushRuleEntryOfRemoteAt), or the remote is an error, never every collection under one entry.

A path segment that is empty, "." or "..", or that holds an encoded "/", is refused: git and curl resolve them, so the push would go somewhere else than the entry derived here. A remote whose entry cannot be derived is an error, which the caller must treat as a refusal and never as "no rules".

func PushRuleEntryOfRemoteAt added in v0.9.0

func PushRuleEntryOfRemoteAt(provider, remote, basePath string) (host, org string, err error)

PushRuleEntryOfRemoteAt is PushRuleEntryOfRemote for a provider row whose Azure DevOps Server lives under basePath (for example "/tfs").

func PushRuleEntryOfServerRemote added in v0.9.0

func PushRuleEntryOfServerRemote(provider, remote, basePath string) (host, org string, err error)

PushRuleEntryOfServerRemote is PushRuleEntryOfRemoteAt for the callers that have the provider row's base path in hand, and the one both the run's dispatch plan and the git route use, so the two can never key one remote differently. Everything but an Azure DevOps Server remote is PushRuleEntryOfRemoteAt's.

An Azure DevOps Server remote is read from its "_git" segment, never by counting from the front: the project is the segment right before "_git", and the segments between the row's base path and the project are the collection. After the base path exactly one such segment may remain when the base path itself names no collection, and none when it does (the collection is then the base path's last segment). Any other shape is an error, never a guess, because a guess files the push under another collection's rules.

A base path of two segments names its collection (prefix and collection). Residual: a base path of one segment is a collection or a prefix such as "/tfs", and the row does not say which, so the shape of the remote decides: a collection segment beyond it makes the base a prefix, none makes it the collection. A collection-only row therefore cannot tell a remote with one segment too many from a prefix row's.

func PushRuleKey added in v0.9.0

func PushRuleKey(provider, host, org string) (string, error)

PushRuleKey is the identity of an SCM entry for push rules, "host/org", both canonical, or an error saying what cannot be one entry.

func RemoteRunnerDefaultName added in v0.9.0

func RemoteRunnerDefaultName(id string) string

RemoteRunnerDefaultName is the name a configured remote-hosted runner gets when its row is made.

func ShareBackendRejectsTemplate added in v0.7.0

func ShareBackendRejectsTemplate(backend DriveBackend, tmpl HomeTemplate) bool

ShareBackendRejectsTemplate is the MIRROR rule: a SHARE's directories are named by whoever owns the share, and Wardyn never mkdir's on one, so a `hash` home names a directory that will never exist. The empty template is excluded on both sides: ValidateUserDrive defaults it before this is asked.

func SplitRequirementKey added in v0.5.0

func SplitRequirementKey(key string) (typ, rest string, ok bool)

SplitRequirementKey splits a "<type>:<key>" requirement key on the first colon only (a write:<path> key may itself contain colons) and reports whether the type token is known.

func SuggestedRunnerMaxRuns added in v0.9.0

func SuggestedRunnerMaxRuns(cpus int) int

SuggestedRunnerMaxRuns is what registration offers when no Runs at once is given: about half the host's CPU cores, at least one, at most the ceiling.

func ValidComponentName added in v0.8.9

func ValidComponentName(name string) error

ValidComponentName checks a component's display name: 1 to MaxComponentNameRunes printable characters with no surrounding space, so two names that read the same are the same row.

func ValidInjectionFormat added in v0.8.9

func ValidInjectionFormat(format string) error

ValidInjectionFormat is the rule for the value template of a proxy-injected credential header. The secret is substituted for its one %s, so a template with none sends a bare prefix and drops the credential, one with two renders a formatting error onto the wire, and a line break is a header-splitting shape. Empty is valid: the caller chooses the default.

func ValidMachineName added in v0.9.0

func ValidMachineName(s string) bool

ValidMachineName reports whether s may be stored as a runner's machine name: at most MachineNameMax bytes of printable characters. "" (none reported) is valid.

func ValidRemoteRunnerID added in v0.9.0

func ValidRemoteRunnerID(id string) bool

ValidRemoteRunnerID reports whether id can name a remote-hosted runner.

func ValidRunnerMaxRuns added in v0.9.0

func ValidRunnerMaxRuns(n int) bool

ValidRunnerMaxRuns reports whether n is a Runs at once a self-hosted runner may hold.

func ValidToolEffect added in v0.7.0

func ValidToolEffect(e ToolEffect) bool

ValidToolEffect reports whether e is one of the three effects. A closed enum, checked at every ingest point: an unrecognised effect must be a 400, never a silently-ignored rule that reads as enforcement.

func ValidateComponentRefs added in v0.8.9

func ValidateComponentRefs(refs []ComponentRef, validHost func(string) error) error

ValidateComponentRefs checks a run request's components. Whether each one exists and may be attached is the caller's.

func ValidateDriveName added in v0.9.0

func ValidateDriveName(name string) error

ValidateDriveName is the one name rule for admin and member drives. It checks strings.TrimSpace(name), which is the form the caller stores.

func ValidateHarnessCredentials added in v0.9.0

func ValidateHarnessCredentials(creds []HarnessCredential) error

ValidateHarnessCredentials is the consumer's structural check: a harness and a digest each once, and a digest that is 64 hex digits.

func ValidateHarnessToolRules added in v0.9.0

func ValidateHarnessToolRules(sets []HarnessToolRules) error

ValidateHarnessToolRules is the structural check a consumer of the carrier makes: a harness once, a closed effect, no rule named "*" (the default has its own field) and no tool named twice.

func ValidatePushRuleSets added in v0.9.0

func ValidatePushRuleSets(sets []PushRuleSet) error

ValidatePushRuleSets is the structural check a consumer of the carrier makes before trusting it: a known provider, an organisation, one set per entry, and path patterns the broker can match. Numeric bounds are the authoring door's (validatePushRules in internal/api).

func ValidateRemoteRunnerName added in v0.9.0

func ValidateRemoteRunnerName(name string) error

ValidateRemoteRunnerName accepts a printable, trimmed, single-line name.

func ValidateRunnerPoolName added in v0.9.0

func ValidateRunnerPoolName(name string) error

ValidateRunnerPoolName accepts a printable, trimmed, single-line name.

func ValidateRunnerPoolUseSubjects added in v0.9.0

func ValidateRunnerPoolUseSubjects(subjects []RunnerPoolSubject) error

ValidateRunnerPoolUseSubjects checks a policy's subjects: one to RunnerPoolUseSubjectsMax, each a user, group or user_type with a printable name, none repeated. The "all" subject is refused: it is no narrowing.

func ValidateUserDrive added in v0.7.0

func ValidateUserDrive(d *UserDrive, runnerTarget string) error

ValidateUserDrive normalizes d in place and validates it: trim, length, no control characters. runnerTarget must match d.Backend.RunnerTarget(), refused here rather than left to fail at run time; host_root is not checked against the deployment's env ceiling (see UserDriveHostRootCheck), since this package cannot read the environment.

func ValidateUserDriveGrant added in v0.7.0

func ValidateUserDriveGrant(g *UserDriveGrant) error

ValidateUserDriveGrant normalizes g in place and validates it: trim, length, no control characters, and for a GROUP subject the shared CanonicalGroupSubject the login-time snapshot is built with, so the subject vocabulary matches capabilitySubjects exactly.

Types

type ADOEntraConfig added in v0.7.10

type ADOEntraConfig struct {
	// TenantID is the Entra tenant every sign-in for this row goes to (GUID); a well-known alias ("common", "organizations") is refused.
	TenantID string `json:"tenant_id"`
	ClientID string `json:"client_id"` // app registration the sign-in names, as a GUID
	// CapabilityCeiling is the widest capability set a run on this row may
	// ever hold; required and non-empty when the block is present.
	CapabilityCeiling []adoscope.Capability `json:"capability_ceiling,omitempty"`
	DefaultProfile    []adoscope.Capability `json:"default_profile,omitempty"` // what a run gets by default; empty reads as adoscope.ProfileDefault
	TokenMode         ADOTokenMode          `json:"token_mode,omitempty"`      // empty reads as ADOTokenModeBearer
	// PATMaxHours is the longest a minted_pat run's token lives (1-168). 0
	// reads as 8; read via PATHours, never directly.
	PATMaxHours int `json:"pat_max_hours,omitempty"`
	// PATMaxDays is the furthest expiry an own_pat token may carry (1-90). 0
	// reads as 30; read via PATDays, never directly. A Server row has no entra
	// block, so it cannot set this and always reads the default, 30.
	PATMaxDays int `json:"pat_max_days,omitempty"`
	// RESTAPI: brokered REST calls, or only git traffic. Default true; read
	// via RESTAPIEnabled, never directly.
	RESTAPI *bool `json:"rest_api,omitempty"`
	// RunnerResident opts this connection's minted tokens in to runner_resident
	// delivery: held in a self-hosted runner's OS keystore for the run and
	// injected by the runner's own proxy, where the class's delivery policy and
	// posture requirement allow it. Off by default.
	RunnerResident bool `json:"runner_resident,omitempty"`
	// RunnerResidentSandbox also hands that token into the sandbox, where the
	// agent can use it past the REST gate and capability holds. Requires
	// RunnerResident.
	RunnerResidentSandbox bool `json:"runner_resident_sandbox,omitempty"`
}

ADOEntraConfig is the Entra lane's configuration on one Azure DevOps row: tenant, app, widest ceiling, default profile, and token presentation. The ceiling is not a default: CapabilityCeiling is the most a run could ever be granted; DefaultProfile is what it gets by default.

func (*ADOEntraConfig) PATDays added in v0.8.2

func (c *ADOEntraConfig) PATDays() int

PATDays is PATMaxDays with the unset default applied.

func (*ADOEntraConfig) PATHours added in v0.8.2

func (c *ADOEntraConfig) PATHours() int

PATHours is PATMaxHours with the unset default applied.

func (*ADOEntraConfig) Profile added in v0.7.10

func (c *ADOEntraConfig) Profile() []adoscope.Capability

Profile is the capabilities a run gets when it asks for nothing: DefaultProfile, or adoscope.ProfileDefault when that is empty.

func (*ADOEntraConfig) RESTAPIEnabled added in v0.7.10

func (c *ADOEntraConfig) RESTAPIEnabled() bool

RESTAPIEnabled: the rest_api default (unset or nil receiver) is enabled.

type ADOTokenMode added in v0.7.10

type ADOTokenMode string

ADOTokenMode is how a run presents itself to Azure DevOps on an Entra-lane row: the Entra access token itself, a personal access token Wardyn creates for the run, or one the person pasted. A named field, so a refused mode says why rather than an opaque "unknown field" 400.

const (
	ADOTokenModeBearer ADOTokenMode = "bearer" // sends the Entra access token itself; the zero value
	// ADOTokenModeMintedPAT creates a short-lived personal access token per
	// run, in the person's name, through the sign-in app's own grant. Named
	// and closed, but the write boundary still refuses it: it is not yet
	// available.
	ADOTokenModeMintedPAT ADOTokenMode = "minted_pat"
	// ADOTokenModeOwnPAT is a personal access token the person pasted in
	// themselves. It needs no tenant or client: nothing signs in. SO EVERY
	// PICKER OF THE ONE SIGN-IN ROW MUST SKIP IT — the console-login capture
	// (adoEntraRow in cmd/wardynd) and the dispatch row lookup take any
	// enabled per_user entra-lane row today, and a row with no tenant or
	// client would fail their validation on every login and could shadow the
	// row that does sign in. Skipping own_pat there is L1's change (#1428).
	ADOTokenModeOwnPAT ADOTokenMode = "own_pat"
)

func (ADOTokenMode) Valid added in v0.7.10

func (m ADOTokenMode) Valid() bool

Valid reports whether m is an accepted mode.

type APIToken added in v0.6.0

type APIToken struct {
	ID              uuid.UUID  `json:"id"`
	Principal       string     `json:"principal"`
	Email           string     `json:"email,omitempty"`
	Role            string     `json:"role"`
	UserType        string     `json:"user_type"`
	Groups          []string   `json:"groups,omitempty"`
	GroupsTruncated *bool      `json:"groups_truncated,omitempty"`
	Name            string     `json:"name"`
	CreatedAt       time.Time  `json:"created_at"`
	LastUsedAt      *time.Time `json:"last_used_at,omitempty"`
	RevokedAt       *time.Time `json:"revoked_at,omitempty"`
	ExpiresAt       *time.Time `json:"expires_at,omitempty"` // nil = never expires; an expired token authenticates nothing
	MintedBy        string     `json:"minted_by,omitempty"`  // admin who minted this for its owner; empty when the owner minted it
	// Disposition is where an admin-minted token stands, derived at read (#1508); empty on any other token.
	Disposition TokenDisposition `json:"disposition,omitempty"`
	// ServiceIdentity names the service identity an admin linked this token to, set with Disposition on
	// a token that has a move row (#1508); empty on any other token.
	ServiceIdentity string `json:"service_identity,omitempty"`
	Token           string `json:"token,omitempty"` // plaintext, create response ONLY
	// IdentityStampedAt is when Role and Groups were last stamped: at mint, then at each sign-in of
	// the owner. Nil reads as stale under WARDYN_ROLE_STAMP_TTL. Never on the wire.
	IdentityStampedAt *time.Time `json:"-"`
	// RevSeq is the revocation sequence read when the token was minted; migration 0176 gave the tokens
	// that existed their order once. 0 only for a token written by a replica that predates it, which a
	// revocation cutoff orders on CreatedAt. Never on the wire.
	RevSeq int64 `json:"-"`
}

APIToken is a per-user, long-lived bearer credential a human mints for scripts/CI, replacing shared use of the deployment-wide admin token; grants/RBAC/ownership bind to the owning human. Email/Role/Groups are a snapshot of the creating session (a bearer token carries no ID token to re-derive them from) — same bounded-stale ceiling as SSHPublicKey.Role: a demotion doesn't reach an outstanding token; revoke it. Groups distinguishes nil ("snapshot unavailable") from empty ("IdP sent no usable groups"); UserType change on the People page revokes tokens rather than re-stamping them. Token carries the plaintext credential, populated only on the create response — never stored, listed or logged. GroupsTruncated is THREE-VALUED: true = cut off by the cookie byte cap at mint; false = complete; nil = unknown (pre-dates this bit) and reads as TRUNCATED (fail closed) — nil-as-false would let a holder silently shed a group-assigned governance profile.

type ActorType

type ActorType string

ActorType distinguishes who performed an action in the audit stream.

const (
	ActorHuman  ActorType = "human"
	ActorAgent  ActorType = "agent"
	ActorSystem ActorType = "system"
	// ActorService is a service identity (#1508): actor "service:<name>", with its sponsor on every row.
	ActorService ActorType = "service"
)

type AgentProvider added in v0.7.2

type AgentProvider struct {
	// ID is the --agent value this row governs: a harness-catalog id OR a
	// WARDYN_AGENT_IMAGES key. The image-map half is deliberate: harnessByID
	// documents an image-map-only custom agent as supported, so a block that
	// refuses agents without a row must let the admin write one for their own.
	ID string `json:"id"`
	// Disabled turns the row off without deleting it — negative-sense, so the
	// zero value is ENABLED. Its runs are refused and the console renders it
	// unavailable, never hidden.
	Disabled bool `json:"disabled,omitempty"`
	// DefaultProvider names the model provider a new run of this agent uses
	// unless the person chooses another; must be enabled for this agent. If
	// later disabled, the default becomes a sentinel whose runs are refused,
	// never silently moved to another provider.
	DefaultProvider string `json:"default_provider,omitempty"`
}

AgentProvider is one agent row: which agent, whether it is offered, and the model provider its runs use unless the person chooses another. How a run reaches its model, and whose credential that is, live on the model provider (ModelProvider); every credential is each person's own.

type AgentProviders added in v0.7.2

type AgentProviders struct {
	// Agents are the per-agent rows; an agent with NO row is not offered at
	// all once the block exists.
	Agents []AgentProvider `json:"agents,omitempty"`
}

AgentProviders is the org's AGENT-ENABLEMENT POLICY: which coding agents this deployment offers and the model provider each uses by default. A sub-object of SiteConfig, shaped like WorkspaceProviders (see that type for the no-DDL / MDM-deliverable doctrine).

It exists because availability was an image map with no auth semantics: a customer's admin captured one AWS SSO session that silently backed every member's runs, with no way for a member to see whose credential was dying.

THE ZERO VALUE IS LEGACY OPEN MODE: with no block, every predicate over it answers exactly what it answered before the block existed (see the api package's agentProvidersConfigured / agentProviderFor).

VALIDATION LIVES IN internal/api (validateAgentProviders): a row's id is checked against the harness catalog / boot image map, and its default against the model providers — server state this package can't see. This file is types and closed enums only.

func (*AgentProviders) Empty added in v0.7.2

func (p *AgentProviders) Empty() bool

Empty reports whether p carries no policy — the shape a PUT uses to CLEAR the block. The write boundary normalizes this back to nil so a later GET doesn't render "agent_providers":{} forever.

type AgentRun

type AgentRun struct {
	ID        uuid.UUID `json:"id"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
	CreatedBy string    `json:"created_by"` // human principal (token `sub`)
	Agent     string    `json:"agent"`      // e.g. "claude-code", "codex-cli"
	Repo      string    `json:"repo"`       // e.g. "org/name"
	Task      string    `json:"task"`       // human task description
	// Title groups runs in the console's list; empty for legacy/system runs, which fall back to Task.
	Title string `json:"title,omitempty"`
	// Description is optional free-text context; display-only, never interpreted.
	Description      string           `json:"description,omitempty"`
	PolicyID         *uuid.UUID       `json:"policy_id,omitempty"`
	ConfinementClass ConfinementClass `json:"confinement_class"`
	State            RunState         `json:"state"`
	SPIFFEID         string           `json:"spiffe_id"`     // spiffe://<trust-domain>/agent-run/<id>; empty while QUEUED, written when the run starts
	RunnerTarget     string           `json:"runner_target"` // "docker"
	// Placement is where the sandbox lives (OD-8): remote (the organisation's own
	// executor), local (a runner the person registered), or empty for a run from
	// before 0.9 and for one whose placement nothing has recorded yet. ALWAYS on
	// the wire — a run has a placement even when the value is the empty one.
	Placement Placement `json:"placement"`
	// PlacementFilled records that a placement was RESOLVED for this run rather
	// than asked for by name, so an incident review can tell "the person chose
	// local" from "local was the only eligible placement".
	PlacementFilled bool `json:"placement_filled,omitempty"`
	// RunnerID names the runner a local run landed on. NULL for a remote run and
	// for every run from before 0.9; omitted on the wire when NULL, never "".
	RunnerID *uuid.UUID `json:"runner_id,omitempty"`
	// RunnerName, RunnerOnline and RunnerLastSeenAt are read-side facts about that runner, projected on
	// the run routes and never stored. They are absent for a remote run and when the runner could not be
	// read. The name is the runner's current one, which equals the name at create: a runner is never
	// renamed and a claimed row is never deleted, so a revoked runner's run keeps it. RunnerOnline is
	// this process's live view, so a pointer: absent is unknown. false is a fact on a single replica (also
	// when the hub cannot be asked); under WARDYN_HA it is never sent, because a replica that does not hold
	// the runner's session cannot tell offline from connected elsewhere.
	RunnerName       string     `json:"runner_name,omitempty"`
	RunnerOnline     *bool      `json:"runner_online,omitempty"`
	RunnerLastSeenAt *time.Time `json:"runner_last_seen_at,omitempty"`
	// EvidenceSource says who can attest this run's evidence (OD-8): substrate or
	// runner_asserted, empty for a legacy or unresolved record. Not
	// `confinement_source`, which says who CHOSE the class.
	EvidenceSource RunEvidenceSource `json:"evidence_source,omitempty"`
	// RunnerPoolID is the runner pool the run was admitted into, and the next three fields say how
	// (all written once at create). A pool is a label for a choice and never admission: the run
	// goes to the exact runner recorded in RunnerID (a self-hosted runner) or RunnerPoolExecutor (a
	// remote-hosted runner's id), whatever the pool or anyone's defaults say later. Nil for a run from
	// before pools and for a server-originated run that names none; RunnerPoolSelection is then empty.
	RunnerPoolID        *uuid.UUID          `json:"runner_pool_id,omitempty"`
	RunnerPoolRevision  int64               `json:"runner_pool_revision,omitempty"`
	RunnerPoolSelection RunnerPoolSelection `json:"runner_pool_selection,omitempty"`
	RunnerPoolExecutor  string              `json:"remote_runner_id,omitempty"`
	// Reserved is what the run holds on its runner while it holds a sandbox there (0174): the CPU and
	// memory it was given and its main drive, written once at insert and summed as the runner's In use.
	Reserved   RunnerAmounts `json:"-"`
	SandboxRef string        `json:"sandbox_ref,omitempty"`
	Image      string        `json:"image,omitempty"` // resolved sandbox image, kept for provenance; empty for legacy rows
	// Interactive marks a human-driven run: sandbox comes up RUNNING, no task exec'd, no completion watcher.
	Interactive bool `json:"interactive"`
	// Experience is the canonical run mode chosen at launch (0.9 New Run), frozen at create and never re-derived. Empty for a run from an older client or before 0.9;
	// BackgroundOnly is the one predicate every interactive door reads. It is not Interactive, which is the legacy execution flag.
	Experience RunExperience `json:"experience,omitempty"`
	// WorkspacePath is the run's primary host directory, denormalized so the control plane can warn (never block) a second run on it.
	WorkspacePath string `json:"workspace_path,omitempty"`
	// WorkspaceID marks a governed scan run: the driver runs wardyn-scan instead of the agent, trusted (not sandbox input) to persist the profile.
	WorkspaceID *uuid.UUID `json:"workspace_id,omitempty"`
	// WorkspaceIDs is the onboarded workspaces this run's policy referenced at create; grants nothing, only tells an `always` egress decision what to persist to.
	WorkspaceIDs []uuid.UUID `json:"workspace_ids,omitempty"`
	SourceID     *uuid.UUID  `json:"source_id,omitempty"` // trusted run→library-source linkage for a per-source scan run; never set by user runs
	// AutoStopAfterSec is the run's idle auto-stop cap, frozen at create so the reaper works with no policy_id to JOIN. 0 = never; <0 = explicit never.
	AutoStopAfterSec int    `json:"auto_stop_after_sec,omitempty"`
	AgentExecID      string `json:"agent_exec_id,omitempty"` // docker exec id, so the crash reconciler can check agent liveness across a wardynd restart
	FailureHint      string `json:"failure_hint,omitempty"`  // operator-facing reason a run FAILED before the agent started; display-only
	// StatusDetail is what the substrate says this run awaits while STARTING, and a lifetime end reason ("lifetime: end_reached", "lifetime: max_lifetime_reached") on a run KILLED at its end or its maximum; blanked on leaving STARTING except a TERMINAL failure reason or a lifetime end reason (kept for postmortem).
	StatusDetail string `json:"status_detail,omitempty"`
	StatusReason string `json:"status_reason,omitempty"` // reason token parsed out of StatusDetail, derived at read, never stored
	// AutonomyLevel freezes the level resolved at create, not the profile/posture that produced it (that's on the audit row).
	AutonomyLevel AutonomyLevel `json:"autonomy_level,omitempty"`
	// AdmittedAt is when a queued run was promoted out of the queue; written by promotion only, so nil for a run that never waited.
	AdmittedAt *time.Time `json:"admitted_at,omitempty"`
	// Queue is a waiting run's place in its pool's queue, derived at read from the queue row and never stored on the run; absent once the run leaves QUEUED.
	Queue  *RunQueue  `json:"queue,omitempty"`
	EndsAt *time.Time `json:"ends_at"` // run's lease end, captured once at create from RunLimits, never re-resolved
	// LifetimeCapAt is the latest end the pool and the deployment allow, measured from launch (created_at plus the lowest of their maximums); absent when neither bounds the lifetime or for a run from before this column. LifetimeCapSource names which one bound it (pool or deployment) and stays off the wire.
	LifetimeCapAt     *time.Time  `json:"lifetime_cap_at,omitempty"`
	LifetimeCapSource LimitSource `json:"-"`
	WaitBudgetSec     int         `json:"wait_budget_sec,omitempty"` // how long an approval this run raises stays open; 0 = deployment default alone applies
	// RunLimits are the owner's profile limits as they stood at create; later clamps use these captured bounds, never the profile's current ones.
	RunLimits           RunLimits  `json:"run_limits"`
	GovernanceProfileID *uuid.UUID `json:"governance_profile_id,omitempty"`
	// LostAt/LostReason mark a run that lost its sandbox but is kept (no proxy/network, files stay); stays RUNNING until a kill or the ended-run grace.
	LostAt     *time.Time `json:"lost_at,omitempty"`
	LostReason LostReason `json:"lost_reason,omitempty"`
	// ContainmentError/At: latest/first stop-confirm failure for a kept run; the lease sweep retries and clears both once it lands.
	ContainmentError   string     `json:"containment_error,omitempty"`
	ContainmentErrorAt *time.Time `json:"containment_error_at,omitempty"`
	// EndedAt stamps the transition into a terminal RunState; a lease-ended run stays RUNNING until the ended-run grace, so its end time is LostAt, not EndedAt.
	EndedAt        *time.Time `json:"ended_at,omitempty"`
	EndTightenedAt *time.Time `json:"end_tightened_at,omitempty"` // profile re-clamp shortened the end
	// PausedAt/PausedReason mark a run frozen with nobody present; keeps state/memory/files/proxy until activity or resume. Not contained — kill still works.
	PausedAt     *time.Time  `json:"paused_at,omitempty"`
	PausedReason PauseReason `json:"paused_reason,omitempty"`
	ActiveAt     *time.Time  `json:"active_at,omitempty"` // presence clock: last human input/egress decision/proxied bytes, or a resume; keepalives never move it; nil reads as CreatedAt
	// ModelProviderID freezes the provider id at create; the kind is deliberately NOT frozen (can change later) and lives only on the run.create audit snapshot.
	ModelProviderID string `json:"model_provider_id,omitempty"`
	UserType        string `json:"user_type,omitempty"` // creator's resolved user type, frozen at create
	Preset          string `json:"preset,omitempty"`    // launch preset this run was expanded from; empty for an explicit-spec run
	PresetVersion   int    `json:"preset_version,omitempty"`
	OperatorOwned   bool   `json:"operator_owned,omitempty"` // mirrors identity.Claims.OperatorOwned so dispatch/revive re-mint read what create decided; the console reads it for the no-personal-owner entry carve-out
	// CreatedVia is the registered portal this run was launched through on the person's behalf; nil when self-launched. The owner is still the person.
	CreatedVia *uuid.UUID `json:"created_via,omitempty"`
	// HasRecording/RecordingBytes/RecordingDurationSec are derived, never stored, projected only with ?include=recording_meta; HasRecording is the only "no recording" signal.
	HasRecording         bool          `json:"has_recording"`
	RecordingBytes       int64         `json:"recording_bytes,omitempty"`
	RecordingDurationSec float64       `json:"recording_duration_sec,omitempty"`
	DiskMiB              int           `json:"disk_mib,omitempty"`  // effective ephemeral disk cap MiB; 0 = no cap resolved
	Attention            *RunAttention `json:"attention,omitempty"` // what this live run awaits and who can clear it; derived, only on view-opted-in read paths

	// HumanAuthor is the principal accountable for the run at create, written once: the service identity's sponsor
	// at launch for a service identity's run, unchanged by a later sponsor change, and CreatedBy for any other run. A
	// record of accountability, not proof that a person was present. Empty for a run from before it was recorded.
	// Read by the second-human rules only.
	HumanAuthor string `json:"-"`
	// LaunchIntent is the setup kept for Run again (a kind Run document), written in the run row's own
	// transaction when set. Never on the wire and never read back onto a listed run: the creator reads it by
	// run id. On a created run it is set only when the store kept it.
	LaunchIntent json.RawMessage `json:"-"`
}

AgentRun is one governed execution of a coding agent on behalf of a human, with its own identity (SPIFFE ID), credential grants, and audit trail.

func (AgentRun) BackgroundOnly added in v0.9.0

func (r AgentRun) BackgroundOnly() bool

BackgroundOnly is the one predicate every interactive door of a run calls (attach, interactive exec, SSH, the UI gateway, a desktop session, a restored session, and the same routes through the API and CLI). It reads the stored Experience, never the request and never the legacy Interactive flag.

An empty Experience is a run launched by an older client or before 0.9: it keeps today's documented behaviour at every door, so this answers false. A new client always carries an experience, so its Background runs answer true.

type ApprovalDecision added in v0.5.0

type ApprovalDecision struct {
	State     ApprovalState
	DecidedBy string
	Reason    string
	Scope     ApprovalScope // meaningful only for an egress_domain approval; others leave the zero value
	ExpiresAt *time.Time
}

ApprovalDecision is what a human (or the sweeper, for a stale-PENDING expiry) is deciding, threaded as one value instead of growing DecideApproval's signature. ExpireStale leaves Scope at "" (never ScopeRun) — an expiry is a sweep nobody decided.

type ApprovalEscalation added in v0.8.6

type ApprovalEscalation struct {
	Tier   int16
	NextAt *time.Time
}

ApprovalEscalation is one pending approval's place on its notification schedule: the highest tier whose due time has passed, and the earliest due time still ahead (nil when no later tier exists).

type ApprovalKind

type ApprovalKind string

ApprovalKind enumerates what a human is being asked to approve.

const (
	ApprovalCredential   ApprovalKind = "credential"
	ApprovalEgressDomain ApprovalKind = "egress_domain"
	ApprovalToolCall     ApprovalKind = "tool_call"
	// ApprovalCredentialReauth: the run's model credential lapsed mid-run, so the proxy holds the
	// next exchange while the owner signs in again. Not a decision — the row moves to APPROVED only
	// via ResolveReauth, never approval.decide.
	ApprovalCredentialReauth ApprovalKind = "credential_reauth"
	// ApprovalPushContent: a brokered git push touched a require_review_paths path, held for an
	// admin decision. Admin-only — a member deciding their own push is the exfiltration it stops.
	ApprovalPushContent ApprovalKind = "push_content"
)

type ApprovalNotifyChannelStat added in v0.8.6

type ApprovalNotifyChannelStat struct {
	Channel        string
	LastSentAt     *time.Time
	LastError      string
	LastErrorAt    *time.Time
	FailedLastHour int
}

ApprovalNotifyChannelStat is one channel's delivery history read from the outbox: when a send last succeeded, the class and time of the last failed attempt, and how many rows went dead in the last hour. LastError is a failure class such as "http_status:503", never text a server or URL wrote.

type ApprovalNotifyState added in v0.8.6

type ApprovalNotifyState string

ApprovalNotifyState is the lifecycle of one approval_notifications outbox row.

const (
	NotifyPending   ApprovalNotifyState = "pending"
	NotifySending   ApprovalNotifyState = "sending"
	NotifySent      ApprovalNotifyState = "sent"
	NotifyCancelled ApprovalNotifyState = "cancelled"
	NotifyDead      ApprovalNotifyState = "dead"
)

type ApprovalRequest

type ApprovalRequest struct {
	ID             uuid.UUID       `json:"id"`
	RunID          uuid.UUID       `json:"run_id"`
	GrantID        *uuid.UUID      `json:"grant_id,omitempty"`
	Kind           ApprovalKind    `json:"kind"`
	RequestedScope json.RawMessage `json:"requested_scope"`
	State          ApprovalState   `json:"state"`
	RequestedAt    time.Time       `json:"requested_at"`
	DecidedAt      *time.Time      `json:"decided_at,omitempty"`
	DecidedBy      string          `json:"decided_by,omitempty"`
	MintedJTI      string          `json:"minted_jti,omitempty"`
	Reason         string          `json:"reason,omitempty"`
	// DecisionScope is how far the decision reaches (see ApprovalScope); empty on a PENDING row. Named
	// decision_scope to avoid clashing with RequestedScope (the unrelated host JSON).
	DecisionScope ApprovalScope `json:"decision_scope,omitempty"`
	// DecisionExpiresAt is set only when DecisionScope is until — a different clock than the EXPIRED-state sweeper, which ages out stale PENDING requests.
	DecisionExpiresAt *time.Time `json:"decision_expires_at,omitempty"`
	// ExpiresAt is when a PENDING request stops waiting: min(requested_at+WaitBudgetSec, EndsAt); computed on read, never stored.
	ExpiresAt *time.Time `json:"expires_at,omitempty"`
	Held      bool       `json:"held,omitempty"` // Held/HeldUntil project approval.Hold(this, now) at response time, never stored; both zero on a DECIDED row
	HeldUntil *time.Time `json:"held_until,omitempty"`
	// EscalationTier is the highest notification tier whose due time has passed, and SLADueAt is the
	// next tier's due time. Both are projected from approval_notifications at response time on PENDING
	// rows only, never stored; tier 0 (the first notice) and "no later tier" leave them zero.
	EscalationTier int16      `json:"escalation_tier,omitempty"`
	SLADueAt       *time.Time `json:"sla_due_at,omitempty"`
}

ApprovalRequest is a blocking human-in-the-loop gate. RequestedScope is EXACTLY what the approver saw; the broker writes MintedJTI back in the same transaction as the mint.

type ApprovalScope added in v0.5.0

type ApprovalScope string

ApprovalScope is how far a human's approve/deny decision reaches — orthogonal to FirstUseMode, which decides whether to escalate at all. egress_domain carries any of the four; tool_call carries none; credential carries only ScopeRun, re-mintable for the rest of the run.

const (
	// ScopeOnce releases exactly one connection — one CONNECT tunnel for HTTPS, one request for HTTP.
	ScopeOnce ApprovalScope = "once"
	// ScopeRun holds for the rest of the run — the legacy default meaning.
	ScopeRun ApprovalScope = "run"
	// ScopeUntil holds until DecisionExpiresAt or run end, whichever comes first.
	ScopeUntil ApprovalScope = "until"
	// ScopeAlways also persists the host onto the run's workspace so future runs inherit it. Operator-only.
	ScopeAlways ApprovalScope = "always"
)

func (ApprovalScope) Normalize added in v0.5.0

func (s ApprovalScope) Normalize() ApprovalScope

Normalize resolves a stored/wire value for every runtime read: empty means legacy run-scoped => ScopeRun; an unknown non-empty value can only come from a newer control plane, and treating it as ScopeRun would be fail-open across versions, so it floors to ScopeOnce. Caveat: on a DENY, once is actually WIDER (it re-raises), so this isn't uniformly fail-closed, just re-denied with extra queue noise.

func (ApprovalScope) Valid added in v0.5.0

func (s ApprovalScope) Valid() bool

Valid reports whether s is empty (legacy run-scoped) or a known scope — rejects garbage at the API write boundary; runtime reads still fail closed via Normalize.

type ApprovalState

type ApprovalState string

ApprovalState is the approval lifecycle.

const (
	ApprovalPending  ApprovalState = "PENDING"
	ApprovalApproved ApprovalState = "APPROVED"
	ApprovalDenied   ApprovalState = "DENIED"
	ApprovalExpired  ApprovalState = "EXPIRED"
	// ApprovalCancelled: the run reached a terminal state while still PENDING; answering it would
	// mint a credential for a gone sandbox. Every reader treats it as a refusal.
	ApprovalCancelled ApprovalState = "CANCELLED"
)

type ArtifactOverride deprecated

type ArtifactOverride struct {
	BaseURL        string `json:"base_url"`
	TokenSecretRef string `json:"token_secret_ref,omitempty"`
}

ArtifactOverride is one ecosystem's corporate artifact-registry redirect: the base URL to emit into that ecosystem's config (.npmrc/pip.conf/cargo config/settings.xml/GOPROXY/nuget.config) plus an optional secret ref for a token injected proxy-side (the sandbox never holds the value).

Deprecated: superseded by EgressRedirect. See SiteConfig.ArtifactOverrides for why the type is kept.

type AttentionBy added in v0.8.0

type AttentionBy string

AttentionBy names who the run is waiting on, in the viewer's own console view (User or Admin; see RunAttention for how the two differ).

const (
	// AttentionYou: the viewer themselves can act (decide the approval, sign
	// in, revive the run).
	AttentionYou AttentionBy = "you"
	// AttentionOwner: only the run's owner can act — reauth and lost-run
	// revive are never delegable — and the viewer isn't the owner; reachable
	// only in Admin view, since User view forces owner=me.
	AttentionOwner AttentionBy = "owner"
	// AttentionAdmin: only a security operator can act; the viewer is a
	// member who cannot decide this run's held approval.
	AttentionAdmin AttentionBy = "admin"
)

type AttentionKind added in v0.8.0

type AttentionKind string

AttentionKind names what a live run is waiting on. Absent (RunAttention nil) means the run needs nobody — terminal, lease-ended, or nothing PENDING held.

const (
	// AttentionApproval: a held approval (any kind but credential_reauth) is
	// parking the sandbox.
	AttentionApproval AttentionKind = "approval"
	// AttentionReauth: the run's model credential lapsed mid-run; the proxy
	// holds the next credential exchange for its owner to sign in.
	AttentionReauth AttentionKind = "reauth"
	// AttentionADOConsent: same as AttentionReauth, specifically an Azure
	// DevOps sign-in or consent request (TallyReauthADOSignIn/Consent).
	AttentionADOConsent AttentionKind = "ado_consent"
	// AttentionLost: the run lost its sandbox for a reason other than lease
	// end (reboot, outage) and needs a revive.
	AttentionLost AttentionKind = "lost"
)

type AuditEvent

type AuditEvent struct {
	ID        uuid.UUID       `json:"id"`
	Time      time.Time       `json:"time"`
	RunID     *uuid.UUID      `json:"run_id,omitempty"`
	ActorType ActorType       `json:"actor_type"`
	Actor     string          `json:"actor"`  // human sub, agent SPIFFE ID, or component name
	Action    string          `json:"action"` // dotted verb, e.g. "credential.mint", "egress.deny", "kernel.process.exec"
	Target    string          `json:"target,omitempty"`
	Outcome   string          `json:"outcome"` // "success" | "failure" | "denied"
	SourceIP  string          `json:"source_ip,omitempty"`
	Data      json.RawMessage `json:"data,omitempty"`

	// DeviceID names the enrolled device that forwarded this row; derived on read, never taken from a caller.
	DeviceID *uuid.UUID `json:"device_id,omitempty"`

	// PrevHash/RowHash form the tamper-evidence chain: RowHash = SHA-256(PrevHash || canonical serialization
	// of the fields above), computed BY POSTGRES in a BEFORE INSERT trigger, never by the caller; sinks use
	// the head hash to detect truncation. Verified via GET /api/v1/audit/chain/verify.
	PrevHash string `json:"prev_hash,omitempty"`
	RowHash  string `json:"row_hash,omitempty"`
}

AuditEvent is one append-only audit record. Every credential mint/revoke, approval decision, policy change, egress decision, and lifecycle change emits one, carrying the delegation chain (human sub + agent run). credential.*/identity.*/approval.*/policy.*/egress.*/run.*/recording.* are control-plane and agent self-report events; kernel.* is the eBPF/Tetragon ground-truth stream, emitted ONLY by the host-scoped sensor via POST /api/v1/internal/groundtruth, which FORCES actor_type=system+actor="wardyn-tetragon-ingest" and rejects any action without the "kernel." prefix. kernel.* Data carries stream/subtype/cgroup_id/container_id, a kind-specific argv/dst/path, loader (the documented mmap execve-hook bypass surface, flagged not blocked), and correlation ("mapped"|"unmapped" — unmapped => run_id NULL, never silently dropped). Outcome stays within "success"|"failure"|"denied": kernel events use failure for an escape/coverage-gap signal and never denied — a detection stream, it does not block. See internal/groundtruth.

type AuditRetentionDrop added in v0.8.6

type AuditRetentionDrop struct {
	Partition string `json:"partition"`
	Rows      int64  `json:"rows"`
	SeqLo     int64  `json:"seq_lo"`
	SeqHi     int64  `json:"seq_hi"`
	Digest    string `json:"digest"`
	// EventSeq is the seq of the chained audit.retention.partition_dropped event.
	EventSeq int64 `json:"event_seq"`
}

AuditRetentionDrop is a completed drop.

type AuditRetentionPartition added in v0.8.6

type AuditRetentionPartition struct {
	Name string `json:"name"`
	// Lo is absent for the legacy partition (it starts at MINVALUE).
	Lo    *time.Time `json:"lo,omitempty"`
	Hi    *time.Time `json:"hi,omitempty"`
	Rows  int64      `json:"rows"`
	State string     `json:"state"` // closed | open | future
	// Eligible is true when audit_retention_drop would take this partition now; otherwise Refusal is the
	// reason it would refuse with.
	Eligible bool   `json:"eligible"`
	Refusal  string `json:"refusal,omitempty"`
}

AuditRetentionPartition is one partition of the audit log as audit_retention_partitions reports it.

type AuditRetentionPolicy added in v0.8.6

type AuditRetentionPolicy struct {
	// Days is the stored policy; EffectiveDays is what the drop function applies now (the pending value
	// once its date has passed).
	Days          int `json:"days"`
	EffectiveDays int `json:"effective_days"`
	// PendingDays and PendingEffectiveAt are set together, for a decrease that has not taken effect.
	PendingDays        *int       `json:"pending_days,omitempty"`
	PendingEffectiveAt *time.Time `json:"pending_effective_at,omitempty"`
}

AuditRetentionPolicy is the persisted retention policy (audit_partition_meta). A decrease, including 0 -> finite, is pending until its date; days 0 is forever.

type AuditRetentionPolicyChange added in v0.8.6

type AuditRetentionPolicyChange struct {
	Outcome string `json:"outcome"`
	AuditRetentionPolicy
}

AuditRetentionPolicyChange is audit_retention_set_policy's answer. Outcome is applied, pending, cancelled or unchanged.

type AuditRetentionStatus added in v0.8.6

type AuditRetentionStatus struct {
	Policy AuditRetentionPolicy `json:"policy"`
	// Cutover is when the log was converted to partitions; everything before it is in the legacy partition.
	Cutover    time.Time                 `json:"cutover"`
	Partitions []AuditRetentionPartition `json:"partitions"`
	// MonthsAhead is how many months past the current one already have a partition.
	MonthsAhead int `json:"months_ahead"`
}

AuditRetentionStatus is what GET /audit/retention reports.

type AutonomyEgressPosture added in v0.8.0

type AutonomyEgressPosture string

AutonomyEgressPosture is one of the three egress states an AutonomyRubric caps against (internal/composer/autonomy.go's posture arithmetic, #97).

const (
	AutonomyEgressOpen     AutonomyEgressPosture = "open"
	AutonomyEgressReviewed AutonomyEgressPosture = "reviewed"
	AutonomyEgressSealed   AutonomyEgressPosture = "sealed"
)

type AutonomyLevel added in v0.8.0

type AutonomyLevel string

AutonomyLevel is one rung on the autonomy ladder a governance profile's AutonomyRubric caps against, L0 (most supervised) through L3 (least). The codes stay internal — the console renders plain labels, like ConfinementClass's CC1/CC2/CC3 (0.8 #77):

  • L0 "attended": interactive only, supervised seeding.
  • L1 "gated": adds non-interactive runs, but tool approvals derive to hold.
  • L2 "unattended": adds auto-approval and seeded auto tools.
  • L3: adds task_mode=exec, the door around every other gate — the top rung.
const (
	AutonomyL0 AutonomyLevel = "L0"
	AutonomyL1 AutonomyLevel = "L1"
	AutonomyL2 AutonomyLevel = "L2"
	AutonomyL3 AutonomyLevel = "L3"
)

func (AutonomyLevel) Rank added in v0.8.0

func (l AutonomyLevel) Rank() int

Rank orders AutonomyLevel weakest (most supervised) -> strongest (least), mirroring ConfinementClass.Rank(): resolving a rubric folds several applicable caps to their MINIMUM level (#97), and Rank is what "minimum" compares on. An unrecognised value ranks below L0 so it never wins a min() against a real level.

func (AutonomyLevel) Valid added in v0.8.0

func (l AutonomyLevel) Valid() bool

Valid reports whether l is one of the four defined rungs. Unlike ConfinementClass (whose every Rank() caller already tolerates rank 0), AutonomyRubric needs this gate: an author-facing field, so a typo must 400 rather than silently rank as "below L0".

type AutonomyPosture added in v0.8.0

type AutonomyPosture struct {
	Egress      AutonomyEgressPosture  `json:"egress"`
	Secrets     AutonomySecretsPosture `json:"secrets"`
	Confinement ConfinementClass       `json:"confinement"`
}

AutonomyPosture is a run's three-axis shape — egress reach, secret power, enforced confinement class — the input resolveRunAutonomy (#97) folds against a profile's AutonomyRubric. Computed, never stored on its own; it travels inside AutonomyResolution.

type AutonomyResolution added in v0.8.0

type AutonomyResolution struct {
	Level   AutonomyLevel   `json:"level"`
	Posture AutonomyPosture `json:"posture"`
	BoundBy []string        `json:"bound_by,omitempty"`
}

AutonomyResolution is what resolveRunAutonomy (#97) decides for one run: the level, the posture that produced it, and every rubric field that bound the result — provenance for the create audit row, the frozen AgentRun.AutonomyLevel, and the preflight response.

BoundBy is a LIST, deliberately: the fold is a min() over three axes, so rows routinely TIE at the resolved level, and naming just one cause would leave an admin editing a row that can't move the level while other causes stay hidden — every tied cause is named, in the fixed field order internal/composer/autonomy.go folds in.

Empty when nothing capped the level (no profile, no rubric, or an unset posture).

type AutonomyRubric added in v0.8.0

type AutonomyRubric struct {
	// EgressOpen caps the level when egress is OPEN: allow-all, or any allowlisted host beyond baseline.
	EgressOpen AutonomyLevel `json:"egress_open,omitempty"`
	// EgressReviewed caps the level when egress is REVIEWED: first-use approval raises approvals, nothing wide open.
	EgressReviewed AutonomyLevel `json:"egress_reviewed,omitempty"`
	// EgressSealed caps the level when egress is SEALED: neither of the above.
	EgressSealed AutonomyLevel `json:"egress_sealed,omitempty"`
	// SecretsPowerful caps the level when the run holds a POWERFUL secret: a write-capable grant, an
	// api_key to a non-baseline host, or a git_pat/ssh_key/env_secret grant.
	SecretsPowerful AutonomyLevel `json:"secrets_powerful,omitempty"`
	// SecretsBaseline caps the level when the run holds any grant, none of them powerful.
	SecretsBaseline AutonomyLevel `json:"secrets_baseline,omitempty"`
	// SecretsNone caps the level when the run holds no grant at all.
	SecretsNone AutonomyLevel `json:"secrets_none,omitempty"`
	// ConfinementCC1/CC2/CC3 cap the level by the run's ENFORCED confinement
	// class (an empty enforced class reads as CC1 — see AutonomyPosture).
	ConfinementCC1 AutonomyLevel `json:"confinement_cc1,omitempty"`
	ConfinementCC2 AutonomyLevel `json:"confinement_cc2,omitempty"`
	ConfinementCC3 AutonomyLevel `json:"confinement_cc3,omitempty"`
	// AgentGuardrailLocks selects the locked L2 managed-settings document for a run that resolves to L2:
	// repository hooks and permission rules cannot override the agent-side guardrails. It caps no level
	// and changes nothing at any other rung.
	AgentGuardrailLocks bool `json:"agent_guardrail_locks,omitempty"`
}

AutonomyRubric maps a run's posture to a permitted AutonomyLevel: three egress postures, three secret postures, three confinement classes, each unset (caps nothing) or one of the four levels. A run resolves to the MINIMUM over every field whose posture applies (internal/composer/autonomy.go, #97); an all-unset rubric caps nothing, like a nil rubric.

It also carries one boolean, AgentGuardrailLocks, that caps no level: it selects the locked L2 managed-settings document and is not part of the nine-field fold.

A closed struct with `omitempty` on every field, not a map — same doctrine as GovernanceLimits: small, complete, validated by the Go type (Validate), no DB CHECK needed.

func (AutonomyRubric) Validate added in v0.8.0

func (a AutonomyRubric) Validate() error

Validate reports the first field carrying an undefined AutonomyLevel, naming it — governanceLimitsRefusal prefixes the field name onto its 400 so an admin knows which of the nine to fix. An empty field is always valid: unset caps nothing.

type AutonomySecretsPosture added in v0.8.0

type AutonomySecretsPosture string

AutonomySecretsPosture is one of the three secret-power states an AutonomyRubric caps against.

const (
	AutonomySecretsPowerful AutonomySecretsPosture = "powerful"
	AutonomySecretsBaseline AutonomySecretsPosture = "baseline"
	AutonomySecretsNone     AutonomySecretsPosture = "none"
)

type AzureSettings added in v0.8.6

type AzureSettings struct {
	Endpoint string `json:"endpoint,omitempty"`
	Route    string `json:"route,omitempty"`
}

AzureSettings is an azure_foundry row's data-plane endpoint and the one route it serves (AzureRoute*).

type BaseImageEntry added in v0.5.0

type BaseImageEntry struct {
	ID    uuid.UUID `json:"id"`
	Kind  string    `json:"kind"` // "registry" | "custom" | "byo"
	Name  string    `json:"name"`
	Image string    `json:"image"` // the ref: the image itself (registry/byo) or the FROM (custom)
	// Steps are Dockerfile lines on a "custom" Image; not applied at build
	// time (host-side build RCE — see WorkspaceBaseImage.Steps), stored only
	// so the row round-trips.
	Steps     []string  `json:"steps,omitempty"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

BaseImageEntry is one tier-2 catalog row: a shared, reusable image an operator saved. Kind is registry|custom|byo, never "recommended" (a derived per-workspace build with no catalog identity, enforced by a store CHECK).

type BedrockSettings added in v0.8.0

type BedrockSettings struct {
	Region       string `json:"region,omitempty"`
	BaseURL      string `json:"base_url,omitempty"`
	SSOStartURL  string `json:"sso_start_url,omitempty"`
	SSOAccountID string `json:"sso_account_id,omitempty"`
	SSORoleName  string `json:"sso_role_name,omitempty"`
}

BedrockSettings is a Bedrock kind's region, optional data-plane address, and (bedrock_sso only) the org-level sign-in setup. The start URL and pin are ADMIN-OWNED: a sign-in proposes, the provider disposes.

type Branding added in v0.8.0

type Branding struct {
	OrgName         string
	NameFormat      string
	Primary         string
	PrimaryText     string
	DarkPrimary     string
	DarkPrimaryText string
	SupportURL      string
	LogoType        string
	LogoFromFile    bool // the logo is the site config's branding.logo_path file (0105_branding_logo_from_file)
	UpdatedAt       time.Time
	UpdatedBy       string
}

Branding is the org-wide console branding record (migration 0091_branding). Only the primary colour, its text colour, the logo and the name are brandable: danger/warning/success/info and the admin-view cue are fixed Wardyn tokens a brand can never recolour. Empty DarkPrimary means the dark pair is derived (DeriveDarkPrimary in internal/api).

type CLIAWS added in v0.9.0

type CLIAWS struct {
	StartURL  string `json:"start_url"`  // the access portal, https
	SSORegion string `json:"sso_region"` // where the portal lives
	AccountID string `json:"account_id"` // 12 digits
	RoleName  string `json:"role_name"`  // the permission set name
	Region    string `json:"region"`     // the CLI's default region
}

CLIAWS pins one IAM Identity Center account and permission set.

type CLIAzure added in v0.9.0

type CLIAzure struct {
	Subscriptions []string `json:"subscriptions"` // 1..MaxCLIAzureSubscriptions GUIDs
	Access        string   `json:"access"`        // read (GET and HEAD only) | write
}

CLIAzure pins subscriptions and whether the CLI may change them.

func (CLIAzure) SubscriptionIDs added in v0.9.0

func (a CLIAzure) SubscriptionIDs() []string

SubscriptionIDs is the row's subscriptions as Azure compares them: lowercase GUIDs, in the row's order. An entry that is not a GUID is "".

type CLIGitHub added in v0.9.0

type CLIGitHub struct {
	Capabilities []ghscope.Capability `json:"capabilities"`
}

CLIGitHub scopes the GitHub CLI to the run's repositories by capability.

type CapabilityEffect added in v0.6.0

type CapabilityEffect string

CapabilityEffect is a grant's direction. Closed and complete; DENY BEATS ALLOW at resolution time, with no user-vs-group precedence — "Bob's user allow overrode the group deny" is a breach report, not a feature.

const (
	CapabilityAllow CapabilityEffect = "allow"
	CapabilityDeny  CapabilityEffect = "deny"
)

func (CapabilityEffect) Valid added in v0.6.0

func (e CapabilityEffect) Valid() bool

Valid reports whether e is allow or deny.

type CapabilityGrant added in v0.6.0

type CapabilityGrant struct {
	ID          uuid.UUID             `json:"id"`
	SubjectType CapabilitySubjectType `json:"subject_type"`
	Subject     string                `json:"subject"`
	Capability  string                `json:"capability"`
	Value       string                `json:"value"`
	Effect      CapabilityEffect      `json:"effect"`
	CreatedAt   time.Time             `json:"created_at"`
	CreatedBy   string                `json:"created_by,omitempty"`
}

CapabilityGrant is one row of the permissioning grant list: "subject S may (or may not) use capability C at value V".

Capability is a PLAIN STRING, not an enum, deliberately: the closed kind set lives in one Go slice (internal/api's capabilityKinds) and is checked at the write boundary, so a new kind needs no schema change. An unknown kind is simply inert.

Value's meaning is per kind: host or "*.suffix" for egress_host, else an exact secret name/workspace uuid/image ref; "*" is always the wildcard.

type CapabilitySubjectType added in v0.6.0

type CapabilitySubjectType string

CapabilitySubjectType names WHO a capability grant is written against. Closed and complete — its DB CHECK is pinned against these constants by internal/db's TestClosedEnumChecksMatchConstants.

const (
	// CapabilitySubjectUser is one human, named by lowercased OIDC "sub" or
	// email — either matches, so an admin can record whichever identity they
	// actually know.
	CapabilitySubjectUser CapabilitySubjectType = "user"
	// CapabilitySubjectGroup is one entry of the login-time union of the ID
	// token's roles+groups claims (oidc.Session.Groups); Entra App Roles are
	// grantable through this with no extra config.
	CapabilitySubjectGroup CapabilitySubjectType = "group"
	// CapabilitySubjectAll is every signed-in human, the baseline when an IdP
	// emits no usable groups claim. Subject is "" for this type.
	CapabilitySubjectAll CapabilitySubjectType = "all"
	// CapabilitySubjectUserType is everyone of one user type (UserType.ID); a
	// person holds exactly one, stamped at sign-in. It sits between group and
	// all for governance ceilings/drives; for grants it's one more subject,
	// so a DENY on a type is a wall no user or group allow lifts.
	CapabilitySubjectUserType CapabilitySubjectType = "user_type"
)

func (CapabilitySubjectType) Valid added in v0.6.0

func (t CapabilitySubjectType) Valid() bool

Valid rejects a garbage subject type at the API write boundary, mirroring ApprovalScope.Valid.

type CeilingOverlay added in v0.8.6

type CeilingOverlay struct {
	AllowedDomains          *[]string              `json:"allowed_domains,omitempty"`
	DeniedDomains           *[]string              `json:"denied_domains,omitempty"`
	AllowAllEgress          *bool                  `json:"allow_all_egress,omitempty"`
	FirstUseApproval        *FirstUseMode          `json:"first_use_approval,omitempty"`
	FirstUseHoldSeconds     *int                   `json:"first_use_hold_seconds,omitempty"`
	MaxHolds                *int                   `json:"max_holds,omitempty"`
	AllowedMethods          *[]string              `json:"allowed_methods,omitempty"`
	MinConfinementClass     *ConfinementClass      `json:"min_confinement_class,omitempty"`
	EligibleGrants          *[]GrantSpec           `json:"eligible_grants,omitempty"`
	AutoStopAfterSec        *int                   `json:"auto_stop_after_sec,omitempty"`
	WorkspaceMounts         *[]WorkspaceMount      `json:"workspace_mounts,omitempty"`
	WorkspaceRepos          *[]WorkspaceRepo       `json:"workspace_repos,omitempty"`
	LLMInspection           *LLMInspectionSpec     `json:"llm_inspection,omitempty"`
	UIApps                  *[]UIApp               `json:"ui_apps,omitempty"`
	Resources               *ResourcesOverlay      `json:"resources,omitempty"`
	ToolRules               *[]ToolRule            `json:"tool_rules,omitempty"`
	GitPushAnyBranch        *bool                  `json:"git_push_any_branch,omitempty"`
	PushRules               *PushRulesOverlay      `json:"push_rules,omitempty"`
	AzureDevOpsCapabilities *[]adoscope.Capability `json:"azure_devops_capabilities,omitempty"`
	GitHubCapabilities      *[]ghscope.Capability  `json:"github_capabilities,omitempty"`
}

CeilingOverlay is the part of a RunPolicySpec a composed governance profile narrows, with PRESENCE typed: every field is a pointer, so "the overlay says nothing about this" (nil, inherit the base) and "the overlay says this is empty" (a pointer to an empty list, a value) can never be confused. That distinction is load-bearing: allowed_domains [] narrows to no domains, while allowed_methods [] would read as "all methods" in the proxy and is refused.

It mirrors RunPolicySpec field for field (composer's coverage guard fails on a field of either that the other lacks). Decoded strictly: an unknown key is an error, never a silently ignored narrowing.

func DecodeCeilingOverlay added in v0.8.6

func DecodeCeilingOverlay(raw []byte) (CeilingOverlay, error)

DecodeCeilingOverlay strictly decodes a stored or submitted overlay.

type Component added in v0.8.9

type Component struct {
	ID         uuid.UUID           `json:"id"`
	Owner      string              `json:"owner,omitempty"`
	Name       string              `json:"name"`
	Definition ComponentDefinition `json:"definition"`
	// Version starts at 1 and moves by one on every update.
	Version   int       `json:"version"`
	CreatedBy string    `json:"created_by,omitempty"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

Component is one stored custom component: an org row (Owner "") an admin wrote and grants, or a person's saved row (Owner is their principal).

func (Component) Validate added in v0.8.9

func (c Component) Validate(validHost func(string) error) error

Validate checks a stored component: its name, and its definition as an org row (Owner "") or a person's.

type ComponentCLI added in v0.9.0

type ComponentCLI struct {
	Kind   string     `json:"kind"` // azure | aws | github
	Azure  *CLIAzure  `json:"azure,omitempty"`
	AWS    *CLIAWS    `json:"aws,omitempty"`
	GitHub *CLIGitHub `json:"github,omitempty"`
}

ComponentCLI is the credential an org row gives a run's command-line tool. Kind names which of the three arms is set, and only that one is.

type ComponentConfigSchema added in v0.9.0

type ComponentConfigSchema struct {
	SchemaVersion int           `json:"schema_version"`
	Groups        []ConfigGroup `json:"groups,omitempty"`
	Fields        []ConfigField `json:"fields"`
}

ComponentConfigSchema is a component's field schema.

type ComponentConfigValues added in v0.9.0

type ComponentConfigValues map[string]json.RawMessage

ComponentConfigValues are the values an attachment supplies, keyed by field ID, each typed by its field's kind.

type ComponentDefinition added in v0.8.9

type ComponentDefinition struct {
	Hosts   []string          `json:"hosts"`
	Secrets []ComponentSecret `json:"secrets,omitempty"`
	Config  map[string]string `json:"config,omitempty"`
	// CLI marks an org row as a CLI credential card (azure, aws or github).
	CLI *ComponentCLI `json:"cli,omitempty"`
	// MCP makes the component a remote MCP server, which a run's harnesses
	// reach only through the proxy's broker route (component_mcp.go).
	MCP *ComponentMCP `json:"mcp,omitempty"`
}

ComponentDefinition is the whole contract of a custom component: the hosts it reaches, the secrets it carries with how each is delivered, and plain (non-secret) environment. It holds secret names, never values.

func (ComponentDefinition) Validate added in v0.8.9

func (d ComponentDefinition) Validate(validHost func(string) error, personDefined bool) error

Validate checks the shape of a definition. It is the one rule every door that accepts a definition runs; every error names the field.

validHost is the egress allowlist's entry rule (proxy.ValidDomainEntry). It is a parameter because that package imports this one.

personDefined is true for a definition a person wrote — an inline one, or a stored row with an owner — and false for an org row (owner ""). A person's hosts must be DNS names, never IP addresses, and a person may not use Shared or PlainHTTP; org rows keep the admin's allowlist semantics.

Left to the caller, because they depend on the deployment or the store: whether the named secrets exist and whose they are, reserved secret names, and every bound on the hosts themselves (deny lists, model hosts, hosts that already carry a credential — compare with Destination).

type ComponentDelivery added in v0.8.9

type ComponentDelivery struct {
	Mode string `json:"mode"` // header | env | file
	// Host, Header and Format apply to header: Host is a bare host — no
	// port, never a wildcard — that one of the definition's hosts names bare
	// or with :443; an empty Header or Format takes the caller's default.
	Host   string `json:"host,omitempty"`
	Header string `json:"header,omitempty"`
	Format string `json:"format,omitempty"`
	// PlainHTTP lets a header credential travel without TLS. That it appears
	// on an org row only is the caller's check.
	PlainHTTP bool `json:"plain_http,omitempty"`
	// Var applies to env: the environment variable name.
	Var string `json:"var,omitempty"`
	// File applies to file: a single name, never a path. The directory is
	// fixed by the runner.
	File string `json:"file,omitempty"`
}

ComponentDelivery says how one secret reaches the run; the mode decides which other fields apply.

type ComponentKind added in v0.8.9

type ComponentKind string

ComponentKind names a thing a run can be given access to. Every kind but ComponentCustom is a closed, built-in kind with its own code; a component enforces nothing itself — it describes primitives policy and the proxy already enforce.

const (
	ComponentAgent         ComponentKind = "agent"
	ComponentModelProvider ComponentKind = "model_provider"
	ComponentGitProvider   ComponentKind = "git_provider" // github | azure_devops
	ComponentGitPAT        ComponentKind = "git_pat"      // other forges
	ComponentSSHKey        ComponentKind = "ssh_key"
	ComponentWorkspace     ComponentKind = "workspace"
	ComponentDrive         ComponentKind = "drive"
	// ComponentCustom is the one open kind, and the only one that is stored.
	ComponentCustom ComponentKind = "custom"
)

type ComponentMCP added in v0.9.0

type ComponentMCP struct {
	// URL is the server's streamable HTTP address: https, no userinfo, its
	// host one of the component's hosts.
	URL       string `json:"url"`
	Transport string `json:"transport"` // http
}

ComponentMCP is a remote MCP server. The run's harnesses never reach URL itself: each is configured with the proxy's broker route, which adds the server's header credential, applies the run's tool rules to its tools and is the only network path to the server.

type ComponentRef added in v0.8.9

type ComponentRef struct {
	ID     *uuid.UUID           `json:"id,omitempty"`
	Inline *ComponentDefinition `json:"inline,omitempty"`
	Name   string               `json:"name,omitempty"`
	// Builtin names a built-in Git provider component, github or azure_devops:
	// API and git-over-HTTPS access to the listed repositories without a
	// workspace, the agent cloning for itself. Org and Repos scope it, Access
	// narrows it.
	Builtin string `json:"builtin,omitempty"`
	// Org is the GitHub owner or the Azure DevOps organisation. Required with Builtin.
	Org string `json:"org,omitempty"`
	// Repos are owner/name (GitHub) or project/repository (Azure DevOps). GitHub
	// requires at least one; an Azure DevOps ref without any reaches every
	// repository the person can reach in Org.
	Repos []string `json:"repos,omitempty"`
	// Access is read or write; empty reads as read.
	Access string `json:"access,omitempty"`
	// MCPHarnesses are the included harnesses (tools[].id) an MCP component's
	// server is turned on for. Absent means every harness that can take it.
	MCPHarnesses []string `json:"mcp_harnesses,omitempty"`
}

ComponentRef attaches one component to a run: a stored one by ID, a run-only definition given inline (never stored as a component), or a built-in Git provider (Builtin; OD-4). Name labels an inline one.

func (ComponentRef) Validate added in v0.8.9

func (r ComponentRef) Validate(validHost func(string) error) error

Validate checks one attachment: exactly one of ID and Inline, and for an inline one its definition, always a person's, and optional label.

type ComponentSecret added in v0.8.9

type ComponentSecret struct {
	SecretName string `json:"secret_name"`
	// Shared marks an org component's secret as the operator's value rather
	// than the launching person's own. Header delivery only (Validate); that
	// it appears on an org row only is the caller's check, since a definition
	// does not know its owner.
	Shared   bool              `json:"shared,omitempty"`
	Delivery ComponentDelivery `json:"delivery"`
}

ComponentSecret is one secret a component carries.

type ComponentSettings added in v0.8.9

type ComponentSettings struct {
	// RequireVaultForCredentials restores the CC3 confinement floor for a
	// component's header credential to a non-baseline host, org and person rows
	// alike. False (default) lifts the floor for component-authored grants only.
	RequireVaultForCredentials bool `json:"require_vault_for_credentials,omitempty"`
	// DenyResidentDelivery refuses env-var and file delivery DEPLOYMENT-WIDE,
	// for org and person rows alike; header delivery keeps working.
	DenyResidentDelivery bool `json:"deny_resident_delivery,omitempty"`
	// AutonomyCap caps a run carrying a SELF-DEFINED component, with or without
	// a governance profile: "" no cap (default), "L1" holds tool calls, "L0"
	// refuses unattended runs. No other value is accepted: the cap only tightens.
	AutonomyCap AutonomyLevel `json:"autonomy_cap,omitempty"`
}

ComponentSettings is SiteConfig.Components. Every field's zero value is the owner-decided default, so an absent block and an all-zero one mean the same thing (PUT /site-config stores the latter as the former).

type ConcurrencyBound added in v0.9.0

type ConcurrencyBound struct {
	Source LimitSource `json:"source"`
	Max    int         `json:"max"`
}

ConcurrencyBound is one source's cap on running runs. The caps are never folded into one number, because each counts a different set: the pool's counts the pool's runs across everyone, governance's one person's runs, the deployment's every run.

type ConfigBindTarget added in v0.9.0

type ConfigBindTarget string

ConfigBindTarget is what a field's value feeds on the component.

const (
	// ConfigBindConfig is a ComponentDefinition.Config key (a plain,
	// non-secret environment value).
	ConfigBindConfig ConfigBindTarget = "config"
	// ConfigBindSecret is the stored secret delivered into one environment
	// variable.
	ConfigBindSecret ConfigBindTarget = "secret"
)

type ConfigBinding added in v0.9.0

type ConfigBinding struct {
	Target ConfigBindTarget `json:"target"`
	// Key is the environment variable name: the config key, or the variable
	// the secret is delivered in. It follows the component env-name rules,
	// including the reserved names.
	Key string `json:"key"`
}

ConfigBinding names the component target a field feeds.

type ConfigCondition added in v0.9.0

type ConfigCondition struct {
	Field  string          `json:"field"`
	Equals json.RawMessage `json:"equals"`
}

ConfigCondition is "Field equals Equals". It is the only expression the schema has.

type ConfigField added in v0.9.0

type ConfigField struct {
	// ID is the field's structural name: lowercase letters, digits and
	// underscores. Values are keyed by it.
	ID          string          `json:"id"`
	Kind        ConfigFieldKind `json:"kind"`
	Label       string          `json:"label"`
	Description string          `json:"description,omitempty"`
	Group       string          `json:"group,omitempty"`
	// Required asks for a value whenever the field is visible and has no default.
	Required bool `json:"required,omitempty"`
	// ReadOnly shows a managed value. It needs a Default, and a value that
	// differs from it is refused.
	ReadOnly bool `json:"read_only,omitempty"`
	// Default is the value an untouched field takes, typed by Kind.
	Default json.RawMessage `json:"default,omitempty"`
	// Min and Max bound an integer or number.
	Min *float64 `json:"min,omitempty"`
	Max *float64 `json:"max,omitempty"`
	// MaxLen bounds a string or each string_list entry, in bytes.
	MaxLen int `json:"max_len,omitempty"`
	// MaxItems bounds a string_list.
	MaxItems int `json:"max_items,omitempty"`
	// Options are an enum's choices.
	Options []ConfigOption `json:"options,omitempty"`
	// VisibleWhen shows the field only while another field holds one value.
	VisibleWhen *ConfigCondition `json:"visible_when,omitempty"`
	Bind        ConfigBinding    `json:"bind"`
}

ConfigField is one setting.

type ConfigFieldKind added in v0.9.0

type ConfigFieldKind string

ConfigFieldKind is the type of one field's value.

const (
	ConfigKindString     ConfigFieldKind = "string"
	ConfigKindBoolean    ConfigFieldKind = "boolean"
	ConfigKindInteger    ConfigFieldKind = "integer"
	ConfigKindNumber     ConfigFieldKind = "number"
	ConfigKindEnum       ConfigFieldKind = "enum"
	ConfigKindStringList ConfigFieldKind = "string_list"
	// ConfigKindSecretRef picks one of the person's stored secrets by name.
	// It never holds a secret value.
	ConfigKindSecretRef ConfigFieldKind = "secret_ref"
)

The closed field kinds. Object and nested kinds are deliberately absent: a field is one value, and layout is groups.

type ConfigGroup added in v0.9.0

type ConfigGroup struct {
	ID          string `json:"id"`
	Label       string `json:"label"`
	Description string `json:"description,omitempty"`
}

ConfigGroup is a labelled section the form lays fields out in.

type ConfigIssue added in v0.9.0

type ConfigIssue struct {
	Path    string `json:"path"`
	Message string `json:"message"`
}

ConfigIssue is one problem in a schema or a set of values.

func ValidateComponentConfigSchema added in v0.9.0

func ValidateComponentConfigSchema(s ComponentConfigSchema) []ConfigIssue

ValidateComponentConfigSchema checks a schema against the closed vocabulary and its bounds.

func ValidateComponentConfigValues added in v0.9.0

func ValidateComponentConfigValues(s ComponentConfigSchema, values ComponentConfigValues, looksSecret func(string) bool) []ConfigIssue

ValidateComponentConfigValues checks one attachment's values against a valid schema. Each supplied value is typed and bounded whether or not its field is visible. A required field is asked for only while visible and without a default; a managed (read-only) field accepts only its default. looksSecret refuses a string that is credential-shaped (a secret reference is a stored secret's name, never its value); this package cannot import the scanner, so the caller passes it, and a nil scanner is itself refused so no caller can opt out of the scan.

type ConfigOption added in v0.9.0

type ConfigOption struct {
	Value string `json:"value"`
	Label string `json:"label"`
}

ConfigOption is one enum choice.

type ConfinementClass

type ConfinementClass string

ConfinementClass declares how strongly a sandbox substrate confines an agent; policy may refuse high-trust credential scopes to weak classes. See threatmodel/.

const (
	// CC1: hardened shared-kernel runc (userns, seccomp, AppArmor, cap-drop).
	CC1 ConfinementClass = "CC1"
	// CC2: gVisor userspace kernel (the default — needs a native Docker engine with runsc registered).
	CC2 ConfinementClass = "CC2"
	// CC3: Kata microVM (requires /dev/kvm).
	CC3 ConfinementClass = "CC3"
)

func (ConfinementClass) Rank

func (c ConfinementClass) Rank() int

Rank orders classes weakest→strongest; unrecognised values rank 0 so a minimum-class gate fails closed. Matching is exact — normalise untrusted input first.

type CredentialDeliveryPolicyDoc added in v0.9.0

type CredentialDeliveryPolicyDoc struct {
	Policy    json.RawMessage `json:"policy"`
	UpdatedAt time.Time       `json:"updated_at"`
	UpdatedBy string          `json:"updated_by"`
}

CredentialDeliveryPolicyDoc is the stored local_credential_delivery document. Policy is the JSON of placement.DeliveryPolicy, validated by the writer; the store keeps it opaque.

type CredentialGrant

type CredentialGrant struct {
	ID        uuid.UUID `json:"id"`
	RunID     uuid.UUID `json:"run_id"`
	CreatedAt time.Time `json:"created_at"`
	Spec      GrantSpec `json:"spec"`
	// Delivery is how the grant reached a local run; empty for a remote run and for one from before 0.9.
	Delivery GrantDelivery `json:"delivery,omitempty"`
}

CredentialGrant records what a run is ELIGIBLE for — not issuance: minting happens only via the broker, and for RequiresApproval grants only inside the transaction that verifies an APPROVED ApprovalRequest for this run+scope.

type CredentialSource added in v0.7.2

type CredentialSource string

CredentialSource says WHOSE credential a git provider's lanes use.

const (
	// CredentialSourceShared: one credential, captured by an admin, backs
	// every run. The zero value, so an unset field keeps legacy behaviour.
	CredentialSourceShared CredentialSource = "shared"
	// CredentialSourcePerUser: one credential per principal, captured or
	// stored under their own namespace.
	CredentialSourcePerUser CredentialSource = "per_user"
)

func (CredentialSource) Valid added in v0.7.2

func (s CredentialSource) Valid() bool

Valid reports whether s is one of the two sources.

type Delegate added in v0.8.0

type Delegate struct {
	ID          uuid.UUID `json:"id"`
	Name        string    `json:"name"`
	IdPClientID string    `json:"idp_client_id"`
	Group       string    `json:"group"`
	// Never serialized: a credential hash has no business leaving the process.
	CredentialSHA256 string     `json:"-"`
	RegisteredBy     string     `json:"registered_by"`
	CreatedAt        time.Time  `json:"created_at"`
	LastUsedAt       *time.Time `json:"last_used_at,omitempty"`
	RevokedAt        *time.Time `json:"revoked_at,omitempty"`
	// Credential is the plaintext portal credential, on the register
	// response ONLY; the row stores its hash.
	Credential string `json:"credential,omitempty"`
}

Delegate is a registered portal (migration 0094, #1142): a trusted front-end that may exchange a signed-in person's own identity-provider token for a short delegated token acting for that person. IdPClientID is the portal's own client id at the identity provider, which a subject token must name; Group is the one group a person must be in for the portal to act for them. Revoking the row (RevokedAt) ends every delegated token it minted at once.

type DelegatedToken added in v0.8.0

type DelegatedToken struct {
	ID              uuid.UUID `json:"id"`
	DelegateID      uuid.UUID `json:"delegate_id"`
	Principal       string    `json:"principal"`
	Email           string    `json:"email,omitempty"`
	UserType        string    `json:"user_type"`
	Groups          []string  `json:"groups"`
	GroupsTruncated bool      `json:"groups_truncated"`
	CreatedAt       time.Time `json:"created_at"`
	ExpiresAt       time.Time `json:"expires_at"`
	// RevSeq is the revocation sequence read when the token was minted; 0 for a token minted before it
	// existed, which a revocation cutoff orders on CreatedAt. Never on the wire.
	RevSeq int64 `json:"-"`
}

DelegatedToken is one live token a Delegate was handed for one person: the identity it replays (always at the user role — a portal never carries admin reach) and when it stops working. Stored only by hash, never refreshed.

type DelegationVia added in v0.8.0

type DelegationVia struct {
	Delegate uuid.UUID `json:"delegate"`
	Grant    uuid.UUID `json:"grant"`
}

DelegationVia names the portal and the delegated token an act was performed through. It is the audit row's data.via and an attach ticket's replayed origin; nil everywhere a person acted for themselves.

type Destination added in v0.8.9

type Destination struct {
	// Host is lower case with no trailing dot; an IP literal is in its
	// canonical spelling. For a wildcard it is the suffix after "*.".
	Host     string
	Wildcard bool
	// Port 0 means any port, as a bare entry does at the proxy.
	Port int
}

Destination is one parsed egress entry — "api.example.com", "*.example.com", either with ":port" — in the one normal form every comparison of a component host against another destination uses: the model-provider veto, the injection-collision check, and dispatch. A gate parses and vetoes a component's hosts BEFORE ComponentDefinition.Validate, so a spelling variant of a vetoed host is refused for what it reaches, not for how it is spelled. The parse is the proxy's own (domainmatch.Classify), so a comparison here can never disagree with what the proxy will match.

func MCPURLDestination added in v0.9.0

func MCPURLDestination(raw string) (Destination, error)

MCPURLDestination checks an MCP server URL and returns its host and port as a destination (Port 0 for the standard https port).

func ParseDestination added in v0.8.9

func ParseDestination(entry string) (Destination, error)

ParseDestination normalises one entry. It refuses only what cannot be compared; whether an entry is valid to author is proxy.ValidDomainEntry's question, asked first.

func (Destination) OverlapsAtAnyPort added in v0.8.9

func (d Destination) OverlapsAtAnyPort(o Destination) bool

OverlapsAtAnyPort reports whether some host the proxy would match against d it would also match against o, whatever the ports. It is the one comparison for both component-host vetoes: a destination a model provider serves must not be reached on any port, and the proxy keys an injected credential by bare host, so a credential on a host collides on every port.

func (Destination) String added in v0.8.9

func (d Destination) String() string

String is the canonical spelling of d; an entry is in normal form when it equals the String of its own parse.

type Device added in v0.8.0

type Device struct {
	ID   uuid.UUID `json:"id"`
	Name string    `json:"name"`
	// Never serialized: a credential hash has no business leaving the process.
	CredentialSHA256 string     `json:"-"`
	EnrolledBy       string     `json:"enrolled_by"`
	CreatedAt        time.Time  `json:"created_at"`
	LastSeenAt       *time.Time `json:"last_seen_at,omitempty"`
	RevokedAt        *time.Time `json:"revoked_at,omitempty"`
	LastSeq          int64      `json:"last_seq"`
	LastRowHash      string     `json:"last_row_hash,omitempty"`
}

Device is one enrolled laptop daemon: an inventory row binding a revocable bearer credential to that device's own audit-federation progress. CredentialSHA256 never reaches a row unhashed; a revoked credential's WHERE RevokedAt IS NULL makes it answer exactly like an unknown one (no oracle). LastSeq/LastRowHash are the chain cursor as ingested so far, verified against each new batch's claimed PrevHash before advancing atomically.

type DeviceAck added in v0.8.0

type DeviceAck struct {
	AckedSeq int64 `json:"acked_seq"`
}

type DeviceEnrolRequest added in v0.8.0

type DeviceEnrolRequest struct {
	Token string `json:"token"`
}

DeviceEnrolRequest/DeviceEnrolResponse are POST /devices/enrol's wire shapes; DeviceAck is the ingest/heartbeat routes' recorded-cursor answer.

type DeviceEnrolResponse added in v0.8.0

type DeviceEnrolResponse struct {
	DeviceID uuid.UUID `json:"device_id"`
	Name     string    `json:"name"`
	Token    string    `json:"token"`
}

type DeviceEnrolmentToken added in v0.8.0

type DeviceEnrolmentToken struct {
	ID          uuid.UUID  `json:"id"`
	TokenSHA256 string     `json:"-"`
	DeviceName  string     `json:"device_name"`
	MintedBy    string     `json:"minted_by"`
	CreatedAt   time.Time  `json:"created_at"`
	ExpiresAt   time.Time  `json:"expires_at"`
	ConsumedAt  *time.Time `json:"consumed_at,omitempty"`
	Token       string     `json:"token,omitempty"` // plaintext, mint response ONLY
}

DeviceEnrolmentToken is a single-use admin-minted token a laptop's first boot exchanges for a Device credential. ConsumeEnrolmentToken's conditional UPDATE ... RETURNING (WHERE ConsumedAt IS NULL) makes redemption single-use.

type DriveBackend added in v0.7.0

type DriveBackend string

DriveBackend names WHERE a drive's bytes live and therefore WHICH runner can mount it. Closed and complete; migration 0054's user_drives.backend CHECK is pinned against these constants.

const (
	DriveBackendDockerVolume DriveBackend = "docker_volume" // per-user Docker named volume, created on first use. Managed, Docker only.
	// DriveBackendHostPath binds <host_root>/<home> from a tree the OPERATOR
	// mounted host-side; Wardyn never holds a share credential. Share, Docker only.
	DriveBackendHostPath DriveBackend = "host_path"
	DriveBackendK8sPVC   DriveBackend = "k8s_pvc" // per-user dynamically-provisioned PVC. Managed, Kubernetes only.
	// DriveBackendK8sPVCStatic is an admin-precreated PVC; Wardyn only ever
	// READS it, a missing claim is a refusal not a create. Share, Kubernetes only.
	DriveBackendK8sPVCStatic DriveBackend = "k8s_pvc_static"
)

func (DriveBackend) Kind added in v0.7.0

func (b DriveBackend) Kind() DriveKind

Kind derives the managed/share split from the backend. An unknown backend reads as a SHARE — the conservative half, since Wardyn never creates or deletes a share.

func (DriveBackend) RunnerTarget added in v0.7.0

func (b DriveBackend) RunnerTarget() string

RunnerTarget is the ONE Config.RunnerTarget value that can mount this backend, or "" — lets the write boundary refuse a k8s drive on a Docker deployment (400) rather than store a row that can never resolve.

func (DriveBackend) Valid added in v0.7.0

func (b DriveBackend) Valid() bool

Valid reports whether b is one of the four backends, mirroring CapabilitySubjectType.Valid.

type DriveKind added in v0.7.0

type DriveKind string

DriveKind is the managed-vs-share split. DERIVED from the backend and never stored: two columns that must agree are two columns that can disagree.

const (
	DriveKindManaged DriveKind = "managed" // Wardyn allocates the object for the member
	DriveKindShare   DriveKind = "share"   // the object exists already and Wardyn only binds it
)

type DriveMount added in v0.7.0

type DriveMount struct {
	// DriveID is the user_drives row this mount came from; labels and reclaim
	// sweeps group by it.
	DriveID    uuid.UUID    `json:"drive_id"`
	Backend    DriveBackend `json:"backend"`
	ObjectName string       `json:"object_name"` // what the substrate is asked for — volume name, PVC name, or absolute host path
	// HostRoot is THIS drive's own share root, carried on a host_path mount
	// so the driver can assert the bind stays inside it. Does NOT replace the
	// deployment's WARDYN_USER_DRIVE_HOST_ROOTS ceiling (that bounds EVERY
	// drive at once; this refuses a link into a different drive's tree). The
	// driver asserts BOTH.
	HostRoot string `json:"host_root,omitempty"`
	// DriveName is the drive's human name, carried for the AUDIT row alone so
	// a member learns which drive without the operator's absolute host path.
	DriveName    string `json:"drive_name,omitempty"`
	StorageClass string `json:"storage_class,omitempty"` // provisioner a managed k8s_pvc claim asks for; carried since a substrate never reads the database
	HomeName     string `json:"home_name"`               // per-user segment ObjectName was built from
	// SubjectHash is the non-PII fingerprint of the principal this mount was
	// resolved for. DEFENCE IN DEPTH over the managed-backend collision the
	// write boundary refuses: a stored row predating that refusal can still
	// resolve two principals onto one volume, so the driver stamps this and
	// refuses the second. A digest, never the claim — a label is echoed by
	// `docker volume inspect` to anybody who can reach the daemon.
	SubjectHash string
	Target      string             `json:"target"` // reserved in-container path (runner.DriveTarget), carried so a runner never hard-codes it
	ReadOnly    bool               `json:"read_only,omitempty"`
	SizeMiB     int                `json:"size_mib,omitempty"`
	Enforcement StorageEnforcement `json:"enforcement"`
}

DriveMount is the RESOLVED answer the runner acts on: one principal's drive, folded with grant overrides and narrowed by their run request, in the shape a substrate can execute without re-reading a row.

A SEPARATE type from ResolvedDrive: that one is admin-facing and carries the whole drive/grant rows; this is run-facing and carries only what a mount needs. ReadOnly rather than Writable, since every mount type the substrates speak says ReadOnly. Does NOT ride RunPolicySpec.WorkspaceMounts: a drive is per-PRINCIPAL and a workspace mount is per-ROW.

type DriveObjectScheme added in v0.8.0

type DriveObjectScheme string

DriveObjectScheme names which half of DriveObjectName's minted form carries the drive — the variable-width slug (migration 0054, `wardyn-drive-<drive-slug>-<home>`) or the drive's own fixed-width id (migration 0067, `wardyn-drive-<drive-id-hex>-<home>`). Closed; pinned against user_drives.object_scheme's CHECK.

IT IS NOT CLIENT-AUTHORED. store.UpsertUserDrive derives it entirely and never reads this field off the request — neither substrate can rename a storage object, so a row keeps whatever scheme it was created under. It rides the wire only so a GET and its round-tripped PUT agree; driveIdentityFields compares it, so a forged flip answers 409.

const (
	// DriveObjectSchemeSlug is the original form: DriveSlug at a variable
	// offset before <home>. The column's DEFAULT; every pre-0067 row reads as
	// this and stays this permanently (DriveSlug is not injective).
	DriveObjectSchemeSlug DriveObjectScheme = "slug"
	// DriveObjectSchemeID is the drive's own UUID, dashless (DriveObjectID),
	// at a FIXED offset before <home>. Every row created from 0067 onward
	// gets this unconditionally.
	DriveObjectSchemeID DriveObjectScheme = "id"
)

func (DriveObjectScheme) Valid added in v0.8.0

func (s DriveObjectScheme) Valid() bool

Valid reports whether s is one of the two schemes.

type DriveReclaim added in v0.7.0

type DriveReclaim string

DriveReclaim is the DECLARED INTENT for a drive's objects when a grant goes away. v1 executes it by documented operator command, not by code: nothing in the control plane deletes a volume or a PVC.

const (
	DriveReclaimRetain DriveReclaim = "retain" // keeps the object after the grant is removed — the default
	DriveReclaimDelete DriveReclaim = "delete" // records that the object SHOULD be reclaimed — a runbook note, not an action
)

func (DriveReclaim) Valid added in v0.7.0

func (r DriveReclaim) Valid() bool

Valid reports whether r is one of the two reclaim intents.

type EffectiveLimit added in v0.9.0

type EffectiveLimit struct {
	Default       LimitAmount `json:"default"`
	Cap           LimitAmount `json:"cap"`
	DefaultSource LimitSource `json:"default_source,omitempty"`
	CapSource     LimitSource `json:"cap_source,omitempty"`
}

EffectiveLimit is one field's answer: what an untouched run gets, the most a request may ask for, and which source bound each. A source is absent when nothing bounded the value (an unlimited cap, or an unlimited default).

func (EffectiveLimit) Apply added in v0.9.0

func (l EffectiveLimit) Apply(asked int) (applied LimitAmount, capped bool)

Apply is a request's value against the limit: 0 asks for the default, a value within the cap is kept, and one past it is reduced to the cap with capped set. Reduced, never refused, as a request's CPU and memory are today.

type EffectiveRunLimits added in v0.9.0

type EffectiveRunLimits struct {
	RunType   RunnerPoolRunType `json:"run_type"`
	CPUMillis EffectiveLimit    `json:"cpu_millis"`
	MemoryMiB EffectiveLimit    `json:"memory_mib"`
	// DiskMiB is the main drive's size in MiB. An unlimited Default means an untouched
	// run gets no size (its scratch is unbounded); with no default but a cap, an
	// untouched run gets the cap (foldField's rule).
	DiskMiB       EffectiveLimit     `json:"disk_mib"`
	Lifetime      EffectiveLimit     `json:"lifetime_sec"`
	LifetimeSpan  EffectiveLimit     `json:"lifetime_span_sec"`
	LifetimeLease EffectiveLimit     `json:"lifetime_lease_sec"`
	NoEndAllowed  bool               `json:"no_end_allowed"`
	Idle          *EffectiveLimit    `json:"idle_sec,omitempty"`
	Concurrency   []ConcurrencyBound `json:"concurrency"`
}

EffectiveRunLimits is what a run of one type gets in one pool for one person, as the preview and the launch both read it. Idle is nil for a Background run: it has no idle stop.

Lifetime caps a FINITE end and is the one number a create reads. It is folded from two bounds that an extension must read apart, because they are measured from different moments: LifetimeSpan (the pool, the deployment and the runner) counts from the run's start, and a run past it is KILLED; LifetimeLease (the person's governance) counts from the moment an end is asked for. A new run's end is bounded by the lesser of the two; an extension's by the lesser of start + LifetimeSpan.Cap and now + LifetimeLease.Cap. NoEndAllowed says whether the run may have no end at all, which is a separate yes: every source must allow it, and governance allows it only when its profile does, whatever finite maximum it also sets.

type EgressRedirect added in v0.5.0

type EgressRedirect struct {
	// From is the public/upstream URL or host being redirected away from.
	From string `json:"from"`
	// To is the corporate-internal URL or host every matching run's egress
	// substitutes to; for an Ecosystem row it's also emitted into that
	// ecosystem's config file (the old ArtifactOverride.BaseURL).
	To string `json:"to"`
	// TokenSecretRef optionally names a secret injected proxy-side as a
	// Bearer token for To's host (the sandbox never holds it). Mutually
	// exclusive with TokenIntegrationRef.
	TokenSecretRef string `json:"token_secret_ref,omitempty"`
	// TokenIntegrationRef optionally names an Integration
	// (SiteConfig.Integrations[i].ID) to take this redirect's token FROM
	// instead of a bare secret. The redirect points AT the integration
	// rather than restating its secret, inheriting its Header/Format so a
	// feed authenticating with something other than "Authorization: Bearer"
	// finally can. Naming nothing, a disabled row, or one with no header
	// credential degrades to redirect-without-token.
	TokenIntegrationRef string `json:"token_integration_ref,omitempty"`
	// Ecosystem, when set, is one of six package-manager ecosystems
	// ("npm"|"pip"|"cargo"|"maven"|"go"|"nuget") this redirect ALSO emits a
	// config file for. Empty means NETWORK-ONLY: no config file is emitted.
	Ecosystem string `json:"ecosystem,omitempty"`
}

EgressRedirect is one outbound redirect: requests to From are substituted to To (From's host dropped from egress, To's host allowed), with an optional token injected proxy-side for To's host. From/To share ArtifactOverride.BaseURL's validation discipline — keep any future change there in sync, since workspacescan.EmitArtifactConfig relies on it for raw string interpolation into .npmrc/settings.xml/etc.

type EphemeralProvider added in v0.7.2

type EphemeralProvider struct {
	DefaultDiskMiB int `json:"default_disk_mib,omitempty"` // 0 = unset
	// MaxDiskMiB: ceiling a larger request is clamped to (0 = unlimited); a
	// DefaultDiskMiB above it is refused at the write boundary.
	MaxDiskMiB int `json:"max_disk_mib,omitempty"`
}

EphemeralProvider is the ephemeral-scratch policy: default size, and the ceiling a larger request is clamped to (never refused).

type FederatedAuditEvent added in v0.8.0

type FederatedAuditEvent struct {
	AuditEvent
	Seq int64 `json:"seq"`
}

FederatedAuditEvent is one audit row plus Seq, its local table's own sequence for the row. Seq alone is not identity — a table reset can restart it — so a replay is recognised by seq together with RowHash.

type FirstUseMode

type FirstUseMode string

FirstUseMode controls how the egress proxy handles an UNKNOWN domain — one that is neither explicitly allowed nor denied — under an allowlist policy. It widens the legacy first_use_approval boolean into three explicit modes while staying wire-compatible: UnmarshalJSON still accepts the old bool (true => deny_with_review, false => always_deny). Inert under allow-all egress.

const (
	// FirstUseAlwaysDeny hard-denies an unknown domain and logs it, without ever
	// raising it for human approval. (legacy first_use_approval=false)
	FirstUseAlwaysDeny FirstUseMode = "always_deny"
	// FirstUseDenyWithReview raises a pending approval and denies the in-flight
	// request immediately; once a human approves, a later retry passes. The
	// sandbox connection is never held open. (legacy first_use_approval=true)
	FirstUseDenyWithReview FirstUseMode = "deny_with_review"
	// FirstUseWaitForReview raises a pending approval and HOLDS the connection
	// open until it is approved/denied or the proxy's hold deadline passes — the
	// request transparently completes if approved in time. On deadline it fails
	// closed (403) with the approval left pending, degrading to deny_with_review.
	FirstUseWaitForReview FirstUseMode = "wait_for_review"
)

func (FirstUseMode) Normalize

func (m FirstUseMode) Normalize() FirstUseMode

Normalize maps an empty or unrecognised value to always_deny (fail closed, matching the legacy boolean zero value).

func (FirstUseMode) RaisesApproval

func (m FirstUseMode) RaisesApproval() bool

RaisesApproval reports whether an unknown domain is escalated to a human rather than hard-denied (true for both review modes).

func (*FirstUseMode) UnmarshalJSON

func (m *FirstUseMode) UnmarshalJSON(b []byte) error

UnmarshalJSON accepts BOTH the legacy boolean form (true => deny_with_review, false => always_deny) and the new string form, so old stored policies whose first_use_approval is a JSON boolean keep decoding without a migration.

func (FirstUseMode) Valid

func (m FirstUseMode) Valid() bool

Valid reports whether m is empty (unset => default always_deny) or one of the three known modes. Used to reject a hand-authored policy with a garbage value at the API boundary; runtime reads still fail closed via Normalize.

type GitLane added in v0.7.2

type GitLane string

GitLane is one of the credential lanes a run can clone with. A provider row's Lanes only veto lanes; they never mint or create a new credential.

const (
	GitLaneApp   GitLane = "app"   // GitHub App broker lane (github.com only)
	GitLanePAT   GitLane = "pat"   // stored git-pat-<slug> lane
	GitLaneSSH   GitLane = "ssh"   // stored ssh-key-<slug> lane
	GitLaneEntra GitLane = "entra" // Microsoft Entra lane: run authorizes as the person, against the org's own tenant; Azure DevOps hosted only
)

func (GitLane) Legacy added in v0.7.10

func (l GitLane) Legacy() bool

Legacy reports whether l is admitted by an empty Lanes list; expanding one must ask this, never ClosedGitLanes.

func (GitLane) Valid added in v0.7.2

func (l GitLane) Valid() bool

Valid reports whether l is one of the closed lanes.

type GitPATScope added in v0.8.6

type GitPATScope struct {
	Host       string    `json:"host"`
	SecretName string    `json:"secret_name"`
	Username   string    `json:"username,omitempty"`
	Repos      *[]string `json:"repos,omitempty"`
	Access     string    `json:"access,omitempty"`
	API        bool      `json:"api,omitempty"`
	Forge      string    `json:"forge,omitempty"`
}

GitPATScope is the one decoded shape of a git_pat grant scope. api, broker and composer all read it, so the contract is defined once.

The four narrowing axes have explicit omission defaults (Normalize):

repos   absent = every repository the PAT reaches; an EMPTY list = none
access  write
api     false
forge   generic

Repos is a pointer to a slice so "absent" (nil) and "empty" (non-nil, no entries) stay distinct. Reading an empty list as unset would turn an empty intersection into an unnarrowed grant.

Wardyn narrows the run, not the PAT: the credential itself keeps whatever scope the forge issued it with.

func DecodeGitPATScope added in v0.8.6

func DecodeGitPATScope(raw json.RawMessage) (GitPATScope, error)

DecodeGitPATScope is the lenient read: unknown keys are ignored, so a stored row with a stray key still loads and launches. host and secret_name are required (fail closed). The result is normalized.

func DecodeGitPATScopeStrict added in v0.8.6

func DecodeGitPATScopeStrict(raw json.RawMessage) (GitPATScope, error)

DecodeGitPATScopeStrict is the write-time decode, for request bodies and the boot policy file. Beyond DecodeGitPATScope it refuses an unknown key (a typo such as "repo" would otherwise read as an omission, and an omission means unnarrowed), an access or forge outside its enum, a malformed repos entry, and api: true on the generic forge, which has no API table. Whether a Bitbucket Server API grant is allowed is a deployment flag, which the api package checks at write.

func (GitPATScope) Narrowed added in v0.8.6

func (s GitPATScope) Narrowed() bool

Narrowed reports whether the scope narrows the run below the PAT: it sets repos (an empty list included), sets access read, or sets api true. Call it on a normalized scope.

func (GitPATScope) Normalize added in v0.8.6

func (s GitPATScope) Normalize() GitPATScope

Normalize applies the omission defaults and fails closed on a value outside an enum: an unknown access reads as read, an unknown forge as generic (the strictest path identity). Strict decoding refuses both before they can be stored; this only matters for a row that predates the check. Repos is returned as is, because absent and empty mean different things.

func (GitPATScope) SetsAnyAxis added in v0.8.6

func (s GitPATScope) SetsAnyAxis() bool

SetsAnyAxis is Narrowed, or a forge other than generic: the test for a scope a lane that ignores the four axes must refuse rather than carry silently.

type GitProvider added in v0.7.2

type GitProvider struct {
	ID   string          `json:"id"`   // operator-chosen stable slug, unique within Git, held to integrationRefRE; a capability grant names a provider by this id
	Kind GitProviderKind `json:"kind"` // which forge's bespoke behaviour this row carries
	// Disabled turns the row off without deleting it (zero value enabled); a
	// disabled row still claims its hosts rather than falling through to ScmHosts.
	Disabled bool `json:"disabled,omitempty"`
	// BaseURLs are the https addresses this row admits: a repo is admitted
	// when its clone URL's scheme/host match one of these and its path equals
	// or extends that base URL's path at a "/" boundary. 1-8 entries; no
	// port, credentials, query or fragment; at most two path segments.
	BaseURLs []string `json:"base_urls"`
	// Lanes are the credential lanes usable for this provider; empty means
	// every legacy lane (LegacyGitLanes) — the field narrows, never widens.
	Lanes []GitLane `json:"lanes,omitempty"`
	// GitHubAppInstallURL is operator-configured, nonsecret installation
	// metadata for this row's App lane. It grants no access and is disclosed
	// by run facts only to operators, never ordinary members.
	GitHubAppInstallURL string           `json:"github_app_install_url,omitempty"`
	CredentialSource    CredentialSource `json:"credential_source,omitempty"` // whose credential the lanes use; empty = CredentialSourceShared, CredentialSourcePerUser requires GitLaneEntra, or GitLanePAT on an Azure DevOps row (a Server row, which has no Entra sign-in)
	Entra               *ADOEntraConfig  `json:"entra,omitempty"`             // present only on, and required by, a row whose Lanes name GitLaneEntra
}

GitProvider is one git-provider row: a kind, the base URLs it admits, and the credential lanes it permits.

type GitProviderKind added in v0.7.2

type GitProviderKind string

GitProviderKind is the closed set of git forges Wardyn has bespoke behaviour for; a self-hosted forge is a github (GHES) or azure_devops (ADO Server) row, not a third kind.

const (
	GitProviderGitHub      GitProviderKind = "github"       // github.com or GitHub Enterprise Server
	GitProviderAzureDevOps GitProviderKind = "azure_devops" // dev.azure.com, org.visualstudio.com, or ADO Server
)

func (GitProviderKind) Valid added in v0.7.2

func (k GitProviderKind) Valid() bool

Valid reports whether k is one of the closed kinds.

type GovernanceAssignment added in v0.7.0

type GovernanceAssignment struct {
	ID          uuid.UUID             `json:"id"`
	SubjectType CapabilitySubjectType `json:"subject_type"`
	Subject     string                `json:"subject"`
	ProfileID   uuid.UUID             `json:"profile_id"`
	Priority    int                   `json:"priority"`
	CreatedAt   time.Time             `json:"created_at"`
	CreatedBy   string                `json:"created_by,omitempty"`
}

GovernanceAssignment binds one profile to one subject (migration 0052's governance_assignments row).

SubjectType REUSES CapabilitySubjectType — the same vocabulary capability_grants is written against; a second enum for the same thing is the dual-matcher drift this codebase warns against elsewhere.

Priority breaks ties WITHIN a tier (higher wins), the group tier's working lever since a member is often in several groups. Never crosses tiers: a user-tier row beats every group-tier row at any priority.

type GovernanceChange added in v0.8.6

type GovernanceChange struct {
	ID         uuid.UUID       `json:"id"`
	TargetKind string          `json:"target_kind"`
	Op         string          `json:"op"`
	TargetKey  string          `json:"target_key"`
	State      string          `json:"state"`
	ProposedBy string          `json:"proposed_by"`
	ProposedAt time.Time       `json:"proposed_at"`
	ExpiresAt  time.Time       `json:"expires_at"`
	Diff       json.RawMessage `json:"diff"`
	DecidedBy  string          `json:"decided_by,omitempty"`
	DecidedAt  *time.Time      `json:"decided_at,omitempty"`
	// Reason is a rejection's optional explanation.
	Reason string `json:"reason,omitempty"`

	// Payload, hashes and emails stay server-side.
	Payload         json.RawMessage `json:"-"`
	BaseHash        string          `json:"-"`
	DeploymentHash  string          `json:"-"`
	ProposedByEmail string          `json:"-"`
	DecidedByEmail  string          `json:"-"`
}

GovernanceChange is one governance write held for a second human (migration 0126). Payload is the validated request, replayed on approval; Diff is the server-rendered before/after the reviewer reads. Neither carries a secret value.

The four personal fields (ProposedBy, ProposedByEmail, DecidedBy, DecidedByEmail) are cleared by the audit_personal_fields erasure scope. The emails never reach a response: they exist for the distinct-human comparison and the erasure match only.

type GovernanceLimits added in v0.7.0

type GovernanceLimits struct {
	// LocalSelfDefinedComponents permits a local run to carry a component its launcher defined.
	// Omitted or false refuses it; runner-reported confinement is not platform attestation.
	LocalSelfDefinedComponents bool `json:"local_self_defined_components,omitempty"`
	// DenyTaskModeExec refuses task_mode=exec for a member under this profile.
	DenyTaskModeExec bool `json:"deny_task_mode_exec,omitempty"`
	// DenyInteractive refuses an interactive run under this profile.
	// Evaluated against POST-COERCION interactivity, since omitting the
	// task coerces to interactive later — a raw "did the caller ask" check
	// would be evaded by leaving the field out. It also refuses the terminal
	// attach and SSH into a run created under this profile, since a task or
	// exec run still has a sandbox to open a shell in (#1392).
	DenyInteractive bool `json:"deny_interactive,omitempty"`
	// DenyUIApps strips ui_apps from a run under this profile at create, and
	// the UI gateway refuses a session into a run created under it. A limit
	// rather than an empty ceiling ui_apps, which stays "no opinion" so
	// existing profiles keep their behaviour (#1391).
	DenyUIApps bool `json:"deny_ui_apps,omitempty"`
	// MaxConcurrentRuns caps how many NON-TERMINAL runs a member under this
	// profile may hold at once. 0 is unlimited.
	MaxConcurrentRuns int `json:"max_concurrent_runs,omitempty"`
	// DenyUserDrive refuses a USER DRIVE mount under this profile, whatever
	// an admin allocated. A DOOR, NOT A QUOTA: a drive outlives the run, so
	// "may this principal persist anything at all" is the question (403 +
	// authz.denied), not "how big".
	DenyUserDrive bool `json:"deny_user_drive,omitempty"`
	// DenyMemberDrives refuses CREATING a member drive under this profile, on top of
	// storage.user_drive.member_create (the org switch). A DOOR with no zero-value
	// opinion: false inherits the switch, so an unset profile changes nothing. DenyUserDrive
	// also blocks creation (a person who may mount no drive may not make one); this door
	// narrows only the creating. Operators are exempt from both doors.
	DenyMemberDrives bool `json:"deny_member_drives,omitempty"`
	// DenyPlacements refuses a run whose resolved placement is listed (403 placement_denied +
	// authz.denied). A DENY LIST: absent means every placement is permitted, including for a
	// member with no assigned profile. Values are `remote` and `local`; a profile denying both is
	// refused at write, since the people it binds could run nothing.
	DenyPlacements []Placement `json:"deny_placements,omitempty"`
	// MaxCPUMillis and MaxMemoryMiB cap the sandbox's CPU (millicores) and memory a run under this
	// profile may be given, whatever its ceiling's own resources or a member's request say. 0 is
	// unlimited. A CLAMP, NOT A DOOR, like MaxEphemeralDiskMiB. Folded into composer.Clamp at
	// create and preflight and re-applied at dispatch; assigned members only, operators exempt.
	MaxCPUMillis int `json:"max_cpu_millis,omitempty"`
	MaxMemoryMiB int `json:"max_memory_mib,omitempty"`
	// MaxEphemeralDiskMiB caps the EPHEMERAL scratch (the writable layer when no drive is
	// mounted) a run under this profile may be given. 0 is unlimited. A CLAMP, NOT A DOOR: no
	// authz.denied at the bound. Enforced at dispatch only (runs_dispatch.go), folded with the
	// deployment's own storage.ephemeral.max_disk_mib; assigned members only, operators exempt.
	// Whether it actually binds depends on the substrate (Docker's overlay2 doesn't enforce
	// size at all) — render through StorageEnforcement, never claim a cap the substrate doesn't keep.
	MaxEphemeralDiskMiB int `json:"max_ephemeral_disk_mib,omitempty"`
	// MaxDriveSizeMiB caps how large a USER DRIVE may be under this profile. 0 is unlimited —
	// DenyUserDrive's twin ("may persist" vs "how much"). PER PRINCIPAL: a drive is one tree
	// belonging to one subject; a team-shared drive's ceiling is not the sum of its members'
	// and can't be derived from one, so sharing (0.8) adds a scope axis instead. Clamped in
	// newResolvedDrive, the one place holding both facts — never at grant write, where the
	// subject is claims-resolved and unreadable from the row. Folds with
	// storage.user_drive.max_size_mib in one min(), so launch, /me and preview can't disagree.
	MaxDriveSizeMiB int `json:"max_drive_size_mib,omitempty"`
	// AutonomyRubric maps a run's posture to a permitted AutonomyLevel
	// (0.8, #77). A POINTER so an unset rubric doesn't put
	// `"autonomy_rubric":{}` on every profile's wire body. Nil means "no
	// rubric" (resolveRunAutonomy, #97, treats it like no assigned profile).
	AutonomyRubric *AutonomyRubric `json:"autonomy_rubric,omitempty"`
	// RunLimits is embedded, so its seven fields sit flat on the limits wire
	// object beside the ones above — the same field set a run captures at
	// create (AgentRun.RunLimits).
	RunLimits
}

GovernanceLimits carries the autonomy switches that BOUND A REQUEST rather than a policy (RunPolicySpec bounds what a sandbox may reach once running, enforced by the proxy). These refuse a run SHAPE before it exists, or (DenyUserDrive) leave state behind:

  • DenyTaskModeExec: task_mode=exec has no agent and no toolgate, so no tool_rules ceiling binds it.
  • DenyInteractive: an interactive run refuses tool_approvals=hold by design, so tool_rules can't express "supervised" there either.
  • DenyUIApps: a UI app is relay access into the sandbox that no tool_rules ceiling sees.
  • DenyUserDrive: a drive OUTLIVES the run, so no tool_rules ceiling describes the escape — the storage itself is the escape.
  • DenyPlacements: a placement the people under this profile may not run on; a deny list, so absent means permitted.
  • MaxConcurrentRuns is a QUOTA, not a door: 422 with no authz.denied, not 403.
  • MaxCPUMillis/MaxMemoryMiB/MaxEphemeralDiskMiB/MaxDriveSizeMiB are neither: they CLAMP — capped and told so, never refused (disk_mib is policy-authored, so refusing would break every stored policy the day a limit is first written).

A CLOSED struct with `omitempty` on every field, not a map: small, complete, validated by the Go type itself, no DB CHECK needed. Every zero value means "unrestricted" except LocalSelfDefinedComponents (default deny), so an omitted limits object behaves exactly as before this struct existed.

type GovernanceProfile added in v0.7.0

type GovernanceProfile struct {
	ID        uuid.UUID        `json:"id"`
	Name      string           `json:"name"`
	Ceiling   RunPolicySpec    `json:"ceiling"`
	Limits    GovernanceLimits `json:"limits"`
	CreatedAt time.Time        `json:"created_at"`
	UpdatedAt time.Time        `json:"updated_at"`
	CreatedBy string           `json:"created_by,omitempty"`
	// Contact is who owns the profile and how to ask for a change; nil is none.
	// Validated on write and re-validated on every read (internal/policyref).
	// Never inherited from a base: disclosing a base's contact would disclose the base.
	Contact *policyref.Contact `json:"contact,omitempty"`
	// ContactSet is a write instruction, never stored or serialized: true makes an
	// upsert write Contact (nil clears it), false keeps the stored value.
	ContactSet bool `json:"-"`
	// BaseProfileID is the profile this one composes on; nil with a non-nil Overlay means the
	// deployment default. Only a composed profile has one.
	BaseProfileID *uuid.UUID `json:"base_profile_id,omitempty"`
	// Overlay marks the profile composed. A non-nil pointer to an empty overlay is valid: it is the
	// base unchanged.
	Overlay *CeilingOverlay `json:"overlay,omitempty"`
	// OverlayLimits narrows the base's limits. Only with an Overlay; nil is no narrowing.
	OverlayLimits *LimitsOverlay `json:"overlay_limits,omitempty"`
}

GovernanceProfile is one named, assignable ceiling (the governance_profiles row of migrations 0052 and 0125).

A STANDALONE profile (Overlay nil) is a full RunPolicySpec with replacement semantics: an assigned profile IS the principal's ceiling; with no assignment a principal falls through to Config.DefaultPolicy byte for byte. A COMPOSED profile (Overlay set) stores no raw Ceiling or Limits, only a base (BaseProfileID, or the deployment default when nil) and an overlay that can only narrow it; the effective ceiling is composer.ApplyOverlay over the resolved base, computed whenever authority is read (internal/api/governance_compose.go is the only reader). composer.Clamp is NOT that meet (it drops workspace_mounts unconditionally, is order-dependent through llm_inspection and defaults unnamed tools to hold), so composition has its own function.

Name is the UNIQUE human handle: what an admin assigns by, what the console lists, and the resolver's last ORDER BY tie-break.

func (GovernanceProfile) Composed added in v0.8.6

func (p GovernanceProfile) Composed() bool

Composed reports whether the profile is a base plus an overlay rather than a standalone ceiling.

type GrantDelivery added in v0.9.0

type GrantDelivery string

GrantDelivery is how one grant reached a run (credential_grants.delivery).

const (
	GrantDeliveryNone     GrantDelivery = ""
	GrantDeliveryOwn      GrantDelivery = "own"
	GrantDeliveryViaOrg   GrantDelivery = "via_org"
	GrantDeliveryResident GrantDelivery = "runner_resident"
)

type GrantKind

type GrantKind string

GrantKind enumerates broker-mintable credential kinds.

const (
	GrantGitHubToken GrantKind = "github_token"
	GrantCloudSTS    GrantKind = "cloud_sts" // HARD-REQUIRES SPIRE identity provider
	GrantAPIKey      GrantKind = "api_key"   // proxy-side injection only
	// GrantGitPAT returns a stored PAT to the git credential helper for a non-GitHub host (ADO/GitLab), an opaque CONNECT tunnel the proxy can't inject into.
	GrantGitPAT GrantKind = "git_pat"
	// GrantSSHKey materializes a resident, agent-readable SSH private key for git-over-SSH — a DOCUMENTED EXCEPTION to the no-resident-secret invariant
	// (no credential-helper seam for SSH). agent-run writes it 0400 just before the clone and wipes it right after; residual risk is code running as the
	// agent uid during that window. See broker.mintSSHKey.
	GrantSSHKey GrantKind = "ssh_key"
	// GrantEnvSecret places a stored secret into the sandbox environment at dispatch — the second DOCUMENTED EXCEPTION to the no-resident-secret invariant.
	// Unlike every other kind: NOT BROKERED (no mint/approval/TTL/JTI); RESIDENT FOR THE WHOLE RUN (readable via /proc/self/environ as the agent uid);
	// NO REVOCATION (killing the run doesn't un-disclose it). ADMIN-ONLY by default — see threatmodel/THREAT-MODEL.md §5.1a.
	GrantEnvSecret GrantKind = "env_secret"
	// GrantFileSecret places a stored secret in a FILE inside the sandbox at dispatch — the third DOCUMENTED EXCEPTION to the no-resident-secret invariant.
	// Like GrantEnvSecret: NOT BROKERED (no mint/approval/TTL/JTI), RESIDENT FOR THE WHOLE RUN, NO REVOCATION (killing the run doesn't un-disclose it).
	// The path is fixed, runner.ComponentSecretDir plus the scope's file name, never authored. ADMIN-ONLY by default for a policy author, under the same
	// switch as GrantEnvSecret; a person's OWN secret reaches a run this way through a custom component, under the component permission.
	GrantFileSecret GrantKind = "file_secret"
)

type GrantSpec

type GrantSpec struct {
	Kind GrantKind `json:"kind"`
	// Scope is kind-specific: {repos,permissions} for github_token; {host,header} for api_key;
	// {host,secret_name,username?} for git_pat; {host,key_secret_ref,username?,known_hosts_secret_ref?} for ssh_key;
	// {name,secret_name} for env_secret; {file,secret_name} for file_secret.
	Scope json.RawMessage `json:"scope"`
	// TTL of the minted credential. Max (and default) 1h.
	TTLSeconds int `json:"ttl_seconds,omitempty"`
	// RequiresApproval forces a human approval to mint (vs auto-mint on policy).
	RequiresApproval bool `json:"requires_approval"`
	OwnerOnly        bool `json:"owner_only,omitempty"` // stored secret from the run owner's own row only, never the operator's
}

GrantSpec is a credential scope description. The broker enforces the invariant: a minted credential's scope is exactly the approved scope — never wider.

type HarnessCredential added in v0.9.0

type HarnessCredential struct {
	Harness string `json:"harness"`
	// SHA256 is the lower-case hex digest of the credential.
	SHA256 string `json:"sha256"`
}

HarnessCredential binds one included harness to the SHA-256 of the random credential minted for it at dispatch. The proxy config carries only the digest; the plaintext goes to that harness's own process (WARDYN_TOOLGATE_CREDENTIAL) and nowhere else, so the approval route can name the harness from a value only that harness was given, never from the request body.

type HarnessToolRules added in v0.9.0

type HarnessToolRules struct {
	Harness string `json:"harness"`
	// Default is the effect for a tool no rule names. Empty means the harness
	// has no default, and an unmatched tool is held for a human.
	Default ToolEffect `json:"default,omitempty"`
	Rules   []ToolRule `json:"rules,omitempty"`
}

HarnessToolRules is the tool rules of one included harness and its own default effect (the "*" rule). The approval broker's lookup key is Harness: the id of the included tool that raised the request, which for a harness is its agent name.

The harness id MUST come from an identity the proxy binds itself, never from the body of the approval request: every process in the sandbox can post to the proxy's local approvals route, so a harness read from the body lets one tool borrow a sibling's allow. The binding is a per-included-tool local credential minted at dispatch and injected only into that harness's own hook configuration; A-L3 lands it. Until it does, the run door keeps refusing more than one included tool and any per-tool rule or default effect (runModeUnavailable), so no run has a second harness to confuse.

Like PushRuleSet, the sets are the product after the ceiling: policy and ceiling rules are laid into each harness's set first, and a request's per-tool rules may only narrow them (hold or deny; a rule that allows is refused), so an administrator's deny survives a person's allow.

type HomeTemplate added in v0.7.0

type HomeTemplate string

HomeTemplate names WHICH identity a drive's per-user home name is derived from. Closed; pinned against user_drives.home_template's CHECK.

const (
	// HomeTemplateHash is `d-` + a truncated sha256 over the drive id and the
	// subject: deterministic, DNS-1123-safe, leaks no identity. The default,
	// and the ONLY template a MANAGED backend may use — ValidateUserDrive
	// refuses the others on managed.
	HomeTemplateHash HomeTemplate = "hash"
	// HomeTemplateSub uses the sign-in subject claim verbatim, lowercased.
	// SHARE backends only: two subjects differing only in case collapse to
	// one home.
	HomeTemplateSub HomeTemplate = "sub"
	// HomeTemplateEmailLocal uses the part of the email claim before the "@"
	// (alice@corp -> alice). SHARE backends only: the domain is dropped, so
	// alice@corp.example and alice@partner.example name ONE directory — the
	// answer is a home_override on the colliding person's allocation.
	//
	// THERE IS DELIBERATELY NO WHOLE-EMAIL TEMPLATE: driveHomeSegmentRe
	// excludes "@", so it could only ever resolve for a claim that was not an
	// address; anything else is a per-user home_override.
	HomeTemplateEmailLocal HomeTemplate = "email_local"
)

func (HomeTemplate) Valid added in v0.7.0

func (t HomeTemplate) Valid() bool

Valid reports whether t is one of the three templates.

type ImageBuild added in v0.9.0

type ImageBuild struct {
	Schema        int              `json:"schema"`
	Lane          string           `json:"lane"`
	CacheKey      string           `json:"cache_key"`
	BuildID       string           `json:"build_id"`
	Reason        string           `json:"reason"`
	BuiltAt       time.Time        `json:"built_at"`
	WardynVersion string           `json:"wardyn_version"`
	Resolved      bool             `json:"resolved"`
	Inputs        ImageBuildInputs `json:"inputs"`
	Image         ImageBuildImage  `json:"image"`
}

ImageBuild is the record of what built a workspace's image. An empty string means "not known"; a field is never guessed.

func (ImageBuild) SHA256 added in v0.9.0

func (r ImageBuild) SHA256() (string, error)

SHA256 is the hex SHA-256 of the record's encoding/json bytes. It is computed, never stored.

type ImageBuildFeature added in v0.9.0

type ImageBuildFeature struct {
	Ref     string         `json:"ref"`
	Digest  string         `json:"digest"`
	Options map[string]any `json:"options"`
}

ImageBuildFeature is a dev container feature; Options is what the generator wrote.

type ImageBuildGenerator added in v0.9.0

type ImageBuildGenerator struct {
	Salt        string            `json:"salt"`
	FilesSHA256 map[string]string `json:"files_sha256"`
}

ImageBuildGenerator names the generator salt and the hash of each file it built.

type ImageBuildImage added in v0.9.0

type ImageBuildImage struct {
	Ref string `json:"ref"`
	ID  string `json:"id"`
}

ImageBuildImage is the build's own tag and its image ID.

type ImageBuildInputs added in v0.9.0

type ImageBuildInputs struct {
	Base       ImageBuildRef         `json:"base"`
	Features   []ImageBuildFeature   `json:"features,omitempty"`
	Installers []ImageBuildInstaller `json:"installers,omitempty"`
	Generator  *ImageBuildGenerator  `json:"generator,omitempty"`
	Repo       *ImageBuildRepo       `json:"repo,omitempty"`
}

ImageBuildInputs are the build's unpinned-or-pinned inputs, per lane.

type ImageBuildInstaller added in v0.9.0

type ImageBuildInstaller struct {
	Tool     string `json:"tool"`
	Channel  string `json:"channel"`
	Version  string `json:"version"`
	Platform string `json:"platform"`
	SHA256   string `json:"sha256"`
}

ImageBuildInstaller is a tool the generated Dockerfile installs.

type ImageBuildRef added in v0.9.0

type ImageBuildRef struct {
	Ref    string `json:"ref"`
	Digest string `json:"digest"`
}

ImageBuildRef is an image reference and the digest it resolved to.

type ImageBuildRepo added in v0.9.0

type ImageBuildRepo struct {
	URL string `json:"url"`
	Ref string `json:"ref"`
}

ImageBuildRepo is the repository a repo-devcontainer build read, as built.

type Integration added in v0.5.0

type Integration struct {
	// ID is an operator-chosen stable slug, unique within
	// SiteConfig.Integrations (no generated uuid — egress redirects'
	// token_integration_ref and workspace requirements point at it).
	ID   string `json:"id"`
	Name string `json:"name"`
	// Kind is the ONE field saying what this connects to: a closed-set kind
	// (ClosedIntegrationKinds) with bespoke behavior, or any other slug (generic).
	Kind string `json:"kind"`
	// Disabled turns the integration off without deleting its configuration.
	Disabled bool `json:"disabled,omitempty"`
	// Secrets are the required secrets, each with its delivery; refs/names only.
	Secrets []IntegrationSecret `json:"secrets,omitempty"`
	// Egress is WHERE THE SYSTEM LIVES: hosts this integration may reach
	// (proxy.ValidDomainEntry syntax: exact host or leading-"*." wildcard,
	// optionally ":port"). TRUST BOUNDARY: a wildcard opens the path but
	// never carries the credential — proxy-side injection needs an exact
	// entry, so a proxy_header secret is rejected at write time if any
	// egress entry is a wildcard or has a port.
	Egress []string `json:"egress,omitempty"`
	// Config is non-secret, kind-validated configuration; closed kinds accept
	// only known keys (unknown 400s by name at write), generic kinds take any
	// string keys.
	Config map[string]any `json:"config,omitempty"`
	Docs   string         `json:"docs,omitempty"`
	// DisabledCapabilities lists capability names this integration does NOT support.
	DisabledCapabilities []string  `json:"disabled_capabilities,omitempty"`
	CreatedAt            time.Time `json:"created_at"`
	UpdatedAt            time.Time `json:"updated_at"`
	// contains filtered or unexported fields
}

Integration is one operator-configured external connection: a base component (required secrets each with its delivery, required egress, non-secret config) extended by kind. Secrets holds names, never values; the broker/proxy resolve the named secret at dispatch time. UnmarshalJSON also accepts the pre-base-component shape, folding it forward one-way (write-new; see foldLegacyIntegration).

func (Integration) CredentialsMap added in v0.5.0

func (in Integration) CredentialsMap() map[string]string

CredentialsMap flattens Secrets into a role → secret_name map for the ONE consumer that wants this shape: the integration.delete audit payload.

func (Integration) HeaderSecret added in v0.5.0

func (in Integration) HeaderSecret() (secretName, header, format string, ok bool)

HeaderSecret returns the first proxy_header-delivered secret row as the (secretName, header, format) triple injection consumes. An empty stored format reads as "%s", not injectionRuleFromScope's "Bearer %s" default.

func (Integration) RoleSecret added in v0.5.0

func (in Integration) RoleSecret(role string) string

RoleSecret returns the secret NAME stored for role ("" when not configured).

func (*Integration) UnmarshalJSON added in v0.5.0

func (in *Integration) UnmarshalJSON(b []byte) error

UnmarshalJSON accepts BOTH the base-component shape (discriminated by its "kind" key) and the legacy {category, type, hosts, header, credentials} shape, folding the latter forward (foldLegacyIntegration) — the ONE read-time migration chokepoint; marshal emits only the new shape.

type IntegrationDelivery added in v0.5.0

type IntegrationDelivery struct {
	Mode string `json:"mode"` // proxy_header | resident_file | resident_env
	// Header/Format apply to proxy_header: field name and value template (one
	// %s; empty means the raw secret IS the value).
	Header string `json:"header,omitempty"`
	Format string `json:"format,omitempty"`
	// Path applies to resident_file: the in-sandbox path the secret lands at.
	Path string `json:"path,omitempty"`
	// Var applies to resident_env: the environment variable name.
	Var string `json:"var,omitempty"`
}

IntegrationDelivery says how ONE secret reaches the run; the mode decides which other fields apply.

type IntegrationList added in v0.5.0

type IntegrationList []Integration

IntegrationList is SiteConfig's integrations slice with the read-time migration applied at decode: rows folded from legacy artifact_mirror/ host_proxy categories are DROPPED (network topology, homed under Corporate network). A new-shape row is never dropped, whatever its kind.

func (*IntegrationList) UnmarshalJSON added in v0.5.0

func (l *IntegrationList) UnmarshalJSON(b []byte) error

UnmarshalJSON decodes the rows, then drops legacy topology rows.

type IntegrationSecret added in v0.5.0

type IntegrationSecret struct {
	Role       string               `json:"role"`
	SecretName string               `json:"secret_name"`
	Delivery   *IntegrationDelivery `json:"delivery,omitempty"`
}

IntegrationSecret is one required secret of an integration: a role, a ref into the secret store (a NAME, never a value), and how it's delivered. A nil Delivery is allowed on CLOSED kinds only (their own bespoke transport delivers it).

type InternalHost added in v0.7.0

type InternalHost struct {
	// HostSuffix matches a request host by label suffix: itself, or any host
	// ending in "."+HostSuffix (never a mid-label substring match).
	HostSuffix string `json:"host_suffix"`
	// CIDRs scopes the lift to these ranges only, each inside RFC1918,
	// fc00::/7, or 100.64.0.0/10 (ipguard.Liftable) — never loopback/
	// link-local/metadata/multicast/NAT64. Empty lifts the full Liftable set.
	CIDRs []string `json:"cidrs,omitempty"`
	// Baseline grades this host and EVERY subdomain as baseline egress, where
	// SiteEgress.BaselineHosts names exact hosts. Default false, so declaring an
	// internal host never changes a grade unless the operator says so. Set only
	// through PUT /governance/egress-baseline (or the boot seed on an empty
	// document): PUT /site-config refuses a changed set of marks.
	Baseline bool `json:"baseline,omitempty"`
}

InternalHost is one SiteConfig.InternalHosts entry — see that field's doc.

type Invitation added in v0.9.0

type Invitation struct {
	ID              uuid.UUID       `json:"id"`
	Target          string          `json:"target"`
	CreatedBy       string          `json:"created_by"`
	CreatedAt       time.Time       `json:"created_at"`
	ExpiresAt       time.Time       `json:"expires_at"`
	TokenName       string          `json:"token_name,omitempty"`
	TokenTTLSeconds *int64          `json:"token_ttl_seconds,omitempty"` // nil = the deployment rule
	ReplacesTokenID *uuid.UUID      `json:"replaces_token_id,omitempty"`
	ClaimedAt       *time.Time      `json:"claimed_at,omitempty"`
	TokenID         *uuid.UUID      `json:"token_id,omitempty"`
	RevokedAt       *time.Time      `json:"revoked_at,omitempty"`
	RevokedBy       string          `json:"revoked_by,omitempty"`
	State           InvitationState `json:"state"`
	ConsolePath     string          `json:"console_path,omitempty"` // create response only
}

Invitation lets one named person mint their own API token (migration 0157, #1508). It holds no secret: only the target, signed in, can claim it, and TokenID is metadata that authenticates nothing.

func (Invitation) StateAt added in v0.9.0

func (i Invitation) StateAt(now time.Time) InvitationState

StateAt is the invitation's state at now: claimed and revoked are final, then the clock decides.

type InvitationState added in v0.9.0

type InvitationState string

InvitationState is where an invitation stands. It is derived from the row and the clock, never stored.

const (
	InvitationPending InvitationState = "pending"
	InvitationClaimed InvitationState = "claimed"
	InvitationExpired InvitationState = "expired"
	InvitationRevoked InvitationState = "revoked"
)

type LLMInspectionSpec

type LLMInspectionSpec struct {
	// Mode is "off" (default), "alert" (scan + audit, forward unchanged), or
	// "block" (a qualifying finding refuses the request). "" == "off".
	Mode string `json:"mode"`
	// WorkspaceSecretNames are operator-declared secret NAMES, resolved against the at-rest secret
	// store — the field an operator/admin actually AUTHORS. A name is not sensitive the way a value
	// is, so it may freely appear in a stored policy row. Resolved to WorkspaceSecretValues ONLY at
	// dispatch, in memory, on the per-dispatch policy copy handed to the proxy.
	WorkspaceSecretNames []string `json:"workspace_secret_names,omitempty"`
	// WorkspaceSecretValues are the RESOLVED secret VALUES the run must not leak into a prompt — the
	// v1 detection corpus the proxy actually matches against. NOT an authoring field:
	// validatePolicySpec refuses a non-empty value here on every policy write. Populated ONLY by
	// dispatch, in memory; every other copy carries names only, values redacted to a count. NEVER
	// logged. Values shorter than the masking floor are ignored.
	WorkspaceSecretValues []string `json:"workspace_secret_values,omitempty"`
	// DetectSecrets enables the known-secret detector (exact match against WorkspaceSecretValues).
	// At least one Detect* must be true when Mode != off.
	DetectSecrets bool `json:"detect_secrets,omitempty"`
	// DetectSecretPatterns enables the regex catalog of well-known secret formats (AWS/GitHub/Slack/
	// Google keys, PEM keys, JWTs, Stripe) — higher precision than entropy but can false-positive on
	// example/test keys in code.
	DetectSecretPatterns bool `json:"detect_secret_patterns,omitempty"`
	// DetectEntropy enables the Shannon-entropy detector (high FP in code; medium
	// severity so a strict block_min_severity can exclude it). Off by default.
	DetectEntropy bool `json:"detect_entropy,omitempty"`
	// DetectPII enables the regex/Luhn PII detector (best-effort, high false-negative recall — a
	// visibility signal, never a control). Off by default.
	DetectPII bool `json:"detect_pii,omitempty"`
	// DetectorSidecarURL, when set, adds an out-of-process detection sidecar the proxy POSTs each
	// span to. Trusted operator config. A sidecar error/timeout/non-200 respects on_scanner_error
	// like any other scanner error: fail-OPEN by default, fail-CLOSED when on_scanner_error=block —
	// so block mode with the sidecar as the SOLE detector DOES guarantee a block on sidecar failure.
	DetectorSidecarURL string `json:"detector_sidecar_url,omitempty"`
	// ClassifiedMarkers are operator-defined literal markers (e.g. "INTERNAL ONLY",
	// "CONFIDENTIAL//NOFORN") whose presence in outbound content flags a
	// classified-content leak. Case-insensitive substring match; category "classified".
	ClassifiedMarkers []string `json:"classified_markers,omitempty"`
	// ScanAttachments opts into decoding+scanning base64 image/document attachment
	// bytes in a prompt (off by default — binary, large, high-FP).
	ScanAttachments bool `json:"scan_attachments,omitempty"`
	// InspectForwardEgress extends inspection from the LLM routes to the GENERIC plaintext-HTTP
	// forward path, so a custom (non-LLM) HTTP connector's POST/PUT body is scanned too. Off by
	// default. (HTTPS connectors tunnel via opaque CONNECT and remain uninspected unless MITM'd.)
	InspectForwardEgress bool `json:"inspect_forward_egress,omitempty"`
	// MaxScanBytes caps the size of a single extracted span scanned; 0 => a
	// built-in default. A larger span is skipped (fail-open) and recorded.
	MaxScanBytes int `json:"max_scan_bytes,omitempty"`
	// OnScannerError selects behavior when the scanner ERRORS (e.g. an
	// unparseable body): "pass" (default, fail-open) or "block".
	OnScannerError string `json:"on_scanner_error,omitempty"`
	// RequireInspectableLLM, when true, refuses to schedule a run whose resolved LLM transport is
	// opaque (subscription-OAuth/Bedrock CONNECT) and therefore cannot be inspected — fail-closed,
	// like MinConfinementClass. Default false only WARNS (the common subscription user isn't punished).
	RequireInspectableLLM bool `json:"require_inspectable_llm,omitempty"`
	// InterceptTLS opts the run into TLS-MITM of opaque CONNECT tunnels to known LLM hosts
	// (Anthropic/OpenAI), making the subscription-OAuth path inspectable. TRUST BOUNDARY: the
	// control plane provisions a per-run CA — the PRIVATE key goes only to the proxy sidecar; the
	// sandbox trusts only the CA's PUBLIC cert. Adds a CA trust dependency inside the sandbox. Off by default.
	InterceptTLS bool `json:"intercept_tls,omitempty"`
	// BlockMinSeverity is the minimum finding severity that triggers a block in
	// "block" mode ("low"|"medium"|"high"|"critical"; "" => low).
	BlockMinSeverity string `json:"block_min_severity,omitempty"`
}

LLMInspectionSpec configures optional outbound LLM prompt inspection for a run. The zero value (or a nil *LLMInspectionSpec on the policy) means OFF.

type LaunchPreset added in v0.8.0

type LaunchPreset struct {
	Name        string          `json:"name"`
	Version     int             `json:"version"`
	Description string          `json:"description,omitempty"`
	UserTypes   []string        `json:"user_types"`
	Request     json.RawMessage `json:"request"`
	CreatedAt   time.Time       `json:"created_at"`
	UpdatedAt   time.Time       `json:"updated_at"`
	CreatedBy   string          `json:"created_by,omitempty"`
	UpdatedBy   string          `json:"updated_by,omitempty"`
}

LaunchPreset is one row of launch_presets (migration 0087): an admin-managed, named bundle of existing POST /runs fields. Request is the stored create-run body, kept raw here because its Go type lives in the public SDK (pkg/client.CreateRunRequest), which imports this package.

type LimitAmount added in v0.9.0

type LimitAmount struct {
	Value     int  `json:"value"`
	Unlimited bool `json:"unlimited,omitempty"`
}

LimitAmount is an effective number. Unlimited is its own value, never a zero.

type LimitSource added in v0.9.0

type LimitSource string

LimitSource names which bound an effective limit came from.

const (
	LimitSourcePool       LimitSource = "pool"
	LimitSourceGovernance LimitSource = "governance"
	LimitSourceDeployment LimitSource = "deployment"
	LimitSourceRunner     LimitSource = "runner"
)

type LimitsOverlay added in v0.8.6

type LimitsOverlay struct {
	LocalSelfDefinedComponents *bool           `json:"local_self_defined_components,omitempty"`
	DenyTaskModeExec           *bool           `json:"deny_task_mode_exec,omitempty"`
	DenyInteractive            *bool           `json:"deny_interactive,omitempty"`
	DenyUIApps                 *bool           `json:"deny_ui_apps,omitempty"`
	MaxConcurrentRuns          *int            `json:"max_concurrent_runs,omitempty"`
	DenyUserDrive              *bool           `json:"deny_user_drive,omitempty"`
	DenyMemberDrives           *bool           `json:"deny_member_drives,omitempty"`
	DenyPlacements             *[]Placement    `json:"deny_placements,omitempty"`
	MaxCPUMillis               *int            `json:"max_cpu_millis,omitempty"`
	MaxMemoryMiB               *int            `json:"max_memory_mib,omitempty"`
	MaxEphemeralDiskMiB        *int            `json:"max_ephemeral_disk_mib,omitempty"`
	MaxDriveSizeMiB            *int            `json:"max_drive_size_mib,omitempty"`
	AutonomyRubric             *AutonomyRubric `json:"autonomy_rubric,omitempty"`
	MaxEndAheadSec             *int            `json:"max_end_ahead_sec,omitempty"`
	DefaultEndSec              *int            `json:"default_end_sec,omitempty"`
	AllowNoEnd                 *bool           `json:"allow_no_end,omitempty"`
	MaxWaitSec                 *int            `json:"max_wait_sec,omitempty"`
	DefaultWaitSec             *int            `json:"default_wait_sec,omitempty"`
	UserChangesLimits          *bool           `json:"user_changes_limits,omitempty"`
	PauseIdleAfterSec          *int            `json:"pause_idle_after_sec,omitempty"`
}

LimitsOverlay is CeilingOverlay's counterpart for GovernanceLimits. The embedded RunLimits fields sit flat on the wire, as they do on the limits object itself.

func DecodeLimitsOverlay added in v0.8.6

func DecodeLimitsOverlay(raw []byte) (LimitsOverlay, error)

DecodeLimitsOverlay strictly decodes a stored or submitted limits overlay.

type LostReason added in v0.8.0

type LostReason string

LostReason says why a kept run lost its sandbox (AgentRun.LostReason).

const (
	// LostEnded is a run whose lease ran out (AgentRun.EndsAt passed): stopped
	// and kept for the ended-run grace.
	LostEnded LostReason = "ended"
	// LostReboot is an interactive run whose agent container exited under it
	// (a host reboot, a Docker Desktop restart, a long suspend) but still
	// exists: kept with its files and its proxy stopped.
	LostReboot LostReason = "reboot"
	// LostOutage is an interactive run whose run token lapsed because the
	// control plane was unreachable past the token's life: its proxy is stopped
	// so it has no egress, and its agent is left running.
	LostOutage LostReason = "outage"
)

type MemberDrivePolicy added in v0.9.0

type MemberDrivePolicy struct {
	Enabled      bool `json:"enabled,omitempty"`
	MaxPerPerson int  `json:"max_per_person,omitempty"` // 1..MaxMemberDrivesPerPerson when Enabled
	// SizeMiB is the k8s claim request, required when Enabled and bounded by
	// UserDriveProvider.MaxSizeMiB. A Docker volume has no size limit, so it must be 0 there.
	SizeMiB      int    `json:"size_mib,omitempty"`
	StorageClass string `json:"storage_class,omitempty"` // k8s only; "" = the cluster default
}

MemberDrivePolicy turns member drive creation on for the organisation (off by default) and bounds it. A profile can still deny it (GovernanceLimits.DenyMemberDrives).

type ModelProvider added in v0.8.0

type ModelProvider struct {
	ID string `json:"id"` // admin's slug ("corp-gateway"); unique within the block
	// UID is SERVER-OWNED: minted on first write, never reissued once deleted. Every person's credential is
	// keyed by it, so a submitted value is ignored rather than trusted.
	UID  string            `json:"uid,omitempty"`
	Name string            `json:"name,omitempty"` // shown when people choose it ("Corp gateway")
	Kind ModelProviderKind `json:"kind"`
	// Disabled is negative-sense so the zero value is ENABLED. A disabled provider may remain an agent's
	// default: that default becomes a refused sentinel, never a fall-through to another provider.
	Disabled bool `json:"disabled,omitempty"`
	// BaseURL: required for custom_endpoint, an optional gateway override for Anthropic/OpenAI, never set
	// on Bedrock (its address is Bedrock.BaseURL).
	BaseURL string           `json:"base_url,omitempty"`
	Auth    *ProviderAuth    `json:"auth,omitempty"`    // how each person's token is sent; custom_endpoint only
	Bedrock *BedrockSettings `json:"bedrock,omitempty"` // region, address and sign-in setup for a Bedrock kind
	Azure   *AzureSettings   `json:"azure,omitempty"`   // endpoint and route for azure_foundry
	// Harnesses are the harnesses this provider serves, with per-pairing settings.
	Harnesses []ProviderHarness `json:"harnesses,omitempty"`
}

ModelProvider is one admin-configured provider.

func (ModelProvider) Serves added in v0.8.0

func (p ModelProvider) Serves(harness string) bool

Serves reports whether harness is enabled for this provider; says nothing about Disabled.

type ModelProviderKind added in v0.8.0

type ModelProviderKind string

ModelProviderKind is what kind of credential each person brings to a provider, and so which dispatch lane serves it. A closed set.

const (
	ModelProviderAnthropicSubscription ModelProviderKind = "anthropic_subscription" // own Claude subscription, via container sign-in
	ModelProviderBedrockSSO            ModelProviderKind = "bedrock_sso"            // own AWS sign-in via admin's start URL + account/role pin
	ModelProviderAnthropicAPIKey       ModelProviderKind = "anthropic_api_key"      // own Anthropic API key
	ModelProviderOpenAIAPIKey          ModelProviderKind = "openai_api_key"         // own OpenAI API key
	ModelProviderBedrockBearer         ModelProviderKind = "bedrock_bearer"         // own Bedrock API key
	// ModelProviderCustomEndpoint: admin's own endpoint, reached with each person's own token/PAT.
	ModelProviderCustomEndpoint ModelProviderKind = "custom_endpoint"
	// ModelProviderAzureFoundry: each person's own Entra sign-in, captured for one Azure resource audience.
	// Writable only while validateModelProviders' activation flag (azureFoundryGateReady) is true.
	ModelProviderAzureFoundry ModelProviderKind = "azure_foundry"
)

func (ModelProviderKind) IsBedrock added in v0.8.0

func (k ModelProviderKind) IsBedrock() bool

IsBedrock reports whether k is one of the two Bedrock kinds (address/sign-in live in ModelProvider.Bedrock).

func (ModelProviderKind) Valid added in v0.8.0

func (k ModelProviderKind) Valid() bool

Valid reports whether k is one of the closed kinds.

type ModelProviders added in v0.8.0

type ModelProviders struct {
	Providers []ModelProvider `json:"providers,omitempty"`
}

ModelProviders is the org's model-provider CONFIGURATION: which credential kinds this deployment supports, where each sends requests, and which harnesses may use it. Configuration ONLY — no credential lives on a record, every person supplies their own. The zero value is load-bearing: with no block, run create and dispatch keep the existing lane-resolution path unchanged. Validation lives in internal/api.

func (*ModelProviders) Empty added in v0.8.0

func (p *ModelProviders) Empty() bool

Empty reports whether p carries no provider — the shape a PUT uses to CLEAR the block, normalized back to nil so a later GET doesn't render "model_providers":{} forever.

type ModelProvidersRead added in v0.9.0

type ModelProvidersRead struct {
	ModelProviders
	ConnectedPeople      map[string]int      `json:"connected_people,omitempty"`
	EntraLoginConfigured bool                `json:"entra_login_configured"`
	KindsAvailable       []ModelProviderKind `json:"kinds_available"`
}

ModelProvidersRead is GET /api/v1/model-providers' body: the stored block, and ConnectedPeople — per provider ID, how many people hold a credential of their own for it (#970). Read-only: PUT's strict decode refuses the field, and the ETag covers the stored block alone, so a person connecting never stales an admin's edit. Admins only, as the route is: a member never reads this list. It carries counts and configuration, never a credential value.

EntraLoginConfigured says whether this install has the console Entra login an azure_foundry provider signs people in through; KindsAvailable lists the kinds a write accepts now (azure_foundry only while the release has it switched on). Both are server facts, not stored and not in the ETag.

type OverrideDirection added in v0.9.0

type OverrideDirection string

OverrideDirection says whether the operation gives the run less reach or more.

const (
	OverrideTighten OverrideDirection = "tighten"
	OverrideWiden   OverrideDirection = "widen"
)

type OverrideKind added in v0.9.0

type OverrideKind string

OverrideKind names one thing a person may change on one component for one run (OD-1: New Run card edits are per-component overrides on the request).

const (
	OverrideAgentHost         OverrideKind = "agent_host"          // a destination on the agent component
	OverrideAgentHostWildcard OverrideKind = "agent_host_wildcard" // the same, spelled with a wildcard
	OverrideAgentSecret       OverrideKind = "agent_secret"        // a stored secret injected as a header on the agent component
	OverrideToolRuleRestrict  OverrideKind = "tool_rule_restrict"  // a tool rule whose effect is hold or deny
	OverrideToolRuleAllow     OverrideKind = "tool_rule_allow"     // a tool rule whose effect is allow
	OverrideADOCapability     OverrideKind = "ado_capability"      // the run's Azure DevOps capability set
	OverrideGitPATScope       OverrideKind = "git_pat_scope"       // repos, access or api of a git_pat grant
	OverridePushDeny          OverrideKind = "push_rule_deny"      // a push deny path of one provider and organisation
	OverridePushReview        OverrideKind = "push_rule_review"    // a push require-review path of one provider and organisation
	OverrideRunHostAllow      OverrideKind = "run_host_allow"      // a host the person adds to the run's allowed list, on top of the base policy
	OverrideRunHostWildcard   OverrideKind = "run_host_wildcard"   // the same, spelled with a wildcard
	OverrideRunHostDeny       OverrideKind = "run_host_deny"       // a host the person adds to the run's denied list, on top of the base policy
	OverrideFirstUseApproval  OverrideKind = "first_use_approval"  // how an unlisted destination is handled, chosen on top of the base policy
	OverrideAllowAllEgress    OverrideKind = "allow_all_egress"    // request all other egress, on top of the base policy
)

type OverrideNarrowing added in v0.9.0

type OverrideNarrowing struct {
	Kind      OverrideKind      `json:"kind"`
	Op        OverrideOp        `json:"op"`
	Direction OverrideDirection `json:"direction"`
	Rule      OverrideRule      `json:"rule"`
}

OverrideNarrowing is one row of the narrowing table.

func OverrideNarrowingTable added in v0.9.0

func OverrideNarrowingTable() []OverrideNarrowing

OverrideNarrowingTable returns the table, in its fixed order.

func OverrideRuleFor added in v0.9.0

func OverrideRuleFor(kind OverrideKind, op OverrideOp) (OverrideNarrowing, bool)

OverrideRuleFor looks one operation up; false for a pair the table has no row for.

type OverrideOp added in v0.9.0

type OverrideOp string

OverrideOp is what the person does to the entry.

const (
	OverrideAdd    OverrideOp = "add"
	OverrideRemove OverrideOp = "remove"
	// OverrideNarrow reduces a value the source resolved: a smaller capability
	// set, a subset of repositories, write to read.
	OverrideNarrow OverrideOp = "narrow"
	// OverrideSet chooses a run-wide scalar the source also holds.
	OverrideSet OverrideOp = "set"
)

type OverrideRule added in v0.9.0

type OverrideRule string

OverrideRule is what a person may do with the operation.

const (
	// OverrideAllowed: always, because it only takes reach away.
	OverrideAllowed OverrideRule = "allowed"
	// OverrideClamped: allowed to ask, and the ceiling and the member grant
	// pipeline may still drop it; the preview shows what they dropped.
	OverrideClamped OverrideRule = "ceiling_clamped"
	// OverrideRefused: never, from a run request.
	OverrideRefused OverrideRule = "refused"
	// OverrideWithinPolicy: allowed when no looser than the policy for that
	// entry, refused otherwise. The check needs the resolved policy, so the
	// server answers it after the policy fold, not from this row alone.
	OverrideWithinPolicy OverrideRule = "within_policy"
)

type PauseReason added in v0.8.0

type PauseReason string

PauseReason says why a run's agent is frozen (AgentRun.PausedReason).

const (
	PauseWaiting PauseReason = "waiting" // parked on an open request nobody has answered
	PauseIdle    PauseReason = "idle"    // unused past pause_idle_after_sec, CPU quiet
)

type Person added in v0.8.0

type Person struct {
	Principal       string     `json:"principal"`
	Email           string     `json:"email,omitempty"`
	Issuer          string     `json:"issuer,omitempty"`
	TenantID        string     `json:"tenant_id,omitempty"`
	ObjectID        string     `json:"object_id,omitempty"`
	CreatedBy       string     `json:"created_by"`
	CreatedAt       time.Time  `json:"created_at"`
	FirstSignedInAt *time.Time `json:"first_signed_in_at,omitempty"`
}

Person is an identity an admin created or confirmed before its first sign-in, keyed by the identity provider's subject — or, on Entra ID, by Issuer, TenantID and ObjectID, all three set or none (#1195).

type PersonList added in v0.8.6

type PersonList struct {
	People     []PersonSummary `json:"people"`
	NextCursor string          `json:"next_cursor,omitempty"`
}

PersonList is one page of GET /people. NextCursor is empty on the last page.

type PersonSummary added in v0.8.6

type PersonSummary struct {
	Principal       string     `json:"principal"`
	Email           string     `json:"email,omitempty"`
	IssuerKind      string     `json:"issuer_kind"`
	PreCreated      bool       `json:"pre_created"`
	FirstSignedInAt *time.Time `json:"first_signed_in_at,omitempty"`
	LastSignedInAt  *time.Time `json:"last_signed_in_at,omitempty"`
	DeactivatedAt   *time.Time `json:"deactivated_at,omitempty"`
	Role            string     `json:"role,omitempty"`
	ActiveSessions  int        `json:"active_sessions"`
	APITokens       int        `json:"api_tokens"`
	SSHKeys         int        `json:"ssh_keys"`
	Credentials     int        `json:"credentials"`
	ActiveRuns      int        `json:"active_runs"`
}

PersonSummary is one row of GET /people: a person this deployment knows, with the counts behind the leaver actions. ActiveSessions is 0 or 1: sessions are stateless cookies, so it says whether the last sign-in could still hold a live one, not how many cookies are out. Role is the role the person's email and last verified groups derive: "denied" when sign-in would refuse them, "unknown" when that sign-in's groups were truncated, and empty when sign-in is not SSO.

type Placement added in v0.9.0

type Placement string

Placement says where a run's sandbox lives.

const (
	PlacementRemote Placement = "remote" // the organisation's own executor
	PlacementLocal  Placement = "local"  // a registered runner on the person's laptop
)

func (Placement) Valid added in v0.9.0

func (p Placement) Valid() bool

Valid reports whether p names one of the two placements.

type ProviderAuth added in v0.8.0

type ProviderAuth struct {
	Header string `json:"header,omitempty"`
	Format string `json:"format,omitempty"`
}

ProviderAuth is how a custom endpoint expects each person's token: header and value format ("Bearer %s").

type ProviderHarness added in v0.8.0

type ProviderHarness struct {
	Harness string `json:"harness"` // harness-catalog id ("claude-code", "codex-cli")
	// Model: model id this harness uses on this provider; admin-set, no member override. Required
	// (an inference profile) on a Bedrock kind.
	Model string `json:"model,omitempty"`
	// FastModel is the deployment the Messages harness uses for its small-model alias; azure_foundry only,
	// on that harness only. Unset means Model.
	FastModel string `json:"fast_model,omitempty"`
	Path      string `json:"path,omitempty"` // where a custom endpoint serves this harness's API dialect, under BaseURL; may not change the host
	// AuthHeader and AuthFormat override the provider's Auth for this harness alone (custom_endpoint only).
	AuthHeader string `json:"auth_header,omitempty"`
	AuthFormat string `json:"auth_format,omitempty"`
}

ProviderHarness is one harness a provider is enabled for, and the settings for that pairing.

type PushContentScope added in v0.8.0

type PushContentScope struct {
	// Repo is the repository as the run's grant names it: "github.com/<owner>/<repo>"
	// on the GitHub App lane, "<host>/<path>" on the git_pat lane (Azure DevOps:
	// "dev.azure.com/<org>/<project>/_git/<repo>").
	Repo string `json:"repo"`
	// Branch is the ref the push updates, "refs/heads/…"; several names join
	// sorted by ", ".
	Branch string `json:"branch"`
	// ActsAs is the credential the forwarded push authenticates with, as
	// "<grant kind>:<grant id>" — "github_token:<uuid>" or "git_pat:<uuid>".
	ActsAs string `json:"acts_as"`
	// Paths are the first PushContentMaxPaths review-matched paths, sorted,
	// each git-quoted (core.quotePath on, the sidecar's quotePath) so every
	// path is valid UTF-8 on the wire. A path ending in "/" is a directory the
	// push does not carry.
	Paths []string `json:"paths"`
	// PathsTotal is how many paths matched a review pattern in all.
	PathsTotal int `json:"paths_total"`
	// Commits are the object ids the push sets its refs to, sorted.
	Commits []string `json:"commits"`
	// Updates pairs each ref the push updates with the object id it sets it
	// to — all zeros for a delete — sorted by ref. Branch and Commits name the
	// same refs and ids for the card; Updates is what binds one to the other,
	// so the same commits sent to the same refs in a different assignment are
	// a different scope. A row raised before it existed has none and matches
	// no new raise.
	Updates []PushRefUpdate `json:"updates"`
	// PathsDigest is the lower-case hex SHA-256 over every review-matched path,
	// sorted, each followed by a NUL byte (a git path cannot contain one).
	PathsDigest string `json:"paths_digest"`

	// ActsAsKind and ActsAsLabel are SERVER-SET: the control plane resolves
	// ActsAs against the run's own grants and stamps them before the row is
	// stored, refusing a raise that carries either — a sidecar's word on who a
	// push acts as is not taken. ActsAsKind is one of the PushActsAs*
	// constants; ActsAsLabel is the principal the console names.
	ActsAsKind  string `json:"acts_as_kind,omitempty"`
	ActsAsLabel string `json:"acts_as_label,omitempty"`
}

PushContentScope is the requested_scope of an ApprovalPushContent row — the bytes the console renders on the push card, stored verbatim.

The dedup key is the whole scope: approval.RequestApproval and the partial unique index from migration 0022 both compare it, so two raises of the same push collapse into one PENDING row. What makes it the right key is Commits plus PathsDigest: commits are content addresses, so a repack of the same commits (git's retry after an approval) yields the same scope where a pack digest would not, and PathsDigest covers every review-matched path, not just the ten Paths names, so two pushes whose first ten matches agree are still two questions.

func (PushContentScope) Validate added in v0.8.0

func (s PushContentScope) Validate() error

Validate checks the shape the proxy writes, so a row the console renders can be trusted to be one: every bound here is one the sidecar already honours.

type PushPathList added in v0.8.0

type PushPathList struct {
	Paths     []string `json:"paths"`
	Truncated bool     `json:"truncated"`
}

PushPathList is a held push's complete review-matched path list, sorted and bounded: every path when Truncated is false, else the first ones that fit the bounds. Sent by the sidecar beside the push_content requested_scope; the control plane verifies it against that scope (VerifyAgainst) and keeps it immutable per approval.

func NewPushPathList added in v0.8.0

func NewPushPathList(paths []string) PushPathList

NewPushPathList bounds paths, which are sorted and distinct.

func (PushPathList) VerifyAgainst added in v0.8.0

func (l PushPathList) VerifyAgainst(s PushContentScope) error

VerifyAgainst checks l is the list s was built from: sorted, distinct and within bounds, opening with every one of s.Paths, and — unless Truncated — exactly PathsTotal paths whose digest is s.PathsDigest. A truncated list must also have been cut by a bound (PushPathListMaxPaths paths, or within pushPathListSlack of PushPathListMaxBytes); which path follows the cut is still the sidecar's word, since nothing the scope carries names it.

type PushRefUpdate added in v0.8.0

type PushRefUpdate struct {
	Ref string `json:"ref"`
	New string `json:"new"`
}

PushRefUpdate is one ref a held push sets and the object id it sets it to.

type PushRuleSet added in v0.9.0

type PushRuleSet struct {
	Provider string `json:"provider"`
	Host     string `json:"host,omitempty"`
	Org      string `json:"org"`
	// BasePath is the path prefix of an Azure DevOps Server provider row (for
	// example "/tfs"), recorded from the row at dispatch. The git route derives
	// a remote's collection with it: without it a remote under a prefix is
	// ambiguous and its push is refused. Empty for every other entry.
	BasePath string        `json:"base_path,omitempty"`
	Rules    PushRulesSpec `json:"rules"`
}

PushRuleSet is the push content rules of one SCM entry (provider host and organisation), wherever the entry came from: a repository or workspace, a tool or a component. It is the dispatch-plan carrier the proxy's git route reads: the set of the entry that owns the remote applies, and a remote whose entry has no set has no content rules. Host empty is the provider's public host (github.com, dev.azure.com); a GitHub Enterprise Server or Azure DevOps Server names its own. The run-wide RunPolicySpec.PushRules remains only as the older-client mapping, which the dispatch plan fans out to one set per entry the run brings.

The sets are the product AFTER the ceiling: the dispatch plan lays the policy's and the governance ceiling's rules into each entry's set first, and a request's rules (RunOverrides.PushRules) may only ADD a deny or a require-review path to them, never remove or relax one. A person's rules therefore cannot displace an administrator's.

func (PushRuleSet) Key added in v0.9.0

func (s PushRuleSet) Key() string

Key is the entry this set belongs to, "" when the set does not name one (ValidatePushRuleSets refuses such a set).

type PushRulesOverlay added in v0.8.6

type PushRulesOverlay struct {
	DenyPaths          *[]string `json:"deny_paths,omitempty"`
	MaxInspectPackMiB  *int      `json:"max_inspect_pack_mib,omitempty"`
	RequireReviewPaths *[]string `json:"require_review_paths,omitempty"`
	HoldSeconds        *int      `json:"hold_seconds,omitempty"`
	DenyNewExecutables *bool     `json:"deny_new_executables,omitempty"`
	MaxFileSizeMiB     *int      `json:"max_file_size_mib,omitempty"`
}

PushRulesOverlay mirrors PushRulesSpec with per-field presence.

type PushRulesSpec added in v0.8.0

type PushRulesSpec struct {
	// DenyPaths are path patterns (e.g. ".github/workflows/**") the broker
	// refuses in a push: "**" crosses path segments, "*" and "?" do not, and
	// the pattern is anchored at the repository root. A trailing "/" means
	// everything beneath the directory (DenyPathSegments).
	DenyPaths []string `json:"deny_paths,omitempty"`
	// MaxInspectPackMiB caps how much of an incoming push the broker buffers
	// before refusing it as too large. 0/absent takes the broker's own default,
	// below the authored maximum so raising this is a real remedy; bounded
	// 0..64 by validatePolicySpec.
	MaxInspectPackMiB int `json:"max_inspect_pack_mib,omitempty"`
	// RequireReviewPaths are patterns in DenyPaths' language whose match HOLDS
	// a push for an admin's decision instead of refusing it. A deny match wins
	// over a review match; an unattended run refuses rather than holding.
	RequireReviewPaths []string `json:"require_review_paths,omitempty"`
	// HoldSeconds is how long a held push waits for that decision before it
	// is refused. 0/absent means 120; bounded 0..600 by validatePolicySpec.
	HoldSeconds int `json:"hold_seconds,omitempty"`
	// DenyNewExecutables is reserved: the broker cannot yet tell a newly added executable from an
	// edit to an existing one, so validation refuses it when true rather than store a rule nothing
	// enforces, and IsSet does not count it, so a stored true never makes the broker inspect for
	// a rule it cannot apply.
	DenyNewExecutables bool `json:"deny_new_executables,omitempty"`
	// MaxFileSizeMiB refuses a push that introduces a file larger than this many MiB, or one whose
	// size the pack does not carry and the forge cannot show unchanged. 0/absent is off; bounded
	// 0..1024 by validatePolicySpec.
	MaxFileSizeMiB int `json:"max_file_size_mib,omitempty"`
}

PushRulesSpec declares content rules for a run's brokered git pushes — the counterpart to GitPushAnyBranch's branch confinement: WHAT a push may touch, not WHERE it may land. A closed struct, deliberately not a free-form rules map: an open map evades the strict-field JSON decoder and the doc-census test, so a typo'd key would silently do nothing instead of failing at write time. This type stores and validates; how DenyPathSegments entries MATCH is the git broker's (internal/egress/proxy/push_rules.go), reading them on both brokered lanes.

func PushRulesForRemote added in v0.9.0

func PushRulesForRemote(sets []PushRuleSet, provider, remote string) (spec PushRulesSpec, found bool, err error)

PushRulesForRemote is the git route's lookup: the set of the entry that owns remote. found false means the entry has no content rules; it never falls back to another entry's. A remote whose entry cannot be derived is an error, and the caller refuses the push rather than treating it as having no rules.

func PushRulesForRemoteAt added in v0.9.0

func PushRulesForRemoteAt(sets []PushRuleSet, provider, remote, basePath string) (spec PushRulesSpec, found bool, err error)

PushRulesForRemoteAt is PushRulesForRemote for a provider row whose Azure DevOps Server lives under basePath (for example "/tfs").

func PushRulesForServerRemote added in v0.9.0

func PushRulesForServerRemote(sets []PushRuleSet, provider, remote, basePath string) (spec PushRulesSpec, found bool, err error)

PushRulesForServerRemote is PushRulesForRemoteAt over PushRuleEntryOfServerRemote.

func (*PushRulesSpec) IsSet added in v0.8.0

func (s *PushRulesSpec) IsSet() bool

IsSet reports whether this spec carries an actual rule — NOT a bare != nil. An all-zero-but-non-nil *PushRulesSpec (a literal `push_rules: {}`) says nothing about what a push may touch and must read exactly like an absent one wherever consulted (composer's clamp/risk grade, the broker's advertisement/enforcement) — one method so those readers can't drift.

type RemoteRunner added in v0.9.0

type RemoteRunner struct {
	ID   string     `json:"id"`
	Kind RunnerKind `json:"kind"`
	Name string     `json:"name"`
	// PeoplePools is who may add it to their own user pools; PeopleSubjects lists them when it is
	// listed. A listed policy whose people all left lists nobody and admits nobody.
	PeoplePools    RemoteRunnerPeoplePools `json:"people_pools"`
	PeopleSubjects []RunnerPoolSubject     `json:"people_subjects,omitempty"`
	// Limits is what an administrator declared (proposed once at boot from what the server measured);
	// nil while any of them is unset, and then the runner takes no run.
	Limits *RunnerLimits `json:"limits"`
	// Revision starts at 1 and rises on every change to the name or the people policy.
	Revision  int64     `json:"revision"`
	UpdatedBy string    `json:"updated_by,omitempty"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

RemoteRunner is a remote-hosted runner: a Docker host or Kubernetes cluster this server is set up with, keyed by the name of the substrate it is configured as. A row whose substrate this server does not run stays, and is never a candidate.

type RemoteRunnerPeoplePools added in v0.9.0

type RemoteRunnerPeoplePools string

RemoteRunnerPeoplePools is a remote-hosted runner's "Available to people's pools" policy: who may add it to their own user pools. Off by default; checked when a person adds it and again at launch.

const (
	PeoplePoolsOff      RemoteRunnerPeoplePools = "off"
	PeoplePoolsEveryone RemoteRunnerPeoplePools = "everyone"
	PeoplePoolsListed   RemoteRunnerPeoplePools = "listed"
)

func (RemoteRunnerPeoplePools) Valid added in v0.9.0

func (p RemoteRunnerPeoplePools) Valid() bool

Valid reports whether p is one of the three.

type RemoteRunnerPoolRef added in v0.9.0

type RemoteRunnerPoolRef struct {
	ID    uuid.UUID       `json:"id"`
	Name  string          `json:"name"`
	Scope RunnerPoolScope `json:"scope"`
	// Owner is a user pool's owner, set on the operators' reads only.
	Owner string `json:"owner,omitempty"`
}

RemoteRunnerPoolRef names one live pool a remote-hosted runner is in.

type ResolvedDrive added in v0.7.0

type ResolvedDrive struct {
	Drive    UserDrive
	Grant    UserDriveGrant
	Tier     CapabilitySubjectType
	HomeName string // per-user segment: the subdirectory of a share, or the suffix of a managed object's name
	// SubjectHash fingerprints the principal HomeName was derived FROM
	// (DriveSubjectHash), carried so the runner can stamp it and refuse one
	// stamped for somebody else.
	SubjectHash string `json:"subject_hash,omitempty"`
	ObjectName  string // what the runner asks the substrate for — a volume name, a PVC name, or an absolute host path
	SizeMiB     int
	Writable    bool
	Enforcement StorageEnforcement
	// Paused is set when the grant that WON is disabled: nothing is derived
	// and nothing may be mounted, but the row still wins its tier (DESIGN
	// §2.2) rather than falling through.
	Paused bool
}

ResolvedDrive is THE answer for one principal: the winning drive, the grant that won, the tier it won at, and everything derived from the pair. Built once by the API resolver so no consumer re-derives a home name or override. Writable is already folded but NOT yet narrowed by the run request.

NO json tags: this type crosses no wire. The preview and /me each compose their own response struct from it.

type ResolvedInjection added in v0.4.4

type ResolvedInjection struct {
	Host      string `json:"host"`
	Header    string `json:"header"`
	Value     string `json:"value"`
	JTI       string `json:"jti"`
	ExpiresAt int64  `json:"expires_at,omitempty"`
	// Organisation is the ADO org a per-person credential was dispatched for — informational only;
	// the proxy's REST gate pins the org from its own dispatch-time config, never from a resolve.
	Organisation string `json:"organisation,omitempty"`
	// Capabilities is the same lane's granted capability set, read once to confirm an asked-for
	// capability came back granted. Empty on other lanes.
	Capabilities []string `json:"capabilities,omitempty"`
}

ResolvedInjection is the wire contract of GET /api/v1/internal/injection/{grantID}: header name plus the server-formatted secret value. ExpiresAt (unix ms, 0 = never) marks a credential the proxy must re-resolve before it lapses; only a single-use approval-gated grant leaves it 0.

type ResolvedRunnerPool added in v0.9.0

type ResolvedRunnerPool struct {
	ID        uuid.UUID           `json:"id"`
	Name      string              `json:"name"`
	Scope     RunnerPoolScope     `json:"scope"`
	Revision  int64               `json:"revision"`
	Selection RunnerPoolSelection `json:"selection"`
}

ResolvedRunnerPool is the pool a request resolves to: the pool, the revision it was read at and how it was chosen. It is a label for the choice and never admission: a run is admitted against the persisted pool and the exact target.

type ResourceLimits

type ResourceLimits struct {
	CPUMillis int `json:"cpu_millis,omitempty"` // milli-CPU; 2000 = 2 vCPU
	MemoryMiB int `json:"memory_mib,omitempty"` // hard memory cap in MiB
	PidsLimit int `json:"pids_limit,omitempty"` // max processes/threads (fork-bomb guard)
	DiskMiB   int `json:"disk_mib,omitempty"`   // writable storage cap in MiB (best-effort)
}

ResourceLimits caps a sandbox's resource consumption. A ZERO field means "use the platform default": the dispatch path fills conservative defaults (e.g. 2000m CPU, 4096 MiB, 512 PIDs) so even a policy that sets no limits still runs capped. DiskMiB is best-effort and depends on the storage driver supporting a per-container quota (fail-closed/warn when a cap is demanded but unsupported).

type ResourcesOverlay added in v0.8.6

type ResourcesOverlay struct {
	CPUMillis *int `json:"cpu_millis,omitempty"`
	MemoryMiB *int `json:"memory_mib,omitempty"`
	PidsLimit *int `json:"pids_limit,omitempty"`
	DiskMiB   *int `json:"disk_mib,omitempty"`
}

ResourcesOverlay mirrors ResourceLimits with per-field presence.

type RoleMapping added in v0.7.0

type RoleMapping struct {
	ID                 uuid.UUID `json:"id"`
	Value              string    `json:"value"`
	Role               string    `json:"role"`
	UserType           string    `json:"user_type,omitempty"`
	MigratedFromMember bool      `json:"migrated_from_member,omitempty"`
	CreatedAt          time.Time `json:"created_at"`
	CreatedBy          string    `json:"created_by,omitempty"`
}

RoleMapping is one console-managed (Getting Started -> People) row of migration 0051's role_mappings table: "value maps to role". This is the STORE'S wire type, carrying id/timestamps/provenance — distinct on purpose from internal/auth/oidc's own RoleMapping (just Value/Role), which stays dependency-free of this package (see that type's doc comment) the same way oidc.SessionRevocations keeps oidc dependency-free of store; the API layer converts between the two, mirroring however SessionRevocations bridges store -> oidc today.

Value is expected already canonical (trimmed, lowercased, ASCII) by the API write boundary that owns writes to this table — see the migration comment.

UserType is the row's user type when Role is the user tier (migration 0070_user_tier_rename's column, a foreign key to user_types); "" on a tier row, and on a user row written before types existed, which reads as the built-in "standard".

MigratedFromMember (migration 0098) marks a row that rewrite rewrote from role='member' rather than one an admin actually saved on the built-in type — the store's UpsertRoleMapping clears it on any write that flips the row in place, since choosing a real type is what the marker exists to prompt.

type RunAttention added in v0.8.0

type RunAttention struct {
	Kind    AttentionKind `json:"kind"`
	By      AttentionBy   `json:"by"`
	Pending int           `json:"pending"`
}

RunAttention is GET /runs and GET /me/attention's projection onto one live run: what it's waiting on, who can clear it (in the caller's view), and how many PENDING approvals it carries. Nil when attention() has nothing to say (terminal, lease-ended, nothing PENDING held).

type RunComponent added in v0.8.9

type RunComponent struct {
	RunID uuid.UUID `json:"run_id"`
	// Ordinal is the component's position in the request.
	Ordinal int `json:"ordinal"`
	// SelfDefined is true for an inline component or one the launcher owns;
	// such a run needed the custom-component feature. False is an org
	// component, whose ComponentID names its own grant.
	SelfDefined bool `json:"self_defined"`
	// Erased is true once the person who defined it was erased: every field
	// below is then empty.
	Erased bool `json:"erased,omitempty"`
	// ComponentID is nil for an inline component.
	ComponentID *uuid.UUID `json:"component_id,omitempty"`
	// Owner is "" for an org component, else the person who defined it (the
	// launcher).
	Owner string `json:"owner,omitempty"`
	Name  string `json:"name"`
	// Version is the stored component's version at launch; 0 for inline.
	Version    int                 `json:"version"`
	Definition ComponentDefinition `json:"definition"`
	// MCPHarnesses are the harnesses an MCP component's server was turned on
	// for at launch. Not personal data: it survives an erasure, so a revive
	// still knows which harness reached which server.
	MCPHarnesses []string `json:"mcp_harnesses,omitempty"`
}

RunComponent is the snapshot of one component as a run was launched with it. It is a copy, not a reference: later edits to, or deletion of, the stored component do not change it.

It is also the run's authorization record. Erasing the person who defined it clears every descriptive field and sets Erased; RunID, Ordinal and SelfDefined survive, so a revive still knows which doors the run needed.

type RunEndReason added in v0.9.0

type RunEndReason string

RunEndReason says why the platform ended a run. It is the audit row's `reason` and the run detail's sentence, and it rides the run's status_detail as "lifetime: <reason>" when the run is KILLED at its end or its maximum.

const (
	// RunEndMaxLifetimeReached: the run reached its effective maximum lifetime and was KILLED.
	RunEndMaxLifetimeReached RunEndReason = "max_lifetime_reached"
	// The five ways the pool queue ends a run that waited (docs/design/0.9/pool-queue.md section 9).
	// A run that waited too long, or whose profile, policy or portal changed under it, is FAILED; a
	// run whose pool was removed is KILLED. identity_deactivated is the existing token, FAILED here.
	RunEndQueueTimeout        RunEndReason = "queue_timeout"
	RunEndQueueProfileChanged RunEndReason = "queue_profile_changed"
	RunEndQueuePolicyChanged  RunEndReason = "queue_policy_changed"
	RunEndQueuePortalRevoked  RunEndReason = "queue_portal_revoked"
	RunEndRunnerPoolRemoved   RunEndReason = "runner_pool_removed"
	RunEndIdentityDeactivated RunEndReason = "identity_deactivated"
)
const RunEndEndReached RunEndReason = "end_reached"

RunEndEndReached: a Background task reached its own end time, which came before its maximum lifetime, and was KILLED.

func (RunEndReason) Sentence added in v0.9.0

func (r RunEndReason) Sentence() string

Sentence is what the run's detail page and the audit view say.

func (RunEndReason) Valid added in v0.9.0

func (r RunEndReason) Valid() bool

Valid reports whether r is a defined end reason.

type RunEvidenceSource added in v0.9.0

type RunEvidenceSource string

RunEvidenceSource records who vouches for the evidence a run produces: its confinement, decisions, recordings, exit codes and capabilities. It is not the run's `confinement_source` (`requested` or `defaulted`, written by run.create): that says who chose the class, this says who can attest it.

const (
	RunEvidenceSubstrate      RunEvidenceSource = "substrate"       // the organisation's own substrate
	RunEvidenceRunnerAsserted RunEvidenceSource = "runner_asserted" // the runner said so; nobody can verify it
)

type RunExperience added in v0.9.0

type RunExperience string

RunExperience is the canonical run mode a person chose when the run was launched: a Background task or an Interactive environment (0.9 New Run).

const (
	// ExperienceBackground is a run with no interactive terminal, SSH,
	// interactive exec or attach, web application gateway or graphical desktop.
	// Logs, audit, status and the authorised lifecycle controls stay.
	ExperienceBackground RunExperience = "background"
	// ExperienceInteractive is general sandboxed work through whatever
	// interactive surfaces the placement actually supports.
	ExperienceInteractive RunExperience = "interactive"
)

func (RunExperience) Valid added in v0.9.0

func (e RunExperience) Valid() bool

Valid reports whether e is one of the two modes. The empty value is not a mode.

type RunHarness added in v0.9.0

type RunHarness struct {
	Harness         string `json:"harness"`
	ModelProviderID string `json:"model_provider_id,omitempty"`
	Starts          bool   `json:"starts,omitempty"`
}

RunHarness is one harness a run was launched with and the model provider it was granted ("" for none). The rows are the run's authorisation record: a renew, revive or queued start re-checks the doors each one needed. Starts marks the one harness the sandbox starts; at most one row per run has it.

type RunLayout added in v0.5.0

type RunLayout struct {
	Preset    string            `json:"preset"`
	Layout    []RunLayoutWidget `json:"layout"`
	UpdatedAt time.Time         `json:"updated_at"`
}

RunLayout is a human's saved widget arrangement for the run-detail cockpit (GET/PUT /api/v1/me/run-layout), one row per (principal, preset) — see migration 0037_ui_layouts.sql and internal/api/ui_layout.go's doc comment for why this is a server row and not ui/src/app/lib/storage.ts's localStorage. Principal deliberately does not appear here: it is the row's SCOPE (how internal/store looks a layout up), never a value the API echoes back or a caller supplies — internal/api/ui_layout.go scopes every read and write by principalFromRequest(r) alone.

type RunLayoutWidget added in v0.5.0

type RunLayoutWidget struct {
	Widget string `json:"widget"`
	X      int    `json:"x"`
	Y      int    `json:"y"`
	W      int    `json:"w"`
	H      int    `json:"h"`
}

RunLayoutWidget places one evidence widget on the run cockpit's grid. Widget is validated server-side against a closed id set (internal/api/ui_layout.go's runLayoutWidgetIDs) before a layout is ever stored — an id no build renders would otherwise be a widget that silently vanishes on restore, with nothing to explain why.

type RunLimits added in v0.8.0

type RunLimits struct {
	// MaxEndAheadSec: furthest ahead of NOW a run's end may be set (0 = no limit).
	// Extending within it never needs UserChangesLimits — extending is the lease.
	MaxEndAheadSec int `json:"max_end_ahead_sec,omitempty"`
	// DefaultEndSec is a new run's end, from create; 0 means MaxEndAheadSec, and
	// a run has no end when both are 0.
	DefaultEndSec int `json:"default_end_sec,omitempty"`
	// AllowNoEnd offers "No end" to a user who may change limits.
	AllowNoEnd bool `json:"allow_no_end,omitempty"`
	// MaxWaitSec/DefaultWaitSec: longest and default wait for a decision; 0 =
	// the deployment's approval expiry, which also caps both.
	MaxWaitSec     int `json:"max_wait_sec,omitempty"`
	DefaultWaitSec int `json:"default_wait_sec,omitempty"`
	// UserChangesLimits is the one gate: the user may shorten the end, set No
	// end, or change the wait.
	UserChangesLimits bool `json:"user_changes_limits,omitempty"`
	// PauseIdleAfterSec pauses an unused run after this long (0 pauses only
	// runs waiting for a decision).
	PauseIdleAfterSec int `json:"pause_idle_after_sec,omitempty"`
}

RunLimits bound how long a run lives and how long it waits for a decision (long-holds design rev 4, §2.2). Zero values keep today's behaviour: no end, the deployment's approval expiry as the wait, no idle pause.

They bind every run under a profile, including a security admin's; only a super admin's run skips them (effectiveCeiling resolves no profile for an operator).

type RunPolicy

type RunPolicy struct {
	ID        uuid.UUID     `json:"id"`
	Name      string        `json:"name"`
	CreatedAt time.Time     `json:"created_at"`
	UpdatedAt time.Time     `json:"updated_at"`
	Spec      RunPolicySpec `json:"spec"`
}

RunPolicy is the declarative policy attached to runs: egress allowlist, approval rules, and the maximum credential scopes a run may be granted. Workspace-local configuration may only NARROW a policy, never widen it.

type RunPolicySpec

type RunPolicySpec struct {
	// AllowedDomains is the L2 egress allowlist (exact hosts or "*." wildcards).
	AllowedDomains []string `json:"allowed_domains"`
	// DeniedDomains always wins over AllowedDomains.
	DeniedDomains []string `json:"denied_domains,omitempty"`
	// AllowAllEgress switches L2 egress from default-deny to "allow all (deny-list only)" mode: the
	// proxy allows ANY non-denied PUBLIC host, AllowedDomains may be empty. SECURITY: denied_domains
	// STILL wins, the SSRF/private-IP guard is unaffected, and credential injection STILL requires
	// an EXACT allowlist entry, so allow-all never widens where a secret may be injected.
	AllowAllEgress bool `json:"allow_all_egress,omitempty"`
	// FirstUseApproval controls how an unknown domain is handled: always_deny,
	// deny_with_review, or wait_for_review. Accepts the legacy boolean on the
	// wire. Inert under allow-all.
	FirstUseApproval FirstUseMode `json:"first_use_approval"`
	// FirstUseHoldSeconds bounds how long a wait_for_review connection is HELD
	// open awaiting a decision before the proxy refuses it. 0/absent keeps the
	// built-in 30s default; a positive value overrides it. Only wait_for_review
	// holds. See internal/egress/proxy configureHold.
	FirstUseHoldSeconds int `json:"first_use_hold_seconds,omitempty"`
	// MaxHolds caps concurrent wait_for_review holds. 0/absent keeps the
	// built-in 16 default. The (N+1)th concurrent held connection fails fast
	// rather than consuming an unbounded goroutine.
	MaxHolds int `json:"max_holds,omitempty"`
	// AllowedMethods optionally restricts HTTP methods (empty = all).
	AllowedMethods []string `json:"allowed_methods,omitempty"`
	// MinConfinementClass refuses to launch below this class.
	MinConfinementClass ConfinementClass `json:"min_confinement_class"`
	// EligibleGrants is the ceiling of credential scopes a run may request.
	EligibleGrants []GrantSpec `json:"eligible_grants,omitempty"`
	// AutoStopAfter stops idle sandboxes after this many seconds. 0/absent
	// means never reaped, same as an explicit negative (the reaper skips <=
	// 0); negative exists only to state never-reap intent explicitly.
	AutoStopAfterSec int `json:"auto_stop_after_sec,omitempty"`
	// WorkspaceMounts are host bind mounts injected into the sandbox. TRUST BOUNDARY: authored only
	// on a stored policy or inline create-run request by an admin/SSO-gated operator, never chosen
	// by the in-sandbox agent. validatePolicySpec runs runner.ValidateMount (the same deny-list the
	// docker driver enforces) at write time and again, defense-in-depth, at sandbox-create.
	WorkspaceMounts []WorkspaceMount `json:"workspace_mounts,omitempty"`
	// WorkspaceRepos are additional git repos attached to a run, paralleling WorkspaceMounts for
	// git-cloned (rather than bind-mounted) sources. Same admin/inline authoring surface and trust
	// boundary as WorkspaceMounts: never agent-chosen. validatePolicySpec enforces a unique-target
	// invariant across ALL WorkspaceMounts + WorkspaceRepos dests, so a clone can never land on a
	// bind target or shadow another repo's checkout.
	WorkspaceRepos []WorkspaceRepo `json:"workspace_repos,omitempty"`
	// LLMInspection optionally enables OUTBOUND content inspection at the proxy for brokered LLM
	// routes. Nil/omitted => OFF (the safe default). A guardrail + visibility layer, NOT
	// exfiltration prevention (see internal/contentscan + threatmodel §5.1).
	LLMInspection *LLMInspectionSpec `json:"llm_inspection,omitempty"`
	// UIApps declares the in-sandbox loopback HTTP apps the UI gateway may relay to a browser.
	// OPERATOR-AUTHORED, same trust boundary as WorkspaceMounts: an app is a NAME, a loopback PORT
	// and a PATH — never a command string. The gateway serves ONLY a declared port; `ssh -L` stays
	// the undeclared-port escape hatch.
	UIApps []UIApp `json:"ui_apps,omitempty"`
	// Resources caps sandbox CPU/memory/PIDs/disk. Nil, or a zero field, means "use the platform
	// default" — dispatch fills conservative defaults so EVERY run is capped, the multi-tenant
	// safety control that keeps one runaway/prompt-injected agent from OOM-killing the host,
	// fork-bombing it, or filling storage and taking down sibling runs.
	Resources *ResourceLimits `json:"resources,omitempty"`
	// ToolRules narrows an autonomous run's tool use from a BINARY (`tool_approvals`'s `auto`/`hold`)
	// to a policy: naming a TOOL and an effect (allow/hold/deny) lets "read freely, ask before shell,
	// never fetch the web" become expressible. Operator-authored and evaluated outside the sandbox
	// (proxy-side), so a compromised agent can't rewrite its own rules; it only narrows, and applies
	// only to a run already in `hold`, so a bad rule at worst asks more or refuses a call. Empty
	// preserves today's behaviour.
	ToolRules []ToolRule `json:"tool_rules,omitempty"`
	// GitPushAnyBranch turns OFF branch-namespace confinement for THIS run's brokered pushes (GitHub
	// App, opted-in git_pat and Azure DevOps Entra lanes). By default the broker forwards a push only
	// under refs/heads/wardyn/<run-id>/, so an agent can't rewrite main — right for an autonomous run,
	// wrong for a human-driven sandbox using an external tool with its own branch names. true =
	// forward to any branch the granted token may write, audited per push; the grant's own GitHub
	// ruleset still bounds what the token can touch. Operator-authored, never agent-settable.
	//
	// ponytail: whole-namespace off, not a per-run allowed-prefix list — add a
	// prefix list when someone needs an external tool AND confinement at once.
	GitPushAnyBranch bool `json:"git_push_any_branch,omitempty"`
	// PushRules declares CONTENT rules for this run's brokered git pushes — WHAT a push may touch,
	// alongside GitPushAnyBranch's WHERE. Nil (every pre-existing policy) means no content rules,
	// byte-identical to that prior wire shape, since every reader keys off IsSet. A policy that sets
	// PushRules while this run's only git-capable grant is ssh_key is legal but UNENFORCEABLE (SSH
	// has no broker seam) — composer.Grade surfaces that as a medium-risk WARNING, never a refusal.
	PushRules *PushRulesSpec `json:"push_rules,omitempty"`
	// AzureDevOpsCapabilities chooses THIS run's capabilities on the per-person Azure DevOps lane,
	// in place of the provider row's default_profile — which is what lets a saved policy act as a
	// saved access profile. It chooses only WITHIN the row's capability_ceiling: dispatch refuses a
	// run naming anything outside it, and the resolver re-checks the live ceiling on every request.
	// Empty/absent keeps the row's default_profile. Inert for a run not on that lane.
	AzureDevOpsCapabilities []adoscope.Capability `json:"azure_devops_capabilities,omitempty"`
	// GitHubCapabilities is AzureDevOpsCapabilities' GitHub twin, with the same meaning: THIS
	// run's GitHub capabilities in place of the GitHub provider row's default_profile, chosen only
	// within the row's capability_ceiling. Empty/absent keeps the row's default_profile. Nothing
	// reads it yet — the row block and the lanes that honour it land after this field — so today
	// it is stored, narrowed and compared, and inert at dispatch.
	GitHubCapabilities []ghscope.Capability `json:"github_capabilities,omitempty"`
}

func (RunPolicySpec) Clone added in v0.3.1

func (s RunPolicySpec) Clone() RunPolicySpec

Clone returns a deep copy: every slice/pointer field is reallocated, so the copy shares no backing array with the receiver. A plain `spec := other` is a SHALLOW copy — slice headers still point at the original's backing array, making the process-global default policy aliasable: a per-run `append` to AllowedDomains wrote into the shared spare capacity, so two concurrent create-runs raced on the same element (one run's egress domain silently replacing another's), and any in-place mutation leaked into every later run. Callers deriving a per-run/per-request spec from a shared one must Clone first.

type RunQueue added in v0.9.0

type RunQueue struct {
	PoolID     uuid.UUID `json:"pool_id"`
	QueuedAt   time.Time `json:"queued_at"`
	DeadlineAt time.Time `json:"deadline_at"`
	Position   int       `json:"position"`
	Reason     string    `json:"reason"`
}

RunQueue is what a QUEUED run says about its wait: the pool, when it joined, when its wait ends, how many runs are ahead of it in that pool (a count, never whose) and the wait reason token (runner_pool_at_capacity, runner_pool_no_runner_free or runner_pool_runners_at_capacity).

type RunState

type RunState string

RunState is the AgentRun lifecycle state machine.

const (
	// RunQueued is a launch waiting for a slot in a capped runner pool: non-terminal, before PENDING,
	// and holding nothing (no identity, no sandbox, no slot).
	RunQueued   RunState = "QUEUED"
	RunPending  RunState = "PENDING"
	RunStarting RunState = "STARTING"
	RunRunning  RunState = "RUNNING"
	// RunWaiting is reserved: unused, but kept wired end-to-end for the approval-gated-run feature.
	RunWaiting  RunState = "WAITING_FOR_CONFIRMATION"
	RunStopped  RunState = "STOPPED"
	RunArchived RunState = "ARCHIVED"
	RunFailed   RunState = "FAILED"
	RunKilled   RunState = "KILLED"
	// RunCompleted is terminal success (exit 0); a non-zero exit becomes RunFailed instead.
	RunCompleted RunState = "COMPLETED"
)

func (RunState) IsTerminal added in v0.4.4

func (s RunState) IsTerminal() bool

IsTerminal reports whether the run has ended. SINGLE source of the terminal set — the UI's copy (runs.ts) is pinned equal by TestTerminalRunStates_UIParity.

type RunTemplate added in v0.9.0

type RunTemplate struct {
	ID          uuid.UUID
	Owner       TemplateOwner
	Kind        TemplateKind
	Name        string
	Description string
	Revision    int
	Document    json.RawMessage
	CreatedAt   time.Time
	CreatedBy   string
	UpdatedAt   time.Time
	UpdatedBy   string
}

RunTemplate is one stored template revision: the catalogue key, the kind, and the name, description and document that revision saved. Document is the door-normalised template document as JSON; the store never reads into it.

type Runner added in v0.9.0

type Runner struct {
	ID           uuid.UUID `json:"id"`
	Owner        string    `json:"owner"`
	Name         string    `json:"name"`
	OrgURLSHA256 string    `json:"-"`
	// MintedBy is who minted the registration token this runner redeemed; empty for a runner
	// registered before it was recorded.
	MintedBy string `json:"-"`
	// PublicKey is the runner's Ed25519 key. The private half never leaves the runner.
	PublicKey      []byte        `json:"-"`
	KeyFingerprint string        `json:"key_fingerprint"`
	State          RunnerState   `json:"state"`
	Version        string        `json:"version,omitempty"`
	CreatedAt      time.Time     `json:"created_at"`
	ClaimedAt      *time.Time    `json:"claimed_at,omitempty"`
	LastSeenAt     *time.Time    `json:"last_seen_at,omitempty"`
	RevokedAt      *time.Time    `json:"revoked_at,omitempty"`
	Posture        RunnerPosture `json:"posture"`
	// PostureReportedAt is nil until the runner reports; PostureSource is then runner_asserted.
	PostureReportedAt *time.Time `json:"posture_reported_at,omitempty"`
	PostureSource     string     `json:"posture_source,omitempty"`
	// RevokedBy is the principal that revoked the runner; RevokeReason is "" or
	// RunnerRevokeNotMine. Both are empty until it is revoked.
	RevokedBy    string `json:"-"`
	RevokeReason string `json:"-"`
	// MaxRuns is how many sandbox-holding runs placement may put on the runner at once: the runner's
	// "Runs at once". Its owner sets it; the stored value is the one placement holds.
	MaxRuns int `json:"max_runs"`
	// Limits is the runner's declared limit set (its RunsAtOnce is MaxRuns), nil while its owner has not
	// set them all (0174): such a runner takes no run.
	Limits *RunnerLimits `json:"-"`
	// MachineName is the host name the runner last reported (at registration and on each connect):
	// runner-reported, never an identity, "" until it reports one.
	MachineName string `json:"machine_name"`
}

Runner is one registered wardyn-runnerd. Owner is a person's principal, so the row is personal data.

func (Runner) ClaimExpired added in v0.9.0

func (r Runner) ClaimExpired(now time.Time) bool

ClaimExpired also rejects an invalid zero creation time. Claimed identities do not expire under this rule; revocation is a separate terminal state.

type RunnerActionKind added in v0.9.0

type RunnerActionKind string

RunnerActionKind is a pending action's verb.

const (
	RunnerActionRetire             RunnerActionKind = "retire_run"
	RunnerActionKill               RunnerActionKind = "kill"
	RunnerActionEnd                RunnerActionKind = "end"
	RunnerActionStopProxy          RunnerActionKind = "stop_proxy"
	RunnerActionRecoverMutation    RunnerActionKind = "recover_mutation"
	RunnerActionQuarantine         RunnerActionKind = "quarantine_run"
	RunnerActionRecoverContainment RunnerActionKind = "recover_containment"
)

type RunnerAmounts added in v0.9.0

type RunnerAmounts struct {
	CPUMillis int64 `json:"cpu_millis"`
	MemoryMiB int64 `json:"memory_mib"`
	DiskMiB   int64 `json:"disk_mib"`
}

RunnerAmounts is an amount of CPU (milli-CPU), memory (MiB) and disk (MiB).

func PerRunShare added in v0.9.0

func PerRunShare(total RunnerAmounts, runsAtOnce int) RunnerAmounts

PerRunShare is each run's share of total when runsAtOnce run side by side: total divided by Runs at once, at least one CPU and one GiB of memory and disk, never past the total.

type RunnerCapacity added in v0.9.0

type RunnerCapacity struct {
	CPUMillisMax int64 `json:"cpu_millis_max"`
	MemoryMiBMax int64 `json:"memory_mib_max"`
}

RunnerCapacity is the per-run ceiling a runner advertised: the wire shape of its caps event.

type RunnerClaimRequest added in v0.9.0

type RunnerClaimRequest struct {
	Fingerprint string `json:"fingerprint"`
}

RunnerClaimRequest confirms the fingerprint observed on the owner's host.

type RunnerFilter added in v0.9.0

type RunnerFilter string

RunnerFilter is the state filter of the administrators' runner list; an empty one is active.

const (
	RunnerFilterActive  RunnerFilter = "active"
	RunnerFilterRevoked RunnerFilter = "revoked"
	RunnerFilterAll     RunnerFilter = "all"
)

type RunnerKind added in v0.9.0

type RunnerKind string

RunnerKind is the kind of a runner: a person's registered computer, or a Docker host or Kubernetes cluster this server is set up with.

const (
	RunnerKindSelfHosted   RunnerKind = "self_hosted"
	RunnerKindRemoteHosted RunnerKind = "remote_hosted"
)

type RunnerLimits added in v0.9.0

type RunnerLimits struct {
	RunsAtOnce int           `json:"runs_at_once"`
	Total      RunnerAmounts `json:"total"`
	PerRun     RunnerAmounts `json:"per_run"`
}

RunnerLimits is a runner's declared limit set: how many runs it takes at once, what all of them together may reserve (Total) and what one may reserve (PerRun).

func ProposeRunnerLimits added in v0.9.0

func ProposeRunnerLimits(measured RunnerAmounts, runsAtOnce int) RunnerLimits

ProposeRunnerLimits is what setup offers for a computer that measured as measured (owner ruling RP Q3): half of each as the total, and per run the total divided by Runs at once, at least one CPU and one GiB of memory and disk, never past the total. A measured amount of 0 (not measurable) proposes 0 there, which Validate refuses, so the person is asked for it.

func (RunnerLimits) Validate added in v0.9.0

func (l RunnerLimits) Validate(maxRuns int) error

Validate refuses a limit set the store's checks would: Runs at once from 1 to maxRuns, each amount within the run request contract's bounds (at least one CPU, disk in whole GiB), and no per-run amount past its total.

type RunnerMutationFailure added in v0.9.0

type RunnerMutationFailure struct {
	ID         uuid.UUID `json:"id"`
	Generation uuid.UUID `json:"generation"`
	Operation  string    `json:"operation"`
	Contained  bool      `json:"contained"`
}

RunnerMutationFailure is an immutable failure identity. Contained becomes true only after proxy containment and lifecycle persistence both succeed.

type RunnerPendingAction added in v0.9.0

type RunnerPendingAction struct {
	Retirement *RunnerRetirement      `json:"retirement,omitempty"`
	Quarantine *RunnerQuarantine      `json:"quarantine,omitempty"`
	Recovery   *RunnerMutationFailure `json:"recovery,omitempty"`
	ID         uuid.UUID              `json:"id"`
	RunnerID   uuid.UUID              `json:"runner_id"`
	RunID      uuid.UUID              `json:"run_id"`
	Kind       RunnerActionKind       `json:"kind"`
	Ref        string                 `json:"ref"`
	QueuedAt   time.Time              `json:"queued_at"`
	AppliedAt  *time.Time             `json:"applied_at,omitempty"`
	Outcome    string                 `json:"outcome,omitempty"`
	ObservedAt *time.Time             `json:"observed_at,omitempty"`
	// Bound, Owner, Generation and LostAt are the org identity persisted when
	// the action was queued. A row that is not Bound predates that binding and
	// grants nothing. On a Bound row a nil Generation means no execution had
	// been issued; LostAt is set for stop_proxy only.
	Bound      bool       `json:"-"`
	Owner      string     `json:"-"`
	Generation uuid.UUID  `json:"-"`
	LostAt     *time.Time `json:"-"`
	// Asserted marks a subject with no org run row: RunID is then the
	// runner's assertion, Owner is empty and Generation is nil.
	Asserted bool `json:"-"`
	// ObligationID is the chain a deferred action's successors continue.
	ObligationID uuid.UUID `json:"-"`
}

RunnerPendingAction is applied before readiness when a runner reconnects.

type RunnerPool added in v0.9.0

type RunnerPool struct {
	ID uuid.UUID `json:"id"`
	// Name is a display label a pool's administrator may change. It is never an
	// identity: every reference is by ID.
	Name  string          `json:"name"`
	Scope RunnerPoolScope `json:"scope"`
	// Owner is the person a user pool belongs to (their subject, as runner
	// ownership names them): only they change it or put runners in it, and only
	// they use it. An organization pool has none: administrators keep it.
	Owner string          `json:"owner,omitempty"`
	State RunnerPoolState `json:"state"`
	// Revision starts at 1 and increases by one on every change to the pool, its
	// membership or its use policy. A run records the revision it was admitted
	// against, and a writer that names a stale revision is refused.
	Revision int64 `json:"revision"`
	// Limits are the pool's own limits per run type. Absent, the pool adds no
	// bound of its own (a pool migrated from before limits existed).
	Limits    *RunnerPoolLimits `json:"limits,omitempty"`
	CreatedAt time.Time         `json:"created_at"`
	UpdatedAt time.Time         `json:"updated_at"`
}

RunnerPool is one pool of the catalogue: an organization pool or one person's own user pool.

type RunnerPoolAmount added in v0.9.0

type RunnerPoolAmount struct {
	Default int `json:"default"`
	Cap     int `json:"cap"`
}

RunnerPoolAmount is a CPU or memory limit: what an untouched run gets and the most a request may ask for. Both are positive and Default is at most Cap.

type RunnerPoolBackgroundLimits added in v0.9.0

type RunnerPoolBackgroundLimits struct {
	CPUMillis *RunnerPoolAmount `json:"cpu_millis,omitempty"`
	MemoryMiB *RunnerPoolAmount `json:"memory_mib,omitempty"`
	// DiskMiB bounds the main drive of this run type's runs: what an untouched run
	// gets and the most a request may ask for, in MiB, each a whole number of GiB.
	// Nil: the pool sets no main-drive bound.
	DiskMiB  *RunnerPoolAmount   `json:"disk_mib,omitempty"`
	Lifetime *RunnerPoolDuration `json:"lifetime,omitempty"`
}

RunnerPoolBackgroundLimits are a pool's limits for Background tasks. It has no idle stop: a Background task ends when its command or agent exits, or at its maximum lifetime. Decoding a body that sends `idle` here fails.

Every field is optional: one left out is set by the pool's runners (their per-run limits, and governance and the deployment for time), never "no limit".

type RunnerPoolDefaults added in v0.9.0

type RunnerPoolDefaults struct {
	PoolID  *uuid.UUID       `json:"pool_id,omitempty"`
	Barrier ConfinementClass `json:"barrier,omitempty"`
}

RunnerPoolDefaults is the default pool, for a person or for the organization: the pool a run that names none starts on. A person's own wins over the organization's; it is never a way to widen what a person may use, because a default only ever seeds a choice the catalogue must still permit. The organization's names an organization pool only.

Barrier is the organisation's alone: the class an untouched launch gets when it is at or above the floor and the pool offers it. A person's own document never carries one.

func (RunnerPoolDefaults) Validate added in v0.9.0

func (d RunnerPoolDefaults) Validate() error

Validate checks the document's shape. Whether the pool exists, is permitted and may be this kind of default is the writer's and the resolver's, against the live catalogue.

type RunnerPoolDuration added in v0.9.0

type RunnerPoolDuration struct {
	DefaultSec int  `json:"default_sec"`
	MaxSec     int  `json:"max_sec,omitempty"`
	Unlimited  bool `json:"unlimited,omitempty"`
}

RunnerPoolDuration is a lifetime or an idle-stop limit, in seconds. It has exactly one of two forms: a finite MaxSec, with DefaultSec from 1 to MaxSec, or Unlimited, with MaxSec 0 and a DefaultSec of 0 (no end, or never stop for idleness, by default) up to RunnerPoolSecondsMax. Unlimited is never encoded by a zero MaxSec alone, so an omitted number cannot read as it. For an idle stop, Unlimited is "never". An Interactive lifetime that is Unlimited still needs a DefaultSec of at least 1; a Background lifetime may default to no end.

type RunnerPoolInteractiveLimits added in v0.9.0

type RunnerPoolInteractiveLimits struct {
	CPUMillis *RunnerPoolAmount `json:"cpu_millis,omitempty"`
	MemoryMiB *RunnerPoolAmount `json:"memory_mib,omitempty"`
	// DiskMiB is the main drive's bound, as on RunnerPoolBackgroundLimits.
	DiskMiB  *RunnerPoolAmount   `json:"disk_mib,omitempty"`
	Lifetime *RunnerPoolDuration `json:"lifetime,omitempty"`
	Idle     *RunnerPoolDuration `json:"idle,omitempty"`
}

RunnerPoolInteractiveLimits are a pool's limits for Interactive environments, which alone have an idle stop. Every field is optional, as on RunnerPoolBackgroundLimits.

type RunnerPoolLimits added in v0.9.0

type RunnerPoolLimits struct {
	Background  *RunnerPoolBackgroundLimits  `json:"background,omitempty"`
	Interactive *RunnerPoolInteractiveLimits `json:"interactive,omitempty"`
	// Barriers are the confinement classes (Fence CC1, Wall CC2, Vault CC3) the
	// pool allows. A run on any other is refused. None listed: every barrier its
	// runners offer.
	Barriers []ConfinementClass `json:"barriers"`
	// MaxConcurrentRuns caps the pool's non-terminal runs, across everyone. Unset
	// is no pool cap; a person's own cap is the governance profile's.
	MaxConcurrentRuns *int `json:"max_concurrent_runs,omitempty"`
}

RunnerPoolLimits is everything a pool limits. A run type with limits is a run type the pool allows, and one without is refused, so "allowed" and "limited" cannot disagree. A pool with no RunnerPoolLimits at all adds no bound: only governance, the deployment and the runner bind its runs.

func (RunnerPoolLimits) AllowsBarrier added in v0.9.0

func (l RunnerPoolLimits) AllowsBarrier(c ConfinementClass) bool

AllowsBarrier reports whether the pool allows runs on confinement class c.

func (RunnerPoolLimits) AllowsRunType added in v0.9.0

func (l RunnerPoolLimits) AllowsRunType(t RunnerPoolRunType) bool

AllowsRunType reports whether the pool serves runs of type t.

func (RunnerPoolLimits) RunTypes added in v0.9.0

func (l RunnerPoolLimits) RunTypes() []RunnerPoolRunType

RunTypes lists the run types the pool allows, Background first.

func (RunnerPoolLimits) Validate added in v0.9.0

func (l RunnerPoolLimits) Validate() error

Validate refuses limits a pool cannot keep: no run type, no barrier, an unknown or repeated barrier, a default past its cap, a value out of range or a concurrency cap below 1.

type RunnerPoolMember added in v0.9.0

type RunnerPoolMember struct {
	PoolID uuid.UUID  `json:"pool_id"`
	Kind   RunnerKind `json:"kind"`
	// RunnerID is a claimed self-hosted runner (user pools only).
	RunnerID *uuid.UUID `json:"runner_id,omitempty"`
	// RemoteRunnerID is a remote-hosted runner (either scope).
	RemoteRunnerID string    `json:"remote_runner_id,omitempty"`
	AddedAt        time.Time `json:"added_at"`
}

RunnerPoolMember is one runner of a pool: exactly one of RunnerID (a self-hosted runner) and RemoteRunnerID (a remote-hosted runner) is set, and Kind says which. A self-hosted membership is added by the runner's own owner into their own user pool and never by anyone on their behalf.

func (RunnerPoolMember) Validate added in v0.9.0

func (m RunnerPoolMember) Validate(scope RunnerPoolScope) error

Validate refuses a member that names no runner, two runners, or a self-hosted runner in an organization pool.

type RunnerPoolRef added in v0.9.0

type RunnerPoolRef struct {
	ID   uuid.UUID `json:"id"`
	Name string    `json:"name"`
}

RunnerPoolRef names one pool a runner is a member of.

type RunnerPoolRunType added in v0.9.0

type RunnerPoolRunType string

RunnerPoolRunType is the kind of run a pool serves. The values are the run mode's wire values (a Background task, an Interactive environment).

const (
	RunnerPoolRunBackground  RunnerPoolRunType = "background"
	RunnerPoolRunInteractive RunnerPoolRunType = "interactive"
)

func (RunnerPoolRunType) Plural added in v0.9.0

func (t RunnerPoolRunType) Plural() string

Plural is how a sentence names runs of the type.

func (RunnerPoolRunType) Valid added in v0.9.0

func (t RunnerPoolRunType) Valid() bool

Valid reports whether t is one of the two run types.

type RunnerPoolScope added in v0.9.0

type RunnerPoolScope string

RunnerPoolScope says whose a pool is. It is fixed for the life of a pool: an organization pool is the administrators', a user pool its owner's.

const (
	// RunnerPoolOrganization pools hold remote-hosted runners only, and have no owner.
	RunnerPoolOrganization RunnerPoolScope = "organization"
	// RunnerPoolUser pools belong to one person.
	RunnerPoolUser RunnerPoolScope = "user"
)

func (RunnerPoolScope) Placement added in v0.9.0

func (s RunnerPoolScope) Placement() Placement

Placement is the run placement a pool of this scope serves today: an organization pool places on its remote-hosted runners, a user pool on its owner's own self-hosted runners (a user pool's remote-hosted members are listed but not yet candidates). Empty for a value outside the set.

func (RunnerPoolScope) Valid added in v0.9.0

func (s RunnerPoolScope) Valid() bool

Valid reports whether s is one of the two scopes.

type RunnerPoolSelection added in v0.9.0

type RunnerPoolSelection string

RunnerPoolSelection says how a run's pool was chosen.

const (
	// RunnerPoolSelectedExplicit: the request named the pool.
	RunnerPoolSelectedExplicit RunnerPoolSelection = "explicit"
	// RunnerPoolSelectedPersonal: the person's own saved default.
	RunnerPoolSelectedPersonal RunnerPoolSelection = "personal_default"
	// RunnerPoolSelectedOrg: the organisation's default.
	RunnerPoolSelectedOrg RunnerPoolSelection = "organisation_default"
)

func (RunnerPoolSelection) Valid added in v0.9.0

func (s RunnerPoolSelection) Valid() bool

Valid reports whether s is one of the three.

type RunnerPoolState added in v0.9.0

type RunnerPoolState string

RunnerPoolState is a pool's lifecycle. A deleted pool is a tombstone: runs that were bound to it keep their history, and nothing new can choose it.

const (
	RunnerPoolActive   RunnerPoolState = "active"
	RunnerPoolDisabled RunnerPoolState = "disabled"
	RunnerPoolDeleted  RunnerPoolState = "deleted"
)

func (RunnerPoolState) Valid added in v0.9.0

func (s RunnerPoolState) Valid() bool

Valid reports whether s is one of the three lifecycle states.

type RunnerPoolSubject added in v0.9.0

type RunnerPoolSubject struct {
	SubjectType CapabilitySubjectType `json:"subject_type"`
	Subject     string                `json:"subject"`
}

RunnerPoolSubject is one person, group or role a pool-use policy admits. It reuses the capability subject vocabulary: a role is a user_type, and an identity provider's roles arrive as groups.

type RunnerPoolUsePolicy added in v0.9.0

type RunnerPoolUsePolicy struct {
	PoolID   uuid.UUID           `json:"pool_id"`
	Subjects []RunnerPoolSubject `json:"subjects"`
	// Revision is the pool's revision this policy was written at.
	Revision  int64     `json:"revision"`
	UpdatedAt time.Time `json:"updated_at"`
	UpdatedBy string    `json:"updated_by,omitempty"`
}

RunnerPoolUsePolicy narrows who may use one organization pool. With no policy, everyone who may launch remote runs may use the pool; with one, only the listed subjects may. It never grants launch rights, so it can only narrow. A policy with no subjects is refused rather than stored: "nobody" and "unrestricted" must not look alike, and a disabled pool already closes one.

func (RunnerPoolUsePolicy) Validate added in v0.9.0

func (p RunnerPoolUsePolicy) Validate(scope RunnerPoolScope) error

Validate checks a policy before it is stored against the pool it narrows: a use policy narrows an organization pool only. On a user pool it would either be stored and ignored, reading as a restriction that does not exist, or become a sharing ACL over people's own runners, which 0.9 does not have.

type RunnerPosture added in v0.9.0

type RunnerPosture struct {
	MDMManaged    bool   `json:"mdm_managed"`
	DiskEncrypted bool   `json:"disk_encrypted"`
	OS            string `json:"os"`
	OSVersion     string `json:"os_version"`
	// Evidence is where the runner read mdm_managed and disk_encrypted from: a
	// root-owned file the device's MDM wrote, else its own probe of the OS.
	// Either way the org records the posture as runner_asserted.
	Evidence string `json:"evidence"`
}

RunnerPosture is what a runner reported about its device. Self-reported: it separates a lost or compromised laptop from a healthy one and does not constrain a root developer.

type RunnerQuarantine added in v0.9.0

type RunnerQuarantine struct {
	ID         uuid.UUID `json:"id"`
	Generation uuid.UUID `json:"generation"`
	Asserted   bool      `json:"asserted,omitempty"`
}

RunnerQuarantine identifies an org-authorized isolation disposition, not an evidence acknowledgement or permission to reconstruct missing run authority. Asserted marks a quarantine of a run the org has no row for: Generation is then the runner's own assertion, never compared to org executions, and the runner deletes that run's local evidence after RunnerAssertedEvidenceTTL or at revocation.

type RunnerRegisterRequest added in v0.9.0

type RunnerRegisterRequest struct {
	Token     string `json:"token"`
	PublicKey []byte `json:"public_key"`
	Name      string `json:"name"`
	// Limits is the runner's limit set, which the server requires: wardyn-runnerd measures the host
	// and proposes it (ProposeRunnerLimits), and the person may change any of it.
	Limits *RunnerLimits `json:"limits"`
	// MachineName is the host's own name (os.Hostname), runner-reported; omitted by an older runner.
	MachineName string `json:"machine_name,omitempty"`
}

RunnerRegisterRequest carries only the public half of the runner identity.

type RunnerRegistration added in v0.9.0

type RunnerRegistration struct {
	RunnerID     uuid.UUID   `json:"runner_id"`
	Fingerprint  string      `json:"fingerprint"`
	State        RunnerState `json:"state"`
	OrgURLSHA256 string      `json:"org_url_sha256"`
	// Limits is the limit set the server stored; nil on a runner registered before 0174 until its owner sets one.
	Limits *RunnerLimits `json:"limits"`
}

RunnerRegistration is safe to return to the anonymous runner; it has no human identity.

type RunnerRegistrationToken added in v0.9.0

type RunnerRegistrationToken struct {
	ID           uuid.UUID  `json:"id"`
	Owner        string     `json:"owner"`
	MintedBy     string     `json:"minted_by"`
	Token        string     `json:"token,omitempty"`
	TokenSHA256  string     `json:"-"`
	OrgURLSHA256 string     `json:"-"`
	CreatedAt    time.Time  `json:"created_at"`
	ExpiresAt    time.Time  `json:"expires_at"`
	ConsumedAt   *time.Time `json:"consumed_at,omitempty"`
	// OrgURL is the organisation URL the runner must register against: the mint answers carry it, a
	// token list never does. wardyn-runnerd refuses an --org that does not hash to the server's.
	OrgURL string `json:"org_url,omitempty"`
}

RunnerRegistrationToken is a single-use invitation for one owner's runner. The raw token is returned only at mint; stored rows carry its hash.

type RunnerRetirement added in v0.9.0

type RunnerRetirement struct {
	ID         uuid.UUID          `json:"id"`
	Generation uuid.UUID          `json:"generation"`
	ExecID     string             `json:"exec_id"`
	ExitHash   string             `json:"exit_hash"`
	OutputEnd  int64              `json:"output_end"`
	Spool      RunnerSpoolReceipt `json:"spool"`
}

RunnerRetirement is an org-issued settlement receipt, never authority inferred from a runner's staged exit ACK. Its action separately binds runner/run/ref.

type RunnerSettings added in v0.9.0

type RunnerSettings struct {
	Enabled bool `json:"enabled"`
}

RunnerSettings controls admission to runner routes. Nil and false both deny.

type RunnerSettingsRequest added in v0.9.0

type RunnerSettingsRequest struct {
	Enabled *bool `json:"enabled"`
}

RunnerSettingsRequest turns runners on or off. Enabled is required, so an empty body never switches anything.

type RunnerSpoolReceipt added in v0.9.0

type RunnerSpoolReceipt struct {
	SpoolID       uuid.UUID `json:"spool_id"`
	Seq           int64     `json:"seq"`
	Hash          string    `json:"hash"`
	ReportedDrops uint64    `json:"reported_drops"`
}

RunnerSpoolReceipt identifies the durable accepted prefix including overflow.

type RunnerState added in v0.9.0

type RunnerState string

RunnerState is where a registered runner is in the claim machine.

const (
	// RunnerUnclaimed: registered with a token; offered no run and relays nothing.
	RunnerUnclaimed RunnerState = "unclaimed"
	// RunnerClaimed: the token's owner proved they hold the key.
	RunnerClaimed RunnerState = "claimed"
	// RunnerRevoked is terminal; the key is never reused.
	RunnerRevoked RunnerState = "revoked"
)

type RunnerTally added in v0.9.0

type RunnerTally struct {
	Runs      int   `json:"runs"`
	CPUMillis int64 `json:"cpu_millis"`
	MemoryMiB int64 `json:"memory_mib"`
	DiskMiB   int64 `json:"disk_mib"`
}

RunnerTally is a number of runs and the CPU, memory and disk they reserve.

type RunnerTokenRequest added in v0.9.0

type RunnerTokenRequest struct {
	Owner string `json:"owner"`
}

RunnerTokenRequest names the person an operator is registering a runner for.

type RunnerUpdateRequest added in v0.9.0

type RunnerUpdateRequest struct {
	Limits *RunnerLimits `json:"limits"`
}

RunnerUpdateRequest is an owner's change to their own runner (PUT /me/runners/{id}): the whole limit set, required, so an empty body changes nothing.

type RunnerUsage added in v0.9.0

type RunnerUsage struct {
	Online     bool           `json:"online"`
	InUse      RunnerTally    `json:"in_use"`
	Available  RunnerTally    `json:"available"`
	DiskCapped bool           `json:"disk_capped"`
	Reported   *RunnerAmounts `json:"reported,omitempty"`
}

RunnerUsage is how much of a runner its runs hold. InUse is every run holding a sandbox on it, from every pool and person: what placement counts. Available is the limits' total less InUse, and all zero while the runner is offline or declares no limits. DiskCapped is false where the runner cannot cap a run's disk, so its disk limits are counted, never enforced. Reported is what the runner last said it has, never a limit.

type RunnerView added in v0.9.0

type RunnerView struct {
	ID    uuid.UUID   `json:"id"`
	Name  string      `json:"name"`
	State RunnerState `json:"state"`
	// Owner is set on the administrators' routes only; the caller's own list omits it.
	Owner string `json:"owner,omitempty"`
	// MintedBy names who minted the registration token this runner redeemed. MintedByEmail is that
	// person's email where Wardyn holds one, set on a person's own routes when someone else minted it,
	// so the console can say who without printing an opaque sign-in subject.
	MintedBy      string `json:"minted_by,omitempty"`
	MintedByEmail string `json:"minted_by_email,omitempty"`
	// Online is the hub's live view of a claimed runner and false for any other.
	Online bool `json:"online"`
	// KeyFingerprint is abbreviated for an unclaimed runner on its owner's own routes, so it cannot
	// be copied from there into the claim; KeyFingerprintAbbreviated says so.
	KeyFingerprint            string    `json:"key_fingerprint"`
	KeyFingerprintAbbreviated bool      `json:"key_fingerprint_abbreviated"`
	Version                   string    `json:"version,omitempty"`
	CreatedAt                 time.Time `json:"created_at"`
	// ClaimExpiresAt is when an unclaimed runner lapses; absent for any other state.
	ClaimExpiresAt *time.Time `json:"claim_expires_at,omitempty"`
	ClaimedAt      *time.Time `json:"claimed_at,omitempty"`
	LastSeenAt     *time.Time `json:"last_seen_at,omitempty"`
	RevokedAt      *time.Time `json:"revoked_at,omitempty"`
	// RevokedBy names who revoked the runner; RevokeReason is "" or RunnerRevokeNotMine. Both are
	// absent until it is revoked.
	RevokedBy    string `json:"revoked_by,omitempty"`
	RevokeReason string `json:"revoke_reason,omitempty"`
	// RevokedByEmail is that person's email where Wardyn holds one, set on a person's own list only.
	RevokedByEmail string `json:"revoked_by_email,omitempty"`
	// Posture is absent until the runner has reported; PostureSource is then runner_asserted.
	Posture           *RunnerPosture `json:"posture,omitempty"`
	PostureReportedAt *time.Time     `json:"posture_reported_at,omitempty"`
	PostureSource     string         `json:"posture_source,omitempty"`
	// Capacity, Classes and AllowedRoots are the claimed runner's last caps event, all runner_asserted.
	// They are read from this process's live endpoint, so like Online they are absent when the runner
	// has not connected to this replica, never a stored or guessed value.
	Capacity     *RunnerCapacity    `json:"capacity,omitempty"`
	Classes      []ConfinementClass `json:"classes,omitempty"`
	AllowedRoots []string           `json:"allowed_roots,omitempty"`
	// Kind is always RunnerKindSelfHosted: the one shape both kinds' reads share.
	Kind RunnerKind `json:"kind"`
	// Limits is what the runner's owner declared, which only they change; nil when not all of them are
	// set, and then the runner takes no run. Usage is what its runs hold now, the figures placement
	// counts, shown to its owner and to operators only.
	Limits *RunnerLimits `json:"limits"`
	Usage  RunnerUsage   `json:"usage"`
	// MachineName is the host name the runner last reported at registration or on connect. It is what
	// the runner says, never verified and never an identity; absent until it reports one.
	MachineName string `json:"machine_name,omitempty"`
	// Pools are the owner's live Self-Hosted pools the runner is a member of, by name; [] for none.
	Pools []RunnerPoolRef `json:"pools"`
}

RunnerView is a runner as the management routes show it to a person or an administrator. It is never sent to a runner. Every posture fact is what the runner said about itself.

type SSHPublicKey added in v0.5.0

type SSHPublicKey struct {
	Fingerprint   string     `json:"fingerprint"`
	Principal     string     `json:"principal"`
	Name          string     `json:"name"`
	PublicKey     string     `json:"public_key"` // authorized_keys line; never a secret
	Role          string     `json:"role"`
	RoleCheckedAt *time.Time `json:"role_checked_at,omitempty"`
	Capped        bool       `json:"capped"`
	CreatedAt     time.Time  `json:"created_at"`
	// RevSeq is the revocation sequence read when the key was registered; migration 0176 gave the keys
	// that existed their order once. 0 only for a key written by a replica that predates it, which a
	// revocation cutoff orders on CreatedAt. Never on the wire.
	RevSeq int64 `json:"-"`
}

SSHPublicKey is a human's registered public key for the SSH gateway, distinct from GrantSpec's ssh_key grant (a resident private key for git-over-SSH — see GrantSSHKey). This is the gateway's trust root: it authenticates an incoming connection ONLY against rows here, then authorizes owner-or-admin. Fingerprint is computed server-side from the parsed key, never client-supplied. Role is the registering session's own role, stamped at registration and refreshed on every OIDC login — a BOUNDED-STALE stamp, not a live check: a demoted admin's key keeps its override until RoleCheckedAt exceeds WARDYN_SSH_ROLE_TTL, or re-registration; nil RoleCheckedAt reads as infinitely stale. Capped marks a key registered while an admin's session was in the user view: Role stays user through every re-stamp, and the admin override is never granted.

type ServiceIdentity added in v0.9.0

type ServiceIdentity struct {
	Name         string               `json:"name"`
	Description  string               `json:"description,omitempty"`
	Sponsor      string               `json:"sponsor"`
	SponsorSince time.Time            `json:"sponsor_since"`
	UserType     string               `json:"user_type"`
	CreatedBy    string               `json:"created_by"`
	CreatedAt    time.Time            `json:"created_at"`
	SuspendedAt  *time.Time           `json:"suspended_at,omitempty"`
	SuspendedBy  string               `json:"suspended_by,omitempty"`
	RetiredAt    *time.Time           `json:"retired_at,omitempty"`
	RetiredBy    string               `json:"retired_by,omitempty"`
	State        ServiceIdentityState `json:"state"`
}

ServiceIdentity is a named, sponsored non-human principal (migration 0157, #1508). Its principal is ServicePrincipalPrefix + Name, and its tokens are ordinary api_tokens rows.

func (ServiceIdentity) BaseState added in v0.9.0

func (s ServiceIdentity) BaseState() ServiceIdentityState

BaseState is the state the record alone gives: retired, suspended or active. A sponsor whose session is not live makes an active identity sponsor_inactive, which only the API layer can read.

func (ServiceIdentity) Principal added in v0.9.0

func (s ServiceIdentity) Principal() string

Principal is the identity's principal, "service:<name>".

func (ServiceIdentity) Usable added in v0.9.0

func (s ServiceIdentity) Usable() bool

Usable reports whether the record alone lets a token authenticate: neither suspended nor retired. The sponsor's session is a second, separate check.

type ServiceIdentityState added in v0.9.0

type ServiceIdentityState string

ServiceIdentityState is where a service identity stands. It is derived from the row and the sponsor's session, never stored.

const (
	ServiceIdentityActive          ServiceIdentityState = "active"
	ServiceIdentitySuspended       ServiceIdentityState = "suspended"
	ServiceIdentityRetired         ServiceIdentityState = "retired"
	ServiceIdentitySponsorInactive ServiceIdentityState = "sponsor_inactive"
)

type SiteBranding added in v0.8.4

type SiteBranding struct {
	LogoPath string `json:"logo_path,omitempty"`
}

SiteBranding is SiteConfig.Branding. LogoPath is an absolute path on the wardynd host to an SVG or PNG file; it is read, checked as a console upload is, and stored as the branding logo at every apply. Naming it makes the file the logo's owner: the console offers no Remove logo while it is set. Taking it out of the document (and applying) removes the logo it delivered.

type SiteConfig

type SiteConfig struct {
	// Runners defaults off. PUT /site-config retains the stored value; the
	// dedicated audited runner enablement operation owns changes.
	Runners *RunnerSettings `json:"runners,omitempty"`
	// UpstreamProxySecretRef names a secret holding the corporate upstream
	// proxy URL, or "" for none. UpstreamProxyURL wins if both are set (not
	// rejected, so an operator can migrate between the two).
	UpstreamProxySecretRef string `json:"upstream_proxy_secret_ref,omitempty"`
	// UpstreamProxyURL is the corporate upstream proxy URL written IN THE
	// CLEAR (http only) — topology, not a credential. MUST NOT embed
	// userinfo (validateSiteConfig rejects that).
	UpstreamProxyURL string `json:"upstream_proxy_url,omitempty"`
	// UpstreamProxyNoProxy is the upstream proxy's BYPASS list (like
	// NO_PROXY): destinations wardyn-proxy dials DIRECTLY, matched by
	// host/domain-suffix or CIDR. Exists for a private-endpoint estate whose
	// corporate forward proxy has no route to the endpoint but this sidecar
	// does: an entry is for a host the sidecar can itself resolve and reach.
	// Where the corporate proxy is the only route, leave the host off this
	// list and declare it in InternalHosts alone.
	//
	// SECURITY: a bypassed dial still hits the unconditional private/
	// reserved-IP SSRF guard (InternalHosts is the only lift) and grants no
	// policy allow. "*" is REFUSED at write time; empty (default) is
	// unchanged behavior.
	UpstreamProxyNoProxy []string `json:"upstream_proxy_no_proxy,omitempty"`
	// ArtifactOverrides maps an ecosystem ("npm"|"pip"|"cargo"|"maven"|"go"|
	// "nuget") to its corporate artifact-registry redirect.
	//
	// Deprecated: superseded by EgressRedirects; kept only so a
	// pre-EgressRedirects document still decodes. handlePutSiteConfig and
	// migration 0030 each fold a set value into EgressRedirects once.
	ArtifactOverrides map[string]ArtifactOverride `json:"artifact_overrides,omitempty"`
	// EgressRedirects is the operator's outbound redirect list: FROM a
	// public/upstream URL or host TO a corporate-internal replacement.
	// Ecosystem set gets the full behavior (config file + substitution +
	// token injection); Ecosystem "" is network-only (no config file).
	EgressRedirects []EgressRedirect `json:"egress_redirects,omitempty"`
	// ScmHosts are the operator's default SCM hosts (e.g. "dev.azure.com",
	// "github.example.com") the SCM Provider step / egress bundling consult.
	ScmHosts []string `json:"scm_hosts,omitempty"`
	// Integrations are the operator-configured external connections (AI
	// providers, SCM hosts, generic connections), the generalized
	// replacement the legacy fields above are migrating toward. Both are
	// read; nothing removes the legacy fields yet.
	Integrations IntegrationList `json:"integrations,omitempty"`
	// InternalHosts declares an internal hostname the proxy's unconditional
	// private/reserved-IP SSRF guard would otherwise refuse; each entry
	// LIFTS the guard for matching addresses.
	//
	// SECURITY: leave CIDRs empty unless you know the sandbox's actual
	// addresses — empty is the full liftable set, the safe default.
	// Loopback/link-local/metadata/multicast/NAT64 stay denied regardless.
	// This only lifts the SSRF builtin; policy still decides reachability.
	// Admin-only, validated against ipguard.Liftable. Empty => no lift.
	InternalHosts []InternalHost `json:"internal_hosts,omitempty"`
	// Egress is the org's egress-grading policy. A POINTER for the same
	// byte-identical-GET reason as the provider blocks. Written ONLY through
	// PUT /governance/egress-baseline (a governance write); PUT /site-config
	// carries an absent or unchanged block forward and refuses a changed one.
	Egress *SiteEgress `json:"egress,omitempty"`
	// WorkspaceProviders is the org's workspace-provider POLICY: which git
	// hosts a run may clone from, with which credential lanes, plus storage
	// ceilings. A POINTER since a value struct's omitempty is a no-op
	// (would break the byte-identical-GET claim). Nil is legacy open mode;
	// PUT /site-config's absent-key/explicit-{} makes it MDM-deliverable.
	WorkspaceProviders *WorkspaceProviders `json:"workspace_providers,omitempty"`
	// AgentProviders is the org's agent-enablement POLICY: which coding
	// agents this deployment offers, each one's model-access lane, and
	// whether that credential is shared or per-person. A POINTER for the
	// same byte-identical-GET reason as WorkspaceProviders, with the same
	// absent-key/explicit-{} write terms. Nil is legacy open mode.
	AgentProviders *AgentProviders `json:"agent_providers,omitempty"`
	// ModelProviders is the org's model-provider configuration — a POINTER
	// for the same byte-identical-GET reason. Nil keeps today's
	// lane-resolution path.
	ModelProviders *ModelProviders `json:"model_providers,omitempty"`
	// SignInHelpText and SignInHelpURL are the admin's "what to do next",
	// shown on the sign-in page for refusals a person can't clear alone.
	//
	// SECURITY: PUBLIC — the anonymous /healthz publishes both, so they must
	// never name internal systems. Text <=1,000 chars, no control chars;
	// URL is http(s) only.
	SignInHelpText string `json:"sign_in_help_text,omitempty"`
	SignInHelpURL  string `json:"sign_in_help_url,omitempty"`
	// PolicyHelp is the contact a signed-in person is shown when a deployment-wide
	// rule (no profile binds them) refuses them. A POINTER for the same
	// byte-identical-GET reason as the provider blocks.
	//
	// NOT public: unlike the sign-in help pair, /healthz never publishes it.
	// Validated by policyref.Validate at PUT; a stored value is re-validated by
	// policyref.Project on every read.
	PolicyHelp *policyref.Contact `json:"policy_help,omitempty"`
	// Branding is the part of the console branding (#1215) a site config can
	// carry: today only the logo, as a file wardynd reads when this document is
	// applied. A POINTER for the same byte-identical-GET reason as the provider
	// blocks; nil is "the site config says nothing about branding". Name and
	// colours stay with the Branding card (PUT /branding/settings).
	Branding *SiteBranding `json:"branding,omitempty"`
	// Components is the org's custom-component POLICY. A POINTER for the same
	// byte-identical-GET reason as the provider blocks. Nil — never set, or a
	// document stored before the block existed — is the owner-decided default:
	// no autonomy cap, env-var and file delivery allowed, no CC3 floor for
	// component credentials.
	Components *ComponentSettings `json:"components,omitempty"`
	// EffectiveScmHosts is READ-ONLY, SERVER-OWNED: ScmHosts minus hosts a
	// provider row claims, union enabled providers' hosts. A real field
	// (not a wrapper key) so a get|set round trip still decodes under
	// DisallowUnknownFields. PUT ignores a submitted value; never stored.
	EffectiveScmHosts []string `json:"effective_scm_hosts,omitempty"`
	// WithheldScmHosts is READ-ONLY, SERVER-OWNED, on the same terms as
	// EffectiveScmHosts: each host a DISABLED provider row claims that
	// EffectiveScmHosts therefore leaves out, with the row that withholds it, so
	// an admin is told why a host is missing rather than left to look for it.
	// Projected on read, ignored on PUT, never stored.
	WithheldScmHosts []WithheldScmHost `json:"withheld_scm_hosts,omitempty"`
	// OnboardingCompletedAt records when an operator finished (or left) the
	// Getting Started funnel on THIS INSTALL. Nil until then.
	//
	// Server-side because a localStorage flag outlived a wiped database and
	// was scoped per-origin (127.0.0.1 vs localhost disagreed).
	//
	// NOT settable via PUT /site-config — the handler ignores a submitted
	// value and carries the stored one forward; a dropped value is reported
	// as onboarding_completed_at_ignored.
	OnboardingCompletedAt *time.Time `json:"onboarding_completed_at,omitempty"`
}

SiteConfig is the operator-wide, admin-authored baseline every run inherits: a corporate upstream proxy, egress redirects (package-registry mirrors and any outbound URL/host/IP redirect), and default SCM hosts. One singleton per operator; GetSiteConfig returns the zero value ("unconfigured") when none has been written yet.

SECURITY: secret VALUES never live here — only secret NAMES (refs) the broker/proxy resolve at dispatch/injection time, mirroring GrantSpec.Scope.

type SiteEgress added in v0.9.0

type SiteEgress struct {
	// BaselineHosts are exact hostnames the composer grades as baseline egress,
	// beside its built-in public set: an allowlist holding only these is not
	// graded OPEN, and an api_key to one does not raise the CC3 floor.
	//
	// SECURITY: this LOWERS the egress grade and the confinement floor, so it is
	// a governance write — security operators only, held for a second human where
	// WARDYN_GOVERNANCE_SECOND_HUMAN is on, audited with the before/after sets,
	// like the InternalHost.Baseline marks it shares one writer with.
	// Exact lowercase hostnames only: no wildcard, scheme, port or IP. It grades;
	// it never allows — a host must still pass allowed_domains.
	BaselineHosts []string `json:"baseline_hosts,omitempty"`
}

SiteEgress is SiteConfig.Egress.

type Source added in v0.5.0

type Source struct {
	ID   uuid.UUID  `json:"id"`
	Kind SourceKind `json:"kind"`
	// Locator is the identity: a host dir path (local_dir, trimmed) or
	// canonical repo slug/clone URL (repo, lowercased); with Ref, unique in the store.
	Locator string `json:"locator"`
	// Ref is an optional git ref (repo only); part of identity, so the same repo at two refs is two sources with two contracts.
	Ref          string                          `json:"ref,omitempty"`
	Name         string                          `json:"name"`
	Requirements map[string]WorkspaceRequirement `json:"requirements,omitempty"` // this source's own contract; see the type doc above
	// Profile is this source's scan result, opaque here (never interpreted, only
	// persisted/returned). json.RawMessage, not []byte: a bare []byte gets
	// base64-encoded by encoding/json (WIRE-2), shipping it to GET /sources as
	// an opaque string instead of real JSON.
	Profile     json.RawMessage `json:"profile,omitempty"`
	Status      WorkspaceStatus `json:"status"`                  // scan lifecycle: pending_scan | scanning | scanned | error
	ActiveRunID *uuid.UUID      `json:"active_run_id,omitempty"` // fences this source's in-flight scan run
	CreatedAt   time.Time       `json:"created_at"`
	UpdatedAt   time.Time       `json:"updated_at"`
}

Source is one tier-1 library entry: a repo or dir configured once and attached to many workspaces. Requirements is what it expects of any sandbox using it, in Workspace.Requirements's "<type>:<key>" grammar, minus integration: keys (those compose only at the tier-3 aggregate).

type SourceKind added in v0.5.0

type SourceKind string

SourceKind is a library source's kind. Ephemeral scratch isn't a kind here: it's a per-workspace inline attachment, never a library entity.

const (
	SourceLocalDir SourceKind = "local_dir"
	SourceRepo     SourceKind = "repo"
)

type StorageEnforcement added in v0.7.0

type StorageEnforcement string

StorageEnforcement names WHAT ACTUALLY BINDS BYTES for a drive, so a size nothing enforces is never rendered as a limit: "Wardyn never enforces a drive's size itself. On Kubernetes the size is the volume request and the storage class decides whether it binds. On Docker a managed drive has no byte cap. A share is bounded by its own quota. The size you see is the allocation, not a guarantee." Surfaced via runner.Capabilities.EphemeralDiskEnforcement on the admin setup status.

const (
	// StorageEnforcementFilesystem: the filesystem itself refuses the write
	// (XFS project quota). NOTHING in v1 reports this yet.
	StorageEnforcementFilesystem StorageEnforcement = "filesystem"
	// StorageEnforcementEviction: the kubelet enforces it, MEASURED
	// PERIODICALLY — over the limit the POD IS KILLED, not the write; the
	// agent never sees ENOSPC and in-flight work is lost.
	StorageEnforcementEviction StorageEnforcement = "eviction"
	// StorageEnforcementRequest: the size is a scheduling REQUEST — a block
	// storage class binds it, an NFS/EFS provisioner enforces nothing.
	StorageEnforcementRequest  StorageEnforcement = "request"
	StorageEnforcementExternal StorageEnforcement = "external" // something outside Wardyn binds it; Wardyn claims nothing
	StorageEnforcementNone     StorageEnforcement = "none"     // nothing binds it (e.g. a Docker named volume)
)

func EnforcementFor added in v0.7.0

func EnforcementFor(b DriveBackend) StorageEnforcement

EnforcementFor maps a backend to what actually binds its bytes. An unknown backend reads as `none` — the one answer that cannot mislead an admin into trusting a cap.

type StorageProviders added in v0.7.2

type StorageProviders struct {
	Ephemeral *EphemeralProvider `json:"ephemeral,omitempty"`  // bounds writable scratch when a run mounts no drive
	UserDrive *UserDriveProvider `json:"user_drive,omitempty"` // org switch and ceiling over user drives
}

StorageProviders is the file-system half of WorkspaceProviders; each nil sub-block is legacy behaviour for that half.

type TemplateAction added in v0.9.0

type TemplateAction string

TemplateAction is what a caller asks to do with a template.

const (
	// TemplateRead is list, direct and versioned lookup, and use.
	TemplateRead TemplateAction = "read"
	// TemplateWrite is create, update, delete, and the target side of a copy
	// or publish.
	TemplateWrite TemplateAction = "write"
)

type TemplateActor added in v0.9.0

type TemplateActor struct {
	// Person is the authenticated principal's canonical subject.
	Person string
	// OrgAdmin is the current organisation-administrator tier (the one that
	// writes the organisation's presets, policies and components).
	OrgAdmin bool
	// Groups is the verified group snapshot, each entry canonical. Nil means
	// the snapshot is unavailable (a cookie from before groups were recorded).
	Groups []string
	// GroupsTruncated reports a snapshot that dropped entries or never arrived
	// in full (cookie byte cap, an IdP groups overage, a token whose
	// completeness was not recorded). Group authority cannot be answered from
	// it, in either direction.
	GroupsTruncated bool
	// GroupAdminGrants are the groups the store holds a TemplateGroupAdmin
	// grant for this person on.
	GroupAdminGrants []string
}

TemplateActor is the caller, as the server verified them for this request.

type TemplateDenial added in v0.9.0

type TemplateDenial string

TemplateDenial is why TemplateAuthorize refused. The zero value is allowed.

const (
	TemplateAllowed TemplateDenial = ""
	// TemplateDenyNotOwner: someone else's personal template. A read answers
	// it as a missing template, so the id is no existence oracle.
	TemplateDenyNotOwner TemplateDenial = "not_owner"
	// TemplateDenyNotOrgAdmin: org scope is written by an organisation administrator only.
	TemplateDenyNotOrgAdmin TemplateDenial = "not_org_admin"
	// TemplateDenyNotGroupMember: group scope is read by the group's members only.
	TemplateDenyNotGroupMember TemplateDenial = "not_group_member"
	// TemplateDenyNotGroupAdmin: group scope is written by that group's administrators only.
	TemplateDenyNotGroupAdmin TemplateDenial = "not_group_admin"
	// TemplateDenyGroupUnverified: the group snapshot is unavailable or
	// truncated, so membership and administration cannot be established.
	TemplateDenyGroupUnverified TemplateDenial = "group_unverified"
	// TemplateDenyBadOwner: the owner is malformed (a refusal, never a default).
	TemplateDenyBadOwner TemplateDenial = "bad_owner"
)

func TemplateAuthorize added in v0.9.0

func TemplateAuthorize(actor TemplateActor, owner TemplateOwner, action TemplateAction) TemplateDenial

TemplateAuthorize decides one action on a template of the given owner.

  • Person scope: the owner reads and writes; nobody else does, an organisation administrator included.
  • Org scope: any signed-in person reads; an organisation administrator writes.
  • Group scope: a verified member reads; a verified member who holds the group's template-management grant writes. A grant on group A says nothing about group B, and a grant outlives nothing: once the person leaves the group in the verified snapshot, they are no administrator of it either. An organisation administrator is not implicitly a group administrator; they delegate through the grant.

Group answers need a complete snapshot. Nil or truncated gives TemplateDenyGroupUnverified, never an empty-groups reading.

func TemplateCopyAuthorize added in v0.9.0

func TemplateCopyAuthorize(actor TemplateActor, from, to TemplateOwner) TemplateDenial

TemplateCopyAuthorize decides copying or publishing a template from one owner to another: the caller must read the source and write the target. Owning a personal template does not by itself let its content reach a wider audience; the store still re-checks every reference the copy discloses.

type TemplateGroupAdmin added in v0.9.0

type TemplateGroupAdmin struct {
	Group     string `json:"group_id"`
	Person    string `json:"person"`
	GrantedBy string `json:"granted_by,omitempty"`
	// Label is the group's display label, set by an organisation
	// administrator. Display only: authority follows Group.
	Label string `json:"label,omitempty"`
}

TemplateGroupAdmin is a bounded template-management grant: Person may publish and edit templates for Group, and nothing else. An organisation administrator writes and revokes it through an audited route. It is not a general administrator role, and it is not implied by group membership.

func (TemplateGroupAdmin) Canonical added in v0.9.0

func (g TemplateGroupAdmin) Canonical() (TemplateGroupAdmin, bool)

Canonical returns the grant in the form TemplateAuthorize compares: the group as the verified snapshot spells it (CanonicalGroupSubject) and the person as every user-subject boundary stores it (CanonicalUserSubject). ok is false for a group no snapshot could carry or an empty person, which a writer must refuse. The admin writer stores only what this returns, so a grant written as "DevTeam" authorises exactly the group the snapshot calls "devteam".

type TemplateKind added in v0.9.0

type TemplateKind string

TemplateKind is what a template holds. A policy is a template whose intent holds only run-wide network, credential and access-rule keys under inline_policy, laid onto the user's defaults at use.

const (
	TemplateKindTemplate TemplateKind = "template"
	TemplateKindPolicy   TemplateKind = "policy"
)

func (TemplateKind) Valid added in v0.9.0

func (k TemplateKind) Valid() bool

Valid reports whether k is one of the two kinds.

type TemplateOwner added in v0.9.0

type TemplateOwner struct {
	Scope TemplateScope `json:"scope"`
	// Person is the owning person's canonical subject; set for person scope only.
	Person string `json:"owner_id,omitempty"`
	// Group is the canonical group subject the authenticated group snapshot
	// carries (CanonicalGroupSubject); set for group scope only. Authority
	// follows this identifier, never a display name.
	Group string `json:"group_id,omitempty"`
}

TemplateOwner is the trusted catalogue key of one template: the scope and the identity that scope is bound to. The server assigns it from the authenticated request. A template document never carries one, so imported text cannot name its own owner or audience.

func (TemplateOwner) Valid added in v0.9.0

func (o TemplateOwner) Valid() bool

Valid reports whether the owner is well formed for its scope: person scope names a person and no group, group scope a group and no person, org scope neither.

type TemplateRoute added in v0.9.0

type TemplateRoute struct {
	Route       string
	RouterClass string
	Person      TemplateTier
	Org         TemplateTier
	Group       TemplateTier
}

TemplateRoute classifies one template route: the router tier that gates reaching the handler, and the per-scope authority the handler then enforces with TemplateAuthorize. RouterClass uses authz_test.go's route class names and TestTemplateRoutesMatchRouteMatrix holds each row to routeMatrix and the router.

type TemplateScope added in v0.9.0

type TemplateScope string

TemplateScope is who a template is for.

const (
	// TemplateScopePerson is private to the person who owns it.
	TemplateScopePerson TemplateScope = "person"
	// TemplateScopeOrg is offered to everyone signed in to the organisation.
	TemplateScopeOrg TemplateScope = "org"
	// TemplateScopeGroup is offered to the members of one group.
	TemplateScopeGroup TemplateScope = "group"
)

func (TemplateScope) Valid added in v0.9.0

func (s TemplateScope) Valid() bool

Valid reports whether s is one of the three scopes.

type TemplateTier added in v0.9.0

type TemplateTier string

TemplateTier is the authority a template route asks for, per scope.

const (
	// TemplateTierNone: the route does not act on that scope.
	TemplateTierNone TemplateTier = ""
	// TemplateTierOwner: the person who owns the template.
	TemplateTierOwner TemplateTier = "owner"
	// TemplateTierMember: any signed-in person.
	TemplateTierMember TemplateTier = "member"
	// TemplateTierOrgAdmin: an organisation administrator.
	TemplateTierOrgAdmin TemplateTier = "org_admin"
	// TemplateTierGroupMember: a verified member of that group.
	TemplateTierGroupMember TemplateTier = "group_member"
	// TemplateTierGroupAdmin: a verified administrator of that group, and of no other.
	TemplateTierGroupAdmin TemplateTier = "group_admin"
)

type TokenDisposition added in v0.9.0

type TokenDisposition string

TokenDisposition is where an admin-minted token stands (a token whose minter is not its owner). It is derived at read from the token, the invitations that name it and its move row, never stored (#1508).

const (
	// TokenNeedsDecision: live, with no pending invitation replacing it and no move. Nothing retires it.
	TokenNeedsDecision TokenDisposition = "needs_decision"
	// TokenAwaitingOwner: live, and a pending invitation to its owner names it as the token it replaces.
	TokenAwaitingOwner TokenDisposition = "awaiting_owner"
	// TokenReplacedByOwner: revoked by the claim of the invitation that replaced it.
	TokenReplacedByOwner TokenDisposition = "replaced_by_owner"
	// TokenLinkedToService: live, and an admin linked it to the service identity that replaces it.
	TokenLinkedToService TokenDisposition = "linked_to_service_identity"
	// TokenMovedToService: revoked, with a move row.
	TokenMovedToService TokenDisposition = "moved_to_service_identity"
	// TokenRevoked: revoked, with neither a claim nor a move behind it.
	TokenRevoked TokenDisposition = "revoked"
)

type TokenMove added in v0.9.0

type TokenMove struct {
	TokenID         uuid.UUID `json:"token_id"`
	ServiceIdentity string    `json:"service_identity"`
	LinkedBy        string    `json:"linked_by"`
	LinkedAt        time.Time `json:"linked_at"`
}

TokenMove is the row POST /tokens/{id}/move writes: an admin-minted token linked to the service identity that replaces it. The token stays live until an admin revokes it.

type TokenMoved added in v0.9.0

type TokenMoved struct {
	TokenMove
	Disposition TokenDisposition `json:"disposition"`
}

TokenMoved is the answer to POST /tokens/{id}/move.

type ToolEffect added in v0.7.0

type ToolEffect string

ToolEffect is what a matching ToolRule does with the call.

const (
	// ToolAllow runs the call with no human in the loop. It is still recorded.
	ToolAllow ToolEffect = "allow"
	// ToolHold raises an approval and blocks — today's behaviour for every call.
	ToolHold ToolEffect = "hold"
	// ToolDeny refuses the call without asking anyone.
	ToolDeny ToolEffect = "deny"
)

func ToolEffectForHarness added in v0.9.0

func ToolEffectForHarness(sets []HarnessToolRules, harness, tool string) (ToolEffect, bool)

ToolEffectForHarness reports what the rules of ONE harness say about a tool call, and whether any rule decided it. Match order: the exact tool, then that harness's own default. Another harness's rules never apply, and a harness with no entry has no rules (ok=false), so its call is held for a human exactly as a run with no rules is today; it never inherits a sibling's allow.

type ToolRule added in v0.7.0

type ToolRule struct {
	Tool   string     `json:"tool"`
	Effect ToolEffect `json:"effect"`
}

ToolRule is one entry in ToolRules: a tool NAME and what to do with it. Tool is matched exactly and case-sensitively against the name the harness reports (e.g. "Read", "Bash", "WebFetch") — deliberately not a pattern, since a glob invites a rule that reads narrower than it matches, and the set of tools a harness exposes is small and enumerable. The one wildcard is the literal "*", which sets the default for unmatched tools.

type UIApp added in v0.6.0

type UIApp struct {
	Name string `json:"name"`
	Port int    `json:"port"`
	Path string `json:"path,omitempty"`
}

UIApp is one declared in-sandbox HTTP app the UI gateway may relay to (RunPolicySpec.UIApps). Three fields, no command string: Name identifies the app to the operator and the launcher convention (/usr/local/bin/wardyn-ui-<name>), constrained to a short lower-case slug since it reaches a filesystem path and a URL query (never a slash, space, or ".."); Port is the port the app listens on INSIDE the sandbox, on 127.0.0.1 (the sandbox has no other reachable address); Path is where the app's UI lives, the landing path after the gateway's ticket handoff — empty means "/".

func (UIApp) PathOrRoot added in v0.6.0

func (a UIApp) PathOrRoot() string

PathOrRoot is Path with the empty default applied.

type UserDrive added in v0.7.0

type UserDrive struct {
	ID      uuid.UUID    `json:"id"`
	Name    string       `json:"name"`
	Backend DriveBackend `json:"backend"`
	// HostRoot is the operator-mounted tree a host_path drive binds a
	// subdirectory of; empty for every other backend. Authored by an admin
	// and bound into OTHER PEOPLE's sandboxes, so the security ceiling is the
	// env allowlist (UserDriveHostRootCheck), never this column alone.
	HostRoot     string       `json:"host_root,omitempty"`
	StorageClass string       `json:"storage_class,omitempty"` // k8s_pvc provisioner to request; "" = cluster default
	HomeTemplate HomeTemplate `json:"home_template"`
	// ObjectScheme names which half of a minted object name carries the
	// drive — see DriveObjectScheme. NOT client-authored: on the wire only so
	// a GET and round-tripped PUT agree.
	ObjectScheme DriveObjectScheme `json:"object_scheme,omitempty"`
	// SizeMiB is the ALLOCATION, not a guarantee — see StorageEnforcement. 0
	// means "no allocation shown" except on k8s_pvc, where it IS
	// requests.storage.
	SizeMiB   int          `json:"size_mib,omitempty"`
	Writable  bool         `json:"writable,omitempty"`
	Reclaim   DriveReclaim `json:"reclaim"`
	CreatedAt time.Time    `json:"created_at"`
	UpdatedAt time.Time    `json:"updated_at"`
	CreatedBy string       `json:"created_by,omitempty"`
	// OwnerSubject is set only on a member drive: the canonical subject of the
	// one person who owns it. Written once on create and never decoded from a
	// request; "" on every admin row.
	OwnerSubject string `json:"owner,omitempty"`
	// DeleteStartedAt is a member drive's delete tombstone: while set, the
	// drive is being destroyed and is treated as absent.
	DeleteStartedAt *time.Time `json:"-"`
}

UserDrive is one drive (migration 0054's user_drives row), admin-registered unless OwnerSubject is set. Name is the human handle, unique among admin drives and per owner among member drives: what an admin allocates by, what the console lists, and the last tie-break in the resolver's ORDER BY. Writable defaults to FALSE; a grant or run may narrow it, never widen it.

type UserDriveGrant added in v0.7.0

type UserDriveGrant struct {
	ID              uuid.UUID             `json:"id"`
	SubjectType     CapabilitySubjectType `json:"subject_type"`
	Subject         string                `json:"subject"`
	DriveID         uuid.UUID             `json:"drive_id"`
	Priority        int                   `json:"priority"`                    // breaks ties WITHIN a tier (higher wins); does NOT cross tiers
	SizeMiBOverride int                   `json:"size_mib_override,omitempty"` // 0 means "use the drive's"
	// WritableOverride is TRI-STATE: nil is "use the drive's posture",
	// explicit false is "read only even on a writable drive".
	WritableOverride *bool `json:"writable_override,omitempty"`
	// HomeOverride names this subject's directory verbatim. USER TIER ONLY:
	// on a group/all row it would give every member ONE home.
	HomeOverride string `json:"home_override,omitempty"`
	// Enabled is the admin's off switch. A disabled row still wins its tier:
	// the winner's bit folds to ResolvedDrive.Paused rather than silently
	// falling through to the wider row beneath it.
	Enabled   bool      `json:"enabled"`
	CreatedAt time.Time `json:"created_at"`
	CreatedBy string    `json:"created_by,omitempty"`
}

UserDriveGrant allocates one drive to one subject (migration 0054's user_drive_grants row). SubjectType REUSES CapabilitySubjectType, the same vocabulary capability_grants/governance_assignments use. Every override is a column on the BINDING row, since it has no meaning apart from the (subject, drive) pair.

type UserDriveHostRootCheck added in v0.7.0

type UserDriveHostRootCheck func(hostRoot string) error

UserDriveHostRootCheck is the signature of the deployment's ENV CEILING over admin-authored host_path drives (WARDYN_USER_DRIVE_HOST_ROOTS), returning nil when hostRoot is inside an allowed root.

A hook rather than a rule inside ValidateUserDrive: this package cannot read the environment, and site config (a full-replace row) is the wrong home for a security ceiling. An UNSET env means no host_path drive may be authored at all.

type UserDriveListItem added in v0.7.0

type UserDriveListItem struct {
	UserDrive
	GrantCount int `json:"grant_count"`
}

UserDriveListItem is one row of the admin Drives table. GrantCount is a READ-SIDE derivation kept off UserDrive so a GET/PUT round-trip cannot resurrect a stale count — what makes the delete-if-no-grants check honest.

type UserDriveProvider added in v0.7.2

type UserDriveProvider struct {
	Disabled   bool `json:"disabled,omitempty"`     // turns drives off deployment-wide, negative-sense like GitProvider.Disabled, so zero value is today
	MaxSizeMiB int  `json:"max_size_mib,omitempty"` // largest drive allowed, refused at admin write boundary and clamped at resolve; 0 = no ceiling
	// MemberCreate is the switch for people creating their own drives. nil = off.
	MemberCreate *MemberDrivePolicy `json:"member_create,omitempty"`
}

UserDriveProvider is the org-level user-drive switch and ceiling. Disabled is a different question from GovernanceLimits.DenyUserDrive; the two are checked in order: this org switch first (422, refused-backend), then the per-profile door (403 + authz.denied).

type UserType added in v0.8.0

type UserType struct {
	// ID is the immutable slug role-map values, grant subjects and token
	// stamps refer to.
	ID          string `json:"id"`
	Name        string `json:"name"`
	Description string `json:"description"`
	// Priority breaks a sign-in tie between two custom types (higher wins).
	// Always 0 on the built-in type, which never takes part in a tie.
	Priority  int       `json:"priority"`
	BuiltIn   bool      `json:"built_in"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
	CreatedBy string    `json:"created_by,omitempty"`
}

UserType is one row of the user_types table: an org-defined kind of person ("Portfolio manager"). It carries no controls of its own — governance assignments, capability grants and drive grants name it as a subject.

type WithheldScmHost added in v0.8.4

type WithheldScmHost struct {
	Host         string          `json:"host"`
	ProviderID   string          `json:"provider_id"`
	ProviderKind GitProviderKind `json:"provider_kind"`
}

WithheldScmHost is one entry of SiteConfig.WithheldScmHosts: a host kept out of the effective set because the named provider row is turned off. The row is named the way a refused clone names it, by kind and id.

type Workspace

type Workspace struct {
	ID   uuid.UUID `json:"id"`
	Name string    `json:"name"`
	// Sources is the composition: one-or-more local_dir/repo/ephemeral entries.
	Sources []WorkspaceSource `json:"sources"`
	// BaseImage is the base-image choice; nil means the platform default
	// convention image for the detected/scanned stack.
	BaseImage *WorkspaceBaseImage `json:"base_image,omitempty"`
	// Requirements is the declared requirements contract, keyed by a fixed
	// grammar of "<type>:<key>":
	//
	//	secret:NAME       a named secret must be resolvable, e.g. "secret:acme-anthropic-key"
	//	egress:host       the host must be reachable (bare host or "*."-wildcard), e.g. "egress:api.github.com"
	//	write:/host/path  the HOST path must be mounted writable, e.g. "write:/home/user/repo"
	//	integration:ID    the named Integration must be granted, e.g. "integration:git_host:github.com"
	//
	// Split on the FIRST colon only, since a write:/integration: key may itself
	// contain colons. An overlay on attached sources' own contracts;
	// EffectiveRequirements is the folded result a run consumes.
	Requirements map[string]WorkspaceRequirement `json:"requirements,omitempty"`

	// Attachments is the tier-3 composition: ordered source refs and inline
	// ephemeral rows with per-attachment target/writable/overrides. Empty on
	// a pre-split row, falling back to Sources.
	Attachments []WorkspaceAttachment `json:"attachments,omitempty"`
	// BaseImageID references the shared base-image catalog (tier 2); nil means
	// "recommended" (per-workspace derived build) by design, not absence.
	BaseImageID *uuid.UUID `json:"base_image_id,omitempty"`
	// EffectiveRequirements is derived, read-only, never persisted: the
	// FoldWorkspaceContract result over attachments + source contracts + the
	// overlay above (equals Requirements on a pre-split row).
	EffectiveRequirements map[string]WorkspaceRequirement `json:"effective_requirements,omitempty"`

	// Kind mirrors Sources[0].Type (single-source only).
	Kind WorkspaceKind `json:"kind"`
	// Source mirrors Sources[0]'s host path (local_dir) or repo slug/URL (repo, single-source only).
	Source string `json:"source"`
	// Ref mirrors Sources[0].Ref (single-source repo only).
	Ref string `json:"ref,omitempty"`
	// DefaultTarget mirrors Sources[0].Target (single-source only).
	DefaultTarget string `json:"default_target,omitempty"`

	// Profile is internal/workspacescan's WorkspaceProfile, opaque here; nil/empty until scanned.
	Profile json.RawMessage `json:"profile,omitempty"`
	// ImageRef is the resolved/generated image for this workspace's profile; empty until scanned/built.
	ImageRef string `json:"image_ref,omitempty"`
	// BuiltProfileHash is the profile hash ImageRef was built from, the
	// build-once/reuse-many cache key (rebuild only when it changes).
	BuiltProfileHash string `json:"built_profile_hash,omitempty"`
	// ImageBuild records what built ImageRef; written only by SetWorkspaceBuiltImage.
	ImageBuild *ImageBuild `json:"-"`
	// ApprovedEgress is the operator-owned list of egress hosts explicitly
	// promoted, unioned into a run's allowlist. Never written by a scan;
	// cleared when the composition changes.
	ApprovedEgress []string `json:"approved_egress,omitempty"`
	// DeniedEgress mirrors ApprovedEgress: hosts explicitly blocked, folded
	// into denied_domains at create (deny beats allow/allow_all_egress/a
	// runtime approval alike). Unlike ApprovedEgress, not cleared on source
	// change (migration 0040) — a deny needs no re-review.
	DeniedEgress []string `json:"denied_egress,omitempty"`
	// EgressEditedAt is when an operator last REPLACED one of the two lists
	// above — read by the boot heal, which without it would let a decided
	// `always` approval re-widen a just-removed host every restart. Stamped
	// only by those two scoped setters.
	EgressEditedAt *time.Time `json:"egress_edited_at,omitempty"`
	// ActiveRunID is the in-flight scan/record run, so the import panel can
	// poll "is my step still running" without scanning all runs.
	ActiveRunID *uuid.UUID `json:"active_run_id,omitempty"`
	// RecordResults is the per-task Record Mode state; opaque
	// map[string]api-owned JSON. Written only via SetWorkspaceRecordResult;
	// cleared on composition change.
	RecordResults json.RawMessage `json:"record_results,omitempty"`
	// LLMCred is the model/harness credential binding a run inherits (see
	// WorkspaceLLMCred). Written only via the scoped SetWorkspaceLLMCred.
	LLMCred *WorkspaceLLMCred `json:"llm_cred,omitempty"`
	// OwnedBy is the member who created this workspace (lowercased OIDC sub or
	// email); empty means operator-owned. TRUST BOUNDARY: ownsWorkspaceOrAdmin
	// keys on non-empty — only that member or an admin may read/write it, else
	// the same 404 as a missing workspace. Set only by handleCreateWorkspace.
	OwnedBy   string          `json:"owned_by,omitempty"`
	Status    WorkspaceStatus `json:"status"`
	CreatedAt time.Time       `json:"created_at"`
	UpdatedAt time.Time       `json:"updated_at"`
	// AvailableToYou (#1267): per-caller bit — would the same decide path a
	// launch runs admit THIS workspace for this reader? Computed fresh per GET
	// (workspaceAvailableToCaller), never stored or accepted on write; always
	// true for an operator. Pointer with omitempty: only the two GET stampers
	// set it, so a plain bool would ship a false "unavailable" elsewhere — the
	// console's "absent means fall back" contract needs the omitted key.
	AvailableToYou *bool `json:"available_to_you,omitempty"`
}

Workspace is an onboarded, admin-reviewed composition of one-or-more sources (local_dir | repo | ephemeral — the ephemeral floor means Sources is never empty), a base image choice, and a requirements contract. Import scans the composition once into an opaque profile (internal/workspacescan WorkspaceProfile); runs thereafter reference the workspace by id. Profile/BuiltProfileHash are empty until Status leaves pending_scan.

type WorkspaceAttachment added in v0.5.0

type WorkspaceAttachment struct {
	SourceID  *uuid.UUID `json:"source_id,omitempty"`
	Ephemeral bool       `json:"ephemeral,omitempty"` // marks an inline scratch row (never a library entity)
	Target    string     `json:"target,omitempty"`    // in-sandbox mount/clone/scratch path for this attachment
	Writable  bool       `json:"writable,omitempty"`  // opts a local_dir attachment into read-write, per-attachment not per-source
	// Overrides is this workspace's stance on requirement keys the attached
	// source declares: reqKey -> off|optional|required. Never edits the
	// shared source; keys the source doesn't declare are ignored harmlessly.
	Overrides map[string]string `json:"overrides,omitempty"`
}

WorkspaceAttachment is one tier-3 composition row: either a library source (SourceID set) or an inline ephemeral row (Ephemeral true, SourceID nil); order is load-bearing, attachments[0] is the primary.

type WorkspaceBaseImage added in v0.5.0

type WorkspaceBaseImage struct {
	// Kind is "recommended" (convention image for the detected stack),
	// "registry" (operator-picked, named by Image), "custom" (Image is a base,
	// see Steps), or "byo" (Image used verbatim).
	Kind string `json:"kind"`
	// Image is the base image reference. Meaning depends on Kind: the FROM for
	// "custom", the image itself for "registry"/"byo", unused for "recommended".
	Image string `json:"image,omitempty"`
	// Steps is CATALOG IDENTITY ONLY, never applied to an image. SECURITY:
	// running operator-authored RUN lines would be host-side RCE
	// (assertWrapSafeBase also refuses ONBUILD), so "custom" builds
	// identically to "byo". Must stay: part of base_images' UNIQUE(kind, image)
	// index; removing it mints duplicate rows.
	Steps []string `json:"steps,omitempty"`
}

WorkspaceBaseImage is a Workspace's base-image choice: what the sandbox's container image is built FROM, independent of its Sources.

type WorkspaceKind

type WorkspaceKind string

WorkspaceKind discriminates an onboarded Workspace's legacy, single-source shape; a DERIVED READ-ONLY value mirroring Sources[0] (see Workspace.Kind) — a multi-source Workspace has no single Kind.

const (
	WorkspaceKindLocalDir WorkspaceKind = "local_dir"
	WorkspaceKindRepo     WorkspaceKind = "repo"
	// WorkspaceKindContainer was the pre-composition-model shape (base image, no
	// mount); migration 0029 rewrites every stored row into the composition
	// shape. Deprecated: kept as an anchor for 0029's backfill SQL and any
	// pre-0029 payload that still decodes "container".
	WorkspaceKindContainer WorkspaceKind = "container"
)

type WorkspaceLLMCred added in v0.4.0

type WorkspaceLLMCred struct {
	// ProviderRef pins a run to one model provider (by id). A disallowed or
	// ineligible pin refuses the run rather than falling through to another
	// provider.
	ProviderRef string `json:"provider_ref,omitempty"`
	// ProviderUnavailable is set by the server on a READ only, in place of
	// ProviderRef, for a caller "Available to" does not admit to the pinned
	// provider: the workspace is pinned, to a provider they cannot use, and
	// its id is not theirs to learn (#1018). A write carrying it is refused.
	ProviderUnavailable bool `json:"provider_unavailable,omitempty"`
}

WorkspaceLLMCred is the operator-owned model binding on a workspace: a run that picks it uses the pinned model provider unless it names another. Nil or ProviderRef="" means no pin.

type WorkspaceMount

type WorkspaceMount struct {
	Source   string `json:"source"`
	Target   string `json:"target"`
	ReadOnly *bool  `json:"read_only,omitempty"`
}

WorkspaceMount is one operator/policy-controlled host bind mount. Source is a host path (must be an absolute, cleaned path not under a denied location); Target is the in-container path (must be under an allowed prefix, e.g. /home/agent, /work, or /workspace). ReadOnly is a *bool so the safe default is read-only: omitted (nil) means read-only, and read-write requires the policy author to explicitly set "read_only": false (a plain bool would default to the unsafe direction). Use ReadOnlyOrDefault to resolve it.

func (WorkspaceMount) ReadOnlyOrDefault

func (m WorkspaceMount) ReadOnlyOrDefault() bool

ReadOnlyOrDefault resolves the mount's effective read-only flag: an OMITTED (nil) read_only defaults to true (read-only), the safe default. Read-write is returned only when the policy explicitly set read_only=false.

type WorkspaceProviders added in v0.7.2

type WorkspaceProviders struct {
	// Git rows: hosts a run may clone from and the credential lanes usable there.
	Git []GitProvider `json:"git,omitempty"`
	// Storage is the file-system half. A nil block is legacy behaviour.
	Storage *StorageProviders `json:"storage,omitempty"`
	// GitPatBrokerEnabled is read-only and server-owned: projected from
	// WARDYN_GIT_PAT_BROKER on every GET, never stored or accepted on a PUT.
	// A pointer since nil ("never projected") differs from a present false.
	GitPatBrokerEnabled *bool `json:"git_pat_broker_enabled,omitempty"`
}

WorkspaceProviders is the org's workspace-provider policy: what kinds of work a sandbox may be given and the ceilings it's held to. The zero value is legacy open mode (load-bearing for upgraded installs), never derived from legacy SiteConfig.ScmHosts; URL validation lives in internal/api.

func (*WorkspaceProviders) Empty added in v0.7.2

func (p *WorkspaceProviders) Empty() bool

Empty reports whether p carries no policy — the shape a PUT uses to clear the block; the write boundary normalizes an empty block to nil.

type WorkspaceRepo

type WorkspaceRepo struct {
	Repo   string `json:"repo"`
	Target string `json:"target,omitempty"`
	Ref    string `json:"ref,omitempty"`
}

WorkspaceRepo is one operator/policy-controlled git repo attached to a run, paralleling WorkspaceMount for git-cloned (rather than bind-mounted) sources. Repo is a slug/URL validated the same way the legacy single-repo AgentRun.Repo field is. Target is an optional in-container clone destination; empty defers to the ~/work/<name> convention (not resolved here). Ref is an optional git ref (branch/tag/sha); empty clones the remote's default branch. Carried as a 4th tab-separated field in WARDYN_REPOS for agent-run-lib.sh's clone_one.

type WorkspaceRequirement added in v0.5.0

type WorkspaceRequirement struct {
	// Level is "required" (a run cannot proceed without it) or "optional"
	// (a run may proceed without it, degraded).
	Level string `json:"level"`
	// Provenance is "scan_seeded" (the scanner detected the need) or
	// "operator_set" (an operator declared it directly).
	Provenance string `json:"provenance"`
}

WorkspaceRequirement is one entry in a Workspace's requirements contract, keyed by the grammar documented on Workspace.Requirements.

type WorkspaceSource added in v0.5.0

type WorkspaceSource struct {
	Type WorkspaceSourceType `json:"type"`
	// Path is the host directory path (local_dir only).
	Path string `json:"path,omitempty"`
	// Source is the repo slug/URL (repo only).
	Source string `json:"source,omitempty"`
	// Ref is an optional git ref (branch/tag/sha, repo only).
	Ref string `json:"ref,omitempty"`
	// Target is the mount/clone/scratch-dir path inside the sandbox.
	Target string `json:"target,omitempty"`
	// Writable opts a local_dir source into read-write (default false =
	// read-only; a plain bool since false is already the safe default).
	Writable bool `json:"writable,omitempty"`
	// Overrides is this attachment's stance on requirement keys its source
	// declares: reqKey -> off|optional|required (workspace_contract.go's
	// AttachmentOverride* constants). nil means "say nothing" (server carries
	// forward the prior override); an explicit map REPLACES it outright.
	Overrides map[string]string `json:"overrides,omitempty"`
	// Admitted is a server-owned read projection, never persisted or from a
	// write body: does policy admit this clone URL today? A pointer so
	// "no answer" (absent, no provider row) differs from "not admitted".
	Admitted *bool `json:"admitted,omitempty"`
	// CloneTarget is a server-owned read projection on a repo source, never
	// persisted or from a write body: the absolute path the clone lands at by
	// default (Target, else /home/agent/work/<name>). A run's own selection
	// target, when it sets one, wins over it.
	CloneTarget string `json:"clone_target,omitempty"`
}

WorkspaceSource is one entry in a Workspace's composition (multiples of the same Type allowed). Meaningful fields depend on Type:

local_dir  Path (host path), Target, Writable
repo       Source (slug/URL), Ref, Target
ephemeral  Target only — no Path, no Source, never Writable

type WorkspaceSourceType added in v0.5.0

type WorkspaceSourceType string

WorkspaceSourceType discriminates one entry in a Workspace's composition.

const (
	WorkspaceSourceTypeLocalDir WorkspaceSourceType = "local_dir" // binds a host directory
	WorkspaceSourceTypeRepo     WorkspaceSourceType = "repo"      // clones a git repo
	// WorkspaceSourceTypeEphemeral is a sandbox-lifetime scratch dir, the
	// composition floor ensuring "one-or-more sources" never means zero.
	WorkspaceSourceTypeEphemeral WorkspaceSourceType = "ephemeral"
)

type WorkspaceStatus

type WorkspaceStatus string

WorkspaceStatus is the onboarding/scan lifecycle of a Workspace: not-yet- scanned, mid-scan, scanned, or errored. Later import-pipeline stages are RETIRED (migration 0029 collapsed every row back to scanned/pending_scan).

const (
	WorkspacePendingScan WorkspaceStatus = "pending_scan" // onboarded, not yet scanned
	WorkspaceScanning    WorkspaceStatus = "scanning"     // scan run in flight
	WorkspaceScanned     WorkspaceStatus = "scanned"      // profile derived, ready to use
	WorkspaceError       WorkspaceStatus = "error"        // last scan attempt failed
)

Jump to

Keyboard shortcuts

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