Documentation
¶
Index ¶
- Constants
- Variables
- func ProbeRelayRouter(ctx context.Context, routerURL string) error
- func ValidateAgentdDelivery(image, amd64, arm64 string) error
- func ValidateAgentdSidecar(sidecarEnabled bool, agentdImage string) error
- func ValidateOpencodeDelivery(image, amd64, arm64 string) error
- func ValidateRelayStagingFlags(enabled bool, routerURL, apiServiceURL, namespace string) error
- func ValidateRelayStagingStartup(ctx context.Context, cfg *RelayStagingConfig, c client.Client) error
- type AgentdDeliveryConfig
- type BinaryPins
- type CachedLLMProviderSource
- type CachedOrgStatusClient
- type FailureClass
- type HTTPRelayRouterClient
- type HostResolver
- type LLMProviderSource
- type OpencodeDeliveryConfig
- type OrgStatusClient
- type OrgStatusLogger
- type PodObservation
- type RecoveryPolicy
- type RelayMintRequest
- type RelayRotateReceipt
- type RelayRouterClient
- type RelayStagingConfig
- type UploadStagingConfig
- type WorkspaceReconciler
Constants ¶
const ( LabelApp = "app" LabelComponent = "component" LabelWorkspace = "llmsafespaces.dev/workspace" LabelRuntime = "runtime" LabelTenant = "llmsafespaces.dev/tenant" AppName = "llmsafespaces" ComponentWorkspace = "workspace" )
Labels applied to workspace pods.
const ( // AgentdPinsConfigMapName caches the last successfully resolved pins // per digest so a controller restart during a registry outage does // not brick startup. AgentdPinsConfigMapName = "llmsafespaces-agentd-pins" )
const ( // OpencodePinsConfigMapName caches the last successfully resolved // opencode pins per digest so a controller restart during a registry // outage does not brick startup. OpencodePinsConfigMapName = "llmsafespaces-opencode-pins" )
const ( // SidecarRestartMarkerEnv wires LLMSAFESPACES_RESTART_MARKER_PATH on // both containers so the sidecar's and the supervisor's marker writes // land on one cross-uid-readable file (0640 + shared gid 1000). SidecarRestartMarkerEnv = agentd.SidecarRestartMarkerPath )
const WorkspaceFinalizer = "workspace.llmsafespaces.dev/finalizer"
Variables ¶
ErrAgentdPinsUnavailable wraps every agentd resolution failure where the registry was unreachable AND no usable cache existed. Callers can errors.Is this to offer the manual-pin hint.
ErrOpencodePinsUnavailable wraps every opencode resolution failure where the registry was unreachable AND no usable cache existed. Callers can errors.Is this to offer the manual-pin hint.
Functions ¶
func ProbeRelayRouter ¶ added in v0.34.4
ProbeRelayRouter verifies the router is reachable and serving (GET /healthz). This is the loud half of the guard: a controller enabled for relay-only delivery must refuse to start against a dead router rather than fail every workspace reconcile.
func ValidateAgentdDelivery ¶ added in v0.15.8
ValidateAgentdDelivery is the exported startup guard used by controller main. Wraps validateAgentdDeliveryConfig.
func ValidateAgentdSidecar ¶ added in v0.18.0
ValidateAgentdSidecar is the exported startup guard used by controller main. Wraps validateAgentdSidecarConfig.
func ValidateOpencodeDelivery ¶ added in v0.25.5
ValidateOpencodeDelivery is the exported startup guard used by controller main. Wraps validateOpencodeDeliveryConfig.
func ValidateRelayStagingFlags ¶ added in v0.34.4
ValidateRelayStagingFlags checks the pure flag contract: enabled requires the router URL and the credential-source API URL (worklog D1), and a namespace.
func ValidateRelayStagingStartup ¶ added in v0.34.4
func ValidateRelayStagingStartup(ctx context.Context, cfg *RelayStagingConfig, c client.Client) error
ValidateRelayStagingStartup is the full boot gate: flags probed, router reachable, pub Secret readable + parseable (the router bootstrapped; this also proves the controller's name-scoped get-on-pub RBAC — a successful read implies the namespace exists, so no cluster-scoped Namespace GET is needed or performed), and the mint key created. c must be a DIRECT (non-cached) client — the controller-runtime cache is not started this early.
Types ¶
type AgentdDeliveryConfig ¶ added in v0.15.8
AgentdDeliveryConfig is the #863 overlay-delivery configuration passed from controller main (flags) to the reconciler. Zero value = legacy baked-in mode.
type BinaryPins ¶ added in v0.25.5
BinaryPins are the resolved per-arch binary sha256s of one overlay artifact.
func ResolveOpencodePinsWithCache ¶ added in v0.25.5
func ResolveOpencodePinsWithCache(ctx context.Context, image, flagAMD64, flagARM64 string) (BinaryPins, error)
ResolveOpencodePinsWithCache is the opencode startup entrypoint used by controller main. validateOpencodeDeliveryConfig has already run.
func ResolvePinsWithCache ¶ added in v0.15.10
func ResolvePinsWithCache(ctx context.Context, image, flagAMD64, flagARM64 string) (BinaryPins, error)
ResolvePinsWithCache is the agentd startup entrypoint used by controller main. validateAgentdDeliveryConfig has already run.
type CachedLLMProviderSource ¶ added in v0.34.4
type CachedLLMProviderSource struct {
// contains filtered or unexported fields
}
CachedLLMProviderSource is the LLMProviderSource backed by the API service's internal llm-providers endpoint with a short success-only cache. Unlike org-status there is NO stale-serve: a fetch failure fails the staging pass loudly (credentials must be fresh — a stale set could seal revoked material and miss newly-bound providers).
func NewCachedLLMProviderSource ¶ added in v0.34.4
func NewCachedLLMProviderSource(baseURL, token string, ttl time.Duration) *CachedLLMProviderSource
NewCachedLLMProviderSource constructs the source. baseURL is the API service root; token is the X-Internal-Token (LLMSAFESPACES_INTERNAL_TOKEN — the same Secret that authenticates the org-status call, mounted on both sides by the chart).
func (*CachedLLMProviderSource) LLMProviders ¶ added in v0.34.4
func (c *CachedLLMProviderSource) LLMProviders(ctx context.Context, ownerUserID, workspaceID string) ([]secrets.LLMProviderData, error)
type CachedOrgStatusClient ¶
type CachedOrgStatusClient struct {
// contains filtered or unexported fields
}
CachedOrgStatusClient is an OrgStatusClient backed by the API service's internal org-status endpoint with a per-org TTL cache.
Cache semantics (D20):
- On a fresh cache hit (< TTL): return the cached status, no fetch.
- On a stale entry (>= TTL) or cache miss: attempt a fetch.
- Fetch success: update the cache and return the new status.
- Fetch failure + prior entry: serve the STALE cached status so a transient API outage does not flip the controller into fail-open and leave suspended orgs running. This is the "30s cache absorbs transient failures" guarantee.
- Fetch failure + no entry: return ok=false (fail open — D20: an unwarranted suspension is more disruptive than leaving a pod running).
Concurrency: each orgID has its own entry guarded by a mutex, so concurrent refreshes for the SAME org are deduplicated (only one in-flight fetch per org) while different orgs refresh in parallel. The mutex is held for the duration of the fetch, bounding concurrent same-org fetches to one.
func NewCachedOrgStatusClient ¶
func NewCachedOrgStatusClient(baseURL, token string, ttl time.Duration, logger OrgStatusLogger) *CachedOrgStatusClient
NewCachedOrgStatusClient constructs the client. baseURL is the API service root (e.g. http://llmsafespaces-api.llmsafespaces.svc:8080); leave empty to disable org-suspension (the reconciler then never org-suspends). token, when non-empty, is sent as X-Internal-Token. ttl is the cache freshness window (production: 30s). logger may be nil.
func (*CachedOrgStatusClient) GetOrgStatus ¶
GetOrgStatus implements OrgStatusClient. See CachedOrgStatusClient docs for the cache + fail-open contract.
type FailureClass ¶
type FailureClass string
const ( FailureClassNone FailureClass = "" FailureClassInfrastructure FailureClass = "Infrastructure" FailureClassResource FailureClass = "Resource" FailureClassProcess FailureClass = "Process" FailureClassConfiguration FailureClass = "Configuration" )
type HTTPRelayRouterClient ¶ added in v0.34.4
type HTTPRelayRouterClient struct {
// contains filtered or unexported fields
}
HTTPRelayRouterClient is the RelayRouterClient against the llm-relay router's internal API (US-72.2): POST /internal/v1/tokens and POST /internal/v1/keys/rotate, both authenticated by the controller-held mint key (Authorization: Bearer, constant-time compared server-side).
func NewHTTPRelayRouterClient ¶ added in v0.34.4
func NewHTTPRelayRouterClient(routerURL, namespace string, reader client.Reader) *HTTPRelayRouterClient
NewHTTPRelayRouterClient constructs the client. reader resolves the llm-relay-mint-key Secret in namespace (the controller creates it in the staging pass / startup guard; reads are cheap because mint and rotate are rare operations).
func (*HTTPRelayRouterClient) MintToken ¶ added in v0.34.4
func (c *HTTPRelayRouterClient) MintToken(ctx context.Context, req RelayMintRequest) (string, error)
func (*HTTPRelayRouterClient) RotateKeys ¶ added in v0.34.4
func (c *HTTPRelayRouterClient) RotateKeys(ctx context.Context) (RelayRotateReceipt, error)
type HostResolver ¶
HostResolver resolves a hostname to a list of IP strings. Tests inject a stub via WorkspaceReconciler.HostResolver to avoid hitting real DNS; production uses net.DefaultResolver via defaultHostResolver.
type LLMProviderSource ¶ added in v0.34.4
type LLMProviderSource interface {
LLMProviders(ctx context.Context, ownerUserID, workspaceID string) ([]secrets.LLMProviderData, error)
}
LLMProviderSource is the credential-source seam (worklog D1): the controller→API internal llm-providers endpoint. Interface for test injection.
type OpencodeDeliveryConfig ¶ added in v0.25.5
type OpencodeDeliveryConfig struct {
Image string
BinarySHA256AMD64 string
BinarySHA256ARM64 string
}
OpencodeDeliveryConfig is the design 0053 §4.2 overlay-delivery configuration passed from controller main (flags) to the reconciler. Zero value = opencode stays baked into runtimes/base (S1 default).
type OrgStatusClient ¶
type OrgStatusClient interface {
GetOrgStatus(ctx context.Context, orgID string) (status string, ok bool)
}
OrgStatusClient reports an org's operational status. ok is false when the status could not be determined (API unreachable and no cached value); callers MUST fail open in that case (D20). ok is true whenever a value — fresh or stale — is available.
The interface lets tests inject a fake without an HTTP server.
type OrgStatusLogger ¶
type OrgStatusLogger interface {
Info(msg string, keysAndValues ...any)
Error(err error, msg string, keysAndValues ...any)
}
OrgStatusLogger is the minimal logging surface CachedOrgStatusClient uses. controller-runtime's logr.Logger satisfies it; nil disables logging.
type PodObservation ¶
type RecoveryPolicy ¶
type RecoveryPolicy struct {
MaxAttempts int
BackoffBase time.Duration
BackoffMax time.Duration
BackoffFactor int
StabilityReset time.Duration
// ExhaustionAfter is the ConsecutiveFailures count at which the
// recovery episode is flagged exhausted (#760): a RecoveryExhausted
// condition, a warning Event, and a
// WorkspaceRecoveryExhaustedTotal increment fire once, on the
// crossing. Retries are NOT halted — backoff continues, and
// spec.suspend=true (#699) is the operator's halt.
ExhaustionAfter int32
}
type RelayMintRequest ¶ added in v0.34.4
type RelayMintRequest struct {
WorkspaceID string `json:"workspaceID"`
ProviderSlug string `json:"providerSlug"`
BaseURL string `json:"baseURL"`
ModelAllowlist []string `json:"modelAllowlist"`
TTLSec int64 `json:"ttlSeconds"`
KeyID string `json:"keyID"`
}
RelayMintRequest is the POST /internal/v1/tokens body (US-72.2 contract).
type RelayRotateReceipt ¶ added in v0.34.4
type RelayRotateReceipt struct {
KeyID string `json:"keyID"`
Generation int64 `json:"generation"`
PublicKey []byte `json:"publicKey"`
}
RelayRotateReceipt is the POST /internal/v1/keys/rotate response.
type RelayRouterClient ¶ added in v0.34.4
type RelayRouterClient interface {
MintToken(ctx context.Context, req RelayMintRequest) (string, error)
RotateKeys(ctx context.Context) (RelayRotateReceipt, error)
}
RelayRouterClient is the router internal-API surface the staging pass needs (mint + rotate). Interface for test injection.
type RelayStagingConfig ¶ added in v0.34.4
type RelayStagingConfig struct {
RouterURL string
Namespace string
TokenTTL time.Duration
ProviderSource LLMProviderSource
RouterClient RelayRouterClient
Now func() time.Time
// APIReader is the DIRECT (non-cached) reader every llm-relay Secret
// read goes through (pub, mint-key). The reconciler's cached client
// cannot serve these: a namespace outside the cache scope fails
// without an API call, and an informer inside it needs LIST+WATCH —
// verbs the llm-relay Role deliberately withholds (§4.3 deviation
// note). Writes stay on the reconciler client (controller-runtime
// writes bypass the cache). REQUIRED at construction
// (mgr.GetAPIReader() in production) — there is deliberately no
// cached-client fallback.
APIReader client.Reader
// contains filtered or unexported fields
}
RelayStagingConfig wires the deployment flag. A nil *RelayStagingConfig on the reconciler disables staging entirely (flag off). Construction is NewRelayStagingConfig, which pins a NON-NIL redactor (design §4.9 amendment: fail-open is test-adoption only).
func NewRelayStagingConfig ¶ added in v0.34.4
func NewRelayStagingConfig(routerURL, namespace string, tokenTTL time.Duration, source LLMProviderSource, routerClient RelayRouterClient, redactor secrets.StagedKeyRedactor, apiReader client.Reader) (*RelayStagingConfig, error)
NewRelayStagingConfig constructs the production staging config. The redactor is mandatory (US-72.1 §4.9 amendment binding on US-72.3's construction path) — a nil redactor means an unregistered staged key could echo through diagnostics, which the design forbids. The reader is likewise mandatory: llm-relay reads MUST go through a DIRECT client (mgr.GetAPIReader()) — the cached reconciler client cannot serve them (review r3 finding 1) — so the production wiring cannot silently drop it.
type UploadStagingConfig ¶ added in v0.34.7
type UploadStagingConfig struct {
BudgetBytes int64
CredentialFloorByte int64
MaxConcurrent int
TTLMilliseconds int64
ApplyTimeoutMilli int64
// contains filtered or unexported fields
}
UploadStagingConfig carries the helm → controller → pod env knobs. Zero = unset = agentd defaults.
func ParseUploadStagingFlag ¶ added in v0.34.7
func ParseUploadStagingFlag(raw string) (UploadStagingConfig, error)
ParseUploadStagingFlag is FlagString's inverse (the controller's flag entry point). Unknown keys are REJECTED loudly — a typo'd helm knob must not silently default (Rule: explicit over implicit).
func (UploadStagingConfig) EnvVars ¶ added in v0.34.7
func (c UploadStagingConfig) EnvVars() []corev1.EnvVar
EnvVars lands the SET fields as agentd-consumed env — the exact names cmd/workspace-agentd parses (upload_staging.go / upload_apply.go). Unset fields emit nothing: agentd defaults.
func (UploadStagingConfig) FlagString ¶ added in v0.34.7
func (c UploadStagingConfig) FlagString() string
FlagString renders the --upload-staging flag payload (k=v, comma- separated; empty when fully unset — the flag can be omitted).
type WorkspaceReconciler ¶
type WorkspaceReconciler struct {
client.Client
Scheme *runtime.Scheme
// APIReader is the manager's DIRECT (non-cached) reader for
// cluster-scoped lookups on the reconcile path (#1587): RuntimeEnvironment
// resolution (pod_builder.go) and StorageClass WFFC detection (pvc.go).
// A cached Get of these unwatched cluster-scoped types lazily starts an
// informer that blocks forever when RBAC withholds the list/watch (#1551)
// — wedging workers silently; direct reads fail fast and requeue.
// Wired to mgr.GetAPIReader() in SetupControllers; SetupWithManager
// refuses nil. DirectReader() serves lookups; never use r.Client for them.
APIReader client.Reader
// HostResolver is used by the per-workspace NetworkPolicy generator
// (network_policy.go) to resolve declared FQDNs to /32 ipBlocks at
// reconcile time. Tests inject a stub; production uses
// defaultHostResolver (net.DefaultResolver) when nil.
HostResolver HostResolver
// InferenceRelayURL is the self-hosted relay URL for free-tier inference
// (Epic 42 InferenceRelay fleet). When set, workspace pods route opencode
// free-tier provider requests through this URL for IP distribution. When
// empty (the chart default), opencode uses its default gateway
// (opencode.ai/zen/v1) directly with the built-in `public` key.
InferenceRelayURL string
// OrgStatusClient, when non-nil AND the workspace belongs to an org
// (Spec.Owner.OrgID != ""), is consulted on every Active reconcile to drive
// D20 org-level suspension: if the org is suspended, the workspace
// transitions Active → Suspending (pod killed, PVC retained). The client is
// nil when --api-service-url is unset, disabling the feature. Lookups are
// cached (30s TTL); a lookup failure fails open (workspace keeps running).
OrgStatusClient OrgStatusClient
// WorkspaceTerminationGraceSeconds (#1507) bounds every workspace
// pod's graceful termination: on pod deletion kubelet SIGTERMs
// agentd, whose serial shutdown budget (HTTP drain 25s → bg wait 5s
// → opencode child SIGTERM→SIGKILL 5s) fits inside the default 40s.
// The SUSPEND path deliberately does NOT wait for busy sessions
// (#1507): this grace IS the bounded graceful opencode termination;
// the hard cut at expiry is the deadline. 0 = controller default
// (40s, the serial budget + margin). Sourced from
// --workspace-termination-grace-seconds / Helm
// controller.workspaceTerminationGraceSeconds.
WorkspaceTerminationGraceSeconds int64
// DefaultRuntimeClass is the container runtime class applied to all
// workspace pods (Epic 51 S51.1). Typically "gvisor" for production
// multi-tenant deployments to provide kernel-level isolation against
// container escape. Empty means use the default (runc). Set via
// --default-runtime-class controller flag, sourced from Helm
// .Values.gvisor.defaultRuntimeClass (only set when .Values.gvisor.enabled
// is true). Individual workspaces can override via spec.runtimeClass for
// compatibility opt-out (admin-gated).
DefaultRuntimeClass string
// APIServiceURL is the in-cluster URL of the API service, used by the
// workspace init container's bootstrap subcommand (Epic 35 US-35.4) to
// fetch decrypted credentials via POST /internal/v1/pod-bootstrap. Same
// value as --api-service-url (also used for OrgStatusClient). When empty,
// the bootstrap subcommand degrades gracefully (empty secrets, pod boots
// without credentials; the notify-pull path handles delivery).
APIServiceURL string
// PreviewOriginBaseDomain (Epic 66 Phase 1): when non-empty, workspace
// pods receive PREVIEW_ORIGIN_BASE_DOMAIN so agentd's dev_preview_url
// MCP tool emits the per-workspace-origin bootstrap URL instead of the
// path-based URL. Sourced from the same previewOrigin.baseDomain chart
// value the API consumes — one flag enables the deployment-wide mode.
// Empty = path-based dev preview (unchanged behavior).
PreviewOriginBaseDomain string
// APIPublicURL (#1332) is the externally reachable API origin wired
// into every container that runs agentd tooling as
// LLMSAFESPACE_API_PUBLIC_URL — the origin the dev_preview_url MCP
// tool bakes into user-facing URLs. Distinct from APIServiceURL
// (in-cluster, load-bearing for bootstrap): in sidecar mode the tool
// runs in the sidecar, whose LLMSAFESPACE_API_URL is deliberately the
// svc coordinate. When unset and PreviewOriginBaseDomain is set, the
// controller derives https://api.<baseDomain>; when both are unset
// the env is omitted and the tool validates LLMSAFESPACE_API_URL,
// refusing cluster-internal values. Same value as --api-public-url
// (Helm value controller.apiPublicURL).
APIPublicURL string
// #863 agentd overlay delivery. When AgentdImage is set, buildPod pins a
// digest-addressed image volume into every workspace pod and the
// supervisor self-verifies the binary's sha256 against the per-arch
// pins before exec (design 0053 — the deleted entrypoint's check
// moved into the binary). All three fields must be set together (validated at
// startup by validateAgentdDeliveryConfig). Empty AgentdImage = legacy
// mode (binary baked into runtimes/base; no volume, mount, or env).
AgentdImage string
AgentdBinarySHA256AMD64 string
AgentdBinarySHA256ARM64 string
// Design 0053 §4.2 opencode overlay delivery. When OpencodeImage is
// set, buildPod pins a digest-addressed image volume into every
// workspace pod (RO mount at /opencode on the workspace container
// only) and the supervisor verifies the binary's sha256 against the
// per-arch pins before spawn. All three fields must be set together
// (validated at startup by validateOpencodeDeliveryConfig). Empty
// OpencodeImage = baked-in mode (opencode stays in runtimes/base; S1
// default — the feature is opt-in and inert until the base strip).
OpencodeImage string
OpencodeBinarySHA256AMD64 string
OpencodeBinarySHA256ARM64 string
// AgentdSidecarEnabled (design 0051 US-2, chart-gated, default
// false): when true, buildPod appends the native agentd sidecar and
// switches the main container to supervise-opencode mode. Requires
// AgentdImage (validated at startup) — the sidecar runs the same
// digest-pinned delivery artifact.
AgentdSidecarEnabled bool
// UploadStaging (design 0060 PR 2.5): the env knobs landed on BOTH
// agentd-bearing containers. Zero value = no env = agentd defaults.
UploadStaging UploadStagingConfig
// Recorder emits Kubernetes events on the Workspace (agentd verify
// failures). Injected from mgr.GetEventRecorderFor() in production;
// a FakeRecorder in tests.
Recorder record.EventRecorder
// MaxConcurrentReconciles: how many DIFFERENT workspaces reconcile in
// parallel (controller-runtime never runs the same object concurrently,
// so per-workspace state stays single-writer). 0/negative = framework
// default (1 = fully serial). The default serial behavior made fleet
// resume linear (~8s per workspace — 40 workspaces ≈ 5+ minutes, run
// 33811397260); main.go clamps this to 1..64 to bound apiserver load.
MaxConcurrentReconciles int
// RelayStaging (Epic 72 / design 0058, US-72.3): when non-nil, every
// Creating/Active reconcile runs the staging pass — seal bound BYO
// llm-provider credentials into llm-relay envelope Secrets, mint scoped
// tokens, write the workspace-namespace handoff Secret, maintain the
// CredentialsStaged/CredentialStale/CredentialRejected conditions, and
// run the §4.2 DR/residual-window terminator. NIL (the default —
// --relay-only-key-delivery=false) means zero behavior change: the
// legacy raw-key path and byte-identical batches.
RelayStaging *RelayStagingConfig
// contains filtered or unexported fields
}
func (*WorkspaceReconciler) SetupWithManager ¶
func (r *WorkspaceReconciler) SetupWithManager(mgr ctrl.Manager) error
Source Files
¶
- agentd_overlay.go
- agentd_pins.go
- agentd_sidecar.go
- boot_failure.go
- classification.go
- constants.go
- direct_reader.go
- force_recycle.go
- health.go
- helpers.go
- metrics_wiring.go
- network_policy.go
- opencode_overlay.go
- opencode_pins.go
- org_status_client.go
- org_suspend.go
- overlay_pins.go
- phase_active.go
- phase_creating.go
- phase_pending.go
- phase_suspend.go
- phase_terminating.go
- platform_env.go
- platform_init.go
- pod_builder.go
- pvc.go
- reconciler.go
- recovery.go
- recovery_policy.go
- runtime_resolver.go
- secrets.go
- session_drain.go
- staging.go
- staging_client.go
- staging_guard.go
- suspend_request.go
- upload_staging_env.go