Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions apps/api/src/services/channel-spine-provisioning.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { describe, expect, test } from "bun:test";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { channelSpineDefaults } from "./channel-spine-provisioning.service.js";

const repoRoot = join(import.meta.dir, "../../../..");
const productionCompose = readFileSync(
join(repoRoot, "compose.production.yml"),
"utf8",
);

/**
* New workspaces inherit the deployment's defaults; existing ones never do.
* The env module snapshots at import, so this asserts the parsing contract
Expand All @@ -14,6 +22,17 @@ describe("channelSpineDefaults", () => {
});
});

describe("production channel-spine defaults", () => {
test("passes explicit provider and revision defaults to every API replica", () => {
expect(productionCompose).toContain(
"CHANNEL_SPINE_DEFAULT_PROVIDERS: ${CHANNEL_SPINE_DEFAULT_PROVIDERS:-}",
);
expect(productionCompose).toContain(
"CHANNEL_SPINE_DEFAULT_REVISION: ${CHANNEL_SPINE_DEFAULT_REVISION:-}",
);
});
});

describe("provider list parsing", () => {
// Mirrors the parsing in channelSpineDefaults, which reads a snapshotted
// env value; this pins the shape a deployment is expected to write.
Expand Down
8 changes: 8 additions & 0 deletions apps/api/src/services/tenant.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
getTenantSchemaName,
reconcileChannelSpineConcurrentIndexes,
reconcileTenantSchema,
type TenantDatabase as TenantDatabaseType,
} from "@wateaminbox/database";
Expand Down Expand Up @@ -67,6 +68,13 @@ export async function createTenantSchema(companyId: string): Promise<void> {
const schemaName = getSchemaName(companyId);
await sql`SELECT setup_tenant_schema(${schemaName})`.execute(baseTenantDb);
await reconcileTenantSchema(baseTenantDb, schemaName);
// The provider-spine uniqueness indexes use CREATE INDEX CONCURRENTLY and
// deliberately do not live in a tenant migration. When this deployment
// seeds neutral defaults, provisioning must build them before returning or
// every new workspace would be flagged on but fail readiness indefinitely.
if (env.CHANNEL_SPINE_DEFAULT_PROVIDERS.trim()) {
await reconcileChannelSpineConcurrentIndexes(baseTenantDb, schemaName);
}
}

export async function dropTenantSchema(companyId: string): Promise<void> {
Expand Down
4 changes: 4 additions & 0 deletions compose.production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ services:
PORT: "4445"
APP_URL: https://${APP_DOMAIN:?set APP_DOMAIN}
CORS_ORIGINS: https://${APP_DOMAIN:?set APP_DOMAIN}
# Hosted deployments may opt new workspaces into provider-neutral
# messaging. Empty defaults preserve the self-hosted legacy path.
CHANNEL_SPINE_DEFAULT_PROVIDERS: ${CHANNEL_SPINE_DEFAULT_PROVIDERS:-}
CHANNEL_SPINE_DEFAULT_REVISION: ${CHANNEL_SPINE_DEFAULT_REVISION:-}
JWT_SECRET_FILE: /run/secrets/jwt_secret
# Envelope key for provider credentials (a Telegram bot token, and any
# future token-based adapter). Optional: a linked-device-only host stores
Expand Down
Loading