# policy

> Names an authorization rule once so every place that needs it can say the name. A policy is a boolean about the caller, usable where security is declared and callable in ordinary code.

<!-- id: security-naming-a-policy · area: security · stability: stable · html: https://osysharp.com/reference/security/naming-a-policy/ -->

## Summary        {#summary}

A `policy` gives an authorization rule a name. Declare it once, then say the name wherever it applies — in an
entity's `security { }`, on a page, and inside a function as an ordinary boolean. A policy may take a parameter, which
is how you say *"may they manage **this** one"* rather than *"are they a manager of something"*.

## Signature      {#signature}

```osy syntax
policy Name => <predicate over `user`>;
policy Name(Type parameter) => <predicate over `user` and that parameter>;
```

## Description    {#description}

The predicate is about the **caller**. `user` is whoever is asking, and the rule may look at any data it needs to
decide — including rows the caller could not read themselves. That is deliberate: whether you *may* do a thing must
not depend on which rows you happen to be allowed to *see*, or every grant becomes circular.

```osy title="one rule, named" test app=security-naming-a-policy
[Principal] entity Person {
  [Required, MaxLength(200), Unique] string Email;
  security { allow read when IsAuthenticated; }
}

entity Team {
  [Required, MaxLength(80)] string Name;
  security { allow read when IsAuthenticated; }
}

entity TeamLead {
  [Required] Person Person;
  [Required] Team Team;
  security { allow read when IsAuthenticated; }
}

// "is a lead of some team" — a fact about the caller alone.
policy IsALead => TeamLead.Any(l => l.Person == user);
```

### Call it like the boolean it is   {#calling}

A policy reads as a boolean, so you can use it as one. This is the alternative to copying the predicate into every
function that needs it — one rule in as many copies as your app has entry points, each able to drift, in the one
category where drift is a hole rather than a bug.

```osy title="named, not re-written" test app=security-naming-a-policy
string LeadsOnly() {
  if (!IsALead) { throw new NotAuthorized("Only a team lead may do this."); }
  return "done";
}
```

### A parameter is what makes it about **this** one   {#parameters}

`IsALead` asks whether you lead *anything*. Most real authority is narrower — may you manage **this** team? Give the
policy a parameter and pass the row:

```osy title="authority about a particular row" test app=security-naming-a-policy
policy CanManageTeam(Team t) => TeamLead.Any(l => l.Person == user && l.Team == t);

string Rename(Guid teamId, string name) {
  var team = Team.Where(t => t.Id == teamId).FirstOrDefault();
  if (!CanManageTeam(team)) { throw new NotAuthorized("Only a lead of THIS team may rename it."); }
  team.Name = name;
  return name;
}
```

The difference matters more than it looks. A lead of *another* team passes `IsALead` and fails `CanManageTeam(team)` —
and that is usually the only case that tells a working rule from a broken one, because a manager and a non-member
behave the same under both.

A parameter may also be the row's **id** — `policy IsMyBasket(Guid basket) => Basket.Any(b => b.Id == basket && b.VisitId
== Visitor.Id);` — which is what a rule passes (`where IsMyBasket(Id)`) and what a page holds. Called in a body, it
answers exactly as it does in the rule: a comparison with nobody (no signed-in `user`, no visit) matches nothing.

### A policy does not see the row it guards   {#no-row}

A policy's body is like a C# `static` method: the names in it are **tables, enums, `user`, other policies and its own
parameters** — never a field of the row a rule is guarding. That holds wherever the policy is named, so it means the
same thing on every entity, including one that happens to have a field with the same name as a table the policy reads:

```osy title="the policy reads the table, even beside a field of that name" test app=security-policy-sees-no-row
[Principal] entity Person {
  [Required, MaxLength(200), Unique] string Email;
  security { allow read when IsAuthenticated; }
}

entity Desk {
  [Required, MaxLength(60)] string Name;
  Person? Agent;
  security { allow read when IsAuthenticated; }
}

entity Ticket {
  [Required, MaxLength(120)] string Subject;
  Desk? Desk;   // a field named after the table the policies below read
  security {
    allow read when StaffsADesk;             // `Desk` in the body is the TABLE: "staffs any desk"
    allow update where StaffsThisDesk(Desk); // …and the row's own field is passed in, by name
  }
}

