@byok-sdk/cloud-dataplane 0.10.2 → 0.12.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.
@@ -0,0 +1,20 @@
1
+ -- 0016_mailbox_delivery_watermark.sql — server-owned mailbox delivery bound.
2
+ --
3
+ -- Client acknowledgement is allowed only through the highest cursor the
4
+ -- server has returned to that exact tenant/device. Enqueue allocation is not
5
+ -- delivery authority: next_seq may be far ahead while no response has exposed
6
+ -- those rows. Existing acknowledged rows are the one safe backfill fact — an
7
+ -- acknowledgement already persisted before this migration necessarily came
8
+ -- from the historical delivery path.
9
+
10
+ ALTER TABLE device_stream
11
+ ADD COLUMN delivered_seq bigint NOT NULL DEFAULT 0;
12
+
13
+ UPDATE device_stream
14
+ SET delivered_seq = acked_seq;
15
+
16
+ ALTER TABLE device_stream
17
+ ADD CONSTRAINT device_stream_delivery_nonnegative
18
+ CHECK (delivered_seq >= 0),
19
+ ADD CONSTRAINT device_stream_ack_within_delivery
20
+ CHECK (acked_seq <= delivered_seq);
@@ -0,0 +1,23 @@
1
+ -- Durable first-message admission serialized with task cancellation.
2
+ --
3
+ -- This one row records both the admission and the immutable final disposition:
4
+ -- exactly one new agent.message.publish may cross from a live task into the
5
+ -- product consumer. A pending row is deliberately fail-closed: elapsed time
6
+ -- cannot prove that an external consumer stopped, so retries never invoke it
7
+ -- again or synthesize a terminal result. The task row is locked by the
8
+ -- application transaction before inserting here, so cancellation and first
9
+ -- message admission have one durable winner.
10
+
11
+ CREATE TABLE agent_message_admission (
12
+ tenant_id text NOT NULL,
13
+ device_id text NOT NULL,
14
+ task_id text NOT NULL,
15
+ message_id text NOT NULL,
16
+ payload_body text NOT NULL,
17
+ terminal_body text,
18
+ PRIMARY KEY (tenant_id, device_id, task_id),
19
+ UNIQUE (tenant_id, device_id, message_id),
20
+ FOREIGN KEY (tenant_id, task_id)
21
+ REFERENCES task (tenant_id, task_id)
22
+ ON DELETE CASCADE
23
+ );
@@ -4,9 +4,9 @@
4
4
  * The load-bearing rule, and the one a composition can break silently:
5
5
  * **reading is not acknowledging.** `readAfter` is a `SELECT` and nothing else.
6
6
  * The only ack is `advanceCursor`, which the daemon calls after it has durably
7
- * journaled the envelope, and it is monotonic — a lower cursor is refused with
8
- * the cursor it lost to rather than quietly re-delivering work the device has
9
- * already run.
7
+ * journaled the envelope. It is monotonic and bounded by `recordDelivery`:
8
+ * regression and future cursors are refused with the cursor state they lost
9
+ * to rather than re-delivering or silently skipping work.
10
10
  *
11
11
  * `collectRetired` deletes acked rows and **marks** unacked ones `expired`. It
12
12
  * never deletes an unacked row. §12.7.5 requires an envelope that aged out
@@ -19,7 +19,7 @@
19
19
  * the SQL expression of §12.6.2 layer 3: there is no bare device index to
20
20
  * accidentally query.
21
21
  */
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';
22
+ import { type Clock, type MailboxAdvanceCursorInput, type MailboxAppendInput, type MailboxCursorState, type MailboxMessage, type MailboxPage, type MailboxReadQuery, type MailboxRecordDeliveryInput, type MailboxRetentionInput, type MailboxRetentionResult, type MailboxStore, type TenantId } from '@byok-sdk/core';
23
23
  import type { Pool } from 'pg';
24
24
  export declare const OUTBOX_COLUMNS = "tenant_id, device_id, seq, message_id, body, body_hash, byte_size, state, appended_at";
