# [Actor] — a principal that code may run as

> A class marked `[Actor]` declares a principal that code may run as: a robot, a nightly job, an integration, a digital coworker. The platform makes its account once, from the class's own `Principal` member, and gives it exactly the roles the declaration names. `runas (Name) { … }` then runs a block as it — reads, writes and calls all answer to its roles, not the caller's — and commits what the block wrote as the actor. It is the one thing code outside a test may run as; no other account can be acted as.

<!-- id: security-declared-actor · area: security · stability: preview · html: https://osysharp.com/reference/security/declared-actor/ -->

## Summary        {#summary}
Some work is done by nobody in particular: a nightly run that settles invoices, a machine that sorts parcels, an
assistant that files what it was asked to. That work still needs to be **somebody** to your `security { }` rules — with
rights you chose, and a name on what it changed.

A declared actor is that somebody. You declare it as a class; the platform makes its account and stamps its roles;
and a block of code runs as it:

```osy syntax
[Actor(Roles = [DepotRole.Dispatcher])]
public class SortingMachine {
  public static Account Principal => new Account { Email = "sorting-machine@depot.example" };
}

runas (SortingMachine) { parcel.Bay = "B4"; }      // the machine's rights, the machine's name on the change
```

## Signature      {#signature}
```osy syntax
[Actor]                                   // an actor that holds no role
[Actor(Roles = [AppRole.Finance, …])]     // …or the roles its account holds — members of your [Role] enum
public class Name {
  public static <Principal> Principal => new <Principal> { … };   // how its account row is made — required
}

runas (Name) { … }                        // run the block as the actor
runas (Name, onBehalfOf: person) { … }    // …for a person, and never past their own rights — see security-on-behalf-of
runas (Name, scope: row) { … }            // …naming the piece of work on every change the block records

Actor.Of<Name>()            → <Principal>  // the actor's own account row, as a value
Actor.Of<T>()               → <Principal>  // …of the class a type parameter names: `Principal RowOf<T>() => Actor.Of<T>();`
Actor.Of(type)              → <Principal>  // …of a class held as a value: `Actor.Of(typeof(Name))`, `Actor.Of(agent.GetType())`
Actor.From(row)             → <Principal>  // a row you hold, as an actor's — refused unless it IS a declared actor's
runas (Actor.From(task.Actor)) { … }      // …which is how generic code runs as an actor it cannot name
```

## Description    {#description}

