@absolutejs/voice 0.0.22-beta.434 → 0.0.22-beta.436

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.
@@ -2,6 +2,8 @@ import { Elysia } from 'elysia';
2
2
  import type { VoiceFailureReplayReport, VoiceOperationsRecord } from './operationsRecord';
3
3
  import type { VoiceOperationalStatusReport } from './operationalStatus';
4
4
  import type { VoiceOpsRecoveryReport } from './opsRecovery';
5
+ import type { VoiceAuditEventStore } from './audit';
6
+ import type { VoiceTraceEventStore } from './trace';
5
7
  import type { VoiceMonitorIssue } from './voiceMonitoring';
6
8
  export type VoiceIncidentTimelineStatus = 'fail' | 'pass' | 'warn';
7
9
  export type VoiceIncidentTimelineSeverity = 'critical' | 'info' | 'warn';
@@ -22,6 +24,8 @@ export type VoiceIncidentRecoveryAction = {
22
24
  };
23
25
  export type VoiceIncidentRecoveryActionResult = {
24
26
  actionId: string;
27
+ afterStatus?: VoiceIncidentTimelineStatus;
28
+ beforeStatus?: VoiceIncidentTimelineStatus;
25
29
  detail?: string;
26
30
  href?: string;
27
31
  ok: boolean;
@@ -34,6 +38,31 @@ export type VoiceIncidentRecoveryActionHandlerInput = {
34
38
  request: Request;
35
39
  };
36
40
  export type VoiceIncidentRecoveryActionHandler = (input: VoiceIncidentRecoveryActionHandlerInput) => Promise<VoiceIncidentRecoveryActionResult> | VoiceIncidentRecoveryActionResult;
41
+ export type VoiceIncidentRecoveryOutcome = 'failed' | 'improved' | 'regressed' | 'unchanged';
42
+ export type VoiceIncidentRecoveryOutcomeEntry = {
43
+ actionId: string;
44
+ afterStatus?: VoiceIncidentTimelineStatus;
45
+ at: number;
46
+ beforeStatus?: VoiceIncidentTimelineStatus;
47
+ detail?: string;
48
+ eventId: string;
49
+ outcome: VoiceIncidentRecoveryOutcome;
50
+ status?: number;
51
+ traceId?: string;
52
+ };
53
+ export type VoiceIncidentRecoveryOutcomeReport = {
54
+ checkedAt: number;
55
+ entries: VoiceIncidentRecoveryOutcomeEntry[];
56
+ failed: number;
57
+ improved: number;
58
+ regressed: number;
59
+ total: number;
60
+ unchanged: number;
61
+ };
62
+ export type VoiceIncidentRecoveryOutcomeOptions = {
63
+ audit?: VoiceAuditEventStore;
64
+ limit?: number;
65
+ };
37
66
  export type VoiceIncidentTimelineEvent = {
38
67
  action?: VoiceIncidentTimelineAction;
39
68
  at: number;
@@ -91,14 +120,22 @@ export type VoiceIncidentTimelineOptions = {
91
120
  export type VoiceIncidentTimelineRoutesOptions = VoiceIncidentTimelineOptions & {
92
121
  actionHandlers?: Record<string, VoiceIncidentRecoveryActionHandler>;
93
122
  actionPath?: false | string;
123
+ audit?: VoiceAuditEventStore;
94
124
  headers?: HeadersInit;
95
125
  htmlPath?: false | string;
96
126
  markdownPath?: false | string;
97
127
  name?: string;
98
128
  path?: string;
99
129
  render?: (report: VoiceIncidentTimelineReport) => string | Promise<string>;
130
+ recoveryOutcomeHtmlPath?: false | string;
131
+ recoveryOutcomePath?: false | string;
100
132
  title?: string;
133
+ trace?: VoiceTraceEventStore;
101
134
  };
135
+ export declare const buildVoiceIncidentRecoveryOutcomeReport: (options: VoiceIncidentRecoveryOutcomeOptions) => Promise<VoiceIncidentRecoveryOutcomeReport>;
136
+ export declare const renderVoiceIncidentRecoveryOutcomeHTML: (report: VoiceIncidentRecoveryOutcomeReport, options?: {
137
+ title?: string;
138
+ }) => string;
102
139
  export declare const buildVoiceIncidentTimelineReport: (options: VoiceIncidentTimelineOptions) => Promise<VoiceIncidentTimelineReport>;
103
140
  export declare const renderVoiceIncidentTimelineMarkdown: (report: VoiceIncidentTimelineReport, options?: {
104
141
  title?: string;
package/dist/index.d.ts CHANGED
@@ -52,7 +52,7 @@ export { assertVoiceLiveOpsControlEvidence, assertVoiceLiveOpsEvidence, buildVoi
52
52
  export type { VoiceLiveOpsAction, VoiceLiveOpsActionInput, VoiceLiveOpsActionResult, VoiceLiveOpsControllerOptions, VoiceLiveOpsControlState, VoiceLiveOpsControlStatus, VoiceLiveOpsControlStore, VoiceLiveOpsControlEvidenceInput, VoiceLiveOpsControlEvidenceReport, VoiceLiveOpsEvidenceInput, VoiceLiveOpsEvidenceReport, VoiceLiveOpsRoutesOptions } from './liveOps';
53
53
  export { buildVoiceDeliveryRuntimeReport, createVoiceDeliveryRuntime, createVoiceDeliveryRuntimePresetConfig, createVoiceDeliveryRuntimeRoutes, renderVoiceDeliveryRuntimeHTML } from './deliveryRuntime';
54
54
  export { buildVoiceOperationalStatusReport, createVoiceOperationalStatusRoutes, renderVoiceOperationalStatusHTML } from './operationalStatus';
55
- export { buildVoiceIncidentTimelineReport, createVoiceIncidentTimelineRoutes, renderVoiceIncidentTimelineHTML, renderVoiceIncidentTimelineMarkdown } from './incidentTimeline';
55
+ export { buildVoiceIncidentRecoveryOutcomeReport, buildVoiceIncidentTimelineReport, createVoiceIncidentTimelineRoutes, renderVoiceIncidentRecoveryOutcomeHTML, renderVoiceIncidentTimelineHTML, renderVoiceIncidentTimelineMarkdown } from './incidentTimeline';
56
56
  export { applyVoiceDataRetentionPolicy, assertVoiceDataControlEvidence, buildVoiceDataControlReport, buildVoiceDataRetentionPlan, createVoiceDataControlRoutes, createVoiceZeroRetentionPolicy, evaluateVoiceDataControlEvidence, renderVoiceDataControlHTML, renderVoiceDataControlMarkdown, voiceComplianceRedactionDefaults } from './dataControl';
57
57
  export type { VoiceDataControlAssertionInput, VoiceDataControlAssertionReport, VoiceDataControlProviderKeySurface, VoiceDataControlReport, VoiceDataControlRoutesOptions, VoiceDataControlStorageSurface, VoiceDataRetentionPolicy, VoiceDataRetentionReport, VoiceDataRetentionScope, VoiceDataRetentionScopeReport, VoiceDataRetentionStores } from './dataControl';
58
58
  export type { VoiceDemoReadyReport, VoiceDemoReadyRoutesOptions, VoiceDemoReadySection, VoiceDemoReadyStatus } from './demoReadyRoutes';
@@ -60,7 +60,7 @@ export type { VoiceDeliverySinkDescriptor, VoiceDeliverySinkDescriptorInput, Voi
60
60
  export type { VoiceOpsActionAuditRecord, VoiceOpsActionAuditRoutesOptions, VoiceOpsActionHistoryEntry, VoiceOpsActionHistoryReport } from './opsActionAuditRoutes';
61
61
  export type { VoiceDeliveryRuntime, VoiceDeliveryRuntimeAuditConfig, VoiceDeliveryRuntimeConfig, VoiceDeliveryRuntimeFilePresetOptions, VoiceDeliveryRuntimePresetLeaseConfig, VoiceDeliveryRuntimePresetMode, VoiceDeliveryRuntimePresetOptions, VoiceDeliveryRuntimeReport, VoiceDeliveryRuntimeRoutesOptions, VoiceDeliveryRuntimeS3PresetOptions, VoiceDeliveryRuntimeSummary, VoiceDeliveryRuntimeTickResult, VoiceDeliveryRuntimeTraceConfig, VoiceDeliveryRuntimeWebhookPresetOptions } from './deliveryRuntime';
62
62
  export type { VoiceOperationalStatus, VoiceOperationalStatusCheck, VoiceOperationalStatusOptions, VoiceOperationalStatusReport, VoiceOperationalStatusRoutesOptions, VoiceOperationalStatusValue } from './operationalStatus';
63
- export type { VoiceIncidentRecoveryAction, VoiceIncidentRecoveryActionHandler, VoiceIncidentRecoveryActionHandlerInput, VoiceIncidentRecoveryActionResult, VoiceIncidentTimelineAction, VoiceIncidentTimelineEvent, VoiceIncidentTimelineLinks, VoiceIncidentTimelineOptions, VoiceIncidentTimelineReport, VoiceIncidentTimelineRoutesOptions, VoiceIncidentTimelineSeverity, VoiceIncidentTimelineStatus, VoiceIncidentTimelineValue } from './incidentTimeline';
63
+ export type { VoiceIncidentRecoveryAction, VoiceIncidentRecoveryActionHandler, VoiceIncidentRecoveryActionHandlerInput, VoiceIncidentRecoveryActionResult, VoiceIncidentRecoveryOutcome, VoiceIncidentRecoveryOutcomeEntry, VoiceIncidentRecoveryOutcomeOptions, VoiceIncidentRecoveryOutcomeReport, VoiceIncidentTimelineAction, VoiceIncidentTimelineEvent, VoiceIncidentTimelineLinks, VoiceIncidentTimelineOptions, VoiceIncidentTimelineReport, VoiceIncidentTimelineRoutesOptions, VoiceIncidentTimelineSeverity, VoiceIncidentTimelineStatus, VoiceIncidentTimelineValue } from './incidentTimeline';
64
64
  export { compareVoiceEvalBaseline, createVoiceFileEvalBaselineStore, createVoiceFileScenarioFixtureStore, createVoiceEvalRoutes, renderVoiceEvalBaselineHTML, renderVoiceEvalHTML, renderVoiceScenarioEvalHTML, renderVoiceScenarioFixtureEvalHTML, runVoiceScenarioEvals, runVoiceScenarioFixtureEvals, runVoiceSessionEvals } from './evalRoutes';
65
65
  export { assertVoiceSimulationSuiteEvidence, createVoiceSimulationSuiteRoutes, evaluateVoiceSimulationSuiteEvidence, renderVoiceSimulationSuiteHTML, runVoiceSimulationSuite } from './simulationSuite';
66
66
  export { createVoiceWorkflowContract, createVoiceWorkflowContractHandler, createVoiceWorkflowContractPreset, createVoiceWorkflowScenario, recordVoiceWorkflowContractTrace, validateVoiceWorkflowRouteResult } from './workflowContract';
package/dist/index.js CHANGED
@@ -30183,6 +30183,62 @@ var defaultIncidentRecoveryActions = (events, links) => {
30183
30183
  return actions;
30184
30184
  };
30185
30185
  var worstStatus3 = (statuses) => statuses.includes("fail") ? "fail" : statuses.includes("warn") ? "warn" : "pass";
30186
+ var statusRank6 = (status) => status === "fail" ? 3 : status === "warn" ? 2 : status === "pass" ? 1 : 0;
30187
+ var isRecord4 = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
30188
+ var getIncidentRecoveryBody = (event) => {
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 : {};
30208
+ return {
30209
+ actionId: event.action.replace(/^incident\./, ""),
30210
+ afterStatus,
30211
+ at: event.at,
30212
+ beforeStatus,
30213
+ detail: getIncidentRecoveryDetail(event),
30214
+ eventId: event.id,
30215
+ outcome,
30216
+ status: typeof payload.status === "number" ? payload.status : undefined,
30217
+ traceId: event.traceId
30218
+ };
30219
+ };
30220
+ var buildVoiceIncidentRecoveryOutcomeReport = async (options) => {
30221
+ const events = options.audit ? await options.audit.list({
30222
+ limit: options.limit ?? 50,
30223
+ resourceType: "voice.ops.action",
30224
+ type: "operator.action"
30225
+ }) : [];
30226
+ const entries = events.filter((event) => event.action.startsWith("incident.")).map(toIncidentRecoveryOutcomeEntry).sort((left, right) => right.at - left.at);
30227
+ return {
30228
+ checkedAt: Date.now(),
30229
+ entries,
30230
+ failed: entries.filter((entry) => entry.outcome === "failed").length,
30231
+ improved: entries.filter((entry) => entry.outcome === "improved").length,
30232
+ regressed: entries.filter((entry) => entry.outcome === "regressed").length,
30233
+ total: entries.length,
30234
+ unchanged: entries.filter((entry) => entry.outcome === "unchanged").length
30235
+ };
30236
+ };
30237
+ var renderVoiceIncidentRecoveryOutcomeHTML = (report, options = {}) => {
30238
+ const title = options.title ?? "AbsoluteJS Voice Incident Recovery Outcomes";
30239
+ const rows = report.entries.map((entry) => `<article class="${escapeHtml43(entry.outcome)}"><span>${escapeHtml43(entry.outcome.toUpperCase())}</span><h2>${escapeHtml43(entry.actionId)}</h2><p>${escapeHtml43(new Date(entry.at).toLocaleString())}</p><strong>${escapeHtml43(entry.beforeStatus ?? "unknown")} -> ${escapeHtml43(entry.afterStatus ?? "unknown")}</strong>${entry.detail ? `<p>${escapeHtml43(entry.detail)}</p>` : ""}</article>`).join("");
30240
+ 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:#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>${escapeHtml43(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>`;
30241
+ };
30186
30242
  var pushOperationalStatusEvents = (events, report, links) => {
30187
30243
  if (!report) {
30188
30244
  return;
@@ -30429,6 +30485,8 @@ var createVoiceIncidentTimelineRoutes = (options) => {
30429
30485
  const htmlPath = options.htmlPath === undefined ? "/voice/incident-timeline" : options.htmlPath;
30430
30486
  const markdownPath = options.markdownPath === undefined ? "/voice/incident-timeline.md" : options.markdownPath;
30431
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;
30432
30490
  const routes = new Elysia46({
30433
30491
  name: options.name ?? "absolutejs-voice-incident-timeline"
30434
30492
  }).get(path, async () => {
@@ -30519,12 +30577,64 @@ var createVoiceIncidentTimelineRoutes = (options) => {
30519
30577
  report,
30520
30578
  request
30521
30579
  });
30522
- return new Response(JSON.stringify(result), {
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), {
30523
30605
  headers: {
30524
30606
  "Content-Type": "application/json; charset=utf-8",
30525
30607
  ...options.headers
30526
30608
  },
30527
- status: result.ok ? 200 : 500
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
+ }
30528
30638
  });
30529
30639
  });
30530
30640
  }
@@ -36038,7 +36148,7 @@ var defaultRequirement = {
36038
36148
  requireFallback: false,
36039
36149
  requireTimeoutBudget: false
36040
36150
  };
36041
- var statusRank6 = {
36151
+ var statusRank7 = {
36042
36152
  pass: 0,
36043
36153
  warn: 1,
36044
36154
  fail: 2
@@ -36054,7 +36164,7 @@ var surfaceProviderNames = (surface) => uniqueSorted8([
36054
36164
  ...isProviderList(surface.fallback) ? surface.fallback : [],
36055
36165
  ...isProviderList(surface.allowProviders) ? surface.allowProviders : []
36056
36166
  ]);
36057
- var surfaceStatus = (issues) => issues.reduce((status, issue) => statusRank6[issue.status] > statusRank6[status] ? issue.status : status, "pass");
36167
+ var surfaceStatus = (issues) => issues.reduce((status, issue) => statusRank7[issue.status] > statusRank7[status] ? issue.status : status, "pass");
36058
36168
  var resolvedRequirement = (surface, options) => ({
36059
36169
  ...defaultRequirement,
36060
36170
  ...options.defaultRequirement ?? {},
@@ -37198,12 +37308,12 @@ var recommendVoiceProviderStack = (input) => {
37198
37308
  };
37199
37309
  };
37200
37310
  var rollupContractStatus = (checks) => checks.some((check) => check.status === "fail") ? "fail" : checks.some((check) => check.status === "warn") ? "warn" : "pass";
37201
- var statusRank7 = {
37311
+ var statusRank8 = {
37202
37312
  pass: 0,
37203
37313
  warn: 1,
37204
37314
  fail: 2
37205
37315
  };
37206
- var statusExceeds2 = (actual, max2) => statusRank7[actual] > statusRank7[max2];
37316
+ var statusExceeds2 = (actual, max2) => statusRank8[actual] > statusRank8[max2];
37207
37317
  var buildVoiceProviderContractMatrix = (input) => {
37208
37318
  const rows = input.contracts.map((contract) => {
37209
37319
  const configured = contract.configured !== false;
@@ -41378,6 +41488,7 @@ export {
41378
41488
  renderVoiceLatencySLOMarkdown,
41379
41489
  renderVoiceIncidentTimelineMarkdown,
41380
41490
  renderVoiceIncidentTimelineHTML,
41491
+ renderVoiceIncidentRecoveryOutcomeHTML,
41381
41492
  renderVoiceHandoffHealthHTML,
41382
41493
  renderVoiceGuardrailMarkdown,
41383
41494
  renderVoiceFailureReplayMarkdown,
@@ -41879,6 +41990,7 @@ export {
41879
41990
  buildVoiceLiveOpsControlState,
41880
41991
  buildVoiceLatencySLOGate,
41881
41992
  buildVoiceIncidentTimelineReport,
41993
+ buildVoiceIncidentRecoveryOutcomeReport,
41882
41994
  buildVoiceIncidentBundle,
41883
41995
  buildVoiceIOProviderRouterTraceEvent,
41884
41996
  buildVoiceGuardrailReport,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.434",
3
+ "version": "0.0.22-beta.436",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",