@absolutejs/voice 0.0.22-beta.227 → 0.0.22-beta.228

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
@@ -2781,6 +2781,19 @@ app.use(
2781
2781
  keyPrefix: 'voice/observability-exports',
2782
2782
  kind: 's3',
2783
2783
  label: 'S3 customer-owned observability archive'
2784
+ },
2785
+ {
2786
+ kind: 'sqlite',
2787
+ path: '.voice-runtime/observability-exports.sqlite',
2788
+ tableName: 'voice_observability_exports',
2789
+ label: 'SQLite customer-owned observability warehouse'
2790
+ },
2791
+ {
2792
+ connectionString: process.env.VOICE_OBSERVABILITY_EXPORT_POSTGRES_URL,
2793
+ kind: 'postgres',
2794
+ schemaName: 'voice',
2795
+ tableName: 'observability_exports',
2796
+ label: 'Postgres customer-owned observability warehouse'
2784
2797
  }
2785
2798
  ],
2786
2799
  deliveryReceipts: observabilityReceipts,
@@ -2819,7 +2832,7 @@ const exportReport = await buildVoiceObservabilityExport({
2819
2832
  });
2820
2833
  ```
2821
2834
 
2822
- 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`, delivery history at `GET /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. Delivery receipt stores persist run id, destinations, status, and target history so operators can prove exports have been continuously healthy. 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.
2835
+ 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`, delivery history at `GET /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; SQLite and Postgres delivery persist the manifest, artifact index, checksum metadata, status, run id, and timestamps into buyer-owned database tables. Delivery receipt stores persist run id, destinations, status, and target history so operators can prove exports have been continuously healthy. 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.
2823
2836
 
2824
2837
  Pass the same report into production readiness when export health should block deploys:
2825
2838
 
package/dist/index.js CHANGED
@@ -22448,6 +22448,7 @@ var createVoiceOpsRecoveryRoutes = (options = {}) => {
22448
22448
 
22449
22449
  // src/observabilityExport.ts
22450
22450
  import { Elysia as Elysia39 } from "elysia";
22451
+ import { Database } from "bun:sqlite";
22451
22452
  import { createHash } from "crypto";
22452
22453
  import { mkdir as mkdir4, readFile as readFile2, stat, unlink } from "fs/promises";
22453
22454
  import { join as join3 } from "path";
@@ -23038,6 +23039,116 @@ var writeS3Object = async (input) => {
23038
23039
  type: input.contentType
23039
23040
  });
23040
23041
  };
23042
+ var quoteObservabilityIdentifier = (value) => `"${value.replace(/"/g, '""')}"`;
23043
+ var normalizeObservabilityIdentifier = (value) => value?.trim().replace(/[^a-zA-Z0-9_]+/g, "_").replace(/^_+|_+$/g, "") || "voice_observability_exports";
23044
+ var buildObservabilityExportDatabaseRecord = (input) => ({
23045
+ artifactCount: input.artifactIndex.summary.total,
23046
+ artifactIndex: input.artifactIndex,
23047
+ checkedAt: input.checkedAt,
23048
+ exportStatus: input.report.status,
23049
+ manifest: input.report,
23050
+ runId: input.runId,
23051
+ status: input.report.status
23052
+ });
23053
+ var deliverObservabilityExportToSQLite = async (input) => {
23054
+ if (!input.destination.database && !input.destination.path) {
23055
+ throw new Error("SQLite observability export delivery requires destination.database or destination.path.");
23056
+ }
23057
+ const database = input.destination.database ?? new Database(input.destination.path, { create: true });
23058
+ const table = quoteObservabilityIdentifier(normalizeObservabilityIdentifier(input.destination.tableName));
23059
+ const record = buildObservabilityExportDatabaseRecord(input);
23060
+ database.exec(`CREATE TABLE IF NOT EXISTS ${table} (
23061
+ run_id TEXT PRIMARY KEY,
23062
+ checked_at INTEGER NOT NULL,
23063
+ status TEXT NOT NULL,
23064
+ export_status TEXT NOT NULL,
23065
+ artifact_count INTEGER NOT NULL,
23066
+ manifest_json TEXT NOT NULL,
23067
+ artifact_index_json TEXT NOT NULL,
23068
+ payload_json TEXT NOT NULL
23069
+ )`);
23070
+ database.query(`INSERT INTO ${table}
23071
+ (run_id, checked_at, status, export_status, artifact_count, manifest_json, artifact_index_json, payload_json)
23072
+ VALUES ($runId, $checkedAt, $status, $exportStatus, $artifactCount, $manifest, $artifactIndex, $payload)
23073
+ ON CONFLICT(run_id) DO UPDATE SET
23074
+ checked_at = excluded.checked_at,
23075
+ status = excluded.status,
23076
+ export_status = excluded.export_status,
23077
+ artifact_count = excluded.artifact_count,
23078
+ manifest_json = excluded.manifest_json,
23079
+ artifact_index_json = excluded.artifact_index_json,
23080
+ payload_json = excluded.payload_json`).run({
23081
+ $artifactCount: input.artifactIndex.summary.total,
23082
+ $artifactIndex: input.index,
23083
+ $checkedAt: input.checkedAt,
23084
+ $exportStatus: input.report.status,
23085
+ $manifest: input.manifest,
23086
+ $payload: JSON.stringify(record),
23087
+ $runId: input.runId,
23088
+ $status: input.report.status
23089
+ });
23090
+ return input.destination.path ? `sqlite://${input.destination.path}/${table.replaceAll('"', "")}` : `sqlite://memory/${table.replaceAll('"', "")}`;
23091
+ };
23092
+ var deliverObservabilityExportToPostgres = async (input) => {
23093
+ const sql = input.destination.sql ?? (input.destination.connectionString ? (() => {
23094
+ const client = new Bun.SQL(input.destination.connectionString);
23095
+ return { unsafe: client.unsafe.bind(client) };
23096
+ })() : undefined);
23097
+ if (!sql) {
23098
+ throw new Error("Postgres observability export delivery requires destination.sql or destination.connectionString.");
23099
+ }
23100
+ const schema = normalizeObservabilityIdentifier(input.destination.schemaName ?? "public");
23101
+ const table = normalizeObservabilityIdentifier(input.destination.tableName);
23102
+ const qualifiedTable = `${quoteObservabilityIdentifier(schema)}.${quoteObservabilityIdentifier(table)}`;
23103
+ const record = buildObservabilityExportDatabaseRecord(input);
23104
+ await sql.unsafe(`CREATE SCHEMA IF NOT EXISTS ${quoteObservabilityIdentifier(schema)}`);
23105
+ await sql.unsafe(`CREATE TABLE IF NOT EXISTS ${qualifiedTable} (
23106
+ run_id TEXT PRIMARY KEY,
23107
+ checked_at BIGINT NOT NULL,
23108
+ status TEXT NOT NULL,
23109
+ export_status TEXT NOT NULL,
23110
+ artifact_count INTEGER NOT NULL,
23111
+ manifest_json JSONB NOT NULL,
23112
+ artifact_index_json JSONB NOT NULL,
23113
+ payload JSONB NOT NULL
23114
+ )`);
23115
+ await sql.unsafe(`INSERT INTO ${qualifiedTable}
23116
+ (run_id, checked_at, status, export_status, artifact_count, manifest_json, artifact_index_json, payload)
23117
+ VALUES ($1, $2, $3, $4, $5, $6::jsonb, $7::jsonb, $8::jsonb)
23118
+ ON CONFLICT (run_id) DO UPDATE SET
23119
+ checked_at = EXCLUDED.checked_at,
23120
+ status = EXCLUDED.status,
23121
+ export_status = EXCLUDED.export_status,
23122
+ artifact_count = EXCLUDED.artifact_count,
23123
+ manifest_json = EXCLUDED.manifest_json,
23124
+ artifact_index_json = EXCLUDED.artifact_index_json,
23125
+ payload = EXCLUDED.payload`, [
23126
+ input.runId,
23127
+ input.checkedAt,
23128
+ input.report.status,
23129
+ input.report.status,
23130
+ input.artifactIndex.summary.total,
23131
+ input.manifest,
23132
+ input.index,
23133
+ JSON.stringify(record)
23134
+ ]);
23135
+ return `postgres://${schema}/${table}`;
23136
+ };
23137
+ var observabilityExportDeliveryFailureTarget = (destination) => {
23138
+ if (destination.kind === "file") {
23139
+ return destination.directory;
23140
+ }
23141
+ if (destination.kind === "s3") {
23142
+ return destination.bucket ? `s3://${destination.bucket}/${normalizeExportS3KeyPrefix(destination.keyPrefix)}` : normalizeExportS3KeyPrefix(destination.keyPrefix);
23143
+ }
23144
+ if (destination.kind === "sqlite") {
23145
+ return destination.path ?? "sqlite://memory";
23146
+ }
23147
+ if (destination.kind === "postgres") {
23148
+ return destination.connectionString ?? "postgres://configured-client";
23149
+ }
23150
+ return destination.url;
23151
+ };
23041
23152
  var createVoiceMemoryObservabilityExportDeliveryReceiptStore = () => {
23042
23153
  const receipts = new Map;
23043
23154
  return {
@@ -23437,7 +23548,7 @@ var deliverVoiceObservabilityExport = async (options) => {
23437
23548
  `;
23438
23549
  const destinations = await Promise.all(options.destinations.map(async (destination) => {
23439
23550
  const destinationId = destination.id ?? `${destination.kind}-${destination.label ?? "export"}`;
23440
- const label = destination.label ?? (destination.kind === "file" ? "File observability export" : "Webhook observability export");
23551
+ const label = destination.label ?? (destination.kind === "file" ? "File observability export" : destination.kind === "s3" ? "S3 observability export" : destination.kind === "sqlite" ? "SQLite observability export" : destination.kind === "postgres" ? "Postgres observability export" : "Webhook observability export");
23441
23552
  try {
23442
23553
  if (destination.kind === "file") {
23443
23554
  const target = join3(destination.directory, runId);
@@ -23507,6 +23618,48 @@ var deliverVoiceObservabilityExport = async (options) => {
23507
23618
  target: destination.bucket ? `s3://${destination.bucket}/${rootKey}` : rootKey
23508
23619
  };
23509
23620
  }
23621
+ if (destination.kind === "sqlite") {
23622
+ const target = await deliverObservabilityExportToSQLite({
23623
+ artifactIndex,
23624
+ checkedAt,
23625
+ destination,
23626
+ index,
23627
+ manifest,
23628
+ report: options.report,
23629
+ runId
23630
+ });
23631
+ return {
23632
+ artifactCount: artifactIndex.summary.total,
23633
+ deliveredAt: Date.now(),
23634
+ destinationId,
23635
+ destinationKind: destination.kind,
23636
+ label,
23637
+ manifestBytes: byteLength(manifest),
23638
+ status: "delivered",
23639
+ target
23640
+ };
23641
+ }
23642
+ if (destination.kind === "postgres") {
23643
+ const target = await deliverObservabilityExportToPostgres({
23644
+ artifactIndex,
23645
+ checkedAt,
23646
+ destination,
23647
+ index,
23648
+ manifest,
23649
+ report: options.report,
23650
+ runId
23651
+ });
23652
+ return {
23653
+ artifactCount: artifactIndex.summary.total,
23654
+ deliveredAt: Date.now(),
23655
+ destinationId,
23656
+ destinationKind: destination.kind,
23657
+ label,
23658
+ manifestBytes: byteLength(manifest),
23659
+ status: "delivered",
23660
+ target
23661
+ };
23662
+ }
23510
23663
  const controller = new AbortController;
23511
23664
  const timeout = setTimeout(() => controller.abort(), destination.timeoutMs ?? 1e4);
23512
23665
  try {
@@ -23551,7 +23704,7 @@ var deliverVoiceObservabilityExport = async (options) => {
23551
23704
  label,
23552
23705
  manifestBytes: byteLength(manifest),
23553
23706
  status: "failed",
23554
- target: destination.kind === "file" ? destination.directory : destination.kind === "s3" ? destination.bucket ? `s3://${destination.bucket}/${normalizeExportS3KeyPrefix(destination.keyPrefix)}` : normalizeExportS3KeyPrefix(destination.keyPrefix) : destination.url
23707
+ target: observabilityExportDeliveryFailureTarget(destination)
23555
23708
  };
23556
23709
  }
23557
23710
  }));
