forbid_read_transfers_over_1000
A forbid rule with a sum over a (value, timepoint) domain and a filtered
temporal body. Forbid a Read if the same user’s resolved positive
Transfers in the last hour total more than 1000.
The two-binder domain for (a: Long), (t: Timepoint). is what keeps equal
amounts made at different timepoints from being deduplicated — a is the summed
value and t distinguishes the timepoints, so the sum is per-occurrence.
Because the policy set contains only this forbid rule, no request can ever be
allowed — the interesting contrast is the threshold, i.e. whether the forbid
fires (an explicit deny, shown as [rules: 0]) or not (a default deny).
The trace shows both sides of the threshold:
@0/@1— alice’s firstTransferresolves to 600 (a history-only event; no matching rule applies, so the decision is a default deny).@10— alice reads with only 600 transferred in the last hour → default deny (theforbiddoes not fire; 600 is not over 1000).@20/@21— a secondTransferfor alice resolves to 600 (running total 1200).@30— alice reads again, now with 600 + 600 = 1200 transferred in the last hour → explicit deny ([rules: 0], theforbidfires because the sum is over 1000).@40— bob reads with noTransfers of his own → default deny (the per-user pininput.user: context.input.usermeans alice’s transfers do not count for bob).
Referenced by guide/04-temporal-expressions.md.
Policy
// A FORBID rule with a `sum` over a (value, timepoint) domain and a filtered
// temporal body. The two-binder domain `for (a: Long), (t: Timepoint).` keeps
// equal amounts made at different timepoints from being deduplicated. Forbid a
// Read if the same user's resolved positive Transfers in the last hour total
// more than 1000.
@id("forbid_read_when_transfers_over_1000")
forbid (
principal,
action == Drupe::Action::"Read",
resource
)
when temporal {
exists (total: Long). (
(sum a for (a: Long), (t: Timepoint). where (
formerly within 1h (
Drupe::Action::"Transfer"::response{ input.user: context.input.user, output.amount: a }
&& a > 0 && tp(t)
)
)) == total
&& total > 1000
)
};
Schema
namespace Drupe {
type ContentFilterFinding = {
severityScore: decimal
};
type LoginInput = {
server: String,
user: String
};
type LoginOutput = {
result: Bool
};
type PromptAttackFinding = {
severityScore: decimal
};
type ReadInput = {
document: String,
user: String
};
type ReadOutput = { };
type SensitiveInfoFinding = {
confidenceScore: decimal
};
type SystemContext = {
now: datetime
};
type TransferInput = {
user: String
};
type TransferOutput = {
amount: Long
};
entity Gateway;
entity IamEntity = {
id: String
};
entity OAuthUser = {
id: String
} tags String;
entity UnauthenticatedUser;
action "CallTool" in [Action::"Mcp"] appliesTo {
principal: [OAuthUser, IamEntity, UnauthenticatedUser],
resource: [Gateway],
context: {
system: SystemContext
}
};
action "Http" appliesTo {
principal: [OAuthUser, IamEntity, UnauthenticatedUser],
resource: [Gateway],
context: {
system: SystemContext
}
};
action "InvokeAgent" in [Action::"Http"] appliesTo {
principal: [OAuthUser, IamEntity, UnauthenticatedUser],
resource: [Gateway],
context: {
input?: { },
system: SystemContext
}
};
action "InvokeLLM" in [Action::"Http"] appliesTo {
principal: [OAuthUser, IamEntity, UnauthenticatedUser],
resource: [Gateway],
context: {
input?: { },
system: SystemContext
}
};
action "Login" in [Action::"CallTool"] appliesTo {
principal: [IamEntity, OAuthUser, UnauthenticatedUser],
resource: [Gateway],
context: {
input: LoginInput,
output?: LoginOutput,
system: SystemContext
}
};
action "Mcp" appliesTo {
principal: [OAuthUser, IamEntity, UnauthenticatedUser],
resource: [Gateway],
context: {
system: SystemContext
}
};
action "Read" in [Action::"CallTool"] appliesTo {
principal: [IamEntity, OAuthUser, UnauthenticatedUser],
resource: [Gateway],
context: {
input: ReadInput,
output?: ReadOutput,
system: SystemContext
}
};
action "Transfer" in [Action::"CallTool"] appliesTo {
principal: [IamEntity, OAuthUser, UnauthenticatedUser],
resource: [Gateway],
context: {
input: TransferInput,
output?: TransferOutput,
system: SystemContext
}
};
action "UnknownTool" in [Action::"CallTool"] appliesTo {
principal: [OAuthUser, IamEntity, UnauthenticatedUser],
resource: [Gateway],
context: {
system: SystemContext
}
};
}
Trace
@0 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { user: "alice" }) Drupe::Action::"Transfer"::request(input: { user: "alice" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u1")
@1 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") Drupe::Action::"Transfer"::response(input: { user: "alice" }, output: { amount: 600 }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u1")
@10 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { document: "doc1", user: "alice" }) Drupe::Action::"Read"::request(input: { document: "doc1", user: "alice" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u2")
@20 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { user: "alice" }) Drupe::Action::"Transfer"::request(input: { user: "alice" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u3")
@21 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") Drupe::Action::"Transfer"::response(input: { user: "alice" }, output: { amount: 600 }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u3")
@30 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { document: "doc1", user: "alice" }) Drupe::Action::"Read"::request(input: { document: "doc1", user: "alice" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u4")
@40 scope(principal: Drupe::OAuthUser::"bob", resource: Drupe::Gateway::"gw1") request_context(input: { document: "doc1", user: "bob" }) Drupe::Action::"Read"::request(input: { document: "doc1", user: "bob" }, callerPrincipal: Drupe::OAuthUser::"bob", callerResource: Drupe::Gateway::"gw1", requestId: "u5")
Expected Output
@0 (time point 0): DENY
@10 (time point 1): DENY
@20 (time point 2): DENY
@30 (time point 3): DENY [rules: 0]
@40 (time point 4): DENY