chore: retire the Riposte Sextant integration

It pushed agent readings over HTTP into a sibling product. That product was
absorbed into the SOAR in 0.112.0, and the readings now go through the built-in
SOC Reporting integration, which writes into the same database the server
already owns — no base URL, no ingestion token, no second deployment to keep
reachable. This integration is the seam the absorption spent five phases
removing.

Nothing here is worth keeping behind: the counters, the rule that an omitted
counter stays unknown rather than zero, and the client-listing command all
exist on the other side.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-17 09:02:43 +02:00
parent 1d475a6cf5
commit c22982c8af
4 changed files with 0 additions and 241 deletions
@@ -1,80 +0,0 @@
id: riposte-sextant
name: Riposte Sextant
version: 1.1.0
description: >
Riposte Sextant — SOC steering. Push what this SOAR already knows about a
client's estate into their steering file: agent counters, measured on the
vendor console by another command and mapped in the playbook that calls this
one. Sextant never connects to anything itself; it receives. Bearer token
authentication, stdlib-only, no extra Python dependencies.
changelog: "1.1.0 — List the clients Sextant knows, and the identifier to deposit under.\n1.0.0 — Initial release: push agent counters, and a connection test."
category: reporting
config_schema:
properties:
base_url:
type: string
description: "Sextant's public URL, e.g. https://sextant.example.corp (no trailing path)"
token:
type: string
description: "Ingestion token, created in Sextant under Settings. It writes readings and nothing else: it carries no role and reads nothing."
x-soar-sensitive: true
required:
- base_url
- token
commands:
- id: push_agent_stats
name: sextant-push-agent-stats
description: >
Deposit one agent reading for a client. Counters left empty stay UNKNOWN in
Sextant — never zero: "we did not measure how many agents are in error" and
"no agent is in error" are opposite pieces of news, and the second one
reassures wrongly. Map only what the console actually returned.
# safe_write, and not read: this writes a reading into another product. It
# changes nothing on the customer's estate, which is what "safe" means here.
risk: safe_write
inputs_schema:
properties:
client_id:
type: string
description: "The client's id IN THIS SOAR. Sextant resolves its own client through the pairing already recorded on the client sheet — no extra identifier to copy, so none to get wrong."
expected:
type: number
description: "Agents the contract commits to. A commitment, not a measurement — no console knows it. Without it the coverage ratio stays unknown and the raw counters still stand."
deployed: { type: number, description: "Agents installed on the estate" }
connected: { type: number, description: "Agents talking to the console" }
disconnected: { type: number, description: "Agents known but silent" }
errored: { type: number, description: "Agents reporting a fault" }
outdated: { type: number, description: "Agents running an old version" }
unmanaged: { type: number, description: "Machines with no agent" }
required: [client_id]
outputs_schema:
properties:
client_id: { type: string, description: "The Sextant client the reading was filed under — the confirmation that the pairing pointed at the right one" }
sent: { type: object, description: "The counters actually deposited, so the run log shows what was left unmeasured" }
- id: list_clients
name: sextant-list-clients
description: >
List the clients Sextant knows and the identifier to deposit under. Answers
the question you have while writing the playbook, and the refusal you get
on the first run — "no Sextant client is paired with this SOAR client".
Clients with no pairing appear with an empty identifier: those are the ones
to go and pair, on their sheet in Sextant.
risk: read
inputs_schema: { properties: {} }
outputs_schema:
properties:
clients:
type: array
description: "code, name, soar_client_id (empty when unpaired) and whether the client is being steered"
- id: test_connection
name: sextant-test-connection
description: "Check the URL and the ingestion token, without depositing anything."
risk: read
inputs_schema: { properties: {} }
outputs_schema:
properties:
ok: { type: boolean }
@@ -1,38 +0,0 @@
import json, os, sys, urllib.error, urllib.request
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def main():
cfg = _cfg()
base = str(cfg.get("base_url") or "").rstrip("/")
if not base:
raise Exception("base_url is not configured")
req = urllib.request.Request(
base + "/api/ingest/clients",
headers={
"Accept": "application/json",
"Authorization": "Bearer " + str(cfg.get("token") or ""),
"User-Agent": "Riposte-SOAR/sextant",
},
method="GET",
)
with urllib.request.urlopen(req, timeout=30) as r:
result = json.loads(r.read() or b"{}")
clients = result.get("clients", [])
# The unpaired ones are surfaced separately rather than left to be spotted in
# a list: they are the whole reason somebody runs this command twice.
unpaired = [c.get("code") for c in clients if not c.get("soar_client_id")]
print(json.dumps({"clients": clients, "unpaired": unpaired}))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
@@ -1,85 +0,0 @@
import json, os, sys, urllib.error, urllib.request
# The six counters Sextant knows, plus the contractual expectation. The
# vocabulary is fixed on purpose: the steering portfolio compares clients to one
# another, and it can only do that if "connected" means the same thing whichever
# console produced it.
COUNTERS = ["expected", "deployed", "connected", "disconnected", "errored", "outdated", "unmanaged"]
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _post(path, body):
cfg = _cfg()
base = str(cfg.get("base_url") or "").rstrip("/")
if not base:
raise Exception("base_url is not configured")
req = urllib.request.Request(
base + path,
data=json.dumps(body).encode("utf-8"),
headers={
"Content-Type": "application/json",
"Accept": "application/json",
"Authorization": "Bearer " + str(cfg.get("token") or ""),
"User-Agent": "Riposte-SOAR/sextant",
},
method="POST",
)
with urllib.request.urlopen(req, timeout=60) as r:
raw = r.read()
return json.loads(raw) if raw else {}
def _counter(name, value):
"""Return a counter, or None when it was not measured.
An ABSENT value is dropped rather than sent as zero. That is the whole
contract: an unmapped field stays unknown in Sextant, and a zero in its
place would be a confident lie about a customer's estate.
A value that is present but not a number is REFUSED instead, and the field
is named. That is a broken mapping in the calling playbook, and it must be
visible — silently dropping it would look exactly like "not measured".
"""
if value is None or value == "":
return None
try:
n = float(value)
except (TypeError, ValueError):
raise Exception(name + " is not a number: " + repr(value))
if n != int(n):
raise Exception(name + " is not a whole number: " + repr(value))
return int(n)
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
client_id = str(inputs.get("client_id") or "").strip()
if not client_id:
raise Exception("client_id is required — it is the client's id in this SOAR")
body = {"client_id": client_id}
for name in COUNTERS:
value = _counter(name, inputs.get(name))
if value is not None:
body[name] = value
result = _post("/api/ingest/agents", body)
sent = {k: v for k, v in body.items() if k != "client_id"}
print(json.dumps({"client_id": result.get("client_id", ""), "sent": sent}))
try:
main()
except urllib.error.HTTPError as e:
# Sextant names its refusals — an unpaired client says which identifier it
# did not recognise. Passing the body through is what turns "HTTP 422" into
# something an operator can act on.
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
@@ -1,38 +0,0 @@
import json, os, sys, urllib.error, urllib.request
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def main():
cfg = _cfg()
base = str(cfg.get("base_url") or "").rstrip("/")
if not base:
raise Exception("base_url is not configured")
req = urllib.request.Request(
base + "/api/ingest/ping",
headers={
"Accept": "application/json",
"Authorization": "Bearer " + str(cfg.get("token") or ""),
"User-Agent": "Riposte-SOAR/sextant",
},
method="GET",
)
with urllib.request.urlopen(req, timeout=30) as r:
result = json.loads(r.read() or b"{}")
# The probe deposits nothing and reads nothing back but an acknowledgement:
# an ingestion token must not become a way to enumerate clients.
if not result.get("ok"):
raise Exception("unexpected response from Sextant")
print(json.dumps({"ok": True}))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)