@byok-sdk/cloud 0.10.1 → 0.10.2

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.
@@ -3,9 +3,15 @@
3
3
  *
4
4
  * Rows live under a `(tenant, deviceId)` composite key, so a cross-tenant read
5
5
  * is not "denied" — it addresses a different key space and finds nothing
6
- * (§12.6.2 layer 3). The pre-tenant index below holds the SAME record objects,
7
- * so a revocation applied through the composite key is immediately visible to
8
- * `/byok/challenge` and `/byok/token` with no second copy to keep in sync.
6
+ * (§12.6.2 layer 3). The pre-tenant index below maps a deviceId to that same
7
+ * composite key, so a revocation applied through the composite key is
8
+ * immediately visible to `/byok/challenge` and `/byok/token` with no second
9
+ * copy to keep in sync — and a deleted row is removed from BOTH in one step.
10
+ *
11
+ * Revocation DELETES. `DeviceRecord.revoked` survives as the field every auth
12
+ * path already reads, but no record this store writes ever carries `true`:
13
+ * revocation and machine supersession remove the record outright, so "revoked"
14
+ * and "never registered" are one indistinguishable answer rather than two.
9
15
  */
10
16
  import { type PresenceStore, type TenantId, type TenantReadiness } from '@byok-sdk/core';
11
17
  import type { DeviceDirectory, DeviceRecord, DeviceRegistration } from '../ports';
@@ -13,6 +19,10 @@ export declare class InMemoryDeviceDirectory implements DeviceDirectory {
13
19
  #private;
14
20
  register(tenant: TenantId, input: DeviceRegistration): Promise<DeviceRecord>;
15
21
  get(tenant: TenantId, deviceId: string): Promise<DeviceRecord | undefined>;
22
+ /**
23
+ * Revocation removes the registration. A no-op for a device this tenant does
24
+ * not own: revoking what you cannot address changes nothing.
25
+ */
16
26
  revoke(tenant: TenantId, deviceId: string): Promise<void>;
17
27
  recordCapabilities(tenant: TenantId, input: {
18
28
  readonly deviceId: string;
@@ -50,6 +50,12 @@ export interface DeviceRecord {
50
50
  readonly proofKeyId: string;
51
51
  /** Current proof signing-key rotation generation. */
52
52
  readonly proofKeyEpoch: number;
53
+ /**
54
+ * Always `false`. Revocation and machine supersession DELETE the record, so
55
+ * no implementation ever produces a `true` here; the field stays because
56
+ * every auth path reads it and a missing row must fail exactly as a revoked
57
+ * one used to.
58
+ */
53
59
  readonly revoked: boolean;
54
60
  /**
55
61
  * Client-hashed physical machine identity (lowercase hex SHA-256), when the
@@ -65,7 +71,7 @@ export interface DeviceRecord {
65
71
  */
66
72
  readonly capabilities?: readonly string[];
67
73
  }
68
- /** Everything `POST /byok/pair` knows at registration time. `tenantId` is the store's first parameter; `revoked` is the store's own to set. */
74
+ /** Everything `POST /byok/pair` knows at registration time. `tenantId` is the store's first parameter; `revoked` is the store's own to set (always `false`). */
69
75
  export interface DeviceRegistration {
70
76
  readonly productId: string;
71
77
  readonly deviceId: string;
@@ -75,27 +81,46 @@ export interface DeviceRegistration {
75
81
  readonly proofKeyEpoch: number;
76
82
  /**
77
83
  * Optional client-hashed physical machine identity from `PairRequest`. When
78
- * present, `register` revokes this tenant/product's prior non-revoked rows
79
- * carrying the same value before inserting, so one physical machine holds
80
- * one active device row per product. It names no tenant and no product of
84
+ * present, `register` DELETES this tenant/product's prior rows carrying the
85
+ * same value — and the device-scoped state those rows were the only reason
86
+ * to keep — before inserting, so one physical machine holds one device row
87
+ * per product. It names no tenant and no product of
81
88
  * its own — both still come from the redeemed pairing code's claims — so it
82
89
  * can only ever supersede rows the caller already addressed.
83
90
  */
84
91
  readonly machineId?: string;
85
92
  }
86
93
  export interface DeviceDirectory {
87
- /** Registers the device. With `input.machineId` set this ALSO revokes this tenant/product's prior non-revoked rows carrying that same machine identity — one physical machine, one active row. */
94
+ /** Registers the device. With `input.machineId` set this ALSO deletes this tenant/product's prior rows carrying that same machine identity — one physical machine, one row. */
88
95
  register(tenant: TenantId, input: DeviceRegistration): Promise<DeviceRecord>;
89
96
  /** The row `tenant` owns under `deviceId` — `undefined` including when the device exists under a DIFFERENT tenant. */
90
97
  get(tenant: TenantId, deviceId: string): Promise<DeviceRecord | undefined>;
98
+ /**
99
+ * Deletes the registration and the device-scoped state it owned (presence,
100
+ * challenge nonces, assertion-replay entries, inbound dedup) — never the
101
+ * history keyed by the device_id string. A no-op for a device this tenant
102
+ * cannot address. Afterwards every read answers exactly as it does for a
103
+ * device that was never registered.
104
+ *
105
+ * How far the deletion physically reaches is driver-scoped. The durable
106
+ * (Postgres) driver deletes the registration and every dependent
107
+ * device-scoped row in the same transaction, so the state is gone the moment
108
+ * `revoke` resolves. The in-memory reference directory deletes only the
109
+ * registration: its sibling stores are separate maps it does not own, so a
110
+ * presence hint expires on its own `expiresAt` TTL and, until then, is
111
+ * excluded from every projection by the readiness active-device filter
112
+ * (`activeDeviceIds`, built from the surviving rows). The observable contract
113
+ * above holds either way — no read path can see a revoked device.
114
+ */
91
115
  revoke(tenant: TenantId, deviceId: string): Promise<void>;
92
116
  list(tenant: TenantId): Promise<readonly DeviceRecord[]>;
93
- /** Set-wise tenant observation; revoked devices never contribute presence. */
117
+ /** Set-wise tenant observation. `revokedDeviceCount` is structurally 0 — a revoked device has no row to count. */
94
118
  readiness(tenant: TenantId, presence: PresenceStore): Promise<TenantReadiness>;
95
119
  /**
96
120
  * Persist a capability snapshot obtained from an authenticated device
97
- * message. Implementations may return `undefined` for an unknown/revoked
98
- * device; callers must fail closed in that case.
121
+ * message. Implementations may return `undefined` for an unknown (including
122
+ * revoked, which is the same absence) device; callers must fail closed in
123
+ * that case.
99
124
  */
100
125
  recordCapabilities(tenant: TenantId, input: {
101
126
  readonly deviceId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byok-sdk/cloud",
3
- "version": "0.10.1",
3
+ "version": "0.10.2",
4
4
  "description": "BYOK SDK hosted device surface: stateless frozen-v1 HTTP handlers over tenant-first @byok-sdk/core ports",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -44,8 +44,8 @@
44
44
  "clean": "rm -rf dist"
45
45
  },
46
46
  "dependencies": {
47
- "@byok-sdk/core": "0.10.1",
48
- "@byok-sdk/protocol": "0.10.1",
47
+ "@byok-sdk/core": "0.10.2",
48
+ "@byok-sdk/protocol": "0.10.2",
49
49
  "hono": "^4.12.30",
50
50
  "zod": "^4.4.3"
51
51
  }