---
title: Policies
editUrl: https://github.com/Cratis/Screenplay/edit/main/Documentation/screenplay/policies.md
description: A policy accepts one quoted or fenced-text description body line, printed first before its implementation. It is report-only authoring metadata…
---

A policy accepts one quoted or fenced-text `description` body line, printed first before its implementation. It is report-only authoring metadata (`PLAY0270`), not a requirement: a description-only policy still needs `require`, a file or code. Markdown `documentation` is not supported here.

Policies are named authorization rules. Modules, features, commands, and queries reference them by name with `authorize`. Policies support role-based, claim-based, and fully custom logic. Authorization gates on enclosing scopes narrow access; they never replace a gate closer to a command or query.

## Syntax

```screenplay
policy <Name>
  require <condition>
```

Write one `require` line. Its `<condition>` can be `authenticated`, `role "<role>"`, `claim "<claim>" matches <subject|"value"|expression>`, `not <condition>`, or a combination such as `role "<role>" or (role "<role>" and claim "<claim>" matches "<value>")`. Continue a condition at deeper indentation rather than starting another `require` line; a second line reports `PLAY0441`.

Alternatively, implement the policy in an inline block:

````screenplay
policy <Name>
  ```csharp
    <C# returning bool>
    ```
````

Instead of the inline `csharp` block, a policy can name its implementation with `file <path>`. A policy may have a `require` condition or one implementation (`file` or inline code), never both. Mixing `require` with an implementation reports `PLAY0440`.

## Authorization scopes

Declare `authorize <policy expression>` directly in a module or feature body, or on a command or query. Each gate can use policy names joined by `and` and `or`, with parentheses for grouping. Names written next to one another mean `and`. Every gate along the path to a command or query must pass: its own gate **and** every enclosing feature's gate (including nested features) **and** the module's gate. This is the same AND rule used for several policies on one construct. An `or` inside a gate does not bypass any enclosing gate.

```screenplay
policy HasPortalAccess
  require authenticated

policy CanManageOrders
  require role "OrderManager"

policy IsRegionalManager
  require role "RegionalManager"

module Portal
  authorize HasPortalAccess
  feature Orders
    authorize CanManageOrders or IsRegionalManager
    feature Returns
      slice StateChange ReturnOrder
        command RequestReturn
          authorize CanManageOrders
```

`RequestReturn` requires `HasPortalAccess` **and** (`CanManageOrders` **or** `IsRegionalManager`) **and** `CanManageOrders`. A feature without its own gate still inherits its ancestors' gates. When a folder declares the same module or feature gate in different files, distinct gates accumulate with AND; identical repeated gates are reported and kept once. Repeated `authorize` lines on a command or query combine with AND in authored order; printing writes them as one `authorize A and B` line. The printer keeps a gate where it was declared rather than copying inherited gates onto each command or query.

The source compiler resolves policy names at all four positions and warns about unknown names. Portable conditions bind to the ESM and run in the reference evaluator. A custom implementation binds as an opaque ESM v3 policy predicate; it requires a target provider to evaluate it.

## Conditions

| Condition | Meaning |
| --- | --- |
| `authenticated` | The caller must be authenticated. |
| `not <condition>` | The operand must be false; parentheses can group its operand. |
| `role "<role>"` | The caller must have the role. |
| `claim "<claim>" matches subject` | The claim must match the subject of the current event source. |
| `claim "<claim>" matches "<value>"` | The claim must equal that literal value. |
| `claim "<claim>" matches <expression>` | The claim must equal the value the expression resolves to. |

A quoted target is the value itself; anything unquoted - a path such as `invoice.department`, or a `$` rooted expression such as `$context.tenant` - names where the value is read from. Quote a literal, leave an expression unquoted; the two are distinct in the syntax tree, so a tool consuming a policy always knows which one it is looking at.

Conditions combine with `or` and `and`, and parentheses group them. A condition may continue on the next line at deeper indentation.

