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

write_after_read_formerly

The flagship history-dependent policy in the guide’s literal wording: permit a Write only if the same user successfully read the same document within the last hour. formerly within 1h is the existential past operator; the field pins input.user: context.input.user and input.document: context.input.document correlate the past Read response to the current request.

This bundle preserves the guide’s literal Read/Write text. (The sibling bundle examples/write_after_read adapts the same concept to the designated SellShares/ApproveSale schema.) The schema here is lifted from temporal corpus case 0004_write_after_read, which carries the Read/Write actions.

The trace shows both outcomes:

  • @0 — alice reads doc1 (a decision event; a Read is not a Write permit, so the decision is a deny).
  • @5 — the read completes successfully (a history-only response event; no verdict is produced, but it records the success for later lookups).
  • @10 — alice writes doc1, 10s after her read -> allow (matching read response in the window).
  • @20 — alice writes doc2, which she never read -> deny (the input.document pin fails).
  • @30 — bob writes doc1, which he never read -> deny (the input.user pin fails).
  • @3700 — alice writes doc1 again, but 3700s > 1h after her read -> deny (the window has expired).

Referenced by guide/04-temporal-expressions.md.

Policy

// Permit a Write only if the same user successfully read the same document
// within the last hour (the canonical write-after-read policy). `formerly
// within 1h` is the existential past operator; `input.user`/`input.document`
// pin the past Read to the CURRENT request via context.* correlation.
@id("write_after_read")
permit (
    principal,
    action == Drupe::Action::"Write",
    resource
)
when temporal {
    formerly within 1h Drupe::Action::"Read"::response{
        input.user: context.input.user,
        input.document: context.input.document
    }
};

Schema

namespace Drupe {
  type ApproveInput = {
    approver: String,
    request_id: String
  };

  type ApproveOutput = {
    result: Bool
  };

  type ContentFilterFinding = {
    severityScore: decimal
  };

  type LoginInput = {
    server: String,
    user: String
  };

  type LoginOutput = {
    result: Bool
  };

  type LogoutInput = {
    server: String,
    user: String
  };

  type LogoutOutput = {
    result: Bool
  };

  type PromptAttackFinding = {
    severityScore: decimal
  };

  type ReadInput = {
    document: String,
    user: String
  };

  type ReadOutput = {
    result: Bool
  };

  type SensitiveInfoFinding = {
    confidenceScore: decimal
  };

  type SubmitInput = {
    request_id: String,
    user: String
  };

  type SubmitOutput = {
    result: Bool
  };

  type SystemContext = {
    now: datetime
  };

  type WriteInput = {
    document: String,
    user: String
  };

  type WriteOutput = {
    result: Bool
  };

  entity Gateway;

  entity IamEntity = {
    id: String
  };

  entity OAuthUser = {
    id: String
  } tags String;

  entity UnauthenticatedUser;

  action "Approve" in [Action::"CallTool"] appliesTo {
    principal: [IamEntity, OAuthUser, UnauthenticatedUser],
    resource: [Gateway],
    context: {
      input: ApproveInput,
      output?: ApproveOutput,
      system: SystemContext
    }
  };

  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 "Logout" in [Action::"CallTool"] appliesTo {
    principal: [IamEntity, OAuthUser, UnauthenticatedUser],
    resource: [Gateway],
    context: {
      input: LogoutInput,
      output?: LogoutOutput,
      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 "Submit" in [Action::"CallTool"] appliesTo {
    principal: [IamEntity, OAuthUser, UnauthenticatedUser],
    resource: [Gateway],
    context: {
      input: SubmitInput,
      output?: SubmitOutput,
      system: SystemContext
    }
  };

  action "UnknownTool" in [Action::"CallTool"] appliesTo {
    principal: [OAuthUser, IamEntity, UnauthenticatedUser],
    resource: [Gateway],
    context: {
      system: SystemContext
    }
  };

  action "Write" in [Action::"CallTool"] appliesTo {
    principal: [IamEntity, OAuthUser, UnauthenticatedUser],
    resource: [Gateway],
    context: {
      input: WriteInput,
      output?: WriteOutput,
      system: SystemContext
    }
  };
}

Trace

@0 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: "u1")
@5 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { document: "doc1", user: "alice" }) Drupe::Action::"Read"::response(input: { document: "doc1", user: "alice" }, output: { result: true }, 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::"Write"::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: { document: "doc2", user: "alice" }) Drupe::Action::"Write"::request(input: { document: "doc2", user: "alice" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u3")
@30 scope(principal: Drupe::OAuthUser::"bob", resource: Drupe::Gateway::"gw1") request_context(input: { document: "doc1", user: "bob" }) Drupe::Action::"Write"::request(input: { document: "doc1", user: "bob" }, callerPrincipal: Drupe::OAuthUser::"bob", callerResource: Drupe::Gateway::"gw1", requestId: "u4")
@3700 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { document: "doc1", user: "alice" }) Drupe::Action::"Write"::request(input: { document: "doc1", user: "alice" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u5")

Expected Output

@0 (time point 0): DENY
@10 (time point 1): ALLOW  [rules: 0]
@20 (time point 2): DENY
@30 (time point 3): DENY
@3700 (time point 4): DENY