collabSupport/services/voiceDiag/checks/executiveAssistant.js
jmcqueen d12723d010 Voicediag: store voice standards + port-hygiene checks + apply-all card
Refactor every /voicediag check to declare a top-level `standards`
object so the desired state is legible without reading run() logic
and can drive a documented reference table. Upgrade callForwarding
to error severity, tighten voicemail with three send-to-VM error
paths + a `stop_sending_to_voicemail` remediation, and add a
`disable_hoteling` remediation.

Add a port-hygiene check bucket under services/voiceDiag/checks/port
(portType, portVlan, portPoe, portEnabled) that reuses the phone-
status snapshot to enforce switchport standards. Configurable via
VOICE_STANDARD_PHONE_VLAN (default 102) and VOICE_STANDARD_ENABLED
(kill-switch). Preserve Meraki `portType`/`voiceVlan`/`dataVlan`
through the enrichment chain so the checks have clean data to read.

Add an "apply all N fixes" combined card that shows up when 2+
remediations are available. New confirm_voicediag_all /
cancel_voicediag_all actions run each fix in sequence (readable
audit trail, no per-person write-throttle stacking), accumulate
individual failures into a summary rather than aborting.

Adds regression tests asserting every check exposes .standards,
plus coverage for port checks, kill-switch, and combined-card
iteration. 63 tests in the checks file, 188 total, all green.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-08 14:14:38 -04:00

65 lines
2.5 KiB
JavaScript

// src/services/voiceDiag/checks/executiveAssistant.js
//
// Info-only check for the Executive / Executive Assistant feature.
// This isn't something a store line would normally be configured
// for; when the type is anything other than 'UNASSIGNED', calls can
// be filtered/screened through an assistant relationship in ways
// that look like "the store phone isn't behaving right". We surface
// the current type so an operator can decide whether to unassign it
// in Control Hub. No remediation button — this is intentional
// executive configuration, we don't auto-flip it.
//
// Endpoint: GET /v1/people/{personId}/features/executiveAssistant
// Scope: spark-admin:people_read
// Response shape: { type: 'UNASSIGNED' | 'EXECUTIVE' | 'EXECUTIVE_ASSISTANT' }
//
// NOTE: Webex has announced a path migration for this endpoint (see
// developer.webex.com changelog — /v1/telephony/config/people/{id}/executive
// is the new path). When that ships and starts returning 404 on the
// legacy path, swap the ENDPOINT constant. Do NOT eagerly switch
// before Cisco's cutover window closes — until then only the legacy
// path is live.
const ENDPOINT = (personId) =>
`people/${personId}/features/executiveAssistant`;
// Store-line standard: UNASSIGNED. Being an executive or exec-
// assistant on a store line routes calls through screening. No
// auto-remediation offered — unassigning has downstream implications
// (the assistant relationship needs cleanup on both sides), so we
// surface as info-only and let the operator handle it in Control Hub.
export const EXECUTIVE_ASSISTANT_STANDARDS = Object.freeze({
type: 'UNASSIGNED',
});
export const executiveAssistantCheck = {
id: 'executiveAssistant',
label: 'Executive / Assistant',
requires: ['personId'],
scope: 'spark-admin:people_read',
standards: EXECUTIVE_ASSISTANT_STANDARDS,
async run(ctx) {
const data = await ctx.webex.request('GET', ENDPOINT(ctx.personId));
const type = data?.type || 'UNASSIGNED';
if (type === EXECUTIVE_ASSISTANT_STANDARDS.type) {
return {
status: 'ok',
message: 'No executive / assistant relationship (normal for a store line).',
details: { type },
remediation: null,
};
}
return {
status: 'warn',
message:
`This line is configured as **${type}** in the executive/assistant feature. ` +
`Verify this is intentional — otherwise calls may be routed through an ` +
`assistant relationship.`,
details: { type },
remediation: null,
};
},
};