feat(db): persist runtime-service exposure state (PAP-17051)

Add a nullable `exposure` jsonb column to `workspace_runtime_services`, typed
with the shared `RuntimeExposureStatus` contract, plus additive migration
0212. Exposure lifecycle (pending/ready/failed/cleanup_pending/removed),
listener ownership, resolved hostname, canonical public URL, broker ref, and
last non-sensitive error persist here, modelled independently of process
`status`/`healthStatus` so HTTPS provisioning/failure/cleanup surface
separately from backend health. Flows through the runtime-service read model
automatically via drizzle `$inferSelect`.

Migration is additive-only (single ADD COLUMN) and hand-authored to avoid
sweeping unrelated stale-dist schema; numbering + safety gates pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
DottaandClaude Opus 4.8 committed 2026-08-16 19:24:27 +00:00
1 parent ec7adcaf3d
commit 1d7d22fae0
3 files changed
+15 -1

No files matched your search

@@ -0,0 +1 @@
ALTER TABLE "workspace_runtime_services" ADD COLUMN "exposure" jsonb;
+8 -1
View File
@@ -1520,6 +1520,13 @@
"when": 1786711898729,
"tag": "0218_mushy_jack_murdock",
"breakpoints": true
},
{
"idx": 219,
"version": "7",
"when": 1786908241945,
"tag": "0219_runtime_service_exposure",
"breakpoints": true
}
]
}
}
@@ -7,6 +7,7 @@ import {
timestamp,
uuid,
} from "drizzle-orm/pg-core";
import type { RuntimeExposureStatus } from "@paperclipai/shared";
import { companies } from "./companies.js";
import { projects } from "./projects.js";
import { projectWorkspaces } from "./project_workspaces.js";
@@ -42,6 +43,11 @@ export const workspaceRuntimeServices = pgTable(
startedAt: timestamp("started_at", { withTimezone: true }).notNull().defaultNow(),
stoppedAt: timestamp("stopped_at", { withTimezone: true }),
stopPolicy: jsonb("stop_policy").$type<Record<string, unknown>>(),
// Structured opt-in `tailscale_https` exposure state (PAP-17049/17050).
// Null when the service is not configured for HTTPS exposure. Modelled
// independently of process `status`/`healthStatus` so HTTPS provisioning /
// failure / cleanup surface separately from backend process health.
exposure: jsonb("exposure").$type<RuntimeExposureStatus | null>(),
healthStatus: text("health_status").notNull().default("unknown"),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),