JamJet
Cloud

Approvals

Human-in-the-loop queue. SDK create-and-poll flow, dashboard decisions, policy-gateway flow, SLA badges and bulk decide.

Approvals

The Approvals surface is the human-in-the-loop queue where a person reviews a pending action and either lets it proceed or stops it. It matters because some actions (production deploys, payments, bulk communications, irreversible mutations) carry a cost of mistake high enough that a human sign-off is worth the latency. You work the queue from the dashboard at /dashboard/approvals, under the Enforce chapter.

The working queue

Open /dashboard/approvals to see two tabs: Pending and History. Each pending request shows:

  • The tool name being requested.
  • A tool-input preview so you can see what the call would do.
  • The trace id that the request belongs to.
  • Requested and decided timestamps.
  • An SLA badge that flags how long the request has been waiting, including breach and expired states.
  • Approve and Reject buttons.

You can bulk-select multiple pending requests and decide them together, with an optional shared reason applied to the whole selection. A badge in the navigation shows the current pending count so you know when work is waiting.

Decisions are written through POST /v1/approvals/{id} for a single request and POST /v1/approvals/bulk-decide for a selection.

The SDK helper

The SDK ships a requireApproval(action, options) helper that creates a pending approval in the dashboard, then long-polls until a reviewer approves or rejects it (or the timeout elapses). On approval the call returns. On rejection it throws JamjetApprovalRejected carrying the reviewer's reason. On timeout it throws as well. Wrap it in a try/catch and only proceed past it when a human approved.

import { requireApproval, JamjetApprovalRejected } from '@jamjet/cloud'

try {
  await requireApproval('production_deploy', {
    context: { service: 'auth-api', version: '2.3.1' },
    timeoutMs: 30 * 60 * 1000, // 30 minutes
  })
  // Reaches here only if a human approved.
  await deployService()
} catch (err) {
  if (err instanceof JamjetApprovalRejected) {
    console.warn('Rejected:', err.reason)
  } else {
    throw err
  }
}
import jamjet.cloud as jamjet

try:
    await jamjet.require_approval('production_deploy',
      context={'service': 'auth-api', 'version': '2.3.1'},
    )
    # Reaches here only if a human approved.
    await deploy_service()
except Exception as err:
    # Thrown on rejection or timeout.
    print('Not approved:', err)

Options

OptionDefaultDescription
timeoutMsabout 1 hourHow long to long-poll before giving up. When it elapses, the helper throws.
pollIntervalMs5 secondsHow often to check approval status while waiting.
signalnoneA standard AbortSignal. When it fires, the in-flight request is cancelled.

The declarative policy('require_approval', ...) rule is evaluated but has no observable effect today: it neither gates the call nor appears on the span. Use requireApproval() from code, or the policy-gateway flow below, for a working gate.

The policy-gateway flow

The other working path to a pending approval comes from the policy proxy. When the proxy evaluates a call and emits a WAITING_FOR_APPROVAL decision, that call surfaces as a pending request in the Approvals queue. You then decide it in the dashboard exactly as you would any other pending item. The first decision wins: once a request is approved or rejected, later attempts to decide the same request have no effect.

Define the gating in your policy proxy and resolve the resulting requests from /dashboard/approvals.

Next steps

On this page