@absolutejs/voice 0.0.22-beta.360 → 0.0.22-beta.362
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.d.ts +2 -2
- package/dist/index.js +289 -0
- package/dist/profileSwitchRecommendation.d.ts +192 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -31,8 +31,8 @@ export { assertVoiceCompetitiveCoverage, buildVoiceCompetitiveCoverageReport, cr
|
|
|
31
31
|
export type { VoiceCompetitiveCoverageAssertionInput, VoiceCompetitiveCoverageAssertionReport, VoiceCompetitiveCoverageIssue, VoiceCompetitiveCoverageLevel, VoiceCompetitiveCoverageReport, VoiceCompetitiveCoverageReportInput, VoiceCompetitiveCoverageRoutesOptions, VoiceCompetitiveCoverageStatus, VoiceCompetitiveCoverageSummary, VoiceCompetitiveDepthLevel, VoiceCompetitiveEvidence, VoiceCompetitiveSurface } from './competitiveCoverage';
|
|
32
32
|
export type { VoicePlatformCoverageAssertionInput, VoicePlatformCoverageAssertionReport, VoicePlatformCoverageEvidence, VoicePlatformCoverageRoutesOptions, VoicePlatformCoverageStatus, VoicePlatformCoverageSummary, VoicePlatformCoverageSummaryInput, VoicePlatformCoverageSurface } from './platformCoverage';
|
|
33
33
|
export { assertVoiceProofTrendEvidence, buildEmptyVoiceProofTrendReport, buildVoiceProofTrendProfileSummaries, buildVoiceProofTrendRecommendationReport, buildVoiceProofTrendReportFromRealCallProfiles, buildVoiceProofTrendReport, buildVoiceRealCallProfileDefaults, buildVoiceRealCallProfileHistoryReport, createVoiceProofTrendRecommendationRoutes, createVoiceProofTrendRoutes, createVoiceRealCallProfileHistoryRoutes, DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS, DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS, evaluateVoiceProofTrendEvidence, formatVoiceProofTrendAge, normalizeVoiceProofTrendReport, readVoiceProofTrendReportFile, renderVoiceProofTrendRecommendationHTML, renderVoiceProofTrendRecommendationMarkdown, renderVoiceRealCallProfileHistoryHTML, renderVoiceRealCallProfileHistoryMarkdown, resolveVoiceRealCallProfileProviderRoute } from './proofTrends';
|
|
34
|
-
export { applyVoiceProfileSwitchGuard, createVoiceProfileSwitchPolicyProofRoutes, recommendVoiceProfileSwitch, renderVoiceProfileSwitchPolicyProofHTML, runVoiceProfileSwitchPolicyProof } from './profileSwitchRecommendation';
|
|
35
|
-
export type { VoiceProfileSwitchGuardAction, VoiceProfileSwitchGuardDecision, VoiceProfileSwitchGuardMode, VoiceProfileSwitchGuardOptions, VoiceProfileSwitchObservedSignals, VoiceProfileSwitchPolicyProofCase, VoiceProfileSwitchPolicyProofCaseResult, VoiceProfileSwitchPolicyProofOptions, VoiceProfileSwitchPolicyProofReport, VoiceProfileSwitchPolicyProofRoutesOptions, VoiceProfileSwitchRecommendation, VoiceProfileSwitchRecommendationOptions } from './profileSwitchRecommendation';
|
|
34
|
+
export { applyVoiceProfileSwitchGuard, buildVoiceProfileSwitchReadinessReport, buildVoiceProfileSwitchLiveDecisionReport, createVoiceProfileSwitchLiveDecisionRoutes, createVoiceProfileSwitchPolicyProofRoutes, createVoiceProfileSwitchReadinessRoutes, recommendVoiceProfileSwitch, renderVoiceProfileSwitchLiveDecisionHTML, renderVoiceProfileSwitchPolicyProofHTML, renderVoiceProfileSwitchReadinessHTML, runVoiceProfileSwitchPolicyProof } from './profileSwitchRecommendation';
|
|
35
|
+
export type { VoiceProfileSwitchGuardAction, VoiceProfileSwitchGuardDecision, VoiceProfileSwitchGuardMode, VoiceProfileSwitchGuardOptions, VoiceProfileSwitchObservedSignals, VoiceProfileSwitchLiveDecisionEvidence, VoiceProfileSwitchLiveDecisionReport, VoiceProfileSwitchLiveDecisionReportOptions, VoiceProfileSwitchLiveDecisionRoutesOptions, VoiceProfileSwitchLiveDecisionSession, VoiceProfileSwitchPolicyProofCase, VoiceProfileSwitchPolicyProofCaseResult, VoiceProfileSwitchPolicyProofOptions, VoiceProfileSwitchPolicyProofReport, VoiceProfileSwitchPolicyProofRoutesOptions, VoiceProfileSwitchReadinessIssue, VoiceProfileSwitchReadinessOptions, VoiceProfileSwitchReadinessReport, VoiceProfileSwitchReadinessRoutesOptions, VoiceProfileSwitchReadinessStatus, VoiceProfileSwitchRecommendation, VoiceProfileSwitchRecommendationOptions } from './profileSwitchRecommendation';
|
|
36
36
|
export { buildVoiceProviderDecisionTraceReport, createVoiceProviderDecisionTraceEvent, createVoiceProviderDecisionTraceRoutes, listVoiceProviderDecisionTraces, renderVoiceProviderDecisionTraceHTML, renderVoiceProviderDecisionTraceMarkdown } from './providerDecisionTraces';
|
|
37
37
|
export type { VoiceProviderDecisionStatus, VoiceProviderDecisionSurfaceReport, VoiceProviderDecisionTrace, VoiceProviderDecisionTraceInput, VoiceProviderDecisionTraceIssue, VoiceProviderDecisionTraceReport, VoiceProviderDecisionTraceReportOptions, VoiceProviderDecisionTraceRoutesOptions } from './providerDecisionTraces';
|
|
38
38
|
export type { VoiceProofTrendAssertionInput, VoiceProofTrendAssertionReport, VoiceProofTrendCycle, VoiceProofTrendProfileDefinition, VoiceProofTrendProfileRecommendation, VoiceProofTrendProfileSummaryOptions, VoiceProofTrendProfileSummary, VoiceProofTrendProviderRecommendation, VoiceProofTrendProviderSummary, VoiceProofTrendRecommendation, VoiceProofTrendRecommendationOptions, VoiceProofTrendRecommendationReport, VoiceProofTrendRecommendationRoutesOptions, VoiceProofTrendRecommendationStatus, VoiceProofTrendRecommendationSurface, VoiceProofTrendRealCallProfileEvidence, VoiceProofTrendRealCallProfileReportOptions, VoiceProofTrendReport, VoiceProofTrendReportInput, VoiceProofTrendRoutesOptions, VoiceProofTrendRuntimeChannelSummary, VoiceProofTrendStatus, VoiceProofTrendSummary, VoiceRealCallProfileDefault, VoiceRealCallProfileDefaultsOptions, VoiceRealCallProfileDefaultsReport, VoiceRealCallProfileHistoryOptions, VoiceRealCallProfileHistoryReport, VoiceRealCallProfileHistoryRoutesOptions, VoiceRealCallProfileProviderRouteOptions } from './proofTrends';
|
package/dist/index.js
CHANGED
|
@@ -5681,6 +5681,289 @@ var createVoiceProfileSwitchPolicyProofRoutes = (options) => {
|
|
|
5681
5681
|
}
|
|
5682
5682
|
return routes;
|
|
5683
5683
|
};
|
|
5684
|
+
var readStringField = (record, key) => typeof record?.[key] === "string" ? record[key] : undefined;
|
|
5685
|
+
var readNumberField = (record, key) => typeof record?.[key] === "number" && Number.isFinite(record[key]) ? record[key] : undefined;
|
|
5686
|
+
var readBooleanField = (record, key) => typeof record?.[key] === "boolean" ? record[key] : undefined;
|
|
5687
|
+
var auditEventToLiveDecision = (event) => {
|
|
5688
|
+
if (event.type !== "profile.switch" || !event.sessionId) {
|
|
5689
|
+
return;
|
|
5690
|
+
}
|
|
5691
|
+
const payload = event.payload;
|
|
5692
|
+
return {
|
|
5693
|
+
action: readStringField(payload, "action") ?? event.action.replace(/^profile\.switch\./, ""),
|
|
5694
|
+
at: event.at,
|
|
5695
|
+
auditEventId: event.id,
|
|
5696
|
+
autoApplied: readBooleanField(payload, "autoApplied"),
|
|
5697
|
+
autoSwitchCount: readNumberField(payload, "autoSwitchCount"),
|
|
5698
|
+
blockedByPolicy: readStringField(payload, "blockedByPolicy"),
|
|
5699
|
+
confidence: readNumberField(payload, "confidence"),
|
|
5700
|
+
maxAutoSwitchesPerSession: readNumberField(payload, "maxAutoSwitchesPerSession"),
|
|
5701
|
+
minConfidence: readNumberField(payload, "minConfidence"),
|
|
5702
|
+
mode: readStringField(payload, "mode"),
|
|
5703
|
+
outcome: event.outcome,
|
|
5704
|
+
previousProfileId: readStringField(payload, "previousProfileId"),
|
|
5705
|
+
recommendedProfileId: readStringField(payload, "recommendedProfileId"),
|
|
5706
|
+
selectedProfileId: readStringField(payload, "selectedProfileId") ?? event.resource?.id,
|
|
5707
|
+
sessionId: event.sessionId,
|
|
5708
|
+
source: "audit",
|
|
5709
|
+
status: readStringField(payload, "status"),
|
|
5710
|
+
traceId: event.traceId
|
|
5711
|
+
};
|
|
5712
|
+
};
|
|
5713
|
+
var traceEventToLiveDecision = (event) => {
|
|
5714
|
+
if (event.type !== "provider.decision" || event.metadata?.source !== "profile-switch-guard") {
|
|
5715
|
+
return;
|
|
5716
|
+
}
|
|
5717
|
+
const payload = event.payload;
|
|
5718
|
+
return {
|
|
5719
|
+
action: readStringField(payload, "action"),
|
|
5720
|
+
at: event.at,
|
|
5721
|
+
autoApplied: readBooleanField(payload, "autoApplied"),
|
|
5722
|
+
autoSwitchCount: readNumberField(payload, "autoSwitchCount"),
|
|
5723
|
+
blockedByPolicy: readStringField(payload, "blockedByPolicy"),
|
|
5724
|
+
confidence: readNumberField(payload, "confidence"),
|
|
5725
|
+
maxAutoSwitchesPerSession: readNumberField(payload, "maxAutoSwitchesPerSession"),
|
|
5726
|
+
minConfidence: readNumberField(payload, "minConfidence"),
|
|
5727
|
+
mode: readStringField(payload, "mode"),
|
|
5728
|
+
previousProfileId: readStringField(payload, "previousProfileId"),
|
|
5729
|
+
reason: readStringField(payload, "reason"),
|
|
5730
|
+
recommendedProfileId: readStringField(payload, "recommendedProfileId"),
|
|
5731
|
+
scenarioId: event.scenarioId,
|
|
5732
|
+
selectedProfileId: readStringField(payload, "selectedProfileId"),
|
|
5733
|
+
sessionId: event.sessionId,
|
|
5734
|
+
source: "trace",
|
|
5735
|
+
status: readStringField(payload, "status"),
|
|
5736
|
+
traceEventId: event.id,
|
|
5737
|
+
traceId: event.traceId
|
|
5738
|
+
};
|
|
5739
|
+
};
|
|
5740
|
+
var buildVoiceProfileSwitchLiveDecisionReport = async (options) => {
|
|
5741
|
+
const [auditEvents, traceEvents] = await Promise.all([
|
|
5742
|
+
options.audit?.list({
|
|
5743
|
+
limit: options.limit,
|
|
5744
|
+
sessionId: options.sessionId,
|
|
5745
|
+
type: "profile.switch"
|
|
5746
|
+
}) ?? [],
|
|
5747
|
+
options.trace?.list({
|
|
5748
|
+
limit: options.limit,
|
|
5749
|
+
sessionId: options.sessionId,
|
|
5750
|
+
type: "provider.decision"
|
|
5751
|
+
}) ?? []
|
|
5752
|
+
]);
|
|
5753
|
+
const decisions = [
|
|
5754
|
+
...auditEvents.map(auditEventToLiveDecision).filter((decision) => Boolean(decision)),
|
|
5755
|
+
...traceEvents.map(traceEventToLiveDecision).filter((decision) => Boolean(decision))
|
|
5756
|
+
].sort((left, right) => right.at - left.at).slice(0, options.limit);
|
|
5757
|
+
const sessions = Array.from(decisions.reduce((map, decision) => {
|
|
5758
|
+
const existing = map.get(decision.sessionId) ?? {
|
|
5759
|
+
actionCounts: {},
|
|
5760
|
+
actions: [],
|
|
5761
|
+
autoApplied: 0,
|
|
5762
|
+
blockedByPolicy: [],
|
|
5763
|
+
decisions: [],
|
|
5764
|
+
firstSeenAt: decision.at,
|
|
5765
|
+
lastSeenAt: decision.at,
|
|
5766
|
+
sessionId: decision.sessionId
|
|
5767
|
+
};
|
|
5768
|
+
existing.decisions.push(decision);
|
|
5769
|
+
existing.firstSeenAt = Math.min(existing.firstSeenAt, decision.at);
|
|
5770
|
+
existing.lastSeenAt = Math.max(existing.lastSeenAt, decision.at);
|
|
5771
|
+
if (decision.action) {
|
|
5772
|
+
existing.actionCounts[decision.action] = (existing.actionCounts[decision.action] ?? 0) + 1;
|
|
5773
|
+
}
|
|
5774
|
+
if (decision.autoApplied) {
|
|
5775
|
+
existing.autoApplied += 1;
|
|
5776
|
+
}
|
|
5777
|
+
if (decision.blockedByPolicy && !existing.blockedByPolicy.includes(decision.blockedByPolicy)) {
|
|
5778
|
+
existing.blockedByPolicy.push(decision.blockedByPolicy);
|
|
5779
|
+
}
|
|
5780
|
+
map.set(decision.sessionId, existing);
|
|
5781
|
+
return map;
|
|
5782
|
+
}, new Map)).map(([, session]) => ({
|
|
5783
|
+
...session,
|
|
5784
|
+
actions: Object.keys(session.actionCounts).sort(),
|
|
5785
|
+
decisions: session.decisions.sort((left, right) => right.at - left.at),
|
|
5786
|
+
lastDecision: session.decisions.sort((left, right) => right.at - left.at)[0]
|
|
5787
|
+
}));
|
|
5788
|
+
const actionCount = (action) => decisions.filter((decision) => decision.action === action).length;
|
|
5789
|
+
return {
|
|
5790
|
+
generatedAt: new Date().toISOString(),
|
|
5791
|
+
ok: decisions.length > 0,
|
|
5792
|
+
sessions: sessions.sort((left, right) => right.lastSeenAt - left.lastSeenAt),
|
|
5793
|
+
summary: {
|
|
5794
|
+
auditEvents: auditEvents.length,
|
|
5795
|
+
autoApplied: decisions.filter((decision) => decision.autoApplied).length,
|
|
5796
|
+
blocked: actionCount("blocked"),
|
|
5797
|
+
decisions: decisions.length,
|
|
5798
|
+
recommendations: actionCount("recommend"),
|
|
5799
|
+
sessions: sessions.length,
|
|
5800
|
+
switches: actionCount("switch"),
|
|
5801
|
+
traceEvents: traceEvents.length
|
|
5802
|
+
}
|
|
5803
|
+
};
|
|
5804
|
+
};
|
|
5805
|
+
var renderVoiceProfileSwitchLiveDecisionHTML = (report, options = {}) => {
|
|
5806
|
+
const title = options.title ?? "Voice Profile Switch Live Decisions";
|
|
5807
|
+
const rows = report.sessions.flatMap((session) => session.decisions.map((decision) => `<tr>
|
|
5808
|
+
<td><strong>${escapeHtml3(decision.sessionId)}</strong><p>${escapeHtml3(new Date(decision.at).toISOString())}</p></td>
|
|
5809
|
+
<td>${escapeHtml3(decision.source)}</td>
|
|
5810
|
+
<td>${escapeHtml3(decision.action ?? "unknown")}</td>
|
|
5811
|
+
<td>${escapeHtml3(decision.selectedProfileId ?? "none")}</td>
|
|
5812
|
+
<td>${escapeHtml3(decision.blockedByPolicy ?? "none")}</td>
|
|
5813
|
+
<td>${typeof decision.confidence === "number" ? `${Math.round(decision.confidence * 100)}%` : "n/a"}</td>
|
|
5814
|
+
<td>${escapeHtml3(decision.reason ?? decision.outcome ?? "recorded")}</td>
|
|
5815
|
+
</tr>`)).join("");
|
|
5816
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml3(title)}</title><style>:root{color-scheme:dark;background:#11100b;color:#fff7ed;font-family:ui-sans-serif,system-ui,sans-serif}body{margin:0;padding:32px;background:radial-gradient(circle at top right,rgba(251,146,60,.18),transparent 34%),#11100b}main{max-width:1180px;margin:0 auto}.hero,.card{border:1px solid rgba(251,191,36,.24);border-radius:24px;background:rgba(28,25,23,.78);box-shadow:0 24px 90px rgba(0,0,0,.24);padding:24px;margin-bottom:18px}.metric-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px}.metric{border:1px solid rgba(251,191,36,.22);border-radius:18px;padding:16px;background:rgba(120,53,15,.26)}.metric span{display:block;color:#fed7aa;font-size:.75rem;text-transform:uppercase;letter-spacing:.08em}.metric strong{display:block;margin-top:8px;font-size:1.7rem}table{width:100%;border-collapse:collapse}th,td{padding:13px;text-align:left;border-bottom:1px solid rgba(251,191,36,.18);vertical-align:top}th{color:#fde68a;text-transform:uppercase;font-size:.74rem;letter-spacing:.08em}td p{margin:.35rem 0 0;color:#fdba74}pre{white-space:pre-wrap;overflow:auto;border-radius:18px;background:#020617;padding:18px;color:#ffedd5}.empty{color:#fdba74}</style></head><body><main><section class="hero"><h1>${escapeHtml3(title)}</h1><p>This page summarizes real profile switch guard evidence from audit and trace stores. Use it beside policy proof to show bounded policy plus actual session decisions.</p><div class="metric-grid"><div class="metric"><span>Sessions</span><strong>${String(report.summary.sessions)}</strong></div><div class="metric"><span>Decisions</span><strong>${String(report.summary.decisions)}</strong></div><div class="metric"><span>Switches</span><strong>${String(report.summary.switches)}</strong></div><div class="metric"><span>Blocked</span><strong>${String(report.summary.blocked)}</strong></div><div class="metric"><span>Auto applied</span><strong>${String(report.summary.autoApplied)}</strong></div></div></section><section class="card"><h2>Live Guard Decisions</h2>${rows ? `<table><thead><tr><th>Session</th><th>Source</th><th>Action</th><th>Selected</th><th>Blocked by</th><th>Confidence</th><th>Reason</th></tr></thead><tbody>${rows}</tbody></table>` : '<p class="empty">No profile switch guard decisions recorded yet. Start a voice session with profileSwitchGuard enabled.</p>'}</section><section class="card"><h2>Raw Report</h2><pre>${stringifyForHtml(report)}</pre></section></main></body></html>`;
|
|
5817
|
+
};
|
|
5818
|
+
var createVoiceProfileSwitchLiveDecisionRoutes = (options) => {
|
|
5819
|
+
const path = options.path ?? "/api/voice/profile-switch-live-decisions";
|
|
5820
|
+
const htmlPath = options.htmlPath === undefined ? "/voice/profile-switch-live-decisions" : options.htmlPath;
|
|
5821
|
+
const routes = new Elysia({
|
|
5822
|
+
name: options.name ?? "absolutejs-voice-profile-switch-live-decisions"
|
|
5823
|
+
}).get(path, async ({ query }) => buildVoiceProfileSwitchLiveDecisionReport({
|
|
5824
|
+
audit: options.audit,
|
|
5825
|
+
limit: typeof query.limit === "string" && Number.isFinite(Number(query.limit)) ? Number(query.limit) : options.limit,
|
|
5826
|
+
sessionId: typeof query.sessionId === "string" && query.sessionId.trim() ? query.sessionId.trim() : options.sessionId,
|
|
5827
|
+
trace: options.trace
|
|
5828
|
+
}));
|
|
5829
|
+
if (htmlPath) {
|
|
5830
|
+
routes.get(htmlPath, async ({ query }) => {
|
|
5831
|
+
const report = await buildVoiceProfileSwitchLiveDecisionReport({
|
|
5832
|
+
audit: options.audit,
|
|
5833
|
+
limit: typeof query.limit === "string" && Number.isFinite(Number(query.limit)) ? Number(query.limit) : options.limit,
|
|
5834
|
+
sessionId: typeof query.sessionId === "string" && query.sessionId.trim() ? query.sessionId.trim() : options.sessionId,
|
|
5835
|
+
trace: options.trace
|
|
5836
|
+
});
|
|
5837
|
+
const render = options.render ?? ((input) => renderVoiceProfileSwitchLiveDecisionHTML(input, {
|
|
5838
|
+
title: options.title
|
|
5839
|
+
}));
|
|
5840
|
+
return new Response(await render(report), {
|
|
5841
|
+
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
5842
|
+
});
|
|
5843
|
+
});
|
|
5844
|
+
}
|
|
5845
|
+
return routes;
|
|
5846
|
+
};
|
|
5847
|
+
var statusFromIssues = (issues) => issues.some((issue) => issue.status === "fail") ? "fail" : issues.length > 0 ? "warn" : "pass";
|
|
5848
|
+
var buildVoiceProfileSwitchReadinessReport = async (options) => {
|
|
5849
|
+
const live = await buildVoiceProfileSwitchLiveDecisionReport(options);
|
|
5850
|
+
const policy = options.policyProof === false || options.policyProof === undefined ? undefined : await runVoiceProfileSwitchPolicyProof(options.policyProof);
|
|
5851
|
+
const issues = [];
|
|
5852
|
+
const requireLiveDecisions = options.requireLiveDecisions ?? true;
|
|
5853
|
+
const requireAudit = options.requireAudit ?? Boolean(options.autoMode);
|
|
5854
|
+
const requireTrace = options.requireTrace ?? true;
|
|
5855
|
+
const requirePolicyProof = options.requirePolicyProof ?? Boolean(options.policyProof);
|
|
5856
|
+
const requireAllowedProfilePolicy = options.requireAllowedProfilePolicy ?? Boolean(options.autoMode);
|
|
5857
|
+
const maxBlockedRatio = options.maxBlockedRatio ?? 0.5;
|
|
5858
|
+
const maxAutoAppliedRatio = options.maxAutoAppliedRatio ?? 0.75;
|
|
5859
|
+
const decisions = live.summary.decisions;
|
|
5860
|
+
const blockedRatio = decisions > 0 ? live.summary.blocked / decisions : 0;
|
|
5861
|
+
const autoAppliedRatio = decisions > 0 ? live.summary.autoApplied / decisions : 0;
|
|
5862
|
+
if (requireLiveDecisions && decisions === 0) {
|
|
5863
|
+
issues.push({
|
|
5864
|
+
code: "voice.profile_switch.live_decisions_missing",
|
|
5865
|
+
message: "No live profile switch guard decisions have been recorded.",
|
|
5866
|
+
status: "fail"
|
|
5867
|
+
});
|
|
5868
|
+
}
|
|
5869
|
+
if (requireAudit && live.summary.auditEvents === 0) {
|
|
5870
|
+
issues.push({
|
|
5871
|
+
code: "voice.profile_switch.audit_missing",
|
|
5872
|
+
message: "Profile switch guard readiness requires audit evidence.",
|
|
5873
|
+
status: "fail"
|
|
5874
|
+
});
|
|
5875
|
+
}
|
|
5876
|
+
if (requireTrace && live.summary.traceEvents === 0) {
|
|
5877
|
+
issues.push({
|
|
5878
|
+
code: "voice.profile_switch.trace_missing",
|
|
5879
|
+
message: "Profile switch guard readiness requires trace evidence.",
|
|
5880
|
+
status: "warn"
|
|
5881
|
+
});
|
|
5882
|
+
}
|
|
5883
|
+
if (requirePolicyProof && !policy) {
|
|
5884
|
+
issues.push({
|
|
5885
|
+
code: "voice.profile_switch.policy_proof_missing",
|
|
5886
|
+
message: "Profile switch policy proof was not configured.",
|
|
5887
|
+
status: "fail"
|
|
5888
|
+
});
|
|
5889
|
+
}
|
|
5890
|
+
if (policy && !policy.ok) {
|
|
5891
|
+
issues.push({
|
|
5892
|
+
code: "voice.profile_switch.policy_proof_failed",
|
|
5893
|
+
message: "Profile switch policy proof has failing cases.",
|
|
5894
|
+
status: "fail"
|
|
5895
|
+
});
|
|
5896
|
+
}
|
|
5897
|
+
if (requireAllowedProfilePolicy && (options.policyProof === false || !options.policyProof?.allowedProfileIds || options.policyProof.allowedProfileIds.length === 0)) {
|
|
5898
|
+
issues.push({
|
|
5899
|
+
code: "voice.profile_switch.allowed_policy_missing",
|
|
5900
|
+
message: "Auto profile switching should declare an allowedProfileIds policy.",
|
|
5901
|
+
status: "warn"
|
|
5902
|
+
});
|
|
5903
|
+
}
|
|
5904
|
+
if (decisions > 0 && blockedRatio > maxBlockedRatio) {
|
|
5905
|
+
issues.push({
|
|
5906
|
+
code: "voice.profile_switch.blocked_ratio_high",
|
|
5907
|
+
message: `Blocked profile switch decisions are ${Math.round(blockedRatio * 100)}%, above the ${Math.round(maxBlockedRatio * 100)}% threshold.`,
|
|
5908
|
+
status: "warn"
|
|
5909
|
+
});
|
|
5910
|
+
}
|
|
5911
|
+
if (decisions > 0 && autoAppliedRatio > maxAutoAppliedRatio) {
|
|
5912
|
+
issues.push({
|
|
5913
|
+
code: "voice.profile_switch.auto_applied_ratio_high",
|
|
5914
|
+
message: `Auto-applied profile switch decisions are ${Math.round(autoAppliedRatio * 100)}%, above the ${Math.round(maxAutoAppliedRatio * 100)}% threshold.`,
|
|
5915
|
+
status: "warn"
|
|
5916
|
+
});
|
|
5917
|
+
}
|
|
5918
|
+
return {
|
|
5919
|
+
generatedAt: new Date().toISOString(),
|
|
5920
|
+
issues,
|
|
5921
|
+
live,
|
|
5922
|
+
policy,
|
|
5923
|
+
status: statusFromIssues(issues),
|
|
5924
|
+
summary: {
|
|
5925
|
+
auditEvents: live.summary.auditEvents,
|
|
5926
|
+
autoApplied: live.summary.autoApplied,
|
|
5927
|
+
blocked: live.summary.blocked,
|
|
5928
|
+
decisions,
|
|
5929
|
+
policyCases: policy?.summary.total,
|
|
5930
|
+
sessions: live.summary.sessions,
|
|
5931
|
+
switches: live.summary.switches,
|
|
5932
|
+
traceEvents: live.summary.traceEvents
|
|
5933
|
+
}
|
|
5934
|
+
};
|
|
5935
|
+
};
|
|
5936
|
+
var renderVoiceProfileSwitchReadinessHTML = (report, options = {}) => {
|
|
5937
|
+
const title = options.title ?? "Voice Profile Switch Readiness";
|
|
5938
|
+
const issues = report.issues.map((issue) => `<li class="${escapeHtml3(issue.status)}"><strong>${escapeHtml3(issue.code)}</strong><p>${escapeHtml3(issue.message)}</p></li>`).join("");
|
|
5939
|
+
const sessions = report.live.sessions.map((session) => `<tr><td>${escapeHtml3(session.sessionId)}</td><td>${escapeHtml3(session.actions.join(", ") || "none")}</td><td>${String(session.decisions.length)}</td><td>${String(session.autoApplied)}</td><td>${escapeHtml3(session.blockedByPolicy.join(", ") || "none")}</td></tr>`).join("");
|
|
5940
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml3(title)}</title><style>:root{color-scheme:dark;background:#0b1020;color:#eff6ff;font-family:ui-sans-serif,system-ui,sans-serif}body{margin:0;padding:32px;background:radial-gradient(circle at top left,rgba(59,130,246,.2),transparent 34%),#0b1020}main{max-width:1140px;margin:0 auto}.hero,.card{background:rgba(15,23,42,.82);border:1px solid rgba(147,197,253,.24);border-radius:24px;margin-bottom:18px;padding:24px}.status{border-radius:999px;display:inline-flex;font-weight:900;padding:8px 12px}.pass{background:rgba(34,197,94,.18);color:#bbf7d0}.warn{background:rgba(245,158,11,.18);color:#fde68a}.fail{background:rgba(239,68,68,.18);color:#fecaca}.grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));margin-top:16px}.metric{background:#0f172a;border:1px solid rgba(147,197,253,.2);border-radius:18px;padding:16px}.metric span{color:#93c5fd;display:block;font-size:.75rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}.metric strong{display:block;font-size:1.8rem;margin-top:8px}ul{display:grid;gap:10px;list-style:none;padding:0}li{border-radius:16px;padding:14px}li p{margin:.35rem 0 0}table{border-collapse:collapse;width:100%}td,th{border-bottom:1px solid rgba(147,197,253,.18);padding:12px;text-align:left}pre{background:#020617;border-radius:18px;color:#dbeafe;overflow:auto;padding:18px}</style></head><body><main><section class="hero"><h1>${escapeHtml3(title)}</h1><p>Deploy-facing readiness for profile switching: policy proof, audit evidence, trace evidence, and live session behavior.</p><p class="status ${escapeHtml3(report.status)}">${escapeHtml3(report.status.toUpperCase())}</p><div class="grid"><div class="metric"><span>Sessions</span><strong>${String(report.summary.sessions)}</strong></div><div class="metric"><span>Decisions</span><strong>${String(report.summary.decisions)}</strong></div><div class="metric"><span>Policy cases</span><strong>${String(report.summary.policyCases ?? 0)}</strong></div><div class="metric"><span>Audit</span><strong>${String(report.summary.auditEvents)}</strong></div><div class="metric"><span>Trace</span><strong>${String(report.summary.traceEvents)}</strong></div></div></section><section class="card"><h2>Issues</h2>${issues ? `<ul>${issues}</ul>` : '<p class="pass status">No readiness issues.</p>'}</section><section class="card"><h2>Live Sessions</h2>${sessions ? `<table><thead><tr><th>Session</th><th>Actions</th><th>Decisions</th><th>Auto applied</th><th>Blocked by</th></tr></thead><tbody>${sessions}</tbody></table>` : "<p>No live guard sessions found.</p>"}</section><section class="card"><h2>Raw Report</h2><pre>${stringifyForHtml(report)}</pre></section></main></body></html>`;
|
|
5941
|
+
};
|
|
5942
|
+
var createVoiceProfileSwitchReadinessRoutes = (options) => {
|
|
5943
|
+
const path = options.path ?? "/api/voice/profile-switch-readiness";
|
|
5944
|
+
const htmlPath = options.htmlPath === undefined ? "/voice/profile-switch-readiness" : options.htmlPath;
|
|
5945
|
+
const routes = new Elysia({
|
|
5946
|
+
name: options.name ?? "absolutejs-voice-profile-switch-readiness"
|
|
5947
|
+
}).get(path, async ({ query }) => buildVoiceProfileSwitchReadinessReport({
|
|
5948
|
+
...options,
|
|
5949
|
+
limit: typeof query.limit === "string" && Number.isFinite(Number(query.limit)) ? Number(query.limit) : options.limit,
|
|
5950
|
+
sessionId: typeof query.sessionId === "string" && query.sessionId.trim() ? query.sessionId.trim() : options.sessionId
|
|
5951
|
+
}));
|
|
5952
|
+
if (htmlPath) {
|
|
5953
|
+
routes.get(htmlPath, async ({ query }) => {
|
|
5954
|
+
const report = await buildVoiceProfileSwitchReadinessReport({
|
|
5955
|
+
...options,
|
|
5956
|
+
limit: typeof query.limit === "string" && Number.isFinite(Number(query.limit)) ? Number(query.limit) : options.limit,
|
|
5957
|
+
sessionId: typeof query.sessionId === "string" && query.sessionId.trim() ? query.sessionId.trim() : options.sessionId
|
|
5958
|
+
});
|
|
5959
|
+
const render = options.render ?? ((input) => renderVoiceProfileSwitchReadinessHTML(input, { title: options.title }));
|
|
5960
|
+
return new Response(await render(report), {
|
|
5961
|
+
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
5962
|
+
});
|
|
5963
|
+
});
|
|
5964
|
+
}
|
|
5965
|
+
return routes;
|
|
5966
|
+
};
|
|
5684
5967
|
|
|
5685
5968
|
// src/plugin.ts
|
|
5686
5969
|
var resolveQueryScenario = (query) => {
|
|
@@ -37661,7 +37944,9 @@ export {
|
|
|
37661
37944
|
renderVoiceProviderCapabilityHTML,
|
|
37662
37945
|
renderVoiceProofTrendRecommendationMarkdown,
|
|
37663
37946
|
renderVoiceProofTrendRecommendationHTML,
|
|
37947
|
+
renderVoiceProfileSwitchReadinessHTML,
|
|
37664
37948
|
renderVoiceProfileSwitchPolicyProofHTML,
|
|
37949
|
+
renderVoiceProfileSwitchLiveDecisionHTML,
|
|
37665
37950
|
renderVoiceProductionReadinessHTML,
|
|
37666
37951
|
renderVoicePostCallAnalysisMarkdown,
|
|
37667
37952
|
renderVoicePhoneAgentProductionSmokeHTML,
|
|
@@ -37915,7 +38200,9 @@ export {
|
|
|
37915
38200
|
createVoiceProviderCapabilityHTMLHandler,
|
|
37916
38201
|
createVoiceProofTrendRoutes,
|
|
37917
38202
|
createVoiceProofTrendRecommendationRoutes,
|
|
38203
|
+
createVoiceProfileSwitchReadinessRoutes,
|
|
37918
38204
|
createVoiceProfileSwitchPolicyProofRoutes,
|
|
38205
|
+
createVoiceProfileSwitchLiveDecisionRoutes,
|
|
37919
38206
|
createVoiceProductionReadinessRoutes,
|
|
37920
38207
|
createVoicePostgresTraceSinkDeliveryStore,
|
|
37921
38208
|
createVoicePostgresTraceEventStore,
|
|
@@ -38116,6 +38403,8 @@ export {
|
|
|
38116
38403
|
buildVoiceProofTrendReport,
|
|
38117
38404
|
buildVoiceProofTrendRecommendationReport,
|
|
38118
38405
|
buildVoiceProofTrendProfileSummaries,
|
|
38406
|
+
buildVoiceProfileSwitchReadinessReport,
|
|
38407
|
+
buildVoiceProfileSwitchLiveDecisionReport,
|
|
38119
38408
|
buildVoiceProductionReadinessReport,
|
|
38120
38409
|
buildVoiceProductionReadinessGate,
|
|
38121
38410
|
buildVoicePostCallAnalysisReport,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Elysia } from 'elysia';
|
|
2
2
|
import type { VoiceRealCallProfileDefault, VoiceRealCallProfileDefaultsReport, VoiceRealCallProfileHistoryReport } from './proofTrends';
|
|
3
3
|
import { type StoredVoiceAuditEvent, type VoiceAuditActor, type VoiceAuditEventStore } from './audit';
|
|
4
|
+
import type { VoiceTraceEventStore } from './trace';
|
|
4
5
|
export type VoiceProfileSwitchObservedSignals = {
|
|
5
6
|
currentProfileId?: string;
|
|
6
7
|
fallbackUsed?: boolean;
|
|
@@ -110,6 +111,109 @@ export type VoiceProfileSwitchPolicyProofRoutesOptions = VoiceProfileSwitchPolic
|
|
|
110
111
|
path?: string;
|
|
111
112
|
render?: (report: VoiceProfileSwitchPolicyProofReport) => string | Promise<string>;
|
|
112
113
|
};
|
|
114
|
+
export type VoiceProfileSwitchLiveDecisionEvidence = {
|
|
115
|
+
action?: VoiceProfileSwitchGuardAction | string;
|
|
116
|
+
at: number;
|
|
117
|
+
auditEventId?: string;
|
|
118
|
+
autoApplied?: boolean;
|
|
119
|
+
autoSwitchCount?: number;
|
|
120
|
+
blockedByPolicy?: string;
|
|
121
|
+
confidence?: number;
|
|
122
|
+
maxAutoSwitchesPerSession?: number;
|
|
123
|
+
minConfidence?: number;
|
|
124
|
+
mode?: VoiceProfileSwitchGuardMode | string;
|
|
125
|
+
outcome?: string;
|
|
126
|
+
previousProfileId?: string;
|
|
127
|
+
reason?: string;
|
|
128
|
+
recommendedProfileId?: string;
|
|
129
|
+
scenarioId?: string;
|
|
130
|
+
selectedProfileId?: string;
|
|
131
|
+
sessionId: string;
|
|
132
|
+
source: 'audit' | 'trace';
|
|
133
|
+
status?: string;
|
|
134
|
+
traceEventId?: string;
|
|
135
|
+
traceId?: string;
|
|
136
|
+
};
|
|
137
|
+
export type VoiceProfileSwitchLiveDecisionSession = {
|
|
138
|
+
actionCounts: Record<string, number>;
|
|
139
|
+
actions: string[];
|
|
140
|
+
autoApplied: number;
|
|
141
|
+
blockedByPolicy: string[];
|
|
142
|
+
decisions: VoiceProfileSwitchLiveDecisionEvidence[];
|
|
143
|
+
firstSeenAt: number;
|
|
144
|
+
lastDecision?: VoiceProfileSwitchLiveDecisionEvidence;
|
|
145
|
+
lastSeenAt: number;
|
|
146
|
+
sessionId: string;
|
|
147
|
+
};
|
|
148
|
+
export type VoiceProfileSwitchLiveDecisionReport = {
|
|
149
|
+
generatedAt: string;
|
|
150
|
+
ok: boolean;
|
|
151
|
+
sessions: VoiceProfileSwitchLiveDecisionSession[];
|
|
152
|
+
summary: {
|
|
153
|
+
auditEvents: number;
|
|
154
|
+
autoApplied: number;
|
|
155
|
+
blocked: number;
|
|
156
|
+
decisions: number;
|
|
157
|
+
recommendations: number;
|
|
158
|
+
sessions: number;
|
|
159
|
+
switches: number;
|
|
160
|
+
traceEvents: number;
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
export type VoiceProfileSwitchLiveDecisionReportOptions = {
|
|
164
|
+
audit?: VoiceAuditEventStore;
|
|
165
|
+
limit?: number;
|
|
166
|
+
sessionId?: string;
|
|
167
|
+
trace?: VoiceTraceEventStore;
|
|
168
|
+
};
|
|
169
|
+
export type VoiceProfileSwitchLiveDecisionRoutesOptions = VoiceProfileSwitchLiveDecisionReportOptions & {
|
|
170
|
+
htmlPath?: false | string;
|
|
171
|
+
name?: string;
|
|
172
|
+
path?: string;
|
|
173
|
+
render?: (report: VoiceProfileSwitchLiveDecisionReport) => string | Promise<string>;
|
|
174
|
+
title?: string;
|
|
175
|
+
};
|
|
176
|
+
export type VoiceProfileSwitchReadinessStatus = 'fail' | 'pass' | 'warn';
|
|
177
|
+
export type VoiceProfileSwitchReadinessIssue = {
|
|
178
|
+
code: string;
|
|
179
|
+
message: string;
|
|
180
|
+
status: Exclude<VoiceProfileSwitchReadinessStatus, 'pass'>;
|
|
181
|
+
};
|
|
182
|
+
export type VoiceProfileSwitchReadinessReport = {
|
|
183
|
+
generatedAt: string;
|
|
184
|
+
issues: VoiceProfileSwitchReadinessIssue[];
|
|
185
|
+
live: VoiceProfileSwitchLiveDecisionReport;
|
|
186
|
+
policy?: VoiceProfileSwitchPolicyProofReport;
|
|
187
|
+
status: VoiceProfileSwitchReadinessStatus;
|
|
188
|
+
summary: {
|
|
189
|
+
auditEvents: number;
|
|
190
|
+
autoApplied: number;
|
|
191
|
+
blocked: number;
|
|
192
|
+
decisions: number;
|
|
193
|
+
policyCases?: number;
|
|
194
|
+
sessions: number;
|
|
195
|
+
switches: number;
|
|
196
|
+
traceEvents: number;
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
export type VoiceProfileSwitchReadinessOptions = VoiceProfileSwitchLiveDecisionReportOptions & {
|
|
200
|
+
autoMode?: boolean;
|
|
201
|
+
maxBlockedRatio?: number;
|
|
202
|
+
maxAutoAppliedRatio?: number;
|
|
203
|
+
policyProof?: false | VoiceProfileSwitchPolicyProofOptions;
|
|
204
|
+
requireAudit?: boolean;
|
|
205
|
+
requireAllowedProfilePolicy?: boolean;
|
|
206
|
+
requireLiveDecisions?: boolean;
|
|
207
|
+
requirePolicyProof?: boolean;
|
|
208
|
+
requireTrace?: boolean;
|
|
209
|
+
};
|
|
210
|
+
export type VoiceProfileSwitchReadinessRoutesOptions = VoiceProfileSwitchReadinessOptions & {
|
|
211
|
+
htmlPath?: false | string;
|
|
212
|
+
name?: string;
|
|
213
|
+
path?: string;
|
|
214
|
+
render?: (report: VoiceProfileSwitchReadinessReport) => string | Promise<string>;
|
|
215
|
+
title?: string;
|
|
216
|
+
};
|
|
113
217
|
export declare const recommendVoiceProfileSwitch: (options: VoiceProfileSwitchRecommendationOptions) => VoiceProfileSwitchRecommendation;
|
|
114
218
|
export declare const applyVoiceProfileSwitchGuard: (options: VoiceProfileSwitchGuardOptions) => Promise<VoiceProfileSwitchGuardDecision>;
|
|
115
219
|
export declare const runVoiceProfileSwitchPolicyProof: (options: VoiceProfileSwitchPolicyProofOptions) => Promise<VoiceProfileSwitchPolicyProofReport>;
|
|
@@ -156,3 +260,91 @@ export declare const createVoiceProfileSwitchPolicyProofRoutes: (options: VoiceP
|
|
|
156
260
|
standaloneSchema: {};
|
|
157
261
|
response: {};
|
|
158
262
|
}>;
|
|
263
|
+
export declare const buildVoiceProfileSwitchLiveDecisionReport: (options: VoiceProfileSwitchLiveDecisionReportOptions) => Promise<VoiceProfileSwitchLiveDecisionReport>;
|
|
264
|
+
export declare const renderVoiceProfileSwitchLiveDecisionHTML: (report: VoiceProfileSwitchLiveDecisionReport, options?: {
|
|
265
|
+
title?: string;
|
|
266
|
+
}) => string;
|
|
267
|
+
export declare const createVoiceProfileSwitchLiveDecisionRoutes: (options: VoiceProfileSwitchLiveDecisionRoutesOptions) => Elysia<"", {
|
|
268
|
+
decorator: {};
|
|
269
|
+
store: {};
|
|
270
|
+
derive: {};
|
|
271
|
+
resolve: {};
|
|
272
|
+
}, {
|
|
273
|
+
typebox: {};
|
|
274
|
+
error: {};
|
|
275
|
+
}, {
|
|
276
|
+
schema: {};
|
|
277
|
+
standaloneSchema: {};
|
|
278
|
+
macro: {};
|
|
279
|
+
macroFn: {};
|
|
280
|
+
parser: {};
|
|
281
|
+
response: {};
|
|
282
|
+
}, {
|
|
283
|
+
[x: string]: {
|
|
284
|
+
get: {
|
|
285
|
+
body: unknown;
|
|
286
|
+
params: {};
|
|
287
|
+
query: unknown;
|
|
288
|
+
headers: unknown;
|
|
289
|
+
response: {
|
|
290
|
+
200: VoiceProfileSwitchLiveDecisionReport;
|
|
291
|
+
};
|
|
292
|
+
};
|
|
293
|
+
};
|
|
294
|
+
}, {
|
|
295
|
+
derive: {};
|
|
296
|
+
resolve: {};
|
|
297
|
+
schema: {};
|
|
298
|
+
standaloneSchema: {};
|
|
299
|
+
response: {};
|
|
300
|
+
}, {
|
|
301
|
+
derive: {};
|
|
302
|
+
resolve: {};
|
|
303
|
+
schema: {};
|
|
304
|
+
standaloneSchema: {};
|
|
305
|
+
response: {};
|
|
306
|
+
}>;
|
|
307
|
+
export declare const buildVoiceProfileSwitchReadinessReport: (options: VoiceProfileSwitchReadinessOptions) => Promise<VoiceProfileSwitchReadinessReport>;
|
|
308
|
+
export declare const renderVoiceProfileSwitchReadinessHTML: (report: VoiceProfileSwitchReadinessReport, options?: {
|
|
309
|
+
title?: string;
|
|
310
|
+
}) => string;
|
|
311
|
+
export declare const createVoiceProfileSwitchReadinessRoutes: (options: VoiceProfileSwitchReadinessRoutesOptions) => Elysia<"", {
|
|
312
|
+
decorator: {};
|
|
313
|
+
store: {};
|
|
314
|
+
derive: {};
|
|
315
|
+
resolve: {};
|
|
316
|
+
}, {
|
|
317
|
+
typebox: {};
|
|
318
|
+
error: {};
|
|
319
|
+
}, {
|
|
320
|
+
schema: {};
|
|
321
|
+
standaloneSchema: {};
|
|
322
|
+
macro: {};
|
|
323
|
+
macroFn: {};
|
|
324
|
+
parser: {};
|
|
325
|
+
response: {};
|
|
326
|
+
}, {
|
|
327
|
+
[x: string]: {
|
|
328
|
+
get: {
|
|
329
|
+
body: unknown;
|
|
330
|
+
params: {};
|
|
331
|
+
query: unknown;
|
|
332
|
+
headers: unknown;
|
|
333
|
+
response: {
|
|
334
|
+
200: VoiceProfileSwitchReadinessReport;
|
|
335
|
+
};
|
|
336
|
+
};
|
|
337
|
+
};
|
|
338
|
+
}, {
|
|
339
|
+
derive: {};
|
|
340
|
+
resolve: {};
|
|
341
|
+
schema: {};
|
|
342
|
+
standaloneSchema: {};
|
|
343
|
+
response: {};
|
|
344
|
+
}, {
|
|
345
|
+
derive: {};
|
|
346
|
+
resolve: {};
|
|
347
|
+
schema: {};
|
|
348
|
+
standaloneSchema: {};
|
|
349
|
+
response: {};
|
|
350
|
+
}>;
|