rename(phase 3): compose/DB/infra + stray code refs capakraken → nexus (#62)
CI / Lint (push) Successful in 3m4s
CI / Typecheck (push) Successful in 3m6s
CI / Architecture Guardrails (push) Successful in 3m8s
CI / Assistant Split Regression (push) Successful in 3m48s
CI / Build (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Fresh-Linux Docker Deploy (push) Has been cancelled
CI / Release Images (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled

rename(phase 3): compose/DB/infra + stray code refs capakraken → nexus (#62)

Co-authored-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com>
Co-committed-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com>
This commit was merged in pull request #62.
This commit is contained in:
2026-05-21 20:07:18 +02:00
committed by Hartmut
parent b41c1d2501
commit 19aeb2ba04
44 changed files with 406 additions and 187 deletions
@@ -105,10 +105,10 @@ describe("RBAC cache Redis pub/sub (#57)", () => {
// Simulate a peer instance publishing an invalidation: grab any
// subscriber on the channel and fire the event as if Redis delivered it.
const subs = channelSubscribers.get("capakraken:rbac-invalidate");
const subs = channelSubscribers.get("nexus:rbac-invalidate");
expect(subs).toBeDefined();
expect(subs!.size).toBeGreaterThanOrEqual(1);
for (const sub of subs!) sub.emit("message", "capakraken:rbac-invalidate", "1");
for (const sub of subs!) sub.emit("message", "nexus:rbac-invalidate", "1");
// Next load must hit the DB again.
await loadRoleDefaults();
@@ -126,6 +126,6 @@ describe("RBAC cache Redis pub/sub (#57)", () => {
const newPublishes = publishCalls.slice(countBefore);
expect(newPublishes.length).toBe(1);
expect(newPublishes[0]!.channel).toBe("capakraken:rbac-invalidate");
expect(newPublishes[0]!.channel).toBe("nexus:rbac-invalidate");
});
});
@@ -24,7 +24,7 @@ describe("assertWebhookUrlAllowed — SSRF guard", () => {
it("allows an HTTPS URL with a path and query string", async () => {
await expect(
assertWebhookUrlAllowed("https://hooks.external.io/events?source=capakraken"),
assertWebhookUrlAllowed("https://hooks.external.io/events?source=nexus"),
).resolves.toBeUndefined();
});
+3 -3
View File
@@ -22,15 +22,15 @@ export function getAppBaseUrl(): string {
if (process.env["NODE_ENV"] === "production") {
throw new Error(
"NEXTAUTH_URL must be set in production — email links will contain localhost otherwise. " +
"Set it to the public URL of this app (e.g. https://capakraken.example.com).",
"Set it to the public URL of this app (e.g. https://nexus.example.com).",
);
}
if (!warned) {
warned = true;
console.warn(
"[capakraken] NEXTAUTH_URL is not set — falling back to http://localhost:3100 for email links. " +
"Set NEXTAUTH_URL in your .env to suppress this warning.",
"[nexus] NEXTAUTH_URL is not set — falling back to http://localhost:3100 for email links. " +
"Set NEXTAUTH_URL in your .env to suppress this warning.",
);
}
+2 -2
View File
@@ -44,13 +44,13 @@ const redactConfig = { paths: REDACT_PATHS, censor: "[REDACTED]" };
export const logger = isProduction
? pino({
level: LOG_LEVEL,
base: { service: "capakraken-api" },
base: { service: "nexus-api" },
redact: redactConfig,
})
: pino(
{
level: LOG_LEVEL,
base: { service: "capakraken-api" },
base: { service: "nexus-api" },
redact: redactConfig,
formatters: {
level(label: string) {
+1 -1
View File
@@ -31,7 +31,7 @@ type RateLimiterBackend = {
reset: () => Promise<void>;
};
const DEFAULT_REDIS_KEY_PREFIX = "capakraken:ratelimit";
const DEFAULT_REDIS_KEY_PREFIX = "nexus:ratelimit";
const DEFAULT_REDIS_BACKEND = process.env["RATE_LIMIT_BACKEND"] as RateLimitBackendMode | undefined;
const DEFAULT_REDIS_URL = process.env["REDIS_URL"]?.trim();
const warnedRedisFailures = new Set<string>();
+1 -1
View File
@@ -201,7 +201,7 @@ const REDIS_URL =
: (() => {
throw new Error("REDIS_URL required in production");
})());
const CHANNEL = "capakraken:sse";
const CHANNEL = "nexus:sse";
let publisher: Redis | null = null;
let subscriber: Redis | null = null;
+1 -1
View File
@@ -42,7 +42,7 @@ const ROLE_DEFAULTS_TTL = 10_000;
// We publish a single invalidate message per change; every node subscribes and
// clears its local cache on receipt. Failure to publish/subscribe is logged
// but never thrown — the TTL above is the fall-back.
const RBAC_INVALIDATE_CHANNEL = "capakraken:rbac-invalidate";
const RBAC_INVALIDATE_CHANNEL = "nexus:rbac-invalidate";
let _rbacPublisher: Redis | null = null;
let _rbacSubscriber: Redis | null = null;
+1 -1
View File
@@ -8,7 +8,7 @@
"./client": "./src/client.ts"
},
"scripts": {
"db:doctor": "node ../../scripts/db-doctor.mjs capakraken",
"db:doctor": "node ../../scripts/db-doctor.mjs nexus",
"db:push": "node ../../scripts/prisma-with-env.mjs db push --schema ./prisma/schema.prisma",
"db:migrate": "node ../../scripts/prisma-with-env.mjs migrate dev --schema ./prisma/schema.prisma",
"db:migrate:deploy": "node ../../scripts/prisma-with-env.mjs migrate deploy --schema ./prisma/schema.prisma",
+19 -19
View File
@@ -22,34 +22,34 @@ test.afterEach(() => {
process.env = { ...ORIGINAL_ENV };
});
test("assertDestructiveDbAllowed allows an explicitly confirmed disposable capakraken test database", () => {
test("assertDestructiveDbAllowed allows an explicitly confirmed disposable nexus test database", () => {
setEnv({
DATABASE_URL: "postgresql://tester:secret@localhost:5432/capakraken_test",
DATABASE_URL: "postgresql://tester:secret@localhost:5432/nexus_test",
ALLOW_DESTRUCTIVE_DB_TOOLS: "true",
CONFIRM_DESTRUCTIVE_DB_NAME: "capakraken_test",
CONFIRM_DESTRUCTIVE_DB_NAME: "nexus_test",
});
const target = assertDestructiveDbAllowed({
commandName: "db:test",
allowedDatabaseNames: ["capakraken_test"],
allowedDatabaseNames: ["nexus_test"],
});
assert.equal(target.databaseName, "capakraken_test");
assert.equal(target.databaseName, "nexus_test");
assert.equal(target.hostname, "localhost");
});
test("assertDestructiveDbAllowed rejects protected live database names even if allowlisted", () => {
setEnv({
DATABASE_URL: "postgresql://tester:secret@localhost:5432/capakraken",
DATABASE_URL: "postgresql://tester:secret@localhost:5432/nexus",
ALLOW_DESTRUCTIVE_DB_TOOLS: "true",
CONFIRM_DESTRUCTIVE_DB_NAME: "capakraken",
CONFIRM_DESTRUCTIVE_DB_NAME: "nexus",
});
assert.throws(
() =>
assertDestructiveDbAllowed({
commandName: "db:test",
allowedDatabaseNames: ["capakraken"],
allowedDatabaseNames: ["nexus"],
}),
/explicitly protected/u,
);
@@ -57,7 +57,7 @@ test("assertDestructiveDbAllowed rejects protected live database names even if a
test("assertDestructiveDbAllowed rejects missing confirmation", () => {
setEnv({
DATABASE_URL: "postgresql://tester:secret@localhost:5432/capakraken_e2e",
DATABASE_URL: "postgresql://tester:secret@localhost:5432/nexus_e2e",
ALLOW_DESTRUCTIVE_DB_TOOLS: "true",
CONFIRM_DESTRUCTIVE_DB_NAME: "wrong_db",
});
@@ -66,24 +66,24 @@ test("assertDestructiveDbAllowed rejects missing confirmation", () => {
() =>
assertDestructiveDbAllowed({
commandName: "db:test",
allowedDatabaseNames: ["capakraken_e2e"],
allowedDatabaseNames: ["nexus_e2e"],
}),
/CONFIRM_DESTRUCTIVE_DB_NAME=capakraken_e2e/u,
/CONFIRM_DESTRUCTIVE_DB_NAME=nexus_e2e/u,
);
});
test("assertDestructiveDbAllowed rejects missing destructive allow flag", () => {
setEnv({
DATABASE_URL: "postgresql://tester:secret@localhost:5432/capakraken_ci",
DATABASE_URL: "postgresql://tester:secret@localhost:5432/nexus_ci",
ALLOW_DESTRUCTIVE_DB_TOOLS: undefined,
CONFIRM_DESTRUCTIVE_DB_NAME: "capakraken_ci",
CONFIRM_DESTRUCTIVE_DB_NAME: "nexus_ci",
});
assert.throws(
() =>
assertDestructiveDbAllowed({
commandName: "db:test",
allowedDatabaseNames: ["capakraken_ci"],
allowedDatabaseNames: ["nexus_ci"],
}),
/ALLOW_DESTRUCTIVE_DB_TOOLS=true/u,
);
@@ -99,19 +99,19 @@ test("assertSafeSeedTarget rejects unexpected legacy disposable databases", () =
assert.throws(() => assertSafeSeedTarget("db:seed"), /not in the destructive-tool allowlist/u);
});
test("assertNexusDbTarget accepts non-destructive capakraken targets", () => {
test("assertNexusDbTarget accepts non-destructive nexus targets", () => {
setEnv({
DATABASE_URL: "postgresql://tester:secret@localhost:5432/capakraken_dev",
DATABASE_URL: "postgresql://tester:secret@localhost:5432/nexus_dev",
});
const target = assertNexusDbTarget("db:seed:holidays");
assert.equal(target.databaseName, "capakraken_dev");
assert.equal(target.databaseName, "nexus_dev");
});
test("assertNexusDbTarget rejects legacy non-capakraken targets", () => {
test("assertNexusDbTarget rejects legacy non-nexus targets", () => {
setEnv({
DATABASE_URL: "postgresql://tester:secret@localhost:5432/legacy_non_capakraken",
DATABASE_URL: "postgresql://tester:secret@localhost:5432/legacy_non_nexus",
});
assert.throws(() => assertNexusDbTarget("db:seed:holidays"), /not a valid Nexus target/u);
+1 -1
View File
@@ -6,7 +6,7 @@ interface DestructiveGuardOptions {
requireConfirmation?: boolean;
}
const PROTECTED_DATABASE_NAMES = new Set(["capakraken"]);
const PROTECTED_DATABASE_NAMES = new Set(["nexus"]);
export function parseDatabaseUrl(rawUrl: string) {
const parsed = new URL(rawUrl);
+1 -1
View File
@@ -157,7 +157,7 @@ async function main() {
const options = parseArgs(process.argv.slice(2));
const target = assertDestructiveDbAllowed({
commandName: "db:reset:dispo",
allowedDatabaseNames: ["capakraken_test", "capakraken_e2e", "capakraken_ci"],
allowedDatabaseNames: ["nexus_test", "nexus_e2e", "nexus_ci"],
});
const databaseUrl = process.env.DATABASE_URL;
+2 -2
View File
@@ -4,7 +4,7 @@ import {
parseDatabaseUrl,
} from "./destructive-db-guard.js";
const TEST_DATABASE_NAMES = ["capakraken_test", "capakraken_e2e", "capakraken_ci"];
const TEST_DATABASE_NAMES = ["nexus_test", "nexus_e2e", "nexus_ci"];
export function assertSafeSeedTarget(commandName: string) {
return assertDestructiveDbAllowed({
@@ -24,7 +24,7 @@ export function assertNexusDbTarget(commandName: string) {
const target = parseDatabaseUrl(rawUrl);
if (!target.databaseName.startsWith("capakraken")) {
if (!target.databaseName.startsWith("nexus")) {
throw new Error(
`${commandName} aborted: database '${target.databaseName}' is not a valid Nexus target. Target=${formatTarget(target)}`,
);
+1 -1
View File
@@ -2372,7 +2372,7 @@ async function main() {
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join(" ");
const email = `${eid}@capakraken.example`;
const email = `${eid}@nexus.example`;
const lcrCents = Math.round(lcr * 100);
const ucrCents = Math.round(ucr * 100);
const availability = computeAvailability(fraction, availDays);
+2 -2
View File
@@ -23,7 +23,7 @@ function toDisplayName(eid) {
}
function toEmail(eid) {
return `${eid}@capakraken.example`;
return `${eid}@nexus.example`;
}
function computeSkillLabel(chapter, typeOfWork) {
@@ -150,7 +150,7 @@ async function main() {
{
col: 15, // O
header: "Email\n(generated)",
doc: "Generated email: firstname.lastname@capakraken.example. Required unique field in Nexus. Replace with real email in production.",
doc: "Generated email: firstname.lastname@nexus.example. Required unique field in Nexus. Replace with real email in production.",
},
{
col: 16, // P