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, kept here since every write boundary already reaches this package.
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.
Lives here rather than internal/auth/oidc (which states the same rule for the match surface) so pkg/client and cmd/wardyn don't need the OIDC/JOSE stack; TestCanonicalGroupSubjectHasOneAnswer pins the two copies together.
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
- Variables
- func ASCIIOnlySubject(s string) bool
- func CanonicalGroupSubject(s string) (string, bool)
- func CanonicalUserSubject(s string) string
- func ClosedADOTokenModeList() []string
- func ClosedCredentialSourceList() []string
- func ClosedGitLaneList() []string
- func ClosedGitProviderKindList() []string
- func ClosedIntegrationKindList() []string
- func ClosedModelProviderKindList() []string
- func DenyPathSegments(pattern string) ([]string, error)
- func DriveHomeName(d UserDrive, subject, override string) (string, error)
- func DriveHomeStricterRuleClause(b DriveBackend) string
- func DriveObjectID(id uuid.UUID) string
- func DriveObjectName(d UserDrive, home string) string
- func DriveObjectNamedByWardyn(b DriveBackend) bool
- func DriveSlug(name string) string
- func DriveSubjectHash(subject string) string
- func FoldWorkspaceContract(attachments []WorkspaceAttachment, sources map[uuid.UUID]Source, ...) map[string]WorkspaceRequirement
- func ManagedBackendRejectsTemplate(backend DriveBackend, tmpl HomeTemplate) bool
- func PushPathsDigest(paths []string) string
- func ShareBackendRejectsTemplate(backend DriveBackend, tmpl HomeTemplate) bool
- func SplitRequirementKey(key string) (typ, rest string, ok bool)
- func ValidToolEffect(e ToolEffect) bool
- func ValidateUserDrive(d *UserDrive, runnerTarget string) error
- func ValidateUserDriveGrant(g *UserDriveGrant) error
- type ADOEntraConfig
- type ADOTokenMode
- type APIToken
- type ActorType
- type AgentProvider
- type AgentProviders
- type AgentRun
- type ApprovalDecision
- type ApprovalKind
- type ApprovalRequest
- type ApprovalScope
- type ApprovalState
- type ArtifactOverridedeprecated
- type AttentionBy
- type AttentionKind
- type AuditEvent
- type AutonomyEgressPosture
- type AutonomyLevel
- type AutonomyPosture
- type AutonomyResolution
- type AutonomyRubric
- type AutonomySecretsPosture
- type BaseImageEntry
- type BedrockSettings
- type Branding
- type CapabilityEffect
- type CapabilityGrant
- type CapabilitySubjectType
- type ConfinementClass
- type CredentialGrant
- type CredentialSource
- type Delegate
- type DelegatedToken
- type DelegationVia
- type Device
- type DeviceAck
- type DeviceEnrolRequest
- type DeviceEnrolResponse
- type DeviceEnrolmentToken
- type DriveBackend
- type DriveKind
- type DriveMount
- type DriveObjectScheme
- type DriveReclaim
- type EgressRedirect
- type EphemeralProvider
- type FederatedAuditEvent
- type FirstUseMode
- type GitLane
- type GitProvider
- type GitProviderKind
- type GovernanceAssignment
- type GovernanceLimits
- type GovernanceProfile
- type GrantKind
- type GrantSpec
- type HomeTemplate
- type Integration
- type IntegrationDelivery
- type IntegrationList
- type IntegrationSecret
- type InternalHost
- type LLMInspectionSpec
- type LaunchPreset
- type LostReason
- type ModelProvider
- type ModelProviderKind
- type ModelProviders
- type PauseReason
- type Person
- type ProviderAuth
- type ProviderHarness
- type PushContentScope
- type PushPathList
- type PushRefUpdate
- type PushRulesSpec
- type ResolvedDrive
- type ResolvedInjection
- type ResourceLimits
- type RoleMapping
- type RunAttention
- type RunLayout
- type RunLayoutWidget
- type RunLimits
- type RunPolicy
- type RunPolicySpec
- type RunState
- type SSHPublicKey
- type SiteBranding
- type SiteConfig
- type Source
- type SourceKind
- type StorageEnforcement
- type StorageProviders
- type ToolEffect
- type ToolRule
- type UIApp
- type UserDrive
- type UserDriveGrant
- type UserDriveHostRootCheck
- type UserDriveListItem
- type UserDriveProvider
- type UserType
- type WithheldScmHost
- type Workspace
- type WorkspaceAttachment
- type WorkspaceBaseImage
- type WorkspaceKind
- type WorkspaceLLMCred
- type WorkspaceMount
- type WorkspaceProviders
- type WorkspaceRepo
- type WorkspaceRequirement
- type WorkspaceSource
- type WorkspaceSourceType
- type WorkspaceStatus
Constants ¶
const ( BrandNamePrefix = "prefix" BrandNameSuffix = "suffix" )
Branding name formats: "<Company> Wardyn" and "Wardyn for <Company>".
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).
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).
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.
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.
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.
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.
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.
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.
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.
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.
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").
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.
const ModelProviderSecretPrefix = "wardyn-provider-"
ModelProviderSecretPrefix starts every per-person credential name: wardyn-provider-<uid>-{key,oauth,sso}.
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.
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.
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.
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.
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 ¶
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.
var ApprovalKinds = []ApprovalKind{ ApprovalCredential, ApprovalEgressDomain, ApprovalToolCall, ApprovalCredentialReauth, ApprovalPushContent, }
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.
var ApprovalStates = []ApprovalState{ ApprovalPending, ApprovalApproved, ApprovalDenied, ApprovalExpired, ApprovalCancelled, }
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.
var ClosedADOTokenModes = map[ADOTokenMode]bool{ ADOTokenModeBearer: true, ADOTokenModeMintedPAT: true, ADOTokenModeOwnPAT: true, }
ClosedADOTokenModes is the closed token-mode set — see ClosedGitLanes.
var ClosedCredentialSources = map[CredentialSource]bool{ CredentialSourceShared: true, CredentialSourcePerUser: true, }
ClosedCredentialSources is the closed source set — the only values a write may name.
var ClosedGitLanes = map[GitLane]bool{ GitLaneApp: true, GitLanePAT: true, GitLaneSSH: true, GitLaneEntra: true, }
ClosedGitLanes is the closed lane set — see ClosedGitProviderKinds.
var ClosedGitProviderKinds = map[GitProviderKind]bool{ GitProviderGitHub: true, GitProviderAzureDevOps: true, }
ClosedGitProviderKinds is the only kinds a write may name.
var ClosedIntegrationKinds = map[string]bool{ IntegrationKindGitHubApp: true, IntegrationKindGitHost: true, }
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).
var ClosedModelProviderKinds = map[ModelProviderKind]bool{ ModelProviderAnthropicSubscription: true, ModelProviderBedrockSSO: true, ModelProviderAnthropicAPIKey: true, ModelProviderOpenAIAPIKey: true, ModelProviderBedrockBearer: true, ModelProviderCustomEndpoint: true, }
ClosedModelProviderKinds is the only kind set a write may name.
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).
var DriveBackends = []DriveBackend{ DriveBackendDockerVolume, DriveBackendHostPath, DriveBackendK8sPVC, DriveBackendK8sPVCStatic, }
DriveBackends is the closed set in the order the admin surface shows them.
var DriveObjectSchemes = []DriveObjectScheme{DriveObjectSchemeSlug, DriveObjectSchemeID}
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).
var DriveReclaims = []DriveReclaim{DriveReclaimRetain, DriveReclaimDelete}
DriveReclaims is the closed set in admin-surface order.
var HomeTemplates = []HomeTemplate{HomeTemplateHash, HomeTemplateSub, HomeTemplateEmailLocal}
HomeTemplates is the closed set in admin-surface order.
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.
var NonTerminalRunStates = []RunState{RunPending, RunStarting, RunRunning, RunWaiting}
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.
Functions ¶
func ASCIIOnlySubject ¶ added in v0.7.0
ASCIIOnlySubject reports whether s has no rune above ASCII — mirrors oidc.ASCIIOnly so internal/types needn't import the OIDC stack.
func CanonicalGroupSubject ¶ added in v0.7.0
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 CanonicalUserSubject ¶ added in v0.7.0
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.
Known gap: internal/api's capabilitySubjects folds with a bare ToLower, so a non-ASCII subject stored here can end up a dead, not misdirected, row there; fixing it belongs on the session surface.
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 DenyPathSegments ¶ added in v0.8.0
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 DriveHomeName ¶ added in v0.7.0
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
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
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
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
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:
- Each attachment contributes its source's contract; a dangling SourceID contributes nothing.
- An override of "off" drops that source's contribution for that key.
- An override lane (optional|required) replaces the source's lane.
- 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.
- 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).
- 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 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 PushPathsDigest ¶ added in v0.8.0
PushPathsDigest is PushContentScope.PathsDigest over paths.
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
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 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 ValidateUserDrive ¶ added in v0.7.0
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"`
}
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"`
MintedBy string `json:"minted_by,omitempty"` // admin who minted this for its owner; empty when the owner minted it
Token string `json:"token,omitempty"` // plaintext, create response ONLY
}
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.
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>
RunnerTarget string `json:"runner_target"` // "docker"
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"`
// 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; blanked on leaving STARTING except a TERMINAL failure 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"`
EndsAt *time.Time `json:"ends_at"` // run's lease end, captured once at create from RunLimits, never re-resolved
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; 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:"-"` // mirrors identity.Claims.OperatorOwned so dispatch/revive re-mint read what create decided
// 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
}
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.
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 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 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"`
}
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 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"`
}
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.
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 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
Logo []byte
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 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 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 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"`
}
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 ( // 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"`
}
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
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 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 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 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 )
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 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 )
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"`
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 GovernanceLimits ¶ added in v0.7.0
type GovernanceLimits struct {
// 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"`
// 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.
- MaxConcurrentRuns is a QUOTA, not a door: 422 with no authz.denied, not 403.
- 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", 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"`
}
GovernanceProfile is one named, assignable ceiling (migration 0052's governance_profiles row).
Ceiling is a full RunPolicySpec with REPLACEMENT semantics, not composition: an assigned profile IS the principal's ceiling; with no assignment a principal falls through to Config.DefaultPolicy byte for byte. composer.Clamp is NOT a lattice meet (drops workspace_mounts unconditionally, is order-dependent through llm_inspection, defaults unnamed tools to hold), so a "composed" ceiling would silently lose fields and couldn't express an autonomous profile.
Name is the UNIQUE human handle: what an admin assigns by, what the console lists, and the resolver's last ORDER BY tie-break.
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" )
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.
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 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 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"`
}
InternalHost is one SiteConfig.InternalHosts entry — see that field's doc.
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 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 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
// 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" )
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 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 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"`
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
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
PushRefUpdate is one ref a held push sets and the object id it sets it to.
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 (*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 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 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 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 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"`
}
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 RunState ¶
type RunState string
RunState is the AgentRun lifecycle state machine.
const ( 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
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 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"`
}
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 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 {
// 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 because a corporate forward proxy
// won't CONNECT to an internal address (breaks PrivateLink estates).
//
// 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"`
// 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"`
// 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"`
// 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 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 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" )
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
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"`
}
UserDrive is one admin-registered drive (migration 0054's user_drives row). Name is the UNIQUE human handle: 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
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
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
}
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"`
// 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"`
// 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"`
}
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 )
Source Files
¶
- agent_provider.go
- attention.go
- branding.go
- delegate.go
- device.go
- governance.go
- group_subject.go
- holds.go
- launch_preset.go
- lost_reason.go
- mint_wire.go
- model_provider.go
- policy.go
- push_content.go
- role_mapping.go
- secret_sentinels.go
- site_config.go
- types.go
- types_capability.go
- ui_layout.go
- user_drive.go
- user_drive_home.go
- user_drive_validate.go
- user_type.go
- workspace.go
- workspace_contract.go
- workspace_provider.go