Audit journal
Verified with Cortex on October 2, 2026
The audit journal keeps a record of what Cortex does: who acted, which action, toward which destination, under which DLP profile and which types of entities were redacted. It holds neither the content of conversations nor the redacted values: the console says so under the table, and the detail window shows "Request content: not recorded by design". This page explains how to read it, export it and verify its integrity. Access is limited to the "Auditor" and "Administrator" roles (see Users and access).
What is recorded
Each entry carries a sequence number, a timestamp, the originating user, the action, the destination, the DLP profile applied and, for requests, the redacted entity types with their count (shown as "category × count"). Actions cover conversations and DLP scans, but also configuration changes: destinations, DLP rules, users and roles, retention, the knowledge base, web search, sign-ins.
The journal write is part of the same transaction as the request: if the entry cannot be written, the request fails. A request that cannot be audited does not happen. A few entries are the exception, because they describe a refusal or a failure that cancels the request itself: a blocked request (DLP, budget or quota), DLP unavailable, a chat error, a blocked or unavailable web search. They are written in their own transaction so that they survive the cancellation. If that write fails, the original error is still returned and a warning without content is logged. A routing fallback (a switch to another destination) is a separate case: it describes a request still in progress, and its entry is written the same way, in its own transaction, without interrupting the request if the write fails.
The database rejects changes and deletions of entries for the identity the gateway uses. No retention purge touches the journal (see Retention and erasure). Each tenant sees only its own journal.
Read the journal
Open "Audit journal" in the console. The subtitle reads "Read only": the "Auditor" role can read, filter, export and verify the journal, without being able to change it. Four cards summarize the state: "Events (7 d)" (with the count for the last 24 hours), "Chain integrity", "Blocked requests" (last 7 days) and "Retention" (the duration of the conversation retention policy, or "no policy set").
The most recent entries come first, 50 at a time; "Load more" shows the next ones. The columns are "No.", "Timestamp", "User / action", "Destination" (with the DLP profile) and "Entities". A request refused by DLP carries the "blocked" badge. The chevron at the end of a row opens "Event detail", which repeats these fields; "Close" closes it.
- In the filter bar, choose an "Action" ("All actions" by default), a "Destination", or a period with "From" and "To". The "User" field accepts a name, an email or a UUID.
- Click "Filter". Until you do, the list and the export keep the previous filters.
The "History" link on a row of the users list opens this journal with the "User" filter prefilled. The link is in the row's "…" menu. From the "History" tab of the "Retention" page, "View everything in Audit" opens the full journal, with no filter.
Export
The "Export CSV" button is at the top of the page. It is inactive while the list is empty. The export applies the filters from the last time you clicked "Filter" and covers the whole filtered result, not only the displayed rows. The audit-YYYY-MM-DD.csv file contains the columns id, seq, actor_user_id, action, destination_id, policy_applied, redacted_types and occurred_at. The chain hash columns are not in it: the export cannot be used to verify integrity.
Verify integrity
Each entry is linked to the previous one by a hash computed with a secret key specific to the tenant (HMAC-SHA256, key encrypted by the KEK). Changing, removing or reordering an entry breaks the chain: tampering is detectable.
In the console, click "Verify chain", under the cards. The result "Chain intact — N entries checked." or "Chain break detected at sequence N." appears, and the "Chain integrity" card shows "Intact" or "Broken". It displays "not verified" until you run the check since the page was loaded: the state is not kept and resets each time you open or change page.
To verify outside the console, or to schedule the check (cron), run this from the repository checkout, with the gateway's KEK and the database owner identity (these commands do not read .env):
MIGRATION_DATABASE_URL='postgres://cortex_owner:<password>@localhost:5432/cortex' \
KMS_LOCAL_KEK='<the gateway KEK>' \
pnpm --filter gateway audit:verify -- --slug cortex
The command prints one line per tenant (ok, the entry count and the hash of the last entry) and exits with an error code on a break. Without --slug, it checks every tenant. Keeping these hashes in your archived reports gives you a manual comparison point.