touchless_share_capped
EXPERIMENTAL. This bundle uses arithmetic in temporal comparison operands, gated behind the off-by-default Cargo feature
experimental-arithmetic. A default build rejects this policy; the test harnesses check this bundle only when built with that feature.
This bundle exercises * (ratio and percentage guards).
A share-of-total cap: touchless approvals may proceed only while they make up at most 85% of the human decisions in the window. Percentages are written cross-multiplied, which stays exact over integers; the language has no division, deliberately.
permit (principal, action == PriorAuth::Action::"TouchlessApprove", resource)
when temporal {
100 * (count for (t: Timepoint). where (
formerly within 24h (PriorAuth::Action::"TouchlessApprove"::response{} && tp(t))
))
<= 85 * count for (t: Timepoint). where (
formerly within 24h (PriorAuth::Action::"Decide"::response{} && tp(t))
)
};
Note the fencing: the left aggregate is parenthesized (its greedy where
body would otherwise swallow the <=); the right one is the final position
of the final operand and stays bare.
Replay
| Decision point | Event | 100 × touchless vs 85 × decided | Verdict |
|---|---|---|---|
@0, @2, @4 | human decisions d1–d3 | not TouchlessApprove | deny |
@10 | touchless t1 | 0 <= 255 | allow |
@20 | touchless t2 | 100 <= 255 | allow |
@30 | touchless t3 | 200 <= 255 | allow |
@40 | touchless t4 | 300 > 255 | deny |
@50 | human decision d4 | not TouchlessApprove | deny |
@60 | touchless t5 | 300 <= 340 | allow |
The @50 human decision is what reopens automated capacity at @60.
Files
policy.dw: the permit with the cross-multiplied share cap.schema.cedarschema:PriorAuthaction schema (Decide,TouchlessApprove).trace.log: three human decisions, four touchless attempts, one more human decision, one more touchless attempt.expected.out: the per-decision-point verdict stream.
Reproduce (run from this directory):
dogwood validate policy.dw --policy-schema schema.cedarschema
dogwood replay policy.dw --policy-schema schema.cedarschema --trace trace.log
World: priorauth. Default event schema; no providers.
Referenced by guide/04-temporal-expressions.md (arithmetic in comparison operands).
Policy
// A share-of-total cap: touchless (automated) approvals may proceed only
// while they make up at most 85% of the human decisions seen in the window.
//
// The `*` on both sides is the arithmetic this bundle exists to exercise: a
// percentage guard is written cross-multiplied (`100 * a <= 85 * b`), which
// stays exact over integers — the language has no division, deliberately.
//
@id("touchless_share_capped")
permit (
principal,
action == PriorAuth::Action::"TouchlessApprove",
resource
)
when temporal {
100 * (count for (t: Timepoint). where (
formerly within 24h (PriorAuth::Action::"TouchlessApprove"::response{} && tp(t))
))
<= 85 * count for (t: Timepoint). where (
formerly within 24h (PriorAuth::Action::"Decide"::response{} && tp(t))
)
};
Schema
namespace PriorAuth {
type DecideInput = {
case: String
};
type DecideOutput = { };
type TouchlessApproveInput = {
case: String
};
type TouchlessApproveOutput = { };
type SystemContext = {
now: datetime
};
entity Gateway;
entity OAuthUser = {
id: String
} tags String;
action "Decide" appliesTo {
principal: [OAuthUser],
resource: [Gateway],
context: {
input: DecideInput,
output?: DecideOutput,
system: SystemContext
}
};
action "TouchlessApprove" appliesTo {
principal: [OAuthUser],
resource: [Gateway],
context: {
input: TouchlessApproveInput,
output?: TouchlessApproveOutput,
system: SystemContext
}
};
}
Trace
@0 scope(principal: PriorAuth::OAuthUser::"agent", resource: PriorAuth::Gateway::"gw1") request_context(input: { case: "c1" }) PriorAuth::Action::"Decide"::request(input: { case: "c1" }, callerPrincipal: PriorAuth::OAuthUser::"agent", callerResource: PriorAuth::Gateway::"gw1", requestId: "d1")
@1 scope(principal: PriorAuth::OAuthUser::"agent", resource: PriorAuth::Gateway::"gw1") PriorAuth::Action::"Decide"::response(input: { case: "c1" }, callerPrincipal: PriorAuth::OAuthUser::"agent", callerResource: PriorAuth::Gateway::"gw1", requestId: "d1")
@2 scope(principal: PriorAuth::OAuthUser::"agent", resource: PriorAuth::Gateway::"gw1") request_context(input: { case: "c2" }) PriorAuth::Action::"Decide"::request(input: { case: "c2" }, callerPrincipal: PriorAuth::OAuthUser::"agent", callerResource: PriorAuth::Gateway::"gw1", requestId: "d2")
@3 scope(principal: PriorAuth::OAuthUser::"agent", resource: PriorAuth::Gateway::"gw1") PriorAuth::Action::"Decide"::response(input: { case: "c2" }, callerPrincipal: PriorAuth::OAuthUser::"agent", callerResource: PriorAuth::Gateway::"gw1", requestId: "d2")
@4 scope(principal: PriorAuth::OAuthUser::"agent", resource: PriorAuth::Gateway::"gw1") request_context(input: { case: "c3" }) PriorAuth::Action::"Decide"::request(input: { case: "c3" }, callerPrincipal: PriorAuth::OAuthUser::"agent", callerResource: PriorAuth::Gateway::"gw1", requestId: "d3")
@5 scope(principal: PriorAuth::OAuthUser::"agent", resource: PriorAuth::Gateway::"gw1") PriorAuth::Action::"Decide"::response(input: { case: "c3" }, callerPrincipal: PriorAuth::OAuthUser::"agent", callerResource: PriorAuth::Gateway::"gw1", requestId: "d3")
@10 scope(principal: PriorAuth::OAuthUser::"agent", resource: PriorAuth::Gateway::"gw1") request_context(input: { case: "c4" }) PriorAuth::Action::"TouchlessApprove"::request(input: { case: "c4" }, callerPrincipal: PriorAuth::OAuthUser::"agent", callerResource: PriorAuth::Gateway::"gw1", requestId: "t1")
@11 scope(principal: PriorAuth::OAuthUser::"agent", resource: PriorAuth::Gateway::"gw1") PriorAuth::Action::"TouchlessApprove"::response(input: { case: "c4" }, callerPrincipal: PriorAuth::OAuthUser::"agent", callerResource: PriorAuth::Gateway::"gw1", requestId: "t1")
@20 scope(principal: PriorAuth::OAuthUser::"agent", resource: PriorAuth::Gateway::"gw1") request_context(input: { case: "c5" }) PriorAuth::Action::"TouchlessApprove"::request(input: { case: "c5" }, callerPrincipal: PriorAuth::OAuthUser::"agent", callerResource: PriorAuth::Gateway::"gw1", requestId: "t2")
@21 scope(principal: PriorAuth::OAuthUser::"agent", resource: PriorAuth::Gateway::"gw1") PriorAuth::Action::"TouchlessApprove"::response(input: { case: "c5" }, callerPrincipal: PriorAuth::OAuthUser::"agent", callerResource: PriorAuth::Gateway::"gw1", requestId: "t2")
@30 scope(principal: PriorAuth::OAuthUser::"agent", resource: PriorAuth::Gateway::"gw1") request_context(input: { case: "c6" }) PriorAuth::Action::"TouchlessApprove"::request(input: { case: "c6" }, callerPrincipal: PriorAuth::OAuthUser::"agent", callerResource: PriorAuth::Gateway::"gw1", requestId: "t3")
@31 scope(principal: PriorAuth::OAuthUser::"agent", resource: PriorAuth::Gateway::"gw1") PriorAuth::Action::"TouchlessApprove"::response(input: { case: "c6" }, callerPrincipal: PriorAuth::OAuthUser::"agent", callerResource: PriorAuth::Gateway::"gw1", requestId: "t3")
@40 scope(principal: PriorAuth::OAuthUser::"agent", resource: PriorAuth::Gateway::"gw1") request_context(input: { case: "c7" }) PriorAuth::Action::"TouchlessApprove"::request(input: { case: "c7" }, callerPrincipal: PriorAuth::OAuthUser::"agent", callerResource: PriorAuth::Gateway::"gw1", requestId: "t4")
@50 scope(principal: PriorAuth::OAuthUser::"agent", resource: PriorAuth::Gateway::"gw1") request_context(input: { case: "c8" }) PriorAuth::Action::"Decide"::request(input: { case: "c8" }, callerPrincipal: PriorAuth::OAuthUser::"agent", callerResource: PriorAuth::Gateway::"gw1", requestId: "d4")
@51 scope(principal: PriorAuth::OAuthUser::"agent", resource: PriorAuth::Gateway::"gw1") PriorAuth::Action::"Decide"::response(input: { case: "c8" }, callerPrincipal: PriorAuth::OAuthUser::"agent", callerResource: PriorAuth::Gateway::"gw1", requestId: "d4")
@60 scope(principal: PriorAuth::OAuthUser::"agent", resource: PriorAuth::Gateway::"gw1") request_context(input: { case: "c9" }) PriorAuth::Action::"TouchlessApprove"::request(input: { case: "c9" }, callerPrincipal: PriorAuth::OAuthUser::"agent", callerResource: PriorAuth::Gateway::"gw1", requestId: "t5")
Expected Output
@0 (time point 0): DENY
@2 (time point 1): DENY
@4 (time point 2): DENY
@10 (time point 3): ALLOW [rules: 0]
@20 (time point 4): ALLOW [rules: 0]
@30 (time point 5): ALLOW [rules: 0]
@40 (time point 6): DENY
@50 (time point 7): DENY
@60 (time point 8): ALLOW [rules: 0]