Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / Security

capability rows that belong to a user

using Osyrin.Agents; // its rows are owner-scoped to your [Principal] — declare one

Some capability tables hold rows that belong to one signed-in user — a chat conversation is yours, not the app's. Those capabilities scope every read and write to your app's `[Principal]` automatically, with no rule for you to write. The one thing they need from you is a `[Principal]` entity to scope to: an app that imports such a capability and declares none is a compile error, because a row owned by a user has no meaning in an app with no users.

stable1 example compiled by CIsecurityauthorizationcapabilitiesownership

Summary#

A capability can ship tables whose rows belong to one signed-in user. Conversation memory is the clearest case: a chat session is yours, and another user of the same app must not be able to list or open it.

Those capabilities carry that rule themselves. You do not write it, you cannot forget it, and it holds wherever the read comes from — a page, a function, an MCP tool. What the capability cannot supply is who the users are: that is your app's [Principal] entity. So the one requirement is that you declare one.

using Osyrin.Agents;

[Principal] entity User { string Email; }     // ← the only thing the capability needs from you

// Nothing else. Every ChatSession read and write is already scoped to the signed-in user.

Signature#

using Osyrin.Agents;                     // a capability whose rows are owner-scoped
[Principal] entity User { … }                 // REQUIRED — the type its rows are scoped to

Importing such a capability without a [Principal] is refused at compile time, naming the capability and both ways out:

`using Osyrin.Agents;` brings in 'ChatSession', whose `UserId` belongs to a signed-in user — and this app
declares no `[Principal]` entity, so there is no user for it to belong to. Either declare one
(`[Principal] entity User { … }`), or drop `using Osyrin.Agents;`.

Description#

Under secure by default (deny-all) a table nobody has granted access to is denied to everyone. A capability's owner-scoped tables come with their grant already written — this row is reachable by the principal it belongs to — so opting in gives you a working, private store rather than a locked one.

Three consequences worth knowing:

  • The rule is on the entity, not on the route. It is enforced by the read engine, so a conversation you do not own is absent from a query, not merely hidden by whichever endpoint you went through. Reaching the same table from a function or a tool gets the same answer.
  • An unowned row is reachable by nobody. If a row is written with no owner, it does not match the rule for any principal — it is not shared, it is stranded. Anonymous callers own nothing, so a feature that must work signed-out needs a different store.
  • You can grant MORE, never less. A partial entity block of your own lands alongside the capability's rule and the grants combine, so you can add a support role that reads every conversation. You cannot use it to take the owner's access away.

That combining rule is specific to this mechanism. A row that is part of another row (rows that are part of another row) composes the other way: declaring a block there REPLACES the derived rule rather than adding to it. The two differ because the questions do — a capability's own grant is a promise it makes about its rows and yours cannot revoke it, while a derived rule is only a default standing in for a decision you had not made yet.

// Add to what the capability already grants — the owner's access stays.
partial entity ChatSession {
  security { allow read when user.IsSupportAgent; }
}

Examples#

An app that opts into conversation memory. There is no security block for ChatSession anywhere in it — the capability brought its own, and the [Principal] is what it scopes to:

app Helpdesk { use Osyrin.Agents; }

using Osyrin.Agents;

// The capability needs a principal type to scope its rows to. Without this the compile is refused.
[Principal] entity User { string Email; }

entity Ticket {
  string Subject;
  security { allow read, create where Reporter == user; }
  User Reporter;
}

See also#

Related

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…

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…

Reading a capability's source

Prints the source of a capability you opted into with `using`. A capability's source ships inside the platform rather…

principal predicates (IsAuthenticated / IsAnonymous) and open reads

Two built-in `when` predicates say who a request is: `IsAuthenticated` is a signed-in user, `IsAnonymous` is an…