Skip to main content

compliance

Platform audit trail + GDPR data-subject rights. Re-homed from a removed addon back into the always-loaded core by ADR-0009 — the personal data it acts on (user, auth, tenant) is core-owned, so the compliance plane ships on day one.

What it owns​

  • Audit trail — an append-only compliance_audit_events collection. Every other module emits events through the iface.AuditSink seam without importing this package; the sink write is fire-and-forget so it never blocks a hot path.
  • Auth security events — initialization wires the sink to the canonical auth service through iface.AuditSinkSetter under module.ServiceAuthService, alongside the existing password-service and tenant-service consumers. Successful initial-password enrollment emits self_password_added through the auth security-event lane and mirrors it once as auth.password.added in the compliance trail. Enrollment does not also emit a direct password-service audit event; existing password-service direct-audit actions keep their sink.
  • Module-config change events — every authenticated mutation of a module's configuration (PATCH /v1/admin/modules/{name}, PATCH …/environments/{env}, PUT …/active-environment, enable/disable) emits module.config.updated, module.config.environment_activated, module.enabled or module.disabled into the same trail with the acting user's UUID, tenant context, IP, User-Agent, the key names the request submitted and the failure code — never values or secrets, never the actor's email. Emission is best-effort (a sink failure is logged, not rolled back) and events share the trail's existing two-year retention; deployments needing guaranteed evidence should plan a durable outbox. The records hold an actor UUID, tenant context, IP and User-Agent for privileged-change forensics — the deployer documents the applicable lawful basis and retention for them in its RoPA / privacy materials.
  • GDPR DSR pipeline — right-of-access export and right-to-erasure over the iface.PIIProducer seam. The pipeline walks every registered producer (user, auth, tenant, authz, notification) at request time, so adding a producer needs no change here.
  • Per-tenant KMS — envelope encryption + crypto-shred of a tenant's data-encryption key.
  • Legal hold — an active hold on a subject blocks erasure platform-wide (DSR erase + retention auto-cleanup return 409).
  • Retention auto-cleanup — a daily job that hard-deletes anonymized user tombstones past the retention window. Off by default.
  • Mediated erasure-request workflow — a subject lodges a request; an operator reviews, then executes or rejects it.
  • SOC2 evidence — a point-in-time controls snapshot. Gated off by default (soc2_enabled).
  • Compliance policies — a named, versioned catalog: one platform policy plus tenant policies assigned to internal or client tenants. A policy sets what reaches the operational logs and traces (IP addresses, user agents, user identifiers, personal-data keys, free-text scanning) and the audit retention of each retention class — stored and resolved today, applied to stored audit events by a later stage. Changes need a reason, keep an immutable version, and — when they lower protection — wait for a second operator (four eyes).

Routes​

  • Self-service (authenticated subject): POST /v1/me/dsr/{export,erase,erasure-request}.

  • Admin reads (system.compliance.audit.read): GET /v1/admin/audit-events, …/compliance/legal-holds, …/compliance/retention/preview, …/compliance/erasure-requests, and …/compliance/soc2 (when enabled).

  • Admin writes (step-up gated): place/release legal holds (system.compliance.legalhold.manage), execute/reject erasure requests (system.compliance.dsr.manage).

  • Policy engine reads (system.compliance.policy.read): list and read policies and their versions, the effective policy of a tenant, the retention-class catalog, assignments, change requests, and validation previews (POST …/compliance/policies/validate, POST …/compliance/policy-assignments/{tenantId}/validate).

  • Policy engine writes (system.compliance.policy.manage, step-up gated): create, change and delete policies, assign a policy to a tenant or move it back to the platform policy, approve or reject change requests. A change that waits for four eyes answers 202 with the created request.

    Until CEDAR_ENFORCE_ACTIONS includes system.compliance.policy.manage, the role table grants it by suffix to the platform manager role too, who can then manage and approve policies — the same as legalhold.manage and dsr.manage today. Add it to CEDAR_ENFORCE_ACTIONS once the policy engine is in use.

  • Client surface (Tier 2, own tenant only): GET /v1/tenants/{tenantId}/compliance/policy shows a client how its own data is logged and the retention configured for it; retentionEnforced stays false until audit events are stamped and deleted by class.

Config​

soc2_enabled (default false) · auto_cleanup_enabled (default false) · retention_years (default 5) · export_retention_days (default 30) · four_eyes_enabled (default true).

:::note Always-on Unlike a fork's optional modules, the audit log + GDPR DSR pipeline cannot be disabled — that is the point of making compliance core. Only the SOC2-evidence and retention-auto-cleanup sub-features are gated. :::

Retention classes​

ClassEventsPurposeLegal basisMinimumDefaultSet per tenant
admin_accessTier-1 operator logins, logouts, failed logins, MFA, impersonationControl of administrator accessArt. 6(1)(c) GDPR — Garante, system administrators measure184 days (hard)365yes
privileged_changeConfiguration, role, user, tenant and policy changes by operatorsAccountability and securityArt. 6(1)(f) — legitimate interest1730yes
client_activityActions and logins of Tier-2 client usersSecurity of the service to the clientArt. 6(1)(b) or (f)1365yes
authentication_securityAuthentication security eventsFraud and abuse detectionArt. 6(1)(f)1365no
compliance_evidencePolicy versions, assignments, change requests, DSR, legal holds, verificationsDemonstrating compliance (art. 5(2), 30)Art. 6(1)(c)11825no

The catalog is fixed in code: a policy only chooses the days, within the minimum and ten years. Adapt the purposes and legal bases to your own records of processing. Stamping and deleting audit events by class is a later stage; until it ships, audit events keep the collection's current retention.

Four eyes​

With four_eyes_enabled on (the default), a policy change that raises warnings is not applied: it becomes a change request that another operator approves or rejects with a note. The warnings are:

WarningRaised when
less_restrictive_than_platforma tenant policy is less restrictive than the platform policy
less_restrictive_than_currentan assignment, a reassignment, a removal or an edit leaves a tenant with a less restrictive effective policy than it has now
retention_longer_than_defaulta class is set above its default
retention_shorter_than_defaulta class is set below its default, so audit evidence expires sooner
sink_retention_longer_than_defaulta sink value of the platform policy is above its default
external_sink_changedan external sink is added or changed against the current platform policy (GDPR art. 28 and 44 — check the processor agreement and transfer basis)
ip_fullfull IP addresses are kept
review_overduethe review date is in the past or empty
accountability_incompletethe record-of-processing or assessment reference is empty

The operator ticks an acknowledgement to send a change with warnings. An approval re-checks the current state and supersedes the request if the policy or the assignment changed meanwhile. Requests expire after 14 days: a request past that age can no longer be approved or rejected, even before the hourly sweep marks it. Reasons and notes are personal data kept as compliance evidence: e-mail addresses, IBANs and codici fiscali typed into them are masked before they are stored.

:::caution Supported controls, not conformity The policy engine supports GDPR minimisation and storage limitation and ISO/IEC 27001 controls such as A.5.3, A.8.11 and A.8.15. Conformity is shown by your risk assessment and Statement of Applicability, not by this module. :::