@neetru/sdk 3.2.1 → 3.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,25 @@ All notable changes to `@neetru/sdk` will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.2.2] - 2026-08-05 — **fix(db-sql): renovação de lease cai pra emissão nova quando renew falha (death spiral do cert mTLS)**
9
+
10
+ Release **patch** (bug crítico de conexão em produção — mesma classe do 3.2.1, um nível mais fundo).
11
+
12
+ ### Contexto
13
+
14
+ Continuação direta do bug do 3.2.1. Confirmado em campo (pdv-agiliza, 2026-08-05, SSH na `snee-002-multi`): mesmo com o hot-swap do 3.2.1 correto, o `SqlLeaseManager._doRenew()` só chamava **renew** (com `leaseId`). O `renew` do Core (`renewLeaseCore`) só funciona com um lease AINDA vivo; se o lease expirou/foi revogado/foi GC'd do Firestore, o renew falha pra sempre — e o `_doRenew` antigo só re-tentava renew a cada 30s, sem NUNCA emitir um lease NOVO. Resultado (death spiral): o cert mTLS de cliente (efêmero, ~15min, mintado por lease em `issuer-core.ts`) expira, TODA leitura do banco do produto morre com `certificate expired` (TLS alert 45, `ERR_SSL_SSL/TLS_ALERT_CERTIFICATE_EXPIRED`), e o processo NUNCA se recupera até restart manual. Observado determinístico por 3h+ (`/api/db/doc` e tenant-resolve do pdv, 500 em toda leitura; schema/relógio/PgBouncer/Postgres todos confirmados sãos — era só o cert).
15
+
16
+ ### Corrigido
17
+
18
+ - `_doRenew()`: quando o `renew` (com `leaseId`) falha, **cai pra emissão de um lease NOVO** (`fetchLease()` sem `leaseId`) e faz o swap do pool com o certificado fresco. Quebra o death spiral sem depender de restart do processo. Só desiste (reagenda em 30s) se AMBOS falharem (Core inalcançável).
19
+ - Falha de renovação deixou de ser silenciosa em produção: `console.warn` sempre (não só em dev) — renovação que falha é degradação real de disponibilidade e precisa aparecer no log do produto (`app.log`).
20
+ - 3 testes novos: fallback pra lease novo no renew-fail + swap do pool; reagenda sem lançar quando ambos falham; renovação seguinte usa o novo `leaseId`.
21
+ - **Fix de drift:** `VERSION` em `src/index.ts` estava travado em `3.2.0` (o release 3.2.1 bumpou só o `package.json`) — o guard `SDK-QW-5` estava vermelho desde 2026-07-27 (CI off, ninguém pegou). Agora sincronizado em `3.2.2`.
22
+
23
+ ### Follow-up (não nesta release)
24
+
25
+ - **Re-lease REATIVO** (P1.5 da auditoria de estabilização 2026-08-05, `docs/_review/DIAGNOSTICO_ESTABILIZACAO_SAAS_2026-08-05.md`): ao pegar `certificate expired`/erro de conexão numa query, invalidar o pool + emitir lease novo + retry 1x, pra um processo em execução se auto-curar mesmo se o timer de renovação não tiver disparado. Exige wrapper resiliente sobre o `pg.Pool` (cuidado com transações Drizzle) — trabalho separado, testado à parte.
26
+
8
27
  ## [3.2.1] - 2026-07-27 — **fix(db-sql): hot-swap do pool quando host/port/user/senha mudam (bug_pdv_agiliza)**
9
28
 
10
29
  Release **patch** (bug crítico de conexão em produção — bloqueava login/queries em qualquer produto que renovasse lease sem reiniciar o processo).
package/dist/auth.cjs CHANGED
@@ -278,6 +278,9 @@ var init_db_errors = __esm({
278
278
  });
279
279
 
280
280
  // src/db/sql/lease.ts
281
+ function hasConnectionChanged(oldLease, newLease) {
282
+ return oldLease.leaseId !== newLease.leaseId || oldLease.credentialVersion !== newLease.credentialVersion || oldLease.host !== newLease.host || oldLease.port !== newLease.port || oldLease.dbName !== newLease.dbName || oldLease.user !== newLease.user || oldLease.password !== newLease.password || oldLease.sslca !== newLease.sslca || oldLease.clientCert !== newLease.clientCert || oldLease.clientKey !== newLease.clientKey;
283
+ }
281
284
  function mapPoolError(err) {
282
285
  if (err instanceof NeetruDbError) return err;
283
286
  const message = err instanceof Error ? err.message : String(err);
@@ -382,21 +385,25 @@ var init_lease = __esm({
382
385
  async _doRenew() {
383
386
  if (this._closed) return;
384
387
  try {
385
- const renewOpts = {
386
- leaseId: this._lease.leaseId
388
+ const withDb = (o) => {
389
+ if (this._deps.database !== void 0) o.database = this._deps.database;
390
+ return o;
387
391
  };
388
- if (this._deps.database !== void 0) {
389
- renewOpts.database = this._deps.database;
392
+ let newLease;
393
+ try {
394
+ newLease = await this._deps.fetchLease(withDb({ leaseId: this._lease.leaseId }));
395
+ } catch (renewErr) {
396
+ newLease = await this._deps.fetchLease(withDb({}));
390
397
  }
391
- const newLease = await this._deps.fetchLease(renewOpts);
392
398
  await this._swapPool(newLease);
393
399
  } catch (err) {
394
400
  this._renewalTimer = setTimeout(() => {
395
401
  void this._doRenew();
396
402
  }, MIN_RENEWAL_DELAY_MS);
397
- if (process.env["NODE_ENV"] !== "production") {
398
- console.warn("[SqlLeaseManager] Lease renewal failed, will retry:", err);
399
- }
403
+ console.warn(
404
+ `[SqlLeaseManager] renova\xE7\xE3o E emiss\xE3o de lease novo falharam; re-tentando em ${MIN_RENEWAL_DELAY_MS}ms (cert mTLS expira ~15min ap\xF3s o \xFAltimo swap):`,
405
+ err instanceof Error ? err.message : err
406
+ );
400
407
  return;
401
408
  }
402
409
  this._scheduleRenewal();
@@ -407,8 +414,7 @@ var init_lease = __esm({
407
414
  * Exposto como método protegido para testes.
408
415
  */
409
416
  async _swapPool(newLease) {
410
- const credRotated = newLease.credentialVersion !== this._lease.credentialVersion;
411
- if (!credRotated && newLease.leaseId === this._lease.leaseId) {
417
+ if (!hasConnectionChanged(this._lease, newLease)) {
412
418
  this._lease = newLease;
413
419
  return;
414
420
  }