store

package
v0.7.21 Latest Latest
Warning

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

Go to latest
Published: Jul 21, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Index

Constants

View Source
const (
	NetnsSlotStateFree     = "free"
	NetnsSlotStateReserved = "reserved"
	NetnsSlotStateRealized = "realized"
	NetnsSlotStatePooled   = "pooled"
	NetnsSlotStateAdopted  = "adopted"
)

Container netns pool lifecycle states. Stored as TEXT; queries use literals.

View Source
const (
	FirecrackerVMMSlotStatusSpawning  = "spawning"
	FirecrackerVMMSlotStatusLoaded    = "loaded"
	FirecrackerVMMSlotStatusAllocated = "allocated"
	FirecrackerVMMSlotStatusReleased  = "released"
)

Firecracker warm-VMM pool status constants. Kept as bare strings rather than a typed enum because SQLite stores them as TEXT and the store-layer queries WHERE status='...' literals — a typed wrapper would just force every call site to string()-coerce. The state machine is documented inline in the CREATE TABLE statement above.

Variables

View Source
var ErrCustomDomainConflict = errors.New("custom domain hostname already taken")

ErrCustomDomainConflict is returned by AddCustomDomain when the hostname is already owned by a different sandbox. Surfaced through the API as 409. Same hostname for the same sandbox is idempotent (not a conflict) — that lets retries and reconcile re-converge without surfacing spurious errors.

View Source
var ErrCustomDomainPortMismatch = errors.New("custom domain target_port mismatch on re-add")

ErrCustomDomainPortMismatch surfaces an idempotent re-add of an already-attached hostname that carries a different target_port than the stored row. We never silently change the dial target — that would redirect live traffic without the caller knowing. The service layer translates this to HTTP 409 so the caller can detach + re-add deliberately.

View Source
var ErrJSBundleInUse = errors.New("js bundle is referenced by an active sandbox")

ErrJSBundleInUse blocks DELETE /v1/js-bundles/{digest} while an isolate sandbox still pins that bundle digest (plans/isolate-runtime.md §8).

View Source
var ErrNoFreeContainerNetnsSlot = errors.New("container netns pool: no free slot")

ErrNoFreeContainerNetnsSlot is returned when the netns pool is exhausted.

View Source
var ErrNoFreeFirecrackerTapSlot = errors.New("firecracker tap pool: no free slot")

ErrNoFreeFirecrackerTapSlot is returned by AllocateFirecrackerTapSlot when every slot is claimed. The Firecracker create path translates this into a 503-ish admission error upstream — operators see "pool exhausted" before the customer sees a confusing timeout.

View Source
var ErrNoFreeFirecrackerVMMSlot = errors.New("firecracker vmm pool: no loaded slot")

ErrNoFreeFirecrackerVMMSlot is returned by AllocateFirecrackerVMMSlot when no 'loaded' slot exists for the requested template. PR 4-B's caller treats this as the cold-spawn fallback signal, not an error state — the pool being momentarily empty is the expected behavior under load between spawn-and-load passes.

View Source
var ErrNoPooledContainerNetnsSlot = errors.New("container netns pool: no pooled slot")

ErrNoPooledContainerNetnsSlot means no prewarmed slot is available for claim.

View Source
var ErrNotFound = errors.New("sandbox not found")
View Source
var ErrSandboxNameConflict = errors.New("sandbox name already in use")

ErrSandboxNameConflict is returned by Create/Upsert when the sandbox's name collides with an existing row's name or id. Names are unique across the sandboxes table; empty names skip the name uniqueness check but ids still cannot collide with existing non-empty names.

View Source
var ErrSnapshotNameConflict = errors.New("snapshot name already in use")
View Source
var ErrTemplateIDConflict = errors.New("template id already in use")

ErrTemplateIDConflict is returned by CreateTemplate when a row with the caller-supplied ID already exists. Operators that retry POST /v1/templates with an explicit id get a 409 instead of a 500 — the standard idempotency shape the v1 API uses everywhere else.

View Source
var ErrTemplateInUse = errors.New("template is referenced by an active sandbox")

ErrTemplateInUse is the service-layer sentinel for "cannot delete: an active sandbox still names this template_id". DeleteTemplate returns it after IsTemplateReferenced returns true; the API translates it to 409. Held in the store package only because the SQL probe lives here.

View Source
var ErrVolumeExists = errors.New("volume already exists for this tenant")

ErrVolumeExists is returned by CreateVolume when the (tenant, name) pair is already taken. Facades map this to 409 Conflict. It is distinct from a generic constraint error so callers can branch on it for idempotent get-or-create semantics.

View Source
var ErrVolumeInUse = errors.New("volume is still attached")

ErrVolumeInUse is returned by DeleteVolumeIfUnattached when indexed attachments still reference the volume.

View Source
var ErrVolumeQuotaExceeded = errors.New("tenant volume quota exceeded")

ErrVolumeQuotaExceeded is returned by GetOrCreateVolume when inserting a new row would exceed the tenant's configured volume-count cap. Existing rows are returned idempotently and do not consume quota again.

View Source
var ErrWasmModuleIDConflict = errors.New("wasm module id already in use")

ErrWasmModuleIDConflict is returned when POST /v1/wasm-modules reuses an id bound to a different module_ref.

View Source
var ErrWasmModuleInUse = errors.New("wasm module is referenced by an active sandbox")

ErrWasmModuleInUse blocks DELETE while a sandbox still references the module.

Functions

This section is empty.

Types

type ClusterSecretRecord

type ClusterSecretRecord struct {
	Ref           string
	SandboxID     string
	Version       int
	Recipients    []string
	SealedPayload []byte
	CreatedAt     time.Time
	UpdatedAt     time.Time
}

ClusterSecretRecord is an opaque cluster-secret payload addressed by ref. The store never decrypts SealedPayload; service owns the envelope format.

type ContainerNetnsPoolStats

type ContainerNetnsPoolStats struct {
	Total    int
	Free     int
	Reserved int
	Realized int
	Pooled   int
	Adopted  int
}

ContainerNetnsPoolStats reports pool occupancy.

type ContainerNetnsSlot

type ContainerNetnsSlot struct {
	SlotID      string
	NetnsPath   string
	ContainerIP string
	SandboxID   string
	State       string
	CreatedAt   time.Time
	UpdatedAt   time.Time
}

ContainerNetnsSlot is one row of container_netns_slots.

type CustomDomainRow

type CustomDomainRow struct {
	Hostname   string
	SandboxID  string
	Status     models.CustomDomainStatus
	LastError  string
	TargetPort int
	CreatedAt  time.Time
	UpdatedAt  time.Time
}

CustomDomainRow is the per-row representation read out of sandbox_custom_domains. ListAllCustomDomains returns these so the reconcile loop and the cluster FSM hydration can walk the full set.

type FirecrackerTapPoolStats

type FirecrackerTapPoolStats struct {
	Total     int
	Allocated int
	Free      int
}

FirecrackerTapPoolStats reports the current pool occupancy. Used by /healthz and the admission controller — a near-empty pool blocks new Firecracker creates upstream of the failing Allocate call, which is a better operator experience than discovering the exhaustion on the next user request.

type FirecrackerTapSlot

type FirecrackerTapSlot struct {
	TapName     string
	CIDR        string
	HostIP      string
	GuestIP     string
	VsockCID    uint32
	SandboxID   string
	CreatedAt   time.Time
	AllocatedAt time.Time
}

FirecrackerTapSlot is one pre-seeded row of the firecracker_tap_pool. Mirrors the table shape. SandboxID is empty when the slot is free, set to the owning sandbox when allocated; AllocatedAt is the zero time when free.

type FirecrackerVMMPoolStats

type FirecrackerVMMPoolStats struct {
	Total     int
	Spawning  int
	Loaded    int
	Allocated int
	Released  int
}

FirecrackerVMMPoolStats is the per-template breakdown by status used by /healthz and PR 4-B's refill goroutine to decide how many new slots to spawn. Total is the count of all non-deleted rows for the template; the sum of the per-status counters equals Total.

type FirecrackerVMMSlot

type FirecrackerVMMSlot struct {
	ID          string
	TemplateID  string
	Status      string
	SandboxID   string
	APISocket   string
	RunDir      string
	VsockCID    uint32
	CreatedAt   time.Time
	LoadedAt    time.Time
	AllocatedAt time.Time
	ReleasedAt  time.Time
	LastError   string
}

FirecrackerVMMSlot is one row of firecracker_vmm_pool. Mirrors the table shape; nullable DATETIME columns map to zero-valued time.Time when absent so the policy wrapper (internal/pool/vmm) doesn't have to plumb sql.NullTime through its API.

