login_attempt_custom_kind
A custom, author-defined event kind. The per-case event schema
(event.dwschema) names attempt as the decision kind and outcome as
history (instead of the conventional request/response), and renames the
injected principal field to actor (instead of callerPrincipal). The
policy permits a Read only if the same actor formerly attempted a Login
within the last hour (formerly within 1h, correlating actor: context.principal and input.user: context.input.user).
The event schema is passed with --event-schema event.dwschema; without it the
CLI would default to request/response and reject the ::attempt kind.
The trace shows all three cases:
@0— alice attempts aLogin(a history-only event here; noReadpermit applies to a login, so the decision is a deny).@10— alice reads, 10s after her login → allow (a matching login by the same actor is within the window).@20— bob reads with no prior login of his own → deny.
Referenced by guide/04-temporal-expressions.md.
Policy
// Event kinds are author-defined. This event schema names `attempt` (the
// decision kind) and `outcome` (history) instead of request/response, and
// renames the injected principal field to `actor`. Permit a Read only if the
// same actor formerly attempted a Login within the last hour.
@id("read_after_login_attempt")
permit (
principal,
action == Drupe::Action::"Read",
resource
)
when temporal {
formerly within 1h Drupe::Action::"Login"::attempt{
input.user: context.input.user,
actor: principal
}
};
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 = {
result: Bool
};
type SensitiveInfoFinding = {
confidenceScore: decimal
};
type SystemContext = {
now: datetime
};
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 "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: { server: "s1", user: "alice" }, actor: Drupe::OAuthUser::"alice") Drupe::Action::"Login"::attempt(input: { server: "s1", user: "alice" }, actor: Drupe::OAuthUser::"alice")
@10 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { document: "doc1", user: "alice" }, actor: Drupe::OAuthUser::"alice") Drupe::Action::"Read"::attempt(input: { document: "doc1", user: "alice" }, actor: Drupe::OAuthUser::"alice")
@20 scope(principal: Drupe::OAuthUser::"bob", resource: Drupe::Gateway::"gw1") request_context(input: { document: "doc1", user: "alice" }, actor: Drupe::OAuthUser::"bob") Drupe::Action::"Read"::attempt(input: { document: "doc1", user: "alice" }, actor: Drupe::OAuthUser::"bob")
Expected Output
@0 (time point 0): DENY
@10 (time point 1): ALLOW [rules: 0]
@20 (time point 2): DENY
Event Schema
// A custom event schema exercising the per-case override: the event kinds
// are `attempt` (the decision kind) and `outcome` (history), NOT the
// conventional request/response, and the injected principal field is
// named `actor` rather than `callerPrincipal`. This probes that (a) the
// harness honors a per-case event.dwschema, (b) author-defined kinds work
// end to end, and (c) a renamed injected reserved field is usable.
decision event <A>::attempt {
...inputs(A),
actor: principalType(A),
}
event <A>::outcome {
...inputs(A),
...outputs(A),
actor: principalType(A),
}