policy StaffsADesk => Desk.Any(d => d.Agent == user);
policy StaffsThisDesk(Desk desk) => Desk.Any(d => d == desk && d.Agent == user);
```

So a rule that needs the row **says so itself**: pass the field as an argument (`where StaffsThisDesk(Desk)`), or write
that part in the rule's own `where`. A body that reaches for a field of a row it guards is an error at the policy,
naming both:

```osy title="refused — the policy cannot see the Ticket it guards" syntax
policy MyTicket => Requester == user;     // ✗ 'Requester' is a field of Ticket, not something a policy can see

policy Requested(Person by) => by == user;
// on Ticket:  allow read where Requested(Requester);    ✓ the row hands it in
//        or:  allow read where Requester == user;        ✓ the rule says it itself
```

**Not the caller's row either.** `user` is how a policy reads the signed-in person — a bare name is never one of their
fields, wherever the policy is used: in a `security { }` rule, in an `[Authorize]`, or called from a function. So a
policy means the same thing even when the caller's own entity has a field named after a table it reads:

```osy title="called from a function, the policy still reads the table — not the caller's own field" test app=security-policy-sees-no-caller-row
[Principal] entity Person {
  [Required, MaxLength(200), Unique] string Email;
  [ForeignKey(Member)] Desk[] Desk;   // the caller's OWN desks — a field named after the table
  security { allow read when IsAuthenticated; }
}

entity Desk {
  [Required, MaxLength(60)] string Name;
  Person? Member;
  Person? Lead;
  security { allow read when IsAuthenticated; }
}

policy LeadsADesk => Desk.Any(d => d.Lead == user);   // ANY desk the caller leads: the table

bool AmILead() {
  return LeadsADesk;
}
```

A field of the caller is written through `user`, and a bare one is refused with that spelling:

```osy title="refused — say whose field it is" syntax
policy IsVerified => EmailVerified;        // ✗ 'EmailVerified' is a field of the caller's 'Person'
policy IsVerified => user.EmailVerified;   // ✓
```

### The rule the engine enforces can be composed too   {#in-a-where}

A `where` may CALL a parameterised policy, passing the row it is about. This is where naming rules stops being tidy
and starts being the difference between a rule you can read and one nobody dares touch.

Take a grant-handling system. Whether a caseworker may read a case is **four independent rules at once**:

- they work for the unit that owns the scheme it was applied under;
- they have not **recused** themselves from this particular case;
- if the case is **restricted**, they are senior enough to see one;
- and they are signed in at all.

Written inline, that is one welded expression — and it has to be repeated on every entity that hangs off a case:
the attachments, the assessments, the notes, the decision, the payment.

```osy title="the welded form — four rules, no names, copied five times" syntax
entity Case {
  security {
    allow read where RoleGrant.Any(g => g.Holder == user && g.Unit == Scheme.OwningUnit)
                  && !Recusal.Any(r => r.Person == user && r.Case.Id == Id && r.Lifted == false)
                  && (!Restricted || RoleGrant.Any(g => g.Holder == user && g.Level == Level.Senior));
  }
}
```

⚠ **The problem is not that it is long — it is that the fifth copy is where a clause goes missing**, and nothing
catches that. It compiles, that entity's own tests pass, and the defect is *"a caseworker read a case they had
recused themselves from"*. Nobody sees it until an audit.

Name each rule once, and the predicate reads like the sentence it enforces:

```osy title="the same rule, composed" test app=security-policy-in-a-where
[Role] enum Level { Caseworker, Senior }

[Principal] entity Person {
  [Required, MaxLength(200), Unique] string Email;
  security { allow read when IsAuthenticated; }
}
entity Unit { [Required, MaxLength(60)] string Name;
  security { allow read when IsAuthenticated; } }
[RoleGrant] entity RoleGrant { [Required] Person Holder; [Required] Level Level; Unit? Unit;
  security { allow read when IsAuthenticated; } }
entity Scheme { [Required, MaxLength(60)] string Name; [Required] Unit OwningUnit;
  security { allow read when IsAuthenticated; } }
entity Recusal { [Required] Person Person; [Required] Case Case; bool Lifted;
  security { allow read when IsAuthenticated; } }

// Each rule, named once. The first two are ABOUT a row, so they take one.
policy HandledByUnit(Unit u)   => RoleGrant.Any(g => g.Holder == user && g.Unit == u);
policy HasRecused(Guid caseId) => Recusal.Any(r => r.Person == user && r.Case.Id == caseId && r.Lifted == false);
policy MaySeeRestricted        => RoleGrant.Any(g => g.Holder == user && g.Level == Level.Senior);