type PendingImageGCEntry

type PendingImageGCEntry struct {
	Image       string
	ScheduledAt time.Time
}

PendingImageGCEntry is one row from the pending_image_gc ledger. scheduled_at travels with the image so the janitor can pin its remove/delete decision to the exact row it observed — see DeletePendingImageGCIfScheduledAt for the refresh-race rationale.

type ReserveHostPortResult

type ReserveHostPortResult struct {
	Reserved bool
	Existing *models.ExposedPort
}

ReserveHostPortResult is the three-state outcome of TryReserveHostPort. Exactly one of Reserved/Existing/(neither) is set:

  • Reserved: the row was inserted; the candidate host port is now ours.
  • Existing != nil: a row for (sandbox_id, port) already exists. The allocator MUST stop walking the pool — no other host_port will satisfy the (sandbox_id, port) primary key. Caller decides whether to reuse the existing exposure or surface an error.
  • both zero: the partial unique index on host_port rejected this candidate (some other sandbox owns it). Caller may retry.

type Store

type Store struct {
	// contains filtered or unexported fields
}

func Open

func Open(path string) (*Store, error)

func (*Store) AddCustomDomain

func (s *Store) AddCustomDomain(ctx context.Context, sandboxID, hostname string, targetPort int) error

AddCustomDomain inserts a hostname → sandbox mapping. Returns ErrCustomDomainConflict when the hostname is already owned by a different sandbox; returns ErrCustomDomainPortMismatch when the row exists for the same sandbox but with a different targetPort; returns nil when the same (hostname, sandbox, targetPort) tuple already exists (idempotent — the caller may retry safely). targetPort=0 is the toolbox-default sentinel. New rows start in CustomDomainPendingDNS.

func (*Store) AdoptContainerNetnsSlot

func (s *Store) AdoptContainerNetnsSlot(ctx context.Context, sandboxID string, now time.Time) (*ContainerNetnsSlot, error)

AdoptContainerNetnsSlot transitions a realized slot to adopted after the container task is running. Idempotent when already adopted.

func (*Store) AllocateFirecrackerTapSlot

func (s *Store) AllocateFirecrackerTapSlot(ctx context.Context, sandboxID string, now time.Time) (*FirecrackerTapSlot, error)

AllocateFirecrackerTapSlot claims a free slot for sandboxID and returns it. Idempotent: if sandboxID already owns a slot, that slot is returned without changes (the partial unique index guarantees at most one). If no slot is free, returns ErrNoFreeFirecrackerTapSlot.

The implementation is a two-step inside the SQLite single-writer window:

  1. SELECT a row WHERE sandbox_id IS NULL LIMIT 1.
  2. UPDATE that row SET sandbox_id = ?, allocated_at = ? WHERE tap_name = ? AND sandbox_id IS NULL.

The WHERE clause on UPDATE re-checks sandbox_id IS NULL so a race with a concurrent allocate of the same row updates RowsAffected=0 and we loop. SQLite's single-writer model makes this contest rare in practice but the code stays correct under any future change.

func (*Store) AllocateFirecrackerVMMSlot

func (s *Store) AllocateFirecrackerVMMSlot(ctx context.Context, templateID, sandboxID string, now time.Time) (*FirecrackerVMMSlot, error)

AllocateFirecrackerVMMSlot claims one 'loaded' slot for templateID + sandboxID. Idempotent: if sandboxID already owns a slot, that slot is returned without re-allocation (the partial unique index makes a duplicate claim a hard error, but the pre-check yields a cleaner happy path). If no 'loaded' slot for templateID exists, returns ErrNoFreeFirecrackerVMMSlot — PR 4-B's caller falls back to cold spawn rather than failing the create.

The allocator shape is lifted from AllocateFirecrackerTapSlot: SELECT one free row, UPDATE WHERE row is still free. The UPDATE's WHERE re-checks status='loaded' AND sandbox_id IS NULL so a race against a concurrent allocator updates RowsAffected=0 and we loop. SQLite's single writer makes the contest rare, but the loop keeps correctness if the locking model ever changes.

func (*Store) BeginPrewarmContainerNetnsSlot

func (s *Store) BeginPrewarmContainerNetnsSlot(ctx context.Context, now time.Time) (*ContainerNetnsSlot, error)

BeginPrewarmContainerNetnsSlot reserves a free slot under its own slot_id so the refill ticker can CNI-realize it without a sandbox owner yet.

func (*Store) ClaimIdempotentRequest

func (s *Store) ClaimIdempotentRequest(ctx context.Context, scope, fingerprint string, now time.Time, pendingTTL time.Duration) (*models.IdempotentRequestRecord, bool, error)

ClaimIdempotentRequest is the generic claim/replay primitive for caller-retry dedupe. scope is a facade-defined namespace string ("e2b.create" today; "daytona.create" or "v1.create" later) so the same fingerprint can be reused across facades without colliding.

Three outcomes per call:

  1. INSERTed a fresh pending row → acquired=true, caller owns the work.
  2. Found a Ready row whose ReplayUntil has not expired → acquired=false, caller replays the TargetID instead of running the work again.
  3. Found a Pending row whose LockedUntil has not expired → acquired=false, caller waits.

Stale Pending or Ready rows past their TTLs are reclaimed as a fresh Pending row (acquired=true), so a crashed claimer cannot block future retries indefinitely.

func (*Store) ClaimPooledContainerNetnsSlot

func (s *Store) ClaimPooledContainerNetnsSlot(ctx context.Context, sandboxID string, now time.Time) (*ContainerNetnsSlot, error)

ClaimPooledContainerNetnsSlot hands a prewarmed slot to sandboxID. Idempotent when sandboxID already owns a slot. Returns ErrNoPooledContainerNetnsSlot on miss.

func (*Store) ClearNetworkQuotaExceeded

func (s *Store) ClearNetworkQuotaExceeded(ctx context.Context, id string) error

ClearNetworkQuotaExceeded resets the flag and the detection timestamp. Used when an operator raises the limit (or sets it to unlimited) and the counter is no longer over the new ceiling.

func (*Store) Close

func (s *Store) Close() error

func (*Store) CompareCloneGeneration

func (s *Store) CompareCloneGeneration(ctx context.Context, sandboxID, snapshotGen string) error

CompareCloneGeneration rejects stale snapshot writes when wantGen is older than the row's current clone_generation (§4.8 fencing).

func (*Store) CompleteIdempotentRequest

func (s *Store) CompleteIdempotentRequest(ctx context.Context, scope, fingerprint, targetID string, now time.Time, replayTTL time.Duration) error

CompleteIdempotentRequest moves a Pending row to Ready, recording the target ID the work produced and extending the lock-and-replay window out to replayTTL from now. Returns ErrNotFound if no row matched — indicating either a programming error or a too-aggressive cleanup that removed the row mid-flight.

func (*Store) CountVolumeAttachments

func (s *Store) CountVolumeAttachments(ctx context.Context, tenant, volumeID string) (int, error)

CountVolumeAttachments returns how many sandbox references currently point at volumeID for tenant. It is backed by idx_volume_attachments_volume and is used on Daytona delete instead of scanning every sandbox's encrypted mounts.

func (*Store) CountVolumes

func (s *Store) CountVolumes(ctx context.Context, tenant string) (int, error)

CountVolumes returns how many volumes tenant owns. Used for per-tenant quota enforcement at create time.

func (*Store) Create

func (s *Store) Create(ctx context.Context, sandbox *models.Sandbox) error

func (*Store) CreateSnapshot

func (s *Store) CreateSnapshot(ctx context.Context, snapshot *models.SandboxSnapshot) error

func (*Store) CreateTemplate

func (s *Store) CreateTemplate(ctx context.Context, template *models.Template) error

CreateTemplate inserts a freshly-allocated template row. Callers set status=pending and an empty rootfs_path; the build goroutine flips both via UpdateTemplateStatus once mkfs returns. A PK collision becomes ErrTemplateIDConflict so an operator pipeline that retries POST with an explicit ID gets a 409 instead of a 500.

func (*Store) CreateVolume

func (s *Store) CreateVolume(ctx context.Context, v *models.Volume) error

CreateVolume inserts a new volume row. The (tenant, name) unique index makes this the idempotency boundary: a duplicate returns ErrVolumeExists rather than a second row. CreatedAt is stamped here when the caller leaves it zero.

func (*Store) Delete

func (s *Store) Delete(ctx context.Context, id string) error

func (*Store) DeleteAllWasmCheckpointPushes

