access_not_revoked_since_grant
The “open session” idiom: a negated left operand on since expresses “has
not happened since.” Because ! binds tighter than since, !A since within W B negates only A, giving “no A has happened since B.” Here: permit an
Access only if the user has not been Revoked on this resource since they
were Granted it within the last hour (!Revoke since within 1h Grant, with
both input.user and input.resource pinned).
The trace shows both outcomes:
@0—Grantfordoc1/alice(a history-only event here; noAccesspermit applies, so the decision is a deny).@100—aliceaccessesdoc1, after the grant and with no intervening revoke → allow (the not-revoked-since-grant chain holds).@200—Revokefordoc1/alice(history-only; deny).@300—aliceaccessesdoc1again, but aRevokenow sits between the grant and this access → deny (the negated-left chain is broken).
Referenced by guide/04-temporal-expressions.md.
Policy
// The "open session" idiom: a negated LEFT operand expresses "has not happened
// since". Because `!` binds tighter than `since`, `!A since within W B`
// negates only A. Permit an Access only if the user has NOT been revoked on
// this resource since they were granted it within the last hour.
@id("access_not_revoked_since_grant")
permit (
principal,
action == Drupe::Action::"Access",
resource
)
when temporal {
!Drupe::Action::"Revoke"::request{ input.user: context.input.user, input.resource: context.input.resource }
since within 1h
Drupe::Action::"Grant"::request{ input.user: context.input.user, input.resource: context.input.resource }
};
Schema
namespace Drupe {
type AccessInput = {
resource: String,
user: String
};
type AccessOutput = { };
type ContentFilterFinding = {
severityScore: decimal
};
type GrantInput = {
resource: String,
user: String
};
type GrantOutput = { };
type PromptAttackFinding = {
severityScore: decimal
};
type RevokeInput = {
resource: String,
user: String
};
type RevokeOutput = { };
type SensitiveInfoFinding = {
confidenceScore: decimal
};
type SystemContext = {
now: datetime
};
entity Gateway;
entity IamEntity = {
id: String
};
entity OAuthUser = {
id: String
} tags String;
entity UnauthenticatedUser;
action "Access" in [Action::"CallTool"] appliesTo {
principal: [IamEntity, OAuthUser, UnauthenticatedUser],
resource: [Gateway],
context: {
input: AccessInput,
output?: AccessOutput,
system: SystemContext
}
};
action "CallTool" in [Action::"Mcp"] appliesTo {
principal: [OAuthUser, IamEntity, UnauthenticatedUser],
resource: [Gateway],
context: {
system: SystemContext
}
};
action "Grant" in [Action::"CallTool"] appliesTo {
principal: [IamEntity, OAuthUser, UnauthenticatedUser],
resource: [Gateway],
context: {
input: GrantInput,
output?: GrantOutput,
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 "Mcp" appliesTo {
principal: [OAuthUser, IamEntity, UnauthenticatedUser],
resource: [Gateway],
context: {
system: SystemContext
}
};
action "Revoke" in [Action::"CallTool"] appliesTo {
principal: [IamEntity, OAuthUser, UnauthenticatedUser],
resource: [Gateway],
context: {
input: RevokeInput,
output?: RevokeOutput,
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: { resource: "doc1", user: "alice" }) Drupe::Action::"Grant"::request(input: { resource: "doc1", user: "alice" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u1")
@100 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { resource: "doc1", user: "alice" }) Drupe::Action::"Access"::request(input: { resource: "doc1", user: "alice" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u2")
@200 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { resource: "doc1", user: "alice" }) Drupe::Action::"Revoke"::request(input: { resource: "doc1", user: "alice" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u3")
@300 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { resource: "doc1", user: "alice" }) Drupe::Action::"Access"::request(input: { resource: "doc1", user: "alice" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u4")
Expected Output
@0 (time point 0): DENY
@100 (time point 1): ALLOW [rules: 0]
@200 (time point 2): DENY
@300 (time point 3): DENY