entity Case {
  [Required, MaxLength(40)] string Reference;
  [Required] Scheme Scheme;
  bool Restricted;
  security {
    allow read where HandledByUnit(Scheme.OwningUnit)
                  && !HasRecused(Id)
                  && (!Restricted || MaySeeRestricted);
  }
}
```

**Nothing is given up for that.** A policy INLINES — the persisted predicate is byte-for-byte the welded one above,
so the database does the same work and a named rule costs nothing at run time. What changes is everything around it:

| | welded | composed |
|---|---|---|
| the recusal rule lives in | five places | one |
| renaming `Lifted` | five edits, and a miss compiles | one edit |
| a missing clause on entity five | silent | still silent — **but there is only one clause to miss** |
| reading the rule | parse the expression | read the names |

### A policy can be built from other policies   {#composing}

Once the four rules above have names, the obvious next move is to name their combination — *"may this person work on
this case at all"* is itself a rule, and it is wanted in more than one place. So a policy body may call another
policy, exactly as a `where` may:

```osy title="a policy whose body is other policies" test app=security-policy-composed
[Role] enum Level { Member, Finance, Admin }

[Principal] entity Person {
  [Required, MaxLength(200), Unique] string Email;
  security { allow read when IsAuthenticated; }
}
entity Org { [Required, MaxLength(60)] string Name;
  security { allow read when IsAuthenticated; } }
[RoleGrant] entity Membership { [Required] Person Who; [Required] Org Org; [Required] Level Level;
  security { allow read when IsAuthenticated; } }
entity Project { [Required] Org Owner; [Required, MaxLength(60)] string Code;
  security { allow read when IsAuthenticated; } }

policy IsFinance(Org o) => Membership.Any(m => m.Who == user && m.Org == o && m.Level == Level.Finance);
policy IsAdmin(Org o)   => Membership.Any(m => m.Who == user && m.Org == o && m.Level == Level.Admin);

// The composite, named once — and then used like any other policy.
policy MayApprove(Org o)      => IsFinance(o) || IsAdmin(o);
// It may pass a DIFFERENT row than its own parameter, and reach across a relation to find one.
policy MayTouch(Project p)    => MayApprove(p.Owner);
// …and it composes further, to any depth.
policy MayTouchButNotSpend(Project p) => MayTouch(p) && !IsFinance(p.Owner);

entity Invoice {
  [Required] Project Project;
  [Required, MaxLength(40)] string Reference;
  security { allow read where MayTouch(Project); }
}
```

The argument you write is spliced into the policy you called, so `MayApprove(p.Owner)` asks about **that project's
org** and not about anything else in scope. As with a `where`, nothing is given up: the persisted predicate is the
same welded expression either way.

⚠ **The one thing a policy may not do is reach itself.** `A(o) => B(o)` together with `B(o) => A(o)` is refused, and
so is `A(o) => A(o)`; the message names each policy on the loop, at its own declaration, with the route it takes back
to itself (`A → B → A`). A policy is inlined into every rule that names it, so a cycle has no predicate to inline.

⚠ **And the argument has to be the right kind of row.** Handing a `Project` to a policy that is about an `Org` is
refused at the argument you wrote, naming both types and the relation that gets you from one to the other — *"policy
'IsFinance' is about an Org (its parameter `o`), and `p` is a Project. Pass the Org it belongs to: `p.Owner`."*

### And a query says nothing about it   {#in-a-query}

The rule lives on the entity, so a query over `Case` is just a query. It filters for what the caller is *looking
for*; who the caller is allowed to *see* is already inside the statement the compiler emits, composed from the four
rules above. Nothing here restates a grant, names a role, or checks anything:

```osy title="a query about cases, and not one word about who may see them" test app=security-policy-in-a-where
List<Case> RestrictedUnder(string scheme) {
  return Case.Where(c => c.Restricted && c.Scheme.Name == scheme)
             .OrderBy(c => c.Reference)
             .ToList();
}
```

A caseworker outside the unit gets an empty list, not an error; a recused one does not see the case they recused
from; a junior one sees no restricted case at all — and the function above is the same function for every one of
them. That is the point of putting the rule on the entity: there is no call site that could have forgotten it.

⚠ **Pass the ID, not the row.** `HasRecused(Id)` — a row predicate has no `this`, and the compiler will tell you so
(that is a fact about predicates, not about policies: the welded form cannot say `this` either).

### `when` or `where` — the argument decides   {#when-or-where}

A rule has two clauses: `when` asks who the **caller** is, once per request, with no row in hand; `where` narrows
**rows**, one at a time. A parameterised policy called with one of this entity's own members can only be about the
row — `IsAdmin(Organization)` written on `RoleGrant` means *this row's* `Organization`, and nothing else a C# reader
could take it for — so it is a row rule **whichever word you wrote**. Both of these compile, and they compile to the
same rule:

```osy title="organisation-scoped access, said either way" test app=security-policy-in-a-when
[Role] enum OrgRole { Admin, Member }

