@byok-sdk/cloud-dataplane 0.5.0 → 0.6.1

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.
@@ -0,0 +1,6 @@
1
+ -- Host cancellation tombstone and durable delivery identity.
2
+ -- Additive and forward-only: existing task rows remain uncancelled.
3
+ ALTER TABLE task
4
+ ADD COLUMN cancel_requested_at timestamptz,
5
+ ADD COLUMN cancel_reason text,
6
+ ADD COLUMN cancel_message_id text;
@@ -0,0 +1,21 @@
1
+ -- U3 tenant readiness observation facts.
2
+ --
3
+ -- The row remains one lossy, TTL-bounded projection per (tenant, device). These
4
+ -- fields are optional because older daemons cannot report them; absence is
5
+ -- unknown, never a host-derived default.
6
+ ALTER TABLE device_presence
7
+ ADD COLUMN client_version text;
8
+
9
+ ALTER TABLE device_presence
10
+ ADD COLUMN protocol_versions jsonb;
11
+
12
+ ALTER TABLE device_presence
13
+ ADD COLUMN runtimes jsonb;
14
+
15
+ ALTER TABLE device_presence
16
+ ADD CONSTRAINT device_presence_protocol_versions_shape
17
+ CHECK (protocol_versions IS NULL OR jsonb_typeof(protocol_versions) = 'array');
18
+
19
+ ALTER TABLE device_presence
20
+ ADD CONSTRAINT device_presence_runtimes_shape
21
+ CHECK (runtimes IS NULL OR jsonb_typeof(runtimes) = 'array');
@@ -0,0 +1,46 @@
1
+ -- 0011_tenant_erasure.sql — package-owned, resumable operator evidence.
2
+ --
3
+ -- This ledger is deliberately NOT tenant product data. It records one operator
4
+ -- operation and its progress so a caller can resume after an R2/database/crash
5
+ -- boundary, and a completed receipt remains auditable after every tenant-owned
6
+ -- product row and R2 object is gone. The erasure implementation is the only
7
+ -- writer; hosts do not receive raw SQL/table-order authority.
8
+
9
+ CREATE TABLE tenant_erasure_operation (
10
+ tenant_id text NOT NULL,
11
+ operation_id text NOT NULL,
12
+ state text NOT NULL,
13
+ revision bigint NOT NULL DEFAULT 0,
14
+ lease_token text,
15
+ lease_expires_at timestamptz,
16
+ r2_cursor text,
17
+ r2_complete boolean NOT NULL DEFAULT false,
18
+ sql_table_index integer NOT NULL DEFAULT 0,
19
+ r2_objects_deleted bigint NOT NULL DEFAULT 0,
20
+ sql_rows_deleted bigint NOT NULL DEFAULT 0,
21
+ started_at timestamptz NOT NULL,
22
+ updated_at timestamptz NOT NULL,
23
+ completed_at timestamptz,
24
+ last_error_code text,
25
+ PRIMARY KEY (tenant_id, operation_id),
26
+ CONSTRAINT tenant_erasure_operation_state CHECK (state IN ('running', 'completed')),
27
+ CONSTRAINT tenant_erasure_operation_revision_nonnegative CHECK (revision >= 0),
28
+ CONSTRAINT tenant_erasure_operation_sql_table_index_nonnegative CHECK (sql_table_index >= 0),
29
+ CONSTRAINT tenant_erasure_operation_counters_nonnegative CHECK (
30
+ r2_objects_deleted >= 0 AND sql_rows_deleted >= 0
31
+ ),
32
+ CONSTRAINT tenant_erasure_operation_completed_receipt CHECK (
33
+ (state = 'running' AND completed_at IS NULL)
34
+ OR (state = 'completed' AND completed_at IS NOT NULL AND r2_complete)
35
+ )
36
+ );
37
+
38
+ -- At most one unfinished erasure can own a tenant. Completed receipts do not
39
+ -- participate, so a future explicitly authorized erasure operation still has
40
+ -- an idempotency key of its own without deleting its predecessor's evidence.
41
+ CREATE UNIQUE INDEX tenant_erasure_operation_one_running
42
+ ON tenant_erasure_operation (tenant_id)
43
+ WHERE state = 'running';
44
+
45
+ CREATE INDEX tenant_erasure_operation_readback
46
+ ON tenant_erasure_operation (tenant_id, state, updated_at);
@@ -0,0 +1,41 @@
1
+ -- 0012_agent_home_contract.sql — durable Agent capability and attempt identity.
2
+ --
3
+ -- Additive only. Existing legacy device/task rows remain valid with NULL
4
+ -- Agent fields and therefore cannot accidentally acquire Agent semantics.
5
+ -- The hosted gate treats a NULL capability snapshot as unknown and refuses an
6
+ -- Agent offer before either a mailbox row or a task attempt is created.
7
+
8
+ ALTER TABLE device
9
+ ADD COLUMN capabilities jsonb;
10
+
11
+ ALTER TABLE device
12
+ ADD CONSTRAINT device_capabilities_shape
13
+ CHECK (capabilities IS NULL OR jsonb_typeof(capabilities) = 'array');
14
+
15
+ ALTER TABLE task
16
+ ADD COLUMN agent_id text,
17
+ ADD COLUMN agent_profile_revision text,
18
+ ADD COLUMN terminal_cause text;
19
+
20
+ ALTER TABLE task
21
+ ADD CONSTRAINT task_agent_ref_pair
22
+ CHECK ((agent_id IS NULL) = (agent_profile_revision IS NULL));
23
+
24
+ ALTER TABLE task
25
+ ADD CONSTRAINT task_agent_ref_bounded
26
+ CHECK (
27
+ agent_id IS NULL
28
+ OR (
29
+ octet_length(agent_id) BETWEEN 1 AND 160
30
+ AND octet_length(agent_profile_revision) BETWEEN 1 AND 160
31
+ AND position('/' IN agent_id) = 0
32
+ AND position(E'\\' IN agent_id) = 0
33
+ AND position(':' IN agent_id) = 0
34
+ AND agent_id !~ '[<>"|?*]'
35
+ AND agent_id NOT IN ('.', '..')
36
+ AND agent_id !~ '[. ]$'
37
+ AND lower(split_part(agent_id, '.', 1)) !~ '^(con|prn|aux|nul|com[1-9]|lpt[1-9])$'
38
+ AND agent_id !~ '[[:cntrl:]]'
39
+ AND agent_profile_revision !~ '[[:cntrl:]]'
40
+ )
41
+ );
@@ -0,0 +1,47 @@
1
+ -- 0013_agent_egress_contract.sql — first-write-wins reliable Agent egress facts.
2
+ --
3
+ -- The typed policy never authorizes raw workspace/transcript/artifact bytes to
4
+ -- this table. `payload_json` is the daemon's sanitized JSON projection; exact
5
+ -- AgentRef, session, cursor, content hash, and cloud receipt identity remain
6
+ -- independently addressable so a readback never has to reinterpret an opaque
7
+ -- envelope body.
8
+
9
+ CREATE TABLE agent_egress_event (
10
+ tenant_id text NOT NULL,
11
+ device_id text NOT NULL,
12
+ event_id uuid NOT NULL,
13
+ agent_id text NOT NULL,
14
+ agent_profile_revision text NOT NULL,
15
+ session_ref text NOT NULL,
16
+ policy_revision text NOT NULL,
17
+ cursor bigint NOT NULL,
18
+ payload_json jsonb NOT NULL,
19
+ content_hash text NOT NULL,
20
+ byte_count integer NOT NULL,
21
+ receipt_id uuid NOT NULL,
22
+ recorded_at timestamptz NOT NULL,
23
+ PRIMARY KEY (tenant_id, device_id, event_id),
24
+ CONSTRAINT agent_egress_event_cursor_positive CHECK (cursor > 0 AND cursor <= 2147483647),
25
+ CONSTRAINT agent_egress_event_byte_count CHECK (byte_count >= 0 AND byte_count <= 262144),
26
+ CONSTRAINT agent_egress_event_hash_shape CHECK (content_hash ~ '^sha256:[a-f0-9]{64}$'),
27
+ CONSTRAINT agent_egress_event_agent_ref_bounded CHECK (
28
+ octet_length(agent_id) BETWEEN 1 AND 160
29
+ AND octet_length(agent_profile_revision) BETWEEN 1 AND 160
30
+ AND agent_id !~ '[[:cntrl:]]'
31
+ AND agent_profile_revision !~ '[[:cntrl:]]'
32
+ ),
33
+ CONSTRAINT agent_egress_event_session_bounded CHECK (
34
+ octet_length(session_ref) BETWEEN 1 AND 512
35
+ AND session_ref !~ '[[:cntrl:]]'
36
+ ),
37
+ CONSTRAINT agent_egress_event_policy_bounded CHECK (
38
+ octet_length(policy_revision) BETWEEN 1 AND 160
39
+ AND policy_revision !~ '[[:cntrl:]]'
40
+ )
41
+ );
42
+
43
+ -- Readback and operational inspection preserve the same tenant/device/session
44
+ -- partition as the wire cursor. This is intentionally not unique: event id is
45
+ -- the reliable idempotency authority, and cursor is an exact observation.
46
+ CREATE INDEX agent_egress_event_cursor_idx
47
+ ON agent_egress_event (tenant_id, device_id, agent_id, agent_profile_revision, session_ref, cursor);
@@ -0,0 +1,14 @@
1
+ import type { AgentEgressRecord, AgentEgressStore } from '@byok-sdk/cloud';
2
+ import type { Clock, TenantId } from '@byok-sdk/core';
3
+ import type { Pool } from 'pg';
4
+ /** Postgres implementation of the immutable reliable Agent egress receipt fact. */
5
+ export declare class PostgresAgentEgressStore implements AgentEgressStore {
6
+ private readonly pool;
7
+ private readonly clock;
8
+ constructor(pool: Pool, clock: Clock);
9
+ record(tenant: TenantId, input: Omit<AgentEgressRecord, 'tenantId' | 'recordedAt'>): Promise<{
10
+ readonly record: AgentEgressRecord;
11
+ readonly created: boolean;
12
+ }>;
13
+ get(tenant: TenantId, deviceId: string, eventId: string): Promise<AgentEgressRecord | undefined>;
14
+ }
@@ -21,6 +21,19 @@
21
21
  */
