feat(ovhcloud): hold Riposte's on-call ladder in a VoIP call queue (1.1.0)
Riposte's pager can hand its on-call ladder to a marketplace instance, so that a number published to clients rings whoever is on duty without the call ever crossing the SOC's own trunk — and without spending one of its simultaneous calls, which are sold in blocks and are the reason the PBX-side mode is too expensive for a small deployment. Three commands, and they are a contract rather than this integration's own vocabulary: set_oncall_ladder (ordered numbers + ring_seconds), get_oncall_ladder (what the carrier ACTUALLY holds, so a comparison can be honest) and list_calls (the journal, after the fact). The pager offers whichever instances declare the first one; the README now documents the contract for whoever writes the next router. Extends the existing ovhcloud integration rather than adding a second one: it already carries the signed-request authentication, and making an operator enter the same application key twice would serve nobody. Three telephony_* config fields are added; every existing command is untouched, and an instance that leaves them empty is exactly the OVHcloud reader it was before. set_oncall_ladder RECONCILES rather than rebuilds. Agents are created when a number has none, reordered by position, and anybody who drops off the ladder is LOGGED OUT and removed from the queue — never deleted, because deleting throws away whatever else the operator configured on that agent and they are on the rota again next week. It also enforces the sequential strategy: publishing an ordered list into a queue set to ring everybody at once is a lie that is invisible from Riposte. Two details that would each have cost an evening: OVH accepts no "+" in a phoneNumber field, so E.164 is converted to the 00-prefixed form both ways; and a service holding several queues is REFUSED rather than guessed, because guessing publishes the on-call ladder into whichever queue the API happened to list first. Verified against a stubbed API for the first publication and for a rota handover. The signature, the error codes and the exact number format are only proved by the first real call. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TN1R4EnNFUV2Xwtd9q98tj
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
id: ovhcloud
|
||||
name: OVHcloud
|
||||
version: 1.0.0
|
||||
description: "OVHcloud (API v1) — cloud account and infrastructure visibility: read the account, list public cloud projects, and list/read dedicated servers. Signed application-key authentication; stdlib-only, no extra Python dependencies. (French vendor.)"
|
||||
changelog: "1.0.0 — Initial release: get account, list cloud projects, list/get dedicated servers."
|
||||
version: 1.1.0
|
||||
description: "OVHcloud (API v1) — cloud account and infrastructure visibility, plus VoIP on-call routing: read the account, list public cloud projects, list/read dedicated servers, and hold Riposte's on-call ladder in a telephony call queue so a client number reaches whoever is on duty without any channel on your own PBX. Signed application-key authentication; stdlib-only, no extra Python dependencies. (French vendor.)"
|
||||
changelog: "1.1.0 — On-call routing: set_oncall_ladder / get_oncall_ladder / list_calls, backed by a VoIP call queue (easyHunting). Riposte publishes the ordered on-call numbers and the carrier rings them in that order, so an inbound client call never crosses your own trunk and spends no simultaneous call on it. Needs the three telephony_* config fields; every other command is unchanged. | 1.0.0 — Initial release: get account, list cloud projects, list/get dedicated servers."
|
||||
category: cloud
|
||||
|
||||
# Per-instance configuration. Requests are signed (SHA-1) with the application
|
||||
@@ -24,6 +24,19 @@ config_schema:
|
||||
type: string
|
||||
description: "Consumer key"
|
||||
x-soar-sensitive: true
|
||||
# ── VoIP on-call routing ─────────────────────────────────────────────
|
||||
#
|
||||
# Only the three on-call commands read these. Leave them empty and this
|
||||
# integration is exactly what it was before: an OVHcloud reader.
|
||||
telephony_billing_account:
|
||||
type: string
|
||||
description: "Telephony billing account, e.g. fr-xxxxx-1 (Telecom > VoIP in the control panel)"
|
||||
telephony_service:
|
||||
type: string
|
||||
description: "The call queue's service name — the number it is attached to, in 0033xxxxxxxxx form"
|
||||
telephony_queue_id:
|
||||
type: string
|
||||
description: "Queue id, only needed when the service holds more than one queue"
|
||||
required:
|
||||
- application_key
|
||||
- application_secret
|
||||
@@ -72,3 +85,63 @@ commands:
|
||||
properties: {}
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
|
||||
# ── On-call routing ───────────────────────────────────────────────────────
|
||||
#
|
||||
# THE THREE COMMANDS RIPOSTE'S PAGER LOOKS FOR. An integration becomes an
|
||||
# on-call router by declaring set_oncall_ladder and nothing else: there is no
|
||||
# registry, no vendor list, and the other two are optional.
|
||||
#
|
||||
# What they act on is a VoIP CALL QUEUE (easyHunting): its agents are the rungs,
|
||||
# their position is the order, and the queue rings them one after another. The
|
||||
# call is answered by the carrier, so it never reaches your PBX and spends none
|
||||
# of its simultaneous calls — which is the whole reason to route an on-call
|
||||
# number this way rather than through Asterisk.
|
||||
#
|
||||
# WHAT THIS CANNOT DO, said here because it decides whether the trade is worth
|
||||
# it: a carrier's queue cannot ask the analyst to press a key before connecting
|
||||
# the client. A mobile's voicemail answering therefore ends the search as though
|
||||
# a person had picked up. Keeping ring_seconds under 20 is the only mitigation.
|
||||
- id: set_oncall_ladder
|
||||
name: ovh-set-oncall-ladder
|
||||
description: "Publish the ordered on-call numbers to the call queue. Agents are created as needed, ordered by position, and anybody no longer on the ladder is logged out — never deleted."
|
||||
# DESTRUCTIVE rather than safe_write, and deliberately above its blast radius:
|
||||
# nothing is destroyed, but this decides which phone rings at 03:00. Riposte's
|
||||
# pager calls it directly; the tier is what stops an AI agent rerouting an
|
||||
# on-call number unattended.
|
||||
risk: destructive
|
||||
inputs_schema:
|
||||
properties:
|
||||
numbers:
|
||||
type: array
|
||||
description: "Ordered numbers to ring (E.164 or 00-prefixed). Rung 1 first."
|
||||
ring_seconds:
|
||||
type: number
|
||||
description: "How long each rung rings before the next one, 5 to 30"
|
||||
required: [numbers]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: get_oncall_ladder
|
||||
name: ovh-get-oncall-ladder
|
||||
description: "Read back the ladder the queue actually holds, in its order. Reads only; places no call."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties: {}
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: list_calls
|
||||
name: ovh-list-oncall-calls
|
||||
description: "Recent inbound calls on the on-call number, for the pager journal."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
since:
|
||||
type: string
|
||||
description: "ISO-8601 instant to read from (default: 24 hours ago)"
|
||||
limit:
|
||||
type: number
|
||||
description: "Maximum calls to detail (default 50, max 200)"
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
Reference in New Issue
Block a user