[Principal] entity User {
  [Required, MaxLength(200), Unique] string Email;
  security { allow read when IsAuthenticated; }
}
entity Organization { [Required, MaxLength(80)] string Name;
  security { allow read when IsAuthenticated; } }
[RoleGrant] entity Membership { [Required] User User; [Required] Organization Organization; [Required] OrgRole Role;
  security { allow read when IsAuthenticated; } }

// "is an admin of THIS organisation" — a fact about the caller AND a row.
policy IsAdmin(Organization o) => Membership.Any(m => m.User == user && m.Organization == o && m.Role == OrgRole.Admin);

entity RoleGrant {
  [Required] Organization Organization;
  [Required, MaxLength(40)] string Label;
  security {
    allow read when IsAdmin(Organization);                    // read: this row's Organization
    allow create, update, delete where IsAdmin(Organization); // the same rule, the other word
  }
}
```

The `create` line is a **with-check**: a new `RoleGrant` may only be written for an organisation the caller
administers, decided against the row being created. A `when` that mixes a caller fact with a row fact —
`when IsAuthenticated && IsAdmin(Organization)` — keeps the caller half as the `when` and makes the row half the
`where`. And `osy explain` reads either spelling as the one sentence it is: *"only rows of an Organization where the
caller's Membership has Role = Admin"*.

What the compiler still refuses, at the rule, naming what you passed and what would work:

- **A field-level rule's `when`** — `deny read Label when IsAdmin(Organization)` — cannot be about the row: a
  `when` guard runs once per request, before any row is in hand. Guard `when` on the caller alone, or write this
  rule's OWN `where` instead — a field mask may carry a row-scoped condition, exactly like a row rule's
  (`deny read Label where IsAdmin(Organization);`, [[security-entity-security#field-mask-row-scoped]]).
- **An entity that is not a member** — `IsAdmin(Organization)` on an entity with no `Organization` property. There
  the name IS the entity set (every `Organization`), which a policy taking one row cannot take; the refusal says so
  and offers both ways out: pass a specific row, or add the relation and write the row rule.

### Two packages, one name: say which   {#namespaced}

`IsAdmin` and `IsSalesPerson` are exactly the names two publishers both pick, so a policy's name carries the
namespace it was declared in. An app that installs two packages which each declare `IsAdmin` names the one it means
in full:

```osy title="two packages both say IsAdmin — name the one you mean" syntax
security { allow read when Acme.Crm.IsAdmin; }   // …and `Other.Kit.IsAdmin` is a different rule
```

The bare name keeps working wherever only one declaration answers to it, which is nearly always. **When more than
one does, the bare name is an error** naming every candidate — a security rule may not be decided by which file was
read first. Declaring the same name in two packages is not itself a problem; only using it unqualified is.

**The app's own policy wins its bare name in the app's own code**, as a declaration in C#'s enclosing namespace beats
one a `using` imports. An app that declares `policy IsAdmin => …;` and uses a package that declares its own `IsAdmin`
means its own wherever it writes `IsAdmin`, and reaches the package's by its full name:

```osy title="the app's own IsAdmin bare, the package's by its full name" syntax
policy IsAdmin => RoleGrant.Any(g => g.Member == user && g.Role == AppRole.Admin);
security {
  allow read when IsAdmin;                 // this app's own
  allow update when Acme.Crm.IsAdmin;      // the package's
}
```

Inside the PACKAGE, the bare name the app shares is still an error — the package's rule would otherwise enforce the
app's policy — so a package names its own by its full name, or picks a name an app is unlikely to share.

### How does a package say "staff" without knowing the app's roles?   {#supplied}
A package's rules sometimes have to say WHO in the app's own terms — a conversations kit must keep internal notes
from everyone who is not staff, and staff are the app's Agent and Lead, roles the package has never seen. The package
declares the policy with **no body**, and the app that uses it **defines** it:

```osy title="the package asks — no body" syntax
namespace Acme.Desk;

/// Who works the desk — the app's to say.
policy IsDeskStaff;