### How is the actor's account made?   {#account}
By the platform, once. `Principal` builds one row of your `[Principal]` type; the platform runs it when the app is
deployed (and on first use in a database that has none yet, such as a test's), keeps the row under an identity of its
own, and gives it a grant for each role in `Roles`. A later deploy brings the grants back to **exactly** what the
declaration says, so removing a role from the source removes it from the actor.

Nothing else makes or widens an actor: the roles are the declaration's, never a caller's choice, and no function a
request can call builds the account.

`Actor.Of<Name>()` answers that row wherever you need it as a value — the holder of a piece of work, the owner a query
compares with. You do not need the right to read the row to name it.

Generic code names the class by its type parameter — `Principal RowOf<T>() => Actor.Of<T>();`, which is how a kit
answers the account of an actor your app declares. Which class `T` is arrives with the call; a class that is not a
declared actor is refused when it runs (*`Apprentice` is not a declared actor of this app*).

Code that holds the class as a **value** — a `Type` in a variable, a list of them, `agent.GetType()` — writes
`Actor.Of(type)`: the same account, with the same refusal for a type that is not a declared actor, and it answers
before the actor has ever acted. `Actor.Of(typeof(Stapler))`, written in place, is refused where it is written. A
`Type` is only a name: the platform looks it up among the actors your app declares, so no value code can build makes
an actor that was not declared.

### What may the actor do?   {#rights}
What its roles allow under your ordinary `security { }` blocks, and nothing more. There is no special access: an actor
holding `Dispatcher` is refused wherever a dispatcher would be. An admin calling a function that runs as the actor
gets the actor's rights inside the block, not their own — and gets their own back when it ends.

### How does code run as it?   {#runas}
With `runas (Name) { … }`, in a function, an event handler or a workflow body. Inside the block every read is the
actor's, every write is judged as the actor, every `[Authorize]` is asked of the actor, and `Session.CurrentUser` is
the actor's account.

In a **workflow body** this is how one step answers to rules at all: a body otherwise writes with the app's own
authority ([What a workflow body may write](https://osysharp.com/reference/workflow/body-security/)), and a `runas` block inside it is held to the actor's roles instead.

### What about a row I already hold?   {#rows-you-hold}
It is read as the actor too. "Every read is the actor's" covers a member read off a variable from outside the block,
whether or not you had read that member a line earlier:

| `parcel` was read by the caller, then… | answer |
|---|---|
| a member the actor may not read, inside the block | hidden — it reads as the member's empty value (`""`, `0`, `null`), the same as for anyone it is hidden from |
| a member only the ACTOR may read, inside the block | its value |
| a reference followed inside the block (`parcel.At.Keycode`) | the row it leads to, as the actor reads it |
| a row the actor may not read at all | every member hidden |
| the same members **after** the block | what the caller may read, as before — nothing the actor saw is left on the row |

So a value the block needs from the caller's side is taken **before** it, into a local — a local is a value, not a
read:

```osy syntax
var contents = parcel.Contents;                // the caller may read this; the machine may not
runas (SortingMachine) {
  parcel.Bay = BayFor(contents);               // the value the caller read — `parcel.Contents` here would be ""
}
```

Nothing says so **when it runs**: it is an ordinary hidden read. With `onBehalfOf:` a member is hidden when **either**
may not read it ([[security-on-behalf-of#reads]]).

The compiler says so where it can see it. `osy validate`, the editor and the compile warn
(`RUNAS_READS_A_HELD_ROWS_HIDDEN_MEMBER`) on a read inside the block of a member that carries a `deny read`, off a
parameter or a local from outside the block:

```text
`parcel.Contents` is read inside `runas (SortingMachine)`, and `Parcel` has a `deny read Contents` — … Read it
before the block and use the local: `var contents = parcel.Contents;` above `runas`.
```

| the warning sees | it does not see |
|---|---|
| `row.Member` where `row` is a parameter, or a local whose type is written or plain from `new T { }` or `T.Single(…)` / `First` / `FirstOrDefault` / `SingleOrDefault` / `Find` | a row reached another way — a class's field, a call's result |
| a `deny read Member …` on any declaration of the entity, with or without a condition | whether that condition holds for this actor: that is decided when it runs, so an actor who MAY read the member is warned too |
| | an entity-wide `deny read`, or a row the actor may not read at all |

A row the block reads itself is the actor's own read and is never warned; nor is code in a test, where reading a
member as somebody it is hidden from is how hidden is proved. `[SuppressWarning("RUNAS_READS_A_HELD_ROWS_HIDDEN_MEMBER")]`
on the function records a read that is meant.

### What happens to what the block writes?   {#unit-of-work}
A `runas` block is a unit of work of its own:

| when | what happens |
|---|---|
| the block **starts** | what you have pending is committed first, as you |
| the block **ends** | what it wrote is committed, as the actor — its `[CreatedBy]` / `[ModifiedBy]` stamps and its change-trail entries name the actor |
| the block **throws** | what it wrote is discarded |

So nothing the actor did is ever saved later under somebody else's name. For the same reason a block cannot wait on a
person, a client or another run: do the waiting outside it.

### And in a task, beside other code?   {#beside}
The same, with one difference. A **task** — a call that was not awaited where it was made
([async / await and Task](https://osysharp.com/reference/function/async-await/)) — runs beside its caller, and a block inside it runs beside everything else that is
running: the caller, and every other task. So that none of them is ever read, written or recorded as the actor, the
block works in a unit of work that is **separate from its caller's**:

| | a block in a call you `await` where you make it | a block in a task |
|---|---|---|
| what you have pending when it starts | committed first, as you | **left pending** — a task may not commit its caller's work — and not there for the block |
| what the block reads | what is stored, as the actor | the same |
| what the block wrote, when it ends | committed, as the actor | the same |
| the caller, and the tasks beside it | — | go on reading and writing as who they are, the whole time |

So `await Task.WhenAll(robotA.Work(…), robotB.Work(…))` runs two actors at once, each as itself. Three things follow:

- **A row you made and have not saved is not there for the block.** Used inside it, the block is refused and says
  so: save it before you start the task (`UnitOfWork.Commit()`), or make the row inside the block. In a workflow body
  nothing needs saying — what the body has written is saved before a task starts ([Tasks that wait (Task.WhenAll across days)](https://osysharp.com/reference/workflow/tasks-that-wait/)).
- **A row from outside is still the actor's inside** ([above](#rows-you-hold)), and yours again after: a row the block
  read and handed back — returned, kept in a variable — is read as whoever uses it next.
- **Outside its block a task still writes nothing** ([[function-async-await#writes]]). The block is where a task's
  writes go.

A block entered on your own thread **while tasks you started are still running** is kept apart from them the same
way; there your pending work is committed first, as usual. And a task started *inside* a block is the block's own
work: the block does not end until it has finished.

The change trail records every change an actor makes, whatever the app otherwise audits, so an app that has an actor
— its own, or one a package it uses declares — includes the trail: `use Osysharp.Observability;` ([audit read access (app.Audit)](https://osysharp.com/reference/config/audit/)).
An app that does not is refused when it is compiled, naming the actor.

### Can a package declare an actor?   {#in-a-package}
Yes: a kit declares the identity its own code runs as, and its functions say `runas (Name) { … }` like any other
code. The roles it names are the **app's**, written in the package's role vocabulary ([`role` — a package's name for the app's own roles](https://osysharp.com/reference/security/role-type/)):

```osy syntax
[Actor(Roles = [role.Sweeper])]
public class Sweeper { public static Member Principal => new Member { Email = "sweeper@cleaning.example" }; }
```

An app that uses the kit and has no `Sweeper` in its `[Role]` enum is refused when it is compiled. The actor then
holds exactly that role in the app, whoever calls the kit's function.

### What will the compiler refuse?   {#refused}
Outside a test, `runas` runs as a declared actor and nothing else.

| you write | answer |
|---|---|
| `runas (SortingMachine)` — a class marked `[Actor]` | runs as the actor |
| `runas (Actor.Of<SortingMachine>())` | the same |
| `Actor.Of(typeof(SortingMachine))`, `Actor.Of(type)` | the actor's account; a `Type` that names no declared actor is refused when it runs |
| `Actor.Of("SortingMachine")` — anything but a `Type` | compile error saying what to write |
| `runas (Actor.From(row))` | compiles; refused when it runs unless `row` is a declared actor's own account |
| `runas (Account.Single(…))` — any other row | compile error: a row is never run as because code can read it |
| `runas (Stapler)` — a class that is not an actor | compile error naming the class |
| `runas (AuthBootstrap)`, `runas (new Visitor { … })` | compile error: test-only forms ([runas](https://osysharp.com/reference/testing/runas/)) |

An `[Actor]` that cannot be one is refused where it is written: a role that is not in your `[Role]` enum, a missing
`Principal`, a `Principal` that builds something other than your `[Principal]` type, or the attribute on an `entity`.
So is an app that has an actor and does not keep the change trail ([above](#unit-of-work)).

## Examples       {#examples}
```osy title="a courier may not move a parcel, and may have the machine sort it" run app=security-declared-actor
using Osysharp.Observability;

[Role] enum DepotRole { Dispatcher, Courier }

[Principal] entity Account {
  [Unique, MaxLength(200)] string Email;
  security { allow read when IsAuthenticated; }
}

[RoleGrant] entity RoleGrant {
  [Required] Account Holder;
  [Required] DepotRole Level;
  security { allow read when IsAuthenticated; }
}

policy IsDispatcher => RoleGrant.Any(g => g.Holder == user && g.Level == DepotRole.Dispatcher);

/// The sorting machine: it holds the dispatcher's role, and nothing else.
[Actor(Roles = [DepotRole.Dispatcher])]
public class SortingMachine {
  public static Account Principal => new Account { Email = "sorting-machine@depot.example" };
}

entity Parcel {
  [Required, MaxLength(40)] string Code;
  [MaxLength(40)] string Bay = "";
  [ModifiedBy] Guid? MovedBy;
  security {
    allow read when IsAuthenticated;
    allow create, update when IsDispatcher;      // a courier may not move a parcel
  }
}

/// Moving it by hand: only a dispatcher's to do.
void MoveByHand(Parcel parcel, string bay) { parcel.Bay = bay; UnitOfWork.Commit(); }

/// Anybody signed in may ask for a parcel to be sorted; the machine does the moving.
void Sort(Parcel parcel, string bay) {
  runas (SortingMachine) { parcel.Bay = bay; }
}

[TestFixture]
void Depot() {
  var cleo = new Account { Email = "cleo@depot.example" };
  new RoleGrant { Holder = cleo, Level = DepotRole.Courier };
  new Parcel { Code = "P-1" };
}

principal Cleo => Account.Single(a => a.Email == "cleo@depot.example");

[Test(Depot)]
[runas(Cleo)]
void a_courier_cannot_move_a_parcel_but_the_machine_sorts_it_for_her() {
  var parcel = Parcel.Single(p => p.Code == "P-1");
  Assert.Denied(() => MoveByHand(parcel, "B4"));
  UnitOfWork.Discard();

  Sort(parcel, "B4");

  var sorted = Parcel.Single(p => p.Code == "P-1");
  Assert.Equal("B4", sorted.Bay);
  Assert.Equal(Actor.Of<SortingMachine>().Id, sorted.MovedBy);     // the machine's change, not Cleo's
  Type machine = typeof(SortingMachine);
  Assert.Equal(sorted.MovedBy, Actor.Of(machine).Id);              // the same account, from the class as a value
}
```

A row the caller holds is read as the actor inside the block, and as the caller again after it — in both directions:

```osy title="a row held by the caller, read inside the block and after it" run app=security-declared-actor-held-row
using Osysharp.Observability;

[Role] enum DepotRole { Dispatcher, Clerk }

[Principal] entity Account {
  [Unique, MaxLength(200)] string Email;
  security { allow read when IsAuthenticated; }
}

[RoleGrant] entity RoleGrant {
  [Required] Account Holder;
  [Required] DepotRole Level;
  security { allow read when IsAuthenticated; }
}

policy IsDispatcher => RoleGrant.Any(g => g.Holder == user && g.Level == DepotRole.Dispatcher);
policy IsClerk => RoleGrant.Any(g => g.Holder == user && g.Level == DepotRole.Clerk);

[Actor(Roles = [DepotRole.Dispatcher])]
public class SortingMachine {
  public static Account Principal => new Account { Email = "sorting-machine@depot.example" };
}

entity Depot {
  [MaxLength(40)] string Name = "";
  [MaxLength(40)] string Keycode = "";
  security {
    allow read when IsAuthenticated;
    deny read Keycode when !IsClerk;              // a clerk's to read
  }
}

entity Parcel {
  [Required, MaxLength(40)] string Code;
  [MaxLength(80)] string Contents = "";
  [MaxLength(80)] string Route = "";
  Depot? At;
  security {
    allow read when IsAuthenticated;
    allow create, update when IsDispatcher || IsClerk;
    deny read Contents when !IsClerk;             // a clerk's to read
    deny read Route when !IsDispatcher;           // a dispatcher's — so the machine's — to read
  }
}

/// A slip a clerk files; nobody else reads one.
entity Slip {
  [MaxLength(40)] string Line = "";
  security { allow read, create when IsClerk; }
}

public class Seen {
  public string Before = "";
  public string Inside = "";
  public string Queried = "";
  public string After = "";
}

/// The caller hands over a row it has already read from.
Seen ReadHeld(Parcel parcel) {
  var seen = new Seen { };
  seen.Before = parcel.Contents + "|" + parcel.Route + "|" + parcel.At!.Keycode;
  runas (SortingMachine) {
    seen.Inside = parcel.Contents + "|" + parcel.Route + "|" + parcel.At!.Keycode;
    var again = Parcel.Single(p => p.Code == parcel.Code);
    seen.Queried = again.Contents + "|" + again.Route;
  }
  seen.After = parcel.Contents + "|" + parcel.Route + "|" + parcel.At!.Keycode;
  return seen;
}

/// The block is the first to read the row's members at all.
Seen ReadFirstInside(Parcel parcel) {
  var seen = new Seen { };
  runas (SortingMachine) { seen.Inside = parcel.Contents + "|" + parcel.Route + "|" + parcel.At!.Keycode; }
  seen.After = parcel.Contents + "|" + parcel.Route + "|" + parcel.At!.Keycode;
  return seen;
}

Seen ReadSlip(Slip slip) {
  var seen = new Seen { };
  seen.Before = slip.Line;
  runas (SortingMachine) { seen.Inside = slip.Line + "|" + Slip.Count(); }
  seen.After = slip.Line;
  return seen;
}

/// For the caller: hidden from either is hidden.
Seen ReadHeldFor(Parcel parcel, Account person) {
  var seen = new Seen { };
  runas (SortingMachine, onBehalfOf: person) { seen.Inside = parcel.Contents + "|" + parcel.Route; }
  seen.After = parcel.Contents + "|" + parcel.Route;
  return seen;
}

/// What the caller may read and the actor may not is taken before the block.
string CarriedIn(Parcel parcel) {
  var contents = parcel.Contents;
  var carried = "";
  runas (SortingMachine) { carried = contents + "|" + parcel.Contents; }
  return carried;
}

[TestFixture]
void World() {
  var cleo = new Account { Email = "cleo@depot.example" };
  new RoleGrant { Holder = cleo, Level = DepotRole.Clerk };
  var north = new Depot { Name = "North", Keycode = "4711" };
  new Parcel { Code = "P-1", Contents = "glass", Route = "via-Oslo", At = north };
  new Slip { Line = "paid" };
}

principal Cleo => Account.Single(a => a.Email == "cleo@depot.example");

[Test(World)]
[runas(Cleo)]
void a_held_row_is_read_as_the_actor_inside_the_block_and_as_the_caller_after_it() {
  var seen = ReadHeld(Parcel.Single(p => p.Code == "P-1"));
  Assert.Equal("glass||4711", seen.Before);          // Cleo, a clerk: the contents and the keycode, not the route
  Assert.Equal("|via-Oslo|", seen.Inside);           // the machine, a dispatcher: the route, and neither of hers
  Assert.Equal("|via-Oslo", seen.Queried);           // …the same as a row the block reads for itself
  Assert.Equal("glass||4711", seen.After);           // Cleo again: hers are back, and the route is not left behind
}

[Test(World)]
[runas(Cleo)]
void what_the_actor_read_first_is_not_left_on_the_row_for_the_caller() {
  var seen = ReadFirstInside(Parcel.Single(p => p.Code == "P-1"));
  Assert.Equal("|via-Oslo|", seen.Inside);
  Assert.Equal("glass||4711", seen.After);
}

[Test(World)]
[runas(Cleo)]
void a_row_the_actor_may_not_read_at_all_reads_as_nothing_inside_the_block() {
  var seen = ReadSlip(Slip.Single());
  Assert.Equal("paid", seen.Before);
  Assert.Equal("|0", seen.Inside);
  Assert.Equal("paid", seen.After);
}

[Test(World)]
[runas(Cleo)]
void on_behalf_of_the_caller_what_either_may_not_read_is_hidden() {
  var seen = ReadHeldFor(Parcel.Single(p => p.Code == "P-1"), Cleo);
  Assert.Equal("|", seen.Inside);
  Assert.Equal("glass|", seen.After);
}

[Test(World)]
[runas(Cleo)]
void a_value_taken_before_the_block_is_the_callers() {
  Assert.Equal("glass|", CarriedIn(Parcel.Single(p => p.Code == "P-1")));
}
```

Two actors at once, each as itself — and the person who started them still herself:

```osy title="two machines work side by side, each as itself" run app=security-declared-actor-beside
using Osysharp.Observability;

[Role] enum DepotRole { Sorter, Weigher, Courier }

[Principal] entity Account {
  [Unique, MaxLength(200)] string Email;
  security { allow read when IsAuthenticated; }
}

[RoleGrant] entity RoleGrant {
  [Required] Account Holder;
  [Required] DepotRole Level;
  security { allow read when IsAuthenticated; }
}

policy IsSorter  => RoleGrant.Any(g => g.Holder == user && g.Level == DepotRole.Sorter);
policy IsWeigher => RoleGrant.Any(g => g.Holder == user && g.Level == DepotRole.Weigher);
policy IsCourier => RoleGrant.Any(g => g.Holder == user && g.Level == DepotRole.Courier);

[Actor(Roles = [DepotRole.Sorter])]
public class SortingMachine { public static Account Principal => new Account { Email = "sorter@depot.example" }; }

[Actor(Roles = [DepotRole.Weigher])]
public class Scales { public static Account Principal => new Account { Email = "scales@depot.example" }; }

entity Parcel {
  [Required, MaxLength(40)] string Code;
  [MaxLength(40)] string Bay = "";
  int Grams;
  [MaxLength(80)] string Note = "";
  [ModifiedBy] Guid? ChangedBy;
  security {
    allow read when IsAuthenticated;
    allow update Bay when IsSorter;               // only the sorting machine places a parcel
    allow update Grams when IsWeigher;            // only the scales weigh one
    allow update Note when IsCourier;             // a courier may leave a note
    deny read Grams when !IsWeigher;              // …and only the scales read a weight
  }
}

/// Each is a task when it is started without `await`: it runs beside its caller, as its own actor.
async Task<string> Place(Guid parcel, string bay) {
  await Task.Delay(5);
  runas (SortingMachine) {
    var p = Parcel.Single(x => x.Id == parcel);
    await Task.Delay(10);                         // still open while the scales open and close theirs
    p.Bay = bay;
    return $"placed, weight seen: {p.Grams}";     // the sorter may not read a weight: hidden, so it reads 0
  }
}

async Task<string> Weigh(Guid parcel, int grams) {
  runas (Scales) {
    var p = Parcel.Single(x => x.Id == parcel);
    p.Grams = grams;
  }
  await Task.Delay(5);
  runas (Scales) { return $"weighed: {Parcel.Single(x => x.Id == parcel).Grams}"; }
}

[TestFixture]
void Depot() {
  var cleo = new Account { Email = "cleo@depot.example" };
  new RoleGrant { Holder = cleo, Level = DepotRole.Courier };
  new Parcel { Code = "P-1" };
}

principal Cleo => Account.Single(a => a.Email == "cleo@depot.example");

[Test(Depot)]
[runas(Cleo)]
void two_machines_work_at_once_each_as_itself_and_the_courier_stays_the_courier() {
  var parcel = Parcel.Single(p => p.Code == "P-1");
  var placing = Place(parcel.Id, "B4");           // started…
  var weighing = Weigh(parcel.Id, 1200);          // …and started: both are running now
  parcel.Note = "fragile";                        // Cleo's own change, while they work
  var said = await Task.WhenAll(placing, weighing);
  UnitOfWork.Commit();

  Assert.Equal("placed, weight seen: 0", said[0]);    // the sorter never read as the scales (they wrote 1200)
  Assert.Equal("weighed: 1200", said[1]);             // the scales read what they wrote
  var after = Parcel.Single(p => p.Code == "P-1");
  Assert.Equal("B4", after.Bay);
  Assert.Equal("fragile", after.Note);
  Assert.Equal(0, after.Grams);                       // Cleo is a courier: the weight is not hers to read…
  Assert.True(Security.IsMasked(after.Grams));        // …and that 0 is a withheld value, not a weighed one
  Assert.Equal(Cleo.Id, after.ChangedBy);             // the last change stored is her note, under her name
}
```

## See also       {#see-also}
- [runas (actor, onBehalfOf: person) — an actor acting for a person](https://osysharp.com/reference/security/on-behalf-of/) — an actor acting for a person: `onBehalfOf:` and `scope:`
- [async / await and Task](https://osysharp.com/reference/function/async-await/) — tasks: starting work beside the caller, and what a task may write
- [What may go past the caller's own rights — and what never does](https://osysharp.com/reference/security/past-the-callers-rights/) — every route past one person's own rights, side by side
- [acting for another principal](https://osysharp.com/reference/security/acting-for-another-principal/) — a device or integration that signs in as itself
- [runas](https://osysharp.com/reference/testing/runas/) — `runas` in a test, where any seeded principal may be named
- [role grants (and the first admin)](https://osysharp.com/reference/security/role-grants/) — the roles an actor's `Roles` names
- [What a workflow body may write](https://osysharp.com/reference/workflow/body-security/) — what a workflow body may write without one