func (s *Store) DeleteAllWasmCheckpointPushes(ctx context.Context, sandboxID string) error

DeleteAllWasmCheckpointPushes removes all retained AOCR push-history rows for sandboxID.

func (*Store) DeleteAllWasmStateKV

func (s *Store) DeleteAllWasmStateKV(ctx context.Context, sandboxID string) error

DeleteAllWasmStateKV removes every durable host-KV row for sandboxID.

func (*Store) DeleteClusterSecretsForSandbox

func (s *Store) DeleteClusterSecretsForSandbox(ctx context.Context, sandboxID string) error

func (*Store) DeleteFirecrackerVMMSlot

func (s *Store) DeleteFirecrackerVMMSlot(ctx context.Context, slotID string) error

DeleteFirecrackerVMMSlot drops the row. PR 4-B's GC sweep calls this after the VMM process for the slot is confirmed gone, so there is no on-disk runDir or live socket the row was the last reference to. Returns ErrNotFound when the row was already deleted — idempotent on double-call.

func (*Store) DeleteIdempotentRequest

func (s *Store) DeleteIdempotentRequest(ctx context.Context, scope, fingerprint string) error

DeleteIdempotentRequest drops the row outright. Used by failure paths where the in-flight write rolled back and the next retry should run the work again from scratch instead of waiting for LockedUntil.

func (*Store) DeleteMounts

func (s *Store) DeleteMounts(ctx context.Context, sandboxID string) error

DeleteMounts removes mount config for a sandbox. The cascade on the sandboxes table handles this when a sandbox is destroyed; explicit deletes are useful for replacing mounts.

func (*Store) DeletePendingImageGC

func (s *Store) DeletePendingImageGC(ctx context.Context, image string) error

DeletePendingImageGC removes the ledger row for an image unconditionally. Used when the janitor decides the image is back in use (HasActiveImageRef = true) and the row should be dropped regardless of timestamp — the destroy path will re-schedule with a fresh timestamp if the image goes idle again. Missing rows are not an error.

func (*Store) DeletePendingImageGCIfScheduledAt

func (s *Store) DeletePendingImageGCIfScheduledAt(ctx context.Context, image string, at time.Time) (bool, error)

DeletePendingImageGCIfScheduledAt removes the row only if its scheduled_at still matches `at` — i.e. nobody has refreshed the row since the janitor observed it. Returns whether the delete actually happened so the caller can detect the refresh race.

Why this exists: the sweep does (list, [check active, remove image, delete row]). If a destroy of another sandbox sharing the image upserts the row with a fresh timestamp between the list and the delete, an unconditional delete would silently throw away the extended TTL that destroy was supposed to buy. The janitor uses this to keep the "TTL clock restarts from the most recent destroy" contract under churn.

func (*Store) DeletePendingVolumeDeletion

func (s *Store) DeletePendingVolumeDeletion(ctx context.Context, volumeID string) error

DeletePendingVolumeDeletion removes a reclaim-ledger row once its backend bytes have been deleted (or skipped because a live volume reclaimed the source). Idempotent: a missing row is success, so the reclaim loop can run at-least-once without tracking which rows it already cleared.

func (*Store) DeletePort

func (s *Store) DeletePort(ctx context.Context, sandboxID string, port int) error

func (*Store) DeleteSnapshot

func (s *Store) DeleteSnapshot(ctx context.Context, name string) error

func (*Store) DeleteSnapshotAlias

func (s *Store) DeleteSnapshotAlias(ctx context.Context, alias string) error

DeleteSnapshotAlias removes the alias row. FK cascade also drops the row when its underlying sandbox_snapshots row is deleted, so explicit deletes are only needed when the facade wants to forget an alias without removing the native snapshot.

func (*Store) DeleteTemplate

func (s *Store) DeleteTemplate(ctx context.Context, id string) error

func (*Store) DeleteVolume

func (s *Store) DeleteVolume(ctx context.Context, tenant, id string) error

DeleteVolume removes the volume id owned by tenant. Returns ErrNotFound when no such row exists so the facade can answer 404 rather than a silent 200. Callers MUST enforce the no-live-attacher rule before calling this; the store only owns the row.

func (*Store) DeleteVolumeAttachmentsForSandbox

func (s *Store) DeleteVolumeAttachmentsForSandbox(ctx context.Context, sandboxID string) error

DeleteVolumeAttachmentsForSandbox removes the indexed platform-volume references for sandboxID. SQLite normally reaches this through the sandbox FK cascade, but the explicit method lets service code share the cluster-mode cleanup path and is idempotent for rollback.

func (*Store) DeleteVolumeIfUnattached

func (s *Store) DeleteVolumeIfUnattached(ctx context.Context, tenant, id, fallbackSource string) error

DeleteVolumeIfUnattached schedules backend cleanup and removes the volume row only when no indexed attachments remain. The pending cleanup row is inserted in the same transaction before the metadata row is removed, so remote data never loses its reconciliation coordinates.

fallbackSource is used only for rows created before the source column existed (vol.Source == ""); the frozen vol.Source is authoritative when present, so reclaim always targets exactly what was created even if config later changed.

func (*Store) DeleteWasmCheckpointPush

func (s *Store) DeleteWasmCheckpointPush(ctx context.Context, id int64) error

DeleteWasmCheckpointPush removes one push history row by id.

func (*Store) DeleteWasmModule

func (s *Store) DeleteWasmModule(ctx context.Context, id string) error

DeleteWasmModule removes a wasm_modules catalogue row.

func (*Store) DeleteWasmStateKV

func (s *Store) DeleteWasmStateKV(ctx context.Context, sandboxID, key string) error

DeleteWasmStateKV removes one durable host-KV row.

func (*Store) FinishPrewarmContainerNetnsSlot

func (s *Store) FinishPrewarmContainerNetnsSlot(ctx context.Context, slotID, netnsPath, containerIP string, now time.Time) error

FinishPrewarmContainerNetnsSlot moves a refill-reserved slot into the pooled warm queue (network prepaid, no sandbox owner).

func (*Store) Get

func (s *Store) Get(ctx context.Context, id string) (*models.Sandbox, error)

func (*Store) GetClusterSecret

func (s *Store) GetClusterSecret(ctx context.Context, ref string) (*ClusterSecretRecord, error)

func (*Store) GetCompatState

func (s *Store) GetCompatState(ctx context.Context, sandboxID, facade string) (*models.SandboxCompatState, error)

GetCompatState returns the state blob for (sandboxID, facade), or ErrNotFound when no row exists. Callers unmarshal state_json themselves.

func (*Store) GetContainerNetnsPoolStats

func (s *Store) GetContainerNetnsPoolStats(ctx context.Context) (ContainerNetnsPoolStats, error)

func (*Store) GetContainerNetnsSlotBySandbox

func (s *Store) GetContainerNetnsSlotBySandbox(ctx context.Context, sandboxID string) (*ContainerNetnsSlot, error)

GetContainerNetnsSlotBySandbox returns the slot owned by sandboxID, or nil.

func (*Store) GetFirecrackerTapPoolStats

func (s *Store) GetFirecrackerTapPoolStats(ctx context.Context) (FirecrackerTapPoolStats, error)

func (*Store) GetFirecrackerTapSlotBySandbox

func (s *Store) GetFirecrackerTapSlotBySandbox(ctx context.Context, sandboxID string) (*FirecrackerTapSlot, error)

GetFirecrackerTapSlotBySandbox returns the slot currently owned by sandboxID, or nil if it owns none. Used by both the idempotent allocate path and the runtime driver's Inspect/Destroy paths.

func (*Store) GetFirecrackerVMMPoolStats

func (s *Store) GetFirecrackerVMMPoolStats(ctx context.Context, templateID string) (FirecrackerVMMPoolStats, error)

GetFirecrackerVMMPoolStats returns per-template occupancy by status. Single GROUP BY scan covered by idx_firecracker_vmm_pool_template_status. /healthz and PR 4-C's metrics exporter both call this; PR 4-B's refill goroutine prefers ListFirecrackerVMMSlotsForRefill which gives it the row ids it may want to act on.

func (*Store) GetFirecrackerVMMSlotByID

func (s *Store) GetFirecrackerVMMSlotByID(ctx context.Context, slotID string) (*FirecrackerVMMSlot, error)

GetFirecrackerVMMSlotByID returns the slot row whose id matches, or nil if not found. Lets PR 4-B's refill goroutine re-read its own freshly-inserted row to inspect the row's authoritative status (the daemon may have raced a GC between InsertFirecrackerVMMSlot and the spawner attempt). Unlike GetFirecrackerVMMSlotBySandbox, the row's sandbox_id is unknown to the caller — we project the column explicitly so an 'allocated' slot reads back with its claimant.

