v1alpha1

package
v0.0.0-...-c805bba Latest Latest
Warning

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

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

README

pkg/apis/v1alpha1

API schema for group agentprimitives.authzed.com, version v1alpha1 — the only version. 27 kinds register into SchemeBuilder from their own init() (see register.go and doc.go); everything else in this package is either a shared sub-type or generated.

The kinds

Area Kinds
Agent AgentClass, AgentSession, AgentUI, AgentSettings, ClusterAgentSettings
Identity AgentIdentity, UserIdentity, SessionUserIdentity, ClusterIdentityProvider, CredentialUpdateRequest
Authorization AgentSessionGrants, SpiceDBBootstrap
Tools MCPServer, SidecarToolbox, SpiceboxToolspec, SpiceboxToolkit, SpiceboxToolchain, ToolCall
Tool sandbox SpiceboxClass (template), SpiceboxSession (instance)
Skills Skill, SkillSource, ClusterSkill, ClusterSkillSource
Surfaces Channel, ArtifactRender
Workspace WorkspaceSource

Each kind lives in <lowercased-kind>_types.go. The doc comment above the root struct carries the +kubebuilder: markers that define scope, short name, printer columns, and the status subresource.

Not a kind — shared and generated files

File What it is
zz_generated.deepcopy.go Generated by mage gen:api. Never hand-edit.
conditions.go Condition-type and reason string constants, shared by controllers and CLI.
settings_common_types.go, effective_settings_types.go The tiered-settings shapes. EffectiveSettings is the resolved snapshot stamped onto status; it mirrors pkg/platform/settings.EffectiveSettings in a CRD-serializable form (metav1.Duration, not time.Duration) — the two must stay in sync.
credential_descriptor.go, persession_secrets.go, configmap_ref.go Reference shapes reused across identity and tool kinds.
pinning_types.go, toolguard_types.go, openrouter_routing_types.go, inbound_attachments.go Sub-types embedded by several kinds.
agentsession_ownership.go, archive.go, wake.go Small derivation helpers on AgentSession — read a fact off the object rather than re-deriving it per caller.
toolcall_validation.go, spiceboxclass_validation.go, spiceboxtoolchain_validation.go, workspacesource_validation.go Hand-written validation that CEL markers cannot express (e.g. refusing a ToolCall credential source that names a Secret outside its own namespace — a confused-deputy secret-theft vector). Called by controllers/admission, not by controller-gen.

Constraints

  • This package imports nothing else from pkg/. Helpers stay leaf-shaped (json.go wraps encoding/json) precisely so no group can be pulled in.
  • Doc comments are user-facing. They become the description in config/crds/*.yaml and the text kubectl explain prints.
  • Any CRD-shaping edit needs mage gen:api then mage manifests. See the chain in ../README.md.
  • agentclass_types.go is intentionally not gofmt-clean. gofmt rewrites the paired single quotes in its CEL XValidation rules into smart quotes and the regenerated CRD stops installing. The full explanation is in ../README.md — read it before reformatting the file.

Documentation

Overview

Package v1alpha1 contains API Schema definitions for the agentprimitives.authzed.com v1alpha1 API group (Spicebox).

+kubebuilder:object:generate=true +groupName=agentprimitives.authzed.com +versionName=v1alpha1

Index

Constants

View Source
const (
	ToolSessionLogOff        = "off"
	ToolSessionLogHighSignal = "highSignal"
	ToolSessionLogFull       = "full"
)

ToolSessionLog modes — values for AgentClassSpec.ToolSessionLog.

View Source
const (
	// FactSourceStateGated is a producer every call of which is gated; GatedBy
	// names the types.
	FactSourceStateGated = "gated"
	// FactSourceStateUngated is a producer no permission check gates.
	FactSourceStateUngated = "ungated"
	// FactSourceStateUnresolved is a real producer whose gate could not be
	// determined. Distinct from ungated, and never treated as blocked.
	FactSourceStateUnresolved = "unresolved"
	// FactSourceStateAbsent is an observed fact nothing on this class records.
	FactSourceStateAbsent = "absent"
	// FactSourceStateEnvelope is an envelope fact, which no tool ever records.
	FactSourceStateEnvelope = "envelope"
)

The values of FactSource.State. Each is a state the reconciler actually produces — the CRD enum on the field is exactly this set, so a new one is added in both places or not at all.

View Source
const (
	ArtifactVisibilitySession      = "session"
	ArtifactVisibilityOrganization = "organization"
)

The ArtifactVisibility values. An absent field means ArtifactVisibilitySession.

View Source
const (
	// MetaagentTriggerMention is the default: the ambient path is off entirely.
	// The metaagent runs only when addressed explicitly, plus at session start.
	// This is today's behavior.
	MetaagentTriggerMention = "mention"

	// MetaagentTriggerShadow classifies every inbound turn and APPLIES NOTHING.
	//
	// The measurement mode, and the reason it exists is that the alternative is
	// measuring in production. The prefilter is a keyword heuristic that trades
	// recall for cost and whose miss rate is unmeasured; the classifier behind
	// it is an LLM reading untrusted text. Shadow runs both for real and records
	// what WOULD have happened — prefiltered-out versus extractor-invoked, and
	// which decision came back — while changing nothing about the session.
	//
	// So a cluster can turn this on for a few agents, read the numbers, and
	// decide whether inline is worth it. That is the same discipline the plan
	// gate's logging mode follows, for the same reason.
	MetaagentTriggerShadow = "shadow"

	// MetaagentTriggerInline classifies every inbound turn AND acts on it.
	//
	// Everything downstream still applies: a widening needs approval, standing
	// still gates the surface, and the admin ceiling still clamps. What changes
	// is only how often the classifier runs.
	MetaagentTriggerInline = "inline"
)

MetaagentTrigger names WHEN the metaagent classifies a turn, and whether it acts on what it finds.

Three operating points, ordered by how much they do. The default is the conservative one deliberately: the metaagent turns an utterance into a session change, so widening WHEN it looks widens how often untrusted text reaches an authorization surface — even though the classifier itself can grant nothing.

All three leave the EXPLICIT path alone. An @metaagent mention, and the session-start trigger, behave identically under every value; this setting governs the AMBIENT half only. A user who addresses the metaagent directly is making a request, and silently ignoring it in one mode would be surprising.

View Source
const (
	AgentIdentityConditionValid   = "Valid"
	AgentIdentityConditionRefresh = "Refresh"

	// AgentIdentityConditionPlatformLinked reports whether this identity is
	// linked to the singleton SpiceDB platform object
	// (agentidentity:<ns>/<name>#platform@platform:platform).
	//
	// That ONE relationship is the only live arm of
	// agentidentity#update_credential — the permission gating "a human replaces
	// this agent's own dead shared credential" — so while it is absent every
	// such check fails closed, otherwise SILENTLY: the schema compiles, the card
	// publishes, the button renders, the click is refused.
	//
	// Deliberately NOT folded into Valid: an identity whose credentials all
	// resolve is usable by every session, and Valid=False over a SpiceDB-side
	// problem would stop agents that work fine. What is broken is only the
	// operator's ability to REPAIR it later.
	AgentIdentityConditionPlatformLinked = "PlatformLinked"
)
View Source
const (
	LabelAttendedParentNamespace = "agentprimitives.authzed.com/attended-parent-namespace"
	LabelAttendedParentName      = "agentprimitives.authzed.com/attended-parent-name"
)

LabelAttendedParentNamespace and LabelAttendedParentName together name the session watching an `attended` delegated child — sr.Spec.Parent, the request's OWN parent, not necessarily the tree's root. Set once at creation, alongside LabelDelegationRoot, and never updated: the pair is a pure function of the request that created the child, so a re-apply is byte-identical.

Split into TWO labels rather than one "<namespace>/<name>" value for the same reason LabelWorkshopSessionNamespace/LabelWorkshopSessionName are (workshop_types.go): a Kubernetes label VALUE may not contain "/" — validation.IsValidLabelValue rejects it — so a combined value would be rejected by a real apiserver on every attended child's Create, a failure a fake-client unit test would never catch. Each half, a bare namespace or session name, is independently a valid label value on its own.

Two things read the pair. The one-attended-child-at-a-time check (spec §lifecycle) lists a tree's whole closure and keeps only members carrying LabelAttendedParentName with a non-terminal AgentSession phase — its mere presence is the "this is an attended child" marker, independent of whatever the SubagentRequest's own spec.mode says today. Tasks 6-7's routing (append the child's turns to the watching session's inbox, wake it on mention or on the child's error/completion) resolve the watching session FROM this pair rather than re-deriving it from the child's OwningSubagentRequest at delivery time — a live read on every turn the SubagentRequest controller already paid for once, at creation. Both are still ordinary label VALUES (a namespace or session name), so a caller who needs to find every child watched by one session can select on them directly (client.MatchingLabels{LabelAttendedParentNamespace: ns, LabelAttendedParentName: name}) with no hash or List-then-filter.

View Source
const (
	// PendingRestartModeRestart (the empty-string default) is
	// restart-from-here: turns 0..CutTurnIndex are copied and the edited
	// message becomes turn CutTurnIndex+1 in the child.
	PendingRestartModeRestart = ""
	// PendingRestartModeInherit is continuation-inherit: the FULL parent
	// transcript is copied (the reconciler recomputes the cut as the
	// parent's last turn index) and the new message is appended after it.
	// Written by channelsd when a new inbound arrives for a terminal
	// session that a fresh clean retry can continue from.
	PendingRestartModeInherit = "inherit"
	// PendingRestartModeTakeover is a DIFFERENT-user continuation: someone who
	// is not the parent's started_by continues a terminal predecessor in the
	// same thread and becomes the child's owner. InheritHistory selects whether
	// the parent transcript carries over (ordinary terminal) or the child
	// starts fresh (policy/security halt). Authorization is the channelsd choke
	// point — the requester sent a legitimate app-mention into the thread, the
	// same basis on which they could start their own session — so
	// ReconcileRestart does NOT run the agentsession#fork gate for this mode.
	// The inherit and restart modes keep it.
	PendingRestartModeTakeover = "takeover"
)

PendingRestartMode selects how the fork reconciler seeds the child's transcript. Empty is the default restart-from-here behavior.

View Source
const (
	ChannelRoleInput      = "input"
	ChannelRoleOutput     = "output"
	ChannelRoleBoth       = "both"
	ChannelRoleMonitoring = "monitoring"
)

ChannelRole values for ChannelSpec.Role. These match the kubebuilder enum.

View Source
const (
	// ConditionIdPValid is the readiness condition identityd consults
	// before serving IdP login.
	ConditionIdPValid = "Valid"

	ReasonIdPReady           = "Ready"
	ReasonIdPSecretMissing   = "SecretMissing"
	ReasonIdPDiscoveryFailed = "DiscoveryFailed"
	ReasonIdPConfigInvalid   = "ConfigInvalid"
)

ClusterIdentityProvider condition type + reasons.

View Source
const (
	ReasonClassValid                 = "Valid"
	ReasonClassInvalidImage          = "InvalidImage"
	ReasonClassInvalidTool           = "InvalidTool"
	ReasonClassInvalidResource       = "InvalidResource"
	ReasonClassInvalidEnvDefaults    = "InvalidEnvDefaults"
	ReasonClassInvalidPrivateVolumes = "InvalidPrivateVolumes"
	ReasonToolspecCoverageMissing    = "ToolspecCoverageMissing"
	// ReasonClassInvalidSandbox marks a class whose spec.sandbox names an
	// unregistered backend, or a backend that cannot satisfy what the class asks.
	ReasonClassInvalidSandbox = "InvalidSandbox"
	// ReasonClassInvalidSandboxWarmPool marks a class that requests non-zero
	// spec.sandbox.warmPool.replicas on a backend whose Runtime does not
	// implement sandboxkinds.Prewarmer. Distinct from ReasonClassInvalidSandbox
	// so an operator can tell "the backend itself is wrong" apart from "the
	// backend is fine, but it cannot pre-warm."
	ReasonClassInvalidSandboxWarmPool = "InvalidSandboxWarmPool"
	// ReasonSandboxKindNotPermitted marks a resource whose resolved sandbox
	// backend exists but is excluded by an allowedSandboxKinds ceiling. Distinct
	// from ReasonClassInvalidSandbox, which means the named backend is not
	// registered at all � the two need different operator responses.
	ReasonSandboxKindNotPermitted = "SandboxKindNotPermitted"
	// ReasonSandboxConfigIgnored marks a resource where a sandbox config value
	// was not a JSON object and was skipped. Non-fatal: the resolved backend is
	// still usable. Distinct from ReasonSandboxKindNotPermitted, which means the
	// backend itself is forbidden here.
	ReasonSandboxConfigIgnored = "SandboxConfigIgnored"
)

SpiceboxClass reasons.

View Source
const (
	ReasonToolkitMissing    = "ToolkitMissing"
	ReasonCELCompileError   = "CELCompileError"
	ReasonUnknownSubcommand = "UnknownSubcommand"
	ReasonSpecLoadFailed    = "SpecLoadFailed"
)

SpiceboxToolspec reasons.

View Source
const (
	SpiceboxSessionConditionReady       = "Ready"
	SpiceboxSessionConditionProgressing = "Progressing"
	SpiceboxSessionConditionFailed      = "Failed"
	SpiceboxSessionConditionTerminated  = "Terminated"
)

SpiceboxSession condition types.

View Source
const (
	ReasonPodReady     = "PodReady"
	ReasonPodNotReady  = "PodNotReady"
	ReasonPodMissing   = "PodMissing"
	ReasonCreating     = "Creating"
	ReasonDraining     = "Draining"
	ReasonPodOOMKilled = "PodOOMKilled"
	ReasonPodCrashed   = "PodCrashed"
	// ReasonPodStartFailed marks a bundle pod wedged in a terminal container
	// "Waiting" state it will not recover from on its own � an unpullable image
	// (ImagePullBackOff), an unsatisfiable config (CreateContainerConfigError), a
	// crash loop, etc. The specific kubelet reason is carried in the condition
	// message. Distinct from ReasonPodCrashed (PodFailed phase), since these pods
	// never reach PodFailed � they retry forever � so without this the bundle (and
	// its waiting AgentSession) would hang silently.
	ReasonPodStartFailed = "PodStartFailed"
	// ReasonWorkspaceProvisioningFailed marks a session whose workspace (or
	// snapshot-store) PVC could not be provisioned by its StorageClass — an
	// undersized request the backend rejects (Filestore's minimum share size),
	// a disabled cloud API, quota, or a missing class. The provider's actual
	// event message is carried in the condition message. Distinct from
	// BundleFailed (a pod that scheduled but never became Ready): here no pod can
	// ever schedule because its volume never binds, so we fail fast with the
	// storage reason instead of waiting out bundleReadyDeadline behind the
	// generic "did not become Ready" text.
	ReasonWorkspaceProvisioningFailed = "WorkspaceProvisioningFailed"
	ReasonNodeEvicted                 = "NodeEvicted"
	ReasonClassMissing                = "ClassMissing"
	ReasonClassInvalid                = "ClassInvalid"
	ReasonIdleTTL                     = "IdleTTL"
	ReasonMaxDuration                 = "MaxDuration"
	ReasonUserDelete                  = "UserDelete"
	ReasonToolspecNotInClass          = "ToolspecNotInClass"
)

SpiceboxSession reasons.

View Source
const (
	ToolCallConditionValidated = "Validated"
	ToolCallConditionRunning   = "Running"
	ToolCallConditionSucceeded = "Succeeded"
	ToolCallConditionFailed    = "Failed"
	ToolCallConditionTimeout   = "Timeout"
	ToolCallConditionCanceled  = "Canceled"
	// ToolCallConditionSnapshotAuditRecorded indicates the workspace
	// snapshot audit entry (tool_dispatch_snapshot memory kind) has
	// been recorded for this ToolCall. Used by the toolcall reconciler
	// to skip redundant Record calls on re-entry for streaming ToolCalls.
	ToolCallConditionSnapshotAuditRecorded = "SnapshotAuditRecorded"
	// ToolCallConditionReconcileRetrying is True while the reconciler is
	// failing on this ToolCall and being requeued. Its message carries the
	// last error and its LastTransitionTime marks when the current streak
	// began — which is what bounds how long the call may keep failing before
	// it is finished outright (ReasonToolCallReconcileFailed). It exists so a
	// stuck call is legible on the object from the FIRST failure, rather than
	// only in an operator log nobody is watching while a person waits.
	ToolCallConditionReconcileRetrying = "ReconcileRetrying"
)

ToolCall condition types.

View Source
const (
	ReasonToolCallValid             = "Valid"
	ReasonSessionNotActive          = "SessionNotActive"
	ReasonToolUnknown               = "ToolUnknown"
	ReasonInputArtifactMissing      = "InputArtifactMissing"
	ReasonExecStarted               = "ExecStarted"
	ReasonExecClosed                = "ExecClosed"
	ReasonProcessExited             = "ProcessExited"
	ReasonNonZeroExit               = "NonZeroExit"
	ReasonSessionGone               = "SessionGone"
	ReasonOperatorRestart           = "OperatorRestart"
	ReasonHydrateFailed             = "HydrateFailed"
	ReasonDeadlineExceeded          = "DeadlineExceeded"
	ReasonSpiceboxSessionTerminated = "SpiceboxSessionTerminated"
	ReasonToolspecDenied            = "ToolspecDenied"
	ReasonStreamTokenHashMissing    = "StreamTokenHashMissing"
	// ReasonSessionBindingMismatch: the ToolCall's spec.session names a bundle
	// belonging to a different AgentSession than the one that owns the call.
	// Both fields are creator-written, and everything privileged downstream
	// keys on spec.session — the sandbox to exec into and the session whose
	// use_token grant is consulted — so they must agree.
	ReasonSessionBindingMismatch = "SessionBindingMismatch"
	// ReasonSandboxUnresolved: the session has no sandbox handle yet, or its
	// kind has no runtime linked into this binary. Never a fallback to a
	// default backend � running in the wrong sandbox is worse than refusing.
	ReasonSandboxUnresolved = "SandboxUnresolved"
	// Interactive-mode terminal reasons. ReasonToolCallMaxDuration is named
	// to avoid colliding with SpiceboxSession's ReasonMaxDuration above; both
	// share the same value string but Go const names must be unique.
	ReasonIdleExit            = "IdleExit"
	ReasonToolCallMaxDuration = "ToolCallMaxDuration"
	// Reconcile-failure surfacing. ReasonToolCallReconcileError marks the
	// ReconcileRetrying condition while attempts continue;
	// ReasonToolCallReconcileRecovered clears it after one succeeds; and
	// ReasonToolCallReconcileFailed is the TERMINAL Failed reason for a call
	// that kept failing past the budget, carrying the last error so the
	// runner can hand the agent — and the agent the user — a reason.
	ReasonToolCallReconcileError     = "ReconcileError"
	ReasonToolCallReconcileRecovered = "ReconcileRecovered"
	ReasonToolCallReconcileFailed    = "ReconcileFailed"
)

ToolCall reasons.

View Source
const (
	ReasonAllReferencesResolve    = "AllReferencesResolve"
	ReasonSpecInvalid             = "SpecInvalid"
	ReasonSecretMissing           = "SecretMissing"
	ReasonSecretKeyMissing        = "SecretKeyMissing"
	ReasonOAuthSecretIncomplete   = "OAuthSecretIncomplete"
	ReasonBindingPrefixMixed      = "BindingPrefixMixed"
	ReasonBindingModeMismatch     = "BindingModeMismatch"
	ReasonBindingMatchUnparseable = "BindingMatchUnparseable"
	ReasonAgentCredentialExpired  = "AgentCredentialExpired"
	ReasonCredentialEmpty         = "CredentialEmpty"

	// ReasonCredentialShapeMismatch: the stored value does not match the token
	// format its provider declares (provider.TokenShape). It is a Valid=False
	// reason rather than a warning because the alternative is what it replaces:
	// every readiness gate reporting healthy and the credential failing as an
	// opaque 401 inside a tool's own output, minutes later. The message names
	// both the expected shape and the one the value actually has.
	ReasonCredentialShapeMismatch = "CredentialShapeMismatch"

	// PlatformLinked reasons. ReasonPlatformLinkFailed is TRANSIENT (SpiceDB
	// unreachable, schema not composed yet) and keeps being retried. The two
	// Unrepresentable* reasons are PERMANENT: a Kubernetes name may contain
	// '.', a SpiceDB object id may not, so the link can never be written and
	// the reconciler stops retrying and says so. For a class that means it can
	// never appear in the browser's agent picker.
	ReasonPlatformLinked              = "PlatformLinked"
	ReasonPlatformLinkFailed          = "PlatformLinkFailed"
	ReasonUnrepresentableIdentityName = "UnrepresentableIdentityName"
	ReasonUnrepresentableClassName    = "UnrepresentableClassName"

	// AgentIdentity Refresh-condition reasons, set by the OAuth refresh controller.
	ReasonNoOAuthCredentials     = "NoOAuthCredentials"
	ReasonAllOAuthFresh          = "AllOAuthFresh"
	ReasonRefreshSucceeded       = "RefreshSucceeded"
	ReasonNoRefreshToken         = "NoRefreshToken"
	ReasonTokenEndpointError     = "TokenEndpointError"
	ReasonRefreshResponseInvalid = "RefreshResponseInvalid"
)

AgentIdentity reasons.

View Source
const (
	ToolCallConditionEnvHighEntropy = "EnvHighEntropy"

	ReasonToolCallEnvSecretLike     = "ToolCallEnvSecretLike"
	ReasonToolCallEnvShadowsAgent   = "ToolCallEnvShadowsAgent"
	ReasonCredentialSourceForbidden = "CredentialSourceForbidden"
	ReasonAgentNotFound             = "AgentNotFound"
	ReasonAgentBindingMissing       = "AgentBindingMissing"
	ReasonAgentCredentialUnresolved = "AgentCredentialUnresolved"
	ReasonAgentSecretMissing        = "AgentSecretMissing"
	ReasonAgentSecretKeyMissing     = "AgentSecretKeyMissing"

	// ReasonAgentCredentialRevoked is set on the ToolCall Failed condition
	// when the sandbox-exec pre-dispatch use_token check gets a definitive
	// deny from SpiceDB (CheckUseToken returns false, nil) � the credential
	// resolved fine (ReasonAgentCredentialUnresolved does not apply) but the
	// operator's authorized_token grant for it no longer matches (revoked,
	// rotated, or never granted). Distinct from
	// ReasonAgentSessionTokenAuthzUnavailable, which is the indeterminate
	// case (the check itself couldn't be confirmed) and fails the parent
	// AgentSession, not just this ToolCall. A definitive deny is surgical:
	// only this ToolCall fails; the session continues.
	ReasonAgentCredentialRevoked = "AgentCredentialRevoked"
)

ToolCall agent-related condition types and reasons.

View Source
const (
	FinalizerSpiceboxSession  = "spiceboxsession.agentprimitives.authzed.com/finalizer"
	FinalizerToolCall         = "toolcall.agentprimitives.authzed.com/finalizer"
	FinalizerAgentSession     = "agentsession.agentprimitives.authzed.com/finalizer"
	FinalizerArtifactRender   = "artifactrender.agentprimitives.authzed.com/finalizer"
	FinalizerSpiceDBBootstrap = "spicedbbootstrap.agentprimitives.authzed.com/finalizer"
	FinalizerSidecarToolbox   = "sidecartoolbox.agentprimitives.authzed.com/finalizer"
	FinalizerPublicEndpoint   = "publicendpoint.agentprimitives.authzed.com/finalizer"
	FinalizerUserIdentity     = "useridentity.agentprimitives.authzed.com/finalizer"
	FinalizerWorkshop         = "workshop.agentprimitives.authzed.com/finalizer"
	// FinalizerSubagentRequest is added ONLY to an `attended`-mode
	// SubagentRequest, giving the controller a chance to notify a live
	// attended child's watching parent (a fixed inbound line + a forced wake)
	// before an early delete (a "stop") cascades the child away — the
	// GC-cascade would otherwise remove both objects with nothing left to
	// read. Every other mode never carries it.
	FinalizerSubagentRequest = "subagentrequest.agentprimitives.authzed.com/finalizer"
)

Finalizers.

View Source
const (
	AgentClassConditionValid = "Valid"
	// AgentClassConditionPlatformLinked is True once the controller has written
	// agentclass:<ns>/<name>#platform@platform:platform � the tuple that makes
	// agentclass#start_session satisfiable, and so the only reason this class
	// can appear in the browser's agent picker at all. A condition rather than
	// a silent write because its absence is otherwise invisible: the schema
	// compiles, the dashboard renders, and the picker is simply empty. Reasons
	// are shared with AgentIdentity's platform link, plus
	// ReasonUnrepresentableClassName for the permanent case.
	AgentClassConditionPlatformLinked = "PlatformLinked"
	// AgentClassConditionStartersLinked is True once agentclass#starter holds
	// exactly spec.authz.session.allowedStarters (reason StartersLinked), or
	// when the class declares none and the relation was cleared (reason
	// NoAllowedStarters). False with StartersLinkFailed on a transient write
	// error (requeued), or ReasonUnrepresentableClassName (permanent).
	AgentClassConditionStartersLinked = "StartersLinked"
	// AgentClassConditionSettingsAccepted is True when the 4-tier settings
	// chain resolves without any fatal violation for this class.
	AgentClassConditionSettingsAccepted = "SettingsAccepted"
	// AgentClassConditionSchemaValidated is True when the operator has verified
	// every referenced tool's permission_check against the live SpiceDB schema.
	// Lazy: only re-runs on an AgentClass or referenced-tool Generation bump.
	AgentClassConditionSchemaValidated = "SchemaValidated"
	// AgentClassConditionAgentSessionGrantsWritten is True when the controller
	// has reconciled the owned "<className>-grants" AgentSessionGrants CR from
	// the union of resolved tool Permission.Check pairs. False with reason
	// AgentSessionGrantsWriteFailed when the write errored � surfaced without
	// failing the overall reconcile, so an API-server hiccup does not flap Valid.
	AgentClassConditionAgentSessionGrantsWritten = "AgentSessionGrantsWritten"
	// AgentClassConditionToolAuthDisabled is True when spec.authz.toolCalls.mode is
	// "disabled" � the operator-visible signal that per-tool authz is bypassed
	// for every session of this class. Reason is always ReasonToolAuthBypassed;
	// absent or False on enforcing/permissive classes.
	AgentClassConditionToolAuthDisabled = "ToolAuthDisabled"
	// AgentClassConditionInformationLeakageReady is True when every bound
	// Channel's kind satisfies the capability level required by
	// spec.authz.informationLeakage.mode. False (with a specific reason)
	// when mode=enforcing and a bound channel's kind either has no
	// AudienceResolver or is SingleUser with singleUserBypass=false.
	// Always True when mode=disabled or mode=logging.
	AgentClassConditionInformationLeakageReady = "InformationLeakageReady"
	// AgentClassConditionCapabilitiesValid is True when every key in
	// spec.capabilities names a registered capability and its config parses
	// (both the common {enabled} envelope and the capability-specific
	// ParseConfig). False (with a message) does NOT block readiness � an
	// unrecognized or malformed grant is dropped gracefully at assembly
	// time (see pkg/agent/tool/meta/capability), so this condition is purely
	// diagnostic. Absent capabilities map � vacuously True.
	AgentClassConditionCapabilitiesValid = "CapabilitiesValid"
	// AgentClassConditionSubagentsCapabilityGranted is True when
	// spec.subagents is empty (nothing to delegate to, so the question does
	// not apply) OR spec.capabilities carries a "subagents" entry. False when
	// spec.subagents names one or more classes but spec.capabilities has no
	// "subagents" key at all: the roster then has nowhere to send a delegate
	// call, since the subagents capability (DefaultOn() == false) is what
	// offers the delegate/reply_to_subagent tools in the first place — a
	// class in this state is fully valid and reconciles cleanly, but can
	// never actually hand work to anyone on its own roster. Purely
	// diagnostic, same as CapabilitiesValid: does NOT block readiness, since
	// nothing about this is a spec error, only an easy-to-miss omission.
	AgentClassConditionSubagentsCapabilityGranted = "SubagentsCapabilityGranted"
)

AgentClass condition types.

View Source
const (
	ReasonConfigMapMissing            = "ConfigMapMissing"
	ReasonConfigMapKeyMissing         = "ConfigMapKeyMissing"
	ReasonToolspecMissing             = "ToolspecMissing"
	ReasonAgentIdentityMissing        = "AgentIdentityMissing"
	ReasonAgentIdentityBindingMissing = "AgentIdentityBindingMissing"
	// ReasonAgentIdentityInvalid fires when the AgentClass's bound
	// AgentIdentity exists but is itself not Valid=True (e.g. an empty-
	// value Secret backing one of its credentials). Propagating the
	// identity's invalidity into the class completes the start-gate
	// chain: the AgentSession reconciler parks any session whose
	// AgentClass is not Valid (ReasonAgentClassNotValid).
	ReasonAgentIdentityInvalid    = "AgentIdentityInvalid"
	ReasonBundleToolNameCollision = "BundleToolNameCollision"
	ReasonTestProviderNotAllowed  = "TestProviderNotAllowed"

	// StartersLinked reasons. ReasonStartersLinked is the AgentClassConditionStartersLinked
	// True reason when a non-empty allowedStarters was written; ReasonNoAllowedStarters
	// is the True reason when the class declares none and the relation was cleared.
	// ReasonStartersLinkFailed is the transient False reason (requeued); the permanent
	// False reason reuses ReasonUnrepresentableClassName, shared with PlatformLinked.
	ReasonStartersLinked     = "StartersLinked"
	ReasonNoAllowedStarters  = "NoAllowedStarters"
	ReasonStartersLinkFailed = "StartersLinkFailed"
)

AgentClass reasons.

View Source
const (
	ReasonToolPermissionMissing = "ToolPermissionMissing"
	// ReasonPlanGateRequiresToolCallEnforcing: planGate.mode=enforcing with
	// toolCalls.mode not enforcing. The plan gate enforces the CLASS axis
	// itself, but the INSTANCE axis rides on tool_call_authz � so with that
	// hook permissive or unregistered, per-resource gating would silently stop
	// while the class axis kept refusing, which reads as "the gate is working".
	ReasonPlanGateRequiresToolCallEnforcing = "PlanGateRequiresToolCallEnforcing"
	// ReasonPlanExampleInvalid: an authored planGate.examples entry names a
	// handle or slot type this class cannot declare. Refused rather than
	// dropped: the prompt tells agents an undeclarable handle is silently
	// removed from their phase, and planners transcribe these examples closely,
	// so admitting one would teach that failure into every plan the agent
	// writes — surfacing much later as calls denied for no visible reason.
	ReasonPlanExampleInvalid = "PlanExampleInvalid"
	// ReasonTrifectaCeilingContradicted: the class declares
	// authz.trifecta.neverConsequential yet holds a readwrite or external tool
	// permission. A declared ceiling checked against the derived surface, so a
	// contradiction is surfaced at apply time rather than as an untraceable
	// refusal during a later delegation.
	ReasonTrifectaCeilingContradicted = "TrifectaCeilingContradicted"
	// ReasonBoundEntitiesRenamed: spec.boundEntities still set after the
	// rename. Rejected rather than ignored � silently dropping an authz field
	// would leave the agent less constrained than its author wrote.
	ReasonBoundEntitiesRenamed = "BoundEntitiesRenamed"
	// ReasonSlotDeclarationInvalid: an authz.slots entry is internally
	// inconsistent � an unknown enum value, a duplicate resourceType, or a
	// trust policy declared where it cannot take effect. Rejected rather than
	// ignored: a policy field that never runs is worse than an absent one,
	// because its presence in the YAML reads as protection.
	ReasonSlotDeclarationInvalid = "SlotDeclarationInvalid"
	// ReasonSlotPreconditionUnsatisfiable: a slot precondition reads an
	// observed fact whose every declared producer is a tool gated by that same
	// slot. Nothing can ever satisfy it — the agent is handed a hint naming a
	// call it will be denied, and burns its budget retrying. Refused at
	// admission for the same reason a subagent cycle is: a bound that only
	// holds at runtime does not hold.
	//
	// Its OWN reason rather than ReasonSlotDeclarationInvalid because this is
	// not an internal-consistency claim about the declaration — it is a claim
	// about the declaration against the class's resolved tools, and it can only
	// be judged once those are resolved.
	ReasonSlotPreconditionUnsatisfiable = "SlotPreconditionUnsatisfiable"
	// ReasonSlotPreconditionUnroutable: a slot precondition's waiver has no one
	// to route to. A Refused verdict raises a human waiver card whose approver
	// pool is the precondition's own approvers[] when SET (authoritative), or the
	// slot type's resolved standing when UNSET. Two shapes leave that pool empty
	// by SHAPE: a SET approvers[] that names no subject (a list of blanks, which
	// overrides the standing default with a route to no one), or an UNSET
	// approvers[] whose slot type resolves to no standing at all.
	//
	// Refused at admission, and only for the structurally-empty case: this
	// cannot and does not claim SpiceDB currently holds zero approver subjects
	// for a declared standing (that is a runtime fact, not a shape). Its OWN
	// reason rather than ReasonSlotPreconditionUnsatisfiable because the defect
	// is the absent APPROVER route, not an unsatisfiable predicate, and rather
	// than ReasonSlotDeclarationInvalid because it is a claim about the
	// precondition's own approver routing, judged against the resolved standings.
	ReasonSlotPreconditionUnroutable = "SlotPreconditionUnroutable"
	// ReasonRosterInvalid marks an AgentClass whose delegation roster does not
	// hold together: a spec.subagents graph with a cycle, one that exceeds the
	// depth ceiling, or one naming a class that does not exist — plus a
	// spec.subagentModes entry keyed on a name absent from spec.subagents, or
	// carrying a value that is not a delegation mode.
	ReasonRosterInvalid = "RosterInvalid"
	// ReasonConfigInvalid: spec.config violates spec.configSchema, or a bound
	// toolspec constraint references a config key not declared in configSchema.
	ReasonConfigInvalid = "ConfigInvalid"
	// ReasonUserPreferencesInvalid: spec.userPreferences is self-inconsistent
	// (duplicate key, default violating its own type/enum/pattern, …).
	ReasonUserPreferencesInvalid        = "UserPreferencesInvalid"
	ReasonPermissionSpecInvalid         = "PermissionSpecInvalid"
	ReasonPermissionTemplateUnresolved  = "PermissionTemplateUnresolved"
	ReasonPermissionTransformUnknown    = "PermissionTransformUnknown"
	ReasonPermissionSchemaMismatch      = "PermissionSchemaMismatch"
	ReasonSpiceDBUnreachable            = "SpiceDBUnreachable"
	ReasonAgentSessionGrantsWritten     = "AgentSessionGrantsWritten"
	ReasonAgentSessionGrantsWriteFailed = "AgentSessionGrantsWriteFailed"
	// ReasonToolAuthBypassed is the Reason on AgentClassConditionToolAuthDisabled
	// when status=True. Message includes a fixed operator-readable note that
	// tool calls run without SpiceDB validation.
	ReasonToolAuthBypassed = "ToolAuthBypassed"

	// ReasonInfoLeakageReady is the Reason on
	// AgentClassConditionInformationLeakageReady when status=True.
	ReasonInfoLeakageReady = "CapabilitySatisfied"
	// ReasonChannelKindLacksAudienceResolver is set when a bound Channel's
	// kind has no AudienceResolver and
	// informationLeakage.onUnsupportedChannel=blockBinding.
	ReasonChannelKindLacksAudienceResolver = "ChannelKindLacksAudienceResolver"
	// ReasonSingleUserBypassDisabled is set when a bound Channel's kind
	// reports CapabilitySingleUser but informationLeakage.singleUserBypass
	// is false.
	ReasonSingleUserBypassDisabled = "SingleUserBypassDisabled"

	// ReasonCapabilitiesValid is the Reason on
	// AgentClassConditionCapabilitiesValid when status=True.
	ReasonCapabilitiesValid = "CapabilitiesValid"
	// ReasonUnknownCapability is set when spec.capabilities has a key that
	// names no registered capability.
	ReasonUnknownCapability = "UnknownCapability"
	// ReasonInvalidCapabilityConfig is set when spec.capabilities has a
	// known key whose value fails to parse (the common {enabled} envelope
	// or the capability-specific config).
	ReasonInvalidCapabilityConfig = "InvalidCapabilityConfig"

	// ReasonSubagentsCapabilityGranted is the Reason on
	// AgentClassConditionSubagentsCapabilityGranted when status=True (either
	// no roster, or the capability is present).
	ReasonSubagentsCapabilityGranted = "SubagentsCapabilityGranted"
	// ReasonSubagentsCapabilityMissing is set when spec.subagents is
	// non-empty but spec.capabilities has no "subagents" entry: the roster
	// exists but nothing offers the delegate tool that would use it.
	ReasonSubagentsCapabilityMissing = "SubagentsCapabilityMissing"
)

AgentClass reasons for per-tool permission validation.

View Source
const (
	AgentSessionConditionClassResolved = "ClassResolved"
	AgentSessionConditionBundlesReady  = "BundlesReady"
	AgentSessionConditionRunnerReady   = "RunnerReady"
	AgentSessionConditionSucceeded     = "Succeeded"
	AgentSessionConditionFailed        = "Failed"
	// AgentSessionConditionSettingsAccepted is True when the 4-tier settings
	// chain resolves without any fatal violation for this session.
	AgentSessionConditionSettingsAccepted = "SettingsAccepted"
	// AgentSessionConditionSkillBundlesIntegrity is False when a staged skill
	// bundle's store-returned bytes did not hash to its SkillBundleRef digest �
	// a corrupt or poisoned cache. The skill still runs instruction-only, but
	// the failure is surfaced here so `kubectl describe` distinguishes it from
	// the benign not-cached case, which produces no condition at all. Absent
	// when every bundle the store served verified cleanly.
	AgentSessionConditionSkillBundlesIntegrity = "SkillBundlesIntegrity"
	// AgentSessionConditionSandboxScheduling is the durable, channelsd-visible
	// twin of the one-shot "capacity" monitoring event: False
	// (ReasonSandboxUnschedulable) when a bundle or content-guard detector pod
	// has been stuck Pending on a scheduler failure (insufficient cpu/memory,
	// taints, �) past the grace window, with the scheduler's real reason in
	// Message; True (ReasonSandboxScheduled) once nothing is stalled, so a
	// resolved capacity problem is reflected as promptly as the stall was.
	// Absent until the first wait observes a scheduling result.
	//
	// Unlike the monitoring event it is set regardless of whether
	// MonitoringPublish is configured: it is the primary signal channelsd
	// reacts to, and must work on clusters with no NATS wiring.
	AgentSessionConditionSandboxScheduling = "SandboxScheduling"
)

AgentSession condition types.

View Source
const (
	ReasonSettingsResolved         = "SettingsResolved" // accepted, no warnings
	ReasonSettingsClamped          = "SettingsClamped"  // accepted, budget clamped
	ReasonSettingsSelfInconsistent = "SelfInconsistent" // a Settings default violates its own ceiling
	ReasonSettingsNotSingleton     = "NotSingleton"     // a Settings CR has a non-well-known name
	ReasonSettingsConsistent       = "Consistent"       // Settings CR SelfConsistent=True
	ReasonClassPreferencesValid    = "ClassPreferencesValid"
	ReasonClassPreferencesInvalid  = "ClassPreferencesInvalid"
)

Settings reasons (shared by AgentClass + AgentSession SettingsAccepted and the Settings CR SelfConsistent conditions).

View Source
const (
	SettingsConditionSelfConsistent = "SelfConsistent"

	// SettingsConditionClassPreferencesValid reports whether every
	// classUserPreferences global matches an installed class's declared
	// userPreferences schema. Unknown class names are tolerated (install
	// order); unknown keys and type mismatches against an INSTALLED class
	// are violations.
	SettingsConditionClassPreferencesValid = "ClassPreferencesValid"
)

ClusterAgentSettings / AgentSettings condition types.

View Source
const (
	AgentSessionPhasePending   = "Pending"
	AgentSessionPhaseRunning   = "Running"
	AgentSessionPhaseSucceeded = "Succeeded"
	AgentSessionPhaseFailed    = "Failed"
	// AgentSessionPhaseAwaitingApproval is the legacy phase name; see
	// AgentSessionPhaseAwaitingDecision. Kept for back-compat with
	// in-flight CRs written before the unified state machine.
	AgentSessionPhaseAwaitingApproval = "AwaitingApproval"
	// AgentSessionPhaseAwaitingDecision is the canonical phase entered
	// when one or more tool dispatches are paused waiting for a human
	// decision (tool_call, leakage_share, content_inspect, scope_review).
	// Restored to the pre-decision phase when all pending decisions resolve.
	AgentSessionPhaseAwaitingDecision = "AwaitingDecision"
	// AgentSessionPhaseHeld is a forensic hold: the session is parked with its
	// workspace preserved, pending a human decision to release it. Byte-identical
	// to lifecycle.PhaseHeld, which projects onto status.phase.
	AgentSessionPhaseHeld = "Held"
	// AgentSessionPhaseAwaitingRetry is entered when the LLM provider
	// returned an error on a channel-attached session. Pod exits clean;
	// channelsd posts a Retry button on the channel. Click sets the
	// wake annotation; the controller transitions back to Pending and
	// respawns the runner. Non-terminal.
	AgentSessionPhaseAwaitingRetry = "AwaitingRetry"
)

AgentSession phases (status.phase string values).

View Source
const (
	ReasonAgentClassMissing  = "AgentClassMissing"
	ReasonAgentClassNotValid = "AgentClassNotValid"
	ReasonRunnerCreating     = "RunnerCreating"
	ReasonRunnerReady        = "RunnerReady"
	// ReasonRunnerPodRefused is set on RunnerReady=False when creating the
	// runner workload was REFUSED — an admission verdict (a quota, a policy, a
	// webhook), as opposed to ReasonSandboxUnschedulable, where it was created
	// and could not be placed. The condition message carries the refusal's own
	// words, and it is a positive readiness gate
	// (pkg/controllers/agentstatus.notReadyGates), so a person watching the
	// session is told why instead of watching it sit.
	ReasonRunnerPodRefused = "RunnerPodRefused"
	// ReasonRunnerStartError is set on RunnerReady=False when the runner
	// workload could not be created for any reason OTHER than an admission
	// refusal (a transient apiserver error, a client-side pod-build failure).
	// It deliberately does not carry ReasonRunnerPodRefused's "not allowed the
	// resources it needs" phrasing (pkg/controllers/agentstatus/friendly.go) —
	// that wording is reserved for a genuine refusal, and applying it to an
	// unrelated error would misattribute the cause to a quota or policy that
	// was never involved.
	ReasonRunnerStartError    = "RunnerStartError"
	ReasonAgentSessionStalled = "Stalled"
	ReasonAgentSessionBudget  = "BudgetExceeded"
	// ReasonAgentSessionExpired is set on the Failed condition when a session
	// exceeds its wall-clock budget.sessionExpiration (measured from
	// status.startedAt). Distinct from ReasonAgentSessionBudget (turns/tokens/
	// active run-time). Its string value "SessionExpired" is mirrored by the
	// lifecycle Expired event's FailureReason.
	ReasonAgentSessionExpired     = "SessionExpired"
	ReasonAgentSessionRunnerCrash = "RunnerCrashed"
	ReasonAgentSessionBundleFail  = "BundleFailed" // a tool bundle's sandbox failed terminally
	ReasonAgentSessionMemoryDown  = "MemoryUnavailable"
	ReasonAgentSessionProviderErr = "ProviderError"
	// ReasonAgentSessionRefusal: the provider returned stop_reason=refusal
	// (content-policy block). Channel-attached sessions park in AwaitingRetry
	// under this reason (recoverable, Retry button); local sessions fail.
	ReasonAgentSessionRefusal = "Refusal"
	// ReasonAgentSessionScopeReviewFailed is set on the Failed condition when
	// the runner's cold-start scope review could not complete (publish error,
	// timeout, or authzd error). The session fails closed: the agent is never
	// run unscoped when scope review is unavailable.
	ReasonAgentSessionScopeReviewFailed = "ScopeReviewFailed"
	// ReasonAgentSessionColdStartDenied is set on the Failed condition when
	// cold-start scope review COMPLETED and left the session no turn-0 to run:
	// the approver denied the prompt, or the cleaned remainder was empty.
	// Distinct from ReasonAgentSessionScopeReviewFailed, which means review
	// could not complete at all (publish error, timeout, authzd error).
	//
	// Only a DELEGATED child terminalizes on this arm. A session with a person
	// on the other end parks Idle instead (the conversation stays alive for a
	// follow-up) and a kubectl-driven one completes with an empty result;
	// neither ever carries this reason. A delegated child has no person and no
	// exit code, so its refusal has to reach the waiting parent, and this
	// string is what the parent's delegate / reply_to_subagent call reads back
	// out of the SubagentRequest.
	ReasonAgentSessionColdStartDenied = "ColdStartDenied"
	// ReasonAgentSessionToolGuardHalt is set on the Failed condition when a
	// tool-guard rule with action=halt fires (circuit breaker or rate limit).
	ReasonAgentSessionToolGuardHalt = "ToolGuardHalt"
	// ReasonAgentSessionFederatedIdPSecretMissing is set when a synthesized
	// type=federated credential references an IdP-identity Secret that does not
	// exist � fail-closed and loud, never a silent runner hang.
	ReasonAgentSessionFederatedIdPSecretMissing = "FederatedIdPSecretMissing"
	// ReasonAgentSessionContentGuardHalt is set on the Failed condition when a
	// content-inspector plugin is misconfigured at session start (unregistered ID
	// or invalid plugin config). The session fails closed rather than running with
	// a broken content-guard policy.
	ReasonAgentSessionContentGuardHalt = "ContentGuardHalt"
	// ReasonAgentSessionAwaitingDetector is set while one or more content-guard
	// detector pods have been created but have not yet reported a Ready PodIP.
	// Runner-pod creation is held until every detector IP is reflected; this
	// reason makes the wait visible rather than leaving the session in a blank
	// phase (no-silent-hang policy).
	ReasonAgentSessionAwaitingDetector = "AwaitingDetector"
	// ReasonAgentSessionComplete is set on the Succeeded condition when the
	// agent_work_complete tool fires for a kubectl-driven session (no
	// spec.channel). The string deliberately does not match the tool name:
	// monitoring and alerting filter on it, so it is frozen for compatibility.
	ReasonAgentSessionComplete = "AgentComplete"
	// ReasonAgentSessionAwaitingToolApproval is the Reason set on the
	// Working / AwaitingDecision phase transition.
	ReasonAgentSessionAwaitingToolApproval   = "AwaitingToolApproval"
	ReasonAgentSessionAwaitingEntityApproval = "AwaitingEntityApproval"
	// ReasonAgentSessionSkillBundleDigestMismatch is the reason on
	// SkillBundlesIntegrity=False. The digest is the bundle's content identity,
	// so a mismatch is a tamper signal: the skill falls back to
	// instruction-only and the mismatched bytes are never mounted.
	ReasonAgentSessionSkillBundleDigestMismatch = "SkillBundleDigestMismatch"
)

AgentSession reasons.

View Source
const (
	// ReasonSandboxUnschedulable is the Reason on SandboxScheduling=False.
	ReasonSandboxUnschedulable = "Unschedulable"
	// ReasonSandboxScheduled is the Reason on SandboxScheduling=True.
	ReasonSandboxScheduled = "Scheduled"
)

AgentSessionConditionSandboxScheduling reasons.

View Source
const (
	ChannelConditionValid     = "Valid"
	ChannelConditionConnected = "Connected"
	// ChannelConditionDeliverable records the outcome of the most recent
	// outbound delivery attempt through this Channel. Owned by channelsd's
	// outbound relay: False (DeliveryFailed) when a Sender's Send errored —
	// the bot evicted from or never invited to its Slack channel, a deleted
	// destination, a revoked token — and True (DeliverySucceeded) as soon as
	// a send lands again. Distinct from Connected, which reports
	// listener/socket health: a Slack channel whose bot token connects fine
	// still cannot deliver into a channel the bot is not a member of, and
	// that fact is observable only at send time. Absent until the first
	// outbound send is attempted; monitored by pkg/controllers/monitoring so
	// a channel that silently swallows agent replies alerts instead of
	// looking healthy.
	ChannelConditionDeliverable = "Deliverable"
	// ChannelConditionScopesValid is True when the runtime credential carries
	// every scope the listener needs. Owned by channelsd's listener, not the
	// operator. On False the message names the exact missing scopes, so an
	// operator can reinstall the app with the right grants without grepping
	// channelsd logs after the first failed message.
	ChannelConditionScopesValid = "ScopesValid"
	// ChannelConditionInformationLeakageReady mirrors the AgentClass-side
	// InformationLeakageReady check from the Channel's perspective. True
	// when this Channel's kind satisfies the capability level required by
	// the bound AgentClass's informationLeakage policy. Set by the channel
	// controller; absent on monitoring-role channels (no AgentClass).
	ChannelConditionInformationLeakageReady = "InformationLeakageReady"

	// ChannelConditionWebhookURLDrift is True when a Channel's inbound
	// webhook, as currently registered with its third-party provider (a
	// GitHub App's hook_attributes.url), no longer matches where this
	// cluster actually serves deliveries
	// (channelevents.WebhookPathFor under the cluster's external base URL).
	// Status=Unknown means the provider could not be reached/read — that is
	// NOT the same fact as "no drift" and is reported distinctly, never
	// folded into False. Set by the channel controller for whichever kind
	// implements channelkinds.WebhookURLDriftChecker (github today);
	// absent for every other kind, and absent when the check has nothing to
	// run against yet (no ExternalBaseURL configured, no credentials
	// Secret).
	//
	// Whether the controller CORRECTS a mismatch, rather than only reporting
	// it, turns on one fact: whether this tool registered the application in
	// the first place, recorded by the provenance marker a wizard stamps on
	// the Channel (channelkinds.AnnotationAppProvisionedBy).
	//
	//   - MARKED — the application is this tool's own. The controller
	//     repoints it to where the cluster now serves and reports the result
	//     here: False/URLRepointed when the write lands, True/RepointFailed,
	//     carrying the provider's error, when it does not.
	//   - UNMARKED — a human registered it. Silently changing an
	//     outward-facing setting on a resource the cluster does not own needs
	//     a human, not an automatic fix, so the mismatch is reported
	//     (True/URLDrifted) and never written back. A human corrects it on
	//     the provider's own settings page; the next reconcile clears the
	//     condition once the registered URL matches again.
	ChannelConditionWebhookURLDrift = "WebhookURLDrift"
)

Channel condition types.

View Source
const (
	ChannelConditionMetaagentChannelMembership = "MetaagentChannelMembership"

	ChannelReasonMetaagentInvited            = "Invited"
	ChannelReasonMetaagentInsufficientScopes = "InsufficientScopes"
	ChannelReasonMetaagentDMNotSupported     = "DMNotSupported"
	ChannelReasonMetaagentAppNotInstalled    = "MetaagentAppNotInstalled"
)

MetaagentChannelMembership condition tracks whether the metaagent bot has been auto-invited to the channel by the AgentSession controller. Set only when the bound AgentClass has scope.enabled=true.

View Source
const (
	ReasonChannelAllReferencesResolve    = "AllReferencesResolve"
	ReasonChannelSpecInvalid             = "SpecInvalid"
	ReasonChannelSecretMissing           = "SecretMissing"
	ReasonChannelSecretKeyMissing        = "SecretKeyMissing"
	ReasonChannelAgentClassMissing       = "AgentClassMissing"
	ReasonChannelAgentClassNotValid      = "AgentClassNotValid"
	ReasonChannelAgentIdentityMissing    = "AgentIdentityMissing"
	ReasonChannelSocketAttached          = "SocketAttached"          // set by channelsd on Connected=True
	ReasonChannelSocketDetached          = "SocketDetached"          // set by channelsd on Connected=False
	ReasonChannelDependenciesUnavailable = "DependenciesUnavailable" // memory or SpiceDB unreachable
	ReasonChannelListenerStartFailed     = "ListenerStartFailed"     // set by channelsd when listener.Start fails
	ReasonChannelScopesGranted           = "ScopesGranted"           // set by channelsd on ScopesValid=True
	ReasonChannelMissingScopes           = "MissingScopes"           // set by channelsd on ScopesValid=False; message lists the missing scopes
	ReasonChannelDeliverySucceeded       = "DeliverySucceeded"       // set by channelsd's relay on Deliverable=True
	ReasonChannelDeliveryFailed          = "DeliveryFailed"          // set by channelsd's relay on Deliverable=False; message carries the send error

	// ReasonChannelOutputBindingUnresolvable is set on a role=input Channel
	// whose reply target cannot be resolved: its AgentClass has zero or more
	// than one role=output Channel, or the resolved Channel's kind provides no
	// outbound anchor. Reported at apply time so the misconfiguration does not
	// first surface when the cron fires.
	ReasonChannelOutputBindingUnresolvable = "OutputBindingUnresolvable"

	// ReasonChannelOutputDestinationMissing is set on a role=output Channel
	// bound to an AgentClass whose status.userlessInput is true: the class's
	// input carries no human, so no inbound message supplies a destination and
	// this Channel has to configure its own. It is refused at apply time
	// because the alternative is a Channel that reports Valid=True with nowhere
	// to post: the failure would then wait to surface until the agent finished
	// its work and the reply went nowhere.
	//
	// Distinct from ReasonChannelOutputBindingUnresolvable, which is reported
	// on the role=input Channel about ITS target; this one is reported on the
	// output Channel itself, which is the object a human has to edit.
	ReasonChannelOutputDestinationMissing = "OutputDestinationMissing"

	// WebhookURLDrift reasons. ReasonChannelWebhookURLDrifted is Status=True
	// (a mismatch was found); ReasonChannelWebhookURLMatches is Status=False
	// (registered and expected agree); ReasonChannelWebhookProviderUnreachable
	// is Status=Unknown (the provider could not be reached/read — distinct
	// from both, never conflated with "matches").
	ReasonChannelWebhookURLDrifted          = "URLDrifted"
	ReasonChannelWebhookURLMatches          = "URLMatches"
	ReasonChannelWebhookProviderUnreachable = "ProviderUnreachable"

	// Repoint reasons, set only on a Channel whose application this tool
	// registered. ReasonChannelWebhookURLRepointed is Status=False — the
	// registration now matches because this controller just wrote it, which
	// is a different fact from URLMatches ("it already agreed") and is worth
	// telling apart when reading a Channel's history.
	// ReasonChannelWebhookRepointFailed is Status=True: the write was refused,
	// so deliveries still go somewhere wrong, and the condition's message
	// carries the provider's error.
	ReasonChannelWebhookURLRepointed  = "URLRepointed"
	ReasonChannelWebhookRepointFailed = "RepointFailed"
)

Channel reasons.

View Source
const (
	AgentSessionPhaseIdle     = "Idle"
	AgentSessionConditionIdle = "Idle"
	// AgentSessionConditionAwaitingRetry is True while the runner has
	// exited for a provider error and channelsd is waiting on the user
	// to click Retry. Set by the runner's WriteAwaitingRetry; cleared
	// by the controller on wake (AwaitingRetry � Pending).
	AgentSessionConditionAwaitingRetry = "AwaitingRetry"
	ReasonAgentSessionAwaitingUserMsg  = "AwaitingUserMessage"
	ReasonAgentSessionWakeRequested    = "WakeRequested"
	ReasonAgentSessionAuthzWriteFail   = "AuthzWriteFailed" // SpiceDB write failure on new-session creation

	// AgentSessionConditionStarterAllowed records the start-gate verdict for a
	// class that declares spec.authz.session.allowedStarters. True (reason
	// StarterAllowed) is set ONCE and never re-asked, so an operator restart
	// cannot re-decide a running session; False with NotAnAllowedStarter is
	// the refusal (the session is Failed); False with StartAuthzUnavailable is
	// indeterminate (requeued, never a refusal).
	AgentSessionConditionStarterAllowed     = "StarterAllowed"
	ReasonAgentSessionStarterAllowed        = "StarterAllowed"
	ReasonAgentSessionNotAnAllowedStarter   = "NotAnAllowedStarter"
	ReasonAgentSessionStartAuthzUnavailable = "StartAuthzUnavailable"

	// ReasonAgentSessionRetryBudgetExhausted is set on the AwaitingRetry
	// condition (cleared) and the Failed condition when the runner has hit
	// the maxRetry cap � no more automatic AwaitingRetry cycles are allowed.
	ReasonAgentSessionRetryBudgetExhausted = "RetryBudgetExhausted"
	// ReasonAgentSessionRetryTimeout is set on the Failed condition when the
	// operator's AwaitingRetry TTL elapses without the user clicking Retry.
	ReasonAgentSessionRetryTimeout = "RetryTimeout"
)

AgentSession new phase + condition + reasons added by channels.

View Source
const (
	// AgentSessionConditionInteractPolicyApplied is stamped after the
	// AgentClass.spec.sessionInteractPermission snapshot step at session
	// creation. True when the snapshot was captured (or the field was
	// empty); False when the snapshot was captured but the SpiceDB write
	// failed.
	AgentSessionConditionInteractPolicyApplied = "InteractPolicyApplied"
	// AgentSessionConditionPermissionRequestPending is True when
	// len(Status.PendingRequesters) > 0.
	AgentSessionConditionPermissionRequestPending = "PermissionRequestPending"
	// AgentSessionConditionStartApprovalPending is True while a session an
	// org non-member started sits parked awaiting a platform admin's
	// start_approval decision. Written by channelsd alongside the
	// PendingRequesters entry it refers to; the operator reads the sibling
	// annotation (AnnotationStartApprovalRequestRef), not this condition, to
	// withhold the runner.
	AgentSessionConditionStartApprovalPending = "StartApprovalPending"
	// The three *ApprovalPending conditions are channelsd-owned observable
	// surfaces for an outstanding decision of each category. All are written by
	// the generic park handler (registry-driven via
	// channelinteractions.Category.PendingCondition) alongside the
	// PendingInteractions list; none of them derives phase �
	// phase=AwaitingDecision comes from the runner's signed lifecycle
	// projection.
	AgentSessionConditionToolApprovalPending              = "ToolApprovalPending"
	AgentSessionConditionInfoLeakageApprovalPending       = "InfoLeakageApprovalPending"
	AgentSessionConditionContentInspectionApprovalPending = "ContentInspectionApprovalPending"
	// AgentSessionConditionPreconditionWaiverPending is True while a
	// precondition_waiver decision is outstanding: a human is being asked to
	// waive a slot precondition whose CEL predicate over signed facts Refused.
	// Same generic-park-handler machinery and same observable-only status as the
	// three above — approving it binds a slot grant, which IS the waiver, but the
	// phase stays derived from the runner's signed lifecycle projection.
	AgentSessionConditionPreconditionWaiverPending = "PreconditionWaiverPending"
	// AgentSessionConditionPreferenceConfirmPending is True while a user_preference_confirm
	// decision is outstanding: the addressee is being asked to confirm a preference update.
	AgentSessionConditionPreferenceConfirmPending = "PreferenceConfirmPending"
	// AgentSessionConditionScopeReviewPending is True while a cold-start scope_review
	// decision is outstanding and turn-0 execution is blocked. Written by the operator
	// from the folded lifecycle projection; cleared when the decision resolves.
	AgentSessionConditionScopeReviewPending = "ScopeReviewPending"
	// AgentSessionConditionToolCallGated is True when one or more tool calls were
	// denied by a hook (toolguard, revocation, content-guard) but not surfaced to a
	// human approver. The operator writes this from the folded lifecycle projection so
	// the count is readable from CR status without grepping logs (no-silent-errors).
	AgentSessionConditionToolCallGated = "ToolCallGated"

	// ReasonInteractPolicyApplied marks that the AgentSession received
	// the AgentClass snapshot at creation.
	ReasonInteractPolicyApplied = "AppliedFromAgentClass"
	// ReasonInteractPolicyNotConfigured marks that AgentClass had no
	// sessionInteractPermission set.
	ReasonInteractPolicyNotConfigured = "NotConfigured"
	// ReasonInteractPolicySpiceDBWriteFailed marks that the snapshot was
	// captured but the SpiceDB participant write failed.
	ReasonInteractPolicySpiceDBWriteFailed = "SpiceDBWriteFailed"

	// Restart-from-here condition types and reasons. Set by the
	// AgentSession controller's restart reconciler and the toolcall
	// controller's JIT-snapshot path, respectively.
	// AgentSessionConditionSupersededByRestart indicates the session was
	// archived because the user triggered Restart-from-here, producing a
	// child AgentSession (status.supersededBy). The child carries the
	// continuation; this session is read-only.
	AgentSessionConditionSupersededByRestart = "SupersededByRestart"

	// AgentSessionConditionWorkspaceSnapshotFailed indicates a JIT
	// workspace snapshot Job failed. While True, the runner refuses to
	// dispatch further stateImpact={readwrite,external} tool calls � the
	// next would lose the recoverable cut point for fork-at-this-turn.
	AgentSessionConditionWorkspaceSnapshotFailed = "WorkspaceSnapshotFailed"

	// ReasonReplacedByForkedSession is the reason for
	// SupersededByRestart=True. The condition Message names the child.
	ReasonReplacedByForkedSession = "ReplacedByForkedSession"

	// ReasonSnapshotJobFailed is the reason for
	// WorkspaceSnapshotFailed=True. The condition Message names the
	// failed Job and its terminating phase.
	ReasonSnapshotJobFailed = "SnapshotJobFailed"

	// AgentSessionConditionRestartDenied indicates a restart-from-here (fork)
	// was denied by the SessionFork authz gate � the forker is not the session
	// owner. The PendingRestart marker is cleared; no child is created.
	//
	// It reports the CURRENT attempt's verdict, not "a denial happened once":
	// the operator sets it False (ReasonRestartAttemptPending) whenever it
	// picks up a new PendingRestart, so a session whose later continuation
	// succeeds stops reading as denied, and a repeat denial is a genuine
	// False�True transition � which is what lets anything keying off
	// LastTransitionTime see the user's retry at all.
	AgentSessionConditionRestartDenied = "RestartDenied"

	// ReasonForkNotAuthorized is the reason for AgentSessionConditionRestartDenied.
	ReasonForkNotAuthorized = "ForkNotAuthorized"

	// ReasonRestartAttemptPending is the reason for
	// AgentSessionConditionRestartDenied=False: the operator has picked up a new
	// PendingRestart and cleared the previous attempt's verdict before running
	// the fork gate. It is what makes the condition report current state � and
	// what makes a repeat denial a real False�True transition, so consumers that
	// key off LastTransitionTime see the retry.
	ReasonRestartAttemptPending = "RestartAttemptPending"

	// ReasonRestartMarkerUnverified is the reason for RestartDenied=True when
	// status.pendingRestart could not be authenticated as connector-authored:
	// an unregistered signing key, or a digest mismatch. A marker carrying no
	// attestation envelope at all gets RestartMarkerUnsigned instead � that is
	// what a pre-upgrade connector writes, so it must not read as a forgery.
	// Distinct from ForkNotAuthorized, where a KNOWN requester failed the
	// SpiceDB fork check; here the requester on the marker cannot be believed
	// at all.
	ReasonRestartMarkerUnverified = "RestartMarkerUnverified"
)

Multiplayer-sessions condition types and reasons.

View Source
const (
	AgentSessionConditionStorageReclaimed = "StorageReclaimed"
	ReasonAgentSessionStorageReclaimed    = "RetentionElapsed"
)

AgentSessionConditionStorageReclaimed is set True by the operator's storage-reclaim sweeps once a session's workspace and snapshot-store PVCs have been deleted. Two sweeps set it: the terminal sweep, once a TERMINAL session's retention has elapsed past FinishedAt; and the idle sweep, once a SLEPT, node-pinned Idle session has been asleep past --idle-storage-reclaim-after (that session stays Idle and wakeable). Either way the AgentSession itself — transcript, memory records, SpiceDB relationships — is untouched; only the re-creatable scratch volumes go.

The condition records that a sweep ran; it does NOT gate future sweeps. A session that wakes after being swept re-provisions both claims (and, if terminal, finishes again), and that next lifetime's storage must still be reclaimable — a write-once reading stranded it on the node's local disk forever. What keeps re-provisioned storage safe from a stale decision is the deadline: the terminal sweep measures from the CURRENT FinishedAt, the idle sweep from the CURRENT SleptAt, so fresh storage always gets a fresh window.

View Source
const (
	ReasonAgentSessionAgentWorkComplete = "AgentWorkComplete" // agent_work_complete tool was called
	ReasonAgentSessionArchived          = "Archived"          // operator archive sweep transitioned Idle � Succeeded
)

Reasons for the AgentSession.Idle condition's per-cause subdivision.

View Source
const (
	AnnotationWakeRequestedAt = "agentprimitives.authzed.com/wake-requested-at"

	// AnnotationUIServeRequestedAt asks the operator to spawn a SERVE-ONLY
	// runner: one that answers a browser's agent-UI requests and runs no agent
	// loop. Written by webd when a data binding finds no runner subscribed.
	//
	// A SECOND key rather than a second meaning for the one above, and the
	// distinction is the point: AnnotationWakeRequestedAt resumes the
	// CONVERSATION, so the runner it spawns replays memory, claims the live
	// region and takes an LLM turn. Reusing it made a dashboard load put
	// content in the agent's context that no human asked for, and on a live
	// cluster left the session Failed. A pod spawned via THIS key must not move
	// phase or write a terminal status; internal/cmd/runner enforces that structurally
	// by never calling Loop.Run, the sole writer of both.
	//
	// Both keys carry an RFC3339Nano timestamp the reconciler compares against
	// what it has already acted on, so re-stamping while a spawn is in flight
	// coalesces rather than duplicating.
	AnnotationUIServeRequestedAt = "agentprimitives.authzed.com/ui-serve-requested-at"

	// AnnotationSidecarFailureReported carries, as a JSON object, the
	// sidecar-name � failure-fingerprint map the runner has already reported to
	// the user � the durable half of its once-per-failure contract, since a
	// process-local record alone re-reports the same failure on every restart.
	// Written by the runner, which already patches session annotations, and
	// deliberately not status: status here is the operator's observation of the
	// pods.
	//
	// It lives beside AnnotationWakeRequestedAt because those two are the whole
	// of what a session runner may write on the main resource �
	// pkg/controllers/webhooks/agentsession's runnerWritableAnnotations refuses
	// every other key. internal/cmd/runner still defines its own copy
	// (sidecarFailureReportedAnnotation); it should be collapsed onto this one.
	AnnotationSidecarFailureReported = "agentprimitives.authzed.com/sidecar-failure-reported"

	// AnnotationAwaitingRetrySince records when the operator first observed
	// the session in phase=AwaitingRetry. Used by the retry-TTL mechanism:
	// the operator stamps this once on entry and requeueAfter the remainder;
	// when the TTL elapses the operator emits RetryTTLExpired � Failed[RetryTimeout].
	// Cleared when the session wakes (RetryRequested � Pending).
	AnnotationAwaitingRetrySince = "agentprimitives.authzed.com/awaiting-retry-since"

	// AnnotationStartedByExternalID records the kind-specific external identity
	// (a Slack user_id, say) of the inbound that created the AgentSession.
	// Stamped and consumed by the channelsd pipeline as a fast-path "same
	// external user" check, bypassing the canonical_id round-trip � which is
	// fragile when upstream identity resolution flickers across inbounds and
	// yields different canonical_ids for the same human.
	AnnotationStartedByExternalID = "agentprimitives.authzed.com/started-by-external-id"

	// AnnotationStartedByEmail records the VERIFIED email of the human who
	// originally created the AgentSession, when the originating channel kind
	// vouches for one (webchat/idp always; Slack when a users.info lookup
	// resolved it) � the same email fed into identity.FromExternal(...).Email
	// when the session's started-by canonical was computed (see
	// AnnotationStartedByCanonicalID). Stamped alongside
	// AnnotationStartedByExternalID at session-creation time.
	//
	// Consumed by IdentityChoiceGate.requester() so the ask|dynamic prompt's
	// Audience.Requester carries the SAME verified email the deciding click's
	// payload does; both sides then canonicalize to the identical SpiceDB
	// subject. Without it, Canonical() rejects the email-less requester with
	// ErrSyntheticSubject and DecideRequester fails every decision closed.
	//
	// Empty when the channel kind has no verified email for the starter
	// (kubectl-driven sessions, or a channel-native id with no email lookup).
	// The gate's requester then has no Email either and fails closed like any
	// guest identity � intended: such an initiator cannot decide their own
	// identity_choice prompt, just as they could never pass through as
	// themselves.
	AnnotationStartedByEmail = "agentprimitives.authzed.com/started-by-email"

	// AnnotationBackfilledFromTS records the kind-opaque cursor of the
	// OLDEST message seeded into memory at thread adoption. The
	// read_thread_history tool defaults its BeforeTS to this value so
	// the agent pages strictly older than what is already in memory.
	AnnotationBackfilledFromTS = "agentprimitives.authzed.com/backfilled-from-ts"

	// AnnotationBackfilledThroughTS records the kind-opaque cursor of the
	// NEWEST message seeded into memory. The pipeline advances it on
	// every catch-up so the next catch-up only seeds messages newer
	// than this.
	AnnotationBackfilledThroughTS = "agentprimitives.authzed.com/backfilled-through-ts"
)

Annotation key for channelsd � operator wake-up signaling.

View Source
const (
	LabelChannelName = "channel.agentprimitives.authzed.com/name"
	LabelChannelKind = "channel.agentprimitives.authzed.com/kind"
	LabelChannelKey  = "channel.agentprimitives.authzed.com/key" // sha256-hex of channelKey

	// LabelOutputChannelKey is the sha256 of the session's
	// spec.outputChannel.Key, patched by channelsd's outbound relay once a
	// cron-spawned session's thread anchor is captured on the first send. The
	// pipeline's session lookup matches it as a union alongside
	// LabelChannelKey, so an inbound reply to a cron-spawned thread resolves
	// the right AgentSession.
	LabelOutputChannelKey = "channel.agentprimitives.authzed.com/output-key"
)

Label keys for channel-spawned AgentSessions.

View Source
const (
	ReasonPinDrifted      = "PinDrifted"      // live identity != baseline
	ReasonPinMatch        = "PinMatch"        // live identity == baseline
	ReasonPinVerifyFailed = "PinVerifyFailed" // could not compute live identity
)
View Source
const (
	ReasonMCPServerAllowlistResolved      = "AllowlistResolved"
	ReasonMCPServerProbeOK                = "ProbeOK"
	ReasonMCPServerProbeFailed            = "ProbeFailed"
	ReasonMCPServerAllowlistDrift         = "AllowlistDrift"
	ReasonMCPServerConstraintCompileError = "ConstraintCompileError"
	ReasonMCPServerAuthResolutionFailed   = "AuthResolutionFailed"
	// ReasonMCPServerSpicedbSchemaMissing fires when one or more
	// `tools[*].permission.check.resourceType` or
	// `tools[*].permissionVariants[*].check.resourceType` references a
	// resource that is not declared in this MCPServer's
	// `spec.spiceDBSchema.resources[*].name` (the implicit `user` type
	// is always allowed). The condition message lists every offending
	// reference so operators can fix the spec without source-diving.
	ReasonMCPServerSpicedbSchemaMissing = "SpicedbSchemaMissing"

	// ReasonMCPServerWildcardLeafNotRouted is the converse of
	// ReasonMCPServerRouteViaSessionGrantMissingLeaf, and the fail-OPEN
	// half of the same invariant: a tool's Permission.Check targets a
	// permission that a `wildcard: true` relation can satisfy, but the
	// check does NOT set routeViaSessionGrant. The direct
	// `<resourceType>:<id>#<permission>@user:<subject>` check then
	// succeeds for every user, so the gate never denies and the approval
	// flow it exists to trigger never fires � silently. The condition
	// message names each offending (tool spec path, resourceType,
	// permission, wildcard relation) and both ways to resolve it.
	ReasonMCPServerWildcardLeafNotRouted = "WildcardLeafNotRouted"

	// ReasonMCPServerAuthCredentialMissing fires when spec.auth.provider is
	// set but spec.auth.credential is empty. Credential names must be
	// declared explicitly � there is no metadata.name fallback � so both
	// setup-identity and the runner descriptor agree on what credential to
	// look up. The condition message names the missing field.
	ReasonMCPServerAuthCredentialMissing = "AuthCredentialMissing"

	// ReasonMCPServerLabelsCompileError fires when a tool's `labels` block has
	// a malformed CEL expression (when / forEach / label.*). The condition
	// message lists each offending (tool, block-index, field, err) tuple so
	// authors can fix the spec without source-diving.
	ReasonMCPServerLabelsCompileError = "LabelsCompileError"

	// ReasonMCPServerRelationshipsCompileError fires when a tool's
	// `writesRelationships` block has a malformed CEL expression (when /
	// forEach / tuple.resource / tuple.relation / tuple.subject). The message
	// names the offending (tool, block-index, field, err) so authors can fix
	// the spec without source-diving.
	//
	// Validation compiles through the SAME relwrites env that executes these
	// expressions, so "validated" and "will run" cannot drift. Compiling them
	// first at dispatch time instead would let a server carrying an
	// uncompilable expression report Valid=True and fail mid-turn, as an error
	// the agent cannot act on.
	ReasonMCPServerRelationshipsCompileError = "RelationshipsCompileError"
)

MCPServer reconciler condition reasons.

View Source
const (
	ReasonAgentSessionMCPServerMissing          = "MCPServerMissing"
	ReasonAgentSessionMCPServerInvalid          = "MCPServerInvalid"
	ReasonAgentSessionMCPAuthResolutionFailed   = "MCPAuthResolutionFailed"
	ReasonAgentSessionMCPServerUnreachable      = "MCPServerUnreachable"
	ReasonAgentSessionMCPAllowlistDrift         = "MCPAllowlistDrift"
	ReasonAgentSessionMCPConstraintCompileError = "MCPConstraintCompileError"
)

AgentSession session-fatal reasons specific to MCP tool dispatch.

View Source
const (
	ReasonAgentClassMCPServerMissing = "AgentClassMCPServerMissing"
	ReasonAgentClassMCPServerInvalid = "AgentClassMCPServerInvalid"
	// ReasonAgentClassSkillMissing fires when the AgentClass opts into a
	// skill (spec.skills, a canonical name) for which no Skill or
	// ClusterSkill has materialized yet � so the class is parked
	// (Valid=False) rather than silently dropping the skill at session
	// start. ReasonAgentClassSkillInvalid fires when such a Skill exists
	// but is not itself Valid=True (e.g. its SKILL.md was rejected).
	ReasonAgentClassSkillMissing = "AgentClassSkillMissing"
	ReasonAgentClassSkillInvalid = "AgentClassSkillInvalid"
	// ReasonAgentClassSpicedbSchemaConflict fires when two or more
	// MCPServers referenced by the AgentClass declare the same SpiceDB
	// resource (by name) with non-identical definitions � the
	// guardian/schema composer cannot merge them into a single coherent
	// SpiceDB schema. The condition message names the conflicting
	// resource(s). The cross-MCPServer mirror of
	// ReasonMCPServerSpicedbSchemaMissing's intra-fragment check.
	ReasonAgentClassSpicedbSchemaConflict = "SpicedbSchemaConflict"

	// ReasonAgentClassSlotAutofillUnfillable marks a slot whose autoFillArgs
	// target a sandbox tool. Auto-fill writes a NAMED argument; a sandbox tool
	// takes argv, so the value would be ignored by both the tool and its
	// permission check � silently. Declaring autoFillArgs is an opt-in, and an
	// opt-in that cannot be honoured makes the class not-ready rather than
	// quietly binding nothing.
	ReasonAgentClassSlotAutofillUnfillable = "SlotAutofillUnfillable"
)

AgentClass condition reasons specific to MCP server refs.

View Source
const (
	ReasonArtifactRenderRendered        = "Rendered"
	ReasonArtifactRenderRendererUnknown = "RendererUnknown"
	ReasonArtifactRenderPayloadTooLarge = "PayloadTooLarge"
	ReasonArtifactRenderRendererError   = "RendererError"
	ReasonArtifactRenderOutputTooLarge  = "OutputTooLarge"
	ReasonArtifactRenderTimeout         = "Timeout"
	ReasonArtifactRenderInternalError   = "InternalError"
	// ReasonArtifactRenderMalformedInput: the payload was structurally broken
	// before rendering (e.g. truncated mid-tag, or fully HTML-entity-encoded so
	// it would render as literal source). Distinct from RendererError so a bad
	// input is greppable separately from an internal renderer fault.
	ReasonArtifactRenderMalformedInput = "MalformedInput"
)

ArtifactRender condition reasons.

View Source
const (
	IdentityModeAgent           = "agent"
	IdentityModeUserPassthrough = "userPassthrough"
	IdentityModeAsk             = "ask"     // interactive: ask the initiating user
	IdentityModeDynamic         = "dynamic" // interactive: LLM recommends, user confirms
)

AgentClass identity modes (AgentClassSpec.IdentityMode).

View Source
const (
	ReasonAgentSessionStartDenied          = "StartDenied"
	ReasonAgentSessionStartApprovalTimeout = "StartApprovalTimeout"
)

Reasons on the Failed condition for a start_approval that ended without an approve: StartDenied when a platform admin clicked Deny, and StartApprovalTimeout when the request expired with no decision at all.

View Source
const (
	AgentSessionConditionCredentialsReady = "CredentialsReady"

	ReasonAwaitingUserCredentials = "AwaitingUserCredentials"
	ReasonCredentialLinkTimeout   = "CredentialLinkTimeout"
	ReasonUserIdentityResolved    = "UserIdentityResolved"
	ReasonNotPassthrough          = "NotPassthroughClass"
	ReasonMissingStarterSubject   = "MissingStarterSubject"

	ReasonIdentityChoiceTimeout   = "IdentityChoiceTimeout"
	ReasonIdentityChoiceCancelled = "IdentityChoiceCancelled"
	// ReasonIdentityChoiceFailed is the terminal Failed reason for an
	// identityMode=ask|dynamic session whose choice gate failed CLOSED for a
	// reason other than cancel or timeout: the interactive choice path was
	// unavailable (no channel), the choice envelope failed to build, the await
	// transport errored, or the channel returned an unknown action. Distinct from
	// ScopeReviewFailed � an identity halt is never mislabelled as a scope-review
	// failure. Cancel/timeout use their own reasons above so the runner's terminal
	// write agrees with the gate's IdentityChoiceCancelled fold and the operator's
	// IdentityChoiceTimeout backstop.
	ReasonIdentityChoiceFailed = "IdentityChoiceFailed"
)

AgentSession passthrough-identity condition + reasons.

View Source
const (
	UserIdentityConditionValid   = "Valid"
	UserIdentityConditionRefresh = "Refresh"
)

UserIdentity condition types. The string values intentionally match AgentIdentity's ("Valid"/"Refresh"); the UserIdentity reconcilers reuse the AgentIdentity reason constants (ReasonAllReferencesResolve, ReasonSpecInvalid, ReasonNoOAuthCredentials, ...).

View Source
const (
	SessionUserIdentityConditionReady = "Ready"

	ReasonSessionUserIdentityResolved = "AllCredentialsBound"
	ReasonSessionUserIdentityMissing  = "CredentialsMissing"
)

SessionUserIdentity condition type + reasons.

View Source
const (
	WebdTrustedURLKey = "trusted-url"
	WebdSandboxURLKey = "sandbox-url"
)

WebdTrustedURLKey + WebdSandboxURLKey are the ConfigMap keys carrying webd's two externally-reachable origin URLs: the auth/trusted surface and the sandbox artifact-content surface. webd polls both.

View Source
const (
	ReasonAgentClassSidecarToolboxMissing = "AgentClassSidecarToolboxMissing"
	ReasonAgentClassSidecarToolboxInvalid = "AgentClassSidecarToolboxInvalid"
)

AgentClass condition reasons specific to SidecarToolbox refs.

View Source
const (
	ReasonSidecarToolboxSpecOK                 = "SpecOK"
	ReasonSidecarToolboxClassMissing           = "ClassMissing"
	ReasonSidecarToolboxClassInvalid           = "ClassInvalid"
	ReasonSidecarToolboxProviderMissing        = "ProviderMissing"
	ReasonSidecarToolboxConstraintCompileError = "ConstraintCompileError"
	ReasonSidecarToolboxSourceInvalid          = "SourceInvalid"
	ReasonSidecarToolboxProbeFailed            = "ProbeFailed"
	ReasonSidecarToolboxAllowlistDrift         = "AllowlistDrift"
	ReasonSidecarToolboxProbeOK                = "ProbeOK"
	// ReasonSidecarToolboxDeferredToSession is set on Reachable=Unknown for
	// secret-gated (separate-pod) sidecars, which the operator does NOT probe at
	// admission time: the real sidecar exists only per-session, once its gating
	// secret is delivered, and runs in a namespace the operator's egress is
	// deliberately locked out of. The runner probes the live pod instead and the
	// result lands on AgentSession.status.resolvedSidecarToolboxes[]. NOT a
	// failure � the admin UI renders it as "Deferred", not red/ProbeFailed.
	ReasonSidecarToolboxDeferredToSession = "DeferredToSession"
	// ReasonSidecarToolboxDeletionBlocked is set when deletion is blocked
	// because one or more AgentClasses still reference this SidecarToolbox.
	// Operators must remove the reference from each AgentClass before the
	// finalizer can be released.
	ReasonSidecarToolboxDeletionBlocked = "DeletionBlocked"
)

SidecarToolbox reasons (Valid / Reachable conditions).

View Source
const (
	ReasonAgentSessionSidecarToolboxMissing = "SidecarToolboxMissing"
	ReasonAgentSessionSidecarToolboxInvalid = "SidecarToolboxInvalid"
	ReasonAgentSessionSidecarBootFailed     = "SidecarBootFailed"
	// ReasonAgentSessionImagePinDrifted is set on AgentSessionConditionSettingsAccepted
	// (False) when a SidecarToolbox's image has drifted from its baseline digest
	// and the cluster/namespace pinning policy mode is "block". The session is
	// held in Pending until the operator updates or re-freezes the toolbox.
	ReasonAgentSessionImagePinDrifted = "ImagePinDrifted"
)

AgentSession sidecar-related fatal reasons.

View Source
const (
	// SkillConditionValid is True when the skill's canonical name + frontmatter
	// + body pass validation. The admission webhook hard-denies invalid specs;
	// this condition is the controller-set mirror for already-stored objects.
	SkillConditionValid = "Valid"
	// SkillConditionPinned is advisory: True for frozen/named refs, False (with
	// ReasonSkillUnpinnedRolling) for an unpinned, rolling skill.
	SkillConditionPinned = "Pinned"
)

Skill condition types.

View Source
const (
	ReasonSkillValid           = "Valid"
	ReasonSkillInvalidSpec     = "InvalidSpec"
	ReasonSkillPinFrozen       = "Frozen"
	ReasonSkillPinNamed        = "NamedRef"
	ReasonSkillUnpinnedRolling = "UnpinnedRolling"
)

Skill reasons.

View Source
const (
	ReasonSkillSourceSynced            = "Synced"
	ReasonSkillSourceAuthResolveFailed = "AuthResolutionFailed"
	ReasonSkillSourceFetchFailed       = "FetchFailed"
	ReasonSkillSourceDiscoverFailed    = "DiscoverFailed"
	ReasonSkillSourceMaterializeFailed = "MaterializeFailed"
	// ReasonSkillSourceNoSkillsDiscovered is set when the fetch itself
	// succeeded but the pass materialized no skill at all — the tree held no
	// SKILL.md under spec.subpath, or every one it held was rejected. Ready is
	// False because a source that yields nothing satisfies no AgentClass
	// opting into it, and Synced would render it identically to a source that
	// yielded everything.
	ReasonSkillSourceNoSkillsDiscovered = "NoSkillsDiscovered"
)

SkillSource reasons.

View Source
const (
	ReasonToolchainValid         = "Valid"
	ReasonToolchainInvalidSpec   = "InvalidSpec"
	ReasonToolchainUnknownKind   = "UnknownSourceKind"
	ReasonToolchainInvalidSource = "InvalidSource"
)

SpiceboxToolchain reasons.

View Source
const (
	ReasonToolchainMissing    = "ToolchainMissing"
	ReasonToolchainNotValid   = "ToolchainNotValid"
	ReasonToolchainResolveErr = "ToolchainResolveError"
)

SpiceboxSession toolchain reasons.

View Source
const (
	ReasonWorkspaceSourceSpecOK            = "SpecOK"
	ReasonWorkspaceSourceSpecInvalid       = "SpecInvalid"
	ReasonWorkspaceSourceUnknownKind       = "UnknownDriverKind"
	ReasonWorkspaceSourceBaseUnconfigured  = "BaseStorageUnconfigured"
	ReasonWorkspaceSourceMaterializing     = "Materializing"
	ReasonWorkspaceSourceMaterialized      = "Materialized"
	ReasonWorkspaceSourceMaterializeFailed = "MaterializeFailed"
	// ReasonWorkspaceSourceRefreshing marks the Ready condition while a
	// scheduled base-refresh Job (spec.base.refresh) is in progress. The base
	// stays Ready=True/Refreshing rather than flipping unusable � the prior
	// checkout is still valid while the refresh Job runs.
	ReasonWorkspaceSourceRefreshing = "Refreshing"
	// ReasonWorkspaceSourceSpecChanged fires when the materialize Job already
	// succeeded for an older generation of the spec. The base checkout is
	// materialized exactly once (by stable Job name) and a spec edit does not
	// re-materialize it, so the controller reports the divergence honestly
	// instead of latching Ready=True for a generation it never materialized.
	ReasonWorkspaceSourceSpecChanged = "SpecChanged"
)

WorkspaceSource controller reasons.

View Source
const (
	ReasonAgentClassWorkspaceSourceMissing     = "WorkspaceSourceMissing"
	ReasonAgentClassWorkspaceSourceInvalid     = "WorkspaceSourceInvalid"
	ReasonAgentSessionWorkspaceSourceNotReady  = "WorkspaceSourceNotReady"
	ReasonAgentSessionWorkspaceOverlayFailed   = "WorkspaceOverlayFailed"
	ReasonAgentSessionWorkspaceStorageRequired = "WorkspaceStorageRequired"
)

WorkspaceSource binding reasons (AgentClass validation + AgentSession overlay).

View Source
const (
	ReasonAgentClassAgentUIMissing = "AgentUIMissing"
	ReasonAgentClassAgentUIInvalid = "AgentUIInvalid"
)

AgentUI binding reasons (AgentClass validation of spec.agentUI.ref). Mirrors the WorkspaceSource pair above: an AgentClass that opts into an AgentUI it cannot resolve, or one that is not itself Valid, parks at Valid=False rather than reporting AllReferencesResolve and failing later as an empty browser-tool surface.

View Source
const (
	// ReasonAgentUISpecOK marks the page — spec.view, or spec.slots compiled
	// — as having parsed and validated cleanly against the platform
	// vocabulary.
	ReasonAgentUISpecOK = "SpecOK"
	// ReasonAgentUIInvalidDefault marks a page — spec.view, or spec.slots
	// compiled — that failed either the strict wire parse (an unknown
	// structural key) or vocabulary validation (an unknown/duplicate/missing
	// hook name, an unknown component, an out-of-bounds tree, or a binding to
	// a tool outside the eligible-tools ceiling). The condition message
	// carries the *uicomponents.ValidationError's real Path and Reason, so the
	// failure is diagnosable without source-diving. A default that fails its
	// own parse is attributed to the CR's real index ("slots[1]"); everything
	// caught after compilation is attributed to the compiled tree
	// ("view.children[1]…"), which is where the node actually sits on the page
	// the runner serves.
	ReasonAgentUIInvalidDefault = "InvalidDefault"
	// ReasonAgentUIGrantUnresolved marks Valid=Unknown when the AgentClasses
	// in this namespace could not be listed to compute the deployment half
	// of the eligible-tools ceiling. Deliberately Unknown, not False: this
	// status is not itself an authorization decision, so persisting a
	// denial derived from an input this reconcile failed to read would be a
	// false diagnosis, not a safe default. status.eligibleTools is left
	// untouched (the last successfully-observed ceiling) rather than reset.
	ReasonAgentUIGrantUnresolved = "GrantUnresolved"
	// ReasonAgentUIActionToolNotRequested is the Valid=False reason for an
	// action naming a tool the UI never requested. Distinct from
	// ReasonAgentUIInvalidDefault because the fix is a different field
	// (spec.tools) than the declaration the author is looking at.
	ReasonAgentUIActionToolNotRequested = "ActionToolNotRequested"
)

AgentUI reasons (Valid condition).

View Source
const (
	// ReasonAgentUIToolsFullyGranted marks ToolsGranted=True: every
	// (deduplicated) tool in spec.tools made the eligible-tools ceiling.
	ReasonAgentUIToolsFullyGranted = "AllGranted"
	// ReasonAgentUIToolsPartiallyGranted marks ToolsGranted=False: at least
	// one requested tool did not make the ceiling. The condition message
	// carries uigrant.ExplainCeiling's per-tool diagnosis.
	ReasonAgentUIToolsPartiallyGranted = "PartiallyGranted"
)

AgentUI reasons (ToolsGranted condition).

View Source
const (
	WorkshopConditionNamespaceReady = "NamespaceReady"
	WorkshopConditionRBACReady      = "RBACReady"
	WorkshopConditionTupleWritten   = "TupleWritten"
	WorkshopConditionTokensReady    = "TokensReady"

	ReasonWorkshopProvisioned     = "Provisioned"
	ReasonWorkshopProvisionFailed = "ProvisionFailed"
	ReasonWorkshopExpired         = "Expired"
	// ReasonWorkshopSessionFinished marks the workshop released because its
	// builder session reached a terminal phase (and any install it requested
	// was decided) — the standing reason for a teardown nobody asked for, the
	// way ReasonWorkshopExpired is for the max-age sweeper.
	ReasonWorkshopSessionFinished = "SessionFinished"
)

Workshop condition types + reasons. Each names one provisioning layer so a failed layer is visible on the CR rather than only in operator logs.

View Source
const (
	WorkshopProbePhasePending   = "Pending"
	WorkshopProbePhaseRunning   = "Running"
	WorkshopProbePhaseSucceeded = "Succeeded"
	WorkshopProbePhaseFailed    = "Failed"

	WorkshopProbeConditionProbed = "Probed"

	ReasonWorkshopProbeSucceeded = "ProbeSucceeded"
	ReasonWorkshopProbeFailed    = "ProbeFailed"
	ReasonWorkshopProbeDenied    = "ProbeDenied" // tuple absent
)

WorkshopProbe phases, condition type, and reasons.

View Source
const (
	ReasonBuilderClassInvalid = "BuilderClassInvalid"
	// ReasonReferencesWorkshopTool marks a NON-workshop class that references
	// a workshop-labeled cluster-scoped toolspec/toolkit.
	ReasonReferencesWorkshopTool = "ReferencesWorkshopTool"
)

Builder-class validation reasons (spec §1.0's deferred set, enforced by the AgentClass controller when the class is sanctioned).

View Source
const (
	// RelationshipSourceConditionReady is True when the last pass enumerated
	// (fully or partially) and synced without a fatal error. Mirrors
	// SkillSourceConditionReady's single-condition shape; see the CRD's own
	// "Ready" printcolumn.
	RelationshipSourceConditionReady = "Ready"
	// RelationshipSourceConditionPartialFailure is True when the last
	// COMPLETED pass finished with per-scope errors, and False when that pass
	// finished clean. It is the second half of a two-condition split that
	// exists because Ready alone could not express partial success.
	//
	// Ready keeps its meaning exactly: a pass that enumerated and synced
	// without a fatal error is Ready=True/Synced, and per-scope failures stay
	// non-fatal (relsync.Pass's own doc: "one scope failing is not a pass
	// failure"). That is correct, and it is also why a whole arm of a sync can
	// fail invisibly — a GitHub source whose every per-repository team fetch
	// answered 403 reported Ready=True, Synced, scopesProcessed: 156, with the
	// 403s reaching only the operator log. Nothing downstream could tell
	// partial success from success. This condition is that missing signal:
	// True here and Ready=True together mean "ran, and some of it failed".
	//
	// It is set on the SAME status write status.sync.lastPass's counts ride,
	// from the same PassResult, so the condition and the counts can never
	// disagree. A pass that never completed (a fatal error, a total
	// enumeration failure, a parked source) leaves it untouched: it describes
	// the last pass that actually finished, and inventing a verdict for a pass
	// that did not run would be a false diagnosis. Ready=False is the signal
	// for those.
	RelationshipSourceConditionPartialFailure = "PartialFailure"
)

RelationshipSource condition types.

View Source
const (
	// ReasonRelationshipSourceSynced marks Ready=True after a pass completed
	// (fully or partially resumed) with no fatal error. Per-scope failures
	// (PassResult.ScopeErrors) are logged, not fatal — see relsync.Pass's own
	// doc on "one scope failing is not a pass failure". Whether this pass had
	// any is reported by RelationshipSourceConditionPartialFailure, not here.
	ReasonRelationshipSourceSynced = "Synced"
	// ReasonRelationshipSourceScopeErrors marks PartialFailure=True: the last
	// completed pass reported at least one PassResult.ScopeError. The
	// condition message carries the failure count and one sampled error; the
	// bounded sample set is on status.sync.lastPass.scopeErrorSamples.
	ReasonRelationshipSourceScopeErrors = "ScopeErrors"
	// ReasonRelationshipSourceAllScopesSynced marks PartialFailure=False: the
	// last completed pass reported no scope errors at all.
	ReasonRelationshipSourceAllScopesSynced = "AllScopesSynced"
	// ReasonRelationshipSourceKindUnregistered marks Ready=False when
	// spec.kind names no relsync.Kind registered in this binary.
	ReasonRelationshipSourceKindUnregistered = "KindUnregistered"
	// ReasonRelationshipSourceKindClaimed marks Ready=False when another
	// RelationshipSource already claims spec.kind — see the package doc on
	// why two CRs of the same kind must never both sync (the reap-vs-reap
	// annihilation case).
	ReasonRelationshipSourceKindClaimed = "KindClaimed"
	// ReasonRelationshipSourceAuthResolveFailed marks Ready=False when
	// spec.auth's AgentIdentity/credential/Secret cannot be resolved.
	ReasonRelationshipSourceAuthResolveFailed = "AuthResolutionFailed"
	// ReasonRelationshipSourcePassFailed marks Ready=False if relsync.Pass
	// itself ever returns a non-nil error — defensive, not reachable in the
	// current implementation. relsync.Pass's own doc says its error return
	// is reserved for a failure the algorithm cannot even attempt to
	// recover from, and today it always returns nil: every anticipated
	// failure (enumeration, a scope's fetch/write, the reap scan) folds
	// into PassResult.ScopeErrors instead — see
	// ReasonRelationshipSourceEnumerationFailed below for the "enumeration
	// produced nothing" case that reason exists to catch. Kept as the
	// defensive branch it is, not because it fires today.
	ReasonRelationshipSourcePassFailed = "PassFailed"
	// ReasonRelationshipSourceEnumerationFailed marks Ready=False when a pass
	// enumerated NOTHING and processed NOTHING — EnumComplete=false with zero
	// scopes visited. "One scope failing is not a pass failure" (Synced,
	// above) does not extend to a total enumeration failure (a revoked
	// token, missing_scope, a network outage): that pass wrote nothing, and
	// without this reason it folds into ScopeErrors while Ready still reads
	// True/Synced, so `kubectl get relationshipsources` shows a healthy
	// source that has never written a tuple. A pass that visited at least
	// one scope (a budget-truncated but otherwise productive cycle) still
	// reports Synced — see PassResult.Processed.
	ReasonRelationshipSourceEnumerationFailed = "EnumerationFailed"
)

RelationshipSource reasons.

View Source
const (
	// IdentityKindAgentIdentity is the AGENT's own shared credential.
	IdentityKindAgentIdentity = "AgentIdentity"
	// IdentityKindUserIdentity is a person's standing credential.
	IdentityKindUserIdentity = "UserIdentity"
	// IdentityKindSessionUserIdentity is the per-session projection of a
	// UserIdentity — same ownership (a person's), different CR.
	IdentityKindSessionUserIdentity = "SessionUserIdentity"
)

The three spellings ResolvedCredentialRef.IdentityKind may carry. They are constants because WHOSE credential a request names decides which humans see the card (pkg/channels/channelsd/pipeline), which gate authorizes the click, and which object the replacement is written to (pkg/platform/identityd) — three packages that must agree on the spelling exactly. A typo in any one of them fails OPEN in the worst direction: an agent's shared credential silently treated as a person's own.

View Source
const (
	CredentialUpdateRequestPhasePending   = "Pending"
	CredentialUpdateRequestPhaseOpen      = "Open"
	CredentialUpdateRequestPhaseFulfilled = "Fulfilled"
	CredentialUpdateRequestPhaseRefused   = "Refused"
	CredentialUpdateRequestPhaseExpired   = "Expired"

	// CredentialUpdateRequestPhaseCollapsed is a request that resolved to a
	// credential another request already holds a live card for. It is
	// deliberately neither of the two phases anything downstream acts on:
	//
	//   - NOT Open, and Open is the ONLY phase channelsd's
	//     CredentialUpdateWatcher publishes on, so "no second card" is
	//     structural here rather than a second check bolted onto the publisher.
	//   - NOT terminal (IsCredentialUpdateRequestTerminal), so the blocked
	//     agent's request_credential_update call keeps waiting instead of
	//     being told its ask was settled when nothing has been decided yet.
	//
	// It is a named phase rather than leaving the request in Pending so that
	// `kubectl get credupdate` answers "why has nothing happened for my
	// session" directly, next to status.collapsedInto naming what it waits on.
	CredentialUpdateRequestPhaseCollapsed = "Collapsed"
)

CredentialUpdateRequest phases.

View Source
const (
	// Opened a card.
	CredentialUpdateDeterminationRejectedVerified    = "RejectedVerified"
	CredentialUpdateDeterminationRejectedRefreshDead = "RejectedRefreshDead"
	// RejectedUnverified: opened on the platform's own observed auth failures
	// when the provider could not be reached to confirm.
	CredentialUpdateDeterminationRejectedUnverified = "RejectedUnverified"
	// Refused.
	CredentialUpdateDeterminationCredentialLive  = "CredentialLive"
	CredentialUpdateDeterminationUnverified      = "Unverified"
	CredentialUpdateDeterminationNotUpdatable    = "NotUpdatable"
	CredentialUpdateDeterminationSelfHealed      = "SelfHealed"
	CredentialUpdateDeterminationNoCredential    = "NoCredential"
	CredentialUpdateDeterminationAmbiguousCred   = "AmbiguousCredential"
	CredentialUpdateDeterminationBudgetExhausted = "BudgetExhausted"
	// CredentialUpdateDeterminationCollapsed is neither an open nor a refusal,
	// which is why it sits in its own group: nothing was asked of a human for
	// THIS request, and nothing was denied to the agent either. The identical
	// credential already has a card in front of a human, and this request is
	// waiting on that one. See CredentialUpdateRequestPhaseCollapsed.
	CredentialUpdateDeterminationCollapsed = "Collapsed"
)

CredentialUpdateRequest determinations. Every one of these is surfaced to the agent verbatim; none of them is a silent outcome.

View Source
const (
	StandingSessionOnly = "session-only"
	StandingRequired    = "required"
)

Standing modes for SpiceDBResource.Standing.

View Source
const (
	MCPServerConditionValid     = "Valid"
	MCPServerConditionReachable = "Reachable"
	// MCPServerConditionSpiceDBSchemaValid is guardian-owned, NOT the mcpserver
	// controller that owns Valid/Reachable above. False means this MCPServer's
	// fragment was excluded from the cluster-wide schema compose, either as
	// FragmentInvalid (bad on its own — a RawZed syntax error, a reserved
	// scaffold definition redeclared, an internal conflict) or FragmentConflict
	// (valid alone, but colliding with an already-accepted fragment; earlier
	// namespace/name wins and the later is marked here).
	//
	// Isolating the failure here rather than freezing the compose pass is what
	// stops one tenant's bad or hostile MCPServer from taking down
	// SchemaIncluded for every AgentSessionGrants in the cluster.
	MCPServerConditionSpiceDBSchemaValid = "SpiceDBSchemaValid"
)

MCPServer condition types.

View Source
const (
	PinModeBlock   = "block"
	PinModeApprove = "approve"
	PinModeWarn    = "warn"
	PinModeOff     = "off"
)

Pin mode values, ordered strongest-first for ceiling folding: block > approve > warn > off. An empty Mode on a PinningRule means approve.

View Source
const (
	PublicEndpointPhasePending = "Pending"
	PublicEndpointPhaseReady   = "Ready"
	PublicEndpointPhaseFailed  = "Failed"
)
View Source
const (
	// ReasonPublicEndpointTunnelOpen — the tunnel is open and status.url is live.
	ReasonPublicEndpointTunnelOpen = "TunnelOpen"
	// ReasonPublicEndpointTunnelOpening — a concurrent reconcile of this same
	// endpoint is inside the provider's Start. This one declined to open a
	// second provider session and will re-check shortly.
	ReasonPublicEndpointTunnelOpening = "TunnelOpening"
	// ReasonPublicEndpointAuthTokenMissing — spec.authTokenRef does not resolve
	// to a non-empty Secret key. A prerequisite that has not arrived yet rather
	// than a failure, so the phase is Pending and the controller re-checks.
	ReasonPublicEndpointAuthTokenMissing = "AuthTokenSecretMissing"
	// ReasonPublicEndpointProviderUnknown — spec.provider names no registered
	// tunnel provider. Permanent until the spec is edited, so the phase is
	// Failed and the controller does not requeue.
	ReasonPublicEndpointProviderUnknown = "UnknownProvider"
	// ReasonPublicEndpointTunnelFailed — the provider refused to open the
	// tunnel (bad token, provider outage, session limit reached).
	ReasonPublicEndpointTunnelFailed = "TunnelStartFailed"
	// ReasonPublicEndpointLocalURLMissing — spec.localURL is empty. The CRD
	// marks the field required, so this is reachable only for a CR created
	// against an earlier version of the schema; refused rather than defaulted,
	// because a guessed local address 404s webd for every route.
	ReasonPublicEndpointLocalURLMissing = "LocalURLMissing"
	// ReasonPublicEndpointClusterKindRefusesTunnels — this cluster kind's
	// PublicEndpointPolicy is Never. Durable clusters have real ingress and
	// webd's external URL already names a genuine host; opening a tunnel would
	// overwrite it. Refused rather than quietly ignored so the CR says why.
	ReasonPublicEndpointClusterKindRefusesTunnels = "ClusterKindRefusesTunnels"
	// ReasonPublicEndpointReservedDomainUnsupported — spec.reservedDomain is
	// set but no registered provider honors it yet. Refused fail-closed rather
	// than accepted-and-ignored: silently handing back a per-session URL that
	// changes on every restart would leave the operator believing a promise
	// the system is not keeping.
	ReasonPublicEndpointReservedDomainUnsupported = "ReservedDomainUnsupported"
)

PublicEndpoint condition reasons.

View Source
const (
	SessionHoldPhaseActive   = "Active"
	SessionHoldPhaseReleased = "Released"
)
View Source
const (
	ClusterAgentSettingsName = "cluster"
	AgentSettingsName        = "default"
)

Well-known names for the singleton settings resources. The settings webhook enforces that only these names may exist; the resolver and controllers look them up by these names.

View Source
const (
	SidecarToolboxConditionValid     = "Valid"
	SidecarToolboxConditionReachable = "Reachable"
	// SidecarToolboxConditionSpiceDBSchemaValid is the guardian-owned condition
	// reporting whether this toolbox's spec.spicedbSchema fragment was accepted
	// into the cluster-wide compose. False (with FragmentInvalid/FragmentConflict)
	// means the fragment was excluded so ONE bad or hostile toolbox cannot freeze
	// schema convergence for every session — the same isolation MCPServer gets.
	SidecarToolboxConditionSpiceDBSchemaValid = "SpiceDBSchemaValid"
)
View Source
const (
	SidecarToolboxReasonFragmentInvalid  = "FragmentInvalid"
	SidecarToolboxReasonFragmentConflict = "FragmentConflict"
	SidecarToolboxReasonFragmentValid    = "FragmentValid"
)

SidecarToolbox SpiceDBSchemaValid reasons, mirroring the MCPServer set: FragmentInvalid = failed per-fragment ValidateFragment on its own; FragmentConflict = valid alone but collided with an already-accepted fragment; FragmentValid = stamped True only to clear a previously-invalid fragment.

View Source
const (
	SpiceboxToolkitConditionValid = "Valid"
	// SpiceboxToolkitConditionSpiceDBSchemaValid is guardian-owned, NOT owned by
	// the spiceboxtoolkit controller that owns Valid above. False means this
	// toolkit's spec.spicedbSchema fragment was excluded from the cluster-wide
	// schema compose, either as FragmentInvalid (bad on its own — a RawZed
	// syntax error, a reserved scaffold definition redeclared, an internal
	// conflict) or FragmentConflict (valid alone, but colliding with an
	// already-accepted fragment; earlier namespace/name wins and the later is
	// marked here).
	//
	// A SpiceboxToolkit CR is installed, so it is tenant input like MCPServer
	// and SidecarToolbox — the //go:embed'ed built-in toolkits are a different,
	// compile-time set and are not gated by this condition. Isolating the
	// failure here rather than freezing the compose pass is what stops one
	// tenant's bad or hostile toolkit from taking down SchemaIncluded for every
	// AgentSessionGrants in the cluster.
	SpiceboxToolkitConditionSpiceDBSchemaValid = "SpiceDBSchemaValid"
)

SpiceboxToolkit condition types.

View Source
const (
	SpiceDBBootstrapConditionValid                = "Valid"
	SpiceDBBootstrapConditionSchemaIncluded       = "SchemaIncluded"
	SpiceDBBootstrapConditionRelationshipsApplied = "RelationshipsApplied"
)

SpiceDBBootstrap condition types.

View Source
const (
	SpiceDBBootstrapReclaimDelete = "Delete"
	SpiceDBBootstrapReclaimRetain = "Retain"
)

SpiceDBBootstrap reclaim policies.

View Source
const (
	ReasonCanonicalizeRequiresUserEmail = "CanonicalizeRequiresUserEmail"
	ReasonUserSubjectNotCanonical       = "UserSubjectNotCanonical"
	ReasonSubjectSetCannotCanonicalize  = "SubjectSetCannotCanonicalize"
	ReasonSubjectWildcardConflict       = "SubjectWildcardConflict"
	ReasonCannotRedeclareUser           = "CannotRedeclareUser"
	// ReasonRelationOwnedByAnotherSource marks a relationship whose
	// resourceType#relation is claimed by a different in-tree writer (see
	// pkg/authz/spicedb/relsource). The write-time guard
	// (spicedb.TouchBootstrapRelationshipVia) refuses the same relationship;
	// this reason surfaces that refusal on the CR itself instead of only in a
	// reconcile log.
	ReasonRelationOwnedByAnotherSource = "RelationOwnedByAnotherSource"
	// ReasonSpicedbSchemaFragmentInvalid is the SchemaIncluded=False reason
	// when this CR's own spec.spicedbSchema fragment is invalid ON ITS OWN
	// (guardianschema.ValidateFragment) — a RawZed syntax error, or it
	// redeclares a reserved scaffold definition. Distinct from
	// ReasonSpicedbSchemaConflict below, which is for a fragment that is
	// valid alone but collides with an already-accepted one.
	ReasonSpicedbSchemaFragmentInvalid = "SpicedbSchemaFragmentInvalid"
	// ReasonSpicedbSchemaConflict is the SchemaIncluded=False reason when
	// this CR's fragment is valid alone but collides with an already-accepted
	// fragment (guardianschema.PartitionCompatibleFragments) — see
	// ReasonSpicedbSchemaFragmentInvalid above for the invalid-alone case.
	ReasonSpicedbSchemaConflict = "SpicedbSchemaConflict"
	ReasonAllTouched            = "AllTouched"
	ReasonPartialApply          = "PartialApply"
	ReasonSpiceDBUnavailable    = "SpiceDBUnavailable"
	ReasonSpiceDBAuthFailed     = "SpiceDBAuthFailed"
	ReasonSpiceDBRejected       = "SpiceDBRejected"
	ReasonSchemaNotReady        = "SchemaNotReady"
	ReasonSchemaWriteFailed     = "SchemaWriteFailed"
)

SpiceDBBootstrap condition reasons.

View Source
const (
	// AgentClassPermissionStartSession = starter + platform->start_session.
	AgentClassPermissionStartSession = "start_session"
	// AgentClassPermissionStartExplicit = starter. The platform-admin arm is
	// absent by design: an explicit gate is one admins list themselves on.
	AgentClassPermissionStartExplicit = "start_explicit"
	// AgentClassRelationStarter is the relation AllowedStarters is written to.
	AgentClassRelationStarter = "starter"
)

Names on the SpiceDB `agentclass` definition (pkg/authz/spicedb/schema/schema.zed). Mirrored here, not imported, because this package depends on nothing.

View Source
const (
	SubagentRequestPhasePending = "Pending"
	SubagentRequestPhaseRunning = "Running"
	// SubagentRequestPhaseAwaitingParent is NON-terminal: the child asked its
	// parent a question and parked. IsTerminal deliberately excludes it, so a
	// reconcile still revisits the request and the child stays alive; what
	// bounds it is the controller's own per-exchange parent-reply timeout, not
	// a phase transition the child can reach on its own.
	SubagentRequestPhaseAwaitingParent = "AwaitingParent"
	// SubagentRequestPhaseAwaitingDisclosure is NON-terminal: a requested data
	// slot would disclose its datum to this child, so a human who can speak
	// for that datum is being asked.
	//
	// THE CHILD IS NOT CREATED WHILE THIS HOLDS. Starting it with the
	// undecided slot omitted would hand it a partial handoff, which it cannot
	// distinguish from a parent that chose to send less — so it would proceed
	// on the shorter set as though that were the task. The delegation waits
	// whole or fails whole.
	SubagentRequestPhaseAwaitingDisclosure = "AwaitingDisclosure"
	SubagentRequestPhaseSucceeded          = "Succeeded"
	SubagentRequestPhaseFailed             = "Failed"
	SubagentRequestPhaseDenied             = "Denied"
)
View Source
const (
	SubagentModeSingleTurn = "single_turn"
	SubagentModeTask       = "task"
	SubagentModeChat       = "chat"
	// SubagentModeAttended is a channel for a full back-and-forth with a HUMAN
	// rather than the parent agent. Registered here as a legal Mode value; the
	// routing, respond_to_user binding and provisioning that give it behavior
	// distinct from chat are added in a later change.
	SubagentModeAttended = "attended"
)
View Source
const (
	ToolGuardActionOff  = "off"
	ToolGuardActionWarn = "warn"
	ToolGuardActionDeny = "deny"
	ToolGuardActionHalt = "halt"
)

Breaker / rate-limit action values (BreakerSpec.Action, RateLimitSpec.Action, ToolGuardCeiling.MinAction). Severity order: off < warn < deny < halt.

View Source
const (
	WorkshopPhaseProvisioning = "Provisioning"
	WorkshopPhaseReady        = "Ready"
	WorkshopPhaseExpired      = "Expired"
	WorkshopPhaseReleased     = "Released"
	WorkshopPhaseDeleting     = "Deleting"
)

Workshop phases. Released is the terminal phase for a workshop whose builder session finished (and whose install request, if it made one, has been decided) — recorded just before the CR is deleted, so the ordinary finalizer teardown runs and the workshop stops counting against the starter's cap.

View Source
const (
	WorkshopClosePhaseClosed   = "Closed"
	WorkshopClosePhaseRefused  = "Refused"
	WorkshopClosePhaseNotFound = "NotFound"
)

WorkshopCloseStatus.Phase values, matching WorkshopCloseStatus.Phase's +kubebuilder:validation:Enum exactly. Written only by the Workshop controller's close pass (pkg/controllers/workshop/close.go).

View Source
const (
	WorkshopInstallPhaseRequested = "Requested"
	WorkshopInstallPhaseApproved  = "Approved"
	WorkshopInstallPhaseInstalled = "Installed"
	WorkshopInstallPhaseDeclined  = "Declined"
	WorkshopInstallPhaseFailed    = "Failed"
)

WorkshopInstallStatus.Phase values, matching WorkshopInstallStatus.Phase's +kubebuilder:validation:Enum exactly. The channelsd WorkshopHandoffWatcher only ever writes Requested (on card delivery); admind writes Installed/Declined/Failed on the admin's click. Approved is reserved for a future two-step approve→install flow and is not written today.

View Source
const (
	LabelWorkshopSessionNamespace = "agentprimitives.authzed.com/workshop-session-namespace"
	LabelWorkshopSessionName      = "agentprimitives.authzed.com/workshop-session-name"
	LabelWorkshopNamespace        = "agentprimitives.authzed.com/workshop-namespace"
)

Labels stamped on the workshop NAMESPACE (session attribution) and on the cluster-scoped tool CRs a workshop authors (reaping + the outside-reference refusal). Values are namespace/name segments, all label-safe.

View Source
const (
	WorkspaceSourceConditionValid = "Valid"
	WorkspaceSourceConditionReady = "Ready"
)
View Source
const AgentSessionConditionCredentialRequestPublished = "CredentialRequestPublished"

AgentSessionConditionCredentialRequestPublished is set True by channelsd once it has minted a passthrough link and delivered a KindCredentialRequest on the session's credential_request sub-channel. It doubles as the dedup key: channelsd's watcher skips a session that is already True, so a long-parked session does not spam the user with a fresh "Connect your accounts" prompt every poll. LastTransitionTime is effectively the link's mint time.

View Source
const AgentSessionConditionCredentialUpdatePending = "CredentialUpdatePending"

AgentSessionConditionCredentialUpdatePending marks a session parked in AwaitingCredentials because a CredentialUpdateRequest it owns is Open � as distinct from the SAME phase entered via the passthrough identity gate. The two causes share one phase but need independent unpark signals, and this condition is what tells the credential-update park/unpark logic "I am the one who parked this session", so it never mistakes an identity-gate park for its own or vice versa.

Unlike the channelsd-owned CredentialRequestPublished, the operator's AgentSession reconciler writes this, through the same agentstatus.WriteOwned path as every other operator-owned condition here.

View Source
const (
	// AgentSessionGrantsConditionSchemaIncluded reports whether every pair
	// in spec.pairs is present in the live SpiceDB agentsession definition.
	AgentSessionGrantsConditionSchemaIncluded = "SchemaIncluded"
)

AgentSessionGrants condition types.

View Source
const AgentSessionPhaseAwaitingCredentials = "AwaitingCredentials"

AgentSession passthrough-identity phase. Entered when an identityMode=userPassthrough session's starter is missing one or more credentials the agent needs. The operator does not spawn a runner pod while a session is in this phase.

View Source
const AgentSessionPhaseAwaitingIdentityChoice = "AwaitingIdentityChoice"

AwaitingIdentityChoice: an identityMode=ask|dynamic session is waiting for the initiating user to choose an identity. UNLIKE AwaitingCredentials, the operator DOES keep the runner pod alive in this phase � the runner drives the choice prompt.

View Source
const AgentSessionPhaseAwaitingStartApproval = "AwaitingStartApproval"

AwaitingStartApproval: a session started by an org non-member (a channel guest) without agentclass#start_session is parked awaiting a platform admin's decision. Like AwaitingCredentials, the operator spawns NO runner pod while a session is in this phase — the session has no started_by tuple yet, so nothing may act on the guest's behalf until an admin approves.

View Source
const AgentUIConditionToolsGranted = "ToolsGranted"

AgentUIConditionToolsGranted is a SEPARATE condition from Valid: whether EligibleTools fully covers the UI's own (deduplicated) spec.tools request. True means every requested tool made the ceiling; False means at least one did not, with the message carrying uigrant.ExplainCeiling's diagnosis so an operator can see WHICH tool and WHY without source-diving. A UI can be Valid=True (every slot default well-formed) and ToolsGranted=False at the same time — they are independent facts, not a shared verdict.

View Source
const AgentUIConditionValid = "Valid"

AgentUIConditionValid is the Valid condition type on AgentUIStatus. True means the page — spec.view, or spec.slots compiled — parses and validates against the platform vocabulary, including every hook's contract; on a resolve failure for the AgentClass grant it is set Unknown (see ReasonAgentUIGrantUnresolved), never derived from a ceiling this reconcile failed to read.

View Source
const AnnotationAuthzServiceSubject = "agentprimitives.authzed.com/authz-service-subject"

AnnotationAuthzServiceSubject records the fully-qualified, non-human SpiceDB subject ("service:<id>") an AgentSession acts as when its inbound carried no human at all — a webhook delivery, a cron tick. The value is Channel.spec.authzSubject, validated as a service subject and canonicalized by the channelsd pipeline at session creation, in the same branch that decides there is no user to attribute the session to.

It is the SIBLING of AnnotationStartedByCanonicalID, never a substitute for it: started_by is `relation started_by: user` in the schema, so a service subject stamped there would assert a human who does not exist. The two annotations are mutually exclusive by construction — the pipeline writes this one exactly where it suppresses that one.

The runner reads it as the FINAL fallback for the tool-call authorization subject (see ResolveAuthSubjects). Without it such a session authorizes every tool call as the empty subject, which SpiceDB rejects as a malformed request rather than answering the authorization question at all.

View Source
const AnnotationForkedFromThread = "agentprimitives.authzed.com/forked-from-thread"

AnnotationForkedFromThread records the Slack thread the parent session occupied when a child was forked via Restart-from-here. Format:

"<team_id>:<channel_id>:<thread_ts>"

Stamped on the child by BuildChildSession so the Slack sender can:

  • Render the new-thread starter message with a backlink URL to the parent thread.
  • Post a forward-link notice in the parent thread once the new thread root ts is known (first-send capture).
  • Post an ephemeral in the new thread to the original requester.

Absent on sessions that are not restarts or whose parent had no Slack thread binding.

View Source
const AnnotationInboundAttachmentCount = "agentprimitives.authzed.com/inbound-attachment-count"

AnnotationInboundAttachmentCount records how many attachments rode on the inbound message that CREATED this AgentSession. Stamped by the channelsd pipeline atomically with the Create; absent means "none".

It exists because a new session's first message arrives SPLIT IN TWO: the text goes into spec.Prompt and becomes turn 0, while the attachment note and content blocks can only be written afterwards as a separate "inbox" turn — composing them needs a fetch/upload/extract round trip that cannot run before the session exists to key the upload against, and takes as long as the file is big.

The runner is spawned the moment the AgentSession exists, BEFORE that second write lands. Without this annotation it cannot tell "no attachments" from "attachments not written yet", so it answers a message it has not finished receiving. The annotation is the fence: its presence means an inbox turn is still owed for turn 0. See Loop.awaitInboundAttachmentTurn.

The decimal count is informational; the fence keys on PRESENCE, because even a partially-processed batch writes exactly one inbox turn for the message.

View Source
const AnnotationPinRefreeze = "agentprimitives.authzed.com/pin-refreeze"

AnnotationPinRefreeze requests that the dependency's pin baseline be re-frozen to the given identity ("sha256:�" manifest hash or image digest). Honored by the owning reconciler ONLY when the live identity equals the annotation value (race-free accept: you re-freeze the exact thing you reviewed); the annotation is cleared once honored. A stale value (live moved again) leaves PinDrift in place with a message noting the failed refreeze.

View Source
const AnnotationSessionOpening = "agentprimitives.authzed.com/session-opening"

AnnotationSessionOpening carries the one line that opens this session's outbound thread: what triggered it, in the trigger's own words.

Only a session whose INBOUND CARRIES NO HUMAN gets one. A webhook payload or a cron tick arrives with no message of its own, so the outbound thread's root is whatever the agent happens to emit first — a plan checklist, a status caption — with nothing saying what it is about. channelsd's outbound relay posts this ahead of the session's first output and takes the thread root from that send.

Retained after posting — never cleared — so its text survives for channelsd to re-render the session's pinned status from later. See AnnotationSessionOpeningSent for what stops the line from being posted a second time.

Stamped at session creation by the channelsd pipeline, from channelkinds/outputbind.SessionOpening — the same place the outbound anchor itself is derived, because it is the same question: what roots this thread. Absent on a human-initiated session, whose thread is already rooted by the person's own message, and on a trigger whose kind cannot describe itself.

View Source
const AnnotationSessionOpeningSent = "agentprimitives.authzed.com/session-opening-sent"

AnnotationSessionOpeningSent marks that the AnnotationSessionOpening line has already been posted, independently of the text itself (which is retained, not cleared, for the pinned-status renderer).

The outputChannel's own thread_ts is the primary "already open" signal, but it is only ever populated for a kind whose Sender reports routing metadata back on first send — today, only slack (and its fake test double). Any other kind implementing OutboundAnchorProvider would report no root, and without this marker the opening line would be re-posted ahead of every subsequent envelope for the session's life. Stamped by channelsd's outbound relay right after a successful post; left unset on a send failure so the next envelope retries.

View Source
const AnnotationStandinSource = "agentprimitives.authzed.com/standin-source"

AnnotationStandinSource marks an AgentClass as a credential-free REHEARSAL stand-in projected for another agent (agent-builder plan 9b, Task 1; pkg/web/workshopprojectsrv), rather than something a workshop's builder session authored itself. Value is "<namespace>/<name>" of the source class the reachability check verified — WHEN the annotation is genuine.

SECURITY: this annotation is a HUMAN-READABLE LABEL ONLY and MUST NEVER be trusted as the basis for a security decision. A workshop's builder session can server-side-apply arbitrary metadata.annotations into its own namespace via its workshop_apply tool, and the admission webhook's checkAgentClass never inspects annotations — so a builder (or a prompt-injected model driving one) can stamp this marker onto a class it authored itself. The authoritative record is Workshop.status.standins (WorkshopStatus.Standins, this same file) — an operator-owned status field a builder's SA holds no update on — which is what pkg/web/workshopprojectsrv's collision check and pkg/tools/workshopmcp's soleAuthoredClassName both consult instead. This annotation still gets stamped, purely so a human reading `kubectl get agentclass -o yaml` can see at a glance what a bare name stands in for.

Declared here (pkg/apis, not the route's own package) because it is a cross-package marker: pkg/web/workshopprojectsrv stamps it when it projects a stand-in, and a human (or a debugging session) reading either package's output benefits from the same constant name.

View Source
const AnnotationStartApprovalRequestRef = "agentprimitives.authzed.com/start-approval-request-ref"

AnnotationStartApprovalRequestRef marks an AgentSession as parked awaiting a platform admin's start_approval decision, and names the RequestRef of the PendingRequesters entry the decision resolves. Stamped by the channelsd pipeline ON THE CREATE itself — the operator spawns runners off the object's existence, so a marker patched afterwards could lose the race it exists to close. The operator withholds the runner (and owner resolution) while it is present; the approve handler removes it to unpark.

View Source
const AnnotationStartedByCanonicalID = "agentprimitives.authzed.com/started-by-canonical-id"

AnnotationStartedByCanonicalID records the canonical SpiceDB subject ("user:<base64(email)>") of the human who created the AgentSession. Stamped by the channelsd pipeline at session creation � the same place it canonicalizes identity for the SpiceDB started_by write. The AgentSession passthrough gate reads it to locate the starter's UserIdentity. Absent on kubectl-driven sessions.

View Source
const AnnotationTriggerOwnerSubject = "agentprimitives.authzed.com/trigger-owner-subject"

AnnotationTriggerOwnerSubject carries the subject-set reference of the external account a triggered session's inbound belongs to — for a GitHub pull-request session, the PR author as "github_user:<numeric-id>#user".

Written by channelsd from a VERIFIED webhook delivery (the kind's TriggerOwnerProvider derives it from provider-structural payload fields, never submitter-authored text) and carried through the same PreTurnAnnotations stamp every kind-supplied annotation uses. The operator's owner resolver reads it and writes an ADDITIONAL agentsession#owner tuple — additional to, never instead of, the channel-policy-resolved owner, which remains the approval anchor a session must always have.

The subject-set resolves to a platform user only through the attested identity edge (github_user#user@user:<canonical>) minted from a verified credential, so a session whose author never linked one grants nobody anything — and lights up retroactively, with no rewrite here, the moment they do. The resolver validates the value's TYPE against the channel-kind session links the composed schema actually admits, fail-closed: a value naming any other type is logged and not written.

View Source
const (
	ArtifactRenderConditionReady = "Ready"
)
View Source
const AuthzSlotMembershipDefault = "frozen"

AuthzSlotMembershipDefault mirrors AuthzSlot.Membership's +kubebuilder:default marker. Slots is +listType=atomic, so SSA merges a re-applied slot as a REPLACE of the whole list, not field by field — a re-apply of a slot the bundle leaves membership unset on does NOT pick up the value the apiserver already defaulted onto the live object. An installer that completes membership with this constant before applying therefore writes what the apiserver would have defaulted anyway, which is what keeps a byte-identical re-install a true SSA no-op. See pkg/platform/oap/install/apply.go's completion of spec.authz.slots.

View Source
const ClusterIdentityProviderName = "default"

ClusterIdentityProviderName is the enforced singleton object name.

View Source
const CredentialUpdateRequestConditionCardDelivered = "CardDelivered"

CredentialUpdateRequestConditionCardDelivered is stamped True by channelsd's CredentialUpdateWatcher in the SAME patch that persists status.interactionRef, so the two are always set together and can never disagree; absent means "not yet delivered", exactly like an empty interactionRef.

It exists because determination (the operator's reconciler) and publication (channelsd, a separate process) are split, and publication has several silent skip paths � no InputChannel, no started-by subject, no ResolvedCredential, a watcher disabled by a bad signing key. Without the signal, a request that was never delivered still expires reading "nobody updated the credential in time", when in truth nobody was ever asked.

View Source
const DefaultMaxWorkshopsPerStarter = int32(3)

DefaultMaxWorkshopsPerStarter is the per-starter live-workshop ceiling in force when spec.limits.maxWorkshopsPerStarter is unset.

View Source
const DefaultSandboxKind = "pod"

DefaultSandboxKind is the backend a class gets when spec.sandbox.kind is unset. Single source of truth for that name: the CRD default marker, the pod kind's registry key, and SandboxBackend.ResolvedKind all derive from it.

View Source
const GroupName = "agentprimitives.authzed.com"
View Source
const IdentitiesNamespace = "agentprimitives-identities"

IdentitiesNamespace is the fixed namespace that holds cluster-scoped UserIdentity master credential Secrets. UserIdentity is cluster-scoped and has no namespace of its own, so its credentials' secretRefs always resolve against this namespace.

View Source
const IdentitydExternalURLConfigMap = "spicebox-identityd-external-url"

IdentitydExternalURLConfigMap names the ConfigMap holding identityd's external URL (the URL used to mint signed links + to register as the OAuth redirect). Operator-populated; default is a kubectl-port-forward example.

View Source
const InboundAttachmentWriteBudget = 5 * time.Minute

InboundAttachmentWriteBudget is how long the writer of that inbox turn is allowed to take, and therefore exactly how long the runner waits for it before giving up and answering without the attachment.

ONE constant deliberately serves both halves. A fence whose budget is chosen independently of the work it fences is worse than no fence: shorter than the writer's own timeout, it expires on healthy-but-slow batches, silently reintroducing the race it exists to close in exactly the case — a big file on a slow link — where it matters most.

The value is the channelsd side's whole-batch bound: up to a kind's per-message attachment limit (Slack: 10), each fetched serially under its own 60s sub-deadline, with extraction capped at 30s inside that.

View Source
const LabelCascadeOf = "agentprimitives.authzed.com/cascade-of"

LabelCascadeOf names the ORIGINATING SessionHold a cascaded hold was fanned out from. Set only on a hold this controller's own cascade created for a descendant of the originating hold's session (pkg/controllers/sessionhold's cascadeHold) — a directly-created hold (manual, or a tripper's) never carries it.

It exists so a release can find and clear exactly the holds IT created: a cascaded hold is owner-ref'd to its own child session, not to the originating hold, so k8s garbage collection never removes it just because the originating hold is released — only this label lets releaseCascade (pkg/controllers/sessionhold) discover and release them together. Without it, cascading a hold across a delegation subtree would be a one-way door: every descendant frozen, and nothing left that knows which holds a single release should clear.

View Source
const LabelDelegationRoot = "agentprimitives.authzed.com/delegation-root"

LabelDelegationRoot names the root of the delegation tree a session belongs to. It is absent on a root and present on every descendant, set once at creation by the SubagentRequest controller and never updated — the value is a pure function of the parent, so a re-apply is byte-identical.

It exists so the delegation closure is one indexed List for the whole tree rather than a walk per member -- true of ListClosure itself; DescendantsOf narrows that List down to one member's subtree with a containment walk per member, since a "whole tree" List carries no per-member ancestry on its own. The pooled total-agent ceiling, the forensic-hold cascade and the closure-aware denial streak all need "every session in this tree", and all three would otherwise re-derive it from parent chains on every reconcile.

View Source
const LabelToolUseID = "agentprimitives.authzed.com/toolUseID"

LabelToolUseID is the ToolCall metadata label carrying the LLM-emitted tool_use ID (the RAW id, not the RFC-1123-normalized form the object name uses). The runner stamps it when it builds the ToolCall; the toolcall controller reads it to make the per-dispatch tool_dispatch_snapshot audit entry ID unique. It is the ONLY component of that ID that distinguishes two dispatches which land on the same (turnIndex, sequence) — turnIndex resets to 0 on session resume and the memory scope (namespace/name) is reused across recreated sessions that share a deterministic name — so an unstamped (empty) value collapses the ID to `tds-<turn>-<seq>-` and two such runs collide on an append-only write.

View Source
const MCPServerReasonFragmentConflict = "FragmentConflict"

MCPServerReasonFragmentConflict is the SpiceDBSchemaValid=False reason when this fragment is valid on its own but conflicts with an already-accepted fragment from ANOTHER MCPServer (same resource name, different body) — the N-way incremental isolation pass (guardianschema.PartitionCompatibleFragments) rejected it so it does not freeze the whole cluster-wide schema. First-in-sort-order wins the conflict, so the MCPServer carrying this reason is the later of the two.

View Source
const MCPServerReasonFragmentInvalid = "FragmentInvalid"

MCPServerReasonFragmentInvalid is the SpiceDBSchemaValid=False reason when spec.spiceDBSchema fails per-fragment validation ON ITS OWN (guardianschema.ValidateFragment) — a RawZed syntax error, a reserved scaffold definition redeclared, or an internal conflict.

View Source
const MCPServerReasonFragmentValid = "FragmentValid"

MCPServerReasonFragmentValid is the SpiceDBSchemaValid=True reason — only stamped to clear a PREVIOUSLY-invalid fragment that has since been fixed; a fragment that has always been valid never has this condition set at all (see pkg/controllers/guardian's per-reconcile fragment validation pass).

View Source
const MaxLineageWalk = 16

MaxLineageWalk bounds every ancestor walk in the codebase.

Admission DAG-validates delegation rosters, so a cycle reaching this code means something upstream already went wrong — but a reconciler that hangs on a bad walk takes the whole operator down with it, so every walk terminates loudly (an error) rather than looping forever.

View Source
const MemoryTokenSecretSuffix = "-memory-token"

MemoryTokenSecretSuffix is the suffix of the per-session Secret holding the memory bearer token, the Ed25519 audit-signing seed, and the args-hash HMAC key. The full name is <sessionName> + this suffix; see pkg/controllers/agentsession.MemoryTokenSecretName.

View Source
const PassthroughCredentialSecretSuffix = "-passthrough-creds"

PassthroughCredentialSecretSuffix is the suffix of every per-session passthrough-credential Secret name. Used to recognize such a source so it can be bound to its owning session (see ToolCall.ValidateCredentialSourceOwnership).

View Source
const PassthroughLinkSigningKeySecret = "spicebox-passthrough-link-key"

PassthroughLinkSigningKeySecret names the Secret in the system namespace whose "key" data holds the HMAC signing key for passthrough deep-links. Generated once by `oap install`; mounted by channelsd (mint) and identityd (verify).

View Source
const PinDriftCondition = "PinDrift"

PinDriftCondition reports whether a dependency's live identity diverged from its recorded pin baseline. Shared by every pinned-dependency CR (MCPServer and SidecarToolbox today).

View Source
const (
	// PublicEndpointConditionReady is True exactly while the operator holds an
	// open tunnel for this endpoint and status.url names it. False carries the
	// reason the tunnel is not up, in the reasons below.
	PublicEndpointConditionReady = "Ready"
)

PublicEndpoint condition types.

View Source
const (
	// ReasonAgentClassUserLessMissingAuthz fires when an AgentClass binds
	// an input-role (or both-role) Channel whose kind reports
	// UserAttributable=false (e.g. bento) but the AgentClass has no
	// sessionInteractPermission AND/OR the Channel has no authzSubject �
	// leaving the spawned AgentSession with no SpiceDB subject to attribute
	// work to and no broad grant for humans to interact with the session.
	ReasonAgentClassUserLessMissingAuthz = "UserLessChannelMissingAuthz"
)

AgentClass condition reasons covering Channel-level validation (authz attribution for non-user-attributable input channels).

View Source
const ReasonAgentSessionSkillResolutionFailed = "SkillResolutionFailed"

ReasonAgentSessionSkillResolutionFailed is set on the AgentSession Failed condition when the runner cannot resolve every skill the AgentClass opts into at startup. The AgentClass gate already proved each skill materialized and is Valid=True before the session was allowed to spawn, so an unresolvable skill in the runner is a genuine inconsistency � usually the per-session runner ServiceAccount lacking read on skills/clusterskills � never a benign "not synced yet". Fail closed and diagnosable, rather than silently composing a skill-less prompt.

View Source
const ReasonAgentSessionTokenAuthzUnavailable = "TokenAuthzUnavailable"

ReasonAgentSessionTokenAuthzUnavailable is set on the AgentSession Failed condition when the runner's per-call use_token SpiceDB check is indeterminate � the checker is unconfigured (typed-nil) or the RPC itself errored (SpiceDB unreachable/timeout). This is distinct from a definitive deny (revoked token), which is surgical � an IsError tool result � and does NOT fail the session. An indeterminate check must fail closed: never let a MCP call proceed when authorization could not be confirmed.

View Source
const ReasonAgentSessionTokenGrantFailed = "TokenGrantFailed"

ReasonAgentSessionTokenGrantFailed is set on the AgentSession Failed condition when the operator cannot reconcile the session's externaltoken authorized_token grants (a credential value that won't resolve, or a SpiceDB write failure). It is fail-closed: the runner is not dispatched with an incomplete grant set, so the token-use pre-send check can never wave through an un-authorized token.

View Source
const ReasonAgentSessionWorkshopLimitExceeded = "WorkshopLimitExceeded"

ReasonAgentSessionWorkshopLimitExceeded is the boot-failure reason when a starter already holds maxWorkshopsPerStarter live workshops. A resource limit, not a policy halt: it is NOT in policyHaltReasons, so a takeover of such a thread inherits normally.

View Source
const (
	ReasonBuiltinCollision = "BuiltinCollision"
)

SpiceboxToolkit reasons.

View Source
const (
	ReasonClassInvalidToolchains = "InvalidToolchains"
)

SpiceboxClass toolchain reasons.

View Source
const ReasonCredentialRequestPublished = "CredentialRequestPublished"

ReasonCredentialRequestPublished is the Reason on a True AgentSessionConditionCredentialRequestPublished.

View Source
const ReasonCredentialUpdateCardDelivered = "Published"

ReasonCredentialUpdateCardDelivered is the Reason on a True CredentialUpdateRequestConditionCardDelivered.

View Source
const ReasonCredentialUpdateNoRecipient = "NoRecipient"

ReasonCredentialUpdateNoRecipient is the Reason on CardDelivered=False when the request resolved to an agent's OWN shared credential and nobody could be shown a card: no role=monitoring Channel to broadcast to, and the turn's author does not hold agentidentity#update_credential either.

Surfaced rather than skipped, because a park with no reachable recipient is otherwise indistinguishable from one a human is still thinking about � the request would sit Open for its whole window and expire having asked nobody. status.interactionRef stays EMPTY here, which keeps the Expired reason honest: "never delivered", not "nobody updated it in time".

View Source
const ReasonCredentialUpdateRequested = "CredentialUpdateRequested"

ReasonCredentialUpdateRequested is the Reason on a True AgentSessionConditionCredentialUpdatePending: an Open CredentialUpdateRequest exists for this session.

View Source
const ReasonCredentialUpdateResolved = "CredentialUpdateResolved"

ReasonCredentialUpdateResolved is the Reason on a False AgentSessionConditionCredentialUpdatePending: the CredentialUpdateRequest that caused the park reached a terminal phase (Fulfilled/Expired/Refused) or no longer exists.

View Source
const ReasonWebdExternalURLNotConfigured = "WebdExternalURLNotConfigured"

ReasonWebdExternalURLNotConfigured is the Reason on a False SessionUserIdentityConditionCredentialLinkAvailable: the spicebox-webd-external-url ConfigMap carries no externally reachable URL, so no credential link can be generated.

View Source
const SecretOutputSecretSuffix = "-secret-outputs"

SecretOutputSecretSuffix is the suffix of the per-session Secret the operator writes captured tool secret-output values into. The full name is <sessionName> + this suffix; see pkg/web/secretoutsrv.SecretOutputSecretName.

View Source
const SessionUserIdentityConditionCredentialLinkAvailable = "CredentialLinkAvailable"

SessionUserIdentityConditionCredentialLinkAvailable reports whether channelsd can mint a usable "Connect your accounts" deep-link for the parked session. False (ReasonWebdExternalURLNotConfigured) when the platform's external web address is unconfigured: a link minted then would be hostless or silently point at localhost, so channelsd fails CLOSED rather than ship a broken button. It recovers on the next tick after the ConfigMap is fixed. The session-observable counterpart of the monitoring event the watcher emits.

View Source
const (
	// SkillSourceConditionReady is True when the last sync cloned, discovered,
	// cached, and materialized Skills without a fatal error.
	SkillSourceConditionReady = "Ready"
)

SkillSource condition types.

View Source
const SlackOAuthSecret = "spicebox-slack-oauth"

SlackOAuthSecret names the Secret holding the Slack app's OAuth client_id + client_secret for "Sign in with Slack". Operator-populated before deploying identityd. `oap install` creates an empty skeleton.

View Source
const (
	SpiceboxClassConditionValid = "Valid"
)

SpiceboxClass condition types.

View Source
const (
	SpiceboxToolchainConditionValid = "Valid"
)

SpiceboxToolchain condition types.

View Source
const SpiceboxToolkitReasonFragmentConflict = "FragmentConflict"

SpiceboxToolkitReasonFragmentConflict is the SpiceDBSchemaValid=False reason when this fragment is valid alone but collides with an already-accepted one. First in order wins, so the toolkit carrying this reason is the later of the two.

View Source
const SpiceboxToolkitReasonFragmentInvalid = "FragmentInvalid"

SpiceboxToolkitReasonFragmentInvalid is the SpiceDBSchemaValid=False reason when spec.spicedbSchema fails per-fragment validation ON ITS OWN — a RawZed syntax error, a redeclared reserved scaffold definition, or an internal conflict.

View Source
const SpiceboxToolkitReasonFragmentValid = "FragmentValid"

SpiceboxToolkitReasonFragmentValid is the SpiceDBSchemaValid=True reason, stamped only to clear a PREVIOUSLY-invalid fragment that has since been fixed. A fragment that has always been valid never carries this condition.

View Source
const (
	SpiceboxToolspecConditionValid = "Valid"
)
View Source
const ToolchainCachePath = "/var/ap-cache"

ToolchainCachePath is the writable, disk-backed scratch volume every toolchain-bearing pod mounts. /tmp and /work are memory-backed emptyDirs (50Mi / 100Mi) charged against the pod memory limit, so GOCACHE, TMPDIR, CARGO_HOME and friends must point here or a build dies immediately.

View Source
const ToolchainRootPath = "/opt/ap-toolchains"

ToolchainRootPath is where toolchain overlays are mounted in the sandbox container. A toolchain named "go" lands at /opt/ap-toolchains/go. Its image payload MUST be built at that same absolute path: build path == mount path is what makes the payload relocation-free (shebangs, RPATHs, and wrapper scripts all bake absolute paths). Keep in sync with pkg/platform/podspec/builder.go — podspec imports this package, not the reverse.

View Source
const UpstreamAuthProviderNone = "none"

UpstreamAuthProviderNone is the explicit sentinel for SidecarToolboxUpstream.Provider that opts a sidecar OUT of the AgentIdentity/provider-library credential path. Its credential is instead a projected ServiceAccount token + bearer, minted by a controller (the AgentSession/Workshop reconcilers) — never a credential resolved from the /providers/ library. It is a valid, non-empty Provider value: only a truly empty Provider is refused, because the field must always state explicit intent (a real provider name, or this sentinel), never an accidental omission.

View Source
const WebdExternalURLConfigMap = "spicebox-webd-external-url"

WebdExternalURLConfigMap names the ConfigMap holding webd's externally reachable base URL. The "trusted-url" key carries the URL (the webd deployment uses this to mint artifact deep-links). Created by the webd install task; may be unpopulated until that task runs.

View Source
const WebdExternalURLFieldOwner = "publicendpoint-external-url"

WebdExternalURLFieldOwner is the server-side-apply field manager the PublicEndpoint controller writes the two keys above under.

It lives here rather than in that controller because it is a CONTRACT, not an implementation detail: it is how anything else can tell whether the controller has actually taken ownership of those keys. A host-side writer of the same ConfigMap (`oap desktop`, whose cluster kind creates an endpoint only on demand) must stand down once the handover has happened and keep writing until it has — and "an endpoint exists" is not that question, since an endpoint whose first reconcile fails never writes anything at all.

View Source
const WorkshopCloseTargetAll = "*"

WorkshopCloseTargetAll is the WorkshopCloseRequest.Target that means "every other live workshop of this workshop's own starter".

It exists because the two halves of a close cannot see the same thing. The sidecar's Role is name-restricted to its OWN Workshop (get/update/patch), so the tool that writes a close request cannot list the person's workshops and has no way to name them. The Workshop controller can, so a wildcard is the only form "close all of mine" can take: the tool writes this target for a workshop's FIRST such ask (and "*:<n>" for each later one, which is how a person asks again), and the controller resolves it against the workshops it can see, records one ordinary decision per resolved target it may decide, and answers the ask itself with a summary of what that ask decided.

View Source
const WorkshopLimitBody = "You already have the maximum number of builder workshops open at once. Finish or close one before starting another."

WorkshopLimitBody is what the person who tried to start a builder workshop reads when they are already at the per-starter cap. Plain words only — see refuseStart's identical rule in pkg/controllers/agentsession/start_gate.go.

Declared here rather than in either refusing package because TWO gates say it: the browser start route refuses the request outright (it names the person's open workshops after this sentence), and the operator's ensureWorkshop boot-fails a session that reached it anyway. Two copies of one sentence is how the two come to word the same refusal differently.

View Source
const WorkshopTokenSecretSuffix = "-workshop-token"

WorkshopTokenSecretSuffix is the suffix of the per-session Secret holding the agent-builder workshop sidecar's bearer token. The full name is <sessionName> + this suffix; see WorkshopTokenSecretName.

Variables

View Source
var (
	SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
	SchemeBuilder      = &scheme.Builder{GroupVersion: SchemeGroupVersion}
	AddToScheme        = SchemeBuilder.AddToScheme
)
View Source
var (
	DefaultTmpSize  = resource.MustParse("50Mi")
	DefaultWorkSize = resource.MustParse("100Mi")
	// DefaultCacheSize is the historical /var/ap-cache SizeLimit, applied when
	// a toolchain-bearing class does not set CacheSize.
	DefaultCacheSize = resource.MustParse("2Gi")
)

Scratch-volume defaults. Read them through EffectiveTmpSize / EffectiveWorkSize / EffectiveCacheSize rather than the pointers, so "unset" means the same thing in the pod builder and in validation.

View Source
var ApprovalConditionTypes = map[string]bool{
	AgentSessionConditionPermissionRequestPending:         true,
	AgentSessionConditionStartApprovalPending:             true,
	AgentSessionConditionToolApprovalPending:              true,
	AgentSessionConditionInfoLeakageApprovalPending:       true,
	AgentSessionConditionContentInspectionApprovalPending: true,
	AgentSessionConditionPreconditionWaiverPending:        true,
	AgentSessionConditionPreferenceConfirmPending:         true,
	AgentSessionConditionCredentialRequestPublished:       true,
	AgentSessionConditionInteractPolicyApplied:            true,
}

ApprovalConditionTypes are the AgentSession condition types written by channelsd. Disjoint from the operator's lifecycle conditions. This drives the SSA OWNERSHIP partition (which writer may set each condition) — so it intentionally includes channelsd's bookkeeping/lifecycle conditions (InteractPolicyApplied, CredentialRequestPublished) that are NOT "awaiting a human decision". For the awaiting-approval question, use pendingApprovalConditionTypes, NOT this map.

View Source
var ErrLineageCycle = errors.New("lineage: cycle or depth exceeded")

ErrLineageCycle is returned when a parent walk exceeds MaxLineageWalk hops, which means the chain either loops or is implausibly deep. It is a SENTINEL so callers can tell it apart from a transient read failure: a cycle cannot clear on its own — it takes a human editing a roster — so a caller that retries on it retries forever.

PerSessionSecretSuffixes is the canonical set of Secret name suffixes for the deterministic per-session Secrets the operator/runner materialize — each owned by, and living in the namespace of, exactly one AgentSession:

  • PassthroughCredentialSecretSuffix ("-passthrough-creds") — projected userPassthrough credential values (see PassthroughCredentialSecretName).
  • MemoryTokenSecretSuffix ("-memory-token") — the memory bearer token, the Ed25519 audit-signing seed, and the args-hash HMAC key (see pkg/controllers/agentsession.MemoryTokenSecretName).
  • SecretOutputSecretSuffix ("-secret-outputs") — captured tool secret-output values (see pkg/web/secretoutsrv.SecretOutputSecretName).
  • WorkshopTokenSecretSuffix ("-workshop-token") — the agent-builder workshop sidecar bearer token (see WorkshopTokenSecretName).

ToolCall.ValidateCredentialSourceOwnership binds every credential source whose Secret name carries one of these suffixes to the ToolCall's own AgentSession: a matching suffix under a DIFFERENT session prefix is a cross-session reference and is refused. That matters most for MemoryTokenSecretSuffix, whose Secret holds the audit-signing seed — naming another session's would let a compromised runner forge signed append-only entries under the victim's identity and replay its args-hash HMAC key. A new per-session Secret kind anywhere in the codebase MUST add its suffix here, or ValidateCredentialSourceOwnership silently fails to bind it.

The name-builder functions live in the packages that own each Secret's lifecycle, not here: they already import v1alpha1, so importing back would cycle. Each builds its name by appending the suffix declared here rather than a duplicated literal, and TestPerSessionSecretSuffixesMatchOwners pins that.

Functions

func AgentChannelRoles

func AgentChannelRoles() []string

AgentChannelRoles is every role a Channel BOUND TO AN AGENT may have: the legal set minus monitoring.

The distinction is the CRD's own, stated on AgentClass above and enforced by the Channel controller: a monitoring Channel is a framework-event sink that binds to no AgentClass, so it is never a role something choosing a channel FOR an agent may pick. Every caller that offers a role — a bundle's requires.channels declaration, `oap channel create --role` — offers exactly this set, and reaches monitoring through its own separate route (`--monitoring`, channelkinds.WizardInput.Monitoring) rather than by naming the role.

Derived by subtraction from AllChannelRoles rather than written out, so a role added to the CRD becomes offerable without an edit here — the same reason AllChannelRoles exists at all.

func AgentSessionPhaseStarted

func AgentSessionPhaseStarted(phase string) bool

AgentSessionPhaseStarted reports whether an AgentSession has progressed past initial scheduling: "" and Pending mean the runner has not come up yet, every other phase means it has.

A session parked in AwaitingCredentials or AwaitingIdentityChoice counts as STARTED and is legitimately waiting on the user. Callers gating on "is the runner still coming up?" (startup-grace clocks, browser/TUI readiness waits, channel-health probes) must therefore treat it as started, not stuck, or a session parked on a prompt is mis-declared a failed startup. Shared so the predicate is defined exactly once.

func AllChannelRoles

func AllChannelRoles() []string

AllChannelRoles returns every legal ChannelSpec.Role value, in the order the field's own +kubebuilder:validation:Enum lists them. A new role is added HERE and to that marker, together — a caller enumerating roles (a channel kind declaring it serves all of them, a lint checking a declared role is a real one) must never carry its own copy of the list.

A fresh slice per call, so a caller that sorts or truncates the answer cannot corrupt the next caller's.

func ApprovalConditionsActive

func ApprovalConditionsActive(s *AgentSession) bool

ApprovalConditionsActive reports whether the session is awaiting a human decision: any pending-approval queue is non-empty, or any pending-approval condition is True. The operator — the sole writer of phase — derives phase=AwaitingDecision from it. channelsd's own bookkeeping conditions that do not block on a human are deliberately excluded; see pendingApprovalConditionTypes.

func ArchivedBySweep

func ArchivedBySweep(sess *AgentSession) bool

ArchivedBySweep reports whether Phase==Succeeded was reached by the operator's long-idle archive sweep rather than by the agent finishing. The sweep clears FailureReason and records itself only as the Idle condition's reason, so this is the sole runtime signal distinguishing "parked" from "done".

A superseded parent is excluded even when it still carries the Archived condition: SupersedeParent stamps Phase=Succeeded without clearing it, and a resumed parent would contend with the very child that replaced it.

func AuthzServiceSubject

func AuthzServiceSubject(sess *AgentSession) identity.CanonicalUserID

AuthzServiceSubject returns the non-human SpiceDB subject sess acts as when its inbound carried no human — the value of AnnotationAuthzServiceSubject, which the channelsd pipeline stamps from the input Channel's declared spec.authzSubject. Empty for every session a human started, and for a kubectl-driven one.

The return type is CanonicalUserID and the value is a FULLY QUALIFIED "service:<id>" reference, which reads like a contradiction and is not: a canonical that came from an inbound is already allowed to carry its own object type, precisely because a kind that supplies no starting user assigns the Channel's subject verbatim. CanonicalUserID.SubjectRef is the accessor that knows this — use it, not CanonicalUserID.Subject, on anything derived from here, or the value double-prefixes into "user:service:<id>" and names nothing.

func CallRate

func CallRate(maxCalls int32, window time.Duration) float64

CallRate expresses a sliding-window call limit (maxCalls over window) as calls per second — the only quantity comparable across two different windows. Comparing call COUNTS instead lets a {100 calls, 1m} burst bound (144,000/day) displace an authored {200 calls, 24h}, relaxing the very rule a ceiling exists to tighten.

Even so, nothing chooses one pair over another: bounds with differing windows are carried ALONGSIDE each other and all are enforced, because neither shape implies the other at every horizon — a rate comparison says which is stricter on average, never which is stricter over one second. CallRate therefore decides only PRESENTATION (which pair leads a folded ceiling), never which bound survives.

A limit missing EITHER half is unlimited (+Inf): enforcement needs both positive, so a half-authored pair caps nothing and must never read as a bound. +Inf keeps ENFORCEMENT safe for objects stored before the XValidation rules on RateLimitSpec and ToolGuardCeiling existed, and keeps a zero window from being divided by.

It is NOT a backstop for ADMISSION, and that difference wedged a cluster: both types are mirrored into status.effectiveSettings, so a stored half-authored pair enforced nothing (fine) and then made the status write inadmissible (fatal), failing every reconcile with an error naming a field on another object. Whatever is mirrored into status must be narrowed to what these rules accept — the ceiling fold and the tier-policy mirror both drop a pair that cannot enforce, and report it.

func ExpandToolchainEnv

func ExpandToolchainEnv(env map[string]string, root, cache string) (map[string]string, error)

ExpandToolchainEnv substitutes the two supported template variables. It is a literal replace, not text/template, so the supported spelling is exactly "{{ .Root }}" and "{{ .Cache }}". Any residual "{{" fails closed rather than reaching a pod as a literal brace — a silently-unexpanded GOCACHE would send the build cache to a path named "{{.Cache}}/gobuild".

func FailureReasonWrittenForThePerson

func FailureReasonWrittenForThePerson(reason string) bool

FailureReasonWrittenForThePerson reports whether a Failed condition carrying this reason ALSO carries a message composed for the person who tried to start the session — plain words, and where possible a next step they can take.

It names the boot-refusal reasons only, and it is an allowlist because it has to be: most Failed messages are written for an operator and name pods, CR kinds, relations and retry budgets. A browser surface that forwarded every Failed message would put that vocabulary in front of the very person the fixed refusal copy exists to protect.

A reason is added here ONLY TOGETHER WITH ITS COPY — that is, only once the writer that fails the session passes a person-facing constant as the message — never because the reason string itself sounds user-facing. The two today, with the message each carries:

  • ReasonAgentSessionWorkshopLimitExceeded → WorkshopLimitBody
  • ReasonAgentSessionNotAnAllowedStarter → startRefusedBody (pkg/controllers/agentsession/start_gate.go), the fixed copy that refuseStart passes to markBootFailed in place of its operator detail

Deliberately absent: ReasonAgentSessionStartDenied and ReasonAgentSessionStartApprovalTimeout, whose messages are inline prose about the start-approval request rather than copy written for the person. Give either one a person-facing constant and it belongs here.

func InboundAttachmentCount

func InboundAttachmentCount(ann map[string]string) (count int, present bool)

InboundAttachmentCount reports how many attachments rode on the inbound message that created sess, and whether the annotation was present at all. A malformed value reads as absent: the annotation is a fence signal, and a value nothing wrote deliberately must not hold up the first turn.

func IsApprovalCondition

func IsApprovalCondition(t string) bool

IsApprovalCondition reports whether t is a channelsd-owned approval condition.

func IsCollectiveOwnership

func IsCollectiveOwnership(src OwnerSource) bool

IsCollectiveOwnership reports whether the owner is a POPULATION rather than a single person — the case a channel must state out loud, because the people affected did not individually agree to it and cannot see the SpiceDB tuple.

Both ownerless sources qualify. A permission subject-set is a group by construction, and an output-channel group is every member of that channel, growing silently as people join.

func IsCredentialUpdateRequestAwaitingHuman

func IsCredentialUpdateRequestAwaitingHuman(phase string) bool

IsCredentialUpdateRequestAwaitingHuman reports whether a request in this phase is still waiting on a person: a card is live for it (Open), or one is live for the identical credential and this request is riding on that answer (Collapsed).

It is the predicate the AgentSession controller parks on. Both phases park because the agent behind each is blocked identically — the meta tool waits on non-terminal, not on Open — so a collapsed request's session must read AwaitingCredentials too, not silently read Running while its runner is stuck.

Pending is deliberately excluded: nothing has been determined yet, and a request about to be refused outright must not flip its session through a park it leaves microseconds later.

It is deliberately NOT the negation of IsCredentialUpdateRequestTerminal: written that way, an unknown or typo'd phase would park a session forever with no card anywhere. As an allowlist it fails closed to "not waiting".

func IsCredentialUpdateRequestTerminal

func IsCredentialUpdateRequestTerminal(phase string) bool

IsCredentialUpdateRequestTerminal reports whether phase is one the meta tool's poll loop may stop on. Unknown and empty phases are NOT terminal, so a typo can never strand a blocked agent on a phase that will never resolve — the idle TTL remains the backstop.

func IsDelegatedChild

func IsDelegatedChild(sess *AgentSession, reachesSession func(kindName string) (bool, error)) (bool, error)

IsDelegatedChild reports whether this session is the CHILD half of a conversational delegation: it names a parent, and its own binding reaches that parent's session rather than a person.

Both halves are required and neither is sufficient. spec.parent alone also describes a single_turn child, which is headless (no binding at all) and was never channel-attached to begin with. A session-counterparty binding alone would describe any future agent-to-agent surface, including one a session holds for reasons that have nothing to do with being delegated to.

The caller decides what to do with the answer; the runner uses it to decide that agent_work_complete COMPLETES such a session instead of parking it Idle — see runner.Loop.DelegatedChild for why that distinction is load-bearing.

reachesSession answers the kind question, and it is REQUIRED for the same reason ResolveHumanDirectedBinding's predicate is: this package cannot import the channel-kind registry that knows the answer. Callers inside the module pass registry.AllowsSessionCounterparty. An error from it is returned rather than swallowed — a kind missing from a binary's blank imports is a wiring bug, and guessing either way here silently changes when a delegation completes.

func IsPendingApprovalCondition

func IsPendingApprovalCondition(t string) bool

IsPendingApprovalCondition reports whether t, when True, means the session is awaiting a human approval decision (a strict subset of IsApprovalCondition).

func IsSubagentMode

func IsSubagentMode(m string) bool

IsSubagentMode reports whether m is one of the four declared modes.

The empty string is NOT one of them. Empty is the absent-field default that EffectiveMode resolves; a caller validating a SUPPLIED value has to tell "unset" apart from "misspelled", because reading a misspelling as the default turns an operator's or an agent's mistake into a silently different delegation.

func IsWorkshopCloseWildcard

func IsWorkshopCloseWildcard(target string) bool

IsWorkshopCloseWildcard reports whether a WorkshopCloseRequest target is a wildcard ask — "every other live workshop of this workshop's own starter" — rather than a Workshop the request names.

func OwnerMayComeFromOutputChannel

func OwnerMayComeFromOutputChannel(ceiling *OwnerCeiling) bool

OwnerMayComeFromOutputChannel reports whether a class with this ownerCeiling permits a session's owner to be taken from the output channel's membership.

The ceiling is the admin's veto over what a Channel may declare, and ResolveOwnerSubject never sees it: the ceiling is enforced by refusing the CONFIG (the Channel controller rejects an explicit/ownerless policy under starterOnly), not at resolve time. A derived owner is never written into a Channel's spec, so it would slip past that refusal entirely — starterOnly would silently acquire a whole-channel owner, which is the precise thing it exists to forbid. Hence this gate, consulted by every caller that would supply an outputGroupRef.

  • starterOnly: the owner must resolve to the session's starting user, and a session that has no starter has no owner this ceiling admits.
  • fixed: the owner is pinned to a subject the admin named. Deriving a different one would override that; deriving the pinned one is a derivation nobody asked for. Neither: the Channel must say so itself.

A nil ceiling (the common case) permits it.

func PassthroughCredentialSecretName

func PassthroughCredentialSecretName(sessionName string) string

PassthroughCredentialSecretName is the deterministic name of the per-session Secret into which the operator projects a userPassthrough session's type=static credential VALUES, keyed by credential name. It lives in the session's own namespace (owned by the AgentSession), so a sandbox ToolCall's credential source resolves WITHIN the ToolCall's namespace — satisfying ToolCall.ValidateCredentialSourceNamespaces, which refuses a cross-namespace (confused-deputy) Secret reference. The session namespace holds only this session's projected credentials, so that boundary stays closed.

type=oauth / type=federated credentials are NOT projected here: their JIT refresh / ID-JAG mint must stay anchored on the master / IdP-identity Secret in the identities namespace (projecting a refresh-token copy would risk a double-spend against the useridentity refresh controller).

func ResolveStartedByCanonical

func ResolveStartedByCanonical(sess *AgentSession) identity.CanonicalUserID

ResolveStartedByCanonical returns the canonical id of the human who created sess, preferring the canonical the channel pipeline already stamped and falling back to a synthetic derived from the external id alone. Empty when sess carries neither — a kubectl-driven session has no human starter.

Use this, never a hand-rolled derivation. The two encodings are NOT interchangeable: the pipeline keys a starter with a channel-verified email by base64url(email), while a derivation with no email in hand can only produce the synthetic base64url(kind:teamScope:externalID) — a different subject for the same person. Substituting the synthetic loses the email irrecoverably, and silently: a subject with no "@" is the ordinary state for a guest or foreign-workspace user, so nothing downstream treats it as an error.

The fallback is correct where it applies — an email-less starter is INTENDED to be keyed by the synthetic subject — and wrong only as a substitute for a canonical that was already resolved.

func Resource

func Resource(resource string) schema.GroupResource

Resource returns the fully-qualified GroupResource for a given kind.

func RootNameFor

func RootNameFor(sess *AgentSession) string

RootNameFor returns the name of the delegation tree sess belongs to: sess's own LabelDelegationRoot value if it carries one, and sess's own name otherwise — a session with no label IS a root, by construction.

This is the induction step: buildChild calls RootNameFor(parent) to compute a new child's root with no API call and no walk, by reading the parent's label (or, when the parent is itself a root, the parent's name) once, at creation time. The same read applies to any session, not only a parent — DescendantsOf calls RootNameFor(sess) to resolve sess's own tree root the same way before listing.

func SplitRosterEntry

func SplitRosterEntry(entry string) (name, digest string, pinned bool)

SplitRosterEntry splits a spec.subagents entry into its class name and optional digest pin: "reviewer@sha256:<hex>" → ("reviewer", "sha256:<hex>", true).

func StartApprovalPending

func StartApprovalPending(sess *AgentSession) bool

StartApprovalPending reports whether sess is parked awaiting a platform admin's start_approval decision (AnnotationStartApprovalRequestRef present). The operator withholds the runner and owner resolution while this is true.

func StartApprovalRequestRef

func StartApprovalRequestRef(sess *AgentSession) string

StartApprovalRequestRef returns the RequestRef of the pending start_approval request sess is parked on, or "" when it is not parked.

func StartedByCanonical

func StartedByCanonical(sess *AgentSession) identity.CanonicalUserID

StartedByCanonical returns the BARE canonical SpiceDB subject (no "user:" prefix) of the human who created sess, read from AnnotationStartedByCanonicalID. The annotation stores the "user:<canonical>" PREFIXED form (see AnnotationStartedByCanonicalID's doc); this helper strips the prefix so callers that need the bare id (e.g. a SpiceDB object id, or identity.Principal comparisons) don't hand-roll TrimPrefix at each call site. Returns "" when the annotation is absent (kubectl-driven sessions). For the prefixed form, use StartedBySubject.

func StartedByEmail

func StartedByEmail(sess *AgentSession) identity.Email

StartedByEmail returns the verified email of the human who created sess, read from AnnotationStartedByEmail. Empty when the originating channel kind vouched for no email (see AnnotationStartedByEmail's doc).

func StartedByExternalID

func StartedByExternalID(sess *AgentSession) identity.RawExternalID

StartedByExternalID returns the kind-specific external identity (e.g. a Slack user_id) of the human who created sess, read from AnnotationStartedByExternalID. Empty when absent.

func StartedBySubject

func StartedBySubject(sess *AgentSession) identity.Subject

StartedBySubject returns the canonical SpiceDB subject, "user:<canonical>", of the human who created sess, read from AnnotationStartedByCanonicalID exactly as stored (no stripping). Use this at sites that compare against or emit the prefixed subject form; use StartedByCanonical for the bare id.

func SubagentModesAll

func SubagentModesAll() []string

SubagentModesAll lists the four declared modes, narrowest first: single_turn (no channel), task (one bounded parent question), then the two unbounded back-and-forth modes ordered by who is on the other end — chat (the parent agent) before attended (a human), since a human-directed conversation is the widest surface of the four. A function returning a fresh slice rather than an exported package-level var, so no caller can mutate the canonical list out from under the others.

func ToolchainRootFor

func ToolchainRootFor(name string) string

ToolchainRootFor returns the absolute mount root for a toolchain by name.

func UIServePending

func UIServePending(sess *AgentSession) bool

UIServePending reports whether an unconsumed UI-serve request is stamped on the session: the ui-serve-requested-at annotation is present and newer than status.lastUIServeAt (which the operator sets each time it acts on one).

The mirror of WakePending, deliberately down to the unparsable-annotation rule: a request we cannot order against the consumed marker is treated as no request, because the alternative is a spawn that fires on every reconcile forever.

A SEPARATE predicate over a SEPARATE annotation because the two requests differ in what they do to a conversation (see AnnotationUIServeRequestedAt): on one key, a dashboard's request would consume a pending message wake.

Says nothing about phase: pair it with WakeEligible, the same precondition both requests share.

func ValidateEnvDefaults

func ValidateEnvDefaults(env map[string]string, reserved []string) error

ValidateEnvDefaults rejects env keys matching a reserved env-var name. Reserved names are the toolkits' sensitive env vars, whose values the operator controls end to end from the matching AgentIdentity credential; a class setting one in EnvDefaults would shadow that value before the runner attaches it. The caller computes `reserved` from the toolkit registry, so this stays a pure, registry-free check.

Every offender is collected and sorted rather than returned on first hit: map order would otherwise vary the status-condition message per reconcile. The same applies to the other validators here.

func ValidatePrivateVolumes

func ValidatePrivateVolumes(pvs []PrivateVolume, mounts []SpiceboxMount) error

ValidatePrivateVolumes checks the PrivateVolumes slice for: empty name/mountPath, non-DNS-1123 names, non-absolute mount paths, names colliding with pod-builder-reserved volume names or with ConfigMap mount names from spec.mounts, mount paths colliding with reserved paths, and duplicate names or mount paths within the slice itself. Returns a single error listing every problem, or nil when clean.

func ValidateScratchSizes

func ValidateScratchSizes(r SpiceboxResources) error

ValidateScratchSizes checks the memory-backed /tmp and /work emptyDirs and the disk-backed /var/ap-cache emptyDir: each effective size must be positive.

It deliberately does NOT require tmpSize + workSize to fit inside the memory limit. An emptyDir's sizeLimit is a CAP, not a reservation: tmpfs pages are charged against the container's memory limit only as they are written, so a 64Mi-memory class with the default 50Mi/100Mi caps is valid — the memory limit simply binds first. Many shipped classes are exactly that shape.

That raising a cap buys nothing without raising memory too belongs in the field documentation, not in a validator that rejects working configurations.

func ValidateToolchainSpec

func ValidateToolchainSpec(name string, s SpiceboxToolchainSpec) error

ValidateToolchainSpec checks a SpiceboxToolchain spec in isolation: no CR reads, no registry lookups (the delivery Kind validates its own fields separately). Returns one error listing every problem, or nil when clean.

func ValidateWorkspaceSourceSpec

func ValidateWorkspaceSourceSpec(name string, s WorkspaceSourceSpec) error

ValidateWorkspaceSourceSpec performs structural validation of a WorkspaceSource spec (kind/locator present, scope paths safe, base size parseable). Driver-specific validation (that the kind is registered and the locator is well-formed for that kind) happens in the controller, which can import the driver registry; this apis-package function must not.

func WakeEligible

func WakeEligible(sess *AgentSession) bool

WakeEligible reports whether a parked AgentSession's runner pod has exited, so stamping the wake-requested-at annotation will make the operator respawn it. Idle and AwaitingRetry park with the pod gone. The other Resume phases keep a live or starting pod (Running/Pending) or wait on the credential-link flow (AwaitingCredentials), where a respawn would either duplicate a live runner or start one before its credential exists.

An archive-swept session also parks with its pod reaped, but at Succeeded — terminal-looking, yet asleep rather than finished. ArchivedBySweep is the one signal separating the two; gating on phase alone left such a session with no wake annotation, so channelsd routed the message in, no runner was ever respawned, and the turn stranded with no reply.

Lives here rather than in any one consumer because three packages must agree on it: channelsd decides whether to stamp, the operator decides whether to respawn, and the runner decides whether its own post-Idle wake request would be honored. A fourth copy of this switch is how they drift apart.

func WakePending

func WakePending(sess *AgentSession) bool

WakePending reports whether an unconsumed wake request is already stamped on the session: the wake-requested-at annotation is present and newer than status.lastWakeAt (which the operator sets to "now" each time it consumes one). An unparsable annotation is treated as no request — a wake we cannot order against lastWakeAt could otherwise fire on every reconcile forever.

Says nothing about phase: pair it with WakeEligible to decide whether the operator will act on the request.

func WalkAncestors

func WalkAncestors(
	ctx context.Context,
	r client.Reader,
	sess *AgentSession,
	visit func(*AgentSession) (stop bool, err error),
) error

WalkAncestors calls visit for sess and then for each ancestor in turn, stopping when visit returns stop=true, when a session has no parent, or when MaxLineageWalk hops have been taken (an error).

visit owns the decision to stop; WalkAncestors owns termination and the no-silent-errors contract. A failed Get while walking is an error, never "the chain ended" — a broken lineage link must surface rather than be mistaken for a legitimate root.

func WorkshopName

func WorkshopName(session string) string

WorkshopName is the Workshop CR's name for a session: deterministic, so the runner Role can pin it by resourceName and a restarted reconciler finds it.

func WorkshopNamespaceName

func WorkshopNamespaceName(uid types.UID) string

WorkshopNamespaceName derives the workshop namespace from the session UID: "ws-" + the first 12 hex characters (hyphens stripped). Short, DNS-safe, and collision-free for the lifetime that matters (a UID is unique per session object; a session re-created under the same name gets a new UID and so a fresh, empty workshop — never a predecessor's leftovers).

func WorkshopNamespacesFor

func WorkshopNamespacesFor(ctx context.Context, r client.Reader, root *AgentSession) ([]string, error)

WorkshopNamespacesFor resolves the workshop namespace(s) root itself provisioned — never a descendant's — so a caller can extend a root-namespace-scoped closure List (ListClosure) across the workshop boundary Task 2 introduced without ListClosure itself ever becoming a cluster-wide List.

A builder session gets AT MOST one Workshop CR, deterministically named WorkshopName(root.Name) in root.Namespace (workshop_types.go), so this is a single Get, never a List: the slice return is future-proofing for a root that could someday own more than one, not a sign this List's shape.

The Workshop CR must be genuinely CONTROLLER-OWNED by root (an AgentSession-kind owner ref whose UID equals root.UID) — the same test workshopIdentityFor's SECURITY note applies to a sidecar identity, applied here to a resource count instead: an unowned or foreign-owned same-named CR (a leftover from a deleted-and-recreated root under the same name, which mints a fresh UID and so a fresh workshop per WorkshopNamespaceName's own doc) must not have its live children silently pooled into an unrelated root's ceiling.

Returns (nil, nil) — not an error — when root has no Workshop, the found Workshop is not root's own, or the Workshop has not yet recorded a status.namespace (still Provisioning). Any of those simply means "root provisioned no workshop namespace to add", which is the ordinary case for every non-builder session and is not a failure.

func WorkshopServiceAccountName

func WorkshopServiceAccountName(session string) string

WorkshopServiceAccountName is the sidecar's ServiceAccount in the SESSION's namespace. The "-workshop-sa" suffix is load-bearing: the workshop webhook attributes it to the session the same way "-runner-sa" is attributed.

func WorkshopTokenSecretName

func WorkshopTokenSecretName(session string) string

WorkshopTokenSecretName is the per-session Secret holding the workshop bearer (key "token"). Covered by PerSessionSecretSuffixes ownership guards.

Types

type AgentClass

type AgentClass struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   AgentClassSpec   `json:"spec,omitempty"`
	Status AgentClassStatus `json:"status,omitempty"`
}

+kubebuilder:object:root=true +kubebuilder:resource:scope=Namespaced,categories={authzed,spicebox},shortName=agcls +kubebuilder:subresource:status +kubebuilder:printcolumn:name="Valid",type="string",JSONPath=".status.conditions[?(@.type=='Valid')].status" +kubebuilder:printcolumn:name="OAP",type="string",JSONPath=".status.oapInstall.version" +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +genclient

AgentClass is the reviewable template an agent is defined by: model, system prompt, tools, skills, identity, channels, budget and authz policy. It never runs on its own -- an AgentSession is one instantiation of it.

Namespaced. Reconciled by pkg/controllers/agentclass, which validates the spec and confirms referenced objects exist, then reports Valid=True/False. The AgentSession reconciler gates on Valid=True, so an invalid class fails a session closed rather than starting it degraded.

func (*AgentClass) DeepCopy

func (in *AgentClass) DeepCopy() *AgentClass

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentClass.

func (*AgentClass) DeepCopyInto

func (in *AgentClass) DeepCopyInto(out *AgentClass)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*AgentClass) DeepCopyObject

func (in *AgentClass) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

func (*AgentClass) EffectiveSessionInteractPermission

func (ac *AgentClass) EffectiveSessionInteractPermission() string

EffectiveSessionInteractPermission is the subject-set that governs agentsession#participant for sessions of this class: the authored spec.authz.session.interactPermission when there is one, otherwise the value the reconciler derived onto status.

THE accessor. An authored value always wins — derivation fills an absence, it never overrides an intent — and keeping that precedence in one place is what stops a consumer from reading only half of it and disagreeing about who may interact with a session.

func (*AgentClass) ReferencesSidecarToolbox

func (ac *AgentClass) ReferencesSidecarToolbox(name string) bool

ReferencesSidecarToolbox reports whether this class declares a SidecarToolbox ref equal to name.

It lives here, on the type, because two independently-reachable gates ask the same question about the agent-builder workshop and must answer it identically: the operator's ensureWorkshop (a cluster sanction buys nothing unless the class actually wires that sidecar in) and the browser start route's per-starter workshop cap (a class that creates no workshop must never be counted against it). A second copy of the loop is how the two come to disagree about which classes are workshop classes — the route refusing a start the operator would have allowed, or admitting one it will boot-fail.

A nil receiver reports false: no class declares nothing.

func (*AgentClass) StartGatePermission

func (ac *AgentClass) StartGatePermission() string

StartGatePermission names the agentclass permission a would-be starter must hold on this class, or "" when the class declares no allowlist and so has no start gate. Pure over spec.

func (*AgentClass) StarterSubjectSet

func (ac *AgentClass) StarterSubjectSet() string

StarterSubjectSet is the SpiceDB subject-set naming this class's explicit starters, "agentclass:<ns>/<name>#starter" — the value OnlyStartersInteract applies as the session interact permission. Computed from the object's own name so a renamed (bundle-prefixed) class still names itself.

type AgentClassList

type AgentClassList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []AgentClass `json:"items"`
}

+kubebuilder:object:root=true

func (*AgentClassList) DeepCopy

func (in *AgentClassList) DeepCopy() *AgentClassList

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentClassList.

func (*AgentClassList) DeepCopyInto

func (in *AgentClassList) DeepCopyInto(out *AgentClassList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*AgentClassList) DeepCopyObject

func (in *AgentClassList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type AgentClassMCPServerRef

type AgentClassMCPServerRef struct {
	// Name is the LLM-prefix; pattern [a-z0-9_-]{1,32}.
	Name string `json:"name"`
	// Ref names the MCPServer CR in the same namespace.
	Ref string `json:"ref"`
	// CredentialRemap remaps a tool-declared credential name to a
	// differently-named credential in the agent's identity catalog —
	// the opt-in escape hatch when two tools declare the same credential
	// name but need different tokens. Keyed by the tool's declared name.
	// +optional
	CredentialRemap map[string]string `json:"credentialRemap,omitempty"`
}

AgentClassMCPServerRef references an MCPServer CR in the same namespace.

func (*AgentClassMCPServerRef) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentClassMCPServerRef.

func (*AgentClassMCPServerRef) DeepCopyInto

func (in *AgentClassMCPServerRef) DeepCopyInto(out *AgentClassMCPServerRef)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type AgentClassSidecarToolboxRef

type AgentClassSidecarToolboxRef struct {
	// Name is the LLM-prefix; pattern [a-z0-9_-]{1,32}.
	Name string `json:"name"`
	// Ref names the SidecarToolbox CR in the same namespace.
	Ref string `json:"ref"`
}

AgentClassSidecarToolboxRef references a SidecarToolbox CR in the same namespace.

func (*AgentClassSidecarToolboxRef) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentClassSidecarToolboxRef.

func (*AgentClassSidecarToolboxRef) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type AgentClassSpec

type AgentClassSpec struct {
	// DisplayName is the human-friendly label surfaced to end users
	// (channel thread messages, approver prompts, audit logs). When
	// unset, callers fall back to the AgentClass's metadata.name.
	// Examples: "MarketingBot", "HubSpot Companies Agent".
	// +optional
	DisplayName string `json:"displayName,omitempty"`

	// Description is a human-readable summary surfaced in events / status.
	// +optional
	Description string `json:"description,omitempty"`

	// Model is optional: when omitted, the class inherits the resolved tier
	// default model (see status.effectiveSettings.model). At least one of the
	// class or a settings tier must supply a model before a session runs.
	// +optional
	Model *ModelConfig `json:"model,omitempty"`

	// Harness names the agent harness that runs this class's outer loop.
	// Absent means "ap-native", the built-in runner loop. A named harness
	// must be registered in the operator binary; an unknown value marks the
	// AgentClass Valid=False rather than failing at pod-create time.
	//
	// +optional
	// +kubebuilder:validation:MinLength=1
	Harness string `json:"harness,omitempty"`

	SystemPrompt PromptSource `json:"systemPrompt"`

	// AgentIdentity in same namespace; default for all bundles.
	// +optional
	AgentIdentity string `json:"agentIdentity,omitempty"`

	// IdentityMode selects how the agent's tool credentials are sourced.
	//
	//   - agent (default): tools use the credentials bound to
	//     spec.agentIdentity. Today's behavior.
	//   - userPassthrough: tools use the credentials of the user who
	//     started the session, drawn from that user's UserIdentity
	//     catalog. spec.agentIdentity is ignored for credentialed tools.
	//     The session parks in AwaitingCredentials until the starter has
	//     linked everything the agent needs.
	//   - ask: interactive. At session start, the initiating user is asked
	//     to choose between acting as the agent (spec.agentIdentity) or as
	//     themselves (userPassthrough). Requires spec.agentIdentity. The
	//     session parks in AwaitingIdentityChoice until the user answers.
	//   - dynamic: interactive. An isolated recommender LLM (configured via
	//     spec.identityRecommendation) proposes agent or userPassthrough
	//     for the session, but the initiating user always confirms — the
	//     recommendation is advisory only. Requires spec.agentIdentity and
	//     spec.identityRecommendation.prompt. Also parks in
	//     AwaitingIdentityChoice until confirmed.
	// +kubebuilder:validation:Enum=agent;userPassthrough;ask;dynamic
	// +kubebuilder:default=agent
	// +optional
	IdentityMode string `json:"identityMode,omitempty"`

	// CredentialLinkTimeout caps how long a userPassthrough session may sit
	// in AwaitingCredentials before the operator fails it. Measured from
	// max(parkedAt, lastInteractionAt). Default 30m.
	// +kubebuilder:default="30m"
	// +optional
	CredentialLinkTimeout *metav1.Duration `json:"credentialLinkTimeout,omitempty"`

	// IdentityRecommendation configures the dynamic-mode recommender LLM.
	// Required when identityMode=dynamic; ignored otherwise.
	// +optional
	IdentityRecommendation *IdentityRecommendationConfig `json:"identityRecommendation,omitempty"`

	// IdentityChoiceTimeout caps how long an ask|dynamic session may sit in
	// AwaitingIdentityChoice before the operator fails it. Measured from
	// status.identityChoiceParkedAt. Default 30m.
	// +kubebuilder:default="30m"
	// +optional
	IdentityChoiceTimeout *metav1.Duration `json:"identityChoiceTimeout,omitempty"`

	// ToolBundles is optional. An empty/missing list means the agent runs
	// with only meta tools (e.g. agent_work_complete). Plan 1 supports only
	// the empty case; Plan 2 wires sandbox tool dispatch.
	// +optional
	ToolBundles []ToolBundle `json:"toolBundles,omitempty"`

	// MCPServers references MCPServer CRs in the same namespace whose
	// allowlisted tools should be exposed to the LLM during a session.
	// +optional
	MCPServers []AgentClassMCPServerRef `json:"mcpServers,omitempty"`

	// CredentialExplanations lets the AgentClass explain, per credential,
	// why this agent needs it. Keyed by the FINAL (post-credentialRemap)
	// credential name. Optional; credentials without an entry fall back to
	// the LLM explainer (if configured) or a generic sentence for the why.
	// Title/description are always sourced from the tool/provider catalog,
	// never here.
	// +optional
	CredentialExplanations []CredentialExplanationSpec `json:"credentialExplanations,omitempty"`

	// SidecarToolboxes references SidecarToolbox CRs in the same namespace
	// whose tools should be exposed to the LLM. Each ref's Name is the
	// LLM-facing prefix; pattern [a-z0-9_-]{1,32}.
	// +optional
	SidecarToolboxes []AgentClassSidecarToolboxRef `json:"sidecarToolboxes,omitempty"`

	// WorkspaceSource optionally binds a single WorkspaceSource whose
	// materialized base seeds each session's /workspace as a per-session
	// copy-on-write overlay. The referenced CR must be Valid. A single source
	// per class (it seeds the one /workspace); multi-source is a later phase.
	// +optional
	WorkspaceSource *AgentClassWorkspaceSourceRef `json:"workspaceSource,omitempty"`

	// ToolSessionLog controls whether an interactive tool's parsed
	// stream events (e.g. Claude Code's stream-json) are persisted to
	// the tool_session memory Kind so `oap agent logs --follow-live`
	// can show them. The runner-to-channel (Slack) stream is
	// unaffected regardless.
	//   off        — do not persist.
	//   highSignal — persist tool_use/result events, skip text deltas.
	//   full       — persist every event.
	// +kubebuilder:validation:Enum=off;highSignal;full
	// +kubebuilder:default=highSignal
	// +optional
	ToolSessionLog string `json:"toolSessionLog,omitempty"`

	// Budget is optional: when omitted, the class inherits the resolved tier
	// default budget (see status.effectiveSettings.budget).
	// +optional
	Budget *BudgetConfig `json:"budget,omitempty"`

	// BoundEntities is the PRE-RENAME spelling of spec.authz.slots.
	//
	// Still deserialized on purpose. This is an AUTHZ field: dropping it from
	// the schema outright would make an upgraded AgentClass silently lose its
	// instance constraints — nothing would fail, the agent would just be less
	// constrained than its author wrote. Instead the reconciler rejects a class
	// that still sets it, naming the new field, so the loss is impossible to
	// miss. Loud beats silent when what is being dropped is a constraint.
	//
	// Deprecated: move to spec.authz.slots.
	// +optional
	// +listType=atomic
	BoundEntities []AuthzSlot `json:"boundEntities,omitempty"`

	// Channels governs channel-attached behavior. Optional — kubectl-driven
	// sessions ignore this entirely.
	// +optional
	Channels *ChannelsConfig `json:"channels,omitempty"`

	// Authz consolidates tool-call, session-interact, and information-leakage
	// authorization policy. Optional; absent means legacy defaults apply.
	// +optional
	Authz *AuthzBlock `json:"authz,omitempty"`

	// Skills lists the agent skills this class opts into. Subject to the
	// tiered AllowedSkills/DeniedSkills ceilings, which match on Ref; a
	// disallowed skill makes the class SettingsAccepted=False.
	// +optional
	Skills []AgentSkill `json:"skills,omitempty"`

	// Subagents is the closed set of AgentClass names this class may delegate
	// to, in its own namespace. Membership is checked at delegation time and the
	// whole graph is DAG-validated at admission, so a cycle is a validation
	// error rather than a runtime loop — the only kind of bound that holds.
	//
	// Absent or empty means this class delegates to nobody, which is the
	// default: delegation is opt-in.
	//
	// Membership alone permits single_turn delegation ONLY: a headless child
	// that takes spec.prompt in and hands status.result back. Widening a member
	// to one of the conversational modes is a separate, explicit declaration in
	// subagentModes, so a roster written before those modes existed cannot
	// silently gain the ability to spawn a child with a two-way channel.
	//
	// An entry may optionally carry a digest pin: "name@sha256:<64 lowercase hex>".
	// Pinned entries name the exact digest of the AgentClass to delegate to;
	// the SubagentRequest controller verifies the pin matches at delegation time.
	// Only a properly formed @sha256:<64 hex> suffix is recognized as a pin;
	// anything else stays part of the literal name (and the pattern below refuses it).
	// +optional
	// +listType=atomic
	// +kubebuilder:validation:items:Pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(@sha256:[a-f0-9]{64})?$`
	Subagents []string `json:"subagents,omitempty"`

	// SubagentModes raises the delegation-mode ceiling for individual roster
	// members, keyed by the AgentClass name exactly as it appears in subagents.
	// Values are SubagentRequest modes -- single_turn, task, chat -- documented
	// on SubagentRequestSpec.Mode, which is where what each one exposes is
	// described.
	//
	// The ceiling sits HERE, on the delegating parent, and not on the child's
	// own class: two parents may legitimately be trusted with different modes
	// for the same child.
	//
	// A delegating agent names the mode it wants in its delegate call, and the
	// SubagentRequest controller refuses any mode this map does not permit --
	// it may pick NARROWER than permitted, never wider, and a refusal is never
	// downgraded to single_turn and run anyway. single_turn is permitted for
	// every roster member without being listed, because it provisions strictly
	// less than the other two (no channel at all). Nothing else is implied:
	// chat does not imply task.
	//
	// A key naming a class absent from subagents, or a value that is not one of
	// the three modes, is a configuration error rather than a silent no-op --
	// it marks the class Valid=False with reason RosterInvalid. There is no
	// apiserver-level Enum on the values: controller-gen cannot put an items
	// enum inside a map's additionalProperties ("must apply
	// kubebuilder:validation:items:Enum to an array value, found object"), so
	// the AgentClass controller is the one authority on what a valid value is.
	// +optional
	SubagentModes map[string][]string `json:"subagentModes,omitempty"`

	// ToolGuard is this class's tool circuit-breaker / rate-limit rules,
	// consulted first in the tier walk (overrides namespace/cluster defaults
	// and the built-in rule for matching tools).
	// +optional
	ToolGuard *ToolGuardPolicy `json:"toolGuard,omitempty"`

	// Capabilities grants optional meta-tool capabilities to the agent. A key
	// present grants that capability; the value is its per-capability config
	// (may be empty {}). Default-on capabilities (planning, channel
	// interaction, …) are active without being listed; list one with
	// {"enabled": false} to disable it. Unknown or unavailable grants are
	// ignored gracefully and surfaced on status (CapabilitiesValid), never
	// blocking readiness.
	// +optional
	// +kubebuilder:pruning:PreserveUnknownFields
	Capabilities map[string]apiextensionsv1.JSON `json:"capabilities,omitempty"`

	// Config is a map of installer-bound values exposed to this class's
	// toolspec constraint CEL as the `config` root (see the toolspec
	// validator). It is generic and toolkit-agnostic: it never mentions repos
	// or any tool-specific shape — a constraint decides what a value means.
	// Each value is a JSON scalar or array. Validated against ConfigSchema by
	// the AgentClass controller; a config that violates its schema, or that a
	// bound toolspec's CEL references a key not declared here, marks the class
	// Valid=False rather than failing at tool-call time.
	// +optional
	// +kubebuilder:pruning:PreserveUnknownFields
	Config map[string]apiextensionsv1.JSON `json:"config,omitempty"`

	// ConfigSchema declares each Config key's contract, so Config can be
	// validated at reconcile and every `config.X` a bound toolspec constraint
	// references is known to exist. Mirrors the oap install-question shape, so a
	// bundle's install question maps 1:1 onto a key.
	// +optional
	ConfigSchema []ConfigKeySchema `json:"configSchema,omitempty"`

	// UserPreferences declares the per-user preferences this class offers.
	// Empty means the feature is absent for this class: no preferences tools
	// are offered and no preference endpoints are active.
	// +optional
	UserPreferences []UserPreferenceSchema `json:"userPreferences,omitempty"`

	// CompletionRequirements are the conditions a session of this class must
	// satisfy before its agent may declare a round of work complete. Each entry
	// is a key registered in the completion-requirement registry
	// (pkg/agent/completion) — "artifact-delivered", "plan-steps-complete",
	// "trigger-status-concluded".
	//
	// This is the OPERATOR's guarantee that no session of this class finishes
	// without producing what the class exists to produce, and it deliberately
	// does not depend on the agent choosing to declare anything: an agent that
	// forgets its own obligation is precisely the case it covers.
	//
	// NOT the same axis as a plan phase's `requires`, which orders phase ENTRY
	// and never reads an agent's assertion that it finished something. These
	// are read at exactly that assertion.
	//
	// An unmet requirement makes agent_work_complete refuse, naming what is
	// missing. The agent may still finish by supplying a `bypass_reason`, which
	// is recorded on status.completionBypasses and posted to the session's
	// channel — a bypass with no escape would turn a degraded result into a
	// wedged session, and one nobody sees would be an off switch.
	//
	// Not enum-validated: the registry is open by design, and a key this build
	// does not know fails closed at call time (refuse, name it, stay
	// bypassable) rather than being silently ignored at admission.
	// +optional
	// +listType=atomic
	// +kubebuilder:validation:items:Pattern=`^[a-z][a-z0-9-]*$`
	CompletionRequirements []string `json:"completionRequirements,omitempty"`

	// AgentUI references the AgentUI this class serves and carries the
	// DEPLOYMENT half of the tool grant. It lives here, not on AgentUI,
	// because the bundle author writes the request and the deployer writes the
	// authorization — different objects, so no two field managers contend.
	// Absent ⇒ no UI-callable tools.
	// +optional
	AgentUI *AgentClassUIGrant `json:"agentUI,omitempty"`
}

The emptiness test is size(...) > 0 and NOT `!= <two single quotes>`, which is the obvious spelling and does not survive the toolchain: gofmt applies typographic conversion to doc comments, and a kubebuilder marker attached to a type IS a doc comment — so a pair of single quotes is rewritten to a curly quote on the next gofmt, the CEL no longer lexes, and every CRD install fails. Keep this expression quote-free. TestCRDValidationRulesParse guards the class. +kubebuilder:validation:XValidation:rule="!(has(self.identityMode) && (self.identityMode == 'ask' || self.identityMode == 'dynamic')) || (has(self.agentIdentity) && size(self.agentIdentity) > 0)",message="spec.identityMode ask|dynamic requires spec.agentIdentity to be set" +kubebuilder:validation:XValidation:rule="!(has(self.identityMode) && self.identityMode == 'dynamic') || (has(self.identityRecommendation) && has(self.identityRecommendation.prompt))",message="spec.identityMode=dynamic requires spec.identityRecommendation.prompt"

func (*AgentClassSpec) DeepCopy

func (in *AgentClassSpec) DeepCopy() *AgentClassSpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentClassSpec.

func (*AgentClassSpec) DeepCopyInto

func (in *AgentClassSpec) DeepCopyInto(out *AgentClassSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*AgentClassSpec) GetAuthz

func (s *AgentClassSpec) GetAuthz() *AuthzBlock

GetAuthz returns Spec.Authz or a zero-value pointer; never returns nil.

func (*AgentClassSpec) GetOwnerCeiling

func (s *AgentClassSpec) GetOwnerCeiling() *OwnerCeiling

GetOwnerCeiling returns the class's OwnerCeiling or nil, nil-safe across an absent Authz block.

func (*AgentClassSpec) GetScope

func (s *AgentClassSpec) GetScope() *ScopeSpec

GetScope returns Spec.Authz.Scope or a zero-value pointer; never returns nil. Callers can read fields directly.

func (*AgentClassSpec) GetSlots

func (s *AgentClassSpec) GetSlots() []AuthzSlot

GetSlots returns the instance-axis slot declarations.

The ONE place any consumer reads them, and it deliberately does not fall back to the deprecated spec.boundEntities: a class still using the old spelling is rejected by the reconciler (ReasonBoundEntitiesRenamed), so falling back here would resurrect exactly the silent behavior that rejection exists to prevent. A reader that cannot reach the old field cannot accidentally honour it.

func (*AgentClassSpec) PermittedSubagentModes

func (s *AgentClassSpec) PermittedSubagentModes(name string) []string

PermittedSubagentModes returns the SubagentRequest modes this class may delegate to name in, single_turn first and declared widenings after it in declaration order.

Nil -- nothing permitted at all -- for a name that is not on the roster, so a mode check fails closed on its own rather than depending on a membership check having run first.

single_turn is always present for a roster member. It is the one mode that provisions strictly less than the others (no Channel at all), so it is what "the agent may choose narrower" means concretely. Nothing else is implied -- chat does not imply task -- because the difference between those two is an attack-surface declaration, and inferring one from the other would grant a surface the operator never wrote.

A declared mode that is not one of the three known values is DROPPED here rather than returned: an unrecognized string must never widen anything. It is not silently tolerated overall -- the AgentClass controller refuses the same value at validation time (Valid=False/RosterInvalid), which is where an operator sees it.

func (*AgentClassSpec) RosterNames

func (s *AgentClassSpec) RosterNames() []string

RosterNames returns the deduplicated roster in declaration order. Nil for a class that declares none. Pinned entries have their pins stripped.

func (*AgentClassSpec) RosterPins

func (s *AgentClassSpec) RosterPins() map[string][]string

RosterPins returns, per roster name, the distinct digest pins declared for it, in declaration order. A name with no pinned entry is absent. More than one distinct pin for a name is contradictory; the SubagentRequest controller refuses delegation to such a name rather than choosing one.

func (*AgentClassSpec) SlotPermissionSets

func (s *AgentClassSpec) SlotPermissionSets() map[string][]string

SlotPermissionSets maps each slot's resource type to every permission a grant on it may confer — the CEILING an approval may bind within.

The plural companion to SlotPermissions. Binding is filtered through this: an approval naming a permission the slot never declared has no slot_grant_<permission> relation in the composed schema, so writing it fails the WHOLE grant write at SpiceDB and the approval buys nothing at all — not even the permissions that were declared.

func (*AgentClassSpec) SlotPermissions

func (s *AgentClassSpec) SlotPermissions() map[string]string

SlotPermissions maps each declared slot resource type to the permission a grant on it confers.

Standing on a slot is a question about the PAIR, never the type alone: holding `read` on a repository says nothing about a slot whose grant confers `write`, and checking the type without its permission would call the second delegable because the approver happened to hold the first.

func (*AgentClassSpec) SlotResourceTypes

func (s *AgentClassSpec) SlotResourceTypes() []string

SlotResourceTypes lists the resource types this class opted into on the instance axis — what a plan phase's slot requests are validated against.

Nil means the class does not use the instance axis, so every slot a phase requests is undeclared and gets dropped at freeze. That direction is deliberate: a slot request becomes a SpiceDB grant when the plan is approved, and a class that never declared the type never opted into having grants written against it.

type AgentClassStatus

type AgentClassStatus struct {
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`
	// +patchStrategy=merge
	// +patchMergeKey=type
	// +optional
	Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`

	// BoundChannels lists Channels in the same namespace that target this
	// AgentClass. Reconciled by the AgentClass controller.
	// +optional
	BoundChannels []BoundChannelRef `json:"boundChannels,omitempty"`

	// AgentSessionGrantsRef names the owned AgentSessionGrants CR in the
	// same namespace. Empty until the AgentClass reconciler has resolved
	// tools and created/updated the CR.
	// +optional
	AgentSessionGrantsRef string `json:"agentSessionGrantsRef,omitempty"`

	// EffectiveSettings is the resolved 4-tier settings snapshot (cluster →
	// namespace → class). Stamped by the AgentClass reconciler.
	// +optional
	EffectiveSettings *EffectiveSettings `json:"effectiveSettings,omitempty"`

	// OapInstall records provenance for an AgentClass installed from a
	// `.oap` bundle: the source it came from, its content digest, and when
	// it was installed. Absent for AgentClasses created by other means
	// (kubectl apply, wizard, etc.).
	// +optional
	OapInstall *OapInstallStatus `json:"oapInstall,omitempty"`

	// ResolvedSlots publishes, per declared slot, how a value becomes the
	// SpiceDB object id — derived by the reconciler from the tools, never
	// authored. Anything writing a slot grant ahead of the call (thread
	// seeding, an approval, a class default) must mint the identical id, and
	// a second authored copy of the chain would drift silently: the grant
	// would be written, never matched, and nothing would error.
	// +optional
	// +listType=atomic
	ResolvedSlots []ResolvedSlot `json:"resolvedSlots,omitempty"`

	// ResolvedPermissionTitles publishes, per (resourceType, permission) pair,
	// the human phrase declared on the SpiceDBPermission that names it — so the
	// runner can render an approval card without re-reading every MCPServer and
	// SpiceboxToolkit schema fragment. An OBSERVATION: the reconciler derives
	// this from the tools, never authors it. A pair with no declared Title is
	// OMITTED rather than published with an empty one, so the card can tell
	// "nobody wrote a title" (detokenize the handle) from "somebody declared it
	// blank" (which this field never represents).
	// +optional
	// +listType=atomic
	ResolvedPermissionTitles []ResolvedPermissionTitle `json:"resolvedPermissionTitles,omitempty"`

	// ResolvedResourceDisplays publishes, per resource type, the declared
	// icon/label/name presentation — the sibling of ResolvedPermissionTitles
	// for RESOURCE INSTANCE lines instead of permission lines. An OBSERVATION:
	// the reconciler derives this from the tools, never authors it. A type
	// with no declared Display is OMITTED rather than published empty, so the
	// card can tell "nobody declared a display" (fall back to the wire type
	// name) from "somebody declared one with blank fields".
	// +optional
	// +listType=atomic
	ResolvedResourceDisplays []ResolvedResourceDisplay `json:"resolvedResourceDisplays,omitempty"`

	// ResolvedResourceStandings publishes, per RESOURCE TYPE, how approval for it
	// is governed: `required` plus the permission an approver must hold, or
	// `session-only` when nothing local governs it.
	//
	// Keyed by resource type rather than by slot, unlike ResolvedSlots[].Standing.
	// A tool's permission check can name a type the class never declared as a
	// slot, and the approval router needs an answer for THAT type too — with no
	// entry it must refuse rather than fall back, which is the whole point of
	// removing the default.
	// +optional
	// +listType=atomic
	ResolvedResourceStandings []ResolvedResourceStanding `json:"resolvedResourceStandings,omitempty"`

	// FactSources publishes, per fact this class's slot preconditions read,
	// which tool can record it and what gates that tool — so an author can see
	// that a precondition is answerable, and see it BEFORE a session sits on an
	// undetermined verdict with nothing anywhere reporting a fault.
	//
	// Deliberately a warning surface rather than a gate. The impossible case
	// admission CAN prove — every producer of a fact gated by the very slot the
	// precondition holds shut — is refused with Valid=False. A fact NOTHING
	// declares a producer for is permanently undetermined too, but it is
	// indistinguishable at admission from one a channel kind or an
	// out-of-class tool legitimately supplies, so it lands here as
	// `state: absent` instead of failing the class.
	//
	// Read each entry's `state`, not its prose: an empty producedBy is both
	// `absent` and `envelope`, and an empty gatedBy is both `ungated` and
	// `unresolved`.
	//
	// An OBSERVATION: derived by the reconciler from the slots and the tools,
	// written set-on-change. Never SSA-applied — a volatile field a client
	// applied would churn ownership on every reconcile.
	// +optional
	// +listType=atomic
	FactSources []FactSource `json:"factSources,omitempty"`

	// UserlessInput reports whether a session of this class can be BORN with no
	// human on it: at least one bound Channel with an inbound role (input or
	// both) is of a kind whose Kind.UserAttributable() is false AND whose
	// Kind.SpawnsSessionOnInbound() is true — github and bento today, a webhook
	// payload and a cron tick, neither of which names a person who asked for
	// anything.
	//
	// Both halves, because every rule below is about a session the inbound
	// BROUGHT INTO EXISTENCE. A kind that spawns nothing (`agent`, the
	// conversational-delegation transport the operator binds to a child's class
	// for the life of a SubagentRequest) carries no human either, but every
	// session on it was pre-created with its own attribution and its own
	// standing — so it is NOT counted here, and none of the rules fire for it.
	//
	// One fact, several unrelated-looking rules. There is no user to attribute
	// a session to (spec.authz.session.interactPermission becomes required, and
	// so does the Channel's own spec.authzSubject); with none declared, the
	// membership of the class's single role=output Channel is derived as the
	// effective one (status.derivedSessionInteractPermission) and written to
	// SpiceDB per session; there is no inbound message to reply to (a
	// role=output Channel must carry its own destination, since nothing inbound
	// supplies one); and a session with no human on any leg acts as the service
	// subject its Channel declared. Published as an OBSERVATION so each of those
	// consumers reads the answer rather than re-listing this class's Channels
	// and re-deriving the predicate — a second copy of the walk is a second
	// place for the next rule to be forgotten, and a second place for one
	// consumer to disagree with the others about which Channels count.
	//
	// Derived by the AgentClass reconciler from the same Channel list that
	// produces status.boundChannels, on every reconcile, BEFORE the Valid
	// condition is decided. So a consumer that has observed Valid=True on this
	// class has observed a derived value: `false` there means "derived false",
	// not "not yet derived".
	// +optional
	UserlessInput bool `json:"userlessInput,omitempty"`

	// DerivedSessionInteractPermission is the subject-set the reconciler chose
	// as this class's interact policy because the class DECLARED none and its
	// input carries no human — the membership of the single role=output
	// Channel bound to it (slack_channel:<id>#member for a Slack destination).
	//
	// Present only when the derivation actually fired: the class left
	// spec.authz.session.interactPermission empty, status.userlessInput is
	// true, and exactly one bound role=output Channel yielded a membership
	// subject-set. A class that DECLARED the field leaves this empty — an
	// authored value is never mirrored here, so a non-empty value always reads
	// as "nobody wrote this; it was derived, and here is from what".
	//
	// It exists because the alternative was a fresh webhook-driven install
	// sitting at Valid=False until a human hand-patched the one value the
	// install already knew: the Slack channel the agent posts into is
	// per-install, so no bundle can carry it, and the people in that channel
	// are exactly the people who may interact with the sessions it announces.
	// Published on status rather than left implicit so an operator can see
	// WHICH subject-set is in force, and that it was not something they wrote,
	// without reading code.
	// +optional
	DerivedSessionInteractPermission string `json:"derivedSessionInteractPermission,omitempty"`
}

func (*AgentClassStatus) DeepCopy

func (in *AgentClassStatus) DeepCopy() *AgentClassStatus

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentClassStatus.

func (*AgentClassStatus) DeepCopyInto

func (in *AgentClassStatus) DeepCopyInto(out *AgentClassStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type AgentClassUIGrant

type AgentClassUIGrant struct {
	// Ref is the AgentUI name in the same namespace.
	Ref string `json:"ref"`

	// GrantedTools authorizes these tool names for direct browser invocation.
	// This is the ONLY place a deployment says yes. Note the asymmetry that
	// makes it matter: a readonly tool auto-runs at click frequency with no
	// human in the loop, while a side-effecting one already takes an approval
	// on every call — so this grant carries the most weight for readonly tools.
	//
	// The item pattern is the SAME one AgentUI.spec.tools carries — see that
	// field for why both are pinned to synthesize.NormalizeName's output
	// alphabet. A grant and a request that cannot be spelled differently
	// cannot silently disagree. As there: this holds for objects written AFTER
	// this upgrade only, since a CRD pattern is not retroactive, so the
	// runner's normalize step MUST NOT be deleted as redundant.
	// +kubebuilder:validation:items:Pattern=`^[a-z0-9_-]{1,128}$`
	// +optional
	GrantedTools []string `json:"grantedTools,omitempty"`
}

AgentClassUIGrant authorizes browser-reachable tool calls for one AgentUI.

GrantedTools is a deliberate act by whoever deploys the class. It is applied as a pure function of that decision — no wall-clock, no counters — so a byte-identical re-apply is an SSA no-op.

func (*AgentClassUIGrant) DeepCopy

func (in *AgentClassUIGrant) DeepCopy() *AgentClassUIGrant

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentClassUIGrant.

func (*AgentClassUIGrant) DeepCopyInto

func (in *AgentClassUIGrant) DeepCopyInto(out *AgentClassUIGrant)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type AgentClassWorkspaceSourceRef

type AgentClassWorkspaceSourceRef struct {
	// Ref names the WorkspaceSource CR in the AgentClass's namespace.
	Ref string `json:"ref"`
}

AgentClassWorkspaceSourceRef references a WorkspaceSource CR in the same namespace.

func (*AgentClassWorkspaceSourceRef) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentClassWorkspaceSourceRef.

func (*AgentClassWorkspaceSourceRef) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type AgentCredential

type AgentCredential struct {
	// Name is the credential's unique identifier. It is matched against the
	// credential names a toolkit/MCPServer declares to inject the resolved
	// value at runtime.
	Name string `json:"name"`

	// Type discriminates which of the blocks below is populated.
	// +kubebuilder:validation:Enum=static;oauth;federated;githubApp
	Type string `json:"type"`

	// Static is populated when type=static.
	// +optional
	Static *StaticCredentialSource `json:"static,omitempty"`

	// OAuth is populated when type=oauth. The referenced Secret holds
	// access_token, refresh_token, expires_at, token_endpoint, client_id,
	// scope as separate keys. Key names are fixed; not configurable.
	// +optional
	OAuth *OAuthCredentialSource `json:"oauth,omitempty"`

	// Federated is populated when type=federated. The credential is MINTED
	// on demand via ID-JAG from the user's enterprise identity — there is
	// no stored access token. Valid ONLY on UserIdentity /
	// SessionUserIdentity; rejected on AgentIdentity (bot identities have no
	// human subject to assert).
	// +optional
	Federated *FederatedCredentialSource `json:"federated,omitempty"`

	// GitHubApp is populated when type=githubApp.
	// +optional
	GitHubApp *GitHubAppCredentialSource `json:"githubApp,omitempty"`

	// AllowedHosts scopes where this credential may be sent, as host patterns
	// ("github.com", "*.internal.example"). Matching is on the URL authority
	// (host and port), case-insensitively; a leading "*." matches one or more
	// leading labels but never the apex, so an author who wants both lists
	// both.
	//
	// It exists because destination and credential were independent fields on
	// the same tenant-writable object with nothing tying them together: a
	// SkillSource naming spec.repoURL: "https://attacker.example/x.git" and
	// spec.auth pointing at a production PAT made the operator send that PAT to
	// that host as an HTTP Basic password, reading the Secret with its own
	// cluster-wide credentials so the actor never needed `get secrets`.
	//
	// EMPTY MEANS UNSCOPED, which is the only reading that does not break every
	// existing credential on upgrade. That makes the field opt-in, so a caller
	// with a stricter default enforces it itself rather than relying on this:
	// the SkillSource controller refuses an unscoped credential for a clone
	// outright, because there is no safe default destination for one.
	//
	// +optional
	AllowedHosts []string `json:"allowedHosts,omitempty"`
}

func (*AgentCredential) DeepCopy

func (in *AgentCredential) DeepCopy() *AgentCredential

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentCredential.

func (*AgentCredential) DeepCopyInto

func (in *AgentCredential) DeepCopyInto(out *AgentCredential)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type AgentIdentity

type AgentIdentity struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   AgentIdentitySpec   `json:"spec,omitempty"`
	Status AgentIdentityStatus `json:"status,omitempty"`
}

+kubebuilder:object:root=true +kubebuilder:resource:scope=Namespaced,categories={authzed,spicebox},shortName=agid +kubebuilder:subresource:status +kubebuilder:printcolumn:name="Valid",type="string",JSONPath=".status.conditions[?(@.type=='Valid')].status" +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +genclient

AgentIdentity is the named credential set an agent acts as: reusable static and OAuth credential descriptors that toolkits and MCP servers request by name. It carries references to Secrets, never token bytes.

Namespaced. Reconciled by pkg/controllers/agentidentity -- one reconciler for validity (every referenced Secret + key must exist), one for proactive OAuth refresh. type=federated is rejected here: those credentials are minted on demand from a human enterprise identity, so they only ever live on a UserIdentity or SessionUserIdentity.

func (*AgentIdentity) DeepCopy

func (in *AgentIdentity) DeepCopy() *AgentIdentity

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentIdentity.

func (*AgentIdentity) DeepCopyInto

func (in *AgentIdentity) DeepCopyInto(out *AgentIdentity)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*AgentIdentity) DeepCopyObject

func (in *AgentIdentity) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type AgentIdentityList

type AgentIdentityList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []AgentIdentity `json:"items"`
}

+kubebuilder:object:root=true

func (*AgentIdentityList) DeepCopy

func (in *AgentIdentityList) DeepCopy() *AgentIdentityList

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentIdentityList.

func (*AgentIdentityList) DeepCopyInto

func (in *AgentIdentityList) DeepCopyInto(out *AgentIdentityList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*AgentIdentityList) DeepCopyObject

func (in *AgentIdentityList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type AgentIdentitySpec

type AgentIdentitySpec struct {
	// Description is a human-readable summary surfaced in events / status.
	// +optional
	Description string `json:"description,omitempty"`

	// Credentials are reusable named credential descriptors. Each has a `type`
	// discriminator and exactly one type-specific block populated.
	// +optional
	Credentials []AgentCredential `json:"credentials,omitempty"`

	// RefreshThreshold overrides the operator-wide default refresh
	// threshold for this AgentIdentity. type=oauth credentials whose
	// expires_at is within this duration are refreshed proactively.
	// No effect on type=static credentials.
	// +optional
	RefreshThreshold *metav1.Duration `json:"refreshThreshold,omitempty"`
}

+kubebuilder:validation:XValidation:rule="!has(self.credentials) || !self.credentials.exists(c, c.type == 'federated')",message="spec.credentials: type=federated is passthrough-only and not permitted on an AgentIdentity"

func (*AgentIdentitySpec) DeepCopy

func (in *AgentIdentitySpec) DeepCopy() *AgentIdentitySpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentIdentitySpec.

func (*AgentIdentitySpec) DeepCopyInto

func (in *AgentIdentitySpec) DeepCopyInto(out *AgentIdentitySpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type AgentIdentityStatus

type AgentIdentityStatus struct {
	// ObservedGeneration is the spec generation this status reflects.
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`

	// ResolvedCredentials lists credential names whose Secret + key resolved.
	// +optional
	ResolvedCredentials []string `json:"resolvedCredentials,omitempty"`

	// LastSetupAt is the most recent timestamp at which any credential
	// on this identity was created or updated by the setup CLI.
	// +optional
	LastSetupAt *metav1.Time `json:"lastSetupAt,omitempty"`

	// LastRefreshAt is the most recent successful OAuth refresh
	// performed by the refresh controller.
	// +optional
	LastRefreshAt *metav1.Time `json:"lastRefreshAt,omitempty"`

	// ObservedCredentials is the durable trigger state for in-flight credential
	// revocation: the credential set the operator has already published revokes
	// for. The reconciler diffs spec.credentials against it, so a removal or
	// replacement committed while the operator was down — or one whose publish
	// failed — is still emitted on the next reconcile, rather than living only
	// in a process-scoped map that dies with the pod.
	//
	// Operator-owned and derived: a pure function of spec.credentials, sorted
	// by name, so an unchanged spec re-writes nothing. An entry whose revoke
	// failed to publish deliberately keeps its PREVIOUS value, which is what
	// makes the next reconcile re-derive and re-emit it.
	// +optional
	ObservedCredentials []ObservedCredential `json:"observedCredentials,omitempty"`

	// Conditions carries Valid, Refresh and PlatformLinked.
	// +patchStrategy=merge
	// +patchMergeKey=type
	// +optional
	Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}

func (*AgentIdentityStatus) DeepCopy

func (in *AgentIdentityStatus) DeepCopy() *AgentIdentityStatus

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentIdentityStatus.

func (*AgentIdentityStatus) DeepCopyInto

func (in *AgentIdentityStatus) DeepCopyInto(out *AgentIdentityStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type AgentInjection

type AgentInjection struct {
	// Injected lists the env keys that were materialized from credentials,
	// each with a masked sample of the value. The masked field is
	// defence-in-depth for casual inspection — anyone with `get` on this
	// ToolCall already has access to the same namespace's Secrets.
	Injected []InjectedEnvKey `json:"injected"`
}

func (*AgentInjection) DeepCopy

func (in *AgentInjection) DeepCopy() *AgentInjection

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentInjection.

func (*AgentInjection) DeepCopyInto

func (in *AgentInjection) DeepCopyInto(out *AgentInjection)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type AgentResult

type AgentResult struct {
	// Summary is the agent's own account of what it did.
	Summary string `json:"summary"`
	// Artifacts are the durable outputs the agent produced.
	// +optional
	Artifacts []ResultArtifact `json:"artifacts,omitempty"`
}

AgentResult is what the agent reported on finishing successfully.

func (*AgentResult) DeepCopy

func (in *AgentResult) DeepCopy() *AgentResult

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentResult.

func (*AgentResult) DeepCopyInto

func (in *AgentResult) DeepCopyInto(out *AgentResult)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type AgentSession

type AgentSession struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   AgentSessionSpec   `json:"spec,omitempty"`
	Status AgentSessionStatus `json:"status,omitempty"`
}

+kubebuilder:object:root=true +kubebuilder:resource:scope=Namespaced,categories={authzed,spicebox},shortName=agses +kubebuilder:subresource:status +kubebuilder:printcolumn:name="Class",type="string",JSONPath=".spec.class" +kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase" +kubebuilder:printcolumn:name="Turns",type="integer",JSONPath=".status.progress.turnCount" +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +genclient

AgentSession is one running instance of an AgentClass: a conversation with its own runner Pod, budget, channel bindings and lifecycle phase. Sessions are disposable; the class they name is the durable definition.

Namespaced. Reconciled by pkg/controllers/agentsession, which gates on the class being Valid, provisions the per-session ServiceAccount, RBAC and Secrets, and owns the runner Pod. Status is written by more than one actor, so every writer goes through pkg/controllers/agentstatus rather than a bare status update.

func DescendantsOf

func DescendantsOf(ctx context.Context, r client.Reader, sess *AgentSession) ([]AgentSession, error)

DescendantsOf returns every session in sess's own subtree: strict descendants of sess, excluding sess itself, its ancestors, and any other branch of the same tree. Unlike ListClosure, which returns an entire tree given its root's name, DescendantsOf narrows a tree-wide closure down to one member's own subtree — for a hold cascade or a ceiling scoped to a mid-tree delegation rather than the whole tree.

It resolves sess's tree root via RootNameFor, lists that whole closure via ListClosure, then keeps only members whose ancestor chain passes through sess — using WalkAncestors for the containment test rather than a fourth hand-rolled walk. A member whose walk errors propagates the error immediately rather than being silently excluded: dropping a session from a hold cascade because its ancestor walk happened to fail would defeat the cascade's entire purpose.

func ListClosure

func ListClosure(ctx context.Context, r client.Reader, ns, rootName string) ([]AgentSession, error)

ListClosure returns every session in namespace ns labelled as a member of the delegation tree rooted at rootName.

It returns DESCENDANTS ONLY. The root itself is never labelled with its own name — RootNameFor's whole point is that a root needs no label — so the root is never a member of this list. A caller counting the whole tree (the pooled agent ceiling in particular) must add one for the root itself.

func ResolveRoot

func ResolveRoot(ctx context.Context, r client.Reader, sess *AgentSession) (*AgentSession, error)

ResolveRoot returns the topmost ancestor of sess — sess itself when it has no parent. It is the identity of the delegation tree: the pooled budget, the total-agent ceiling and the closure-aware denial streak are all properties of the root rather than of any individual member.

func (*AgentSession) DeepCopy

func (in *AgentSession) DeepCopy() *AgentSession

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentSession.

func (*AgentSession) DeepCopyInto

func (in *AgentSession) DeepCopyInto(out *AgentSession)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*AgentSession) DeepCopyObject

func (in *AgentSession) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

func (*AgentSession) ParentRef

func (s *AgentSession) ParentRef() (NamespacedRef, bool)

ParentRef returns the delegating session, if this session was delegated. Use this rather than reading Spec.Parent directly, so the nil check lives in one place.

type AgentSessionGrants

type AgentSessionGrants struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   AgentSessionGrantsSpec   `json:"spec,omitempty"`
	Status AgentSessionGrantsStatus `json:"status,omitempty"`
}

AgentSessionGrants is the per-AgentClass declaration of the (resourceType, permission) pairs that must exist as grant_<perm>_<resType> relations on the SpiceDB "agentsession" definition.

Namespaced. Reconciled by pkg/controllers/guardian, which watches every AgentSessionGrants cluster-wide and composes their union -- together with the schema fragments contributed by MCPServer and SpiceDBBootstrap -- into a single agentsession schema block. No individual CR owns that block; the composition of all of them does, so a lone CR read in isolation does not tell you what SpiceDB actually enforces.

func (*AgentSessionGrants) DeepCopy

func (in *AgentSessionGrants) DeepCopy() *AgentSessionGrants

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentSessionGrants.

func (*AgentSessionGrants) DeepCopyInto

func (in *AgentSessionGrants) DeepCopyInto(out *AgentSessionGrants)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*AgentSessionGrants) DeepCopyObject

func (in *AgentSessionGrants) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type AgentSessionGrantsList

type AgentSessionGrantsList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []AgentSessionGrants `json:"items"`
}

+kubebuilder:object:root=true

func (*AgentSessionGrantsList) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentSessionGrantsList.

func (*AgentSessionGrantsList) DeepCopyInto

func (in *AgentSessionGrantsList) DeepCopyInto(out *AgentSessionGrantsList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*AgentSessionGrantsList) DeepCopyObject

func (in *AgentSessionGrantsList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type AgentSessionGrantsSpec

type AgentSessionGrantsSpec struct {
	// Pairs is the unique set of (resourceType, permission) tuples this
	// class needs as grant relations. Order is normalized at write time.
	// +listType=map
	// +listMapKey=resourceType
	// +listMapKey=permission
	Pairs []GrantPair `json:"pairs,omitempty"`

	// Slots is the set of (resourceType, permission) tuples this class declares
	// through authz.slots — the INSTANCE axis.
	//
	// Shape-identical to Pairs and semantically its mirror image, which is why
	// it is a separate field rather than more entries in the same list. A Pair
	// makes the composer put a grant relation on the SESSION pointing at the
	// resource; a Slot makes it put one on the RESOURCE pointing at the session.
	// Collapsing them would lose exactly the distinction that decides whether
	// the resulting check is per-requester.
	// +listType=map
	// +listMapKey=resourceType
	// +listMapKey=permission
	// +optional
	Slots []GrantPair `json:"slots,omitempty"`
}

func (*AgentSessionGrantsSpec) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentSessionGrantsSpec.

func (*AgentSessionGrantsSpec) DeepCopyInto

func (in *AgentSessionGrantsSpec) DeepCopyInto(out *AgentSessionGrantsSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type AgentSessionGrantsStatus

type AgentSessionGrantsStatus struct {
	// Conditions carries SchemaIncluded.
	// +patchStrategy=merge
	// +patchMergeKey=type
	// +optional
	Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`

	// ObservedPairCount echoes len(spec.pairs) at the last observation.
	// +optional
	ObservedPairCount int32 `json:"observedPairCount,omitempty"`

	// ObservedSchemaWrittenAt records when the guardian controller last
	// confirmed (or wrote) a SpiceDB schema that includes these pairs.
	// +optional
	ObservedSchemaWrittenAt *metav1.Time `json:"observedSchemaWrittenAt,omitempty"`
}

func (*AgentSessionGrantsStatus) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentSessionGrantsStatus.

func (*AgentSessionGrantsStatus) DeepCopyInto

func (in *AgentSessionGrantsStatus) DeepCopyInto(out *AgentSessionGrantsStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type AgentSessionList

type AgentSessionList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []AgentSession `json:"items"`
}

+kubebuilder:object:root=true

func (*AgentSessionList) DeepCopy

func (in *AgentSessionList) DeepCopy() *AgentSessionList

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentSessionList.

func (*AgentSessionList) DeepCopyInto

func (in *AgentSessionList) DeepCopyInto(out *AgentSessionList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*AgentSessionList) DeepCopyObject

func (in *AgentSessionList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type AgentSessionProgress

type AgentSessionProgress struct {
	// TurnCount is how many agent turns have completed.
	// +optional
	TurnCount int32 `json:"turnCount,omitempty"`
	// InputTokens is cumulative prompt tokens sent to the provider.
	// +optional
	InputTokens int64 `json:"inputTokens,omitempty"`
	// OutputTokens is cumulative completion tokens returned by the provider.
	// +optional
	OutputTokens int64 `json:"outputTokens,omitempty"`
	// CacheCreationTokens is cumulative cache-write tokens (priced ~1.25x input).
	// +optional
	CacheCreationTokens int64 `json:"cacheCreationTokens,omitempty"`
	// CacheReadTokens is cumulative cache-read tokens (priced ~0.1x input).
	// +optional
	CacheReadTokens int64 `json:"cacheReadTokens,omitempty"`
	// ToolCallCount is how many tool calls the agent has issued.
	// +optional
	ToolCallCount int32 `json:"toolCallCount,omitempty"`
}

AgentSessionProgress accumulates usage counters for the CURRENT runner pod; they reset when the pod is replaced.

func (*AgentSessionProgress) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentSessionProgress.

func (*AgentSessionProgress) DeepCopyInto

func (in *AgentSessionProgress) DeepCopyInto(out *AgentSessionProgress)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type AgentSessionSpec

type AgentSessionSpec struct {
	// Class names the AgentClass in the same namespace.
	Class string `json:"class"`

	// Prompt is the initial user message. Immutable after the session
	// transitions out of Pending.
	Prompt PromptSource `json:"prompt"`

	// AgentIdentity overrides AgentClass.spec.agentIdentity; a per-bundle
	// agentIdentity still wins over this.
	// +optional
	AgentIdentity string `json:"agentIdentity,omitempty"`

	// Budget optionally narrows the AgentClass budget. Each dimension must
	// be <= the AgentClass cap.
	// +optional
	Budget *BudgetConfig `json:"budget,omitempty"`

	// InputChannel records the channel-driven origin — the Channel
	// whose listener (or scheduler, for bento) produced the inbound
	// message that created this session. Set by channelsd at session
	// creation; nil for kubectl-driven sessions.
	// +optional
	InputChannel *ChannelBinding `json:"inputChannel,omitempty"`

	// OutputChannel records the Channel the session's outbound
	// messages route through. Nil means "same as InputChannel" —
	// the existing single-Channel-per-session case (Slack role=both).
	// +optional
	OutputChannel *ChannelBinding `json:"outputChannel,omitempty"`

	// ForkedFrom names the prior AgentSession (same namespace) this
	// session was forked from via Restart-from-here. Immutable.
	// Distinct from ChannelBinding.InheritFrom (set on auto-inherit
	// after archive); ForkedFrom is set on explicit user-triggered
	// restart.
	// +optional
	ForkedFrom string `json:"forkedFrom,omitempty"`

	// ForkedAtTurn is the index of the last turn copied from
	// ForkedFrom (inclusive). The new user text became turn
	// ForkedAtTurn+1.
	// +optional
	ForkedAtTurn *int32 `json:"forkedAtTurn,omitempty"`

	// Parent is the session that delegated this one. Set ONLY by the
	// SubagentRequest controller; a child is never self-declared.
	//
	// Distinct from ForkedFrom: a fork REPLACES its parent and outlives it, so
	// it gets fresh SpiceDB tuples. A delegated child is SUBORDINATE to a live
	// parent, so its standing resolves through a live `agentsession#parent`
	// arrow and dies when the parent's does.
	//
	// Immutable: re-parenting would silently move a running session's approval
	// and transcript-read standing to a different set of humans.
	// +optional
	Parent *NamespacedRef `json:"parent,omitempty"`
}

The rule pins parent on UPDATE only. It must compare, not merely test presence: a rule that only refuses REMOVING parent still lets it be repointed at a different session, which silently moves a running session's approval and transcript-read standing to a different set of humans. +kubebuilder:validation:XValidation:rule="!has(oldSelf.parent) || (has(self.parent) && self.parent == oldSelf.parent)",message="spec.parent is immutable once set"

func (*AgentSessionSpec) DeepCopy

func (in *AgentSessionSpec) DeepCopy() *AgentSessionSpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentSessionSpec.

func (*AgentSessionSpec) DeepCopyInto

func (in *AgentSessionSpec) DeepCopyInto(out *AgentSessionSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type AgentSessionStartFailure

type AgentSessionStartFailure struct {
	// Reason is the machine-readable failure code (mirrors the constants used
	// for AgentSession status conditions, e.g. ReasonAgentSessionAuthzWriteFail).
	Reason string `json:"reason"`
	// Message is the optional human-readable description of the failure.
	// +optional
	Message string `json:"message,omitempty"`
}

AgentSessionStartFailure carries the channelsd-owned start-failure signal. Set by channelsd when a terminal pre-start failure is detected (e.g. an authz-write failure on session creation). The operator reads it and drives the Failed state; channelsd never writes phase/failureReason/finishedAt.

func (*AgentSessionStartFailure) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentSessionStartFailure.

func (*AgentSessionStartFailure) DeepCopyInto

func (in *AgentSessionStartFailure) DeepCopyInto(out *AgentSessionStartFailure)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type AgentSessionStatus

type AgentSessionStatus struct {
	// ObservedGeneration is the spec generation this status reflects.
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`
	// Phase is the lifecycle phase: Pending, Running, Idle, one of the
	// Awaiting* park states, Succeeded, or Failed.
	// +optional
	Phase string `json:"phase,omitempty"`
	// StartedAt is when the session first left Pending.
	// +optional
	StartedAt *metav1.Time `json:"startedAt,omitempty"`
	// FinishedAt is when the session reached a terminal phase.
	// +optional
	FinishedAt *metav1.Time `json:"finishedAt,omitempty"`

	// RunDuration is the session's cumulative ACTIVE run-time (excludes time
	// parked waiting on a human), the accumulator behind budget.maxDuration. The
	// runner seeds a new pod from this and flushes it back before the pod can
	// sleep, so run-time survives the sleep/resume boundary. Unlike
	// status.progress (which resets per pod), this is monotonic across the
	// whole session.
	// +optional
	RunDuration *metav1.Duration `json:"runDuration,omitempty"`

	// LastWakeAt records the most-recent wake-requested-at the operator has
	// acted on. Compared against the metadata annotation to detect new
	// wake-up requests.
	// +optional
	LastWakeAt *metav1.Time `json:"lastWakeAt,omitempty"`

	// LastUIServeAt records the most-recent ui-serve-requested-at the operator
	// has acted on, as LastWakeAt does for a conversation wake.
	//
	// Deliberately a SEPARATE marker: a conversation wake resumes the agent
	// loop while a UI-serve spawn never does, so sharing one would let a
	// dashboard request consume a pending message wake — the user's message
	// silently never delivered — or a page load be answered by an agent turn
	// nobody asked for.
	// +optional
	LastUIServeAt *metav1.Time `json:"lastUIServeAt,omitempty"`

	// LastIdleAt records when the session most recently entered phase=Idle.
	// Stamped by the AgentSession reconciler on Idle entry; cleared on
	// Idle → Running. Drives the operator's archive sweep deadline.
	// +optional
	LastIdleAt *metav1.Time `json:"lastIdleAt,omitempty"`

	// SleptAt is set when an Idle session's pods have been reaped (scaled to
	// zero) and cleared when the session is woken. Distinct from LastIdleAt
	// (which marks Idle entry): SleptAt marks that the compute footprint is
	// gone. Diagnostic + drives the reap decision's "already slept" guard.
	// +optional
	SleptAt *metav1.Time `json:"sleptAt,omitempty"`

	// RunnerPodName is the runner Pod currently backing the session; empty
	// once the pod has been reaped.
	// +optional
	RunnerPodName string `json:"runnerPodName,omitempty"`
	// RunnerRestarts counts container restarts observed on the runner Pod.
	// +optional
	RunnerRestarts int32 `json:"runnerRestarts,omitempty"`

	// RetryAttempts counts the number of times the runner has entered
	// AwaitingRetry (provider-error). Bumped by the runner's
	// WriteAwaitingRetry; never reset. Used by channelsd's session_watcher
	// as a dedup key for posting the Retry button (uid+RetryAttempts) so
	// each re-entry posts exactly one button.
	// +optional
	RetryAttempts int32 `json:"retryAttempts,omitempty"`

	// BundleSessions is one entry per tool bundle the AgentSession reconciler
	// provisioned a SpiceboxSession for. Empty when the class declares none.
	// +optional
	BundleSessions []ResolvedBundle `json:"bundleSessions,omitempty"`

	// ResolvedSidecarToolboxes snapshots each referenced SidecarToolbox at
	// session start, frozen for the session lifetime so a mid-flight CR edit
	// cannot change an in-flight session's runtime contract. Empty when
	// AgentClass.spec.sidecarToolboxes is empty.
	// +optional
	ResolvedSidecarToolboxes []ResolvedSidecarToolbox `json:"resolvedSidecarToolboxes,omitempty"`

	// ResolvedWorkspaceSource snapshots the bound WorkspaceSource for a running
	// session: the ref, the base PVC the overlay was cut from, and whether the
	// cut completed. Frozen at session start (like ResolvedSidecarToolboxes).
	// +optional
	ResolvedWorkspaceSource *ResolvedWorkspaceSource `json:"resolvedWorkspaceSource,omitempty"`

	// SidecarReachability is the runner's per-session live-probe result for each
	// sidecar it has probed (keyed by Name). Runner-owned; kept separate from
	// ResolvedSidecarToolboxes (operator-owned, rebuilt each reconcile) so a
	// merge write never wipes it. Empty until the runner probes a sidecar.
	// +optional
	SidecarReachability []SidecarReachability `json:"sidecarReachability,omitempty"`

	// ResolvedContentGuardDetectors snapshots the detector sidecars injected for
	// content-guard inspectors (e.g. prompt-injection). Empty ⇒ none.
	// +optional
	ResolvedContentGuardDetectors []ResolvedContentGuardDetector `json:"resolvedContentGuardDetectors,omitempty"`

	// ResolvedSkillBundles lists the skills staged onto disk in the sandbox pod
	// for this session -- every AgentSkill whose Target is sandbox/both AND
	// that some ToolBundle's StageSkills names (directly or via "*"). A
	// staged entry always carries a composed SKILL.md; supporting files ride
	// along when the skill also has a bundle. A skill that is not staged
	// (agent-targeted, or not named by any StageSkills) does not appear here
	// at all -- it still reaches the agent via load_skill.
	// +optional
	ResolvedSkillBundles []ResolvedSkillBundle `json:"resolvedSkillBundles,omitempty"`

	// ObservedPins records, per dependency the session actually used, the
	// identity observed at session start (one unified list across kinds;
	// Pin.Kind discriminates). Drift = observed != the definition CR's
	// baseline pin.
	// +optional
	ObservedPins []ObservedPin `json:"observedPins,omitempty"`

	// CredentialAuthFailures records, per tool ORIGIN, that the platform ITSELF
	// observed an auth-shaped failure there — the independent corroboration a
	// CredentialUpdateRequest needs when the provider cannot be re-probed to
	// confirm the agent's claim. Runner-owned (only the runner sees a tool
	// call's upstream outcome); the operator reads and never writes it.
	//
	// An entry is REMOVED the moment a call to the same origin succeeds: a
	// stale entry would corroborate a healthy credential forever and help talk
	// a human into re-entering a working token.
	// +optional
	CredentialAuthFailures []CredentialAuthFailure `json:"credentialAuthFailures,omitempty"`

	// Progress accumulates turn, token and tool counts for the CURRENT pod;
	// unlike RunDuration it resets when the pod is replaced.
	// +optional
	Progress *AgentSessionProgress `json:"progress,omitempty"`
	// Result is the agent's final summary and artifacts, set on success.
	// +optional
	Result *AgentResult `json:"result,omitempty"`
	// PinnedMessage is the runner's projection of the triggered session's
	// opening message; channelsd renders it onto the anchored opening line.
	// +optional
	PinnedMessage *PinnedMessageStatus `json:"pinnedMessage,omitempty"`
	// CompletionBypasses records every time this session declared its work
	// complete while a requirement its AgentClass declared was unmet.
	//
	// An OBSERVATION the runner writes, never an applied field: it carries a
	// wall-clock stamp and accumulates as the session runs, so it belongs in
	// controller-owned status rather than anywhere a client server-side-applies.
	// Append-only within a session — a later bypass never rewrites an earlier
	// one, because "it happened twice" is the fact an operator most wants.
	// +optional
	// +listType=atomic
	CompletionBypasses []CompletionBypass `json:"completionBypasses,omitempty"`

	// FailureReason is the human-readable reason phase became Failed.
	// +optional
	FailureReason string `json:"failureReason,omitempty"`
	// RunnerNotes are timestamped diagnostics the runner appended for operators.
	// +optional
	RunnerNotes []RunnerNote `json:"runnerNotes,omitempty"`

	// AppliedInteractPermission is the SessionInteractPermission value
	// captured from AgentClass at session creation. Frozen for the
	// session's lifetime; admins can compare against the class's
	// current spec to see drift.
	// +optional
	AppliedInteractPermission string `json:"appliedInteractPermission,omitempty"`

	// AppliedInteractPermissionAt is when the snapshot was captured.
	// +optional
	AppliedInteractPermissionAt *metav1.Time `json:"appliedInteractPermissionAt,omitempty"`

	// PendingRequesters tracks ad-hoc permission requests that have
	// been delivered to the original requester but not yet decided.
	// Channel-agnostic; populated and cleared by channelsd's pipeline.
	// +optional
	PendingRequesters []PendingRequester `json:"pendingRequesters,omitempty"`

	// PendingInteractions is the single list of interaction prompts awaiting a
	// user decision — tool_approval, info_leakage and content_inspection all
	// park here, keyed by Category. Written by channelsd's
	// HandleInteractionRequest. Each entry is self-contained so consumers
	// (oap session approve, status-watchdog, webd, admind) can display and
	// drive the decision without reading the memory layer. It does NOT drive
	// phase: phase=AwaitingDecision derives from the runner's signed lifecycle
	// projection.
	// +optional
	// +listType=map
	// +listMapKey=requestID
	PendingInteractions []PendingInteraction `json:"pendingInteractions,omitempty"`

	// SupersededBy names the AgentSession that forked from this one
	// via Restart-from-here. Set when phase transitions to Succeeded
	// due to a fork (rather than natural completion). Diagnostic only.
	// +optional
	SupersededBy string `json:"supersededBy,omitempty"`

	// PendingRestart is set by channelsd when a RestartTrigger is
	// published, and cleared by the controller once the fork is
	// complete (SupersededBy populated). Crash-recovery marker; the
	// controller's reconciler retries an unfinished restart on resume.
	// +optional
	PendingRestart *PendingRestart `json:"pendingRestart,omitempty"`

	// StartFailure is set by channelsd when it detects a terminal pre-start
	// failure (e.g. an authz-write failure on session creation). The operator
	// reads it and produces the Failed state — channelsd never writes
	// phase/failureReason/finishedAt itself.
	// +optional
	StartFailure *AgentSessionStartFailure `json:"startFailure,omitempty"`

	// SatisfiedSecretOutputs records secret-output handles the runner has
	// captured and published to the per-session secret-output Secret (via the
	// operator-mediated endpoint). Observability only: the value is never here.
	// +optional
	// +listType=map
	// +listMapKey=handle
	SatisfiedSecretOutputs []SecretOutputCompletion `json:"satisfiedSecretOutputs,omitempty"`

	// AuditPublicKey is the base64 (std) Ed25519 public key whose
	// private half (per-session Secret key "audit-signing-key") signs
	// this session's append-only audit entries. The K8s-witnessed trust
	// anchor for offline verification (`oap audit verify`).
	// +optional
	AuditPublicKey string `json:"auditPublicKey,omitempty"`
	// AuditKeyID is the key fingerprint (hex SHA-256 prefix) — matches
	// the keyId in each signed entry's provenance.
	// +optional
	AuditKeyID string `json:"auditKeyID,omitempty"`
	// AuditChainHeads records each publisher's final chain head
	// (publisher → "seq:lastHash"), stamped once the session reaches a
	// terminal phase. It anchors tail-truncation detection for ended
	// sessions; `oap audit verify` reads it.
	// +optional
	AuditChainHeads map[string]string `json:"auditChainHeads,omitempty"`

	// EffectiveSettings is the resolved 4-tier settings snapshot
	// (cluster → namespace → class → session). Stamped by the AgentSession
	// reconciler before the runner pod is created; the runner reads it.
	// +optional
	EffectiveSettings *EffectiveSettings `json:"effectiveSettings,omitempty"`

	// EffectiveIdentityMode is the RESOLVED identity mode (agent|userPassthrough).
	// For static modes it mirrors spec.identityMode. For ask|dynamic it is unset
	// until the initiating user chooses, then set from the signed choice event.
	// All runtime identity logic reads THIS, not spec.identityMode.
	// +optional
	EffectiveIdentityMode string `json:"effectiveIdentityMode,omitempty"`

	// IdentityChoiceParkedAt is when the session entered AwaitingIdentityChoice.
	// The operator measures IdentityChoiceTimeout from here.
	// +optional
	IdentityChoiceParkedAt *metav1.Time `json:"identityChoiceParkedAt,omitempty"`

	// StartApprovalParkedAt is when the session entered AwaitingStartApproval
	// (parked on AnnotationStartApprovalRequestRef). The operator measures the
	// start-approval deadline from here.
	// +optional
	StartApprovalParkedAt *metav1.Time `json:"startApprovalParkedAt,omitempty"`

	// EstimatedCost is the best-effort end-of-session USD cost estimate.
	// Runner-owned; written at SessionEnd when reportSessionCost is on.
	// +optional
	EstimatedCost *EstimatedSessionCost `json:"estimatedCost,omitempty"`

	// AwaitingUserInputSince is set when the agent yields via
	// await_user_message and cleared on resume or terminal. It is the durable
	// floor under the at-most-once turn_activity yield event: a non-nil value
	// counts as a visible wait, so the silence watchdog never warns while the
	// agent is correctly waiting for a reply. A scalar rather than a condition,
	// so it composes with the owner-partitioned condition merge in
	// pkg/controllers/agentstatus.
	// +optional
	AwaitingUserInputSince *metav1.Time `json:"awaitingUserInputSince,omitempty"`

	// ParentExchange is a delegated child's side of the conversation with the
	// agent that delegated to it. Written by the child's own runner (the
	// ask_parent meta tool sets it; the loop clears Pending on resume) and read
	// by the SubagentRequest controller, which mirrors it onto the request the
	// child answers so the parent's delegate call can return with the question
	// instead of blocking to a terminal phase.
	//
	// Set only on a conversational (task/chat) child. A single_turn child is
	// headless, has no parent to ask, and leaves this nil for its whole life.
	// +optional
	ParentExchange *ParentExchange `json:"parentExchange,omitempty"`

	// InputRequest is this child's outstanding mid-flight ask for data. The
	// SubagentRequest controller mirrors it onto the request its parent is
	// polling, the same route ParentExchange takes.
	// +optional
	InputRequest *InputRequest `json:"inputRequest,omitempty"`

	// AgentWakeCredit is how many further AGENT-DRIVEN wakes this session will
	// accept before a human speaks again. Refilled to the class's WakeBudget by
	// every human turn, counted down by each agent-driven wake.
	//
	// CHANNELSD-OWNED, and that ownership is the control. The sessions it
	// bounds are the ones talking to each other, so a budget either of them
	// could write would be widened by the loop it exists to stop — the same
	// reasoning that makes SubagentRequest's ExchangesRemaining
	// controller-owned rather than mirrored from the child.
	//
	// Nil means no cross-agent wake has been considered yet. Zero means the
	// budget is spent: further agent messages still APPEND — the session sees
	// the whole conversation — but none of them wakes it until a person
	// speaks.
	// +optional
	AgentWakeCredit *int `json:"agentWakeCredit,omitempty"`

	// ClosureDenied reports that some member of this session's DELEGATION
	// CLOSURE has had an authorization decision denied. The trifecta dispatch
	// gate refuses in every mode when it is true — a denied closure is a
	// structural fact ("this delegation has already been judged to have gone
	// wrong"), not a policy judgement about the call in front of the gate.
	//
	// OPERATOR-DERIVED, and it has to be. Answering it means reading every
	// session in the tree, and the runner's Role pins agentsessions to its own
	// name with no `list` at all — a runner-side closure walk is Forbidden at
	// the first hop in production while passing every test on an admin client.
	// So the operator computes the closure fact and the runner reads it from
	// the one object it is allowed to Get: its own.
	//
	// A denial ANYWHERE in the closure sets it, because that is what makes the
	// signal useful: a parent that cannot get approval routes around the
	// refusal by delegating to a different child, and a per-session flag would
	// let it. Nil means not yet evaluated; false means evaluated and clean.
	// +optional
	ClosureDenied *bool `json:"closureDenied,omitempty"`

	// ToolGuard reflects live tool-guard enforcement: currently-open circuit
	// breakers. Patched by the runner on breaker transitions.
	// +optional
	ToolGuard *ToolGuardStatus `json:"toolGuard,omitempty"`

	// ActiveWidgets records the most recent MCP-UI interactive widgets
	// persisted for this session, so a user opening the browser session-view
	// page minutes after a widget was produced can still fetch and render it.
	// Runner-owned; capped to the most recent entries to bound growth over a
	// long session.
	// +optional
	ActiveWidgets []WidgetRef `json:"activeWidgets,omitempty"`

	// PermissionSurface is what this session's tools can actually reach:
	// every permission handle the runner enumerated from the live tool
	// envelope, with the tools that reach it.
	//
	// It answers "what can this agent do?" for the CLI and admin panel from
	// the ONE producer that can answer it exactly — the runner, which holds
	// the real []tool.Tool. Any second derivation (walking CRs, guessing at
	// MCP tool names) would drift from what dispatch actually checks, and the
	// surface≡dispatch equivalence is the whole point of enumerating it.
	//
	// An OBSERVATION, never authority. Nothing may gate on this field: it is
	// a snapshot of a resolution, and a resolution read back later could be
	// stale. Durable authority is carried by handles in frozen plans,
	// approval records and the audit log — which is what permsurface's
	// "only Handle persists" rule is protecting. Writing it here rather than
	// into memory keeps that line visible: status is where this repo puts
	// things a controller observed, and status is definitionally not a grant.
	//
	// A pure function of the tool envelope — sorted, deduplicated, no
	// timestamps — so re-writing it on an unchanged session is a no-op diff.
	// +optional
	PermissionSurface []PermissionSurfaceEntry `json:"permissionSurface,omitempty"`

	// PassthroughCredHashes maps each projected passthrough credential name to
	// the SHA-256 hex of its value as of the last reconcile. A change on the
	// next reconcile means the credential was re-linked or removed, and the
	// operator emits a per-session `credential` invalidation on the
	// ap.revocation bus so a running session picks it up on its next tool call.
	// Operator-owned observation, set on change — never an SSA-applied field.
	// +optional
	PassthroughCredHashes map[string]string `json:"passthroughCredHashes,omitempty"`

	// InteractorTuplesWritten lists the subjects for which this session has
	// already written an agentclass#interactor tuple, so a long multi-turn
	// session does not re-issue the idempotent SpiceDB touch every reconcile.
	// Controller-owned; set-once per subject.
	// +optional
	InteractorTuplesWritten []string `json:"interactorTuplesWritten,omitempty"`

	// Conditions are the session's status conditions, partitioned by owner and
	// merged through pkg/controllers/agentstatus.
	// +patchStrategy=merge
	// +patchMergeKey=type
	// +listType=map
	// +listMapKey=type
	// +optional
	Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}

func (*AgentSessionStatus) DeepCopy

func (in *AgentSessionStatus) DeepCopy() *AgentSessionStatus

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentSessionStatus.

func (*AgentSessionStatus) DeepCopyInto

func (in *AgentSessionStatus) DeepCopyInto(out *AgentSessionStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type AgentSettings

type AgentSettings struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   SettingsSpec   `json:"spec,omitempty"`
	Status SettingsStatus `json:"status,omitempty"`
}

+kubebuilder:object:root=true +kubebuilder:resource:scope=Namespaced,categories={authzed,spicebox},shortName=agset +kubebuilder:subresource:status +kubebuilder:printcolumn:name="Consistent",type="string",JSONPath=".status.conditions[?(@.type=='SelfConsistent')].status" +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +genclient

AgentSettings is the namespace tier of agent governance settings: limits that narrow what agents in this namespace may do, and defaults that fill in fields a lower tier omits. Per-namespace singleton -- the only permitted name is "default" (AgentSettingsName).

Namespaced. Reconciled by pkg/controllers/settings, which reports SelfConsistent. Tier resolution itself lives in pkg/platform/settings: a limit here can only narrow the ClusterAgentSettings ceiling, never widen it, and the resolved snapshot is stamped onto AgentClass/AgentSession status.

func (*AgentSettings) DeepCopy

func (in *AgentSettings) DeepCopy() *AgentSettings

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentSettings.

func (*AgentSettings) DeepCopyInto

func (in *AgentSettings) DeepCopyInto(out *AgentSettings)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*AgentSettings) DeepCopyObject

func (in *AgentSettings) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type AgentSettingsList

type AgentSettingsList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []AgentSettings `json:"items"`
}

+kubebuilder:object:root=true

func (*AgentSettingsList) DeepCopy

func (in *AgentSettingsList) DeepCopy() *AgentSettingsList

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentSettingsList.

func (*AgentSettingsList) DeepCopyInto

func (in *AgentSettingsList) DeepCopyInto(out *AgentSettingsList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*AgentSettingsList) DeepCopyObject

func (in *AgentSettingsList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type AgentSkill

type AgentSkill struct {
	// Name is the local handle, unique within this class, and constrained to
	// [a-z0-9_-]{1,32} (validateSkillsSpec) because it is also the directory
	// name under the mount path when this skill is staged — which is why a
	// sandbox-targeted skill's Name must equal its SKILL.md frontmatter name;
	// Claude Code discovers a skill only when those agree.
	Name string `json:"name"`
	// Ref is the skill's canonical name, "<repo-locator>//<subpath>@<ref>".
	Ref string `json:"ref"`
	// Target says who consumes this skill.
	// +kubebuilder:default=agent
	// +optional
	Target SkillTarget `json:"target,omitempty"`
}

AgentSkill is one skill this class opts into: a local handle plus a reference to the shared skill, the same shape as ToolBundle{Name, Class}.

func (*AgentSkill) DeepCopy

func (in *AgentSkill) DeepCopy() *AgentSkill

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentSkill.

func (*AgentSkill) DeepCopyInto

func (in *AgentSkill) DeepCopyInto(out *AgentSkill)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type AgentUI

type AgentUI struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   AgentUISpec   `json:"spec,omitempty"`
	Status AgentUIStatus `json:"status,omitempty"`
}

+kubebuilder:object:root=true +kubebuilder:resource:scope=Namespaced,categories={authzed,spicebox},shortName=aui +kubebuilder:subresource:status +kubebuilder:printcolumn:name="Valid",type="string",JSONPath=".status.conditions[?(@.type=='Valid')].status" +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +genclient

AgentUI is a bundle-authored DECLARATION of an agent-defined view: a page tree in which generative hooks are the agent's regions, plus the tools and actions the view asks the browser be allowed to fire. It is a declaration, not a web app.

Namespaced. Reconciled by pkg/controllers/agentui, which validates the whole page against the platform component vocabulary and observes both its hook table and an eligibility ceiling onto status. Naming a tool here authorizes nothing: the deployment half of the grant lives on AgentClass.spec.agentUI, a separate object with a separate writer, and the runner still re-authorizes every click.

func (*AgentUI) DeepCopy

func (in *AgentUI) DeepCopy() *AgentUI

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentUI.

func (*AgentUI) DeepCopyInto

func (in *AgentUI) DeepCopyInto(out *AgentUI)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*AgentUI) DeepCopyObject

func (in *AgentUI) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type AgentUIAction

type AgentUIAction struct {
	// Name is how a control refers to this action; unique within the UI.
	// +kubebuilder:validation:Pattern=`^[a-zA-Z0-9_.-]{1,64}$`
	Name string `json:"name"`

	// Tool carries the same normalize alphabet as AgentUISpec.Tools, for the
	// same reason. Optional because an action may declare a Prompt instead:
	// exactly one of Tool and Prompt is set, enforced by pkg/web/uicomponents'
	// validateActions rather than the apiserver, since "exactly one of these
	// two" in CEL buys a worse message than the validator's own.
	// +kubebuilder:validation:Pattern=`^[a-z0-9_-]{1,128}$`
	// +optional
	Tool string `json:"tool,omitempty"`

	// Prompt makes this action ASK THE AGENT instead of calling a tool: the
	// control sends this text to the session as a message from the viewer. It
	// covers "summarize this" or "tell me about this row", which are not tool
	// calls, and grants nothing new — the message is one the viewer could have
	// typed themselves, on the same route under the same authorization and
	// attributed to them. {key} placeholders name a binding parameter or one of
	// this action's Inputs, so a row's values can reach the sentence.
	//
	// The length cap is generous but real: a prompt long enough to be a
	// document belongs in spec.systemPrompt, where standing instructions live.
	// +kubebuilder:validation:MaxLength=2000
	// +optional
	Prompt string `json:"prompt,omitempty"`

	// Args is the args template, held as raw JSON for the same reason
	// AgentUISlot.Default is: CRD types must not depend on pkg/web/uicomponents.
	// +optional
	Args *apiextensionsv1.JSON `json:"args,omitempty"`

	// Inputs names the values a CONTROL may supply at invoke time — a
	// server-side allowlist enforced by pkg/web/uicomponents' validateActions
	// (unique names, disjoint from every binding parameter name).
	// +kubebuilder:validation:items:Pattern=`^[a-zA-Z0-9_.-]{1,64}$`
	// +optional
	Inputs []string `json:"inputs,omitempty"`
}

AgentUIAction is one bundle-authored action: a tool this UI's controls may fire, plus the args template to fire it with. It authorizes NOTHING — the deployment grant (AgentClass.spec.agentUI.grantedTools) and the per-click, viewer-bound re-authorization in the runner are the authorization, and both still run. This field exists so the tool and the args template live SERVER-side, where a browser can name an action without being able to choose what it does.

func (*AgentUIAction) DeepCopy

func (in *AgentUIAction) DeepCopy() *AgentUIAction

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentUIAction.

func (*AgentUIAction) DeepCopyInto

func (in *AgentUIAction) DeepCopyInto(out *AgentUIAction)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type AgentUIChrome

type AgentUIChrome struct {
	// InitialState is how the session shell should first appear; the user's
	// own toggle overrides it thereafter.
	// +kubebuilder:validation:Enum=expanded;collapsed
	// +optional
	InitialState string `json:"initialState,omitempty"`
}

AgentUIChrome carries presentation REQUESTS only. Chrome authority is not negotiable: the user's explicit toggle always wins, and a trust event (an approval addressed to the viewer, a credential request, a hard error) auto-reveals chrome regardless of what is asked for here.

func (*AgentUIChrome) DeepCopy

func (in *AgentUIChrome) DeepCopy() *AgentUIChrome

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentUIChrome.

func (*AgentUIChrome) DeepCopyInto

func (in *AgentUIChrome) DeepCopyInto(out *AgentUIChrome)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type AgentUIHook

type AgentUIHook struct {
	// Name is the hook's identifier, unique within the page.
	Name string `json:"name"`

	// Intent is the author's guidance for what the agent should put here.
	// +optional
	Intent string `json:"intent,omitempty"`

	// AllowedComponents is the component vocabulary this hook accepts; "*"
	// permits any registered component.
	// +optional
	AllowedComponents []string `json:"allowedComponents,omitempty"`
}

AgentUIHook is one region the page exposes to the agent, as OBSERVED from spec by the controller — status-subresource only, never applied by a client.

func (*AgentUIHook) DeepCopy

func (in *AgentUIHook) DeepCopy() *AgentUIHook

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentUIHook.

func (*AgentUIHook) DeepCopyInto

func (in *AgentUIHook) DeepCopyInto(out *AgentUIHook)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type AgentUIList

type AgentUIList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []AgentUI `json:"items"`
}

+kubebuilder:object:root=true

func (*AgentUIList) DeepCopy

func (in *AgentUIList) DeepCopy() *AgentUIList

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentUIList.

func (*AgentUIList) DeepCopyInto

func (in *AgentUIList) DeepCopyInto(out *AgentUIList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*AgentUIList) DeepCopyObject

func (in *AgentUIList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type AgentUISlot

type AgentUISlot struct {
	// Name is the slot identifier a declaration node targets; unique per UI.
	Name string `json:"name"`

	// AgentWritable permits the agent to overwrite this slot at runtime.
	// Default false: a slot is author-owned unless it opts in.
	// +optional
	AgentWritable bool `json:"agentWritable,omitempty"`

	// Default is the Tier-0 declaration node rendered with NO agent turn
	// involved — which is what lets a brand-new session paint a complete UI on
	// first load. Held as raw JSON because CRD types must not depend on
	// pkg/web/uicomponents; the controller validates it against the vocabulary at
	// reconcile time.
	// +optional
	Default *apiextensionsv1.JSON `json:"default,omitempty"`
}

func (*AgentUISlot) DeepCopy

func (in *AgentUISlot) DeepCopy() *AgentUISlot

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentUISlot.

func (*AgentUISlot) DeepCopyInto

func (in *AgentUISlot) DeepCopyInto(out *AgentUISlot)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type AgentUISpec

type AgentUISpec struct {
	// DisplayName is the human label for this view; empty falls back to the
	// CR name.
	// +optional
	DisplayName string `json:"displayName,omitempty"`

	// Chrome carries presentation requests for the surrounding session shell.
	// +optional
	Chrome *AgentUIChrome `json:"chrome,omitempty"`

	// View is the page: one component tree in which every oap:generative
	// node is a region the agent may fill (its name, intent, and the
	// components the AGENT may put there). Held as raw JSON for the same
	// reason AgentUISlot.Default is — CRD types must not depend on
	// pkg/web/uicomponents; the controller validates it at reconcile time.
	//
	// Exactly one of View and Slots is set; the controller (not CEL) enforces
	// it, for the same "better message" reason AgentUIAction's tool/prompt
	// exclusivity is checked there.
	// +optional
	View *apiextensionsv1.JSON `json:"view,omitempty"`

	// Slots is the page's LEGACY flat shape — named regions in a row. Still
	// accepted: the controller compiles each agentWritable slot into a hook
	// (allowedComponents ["*"]) inside a root ap:stack and validates the
	// result exactly as it would a View. New pages declare View.
	// +optional
	Slots []AgentUISlot `json:"slots,omitempty"`

	// Tools is what this UI REQUESTS the browser be able to call directly —
	// one of three conditions (see pkg/web/uigrant). A tool named here is
	// callable only if its origin also permits app-visible calls AND the
	// deployment granted it.
	//
	// The item pattern is exactly the output alphabet of
	// synthesize.NormalizeName, the transform every synthesized tool name
	// passes through to become a Loop.AppTools key, and is the same pattern
	// AgentClass.spec.agentUI.grantedTools carries. Pinning both vocabularies
	// to it makes them identical by construction: a camelCase
	// "widgets_createIssue" is rejected loudly at write time instead of
	// validating clean, matching clean against an equally-unnormalized
	// grantedTools, and dying as a dead button with no denial log anywhere.
	//
	// A CRD pattern is not retroactive: objects persisted before it can still
	// carry non-conforming names until next written, so the runner's normalize
	// step MUST NOT be deleted as redundant.
	// +kubebuilder:validation:items:Pattern=`^[a-z0-9_-]{1,128}$`
	// +optional
	Tools []string `json:"tools,omitempty"`

	// Actions is the bundle-authored action table this UI's controls may fire
	// — see AgentUIAction's doc comment for why the tool and args template
	// live here, server-side, rather than in a control's own props. Like
	// Tools, naming a tool here is a REQUEST: it authorizes nothing by
	// itself, and both the deployment grant and the runner's per-click,
	// viewer-bound re-authorization still run.
	// +optional
	Actions []AgentUIAction `json:"actions,omitempty"`
}

AgentUISpec is entirely BUNDLE-AUTHORED. Nothing here authorizes anything: Tools is a request, and the deployment's grant lives on a different object (AgentClass.spec.agentUI.grantedTools) so the two never share a field manager.

func (*AgentUISpec) DeepCopy

func (in *AgentUISpec) DeepCopy() *AgentUISpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentUISpec.

func (*AgentUISpec) DeepCopyInto

func (in *AgentUISpec) DeepCopyInto(out *AgentUISpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type AgentUIStatus

type AgentUIStatus struct {
	// EligibleTools is a CEILING, not a grant: spec.tools intersected with the
	// UNION of GrantedTools across every AgentClass naming this AgentUI. It
	// stops there and does NOT filter by per-origin app-visibility, because
	// which origins resolve at all is SESSION-scoped — a property of the
	// AgentSession, unknowable to this namespaced CR.
	//
	// The union is SOUND IN THE REJECT DIRECTION for this field's one purpose,
	// gating which tool names a Tier-0 slot default may bind to: a tool outside
	// it is one NO referencing class could ever grant, so rejecting the binding
	// is never a false negative. Deliberately not called "fail-closed" — that
	// describes the ACCEPT direction, where the union is the LOOSEST choice
	// among referencing classes, and carrying that framing elsewhere would be
	// false. One AgentUI may be deployed by several classes with different
	// grants, so there is no single "the grant" to observe here in principle.
	//
	// OPERATOR-FACING OBSERVATION ONLY — an admin UI showing "tools a bundle
	// could ever bind to". It MUST NOT be read as, or fed into, an
	// authorization decision: the narrower per-session EFFECTIVE set the runner
	// computes against live-resolved origins is what gates a real tool call.
	// Status-subresource only; a client applying spec must never send it.
	// +optional
	EligibleTools []string `json:"eligibleTools,omitempty"`

	// Hooks lists the page's generative regions after the spec.slots shim, so
	// an operator can see what the agent may write without compiling the page
	// themselves. A pure function of spec: written only when the page was
	// validated, and cleared whenever it is not; preserved across a
	// transient grant-resolve failure (Valid=Unknown), the same as
	// EligibleTools, since that failure means this reconcile never
	// re-validated the page at all.
	// +optional
	Hooks []AgentUIHook `json:"hooks,omitempty"`

	// Conditions carries Valid and ToolsGranted; see their type constants.
	// +optional
	Conditions []metav1.Condition `json:"conditions,omitempty"`
}

func (*AgentUIStatus) DeepCopy

func (in *AgentUIStatus) DeepCopy() *AgentUIStatus

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentUIStatus.

func (*AgentUIStatus) DeepCopyInto

func (in *AgentUIStatus) DeepCopyInto(out *AgentUIStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type AllowedMCPServer

type AllowedMCPServer struct {
	// Name is the permitted MCPServer CR name.
	Name string `json:"name"`
	// Tools narrows to specific tool names; nil, empty, or ["*"] allows all.
	// +optional
	// +listType=atomic
	Tools []string `json:"tools,omitempty"`
}

AllowedMCPServer names a permitted MCPServer (by CR name) and optionally restricts which of its tools may be used. A nil/empty Tools list, or a list containing "*", means all of that server's tools are allowed.

func (*AllowedMCPServer) DeepCopy

func (in *AllowedMCPServer) DeepCopy() *AllowedMCPServer

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedMCPServer.

func (*AllowedMCPServer) DeepCopyInto

func (in *AllowedMCPServer) DeepCopyInto(out *AllowedMCPServer)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ArtifactRender

type ArtifactRender struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   ArtifactRenderSpec   `json:"spec,omitempty"`
	Status ArtifactRenderStatus `json:"status,omitempty"`
}

+kubebuilder:object:root=true +kubebuilder:resource:scope=Namespaced,categories={authzed,agentprimitives},shortName=arender +kubebuilder:subresource:status +kubebuilder:printcolumn:name="Kind",type="string",JSONPath=".spec.kind" +kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase" +kubebuilder:printcolumn:name="MIME",type="string",JSONPath=".status.outputMIME" +kubebuilder:printcolumn:name="Size",type="integer",JSONPath=".status.outputSize" +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +genclient

ArtifactRender is one request to turn agent-supplied bytes into a deliverable artifact -- an HTML page, an SVG, an image -- through a named renderer plug-in. The agent creates it; the rendered output is persisted to the artifactstore and referenced from status.

Namespaced. Reconciled by pkg/controllers/artifactrender, which drives the phase machine Pending -> Rendering -> Ready/Failed by dispatching to the renderer registered under spec.kind (pkg/channels/channelassets/registry).

func (*ArtifactRender) DeepCopy

func (in *ArtifactRender) DeepCopy() *ArtifactRender

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArtifactRender.

func (*ArtifactRender) DeepCopyInto

func (in *ArtifactRender) DeepCopyInto(out *ArtifactRender)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*ArtifactRender) DeepCopyObject

func (in *ArtifactRender) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type ArtifactRenderList

type ArtifactRenderList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []ArtifactRender `json:"items"`
}

+kubebuilder:object:root=true

func (*ArtifactRenderList) DeepCopy

func (in *ArtifactRenderList) DeepCopy() *ArtifactRenderList

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArtifactRenderList.

func (*ArtifactRenderList) DeepCopyInto

func (in *ArtifactRenderList) DeepCopyInto(out *ArtifactRenderList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*ArtifactRenderList) DeepCopyObject

func (in *ArtifactRenderList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type ArtifactRenderPhase

type ArtifactRenderPhase string

ArtifactRenderPhase is the closed-enum lifecycle state. +kubebuilder:validation:Enum=Pending;Rendering;Ready;Failed

const (
	ArtifactRenderPhasePending   ArtifactRenderPhase = "Pending"
	ArtifactRenderPhaseRendering ArtifactRenderPhase = "Rendering"
	ArtifactRenderPhaseReady     ArtifactRenderPhase = "Ready"
	ArtifactRenderPhaseFailed    ArtifactRenderPhase = "Failed"
)

type ArtifactRenderSpec

type ArtifactRenderSpec struct {
	// Kind names the renderer plug-in (matches the registered Kind() value).
	// Pattern: lowercase, dash-separated. Example: "html".
	Kind string `json:"kind"`

	// Filename is a delivery hint for the channel's file-upload primitive.
	// Renderers may rewrite (e.g. force ".html" extension).
	// +optional
	Filename string `json:"filename,omitempty"`

	// AltText is the accessibility / channel-side preview text shown when
	// the artifact can't render. Required for image-MIME renderer outputs;
	// optional otherwise (the runner enforces in prepare_artifact).
	// +optional
	AltText string `json:"altText,omitempty"`

	// Payload is the raw input bytes. Exactly one of Payload (inline,
	// ≤256 KiB) or PayloadRef (artifactstore) MUST be set.
	// +optional
	Payload []byte `json:"payload,omitempty"`

	// PayloadRef is an artifactstore.Ref the controller fetches the input bytes
	// from, for payloads too large to inline. Deleted with the CR.
	// +optional
	PayloadRef string `json:"payloadRef,omitempty"`

	// TimeoutSeconds is the agent-requested per-render budget. Operator
	// caps at 300; default 30.
	// +kubebuilder:default=30
	// +kubebuilder:validation:Minimum=1
	// +kubebuilder:validation:Maximum=300
	TimeoutSeconds int32 `json:"timeoutSeconds,omitempty"`

	// CSP is a widget's declared `_meta.ui.csp` (mcp-ui / MCP Apps protocol),
	// carried from the MCP tool result so the session-view page's per-widget CSP
	// can honor domains the widget says it needs instead of falling back to the
	// restrictive same-origin default. nil when the widget declared none, and
	// always nil for non-mcpui renders. MCP-server-authored and therefore
	// UNTRUSTED: every consumer sanitizes each domain before use.
	// +optional
	CSP *WidgetCSP `json:"csp,omitempty"`
}

func (*ArtifactRenderSpec) DeepCopy

func (in *ArtifactRenderSpec) DeepCopy() *ArtifactRenderSpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArtifactRenderSpec.

func (*ArtifactRenderSpec) DeepCopyInto

func (in *ArtifactRenderSpec) DeepCopyInto(out *ArtifactRenderSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ArtifactRenderStatus

type ArtifactRenderStatus struct {
	// ObservedGeneration is the spec generation this status reflects.
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`
	// Phase is the render lifecycle position.
	// +optional
	Phase ArtifactRenderPhase `json:"phase,omitempty"`

	// OutputRef is the artifactstore ref the rendered bytes landed under.
	// Set when phase=Ready.
	// +optional
	OutputRef string `json:"outputRef,omitempty"`
	// OutputMIME is the rendered artifact's content type.
	// +optional
	OutputMIME string `json:"outputMIME,omitempty"`
	// OutputSize is the rendered artifact's size in bytes.
	// +optional
	OutputSize int64 `json:"outputSize,omitempty"`
	// OutputFilename is the delivery filename, after any renderer rewrite.
	// +optional
	OutputFilename string `json:"outputFilename,omitempty"`
	// Warnings are the sanitizer's findings, surfaced so the agent can fix its
	// source; a non-empty list does not mean the render failed.
	// +optional
	// +listType=atomic
	Warnings []SanitizerWarning `json:"warnings,omitempty"`

	// FailureReason is the machine-readable failure code. Set when phase=Failed.
	// +optional
	FailureReason string `json:"failureReason,omitempty"`
	// FailureMessage is the human-readable detail behind FailureReason.
	// +optional
	FailureMessage string `json:"failureMessage,omitempty"`

	// StartedAt is when rendering began.
	// +optional
	StartedAt *metav1.Time `json:"startedAt,omitempty"`
	// FinishedAt is when rendering reached Ready or Failed.
	// +optional
	FinishedAt *metav1.Time `json:"finishedAt,omitempty"`

	// Conditions carries Ready; see ArtifactRenderConditionReady.
	// +patchStrategy=merge
	// +patchMergeKey=type
	// +optional
	Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}

func (*ArtifactRenderStatus) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArtifactRenderStatus.

func (*ArtifactRenderStatus) DeepCopyInto

func (in *ArtifactRenderStatus) DeepCopyInto(out *ArtifactRenderStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type AuthzBlock

type AuthzBlock struct {
	// ToolCalls authorizes per-tool dispatch.
	// +optional
	ToolCalls *ToolCallsAuthz `json:"toolCalls,omitempty"`

	// Session authorizes who may interact with sessions of this class.
	// +optional
	Session *SessionAuthz `json:"session,omitempty"`

	// InformationLeakage gates outbound channel messages against tainted
	// resources accessed by the agent during a session.
	// +optional
	InformationLeakage *InformationLeakagePolicy `json:"informationLeakage,omitempty"`

	// Scope enables dynamic per-session permission scope. Off by
	// default. When Enabled=false, tool dispatch behavior is
	// identical to today (no Layer 2 check, no Layer 3 SpiceDB
	// disallow tuples).
	// +optional
	Scope *ScopeSpec `json:"scope,omitempty"`

	// ApprovalTimeout is the consolidated approval-WAIT deadline for every
	// human-in-the-loop authz gate on this class: the tool-call approval, the
	// information-leakage share approval, and the cold-start scope approval all
	// source their wait from this single value. Defaults to 10 minutes — long
	// enough for an approver to click the Slack block. This is the approval-WAIT
	// timeout only; it does NOT cover liveness watchdogs (budget.maxDuration,
	// per-turn, silence, maxLlmLatencyMs).
	// +optional
	ApprovalTimeout *metav1.Duration `json:"approvalTimeout,omitempty"`

	// OwnerCeiling optionally bounds what bound Channels may declare as the
	// session owner. Absent (the common case) ⇒ no ceiling.
	// +optional
	OwnerCeiling *OwnerCeiling `json:"ownerCeiling,omitempty"`

	// Slots declares the resource TYPES this agent operates on, each gated by a
	// permission — the instance axis of the two-axis ceiling. At session start
	// defaults bind; per user message the runner extracts instances of these
	// types, Checks each against SpiceDB, and adds authorized ones to the
	// session's bound set. Bindings auto-fill matching tool args.
	//
	// Renamed and relocated from spec.boundEntities so the CRD, the plan gate,
	// the approval flow and the SpiceDB grant all call it the same thing.
	// +optional
	// +listType=atomic
	Slots []AuthzSlot `json:"slots,omitempty"`

	// PlanGate gates permissioned tool calls on membership in an approved
	// plan's active phase. Off by default. A class may set a mode STRICTER
	// than the tier default freely; it may only go below the tier's
	// SettingsLimits.MinPlanGateMode if no floor is set.
	// +optional
	PlanGate *PlanGateConfig `json:"planGate,omitempty"`

	// Trifecta refuses a DELEGATION whose closure combines untrusted input,
	// sensitive access and consequential action. Off by default.
	//
	// A property of the closure rather than of this class: a class that reads
	// untrusted content is fine, one that can write is fine, and the
	// delegation joining them is what this refuses.
	// +optional
	Trifecta *TrifectaConfig `json:"trifecta,omitempty"`

	// CrossAgentThreads lets another agent's messages reach this one inside a
	// human's thread. Off by default.
	// +optional
	CrossAgentThreads *CrossAgentThreadsConfig `json:"crossAgentThreads,omitempty"`

	// Metaagent configures WHEN the metaagent classifies a turn. Defaults to
	// mention-only; inline is opt-in per the field comment.
	// +optional
	Metaagent *MetaagentConfig `json:"metaagent,omitempty"`
}

AuthzBlock groups the three authorization panes on an AgentClass: tool-call authz, session-interact authz, and information-leakage authz.

func (*AuthzBlock) DeepCopy

func (in *AuthzBlock) DeepCopy() *AuthzBlock

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthzBlock.

func (*AuthzBlock) DeepCopyInto

func (in *AuthzBlock) DeepCopyInto(out *AuthzBlock)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*AuthzBlock) GetCrossAgentThreads

func (b *AuthzBlock) GetCrossAgentThreads() *CrossAgentThreadsConfig

GetCrossAgentThreads returns Authz.CrossAgentThreads or a zero-value pointer; never returns nil.

The zero value is the CLOSED one — Enabled false, WakeBudget zero — so a class that declares nothing admits no other agent and grants no wake. Every caller therefore gets the safe answer by default rather than by remembering to nil-check, which is the point of the getter.

func (*AuthzBlock) GetSession

func (b *AuthzBlock) GetSession() *SessionAuthz

GetSession returns Authz.Session or a zero-value pointer; never returns nil.

func (*AuthzBlock) GetToolCalls

func (b *AuthzBlock) GetToolCalls() *ToolCallsAuthz

GetToolCalls returns Authz.ToolCalls or a zero-value pointer; never returns nil.

func (*AuthzBlock) GetTrifecta

func (b *AuthzBlock) GetTrifecta() *TrifectaConfig

GetTrifecta returns Authz.Trifecta or a zero-value pointer; never returns nil.

The zero value is the OFF one — Mode empty, which every consumer treats as disabled — so a class that declares nothing runs no trifecta check. Callers get that answer by construction rather than by remembering to nil-check.

func (*AuthzBlock) ResolvedApprovalTimeout

func (a *AuthzBlock) ResolvedApprovalTimeout() time.Duration

ResolvedApprovalTimeout returns ApprovalTimeout, or 10 minutes when the block or the field is nil. This is the single source for every approval-WAIT deadline (tool-call, information-leakage share, cold-start scope).

type AuthzSlot

type AuthzSlot struct {
	// +kubebuilder:validation:MinLength=1
	// +kubebuilder:validation:Pattern=`^[a-z][a-z0-9_]*(/[a-z][a-z0-9_]*)*$`
	ResourceType string `json:"resourceType"`

	// +kubebuilder:validation:MinLength=1
	Description string `json:"description"`

	// +kubebuilder:validation:MinLength=1
	// +kubebuilder:validation:Pattern=`^[a-z][a-z0-9_]*$`
	Permission string `json:"permission"`

	// Permissions are the ADDITIONAL permissions a grant on this slot may
	// confer, beyond Permission.
	//
	// A slot used to be one (resourceType, permission) pair fixed at declaration
	// time. That could not represent a real type: git_repo is reached by read,
	// write, fetch AND push, and a plan that later needs `read` on a slot
	// declared for `push` had nowhere to bind it. The approval was recorded, no
	// grant could be written for it, and the very next call escalated again —
	// a human clicking Approve on the same amendment forever.
	//
	// Every permission named here gets its own `slot_grant_<permission>`
	// relation in the composed SpiceDB schema, which is what makes a grant for
	// it writable at all. Declaring a permission does NOT grant it: a grant is
	// still written only for what an approval actually named, still scoped to
	// the session, still expiring and revocable. This is the CEILING of what an
	// approval on this slot may ever bind.
	// +optional
	// +listType=atomic
	Permissions []string `json:"permissions,omitempty"`

	// +optional
	// +listType=atomic
	Defaults []string `json:"defaults,omitempty"`

	// +optional
	ExtractionPrompt string `json:"extractionPrompt,omitempty"`

	// +optional
	// +listType=atomic
	AutoFillArgs []AuthzSlotAutoFillArg `json:"autoFillArgs,omitempty"`

	// FillFrom names how an instance may come to occupy this slot. Unset means
	// today's behavior: class-pinned defaults bind at session start and the
	// extractor may propose instances — for every source EXCEPT trigger
	// (below), an unset list narrows nothing.
	//
	//   - default        class-pinned IDs, bound at session start
	//   - query/extract  the user names an instance in a message, unprompted
	//   - ask            the agent prompts and waits, and the user names it in
	//                    the reply. Binds through the SAME path as query — the
	//                    two differ in who started the exchange, not in how the
	//                    value binds — so declaring ask needs no extra tool: the
	//                    agent asks with respond_to_user and waits with
	//                    await_user_message.
	//   - channel_thread the channel seeds candidates from the thread it was
	//                    minted from, governed by AutoGrantFrom
	//   - metaagent      ambient scope intent, human-approved. NOT yet
	//                    implemented as a binding path: a slot naming only
	//                    metaagent binds nothing today.
	//   - observed       a tool result recorded a fact about the instance. NOT
	//                    yet implemented as a binding path: a slot naming only
	//                    observed binds nothing today.
	//   - trigger        the pipeline binds the instances the signed webhook
	//                    delivery names, at session mint. The ONE source that
	//                    is NEVER implied by an unset list: naming it puts a
	//                    session-mint SpiceDB grant behind no human and no
	//                    second gate, so a slot must name "trigger" here
	//                    explicitly, or it binds nothing from a trigger
	//                    however permissive (or absent) its other fillFrom
	//                    entries read.
	// +optional
	// +listType=atomic
	// +kubebuilder:validation:items:Enum=default;query;extract;ask;channel_thread;metaagent;observed;trigger
	FillFrom []string `json:"fillFrom,omitempty"`

	// AutoGrantFrom names WHOSE contributions to a thread may bind this slot
	// without an approval. It governs channel_thread seeding only.
	//
	// A thread is multi-author, so "found in the thread" would otherwise mean
	// "anyone's value" — an SSRF/exfil surface. Unset means owner: only the
	// session owner's contributions auto-bind, and everyone else's route to
	// approval. participants trusts any thread member and is only defensible
	// for a tightly-controlled channel. none trusts nobody, so every value the
	// thread offers goes in front of a human.
	//
	// "Trust nobody" is the explicit value `none` rather than an empty list
	// because an empty list is NOT representable here: omitempty drops it on
	// serialization, so `autoGrantFrom: []` reads back as unset and would
	// silently become owner — a policy written to be restrictive quietly
	// widening. When values disagree, the most restrictive wins.
	// +optional
	// +listType=atomic
	// +kubebuilder:validation:items:Enum=owner;participants;none
	AutoGrantFrom []string `json:"autoGrantFrom,omitempty"`

	// TriggerInstance is a CEL expression over the verified webhook delivery
	// ({event: string, payload: dyn}) yielding the resource ID this slot
	// binds at trigger time. The resource TYPE is always this slot's
	// resourceType — the expression yields only the id half. When set it is
	// authoritative for this slot; when unset, a channel kind that derives
	// instances from the delivery (TriggerSlotProvider) supplies them.
	// Compiled and validated at class admission; at delivery time an eval
	// error, non-string, or empty result binds nothing. Meaningful only when
	// fillFrom includes "trigger".
	// +optional
	TriggerInstance string `json:"triggerInstance,omitempty"`

	// Membership decides whether a grant follows the session's member set as it
	// grows, or is pinned to the set present when it was approved.
	//
	// Defaults to frozen. The appealing argument for dynamic is that the
	// approver grants to a SESSION and trusts its governance — but that only
	// holds while session membership is hard to obtain, and it is not: thread
	// adoption admits recent authors, and a channel-kind link can make one
	// tuple mean an entire channel. Following a set that grows on its own is
	// therefore an explicit choice, not the quiet default.
	//
	// Set dynamic where the session really is a small owned thread and the
	// approval card has shown the approver the concrete subject set.
	// +optional
	// +kubebuilder:validation:Enum=dynamic;frozen
	// +kubebuilder:default=frozen
	Membership string `json:"membership,omitempty"`

	// Requires are predicates over the facts a candidate arrived with, all of
	// which must be Satisfied before it may occupy this slot. Any Refused or
	// Undetermined verdict holds the slot closed.
	// +optional
	// +listType=atomic
	Requires []SlotPrecondition `json:"requires,omitempty"`
}

AuthzSlot declares a TYPE of resource this agent operates on, gated by a permission — the instance axis of the two-axis ceiling: the permission Check'd at bind time, optional default IDs to bind at session start, and the tool args this binding auto-fills.

Renamed from BoundEntityType: "slot" is what the plan gate, the approval flow and the SpiceDB grant all call it, and one concept carrying two names is a standing source of misreading.

func (*AuthzSlot) DeepCopy

func (in *AuthzSlot) DeepCopy() *AuthzSlot

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthzSlot.

func (*AuthzSlot) DeepCopyInto

func (in *AuthzSlot) DeepCopyInto(out *AuthzSlot)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (AuthzSlot) EffectivePermissions

func (s AuthzSlot) EffectivePermissions() []string

EffectivePermissions is every permission a grant on this slot may confer: Permission plus Permissions, deduplicated, in declaration order.

Read this rather than either field, so "the slot's permissions" cannot mean one thing at the schema composer and another at the binding site — a mismatch there writes a grant against a relation the schema never emitted, which fails at SpiceDB with a FailedPrecondition nobody sees.

type AuthzSlotAutoFillArg

type AuthzSlotAutoFillArg struct {
	// +kubebuilder:validation:MinLength=1
	ArgName string `json:"argName"`

	// +optional
	ToolNamePattern string `json:"toolNamePattern,omitempty"`
}

AuthzSlotAutoFillArg fills a tool argument from a bound slot instance.

func (*AuthzSlotAutoFillArg) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthzSlotAutoFillArg.

func (*AuthzSlotAutoFillArg) DeepCopyInto

func (in *AuthzSlotAutoFillArg) DeepCopyInto(out *AuthzSlotAutoFillArg)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type BentoChannelConfig

type BentoChannelConfig struct {
	// Generate is a Bento `generate` input config. Exactly one
	// input-shape sub-block is allowed per Channel.
	// +optional
	Generate *BentoGenerateConfig `json:"generate,omitempty"`
}

BentoChannelConfig carries kind-specific config for kind="bento".

func (*BentoChannelConfig) DeepCopy

func (in *BentoChannelConfig) DeepCopy() *BentoChannelConfig

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BentoChannelConfig.

func (*BentoChannelConfig) DeepCopyInto

func (in *BentoChannelConfig) DeepCopyInto(out *BentoChannelConfig)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type BentoGenerateConfig

type BentoGenerateConfig struct {
	// Mapping is the bloblang script Bento evaluates per tick.
	// Assign a string to `root` (e.g. `root = "send the weekly
	// digest"`); that string becomes the inbound user prompt.
	// Avoid `root.message = "..."` — the forward output passes the
	// whole `root` value through as bytes, which means a struct-
	// shaped root becomes JSON and confuses the LLM. Optionally set
	// metadata key `routing_key` to force a specific channelKey
	// (default is `cron:<channel-name>:<ts-nanos>`, one new session
	// per firing).
	Mapping string `json:"mapping"`
	// Interval is a Bento interval string: a duration ("168h") or
	// a cron expression ("@every 24h", "0 9 * * MON").
	Interval string `json:"interval"`
	// Count caps total emissions; 0 (default) = unbounded.
	// +kubebuilder:default=0
	// +optional
	Count int64 `json:"count,omitempty"`
}

BentoGenerateConfig is the per-Channel projection of Bento's `generate` input. The bloblang `mapping` runs at trigger time; assign a string to `root` (e.g. `root = "send the weekly digest"`) and that string becomes the inbound user prompt; optional `root.routing_key` lets the mapping force a specific channelKey (default is `cron:<channel-name>:<ts-nanos>`, one new session per firing).

func (*BentoGenerateConfig) DeepCopy

func (in *BentoGenerateConfig) DeepCopy() *BentoGenerateConfig

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BentoGenerateConfig.

func (*BentoGenerateConfig) DeepCopyInto

func (in *BentoGenerateConfig) DeepCopyInto(out *BentoGenerateConfig)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type BoundChannelRef

type BoundChannelRef struct {
	Name string `json:"name"`
	Kind string `json:"kind"`
}

BoundChannelRef points at a Channel that targets this AgentClass. Reconciled by the AgentClass controller; not an authoritative source (the Channel CR is — this is a status mirror).

func (*BoundChannelRef) DeepCopy

func (in *BoundChannelRef) DeepCopy() *BoundChannelRef

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BoundChannelRef.

func (*BoundChannelRef) DeepCopyInto

func (in *BoundChannelRef) DeepCopyInto(out *BoundChannelRef)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type BoundEntityType deprecated

type BoundEntityType = AuthzSlot

BoundEntityType and EntityAutoFillArg are the pre-rename names for AuthzSlot and AuthzSlotAutoFillArg.

Kept as aliases so the rename lands as ONE reviewable diff rather than 200 mechanical edits mixed into it. They are type aliases, not new types, so the two spellings are the same type to the compiler and no conversion is needed.

Deprecated: use AuthzSlot / AuthzSlotAutoFillArg.

type BreakerSpec

type BreakerSpec struct {
	// FailureThreshold is consecutive Execute failures (per tool) that open
	// the breaker.
	// +kubebuilder:validation:Minimum=1
	// +optional
	FailureThreshold int32 `json:"failureThreshold,omitempty"`
	// OriginFailureThreshold is consecutive failures across ALL tools of the
	// tool's origin that open the origin breaker (denying every sibling).
	// +kubebuilder:validation:Minimum=1
	// +optional
	OriginFailureThreshold int32 `json:"originFailureThreshold,omitempty"`
	// InitialCoolOff is the first cool-off period after the breaker opens;
	// it doubles on each successive trip up to MaxCoolOff. Default 30s.
	// +optional
	InitialCoolOff *metav1.Duration `json:"initialCoolOff,omitempty"`
	// MaxCoolOff caps the exponential-backoff cool-off. Default 10m.
	// +optional
	MaxCoolOff *metav1.Duration `json:"maxCoolOff,omitempty"`
	// Action when the breaker denies: halt ends the session; deny returns an
	// IsError tool_result; warn logs/audits but allows; off disables the
	// breaker for matched tools.
	// +kubebuilder:validation:Enum=halt;deny;warn;off
	// +optional
	Action string `json:"action,omitempty"`
}

BreakerSpec configures the circuit breaker for matched tools. Zero-valued fields inherit the built-in defaults (threshold 5, origin threshold 10, cool-off 30s doubling to a 10m cap, action deny).

func (*BreakerSpec) DeepCopy

func (in *BreakerSpec) DeepCopy() *BreakerSpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BreakerSpec.

func (*BreakerSpec) DeepCopyInto

func (in *BreakerSpec) DeepCopyInto(out *BreakerSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type BudgetConfig

type BudgetConfig struct {
	// +kubebuilder:validation:Minimum=1
	MaxTurns int32 `json:"maxTurns"`
	// +kubebuilder:validation:Minimum=1
	MaxTokens int64 `json:"maxTokens"`
	// MaxDuration is the cumulative ACTIVE RUN-TIME budget (e.g. "30m") — the
	// wall time the agent actually spent working, excluding time parked waiting
	// on a human (the next user message or a tool-call approval). It persists
	// across sleep/resume via status.runDuration; it is NOT wall-clock since
	// session start. For a wall-clock lifetime cap, use SessionExpiration.
	// Zero = no run-time cap.
	MaxDuration metav1.Duration `json:"maxDuration"`
	// SessionExpiration is a hard wall-clock lifetime cap measured from
	// status.startedAt (e.g. "24h"), regardless of how much the agent ran.
	// The operator fails the session once exceeded, even while it is idle or
	// asleep. Zero = never expires (no default). Distinct from MaxDuration,
	// which counts only active run-time.
	// +optional
	SessionExpiration metav1.Duration `json:"sessionExpiration,omitempty"`
	// MaxDelegatedAgents bounds the TOTAL number of sessions in one delegation
	// tree, counting the root. It is a property of the tree rather than of any
	// one session: every other dimension here caps a single session's spend,
	// and a tree of N sessions each individually within budget still spends
	// N times it.
	//
	// Resolved from the ROOT session's effective settings and enforced by the
	// SubagentRequest controller before a child is created.
	//
	// Zero means UNSET, not unlimited. A consumer must apply its own built-in
	// bound rather than reading zero as "no cap" — that reading is right for a
	// DURATION (as MaxDuration uses it) and wrong for a COUNT, where it would
	// license exactly the runaway fan-out this field exists to prevent.
	// +optional
	// +kubebuilder:validation:Minimum=0
	MaxDelegatedAgents int32 `json:"maxDelegatedAgents,omitempty"`
}

func (*BudgetConfig) DeepCopy

func (in *BudgetConfig) DeepCopy() *BudgetConfig

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BudgetConfig.

func (*BudgetConfig) DeepCopyInto

func (in *BudgetConfig) DeepCopyInto(out *BudgetConfig)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type BuilderClassRef

type BuilderClassRef struct {
	// +kubebuilder:validation:MinLength=1
	Namespace string `json:"namespace"`
	// +kubebuilder:validation:MinLength=1
	Name string `json:"name"`
	// +kubebuilder:validation:MinLength=1
	SidecarToolbox string `json:"sidecarToolbox"`
}

BuilderClassRef names one sanctioned builder AgentClass and the exact SidecarToolbox CR (by name, in the class's namespace) that receives the workshop identity. All three fields are required: a sanction that did not name the sidecar would leave "which sidecar gets the token" to a naming convention, which is a guess where a boundary needs a declaration.

func (*BuilderClassRef) DeepCopy

func (in *BuilderClassRef) DeepCopy() *BuilderClassRef

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BuilderClassRef.

func (*BuilderClassRef) DeepCopyInto

func (in *BuilderClassRef) DeepCopyInto(out *BuilderClassRef)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type CallRateBound

type CallRateBound struct {
	// MaxCalls is the cap on calls inside Window.
	// +kubebuilder:validation:Minimum=1
	MaxCalls int32 `json:"maxCalls"`
	// Window is the sliding span MaxCalls is counted over. A non-positive
	// duration enforces nothing; the resolver reports one rather than folding
	// it in (pkg/platform/settings, ToolGuardRateBoundUnenforceable).
	Window metav1.Duration `json:"window"`
}

CallRateBound is one sliding-window call bound: at most MaxCalls in Window. Both halves are required, because half of a pair caps nothing (see CallRate) — expressing the pair as one struct is what makes a half-authored bound unrepresentable here, rather than something an XValidation rule has to catch.

func (*CallRateBound) DeepCopy

func (in *CallRateBound) DeepCopy() *CallRateBound

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CallRateBound.

func (*CallRateBound) DeepCopyInto

func (in *CallRateBound) DeepCopyInto(out *CallRateBound)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type Channel

type Channel struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   ChannelSpec   `json:"spec,omitempty"`
	Status ChannelStatus `json:"status,omitempty"`
}

+kubebuilder:object:root=true +kubebuilder:resource:scope=Namespaced,categories={authzed,spicebox},shortName=apch +kubebuilder:subresource:status +kubebuilder:printcolumn:name="Kind",type="string",JSONPath=".spec.kind" +kubebuilder:printcolumn:name="AgentClass",type="string",JSONPath=".spec.agentClass" +kubebuilder:printcolumn:name="Scope",type="string",JSONPath=".spec.sessionScope" +kubebuilder:printcolumn:name="Connected",type="string",JSONPath=".status.conditions[?(@.type=='Connected')].status" +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +genclient

Channel binds one transport conversation -- a Slack channel, a scheduled Bento feed, a terminal, a browser view -- to one AgentClass, and declares how inbound messages correlate to AgentSessions via spec.sessionScope.

Namespaced. Reconciled by pkg/controllers/channel, which validates the referenced Secret, AgentClass and AgentIdentity and owns the Valid condition. Transport is deliberately not the operator's: channelsd owns the Connected condition and patches it through the status subresource.

func (*Channel) DeepCopy

func (in *Channel) DeepCopy() *Channel

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Channel.

func (*Channel) DeepCopyInto

func (in *Channel) DeepCopyInto(out *Channel)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*Channel) DeepCopyObject

func (in *Channel) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type ChannelAttachmentsSpec

type ChannelAttachmentsSpec struct {
	// Enabled turns ingestion on. Must be true for bytes to move.
	Enabled bool `json:"enabled"`

	// MaxSizeBytes caps a single attachment. Zero means the kind's default.
	// +optional
	MaxSizeBytes *int64 `json:"maxSizeBytes,omitempty"`

	// MaxPerMessage caps how many attachments are ingested from one message.
	// Zero means the kind's default. Files past the cap get the oversize-style
	// notice rather than silent omission.
	// +optional
	MaxPerMessage *int32 `json:"maxPerMessage,omitempty"`
}

ChannelAttachmentsSpec bounds attachment ingestion for this Channel. The kind clamps MaxSizeBytes to its own hard maximum; a larger value is clamped, not rejected.

func (*ChannelAttachmentsSpec) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelAttachmentsSpec.

func (*ChannelAttachmentsSpec) DeepCopyInto

func (in *ChannelAttachmentsSpec) DeepCopyInto(out *ChannelAttachmentsSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ChannelBinding

type ChannelBinding struct {
	// Name is the Channel CR name (same namespace as the AgentSession).
	Name string `json:"name"`

	// Kind denormalizes Channel.spec.kind for fast filtering without a
	// secondary lookup.
	Kind string `json:"kind"`

	// Key is the channelKey computed by the kind impl (e.g.,
	// "thread:<channel_id>:<thread_ts>" or "dm:<user_id>"). Free-form;
	// the SHA-256-hashed form is on the metadata.labels for selector lookup.
	Key string `json:"key"`

	// Capabilities lists the format capabilities advertised by the kind
	// (e.g., {"text","markdown"}). The runner consumes these to shape
	// the respond_to_user tool's JSON schema.
	Capabilities []string `json:"capabilities"`

	// NATSSubjectPrefix is the full prefix shared by all of this session's
	// NATS subjects (without trailing dot, e.g., "ap.session.default.foo").
	// Denormalized so the runner doesn't have to construct it.
	NATSSubjectPrefix string `json:"natsSubjectPrefix"`

	// External carries kind-specific opaque routing metadata that the kind's
	// Sender consumes to render outbound (e.g., Slack channel_id, thread_ts).
	// Treated as opaque by the rest of the system.
	// +optional
	External map[string]string `json:"external,omitempty"`

	// InheritFrom names the prior AgentSession (same namespace) whose memory
	// channelsd copied into this session at creation time. Set only when this
	// session was spawned because the prior session for the same Channel + key
	// was archived (Succeeded) or Failed. Diagnostic / introspection only.
	// +optional
	InheritFrom string `json:"inheritFrom,omitempty"`

	// RoutingMode controls which inbound messages reach the agent. "" (the
	// default, and all a bot-originated thread ever uses) routes every message
	// in the conversation. "mention_only" routes only @-mention events and is
	// set when the bot was summoned into a pre-existing thread: the listener
	// drops non-mention replies and the sender appends an "@-mention to reply"
	// hint.
	// +kubebuilder:validation:Enum="";mention_only
	// +optional
	RoutingMode string `json:"routingMode,omitempty"`
}

ChannelBinding records a Channel reference attached to an AgentSession. The InputChannel binding records the channel-driven origin (Set once by channelsd at session creation; immutable thereafter; nil for kubectl-driven sessions). The OutputChannel binding records the outbound routing target when it differs from the input.

func OutboundBinding

func OutboundBinding(sess *AgentSession) *ChannelBinding

OutboundBinding returns the ChannelBinding anything user-facing must be addressed to: OutputChannel when the session has one (cron / split-channel), otherwise InputChannel (the ordinary case, where origin and destination are the same Channel). Nil only for a session with no channel attachment at all.

Every outbound surface must resolve through this rather than hand-rolling the fallback. Omitting it once cleared a status indicator against a bento input binding, which carries no channel_id, so the slack sender refused the clear and a cron session's "Working on the current step…" caption stuck forever. The failure mode is always that shape: correct for every same-channel session, broken only for cron.

func (*ChannelBinding) DeepCopy

func (in *ChannelBinding) DeepCopy() *ChannelBinding

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelBinding.

func (*ChannelBinding) DeepCopyInto

func (in *ChannelBinding) DeepCopyInto(out *ChannelBinding)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*ChannelBinding) SameChannelAs

func (b *ChannelBinding) SameChannelAs(other *ChannelBinding) bool

SameChannelAs reports whether other addresses the same underlying channel as b. A nil other means "same as input" (OutputChannel nil defaults to the InputChannel). When both carry a kind-native channel id in External["channel_id"], that comparison wins; otherwise it falls back to Channel CR name equality. Used to gate channel-history reads when info-leakage is off (read allowed only when output == input channel).

type ChannelCredentialsRef

type ChannelCredentialsRef struct {
	// SecretName is the Secret name in the same namespace as the Channel.
	SecretName string `json:"secretName"`
}

func (*ChannelCredentialsRef) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelCredentialsRef.

func (*ChannelCredentialsRef) DeepCopyInto

func (in *ChannelCredentialsRef) DeepCopyInto(out *ChannelCredentialsRef)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ChannelHistorySpec

type ChannelHistorySpec struct {
	// Enabled turns the capability on. Must be true to inject the tool.
	Enabled bool `json:"enabled"`

	// MaxLookback optionally caps how far back reads may reach. Zero/nil ⇒ the
	// kind's default window.
	// +optional
	MaxLookback *metav1.Duration `json:"maxLookback,omitempty"`

	// MaxMessages optionally caps messages returned per read. Zero/nil ⇒ the
	// kind's default.
	// +optional
	MaxMessages *int32 `json:"maxMessages,omitempty"`
}

ChannelHistorySpec configures whole-channel history reads for this Channel's agent. The kind clamps MaxLookback/MaxMessages to its own hard maximum (Slack ~30d); a value exceeding the kind max is clamped, not rejected.

func (*ChannelHistorySpec) DeepCopy

func (in *ChannelHistorySpec) DeepCopy() *ChannelHistorySpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelHistorySpec.

func (*ChannelHistorySpec) DeepCopyInto

func (in *ChannelHistorySpec) DeepCopyInto(out *ChannelHistorySpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ChannelIdentity

type ChannelIdentity struct {
	// Kind is the channel kind, e.g. "slack".
	Kind string `json:"kind"`
	// Domain is the workspace/org scope (Slack team_id); "" for kinds without one.
	Domain string `json:"domain"`
	// ExternalID is the channel-native user id, e.g. a Slack user_id.
	ExternalID string `json:"externalID"`
	// DisplayName is the channel-native display/real name, when known.
	// +optional
	DisplayName string `json:"displayName,omitempty"`
	// Email is the trusted email the channel vouched for, when known.
	// +optional
	Email string `json:"email,omitempty"`
}

ChannelIdentity is one channel-native identity linked to a canonical user. (Kind, Domain, ExternalID) is the unique key: Domain is the workspace/org scope (Slack team_id) that distinguishes the same person across workspaces.

func (*ChannelIdentity) DeepCopy

func (in *ChannelIdentity) DeepCopy() *ChannelIdentity

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelIdentity.

func (*ChannelIdentity) DeepCopyInto

func (in *ChannelIdentity) DeepCopyInto(out *ChannelIdentity)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ChannelList

type ChannelList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []Channel `json:"items"`
}

+kubebuilder:object:root=true

func (*ChannelList) DeepCopy

func (in *ChannelList) DeepCopy() *ChannelList

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelList.

func (*ChannelList) DeepCopyInto

func (in *ChannelList) DeepCopyInto(out *ChannelList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*ChannelList) DeepCopyObject

func (in *ChannelList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type ChannelMetaagentSpec

type ChannelMetaagentSpec struct {
	// Enabled forces metaagent membership on (true) or off (false).
	// When nil, the AgentSession controller follows the AgentClass's
	// scope.enabled flag.
	// +optional
	Enabled *bool `json:"enabled,omitempty"`
}

ChannelMetaagentSpec lets an operator override the AgentSession controller's default behavior of auto-inviting the metaagent bot to this channel when the AgentClass has scope-management enabled.

func (*ChannelMetaagentSpec) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelMetaagentSpec.

func (*ChannelMetaagentSpec) DeepCopyInto

func (in *ChannelMetaagentSpec) DeepCopyInto(out *ChannelMetaagentSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ChannelOwnerPolicy

type ChannelOwnerPolicy struct {
	// Explicit overrides the starting user for every session of this channel
	// (agent identityMode only; forbidden under userPassthrough). A SpiceDB
	// subject or subject-set ref, e.g. "user:abc" or "group:sec#member".
	// +optional
	Explicit string `json:"explicit,omitempty"`

	// Ownerless is consulted only when the channel kind provides no starting
	// user (e.g. bento).
	// +optional
	Ownerless *ChannelOwnerlessSource `json:"ownerless,omitempty"`
}

ChannelOwnerPolicy is the per-channel owner-resolution policy.

func (*ChannelOwnerPolicy) DeepCopy

func (in *ChannelOwnerPolicy) DeepCopy() *ChannelOwnerPolicy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelOwnerPolicy.

func (*ChannelOwnerPolicy) DeepCopyInto

func (in *ChannelOwnerPolicy) DeepCopyInto(out *ChannelOwnerPolicy)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ChannelOwnerlessSource

type ChannelOwnerlessSource struct {
	// Permission is a SpiceDB subject-set ref whose members become the
	// owners, e.g. "resourcetype:id#relation".
	// +optional
	Permission string `json:"permission,omitempty"`

	// FromOutputChannel links the owner to the output channel's membership
	// group (e.g. slack_channel:<id>#member), resolved via the output
	// channel kind's OwnerGroupRef capability.
	// +optional
	FromOutputChannel bool `json:"fromOutputChannel,omitempty"`
}

ChannelOwnerlessSource names the owner source for ownerless input kinds. Exactly one of the fields should be set.

func (*ChannelOwnerlessSource) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelOwnerlessSource.

func (*ChannelOwnerlessSource) DeepCopyInto

func (in *ChannelOwnerlessSource) DeepCopyInto(out *ChannelOwnerlessSource)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ChannelSpec

type ChannelSpec struct {
	// Kind selects the kind impl. "fake" is an in-process test kind;
	// "slack" and "bento" are channelsd-hosted; "local" is the
	// client-hosted TUI kind driven by `oap agent chat`; "browser" is the
	// client-hosted kind for a session a browser is looking at; "github" is
	// received by webhook at webd rather than relayed by channelsd; "agent"
	// is the channelsd-hosted kind whose counterparty is another
	// AgentSession in this cluster, not an external surface.
	// +kubebuilder:validation:Enum=fake;slack;bento;local;browser;github;agent
	Kind string `json:"kind"`

	// Role declares whether this Channel handles inbound messages, outbound
	// responses, or both; `monitoring` is a framework-event sink bound to no
	// agent.
	//
	// `output` names this Channel's DELIVERY obligation, not deafness. On a kind
	// that has a listener (slack, bento, …) a role=output Channel still runs an
	// inbound listener — only `monitoring` is listener-less — and an inbound
	// message on it routes to a same-channel-reply session (it is not treated as
	// a split-channel input, which only role=input triggers). reviewbot relies
	// on this: its slack Channel is role=output — the webhook flow's delivery
	// target, resolved by outputbind, which refuses role=both — AND the surface a
	// human uses to REQUEST a review. See pkg/channels/channelsd listener
	// reconcile (monitoring is the only skip) and the inbound pipeline's
	// role=input special-case.
	// +kubebuilder:validation:Enum=input;output;both;monitoring
	// +kubebuilder:default=both
	// +optional
	Role string `json:"role,omitempty"`

	// AuthzSubject is the non-human SpiceDB subject this Channel carries.
	// Format: "service:<id>" or "agentsession:<namespace>/<name>". A
	// "user:"/"group:" value is forbidden: on the kinds that assert it
	// the value becomes the identity the session ACTS AS, so an
	// unconstrained one would let channel config impersonate a person or
	// a group.
	//
	// What it MEANS is per-kind, and the two are not variations of one
	// thing:
	//
	// "service:" is the ACTING SUBJECT asserted for an inbound with no
	// human to attribute to. Required for any kind that cannot attribute
	// an inbound message to a human — bento (cron-spawned) and github (a
	// pull request's author has no AP identity) today; the Channel
	// controller refuses such a Channel without it, off the kind's own
	// UserAttributable() declaration rather than a per-kind list. Optional
	// for slack, which falls back to its existing per-user subject
	// resolution.
	//
	// "agentsession:" names the COUNTERPARTY END of a session-to-session
	// conversation. It is a pair-resolution input and never the acting
	// subject: channelsd resolves an agent message's Channel by requiring
	// this to name the other end of the (target, sender) pair, and
	// attributes the message to the session that SENT it — which on a
	// child-to-parent message is the end this field does not name.
	// Required when kind=agent; an inbound asserting an agentsession
	// subject over a Channel of any other kind is refused by the
	// channelsd pipeline.
	//
	// It does NOT become the session's started_by, on either kind. That
	// relation is user-typed in the SpiceDB schema, so a non-human acting
	// subject suppresses both the started_by write and the started-by
	// annotation rather than writing this value into them.
	//
	// Enforced by the Pattern below (apiserver) AND re-checked
	// fail-closed, per-kind, in the channelsd pipeline via
	// authz.ValidateSubject. Examples: "service:hubspot-digest-bot",
	// "agentsession:demo-ns/lead-1".
	// +kubebuilder:validation:Pattern=`^((service|agentsession):[A-Za-z0-9._@/-]+)?$`
	// +optional
	AuthzSubject string `json:"authzSubject,omitempty"`

	// AgentClass names the AgentClass in the same namespace this Channel
	// is bound to. 1:1 invariant — multi-agent routing is the future
	// meta-agent's job, not Channel's. Immutable post-bind. Required for
	// every role except "monitoring" (a monitoring Channel is a
	// framework-event sink, not bound to an agent); the Channel
	// controller enforces presence for non-monitoring roles.
	// +optional
	AgentClass string `json:"agentClass,omitempty"`

	// AgentIdentity overrides AgentClass.spec.agentIdentity at session
	// creation. Frozen onto the AgentSession at bind time.
	// +optional
	AgentIdentity string `json:"agentIdentity,omitempty"`

	// SessionScope governs how channelsd correlates inbound messages to
	// AgentSessions.
	// +kubebuilder:validation:Enum=auto;thread;user;singleton
	// +kubebuilder:default=auto
	SessionScope string `json:"sessionScope,omitempty"`

	// CredentialsRef points at a Secret in the same namespace holding the
	// kind-specific tokens. Required keys depend on kind.
	CredentialsRef ChannelCredentialsRef `json:"credentialsRef"`

	// Slack carries kind-specific config for kind="slack": the connection
	// mode, the resolved bot user, and the app this Channel's credentials
	// belong to. Empty/nil for other kinds.
	// +optional
	Slack *SlackChannelConfig `json:"slack,omitempty"`

	// Fake carries kind-specific config for kind="fake". Empty/nil for other
	// kinds; test-only, never appropriate in production.
	// +optional
	Fake *FakeChannelConfig `json:"fake,omitempty"`

	// Bento carries kind-specific config for kind="bento". Empty/nil
	// for other kinds.
	// +optional
	Bento *BentoChannelConfig `json:"bento,omitempty"`

	// Metaagent overrides scope-management behavior for this channel.
	// When the bound AgentClass has spec.authz.scope.enabled=true, the
	// AgentSession controller auto-invites the metaagent bot to this
	// channel unless this override sets Enabled=false. Pointer-bool so
	// the absence of the field is distinguishable from explicit false.
	// +optional
	Metaagent *ChannelMetaagentSpec `json:"metaagent,omitempty"`

	// Owner declares how sessions spawned via this Channel get their
	// agentsession#owner. Optional. Absent ⇒ owner is the channel kind's
	// starting user (slack/local/cli); a kind that provides no starter
	// (bento) then requires Owner.Ownerless or it is rejected at validation.
	// +optional
	Owner *ChannelOwnerPolicy `json:"owner,omitempty"`

	// ChannelHistory opts this Channel's bound agent into reading the whole
	// channel's prior history (not just its own thread) via the
	// read_channel_history tool. Requires the channel kind to implement
	// ChannelHistoryReader (slack does; the channel controller rejects
	// enabled=true on kinds that don't). Absent/nil ⇒ disabled.
	// +optional
	ChannelHistory *ChannelHistorySpec `json:"channelHistory,omitempty"`

	// Attachments opts this Channel's bound agent into ingesting files users
	// attach to messages. Requires the channel kind to implement
	// AttachmentFetcher and the bound AgentClass to grant the "attachments"
	// capability; all three must agree before any byte is fetched. Absent/nil
	// means disabled — the agent still learns a file was attached and says it
	// cannot read it.
	// +optional
	Attachments *ChannelAttachmentsSpec `json:"attachments,omitempty"`

	// GitHub carries kind-specific config for kind="github".
	// +optional
	GitHub *GitHubChannelConfig `json:"github,omitempty"`
}

func (*ChannelSpec) DeepCopy

func (in *ChannelSpec) DeepCopy() *ChannelSpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelSpec.

func (*ChannelSpec) DeepCopyInto

func (in *ChannelSpec) DeepCopyInto(out *ChannelSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ChannelStatus

type ChannelStatus struct {
	// ObservedGeneration is the spec generation this status reflects.
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`

	// ResolvedAgentClassUID is frozen at first successful Valid=True
	// reconcile. Protects against same-name AgentClass re-create silently
	// re-routing in-flight sessions.
	// +optional
	ResolvedAgentClassUID types.UID `json:"resolvedAgentClassUID,omitempty"`

	// Conditions carries Valid (operator-owned) and Connected/ScopesValid
	// (channelsd-owned).
	// +patchStrategy=merge
	// +patchMergeKey=type
	// +optional
	Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`

	// WebhookURLDriftCheckedAt is when the channel controller last actually
	// called out to a WebhookURLDriftChecker kind's provider (github today)
	// to compare its registered webhook URL against this cluster's — set
	// regardless of whether that call found drift, matched, or errored.
	// A controller-owned observation, not a per-check trigger: the
	// controller throttles the outbound call to at most once per interval
	// per Channel using this timestamp, so an unbounded per-reconcile call
	// against a rate limit this cluster does not control (and whose
	// exhaustion would also break the token-minting path the drift check is
	// meant to be monitoring) cannot happen. Absent means "never checked".
	// +optional
	WebhookURLDriftCheckedAt *metav1.Time `json:"webhookURLDriftCheckedAt,omitempty"`

	// RepointedWebhookURL is the webhook URL the channel controller last
	// SUCCESSFULLY wrote to this Channel's third-party provider (a GitHub
	// App's hook_attributes.url), for a Channel carrying the provenance
	// marker that says this tool registered that application. Absent means
	// no such write has ever landed — including for every Channel whose
	// application a human registered by hand, which this controller never
	// writes to at all.
	//
	// It is a controller-owned OBSERVATION, and it is what makes the repoint
	// event-driven instead of a poll: the controller compares this against
	// the URL it derives LOCALLY (the PublicEndpoint's status.url plus
	// channelevents.WebhookPathFor) and calls the provider only when the two
	// disagree. Without it, "is the registration already correct?" could be
	// answered only by reading the provider on every reconcile — against a
	// rate limit this cluster does not control, and whose exhaustion also
	// breaks the token-minting path.
	//
	// A write that FAILS is deliberately not recorded, so the next reconcile
	// retries rather than believing a URL landed that never did. Losing this
	// value (a status wipe, a restore from an older object) costs one
	// redundant write of the value the provider most likely already has, not
	// correctness.
	// +optional
	RepointedWebhookURL string `json:"repointedWebhookURL,omitempty"`

	// DerivedSessionOwner is the subject-set that will own sessions inbound on
	// this Channel because nothing else supplies an owner: the kind provides no
	// starting user, spec.owner declares no source, and the Channel this
	// agent's output lands in yields a membership subject-set
	// (slack_channel:<id>#member for a Slack destination).
	//
	// Present only when the derivation actually fired. A Channel that declared
	// spec.owner.explicit or spec.owner.ownerless keeps what it declared and
	// leaves this empty, as does one whose kind names a starting user — so a
	// non-empty value always reads as "nobody wrote this owner; it was derived,
	// and here is from what".
	//
	// It is published because of what an owner CARRIES. `owner` is not merely
	// interact: manage_scope, fork, approve, manage_budget and manage_model all
	// resolve through it, so a derived channel-membership owner hands every
	// member of that Slack channel the approve button on this agent's tool
	// calls. That is the same population the agent posts every one of its
	// messages to, and it is what an operator configuring this by hand chose —
	// but it must be visible on the object rather than inferable only from the
	// absence of a field.
	// +optional
	DerivedSessionOwner string `json:"derivedSessionOwner,omitempty"`
}

func (*ChannelStatus) DeepCopy

func (in *ChannelStatus) DeepCopy() *ChannelStatus

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelStatus.

func (*ChannelStatus) DeepCopyInto

func (in *ChannelStatus) DeepCopyInto(out *ChannelStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ChannelsConfig

type ChannelsConfig struct {
	// IdleTTL is how long an idle, channel-attached runner blocks in
	// await_user_message before exiting cleanly to phase=Idle.
	// Default 5m. Zero disables — the runner exits as soon as the
	// agent yields, useful for one-shot cron-driven agents.
	// +kubebuilder:default="5m"
	// +optional
	IdleTTL metav1.Duration `json:"idleTTL,omitempty"`

	// ArchiveAfter is how long an Idle, channel-attached session waits before
	// the operator transitions it to phase=Succeeded. After archive, the next
	// channel inbound for the same Channel + key spawns a fresh AgentSession
	// that inherits the prior session's memory turn-by-turn at creation.
	// Defaults to the operator's --default-channel-archive-after flag
	// (operator default: 4h). Set to 0 to disable archival (Idle indefinitely;
	// useful for tests).
	// +optional
	ArchiveAfter metav1.Duration `json:"archiveAfter,omitempty"`

	// StorageRetention is how long a TERMINAL (Succeeded/Failed) session's
	// workspace and snapshot-store PVCs are kept past FinishedAt before the
	// operator deletes them. The volumes are re-creatable scratch — a woken
	// or restarted session provisions fresh, empty ones on demand — and the
	// session record itself (transcript, memory, relationships) is untouched.
	// Defaults to the operator's --session-storage-retention flag (operator
	// default: 72h; flag 0 disables the sweep cluster-wide). A per-class
	// value here overrides the flag when > 0, same precedence ArchiveAfter
	// follows.
	// +optional
	StorageRetention metav1.Duration `json:"storageRetention,omitempty"`

	// SleepAfter is how long an Idle, channel-attached session keeps its pods
	// warm before the operator reaps ALL of them (sandboxes, runner, detector)
	// while keeping the session Idle and wakeable. On the next inbound the pods
	// are re-created lazily, re-mounting the shared workspace PVC. Must be well
	// under ArchiveAfter. Defaults to the operator's --default-session-sleep-after
	// flag (operator default: 10m). Set to 0 to keep pods warm for the whole Idle
	// window (never sleep).
	// +optional
	SleepAfter metav1.Duration `json:"sleepAfter,omitempty"`

	// ShowAssistantStream renders the agent's live LLM stream (free-form
	// text deltas + tool-use markers) into the channel placeholder while
	// the agent is working. Default false. With this off, the only things
	// that reach the channel between user turns are update_status (the
	// rolling caption / thinking-bubble) and update_plan (the structured
	// checklist). Turn on for debugging — the stream is verbose and
	// includes the agent's internal narrative + every tool call.
	// +kubebuilder:default=false
	// +optional
	ShowAssistantStream bool `json:"showAssistantStream,omitempty"`
}

ChannelsConfig governs how a channel-attached agent behaves.

func (*ChannelsConfig) DeepCopy

func (in *ChannelsConfig) DeepCopy() *ChannelsConfig

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelsConfig.

func (*ChannelsConfig) DeepCopyInto

func (in *ChannelsConfig) DeepCopyInto(out *ChannelsConfig)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ClusterAgentSettings

type ClusterAgentSettings struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   SettingsSpec   `json:"spec,omitempty"`
	Status SettingsStatus `json:"status,omitempty"`
}

+kubebuilder:object:root=true +kubebuilder:resource:scope=Cluster,categories={authzed,spicebox},shortName=clsagset +kubebuilder:subresource:status +kubebuilder:printcolumn:name="Consistent",type="string",JSONPath=".status.conditions[?(@.type=='SelfConsistent')].status" +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +genclient +genclient:nonNamespaced

ClusterAgentSettings is the cluster-wide top tier of agent governance settings: the limits and defaults every namespace inherits, plus the model catalog. Singleton -- the only permitted name is "cluster" (ClusterAgentSettingsName).

Cluster-scoped. Reconciled by pkg/controllers/settings, which reports SelfConsistent. Tier resolution lives in pkg/platform/settings; limits set here are ceilings that lower tiers may narrow but never widen.

func (*ClusterAgentSettings) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterAgentSettings.

func (*ClusterAgentSettings) DeepCopyInto

func (in *ClusterAgentSettings) DeepCopyInto(out *ClusterAgentSettings)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*ClusterAgentSettings) DeepCopyObject

func (in *ClusterAgentSettings) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type ClusterAgentSettingsList

type ClusterAgentSettingsList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []ClusterAgentSettings `json:"items"`
}

+kubebuilder:object:root=true

func (*ClusterAgentSettingsList) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterAgentSettingsList.

func (*ClusterAgentSettingsList) DeepCopyInto

func (in *ClusterAgentSettingsList) DeepCopyInto(out *ClusterAgentSettingsList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*ClusterAgentSettingsList) DeepCopyObject

func (in *ClusterAgentSettingsList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type ClusterIdentityProvider

type ClusterIdentityProvider struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`
	Spec              ClusterIdentityProviderSpec   `json:"spec,omitempty"`
	Status            ClusterIdentityProviderStatus `json:"status,omitempty"`
}

+kubebuilder:object:root=true +kubebuilder:resource:scope=Cluster,categories={authzed,spicebox},shortName=cidp +kubebuilder:subresource:status +kubebuilder:printcolumn:name="Valid",type="string",JSONPath=".status.conditions[?(@.type=='Valid')].status" +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +genclient +genclient:nonNamespaced

ClusterIdentityProvider configures cluster-wide human login: which registered idp.Kind authenticates people (pkg/platform/identity/idp), its OIDC client details, which email domains are admitted, and how long a browser session lives.

Cluster-scoped singleton -- the only permitted name is "default" (ClusterIdentityProviderName). Reconciled by pkg/controllers/clusteridentityprovider, which shares its spec judgment with the admission webhook so the two can never disagree, and refuses a local-only kind on a non-local cluster.

func (*ClusterIdentityProvider) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterIdentityProvider.

func (*ClusterIdentityProvider) DeepCopyInto

func (in *ClusterIdentityProvider) DeepCopyInto(out *ClusterIdentityProvider)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*ClusterIdentityProvider) DeepCopyObject

func (in *ClusterIdentityProvider) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type ClusterIdentityProviderList

type ClusterIdentityProviderList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []ClusterIdentityProvider `json:"items"`
}

ClusterIdentityProviderList contains a list of ClusterIdentityProvider.

func (*ClusterIdentityProviderList) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterIdentityProviderList.

func (*ClusterIdentityProviderList) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*ClusterIdentityProviderList) DeepCopyObject

func (in *ClusterIdentityProviderList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type ClusterIdentityProviderSpec

type ClusterIdentityProviderSpec struct {
	// Kind names the registered idp kind: "oidc" or "google".
	// +kubebuilder:validation:MinLength=1
	Kind string `json:"kind"`

	// Issuer is the OIDC issuer URL. Required for kind=oidc; must be
	// empty for kind=google (the kind pins it). Enforced by webhook.
	// +optional
	Issuer string `json:"issuer,omitempty"`

	// ClientID is the OAuth client id (not secret; inline).
	// +kubebuilder:validation:MinLength=1
	ClientID string `json:"clientID"`

	// ClientSecretRef names the Secret key holding the client secret.
	ClientSecretRef ClusterSecretKeyRef `json:"clientSecretRef"`

	// Scopes are extra scopes beyond openid/email/profile.
	// +optional
	Scopes []string `json:"scopes,omitempty"`

	// AllowedEmailDomains gates who counts as logged in. Empty with
	// AllowAnyEmail=false is invalid (fail closed) — enforced by webhook.
	// +optional
	AllowedEmailDomains []string `json:"allowedEmailDomains,omitempty"`

	// AllowAnyEmail must be set explicitly to run with no domain gate.
	// +optional
	AllowAnyEmail bool `json:"allowAnyEmail,omitempty"`

	// SessionTTL is the idd_session cookie lifetime for IdP-verified
	// logins (and the CLI assertion lifetime). Default 12h.
	// +optional
	SessionTTL *metav1.Duration `json:"sessionTTL,omitempty"`

	// Federation, when Enabled, turns this IdP into an EMA federation
	// source: login requests offline_access so the user's refresh token is
	// captured, and the broker mints upstream tokens via ID-JAG. Requires
	// kind=oidc and a confidential client (see the webhook).
	// +optional
	Federation *FederationConfig `json:"federation,omitempty"`
}

ClusterIdentityProviderSpec configures the cluster's human-login identity provider. The kind is dispatched through pkg/platform/identity/idp/registry.

func (*ClusterIdentityProviderSpec) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterIdentityProviderSpec.

func (*ClusterIdentityProviderSpec) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ClusterIdentityProviderStatus

type ClusterIdentityProviderStatus struct {
	// Conditions carries Valid; identityd consults it before serving login.
	// +optional
	Conditions []metav1.Condition `json:"conditions,omitempty"`
	// ObservedGeneration is the generation the conditions describe.
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`
}

ClusterIdentityProviderStatus reports validity via conditions.

func (*ClusterIdentityProviderStatus) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterIdentityProviderStatus.

func (*ClusterIdentityProviderStatus) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ClusterSecretKeyRef

type ClusterSecretKeyRef struct {
	// Namespace is the Secret's namespace; required, since the referring CRD is
	// cluster-scoped.
	Namespace string `json:"namespace"`
	// Name is the Secret's name.
	Name string `json:"name"`
	// Key is the data key within the Secret holding the value.
	Key string `json:"key"`
}

ClusterSecretKeyRef points at a single key of a Secret in an explicit namespace — needed because the referring CRD is cluster-scoped and has no namespace of its own.

func (*ClusterSecretKeyRef) DeepCopy

func (in *ClusterSecretKeyRef) DeepCopy() *ClusterSecretKeyRef

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterSecretKeyRef.

func (*ClusterSecretKeyRef) DeepCopyInto

func (in *ClusterSecretKeyRef) DeepCopyInto(out *ClusterSecretKeyRef)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ClusterSkill

type ClusterSkill struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`
	Spec              SkillSpec   `json:"spec,omitempty"`
	Status            SkillStatus `json:"status,omitempty"`
}

+kubebuilder:object:root=true +kubebuilder:resource:scope=Cluster,categories={authzed,spicebox},shortName=cskl +kubebuilder:subresource:status +kubebuilder:printcolumn:name="Valid",type="string",JSONPath=".status.conditions[?(@.type=='Valid')].status" +kubebuilder:printcolumn:name="Canonical",type="string",JSONPath=".spec.canonicalName" +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +genclient +genclient:nonNamespaced

ClusterSkill is a cluster-scoped Skill, visible to every namespace. A namespaced Skill with the same canonical name shadows it at resolution time.

Cluster-scoped. Reconciled by pkg/controllers/clusterskill, which only keeps the Valid and Pinned conditions honest -- it never pulls or stages content, because a ClusterSkill is fully described by its spec. Discovery and materialization belong to the ClusterSkillSource controller.

func (*ClusterSkill) DeepCopy

func (in *ClusterSkill) DeepCopy() *ClusterSkill

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterSkill.

func (*ClusterSkill) DeepCopyInto

func (in *ClusterSkill) DeepCopyInto(out *ClusterSkill)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*ClusterSkill) DeepCopyObject

func (in *ClusterSkill) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

func (*ClusterSkill) SkillSpec

func (s *ClusterSkill) SkillSpec() *SkillSpec

SkillSpec returns a pointer to the embedded spec, letting the shared skill validity reconciler (pkg/controllers/internal/skillspec) drive ClusterSkill and Skill — which carry the same SkillSpec — through one interface.

func (*ClusterSkill) SkillStatus

func (s *ClusterSkill) SkillStatus() *SkillStatus

SkillStatus returns a pointer to the embedded status. See SkillSpec.

type ClusterSkillList

type ClusterSkillList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []ClusterSkill `json:"items"`
}

+kubebuilder:object:root=true

func (*ClusterSkillList) DeepCopy

func (in *ClusterSkillList) DeepCopy() *ClusterSkillList

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterSkillList.

func (*ClusterSkillList) DeepCopyInto

func (in *ClusterSkillList) DeepCopyInto(out *ClusterSkillList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*ClusterSkillList) DeepCopyObject

func (in *ClusterSkillList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type ClusterSkillSource

type ClusterSkillSource struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`
	Spec              ClusterSkillSourceSpec `json:"spec,omitempty"`
	Status            SkillSourceStatus      `json:"status,omitempty"`
}

+kubebuilder:object:root=true +kubebuilder:resource:scope=Cluster,categories={authzed,spicebox},shortName=csksrc +kubebuilder:subresource:status +kubebuilder:printcolumn:name="Repo",type="string",JSONPath=".spec.repoURL" +kubebuilder:printcolumn:name="SHA",type="string",JSONPath=".status.resolvedSHA" +kubebuilder:printcolumn:name="Skills",type="integer",JSONPath=".status.discoveredSkills" +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +genclient +genclient:nonNamespaced

ClusterSkillSource is a git repo that materializes cluster-scoped ClusterSkills. Auth is a direct Secret reference, because AgentIdentity is namespaced and so unavailable at cluster scope.

Cluster-scoped. Reconciled by pkg/controllers/clusterskillsource, which clones at spec.ref, discovers SKILL.md files under spec.subpath, caches each bundle by content digest, and owns the ClusterSkills it creates. It is the cluster-scoped mirror of pkg/controllers/skillsource.

func (*ClusterSkillSource) DeepCopy

func (in *ClusterSkillSource) DeepCopy() *ClusterSkillSource

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterSkillSource.

func (*ClusterSkillSource) DeepCopyInto

func (in *ClusterSkillSource) DeepCopyInto(out *ClusterSkillSource)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*ClusterSkillSource) DeepCopyObject

func (in *ClusterSkillSource) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type ClusterSkillSourceAuth

type ClusterSkillSourceAuth struct {
	SecretRef SecretKeyRef `json:"secretRef"` // {Name, Key}
	// Namespace of the Secret (required — cluster scope has no implicit namespace).
	Namespace string `json:"namespace"`
}

func (*ClusterSkillSourceAuth) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterSkillSourceAuth.

func (*ClusterSkillSourceAuth) DeepCopyInto

func (in *ClusterSkillSourceAuth) DeepCopyInto(out *ClusterSkillSourceAuth)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ClusterSkillSourceList

type ClusterSkillSourceList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []ClusterSkillSource `json:"items"`
}

+kubebuilder:object:root=true

func (*ClusterSkillSourceList) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterSkillSourceList.

func (*ClusterSkillSourceList) DeepCopyInto

func (in *ClusterSkillSourceList) DeepCopyInto(out *ClusterSkillSourceList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*ClusterSkillSourceList) DeepCopyObject

func (in *ClusterSkillSourceList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type ClusterSkillSourceSpec

type ClusterSkillSourceSpec struct {
	RepoURL string `json:"repoURL"`
	// +optional
	Ref string `json:"ref,omitempty"`
	// +optional
	Subpath string `json:"subpath,omitempty"`
	// Auth references a Secret holding the clone token (e.g. a github PAT).
	// +optional
	Auth *ClusterSkillSourceAuth `json:"auth,omitempty"`
	// +optional
	Sync SkillSourceSync `json:"sync,omitempty"`
	// DisableRepoInstructions, when true, stops this source from discovering and
	// attaching the repo-root agent-instructions file (AGENTS.md/CLAUDE.md) to
	// the skills it materializes. Default (false) = enabled.
	// +optional
	DisableRepoInstructions bool `json:"disableRepoInstructions,omitempty"`
}

func (*ClusterSkillSourceSpec) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterSkillSourceSpec.

func (*ClusterSkillSourceSpec) DeepCopyInto

func (in *ClusterSkillSourceSpec) DeepCopyInto(out *ClusterSkillSourceSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type CompletionBypass

type CompletionBypass struct {
	// Time is when the bypass was recorded.
	Time metav1.Time `json:"time"`
	// Reason is the agent's stated justification. AGENT-AUTHORED and
	// UNTRUSTED — a surface showing it to a person renders it inert.
	Reason string `json:"reason"`
	// Requirements are the requirement keys that were unmet, so an operator can
	// tell "the report never went out" from "the plan was left half-finished"
	// without reading prose.
	// +optional
	// +listType=atomic
	Requirements []string `json:"requirements,omitempty"`
	// Details is the per-requirement account of what was missing, in the same
	// order as Requirements.
	// +optional
	// +listType=atomic
	Details []string `json:"details,omitempty"`
}

CompletionBypass is one recorded override of this class's completion requirements: what was outstanding, why the agent finished anyway, and when.

func (*CompletionBypass) DeepCopy

func (in *CompletionBypass) DeepCopy() *CompletionBypass

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompletionBypass.

func (*CompletionBypass) DeepCopyInto

func (in *CompletionBypass) DeepCopyInto(out *CompletionBypass)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ConfigKeySchema

type ConfigKeySchema struct {
	// Name is the config key: the map key in spec.config and the identifier a
	// toolspec constraint reads as `config.<Name>`.
	Name string `json:"name"`

	// Type is the value's shape.
	// +kubebuilder:validation:Enum=string;stringList;int;bool;enum
	Type string `json:"type"`

	// Enum is the permitted set: the allowed value when Type=enum, or the
	// allowed item vocabulary when Type=stringList. Empty ⇒ open.
	// +optional
	Enum []string `json:"enum,omitempty"`

	// Pattern is a regexp every string value (or stringList item) must match.
	// +optional
	Pattern string `json:"pattern,omitempty"`

	// Required fails validation when the key is absent from spec.config.
	// +optional
	Required bool `json:"required,omitempty"`
}

ConfigKeySchema declares one AgentClass.spec.config key's contract. The AgentClass controller validates spec.config against these, and cross-checks that every `config.X` a bound toolspec constraint references is declared.

func (*ConfigKeySchema) DeepCopy

func (in *ConfigKeySchema) DeepCopy() *ConfigKeySchema

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigKeySchema.

func (*ConfigKeySchema) DeepCopyInto

func (in *ConfigKeySchema) DeepCopyInto(out *ConfigKeySchema)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ConfigMapKeyRef

type ConfigMapKeyRef struct {
	// Name is the ConfigMap's name.
	Name string `json:"name"`
	// Key is the data key within the ConfigMap.
	Key string `json:"key"`
}

ConfigMapKeyRef points at a single key inside a ConfigMap in the same namespace as the referencing CR.

func (*ConfigMapKeyRef) DeepCopy

func (in *ConfigMapKeyRef) DeepCopy() *ConfigMapKeyRef

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapKeyRef.

func (*ConfigMapKeyRef) DeepCopyInto

func (in *ConfigMapKeyRef) DeepCopyInto(out *ConfigMapKeyRef)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ContentInspectorConfig

type ContentInspectorConfig struct {
	// ID must resolve in the contentguard registry (e.g. "url-allowlist").
	// +kubebuilder:validation:MinLength=1
	ID string `json:"id"`
	// Config is inspector-specific configuration, validated by the inspector's
	// Configure at admission (fail-closed) and at session start.
	Config apiextv1.JSON `json:"config"`
}

ContentInspectorConfig references a registered content-guard inspector by ID and carries its opaque per-instance config (parsed by the inspector).

func (*ContentInspectorConfig) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContentInspectorConfig.

func (*ContentInspectorConfig) DeepCopyInto

func (in *ContentInspectorConfig) DeepCopyInto(out *ContentInspectorConfig)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type CredentialAuthFailure

type CredentialAuthFailure struct {
	// Origin is the failing tool's tool.OriginTool.Origin() value, e.g.
	// "mcpserver/github" — the same vocabulary CredentialUpdateRequestSpec.Origin
	// uses, so the reconciler can match an observation to a request without a
	// second resolution step.
	Origin string `json:"origin"`

	// Count is how many consecutive auth-shaped failures the runner had seen
	// at this origin when the observation was recorded. It is NOT kept live:
	// later failures increment only the runner's in-memory counter, so this
	// stays at its transition-time value until a success clears the entry and
	// a fresh failure records a new one.
	// +optional
	Count int32 `json:"count,omitempty"`

	// ObservedAt is when the runner recorded this observation.
	// +optional
	ObservedAt *metav1.Time `json:"observedAt,omitempty"`
}

CredentialAuthFailure is one origin's auth-shaped-failure observation — the platform's OWN evidence that a credential stopped authenticating, as distinct from the agent's (untrusted) claim in a CredentialUpdateRequest.

Written EXCLUSIVELY by the runner, the only component that sees a tool call's upstream outcome, and only on the TRANSITION into the failing state: the counter lives in runner memory, so a long run of failures is one status write, not one per call.

Presence is the whole signal, and there is deliberately no "cleared" tombstone — a success REMOVES the entry (RemoveCredentialAuthFailure), because an observation outliving the failure it describes would corroborate a credential that demonstrably still works.

func FindCredentialAuthFailure

func FindCredentialAuthFailure(list []CredentialAuthFailure, origin string) *CredentialAuthFailure

FindCredentialAuthFailure returns origin's observation, or nil when there is none — the operator's READ half of the runner-written list, used by the CredentialUpdateRequest reconciler to decide whether the platform has its own evidence backing the agent's claim (credupdate.Input.Corroborated).

Presence IS the signal, so nothing about Count or ObservedAt enters the verdict; the entry is returned rather than a bool only so a caller can log WHY it corroborated.

An EMPTY origin never matches: such a request resolves to no credential anyway, and an empty key must not collide with a malformed entry and manufacture corroboration out of a list this function does not own.

func RemoveCredentialAuthFailure

func RemoveCredentialAuthFailure(list []CredentialAuthFailure, origin string) []CredentialAuthFailure

RemoveCredentialAuthFailure drops origin's observation, if present — the clear-on-success half of CredentialAuthFailure's invariant: a credential that just worked must stop corroborating a replacement request.

Removing the last entry returns nil, not an empty slice, so `omitempty` drops the field and the merge patch carries a deletion rather than an empty array, matching the other runner-owned observation lists.

func UpsertCredentialAuthFailure

func UpsertCredentialAuthFailure(list []CredentialAuthFailure, entry CredentialAuthFailure) []CredentialAuthFailure

UpsertCredentialAuthFailure replaces (by Origin) or appends one auth-failure observation, mirroring UpsertSidecarReachability. Runner-owned; the operator never writes this list.

func (*CredentialAuthFailure) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialAuthFailure.

func (*CredentialAuthFailure) DeepCopyInto

func (in *CredentialAuthFailure) DeepCopyInto(out *CredentialAuthFailure)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type CredentialDescriptor

type CredentialDescriptor struct {
	// Source locates the credential's backing Secret.
	Source CredentialSource `json:"source"`
	// Inject describes how to project the resolved token.
	Inject CredentialInjection `json:"inject"`
}

CredentialDescriptor is a self-contained, resolvable credential need: a reference to the backing Secret plus how to inject the resolved token. It carries references only — never token bytes. It is the unit the token broker resolves and the element type of ToolCall.spec.credentials.

func (*CredentialDescriptor) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialDescriptor.

func (*CredentialDescriptor) DeepCopyInto

func (in *CredentialDescriptor) DeepCopyInto(out *CredentialDescriptor)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type CredentialExplanation

type CredentialExplanation struct {
	// Items is one entry per missing credential, in stable order.
	// +optional
	Items []CredentialExplanationItem `json:"items,omitempty"`
}

CredentialExplanation is the user-facing description of a credential request, one Item per missing credential. Populated by the operator's BuildExplanation; rendered by channelsd (Slack DM) and identityd (portal menu).

func (*CredentialExplanation) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialExplanation.

func (*CredentialExplanation) DeepCopyInto

func (in *CredentialExplanation) DeepCopyInto(out *CredentialExplanation)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type CredentialExplanationItem

type CredentialExplanationItem struct {
	// Credential is the final (post-remap) credential name this row is for.
	Credential string `json:"credential"`
	// Title is the user-facing service/token display name ("GitHub").
	// +optional
	Title string `json:"title,omitempty"`
	// Description is the "what is this token" sentence.
	// +optional
	Description string `json:"description,omitempty"`
	// Why is the agent-specific reason. EMPTY is meaningful: it signals
	// "no explicit reason declared" so the render surface applies its own
	// fallback (channelsd: LLM then static; identityd: static). A non-empty
	// Why is rendered verbatim and never sent to the LLM.
	// +optional
	Why string `json:"why,omitempty"`
}

CredentialExplanationItem is one credential row: a service Title, a Description of what the token is, and an agent-specific Why.

func (*CredentialExplanationItem) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialExplanationItem.

func (*CredentialExplanationItem) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type CredentialExplanationSpec

type CredentialExplanationSpec struct {
	// Credential is the final (post-remap) credential name this entry
	// describes, e.g. "github-token".
	Credential string `json:"credential"`

	// Reason is the agent-specific "why we need this" sentence rendered
	// under the credential's title.
	// +optional
	Reason string `json:"reason,omitempty"`
}

CredentialExplanationSpec is one AgentClass-authored reason a userPassthrough agent needs a credential. Surfaced as the "why" line on the credential-request prompt.

func (*CredentialExplanationSpec) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialExplanationSpec.

func (*CredentialExplanationSpec) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type CredentialInjection

type CredentialInjection struct {
	// EnvVar projects the token into this environment variable
	// (CLI / toolspec tools).
	// +optional
	EnvVar string `json:"envVar,omitempty"`
	// Header projects the token into this HTTP header (MCP tools).
	// +optional
	Header *HeaderInjection `json:"header,omitempty"`
}

CredentialInjection describes how to project a resolved token. Exactly one of EnvVar / Header is set.

func (*CredentialInjection) DeepCopy

func (in *CredentialInjection) DeepCopy() *CredentialInjection

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialInjection.

func (*CredentialInjection) DeepCopyInto

func (in *CredentialInjection) DeepCopyInto(out *CredentialInjection)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type CredentialSource

type CredentialSource struct {
	// Type drives the read and expiry rules; see the type doc.
	// +kubebuilder:validation:Enum=static;oauth;federated;githubApp
	Type string `json:"type"`
	// Namespace is the Secret's namespace; must equal the ToolCall's own, which
	// ToolCall.ValidateCredentialSourceNamespaces enforces.
	Namespace string `json:"namespace"`
	// Name is the Secret name. For type=federated it is the IdP-identity
	// Secret (the subject token source), NOT the upstream token.
	Name string `json:"name"`
	// Key is the Secret data key for type=static; ignored otherwise.
	// +optional
	Key string `json:"key,omitempty"`
	// Resource / ResourceServerURL / Scopes are set only for
	// type=federated and drive the ID-JAG mint.
	// +optional
	Resource string `json:"resource,omitempty"`
	// +optional
	ResourceServerURL string `json:"resourceServerURL,omitempty"`
	// +optional
	Scopes []string `json:"scopes,omitempty"`
}

CredentialSource references one credential's backing Secret. Type drives the read + expiry rules: "static" reads Key; "oauth" reads the fixed multi-key OAuth shape and gates on expires_at.

func (*CredentialSource) DeepCopy

func (in *CredentialSource) DeepCopy() *CredentialSource

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialSource.

func (*CredentialSource) DeepCopyInto

func (in *CredentialSource) DeepCopyInto(out *CredentialSource)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type CredentialUpdateRequest

type CredentialUpdateRequest struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   CredentialUpdateRequestSpec   `json:"spec,omitempty"`
	Status CredentialUpdateRequestStatus `json:"status,omitempty"`
}

+kubebuilder:object:root=true +kubebuilder:resource:scope=Namespaced,categories={authzed,agentprimitives},shortName=credupdate +kubebuilder:subresource:status +kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase" +kubebuilder:printcolumn:name="Determination",type="string",JSONPath=".status.determination" +kubebuilder:printcolumn:name="Origin",type="string",JSONPath=".spec.origin" +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +genclient

CredentialUpdateRequest is an agent's REQUEST that a human replace a credential it believes has stopped authenticating. The spec names only the failing TOOL -- the agent has no vocabulary for naming an identity, which is what stops it pointing at a credential it was not already using.

Namespaced. Reconciled by pkg/controllers/credentialupdaterequest, which independently re-verifies before any card is shown and refuses outright when the credential still authenticates. It is a request, never a decision: the verdict table lives in pkg/platform/identity/credupdate, and this CR only records what was asked and what was determined.

func (*CredentialUpdateRequest) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialUpdateRequest.

func (*CredentialUpdateRequest) DeepCopyInto

func (in *CredentialUpdateRequest) DeepCopyInto(out *CredentialUpdateRequest)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*CredentialUpdateRequest) DeepCopyObject

func (in *CredentialUpdateRequest) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type CredentialUpdateRequestList

type CredentialUpdateRequestList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []CredentialUpdateRequest `json:"items"`
}

+kubebuilder:object:root=true

func (*CredentialUpdateRequestList) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialUpdateRequestList.

func (*CredentialUpdateRequestList) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*CredentialUpdateRequestList) DeepCopyObject

func (in *CredentialUpdateRequestList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type CredentialUpdateRequestSpec

type CredentialUpdateRequestSpec struct {
	// SessionRef is the AgentSession that parked on this request. The CR also
	// carries an owner-ref to it, so the request lives exactly as long as the
	// session does — which is what makes the ask budget derivable by listing
	// rather than storing a counter.
	SessionRef NamespacedRef `json:"sessionRef"`

	// Origin is the failing tool's tool.OriginTool.Origin() value, e.g.
	// "mcpserver/github". The reconciler resolves this to a credential.
	Origin string `json:"origin"`

	// ToolName is the failing tool's LLM-facing name, recorded for the card
	// and for audit. Not used for resolution.
	ToolName string `json:"toolName"`

	// Why is the agent's own explanation. UNTRUSTED and agent-authored: it is
	// the ONLY agent-written string that ever reaches the card, where it is
	// rendered in a visually distinct attributed block. Capped by the meta
	// tool before it is written here.
	// +optional
	// +kubebuilder:validation:MaxLength=280
	Why string `json:"why,omitempty"`

	// RequestedBy is the canonical subject of the turn's author.
	RequestedBy identity.Subject `json:"requestedBy"`
}

func (*CredentialUpdateRequestSpec) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialUpdateRequestSpec.

func (*CredentialUpdateRequestSpec) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type CredentialUpdateRequestStatus

type CredentialUpdateRequestStatus struct {
	// ObservedGeneration is the spec generation this status reflects.
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`

	// Phase is the request's lifecycle position. Fulfilled, Refused and
	// Expired are terminal — see IsCredentialUpdateRequestTerminal.
	// +optional
	Phase string `json:"phase,omitempty"`

	// Determination records WHY the reconciler opened or refused. Surfaced to
	// the agent verbatim, so a refusal is never silent.
	// +optional
	Determination string `json:"determination,omitempty"`

	// Reason is the human-readable determination text. It doubles as the
	// card's verdict line, and is platform-authored in every case.
	// +optional
	Reason string `json:"reason,omitempty"`

	// ResolvedCredential is what Origin resolved to. Empty when resolution
	// itself failed (NoCredential / AmbiguousCredential).
	// +optional
	ResolvedCredential *ResolvedCredentialRef `json:"resolvedCredential,omitempty"`

	// InteractionRef is the requestRef of the published interaction, set only
	// when the request opened a card.
	// +optional
	InteractionRef string `json:"interactionRef,omitempty"`

	// OpenedAt is when the reconciler transitioned this request to Open — the
	// instant a human's wait window actually starts. A controller-owned
	// observation, written ONCE on that transition and never rewritten.
	//
	// Deliberately not CreationTimestamp: creation and determination are
	// separated by however long the pipeline took (an operator restart, minutes
	// of probe backoff against an unreachable provider), and measuring from
	// creation let a deadline elapse before the card existed — Open written,
	// channelsd publishing, and the next reconcile expiring it, blaming a human
	// who had no window at all. Empty on requests opened before this field
	// existed, where CreationTimestamp remains the fallback.
	// +optional
	OpenedAt *metav1.Time `json:"openedAt,omitempty"`

	// CredentialSecretRef names the backing Secret the resolved credential's
	// value lives in, recorded once the request opens a card. It is what the
	// reconciler's Secret watch uses to find the CredentialUpdateRequest(s) a
	// changed Secret should unpark -- an OBSERVATION (set once, never
	// user-supplied), not part of the ask itself.
	// +optional
	CredentialSecretRef *NamespacedRef `json:"credentialSecretRef,omitempty"`

	// CredentialSecretKey is the Secret data key the resolved credential
	// occupies, written alongside CredentialSecretRef. Together they are the
	// credential's full backing coordinates — namespace, name, key — the only
	// handle that identifies a credential rather than the identity CR it was
	// reached through.
	//
	// Empty for a type=oauth credential, whose value is the WHOLE Secret rather
	// than one key of it; that emptiness is the discriminator, not a missing
	// value. A non-empty key scopes change-detection to that key alone, because
	// the operator projects EVERY static credential of a session into one
	// shared Secret and a whole-Secret digest cannot tell "this credential was
	// replaced" from "some other credential was linked".
	// +optional
	CredentialSecretKey string `json:"credentialSecretKey,omitempty"`

	// CollapsedInto names the CredentialUpdateRequest whose card this request is
	// waiting on, set when the reconciler resolved to a credential another
	// request ALREADY holds a live card for: five sessions sharing one dead bot
	// token must raise one card at one admin, not five. Empty on every request
	// that owns its own card, so "is this a follower?" is one nil check.
	//
	// Stored on the FOLLOWER, never mirrored as a list on the canonical: one
	// writer per field (a follower's own reconcile sets its own pointer, so no
	// two contend), and the canonical's follower set stays DERIVED by listing
	// rather than stored and kept in sync, because a mirrored list desyncs.
	// +optional
	CollapsedInto *NamespacedRef `json:"collapsedInto,omitempty"`

	// CredentialSecretObservedHash is a SHA-256 digest of the backing Secret's
	// Data as observed when the request opened. The Secret watch compares the
	// CURRENT hash against this recorded baseline -- a real content change
	// (not an unrelated metadata/label edit that only bumps resourceVersion)
	// is what marks the request Fulfilled.
	// +optional
	CredentialSecretObservedHash string `json:"credentialSecretObservedHash,omitempty"`

	// Conditions carries CardDelivered; see its type constant.
	// +patchStrategy=merge
	// +patchMergeKey=type
	// +optional
	Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}

func (*CredentialUpdateRequestStatus) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialUpdateRequestStatus.

func (*CredentialUpdateRequestStatus) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type CrossAgentThreadsConfig

type CrossAgentThreadsConfig struct {
	// Enabled admits ANOTHER agent's messages. It never admits this agent's
	// own posts, in any configuration — that is a self-loop, and the listener
	// decides self before it decides bot for exactly that reason.
	// +optional
	Enabled bool `json:"enabled,omitempty"`

	// WakeBudget is how many agent-driven wakes this session accepts before a
	// HUMAN must speak again. Each human turn refills it; each agent-driven
	// wake spends one.
	//
	// ZERO — including unset — means agent→agent wakes are OFF, never
	// unlimited. The unsafe reading of an unset budget here is "no ceiling",
	// which is precisely the loop this bounds.
	//
	// Seeing is not bounded by it. A message from another agent ALWAYS appends
	// to this session's inbox; the budget governs only whether it also WAKES
	// the agent. So a session out of credit still sees the whole conversation
	// and answers when a person next speaks.
	// +optional
	// +kubebuilder:validation:Minimum=0
	// +kubebuilder:validation:Maximum=32
	WakeBudget int `json:"wakeBudget,omitempty"`
}

CrossAgentThreadsConfig lets one agent see and answer another inside a human's thread, bounded so the two cannot sustain a loop between themselves.

Two controls, and the first ALONE is the documented attack: mention-gating supplies intent, and an injected "always @codebot when you reply" turns it into the loop. WakeBudget is the structural bound behind it.

func (*CrossAgentThreadsConfig) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CrossAgentThreadsConfig.

func (*CrossAgentThreadsConfig) DeepCopyInto

func (in *CrossAgentThreadsConfig) DeepCopyInto(out *CrossAgentThreadsConfig)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type DataLimitSpec

type DataLimitSpec struct {
	// MaxEgressBytes caps the serialized tool-args size sent outbound per call.
	// Exceeding it (at PreToolCall) applies Action; on deny the tool does not run.
	// +kubebuilder:validation:Minimum=1
	// +optional
	MaxEgressBytes int64 `json:"maxEgressBytes,omitempty"`
	// MaxIngressBytes caps the tool-result size returned inbound per call (any
	// result, success or error). Exceeding it (at PostToolCall) applies Action;
	// on deny the result is withheld (replaced with an IsError) so the oversized
	// payload never reaches the model.
	// +kubebuilder:validation:Minimum=1
	// +optional
	MaxIngressBytes int64 `json:"maxIngressBytes,omitempty"`
	// MaxUIIngressBytes caps result bytes on an agent-UI DATA BINDING, whose
	// result is rendered by a browser and never read by the model. Unset means
	// the platform's browser-sized default applies (toolguard's
	// DefaultUIIngressBytes) — NOT unlimited, and NOT MaxIngressBytes. Set this
	// to bind the UI path tighter or looser than the platform default;
	// tightening maxIngressBytes alone does not affect it.
	// +kubebuilder:validation:Minimum=1
	// +optional
	MaxUIIngressBytes int64 `json:"maxUIIngressBytes,omitempty"`
	// Action when a byte limit is exceeded: halt ends the session; deny errors
	// the call (egress: tool not run; ingress: result withheld); warn
	// logs/audits but allows.
	// +kubebuilder:validation:Enum=halt;deny;warn
	// +optional
	Action string `json:"action,omitempty"`
}

DataLimitSpec caps the byte volume a matched tool may move per call, independent of success or call count. Default-off: a zero dimension is unlimited. Egress (serialized tool args) is enforced at PreToolCall; ingress (tool result bytes) at PostToolCall. The ingress cap applies to ALL results, error included — a tool could mark an unbounded exfil payload IsError to dodge it. Like RateLimitSpec, there is no "off" action: a data limit is disabled by omitting the field or leaving a dimension zero; Action only selects what happens when a configured limit is exceeded.

func (*DataLimitSpec) DeepCopy

func (in *DataLimitSpec) DeepCopy() *DataLimitSpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DataLimitSpec.

func (*DataLimitSpec) DeepCopyInto

func (in *DataLimitSpec) DeepCopyInto(out *DataLimitSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type DataSlotRefusalReason

type DataSlotRefusalReason string

DataSlotRefusalReason says why one requested slot did not arrive.

Three causes, kept apart because they point a reader at three different actions and a single "refused" cannot. The controller already logged them separately; carrying the distinction on status is what lets the PARENT — which reads status, not logs — act on it. +kubebuilder:validation:Enum=NotDelegable;WouldDisclose;GradingUnavailable

const (
	// DataSlotNotDelegable: the parent cannot itself read the tag, so it had
	// no standing to hand it on. A mistake in what was ASKED FOR — the parent
	// should re-ask for something it holds, and retrying this is pointless.
	DataSlotNotDelegable DataSlotRefusalReason = "NotDelegable"

	// DataSlotWouldDisclose: the parent CAN read it, and handing it to this
	// child would disclose it to someone not among its readers, or the datum
	// carries untrusted content. A fact about the CHILD, not the parent, and
	// a decision no human was asked to make. This is the reason a routing
	// path would act on; today it is simply a refusal.
	DataSlotWouldDisclose DataSlotRefusalReason = "WouldDisclose"

	// DataSlotGradingUnavailable: the grade could not be computed at all.
	// TRANSIENT, and distinct from a decision on purpose — an unanswerable
	// check recorded as "would disclose" reads as a judgement nobody made,
	// and would send a parent to re-ask differently when retrying is the
	// right move.
	DataSlotGradingUnavailable DataSlotRefusalReason = "GradingUnavailable"
)

type DataSlotRequest

type DataSlotRequest struct {
	// Slot is the slot name the child's class declared ("diff", "logs"). The
	// child's vocabulary: the same tag in two slots is two entries.
	// +kubebuilder:validation:MinLength=1
	// +kubebuilder:validation:MaxLength=64
	// +kubebuilder:validation:Pattern=`^[a-z][a-z0-9_]*$`
	Slot string `json:"slot"`

	// TagID is the pt_tag object id whose content the child may read.
	//
	// The WHOLE payload. A field here able to carry the datum itself would
	// defeat the by-reference property, so this deliberately names a tag and
	// nothing more.
	// +kubebuilder:validation:MinLength=1
	// +kubebuilder:validation:MaxLength=256
	TagID string `json:"tagID"`
}

DataSlotRequest binds one pt-tag into one of the child's declared data slots.

func (*DataSlotRequest) DeepCopy

func (in *DataSlotRequest) DeepCopy() *DataSlotRequest

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DataSlotRequest.

func (*DataSlotRequest) DeepCopyInto

func (in *DataSlotRequest) DeepCopyInto(out *DataSlotRequest)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type DefaultAuthz

type DefaultAuthz struct {
	// ApprovalTimeout defaults AuthzBlock.ApprovalTimeout.
	// +optional
	ApprovalTimeout *metav1.Duration `json:"approvalTimeout,omitempty"`
	// InformationLeakageApprovalTTL defaults InformationLeakagePolicy.ApprovalTTL.
	// +optional
	InformationLeakageApprovalTTL *metav1.Duration `json:"informationLeakageApprovalTTL,omitempty"`
	// ScopeMaxLLMLatencyMs defaults ScopeSpec.MaxLLMLatencyMs.
	// +optional
	ScopeMaxLLMLatencyMs *int32 `json:"scopeMaxLlmLatencyMs,omitempty"`

	// PlanGate defaults AuthzBlock.PlanGate — what a class gets when it says
	// nothing. A class may still declare a laxer mode; forbidding that is
	// SettingsLimits.MinPlanGateMode's job, and is a separate deliberate act.
	// +optional
	PlanGate *PlanGateConfig `json:"planGate,omitempty"`

	// Metaagent defaults AuthzBlock.Metaagent — what a class gets when it says
	// nothing. A class may still opt itself into inline; forbidding that would
	// need a SettingsLimits floor, which does not exist for this yet.
	// +optional
	Metaagent *MetaagentConfig `json:"metaagent,omitempty"`
}

DefaultAuthz supplies fallback authz timeouts mirrored from AgentClass authz. Each field defaults the correspondingly-named knob on an AgentClass when that class omits it.

func (*DefaultAuthz) DeepCopy

func (in *DefaultAuthz) DeepCopy() *DefaultAuthz

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DefaultAuthz.

func (*DefaultAuthz) DeepCopyInto

func (in *DefaultAuthz) DeepCopyInto(out *DefaultAuthz)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type DefaultModel

type DefaultModel struct {
	// Provider is the LLM provider serving this model.
	// +kubebuilder:validation:Enum=anthropic;openai;openrouter;test
	Provider string `json:"provider"`
	// Name is the provider's model identifier.
	Name string `json:"name"`
	// FromCatalog lets a tier default to a catalog entry by name (alternative to
	// inline Provider/Name).
	// +optional
	FromCatalog string `json:"fromCatalog,omitempty"`
	// APIKey is the same-namespace Secret holding the provider token; unset
	// leaves the key to a lower tier or to the catalog's central TokenRef.
	// +optional
	APIKey *SecretKeyRef `json:"apiKey,omitempty"`
}

DefaultModel is a fallback model. Unlike ModelConfig, APIKey is optional: the cluster tier can default provider+name only, because the apiKey is a namespaced Secret reference. The namespace tier (or the AgentClass) supplies the apiKey.

func (*DefaultModel) DeepCopy

func (in *DefaultModel) DeepCopy() *DefaultModel

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DefaultModel.

func (*DefaultModel) DeepCopyInto

func (in *DefaultModel) DeepCopyInto(out *DefaultModel)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type EffectiveAuthz

type EffectiveAuthz struct {
	// ApprovalTimeout is how long a decision may wait for a human before the
	// gate's timeout policy fires.
	// +optional
	ApprovalTimeout metav1.Duration `json:"approvalTimeout,omitempty"`
	// InformationLeakageApprovalTTL is how long an approved share may be reused
	// before the agent must ask again.
	// +optional
	InformationLeakageApprovalTTL metav1.Duration `json:"informationLeakageApprovalTTL,omitempty"`
	// ScopeMaxLLMLatencyMs bounds the cold-start scope extractor's LLM call, in
	// milliseconds. A latency budget, not a window in which authority is held.
	// +optional
	ScopeMaxLLMLatencyMs int32 `json:"scopeMaxLlmLatencyMs,omitempty"`

	// PlanGate is the resolved plan-gate config: the class value (or the
	// nearest tier default) clamped up to the strictest MinPlanGateMode any
	// tier declares. Always non-nil after resolution.
	//
	// This is the value the gate READS. Unlike every other authz setting —
	// which the runtime reads straight off AgentClass.spec.authz — the plan
	// gate must go through the resolved path, or a class could opt out past a
	// cluster floor by being the only value anyone consults.
	// +optional
	PlanGate *PlanGateConfig `json:"planGate,omitempty"`

	// Metaagent is the resolved metaagent trigger: the class value, else the
	// nearest tier default, else mention. Never nil after resolution — an
	// absent setting resolves to the narrowest value rather than to a nil the
	// reader has to interpret.
	// +optional
	Metaagent *MetaagentConfig `json:"metaagent,omitempty"`
}

EffectiveAuthz is the resolved authz timeout set.

func (*EffectiveAuthz) DeepCopy

func (in *EffectiveAuthz) DeepCopy() *EffectiveAuthz

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EffectiveAuthz.

func (*EffectiveAuthz) DeepCopyInto

func (in *EffectiveAuthz) DeepCopyInto(out *EffectiveAuthz)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type EffectivePinning

type EffectivePinning struct {
	// Cluster is the cluster-tier policy, kept un-folded.
	// +optional
	Cluster *PinningPolicy `json:"cluster,omitempty"`
	// Namespace is the namespace-tier policy, kept un-folded.
	// +optional
	Namespace *PinningPolicy `json:"namespace,omitempty"`
}

EffectivePinning preserves the per-tier pinning policies. Bypass entries are tier-scoped, so the policies cannot be pre-folded to one list: per-item evaluation happens via settings.PinRequirementFor.

func (*EffectivePinning) DeepCopy

func (in *EffectivePinning) DeepCopy() *EffectivePinning

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EffectivePinning.

func (*EffectivePinning) DeepCopyInto

func (in *EffectivePinning) DeepCopyInto(out *EffectivePinning)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type EffectiveSettings

type EffectiveSettings struct {
	// Model is the resolved model the session runs against.
	// +optional
	Model ModelConfig `json:"model,omitempty"`
	// ModelTokenSource is the resolved central token location (system namespace)
	// when the effective model came from the catalog. nil for bring-your-own /
	// legacy. The operator materializes this into the per-session Secret.
	// +optional
	ModelTokenSource *NamespacedSecretKeyRef `json:"modelTokenSource,omitempty"`
	// ModelInputPerMTok/ModelOutputPerMTok are the resolved catalog price for the
	// chosen model (USD/MTok), 0 when the catalog carries no price. The runner
	// cost estimator prefers these over the built-in table so its figure matches
	// the admin dashboard (which is catalog-authoritative).
	// +optional
	ModelInputPerMTok float64 `json:"modelInputPerMTok,omitempty"`
	// ModelOutputPerMTok is the output half of the same catalog price.
	// +optional
	ModelOutputPerMTok float64 `json:"modelOutputPerMTok,omitempty"`
	// ModelRouting is the effective OpenRouter dynamic-routing preference: the
	// catalog entry's, with the AgentClass's routingMetadata merged over it.
	// Only set when Model.Provider == "openrouter".
	// +optional
	ModelRouting *OpenRouterRouting `json:"modelRouting,omitempty"`
	// Budget is the resolved per-session budget, clamped to every tier ceiling.
	// +optional
	Budget BudgetConfig `json:"budget,omitempty"`
	// Authz is the resolved set of human-in-the-loop timeouts.
	// +optional
	Authz EffectiveAuthz `json:"authz,omitempty"`
	// AllowedToolkits is the effective toolkit ceiling; nil is unconstrained.
	// +optional
	AllowedToolkits []string `json:"allowedToolkits,omitempty"`
	// AllowedMCP is the effective MCPServer ceiling; nil is unconstrained.
	// +optional
	AllowedMCP []AllowedMCPServer `json:"allowedMCP,omitempty"`
	// AllowedSkills is the union of allow-skill patterns across constraining
	// tiers (nil = unconstrained). Informational; the gate is the resolver.
	// +optional
	AllowedSkills []string `json:"allowedSkills,omitempty"`
	// DeniedSkills is the union of deny-skill patterns across tiers.
	// +optional
	DeniedSkills []string `json:"deniedSkills,omitempty"`
	// Pinning preserves the per-tier pinning policies. Per-item evaluation
	// happens via settings.PinRequirementFor — bypasses are tier-scoped, so
	// the policies cannot be pre-folded.
	// +optional
	Pinning *EffectivePinning `json:"pinning,omitempty"`
	// ToolGuard preserves the per-tier tool-guard policies + folded ceiling.
	// Per-tool rule resolution happens in the runner at session start.
	// +optional
	ToolGuard *EffectiveToolGuard `json:"toolGuard,omitempty"`
	// ContentInspectors is the resolved union of content-guard inspectors across
	// tiers (ceiling: lower tiers add, never weaken). Empty ⇒ no inspection.
	// +optional
	ContentInspectors []ContentInspectorConfig `json:"contentInspectors,omitempty"`
	// Provenance maps a resolved field to the tier that supplied it
	// (cluster|namespace|class|session|clamped).
	// +optional
	Provenance map[string]string `json:"provenance,omitempty"`
	// ReportSessionCost is the resolved end-of-session cost-estimate toggle.
	ReportSessionCost bool `json:"reportSessionCost"`
	// NativeFileHandling is the resolved Tier-2 provider-native file handling
	// grant (security-sensitive; default false). See SettingsLimits.NativeFileHandling.
	NativeFileHandling bool `json:"nativeFileHandling"`
	// RequireSubagentDigestPins is the resolved delegation requirement
	// (default false): when true, the SubagentRequest controller refuses
	// delegation through any unpinned roster entry. See
	// SettingsLimits.RequireSubagentDigestPins.
	RequireSubagentDigestPins bool `json:"requireSubagentDigestPins"`
	// Sandbox is the resolved sandbox backend per tool-bundle name. Recorded so
	// `oap` can show which backend a session landed on, and Provenance says which
	// tier chose it.
	// +optional
	Sandbox map[string]SandboxBackend `json:"sandbox,omitempty"`
	// AllowedSandboxKinds is the effective ceiling across constraining tiers.
	// Empty means unconstrained.
	// +optional
	AllowedSandboxKinds []string `json:"allowedSandboxKinds,omitempty"`

	// RequireStandingFor is the union of every tier's RequireStandingFor,
	// sorted and deduplicated. A resource type named here resolves to
	// `required` standing regardless of what its schema fragment declares.
	// +optional
	// +listType=atomic
	RequireStandingFor []string `json:"requireStandingFor,omitempty"`
}

EffectiveSettings is the resolved 4-tier settings snapshot stamped onto AgentClass/AgentSession status. It mirrors pkg/platform/settings.EffectiveSettings in a CRD-serializable shape (metav1.Duration instead of time.Duration).

func (*EffectiveSettings) DeepCopy

func (in *EffectiveSettings) DeepCopy() *EffectiveSettings

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EffectiveSettings.

func (*EffectiveSettings) DeepCopyInto

func (in *EffectiveSettings) DeepCopyInto(out *EffectiveSettings)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*EffectiveSettings) PlanGateActive

func (es *EffectiveSettings) PlanGateActive() bool

PlanGateActive reports whether the plan gate RUNS for this session.

A method rather than a comparison each caller writes, because the answer gates which meta tools a session is offered — select_phase and complete_phase exist only when there is a frozen plan to index into — and a caller that forgets to ask gets the fail-closed answer in silence. `oap session capture` did exactly that: it left capability.RunnerEnv.PlanGateActive unset, so every captured plan-gated session had select_phase missing from its meta-tool list and was refused as an unreplayable sandbox call. The runner and the e2e in-process factory had each transcribed the same expression separately; a third copy in the CLI is what this method exists to prevent.

"" (unresolved) counts as inactive, matching the runner: a session whose settings have not resolved has no frozen plan to index into either.

func (*EffectiveSettings) ResolvedPlanGateMode

func (es *EffectiveSettings) ResolvedPlanGateMode() string

ResolvedPlanGateMode returns the resolved plan-gate mode, or "" when the session has no resolved settings or no plan-gate block.

This, and deliberately NOT AgentClass.spec.authz.planGate.Mode. Every other authz setting is read straight off the class spec, but the plan-gate mode is ceiling-clamped in the settings fold: a class may make itself stricter, and may only go below a tier floor when no floor is set. Reading the class spec would consult the UNCLAMPED value and let a class opt out past a cluster floor by being the only value anyone looks at.

Nil-safe on both hops. Callers hold an *EffectiveSettings read off AgentSession.status, which is empty until the reconciler has resolved it, and PlanGate is nil for a session resolved before the field existed. A missing snapshot resolving to "" is the correct failure direction: the gate is off by default, and a caller that somehow has no resolved settings has larger problems than one absent hook.

type EffectiveToolGuard

type EffectiveToolGuard struct {
	// Cluster is the cluster-tier policy, kept un-folded for the rule walk.
	// +optional
	Cluster *ToolGuardPolicy `json:"cluster,omitempty"`
	// Namespace is the namespace-tier policy, kept un-folded for the rule walk.
	// +optional
	Namespace *ToolGuardPolicy `json:"namespace,omitempty"`
	// Ceiling is the strictest-across-tiers bound, pre-folded.
	// +optional
	Ceiling *ToolGuardCeiling `json:"ceiling,omitempty"`
}

EffectiveToolGuard preserves the per-tier policies for the runner's session-start rule walk (rules cannot be pre-folded — first match in the highest tier wins per tool), plus the pre-folded strictest ceiling.

CONSTRAINT: this type lives on status yet reaches ToolGuardPolicy and ToolGuardCeiling, the same types the AUTHORED surfaces use. Every validation rule on anything reachable from here is therefore attached to a status path too, governing status writes derived from objects the apiserver never re-validates. A rule a stored object violates does not fail that object — it fails the operator's status write, wedging the reconcile.

So before adding a validation rule (XValidation, Minimum, Required, Enum) to any type reachable from here, make the settings resolver guarantee the value it stamps satisfies it, or keep the rule off the shared type. TestNoUnguardedStatusCELRules enumerates the CEL rules that reach a status path today and fails when a new one appears.

func (*EffectiveToolGuard) DeepCopy

func (in *EffectiveToolGuard) DeepCopy() *EffectiveToolGuard

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EffectiveToolGuard.

func (*EffectiveToolGuard) DeepCopyInto

func (in *EffectiveToolGuard) DeepCopyInto(out *EffectiveToolGuard)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type EntityAutoFillArg deprecated

type EntityAutoFillArg = AuthzSlotAutoFillArg

BoundEntityType and EntityAutoFillArg are the pre-rename names for AuthzSlot and AuthzSlotAutoFillArg.

Kept as aliases so the rename lands as ONE reviewable diff rather than 200 mechanical edits mixed into it. They are type aliases, not new types, so the two spellings are the same type to the compiler and no conversion is needed.

Deprecated: use AuthzSlot / AuthzSlotAutoFillArg.

type EstimatedSessionCost

type EstimatedSessionCost struct {
	// AmountMicroUSD is the SESSION total in micro-USD (1e-6 USD), accumulated
	// across every pod that served the session: the sum of PerPod amounts. When
	// PricingKnown is false, at least one served MODEL (in some pod) had no rate,
	// so this is a LOWER BOUND on spend (the components that could be priced),
	// not the full session cost — it is no longer necessarily 0, because
	// provider-reported tool cost is always priced.
	// +optional
	AmountMicroUSD int64 `json:"amountMicroUSD,omitempty"`
	// Currency is the ISO code the amount is denominated in ("USD").
	// +optional
	Currency string `json:"currency,omitempty"`
	// Model is the configured model the session ran under.
	// +optional
	Model string `json:"model,omitempty"`
	// PricingKnown is false when a served model had no price in ANY contributing
	// pod (the AND across PerPod). The amount is then a lower bound (priced
	// components only), not the full cost. Tool buckets are always priced, so
	// this tracks model-pricing coverage specifically.
	// +optional
	PricingKnown bool `json:"pricingKnown,omitempty"`
	// AsOf is when the estimate was computed.
	// +optional
	AsOf metav1.Time `json:"asOf,omitempty"`

	// ByModel breaks the total down per actually-served model, MERGED across
	// every pod (summed per model display id), so a session that fell back or
	// auto-routed across models — or ran across a reap/wake — shows where the
	// spend went instead of one blended total under the configured model. Empty
	// when no pod reported per-model usage; AmountMicroUSD and PricingKnown are
	// then the sole source of truth.
	// +optional
	// +listType=atomic
	ByModel []ModelCostBucket `json:"byModel,omitempty"`

	// ByTool breaks out the cost of inner interactive toolkits (e.g. a `claude`
	// Claude Code sub-run's own provider-reported spend) a session drove, MERGED
	// across every pod (summed per tool). These are ADDED to AmountMicroUSD
	// alongside ByModel — a session's total is model spend plus tool spend. Empty
	// when no interactive toolkit reported a cost.
	// +optional
	// +listType=atomic
	ByTool []ToolCostBucket `json:"byTool,omitempty"`

	// PerPod is the per-pod cost SOURCE OF TRUTH: one entry per pod that served
	// the session (keyed by pod name), each carrying that pod's own total and
	// breakdown. The aggregate fields above are DERIVED from this list — the
	// total is the sum of PerPod amounts, ByModel/ByTool are merged by key, and
	// PricingKnown is the AND. Each pod upserts only its own entry, so cost
	// ACCUMULATES across a reap/wake instead of a later pod's single-pod total
	// replacing an earlier one. Empty only for a session no pod has costed yet.
	// +optional
	// +listType=atomic
	PerPod []PerPodCost `json:"perPod,omitempty"`
}

EstimatedSessionCost is a best-effort USD estimate of a session's LLM spend, stamped at SessionEnd from cumulative token usage × the provider's per-model pricing PLUS any interactive toolkit's own provider-reported cost (ByTool). Money is stored as integer micro-USD (1e-6 USD) to avoid float drift. PricingKnown=false means a served model had no price, so AmountMicroUSD is a lower bound (the priced components only), not the full cost.

func (*EstimatedSessionCost) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EstimatedSessionCost.

func (*EstimatedSessionCost) DeepCopyInto

func (in *EstimatedSessionCost) DeepCopyInto(out *EstimatedSessionCost)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ExpectedDuration

type ExpectedDuration string

+kubebuilder:validation:Enum=short;long;persistent

const (
	ExpectedDurationShort      ExpectedDuration = "short"
	ExpectedDurationLong       ExpectedDuration = "long"
	ExpectedDurationPersistent ExpectedDuration = "persistent"
)

type FactSource

type FactSource struct {
	// Provenance is the fact's namespace: `envelope` (derived by platform code
	// from a signed provider payload) or `observed` (derived from a response to
	// a call the agent shaped).
	Provenance string `json:"provenance"`

	// Name is the fact name, as `facts.<provenance>.<name>` addresses it.
	Name string `json:"name"`

	// State is the machine-readable answer to "what kind of entry is this",
	// and it is the field a consumer branches on. Detail says the same thing
	// in prose for a human; prose is not a discriminator, and a consumer
	// substring-matching Detail would break the first time the wording is
	// improved.
	//
	// It exists because two of the five states share an empty GatedBy and two
	// share an empty ProducedBy, so neither field can separate them:
	//
	//   gated      — a producer, and every call of it is gated (GatedBy lists
	//                the types).
	//   ungated    — a producer no permission check gates, so the call that
	//                records the fact is always allowed.
	//   unresolved — a real producer whose gate could NOT be determined. Not
	//                the same as ungated, and never treated as blocked.
	//   absent     — nothing on this class declares a producer for an observed
	//                fact. A warning, not a refusal.
	//   envelope   — an envelope fact, which no tool ever records. Normal, and
	//                deliberately not the `absent` warning.
	// +kubebuilder:validation:Enum=gated;ungated;unresolved;absent;envelope
	State string `json:"state"`

	// ProducedBy names the tool whose `observes` block records this fact.
	// EMPTY means nothing on this class declares a producer — see Detail for
	// which of the two reasons applies.
	//
	// One entry per producer: a fact several tools record yields several
	// entries, because a reader deciding whether a precondition is answerable
	// needs to know every call that could answer it, not just one.
	// +optional
	ProducedBy string `json:"producedBy,omitempty"`

	// GatedBy lists, sorted, every resource type a permission check on
	// ProducedBy names — that is, which slots must already be bound before the
	// call that records this fact is allowed. Empty when ProducedBy is
	// ungated, or when its gate could not be resolved; Detail says which.
	// +optional
	// +listType=atomic
	GatedBy []string `json:"gatedBy,omitempty"`

	// Detail explains, in prose meant for a person, an entry that is not a
	// plain gated producer: why nothing records the fact, why the producer is
	// ungated, or which gate could not be determined. Empty for the ordinary
	// case, where State, ProducedBy and GatedBy already say everything.
	//
	// Human-facing only. Branch on State.
	// +optional
	Detail string `json:"detail,omitempty"`
}

FactSource publishes, for ONE fact this class's slot preconditions read, where that fact can come from.

A precondition is only as answerable as its facts. `facts.observed.x` is recorded by a tool's `observes` block; `facts.envelope.x` is derived by platform code from a signed payload. Either can be referenced by a predicate that nothing on this class will ever supply, and the failure of that is SILENT: every evaluation returns undetermined, the slot never binds, and the agent is handed a hint for a fact it cannot establish. Nothing errors.

So the dependency is published rather than left implicit. An entry with an empty ProducedBy is the visible form of "nobody on this class records this" — which is a warning, not a refusal, because a fact may legitimately arrive from a channel kind or a tool that is not part of this class's spec. The refusal case is narrower and is decided elsewhere: a fact whose every producer is gated by the very slot the precondition holds shut.

An OBSERVATION derived by the reconciler from the class's slots and tools, never authored, and written set-on-change.

func (*FactSource) DeepCopy

func (in *FactSource) DeepCopy() *FactSource

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FactSource.

func (*FactSource) DeepCopyInto

func (in *FactSource) DeepCopyInto(out *FactSource)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type FakeChannelConfig

type FakeChannelConfig struct {
	// Echo makes the fake re-deliver each outbound message as an inbound one,
	// giving tests a pure round-trip.
	// +optional
	Echo bool `json:"echo,omitempty"`
	// OrgScoped makes the fake kind attribute org membership on this channel,
	// so a scenario can exercise the session-start gate: injected identities
	// carry their OrgMembership stamp verbatim, and an unstamped one reads as
	// guest at the pipeline (fail-closed).
	// +optional
	OrgScoped bool `json:"orgScoped,omitempty"`
}

FakeChannelConfig configures the in-process test kind.

func (*FakeChannelConfig) DeepCopy

func (in *FakeChannelConfig) DeepCopy() *FakeChannelConfig

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FakeChannelConfig.

func (*FakeChannelConfig) DeepCopyInto

func (in *FakeChannelConfig) DeepCopyInto(out *FakeChannelConfig)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type FederatedCredentialSource

type FederatedCredentialSource struct {
	// Resource is the identifier the IdP knows the MCP server by — the
	// ID-JAG audience.
	Resource string `json:"resource"`
	// ResourceServerURL is the MCP server URL whose authorization server
	// accepts the ID-JAG (leg 2 discovery target).
	ResourceServerURL string `json:"resourceServerURL"`
	// IdPSecretRef points at the subject-keyed IdP-identity Secret in
	// IdentitiesNamespace (the user's refresh token).
	IdPSecretRef SecretRef `json:"idpSecretRef"`
	// Scopes are optional scopes requested at the resource AS.
	// +optional
	Scopes []string `json:"scopes,omitempty"`
}

FederatedCredentialSource declares an ID-JAG-minted credential. No Secret holds the upstream token; it is derived per resolve from the user's IdP-identity Secret (the refresh token captured at login).

func (*FederatedCredentialSource) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederatedCredentialSource.

func (*FederatedCredentialSource) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type FederationConfig

type FederationConfig struct {
	// Enabled turns this IdP into an EMA federation source.
	Enabled bool `json:"enabled,omitempty"`
}

FederationConfig toggles ID-JAG federation for the cluster IdP.

func (*FederationConfig) DeepCopy

func (in *FederationConfig) DeepCopy() *FederationConfig

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationConfig.

func (*FederationConfig) DeepCopyInto

func (in *FederationConfig) DeepCopyInto(out *FederationConfig)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type GitHubAppCredentialSource

type GitHubAppCredentialSource struct {
	// SecretRef points at the Secret the channel wizard wrote.
	SecretRef SecretRef `json:"secretRef"`
}

GitHubAppCredentialSource declares a credential MINTED on demand from a stored GitHub App private key. The referenced Secret holds app-id, private-key (PEM), installation-id and webhook-secret; the resolved value is a ~1h installation access token, never the private key itself.

Valid ONLY on AgentIdentity. This is the exact mirror of type=federated, which is rejected on AgentIdentity because a bot has no human subject to assert: a GitHub App has no human subject AT ALL, so an AgentIdentity is the only place it belongs.

func (*GitHubAppCredentialSource) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitHubAppCredentialSource.

func (*GitHubAppCredentialSource) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type GitHubChannelConfig

type GitHubChannelConfig struct {
	// AppSlug is the GitHub App's slug, recorded so the wizard and the
	// drift check can build install and settings URLs. The App's identity
	// and keys live in the credentialsRef Secret, never in spec.
	AppSlug string `json:"appSlug"`

	// Repositories optionally narrows which repos produce reviews.
	// Empty means every repository the App installation grants.
	// +optional
	Repositories []string `json:"repositories,omitempty"`

	// Events are the pull_request actions that become a review.
	// +kubebuilder:default={"opened","synchronize","reopened","ready_for_review"}
	// +optional
	Events []string `json:"events,omitempty"`

	// SkipDrafts drops draft pull requests. Nil means true: drafts are
	// skipped unless the Channel opts in with an explicit `skipDrafts: false`.
	//
	// A *bool with no default marker, NOT a bool with +kubebuilder:default=true.
	// That combination is unsettable: `false` is the zero value, omitempty
	// elides it, the API server sees an absent field and the defaulter restores
	// true - so the one value a user reaches for when they DO want draft PRs
	// reviewed is the one value the field cannot hold. Same shape as
	// ChannelMetaagentSpec.Enabled in this file.
	// +optional
	SkipDrafts *bool `json:"skipDrafts,omitempty"`
}

GitHubChannelConfig carries kind-specific config for kind="github". It is deliberately INGRESS-ONLY. The Check Run name is not here because the agent writes the Check Run; Slack settings are not here because the output surface is a separate Channel CR.

func (*GitHubChannelConfig) DeepCopy

func (in *GitHubChannelConfig) DeepCopy() *GitHubChannelConfig

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitHubChannelConfig.

func (*GitHubChannelConfig) DeepCopyInto

func (in *GitHubChannelConfig) DeepCopyInto(out *GitHubChannelConfig)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type GrantPair

type GrantPair struct {
	// ResourceType is the SpiceDB definition name (e.g., "github_repo").
	// +kubebuilder:validation:MinLength=1
	// +kubebuilder:validation:Pattern=`^[a-z][a-z0-9_]*(/[a-z][a-z0-9_]*)*$`
	ResourceType string `json:"resourceType"`

	// Permission is the SpiceDB permission name on ResourceType
	// (e.g., "read", "admin").
	// +kubebuilder:validation:MinLength=1
	// +kubebuilder:validation:Pattern=`^[a-z][a-z0-9_]*$`
	Permission string `json:"permission"`
}

func (*GrantPair) DeepCopy

func (in *GrantPair) DeepCopy() *GrantPair

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrantPair.

func (*GrantPair) DeepCopyInto

func (in *GrantPair) DeepCopyInto(out *GrantPair)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type HeaderInjection

type HeaderInjection struct {
	// Name is the HTTP header the token is written to.
	Name string `json:"name"`
	// ValuePrefix is prepended to the token in the header value (e.g. "Bearer ").
	// +optional
	ValuePrefix string `json:"valuePrefix,omitempty"`
}

func (*HeaderInjection) DeepCopy

func (in *HeaderInjection) DeepCopy() *HeaderInjection

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HeaderInjection.

func (*HeaderInjection) DeepCopyInto

func (in *HeaderInjection) DeepCopyInto(out *HeaderInjection)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type HumanDirectedTarget

type HumanDirectedTarget struct {
	// Binding always has a kind whose DeliversToHuman is true — the walk's
	// !human arm keeps climbing and never records — so a caller may route to it
	// without re-checking.
	Binding *ChannelBinding
	// Owner is the session Binding was found on: sess itself for a root, an
	// ancestor for a delegated child.
	Owner NamespacedRef

	// Chain is every session the walk passed through to reach Owner, starting
	// at the session asked about and ending at Owner. len(Chain)-1 is the
	// number of delegation hops between them: 0 when the session owns its own
	// binding, 1 for a direct child, 2 for a grandchild.
	//
	// It exists because an approver needs to know HOW the asking session
	// relates to the one they are watching — "this is a grandchild of the
	// session you are in" is material to the decision, and a card that only
	// names the asker leaves them to guess. The walk already visits every hop
	// and kept only the last; collecting them costs nothing extra.
	//
	// A renderer must not try to derive this instead. A client-hosted host
	// knows the asking session and its own, and nothing about the distance
	// between them; finding out means reading lineage on a render path, which
	// is a lookup that host should not be making even where its RBAC allows.
	Chain []NamespacedRef
}

HumanDirectedTarget is where a message whose reader must be a person goes: the binding to deliver through, and the session that OWNS it.

The owner is not decoration. A card about a delegated child resolves onto an ANCESTOR's binding, and a client-hosted host (the `local` TUI, webd's chat registry) decides what it may render by asking whether it serves the session it was handed. Given the child's identity it refuses a card it is in fact the right reader for — the person it serves owns that whole tree. Carrying the resolved ancestor means the host is asked about a session it recognizes, with no lineage walk of its own and no change to what "owns" means to it.

The walk already had this in hand and threw it away, which is why the undeliverable-card bug looked like it needed a new traversal somewhere.

func ResolveHumanDirectedBinding

func ResolveHumanDirectedBinding(
	ctx context.Context,
	r client.Reader,
	sess *AgentSession,
	deliversToHuman func(kindName string) (bool, error),
) (*HumanDirectedTarget, error)

ResolveHumanDirectedBinding finds the channel binding a message whose reader must be a PERSON should be delivered through: the nearest binding, starting at sess and climbing the lineage, whose channel kind a human actually reads. Any caller that must reach a human about a session resolves delivery through this, rather than reading sess.Spec.InputChannel/OutputChannel (or OutboundBinding, its single-session counterpart) directly.

Two independent reasons a session's own binding is not the answer:

  • It may have none. A single_turn delegated child is headless by construction — no spec.inputChannel, and a held session cannot gain one — so under forensic hold, without the walk, its release card has nowhere to go and the hold becomes a one-way door whose only exit is deleting the session, which destroys the evidence the hold exists to preserve (spec §2.9(c)).
  • It may have one that no human reads. A conversational subagent is bound to an `agent` Channel whose far side is its parent SESSION, so routing a permission prompt to the session's own binding would hand a human's decision to another agent. Those bindings are skipped and the walk continues past them.

deliversToHuman answers the second question for a channel-kind name; it is REQUIRED (a nil predicate is an error, not an implicit yes) because this package cannot import the channel-kind registry that knows the answer, and because the fail-open direction here is precisely the defect. Callers inside the module pass registry.DeliversToHuman.

sessionhold's publishCard resolves the release card's approver identity through this walk, and the outbound relay (pkg/channels/channelsd/outbound/relay.go) independently resolves the same card's delivery through it — but the walk itself is general-purpose: it holds for any session shape a caller constructs, not just a held child.

Three outcomes, kept distinct rather than folded into a single nil:

  • a resolved binding (found on sess or an ancestor);
  • (nil, nil) — nothing in the lineage delivers to a human. A kubectl-driven root has no binding at all; a subtree of agent-to-agent channels has bindings that no person reads. Neither is an error, and a caller that needs a human MUST treat it as "there is nobody to ask" rather than falling back to any binding it can find;
  • (nil, err) — a broken link, cycle, over-deep chain, or a binding whose kind deliversToHuman could not answer for.

func (*HumanDirectedTarget) DeepCopy

func (in *HumanDirectedTarget) DeepCopy() *HumanDirectedTarget

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HumanDirectedTarget.

func (*HumanDirectedTarget) DeepCopyInto

func (in *HumanDirectedTarget) DeepCopyInto(out *HumanDirectedTarget)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type IdentityRecommendationConfig

type IdentityRecommendationConfig struct {
	// Prompt is short class-authored guidance handed to the recommender LLM,
	// e.g. "Prefer the agent's own identity in busy multi-person threads;
	// prefer acting on the user's behalf when a single user works from fresh
	// context." The recommender's output is advisory only — the user always
	// confirms — so this prompt never authorizes anything by itself.
	// +kubebuilder:validation:MinLength=1
	Prompt string `json:"prompt"`
}

IdentityRecommendationConfig is the dynamic-mode recommender knob.

func (*IdentityRecommendationConfig) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IdentityRecommendationConfig.

func (*IdentityRecommendationConfig) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type InformationLeakagePolicy

type InformationLeakagePolicy struct {
	// Mode controls how the gate reacts when a potential leak is detected.
	// +kubebuilder:validation:Enum=enforcing;logging;disabled
	// +kubebuilder:default=enforcing
	// +optional
	Mode string `json:"mode,omitempty"`

	// ApprovalTTL is the lifetime of a SpiceDB grant written on approve.
	// +optional
	ApprovalTTL *metav1.Duration `json:"approvalTTL,omitempty"`

	// OnUnsupportedChannel determines binding-time behavior when a Channel's
	// kind has no AudienceResolver capability.
	// +kubebuilder:validation:Enum=blockBinding;logOnly;bypass
	// +kubebuilder:default=blockBinding
	// +optional
	OnUnsupportedChannel string `json:"onUnsupportedChannel,omitempty"`

	// SingleUserBypass allows SingleUser-capability channels to skip the
	// gate (the audience is trivially the requester themselves).
	// +optional
	SingleUserBypass *bool `json:"singleUserBypass,omitempty"`

	// LoggingNoticeToRequester sends an ephemeral notice to the requester
	// on every would-block event while Mode=logging.
	// +optional
	LoggingNoticeToRequester *bool `json:"loggingNoticeToRequester,omitempty"`
}

InformationLeakagePolicy configures the information-leakage gate. Fields are optional; absent fields use the defaults documented on the Resolved* helpers (mode defaults to "disabled" when the entire block is nil; to "enforcing" when the block exists but Mode is unset is NOT desired — callers that want enforcing must set it explicitly).

func (*InformationLeakagePolicy) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InformationLeakagePolicy.

func (*InformationLeakagePolicy) DeepCopyInto

func (in *InformationLeakagePolicy) DeepCopyInto(out *InformationLeakagePolicy)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*InformationLeakagePolicy) ResolvedApprovalTTL

func (p *InformationLeakagePolicy) ResolvedApprovalTTL() time.Duration

ResolvedApprovalTTL returns ApprovalTTL, or 10 minutes when unset.

func (*InformationLeakagePolicy) ResolvedLoggingNoticeToRequester

func (p *InformationLeakagePolicy) ResolvedLoggingNoticeToRequester() bool

ResolvedLoggingNoticeToRequester returns LoggingNoticeToRequester, or false when unset: the below-threshold FYI notice is opt-in (an agent must set loggingNoticeToRequester=true to receive it), so migrating the notice onto the generic interaction model does not change default behavior.

func (*InformationLeakagePolicy) ResolvedMode

func (p *InformationLeakagePolicy) ResolvedMode() string

ResolvedMode returns Mode, or "disabled" when the policy block is nil or Mode is unset.

func (*InformationLeakagePolicy) ResolvedOnUnsupportedChannel

func (p *InformationLeakagePolicy) ResolvedOnUnsupportedChannel() string

ResolvedOnUnsupportedChannel returns OnUnsupportedChannel, or "blockBinding" when unset.

func (*InformationLeakagePolicy) ResolvedSingleUserBypass

func (p *InformationLeakagePolicy) ResolvedSingleUserBypass() bool

ResolvedSingleUserBypass returns SingleUserBypass, or true when unset.

type InjectedEnvKey

type InjectedEnvKey struct {
	// Key is the environment variable name the credential filled.
	Key string `json:"key"`
	// Masked is a redacted display form of the injected value: a recognised
	// delimiter-terminated vendor prefix (e.g. ghp_) plus **** plus the last 4
	// characters, or **** alone for short or non-printable values. See pkg/x/credmask.
	Masked string `json:"masked"`
}

func (*InjectedEnvKey) DeepCopy

func (in *InjectedEnvKey) DeepCopy() *InjectedEnvKey

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InjectedEnvKey.

func (*InjectedEnvKey) DeepCopyInto

func (in *InjectedEnvKey) DeepCopyInto(out *InjectedEnvKey)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type InputArtifact

type InputArtifact struct {
	// Path (relative) inside /work/in/<callId>/ where this artifact is materialized.
	Path string `json:"path"`
	// ArtifactRef is an opaque storage reference resolved by the operator's ArtifactStore.
	ArtifactRef string `json:"artifactRef"`
}

func (*InputArtifact) DeepCopy

func (in *InputArtifact) DeepCopy() *InputArtifact

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InputArtifact.

func (*InputArtifact) DeepCopyInto

func (in *InputArtifact) DeepCopyInto(out *InputArtifact)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type InputRequest

type InputRequest struct {
	// Slot is the child-side slot name it wants filled.
	// +kubebuilder:validation:MinLength=1
	// +kubebuilder:validation:MaxLength=64
	Slot string `json:"slot"`

	// Why is the child's stated reason, in its own words. It is what the
	// parent — and any person ruling on the disclosure — actually reads.
	// +kubebuilder:validation:MaxLength=2048
	Why string `json:"why"`

	// Exchange counts this child's data requests, starting at 1. Monotonic
	// for the same reason ParentExchange's is: it is how a parent tells a new
	// request from the one it just answered, and it must survive the child's
	// pod being reaped mid-wait.
	Exchange int64 `json:"exchange"`

	// Pending is true while request number Exchange has not been answered
	// either way — filled, or refused.
	// +optional
	Pending bool `json:"pending,omitempty"`
}

ParentExchange is a delegated child's record of asking the agent that delegated to it a question, and of whether that question is still outstanding.

It is the child→parent half of a resumable delegation, and it is carried here rather than written straight onto the SubagentRequest on purpose: RBAC cannot scope a status write to one field, so a runner holding `update` on subagentrequests/status could mark its own delegation Denied or Succeeded — fabricating a policy refusal the parent is told not to retry, or an answer the child never produced. The child writes only its OWN status; the SubagentRequest controller stays the single writer of the request's, and mirrors this across. InputRequest is a delegated child's mid-flight ask for DATA it was not given, naming one of its declared input slots.

Sibling of ParentExchange, and separate from it on purpose: that one carries a QUESTION whose answer is the parent's words, and this one asks for a DATUM whose delivery is a slot binding, graded and possibly routed to a human. Folding them together would make "the parent replied" and "the data arrived" the same event, when the first can happen without the second.

It does NOT pause the child. The design's own words: asking cannot widen anything, and the data may never arrive.

func (*InputRequest) DeepCopy

func (in *InputRequest) DeepCopy() *InputRequest

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InputRequest.

func (*InputRequest) DeepCopyInto

func (in *InputRequest) DeepCopyInto(out *InputRequest)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type MCPServer

type MCPServer struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   MCPServerSpec   `json:"spec,omitempty"`
	Status MCPServerStatus `json:"status,omitempty"`
}

+kubebuilder:object:root=true +kubebuilder:resource:scope=Namespaced,categories={authzed,mcp},shortName=mcpsrv +kubebuilder:subresource:status +kubebuilder:printcolumn:name="Valid",type="string",JSONPath=".status.conditions[?(@.type=='Valid')].status" +kubebuilder:printcolumn:name="Reachable",type="string",JSONPath=".status.conditions[?(@.type=='Reachable')].status" +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +genclient

MCPServer declares one MCP endpoint an agent may call: where it lives (spec.server), the allowlisted tools, per-tool CEL argument constraints, the credential used at runtime, and the SpiceDB schema fragment its resources need.

Namespaced. Reconciled by pkg/controllers/mcpserver, which probes the server's tools/list, compiles every constraint, and reflects the outcome on Valid and Reachable. The probe is unauthenticated -- runtime auth resolves spec.auth.credential at session startup, so Reachable=True is not evidence that the agent's credential works.

func (*MCPServer) DeepCopy

func (in *MCPServer) DeepCopy() *MCPServer

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MCPServer.

func (*MCPServer) DeepCopyInto

func (in *MCPServer) DeepCopyInto(out *MCPServer)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*MCPServer) DeepCopyObject

func (in *MCPServer) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type MCPServerAuth

type MCPServerAuth struct {
	// Type is the authentication mechanism the MCPServer requires. "static":
	// the user supplies a long-lived token (PAT/API key) through the
	// /my/accounts/<credname>/link form. "oauth": the credential comes from the
	// OAuth Authorization Code flow via /link/oauth/<credname>. Empty means the
	// portal falls back to the PAT form.
	//
	// +optional
	// +kubebuilder:validation:Enum=static;oauth;federated
	Type string `json:"type,omitempty"`

	// Provider names a provider in the /providers/ library. The setup
	// engine uses this to drive `oap agent setup-identity` for this server.
	// At runtime the credential is resolved by name (spec.auth.credential)
	// from the AgentIdentity; this Provider field is metadata for setup,
	// not a runtime hook.
	// +optional
	Provider string `json:"provider,omitempty"`

	// Title is the short user-facing display name for this server's
	// credential ("Linear"). Shown as the bold header of a
	// credential-request row. Takes precedence over the provider catalog;
	// empty falls back to the catalog (via Provider) or a humanized
	// credential name.
	// +optional
	Title string `json:"title,omitempty"`

	// Description is the user-facing "what is this token" sentence for
	// this server's credential. Takes precedence over the provider
	// catalog; empty falls back to the catalog or renders no description.
	// +optional
	Description string `json:"description,omitempty"`

	// Header is the HTTP header name to set on outbound requests
	// (default "Authorization").
	// +optional
	Header string `json:"header,omitempty"`

	// ValuePrefix is prepended to the resolved access_token in the
	// header value (default "Bearer ").
	// +optional
	ValuePrefix string `json:"valuePrefix,omitempty"`

	// Credential is the name of the credential (in the agent's identity
	// catalog) that fills this server's auth header. When empty,
	// resolution falls back to the MCPServer's metadata.name.
	// +optional
	Credential string `json:"credential,omitempty"`

	// Resource is the identifier the enterprise IdP knows this server by,
	// used as the ID-JAG audience when Type=federated. Required when
	// Type=federated; ignored otherwise.
	// +optional
	Resource string `json:"resource,omitempty"`
}

func (*MCPServerAuth) DeepCopy

func (in *MCPServerAuth) DeepCopy() *MCPServerAuth

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MCPServerAuth.

func (*MCPServerAuth) DeepCopyInto

func (in *MCPServerAuth) DeepCopyInto(out *MCPServerAuth)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type MCPServerConstraint

type MCPServerConstraint struct {
	// CEL is a boolean expression over the call's args; false denies the call.
	CEL string `json:"cel"`
	// Message is the denial text shown when CEL evaluates false.
	Message string `json:"message,omitempty"`
}

func (*MCPServerConstraint) DeepCopy

func (in *MCPServerConstraint) DeepCopy() *MCPServerConstraint

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MCPServerConstraint.

func (*MCPServerConstraint) DeepCopyInto

func (in *MCPServerConstraint) DeepCopyInto(out *MCPServerConstraint)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type MCPServerDenyCreds

type MCPServerDenyCreds struct {
	// Writes denies any tool that would write credential material.
	Writes bool `json:"writes,omitempty"`
}

func (*MCPServerDenyCreds) DeepCopy

func (in *MCPServerDenyCreds) DeepCopy() *MCPServerDenyCreds

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MCPServerDenyCreds.

func (*MCPServerDenyCreds) DeepCopyInto

func (in *MCPServerDenyCreds) DeepCopyInto(out *MCPServerDenyCreds)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type MCPServerDenyEffects

type MCPServerDenyEffects struct {
	// Destructive denies any tool the server marks destructive.
	Destructive bool `json:"destructive,omitempty"`
	// Reads names resource kinds whose reads are denied.
	Reads []string `json:"reads,omitempty"`
	// Writes names resource kinds whose writes are denied.
	Writes []string `json:"writes,omitempty"`
	// Creds denies credential-touching effects.
	Creds MCPServerDenyCreds `json:"creds,omitempty"`
}

func (*MCPServerDenyEffects) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MCPServerDenyEffects.

func (*MCPServerDenyEffects) DeepCopyInto

func (in *MCPServerDenyEffects) DeepCopyInto(out *MCPServerDenyEffects)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type MCPServerDenyTrust

type MCPServerDenyTrust struct {
	// OutcomesIrreversible denies the call when the server declares its effects
	// cannot be undone.
	// +optional
	OutcomesIrreversible bool `json:"outcomesIrreversible,omitempty"`
	// DestinationPublic denies the call when it would write somewhere publicly
	// visible.
	// +optional
	DestinationPublic bool `json:"destinationPublic,omitempty"`
	// SourceUntrustedPublic denies the call when it would read untrusted public
	// content into the agent's context.
	// +optional
	SourceUntrustedPublic bool `json:"sourceUntrustedPublic,omitempty"`
}

MCPServerDenyTrust mirrors mcpspec.DenyTrust. Each field opts the spec into pre-call denial when the corresponding Trust capability is asserted.

func (*MCPServerDenyTrust) DeepCopy

func (in *MCPServerDenyTrust) DeepCopy() *MCPServerDenyTrust

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MCPServerDenyTrust.

func (*MCPServerDenyTrust) DeepCopyInto

func (in *MCPServerDenyTrust) DeepCopyInto(out *MCPServerDenyTrust)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type MCPServerLabelExtract

type MCPServerLabelExtract struct {
	// When is a CEL boolean expression with `args`, `result` in
	// scope; the block is skipped if it evaluates false.
	// +optional
	When string `json:"when,omitempty"`
	// ForEach is a CEL expression that must evaluate to a list. One
	// label is emitted per element with `item` bound to that element.
	// When unset, exactly one label is emitted with item=nil.
	// +optional
	ForEach string `json:"forEach,omitempty"`
	// Label is the (resourceType, id, name) CEL triple. All three
	// fields are required CEL string expressions.
	Label MCPServerLabelTuple `json:"label"`
}

MCPServerLabelExtract declares a per-tool labels-CEL block. Mirrors the writesRelationships shape: same when/forEach semantics, same (args, result, item) CEL bindings. Evaluated by the MCP dispatcher after every successful tool call; tuples land in the runner's in-memory LabelStore and are surfaced to channel renderers via the ToolApprovalRequestPayload.Labels field. Threat-model note: labels NEVER reach any LLM context.

func (*MCPServerLabelExtract) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MCPServerLabelExtract.

func (*MCPServerLabelExtract) DeepCopyInto

func (in *MCPServerLabelExtract) DeepCopyInto(out *MCPServerLabelExtract)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type MCPServerLabelTuple

type MCPServerLabelTuple struct {
	// ResourceType is a CEL string expression yielding the SpiceDB
	// definition name (e.g. `"crm_company"`).
	ResourceType string `json:"resourceType"`
	// ID is a CEL string expression yielding the resource id.
	ID string `json:"id"`
	// Name is a CEL string expression yielding the friendly label
	// shown to approvers. Capped + sanitized at extraction time.
	Name string `json:"name"`
}

MCPServerLabelTuple is the (resourceType, id, name) triple expressed as CEL string expressions, evaluated against (args, result, item).

func (*MCPServerLabelTuple) DeepCopy

func (in *MCPServerLabelTuple) DeepCopy() *MCPServerLabelTuple

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MCPServerLabelTuple.

func (*MCPServerLabelTuple) DeepCopyInto

func (in *MCPServerLabelTuple) DeepCopyInto(out *MCPServerLabelTuple)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type MCPServerList

type MCPServerList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []MCPServer `json:"items"`
}

+kubebuilder:object:root=true

func (*MCPServerList) DeepCopy

func (in *MCPServerList) DeepCopy() *MCPServerList

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MCPServerList.

func (*MCPServerList) DeepCopyInto

func (in *MCPServerList) DeepCopyInto(out *MCPServerList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*MCPServerList) DeepCopyObject

func (in *MCPServerList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type MCPServerRelationshipTuple

type MCPServerRelationshipTuple struct {
	// Resource is a CEL string expression that must evaluate to a
	// SpiceDB object reference of the form "<type>:<id>".
	Resource string `json:"resource"`
	// Relation is a CEL string expression that must evaluate to a
	// relation name declared on the Resource's type.
	Relation string `json:"relation"`
	// Subject is a CEL string expression that must evaluate to a
	// SpiceDB object reference of the form "<type>:<id>".
	Subject string `json:"subject"`
}

MCPServerRelationshipTuple is the (resource, relation, subject) triple expressed as CEL string expressions.

func (*MCPServerRelationshipTuple) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MCPServerRelationshipTuple.

func (*MCPServerRelationshipTuple) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type MCPServerRelationshipWrite

type MCPServerRelationshipWrite struct {
	// When is a CEL boolean expression with `args`, `result` in
	// scope; the block is skipped if it evaluates false.
	// When unset, the block is always taken.
	// +optional
	When string `json:"when,omitempty"`
	// ForEach is a CEL expression that must evaluate to a list.
	// One tuple is emitted per element, with `item` bound to that
	// element. When unset, exactly one tuple is emitted.
	// +optional
	ForEach string `json:"forEach,omitempty"`
	// Tuple holds the CEL expressions that compose the SpiceDB
	// relationship tuple.
	Tuple MCPServerRelationshipTuple `json:"tuple"`
	// Exclusive makes this write atomically write-once per subject: the write
	// FAILS (no tuple written) if the subject already holds `relation` on ANY
	// resource of the tuple's resource type. Used for session-pin semantics
	// (a session may be pinned to exactly one cluster). Implemented as a
	// SpiceDB MUST_NOT_MATCH precondition, so it is atomic under concurrent
	// writers. Default false preserves the plain TOUCH-upsert behavior.
	// +optional
	Exclusive bool `json:"exclusive,omitempty"`
	// RequireSlotBound refuses every tuple this block emits unless the calling
	// session holds a SLOT GRANT on the tuple's RESOURCE — the instance a human
	// (or the pool machinery acting on one's approval) named for this session.
	//
	// Declare it on a block that writes an identity or an authority tuple onto
	// an instance the tool itself names. Without it, whatever id the tool's
	// response happens to carry becomes the resource of a real SpiceDB write,
	// so a response naming somebody else's instance writes there too. With it,
	// the write can only ever land on an instance the session was already
	// bound to.
	//
	// The grant's PERMISSION is deliberately not consulted: a grant is a human
	// act naming the instance, and which permission it carries is the pool
	// machinery's concern. Any slot_grant_* on the resource binds it.
	//
	// Default false is byte-identical to the previous behaviour — an unmarked
	// block consults nothing. A marked block whose dispatcher has no checker
	// wired is REFUSED, not written: see pkg/authz/relwrites.Run.
	// +optional
	RequireSlotBound bool `json:"requireSlotBound,omitempty"`
}

MCPServerRelationshipWrite is one JIT-write block on an MCPServer tool. The dispatcher invokes it after tool.Execute succeeds.

func (*MCPServerRelationshipWrite) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MCPServerRelationshipWrite.

func (*MCPServerRelationshipWrite) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type MCPServerServer

type MCPServerServer struct {
	// URL is the MCP server endpoint. Production servers should use
	// https://; http:// is permitted for in-cluster servers addressed
	// by a Kubernetes Service DNS name (*.svc / *.svc.cluster.local)
	// and for loopback test stubs.
	//
	// Scheme/SSRF validation is intentionally NOT a CRD
	// +kubebuilder:validation:Pattern: a regex blunt enough to forbid
	// loopback/private destinations would also reject the legitimate
	// in-cluster http:// case and the envtest e2e harness (which
	// applies http://127.0.0.1:<port>).
	//
	// The authoritative runtime SSRF backstop is the guarded dialer in
	// pkg/x/safehttp: every client that fetches this URL (the MCPServer
	// controller's probe, the runner's session-start probe, the MCP
	// tool-call dispatcher, and the `oap` CLI probe paths) resolves the
	// host and refuses any private/loopback/link-local IP. A finer-
	// grained controller-side scheme check (e.g. surfacing a non-https,
	// non-in-cluster URL on the Valid condition) is RECOMMENDED as a
	// future enhancement but is not implemented today — do not rely on
	// the controller for SSRF protection; pkg/x/safehttp is the backstop.
	URL string `json:"url"`
	// Transport is the MCP wire transport to speak to this endpoint.
	Transport string `json:"transport"`
}

func (*MCPServerServer) DeepCopy

func (in *MCPServerServer) DeepCopy() *MCPServerServer

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MCPServerServer.

func (*MCPServerServer) DeepCopyInto

func (in *MCPServerServer) DeepCopyInto(out *MCPServerServer)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type MCPServerSpec

type MCPServerSpec struct {
	// Name is the toolspec-level server name; tool names are prefixed with it.
	Name string `json:"name"`
	// Version is the spec author's version of this declaration, not the
	// server's self-reported one.
	Version string `json:"version"`

	// PinnedManifestHash, when set, asserts the canonical tools/list manifest
	// hash ("sha256:…", see pkg/authz/pinning/kinds/mcp) this server must serve.
	// Drift from it flips the PinDrift condition and is enforced per the
	// effective pinning mode. When unset, the first observed manifest is
	// recorded as the baseline (trust-on-first-use) in status.pin.
	// +optional
	PinnedManifestHash string `json:"pinnedManifestHash,omitempty"`

	// Intent is the one-line purpose shown to authors and reviewers.
	Intent string `json:"intent,omitempty"`
	// Server is where the endpoint lives and how to talk to it.
	Server MCPServerServer `json:"server"`
	// Auth is how outbound calls to the server are authenticated.
	Auth MCPServerAuth `json:"auth,omitempty"`
	// CallTimeout bounds a single tool call; zero uses the dispatcher default.
	CallTimeout metav1.Duration `json:"callTimeout,omitempty"`
	// Tools is the allowlist: a tool the server offers but that is absent here
	// is never callable.
	Tools []MCPServerTool `json:"tools"`
	// ToolResourceMap declares, per-tool, the SpiceDB resource a read
	// accesses. Consumed by the runner's information-leakage gate.
	// +optional
	ToolResourceMap []ToolResourceMapping `json:"toolResourceMap,omitempty"`
	// Generation holds authoring-time test cases for the spec generator.
	Generation *MCPServerSpecGeneration `json:"generation,omitempty"`

	// SpiceDBSchema declares the SpiceDB resource definitions this MCPServer
	// contributes. The guardian's schema composer concatenates fragments across
	// every MCPServer an AgentClass references; identical declarations dedupe,
	// conflicting ones fail the AgentClass reconcile.
	// +optional
	SpiceDBSchema *SpiceDBSchemaFragment `json:"spicedbSchema,omitempty"`

	// SiteURL is the service's user-facing homepage. identityd discovers
	// a favicon from this URL for credential-row UI surfaces (portal,
	// deep-link menu, Slack Home tab, credential-request DM). Optional;
	// unset renders the deterministic generated fallback icon.
	//
	// Must be http or https; admission rejects other schemes.
	// +optional
	SiteURL string `json:"siteURL,omitempty"`

	// MCPUIAppTools opts this MCPServer into MCP-UI "app"-visible tool calls:
	// a tool whose MCPServerTool.Visibility is EXACTLY ["app"] — present, and
	// NOT also carrying "model"; see that field's own doc for why "including
	// app" is not sufficient — is synthesized into the browser-callable
	// registry instead of the model's (pkg/agent/tool/mcp/synthesize.go).
	// Absent ⇒ disabled (the permanent default): an app-only tool is then
	// synthesized into NEITHER registry.
	// +optional
	MCPUIAppTools *MCPUIAppToolsSpec `json:"mcpUiAppTools,omitempty"`
}

MCPServerSpec mirrors pkg/tools/mcp/spec.Spec field-for-field. Use ToSpec() to obtain the runtime form. Mirroring (rather than embedding) keeps deepcopy generation fully local to v1alpha1.

func (*MCPServerSpec) DeepCopy

func (in *MCPServerSpec) DeepCopy() *MCPServerSpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MCPServerSpec.

func (*MCPServerSpec) DeepCopyInto

func (in *MCPServerSpec) DeepCopyInto(out *MCPServerSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*MCPServerSpec) LookupToolResourceMapping

func (s *MCPServerSpec) LookupToolResourceMapping(name string) *ToolResourceMapping

LookupToolResourceMapping returns the mapping for tool name `name`, or nil.

func (*MCPServerSpec) ToSpec

func (s *MCPServerSpec) ToSpec() (*mcpspec.Spec, error)

ToSpec converts the CR spec to the pkg/tools/mcp/spec.Spec runtime form via JSON round-trip. The two types share an identical JSON shape by construction.

type MCPServerSpecGeneration

type MCPServerSpecGeneration struct {
	// TestCases are the author-supplied allow/deny examples for the spec.
	TestCases []MCPServerSpecTestCase `json:"testCases,omitempty"`
}

MCPServerSpecGeneration mirrors mcpspec.Generation.

func (*MCPServerSpecGeneration) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MCPServerSpecGeneration.

func (*MCPServerSpecGeneration) DeepCopyInto

func (in *MCPServerSpecGeneration) DeepCopyInto(out *MCPServerSpecGeneration)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type MCPServerSpecTestCase

type MCPServerSpecTestCase struct {
	// Intent describes what this case is meant to demonstrate.
	Intent string `json:"intent,omitempty"`
	// ToolName is the tool the case exercises.
	ToolName string `json:"toolName"`
	// Args is the call's argument envelope.
	// +kubebuilder:pruning:PreserveUnknownFields
	// +kubebuilder:validation:Schemaless
	Args json.RawMessage `json:"args,omitempty"`
	// ExpectedAllow is whether the constraints should admit these args.
	ExpectedAllow bool `json:"expectedAllow"`
}

MCPServerSpecTestCase mirrors mcpspec.TestCase. Args is stored as raw JSON to sidestep deepcopy/OpenAPI generation issues with map[string]any — the JSON roundtrip in ToSpec() converts back to map[string]any on the runtime side.

func (*MCPServerSpecTestCase) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MCPServerSpecTestCase.

func (*MCPServerSpecTestCase) DeepCopyInto

func (in *MCPServerSpecTestCase) DeepCopyInto(out *MCPServerSpecTestCase)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type MCPServerStatus

type MCPServerStatus struct {
	// ObservedGeneration is the spec generation this status reflects.
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`
	// LastValidatedAt is the last time the server spec was successfully validated.
	// +optional
	LastValidatedAt *metav1.Time `json:"lastValidatedAt,omitempty"`
	// ObservedTools is the snapshot of names returned by the server's most
	// recent successful tools/list probe. Sorted, deduplicated.
	// +optional
	ObservedTools []string `json:"observedTools,omitempty"`

	// Pin is the recorded manifest baseline in the common PinRecord shape:
	// Digest = canonical manifest hash, Version = the server's self-reported
	// serverInfo.version (audit metadata only — self-reported by the party
	// pinning defends against).
	// +optional
	Pin *PinRecord `json:"pin,omitempty"`

	// Conditions carries Valid, Reachable, PinDrift and SpiceDBSchemaValid.
	// +patchStrategy=merge
	// +patchMergeKey=type
	// +optional
	Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}

func (*MCPServerStatus) DeepCopy

func (in *MCPServerStatus) DeepCopy() *MCPServerStatus

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MCPServerStatus.

func (*MCPServerStatus) DeepCopyInto

func (in *MCPServerStatus) DeepCopyInto(out *MCPServerStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type MCPServerTool

type MCPServerTool struct {
	// Name is the tool name as the upstream server reports it.
	Name string `json:"name"`
	// Intent is the one-line purpose shown to authors and reviewers.
	Intent string `json:"intent,omitempty"`
	// DescriptionOverride replaces the server's own description in the prompt;
	// empty keeps what the server sent.
	DescriptionOverride string `json:"descriptionOverride,omitempty"`
	// Args is the argument allowlist and CEL constraints for this tool.
	Args MCPServerToolArgs `json:"args,omitempty"`
	// Deny lists effect and trust capabilities that refuse the call pre-dispatch.
	Deny MCPServerToolDeny `json:"deny,omitempty"`
	// Effects is the declared effect profile used for approval and gating.
	Effects MCPServerToolEffects `json:"effects,omitempty"`
	// Visibility mirrors mcpspec.Tool.Visibility (MCP Apps' _meta.ui.visibility):
	// which surfaces ("model" / "app") a tool is exposed to.
	//
	// This repo's routing is an EXCLUSIVE split, not a fan-out (see
	// pkg/agent/tool/mcp/synthesize.go): a tool is browser-callable if and
	// only if this list contains "app" and does NOT contain "model", and the
	// owning MCPServer sets mcpUiAppTools.enabled. Concretely:
	//
	//	unset / []        -> model-visible only; never reaches the browser
	//	["app"]           -> browser-callable (with the opt-in); withheld from the model
	//	["app"] no opt-in -> rejected by default: in NEITHER registry
	//	["model"]         -> model-visible only
	//	["app","model"]   -> model-visible only; NOT browser-callable
	//
	// So "unset" is not "both": it is the model surface. A tool intended for
	// an agent UI must say ["app"] and nothing else. `oap agent lint` reports
	// any other value for a tool an AgentUI binds to.
	// +optional
	Visibility []string `json:"visibility,omitempty"`
	// Trust is the SEP-1913 trust + action-security annotation snapshot.
	// Mirrors mcpspec.Trust.
	// +optional
	Trust MCPServerToolTrust `json:"trust,omitempty"`
	// Permission declares the per-tool authz policy. Optional in the schema
	// only: a tool without one fails AgentClass-time validation.
	// +optional
	Permission *authz.Permission `json:"permission,omitempty"`

	// PermissionVariants are conditional Permission blocks, each carrying a CEL
	// `When` predicate over the call's args. First match wins; Permission above
	// is the fallback when none match.
	// +optional
	PermissionVariants []authz.PermissionVariant `json:"permissionVariants,omitempty"`

	// WritesRelationships declares JIT SpiceDB relationship writes the
	// dispatcher performs after a successful tool call. `When` and `ForEach`
	// are CEL over (args, result); the tuple fields are CEL string expressions.
	// +optional
	WritesRelationships []MCPServerRelationshipWrite `json:"writesRelationships,omitempty"`

	// Observes declares facts this tool's result asserts about specific
	// resource instances, co-derived with the subjects they are about.
	// Evaluated after a SUCCESSFUL call.
	// +optional
	// +listType=atomic
	Observes []ObservesBlock `json:"observes,omitempty"`

	// Labels declares per-tool CEL blocks extracting (resourceType, id, name)
	// tuples from tool responses for approval-prompt rendering. Evaluated after
	// a SUCCESSFUL call; failures are non-fatal (logged and skipped). Labels
	// never reach any LLM context.
	// +optional
	Labels []MCPServerLabelExtract `json:"labels,omitempty"`
}

func (*MCPServerTool) DeepCopy

func (in *MCPServerTool) DeepCopy() *MCPServerTool

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MCPServerTool.

func (*MCPServerTool) DeepCopyInto

func (in *MCPServerTool) DeepCopyInto(out *MCPServerTool)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type MCPServerToolArgs

type MCPServerToolArgs struct {
	// AllowedFields lists the argument keys the agent may pass to this
	// tool. Enforcement is fail-closed: an empty/unset AllowedFields
	// DENIES any argument the agent passes (an arg-less tool still
	// works). To accept free-form arguments, set UnconstrainedArgs=true
	// instead of leaving this empty.
	// +optional
	AllowedFields []string `json:"allowedFields,omitempty"`
	// UnconstrainedArgs explicitly opts this tool out of AllowedFields
	// enforcement, for a tool that legitimately accepts arbitrary
	// arguments. Required to allow free-form args — an empty AllowedFields
	// alone is fail-closed, not allow-all.
	// +optional
	UnconstrainedArgs bool `json:"unconstrainedArgs,omitempty"`
	// Constraints are CEL predicates every call's args must satisfy (AND).
	Constraints []MCPServerConstraint `json:"constraints,omitempty"`
	// SensitiveFields names arg keys to redact from logs and approval prompts.
	SensitiveFields []string `json:"sensitiveFields,omitempty"`
}

func (*MCPServerToolArgs) DeepCopy

func (in *MCPServerToolArgs) DeepCopy() *MCPServerToolArgs

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MCPServerToolArgs.

func (*MCPServerToolArgs) DeepCopyInto

func (in *MCPServerToolArgs) DeepCopyInto(out *MCPServerToolArgs)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type MCPServerToolDeny

type MCPServerToolDeny struct {
	// Effects denies calls by declared effect (destructive, reads, writes).
	Effects MCPServerDenyEffects `json:"effects,omitempty"`
	// Trust denies calls by asserted SEP-1913 trust capability.
	// +optional
	Trust MCPServerDenyTrust `json:"trust,omitempty"`
}

func (*MCPServerToolDeny) DeepCopy

func (in *MCPServerToolDeny) DeepCopy() *MCPServerToolDeny

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MCPServerToolDeny.

func (*MCPServerToolDeny) DeepCopyInto

func (in *MCPServerToolDeny) DeepCopyInto(out *MCPServerToolDeny)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type MCPServerToolEffects

type MCPServerToolEffects struct {
	// Destructive means a call can remove or overwrite upstream state.
	Destructive bool `json:"destructive,omitempty"`
	// ReadOnly means a call mutates nothing upstream.
	ReadOnly bool `json:"readOnly,omitempty"`
	// Idempotent means repeating a call has the same effect as making it once.
	Idempotent bool `json:"idempotent,omitempty"`
	// OpenWorld means the call may reach systems beyond the named server.
	OpenWorld bool `json:"openWorld,omitempty"`
}

MCPServerToolEffects mirrors mcpspec.Effects.

func (*MCPServerToolEffects) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MCPServerToolEffects.

func (*MCPServerToolEffects) DeepCopyInto

func (in *MCPServerToolEffects) DeepCopyInto(out *MCPServerToolEffects)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type MCPServerToolTrust

type MCPServerToolTrust struct {
	// MaliciousActivityHint is the server's own admission that this tool can be
	// abused; self-reported, so treat it as a signal, not a guarantee.
	// +optional
	MaliciousActivityHint bool `json:"maliciousActivityHint,omitempty"`
	// Attribution names the parties the server credits for the tool.
	// +optional
	Attribution []string `json:"attribution,omitempty"`
	// InputMetadata is the server's per-argument security annotations, verbatim.
	// +optional
	// +kubebuilder:pruning:PreserveUnknownFields
	// +kubebuilder:validation:Schemaless
	InputMetadata *apiextv1.JSON `json:"inputMetadata,omitempty"`
	// ReturnMetadata is the server's per-result security annotations, verbatim.
	// +optional
	// +kubebuilder:pruning:PreserveUnknownFields
	// +kubebuilder:validation:Schemaless
	ReturnMetadata *apiextv1.JSON `json:"returnMetadata,omitempty"`
}

MCPServerToolTrust mirrors mcpspec.Trust — a snapshot of SEP-1913 trust + action-security annotations captured from the MCP server's tools/list response.

func (*MCPServerToolTrust) DeepCopy

func (in *MCPServerToolTrust) DeepCopy() *MCPServerToolTrust

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MCPServerToolTrust.

func (*MCPServerToolTrust) DeepCopyInto

func (in *MCPServerToolTrust) DeepCopyInto(out *MCPServerToolTrust)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type MCPUIAppToolsSpec

type MCPUIAppToolsSpec struct {
	// Enabled turns the capability on; no app-visible tool is callable without it.
	Enabled bool `json:"enabled"`

	// MaxCallsPerMin optionally caps autonomous app-tool calls per minute.
	// Enforced by a DEDICATED per-origin limiter on the app-tool call path
	// (runner.AppToolRateLimiter, wired in internal/cmd/runner from this field) —
	// deliberately NOT a toolguard rule, which would also gate this
	// MCPServer's LLM-visible tools and strip its circuit breaker. Nil ⇒
	// unlimited.
	// +optional
	// +kubebuilder:validation:Minimum=1
	MaxCallsPerMin *int32 `json:"maxCallsPerMin,omitempty"`
}

MCPUIAppToolsSpec configures MCP-UI app-visible tool calls for this MCPServer.

func (*MCPUIAppToolsSpec) DeepCopy

func (in *MCPUIAppToolsSpec) DeepCopy() *MCPUIAppToolsSpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MCPUIAppToolsSpec.

func (*MCPUIAppToolsSpec) DeepCopyInto

func (in *MCPUIAppToolsSpec) DeepCopyInto(out *MCPUIAppToolsSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type MetaagentConfig

type MetaagentConfig struct {
	// Trigger is when the metaagent classifies a turn, and whether it acts.
	//
	//   mention (default) — ambient off; explicit @metaagent only.
	//   shadow            — classify every turn, apply nothing, record it.
	//   inline            — classify every turn and act.
	//
	// +kubebuilder:validation:Enum=mention;shadow;inline
	// +kubebuilder:default=mention
	// +optional
	Trigger string `json:"trigger,omitempty"`
}

MetaagentConfig configures the metaagent's ambient trigger.

func (*MetaagentConfig) AppliesAmbient

func (m *MetaagentConfig) AppliesAmbient() bool

AppliesAmbient reports whether an ambient classification may CHANGE anything. Only inline does; shadow runs the whole path and stops short of the effect.

func (*MetaagentConfig) ClassifiesAmbient

func (m *MetaagentConfig) ClassifiesAmbient() bool

ClassifiesAmbient reports whether this trigger runs the classifier on every inbound turn — true for both shadow and inline.

func (*MetaagentConfig) DeepCopy

func (in *MetaagentConfig) DeepCopy() *MetaagentConfig

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetaagentConfig.

func (*MetaagentConfig) DeepCopyInto

func (in *MetaagentConfig) DeepCopyInto(out *MetaagentConfig)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*MetaagentConfig) GetMetaagentTrigger

func (m *MetaagentConfig) GetMetaagentTrigger() string

GetMetaagentTrigger returns the resolved trigger, defaulting to mention.

Fail-safe on an unrecognized value: anything that is not exactly shadow or inline resolves to mention, so a typo NARROWS when the metaagent looks rather than silently widening it. The CRD enum rejects a bad value at admission; this covers one that predates the enum or arrives from an older tier.

type ModelCatalogEntry

type ModelCatalogEntry struct {
	// Name is the model identifier, e.g. "claude-opus-4-8".
	Name string `json:"name"`

	// Provider is the LLM provider serving this model.
	// +kubebuilder:validation:Enum=anthropic;openai;openrouter;test
	// +optional
	Provider string `json:"provider,omitempty"`

	// TokenRef is the central API-token Secret (cluster tier only). Required at
	// the cluster tier; forbidden below it.
	// +optional
	TokenRef *NamespacedSecretKeyRef `json:"tokenRef,omitempty"`

	// Default marks the inherited default entry. At most one cluster entry may
	// set it; `oap init` sets it.
	// +optional
	Default bool `json:"default,omitempty"`

	// InputPerMTok / OutputPerMTok are best-effort USD list prices per million
	// input / output tokens, surfaced by the admin dashboard's cost estimator
	// (labelled "estimated"). Optional; unset ⇒ no catalog price for this model
	// and the dashboard shows "est. (no price)".
	// +kubebuilder:validation:Minimum=0
	// +optional
	InputPerMTok float64 `json:"inputPerMTok,omitempty"`
	// OutputPerMTok is the output half of the same best-effort list price.
	// +kubebuilder:validation:Minimum=0
	// +optional
	OutputPerMTok float64 `json:"outputPerMTok,omitempty"`

	// Routing configures OpenRouter dynamic model selection (auto/fallback/provider
	// routing). Only consulted when Provider == "openrouter". Optional.
	// +optional
	Routing *OpenRouterRouting `json:"routing,omitempty"`
}

ModelCatalogEntry is one permitted model in the cluster-tier model catalog. At the cluster tier TokenRef is required (the central credential); at the namespace tier entries are name-only narrowing references and TokenRef/Default must be unset (enforced by the settings webhook).

func (*ModelCatalogEntry) DeepCopy

func (in *ModelCatalogEntry) DeepCopy() *ModelCatalogEntry

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ModelCatalogEntry.

func (*ModelCatalogEntry) DeepCopyInto

func (in *ModelCatalogEntry) DeepCopyInto(out *ModelCatalogEntry)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (ModelCatalogEntry) HasPrice

func (e ModelCatalogEntry) HasPrice() bool

HasPrice reports whether this entry carries a usable per-token price.

type ModelConfig

type ModelConfig struct {
	// +kubebuilder:validation:Enum=anthropic;openai;openrouter;test
	// +optional
	Provider string `json:"provider,omitempty"`
	// +optional
	Name string `json:"name,omitempty"`
	// FromCatalog references a model catalog entry by name. Mutually exclusive
	// with an inline Provider/Name. When set, Provider and the token come from
	// the catalog entry.
	// +optional
	FromCatalog string `json:"fromCatalog,omitempty"`
	// APIKey is a bring-your-own token. Only honored when allowModelOverride is
	// granted (else a fatal violation). Catalog references leave this empty.
	// +optional
	APIKey SecretKeyRef `json:"apiKey,omitempty"`
	// RoutingMetadata refines dynamic model selection for this agent. Merged over
	// the resolved catalog entry's routing with narrowing-only semantics: an
	// AgentClass may constrain, never loosen, admin policy. Inert unless the
	// resolved model performs dynamic selection (Provider == "openrouter").
	// +optional
	RoutingMetadata *OpenRouterRouting `json:"routingMetadata,omitempty"`
}

func (*ModelConfig) DeepCopy

func (in *ModelConfig) DeepCopy() *ModelConfig

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ModelConfig.

func (*ModelConfig) DeepCopyInto

func (in *ModelConfig) DeepCopyInto(out *ModelConfig)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ModelCostBucket

type ModelCostBucket struct {
	// Model is the served-model display id verbatim (provider/model, or a
	// provider-prefixed served model under routing) — never re-parsed here.
	Model string `json:"model"`
	// InputTokens is this model's share of prompt tokens.
	// +optional
	InputTokens int64 `json:"inputTokens,omitempty"`
	// OutputTokens is this model's share of completion tokens.
	// +optional
	OutputTokens int64 `json:"outputTokens,omitempty"`
	// AmountMicroUSD is this bucket's share of the session cost, in
	// micro-USD (1e-6 USD): the provider-reported cost when available,
	// else tokens × the resolved per-model pricing table rate.
	// +optional
	AmountMicroUSD int64 `json:"amountMicroUSD,omitempty"`
	// PricingKnown is true when AmountMicroUSD is a real cost — either the
	// provider reported it directly, or the pricing table had a rate for
	// this model. False means AmountMicroUSD is 0 and must not be shown as
	// real spend (same no-fabrication contract as EstimatedSessionCost).
	// +optional
	PricingKnown bool `json:"pricingKnown,omitempty"`
}

ModelCostBucket is one served model's slice of a session's cost estimate.

func (*ModelCostBucket) DeepCopy

func (in *ModelCostBucket) DeepCopy() *ModelCostBucket

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ModelCostBucket.

func (*ModelCostBucket) DeepCopyInto

func (in *ModelCostBucket) DeepCopyInto(out *ModelCostBucket)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type MountFormat

type MountFormat string

MountFormat describes what a mount's source content IS. The builder derives the delivery machinery from it: raw mounts the volume directly, tarGz needs an initContainer to expand the archive first. +kubebuilder:validation:Enum=raw;tarGz

const (
	MountFormatRaw   MountFormat = "raw"
	MountFormatTarGz MountFormat = "tarGz"
)

type MountSource

type MountSource struct {
	// ConfigMapRef names a ConfigMap in the session's namespace.
	// +optional
	ConfigMapRef *corev1.LocalObjectReference `json:"configMapRef,omitempty"`
}

func (*MountSource) DeepCopy

func (in *MountSource) DeepCopy() *MountSource

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MountSource.

func (*MountSource) DeepCopyInto

func (in *MountSource) DeepCopyInto(out *MountSource)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type NamespacedRef

type NamespacedRef struct {
	// Namespace is the object's namespace.
	Namespace string `json:"namespace"`
	// Name is the object's name.
	Name string `json:"name"`
}

NamespacedRef names a namespaced object.

func (*NamespacedRef) DeepCopy

func (in *NamespacedRef) DeepCopy() *NamespacedRef

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedRef.

func (*NamespacedRef) DeepCopyInto

func (in *NamespacedRef) DeepCopyInto(out *NamespacedRef)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type NamespacedSecretKeyRef

type NamespacedSecretKeyRef struct {
	// Namespace is the Secret's namespace.
	Namespace string `json:"namespace"`
	// Name is the Secret's name.
	Name string `json:"name"`
	// Key is the data key within the Secret holding the value.
	Key string `json:"key"`
}

NamespacedSecretKeyRef is a SecretKeyRef that names its namespace explicitly. Used for catalog tokens, which live in the operator/system namespace and so cannot be resolved "in the same namespace" like a tenant SecretKeyRef.

func (*NamespacedSecretKeyRef) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedSecretKeyRef.

func (*NamespacedSecretKeyRef) DeepCopyInto

func (in *NamespacedSecretKeyRef) DeepCopyInto(out *NamespacedSecretKeyRef)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type NetworkMode

type NetworkMode string

+kubebuilder:validation:Enum=none;allowlist

const (
	NetworkModeNone      NetworkMode = "none"
	NetworkModeAllowlist NetworkMode = "allowlist"
)

type OAuthCredentialSource

type OAuthCredentialSource struct {
	// SecretRef points at a Secret whose keys hold the OAuth state.
	// Required keys (read at runtime / refresh time):
	//   access_token   — current access token
	//   refresh_token  — long-lived refresh token (optional)
	//   expires_at     — RFC 3339 timestamp; absent → never expires
	//   token_endpoint — auth-server URL for refresh
	//   client_id      — OAuth client ID used at registration
	//   scope          — granted scopes (informational)
	SecretRef SecretRef `json:"secretRef"`
}

func (*OAuthCredentialSource) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OAuthCredentialSource.

func (*OAuthCredentialSource) DeepCopyInto

func (in *OAuthCredentialSource) DeepCopyInto(out *OAuthCredentialSource)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type OapInstallStatus

type OapInstallStatus struct {
	// SourceRef identifies where the bundle came from (e.g. an OCI
	// reference or a local file path), in a form meaningful to SourceKind.
	// +optional
	SourceRef string `json:"sourceRef,omitempty"`

	// Digest is the content digest of the installed `.oap` bundle
	// (e.g. "sha256:...").
	Digest string `json:"digest"`

	// Version is the bundle's declared version, when the bundle manifest
	// carries one.
	// +optional
	Version string `json:"version,omitempty"`

	// SourceKind identifies the kind of SourceRef: "registry" (an OCI
	// reference) or "file" (a local .oap file or folder-source directory).
	SourceKind string `json:"sourceKind"`

	// InstalledAt is when this AgentClass was installed from the bundle.
	// +optional
	InstalledAt metav1.Time `json:"installedAt,omitempty"`
}

OapInstallStatus is the provenance record for an AgentClass installed from a `.oap` bundle.

func (*OapInstallStatus) DeepCopy

func (in *OapInstallStatus) DeepCopy() *OapInstallStatus

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OapInstallStatus.

func (*OapInstallStatus) DeepCopyInto

func (in *OapInstallStatus) DeepCopyInto(out *OapInstallStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ObserveSubject

type ObserveSubject struct {
	// ResourceType is a CEL string expression yielding a SpiceDB definition
	// name (commonly a literal, e.g. `"github_pr"`).
	ResourceType string `json:"resourceType"`
	// ResourceID is a CEL string expression yielding the object id.
	ResourceID string `json:"resourceID"`
}

ObserveSubject is one subject as a (resourceType, resourceID) pair of CEL string expressions.

func (*ObserveSubject) DeepCopy

func (in *ObserveSubject) DeepCopy() *ObserveSubject

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObserveSubject.

func (*ObserveSubject) DeepCopyInto

func (in *ObserveSubject) DeepCopyInto(out *ObserveSubject)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ObservedCredential

type ObservedCredential struct {
	// Name is the credential name from spec.credentials[].name.
	Name string `json:"name"`

	// Fingerprint is "<type>:<secretRefName>": stable across reconciles and
	// sensitive to both a type change (static↔oauth) and a Secret-ref rename,
	// which is how an OAuth refresh surfaces a replaced credential.
	Fingerprint string `json:"fingerprint"`

	// SecretNames are the backing Secret names for this credential, recorded
	// so a REMOVED credential's revoke key can be reconstructed without
	// re-reading a spec that no longer mentions it.
	//
	// Empty for UserIdentity, whose backing Secret name is derived from
	// (identity name, credential name) rather than referenced by the spec.
	// +optional
	SecretNames []string `json:"secretNames,omitempty"`
}

ObservedCredential is one entry of an identity's status.observedCredentials — the operator's record of a credential it has already accounted for in the revocation diff. Shared by AgentIdentity and UserIdentity.

func (*ObservedCredential) DeepCopy

func (in *ObservedCredential) DeepCopy() *ObservedCredential

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObservedCredential.

func (*ObservedCredential) DeepCopyInto

func (in *ObservedCredential) DeepCopyInto(out *ObservedCredential)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ObservedPin

type ObservedPin struct {
	// Name identifies the dependency: MCPServer ref name, SidecarToolbox
	// name, Toolkit name, or canonical skill name.
	Name string `json:"name"`
	// Pin is the identity observed at session start; drift is this against the
	// definition CR's baseline.
	Pin PinRecord `json:"pin"`
}

ObservedPin is one per-session pin observation.

func UpsertObservedPin

func UpsertObservedPin(list []ObservedPin, name string, pin PinRecord) []ObservedPin

UpsertObservedPin upserts (by Name + Pin.Kind) one ObservedPin into list. When an entry with the same Name and Kind already exists, its Pin is replaced; otherwise a new entry is appended. Keying by both fields prevents a same-named dependency from different kinds (e.g. an MCP server and an image pin that share a name) from clobbering each other. The updated slice is returned; the caller is responsible for assigning it back (e.g. sess.Status.ObservedPins = UpsertObservedPin(sess.Status.ObservedPins, name, pin)).

func (*ObservedPin) DeepCopy

func (in *ObservedPin) DeepCopy() *ObservedPin

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObservedPin.

func (*ObservedPin) DeepCopyInto

func (in *ObservedPin) DeepCopyInto(out *ObservedPin)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ObservedSettingsLimits

type ObservedSettingsLimits struct {
	// AllowedMCPServers is the set of names last observed in
	// spec.limits.allowedMCPServers.
	// +optional
	AllowedMCPServers *[]string `json:"allowedMCPServers,omitempty"`

	// AllowedToolkits is the set of names last observed in
	// spec.limits.allowedToolkits.
	// +optional
	AllowedToolkits *[]string `json:"allowedToolkits,omitempty"`
}

ObservedSettingsLimits is the operator's record of the allowlists it last diffed for tool-origin revocation. Each list preserves the spec's tri-state: a nil pointer records "the list was absent" (allow-all, nothing to revoke against), a pointer to an empty list records deny-all, and a populated list records the allowlist. Collapsing nil and [] would turn a widening to allow-all into a revoke of every origin.

Names are sorted so an unchanged spec produces a byte-identical record.

func (*ObservedSettingsLimits) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObservedSettingsLimits.

func (*ObservedSettingsLimits) DeepCopyInto

func (in *ObservedSettingsLimits) DeepCopyInto(out *ObservedSettingsLimits)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ObservesBlock

type ObservesBlock struct {
	// When is a CEL boolean over `args`, `result`; the block is skipped if it
	// evaluates false. Unset means always taken.
	// +optional
	When string `json:"when,omitempty"`
	// ForEach is a CEL expression yielding a list. One observation is emitted
	// per element with `item` bound to it. Unset emits exactly one, with `item`
	// bound to nil.
	// +optional
	ForEach string `json:"forEach,omitempty"`
	// Subjects are the objects this observation is about, as CEL string
	// expression pairs. At least one is REQUIRED: a block recording facts about
	// nothing would produce a session-scoped boolean that answers for every
	// instance at once.
	// +kubebuilder:validation:MinItems=1
	// +listType=atomic
	Subjects []ObserveSubject `json:"subjects"`
	// Facts maps a fact name to a CEL expression yielding its value, evaluated
	// against the same `item` as Subjects.
	// +kubebuilder:validation:MinProperties=1
	Facts map[string]string `json:"facts"`
}

ObservesBlock is one observation declaration on a tool. The dispatcher evaluates it after tool.Execute succeeds.

One block yields the SUBJECTS and the FACTS from the same `item`. They are not two declarations that happen to agree: a fact and the object it describes come out of one payload, which is what stops a fact about one instance from being made to answer for another.

func (*ObservesBlock) DeepCopy

func (in *ObservesBlock) DeepCopy() *ObservesBlock

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObservesBlock.

func (*ObservesBlock) DeepCopyInto

func (in *ObservesBlock) DeepCopyInto(out *ObservesBlock)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type OpenBreaker

type OpenBreaker struct {
	// Key is the breaker key: "tool/<name>" or "origin/<kind>/<name>".
	Key string `json:"key"`
	// OpenedAt is when this circuit last opened.
	// +optional
	OpenedAt metav1.Time `json:"openedAt,omitempty"`
	// RetryAt is when the cool-off elapses and calls are admitted again.
	// +optional
	RetryAt metav1.Time `json:"retryAt,omitempty"`
	// Trips counts consecutive opens without an intervening success.
	// +optional
	Trips int32 `json:"trips,omitempty"`
}

OpenBreaker is one currently-open circuit.

func (*OpenBreaker) DeepCopy

func (in *OpenBreaker) DeepCopy() *OpenBreaker

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenBreaker.

func (*OpenBreaker) DeepCopyInto

func (in *OpenBreaker) DeepCopyInto(out *OpenBreaker)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type OpenRouterMaxPrice

type OpenRouterMaxPrice struct {
	// +kubebuilder:validation:Minimum=0
	// +optional
	Prompt float64 `json:"prompt,omitempty"` // max prompt price, USD per million tokens (0 = no cap)
	// +kubebuilder:validation:Minimum=0
	// +optional
	Completion float64 `json:"completion,omitempty"` // max completion price, USD per million tokens (0 = no cap)
}

OpenRouterMaxPrice caps per-model price, in USD per MILLION tokens (OpenRouter's max_price units; e.g. Prompt: 1 means at most $1/M prompt tokens). 0 on an axis means "no cap on this axis" (see pkg/platform/settings/routingmerge.go's tightenPrice and pkg/agent/llm/openrouter/extra.go's maxPriceObject), not "cap at $0". Only consulted when the owning ModelCatalogEntry's Provider == "openrouter".

func (*OpenRouterMaxPrice) DeepCopy

func (in *OpenRouterMaxPrice) DeepCopy() *OpenRouterMaxPrice

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenRouterMaxPrice.

func (*OpenRouterMaxPrice) DeepCopyInto

func (in *OpenRouterMaxPrice) DeepCopyInto(out *OpenRouterMaxPrice)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type OpenRouterRouting

type OpenRouterRouting struct {
	// +optional
	// +listType=atomic
	Models []string `json:"models,omitempty"` // fallback list (tried in order)
	// +kubebuilder:validation:Enum=price;throughput;latency
	// +optional
	Sort string `json:"sort,omitempty"` // "price" | "throughput" | "latency"
	// +optional
	// +listType=atomic
	Order []string `json:"order,omitempty"` // provider slugs, in order
	// +optional
	// +listType=atomic
	Only []string `json:"only,omitempty"` // allow only these provider slugs
	// +optional
	// +listType=atomic
	Ignore []string `json:"ignore,omitempty"` // skip these provider slugs
	// +optional
	AllowFallbacks *bool `json:"allowFallbacks,omitempty"` // default true
	// RequireParameters: unset defaults to TRUE when the request carries tools
	// (the openrouter provider injects it — see
	// pkg/agent/llm/openrouter/extra.go's providerObject); explicit values are
	// honored verbatim. OpenRouter's own wire default is false — agents need
	// tools, so auto/fallback routing must only pick providers that support
	// the request's parameters.
	// +optional
	RequireParameters *bool `json:"requireParameters,omitempty"`
	// +kubebuilder:validation:Enum=allow;deny
	// +optional
	DataCollection string `json:"dataCollection,omitempty"` // "allow" | "deny"
	// +optional
	MaxPrice *OpenRouterMaxPrice `json:"maxPrice,omitempty"` // per-million-token price caps

	// Auto-router hints — apply when the model is "openrouter/auto".
	// +optional
	// +listType=atomic
	AllowedModels []string `json:"allowedModels,omitempty"` // narrow the auto-router candidate set
	// CostQualityTradeoff is intentionally unvalidated (no Minimum/Maximum
	// marker): OpenRouter documents no formal range for this value — its docs
	// example value is 3 — so a range marker would reject valid input.
	// +optional
	CostQualityTradeoff *float64 `json:"costQualityTradeoff,omitempty"` // auto-router cost/quality tradeoff (OpenRouter docs example: 3; range not formally documented)
}

OpenRouterRouting configures OpenRouter's dynamic model-selection behavior (auto-router, fallback list, provider preferences). It is spec — a pure function of admin/AgentClass input — and is only consulted when the owning ModelCatalogEntry's Provider == "openrouter"; on any other provider it is ignored. This is the CRD-facing (kubebuilder-validated) mirror of the neutral `llm.OpenRouterRouting` type; `pkg/platform/settings/resolve.go` converts between the two (a small `toLLM()` converter) — keep the two in sync.

func (*OpenRouterRouting) DeepCopy

func (in *OpenRouterRouting) DeepCopy() *OpenRouterRouting

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenRouterRouting.

func (*OpenRouterRouting) DeepCopyInto

func (in *OpenRouterRouting) DeepCopyInto(out *OpenRouterRouting)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type OpeningBadge

type OpeningBadge string

OpeningBadge is the status marker rendered on a triggered session's opening message. The concluded-outcome values equal channelkinds.TriggerOutcome so a verdict folds directly; in_progress/unfinished/done are framework-derived. +kubebuilder:validation:Enum=in_progress;clean;problems_found;could_not_finish;unfinished;done

const (
	OpeningBadgeInProgress     OpeningBadge = "in_progress"
	OpeningBadgeClean          OpeningBadge = "clean"
	OpeningBadgeProblemsFound  OpeningBadge = "problems_found"
	OpeningBadgeCouldNotFinish OpeningBadge = "could_not_finish"
	OpeningBadgeUnfinished     OpeningBadge = "unfinished"
	OpeningBadgeDone           OpeningBadge = "done"
)

type OutputArtifact

type OutputArtifact struct {
	// Path is the in-pod path this artifact was harvested from.
	Path string `json:"path"`
	// ArtifactRef is the opaque storage reference the bytes landed under.
	ArtifactRef string `json:"artifactRef"`
	// Size is the stored artifact's size in bytes.
	Size int64 `json:"size"`
}

func (*OutputArtifact) DeepCopy

func (in *OutputArtifact) DeepCopy() *OutputArtifact

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OutputArtifact.

func (*OutputArtifact) DeepCopyInto

func (in *OutputArtifact) DeepCopyInto(out *OutputArtifact)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type OwnerCeiling

type OwnerCeiling struct {
	// StarterOnly forbids Channels from declaring explicit/foreign-group
	// owners — owner must resolve to the session's starting user.
	// +optional
	StarterOnly bool `json:"starterOnly,omitempty"`

	// Fixed pins the owner to this subject/subject-set ref regardless of
	// Channel policy. Mutually exclusive with StarterOnly.
	// +optional
	Fixed string `json:"fixed,omitempty"`
}

OwnerCeiling caps Channel-declared owner policy for a class.

func (*OwnerCeiling) DeepCopy

func (in *OwnerCeiling) DeepCopy() *OwnerCeiling

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OwnerCeiling.

func (*OwnerCeiling) DeepCopyInto

func (in *OwnerCeiling) DeepCopyInto(out *OwnerCeiling)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type OwnerSource

type OwnerSource string

OwnerSource names HOW a session's owner was resolved. It is the difference between "this person owns the session" and "everyone in that channel owns the session", which is not inferable from the subject string alone — a subject set may be a two-person group or an entire Slack channel.

const (
	OwnerSourcePassthrough OwnerSource = "passthrough"
	// OwnerSourceCeiling: the class's ownerCeiling.fixed pinned the owner.
	// Distinct from Explicit so a surface describing ownership to a human can
	// say the ADMIN pinned it rather than implying the Channel chose it.
	OwnerSourceCeiling       OwnerSource = "ceiling"
	OwnerSourceExplicit      OwnerSource = "explicit"
	OwnerSourceStarter       OwnerSource = "starter"
	OwnerSourcePermission    OwnerSource = "permission"
	OwnerSourceOutputChannel OwnerSource = "outputChannel"
)

func ResolveOwnerSubject

func ResolveOwnerSubject(identityMode string, policy *ChannelOwnerPolicy, starter, outputGroupRef string, ceiling *OwnerCeiling) (string, OwnerSource, error)

ResolveOwnerSubject decides the SpiceDB subject that owns a session, and reports which rule decided it.

Precedence (highest first):

  1. identityMode=userPassthrough → starter (forced; error if absent)
  2. policy.Explicit → explicit override
  3. starter != "" → channel-provided starting user
  4. policy.Ownerless.Permission → permission subject-set
  5. outputGroupRef != "" → the output channel's membership
  6. nothing resolvable → error (fail-closed)

The ORDER is what makes channel ownership narrower than it first appears: starter outranks both ownerless sources, so on a channel where the kind attributes messages to a user (Slack), the human who summoned the agent owns the session and rule 5 never fires. It applies to kinds with no per-user attribution — which is exactly the case it was added for.

Rule 5 is DERIVED, not declared. It fires for a policy that asked for it (spec.owner.ownerless.fromOutputChannel) and equally for a Channel that declared no owner at all — the two are indistinguishable by the time control reaches it, because every declared source has already returned above. The caller decides whether there is anything to derive from by supplying (or withholding) outputGroupRef.

Shared by the operator (which writes the SpiceDB tuple) and by anything that needs to DESCRIBE the ownership to a human. Those must not drift: a channel telling people "everyone here owns me" while the operator wrote an individual owner is worse than saying nothing.

type ParentExchange

type ParentExchange struct {
	// Exchange counts the questions this child has asked, starting at 1.
	// Monotonic: it is read back from this same field and incremented, so it
	// survives the pod being reaped mid-wait and is never reused. A parent
	// tells a new question from the one it just answered by this number
	// alone.
	Exchange int64 `json:"exchange"`

	// Pending is true while question number Exchange is unanswered — the one
	// fact that says the child is parked on its parent rather than working.
	//
	// Cleared wherever awaitingUserInputSince is cleared on a RESUME (the
	// loop's own resume hook, and the start of every Run, which is what makes
	// it self-healing across a reap→wake cycle), and deliberately NOT cleared
	// on the idle path: a child that parked, timed out to Idle and had its
	// pod reaped is still waiting, and that is precisely the state the whole
	// design keeps resumable.
	// +optional
	Pending bool `json:"pending,omitempty"`

	// Question is the text of question number Exchange, in the child's own
	// words. Bounded because status is not a transport for arbitrary
	// payloads; the child's runner refuses a longer one rather than
	// truncating, so what the parent reads is never a silently altered
	// question.
	// +kubebuilder:validation:MaxLength=4096
	// +optional
	Question string `json:"question,omitempty"`
}

func (*ParentExchange) DeepCopy

func (in *ParentExchange) DeepCopy() *ParentExchange

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ParentExchange.

func (*ParentExchange) DeepCopyInto

func (in *ParentExchange) DeepCopyInto(out *ParentExchange)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type PendingInteraction

type PendingInteraction struct {
	// RequestID is the interaction RequestRef — the map key + the handle
	// echoed on the eventual interaction_applied.
	RequestID string `json:"requestID"`

	// Category is the registered interaction category ("content_inspection").
	// status-watchdog switches wait-visibility on it; oap session approve
	// selects the decision envelope from it.
	Category string `json:"category"`

	// ApproverSubject is the SpiceDB subject-set ref that has standing to
	// decide (session-set policies: "agentsession:<ns>/<name>#approve").
	// oap session approve uses it for the client-side pre-check; the server
	// re-checks authoritatively. Empty for policies with no fixed subject.
	// +optional
	ApproverSubject string `json:"approverSubject,omitempty"`

	// RequestRef is the channel-kind-opaque round-trip handle. For the generic
	// model this equals RequestID; kept distinct for parity with the typed
	// lists and future kinds.
	// +optional
	RequestRef string `json:"requestRef,omitempty"`

	// RequestedAt is the park timestamp; used by display + the timeout watcher.
	RequestedAt metav1.Time `json:"requestedAt"`

	// AgentDisplayName is the operator-configured AgentClass label, forwarded
	// from the request payload for restart-resilient display.
	// +optional
	AgentDisplayName string `json:"agentDisplayName,omitempty"`

	// Summary is the publisher-authored one-line human description (the
	// interaction_request Lead) — the self-contained display string consumers
	// render without reading the memory layer. Category-agnostic.
	// +optional
	Summary string `json:"summary,omitempty"`
}

PendingInteraction is one generic interaction awaiting a user decision. Written by HandleInteractionRequest on park, cleared by HandleInteractionDecision (decision) / HandleInteractionApplied (timeout). The field set is a modest union of the approval families' display fields; tool_approval, info_leakage, and content_inspection each populate the subset they need (this is the single pending-approval list for all three).

func (*PendingInteraction) DeepCopy

func (in *PendingInteraction) DeepCopy() *PendingInteraction

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PendingInteraction.

func (*PendingInteraction) DeepCopyInto

func (in *PendingInteraction) DeepCopyInto(out *PendingInteraction)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type PendingRequester

type PendingRequester struct {
	// Kind is the channel kind ("slack", future "discord", ...).
	Kind string `json:"kind"`
	// TeamScope is the per-kind workspace/server scope (Slack team ID, ...).
	// +optional
	TeamScope string `json:"teamScope,omitempty"`
	// ExternalID is the per-kind user id (Slack U-id, ...).
	ExternalID string `json:"externalId"`
	// Email is the user's email when the kind has it.
	// +optional
	Email string `json:"email,omitempty"`

	// RequestRef is the kind-defined opaque handle the channel kind
	// uses to find its user-facing artifacts (rejection message, DM,
	// thread refs) when the decision arrives.
	RequestRef string `json:"requestRef"`

	// Category is the interaction category this entry was raised under —
	// "start_approval" for a parked guest session start; empty means the
	// original session-join request ("permission_request"). The decision
	// pipe's category witness reads it so a click cannot resolve a pending
	// request under a weaker standing policy than it was raised behind.
	// +optional
	Category string `json:"category,omitempty"`

	// RequestedAt is when the request was delivered to the original
	// requester.
	RequestedAt metav1.Time `json:"requestedAt"`

	// MessageText is the body of the inbound that triggered the
	// permission request. Replayed verbatim through the inbound
	// pipeline when the original requester clicks Approve, so the
	// user does not have to re-send their message after approval.
	// Truncated to a reasonable bound by the pipeline before storage.
	// +optional
	MessageText string `json:"messageText,omitempty"`

	// ChannelKey is the per-channel routing key (e.g.
	// "thread:C0:1.2") that the inbound carried. Replayed alongside
	// MessageText so the resubmit lands on the same session/thread.
	// +optional
	ChannelKey string `json:"channelKey,omitempty"`
}

PendingRequester captures one ad-hoc permission request that has been delivered to the original requester but not yet decided. Channel-agnostic; populated and cleared by channelsd's pipeline. RequestRef is opaque to the pipeline (the channel kind defines its format) and round-tripped to the eventual decision.

func (*PendingRequester) DeepCopy

func (in *PendingRequester) DeepCopy() *PendingRequester

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PendingRequester.

func (*PendingRequester) DeepCopyInto

func (in *PendingRequester) DeepCopyInto(out *PendingRequester)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type PendingRestart

type PendingRestart struct {
	// CutTurnIndex is the index of the last turn copied to the child
	// (inclusive). The user's edited message becomes turn
	// CutTurnIndex+1 in the child session. Ignored when Mode is
	// "inherit" — the reconciler recomputes the cut as the parent's
	// last turn so the whole transcript carries forward.
	CutTurnIndex int32 `json:"cutTurnIndex"`

	// Mode selects the transcript-seeding behavior: "" (restart-from-here,
	// cut at CutTurnIndex) or "inherit" (full-transcript continuation). Both
	// modes are gated on agentsession#fork and copy the parent's denied
	// tuples to the child.
	// +optional
	Mode string `json:"mode,omitempty"`

	// NewUserText is the edited message text the user submitted
	// through the channel-kind modal. Becomes the new session's
	// inbox turn at CutTurnIndex+1.
	NewUserText string `json:"newUserText"`

	// TriggeredBy is the canonical subject of the user who clicked
	// Restart (e.g., "user:alice"). Used for SpiceDB interact-perm
	// re-check by the reconciler.
	TriggeredBy identity.Subject `json:"triggeredBy"`

	// RequestedAt is when channelsd published the trigger.
	RequestedAt metav1.Time `json:"requestedAt"`

	// TargetSessionName is the deterministic name of the child
	// AgentSession the reconciler will create. Stamped here so a
	// crash-and-retry produces the same name.
	TargetSessionName string `json:"targetSessionName"`

	// NewOwnerExternalID is the channel external-id of the taking-over user
	// (takeover mode only). Stamped onto the child's
	// AnnotationStartedByExternalID so the child is owned by the new user, not
	// the parent's owner. Empty for restart/inherit modes.
	// +optional
	NewOwnerExternalID string `json:"newOwnerExternalID,omitempty"`

	// InheritHistory selects the takeover-mode transcript seeding: true copies
	// the full parent transcript (ordinary terminal), false seeds only the new
	// user's message (policy/security-halt carve-out — the halted transcript is
	// not handed to a new owner). Ignored for restart/inherit modes.
	// +optional
	InheritHistory bool `json:"inheritHistory,omitempty"`

	// Signature is channelsd's Ed25519 attestation over this marker, bound to
	// the parent session's namespace, name and UID. The operator's restart
	// reconciler verifies it before acting and refuses the restart when it is
	// absent or does not verify.
	//
	// It exists because the marker is an authorization input the session's own
	// runner can write: the runner Role grants patch on agentsessions/status,
	// Kubernetes RBAC has no field-level granularity, and takeover mode skips
	// the SpiceDB fork gate. Without an author binding, a compromised runner
	// could name any victim in triggeredBy and the operator would stamp that
	// identity onto the child session, projecting the victim's credentials into
	// the attacker's pod. See pkg/agent/restartmarker.
	// +optional
	Signature *PendingRestartSignature `json:"signature,omitempty"`
}

PendingRestart records an in-flight Restart-from-here trigger. Set on the prior (parent) AgentSession by channelsd when the RestartTrigger envelope is published; cleared by the AgentSession controller's restart reconciler once SupersededBy is populated.

func (*PendingRestart) DeepCopy

func (in *PendingRestart) DeepCopy() *PendingRestart

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PendingRestart.

func (*PendingRestart) DeepCopyInto

func (in *PendingRestart) DeepCopyInto(out *PendingRestart)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type PendingRestartSignature

type PendingRestartSignature struct {
	// Publisher is the provenance publisher identity that signed the marker.
	Publisher string `json:"publisher"`

	// KeyID is the content address of the signing key's public half. The
	// registry refuses to bind a keyID to a key that does not hash to it, so a
	// tampered registration cannot make an attacker's key resolve here.
	KeyID string `json:"keyId"`

	// Sig is the raw Ed25519 signature over the canonical marker digest.
	Sig []byte `json:"sig"`
}

PendingRestartSignature is the connector's attestation over a PendingRestart marker: an Ed25519 signature by a registered component publisher key, over a canonical digest of the marker bound to its parent session. Verified by the operator against the same publisher-key registry that backs append-only memory provenance (pkg/memory/publisherkeys).

func (*PendingRestartSignature) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PendingRestartSignature.

func (*PendingRestartSignature) DeepCopyInto

func (in *PendingRestartSignature) DeepCopyInto(out *PendingRestartSignature)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type PerPodCost

type PerPodCost struct {
	// Pod is the serving pod's name (its HOSTNAME); the upsert key.
	Pod string `json:"pod"`
	// AmountMicroUSD is this pod's own total in micro-USD (model + tool).
	// +optional
	AmountMicroUSD int64 `json:"amountMicroUSD,omitempty"`
	// Currency is the ISO code this pod's amount is denominated in ("USD").
	// +optional
	Currency string `json:"currency,omitempty"`
	// PricingKnown is false when a model this pod served had no rate (this pod's
	// amount is then a lower bound).
	// +optional
	PricingKnown bool `json:"pricingKnown,omitempty"`
	// AsOf is when this pod last computed its estimate.
	// +optional
	AsOf metav1.Time `json:"asOf,omitempty"`
	// ByModel is this pod's per-served-model breakdown.
	// +optional
	// +listType=atomic
	ByModel []ModelCostBucket `json:"byModel,omitempty"`
	// ByTool is this pod's per-interactive-toolkit breakdown.
	// +optional
	// +listType=atomic
	ByTool []ToolCostBucket `json:"byTool,omitempty"`
}

PerPodCost is one serving pod's slice of a session's cost — the per-pod source of truth the EstimatedSessionCost aggregate is derived from. A pod rewrites only its OWN entry (keyed by Pod), so the session total accumulates across pods rather than a later pod clobbering an earlier one.

func (*PerPodCost) DeepCopy

func (in *PerPodCost) DeepCopy() *PerPodCost

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PerPodCost.

func (*PerPodCost) DeepCopyInto

func (in *PerPodCost) DeepCopyInto(out *PerPodCost)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type PermissionSurfaceEntry

type PermissionSurfaceEntry struct {
	// Handle is the durable, opaque reference to the permission class.
	Handle string `json:"handle"`

	// StateImpact is the MAX severity across every tool reaching this handle.
	// Max, not per-tool: a handle one tool reads and another writes is a write
	// handle, and reporting the milder value would understate the reach.
	// +kubebuilder:validation:Enum=readonly;readwrite;external
	StateImpact string `json:"stateImpact"`

	// Tools are the LLM-facing names that reach this handle, sorted and
	// deduplicated. A tool reaching one handle through both its base
	// permission and a variant appears once — it is one route, not two.
	// +optional
	Tools []string `json:"tools,omitempty"`
}

PermissionSurfaceEntry is one permission class this session's tools can reach, and the tools that reach it.

Handle is the wire form permsurface mints — "perm:write:github_repo" or "tool:apply_workspace". It is carried as a string rather than a parsed struct because that is the form a human reads on an approval card and in an audit record, and the form a plan declares.

func (*PermissionSurfaceEntry) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PermissionSurfaceEntry.

func (*PermissionSurfaceEntry) DeepCopyInto

func (in *PermissionSurfaceEntry) DeepCopyInto(out *PermissionSurfaceEntry)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type PinRecord

type PinRecord struct {
	// Kind is the pinning registry kind name (skill, image, mcp, cli, …).
	// Validated against the registry by controllers, not by a CRD enum, so
	// new kinds register without an API change.
	// +kubebuilder:validation:MinLength=1
	Kind string `json:"kind"`

	// Strength is the syntactic pin strength of the declared ref.
	// +kubebuilder:validation:Enum=frozen;named;unpinned
	Strength string `json:"strength"`

	// Digest is the immutable identity: git sha, sha256:… image digest,
	// canonical tool-manifest hash, or binary hash.
	// +optional
	Digest string `json:"digest,omitempty"`

	// Version is the human-readable identity: tag, serverInfo.version, or
	// version-probe output.
	// +optional
	Version string `json:"version,omitempty"`

	// ObservedAt is when the recording controller observed this identity.
	// +optional
	ObservedAt *metav1.Time `json:"observedAt,omitempty"`

	// Details carries kind-specific extras (toolCount, registry host, …).
	// +optional
	Details map[string]string `json:"details,omitempty"`
}

PinRecord is the recorded identity of a pinned dependency — one shape for every kind, written by the resolving controller and consumed by settings enforcement, drift detection, audit logging, and `oap pin`. Definition CRs carry the baseline; AgentSession status carries per-session observations.

func (*PinRecord) DeepCopy

func (in *PinRecord) DeepCopy() *PinRecord

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PinRecord.

func (*PinRecord) DeepCopyInto

func (in *PinRecord) DeepCopyInto(out *PinRecord)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type PinnedMessageStatus

type PinnedMessageStatus struct {
	// Badge is the current status marker. The runner writes in_progress and, on
	// conclusion, the outcome; channelsd derives the terminal unfinished/done.
	// +optional
	Badge OpeningBadge `json:"badge,omitempty"`
	// Body is the agent-authored enrichment, last-write-wins; may be empty.
	// +optional
	Body string `json:"body,omitempty"`
	// Link is the delivered-result view link, set on conclusion; may be empty.
	// +optional
	Link string `json:"link,omitempty"`
}

PinnedMessageStatus is the runner's projection of a triggered session's opening-message content, consumed by channelsd to edit that message in place. The opening TEXT is not here — it stays in AnnotationSessionOpening.

func (*PinnedMessageStatus) DeepCopy

func (in *PinnedMessageStatus) DeepCopy() *PinnedMessageStatus

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PinnedMessageStatus.

func (*PinnedMessageStatus) DeepCopyInto

func (in *PinnedMessageStatus) DeepCopyInto(out *PinnedMessageStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type PinningBypass

type PinningBypass struct {
	// Kind is the pinning registry kind this exemption applies to.
	// +kubebuilder:validation:MinLength=1
	Kind string `json:"kind"`

	// Name identifies the exempted item: a canonical skill name, MCPServer
	// name, SidecarToolbox name, or SpiceboxToolkit name. Trailing-wildcard
	// patterns are allowed (same syntax as AllowedSkills).
	// +kubebuilder:validation:MinLength=1
	Name string `json:"name"`

	// Reason is the required audit-trail justification for the exemption.
	// +kubebuilder:validation:MinLength=1
	Reason string `json:"reason"`
}

PinningBypass exempts one named dependency from this tier's (and lower tiers') pinning rules. Reason is required: the escape hatch must leave an audit trail.

func (*PinningBypass) DeepCopy

func (in *PinningBypass) DeepCopy() *PinningBypass

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PinningBypass.

func (*PinningBypass) DeepCopyInto

func (in *PinningBypass) DeepCopyInto(out *PinningBypass)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type PinningPolicy

type PinningPolicy struct {
	// Rules is at most one requirement per dependency kind.
	// +optional
	// +listType=map
	// +listMapKey=kind
	Rules []PinningRule `json:"rules,omitempty"`

	// Bypass exempts named dependencies from this tier's rules and lower.
	// +optional
	// +listType=atomic
	Bypass []PinningBypass `json:"bypass,omitempty"`
}

PinningPolicy is the unified per-kind pinning ceiling carried in SettingsLimits. Rules narrow downward (strongest wins per kind across tiers); Bypass entries are tier-scoped escape hatches — a bypass only neutralizes rules set at its own tier or below, so a namespace bypass cannot pierce a cluster ceiling.

func (*PinningPolicy) DeepCopy

func (in *PinningPolicy) DeepCopy() *PinningPolicy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PinningPolicy.

func (*PinningPolicy) DeepCopyInto

func (in *PinningPolicy) DeepCopyInto(out *PinningPolicy)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type PinningRule

type PinningRule struct {
	// Kind is the pinning registry kind name this rule governs.
	// +kubebuilder:validation:MinLength=1
	Kind string `json:"kind"`

	// MinStrength is the minimum pin strength a declared ref must have.
	// Empty = no floor (drift observation still applies).
	// +kubebuilder:validation:Enum=frozen;named
	// +optional
	MinStrength string `json:"minStrength,omitempty"`

	// Mode governs what a violation or drift does: block (fail closed),
	// approve (force per-call user approval), warn (surface warnings only),
	// off (observe only). Empty = approve.
	// +kubebuilder:validation:Enum=block;approve;warn;off
	// +optional
	Mode string `json:"mode,omitempty"`
}

PinningRule is the requirement for one dependency kind.

func (*PinningRule) DeepCopy

func (in *PinningRule) DeepCopy() *PinningRule

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PinningRule.

func (*PinningRule) DeepCopyInto

func (in *PinningRule) DeepCopyInto(out *PinningRule)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type PlanExample

type PlanExample struct {
	// Task is the one-line request this plan answers — the "when you are asked
	// to X" half. Without it an example shows a shape with no occasion, and the
	// agent cannot tell which of several examples applies.
	// +kubebuilder:validation:MinLength=1
	// +kubebuilder:validation:MaxLength=200
	Task string `json:"task"`

	// Phases are the plan itself. Rendered as the JSON an agent would pass to
	// update_plan, because that is the form it copies.
	// +kubebuilder:validation:MinItems=1
	// +kubebuilder:validation:MaxItems=6
	// +listType=atomic
	Phases []PlanExamplePhase `json:"phases"`
}

PlanExample is one worked plan an AgentClass author supplies to guide planning, in the same shape update_plan takes.

func (*PlanExample) DeepCopy

func (in *PlanExample) DeepCopy() *PlanExample

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlanExample.

func (*PlanExample) DeepCopyInto

func (in *PlanExample) DeepCopyInto(out *PlanExample)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type PlanExamplePhase

type PlanExamplePhase struct {
	// ID is the phase's identifier, as update_plan takes it.
	// +kubebuilder:validation:MinLength=1
	// +kubebuilder:validation:MaxLength=60
	ID string `json:"id"`

	// Label is the human phrase for the phase.
	// +optional
	// +kubebuilder:validation:MaxLength=120
	Label string `json:"label,omitempty"`

	// Permissions are the wire-format handles this phase declares
	// ("perm:<permission>:<resourceType>" or "tool:<name>"). Each is checked
	// against the class's surface at admission.
	// +optional
	// +listType=atomic
	// +kubebuilder:validation:MaxItems=12
	Permissions []string `json:"permissions,omitempty"`

	// Slots are the resource TYPES this phase names an instance of. Types only:
	// an authored example must not carry a concrete instance id, because a
	// planner that copies one would address an object belonging to whoever the
	// example was written about.
	// +optional
	// +listType=atomic
	// +kubebuilder:validation:MaxItems=6
	Slots []string `json:"slots,omitempty"`
}

PlanExamplePhase is one phase of an authored example.

func (*PlanExamplePhase) DeepCopy

func (in *PlanExamplePhase) DeepCopy() *PlanExamplePhase

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlanExamplePhase.

func (*PlanExamplePhase) DeepCopyInto

func (in *PlanExamplePhase) DeepCopyInto(out *PlanExamplePhase)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type PlanGateConfig

type PlanGateConfig struct {
	// Mode selects how much of the gate runs.
	//
	//   disabled  — off entirely; no prompt change, no plan schema surfaced.
	//   logging   — the gate runs in full: enumeration, ceiling computation,
	//               approver resolution, severity, card rendering and the
	//               append-only write. What it holds back is CEILINGS and
	//               APPROVALS — the card is logged instead of published, so
	//               nothing is approved and no grant is written, and a call that
	//               overruns a declared phase is RECORDED rather than refused.
	//
	//               A call made with NO plan at all is still refused (see
	//               RequirePlan). That is not an exception to the mode so much as
	//               its precondition: without a declared plan there are no
	//               ceilings to record and the mode measures nothing.
	//   enforcing — the card is published and the call blocks on the outcome.
	//
	// +kubebuilder:validation:Enum=disabled;logging;enforcing
	// +kubebuilder:default=disabled
	// +optional
	Mode string `json:"mode,omitempty"`

	// RequirePlan denies every permissioned call made before a plan is declared.
	// Without it, an agent that simply never calls update_plan falls through the
	// implicit-phase rule and is unconstrained — i.e. "don't plan" is a total
	// bypass.
	//
	// It bites in LOGGING mode too, which is the one place this differs from
	// everything else the gate does. "You must declare a plan" and "you must stay
	// within its ceiling" are separable claims; only the second is what logging
	// mode holds back. Welding them together made logging unable to produce the
	// dataset it exists for — agents told they MUST plan simply did not, because
	// in logging nothing depended on it, and no wording fixes a consequence the
	// code does not implement.
	//
	// So: with RequirePlan, a session under logging still refuses calls made with
	// no plan at all, and still records-but-permits calls that overrun a declared
	// ceiling. That yields real phases, ceilings, severity and approver data
	// without switching on the enforcement the data is meant to justify.
	// UNSET derives from Mode: required whenever the gate runs at all. Turning
	// the gate on IS the intent to make agents plan, and leaving this an
	// independent opt-in defaulting false meant a cluster could run
	// `mode: logging`, look gated, and collect nothing — which is what happened
	// across five live sessions, every call landing on the implicit phase
	// because nothing required a plan.
	//
	// Explicit `false` remains an opt-out, because gate-on-but-observe-only is a
	// real rollout stage: it records which handles calls actually resolve to
	// without changing what the agent may do. Explicit `true` under
	// `mode: disabled` is still false — a gate that does not run cannot refuse
	// anything, and honouring it would promise a denial that never arrives.
	//
	// Pointer rather than a kubebuilder default: a static default cannot express
	// "depends on another field", and it would erase the difference between
	// unset and a deliberate false.
	//
	// UPGRADE NOTE. This field previously carried `+kubebuilder:default=false`,
	// so every AgentClass created under the older CRD has an explicit `false`
	// PERSISTED — which now reads as the observe-only opt-out rather than as
	// unset. Such a class keeps the old behaviour after an upgrade and will not
	// require a plan. Re-applying the class (or deleting the field) is what
	// restores derivation; there is no way to distinguish a stamped default from
	// a deliberate choice after the fact.
	// +optional
	RequirePlan *bool `json:"requirePlan,omitempty"`

	// Limits bound plan size. The defaults are deliberately expansive: they
	// exist to stop a runaway or hostile plan from DoS-ing the approver and
	// producing an unrenderable card, NOT to shape normal authoring.
	// +optional
	Limits *PlanLimits `json:"limits,omitempty"`

	// Rendering bounds what a single approval card shows or auto-approves.
	// +optional
	Rendering *PlanRendering `json:"rendering,omitempty"`

	// Examples are worked plans this class's author supplies, rendered after the
	// one the runtime derives from the session's own permission surface.
	//
	// Optional, and most classes want none: the derived example already speaks
	// the agent's own handles and shows the shape that costs one approval. These
	// are for a class whose GOOD plan is not obvious from its surface alone —
	// where the order of phases matters, or where two resource types must be
	// named together, or where the natural split is by audience rather than by
	// read-then-write.
	//
	// They are TRUSTED text, at the same level as spec.systemPrompt: an operator
	// authors them, never the agent. But unlike the system prompt they are
	// checked — every handle an example names must exist on this class's own
	// surface, or the class goes Valid=False naming the offender. The prompt
	// tells agents that a handle off the declarable list is silently dropped,
	// and an EXAMPLE carrying such a handle would teach exactly that failure to
	// every plan the agent writes. Measured, planners transcribe these examples
	// closely, so a wrong one is not inert.
	// +optional
	// +listType=atomic
	// +kubebuilder:validation:MaxItems=5
	Examples []PlanExample `json:"examples,omitempty"`
}

PlanGateConfig configures the plan gate. Modes follow the existing informationLeakage.mode / toolCalls.mode convention.

func (*PlanGateConfig) DeepCopy

func (in *PlanGateConfig) DeepCopy() *PlanGateConfig

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlanGateConfig.

func (*PlanGateConfig) DeepCopyInto

func (in *PlanGateConfig) DeepCopyInto(out *PlanGateConfig)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type PlanLimits

type PlanLimits struct {
	// +kubebuilder:default=50
	// +optional
	MaxPhases int32 `json:"maxPhases,omitempty"`

	// MaxPermissionsPerPhase is naturally bounded by the permission surface (a
	// phase cannot declare a handle that does not exist), so this is a backstop
	// rather than a real constraint.
	// +kubebuilder:default=64
	// +optional
	MaxPermissionsPerPhase int32 `json:"maxPermissionsPerPhase,omitempty"`

	// MaxSlotsPerPhase bounds the instance axis, which is genuinely unbounded
	// (a thread can carry many URLs), hence the larger default.
	// +kubebuilder:default=128
	// +optional
	MaxSlotsPerPhase int32 `json:"maxSlotsPerPhase,omitempty"`
}

PlanLimits bound plan size. Exceeding one is a tool error naming the limit — the agent re-plans smaller — never a silent truncation, which would drop declared reach while leaving the plan looking complete.

func (*PlanLimits) DeepCopy

func (in *PlanLimits) DeepCopy() *PlanLimits

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlanLimits.

func (*PlanLimits) DeepCopyInto

func (in *PlanLimits) DeepCopyInto(out *PlanLimits)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type PlanRendering

type PlanRendering struct {
	// MaxNamedApprovers is the largest approver population rendered by name
	// before falling back to a count or a channel-supplied description.
	// +kubebuilder:default=5
	// +optional
	MaxNamedApprovers int32 `json:"maxNamedApprovers,omitempty"`

	// MaxSingleCardHandles bounds a phase ceiling shown on one card; a phase
	// exceeding it is split rather than folded behind "+N more", so the
	// approver sees what they approve.
	// +kubebuilder:default=16
	// +optional
	MaxSingleCardHandles int32 `json:"maxSingleCardHandles,omitempty"`

	// MaxAutoApproveHandles bounds the UNION of all handles auto-approved
	// without a human across a session — the tier-0 budget.
	// +kubebuilder:default=8
	// +optional
	MaxAutoApproveHandles int32 `json:"maxAutoApproveHandles,omitempty"`
}

PlanRendering bounds presentation and auto-approval — never the ceiling itself. Exceeding MaxSingleCardHandles splits a card; it never drops a handle. Severity overrides these in one direction only: an Elevated or Severe card renders every handle regardless, because the fold is exactly wrong on the cards that matter most.

func (*PlanRendering) DeepCopy

func (in *PlanRendering) DeepCopy() *PlanRendering

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlanRendering.

func (*PlanRendering) DeepCopyInto

func (in *PlanRendering) DeepCopyInto(out *PlanRendering)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type PreDispatchSnapshot

type PreDispatchSnapshot struct {
	// SessionUID is the parent AgentSession's UID. Snapshot handles
	// are addressed under this UID.
	SessionUID string `json:"sessionUID"`

	// TurnIndex is the runner's current turn.
	TurnIndex int32 `json:"turnIndex"`

	// Sequence is the within-turn ordering: 0 for the first stateful
	// dispatch in the turn, 1 for the second, etc.
	Sequence int32 `json:"sequence"`
}

PreDispatchSnapshot is the request the runner stamps onto a ToolCall whose tool has stateImpact ∈ {readwrite, external}. The toolcall controller honors it by running Snapshotter.Snapshot against the bundle session's workspace PVC at this handle, then dispatching the tool as usual. If the snapshot Job fails, the ToolCall's status captures the failure and the AgentSession's WorkspaceSnapshotFailed condition is set.

func (*PreDispatchSnapshot) DeepCopy

func (in *PreDispatchSnapshot) DeepCopy() *PreDispatchSnapshot

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PreDispatchSnapshot.

func (*PreDispatchSnapshot) DeepCopyInto

func (in *PreDispatchSnapshot) DeepCopyInto(out *PreDispatchSnapshot)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type PreferenceEnumValue

type PreferenceEnumValue struct {
	// Value is the stored token; unique within the enum.
	// +kubebuilder:validation:MinLength=1
	Value string `json:"value"`

	// Description is user-facing copy for this choice, surfaced by the
	// preferences tools and any schema-driven form.
	// +optional
	Description string `json:"description,omitempty"`
}

PreferenceEnumValue is one permitted value of an enum-typed preference.

func (*PreferenceEnumValue) DeepCopy

func (in *PreferenceEnumValue) DeepCopy() *PreferenceEnumValue

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PreferenceEnumValue.

func (*PreferenceEnumValue) DeepCopyInto

func (in *PreferenceEnumValue) DeepCopyInto(out *PreferenceEnumValue)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type PreferenceGlobal

type PreferenceGlobal struct {
	// Value is the admin-set value; validated against the class's
	// userPreferences schema by the settings controller (status violation on
	// mismatch) and ignored WITH a snapshot violation at resolve time if it
	// no longer type-checks.
	// +kubebuilder:pruning:PreserveUnknownFields
	Value apiextv1.JSON `json:"value"`

	// Lock mandates the value: the user layer is ignored for this key and
	// set_preference refuses it, naming the admin policy.
	// +optional
	Lock bool `json:"lock,omitempty"`
}

PreferenceGlobal is one platform-admin value for a class preference key.

func (*PreferenceGlobal) DeepCopy

func (in *PreferenceGlobal) DeepCopy() *PreferenceGlobal

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PreferenceGlobal.

func (*PreferenceGlobal) DeepCopyInto

func (in *PreferenceGlobal) DeepCopyInto(out *PreferenceGlobal)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type PrivateVolume

type PrivateVolume struct {
	// Name is the volume name (also the pod volume Name). Must be a
	// DNS-1123 label. Rejected at class validation if it collides with a
	// pod-builder-reserved volume name (work, tmp, workspace), a ConfigMap
	// mount name from spec.mounts, or another PrivateVolume.
	Name string `json:"name"`

	// MountPath is the absolute path inside the spicebox container where
	// the volume is mounted. Must start with "/". Rejected at class
	// validation if it collides with a pod-builder-reserved mount path
	// (/work, /tmp, /workspace) or another PrivateVolume.
	MountPath string `json:"mountPath"`
}

PrivateVolume describes a per-bundle emptyDir mount.

func (*PrivateVolume) DeepCopy

func (in *PrivateVolume) DeepCopy() *PrivateVolume

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrivateVolume.

func (*PrivateVolume) DeepCopyInto

func (in *PrivateVolume) DeepCopyInto(out *PrivateVolume)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type PromptSource

type PromptSource struct {
	// +optional
	Inline string `json:"inline,omitempty"`
	// +optional
	ConfigMapRef *ConfigMapKeyRef `json:"configMapRef,omitempty"`
}

PromptSource carries exactly one of inline or configMapRef.

func (*PromptSource) DeepCopy

func (in *PromptSource) DeepCopy() *PromptSource

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PromptSource.

func (*PromptSource) DeepCopyInto

func (in *PromptSource) DeepCopyInto(out *PromptSource)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type PublicEndpoint

type PublicEndpoint struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`
	Spec              PublicEndpointSpec   `json:"spec,omitempty"`
	Status            PublicEndpointStatus `json:"status,omitempty"`
}

+kubebuilder:object:root=true +kubebuilder:subresource:status +kubebuilder:resource:scope=Cluster,categories={authzed,spicebox},shortName=pubep +kubebuilder:printcolumn:name="URL",type=string,JSONPath=`.status.url` +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase` +genclient +genclient:nonNamespaced

PublicEndpoint is how this cluster is reached from the public Internet.

Cluster-scoped because it describes the cluster's own front door, not any one namespace's. status.url is the single source of truth for where this cluster is reachable; everything that needs a public address reads it, directly or through the ConfigMap the controller derives from it.

func (*PublicEndpoint) DeepCopy

func (in *PublicEndpoint) DeepCopy() *PublicEndpoint

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PublicEndpoint.

func (*PublicEndpoint) DeepCopyInto

func (in *PublicEndpoint) DeepCopyInto(out *PublicEndpoint)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*PublicEndpoint) DeepCopyObject

func (in *PublicEndpoint) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type PublicEndpointList

type PublicEndpointList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []PublicEndpoint `json:"items"`
}

PublicEndpointList contains a list of PublicEndpoint.

func (*PublicEndpointList) DeepCopy

func (in *PublicEndpointList) DeepCopy() *PublicEndpointList

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PublicEndpointList.

func (*PublicEndpointList) DeepCopyInto

func (in *PublicEndpointList) DeepCopyInto(out *PublicEndpointList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*PublicEndpointList) DeepCopyObject

func (in *PublicEndpointList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type PublicEndpointSpec

type PublicEndpointSpec struct {
	// Target is the in-cluster Service the tunnel forwards to.
	Target PublicEndpointTarget `json:"target"`
	// Provider names a registered tunnel provider: "ngrok" today. Dispatched
	// through pkg/web/localtunnel/registry; a new provider is a registration,
	// never a branch in a consumer.
	// +kubebuilder:validation:MinLength=1
	Provider string `json:"provider"`
	// AuthTokenRef names the Secret key holding the provider's auth token.
	AuthTokenRef ClusterSecretKeyRef `json:"authTokenRef"`
	// LocalURL is where the target is reachable FROM THE HOST while no tunnel
	// is up — the address a port-forward binds, scheme and port included:
	// "http://localhost:8080" for a `kubectl port-forward`-shaped install,
	// "http://127.0.0.1:17080" for the desktop app's own forward.
	//
	// It is set by whoever creates this CR because the cluster cannot know it.
	// The host half and the port half BOTH vary, and they vary independently:
	// `oap install` seeds webd's external URL as "http://localhost:8080", while
	// `oap desktop` binds 127.0.0.1 on a port it picks at runtime by scanning
	// upward from a base. webd dispatches on an EXACT bare-host match, so
	// "localhost" and "127.0.0.1" are different origins to it and guessing
	// either one 404s the browser at the address the user was told to open.
	//
	// REQUIRED, and deliberately not defaulted: a default is what produced the
	// bug this field exists to close — a plausible-looking address written by
	// something that could not know the real one. The controller ALSO refuses
	// an empty value at runtime, for a CR created against an earlier version of
	// this CRD where the apiserver did not yet enforce it.
	// +kubebuilder:validation:MinLength=1
	LocalURL string `json:"localURL"`
	// ReservedDomain pins a stable hostname. Empty means the provider assigns
	// one per session, which changes on every restart — see the repointing
	// the channel controller does in response.
	ReservedDomain string `json:"reservedDomain,omitempty"`
}

func (*PublicEndpointSpec) DeepCopy

func (in *PublicEndpointSpec) DeepCopy() *PublicEndpointSpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PublicEndpointSpec.

func (*PublicEndpointSpec) DeepCopyInto

func (in *PublicEndpointSpec) DeepCopyInto(out *PublicEndpointSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type PublicEndpointStatus

type PublicEndpointStatus struct {
	// URL is the live public address, empty until the tunnel is Ready. It is
	// an OBSERVATION: only this controller writes it, and it is never mirrored
	// into an applied field.
	URL string `json:"url,omitempty"`
	// Phase is Pending, Ready or Failed.
	Phase string `json:"phase,omitempty"`
	// ObservedAt is when the controller last CHANGED url, phase or the Ready
	// condition. It is deliberately NOT a per-reconcile heartbeat: a
	// freshly-stamped timestamp on every reconcile would make every reconcile a
	// status write, churning the object and re-triggering every watcher for no
	// new information.
	ObservedAt *metav1.Time       `json:"observedAt,omitempty"`
	Conditions []metav1.Condition `json:"conditions,omitempty"`
}

func (*PublicEndpointStatus) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PublicEndpointStatus.

func (*PublicEndpointStatus) DeepCopyInto

func (in *PublicEndpointStatus) DeepCopyInto(out *PublicEndpointStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type PublicEndpointTarget

type PublicEndpointTarget struct {
	// Namespace is the Service's namespace.
	Namespace string `json:"namespace"`
	// Service is the Service's name.
	Service string `json:"service"`
	// Port is the Service port to forward to.
	Port int32 `json:"port"`
}

func (*PublicEndpointTarget) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PublicEndpointTarget.

func (*PublicEndpointTarget) DeepCopyInto

func (in *PublicEndpointTarget) DeepCopyInto(out *PublicEndpointTarget)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type RateLimitSpec

type RateLimitSpec struct {
	// MaxCallsPerTurn caps calls within a single agent turn; 0 is unlimited.
	// +kubebuilder:validation:Minimum=1
	// +optional
	MaxCallsPerTurn int32 `json:"maxCallsPerTurn,omitempty"`
	// MaxCalls caps calls in the sliding Window; both must be set together.
	// +kubebuilder:validation:Minimum=1
	// +optional
	MaxCalls int32 `json:"maxCalls,omitempty"`
	// Window is the sliding-window span for MaxCalls; both must be set together.
	// +optional
	Window *metav1.Duration `json:"window,omitempty"`
	// Action when a rate cap is hit: halt ends the session; deny errors the
	// call; warn logs and audits but allows it.
	// +kubebuilder:validation:Enum=halt;deny;warn
	// +optional
	Action string `json:"action,omitempty"`
}

RateLimitSpec caps call volume for matched tools regardless of success.

maxCalls and window are enforced only as a pair (see CallRate), so the apiserver refuses a half-authored one rather than admitting a rule that enforces nothing while reading as if it did. +kubebuilder:validation:XValidation:rule="has(self.maxCalls) == has(self.window)",message="rateLimit.maxCalls and rateLimit.window must be set together"

func (*RateLimitSpec) DeepCopy

func (in *RateLimitSpec) DeepCopy() *RateLimitSpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitSpec.

func (*RateLimitSpec) DeepCopyInto

func (in *RateLimitSpec) DeepCopyInto(out *RateLimitSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type RefusedDataSlot

type RefusedDataSlot struct {
	// Slot is the child-side slot name that stayed empty.
	// +kubebuilder:validation:MinLength=1
	// +kubebuilder:validation:MaxLength=64
	Slot string `json:"slot"`

	// TagID is the tag that was not bound.
	// +kubebuilder:validation:MinLength=1
	// +kubebuilder:validation:MaxLength=256
	TagID string `json:"tagID"`

	// Reason is why. Machine-readable so a parent can branch on it rather
	// than parse prose.
	Reason DataSlotRefusalReason `json:"reason"`
}

RefusedDataSlot is one slot the parent asked for and did not get.

func (*RefusedDataSlot) DeepCopy

func (in *RefusedDataSlot) DeepCopy() *RefusedDataSlot

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RefusedDataSlot.

func (*RefusedDataSlot) DeepCopyInto

func (in *RefusedDataSlot) DeepCopyInto(out *RefusedDataSlot)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type RelationshipSource

type RelationshipSource struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   RelationshipSourceSpec   `json:"spec,omitempty"`
	Status RelationshipSourceStatus `json:"status,omitempty"`
}

+kubebuilder:object:root=true +kubebuilder:resource:scope=Namespaced,categories={authzed,spicebox},shortName=relsrc +kubebuilder:subresource:status +kubebuilder:printcolumn:name="Kind",type="string",JSONPath=".spec.kind" +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"

RelationshipSource declares one upstream directory (Slack first) to poll and sync into SpiceDB. The upstream kind is registered (pkg/platform/relsync); each poll pass writes the membership relationships that upstream currently reports and prunes ones it no longer reports.

Namespaced. Reconciled by pkg/controllers/relationshipsource, which resolves spec.auth's credential, enumerates upstream scopes through the named kind, and writes/prunes relationships in SpiceDB one pass at a time. status.sync tracks progress within and across passes so a large directory is synced incrementally rather than in one reconcile.

func (*RelationshipSource) DeepCopy

func (in *RelationshipSource) DeepCopy() *RelationshipSource

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RelationshipSource.

func (*RelationshipSource) DeepCopyInto

func (in *RelationshipSource) DeepCopyInto(out *RelationshipSource)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*RelationshipSource) DeepCopyObject

func (in *RelationshipSource) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type RelationshipSourceAuth

type RelationshipSourceAuth struct {
	// AgentIdentity is the AgentIdentity CR (same namespace) holding the credential.
	AgentIdentity string `json:"agentIdentity"`
	// Credential is the AgentCredential name on that identity (e.g. a slack bot token).
	Credential string `json:"credential"`
}

RelationshipSourceAuth references a credential on an AgentIdentity used to read the upstream directory.

func (*RelationshipSourceAuth) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RelationshipSourceAuth.

func (*RelationshipSourceAuth) DeepCopyInto

func (in *RelationshipSourceAuth) DeepCopyInto(out *RelationshipSourceAuth)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type RelationshipSourceList

type RelationshipSourceList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []RelationshipSource `json:"items"`
}

+kubebuilder:object:root=true

func (*RelationshipSourceList) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RelationshipSourceList.

func (*RelationshipSourceList) DeepCopyInto

func (in *RelationshipSourceList) DeepCopyInto(out *RelationshipSourceList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*RelationshipSourceList) DeepCopyObject

func (in *RelationshipSourceList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type RelationshipSourcePassStats

type RelationshipSourcePassStats struct {
	// ScopesProcessed is how many scopes the pass actually visited.
	// +optional
	ScopesProcessed int32 `json:"scopesProcessed,omitempty"`
	// Written is relationship tuples touched as additions.
	// +optional
	Written int32 `json:"written,omitempty"`
	// Pruned is tuples deleted by the per-scope diff or the cross-resource reap.
	// +optional
	Pruned int32 `json:"pruned,omitempty"`
	// ReapedScopes is scopes whose whole resource object was swept.
	// +optional
	ReapedScopes int32 `json:"reapedScopes,omitempty"`
	// JoinMisses is upstream members whose identity resolved to no platform
	// user, dropped rather than written. A nonzero, growing value is the
	// signature of a broken identity join.
	// +optional
	JoinMisses int32 `json:"joinMisses,omitempty"`
	// ScopeErrors is how many scopes failed this pass. Non-fatal by design
	// (relsync.Pass's own doc: "one scope failing is not a pass failure"), so
	// Ready stays True — this count, and
	// RelationshipSourceConditionPartialFailure alongside it, are the only
	// things that distinguish a partially-failed pass from a clean one.
	//
	// A count, never a length: ScopeErrorSamples below is capped, so
	// len(samples) says how many failures were SAMPLED, not how many there
	// were. 156 failing scopes report 156 here and a handful there.
	// +optional
	ScopeErrors int32 `json:"scopeErrors,omitempty"`
	// ScopeErrorSamples is a small, capped sample of this pass's scope
	// errors, sorted deterministically by (scope, message) and truncated to
	// the first few. Deliberately NOT the whole list: a status subresource
	// must not grow with the size of a failing directory, and an entire arm
	// of a sync failing is exactly when the list would be longest.
	//
	// Each message is scrubbed before it lands here — a URL's query string or
	// fragment is stripped (a scope error quotes an upstream request URL, and
	// both halves are where a credential would ride) and the text is bounded.
	// The sample set is also preserved verbatim across passes whose failure
	// count and sampled scopes are unchanged: error text that varies run to
	// run would otherwise make every reconcile a status write, and the
	// controller's self-watch has no predicate. The live example is a rate
	// limit, whose backoff the kind computes fresh from one upstream reset
	// timestamp on every pass — see the relationshipsource controller's
	// preserveStableSamples for that and for the two trades it accepts, and
	// FinishedAt's own note above for the same hazard in its first form.
	// +optional
	// +kubebuilder:validation:MaxItems=5
	ScopeErrorSamples []RelationshipSourceScopeError `json:"scopeErrorSamples,omitempty"`
	// Scoped reports that this pass processed an explicit scope subset.
	// +optional
	Scoped bool `json:"scoped,omitempty"`
	// FinishedAt is when the recorded pass completed.
	// +optional
	FinishedAt *metav1.Time `json:"finishedAt,omitempty"`
}

RelationshipSourcePassStats summarizes what ONE sync pass did. A pass is not always a whole cycle: with spec.sync.maxScopesPerPass set a cycle is several passes, and an event-driven wake processes a single scope. Scoped records that, so a one-scope wake cannot be misread as a full-directory result.

Controller-owned observation. FinishedAt is stamped ONLY when the status is already being written for another reason — see applySyncResult, and the identical treatment CycleStartedAt/LastSyncTime get there. Restamping it on every pass would make every reconcile a status write, and the controller's self-watch has no predicate, so that is an infinite reconcile loop that re-runs the upstream Pass forever.

func (*RelationshipSourcePassStats) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RelationshipSourcePassStats.

func (*RelationshipSourcePassStats) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type RelationshipSourceScopeError

type RelationshipSourceScopeError struct {
	// Scope is the upstream scope id that failed. EMPTY when the failure is
	// not attributable to a single scope — enumeration itself failing, or the
	// reap scan (see relsync.ScopeError's own doc).
	// +optional
	// +kubebuilder:validation:MaxLength=256
	Scope string `json:"scope,omitempty"`
	// Message is the failure text, scrubbed and truncated. Never raw error
	// text: see ScopeErrorSamples above for what is removed and why.
	// +optional
	// +kubebuilder:validation:MaxLength=512
	Message string `json:"message,omitempty"`
}

RelationshipSourceScopeError is one sampled per-scope failure from a sync pass. Controller-owned observation.

func (*RelationshipSourceScopeError) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RelationshipSourceScopeError.

func (*RelationshipSourceScopeError) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type RelationshipSourceSpec

type RelationshipSourceSpec struct {
	// Kind selects the registered relsync kind (e.g. "slack").
	// +kubebuilder:validation:MinLength=1
	Kind string `json:"kind"`

	// Auth references the credential used to read the upstream.
	Auth RelationshipSourceAuth `json:"auth"`

	// Sync controls re-poll cadence.
	// +optional
	Sync RelationshipSourceSync `json:"sync,omitempty"`

	// BaseURL is the upstream endpoint for kinds whose service is
	// customer-hosted — the 1Password SCIM Bridge, for instance, which has no
	// constant address. Kinds that talk to a fixed vendor endpoint (Slack)
	// ignore it.
	//
	// Scheme and destination are deliberately unconstrained in the SCHEMA — no
	// kubebuilder Pattern, no host allowlist here. In-cluster (a Kubernetes
	// Service DNS name over http://, e.g.
	// http://scim.1password.svc.cluster.local) and loopback (a test/dev bridge
	// on http://127.0.0.1:<port>) are legitimate destinations — in fact the
	// primary deployment shape for a customer-hosted service — mirroring
	// MCPServerServer.URL's own doc (pkg/apis/v1alpha1/mcpserver_types.go).
	// For the same reason a kind dereferencing this field must NOT wrap its
	// client in pkg/x/safehttp: that guarded dialer refuses every
	// private/loopback IP, which is exactly what this field exists to carry.
	//
	// It is nonetheless a TENANT-WRITABLE DESTINATION, and the credential sent
	// to it must be scoped to it. This field and spec.auth sit on the same CR,
	// and anyone who can write the CR chooses both; the operator then reads the
	// named Secret with its OWN cluster-wide credentials and sends the value
	// here as a bearer token. An unconstrained pairing is credential
	// exfiltration with no `get secrets` needed — the SkillSource incident
	// (pkg/platform/identity/credhost's package doc) on an identical CR shape.
	// So when this field is set, the RelationshipSource controller requires the
	// referenced credential to declare spec.credentials[].allowedHosts and to
	// admit this host; it refuses the sync otherwise, on the Ready condition.
	// An empty value is checked against nothing: a kind with a constant vendor
	// endpoint has no tenant-writable destination to guard.
	//
	// Required by any kind that declares it needs one; such a kind refuses to
	// run rather than guessing when it is empty.
	// +optional
	// +kubebuilder:validation:MaxLength=2048
	BaseURL string `json:"baseURL,omitempty"`

	// Config is this kind's own configuration, opaque to the CRD: the
	// registered kind parses and validates it, so adding a kind never adds a
	// field here. Shape and required keys are the kind's to document (see
	// docs/relationshipsource.md).
	// +optional
	// +kubebuilder:pruning:PreserveUnknownFields
	Config *apiextensionsv1.JSON `json:"config,omitempty"`
}

RelationshipSourceSpec declares one upstream directory to sync.

func (*RelationshipSourceSpec) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RelationshipSourceSpec.

func (*RelationshipSourceSpec) DeepCopyInto

func (in *RelationshipSourceSpec) DeepCopyInto(out *RelationshipSourceSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type RelationshipSourceStatus

type RelationshipSourceStatus struct {
	// ObservedGeneration is the spec generation this status reflects.
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`

	// Sync is the resumption marker for the in-progress or most recently
	// completed enumeration cycle.
	// +optional
	Sync RelationshipSourceSyncStatus `json:"sync,omitempty"`

	// +patchStrategy=merge
	// +patchMergeKey=type
	// +optional
	Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}

RelationshipSourceStatus is the observed state. Controller-owned.

func (*RelationshipSourceStatus) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RelationshipSourceStatus.

func (*RelationshipSourceStatus) DeepCopyInto

func (in *RelationshipSourceStatus) DeepCopyInto(out *RelationshipSourceStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type RelationshipSourceSync

type RelationshipSourceSync struct {
	// Interval between full passes. Zero → controller default (15m).
	// +optional
	Interval metav1.Duration `json:"interval,omitempty"`

	// MaxScopesPerPass bounds how many scopes one reconcile processes, so a
	// single enormous source cannot starve every other RelationshipSource
	// the operator manages. Zero → UNBOUNDED (the controller passes this
	// value straight through to relsync.Pass with no substituted default),
	// which is also why unbounded is the right choice unless this source's
	// scope count genuinely needs the budget: setting ANY bound below the
	// source's total scope count defers cross-resource reaping (an
	// identity/workspace-membership edge no scope still asserts) — that
	// reap only ever runs on a pass whose fetch coverage reaches every
	// enumerated scope, which a persistent bound may prevent from ever
	// happening.
	// +optional
	// +kubebuilder:validation:Minimum=1
	MaxScopesPerPass int32 `json:"maxScopesPerPass,omitempty"`
}

RelationshipSourceSync controls cadence and per-pass cost.

func (*RelationshipSourceSync) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RelationshipSourceSync.

func (*RelationshipSourceSync) DeepCopyInto

func (in *RelationshipSourceSync) DeepCopyInto(out *RelationshipSourceSync)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type RelationshipSourceSyncStatus

type RelationshipSourceSyncStatus struct {
	// ResumeAfter is the ScopeID of the last scope completed this cycle.
	// Empty means the cycle starts from the beginning. The next pass takes
	// the first scope sorting AFTER this one — a comparison, not a lookup,
	// so a scope deleted between passes does not strand the cursor.
	//
	// This is deliberately a ScopeID and never an upstream pagination
	// token: upstream cursors expire, ours does not.
	// +optional
	// +kubebuilder:validation:MaxLength=256
	ResumeAfter string `json:"resumeAfter,omitempty"`

	// EnumComplete reports whether scope enumeration finished this cycle.
	// Orphan reaping is gated on it: a truncated enumeration is non-empty
	// and well-formed and short, and reaping against it would delete every
	// scope it never reached.
	// +optional
	EnumComplete bool `json:"enumComplete,omitempty"`

	// CycleStartedAt is when the current cycle began.
	// +optional
	CycleStartedAt *metav1.Time `json:"cycleStartedAt,omitempty"`

	// LastSyncTime is the last completed full cycle.
	// +optional
	LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"`

	// LastPass summarizes the most recently recorded pass. Only the most
	// recent is kept: pass history belongs in the audit plane, not in a CR
	// status that would grow without bound.
	// +optional
	LastPass *RelationshipSourcePassStats `json:"lastPass,omitempty"`
}

RelationshipSourceSyncStatus is the resumption marker. Controller-owned.

func (*RelationshipSourceSyncStatus) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RelationshipSourceSyncStatus.

func (*RelationshipSourceSyncStatus) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ResolvedBundle

type ResolvedBundle struct {
	// Name is the bundle name from AgentClass.spec.toolBundles.
	Name string `json:"name"`
	// SpiceboxSessionName is the sandbox session provisioned for the bundle.
	SpiceboxSessionName string `json:"spiceboxSessionName"`
	// AgentIdentity is the identity resolved for this bundle's sandbox.
	AgentIdentity string `json:"agentIdentity"`
	// Restarts counts how many times the operator deleted + recreated this
	// bundle's SpiceboxSession after it reported Failed. One retry is allowed
	// per session; a second failure is terminal (BundleFailed).
	// +optional
	Restarts int32 `json:"restarts,omitempty"`
	// RetriedSessionUID is the UID of the failed SpiceboxSession instance
	// the retry deleted. The terminal check ignores Failed observations
	// carrying this UID: they are stale cache reads of the deleted
	// instance, not the replacement failing.
	// +optional
	RetriedSessionUID string `json:"retriedSessionUID,omitempty"`
}

ResolvedBundle records the SpiceboxSession the operator provisioned for one of the class's tool bundles.

func (*ResolvedBundle) DeepCopy

func (in *ResolvedBundle) DeepCopy() *ResolvedBundle

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResolvedBundle.

func (*ResolvedBundle) DeepCopyInto

func (in *ResolvedBundle) DeepCopyInto(out *ResolvedBundle)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ResolvedContentGuardDetector

type ResolvedContentGuardDetector struct {
	// Inspector is the content-guard inspector this detector serves.
	Inspector string `json:"inspector"`
	// Image is the detector container image.
	Image string `json:"image"`
	// Port is the detector's HTTP port on its pod.
	Port int32 `json:"port"`
	// HealthPath is the readiness path; empty means the pod has no probe path.
	HealthPath string `json:"healthPath,omitempty"`
	// PodName is the detector pod's name.
	PodName string `json:"podName,omitempty"`
	// PodIP is the detector pod's IP, reflected once Ready. Empty ⇒ not ready;
	// the operator requeues and holds runner-pod creation.
	PodIP string `json:"podIP,omitempty"`
}

ResolvedContentGuardDetector snapshots one content-guard detector that the operator runs as a SEPARATE per-session pod (own NetworkPolicy, egress denied) for the inspector that needs it. The runner reaches it at http://<PodIP>:<Port> (set into the runner's CONTENTGUARD_DETECTOR_ENDPOINT once the detector pod is Ready). Runner-pod creation is gated on PodIP being reflected here — mirrors separate-pod SidecarToolbox.

func (*ResolvedContentGuardDetector) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResolvedContentGuardDetector.

func (*ResolvedContentGuardDetector) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ResolvedCredentialRef

type ResolvedCredentialRef struct {
	// IdentityKind is the identity CR's Kind spelling: "AgentIdentity",
	// "UserIdentity", or "SessionUserIdentity". SessionUserIdentity is the
	// per-session projection of a UserIdentity — both are user-owned
	// (passthrough) credentials, as distinct from an AgentIdentity's own.
	IdentityKind string `json:"identityKind"`
	// Namespace is the identity CR's namespace; empty for cluster-scoped kinds.
	Namespace string `json:"namespace"`
	// Name is the identity CR name.
	Name string `json:"name"`
	// Credential is the credential name within that identity.
	Credential string `json:"credential"`
	// ProviderID is the provider catalog id, recorded so the card can render a
	// title and icon without re-resolving.
	// +optional
	ProviderID string `json:"providerID,omitempty"`
}

ResolvedCredentialRef identifies the credential an Origin resolved to. It is the budget key: the ask budget counts SPENT asks per (session, resolved credential), NOT per origin, because two origins can share one token and asking twice for it is the same imposition on the human. "Spent" is credentialupdaterequest.askIsSpent -- every ask this reconciler has DETERMINED, which is broader than terminal: a Collapsed ask riding somebody else's live card has already cost a human attention and is charged then, not when it eventually settles.

func (*ResolvedCredentialRef) AgentOwned

func (rc *ResolvedCredentialRef) AgentOwned() bool

AgentOwned reports whether rc names the AGENT's own shared credential rather than one belonging to a person. Nil-safe: unresolved is not agent-owned.

The distinction drives the whole credential-update flow. A user-owned credential has one obvious human behind it, so the card DMs them and the click writes their own master Secret. An AgentIdentity's credential is a shared secret held on the agent's behalf: replacing it changes what every session of that agent authenticates as, the gate is `agentidentity#update_credential`, and the value belongs in the AgentIdentity's own Secret. Routing one as the other overwrites a bystander's personal credential while the shared one stays dead.

func (*ResolvedCredentialRef) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResolvedCredentialRef.

func (*ResolvedCredentialRef) DeepCopyInto

func (in *ResolvedCredentialRef) DeepCopyInto(out *ResolvedCredentialRef)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ResolvedPermissionTitle

type ResolvedPermissionTitle struct {
	// ResourceType is the SpiceDB resource type the permission is declared on.
	ResourceType string `json:"resourceType"`

	// Permission is the permission handle (SpiceDBPermission.Name).
	Permission string `json:"permission"`

	// Title is the phrase a human reads on an approval card, copied from the
	// declaring SpiceDBPermission.Title.
	Title string `json:"title"`

	// PlanningNote is the guidance the agent reads while declaring a plan,
	// copied from the declaring SpiceDBPermission.PlanningNote.
	//
	// It rides this entry rather than a list of its own because the key is
	// identical — (resourceType, permission) — and a second list would mean a
	// second walk of every toolkit and MCPServer producing the same keys, free
	// to drift from this one. The two strings differ in AUDIENCE, not in what
	// identifies them: Title is read by a human deciding, PlanningNote by the
	// agent planning.
	// +optional
	PlanningNote string `json:"planningNote,omitempty"`
}

ResolvedPermissionTitle publishes the human phrase declared for one (resourceType, permission) pair, so the runner can render a card without re-reading every schema fragment. An OBSERVATION: the controller owns it.

func (*ResolvedPermissionTitle) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResolvedPermissionTitle.

func (*ResolvedPermissionTitle) DeepCopyInto

func (in *ResolvedPermissionTitle) DeepCopyInto(out *ResolvedPermissionTitle)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ResolvedResourceDisplay

type ResolvedResourceDisplay struct {
	// ResourceType is the SpiceDB resource type the display is declared on.
	ResourceType string `json:"resourceType"`

	// Name mirrors SpiceDBResourceDisplay.Name.
	Name string `json:"name,omitempty"`

	// Icon mirrors SpiceDBResourceDisplay.Icon.
	Icon string `json:"icon,omitempty"`

	// Label mirrors SpiceDBResourceDisplay.Label.
	Label string `json:"label,omitempty"`
}

ResolvedResourceDisplay publishes the presentation declared for one resource type — SpiceDBResource.Display, flattened with its type name — so the runner can render a resource-instance line without re-reading every schema fragment. An OBSERVATION: the controller owns it.

func (*ResolvedResourceDisplay) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResolvedResourceDisplay.

func (*ResolvedResourceDisplay) DeepCopyInto

func (in *ResolvedResourceDisplay) DeepCopyInto(out *ResolvedResourceDisplay)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ResolvedResourceStanding

type ResolvedResourceStanding struct {
	// ResourceType is the SpiceDB resource type this answer is for.
	ResourceType string `json:"resourceType"`

	// Standing is `required` or `session-only`. Never empty: a type whose
	// fragments declare nothing is refused at resolve time rather than published
	// with a blank answer a reader would have to interpret.
	Standing string `json:"standing"`

	// ApproverPermission is the permission an approver must hold on an instance,
	// set only when Standing is `required`.
	// +optional
	ApproverPermission string `json:"approverPermission,omitempty"`
}

ResolvedResourceStanding is how approval for one resource type is governed, resolved from every schema fragment reachable from this class.

func (*ResolvedResourceStanding) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResolvedResourceStanding.

func (*ResolvedResourceStanding) DeepCopyInto

func (in *ResolvedResourceStanding) DeepCopyInto(out *ResolvedResourceStanding)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ResolvedSidecarToolbox

type ResolvedSidecarToolbox struct {
	// Name is the LLM-facing prefix (the `name` field from
	// AgentClassSidecarToolboxRef).
	Name string `json:"name"`

	// Ref is the SidecarToolbox CR name (the `ref` field from
	// AgentClassSidecarToolboxRef).
	Ref string `json:"ref"`

	// Port is the runner-side loopback port (operator-allocated).
	// Reachable at 127.0.0.1:<Port>.
	Port int32 `json:"port"`

	// Spec is the SidecarToolbox spec snapshot at session-start time.
	Spec SidecarToolboxSpec `json:"spec"`

	// EffectiveNetworkMode is the merged network mode the sidecar runs under:
	// the SpiceboxClass's mode, upgraded to `allowlist` when the class says
	// `none` but the SidecarToolbox adds allowedHosts. For separate-pod
	// sidecars the operator stamps an L3/L4 NetworkPolicy from it (none → full
	// egress deny; allowlist → DNS + coarse TCP 443/80).
	// +optional
	EffectiveNetworkMode NetworkMode `json:"effectiveNetworkMode,omitempty"`

	// EffectiveAllowedHosts is the sorted, deduped union of the SpiceboxClass's
	// network.allowedHosts and the SidecarToolbox's sandbox.network.allowedHosts.
	// RECORDED, not enforced: hostnames are not expressible in stock
	// NetworkPolicy, so this is the source of truth a DNS-aware policy
	// controller consumes to narrow beyond the coarse L3/L4 policy.
	// +optional
	EffectiveAllowedHosts []string `json:"effectiveAllowedHosts,omitempty"`

	// RunMode is "in-pod" (default, container in the agent pod) or
	// "separate-pod" (secret-gated: its own per-session pod).
	// +optional
	RunMode string `json:"runMode,omitempty"`
	// SidecarPodName is the separate sidecar pod's name (RunMode=="separate-pod").
	// +optional
	SidecarPodName string `json:"sidecarPodName,omitempty"`
	// SidecarPodIP is the separate sidecar pod's IP, reflected once Ready; the
	// runner reaches it at http://<SidecarPodIP>:<port>.
	// +optional
	SidecarPodIP string `json:"sidecarPodIP,omitempty"`
	// AwaitingSecret is true when a secret-gated toolbox's bound secret-output
	// is not yet available; the operator holds pod creation until it is.
	// +optional
	AwaitingSecret bool `json:"awaitingSecret,omitempty"`
	// SecretTokenHash is a hash of the resolved secret value; a change triggers
	// pod replacement.
	// +optional
	SecretTokenHash string `json:"secretTokenHash,omitempty"`

	// PodFailure records a TERMINAL container state on a separate sidecar pod
	// (CrashLoopBackOff, ImagePullBackOff) — one the pod will not recover from
	// on its own. Nil while the pod is healthy or still starting; cleared once
	// it goes Ready.
	//
	// OPERATOR-owned, deliberately separate from the runner-owned
	// SidecarReachability: reachability answers "the runner probed a running
	// server and it did/didn't answer", PodFailure answers "the pod never got
	// far enough to be probed." A crash-looping sidecar never receives a PodIP,
	// so the prober never runs and reachability would stay silently empty —
	// exactly the silent degradation this field exists to end.
	// +optional
	PodFailure *SidecarPodFailure `json:"podFailure,omitempty"`
}

ResolvedSidecarToolbox snapshots one SidecarToolbox CR for a running AgentSession. Includes the LLM-facing prefix and the operator-allocated loopback port the runner uses to reach the sidecar.

func (*ResolvedSidecarToolbox) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResolvedSidecarToolbox.

func (*ResolvedSidecarToolbox) DeepCopyInto

func (in *ResolvedSidecarToolbox) DeepCopyInto(out *ResolvedSidecarToolbox)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ResolvedSkillBundle

type ResolvedSkillBundle struct {
	// CanonicalName is the skill's canonical name (the AgentClass opt-in key).
	CanonicalName string `json:"canonicalName"`
	// LocalName is the AgentClass's AgentSkill.Name for this skill -- the
	// name a disk-based consumer (Claude Code) actually discovers, because
	// the sandbox pod builder mounts the staged bundle's CONTENT at
	// /skills/<LocalName>/ (see pkg/controllers/agentsession/bundles.go's
	// BuildBundleSession). It is deliberately NOT the same value as
	// MountName below: LocalName only has to be unique within one
	// AgentClass (enforced by validateSkillsSpec) and match the skill's own
	// SKILL.md frontmatter name (enforced in resolveAndStageSkillBundles),
	// whereas MountName has to be safe and unique as a Kubernetes object
	// name/volume name across every repo and version a cluster ever sees --
	// two different scopes, two different values.
	LocalName string `json:"localName"`
	// MountName is the sanitized, collision-free ConfigMap/volume name the
	// staged bundle's ConfigMap and the pod's internal shared-unpack
	// subPath use. It is NOT the sandbox-visible directory name a disk-based
	// consumer discovers the skill under -- see LocalName for that.
	MountName string `json:"mountName"`
	// ConfigMapName is the per-session ConfigMap holding the bundle tarball.
	ConfigMapName string `json:"configMapName"`
	// Digest is a cheap, no-I/O change-detection fingerprint over this
	// skill's staging inputs (the composed SKILL.md content, plus the
	// underlying Skill's bundle digest when one exists). It lets the
	// operator skip re-reading the bundle store on an unchanged reconcile
	// pass; it is NOT the hash of the staged archive's actual bytes -- see
	// ArchiveDigest for that.
	Digest string `json:"digest"`
	// ArchiveDigest is the sha256 content digest of the actual staged
	// archive bytes (the ConfigMap's bundle.tar.gz key). The sandbox pod's
	// mount-unpack init container verifies content against it before
	// extracting, so a ConfigMap that drifted between staging and use fails
	// the pod rather than silently running altered content.
	// +optional
	ArchiveDigest string `json:"archiveDigest,omitempty"`
}

ResolvedSkillBundle records one skill the operator staged onto disk in the sandbox pod for a session. A skill that is not staged at all (agent- targeted, or not named by any ToolBundle's StageSkills) does not appear here.

func (*ResolvedSkillBundle) DeepCopy

func (in *ResolvedSkillBundle) DeepCopy() *ResolvedSkillBundle

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResolvedSkillBundle.

func (*ResolvedSkillBundle) DeepCopyInto

func (in *ResolvedSkillBundle) DeepCopyInto(out *ResolvedSkillBundle)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ResolvedSlot

type ResolvedSlot struct {
	// ResourceType is the SpiceDB resource type the slot declares.
	ResourceType string `json:"resourceType"`

	// Permission is the permission a grant on this slot confers.
	Permission string `json:"permission"`

	// ValueTransforms is the transform chain a free-form value passes through
	// to become the object id, in order, as declared by the tools that key this
	// type through resourceIDExpr. Empty means this slot is not value-keyed:
	// its ids are already distinct resources and are used as-is.
	// +optional
	// +listType=atomic
	ValueTransforms []string `json:"valueTransforms,omitempty"`

	// Standing is how an approval on this slot gets its authority:
	// `session-only` (the approver's decision is the authority) or `required`
	// (the approver must already hold the permission on the named instance).
	// Resolved from the type's schema fragments and the admin veto — an
	// OBSERVATION, so it lives here rather than on spec.
	// +optional
	Standing string `json:"standing,omitempty"`

	// ApproverPermission is the permission an approver must hold on the named
	// instance for their approval to count, published only when Standing is
	// `required`. Empty for `session-only`, where no local permission is
	// consulted and the session's own approvers decide.
	//
	// Published rather than assumed: the router used to hardcode `#owner`, which
	// made a type whose `owner` is a real computed permission indistinguishable
	// from one whose `owner` is a bare relation nothing ever writes.
	// +optional
	ApproverPermission string `json:"approverPermission,omitempty"`
}

ResolvedSlot is the reconciler's answer to "how do I name an instance of this slot type", published for every writer of a slot grant.

func (*ResolvedSlot) DeepCopy

func (in *ResolvedSlot) DeepCopy() *ResolvedSlot

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResolvedSlot.

func (*ResolvedSlot) DeepCopyInto

func (in *ResolvedSlot) DeepCopyInto(out *ResolvedSlot)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ResolvedWorkspaceSource

type ResolvedWorkspaceSource struct {
	// Ref is the bound WorkspaceSource CR name.
	Ref string `json:"ref"`
	// BaseClaimName is the shared base PVC the session overlay was cut from.
	BaseClaimName string `json:"baseClaimName"`
	// OverlayCut is true once the reflink cut of base -> the session workspace
	// PVC has completed successfully.
	// +optional
	OverlayCut bool `json:"overlayCut,omitempty"`

	// Kind/Locator/Revision snapshot the bound source's driver + locator + git
	// revision at session start, so the runner can build sync/apply commands.
	// +optional
	Kind string `json:"kind,omitempty"`
	// +optional
	Locator string `json:"locator,omitempty"`
	// +optional
	Revision string `json:"revision,omitempty"`

	// ReconcileImage and ReconcileServiceAccount snapshot the git image and
	// ServiceAccount the runner's sync_workspace/apply_workspace Job runs as.
	// The operator resolves them from its own config (--materialize-image,
	// --snapshot-service-account), so a cluster that digest-pins the
	// base-materialize Job gets the same pinned image here without a separate
	// flag. Empty ⇒ the runner falls back to its built-in defaults.
	// +optional
	ReconcileImage string `json:"reconcileImage,omitempty"`
	// +optional
	ReconcileServiceAccount string `json:"reconcileServiceAccount,omitempty"`
}

ResolvedWorkspaceSource snapshots a session's bound WorkspaceSource.

func (*ResolvedWorkspaceSource) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResolvedWorkspaceSource.

func (*ResolvedWorkspaceSource) DeepCopyInto

func (in *ResolvedWorkspaceSource) DeepCopyInto(out *ResolvedWorkspaceSource)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ResultArtifact

type ResultArtifact struct {
	// ID is the handle an earlier tool call returned for the artifact, written
	// by the terminal tool that ended the round.
	//
	// Which FORM of handle depends on who reads it back, and the two terminal
	// tools say so in their own schemas. agent_work_complete's list is an audit
	// note nothing resolves, so any handle the agent holds is legible there.
	// return_result's is not: the SubagentRequest controller copies it to the
	// delegating parent, which attaches it by asking the API server for the
	// ArtifactRender it names — so only the `ar-…` render handle works, and an
	// artifact-store id would fail at the moment the parent tried to deliver
	// it.
	ID string `json:"id"`
	// Description is the agent's one-line account of the artifact.
	Description string `json:"description"`
}

func (*ResultArtifact) DeepCopy

func (in *ResultArtifact) DeepCopy() *ResultArtifact

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResultArtifact.

func (*ResultArtifact) DeepCopyInto

func (in *ResultArtifact) DeepCopyInto(out *ResultArtifact)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type RunnerNote

type RunnerNote struct {
	Time    metav1.Time `json:"time"`
	Message string      `json:"message"`
}

RunnerNote is one timestamped diagnostic the runner recorded for operators.

func (*RunnerNote) DeepCopy

func (in *RunnerNote) DeepCopy() *RunnerNote

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RunnerNote.

func (*RunnerNote) DeepCopyInto

func (in *RunnerNote) DeepCopyInto(out *RunnerNote)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SandboxBackend

type SandboxBackend struct {
	// Kind names a registered sandbox kind. Defaults to "pod". An
	// UNRECOGNIZED kind marks the class Valid=False — lookup never falls back
	// to the built-in backend, because silently installing a different
	// substrate than the one asked for is worse than refusing.
	// +kubebuilder:default=pod
	// +optional
	Kind string `json:"kind,omitempty"`

	// Config is passed through to the backend verbatim. Opaque to AP: each
	// kind parses its own. Unset for the built-in pod backend, which takes all
	// of its configuration from the pod-vocabulary fields above.
	// +optional
	Config *apiextensionsv1.JSON `json:"config,omitempty"`

	// WarmPool requests pre-warmed capacity for this backend. Unset or
	// replicas: 0 means pre-warming is off — it spends real money on idle
	// capacity, so it is opt-in. Only a backend whose Runtime implements the
	// sandboxkinds.Prewarmer interface can honor a non-zero value; asking for
	// it on a backend that cannot is a class validation error, not a silent
	// no-op.
	//
	// PREREQUISITE: pre-warming only actually adopts a pod when the
	// cluster's agent-sandbox install allows the
	// "agentprimitives.authzed.com" label domain (its AllowedLabelDomains
	// defaults to "sandbox.users.io" alone). Without that grant AP cannot
	// label an adopted pod, and — because AP's per-session NetworkPolicy
	// selects on that label — the backend degrades to ordinary cold
	// sandboxes and emits a monitoring warning rather than run one
	// unpoliced.
	// +optional
	WarmPool *WarmPoolConfig `json:"warmPool,omitempty"`
}

SandboxBackend selects and configures a sandbox backend.

func (*SandboxBackend) DeepCopy

func (in *SandboxBackend) DeepCopy() *SandboxBackend

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SandboxBackend.

func (*SandboxBackend) DeepCopyInto

func (in *SandboxBackend) DeepCopyInto(out *SandboxBackend)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (SandboxBackend) ResolvedKind

func (b SandboxBackend) ResolvedKind() string

ResolvedKind returns the backend name to look up: the explicit kind, or the documented default when unset.

The kubebuilder default fills this in at the API server, so an empty value should never reach a controller in production. Resolving it here anyway keeps the meaning of "unset" in one place rather than depending on the served schema being current, and makes the path reachable from a unit test.

type SandboxHandle

type SandboxHandle struct {
	// Kind is the sandbox kind that owns Ref.
	Kind string `json:"kind"`
	// Ref is the backend's own identifier for this sandbox. The pod backend
	// uses "<namespace>/<podName>"; another backend may use a bare ID.
	Ref string `json:"ref"`
	// Prewarmed reports whether this sandbox was adopted from a pool of
	// already-running capacity rather than created on demand.
	//
	// LOAD-BEARING, not decoration. A pre-warming backend may resolve Ref
	// differently for an adopted sandbox than for a cold one, and every verb
	// (status, teardown, exec) resolves from the stored handle alone. It is
	// persisted here, rather than recomputed, precisely so it survives an
	// operator restart: recomputing it would ask a question whose inputs no
	// longer exist once the sandbox is running.
	// +optional
	Prewarmed bool `json:"prewarmed,omitempty"`
}

SandboxHandle is the CRD-serializable form of sandboxkinds.Handle: an opaque reference only the owning kind interprets.

func (*SandboxHandle) DeepCopy

func (in *SandboxHandle) DeepCopy() *SandboxHandle

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SandboxHandle.

func (*SandboxHandle) DeepCopyInto

func (in *SandboxHandle) DeepCopyInto(out *SandboxHandle)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SanitizerWarning

type SanitizerWarning struct {
	// kind is "tag", "attr", or "css".
	Kind string `json:"kind"`
	// name is the element, attribute, or CSS construct affected.
	Name string `json:"name"`
	// action is "unwrapped", "removed", "stripped", or "kept".
	Action string `json:"action"`
	// count is the number of occurrences.
	Count int `json:"count"`
	// note is an optional actionable hint.
	// +optional
	Note string `json:"note,omitempty"`
}

SanitizerWarning is a structured finding from the artifact renderer's sanitizer, surfaced to the agent so it can adjust the source. Mirrors channelassets.Warning (the apis package cannot import channelassets).

func (*SanitizerWarning) DeepCopy

func (in *SanitizerWarning) DeepCopy() *SanitizerWarning

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SanitizerWarning.

func (*SanitizerWarning) DeepCopyInto

func (in *SanitizerWarning) DeepCopyInto(out *SanitizerWarning)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ScopeSpec

type ScopeSpec struct {
	// Enabled turns the feature on for sessions of this class.
	// +kubebuilder:default=false
	// +optional
	Enabled bool `json:"enabled,omitempty"`

	// ColdStart governs how a genuinely-new session's first user message is
	// handled:
	//   extractAndApprove   — extract {scope, cleanedTask}; post the approval
	//                         block; agent blocks until the approver resolves.
	//   extractAndAutoApply — extract; apply scope; run the cleaned task; no
	//                         approval click.
	//   off                 — no cold-start extraction; run the raw first turn
	//                         unmodified (mid-session @metaagent still works).
	// Ignored when Enabled=false.
	// +kubebuilder:validation:Enum=extractAndApprove;extractAndAutoApply;off
	// +kubebuilder:default=extractAndApprove
	// +optional
	ColdStart string `json:"coldStart,omitempty"`

	// MaxLLMLatencyMs caps the combined extractor+composer LLM latency
	// before authzd gives up and posts a CannotAddressMessage.
	// +kubebuilder:default=5000
	// +optional
	MaxLLMLatencyMs int32 `json:"maxLlmLatencyMs,omitempty"`

	// ComposerModelOverride and ExtractorModelOverride allow per-class
	// LLM model selection; empty falls back to the authzd default.
	// +optional
	ComposerModelOverride string `json:"composerModelOverride,omitempty"`
	// +optional
	ExtractorModelOverride string `json:"extractorModelOverride,omitempty"`
}

ScopeSpec configures the dynamic per-session scope behavior for AgentSessions of this class. Off by default; when enabled, the runner evaluates a Layer 2 SessionScope memory doc before SpiceDB Checks, and SpiceDB gains per-session disallow relations that always override allow paths.

func (*ScopeSpec) DeepCopy

func (in *ScopeSpec) DeepCopy() *ScopeSpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScopeSpec.

func (*ScopeSpec) DeepCopyInto

func (in *ScopeSpec) DeepCopyInto(out *ScopeSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SecretKeyRef

type SecretKeyRef struct {
	// Name is the Secret's name, in the referring object's own namespace.
	Name string `json:"name"`
	// Key is the data key within the Secret holding the value.
	Key string `json:"key"`
}

func (*SecretKeyRef) DeepCopy

func (in *SecretKeyRef) DeepCopy() *SecretKeyRef

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretKeyRef.

func (*SecretKeyRef) DeepCopyInto

func (in *SecretKeyRef) DeepCopyInto(out *SecretKeyRef)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SecretOutputCompletion

type SecretOutputCompletion struct {
	// Handle is the secret-output handle the runner captured.
	Handle string `json:"handle"`
	// SecretName is the per-session Secret key the value was written under.
	SecretName string `json:"secretName"`
	// WrittenAt is when the value landed in the Secret.
	WrittenAt *metav1.Time `json:"writtenAt,omitempty"`

	// Name is the secret-output logical name (the toolspec's
	// secretOutput.name). Used by the runner to enforce write-once
	// per name without Secret-read RBAC.
	Name string `json:"name,omitempty"`
}

SecretOutputCompletion maps a secret-output handle to the per-session Secret key (name) its value was written under. No value is stored here.

func (*SecretOutputCompletion) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretOutputCompletion.

func (*SecretOutputCompletion) DeepCopyInto

func (in *SecretOutputCompletion) DeepCopyInto(out *SecretOutputCompletion)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SecretRef

type SecretRef struct {
	// Name is the Secret's name, in the referring object's own namespace.
	Name string `json:"name"`
}

SecretRef references a Secret by name (no key — see OAuthCredentialSource for the fixed multi-key shape it implies).

func (*SecretRef) DeepCopy

func (in *SecretRef) DeepCopy() *SecretRef

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretRef.

func (*SecretRef) DeepCopyInto

func (in *SecretRef) DeepCopyInto(out *SecretRef)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SessionAuthz

type SessionAuthz struct {
	// InteractPermission is a SpiceDB subject-set ("<type>:<id>#<relation>",
	// e.g. "group:engineering#member") written as agentsession#participant on
	// every new session of this class. It WIDENS interact to that population;
	// it never restricts the starter, who always holds interact as started_by.
	// Mutually exclusive with OnlyStartersInteract.
	// +optional
	InteractPermission string `json:"interactPermission,omitempty"`

	// AllowedStarters lists who may start a session of this class, as SpiceDB
	// subject refs: "user:<canonical>" or a subject-set "group:<id>#member".
	// Written by the AgentClass controller as agentclass#starter tuples; the
	// set on the class IS the set in SpiceDB (entries removed here are deleted
	// there). Empty means no start gate — anyone the channel admits may start,
	// as before.
	//
	// Enforced at the AgentSession reconciler for EVERY entry path (channel,
	// browser, delegation, restart): a session whose started_by does not hold
	// the gate permission is failed before any runner pod exists, and the
	// person is told. Values are canonical ids, never raw emails — a bundle
	// install question canonicalizes on the way in.
	//
	// A start gate makes a class HUMAN-ENTRY-ONLY: every arm of the gate
	// resolves a person, so a session nobody started — a cron, a webhook — is
	// refused structurally. A class that declares an allowlist therefore cannot
	// be bound to an input Channel that carries no person; the controller
	// refuses that combination rather than let every such session fail.
	//
	// Removal is PROSPECTIVE. Dropping someone from this list deletes their
	// standing to start NEW sessions; it does not end the ones already running,
	// which keep their verdict as a status condition. Who may keep TALKING to a
	// live session is OnlyStartersInteract's question, not this field's.
	// +kubebuilder:validation:MaxItems=64
	// +optional
	AllowedStarters []string `json:"allowedStarters,omitempty"`

	// PlatformAdminsMayStart, when false, makes the gate EXPLICIT: only
	// AllowedStarters may start, and the platform-admin arm of
	// agentclass#start_session does not apply (agentclass#start_explicit is
	// checked instead). Admins who want to use such an agent list themselves.
	// Default true. Meaningful only with a non-empty AllowedStarters — the
	// controller refuses false with an empty list, since nobody could start.
	// +optional
	PlatformAdminsMayStart *bool `json:"platformAdminsMayStart,omitempty"`

	// OnlyStartersInteract fixes the session interact set to this class's
	// starters: the applied interact permission becomes
	// "agentclass:<ns>/<name>#starter", so the existing Interact gate admits
	// exactly AllowedStarters and nobody else — no channel-derived membership,
	// no declared widening. Requires a non-empty AllowedStarters and an empty
	// InteractPermission. Rename-safe: the subject-set is computed from the
	// class's own name at use time, never stored.
	// +optional
	OnlyStartersInteract bool `json:"onlyStartersInteract,omitempty"`

	// ArtifactVisibility declares who may VIEW the artifacts produced by this
	// class's sessions. "session" (the default, and what an empty value means)
	// keeps today's audience: the session's interact set plus platform admins.
	// "organization" opts the class's sessions into the org-wide audience —
	// any user who can authenticate against the cluster's IdP may view the
	// artifacts (view only: never the conversation, the plan, or send
	// standing, and a session's deny list still beats it).
	//
	// Level-triggered, both ways: the AgentSession reconciler re-levels the
	// agentsession#artifact_org_viewer wildcard tuple from this value on every
	// reconcile — including reconciles of completed sessions — so flipping to
	// "organization" exposes existing sessions' artifacts, and flipping back
	// revokes org-wide access everywhere.
	// +kubebuilder:validation:Enum=session;organization
	// +optional
	ArtifactVisibility string `json:"artifactVisibility,omitempty"`
}

SessionAuthz configures who may START a session of this class and who may INTERACT with one once it exists.

func (*SessionAuthz) DeepCopy

func (in *SessionAuthz) DeepCopy() *SessionAuthz

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SessionAuthz.

func (*SessionAuthz) DeepCopyInto

func (in *SessionAuthz) DeepCopyInto(out *SessionAuthz)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*SessionAuthz) OrgWideArtifactVisibility

func (s *SessionAuthz) OrgWideArtifactVisibility() bool

OrgWideArtifactVisibility reports whether sessions of this class widen their artifacts' view audience to the whole IdP-authenticated organization. Nil-safe; only an explicit "organization" opts in.

type SessionDefaults

type SessionDefaults struct {
	// IdleTTL is how long a session may sit idle before it is terminated.
	// +kubebuilder:default="30m"
	IdleTTL metav1.Duration `json:"idleTTL,omitempty"`
	// MaxDuration is the wall-clock lifetime cap on a session.
	// +kubebuilder:default="4h"
	MaxDuration metav1.Duration `json:"maxDuration,omitempty"`
}

func (*SessionDefaults) DeepCopy

func (in *SessionDefaults) DeepCopy() *SessionDefaults

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SessionDefaults.

func (*SessionDefaults) DeepCopyInto

func (in *SessionDefaults) DeepCopyInto(out *SessionDefaults)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SessionHold

type SessionHold struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   SessionHoldSpec   `json:"spec,omitempty"`
	Status SessionHoldStatus `json:"status,omitempty"`
}

SessionHold parks an AgentSession for forensic review and gates its return to running on a human decision.

Shaped after CredentialUpdateRequest deliberately: spec.sessionRef is 1:1, so its mapper is a pure name projection needing no List, and the owner-ref to the AgentSession makes the hold live exactly as long as the session it holds.

This CR's controller NEVER writes AgentSession.status.phase. Phase is owned by the AgentSession reconciler alone; co-ownership by two operator reconcilers is the hazard credentialupdate.go documents.

+kubebuilder:object:root=true +kubebuilder:subresource:status +kubebuilder:printcolumn:name="Session",type=string,JSONPath=`.spec.sessionRef.name` +kubebuilder:printcolumn:name="Source",type=string,JSONPath=`.spec.source` +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`

func (*SessionHold) DeepCopy

func (in *SessionHold) DeepCopy() *SessionHold

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SessionHold.

func (*SessionHold) DeepCopyInto

func (in *SessionHold) DeepCopyInto(out *SessionHold)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*SessionHold) DeepCopyObject

func (in *SessionHold) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

func (*SessionHold) IsReleased

func (h *SessionHold) IsReleased() bool

IsReleased reports whether this hold has been cleared by a human.

type SessionHoldList

type SessionHoldList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []SessionHold `json:"items"`
}

+kubebuilder:object:root=true

func (*SessionHoldList) DeepCopy

func (in *SessionHoldList) DeepCopy() *SessionHoldList

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SessionHoldList.

func (*SessionHoldList) DeepCopyInto

func (in *SessionHoldList) DeepCopyInto(out *SessionHoldList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*SessionHoldList) DeepCopyObject

func (in *SessionHoldList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type SessionHoldSpec

type SessionHoldSpec struct {
	// SessionRef is the AgentSession this hold parks. The CR also carries an
	// owner-ref to it, so the hold is garbage-collected with the session.
	SessionRef NamespacedRef `json:"sessionRef"`

	// Reason is platform-authored trip text shown on the release card. It is
	// never agent-authored: on this card the agent is the SUBJECT of the
	// decision, not the requester, so it gets no voice.
	// +kubebuilder:validation:MaxLength=512
	Reason string `json:"reason"`

	// Source is "manual" or "tripper/<name>". It exists for the audit trail and
	// the card's wording; nothing branches on it.
	// +kubebuilder:validation:MaxLength=128
	Source string `json:"source"`

	// TrippedBy is the canonical subject of the human who tripped it. Empty for
	// an automated trip, which is identified by Source instead.
	// +optional
	TrippedBy identity.Subject `json:"trippedBy,omitempty"`
}

func (*SessionHoldSpec) DeepCopy

func (in *SessionHoldSpec) DeepCopy() *SessionHoldSpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SessionHoldSpec.

func (*SessionHoldSpec) DeepCopyInto

func (in *SessionHoldSpec) DeepCopyInto(out *SessionHoldSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SessionHoldStatus

type SessionHoldStatus struct {
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`

	// Phase is Active or Released.
	// +optional
	Phase string `json:"phase,omitempty"`

	// Determination is platform-authored text explaining the current phase,
	// surfaced rather than logged so a refusal is never silent.
	//
	// WRITTEN ONLY BY THE SESSIONHOLD CONTROLLER, which owns the hold's
	// DISPOSITION: what was decided about it — released, refused, cascaded to
	// descendants, or a cascade that failed. See Containment for the other
	// half and why they are two fields.
	// +optional
	Determination string `json:"determination,omitempty"`

	// Containment is platform-authored text explaining what happened to the
	// SESSION as it was parked: that it is held, and whether the workspace
	// snapshot that preserves the evidence succeeded.
	//
	// WRITTEN ONLY BY THE AGENTSESSION RECONCILER.
	//
	// It is a separate field from Determination because the two narratives are
	// INDEPENDENT, not alternatives. A workspace snapshot can fail while a
	// cascade to descendants also fails, and both are true at once. Sharing one
	// field made each writer erase the other's report — so an operator
	// debugging a doubly-degraded hold saw only whichever controller wrote
	// last, in exactly the state where they need both. Two writers on one
	// free-text status field also woke each other's watches on every pass; the
	// loop was the symptom, the shared field was the disease.
	//
	// Splitting is necessary but not sufficient: each writer must ALSO skip
	// the write when the text is unchanged, or the two keep waking each other
	// through the same object even though neither is erasing anything.
	// +optional
	Containment string `json:"containment,omitempty"`

	// TrippedAt is when the controller first observed this hold. An OBSERVATION,
	// set once: it must never live in spec, or a re-apply stops being a no-op.
	// +optional
	TrippedAt *metav1.Time `json:"trippedAt,omitempty"`

	// SnapshotHandle names the workspace snapshot taken at trip time, empty
	// until the snapshot Job completes.
	// +optional
	SnapshotHandle string `json:"snapshotHandle,omitempty"`

	// InteractionRef is the requestRef of the published release card.
	// +optional
	InteractionRef string `json:"interactionRef,omitempty"`

	// ReleasedBy is the canonical subject of the human who approved the release
	// card, set once by the SessionHold controller's Decide alongside
	// Phase=Released. The AgentSession reconciler reads it back to attribute the
	// lifecyclecore.Released event it emits when it observes this hold released.
	// +optional
	ReleasedBy identity.Subject `json:"releasedBy,omitempty"`
}

func (*SessionHoldStatus) DeepCopy

func (in *SessionHoldStatus) DeepCopy() *SessionHoldStatus

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SessionHoldStatus.

func (*SessionHoldStatus) DeepCopyInto

func (in *SessionHoldStatus) DeepCopyInto(out *SessionHoldStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SessionUserIdentity

type SessionUserIdentity struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   SessionUserIdentitySpec   `json:"spec,omitempty"`
	Status SessionUserIdentityStatus `json:"status,omitempty"`
}

+kubebuilder:object:root=true +kubebuilder:resource:scope=Namespaced,categories={authzed,spicebox},shortName=suid +kubebuilder:subresource:status +kubebuilder:printcolumn:name="Session",type="string",JSONPath=".spec.agentSession" +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +genclient

SessionUserIdentity is one session's narrowing of a user's UserIdentity catalog down to just the credentials that session's agent asked for, for identityMode=userPassthrough. It is 1:1 with its AgentSession: metadata.name equals the session name, and an owner-ref ties their lifetimes together.

Namespaced. It has no controller of its own -- pkg/controllers/agentsession builds it and server-side-applies it. status.missingCredentials is what parks the session in AwaitingCredentials, so an empty spec.credentials here means "still waiting for the user to link", not "no credentials needed".

func (*SessionUserIdentity) DeepCopy

func (in *SessionUserIdentity) DeepCopy() *SessionUserIdentity

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SessionUserIdentity.

func (*SessionUserIdentity) DeepCopyInto

func (in *SessionUserIdentity) DeepCopyInto(out *SessionUserIdentity)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*SessionUserIdentity) DeepCopyObject

func (in *SessionUserIdentity) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type SessionUserIdentityList

type SessionUserIdentityList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []SessionUserIdentity `json:"items"`
}

+kubebuilder:object:root=true

func (*SessionUserIdentityList) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SessionUserIdentityList.

func (*SessionUserIdentityList) DeepCopyInto

func (in *SessionUserIdentityList) DeepCopyInto(out *SessionUserIdentityList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*SessionUserIdentityList) DeepCopyObject

func (in *SessionUserIdentityList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type SessionUserIdentitySpec

type SessionUserIdentitySpec struct {
	// AgentSession is the name of the owning AgentSession (same namespace).
	// metadata.name equals this value (1:1).
	AgentSession string `json:"agentSession"`

	// UserIdentity is the name of the cluster-scoped UserIdentity this
	// projection was narrowed from.
	UserIdentity string `json:"userIdentity"`

	// Subject is the starter's canonical SpiceDB subject.
	Subject string `json:"subject"`

	// Credentials is the subset of the UserIdentity's catalog the agent
	// needs. Same shape as AgentIdentity.Spec.Credentials. SecretRefs name
	// Secrets in the agentprimitives-identities namespace. Empty while
	// the session is parked with missing credentials.
	// +optional
	Credentials []AgentCredential `json:"credentials,omitempty"`
}

func (*SessionUserIdentitySpec) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SessionUserIdentitySpec.

func (*SessionUserIdentitySpec) DeepCopyInto

func (in *SessionUserIdentitySpec) DeepCopyInto(out *SessionUserIdentitySpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SessionUserIdentityStatus

type SessionUserIdentityStatus struct {
	// ObservedGeneration is the spec generation this status reflects.
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`

	// MissingCredentials lists credential names the agent needs but the
	// user has not linked. Empty → ready to proceed.
	// +optional
	MissingCredentials []string `json:"missingCredentials,omitempty"`

	// ParkedAt is when the owning session first entered AwaitingCredentials.
	// The credential-link timeout is measured from
	// max(ParkedAt, LastInteractionAt).
	// +optional
	ParkedAt *metav1.Time `json:"parkedAt,omitempty"`

	// LastInteractionAt is bumped by identityd's heartbeat while the user is
	// actively linking, so an in-progress link does not time out.
	// +optional
	LastInteractionAt *metav1.Time `json:"lastInteractionAt,omitempty"`

	// Explanation is the structured per-credential description rendered to
	// the user (one Item per missing credential). Populated by the
	// operator's BuildExplanation; rendered by channelsd (Slack DM) and
	// identityd (portal menu).
	// +optional
	Explanation *CredentialExplanation `json:"explanation,omitempty"`

	// Conditions carries Ready; see SessionUserIdentityConditionReady.
	// +patchStrategy=merge
	// +patchMergeKey=type
	// +optional
	Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}

func (*SessionUserIdentityStatus) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SessionUserIdentityStatus.

func (*SessionUserIdentityStatus) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SettingsAuthzCeiling

type SettingsAuthzCeiling struct {
	// MaxApprovalTimeout caps AuthzBlock.ApprovalTimeout: how long a tool-call,
	// leakage-share, or cold-start scope approval may wait for a human before
	// the gate's timeout policy fires. Zero = no ceiling on the wait.
	// +optional
	MaxApprovalTimeout metav1.Duration `json:"maxApprovalTimeout,omitempty"`

	// MaxInformationLeakageApprovalTTL caps
	// InformationLeakagePolicy.ApprovalTTL: how long an approved leakage share
	// may be reused before the agent must ask again. Zero = no ceiling on
	// reuse.
	// +optional
	MaxInformationLeakageApprovalTTL metav1.Duration `json:"maxInformationLeakageApprovalTTL,omitempty"`
}

SettingsAuthzCeiling caps how long a human-in-the-loop authz window may stay open. Each field is optional; an unset (zero) field contributes no ceiling for that window. A window that a lower tier asks to be LONGER than the tightest ceiling is clamped down to it by the resolver, which records the clamp on status.effectiveSettings (provenance "clamped") and raises a non-fatal AuthzClamped violation.

Only the two windows that bound granted authority are capped. The cold-start scopeMaxLlmLatencyMs is a latency budget for the extractor LLM, not a window in which authority is held, so it stays a default-only knob.

func (*SettingsAuthzCeiling) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SettingsAuthzCeiling.

func (*SettingsAuthzCeiling) DeepCopyInto

func (in *SettingsAuthzCeiling) DeepCopyInto(out *SettingsAuthzCeiling)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SettingsBudgetCeiling

type SettingsBudgetCeiling struct {
	// MaxTurns caps agent turns per session. Zero = no ceiling on turns.
	// +kubebuilder:validation:Minimum=1
	// +optional
	MaxTurns int32 `json:"maxTurns,omitempty"`
	// MaxTokens caps total tokens per session. Zero = no ceiling on tokens.
	// +kubebuilder:validation:Minimum=1
	// +optional
	MaxTokens int64 `json:"maxTokens,omitempty"`
	// MaxDuration is a duration (e.g. "2h"). Zero = no ceiling on duration.
	// +optional
	MaxDuration metav1.Duration `json:"maxDuration,omitempty"`
	// SessionExpiration is a duration (e.g. "24h"). Zero = no ceiling on the
	// wall-clock lifetime cap.
	// +optional
	SessionExpiration metav1.Duration `json:"sessionExpiration,omitempty"`
	// MaxDelegatedAgents caps the total number of sessions in one delegation
	// tree. Zero = no ceiling imposed by this tier (the controller's own
	// built-in bound still applies — see BudgetConfig.MaxDelegatedAgents).
	// +kubebuilder:validation:Minimum=1
	// +optional
	MaxDelegatedAgents int32 `json:"maxDelegatedAgents,omitempty"`
}

SettingsBudgetCeiling caps budget dimensions. Each field is optional; an unset (zero) field contributes no ceiling for that dimension.

func (*SettingsBudgetCeiling) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SettingsBudgetCeiling.

func (*SettingsBudgetCeiling) DeepCopyInto

func (in *SettingsBudgetCeiling) DeepCopyInto(out *SettingsBudgetCeiling)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SettingsDefaults

type SettingsDefaults struct {
	// Model defaults the agent model when an AgentClass omits its own.
	// +optional
	Model *DefaultModel `json:"model,omitempty"`

	// Budget defaults the agent budget when an AgentClass omits its own.
	// +optional
	Budget *BudgetConfig `json:"budget,omitempty"`

	// Authz defaults the per-class approval/authz timeouts.
	// +optional
	Authz *DefaultAuthz `json:"authz,omitempty"`

	// ToolGuard is this tier's default tool-guard policy, consulted when the
	// AgentClass has no matching rule (class → namespace → cluster → built-in).
	// +optional
	ToolGuard *ToolGuardPolicy `json:"toolGuard,omitempty"`

	// ReportSessionCost toggles the end-of-session cost estimate (a closing
	// channel message + status.estimatedCost). Optional, on by default: nil at
	// every tier ⇒ on. A lower tier's explicit value wins (namespace over
	// cluster). A behavioral default, so it lives in Defaults, not Limits.
	// +optional
	ReportSessionCost *bool `json:"reportSessionCost,omitempty"`

	// Sandbox is the default sandbox backend for agents in scope. A lower tier
	// that names its own backend overrides this; a bundle that names none
	// inherits it.
	// +optional
	Sandbox *SandboxBackend `json:"sandbox,omitempty"`
}

SettingsDefaults are fallback values used when a lower tier omits the field.

func (*SettingsDefaults) DeepCopy

func (in *SettingsDefaults) DeepCopy() *SettingsDefaults

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SettingsDefaults.

func (*SettingsDefaults) DeepCopyInto

func (in *SettingsDefaults) DeepCopyInto(out *SettingsDefaults)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SettingsLimits

type SettingsLimits struct {
	// Budget caps the agent budget dimensions. Each dimension is optional.
	// +optional
	Budget *SettingsBudgetCeiling `json:"budget,omitempty"`

	// Authz caps the human-in-the-loop approval windows. Ceiling semantics:
	// each field is a MAXIMUM, the shortest across tiers wins, and a lower tier
	// — including the AgentClass — may ask for a shorter window but never a
	// longer one. Distinct from Defaults.Authz, which merely supplies a value
	// the class is free to override.
	// +optional
	Authz *SettingsAuthzCeiling `json:"authz,omitempty"`

	// DeniedModels rejects catalog models by name even when present in the
	// catalog. UNION across tiers (a deny anywhere wins). Same pattern as
	// DeniedSkills.
	// +optional
	DeniedModels []string `json:"deniedModels,omitempty"`

	// AllowModelOverride permits an AgentClass to bring its own model apiKey
	// (instead of referencing the catalog). Top-down: the cluster must grant it;
	// lower tiers can only further restrict. nil/false ⇒ catalog-only.
	// +optional
	AllowModelOverride *bool `json:"allowModelOverride,omitempty"`

	// MinPlanGateMode is the plan-gate strictness an AgentClass may not go
	// below. Distinct from DefaultAuthz.PlanGate, which only says what a class
	// gets when it declares nothing: a class may freely declare a laxer mode
	// than the default, and this is what forbids it.
	//
	// Ordered disabled < logging < enforcing. Tiers ratchet UP only — a
	// namespace declaring a laxer floor than the cluster's does not loosen it.
	// Empty ⇒ no floor.
	// +kubebuilder:validation:Enum=disabled;logging;enforcing
	// +optional
	MinPlanGateMode string `json:"minPlanGateMode,omitempty"`

	// AllowedToolkits restricts which SpiceboxToolkit names may be used.
	// Same tri-state semantics as AllowedMCPServers.
	// +optional
	AllowedToolkits *[]string `json:"allowedToolkits,omitempty"`

	// AllowedMCPServers restricts which MCPServer CRs (by name) and, optionally,
	// which of their tools may be used. nil = no constraint; non-nil empty =
	// deny-all.
	// +optional
	AllowedMCPServers *[]AllowedMCPServer `json:"allowedMCPServers,omitempty"`

	// AllowedSkills restricts which skills (by canonical name) may be used.
	// Same tri-state semantics as AllowedMCPServers. Entries may be exact canonical
	// names or trailing-wildcard patterns ("github.com/org/**", "repo//x@*").
	// The effective ceiling requires a match in every constraining tier.
	// +optional
	AllowedSkills *[]string `json:"allowedSkills,omitempty"`

	// DeniedSkills lists skill patterns that are rejected even if allowed. The
	// effective deny set is the UNION across tiers (a deny anywhere wins). Same
	// pattern syntax as AllowedSkills.
	// +optional
	DeniedSkills []string `json:"deniedSkills,omitempty"`

	// Pinning is the unified per-kind dependency-pinning ceiling: minimum pin
	// strengths and enforcement modes per kind, plus tier-scoped bypasses.
	// +optional
	Pinning *PinningPolicy `json:"pinning,omitempty"`

	// ToolGuard is the hard ceiling on tool circuit-breaker / rate-limit
	// policy: bounds lower tiers cannot escape (strictest across tiers).
	// +optional
	ToolGuard *ToolGuardCeiling `json:"toolGuard,omitempty"`

	// ContentInspectors are content-guard plugins (referenced by registry ID)
	// enforced on tool I/O. Ceiling semantics: lower tiers ADD inspectors but
	// cannot remove or weaken a higher tier's. Default-off: nil ⇒ no inspection.
	// +optional
	ContentInspectors *[]ContentInspectorConfig `json:"contentInspectors,omitempty"`

	// NativeFileHandling is a security-sensitive Tier-2 grant: it lets the
	// files modality route large artifact bytes through the provider's
	// code-execution sandbox instead of the Tier-1 fetch_artifact tool
	// (content-guard still inspects at the bridge). Top-down: the cluster must
	// grant it; lower tiers can only further restrict. nil/false ⇒ off.
	// +optional
	NativeFileHandling *bool `json:"nativeFileHandling,omitempty"`

	// RequireSubagentDigestPins is a security requirement, not a grant: when
	// true, delegation through an UNPINNED spec.subagents roster entry is
	// refused — every delegation target must be pinned name@sha256:<digest>
	// and match its installed bundle digest. Requirement semantics fold
	// downward: any tier may add the requirement; no lower tier can remove
	// one set above it. nil/false ⇒ not required.
	// +optional
	RequireSubagentDigestPins *bool `json:"requireSubagentDigestPins,omitempty"`

	// AllowedSandboxKinds constrains which sandbox backends may be used at all.
	// Tri-state, like the other allowlists here: nil imposes no ceiling; a
	// non-nil list (including an empty one, which denies everything)
	// constrains. The effective set is the intersection across constraining
	// tiers, so a lower tier can narrow but never widen.
	//
	// This is how a cluster admin refuses a backend outright — which matters
	// once a backend can run workloads, and their credentials, outside the
	// cluster.
	// +optional
	AllowedSandboxKinds *[]string `json:"allowedSandboxKinds,omitempty"`
	// RequireStandingFor names resource types that MUST use `required` standing,
	// whatever their schema fragment declares. It is the admin veto over
	// SpiceDBResource.Standing.
	//
	// A CEILING, not a default — the same shape as MinPlanGateMode. Tiers UNION
	// their sets: a namespace may add types the cluster did not name, and can
	// never remove one it did. Empty means no veto.
	// +optional
	// +listType=atomic
	RequireStandingFor []string `json:"requireStandingFor,omitempty"`

	// BuilderClasses is the cluster-admin sanction list for the agent-builder
	// workshop (spec §1.1): only a class named here, in this exact namespace,
	// may be provisioned a workshop — and only when it also references the
	// named SidecarToolbox. CLUSTER TIER ONLY: the AgentSession reconciler
	// reads this from ClusterAgentSettings alone, so a namespace tenant's
	// AgentSettings carrying it is inert (a namespace must not self-sanction).
	// +optional
	BuilderClasses *[]BuilderClassRef `json:"builderClasses,omitempty"`

	// MaxWorkshopsPerStarter caps live workshops per starting person,
	// cluster-wide default 3 when unset. Cluster tier only, like BuilderClasses.
	// +optional
	MaxWorkshopsPerStarter *int32 `json:"maxWorkshopsPerStarter,omitempty"`
}

SettingsLimits are the ceiling/allowlist fields. They narrow downward: budget dimensions via min(), allowlists via intersection.

func (*SettingsLimits) BuilderClassFor

func (l *SettingsLimits) BuilderClassFor(ns, name string) *BuilderClassRef

BuilderClassFor returns the sanction entry for ns/name, or nil. Nil receiver and absent list both answer nil — the fail-closed reading of "no sanction".

func (*SettingsLimits) DeepCopy

func (in *SettingsLimits) DeepCopy() *SettingsLimits

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SettingsLimits.

func (*SettingsLimits) DeepCopyInto

func (in *SettingsLimits) DeepCopyInto(out *SettingsLimits)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*SettingsLimits) MaxWorkshopsPerStarterOrDefault

func (l *SettingsLimits) MaxWorkshopsPerStarterOrDefault() int32

MaxWorkshopsPerStarterOrDefault returns the per-starter live-workshop ceiling. Nil receiver and unset field both answer the default.

THE accessor, for the same reason WorkshopLimitBody is one constant: two gates count against this ceiling — the browser start route refuses at it, the operator's ensureWorkshop boot-fails at it — and a second reading of "unset means 3" in either package is a number that can drift, leaving the route admitting a start the operator immediately kills.

type SettingsSpec

type SettingsSpec struct {
	// Limits are governance ceilings. Absent = this tier imposes no ceiling.
	// +optional
	Limits *SettingsLimits `json:"limits,omitempty"`

	// Defaults are fallback values used when a lower tier omits the field.
	// +optional
	Defaults *SettingsDefaults `json:"defaults,omitempty"`

	// ModelCatalog is the registry of permitted models — the model allowlist.
	// The cluster tier authors entries with central TokenRefs and marks one
	// Default; lower tiers may list name-only entries to narrow the usable set
	// (intersection).
	// +optional
	ModelCatalog *[]ModelCatalogEntry `json:"modelCatalog,omitempty"`

	// ClassUserPreferences carries admin globals for classes' userPreferences,
	// keyed by class name (same namespace), then preference key. NAMESPACE
	// TIER ONLY: the ClusterAgentSettings webhook rejects a non-empty value —
	// namespaced class names make a cluster-tier global ambiguous.
	// +optional
	ClassUserPreferences map[string]map[string]PreferenceGlobal `json:"classUserPreferences,omitempty"`
}

SettingsSpec is the spec shared by ClusterAgentSettings and AgentSettings. It carries two field groups with opposite merge semantics: Limits (ceilings that narrow downward via min/intersection) and Defaults (fallbacks used when a lower tier omits the field).

func (*SettingsSpec) DeepCopy

func (in *SettingsSpec) DeepCopy() *SettingsSpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SettingsSpec.

func (*SettingsSpec) DeepCopyInto

func (in *SettingsSpec) DeepCopyInto(out *SettingsSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SettingsStatus

type SettingsStatus struct {
	// ObservedGeneration is the spec generation this status reflects.
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`

	// ObservedLimits is the durable trigger state for tool-origin revocation:
	// the allowlists the operator has already published revokes for. The
	// settings RevokePublisher diffs spec.limits against this record, so an
	// entry withdrawn while the operator was down — or one whose revoke failed
	// to publish — is still emitted on the next reconcile, instead of living
	// only in a process-scoped map that dies with the pod.
	//
	// nil means "never observed": prime without emitting, so a fresh install
	// does not revoke origins nobody ever granted.
	// +optional
	ObservedLimits *ObservedSettingsLimits `json:"observedLimits,omitempty"`

	// Conditions carries SelfConsistent; see its type constant.
	// +patchStrategy=merge
	// +patchMergeKey=type
	// +optional
	Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}

SettingsStatus is the shared status for both settings CRDs.

func (*SettingsStatus) DeepCopy

func (in *SettingsStatus) DeepCopy() *SettingsStatus

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SettingsStatus.

func (*SettingsStatus) DeepCopyInto

func (in *SettingsStatus) DeepCopyInto(out *SettingsStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SidecarPodFailure

type SidecarPodFailure struct {
	// Reason is the kubelet's terminal container Waiting reason, e.g.
	// "CrashLoopBackOff" or "ImagePullBackOff".
	Reason string `json:"reason"`

	// Message is the most actionable text available: the container's last
	// terminated message when present — the crashing container's log tail,
	// thanks to the pod's FallbackToLogsOnError policy — otherwise the
	// kubelet's waiting message. Preferring the log tail is what makes the
	// field worth surfacing; the waiting message alone is content-free for a
	// crash loop ("back-off Ns restarting failed container=…").
	// +optional
	Message string `json:"message,omitempty"`

	// ExitCode is the container's last terminated exit code. Nil when the
	// container never ran (e.g. the image could not be pulled).
	// +optional
	ExitCode *int32 `json:"exitCode,omitempty"`
}

SidecarPodFailure is the operator's record of why a separate-pod sidecar will not become Ready. The runner reads it to tell the agent and the user that a promised toolset is unavailable, and why, instead of proceeding tool-less and silently.

It carries no timestamp on purpose: AgentSession status is written under only-changed-writes semantics, so a wall-clock field would rewrite status on every reconcile and churn field ownership. The Pod itself remains the source of truth for timing.

func (*SidecarPodFailure) DeepCopy

func (in *SidecarPodFailure) DeepCopy() *SidecarPodFailure

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SidecarPodFailure.

func (*SidecarPodFailure) DeepCopyInto

func (in *SidecarPodFailure) DeepCopyInto(out *SidecarPodFailure)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SidecarReachability

type SidecarReachability struct {
	// Name is the LLM-facing prefix (ResolvedSidecarToolbox.Name).
	Name string `json:"name"`
	// Reachable is true when the last live tools/list probe succeeded and the
	// declared allowlist was satisfied.
	Reachable bool `json:"reachable"`
	// ObservedTools is the tool names the live probe reported (sorted). The
	// runtime source of truth, distinct from the SidecarToolbox CR's
	// admission-time status.observedTools (which is not read at runtime).
	// +optional
	ObservedTools []string `json:"observedTools,omitempty"`
	// Unreachable is the probe/allowlist error when Reachable is false; empty
	// when Reachable is true.
	// +optional
	Unreachable string `json:"unreachable,omitempty"`
	// ObservedAt is when the runner last probed this sidecar.
	// +optional
	ObservedAt *metav1.Time `json:"observedAt,omitempty"`
}

SidecarReachability is the runner's live-probe result for one sidecar, keyed by Name. Written EXCLUSIVELY by the runner, which is co-located, already permitted runner→sidecar by the per-session NetworkPolicy, and probes a secret-gated sidecar's REAL pod with the REAL delivered secret. It is its own list rather than fields on ResolvedSidecarToolbox because the operator rebuilds that array each reconcile and a JSON-merge write would wipe per-element runner fields.

No entry = not yet probed (AwaitingSecret, or pod not Ready). Reachable=true = tools/list succeeded and the allowlist is satisfied. Reachable=false = up but unreachable or drifted, with Unreachable carrying the reason, so a mid-session sidecar failure is never silent.

func UpsertSidecarReachability

func UpsertSidecarReachability(list []SidecarReachability, entry SidecarReachability) []SidecarReachability

UpsertSidecarReachability replaces (by Name) or appends the given per-session sidecar reachability observation, mirroring UpsertObservedPin. Runner-owned; the operator never writes this list.

func (*SidecarReachability) DeepCopy

func (in *SidecarReachability) DeepCopy() *SidecarReachability

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SidecarReachability.

func (*SidecarReachability) DeepCopyInto

func (in *SidecarReachability) DeepCopyInto(out *SidecarReachability)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SidecarToolbox

type SidecarToolbox struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   SidecarToolboxSpec   `json:"spec,omitempty"`
	Status SidecarToolboxStatus `json:"status,omitempty"`
}

+kubebuilder:object:root=true +kubebuilder:resource:scope=Namespaced,categories={authzed,spicebox},shortName=sbxtb +kubebuilder:subresource:status +kubebuilder:printcolumn:name="Valid",type="string",JSONPath=".status.conditions[?(@.type=='Valid')].status" +kubebuilder:printcolumn:name="Reachable",type="string",JSONPath=".status.conditions[?(@.type=='Reachable')].status" +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +genclient

SidecarToolbox declares a user-supplied MCP server that runs as a sidecar container alongside the agent runner pod: its image, sandbox shape, tool allowlist, CEL constraints and egress policy.

Namespaced. Reconciled by pkg/controllers/sidecartoolbox, which validates the spec and runs a one-shot probe Pod to confirm the image is reachable and its live tool list matches the allowlist. Upstream credentials are resolved by the AgentSession reconciler at pod-create time and frozen for the life of the pod -- there is no just-in-time refresh for a sidecar.

func (*SidecarToolbox) DeepCopy

func (in *SidecarToolbox) DeepCopy() *SidecarToolbox

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SidecarToolbox.

func (*SidecarToolbox) DeepCopyInto

func (in *SidecarToolbox) DeepCopyInto(out *SidecarToolbox)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*SidecarToolbox) DeepCopyObject

func (in *SidecarToolbox) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type SidecarToolboxConfigMapKeyRef

type SidecarToolboxConfigMapKeyRef struct {
	// Name is the ConfigMap's name, in the toolbox's own namespace.
	Name string `json:"name"`
	// Key is the data key within the ConfigMap.
	Key string `json:"key"`
}

func (*SidecarToolboxConfigMapKeyRef) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SidecarToolboxConfigMapKeyRef.

func (*SidecarToolboxConfigMapKeyRef) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SidecarToolboxHealthcheck

type SidecarToolboxHealthcheck struct {
	// Path is the HTTP path probed for readiness.
	// +kubebuilder:default=/healthz
	Path string `json:"path,omitempty"`
	// TimeoutSeconds bounds how long the probe waits for the sidecar to answer.
	// +kubebuilder:default=30
	// +kubebuilder:validation:Minimum=1
	TimeoutSeconds int32 `json:"timeoutSeconds,omitempty"`
}

func (*SidecarToolboxHealthcheck) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SidecarToolboxHealthcheck.

func (*SidecarToolboxHealthcheck) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SidecarToolboxInlineSource

type SidecarToolboxInlineSource struct {
	// BaseImage is the image the script runs on top of.
	BaseImage string `json:"baseImage"`
	// Script is where the server's source comes from.
	Script SidecarToolboxScriptSource `json:"script"`
	// Entrypoint is the argv that starts the server inside the container.
	Entrypoint []string `json:"entrypoint"`
}

func (*SidecarToolboxInlineSource) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SidecarToolboxInlineSource.

func (*SidecarToolboxInlineSource) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SidecarToolboxIsolation

type SidecarToolboxIsolation string

SidecarToolboxIsolation is SidecarToolboxSpec.Isolation's type. See that field's doc comment for what each value buys.

const (
	// SidecarToolboxIsolationAuto is the default: Isolation contributes
	// nothing to the run-mode decision, so a non-secret-gated toolbox runs
	// in-pod exactly as it always has.
	SidecarToolboxIsolationAuto SidecarToolboxIsolation = "auto"
	// SidecarToolboxIsolationIsolated forces a separate per-session pod even
	// when the toolbox has no SecretInputs.
	SidecarToolboxIsolationIsolated SidecarToolboxIsolation = "isolated"
)

type SidecarToolboxList

type SidecarToolboxList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []SidecarToolbox `json:"items"`
}

+kubebuilder:object:root=true

func (*SidecarToolboxList) DeepCopy

func (in *SidecarToolboxList) DeepCopy() *SidecarToolboxList

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SidecarToolboxList.

func (*SidecarToolboxList) DeepCopyInto

func (in *SidecarToolboxList) DeepCopyInto(out *SidecarToolboxList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*SidecarToolboxList) DeepCopyObject

func (in *SidecarToolboxList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type SidecarToolboxNetwork

type SidecarToolboxNetwork struct {
	// AllowedHosts merges with the SpiceboxClass's network.allowedHosts.
	// If the class's mode is "none" and any AllowedHosts are supplied
	// here, the effective sidecar network upgrades to "allowlist" with
	// exactly these hosts.
	// +optional
	AllowedHosts []string `json:"allowedHosts,omitempty"`
}

func (*SidecarToolboxNetwork) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SidecarToolboxNetwork.

func (*SidecarToolboxNetwork) DeepCopyInto

func (in *SidecarToolboxNetwork) DeepCopyInto(out *SidecarToolboxNetwork)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SidecarToolboxSandbox

type SidecarToolboxSandbox struct {
	// Class names a SpiceboxClass (cluster-scoped) whose resources,
	// runtimeClassName, default network mode, pidsLimit are inherited.
	Class string `json:"class"`
	// Network widens the class's egress for this toolbox.
	// +optional
	Network SidecarToolboxNetwork `json:"network,omitempty"`
}

func (*SidecarToolboxSandbox) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SidecarToolboxSandbox.

func (*SidecarToolboxSandbox) DeepCopyInto

func (in *SidecarToolboxSandbox) DeepCopyInto(out *SidecarToolboxSandbox)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SidecarToolboxScriptSource

type SidecarToolboxScriptSource struct {
	// ConfigMapRef names the ConfigMap key holding the script body.
	ConfigMapRef SidecarToolboxConfigMapKeyRef `json:"configMapRef"`
}

func (*SidecarToolboxScriptSource) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SidecarToolboxScriptSource.

func (*SidecarToolboxScriptSource) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SidecarToolboxSecretInput

type SidecarToolboxSecretInput struct {
	// Name is the env-var name (when Deliver=="env") or the logical key.
	Name string `json:"name"`
	// Deliver is how the value reaches the sidecar at startup: "env" (env var
	// named Name) or "file:<path>" (mounted file at <path>).
	// +kubebuilder:validation:Pattern=`^(env|file:/.+)$`
	Deliver string `json:"deliver"`
	// From is the secret-output logical name the producer emitted (matches the
	// producer's secretOutput.name / the per-session Secret key).
	From string `json:"from"`
}

func (*SidecarToolboxSecretInput) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SidecarToolboxSecretInput.

func (*SidecarToolboxSecretInput) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SidecarToolboxSource

type SidecarToolboxSource struct {
	// Image is a prebuilt container image reference.
	// +optional
	Image string `json:"image,omitempty"`
	// Inline builds the sidecar from a script layered onto a base image.
	// +optional
	Inline *SidecarToolboxInlineSource `json:"inline,omitempty"`
}

SidecarToolboxSource is a discriminated union — exactly one of Image or Inline must be set. Validated by the kind's ValidateFile and the controller's spec phase.

func (*SidecarToolboxSource) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SidecarToolboxSource.

func (*SidecarToolboxSource) DeepCopyInto

func (in *SidecarToolboxSource) DeepCopyInto(out *SidecarToolboxSource)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SidecarToolboxSpec

type SidecarToolboxSpec struct {
	// Name is the toolbox's toolspec-level name.
	Name string `json:"name"`
	// Version is the spec author's version of this declaration.
	Version string `json:"version"`
	// Intent is the one-line purpose shown to authors and reviewers.
	Intent string `json:"intent,omitempty"`

	// Source is where the sidecar's container image comes from.
	Source SidecarToolboxSource `json:"source"`
	// Sandbox is the SpiceboxClass shape and egress the sidecar runs under.
	Sandbox SidecarToolboxSandbox `json:"sandbox"`
	// Transport is how the runner reaches the sidecar's MCP endpoint.
	Transport SidecarToolboxTransport `json:"transport"`
	// UpstreamAuth is the credential the sidecar needs for its own upstream.
	UpstreamAuth SidecarToolboxUpstream `json:"upstreamAuth"`

	// Config is an opaque configuration document for the sidecar image,
	// delivered verbatim as the AP_SIDECAR_CONFIG environment variable. The
	// platform does not interpret it; validation belongs to whoever admits the
	// object (the workshop webhook parses it for first-party images known to
	// consume one, e.g. ap-api-adapter). Bounded because it rides in the pod
	// environment.
	//
	// MaxLength counts Unicode code points, not bytes — up to 4 bytes each in
	// UTF-8 — so it alone admits up to 262144 bytes. The BYTE cap (what execve
	// and apiadapter.MaxConfigBytes both count) is enforced by
	// apiadapter.Parse, which both the workshop webhook (at admission) and the
	// adapter binary (at boot) run. Deliberately NOT a CEL XValidation here:
	// this spec type is embedded by value into AgentSession's
	// status.resolvedSidecarToolboxes snapshot, and a CEL rule that reaches a
	// status path both trips the status-CEL guard (pkg/platform/settings'
	// TestNoUnguardedStatusCELRules) and blows the apiserver's CRD rule-cost
	// budget inside that unbounded status array — the agentsessions CRD is
	// then refused at install. Residual admin-path risk (no webhook, a
	// >131072-byte multi-byte document): execve fails with E2BIG and the
	// kubelet reports the container-start error on the Pod.
	// +optional
	// +kubebuilder:validation:MaxLength=65536
	Config string `json:"config,omitempty"`

	// Tools mirrors MCPServerTool field-for-field — same allowlist, CEL,
	// deny effects, sensitive fields. See MCPServerTool for details.
	Tools []MCPServerTool `json:"tools"`

	// ToolResourceMap declares, per-tool, the SpiceDB resource a read accesses —
	// identical semantics to MCPServer.spec.toolResourceMap. Consumed by the
	// runner's information-leakage gate so a sidecar tool participates in
	// per-datum egress (its result mints a pt-tag) instead of falling to the
	// undeclared coarse floor.
	// +optional
	ToolResourceMap []ToolResourceMapping `json:"toolResourceMap,omitempty"`

	// SpiceDBSchema declares the SpiceDB resource definitions this toolbox
	// contributes — identical to MCPServer.spec.spicedbSchema. The guardian's
	// schema composer concatenates fragments across every MCPServer AND
	// SidecarToolbox in the cluster so the audiences the tags derive from
	// resolve; identical declarations dedupe, conflicting ones fail the reconcile.
	// +optional
	SpiceDBSchema *SpiceDBSchemaFragment `json:"spicedbSchema,omitempty"`

	// MCPUIAppTools is ACCEPTED BUT NOT CONSUMED: setting enabled: true changes
	// nothing, and no error, log, or condition says so. It is typed identically
	// to MCPServer's stanza so a toolbox can declare the intent to be an MCP-UI
	// app-tool origin, but nothing reads it, so no sidecar tool is
	// browser-callable. Recorded, not enforced — the same posture as this CRD's
	// effectiveAllowedHosts.
	//
	// A toolbox tool named in AgentUI.spec.tools is therefore denied by the
	// three-way grant's origin condition: the right fail-closed answer for an
	// unwired origin, just not the one this field's name suggests.
	//
	// Absent ⇒ disabled: the permanent, fail-closed default.
	// +optional
	MCPUIAppTools *MCPUIAppToolsSpec `json:"mcpUiAppTools,omitempty"`

	// SecretInputs binds secret-output handles (produced by an earlier tool
	// call) to this toolbox. A toolbox with any SecretInput is "secret-gated":
	// it is NOT injected into the agent pod; the operator runs it as a separate
	// per-session pod once the bound secret is available.
	// +optional
	SecretInputs []SidecarToolboxSecretInput `json:"secretInputs,omitempty"`

	// Isolation declares that this toolbox must run in its own per-session pod
	// even though it has no SecretInputs. A secret-gated toolbox (any
	// SecretInputs) is ALREADY isolated for its own reason -- its gating
	// secret only exists per-session, so it cannot be baked into the runner
	// pod's spec -- regardless of this field; Isolation is for the toolbox
	// that needs a separate pod's consequences WITHOUT a gating secret:
	//
	//   - Its own NetworkPolicy, scoped to exactly this sidecar's ingress/
	//     egress, instead of inheriting the runner pod's shared policy (see
	//     pkg/controllers/agentsession/netpol.go: BuildRunnerNetworkPolicy vs
	//     BuildSidecarNetworkPolicy).
	//   - Eligibility for BuildSidecarPod's identity branch (a projected
	//     ServiceAccount token + operator env), which is reachable only in
	//     separate-pod mode -- an in-pod sidecar can never receive it.
	//
	// Absent (or "auto") never changes an existing toolbox's run mode:
	// today's in-pod behavior is preserved exactly.
	// +optional
	// +kubebuilder:validation:Enum=auto;isolated
	Isolation SidecarToolboxIsolation `json:"isolation,omitempty"`
}

func (*SidecarToolboxSpec) DeepCopy

func (in *SidecarToolboxSpec) DeepCopy() *SidecarToolboxSpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SidecarToolboxSpec.

func (*SidecarToolboxSpec) DeepCopyInto

func (in *SidecarToolboxSpec) DeepCopyInto(out *SidecarToolboxSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*SidecarToolboxSpec) LookupToolResourceMapping

func (s *SidecarToolboxSpec) LookupToolResourceMapping(name string) *ToolResourceMapping

LookupToolResourceMapping returns the mapping for tool name `name`, or nil. Mirrors MCPServerSpec.LookupToolResourceMapping.

type SidecarToolboxStatus

type SidecarToolboxStatus struct {
	// ObservedGeneration is the spec generation this status reflects.
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`
	// LastValidatedAt is when the spec last passed validation.
	// +optional
	LastValidatedAt *metav1.Time `json:"lastValidatedAt,omitempty"`
	// ObservedTools is the tool list the admission-time probe saw. Not read at
	// runtime — the runner's live per-session probe is authoritative there.
	// +optional
	ObservedTools []string `json:"observedTools,omitempty"`

	// Pin is the recorded pin baseline in the common PinRecord shape:
	// Digest = kubelet-resolved image digest (sha256:…), Version = declared
	// image tag (audit metadata). Strength reflects the declared ref: frozen
	// iff the source image is by-digest, named iff by-tag, else unpinned.
	// +optional
	Pin *PinRecord `json:"pin,omitempty"`

	// Conditions carries Valid, Reachable and PinDrift.
	// +patchStrategy=merge
	// +patchMergeKey=type
	// +optional
	Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}

func (*SidecarToolboxStatus) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SidecarToolboxStatus.

func (*SidecarToolboxStatus) DeepCopyInto

func (in *SidecarToolboxStatus) DeepCopyInto(out *SidecarToolboxStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SidecarToolboxTransport

type SidecarToolboxTransport struct {
	// Port is advisory — the operator allocates a free port at pod-build
	// time and sets MCP_PORT in the sidecar's env regardless. Sidecar
	// code is required to read MCP_PORT to bind.
	// +kubebuilder:default=8080
	Port int32 `json:"port,omitempty"`
	// Path is the HTTP path the sidecar's MCP Streamable-HTTP endpoint listens
	// on (e.g. "/mcp"); empty means the pod root "/". The runner appends it to
	// the pod URL for BOTH the tools/list reachability probe and tool-call
	// dispatch, so it must match where the sidecar actually serves MCP. A
	// leading slash is optional — the runner normalizes it.
	// +optional
	Path string `json:"path,omitempty"`
	// Healthcheck is how the operator's probe decides the sidecar is up.
	// +optional
	Healthcheck SidecarToolboxHealthcheck `json:"healthcheck,omitempty"`
}

func (*SidecarToolboxTransport) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SidecarToolboxTransport.

func (*SidecarToolboxTransport) DeepCopyInto

func (in *SidecarToolboxTransport) DeepCopyInto(out *SidecarToolboxTransport)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SidecarToolboxUpstream

type SidecarToolboxUpstream struct {
	// Provider names a provider in the /providers/ library, or the
	// UpstreamAuthProviderNone sentinel ("none") for a controller-issued-token
	// sidecar that needs no AgentIdentity credential. Drives
	// `oap agent setup-identity` and projects the resolved credential
	// into the sidecar's env at session boot via the toolbox: authkind.
	Provider string `json:"provider"`

	// EnvVar is the environment variable name the resolved upstream
	// credential is injected into (in the per-session sidecar Secret).
	// Empty means the sidecar needs no upstream credential (e.g. the echo
	// example) — the per-session Secret is written empty.
	// +optional
	EnvVar string `json:"envVar,omitempty"`
}

func (*SidecarToolboxUpstream) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SidecarToolboxUpstream.

func (*SidecarToolboxUpstream) DeepCopyInto

func (in *SidecarToolboxUpstream) DeepCopyInto(out *SidecarToolboxUpstream)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type Skill

type Skill struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   SkillSpec   `json:"spec,omitempty"`
	Status SkillStatus `json:"status,omitempty"`
}

+kubebuilder:object:root=true +kubebuilder:resource:categories={authzed,spicebox},shortName=skl +kubebuilder:subresource:status +kubebuilder:printcolumn:name="Valid",type="string",JSONPath=".status.conditions[?(@.type=='Valid')].status" +kubebuilder:printcolumn:name="Pinned",type="string",JSONPath=".status.conditions[?(@.type=='Pinned')].status" +kubebuilder:printcolumn:name="Canonical",type="string",JSONPath=".spec.canonicalName" +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"

Skill is one agentskills.io skill: a SKILL.md (frontmatter + body) plus optional bundled files. It is the addressable unit an AgentClass opts into. A Skill may be hand-authored (empty Source) or materialized by a SkillSource.

Namespaced. Reconciled by pkg/controllers/skill, which only keeps the Valid and Pinned conditions honest -- it never pulls or stages content. Identity is spec.canonicalName (pkg/tools/skills/canonical), not metadata.name, and a namespaced Skill shadows a ClusterSkill sharing that canonical name.

func (*Skill) DeepCopy

func (in *Skill) DeepCopy() *Skill

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Skill.

func (*Skill) DeepCopyInto

func (in *Skill) DeepCopyInto(out *Skill)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*Skill) DeepCopyObject

func (in *Skill) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

func (*Skill) SkillSpec

func (s *Skill) SkillSpec() *SkillSpec

SkillSpec returns a pointer to the embedded spec. It lets the shared skill validity reconciler (pkg/controllers/internal/skillspec) drive Skill and ClusterSkill, which carry the same SkillSpec, through one interface.

func (*Skill) SkillStatus

func (s *Skill) SkillStatus() *SkillStatus

SkillStatus returns a pointer to the embedded status. See SkillSpec.

type SkillBundleMount

type SkillBundleMount struct {
	// MountName is the sanitized, collision-free directory name the bundle
	// unpacks to under /skills/<MountName>/.
	MountName string `json:"mountName"`
	// ConfigMapName is the per-session ConfigMap holding the bundle tarball
	// (binaryData key "bundle.tar.gz").
	ConfigMapName string `json:"configMapName"`
}

SkillBundleMount names one staged skill bundle the sandbox pod unpacks. The ConfigMap (created by the AgentSession controller in the session namespace) holds the gzipped tar under "bundle.tar.gz"; the init container extracts it to /skills/<MountName>/.

func (*SkillBundleMount) DeepCopy

func (in *SkillBundleMount) DeepCopy() *SkillBundleMount

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SkillBundleMount.

func (*SkillBundleMount) DeepCopyInto

func (in *SkillBundleMount) DeepCopyInto(out *SkillBundleMount)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SkillBundleRef

type SkillBundleRef struct {
	// Digest is the content digest of the bundle archive.
	Digest string `json:"digest"`
	// CacheKey is the skillbundle.Store lookup key.
	CacheKey string `json:"cacheKey"`
}

SkillBundleRef points at the cached scripts/assets archive.

func (*SkillBundleRef) DeepCopy

func (in *SkillBundleRef) DeepCopy() *SkillBundleRef

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SkillBundleRef.

func (*SkillBundleRef) DeepCopyInto

func (in *SkillBundleRef) DeepCopyInto(out *SkillBundleRef)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SkillFrontmatter

type SkillFrontmatter struct {
	// Name is the frontmatter "name" (kebab-case); must match the canonical
	// skill directory basename.
	Name string `json:"name"`
	// License is the skill author's declared license identifier.
	// +optional
	License string `json:"license,omitempty"`
	// Compatibility is the author's declared compatibility statement.
	// +optional
	Compatibility string `json:"compatibility,omitempty"`
	// Metadata is the author's arbitrary key/value frontmatter, carried
	// verbatim and interpreted by nothing here.
	// +optional
	Metadata map[string]string `json:"metadata,omitempty"`
	// AllowedTools is the experimental space-separated pre-approved tool list.
	// RECORDED, not enforced: nothing gates a tool call on it.
	// +optional
	AllowedTools string `json:"allowedTools,omitempty"`
}

SkillFrontmatter holds the optional agentskills.io frontmatter fields.

func (*SkillFrontmatter) DeepCopy

func (in *SkillFrontmatter) DeepCopy() *SkillFrontmatter

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SkillFrontmatter.

func (*SkillFrontmatter) DeepCopyInto

func (in *SkillFrontmatter) DeepCopyInto(out *SkillFrontmatter)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SkillList

type SkillList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []Skill `json:"items"`
}

+kubebuilder:object:root=true

func (*SkillList) DeepCopy

func (in *SkillList) DeepCopy() *SkillList

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SkillList.

func (*SkillList) DeepCopyInto

func (in *SkillList) DeepCopyInto(out *SkillList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*SkillList) DeepCopyObject

func (in *SkillList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type SkillProvenance

type SkillProvenance struct {
	// RepoLocator is the normalized host/org/repo (no scheme, no .git).
	RepoLocator string `json:"repoLocator"`
	// Subpath is the skill's directory within the repo.
	Subpath string `json:"subpath"`
	// Ref is the requested ref (tag/branch/sha) as written in the source.
	// +optional
	Ref string `json:"ref,omitempty"`
	// ResolvedSHA is the commit the controller actually pulled.
	// +optional
	ResolvedSHA string `json:"resolvedSHA,omitempty"`
	// SourceName is the owning SkillSource CR name (empty for hand-authored).
	// +optional
	SourceName string `json:"sourceName,omitempty"`
}

SkillProvenance records where a materialized skill came from.

func (*SkillProvenance) DeepCopy

func (in *SkillProvenance) DeepCopy() *SkillProvenance

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SkillProvenance.

func (*SkillProvenance) DeepCopyInto

func (in *SkillProvenance) DeepCopyInto(out *SkillProvenance)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SkillRepoInstructions

type SkillRepoInstructions struct {
	// SourceFile is the repo-root filename the content came from
	// ("AGENTS.md" or "CLAUDE.md"), shown as provenance in the prompt.
	SourceFile string `json:"sourceFile"`
	// Content is the (possibly truncated) file contents.
	Content string `json:"content"`
	// Truncated is true when Content was capped at the 64 KiB injection limit.
	// +optional
	Truncated bool `json:"truncated,omitempty"`
}

SkillRepoInstructions is the repo-root agent-instructions file discovered alongside a skill's SKILL.md and injected into the agent prompt for any AgentClass that links the skill.

func (*SkillRepoInstructions) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SkillRepoInstructions.

func (*SkillRepoInstructions) DeepCopyInto

func (in *SkillRepoInstructions) DeepCopyInto(out *SkillRepoInstructions)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SkillSource

type SkillSource struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   SkillSourceSpec   `json:"spec,omitempty"`
	Status SkillSourceStatus `json:"status,omitempty"`
}

+kubebuilder:object:root=true +kubebuilder:resource:categories={authzed,spicebox},shortName=sksrc +kubebuilder:subresource:status +kubebuilder:printcolumn:name="Repo",type="string",JSONPath=".spec.repoURL" +kubebuilder:printcolumn:name="SHA",type="string",JSONPath=".status.resolvedSHA" +kubebuilder:printcolumn:name="Skills",type="integer",JSONPath=".status.discoveredSkills" +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"

SkillSource is a git repo that yields one or more Skills.

Namespaced. Reconciled by pkg/controllers/skillsource, which clones at spec.ref, discovers SKILL.md files under spec.subpath, caches each skill's bundle by content digest, and materializes owned Skill CRs. Fetch and discovery failures surface on the Ready condition and requeue on the sync interval, so a bad credential or ref degrades this source rather than crash-looping the operator.

func (*SkillSource) DeepCopy

func (in *SkillSource) DeepCopy() *SkillSource

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SkillSource.

func (*SkillSource) DeepCopyInto

func (in *SkillSource) DeepCopyInto(out *SkillSource)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*SkillSource) DeepCopyObject

func (in *SkillSource) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type SkillSourceAuth

type SkillSourceAuth struct {
	// AgentIdentity is the AgentIdentity CR (same namespace) holding the credential.
	AgentIdentity string `json:"agentIdentity"`
	// Credential is the AgentCredential name on that identity (e.g. a github_pat).
	Credential string `json:"credential"`
}

SkillSourceAuth references a credential on an AgentIdentity for cloning.

func (*SkillSourceAuth) DeepCopy

func (in *SkillSourceAuth) DeepCopy() *SkillSourceAuth

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SkillSourceAuth.

func (*SkillSourceAuth) DeepCopyInto

func (in *SkillSourceAuth) DeepCopyInto(out *SkillSourceAuth)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SkillSourceList

type SkillSourceList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []SkillSource `json:"items"`
}

+kubebuilder:object:root=true

func (*SkillSourceList) DeepCopy

func (in *SkillSourceList) DeepCopy() *SkillSourceList

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SkillSourceList.

func (*SkillSourceList) DeepCopyInto

func (in *SkillSourceList) DeepCopyInto(out *SkillSourceList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*SkillSourceList) DeepCopyObject

func (in *SkillSourceList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type SkillSourceSpec

type SkillSourceSpec struct {
	// RepoURL is the git repo to clone (e.g. "https://github.com/org/repo").
	RepoURL string `json:"repoURL"`

	// Ref is the branch, tag, or commit SHA to fetch. Empty = default branch
	// (a rolling source — warned downstream).
	// +optional
	Ref string `json:"ref,omitempty"`

	// Subpath restricts SKILL.md discovery to this subtree. Empty = whole repo.
	// +optional
	Subpath string `json:"subpath,omitempty"`

	// Auth references the credential used to clone a private repo.
	// +optional
	Auth *SkillSourceAuth `json:"auth,omitempty"`

	// Sync controls re-polling cadence.
	// +optional
	Sync SkillSourceSync `json:"sync,omitempty"`

	// DisableRepoInstructions, when true, stops this source from discovering and
	// attaching the repo-root agent-instructions file (AGENTS.md/CLAUDE.md) to
	// the skills it materializes. Default (false) = enabled.
	// +optional
	DisableRepoInstructions bool `json:"disableRepoInstructions,omitempty"`
}

SkillSourceSpec is the desired state of a SkillSource.

func (*SkillSourceSpec) DeepCopy

func (in *SkillSourceSpec) DeepCopy() *SkillSourceSpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SkillSourceSpec.

func (*SkillSourceSpec) DeepCopyInto

func (in *SkillSourceSpec) DeepCopyInto(out *SkillSourceSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SkillSourceStatus

type SkillSourceStatus struct {
	// ObservedGeneration is the spec generation this status reflects.
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`
	// ResolvedSHA is the commit the last successful sync pulled.
	// +optional
	ResolvedSHA string `json:"resolvedSHA,omitempty"`
	// DiscoveredSkills is the number of Skills materialized on the last sync.
	// +optional
	DiscoveredSkills int32 `json:"discoveredSkills,omitempty"`
	// +optional
	LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"`
	// DiscoveryProblems lists SKILL.md files found but skipped on the last sync
	// (invalid frontmatter, name/dir mismatch, parse/bundle error) WITH the
	// reason, so an operator can see why a skill did not materialize without
	// reading operator logs. Capped (excess summarized).
	// +optional
	DiscoveryProblems []string `json:"discoveryProblems,omitempty"`
	// +patchStrategy=merge
	// +patchMergeKey=type
	// +optional
	Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}

SkillSourceStatus is the observed state.

func (*SkillSourceStatus) DeepCopy

func (in *SkillSourceStatus) DeepCopy() *SkillSourceStatus

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SkillSourceStatus.

func (*SkillSourceStatus) DeepCopyInto

func (in *SkillSourceStatus) DeepCopyInto(out *SkillSourceStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SkillSourceSync

type SkillSourceSync struct {
	// Interval between re-polls. Zero → controller default (1h).
	// +optional
	Interval metav1.Duration `json:"interval,omitempty"`
}

SkillSourceSync controls the re-poll cadence.

func (*SkillSourceSync) DeepCopy

func (in *SkillSourceSync) DeepCopy() *SkillSourceSync

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SkillSourceSync.

func (*SkillSourceSync) DeepCopyInto

func (in *SkillSourceSync) DeepCopyInto(out *SkillSourceSync)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SkillSpec

type SkillSpec struct {
	// CanonicalName is the stable, collision-free identity, e.g.
	// "github.com/someorg/somerepo//skills/skillone@v1.2.0". All references
	// (AgentClass, settings ceilings) use this string.
	CanonicalName string `json:"canonicalName"`

	// DisplayName is an optional human label; defaults to the frontmatter name.
	// +optional
	DisplayName string `json:"displayName,omitempty"`

	// Description is the frontmatter description — the always-on metadata that
	// progressive disclosure injects into the agent prompt (~100 tokens).
	Description string `json:"description"`

	// Body is the SKILL.md markdown body, loaded on demand by the load_skill
	// tool. Stored inline, well within the etcd object budget.
	Body string `json:"body"`

	// Frontmatter carries the remaining SKILL.md frontmatter fields.
	// +optional
	Frontmatter SkillFrontmatter `json:"frontmatter,omitempty"`

	// Source records git provenance, written by the SkillSource controller.
	// Empty for hand-authored skills.
	// +optional
	Source *SkillProvenance `json:"source,omitempty"`

	// Bundle references the cached scripts/assets archive; nil means the skill
	// is instruction-only and nothing is staged into the sandbox for it.
	// +optional
	Bundle *SkillBundleRef `json:"bundle,omitempty"`

	// RepoInstructions is the repo-root agent-instructions file (AGENTS.md or
	// CLAUDE.md) discovered in the skill's source repo, injected into the agent's
	// system prompt when this skill is linked to an AgentClass. nil for
	// hand-authored skills (no repo) or when the source set
	// disableRepoInstructions.
	// +optional
	RepoInstructions *SkillRepoInstructions `json:"repoInstructions,omitempty"`
}

SkillSpec is the desired state of a Skill.

func (*SkillSpec) DeepCopy

func (in *SkillSpec) DeepCopy() *SkillSpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SkillSpec.

func (*SkillSpec) DeepCopyInto

func (in *SkillSpec) DeepCopyInto(out *SkillSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SkillStatus

type SkillStatus struct {
	// ObservedGeneration is the spec generation this status reflects.
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`

	// Pin is the recorded pin identity in the common PinRecord shape. Its
	// Strength ("frozen" | "named" | "unpinned") is the single source of the
	// skill's syntactic pin strength. nil when the canonical name did not
	// parse -- the Valid and Pinned conditions carry the reason.
	// +optional
	Pin *PinRecord `json:"pin,omitempty"`

	// Conditions carries Valid and Pinned.
	// +patchStrategy=merge
	// +patchMergeKey=type
	// +optional
	Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}

SkillStatus is the observed state of a Skill.

func (*SkillStatus) DeepCopy

func (in *SkillStatus) DeepCopy() *SkillStatus

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SkillStatus.

func (*SkillStatus) DeepCopyInto

func (in *SkillStatus) DeepCopyInto(out *SkillStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SkillTarget

type SkillTarget string

SkillTarget names the consumer of a skill. +kubebuilder:validation:Enum=agent;sandbox;both

const (
	// SkillTargetAgent is the default and the pre-existing behaviour: the
	// description goes in the system prompt and the body is available via
	// load_skill. Nothing is staged and no ConfigMap is created.
	SkillTargetAgent SkillTarget = "agent"
	// SkillTargetSandbox stages the skill on disk and suppresses it from the
	// outer agent's prompt entirely.
	SkillTargetSandbox SkillTarget = "sandbox"
	// SkillTargetBoth does both.
	SkillTargetBoth SkillTarget = "both"
)

type SlackChannelConfig

type SlackChannelConfig struct {
	// Mode is how channelsd connects to Slack.
	// +kubebuilder:validation:Enum=socket
	// +kubebuilder:default=socket
	Mode string `json:"mode,omitempty"`

	// BotUserID is auto-resolved by channelsd from auth.test on first
	// connect when empty.
	// +optional
	BotUserID string `json:"botUserId,omitempty"`

	// AppID is the Slack app this Channel's credentials belong to, recorded
	// when `oap channel create` provisioned the app itself. Empty for a Channel
	// whose tokens were pasted: nothing in the run knew which app they came
	// from, and no Slack API can be asked.
	//
	// Stable desired state, not an observation: it is written once from what
	// the run created, nothing recomputes it, and a byte-identical re-apply is
	// a no-op.
	// +optional
	AppID string `json:"appId,omitempty"`

	// OutputDefaults govern where the FIRST outbound message of a
	// session lands when the session was not triggered by an
	// inbound Slack message.
	// +optional
	OutputDefaults *SlackOutputDefaults `json:"outputDefaults,omitempty"`
}

SlackChannelConfig is the kind-specific config for a Channel of kind="slack": how channelsd connects, which bot user the credentials resolved to, which Slack app they came from, and where an unsolicited message lands. Its presence is also what the "exactly one kind block matches kind" validation checks for.

func (*SlackChannelConfig) DeepCopy

func (in *SlackChannelConfig) DeepCopy() *SlackChannelConfig

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SlackChannelConfig.

func (*SlackChannelConfig) DeepCopyInto

func (in *SlackChannelConfig) DeepCopyInto(out *SlackChannelConfig)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SlackOutputDefaults

type SlackOutputDefaults struct {
	// ChannelID is the Slack channel to post into (e.g. C0123ABC).
	ChannelID string `json:"channelId"`
	// ThreadStrategy controls whether each session gets its own
	// thread, appends to a fixed digest thread, or posts directly
	// without a thread anchor.
	// +kubebuilder:validation:Enum=new-thread-per-session;direct;static-thread
	// +kubebuilder:default=new-thread-per-session
	// +optional
	ThreadStrategy string `json:"threadStrategy,omitempty"`
	// StaticThreadTS is required when ThreadStrategy=static-thread.
	// +optional
	StaticThreadTS string `json:"staticThreadTs,omitempty"`
}

SlackOutputDefaults governs where the FIRST outbound message of a session lands when the session was not triggered by an inbound Slack message. Ignored for human-initiated sessions (which carry channel + thread_ts on the inbound).

func (*SlackOutputDefaults) DeepCopy

func (in *SlackOutputDefaults) DeepCopy() *SlackOutputDefaults

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SlackOutputDefaults.

func (*SlackOutputDefaults) DeepCopyInto

func (in *SlackOutputDefaults) DeepCopyInto(out *SlackOutputDefaults)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SlotPrecondition

type SlotPrecondition struct {
	// CEL is a boolean predicate over `facts` and `slot`.
	//
	// `facts` is addressed as facts.<provenance>.<name>, where provenance is
	// `envelope` (derived by platform code from a signed provider payload) or
	// `observed` (derived from a response to a call the agent shaped). They are
	// deliberately separate namespaces: an author gating on the first must not
	// silently receive the second.
	//
	// `slot` carries {resourceType, resourceID} of the candidate being decided.
	//
	// has() is NOT available over facts. It would let an author turn "not yet
	// known" into a decidable boolean, collapsing the tri-state.
	// +kubebuilder:validation:MinLength=1
	CEL string `json:"cel"`

	// UndeterminedHint is shown to the AGENT while some referenced fact has not
	// been recorded. It is the only party who can fix that, by making the call
	// that establishes it — so this should name that call.
	//
	// Required, and the reason is recorded on PermissionCheck.ResourceIDHint:
	// without one the agent sees a raw denial that reads as a system fault and
	// tells it not to bother retrying.
	// +kubebuilder:validation:MinLength=1
	UndeterminedHint string `json:"undeterminedHint"`

	// RefusalMessage explains, in plain language, what was refused and why.
	//
	// It cannot be derived from CEL, and it must not be confused with the
	// agent's own justification for a call — that is text the agent authored,
	// and this is a gate the agent is subject to.
	// +kubebuilder:validation:MinLength=1
	RefusalMessage string `json:"refusalMessage"`

	// Approvers routes the WAIVER card for a Refused verdict, as SpiceDB subject-set
	// expressions (e.g. "agentsession:{ns}/{name}#approve"). The card asks a RISK
	// question ("accept running untrusted code from a fork?"), which is not the same
	// as "who may grant read on this resource" — and the resource's owner set is
	// often empty for a userless session, which turns an appealable gate into an
	// unappealable crash. Unset defaults to the slot's resolved standing.
	// +optional
	// +listType=atomic
	Approvers []string `json:"approvers,omitempty"`
}

SlotPrecondition is a predicate over the facts a candidate instance arrived with, which must hold before that instance may occupy the slot.

Evaluation is TRI-STATE, and the third state is the point. Satisfied binds. Refused holds the slot closed and tells the agent why. UNDETERMINED — some referenced fact has not been recorded yet — also holds it closed, but is a different thing: nobody has answered the question, so the agent is told what to establish rather than that it was refused.

Undetermined never binds. That is what makes the verdict monotonic: because facts are write-once, a precondition can move undetermined→satisfied or undetermined→refused, never satisfied→refused. So a bound slot cannot be invalidated later, and the gate is order-independent — "call the tool early and you are gated, late and you are not" is a real bug class in dispatch-time gates and this shape rules it out.

func (*SlotPrecondition) DeepCopy

func (in *SlotPrecondition) DeepCopy() *SlotPrecondition

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SlotPrecondition.

func (*SlotPrecondition) DeepCopyInto

func (in *SlotPrecondition) DeepCopyInto(out *SlotPrecondition)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SpiceDBBootstrap

type SpiceDBBootstrap struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   SpiceDBBootstrapSpec   `json:"spec,omitempty"`
	Status SpiceDBBootstrapStatus `json:"status,omitempty"`
}

SpiceDBBootstrap declaratively seeds SpiceDB state: an optional schema fragment and/or a set of relationships TOUCHed into SpiceDB. At least one of spec.spicedbSchema.resources and spec.relationships MUST be non-empty.

Namespaced. It has no reconciler of its own -- pkg/controllers/guardian watches it from the AgentSessionGrants loop, composes its fragment into the unified schema alongside MCPServer fragments, and refcounts its relationships across every CR that claims them, so deleting one CR removes only the tuples no other CR still owns.

func (*SpiceDBBootstrap) DeepCopy

func (in *SpiceDBBootstrap) DeepCopy() *SpiceDBBootstrap

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceDBBootstrap.

func (*SpiceDBBootstrap) DeepCopyInto

func (in *SpiceDBBootstrap) DeepCopyInto(out *SpiceDBBootstrap)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*SpiceDBBootstrap) DeepCopyObject

func (in *SpiceDBBootstrap) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type SpiceDBBootstrapList

type SpiceDBBootstrapList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []SpiceDBBootstrap `json:"items"`
}

+kubebuilder:object:root=true

func (*SpiceDBBootstrapList) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceDBBootstrapList.

func (*SpiceDBBootstrapList) DeepCopyInto

func (in *SpiceDBBootstrapList) DeepCopyInto(out *SpiceDBBootstrapList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*SpiceDBBootstrapList) DeepCopyObject

func (in *SpiceDBBootstrapList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type SpiceDBBootstrapRelationship

type SpiceDBBootstrapRelationship struct {
	// Resource is the tuple's left-hand object.
	Resource SpiceDBObjectRef `json:"resource"`
	// Relation is the relation name declared on Resource's type.
	// +kubebuilder:validation:Pattern=`^[a-z][a-z0-9_]*$`
	Relation string `json:"relation"`
	// Subject is the tuple's right-hand subject.
	Subject SpiceDBSubjectRef `json:"subject"`
}

func (*SpiceDBBootstrapRelationship) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceDBBootstrapRelationship.

func (*SpiceDBBootstrapRelationship) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SpiceDBBootstrapSpec

type SpiceDBBootstrapSpec struct {
	// SpiceDBSchema is an optional schema fragment composed into the
	// unified SpiceDB schema by the guardian reconciler. Same shape and
	// rules as MCPServer.spec.spicedbSchema. Must not redeclare 'user'.
	// +optional
	SpiceDBSchema *SpiceDBSchemaFragment `json:"spicedbSchema,omitempty"`

	// Relationships is an optional list of tuples to TOUCH into SpiceDB.
	// With reclaimPolicy=Delete (default), the controller also DELETEs
	// tuples that were previously TOUCHed but have since dropped out
	// of the cluster-wide desired set (refcounted across all
	// SpiceDBBootstrap CRs).
	// +optional
	// +listType=atomic
	Relationships []SpiceDBBootstrapRelationship `json:"relationships,omitempty"`

	// ReclaimPolicy controls cleanup of tuples this CR previously
	// wrote. Delete (default) reconciles to desired state via
	// cluster-wide refcounting; Retain TOUCHes on each reconcile but
	// never DELETEs and does not participate in refcount.
	// +kubebuilder:validation:Enum=Delete;Retain
	// +kubebuilder:default=Delete
	// +optional
	ReclaimPolicy string `json:"reclaimPolicy,omitempty"`
}

+kubebuilder:validation:XValidation:rule="(has(self.spicedbSchema) && has(self.spicedbSchema.resources) && size(self.spicedbSchema.resources) > 0) || (has(self.relationships) && size(self.relationships) > 0)",message="spec must declare at least one of spicedbSchema.resources or relationships"

func (*SpiceDBBootstrapSpec) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceDBBootstrapSpec.

func (*SpiceDBBootstrapSpec) DeepCopyInto

func (in *SpiceDBBootstrapSpec) DeepCopyInto(out *SpiceDBBootstrapSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SpiceDBBootstrapStatus

type SpiceDBBootstrapStatus struct {
	// ObservedGeneration is the spec generation this status reflects.
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`
	// ObservedRelationships is the count of tuples in spec.relationships
	// that the controller successfully TOUCHed on the last reconcile.
	// +optional
	ObservedRelationships int32 `json:"observedRelationships,omitempty"`
	// Conditions carries Valid, SchemaIncluded and RelationshipsApplied.
	// +patchStrategy=merge
	// +patchMergeKey=type
	// +optional
	Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}

func (*SpiceDBBootstrapStatus) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceDBBootstrapStatus.

func (*SpiceDBBootstrapStatus) DeepCopyInto

func (in *SpiceDBBootstrapStatus) DeepCopyInto(out *SpiceDBBootstrapStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SpiceDBObjectRef

type SpiceDBObjectRef struct {
	// Type is the SpiceDB definition name.
	// +kubebuilder:validation:Pattern=`^[a-z][a-z0-9_]*(/[a-z][a-z0-9_]*)*$`
	Type string `json:"type"`
	// SpiceDB's object_id regex is `^(([a-zA-Z0-9/_|\-=+]{1,})|\*)$` — letters,
	// digits, and _ / | - = + . The pattern below admits all of those, plus '@'
	// and '.' so that an email can be carried verbatim when
	// subject.canonicalize=true; the controller's L2 validation rejects an '@'
	// in a user subject when canonicalize is false.
	//
	// '=' is load-bearing beyond email. It is the escape character
	// authz.spicedb_escape uses to fit a free-form value — a repository URL —
	// into an object id INJECTIVELY, which is what lets an approval name one
	// repository without also covering another. A bootstrap that seeds a grant
	// on such a value has to be able to express the id the check will compute,
	// so a pattern narrower than SpiceDB's own rejects legal ids.
	// +kubebuilder:validation:Pattern=`^[a-zA-Z0-9_/.@=|+-]+$`
	// +kubebuilder:validation:MaxLength=1024
	ID string `json:"id"`
}

func (*SpiceDBObjectRef) DeepCopy

func (in *SpiceDBObjectRef) DeepCopy() *SpiceDBObjectRef

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceDBObjectRef.

func (*SpiceDBObjectRef) DeepCopyInto

func (in *SpiceDBObjectRef) DeepCopyInto(out *SpiceDBObjectRef)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SpiceDBPermission

type SpiceDBPermission struct {
	// Name is the permission name.
	Name string `json:"name"`
	// Expr is the right-hand side of `permission <name> = …`, pasted verbatim.
	Expr string `json:"expr"`

	// Title is the phrase a human reads on an approval card instead of the
	// permission's handle. `perm:push:git_repo` is a WIRE FORMAT; asking
	// somebody to decide on it makes the decision slower and worse exactly
	// where care matters most.
	//
	// Declared here because this is where the permission itself is declared, so
	// one declaration serves every channel and the CLI. Absent is fine and
	// common: the card detokenizes the handle instead, which shows no plumbing
	// and invents no English.
	// +optional
	// +kubebuilder:validation:MaxLength=120
	Title string `json:"title,omitempty"`

	// PlanningNote is one line of guidance the agent reads while DECLARING a
	// plan, rendered next to the handles it may declare.
	//
	// The knowledge that shapes a good plan is toolkit-specific, so it lives
	// with the toolkit rather than in the runner's generic prompt. git_repo
	// splits its permissions across two instances — read/write key on the
	// checked-out copy, fetch/push on the remote URL — so a phase declaring
	// `write` without `read` can change files it cannot open, and the first
	// read interrupts the human with an amendment the plan should have carried
	// from the start.
	//
	// It shapes what the agent DECLARES, never what anything grants: a note is
	// prose the model reads at plan time, and no authorization decision reads
	// it. A phase that ignores its guidance still gets the gate it earned.
	// +optional
	// +kubebuilder:validation:MaxLength=300
	PlanningNote string `json:"planningNote,omitempty"`
}

SpiceDBPermission is one permission on a resource. Expr is the right-hand-side of the SpiceDB schema's `permission <name> = ...` statement; the composer pastes it verbatim.

func (*SpiceDBPermission) DeepCopy

func (in *SpiceDBPermission) DeepCopy() *SpiceDBPermission

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceDBPermission.

func (*SpiceDBPermission) DeepCopyInto

func (in *SpiceDBPermission) DeepCopyInto(out *SpiceDBPermission)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SpiceDBRelation

type SpiceDBRelation struct {
	// Name is the relation name as it appears in the composed schema.
	Name string `json:"name"`
	// SubjectType must be a bare resource-type name (e.g. "user",
	// "hubspot_owner") declared in this same SpiceDBSchema or the
	// implicit "user" type. Wildcards ("user:*") are expressed via
	// the separate Wildcard field. Subject-relation forms
	// ("team#member") are NOT representable. Exactly one
	// SubjectType per Relation — SpiceDB unions like
	// `relation viewer: user | team#member` cannot be modeled.
	SubjectType string `json:"subjectType"`
	// Wildcard, when true, makes the relation accept ANY subject
	// of SubjectType — emitted as `relation <name>: <subjectType>:*`
	// in the composed schema. Use sparingly: a wildcard relation
	// effectively grants the underlying-resource permission to
	// every subject of that type, so any per-call gating must come
	// from a different layer (e.g. `stateImpact: external` on the
	// tool, which routes every call through the approval flow
	// regardless of the SpiceDB Check result).
	// +optional
	Wildcard bool `json:"wildcard,omitempty"`
}

SpiceDBRelation is one relation on a resource.

func (*SpiceDBRelation) DeepCopy

func (in *SpiceDBRelation) DeepCopy() *SpiceDBRelation

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceDBRelation.

func (*SpiceDBRelation) DeepCopyInto

func (in *SpiceDBRelation) DeepCopyInto(out *SpiceDBRelation)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SpiceDBResource

type SpiceDBResource struct {
	// Name is the SpiceDB definition name; the composer dedupes on it.
	Name string `json:"name"`
	// Relations are the resource's relations, emitted verbatim.
	Relations []SpiceDBRelation `json:"relations,omitempty"`
	// Permissions are the resource's computed permissions.
	Permissions []SpiceDBPermission `json:"permissions,omitempty"`

	// Standing declares whether SpiceDB is AUTHORITATIVE for this resource type —
	// that is, whether an approver can be expected to already hold a permission on
	// an instance of it. REQUIRED, with no default.
	//
	//   - required      SpiceDB governs who may approve. The approver pool is
	//                   ApproverPermission on the named instance, and an EMPTY pool
	//                   is a final refusal — nobody can approve what nobody governs.
	//   - session-only  No local permission governs approval for this type, so the
	//                   session's own approvers decide and their decision IS the
	//                   authority. Everything downstream is unchanged: the grant is
	//                   still written, still expiring, still session-scoped and
	//                   revocable, and the tool-call Check still runs.
	//
	// There is deliberately NO default. A default is a hole you open by omission:
	// defaulting to session-only silently widens who may approve a type somebody
	// forgot to classify, and defaulting to required makes a forge-governed type
	// permanently unbindable because nothing writes a SpiceDB tuple for every git
	// remote. Neither failure announces itself, so the author states the answer.
	// Same reasoning as AP_CLUSTER_KIND, which also refuses to default.
	//
	// Choosing is a question about the RESOURCE, not about convenience: does a
	// permission on this instance already say who may speak for it? A CRM company
	// with seeded owner tuples: yes, `required`. A git remote whose permissions
	// live at the forge: no, `session-only`.
	//
	// A cluster or namespace admin can force any type to `required` with
	// SettingsLimits.RequireStandingFor, which no fragment may widen past.
	// +kubebuilder:validation:Enum=session-only;required
	// +kubebuilder:validation:Required
	Standing string `json:"standing"`

	// ApproverPermission names the permission an approver must hold on an
	// instance for their approval to count. Required when Standing is `required`,
	// and must be empty when it is `session-only` — a type nothing governs has no
	// permission to name, and accepting one there would read as governance that
	// is never consulted.
	//
	// Declared rather than assumed. This was hardcoded to "owner" at four call
	// sites, which silently made two very different types look identical to the
	// approval router: crm_company's `owner` is a real computed permission backed
	// by seeded tuples, while git_repo's `owner` is a bare relation declared only
	// so the checks are answerable and never populated by anything. The router
	// could not tell governance from an artifact of schema shape, so a push to a
	// git remote resolved to an empty owner-set and became unapprovable forever.
	//
	// Naming it also lifts the assumption that the approving permission is called
	// "owner": a type may route approval through `maintainer`, `admin`, or any
	// permission its own schema defines.
	// +optional
	ApproverPermission string `json:"approverPermission,omitempty"`
	// Display declares how an INSTANCE of this resource type presents on an
	// approval card — an icon, and how to turn its raw value (a URL, typically)
	// into a short label. Absent means the card falls back to the wire type
	// name, exactly as before this field existed.
	// +optional
	Display *SpiceDBResourceDisplay `json:"display,omitempty"`
}

SpiceDBResource is one SpiceDB resource definition contributed by a fragment.

func (*SpiceDBResource) DeepCopy

func (in *SpiceDBResource) DeepCopy() *SpiceDBResource

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceDBResource.

func (*SpiceDBResource) DeepCopyInto

func (in *SpiceDBResource) DeepCopyInto(out *SpiceDBResource)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (SpiceDBResource) StandingDeclared

func (r SpiceDBResource) StandingDeclared() bool

StandingDeclared reports whether this resource states a standing at all.

Replaces an EffectiveStanding helper that resolved unset to session-only. That default was the hole: a type nobody classified silently became one whose approval the session's own approvers could grant, and nothing anywhere said so. Callers must now handle "undeclared" as its own outcome and refuse, which is why this returns a bool rather than picking a side — a fail-closed guess of `required` would be just as wrong in the other direction, making a forge-governed type permanently unbindable.

func (SpiceDBResource) ValidateStanding

func (r SpiceDBResource) ValidateStanding() error

ValidateStanding reports why this resource's standing declaration is unusable, or nil when it is complete.

Two rules, both fail-closed. A missing standing is refused rather than defaulted (see the Standing doc). And ApproverPermission must be present exactly when it is consulted: required-without-one leaves the router with no pool to resolve, and session-only-with-one reads as governance that is never checked — the kind of line a reviewer trusts and the code ignores.

type SpiceDBResourceDisplay

type SpiceDBResourceDisplay struct {
	// Name is the human name for this resource TYPE — "Git repository", never
	// the wire handle "git_repo". Shown when no per-instance label can be
	// derived (Label is "none", unset, or the deriver produces nothing for
	// this instance's value).
	// +optional
	// +kubebuilder:validation:MaxLength=60
	Name string `json:"name,omitempty"`

	// Icon names a glyph from a CLOSED, code-defined registry — never a URL
	// and never free-form. A vendor-specific type (github_repo) may name the
	// vendor's own mark; a host-agnostic type (git_repo) must name a generic
	// one, because claiming a vendor mark would lie the moment an instance
	// points somewhere else (a self-hosted remote). An unrecognized name
	// renders no icon — never a fallback image, never a guess.
	// +optional
	// +kubebuilder:validation:MaxLength=40
	Icon string `json:"icon,omitempty"`

	// Label names a deriver from a CLOSED, code-defined set that turns an
	// instance's raw value into a short display label — "url_path",
	// "b64url_path", "last_segment", or "none" (the set registered in
	// pkg/authz/plangate's labelDerivers, which is the authority; this list is
	// prose and has drifted from it once). Not a template and not a regex: nothing here
	// can produce a label the code did not author, and the deriver never sees
	// anything an agent did not itself write into the plan (the instance
	// value), so a declaration can shorten a value's presentation but can
	// never fabricate one. An unrecognized name derives nothing, and the card
	// falls back to Name.
	// +optional
	// +kubebuilder:validation:MaxLength=40
	Label string `json:"label,omitempty"`
}

SpiceDBResourceDisplay is how a resource TYPE says its instances should present on an approval card. It never carries an instance's own value — only how to derive and label it, so an agent-authored URL can never smuggle in a rendering directive.

func (*SpiceDBResourceDisplay) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceDBResourceDisplay.

func (*SpiceDBResourceDisplay) DeepCopyInto

func (in *SpiceDBResourceDisplay) DeepCopyInto(out *SpiceDBResourceDisplay)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SpiceDBSchemaFragment

type SpiceDBSchemaFragment struct {
	// Resources lists structured SpiceDB definitions contributed by this
	// fragment. Each resource is emitted with its declared relations and
	// permissions; the composer dedupes by name across fragments.
	// +optional
	Resources []SpiceDBResource `json:"resources,omitempty"`

	// RawZed is appended verbatim to the composed schema after Resources
	// are emitted. Use this for SpiceDB schema features the structured
	// form can't represent: subject-relations (e.g. `slack_user#user`)
	// and unions (e.g. `user | agent`). The composer does not parse or
	// validate RawZed beyond schema-load time; malformed text fails the
	// SpiceDB WriteSchema call.
	// +optional
	RawZed string `json:"rawZed,omitempty"`
}

SpiceDBSchemaFragment is a SpiceDB schema fragment contributed by an MCPServer or a channel kind.

func (*SpiceDBSchemaFragment) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceDBSchemaFragment.

func (*SpiceDBSchemaFragment) DeepCopyInto

func (in *SpiceDBSchemaFragment) DeepCopyInto(out *SpiceDBSchemaFragment)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SpiceDBSubjectRef

type SpiceDBSubjectRef struct {
	// Type is the SpiceDB definition name of the subject.
	// +kubebuilder:validation:Pattern=`^[a-z][a-z0-9_]*(/[a-z][a-z0-9_]*)*$`
	Type string `json:"type"`
	// ID is required UNLESS Wildcard=true (which forces the wire-form
	// SpiceDB subject id to "*", the wildcard match-any value).
	// +optional
	// +kubebuilder:validation:Pattern=`^[a-zA-Z0-9_/.@=|+-]+$`
	// +kubebuilder:validation:MaxLength=1024
	ID string `json:"id,omitempty"`
	// Relation, when set, makes this a subject-set reference like
	// "group:eng#member". Mutually exclusive with canonicalize=true
	// and wildcard=true.
	// +optional
	// +kubebuilder:validation:Pattern=`^[a-z][a-z0-9_]*$`
	Relation string `json:"relation,omitempty"`
	// Canonicalize, when true, runs ID through identity.EmailReference(id).Canonical()
	// before writing. Only valid when Type=="user" and ID is an email.
	// Mutually exclusive with wildcard=true.
	// +optional
	Canonicalize bool `json:"canonicalize,omitempty"`
	// Wildcard, when true, writes the SpiceDB wildcard subject
	// (`<type>:*`) — every subject of Type matches. Requires the
	// corresponding relation on the target resource to itself be
	// wildcard-typed in the schema (see
	// SpiceDBRelation.Wildcard). Mutually exclusive with
	// Relation and Canonicalize. When true, ID is ignored.
	// +optional
	Wildcard bool `json:"wildcard,omitempty"`
}

func (*SpiceDBSubjectRef) DeepCopy

func (in *SpiceDBSubjectRef) DeepCopy() *SpiceDBSubjectRef

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceDBSubjectRef.

func (*SpiceDBSubjectRef) DeepCopyInto

func (in *SpiceDBSubjectRef) DeepCopyInto(out *SpiceDBSubjectRef)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SpiceboxClass

type SpiceboxClass struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   SpiceboxClassSpec   `json:"spec,omitempty"`
	Status SpiceboxClassStatus `json:"status,omitempty"`
}

+kubebuilder:object:root=true +kubebuilder:resource:scope=Cluster,categories={authzed,spicebox},shortName=sbxcls +kubebuilder:subresource:status +genclient +genclient:nonNamespaced

SpiceboxClass is the template a tool sandbox is cut from: image, resources, network mode, mounts, and the toolspecs and toolchains available inside it. A SpiceboxSession instantiates one.

Cluster-scoped. Reconciled by pkg/controllers/spiceboxclass, which validates the spec against the selected sandbox backend (pkg/tools/sandboxkinds) and the referenced toolchains, surfaces Valid, and maintains the pre-warm pool. A class naming a missing or Valid=False toolchain goes Valid=False rather than starting a sandbox without the compiler it promised.

func (*SpiceboxClass) DeepCopy

func (in *SpiceboxClass) DeepCopy() *SpiceboxClass

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceboxClass.

func (*SpiceboxClass) DeepCopyInto

func (in *SpiceboxClass) DeepCopyInto(out *SpiceboxClass)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*SpiceboxClass) DeepCopyObject

func (in *SpiceboxClass) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type SpiceboxClassList

type SpiceboxClassList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []SpiceboxClass `json:"items"`
}

+kubebuilder:object:root=true

func (*SpiceboxClassList) DeepCopy

func (in *SpiceboxClassList) DeepCopy() *SpiceboxClassList

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceboxClassList.

func (*SpiceboxClassList) DeepCopyInto

func (in *SpiceboxClassList) DeepCopyInto(out *SpiceboxClassList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*SpiceboxClassList) DeepCopyObject

func (in *SpiceboxClassList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type SpiceboxClassSpec

type SpiceboxClassSpec struct {
	// Image is the OCI image used for sandbox pods of this class. Optional: when
	// empty, the operator's configured default sandbox image is used
	// (--sandbox-image, which `oap install` registry-qualifies for the cluster, the
	// same way it does the runner image). Set it only to pin a custom sandbox
	// image — most classes (and every identity-setup bundle) should leave it
	// unset so the image is portable across local and cloud clusters.
	// +optional
	Image string `json:"image,omitempty"`

	// RuntimeClassName selects the Kubernetes RuntimeClass. Defaults to "kata-fc" when unset.
	// +optional
	RuntimeClassName *string `json:"runtimeClassName,omitempty"`

	// Resources are the sandbox container's CPU, memory and storage limits.
	Resources SpiceboxResources `json:"resources"`

	// Network is the sandbox's egress posture.
	// +optional
	Network SpiceboxNetwork `json:"network,omitempty"`

	// Mounts are extra read-only volumes composed into every sandbox pod.
	// +optional
	Mounts []SpiceboxMount `json:"mounts,omitempty"`

	// Tools are the executables a ToolCall may dispatch inside this sandbox.
	// +optional
	Tools []SpiceboxTool `json:"tools,omitempty"`

	// Toolspecs lists SpiceboxToolspec CRs whose validation rules apply to
	// ToolCalls in sessions of this class. At least one Toolspec must cover
	// each tool in spec.tools, otherwise the class is marked Valid=False.
	// +optional
	Toolspecs []ToolspecRef `json:"toolspecs,omitempty"`

	// Toolchains names SpiceboxToolchain CRs whose payloads are composed into
	// every pod of this class as read-only overlays under /opt/ap-toolchains.
	// Each named toolchain must exist and be Valid=True or the session fails
	// closed. Order is irrelevant: the operator sorts and deduplicates.
	// +optional
	Toolchains []string `json:"toolchains,omitempty"`

	// SessionDefaults are the lifetime defaults sessions of this class inherit.
	// +optional
	SessionDefaults SessionDefaults `json:"sessionDefaults,omitempty"`

	// EnvDefaults is merged into every bundle pod's spicebox container as
	// unconditional environment variables. Used to point process-level env
	// at PrivateVolumes mount paths (e.g. GIT_DIR=/var/ap-git/.git so git's
	// metadata + url-rewrite credentials never land on the shared workspace).
	// Keys that exactly match an auth-injected env var (those declared
	// sensitive in any builtin toolkit's env.allowed) are rejected at class
	// validation.
	// +optional
	EnvDefaults map[string]string `json:"envDefaults,omitempty"`

	// PrivateVolumes are emptyDir volumes mounted only on this class's
	// bundle pods. Use these to hold per-bundle state that must NOT be
	// visible to other bundles sharing the workspace (e.g. .git directory
	// for credential isolation).
	// +optional
	PrivateVolumes []PrivateVolume `json:"privateVolumes,omitempty"`

	// Sandbox selects the backend that runs this class's sandboxes. Optional:
	// an unset kind defaults to "pod", the built-in Kubernetes Pod backend.
	//
	// The pod-vocabulary fields above (Image, RuntimeClassName, Resources,
	// Mounts, PrivateVolumes) remain the shared vocabulary every pod-shaped
	// backend consumes; this stanza carries only the discriminator and
	// backend-specific extras.
	// +optional
	Sandbox SandboxBackend `json:"sandbox,omitempty"`
}

func (*SpiceboxClassSpec) DeepCopy

func (in *SpiceboxClassSpec) DeepCopy() *SpiceboxClassSpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceboxClassSpec.

func (*SpiceboxClassSpec) DeepCopyInto

func (in *SpiceboxClassSpec) DeepCopyInto(out *SpiceboxClassSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SpiceboxClassStatus

type SpiceboxClassStatus struct {
	// ObservedGeneration is the spec generation this status reflects.
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`

	// Conditions carries Valid; see SpiceboxClassConditionValid.
	// +patchStrategy=merge
	// +patchMergeKey=type
	// +optional
	Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`

	// ToolspecCoverage maps each tool name in spec.tools to the names of the
	// SpiceboxToolspecs (from spec.toolspecs) that authorize it. A tool with
	// no covering Toolspec causes Valid=False.
	// +optional
	ToolspecCoverage map[string][]string `json:"toolspecCoverage,omitempty"`

	// ResolvedToolchains is the class's toolchain names resolved to concrete
	// mounts, recorded so an operator can answer "what is this class actually
	// running?" without reading controller logs.
	//
	// OBSERVATION ONLY. Nothing consumes it for correctness — sessions resolve
	// fresh at bind and freeze their own copy. A stale value here can therefore
	// never mis-shape a session; at worst it mis-shapes the warm pool, which
	// self-heals when the next reconcile re-points the template.
	// +optional
	ResolvedToolchains []ToolchainMount `json:"resolvedToolchains,omitempty"`

	// ToolchainSetDigest is the digest of the resolved set. Computed by the SAME
	// function the session path uses, so this and
	// SpiceboxSession.status.toolchainSetDigest are directly comparable by eye —
	// which is how an operator confirms a session's pod matches the warm pool's
	// shape.
	// +optional
	ToolchainSetDigest string `json:"toolchainSetDigest,omitempty"`

	// ToolchainResolutionMessage explains why resolution failed, when it did.
	// Pre-warming is skipped in that case but the class stays Valid: class
	// validity must not depend on the toolchain catalog, or an unrelated
	// toolchain edit would flip classes red that never asked for a pool. This
	// field is what keeps that decision from making the failure invisible.
	// +optional
	ToolchainResolutionMessage string `json:"toolchainResolutionMessage,omitempty"`
}

func (*SpiceboxClassStatus) DeepCopy

func (in *SpiceboxClassStatus) DeepCopy() *SpiceboxClassStatus

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceboxClassStatus.

func (*SpiceboxClassStatus) DeepCopyInto

func (in *SpiceboxClassStatus) DeepCopyInto(out *SpiceboxClassStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SpiceboxMount

type SpiceboxMount struct {
	// Name is the pod volume name.
	Name string `json:"name"`
	// Source is where the mounted content comes from.
	Source MountSource `json:"source"`
	// MountPath is the absolute path inside the sandbox container.
	MountPath string `json:"mountPath"`
	// Format describes what the source content is. Defaults to raw, which is
	// a direct read-only volume mount and is what every mount predating this
	// field does.
	// +kubebuilder:default=raw
	// +optional
	Format MountFormat `json:"format,omitempty"`
	// Digest is the expected content identity ("sha256:..."), verified before
	// the content is used. Empty skips verification.
	// +optional
	Digest string `json:"digest,omitempty"`
}

func (*SpiceboxMount) DeepCopy

func (in *SpiceboxMount) DeepCopy() *SpiceboxMount

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceboxMount.

func (*SpiceboxMount) DeepCopyInto

func (in *SpiceboxMount) DeepCopyInto(out *SpiceboxMount)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SpiceboxNetwork

type SpiceboxNetwork struct {
	// Mode is the egress posture: none denies all, allowlist permits DNS plus
	// coarse outbound TCP.
	// +kubebuilder:default=none
	Mode NetworkMode `json:"mode,omitempty"`
	// AllowedHosts is the intended hostname allowlist. RECORDED, not enforced:
	// stock NetworkPolicy cannot express hostnames, so a DNS-aware policy
	// controller must consume it to narrow beyond Mode's L3/L4 rules.
	// +optional
	AllowedHosts []string `json:"allowedHosts,omitempty"`
}

func (*SpiceboxNetwork) DeepCopy

func (in *SpiceboxNetwork) DeepCopy() *SpiceboxNetwork

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceboxNetwork.

func (*SpiceboxNetwork) DeepCopyInto

func (in *SpiceboxNetwork) DeepCopyInto(out *SpiceboxNetwork)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SpiceboxResources

type SpiceboxResources struct {
	// CPU is the sandbox container's CPU limit.
	CPU resource.Quantity `json:"cpu"`
	// Memory is the container's memory limit, and the real cap on the
	// memory-backed /tmp and /work — see TmpSize.
	Memory resource.Quantity `json:"memory"`
	// EphemeralStorage is the container's disk-backed storage limit.
	EphemeralStorage resource.Quantity `json:"ephemeralStorage"`
	// PidsLimit caps processes inside the sandbox, bounding fork bombs.
	// +kubebuilder:default=64
	// +kubebuilder:validation:Minimum=1
	PidsLimit int64 `json:"pidsLimit,omitempty"`

	// TmpSize is the size limit of the pod's /tmp. Defaults to 50Mi when unset.
	//
	// /tmp and /work are memory-backed (tmpfs) emptyDirs. The size here is a CAP,
	// not a reservation: pages are charged against this class's Memory limit only
	// as they are written. So raising TmpSize alone buys nothing — the memory
	// limit binds first, and the pod is OOM-killed rather than told ENOSPC. Raise
	// Memory alongside it.
	//
	// Prefer pointing a tool's scratch at the disk-backed /var/ap-cache over
	// growing tmpfs at all: TMPDIR, GOCACHE, PNPM_HOME and the XDG roots already
	// resolve there when a toolchain is attached, so /tmp holds only what a tool
	// writes in defiance of them.
	// +optional
	TmpSize *resource.Quantity `json:"tmpSize,omitempty"`

	// WorkSize is the size limit of the pod's /work. Defaults to 100Mi when
	// unset. Memory-backed; see TmpSize.
	// +optional
	WorkSize *resource.Quantity `json:"workSize,omitempty"`

	// CacheSize is the size limit of the disk-backed /var/ap-cache volume the
	// toolchain overlay mounts. Defaults to 2Gi when unset; only takes effect
	// when a toolchain is attached (no toolchains, no cache volume).
	//
	// Unlike TmpSize/WorkSize this is DISK-backed, not memory-backed: it holds
	// the large consumers a toolchain redirects here — GOCACHE, GOMODCACHE,
	// PNPM_HOME, the XDG roots and TMPDIR. The pod builder raises the
	// container's ephemeral-storage limit by exactly this value, so raising it
	// is charged against node disk, not Memory. Raise it for a build box that
	// resolves a large dependency graph (e.g. reviewing a Go dependency-bump
	// PR): the module + build caches easily exceed 2Gi and the kubelet evicts
	// the pod when the volume passes its SizeLimit.
	// +optional
	CacheSize *resource.Quantity `json:"cacheSize,omitempty"`
}

func (*SpiceboxResources) DeepCopy

func (in *SpiceboxResources) DeepCopy() *SpiceboxResources

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceboxResources.

func (*SpiceboxResources) DeepCopyInto

func (in *SpiceboxResources) DeepCopyInto(out *SpiceboxResources)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (SpiceboxResources) EffectiveCacheSize

func (r SpiceboxResources) EffectiveCacheSize() resource.Quantity

EffectiveCacheSize returns CacheSize, or the default when unset.

func (SpiceboxResources) EffectiveTmpSize

func (r SpiceboxResources) EffectiveTmpSize() resource.Quantity

EffectiveTmpSize returns TmpSize, or the default when unset.

func (SpiceboxResources) EffectiveWorkSize

func (r SpiceboxResources) EffectiveWorkSize() resource.Quantity

EffectiveWorkSize returns WorkSize, or the default when unset.

type SpiceboxSession

type SpiceboxSession struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   SpiceboxSessionSpec   `json:"spec,omitempty"`
	Status SpiceboxSessionStatus `json:"status,omitempty"`
}

+kubebuilder:object:root=true +kubebuilder:resource:scope=Namespaced,categories={authzed,spicebox},shortName=sbxses +kubebuilder:subresource:status +kubebuilder:printcolumn:name="Class",type="string",JSONPath=".spec.class" +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +genclient

SpiceboxSession is one live sandbox instantiated from a SpiceboxClass: the long-lived pod that this session's ToolCalls execute inside, plus its workspace, idle TTL and default env.

Namespaced. Reconciled by pkg/controllers/spiceboxsession, which owns the sandbox pod through the registered backend (pkg/tools/sandboxkinds -- pod or agent-sandbox). An AgentSession that runs sandbox tools creates and owns one of these; it is also usable on its own, without an agent.

func (*SpiceboxSession) DeepCopy

func (in *SpiceboxSession) DeepCopy() *SpiceboxSession

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceboxSession.

func (*SpiceboxSession) DeepCopyInto

func (in *SpiceboxSession) DeepCopyInto(out *SpiceboxSession)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*SpiceboxSession) DeepCopyObject

func (in *SpiceboxSession) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type SpiceboxSessionList

type SpiceboxSessionList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []SpiceboxSession `json:"items"`
}

+kubebuilder:object:root=true

func (*SpiceboxSessionList) DeepCopy

func (in *SpiceboxSessionList) DeepCopy() *SpiceboxSessionList

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceboxSessionList.

func (*SpiceboxSessionList) DeepCopyInto

func (in *SpiceboxSessionList) DeepCopyInto(out *SpiceboxSessionList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*SpiceboxSessionList) DeepCopyObject

func (in *SpiceboxSessionList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type SpiceboxSessionSpec

type SpiceboxSessionSpec struct {
	// Class names the cluster-scoped SpiceboxClass to instantiate.
	Class string `json:"class"`

	// Agent names an AgentIdentity in the same namespace whose credential
	// bindings are used as the default for ToolCalls in this session.
	// Optional; if unset and ToolCall.spec.agent is also unset, calls run with
	// no AgentIdentity-injected env.
	// +optional
	Agent string `json:"agent,omitempty"`

	// IdleTTL overrides the class's idle timeout for this session.
	// +optional
	IdleTTL *metav1.Duration `json:"idleTTL,omitempty"`

	// MaxDuration overrides the class's wall-clock lifetime cap.
	// +optional
	MaxDuration *metav1.Duration `json:"maxDuration,omitempty"`

	// Workspace selects whether the sandbox shares a workspace volume or gets
	// its own.
	// +optional
	Workspace WorkspaceConfig `json:"workspace,omitempty"`

	// DefaultEnv is non-secret environment merged into every ToolCall in this
	// session, at lower precedence than ToolCall.spec.env and any
	// AgentIdentity-injected env. Used by the AgentSession controller to stamp
	// static tool-hardening env (e.g. GIT_CONFIG_GLOBAL) onto bundle sessions.
	// +optional
	DefaultEnv map[string]string `json:"defaultEnv,omitempty"`

	// Toolspecs optionally narrows the Class's toolspec set for this session.
	// If set, every entry must appear in the resolved Class's spec.toolspecs.
	// If unset, the session inherits the full class set.
	// +optional
	Toolspecs []ToolspecRef `json:"toolspecs,omitempty"`

	// Mounts are the data mounts this session's pod materializes, resolved by
	// the AgentSession controller. Concatenated with the class's authored
	// mounts by the pod builder, which does not care which provenance a mount
	// came from.
	// +optional
	Mounts []SpiceboxMount `json:"mounts,omitempty"`

	// SkillBundles is DEPRECATED; superseded by Mounts. Retained so a session
	// created before mounts existed and re-hydrated afterwards still builds:
	// sessions are wakeable, so a removal would break in-flight work mid-upgrade.
	// Removal is Task 11 of the sandbox-data-mounts plan, not an aspiration.
	// +optional
	SkillBundles []SkillBundleMount `json:"skillBundles,omitempty"`

	// Sandbox overrides the referenced SpiceboxClass's sandbox backend for this
	// session. Stamped by the AgentSession reconciler with the tier-resolved
	// decision; unset for a directly-created session, which then uses the
	// class's own setting. Folded into status.resolvedClass at bind time.
	// +optional
	Sandbox *SandboxBackend `json:"sandbox,omitempty"`

	// ToolConfig is a snapshot of the owning AgentClass.spec.config, stamped by
	// the AgentSession controller at session creation. The toolcall controller
	// exposes it to toolspec constraint CEL as the `config` root. Snapshotted
	// (not read live) so a mid-session class edit cannot silently change the
	// authority a running session was admitted under.
	// +optional
	// +kubebuilder:pruning:PreserveUnknownFields
	ToolConfig map[string]apiextensionsv1.JSON `json:"toolConfig,omitempty"`
}

func (*SpiceboxSessionSpec) DeepCopy

func (in *SpiceboxSessionSpec) DeepCopy() *SpiceboxSessionSpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceboxSessionSpec.

func (*SpiceboxSessionSpec) DeepCopyInto

func (in *SpiceboxSessionSpec) DeepCopyInto(out *SpiceboxSessionSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SpiceboxSessionStatus

type SpiceboxSessionStatus struct {
	// ObservedGeneration is the spec generation this status reflects.
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`

	// PodName is the sandbox Pod managed by this session.
	// +optional
	PodName string `json:"podName,omitempty"`

	// LastActivityAt is the most recent time a ToolCall completed against this session.
	// +optional
	LastActivityAt *metav1.Time `json:"lastActivityAt,omitempty"`

	// CallCount is a monotonic counter of completed ToolCalls.
	// +optional
	CallCount int64 `json:"callCount,omitempty"`

	// ResolvedClass is a snapshot of the SpiceboxClass spec at bind time.
	// Tool catalogs are frozen for the session lifetime, even if the class changes.
	// +optional
	ResolvedClass *SpiceboxClassSpec `json:"resolvedClass,omitempty"`

	// ResolvedToolchains is the frozen, self-contained resolution of the
	// class's spec.toolchains, captured at first bind alongside ResolvedClass.
	// The pod builder reads only this — it never re-reads SpiceboxToolchain CRs,
	// so a catalog edit mid-session cannot change a running pod.
	// +optional
	ResolvedToolchains []ToolchainMount `json:"resolvedToolchains,omitempty"`

	// ToolchainSetDigest is a stable hex sha256 over the (name, image) pairs of
	// ResolvedToolchains, order-independent and image-sensitive. Recorded
	// alongside the freeze and carried into the toolchain-audit "resolved" entry
	// so an investigation can match a session's pod to what was attested.
	// +optional
	ToolchainSetDigest string `json:"toolchainSetDigest,omitempty"`

	// ResolvedAgent is a snapshot of spec.agent at session bind time. Frozen
	// for the session lifetime so a mid-flight Session edit doesn't change
	// identity for an in-flight session.
	// +optional
	ResolvedAgent string `json:"resolvedAgent,omitempty"`

	// EffectiveToolspecs is the resolved set of SpiceboxToolspec names in use
	// for this session — equal to spec.toolspecs if non-empty, else the
	// resolved class's spec.toolspecs. Recomputed on session and class changes.
	// +optional
	EffectiveToolspecs []string `json:"effectiveToolspecs,omitempty"`

	// Sandbox is the durable handle to this session's sandbox, written by the
	// SpiceboxSession controller once the backend has created it. It is the
	// only link the ToolCall controller has to the sandbox, and it must
	// survive an operator restart, so it lives here rather than in memory.
	// +optional
	Sandbox *SandboxHandle `json:"sandbox,omitempty"`

	// Conditions carries Ready, Progressing, Failed and Terminated.
	// +patchStrategy=merge
	// +patchMergeKey=type
	// +optional
	Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}

func (*SpiceboxSessionStatus) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceboxSessionStatus.

func (*SpiceboxSessionStatus) DeepCopyInto

func (in *SpiceboxSessionStatus) DeepCopyInto(out *SpiceboxSessionStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SpiceboxTool

type SpiceboxTool struct {
	// Name is how a ToolCall refers to this tool.
	Name string `json:"name"`
	// Command is the absolute path + argv[0] of the tool binary.
	Command []string `json:"command"`
	// DefaultArgs are prepended to every invocation's arguments.
	// +optional
	DefaultArgs []string `json:"defaultArgs,omitempty"`
	// ExpectedDuration picks the dispatch mode: short runs synchronously, long
	// and persistent stream.
	// +kubebuilder:default=short
	ExpectedDuration ExpectedDuration `json:"expectedDuration,omitempty"`
}

func (*SpiceboxTool) DeepCopy

func (in *SpiceboxTool) DeepCopy() *SpiceboxTool

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceboxTool.

func (*SpiceboxTool) DeepCopyInto

func (in *SpiceboxTool) DeepCopyInto(out *SpiceboxTool)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SpiceboxToolchain

type SpiceboxToolchain struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   SpiceboxToolchainSpec   `json:"spec,omitempty"`
	Status SpiceboxToolchainStatus `json:"status,omitempty"`
}

+kubebuilder:object:root=true +kubebuilder:resource:scope=Cluster,categories={authzed,spicebox},shortName=sbxtc +kubebuilder:subresource:status +genclient +genclient:nonNamespaced

SpiceboxToolchain is a language or tooling overlay a sandbox can mount -- a compiler, an SDK, a linter -- delivered by a registered backend and mounted at ToolchainRootPath/<name>, with the PATH entries and env it needs.

Cluster-scoped. Reconciled by pkg/controllers/spiceboxtoolchain, which validates the spec and then asks the named delivery Kind to validate its own fields. A Valid=False toolchain is refused at session-bind time, so a broken catalog entry fails the session closed instead of quietly dropping a compiler out of the sandbox.

func (*SpiceboxToolchain) DeepCopy

func (in *SpiceboxToolchain) DeepCopy() *SpiceboxToolchain

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceboxToolchain.

func (*SpiceboxToolchain) DeepCopyInto

func (in *SpiceboxToolchain) DeepCopyInto(out *SpiceboxToolchain)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*SpiceboxToolchain) DeepCopyObject

func (in *SpiceboxToolchain) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type SpiceboxToolchainList

type SpiceboxToolchainList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []SpiceboxToolchain `json:"items"`
}

+kubebuilder:object:root=true

func (*SpiceboxToolchainList) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceboxToolchainList.

func (*SpiceboxToolchainList) DeepCopyInto

func (in *SpiceboxToolchainList) DeepCopyInto(out *SpiceboxToolchainList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*SpiceboxToolchainList) DeepCopyObject

func (in *SpiceboxToolchainList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type SpiceboxToolchainSpec

type SpiceboxToolchainSpec struct {
	// Description is human-facing text surfaced by `oap`.
	// +optional
	Description string `json:"description,omitempty"`

	// Source describes where the payload comes from. Kind selects a registered
	// delivery backend (pkg/tools/toolchain/kinds/registry).
	Source ToolchainSource `json:"source"`

	// Bin lists PATH entries relative to this toolchain's root. Each becomes
	// <ToolchainRootPath>/<name>/<entry> on the sandbox container's PATH.
	// +optional
	Bin []string `json:"bin,omitempty"`

	// Env is the process environment this toolchain needs. Values may reference
	// exactly two template variables, spelled with surrounding spaces:
	// "{{ .Root }}" (this toolchain's root) and "{{ .Cache }}"
	// (ToolchainCachePath). Any other template text is rejected at validation.
	// +optional
	Env map[string]string `json:"env,omitempty"`

	// Provides are opaque capability labels (e.g. "go", "gopls") surfaced to
	// operators. Advisory — nothing selects on them.
	// +optional
	Provides []string `json:"provides,omitempty"`

	// Detect are workspace-relative marker files (e.g. "go.mod") that hint this
	// toolchain is applicable. Advisory only — nothing acts on them.
	// +optional
	Detect []string `json:"detect,omitempty"`

	// SizeBytes is the payload's on-disk usage (measured via `du -sk * 1024`),
	// NOT apparent size. The kubelet enforces emptyDir SizeLimit against allocated
	// blocks; under-estimating this field evicts the pod mid-copy. The operator
	// derives both the overlay emptyDir SizeLimit and the pod's ephemeral-storage
	// limit from this value.
	// +kubebuilder:validation:Minimum=1
	SizeBytes int64 `json:"sizeBytes"`
}

func (*SpiceboxToolchainSpec) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceboxToolchainSpec.

func (*SpiceboxToolchainSpec) DeepCopyInto

func (in *SpiceboxToolchainSpec) DeepCopyInto(out *SpiceboxToolchainSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SpiceboxToolchainStatus

type SpiceboxToolchainStatus struct {
	// ObservedGeneration is the spec generation this status reflects.
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`
	// Conditions carries Valid; see SpiceboxToolchainConditionValid.
	// +patchStrategy=merge
	// +patchMergeKey=type
	// +optional
	Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}

func (*SpiceboxToolchainStatus) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceboxToolchainStatus.

func (*SpiceboxToolchainStatus) DeepCopyInto

func (in *SpiceboxToolchainStatus) DeepCopyInto(out *SpiceboxToolchainStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SpiceboxToolkit

type SpiceboxToolkit struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   SpiceboxToolkitSpec   `json:"spec,omitempty"`
	Status SpiceboxToolkitStatus `json:"status,omitempty"`
}

+kubebuilder:object:root=true +kubebuilder:resource:scope=Cluster,categories={authzed,spicebox},shortName=sbxtk +kubebuilder:subresource:status +genclient +genclient:nonNamespaced

SpiceboxToolkit is the machine-readable description of one CLI a sandbox tool can be built from: its subcommands, flags, env, parser config and default per-tool authz policy. A SpiceboxToolspec narrows a toolkit down to the specific tool an agent is actually handed.

Cluster-scoped. Reconciled by pkg/controllers/spiceboxtoolkit, which detects collisions with the built-in toolkits and stamps a cli-kind pin baseline onto status.

func (*SpiceboxToolkit) DeepCopy

func (in *SpiceboxToolkit) DeepCopy() *SpiceboxToolkit

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceboxToolkit.

func (*SpiceboxToolkit) DeepCopyInto

func (in *SpiceboxToolkit) DeepCopyInto(out *SpiceboxToolkit)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*SpiceboxToolkit) DeepCopyObject

func (in *SpiceboxToolkit) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type SpiceboxToolkitList

type SpiceboxToolkitList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []SpiceboxToolkit `json:"items"`
}

+kubebuilder:object:root=true

func (*SpiceboxToolkitList) DeepCopy

func (in *SpiceboxToolkitList) DeepCopy() *SpiceboxToolkitList

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceboxToolkitList.

func (*SpiceboxToolkitList) DeepCopyInto

func (in *SpiceboxToolkitList) DeepCopyInto(out *SpiceboxToolkitList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*SpiceboxToolkitList) DeepCopyObject

func (in *SpiceboxToolkitList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type SpiceboxToolkitSpec

type SpiceboxToolkitSpec struct {
	// Name is the toolkit identifier a SpiceboxToolspec references.
	Name string `json:"name"`
	// Version is the upstream CLI version this description was written against.
	Version string `json:"version,omitempty"`
	// ToolkitRevision is the description's own revision, bumped when the
	// declared surface changes.
	ToolkitRevision string `json:"toolkitRevision"`
	// Target identifies the binary this toolkit drives.
	Target ToolkitTarget `json:"target"`
	// Docs is prose about the CLI, surfaced to authors.
	Docs string `json:"docs,omitempty"`
	// Parser selects how an invocation's argv is validated and bound.
	Parser ToolkitParserConfig `json:"parser"`
	// Env describes the environment variables the binary reads. It documents
	// and wires them — notably which are sensitive — and is not a filter on the
	// environment the binary actually receives. Mirrors
	// pkg/tools/toolspec/toolkit.Toolkit.Env.
	Env ToolkitEnv `json:"env"`

	// EnvDefaults is static, non-secret environment this toolkit sets on every
	// invocation of its binary, so a CLI's own hardening travels with the
	// description of that CLI. It is a floor: the runner stamps it onto each
	// ToolCall's spec.env, and the toolcall controller demotes any key still
	// carrying this declared value below SpiceboxSession.spec.defaultEnv and
	// SpiceboxClass.spec.envDefaults, so an operator setting the same key keeps
	// winning.
	//
	// Values are never secret. A key that Env.Allowed marks sensitive is
	// rejected: those come from an AgentIdentity credential, and a static
	// default for the same key would either fail the call
	// (ToolCallEnvShadowsAgent) or replace the credential with a fixed string.
	// Mirrors pkg/tools/toolspec/toolkit.Toolkit.EnvDefaults.
	// +optional
	EnvDefaults map[string]string `json:"envDefaults,omitempty"`

	// GlobalFlags are flags accepted before any subcommand.
	GlobalFlags []ToolkitFlag `json:"globalFlags,omitempty"`
	// Subcommands are the invocations this toolkit describes; anything absent
	// here is not expressible as a tool.
	Subcommands []ToolkitSubcommand `json:"subcommands"`

	// Permission is the toolkit-wide default per-tool authz policy.
	// Subcommands may override individually via their own Permission
	// block. Mirrors pkg/tools/toolspec/toolkit.Toolkit.Permission.
	// +optional
	Permission *authz.Permission `json:"permission,omitempty"`

	// StreamFormat names a registered parser in pkg/tools/toolkitstream/registry.
	// When non-empty AND a subcommand has Mode stream or interactive, the
	// sandbox tool routes the bridge's stdout through the named parser before
	// publishing KindToolSessionEvent envelopes. Empty (default) preserves
	// the raw KindToolSessionDelta path. Mirrors
	// pkg/tools/toolspec/toolkit.Toolkit.StreamFormat. Example: "claude-stream-json".
	// +optional
	StreamFormat string `json:"streamFormat,omitempty"`

	// CostTreatment declares how this toolkit's provider-reported cost counts
	// toward session spend. "metered" (default) is real billed money and counts;
	// "subscription-equivalent" is a flat-rate estimate (e.g. a Claude Max OAuth
	// run) that is recorded and shown but EXCLUDED from the session total, so it
	// cannot inflate spend or trip cost alerts. Mirrors
	// pkg/tools/toolspec/toolkit.Toolkit.CostTreatment.
	// +kubebuilder:validation:Enum=metered;subscription-equivalent
	// +optional
	CostTreatment string `json:"costTreatment,omitempty"`

	// SiteURL is the toolkit-backed service's user-facing homepage.
	// identityd discovers a favicon from this URL for the credential
	// rows backed by this toolkit's sensitive env vars (e.g., a
	// github-token row resolves through the github toolkit). Optional;
	// unset renders the deterministic generated fallback icon.
	//
	// Must be http or https; admission rejects other schemes.
	// +optional
	SiteURL string `json:"siteURL,omitempty"`

	// SpiceDBSchema declares the SpiceDB resource types this toolkit's
	// permission checks name, so the toolkit ships the definitions its own
	// checks depend on. Mirrors pkg/toolspec/toolkit.Toolkit.SpiceDBSchema; the
	// guardian composes it alongside MCPServer and SpiceDBBootstrap fragments.
	// +optional
	SpiceDBSchema *SpiceDBSchemaFragment `json:"spicedbSchema,omitempty"`
}

SpiceboxToolkitSpec mirrors pkg/tools/toolspec/toolkit.Toolkit.

The shape is re-declared rather than embedded so the CRD carries explicit kubebuilder annotations and controller-gen emits a stable DeepCopy. Conversion uses a JSON round-trip (ToToolkit), so this field set must stay byte-compatible with toolkit.Toolkit's JSON shape.

func (*SpiceboxToolkitSpec) DeepCopy

func (in *SpiceboxToolkitSpec) DeepCopy() *SpiceboxToolkitSpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceboxToolkitSpec.

func (*SpiceboxToolkitSpec) DeepCopyInto

func (in *SpiceboxToolkitSpec) DeepCopyInto(out *SpiceboxToolkitSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*SpiceboxToolkitSpec) ToToolkit

func (s *SpiceboxToolkitSpec) ToToolkit() (*toolkit.Toolkit, error)

ToToolkit converts the CR spec to the toolspec library type via JSON round-trip. The CR spec field set is byte-compatible with toolkit.Toolkit's JSON encoding.

type SpiceboxToolkitStatus

type SpiceboxToolkitStatus struct {
	// ObservedGeneration is the spec generation this status reflects.
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`

	// Pin is the recorded pin baseline in the common PinRecord shape:
	// Digest = pinnedBinaryHash assertion (if set), Version = versionRange
	// (audit metadata). Strength reflects the declared ref: frozen iff
	// PinnedBinaryHash is set, named iff VersionRange is set, else unpinned.
	// +optional
	Pin *PinRecord `json:"pin,omitempty"`

	// Conditions carries Valid; see SpiceboxToolkitConditionValid.
	// +patchStrategy=merge
	// +patchMergeKey=type
	// +optional
	Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}

func (*SpiceboxToolkitStatus) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceboxToolkitStatus.

func (*SpiceboxToolkitStatus) DeepCopyInto

func (in *SpiceboxToolkitStatus) DeepCopyInto(out *SpiceboxToolkitStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SpiceboxToolspec

type SpiceboxToolspec struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   SpiceboxToolspecSpec   `json:"spec,omitempty"`
	Status SpiceboxToolspecStatus `json:"status,omitempty"`
}

+kubebuilder:object:root=true +kubebuilder:resource:scope=Cluster,categories={authzed,spicebox},shortName=sbxtsp +kubebuilder:subresource:status +genclient +genclient:nonNamespaced

SpiceboxToolspec is one sandbox tool as the agent sees it: a SpiceboxToolkit reference narrowed to an allowed subcommand set, with CEL argument constraints, denied effects and an authz policy.

Cluster-scoped. Reconciled by pkg/controllers/spiceboxtoolspec, which resolves the referenced toolkit, compiles every CEL expression, and surfaces Valid=True/False. A Valid=False toolspec is skipped at ToolCall validation time rather than executed unchecked.

func (*SpiceboxToolspec) DeepCopy

func (in *SpiceboxToolspec) DeepCopy() *SpiceboxToolspec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceboxToolspec.

func (*SpiceboxToolspec) DeepCopyInto

func (in *SpiceboxToolspec) DeepCopyInto(out *SpiceboxToolspec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*SpiceboxToolspec) DeepCopyObject

func (in *SpiceboxToolspec) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type SpiceboxToolspecList

type SpiceboxToolspecList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []SpiceboxToolspec `json:"items"`
}

+kubebuilder:object:root=true

func (*SpiceboxToolspecList) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceboxToolspecList.

func (*SpiceboxToolspecList) DeepCopyInto

func (in *SpiceboxToolspecList) DeepCopyInto(out *SpiceboxToolspecList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*SpiceboxToolspecList) DeepCopyObject

func (in *SpiceboxToolspecList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type SpiceboxToolspecSpec

type SpiceboxToolspecSpec struct {
	// Name is the LLM-facing tool name this spec produces.
	Name string `json:"name,omitempty"`
	// Version is the spec author's version of this declaration.
	Version string `json:"version,omitempty"`
	// Intent is the one-line purpose shown to authors and reviewers.
	Intent string `json:"intent,omitempty"`
	// Toolkit is the SpiceboxToolkit this spec narrows.
	Toolkit ToolspecToolkitRef `json:"toolkit"`
	// Require declares preconditions the sandbox must satisfy.
	Require ToolspecRequire `json:"require,omitempty"`
	// AllowSubcommands is the allowlist of toolkit subcommand paths; anything
	// absent is denied.
	AllowSubcommands []string `json:"allowSubcommands"`
	// Deny refuses calls by declared effect even inside AllowSubcommands.
	Deny ToolspecDeny `json:"deny,omitempty"`
	// Allow widens what a call may reach beyond the defaults.
	Allow ToolspecAllow `json:"allow,omitempty"`
	// Exceptions conditionally relax named Deny rules.
	Exceptions []ToolspecException `json:"exceptions,omitempty"`
	// Constraints are CEL predicates every call's arguments must satisfy.
	Constraints []ToolspecConstraint `json:"constraints,omitempty"`
	// Sensitive names arguments and env to redact from logs and prompts.
	Sensitive ToolspecSensitive `json:"sensitive,omitempty"`
	// SecretOutput, when non-nil, declares that this tool produces a single
	// secret value that the runner must capture out-of-band. Mirrors
	// pkg/tools/toolspec/spec.SecretOutputSpec; json tags must match exactly so the
	// ToSpec JSON round-trip carries the value through.
	// +optional
	SecretOutput *ToolspecSecretOutput `json:"secretOutput,omitempty"`
	// WritesRelationships are JIT SpiceDB relationship-write blocks the
	// runner evaluates after a SUCCESSFUL tool call. Reuses the MCPServer
	// per-tool type and CEL semantics (when/forEach/tuple), with two extra
	// bindings on the sandbox path: `session` ("<ns>/<name>" of the
	// AgentSession) and `args.argv` (the raw argv list the agent supplied).
	// For toolspecs that also declare secretOutput, `result` carries only
	// {success: bool} — the captured secret value is never exposed to CEL.
	// +optional
	WritesRelationships []MCPServerRelationshipWrite `json:"writesRelationships,omitempty"`

	// Observes declares facts this tool's result asserts about specific
	// resource instances, co-derived with the subjects they are about.
	// Evaluated after a SUCCESSFUL call. Reuses the MCPServer per-tool type
	// and CEL semantics; see MCPServerTool.Observes.
	// +optional
	// +listType=atomic
	Observes []ObservesBlock `json:"observes,omitempty"`
}

SpiceboxToolspecSpec mirrors pkg/tools/toolspec/spec.Spec.

func (*SpiceboxToolspecSpec) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceboxToolspecSpec.

func (*SpiceboxToolspecSpec) DeepCopyInto

func (in *SpiceboxToolspecSpec) DeepCopyInto(out *SpiceboxToolspecSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*SpiceboxToolspecSpec) ToSpec

func (s *SpiceboxToolspecSpec) ToSpec() (*spec.Spec, error)

ToSpec converts the CR spec to the toolspec library type via JSON round-trip.

type SpiceboxToolspecStatus

type SpiceboxToolspecStatus struct {
	// ObservedGeneration is the spec generation this status reflects.
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`
	// ResolvedToolkit names the SpiceboxToolkit CR (or "<builtin>") that this
	// Toolspec's spec.toolkit reference resolved to.
	// +optional
	ResolvedToolkit string `json:"resolvedToolkit,omitempty"`
	// Conditions carries Valid; see SpiceboxToolspecConditionValid.
	// +patchStrategy=merge
	// +patchMergeKey=type
	// +optional
	Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}

func (*SpiceboxToolspecStatus) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpiceboxToolspecStatus.

func (*SpiceboxToolspecStatus) DeepCopyInto

func (in *SpiceboxToolspecStatus) DeepCopyInto(out *SpiceboxToolspecStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type StaticCredentialSource

type StaticCredentialSource struct {
	// SecretRef points at a Secret in the same namespace as the AgentIdentity.
	SecretRef SecretKeyRef `json:"secretRef"`
}

func (*StaticCredentialSource) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StaticCredentialSource.

func (*StaticCredentialSource) DeepCopyInto

func (in *StaticCredentialSource) DeepCopyInto(out *StaticCredentialSource)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type StatusFieldOwner

type StatusFieldOwner string

StatusFieldOwner identifies the component responsible for writing a given AgentSession status field. This file is the ONE authoritative declaration of the partition: agentstatus.WriteOwned uses the ownership maps to determine which conditions belong to which writer, and TestEveryStatusFieldHasExactlyOneOwner fails if a newly-added status field is left unclassified — so ownership cannot silently drift.

const (
	OwnerOperator  StatusFieldOwner = "agentsession"           // operator agentsession controller
	OwnerApprovals StatusFieldOwner = "agentsession-approvals" // channelsd approval pipeline
)

type StreamingEndpoint

type StreamingEndpoint struct {
	// Available is false until the gateway is ready to serve this call's stream.
	Available bool `json:"available"`
	// GatewayEndpoint is the URL the runner connects to for live output.
	GatewayEndpoint string `json:"gatewayEndpoint,omitempty"`
}

func (*StreamingEndpoint) DeepCopy

func (in *StreamingEndpoint) DeepCopy() *StreamingEndpoint

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StreamingEndpoint.

func (*StreamingEndpoint) DeepCopyInto

func (in *StreamingEndpoint) DeepCopyInto(out *StreamingEndpoint)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SubagentRequest

type SubagentRequest struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   SubagentRequestSpec   `json:"spec,omitempty"`
	Status SubagentRequestStatus `json:"status,omitempty"`
}

SubagentRequest is a runner's request to delegate a task to a child session.

The runner creates this CR and polls it; the OPERATOR validates roster membership, identity monotonicity and budget, then creates the child AgentSession and writes the lineage tuples. The runner deliberately cannot create an AgentSession directly: the party whose behaviour delegation constrains must not also be the party that authorizes it — the same reasoning that puts forensic-hold trippers in the operator.

+kubebuilder:object:root=true +kubebuilder:subresource:status +kubebuilder:resource:shortName=subreq +kubebuilder:printcolumn:name="Parent",type=string,JSONPath=`.spec.parent.name` +kubebuilder:printcolumn:name="Class",type=string,JSONPath=`.spec.class` +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase` +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`

func OwningSubagentRequest

func OwningSubagentRequest(ctx context.Context, r client.Reader, sess *AgentSession) (*SubagentRequest, error)

OwningSubagentRequest returns the SubagentRequest whose reconcile created sess, or (nil, nil) when sess was not delegated at all.

It is how a component holding a delegated CHILD reaches the terms of the delegation — its mode, and the exchange budget counted down from it. Those live on the request rather than on the child on purpose: the child's runner can patch its own AgentSession, and a ceiling the constrained party can rewrite is not a ceiling. The request carries none of that risk — no runner Role grants any write on subagentrequests beyond `create`.

The link is the child's controller OwnerReference, which the SubagentRequest controller stamps in the same Create that sets spec.parent — never a name derived from a naming convention, which would resolve to whatever happens to sit at that name.

Fail-closed, and the two "no request" outcomes are kept apart:

  • spec.parent nil -> (nil, nil). An ordinary session, delegated by nobody. A caller gating on delegation terms has nothing to gate on and proceeds.
  • spec.parent set but no SubagentRequest owner -> an ERROR. Only that controller sets spec.parent, and it sets both in one Create, so this shape means the link was broken after the fact. Reading it as "not delegated" would drop every term of the delegation silently.

func (*SubagentRequest) DeepCopy

func (in *SubagentRequest) DeepCopy() *SubagentRequest

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubagentRequest.

func (*SubagentRequest) DeepCopyInto

func (in *SubagentRequest) DeepCopyInto(out *SubagentRequest)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*SubagentRequest) DeepCopyObject

func (in *SubagentRequest) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

func (*SubagentRequest) EffectiveMode

func (r *SubagentRequest) EffectiveMode() string

EffectiveMode returns the declared mode, resolving empty to single_turn. Callers must use this rather than reading Spec.Mode directly: an empty string reaching a switch that only knows the three constants is how a default becomes a panic or, worse, a silent widening.

func (*SubagentRequest) ExchangeBudget

func (r *SubagentRequest) ExchangeBudget() (limit int64, bounded bool)

ExchangeBudget reports how many questions a child delegated in this mode may ask its parent, and whether the mode bounds them at all. It is one half of what Spec.Mode DECLARES, and this method is the only place that half is interpreted — the SubagentRequest controller resolves it once, where an exchange is honoured, and records the answer on SubagentRequestStatus.ExchangesRemaining. No other component reads Spec.Mode to answer this question; encoding it a second time (in provisioning, in the runner, in a settings tier) would put one attack-surface decision in two places that can disagree.

single_turn -> (0, true)  — headless: no channel, so no question to carry.
                            reconcileParentExchange never reaches the
                            budget for one, because it discards a
                            single_turn child's parentExchange outright.
task        -> (1, true)  — "a bounded question", per Spec.Mode's own doc.
chat        -> (0, false) — a full back-and-forth; nothing here bounds it.
attended    -> (0, false) — also a full back-and-forth, but with a human
                            rather than the parent agent; nothing here
                            bounds that conversation either.

An unrecognized mode is bounded at ZERO, not unbounded. Reconcile denies one long before this is reached (SubagentModeUnrecognized), so this is the second, fail-closed answer for a stored object that reached etcd another way.

func (*SubagentRequest) IsTerminal

func (r *SubagentRequest) IsTerminal() bool

IsTerminal reports whether the request has reached a final phase.

func (*SubagentRequest) PermitsHumanInitiative

func (r *SubagentRequest) PermitsHumanInitiative() bool

PermitsHumanInitiative reports whether a PERSON may open a turn into a child delegated in this mode. It is the other half of what Spec.Mode declares, and like ExchangeBudget it is the only place that half is interpreted.

chat and attended say yes. A task child "may ask; it may not be driven" — its whole declaration is that untrusted content moves it exactly one bounded step — so admitting a human-authored turn would hand it the very surface the mode exists to withhold. single_turn is headless and has no surface to be addressed on at all; it answers no here too, so a channel that somehow resolved to one refuses rather than falling through. attended is human-directed by definition — its whole point is a person driving the child's conversation — so it answers yes alongside chat.

It says nothing about the child's PARENT. A parent answering its child is not a person opening a turn, and the inbound path admits that reply without reaching this method — see the pipeline's initiative gate.

type SubagentRequestList

type SubagentRequestList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []SubagentRequest `json:"items"`
}

+kubebuilder:object:root=true

func (*SubagentRequestList) DeepCopy

func (in *SubagentRequestList) DeepCopy() *SubagentRequestList

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubagentRequestList.

func (*SubagentRequestList) DeepCopyInto

func (in *SubagentRequestList) DeepCopyInto(out *SubagentRequestList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*SubagentRequestList) DeepCopyObject

func (in *SubagentRequestList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type SubagentRequestSpec

type SubagentRequestSpec struct {
	// Parent is the delegating session, set by the runner from its own
	// identity.
	//
	// The claim is verified at admission: the validating webhook at
	// pkg/controllers/webhooks/subagentrequest compares this field against the
	// authenticated principal, which for a per-session runner is
	// `system:serviceaccount:<ns>:<session>-runner-sa` and therefore names the
	// one session it may delegate as. A runner claiming a different session —
	// to reach a larger pooled budget, a wider roster, or a userPassthrough
	// parent it could then inherit from — is refused before the object is
	// persisted.
	//
	// The webhook is failurePolicy: Fail and bounded by matchConditions to
	// runner ServiceAccounts. Principals outside that set are governed by
	// ordinary RBAC: only the per-session runner Role grants an unrestricted
	// namespace-scoped create to an untrusted principal. The operator's own
	// aggregate ClusterRole also carries the verb, cluster-wide — but only so
	// it can grant it onto each runner Role in turn (Kubernetes' RBAC
	// escalation prevention requires a granter to already hold what it
	// grants); the operator is trusted infrastructure, is structurally exempt
	// from the webhook via matchConditions, and never creates one of these
	// itself.
	Parent NamespacedRef `json:"parent"`

	// Class is the AgentClass to delegate to. Must appear in the parent class's
	// spec.subagents roster.
	// +kubebuilder:validation:MinLength=1
	Class string `json:"class"`

	// Task is the parent's own instruction, used verbatim as the child's
	// spec.prompt. In this track it is the whole payload; data slots (tag
	// references) arrive in Track 2.
	// +kubebuilder:validation:MinLength=1
	// +kubebuilder:validation:MaxLength=8192
	Task string `json:"task"`

	// Mode declares how much surface the child gets, and is an ATTACK-SURFACE
	// declaration rather than a UX preference. A single_turn child fed
	// laundered untrusted content does one bounded thing and returns; a
	// conversational child can be driven by that content across many turns,
	// and has a two-way channel to be driven through. Widen it deliberately.
	//
	// The surface is agent-to-agent. A conversational child is bound to a
	// Channel of kind "agent" whose counterparty is its PARENT session — no
	// person is on the other end of it, and nobody outside the delegation tree
	// can address the child at all. What still reaches a human is a
	// human-directed prompt — an approval, a credential request, an identity
	// choice — which is routed past every binding no human reads, up the
	// lineage, to the nearest ancestor channel someone is actually watching.
	//
	//   single_turn — no channel at all: spec.prompt in, status.result out.
	//                 The default, and the only mode that parallelizes, since
	//                 nothing can arrive mid-flight.
	//   task        — a channel for clarification: the child asks its parent a
	//                 bounded question and continues.
	//   chat        — a channel for a full back-and-forth with the parent.
	//   attended    — a channel for a full back-and-forth with a HUMAN rather
	//                 than the parent agent. Registered here as a legal value;
	//                 the routing, binding and provisioning that give it
	//                 behavior distinct from chat land in a later change.
	//
	// task and chat get identical PROVISIONING — the same `agent` Channel, built
	// by the same branch — and differ in two enforced ways, each read from this
	// field at the point the question is asked and nowhere else:
	//
	//   - the exchange budget. A task child's clarification is bounded to
	//     ExchangeBudget() questions; a chat child's is unbounded. The
	//     SubagentRequest controller counts it down on status.exchangesRemaining
	//     as it honours each one, and refuses the exchange after that.
	//   - the initiative gate. A task child may ASK but may not be DRIVEN: a
	//     human cannot open a turn into one (channelsd's inbound pipeline
	//     refuses it), while a chat child may be addressed freely. The parent's
	//     own reply is not a human opening a turn and is always admitted.
	//
	// Empty means single_turn. The default is opt-in-shaped on purpose,
	// matching every other capability here that widens what untrusted content
	// can drive.
	//
	// This field is a REQUEST, not a grant. The delegating agent names a mode
	// in its delegate call and it lands here; the ceiling is the PARENT
	// class's AgentClassSpec.SubagentModes entry for spec.class, and the
	// SubagentRequest controller denies anything wider than that entry allows.
	// A denied mode is never downgraded to single_turn and run anyway: a
	// silent downgrade would make the feature look broken rather than refused
	// and would hide the roster misconfiguration behind it indefinitely.
	// +kubebuilder:validation:Enum=single_turn;task;chat;attended
	// +optional
	Mode string `json:"mode,omitempty"`

	// DataSlots hands the child specific information BY REFERENCE: each entry
	// names one of the child's declared slots and the pt-tag whose content it
	// may read.
	//
	// By reference is the design, not an encoding choice. There is nowhere in
	// a slot to put content, so a parent structurally cannot launder data into
	// one — the architectural separation of instruction and data channels the
	// handoff rests on. Task carries the parent's own WORDS; this carries
	// references to data it is not repeating.
	//
	// A REQUEST, like Mode. The controller binds only the tags the parent can
	// itself read (`pt_tag:<T>#access@agentsession:<parent>`), so the parent's
	// judgment selects within an envelope it cannot widen; anything else is
	// refused and reported rather than silently dropped, because a child
	// waiting on data that will never arrive is worse than one told it did not
	// get it.
	// +optional
	// +kubebuilder:validation:MaxItems=32
	DataSlots []DataSlotRequest `json:"dataSlots,omitempty"`
}

func (*SubagentRequestSpec) DeepCopy

func (in *SubagentRequestSpec) DeepCopy() *SubagentRequestSpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubagentRequestSpec.

func (*SubagentRequestSpec) DeepCopyInto

func (in *SubagentRequestSpec) DeepCopyInto(out *SubagentRequestSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type SubagentRequestStatus

type SubagentRequestStatus struct {
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`

	// Phase is Pending, Running, AwaitingParent, Succeeded, Failed or Denied.
	//
	// AwaitingParent is the one NON-terminal phase a caller must act on rather
	// than keep waiting through: the child has asked its delegating parent a
	// question (Message) and has parked until the answer arrives. It is what
	// makes a conversational delegation resumable — the parent's delegate call
	// returns there instead of blocking to a terminal phase, answers on its
	// next turn, and the child stays alive across the gap.
	// +optional
	Phase string `json:"phase,omitempty"`

	// ChildRef names the created child session, empty until it exists.
	// +optional
	ChildRef *NamespacedRef `json:"childRef,omitempty"`

	// BoundDataSlots are the slots actually bound onto the child — the subset
	// of spec.dataSlots the parent had standing to delegate.
	// +optional
	BoundDataSlots []DataSlotRequest `json:"boundDataSlots,omitempty"`

	// RefusedDataSlots are the slots the parent asked for and did not get,
	// each carrying WHY.
	//
	// Recorded rather than dropped, and this is the whole reason the field
	// exists: a child waiting on data that will never arrive, with nothing
	// anywhere saying why, is a far worse failure than a refusal it can see.
	// The parent reads this back and can say so, or re-ask for something it
	// does hold.
	//
	// The reason is carried per slot because the causes point a reader at
	// different actions, and a bare list cannot tell them apart. See
	// DataSlotRefusalReason.
	// +optional
	RefusedDataSlots []RefusedDataSlot `json:"refusedDataSlots,omitempty"`

	// PendingDataSlots are slots whose binding would disclose, and for which a
	// human who can speak for the datum is being asked. Written by the
	// controller when it parks at AwaitingDisclosure; cleared once every one
	// is decided.
	//
	// Distinct from RefusedDataSlots because the states are opposite in what
	// they ask of a reader: a refusal is over, a pending slot is a question
	// still open. Collapsing them would have a parent report a denial for a
	// decision nobody has made yet.
	// +optional
	PendingDataSlots []DataSlotRequest `json:"pendingDataSlots,omitempty"`

	// InputRequest mirrors the child's outstanding mid-flight ask for data
	// (its status.inputRequest), so the parent — which polls THIS object and
	// cannot read the child's — can see what was asked for and why.
	//
	// Mirrored rather than read through, for the reason parentExchange is: the
	// parent's runner has no permission on the child's AgentSession, and RBAC
	// cannot scope a status read to one field.
	// +optional
	InputRequest *InputRequest `json:"inputRequest,omitempty"`

	// DisclosureRequestedAt is when the wait for a disclosure decision began.
	// It bounds that wait: a question nobody answers must end as a stated
	// failure rather than a delegation that hangs until the parent's own tool
	// timeout and reports nothing about why.
	// +optional
	DisclosureRequestedAt *metav1.Time `json:"disclosureRequestedAt,omitempty"`

	// ApprovedDataSlots are the slots a human cleared for disclosure to THIS
	// child. Written by channelsd when an approval lands, and consumed by the
	// controller on its next pass.
	//
	// PER REQUEST, never a standing grant, and that is the point of recording
	// it here rather than in a decision store keyed by tag: consent was given
	// to disclose this datum to this child for this delegation. The same tag
	// handed to a different child is a different disclosure and gets asked
	// again.
	// +optional
	ApprovedDataSlots []DataSlotRequest `json:"approvedDataSlots,omitempty"`

	// DeniedDataSlots are the slots a human REFUSED to disclose to this child.
	// Written by channelsd alongside ApprovedDataSlots.
	//
	// Recorded as explicitly as an approval, and that is the point of writing
	// it at all: without it the controller cannot tell "someone said no" from
	// "nobody has answered yet", so a refused disclosure would sit until the
	// wait window elapsed and then report a timeout — telling the parent
	// nobody decided when in fact someone did.
	// +optional
	DeniedDataSlots []DataSlotRequest `json:"deniedDataSlots,omitempty"`

	// Result is the child's final answer, copied from the child's
	// status.result once it reaches Succeeded.
	// +optional
	Result string `json:"result,omitempty"`

	// Artifacts are the durable outputs the child handed back alongside its
	// Result, copied from the child's status.result.artifacts when it reaches
	// Succeeded. Without this field a child could accept artifact handles into
	// return_result and nothing would ever carry them across the delegation
	// boundary: the parent could not see that an artifact existed, could not
	// attach it, and could not even decide to ask the child for it again.
	//
	// Handing an artifact to the delegating agent is delivery TO THE PARENT,
	// which is a different act from delivery to a user. Nothing here reaches a
	// person; the parent decides whether to deliver what it was given, ask for
	// something else, or produce its own.
	//
	// The CHILD's own claim, and UNTRUSTED exactly as Result and Message are.
	// Each ID is copied verbatim — this controller does not resolve it, and
	// its presence here is not evidence that any bytes exist. The parent's
	// respond_to_user is what verifies it, at the moment the parent tries to
	// attach it: that call re-reads this list, requires the render to be owned
	// by the child this request names and to be Ready, and refuses legibly
	// otherwise. Verifying HERE instead would put the check a reconcile before
	// the use, where a render that is later revised, GC'd or never was would
	// still read as verified.
	//
	// BOUNDED on copy (count and per-entry description length) rather than
	// mirrored wholesale: status is not a transport for an arbitrary payload,
	// and a child's list is as unbounded as its summary was before
	// truncateResult existed. An overflowing list is cut and said so in
	// Determination, never silently.
	// +optional
	// +listType=atomic
	Artifacts []ResultArtifact `json:"artifacts,omitempty"`

	// Message is the child's outstanding question to its parent, copied from
	// the child's status.parentExchange.question while phase is
	// AwaitingParent. It is the CHILD's own words, exactly as Result is, and
	// every consumer must treat it as untrusted content — the delegate tool
	// returns it to the parent's model as an uninspected-by-default tool
	// result, i.e. one that runs through the same content-guard inspection any
	// untrusted meta-tool result gets.
	//
	// Retained after the exchange is answered, as a record of what was last
	// asked; Exchange, not the presence of this field, says whether a question
	// is outstanding.
	// +optional
	Message string `json:"message,omitempty"`

	// Exchange is the count of questions the child has asked its parent so
	// far, mirrored from the child's status.parentExchange.exchange. It is
	// monotonic and never decreases.
	//
	// It exists so a parent can tell a NEW question from the one it just
	// answered. Without it, a reply followed by a re-poll that lands before
	// the child has woken reads the same outstanding question a second time
	// and answers itself in a loop.
	//
	// MIRRORED, therefore never a ceiling. The child writes the number this
	// copies, so it can restate it — which is exactly why the exchange budget
	// is measured against ExchangesRemaining below and never against this.
	// +optional
	Exchange int64 `json:"exchange,omitempty"`

	// ExchangesRemaining is how many FURTHER questions this delegation will
	// carry from its child to its parent. It is the exchange budget
	// SubagentRequestSpec.Mode declares, resolved once (ExchangeBudget) at the
	// point an exchange is honoured and counted down from there.
	//
	// Controller-owned and monotonically decreasing. That is the whole point:
	// the child writes its own status.parentExchange — including the exchange
	// NUMBER — so a budget measured against anything the child authors could be
	// widened by restating it, and the party a ceiling constrains must not be
	// able to widen it. Nothing outside this controller writes this field; the
	// per-session runner Role grants no write on subagentrequests/status at all.
	//
	// Nil means "no bound is in force", and covers two states that need no
	// distinguishing because neither refuses anything: a mode whose budget is
	// unbounded (chat), and a bounded delegation that has not yet had a first
	// exchange honoured. Zero means the budget is spent and the next question
	// will be refused.
	//
	// Read by the CHILD as well as by the controller: the child's ask_parent
	// checks it before recording a question, so a question that would be
	// refused comes back to the child as a legible tool result in the same turn
	// instead of parking it on an answer that will never arrive. That read is
	// legibility, not enforcement — the controller refuses regardless of what
	// the child does with it.
	// +optional
	ExchangesRemaining *int64 `json:"exchangesRemaining,omitempty"`

	// AwaitingParentSince is when the CURRENT unanswered question was first
	// observed. Controller-owned (never copied from the child) so a child
	// cannot extend its own parking bound by restamping it: it is the clock
	// the SubagentRequest controller measures a silent parent against, and a
	// child that answered to nobody must not be able to hold a session, its
	// Channel and its share of the tree's agent ceiling open forever.
	//
	// Cleared when the exchange is answered, so the bound is per-exchange
	// rather than a budget the whole conversation shares.
	// +optional
	AwaitingParentSince *metav1.Time `json:"awaitingParentSince,omitempty"`

	// CompletionTime is when this request reached a terminal phase — Succeeded,
	// Failed or Denied alike, so it is the clock for "how long has this been
	// resolved", not a success timestamp.
	//
	// Controller-owned, written in the same status update that sets the
	// terminal phase, and never rewritten afterwards. It exists for one
	// consumer: the reclaim pass that deletes a resolved delegation once it
	// has been kept long enough (SubagentRequest reconciler,
	// DefaultTerminalRetention). A delegation whose parent is still alive is
	// reclaimed by nothing else — the owner-reference cascade only runs when
	// the PARENT is deleted — so without this clock a long-lived conversational
	// parent accrues one request, one Channel and one child session per
	// delegation for its entire lifetime.
	//
	// Nil on a request that has not resolved, and on one resolved before this
	// field existed; the reclaim pass stamps the latter on its next reconcile
	// rather than treating "no clock" as "expired", so an upgrade cannot sweep
	// away every historical delegation at once.
	// +optional
	CompletionTime *metav1.Time `json:"completionTime,omitempty"`

	// FailureReason is a stable machine-readable reason on Failed or Denied.
	// A DENIAL is not a Failure: keeping the phases distinct is what lets the
	// delegate meta-tool (pkg/agent/tool/meta/delegate.go) tell the parent's
	// model, in the imperative result text it returns on Denied, not to
	// re-attempt the same work through another child -- while a Failed
	// result is presented as retryable. Nothing in this type or the
	// controller binds or enforces that instruction; it is prose at the
	// tool/model boundary, not a system property.
	// +optional
	FailureReason string `json:"failureReason,omitempty"`

	// Determination is human-readable text explaining the phase, surfaced
	// rather than logged so a refusal is never silent.
	// +optional
	Determination string `json:"determination,omitempty"`
}

func (*SubagentRequestStatus) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubagentRequestStatus.

func (*SubagentRequestStatus) DeepCopyInto

func (in *SubagentRequestStatus) DeepCopyInto(out *SubagentRequestStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolBundle

type ToolBundle struct {
	// Name is the LLM-facing prefix; pattern [a-z0-9_-]{1,32}.
	Name string `json:"name"`
	// Class names a SpiceboxClass (cluster-scoped).
	Class string `json:"class"`
	// Toolspecs lists SpiceboxToolspec names that scope this bundle.
	Toolspecs []string `json:"toolspecs"`
	// AgentIdentity overrides spec.agentIdentity for this bundle.
	// +optional
	AgentIdentity string `json:"agentIdentity,omitempty"`
	// CredentialRemap remaps a tool-declared credential name to a
	// differently-named credential in the agent's identity catalog —
	// the opt-in escape hatch when two tools declare the same credential
	// name but need different tokens. Keyed by the tool's declared name.
	// +optional
	CredentialRemap map[string]string `json:"credentialRemap,omitempty"`
	// Sandbox overrides the sandbox backend for this bundle, taking precedence
	// over the referenced SpiceboxClass's own setting: the class states the
	// bundle's default, and the agent consuming it may have a reason to run it
	// elsewhere. Unset inherits the class's setting, or the tier defaults.
	// +optional
	Sandbox *SandboxBackend `json:"sandbox,omitempty"`
	// StageSkills names which of this AgentClass's sandbox-targeted skills get
	// staged to disk, by their local AgentSkill.Name. "*" means all of them.
	// Absent means none — the default, since most agents consume a skill
	// through load_skill and never need it on disk.
	//
	// Enforced CLASS-WIDE, not per-bundle: the union of every ToolBundle's
	// StageSkills in this class is staged onto EVERY bundle's sandbox session,
	// so a bundle whose own StageSkills names nothing still receives whatever
	// another bundle in the class opted in. A per-bundle mount, scoped to only
	// the bundle that named it, is a possible future direction and not
	// current behaviour.
	// +optional
	StageSkills []string `json:"stageSkills,omitempty"`
}

func (*ToolBundle) DeepCopy

func (in *ToolBundle) DeepCopy() *ToolBundle

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolBundle.

func (*ToolBundle) DeepCopyInto

func (in *ToolBundle) DeepCopyInto(out *ToolBundle)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolCall

type ToolCall struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   ToolCallSpec   `json:"spec,omitempty"`
	Status ToolCallStatus `json:"status,omitempty"`
}

+kubebuilder:object:root=true +kubebuilder:resource:scope=Namespaced,categories={authzed,spicebox},shortName=tc +kubebuilder:subresource:status +kubebuilder:printcolumn:name="Session",type="string",JSONPath=".spec.session" +kubebuilder:printcolumn:name="Tool",type="string",JSONPath=".spec.tool" +kubebuilder:printcolumn:name="Exit",type="integer",JSONPath=".status.exitCode" +kubebuilder:printcolumn:name="Succeeded",type="string",JSONPath=".status.conditions[?(@.type=='Succeeded')].status" +kubebuilder:printcolumn:name="Failed",type="string",JSONPath=".status.conditions[?(@.type=='Failed')].status" +kubebuilder:printcolumn:name="Timeout",type="string",JSONPath=".status.conditions[?(@.type=='Timeout')].status",priority=1 +kubebuilder:printcolumn:name="Canceled",type="string",JSONPath=".status.conditions[?(@.type=='Canceled')].status",priority=1 +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +genclient

ToolCall is one execution of one sandbox tool inside a SpiceboxSession: the tool name, its args, non-secret env, stdin, and credential references -- never token bytes. The runner creates it; the outcome lands on status.

Namespaced. Reconciled by pkg/controllers/toolcall, which validates the call against the session's resolved toolspecs, hands the credential descriptors to the token broker to materialize exec env, and runs the process. Secrets supplied through spec.env are rejected rather than passed through.

func (*ToolCall) DeepCopy

func (in *ToolCall) DeepCopy() *ToolCall

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolCall.

func (*ToolCall) DeepCopyInto

func (in *ToolCall) DeepCopyInto(out *ToolCall)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*ToolCall) DeepCopyObject

func (in *ToolCall) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

func (*ToolCall) ValidateCredentialSourceNamespaces

func (tc *ToolCall) ValidateCredentialSourceNamespaces() error

ValidateCredentialSourceNamespaces returns an error if any credential source references a Secret outside the ToolCall's own namespace.

The operator resolves these sources via the broker and injects the resulting Secret values into the sandbox exec. A source naming a Secret in another namespace (e.g. kube-system) would be a confused-deputy secret-theft vector — the operator holds cluster-wide secret read, the ToolCall does not. Legitimate sources are always in the session's own namespace (the runner resolves them from there). Enforced both at admission (the ToolCall webhook) and again in the operator before the broker reads the Secret (defense-in-depth).

func (*ToolCall) ValidateCredentialSourceOwnership

func (tc *ToolCall) ValidateCredentialSourceOwnership() error

ValidateCredentialSourceOwnership returns an error if any credential source references another session's per-session Secret.

Every per-session Secret (PerSessionSecretSuffixes) lives in the session's own namespace, which channel-attached sessions share, so the namespace check alone does not stop a ToolCall owned by session A from naming session B's. That matters most for B's -memory-token, which holds the audit-signing seed and the args-hash HMAC key: reading it would let a compromised runner forge signed append-only entries under B's identity, defeating the tamper-evident trust root. ToolCall.spec is stamped by trusted runner code rather than the LLM, so this only bites under runner compromise — exactly the confused-deputy case this layer defends.

So a source whose name ends in a per-session suffix is bound to THIS ToolCall's own AgentSession; names matching no suffix (a legitimate MCPServer static credential, say) are unaffected. Enforced at admission and re-checked in the operator before the broker resolves anything.

type ToolCallList

type ToolCallList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []ToolCall `json:"items"`
}

+kubebuilder:object:root=true

func (*ToolCallList) DeepCopy

func (in *ToolCallList) DeepCopy() *ToolCallList

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolCallList.

func (*ToolCallList) DeepCopyInto

func (in *ToolCallList) DeepCopyInto(out *ToolCallList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*ToolCallList) DeepCopyObject

func (in *ToolCallList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type ToolCallMode

type ToolCallMode string

+kubebuilder:validation:Enum=sync;stream;interactive

const (
	ToolCallModeSync        ToolCallMode = "sync"
	ToolCallModeStream      ToolCallMode = "stream"
	ToolCallModeInteractive ToolCallMode = "interactive"
)

type ToolCallSpec

type ToolCallSpec struct {
	// Session is the name of the SpiceboxSession (same namespace) this call targets.
	Session string `json:"session"`

	// Tool is the name of the tool from the session's class catalog.
	Tool string `json:"tool"`

	// Credentials is the resolved credential descriptor set the runner
	// stamps onto the ToolCall. The toolcall controller hands it to the
	// token broker to materialize exec env vars. References only — never
	// token bytes. When empty, no credentials are injected.
	// +optional
	Credentials []CredentialDescriptor `json:"credentials,omitempty"`

	// Args is the argv passed to the tool, after the class's DefaultArgs.
	// +optional
	Args []string `json:"args,omitempty"`

	// Env is non-secret configuration for the tool process. Use a bound
	// AgentIdentity for any secret material; supplying secrets here is an
	// error — the operator rejects keys whose name matches the well-known
	// secret-name pattern.
	// +optional
	Env map[string]string `json:"env,omitempty"`

	// Stdin is inline bytes piped to the tool's stdin.
	// For large payloads, use InputArtifacts instead.
	// +optional
	Stdin string `json:"stdin,omitempty"`

	// InputArtifacts are stored payloads materialized into the pod before exec.
	// +optional
	InputArtifacts []InputArtifact `json:"inputArtifacts,omitempty"`

	// Workspace selects which workspace volume the tool runs against.
	// +optional
	Workspace WorkspaceConfig `json:"workspace,omitempty"`

	// CaptureOutputs lists paths (inside the pod) to harvest after exec.
	// Supports template variables: {{.callId}}, {{.sessionName}}.
	// +optional
	CaptureOutputs []string `json:"captureOutputs,omitempty"`

	// Timeout is the hard deadline for the tool process.
	// +kubebuilder:default="60s"
	Timeout metav1.Duration `json:"timeout,omitempty"`

	// Mode selects the execution path.
	// +kubebuilder:default=sync
	// +optional
	Mode ToolCallMode `json:"mode,omitempty"`

	// IdleTimeout (interactive mode only) ends the session after this much
	// wall-clock with no activity — no tool output and no human input. The
	// runner-side bridge enforces it. Zero means a system default applies.
	// +optional
	IdleTimeout metav1.Duration `json:"idleTimeout,omitempty"`

	// MaxDuration (interactive mode only) is a hard wall-clock cap on the
	// whole session, enforced by the controller's exec deadline. Zero means
	// a system safety ceiling applies.
	// +optional
	MaxDuration metav1.Duration `json:"maxDuration,omitempty"`

	// StreamTokenHash is the hex-encoded SHA-256 of the gateway stream token.
	// The runner sets this at ToolCall creation and keeps the token preimage
	// in memory; the raw token never touches the API server. Required for
	// streaming ToolCalls (mode=stream or mode=interactive).
	// +optional
	StreamTokenHash string `json:"streamTokenHash,omitempty"`

	// PreDispatchSnapshot, when non-nil, requests the operator's
	// toolcall controller snapshot the bundle session's workspace
	// PVC *before* dispatching the tool. Set by the runner when the
	// tool's stateImpact is readwrite or external — enables fork-
	// from-this-turn semantics. The controller writes the resulting
	// tool_dispatch_snapshot memory entry after the snapshot Job
	// completes.
	// +optional
	PreDispatchSnapshot *PreDispatchSnapshot `json:"preDispatchSnapshot,omitempty"`
}

func (*ToolCallSpec) DeepCopy

func (in *ToolCallSpec) DeepCopy() *ToolCallSpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolCallSpec.

func (*ToolCallSpec) DeepCopyInto

func (in *ToolCallSpec) DeepCopyInto(out *ToolCallSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolCallStatus

type ToolCallStatus struct {
	// ObservedGeneration is the spec generation this status reflects.
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`
	// StartedAt is when the tool process began.
	// +optional
	StartedAt *metav1.Time `json:"startedAt,omitempty"`
	// FinishedAt is when the tool process exited or was killed.
	// +optional
	FinishedAt *metav1.Time `json:"finishedAt,omitempty"`
	// ExitCode is the process exit status; nil when it never ran or is running.
	// +optional
	ExitCode *int32 `json:"exitCode,omitempty"`
	// StdoutArtifactRef points at the captured stdout in the artifact store.
	// +optional
	StdoutArtifactRef string `json:"stdoutArtifactRef,omitempty"`
	// StderrArtifactRef points at the captured stderr in the artifact store.
	// +optional
	StderrArtifactRef string `json:"stderrArtifactRef,omitempty"`
	// StdoutTruncated means the capture hit its size cap and is incomplete.
	// +optional
	StdoutTruncated bool `json:"stdoutTruncated,omitempty"`
	// StderrTruncated means the capture hit its size cap and is incomplete.
	// +optional
	StderrTruncated bool `json:"stderrTruncated,omitempty"`
	// OutputArtifacts are the files harvested per spec.captureOutputs.
	// +optional
	OutputArtifacts []OutputArtifact `json:"outputArtifacts,omitempty"`
	// Streaming is where a stream/interactive call's live output can be read.
	// +optional
	Streaming *StreamingEndpoint `json:"streaming,omitempty"`

	// Toolspec carries the toolspec validation result, populated on every
	// reconcile that ran the validator — which is whenever the session's class
	// declares any toolspecs.
	// +optional
	Toolspec *ToolspecValidation `json:"toolspec,omitempty"`

	// Agent reports the AgentIdentity binding that supplied env to this call.
	// nil when no agent was bound.
	// +optional
	Agent *AgentInjection `json:"agent,omitempty"`

	// Conditions carries Validated, Running, Succeeded, Failed, Timeout and
	// Canceled.
	// +patchStrategy=merge
	// +patchMergeKey=type
	// +optional
	Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}

func (*ToolCallStatus) DeepCopy

func (in *ToolCallStatus) DeepCopy() *ToolCallStatus

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolCallStatus.

func (*ToolCallStatus) DeepCopyInto

func (in *ToolCallStatus) DeepCopyInto(out *ToolCallStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolCallsAuthz

type ToolCallsAuthz struct {
	// +kubebuilder:validation:Enum=currentRequester;startedBy;both
	// +kubebuilder:default=currentRequester
	// +optional
	Subject string `json:"subject,omitempty"`

	// +kubebuilder:validation:Enum=permissive;enforcing;disabled
	// +kubebuilder:default=enforcing
	// +optional
	Mode string `json:"mode,omitempty"`
}

ToolCallsAuthz configures per-tool-call authorization.

func (*ToolCallsAuthz) DeepCopy

func (in *ToolCallsAuthz) DeepCopy() *ToolCallsAuthz

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolCallsAuthz.

func (*ToolCallsAuthz) DeepCopyInto

func (in *ToolCallsAuthz) DeepCopyInto(out *ToolCallsAuthz)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolCostBucket

type ToolCostBucket struct {
	// Tool is the outer tool name (e.g. "claude-oauth").
	Tool string `json:"tool"`
	// AmountMicroUSD is this tool's provider-reported cost, summed across its
	// invocations, in micro-USD (1e-6 USD). Negative reports are excluded from
	// the sum (see Anomalous).
	// +optional
	AmountMicroUSD int64 `json:"amountMicroUSD,omitempty"`
	// PricingKnown is true when AmountMicroUSD is a real cost — i.e. at least one
	// non-negative cost tally was reported. False when none was reported (the
	// amount is 0 and must not be shown as real spend) or every report was
	// anomalous.
	// +optional
	PricingKnown bool `json:"pricingKnown,omitempty"`
	// Anomalous is true when a negative (nonsensical) provider cost was reported
	// for this tool. The tool is still recorded as seen, but the negative value
	// is excluded from AmountMicroUSD and the session total — it signals "a cost
	// report here does not make sense" rather than silently dropping it.
	// +optional
	Anomalous bool `json:"anomalous,omitempty"`
	// SubscriptionEquivalent is true when this tool ran under a flat-rate
	// subscription (toolkit CostTreatment=subscription-equivalent), so its
	// AmountMicroUSD is an API-equivalent estimate, not money charged. The
	// bucket is recorded and shown, but its amount is EXCLUDED from the session
	// total / admind spend so it cannot inflate the figure or trip cost alerts.
	// +optional
	SubscriptionEquivalent bool `json:"subscriptionEquivalent,omitempty"`
}

ToolCostBucket is one interactive toolkit's slice of a session's cost.

func (*ToolCostBucket) DeepCopy

func (in *ToolCostBucket) DeepCopy() *ToolCostBucket

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolCostBucket.

func (*ToolCostBucket) DeepCopyInto

func (in *ToolCostBucket) DeepCopyInto(out *ToolCostBucket)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolGuardCeiling

type ToolGuardCeiling struct {
	// MaxFailureThreshold caps the effective breaker threshold (min wins).
	// Note: the per-origin threshold (BreakerSpec.OriginFailureThreshold)
	// deliberately has no ceiling yet.
	// +kubebuilder:validation:Minimum=1
	// +optional
	MaxFailureThreshold *int32 `json:"maxFailureThreshold,omitempty"`
	// MinInitialCoolOff raises the effective initial cool-off (max wins).
	// Note: MaxCoolOff deliberately has no floor yet.
	// +optional
	MinInitialCoolOff *metav1.Duration `json:"minInitialCoolOff,omitempty"`
	// MinAction is a severity floor (off < warn < deny < halt). Setting
	// "deny" makes the breaker non-disableable below this tier.
	// +kubebuilder:validation:Enum=warn;deny;halt
	// +optional
	MinAction *string `json:"minAction,omitempty"`
	// MaxCallsPerTurn / MaxCalls+Window impose rate ceilings even when no
	// lower-tier rule configures a rate limit.
	// +kubebuilder:validation:Minimum=1
	// +optional
	MaxCallsPerTurn *int32 `json:"maxCallsPerTurn,omitempty"`
	// MaxCalls caps calls in the sliding Window; both must be set together.
	// +kubebuilder:validation:Minimum=1
	// +optional
	MaxCalls *int32 `json:"maxCalls,omitempty"`
	// Window is the sliding-window span for MaxCalls; both must be set together.
	// +optional
	Window *metav1.Duration `json:"window,omitempty"`
	// RateBounds carries sliding-window bounds BESIDE MaxCalls/Window. Every
	// bound binds: a call must fit under all of them, and a lower tier can only
	// add bounds, never trade one away.
	//
	// A ceiling is a conjunction, not a choice. Two tiers naming different
	// windows have written bounds neither of which implies the other — a
	// cluster {5 calls, 1s} permits 432,000/day, a namespace {10 calls, 24h}
	// permits all 10 inside one second — so collapsing them by calls/second
	// discards a bound its author wrote, on the very surface this type calls
	// the hard bound lower tiers cannot escape.
	//
	// The settings fold writes it: the strictest-by-rate pair leads in
	// MaxCalls/Window, so a reader that knows only the pair still sees a real
	// bound, and every other distinct window lands here, deduped
	// strictest-per-window and window-ordered so the object is stable to
	// re-stamp onto status. Authoring it directly is how one tier expresses
	// burst-plus-sustained on its own.
	//
	// It deliberately carries no MaxItems: one schema governs both the AUTHORED
	// surface and the FOLDED one stamped onto status, so a cap of N would also
	// bind a fold that unions two tiers of N+1 windows and can legitimately
	// produce 2N+1 — the status write rejected by the very bound meant to keep
	// it small, wedging the reconcile. Enforcement stays cheap instead because
	// the per-call sweep is one pass over call history per bound, and the size
	// of that history is set by maxCalls over the longest window, not by how
	// many horizons are named.
	// +listType=atomic
	// +optional
	RateBounds []CallRateBound `json:"rateBounds,omitempty"`
	// MaxEgressBytes / MaxIngressBytes impose per-call byte ceilings even when
	// no lower-tier rule configures a data limit (an unset limit is
	// "unlimited", so the ceiling wins). Folded strictest-across-tiers (min).
	// +kubebuilder:validation:Minimum=1
	// +optional
	MaxEgressBytes *int64 `json:"maxEgressBytes,omitempty"`
	// MaxIngressBytes is the inbound half of the same per-call byte ceiling.
	// +kubebuilder:validation:Minimum=1
	// +optional
	MaxIngressBytes *int64 `json:"maxIngressBytes,omitempty"`
	// MaxUIIngressBytes imposes a ceiling on the UI data-binding ingress path
	// (toolguard.DefaultUIIngressBytes when neither a rule nor this ceiling
	// sets one — this path is never unlimited). Folded strictest-across-tiers
	// (min), independent of MaxIngressBytes.
	// +kubebuilder:validation:Minimum=1
	// +optional
	MaxUIIngressBytes *int64 `json:"maxUIIngressBytes,omitempty"`
}

ToolGuardCeiling is the Limits-side hard bound lower tiers cannot escape. Folded strictest-across-tiers (like SettingsBudgetCeiling).

maxCalls and window bound call volume only as a pair (see CallRate): both the cross-tier fold and the per-rule clamp skip a ceiling missing either half, so a half-authored one is admitted, reported by nothing, and enforces nothing. The apiserver refuses it instead. rateBounds needs no such rule: it carries each pair as one struct with both halves required. +kubebuilder:validation:XValidation:rule="has(self.maxCalls) == has(self.window)",message="toolGuard.maxCalls and toolGuard.window must be set together"

func (*ToolGuardCeiling) DeepCopy

func (in *ToolGuardCeiling) DeepCopy() *ToolGuardCeiling

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolGuardCeiling.

func (*ToolGuardCeiling) DeepCopyInto

func (in *ToolGuardCeiling) DeepCopyInto(out *ToolGuardCeiling)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolGuardMatch

type ToolGuardMatch struct {
	// Kind is the runtime tool kind. Sidecar-toolbox tools report "mcp"
	// (they are synthesized through the MCP synthesizer); select them via
	// Origin "sidecartoolbox/*".
	// +kubebuilder:validation:Enum=sandbox;mcp;meta
	// +optional
	Kind string `json:"kind,omitempty"`
	// Tool is a glob over the LLM-visible tool name (e.g. "github_*").
	// +optional
	Tool string `json:"tool,omitempty"`
	// Origin is a glob over the tool's origin in "<kind>/<name>" form,
	// e.g. "mcpserver/github" or "sidecartoolbox/*".
	// Note: a bare "*" also matches origin-less tools (empty origin); use a
	// prefixed glob like "mcpserver/*" to scope to tools that have an origin.
	// +optional
	Origin string `json:"origin,omitempty"`
}

ToolGuardMatch selects tools. All set fields must match (AND); an empty field matches anything. Tool and Origin accept path.Match globs.

func (*ToolGuardMatch) DeepCopy

func (in *ToolGuardMatch) DeepCopy() *ToolGuardMatch

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolGuardMatch.

func (*ToolGuardMatch) DeepCopyInto

func (in *ToolGuardMatch) DeepCopyInto(out *ToolGuardMatch)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolGuardPolicy

type ToolGuardPolicy struct {
	// Rules are evaluated in order, first match wins; empty means this tier
	// contributes nothing and the walk falls through.
	// +optional
	// +listType=atomic
	Rules []ToolGuardRule `json:"rules,omitempty"`
}

ToolGuardPolicy is an ordered, first-match-wins rule list governing per-tool circuit breakers and rate limits. It appears on AgentClass.spec.toolGuard and on settings Defaults; the tier walk is class → namespace defaults → cluster defaults → built-in rule (pkg/authz/toolguard.Builtin).

func (*ToolGuardPolicy) DeepCopy

func (in *ToolGuardPolicy) DeepCopy() *ToolGuardPolicy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolGuardPolicy.

func (*ToolGuardPolicy) DeepCopyInto

func (in *ToolGuardPolicy) DeepCopyInto(out *ToolGuardPolicy)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolGuardRule

type ToolGuardRule struct {
	// Match selects the tools this rule governs.
	Match ToolGuardMatch `json:"match"`

	// Breaker configures the circuit breaker; nil means matched tools get none.
	// +optional
	Breaker *BreakerSpec `json:"breaker,omitempty"`
	// RateLimit caps call volume; nil means matched tools get no rate limit.
	// +optional
	RateLimit *RateLimitSpec `json:"rateLimit,omitempty"`
	// DataLimit caps per-call byte volume; nil means no byte cap.
	// +optional
	DataLimit *DataLimitSpec `json:"dataLimit,omitempty"`
}

ToolGuardRule pairs a tool matcher with breaker and/or rate-limit specs. A matched rule fully replaces the built-in rule for that tool: a nil Breaker means no breaker; a nil RateLimit means no rate limit.

func (*ToolGuardRule) DeepCopy

func (in *ToolGuardRule) DeepCopy() *ToolGuardRule

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolGuardRule.

func (*ToolGuardRule) DeepCopyInto

func (in *ToolGuardRule) DeepCopyInto(out *ToolGuardRule)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolGuardStatus

type ToolGuardStatus struct {
	// OpenBreakers lists the circuits currently denying calls; empty means none.
	// +optional
	// +listType=map
	// +listMapKey=key
	OpenBreakers []OpenBreaker `json:"openBreakers,omitempty"`
}

ToolGuardStatus is the live enforcement picture on AgentSession status.

func (*ToolGuardStatus) DeepCopy

func (in *ToolGuardStatus) DeepCopy() *ToolGuardStatus

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolGuardStatus.

func (*ToolGuardStatus) DeepCopyInto

func (in *ToolGuardStatus) DeepCopyInto(out *ToolGuardStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolReads

type ToolReads struct {
	// ResourceType is the SpiceDB definition name (e.g. "linear_issue").
	ResourceType string `json:"resourceType"`

	// IDArg names a top-level field inside the tool's args envelope
	// whose string value is the SpiceDB resource ID. Mutually exclusive
	// with ResultIDField.
	// +optional
	IDArg string `json:"idArg,omitempty"`

	// ResultIDField names a top-level field on the tool result's content
	// (parsed as JSON) whose string value is the SpiceDB resource ID.
	// Set this when the agent's input identifier differs from the
	// SpiceDB resource ID (e.g. Linear `id: "L-140"` returns a UUID in
	// the response). When set, the pre-execute requester view check
	// happens AFTER the call against the response-extracted ID.
	// Mutually exclusive with IDArg.
	// +optional
	ResultIDField string `json:"resultIDField,omitempty"`

	// Permission is the SpiceDB permission checked against the requester
	// (e.g. "view").
	Permission string `json:"permission"`

	// BypassRequesterCheck, when true, skips the requester view check.
	// The taint is still recorded; this is useful for agents whose service
	// identity legitimately has broader access than the requester.
	// +optional
	BypassRequesterCheck *bool `json:"bypassRequesterCheck,omitempty"`
}

ToolReads describes the SpiceDB resource a read-style MCP tool accesses.

Exactly one of `idArg` or `resultIDField` must be set. Use `idArg` when the agent passes the SpiceDB resource ID directly (e.g. an MCPServer that takes a UUID). Use `resultIDField` when the agent passes a human-readable identifier (e.g. Linear's "L-140") and the SpiceDB resource ID (UUID) only appears in the tool's response — the hook parses the result content as JSON and extracts the named field.

func (*ToolReads) DeepCopy

func (in *ToolReads) DeepCopy() *ToolReads

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolReads.

func (*ToolReads) DeepCopyInto

func (in *ToolReads) DeepCopyInto(out *ToolReads)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolResourceMapping

type ToolResourceMapping struct {
	// Tool is the MCP tool name this mapping applies to.
	Tool string `json:"tool"`

	// Reads declares the resource the tool reads. Set this OR NoTaint;
	// unset both is treated as "unmapped" and may block tool dispatch
	// depending on AgentClass.Authz.InformationLeakage.Mode.
	// +optional
	Reads *ToolReads `json:"reads,omitempty"`

	// NoTaint marks the tool as a pure utility that reads no user data.
	// Set true to opt out of taint capture and the requester view check.
	// +optional
	NoTaint bool `json:"noTaint,omitempty"`
}

ToolResourceMapping declares how a specific MCP tool relates to SpiceDB resources. Consumed by the runner's information-leakage gate.

func (*ToolResourceMapping) DeepCopy

func (in *ToolResourceMapping) DeepCopy() *ToolResourceMapping

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolResourceMapping.

func (*ToolResourceMapping) DeepCopyInto

func (in *ToolResourceMapping) DeepCopyInto(out *ToolResourceMapping)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolchainMount

type ToolchainMount struct {
	// Name is the toolchain name; it is also the directory under the root path.
	Name string `json:"name"`
	// SourceKind is the delivery backend that resolved this mount.
	SourceKind string `json:"sourceKind"`
	// Image is the OCI image carrying the payload.
	Image string `json:"image"`
	// Prefix is the payload's absolute path inside Image.
	Prefix string `json:"prefix"`
	// Bin lists PATH entries relative to this toolchain's root.
	// +optional
	Bin []string `json:"bin,omitempty"`
	// Env is already expanded — no templates remain.
	// +optional
	Env map[string]string `json:"env,omitempty"`
	// SizeBytes is the payload's on-disk usage, driving the emptyDir SizeLimit.
	// +optional
	SizeBytes int64 `json:"sizeBytes,omitempty"`
}

ToolchainMount is a resolved, self-contained toolchain: everything the pod builder needs, with no further CR reads. Frozen onto SpiceboxSessionStatus.ResolvedToolchains at first bind, mirroring how SkillBundleMount is threaded onto the session spec.

func (*ToolchainMount) DeepCopy

func (in *ToolchainMount) DeepCopy() *ToolchainMount

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolchainMount.

func (*ToolchainMount) DeepCopyInto

func (in *ToolchainMount) DeepCopyInto(out *ToolchainMount)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolchainSource

type ToolchainSource struct {
	// Kind names a registered delivery backend. "image" today.
	Kind string `json:"kind"`

	// Image is the OCI image carrying the payload. Must be a regular container
	// image, NOT an ORAS artifact: containerd 2.1 silently produces an empty
	// mount for artifacts with custom layer media types.
	// +optional
	Image string `json:"image,omitempty"`

	// Prefix is the absolute path of the payload inside Image. It MUST equal
	// <ToolchainRootPath>/<metadata.name>. Redundant by construction, and kept
	// so the invariant is reviewable in the CR rather than implicit in a
	// Dockerfile.
	// +optional
	Prefix string `json:"prefix,omitempty"`
}

func (*ToolchainSource) DeepCopy

func (in *ToolchainSource) DeepCopy() *ToolchainSource

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolchainSource.

func (*ToolchainSource) DeepCopyInto

func (in *ToolchainSource) DeepCopyInto(out *ToolchainSource)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolkitCredsEffect

type ToolkitCredsEffect struct {
	// Required names credentials the invocation needs to succeed.
	Required []string `json:"required"`
	// Writes names credentials the invocation may overwrite.
	Writes []string `json:"writes"`
}

func (*ToolkitCredsEffect) DeepCopy

func (in *ToolkitCredsEffect) DeepCopy() *ToolkitCredsEffect

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolkitCredsEffect.

func (*ToolkitCredsEffect) DeepCopyInto

func (in *ToolkitCredsEffect) DeepCopyInto(out *ToolkitCredsEffect)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolkitEffects

type ToolkitEffects struct {
	// Destructive means the invocation can remove or overwrite state.
	Destructive bool `json:"destructive"`
	// Reads names the resource kinds the invocation reads.
	Reads []string `json:"reads"`
	// Writes names the resource kinds the invocation mutates.
	Writes []string `json:"writes"`
	// Network is where the invocation may reach.
	Network ToolkitNetworkEffect `json:"network"`
	// Filesystem is what the invocation may touch on disk.
	Filesystem ToolkitFsEffect `json:"filesystem"`
	// Creds is which credentials the invocation needs or rewrites.
	Creds ToolkitCredsEffect `json:"creds"`
}

func (*ToolkitEffects) DeepCopy

func (in *ToolkitEffects) DeepCopy() *ToolkitEffects

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolkitEffects.

func (*ToolkitEffects) DeepCopyInto

func (in *ToolkitEffects) DeepCopyInto(out *ToolkitEffects)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolkitEnv

type ToolkitEnv struct {
	// Allowed DESCRIBES the environment variables the binary reads. It is not
	// an enforcement boundary: nothing strips a variable for being absent here.
	// The environment a tool call runs with is composed by the ToolCall
	// controller from the broker-resolved credentials, ToolCall.spec.env,
	// SpiceboxSession.spec.defaultEnv, this toolkit's envDefaults and the
	// sandbox container's own env.
	//
	// What an entry drives is keyed off Sensitive: redaction wherever the call
	// is surfaced, which credential a setup flow must obtain, and which names a
	// SpiceboxClass may not shadow through its own envDefaults. Adding a
	// variable here documents and wires it; omitting one does not keep it out
	// of the binary's environment.
	Allowed []ToolkitEnvVar `json:"allowed"`
}

func (*ToolkitEnv) DeepCopy

func (in *ToolkitEnv) DeepCopy() *ToolkitEnv

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolkitEnv.

func (*ToolkitEnv) DeepCopyInto

func (in *ToolkitEnv) DeepCopyInto(out *ToolkitEnv)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolkitEnvVar

type ToolkitEnvVar struct {
	// Name is the environment variable name.
	Name string `json:"name"`

	// Title is the short user-facing display name for the credential this
	// env var carries ("GitHub"). Shown as the bold header of a
	// credential-request row. Takes precedence over the provider catalog;
	// when empty the catalog (via Provider) or a humanized credential name
	// is used. Only meaningful when Sensitive is true.
	// +optional
	Title string `json:"title,omitempty"`

	// Description is the user-facing explanation of what this variable carries.
	Description string `json:"description,omitempty"`
	// Sensitive marks the value as credential material: it is resolved from an
	// identity, masked in output, and never logged.
	Sensitive bool `json:"sensitive,omitempty"`

	// Provider names a provider in the /providers/ library that satisfies
	// this env's auth needs. Used by `oap agent setup-identity` to dispatch
	// the right setup flow. Optional.
	// +optional
	Provider string `json:"provider,omitempty"`

	// Prompt is free-text the LLM-fallback setup agent uses as system
	// context when no provider matches. Optional.
	// +optional
	Prompt string `json:"prompt,omitempty"`

	// Credential is the name of the credential (in the agent's identity
	// catalog) that fills this env var. When empty, resolution falls back
	// to a name derived from the env var (lowercased, "_"→"-"). Only
	// meaningful when Sensitive is true.
	// +optional
	Credential string `json:"credential,omitempty"`
}

func (*ToolkitEnvVar) DeepCopy

func (in *ToolkitEnvVar) DeepCopy() *ToolkitEnvVar

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolkitEnvVar.

func (*ToolkitEnvVar) DeepCopyInto

func (in *ToolkitEnvVar) DeepCopyInto(out *ToolkitEnvVar)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolkitFlag

type ToolkitFlag struct {
	// Long is the flag's long form, without leading dashes.
	Long string `json:"long"`
	// Short is the single-letter form, without its dash; empty if none.
	Short string `json:"short,omitempty"`
	// Type is the value type; "bool" means the flag takes no value.
	Type string `json:"type"`
	// Description is the user-facing explanation of the flag.
	Description string `json:"description,omitempty"`
	// Sensitive keeps the flag's value out of logs and approval prompts.
	Sensitive bool `json:"sensitive,omitempty"`
	// Values, when non-empty, restricts the flag to this enumeration.
	Values []string `json:"values,omitempty"`
	// SplitOn, when non-empty, splits one supplied value into repeated flag
	// occurrences on this separator.
	SplitOn string `json:"splitOn,omitempty"`

	// OptionalValue declares that the binary accepts this flag's value only in
	// the attached `--flag=value` form (git's PARSE_OPT_OPTARG, pflag's
	// NoOptDefVal), so the parser must not consume the following token as the
	// value — the binary won't. Mirrors
	// pkg/tools/toolspec/toolkit.Flag.OptionalValue; invalid on type "bool".
	// +optional
	OptionalValue bool `json:"optionalValue,omitempty"`
}

func (*ToolkitFlag) DeepCopy

func (in *ToolkitFlag) DeepCopy() *ToolkitFlag

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolkitFlag.

func (*ToolkitFlag) DeepCopyInto

func (in *ToolkitFlag) DeepCopyInto(out *ToolkitFlag)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolkitFsEffect

type ToolkitFsEffect struct {
	// Paths names the filesystem locations the invocation touches.
	Paths []string `json:"paths"`
}

func (*ToolkitFsEffect) DeepCopy

func (in *ToolkitFsEffect) DeepCopy() *ToolkitFsEffect

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolkitFsEffect.

func (*ToolkitFsEffect) DeepCopyInto

func (in *ToolkitFsEffect) DeepCopyInto(out *ToolkitFsEffect)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolkitNetworkEffect

type ToolkitNetworkEffect struct {
	// Destinations names the hosts the invocation contacts; empty means none.
	Destinations []string `json:"destinations"`
}

func (*ToolkitNetworkEffect) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolkitNetworkEffect.

func (*ToolkitNetworkEffect) DeepCopyInto

func (in *ToolkitNetworkEffect) DeepCopyInto(out *ToolkitNetworkEffect)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolkitParserConfig

type ToolkitParserConfig struct {
	// Kind selects the argv parser: declarative drives off this spec, builtin
	// names a compiled-in parser.
	// +kubebuilder:validation:Enum=declarative;builtin
	Kind string `json:"kind"`
	// Name identifies the builtin parser; unused when Kind is declarative.
	Name string `json:"name,omitempty"`
}

func (*ToolkitParserConfig) DeepCopy

func (in *ToolkitParserConfig) DeepCopy() *ToolkitParserConfig

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolkitParserConfig.

func (*ToolkitParserConfig) DeepCopyInto

func (in *ToolkitParserConfig) DeepCopyInto(out *ToolkitParserConfig)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolkitPositional

type ToolkitPositional struct {
	// Name is the argument name the tool schema exposes to the agent.
	Name string `json:"name"`
	// Type is the value type used to validate and render the argument.
	Type string `json:"type"`
	// Required rejects an invocation that omits this argument.
	Required bool `json:"required,omitempty"`
	// Values, when non-empty, restricts the argument to this enumeration.
	Values []string `json:"values,omitempty"`
	// SplitOn, when non-empty, splits one supplied value into several argv
	// entries on this separator.
	SplitOn string `json:"splitOn,omitempty"`

	// AfterDashDash marks this slot as where post-`--` arguments start
	// binding, for a CLI that overloads `--` as a separator rather than a
	// plain option terminator (git's `log [<rev>] [-- <path>…]`). Mirrors
	// pkg/tools/toolspec/toolkit.Positional.AfterDashDash; at most one positional
	// per subcommand may set it.
	// +optional
	AfterDashDash bool `json:"afterDashDash,omitempty"`
}

func (*ToolkitPositional) DeepCopy

func (in *ToolkitPositional) DeepCopy() *ToolkitPositional

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolkitPositional.

func (*ToolkitPositional) DeepCopyInto

func (in *ToolkitPositional) DeepCopyInto(out *ToolkitPositional)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolkitProbeExtract

type ToolkitProbeExtract struct {
	// Kind selects the extraction strategy applied to the probe's output.
	// +kubebuilder:validation:Enum=regex;json;line1
	Kind string `json:"kind"`
	// Pattern is the regex or JSON path Kind applies; unused for line1.
	Pattern string `json:"pattern,omitempty"`
}

func (*ToolkitProbeExtract) DeepCopy

func (in *ToolkitProbeExtract) DeepCopy() *ToolkitProbeExtract

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolkitProbeExtract.

func (*ToolkitProbeExtract) DeepCopyInto

func (in *ToolkitProbeExtract) DeepCopyInto(out *ToolkitProbeExtract)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolkitSubcommand

type ToolkitSubcommand struct {
	// Path is the subcommand words after the binary, e.g. ["remote","add"].
	Path []string `json:"path"`
	// Description is the user-facing explanation of what the subcommand does.
	Description string `json:"description,omitempty"`
	// Positional declares the subcommand's positional arguments, in order.
	Positional []ToolkitPositional `json:"positional,omitempty"`
	// Flags are the flags this subcommand accepts beyond the global ones.
	Flags []ToolkitFlag `json:"flags,omitempty"`
	// Effects is the declared effect profile driving approval and authz gating.
	Effects ToolkitEffects `json:"effects"`

	// Permission overrides the toolkit-level default permission for
	// this subcommand. Required by the AgentClass validator's
	// "enforcing" mode when Effects.Destructive or Effects.Writes
	// flag the subcommand as state-mutating. Mirrors
	// pkg/tools/toolspec/toolkit.Subcommand.Permission.
	// +optional
	Permission *authz.Permission `json:"permission,omitempty"`

	// PermissionVariants branch this subcommand's authority on its ARGUMENTS:
	// CEL `When` over the parsed args, first match wins, Permission is the
	// fallback. Mirrors pkg/toolspec/toolkit.Subcommand.PermissionVariants and
	// matches MCPServerTool.PermissionVariants exactly — the same idea should
	// not grow a second vocabulary.
	// +optional
	PermissionVariants []authz.PermissionVariant `json:"permissionVariants,omitempty"`

	// Mode declares how the runner dispatches this subcommand. Mirrors
	// pkg/tools/toolspec/toolkit.Subcommand.Mode. Values: "" (sync),
	// "stream" (streaming output, no stdin bridge), "interactive"
	// (streaming output + live stdin pipe).
	// +optional
	Mode string `json:"mode,omitempty"`

	// Timeout overrides the mode-derived per-call execution budget for this
	// subcommand (a Go duration string, e.g. "10m" for a slow clone). Empty
	// applies the mode default: 30m for stream/interactive, 5m for sync.
	// Mirrors pkg/tools/toolspec/toolkit.Subcommand.Timeout; a non-parseable or
	// non-positive value is rejected when the toolkit is loaded.
	// +optional
	Timeout string `json:"timeout,omitempty"`
}

func (*ToolkitSubcommand) DeepCopy

func (in *ToolkitSubcommand) DeepCopy() *ToolkitSubcommand

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolkitSubcommand.

func (*ToolkitSubcommand) DeepCopyInto

func (in *ToolkitSubcommand) DeepCopyInto(out *ToolkitSubcommand)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolkitTarget

type ToolkitTarget struct {
	// Binary is the executable name as invoked inside the sandbox.
	Binary string `json:"binary"`
	// VersionRange is the semver range the description is valid for; empty
	// accepts any version.
	VersionRange string `json:"versionRange,omitempty"`
	// VersionProbe is how to ask the binary its version; nil skips the check.
	VersionProbe *ToolkitVersionProbe `json:"versionProbe,omitempty"`

	// PinnedBinaryHash, when set, asserts the sha256 of the toolkit binary
	// ("sha256:…"). RECORDED, not verified: in-sandbox measurement needs a
	// sandbox exec primitive that does not exist yet.
	// +optional
	PinnedBinaryHash string `json:"pinnedBinaryHash,omitempty"`
}

func (*ToolkitTarget) DeepCopy

func (in *ToolkitTarget) DeepCopy() *ToolkitTarget

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolkitTarget.

func (*ToolkitTarget) DeepCopyInto

func (in *ToolkitTarget) DeepCopyInto(out *ToolkitTarget)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolkitVersionProbe

type ToolkitVersionProbe struct {
	// Args is the argv appended to the binary to make it print its version.
	Args []string `json:"args"`
	// Extract is how to pull the version string out of that output.
	Extract ToolkitProbeExtract `json:"extract"`
}

func (*ToolkitVersionProbe) DeepCopy

func (in *ToolkitVersionProbe) DeepCopy() *ToolkitVersionProbe

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolkitVersionProbe.

func (*ToolkitVersionProbe) DeepCopyInto

func (in *ToolkitVersionProbe) DeepCopyInto(out *ToolkitVersionProbe)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolspecAllow

type ToolspecAllow struct {
	// Network widens where a call may reach.
	Network ToolspecAllowNetwork `json:"network,omitempty"`
	// Filesystem widens what a call may touch on disk.
	Filesystem ToolspecAllowFilesystem `json:"filesystem,omitempty"`
	// Creds names the credentials a call may consume.
	Creds ToolspecAllowCreds `json:"creds,omitempty"`
}

func (*ToolspecAllow) DeepCopy

func (in *ToolspecAllow) DeepCopy() *ToolspecAllow

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolspecAllow.

func (*ToolspecAllow) DeepCopyInto

func (in *ToolspecAllow) DeepCopyInto(out *ToolspecAllow)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolspecAllowCreds

type ToolspecAllowCreds struct {
	// Required names the credentials that must resolve for a call to run.
	Required []string `json:"required,omitempty"`
}

func (*ToolspecAllowCreds) DeepCopy

func (in *ToolspecAllowCreds) DeepCopy() *ToolspecAllowCreds

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolspecAllowCreds.

func (*ToolspecAllowCreds) DeepCopyInto

func (in *ToolspecAllowCreds) DeepCopyInto(out *ToolspecAllowCreds)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolspecAllowFilesystem

type ToolspecAllowFilesystem struct {
	// PathsUnder are the directory roots a call may touch.
	PathsUnder []string `json:"pathsUnder,omitempty"`
}

func (*ToolspecAllowFilesystem) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolspecAllowFilesystem.

func (*ToolspecAllowFilesystem) DeepCopyInto

func (in *ToolspecAllowFilesystem) DeepCopyInto(out *ToolspecAllowFilesystem)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolspecAllowNetwork

type ToolspecAllowNetwork struct {
	// Destinations are the hosts a call may contact; empty allows none.
	Destinations []string `json:"destinations,omitempty"`
}

func (*ToolspecAllowNetwork) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolspecAllowNetwork.

func (*ToolspecAllowNetwork) DeepCopyInto

func (in *ToolspecAllowNetwork) DeepCopyInto(out *ToolspecAllowNetwork)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolspecConstraint

type ToolspecConstraint struct {
	// CEL is a boolean expression over the call's arguments; false denies.
	CEL string `json:"cel"`
	// Message is the denial text shown when CEL evaluates false.
	Message string `json:"message,omitempty"`
}

func (*ToolspecConstraint) DeepCopy

func (in *ToolspecConstraint) DeepCopy() *ToolspecConstraint

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolspecConstraint.

func (*ToolspecConstraint) DeepCopyInto

func (in *ToolspecConstraint) DeepCopyInto(out *ToolspecConstraint)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolspecDeny

type ToolspecDeny struct {
	// Effects refuses calls whose declared effects match.
	Effects ToolspecDenyEffects `json:"effects,omitempty"`
}

func (*ToolspecDeny) DeepCopy

func (in *ToolspecDeny) DeepCopy() *ToolspecDeny

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolspecDeny.

func (*ToolspecDeny) DeepCopyInto

func (in *ToolspecDeny) DeepCopyInto(out *ToolspecDeny)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolspecDenyCreds

type ToolspecDenyCreds struct {
	// Writes denies any subcommand that would write credential material.
	Writes bool `json:"writes,omitempty"`
}

func (*ToolspecDenyCreds) DeepCopy

func (in *ToolspecDenyCreds) DeepCopy() *ToolspecDenyCreds

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolspecDenyCreds.

func (*ToolspecDenyCreds) DeepCopyInto

func (in *ToolspecDenyCreds) DeepCopyInto(out *ToolspecDenyCreds)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolspecDenyEffects

type ToolspecDenyEffects struct {
	// Destructive denies any subcommand the toolkit marks destructive.
	Destructive bool `json:"destructive,omitempty"`
	// Reads names resource kinds whose reads are denied.
	Reads []string `json:"reads,omitempty"`
	// Writes names resource kinds whose writes are denied.
	Writes []string `json:"writes,omitempty"`
	// Creds denies credential-touching effects.
	Creds ToolspecDenyCreds `json:"creds,omitempty"`
}

func (*ToolspecDenyEffects) DeepCopy

func (in *ToolspecDenyEffects) DeepCopy() *ToolspecDenyEffects

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolspecDenyEffects.

func (*ToolspecDenyEffects) DeepCopyInto

func (in *ToolspecDenyEffects) DeepCopyInto(out *ToolspecDenyEffects)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolspecException

type ToolspecException struct {
	// Overrides names the deny rules this exception relaxes.
	Overrides []string `json:"overrides"`
	// When is the CEL predicate that must hold for the relaxation to apply.
	When string `json:"when"`
	// Message explains the exception in denial output.
	Message string `json:"message,omitempty"`
}

func (*ToolspecException) DeepCopy

func (in *ToolspecException) DeepCopy() *ToolspecException

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolspecException.

func (*ToolspecException) DeepCopyInto

func (in *ToolspecException) DeepCopyInto(out *ToolspecException)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolspecFailedOnRef

type ToolspecFailedOnRef struct {
	// Path locates the failing rule inside the toolspec.
	Path string `json:"path"`
	// Message is that rule's own denial text.
	Message string `json:"message,omitempty"`
}

func (*ToolspecFailedOnRef) DeepCopy

func (in *ToolspecFailedOnRef) DeepCopy() *ToolspecFailedOnRef

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolspecFailedOnRef.

func (*ToolspecFailedOnRef) DeepCopyInto

func (in *ToolspecFailedOnRef) DeepCopyInto(out *ToolspecFailedOnRef)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolspecFailure

type ToolspecFailure struct {
	// SpecName is the SpiceboxToolspec that denied the call.
	SpecName string `json:"specName"`
	// Reason is the denial explanation, safe to show the agent.
	Reason string `json:"reason,omitempty"`
	// FailedOn points at the specific rule that denied; nil when the spec
	// denied without one.
	FailedOn *ToolspecFailedOnRef `json:"failedOn,omitempty"`
}

func (*ToolspecFailure) DeepCopy

func (in *ToolspecFailure) DeepCopy() *ToolspecFailure

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolspecFailure.

func (*ToolspecFailure) DeepCopyInto

func (in *ToolspecFailure) DeepCopyInto(out *ToolspecFailure)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolspecRef

type ToolspecRef struct {
	// Name is the SpiceboxToolspec CR name.
	Name string `json:"name"`
}

ToolspecRef is a cluster-scoped reference to a SpiceboxToolspec.

func (*ToolspecRef) DeepCopy

func (in *ToolspecRef) DeepCopy() *ToolspecRef

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolspecRef.

func (*ToolspecRef) DeepCopyInto

func (in *ToolspecRef) DeepCopyInto(out *ToolspecRef)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolspecRequire

type ToolspecRequire struct {
	// VerifiedBinaryVersion refuses the call unless the sandbox's binary
	// version was probed and matched the toolkit's declared range.
	VerifiedBinaryVersion bool `json:"verifiedBinaryVersion,omitempty"`
}

func (*ToolspecRequire) DeepCopy

func (in *ToolspecRequire) DeepCopy() *ToolspecRequire

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolspecRequire.

func (*ToolspecRequire) DeepCopyInto

func (in *ToolspecRequire) DeepCopyInto(out *ToolspecRequire)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolspecSecretOutput

type ToolspecSecretOutput struct {
	// Name is the logical key for the secret (e.g. "kubeconfig").
	Name string `json:"name"`
	// Source declares where the secret value lives: "stdout" or "file:<absolute-path>".
	Source string `json:"source"`
	// Description is human-visible text about the secret (caveats, usage, expiry).
	// +optional
	Description string `json:"description,omitempty"`
}

ToolspecSecretOutput mirrors pkg/tools/toolspec/spec.SecretOutputSpec. Json tags must be identical to those on spec.SecretOutputSpec so that the ToSpec JSON round-trip carries all fields through without explicit mapping.

func (*ToolspecSecretOutput) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolspecSecretOutput.

func (*ToolspecSecretOutput) DeepCopyInto

func (in *ToolspecSecretOutput) DeepCopyInto(out *ToolspecSecretOutput)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolspecSensitive

type ToolspecSensitive struct {
	// Flags names flags whose values must be redacted.
	Flags []string `json:"flags,omitempty"`
	// Env names environment variables whose values must be redacted.
	Env []string `json:"env,omitempty"`
	// Positional names positional arguments whose values must be redacted.
	Positional []string `json:"positional,omitempty"`
}

func (*ToolspecSensitive) DeepCopy

func (in *ToolspecSensitive) DeepCopy() *ToolspecSensitive

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolspecSensitive.

func (*ToolspecSensitive) DeepCopyInto

func (in *ToolspecSensitive) DeepCopyInto(out *ToolspecSensitive)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolspecToolkitRef

type ToolspecToolkitRef struct {
	// Name is the SpiceboxToolkit name (or a builtin toolkit's name).
	Name string `json:"name"`
	// Revision is the toolkitRevision this spec was authored against; a
	// mismatch is a validation failure, not a silent upgrade.
	Revision string `json:"revision"`
}

func (*ToolspecToolkitRef) DeepCopy

func (in *ToolspecToolkitRef) DeepCopy() *ToolspecToolkitRef

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolspecToolkitRef.

func (*ToolspecToolkitRef) DeepCopyInto

func (in *ToolspecToolkitRef) DeepCopyInto(out *ToolspecToolkitRef)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ToolspecValidation

type ToolspecValidation struct {
	// AcceptedBy is the name of the SpiceboxToolspec that returned Allow=true.
	// Mutually exclusive with Failures.
	// +optional
	AcceptedBy string `json:"acceptedBy,omitempty"`
	// Failures lists every candidate toolspec that denied this call. Empty
	// when AcceptedBy is set; populated when no candidate allowed.
	// +optional
	Failures []ToolspecFailure `json:"failures,omitempty"`
}

func (*ToolspecValidation) DeepCopy

func (in *ToolspecValidation) DeepCopy() *ToolspecValidation

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToolspecValidation.

func (*ToolspecValidation) DeepCopyInto

func (in *ToolspecValidation) DeepCopyInto(out *ToolspecValidation)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type TrifectaConfig

type TrifectaConfig struct {
	// Mode selects how much of the check runs.
	//
	//   disabled  — off entirely.
	//   logging   — legs are derived and the verdict recorded, nothing refused.
	//               Two-leg near-misses are the dataset this mode exists for.
	//   enforcing — a three-leg delegation is refused.
	//
	// Its OWN mode, and deliberately not reached through toolCalls.mode: a
	// control that disappears when an operator disables a different permission
	// check is not a control. An operator turning off tool-call authz has not
	// thereby decided that untrusted content may drive a write.
	// +kubebuilder:validation:Enum=disabled;logging;enforcing
	// +optional
	Mode string `json:"mode,omitempty"`

	// NeverConsequential declares that this class must never hold leg C — it
	// may read, but must never be able to act.
	//
	// A DECLARED ceiling validated at admission against the DERIVED permission
	// surface, mirroring how budget already works: the class declares, the
	// controller derives, and a contradiction marks the class Valid=False
	// rather than being silently ignored. A class that declares this and holds
	// a readwrite or external handle is a configuration mistake its author
	// wants to hear about at apply time, not a delegation that quietly fails
	// later.
	// +optional
	NeverConsequential bool `json:"neverConsequential,omitempty"`
}

TrifectaConfig configures the closure trifecta check. Modes follow the same informationLeakage.mode / toolCalls.mode / planGate.mode convention, so an operator has one mental model across every gate.

func (*TrifectaConfig) DeepCopy

func (in *TrifectaConfig) DeepCopy() *TrifectaConfig

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrifectaConfig.

func (*TrifectaConfig) DeepCopyInto

func (in *TrifectaConfig) DeepCopyInto(out *TrifectaConfig)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type UserIdentity

type UserIdentity struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   UserIdentitySpec   `json:"spec,omitempty"`
	Status UserIdentityStatus `json:"status,omitempty"`
}

+kubebuilder:object:root=true +kubebuilder:resource:scope=Cluster,categories={authzed,spicebox},shortName=uid +kubebuilder:subresource:status +kubebuilder:printcolumn:name="Subject",type="string",JSONPath=".spec.subject" +kubebuilder:printcolumn:name="Valid",type="string",JSONPath=".status.conditions[?(@.type=='Valid')].status" +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +genclient +genclient:nonNamespaced

UserIdentity is a human's credential catalog: the credentials one person has linked, keyed by their canonical SpiceDB subject, used by identityMode=userPassthrough agents through a per-session SessionUserIdentity projection.

Cluster-scoped. Reconciled by pkg/controllers/useridentity -- one reconciler for validity, one for proactive OAuth refresh. metadata.name is a deterministic hash of spec.subject rather than the subject itself, and the referenced Secrets live in the agentprimitives-identities namespace.

func (*UserIdentity) DeepCopy

func (in *UserIdentity) DeepCopy() *UserIdentity

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserIdentity.

func (*UserIdentity) DeepCopyInto

func (in *UserIdentity) DeepCopyInto(out *UserIdentity)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*UserIdentity) DeepCopyObject

func (in *UserIdentity) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type UserIdentityList

type UserIdentityList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []UserIdentity `json:"items"`
}

+kubebuilder:object:root=true

func (*UserIdentityList) DeepCopy

func (in *UserIdentityList) DeepCopy() *UserIdentityList

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserIdentityList.

func (*UserIdentityList) DeepCopyInto

func (in *UserIdentityList) DeepCopyInto(out *UserIdentityList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*UserIdentityList) DeepCopyObject

func (in *UserIdentityList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type UserIdentitySpec

type UserIdentitySpec struct {
	// Subject is the canonical SpiceDB subject of the user this catalog
	// belongs to, e.g. "user:YWxpY2VAZXhhbXBsZS5jb20=". metadata.name is a
	// deterministic hash of this value (see pkg/platform/identity/useridentity).
	// +kubebuilder:validation:MinLength=1
	Subject string `json:"subject"`

	// DisplayName is a human-friendly label (e.g. an email address) surfaced in
	// CLI output and the web UI.
	// +optional
	DisplayName string `json:"displayName,omitempty"`

	// Credentials are the user's reusable named credentials. Same shape as
	// AgentIdentity.Spec.Credentials. The secretRefs name Secrets in the
	// agentprimitives-identities namespace (IdentitiesNamespace).
	// Credential names must match what tools declare (or the runtime
	// default — see MCPServerAuth.Credential / ToolkitEnvVar.Credential).
	// +optional
	Credentials []AgentCredential `json:"credentials,omitempty"`

	// RefreshThreshold overrides the operator-wide default OAuth refresh
	// threshold for this identity. No effect on type=static credentials.
	// +optional
	RefreshThreshold *metav1.Duration `json:"refreshThreshold,omitempty"`
}

func (*UserIdentitySpec) DeepCopy

func (in *UserIdentitySpec) DeepCopy() *UserIdentitySpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserIdentitySpec.

func (*UserIdentitySpec) DeepCopyInto

func (in *UserIdentitySpec) DeepCopyInto(out *UserIdentitySpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type UserIdentityStatus

type UserIdentityStatus struct {
	// ObservedGeneration is the spec generation this status reflects.
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`

	// ResolvedCredentials lists credential names whose Secret + key resolved.
	// +optional
	ResolvedCredentials []string `json:"resolvedCredentials,omitempty"`

	// AvailableCredentials is the set of credential names this identity
	// successfully provides — a convenience for the operator's passthrough
	// gate and CLI output.
	// +optional
	AvailableCredentials []string `json:"availableCredentials,omitempty"`

	// LastRefreshAt is the most recent successful OAuth refresh.
	// +optional
	LastRefreshAt *metav1.Time `json:"lastRefreshAt,omitempty"`

	// ObservedCredentials is the durable trigger state for in-flight
	// credential revocation — see AgentIdentityStatus.ObservedCredentials for
	// the full contract. SecretNames is unused here: a UserIdentity
	// credential's master Secret name is derived from (identity, credential)
	// rather than referenced by the spec.
	// +optional
	ObservedCredentials []ObservedCredential `json:"observedCredentials,omitempty"`

	// Conditions carries Valid and Refresh; the reason constants are shared
	// with AgentIdentity.
	// +patchStrategy=merge
	// +patchMergeKey=type
	// +optional
	Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`

	// ChannelIdentities is the durable directory of the channel-native
	// identities (Slack user in a workspace, …) that map to this canonical
	// user. One canonical user fans out to many entries — the same human has
	// distinct external IDs across unrelated Slack workspaces/orgs. Written by
	// channelsd (best-effort) via a status merge-patch disjoint from the
	// credential fields the useridentity controller owns.
	// +optional
	// +listType=map
	// +listMapKey=kind
	// +listMapKey=domain
	// +listMapKey=externalID
	ChannelIdentities []ChannelIdentity `json:"channelIdentities,omitempty"`
}

func (*UserIdentityStatus) DeepCopy

func (in *UserIdentityStatus) DeepCopy() *UserIdentityStatus

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserIdentityStatus.

func (*UserIdentityStatus) DeepCopyInto

func (in *UserIdentityStatus) DeepCopyInto(out *UserIdentityStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type UserPreferenceSchema

type UserPreferenceSchema struct {
	// Name is the preference key, unique within the list; the identifier the
	// tools and (phase 2) toolspec CEL read as `prefs.<Name>`.
	// +kubebuilder:validation:MinLength=1
	Name string `json:"name"`

	// Type is the value's shape.
	// +kubebuilder:validation:Enum=string;stringList;int;bool;enum
	Type string `json:"type"`

	// Enum is the permitted value set; only for Type=enum.
	// +optional
	Enum []PreferenceEnumValue `json:"enum,omitempty"`

	// Pattern is a regexp every string value (or stringList item) must match.
	// Only for Type=string and Type=stringList.
	// +optional
	Pattern string `json:"pattern,omitempty"`

	// Default is the class-authored fallback used when neither an admin
	// global nor a user value is set; type-checked at reconcile.
	// +optional
	// +kubebuilder:pruning:PreserveUnknownFields
	Default *apiextensionsv1.JSON `json:"default,omitempty"`

	// Description is user-facing copy shown by the tools and forms.
	// +optional
	Description string `json:"description,omitempty"`

	// Visibility controls who may read this preference's resolved value.
	// "self" (the default): only the user themself — resolved for the
	// session's current turn author. "class": any session of this class may
	// also read it for a NAMED user (get_preferences' user argument), so the
	// agent can honor it when addressing that user (e.g. a notification
	// opt-out). Marking a preference "class" is the class author's
	// declaration that its value is safe to show anyone who can talk to
	// this agent.
	// +kubebuilder:validation:Enum=self;class
	// +optional
	Visibility string `json:"visibility,omitempty"`
}

UserPreferenceSchema declares one per-user preference this class offers. Values are saved per user (memory data plane), resolved per turn author, and read by the agent through get_preferences. NEVER declare a secret as a preference — credentials belong to the identity plane (UserIdentity).

func (*UserPreferenceSchema) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserPreferenceSchema.

func (*UserPreferenceSchema) DeepCopyInto

func (in *UserPreferenceSchema) DeepCopyInto(out *UserPreferenceSchema)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WarmPoolConfig

type WarmPoolConfig struct {
	// Replicas is the number of sandboxes kept ready, PER NAMESPACE listed
	// below. 0 disables pre-warming.
	// +kubebuilder:validation:Minimum=0
	// +optional
	Replicas int32 `json:"replicas,omitempty"`

	// Namespaces lists the namespaces to keep pre-warmed capacity in; a
	// separate pool is created in EACH. SpiceboxClass is cluster-scoped but the
	// pool objects are namespaced, and adoption only ever looks a pool up in
	// the ADOPTING SESSION's own namespace — so a pool in a namespace nothing
	// runs sessions in is pure idle spend nothing can adopt. Naming them
	// explicitly rather than discovering them is deliberate: pre-warming spends
	// real money, so the cluster owner states exactly where, as they state
	// exactly how many.
	//
	// Replicas > 0 with an empty Namespaces is a class validation error, not a
	// silent no-op.
	// +optional
	Namespaces []string `json:"namespaces,omitempty"`
}

WarmPoolConfig is how much pre-warmed capacity to keep, and where.

func (*WarmPoolConfig) DeepCopy

func (in *WarmPoolConfig) DeepCopy() *WarmPoolConfig

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WarmPoolConfig.

func (*WarmPoolConfig) DeepCopyInto

func (in *WarmPoolConfig) DeepCopyInto(out *WarmPoolConfig)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WidgetCSP

type WidgetCSP struct {
	// ConnectDomains lists the origins the widget needs XHR/fetch/WebSocket
	// access to (maps to the CSP connect-src directive).
	// +optional
	ConnectDomains []string `json:"connectDomains,omitempty"`
	// ResourceDomains lists the origins the widget loads scripts, styles,
	// and images from (maps to script-src/style-src/img-src).
	// +optional
	ResourceDomains []string `json:"resourceDomains,omitempty"`
	// FrameDomains lists the origins the widget may embed as a nested
	// frame (maps to frame-src).
	// +optional
	FrameDomains []string `json:"frameDomains,omitempty"`
}

WidgetCSP mirrors a widget's `_meta.ui.csp` (mcp-ui / MCP Apps protocol): the domains it declares it needs beyond the same-origin defaults. See pkg/web/webui/sessionview/widgets.go's buildWidgetCSP for the exact directive mapping and pkg/web/webui/sessionview.WidgetCSPMeta for the package-local DTO mirror consumed by the session-view page.

func (*WidgetCSP) DeepCopy

func (in *WidgetCSP) DeepCopy() *WidgetCSP

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WidgetCSP.

func (*WidgetCSP) DeepCopyInto

func (in *WidgetCSP) DeepCopyInto(out *WidgetCSP)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WidgetRef

type WidgetRef struct {
	// ArtifactID is the artifacts.Service head ID the widget's HTML was
	// finalized under — the durable handle the browser session-view page
	// fetches by.
	ArtifactID string `json:"artifactID"`
	// Tool is the MCP tool name (tool.UIResourceSpec.Tool) that produced the
	// widget.
	// +optional
	Tool string `json:"tool,omitempty"`
	// RendererKind is the channelassets.Renderer kind that rendered the
	// widget — always "mcpui" today; carried explicitly so a future second
	// widget-renderer kind doesn't need a status-shape change.
	// +optional
	RendererKind string `json:"rendererKind,omitempty"`
	// Origin is the MCPServer CR name whose ui:// resource produced this
	// widget ("mcpserver/<name>", the string tool.OriginTool.Origin returns).
	//
	// It is the widget's IDENTITY, and it is recorded here because this is
	// where the browser can be told about it authoritatively: webd resolves a
	// shell-supplied artifact id against this list and stamps the answer onto
	// the app-tool call, so the runner can refuse a widget that reaches for a
	// DIFFERENT server's tool. The app-tool registry is one flat map across
	// every origin, so without this the tool name alone decided.
	//
	// Empty on a widget persisted before this field existed, which the pin
	// reads as "unknown origin" and therefore leaves unpinned — the same
	// posture those widgets already had.
	// +optional
	Origin string `json:"origin,omitempty"`
}

WidgetRef identifies one persisted MCP-UI interactive widget artifact, recorded onto AgentSessionStatus.ActiveWidgets by the runner's applyUIResource hook.

func (*WidgetRef) DeepCopy

func (in *WidgetRef) DeepCopy() *WidgetRef

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WidgetRef.

func (*WidgetRef) DeepCopyInto

func (in *WidgetRef) DeepCopyInto(out *WidgetRef)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type Workshop

type Workshop struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   WorkshopSpec   `json:"spec"`
	Status WorkshopStatus `json:"status,omitempty"`
}

Workshop is one builder session's isolated build space: the durable record of its namespace, RBAC, SpiceDB tuple, tokens and sidecar identity, created by the AgentSession reconciler ONLY for a sanctioned class (spec §1.1) and owned end-to-end by the workshop controller. A restarted operator re-provisions from this CR; the webhook attributes SA → session → workshop by reading it; the sweeper enforces its limits without the session's cooperation.

SECURITY: status is the trust boundary. status.sidecarIdentity is the ONLY source the sidecar-pod builder consumes — never AgentSession.status, which the runner holds patch on. spec.session, spec.starterCanonical and spec.limits are immutable after creation (CEL below): the sidecar SA holds update on this one object for the later-plan request fields, and must not be able to re-point the workshop or raise its own limits.

+kubebuilder:object:root=true +kubebuilder:subresource:status +kubebuilder:resource:shortName=wksp +kubebuilder:printcolumn:name="Session",type=string,JSONPath=`.spec.session.name` +kubebuilder:printcolumn:name="Workspace",type=string,JSONPath=`.status.namespace` +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase` +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`

func (*Workshop) DeepCopy

func (in *Workshop) DeepCopy() *Workshop

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Workshop.

func (*Workshop) DeepCopyInto

func (in *Workshop) DeepCopyInto(out *Workshop)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*Workshop) DeepCopyObject

func (in *Workshop) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type WorkshopCapabilityRequest

type WorkshopCapabilityRequest struct {
	Summary     string `json:"summary"`
	ArtifactRef string `json:"artifactRef"`
	// +optional
	DraftRef string `json:"draftRef,omitempty"`
}

func (*WorkshopCapabilityRequest) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkshopCapabilityRequest.

func (*WorkshopCapabilityRequest) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WorkshopCapabilityRequestStatus

type WorkshopCapabilityRequestStatus struct {
	// +optional
	DeliveredAt *metav1.Time `json:"deliveredAt,omitempty"`
	// +optional
	NoticeRef string `json:"noticeRef,omitempty"`
}

func (*WorkshopCapabilityRequestStatus) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkshopCapabilityRequestStatus.

func (*WorkshopCapabilityRequestStatus) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WorkshopCloseRequest

type WorkshopCloseRequest struct {
	// Target is a Workshop: its name in the requester's builder namespace,
	// `namespace/name` for one elsewhere, `*` for every other workshop of the
	// same person, `*:<n>` for a later such ask.
	// +kubebuilder:validation:MinLength=1
	// +kubebuilder:validation:MaxLength=127
	// +kubebuilder:validation:Pattern=`^(\*(:[1-9][0-9]*)?|([a-z0-9]([-a-z0-9]*[a-z0-9])?/)?[a-z0-9]([-a-z0-9]*[a-z0-9])?)$`
	Target string `json:"target"`
	// RequestedAt is when the tool asked.
	RequestedAt metav1.Time `json:"requestedAt"`
}

WorkshopCloseRequest asks the operator to end ANOTHER workshop of the same person (or anyone's, when the requester is a platform admin). Written by the sidecar's close_others tool onto the REQUESTING workshop's spec; decided by the Workshop controller against SpiceDB workshop#close and recorded in status, once per target. The workshop a builder is IN is never a valid target: the tool refuses it before writing, and the controller refuses it again.

RequestedAt is a wall-clock value in a spec field, which the repo's SSA rule otherwise warns against — it is safe here for the same reason WorkshopTestWatch.StartedAt is: the tool writes an entry ONCE per target with a plain Update, never a repeated apply, and the list is keyed by target so there is no second write of the same entry to be non-idempotent about. The controller's own observations live in status.closeRequests, never here.

func (*WorkshopCloseRequest) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkshopCloseRequest.

func (*WorkshopCloseRequest) DeepCopyInto

func (in *WorkshopCloseRequest) DeepCopyInto(out *WorkshopCloseRequest)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WorkshopCloseStatus

type WorkshopCloseStatus struct {
	// Target echoes the spec request's target, and is this list's key.
	Target string `json:"target"`
	// Phase is Closed, Refused, or NotFound.
	// +kubebuilder:validation:Enum=Closed;Refused;NotFound
	Phase string `json:"phase"`
	// Message is the plain-words why, for a decision that was not a close.
	// +optional
	Message string `json:"message,omitempty"`
	// Ask is the wildcard request this entry was decided for; empty for a
	// named request. An entry decided before the field existed carries none
	// and reads as a named decision; deploy the CRD and operator before the
	// workshop image.
	// +optional
	Ask string `json:"ask,omitempty"`
	// DecidedAt is when the controller decided.
	// +optional
	DecidedAt *metav1.Time `json:"decidedAt,omitempty"`
}

WorkshopCloseStatus is the Workshop controller's decision for one spec.closeRequests entry. Written once per CANONICAL target — the namespace and name the spelling resolves to — and never revised: the close it records has already happened (the target's builder session is deleted), so re-deciding could only reach through to a LATER session that happens to carry the same name.

Two spellings of one workshop ("x" and "<builder namespace>/x") are one canonical target, so the second is answered by copying the first's decision into an entry of its own: a second entry, never a second close.

A wildcard ask ("*", "*:<n>") is answered in an entry of its own too — its summary of what THAT ask decided — and every workshop the ask decided carries the ask in Ask. A later ask reads that to skip what is already settled, and the tool reads it to report the answer to its own call.

func (*WorkshopCloseStatus) DeepCopy

func (in *WorkshopCloseStatus) DeepCopy() *WorkshopCloseStatus

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkshopCloseStatus.

func (*WorkshopCloseStatus) DeepCopyInto

func (in *WorkshopCloseStatus) DeepCopyInto(out *WorkshopCloseStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WorkshopCredentialRequest

type WorkshopCredentialRequest struct {
	// Identity is the AgentIdentity (in the workshop namespace W) the credential
	// belongs to.
	// +kubebuilder:validation:MinLength=1
	Identity string `json:"identity"`
	// Credential is the credential name on that AgentIdentity.
	// +kubebuilder:validation:MinLength=1
	Credential string `json:"credential"`
	// AuthKind is pat, static, or oauth-mcp.
	// +kubebuilder:validation:Enum=pat;static;oauth-mcp
	AuthKind string `json:"authKind"`
}

WorkshopCredentialRequest is one bot/shared credential the builder asked a person to connect (plan 5a §6). Refs and names only — never a secret value.

func (*WorkshopCredentialRequest) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkshopCredentialRequest.

func (*WorkshopCredentialRequest) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WorkshopCredentialRequestStatus

type WorkshopCredentialRequestStatus struct {
	Identity   string `json:"identity"`
	Credential string `json:"credential"`
	// +optional
	DeliveredAt *metav1.Time `json:"deliveredAt,omitempty"`
	// +optional
	NoticeRef string `json:"noticeRef,omitempty"`
}

WorkshopCredentialRequestStatus is the observed delivery state for one entry in spec.credentialRequests (plan 5a §6). Identity/Credential key it back to its spec request; DeliveredAt/NoticeRef are set by the WorkshopCredentialWatcher when it DELIVERS the credential card to the builder session (NoticeRef is that interaction's request id). A non-nil DeliveredAt is the dedup marker that stops re-delivery — it records card delivery, not that the person has connected the credential (Secret existence is read directly).

func (*WorkshopCredentialRequestStatus) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkshopCredentialRequestStatus.

func (*WorkshopCredentialRequestStatus) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WorkshopExport

type WorkshopExport struct {
	// ArtifactRef is the opaque artifactstore reference the bundle bytes are
	// stored under, scoped to the builder session (spec.session) — never the
	// workshop namespace.
	ArtifactRef string `json:"artifactRef"`
	// Digest is the sha256 (hex-encoded) of the stored bytes.
	Digest string `json:"digest"`
	// ExportedAt is when this digest was first recorded.
	ExportedAt metav1.Time `json:"exportedAt"`
}

WorkshopExport is the durable record of the workshop's most recently exported drafted .oap bundle.

func (*WorkshopExport) DeepCopy

func (in *WorkshopExport) DeepCopy() *WorkshopExport

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkshopExport.

func (*WorkshopExport) DeepCopyInto

func (in *WorkshopExport) DeepCopyInto(out *WorkshopExport)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WorkshopInstallRequest

type WorkshopInstallRequest struct {
	SuggestedName string `json:"suggestedName"`
	BundleDigest  string `json:"bundleDigest"`
	// +optional
	Answers map[string]string `json:"answers,omitempty"`
}

WorkshopInstallRequest and WorkshopCapabilityRequest are the later-plan sidecar-written requests (spec §2.1). Digest/refs only — never a secret.

func (*WorkshopInstallRequest) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkshopInstallRequest.

func (*WorkshopInstallRequest) DeepCopyInto

func (in *WorkshopInstallRequest) DeepCopyInto(out *WorkshopInstallRequest)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WorkshopInstallStatus

type WorkshopInstallStatus struct {
	// Phase is Requested, Approved, Installed, Declined, or Failed.
	// +kubebuilder:validation:Enum=Requested;Approved;Installed;Declined;Failed
	Phase string `json:"phase"`
	// +optional
	RequestedAt *metav1.Time `json:"requestedAt,omitempty"`
	// +optional
	DeliveredAt *metav1.Time `json:"deliveredAt,omitempty"`
	// ApprovedBy is the canonical id of the admin who approved/declined. Set by admind.
	// +optional
	ApprovedBy string `json:"approvedBy,omitempty"`
	// InstalledRef is "<namespace>/<name>" of the installed AgentClass on success.
	// +optional
	InstalledRef string `json:"installedRef,omitempty"`
	// +optional
	Message string `json:"message,omitempty"`
}

func (*WorkshopInstallStatus) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkshopInstallStatus.

func (*WorkshopInstallStatus) DeepCopyInto

func (in *WorkshopInstallStatus) DeepCopyInto(out *WorkshopInstallStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WorkshopLimits

type WorkshopLimits struct {
	// MaxAge bounds the workshop's total life from provisionedAt, regardless
	// of session state. The sweeper deletes past it.
	MaxAge metav1.Duration `json:"maxAge"`
	// +kubebuilder:validation:Minimum=1
	MaxObjectsPerKind int32 `json:"maxObjectsPerKind"`
	// +kubebuilder:validation:Minimum=1
	MaxObjects int32 `json:"maxObjects"`
	// MaxConcurrentProbes must be at least 1: zero is never a valid config —
	// it would deadlock every WorkshopProbe in the namespace permanently
	// (workshopprobe.Reconciler's concurrency-cap check has no slot to ever
	// grant). The default is 2 (defaultWorkshopLimits,
	// pkg/controllers/agentsession/workshop_hook.go).
	// +kubebuilder:validation:Minimum=1
	MaxConcurrentProbes int32 `json:"maxConcurrentProbes"`
}

func (*WorkshopLimits) DeepCopy

func (in *WorkshopLimits) DeepCopy() *WorkshopLimits

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkshopLimits.

func (*WorkshopLimits) DeepCopyInto

func (in *WorkshopLimits) DeepCopyInto(out *WorkshopLimits)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WorkshopList

type WorkshopList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []Workshop `json:"items"`
}

+kubebuilder:object:root=true

func (*WorkshopList) DeepCopy

func (in *WorkshopList) DeepCopy() *WorkshopList

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkshopList.

func (*WorkshopList) DeepCopyInto

func (in *WorkshopList) DeepCopyInto(out *WorkshopList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*WorkshopList) DeepCopyObject

func (in *WorkshopList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type WorkshopProbe

type WorkshopProbe struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="workshop probe spec is immutable after creation"
	// +kubebuilder:validation:XValidation:rule="(has(self.image)?1:0)+(has(self.script)?1:0)+(has(self.cliHelp)?1:0)==1",message="exactly one of image, script, cliHelp must be set"
	Spec   WorkshopProbeSpec   `json:"spec,omitempty"`
	Status WorkshopProbeStatus `json:"status,omitempty"`
}

WorkshopProbe is the tool-discovery probe object for an agent-builder workshop: the operator runs one pod against spec.image (or spec.script's baseImage, or spec.cliHelp's image) and records what it discovered — the probed tool set, help text, or a failure — on status. Namespaced to the workshop's own namespace; the sidecar SA creates it, the operator (never the sidecar) runs the pod and writes status.

+kubebuilder:object:root=true +kubebuilder:subresource:status +kubebuilder:resource:shortName=wprobe +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase` +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`

func (*WorkshopProbe) DeepCopy

func (in *WorkshopProbe) DeepCopy() *WorkshopProbe

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkshopProbe.

func (*WorkshopProbe) DeepCopyInto

func (in *WorkshopProbe) DeepCopyInto(out *WorkshopProbe)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*WorkshopProbe) DeepCopyObject

func (in *WorkshopProbe) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type WorkshopProbeCliHelp

type WorkshopProbeCliHelp struct {
	Image  string   `json:"image"`
	Binary string   `json:"binary"`
	Args   []string `json:"args,omitempty"`
}

func (*WorkshopProbeCliHelp) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkshopProbeCliHelp.

func (*WorkshopProbeCliHelp) DeepCopyInto

func (in *WorkshopProbeCliHelp) DeepCopyInto(out *WorkshopProbeCliHelp)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WorkshopProbeList

type WorkshopProbeList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []WorkshopProbe `json:"items"`
}

+kubebuilder:object:root=true

func (*WorkshopProbeList) DeepCopy

func (in *WorkshopProbeList) DeepCopy() *WorkshopProbeList

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkshopProbeList.

func (*WorkshopProbeList) DeepCopyInto

func (in *WorkshopProbeList) DeepCopyInto(out *WorkshopProbeList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*WorkshopProbeList) DeepCopyObject

func (in *WorkshopProbeList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type WorkshopProbeScript

type WorkshopProbeScript struct {
	BaseImage string `json:"baseImage"`
	Script    string `json:"script"`
}

func (*WorkshopProbeScript) DeepCopy

func (in *WorkshopProbeScript) DeepCopy() *WorkshopProbeScript

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkshopProbeScript.

func (*WorkshopProbeScript) DeepCopyInto

func (in *WorkshopProbeScript) DeepCopyInto(out *WorkshopProbeScript)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WorkshopProbeSpec

type WorkshopProbeSpec struct {
	Image   string                `json:"image,omitempty"`
	Script  *WorkshopProbeScript  `json:"script,omitempty"`
	CliHelp *WorkshopProbeCliHelp `json:"cliHelp,omitempty"`
	// +kubebuilder:default=120
	// +kubebuilder:validation:Minimum=5
	// +kubebuilder:validation:Maximum=600
	TimeoutSeconds int32 `json:"timeoutSeconds,omitempty"`
}

WorkshopProbeSpec: exactly one of Image, Script, CliHelp, enforced at admission by the CEL rule on WorkshopProbe.Spec above (no webhook). TimeoutSeconds bounds the pod.

func (*WorkshopProbeSpec) DeepCopy

func (in *WorkshopProbeSpec) DeepCopy() *WorkshopProbeSpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkshopProbeSpec.

func (*WorkshopProbeSpec) DeepCopyInto

func (in *WorkshopProbeSpec) DeepCopyInto(out *WorkshopProbeSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WorkshopProbeStatus

type WorkshopProbeStatus struct {
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`
	// +optional
	Phase string `json:"phase,omitempty"`
	// +optional
	Tools []WorkshopProbedTool `json:"tools,omitempty"`
	// +optional
	HelpText string `json:"helpText,omitempty"`
	// +optional
	PodFailure string `json:"podFailure,omitempty"`
	// +optional
	ResolvedDigest string `json:"resolvedDigest,omitempty"`
	// +optional
	// +patchStrategy=merge
	// +patchMergeKey=type
	Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}

func (*WorkshopProbeStatus) DeepCopy

func (in *WorkshopProbeStatus) DeepCopy() *WorkshopProbeStatus

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkshopProbeStatus.

func (*WorkshopProbeStatus) DeepCopyInto

func (in *WorkshopProbeStatus) DeepCopyInto(out *WorkshopProbeStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WorkshopProbedTool

type WorkshopProbedTool struct {
	Name        string `json:"name"`
	Description string `json:"description,omitempty"`
	// InputSchema is JSON text (not json.RawMessage: a CRD status field
	// serializes cleanly as a plain string).
	InputSchema string `json:"inputSchema,omitempty"`
}

func (*WorkshopProbedTool) DeepCopy

func (in *WorkshopProbedTool) DeepCopy() *WorkshopProbedTool

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkshopProbedTool.

func (*WorkshopProbedTool) DeepCopyInto

func (in *WorkshopProbedTool) DeepCopyInto(out *WorkshopProbedTool)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WorkshopSidecarIdentity

type WorkshopSidecarIdentity struct {
	ServiceAccount string `json:"serviceAccount"`
	TokenSecret    string `json:"tokenSecret"`
}

func (*WorkshopSidecarIdentity) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkshopSidecarIdentity.

func (*WorkshopSidecarIdentity) DeepCopyInto

func (in *WorkshopSidecarIdentity) DeepCopyInto(out *WorkshopSidecarIdentity)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WorkshopSpec

type WorkshopSpec struct {
	// Session is the builder AgentSession this workshop belongs to, in the
	// same namespace as this CR. Set once by the AgentSession reconciler.
	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="spec.session is immutable"
	Session NamespacedRef `json:"session"`

	// StarterCanonical is the canonical id of the person who started the
	// session ("user:" prefix stripped), recorded so maxWorkshopsPerStarter
	// can count without reading every session. Set once.
	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="spec.starterCanonical is immutable"
	// +optional
	StarterCanonical string `json:"starterCanonical,omitempty"`

	// SidecarToolbox is the name of the sanctioned SidecarToolbox CR (in the
	// class's namespace) that receives this workshop's identity — copied from
	// the BuilderClassRef at creation. The AgentSession reconciler consumes it
	// (workshopIdentityFor) to decide WHICH sidecar of the class gets the
	// projected SA token. Set once.
	// +kubebuilder:validation:MinLength=1
	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="spec.sidecarToolbox is immutable"
	SidecarToolbox string `json:"sidecarToolbox"`

	// Limits are copied from ClusterAgentSettings at creation and are
	// immutable: the sidecar SA holds update on this object for the request
	// fields below and must not be able to raise its own ceilings.
	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="spec.limits is immutable"
	Limits WorkshopLimits `json:"limits"`

	// ExportRequested, InstallRequest and CapabilityRequest are written by the
	// workshop sidecar in later plans (export/install/handoff flows). This
	// plan's controller ignores them; they exist now so the CRD does not churn.
	// +optional
	ExportRequested bool `json:"exportRequested,omitempty"`
	// +optional
	InstallRequest *WorkshopInstallRequest `json:"installRequest,omitempty"`
	// +optional
	CapabilityRequest *WorkshopCapabilityRequest `json:"capabilityRequest,omitempty"`

	// CredentialRequests are the bot/shared credentials the builder has asked a
	// person to connect for AgentIdentities it authored in this workshop (plan
	// 5a). Written by the sidecar's request_credential tool via its update grant
	// on this CR. A pure function of (identity, credential): a re-request of the
	// same pair is byte-identical, so this is SSA-idempotent. AuthKind is
	// pat|static (a pasted secret) or oauth-mcp (a shared/bot OAuth connect the
	// person authorizes through identityd).
	// +optional
	// +listType=atomic
	CredentialRequests []WorkshopCredentialRequest `json:"credentialRequests,omitempty"`

	// CloseRequests are the OTHER workshops this workshop's builder has been
	// asked to end, written by the sidecar's close_others tool through its
	// update grant on this CR. Each Target is a Workshop — bare in this same
	// (builder) namespace, `namespace/name` for one elsewhere — or a wildcard
	// ask the controller expands: `*` the first time this workshop asks to
	// close every other one, and `*:<n>` for each later ask, so a person may
	// ask again and have it expanded afresh. Either way a target is requested
	// at most once under that exact spelling, since the list is keyed by
	// target.
	//
	// A request is only ever a REQUEST: the Workshop controller decides each
	// against SpiceDB's workshop#close for the person this workshop's builder
	// acts for (spec.starterCanonical) and records the outcome once in
	// status.closeRequests. Nothing here authorizes anything.
	// +optional
	// +listType=map
	// +listMapKey=target
	CloseRequests []WorkshopCloseRequest `json:"closeRequests,omitempty"`

	// TestWatch is the sidecar's request to watch the person's own test of a
	// built class (Try it as yourself). See WorkshopTestWatch.
	// +optional
	TestWatch *WorkshopTestWatch `json:"testWatch,omitempty"`
}

func (*WorkshopSpec) DeepCopy

func (in *WorkshopSpec) DeepCopy() *WorkshopSpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkshopSpec.

func (*WorkshopSpec) DeepCopyInto

func (in *WorkshopSpec) DeepCopyInto(out *WorkshopSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WorkshopStandin

type WorkshopStandin struct {
	// Name is the stand-in's own metadata.name — bare, per the projection
	// route's Ruling C, so it can collide with a bare name the workshop's
	// builder authored itself; that collision is exactly what this registry
	// exists to adjudicate.
	Name string `json:"name"`
	// SourceNamespace/SourceName are the foreign AgentClass this stand-in
	// stands in for — the same {namespace, name} pair the project-agent
	// route's reachability check verified before projecting.
	SourceNamespace string `json:"sourceNamespace"`
	SourceName      string `json:"sourceName"`
}

WorkshopStandin is one authoritative registry entry naming a credential-free rehearsal stand-in AgentClass this workshop has projected (see WorkshopStatus.Standins' own doc for why this list, not the stand-in object's own annotation, is the security boundary).

func (*WorkshopStandin) DeepCopy

func (in *WorkshopStandin) DeepCopy() *WorkshopStandin

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkshopStandin.

func (*WorkshopStandin) DeepCopyInto

func (in *WorkshopStandin) DeepCopyInto(out *WorkshopStandin)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WorkshopStatus

type WorkshopStatus struct {
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`
	// Namespace is the provisioned workshop namespace (ws-<uid12>).
	// +optional
	Namespace string `json:"namespace,omitempty"`
	// Phase is Provisioning, Ready, Expired or Deleting.
	// +optional
	Phase string `json:"phase,omitempty"`
	// +optional
	ProvisionedAt *metav1.Time `json:"provisionedAt,omitempty"`
	// SidecarIdentity is the identity the AgentSession reconciler hands the
	// workshop sidecar pod: run as this ServiceAccount (projected token) and
	// envFrom this Secret (the workshop bearer). Controller-owned status on an
	// object the runner cannot touch — the ONLY source BuildSidecarPod
	// consumes (spec §13).
	// +optional
	SidecarIdentity *WorkshopSidecarIdentity `json:"sidecarIdentity,omitempty"`
	// Export records the most recently stored drafted .oap bundle for this
	// workshop. Written by the tuple-authorized draft-export operator route
	// (pkg/web/workshopdraftsrv) immediately after it durably stores the
	// bytes under the builder session's scope — never by the sidecar, which
	// holds no update on this object's status subresource. Set-once per
	// digest: a re-export whose content is byte-identical to the last one
	// leaves this field (and ExportedAt) untouched, so a retry is not
	// volatile churn on a controller-owned field.
	// +optional
	Export *WorkshopExport `json:"export,omitempty"`
	// Standins is the AUTHORITATIVE registry of every credential-free
	// rehearsal stand-in AgentClass this workshop has projected (agent-builder
	// plan 9b, Task 1; pkg/web/workshopprojectsrv), written by the
	// tuple-authorized project-agent operator route immediately after it
	// creates or updates the stand-in AgentClass — never by the sidecar,
	// which holds no update on this object's status subresource (same
	// division of labor as Export, above).
	//
	// This is the fix for a real hole: the stand-in AgentClass itself also
	// carries AnnotationStandinSource, but that annotation is a
	// human-readable label ONLY and must NEVER be trusted as a security
	// decision — a builder's own workshop_apply tool server-side-applies
	// arbitrary metadata.annotations into the workshop namespace, and the
	// admission webhook never inspects them, so a builder (or a
	// prompt-injected model driving one) can stamp the marker onto a class
	// it authored itself. This list is what the project-agent route's
	// collision check and soleAuthoredClassName's authored-count now consult
	// instead: a bare name is a stand-in for {SourceNamespace, SourceName}
	// iff an entry here says so, never because some object merely carries
	// the annotation. This is this repo's SSA discipline applied literally
	// (observations belong in controller-owned status, never a
	// client-writable field) — see CLAUDE.md's "Server-side apply" section.
	//
	// Set-once per (Name, SourceNamespace, SourceName): re-projecting the
	// same source is a no-op here. No timestamp, unlike Export/ExportedAt
	// above: an entry carries no field that a retry could ever leave stale
	// (Export's ExportedAt exists to record WHEN a digest was first seen;
	// a stand-in entry has no analogous volatile fact — the same three
	// fields are true for as long as the entry exists at all), so there is
	// nothing here for the SSA-churn rule to protect against.
	//
	// Bounded at spec.limits.maxObjectsPerKind entries: the project-agent
	// route writes as the operator, so it bypasses the admission webhook's
	// checkLimits (which only ever fires for a write attributed to the
	// sidecar's own SA) — capping this list at the SAME per-kind ceiling a
	// sidecar-authored AgentClass would have been held to is what keeps a
	// workshop's stand-in count from becoming an unbounded mint through this
	// one route (see pkg/web/workshopprojectsrv's own package doc).
	// +optional
	// +listType=atomic
	Standins []WorkshopStandin `json:"standins,omitempty"`
	// Install is the observed state of spec.installRequest. status.install.phase
	// (Requested→Approved/Installed or Declined/Failed) is written by TWO owners
	// with disjoint fields: the channelsd WorkshopHandoffWatcher sets Requested +
	// DeliveredAt when it delivers the admin card; admind sets Installed/Declined/
	// Failed + ApprovedBy + InstalledRef on the admin's click. Volatile values here.
	// +optional
	Install *WorkshopInstallStatus `json:"install,omitempty"`
	// CapabilityRequest is the observed delivery state of spec.capabilityRequest,
	// written SOLELY by the WorkshopHandoffWatcher.
	// +optional
	CapabilityRequest *WorkshopCapabilityRequestStatus `json:"capabilityRequest,omitempty"`
	// CredentialRequests mirrors spec.credentialRequests with the observed
	// delivery state. Written SOLELY by the channelsd WorkshopCredentialWatcher
	// (deliveredAt/noticeRef) — a single writer, so the atomic list cannot
	// clobber. Volatile values live HERE, never in the applied spec.
	// +optional
	// +listType=atomic
	CredentialRequests []WorkshopCredentialRequestStatus `json:"credentialRequests,omitempty"`
	// CloseRequests is the Workshop controller's decision for each
	// spec.closeRequests entry — Closed, Refused or NotFound — written SOLELY
	// by that controller, one entry per requested spelling, SET ONCE. A
	// decision already recorded for a target is what stops it being decided
	// (and closed) a second time under any spelling, so nothing here is ever
	// rewritten.
	// +optional
	// +listType=map
	// +listMapKey=target
	CloseRequests []WorkshopCloseStatus `json:"closeRequests,omitempty"`
	// TestWatch is the observed state of spec.testWatch, written SOLELY by the
	// Workshop controller. Set-on-change: each delivered event is appended
	// once; a replaced watch resets it.
	// +optional
	TestWatch *WorkshopTestWatchStatus `json:"testWatch,omitempty"`
	// +optional
	// +patchStrategy=merge
	// +patchMergeKey=type
	Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}

func (*WorkshopStatus) DeepCopy

func (in *WorkshopStatus) DeepCopy() *WorkshopStatus

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkshopStatus.

func (*WorkshopStatus) DeepCopyInto

func (in *WorkshopStatus) DeepCopyInto(out *WorkshopStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WorkshopTestWatch

type WorkshopTestWatch struct {
	// Class is the AgentClass (in the workshop namespace) whose sessions count.
	// +kubebuilder:validation:MinLength=1
	Class string `json:"class"`
	// StartedAt is when the watch began; only sessions created after it are
	// the person's test.
	StartedAt metav1.Time `json:"startedAt"`
	// Deadline is when the watch times out.
	Deadline metav1.Time `json:"deadline"`
}

WorkshopTestWatch asks the Workshop controller to watch for the person's own test of one class authored in this workshop, and to wake the builder as it starts, pauses, resumes, ends, or times out. Written by the sidecar's watch_test tool through its update grant on this CR — a plain Update, not an SSA apply.

(Class, StartedAt) is the watch's IDENTITY: the controller keys its own delivered-events record on the pair (testwatch.go's reconcileTestWatch) and ignores sessions created before StartedAt. So StartedAt is volatile, not idempotent — a watch_test call for a new test stamps the current time and begins a new record — with one deliberate exception: a call for the same class while its recorded test is still running KEEPS the pair and moves only Deadline, because a new StartedAt there would arm a watch that could never see the test already under way (handleWatchTest's watchStillCoversItsTest, pkg/tools/workshopmcp/tools_trytest.go).

The observations this watch produces live in status.testWatch, never here.

func (*WorkshopTestWatch) DeepCopy

func (in *WorkshopTestWatch) DeepCopy() *WorkshopTestWatch

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkshopTestWatch.

func (*WorkshopTestWatch) DeepCopyInto

func (in *WorkshopTestWatch) DeepCopyInto(out *WorkshopTestWatch)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WorkshopTestWatchStatus

type WorkshopTestWatchStatus struct {
	// Class echoes the spec.testWatch.class this status describes, so a
	// replaced watch (a different class or startedAt) starts a fresh record.
	Class string `json:"class"`
	// StartedAt echoes spec.testWatch.startedAt for the same reason.
	StartedAt metav1.Time `json:"startedAt"`
	// Session is the test session, once one qualified.
	// +optional
	Session string `json:"session,omitempty"`
	// Delivered lists the events the builder has been told about, in order:
	// started, paused, resumed, ended, timedOut. Each is delivered at most
	// once, except paused and resumed, which repeat each time the session
	// pauses and comes back. A re-armed watch that kept its identity drops
	// timedOut so the rest of that test can still be reported.
	// +optional
	// +listType=atomic
	Delivered []string `json:"delivered,omitempty"`
	// LastPhase is the test session's phase at the last delivery, so a repeat
	// of the same phase delivers nothing.
	// +optional
	LastPhase string `json:"lastPhase,omitempty"`
}

WorkshopTestWatchStatus is the controller's record of a spec.testWatch.

func (*WorkshopTestWatchStatus) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkshopTestWatchStatus.

func (*WorkshopTestWatchStatus) DeepCopyInto

func (in *WorkshopTestWatchStatus) DeepCopyInto(out *WorkshopTestWatchStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WorkspaceBase

type WorkspaceBase struct {
	// Size is the base PVC request (default 2Gi).
	// +optional
	Size string `json:"size,omitempty"`
	// Refresh controls whether/how often the shared base is re-pulled from the
	// origin. "" or "onDemand" (default) never auto-refreshes. A Go duration
	// (e.g. "10m") re-pulls the base on that interval so new sessions overlay
	// off a near-current base and pull only the delta.
	// +optional
	Refresh string `json:"refresh,omitempty"`
}

WorkspaceBase configures the shared read-cache base.

func (*WorkspaceBase) DeepCopy

func (in *WorkspaceBase) DeepCopy() *WorkspaceBase

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceBase.

func (*WorkspaceBase) DeepCopyInto

func (in *WorkspaceBase) DeepCopyInto(out *WorkspaceBase)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WorkspaceConfig

type WorkspaceConfig struct {
	// Mode is whether /workspace is a shared RWX volume or private to this
	// sandbox.
	// +kubebuilder:default=shared
	Mode WorkspaceMode `json:"mode,omitempty"`

	// SharedClaimName is the name of the RWX PersistentVolumeClaim to mount
	// at /workspace. Set by the AgentSession controller for bundle sessions
	// when a workspace StorageClass is configured. Ignored when Mode is
	// isolated or this is empty.
	// +optional
	SharedClaimName string `json:"sharedClaimName,omitempty"`
}

func (*WorkspaceConfig) DeepCopy

func (in *WorkspaceConfig) DeepCopy() *WorkspaceConfig

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceConfig.

func (*WorkspaceConfig) DeepCopyInto

func (in *WorkspaceConfig) DeepCopyInto(out *WorkspaceConfig)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WorkspaceMode

type WorkspaceMode string

+kubebuilder:validation:Enum=shared;isolated

const (
	WorkspaceShared   WorkspaceMode = "shared"
	WorkspaceIsolated WorkspaceMode = "isolated"
)

type WorkspaceScope

type WorkspaceScope struct {
	// Paths is the allowlist; empty exposes the whole checkout.
	// +optional
	Paths []WorkspaceScopePath `json:"paths,omitempty"`
}

WorkspaceScope is the exposure boundary — the allowlist of subpaths.

func (*WorkspaceScope) DeepCopy

func (in *WorkspaceScope) DeepCopy() *WorkspaceScope

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceScope.

func (*WorkspaceScope) DeepCopyInto

func (in *WorkspaceScope) DeepCopyInto(out *WorkspaceScope)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WorkspaceScopePath

type WorkspaceScopePath struct {
	// Path is a subpath relative to the checkout root.
	Path string `json:"path"`
	// Writable permits the session to modify this subtree; default is read-only.
	// +optional
	Writable bool `json:"writable,omitempty"`
}

func (*WorkspaceScopePath) DeepCopy

func (in *WorkspaceScopePath) DeepCopy() *WorkspaceScopePath

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceScopePath.

func (*WorkspaceScopePath) DeepCopyInto

func (in *WorkspaceScopePath) DeepCopyInto(out *WorkspaceScopePath)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WorkspaceSource

type WorkspaceSource struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   WorkspaceSourceSpec   `json:"spec,omitempty"`
	Status WorkspaceSourceStatus `json:"status,omitempty"`
}

WorkspaceSource declares a named, pluggable origin that per-session workspaces are cut from. The origin kind is registered (pkg/platform/workspacekinds); git is the backend that exists today.

Namespaced. Reconciled by pkg/controllers/workspacesource, which validates the spec against the named kind, provisions a shared base PVC, and runs a Job to materialize the checkout into it. Sessions never clone the origin themselves: the AgentSession reconciler cuts a copy-on-write overlay off that shared base once, then freezes it for the session lifetime, so re-pointing this object mid-session does not move a running workspace.

func (*WorkspaceSource) DeepCopy

func (in *WorkspaceSource) DeepCopy() *WorkspaceSource

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceSource.

func (*WorkspaceSource) DeepCopyInto

func (in *WorkspaceSource) DeepCopyInto(out *WorkspaceSource)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*WorkspaceSource) DeepCopyObject

func (in *WorkspaceSource) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type WorkspaceSourceList

type WorkspaceSourceList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []WorkspaceSource `json:"items"`
}

+kubebuilder:object:root=true

func (*WorkspaceSourceList) DeepCopy

func (in *WorkspaceSourceList) DeepCopy() *WorkspaceSourceList

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceSourceList.

func (*WorkspaceSourceList) DeepCopyInto

func (in *WorkspaceSourceList) DeepCopyInto(out *WorkspaceSourceList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*WorkspaceSourceList) DeepCopyObject

func (in *WorkspaceSourceList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type WorkspaceSourceRef

type WorkspaceSourceRef struct {
	// Kind selects the registered driver (e.g. "git").
	// +kubebuilder:validation:Pattern=`^[a-z0-9]+$`
	Kind string `json:"kind"`
	// Locator is the driver-parsed source location (repo URL, file:// path, …).
	Locator string `json:"locator"`
	// Ref is the driver-specific revision (a git branch, tag or SHA); empty
	// takes the driver's default.
	// +optional
	Ref string `json:"ref,omitempty"`
	// Config is driver-specific extra configuration, opaque here.
	// +optional
	Config map[string]string `json:"config,omitempty"`
}

WorkspaceSourceRef identifies the driver and what it points at.

func (*WorkspaceSourceRef) DeepCopy

func (in *WorkspaceSourceRef) DeepCopy() *WorkspaceSourceRef

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceSourceRef.

func (*WorkspaceSourceRef) DeepCopyInto

func (in *WorkspaceSourceRef) DeepCopyInto(out *WorkspaceSourceRef)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WorkspaceSourceSpec

type WorkspaceSourceSpec struct {
	// Source is the driver and the location it points at.
	Source WorkspaceSourceRef `json:"source"`
	// Scope narrows which subpaths a session may see; empty exposes everything.
	// +optional
	Scope WorkspaceScope `json:"scope,omitempty"`
	// Base configures the shared read-cache every session overlays off.
	// +optional
	Base WorkspaceBase `json:"base,omitempty"`
}

func (*WorkspaceSourceSpec) DeepCopy

func (in *WorkspaceSourceSpec) DeepCopy() *WorkspaceSourceSpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceSourceSpec.

func (*WorkspaceSourceSpec) DeepCopyInto

func (in *WorkspaceSourceSpec) DeepCopyInto(out *WorkspaceSourceSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type WorkspaceSourceStatus

type WorkspaceSourceStatus struct {
	// ObservedGeneration is the spec generation this status reflects.
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`
	// BaseClaimName is the shared base PVC sessions cut their overlay from.
	// +optional
	BaseClaimName string `json:"baseClaimName,omitempty"`
	// LastMaterializedAt is when the base checkout was first populated.
	// +optional
	LastMaterializedAt *metav1.Time `json:"lastMaterializedAt,omitempty"`
	// LastRefreshedAt is when a scheduled base refresh last completed.
	// +optional
	LastRefreshedAt *metav1.Time `json:"lastRefreshedAt,omitempty"`
	// Conditions carries Valid and Ready.
	// +patchStrategy=merge
	// +patchMergeKey=type
	// +optional
	Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}

func (*WorkspaceSourceStatus) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceSourceStatus.

func (*WorkspaceSourceStatus) DeepCopyInto

func (in *WorkspaceSourceStatus) DeepCopyInto(out *WorkspaceSourceStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL