From f8d00fed36975900f437ff46bc99d31b62f6fe16 Mon Sep 17 00:00:00 2001 From: Tony Chang Date: Mon, 12 Oct 2026 00:32:48 +0000 Subject: [PATCH] fix(postgres): always-on is the database default; limits no longer echoes a queued resize as applied Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- src/commands/postgres.ts | 25 ++++++++++++++++++------- src/index.ts | 4 ++-- test/limits.test.ts | 14 +++++++++++++- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/commands/postgres.ts b/src/commands/postgres.ts index d6d42d4..6f3f3a9 100644 --- a/src/commands/postgres.ts +++ b/src/commands/postgres.ts @@ -17,9 +17,9 @@ export function alwaysOnLine(group: string, scaleToZero: unknown): string { return `postgres ${group}: always-on ${scaleToZero === false ? 'on — instance stays warm' : scaleToZero === true ? 'off — scales to zero when idle' : 'unknown — the provider did not report it'}` } -// Show or set a postgres service's idle mode: scale-to-zero (the default: instance suspends when -// idle, cold-starts on the next connection) or always-on (instance stays warm; idle RAM bills at -// actual usage). Reads GET /database/instance, sets PATCH /database/settings {scaleToZero} — +// Show or set a postgres service's idle mode: always-on (the default for new databases: instance +// stays warm; idle RAM bills at actual usage) or scale-to-zero (instance suspends when idle, +// cold-starts on the next connection). Reads GET /database/instance, sets PATCH /database/settings {scaleToZero} — // insta-db-backed postgres only. export async function dbAlwaysOn(first: string | undefined, second: string | undefined, opts: Opts): Promise { const { mode, service } = alwaysOnArgs(first, second) @@ -70,7 +70,7 @@ export async function settleScaleToZero( export function alwaysOnSetLine(service: string | undefined, branch: string | undefined, mode: 'on' | 'off', scaleToZero: unknown): string { const group = service ?? 'default' if (scaleToZero !== (mode === 'off')) return `postgres ${group}: always-on ${mode} requested — the database has not applied it yet; confirm with \`insta postgres always-on${service ? ` ${service}` : ''}${branch ? ` --branch ${branch}` : ''}\`` - return `postgres ${group}: always-on ${mode === 'on' ? 'ENABLED — instance stays warm (no cold starts; idle RAM bills at actual usage)' : 'disabled — scales to zero when idle (default; first connection after idle cold-starts)'}` + return `postgres ${group}: always-on ${mode === 'on' ? 'ENABLED — instance stays warm (default; no cold starts; idle RAM bills at actual usage)' : 'disabled — scales to zero when idle (first connection after idle cold-starts)'}` } // Validated pass-throughs for the provider's quantity strings. The insta-db resize API takes @@ -162,9 +162,20 @@ export async function dbLimits(service: string | undefined, opts: Opts & { cpu?: } if (handleApproval(res, opts.json)) return if (opts.json) return printJson(res.body) - const cpu = typeof res.body?.cpuMilli === 'number' ? `${res.body.cpuMilli / 1000} vCPU` : (opts.cpu ?? 'unchanged') - const mem = typeof res.body?.memoryMib === 'number' ? fmtMib(res.body.memoryMib) : (opts.memory ?? 'unchanged') - info(`postgres ${service ?? 'default'}: ceiling set to ${cpu} / ${mem}`) + info(limitsSetLine(service, opts.branch, opts, res.status, res.body)) +} + +// A queued resize (202, or `pending` from the adapter) carries the instance row from BEFORE the +// patch, so its numbers are the old ceiling: name what was requested and point at the read-back. +export function limitsSetLine(service: string | undefined, branch: string | undefined, req: { cpu?: string; memory?: string }, status: number, body: any): string { + const group = service ?? 'default' + if (status === 202 || body?.pending === true) { + const asked = [req.cpu ? `${req.cpu} vCPU` : null, req.memory ?? null].filter(Boolean).join(' / ') + return `postgres ${group}: ceiling ${asked} requested — the database has not applied it yet; confirm with \`insta postgres limits${service ? ` ${service}` : ''}${branch ? ` --branch ${branch}` : ''}\`` + } + const cpu = typeof body?.cpuMilli === 'number' ? `${body.cpuMilli / 1000} vCPU` : (req.cpu ?? 'unchanged') + const mem = typeof body?.memoryMib === 'number' ? fmtMib(body.memoryMib) : (req.memory ?? 'unchanged') + return `postgres ${group}: ceiling set to ${cpu} / ${mem}` } // Bytes → human units, one decimal above KiB. Local because the metrics payload is the only diff --git a/src/index.ts b/src/index.ts index 50da0e6..4192ebd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -471,7 +471,7 @@ pg.command('volume [service]').description("Show or grow a postgres service's pr .option('--size ', 'new size in whole Gi, e.g. 10 (must be ≥ the current size)') .option('--json').option('--branch ', 'branch (default: current)') .action(guard((service, o) => pgCmd.dbVolume(service, o))) -pg.command('always-on [mode] [service]').description('Show or set a postgres service always-on (mode: on|off; no mode = print the current setting). on = instance stays warm, no cold starts; off = default scale-to-zero (idle instance suspends; first connection cold-starts). insta-db-backed services only') +pg.command('always-on [mode] [service]').description('Show or set a postgres service always-on (mode: on|off; no mode = print the current setting). on = instance stays warm, no cold starts (the default for new postgres services); off = scale-to-zero (idle instance suspends; first connection cold-starts). insta-db-backed services only') .option('--json').option('--branch ', 'branch (default: current)') .action(guard((mode, service, o) => pgCmd.dbAlwaysOn(mode, service, o))) pg.command('private-access [mode] [service]').description('Show or set private network access (mode: on|off; no mode = print public and private access). on = mint DATABASE_PRIVATE_URL beside DATABASE_URL (unchanged) — it resolves only inside InstaCloud compute, not from a laptop or CI; bind it into a compute with `insta secrets bind … --source-name DATABASE_PRIVATE_URL`. off = retract it (refused while public access is closed). insta-db-backed only (gated: service.setAccess)') @@ -503,7 +503,7 @@ for (const type of ['redis', 'mysql', 'mongodb'] as const) { .option('--size ', 'new size in whole Gi, e.g. 10 (must be ≥ the current size)') .option('--json').option('--branch ', 'branch (default: current)') .action(guard((service, o) => computeCmd.serviceVolume(type, service, o))) - g.command('always-on [service]').description(`Set a ${type} service always-on (mode: on|off). on = machines never scale to zero; off = scale-to-zero. Billing is actual usage either way`) + g.command('always-on [service]').description(`Set a ${type} service always-on (mode: on|off). on = machines never scale to zero (the default for new ${type} services); off = scale-to-zero. Billing is actual usage either way`) .option('--json').option('--branch ', 'branch (default: current)') .action(guard((mode, service, o) => computeCmd.serviceAlwaysOn(type, mode, service, o))) addObservability(g, type, type) diff --git a/test/limits.test.ts b/test/limits.test.ts index 4d82396..d48712f 100644 --- a/test/limits.test.ts +++ b/test/limits.test.ts @@ -41,7 +41,7 @@ describe('parseMemoryMb', () => { import { parseCpu, fmtMb } from '../src/commands/compute.js' import { fetchDbInstance } from '../src/commands/postgres.js' import { ApiError } from '../src/api.js' -import { parseDbCpu, parseDbMemory, fmtMib, alwaysOnArgs, alwaysOnLine, alwaysOnSetLine, settleScaleToZero, restartLine } from '../src/commands/postgres.js' +import { parseDbCpu, parseDbMemory, fmtMib, alwaysOnArgs, alwaysOnLine, alwaysOnSetLine, limitsSetLine, settleScaleToZero, restartLine } from '../src/commands/postgres.js' describe('parseCpu (compute --cpu override)', () => { it('accepts exactly the provider grid the help text advertises', () => { @@ -144,6 +144,18 @@ describe('postgres always-on arguments', () => { }) }) +describe('postgres limits set confirmation', () => { + it('reports the applied ceiling from a synchronous answer', () => { + expect(limitsSetLine('db', undefined, { memory: '2Gi' }, 200, { cpuMilli: 2000, memoryMib: 2048 })).toBe('postgres db: ceiling set to 2 vCPU / 2 GiB') + }) + it('does not echo the pre-patch row of a queued resize as the new ceiling', () => { + expect(limitsSetLine('db', 'feat', { memory: '2Gi' }, 202, { cpuMilli: 4000, memoryMib: 4096, pending: true })) + .toBe('postgres db: ceiling 2Gi requested — the database has not applied it yet; confirm with `insta postgres limits db --branch feat`') + expect(limitsSetLine(undefined, undefined, { cpu: '2', memory: '2Gi' }, 200, { cpuMilli: 4000, memoryMib: 4096, pending: true })) + .toMatch(/ceiling 2 vCPU \/ 2Gi requested .*`insta postgres limits`$/) + }) +}) + describe('postgres always-on set confirmation', () => { it('confirms only when the reported value matches the request, and otherwise points at the read-back for that branch', () => { expect(alwaysOnSetLine('db', undefined, 'on', false)).toMatch(/always-on ENABLED/)