@absolutejs/voice 0.0.22-beta.437 → 0.0.22-beta.438
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/incidentTimeline.d.ts +10 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +817 -760
- package/dist/productionReadiness.d.ts +14 -0
- package/dist/vue/useVoiceReadinessFailures.d.ts +16 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -21310,10 +21310,10 @@ var createVoiceDeliveryRuntimeRoutes = (options) => {
|
|
|
21310
21310
|
return routes;
|
|
21311
21311
|
};
|
|
21312
21312
|
// src/operationalStatus.ts
|
|
21313
|
-
import { Elysia as
|
|
21313
|
+
import { Elysia as Elysia46 } from "elysia";
|
|
21314
21314
|
|
|
21315
21315
|
// src/productionReadiness.ts
|
|
21316
|
-
import { Elysia as
|
|
21316
|
+
import { Elysia as Elysia45 } from "elysia";
|
|
21317
21317
|
|
|
21318
21318
|
// src/handoffHealth.ts
|
|
21319
21319
|
import { Elysia as Elysia33 } from "elysia";
|
|
@@ -26434,108 +26434,666 @@ var createVoiceOpsRecoveryRoutes = (options = {}) => {
|
|
|
26434
26434
|
return routes;
|
|
26435
26435
|
};
|
|
26436
26436
|
|
|
26437
|
-
// src/
|
|
26437
|
+
// src/incidentTimeline.ts
|
|
26438
26438
|
import { Elysia as Elysia43 } from "elysia";
|
|
26439
|
-
|
|
26440
|
-
|
|
26441
|
-
|
|
26442
|
-
|
|
26443
|
-
|
|
26444
|
-
var voiceObservabilityExportSchemaId = "com.absolutejs.voice.observability-export";
|
|
26445
|
-
var createVoiceObservabilityExportSchema = () => ({
|
|
26446
|
-
id: voiceObservabilityExportSchemaId,
|
|
26447
|
-
version: voiceObservabilityExportSchemaVersion
|
|
26448
|
-
});
|
|
26449
|
-
var assertVoiceObservabilityExportSchema = (input) => {
|
|
26450
|
-
if (input.schema?.id !== voiceObservabilityExportSchemaId || input.schema?.version !== voiceObservabilityExportSchemaVersion) {
|
|
26451
|
-
throw new Error(`Unsupported voice observability export schema: ${input.schema?.id ?? "missing"}@${input.schema?.version ?? "missing"}`);
|
|
26439
|
+
var escapeHtml41 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
26440
|
+
var resolveValue = async (value) => typeof value === "function" ? await value() : value;
|
|
26441
|
+
var linkForSession = (link, sessionId) => {
|
|
26442
|
+
if (!link || !sessionId) {
|
|
26443
|
+
return;
|
|
26452
26444
|
}
|
|
26445
|
+
return typeof link === "function" ? link(sessionId) : link;
|
|
26453
26446
|
};
|
|
26454
|
-
var
|
|
26455
|
-
var
|
|
26456
|
-
var
|
|
26457
|
-
var
|
|
26458
|
-
var
|
|
26459
|
-
|
|
26460
|
-
|
|
26461
|
-
|
|
26462
|
-
|
|
26463
|
-
|
|
26464
|
-
|
|
26465
|
-
|
|
26466
|
-
|
|
26467
|
-
|
|
26468
|
-
|
|
26469
|
-
|
|
26447
|
+
var statusToSeverity = (status) => status === "fail" || status === "failed" ? "critical" : status === "warn" || status === "warning" || status === "recovered" ? "warn" : "info";
|
|
26448
|
+
var failureReplayStatusToSeverity = (status) => status === "failed" ? "critical" : status === "healthy" ? "info" : "warn";
|
|
26449
|
+
var withinWindow = (event, now, windowMs) => !windowMs || event.at >= now - windowMs;
|
|
26450
|
+
var eventStatus2 = (event) => event.severity === "critical" ? "fail" : event.severity === "warn" ? "warn" : "pass";
|
|
26451
|
+
var defaultIncidentRecoveryActions = (events, links) => {
|
|
26452
|
+
const actions = [];
|
|
26453
|
+
const add = (action) => {
|
|
26454
|
+
const key = `${action.id}:${action.sessionId ?? ""}:${action.href ?? ""}`;
|
|
26455
|
+
if (actions.some((existing) => `${existing.id}:${existing.sessionId ?? ""}:${existing.href ?? ""}` === key)) {
|
|
26456
|
+
return;
|
|
26457
|
+
}
|
|
26458
|
+
actions.push(action);
|
|
26459
|
+
};
|
|
26460
|
+
for (const event of events) {
|
|
26461
|
+
if (event.category === "delivery") {
|
|
26462
|
+
add({
|
|
26463
|
+
detail: "Ask the app to tick delivery workers or retry failed delivery queue work.",
|
|
26464
|
+
eventId: event.id,
|
|
26465
|
+
href: links.deliveryRuntime,
|
|
26466
|
+
id: "delivery.retry",
|
|
26467
|
+
label: "Retry delivery work",
|
|
26468
|
+
method: "POST",
|
|
26469
|
+
sessionId: event.sessionId
|
|
26470
|
+
});
|
|
26471
|
+
}
|
|
26472
|
+
if (event.category === "readiness" || event.category === "operational-status") {
|
|
26473
|
+
add({
|
|
26474
|
+
detail: "Refresh production readiness and proof freshness before declaring the incident resolved.",
|
|
26475
|
+
eventId: event.id,
|
|
26476
|
+
href: links.productionReadiness ?? links.operationalStatus,
|
|
26477
|
+
id: "readiness.refresh",
|
|
26478
|
+
label: "Refresh readiness proof",
|
|
26479
|
+
method: "POST",
|
|
26480
|
+
sessionId: event.sessionId
|
|
26481
|
+
});
|
|
26482
|
+
}
|
|
26483
|
+
if (event.sessionId) {
|
|
26484
|
+
add({
|
|
26485
|
+
detail: "Generate or open a support/debug artifact for the affected call.",
|
|
26486
|
+
eventId: event.id,
|
|
26487
|
+
href: linkForSession(links.supportBundle, event.sessionId) ?? linkForSession(links.callDebugger, event.sessionId),
|
|
26488
|
+
id: "support.bundle",
|
|
26489
|
+
label: "Generate support bundle",
|
|
26490
|
+
method: "POST",
|
|
26491
|
+
sessionId: event.sessionId
|
|
26492
|
+
});
|
|
26493
|
+
}
|
|
26470
26494
|
}
|
|
26471
|
-
if (
|
|
26472
|
-
|
|
26495
|
+
if (events.some((event) => event.severity !== "info")) {
|
|
26496
|
+
add({
|
|
26497
|
+
detail: "Rerun the app proof pack to confirm the current release evidence is fresh.",
|
|
26498
|
+
href: links.proofPack,
|
|
26499
|
+
id: "proof.rerun",
|
|
26500
|
+
label: "Rerun proof pack",
|
|
26501
|
+
method: "POST"
|
|
26502
|
+
});
|
|
26473
26503
|
}
|
|
26474
|
-
return;
|
|
26504
|
+
return actions;
|
|
26475
26505
|
};
|
|
26476
|
-
var
|
|
26477
|
-
|
|
26506
|
+
var worstStatus2 = (statuses) => statuses.includes("fail") ? "fail" : statuses.includes("warn") ? "warn" : "pass";
|
|
26507
|
+
var statusRank6 = (status) => status === "fail" ? 3 : status === "warn" ? 2 : status === "pass" ? 1 : 0;
|
|
26508
|
+
var isRecord3 = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
26509
|
+
var getIncidentRecoveryBody = (event) => {
|
|
26510
|
+
const payload = isRecord3(event.payload) ? event.payload : {};
|
|
26511
|
+
return isRecord3(payload.body) ? payload.body : {};
|
|
26478
26512
|
};
|
|
26479
|
-
var
|
|
26480
|
-
|
|
26481
|
-
|
|
26482
|
-
|
|
26483
|
-
|
|
26484
|
-
|
|
26485
|
-
|
|
26486
|
-
});
|
|
26487
|
-
}
|
|
26488
|
-
return schema;
|
|
26513
|
+
var getIncidentRecoveryStatus = (value) => value === "fail" || value === "pass" || value === "warn" ? value : undefined;
|
|
26514
|
+
var getIncidentRecoveryDetail = (event) => {
|
|
26515
|
+
const payload = isRecord3(event.payload) ? event.payload : {};
|
|
26516
|
+
const body = getIncidentRecoveryBody(event);
|
|
26517
|
+
const result = isRecord3(body.result) ? body.result : {};
|
|
26518
|
+
const detail = result.detail ?? payload.error;
|
|
26519
|
+
return typeof detail === "string" ? detail : undefined;
|
|
26489
26520
|
};
|
|
26490
|
-
var
|
|
26491
|
-
|
|
26492
|
-
|
|
26493
|
-
|
|
26494
|
-
|
|
26495
|
-
|
|
26496
|
-
|
|
26497
|
-
}
|
|
26521
|
+
var toIncidentRecoveryOutcomeEntry = (event) => {
|
|
26522
|
+
const body = getIncidentRecoveryBody(event);
|
|
26523
|
+
const beforeStatus = getIncidentRecoveryStatus(body.beforeStatus);
|
|
26524
|
+
const afterStatus = getIncidentRecoveryStatus(body.afterStatus);
|
|
26525
|
+
const beforeRank = statusRank6(beforeStatus);
|
|
26526
|
+
const afterRank = statusRank6(afterStatus);
|
|
26527
|
+
const outcome = event.outcome === "error" ? "failed" : beforeRank > 0 && afterRank > 0 && afterRank < beforeRank ? "improved" : beforeRank > 0 && afterRank > beforeRank ? "regressed" : "unchanged";
|
|
26528
|
+
const payload = isRecord3(event.payload) ? event.payload : {};
|
|
26529
|
+
return {
|
|
26530
|
+
actionId: event.action.replace(/^incident\./, ""),
|
|
26531
|
+
afterStatus,
|
|
26532
|
+
at: event.at,
|
|
26533
|
+
beforeStatus,
|
|
26534
|
+
detail: getIncidentRecoveryDetail(event),
|
|
26535
|
+
eventId: event.id,
|
|
26536
|
+
outcome,
|
|
26537
|
+
status: typeof payload.status === "number" ? payload.status : undefined,
|
|
26538
|
+
traceId: event.traceId
|
|
26539
|
+
};
|
|
26498
26540
|
};
|
|
26499
|
-
var
|
|
26500
|
-
|
|
26501
|
-
|
|
26502
|
-
|
|
26503
|
-
|
|
26504
|
-
|
|
26541
|
+
var buildVoiceIncidentRecoveryOutcomeReport = async (options) => {
|
|
26542
|
+
const events = options.audit ? await options.audit.list({
|
|
26543
|
+
limit: options.limit ?? 50,
|
|
26544
|
+
resourceType: "voice.ops.action",
|
|
26545
|
+
type: "operator.action"
|
|
26546
|
+
}) : [];
|
|
26547
|
+
const entries = events.filter((event) => event.action.startsWith("incident.")).map(toIncidentRecoveryOutcomeEntry).sort((left, right) => right.at - left.at);
|
|
26548
|
+
return {
|
|
26549
|
+
checkedAt: Date.now(),
|
|
26550
|
+
entries,
|
|
26551
|
+
failed: entries.filter((entry) => entry.outcome === "failed").length,
|
|
26552
|
+
improved: entries.filter((entry) => entry.outcome === "improved").length,
|
|
26553
|
+
regressed: entries.filter((entry) => entry.outcome === "regressed").length,
|
|
26554
|
+
total: entries.length,
|
|
26555
|
+
unchanged: entries.filter((entry) => entry.outcome === "unchanged").length
|
|
26556
|
+
};
|
|
26557
|
+
};
|
|
26558
|
+
var renderVoiceIncidentRecoveryOutcomeHTML = (report, options = {}) => {
|
|
26559
|
+
const title = options.title ?? "AbsoluteJS Voice Incident Recovery Outcomes";
|
|
26560
|
+
const rows = report.entries.map((entry) => `<article class="${escapeHtml41(entry.outcome)}"><span>${escapeHtml41(entry.outcome.toUpperCase())}</span><h2>${escapeHtml41(entry.actionId)}</h2><p>${escapeHtml41(new Date(entry.at).toLocaleString())}</p><strong>${escapeHtml41(entry.beforeStatus ?? "unknown")} -> ${escapeHtml41(entry.afterStatus ?? "unknown")}</strong>${entry.detail ? `<p>${escapeHtml41(entry.detail)}</p>` : ""}</article>`).join("");
|
|
26561
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml41(title)}</title><style>body{background:#10120d;color:#fbf4df;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:980px;padding:32px}.hero,article{background:#181711;border:1px solid #39301d;border-radius:24px;padding:20px}.hero{margin-bottom:16px}h1{font-size:clamp(2rem,6vw,4.5rem);line-height:.95}.summary{display:flex;flex-wrap:wrap;gap:10px}.summary span{border:1px solid #4a3f23;border-radius:999px;padding:8px 12px}section{display:grid;gap:12px}article.improved{border-color:rgba(34,197,94,.65)}article.failed,article.regressed{border-color:rgba(239,68,68,.8)}article.unchanged{border-color:rgba(245,158,11,.7)}article span{color:#fcd34d;font-weight:900;letter-spacing:.08em}article strong{display:block;font-size:1.4rem;margin:.5rem 0}p{color:#cfc5a8}</style></head><body><main><section class="hero"><span>Recovery proof</span><h1>${escapeHtml41(title)}</h1><div class="summary"><span>${String(report.improved)} improved</span><span>${String(report.unchanged)} unchanged</span><span>${String(report.regressed)} regressed</span><span>${String(report.failed)} failed</span><span>${String(report.total)} total</span></div></section><section>${rows || "<p>No incident recovery actions have been recorded.</p>"}</section></main></body></html>`;
|
|
26562
|
+
};
|
|
26563
|
+
var buildVoiceIncidentRecoveryOutcomeReadinessCheck = (report, options = {}) => {
|
|
26564
|
+
const failOnFailed = options.failOnFailed ?? true;
|
|
26565
|
+
const failOnRegressed = options.failOnRegressed ?? true;
|
|
26566
|
+
const warnWhenEmpty = options.warnWhenEmpty ?? false;
|
|
26567
|
+
const maxUnchanged = options.maxUnchanged ?? Number.POSITIVE_INFINITY;
|
|
26568
|
+
const tooManyUnchanged = report.unchanged > maxUnchanged;
|
|
26569
|
+
const status = failOnFailed && report.failed > 0 || failOnRegressed && report.regressed > 0 ? "fail" : report.failed > 0 || report.regressed > 0 || tooManyUnchanged || warnWhenEmpty && report.total === 0 ? "warn" : "pass";
|
|
26570
|
+
return {
|
|
26571
|
+
actions: status === "pass" ? [] : [
|
|
26572
|
+
{
|
|
26573
|
+
description: "Open incident recovery outcomes to inspect failed, regressed, or unchanged operator actions.",
|
|
26574
|
+
href: options.href ?? "/api/voice/incident-timeline/recovery-outcomes",
|
|
26575
|
+
label: "Open recovery outcomes"
|
|
26576
|
+
}
|
|
26577
|
+
],
|
|
26578
|
+
detail: status === "pass" ? `${report.improved} improved recovery action(s), ${report.unchanged} unchanged, ${report.regressed} regressed, and ${report.failed} failed.` : `${report.failed} failed, ${report.regressed} regressed, and ${report.unchanged} unchanged incident recovery action(s) need review.`,
|
|
26579
|
+
gateExplanation: {
|
|
26580
|
+
evidenceHref: options.href ?? "/api/voice/incident-timeline/recovery-outcomes",
|
|
26581
|
+
observed: `${report.failed} failed, ${report.regressed} regressed, ${report.unchanged} unchanged`,
|
|
26582
|
+
remediation: "Inspect recent incident recovery actions, fix failed or regressed handlers, rerun the recovery, and refresh readiness before deploy.",
|
|
26583
|
+
threshold: `failed <= ${failOnFailed ? 0 : "warn"}, regressed <= ${failOnRegressed ? 0 : "warn"}, unchanged <= ${Number.isFinite(maxUnchanged) ? maxUnchanged : "unbounded"}`,
|
|
26584
|
+
thresholdLabel: "Incident recovery outcome budget",
|
|
26585
|
+
unit: "count"
|
|
26586
|
+
},
|
|
26587
|
+
href: options.href ?? "/api/voice/incident-timeline/recovery-outcomes",
|
|
26588
|
+
label: options.label ?? "Incident recovery outcomes",
|
|
26589
|
+
status,
|
|
26590
|
+
value: `${report.improved}/${report.total} improved`
|
|
26591
|
+
};
|
|
26592
|
+
};
|
|
26593
|
+
var pushOperationalStatusEvents = (events, report, links) => {
|
|
26594
|
+
if (!report) {
|
|
26595
|
+
return;
|
|
26596
|
+
}
|
|
26597
|
+
for (const check of report.checks) {
|
|
26598
|
+
if (check.status === "pass") {
|
|
26599
|
+
continue;
|
|
26600
|
+
}
|
|
26601
|
+
events.push({
|
|
26602
|
+
action: {
|
|
26603
|
+
href: check.href ?? links.operationalStatus,
|
|
26604
|
+
label: "Open source"
|
|
26605
|
+
},
|
|
26606
|
+
at: report.checkedAt,
|
|
26607
|
+
category: check.label.toLowerCase().includes("readiness") ? "readiness" : "operational-status",
|
|
26608
|
+
detail: check.detail,
|
|
26609
|
+
href: check.href ?? links.operationalStatus,
|
|
26610
|
+
id: `operational:${check.label}`,
|
|
26611
|
+
label: check.label,
|
|
26612
|
+
severity: statusToSeverity(check.status),
|
|
26613
|
+
source: "operational-status",
|
|
26614
|
+
value: check.value
|
|
26505
26615
|
});
|
|
26506
26616
|
}
|
|
26507
26617
|
};
|
|
26508
|
-
var
|
|
26509
|
-
if (!
|
|
26510
|
-
|
|
26511
|
-
|
|
26512
|
-
|
|
26513
|
-
|
|
26618
|
+
var pushOpsRecoveryEvents = (events, report, links) => {
|
|
26619
|
+
if (!report) {
|
|
26620
|
+
return;
|
|
26621
|
+
}
|
|
26622
|
+
for (const issue of report.issues) {
|
|
26623
|
+
events.push({
|
|
26624
|
+
action: {
|
|
26625
|
+
href: issue.href ?? links.operationalStatus,
|
|
26626
|
+
label: "Inspect recovery issue"
|
|
26627
|
+
},
|
|
26628
|
+
at: report.checkedAt,
|
|
26629
|
+
category: "recovery",
|
|
26630
|
+
detail: issue.detail,
|
|
26631
|
+
href: issue.href,
|
|
26632
|
+
id: `ops-recovery:${issue.code}`,
|
|
26633
|
+
label: issue.label,
|
|
26634
|
+
severity: issue.severity === "fail" ? "critical" : "warn",
|
|
26635
|
+
source: "ops-recovery",
|
|
26636
|
+
value: issue.value
|
|
26514
26637
|
});
|
|
26515
26638
|
}
|
|
26516
|
-
|
|
26517
|
-
|
|
26518
|
-
|
|
26519
|
-
|
|
26520
|
-
|
|
26521
|
-
|
|
26522
|
-
|
|
26639
|
+
for (const session of report.failedSessions) {
|
|
26640
|
+
events.push({
|
|
26641
|
+
action: {
|
|
26642
|
+
href: session.operationsRecordHref ?? linkForSession(links.operationsRecords, session.sessionId) ?? linkForSession(links.callDebugger, session.sessionId),
|
|
26643
|
+
label: "Open affected call"
|
|
26644
|
+
},
|
|
26645
|
+
at: session.at,
|
|
26646
|
+
category: "call",
|
|
26647
|
+
detail: session.error,
|
|
26648
|
+
href: session.operationsRecordHref ?? linkForSession(links.operationsRecords, session.sessionId),
|
|
26649
|
+
id: `failed-session:${session.sessionId}:${session.at}`,
|
|
26650
|
+
label: "Failed session",
|
|
26651
|
+
sessionId: session.sessionId,
|
|
26652
|
+
severity: "critical",
|
|
26653
|
+
source: "ops-recovery",
|
|
26654
|
+
value: session.provider
|
|
26523
26655
|
});
|
|
26524
26656
|
}
|
|
26525
26657
|
};
|
|
26526
|
-
var
|
|
26527
|
-
if (!
|
|
26528
|
-
pushValidationIssue(issues, {
|
|
26529
|
-
code: "voice.observability.export.missing_field",
|
|
26530
|
-
message: `${path} must be an array.`,
|
|
26531
|
-
path
|
|
26532
|
-
});
|
|
26658
|
+
var pushMonitorEvents = (events, issues, links) => {
|
|
26659
|
+
if (!issues) {
|
|
26533
26660
|
return;
|
|
26534
26661
|
}
|
|
26535
|
-
|
|
26536
|
-
|
|
26537
|
-
|
|
26538
|
-
|
|
26662
|
+
for (const issue of issues) {
|
|
26663
|
+
if (issue.status === "resolved") {
|
|
26664
|
+
continue;
|
|
26665
|
+
}
|
|
26666
|
+
const sessionId = issue.impactedSessions[0];
|
|
26667
|
+
events.push({
|
|
26668
|
+
action: {
|
|
26669
|
+
href: issue.operationsRecordHrefs[0] ?? linkForSession(links.operationsRecords, sessionId) ?? links.monitorIssues,
|
|
26670
|
+
label: "Open monitor evidence"
|
|
26671
|
+
},
|
|
26672
|
+
at: issue.lastSeenAt,
|
|
26673
|
+
category: "monitor",
|
|
26674
|
+
detail: issue.detail,
|
|
26675
|
+
href: issue.operationsRecordHrefs[0] ?? linkForSession(links.operationsRecords, sessionId) ?? links.monitorIssues,
|
|
26676
|
+
id: `monitor:${issue.id}`,
|
|
26677
|
+
label: issue.label,
|
|
26678
|
+
sessionId,
|
|
26679
|
+
severity: issue.severity === "critical" ? "critical" : issue.severity === "warn" ? "warn" : "info",
|
|
26680
|
+
source: `monitor:${issue.monitorId}`,
|
|
26681
|
+
value: issue.value
|
|
26682
|
+
});
|
|
26683
|
+
}
|
|
26684
|
+
};
|
|
26685
|
+
var pushOperationsRecordEvents = (events, records, links) => {
|
|
26686
|
+
if (!records) {
|
|
26687
|
+
return;
|
|
26688
|
+
}
|
|
26689
|
+
for (const record of records) {
|
|
26690
|
+
if (record.status === "healthy") {
|
|
26691
|
+
continue;
|
|
26692
|
+
}
|
|
26693
|
+
const href = linkForSession(links.operationsRecords, record.sessionId);
|
|
26694
|
+
const debuggerHref = linkForSession(links.callDebugger, record.sessionId);
|
|
26695
|
+
events.push({
|
|
26696
|
+
action: {
|
|
26697
|
+
href: debuggerHref ?? href,
|
|
26698
|
+
label: debuggerHref ? "Open call debugger" : "Open operations record"
|
|
26699
|
+
},
|
|
26700
|
+
at: record.checkedAt,
|
|
26701
|
+
category: "call",
|
|
26702
|
+
detail: record.status === "failed" ? "Call operations record failed." : "Call operations record has warnings.",
|
|
26703
|
+
href,
|
|
26704
|
+
id: `operations-record:${record.sessionId}`,
|
|
26705
|
+
label: `Operations record ${record.status}`,
|
|
26706
|
+
sessionId: record.sessionId,
|
|
26707
|
+
severity: statusToSeverity(record.status),
|
|
26708
|
+
source: "operations-record",
|
|
26709
|
+
value: record.outcome.complete ? "complete" : "incomplete"
|
|
26710
|
+
});
|
|
26711
|
+
}
|
|
26712
|
+
};
|
|
26713
|
+
var pushFailureReplayEvents = (events, replays, links) => {
|
|
26714
|
+
if (!replays) {
|
|
26715
|
+
return;
|
|
26716
|
+
}
|
|
26717
|
+
for (const replay of replays) {
|
|
26718
|
+
if (replay.status === "healthy") {
|
|
26719
|
+
continue;
|
|
26720
|
+
}
|
|
26721
|
+
const href = replay.operationsRecordHref ?? linkForSession(links.failureReplay, replay.sessionId) ?? linkForSession(links.callDebugger, replay.sessionId);
|
|
26722
|
+
events.push({
|
|
26723
|
+
action: {
|
|
26724
|
+
href: linkForSession(links.callDebugger, replay.sessionId) ?? href ?? linkForSession(links.supportBundle, replay.sessionId),
|
|
26725
|
+
label: "Open replay/debug artifact"
|
|
26726
|
+
},
|
|
26727
|
+
at: replay.providers.steps[0]?.at ?? replay.media.steps[0]?.at ?? Date.now(),
|
|
26728
|
+
category: "failure-replay",
|
|
26729
|
+
detail: replay.summary.issues.join("; ") || replay.summary.userHeard.join(" ") || `Failure replay is ${replay.status}.`,
|
|
26730
|
+
href,
|
|
26731
|
+
id: `failure-replay:${replay.sessionId}`,
|
|
26732
|
+
label: `Failure replay ${replay.status}`,
|
|
26733
|
+
sessionId: replay.sessionId,
|
|
26734
|
+
severity: failureReplayStatusToSeverity(replay.status),
|
|
26735
|
+
source: "failure-replay",
|
|
26736
|
+
value: `${replay.providers.errors} provider errors / ${replay.media.errors} media errors`
|
|
26737
|
+
});
|
|
26738
|
+
}
|
|
26739
|
+
};
|
|
26740
|
+
var buildVoiceIncidentTimelineReport = async (options) => {
|
|
26741
|
+
const now = options.now ?? Date.now();
|
|
26742
|
+
const links = options.links ?? {};
|
|
26743
|
+
const [
|
|
26744
|
+
operationalStatus,
|
|
26745
|
+
opsRecovery,
|
|
26746
|
+
monitorIssues,
|
|
26747
|
+
operationsRecords,
|
|
26748
|
+
failureReplays
|
|
26749
|
+
] = await Promise.all([
|
|
26750
|
+
resolveValue(options.operationalStatus),
|
|
26751
|
+
resolveValue(options.opsRecovery),
|
|
26752
|
+
resolveValue(options.monitorIssues),
|
|
26753
|
+
resolveValue(options.operationsRecords),
|
|
26754
|
+
resolveValue(options.failureReplays)
|
|
26755
|
+
]);
|
|
26756
|
+
const events = [];
|
|
26757
|
+
pushOperationalStatusEvents(events, operationalStatus, links);
|
|
26758
|
+
pushOpsRecoveryEvents(events, opsRecovery, links);
|
|
26759
|
+
pushMonitorEvents(events, monitorIssues, links);
|
|
26760
|
+
pushOperationsRecordEvents(events, operationsRecords, links);
|
|
26761
|
+
pushFailureReplayEvents(events, failureReplays, links);
|
|
26762
|
+
const filtered = events.filter((event) => withinWindow(event, now, options.windowMs)).sort((left, right) => right.at - left.at).slice(0, options.limit ?? 50);
|
|
26763
|
+
const summary = {
|
|
26764
|
+
critical: filtered.filter((event) => event.severity === "critical").length,
|
|
26765
|
+
info: filtered.filter((event) => event.severity === "info").length,
|
|
26766
|
+
total: filtered.length,
|
|
26767
|
+
warn: filtered.filter((event) => event.severity === "warn").length
|
|
26768
|
+
};
|
|
26769
|
+
const baseReport = {
|
|
26770
|
+
events: filtered,
|
|
26771
|
+
generatedAt: now,
|
|
26772
|
+
links,
|
|
26773
|
+
status: worstStatus2(filtered.map(eventStatus2)),
|
|
26774
|
+
summary,
|
|
26775
|
+
windowMs: options.windowMs
|
|
26776
|
+
};
|
|
26777
|
+
const configuredActions = typeof options.recoveryActions === "function" ? await options.recoveryActions({
|
|
26778
|
+
events: filtered,
|
|
26779
|
+
report: baseReport
|
|
26780
|
+
}) : options.recoveryActions;
|
|
26781
|
+
return {
|
|
26782
|
+
...baseReport,
|
|
26783
|
+
actions: configuredActions === undefined ? defaultIncidentRecoveryActions(filtered, links) : [...configuredActions]
|
|
26784
|
+
};
|
|
26785
|
+
};
|
|
26786
|
+
var renderVoiceIncidentTimelineMarkdown = (report, options = {}) => {
|
|
26787
|
+
const title = options.title ?? "AbsoluteJS Voice Incident Timeline";
|
|
26788
|
+
const rows = report.events.map((event) => {
|
|
26789
|
+
const when = new Date(event.at).toISOString();
|
|
26790
|
+
const target = event.href ? ` [open](${event.href})` : "";
|
|
26791
|
+
const session = event.sessionId ? ` session=${event.sessionId}` : "";
|
|
26792
|
+
const value = event.value === undefined ? "" : ` value=${event.value}`;
|
|
26793
|
+
return `- ${when} ${event.severity.toUpperCase()} ${event.label}${session}${value}${target}${event.detail ? ` - ${event.detail}` : ""}`;
|
|
26794
|
+
}).join(`
|
|
26795
|
+
`);
|
|
26796
|
+
return `# ${title}
|
|
26797
|
+
|
|
26798
|
+
Status: ${report.status}
|
|
26799
|
+
|
|
26800
|
+
Generated: ${new Date(report.generatedAt).toISOString()}
|
|
26801
|
+
|
|
26802
|
+
Summary: ${report.summary.critical} critical, ${report.summary.warn} warn, ${report.summary.info} info, ${report.summary.total} total.
|
|
26803
|
+
|
|
26804
|
+
## Events
|
|
26805
|
+
|
|
26806
|
+
${rows || "- No incident timeline events."}
|
|
26807
|
+
|
|
26808
|
+
## Recovery Actions
|
|
26809
|
+
|
|
26810
|
+
${report.actions.map((action) => `- ${action.method ?? "GET"} ${action.id}: ${action.label}${action.href ? ` (${action.href})` : ""}${action.detail ? ` - ${action.detail}` : ""}`).join(`
|
|
26811
|
+
`) || "- No recovery actions."}
|
|
26812
|
+
`;
|
|
26813
|
+
};
|
|
26814
|
+
var renderVoiceIncidentTimelineHTML = (report, options = {}) => {
|
|
26815
|
+
const title = options.title ?? "AbsoluteJS Voice Incident Timeline";
|
|
26816
|
+
const actionPath = options.actionPath ?? "/api/voice/incident-timeline/actions";
|
|
26817
|
+
const events = report.events.map((event) => `<article class="${escapeHtml41(event.severity)}">
|
|
26818
|
+
<span>${escapeHtml41(event.severity.toUpperCase())} / ${escapeHtml41(event.category)}</span>
|
|
26819
|
+
<h2>${escapeHtml41(event.label)}</h2>
|
|
26820
|
+
<p>${escapeHtml41(new Date(event.at).toLocaleString())}${event.sessionId ? ` \xB7 session ${escapeHtml41(event.sessionId)}` : ""}</p>
|
|
26821
|
+
${event.value === undefined ? "" : `<strong>${escapeHtml41(String(event.value))}</strong>`}
|
|
26822
|
+
${event.detail ? `<p>${escapeHtml41(event.detail)}</p>` : ""}
|
|
26823
|
+
<div>${event.href ? `<a href="${escapeHtml41(event.href)}">Open source</a>` : ""}${event.action?.href ? `<a href="${escapeHtml41(event.action.href)}">${escapeHtml41(event.action.label)}</a>` : ""}</div>
|
|
26824
|
+
</article>`).join("");
|
|
26825
|
+
const actions = report.actions.map((action) => {
|
|
26826
|
+
const label = escapeHtml41(action.label);
|
|
26827
|
+
const detail = action.detail ? `<p>${escapeHtml41(action.detail)}</p>` : "";
|
|
26828
|
+
const href = action.href ? `<a href="${escapeHtml41(action.href)}">Open target</a>` : "";
|
|
26829
|
+
const control = action.method === "POST" ? `<button type="button" data-voice-incident-action="${escapeHtml41(action.id)}" ${action.disabled ? "disabled" : ""}>${label}</button>` : href;
|
|
26830
|
+
return `<article class="action"><span>${escapeHtml41(action.method ?? "GET")}</span><h2>${label}</h2>${detail}<div>${control}${href && action.method === "POST" ? href : ""}</div></article>`;
|
|
26831
|
+
}).join("");
|
|
26832
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml41(title)}</title><style>body{background:#11110d;color:#faf4df;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1100px;padding:32px}.hero{background:linear-gradient(135deg,rgba(248,113,113,.2),rgba(245,158,11,.13),rgba(34,197,94,.12));border:1px solid #39301d;border-radius:30px;margin-bottom:18px;padding:28px}.eyebrow{color:#fcd34d;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}.status{border:1px solid #575030;border-radius:999px;display:inline-flex;font-weight:900;padding:8px 12px}.status.pass{border-color:rgba(34,197,94,.65)}.status.warn{border-color:rgba(245,158,11,.75)}.status.fail{border-color:rgba(239,68,68,.85)}.grid{display:grid;gap:14px}.actions{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));margin:0 0 18px}.summary{display:flex;flex-wrap:wrap;gap:10px}.summary span{background:#181711;border:1px solid #39301d;border-radius:999px;padding:8px 12px}article{background:#181711;border:1px solid #39301d;border-radius:22px;padding:18px}article.critical{border-color:rgba(239,68,68,.85)}article.warn{border-color:rgba(245,158,11,.75)}article.info{border-color:rgba(34,197,94,.55)}article.action{border-color:#5b4a22}article span{color:#fcd34d;font-size:.78rem;font-weight:900;letter-spacing:.08em}article h2{margin:.35rem 0}.muted,article p{color:#cfc5a8}article strong{display:block;font-size:1.3rem;margin:.5rem 0}a{color:#fde68a;margin-right:12px}button{background:#fcd34d;border:0;border-radius:999px;color:#171307;cursor:pointer;font-weight:900;padding:10px 14px}button:disabled{cursor:not-allowed;opacity:.55}</style></head><body><main><section class="hero"><p class="eyebrow">Operational triage</p><h1>${escapeHtml41(title)}</h1><p class="status ${escapeHtml41(report.status)}">Overall: ${escapeHtml41(report.status.toUpperCase())}</p><p class="muted">Generated ${escapeHtml41(new Date(report.generatedAt).toLocaleString())}</p><div class="summary"><span>${String(report.summary.critical)} critical</span><span>${String(report.summary.warn)} warn</span><span>${String(report.summary.info)} info</span><span>${String(report.summary.total)} total</span></div></section><h2>Recovery actions</h2><section class="actions">${actions || '<article class="action"><span>NONE</span><h2>No recovery actions</h2><p>No executable actions are available for this report.</p></article>'}</section><h2>Timeline</h2><section class="grid">${events || '<article class="info"><span>INFO</span><h2>No incident events</h2><p>No non-pass operational events were found in this window.</p></article>'}</section></main><script>const voiceIncidentActionPath=${JSON.stringify(actionPath)};document.querySelectorAll("[data-voice-incident-action]").forEach((button)=>{button.addEventListener("click",async()=>{const id=button.getAttribute("data-voice-incident-action");if(!id)return;button.disabled=true;const original=button.textContent;button.textContent="Running...";try{const response=await fetch(voiceIncidentActionPath+"/"+encodeURIComponent(id),{method:"POST"});button.textContent=response.ok?"Done":"Failed";if(response.ok)setTimeout(()=>location.reload(),700)}catch{button.textContent="Failed"}finally{setTimeout(()=>{button.disabled=false;button.textContent=original},1600)}})});</script></body></html>`;
|
|
26833
|
+
};
|
|
26834
|
+
var createVoiceIncidentTimelineRoutes = (options) => {
|
|
26835
|
+
const path = options.path ?? "/api/voice/incident-timeline";
|
|
26836
|
+
const htmlPath = options.htmlPath === undefined ? "/voice/incident-timeline" : options.htmlPath;
|
|
26837
|
+
const markdownPath = options.markdownPath === undefined ? "/voice/incident-timeline.md" : options.markdownPath;
|
|
26838
|
+
const actionPath = options.actionPath === undefined ? "/api/voice/incident-timeline/actions" : options.actionPath;
|
|
26839
|
+
const recoveryOutcomePath = options.recoveryOutcomePath === undefined ? "/api/voice/incident-timeline/recovery-outcomes" : options.recoveryOutcomePath;
|
|
26840
|
+
const recoveryOutcomeHtmlPath = options.recoveryOutcomeHtmlPath === undefined ? "/voice/incident-recovery-outcomes" : options.recoveryOutcomeHtmlPath;
|
|
26841
|
+
const routes = new Elysia43({
|
|
26842
|
+
name: options.name ?? "absolutejs-voice-incident-timeline"
|
|
26843
|
+
}).get(path, async () => {
|
|
26844
|
+
const report = await buildVoiceIncidentTimelineReport(options);
|
|
26845
|
+
return new Response(JSON.stringify(report), {
|
|
26846
|
+
headers: {
|
|
26847
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
26848
|
+
...options.headers
|
|
26849
|
+
},
|
|
26850
|
+
status: report.status === "fail" ? 503 : 200
|
|
26851
|
+
});
|
|
26852
|
+
});
|
|
26853
|
+
if (htmlPath !== false) {
|
|
26854
|
+
routes.get(htmlPath, async () => {
|
|
26855
|
+
const report = await buildVoiceIncidentTimelineReport(options);
|
|
26856
|
+
const body = await (options.render ?? ((input) => renderVoiceIncidentTimelineHTML(input, {
|
|
26857
|
+
actionPath: actionPath === false ? undefined : actionPath,
|
|
26858
|
+
title: options.title
|
|
26859
|
+
})))(report);
|
|
26860
|
+
return new Response(body, {
|
|
26861
|
+
headers: {
|
|
26862
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
26863
|
+
...options.headers
|
|
26864
|
+
}
|
|
26865
|
+
});
|
|
26866
|
+
});
|
|
26867
|
+
}
|
|
26868
|
+
if (markdownPath !== false) {
|
|
26869
|
+
routes.get(markdownPath, async () => {
|
|
26870
|
+
const report = await buildVoiceIncidentTimelineReport(options);
|
|
26871
|
+
return new Response(renderVoiceIncidentTimelineMarkdown(report, {
|
|
26872
|
+
title: options.title
|
|
26873
|
+
}), {
|
|
26874
|
+
headers: {
|
|
26875
|
+
"Content-Type": "text/markdown; charset=utf-8",
|
|
26876
|
+
...options.headers
|
|
26877
|
+
}
|
|
26878
|
+
});
|
|
26879
|
+
});
|
|
26880
|
+
}
|
|
26881
|
+
if (actionPath !== false) {
|
|
26882
|
+
routes.get(actionPath, async () => {
|
|
26883
|
+
const report = await buildVoiceIncidentTimelineReport(options);
|
|
26884
|
+
return new Response(JSON.stringify({
|
|
26885
|
+
actions: report.actions,
|
|
26886
|
+
generatedAt: report.generatedAt,
|
|
26887
|
+
status: report.status
|
|
26888
|
+
}), {
|
|
26889
|
+
headers: {
|
|
26890
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
26891
|
+
...options.headers
|
|
26892
|
+
}
|
|
26893
|
+
});
|
|
26894
|
+
}).post(`${actionPath}/:actionId`, async ({ params, request }) => {
|
|
26895
|
+
const actionId = params.actionId;
|
|
26896
|
+
const report = await buildVoiceIncidentTimelineReport(options);
|
|
26897
|
+
const action = report.actions.find((item) => item.id === actionId);
|
|
26898
|
+
const handler = options.actionHandlers?.[actionId];
|
|
26899
|
+
if (!action) {
|
|
26900
|
+
return new Response(JSON.stringify({
|
|
26901
|
+
actionId,
|
|
26902
|
+
ok: false,
|
|
26903
|
+
status: "not_found"
|
|
26904
|
+
}), {
|
|
26905
|
+
headers: {
|
|
26906
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
26907
|
+
...options.headers
|
|
26908
|
+
},
|
|
26909
|
+
status: 404
|
|
26910
|
+
});
|
|
26911
|
+
}
|
|
26912
|
+
if (action.disabled || action.method !== "POST" || !handler) {
|
|
26913
|
+
return new Response(JSON.stringify({
|
|
26914
|
+
actionId,
|
|
26915
|
+
ok: false,
|
|
26916
|
+
status: action.disabled ? "disabled" : "not_executable"
|
|
26917
|
+
}), {
|
|
26918
|
+
headers: {
|
|
26919
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
26920
|
+
...options.headers
|
|
26921
|
+
},
|
|
26922
|
+
status: 409
|
|
26923
|
+
});
|
|
26924
|
+
}
|
|
26925
|
+
const result = await handler({
|
|
26926
|
+
action,
|
|
26927
|
+
actionId,
|
|
26928
|
+
report,
|
|
26929
|
+
request
|
|
26930
|
+
});
|
|
26931
|
+
const status = result.ok ? 200 : 500;
|
|
26932
|
+
const afterReport = await buildVoiceIncidentTimelineReport(options);
|
|
26933
|
+
const resultWithStatus = {
|
|
26934
|
+
...result,
|
|
26935
|
+
afterStatus: result.afterStatus ?? afterReport.status,
|
|
26936
|
+
beforeStatus: result.beforeStatus ?? report.status
|
|
26937
|
+
};
|
|
26938
|
+
await recordVoiceOpsActionAudit({
|
|
26939
|
+
actionId: `incident.${actionId}`,
|
|
26940
|
+
body: {
|
|
26941
|
+
action,
|
|
26942
|
+
afterStatus: resultWithStatus.afterStatus,
|
|
26943
|
+
beforeStatus: resultWithStatus.beforeStatus,
|
|
26944
|
+
eventIds: report.events.map((event) => event.id),
|
|
26945
|
+
result
|
|
26946
|
+
},
|
|
26947
|
+
error: result.ok ? undefined : result.detail ?? result.status,
|
|
26948
|
+
ok: result.ok,
|
|
26949
|
+
ranAt: Date.now(),
|
|
26950
|
+
status
|
|
26951
|
+
}, {
|
|
26952
|
+
audit: options.audit,
|
|
26953
|
+
trace: options.trace
|
|
26954
|
+
});
|
|
26955
|
+
return new Response(JSON.stringify(resultWithStatus), {
|
|
26956
|
+
headers: {
|
|
26957
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
26958
|
+
...options.headers
|
|
26959
|
+
},
|
|
26960
|
+
status
|
|
26961
|
+
});
|
|
26962
|
+
});
|
|
26963
|
+
}
|
|
26964
|
+
if (recoveryOutcomePath !== false) {
|
|
26965
|
+
routes.get(recoveryOutcomePath, async () => {
|
|
26966
|
+
const report = await buildVoiceIncidentRecoveryOutcomeReport({
|
|
26967
|
+
audit: options.audit
|
|
26968
|
+
});
|
|
26969
|
+
return new Response(JSON.stringify(report), {
|
|
26970
|
+
headers: {
|
|
26971
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
26972
|
+
...options.headers
|
|
26973
|
+
}
|
|
26974
|
+
});
|
|
26975
|
+
});
|
|
26976
|
+
}
|
|
26977
|
+
if (recoveryOutcomeHtmlPath !== false) {
|
|
26978
|
+
routes.get(recoveryOutcomeHtmlPath, async () => {
|
|
26979
|
+
const report = await buildVoiceIncidentRecoveryOutcomeReport({
|
|
26980
|
+
audit: options.audit
|
|
26981
|
+
});
|
|
26982
|
+
return new Response(renderVoiceIncidentRecoveryOutcomeHTML(report, {
|
|
26983
|
+
title: `${options.title ?? "AbsoluteJS Voice Incident Timeline"} Recovery Outcomes`
|
|
26984
|
+
}), {
|
|
26985
|
+
headers: {
|
|
26986
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
26987
|
+
...options.headers
|
|
26988
|
+
}
|
|
26989
|
+
});
|
|
26990
|
+
});
|
|
26991
|
+
}
|
|
26992
|
+
return routes;
|
|
26993
|
+
};
|
|
26994
|
+
|
|
26995
|
+
// src/observabilityExport.ts
|
|
26996
|
+
import { Elysia as Elysia44 } from "elysia";
|
|
26997
|
+
import { Database as Database4 } from "bun:sqlite";
|
|
26998
|
+
import { createHash } from "crypto";
|
|
26999
|
+
import { mkdir as mkdir2, readFile, stat, unlink } from "fs/promises";
|
|
27000
|
+
import { join as join2 } from "path";
|
|
27001
|
+
var voiceObservabilityExportSchemaVersion = "1.0.0";
|
|
27002
|
+
var voiceObservabilityExportSchemaId = "com.absolutejs.voice.observability-export";
|
|
27003
|
+
var createVoiceObservabilityExportSchema = () => ({
|
|
27004
|
+
id: voiceObservabilityExportSchemaId,
|
|
27005
|
+
version: voiceObservabilityExportSchemaVersion
|
|
27006
|
+
});
|
|
27007
|
+
var assertVoiceObservabilityExportSchema = (input) => {
|
|
27008
|
+
if (input.schema?.id !== voiceObservabilityExportSchemaId || input.schema?.version !== voiceObservabilityExportSchemaVersion) {
|
|
27009
|
+
throw new Error(`Unsupported voice observability export schema: ${input.schema?.id ?? "missing"}@${input.schema?.version ?? "missing"}`);
|
|
27010
|
+
}
|
|
27011
|
+
};
|
|
27012
|
+
var isRecord4 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
27013
|
+
var isStatus2 = (value) => value === "fail" || value === "pass" || value === "warn";
|
|
27014
|
+
var getRecord2 = (value, key) => isRecord4(value) && isRecord4(value[key]) ? value[key] : undefined;
|
|
27015
|
+
var getRecordArray = (value, key) => isRecord4(value) && Array.isArray(value[key]) ? value[key] : undefined;
|
|
27016
|
+
var inferVoiceObservabilityExportRecordKind = (record) => {
|
|
27017
|
+
if (isRecord4(record.manifest) && isRecord4(record.artifactIndex)) {
|
|
27018
|
+
return "database-record";
|
|
27019
|
+
}
|
|
27020
|
+
if (Array.isArray(record.receipts)) {
|
|
27021
|
+
return "delivery-history";
|
|
27022
|
+
}
|
|
27023
|
+
if (typeof record.runId === "string" && Array.isArray(record.destinations)) {
|
|
27024
|
+
return "delivery-receipt";
|
|
27025
|
+
}
|
|
27026
|
+
if (Array.isArray(record.destinations) && isRecord4(record.summary) && typeof record.exportStatus === "string") {
|
|
27027
|
+
return "delivery-report";
|
|
27028
|
+
}
|
|
27029
|
+
if (Array.isArray(record.artifacts) && isRecord4(record.summary)) {
|
|
27030
|
+
return Array.isArray(record.envelopes) ? "manifest" : "artifact-index";
|
|
27031
|
+
}
|
|
27032
|
+
return;
|
|
27033
|
+
};
|
|
27034
|
+
var pushValidationIssue = (issues, issue) => {
|
|
27035
|
+
issues.push(issue);
|
|
27036
|
+
};
|
|
27037
|
+
var requireRecordSchema = (issues, record, path) => {
|
|
27038
|
+
const schema = getRecord2(record, "schema");
|
|
27039
|
+
if (schema?.id !== voiceObservabilityExportSchemaId || schema?.version !== voiceObservabilityExportSchemaVersion) {
|
|
27040
|
+
pushValidationIssue(issues, {
|
|
27041
|
+
code: "voice.observability.export.unsupported_schema",
|
|
27042
|
+
message: `Unsupported voice observability export schema: ${schema?.id ?? "missing"}@${schema?.version ?? "missing"}`,
|
|
27043
|
+
path: `${path}.schema`
|
|
27044
|
+
});
|
|
27045
|
+
}
|
|
27046
|
+
return schema;
|
|
27047
|
+
};
|
|
27048
|
+
var requireArrayField = (issues, record, key, path) => {
|
|
27049
|
+
if (!Array.isArray(record[key])) {
|
|
27050
|
+
pushValidationIssue(issues, {
|
|
27051
|
+
code: "voice.observability.export.missing_field",
|
|
27052
|
+
message: `${path}.${key} must be an array.`,
|
|
27053
|
+
path: `${path}.${key}`
|
|
27054
|
+
});
|
|
27055
|
+
}
|
|
27056
|
+
};
|
|
27057
|
+
var requireNumberField = (issues, record, key, path) => {
|
|
27058
|
+
if (typeof record[key] !== "number") {
|
|
27059
|
+
pushValidationIssue(issues, {
|
|
27060
|
+
code: "voice.observability.export.missing_field",
|
|
27061
|
+
message: `${path}.${key} must be a number.`,
|
|
27062
|
+
path: `${path}.${key}`
|
|
27063
|
+
});
|
|
27064
|
+
}
|
|
27065
|
+
};
|
|
27066
|
+
var requireStatusField = (issues, record, key, path) => {
|
|
27067
|
+
if (!isStatus2(record[key])) {
|
|
27068
|
+
pushValidationIssue(issues, {
|
|
27069
|
+
code: "voice.observability.export.missing_field",
|
|
27070
|
+
message: `${path}.${key} must be pass, warn, or fail.`,
|
|
27071
|
+
path: `${path}.${key}`
|
|
27072
|
+
});
|
|
27073
|
+
}
|
|
27074
|
+
};
|
|
27075
|
+
var requireDeliveryDestinationStatusField = (issues, record, key, path) => {
|
|
27076
|
+
if (record[key] !== "delivered" && record[key] !== "failed") {
|
|
27077
|
+
pushValidationIssue(issues, {
|
|
27078
|
+
code: "voice.observability.export.missing_field",
|
|
27079
|
+
message: `${path}.${key} must be delivered or failed.`,
|
|
27080
|
+
path: `${path}.${key}`
|
|
27081
|
+
});
|
|
27082
|
+
}
|
|
27083
|
+
};
|
|
27084
|
+
var validateDeliveryDestinations = (issues, destinations, path) => {
|
|
27085
|
+
if (!destinations) {
|
|
27086
|
+
pushValidationIssue(issues, {
|
|
27087
|
+
code: "voice.observability.export.missing_field",
|
|
27088
|
+
message: `${path} must be an array.`,
|
|
27089
|
+
path
|
|
27090
|
+
});
|
|
27091
|
+
return;
|
|
27092
|
+
}
|
|
27093
|
+
destinations.forEach((destination, index) => {
|
|
27094
|
+
const destinationPath = `${path}.${index}`;
|
|
27095
|
+
if (!isRecord4(destination)) {
|
|
27096
|
+
pushValidationIssue(issues, {
|
|
26539
27097
|
code: "voice.observability.export.invalid_shape",
|
|
26540
27098
|
message: `${destinationPath} must be an object.`,
|
|
26541
27099
|
path: destinationPath
|
|
@@ -26555,7 +27113,7 @@ var validateDeliveryDestinations = (issues, destinations, path) => {
|
|
|
26555
27113
|
};
|
|
26556
27114
|
var validateVoiceObservabilityExportRecord = (input, options = {}) => {
|
|
26557
27115
|
const issues = [];
|
|
26558
|
-
if (!
|
|
27116
|
+
if (!isRecord4(input)) {
|
|
26559
27117
|
return {
|
|
26560
27118
|
issues: [
|
|
26561
27119
|
{
|
|
@@ -26590,21 +27148,21 @@ var validateVoiceObservabilityExportRecord = (input, options = {}) => {
|
|
|
26590
27148
|
requireArrayField(issues, input, "sessionIds", "$");
|
|
26591
27149
|
requireNumberField(issues, input, "checkedAt", "$");
|
|
26592
27150
|
requireStatusField(issues, input, "status", "$");
|
|
26593
|
-
if (!
|
|
27151
|
+
if (!isRecord4(input.deliveries)) {
|
|
26594
27152
|
pushValidationIssue(issues, {
|
|
26595
27153
|
code: "voice.observability.export.missing_field",
|
|
26596
27154
|
message: "$.deliveries must be an object.",
|
|
26597
27155
|
path: "$.deliveries"
|
|
26598
27156
|
});
|
|
26599
27157
|
}
|
|
26600
|
-
if (!
|
|
27158
|
+
if (!isRecord4(input.redaction)) {
|
|
26601
27159
|
pushValidationIssue(issues, {
|
|
26602
27160
|
code: "voice.observability.export.missing_field",
|
|
26603
27161
|
message: "$.redaction must be an object.",
|
|
26604
27162
|
path: "$.redaction"
|
|
26605
27163
|
});
|
|
26606
27164
|
}
|
|
26607
|
-
if (!
|
|
27165
|
+
if (!isRecord4(input.summary)) {
|
|
26608
27166
|
pushValidationIssue(issues, {
|
|
26609
27167
|
code: "voice.observability.export.missing_field",
|
|
26610
27168
|
message: "$.summary must be an object.",
|
|
@@ -26616,7 +27174,7 @@ var validateVoiceObservabilityExportRecord = (input, options = {}) => {
|
|
|
26616
27174
|
requireArrayField(issues, input, "artifacts", "$");
|
|
26617
27175
|
requireNumberField(issues, input, "checkedAt", "$");
|
|
26618
27176
|
requireStatusField(issues, input, "status", "$");
|
|
26619
|
-
if (!
|
|
27177
|
+
if (!isRecord4(input.summary)) {
|
|
26620
27178
|
pushValidationIssue(issues, {
|
|
26621
27179
|
code: "voice.observability.export.missing_field",
|
|
26622
27180
|
message: "$.summary must be an object.",
|
|
@@ -26628,7 +27186,7 @@ var validateVoiceObservabilityExportRecord = (input, options = {}) => {
|
|
|
26628
27186
|
requireNumberField(issues, input, "checkedAt", "$");
|
|
26629
27187
|
requireStatusField(issues, input, "status", "$");
|
|
26630
27188
|
requireStatusField(issues, input, "exportStatus", "$");
|
|
26631
|
-
if (!
|
|
27189
|
+
if (!isRecord4(input.manifest)) {
|
|
26632
27190
|
pushValidationIssue(issues, {
|
|
26633
27191
|
code: "voice.observability.export.missing_field",
|
|
26634
27192
|
message: "$.manifest must be an object.",
|
|
@@ -26642,7 +27200,7 @@ var validateVoiceObservabilityExportRecord = (input, options = {}) => {
|
|
|
26642
27200
|
path: `$.manifest${issue.path.slice(1)}`
|
|
26643
27201
|
})));
|
|
26644
27202
|
}
|
|
26645
|
-
if (!
|
|
27203
|
+
if (!isRecord4(input.artifactIndex)) {
|
|
26646
27204
|
pushValidationIssue(issues, {
|
|
26647
27205
|
code: "voice.observability.export.missing_field",
|
|
26648
27206
|
message: "$.artifactIndex must be an object.",
|
|
@@ -26842,11 +27400,11 @@ var buildObservabilityExportDatabaseRecord = (input) => ({
|
|
|
26842
27400
|
});
|
|
26843
27401
|
var parseObservabilityExportJson = (value) => typeof value === "string" ? JSON.parse(value) : value;
|
|
26844
27402
|
var collectReplayDeliveryDestinations = (value) => {
|
|
26845
|
-
if (!
|
|
27403
|
+
if (!isRecord4(value)) {
|
|
26846
27404
|
return [];
|
|
26847
27405
|
}
|
|
26848
27406
|
if (Array.isArray(value.destinations)) {
|
|
26849
|
-
return value.destinations.filter((destination) =>
|
|
27407
|
+
return value.destinations.filter((destination) => isRecord4(destination));
|
|
26850
27408
|
}
|
|
26851
27409
|
if (Array.isArray(value.receipts)) {
|
|
26852
27410
|
return value.receipts.flatMap((receipt) => collectReplayDeliveryDestinations(receipt));
|
|
@@ -26855,8 +27413,8 @@ var collectReplayDeliveryDestinations = (value) => {
|
|
|
26855
27413
|
};
|
|
26856
27414
|
var replayIssueSeverity = (status) => status === "fail" ? "fail" : "warn";
|
|
26857
27415
|
var buildVoiceObservabilityExportReplayReport = (records) => {
|
|
26858
|
-
const manifest = records.manifest ?? (
|
|
26859
|
-
const artifactIndex = records.artifactIndex ?? (
|
|
27416
|
+
const manifest = records.manifest ?? (isRecord4(records.databaseRecord) ? records.databaseRecord.manifest : undefined);
|
|
27417
|
+
const artifactIndex = records.artifactIndex ?? (isRecord4(records.databaseRecord) ? records.databaseRecord.artifactIndex : undefined);
|
|
26860
27418
|
const validations = {
|
|
26861
27419
|
artifactIndex: validateVoiceObservabilityExportRecord(artifactIndex, {
|
|
26862
27420
|
kind: "artifact-index"
|
|
@@ -26881,12 +27439,12 @@ var buildVoiceObservabilityExportReplayReport = (records) => {
|
|
|
26881
27439
|
kind,
|
|
26882
27440
|
issue
|
|
26883
27441
|
})) ?? []);
|
|
26884
|
-
const manifestRecord =
|
|
26885
|
-
const artifactIndexRecord =
|
|
27442
|
+
const manifestRecord = isRecord4(manifest) ? manifest : undefined;
|
|
27443
|
+
const artifactIndexRecord = isRecord4(artifactIndex) ? artifactIndex : undefined;
|
|
26886
27444
|
const artifacts = [
|
|
26887
27445
|
...Array.isArray(manifestRecord?.artifacts) ? manifestRecord.artifacts : [],
|
|
26888
27446
|
...Array.isArray(artifactIndexRecord?.artifacts) ? artifactIndexRecord.artifacts : []
|
|
26889
|
-
].filter((artifact) =>
|
|
27447
|
+
].filter((artifact) => isRecord4(artifact));
|
|
26890
27448
|
const failedArtifacts = artifacts.filter((artifact) => artifact.status === "fail");
|
|
26891
27449
|
const deliveryDestinations = [
|
|
26892
27450
|
...collectReplayDeliveryDestinations(records.deliveryReport),
|
|
@@ -26895,7 +27453,7 @@ var buildVoiceObservabilityExportReplayReport = (records) => {
|
|
|
26895
27453
|
];
|
|
26896
27454
|
const failedDeliveryDestinations = deliveryDestinations.filter((destination) => destination.status === "failed");
|
|
26897
27455
|
const issues = [
|
|
26898
|
-
...!records.manifest && !
|
|
27456
|
+
...!records.manifest && !isRecord4(records.databaseRecord) ? [
|
|
26899
27457
|
{
|
|
26900
27458
|
code: "voice.observability.export_replay.missing_record",
|
|
26901
27459
|
label: "Export manifest",
|
|
@@ -26903,7 +27461,7 @@ var buildVoiceObservabilityExportReplayReport = (records) => {
|
|
|
26903
27461
|
value: "manifest"
|
|
26904
27462
|
}
|
|
26905
27463
|
] : [],
|
|
26906
|
-
...!records.artifactIndex && !
|
|
27464
|
+
...!records.artifactIndex && !isRecord4(records.databaseRecord) ? [
|
|
26907
27465
|
{
|
|
26908
27466
|
code: "voice.observability.export_replay.missing_record",
|
|
26909
27467
|
label: "Artifact index",
|
|
@@ -27081,7 +27639,7 @@ var loadVoiceObservabilityExportReplaySource = async (source) => {
|
|
|
27081
27639
|
};
|
|
27082
27640
|
var replayVoiceObservabilityExport = async (source) => buildVoiceObservabilityExportReplayReport(await loadVoiceObservabilityExportReplaySource(source));
|
|
27083
27641
|
var escapeObservabilityReplayHtml = (value) => String(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
27084
|
-
var isVoiceObservabilityExportReplayReport = (value) =>
|
|
27642
|
+
var isVoiceObservabilityExportReplayReport = (value) => isRecord4(value) && isRecord4(value.summary) && isRecord4(value.records) && Array.isArray(value.issues) && typeof value.checkedAt === "number" && isStatus2(value.status);
|
|
27085
27643
|
var resolveVoiceObservabilityExportReplayReport = async (input) => {
|
|
27086
27644
|
const resolved = typeof input === "function" ? await input() : input;
|
|
27087
27645
|
return isVoiceObservabilityExportReplayReport(resolved) ? resolved : replayVoiceObservabilityExport(resolved);
|
|
@@ -27100,7 +27658,7 @@ var createVoiceObservabilityExportReplayRoutes = (options) => {
|
|
|
27100
27658
|
...options.headers ?? {}
|
|
27101
27659
|
};
|
|
27102
27660
|
const buildReport = () => resolveVoiceObservabilityExportReplayReport(options.source);
|
|
27103
|
-
const app = new
|
|
27661
|
+
const app = new Elysia44({
|
|
27104
27662
|
name: options.name ?? "absolute-voice-observability-export-replay"
|
|
27105
27663
|
});
|
|
27106
27664
|
app.get(path, async () => Response.json(await buildReport(), { headers }));
|
|
@@ -27966,7 +28524,7 @@ var createVoiceObservabilityExportRoutes = (options = {}) => {
|
|
|
27966
28524
|
artifactDownload: options.links?.artifactDownload ?? (artifactDownloadPath ? (artifact) => `${artifactDownloadPath}/${encodeURIComponent(artifact.id)}` : undefined)
|
|
27967
28525
|
}
|
|
27968
28526
|
});
|
|
27969
|
-
const app = new
|
|
28527
|
+
const app = new Elysia44({
|
|
27970
28528
|
name: options.name ?? "absolute-voice-observability-export"
|
|
27971
28529
|
});
|
|
27972
28530
|
app.get(path, async () => Response.json(await buildReport(), { headers }));
|
|
@@ -28073,7 +28631,7 @@ var buildVoiceReadinessRecoveryActions = (input, options = {}) => {
|
|
|
28073
28631
|
sourceChecks: sourceChecks.length
|
|
28074
28632
|
};
|
|
28075
28633
|
};
|
|
28076
|
-
var
|
|
28634
|
+
var escapeHtml42 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
28077
28635
|
var formatVoiceProofFreshnessDuration = (valueMs) => {
|
|
28078
28636
|
if (valueMs < 1000) {
|
|
28079
28637
|
return `${Math.max(0, Math.round(valueMs))}ms`;
|
|
@@ -28628,6 +29186,15 @@ var resolveOpsRecovery = async (options, input) => {
|
|
|
28628
29186
|
}
|
|
28629
29187
|
return options.opsRecovery;
|
|
28630
29188
|
};
|
|
29189
|
+
var resolveIncidentRecoveryOutcomes = async (options, input) => {
|
|
29190
|
+
if (!options.incidentRecoveryOutcomes) {
|
|
29191
|
+
return;
|
|
29192
|
+
}
|
|
29193
|
+
if (typeof options.incidentRecoveryOutcomes === "function") {
|
|
29194
|
+
return options.incidentRecoveryOutcomes(input);
|
|
29195
|
+
}
|
|
29196
|
+
return options.incidentRecoveryOutcomes;
|
|
29197
|
+
};
|
|
28631
29198
|
var resolveObservabilityExport = async (options, input) => {
|
|
28632
29199
|
if (!options.observabilityExport) {
|
|
28633
29200
|
return;
|
|
@@ -28886,6 +29453,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
28886
29453
|
bargeInReports,
|
|
28887
29454
|
campaignReadiness,
|
|
28888
29455
|
opsRecovery,
|
|
29456
|
+
incidentRecoveryOutcomes,
|
|
28889
29457
|
observabilityExport,
|
|
28890
29458
|
observabilityExportDeliveryHistory,
|
|
28891
29459
|
observabilityExportReplay,
|
|
@@ -28933,6 +29501,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
28933
29501
|
time("bargeInReports", () => resolveBargeInReports(options, { query, request })),
|
|
28934
29502
|
time("campaignReadiness", () => resolveCampaignReadiness(options, { query, request })),
|
|
28935
29503
|
time("opsRecovery", () => resolveOpsRecovery(options, { query, request })),
|
|
29504
|
+
time("incidentRecoveryOutcomes", () => resolveIncidentRecoveryOutcomes(options, { query, request })),
|
|
28936
29505
|
time("observabilityExport", () => resolveObservabilityExport(options, { query, request })),
|
|
28937
29506
|
time("observabilityExportDeliveryHistory", () => resolveObservabilityExportDeliveryHistory(options, { query, request })),
|
|
28938
29507
|
time("observabilityExportReplay", () => resolveObservabilityExportReplay(options, { query, request })),
|
|
@@ -28953,6 +29522,10 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
28953
29522
|
mediaPipeline,
|
|
28954
29523
|
telephonyMedia
|
|
28955
29524
|
});
|
|
29525
|
+
const incidentRecoveryOutcomeReadiness = incidentRecoveryOutcomes && options.incidentRecoveryOutcomeReadiness !== false ? buildVoiceIncidentRecoveryOutcomeReadinessCheck(incidentRecoveryOutcomes, {
|
|
29526
|
+
href: options.incidentRecoveryOutcomeReadiness?.href ?? "/api/voice/incident-timeline/recovery-outcomes",
|
|
29527
|
+
...options.incidentRecoveryOutcomeReadiness
|
|
29528
|
+
}) : undefined;
|
|
28956
29529
|
const checks = [
|
|
28957
29530
|
{
|
|
28958
29531
|
detail: quality.status === "pass" ? "Quality gates are passing." : "Quality gates need attention.",
|
|
@@ -29024,6 +29597,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
29024
29597
|
]
|
|
29025
29598
|
}
|
|
29026
29599
|
] : [],
|
|
29600
|
+
...incidentRecoveryOutcomeReadiness ? [incidentRecoveryOutcomeReadiness] : [],
|
|
29027
29601
|
{
|
|
29028
29602
|
detail: failedSessions === 0 ? sessions.length > 0 ? "Recent sessions have no recorded provider/session failures." : "No sessions have been recorded yet; run a smoke or live session for proof." : `${failedSessions} recent session(s) have failures.`,
|
|
29029
29603
|
href: firstOperationsRecordHref(operationsRecords.failedSessions) ?? options.links?.sessions ?? "/sessions",
|
|
@@ -29357,6 +29931,14 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
29357
29931
|
status: observabilityExport.status,
|
|
29358
29932
|
traceEvents: observabilityExport.summary.traceEvents
|
|
29359
29933
|
} : undefined;
|
|
29934
|
+
const incidentRecoveryOutcomeSummary = incidentRecoveryOutcomes && incidentRecoveryOutcomeReadiness ? {
|
|
29935
|
+
failed: incidentRecoveryOutcomes.failed,
|
|
29936
|
+
improved: incidentRecoveryOutcomes.improved,
|
|
29937
|
+
regressed: incidentRecoveryOutcomes.regressed,
|
|
29938
|
+
status: incidentRecoveryOutcomeReadiness.status,
|
|
29939
|
+
total: incidentRecoveryOutcomes.total,
|
|
29940
|
+
unchanged: incidentRecoveryOutcomes.unchanged
|
|
29941
|
+
} : undefined;
|
|
29360
29942
|
const observabilityExportDeliveryHistorySummary = observabilityExportDeliveryHistory ? (() => {
|
|
29361
29943
|
const latestSuccess = observabilityExportDeliveryHistory.history.receipts.filter((receipt) => receipt.status === "pass" && receipt.summary.delivered > 0 && receipt.summary.failed === 0).sort((left, right) => right.checkedAt - left.checkedAt)[0];
|
|
29362
29944
|
const latestSuccessAgeMs = latestSuccess ? Date.now() - latestSuccess.checkedAt : undefined;
|
|
@@ -29896,6 +30478,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
29896
30478
|
total: handoffs.total
|
|
29897
30479
|
},
|
|
29898
30480
|
liveLatency,
|
|
30481
|
+
incidentRecoveryOutcomes: incidentRecoveryOutcomeSummary,
|
|
29899
30482
|
mediaPipeline: mediaPipelineSummary,
|
|
29900
30483
|
monitoring: monitoringSummary,
|
|
29901
30484
|
monitoringNotifierDelivery: monitoringNotifierDeliverySummary,
|
|
@@ -29950,25 +30533,25 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
29950
30533
|
var buildVoiceProductionReadinessGate = async (options, input = {}) => summarizeVoiceProductionReadinessGate(await buildVoiceProductionReadinessReport(options, input), options.gate || undefined);
|
|
29951
30534
|
var renderVoiceProductionReadinessHTML = (report, options = {}) => {
|
|
29952
30535
|
const title = options.title ?? "AbsoluteJS Voice Production Readiness";
|
|
29953
|
-
const thresholdLink = report.links.sloReadinessThresholds ? `<p><a href="${
|
|
29954
|
-
const profile = report.profile ? `<section class="profile"><p class="eyebrow">Readiness profile</p><h2>${
|
|
30536
|
+
const thresholdLink = report.links.sloReadinessThresholds ? `<p><a href="${escapeHtml42(report.links.sloReadinessThresholds)}">Open Calibration -> Active Readiness Gate</a> to inspect the thresholds currently driving calibrated provider, latency, interruption, reconnect, and monitoring gates.</p>` : "";
|
|
30537
|
+
const profile = report.profile ? `<section class="profile"><p class="eyebrow">Readiness profile</p><h2>${escapeHtml42(report.profile.name)}</h2><p>${escapeHtml42(report.profile.description)}</p><p>${escapeHtml42(report.profile.purpose)}</p><div class="profile-surfaces">${report.profile.surfaces.map((surface) => `<article class="${surface.configured ? "pass" : "warn"}"><span>${surface.configured ? "CONFIGURED" : "EXPECTED"}</span><strong>${surface.href ? `<a href="${escapeHtml42(surface.href)}">${escapeHtml42(surface.label)}</a>` : escapeHtml42(surface.label)}</strong></article>`).join("")}</div></section>` : "";
|
|
29955
30538
|
const checks = report.checks.map((check, index) => {
|
|
29956
|
-
const actions = (check.actions ?? []).map((action) => action.method === "POST" ? `<button type="button" data-readiness-action="${index}" data-action-url="${
|
|
29957
|
-
const explanation = check.gateExplanation ? `<p class="gate-explanation">Why this gate is ${
|
|
29958
|
-
return `<article class="check ${
|
|
30539
|
+
const actions = (check.actions ?? []).map((action) => action.method === "POST" ? `<button type="button" data-readiness-action="${index}" data-action-url="${escapeHtml42(action.href)}">${escapeHtml42(action.label)}</button>` : `<a href="${escapeHtml42(action.href)}">${escapeHtml42(action.label)}</a>`).join("");
|
|
30540
|
+
const explanation = check.gateExplanation ? `<p class="gate-explanation">Why this gate is ${escapeHtml42(check.status)}: observed ${escapeHtml42(String(check.gateExplanation.observed ?? "n/a"))}${check.gateExplanation.unit ? ` ${escapeHtml42(check.gateExplanation.unit)}` : ""}; threshold ${escapeHtml42(String(check.gateExplanation.threshold ?? "n/a"))}${check.gateExplanation.unit ? ` ${escapeHtml42(check.gateExplanation.unit)}` : ""}. ${escapeHtml42(check.gateExplanation.remediation)} ${check.gateExplanation.sourceHref ? `<a href="${escapeHtml42(check.gateExplanation.sourceHref)}">Open threshold source</a>` : ""}</p>` : "";
|
|
30541
|
+
return `<article class="check ${escapeHtml42(check.status)}">
|
|
29959
30542
|
<div>
|
|
29960
|
-
<span>${
|
|
29961
|
-
<h2>${
|
|
29962
|
-
${check.detail ? `<p>${
|
|
30543
|
+
<span>${escapeHtml42(check.status.toUpperCase())}</span>
|
|
30544
|
+
<h2>${escapeHtml42(check.label)}</h2>
|
|
30545
|
+
${check.detail ? `<p>${escapeHtml42(check.detail)}</p>` : ""}
|
|
29963
30546
|
${explanation}
|
|
29964
|
-
${check.proofSource ? `<p class="proof-source">Proof source: ${check.proofSource.href ? `<a href="${
|
|
30547
|
+
${check.proofSource ? `<p class="proof-source">Proof source: ${check.proofSource.href ? `<a href="${escapeHtml42(check.proofSource.href)}">${escapeHtml42(check.proofSource.sourceLabel)}</a>` : escapeHtml42(check.proofSource.sourceLabel)}${check.proofSource.detail ? ` \xB7 ${escapeHtml42(check.proofSource.detail)}` : ""}</p>` : ""}
|
|
29965
30548
|
${actions ? `<p class="actions">${actions}</p>` : ""}
|
|
29966
30549
|
</div>
|
|
29967
|
-
<strong>${
|
|
29968
|
-
${check.href ? `<a href="${
|
|
30550
|
+
<strong>${escapeHtml42(String(check.value ?? check.status))}</strong>
|
|
30551
|
+
${check.href ? `<a href="${escapeHtml42(check.href)}">Open surface</a>` : ""}
|
|
29969
30552
|
</article>`;
|
|
29970
30553
|
}).join("");
|
|
29971
|
-
const snippet =
|
|
30554
|
+
const snippet = escapeHtml42(`createVoiceProductionReadinessRoutes({
|
|
29972
30555
|
htmlPath: '/production-readiness',
|
|
29973
30556
|
path: '/api/production-readiness',
|
|
29974
30557
|
gatePath: '/api/production-readiness/gate',
|
|
@@ -29984,13 +30567,13 @@ var renderVoiceProductionReadinessHTML = (report, options = {}) => {
|
|
|
29984
30567
|
providerRoutingContracts: loadProviderRoutingContracts,
|
|
29985
30568
|
store: traceStore
|
|
29986
30569
|
});`);
|
|
29987
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
30570
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml42(title)}</title><style>body{background:#0c0f14;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1060px;padding:32px}.hero,.primitive,.profile{background:linear-gradient(135deg,rgba(20,184,166,.18),rgba(245,158,11,.12));border:1px solid #26313d;border-radius:28px;margin-bottom:18px;padding:28px}.primitive,.profile{background:#111722}.primitive{border-color:#3a3f2d}.eyebrow{color:#fbbf24;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,5rem);line-height:.9;margin:.2rem 0 1rem}.status{display:inline-flex;border:1px solid #3f3f46;border-radius:999px;padding:8px 12px}.primitive code{color:#fde68a}.primitive p{color:#c8ccd3;line-height:1.55;margin:.45rem 0 0}.primitive pre{background:#0b0f16;border:1px solid #2c3440;border-radius:18px;color:#fef3c7;margin:16px 0 0;overflow:auto;padding:16px}.status.pass,.check.pass,.profile-surfaces .pass{border-color:rgba(34,197,94,.55)}.status.warn,.check.warn,.profile-surfaces .warn{border-color:rgba(245,158,11,.65)}.status.fail,.check.fail{border-color:rgba(239,68,68,.75)}.checks{display:grid;gap:14px}.check{align-items:center;background:#141922;border:1px solid #26313d;border-radius:22px;display:grid;gap:16px;grid-template-columns:1fr auto auto;padding:18px}.check span,.profile-surfaces span{color:#a8b0b8;font-size:.78rem;font-weight:900;letter-spacing:.08em}.check h2{margin:.2rem 0}.check p,.profile p{color:#b9c0c8;margin:.2rem 0 0}.check .proof-source{color:#f9d77e;font-weight:800}.check .gate-explanation{background:#0b0f16;border:1px solid #2c3440;border-radius:14px;color:#fef3c7;margin-top:10px;padding:10px}.check strong{font-size:1.5rem}.profile-surfaces{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));margin-top:16px}.profile-surfaces article{background:#141922;border:1px solid #26313d;border-radius:16px;padding:14px}.profile-surfaces strong{display:block;margin-top:6px}.actions{display:flex;flex-wrap:wrap;gap:10px}.check a,a{color:#fbbf24}button{background:#fbbf24;border:0;border-radius:999px;color:#111827;cursor:pointer;font-weight:800;padding:9px 12px}button:disabled{cursor:wait;opacity:.65}@media(max-width:760px){main{padding:20px}.check{grid-template-columns:1fr}}</style></head><body><main><section class="hero"><p class="eyebrow">Self-hosted readiness</p><h1>${escapeHtml42(title)}</h1><p>One deployable pass/fail report for quality gates, provider failover, session health, handoffs, routing evidence, and optional carrier readiness.</p><p class="status ${escapeHtml42(report.status)}">Overall: ${escapeHtml42(report.status.toUpperCase())}</p><p>Checked ${escapeHtml42(new Date(report.checkedAt).toLocaleString())}</p>${thresholdLink}</section>${profile}<section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceProductionReadinessRoutes(...)</code> builds this deploy gate</h2><p>Mount one package primitive to expose JSON readiness, HTML readiness, and a machine-readable gate route. Feed it the proof stores and contract reports your app already owns.</p><pre><code>${snippet}</code></pre></section><section class="checks">${checks}</section></main><script>document.querySelectorAll("[data-readiness-action]").forEach((button)=>{button.addEventListener("click",async()=>{const url=button.getAttribute("data-action-url");if(!url)return;button.disabled=true;const original=button.textContent;button.textContent="Running...";try{const response=await fetch(url,{method:"POST"});button.textContent=response.ok?"Done. Reloading...":"Failed";if(response.ok)setTimeout(()=>location.reload(),500)}catch{button.textContent="Failed"}finally{setTimeout(()=>{button.disabled=false;button.textContent=original},1500)}})});</script></body></html>`;
|
|
29988
30571
|
};
|
|
29989
30572
|
var createVoiceProductionReadinessRoutes = (options) => {
|
|
29990
30573
|
const path = options.path ?? "/api/production-readiness";
|
|
29991
30574
|
const gatePath = options.gatePath === undefined ? "/api/production-readiness/gate" : options.gatePath;
|
|
29992
30575
|
const htmlPath = options.htmlPath ?? "/production-readiness";
|
|
29993
|
-
const routes = new
|
|
30576
|
+
const routes = new Elysia45({
|
|
29994
30577
|
name: options.name ?? "absolutejs-voice-production-readiness"
|
|
29995
30578
|
});
|
|
29996
30579
|
let cachedReport;
|
|
@@ -30010,544 +30593,144 @@ var createVoiceProductionReadinessRoutes = (options) => {
|
|
|
30010
30593
|
const getReport = async (query, request) => {
|
|
30011
30594
|
const cacheMs = typeof options.cacheMs === "number" && Number.isFinite(options.cacheMs) && options.cacheMs > 0 ? options.cacheMs : 0;
|
|
30012
30595
|
const key = reportCacheKey(query, request);
|
|
30013
|
-
if (cacheMs > 0 && cachedReport && cachedReport.key === key && Date.now() - cachedReport.loadedAt <= cacheMs) {
|
|
30014
|
-
return cachedReport.value;
|
|
30015
|
-
}
|
|
30016
|
-
const value = (async () => {
|
|
30017
|
-
const resolvedOptions = await resolveOptions({ query, request });
|
|
30018
|
-
return {
|
|
30019
|
-
report: await buildVoiceProductionReadinessReport(resolvedOptions, {
|
|
30020
|
-
query,
|
|
30021
|
-
request
|
|
30022
|
-
}),
|
|
30023
|
-
resolvedOptions
|
|
30024
|
-
};
|
|
30025
|
-
})();
|
|
30026
|
-
if (cacheMs > 0) {
|
|
30027
|
-
cachedReport = {
|
|
30028
|
-
key,
|
|
30029
|
-
loadedAt: Date.now(),
|
|
30030
|
-
value
|
|
30031
|
-
};
|
|
30032
|
-
}
|
|
30033
|
-
return value;
|
|
30034
|
-
};
|
|
30035
|
-
routes.get(path, async ({ query, request }) => (await getReport(query, request)).report);
|
|
30036
|
-
if (gatePath !== false) {
|
|
30037
|
-
routes.get(gatePath, async ({ query, request }) => {
|
|
30038
|
-
const { report, resolvedOptions } = await getReport(query, request);
|
|
30039
|
-
const gate = summarizeVoiceProductionReadinessGate(report, resolvedOptions.gate || undefined);
|
|
30040
|
-
return new Response(JSON.stringify(gate), {
|
|
30041
|
-
headers: {
|
|
30042
|
-
"Content-Type": "application/json; charset=utf-8",
|
|
30043
|
-
...resolvedOptions.headers
|
|
30044
|
-
},
|
|
30045
|
-
status: gate.ok ? 200 : 503
|
|
30046
|
-
});
|
|
30047
|
-
});
|
|
30048
|
-
}
|
|
30049
|
-
if (htmlPath !== false) {
|
|
30050
|
-
routes.get(htmlPath, async ({ query, request }) => {
|
|
30051
|
-
const { report, resolvedOptions } = await getReport(query, request);
|
|
30052
|
-
const body = await (resolvedOptions.render ?? renderVoiceProductionReadinessHTML)(report);
|
|
30053
|
-
return new Response(body, {
|
|
30054
|
-
headers: {
|
|
30055
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
30056
|
-
...resolvedOptions.headers
|
|
30057
|
-
}
|
|
30058
|
-
});
|
|
30059
|
-
});
|
|
30060
|
-
}
|
|
30061
|
-
return routes;
|
|
30062
|
-
};
|
|
30063
|
-
|
|
30064
|
-
// src/operationalStatus.ts
|
|
30065
|
-
var escapeHtml42 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
30066
|
-
var resolveValue = async (value) => typeof value === "function" ? await value() : value;
|
|
30067
|
-
var isDeliveryRuntime = (value) => Boolean(value && typeof value === "object" && "isRunning" in value && "summarize" in value);
|
|
30068
|
-
var worstStatus2 = (statuses) => statuses.includes("fail") ? "fail" : statuses.includes("warn") ? "warn" : "pass";
|
|
30069
|
-
var proofPackStatusToCheck = (status, href) => {
|
|
30070
|
-
const checkStatus = status.state === "failed" || status.state === "missing" ? "fail" : status.state === "fresh" ? "pass" : "warn";
|
|
30071
|
-
const age = typeof status.ageMs === "number" ? `${Math.round(status.ageMs / 1000)}s old` : undefined;
|
|
30072
|
-
return {
|
|
30073
|
-
detail: status.error ?? `Proof pack is ${status.state}.`,
|
|
30074
|
-
href,
|
|
30075
|
-
label: "Proof pack freshness",
|
|
30076
|
-
status: checkStatus,
|
|
30077
|
-
value: age ?? status.state
|
|
30078
|
-
};
|
|
30079
|
-
};
|
|
30080
|
-
var deliveryRuntimeStatusToCheck = (report, href) => {
|
|
30081
|
-
const summaries = [report.summary.audit, report.summary.trace].filter(Boolean);
|
|
30082
|
-
const failed = summaries.reduce((total, summary) => total + (summary?.failed ?? 0) + (summary?.deadLettered ?? 0), 0);
|
|
30083
|
-
const pending = summaries.reduce((total, summary) => total + (summary?.pending ?? 0), 0);
|
|
30084
|
-
const status = failed > 0 ? "fail" : pending > 0 || !report.isRunning ? "warn" : "pass";
|
|
30085
|
-
return {
|
|
30086
|
-
detail: failed > 0 ? "Delivery runtime has failed or dead-lettered work." : pending > 0 ? "Delivery runtime has pending work." : report.isRunning ? "Delivery runtime is running with no backlog." : "Delivery runtime is stopped.",
|
|
30087
|
-
href,
|
|
30088
|
-
label: "Delivery runtime",
|
|
30089
|
-
status,
|
|
30090
|
-
value: `${pending} pending / ${failed} failed`
|
|
30091
|
-
};
|
|
30092
|
-
};
|
|
30093
|
-
var productionReadinessStatusToCheck = (report, href) => {
|
|
30094
|
-
const gate = summarizeVoiceProductionReadinessGate(report);
|
|
30095
|
-
return {
|
|
30096
|
-
detail: gate.ok ? "Production readiness gate is open." : `${gate.failures.length} failures and ${gate.warnings.length} warnings.`,
|
|
30097
|
-
href,
|
|
30098
|
-
label: "Production readiness",
|
|
30099
|
-
status: gate.ok ? report.status : "fail",
|
|
30100
|
-
value: `${gate.failures.length} failures / ${gate.warnings.length} warnings`
|
|
30101
|
-
};
|
|
30102
|
-
};
|
|
30103
|
-
var buildVoiceOperationalStatusReport = async (options) => {
|
|
30104
|
-
const [proofPack, deliveryRuntimeReport, productionReadiness] = await Promise.all([
|
|
30105
|
-
resolveValue(options.proofPack),
|
|
30106
|
-
isDeliveryRuntime(options.deliveryRuntime) ? buildVoiceDeliveryRuntimeReport(options.deliveryRuntime) : resolveValue(options.deliveryRuntime),
|
|
30107
|
-
resolveValue(options.productionReadiness)
|
|
30108
|
-
]);
|
|
30109
|
-
const checks = [];
|
|
30110
|
-
if (proofPack) {
|
|
30111
|
-
checks.push(proofPackStatusToCheck(proofPack, options.links?.proofPack));
|
|
30112
|
-
}
|
|
30113
|
-
if (deliveryRuntimeReport) {
|
|
30114
|
-
checks.push(deliveryRuntimeStatusToCheck(deliveryRuntimeReport, options.links?.deliveryRuntime));
|
|
30115
|
-
}
|
|
30116
|
-
if (productionReadiness) {
|
|
30117
|
-
checks.push(productionReadinessStatusToCheck(productionReadiness, options.links?.productionReadiness));
|
|
30118
|
-
}
|
|
30119
|
-
const summary = {
|
|
30120
|
-
fail: checks.filter((check) => check.status === "fail").length,
|
|
30121
|
-
pass: checks.filter((check) => check.status === "pass").length,
|
|
30122
|
-
total: checks.length,
|
|
30123
|
-
warn: checks.filter((check) => check.status === "warn").length
|
|
30124
|
-
};
|
|
30125
|
-
return {
|
|
30126
|
-
checkedAt: Date.now(),
|
|
30127
|
-
checks,
|
|
30128
|
-
links: options.links ?? {},
|
|
30129
|
-
status: worstStatus2(checks.map((check) => check.status)),
|
|
30130
|
-
summary
|
|
30131
|
-
};
|
|
30132
|
-
};
|
|
30133
|
-
var renderVoiceOperationalStatusHTML = (report, options = {}) => {
|
|
30134
|
-
const title = options.title ?? "AbsoluteJS Voice Operational Status";
|
|
30135
|
-
const checks = report.checks.map((check) => `<article class="${escapeHtml42(check.status)}">
|
|
30136
|
-
<span>${escapeHtml42(check.status.toUpperCase())}</span>
|
|
30137
|
-
<h2>${escapeHtml42(check.label)}</h2>
|
|
30138
|
-
<strong>${escapeHtml42(String(check.value ?? check.status))}</strong>
|
|
30139
|
-
${check.detail ? `<p>${escapeHtml42(check.detail)}</p>` : ""}
|
|
30140
|
-
${check.href ? `<a href="${escapeHtml42(check.href)}">Open surface</a>` : ""}
|
|
30141
|
-
</article>`).join("");
|
|
30142
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml42(title)}</title><style>body{background:#10130f;color:#f8f3df;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1040px;padding:32px}.hero{background:linear-gradient(135deg,rgba(132,204,22,.18),rgba(14,165,233,.13));border:1px solid #2c3a28;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#bef264;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,4.8rem);line-height:.92;margin:.2rem 0 1rem}.status{border:1px solid #64748b;border-radius:999px;display:inline-flex;font-weight:900;padding:8px 12px}.grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(230px,1fr))}article{background:#171d15;border:1px solid #2c3a28;border-radius:22px;padding:18px}article.pass{border-color:rgba(34,197,94,.65)}article.warn{border-color:rgba(245,158,11,.75)}article.fail{border-color:rgba(239,68,68,.85)}article span{color:#bef264;font-size:.78rem;font-weight:900;letter-spacing:.08em}article.warn span{color:#fbbf24}article.fail span{color:#fca5a5}article strong{display:block;font-size:1.6rem;margin:.4rem 0}article p{color:#c5ceb9}a{color:#bef264}</style></head><body><main><section class="hero"><p class="eyebrow">Operational status</p><h1>${escapeHtml42(title)}</h1><p class="status ${escapeHtml42(report.status)}">Overall: ${escapeHtml42(report.status.toUpperCase())}</p><p>${String(report.summary.pass)}/${String(report.summary.total)} checks passing. Checked ${escapeHtml42(new Date(report.checkedAt).toLocaleString())}.</p></section><section class="grid">${checks || '<article class="pass"><span>PASS</span><h2>No operational checks configured</h2><strong>0/0</strong></article>'}</section></main></body></html>`;
|
|
30143
|
-
};
|
|
30144
|
-
var createVoiceOperationalStatusRoutes = (options) => {
|
|
30145
|
-
const path = options.path ?? "/api/voice/operational-status";
|
|
30146
|
-
const htmlPath = options.htmlPath === undefined ? "/voice/operational-status" : options.htmlPath;
|
|
30147
|
-
const routes = new Elysia45({
|
|
30148
|
-
name: options.name ?? "absolutejs-voice-operational-status"
|
|
30149
|
-
}).get(path, async () => {
|
|
30150
|
-
const report = await buildVoiceOperationalStatusReport(options);
|
|
30151
|
-
return new Response(JSON.stringify(report), {
|
|
30152
|
-
headers: {
|
|
30153
|
-
"Content-Type": "application/json; charset=utf-8",
|
|
30154
|
-
...options.headers
|
|
30155
|
-
},
|
|
30156
|
-
status: report.status === "fail" ? 503 : 200
|
|
30157
|
-
});
|
|
30158
|
-
});
|
|
30159
|
-
if (htmlPath !== false) {
|
|
30160
|
-
routes.get(htmlPath, async () => {
|
|
30161
|
-
const report = await buildVoiceOperationalStatusReport(options);
|
|
30162
|
-
const body = await (options.render ?? ((input) => renderVoiceOperationalStatusHTML(input, { title: options.title })))(report);
|
|
30163
|
-
return new Response(body, {
|
|
30164
|
-
headers: {
|
|
30165
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
30166
|
-
...options.headers
|
|
30167
|
-
}
|
|
30168
|
-
});
|
|
30169
|
-
});
|
|
30170
|
-
}
|
|
30171
|
-
return routes;
|
|
30172
|
-
};
|
|
30173
|
-
// src/incidentTimeline.ts
|
|
30174
|
-
import { Elysia as Elysia46 } from "elysia";
|
|
30175
|
-
var escapeHtml43 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
30176
|
-
var resolveValue2 = async (value) => typeof value === "function" ? await value() : value;
|
|
30177
|
-
var linkForSession = (link, sessionId) => {
|
|
30178
|
-
if (!link || !sessionId) {
|
|
30179
|
-
return;
|
|
30180
|
-
}
|
|
30181
|
-
return typeof link === "function" ? link(sessionId) : link;
|
|
30182
|
-
};
|
|
30183
|
-
var statusToSeverity = (status) => status === "fail" || status === "failed" ? "critical" : status === "warn" || status === "warning" || status === "recovered" ? "warn" : "info";
|
|
30184
|
-
var failureReplayStatusToSeverity = (status) => status === "failed" ? "critical" : status === "healthy" ? "info" : "warn";
|
|
30185
|
-
var withinWindow = (event, now, windowMs) => !windowMs || event.at >= now - windowMs;
|
|
30186
|
-
var eventStatus2 = (event) => event.severity === "critical" ? "fail" : event.severity === "warn" ? "warn" : "pass";
|
|
30187
|
-
var defaultIncidentRecoveryActions = (events, links) => {
|
|
30188
|
-
const actions = [];
|
|
30189
|
-
const add = (action) => {
|
|
30190
|
-
const key = `${action.id}:${action.sessionId ?? ""}:${action.href ?? ""}`;
|
|
30191
|
-
if (actions.some((existing) => `${existing.id}:${existing.sessionId ?? ""}:${existing.href ?? ""}` === key)) {
|
|
30192
|
-
return;
|
|
30193
|
-
}
|
|
30194
|
-
actions.push(action);
|
|
30195
|
-
};
|
|
30196
|
-
for (const event of events) {
|
|
30197
|
-
if (event.category === "delivery") {
|
|
30198
|
-
add({
|
|
30199
|
-
detail: "Ask the app to tick delivery workers or retry failed delivery queue work.",
|
|
30200
|
-
eventId: event.id,
|
|
30201
|
-
href: links.deliveryRuntime,
|
|
30202
|
-
id: "delivery.retry",
|
|
30203
|
-
label: "Retry delivery work",
|
|
30204
|
-
method: "POST",
|
|
30205
|
-
sessionId: event.sessionId
|
|
30206
|
-
});
|
|
30207
|
-
}
|
|
30208
|
-
if (event.category === "readiness" || event.category === "operational-status") {
|
|
30209
|
-
add({
|
|
30210
|
-
detail: "Refresh production readiness and proof freshness before declaring the incident resolved.",
|
|
30211
|
-
eventId: event.id,
|
|
30212
|
-
href: links.productionReadiness ?? links.operationalStatus,
|
|
30213
|
-
id: "readiness.refresh",
|
|
30214
|
-
label: "Refresh readiness proof",
|
|
30215
|
-
method: "POST",
|
|
30216
|
-
sessionId: event.sessionId
|
|
30217
|
-
});
|
|
30596
|
+
if (cacheMs > 0 && cachedReport && cachedReport.key === key && Date.now() - cachedReport.loadedAt <= cacheMs) {
|
|
30597
|
+
return cachedReport.value;
|
|
30218
30598
|
}
|
|
30219
|
-
|
|
30220
|
-
|
|
30221
|
-
|
|
30222
|
-
|
|
30223
|
-
|
|
30224
|
-
|
|
30225
|
-
|
|
30226
|
-
|
|
30227
|
-
|
|
30228
|
-
|
|
30599
|
+
const value = (async () => {
|
|
30600
|
+
const resolvedOptions = await resolveOptions({ query, request });
|
|
30601
|
+
return {
|
|
30602
|
+
report: await buildVoiceProductionReadinessReport(resolvedOptions, {
|
|
30603
|
+
query,
|
|
30604
|
+
request
|
|
30605
|
+
}),
|
|
30606
|
+
resolvedOptions
|
|
30607
|
+
};
|
|
30608
|
+
})();
|
|
30609
|
+
if (cacheMs > 0) {
|
|
30610
|
+
cachedReport = {
|
|
30611
|
+
key,
|
|
30612
|
+
loadedAt: Date.now(),
|
|
30613
|
+
value
|
|
30614
|
+
};
|
|
30229
30615
|
}
|
|
30616
|
+
return value;
|
|
30617
|
+
};
|
|
30618
|
+
routes.get(path, async ({ query, request }) => (await getReport(query, request)).report);
|
|
30619
|
+
if (gatePath !== false) {
|
|
30620
|
+
routes.get(gatePath, async ({ query, request }) => {
|
|
30621
|
+
const { report, resolvedOptions } = await getReport(query, request);
|
|
30622
|
+
const gate = summarizeVoiceProductionReadinessGate(report, resolvedOptions.gate || undefined);
|
|
30623
|
+
return new Response(JSON.stringify(gate), {
|
|
30624
|
+
headers: {
|
|
30625
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
30626
|
+
...resolvedOptions.headers
|
|
30627
|
+
},
|
|
30628
|
+
status: gate.ok ? 200 : 503
|
|
30629
|
+
});
|
|
30630
|
+
});
|
|
30230
30631
|
}
|
|
30231
|
-
if (
|
|
30232
|
-
|
|
30233
|
-
|
|
30234
|
-
|
|
30235
|
-
|
|
30236
|
-
|
|
30237
|
-
|
|
30632
|
+
if (htmlPath !== false) {
|
|
30633
|
+
routes.get(htmlPath, async ({ query, request }) => {
|
|
30634
|
+
const { report, resolvedOptions } = await getReport(query, request);
|
|
30635
|
+
const body = await (resolvedOptions.render ?? renderVoiceProductionReadinessHTML)(report);
|
|
30636
|
+
return new Response(body, {
|
|
30637
|
+
headers: {
|
|
30638
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
30639
|
+
...resolvedOptions.headers
|
|
30640
|
+
}
|
|
30641
|
+
});
|
|
30238
30642
|
});
|
|
30239
30643
|
}
|
|
30240
|
-
return
|
|
30644
|
+
return routes;
|
|
30241
30645
|
};
|
|
30646
|
+
|
|
30647
|
+
// src/operationalStatus.ts
|
|
30648
|
+
var escapeHtml43 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
30649
|
+
var resolveValue2 = async (value) => typeof value === "function" ? await value() : value;
|
|
30650
|
+
var isDeliveryRuntime = (value) => Boolean(value && typeof value === "object" && "isRunning" in value && "summarize" in value);
|
|
30242
30651
|
var worstStatus3 = (statuses) => statuses.includes("fail") ? "fail" : statuses.includes("warn") ? "warn" : "pass";
|
|
30243
|
-
var
|
|
30244
|
-
|
|
30245
|
-
|
|
30246
|
-
const payload = isRecord4(event.payload) ? event.payload : {};
|
|
30247
|
-
return isRecord4(payload.body) ? payload.body : {};
|
|
30248
|
-
};
|
|
30249
|
-
var getIncidentRecoveryStatus = (value) => value === "fail" || value === "pass" || value === "warn" ? value : undefined;
|
|
30250
|
-
var getIncidentRecoveryDetail = (event) => {
|
|
30251
|
-
const payload = isRecord4(event.payload) ? event.payload : {};
|
|
30252
|
-
const body = getIncidentRecoveryBody(event);
|
|
30253
|
-
const result = isRecord4(body.result) ? body.result : {};
|
|
30254
|
-
const detail = result.detail ?? payload.error;
|
|
30255
|
-
return typeof detail === "string" ? detail : undefined;
|
|
30256
|
-
};
|
|
30257
|
-
var toIncidentRecoveryOutcomeEntry = (event) => {
|
|
30258
|
-
const body = getIncidentRecoveryBody(event);
|
|
30259
|
-
const beforeStatus = getIncidentRecoveryStatus(body.beforeStatus);
|
|
30260
|
-
const afterStatus = getIncidentRecoveryStatus(body.afterStatus);
|
|
30261
|
-
const beforeRank = statusRank6(beforeStatus);
|
|
30262
|
-
const afterRank = statusRank6(afterStatus);
|
|
30263
|
-
const outcome = event.outcome === "error" ? "failed" : beforeRank > 0 && afterRank > 0 && afterRank < beforeRank ? "improved" : beforeRank > 0 && afterRank > beforeRank ? "regressed" : "unchanged";
|
|
30264
|
-
const payload = isRecord4(event.payload) ? event.payload : {};
|
|
30652
|
+
var proofPackStatusToCheck = (status, href) => {
|
|
30653
|
+
const checkStatus = status.state === "failed" || status.state === "missing" ? "fail" : status.state === "fresh" ? "pass" : "warn";
|
|
30654
|
+
const age = typeof status.ageMs === "number" ? `${Math.round(status.ageMs / 1000)}s old` : undefined;
|
|
30265
30655
|
return {
|
|
30266
|
-
|
|
30267
|
-
|
|
30268
|
-
|
|
30269
|
-
|
|
30270
|
-
|
|
30271
|
-
eventId: event.id,
|
|
30272
|
-
outcome,
|
|
30273
|
-
status: typeof payload.status === "number" ? payload.status : undefined,
|
|
30274
|
-
traceId: event.traceId
|
|
30656
|
+
detail: status.error ?? `Proof pack is ${status.state}.`,
|
|
30657
|
+
href,
|
|
30658
|
+
label: "Proof pack freshness",
|
|
30659
|
+
status: checkStatus,
|
|
30660
|
+
value: age ?? status.state
|
|
30275
30661
|
};
|
|
30276
30662
|
};
|
|
30277
|
-
var
|
|
30278
|
-
const
|
|
30279
|
-
|
|
30280
|
-
|
|
30281
|
-
|
|
30282
|
-
}) : [];
|
|
30283
|
-
const entries = events.filter((event) => event.action.startsWith("incident.")).map(toIncidentRecoveryOutcomeEntry).sort((left, right) => right.at - left.at);
|
|
30663
|
+
var deliveryRuntimeStatusToCheck = (report, href) => {
|
|
30664
|
+
const summaries = [report.summary.audit, report.summary.trace].filter(Boolean);
|
|
30665
|
+
const failed = summaries.reduce((total, summary) => total + (summary?.failed ?? 0) + (summary?.deadLettered ?? 0), 0);
|
|
30666
|
+
const pending = summaries.reduce((total, summary) => total + (summary?.pending ?? 0), 0);
|
|
30667
|
+
const status = failed > 0 ? "fail" : pending > 0 || !report.isRunning ? "warn" : "pass";
|
|
30284
30668
|
return {
|
|
30285
|
-
|
|
30286
|
-
|
|
30287
|
-
|
|
30288
|
-
|
|
30289
|
-
|
|
30290
|
-
total: entries.length,
|
|
30291
|
-
unchanged: entries.filter((entry) => entry.outcome === "unchanged").length
|
|
30669
|
+
detail: failed > 0 ? "Delivery runtime has failed or dead-lettered work." : pending > 0 ? "Delivery runtime has pending work." : report.isRunning ? "Delivery runtime is running with no backlog." : "Delivery runtime is stopped.",
|
|
30670
|
+
href,
|
|
30671
|
+
label: "Delivery runtime",
|
|
30672
|
+
status,
|
|
30673
|
+
value: `${pending} pending / ${failed} failed`
|
|
30292
30674
|
};
|
|
30293
30675
|
};
|
|
30294
|
-
var
|
|
30295
|
-
const
|
|
30296
|
-
|
|
30297
|
-
|
|
30298
|
-
|
|
30299
|
-
|
|
30300
|
-
|
|
30301
|
-
|
|
30302
|
-
}
|
|
30303
|
-
for (const check of report.checks) {
|
|
30304
|
-
if (check.status === "pass") {
|
|
30305
|
-
continue;
|
|
30306
|
-
}
|
|
30307
|
-
events.push({
|
|
30308
|
-
action: {
|
|
30309
|
-
href: check.href ?? links.operationalStatus,
|
|
30310
|
-
label: "Open source"
|
|
30311
|
-
},
|
|
30312
|
-
at: report.checkedAt,
|
|
30313
|
-
category: check.label.toLowerCase().includes("readiness") ? "readiness" : "operational-status",
|
|
30314
|
-
detail: check.detail,
|
|
30315
|
-
href: check.href ?? links.operationalStatus,
|
|
30316
|
-
id: `operational:${check.label}`,
|
|
30317
|
-
label: check.label,
|
|
30318
|
-
severity: statusToSeverity(check.status),
|
|
30319
|
-
source: "operational-status",
|
|
30320
|
-
value: check.value
|
|
30321
|
-
});
|
|
30322
|
-
}
|
|
30323
|
-
};
|
|
30324
|
-
var pushOpsRecoveryEvents = (events, report, links) => {
|
|
30325
|
-
if (!report) {
|
|
30326
|
-
return;
|
|
30327
|
-
}
|
|
30328
|
-
for (const issue of report.issues) {
|
|
30329
|
-
events.push({
|
|
30330
|
-
action: {
|
|
30331
|
-
href: issue.href ?? links.operationalStatus,
|
|
30332
|
-
label: "Inspect recovery issue"
|
|
30333
|
-
},
|
|
30334
|
-
at: report.checkedAt,
|
|
30335
|
-
category: "recovery",
|
|
30336
|
-
detail: issue.detail,
|
|
30337
|
-
href: issue.href,
|
|
30338
|
-
id: `ops-recovery:${issue.code}`,
|
|
30339
|
-
label: issue.label,
|
|
30340
|
-
severity: issue.severity === "fail" ? "critical" : "warn",
|
|
30341
|
-
source: "ops-recovery",
|
|
30342
|
-
value: issue.value
|
|
30343
|
-
});
|
|
30344
|
-
}
|
|
30345
|
-
for (const session of report.failedSessions) {
|
|
30346
|
-
events.push({
|
|
30347
|
-
action: {
|
|
30348
|
-
href: session.operationsRecordHref ?? linkForSession(links.operationsRecords, session.sessionId) ?? linkForSession(links.callDebugger, session.sessionId),
|
|
30349
|
-
label: "Open affected call"
|
|
30350
|
-
},
|
|
30351
|
-
at: session.at,
|
|
30352
|
-
category: "call",
|
|
30353
|
-
detail: session.error,
|
|
30354
|
-
href: session.operationsRecordHref ?? linkForSession(links.operationsRecords, session.sessionId),
|
|
30355
|
-
id: `failed-session:${session.sessionId}:${session.at}`,
|
|
30356
|
-
label: "Failed session",
|
|
30357
|
-
sessionId: session.sessionId,
|
|
30358
|
-
severity: "critical",
|
|
30359
|
-
source: "ops-recovery",
|
|
30360
|
-
value: session.provider
|
|
30361
|
-
});
|
|
30362
|
-
}
|
|
30363
|
-
};
|
|
30364
|
-
var pushMonitorEvents = (events, issues, links) => {
|
|
30365
|
-
if (!issues) {
|
|
30366
|
-
return;
|
|
30367
|
-
}
|
|
30368
|
-
for (const issue of issues) {
|
|
30369
|
-
if (issue.status === "resolved") {
|
|
30370
|
-
continue;
|
|
30371
|
-
}
|
|
30372
|
-
const sessionId = issue.impactedSessions[0];
|
|
30373
|
-
events.push({
|
|
30374
|
-
action: {
|
|
30375
|
-
href: issue.operationsRecordHrefs[0] ?? linkForSession(links.operationsRecords, sessionId) ?? links.monitorIssues,
|
|
30376
|
-
label: "Open monitor evidence"
|
|
30377
|
-
},
|
|
30378
|
-
at: issue.lastSeenAt,
|
|
30379
|
-
category: "monitor",
|
|
30380
|
-
detail: issue.detail,
|
|
30381
|
-
href: issue.operationsRecordHrefs[0] ?? linkForSession(links.operationsRecords, sessionId) ?? links.monitorIssues,
|
|
30382
|
-
id: `monitor:${issue.id}`,
|
|
30383
|
-
label: issue.label,
|
|
30384
|
-
sessionId,
|
|
30385
|
-
severity: issue.severity === "critical" ? "critical" : issue.severity === "warn" ? "warn" : "info",
|
|
30386
|
-
source: `monitor:${issue.monitorId}`,
|
|
30387
|
-
value: issue.value
|
|
30388
|
-
});
|
|
30389
|
-
}
|
|
30676
|
+
var productionReadinessStatusToCheck = (report, href) => {
|
|
30677
|
+
const gate = summarizeVoiceProductionReadinessGate(report);
|
|
30678
|
+
return {
|
|
30679
|
+
detail: gate.ok ? "Production readiness gate is open." : `${gate.failures.length} failures and ${gate.warnings.length} warnings.`,
|
|
30680
|
+
href,
|
|
30681
|
+
label: "Production readiness",
|
|
30682
|
+
status: gate.ok ? report.status : "fail",
|
|
30683
|
+
value: `${gate.failures.length} failures / ${gate.warnings.length} warnings`
|
|
30684
|
+
};
|
|
30390
30685
|
};
|
|
30391
|
-
var
|
|
30392
|
-
|
|
30393
|
-
|
|
30394
|
-
|
|
30395
|
-
|
|
30396
|
-
|
|
30397
|
-
|
|
30398
|
-
|
|
30399
|
-
|
|
30400
|
-
const debuggerHref = linkForSession(links.callDebugger, record.sessionId);
|
|
30401
|
-
events.push({
|
|
30402
|
-
action: {
|
|
30403
|
-
href: debuggerHref ?? href,
|
|
30404
|
-
label: debuggerHref ? "Open call debugger" : "Open operations record"
|
|
30405
|
-
},
|
|
30406
|
-
at: record.checkedAt,
|
|
30407
|
-
category: "call",
|
|
30408
|
-
detail: record.status === "failed" ? "Call operations record failed." : "Call operations record has warnings.",
|
|
30409
|
-
href,
|
|
30410
|
-
id: `operations-record:${record.sessionId}`,
|
|
30411
|
-
label: `Operations record ${record.status}`,
|
|
30412
|
-
sessionId: record.sessionId,
|
|
30413
|
-
severity: statusToSeverity(record.status),
|
|
30414
|
-
source: "operations-record",
|
|
30415
|
-
value: record.outcome.complete ? "complete" : "incomplete"
|
|
30416
|
-
});
|
|
30686
|
+
var buildVoiceOperationalStatusReport = async (options) => {
|
|
30687
|
+
const [proofPack, deliveryRuntimeReport, productionReadiness] = await Promise.all([
|
|
30688
|
+
resolveValue2(options.proofPack),
|
|
30689
|
+
isDeliveryRuntime(options.deliveryRuntime) ? buildVoiceDeliveryRuntimeReport(options.deliveryRuntime) : resolveValue2(options.deliveryRuntime),
|
|
30690
|
+
resolveValue2(options.productionReadiness)
|
|
30691
|
+
]);
|
|
30692
|
+
const checks = [];
|
|
30693
|
+
if (proofPack) {
|
|
30694
|
+
checks.push(proofPackStatusToCheck(proofPack, options.links?.proofPack));
|
|
30417
30695
|
}
|
|
30418
|
-
|
|
30419
|
-
|
|
30420
|
-
if (!replays) {
|
|
30421
|
-
return;
|
|
30696
|
+
if (deliveryRuntimeReport) {
|
|
30697
|
+
checks.push(deliveryRuntimeStatusToCheck(deliveryRuntimeReport, options.links?.deliveryRuntime));
|
|
30422
30698
|
}
|
|
30423
|
-
|
|
30424
|
-
|
|
30425
|
-
continue;
|
|
30426
|
-
}
|
|
30427
|
-
const href = replay.operationsRecordHref ?? linkForSession(links.failureReplay, replay.sessionId) ?? linkForSession(links.callDebugger, replay.sessionId);
|
|
30428
|
-
events.push({
|
|
30429
|
-
action: {
|
|
30430
|
-
href: linkForSession(links.callDebugger, replay.sessionId) ?? href ?? linkForSession(links.supportBundle, replay.sessionId),
|
|
30431
|
-
label: "Open replay/debug artifact"
|
|
30432
|
-
},
|
|
30433
|
-
at: replay.providers.steps[0]?.at ?? replay.media.steps[0]?.at ?? Date.now(),
|
|
30434
|
-
category: "failure-replay",
|
|
30435
|
-
detail: replay.summary.issues.join("; ") || replay.summary.userHeard.join(" ") || `Failure replay is ${replay.status}.`,
|
|
30436
|
-
href,
|
|
30437
|
-
id: `failure-replay:${replay.sessionId}`,
|
|
30438
|
-
label: `Failure replay ${replay.status}`,
|
|
30439
|
-
sessionId: replay.sessionId,
|
|
30440
|
-
severity: failureReplayStatusToSeverity(replay.status),
|
|
30441
|
-
source: "failure-replay",
|
|
30442
|
-
value: `${replay.providers.errors} provider errors / ${replay.media.errors} media errors`
|
|
30443
|
-
});
|
|
30699
|
+
if (productionReadiness) {
|
|
30700
|
+
checks.push(productionReadinessStatusToCheck(productionReadiness, options.links?.productionReadiness));
|
|
30444
30701
|
}
|
|
30445
|
-
};
|
|
30446
|
-
var buildVoiceIncidentTimelineReport = async (options) => {
|
|
30447
|
-
const now = options.now ?? Date.now();
|
|
30448
|
-
const links = options.links ?? {};
|
|
30449
|
-
const [
|
|
30450
|
-
operationalStatus,
|
|
30451
|
-
opsRecovery,
|
|
30452
|
-
monitorIssues,
|
|
30453
|
-
operationsRecords,
|
|
30454
|
-
failureReplays
|
|
30455
|
-
] = await Promise.all([
|
|
30456
|
-
resolveValue2(options.operationalStatus),
|
|
30457
|
-
resolveValue2(options.opsRecovery),
|
|
30458
|
-
resolveValue2(options.monitorIssues),
|
|
30459
|
-
resolveValue2(options.operationsRecords),
|
|
30460
|
-
resolveValue2(options.failureReplays)
|
|
30461
|
-
]);
|
|
30462
|
-
const events = [];
|
|
30463
|
-
pushOperationalStatusEvents(events, operationalStatus, links);
|
|
30464
|
-
pushOpsRecoveryEvents(events, opsRecovery, links);
|
|
30465
|
-
pushMonitorEvents(events, monitorIssues, links);
|
|
30466
|
-
pushOperationsRecordEvents(events, operationsRecords, links);
|
|
30467
|
-
pushFailureReplayEvents(events, failureReplays, links);
|
|
30468
|
-
const filtered = events.filter((event) => withinWindow(event, now, options.windowMs)).sort((left, right) => right.at - left.at).slice(0, options.limit ?? 50);
|
|
30469
30702
|
const summary = {
|
|
30470
|
-
|
|
30471
|
-
|
|
30472
|
-
total:
|
|
30473
|
-
warn:
|
|
30474
|
-
};
|
|
30475
|
-
const baseReport = {
|
|
30476
|
-
events: filtered,
|
|
30477
|
-
generatedAt: now,
|
|
30478
|
-
links,
|
|
30479
|
-
status: worstStatus3(filtered.map(eventStatus2)),
|
|
30480
|
-
summary,
|
|
30481
|
-
windowMs: options.windowMs
|
|
30703
|
+
fail: checks.filter((check) => check.status === "fail").length,
|
|
30704
|
+
pass: checks.filter((check) => check.status === "pass").length,
|
|
30705
|
+
total: checks.length,
|
|
30706
|
+
warn: checks.filter((check) => check.status === "warn").length
|
|
30482
30707
|
};
|
|
30483
|
-
const configuredActions = typeof options.recoveryActions === "function" ? await options.recoveryActions({
|
|
30484
|
-
events: filtered,
|
|
30485
|
-
report: baseReport
|
|
30486
|
-
}) : options.recoveryActions;
|
|
30487
30708
|
return {
|
|
30488
|
-
|
|
30489
|
-
|
|
30709
|
+
checkedAt: Date.now(),
|
|
30710
|
+
checks,
|
|
30711
|
+
links: options.links ?? {},
|
|
30712
|
+
status: worstStatus3(checks.map((check) => check.status)),
|
|
30713
|
+
summary
|
|
30490
30714
|
};
|
|
30491
30715
|
};
|
|
30492
|
-
var
|
|
30493
|
-
const title = options.title ?? "AbsoluteJS Voice
|
|
30494
|
-
const
|
|
30495
|
-
|
|
30496
|
-
|
|
30497
|
-
|
|
30498
|
-
|
|
30499
|
-
|
|
30500
|
-
}).join(`
|
|
30501
|
-
`);
|
|
30502
|
-
return `# ${title}
|
|
30503
|
-
|
|
30504
|
-
Status: ${report.status}
|
|
30505
|
-
|
|
30506
|
-
Generated: ${new Date(report.generatedAt).toISOString()}
|
|
30507
|
-
|
|
30508
|
-
Summary: ${report.summary.critical} critical, ${report.summary.warn} warn, ${report.summary.info} info, ${report.summary.total} total.
|
|
30509
|
-
|
|
30510
|
-
## Events
|
|
30511
|
-
|
|
30512
|
-
${rows || "- No incident timeline events."}
|
|
30513
|
-
|
|
30514
|
-
## Recovery Actions
|
|
30515
|
-
|
|
30516
|
-
${report.actions.map((action) => `- ${action.method ?? "GET"} ${action.id}: ${action.label}${action.href ? ` (${action.href})` : ""}${action.detail ? ` - ${action.detail}` : ""}`).join(`
|
|
30517
|
-
`) || "- No recovery actions."}
|
|
30518
|
-
`;
|
|
30519
|
-
};
|
|
30520
|
-
var renderVoiceIncidentTimelineHTML = (report, options = {}) => {
|
|
30521
|
-
const title = options.title ?? "AbsoluteJS Voice Incident Timeline";
|
|
30522
|
-
const actionPath = options.actionPath ?? "/api/voice/incident-timeline/actions";
|
|
30523
|
-
const events = report.events.map((event) => `<article class="${escapeHtml43(event.severity)}">
|
|
30524
|
-
<span>${escapeHtml43(event.severity.toUpperCase())} / ${escapeHtml43(event.category)}</span>
|
|
30525
|
-
<h2>${escapeHtml43(event.label)}</h2>
|
|
30526
|
-
<p>${escapeHtml43(new Date(event.at).toLocaleString())}${event.sessionId ? ` \xB7 session ${escapeHtml43(event.sessionId)}` : ""}</p>
|
|
30527
|
-
${event.value === undefined ? "" : `<strong>${escapeHtml43(String(event.value))}</strong>`}
|
|
30528
|
-
${event.detail ? `<p>${escapeHtml43(event.detail)}</p>` : ""}
|
|
30529
|
-
<div>${event.href ? `<a href="${escapeHtml43(event.href)}">Open source</a>` : ""}${event.action?.href ? `<a href="${escapeHtml43(event.action.href)}">${escapeHtml43(event.action.label)}</a>` : ""}</div>
|
|
30716
|
+
var renderVoiceOperationalStatusHTML = (report, options = {}) => {
|
|
30717
|
+
const title = options.title ?? "AbsoluteJS Voice Operational Status";
|
|
30718
|
+
const checks = report.checks.map((check) => `<article class="${escapeHtml43(check.status)}">
|
|
30719
|
+
<span>${escapeHtml43(check.status.toUpperCase())}</span>
|
|
30720
|
+
<h2>${escapeHtml43(check.label)}</h2>
|
|
30721
|
+
<strong>${escapeHtml43(String(check.value ?? check.status))}</strong>
|
|
30722
|
+
${check.detail ? `<p>${escapeHtml43(check.detail)}</p>` : ""}
|
|
30723
|
+
${check.href ? `<a href="${escapeHtml43(check.href)}">Open surface</a>` : ""}
|
|
30530
30724
|
</article>`).join("");
|
|
30531
|
-
|
|
30532
|
-
const label = escapeHtml43(action.label);
|
|
30533
|
-
const detail = action.detail ? `<p>${escapeHtml43(action.detail)}</p>` : "";
|
|
30534
|
-
const href = action.href ? `<a href="${escapeHtml43(action.href)}">Open target</a>` : "";
|
|
30535
|
-
const control = action.method === "POST" ? `<button type="button" data-voice-incident-action="${escapeHtml43(action.id)}" ${action.disabled ? "disabled" : ""}>${label}</button>` : href;
|
|
30536
|
-
return `<article class="action"><span>${escapeHtml43(action.method ?? "GET")}</span><h2>${label}</h2>${detail}<div>${control}${href && action.method === "POST" ? href : ""}</div></article>`;
|
|
30537
|
-
}).join("");
|
|
30538
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml43(title)}</title><style>body{background:#11110d;color:#faf4df;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1100px;padding:32px}.hero{background:linear-gradient(135deg,rgba(248,113,113,.2),rgba(245,158,11,.13),rgba(34,197,94,.12));border:1px solid #39301d;border-radius:30px;margin-bottom:18px;padding:28px}.eyebrow{color:#fcd34d;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}.status{border:1px solid #575030;border-radius:999px;display:inline-flex;font-weight:900;padding:8px 12px}.status.pass{border-color:rgba(34,197,94,.65)}.status.warn{border-color:rgba(245,158,11,.75)}.status.fail{border-color:rgba(239,68,68,.85)}.grid{display:grid;gap:14px}.actions{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));margin:0 0 18px}.summary{display:flex;flex-wrap:wrap;gap:10px}.summary span{background:#181711;border:1px solid #39301d;border-radius:999px;padding:8px 12px}article{background:#181711;border:1px solid #39301d;border-radius:22px;padding:18px}article.critical{border-color:rgba(239,68,68,.85)}article.warn{border-color:rgba(245,158,11,.75)}article.info{border-color:rgba(34,197,94,.55)}article.action{border-color:#5b4a22}article span{color:#fcd34d;font-size:.78rem;font-weight:900;letter-spacing:.08em}article h2{margin:.35rem 0}.muted,article p{color:#cfc5a8}article strong{display:block;font-size:1.3rem;margin:.5rem 0}a{color:#fde68a;margin-right:12px}button{background:#fcd34d;border:0;border-radius:999px;color:#171307;cursor:pointer;font-weight:900;padding:10px 14px}button:disabled{cursor:not-allowed;opacity:.55}</style></head><body><main><section class="hero"><p class="eyebrow">Operational triage</p><h1>${escapeHtml43(title)}</h1><p class="status ${escapeHtml43(report.status)}">Overall: ${escapeHtml43(report.status.toUpperCase())}</p><p class="muted">Generated ${escapeHtml43(new Date(report.generatedAt).toLocaleString())}</p><div class="summary"><span>${String(report.summary.critical)} critical</span><span>${String(report.summary.warn)} warn</span><span>${String(report.summary.info)} info</span><span>${String(report.summary.total)} total</span></div></section><h2>Recovery actions</h2><section class="actions">${actions || '<article class="action"><span>NONE</span><h2>No recovery actions</h2><p>No executable actions are available for this report.</p></article>'}</section><h2>Timeline</h2><section class="grid">${events || '<article class="info"><span>INFO</span><h2>No incident events</h2><p>No non-pass operational events were found in this window.</p></article>'}</section></main><script>const voiceIncidentActionPath=${JSON.stringify(actionPath)};document.querySelectorAll("[data-voice-incident-action]").forEach((button)=>{button.addEventListener("click",async()=>{const id=button.getAttribute("data-voice-incident-action");if(!id)return;button.disabled=true;const original=button.textContent;button.textContent="Running...";try{const response=await fetch(voiceIncidentActionPath+"/"+encodeURIComponent(id),{method:"POST"});button.textContent=response.ok?"Done":"Failed";if(response.ok)setTimeout(()=>location.reload(),700)}catch{button.textContent="Failed"}finally{setTimeout(()=>{button.disabled=false;button.textContent=original},1600)}})});</script></body></html>`;
|
|
30725
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml43(title)}</title><style>body{background:#10130f;color:#f8f3df;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1040px;padding:32px}.hero{background:linear-gradient(135deg,rgba(132,204,22,.18),rgba(14,165,233,.13));border:1px solid #2c3a28;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#bef264;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,4.8rem);line-height:.92;margin:.2rem 0 1rem}.status{border:1px solid #64748b;border-radius:999px;display:inline-flex;font-weight:900;padding:8px 12px}.grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(230px,1fr))}article{background:#171d15;border:1px solid #2c3a28;border-radius:22px;padding:18px}article.pass{border-color:rgba(34,197,94,.65)}article.warn{border-color:rgba(245,158,11,.75)}article.fail{border-color:rgba(239,68,68,.85)}article span{color:#bef264;font-size:.78rem;font-weight:900;letter-spacing:.08em}article.warn span{color:#fbbf24}article.fail span{color:#fca5a5}article strong{display:block;font-size:1.6rem;margin:.4rem 0}article p{color:#c5ceb9}a{color:#bef264}</style></head><body><main><section class="hero"><p class="eyebrow">Operational status</p><h1>${escapeHtml43(title)}</h1><p class="status ${escapeHtml43(report.status)}">Overall: ${escapeHtml43(report.status.toUpperCase())}</p><p>${String(report.summary.pass)}/${String(report.summary.total)} checks passing. Checked ${escapeHtml43(new Date(report.checkedAt).toLocaleString())}.</p></section><section class="grid">${checks || '<article class="pass"><span>PASS</span><h2>No operational checks configured</h2><strong>0/0</strong></article>'}</section></main></body></html>`;
|
|
30539
30726
|
};
|
|
30540
|
-
var
|
|
30541
|
-
const path = options.path ?? "/api/voice/
|
|
30542
|
-
const htmlPath = options.htmlPath === undefined ? "/voice/
|
|
30543
|
-
const markdownPath = options.markdownPath === undefined ? "/voice/incident-timeline.md" : options.markdownPath;
|
|
30544
|
-
const actionPath = options.actionPath === undefined ? "/api/voice/incident-timeline/actions" : options.actionPath;
|
|
30545
|
-
const recoveryOutcomePath = options.recoveryOutcomePath === undefined ? "/api/voice/incident-timeline/recovery-outcomes" : options.recoveryOutcomePath;
|
|
30546
|
-
const recoveryOutcomeHtmlPath = options.recoveryOutcomeHtmlPath === undefined ? "/voice/incident-recovery-outcomes" : options.recoveryOutcomeHtmlPath;
|
|
30727
|
+
var createVoiceOperationalStatusRoutes = (options) => {
|
|
30728
|
+
const path = options.path ?? "/api/voice/operational-status";
|
|
30729
|
+
const htmlPath = options.htmlPath === undefined ? "/voice/operational-status" : options.htmlPath;
|
|
30547
30730
|
const routes = new Elysia46({
|
|
30548
|
-
name: options.name ?? "absolutejs-voice-
|
|
30731
|
+
name: options.name ?? "absolutejs-voice-operational-status"
|
|
30549
30732
|
}).get(path, async () => {
|
|
30550
|
-
const report = await
|
|
30733
|
+
const report = await buildVoiceOperationalStatusReport(options);
|
|
30551
30734
|
return new Response(JSON.stringify(report), {
|
|
30552
30735
|
headers: {
|
|
30553
30736
|
"Content-Type": "application/json; charset=utf-8",
|
|
@@ -30558,11 +30741,8 @@ var createVoiceIncidentTimelineRoutes = (options) => {
|
|
|
30558
30741
|
});
|
|
30559
30742
|
if (htmlPath !== false) {
|
|
30560
30743
|
routes.get(htmlPath, async () => {
|
|
30561
|
-
const report = await
|
|
30562
|
-
const body = await (options.render ?? ((input) =>
|
|
30563
|
-
actionPath: actionPath === false ? undefined : actionPath,
|
|
30564
|
-
title: options.title
|
|
30565
|
-
})))(report);
|
|
30744
|
+
const report = await buildVoiceOperationalStatusReport(options);
|
|
30745
|
+
const body = await (options.render ?? ((input) => renderVoiceOperationalStatusHTML(input, { title: options.title })))(report);
|
|
30566
30746
|
return new Response(body, {
|
|
30567
30747
|
headers: {
|
|
30568
30748
|
"Content-Type": "text/html; charset=utf-8",
|
|
@@ -30571,130 +30751,6 @@ var createVoiceIncidentTimelineRoutes = (options) => {
|
|
|
30571
30751
|
});
|
|
30572
30752
|
});
|
|
30573
30753
|
}
|
|
30574
|
-
if (markdownPath !== false) {
|
|
30575
|
-
routes.get(markdownPath, async () => {
|
|
30576
|
-
const report = await buildVoiceIncidentTimelineReport(options);
|
|
30577
|
-
return new Response(renderVoiceIncidentTimelineMarkdown(report, {
|
|
30578
|
-
title: options.title
|
|
30579
|
-
}), {
|
|
30580
|
-
headers: {
|
|
30581
|
-
"Content-Type": "text/markdown; charset=utf-8",
|
|
30582
|
-
...options.headers
|
|
30583
|
-
}
|
|
30584
|
-
});
|
|
30585
|
-
});
|
|
30586
|
-
}
|
|
30587
|
-
if (actionPath !== false) {
|
|
30588
|
-
routes.get(actionPath, async () => {
|
|
30589
|
-
const report = await buildVoiceIncidentTimelineReport(options);
|
|
30590
|
-
return new Response(JSON.stringify({
|
|
30591
|
-
actions: report.actions,
|
|
30592
|
-
generatedAt: report.generatedAt,
|
|
30593
|
-
status: report.status
|
|
30594
|
-
}), {
|
|
30595
|
-
headers: {
|
|
30596
|
-
"Content-Type": "application/json; charset=utf-8",
|
|
30597
|
-
...options.headers
|
|
30598
|
-
}
|
|
30599
|
-
});
|
|
30600
|
-
}).post(`${actionPath}/:actionId`, async ({ params, request }) => {
|
|
30601
|
-
const actionId = params.actionId;
|
|
30602
|
-
const report = await buildVoiceIncidentTimelineReport(options);
|
|
30603
|
-
const action = report.actions.find((item) => item.id === actionId);
|
|
30604
|
-
const handler = options.actionHandlers?.[actionId];
|
|
30605
|
-
if (!action) {
|
|
30606
|
-
return new Response(JSON.stringify({
|
|
30607
|
-
actionId,
|
|
30608
|
-
ok: false,
|
|
30609
|
-
status: "not_found"
|
|
30610
|
-
}), {
|
|
30611
|
-
headers: {
|
|
30612
|
-
"Content-Type": "application/json; charset=utf-8",
|
|
30613
|
-
...options.headers
|
|
30614
|
-
},
|
|
30615
|
-
status: 404
|
|
30616
|
-
});
|
|
30617
|
-
}
|
|
30618
|
-
if (action.disabled || action.method !== "POST" || !handler) {
|
|
30619
|
-
return new Response(JSON.stringify({
|
|
30620
|
-
actionId,
|
|
30621
|
-
ok: false,
|
|
30622
|
-
status: action.disabled ? "disabled" : "not_executable"
|
|
30623
|
-
}), {
|
|
30624
|
-
headers: {
|
|
30625
|
-
"Content-Type": "application/json; charset=utf-8",
|
|
30626
|
-
...options.headers
|
|
30627
|
-
},
|
|
30628
|
-
status: 409
|
|
30629
|
-
});
|
|
30630
|
-
}
|
|
30631
|
-
const result = await handler({
|
|
30632
|
-
action,
|
|
30633
|
-
actionId,
|
|
30634
|
-
report,
|
|
30635
|
-
request
|
|
30636
|
-
});
|
|
30637
|
-
const status = result.ok ? 200 : 500;
|
|
30638
|
-
const afterReport = await buildVoiceIncidentTimelineReport(options);
|
|
30639
|
-
const resultWithStatus = {
|
|
30640
|
-
...result,
|
|
30641
|
-
afterStatus: result.afterStatus ?? afterReport.status,
|
|
30642
|
-
beforeStatus: result.beforeStatus ?? report.status
|
|
30643
|
-
};
|
|
30644
|
-
await recordVoiceOpsActionAudit({
|
|
30645
|
-
actionId: `incident.${actionId}`,
|
|
30646
|
-
body: {
|
|
30647
|
-
action,
|
|
30648
|
-
afterStatus: resultWithStatus.afterStatus,
|
|
30649
|
-
beforeStatus: resultWithStatus.beforeStatus,
|
|
30650
|
-
eventIds: report.events.map((event) => event.id),
|
|
30651
|
-
result
|
|
30652
|
-
},
|
|
30653
|
-
error: result.ok ? undefined : result.detail ?? result.status,
|
|
30654
|
-
ok: result.ok,
|
|
30655
|
-
ranAt: Date.now(),
|
|
30656
|
-
status
|
|
30657
|
-
}, {
|
|
30658
|
-
audit: options.audit,
|
|
30659
|
-
trace: options.trace
|
|
30660
|
-
});
|
|
30661
|
-
return new Response(JSON.stringify(resultWithStatus), {
|
|
30662
|
-
headers: {
|
|
30663
|
-
"Content-Type": "application/json; charset=utf-8",
|
|
30664
|
-
...options.headers
|
|
30665
|
-
},
|
|
30666
|
-
status
|
|
30667
|
-
});
|
|
30668
|
-
});
|
|
30669
|
-
}
|
|
30670
|
-
if (recoveryOutcomePath !== false) {
|
|
30671
|
-
routes.get(recoveryOutcomePath, async () => {
|
|
30672
|
-
const report = await buildVoiceIncidentRecoveryOutcomeReport({
|
|
30673
|
-
audit: options.audit
|
|
30674
|
-
});
|
|
30675
|
-
return new Response(JSON.stringify(report), {
|
|
30676
|
-
headers: {
|
|
30677
|
-
"Content-Type": "application/json; charset=utf-8",
|
|
30678
|
-
...options.headers
|
|
30679
|
-
}
|
|
30680
|
-
});
|
|
30681
|
-
});
|
|
30682
|
-
}
|
|
30683
|
-
if (recoveryOutcomeHtmlPath !== false) {
|
|
30684
|
-
routes.get(recoveryOutcomeHtmlPath, async () => {
|
|
30685
|
-
const report = await buildVoiceIncidentRecoveryOutcomeReport({
|
|
30686
|
-
audit: options.audit
|
|
30687
|
-
});
|
|
30688
|
-
return new Response(renderVoiceIncidentRecoveryOutcomeHTML(report, {
|
|
30689
|
-
title: `${options.title ?? "AbsoluteJS Voice Incident Timeline"} Recovery Outcomes`
|
|
30690
|
-
}), {
|
|
30691
|
-
headers: {
|
|
30692
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
30693
|
-
...options.headers
|
|
30694
|
-
}
|
|
30695
|
-
});
|
|
30696
|
-
});
|
|
30697
|
-
}
|
|
30698
30754
|
return routes;
|
|
30699
30755
|
};
|
|
30700
30756
|
// src/dataControl.ts
|
|
@@ -42064,6 +42120,7 @@ export {
|
|
|
42064
42120
|
buildVoiceLatencySLOGate,
|
|
42065
42121
|
buildVoiceIncidentTimelineReport,
|
|
42066
42122
|
buildVoiceIncidentRecoveryOutcomeReport,
|
|
42123
|
+
buildVoiceIncidentRecoveryOutcomeReadinessCheck,
|
|
42067
42124
|
buildVoiceIncidentBundle,
|
|
42068
42125
|
buildVoiceIOProviderRouterTraceEvent,
|
|
42069
42126
|
buildVoiceGuardrailReport,
|