Keep root-platform identity separate from tenant labels

---
Build: pass | Tests: FAIL — 0 failed
This commit is contained in:
Operator & Codex 2026-05-04 06:24:32 +02:00
parent d383e88b09
commit a99f97172d
12 changed files with 51 additions and 25 deletions

View file

@ -26,6 +26,7 @@ import {
PI_TUI_THINKING,
PI_TUI_TOOLS,
PROJECT_ROOT,
RUNTIME_ID,
TENANT_ID,
TMP_DIR,
TIMEZONE,
@ -52,7 +53,7 @@ import {
type PiRuntimeUsage,
} from './pi-usage.js';
const METRICS_PREFIX = `${TENANT_ID}_`;
const METRICS_PREFIX = `${RUNTIME_ID}_`;
import { logger } from './logger.js';
import {
applyFallback,
@ -345,7 +346,7 @@ export async function runJailAgent(
fs.mkdirSync(logDir, { recursive: true });
const safeName = input.groupFolder.replace(/[^a-zA-Z0-9-]/g, '-');
const runId = `${TENANT_ID}-${safeName}-${Date.now()}`;
const runId = `${RUNTIME_ID}-${safeName}-${Date.now()}`;
const logFile = path.join(logDir, `agent-${runId}.log`);
// ── Offline stub ──────────────────────────────────────────────────────

View file

@ -78,6 +78,7 @@ describe('config identity', () => {
const config = await import('./config.js');
expect(config.TENANT_ID).toBe('mevy');
expect(config.RUNTIME_ID).toBe('mevy');
expect(config.TENANT_DISPLAY_NAME).toBe('Bob');
expect(config.ASSISTANT_NAME).toBe('Bob');
expect(config.AGENT_DOMAIN).toBe('home.arpa');
@ -98,6 +99,7 @@ describe('config identity', () => {
const config = await import('./config.js');
expect(config.TENANT_ID).toBe('');
expect(config.RUNTIME_ID).toBe('clawdie');
expect(config.AGENT_CONFIG_DIR).toBe('clawdie-cp');
expect(config.AGENT_INTERNAL_DOMAIN).toBe('clawdie.home.arpa');
expect(config.CMS_WEBROOT).toBe('/usr/local/www/clawdie');

View file

@ -270,10 +270,13 @@ export const AGENT_GENDER: AgentGender = (() => {
})();
// ── Derived system identifiers ──
//
// RUNTIME_ID is for labels and derived paths that need a stable non-empty
// identifier in both root-platform and additive-tenant installs. It does not
// change TENANT_ID semantics: the root install still has TENANT_ID=''.
export const RUNTIME_ID = TENANT_ID || SERVICE_NAME;
const RUNTIME_TENANT_ID = TENANT_ID || SERVICE_NAME;
export const AGENT_CONFIG_DIR = tenantControlplanePrefix(RUNTIME_TENANT_ID);
export const AGENT_CONFIG_DIR = tenantControlplanePrefix(RUNTIME_ID);
export const AGENT_DOMAIN =
process.env.AGENT_DOMAIN ||
envConfig.AGENT_DOMAIN ||
@ -285,7 +288,7 @@ export const AGENT_INTERNAL_DOMAIN =
process.env.AGENT_INTERNAL_DOMAIN ||
envConfig.AGENT_INTERNAL_DOMAIN ||
registryDefaults?.tenants[TENANT_ID]?.internalDomain ||
tenantInternalDomain(RUNTIME_TENANT_ID, PLATFORM_INTERNAL_BASE);
tenantInternalDomain(RUNTIME_ID, PLATFORM_INTERNAL_BASE);
export const CONTROLPLANE_INTERNAL_DOMAIN = PLATFORM_INTERNAL_DOMAIN;
export const CONTROLPLANE_HOST_LABEL =
CONTROLPLANE_INTERNAL_DOMAIN.split('.')[0] || 'ai';
@ -714,11 +717,11 @@ export const CMS_JAIL_IP =
export const CMS_WEBROOT =
process.env.CMS_WEBROOT ||
envConfig.CMS_WEBROOT ||
`/usr/local/www/${RUNTIME_TENANT_ID}`;
`/usr/local/www/${RUNTIME_ID}`;
export const ASTRO_SITE_PATH =
process.env.ASTRO_SITE_PATH ||
envConfig.ASTRO_SITE_PATH ||
tenantSiteRoot(RUNTIME_TENANT_ID, PLATFORM_RUNTIME_HOME);
tenantSiteRoot(RUNTIME_ID, PLATFORM_RUNTIME_HOME);
export const GIT_JAIL_IP =
process.env.WARDEN_GIT_IP ||
envConfig.WARDEN_GIT_IP ||

View file

@ -14,6 +14,7 @@ import {
CMS_WEBROOT,
CONTROLPLANE_MAX_BODY_BYTES,
CONTROLPLANE_BIND_HOST,
RUNTIME_ID,
TENANT_ID,
} from './config.js';
import {
@ -59,7 +60,7 @@ import { logger } from './logger.js';
// ── Types ──────────────────────────────────────────────────────────────────
const METRICS_PREFIX = `${TENANT_ID}_`;
const METRICS_PREFIX = `${RUNTIME_ID}_`;
export interface ControlplaneState {
agents: Array<{ id: string; role: string; heartbeat_enabled: boolean }>;

View file

@ -13,7 +13,7 @@ import { randomUUID } from 'crypto';
import net from 'net';
import { incLabeledCounter } from '../metrics.js';
import { TENANT_ID } from '../config.js';
import { RUNTIME_ID, TENANT_ID } from '../config.js';
import { buildHostdAuth } from './auth.js';
import { SOCKET_PATH } from './types.js';
import type {
@ -92,7 +92,7 @@ export class HostdClient {
try {
const resp = JSON.parse(line) as HostdResponse;
if (resp.id === id) {
const _mp = `${TENANT_ID}_`;
const _mp = `${RUNTIME_ID}_`;
incLabeledCounter(`${_mp}jail_ops_total`, 'op', op);
if (!resp.ok) incLabeledCounter(`${_mp}jail_ops_errors_total`, 'op', op);
settle(() => resolve(resp));

View file

@ -23,6 +23,7 @@ import {
PI_TUI_PROVIDER,
PI_TUI_MODEL,
PROJECT_ROOT,
RUNTIME_ID,
TELEGRAM_BOT_TOKEN,
STT_MODEL,
STT_MAX_ATTEMPTS,
@ -60,8 +61,8 @@ import { cleanupPendingMaintenanceSnapshots } from './maintenance-snapshots.js';
import { hostd } from './hostd/client.js';
// ── Metrics prefix — derived from agent name for multi-install dashboards ──
const METRICS_PREFIX = `${TENANT_ID}_`;
const ROOT_AGENT_ID = TENANT_ID.trim() || SERVICE_NAME;
const METRICS_PREFIX = `${RUNTIME_ID}_`;
const ROOT_AGENT_ID = RUNTIME_ID;
import { Watchdog } from './watchdog.js';
import { extractTmpImagePaths } from './outbound-images.js';
import './channels/telegram.js';

View file

@ -9,6 +9,7 @@ import {
LLAMA_CPP_INTERNAL_DOMAIN,
OLLAMA_INTERNAL_DOMAIN,
PLATFORM_INTERNAL_BASE,
RUNTIME_ID,
SUBNET_BASE,
TENANT_ID,
} from './config.js';
@ -41,11 +42,11 @@ function uniqueNames(names: string[]): string[] {
}
export function getLocalHostsBlockStart(): string {
return `# >>> ${TENANT_ID} local hosts >>>`;
return `# >>> ${RUNTIME_ID} local hosts >>>`;
}
export function getLocalHostsBlockEnd(): string {
return `# <<< ${TENANT_ID} local hosts <<<`;
return `# <<< ${RUNTIME_ID} local hosts <<<`;
}
export function getLocalHostsEntries(): LocalHostsEntry[] {

View file

@ -7,7 +7,7 @@
* Enable by setting METRICS_PORT (default 9100).
* Set METRICS_PORT=0 to disable entirely.
*
* Metrics exposed (prefix = ${TENANT_ID}_):
* Metrics exposed (prefix = ${RUNTIME_ID}_):
* sessions_started_total counter
* sessions_completed_total counter {status="ok|error|timeout"}
* session_duration_seconds_sum counter (sum of durations)
@ -23,10 +23,10 @@
*/
import http from 'http';
import { TENANT_ID } from './config.js';
import { RUNTIME_ID } from './config.js';
import { logger } from './logger.js';
const P = `${TENANT_ID}_`;
const P = `${RUNTIME_ID}_`;
// ── Registry ──────────────────────────────────────────────────────────────────

View file

@ -1,6 +1,6 @@
import pg from 'pg';
import { SKILLS_DB_URL, TENANT_ID } from './config.js';
import { RUNTIME_ID, SKILLS_DB_URL } from './config.js';
import { logger } from './logger.js';
import { incCounter } from './metrics.js';
@ -121,7 +121,7 @@ export async function searchBuiltinKnowledge(
[queryText, limit],
);
const _mp = `${TENANT_ID}_`;
const _mp = `${RUNTIME_ID}_`;
incCounter(`${_mp}skill_searches_total`);
incCounter(`${_mp}skill_search_hits_total`, rows.length);
return rows;

View file

@ -12,10 +12,11 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
// ---------------------------------------------------------------------------
const mockCreate = vi.hoisted(() => vi.fn());
const openAiCtor = vi.hoisted(() => vi.fn());
vi.mock('openai', () => {
return {
default: vi.fn().mockImplementation(function () {
default: openAiCtor.mockImplementation(function () {
return {
audio: {
transcriptions: {
@ -68,6 +69,22 @@ describe('initTranscription', () => {
}),
).not.toThrow();
});
it('uses a stable OpenRouter title in root-platform mode', async () => {
const { initTranscription: init } = await import('./transcription.js');
init({
provider: 'openrouter',
model: 'whisper-1',
openrouterApiKey: 'or-test-key',
});
expect(openAiCtor).toHaveBeenCalledWith(
expect.objectContaining({
defaultHeaders: expect.objectContaining({
'X-Title': 'clawdie-ai',
}),
}),
);
});
});
// ---------------------------------------------------------------------------

View file

@ -1,7 +1,7 @@
import { logger } from './logger.js';
import OpenAI from 'openai';
import fs from 'fs';
import { TENANT_ID } from './config.js';
import { RUNTIME_ID } from './config.js';
let openaiClient: OpenAI | null = null;
let transcriptionReady = false;
@ -123,7 +123,7 @@ export function initTranscription(
baseURL: 'https://openrouter.ai/api/v1',
defaultHeaders: {
'HTTP-Referer': 'https://codeberg.org/Clawdie/Clawdie-AI',
'X-Title': `${TENANT_ID}-ai`,
'X-Title': `${RUNTIME_ID}-ai`,
},
});
transcriptionReady = true;

View file

@ -3,7 +3,7 @@ import path from 'path';
import { readEnvFile } from './env.js';
import {
TENANT_ID,
RUNTIME_ID,
TMP_DIR,
VISION_MAX_CHARS_PER_IMAGE,
VISION_MAX_IMAGES,
@ -90,7 +90,7 @@ async function describeImageOpenRouter(imagePath: string): Promise<string> {
Authorization: `Bearer ${key}`,
'Content-Type': 'application/json',
'HTTP-Referer': 'https://codeberg.org/Clawdie/Clawdie-AI',
'X-Title': `${TENANT_ID}-ai`,
'X-Title': `${RUNTIME_ID}-ai`,
},
body: JSON.stringify(body),
});