submit_after_approval_injection
A def temporal macro (approved_recently) whose predicate-valued parameter
?s is refined inside the macro body with an injected field
(?s{ input.status: "approved" }). The refinement forces the
input.status: "approved" filter onto whatever event the caller passes; the
call site never writes input.status – it supplies only
Drupe::Action::"Approve"::request{ input.user: context.input.user }.
The window ?w is filled at the call site with a bare interval literal (1h,
no within keyword). The policy permits a Submit only if the same user was
formerly Approved within 1h with status “approved”.
This is the load-bearing ?s{…} refinement-in-macro-body path.
Files
policy.dw– the macro definition and thepermit … when temporal { approved_recently(1h, …) }rule.schema.cedarschema– Cedar action schema (Approve + Submit underDrupe), lifted from macros corpus0041_field_injection_omitted_field.trace.log– five events exercising both outcomes.expected.out– captured verbatim fromdogwood replay.
Trace outcomes
| tp | event | verdict | why |
|---|---|---|---|
| 0 | alice Approve (status “approved”) | DENY | only Submit is permitted |
| 1 | alice Submit | ALLOW | alice was formerly Approved within 1h with status “approved” |
| 2 | bob Submit | DENY | bob was never Approved |
| 3 | carol Approve (status “pending”) | DENY | only Submit is permitted |
| 4 | carol Submit | DENY | carol’s prior Approve was status “pending” – excluded by the injected input.status: "approved" filter |
Reproduce
Run from this directory (so relative paths resolve):
dogwood validate policy.dw --policy-schema schema.cedarschema
dogwood replay policy.dw --policy-schema schema.cedarschema --trace trace.log
Note on the guide’s same_session macro
The guide’s literal same_session example (guide/04-temporal-expressions.md:482)
injects a deep context path (context.__drupe.session.id) and does not
pass validate – the validator rejects that deep path (only the corpus’s
replay-only test accepts it). This bundle substitutes the semantically
equivalent, validating field-injection macro from corpus
0041_field_injection_omitted_field to teach the same ?s{…}
refinement-in-macro-body point.
Referenced by guide/04-temporal-expressions.
Policy
// A `def temporal` macro whose predicate-valued parameter `?s` is REFINED in
// the macro body with an injected field (`?s{ input.status: "approved" }`),
// forcing that filter onto whatever event the caller passes -- the caller
// never writes input.status. The call site fills `?w` with a bare interval
// literal (no `within`). Permit a Submit only if the same user was formerly
// Approved (within 1h) WITH status "approved".
def temporal approved_recently(?w, ?s) {
formerly within ?w (?s{ input.status: "approved" })
};
@id("submit_after_approval")
permit (
principal,
action == Drupe::Action::"Submit",
resource
)
when temporal {
approved_recently(
1h,
Drupe::Action::"Approve"::request{ input.user: context.input.user }
)
};
Schema
namespace Drupe {
type ApproveInput = {
status: String,
user: String
};
type ApproveOutput = { };
type SubmitInput = {
user: String
};
type SubmitOutput = { };
type SystemContext = {
now: datetime
};
entity Gateway;
entity OAuthUser = {
id: String
};
action "Approve" appliesTo {
principal: [OAuthUser],
resource: [Gateway],
context: {
input: ApproveInput,
output?: ApproveOutput,
system: SystemContext
}
};
action "Submit" appliesTo {
principal: [OAuthUser],
resource: [Gateway],
context: {
input: SubmitInput,
output?: SubmitOutput,
system: SystemContext
}
};
}
Trace
@0 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { status: "approved", user: "alice" }) Drupe::Action::"Approve"::request(input: { status: "approved", user: "alice" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u1")
@10 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { user: "alice" }) Drupe::Action::"Submit"::request(input: { user: "alice" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u2")
@20 scope(principal: Drupe::OAuthUser::"bob", resource: Drupe::Gateway::"gw1") request_context(input: { user: "bob" }) Drupe::Action::"Submit"::request(input: { user: "bob" }, callerPrincipal: Drupe::OAuthUser::"bob", callerResource: Drupe::Gateway::"gw1", requestId: "u3")
@30 scope(principal: Drupe::OAuthUser::"carol", resource: Drupe::Gateway::"gw1") request_context(input: { status: "pending", user: "carol" }) Drupe::Action::"Approve"::request(input: { status: "pending", user: "carol" }, callerPrincipal: Drupe::OAuthUser::"carol", callerResource: Drupe::Gateway::"gw1", requestId: "u4")
@40 scope(principal: Drupe::OAuthUser::"carol", resource: Drupe::Gateway::"gw1") request_context(input: { user: "carol" }) Drupe::Action::"Submit"::request(input: { user: "carol" }, callerPrincipal: Drupe::OAuthUser::"carol", 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
@40 (time point 4): DENY