Aggregates, locking, and auditing
Erde groups related records into aggregates, keeps them internally consistent, and records who changed what. Three mechanisms do this work: an aggregate is the unit Erde validates and saves together; locking freezes a Project or Site so its data can't change; and auditing stamps every record with who-and-when and tracks each change. This page explains how the three relate so the behavior you see on save, on lock, and in the audit trail makes sense.
The aggregate as one consistency unit
An aggregate is a parent record (the aggregate root) plus its child records, treated as a single unit. A Lab Report and its samples and batches form one aggregate; a Well and its casing and annular intervals form another. Children belong to exactly one aggregate and can't move to another once added.
Every record carries identity and audit fields from a shared base:
| Field | Purpose |
|---|---|
ExternalId | The stable identifier Erde exposes in the API and URLs (External ID, a time-ordered UUID) |
AggregateExternalId | The ExternalId of the record's aggregate root; on a root it equals its own ExternalId |
ConcurrencyVersion | The optimistic-concurrency counter (Concurrency version) |
CreatedAt / CreatedBy | Stamped when the record is first saved |
LastModifiedAt / LastModifiedBy | Stamped on every later change |
The aggregate boundary matters on save: when you change a child, Erde also advances the root's ConcurrencyVersion. That way two people can't independently edit different children of the same aggregate without one of them hitting a concurrency conflict — the aggregate stays consistent as a whole.
Locking
Each Project and each Site carries an IsLocked flag, off by default. Locking freezes the aggregate: while a Project or Site is locked, Erde blocks every create, update, and delete on that record and on every record scoped to it — its Locations, Samples, Lab Reports, monitoring records, and their children. Reads are unaffected, so locked data stays fully visible and reportable.
Enforcement is automatic and central. A save-time check resolves each pending change to its owning Project or Site and rejects the save if that scope is locked, returning 409 Conflict. Because the check sits in the save pipeline, every path that writes scoped data inherits it — there's no way to bypass the lock through a normal edit screen or API call.
Locking a Project or Site blocks all edits to its data — every scoped record and child — until it is unlocked. Lock a Project or Site only when you intend to freeze it (for example, after a reporting milestone). Reads continue to work; writes return a conflict.
You toggle the lock through a dedicated action, not through the normal Save form — IsLocked is deliberately absent from the Project and Site save requests, so the lock can only change through the lock and unlock endpoints. Read responses do expose the flag (and the matching permission), so the interface can disable Edit, Add, and Delete affordances on a locked aggregate.
Toggling the lock is restricted to global management roles: Administrator and Data Manager. Other roles can edit data they have access to but cannot freeze it.
Auditing
Auditing happens automatically on every save. Erde stamps the audit fields, advances the concurrency counter, and records what changed — you never call it yourself.
Figure: what auditing records at each stage of a record's life.
On a create, Erde sets CreatedAt and CreatedBy. On a change, it sets LastModifiedAt and LastModifiedBy, increments ConcurrencyVersion, and writes a per-change row to the Audit Log capturing the property-level before-and-after values. On a delete, it writes an Audit Log row with a snapshot of the record. Creates are captured by the CreatedBy and CreatedAt fields on the record itself, not as a separate Audit Log row.
The incremented ConcurrencyVersion is the optimistic-concurrency guard: if you load a record, someone else saves a change first, and then you save, your save is rejected because the version you started from no longer matches. You re-load and retry against the current state.
Two separate audit trails
Erde keeps two distinct logs. Don't conflate them — they answer different questions and live in different databases.
| Aspect | Audit Log | System Audit Log |
|---|---|---|
| Records | Domain-data changes: who changed which record, and what changed | Operational and security events: logins, user management, configuration, host start/stop |
| Written | Automatically on every save | At the point each event happens |
| Stored in | The Application database | The Platform database |
| Granularity | Per record, per property change | Per system event |
| Viewed at | Activity Hub → Audit Log, and the per-record Activity Timeline | Administration → System Audit Log (Administrator only) |
The Audit Log is the change history for your data — it backs the Activity Timeline on a record and the Activity Hub → Audit Log feed. The System Audit Log is the operational record an administrator reviews for sign-ins, account changes, and setting updates. See View the system audit log for how to read and filter the System Audit Log.
How it fits together
The three mechanisms reinforce each other on every save:
| Step | What happens |
|---|---|
| Lock check | If the record's Project or Site is locked, the save is rejected (409) before anything is written |
| Audit stamping | CreatedBy / LastModifiedBy and timestamps are set; ConcurrencyVersion advances on changes |
| Audit logging | A per-change Audit Log row is written for edits and deletes |
Because the lock check runs first, a blocked save never stamps audit fields or advances a version — a frozen aggregate leaves no trace of an attempted write.