Osy#betaRun it on Osyrin: osyrin.com

Reference / Security

adding grants to your own entity from another file

partial entity Order { security { allow read when IsZoneAgent; } }

A `security { }` block lives inside its entity's declaration, so a build shape that ships a subset of your files cannot add a grant — the entity is in a file both shapes need. A `partial entity` over your OWN entity solves it: a second file states extra access for an entity declared elsewhere. The grants add; they can never relax what the declaration denied, because the runtime folds policy rows so `allow` rules OR while the defaults take the more restrictive answer.

preview1 example compiled by CIsecurityauthorizationpartial

Summary#

A grant is written inside its entity's security { } block. That is the right default — one entity, one place to read its rules — and it has one case it cannot serve: an app that builds into more than one shape, where some grants belong to only one of them.

partial entity X { security { … } } over the app's own entity is the answer. A second file states extra access for an entity declared elsewhere, and the file can be one a given build never compiles.

Signature#

// shared file — every shape compiles this one
entity Order {
  [MaxLength(40)] string Code;
  security { allow read when IsAuthenticated; }
}

// shape-specific file — only some builds compile this one
partial entity Order {
  security { allow read, update when IsZoneAgent; }
}

Description#

Why a folder cannot do this on its own#

Splitting an app by build shape is ordinary file selection: put the files one shape does not want in a subfolder and let its manifest glob non-recursively. Whole declarations move cleanly that way — an entity, a function, a page.

A grant does not, and the reason is structural rather than awkward. The grant lives inside the entity's declaration, and the entities that need shape-specific access are exactly the ones both shapes need. There is no file you can put the grant in that the smaller build will not read, because the grant is not in a file of its own — it is three lines inside a declaration that has to be shared.

What adds, and what cannot#

An entity accumulates several policy rows. Its own declaration writes one; a partial writes another; capability ownership and derived access write more. The runtime folds them with two different rules, and the asymmetry is the whole safety story:

  • allow rules OR together. A partial can grant access the declaration did not.
  • The operation DEFAULTS take the more restrictive answer. A second policy may only ever narrow what another left open.

So a partial can add a grant and can never relax one. You do not have to read every file to know the floor: the declaration's own block is the floor, and nothing in another file can lower it.

One partial per entity#

Two partial entity Order blocks in one app are refused. Each would rebuild the same policy row wholesale, so the second would silently win and the first's rules would vanish — a refusal is better than picking. Put the shape's grants in one block.

A partial states security and nothing else#

A partial may carry a security { } block and [SuppressWarning]. It may not declare members or invariants. To relate your entity to something else, declare the reference on the entity that should carry it.

Examples#

[Role] enum AppRole { Authenticator, Member }
[Principal] entity User {
  [Unique, MaxLength(200)] string Email;
  security { allow read when IsAuthenticated; }
}
[RoleGrant] entity RoleGrant { User Grantee; [Required] AppRole Level;
  security { allow read when IsAuthenticated; } }
policy IsMember => RoleGrant.Any(g => g.Grantee == user && g.Level == AppRole.Member);

entity Order {
  [MaxLength(40)] string Code;
  security { allow read when IsAuthenticated; }
}

// In a real app this block lives in a file the smaller build does not glob.
partial entity Order {
  security { allow read, update when IsMember; }
}

An Order is readable by any signed-in user in both shapes, and updatable by a member only in the shape that compiles the second file. The allow read in the partial is redundant beside the declaration's — harmless, because the rules OR.

See also#

Related

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…

capability rows that belong to a user

Some capability tables hold rows that belong to one signed-in user — your own allowance is yours, not the app's. Those…

[Authority]

What an app that imports your entity may do to the `security { }` you declared on it. The default is `Narrowable` — the…

secure by default (deny-all)

Deny-all is the posture, and it is the only one: an entity that declares no `security { }` block is denied to every…