func (*Store) GetFirecrackerVMMSlotBySandbox

func (s *Store) GetFirecrackerVMMSlotBySandbox(ctx context.Context, sandboxID string) (*FirecrackerVMMSlot, error)

GetFirecrackerVMMSlotBySandbox returns the slot currently claimed by sandboxID, or nil if it owns none. Used by the idempotent Allocate pre-check and by PR 4-B's destroy path to find the slot whose VMM process needs to be torn down.

func (*Store) GetIdempotentRequest

func (s *Store) GetIdempotentRequest(ctx context.Context, scope, fingerprint string) (*models.IdempotentRequestRecord, error)

GetIdempotentRequest returns the row for (scope, fingerprint), or ErrNotFound when no row exists.

func (*Store) GetMounts

func (s *Store) GetMounts(ctx context.Context, sandboxID string) ([]byte, error)

GetMounts returns the encrypted mount blob, or ErrNotFound if no row exists.

func (*Store) GetOrCreateVolume

func (s *Store) GetOrCreateVolume(ctx context.Context, v *models.Volume, maxPerTenant int) (*models.Volume, bool, error)

GetOrCreateVolume returns the existing (tenant,name) volume or inserts v as a new row when absent. The existence check, quota check, and insert run in one transaction so concurrent creates for different names cannot both observe the same pre-insert count and overshoot maxPerTenant. created is true only when a row was inserted by this call.

func (*Store) GetPortByHostPort

func (s *Store) GetPortByHostPort(ctx context.Context, hostPort int) (*models.ExposedPort, error)

GetPortByHostPort returns the raw-TCP exposure bound to hostPort, or nil if no exposure owns it. The L4 wake listener uses this to map Caddy's PROXY protocol destination port back to a sandbox/container port.

func (*Store) GetSnapshot

func (s *Store) GetSnapshot(ctx context.Context, name string) (*models.SandboxSnapshot, error)

func (*Store) GetSnapshotAlias

func (s *Store) GetSnapshotAlias(ctx context.Context, alias string) (*models.SnapshotAlias, error)

GetSnapshotAlias returns the alias row, or ErrNotFound if the alias does not exist.

func (*Store) GetTemplate

func (s *Store) GetTemplate(ctx context.Context, id string) (*models.Template, error)

func (*Store) GetVolume

func (s *Store) GetVolume(ctx context.Context, tenant, name string) (*models.Volume, error)

GetVolume returns the volume named name owned by tenant, or ErrNotFound.

func (*Store) GetVolumeByID

func (s *Store) GetVolumeByID(ctx context.Context, tenant, id string) (*models.Volume, error)

