policy.call_ungranted
An action listed under actions: calls another action that refuses some of the people the listed action's line lets in.
Area 9, Policy; declinable; the next step is usually the human's.
Why Jadpo refuses it
A line under actions: says who may run an action, and it replaces the
entities' grants only on the creates, updates, deletes and transitions in
that action's own body. An action it calls keeps its own grants, with the
same caller. So the called action can refuse some of the people the line
lets in, and they get 403 not_permitted part way through.
Jadpo won't choose the fix for you. Each one decides who may make the change: everyone the line names, everyone on every path to the called action, or only some callers, on purpose.
What it looks like
main.jadpo:
type Handle = Text { max_length: 40}type StaffRole = Enum { librarian}entity Member { id: Uuid handle: Handle { unique: true } policy: { read: [id] }}principal { user: { subject: Member.handle }}entity Staff { id: Uuid member_id: Member.id { on_delete: OnDelete.cascade } role: StaffRole membership: { scope: MembershipScope.application member: member_id role: role } policy: { read: [member_id] }}entity Book { id: Uuid title: Handle borrower_id: Member.id? { on_delete: OnDelete.restricted } policy: { create: [StaffRole.librarian] read: [Access.authenticated] update: [StaffRole.librarian] }}/// Lends a book to a member, as staff do at the desk.action lend(book_id: Book.id, member_id: Member.id) fails BookNotFound, MemberNotFound -> Book { return attempt Book.update(book_id) { set: { borrower_id: member_id } }}/// Borrows a book for whoever is signed in.action borrow(book_id: Book.id, caller: Principal.user) fails BookNotFound, MemberNotFound -> Book { return attempt lend(book_id, caller.id)}policy { actions: { borrow: [Access.authenticated] }}
error borrow calls lend, which refuses some of the people borrow's line lets in
--> main.jadpo:69:20
|69 | return attempt lend(book_id, caller.id) | ^^^^^^^^^^^^^^^^^^^^^^^^ refuses some callers `Access.authenticated` lets in
- reason
borrow's line underactions:covers only the creates, updates, deletes and transitions inborrow's own body;lendkeeps its own grants, with the same caller.lendchangesBookrecords, which onlyStaffRole.librarianmay, so some callersAccess.authenticatedlets in get 403not_permittedfrom it.- next
- Make the change in
borrow's own body, or listlendunderactions:for exactly the peopleborrow's line names, or writedecline policy.call_ungranted: "<why>"above it if this is deliberate, which a person then accepts (human) - rule
- policy.
call_ ungranted - help
- https://jadpo.
dev/docs/errors/policy. call_ ungranted
(human) at the end of the next: line tells your coding agent to stop
and ask you which fix you want, because the choice is yours to make: it
decides who may lend a book.
borrow is listed for anyone signed in, but the Book change is made in
lend, which only StaffRole.librarian may make. What the called action
refuses is followed through everything it does: its creates, updates and
deletes, a field's own grants where they narrow the record's, the line of
a listed action or query it runs, and the same for every action it calls
in turn.
How to fix it
The question is who should be able to make the change the called action makes.
If everyone borrow's line names may make it, only through borrow,
make the change in borrow's own body, where its line applies:
action borrow(book_id: Book.id, caller: Principal.user) fails BookNotFound -> Book { return attempt Book.update(book_id) { set: { borrower_id: caller.id } }}
Anyone signed in may borrow a book for themselves through borrow.
lend is unchanged, so only librarians may lend a book to someone else.
If they may make it through lend too, list lend under actions:
for exactly the people borrow's line names:
policy { actions: { borrow: [Access.authenticated] lend: [Access.authenticated] }}
Anyone signed in may now run lend, on every path that reaches it, a
route of its own included. So any member may lend a book to anyone, not
only to themselves. Adding the line widens who may change a Book, so
it also needs a person's acceptance
(Protecting policy).
If only some callers are meant to get through, decline the error
with a decline line above borrow, after its /// comment, giving the
reason:
decline policy.call_ungranted: "Only staff borrow through the desk app."action borrow(book_id: Book.id, caller: Principal.user) fails BookNotFound, MemberNotFound -> Book { ...}
borrow stays as it is, and a member who isn't a librarian still gets
403 not_permitted from lend. policy.call_ungranted is a security
rule, so a new decline of it also needs a person's acceptance:
jadpo check fails on the change until an acceptance lists
PolicyChange.call_ungranted_declined on borrow, and
access.jadpo_record keeps the decline.
Related
Markdown for agents: /docs/errors/policy.call_ungranted.md.Built from Jadpo main at aee2e8f.