@absolutejs/voice 0.0.22-beta.289 → 0.0.22-beta.290
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/index.js +64 -1
- package/dist/productionReadiness.d.ts +10 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -28269,11 +28269,31 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
28269
28269
|
label: "Open calibrated gate source"
|
|
28270
28270
|
}
|
|
28271
28271
|
] : [];
|
|
28272
|
+
const calibratedGateExplanation = (input2) => ({
|
|
28273
|
+
...input2,
|
|
28274
|
+
sourceHref: options.links?.sloReadinessThresholds
|
|
28275
|
+
});
|
|
28276
|
+
const providerSloMetricForIssue = () => {
|
|
28277
|
+
const issue = providerSlo?.issues[0];
|
|
28278
|
+
if (!issue?.kind) {
|
|
28279
|
+
return;
|
|
28280
|
+
}
|
|
28281
|
+
const metrics = providerSlo?.kinds[issue.kind]?.metrics;
|
|
28282
|
+
return Object.values(metrics ?? {}).find((metric) => metric.label === issue.label || issue.code.endsWith(metric.label.toLowerCase().replace(/[^a-z0-9]+/g, "_")));
|
|
28283
|
+
};
|
|
28272
28284
|
checks.push({
|
|
28273
28285
|
detail: liveLatency.total === 0 ? "No browser live-latency measurements are recorded yet." : liveLatency.status === "pass" ? `Live browser turn latency averages ${liveLatency.averageLatencyMs}ms.` : `${liveLatency.failed} failed and ${liveLatency.warnings} warned live-latency measurement(s).`,
|
|
28274
28286
|
href: firstOperationsRecordHref(operationsRecords.failingLatency) ?? options.links?.liveLatency ?? "/traces",
|
|
28275
28287
|
label: "Live latency proof",
|
|
28276
28288
|
proofSource: proofSource("liveLatency", "liveLatencyProof"),
|
|
28289
|
+
gateExplanation: liveLatency.status === "pass" ? undefined : calibratedGateExplanation({
|
|
28290
|
+
evidenceHref: firstOperationsRecordHref(operationsRecords.failingLatency) ?? options.links?.liveLatency ?? "/traces",
|
|
28291
|
+
observed: liveLatency.averageLatencyMs,
|
|
28292
|
+
remediation: "Inspect the slow browser turn, reduce provider/turn latency, then rerun live latency proof so readiness uses fresh samples.",
|
|
28293
|
+
threshold: liveLatency.status === "fail" ? options.liveLatencyFailAfterMs ?? 3200 : options.liveLatencyWarnAfterMs ?? 1800,
|
|
28294
|
+
thresholdLabel: liveLatency.status === "fail" ? "Live latency fail after" : "Live latency warn after",
|
|
28295
|
+
unit: "ms"
|
|
28296
|
+
}),
|
|
28277
28297
|
status: liveLatency.status,
|
|
28278
28298
|
value: liveLatency.averageLatencyMs === undefined ? `${liveLatency.total} samples` : `${liveLatency.averageLatencyMs}ms avg`,
|
|
28279
28299
|
actions: liveLatency.status === "pass" ? [] : [
|
|
@@ -28436,11 +28456,20 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
28436
28456
|
}
|
|
28437
28457
|
if (providerSloSummary && providerSlo) {
|
|
28438
28458
|
const firstIssue = providerSlo.issues[0];
|
|
28459
|
+
const firstMetric = providerSloMetricForIssue();
|
|
28439
28460
|
checks.push({
|
|
28440
28461
|
detail: providerSloSummary.status === "pass" ? `${providerSloSummary.eventsWithLatency} provider latency sample(s) are inside LLM/STT/TTS SLO budgets.` : firstIssue?.detail ?? `${providerSloSummary.issues} provider SLO issue(s) need review.`,
|
|
28441
28462
|
href: firstIssue?.sessionId ? voiceOperationsRecordHref(options.links?.operationsRecords ?? "/voice-operations", firstIssue.sessionId) : options.links?.providerSlo ?? options.links?.resilience ?? "/voice/provider-slos",
|
|
28442
28463
|
label: "Provider SLO gates",
|
|
28443
28464
|
proofSource: proofSource("providerSlo", "providerSlos"),
|
|
28465
|
+
gateExplanation: providerSloSummary.status === "pass" ? undefined : calibratedGateExplanation({
|
|
28466
|
+
evidenceHref: firstIssue?.sessionId ? voiceOperationsRecordHref(options.links?.operationsRecords ?? "/voice-operations", firstIssue.sessionId) : options.links?.providerSlo ?? options.links?.resilience ?? "/voice/provider-slos",
|
|
28467
|
+
observed: firstMetric?.actual ?? firstIssue?.value,
|
|
28468
|
+
remediation: "Inspect the provider SLO report, fix slow or failing STT/LLM/TTS behavior, then rerun provider proof so the calibrated budget is met.",
|
|
28469
|
+
threshold: firstMetric?.threshold,
|
|
28470
|
+
thresholdLabel: firstMetric?.label ?? firstIssue?.label ?? "Provider SLO gate",
|
|
28471
|
+
unit: firstMetric?.unit
|
|
28472
|
+
}),
|
|
28444
28473
|
status: providerSloSummary.status,
|
|
28445
28474
|
value: `${providerSloSummary.eventsWithLatency}/${providerSloSummary.events}`,
|
|
28446
28475
|
actions: providerSloSummary.status === "pass" ? [] : [
|
|
@@ -28577,6 +28606,14 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
28577
28606
|
href: options.links?.reconnectContracts ?? options.links?.sessions ?? "/sessions",
|
|
28578
28607
|
label: "Reconnect recovery contracts",
|
|
28579
28608
|
proofSource: proofSource("reconnectContracts", "reconnect"),
|
|
28609
|
+
gateExplanation: reconnectContractSummary.status === "pass" ? undefined : calibratedGateExplanation({
|
|
28610
|
+
evidenceHref: options.links?.reconnectContracts ?? options.links?.sessions ?? "/sessions",
|
|
28611
|
+
observed: reconnectContractSummary.resumeLatencyP95Ms,
|
|
28612
|
+
remediation: "Inspect reconnect lifecycle traces, restore faster resume/replay-safe state, then rerun reconnect proof.",
|
|
28613
|
+
threshold: options.reconnectResumeFailAfterMs,
|
|
28614
|
+
thresholdLabel: "Reconnect resume p95 fail after",
|
|
28615
|
+
unit: "ms"
|
|
28616
|
+
}),
|
|
28580
28617
|
status: reconnectContractSummary.status,
|
|
28581
28618
|
value: `${reconnectContractSummary.passed}/${reconnectContractSummary.total}`,
|
|
28582
28619
|
actions: reconnectContractSummary.status === "pass" ? [] : [
|
|
@@ -28595,6 +28632,14 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
28595
28632
|
href: options.links?.bargeIn ?? "/barge-in",
|
|
28596
28633
|
label: "Barge-in interruption proof",
|
|
28597
28634
|
proofSource: proofSource("bargeInReports", "bargeIn"),
|
|
28635
|
+
gateExplanation: bargeInSummary.status === "pass" ? undefined : calibratedGateExplanation({
|
|
28636
|
+
evidenceHref: options.links?.bargeIn ?? "/barge-in",
|
|
28637
|
+
observed: bargeInReports?.map((report) => report.averageLatencyMs).filter((value) => typeof value === "number").sort((left, right) => right - left)[0] ?? `${bargeInSummary.failed} failed`,
|
|
28638
|
+
remediation: "Inspect barge-in proof, confirm playback cancellation is immediate, then rerun interruption proof against the calibrated threshold.",
|
|
28639
|
+
threshold: bargeInReports?.[0]?.thresholdMs,
|
|
28640
|
+
thresholdLabel: "Barge-in interruption threshold",
|
|
28641
|
+
unit: typeof bargeInReports?.[0]?.thresholdMs === "number" ? "ms" : "count"
|
|
28642
|
+
}),
|
|
28598
28643
|
status: bargeInSummary.status,
|
|
28599
28644
|
value: `${bargeInSummary.passed}/${bargeInSummary.total}`,
|
|
28600
28645
|
actions: bargeInSummary.status === "pass" ? [] : [
|
|
@@ -28747,6 +28792,14 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
28747
28792
|
detail: monitoringSummary.status === "pass" ? `${monitoringSummary.total} monitor(s) are passing with no open issues.` : options.monitoringRunFailAfterMs !== undefined && monitoringSummary.elapsedMs !== undefined && monitoringSummary.elapsedMs > options.monitoringRunFailAfterMs ? `Monitor run took ${monitoringSummary.elapsedMs}ms, above ${options.monitoringRunFailAfterMs}ms.` : `${monitoringSummary.open} monitor issue(s) open, ${monitoringSummary.criticalOpen} critical.`,
|
|
28748
28793
|
href: options.links?.monitoring ?? "/voice/monitors",
|
|
28749
28794
|
label: "Monitoring issues",
|
|
28795
|
+
gateExplanation: monitoringSummary.status === "pass" ? undefined : calibratedGateExplanation({
|
|
28796
|
+
evidenceHref: options.links?.monitoring ?? "/voice/monitors",
|
|
28797
|
+
observed: monitoringSummary.elapsedMs ?? `${monitoringSummary.open} open issue(s)`,
|
|
28798
|
+
remediation: "Inspect monitor issues or slow monitor execution, resolve open blockers, then rerun the monitor proof.",
|
|
28799
|
+
threshold: options.monitoringRunFailAfterMs,
|
|
28800
|
+
thresholdLabel: "Monitor run fail after",
|
|
28801
|
+
unit: monitoringSummary.elapsedMs !== undefined ? "ms" : "count"
|
|
28802
|
+
}),
|
|
28750
28803
|
status: monitoringSummary.status,
|
|
28751
28804
|
value: `${monitoring.summary.passed}/${monitoringSummary.total}`,
|
|
28752
28805
|
actions: monitoringSummary.status === "pass" ? [] : [
|
|
@@ -28764,6 +28817,14 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
28764
28817
|
detail: monitoringNotifierDeliverySummary.status === "pass" ? `${monitoringNotifierDeliverySummary.sent} monitor notification(s) delivered.` : options.monitoringNotifierDeliveryFailAfterMs !== undefined && monitoringNotifierDeliverySummary.elapsedMs !== undefined && monitoringNotifierDeliverySummary.elapsedMs > options.monitoringNotifierDeliveryFailAfterMs ? `Monitor notification delivery took ${monitoringNotifierDeliverySummary.elapsedMs}ms, above ${options.monitoringNotifierDeliveryFailAfterMs}ms.` : `${monitoringNotifierDeliverySummary.failed} monitor notification delivery failure(s).`,
|
|
28765
28818
|
href: options.links?.monitoringNotifierDelivery ?? "/api/voice/monitor-issues/notifications",
|
|
28766
28819
|
label: "Monitor notifier delivery",
|
|
28820
|
+
gateExplanation: monitoringNotifierDeliverySummary.status === "pass" ? undefined : calibratedGateExplanation({
|
|
28821
|
+
evidenceHref: options.links?.monitoringNotifierDelivery ?? "/api/voice/monitor-issues/notifications",
|
|
28822
|
+
observed: monitoringNotifierDeliverySummary.elapsedMs ?? `${monitoringNotifierDeliverySummary.failed} failed`,
|
|
28823
|
+
remediation: "Inspect monitor notification receipts, fix webhook/email/Slack delivery, then rerun notifier proof.",
|
|
28824
|
+
threshold: options.monitoringNotifierDeliveryFailAfterMs,
|
|
28825
|
+
thresholdLabel: "Monitor notifier delivery fail after",
|
|
28826
|
+
unit: monitoringNotifierDeliverySummary.elapsedMs !== undefined ? "ms" : "count"
|
|
28827
|
+
}),
|
|
28767
28828
|
status: monitoringNotifierDeliverySummary.status,
|
|
28768
28829
|
value: `${monitoringNotifierDeliverySummary.sent}/${monitoringNotifierDeliverySummary.total}`,
|
|
28769
28830
|
actions: monitoringNotifierDeliverySummary.status === "pass" ? [] : [
|
|
@@ -28873,11 +28934,13 @@ var renderVoiceProductionReadinessHTML = (report, options = {}) => {
|
|
|
28873
28934
|
const profile = report.profile ? `<section class="profile"><p class="eyebrow">Readiness profile</p><h2>${escapeHtml41(report.profile.name)}</h2><p>${escapeHtml41(report.profile.description)}</p><p>${escapeHtml41(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="${escapeHtml41(surface.href)}">${escapeHtml41(surface.label)}</a>` : escapeHtml41(surface.label)}</strong></article>`).join("")}</div></section>` : "";
|
|
28874
28935
|
const checks = report.checks.map((check, index) => {
|
|
28875
28936
|
const actions = (check.actions ?? []).map((action) => action.method === "POST" ? `<button type="button" data-readiness-action="${index}" data-action-url="${escapeHtml41(action.href)}">${escapeHtml41(action.label)}</button>` : `<a href="${escapeHtml41(action.href)}">${escapeHtml41(action.label)}</a>`).join("");
|
|
28937
|
+
const explanation = check.gateExplanation ? `<p class="gate-explanation">Why this gate is ${escapeHtml41(check.status)}: observed ${escapeHtml41(String(check.gateExplanation.observed ?? "n/a"))}${check.gateExplanation.unit ? ` ${escapeHtml41(check.gateExplanation.unit)}` : ""}; threshold ${escapeHtml41(String(check.gateExplanation.threshold ?? "n/a"))}${check.gateExplanation.unit ? ` ${escapeHtml41(check.gateExplanation.unit)}` : ""}. ${escapeHtml41(check.gateExplanation.remediation)} ${check.gateExplanation.sourceHref ? `<a href="${escapeHtml41(check.gateExplanation.sourceHref)}">Open threshold source</a>` : ""}</p>` : "";
|
|
28876
28938
|
return `<article class="check ${escapeHtml41(check.status)}">
|
|
28877
28939
|
<div>
|
|
28878
28940
|
<span>${escapeHtml41(check.status.toUpperCase())}</span>
|
|
28879
28941
|
<h2>${escapeHtml41(check.label)}</h2>
|
|
28880
28942
|
${check.detail ? `<p>${escapeHtml41(check.detail)}</p>` : ""}
|
|
28943
|
+
${explanation}
|
|
28881
28944
|
${check.proofSource ? `<p class="proof-source">Proof source: ${check.proofSource.href ? `<a href="${escapeHtml41(check.proofSource.href)}">${escapeHtml41(check.proofSource.sourceLabel)}</a>` : escapeHtml41(check.proofSource.sourceLabel)}${check.proofSource.detail ? ` \xB7 ${escapeHtml41(check.proofSource.detail)}` : ""}</p>` : ""}
|
|
28882
28945
|
${actions ? `<p class="actions">${actions}</p>` : ""}
|
|
28883
28946
|
</div>
|
|
@@ -28901,7 +28964,7 @@ var renderVoiceProductionReadinessHTML = (report, options = {}) => {
|
|
|
28901
28964
|
providerRoutingContracts: loadProviderRoutingContracts,
|
|
28902
28965
|
store: traceStore
|
|
28903
28966
|
});`);
|
|
28904
|
-
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:#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 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>${escapeHtml41(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 ${escapeHtml41(report.status)}">Overall: ${escapeHtml41(report.status.toUpperCase())}</p><p>Checked ${escapeHtml41(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>`;
|
|
28967
|
+
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:#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>${escapeHtml41(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 ${escapeHtml41(report.status)}">Overall: ${escapeHtml41(report.status.toUpperCase())}</p><p>Checked ${escapeHtml41(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>`;
|
|
28905
28968
|
};
|
|
28906
28969
|
var createVoiceProductionReadinessRoutes = (options) => {
|
|
28907
28970
|
const path = options.path ?? "/api/production-readiness";
|
|
@@ -32,9 +32,19 @@ export type VoiceProductionReadinessAction = {
|
|
|
32
32
|
label: string;
|
|
33
33
|
method?: 'GET' | 'POST';
|
|
34
34
|
};
|
|
35
|
+
export type VoiceProductionReadinessGateExplanation = {
|
|
36
|
+
evidenceHref?: string;
|
|
37
|
+
observed?: number | string;
|
|
38
|
+
remediation: string;
|
|
39
|
+
sourceHref?: string;
|
|
40
|
+
threshold?: number | string;
|
|
41
|
+
thresholdLabel?: string;
|
|
42
|
+
unit?: 'count' | 'ms' | 'rate' | 'status';
|
|
43
|
+
};
|
|
35
44
|
export type VoiceProductionReadinessCheck = {
|
|
36
45
|
actions?: VoiceProductionReadinessAction[];
|
|
37
46
|
detail?: string;
|
|
47
|
+
gateExplanation?: VoiceProductionReadinessGateExplanation;
|
|
38
48
|
href?: string;
|
|
39
49
|
label: string;
|
|
40
50
|
proofSource?: VoiceProductionReadinessProofSource;
|