@byok-sdk/cloud-dataplane 0.4.2 → 0.6.0
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 +81 -5
- package/dist/index.d.ts +4 -2
- package/dist/index.js +1090 -289
- package/dist/index.js.map +1 -1
- package/dist/migrate.d.ts +42 -0
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +493 -247
- package/dist/runtime.js.map +1 -1
- package/dist/sql/0008_device_assertion_replay.sql +17 -0
- package/dist/sql/0009_task_cancellation.sql +6 -0
- package/dist/sql/0010_tenant_readiness.sql +21 -0
- package/dist/sql/0011_tenant_erasure.sql +46 -0
- package/dist/stores/core/mailbox.d.ts +13 -0
- package/dist/stores/device-assertion-replay.d.ts +10 -0
- package/dist/stores/devices.d.ts +3 -2
- package/dist/stores/index.d.ts +2 -0
- package/dist/stores/r2-blobs.d.ts +1 -1
- package/dist/stores/task-attempts.d.ts +14 -0
- package/dist/stores/task-cancellations.d.ts +9 -0
- package/dist/tenant-erasure.d.ts +77 -0
- package/package.json +4 -4
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
-- Atomic single-use ledger for device assertions exchanged by hosted consumers.
|
|
2
|
+
-- The durable connector/session credential is host-owned and is not stored here.
|
|
3
|
+
|
|
4
|
+
CREATE TABLE device_assertion_replay (
|
|
5
|
+
tenant_id TEXT NOT NULL,
|
|
6
|
+
issuer TEXT NOT NULL,
|
|
7
|
+
product_id TEXT NOT NULL,
|
|
8
|
+
device_id TEXT NOT NULL,
|
|
9
|
+
audience TEXT NOT NULL,
|
|
10
|
+
jti TEXT NOT NULL,
|
|
11
|
+
expires_at TIMESTAMPTZ NOT NULL,
|
|
12
|
+
consumed_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
13
|
+
PRIMARY KEY (tenant_id, issuer, product_id, device_id, audience, jti)
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
CREATE INDEX device_assertion_replay_expiry_idx
|
|
17
|
+
ON device_assertion_replay (expires_at);
|
|
@@ -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);
|
|
@@ -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);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { DeviceAssertionReplayAuthority, DeviceAssertionReplayConsumeInput } from '@byok-sdk/core';
|
|
2
|
+
import type { Pool } from 'pg';
|
|
3
|
+
/** Durable atomic replay authority for hosted device-assertion exchange. */
|
|
4
|
+
export declare class PostgresDeviceAssertionReplayAuthority implements DeviceAssertionReplayAuthority {
|
|
5
|
+
#private;
|
|
6
|
+
constructor(pool: Pool);
|
|
7
|
+
consume(input: DeviceAssertionReplayConsumeInput): Promise<boolean>;
|
|
8
|
+
/** Bounded retention cleanup; callers choose cadence and batch size. */
|
|
9
|
+
deleteExpired(before: Date, limit: number): Promise<number>;
|
|
10
|
+
}
|
package/dist/stores/devices.d.ts
CHANGED
|
@@ -9,15 +9,16 @@
|
|
|
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
|
|
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
21
|
list(tenant: TenantId): Promise<readonly DeviceRecord[]>;
|
|
22
|
+
readiness(tenant: TenantId, _presence: PresenceStore): Promise<TenantReadiness>;
|
|
22
23
|
resolveByDeviceId(deviceId: string): Promise<DeviceRecord | undefined>;
|
|
23
24
|
}
|
package/dist/stores/index.d.ts
CHANGED
|
@@ -31,8 +31,10 @@ 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 { PostgresDeviceAssertionReplayAuthority } from './device-assertion-replay';
|
|
36
38
|
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';
|
|
37
39
|
export type { ObjectStoreFetch, R2BlobErrorCode, R2BlobStoreOptions } from './r2-blobs';
|
|
38
40
|
export type { R2DeleteResult, R2ListedObject, R2ObjectMaintenance, R2ObjectMaintenanceOptions, R2ObjectPage, } from './r2-blobs';
|
|
@@ -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
|
|
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
|
}
|
|
@@ -17,6 +17,19 @@
|
|
|
17
17
|
import type { 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 owner_device_id: string | null;
|
|
25
|
+
readonly status: string;
|
|
26
|
+
readonly cancel_requested_at: Date | null;
|
|
27
|
+
readonly cancel_reason: string | null;
|
|
28
|
+
readonly cancel_message_id: string | null;
|
|
29
|
+
readonly updated_at: Date;
|
|
30
|
+
}
|
|
31
|
+
export declare const TASK_SELECT_COLUMNS = "tenant_id, task_id, device_id, owner_device_id, status, cancel_requested_at, cancel_reason, cancel_message_id, updated_at";
|
|
32
|
+
export declare function taskRowToAttempt(row: TaskRow): TaskAttempt;
|
|
20
33
|
export declare class PostgresTaskAttemptStore implements TaskAttemptStore {
|
|
21
34
|
#private;
|
|
22
35
|
constructor(pool: Pool, clock: Clock);
|
|
@@ -25,6 +38,7 @@ export declare class PostgresTaskAttemptStore implements TaskAttemptStore {
|
|
|
25
38
|
readonly deviceId: string;
|
|
26
39
|
}): Promise<TaskAttempt>;
|
|
27
40
|
get(tenant: TenantId, taskId: string): Promise<TaskAttempt | undefined>;
|
|
41
|
+
getMany(tenant: TenantId, taskIds: readonly string[]): Promise<readonly TaskAttempt[]>;
|
|
28
42
|
claim(tenant: TenantId, input: {
|
|
29
43
|
readonly taskId: string;
|
|
30
44
|
readonly deviceId: string;
|
|
@@ -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 0010, 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', '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.
|
|
3
|
+
"version": "0.6.0",
|
|
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.
|
|
53
|
-
"@byok-sdk/core": "0.
|
|
54
|
-
"@byok-sdk/protocol": "0.
|
|
52
|
+
"@byok-sdk/cloud": "0.6.0",
|
|
53
|
+
"@byok-sdk/core": "0.6.0",
|
|
54
|
+
"@byok-sdk/protocol": "0.6.0",
|
|
55
55
|
"aws4fetch": "1.0.20",
|
|
56
56
|
"fast-xml-parser": "^5.10.1",
|
|
57
57
|
"pg": "^8.22.0"
|