collabSupport/services/voiceDiag/checks/wan/wanLinkState.js
jmcqueen b802383441 Add Prisma SD-WAN voice-quality enrichment for /phonestatus + /voicediag
Introduces a full Palo Alto Prisma SD-WAN integration (dual-mode SASE
OAuth 2.0 / legacy CloudGenix auth, pagination, 429 backoff, session
priming) that surfaces per-path latency/jitter/loss/MOS, site
healthscore, link state, and alarm data for a store. Wired into the
/phonestatus WAN follow-up and eight new /voicediag WAN checks graded
against ITU-T G.114 / RFC 3550 defaults (env-overridable via
WAN_STANDARD_*).

Also adds a shape-aware detail renderer for /voicediag (per-link
tables with verdict icons instead of a stringified JSON dump) and a
--window flag (15m / 1h / 6h / 24h / 1d, env default via
WAN_STANDARD_WINDOW_MINUTES) so operators can widen the look-back
without redeploying. scripts/prismaProbe.js is bundled as a CLI for
schema iteration against a live tenant.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-09 09:45:29 -04:00

92 lines
2.7 KiB
JavaScript

// src/services/voiceDiag/checks/wan/wanLinkState.js
//
// Per-path administrative + operational up/down state. Any single
// path being down at a Prisma SD-WAN site is an error-severity
// signal: SD-WAN by design tolerates one path being down (traffic
// shifts to a surviving path), but a DOWN path is still lost
// capacity + a lost failover buffer, and voice-quality problems
// are much more likely on the remaining paths under load.
//
// A path with `up === null` (LinkState metric didn't come back)
// isn't counted as down — surfaced as "unknown" in details so the
// operator can see the visibility gap.
//
// No remediation — bringing a WAN link back up is a physical /
// carrier task, not something the bot should try to automate.
import {
maybeSkippedByKillSwitch,
getLinks,
labelForLink,
} from './_helpers.js';
export const WAN_LINK_STATE_STANDARDS = Object.freeze({
perLinkUp: true,
});
export const wanLinkStateCheck = {
id: 'wanLinkState',
label: 'SD-WAN Link State',
requires: ['sdwanSite'],
scope: null,
standards: WAN_LINK_STATE_STANDARDS,
async run(ctx) {
const skip = maybeSkippedByKillSwitch(wanLinkStateCheck);
if (skip) return skip;
const links = getLinks(ctx);
if (links.length === 0) {
return {
status: 'skipped',
message: 'No WAN paths reported for this site.',
details: null,
remediation: null,
};
}
const down = links.filter((l) => l.up === false);
const unknown = links.filter((l) => l.up === null || l.up === undefined);
const up = links.filter((l) => l.up === true);
const details = {
total: links.length,
up: up.length,
down: down.length,
unknown: unknown.length,
offenders: down.map(labelForLink),
unknownLabels: unknown.map(labelForLink),
};
if (down.length > 0) {
return {
status: 'error',
message:
`${down.length} of ${links.length} WAN path(s) DOWN: ` +
down.map(labelForLink).join(', ') +
`. Voice quality on surviving paths may degrade under load.`,
details,
remediation: null,
};
}
if (unknown.length > 0 && up.length === 0) {
// Prisma returned NO LinkState samples at all — treat as a
// reporting warning, not a health error.
return {
status: 'warn',
message: `Link state unknown for all ${unknown.length} WAN path(s) (no samples from Prisma).`,
details,
remediation: null,
};
}
return {
status: 'ok',
message: `All ${up.length} WAN path(s) up.` +
(unknown.length > 0 ? ` (${unknown.length} with no state samples.)` : ''),
details,
remediation: null,
};
},
};