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

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
@@ -2753,12 +2753,16 @@ import {
2753
2753
 
2754
2754
  app.use(
2755
2755
  createVoiceObservabilityExportRoutes({
2756
+ artifactIntegrity: {
2757
+ maxAgeMs: 15 * 60 * 1000
2758
+ },
2756
2759
  artifacts: [
2757
2760
  {
2758
2761
  id: 'latest-proof-pack',
2759
2762
  kind: 'proof-pack',
2760
2763
  label: 'Latest proof pack',
2761
- path: '.voice-runtime/proof-pack/latest.md'
2764
+ path: '.voice-runtime/proof-pack/latest.md',
2765
+ required: true
2762
2766
  }
2763
2767
  ],
2764
2768
  audit: runtimeStorage.audit,
@@ -2773,6 +2777,9 @@ app.use(
2773
2777
  );
2774
2778
 
2775
2779
  const exportReport = await buildVoiceObservabilityExport({
2780
+ artifactIntegrity: {
2781
+ maxAgeMs: 15 * 60 * 1000
2782
+ },
2776
2783
  audit: runtimeStorage.audit,
2777
2784
  auditDeliveries: runtimeStorage.auditDeliveries,
2778
2785
  links: {
@@ -2784,7 +2791,7 @@ const exportReport = await buildVoiceObservabilityExport({
2784
2791
  });
2785
2792
  ```
2786
2793
 
2787
- The route helper exposes JSON at `/api/voice/observability-export`, Markdown at `/voice/observability-export.md`, and HTML at `/voice/observability-export`. 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.
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.
2788
2795
 
2789
2796
  Pass the same report into production readiness when export health should block deploys:
2790
2797
 
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 { buildVoiceObservabilityExport, createVoiceObservabilityExportRoutes, renderVoiceObservabilityExportMarkdown } from './observabilityExport';
56
+ export { buildVoiceObservabilityArtifactIndex, buildVoiceObservabilityExport, createVoiceObservabilityExportRoutes, 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, VoiceObservabilityExportArtifactKind, VoiceObservabilityExportDeliverySummary, VoiceObservabilityExportEnvelope, VoiceObservabilityExportIssue, VoiceObservabilityExportIssueCode, VoiceObservabilityExportOptions, VoiceObservabilityExportRedactionSummary, VoiceObservabilityExportReport, VoiceObservabilityExportRoutesOptions, VoiceObservabilityExportStatus } from './observabilityExport';
121
+ export type { VoiceObservabilityExportArtifact, VoiceObservabilityExportArtifactChecksum, VoiceObservabilityExportArtifactFreshness, VoiceObservabilityExportArtifactIndex, VoiceObservabilityExportArtifactIndexItem, VoiceObservabilityExportArtifactKind, VoiceObservabilityExportDeliverySummary, 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
@@ -24942,6 +24942,8 @@ var createVoiceOperationsRecordRoutes = (options) => {
24942
24942
  };
24943
24943
  // src/observabilityExport.ts
24944
24944
  import { Elysia as Elysia42 } from "elysia";
24945
+ import { createHash } from "crypto";
24946
+ import { readFile as readFile2, stat } from "fs/promises";
24945
24947
  var isDeliveryStore = (value) => !Array.isArray(value) && typeof value.list === "function";
24946
24948
  var getString18 = (value) => typeof value === "string" ? value : undefined;
24947
24949
  var getProviderKind = (payload) => getString18(payload.kind) ?? getString18(payload.providerKind);
@@ -24965,6 +24967,108 @@ var createOperationArtifact = (record, href) => ({
24965
24967
  status: record.status === "failed" ? "fail" : record.status === "warning" ? "warn" : "pass"
24966
24968
  });
24967
24969
  var unique2 = (values) => [...new Set(values)].sort();
24970
+ var stripArtifactPathAnchor = (path) => path.split("#")[0] ?? path;
24971
+ var toEpochMs = (value) => {
24972
+ if (typeof value === "number") {
24973
+ return Number.isFinite(value) ? value : undefined;
24974
+ }
24975
+ if (typeof value === "string") {
24976
+ const parsed = Date.parse(value);
24977
+ return Number.isFinite(parsed) ? parsed : undefined;
24978
+ }
24979
+ return;
24980
+ };
24981
+ var checksumFile = async (path) => {
24982
+ const buffer = await readFile2(path);
24983
+ return createHash("sha256").update(buffer).digest("hex");
24984
+ };
24985
+ var inferContentType = (artifact) => {
24986
+ if (artifact.contentType) {
24987
+ return artifact.contentType;
24988
+ }
24989
+ const path = artifact.path ? stripArtifactPathAnchor(artifact.path) : "";
24990
+ if (path.endsWith(".json")) {
24991
+ return "application/json; charset=utf-8";
24992
+ }
24993
+ if (path.endsWith(".md") || path.endsWith(".markdown")) {
24994
+ return "text/markdown; charset=utf-8";
24995
+ }
24996
+ if (path.endsWith(".html")) {
24997
+ return "text/html; charset=utf-8";
24998
+ }
24999
+ if (path.endsWith(".png")) {
25000
+ return "image/png";
25001
+ }
25002
+ if (path.endsWith(".jpg") || path.endsWith(".jpeg")) {
25003
+ return "image/jpeg";
25004
+ }
25005
+ if (path.endsWith(".txt") || path.endsWith(".log")) {
25006
+ return "text/plain; charset=utf-8";
25007
+ }
25008
+ return "application/octet-stream";
25009
+ };
25010
+ var addArtifactDownloadHrefs = (artifacts, links) => artifacts.map((artifact) => ({
25011
+ ...artifact,
25012
+ contentType: artifact.contentType ?? inferContentType(artifact),
25013
+ downloadHref: artifact.downloadHref ?? (artifact.path ? links?.artifactDownload?.(artifact) : undefined)
25014
+ }));
25015
+ var verifyArtifact = async (artifact, options) => {
25016
+ const now = options.now ?? Date.now();
25017
+ const maxAgeMs = artifact.maxAgeMs ?? options.maxAgeMs;
25018
+ if (!artifact.path) {
25019
+ return maxAgeMs === undefined ? artifact : {
25020
+ ...artifact,
25021
+ freshness: {
25022
+ checkedAt: now,
25023
+ generatedAt: artifact.generatedAt,
25024
+ maxAgeMs,
25025
+ status: artifact.status ?? "pass"
25026
+ }
25027
+ };
25028
+ }
25029
+ const filePath = stripArtifactPathAnchor(artifact.path);
25030
+ try {
25031
+ const file = await stat(filePath);
25032
+ const generatedAt = artifact.generatedAt ?? file.mtimeMs;
25033
+ const generatedAtMs = toEpochMs(generatedAt);
25034
+ const ageMs = generatedAtMs === undefined ? undefined : now - generatedAtMs;
25035
+ const isStale = maxAgeMs !== undefined && ageMs !== undefined && ageMs > maxAgeMs;
25036
+ const checksum = options.checksum === false ? artifact.checksum : {
25037
+ algorithm: "sha256",
25038
+ value: await checksumFile(filePath)
25039
+ };
25040
+ return {
25041
+ ...artifact,
25042
+ bytes: artifact.bytes ?? file.size,
25043
+ checksum,
25044
+ freshness: maxAgeMs === undefined && generatedAt === undefined ? artifact.freshness : {
25045
+ ageMs,
25046
+ checkedAt: now,
25047
+ generatedAt,
25048
+ maxAgeMs,
25049
+ status: isStale ? options.staleSeverity ?? "fail" : "pass"
25050
+ },
25051
+ generatedAt,
25052
+ status: isStale && (options.staleSeverity ?? "fail") === "fail" ? "fail" : artifact.status
25053
+ };
25054
+ } catch {
25055
+ const severity = artifact.required ? options.missingSeverity ?? "fail" : "warn";
25056
+ return {
25057
+ ...artifact,
25058
+ freshness: {
25059
+ checkedAt: now,
25060
+ generatedAt: artifact.generatedAt,
25061
+ maxAgeMs,
25062
+ status: severity
25063
+ },
25064
+ status: severity
25065
+ };
25066
+ }
25067
+ };
25068
+ var verifyArtifacts = (artifacts, options) => {
25069
+ const integrity = options ?? {};
25070
+ return Promise.all(artifacts.map((artifact) => verifyArtifact(artifact, integrity)));
25071
+ };
24968
25072
  var collectSessionIds = (input) => unique2([
24969
25073
  ...input.sessionIds ?? [],
24970
25074
  ...input.events.map((event) => event.sessionId),
@@ -24991,6 +25095,27 @@ var collectIssues = (input) => {
24991
25095
  value: failedOperationsRecords
24992
25096
  });
24993
25097
  }
25098
+ for (const artifact of input.artifacts) {
25099
+ if (artifact.path && artifact.status !== "pass" && artifact.required && artifact.bytes === undefined && artifact.freshness?.ageMs === undefined) {
25100
+ issues.push({
25101
+ code: "voice.observability.artifact_missing",
25102
+ detail: `${artifact.label} was required but could not be verified at ${artifact.path}.`,
25103
+ label: "Required artifact",
25104
+ severity: artifact.status === "fail" ? "fail" : "warn",
25105
+ value: artifact.id
25106
+ });
25107
+ continue;
25108
+ }
25109
+ if (artifact.freshness?.maxAgeMs !== undefined && artifact.freshness.ageMs !== undefined && artifact.freshness.ageMs > artifact.freshness.maxAgeMs) {
25110
+ issues.push({
25111
+ code: "voice.observability.artifact_stale",
25112
+ detail: `${artifact.label} is older than the configured freshness window.`,
25113
+ label: "Stale artifact",
25114
+ severity: artifact.freshness.status === "fail" ? "fail" : "warn",
25115
+ value: artifact.id
25116
+ });
25117
+ }
25118
+ }
24994
25119
  if ((input.auditDeliveries?.failed ?? 0) > 0) {
24995
25120
  issues.push({
24996
25121
  code: "voice.observability.audit_delivery_failed",
@@ -25076,7 +25201,7 @@ var buildVoiceObservabilityExport = async (options = {}) => {
25076
25201
  const traceDeliverySummary = traceDeliveries ? await summarizeVoiceTraceSinkDeliveries(traceDeliveries) : undefined;
25077
25202
  const auditDeliverySummary = auditDeliveries ? await summarizeVoiceAuditSinkDeliveries(auditDeliveries) : undefined;
25078
25203
  const operationArtifacts = operationsRecords.map((record) => createOperationArtifact(record, options.links?.operationsRecord?.(record.sessionId)));
25079
- const artifacts = [...operationArtifacts, ...options.artifacts ?? []];
25204
+ const artifacts = addArtifactDownloadHrefs(await verifyArtifacts([...operationArtifacts, ...options.artifacts ?? []], options.artifactIntegrity), options.links);
25080
25205
  const operationHrefBySessionId = new Map(sessionIds.map((sessionId) => [
25081
25206
  sessionId,
25082
25207
  options.links?.operationsRecord?.(sessionId)
@@ -25086,6 +25211,7 @@ var buildVoiceObservabilityExport = async (options = {}) => {
25086
25211
  ...auditEvents.map((event) => buildAuditEnvelope(event, event.sessionId ? operationHrefBySessionId.get(event.sessionId) : undefined))
25087
25212
  ].sort((left, right) => left.at - right.at);
25088
25213
  const issues = collectIssues({
25214
+ artifacts,
25089
25215
  auditDeliveries: auditDeliverySummary,
25090
25216
  operationsRecords,
25091
25217
  totalEvidence: events.length + auditEvents.length + operationsRecords.length + artifacts.length,
@@ -25121,7 +25247,7 @@ var renderVoiceObservabilityExportMarkdown = (report, options = {}) => {
25121
25247
  const title = options.title ?? "Voice Observability Export";
25122
25248
  const issues = report.issues.map((issue) => `- ${issue.severity}: ${issue.label}${issue.value !== undefined ? ` (${issue.value})` : ""}${issue.detail ? ` - ${issue.detail}` : ""}`).join(`
25123
25249
  `) || "No observability export issues.";
25124
- const artifacts = report.artifacts.map((artifact) => `- ${artifact.label}: ${artifact.kind}${artifact.href ? ` (${artifact.href})` : ""}${artifact.status ? ` - ${artifact.status}` : ""}`).join(`
25250
+ const artifacts = report.artifacts.map((artifact) => `- ${artifact.label}: ${artifact.kind}${artifact.href ? ` (${artifact.href})` : ""}${artifact.status ? ` - ${artifact.status}` : ""}${artifact.bytes !== undefined ? `, ${artifact.bytes} bytes` : ""}${artifact.checksum ? `, sha256 ${artifact.checksum.value}` : ""}${artifact.freshness?.ageMs !== undefined ? `, age ${Math.round(artifact.freshness.ageMs)}ms` : ""}`).join(`
25125
25251
  `) || "No artifacts attached.";
25126
25252
  return `# ${title}
25127
25253
 
@@ -25156,19 +25282,87 @@ ${artifacts}
25156
25282
  ${issues}
25157
25283
  `;
25158
25284
  };
25285
+ var buildVoiceObservabilityArtifactIndex = (report) => {
25286
+ const artifacts = report.artifacts.map((artifact) => ({
25287
+ bytes: artifact.bytes,
25288
+ checksum: artifact.checksum,
25289
+ contentType: artifact.contentType,
25290
+ downloadHref: artifact.downloadHref,
25291
+ freshness: artifact.freshness,
25292
+ href: artifact.href,
25293
+ id: artifact.id,
25294
+ kind: artifact.kind,
25295
+ label: artifact.label,
25296
+ required: artifact.required,
25297
+ sessionId: artifact.sessionId,
25298
+ status: artifact.status
25299
+ }));
25300
+ return {
25301
+ artifacts,
25302
+ checkedAt: report.checkedAt,
25303
+ status: report.status,
25304
+ summary: {
25305
+ downloadable: artifacts.filter((artifact) => artifact.downloadHref).length,
25306
+ failed: artifacts.filter((artifact) => artifact.status === "fail").length,
25307
+ required: artifacts.filter((artifact) => artifact.required).length,
25308
+ total: artifacts.length,
25309
+ warn: artifacts.filter((artifact) => artifact.status === "warn").length
25310
+ }
25311
+ };
25312
+ };
25159
25313
  var createVoiceObservabilityExportRoutes = (options = {}) => {
25160
25314
  const path = options.path ?? "/api/voice/observability-export";
25315
+ const artifactIndexPath = options.artifactIndexPath ?? `${path}/artifacts`;
25316
+ const artifactDownloadPath = options.artifactDownloadPath ?? `${path}/artifacts`;
25161
25317
  const markdownPath = options.markdownPath ?? "/voice/observability-export.md";
25162
25318
  const htmlPath = options.htmlPath ?? "/voice/observability-export";
25163
25319
  const headers = {
25164
25320
  "cache-control": "no-store",
25165
25321
  ...options.headers ?? {}
25166
25322
  };
25167
- const buildReport = () => buildVoiceObservabilityExport(options);
25323
+ const buildReport = () => buildVoiceObservabilityExport({
25324
+ ...options,
25325
+ links: {
25326
+ ...options.links,
25327
+ artifactDownload: options.links?.artifactDownload ?? (artifactDownloadPath ? (artifact) => `${artifactDownloadPath}/${encodeURIComponent(artifact.id)}` : undefined)
25328
+ }
25329
+ });
25168
25330
  const app = new Elysia42({
25169
25331
  name: options.name ?? "absolute-voice-observability-export"
25170
25332
  });
25171
25333
  app.get(path, async () => Response.json(await buildReport(), { headers }));
25334
+ if (artifactIndexPath !== false) {
25335
+ app.get(artifactIndexPath, async () => Response.json(buildVoiceObservabilityArtifactIndex(await buildReport()), { headers }));
25336
+ }
25337
+ if (artifactDownloadPath !== false) {
25338
+ app.get(`${artifactDownloadPath}/:artifactId`, async ({ params }) => {
25339
+ const artifactId = decodeURIComponent(params.artifactId);
25340
+ const report = await buildReport();
25341
+ const artifact = report.artifacts.find((item) => item.id === artifactId);
25342
+ if (!artifact?.path) {
25343
+ return Response.json({ error: "Artifact is not downloadable.", artifactId }, { headers, status: 404 });
25344
+ }
25345
+ try {
25346
+ const body = await readFile2(stripArtifactPathAnchor(artifact.path));
25347
+ return new Response(body, {
25348
+ headers: {
25349
+ ...headers,
25350
+ "content-disposition": `attachment; filename="${encodeURIComponent(artifact.id)}"`,
25351
+ "content-type": artifact.contentType ?? inferContentType(artifact),
25352
+ ...artifact.checksum ? {
25353
+ "x-absolute-voice-artifact-sha256": artifact.checksum.value
25354
+ } : {},
25355
+ "x-absolute-voice-artifact-id": artifact.id,
25356
+ ...artifact.freshness ? {
25357
+ "x-absolute-voice-artifact-freshness": artifact.freshness.status
25358
+ } : {}
25359
+ }
25360
+ });
25361
+ } catch {
25362
+ return Response.json({ error: "Artifact file is not available.", artifactId }, { headers, status: 404 });
25363
+ }
25364
+ });
25365
+ }
25172
25366
  if (markdownPath !== false) {
25173
25367
  app.get(markdownPath, async () => {
25174
25368
  const report = await buildReport();
@@ -28195,6 +28389,7 @@ export {
28195
28389
  buildVoiceOpsActionHistoryReport,
28196
28390
  buildVoiceOperationsRecord,
28197
28391
  buildVoiceObservabilityExport,
28392
+ buildVoiceObservabilityArtifactIndex,
28198
28393
  buildVoiceLiveOpsControlState,
28199
28394
  buildVoiceLatencySLOGate,
28200
28395
  buildVoiceIncidentBundle,
@@ -6,15 +6,31 @@ import { type VoiceTraceSinkDeliveryQueueSummary } from './queue';
6
6
  import { type StoredVoiceTraceEvent, type VoiceTraceEventStore, type VoiceTraceEventType, type VoiceTraceRedactionConfig, type VoiceTraceSinkDeliveryRecord, type VoiceTraceSinkDeliveryStore, type VoiceTraceSummary } from './trace';
7
7
  export type VoiceObservabilityExportStatus = 'fail' | 'pass' | 'warn';
8
8
  export type VoiceObservabilityExportArtifactKind = 'incident' | 'markdown' | 'operations-record' | 'proof-pack' | 'readiness' | 'screenshot' | 'slo' | 'trace' | 'audit' | 'custom';
9
+ export type VoiceObservabilityExportArtifactChecksum = {
10
+ algorithm: 'sha256';
11
+ value: string;
12
+ };
13
+ export type VoiceObservabilityExportArtifactFreshness = {
14
+ ageMs?: number;
15
+ checkedAt: number;
16
+ generatedAt?: number | string;
17
+ maxAgeMs?: number;
18
+ status: VoiceObservabilityExportStatus;
19
+ };
9
20
  export type VoiceObservabilityExportArtifact = {
10
21
  bytes?: number;
22
+ checksum?: VoiceObservabilityExportArtifactChecksum;
11
23
  contentType?: string;
24
+ downloadHref?: string;
25
+ freshness?: VoiceObservabilityExportArtifactFreshness;
12
26
  generatedAt?: number | string;
13
27
  href?: string;
14
28
  id: string;
15
29
  kind: VoiceObservabilityExportArtifactKind;
16
30
  label: string;
31
+ maxAgeMs?: number;
17
32
  path?: string;
33
+ required?: boolean;
18
34
  sessionId?: string;
19
35
  status?: VoiceObservabilityExportStatus;
20
36
  };
@@ -31,7 +47,7 @@ export type VoiceObservabilityExportEnvelope = {
31
47
  severity: VoiceObservabilityExportStatus;
32
48
  traceId?: string;
33
49
  };
34
- export type VoiceObservabilityExportIssueCode = 'voice.observability.no_evidence' | 'voice.observability.operation_failed' | 'voice.observability.audit_delivery_failed' | 'voice.observability.audit_delivery_pending' | 'voice.observability.trace_delivery_failed' | 'voice.observability.trace_delivery_pending';
50
+ export type VoiceObservabilityExportIssueCode = 'voice.observability.no_evidence' | 'voice.observability.operation_failed' | 'voice.observability.artifact_missing' | 'voice.observability.artifact_stale' | 'voice.observability.audit_delivery_failed' | 'voice.observability.audit_delivery_pending' | 'voice.observability.trace_delivery_failed' | 'voice.observability.trace_delivery_pending';
35
51
  export type VoiceObservabilityExportIssue = {
36
52
  code: VoiceObservabilityExportIssueCode;
37
53
  detail?: string;
@@ -65,13 +81,47 @@ export type VoiceObservabilityExportReport = {
65
81
  traceEvents: number;
66
82
  };
67
83
  };
84
+ export type VoiceObservabilityExportArtifactIndexItem = {
85
+ bytes?: number;
86
+ checksum?: VoiceObservabilityExportArtifactChecksum;
87
+ contentType?: string;
88
+ downloadHref?: string;
89
+ freshness?: VoiceObservabilityExportArtifactFreshness;
90
+ href?: string;
91
+ id: string;
92
+ kind: VoiceObservabilityExportArtifactKind;
93
+ label: string;
94
+ required?: boolean;
95
+ sessionId?: string;
96
+ status?: VoiceObservabilityExportStatus;
97
+ };
98
+ export type VoiceObservabilityExportArtifactIndex = {
99
+ artifacts: VoiceObservabilityExportArtifactIndexItem[];
100
+ checkedAt: number;
101
+ status: VoiceObservabilityExportStatus;
102
+ summary: {
103
+ downloadable: number;
104
+ failed: number;
105
+ required: number;
106
+ total: number;
107
+ warn: number;
108
+ };
109
+ };
68
110
  export type VoiceObservabilityExportOptions = {
69
111
  artifacts?: VoiceObservabilityExportArtifact[];
112
+ artifactIntegrity?: {
113
+ checksum?: false | 'sha256';
114
+ maxAgeMs?: number;
115
+ missingSeverity?: Exclude<VoiceObservabilityExportStatus, 'pass'>;
116
+ now?: number;
117
+ staleSeverity?: Exclude<VoiceObservabilityExportStatus, 'pass'>;
118
+ };
70
119
  audit?: VoiceAuditEventStore;
71
120
  auditDeliveries?: VoiceAuditSinkDeliveryRecord[] | VoiceAuditSinkDeliveryStore;
72
121
  events?: StoredVoiceTraceEvent[];
73
122
  includeOperationsRecords?: boolean;
74
123
  links?: {
124
+ artifactDownload?: (artifact: VoiceObservabilityExportArtifact) => string;
75
125
  operationsRecord?: (sessionId: string) => string;
76
126
  };
77
127
  operationsRecords?: VoiceOperationsRecord[];
@@ -82,6 +132,8 @@ export type VoiceObservabilityExportOptions = {
82
132
  };
83
133
  export type VoiceObservabilityExportRoutesOptions = VoiceObservabilityExportOptions & {
84
134
  headers?: HeadersInit;
135
+ artifactDownloadPath?: false | string;
136
+ artifactIndexPath?: false | string;
85
137
  htmlPath?: false | string;
86
138
  markdownPath?: false | string;
87
139
  name?: string;
@@ -93,6 +145,7 @@ export declare const buildVoiceObservabilityExport: (options?: VoiceObservabilit
93
145
  export declare const renderVoiceObservabilityExportMarkdown: (report: VoiceObservabilityExportReport, options?: {
94
146
  title?: string;
95
147
  }) => string;
148
+ export declare const buildVoiceObservabilityArtifactIndex: (report: VoiceObservabilityExportReport) => VoiceObservabilityExportArtifactIndex;
96
149
  export declare const createVoiceObservabilityExportRoutes: (options?: VoiceObservabilityExportRoutesOptions) => Elysia<"", {
97
150
  decorator: {};
98
151
  store: {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.220",
3
+ "version": "0.0.22-beta.222",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",