Retention policies
A RetentionPolicy is the per-purpose rule the engine resolves against: how long
to keep data, from when, what to do at erasure, and whether another law holds it.
from dpdpstack import RetentionPolicy, Action, Trigger
RetentionPolicy(
purpose="marketing",
retention_days=730, # keep for 2 years…
trigger=Trigger.SINCE_LAST_ACTIVITY, # …from last activity (or SINCE_GRANT)
action=Action.DELETE, # delete vs ANONYMIZE at erasure
legal_hold_days=0, # >0 means erasure defers under a hold
legal_basis="", # recorded for audit when held
)Fields
| Field | Meaning |
|---|---|
purpose | The purpose this rule applies to (required). |
retention_days | How long to keep data. 0 means no time-based retention. |
trigger | Trigger.SINCE_GRANT or Trigger.SINCE_LAST_ACTIVITY. |
action | Action.DELETE or Action.ANONYMIZE. |
legal_hold_days | If greater than zero, erasure is deferred for this many days. |
legal_basis | The cited law; recorded in the audit log and certificate. |
Built-in presets
For the common Indian retention-vs-deletion conflicts, use a preset instead of hand-writing the rule:
from dpdpstack import rbi_kyc, pmla, cert_in_logs
rbi_kyc("kyc") # KYC: 5-year hold, anonymize - RBI KYC Master Direction
pmla("financial_records") # transaction/identity records - PMLA
cert_in_logs("logs") # system logs: 180-day hold - CERT-In DirectionsEach returns a fully-formed RetentionPolicy with the right legal_hold_days,
action, and legal_basis filled in:
| Preset | Hold | Action | Basis recorded |
|---|---|---|---|
rbi_kyc | 1825 days (5y) | anonymize | RBI KYC Master Direction (5 years) |
pmla | 1825 days | anonymize | PMLA (retain transaction records) |
cert_in_logs | 180 days | anonymize | CERT-In Directions (180-day log retention) |
Custom policies
Build your own for any sector rule - for example, a 7-year tax/Companies-Act hold on invoices that anonymizes rather than deletes:
invoices = RetentionPolicy(
purpose="invoices",
retention_days=2555, # ~7 years
action=Action.ANONYMIZE,
legal_hold_days=2555,
legal_basis="Companies Act 2013 - books of account (8 years)",
)Lint & score your policies
lint_policy statically checks a policy for compliance smells - a legal hold with no
recorded basis, a hold that will hard-delete a regulated row, a basis cited without a
hold period, retention far past what's justified - each tied to a DPDP citation.
from dpdpstack import RetentionPolicy, Action, lint_policy
lint_policy(RetentionPolicy(purpose="kyc", legal_hold_days=1825, action=Action.DELETE))
# [ERROR E001: … no legal_basis recorded …,
# WARNING W001: … action=delete will hard-delete … consider action=anonymize …]score_policies rolls the findings across all your policies into a graded readiness
report - a deterministic 0–100 score, letter grade, and tier:
from dpdpstack import score_policies, rbi_kyc, pmla
score_policies([rbi_kyc(), pmla()]).summary
# '100/100 (A+, exemplary) across 2 policies: 2 clean, 0 errors, 0 warnings.'From the shell (exit code is non-zero if any error is found, so it drops into CI):
dpdpstack lint --presets --score
# OK kyc: no findings … Readiness: 100/100 (A+, exemplary) across 5 policies …On the hosted platform, GET /readiness scores your configured policies and the
dashboard shows the grade - the same linter, so SDK and platform never disagree.
DPDPStack is tooling, not legal advice - you set the retention periods and bases that fit your obligations.