@absolutejs/voice 0.0.22-beta.223 → 0.0.22-beta.224
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 +7 -1
- package/dist/index.js +53 -1
- package/dist/observabilityExport.d.ts +10 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2761,6 +2761,12 @@ app.use(
|
|
|
2761
2761
|
directory: '.voice-runtime/observability-exports',
|
|
2762
2762
|
kind: 'file',
|
|
2763
2763
|
label: 'Local customer-owned observability archive'
|
|
2764
|
+
},
|
|
2765
|
+
{
|
|
2766
|
+
bucket: process.env.VOICE_OBSERVABILITY_EXPORT_S3_BUCKET,
|
|
2767
|
+
keyPrefix: 'voice/observability-exports',
|
|
2768
|
+
kind: 's3',
|
|
2769
|
+
label: 'S3 customer-owned observability archive'
|
|
2764
2770
|
}
|
|
2765
2771
|
],
|
|
2766
2772
|
artifacts: [
|
|
@@ -2798,7 +2804,7 @@ const exportReport = await buildVoiceObservabilityExport({
|
|
|
2798
2804
|
});
|
|
2799
2805
|
```
|
|
2800
2806
|
|
|
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.
|
|
2807
|
+
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; S3 delivery writes the same manifest, index, and artifact files through Bun's native S3 client. 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.
|
|
2802
2808
|
|
|
2803
2809
|
Pass the same report into production readiness when export health should block deploys:
|
|
2804
2810
|
|
package/dist/index.js
CHANGED
|
@@ -24988,6 +24988,14 @@ var safeArtifactFileName = (artifact) => {
|
|
|
24988
24988
|
const extension = artifact.contentType === "image/png" ? ".png" : artifact.contentType?.includes("markdown") ? ".md" : artifact.contentType?.includes("json") ? ".json" : "";
|
|
24989
24989
|
return `${artifact.id.replace(/[^a-z0-9_.-]/gi, "-")}${extension}`;
|
|
24990
24990
|
};
|
|
24991
|
+
var normalizeExportS3KeyPrefix = (prefix) => prefix?.trim().replace(/^\/+|\/+$/g, "") ?? "voice/observability-exports";
|
|
24992
|
+
var joinS3Key = (...parts) => parts.map((part) => part.trim().replace(/^\/+|\/+$/g, "")).filter(Boolean).join("/");
|
|
24993
|
+
var writeS3Object = async (input) => {
|
|
24994
|
+
const file = input.client.file(input.key, input.options);
|
|
24995
|
+
await file.write(input.value, {
|
|
24996
|
+
type: input.contentType
|
|
24997
|
+
});
|
|
24998
|
+
};
|
|
24991
24999
|
var inferContentType = (artifact) => {
|
|
24992
25000
|
if (artifact.contentType) {
|
|
24993
25001
|
return artifact.contentType;
|
|
@@ -25352,6 +25360,50 @@ var deliverVoiceObservabilityExport = async (options) => {
|
|
|
25352
25360
|
target
|
|
25353
25361
|
};
|
|
25354
25362
|
}
|
|
25363
|
+
if (destination.kind === "s3") {
|
|
25364
|
+
const keyPrefix = normalizeExportS3KeyPrefix(destination.keyPrefix);
|
|
25365
|
+
const rootKey = joinS3Key(keyPrefix, runId);
|
|
25366
|
+
const client = destination.client ?? new Bun.S3Client(destination);
|
|
25367
|
+
const s3Options = destination;
|
|
25368
|
+
await writeS3Object({
|
|
25369
|
+
client,
|
|
25370
|
+
contentType: "application/json",
|
|
25371
|
+
key: joinS3Key(rootKey, "manifest.json"),
|
|
25372
|
+
options: s3Options,
|
|
25373
|
+
value: manifest
|
|
25374
|
+
});
|
|
25375
|
+
await writeS3Object({
|
|
25376
|
+
client,
|
|
25377
|
+
contentType: "application/json",
|
|
25378
|
+
key: joinS3Key(rootKey, "artifact-index.json"),
|
|
25379
|
+
options: s3Options,
|
|
25380
|
+
value: index
|
|
25381
|
+
});
|
|
25382
|
+
if (destination.includeArtifacts !== false) {
|
|
25383
|
+
for (const artifact of options.report.artifacts) {
|
|
25384
|
+
if (!artifact.path) {
|
|
25385
|
+
continue;
|
|
25386
|
+
}
|
|
25387
|
+
await writeS3Object({
|
|
25388
|
+
client,
|
|
25389
|
+
contentType: artifact.contentType ?? inferContentType(artifact),
|
|
25390
|
+
key: joinS3Key(rootKey, "artifacts", safeArtifactFileName(artifact)),
|
|
25391
|
+
options: s3Options,
|
|
25392
|
+
value: await readFile2(stripArtifactPathAnchor(artifact.path))
|
|
25393
|
+
});
|
|
25394
|
+
}
|
|
25395
|
+
}
|
|
25396
|
+
return {
|
|
25397
|
+
artifactCount: destination.includeArtifacts === false ? 0 : options.report.artifacts.filter((artifact) => artifact.path).length,
|
|
25398
|
+
deliveredAt: Date.now(),
|
|
25399
|
+
destinationId,
|
|
25400
|
+
destinationKind: destination.kind,
|
|
25401
|
+
label,
|
|
25402
|
+
manifestBytes: byteLength(manifest),
|
|
25403
|
+
status: "delivered",
|
|
25404
|
+
target: destination.bucket ? `s3://${destination.bucket}/${rootKey}` : rootKey
|
|
25405
|
+
};
|
|
25406
|
+
}
|
|
25355
25407
|
const controller = new AbortController;
|
|
25356
25408
|
const timeout = setTimeout(() => controller.abort(), destination.timeoutMs ?? 1e4);
|
|
25357
25409
|
try {
|
|
@@ -25396,7 +25448,7 @@ var deliverVoiceObservabilityExport = async (options) => {
|
|
|
25396
25448
|
label,
|
|
25397
25449
|
manifestBytes: byteLength(manifest),
|
|
25398
25450
|
status: "failed",
|
|
25399
|
-
target: destination.kind === "file" ? destination.directory : destination.url
|
|
25451
|
+
target: destination.kind === "file" ? destination.directory : destination.kind === "s3" ? destination.bucket ? `s3://${destination.bucket}/${normalizeExportS3KeyPrefix(destination.keyPrefix)}` : normalizeExportS3KeyPrefix(destination.keyPrefix) : destination.url
|
|
25400
25452
|
};
|
|
25401
25453
|
}
|
|
25402
25454
|
}));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Elysia } from 'elysia';
|
|
2
|
+
import type { S3Client, S3Options } from 'bun';
|
|
2
3
|
import { type VoiceAuditSinkDeliveryQueueSummary, type VoiceAuditSinkDeliveryRecord, type VoiceAuditSinkDeliveryStore } from './auditSinks';
|
|
3
4
|
import type { VoiceAuditEventStore, VoiceAuditEventType } from './audit';
|
|
4
5
|
import { type VoiceOperationsRecord } from './operationsRecord';
|
|
@@ -113,7 +114,15 @@ export type VoiceObservabilityExportDeliveryDestination = {
|
|
|
113
114
|
includeArtifacts?: boolean;
|
|
114
115
|
kind: 'file';
|
|
115
116
|
label?: string;
|
|
116
|
-
} | {
|
|
117
|
+
} | (S3Options & {
|
|
118
|
+
bucket?: string;
|
|
119
|
+
client?: Pick<S3Client, 'file'>;
|
|
120
|
+
id?: string;
|
|
121
|
+
includeArtifacts?: boolean;
|
|
122
|
+
keyPrefix?: string;
|
|
123
|
+
kind: 's3';
|
|
124
|
+
label?: string;
|
|
125
|
+
}) | {
|
|
117
126
|
fetch?: typeof fetch;
|
|
118
127
|
headers?: Record<string, string>;
|
|
119
128
|
id?: string;
|