The voting Worker writes selected activity to the D1 audit_logs table. These records support incident review and troubleshooting; they are not a complete request log.
Recorded actions
| Action | Written when |
|---|---|
election.created | An admin creates an election |
election.deleted | An admin deletes an election |
election.closed | An admin closes an election manually |
election.viewed | The public election detail route is opened |
tokens.generated | An admin creates a token batch |
vote.submitted | A ballot is accepted |
results.viewed | The legacy results route is requested |
token.invalid | A submitted ballot token fails validation |
admin.access | An invite is resent, bulk reminders or results email run, election dates change, or a distribution list is mutated |
rate_limit.exceeded exists in the action constants but the current rate-limit middleware does not write it.
Record shape
| Column | Type | Notes |
|---|---|---|
id | text | Generated UUID |
action | text | One of the action strings above |
ip_address | text or null | CF-Connecting-IP, then X-Forwarded-For |
user_agent | text or null | Request User-Agent |
election_id | text or null | Foreign key to elections; set to null when the election is deleted |
timestamp | integer | Unix time in milliseconds |
metadata | text | JSON object serialized as text |
Indexes cover action, election_id, and timestamp.
Example:
{
"id": "4d502067-4ca3-4d61-b431-3d3ccbf74a75",
"action": "vote.submitted",
"ip_address": "192.0.2.1",
"user_agent": "Mozilla/5.0",
"election_id": "elec_123",
"timestamp": 1785038400000,
"metadata": "{\"ballot_type\":\"SIMPLE_TRIPLE\",\"choice\":\"YES\"}"
}Plaintext ballot tokens and admin API keys are not written by the current audit call sites. Some admin.access metadata does contain operational data, including an invite email address when an invitation is resent.
Viewing records
GET /admin/audit-logs renders the authenticated browser interface. It supports:
| Query parameter | Purpose | Default |
|---|---|---|
limit | Rows per page | 100 |
offset | Pagination offset | 0 |
action | Exact action filter | none |
election_id | Exact election filter | none |
Current JSON-route conflict
A JSON handler is registered at the same path, but the admin UI router is mounted first. Requests currently reach the HTML page, not the JSON handler. Route separation is tracked in ihnyc-rc-vote issue #10.
For machine-readable access in the current release, query D1 directly:
SELECT *
FROM audit_logs
WHERE election_id = ?
ORDER BY timestamp DESC;Failure and retention behavior
Audit writes are best effort. createAuditLog catches insertion errors, writes them to the Worker console, and lets the original request continue.
There is no automatic retention, archival, export, or alerting. Records remain in D1 until they are removed operationally.
Sources
Verified against ihnyc-rc-vote commit 2451aa1.
migrations/002_audit_logs.sqlsrc/utils/audit.tssrc/routes/admin.tssrc/routes/admin-ui.tssrc/routes/vote.tssrc/routes/results.tssrc/index.ts