`not` binds tighter than `and`, which binds tighter than `or`; `and` and `or` are left associative - the rule a general purpose language follows, so `a or b and c` means `a or (b and c)` and `a or b or c` means `(a or b) or c`. Parentheses override that. This is the one condition grammar the language has: a `produces when` condition combines by exactly the same rules, over comparisons instead of roles and claims.

Printing writes the parentheses back wherever the grouping is not the one those rules produce, so a printed policy always compiles to the condition it came from. It also writes them where mixing `or` and `and` would otherwise leave a reader to work the precedence out - the text says which grouping it means rather than assuming you know.

To allow signed-in people but exclude service identities, negate their role and claim match:

```screenplay
policy PersonOnly
  require authenticated and not role "Service" and not claim "actorKind" matches "service"
```

A caller with either the `Service` role or an `actorKind` value of `service` is denied. A missing claim has no match, so its negation is true. `not role "Service"` alone does **not** require authentication; keep `authenticated` when you need a signed-in caller. `not (role "Service" or claim "actorKind" matches "service")` negates the whole group, while `not not authenticated` restores the original condition. `not` belongs to policy conditions, not named-policy expressions in `authorize`, and cannot negate an inline or file implementation.

## Examples

```screenplay
policy IsAuthenticated
  require authenticated

policy IsAccountant
  require role "Accountant"

policy CanViewSensitiveFinancials
  require role "FinanceDirector"
    or role "Auditor"

policy IsCustomerSelf
  require claim "customerId" matches subject

policy IsFinanceDepartment
  require claim "department" matches "Finance"

policy CanManageInvoice
  require role "InvoiceManager"
    or (role "Accountant" and claim "department" matches invoice.department)
```

`IsFinanceDepartment` compares against the literal text `Finance`. `CanManageInvoice` allows an `InvoiceManager`, or an `Accountant` whose department matches `invoice.department`. Its parentheses make the grouping explicit but do not change it: `and` already binds tighter than `or`.

## Portable evaluation