@@ -26701,7 +26854,7 @@ var createVoiceTraceDeliveryRoutes = (options) => {
26701
26854
  return routes;
26702
26855
  };
26703
26856
  // src/sqliteStore.ts
26704
- import { Database } from "bun:sqlite";
26857
+ import { Database as Database2 } from "bun:sqlite";
26705
26858
  var normalizeTableNameSegment = (value) => value.trim().replace(/[^a-zA-Z0-9_]+/g, "_").replace(/^_+|_+$/g, "") || "voice";
26706
26859
  var resolveTableName = (input) => {
26707
26860
  if (input.options.tableName) {
@@ -26712,7 +26865,7 @@ var resolveTableName = (input) => {
26712
26865
  return `${prefix}_${fallback}`;
26713
26866
  };
26714
26867
  var openVoiceSQLiteDatabase = (path) => {
26715
- const database = new Database(path, {
26868
+ const database = new Database2(path, {
26716
26869
  create: true
26717
26870
  });
26718
26871
  database.exec("PRAGMA journal_mode = WAL;");
@@ -1,10 +1,12 @@
1
1
  import { Elysia } from 'elysia';
2
2
  import type { S3Client, S3Options } from 'bun';
3
+ import { Database } from 'bun:sqlite';
3
4
  import { type VoiceAuditSinkDeliveryQueueSummary, type VoiceAuditSinkDeliveryRecord, type VoiceAuditSinkDeliveryStore } from './auditSinks';
4
5
  import type { VoiceAuditEventStore, VoiceAuditEventType } from './audit';
5
6
  import { type VoiceOperationsRecord } from './operationsRecord';
6
7
  import { type VoiceTraceSinkDeliveryQueueSummary } from './queue';
7
8
  import { type StoredVoiceTraceEvent, type VoiceTraceEventStore, type VoiceTraceEventType, type VoiceTraceRedactionConfig, type VoiceTraceSinkDeliveryRecord, type VoiceTraceSinkDeliveryStore, type VoiceTraceSummary } from './trace';
9
+ import type { VoicePostgresClient } from './postgresStore';
8
10
  export type VoiceObservabilityExportStatus = 'fail' | 'pass' | 'warn';
9
11
  export type VoiceObservabilityExportArtifactKind = 'incident' | 'markdown' | 'operations-record' | 'proof-pack' | 'readiness' | 'screenshot' | 'slo' | 'trace' | 'audit' | 'custom';
10
12
  export type VoiceObservabilityExportArtifactChecksum = {
@@ -123,6 +125,21 @@ export type VoiceObservabilityExportDeliveryDestination = {
123
125
  kind: 's3';
124
126
  label?: string;
125
127
  }) | {
128
+ database?: Database;
129
+ id?: string;
130
+ kind: 'sqlite';
131
+ label?: string;
132
+ path?: string;
133
+ tableName?: string;
134
+ } | {
135
+ connectionString?: string;
136
+ id?: string;
137
+ kind: 'postgres';
138
+ label?: string;
139
+ schemaName?: string;
140
+ sql?: VoicePostgresClient;
141
+ tableName?: string;
142
+ } | {
126
143
  fetch?: typeof fetch;
127
144
  headers?: Record<string, string>;
128
145
  id?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.227",
3
+ "version": "0.0.22-beta.228",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",