Audit Trail
Every write to a project is recorded. The audit trail captures who changed what, when, and how, then links each record to the one before it so the history cannot be silently altered. It is always on: there is no setting to enable or disable it, and no mode to opt into.
This makes a project's history opposable: anyone with owner access can reconstruct exactly how the workspace reached its current state.
What Gets Recorded
Every meaningful change to a project produces an event:
- Versioning operations (snapshot creation, rewind, rename, deletion)
- Lifecycle events (project creation, rename, transfer, deletion, restore)
- Repository syncs, imports, automation runs, and MCP tool calls
- Rejected write attempts from connections without write access
Canvas edits between snapshots are working state, not audit material — the snapshot is the record of the canvas. Each event stores the actor, the action, the affected entity, a human-readable summary, structured metadata, and a strictly monotonic timestamp.
The Hash Chain
Each event carries a SHA-256 hash computed over the event itself plus the hash of the previous event in the chain. This links the records together:
event 1 → hash(event 1)
event 2 → hash(event 2 + hash(event 1))
event 3 → hash(event 3 + hash(event 2))
Modifying or deleting any event breaks every hash after it. Reordering events breaks the chain too. The database enforces append-only access: the audit table rejects updates and deletes even from a direct database session.
Timestamps are strictly monotonic. Two events written in the same millisecond are still ordered deterministically, so the chain has no ambiguity about which event came first.
Verifying Integrity
A project owner can verify the entire chain at any time:
GET /api/projects/{projectId}/audit/verify
The response reports the total number of events and, if the chain is broken, the exact event where verification failed:
{
"valid": false,
"entries": 412,
"brokenAt": "b3f1c8e2-..."
}
Verification is also available in the project audit view: the chain badge shows the current status and, when broken, names the event where tampering was detected.
Viewing History
Project owners
Open the audit trail from the project toolbar: the shield icon next to the share and repository sync buttons opens it as a dialog over the canvas, so you can review history without leaving your work.
The view lists events with timestamp, actor, action, and summary. Metadata for each event can be expanded inline. Filter by action or by user, and page through history with a cursor:
GET /api/projects/{projectId}/audit?limit=50&action=temporal.apply
Roles and the Versioning API
The versioning API is enforced server-side. Client state cannot bypass these rules:
| Action | Viewer | Editor | Owner |
|---|---|---|---|
| View history | ✓ | ✓ | ✓ |
| Reconstruct a state | ✓ | ✓ | ✓ |
| Create a snapshot | ✓ | ✓ | |
| Rewind (apply) | ✓ | ||
| Rename a state | ✓ | ||
| Delete a state | ✓ |
A read-only collaborator cannot wipe or replace the canvas, even with a crafted client. Every rejected attempt is itself recorded in the audit trail.
Rewind
Rewinding restores a past state atomically. The server replays the state into a single transaction: the project is rewritten, the live document is replaced, and every connected client converges to the restored state. There is no partial apply.
Before applying, the confirmation dialog flags unsaved local changes so they are not lost silently. Rewinds are themselves audit events, so a restore never erases the history it rewinds.
Official Records
"Generate the dossier" in the project menu seals the current canvas state as an official record and produces the formal PDF dossier in one flow. Each generation creates a new seal, so every dossier is a fresh reference point. The flow is available to every project editor; there is no mode to enable.
Seals are immutable: a sealed state cannot be deleted or renamed through the versioning API. Attempts are refused with a 409 and recorded in the audit trail as records.seal_guard events, so the refusal itself is part of the history.
The Dossier
The dossier downloads as a ZIP archive containing everything needed to read and verify it:
- The PDF — the formal document described below
ideon-record-<sealId>.json— the verifiable bundle in theideon-record-v1formatverify-record.mjs— the standalone verifier itself
The PDF is written for a non-technical reader — no hashes, no block IDs, no raw action codes. It contains:
- A cover page: project name, sealed by, seal date
- A map of the canvas, when the capture succeeds (in the same light theme as the document)
- A certificate: sealed by, seal date, the official record number, and an integrity status
- The audit timeline up to the seal: who did what, when, in plain language (snapshot, rewind, seal, project update, canvas work) — bursts of canvas edits between snapshots are summarized into single rows
- The inventory of the sealed state: every block with its type and title
- The drift since the previous seal: added, modified, and removed blocks (empty for the first dossier)
The dossier is what you hand to a client or an auditor without opening Ideon. The drift section makes each dossier tell the story since the last one: what moved between two official records.
The PDF itself is locked against modification with an AES-256 owner password generated at download time and held by no one — the server included. Everyone can open, read, and print it; nobody can edit it. The certificate page states it: "Modification: locked".
The Verifiable Bundle
The PDF is for humans; the bundle is for verification. The bundle format:
{
"manifest": {
"format": "ideon-record-v1",
"algorithm": "sha256",
"projectId": "...",
"sealId": "...",
"stateHash": "..."
},
"state": "<exact JSON string of the sealed state>",
"audit": [<chained audit events up to the seal>]
}
Unzip the archive, then verify without Ideon, without a database, and without trusting the producer:
node verify-record.mjs idon-record-<sealId>.json
The verifier (zero dependencies, Node crypto only) recomputes the SHA-256 of the state against the manifest's stateHash and re-walks the entire audit hash chain. It prints VALID or INVALID with the precise reason and exits 0 or 1, so it works in scripts and CI. Tampering with the state or with any audit event is caught by recomputation.
Block Provenance
Right-click any block and choose "Provenance" to see who created it, who modified it since, and whether it belongs to the current sealed state. Attribution is at snapshot level (a 30–120 s window), not keystroke by keystroke — the snapshot is the record of the canvas.
Immutability
A sealed state cannot be deleted or renamed through the versioning API. Attempts are refused with a 409 and recorded in the audit trail as records.seal_guard events, so the refusal itself is part of the history.
Append-Only by Design
The audit table uses row-level security with forced row-level access:
- Any session can read audit events for projects it can access
- Any session can insert events, but only with its own identity as the actor
- No session can update or delete an event, under any circumstances
The chain is the record of what happened. It is not a log that can be cleaned up.