Approval queue for high-risk agent actions

Agent buildHard40 min

Your task

Infrastructure agents propose actions on behalf of the engineer who launched them, and anything risky waits in an approval queue until humans sign off. Write approval_queue(events, ttl, max_pending, now), which replays the queue's log and reports where every proposed action ended up.

events is a list in time order (whole seconds, never decreasing) of three kinds of event:

  • ["propose", t, action_id, agent, owner, risk]: agent proposes an action on behalf of owner, the engineer who launched it. risk is an integer from 0 to 100. Action ids are unique.
  • ["approve", t, action_id, person]
  • ["reject", t, action_id, person]

Rules

  1. Risk below 40 runs at once: the action is "auto" and never enters the queue.
  2. Risk from 40 to 79 needs one approval. Risk 80 or more needs approvals from two different people.
  3. The owner can never approve their own action; their approvals are ignored. A person who approves twice counts once. As soon as an action has the approvals it needs, it is "approved".
  4. A reject from anyone, the owner included, makes a pending action "rejected" at once.
  5. A pending action expires ttl seconds after it was proposed: from second t + ttl on it is "expired", and approvals or rejects at or after that second are ignored.
  6. Each agent may have at most max_pending pending actions (max_pending may be 0). A risky proposal from an agent already at that limit is "refused" and never enters the queue. An action stops being pending the moment it is approved, rejected or expires; expiry happens before anything else in that second, so an action that expires at second t frees its slot for a proposal at second t.
  7. Approvals and rejects for an action that is not pending (unknown, auto, refused, or already decided) are ignored.

Return a dict mapping every proposed action id to its status at second now (no earlier than the last event): "auto", "approved", "rejected", "expired", "refused", or "pending" for an action still waiting. An action still waiting when now reaches its deadline is "expired".

Example, with ttl 600, max_pending 1 and now 2000:

["propose", 0, "a1", "deployer", "maya", 85]   pending, needs two approvals
["approve", 10, "a1", "maya"]                   ignored: maya is the owner
["approve", 20, "a1", "li"]                     one approval
["propose", 30, "a2", "deployer", "maya", 50]   refused: deployer already has one pending
["propose", 30, "a3", "cleaner", "li", 10]      auto
["approve", 40, "a1", "li"]                     ignored: li already approved
["approve", 50, "a1", "sam"]                    second approver: approved
["propose", 60, "a4", "deployer", "maya", 60]   pending: the slot is free again
["reject", 70, "a4", "maya"]                    rejected
["propose", 100, "a5", "deployer", "sam", 45]   pending until second 700

The result is {"a1": "approved", "a2": "refused", "a3": "auto", "a4": "rejected", "a5": "expired"}.

A second example, with ttl 100, max_pending 1 and now 150: the events ["propose", 0, "b1", "bot", "kim", 50], ["propose", 100, "b2", "bot", "kim", 50], ["approve", 100, "b1", "ana"], ["approve", 120, "b2", "ana"] give {"b1": "expired", "b2": "approved"}. At second 100, b1 expires before b2 is proposed, and ana's approval of b1 comes too late.

Examples

approval_queue(events: list[list], ttl: int, max_pending: int, now: int) -> dict
Example 1
Inputapproval_queue([["propose", 0, "a1", "deployer", "maya", 85], ["approve", 10, "a1", "maya"], ["approve", 20, "a1", "li"], ["propose", 30, "a2", "deployer", "maya", 50], ["propose", 30, "a3", "cleaner", "li", 10], ["approve", 40, "a1", "li"], ["approve", 50, "a1", "sam"], ["propose", 60, "a4", "deployer", "maya", 60], ["reject", 70, "a4", "maya"], ["propose", 100, "a5", "deployer", "sam", 45]], 600, 1, 2000)
Output{"a1": "approved", "a2": "refused", "a3": "auto", "a4": "rejected", "a5": "expired"}
Example 2
Inputapproval_queue([["propose", 0, "b1", "bot", "kim", 50], ["propose", 100, "b2", "bot", "kim", 50], ["approve", 100, "b1", "ana"], ["approve", 120, "b2", "ana"]], 100, 1, 150)
Output{"b1": "expired", "b2": "approved"}