25
25
  export interface OutboxRow {
@@ -40,6 +40,7 @@ export declare class PostgresMailboxStore implements MailboxStore {
40
40
  append(tenant: TenantId, input: MailboxAppendInput): Promise<MailboxMessage>;
41
41
  readAfter(tenant: TenantId, query: MailboxReadQuery): Promise<MailboxPage>;
42
42
  advanceCursor(tenant: TenantId, input: MailboxAdvanceCursorInput): Promise<MailboxCursorState>;
43
+ recordDelivery(tenant: TenantId, input: MailboxRecordDeliveryInput): Promise<MailboxCursorState>;
43
44
  readCursor(tenant: TenantId, deviceId: string): Promise<MailboxCursorState>;
44
45
  collectRetired(tenant: TenantId, input: MailboxRetentionInput): Promise<MailboxRetentionResult>;
45
46
  }
@@ -19,7 +19,16 @@
19
19
  */
20
20
  import { type Clock, type PresenceStore, type TenantId, type TenantReadiness } from '@byok-sdk/core';
21
21
  import type { DeviceDirectory, DeviceRecord, DeviceRegistration } from '@byok-sdk/cloud';
22
- import type { Pool } from 'pg';
22
+ import type { Pool, PoolClient } from 'pg';
23
+ /**
24
+ * The client-scoped registration mutation shared by standalone device
25
+ * registration and pairing enrollment. Its caller owns the transaction so the
26
+ * guarded pairing-code update, machine supersession cleanup, and insert can
27
+ * commit or roll back together.
28
+ */
29
+ export declare function registerDeviceOnClient(client: PoolClient, tenant: TenantId, input: DeviceRegistration): Promise<DeviceRecord>;
30
+ /** Read one active device while a caller-owned transaction still holds its authority lock. */
31
+ export declare function getDeviceOnClient(client: PoolClient, tenant: TenantId, deviceId: string): Promise<DeviceRecord | undefined>;
23
32
  export declare class PostgresDeviceDirectory implements DeviceDirectory {
24
33
  #private;
25
34
  constructor(pool: Pool, clock?: Clock);
@@ -21,6 +21,5 @@ export declare class PostgresNonceStore implements NonceStore {
21
21
  #private;
22
22
  constructor(pool: Pool, clock: Clock, crypto: CloudCrypto, ttlMs?: number);
23
23
  issue(tenant: TenantId, deviceId: string): Promise<string>;
24
- validate(tenant: TenantId, deviceId: string, nonce: string): Promise<boolean>;
25
- markUsed(tenant: TenantId, nonce: string): Promise<void>;
24
+ consumeIfValid(tenant: TenantId, deviceId: string, nonce: string): Promise<boolean>;
26
25
  }
@@ -2,12 +2,11 @@
2
2
  * Postgres {@link PairingCodeStore}: single-use codes bound to the tenant and
3
3
  * product they were minted for.
4
4
  *
5
- * Redemption is one guarded statement. `UPDATE ... WHERE redeemed_at IS NULL
6
- * AND expires_at >= $now RETURNING ...` consumes and reports in the same
7
- * round trip, and zero rows is the typed rejection. A read-then-write would let
8
- * two concurrent redemptions both observe an unused code, and single-use is
9
- * exactly what makes the caller's "redeem, then register the device" sequence
10
- * exclusive.
5
+ * Enrollment is one transaction. `UPDATE ... WHERE redeemed_at IS NULL AND
6
+ * expires_at >= $now RETURNING ...` claims the code only inside the same client
7
+ * transaction that applies machine supersession/state cleanup and inserts the
8
+ * device. A read-then-write would let two callers observe an unused code; an
9
+ * autocommitted redemption would strand a code when registration fails.
11
10
  *
12
11
  * Unknown, expired, and already-used all answer `undefined`. The reference
13
12
  * server distinguishes them in its 401 text; a hosted multi-tenant surface
@@ -16,11 +15,19 @@
16
15
  * difference an attacker enumerating codes would pay for.
17
16
  */
18
17
  import type { Clock, TenantId } from '@byok-sdk/core';
19
- import type { PairingCodeClaims, PairingCodeInfo, PairingCodeIssueInput, PairingCodeStore } from '@byok-sdk/cloud';
18
+ import type { DeviceRecord, PairingCodeInfo, PairingCodeIssueInput, PairingCodeStore } from '@byok-sdk/cloud';
20
19
  import type { Pool } from 'pg';
21
20
  export declare class PostgresPairingCodeStore implements PairingCodeStore {
22
21
  #private;
23
22
  constructor(pool: Pool, clock: Clock);
24
23
  issue(tenant: TenantId, input: PairingCodeIssueInput): Promise<PairingCodeInfo>;
25
- redeem(code: string): Promise<PairingCodeClaims | undefined>;
24
+ redeemAndRegister(input: {
25
+ readonly pairingCode: string;
26
+ readonly deviceId: string;
27
+ readonly deviceName: string;
28
+ readonly devicePublicKey: string;
29
+ readonly proofKeyId: string;
30
+ readonly proofKeyEpoch: number;
31
+ readonly machineId?: string;
32
+ }): Promise<DeviceRecord | undefined>;
26
33
  }
@@ -14,7 +14,7 @@
14
14
  * `claim` and `recordStatus` on a task this tenant never offered write nothing
15
15
  * and return `undefined`.
16
16
  */
17
- import type { AgentRef, TaskAttempt, TaskAttemptStatus, TaskAttemptStore } from '@byok-sdk/cloud';
17
+ import { type AgentMessageAdmission, type AgentRef, type TaskAttempt, type TaskAttemptStatus, type TaskAttemptStore } from '@byok-sdk/cloud';
18
18
  import type { Clock, TenantId } from '@byok-sdk/core';
19
19
  import type { Pool } from 'pg';
20
20
  export interface TaskRow {
@@ -49,6 +49,25 @@ export declare class PostgresTaskAttemptStore implements TaskAttemptStore {
49
49
  readonly attempt: TaskAttempt;
50
50
  readonly created: boolean;
51
51
  }>;
52
+ reserveAgentMessage(tenant: TenantId, input: {
53
+ readonly taskId: string;
54
+ readonly deviceId: string;
55
+ readonly messageId: string;
56
+ readonly payloadBody: string;
57
+ }): Promise<'reserved' | 'pending' | 'rejected'>;
58
+ readAgentMessage(tenant: TenantId, input: {
59
+ readonly taskId: string;
60
+ readonly deviceId: string;
61
+ readonly messageId: string;
62
+ readonly payloadBody: string;
63
+ }): Promise<AgentMessageAdmission | undefined>;
64
+ finalizeAgentMessage(tenant: TenantId, input: {
65
+ readonly taskId: string;
66
+ readonly deviceId: string;
67
+ readonly messageId: string;
68
+ readonly payloadBody: string;
69
+ readonly terminalBody: string;
70
+ }): Promise<AgentMessageAdmission | undefined>;
52
71
  get(tenant: TenantId, taskId: string): Promise<TaskAttempt | undefined>;
53
72
  getMany(tenant: TenantId, taskIds: readonly string[]): Promise<readonly TaskAttempt[]>;
54
73
  claim(tenant: TenantId, input: {
@@ -1,8 +1,8 @@
1
1
  import { type Clock, type TenantId } from '@byok-sdk/core';
2
2
  import type { Pool } from 'pg';
3
3
  import { type R2BlobStoreOptions, type R2ObjectMaintenance } from './stores/r2-blobs';
4
- /** Every tenant-owned table as of 0014, 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_memory_projection_metering_receipt', 'agent_memory_projection_head', 'agent_memory_projection_erase_fence', 'agent_egress_event', 'device_request_receipts', 'proof_request_receipt', 'task', 'device_presence', 'device_assertion_replay', 'device_stream', 'inbound_dedup', 'auth_nonce', 'pairing_code', 'device'];
4
+ /** Every tenant-owned table as of 0017, 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_memory_projection_metering_receipt', 'agent_memory_projection_head', 'agent_memory_projection_erase_fence', 'agent_egress_event', 'device_request_receipts', 'proof_request_receipt', 'agent_message_admission', 'task', 'device_presence', 'device_assertion_replay', 'device_stream', 'inbound_dedup', 'auth_nonce', 'pairing_code', 'device'];
6
6
  export declare const TENANT_ERASURE_ERROR_CODES: {
7
7
  readonly tenant_erasure_invalid_input: 'tenant_erasure_invalid_input';
8
8
  readonly tenant_erasure_schema_drift: 'tenant_erasure_schema_drift';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byok-sdk/cloud-dataplane",
3
- "version": "0.10.2",
3
+ "version": "0.12.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.10.2",
53
- "@byok-sdk/core": "0.10.2",
54
- "@byok-sdk/protocol": "0.10.2",
52
+ "@byok-sdk/cloud": "0.12.0",
53
+ "@byok-sdk/core": "0.12.0",
54
+ "@byok-sdk/protocol": "0.12.0",
55
55
  "aws4fetch": "1.0.20",
56
56
  "fast-xml-parser": "^5.10.1",
57
57
  "pg": "^8.22.0"