From 6d5f5c3fb77f27e021ff3965dddd4cba9688d3a1 Mon Sep 17 00:00:00 2001 From: jwfing Date: Sun, 11 Oct 2026 17:34:12 -0700 Subject: [PATCH] fix(postgres): limits says a queued resize is not applied yet; databases default to always-on (IOZI-30) insta-db answers a cpu/memory PATCH with 202 and the instance row from before the patch, which `postgres limits` echoed as the new ceiling. On 202 or pending: true it now prints the requested values as "requested, not applied yet" and points at the read-back, like alwaysOnSetLine. The always-on confirmation, comment and help no longer call scale-to-zero the database default (platform #666). Closes #369 Co-Authored-By: Claude Opus 5.5 --- src/commands/postgres.ts | 31 ++++++++++++++++++++++++------- src/index.ts | 2 +- test/limits.test.ts | 22 +++++++++++++++++++++- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/commands/postgres.ts b/src/commands/postgres.ts index d6d42d4..63fad0b 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 databases since platform +// #666: 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 (no cold starts; idle RAM bills at actual usage; the default for databases)' : '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,26 @@ 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, { cpu: body.cpu as string | undefined, memory: body.memory as string | undefined }, res)) +} + +// insta-db queues the resize and answers 202 (the platform adds pending: true) with the row from +// BEFORE the patch, so on pending the body's cpuMilli/memoryMib are the old ceiling: print what was +// asked for and point at the read-back, the way alwaysOnSetLine does. +export function limitsSetLine( + service: string | undefined, + branch: string | undefined, + requested: { cpu?: string; memory?: string }, + res: { status: number; body: any }, +): string { + const group = service ?? 'default' + if (res.status === 202 || res.body?.pending === true) { + const asked = [requested.cpu && `${requested.cpu} vCPU`, requested.memory].filter(Boolean).join(' / ') + return `postgres ${group}: ceiling ${asked} requested, not applied yet — confirm with \`insta postgres limits${service ? ` ${service}` : ''}${branch ? ` --branch ${branch}` : ''}\`` + } + const cpu = typeof res.body?.cpuMilli === 'number' ? `${res.body.cpuMilli / 1000} vCPU` : (requested.cpu ?? 'unchanged') + const mem = typeof res.body?.memoryMib === 'number' ? fmtMib(res.body.memoryMib) : (requested.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..c17cda1 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 databases); 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)') diff --git a/test/limits.test.ts b/test/limits.test.ts index 4d82396..a9fdc97 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, settleScaleToZero, restartLine, limitsSetLine } from '../src/commands/postgres.js' describe('parseCpu (compute --cpu override)', () => { it('accepts exactly the provider grid the help text advertises', () => { @@ -164,6 +164,26 @@ describe('postgres always-on set confirmation', () => { }) }) +// insta-db answers a resize with 202 and the row from before the patch: echoing its cpuMilli/memoryMib +// reported the OLD ceiling as the new one (instacloud-cli#369). +describe('postgres limits set confirmation', () => { + it('on 202 prints the requested values as not applied yet, never the pre-change row', () => { + const line = limitsSetLine('db', undefined, { memory: '2Gi' }, { status: 202, body: { cpuMilli: 4000, memoryMib: 4096, pending: true } }) + expect(line).toBe('postgres db: ceiling 2Gi requested, not applied yet — confirm with `insta postgres limits db`') + expect(line).not.toContain('4 GiB') + }) + it('treats pending: true as not applied even without a 202, and names the branch in the read-back', () => { + expect(limitsSetLine(undefined, 'feat', { cpu: '2', memory: '4Gi' }, { status: 200, body: { cpuMilli: 1000, memoryMib: 1024, pending: true } })) + .toBe('postgres default: ceiling 2 vCPU / 4Gi requested, not applied yet — confirm with `insta postgres limits --branch feat`') + }) + it('confirms from the reported row when the change was applied', () => { + expect(limitsSetLine('db', undefined, { memory: '2Gi' }, { status: 200, body: { cpuMilli: 2000, memoryMib: 2048 } })) + .toBe('postgres db: ceiling set to 2 vCPU / 2 GiB') + expect(limitsSetLine('db', undefined, { cpu: '2500m' }, { status: 200, body: {} })) + .toBe('postgres db: ceiling set to 2500m / unchanged') + }) +}) + describe('postgres restart confirmation', () => { it('says restarted only when the platform finished it', () => { expect(restartLine('db', { state: 'running' })).toBe('postgres db: restarted — ALTER SYSTEM settings that need a restart are now in effect')