Positive declarative policies execute in the portable ESM v1 reference evaluator. Policy-condition negation selects ESM v7 as a byte-preserving extension ([decision 0027](https://github.com/Cratis/Screenplay/blob/main/decisions/0027-policy-negation-joins-esm-v7.md)); models without it keep their existing versions, bytes and revisions. Pre-extension v7 strict readers reject the new `not` variant. A consumer must explicitly support negation before reading, executing or rendering a model that uses it. Inline `csharp` and `file` policy predicates bind to ESM v3 with a stable requirement id, context/result contract version 1, and the required capability `pure`. The compilation result's implementation requirement carries the inline body's exact source span and dedented line map, or a resolved file's whole-file span; canonical ESM JSON still carries only the requirement id. The reference evaluator cannot execute their bodies. An authorization that depends on one returns `SemanticUnsupported` with the Authorization capability and the policy name; it never guesses allow or deny. A specification asserting `then denied` does not pass on this unsupported outcome. A provider must admit and evaluate the implementation before it can decide authorization.

For a mixed gate, evaluation follows authored order: a portable denial on the left of `and` denies without evaluating the right, and a portable allowance on the left of `or` allows without evaluating the right. If an opaque operand is reached first, the reference evaluator returns unsupported; a later portable operand cannot decide the result. Enclosing gates combine in module → feature → construct order. This matches Stage's generated C# `&&`/`||` expression (`SemanticPolicyArtifactRenderer.Authorization`) and Arc's ordered, short-circuiting policy loops (`ArcAuthorizationPolicyRuntime.IsAuthorized` and `AspNetAuthorizationPolicyRuntime.IsAuthorized`), following the runtime-authority boundary in [decision 0001](https://github.com/Cratis/Screenplay/blob/main/decisions/0001-chronicle-runtime-semantic-authority.md). An absent caller still denies authorization. The execution request must supply a caller explicitly when an authorized command or query runs. A missing caller cannot satisfy authorization. An authenticated condition checks the caller's authentication flag; a role compares the caller's roles by ordinal, case-sensitive text. Claim **types** compare ordinal-ignore-case, while claim **values** compare ordinal, case-sensitively. If a caller carries several values for the same claim type, **any** matching value satisfies that condition. A missing caller claim makes a comparison against a known text target false, so its negation is true. A comparison target that is missing, null, an absent subject, or not text instead makes the condition **unknown**. `not unknown` remains unknown, including under double negation; a policy whose final condition is unknown denies. Conditions use three-valued Kleene logic: `true or unknown` is true, `false or unknown` is unknown, `false and unknown` is false, and `true and unknown` is unknown (in either operand order). Thus a satisfied role alternative can still allow access when another target is undecidable. Positive policies keep their existing allow/deny outcomes. Opaque predicates cannot be nested under negation: unsupported execution is never converted to false or permission. `and` and `or` retain authored-order short-circuiting when the left operand decides the result.

In the portable ESM v1 profile, an artifact path must resolve from a command property (including declared composite members) or the keyed query argument. An absent or null nested value makes its claim comparison unknown. `$`-rooted expressions remain valid authoring syntax but do not bind to this portable profile. A `subject` comes from a command's identifier property or a keyed query's argument; if no identifier is available, it cannot match. When binding an authorized command or query, the compiler warns with `PLAY0546` if a claim under `not` (including inside a group) targets an optional path, a command's `subject` without an identifier, or a non-string type. The warning does not prevent binding; check that the target is available and text when the policy runs. The TypeScript syntax compiler does not perform this semantic binding check.

A failed effective authorization yields `Unauthorized` before command validation or query lookup and never changes the world. For the caller fixture and denial assertion, see [Specifications](/screenplay/specifications/#rejections).

## Custom logic

When the declarative conditions cannot express the rule, drop into C#. The block answers with a `bool` — the same answer `require authenticated` gives, so a policy written in code and a policy written in conditions mean the same kind of thing and compose the same way:

````screenplay
policy IsAdultCustomer
  ```csharp
    var dateOfBirth = context.Identity.ClaimValue("dateOfBirth");
    return dateOfBirth is not null && DateTime.TryParse(dateOfBirth, System.Globalization.CultureInfo.InvariantCulture,
      System.Globalization.DateTimeStyles.AssumeUniversal | System.Globalization.DateTimeStyles.AdjustToUniversal,
      out var born) && born <= context.Occurred.UtcDateTime.AddYears(-18);
    ```
````

The same custom logic can live in a repository-relative file instead of an inline block:

```screenplay
policy IsAdultCustomer
  file Policies/IsAdultCustomer.cs
```

`file` is the *implemented by* alternative: the file provides the same `PolicyContext` and `bool` answer as the inline block, not a location for generating a declaration. The compiler does not resolve the path; an absolute path warns with `PLAY0264`.

There is deliberately no result type carrying a denial reason. The declarative half has no way to say *why* a `require` failed either, and adding one only to the code half would split the language into two kinds of policy — one that can explain itself and one that cannot. A policy answers whether the caller may act; the message a user sees is a runtime's concern, and `validate` is where the language attaches messages to rejections.

## What the code can see

`context` is the `PolicyContext` — the caller, and what the decision is about:

| Member | Value |
| --- | --- |
| `context.Identity` | The caller: `Id`, `Name`, `UserName`, `IsAuthenticated`, `Roles`, `Claims`. |
| `context.Artifact` | The command being authorized, or the arguments of the query. What a `matches <path>` condition resolves against. |
| `context.Subject` | The identifier of the thing being acted on. What `matches subject` compares the claim to. |
| `context.Tenant` | The tenant the authorized command or query is executing for. |
| `context.Occurred` | When the authorized command or query was received. |

`Identity` answers the same questions the conditions ask, so the two forms read alike:

```csharp
context.Identity.IsAuthenticated              // require authenticated
context.Identity.HasRole("Accountant")        // require role "Accountant"
context.Identity.ClaimValue("department")     // require claim "department" matches …
```

A policy is given an `Identity` and not a `CausedBy`: it decides what a caller may do and needs what they can prove, and it records nothing. See [Contexts](/screenplay/context/) for the full set and for how a rule's context deliberately differs.

See [Commands](/screenplay/commands/#authorization) and [Queries](/screenplay/queries/) for how policies are applied.
