@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 +9 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +198 -3
- package/dist/observabilityExport.d.ts +54 -1
- package/package.json +1 -1
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(
|
|
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: {};
|