Osy#betaRun it on Osyrin: osyrin.com

Reference / Entity

[CreatedAt] [CreatedBy] [ModifiedAt] [ModifiedBy]

[CreatedAt] DateTime Created; [CreatedBy] Guid? Author; [ModifiedAt] DateTime Modified; [ModifiedBy] Guid? Editor;

Row stamps: an entity declares a field and marks it, and the platform writes into it when the row is saved — the instant it was created, who created it, the instant of the last write, who made it. An entity that declares none has none.

stable2 examples compiled by CIentityauditsecuritydata

Summary#

A row stamp is a field the platform writes for you when a row is saved. You declare the field, mark it with one of four attributes, and never assign it again:

AttributeTypeWritten
[CreatedAt]DateTimethe instant the row's unit of work committed — once, when the row is created
[CreatedBy]Guid?the id of whoever was signed in — once, when the row is created
[ModifiedAt]DateTimethe instant of the last commit that wrote the row, its creation included
[ModifiedBy]Guid?the id of whoever made that write

Nothing is stamped that you did not declare. An entity with no stamp has no such column — the platform adds nothing to a row behind your back.

Signature#

entity Order {
  [CreatedAt]  DateTime Placed;      // the field's name is yours
  [CreatedBy]  Guid?    PlacedBy;    // a WHO stamp is a nullable Guid: a system write has nobody signed in
  [ModifiedAt] DateTime Changed;
  [ModifiedBy] Guid?    ChangedBy;
}

Description#

When does the platform write them?#

On the commit that saves the row. [CreatedAt] and [CreatedBy] are written once, when the row is created; [ModifiedAt] and [ModifiedBy] on that commit and on every commit that changes the row after it. The instant is the commit's own — the same for every row it writes, and the pinned clock in a test (see TestClock.Advance and TestClock.Set).

A write the system makes with nobody signed in — a scheduled job, a migration — writes the instant stamps and leaves the Guid? ones as they were.

entity Sighting {
  [Required, MaxLength(200)] string Species;
  int Count;
  [CreatedAt] DateTime Logged;
  [CreatedBy] Guid? LoggedBy;
  [ModifiedAt] DateTime Changed;
}

void LogSighting(string species, int count) {
  new Sighting { Species = species, Count = count };   // no stamp is set here — the commit writes them
}

List<Sighting> Newest() {
  return Sighting.OrderByDescending(s => s.Logged).Take(20).ToList();
}

int LoggedToday() {
  var today = DateTime.UtcNow.Date;
  return Sighting.Where(s => s.Logged >= today).Count();
}

Can my code write a stamp?#

No — and the compiler says so rather than letting the value vanish. A stamp in an object initializer is refused, because the commit would replace it; so is any assignment to a [ModifiedAt]/[ModifiedBy] field, which every commit rewrites. To make a row as someone — a test world's rows, say — create it inside runas (Ada) { … }.

A [CreatedAt] assigned on a row that already exists is kept: that is how a test backdates something to see it age.

A client, an API call and an MCP tool cannot write a stamp either: a value sent for one is ignored, on every surface.

Who owns a row?#

[CreatedBy] is the field an ownership rule reads, and it is safe to read because nobody can forge it — the platform writes the signed-in person's id over whatever was sent:

[Principal]
entity Member {
  [Required, MaxLength(200)] string Email;
  security { allow read where Id == user.Id; }
}

entity Note {
  [Required, MaxLength(200)] string Text;
  [CreatedBy] Guid? Author;
  security {
    allow read, update, delete where Author == user.Id;
    allow create when IsAuthenticated;
  }
}

What if I name a field CreatedAt without the attribute?#

Then it is an ordinary field: you set it, and the platform leaves it alone. That is the shape for a date that comes from somewhere else — the moment an imported order was placed in the shop it came from, say.

Reading a stamp an entity does not declare is a compile error that names the line to add:

entity 'Ticket' has no property 'CreatedAt' — the instant a row was created is not a column the platform adds
behind your back: declare it on 'Ticket' with `[CreatedAt] DateTime CreatedAt;`, and the platform writes it.

Where can a stamp go?#

On a stored entity's field of the right type: DateTime (or DateTime?) for an instant, Guid? for a person. One field per stamp, one stamp per field. A class has no rows to stamp, and an external entity's rows are written by its data source, so both refuse the attributes by name.

See also#

Related

entity

Declares a persisted type — a table of rows the app stores, queries and secures. Every entity gets an Id from the…

entity members

The typed members an entity holds — text, numbers, dates, booleans, Guids, enums and references. A member's type…

security { }

The rules that decide who may read and write an entity's rows. A where clause filters by the row (the owner sees their…

Current time (DateTime.UtcNow, DurableClock.Now)

Read the current instant. `DateTime.UtcNow` (and the platform-idiomatic `DurableClock.Now`/…) return the current time…

TestClock.Advance and TestClock.Set

In a test, moves the run's clock FORWARD by a duration, then lets the next Workflow.Settle fire every workflow timer…