Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

  • @0Grant for doc1/alice (a history-only event here; no Access permit applies, so the decision is a deny).
  • @100alice accesses doc1, after the grant and with no intervening revoke → allow (the not-revoked-since-grant chain holds).
  • @200Revoke for doc1/alice (history-only; deny).
  • @300alice accesses doc1 again, but a Revoke now 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