22
22
  import { type Clock, type MailboxAdvanceCursorInput, type MailboxAppendInput, type MailboxCursorState, type MailboxMessage, type MailboxPage, type MailboxReadQuery, type MailboxRetentionInput, type MailboxRetentionResult, type MailboxStore, type TenantId } from '@byok-sdk/core';
23
23
  import type { Pool } from 'pg';
24
+ export declare const OUTBOX_COLUMNS = "tenant_id, device_id, seq, message_id, body, body_hash, byte_size, state, appended_at";
25
+ export interface OutboxRow {
26
+ readonly tenant_id: string;
27
+ readonly device_id: string;
28
+ readonly seq: bigint;
29
+ readonly message_id: string;
30
+ readonly body: string;
31
+ readonly body_hash: string;
32
+ readonly byte_size: bigint;
33
+ readonly state: string;
34
+ readonly appended_at: string;
35
+ }
36
+ export declare function toMailboxMessage(row: OutboxRow): MailboxMessage;
24
37
  export declare class PostgresMailboxStore implements MailboxStore {
25
38
  #private;
26
39
  constructor(pool: Pool, clock: Clock);
@@ -9,15 +9,20 @@
9
9
  * guessed. One row, two access paths, never two copies to keep in sync — a
10
10
  * stale pre-tenant index would be a revoked device that can still get a token.
11
11
  */
12
- import type { TenantId } from '@byok-sdk/core';
12
+ import { type Clock, type PresenceStore, type TenantId, type TenantReadiness } from '@byok-sdk/core';
13
13
  import type { DeviceDirectory, DeviceRecord, DeviceRegistration } from '@byok-sdk/cloud';
14
14
  import type { Pool } from 'pg';
15
15
  export declare class PostgresDeviceDirectory implements DeviceDirectory {
16
16
  #private;
17
- constructor(pool: Pool);
17
+ constructor(pool: Pool, clock?: Clock);
18
18
  register(tenant: TenantId, input: DeviceRegistration): Promise<DeviceRecord>;
19
19
  get(tenant: TenantId, deviceId: string): Promise<DeviceRecord | undefined>;
20
20
  revoke(tenant: TenantId, deviceId: string): Promise<void>;
21
+ recordCapabilities(tenant: TenantId, input: {
22
+ readonly deviceId: string;
23
+ readonly capabilities: readonly string[];
24
+ }): Promise<DeviceRecord | undefined>;
21
25
  list(tenant: TenantId): Promise<readonly DeviceRecord[]>;
26
+ readiness(tenant: TenantId, _presence: PresenceStore): Promise<TenantReadiness>;
22
27
  resolveByDeviceId(deviceId: string): Promise<DeviceRecord | undefined>;
23
28
  }
@@ -31,13 +31,15 @@ export { PostgresPairingCodeStore } from './pairing-codes';
31
31
  export { PostgresRequestReceiptStore } from './receipts';
32
32
  export { PostgresProofRequestReceiptStore } from './proof-receipts';
33
33
  export { PostgresTaskAttemptStore } from './task-attempts';
34
+ export { PostgresTaskCancellationStore } from './task-cancellations';
34
35
  export { PostgresActivityStore } from './activity';
35
36
  export { PostgresApprovalTimelineStore } from './approval-timeline';
37
+ export { PostgresAgentEgressStore } from './agent-egress';
36
38
  export { PostgresDeviceAssertionReplayAuthority } from './device-assertion-replay';
37
39
  export { DEFAULT_MAX_ATTEMPTS, DEFAULT_PRESIGN_TTL_SECONDS, DEFAULT_RETRY_DELAY_MS, MAX_PRESIGN_TTL_SECONDS, MIN_PRESIGN_TTL_SECONDS, ObjectStoreRequestError, R2_BLOB_ERROR_CODES, R2BlobStoreError, R2CloudBlobStore, R2ObjectMaintenanceStore, } from './r2-blobs';
38
40
  export type { ObjectStoreFetch, R2BlobErrorCode, R2BlobStoreOptions } from './r2-blobs';
39
41
  export type { R2DeleteResult, R2ListedObject, R2ObjectMaintenance, R2ObjectMaintenanceOptions, R2ObjectPage, } from './r2-blobs';
40
- /** Every cloud-local port. All eleven, or it is not a composition. */
42
+ /** Every cloud-local port. All twelve, or it is not a composition. */
41
43
  export type PostgresCloudStores = CloudStores;
42
44
  /** Everything the blob store needs that is not already a composition-wide input. */
43
45
  export type PostgresObjectStorageOptions = Omit<R2BlobStoreOptions, 'objects'>;
@@ -179,7 +179,7 @@ export interface R2BlobStoreOptions {
179
179
  /** One tenant-prefixed R2 key returned by ListObjectsV2. */
180
180
  export interface R2ListedObject {
181
181
  readonly key: string;
182
- /** Present only when the key is exactly `<tenant>/sha256/<64 lowercase hex>`. */
182
+ /** Present only when the key is exactly `tenants/<tenant>/objects/sha256/<64 lowercase hex>`. */
183
183
  readonly hash?: ContentHash;
184
184
  readonly byteSize: bigint;
185
185
  }
@@ -14,17 +14,43 @@
14
14
  * `claim` and `recordStatus` on a task this tenant never offered write nothing
15
15
  * and return `undefined`.
16
16
  */
17
- import type { TaskAttempt, TaskAttemptStatus, TaskAttemptStore } from '@byok-sdk/cloud';
17
+ import type { AgentRef, TaskAttempt, TaskAttemptStatus, TaskAttemptStore } from '@byok-sdk/cloud';
18
18
  import type { Clock, TenantId } from '@byok-sdk/core';
19
19
  import type { Pool } from 'pg';
20
+ export interface TaskRow {
21
+ readonly tenant_id: string;
22
+ readonly task_id: string;
23
+ readonly device_id: string;
24
+ readonly agent_id: string | null;
25
+ readonly agent_profile_revision: string | null;
26
+ readonly owner_device_id: string | null;
27
+ readonly status: string;
28
+ readonly terminal_cause: string | null;
29
+ readonly cancel_requested_at: Date | null;
30
+ readonly cancel_reason: string | null;
31
+ readonly cancel_message_id: string | null;
32
+ readonly updated_at: Date;
33
+ }
34
+ export declare const TASK_SELECT_COLUMNS = "tenant_id, task_id, device_id, agent_id, agent_profile_revision, owner_device_id, status, terminal_cause, cancel_requested_at, cancel_reason, cancel_message_id, updated_at";
35
+ export declare function taskRowToAttempt(row: TaskRow): TaskAttempt;
20
36
  export declare class PostgresTaskAttemptStore implements TaskAttemptStore {
21
37
  #private;
22
38
  constructor(pool: Pool, clock: Clock);
23
39
  open(tenant: TenantId, input: {
24
40
  readonly taskId: string;
25
41
  readonly deviceId: string;
42
+ readonly agentRef?: AgentRef;
26
43
  }): Promise<TaskAttempt>;
44
+ reserveAgentOffer(tenant: TenantId, input: {
45
+ readonly taskId: string;
46
+ readonly deviceId: string;
47
+ readonly agentRef: AgentRef;
48
+ }): Promise<{
49
+ readonly attempt: TaskAttempt;
50
+ readonly created: boolean;
51
+ }>;
27
52
  get(tenant: TenantId, taskId: string): Promise<TaskAttempt | undefined>;
53
+ getMany(tenant: TenantId, taskIds: readonly string[]): Promise<readonly TaskAttempt[]>;
28
54
  claim(tenant: TenantId, input: {
29
55
  readonly taskId: string;
30
56
  readonly deviceId: string;
@@ -32,5 +58,7 @@ export declare class PostgresTaskAttemptStore implements TaskAttemptStore {
32
58
  recordStatus(tenant: TenantId, input: {
33
59
  readonly taskId: string;
34
60
  readonly status: TaskAttemptStatus;
61
+ readonly agentRef?: AgentRef;
62
+ readonly terminalCause?: string;
35
63
  }): Promise<TaskAttempt | undefined>;
36
64
  }
@@ -0,0 +1,9 @@
1
+ import type { TaskCancellationMutation, TaskCancellationRequest, TaskCancellationStore } from '@byok-sdk/cloud';
2
+ import type { Clock, TenantId } from '@byok-sdk/core';
3
+ import type { Pool } from 'pg';
4
+ /** PostgreSQL atomic authority for host cancellation state plus mailbox delivery. */
5
+ export declare class PostgresTaskCancellationStore implements TaskCancellationStore {
6
+ #private;
7
+ constructor(pool: Pool, clock: Clock);
8
+ request(tenant: TenantId, input: TaskCancellationRequest): Promise<TaskCancellationMutation | undefined>;
9
+ }
@@ -0,0 +1,77 @@
1
+ import { type Clock, type TenantId } from '@byok-sdk/core';
2
+ import type { Pool } from 'pg';
3
+ import { type R2BlobStoreOptions, type R2ObjectMaintenance } from './stores/r2-blobs';
4
+ /** Every tenant-owned table as of 0013, in child-before-parent deletion order. */
5
+ export declare const TENANT_ERASURE_TABLES: readonly ['object_reference', 'object_manifest', 'storage_reservation', 'storage_usage', 'storage_entitlement', 'gc_cursor', 'cleanup_job', 'tenant_retention_policy', 'skill_pack_file', 'skill_pack', 'approval_timeline_tail', 'activity_tail', 'attested_record', 'board_item', 'tenant_stream', 'outbox', 'agent_egress_event', 'device_request_receipts', 'proof_request_receipt', 'task', 'device_presence', 'device_assertion_replay', 'device_stream', 'inbound_dedup', 'auth_nonce', 'pairing_code', 'device'];
6
+ export declare const TENANT_ERASURE_ERROR_CODES: {
7
+ readonly tenant_erasure_invalid_input: 'tenant_erasure_invalid_input';
8
+ readonly tenant_erasure_schema_drift: 'tenant_erasure_schema_drift';
9
+ readonly tenant_erasure_object_key_invalid: 'tenant_erasure_object_key_invalid';
10
+ readonly tenant_erasure_storage_failure: 'tenant_erasure_storage_failure';
11
+ readonly tenant_erasure_database_failure: 'tenant_erasure_database_failure';
12
+ readonly tenant_erasure_cas_lost: 'tenant_erasure_cas_lost';
13
+ };
14
+ export type TenantErasureErrorCode = (typeof TENANT_ERASURE_ERROR_CODES)[keyof typeof TENANT_ERASURE_ERROR_CODES];
15
+ export declare class TenantErasureError extends Error {
16
+ readonly code: TenantErasureErrorCode;
17
+ constructor(code: TenantErasureErrorCode, message: string, options?: ErrorOptions);
18
+ }
19
+ export type TenantErasureStatus = 'outstanding' | 'partial' | 'completed';
20
+ export interface TenantErasureReadback {
21
+ readonly status: TenantErasureStatus;
22
+ readonly tenantId: TenantId;
23
+ readonly operationId: string;
24
+ readonly startedAt: string;
25
+ readonly updatedAt: string;
26
+ readonly completedAt?: string;
27
+ readonly r2Complete: boolean;
28
+ readonly sqlTableIndex: number;
29
+ readonly r2ObjectsDeleted: bigint;
30
+ readonly sqlRowsDeleted: bigint;
31
+ /** A closed, audit-safe class. Remote messages and object names are never retained. */
32
+ readonly errorCode?: TenantErasureErrorCode;
33
+ }
34
+ export interface TenantErasureConflict {
35
+ readonly status: 'conflict';
36
+ readonly tenantId: TenantId;
37
+ readonly operationId: string;
38
+ readonly activeOperationId: string;
39
+ }
40
+ export type TenantErasureResult = TenantErasureReadback | TenantErasureConflict;
41
+ export interface PostgresTenantErasureOptions {
42
+ /** A Node direct-DSN pool. The host owns pool lifetime and write quiescence. */
43
+ readonly pool: Pool;
44
+ readonly clock: Clock;
45
+ readonly objectStorage: R2ObjectMaintenance;
46
+ /** ListObjectsV2 and one SQL DELETE use this bound; valid range is 1..1000. */
47
+ readonly batchSize?: number;
48
+ /** Maximum R2/SQL pages one operator invocation may advance; valid range is 1..100. */
49
+ readonly maxPagesPerRun?: number;
50
+ /** Crash-recovery lease; a retry may take an expired lease using the operation CAS. */
51
+ readonly leaseMs?: number;
52
+ }
53
+ export interface PostgresTenantErasureCompositionOptions {
54
+ readonly pool: Pool;
55
+ readonly clock: Clock;
56
+ readonly objectStorage: Omit<R2BlobStoreOptions, 'objects'>;
57
+ readonly batchSize?: number;
58
+ readonly maxPagesPerRun?: number;
59
+ readonly leaseMs?: number;
60
+ }
61
+ /**
62
+ * The Node-only erasure authority. It has no raw-table or raw-key API: the
63
+ * static inventory and canonical R2 adapter are the only deletion authority.
64
+ */
65
+ export declare class PostgresTenantErasure {
66
+ #private;
67
+ constructor(options: PostgresTenantErasureOptions);
68
+ /** Read a durable operation receipt without advancing it. */
69
+ readTenantErasure(tenant: TenantId, operationId: string): Promise<TenantErasureReadback | undefined>;
70
+ /**
71
+ * Advance one bounded operation slice. Calls with a completed id replay its
72
+ * receipt; another running id for the same tenant gets a typed conflict.
73
+ */
74
+ eraseTenant(tenant: TenantId, operationId: string): Promise<TenantErasureResult>;
75
+ }
76
+ /** Build the Node maintenance composition against the same direct Postgres/R2 authorities. */
77
+ export declare function createPostgresTenantErasure(options: PostgresTenantErasureCompositionOptions): PostgresTenantErasure;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byok-sdk/cloud-dataplane",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "description": "BYOK SDK hosted data plane: canonical Postgres + R2 stores, migrations, truth transactions, and maintenance",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -49,9 +49,9 @@
49
49
  "clean": "rm -rf dist"
50
50
  },
51
51
  "dependencies": {
52
- "@byok-sdk/cloud": "0.5.0",
53
- "@byok-sdk/core": "0.5.0",
54
- "@byok-sdk/protocol": "0.5.0",
52
+ "@byok-sdk/cloud": "0.6.1",
53
+ "@byok-sdk/core": "0.6.1",
54
+ "@byok-sdk/protocol": "0.6.1",
55
55
  "aws4fetch": "1.0.20",
56
56
  "fast-xml-parser": "^5.10.1",
57
57
  "pg": "^8.22.0"