[Authority(Extensible)]
public entity Entry {
  bool Internal;
  security { deny read when !IsDeskStaff where Internal; }
}
```

```osy title="the app answers — any definition over its own data" syntax
policy IsDeskStaff => RoleGrant.Any(g => g.Member == user && (g.Role == AppRole.Agent || g.Role == AppRole.Lead));
```

Every reference — the package's rules and the app's alike — then means the app's definition. It is compile-checked and
cannot change at runtime, which a table of staff roles the app seeds could. An app that leaves it out is refused by
name (`` `Acme.Desk` needs this app to define `policy IsDeskStaff` ``), and so is a definition whose parameters
differ from the declaration. The bodyless form is package vocabulary: in an app's own source it is refused — the app
writes the body.

The answer may be **another package's policy**. A shop answers the site package's question with the shop's own staff,
in one line:

```osy title="one package's question, answered by another package's policy" syntax
policy ArrangesPages => IsShopStaff;   // Osysharp.Blocks asks; Osysharp.Shop says who staff are
```

### A package's question with a default answer   {#supplied-default}
A package that adds a NEW question to a released version would break every app that uses it, if the app had to answer
before it compiled again. `virtual` gives the question a default — the answer every app gets until it says otherwise:

```osy title="the package asks, and answers for an app that does not" syntax
namespace Acme.Mailboxes;

/// Who manages the mailboxes — the app's admins, unless the app says otherwise.
virtual policy ManagesMailboxes => IsAdmin;

[Authorize(ManagesMailboxes)]
public void StopWatching(string address) { … }
```

An app that defines `policy ManagesMailboxes => …;` decides, exactly as for a bodyless policy. An app that does not
gets the default as its own definition — lowered in the app, so `IsAdmin` there is the app's own `IsAdmin`. An app
that defines neither is refused naming both ways out: define `ManagesMailboxes`, or the `IsAdmin` its default names.
Like the bodyless form, `virtual policy` is package vocabulary, and an app's own source is refused it.

A default may be `false` — **nobody, until the app says**. That is the shape for a question every app need not answer:
Osysharp.Blocks asks `virtual policy ArrangesPages => false;`, so a shop that never arranges its pages compiles with
no line at all, and one that does answers once. A `where` written over such a question —
`allow read when IsAuthenticated where OwnsListAddress(Email)` over `virtual policy OwnsListAddress(string email) =>
false;` — admits no row until the app answers; it is not refused as a `where` that never reads the row
([[security-when-and-where#refused]]).

A default may be **another question** the package asks. It then takes that question's answer — the app's if it gave
one, else that question's own default — so an app that defines neither still compiles:

```osy title="one question defaults to another" syntax
namespace Acme.Desk;

/// Who opens the desk. Nobody, until the app says.
virtual policy OpensDesk => false;
/// Who pauses it: whoever opens it, until the app says otherwise.
virtual policy PausesDesk => OpensDesk;
```

| the app writes | `PausesDesk` admits |
|---|---|
| nothing | nobody (`OpensDesk`'s default) |
| `policy OpensDesk => IsOpener;` | the openers |
| `policy OpensDesk => IsOpener;` and `policy PausesDesk => IsCloser;` | the closers |

Only a default naming something that has **no** answer anywhere is refused — a policy of the app's the app never
wrote (`IsAdmin`, above), or a bodyless question. Defaults that lead back to each other (`virtual policy A => B;`
with `virtual policy B => A;`) are refused when the app is compiled, naming the ring, unless the app answers one of
them.

### A package's own policy over the questions it asks   {#over-the-answers}
A package can ask two questions and name their combination once. The combination is the **package's** policy — an app
cannot replace it — and it means the app's answers everywhere it is named:

```osy title="two questions, and the package's policy over them" syntax
namespace Acme.Desk;

virtual policy OpensDesk => false;
virtual policy ClosesDesk => false;

/// Who runs the desk: whoever opens or closes it.
policy RunsDesk => OpensDesk || ClosesDesk;

[Server, Authorize(RunsDesk)]
public string Run() { … }

public entity Ticket {
  security { allow read when RunsDesk; }
}

