# Changes

> The lightweight, ungated answer to "who changed this row, and when": field NAMES only, never values, newest first. Readable by anyone who may read the row itself — no `app.Audit` grant needed. For the actual old/new values, or a query spanning more than one row, see [[config-audit]]'s `EntityAuditRecord`.

<!-- id: entity-changes · area: entity · stability: stable · html: https://osysharp.com/reference/entity/changes/ -->

## Summary        {#summary}
`<Entity>.Changes(id)` answers "who changed this row, when, and which fields" — the facts behind a concurrency
refusal, handed to your own code rather than only baked into the platform's own message. It carries **field names
only, never values**, which is what lets it be read by anyone who may read the row itself: a caseworker is not an
auditor, and this is not the audit trail.

For the full change record — the actual old/new **values**, or a query that spans more than one row — see
[audit read access (app.Audit)](https://osysharp.com/reference/config/audit/)'s `EntityAuditRecord`, which is deny-by-default and needs an `app.Audit` grant.

## Signature      {#signature}
```osy syntax
<Entity>.Changes(<id>)   →   List<EntityChange>   // newest first
```

Takes exactly one argument — the id of the row whose history you want. `EntityChange` carries:

| Field | Type | Meaning |
|---|---|---|
| `ById` | `Guid?` | who made the change — null for a system or anonymous write |
| `By` | `string` | their display name, AS IT WAS at the time — empty when there was no principal |
| `At` | `DateTime` | when the change was committed |
| `Fields` | `string[]` | the properties this change touched, by name — filtered to the ones the caller may read, so a masked column never appears even as a name |

## Description    {#description}

### Needs the capability, not a grant     {#capability}
`<Entity>.Changes(…)` needs the `Osysharp.Observability` capability — `using Osysharp.Observability;` at the top of
the file — exactly like the audit-trail read surfaces in [audit read access (app.Audit)](https://osysharp.com/reference/config/audit/). What it does **not** need is anything from
`app.Audit`: there is no `Read` predicate to satisfy, because the answer carries no values, only field names, and a
reader who may see the row at all may see which of its fields last moved.

### Why not just read `EntityAuditRecord`?     {#why-lighter}
You can — `EntityAuditRecord.Where(e => e.EntityId == id)` answers the same question with the actual values attached.
`Changes` exists because most callers do not want that: a concurrency refusal, a "last edited" badge, or a change
history strip on a form wants WHO and WHEN, not a values diff a caseworker was never granted to read. Reaching for
the full trail there would mean either widening the audit grant to people who are not auditors, or building a second,
values-stripped view yourself — `Changes` is that view, built in.

## Examples       {#examples}
```osy title="a change-history strip on a form" test app=entity-changes-example
using Osysharp.Observability;

entity Case {
  [Required, MaxLength(40)] string Reference;
  decimal? Amount;
}

List<string> HistoryLines(Case c) {
  var lines = new List<string>();
  foreach (var change in Case.Changes(c.Id)) {
    var who = change.By == "" ? "somebody" : change.By;
    var what = string.Join(", ", change.Fields);
    lines.Add(who + " changed " + what);
  }
  return lines;
}
```

```osy title="naming who changed it in a refusal of your own" test app=entity-changes-refusal
using Osysharp.Observability;

entity Case {
  [Required, MaxLength(40)] string Reference;
  decimal? Amount;
}

string LastEditor(Guid id) {
  var changes = Case.Changes(id);
  return changes.Count == 0 ? "nobody yet" : changes[0].By;   // newest first
}
```

## See also       {#see-also}
- [audit read access (app.Audit)](https://osysharp.com/reference/config/audit/) — the full trail (`EntityAuditRecord`), its values, and who may read it
- [ConcurrencyCheck](https://osysharp.com/reference/entity/concurrency-check/) — the refusal this data backs, when two writers collide
- [use](https://osysharp.com/reference/types/use/) — the `use`/`using` split that gates the capability
