Open Chat Interfacedocs

Compliance export and legal hold

Audit events and, optionally, conversation content exported as verified JSON Lines to S3, exactly once; and legal holds that pause deletion for named people.

Data & storage → Compliance (/admin/compliance). Two tools for eDiscovery, records requests and security monitoring:

  • Compliance export: every audit event (including what was deleted, by whom and when) and, if you turn it on, conversation content, written as JSON Lines to S3-compatible storage every hour or every day. Each run continues exactly where the last one ended.
  • Legal hold: named people whose records no retention job, purge or deletion may remove until the hold is lifted.

An auditor sees everything here and changes nothing.

Data & storage → Compliance in the auditor's read-only view: the end of the export settings, Legal holds with one person on hold and the matter reference, and a history of verified export runs.

The export

StreamWhenEach line
Audit eventsAlways, while the export is onOne audit entry: seq, id, createdAt, action, actorUserId, actorEmail, targetType, targetId, metadata, ipAddress. Includes deletion events.
Conversation contentOnly with Include conversation content onOne message: seq, id, threadId, userId, userEmail, role, status, model, parentMessageId, createdAt, updatedAt, supersededAt, error, thread (title, temporary, projectId), text, toolSteps, files, sources.

The first export includes the whole audit log as retention has kept it. For messages:

  • text is what people read; reasoning is not exported.
  • toolSteps has one line per tool call, such as Searched the web for "opening hours" · 5 results; raw tool results are not exported.
  • files names attachments (attachmentId, filename, mediaType). Attachment contents are never exported.
  • A message is exported when created and again whenever its text, tool steps, status or error change, or a retry replaces it (supersededAt). A reply still being written is exported once, when it finishes.
  • Temporary chats are included, marked "temporary": true.
  • Deletions are not exported as messages. Deleting a conversation or an account leaves the lines already exported; the deletion itself is a deletion event in the audit stream.

Content is off by default

Include conversation content copies everyone's messages to the destination, where OCI's retention, deletion and access controls no longer apply. The page warns before you save it. Turn it on only where your policy requires it, and say so in your acceptable use policy. Content is exported from the moment you save the setting; earlier messages, and messages written while it was off, are never exported.

Deletion events

Everything that moves a person's data to the trash, restores it or deletes it writes one audit entry, whether the person did it, an administrator, or a background job (retention, trash purging, temporary-chat expiry, memory retention). So a downstream archive can tell a deletion from a gap. Each entry is written in the same database transaction as the change: a deletion is never committed without its entry, and no entry is written for a deletion that rolled back. They travel in the audit stream, so they are exported exactly once like every other audit event, can be sent to webhooks, and are kept by audit retention while unexported or while their owner is on legal hold.

actionWhen
conversation.trashA conversation moves to the trash, by its owner or by conversation retention. Counts the files that went with it.
conversation.restoreIts owner restores it from the trash.
conversation.deleteA conversation is destroyed: deleted forever or the trash emptied, the trash window elapsed, or a temporary chat expired. Counts the messages, files and artifacts destroyed with it.
attachment.trashA chat file is deleted on its own (it goes to the trash).
attachment.deleteA project file is deleted, or a chat file in the trash is purged.
project.deleteA project is deleted, with its files; its conversations are kept.
memory.deleteA memory note is deleted by the person, by a model's forget tool or Undo, or by memory retention. One entry per note.
user.deleteAn administrator deletes an account, or a person deletes their own (self: true). Counts everything deleted with it.

Every one has the same metadata.deletion object:

FieldMeaning
typeconversation, attachment, project, memory or user.
idThe ID of what was trashed, restored or deleted.
ownerUserId, ownerEmailWhose data it was; kept after the account is deleted.
reasonuser, admin, tool, retention, trash_expiry, temporary_expiry or unused_expiry (a conversation started and never used).
permanenttrue for *.delete; false for trash and restore.

Who is actorUserId and actorEmail: the owner, the administrator, or null for a background job (the reason names the job). When is createdAt. Never what was deleted: no conversation titles, file names, project names or memory text, because audit entries are shown to administrators, sent to webhooks and kept after the thing itself is gone. With Include conversation content on, the archive already holds the messages, keyed by threadId, so the IDs join a deletion to what it removed.

Objects

Each run that has something to export writes one folder; a run with nothing new writes none and shows as Nothing new to export.

