Retention
Row-level retention floors per regulatory regime. GDPR Art. 17, HIPAA 45 CFR §164.530(j), PCI-DSS Req. 3.1, SOX §802 and friends all share the shape: "data of class X stored beyond TTL Y must be either deleted or anonymized". b.retention ties the framework's building blocks (b.cryptoField.eraseRow for crypto-erasure of sealed columns, b.scheduler for cadence, b.audit for the chain, b.legalHold for per-subject holds) into one operator-facing primitive that emits delete / erase / soft-delete jobs at expiry.
Action vocabulary per row: "erase" (sealed columns + derived hashes go to NULL, __erasedAt set, row remains for FK / audit reference — cleartext is unrecoverable even with a vault key); "delete" (full row DELETE — for tables with no FK / audit reference); "soft-delete" (writes a deletion timestamp into softDeleteField — typical "trash bin" pattern); "warn" (audit only, no row write — used as an early stage in multi-stage schedules); function(row) (escape hatch for joined / conditional retention). Cascades follow rule.cascade[] foreign-key edges so a parent erase fans out into child rows in the same sweep.
b.compliance.set(posture) cascades into applyPosture here, so the active posture's audit_log minimum-retention floor becomes the default ttlMs for any rule the operator declares without an explicit value. complianceFloor(posture, candidateTtlMs) surfaces those minimums for app-side conditional logic.
Audit events (namespace retention): rule.declared, sweep.started, row.processed (with action), row.warned, row.legal_hold_skipped, sweep.completed, sweep.failed, sweep.skipped_concurrent. Each sweep is single-flighted per rule name so a slow run cannot be re-entered by the next scheduler tick.
b.retention.create(opts) #
{
db: object, // b.db handle, must expose .prepare(sql)
audit: boolean | object, // true | false | a b.audit instance
}
Build a retention controller bound to a database handle. Returns an object with declare(rule), run(name, runOpts?), runAll(runOpts?), preview(name), and list(). Audit emit is on by default; pass audit: false for a quiet controller in tests.
var rules = b.retention.create({ db: b.db, audit: true });
rules.declare({
name: "users.notes-ttl",
table: "users",
ageField: "createdAt",
ttlMs: C.TIME.days(90),
action: "erase",
batchSize: 500,
legalHoldField: "__legalHold",
});
var summary = await rules.run("users.notes-ttl");
// → { name, scanned, processed, action: "erase", durationMs, errors: [] }
b.retention.complianceFloor(posture, candidateTtlMs) #
Take a regulatory posture name and a candidate TTL; return the effective TTL that meets-or-exceeds the regime's minimum-retention floor. Floors come from COMPLIANCE_RETENTION_FLOOR_MS (PCI-DSS §10.7.1: 12 months online; HIPAA 45 CFR §164.316(b)(2)(i): 6 years; SOX §802: 7 years; DORA Art. 17: 5 years; NIS2 Art. 23: 3 years; CRA Art. 14: 5 years; LGPD-BR / APPI-JP / PDPA-SG / UK-GDPR variants matched). Throws on an unknown posture so config-time typos surface.
var ttl = b.retention.complianceFloor("hipaa", b.constants.TIME.days(180));
// → 189216000000 (HIPAA's 6-year floor wins over the 180-day candidate)
var sox = b.retention.complianceFloor("sox", 0);
// → 220752000000 (Sarbanes-Oxley §802 — 7 years)
b.retention.applyPosture(posture) #
Cascade hook called by b.compliance.set(posture). Records the posture name and its audit_log retention floor as module state so subsequent complianceFloor callers without an explicit posture argument inherit the active value. Returns null for an empty input or a posture with no retention floor; otherwise returns { posture, floorMs }.
b.compliance.set("hipaa");
b.retention.applyPosture("hipaa");
// → { posture: "hipaa", floorMs: 189216000000 }
b.retention.activePosture();
// → "hipaa"
b.retention.activePosture() #
Read the posture name set by the most recent applyPosture call, or null if b.compliance.set has never run on this process. Used by audit-dashboard tooling to surface "this deployment is pinned to b.compliance directly.
var p = b.retention.activePosture();
if (p === null) console.log("no compliance posture pinned");
else console.log("active posture:", p);
// → "hipaa"
Last updated 2026-08-08T16:39:49.652Z by seeder.