GetVolumeByID returns the volume with the given id scoped to tenant (so one tenant cannot resolve another's id), or ErrNotFound.

func (*Store) GetWasmModule

func (s *Store) GetWasmModule(ctx context.Context, id string) (WasmModuleRecord, error)

GetWasmModule returns one wasm_modules row by catalogue id.

func (*Store) GetWasmStateKV

func (s *Store) GetWasmStateKV(ctx context.Context, sandboxID, key string) ([]byte, bool, error)

GetWasmStateKV returns one durable host-KV value.

func (*Store) HasActiveImageRef

func (s *Store) HasActiveImageRef(ctx context.Context, image string) (bool, error)

HasActiveImageRef reports whether any sandbox row references image with a status other than destroyed. Used by image GC: when this returns false the caller may safely remove the image from Docker. Single indexed query — constant cost regardless of how many destroyed rows have accumulated, so 10k destroyed historical rows do not slow the destroy hot path. Returns true on empty image as a conservative default (caller treats it as "still in use, do not delete").

func (*Store) InsertFirecrackerVMMSlot

func (s *Store) InsertFirecrackerVMMSlot(ctx context.Context, slot FirecrackerVMMSlot, now time.Time) error

InsertFirecrackerVMMSlot reserves a new row in status='spawning'. PR 4-B's refill goroutine calls this BEFORE launching firecracker so a crash mid-spawn leaves a 'spawning' row the GC sweep can clean up rather than a silently-leaked process with no row to find it by.

Validation is strict because a malformed insert is always a bug in the caller — the refill goroutine should hand us a freshly-generated id and an actual template id, not the zero value.

func (*Store) InsertWasmCheckpointPush

func (s *Store) InsertWasmCheckpointPush(ctx context.Context, sandboxID, registryRef, digest string) (int64, error)

InsertWasmCheckpointPush records a successful AOCR push for keep-last-N retention.

func (*Store) IsTemplateReferenced

func (s *Store) IsTemplateReferenced(ctx context.Context, id string) (bool, error)

IsTemplateReferenced reports whether any sandbox row still names this template_id. Used by DeleteTemplate (so an operator gets a 409 instead of yanking the rootfs out from under a live sandbox) and by the GC sweep (so it skips rows that are still in use). Backed by the partial index idx_sandboxes_template_id — constant cost regardless of the destroyed-row history.

func (*Store) IsTemplateReferencedByVMM

func (s *Store) IsTemplateReferencedByVMM(ctx context.Context, id string) (bool, error)

IsTemplateReferencedByVMM reports whether any warm-VMM pool row still names this template. This intentionally includes released rows: even a released row is still persistent state that references the template, and template GC should not leave dangling pool rows behind. Once the VMM-pool GC deletes the row, template GC can remove the template on a later pass.

func (*Store) IsWasmDigestCatalogued

func (s *Store) IsWasmDigestCatalogued(ctx context.Context, digest string) (bool, error)

IsWasmDigestCatalogued reports whether any wasm_modules row pins this content digest. The cache evictor consults it (alongside IsWasmModuleReferenced) so a digest that backs a catalogue id — resolvable later by a fresh create — is never reclaimed out from under the catalogue, even with no live sandbox.

func (*Store) IsWasmModuleReferenced

func (s *Store) IsWasmModuleReferenced(ctx context.Context, moduleID, moduleRef, moduleDigest string) (bool, error)

IsWasmModuleReferenced reports whether any sandbox still names moduleRef or digest id. IsWasmModuleReferenced reports whether any sandbox row still depends on this module. The check spans ref, id, AND the resolved content digest: two aliases/tags can share the same bytes, so deleting/evicting purely by ref would yank a digest still in use by another sandbox (codex C5). A blank moduleDigest simply contributes no extra match.

func (*Store) List

func (s *Store) List(ctx context.Context) ([]*models.Sandbox, error)

func (*Store) ListAllCustomDomains

func (s *Store) ListAllCustomDomains(ctx context.Context) ([]CustomDomainRow, error)

ListAllCustomDomains returns every row in the table. Used by the reconcile loop's matcher-GC pass and by the cluster FSM hydration on cold start. Ordered by hostname so reconcile diffs are stable across calls.

func (*Store) ListAllExposedPorts

func (s *Store) ListAllExposedPorts(ctx context.Context) ([]models.ExposedPort, error)

ListAllExposedPorts returns every row in exposed_ports across every sandbox. Used by reconcile to GC zombie caddy routes / layer4 servers without N+1 per-sandbox lookups.

func (*Store) ListAutoImportPendingIDs

func (s *Store) ListAutoImportPendingIDs(ctx context.Context) ([]string, error)

ListAutoImportPendingIDs returns the IDs of sandboxes whose post-pull auto-import has not yet succeeded. Returns IDs only (not full Sandbox rows) so the reconciler can fetch+retry one at a time and skip rows that have meanwhile been deleted without holding a large in-memory snapshot. Hits the partial index on (auto_import_pending = 1).

func (*Store) ListByOwner

func (s *Store) ListByOwner(ctx context.Context, ownerRef string) ([]*models.Sandbox, error)

ListByOwner returns the sandboxes attributed to ownerRef, newest first. It is the owner-scoped counterpart of List: the API edge uses it to fence a user token to its own sandboxes, and the fleet enforcement loop uses it to fan a standing directive (stop/restore/delete) across one account. An empty ownerRef matches operator/PAT-created rows; callers that want the whole fleet use List instead. Ports and custom domains are intentionally not attached here — the current callers (scoping filter, enforcement) only need identity and lifecycle fields, so we skip the bulk joins.

func (*Store) ListByRuntime

func (s *Store) ListByRuntime(ctx context.Context, runtime string) ([]*models.Sandbox, error)

ListByRuntime returns the sandboxes for one runtime ("wasm", "firecracker", "docker"), newest first. It is the runtime-scoped counterpart of List: the per-runtime background sweeps (wasm periodic checkpoint, wasm durable-push retry) use it instead of List so they scan only their own rows rather than the whole fleet on every tick — at a node packing thousands of mixed-runtime sandboxes, List would load (and scanSandbox-decode) every docker/firecracker row just to filter them back out. Like ListByOwner, ports and custom domains are not attached: the sweep callers only need identity + lifecycle fields.

func (*Store) ListCompatState

func (s *Store) ListCompatState(ctx context.Context, facade string) (map[string]models.SandboxCompatState, error)

ListCompatState returns every row for the given facade keyed by sandbox_id. Empty result is map of length zero, not nil — callers can always index into it.

func (*Store) ListContainerNetnsSlotsByState

func (s *Store) ListContainerNetnsSlotsByState(ctx context.Context, state string) ([]ContainerNetnsSlot, error)

ListContainerNetnsSlotsByState returns all rows in the given state.

func (*Store) ListCustomDomains

func (s *Store) ListCustomDomains(ctx context.Context, sandboxID string) ([]models.CustomDomain, error)

ListCustomDomains returns the canonical-ordered rows for one sandbox. Empty slice (nil) when the sandbox has no custom domains.

func (*Store) ListFirecrackerVMMSlotsForRefill

func (s *Store) ListFirecrackerVMMSlotsForRefill(ctx context.Context, templateID string) ([]FirecrackerVMMSlot, error)

ListFirecrackerVMMSlotsForRefill returns every non-released slot owned by templateID. PR 4-B's refill goroutine calls this once per tick to compute "desired_depth - len(non_released)" — the spawn budget for the next pass. Released rows are excluded so a slow GC doesn't inflate the count and starve refills.

func (*Store) ListGCEligibleTemplates

func (s *Store) ListGCEligibleTemplates(ctx context.Context, olderThan time.Time) ([]*models.Template, error)

ListGCEligibleTemplates returns ready/failed templates not referenced by any sandbox and last touched before olderThan. Pending rows are skipped — they have an in-flight build goroutine that owns the row's terminal transition. The anti-join against sandboxes uses the idx_sandboxes_template_id partial index so the subquery is cheap.

func (*Store) ListNonFreeContainerNetnsSlots

func (s *Store) ListNonFreeContainerNetnsSlots(ctx context.Context) ([]ContainerNetnsSlot, error)

ListNonFreeContainerNetnsSlots returns rows not in the free state for reconcile.

func (*Store) ListOrphanedWasmCheckpointPushes

func (s *Store) ListOrphanedWasmCheckpointPushes(ctx context.Context, limit int) ([]WasmCheckpointPushRecord, error)

ListOrphanedWasmCheckpointPushes returns push-history rows whose sandbox no longer exists, capped at limit (<=0 means a default cap). These are the rows a destroy/reconcile retained because its registry DeleteRef had not yet succeeded; the orphan-ref sweep retries each ref and drops the row once the manifest is confirmed gone, so the tracking table can never leak unbounded rows for sandboxes that are already gone.

func (*Store) ListPendingImageGCDue

func (s *Store) ListPendingImageGCDue(ctx context.Context, cutoff time.Time, limit int) ([]PendingImageGCEntry, error)

ListPendingImageGCDue returns rows whose scheduled_at is at or before cutoff (the janitor passes now - ImageBuildGCTTL). Ordered by scheduled_at so the oldest entries get GC'd first within a sweep. `limit` caps the batch so a backlog (janitor disabled for a while then re-enabled, or just thousands of destroyed sandboxes sharing a few images) doesn't fan out into one huge serial Docker spike per tick — pass 0 for unbounded. scheduled_at is returned so the caller can guard the conditional delete in DeletePendingImageGCIfScheduledAt.

func (*Store) ListPendingVolumeDeletions

func (s *Store) ListPendingVolumeDeletions(ctx context.Context) ([]models.PendingVolumeDeletion, error)

ListPendingVolumeDeletions exposes the durable cleanup ledger for tests and future backend-specific reclaim loops.

func (*Store) ListReadyTemplateIDs

func (s *Store) ListReadyTemplateIDs(ctx context.Context) ([]string, error)

ListReadyTemplateIDs returns the IDs of every template whose artifacts are usable on this host: `status IN ('ready', 'ready_no_snapshot')`. The Phase 6 PR-D capacity heartbeat hands this list to peers so placement can prefer the node that already has the template's artifacts cached.

Returns IDs only (no payload columns) — the snapshot is gossiped every few seconds and a full row projection would balloon heartbeats once a cluster has hundreds of templates. The unknown-allow rule in placement.go nodeFits means a momentary "empty list" mid-startup is safe: peers fall back to "any host" placement until the heartbeat catches up.

func (*Store) ListReadyWasmModuleRefs

func (s *Store) ListReadyWasmModuleRefs(ctx context.Context) ([]string, error)

ListReadyWasmModuleRefs returns module_ref values for ready catalogue rows.

func (*Store) ListReleasedFirecrackerVMMSlots

func (s *Store) ListReleasedFirecrackerVMMSlots(ctx context.Context, olderThan time.Time) ([]FirecrackerVMMSlot, error)

ListReleasedFirecrackerVMMSlots is the GC sweep selector: every row in status='released' whose released_at is older than olderThan. The partial index on released_at WHERE status='released' covers this query exactly so the sweep is cheap even when the steady-state count is zero.

func (*Store) ListSnapshotAliases

func (s *Store) ListSnapshotAliases(ctx context.Context, facade string) (map[string]models.SnapshotAlias, error)

ListSnapshotAliases returns all alias rows for the given facade keyed by alias. Pass empty facade to fetch every alias regardless of facade.

func (*Store) ListSnapshots

func (s *Store) ListSnapshots(ctx context.Context) ([]*models.SandboxSnapshot, error)

func (*Store) ListSnapshotsPendingPush

func (s *Store) ListSnapshotsPendingPush(ctx context.Context) ([]*models.SandboxSnapshot, error)

ListSnapshotsPendingPush returns snapshots the reconciler should retry — 'pending' is the brand-new state set by the snapshot-create path, 'error' is what a failed previous attempt left behind. 'pushing' is intentionally excluded so a row currently being processed by another reconciler tick (or a still-running goroutine kicked off by snapshot-create) is not re-claimed before its terminal state lands.

func (*Store) ListTemplates

func (s *Store) ListTemplates(ctx context.Context) ([]*models.Template, error)

func (*Store) ListTemplatesPendingPush

func (s *Store) ListTemplatesPendingPush(ctx context.Context) ([]*models.Template, error)

ListTemplatesPendingPush returns the templates the reconciler should retry: push_state IN ('pending', 'error'). 'pushing' is intentionally excluded so a row currently being processed by another reconciler tick is not re-claimed before its terminal state lands. Mirrors ListSnapshotsPendingPush exactly.

The status precondition (must be ready) keeps half-built templates from sneaking into the push queue if someone manually flipped push_state. The reconciler enforces the same guard defensively, but filtering at the source means we never even materialize the row.

func (*Store) ListTemplatesReadyBefore

func (s *Store) ListTemplatesReadyBefore(ctx context.Context, cutoff time.Time) ([]*models.Template, error)

ListTemplatesReadyBefore returns the `ready` templates whose `ready_at` is older than the cutoff. Used by the Phase 6 PR-E rotation reconciler to find rebuild candidates. Only `ready` (not `ready_no_snapshot`) qualifies — rotating a ready_no_snapshot row would just re-burn build budget without delivering the rotation's goal (refreshing the snapshot's kernel + toolbox bytes).

Returns rows sorted oldest-first so a reconcile sweep that hits its per-tick fanout cap rotates the most-overdue templates first.

func (*Store) ListUnhealthyTemplates

func (s *Store) ListUnhealthyTemplates(ctx context.Context) ([]*models.Template, error)

ListUnhealthyTemplates returns every template row sitting in status='unhealthy'. The daemon-start scanner in service.RekickUnhealthyTemplatesAtStart sweeps this list once at boot and kicks RebuildTemplateSnapshot for each row, closing the crash-mid-rebuild gap: if sandboxd died after MarkTemplateUnhealthy flipped the row but before the in-process kicker finished, the row would otherwise be stuck unhealthy forever — every create against that template would fail with a confusing "template unhealthy" error and only operator intervention would resolve it.

Mirrors ListTemplatesPendingPush in shape (same SELECT projection, different WHERE) so the scan path is uniform with the existing push reconciler. No status precondition beyond status='unhealthy' itself — the rebuild path inside RebuildTemplateSnapshot re-checks the row under its own read, so a row that another caller already recovered between this list and the rebuild kick is handled cleanly.

