refactor(persistence): migrate fulfillment-store to canonical atomic-write util (DC-103) [glm-grade=A]
writeState() drops its private tmp+writeFileSync+rename copy (no fsync, Date.now() tmp names, best-effort chmod) and delegates to src/utils/atomic-write.js atomicWriteJSON (exclusive-create tmp, fsync, rename, parent-dir fsync, 0600). A torn stripe-fulfillments.json could previously make a webhook retry mint a SECOND valid license key for an order that already has one. Both consumers (routes/billing.js, scripts/stripe-license-bridge.js) JSON.parse only - trailing-newline drop in the canonical serializer is harmless. Unused crypto require removed. +2 DC-103 pins: full lifecycle 0600/complete/zero-leftovers, and dual-instance interleaved writes (bridge+API file-IPC) with no tmp collisions. 121 suites / 2776 tests green. Judge: GLM-5.3 round-1 A (deleg_bd49a97b), URN urn:ump:layk7h326sqymcsh6tiusrs2sdbqdcx6ygvcuwwoxqn4rf7ycoua
This commit is contained in:
@@ -10,8 +10,8 @@
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const platformPaths = require('../../platform-paths');
|
||||
const { atomicWriteJSON } = require('../utils/atomic-write');
|
||||
|
||||
const DELIVERY_LEASE_MS = 5 * 60 * 1000;
|
||||
|
||||
@@ -44,15 +44,11 @@ function createFulfillmentStore(options = {}) {
|
||||
function writeState(state) {
|
||||
const dir = path.dirname(filePath);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
const tmp = `${filePath}.tmp.${process.pid}.${Date.now()}.${crypto.randomBytes(4).toString('hex')}`;
|
||||
fs.writeFileSync(tmp, JSON.stringify(state, null, 2) + '\n', { mode: 0o600 });
|
||||
try {
|
||||
fs.renameSync(tmp, filePath);
|
||||
} catch (error) {
|
||||
try { fs.unlinkSync(tmp); } catch (_) { /* best effort */ }
|
||||
throw error;
|
||||
}
|
||||
try { fs.chmodSync(filePath, 0o600); } catch (_) { /* best effort */ }
|
||||
// Canonical atomic-write (DC-099): fsync-before-rename + exclusive-create
|
||||
// tmp + dir fsync. A crash mid-write can no longer leave a torn
|
||||
// stripe-fulfillments.json — which would have forced the bridge to
|
||||
// re-mint a duplicate license key on the next webhook retry.
|
||||
atomicWriteJSON(filePath, state);
|
||||
}
|
||||
|
||||
function mutate(mutator) {
|
||||
|
||||
Reference in New Issue
Block a user