CASCADE Signal Classification
The deterministic hard and soft classifier, and the four-subsystem revocation the shield-cascade endpoint runs.
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 type | Usual source |
|---|---|
sim_swap | Carrier feed or your own backend |
sim_swap_detected | A carrier lookup that flagged a swap |
port_out | Carrier feed |
number_port | Carrier feed |
number_porting | Carrier feed |
device_compromise | Device attestation failure |
ss7_intercept | Network monitoring |
account_takeover | Your own detection |
scope_poisoning | Agent 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:
| Subsystem | Table | What is written |
|---|---|---|
| Agent delegates | shield_agent_delegates | is_active = false, trust_score = 0, narrowing_reason, scope_narrowed_at, trust_updated_at |
| A2A negotiations | shield_a2a_negotiations | status = 'revoked', trust_state = 'auto_revoked', coefficient and transaction limits zeroed, allowed_scopes emptied |
| Shadow identities | shield_shadow_identities | is_active = false, revoked_at |
| BLAST sessions | shield_blast_sessions | torn_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"}'| Action | Body fields | Effect |
|---|---|---|
cascade_execute | tenant_id, signal_type | Classifies, then runs the four-subsystem cascade for a hard type or records the routing for a soft one |
cascade_dry_run | tenant_id, signal_type | Counts what each subsystem would affect and writes nothing |
cascade_history | tenant_id, optional limit | Returns 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
- Feeding a Hard Deny into Your Fraud Rules Engine: Integration Patternsengineering · 10 min read
- The Pre-Filter Pattern: Paying for Heavy Fraud Signals Only on the Suspicious Tailsecurity · 9 min read
- How to Benchmark an Identity Verification API: Latency, Freshness, Failure Injectionengineering · 12 min read