refactor(persistence): migrate invite-store to canonical atomic-write util (DC-100) [glm-grade=A]
CI / Test & Lint (push) Canceled after 0s
CI / Security audit (push) Canceled after 0s

Drops invite-store's private _atomicWriteJSON copy (pid+Date.now() tmp
names, no fsync, no failure cleanup) in favor of src/utils/atomic-write.js:
fsync'd same-dir tmp+rename, exclusive-create 0600, parent-dir fsync,
cleanup-on-failure. Sole format consumer is a JSON.parse reader, so the
dropped trailing newline is unobservable. +2 store-level regression tests
pin 0600 / complete JSON / no temp leftovers under burst mutations.
Judge: GLM-5.3 cold read, round-1 A, deleg_7177d506.
Verdict: urn:ump:4kwenywtf3nx2mokobvuzrhklk2xigmpyea6nvqdyhv7icnflpkq
Full suite: 121 suites / 2770 tests green.
This commit is contained in:
Hermes
2026-08-22 23:54:37 -07:00
parent 80a82c4cae
commit 3742e2658d
2 changed files with 43 additions and 8 deletions
+4 -8
View File
@@ -5,7 +5,8 @@
* the system emails (or logs in dev) a magic-link-style URL containing the
* raw token. The recipient clicks → accepts → becomes an authorized user.
*
* Storage: data/invites.json. Atomic writes via tmp+rename.
* Storage: data/invites.json. Atomic durable writes via the canonical
* shared atomic-write util (DC-099/DC-100) — fsync'd tmp+rename.
*
* Token shape:
* - 32 random bytes, base64url-encoded (256 bits of entropy).
@@ -30,6 +31,7 @@ const path = require('path');
const fs = require('fs');
const crypto = require('crypto');
const platformPaths = require('../../platform-paths');
const { atomicWriteJSON } = require('../utils/atomic-write');
const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
const PRUNE_AFTER_MS = 7 * 24 * 60 * 60 * 1000; // auto-prune used/expired after 7d
@@ -37,12 +39,6 @@ const PRUNE_AFTER_MS = 7 * 24 * 60 * 60 * 1000; // auto-prune used/expired after
function _nowMs() { return Date.now(); }
function _nowIso() { return new Date().toISOString(); }
function _atomicWriteJSON(filePath, data) {
const tmp = filePath + '.tmp.' + process.pid + '.' + Date.now();
fs.writeFileSync(tmp, JSON.stringify(data, null, 2) + '\n', { mode: 0o600 });
fs.renameSync(tmp, filePath);
}
function _readJSON(filePath, fallback) {
try {
const raw = fs.readFileSync(filePath, 'utf8');
@@ -85,7 +81,7 @@ function createInviteStore(opts = {}) {
if (!data.invites || typeof data.invites !== 'object') data.invites = {};
return data;
}
function _save(data) { _atomicWriteJSON(file, data); }
function _save(data) { atomicWriteJSON(file, data); }
function _prune(data) {
const cutoff = _nowMs() - PRUNE_AFTER_MS;