@byok-sdk/core 0.30.2 → 0.32.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/dist/mailbox.d.ts CHANGED
@@ -15,6 +15,7 @@ import type { CollectExpiredInput, CollectExpiredResult } from './retention';
15
15
  * `@byok-sdk/protocol`, and core must not grow an edge to it (§12.1 invariant).
16
16
  */
17
17
  import type { ContentHash } from './blob';
18
+ import { ByokCoreError } from './errors';
18
19
  import type { TenantId } from './tenant';
19
20
  /**
20
21
  * Row lifecycle. `pending` and `acked` are derived from the device cursor;
@@ -46,9 +47,12 @@ export interface MailboxBody {
46
47
  * How an append is admitted against the tenant's `mailboxLimitBytes`.
47
48
  *
48
49
  * Mailbox bytes have one authority: the retained rows. Usage is the tenant's
49
- * `SUM(byteSize)` over every retained row in any state, and the limit is
50
- * checked in the same serialized unit that inserts the row — there is no
51
- * counter for a writer to forget, so deleting a row needs no accounting.
50
+ * `SUM(byteSize)` over its `pending` rows, and the limit is checked in the same
51
+ * serialized unit that inserts the row — there is no counter for a writer to
52
+ * forget, so acking, dead-lettering or deleting a row needs no accounting.
53
+ * `acked` and `expired` rows stay retained for retention and §12.7.5
54
+ * visibility but hold no quota, so undelivered-and-lost work can never wedge
55
+ * a tenant at its limit.
52
56
  *
53
57
  * - `limited`: refused with `storage_quota_exceeded` when `used + byteSize`
54
58
  * exceeds the limit, and with `storage_entitlement_missing` when the tenant
@@ -76,13 +80,14 @@ export interface MailboxAppendInput {
76
80
  readonly materialize: (seq: number) => MailboxBody | Promise<MailboxBody>;
77
81
  /**
78
82
  * Limit admission for a NEW row. An existing `messageId` returns its row
79
- * before any check, so a retry is never refused for capacity.
83
+ * before any capacity check, so a retry is never refused for capacity. A
84
+ * closed stream refuses before even that (see {@link MailboxStore.closeStream}).
80
85
  */
81
86
  readonly admission: MailboxAdmission;
82
87
  }
83
88
  /** Tenant mailbox usage, derived from the retained rows on every read. */
84
89
  export interface MailboxUsage {
85
- /** `SUM(byteSize)` over every retained row (`pending`, `acked`, `expired`). */
90
+ /** `SUM(byteSize)` over the retained `pending` rows. */
86
91
  readonly bytes: bigint;
87
92
  }
