Compliance — privacy, redaction, retention
How the platform's privacy package classifies data, applies redaction rules, and enforces retention.
Compliance
@fabricorg/platform/privacy defines contracts for data classification, redaction, anonymization, and retention. The platform supplies the contracts; verticals declare the rules in their FabricModule.
Data classes
The platform recognizes a small set of cross-industry data classes:
type PlatformDataClass =
| "asset_event"
| "read_model"
| "raw_payload"
| "evidence_packet"
| "action_evidence"
| "access_event";public enum PlatformDataClass
{
AssetEvent,
ReadModel,
RawPayload,
EvidencePacket,
ActionEvidence,
AccessEvent
}Each class can carry its own retention rule and redaction policy.
Redaction rules
interface RedactionRule {
fieldPath: string; // e.g. "payload.external user.ssn"
method: "nullify" | "mask" | "hash" | "tokenize" | "generalize" | "drop";
reason: string;
dataClass?: PlatformDataClass;
}public sealed class RedactionRule
{
public string FieldPath { get; }
public RedactionMethod Method { get; }
public string Reason { get; }
public PlatformDataClass? DataClass { get; }
}A vertical declares redaction rules in its module:
{
redactionRules: [
{
fieldPath: "payload.external user.ssn",
method: "tokenize",
reason: "Direct identifier",
dataClass: "asset_event",
},
],
}new RedactionRule(
fieldPath: "payload.external user.ssn",
method: RedactionMethod.Tokenize,
reason: "Direct identifier",
dataClass: PlatformDataClass.AssetEvent)The rules are applied by the platform's anonymization pipeline when an event or read model is rendered for a context that requires redaction (analytics export, cross-tenant query, retention-driven anonymization).
Methods:
| Method | Behavior |
|---|---|
nullify | Replace with null. |
mask | Replace with a partially-obscured form (e.g. ***-**-1234). |
hash | One-way hash. Comparable but not reversible. |
tokenize | Replace with a stable surrogate ID. Reversible only via the token vault. |
generalize | Replace with a less-precise form (birthDate → birthYear). |
drop | Remove the field entirely. |
Data classification rules
interface DataClassificationRule {
fieldPath: string;
classification: string; // vertical-defined: "PII", "PHI", "financial", etc.
containsDirectIdentifier?: boolean;
containsSensitiveData?: boolean;
}public sealed class DataClassificationRule
{
public string FieldPath { get; }
public string Classification { get; }
public bool ContainsDirectIdentifier { get; }
public bool ContainsSensitiveData { get; }
}Classification is metadata on a field path. It does not by itself trigger redaction — it informs which redaction rule applies and which retention rule governs the row.
Retention rules
interface RetentionRule {
dataClass: PlatformDataClass;
retainForDays: number;
afterRetention: "delete" | "anonymize" | "archive";
legalHoldExempt?: boolean;
}public sealed class RetentionRule
{
public PlatformDataClass DataClass { get; }
public int RetainForDays { get; }
public string AfterRetention { get; } // "delete" | "anonymize" | "archive"
public bool LegalHoldExempt { get; }
}Each dataClass has a retention policy. After retainForDays, the platform's retention worker takes the configured action.
legalHoldExempt: true means the rule is suspended for any record under legal hold (a vertical concept layered on top — typically a tag on the row that defers retention until the hold is released).
Anonymization metadata
When a row is anonymized, the platform stamps it with AnonymizationMetadata:
interface AnonymizationMetadata {
redactionState: "anonymized";
policyVersion: string;
jobId: string;
redactedAt: string;
reason: string;
dataClass: PlatformDataClass;
redactedFields: AnonymizationFieldMetadata[];
preservedSemantics: string[]; // e.g. "row_existed_at_time_T", "actor_was_agent"
}public sealed class AnonymizationMetadata
{
public string RedactionState { get; } // "anonymized"
public string PolicyVersion { get; }
public string JobId { get; }
public DateTimeOffset RedactedAt { get; }
public string Reason { get; }
public PlatformDataClass DataClass { get; }
public IReadOnlyList<AnonymizationFieldMetadata> RedactedFields { get; }
public IReadOnlyList<string> PreservedSemantics { get; }
}This makes it auditable that a row was redacted, when, by which policy version, and what semantic facts were preserved. The metadata itself is not redacted.
Cross-tenant analytics
The AnalyticsDataContract shape governs what a downstream analytics consumer is allowed to see:
interface AnalyticsDataContractSource {
name: string;
privacyClass:
| "raw"
| "operational_pii"
| "tenant_scoped_tokenized"
| "anonymized"
| "aggregated"
| "differentially_minimized";
containsDirectIdentifiers?: boolean;
containsGloballyStablePersonTokens?: boolean;
anonymizationPolicyVersion?: string;
minimumGroupSize?: number;
}public sealed class AnalyticsDataContractSource
{
public string Name { get; }
public string PrivacyClass { get; }
public bool ContainsDirectIdentifiers { get; }
public bool ContainsGloballyStablePersonTokens { get; }
public string? AnonymizationPolicyVersion { get; }
public int? MinimumGroupSize { get; }
}A contract is draft, accepted, or deprecated. Cross-tenant joins require an explicitly accepted contract referencing the right privacy class. This makes it hard to accidentally ship a query that combines two tenants' raw PII.
What this gives you
- Redaction is declarative and bound to versioned rules.
- Retention is automatic per data class.
- Anonymization is auditable — what, when, why, by which version.
- Cross-tenant access is gated by contracts.
See also
- Privacy engine — using the anonymization engine programmatically.
- Audit trail — what the platform records.
- Policy model — the gate before a write.