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; aReadis not aWritepermit, so the decision is a deny).@5— the read completes successfully (a history-onlyresponseevent; 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 (theinput.documentpin fails).@30— bob writes doc1, which he never read -> deny (theinput.userpin 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