DDL Change Control

Formal DDL approval / change-control workflow. SOX 404 ICFR and PCI DSS Req 6.5 / 10.7 require a documented change-control process for any schema change touching financial reporting or cardholder- data systems. The framework's existing audit emission on DDL only logs that a change happened; this primitive enforces a multi- approver, time-windowed, hash-anchored flow BEFORE the change applies.

Lifecycle: propose(sql, opts) captures the SQL under a SHA3-512 hash and optional signed payload; approve(changeId, approver) adds an approver signature (rejecting self-approval under SOX/PCI postures); reject(changeId, reviewer, reason) terminates; applyApproved(changeId, runner) executes the SQL via the operator-supplied runner ONLY when the change has the minimum approver count, the window is open, and the stored SQL still hashes to its captured digest (defense against in-memory tampering between propose and apply).

Window grammar accepts "always" (24/7), "Mon-Fri 09:00-17:00 UTC", or "Mon,Wed,Fri 14:00-18:00 UTC". Postures sox-404 / sox / pci-dss enforce minimum 2 approvers and disable self- approval. Audit emissions live in the ddl.* namespace: ddl.change.proposed / .approved / .rejected / .applied / .apply_refused (the last carrying the refusal reason — insufficient-approvals / window-closed / sql-tampered / self- approval-denied). State is in-process by default; operators pass a durable opts.store ({ get, put, list }) for cluster-wide visibility.

b.ddlChangeControl.create(opts) #

stable0.8.48sox-404pci-dss
{
  audit:        Object,    // b.audit instance (safeEmit-shaped)
  approvers:    number,    // minimum approvals before applyApproved (default 2; ≥2 under SOX/PCI)
  windowSpec:   string,    // "always" | "Mon-Fri 09:00-17:00 UTC" | "Mon,Wed 14:00-18:00 UTC"
  posture:      string,    // "sox-404" | "sox" | "pci-dss" (forces approvers≥2 + no self-approval)
  signWith:     Function,  // (bytes) → signature; signs propose+approve payloads
  verifyWith:   Function,  // (bytes, sig) → boolean; reserved for store-backed restoration
  store:        Object,    // { get(id), put(id, change), list() }; default in-memory Map
  now:          Function,  // () → ms; testing override
  selfApproval: boolean,   // allow proposer to approve own change (forced false under listed postures)
}

Build a DDL change-control workflow. Returns { propose, approve, reject, applyApproved, list, get, posture, approvers, windowSpec }. propose returns { changeId, sqlHash }; approve returns { changeId, signaturesCount, thresholdMet }; applyApproved runs the SQL through the operator-supplied runner and returns { changeId, result, durationMs }.

var ddl = b.ddlChangeControl.create({
  audit:      auditInstance,
  approvers:  2,
  windowSpec: "Mon-Fri 09:00-17:00 UTC",
  posture:    "sox-404",
});

var p = await ddl.propose("ALTER TABLE accounts ADD COLUMN region TEXT", {
  proposer: "alice",
  reason:   "data-residency expansion",
  ticket:   "JIRA-123",
});
p.changeId;     // → "<32-hex token>"
p.sqlHash;      // → ""

await ddl.approve(p.changeId, "bob");
var a2 = await ddl.approve(p.changeId, "carol");
a2.thresholdMet;   // → true

var applied = await ddl.applyApproved(p.changeId, async function (sql) {
  return { rowsAffected: 0 };
});
applied.result.rowsAffected;   // → 0

Last updated 2026-08-08T16:39:49.652Z by seeder.