Audit, access logs and usage
Accountability means being able to show, afterwards, who did what to whose data and why. The Pamodzi e-Services framework keeps three records for that: the audit trail of every consequential action, the access log that tells citizens who opened their records, and register usage figures for custodians. This lesson explains each, and how they protect personal data instead of copying it.
The audit trail
Every state change, every refused action and every read of restricted data writes an audit row.
What a row holds
| Column | Example (synthetic) |
|---|---|
| Event type and category | data-sharing.approved, category security |
| Outcome | success, denied or failed |
| Actor and role | The user id and the role they acted in |
| Object | The type and id of the record acted on |
| Request id | The same id as the request's X-Request-Id, to tie rows together |
| Channel | Public web, back office, POS, API client, USSD or a system job |
| Address | A keyed hash of the client address, never the address itself |
| Reason | When the action needed one: a reveal, a termination, a refund |
What a row never holds: personal data values, secrets, tokens, passwords. A reveal is recorded with the field paths and the reason, not the values. A register read is recorded with hashes of the records returned and of the search text, not the text. Agreement contacts never appear in an audit row.
Tamper evidence
- The audit table is append-only: the database refuses updates and deletes unless the transaction is a recorded
maintenance operation.
- Rows are hash-chained: each row's hash covers the previous row's hash and the row's own content, so changing,
deleting, swapping or cutting rows breaks the chain at that point.
- Each closed day is anchored: its chain heads and digest are exported and, when a signing key or the platform
seal is configured, signed. A later rewrite of a whole day is caught by its anchor.
- A daily job verifies the chain. A broken chain turns the health check red and alerts administrators, and the
first broken link is recorded.
Refusals are audited too
Every request the policy decision point refuses writes an access.denied row with the action and the step that refused it. A pattern of refusals is often the first sign of misuse.
Coverage
An automated test calls every route that changes data and fails if any of them writes no audit row. Routes added later are covered by the same test.
The access log: what citizens see
When staff open a citizen's case or its documents, the access log records it. The applicant can see, for each access to their case: the role, the ministry, the action and the time. They do not see staff names or addresses.
A reveal of personal data is written there too, in plain words, for example "Viewed your NRC number and phone number in full". This gives the citizen a way to raise a concern if an access looks wrong, and it makes every officer aware that access is visible.
| Who asks | What they get |
|---|---|
| The applicant of the case | Role, ministry, action and time of every staff access |
| Supervisors | The access rows, including reveal reasons |
| Another citizen, or an officer who may not see the case | Refused |
Register usage
Every read of any register is counted, per day, by:
- caller kind: staff, citizen, anonymous, partner client, register key or node;
- ministry;
- outcome: ok, empty or denied.
Form validations against a register are counted as passed or failed. A register's custodians, the DPO and administrators see a usage dashboard: totals per day, by caller, the active grants and agreements, and how fresh the data is. Opening the dashboard is itself audited. Usage is also a reporting source, scoped to the ministry's own registers.
Usage tells a custodian whether an agreement is used as expected. A partner that suddenly reads ten times more than usual, or many empty searches (guessing), is a reason to look closer.
Audit retention
Audit rows are kept per category. The defaults keep every category permanently until a retention period is set. Some categories are locked on and can never be switched off: authentication, lifecycle, security, administration and payment.
When a retention period is set, the purge job removes old rows without weakening the chain:
- it touches only days that are anchored and verify intact just before;
- each removed row leaves a tombstone with its chain position and hash (no actor, object or payload), so the day still
verifies;
- rows about a case or document under an active legal hold are kept;
- every purge run is itself audited.
Example: investigating a question
A citizen sees in their access history that "Support, Example Ministry" viewed their phone number in full on a Saturday. They raise a concern with the DPO.
- The DPO finds the reveal in the audit trail by the case and the time. The row shows the officer, the fields and
the reason: "Return the applicant's call about the missing lease copy".
- The DPO checks the reason against the citizen's own enquiries on the case: there is a message from the citizen
asking to be called back about the lease.
- The DPO confirms the access was for the citizen's own enquiry, records the answer, and replies.
If the reason had not matched any enquiry, the DPO would open a privacy incident (lesson 5).
Your part as a data steward
- Review register usage for the registers you are custodian of, at least monthly.
- Check that reveals in your ministry carry meaningful reasons.
- Never export audit data with personal data into e-mail or spreadsheets; use the platform's own views and exports.
- Report a broken audit chain or a suspicious pattern at once.
Check your understanding
- What does an audit row record about a reveal, and what does it leave out?
- What makes the audit trail tamper-evident?
- What does the applicant see in the access log of their case?
- Which audit categories can never be switched off?
- How does the retention purge keep the hash chain verifiable?
Sign in to record your progress.
Previous: Data-sharing agreements Next: Data subject rights, retention and breaches