Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 the permit … when temporal { approved_recently(1h, …) } rule.
  • schema.cedarschema – Cedar action schema (Approve + Submit under Drupe), lifted from macros corpus 0041_field_injection_omitted_field.
  • trace.log – five events exercising both outcomes.
  • expected.out – captured verbatim from dogwood replay.

Trace outcomes

tpeventverdictwhy
0alice Approve (status “approved”)DENYonly Submit is permitted
1alice SubmitALLOWalice was formerly Approved within 1h with status “approved”
2bob SubmitDENYbob was never Approved
3carol Approve (status “pending”)DENYonly Submit is permitted
4carol SubmitDENYcarol’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