@absolutejs/voice 0.0.22-beta.222 → 0.0.22-beta.223

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/README.md CHANGED
@@ -2756,6 +2756,13 @@ app.use(
2756
2756
  artifactIntegrity: {
2757
2757
  maxAgeMs: 15 * 60 * 1000
2758
2758
  },
2759
+ deliveryDestinations: [
2760
+ {
2761
+ directory: '.voice-runtime/observability-exports',
2762
+ kind: 'file',
2763
+ label: 'Local customer-owned observability archive'
2764
+ }
2765
+ ],
2759
2766
  artifacts: [
2760
2767
  {
2761
2768
  id: 'latest-proof-pack',
@@ -2791,7 +2798,7 @@ const exportReport = await buildVoiceObservabilityExport({
2791
2798
  });
2792
2799
  ```
2793
2800
 
2794
- The route helper exposes JSON at `/api/voice/observability-export`, an artifact index at `/api/voice/observability-export/artifacts`, per-artifact downloads at `/api/voice/observability-export/artifacts/:artifactId`, Markdown at `/voice/observability-export.md`, and HTML at `/voice/observability-export`. Path-backed artifacts are hashed with SHA-256 by default, include byte size and freshness metadata, and can fail the export when required evidence is missing or stale. Failed trace/audit deliveries fail the export report, pending deliveries warn, and every trace/audit envelope includes the linked operations-record URL when one is configured. This is the primitive to use when customers ask how voice evidence leaves the app without going through a hosted vendor dashboard.
2801
+ The route helper exposes JSON at `/api/voice/observability-export`, an artifact index at `/api/voice/observability-export/artifacts`, per-artifact downloads at `/api/voice/observability-export/artifacts/:artifactId`, delivery at `POST /api/voice/observability-export/deliveries`, Markdown at `/voice/observability-export.md`, and HTML at `/voice/observability-export`. Path-backed artifacts are hashed with SHA-256 by default, include byte size and freshness metadata, and can fail the export when required evidence is missing or stale. File delivery writes `manifest.json`, `artifact-index.json`, and artifact files into a customer-owned archive directory; webhook delivery posts the manifest and artifact index to a buyer-owned collector, SIEM bridge, or warehouse endpoint. Failed trace/audit deliveries fail the export report, pending deliveries warn, and every trace/audit envelope includes the linked operations-record URL when one is configured. This is the primitive to use when customers ask how voice evidence leaves the app without going through a hosted vendor dashboard.
2795
2802
 
2796
2803
  Pass the same report into production readiness when export health should block deploys:
2797
2804
 
package/dist/index.d.ts CHANGED
@@ -53,7 +53,7 @@ export { createVoiceReadinessProfile, recommendVoiceReadinessProfile } from './r
53
53
  export { buildVoiceProviderContractMatrix, createVoiceProviderContractMatrixHTMLHandler, createVoiceProviderContractMatrixJSONHandler, createVoiceProviderContractMatrixPreset, createVoiceProviderContractMatrixRoutes, evaluateVoiceProviderStackGaps, renderVoiceProviderContractMatrixHTML, recommendVoiceProviderStack } from './providerStackRecommendations';
54
54
  export { buildVoiceOpsConsoleReport, createVoiceOpsConsoleRoutes, renderVoiceOpsConsoleHTML } from './opsConsoleRoutes';
55
55
  export { buildVoiceOperationsRecord, createVoiceOperationsRecordRoutes, renderVoiceOperationsRecordHTML, renderVoiceOperationsRecordIncidentMarkdown } from './operationsRecord';
56
- export { buildVoiceObservabilityArtifactIndex, buildVoiceObservabilityExport, createVoiceObservabilityExportRoutes, renderVoiceObservabilityExportMarkdown } from './observabilityExport';
56
+ export { buildVoiceObservabilityArtifactIndex, buildVoiceObservabilityExport, createVoiceObservabilityExportRoutes, deliverVoiceObservabilityExport, renderVoiceObservabilityExportMarkdown } from './observabilityExport';
57
57
  export { buildVoiceOpsRecoveryReadinessCheck, buildVoiceOpsRecoveryReport, createVoiceOpsRecoveryRoutes, renderVoiceOpsRecoveryHTML, renderVoiceOpsRecoveryMarkdown } from './opsRecovery';
58
58
  export { buildVoiceIncidentBundle, createStoredVoiceIncidentBundleArtifact, createVoiceIncidentBundleRoutes, createVoiceMemoryIncidentBundleStore, pruneVoiceIncidentBundleArtifacts, saveVoiceIncidentBundleArtifact } from './incidentBundle';
59
59
  export { summarizeVoiceOpsStatus } from './opsStatus';
@@ -118,7 +118,7 @@ export type { VoiceProductionReadinessAction, VoiceProductionReadinessAuditOptio
118
118
  export type { VoiceReadinessProfileName, VoiceReadinessProfileOptions, VoiceReadinessProfileRecommendation, VoiceReadinessProfileRecommendationScore, VoiceReadinessProfileRoutesOptions } from './readinessProfiles';
119
119
  export type { VoiceProviderStackChoice, VoiceProviderStackCapabilities, VoiceProviderStackCapabilityGap, VoiceProviderStackCapabilityGapInput, VoiceProviderStackCapabilityGapReport, VoiceProviderContractCheck, VoiceProviderContractCheckStatus, VoiceProviderContractDefinition, VoiceProviderContractMatrixHandlerOptions, VoiceProviderContractMatrixHTMLHandlerOptions, VoiceProviderContractMatrixInput, VoiceProviderContractMatrixPresetOptions, VoiceProviderContractMatrixReport, VoiceProviderContractMatrixRoutesOptions, VoiceProviderContractMatrixRow, VoiceProviderStackInput, VoiceProviderStackKind, VoiceProviderStackRecommendation } from './providerStackRecommendations';
120
120
  export type { VoiceOperationsRecord, VoiceOperationsRecordAgentHandoff, VoiceOperationsRecordAuditSummary, VoiceOperationsRecordIntegrationEventSummary, VoiceOperationsRecordOptions, VoiceOperationsRecordOutcome, VoiceOperationsRecordProviderDecision, VoiceOperationsRecordReviewSummary, VoiceOperationsRecordRoutesOptions, VoiceOperationsRecordStatus, VoiceOperationsRecordTaskSummary, VoiceOperationsRecordTranscriptTurn, VoiceOperationsRecordTool } from './operationsRecord';
121
- export type { VoiceObservabilityExportArtifact, VoiceObservabilityExportArtifactChecksum, VoiceObservabilityExportArtifactFreshness, VoiceObservabilityExportArtifactIndex, VoiceObservabilityExportArtifactIndexItem, VoiceObservabilityExportArtifactKind, VoiceObservabilityExportDeliverySummary, VoiceObservabilityExportEnvelope, VoiceObservabilityExportIssue, VoiceObservabilityExportIssueCode, VoiceObservabilityExportOptions, VoiceObservabilityExportRedactionSummary, VoiceObservabilityExportReport, VoiceObservabilityExportRoutesOptions, VoiceObservabilityExportStatus } from './observabilityExport';
121
+ export type { VoiceObservabilityExportArtifact, VoiceObservabilityExportArtifactChecksum, VoiceObservabilityExportArtifactFreshness, VoiceObservabilityExportArtifactIndex, VoiceObservabilityExportArtifactIndexItem, VoiceObservabilityExportArtifactKind, VoiceObservabilityExportDeliverySummary, VoiceObservabilityExportDeliveryDestination, VoiceObservabilityExportDeliveryDestinationResult, VoiceObservabilityExportDeliveryOptions, VoiceObservabilityExportDeliveryReport, VoiceObservabilityExportEnvelope, VoiceObservabilityExportIssue, VoiceObservabilityExportIssueCode, VoiceObservabilityExportOptions, VoiceObservabilityExportRedactionSummary, VoiceObservabilityExportReport, VoiceObservabilityExportRoutesOptions, VoiceObservabilityExportStatus } from './observabilityExport';
122
122
  export type { VoiceOpsRecoveryFailedSession, VoiceOpsRecoveryInterventionSummary, VoiceOpsRecoveryIssue, VoiceOpsRecoveryIssueCode, VoiceOpsRecoveryLinks, VoiceOpsRecoveryProviderSummary, VoiceOpsRecoveryReport, VoiceOpsRecoveryReportOptions, VoiceOpsRecoveryRoutesOptions, VoiceOpsRecoveryStatus } from './opsRecovery';
123
123
  export type { StoredVoiceIncidentBundleArtifact, VoiceIncidentBundle, VoiceIncidentBundleArtifactOptions, VoiceIncidentBundleFormat, VoiceIncidentBundleOptions, VoiceIncidentBundleRetentionOptions, VoiceIncidentBundleRetentionReport, VoiceIncidentBundleRoutesOptions, VoiceIncidentBundleStore, VoiceIncidentBundleStoreFilter, VoiceIncidentBundleSummary } from './incidentBundle';
124
124
  export type { VoiceQualityLink, VoiceQualityMetric, VoiceQualityReport, VoiceQualityRoutesOptions, VoiceQualityStatus, VoiceQualityThresholds } from './qualityRoutes';
package/dist/index.js CHANGED
@@ -6972,14 +6972,14 @@ var createProofFetch = (provider, requests) => {
6972
6972
  };
6973
6973
  };
6974
6974
  var createProofDialer = (input) => {
6975
- const fetch = createProofFetch(input.provider, input.requests);
6975
+ const fetch2 = createProofFetch(input.provider, input.requests);
6976
6976
  if (input.provider === "twilio") {
6977
6977
  return createVoiceTwilioCampaignDialer({
6978
6978
  accountSid: "AC_dry_run",
6979
6979
  answerUrl: joinUrlPath(input.baseUrl, "/api/twilio/voice"),
6980
6980
  apiBaseUrl: "https://twilio.dry-run.absolutejs.local",
6981
6981
  authToken: "dry-run-token",
6982
- fetch,
6982
+ fetch: fetch2,
6983
6983
  from: input.from,
6984
6984
  statusCallbackEvents: ["answered", "completed"],
6985
6985
  statusCallbackUrl: joinUrlPath(input.baseUrl, "/api/telephony-webhook")
@@ -6990,7 +6990,7 @@ var createProofDialer = (input) => {
6990
6990
  apiBaseUrl: "https://telnyx.dry-run.absolutejs.local",
6991
6991
  apiKey: "dry-run-token",
6992
6992
  connectionId: "dry-run-connection",
6993
- fetch,
6993
+ fetch: fetch2,
6994
6994
  from: input.from,
6995
6995
  webhookUrl: joinUrlPath(input.baseUrl, "/api/telnyx/webhook")
6996
6996
  });
@@ -7001,7 +7001,7 @@ var createProofDialer = (input) => {
7001
7001
  authId: "dry-run-auth-id",
7002
7002
  authToken: "dry-run-token",
7003
7003
  callbackUrl: joinUrlPath(input.baseUrl, "/api/plivo/webhook"),
7004
- fetch,
7004
+ fetch: fetch2,
7005
7005
  from: input.from
7006
7006
  });
7007
7007
  };
@@ -24943,7 +24943,8 @@ var createVoiceOperationsRecordRoutes = (options) => {
24943
24943
  // src/observabilityExport.ts
24944
24944
  import { Elysia as Elysia42 } from "elysia";
24945
24945
  import { createHash } from "crypto";
24946
- import { readFile as readFile2, stat } from "fs/promises";
24946
+ import { mkdir as mkdir4, readFile as readFile2, stat } from "fs/promises";
24947
+ import { join as join3 } from "path";
24947
24948
  var isDeliveryStore = (value) => !Array.isArray(value) && typeof value.list === "function";
24948
24949
  var getString18 = (value) => typeof value === "string" ? value : undefined;
24949
24950
  var getProviderKind = (payload) => getString18(payload.kind) ?? getString18(payload.providerKind);
@@ -24982,6 +24983,11 @@ var checksumFile = async (path) => {
24982
24983
  const buffer = await readFile2(path);
24983
24984
  return createHash("sha256").update(buffer).digest("hex");
24984
24985
  };
24986
+ var byteLength = (value) => new TextEncoder().encode(value).byteLength;
24987
+ var safeArtifactFileName = (artifact) => {
24988
+ const extension = artifact.contentType === "image/png" ? ".png" : artifact.contentType?.includes("markdown") ? ".md" : artifact.contentType?.includes("json") ? ".json" : "";
24989
+ return `${artifact.id.replace(/[^a-z0-9_.-]/gi, "-")}${extension}`;
24990
+ };
24985
24991
  var inferContentType = (artifact) => {
24986
24992
  if (artifact.contentType) {
24987
24993
  return artifact.contentType;
@@ -25310,10 +25316,108 @@ var buildVoiceObservabilityArtifactIndex = (report) => {
25310
25316
  }
25311
25317
  };
25312
25318
  };
25319
+ var deliverVoiceObservabilityExport = async (options) => {
25320
+ const checkedAt = Date.now();
25321
+ const runId = options.runId ?? new Date(checkedAt).toISOString().replaceAll(":", "-");
25322
+ const artifactIndex = buildVoiceObservabilityArtifactIndex(options.report);
25323
+ const manifest = `${JSON.stringify(options.report, null, 2)}
25324
+ `;
25325
+ const index = `${JSON.stringify(artifactIndex, null, 2)}
25326
+ `;
25327
+ const destinations = await Promise.all(options.destinations.map(async (destination) => {
25328
+ const destinationId = destination.id ?? `${destination.kind}-${destination.label ?? "export"}`;
25329
+ const label = destination.label ?? (destination.kind === "file" ? "File observability export" : "Webhook observability export");
25330
+ try {
25331
+ if (destination.kind === "file") {
25332
+ const target = join3(destination.directory, runId);
25333
+ await mkdir4(join3(target, "artifacts"), { recursive: true });
25334
+ await Bun.write(join3(target, "manifest.json"), manifest);
25335
+ await Bun.write(join3(target, "artifact-index.json"), index);
25336
+ if (destination.includeArtifacts !== false) {
25337
+ for (const artifact of options.report.artifacts) {
25338
+ if (!artifact.path) {
25339
+ continue;
25340
+ }
25341
+ await Bun.write(join3(target, "artifacts", safeArtifactFileName(artifact)), await readFile2(stripArtifactPathAnchor(artifact.path)));
25342
+ }
25343
+ }
25344
+ return {
25345
+ artifactCount: destination.includeArtifacts === false ? 0 : options.report.artifacts.filter((artifact) => artifact.path).length,
25346
+ deliveredAt: Date.now(),
25347
+ destinationId,
25348
+ destinationKind: destination.kind,
25349
+ label,
25350
+ manifestBytes: byteLength(manifest),
25351
+ status: "delivered",
25352
+ target
25353
+ };
25354
+ }
25355
+ const controller = new AbortController;
25356
+ const timeout = setTimeout(() => controller.abort(), destination.timeoutMs ?? 1e4);
25357
+ try {
25358
+ const response = await (destination.fetch ?? fetch)(destination.url, {
25359
+ body: JSON.stringify({
25360
+ artifactIndex,
25361
+ artifacts: destination.includeArtifacts === false ? [] : options.report.artifacts,
25362
+ manifest: options.report,
25363
+ runId,
25364
+ source: "absolutejs-voice"
25365
+ }),
25366
+ headers: {
25367
+ "content-type": "application/json",
25368
+ ...destination.headers ?? {}
25369
+ },
25370
+ method: "POST",
25371
+ signal: controller.signal
25372
+ });
25373
+ if (!response.ok) {
25374
+ throw new Error(`Webhook returned HTTP ${response.status}`);
25375
+ }
25376
+ } finally {
25377
+ clearTimeout(timeout);
25378
+ }
25379
+ return {
25380
+ artifactCount: destination.includeArtifacts === false ? 0 : options.report.artifacts.length,
25381
+ deliveredAt: Date.now(),
25382
+ destinationId,
25383
+ destinationKind: destination.kind,
25384
+ label,
25385
+ manifestBytes: byteLength(manifest),
25386
+ status: "delivered",
25387
+ target: destination.url
25388
+ };
25389
+ } catch (error) {
25390
+ return {
25391
+ artifactCount: 0,
25392
+ deliveredAt: Date.now(),
25393
+ destinationId,
25394
+ destinationKind: destination.kind,
25395
+ error: error instanceof Error ? error.message : String(error),
25396
+ label,
25397
+ manifestBytes: byteLength(manifest),
25398
+ status: "failed",
25399
+ target: destination.kind === "file" ? destination.directory : destination.url
25400
+ };
25401
+ }
25402
+ }));
25403
+ const failed = destinations.filter((destination) => destination.status === "failed").length;
25404
+ return {
25405
+ checkedAt,
25406
+ destinations,
25407
+ exportStatus: options.report.status,
25408
+ status: failed > 0 || options.report.status === "fail" ? "fail" : options.report.status === "warn" ? "warn" : "pass",
25409
+ summary: {
25410
+ delivered: destinations.length - failed,
25411
+ failed,
25412
+ total: destinations.length
25413
+ }
25414
+ };
25415
+ };
25313
25416
  var createVoiceObservabilityExportRoutes = (options = {}) => {
25314
25417
  const path = options.path ?? "/api/voice/observability-export";
25315
25418
  const artifactIndexPath = options.artifactIndexPath ?? `${path}/artifacts`;
25316
25419
  const artifactDownloadPath = options.artifactDownloadPath ?? `${path}/artifacts`;
25420
+ const deliveryPath = options.deliveryPath ?? `${path}/deliveries`;
25317
25421
  const markdownPath = options.markdownPath ?? "/voice/observability-export.md";
25318
25422
  const htmlPath = options.htmlPath ?? "/voice/observability-export";
25319
25423
  const headers = {
@@ -25363,6 +25467,12 @@ var createVoiceObservabilityExportRoutes = (options = {}) => {
25363
25467
  }
25364
25468
  });
25365
25469
  }
25470
+ if (deliveryPath !== false && options.deliveryDestinations) {
25471
+ app.post(deliveryPath, async () => Response.json(await deliverVoiceObservabilityExport({
25472
+ destinations: options.deliveryDestinations ?? [],
25473
+ report: await buildReport()
25474
+ }), { headers }));
25475
+ }
25366
25476
  if (markdownPath !== false) {
25367
25477
  app.get(markdownPath, async () => {
25368
25478
  const report = await buildReport();
@@ -27157,7 +27267,7 @@ var createVoiceOpsRuntime = (config) => {
27157
27267
  pollIntervalMs: _pollIntervalMs,
27158
27268
  backoffMs,
27159
27269
  eventTypes,
27160
- fetch,
27270
+ fetch: fetch2,
27161
27271
  headers,
27162
27272
  retries,
27163
27273
  signingSecret,
@@ -27171,7 +27281,7 @@ var createVoiceOpsRuntime = (config) => {
27171
27281
  webhook: {
27172
27282
  backoffMs,
27173
27283
  eventTypes,
27174
- fetch,
27284
+ fetch: fetch2,
27175
27285
  headers,
27176
27286
  retries,
27177
27287
  signingSecret,
@@ -28113,6 +28223,7 @@ export {
28113
28223
  evaluateVoiceProviderStackGaps,
28114
28224
  encodeTwilioMulawBase64,
28115
28225
  deliverVoiceTraceEventsToSinks,
28226
+ deliverVoiceObservabilityExport,
28116
28227
  deliverVoiceIntegrationEventToSinks,
28117
28228
  deliverVoiceIntegrationEvent,
28118
28229
  deliverVoiceHandoffDelivery,
@@ -107,6 +107,49 @@ export type VoiceObservabilityExportArtifactIndex = {
107
107
  warn: number;
108
108
  };
109
109
  };
110
+ export type VoiceObservabilityExportDeliveryDestination = {
111
+ directory: string;
112
+ id?: string;
113
+ includeArtifacts?: boolean;
114
+ kind: 'file';
115
+ label?: string;
116
+ } | {
117
+ fetch?: typeof fetch;
118
+ headers?: Record<string, string>;
119
+ id?: string;
120
+ includeArtifacts?: boolean;
121
+ kind: 'webhook';
122
+ label?: string;
123
+ timeoutMs?: number;
124
+ url: string;
125
+ };
126
+ export type VoiceObservabilityExportDeliveryDestinationResult = {
127
+ artifactCount: number;
128
+ deliveredAt: number;
129
+ destinationId: string;
130
+ destinationKind: VoiceObservabilityExportDeliveryDestination['kind'];
131
+ error?: string;
132
+ label: string;
133
+ manifestBytes: number;
134
+ status: 'delivered' | 'failed';
135
+ target: string;
136
+ };
137
+ export type VoiceObservabilityExportDeliveryReport = {
138
+ checkedAt: number;
139
+ destinations: VoiceObservabilityExportDeliveryDestinationResult[];
140
+ exportStatus: VoiceObservabilityExportStatus;
141
+ status: VoiceObservabilityExportStatus;
142
+ summary: {
143
+ delivered: number;
144
+ failed: number;
145
+ total: number;
146
+ };
147
+ };
148
+ export type VoiceObservabilityExportDeliveryOptions = {
149
+ destinations: VoiceObservabilityExportDeliveryDestination[];
150
+ report: VoiceObservabilityExportReport;
151
+ runId?: string;
152
+ };
110
153
  export type VoiceObservabilityExportOptions = {
111
154
  artifacts?: VoiceObservabilityExportArtifact[];
112
155
  artifactIntegrity?: {
@@ -134,6 +177,8 @@ export type VoiceObservabilityExportRoutesOptions = VoiceObservabilityExportOpti
134
177
  headers?: HeadersInit;
135
178
  artifactDownloadPath?: false | string;
136
179
  artifactIndexPath?: false | string;
180
+ deliveryDestinations?: VoiceObservabilityExportDeliveryDestination[];
181
+ deliveryPath?: false | string;
137
182
  htmlPath?: false | string;
138
183
  markdownPath?: false | string;
139
184
  name?: string;
@@ -146,6 +191,7 @@ export declare const renderVoiceObservabilityExportMarkdown: (report: VoiceObser
146
191
  title?: string;
147
192
  }) => string;
148
193
  export declare const buildVoiceObservabilityArtifactIndex: (report: VoiceObservabilityExportReport) => VoiceObservabilityExportArtifactIndex;
194
+ export declare const deliverVoiceObservabilityExport: (options: VoiceObservabilityExportDeliveryOptions) => Promise<VoiceObservabilityExportDeliveryReport>;
149
195
  export declare const createVoiceObservabilityExportRoutes: (options?: VoiceObservabilityExportRoutesOptions) => Elysia<"", {
150
196
  decorator: {};
151
197
  store: {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.222",
3
+ "version": "0.0.22-beta.223",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",