audit.jsonl
messages.jsonl (only with content on)
manifest.json (written last)

<prefix> is .oci-compliance/ in the attachment bucket, or your own prefix in a separate bucket. manifest.json names the format (oci-compliance/1), the OCI version, the run, whether content was included, and for each stream its object key, afterSeq, throughSeq, count, first and last sequence numbers, size and SHA-256. Only folders with a manifest.json are complete.

Exactly once

Every audit event, and every message change, appears in exactly one object:

  • Each stream has a cursor. A run writes everything between the cursor and a safe upper bound, reads every object back to check its size, SHA-256 and line count, writes and checks the manifest, and only then moves the cursor, in the same transaction that marks the run succeeded.
  • A failed run moves nothing on. Its objects are deleted and the next run writes the same events again. A run interrupted by a restart is marked failed by the next one, which deletes what it wrote.
  • Transactions still in progress cannot be skipped: the upper bound is read under a brief lock that waits for writers already under way. If the table stays busy, the run fails with stayed busy and the next run retries; nothing is skipped.
  • Sequence numbers can skip (a rolled-back transaction uses one and writes nothing), so check continuity by each manifest's afterSeq equalling the previous one's throughSeq, per stream. seq with the stream is a unique key to deduplicate on.
sha256sum audit.jsonl              # equals streams.audit.sha256
wc -l < audit.jsonl                # equals streams.audit.count
jq -s 'map(.seq) | (. == sort)' audit.jsonl

Destination and schedule

  • Destination: the attachment bucket under .oci-compliance/ (storage reconciliation never treats these as orphans), or a separate S3 bucket (recommended) with its own region, endpoint, access key and prefix. Records meant to be tamper-evident belong in a bucket with versioning or object lock. Test destination writes, reads back and deletes a small object. The credential needs s3:PutObject, s3:GetObject, s3:DeleteObject and the multipart upload actions. Turning the export on is refused while the destination is incomplete.
  • Schedule: every hour, or once a day at an hour (UTC). A check every five minutes runs an export when the current period has none yet; a failed scheduled run is retried after an hour. Export now runs one in the background.
  • Retention of exported objects: kept by default. Delete exported objects after (days) removes older successful runs' objects at the current destination.

Changing settings is audited as compliance.settings.update and kept regardless of retention. Manual runs and every failed run are audited as compliance.export.run, so a webhook can alert on failures. System health has a Compliance export row: a failed latest run is an error; no success for two periods is a warning.

Under Legal holds, enter a person's email and a reason (a matter or case reference) and choose Place hold. Lift ends it, with an optional reason. Both are audited (compliance.hold.place, compliance.hold.lift) and kept regardless of retention; lifted holds stay listed as history. Held people are marked Legal hold in the user list and on their account page.

NormallyUnder hold
Conversation retention moves inactive conversations to the trashSkipped for this person
The trash is purged after its windowTheir trash (conversations and files) is kept
Expired temporary chats are deletedKept (still invisible to them)
Audit retention prunes old entriesEntries by or about them are kept, including deletion events for their data
Memory retention deletes old notesTheir notes are kept
Usage history is pruned after its windowTheir usage events are kept (daily totals are always kept)
Expired and revoked share links are removed after 30 daysTheir links are kept (still unusable)
They empty their trash or delete a conversation foreverRefused: Permanent deletion is paused for this account by your organization. Moving to the trash still works.
They delete a project or a project fileRefused: projects have no trash, so this would destroy the files at once
They delete memory notes (in Settings, with the forget tool, or by undoing a saved note)Refused. Editing a note still works.
They delete a chat fileStill works: it goes to the trash, which is kept
They or an administrator delete the accountRefused, by the server and by a database trigger on any path

Lifting the hold restores normal behaviour: anything past its window is removed at the next run. A hold applies to work that starts after it is placed.

Every place OCI deletes data checks holds, and a test in OCI's own suite fails when a new deletion is added without that decision. Deletions that do not check remove things that are not a person's records: sessions and tokens, connector credentials, configuration, queues and similar.

What a hold does not cover

A hold does not stop the person using OCI (ban them for that), does not change what the export includes, and does not cover data outside OCI's database and file storage. Backup retention still deletes old backups. Editing, such as renaming a conversation or changing a project's instructions or a memory note, is not deletion and is not paused.

On this page