feat(workload): add Workload/Container/App store foundation

Introduces the data layer for the Workload refactor (see
docs/plans/workload-refactor.md): three new tables and store
methods, no behavior changes elsewhere yet.

- workloads: unifying primitive over Project/Stack/StaticSite,
  paired via UNIQUE(kind, ref_id). Notification + webhook config
  hosted here so it lives in one place across kinds.
- containers: normalized index of every Tinyforge-managed
  container with first-class subdomain/proxy_route_id/npm_proxy_id
  columns (heavily queried by ListProxyRoutes / stale detection).
- apps: optional grouping of workloads; schema only, no UI in v1.

Foundation only — deployer surgery, reconciler, and consumer
switchover land in the next commit.
This commit is contained in:
2026-05-09 13:22:25 +03:00
parent 0f60a7a5db
commit f54a6ecee3
9 changed files with 1389 additions and 0 deletions
+62
View File
@@ -328,3 +328,65 @@ type EventLog struct {
CreatedAt string `json:"created_at"`
}
// WorkloadKind enumerates the kinds of things that own containers.
// Each kind has a corresponding row in projects/stacks/static_sites referenced via Workload.RefID.
type WorkloadKind string
const (
WorkloadKindProject WorkloadKind = "project"
WorkloadKindStack WorkloadKind = "stack"
WorkloadKindSite WorkloadKind = "site"
)
// Workload is the unifying primitive that abstracts Project, Stack, and StaticSite.
// Each row is paired with exactly one project/stack/site via (Kind, RefID).
// Notification + webhook config moves here so it lives in one place across kinds.
type Workload struct {
ID string `json:"id"`
Kind string `json:"kind"` // project | stack | site
RefID string `json:"ref_id"`
Name string `json:"name"`
AppID string `json:"app_id"` // nullable; "" = unassigned
NotificationURL string `json:"notification_url"`
NotificationSecret string `json:"-"` // never serialized
WebhookSecret string `json:"-"` // URL-identifier secret; never serialized
WebhookSigningSecret string `json:"-"` // HMAC key; never serialized
WebhookRequireSignature bool `json:"webhook_require_signature"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// Container is the normalized index of every Tinyforge-managed container.
// Replaces the project-specific Instance table after migration. Subdomain/
// proxy fields are hoisted as first-class columns because ListProxyRoutes,
// stale detection, and dashboard queries filter on them frequently.
type Container struct {
ID string `json:"id"`
WorkloadID string `json:"workload_id"`
WorkloadKind string `json:"workload_kind"` // denormalized for filtered queries
Role string `json:"role"` // stage name (project), service name (stack), '' (site)
ContainerID string `json:"container_id"` // Docker container ID; '' between create+start
ImageRef string `json:"image_ref"` // "image:tag" as scheduled
ImageTag string `json:"image_tag"` // just the tag, for ListProxyRoutes
Host string `json:"host"`
State string `json:"state"` // running | stopped | failed | removing | missing
Port int `json:"port"`
Subdomain string `json:"subdomain"`
ProxyRouteID string `json:"proxy_route_id"`
NpmProxyID int `json:"npm_proxy_id"`
LastSeenAt string `json:"last_seen_at"`
ExtraJSON string `json:"extra_json"` // {} default; reserved for kind-specific forward-compat
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// App is an optional grouping of workloads (e.g., "my-saas" = web project + worker stack + redis stack).
// Schema lives here from day one so future UI work is unblocked, but no UI is wired in v1.
type App struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}