From be175bc6c2fdd28f1429c8d91e6d1636f1a90f6d Mon Sep 17 00:00:00 2001 From: Deep-Axe Date: Mon, 26 Oct 2026 19:17:21 +0530 Subject: [PATCH 1/3] Clarify store address collisions during namespace teardown --- services/gateway/src/store-requests.ts | 12 +++++++ tests/store-requests.test.ts | 50 ++++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/services/gateway/src/store-requests.ts b/services/gateway/src/store-requests.ts index ff5be08..42b43f1 100644 --- a/services/gateway/src/store-requests.ts +++ b/services/gateway/src/store-requests.ts @@ -32,6 +32,18 @@ export async function requestStore( // A short database admission transaction, independent of the long-running worker. // The shared lock makes limits authoritative across gateway replicas. await client.query('SELECT pg_advisory_xact_lock(87264005)'); + const existing = await client.query<{ status: string }>( + 'SELECT status FROM stores WHERE slug=$1', + [input.slug], + ); + if (existing.rows[0]) + throw new StoreRequestError( + 409, + existing.rows[0].status === 'Deleting' + ? 'That store address is still being removed; wait a moment and try again' + : 'That store name is in use', + ); + const { rows } = await client.query<{ total: string; mine: string }>( 'SELECT count(*) AS total,count(*) FILTER (WHERE created_by=$2) AS mine FROM stores WHERE team_id=$1', [user.teamId, user.userId], diff --git a/tests/store-requests.test.ts b/tests/store-requests.test.ts index 71541ec..ba48b4d 100644 --- a/tests/store-requests.test.ts +++ b/tests/store-requests.test.ts @@ -20,14 +20,15 @@ const settings = { maxPerTeam: 3, provisionTimeoutMs: 240000, }; -function fixture(existing: { team: string; owner: string }[]) { +type Record = { team: string; owner: string; slug?: string; status?: string }; +function fixture(existing: Record[]) { const records = [...existing]; let tail = Promise.resolve(); let rollbacks = 0; const pool = { async connect() { let release: (() => void) | undefined; - let pending: { team: string; owner: string } | undefined; + let pending: Record | undefined; return { async query(sql: string, args: unknown[] = []) { if (sql === 'BEGIN') return { rows: [] }; @@ -39,6 +40,12 @@ function fixture(existing: { team: string; owner: string }[]) { await previous; return { rows: [] }; } + if (sql === 'SELECT status FROM stores WHERE slug=$1') + return { + rows: records + .filter((r) => r.slug === args[0]) + .map((r) => ({ status: r.status })), + }; if (sql.includes('count(*) AS total')) { const team = records.filter((r) => r.team === args[0]); return { @@ -53,7 +60,12 @@ function fixture(existing: { team: string; owner: string }[]) { if (sql.includes('SELECT 1 FROM upstream_servers')) return { rows: [], rowCount: 0 }; if (sql.includes('INSERT INTO stores')) { - pending = { team: String(args[1]), owner: String(args[2]) }; + pending = { + team: String(args[1]), + owner: String(args[2]), + slug: String(args[3]), + status: 'Provisioning', + }; return { rows: [], rowCount: 1 }; } if (sql.includes('pg_notify')) return { rows: [] }; @@ -121,3 +133,35 @@ test('store admission enforces per-user limits independently of another team and assert.equal(f.records.length, 3); assert.equal(f.rollbacks(), 1); }); + +test('a deleting address reports termination across teams and can be reused after removal', async () => { + for (const team of ['team', 'another-team']) { + const f = fixture([ + { team, owner: 'other', slug: 'north', status: 'Deleting' }, + ]); + await assert.rejects( + () => + requestStore( + f.pool, + user, + { slug: 'north', displayName: 'North' }, + { ...settings, maxPerTeam: 1 }, + ), + (error) => + error instanceof StoreRequestError && + error.status === 409 && + error.message === + 'That store address is still being removed; wait a moment and try again', + ); + assert.equal(f.records.length, 1); + assert.equal(f.rollbacks(), 1); + f.records.length = 0; + await requestStore( + f.pool, + user, + { slug: 'north', displayName: 'North' }, + { ...settings, maxPerTeam: 1 }, + ); + assert.equal(f.records.length, 1); + } +}); From deb23bf0f21512d0cbf191a86ac967020af41f91 Mon Sep 17 00:00:00 2001 From: Deep-Axe Date: Mon, 26 Oct 2026 19:17:21 +0530 Subject: [PATCH 2/3] Hide connection actions for provisioned store servers --- apps/dashboard/src/main.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/dashboard/src/main.tsx b/apps/dashboard/src/main.tsx index 6af04de..fe3c396 100644 --- a/apps/dashboard/src/main.tsx +++ b/apps/dashboard/src/main.tsx @@ -489,7 +489,10 @@ function Connections({ me, run }: { me: Me; run: Run }) { )} - {me.user.role === 'admin' && ( + {server.id.startsWith('store-') && ( +

Managed in Stores

+ )} + {me.user.role === 'admin' && !server.id.startsWith('store-') && (