88
93
  export interface MailboxReadQuery {
@@ -113,8 +118,9 @@ export interface MailboxPage {
113
118
  * whole point of the field.
114
119
  *
115
120
  * `1` for a mailbox that has lost nothing, so a fresh device's cursor `0` is
116
- * always inside the window. Monotonic for as long as dead-lettered rows are
117
- * retained, which §12.7.5 requires.
121
+ * always inside the window. Monotonic: `collectExpired` reaps old dead
122
+ * letters but always keeps each device's highest-seq one, which is the
123
+ * evidence this floor is read from.
118
124
  *
119
125
  * This is a READ of the retained window, never a second retention authority:
120
126
  * `collectExpired` decides what is retained, this reports what it cost.
@@ -144,7 +150,7 @@ export interface MailboxCursorState {
144
150
  /**
145
151
  * Retention cutoffs.
146
152
  *
147
- * Both instants must be **canonical ISO-8601 UTC** (`YYYY-MM-DDTHH:mm:ss.sssZ`);
153
+ * All three instants must be **canonical ISO-8601 UTC** (`YYYY-MM-DDTHH:mm:ss.sssZ`);
148
154
  * anything else is rejected with `timestamp_not_canonical`. A retention sweep
149
155
  * deletes rows, so an offset-bearing string that a string comparison and a SQL
150
156
  * `timestamptz` comparison read differently is the worst place to guess — see
@@ -153,8 +159,41 @@ export interface MailboxCursorState {
153
159
  export interface MailboxCollectExpiredInput extends CollectExpiredInput {
154
160
  /** Acked rows appended before this instant are deleted. */
155
161
  readonly ackedBefore: string;
156
- /** Unacked rows appended before this instant move to `expired`, never deleted here. */
162
+ /** Unacked rows appended before this instant move to `expired`. */
157
163
  readonly expireUnackedBefore: string;
164
+ /**
165
+ * Dead letters (`expired` rows) appended before this instant are deleted,
166
+ * except each device's highest-seq dead letter: that row is the evidence for
167
+ * `recoverableFrom`, so a reap never moves the floor. Only rows that were
168
+ * already `expired` when the call started are eligible; a row this call
169
+ * expires waits for the next call. Reaped rows count in `removed`.
170
+ */
171
+ readonly reapDeadLettersBefore: string;
172
+ }
173
+ /**
174
+ * Which of one device's `pending` rows {@link MailboxStore.expirePending}
175
+ * dead-letters. A listed seq that is not `pending` (acked, already expired, or
176
+ * never written) is skipped.
177
+ */
178
+ export interface MailboxExpirePendingInput {
179
+ readonly deviceId: string;
180
+ readonly seqs: readonly number[];
181
+ }
182
+ /** Why a device's stream was closed: the device it addressed was retired. */
183
+ export declare const MAILBOX_STREAM_CLOSE_CAUSES: readonly ['device_revoked', 'device_superseded'];
184
+ export type MailboxStreamCloseCause = (typeof MAILBOX_STREAM_CLOSE_CAUSES)[number];
185
+ /** An append refused because its device's stream was closed when the device was retired. */
186
+ export declare class MailboxStreamClosedError extends ByokCoreError {
187
+ readonly deviceId: string;
188
+ readonly closeCause: MailboxStreamCloseCause;
189
+ constructor(deviceId: string, closeCause: MailboxStreamCloseCause, options?: ErrorOptions);
190
+ }
191
+ export interface MailboxCloseStreamInput {
192
+ readonly deviceId: string;
193
+ readonly cause: MailboxStreamCloseCause;
194
+ }
195
+ export interface MailboxReopenStreamInput {
196
+ readonly deviceId: string;
158
197
  }
159
198
  /**
160
199
  * Mailbox port. Tenant-first, async.
@@ -165,11 +204,13 @@ export interface MailboxCollectExpiredInput extends CollectExpiredInput {
165
204
  * server has returned to that device),
166
205
  * `timestamp_not_canonical` (a retention cutoff that is not a canonical
167
206
  * ISO-8601 UTC instant), `storage_quota_exceeded` and
168
- * `storage_entitlement_missing` (a `limited` append refused at admission).
207
+ * `storage_entitlement_missing` (a `limited` append refused at admission), and
208
+ * `mailbox_stream_closed` ({@link MailboxStreamClosedError}, any append to a
209
+ * closed stream).
169
210
  */
170
211
  export interface MailboxStore {
171
212
  append(tenant: TenantId, input: MailboxAppendInput): Promise<MailboxMessage>;
172
- /** The tenant's mailbox bytes, summed from the retained rows. */
213
+ /** The tenant's mailbox bytes, summed from the retained `pending` rows. */
173
214
  readUsage(tenant: TenantId): Promise<MailboxUsage>;
174
215
  /** Pure read. Never advances the cursor, never deletes, never marks acked. */
175
216
  readAfter(tenant: TenantId, query: MailboxReadQuery): Promise<MailboxPage>;
@@ -178,6 +219,36 @@ export interface MailboxStore {
178
219
  /** The one and only ack. Monotonic and bounded by the recorded delivery watermark. */
179
220
  advanceCursor(tenant: TenantId, input: MailboxAdvanceCursorInput): Promise<MailboxCursorState>;
180
221
  readCursor(tenant: TenantId, deviceId: string): Promise<MailboxCursorState>;
181
- /** Retention hook: delete acked rows, dead-letter unacked ones. */
222
+ /** Retention hook: delete acked rows, reap old dead letters below the floor, dead-letter unacked ones. */
182
223
  collectExpired(tenant: TenantId, input: MailboxCollectExpiredInput): Promise<CollectExpiredResult>;
224
+ /**
225
+ * Dead-letters the listed undelivered rows of a task the Host abandoned,
226
+ * without waiting for retention. Only `pending` rows move, and only to
227
+ * `expired`: nothing is deleted, no row returns to `pending`, the cursor is
228
+ * untouched, and the device's `seq` allocator keeps counting. The moved rows
229
+ * stop holding mailbox quota and raise `recoverableFrom` exactly as a
230
+ * retention sweep would. Serialized with the device's appends and acks.
231
+ */
232
+ expirePending(tenant: TenantId, input: MailboxExpirePendingInput): Promise<{
233
+ readonly expired: number;
234
+ }>;
235
+ /**
236
+ * Closes a retired device's stream and dead-letters every `pending` row on
237
+ * it, as one step serialized with the device's appends: an append ordered
238
+ * before it has its row expired here, and every append ordered after it is
239
+ * refused with `mailbox_stream_closed`, whatever its admission and even for
240
+ * a known `messageId`. The stream need not exist yet; closing creates it
241
+ * closed. Closing a closed stream keeps its first close instant, records the
242
+ * new cause and still expires pending rows. Cursor, `seq` and
243
+ * `recoverableFrom` move exactly as {@link expirePending} moves them.
244
+ */
245
+ closeStream(tenant: TenantId, input: MailboxCloseStreamInput): Promise<{
246
+ readonly expired: number;
247
+ }>;
248
+ /**
249
+ * Reopens a stream for a device registered again under the same id. Rows,
250
+ * cursor, `seq` and `recoverableFrom` are untouched, so delivery stays
251
+ * monotonic across the close. A no-op on an open or absent stream.
252
+ */
253
+ reopenStream(tenant: TenantId, input: MailboxReopenStreamInput): Promise<void>;
183
254
  }
@@ -18,12 +18,34 @@ import type { TenantId } from './tenant';
18
18
  /** The five presence levels. */
19
19
  export declare const PRESENCE_LEVELS: readonly ['online', 'thinking', 'working', 'error', 'offline'];
20
20
  export type PresenceLevel = (typeof PRESENCE_LEVELS)[number];
21
+ /**
22
+ * The capability matrix a daemon reported for one runtime: the shape of
23
+ * `@byok-sdk/protocol`'s `RuntimeCapabilities` (#1103). Omitted fields are unknown.
24
+ */
25
+ export interface PresenceRuntimeCapabilities {
26
+ readonly steer?: boolean;
27
+ readonly resume?: boolean;
28
+ readonly approvalInteractive?: boolean;
29
+ readonly mcpToolsets?: boolean;
30
+ readonly messageDraft?: boolean;
31
+ readonly toolAuthorizationAuto?: boolean;
32
+ readonly durablePi?: boolean;
33
+ readonly preparedLane?: boolean;
34
+ readonly isolation?: {
35
+ readonly userContext?: boolean;
36
+ readonly userMcpServers?: boolean;
37
+ readonly sessionPersistence?: boolean;
38
+ };
39
+ }
21
40
  /** Runtime facts observed by a daemon's real local probe. Omitted fields are unknown. */
22
41
  export interface PresenceRuntimeFact {
23
42
  readonly id: string;
24
43
  readonly version?: string;
25
44
  readonly authPresent?: boolean;
45
+ readonly capabilities?: PresenceRuntimeCapabilities;
26
46
  }
47
+ /** A frozen copy of one runtime fact holding only the known fields; every presence store returns this. */
48
+ export declare function freezePresenceRuntimeFact(runtime: PresenceRuntimeFact): PresenceRuntimeFact;
27
49
  /** Unexpired presence facts nested in the tenant aggregate. */
28
50
  export interface TenantReadinessPresence {
29
51
  readonly level: PresenceLevel;