Human approval

WOML
<approval
  id="managerApproval"
  name="Manager approval"
  description="Approve the calculated refund."
  timeout="24h"
  on-timeout="reject"
>
  <notify>
    <telegram
      chats="123456789"
      bot-token="{{secrets.TELEGRAM_BOT_TOKEN}}"
    />
  </notify>

  <when-approved>
    <step id="sendRefund"><script>return issueRefund();</script></step>
  </when-approved>

  <when-rejected>
    <step id="recordRejection"><script>return { rejected: true };</script></step>
  </when-rejected>
</approval>
AttributeRequiredMeaning
idYesWaiting-node and context.steps.<id> output identity.
name, descriptionNoHuman-facing decision context.
timeoutNoMaximum durable wait. Omission means no WOML deadline.
on-timeoutNoreject or fail; defaults to fail and requires timeout.

Child order is fixed: optional <notify>, required <when-approved>, required <when-rejected>. Decision arms may be empty; an empty selected arm is a successful no-op.

WOML persists the wait and opaque capability before notification. Restart does not lose it. The first valid decision wins. Matching repeats are idempotent; an opposing later decision conflicts. The approval result becomes:

JSON
{
  "decision": "approved",
  "source": "human",
  "decidedAt": "2026-08-04T12:00:00.000Z"
}

source is human or timeout. on-timeout="reject" publishes a rejected result and runs <when-rejected>. on-timeout="fail" fails the run without executing either arm.

The public decision mechanism is HTTP:

HTTP
POST /api/v1/approvals/{token}/decision
Content-Type: application/json

{ "decision": "approved" }

Valid decisions are exactly approved and rejected. There is no npm API or woml.resume() function.