fix(usage): isolate the Postgres fixture, and stop two more wrong signals
Three findings, one of them mine and CI-affecting. The Postgres suite gets its own schema. CI hands every gated suite the same PICPEAK_PG_TEST_URL and runs jest with parallel workers, and both picpeakRestorePg and externalRelpathFoldPg drop and recreate `events` and `app_settings` in it — so the suite I added would have destroyed their fixtures and vice versa, intermittently. It now creates and drops its own `usage_pg_test` schema and reaches the tables through searchPath, which works because the service queries unqualified names. Verified on a clean database: after the run `public` still holds zero tables. My first attempt at this silently did not apply — the replacement anchor had been reformatted by eslint and I printed success without asserting the match, which is why the first "isolated" claim was wrong. Webhook-only installs are no longer counted as SMTP users. With EMAIL_WEBHOOK_URL and EMAIL_WEBHOOK_SECRET set, adminEmail sends /email/test through the webhook transport and never touches SMTP (#1225 added that path), but the rule recorded the permanent `smtp` marker anyway. Gated on the transport that is actually configured. Activation is written atomically with its acknowledgement. Split across two updates, a failure or a stop between them left the row activation_pending with pending_packet already cleared — registered with the collector, and permanently stuck locally, because tick() has nothing to retry from there. The register case now sets status in the same write and is guarded precisely on activation_pending rather than merely "not withdrawing". Refs #1110
This commit is contained in:
@@ -474,17 +474,26 @@ class UsageService {
|
||||
update.last_packet = JSON.stringify(envelope);
|
||||
update.last_report_date = packet.payload.report_date;
|
||||
}
|
||||
await this.db('product_usage_state')
|
||||
.where({ id: 1 })
|
||||
.whereNot({ status: 'deletion_pending' })
|
||||
.update(update);
|
||||
// Activation goes in with the acknowledgement, not after it. Split
|
||||
// across two writes, a failure or a stop between them left the row
|
||||
// `activation_pending` with pending_packet already cleared — and
|
||||
// tick() has nothing to retry from there, so the installation was
|
||||
// registered with the collector but permanently stuck locally.
|
||||
// Still guarded on activation_pending, so a withdrawal that arrived
|
||||
// first is not overwritten.
|
||||
const ack = this.db('product_usage_state').where({ id: 1 });
|
||||
if (packet.action === 'register') {
|
||||
update.status = 'active';
|
||||
// Precisely activation_pending, not merely "not withdrawing" —
|
||||
// this write is the one that turns participation on.
|
||||
ack.where({ status: 'activation_pending' });
|
||||
} else {
|
||||
ack.whereNot({ status: 'deletion_pending' });
|
||||
}
|
||||
await ack.update(update);
|
||||
await this.db('product_usage_state')
|
||||
.where({ id: 1, status: 'deletion_pending' })
|
||||
.update({ sequence: packet.sequence, pending_packet: null });
|
||||
if (packet.action === 'register')
|
||||
await this.db('product_usage_state')
|
||||
.where({ id: 1, status: 'activation_pending' })
|
||||
.update({ status: 'active' });
|
||||
}
|
||||
return receipt;
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user