CASCADE Signal Classification

The deterministic hard and soft classifier, and the four-subsystem revocation the shield-cascade endpoint runs.

Last reviewed September 16, 2026Fresh

Overview

CASCADE is the deterministic classifier that routes a signal type to immediate revocation or to graduated trust evaluation, plus the orchestrator that fires the revocations in parallel. It is the subject of patent application 64/006,812 (PBCASCADE); the application is pending rather than granted.

The classifier is a pure function: a static lookup of the type, with no model, no probability threshold and no per-organization configuration. The same type always classifies the same way, which is what makes a revocation reproducible after the fact from the audit row alone.

Two places it runs.

  • Inside the ingest pipeline, on every signal, on every plan. A hard signal revokes two subsystems there: agent delegates and A2A negotiations.
  • At `POST /v1/shield-cascade`, on demand, where the full four-subsystem cascade runs.

Who can call the endpoint. It requires the admin scope and the Enterprise plan. The admin scope cannot be put on an API key (the provisioning allowlist excludes it), so the only lane is a tenant-admin session JWT, which is what the dashboard sends. An API key gets 403 API key missing 'admin' scope; a Starter or Pro organization gets 403 plan_required, or 402 payment_required when an Enterprise subscription has lapsed. In the dashboard it is Dashboard > Revocation.

Signal taxonomy

Hard signals revoke immediately.

Signal typeUsual source
sim_swapCarrier feed or your own backend
sim_swap_detectedA carrier lookup that flagged a swap
port_outCarrier feed
number_portCarrier feed
number_portingCarrier feed
device_compromiseDevice attestation failure
ss7_interceptNetwork monitoring
account_takeoverYour own detection
scope_poisoningAgent scope tampering

Soft signals route to graduated trust evaluation: velocity_anomaly, behavioral_anomaly, geo_anomaly, credential_leak, credential_stuffing_attempt, suspicious_login, failed_verification, device_change, unusual_device, number_recycle.

Every soft type produces the same response, and it is worth being plain about this: one trust re-evaluation of all active delegates on the organization. The engine decides what follows from each delegate's own recent activity, so the signal type does not select a per-type action such as "step-up verification" or "re-attestation prompt".

Unknown types are refused by this endpoint with 400 Unknown signal type: <value>. The classifier is not extensible at run time; a new type is a platform release. The ingest pipeline is more permissive: it stores an unrecognised type and runs any playbook that matches it.

Hard signal cascade mechanics

cascade_execute on a hard type fires four independent revocations with Promise.all, so the elapsed time is the slowest subsystem rather than the sum. Each writes, for the whole organization:

SubsystemTableWhat is written
Agent delegatesshield_agent_delegatesis_active = false, trust_score = 0, narrowing_reason, scope_narrowed_at, trust_updated_at
A2A negotiationsshield_a2a_negotiationsstatus = 'revoked', trust_state = 'auto_revoked', coefficient and transaction limits zeroed, allowed_scopes emptied
Shadow identitiesshield_shadow_identitiesis_active = false, revoked_at
BLAST sessionsshield_blast_sessionstorn_down = true, torn_down_at

Teardown is a state change rather than key destruction. The BLAST branch marks sessions torn down so a new request has to perform a fresh handshake. Nothing is overwritten or shredded.

Counts are exact. Each update asks the database for an exact affected-row count rather than counting returned rows, which would stop at 1,000 and undercount a large cascade.

Partial failure is reported. A subsystem whose query fails is logged and added to an errors array on the response; the other three still run. The response is still ok: true, so check errors rather than the status alone.

Latency is returned, never promised. Every response carries cascade_latency_ms, measured for that call. The Revocation tab shows it beside the result and in the history list. There is no published cascade latency figure to compare it against.

Every execution is audited with the correlation id, the signal type, the class and the four counts, under resource_type: "cascade".

API actions

Three actions, all POST to the same path with a tenant-admin session JWT.

curl -sS -X POST https://api.passkeybridge.io/v1/shield-cascade \
  -H "content-type: application/json" \
  -H "authorization: Bearer $PB_SESSION_JWT" \
  -d '{"action":"cascade_execute","tenant_id":"YOUR_TENANT_ID","signal_type":"sim_swap"}'
ActionBody fieldsEffect
cascade_executetenant_id, signal_typeClassifies, then runs the four-subsystem cascade for a hard type or records the routing for a soft one
cascade_dry_runtenant_id, signal_typeCounts what each subsystem would affect and writes nothing
cascade_historytenant_id, optional limitReturns cascade audit rows, newest first. Default 50, maximum 200

Hard signal response:

{
  "ok": true,
  "signal_type": "sim_swap",
  "signal_class": "hard",
  "delegates_revoked": 4,
  "negotiations_revoked": 2,
  "shadows_frozen": 1,
  "tunnels_torn_down": 3,
  "cascade_latency_ms": 118,
  "correlation_id": "pb-19a2f3c4d5e-7b1c9f04",
  "dry_run": false
}

A dry run returns the same shape with dry_run: true and the counts as "would be affected". A soft type returns signal_class: "soft", action_taken: "routed_to_trust_engine" and a message saying the evaluation is deferred to the trust engine through the ingest pipeline: the endpoint itself does not dispatch one, it records that the signal was routed.

Errors: 400 Unknown action. Supported: cascade_execute, cascade_dry_run, cascade_history, 400 tenant_id required, 400 signal_type required (must be a string), 400 Unknown signal type: <value>, 400 limit must be a positive number, 403 plan_required or a missing-scope refusal, 405 Method not allowed, 413 Payload too large, 502 Cascade execution failed, 502 Dry run failed, 502 Failed to fetch cascade history.

Audit actions are cascade_execute, cascade_dry_run and cascade_soft_routed, all with resource_type: "cascade", which is exactly what cascade_history reads back.

From the dashboard. Dashboard > Revocation offers five hard types (SIM swap, Port-out, SS7 intercept, Device compromise, Account takeover), a Dry run button, and a Run cascade button behind a confirmation dialog. The result card shows the four counts and the correlation id; History lists recent runs and soft routings. The other four hard types are reachable through the API.

Confirming it ran. Compare the four counts in the response with the Agents, Legacy identifiers and Session tunnels tabs, and find the run in History or in the audit log under resource_type: "cascade".

Related from the blog