func (*Store) ListVolumes

func (s *Store) ListVolumes(ctx context.Context, tenant string) ([]models.Volume, error)

ListVolumes returns all volumes owned by tenant, newest first. Empty result is a zero-length slice, never nil.

func (*Store) ListWasmCheckpointPushes

func (s *Store) ListWasmCheckpointPushes(ctx context.Context, sandboxID string) ([]WasmCheckpointPushRecord, error)

ListWasmCheckpointPushes returns push history newest-first.

func (*Store) ListWasmModules

func (s *Store) ListWasmModules(ctx context.Context) ([]WasmModuleRecord, error)

ListWasmModules returns all catalogue rows newest-first.

func (*Store) ListWasmModulesOlderThan

func (s *Store) ListWasmModulesOlderThan(ctx context.Context, cutoff time.Time) ([]WasmModuleRecord, error)

ListWasmModulesOlderThan returns catalogue rows not updated since cutoff.

func (*Store) ListWasmStateKVKeys

func (s *Store) ListWasmStateKVKeys(ctx context.Context, sandboxID string) ([]string, error)

ListWasmStateKVKeys lists keys for a sandbox.

func (*Store) LiveVolumeExistsForSource

func (s *Store) LiveVolumeExistsForSource(ctx context.Context, source string) (bool, error)

LiveVolumeExistsForSource reports whether any current volume row resolves to source. The reclaim worker calls this before deleting backend bytes: because sources are deterministic, a delete-then-recreate of the same name produces a new volume row pointing at the same coordinate. When that live row exists the worker must NOT delete the bytes — they now belong to the recreated volume — and simply drops the stale ledger row instead.

func (*Store) MarkContainerNetnsSlotRealized

func (s *Store) MarkContainerNetnsSlotRealized(ctx context.Context, sandboxID, netnsPath, containerIP string, now time.Time) (*ContainerNetnsSlot, error)

MarkContainerNetnsSlotRealized records CNI ADD output for a reserved slot. Idempotent when already realized or adopted with the same paths.

func (*Store) MarkFirecrackerVMMSlotFailed

func (s *Store) MarkFirecrackerVMMSlotFailed(ctx context.Context, slotID, errMsg string, now time.Time) error

MarkFirecrackerVMMSlotFailed records that the spawner could not produce a paused-and-loaded VMM for this slot and moves the row directly to 'released' so the GC sweep cleans it up after the TTL. Skipping the 'loaded' intermediate is intentional: a failed slot was never claimable, and a transient 'loaded' state on a row whose VMM is actually missing would let the next Allocate hand out a dead process. last_error is preserved on the row for operator triage.

func (*Store) MarkFirecrackerVMMSlotLoaded

func (s *Store) MarkFirecrackerVMMSlotLoaded(ctx context.Context, slotID, apiSocket, runDir string, vsockCID uint32, now time.Time) error

MarkFirecrackerVMMSlotLoaded flips 'spawning' → 'loaded' and stamps the per-slot artifact paths the allocator will hand to the sandbox create path. The WHERE clause asserts the current state so a retried call (or a racing GC) can't accidentally walk a slot backwards from 'allocated' to 'loaded'. apiSocket and runDir live in the row so PR 4-B's runtime adapter can adopt a pre-spawned firecracker process across daemon restarts without re-deriving them.

func (*Store) MarkNetworkQuotaExceeded

func (s *Store) MarkNetworkQuotaExceeded(ctx context.Context, id string, detectedAt time.Time) error

MarkNetworkQuotaExceeded flips the flag on. detectedAt records when the crossover was first observed so the API can surface it to the SDK. Calls when already-exceeded preserve the original detectedAt — the trigger time is the interesting one, not the most recent re-observation.

func (*Store) MarkTemplatePushPending

func (s *Store) MarkTemplatePushPending(ctx context.Context, id string) (bool, error)

MarkTemplatePushPending is the kickTemplateBuild success-path seam for Phase 6 PR 6-B.1. Idempotent and state-guarded: the WHERE clause only flips rows whose push_state is currently "active", so a row that the reconciler is already working on (pending|pushing) is left alone. push_error is cleared because a freshly-built artifact is a fresh attempt — any prior failure no longer applies.

Returns (true, nil) when the row moved, (false, nil) when the row did not exist OR was already pending/pushing/error. Callers can gate their reconciler-kick on changed=true so a no-op transition doesn't fire an extra reconciler tick.

func (*Store) MarkTemplateUnhealthy

func (s *Store) MarkTemplateUnhealthy(ctx context.Context, id, reason string) (bool, error)

MarkTemplateUnhealthy is the Phase 6 PR-A transition for "snapshot was ready, now corrupt at load time". The WHERE status='ready' guard is the idempotency primitive: many concurrent Creates can hit the same corrupt snapshot in a burst, and only the first call's UPDATE affects a row — subsequent calls return (false, nil). Callers gate the async rebuild kick on changed=true so exactly one rebuild fires per corruption event.

has_snapshot is cleared in the same row update so the resolver and the warm-pool lister both see HasSnapshot=false on the next read — the cold-boot fallback fires until the rebuild succeeds. The snapshot artifact paths are kept on the row for forensic inspection; the rebuild overwrites them in place.

snapshot_error captures the corruption reason for operator-facing surfaces (the GET /v1/templates/{id} payload, future runbooks). The status itself is the alertable signal; the message is the human-readable detail.

func (*Store) PutClusterSecret

func (s *Store) PutClusterSecret(ctx context.Context, rec ClusterSecretRecord) error

func (*Store) PutMounts

func (s *Store) PutMounts(ctx context.Context, sandboxID string, sealed []byte) error

PutMounts stores an encrypted mount blob for a sandbox. The blob is opaque to the store layer; encryption / decryption happens in the service layer.

func (*Store) PutVolumeAttachments

func (s *Store) PutVolumeAttachments(ctx context.Context, attachments []models.VolumeAttachment) error

PutVolumeAttachments upserts the platform-volume attachments for a sandbox. The volume_id and sandbox_id foreign keys make create/delete races explicit: if a volume is deleted before the sandbox can persist its attachment, the insert fails and the service rolls the sandbox create back.

func (*Store) PutWasmStateKV

func (s *Store) PutWasmStateKV(ctx context.Context, sandboxID, key string, value []byte) error

PutWasmStateKV upserts one durable host-KV row (§4.6).

func (*Store) ReassignContainerNetnsSandbox

func (s *Store) ReassignContainerNetnsSandbox(ctx context.Context, fromSandboxID, toSandboxID string, now time.Time) error

ReassignContainerNetnsSandbox moves adopted slot ownership from one sandbox id to another (warm park → adopt). Idempotent when toSandboxID already owns.

func (*Store) RefreshPendingImageGCIfExists

func (s *Store) RefreshPendingImageGCIfExists(ctx context.Context, image string, at time.Time) (bool, error)

RefreshPendingImageGCIfExists pushes the row's scheduled_at forward when (and only when) a row for image is already present. The Create path calls this after store.Create succeeds, so a freshly-used image that previously had a pending GC gets its deadline reset from "now" instead of inheriting the original destroy's old timestamp.

UPDATE-only (not UPSERT) on purpose: a row should only ever exist when a destroy has scheduled an image for cleanup. We do NOT want the create path inserting one — that would turn pending_image_gc into a one-row-per-image-ever-used table. The row-count stays bounded by "images destroyed in the last TTL window". Returns whether a row was touched, so callers can distinguish "deadline pushed forward" from "no pending GC, nothing to push".

func (*Store) ReleaseContainerNetnsSlot

func (s *Store) ReleaseContainerNetnsSlot(ctx context.Context, sandboxID string, now time.Time) error

ReleaseContainerNetnsSlot returns a slot to the free pool. Idempotent.

func (*Store) ReleaseFirecrackerTapSlot

func (s *Store) ReleaseFirecrackerTapSlot(ctx context.Context, sandboxID string) error

ReleaseFirecrackerTapSlot returns a sandbox's slot to the pool by clearing sandbox_id + allocated_at. Idempotent: releasing a sandbox that owns no slot is a no-op.

func (*Store) ReleaseFirecrackerVMMSlot

func (s *Store) ReleaseFirecrackerVMMSlot(ctx context.Context, sandboxID string, now time.Time) error

ReleaseFirecrackerVMMSlot moves a sandbox's slot from 'allocated' to 'released'. Idempotent in two senses: releasing a sandbox that never owned a slot is a no-op (RowsAffected=0 returns nil), and releasing a slot already in 'released' is a no-op for the same reason. The WHERE-on-status keeps a malformed retry from resurrecting a slot that the spawner failed and already marked released.

