call_temporal_aggregation_macro_count
Calling a def temporal aggregation macro. count_formerly produces a
count, so it is spliced into a comparison and wrapped in an exists binder
that introduces the variable it is compared against — never called on its own:
exists (n: Long). (count_formerly(1h, Login) == n && n > 0)
The macro is defined in the attached library macros.dw (supplied via
--macros) and desugars to
count for ($t: Timepoint). where (formerly within ?w (?s && tp($t))). The
policy permits Alert once at least one matching Login is in the last hour.
The trace shows both verdicts:
@0— aliceLoginons1(a history-only event here; noAlertpermit applies, so the decision is a deny).@2,@10— aliceAlertons1, within 1h of her login → allow (count == 1, son > 0).@20— bobLoginons1(history-only again → deny).@22— aliceAlertons1, still within the window → allow.
Run from this directory:
dogwood validate policy.dw --policy-schema schema.cedarschema --macros macros.dw
dogwood replay policy.dw --policy-schema schema.cedarschema --macros macros.dw --trace trace.log
Referenced by guide/09-calling-macros.md.
Policy
// Calling a `def temporal` AGGREGATION macro: it produces a count, so
// it is spliced into a comparison inside an `exists` binder (which
// introduces the variable it is compared against) — never called on
// its own. Permits Alert once at least one Login is in the window.
@id("alert_after_a_login")
permit (principal, action == Drupe::Action::"Alert", resource)
when temporal {
exists (n: Long). (
(count_formerly(1h, Drupe::Action::"Login"::request{
input.user: _, input.server: context.input.server
})) == n
&& n > 0
)
};
Schema
namespace Drupe {
type AlertInput = {
level: Long,
server: String
};
type AlertOutput = { };
type ContentFilterFinding = {
severityScore: decimal
};
type LoginInput = {
server: String,
user: String
};
type LoginOutput = { };
type PromptAttackFinding = {
severityScore: decimal
};
type SensitiveInfoFinding = {
confidenceScore: decimal
};
type SystemContext = {
now: datetime
};
entity Gateway;
entity IamEntity = {
id: String
};
entity OAuthUser = {
id: String
} tags String;
entity UnauthenticatedUser;
action "Alert" in [Action::"CallTool"] appliesTo {
principal: [IamEntity, OAuthUser, UnauthenticatedUser],
resource: [Gateway],
context: {
input: AlertInput,
output?: AlertOutput,
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 "Mcp" appliesTo {
principal: [OAuthUser, IamEntity, UnauthenticatedUser],
resource: [Gateway],
context: {
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" }) Drupe::Action::"Login"::request(input: { server: "s1", user: "alice" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u1")
@1 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") Drupe::Action::"Login"::response(input: { server: "s1", user: "alice" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u1")
@2 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { level: 1, server: "s1" }) Drupe::Action::"Alert"::request(input: { level: 1, server: "s1" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u2")
@3 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") Drupe::Action::"Alert"::response(input: { level: 1, server: "s1" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u2")
@10 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { level: 2, server: "s1" }) Drupe::Action::"Alert"::request(input: { level: 2, server: "s1" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u3")
@11 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") Drupe::Action::"Alert"::response(input: { level: 2, server: "s1" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u3")
@20 scope(principal: Drupe::OAuthUser::"bob", resource: Drupe::Gateway::"gw1") request_context(input: { server: "s1", user: "bob" }) Drupe::Action::"Login"::request(input: { server: "s1", user: "bob" }, callerPrincipal: Drupe::OAuthUser::"bob", callerResource: Drupe::Gateway::"gw1", requestId: "u4")
@21 scope(principal: Drupe::OAuthUser::"bob", resource: Drupe::Gateway::"gw1") Drupe::Action::"Login"::response(input: { server: "s1", user: "bob" }, callerPrincipal: Drupe::OAuthUser::"bob", callerResource: Drupe::Gateway::"gw1", requestId: "u4")
@22 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { level: 3, server: "s1" }) Drupe::Action::"Alert"::request(input: { level: 3, server: "s1" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u5")
@23 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") Drupe::Action::"Alert"::response(input: { level: 3, server: "s1" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u5")
Expected Output
@0 (time point 0): DENY
@2 (time point 1): ALLOW [rules: 0]
@10 (time point 2): ALLOW [rules: 0]
@20 (time point 3): DENY
@22 (time point 4): ALLOW [rules: 0]
Macros
// Macro library supplied via --macros. `count_formerly(?w, ?s)` (?w a bare
// interval, e.g. `1h`) desugars to
// count for ($t: Timepoint). where (formerly within ?w (?s && tp($t)))
// so a call spliced into a comparison inside an `exists` counts how many
// times the predicate ?s held anywhere within the window ?w.
def temporal count_formerly(?w, ?s) {
count for ($t: Timepoint). where (formerly within ?w (?s && tp($t)))
};