diff --git a/apps/gittensory-ui/src/components/site/docs-nav.tsx b/apps/gittensory-ui/src/components/site/docs-nav.tsx
index 6ddd410859..0b33f14651 100644
--- a/apps/gittensory-ui/src/components/site/docs-nav.tsx
+++ b/apps/gittensory-ui/src/components/site/docs-nav.tsx
@@ -57,6 +57,7 @@ export const docsNav: DocsGroup[] = [
title: "Self-hosting: release & security",
items: [
{ to: "/docs/self-hosting-releases", label: "Releases & images" },
+ { to: "/docs/self-hosting-release-checklist", label: "Beta release checklist" },
{ to: "/docs/self-hosting-security", label: "Security" },
],
},
diff --git a/apps/gittensory-ui/src/routeTree.gen.ts b/apps/gittensory-ui/src/routeTree.gen.ts
index 8d8c8d2ed7..5b1a6cef2e 100644
--- a/apps/gittensory-ui/src/routeTree.gen.ts
+++ b/apps/gittensory-ui/src/routeTree.gen.ts
@@ -28,6 +28,7 @@ import { Route as DocsTroubleshootingRouteImport } from './routes/docs.troublesh
import { Route as DocsSelfHostingTroubleshootingRouteImport } from './routes/docs.self-hosting-troubleshooting'
import { Route as DocsSelfHostingSecurityRouteImport } from './routes/docs.self-hosting-security'
import { Route as DocsSelfHostingReleasesRouteImport } from './routes/docs.self-hosting-releases'
+import { Route as DocsSelfHostingReleaseChecklistRouteImport } from './routes/docs.self-hosting-release-checklist'
import { Route as DocsSelfHostingReesAnalyzersRouteImport } from './routes/docs.self-hosting-rees-analyzers'
import { Route as DocsSelfHostingReesRouteImport } from './routes/docs.self-hosting-rees'
import { Route as DocsSelfHostingRagRouteImport } from './routes/docs.self-hosting-rag'
@@ -162,6 +163,12 @@ const DocsSelfHostingReleasesRoute = DocsSelfHostingReleasesRouteImport.update({
path: '/self-hosting-releases',
getParentRoute: () => DocsRoute,
} as any)
+const DocsSelfHostingReleaseChecklistRoute =
+ DocsSelfHostingReleaseChecklistRouteImport.update({
+ id: '/self-hosting-release-checklist',
+ path: '/self-hosting-release-checklist',
+ getParentRoute: () => DocsRoute,
+ } as any)
const DocsSelfHostingReesAnalyzersRoute =
DocsSelfHostingReesAnalyzersRouteImport.update({
id: '/self-hosting-rees-analyzers',
@@ -405,6 +412,7 @@ export interface FileRoutesByFullPath {
'/docs/self-hosting-rag': typeof DocsSelfHostingRagRoute
'/docs/self-hosting-rees': typeof DocsSelfHostingReesRoute
'/docs/self-hosting-rees-analyzers': typeof DocsSelfHostingReesAnalyzersRoute
+ '/docs/self-hosting-release-checklist': typeof DocsSelfHostingReleaseChecklistRoute
'/docs/self-hosting-releases': typeof DocsSelfHostingReleasesRoute
'/docs/self-hosting-security': typeof DocsSelfHostingSecurityRoute
'/docs/self-hosting-troubleshooting': typeof DocsSelfHostingTroubleshootingRoute
@@ -460,6 +468,7 @@ export interface FileRoutesByTo {
'/docs/self-hosting-rag': typeof DocsSelfHostingRagRoute
'/docs/self-hosting-rees': typeof DocsSelfHostingReesRoute
'/docs/self-hosting-rees-analyzers': typeof DocsSelfHostingReesAnalyzersRoute
+ '/docs/self-hosting-release-checklist': typeof DocsSelfHostingReleaseChecklistRoute
'/docs/self-hosting-releases': typeof DocsSelfHostingReleasesRoute
'/docs/self-hosting-security': typeof DocsSelfHostingSecurityRoute
'/docs/self-hosting-troubleshooting': typeof DocsSelfHostingTroubleshootingRoute
@@ -519,6 +528,7 @@ export interface FileRoutesById {
'/docs/self-hosting-rag': typeof DocsSelfHostingRagRoute
'/docs/self-hosting-rees': typeof DocsSelfHostingReesRoute
'/docs/self-hosting-rees-analyzers': typeof DocsSelfHostingReesAnalyzersRoute
+ '/docs/self-hosting-release-checklist': typeof DocsSelfHostingReleaseChecklistRoute
'/docs/self-hosting-releases': typeof DocsSelfHostingReleasesRoute
'/docs/self-hosting-security': typeof DocsSelfHostingSecurityRoute
'/docs/self-hosting-troubleshooting': typeof DocsSelfHostingTroubleshootingRoute
@@ -579,6 +589,7 @@ export interface FileRouteTypes {
| '/docs/self-hosting-rag'
| '/docs/self-hosting-rees'
| '/docs/self-hosting-rees-analyzers'
+ | '/docs/self-hosting-release-checklist'
| '/docs/self-hosting-releases'
| '/docs/self-hosting-security'
| '/docs/self-hosting-troubleshooting'
@@ -634,6 +645,7 @@ export interface FileRouteTypes {
| '/docs/self-hosting-rag'
| '/docs/self-hosting-rees'
| '/docs/self-hosting-rees-analyzers'
+ | '/docs/self-hosting-release-checklist'
| '/docs/self-hosting-releases'
| '/docs/self-hosting-security'
| '/docs/self-hosting-troubleshooting'
@@ -692,6 +704,7 @@ export interface FileRouteTypes {
| '/docs/self-hosting-rag'
| '/docs/self-hosting-rees'
| '/docs/self-hosting-rees-analyzers'
+ | '/docs/self-hosting-release-checklist'
| '/docs/self-hosting-releases'
| '/docs/self-hosting-security'
| '/docs/self-hosting-troubleshooting'
@@ -851,6 +864,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof DocsSelfHostingReleasesRouteImport
parentRoute: typeof DocsRoute
}
+ '/docs/self-hosting-release-checklist': {
+ id: '/docs/self-hosting-release-checklist'
+ path: '/self-hosting-release-checklist'
+ fullPath: '/docs/self-hosting-release-checklist'
+ preLoaderRoute: typeof DocsSelfHostingReleaseChecklistRouteImport
+ parentRoute: typeof DocsRoute
+ }
'/docs/self-hosting-rees-analyzers': {
id: '/docs/self-hosting-rees-analyzers'
path: '/self-hosting-rees-analyzers'
@@ -1184,6 +1204,7 @@ interface DocsRouteChildren {
DocsSelfHostingRagRoute: typeof DocsSelfHostingRagRoute
DocsSelfHostingReesRoute: typeof DocsSelfHostingReesRoute
DocsSelfHostingReesAnalyzersRoute: typeof DocsSelfHostingReesAnalyzersRoute
+ DocsSelfHostingReleaseChecklistRoute: typeof DocsSelfHostingReleaseChecklistRoute
DocsSelfHostingReleasesRoute: typeof DocsSelfHostingReleasesRoute
DocsSelfHostingSecurityRoute: typeof DocsSelfHostingSecurityRoute
DocsSelfHostingTroubleshootingRoute: typeof DocsSelfHostingTroubleshootingRoute
@@ -1218,6 +1239,7 @@ const DocsRouteChildren: DocsRouteChildren = {
DocsSelfHostingRagRoute: DocsSelfHostingRagRoute,
DocsSelfHostingReesRoute: DocsSelfHostingReesRoute,
DocsSelfHostingReesAnalyzersRoute: DocsSelfHostingReesAnalyzersRoute,
+ DocsSelfHostingReleaseChecklistRoute: DocsSelfHostingReleaseChecklistRoute,
DocsSelfHostingReleasesRoute: DocsSelfHostingReleasesRoute,
DocsSelfHostingSecurityRoute: DocsSelfHostingSecurityRoute,
DocsSelfHostingTroubleshootingRoute: DocsSelfHostingTroubleshootingRoute,
diff --git a/apps/gittensory-ui/src/routes/docs.self-hosting-release-checklist.tsx b/apps/gittensory-ui/src/routes/docs.self-hosting-release-checklist.tsx
new file mode 100644
index 0000000000..83fb90a3ac
--- /dev/null
+++ b/apps/gittensory-ui/src/routes/docs.self-hosting-release-checklist.tsx
@@ -0,0 +1,245 @@
+import { createFileRoute, Link } from "@tanstack/react-router";
+
+import { DocsPage } from "@/components/site/docs-page";
+import { Callout, CodeBlock, FeatureRow } from "@/components/site/primitives";
+
+export const Route = createFileRoute("/docs/self-hosting-release-checklist")({
+ head: () => ({
+ meta: [
+ { title: "Beta release checklist — Gittensory docs" },
+ {
+ name: "description",
+ content:
+ "The smoke matrix to run before publishing a self-host RC image: direct App, brokered, air-gapped, each AI provider, SQLite/Postgres, Redis/Qdrant. Portable commands, expected log events, known-warnings table.",
+ },
+ { property: "og:title", content: "Beta release checklist — Gittensory docs" },
+ {
+ property: "og:description",
+ content:
+ "The smoke matrix to run before publishing a self-host RC image: direct App, brokered, air-gapped, each AI provider, SQLite/Postgres, Redis/Qdrant.",
+ },
+ { property: "og:url", content: "/docs/self-hosting-release-checklist" },
+ ],
+ links: [{ rel: "canonical", href: "/docs/self-hosting-release-checklist" }],
+ }),
+ component: SelfHostingReleaseChecklist,
+});
+
+function SelfHostingReleaseChecklist() {
+ return (
+
+ Every scenario below shares the same core check —
+ No
+
+
+
+ Each provider choice must log
+ SQLite is the default — the base smoke command above already covers it (no{" "}
+
+ Redis is always-on in every scenario above (the base script already boots it) — confirm{" "}
+
+ After every applicable scenario passes, continue with the normal{" "}
+ upgrade flow to cut the tag and publish the
+ image.
+ scripts/smoke-selfhost.sh{" "}
+ boots one container against a fresh Redis on an isolated network, waits for it to become
+ healthy, and asserts on /health, /ready, /metrics,
+ and startup log events. What changes per scenario is the env you pass in and which events
+ you expect (or forbid).
+ 1. Direct GitHub App mode (default)
+ ORB_ENROLLMENT_SECRET — the container uses its own GitHub App private key.
+ Telemetry export is always-on in this mode too; a clean run produces no export error.
+ selfhost_orb_relay_register must NOT appear here — relay registration is
+ brokered-only and silently skips in direct mode (see{" "}
+ GitHub App and Orb).
+ 2. Brokered mode (private / managed-beta only)
+ ORB_ENROLLMENT_SECRET set — the container gets tokens from the central Orb
+ instead of its own App key. A working push-mode registration logs{" "}
+ selfhost_orb_relay_register; a broken one is fatal for push mode (logged at{" "}
+ error, not warn).
+ 3. Air-gapped / no-telemetry mode
+ ORB_AIR_GAP=true disables the fleet-calibration export entirely. There is no
+ "air-gap confirmed" log event — the export function returns before doing anything, so
+ silence (no export error, no export attempt) is the signal. Confirm at the network level
+ too: no outbound request to the collector URL.
+ 4. AI provider: Claude Code / Codex / both
+ selfhost_ai_provider and must NOT log{" "}
+ selfhost_ai_cli_missing (a CLI-subscription provider whose binary isn't on{" "}
+ PATH silently produces no review output — this must be caught here, not in
+ production).
+ /ready (it probes the
+ configured AI provider). Where credentials aren't available for a given RC run, at minimum
+ confirm selfhost_ai_cli_missing does NOT appear — that alone catches the
+ release-blocking case (image built without INSTALL_AI_CLIS=true).
+ 5. SQLite trial mode / Postgres production mode
+ DATABASE_URL set). For Postgres, boot a Postgres container on the same network
+ first and point DATABASE_URL at it.
+ 6. Redis cache + optional Qdrant RAG
+ selfhost_redis_ready appears with githubResponseCacheEnabled{" "}
+ matching whatever GITHUB_CACHE_TTL_SECONDS you set. For the optional Qdrant RAG
+ path, boot Qdrant on the same network and point QDRANT_URL at it.
+ Expected startup events
+ Known warnings: acceptable in beta vs. release-blocking
+ X.Y.Z tag still moves{" "}
latest and publishes an unmarked (non-prerelease) GitHub Release.
+ orb-v* release or prerelease, run the{" "}
+ beta release checklist against the
+ built image — CI only smoke-tests the plain SQLite + Redis + direct-App default, not
+ brokered mode, air-gapped mode, or any AI provider.
+