Documentation
¶
Overview ¶
Package api holds the operator-facing Connect handlers: tasks and node administration. Identity comes from the transport middleware; nothing here authenticates anyone.
Index ¶
- Constants
- func NewAgentProxy(agentURL, agentToken string, logger *slog.Logger, ...) (http.Handler, error)
- func NewArtifactDownloadHandler(a *artifacts.Service, logger *slog.Logger) http.Handler
- func NewTurnSurfaceProxy(agentURL string, logger *slog.Logger) (http.Handler, error)
- func NewUIHandler(logger *slog.Logger) http.Handler
- type ArtifactService
- func (s *ArtifactService) GetArtifactURL(ctx context.Context, req *connect.Request[podiumv1.GetArtifactURLRequest]) (*connect.Response[podiumv1.GetArtifactURLResponse], error)
- func (s *ArtifactService) ListArtifacts(ctx context.Context, req *connect.Request[podiumv1.ListArtifactsRequest]) (*connect.Response[podiumv1.ListArtifactsResponse], error)
- type Canceller
- type Drainer
- type Events
- type IdentityService
- type Injector
- type NodeAdminService
- func (s *NodeAdminService) CreateEnrollmentToken(ctx context.Context, ...) (*connect.Response[podiumv1.CreateEnrollmentTokenResponse], error)
- func (s *NodeAdminService) DeleteNode(ctx context.Context, req *connect.Request[podiumv1.DeleteNodeRequest]) (*connect.Response[podiumv1.DeleteNodeResponse], error)
- func (s *NodeAdminService) DrainNode(ctx context.Context, req *connect.Request[podiumv1.DrainNodeRequest]) (*connect.Response[podiumv1.DrainNodeResponse], error)
- func (s *NodeAdminService) ListNodes(ctx context.Context, _ *connect.Request[podiumv1.ListNodesRequest]) (*connect.Response[podiumv1.ListNodesResponse], error)
- func (s *NodeAdminService) RekeyNode(ctx context.Context, req *connect.Request[podiumv1.RekeyNodeRequest]) (*connect.Response[podiumv1.RekeyNodeResponse], error)
- func (s *NodeAdminService) SetNodeLabels(ctx context.Context, req *connect.Request[podiumv1.SetNodeLabelsRequest]) (*connect.Response[podiumv1.SetNodeLabelsResponse], error)
- func (s *NodeAdminService) SetNodeSlots(ctx context.Context, req *connect.Request[podiumv1.SetNodeSlotsRequest]) (*connect.Response[podiumv1.SetNodeSlotsResponse], error)
- func (s *NodeAdminService) UndrainNode(ctx context.Context, req *connect.Request[podiumv1.UndrainNodeRequest]) (*connect.Response[podiumv1.UndrainNodeResponse], error)
- type PreviewReleaser
- type RegistryService
- func (s *RegistryService) DeleteRegistry(ctx context.Context, req *connect.Request[podiumv1.DeleteRegistryRequest]) (*connect.Response[podiumv1.DeleteRegistryResponse], error)
- func (s *RegistryService) ListRegistries(ctx context.Context, _ *connect.Request[podiumv1.ListRegistriesRequest]) (*connect.Response[podiumv1.ListRegistriesResponse], error)
- func (s *RegistryService) SetRegistry(ctx context.Context, req *connect.Request[podiumv1.SetRegistryRequest]) (*connect.Response[podiumv1.SetRegistryResponse], error)
- type SandboxProviderService
- func (s *SandboxProviderService) DeleteSandboxProvider(ctx context.Context, ...) (*connect.Response[podiumv1.DeleteSandboxProviderResponse], error)
- func (s *SandboxProviderService) ListSandboxProviders(ctx context.Context, _ *connect.Request[podiumv1.ListSandboxProvidersRequest]) (*connect.Response[podiumv1.ListSandboxProvidersResponse], error)
- func (s *SandboxProviderService) SetSandboxProvider(ctx context.Context, req *connect.Request[podiumv1.SetSandboxProviderRequest]) (*connect.Response[podiumv1.SetSandboxProviderResponse], error)
- type SandboxPusher
- type SecretChecker
- type SecretService
- func (s *SecretService) DeleteSecret(ctx context.Context, req *connect.Request[podiumv1.DeleteSecretRequest]) (*connect.Response[podiumv1.DeleteSecretResponse], error)
- func (s *SecretService) ListSecrets(ctx context.Context, req *connect.Request[podiumv1.ListSecretsRequest]) (*connect.Response[podiumv1.ListSecretsResponse], error)
- func (s *SecretService) ReadSecret(ctx context.Context, req *connect.Request[podiumv1.ReadSecretRequest]) (*connect.Response[podiumv1.ReadSecretResponse], error)
- func (s *SecretService) SetSecret(ctx context.Context, req *connect.Request[podiumv1.SetSecretRequest]) (*connect.Response[podiumv1.SetSecretResponse], error)
- type Sessions
- type SnapshotService
- func (s *SnapshotService) DeleteSandboxSnapshots(ctx context.Context, ...) (*connect.Response[podiumv1.DeleteSandboxSnapshotsResponse], error)
- func (s *SnapshotService) DeleteSnapshot(ctx context.Context, req *connect.Request[podiumv1.DeleteSnapshotRequest]) (*connect.Response[podiumv1.DeleteSnapshotResponse], error)
- func (s *SnapshotService) ListSandboxSnapshots(ctx context.Context, _ *connect.Request[podiumv1.ListSandboxSnapshotsRequest]) (*connect.Response[podiumv1.ListSandboxSnapshotsResponse], error)
- func (s *SnapshotService) ListSnapshots(ctx context.Context, _ *connect.Request[podiumv1.ListSnapshotsRequest]) (*connect.Response[podiumv1.ListSnapshotsResponse], error)
- func (s *SnapshotService) SetSandboxSnapshotRetention(ctx context.Context, ...) (*connect.Response[podiumv1.SetSandboxSnapshotRetentionResponse], error)
- func (s *SnapshotService) SetSnapshotRetention(ctx context.Context, ...) (*connect.Response[podiumv1.SetSnapshotRetentionResponse], error)
- type TaskService
- func (s *TaskService) CancelTask(ctx context.Context, req *connect.Request[podiumv1.CancelTaskRequest]) (*connect.Response[podiumv1.CancelTaskResponse], error)
- func (s *TaskService) CreateTask(ctx context.Context, req *connect.Request[podiumv1.CreateTaskRequest]) (*connect.Response[podiumv1.CreateTaskResponse], error)
- func (s *TaskService) GetTask(ctx context.Context, req *connect.Request[podiumv1.GetTaskRequest]) (*connect.Response[podiumv1.GetTaskResponse], error)
- func (s *TaskService) InjectTask(ctx context.Context, req *connect.Request[podiumv1.InjectTaskRequest]) (*connect.Response[podiumv1.InjectTaskResponse], error)
- func (s *TaskService) ListTasks(ctx context.Context, req *connect.Request[podiumv1.ListTasksRequest]) (*connect.Response[podiumv1.ListTasksResponse], error)
- func (s *TaskService) ReleasePreview(ctx context.Context, req *connect.Request[podiumv1.ReleasePreviewRequest]) (*connect.Response[podiumv1.ReleasePreviewResponse], error)
- func (s *TaskService) SetMaxPreviewTTL(d time.Duration)
- func (s *TaskService) StreamTaskEvents(ctx context.Context, req *connect.Request[podiumv1.StreamTaskEventsRequest], ...) error
- type UserService
- func (s *UserService) ListUsers(ctx context.Context, _ *connect.Request[podiumv1.ListUsersRequest]) (*connect.Response[podiumv1.ListUsersResponse], error)
- func (s *UserService) SetUserRole(ctx context.Context, req *connect.Request[podiumv1.SetUserRoleRequest]) (*connect.Response[podiumv1.SetUserRoleResponse], error)
Constants ¶
const ( AgentScopeHeader = "X-Podium-Scope" // AgentInfraHeader matches internal/agent/api.InfraHeader. The proxy sets it for the // dev token and for an admin or owner, and strips a client-supplied copy. A member // editing the Slack bot's MCP list is refused by the conductor when this is absent. AgentInfraHeader = "X-Podium-Infra" )
AgentScopeHeader tells the conductor whether this caller is the shared dev token. agentScopeAll is the operator view (every chat). A signed-in user gets no scope header, which the conductor reads as "own chats only". A client-supplied value is stripped with the login header: the browser must not be able to widen its own view.
const AgentLoginHeader = "X-Podium-Login"
AgentLoginHeader is what the proxy tells the conductor about the human on the other end.
It is a plain header, not a signed assertion. The conductor trusts it only because the bearer proves the request came through this server, which is the only party that knows the token and the only one that authenticated the caller. If the conductor is ever exposed beyond loopback or the compose network, this has to become a signed assertion — docs/security.md says so.
const ArtifactDownloadPrefix = "/artifacts/"
ArtifactDownloadPrefix is the HTTP path the server proxies artifact bytes on. It is not a Connect procedure on purpose: a 512 MB artifact must stream, and a unary response would have to hold all of it in memory at both ends.
const DefaultMaxPreviewTTL = 24 * time.Hour
DefaultMaxPreviewTTL caps how long a preview may ask to stay up, PODIUM_PREVIEW_MAX_TTL. A held preview keeps its node slot, so an unbounded one is a slot lost for good.
const MaxNodeSlots = 256
MaxNodeSlots is the largest slot count SetNodeSlots accepts. It is a guard against a typo, not a considered limit on what a machine can do: the scheduler assigns straight up to this number, so a stray zero on the end would pile hundreds of containers onto one box before anybody noticed. An operator who really wants more can raise max_tasks in the node's own configuration, where the number sits next to the machine it describes.
const TurnSurfacePrefix = "/turn-surface"
TurnSurfacePrefix is where this server serves the conductor's turn surface: under a path of its own, apart from the UI and the operator APIs, so it can be told apart — routed, logged, rate limited or firewalled — from everything else this server answers. A sandbox is given PODIUM_PUBLIC_URL + TurnSurfacePrefix as PODIUM_AGENT_SANDBOX_URL.
Variables ¶
This section is empty.
Functions ¶
func NewAgentProxy ¶
func NewAgentProxy(agentURL, agentToken string, logger *slog.Logger, infra ...func(context.Context) bool) (http.Handler, error)
NewAgentProxy reverse-proxies the conductor's Connect service.
Mount it behind transport.WithIdentity on /podium.agent.v1.AgentService/ only. Two things happen on every request and both matter: any client-supplied Authorization and X-Podium-Login are deleted, and the server's own pair is set. The browser's credential is the dev token, which the conductor must never see; the login is the server's word about who is calling, which a client must never be able to write.
The conductor's /healthz, /readyz and /metrics are deliberately not proxied. They are its own operational surface, they are unauthenticated on its listener, and publishing them through an authenticated origin would put a second, differently-shaped health story in front of an operator.
infra, when set, says whether a signed-in person may edit the Slack bot's MCP list. The dev token always may. Callers that pass only the logger leave infra unset, so a KindUser is not marked as an operator.
func NewArtifactDownloadHandler ¶
NewArtifactDownloadHandler streams an artifact's bytes through the server. It is what `podium artifact get` uses by default: the CLI is guaranteed a route to the control plane and is not guaranteed one to the object store.
func NewTurnSurfaceProxy ¶ added in v0.8.0
NewTurnSurfaceProxy reverse-proxies the conductor's turn surface, so a cloud sandbox reaches it at this server's public address under TurnSurfacePrefix (PODIUM_AGENT_SANDBOX_URL=PODIUM_PUBLIC_URL/turn-surface) instead of through a tunnel of its own to PODIUM_AGENT_SURFACE_LISTEN.
It is the opposite of NewAgentProxy in the one way that matters: the request's own Authorization — the turn's credential — goes through untouched, and the server's bearer is never added, so nothing that arrives here can act as the server. The headers the conductor reads as the server's word about a person are stripped. Mount it on TurnSurfacePrefix + "/": the prefix is removed, and a path that is not one of the surface's routes is 404 here, never forwarded — the conductor's operator API is on the same listener.
func NewUIHandler ¶
NewUIHandler serves the embedded single-page app with an SPA fallback: any path that is not a real file is answered with index.html so the client router can take it. Mount it on "/" after the RPC prefixes — it is the last resort, and it is deliberately unauthenticated, because the bundle is not a secret and the API behind it still demands a token.
A binary built with `-tags noui`, or one built before `make web` ever ran, has no index.html; it gets a handler that says so instead of a confusing 404.
Types ¶
type ArtifactService ¶
type ArtifactService struct {
// contains filtered or unexported fields
}
ArtifactService implements podium.v1.ArtifactService.
func NewArtifactService ¶
func NewArtifactService(a *artifacts.Service, logger *slog.Logger) *ArtifactService
NewArtifactService returns the operator-facing artifact handlers.
func (*ArtifactService) GetArtifactURL ¶
func (s *ArtifactService) GetArtifactURL( ctx context.Context, req *connect.Request[podiumv1.GetArtifactURLRequest], ) (*connect.Response[podiumv1.GetArtifactURLResponse], error)
GetArtifactURL mints a presigned GET. A caller with no route to the object store fetches the same bytes from ArtifactDownloadPrefix instead.
func (*ArtifactService) ListArtifacts ¶
func (s *ArtifactService) ListArtifacts( ctx context.Context, req *connect.Request[podiumv1.ListArtifactsRequest], ) (*connect.Response[podiumv1.ListArtifactsResponse], error)
ListArtifacts returns everything stored for a task, rolled-up logs included.
type Drainer ¶
type Drainer interface {
SetDrain(ctx context.Context, nodeID string, draining bool) error
SetSlots(ctx context.Context, nodeID string, maxTasks int32) error
}
Drainer pushes an operator's standing instructions at a connected node: what it may not take, and how much of it.
type Events ¶
type Events interface {
Subscribe(ctx context.Context, taskID string, fromSeq uint64) (<-chan *podiumv1.TaskEvent, error)
}
Events is the logs service, as much of it as the API needs.
type IdentityService ¶
type IdentityService struct {
// contains filtered or unexported fields
}
IdentityService implements podium.v1.IdentityService. WhoAmI is still mostly the Identity the transport put in the context; when Google Workspace sign-in is on, it also reports the instance claim so the web UI can show the claim screen instead of the app.
func NewIdentityService ¶
func NewIdentityService(st *store.Store, agentEnabled, googleEnabled bool) *IdentityService
NewIdentityService returns the identity handler. agentEnabled is cfg.AgentEnabled(): whether this server proxies the conductor's API, which is how the web UI knows to show its Agent screen at all. googleEnabled is whether PODIUM_GOOGLE_OAUTH_CLIENT_ID is set.
func (*IdentityService) Claim ¶ added in v0.2.0
func (s *IdentityService) Claim( ctx context.Context, req *connect.Request[podiumv1.ClaimRequest], ) (*connect.Response[podiumv1.ClaimResponse], error)
Claim binds an unclaimed instance to the caller's domain. The typed hosted_domain must match; a second claim from anyone else is FailedPrecondition.
func (*IdentityService) WhoAmI ¶
func (s *IdentityService) WhoAmI( ctx context.Context, _ *connect.Request[podiumv1.WhoAmIRequest], ) (*connect.Response[podiumv1.WhoAmIResponse], error)
WhoAmI reports who the transport says the caller is.
The call is behind the same identity middleware as everything else, and that is the point: a client that gets an answer without presenting a credential knows it is on a tailnet and needs no login step, and a client that gets 401 knows it must supply the dev token. The web UI uses exactly that to decide whether to prompt. When Google sign-in is configured, can_claim tells the UI to show the claim screen instead of the rest of the app.
type Injector ¶
type Injector interface {
Inject(ctx context.Context, nodeID, taskID, text string) error
EndWait(ctx context.Context, nodeID, taskID string) error
}
Injector is the node registry's path for a human message into a running task, and for ending a finished task's wait for one.
type NodeAdminService ¶
type NodeAdminService struct {
// contains filtered or unexported fields
}
NodeAdminService implements podium.v1.NodeAdminService.
func NewNodeAdminService ¶
func NewNodeAdminService(st *store.Store, sessions Sessions, drains Drainer, logger *slog.Logger) *NodeAdminService
NewNodeAdminService returns the node administration API.
func (*NodeAdminService) CreateEnrollmentToken ¶
func (s *NodeAdminService) CreateEnrollmentToken( ctx context.Context, req *connect.Request[podiumv1.CreateEnrollmentTokenRequest], ) (*connect.Response[podiumv1.CreateEnrollmentTokenResponse], error)
CreateEnrollmentToken mints a single-use token. The plaintext is returned once and never stored; only its SHA-256 reaches Postgres, so it is never recoverable and never logged.
func (*NodeAdminService) DeleteNode ¶
func (s *NodeAdminService) DeleteNode( ctx context.Context, req *connect.Request[podiumv1.DeleteNodeRequest], ) (*connect.Response[podiumv1.DeleteNodeResponse], error)
DeleteNode forgets a node. It refuses one that still holds a stream unless it has been drained, and refuses one that still has tasks on it whatever its state: tasks.node_id is a foreign key, and a task whose node row vanished is a task nobody can explain.
func (*NodeAdminService) DrainNode ¶
func (s *NodeAdminService) DrainNode( ctx context.Context, req *connect.Request[podiumv1.DrainNodeRequest], ) (*connect.Response[podiumv1.DrainNodeResponse], error)
DrainNode stops a node being given new work. Whatever it is running finishes, which is the whole point: a drain is how a machine is taken out of service without killing the jobs that are already on it.
The flag is a column, so it survives both daemons restarting and applies to a node that is offline right now — draining a machine before you power it back on is a reasonable thing to want. The live session is told as well, so the scheduler stops considering the node immediately rather than at its next heartbeat.
func (*NodeAdminService) ListNodes ¶
func (s *NodeAdminService) ListNodes( ctx context.Context, _ *connect.Request[podiumv1.ListNodesRequest], ) (*connect.Response[podiumv1.ListNodesResponse], error)
ListNodes returns every enrolled node, oldest first, with live slot counts for the ones that currently hold a stream on this server.
func (*NodeAdminService) RekeyNode ¶
func (s *NodeAdminService) RekeyNode( ctx context.Context, req *connect.Request[podiumv1.RekeyNodeRequest], ) (*connect.Response[podiumv1.RekeyNodeResponse], error)
RekeyNode unbinds a node from the Tailscale device it enrolled from. It is what an operator runs when a worker is rebuilt or replaced: the node keeps its ID, its labels and its history, and the next Hello binds it to whatever device it arrives from. Until then the node key alone is enough to connect, which is exactly the exposure the binding removes — so rekey a node when you are about to move it, not as a matter of routine.
func (*NodeAdminService) SetNodeLabels ¶
func (s *NodeAdminService) SetNodeLabels( ctx context.Context, req *connect.Request[podiumv1.SetNodeLabelsRequest], ) (*connect.Response[podiumv1.SetNodeLabelsResponse], error)
SetNodeLabels changes what a node is eligible for after it has enrolled. Until this existed the labels a node was given at enrollment were the labels it had for good, and tagging a machine meant editing nodes.labels by hand and restarting the daemon so its session re-read the row.
It adds and removes rather than replacing, because a whole-list write is a race between two operators who each meant to change one label. The live session is retagged too, so the scheduler routes on the new set immediately rather than at the next reconnect.
func (*NodeAdminService) SetNodeSlots ¶
func (s *NodeAdminService) SetNodeSlots( ctx context.Context, req *connect.Request[podiumv1.SetNodeSlotsRequest], ) (*connect.Response[podiumv1.SetNodeSlotsResponse], error)
SetNodeSlots changes how many tasks a node runs at once. Zero clears the instruction, and the node goes back to the max_tasks in its own configuration file.
It is stored against the node rather than sent and forgotten, for the same reason a drain is: an operator who caps a machine at two tasks means it for the machine, and a value that evaporated on the next reconnect would be worse than no feature at all. A node that is offline right now is a legitimate thing to configure — every stream is sent the count just after its HelloAck, so the node picks it up on its next connection.
func (*NodeAdminService) UndrainNode ¶
func (s *NodeAdminService) UndrainNode( ctx context.Context, req *connect.Request[podiumv1.UndrainNodeRequest], ) (*connect.Response[podiumv1.UndrainNodeResponse], error)
UndrainNode puts a drained node back in the pool.
type PreviewReleaser ¶ added in v0.6.0
type PreviewReleaser interface {
ReleasePreview(ctx context.Context, nodeID, taskID, reason string) error
}
PreviewReleaser is the node registry's path for ending a finished task's preview.
type RegistryService ¶
type RegistryService struct {
// contains filtered or unexported fields
}
RegistryService implements podium.v1.RegistryService. It is the secret store's second table under the same rules: no read endpoint, any authenticated caller, actor recorded.
func NewRegistryService ¶
func NewRegistryService(svc *secrets.Service, logger *slog.Logger) *RegistryService
NewRegistryService returns the registry API.
func (*RegistryService) DeleteRegistry ¶
func (s *RegistryService) DeleteRegistry( ctx context.Context, req *connect.Request[podiumv1.DeleteRegistryRequest], ) (*connect.Response[podiumv1.DeleteRegistryResponse], error)
DeleteRegistry removes a registry login. The next pull from that host is anonymous.
func (*RegistryService) ListRegistries ¶
func (s *RegistryService) ListRegistries( ctx context.Context, _ *connect.Request[podiumv1.ListRegistriesRequest], ) (*connect.Response[podiumv1.ListRegistriesResponse], error)
ListRegistries returns metadata only: hosts, usernames, key ids and who set them.
func (*RegistryService) SetRegistry ¶
func (s *RegistryService) SetRegistry( ctx context.Context, req *connect.Request[podiumv1.SetRegistryRequest], ) (*connect.Response[podiumv1.SetRegistryResponse], error)
SetRegistry creates or replaces the login for one registry host. The request's password is zeroed before the handler returns.
type SandboxProviderService ¶ added in v0.7.0
type SandboxProviderService struct {
// contains filtered or unexported fields
}
SandboxProviderService implements podium.v1.SandboxProviderService: the secret store's third table, under the registries' rules. A change reaches the provider's nodes at once.
func NewSandboxProviderService ¶ added in v0.7.0
func NewSandboxProviderService(svc *secrets.Service, nodes SandboxPusher, logger *slog.Logger) *SandboxProviderService
NewSandboxProviderService returns the sandbox provider API.
func (*SandboxProviderService) DeleteSandboxProvider ¶ added in v0.7.0
func (s *SandboxProviderService) DeleteSandboxProvider( ctx context.Context, req *connect.Request[podiumv1.DeleteSandboxProviderRequest], ) (*connect.Response[podiumv1.DeleteSandboxProviderResponse], error)
DeleteSandboxProvider removes a provider; its nodes stop taking work.
func (*SandboxProviderService) ListSandboxProviders ¶ added in v0.7.0
func (s *SandboxProviderService) ListSandboxProviders( ctx context.Context, _ *connect.Request[podiumv1.ListSandboxProvidersRequest], ) (*connect.Response[podiumv1.ListSandboxProvidersResponse], error)
ListSandboxProviders returns settings and metadata, never a credential.
func (*SandboxProviderService) SetSandboxProvider ¶ added in v0.7.0
func (s *SandboxProviderService) SetSandboxProvider( ctx context.Context, req *connect.Request[podiumv1.SetSandboxProviderRequest], ) (*connect.Response[podiumv1.SetSandboxProviderResponse], error)
SetSandboxProvider creates or replaces a provider. The request's secret is zeroed before the handler returns.
type SandboxPusher ¶ added in v0.7.0
type SandboxPusher interface {
PushSandboxConfig(ctx context.Context, provider string)
SandboxUsage(ctx context.Context) map[string]nodes.SandboxUsage
}
SandboxPusher re-sends a provider's configuration to its connected nodes, and says how many sandboxes they hold.
type SecretChecker ¶
SecretChecker answers whether the secrets a spec names exist, without reading their values. It is optional: a server with no master key has no secrets to check against and refuses the task at assignment instead.
type SecretService ¶
type SecretService struct {
// contains filtered or unexported fields
}
SecretService implements podium.v1.SecretService.
No person can read a value back. A value goes in through SetSecret and comes back out inside an Assign, on its way to the node about to run the task that referenced it, or through ReadSecret to the conductor alone, for its own credentials. A list returns names and metadata: every global secret, plus the caller's own personal secrets, and never another login's.
func NewSecretService ¶
func NewSecretService(svc *secrets.Service, users userLookup, logger *slog.Logger) *SecretService
NewSecretService returns the secret API. users may be nil, in which case a signed-in person cannot write a global secret: the role cannot be checked, so the write is refused.
func (*SecretService) DeleteSecret ¶
func (s *SecretService) DeleteSecret( ctx context.Context, req *connect.Request[podiumv1.DeleteSecretRequest], ) (*connect.Response[podiumv1.DeleteSecretResponse], error)
DeleteSecret removes a secret the caller is allowed to write. Tasks already assigned keep the copy inside their Assign; the next task that references the name fails to resolve.
func (*SecretService) ListSecrets ¶
func (s *SecretService) ListSecrets( ctx context.Context, req *connect.Request[podiumv1.ListSecretsRequest], ) (*connect.Response[podiumv1.ListSecretsResponse], error)
ListSecrets returns metadata only: every global name plus the caller's own personal names. A dev token and a node have no personal secrets, so they see globals. The conductor sees globals, and, when it names a login, that login's MCP and model provider credentials.
func (*SecretService) ReadSecret ¶ added in v0.6.0
func (s *SecretService) ReadSecret( ctx context.Context, req *connect.Request[podiumv1.ReadSecretRequest], ) (*connect.Response[podiumv1.ReadSecretResponse], error)
ReadSecret returns one value to the conductor, for the names it keeps for its own use. Every other caller and every other name is refused the same way.
func (*SecretService) SetSecret ¶
func (s *SecretService) SetSecret( ctx context.Context, req *connect.Request[podiumv1.SetSecretRequest], ) (*connect.Response[podiumv1.SetSecretResponse], error)
SetSecret creates or replaces a secret and returns its metadata. The request's value is zeroed before the handler returns. An unspecified scope is global.
type Sessions ¶
type Sessions interface {
SnapshotOf(nodeID string) (nodes.Snapshot, bool)
SetLabels(nodeID string, labels []string) bool
}
Sessions is the live-session half of the node registry.
type SnapshotService ¶ added in v0.7.2
type SnapshotService struct {
// contains filtered or unexported fields
}
SnapshotService implements podium.v1.SnapshotService.
func NewSnapshotService ¶ added in v0.7.2
func NewSnapshotService(svc *snapshots.Service, sandbox *sandboxsnaps.Service, logger *slog.Logger) *SnapshotService
NewSnapshotService returns the operator-facing snapshot handlers. sandbox may be nil, and then the sandbox snapshot RPCs list nothing and refuse changes.
func (*SnapshotService) DeleteSandboxSnapshots ¶ added in v0.9.0
func (s *SnapshotService) DeleteSandboxSnapshots( ctx context.Context, req *connect.Request[podiumv1.DeleteSandboxSnapshotsRequest], ) (*connect.Response[podiumv1.DeleteSandboxSnapshotsResponse], error)
DeleteSandboxSnapshots deletes the images of the sessions named, one or many.
func (*SnapshotService) DeleteSnapshot ¶ added in v0.7.2
func (s *SnapshotService) DeleteSnapshot( ctx context.Context, req *connect.Request[podiumv1.DeleteSnapshotRequest], ) (*connect.Response[podiumv1.DeleteSnapshotResponse], error)
DeleteSnapshot removes one session's snapshot, row first and object second.
func (*SnapshotService) ListSandboxSnapshots ¶ added in v0.9.0
func (s *SnapshotService) ListSandboxSnapshots( ctx context.Context, _ *connect.Request[podiumv1.ListSandboxSnapshotsRequest], ) (*connect.Response[podiumv1.ListSandboxSnapshotsResponse], error)
ListSandboxSnapshots returns every session image a sandbox provider keeps, the policy, and which configured providers keep them.
func (*SnapshotService) ListSnapshots ¶ added in v0.7.2
func (s *SnapshotService) ListSnapshots( ctx context.Context, _ *connect.Request[podiumv1.ListSnapshotsRequest], ) (*connect.Response[podiumv1.ListSnapshotsResponse], error)
ListSnapshots returns every session snapshot and the retention policy. A server with no object store answers with enabled false rather than an error, so the page can say why.
func (*SnapshotService) SetSandboxSnapshotRetention ¶ added in v0.9.0
func (s *SnapshotService) SetSandboxSnapshotRetention( ctx context.Context, req *connect.Request[podiumv1.SetSandboxSnapshotRetentionRequest], ) (*connect.Response[podiumv1.SetSandboxSnapshotRetentionResponse], error)
SetSandboxSnapshotRetention replaces the policy and sweeps once with it.
func (*SnapshotService) SetSnapshotRetention ¶ added in v0.7.2
func (s *SnapshotService) SetSnapshotRetention( ctx context.Context, req *connect.Request[podiumv1.SetSnapshotRetentionRequest], ) (*connect.Response[podiumv1.SetSnapshotRetentionResponse], error)
SetSnapshotRetention replaces the policy and sweeps once with it.
type TaskService ¶
type TaskService struct {
// contains filtered or unexported fields
}
TaskService implements podium.v1.TaskService.
func NewTaskService ¶
func NewTaskService(st *store.Store, events Events, canceller Canceller, checker SecretChecker, logger *slog.Logger) *TaskService
NewTaskService returns the task API. injector may be the same object as canceller (the node registry implements both); nil means InjectTask is unavailable.
func (*TaskService) CancelTask ¶
func (s *TaskService) CancelTask( ctx context.Context, req *connect.Request[podiumv1.CancelTaskRequest], ) (*connect.Response[podiumv1.CancelTaskResponse], error)
CancelTask cancels a queued task outright and asks the node to stop anything further along. It never waits for the container to die: with the task command as PID 1 a default-disposition SIGTERM is discarded, so a cancel routinely costs the node's full 30s grace period. The terminal status lands when the exited/finished events arrive.
func (*TaskService) CreateTask ¶
func (s *TaskService) CreateTask( ctx context.Context, req *connect.Request[podiumv1.CreateTaskRequest], ) (*connect.Response[podiumv1.CreateTaskResponse], error)
CreateTask validates the spec and queues the task. Scheduling is the scheduler's problem.
func (*TaskService) GetTask ¶
func (s *TaskService) GetTask( ctx context.Context, req *connect.Request[podiumv1.GetTaskRequest], ) (*connect.Response[podiumv1.GetTaskResponse], error)
GetTask returns one task.
func (*TaskService) InjectTask ¶
func (s *TaskService) InjectTask( ctx context.Context, req *connect.Request[podiumv1.InjectTaskRequest], ) (*connect.Response[podiumv1.InjectTaskResponse], error)
InjectTask delivers one human message into a running task. The container stays up. With end_wait it instead tells a task that has answered to stop waiting for a follow-up.
func (*TaskService) ListTasks ¶
func (s *TaskService) ListTasks( ctx context.Context, req *connect.Request[podiumv1.ListTasksRequest], ) (*connect.Response[podiumv1.ListTasksResponse], error)
ListTasks pages newest-first. next_cursor is empty on the last page; feed it back verbatim.
func (*TaskService) ReleasePreview ¶ added in v0.6.0
func (s *TaskService) ReleasePreview( ctx context.Context, req *connect.Request[podiumv1.ReleasePreviewRequest], ) (*connect.Response[podiumv1.ReleasePreviewResponse], error)
ReleasePreview ends a finished task's preview before its ttl does. The row is released first and the node told second, so a node that is away tears it down when it reconnects.
func (*TaskService) SetMaxPreviewTTL ¶ added in v0.6.0
func (s *TaskService) SetMaxPreviewTTL(d time.Duration)
SetMaxPreviewTTL overrides DefaultMaxPreviewTTL. Zero or less keeps the default.
func (*TaskService) StreamTaskEvents ¶
func (s *TaskService) StreamTaskEvents( ctx context.Context, req *connect.Request[podiumv1.StreamTaskEventsRequest], stream *connect.ServerStream[podiumv1.TaskEvent], ) error
StreamTaskEvents replays everything stored after from_seq and then follows the task live. The two storage tables share the node's single per-task seq space, so the merged stream is in strict seq order. It ends when the task is terminal and every event has been sent.
type UserService ¶ added in v0.4.0
type UserService struct {
// contains filtered or unexported fields
}
UserService implements podium.v1.UserService.
func NewUserService ¶ added in v0.4.0
func NewUserService(st *store.Store, logger *slog.Logger) *UserService
NewUserService returns the users API.
func (*UserService) ListUsers ¶ added in v0.4.0
func (s *UserService) ListUsers( ctx context.Context, _ *connect.Request[podiumv1.ListUsersRequest], ) (*connect.Response[podiumv1.ListUsersResponse], error)
ListUsers returns every recorded login, most recently seen first.
func (*UserService) SetUserRole ¶ added in v0.4.0
func (s *UserService) SetUserRole( ctx context.Context, req *connect.Request[podiumv1.SetUserRoleRequest], ) (*connect.Response[podiumv1.SetUserRoleResponse], error)
SetUserRole replaces one login's role. The last owner cannot be demoted.