@absolutejs/voice 0.0.22-beta.437 → 0.0.22-beta.439
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 +59 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +966 -768
- 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,111 +26434,807 @@ 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
|
-
|
|
26505
|
-
|
|
26506
|
-
|
|
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
|
+
};
|
|
26507
26557
|
};
|
|
26508
|
-
var
|
|
26509
|
-
|
|
26510
|
-
|
|
26511
|
-
|
|
26512
|
-
|
|
26513
|
-
|
|
26514
|
-
|
|
26515
|
-
|
|
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
|
+
};
|
|
26516
26592
|
};
|
|
26517
|
-
var
|
|
26518
|
-
|
|
26519
|
-
|
|
26520
|
-
|
|
26521
|
-
|
|
26522
|
-
|
|
26593
|
+
var rate3 = (count, total) => total > 0 ? count / total : 0;
|
|
26594
|
+
var toIncidentRecoveryTrendCycle = (report) => ({
|
|
26595
|
+
checkedAt: report.checkedAt,
|
|
26596
|
+
failed: report.failed,
|
|
26597
|
+
failureRate: rate3(report.failed, report.total),
|
|
26598
|
+
improved: report.improved,
|
|
26599
|
+
improvementRate: rate3(report.improved, report.total),
|
|
26600
|
+
regressed: report.regressed,
|
|
26601
|
+
regressionRate: rate3(report.regressed, report.total),
|
|
26602
|
+
total: report.total,
|
|
26603
|
+
unchanged: report.unchanged,
|
|
26604
|
+
unchangedRate: rate3(report.unchanged, report.total)
|
|
26605
|
+
});
|
|
26606
|
+
var buildVoiceIncidentRecoveryTrendReport = (reports = []) => {
|
|
26607
|
+
const cycles = reports.map(toIncidentRecoveryTrendCycle).sort((left, right) => left.checkedAt - right.checkedAt);
|
|
26608
|
+
const totals = cycles.reduce((summary, cycle) => ({
|
|
26609
|
+
failed: summary.failed + cycle.failed,
|
|
26610
|
+
improved: summary.improved + cycle.improved,
|
|
26611
|
+
regressed: summary.regressed + cycle.regressed,
|
|
26612
|
+
total: summary.total + cycle.total,
|
|
26613
|
+
unchanged: summary.unchanged + cycle.unchanged
|
|
26614
|
+
}), { failed: 0, improved: 0, regressed: 0, total: 0, unchanged: 0 });
|
|
26615
|
+
const latest = cycles.at(-1);
|
|
26616
|
+
const previous = cycles.at(-2);
|
|
26617
|
+
const status = cycles.length === 0 ? "empty" : latest && (latest.failed > 0 || latest.regressed > 0) ? "fail" : latest && previous && (latest.improvementRate < previous.improvementRate || latest.unchangedRate > previous.unchangedRate) ? "warn" : "pass";
|
|
26618
|
+
return {
|
|
26619
|
+
checkedAt: Date.now(),
|
|
26620
|
+
cycles,
|
|
26621
|
+
latest,
|
|
26622
|
+
previous,
|
|
26623
|
+
status,
|
|
26624
|
+
summary: {
|
|
26625
|
+
cycles: cycles.length,
|
|
26626
|
+
failed: totals.failed,
|
|
26627
|
+
failureRate: rate3(totals.failed, totals.total),
|
|
26628
|
+
improved: totals.improved,
|
|
26629
|
+
improvementRate: rate3(totals.improved, totals.total),
|
|
26630
|
+
regressed: totals.regressed,
|
|
26631
|
+
regressionRate: rate3(totals.regressed, totals.total),
|
|
26632
|
+
total: totals.total,
|
|
26633
|
+
unchanged: totals.unchanged,
|
|
26634
|
+
unchangedRate: rate3(totals.unchanged, totals.total)
|
|
26635
|
+
},
|
|
26636
|
+
trend: {
|
|
26637
|
+
failureRateDelta: latest && previous ? latest.failureRate - previous.failureRate : undefined,
|
|
26638
|
+
improvementRateDelta: latest && previous ? latest.improvementRate - previous.improvementRate : undefined,
|
|
26639
|
+
regressionRateDelta: latest && previous ? latest.regressionRate - previous.regressionRate : undefined,
|
|
26640
|
+
unchangedRateDelta: latest && previous ? latest.unchangedRate - previous.unchangedRate : undefined
|
|
26641
|
+
}
|
|
26642
|
+
};
|
|
26643
|
+
};
|
|
26644
|
+
var percent = (value) => value === undefined ? "n/a" : `${Math.round(value * 100)}%`;
|
|
26645
|
+
var renderVoiceIncidentRecoveryTrendMarkdown = (report, options = {}) => {
|
|
26646
|
+
const title = options.title ?? "Voice Incident Recovery Trend";
|
|
26647
|
+
const rows = report.cycles.map((cycle) => `| ${new Date(cycle.checkedAt).toISOString()} | ${cycle.total} | ${cycle.improved} | ${cycle.unchanged} | ${cycle.regressed} | ${cycle.failed} | ${percent(cycle.improvementRate)} | ${percent(cycle.regressionRate)} |`).join(`
|
|
26648
|
+
`);
|
|
26649
|
+
return `# ${title}
|
|
26650
|
+
|
|
26651
|
+
Generated: ${new Date(report.checkedAt).toISOString()}
|
|
26652
|
+
|
|
26653
|
+
Status: **${report.status}**
|
|
26654
|
+
|
|
26655
|
+
Cycles: ${report.summary.cycles}
|
|
26656
|
+
|
|
26657
|
+
Total actions: ${report.summary.total}
|
|
26658
|
+
|
|
26659
|
+
Improvement rate: ${percent(report.summary.improvementRate)}
|
|
26660
|
+
|
|
26661
|
+
Regression rate: ${percent(report.summary.regressionRate)}
|
|
26662
|
+
|
|
26663
|
+
Failure rate: ${percent(report.summary.failureRate)}
|
|
26664
|
+
|
|
26665
|
+
Unchanged rate: ${percent(report.summary.unchangedRate)}
|
|
26666
|
+
|
|
26667
|
+
Improvement delta: ${percent(report.trend.improvementRateDelta)}
|
|
26668
|
+
|
|
26669
|
+
Regression delta: ${percent(report.trend.regressionRateDelta)}
|
|
26670
|
+
|
|
26671
|
+
## Cycles
|
|
26672
|
+
|
|
26673
|
+
| Checked at | Total | Improved | Unchanged | Regressed | Failed | Improve % | Regress % |
|
|
26674
|
+
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
|
26675
|
+
${rows || "| n/a | 0 | 0 | 0 | 0 | 0 | n/a | n/a |"}
|
|
26676
|
+
`;
|
|
26677
|
+
};
|
|
26678
|
+
var renderVoiceIncidentRecoveryTrendHTML = (report, options = {}) => {
|
|
26679
|
+
const title = options.title ?? "AbsoluteJS Voice Incident Recovery Trend";
|
|
26680
|
+
const rows = report.cycles.map((cycle) => `<tr><td>${escapeHtml41(new Date(cycle.checkedAt).toLocaleString())}</td><td>${String(cycle.total)}</td><td>${String(cycle.improved)}</td><td>${String(cycle.unchanged)}</td><td>${String(cycle.regressed)}</td><td>${String(cycle.failed)}</td><td>${escapeHtml41(percent(cycle.improvementRate))}</td><td>${escapeHtml41(percent(cycle.regressionRate))}</td></tr>`).join("");
|
|
26681
|
+
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:1080px;padding:32px}.hero,table{background:#181711;border:1px solid #39301d;border-radius:24px}.hero{margin-bottom:16px;padding:24px}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}table{border-collapse:collapse;overflow:hidden;width:100%}td,th{border-bottom:1px solid #39301d;padding:12px;text-align:left}.pass{color:#86efac}.warn,.empty{color:#fcd34d}.fail{color:#fca5a5}p{color:#cfc5a8}</style></head><body><main><section class="hero"><span>Recovery trend</span><h1>${escapeHtml41(title)}</h1><p class="${escapeHtml41(report.status)}">Status: ${escapeHtml41(report.status)}</p><div class="summary"><span>${String(report.summary.cycles)} cycles</span><span>${String(report.summary.total)} actions</span><span>${escapeHtml41(percent(report.summary.improvementRate))} improved</span><span>${escapeHtml41(percent(report.summary.regressionRate))} regressed</span><span>${escapeHtml41(percent(report.trend.improvementRateDelta))} improvement delta</span></div></section><table><thead><tr><th>Checked at</th><th>Total</th><th>Improved</th><th>Unchanged</th><th>Regressed</th><th>Failed</th><th>Improve %</th><th>Regress %</th></tr></thead><tbody>${rows || '<tr><td colspan="8">No recovery outcome history has been recorded.</td></tr>'}</tbody></table></main></body></html>`;
|
|
26682
|
+
};
|
|
26683
|
+
var pushOperationalStatusEvents = (events, report, links) => {
|
|
26684
|
+
if (!report) {
|
|
26685
|
+
return;
|
|
26686
|
+
}
|
|
26687
|
+
for (const check of report.checks) {
|
|
26688
|
+
if (check.status === "pass") {
|
|
26689
|
+
continue;
|
|
26690
|
+
}
|
|
26691
|
+
events.push({
|
|
26692
|
+
action: {
|
|
26693
|
+
href: check.href ?? links.operationalStatus,
|
|
26694
|
+
label: "Open source"
|
|
26695
|
+
},
|
|
26696
|
+
at: report.checkedAt,
|
|
26697
|
+
category: check.label.toLowerCase().includes("readiness") ? "readiness" : "operational-status",
|
|
26698
|
+
detail: check.detail,
|
|
26699
|
+
href: check.href ?? links.operationalStatus,
|
|
26700
|
+
id: `operational:${check.label}`,
|
|
26701
|
+
label: check.label,
|
|
26702
|
+
severity: statusToSeverity(check.status),
|
|
26703
|
+
source: "operational-status",
|
|
26704
|
+
value: check.value
|
|
26523
26705
|
});
|
|
26524
26706
|
}
|
|
26525
26707
|
};
|
|
26526
|
-
var
|
|
26527
|
-
if (!
|
|
26528
|
-
pushValidationIssue(issues, {
|
|
26529
|
-
code: "voice.observability.export.missing_field",
|
|
26530
|
-
message: `${path} must be an array.`,
|
|
26531
|
-
path
|
|
26532
|
-
});
|
|
26708
|
+
var pushOpsRecoveryEvents = (events, report, links) => {
|
|
26709
|
+
if (!report) {
|
|
26533
26710
|
return;
|
|
26534
26711
|
}
|
|
26535
|
-
|
|
26536
|
-
|
|
26537
|
-
|
|
26538
|
-
|
|
26539
|
-
|
|
26540
|
-
|
|
26541
|
-
|
|
26712
|
+
for (const issue of report.issues) {
|
|
26713
|
+
events.push({
|
|
26714
|
+
action: {
|
|
26715
|
+
href: issue.href ?? links.operationalStatus,
|
|
26716
|
+
label: "Inspect recovery issue"
|
|
26717
|
+
},
|
|
26718
|
+
at: report.checkedAt,
|
|
26719
|
+
category: "recovery",
|
|
26720
|
+
detail: issue.detail,
|
|
26721
|
+
href: issue.href,
|
|
26722
|
+
id: `ops-recovery:${issue.code}`,
|
|
26723
|
+
label: issue.label,
|
|
26724
|
+
severity: issue.severity === "fail" ? "critical" : "warn",
|
|
26725
|
+
source: "ops-recovery",
|
|
26726
|
+
value: issue.value
|
|
26727
|
+
});
|
|
26728
|
+
}
|
|
26729
|
+
for (const session of report.failedSessions) {
|
|
26730
|
+
events.push({
|
|
26731
|
+
action: {
|
|
26732
|
+
href: session.operationsRecordHref ?? linkForSession(links.operationsRecords, session.sessionId) ?? linkForSession(links.callDebugger, session.sessionId),
|
|
26733
|
+
label: "Open affected call"
|
|
26734
|
+
},
|
|
26735
|
+
at: session.at,
|
|
26736
|
+
category: "call",
|
|
26737
|
+
detail: session.error,
|
|
26738
|
+
href: session.operationsRecordHref ?? linkForSession(links.operationsRecords, session.sessionId),
|
|
26739
|
+
id: `failed-session:${session.sessionId}:${session.at}`,
|
|
26740
|
+
label: "Failed session",
|
|
26741
|
+
sessionId: session.sessionId,
|
|
26742
|
+
severity: "critical",
|
|
26743
|
+
source: "ops-recovery",
|
|
26744
|
+
value: session.provider
|
|
26745
|
+
});
|
|
26746
|
+
}
|
|
26747
|
+
};
|
|
26748
|
+
var pushMonitorEvents = (events, issues, links) => {
|
|
26749
|
+
if (!issues) {
|
|
26750
|
+
return;
|
|
26751
|
+
}
|
|
26752
|
+
for (const issue of issues) {
|
|
26753
|
+
if (issue.status === "resolved") {
|
|
26754
|
+
continue;
|
|
26755
|
+
}
|
|
26756
|
+
const sessionId = issue.impactedSessions[0];
|
|
26757
|
+
events.push({
|
|
26758
|
+
action: {
|
|
26759
|
+
href: issue.operationsRecordHrefs[0] ?? linkForSession(links.operationsRecords, sessionId) ?? links.monitorIssues,
|
|
26760
|
+
label: "Open monitor evidence"
|
|
26761
|
+
},
|
|
26762
|
+
at: issue.lastSeenAt,
|
|
26763
|
+
category: "monitor",
|
|
26764
|
+
detail: issue.detail,
|
|
26765
|
+
href: issue.operationsRecordHrefs[0] ?? linkForSession(links.operationsRecords, sessionId) ?? links.monitorIssues,
|
|
26766
|
+
id: `monitor:${issue.id}`,
|
|
26767
|
+
label: issue.label,
|
|
26768
|
+
sessionId,
|
|
26769
|
+
severity: issue.severity === "critical" ? "critical" : issue.severity === "warn" ? "warn" : "info",
|
|
26770
|
+
source: `monitor:${issue.monitorId}`,
|
|
26771
|
+
value: issue.value
|
|
26772
|
+
});
|
|
26773
|
+
}
|
|
26774
|
+
};
|
|
26775
|
+
var pushOperationsRecordEvents = (events, records, links) => {
|
|
26776
|
+
if (!records) {
|
|
26777
|
+
return;
|
|
26778
|
+
}
|
|
26779
|
+
for (const record of records) {
|
|
26780
|
+
if (record.status === "healthy") {
|
|
26781
|
+
continue;
|
|
26782
|
+
}
|
|
26783
|
+
const href = linkForSession(links.operationsRecords, record.sessionId);
|
|
26784
|
+
const debuggerHref = linkForSession(links.callDebugger, record.sessionId);
|
|
26785
|
+
events.push({
|
|
26786
|
+
action: {
|
|
26787
|
+
href: debuggerHref ?? href,
|
|
26788
|
+
label: debuggerHref ? "Open call debugger" : "Open operations record"
|
|
26789
|
+
},
|
|
26790
|
+
at: record.checkedAt,
|
|
26791
|
+
category: "call",
|
|
26792
|
+
detail: record.status === "failed" ? "Call operations record failed." : "Call operations record has warnings.",
|
|
26793
|
+
href,
|
|
26794
|
+
id: `operations-record:${record.sessionId}`,
|
|
26795
|
+
label: `Operations record ${record.status}`,
|
|
26796
|
+
sessionId: record.sessionId,
|
|
26797
|
+
severity: statusToSeverity(record.status),
|
|
26798
|
+
source: "operations-record",
|
|
26799
|
+
value: record.outcome.complete ? "complete" : "incomplete"
|
|
26800
|
+
});
|
|
26801
|
+
}
|
|
26802
|
+
};
|
|
26803
|
+
var pushFailureReplayEvents = (events, replays, links) => {
|
|
26804
|
+
if (!replays) {
|
|
26805
|
+
return;
|
|
26806
|
+
}
|
|
26807
|
+
for (const replay of replays) {
|
|
26808
|
+
if (replay.status === "healthy") {
|
|
26809
|
+
continue;
|
|
26810
|
+
}
|
|
26811
|
+
const href = replay.operationsRecordHref ?? linkForSession(links.failureReplay, replay.sessionId) ?? linkForSession(links.callDebugger, replay.sessionId);
|
|
26812
|
+
events.push({
|
|
26813
|
+
action: {
|
|
26814
|
+
href: linkForSession(links.callDebugger, replay.sessionId) ?? href ?? linkForSession(links.supportBundle, replay.sessionId),
|
|
26815
|
+
label: "Open replay/debug artifact"
|
|
26816
|
+
},
|
|
26817
|
+
at: replay.providers.steps[0]?.at ?? replay.media.steps[0]?.at ?? Date.now(),
|
|
26818
|
+
category: "failure-replay",
|
|
26819
|
+
detail: replay.summary.issues.join("; ") || replay.summary.userHeard.join(" ") || `Failure replay is ${replay.status}.`,
|
|
26820
|
+
href,
|
|
26821
|
+
id: `failure-replay:${replay.sessionId}`,
|
|
26822
|
+
label: `Failure replay ${replay.status}`,
|
|
26823
|
+
sessionId: replay.sessionId,
|
|
26824
|
+
severity: failureReplayStatusToSeverity(replay.status),
|
|
26825
|
+
source: "failure-replay",
|
|
26826
|
+
value: `${replay.providers.errors} provider errors / ${replay.media.errors} media errors`
|
|
26827
|
+
});
|
|
26828
|
+
}
|
|
26829
|
+
};
|
|
26830
|
+
var buildVoiceIncidentTimelineReport = async (options) => {
|
|
26831
|
+
const now = options.now ?? Date.now();
|
|
26832
|
+
const links = options.links ?? {};
|
|
26833
|
+
const [
|
|
26834
|
+
operationalStatus,
|
|
26835
|
+
opsRecovery,
|
|
26836
|
+
monitorIssues,
|
|
26837
|
+
operationsRecords,
|
|
26838
|
+
failureReplays
|
|
26839
|
+
] = await Promise.all([
|
|
26840
|
+
resolveValue(options.operationalStatus),
|
|
26841
|
+
resolveValue(options.opsRecovery),
|
|
26842
|
+
resolveValue(options.monitorIssues),
|
|
26843
|
+
resolveValue(options.operationsRecords),
|
|
26844
|
+
resolveValue(options.failureReplays)
|
|
26845
|
+
]);
|
|
26846
|
+
const events = [];
|
|
26847
|
+
pushOperationalStatusEvents(events, operationalStatus, links);
|
|
26848
|
+
pushOpsRecoveryEvents(events, opsRecovery, links);
|
|
26849
|
+
pushMonitorEvents(events, monitorIssues, links);
|
|
26850
|
+
pushOperationsRecordEvents(events, operationsRecords, links);
|
|
26851
|
+
pushFailureReplayEvents(events, failureReplays, links);
|
|
26852
|
+
const filtered = events.filter((event) => withinWindow(event, now, options.windowMs)).sort((left, right) => right.at - left.at).slice(0, options.limit ?? 50);
|
|
26853
|
+
const summary = {
|
|
26854
|
+
critical: filtered.filter((event) => event.severity === "critical").length,
|
|
26855
|
+
info: filtered.filter((event) => event.severity === "info").length,
|
|
26856
|
+
total: filtered.length,
|
|
26857
|
+
warn: filtered.filter((event) => event.severity === "warn").length
|
|
26858
|
+
};
|
|
26859
|
+
const baseReport = {
|
|
26860
|
+
events: filtered,
|
|
26861
|
+
generatedAt: now,
|
|
26862
|
+
links,
|
|
26863
|
+
status: worstStatus2(filtered.map(eventStatus2)),
|
|
26864
|
+
summary,
|
|
26865
|
+
windowMs: options.windowMs
|
|
26866
|
+
};
|
|
26867
|
+
const configuredActions = typeof options.recoveryActions === "function" ? await options.recoveryActions({
|
|
26868
|
+
events: filtered,
|
|
26869
|
+
report: baseReport
|
|
26870
|
+
}) : options.recoveryActions;
|
|
26871
|
+
return {
|
|
26872
|
+
...baseReport,
|
|
26873
|
+
actions: configuredActions === undefined ? defaultIncidentRecoveryActions(filtered, links) : [...configuredActions]
|
|
26874
|
+
};
|
|
26875
|
+
};
|
|
26876
|
+
var renderVoiceIncidentTimelineMarkdown = (report, options = {}) => {
|
|
26877
|
+
const title = options.title ?? "AbsoluteJS Voice Incident Timeline";
|
|
26878
|
+
const rows = report.events.map((event) => {
|
|
26879
|
+
const when = new Date(event.at).toISOString();
|
|
26880
|
+
const target = event.href ? ` [open](${event.href})` : "";
|
|
26881
|
+
const session = event.sessionId ? ` session=${event.sessionId}` : "";
|
|
26882
|
+
const value = event.value === undefined ? "" : ` value=${event.value}`;
|
|
26883
|
+
return `- ${when} ${event.severity.toUpperCase()} ${event.label}${session}${value}${target}${event.detail ? ` - ${event.detail}` : ""}`;
|
|
26884
|
+
}).join(`
|
|
26885
|
+
`);
|
|
26886
|
+
return `# ${title}
|
|
26887
|
+
|
|
26888
|
+
Status: ${report.status}
|
|
26889
|
+
|
|
26890
|
+
Generated: ${new Date(report.generatedAt).toISOString()}
|
|
26891
|
+
|
|
26892
|
+
Summary: ${report.summary.critical} critical, ${report.summary.warn} warn, ${report.summary.info} info, ${report.summary.total} total.
|
|
26893
|
+
|
|
26894
|
+
## Events
|
|
26895
|
+
|
|
26896
|
+
${rows || "- No incident timeline events."}
|
|
26897
|
+
|
|
26898
|
+
## Recovery Actions
|
|
26899
|
+
|
|
26900
|
+
${report.actions.map((action) => `- ${action.method ?? "GET"} ${action.id}: ${action.label}${action.href ? ` (${action.href})` : ""}${action.detail ? ` - ${action.detail}` : ""}`).join(`
|
|
26901
|
+
`) || "- No recovery actions."}
|
|
26902
|
+
`;
|
|
26903
|
+
};
|
|
26904
|
+
var renderVoiceIncidentTimelineHTML = (report, options = {}) => {
|
|
26905
|
+
const title = options.title ?? "AbsoluteJS Voice Incident Timeline";
|
|
26906
|
+
const actionPath = options.actionPath ?? "/api/voice/incident-timeline/actions";
|
|
26907
|
+
const events = report.events.map((event) => `<article class="${escapeHtml41(event.severity)}">
|
|
26908
|
+
<span>${escapeHtml41(event.severity.toUpperCase())} / ${escapeHtml41(event.category)}</span>
|
|
26909
|
+
<h2>${escapeHtml41(event.label)}</h2>
|
|
26910
|
+
<p>${escapeHtml41(new Date(event.at).toLocaleString())}${event.sessionId ? ` \xB7 session ${escapeHtml41(event.sessionId)}` : ""}</p>
|
|
26911
|
+
${event.value === undefined ? "" : `<strong>${escapeHtml41(String(event.value))}</strong>`}
|
|
26912
|
+
${event.detail ? `<p>${escapeHtml41(event.detail)}</p>` : ""}
|
|
26913
|
+
<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>
|
|
26914
|
+
</article>`).join("");
|
|
26915
|
+
const actions = report.actions.map((action) => {
|
|
26916
|
+
const label = escapeHtml41(action.label);
|
|
26917
|
+
const detail = action.detail ? `<p>${escapeHtml41(action.detail)}</p>` : "";
|
|
26918
|
+
const href = action.href ? `<a href="${escapeHtml41(action.href)}">Open target</a>` : "";
|
|
26919
|
+
const control = action.method === "POST" ? `<button type="button" data-voice-incident-action="${escapeHtml41(action.id)}" ${action.disabled ? "disabled" : ""}>${label}</button>` : href;
|
|
26920
|
+
return `<article class="action"><span>${escapeHtml41(action.method ?? "GET")}</span><h2>${label}</h2>${detail}<div>${control}${href && action.method === "POST" ? href : ""}</div></article>`;
|
|
26921
|
+
}).join("");
|
|
26922
|
+
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>`;
|
|
26923
|
+
};
|
|
26924
|
+
var createVoiceIncidentTimelineRoutes = (options) => {
|
|
26925
|
+
const path = options.path ?? "/api/voice/incident-timeline";
|
|
26926
|
+
const htmlPath = options.htmlPath === undefined ? "/voice/incident-timeline" : options.htmlPath;
|
|
26927
|
+
const markdownPath = options.markdownPath === undefined ? "/voice/incident-timeline.md" : options.markdownPath;
|
|
26928
|
+
const actionPath = options.actionPath === undefined ? "/api/voice/incident-timeline/actions" : options.actionPath;
|
|
26929
|
+
const recoveryOutcomePath = options.recoveryOutcomePath === undefined ? "/api/voice/incident-timeline/recovery-outcomes" : options.recoveryOutcomePath;
|
|
26930
|
+
const recoveryOutcomeHtmlPath = options.recoveryOutcomeHtmlPath === undefined ? "/voice/incident-recovery-outcomes" : options.recoveryOutcomeHtmlPath;
|
|
26931
|
+
const recoveryTrendPath = options.recoveryTrendPath === undefined ? "/api/voice/incident-timeline/recovery-trends" : options.recoveryTrendPath;
|
|
26932
|
+
const recoveryTrendHtmlPath = options.recoveryTrendHtmlPath === undefined ? "/voice/incident-recovery-trends" : options.recoveryTrendHtmlPath;
|
|
26933
|
+
const recoveryTrendMarkdownPath = options.recoveryTrendMarkdownPath === undefined ? "/voice/incident-recovery-trends.md" : options.recoveryTrendMarkdownPath;
|
|
26934
|
+
const buildRecoveryTrendReport = async () => {
|
|
26935
|
+
const reports = typeof options.recoveryTrendReports === "function" ? await options.recoveryTrendReports() : options.recoveryTrendReports;
|
|
26936
|
+
return buildVoiceIncidentRecoveryTrendReport(reports ?? [
|
|
26937
|
+
await buildVoiceIncidentRecoveryOutcomeReport({
|
|
26938
|
+
audit: options.audit
|
|
26939
|
+
})
|
|
26940
|
+
]);
|
|
26941
|
+
};
|
|
26942
|
+
const routes = new Elysia43({
|
|
26943
|
+
name: options.name ?? "absolutejs-voice-incident-timeline"
|
|
26944
|
+
}).get(path, async () => {
|
|
26945
|
+
const report = await buildVoiceIncidentTimelineReport(options);
|
|
26946
|
+
return new Response(JSON.stringify(report), {
|
|
26947
|
+
headers: {
|
|
26948
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
26949
|
+
...options.headers
|
|
26950
|
+
},
|
|
26951
|
+
status: report.status === "fail" ? 503 : 200
|
|
26952
|
+
});
|
|
26953
|
+
});
|
|
26954
|
+
if (htmlPath !== false) {
|
|
26955
|
+
routes.get(htmlPath, async () => {
|
|
26956
|
+
const report = await buildVoiceIncidentTimelineReport(options);
|
|
26957
|
+
const body = await (options.render ?? ((input) => renderVoiceIncidentTimelineHTML(input, {
|
|
26958
|
+
actionPath: actionPath === false ? undefined : actionPath,
|
|
26959
|
+
title: options.title
|
|
26960
|
+
})))(report);
|
|
26961
|
+
return new Response(body, {
|
|
26962
|
+
headers: {
|
|
26963
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
26964
|
+
...options.headers
|
|
26965
|
+
}
|
|
26966
|
+
});
|
|
26967
|
+
});
|
|
26968
|
+
}
|
|
26969
|
+
if (markdownPath !== false) {
|
|
26970
|
+
routes.get(markdownPath, async () => {
|
|
26971
|
+
const report = await buildVoiceIncidentTimelineReport(options);
|
|
26972
|
+
return new Response(renderVoiceIncidentTimelineMarkdown(report, {
|
|
26973
|
+
title: options.title
|
|
26974
|
+
}), {
|
|
26975
|
+
headers: {
|
|
26976
|
+
"Content-Type": "text/markdown; charset=utf-8",
|
|
26977
|
+
...options.headers
|
|
26978
|
+
}
|
|
26979
|
+
});
|
|
26980
|
+
});
|
|
26981
|
+
}
|
|
26982
|
+
if (actionPath !== false) {
|
|
26983
|
+
routes.get(actionPath, async () => {
|
|
26984
|
+
const report = await buildVoiceIncidentTimelineReport(options);
|
|
26985
|
+
return new Response(JSON.stringify({
|
|
26986
|
+
actions: report.actions,
|
|
26987
|
+
generatedAt: report.generatedAt,
|
|
26988
|
+
status: report.status
|
|
26989
|
+
}), {
|
|
26990
|
+
headers: {
|
|
26991
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
26992
|
+
...options.headers
|
|
26993
|
+
}
|
|
26994
|
+
});
|
|
26995
|
+
}).post(`${actionPath}/:actionId`, async ({ params, request }) => {
|
|
26996
|
+
const actionId = params.actionId;
|
|
26997
|
+
const report = await buildVoiceIncidentTimelineReport(options);
|
|
26998
|
+
const action = report.actions.find((item) => item.id === actionId);
|
|
26999
|
+
const handler = options.actionHandlers?.[actionId];
|
|
27000
|
+
if (!action) {
|
|
27001
|
+
return new Response(JSON.stringify({
|
|
27002
|
+
actionId,
|
|
27003
|
+
ok: false,
|
|
27004
|
+
status: "not_found"
|
|
27005
|
+
}), {
|
|
27006
|
+
headers: {
|
|
27007
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
27008
|
+
...options.headers
|
|
27009
|
+
},
|
|
27010
|
+
status: 404
|
|
27011
|
+
});
|
|
27012
|
+
}
|
|
27013
|
+
if (action.disabled || action.method !== "POST" || !handler) {
|
|
27014
|
+
return new Response(JSON.stringify({
|
|
27015
|
+
actionId,
|
|
27016
|
+
ok: false,
|
|
27017
|
+
status: action.disabled ? "disabled" : "not_executable"
|
|
27018
|
+
}), {
|
|
27019
|
+
headers: {
|
|
27020
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
27021
|
+
...options.headers
|
|
27022
|
+
},
|
|
27023
|
+
status: 409
|
|
27024
|
+
});
|
|
27025
|
+
}
|
|
27026
|
+
const result = await handler({
|
|
27027
|
+
action,
|
|
27028
|
+
actionId,
|
|
27029
|
+
report,
|
|
27030
|
+
request
|
|
27031
|
+
});
|
|
27032
|
+
const status = result.ok ? 200 : 500;
|
|
27033
|
+
const afterReport = await buildVoiceIncidentTimelineReport(options);
|
|
27034
|
+
const resultWithStatus = {
|
|
27035
|
+
...result,
|
|
27036
|
+
afterStatus: result.afterStatus ?? afterReport.status,
|
|
27037
|
+
beforeStatus: result.beforeStatus ?? report.status
|
|
27038
|
+
};
|
|
27039
|
+
await recordVoiceOpsActionAudit({
|
|
27040
|
+
actionId: `incident.${actionId}`,
|
|
27041
|
+
body: {
|
|
27042
|
+
action,
|
|
27043
|
+
afterStatus: resultWithStatus.afterStatus,
|
|
27044
|
+
beforeStatus: resultWithStatus.beforeStatus,
|
|
27045
|
+
eventIds: report.events.map((event) => event.id),
|
|
27046
|
+
result
|
|
27047
|
+
},
|
|
27048
|
+
error: result.ok ? undefined : result.detail ?? result.status,
|
|
27049
|
+
ok: result.ok,
|
|
27050
|
+
ranAt: Date.now(),
|
|
27051
|
+
status
|
|
27052
|
+
}, {
|
|
27053
|
+
audit: options.audit,
|
|
27054
|
+
trace: options.trace
|
|
27055
|
+
});
|
|
27056
|
+
return new Response(JSON.stringify(resultWithStatus), {
|
|
27057
|
+
headers: {
|
|
27058
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
27059
|
+
...options.headers
|
|
27060
|
+
},
|
|
27061
|
+
status
|
|
27062
|
+
});
|
|
27063
|
+
});
|
|
27064
|
+
}
|
|
27065
|
+
if (recoveryOutcomePath !== false) {
|
|
27066
|
+
routes.get(recoveryOutcomePath, async () => {
|
|
27067
|
+
const report = await buildVoiceIncidentRecoveryOutcomeReport({
|
|
27068
|
+
audit: options.audit
|
|
27069
|
+
});
|
|
27070
|
+
return new Response(JSON.stringify(report), {
|
|
27071
|
+
headers: {
|
|
27072
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
27073
|
+
...options.headers
|
|
27074
|
+
}
|
|
27075
|
+
});
|
|
27076
|
+
});
|
|
27077
|
+
}
|
|
27078
|
+
if (recoveryOutcomeHtmlPath !== false) {
|
|
27079
|
+
routes.get(recoveryOutcomeHtmlPath, async () => {
|
|
27080
|
+
const report = await buildVoiceIncidentRecoveryOutcomeReport({
|
|
27081
|
+
audit: options.audit
|
|
27082
|
+
});
|
|
27083
|
+
return new Response(renderVoiceIncidentRecoveryOutcomeHTML(report, {
|
|
27084
|
+
title: `${options.title ?? "AbsoluteJS Voice Incident Timeline"} Recovery Outcomes`
|
|
27085
|
+
}), {
|
|
27086
|
+
headers: {
|
|
27087
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
27088
|
+
...options.headers
|
|
27089
|
+
}
|
|
27090
|
+
});
|
|
27091
|
+
});
|
|
27092
|
+
}
|
|
27093
|
+
if (recoveryTrendPath !== false) {
|
|
27094
|
+
routes.get(recoveryTrendPath, async () => {
|
|
27095
|
+
const report = await buildRecoveryTrendReport();
|
|
27096
|
+
return new Response(JSON.stringify(report), {
|
|
27097
|
+
headers: {
|
|
27098
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
27099
|
+
...options.headers
|
|
27100
|
+
}
|
|
27101
|
+
});
|
|
27102
|
+
});
|
|
27103
|
+
}
|
|
27104
|
+
if (recoveryTrendHtmlPath !== false) {
|
|
27105
|
+
routes.get(recoveryTrendHtmlPath, async () => {
|
|
27106
|
+
const report = await buildRecoveryTrendReport();
|
|
27107
|
+
return new Response(renderVoiceIncidentRecoveryTrendHTML(report, {
|
|
27108
|
+
title: `${options.title ?? "AbsoluteJS Voice Incident Timeline"} Recovery Trend`
|
|
27109
|
+
}), {
|
|
27110
|
+
headers: {
|
|
27111
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
27112
|
+
...options.headers
|
|
27113
|
+
}
|
|
27114
|
+
});
|
|
27115
|
+
});
|
|
27116
|
+
}
|
|
27117
|
+
if (recoveryTrendMarkdownPath !== false) {
|
|
27118
|
+
routes.get(recoveryTrendMarkdownPath, async () => {
|
|
27119
|
+
const report = await buildRecoveryTrendReport();
|
|
27120
|
+
return new Response(renderVoiceIncidentRecoveryTrendMarkdown(report, {
|
|
27121
|
+
title: `${options.title ?? "AbsoluteJS Voice Incident Timeline"} Recovery Trend`
|
|
27122
|
+
}), {
|
|
27123
|
+
headers: {
|
|
27124
|
+
"Content-Type": "text/markdown; charset=utf-8",
|
|
27125
|
+
...options.headers
|
|
27126
|
+
}
|
|
27127
|
+
});
|
|
27128
|
+
});
|
|
27129
|
+
}
|
|
27130
|
+
return routes;
|
|
27131
|
+
};
|
|
27132
|
+
|
|
27133
|
+
// src/observabilityExport.ts
|
|
27134
|
+
import { Elysia as Elysia44 } from "elysia";
|
|
27135
|
+
import { Database as Database4 } from "bun:sqlite";
|
|
27136
|
+
import { createHash } from "crypto";
|
|
27137
|
+
import { mkdir as mkdir2, readFile, stat, unlink } from "fs/promises";
|
|
27138
|
+
import { join as join2 } from "path";
|
|
27139
|
+
var voiceObservabilityExportSchemaVersion = "1.0.0";
|
|
27140
|
+
var voiceObservabilityExportSchemaId = "com.absolutejs.voice.observability-export";
|
|
27141
|
+
var createVoiceObservabilityExportSchema = () => ({
|
|
27142
|
+
id: voiceObservabilityExportSchemaId,
|
|
27143
|
+
version: voiceObservabilityExportSchemaVersion
|
|
27144
|
+
});
|
|
27145
|
+
var assertVoiceObservabilityExportSchema = (input) => {
|
|
27146
|
+
if (input.schema?.id !== voiceObservabilityExportSchemaId || input.schema?.version !== voiceObservabilityExportSchemaVersion) {
|
|
27147
|
+
throw new Error(`Unsupported voice observability export schema: ${input.schema?.id ?? "missing"}@${input.schema?.version ?? "missing"}`);
|
|
27148
|
+
}
|
|
27149
|
+
};
|
|
27150
|
+
var isRecord4 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
27151
|
+
var isStatus2 = (value) => value === "fail" || value === "pass" || value === "warn";
|
|
27152
|
+
var getRecord2 = (value, key) => isRecord4(value) && isRecord4(value[key]) ? value[key] : undefined;
|
|
27153
|
+
var getRecordArray = (value, key) => isRecord4(value) && Array.isArray(value[key]) ? value[key] : undefined;
|
|
27154
|
+
var inferVoiceObservabilityExportRecordKind = (record) => {
|
|
27155
|
+
if (isRecord4(record.manifest) && isRecord4(record.artifactIndex)) {
|
|
27156
|
+
return "database-record";
|
|
27157
|
+
}
|
|
27158
|
+
if (Array.isArray(record.receipts)) {
|
|
27159
|
+
return "delivery-history";
|
|
27160
|
+
}
|
|
27161
|
+
if (typeof record.runId === "string" && Array.isArray(record.destinations)) {
|
|
27162
|
+
return "delivery-receipt";
|
|
27163
|
+
}
|
|
27164
|
+
if (Array.isArray(record.destinations) && isRecord4(record.summary) && typeof record.exportStatus === "string") {
|
|
27165
|
+
return "delivery-report";
|
|
27166
|
+
}
|
|
27167
|
+
if (Array.isArray(record.artifacts) && isRecord4(record.summary)) {
|
|
27168
|
+
return Array.isArray(record.envelopes) ? "manifest" : "artifact-index";
|
|
27169
|
+
}
|
|
27170
|
+
return;
|
|
27171
|
+
};
|
|
27172
|
+
var pushValidationIssue = (issues, issue) => {
|
|
27173
|
+
issues.push(issue);
|
|
27174
|
+
};
|
|
27175
|
+
var requireRecordSchema = (issues, record, path) => {
|
|
27176
|
+
const schema = getRecord2(record, "schema");
|
|
27177
|
+
if (schema?.id !== voiceObservabilityExportSchemaId || schema?.version !== voiceObservabilityExportSchemaVersion) {
|
|
27178
|
+
pushValidationIssue(issues, {
|
|
27179
|
+
code: "voice.observability.export.unsupported_schema",
|
|
27180
|
+
message: `Unsupported voice observability export schema: ${schema?.id ?? "missing"}@${schema?.version ?? "missing"}`,
|
|
27181
|
+
path: `${path}.schema`
|
|
27182
|
+
});
|
|
27183
|
+
}
|
|
27184
|
+
return schema;
|
|
27185
|
+
};
|
|
27186
|
+
var requireArrayField = (issues, record, key, path) => {
|
|
27187
|
+
if (!Array.isArray(record[key])) {
|
|
27188
|
+
pushValidationIssue(issues, {
|
|
27189
|
+
code: "voice.observability.export.missing_field",
|
|
27190
|
+
message: `${path}.${key} must be an array.`,
|
|
27191
|
+
path: `${path}.${key}`
|
|
27192
|
+
});
|
|
27193
|
+
}
|
|
27194
|
+
};
|
|
27195
|
+
var requireNumberField = (issues, record, key, path) => {
|
|
27196
|
+
if (typeof record[key] !== "number") {
|
|
27197
|
+
pushValidationIssue(issues, {
|
|
27198
|
+
code: "voice.observability.export.missing_field",
|
|
27199
|
+
message: `${path}.${key} must be a number.`,
|
|
27200
|
+
path: `${path}.${key}`
|
|
27201
|
+
});
|
|
27202
|
+
}
|
|
27203
|
+
};
|
|
27204
|
+
var requireStatusField = (issues, record, key, path) => {
|
|
27205
|
+
if (!isStatus2(record[key])) {
|
|
27206
|
+
pushValidationIssue(issues, {
|
|
27207
|
+
code: "voice.observability.export.missing_field",
|
|
27208
|
+
message: `${path}.${key} must be pass, warn, or fail.`,
|
|
27209
|
+
path: `${path}.${key}`
|
|
27210
|
+
});
|
|
27211
|
+
}
|
|
27212
|
+
};
|
|
27213
|
+
var requireDeliveryDestinationStatusField = (issues, record, key, path) => {
|
|
27214
|
+
if (record[key] !== "delivered" && record[key] !== "failed") {
|
|
27215
|
+
pushValidationIssue(issues, {
|
|
27216
|
+
code: "voice.observability.export.missing_field",
|
|
27217
|
+
message: `${path}.${key} must be delivered or failed.`,
|
|
27218
|
+
path: `${path}.${key}`
|
|
27219
|
+
});
|
|
27220
|
+
}
|
|
27221
|
+
};
|
|
27222
|
+
var validateDeliveryDestinations = (issues, destinations, path) => {
|
|
27223
|
+
if (!destinations) {
|
|
27224
|
+
pushValidationIssue(issues, {
|
|
27225
|
+
code: "voice.observability.export.missing_field",
|
|
27226
|
+
message: `${path} must be an array.`,
|
|
27227
|
+
path
|
|
27228
|
+
});
|
|
27229
|
+
return;
|
|
27230
|
+
}
|
|
27231
|
+
destinations.forEach((destination, index) => {
|
|
27232
|
+
const destinationPath = `${path}.${index}`;
|
|
27233
|
+
if (!isRecord4(destination)) {
|
|
27234
|
+
pushValidationIssue(issues, {
|
|
27235
|
+
code: "voice.observability.export.invalid_shape",
|
|
27236
|
+
message: `${destinationPath} must be an object.`,
|
|
27237
|
+
path: destinationPath
|
|
26542
27238
|
});
|
|
26543
27239
|
return;
|
|
26544
27240
|
}
|
|
@@ -26555,7 +27251,7 @@ var validateDeliveryDestinations = (issues, destinations, path) => {
|
|
|
26555
27251
|
};
|
|
26556
27252
|
var validateVoiceObservabilityExportRecord = (input, options = {}) => {
|
|
26557
27253
|
const issues = [];
|
|
26558
|
-
if (!
|
|
27254
|
+
if (!isRecord4(input)) {
|
|
26559
27255
|
return {
|
|
26560
27256
|
issues: [
|
|
26561
27257
|
{
|
|
@@ -26590,21 +27286,21 @@ var validateVoiceObservabilityExportRecord = (input, options = {}) => {
|
|
|
26590
27286
|
requireArrayField(issues, input, "sessionIds", "$");
|
|
26591
27287
|
requireNumberField(issues, input, "checkedAt", "$");
|
|
26592
27288
|
requireStatusField(issues, input, "status", "$");
|
|
26593
|
-
if (!
|
|
27289
|
+
if (!isRecord4(input.deliveries)) {
|
|
26594
27290
|
pushValidationIssue(issues, {
|
|
26595
27291
|
code: "voice.observability.export.missing_field",
|
|
26596
27292
|
message: "$.deliveries must be an object.",
|
|
26597
27293
|
path: "$.deliveries"
|
|
26598
27294
|
});
|
|
26599
27295
|
}
|
|
26600
|
-
if (!
|
|
27296
|
+
if (!isRecord4(input.redaction)) {
|
|
26601
27297
|
pushValidationIssue(issues, {
|
|
26602
27298
|
code: "voice.observability.export.missing_field",
|
|
26603
27299
|
message: "$.redaction must be an object.",
|
|
26604
27300
|
path: "$.redaction"
|
|
26605
27301
|
});
|
|
26606
27302
|
}
|
|
26607
|
-
if (!
|
|
27303
|
+
if (!isRecord4(input.summary)) {
|
|
26608
27304
|
pushValidationIssue(issues, {
|
|
26609
27305
|
code: "voice.observability.export.missing_field",
|
|
26610
27306
|
message: "$.summary must be an object.",
|
|
@@ -26616,7 +27312,7 @@ var validateVoiceObservabilityExportRecord = (input, options = {}) => {
|
|
|
26616
27312
|
requireArrayField(issues, input, "artifacts", "$");
|
|
26617
27313
|
requireNumberField(issues, input, "checkedAt", "$");
|
|
26618
27314
|
requireStatusField(issues, input, "status", "$");
|
|
26619
|
-
if (!
|
|
27315
|
+
if (!isRecord4(input.summary)) {
|
|
26620
27316
|
pushValidationIssue(issues, {
|
|
26621
27317
|
code: "voice.observability.export.missing_field",
|
|
26622
27318
|
message: "$.summary must be an object.",
|
|
@@ -26628,7 +27324,7 @@ var validateVoiceObservabilityExportRecord = (input, options = {}) => {
|
|
|
26628
27324
|
requireNumberField(issues, input, "checkedAt", "$");
|
|
26629
27325
|
requireStatusField(issues, input, "status", "$");
|
|
26630
27326
|
requireStatusField(issues, input, "exportStatus", "$");
|
|
26631
|
-
if (!
|
|
27327
|
+
if (!isRecord4(input.manifest)) {
|
|
26632
27328
|
pushValidationIssue(issues, {
|
|
26633
27329
|
code: "voice.observability.export.missing_field",
|
|
26634
27330
|
message: "$.manifest must be an object.",
|
|
@@ -26642,7 +27338,7 @@ var validateVoiceObservabilityExportRecord = (input, options = {}) => {
|
|
|
26642
27338
|
path: `$.manifest${issue.path.slice(1)}`
|
|
26643
27339
|
})));
|
|
26644
27340
|
}
|
|
26645
|
-
if (!
|
|
27341
|
+
if (!isRecord4(input.artifactIndex)) {
|
|
26646
27342
|
pushValidationIssue(issues, {
|
|
26647
27343
|
code: "voice.observability.export.missing_field",
|
|
26648
27344
|
message: "$.artifactIndex must be an object.",
|
|
@@ -26842,11 +27538,11 @@ var buildObservabilityExportDatabaseRecord = (input) => ({
|
|
|
26842
27538
|
});
|
|
26843
27539
|
var parseObservabilityExportJson = (value) => typeof value === "string" ? JSON.parse(value) : value;
|
|
26844
27540
|
var collectReplayDeliveryDestinations = (value) => {
|
|
26845
|
-
if (!
|
|
27541
|
+
if (!isRecord4(value)) {
|
|
26846
27542
|
return [];
|
|
26847
27543
|
}
|
|
26848
27544
|
if (Array.isArray(value.destinations)) {
|
|
26849
|
-
return value.destinations.filter((destination) =>
|
|
27545
|
+
return value.destinations.filter((destination) => isRecord4(destination));
|
|
26850
27546
|
}
|
|
26851
27547
|
if (Array.isArray(value.receipts)) {
|
|
26852
27548
|
return value.receipts.flatMap((receipt) => collectReplayDeliveryDestinations(receipt));
|
|
@@ -26855,8 +27551,8 @@ var collectReplayDeliveryDestinations = (value) => {
|
|
|
26855
27551
|
};
|
|
26856
27552
|
var replayIssueSeverity = (status) => status === "fail" ? "fail" : "warn";
|
|
26857
27553
|
var buildVoiceObservabilityExportReplayReport = (records) => {
|
|
26858
|
-
const manifest = records.manifest ?? (
|
|
26859
|
-
const artifactIndex = records.artifactIndex ?? (
|
|
27554
|
+
const manifest = records.manifest ?? (isRecord4(records.databaseRecord) ? records.databaseRecord.manifest : undefined);
|
|
27555
|
+
const artifactIndex = records.artifactIndex ?? (isRecord4(records.databaseRecord) ? records.databaseRecord.artifactIndex : undefined);
|
|
26860
27556
|
const validations = {
|
|
26861
27557
|
artifactIndex: validateVoiceObservabilityExportRecord(artifactIndex, {
|
|
26862
27558
|
kind: "artifact-index"
|
|
@@ -26881,12 +27577,12 @@ var buildVoiceObservabilityExportReplayReport = (records) => {
|
|
|
26881
27577
|
kind,
|
|
26882
27578
|
issue
|
|
26883
27579
|
})) ?? []);
|
|
26884
|
-
const manifestRecord =
|
|
26885
|
-
const artifactIndexRecord =
|
|
27580
|
+
const manifestRecord = isRecord4(manifest) ? manifest : undefined;
|
|
27581
|
+
const artifactIndexRecord = isRecord4(artifactIndex) ? artifactIndex : undefined;
|
|
26886
27582
|
const artifacts = [
|
|
26887
27583
|
...Array.isArray(manifestRecord?.artifacts) ? manifestRecord.artifacts : [],
|
|
26888
27584
|
...Array.isArray(artifactIndexRecord?.artifacts) ? artifactIndexRecord.artifacts : []
|
|
26889
|
-
].filter((artifact) =>
|
|
27585
|
+
].filter((artifact) => isRecord4(artifact));
|
|
26890
27586
|
const failedArtifacts = artifacts.filter((artifact) => artifact.status === "fail");
|
|
26891
27587
|
const deliveryDestinations = [
|
|
26892
27588
|
...collectReplayDeliveryDestinations(records.deliveryReport),
|
|
@@ -26895,7 +27591,7 @@ var buildVoiceObservabilityExportReplayReport = (records) => {
|
|
|
26895
27591
|
];
|
|
26896
27592
|
const failedDeliveryDestinations = deliveryDestinations.filter((destination) => destination.status === "failed");
|
|
26897
27593
|
const issues = [
|
|
26898
|
-
...!records.manifest && !
|
|
27594
|
+
...!records.manifest && !isRecord4(records.databaseRecord) ? [
|
|
26899
27595
|
{
|
|
26900
27596
|
code: "voice.observability.export_replay.missing_record",
|
|
26901
27597
|
label: "Export manifest",
|
|
@@ -26903,7 +27599,7 @@ var buildVoiceObservabilityExportReplayReport = (records) => {
|
|
|
26903
27599
|
value: "manifest"
|
|
26904
27600
|
}
|
|
26905
27601
|
] : [],
|
|
26906
|
-
...!records.artifactIndex && !
|
|
27602
|
+
...!records.artifactIndex && !isRecord4(records.databaseRecord) ? [
|
|
26907
27603
|
{
|
|
26908
27604
|
code: "voice.observability.export_replay.missing_record",
|
|
26909
27605
|
label: "Artifact index",
|
|
@@ -27081,7 +27777,7 @@ var loadVoiceObservabilityExportReplaySource = async (source) => {
|
|
|
27081
27777
|
};
|
|
27082
27778
|
var replayVoiceObservabilityExport = async (source) => buildVoiceObservabilityExportReplayReport(await loadVoiceObservabilityExportReplaySource(source));
|
|
27083
27779
|
var escapeObservabilityReplayHtml = (value) => String(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
27084
|
-
var isVoiceObservabilityExportReplayReport = (value) =>
|
|
27780
|
+
var isVoiceObservabilityExportReplayReport = (value) => isRecord4(value) && isRecord4(value.summary) && isRecord4(value.records) && Array.isArray(value.issues) && typeof value.checkedAt === "number" && isStatus2(value.status);
|
|
27085
27781
|
var resolveVoiceObservabilityExportReplayReport = async (input) => {
|
|
27086
27782
|
const resolved = typeof input === "function" ? await input() : input;
|
|
27087
27783
|
return isVoiceObservabilityExportReplayReport(resolved) ? resolved : replayVoiceObservabilityExport(resolved);
|
|
@@ -27100,7 +27796,7 @@ var createVoiceObservabilityExportReplayRoutes = (options) => {
|
|
|
27100
27796
|
...options.headers ?? {}
|
|
27101
27797
|
};
|
|
27102
27798
|
const buildReport = () => resolveVoiceObservabilityExportReplayReport(options.source);
|
|
27103
|
-
const app = new
|
|
27799
|
+
const app = new Elysia44({
|
|
27104
27800
|
name: options.name ?? "absolute-voice-observability-export-replay"
|
|
27105
27801
|
});
|
|
27106
27802
|
app.get(path, async () => Response.json(await buildReport(), { headers }));
|
|
@@ -27966,7 +28662,7 @@ var createVoiceObservabilityExportRoutes = (options = {}) => {
|
|
|
27966
28662
|
artifactDownload: options.links?.artifactDownload ?? (artifactDownloadPath ? (artifact) => `${artifactDownloadPath}/${encodeURIComponent(artifact.id)}` : undefined)
|
|
27967
28663
|
}
|
|
27968
28664
|
});
|
|
27969
|
-
const app = new
|
|
28665
|
+
const app = new Elysia44({
|
|
27970
28666
|
name: options.name ?? "absolute-voice-observability-export"
|
|
27971
28667
|
});
|
|
27972
28668
|
app.get(path, async () => Response.json(await buildReport(), { headers }));
|
|
@@ -28073,7 +28769,7 @@ var buildVoiceReadinessRecoveryActions = (input, options = {}) => {
|
|
|
28073
28769
|
sourceChecks: sourceChecks.length
|
|
28074
28770
|
};
|
|
28075
28771
|
};
|
|
28076
|
-
var
|
|
28772
|
+
var escapeHtml42 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
28077
28773
|
var formatVoiceProofFreshnessDuration = (valueMs) => {
|
|
28078
28774
|
if (valueMs < 1000) {
|
|
28079
28775
|
return `${Math.max(0, Math.round(valueMs))}ms`;
|
|
@@ -28628,6 +29324,15 @@ var resolveOpsRecovery = async (options, input) => {
|
|
|
28628
29324
|
}
|
|
28629
29325
|
return options.opsRecovery;
|
|
28630
29326
|
};
|
|
29327
|
+
var resolveIncidentRecoveryOutcomes = async (options, input) => {
|
|
29328
|
+
if (!options.incidentRecoveryOutcomes) {
|
|
29329
|
+
return;
|
|
29330
|
+
}
|
|
29331
|
+
if (typeof options.incidentRecoveryOutcomes === "function") {
|
|
29332
|
+
return options.incidentRecoveryOutcomes(input);
|
|
29333
|
+
}
|
|
29334
|
+
return options.incidentRecoveryOutcomes;
|
|
29335
|
+
};
|
|
28631
29336
|
var resolveObservabilityExport = async (options, input) => {
|
|
28632
29337
|
if (!options.observabilityExport) {
|
|
28633
29338
|
return;
|
|
@@ -28886,6 +29591,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
28886
29591
|
bargeInReports,
|
|
28887
29592
|
campaignReadiness,
|
|
28888
29593
|
opsRecovery,
|
|
29594
|
+
incidentRecoveryOutcomes,
|
|
28889
29595
|
observabilityExport,
|
|
28890
29596
|
observabilityExportDeliveryHistory,
|
|
28891
29597
|
observabilityExportReplay,
|
|
@@ -28933,6 +29639,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
28933
29639
|
time("bargeInReports", () => resolveBargeInReports(options, { query, request })),
|
|
28934
29640
|
time("campaignReadiness", () => resolveCampaignReadiness(options, { query, request })),
|
|
28935
29641
|
time("opsRecovery", () => resolveOpsRecovery(options, { query, request })),
|
|
29642
|
+
time("incidentRecoveryOutcomes", () => resolveIncidentRecoveryOutcomes(options, { query, request })),
|
|
28936
29643
|
time("observabilityExport", () => resolveObservabilityExport(options, { query, request })),
|
|
28937
29644
|
time("observabilityExportDeliveryHistory", () => resolveObservabilityExportDeliveryHistory(options, { query, request })),
|
|
28938
29645
|
time("observabilityExportReplay", () => resolveObservabilityExportReplay(options, { query, request })),
|
|
@@ -28953,6 +29660,10 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
28953
29660
|
mediaPipeline,
|
|
28954
29661
|
telephonyMedia
|
|
28955
29662
|
});
|
|
29663
|
+
const incidentRecoveryOutcomeReadiness = incidentRecoveryOutcomes && options.incidentRecoveryOutcomeReadiness !== false ? buildVoiceIncidentRecoveryOutcomeReadinessCheck(incidentRecoveryOutcomes, {
|
|
29664
|
+
href: options.incidentRecoveryOutcomeReadiness?.href ?? "/api/voice/incident-timeline/recovery-outcomes",
|
|
29665
|
+
...options.incidentRecoveryOutcomeReadiness
|
|
29666
|
+
}) : undefined;
|
|
28956
29667
|
const checks = [
|
|
28957
29668
|
{
|
|
28958
29669
|
detail: quality.status === "pass" ? "Quality gates are passing." : "Quality gates need attention.",
|
|
@@ -29024,6 +29735,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
29024
29735
|
]
|
|
29025
29736
|
}
|
|
29026
29737
|
] : [],
|
|
29738
|
+
...incidentRecoveryOutcomeReadiness ? [incidentRecoveryOutcomeReadiness] : [],
|
|
29027
29739
|
{
|
|
29028
29740
|
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
29741
|
href: firstOperationsRecordHref(operationsRecords.failedSessions) ?? options.links?.sessions ?? "/sessions",
|
|
@@ -29357,6 +30069,14 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
29357
30069
|
status: observabilityExport.status,
|
|
29358
30070
|
traceEvents: observabilityExport.summary.traceEvents
|
|
29359
30071
|
} : undefined;
|
|
30072
|
+
const incidentRecoveryOutcomeSummary = incidentRecoveryOutcomes && incidentRecoveryOutcomeReadiness ? {
|
|
30073
|
+
failed: incidentRecoveryOutcomes.failed,
|
|
30074
|
+
improved: incidentRecoveryOutcomes.improved,
|
|
30075
|
+
regressed: incidentRecoveryOutcomes.regressed,
|
|
30076
|
+
status: incidentRecoveryOutcomeReadiness.status,
|
|
30077
|
+
total: incidentRecoveryOutcomes.total,
|
|
30078
|
+
unchanged: incidentRecoveryOutcomes.unchanged
|
|
30079
|
+
} : undefined;
|
|
29360
30080
|
const observabilityExportDeliveryHistorySummary = observabilityExportDeliveryHistory ? (() => {
|
|
29361
30081
|
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
30082
|
const latestSuccessAgeMs = latestSuccess ? Date.now() - latestSuccess.checkedAt : undefined;
|
|
@@ -29896,6 +30616,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
29896
30616
|
total: handoffs.total
|
|
29897
30617
|
},
|
|
29898
30618
|
liveLatency,
|
|
30619
|
+
incidentRecoveryOutcomes: incidentRecoveryOutcomeSummary,
|
|
29899
30620
|
mediaPipeline: mediaPipelineSummary,
|
|
29900
30621
|
monitoring: monitoringSummary,
|
|
29901
30622
|
monitoringNotifierDelivery: monitoringNotifierDeliverySummary,
|
|
@@ -29950,25 +30671,25 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
29950
30671
|
var buildVoiceProductionReadinessGate = async (options, input = {}) => summarizeVoiceProductionReadinessGate(await buildVoiceProductionReadinessReport(options, input), options.gate || undefined);
|
|
29951
30672
|
var renderVoiceProductionReadinessHTML = (report, options = {}) => {
|
|
29952
30673
|
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>${
|
|
30674
|
+
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>` : "";
|
|
30675
|
+
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
30676
|
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 ${
|
|
30677
|
+
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("");
|
|
30678
|
+
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>` : "";
|
|
30679
|
+
return `<article class="check ${escapeHtml42(check.status)}">
|
|
29959
30680
|
<div>
|
|
29960
|
-
<span>${
|
|
29961
|
-
<h2>${
|
|
29962
|
-
${check.detail ? `<p>${
|
|
30681
|
+
<span>${escapeHtml42(check.status.toUpperCase())}</span>
|
|
30682
|
+
<h2>${escapeHtml42(check.label)}</h2>
|
|
30683
|
+
${check.detail ? `<p>${escapeHtml42(check.detail)}</p>` : ""}
|
|
29963
30684
|
${explanation}
|
|
29964
|
-
${check.proofSource ? `<p class="proof-source">Proof source: ${check.proofSource.href ? `<a href="${
|
|
30685
|
+
${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
30686
|
${actions ? `<p class="actions">${actions}</p>` : ""}
|
|
29966
30687
|
</div>
|
|
29967
|
-
<strong>${
|
|
29968
|
-
${check.href ? `<a href="${
|
|
30688
|
+
<strong>${escapeHtml42(String(check.value ?? check.status))}</strong>
|
|
30689
|
+
${check.href ? `<a href="${escapeHtml42(check.href)}">Open surface</a>` : ""}
|
|
29969
30690
|
</article>`;
|
|
29970
30691
|
}).join("");
|
|
29971
|
-
const snippet =
|
|
30692
|
+
const snippet = escapeHtml42(`createVoiceProductionReadinessRoutes({
|
|
29972
30693
|
htmlPath: '/production-readiness',
|
|
29973
30694
|
path: '/api/production-readiness',
|
|
29974
30695
|
gatePath: '/api/production-readiness/gate',
|
|
@@ -29984,13 +30705,13 @@ var renderVoiceProductionReadinessHTML = (report, options = {}) => {
|
|
|
29984
30705
|
providerRoutingContracts: loadProviderRoutingContracts,
|
|
29985
30706
|
store: traceStore
|
|
29986
30707
|
});`);
|
|
29987
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
30708
|
+
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
30709
|
};
|
|
29989
30710
|
var createVoiceProductionReadinessRoutes = (options) => {
|
|
29990
30711
|
const path = options.path ?? "/api/production-readiness";
|
|
29991
30712
|
const gatePath = options.gatePath === undefined ? "/api/production-readiness/gate" : options.gatePath;
|
|
29992
30713
|
const htmlPath = options.htmlPath ?? "/production-readiness";
|
|
29993
|
-
const routes = new
|
|
30714
|
+
const routes = new Elysia45({
|
|
29994
30715
|
name: options.name ?? "absolutejs-voice-production-readiness"
|
|
29995
30716
|
});
|
|
29996
30717
|
let cachedReport;
|
|
@@ -30010,544 +30731,144 @@ var createVoiceProductionReadinessRoutes = (options) => {
|
|
|
30010
30731
|
const getReport = async (query, request) => {
|
|
30011
30732
|
const cacheMs = typeof options.cacheMs === "number" && Number.isFinite(options.cacheMs) && options.cacheMs > 0 ? options.cacheMs : 0;
|
|
30012
30733
|
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
|
-
});
|
|
30734
|
+
if (cacheMs > 0 && cachedReport && cachedReport.key === key && Date.now() - cachedReport.loadedAt <= cacheMs) {
|
|
30735
|
+
return cachedReport.value;
|
|
30218
30736
|
}
|
|
30219
|
-
|
|
30220
|
-
|
|
30221
|
-
|
|
30222
|
-
|
|
30223
|
-
|
|
30224
|
-
|
|
30225
|
-
|
|
30226
|
-
|
|
30227
|
-
|
|
30228
|
-
|
|
30737
|
+
const value = (async () => {
|
|
30738
|
+
const resolvedOptions = await resolveOptions({ query, request });
|
|
30739
|
+
return {
|
|
30740
|
+
report: await buildVoiceProductionReadinessReport(resolvedOptions, {
|
|
30741
|
+
query,
|
|
30742
|
+
request
|
|
30743
|
+
}),
|
|
30744
|
+
resolvedOptions
|
|
30745
|
+
};
|
|
30746
|
+
})();
|
|
30747
|
+
if (cacheMs > 0) {
|
|
30748
|
+
cachedReport = {
|
|
30749
|
+
key,
|
|
30750
|
+
loadedAt: Date.now(),
|
|
30751
|
+
value
|
|
30752
|
+
};
|
|
30229
30753
|
}
|
|
30754
|
+
return value;
|
|
30755
|
+
};
|
|
30756
|
+
routes.get(path, async ({ query, request }) => (await getReport(query, request)).report);
|
|
30757
|
+
if (gatePath !== false) {
|
|
30758
|
+
routes.get(gatePath, async ({ query, request }) => {
|
|
30759
|
+
const { report, resolvedOptions } = await getReport(query, request);
|
|
30760
|
+
const gate = summarizeVoiceProductionReadinessGate(report, resolvedOptions.gate || undefined);
|
|
30761
|
+
return new Response(JSON.stringify(gate), {
|
|
30762
|
+
headers: {
|
|
30763
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
30764
|
+
...resolvedOptions.headers
|
|
30765
|
+
},
|
|
30766
|
+
status: gate.ok ? 200 : 503
|
|
30767
|
+
});
|
|
30768
|
+
});
|
|
30230
30769
|
}
|
|
30231
|
-
if (
|
|
30232
|
-
|
|
30233
|
-
|
|
30234
|
-
|
|
30235
|
-
|
|
30236
|
-
|
|
30237
|
-
|
|
30770
|
+
if (htmlPath !== false) {
|
|
30771
|
+
routes.get(htmlPath, async ({ query, request }) => {
|
|
30772
|
+
const { report, resolvedOptions } = await getReport(query, request);
|
|
30773
|
+
const body = await (resolvedOptions.render ?? renderVoiceProductionReadinessHTML)(report);
|
|
30774
|
+
return new Response(body, {
|
|
30775
|
+
headers: {
|
|
30776
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
30777
|
+
...resolvedOptions.headers
|
|
30778
|
+
}
|
|
30779
|
+
});
|
|
30238
30780
|
});
|
|
30239
30781
|
}
|
|
30240
|
-
return
|
|
30782
|
+
return routes;
|
|
30241
30783
|
};
|
|
30784
|
+
|
|
30785
|
+
// src/operationalStatus.ts
|
|
30786
|
+
var escapeHtml43 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
30787
|
+
var resolveValue2 = async (value) => typeof value === "function" ? await value() : value;
|
|
30788
|
+
var isDeliveryRuntime = (value) => Boolean(value && typeof value === "object" && "isRunning" in value && "summarize" in value);
|
|
30242
30789
|
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 : {};
|
|
30790
|
+
var proofPackStatusToCheck = (status, href) => {
|
|
30791
|
+
const checkStatus = status.state === "failed" || status.state === "missing" ? "fail" : status.state === "fresh" ? "pass" : "warn";
|
|
30792
|
+
const age = typeof status.ageMs === "number" ? `${Math.round(status.ageMs / 1000)}s old` : undefined;
|
|
30265
30793
|
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
|
|
30794
|
+
detail: status.error ?? `Proof pack is ${status.state}.`,
|
|
30795
|
+
href,
|
|
30796
|
+
label: "Proof pack freshness",
|
|
30797
|
+
status: checkStatus,
|
|
30798
|
+
value: age ?? status.state
|
|
30275
30799
|
};
|
|
30276
30800
|
};
|
|
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);
|
|
30801
|
+
var deliveryRuntimeStatusToCheck = (report, href) => {
|
|
30802
|
+
const summaries = [report.summary.audit, report.summary.trace].filter(Boolean);
|
|
30803
|
+
const failed = summaries.reduce((total, summary) => total + (summary?.failed ?? 0) + (summary?.deadLettered ?? 0), 0);
|
|
30804
|
+
const pending = summaries.reduce((total, summary) => total + (summary?.pending ?? 0), 0);
|
|
30805
|
+
const status = failed > 0 ? "fail" : pending > 0 || !report.isRunning ? "warn" : "pass";
|
|
30284
30806
|
return {
|
|
30285
|
-
|
|
30286
|
-
|
|
30287
|
-
|
|
30288
|
-
|
|
30289
|
-
|
|
30290
|
-
total: entries.length,
|
|
30291
|
-
unchanged: entries.filter((entry) => entry.outcome === "unchanged").length
|
|
30807
|
+
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.",
|
|
30808
|
+
href,
|
|
30809
|
+
label: "Delivery runtime",
|
|
30810
|
+
status,
|
|
30811
|
+
value: `${pending} pending / ${failed} failed`
|
|
30292
30812
|
};
|
|
30293
30813
|
};
|
|
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
|
-
}
|
|
30814
|
+
var productionReadinessStatusToCheck = (report, href) => {
|
|
30815
|
+
const gate = summarizeVoiceProductionReadinessGate(report);
|
|
30816
|
+
return {
|
|
30817
|
+
detail: gate.ok ? "Production readiness gate is open." : `${gate.failures.length} failures and ${gate.warnings.length} warnings.`,
|
|
30818
|
+
href,
|
|
30819
|
+
label: "Production readiness",
|
|
30820
|
+
status: gate.ok ? report.status : "fail",
|
|
30821
|
+
value: `${gate.failures.length} failures / ${gate.warnings.length} warnings`
|
|
30822
|
+
};
|
|
30390
30823
|
};
|
|
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
|
-
});
|
|
30824
|
+
var buildVoiceOperationalStatusReport = async (options) => {
|
|
30825
|
+
const [proofPack, deliveryRuntimeReport, productionReadiness] = await Promise.all([
|
|
30826
|
+
resolveValue2(options.proofPack),
|
|
30827
|
+
isDeliveryRuntime(options.deliveryRuntime) ? buildVoiceDeliveryRuntimeReport(options.deliveryRuntime) : resolveValue2(options.deliveryRuntime),
|
|
30828
|
+
resolveValue2(options.productionReadiness)
|
|
30829
|
+
]);
|
|
30830
|
+
const checks = [];
|
|
30831
|
+
if (proofPack) {
|
|
30832
|
+
checks.push(proofPackStatusToCheck(proofPack, options.links?.proofPack));
|
|
30417
30833
|
}
|
|
30418
|
-
|
|
30419
|
-
|
|
30420
|
-
if (!replays) {
|
|
30421
|
-
return;
|
|
30834
|
+
if (deliveryRuntimeReport) {
|
|
30835
|
+
checks.push(deliveryRuntimeStatusToCheck(deliveryRuntimeReport, options.links?.deliveryRuntime));
|
|
30422
30836
|
}
|
|
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
|
-
});
|
|
30837
|
+
if (productionReadiness) {
|
|
30838
|
+
checks.push(productionReadinessStatusToCheck(productionReadiness, options.links?.productionReadiness));
|
|
30444
30839
|
}
|
|
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
30840
|
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
|
|
30841
|
+
fail: checks.filter((check) => check.status === "fail").length,
|
|
30842
|
+
pass: checks.filter((check) => check.status === "pass").length,
|
|
30843
|
+
total: checks.length,
|
|
30844
|
+
warn: checks.filter((check) => check.status === "warn").length
|
|
30482
30845
|
};
|
|
30483
|
-
const configuredActions = typeof options.recoveryActions === "function" ? await options.recoveryActions({
|
|
30484
|
-
events: filtered,
|
|
30485
|
-
report: baseReport
|
|
30486
|
-
}) : options.recoveryActions;
|
|
30487
30846
|
return {
|
|
30488
|
-
|
|
30489
|
-
|
|
30847
|
+
checkedAt: Date.now(),
|
|
30848
|
+
checks,
|
|
30849
|
+
links: options.links ?? {},
|
|
30850
|
+
status: worstStatus3(checks.map((check) => check.status)),
|
|
30851
|
+
summary
|
|
30490
30852
|
};
|
|
30491
30853
|
};
|
|
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>
|
|
30854
|
+
var renderVoiceOperationalStatusHTML = (report, options = {}) => {
|
|
30855
|
+
const title = options.title ?? "AbsoluteJS Voice Operational Status";
|
|
30856
|
+
const checks = report.checks.map((check) => `<article class="${escapeHtml43(check.status)}">
|
|
30857
|
+
<span>${escapeHtml43(check.status.toUpperCase())}</span>
|
|
30858
|
+
<h2>${escapeHtml43(check.label)}</h2>
|
|
30859
|
+
<strong>${escapeHtml43(String(check.value ?? check.status))}</strong>
|
|
30860
|
+
${check.detail ? `<p>${escapeHtml43(check.detail)}</p>` : ""}
|
|
30861
|
+
${check.href ? `<a href="${escapeHtml43(check.href)}">Open surface</a>` : ""}
|
|
30530
30862
|
</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>`;
|
|
30863
|
+
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
30864
|
};
|
|
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;
|
|
30865
|
+
var createVoiceOperationalStatusRoutes = (options) => {
|
|
30866
|
+
const path = options.path ?? "/api/voice/operational-status";
|
|
30867
|
+
const htmlPath = options.htmlPath === undefined ? "/voice/operational-status" : options.htmlPath;
|
|
30547
30868
|
const routes = new Elysia46({
|
|
30548
|
-
name: options.name ?? "absolutejs-voice-
|
|
30869
|
+
name: options.name ?? "absolutejs-voice-operational-status"
|
|
30549
30870
|
}).get(path, async () => {
|
|
30550
|
-
const report = await
|
|
30871
|
+
const report = await buildVoiceOperationalStatusReport(options);
|
|
30551
30872
|
return new Response(JSON.stringify(report), {
|
|
30552
30873
|
headers: {
|
|
30553
30874
|
"Content-Type": "application/json; charset=utf-8",
|
|
@@ -30558,11 +30879,8 @@ var createVoiceIncidentTimelineRoutes = (options) => {
|
|
|
30558
30879
|
});
|
|
30559
30880
|
if (htmlPath !== false) {
|
|
30560
30881
|
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);
|
|
30882
|
+
const report = await buildVoiceOperationalStatusReport(options);
|
|
30883
|
+
const body = await (options.render ?? ((input) => renderVoiceOperationalStatusHTML(input, { title: options.title })))(report);
|
|
30566
30884
|
return new Response(body, {
|
|
30567
30885
|
headers: {
|
|
30568
30886
|
"Content-Type": "text/html; charset=utf-8",
|
|
@@ -30571,130 +30889,6 @@ var createVoiceIncidentTimelineRoutes = (options) => {
|
|
|
30571
30889
|
});
|
|
30572
30890
|
});
|
|
30573
30891
|
}
|
|
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
30892
|
return routes;
|
|
30699
30893
|
};
|
|
30700
30894
|
// src/dataControl.ts
|
|
@@ -31317,7 +31511,7 @@ import { Elysia as Elysia48 } from "elysia";
|
|
|
31317
31511
|
import { mkdir as mkdir3 } from "fs/promises";
|
|
31318
31512
|
import { dirname as dirname2 } from "path";
|
|
31319
31513
|
var escapeHtml45 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
31320
|
-
var
|
|
31514
|
+
var rate4 = (count, total) => count / Math.max(1, total);
|
|
31321
31515
|
var normalizeSearchText = (value) => value.trim().toLowerCase();
|
|
31322
31516
|
var getString18 = (value) => typeof value === "string" ? value : undefined;
|
|
31323
31517
|
var resolveSessionHref3 = (value, sessionId) => {
|
|
@@ -31565,7 +31759,7 @@ var summarizeEvalBaseline = (report) => {
|
|
|
31565
31759
|
return {
|
|
31566
31760
|
failed: report.failed,
|
|
31567
31761
|
failedSessionIds,
|
|
31568
|
-
passRate:
|
|
31762
|
+
passRate: rate4(report.passed, report.total),
|
|
31569
31763
|
passed: report.passed,
|
|
31570
31764
|
total: report.total
|
|
31571
31765
|
};
|
|
@@ -41561,6 +41755,8 @@ export {
|
|
|
41561
41755
|
renderVoiceLatencySLOMarkdown,
|
|
41562
41756
|
renderVoiceIncidentTimelineMarkdown,
|
|
41563
41757
|
renderVoiceIncidentTimelineHTML,
|
|
41758
|
+
renderVoiceIncidentRecoveryTrendMarkdown,
|
|
41759
|
+
renderVoiceIncidentRecoveryTrendHTML,
|
|
41564
41760
|
renderVoiceIncidentRecoveryOutcomeHTML,
|
|
41565
41761
|
renderVoiceHandoffHealthHTML,
|
|
41566
41762
|
renderVoiceGuardrailMarkdown,
|
|
@@ -42063,7 +42259,9 @@ export {
|
|
|
42063
42259
|
buildVoiceLiveOpsControlState,
|
|
42064
42260
|
buildVoiceLatencySLOGate,
|
|
42065
42261
|
buildVoiceIncidentTimelineReport,
|
|
42262
|
+
buildVoiceIncidentRecoveryTrendReport,
|
|
42066
42263
|
buildVoiceIncidentRecoveryOutcomeReport,
|
|
42264
|
+
buildVoiceIncidentRecoveryOutcomeReadinessCheck,
|
|
42067
42265
|
buildVoiceIncidentBundle,
|
|
42068
42266
|
buildVoiceIOProviderRouterTraceEvent,
|
|
42069
42267
|
buildVoiceGuardrailReport,
|