func (*Store) ReleaseOrphanedFirecrackerVMMSlots

func (s *Store) ReleaseOrphanedFirecrackerVMMSlots(ctx context.Context, now time.Time) (int, error)

ReleaseOrphanedFirecrackerVMMSlots marks any warm-pool slot left in 'spawning' or 'loaded' with no sandbox claim as 'released'. The daemon calls this once at startup before refilling so rows stranded by the previous process do not stay invisible to GC forever.

func (*Store) RemoveCustomDomain

func (s *Store) RemoveCustomDomain(ctx context.Context, sandboxID, hostname string) error

RemoveCustomDomain deletes the (sandbox, hostname) row. Cross-sandbox removal is rejected — the API gets ErrNotFound rather than silently stealing a hostname from another sandbox.

func (*Store) ReserveContainerNetnsSlot

func (s *Store) ReserveContainerNetnsSlot(ctx context.Context, sandboxID string, now time.Time) (*ContainerNetnsSlot, error)

ReserveContainerNetnsSlot claims a free slot for sandboxID. Idempotent when sandboxID already owns a slot.

func (*Store) ResetContainerNetnsSlotToFree

func (s *Store) ResetContainerNetnsSlotToFree(ctx context.Context, slotID string, now time.Time) error

ResetContainerNetnsSlotToFree clears a slot row back to the empty free state.

func (*Store) ResolveCustomDomain

func (s *Store) ResolveCustomDomain(ctx context.Context, hostname string) (string, error)

ResolveCustomDomain is the hot path for the TLSAsk handler — single PK lookup, no scan. Returns ErrNotFound for unknown hostnames so the handler can fold it into a 403 without an error log on the success path. We do not surface target_port here because the routing dial target is already baked into the per-domain Caddy route at install time (see IngressCustomDomainHTTPRouteID); TLSAsk only needs the ownership signal.

func (*Store) ResolveSandboxIDByName

func (s *Store) ResolveSandboxIDByName(ctx context.Context, name string) (string, error)

ResolveSandboxIDByName returns the sandbox ID owning the given name, or ErrNotFound if no row matches. Empty input is rejected so an accidental "" lookup does not match a no-name sandbox via the partial unique index's escape hatch.

func (*Store) SchedulePendingImageGC

func (s *Store) SchedulePendingImageGC(ctx context.Context, image string, at time.Time) error

SchedulePendingImageGC records (or refreshes) a pending image-deletion row. UPSERT on the image PK means concurrent or repeated destroys collapse to one row and the TTL clock restarts from the most recent destroy — so a busy churn pattern on the same image keeps deferring removal instead of racing the janitor. Empty image is a no-op.

func (*Store) SchedulePendingVolumeDeletion

func (s *Store) SchedulePendingVolumeDeletion(ctx context.Context, v models.Volume, source string) error

SchedulePendingVolumeDeletion writes (or refreshes) the durable reclaim-ledger row for a volume whose metadata row lives outside this SQLite store (the cluster-FSM path), so the per-node reclaim worker still has the coordinates to delete the S3 prefix / NFS dir. The single-node path uses DeleteVolumeIfUnattached, which does this in one transaction with the row delete; this is the split-store equivalent.

func (*Store) SeedContainerNetnsSlot

func (s *Store) SeedContainerNetnsSlot(ctx context.Context, slotID string, now time.Time) error

SeedContainerNetnsSlot inserts one free pool row. Idempotent on slot_id PK.

func (*Store) SeedFirecrackerTapSlot

func (s *Store) SeedFirecrackerTapSlot(ctx context.Context, slot FirecrackerTapSlot, now time.Time) error

SeedFirecrackerTapSlot inserts one slot row into the pool. Idempotent: the tap_name PRIMARY KEY makes re-seeding with the same name a no-op, so the daemon's boot path can call this for every configured slot without coordinating across restarts. Two distinct slots with the same vsock_cid would trip the unique index — callers must precompute non-colliding CIDs (the wrapper in internal/network/tap does this).

func (*Store) SetAllowPublicTraffic

func (s *Store) SetAllowPublicTraffic(ctx context.Context, id string, allow bool, publicURL string) error

SetAllowPublicTraffic flips the public-exposure flag together with the derived public_url in one statement. The expose_port opt-in path (the only way a private sandbox becomes public after create) is the caller; the two columns must move atomically because every Get materializes both — a public row with an empty public_url (or the reverse) would misreport reachability. A dedicated setter rather than Upsert so the flip doesn't race the runtime-state machine on the rest of the row.

func (*Store) SetAutoImportPending

func (s *Store) SetAutoImportPending(ctx context.Context, id string, pending bool) error

SetAutoImportPending toggles the AOCR auto-import retry flag. The post-pull auto-import path sets it to true on failure; the reconciler clears it after a successful import. The reconciler must call this rather than Upsert to avoid racing the runtime-state machine on the rest of the sandbox row.

func (*Store) SetCustomDomainStatus

func (s *Store) SetCustomDomainStatus(ctx context.Context, hostname string, status models.CustomDomainStatus, lastError string) error

SetCustomDomainStatus updates the per-domain state machine. Idempotent — repeated calls with the same (status, lastError) are still write-once on updated_at, which the caller may use as a heartbeat for "we saw an ask for this host". Returns ErrNotFound when the hostname is unknown so a caller observing an issuance failure for a since-removed host gets a clean signal.

func (*Store) SetFleetSuspended

func (s *Store) SetFleetSuspended(ctx context.Context, id string, suspended bool) error

SetFleetSuspended flips the fleet-suspend marker on a sandbox. The enforcement loop sets it true when a standing=suspend directive stops a running sandbox, and clears it on recovery so only fleet-suspended sandboxes are auto-restarted (a user/operator stop is left alone). Idempotent: writing the same value twice is a harmless no-op UPDATE. Returns ErrNotFound if the row is gone (already deleted), which callers treat as success — there is nothing left to converge.

func (*Store) SetNetworkLimits

func (s *Store) SetNetworkLimits(ctx context.Context, id string, bytesInLimit, bytesOutLimit int64) error

SetNetworkLimits replaces the per-sandbox network byte caps. Zero means unlimited; negative values are rejected. The handler validates first so the store does not re-validate. Returns ErrNotFound if no row matches id.

func (*Store) SetSnapshotPushState

func (s *Store) SetSnapshotPushState(ctx context.Context, name, state, errMsg string) error

SetSnapshotPushState is a narrow single-column update used by the push reconciler. errMsg is overwritten unconditionally (including to empty on success transitions) so callers don't have to remember to clear it.

func (*Store) SetTemplatePushState

func (s *Store) SetTemplatePushState(ctx context.Context, id, state, errMsg string) error

SetTemplatePushState is a narrow single-column update used by the push reconciler. errMsg is overwritten unconditionally (including to empty on success transitions) so callers don't have to remember to clear it. Mirrors SetSnapshotPushState.

func (*Store) SetWakeArmed

func (s *Store) SetWakeArmed(ctx context.Context, id string, armed bool) error

SetWakeArmed toggles the wake_armed flag and bumps updated_at. The flag is set when the sandbox stops in a way that should auto-resume on the next inbound HTTP request (lifecycle idle / involuntary exit, both while Lifecycle.Serverless is true). It is cleared on a manual stop and after a successful wake. Returns ErrNotFound if no row matches id.

This is a dedicated setter rather than going through Upsert so the stop-event path and wake completion don't race the rest of the runtime state on the row (status, container_id, container_ip, etc.).

func (*Store) Touch

func (s *Store) Touch(ctx context.Context, id string, at time.Time) error

func (*Store) TransferFirecrackerTapSlot

func (s *Store) TransferFirecrackerTapSlot(ctx context.Context, fromID, toID string, now time.Time) (*FirecrackerTapSlot, error)

TransferFirecrackerTapSlot re-keys an allocated TAP slot from one owner id to another without changing the TAP name, IPs, or vsock CID. The warm-VMM pool uses this when a parked slot id becomes a real sandbox id. Retrying the same transfer is idempotent once toID owns the slot; trying to overwrite a different target slot fails before the UPDATE hits the partial unique index.

func (*Store) TryReserveHostPort

func (s *Store) TryReserveHostPort(ctx context.Context, sandboxID string, containerPort, hostPort int, protocol, publicURL string, now time.Time) (ReserveHostPortResult, error)