public component DeskPanel() {
  render { if (RunsDesk) { Text("you run the desk"); } }
}
```

| the app writes | `RunsDesk` admits |
|---|---|
| nothing | nobody |
| `policy OpensDesk => IsOpener;` | the openers |
| `policy ClosesDesk => IsCloser;` | the closers |
| both | the openers and the closers |

The function's `[Authorize]`, the rule, a page's `[Authorize(RunsDesk)]`, `if (RunsDesk)` in a body and the control on
screen all give the same answer. A question left at a constant default is reflected on a control as that constant: a
control behind `false` is never drawn. Two answers may name the same policy of the app's (`policy OpensDesk =>
IsOpener;` beside `policy ClosesDesk => IsOpener || IsCloser;`).

### Two packages ask the same question: answer each by name   {#answer-one-kit}
Two packages may both ask `Allowed` — a credits package and a sessions package, each with its own `virtual policy
Allowed => …;`. A bare answer answers **every** package that asks the name. To answer **one** package, name it — the
way C# implements one interface's member explicitly:

```osy title="one answer for one package, the bare one for every other" syntax
policy Crux.Credits.Allowed => Membership.Any(m => m.Member == user && m.Paid);  // only Crux.Credits asks this one
policy Allowed => IsAuthenticated;                                              // every other package asking `Allowed`
```

Each package's own rules, `[Authorize(Allowed)]` functions and pages then get the answer that is theirs. A package you
answer neither way keeps its default. The app's own code names an answer the same way: `[Authorize(Crux.Credits.Allowed)]`,
or `if (Crux.Credits.Allowed) { … }`.

Two refusals keep this honest. Two packages that **default** the same name **differently**, with no answer from the
app, are refused naming both (`` `Crux.Credits.Allowed` and `Crux.Sessions.Allowed` each ask this app `Allowed` and
default it differently ``) — otherwise whichever package was read first would decide for the other. Two packages with
the same default share it. And an answer naming a package that does not ask that question is refused, listing the
packages that do.

### What the compiler will not let you write   {#refusals}

- **Naming a parameterised policy without its argument** — `if (!CanManageTeam)` — is an error. There would be nothing
  for it to decide about, and a rule that quietly decides about nothing denies people who should be allowed.
- **Giving the wrong number of arguments** is an error, naming what the policy declares.
- **A lambda variable that shadows the policy's own parameter** — `CanManageTeam(Team t) => …Any(t => …)` — is an
  error. One name would mean two different rows.
- **Gating a page on a parameterised policy** — `[Authorize(CanManageTeam)]` — is an error. A page gate runs before any
  row is loaded, so there is no argument to give it. Gate the page on a plain policy and check the row inside.
- **A policy that reaches itself** — `A(u) => B(u)` with `B(u) => A(u)`, or `A(u) => A(u)` — is an error. There is
  no fixed predicate to inline, and a rule with no fixed meaning cannot be enforced. Each policy on the loop is named
  at its own declaration, with the route back to itself. Composition that does NOT loop is fine — see [[#composing]].
- **Handing a policy the wrong kind of row** is an error at the argument, naming both types and the relation that
  reaches the wanted one.
- **A bare policy name that two declarations answer to** is an error naming both qualified spellings — see
  [[#namespaced]]. Write the one you mean in full.
- **A policy body that reads a field of a row it guards** — `policy MyTicket => Requester == user;` named on a `Ticket`
  — is an error at the policy. A policy sees no row; pass the field in, or put that part in the rule's `where` — see
  [[#no-row]].
- **A policy body that names one of the caller's fields bare** — `policy IsVerified => EmailVerified;` — is an error
  at the policy, naming `user.EmailVerified`. The caller is `user`, not a row the body stands in — see [[#no-row]].

## Examples       {#examples}

```osy title="the same policy, in all three places" test app=security-naming-a-policy
entity Memo {
  [Required] Team Team;
  [MaxLength(200)] string Note;
  security {
    allow read when IsAuthenticated;
    // Declared authority: the POLICY itself, passing the row it is about. This block used to spell the rule out
    // again by hand — the page claimed the policy was used in all three places while one of them was a copy.
    allow update, delete where CanManageTeam(Team);
  }
}

// …and named in code, for the authority the engine cannot carry for you — an elevated operation, an external call,
// anything where the decision is not a row read.
bool MayManage(Guid teamId) {
  var team = Team.Where(t => t.Id == teamId).FirstOrDefault();
  return CanManageTeam(team);
}
```

## See also       {#see-also}

[security { }](https://osysharp.com/reference/security/entity-security/) — declaring who may read and write an entity's rows.

[principal predicates (IsAuthenticated / IsAnonymous / IsTheApp) and open reads](https://osysharp.com/reference/security/principal-predicates/) — `IsAuthenticated` and the other built-in facts about the caller.

[secure by default (deny-all)](https://osysharp.com/reference/security/secure-by-default/) — why everything is denied until a rule grants it.
