Skip to main content

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:

FieldPurpose
ExternalIdThe stable identifier Erde exposes in the API and URLs (External ID, a time-ordered UUID)
AggregateExternalIdThe ExternalId of the record's aggregate root; on a root it equals its own ExternalId
ConcurrencyVersionThe optimistic-concurrency counter (Concurrency version)
CreatedAt / CreatedByStamped when the record is first saved
LastModifiedAt / LastModifiedByStamped 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.

warning

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.

AspectAudit LogSystem Audit Log
RecordsDomain-data changes: who changed which record, and what changedOperational and security events: logins, user management, configuration, host start/stop
WrittenAutomatically on every saveAt the point each event happens
Stored inThe Application databaseThe Platform database
GranularityPer record, per property changePer system event
Viewed atActivity Hub → Audit Log, and the per-record Activity TimelineAdministration → 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:

StepWhat happens
Lock checkIf the record's Project or Site is locked, the save is rejected (409) before anything is written
Audit stampingCreatedBy / LastModifiedBy and timestamps are set; ConcurrencyVersion advances on changes
Audit loggingA 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.