TryReserveHostPort attempts to claim hostPort for (sandboxID, containerPort) in a single INSERT OR IGNORE. The OR IGNORE swallows two distinct UNIQUE failures — the (sandbox_id, port) primary key AND the partial index on host_port — so on a no-op insert we follow up with a SELECT to disambiguate. Without that disambiguation, retrying expose for an already-exposed port looks identical to a host_port collision and walks the whole allocator pool before failing with "exhausted".

func (*Store) UpdateLifecycle

func (s *Store) UpdateLifecycle(ctx context.Context, id string, l models.Lifecycle) error

UpdateLifecycle replaces the lifecycle fields on a sandbox row (the four timers plus the serverless opt-in) and bumps updated_at. Other fields are untouched. Returns ErrNotFound if no row matches id. The caller must validate the Lifecycle first; the store does not re-validate (it would couple two layers for no gain). wake_armed is intentionally NOT touched here — it transitions on stop/wake events, not on lifecycle edits.

func (*Store) UpdateRuntime

func (s *Store) UpdateRuntime(ctx context.Context, id, containerID, containerIP, publicURL string) error

func (*Store) UpdateSandboxNetCounters

func (s *Store) UpdateSandboxNetCounters(ctx context.Context, id string, deltaIn, deltaOut int64) error

UpdateSandboxNetCounters bumps the cumulative ingress/egress counters by the given deltas. Both values are non-negative byte counts measured since the last sample. Concurrent calls are serialized by SQLite's single writer, and the UPDATE is atomic so a failed sample never partially applies. Returns ErrNotFound if the sandbox row was deleted between the poller's snapshot and this write — the netstats poller treats that as a cleanup signal and drops the in-memory baseline.

func (*Store) UpdateSnapshotImageDistribution

func (s *Store) UpdateSnapshotImageDistribution(ctx context.Context, name, mode, registryRef, digest string) error

UpdateSnapshotImageDistribution flips the distribution metadata on a snapshot row after a successful AOCR push — local_only → aocr. Called from the reconciler success path together with SetSnapshotPushState. VerifiedAt records when the push completed; cluster placement on other nodes uses this together with the new mode to decide the snapshot is fan-outable.

func (*Store) UpdateStatus

func (s *Store) UpdateStatus(ctx context.Context, id string, status models.SandboxStatus, lastError string) error

func (*Store) UpdateTags

func (s *Store) UpdateTags(ctx context.Context, id string, tags map[string]string) error

UpdateTags replaces sandboxes.tags_json on the row matching id and bumps updated_at. Used by facades that want to mutate the native tags field without round-tripping the entire sandbox struct through Upsert. Returns ErrNotFound if no row matches.

func (*Store) UpdateTemplatePushDistribution

func (s *Store) UpdateTemplatePushDistribution(ctx context.Context, id, ref, digest string) error

UpdateTemplatePushDistribution stamps the registry destination metadata after a successful AOCR push. ref is the canonical repo:tag the daemon pushed; digest is the manifest digest the registry surfaced via the push stream's `aux` payload (may be empty). Called from the reconciler success path together with SetTemplatePushState.

Written in one statement so a crash between the two fields cannot land a half-filled row — the consumer-side pull in PR 6-B.2 sees either both fields populated or both empty.

func (*Store) UpdateTemplateSnapshotFailed

func (s *Store) UpdateTemplateSnapshotFailed(ctx context.Context, id, snapshotError string) error

UpdateTemplateSnapshotFailed records the snapshot-phase error and flips status to ready_no_snapshot. The rootfs columns are untouched — the caller has already populated them via UpdateTemplateStatus during the rootfs phase, and the cold-boot fallback still needs the rootfs path intact. has_snapshot stays 0 (column default) so readers correctly skip the snapshot-load path.

func (*Store) UpdateTemplateSnapshotReady

func (s *Store) UpdateTemplateSnapshotReady(ctx context.Context, id, memPath, statePath string, sizeBytes int64, checksum string, vsockCID uint32, hasOverlay bool) error

UpdateTemplateSnapshotReady is the terminal-success seam for the snapshot phase. Writes the snapshot artifact metadata and flips status=ready / has_snapshot=1 / has_overlay=hasOverlay in one UPDATE so a concurrent reader (CreateSandbox racing the build goroutine) never observes "status=ready but the snapshot fields are still zero". snapshot_error is unconditionally cleared so a retried build that finally succeeds doesn't carry a stale message. hasOverlay is true for every PR-B-built template (the snapshot capture path always includes the overlay placeholder); kept as a parameter so a future "snapshot without overlay" build profile (e.g. a tiny boot-only template) does not require a schema change.

func (*Store) UpdateTemplateStatus

func (s *Store) UpdateTemplateStatus(ctx context.Context, id string, status models.TemplateStatus, rootfsPath, lastError string, sizeBytes int64) error

UpdateTemplateStatus is the rootfs-phase seam the build goroutine uses to transition between pending / building_rootfs / ready / ready_no_snapshot / failed. rootfsPath, sizeBytes, and lastError are overwritten unconditionally (including to empty on the success path) so a retried build that succeeds doesn't have to remember to clear stale error text. ready_at is stamped on the ready and ready_no_snapshot transitions (both are terminal-and-usable states); the GC sweep treats "no ready_at" as "never finished building" and leaves the row alone for the build goroutine to finish or fail.

The snapshot phase uses UpdateTemplateSnapshotReady / Failed instead so the snapshot columns and the ready transition land in one row update — readers never observe "status=ready but has_snapshot=0".

func (*Store) UpdateWasmCheckpoint

func (s *Store) UpdateWasmCheckpoint(ctx context.Context, sandboxID, status, checkpointPath, cloneGen, lastError string) error

UpdateWasmCheckpoint persists passivation metadata on a sandbox row.

func (*Store) UpdateWasmRegistryPush

func (s *Store) UpdateWasmRegistryPush(ctx context.Context, sandboxID, registryRef, digest string) error

UpdateWasmRegistryPush records the AOCR ref/digest after a durable checkpoint push.

func (*Store) Upsert

func (s *Store) Upsert(ctx context.Context, sandbox *models.Sandbox) error

func (*Store) UpsertAccountMapping

func (s *Store) UpsertAccountMapping(ctx context.Context, ownerRef, externalID string) error

UpsertAccountMapping records (or refreshes) an identity resolved by the fleet control plane. first_seen is preserved across calls; last_seen advances to now. Idempotent by owner_ref PK. Open-source builds never call this (no validator resolves user tokens); managed builds call it at create time, not per request, to keep the auth hot path write-free.

func (*Store) UpsertCompatState

func (s *Store) UpsertCompatState(ctx context.Context, sandboxID, facade, stateJSON string) error

UpsertCompatState writes the facade-private state blob for (sandboxID, facade). stateJSON is opaque to the store — each facade defines its own schema inside it. created_at is preserved on update so list ordering stays stable.

func (*Store) UpsertPort

func (s *Store) UpsertPort(ctx context.Context, exposure models.ExposedPort) error

func (*Store) UpsertSnapshotAlias

func (s *Store) UpsertSnapshotAlias(ctx context.Context, alias models.SnapshotAlias) error

UpsertSnapshotAlias maps a facade-shaped alternate identifier onto a native sandbox_snapshots row. created_at is preserved on update.

func (*Store) UpsertWasmModule

func (s *Store) UpsertWasmModule(ctx context.Context, rec WasmModuleRecord) error

UpsertWasmModule inserts or updates a wasm_modules catalogue row.

func (*Store) WasmDigestsInUse

func (s *Store) WasmDigestsInUse(ctx context.Context, digests []string) (map[string]struct{}, error)

WasmDigestsInUse returns the subset of digests that are still referenced by a live sandbox OR pinned by a catalogue row. The cache evictor calls this ONCE per sweep with every candidate digest instead of two probes per file, so a large cache no longer issues O(files) serialized queries against the single-writer DB (codex P1). Digests are chunked to stay under SQLite's bound parameter limit.

type WasmCheckpointPushRecord

type WasmCheckpointPushRecord struct {
	ID          int64
	SandboxID   string
	RegistryRef string
	Digest      string
	PushedAt    time.Time
}

WasmCheckpointPushRecord is one AOCR push history row.

type WasmModuleRecord

type WasmModuleRecord struct {
	ID              string
	ModuleRef       string
	Status          string
	ModulePath      string
	ModuleSizeBytes int64
	Digest          string
	Entrypoint      string
	HasWarm         bool
	LastError       string
	CreatedAt       time.Time
	UpdatedAt       time.Time
	ReadyAt         *time.Time
}

WasmModuleRecord is one row in wasm_modules.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL