Subject

Data subject (user) lifecycle + DSR (Data Subject Rights) helpers — register / lookup / export / erase. Tied to GDPR (Articles 15-22) and CCPA workflows; also covers AU Privacy Act review (right to erasure) and HIPAA §164.524 access requests.

App schema declares per-table subjectField (the column that points to the subject) and personalDataCategories (semantic tag for the Record of Processing Activities). This module then walks every table that knows about a given subject without the app having to plumb subject IDs through repository code.

Erasure model: physical row deletion with the audit chain preserved (the subject's data rows are gone; the audit_log entries about them remain hash-linked). b.subject.erase satisfies GDPR Art. 17 in the strict sense (subject data is erased). b.subject.eraseHard layers cryptographic erasure on top — destroys per-row K_row keys for tables that opted into per-row keying, leaving any residual ciphertext in WAL / replica / backup storage undecryptable even if the operator's vault key is later recovered.

Every mutating call routes through cluster.requireLeader and writes a structured audit event (subject.export / subject.rectify / subject.erase / subject.erase_hard / subject.restrict / subject.objection). Erasure is additionally gated by the central legal-hold registry (FRCP Rule 26/37(e), GDPR Art 17(3)(e), SEC Rule 17a-4, HIPAA §164.530(j)(2)) — a stale operator attestation cannot override an active hold.

b.subject.export(subjectId, opts?) #

stable0.1.0gdprccpahipaa
{
  include: "all" | string[],   // table allowlist; "all" exports every subjectField-tagged table
  reason:  string,             // ticket reference recorded in the audit event
}

GDPR Art. 15 (right of access) + Art. 20 (data portability) + HIPAA §164.524 access request. Walks every table whose schema declared a subjectField pointing at the subject identifier and returns { tableName: [unsealedRows] }. Sealed columns are unsealed in-memory for the export; derived-hash columns are used for predicate lookup so plaintext subject IDs never need to land in a query string. Writes a subject.export audit event listing the tables touched.

var dump = b.subject.export("user-4471", {
  include: "all",
  reason:  "GDPR Art. 15 access request 2026-05-08 ticket #4471",
});
Object.keys(dump);
// → ["users", "orders", "audit_log"]

var ordersOnly = b.subject.export("user-4471", {
  include: ["orders"],
  reason:  "GDPR Art. 20 portability subset",
});

b.subject.exportData(subjectId, opts?) #

stable0.1.0gdprccpahipaa
{
  include: "all" | string[],   // table allowlist
  reason:  string,             // ticket reference recorded in the audit event
}

Identical behaviour to b.subject.export. Shipped as a non-reserved alias because some downstream toolchains (older bundlers, TypeScript import { export } parsing, JSON-serialised method lists) trip on the export keyword. New code should prefer b.subject.export; exportData is kept for tool-friendliness.

var dump = b.subject.exportData("user-4471", {
  include: "all",
  reason:  "GDPR Art. 15 access request",
});
Array.isArray(dump.users || []);
// → true

b.subject.rectify(subjectId, opts) #

stable0.1.0gdprccpahipaa
{
  table:   string,         // table name (must declare subjectField in schema)
  id:      string,         // _id of the row to update
  changes: object,         // { fieldName: newValue, ... }
  reason:  string,         // ticket reference recorded in the audit event
}

GDPR Art. 16 (right to rectification). Updates a single row in a single table on behalf of the subject and emits an audit event carrying the before/after values for the changed fields. Leader-only in cluster mode (cluster.requireLeader). Throws when the row cannot be located or opts is missing required keys.

var ok = b.subject.rectify("user-4471", {
  table:   "users",
  id:      "row-9912",
  changes: { email: "new@example.com", displayName: "Jane Roe" },
  reason:  "GDPR Art. 16 rectification ticket #5512",
});
ok;
// → true

b.subject.erase(subjectId, opts) #

stable0.1.0gdprccpahipaa
{
  reason:           string,    // ticket reference recorded in the audit event
  acknowledgements: string[],  // must include every entry in REQUIRED_ERASE_ACKS
  legalHold:        object,    // optional override for testing; defaults to the framework registry
}

GDPR Art. 17 (right to be forgotten). Physical row deletion across every subjectField-tagged table; the audit chain remains intact (entries about the subject stay hash-linked even after the subject's data rows are gone). Leader-only.

Two gates layer in front of the deletion: every operator-supplied acknowledgement in REQUIRED_ERASE_ACKS must be present (no-litigation-hold, no-statutory-retention-required), AND the central legal-hold registry must report no active hold for the subject. The registry is authoritative — a stale attestation cannot override an active hold (FRCP Rule 26/37(e), GDPR Art 17(3)(e), SEC Rule 17a-4, HIPAA §164.530(j)(2)).

Security: any actor recorded here is an audit-record field, NOT authentication. This primitive gates the deletion on acknowledgements and the legal-hold registry, not on caller identity — the caller MUST be authenticated and authorized by your route before invoking.

Returns { rowsDeleted, perTable }. Use b.subject.eraseHard when residual ciphertext in WAL / replicas / backups must also be made undecryptable.

var result = b.subject.erase("user-4471", {
  reason:           "GDPR Art. 17 request 2026-05-08 ticket #4471",
  acknowledgements: [
    "no-litigation-hold",
    "no-statutory-retention-required",
  ],
});
result.rowsDeleted;
// → 12
Object.keys(result.perTable);
// → ["users", "orders", "preferences"]

b.subject.eraseHard(subjectId, opts) #

stable0.8.44gdprccpahipaa
{
  reason:           string,    // ticket reference recorded in the audit event
  acknowledgements: string[],  // must include every entry in REQUIRED_ERASE_ACKS
  legalHold:        object,    // optional override for testing
}

Cryptographic erasure on top of b.subject.erase. For tables that opted into per-row keying via b.cryptoField.declarePerRowKey, the call destroys each row's K_row entry from _blamejs_per_row_keys before the row DELETE, then runs REINDEX on the table so B-tree pages holding the deleted index entries are rebuilt. Residual ciphertext in WAL / replicas / backup archives stays undecryptable even if the operator's vault key is later recovered — the strongest Art. 17 erasure shape the framework offers.

Same legal-hold + acknowledgement gates as b.subject.erase. Security: the actor is an audit-record field, not authentication — authorize the caller upstream. Leader-only. Returns { rowsDeleted, perRowKeysDestroyed, perTable }.

var result = b.subject.eraseHard("user-4471", {
  reason:           "GDPR Art. 17 cryptographic erasure ticket #4471",
  acknowledgements: [
    "no-litigation-hold",
    "no-statutory-retention-required",
  ],
});
result.rowsDeleted;
// → 12
result.perRowKeysDestroyed;
// → 8

b.subject.restrict(subjectId, opts) #

stable0.1.0gdpr
{
  on:     boolean,   // true to apply restriction, false to lift
  reason: string,    // ticket reference recorded in the audit event
}

GDPR Art. 18 (right to restriction of processing). Toggles a flag in _blamejs_subject_restrictions keyed by the subject-id hash; downstream code consults b.subject.isRestricted before processing. Leader-only. The subject ID is hashed before storage so the table carries no plaintext subject identifiers.

b.subject.restrict("user-4471", {
  on:     true,
  reason: "GDPR Art. 18 contested-accuracy hold ticket #6612",
});
b.subject.isRestricted("user-4471");
// → true

b.subject.restrict("user-4471", { on: false, reason: "dispute resolved" });
b.subject.isRestricted("user-4471");
// → false

b.subject.isRestricted(subjectId) #

stable0.1.0gdpr

Cheap read-side check — returns true when the subject currently has an active GDPR Art. 18 restriction. Safe to call on any node (no leader gate); reads from _blamejs_subject_restrictions via the indexed subject-id hash.

if (b.subject.isRestricted("user-4471")) {
  throw new Error("processing paused under GDPR Art. 18");
}
b.subject.isRestricted("user-9999");
// → false

b.subject.recordObjection(subjectId, opts) #

stable0.1.0gdpr
{
  purpose: string,   // e.g. "marketing", "profiling", "automated-decisioning"
  reason:  string,   // optional free-form ticket reference
}

GDPR Art. 21 (right to object). Records a structured audit event (subject.objection) naming the processing purpose the subject is objecting to plus an optional free-form reason. The framework does not enforce the objection automatically — operators wire the downstream consequence (suppress marketing send, exclude from profiling, etc.) into their own pipeline. Leader-only.

b.subject.recordObjection("user-4471", {
  purpose: "marketing",
  reason:  "GDPR Art. 21 opt-out ticket #7780",
});
// → true

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