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