@kubun/store-p2p 0.17.0 → 0.18.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.
Files changed (41) hide show
  1. package/lib/api.d.ts +24 -6
  2. package/lib/api.js +17 -2
  3. package/lib/definition.js +1 -1
  4. package/lib/hub-url.d.ts +1 -0
  5. package/lib/hub-url.js +3 -0
  6. package/lib/index.d.ts +8 -2
  7. package/lib/index.js +6 -0
  8. package/lib/migrations.js +150 -9
  9. package/lib/ops/app-lane.d.ts +32 -2
  10. package/lib/ops/app-lane.js +55 -2
  11. package/lib/ops/app-outbox.d.ts +44 -0
  12. package/lib/ops/app-outbox.js +236 -0
  13. package/lib/ops/catalog-offers.d.ts +18 -0
  14. package/lib/ops/catalog-offers.js +59 -0
  15. package/lib/ops/catalog-routes.d.ts +17 -0
  16. package/lib/ops/catalog-routes.js +47 -0
  17. package/lib/ops/catalog-sync.d.ts +122 -0
  18. package/lib/ops/catalog-sync.js +251 -0
  19. package/lib/ops/circles.d.ts +3 -4
  20. package/lib/ops/circles.js +31 -14
  21. package/lib/ops/control.d.ts +6 -0
  22. package/lib/ops/control.js +12 -3
  23. package/lib/ops/delegations.d.ts +31 -0
  24. package/lib/ops/delegations.js +71 -0
  25. package/lib/ops/document-sweep.d.ts +11 -0
  26. package/lib/ops/document-sweep.js +16 -0
  27. package/lib/ops/groups.d.ts +70 -22
  28. package/lib/ops/groups.js +130 -35
  29. package/lib/ops/hubs.js +8 -4
  30. package/lib/ops/log-handler-completion.d.ts +20 -0
  31. package/lib/ops/log-handler-completion.js +41 -0
  32. package/lib/ops/mls.d.ts +39 -6
  33. package/lib/ops/mls.js +115 -21
  34. package/lib/ops/own-agents.d.ts +68 -0
  35. package/lib/ops/own-agents.js +359 -0
  36. package/lib/ops/sync.d.ts +16 -0
  37. package/lib/ops/sync.js +25 -0
  38. package/lib/owner-classification.d.ts +14 -0
  39. package/lib/owner-classification.js +18 -0
  40. package/lib/tables.d.ts +188 -5
  41. package/package.json +16 -11
@@ -0,0 +1,14 @@
1
+ import type { StoreProvider } from '@kubun/db';
2
+ export type DocumentOwnerClass = 'own' | 'foreign' | 'unresolved';
3
+ export type ClassifyDocumentOwnerParams = {
4
+ ownerDID: string;
5
+ /** The controller DID whose documents this device keeps in custody. Not checked here. */
6
+ custodyOwner: string;
7
+ stores: StoreProvider;
8
+ };
9
+ /**
10
+ * Historical, not current eligibility: a revoked or departed device still owns
11
+ * what it wrote. `foreign` needs positive evidence; a missing binding is not
12
+ * evidence, so anything unattributed is `unresolved` and must be kept.
13
+ */
14
+ export declare function classifyDocumentOwner(params: ClassifyDocumentOwnerParams): Promise<DocumentOwnerClass>;
@@ -0,0 +1,18 @@
1
+ import { DID_PREFIX } from '@kokuin/controller';
2
+ import { normalizeDID } from '@kokuin/token';
3
+ /**
4
+ * Historical, not current eligibility: a revoked or departed device still owns
5
+ * what it wrote. `foreign` needs positive evidence; a missing binding is not
6
+ * evidence, so anything unattributed is `unresolved` and must be kept.
7
+ */ export async function classifyDocumentOwner(params) {
8
+ const owner = normalizeDID(params.ownerDID);
9
+ const custody = normalizeDID(params.custodyOwner);
10
+ if (owner === custody) return 'own';
11
+ const store = await params.stores.getStore('p2p');
12
+ const controllers = await store.listBindingControllers(owner);
13
+ // A DID also bound to another controller stays own: a receipt costs less than a lost document.
14
+ if (controllers.includes(custody)) return 'own';
15
+ // `did:kokuin:` is self-certifying: kumiai accepts a leaf binding only to a log folding to it.
16
+ if (owner.startsWith(DID_PREFIX)) return 'foreign';
17
+ return controllers.length > 0 ? 'foreign' : 'unresolved';
18
+ }
package/lib/tables.d.ts CHANGED
@@ -1,24 +1,28 @@
1
1
  import type { CreatedAtColumn, UpdatedAtColumn } from '@kubun/db-adapter';
2
+ import type { AnchorSlot, AppOutboxEntry, CommitCursor } from '@kumiai/rpc';
2
3
  import type { ColumnType, Insertable, Selectable, Updateable } from 'kysely';
3
4
  type DefaultedStringColumn = ColumnType<string, string | undefined, string>;
4
5
  type DefaultedNumberColumn = ColumnType<number, number | undefined, number>;
5
6
  type NullableStringColumn = ColumnType<string | null, string | null | undefined, string | null>;
7
+ type NullableNumberColumn = ColumnType<number | null, number | null | undefined, number | null>;
6
8
  type JSONArrayColumn<T> = ColumnType<Array<T>, Array<T> | undefined, Array<T>>;
7
9
  type JSONValueColumn<T> = ColumnType<T, T, T>;
8
10
  /**
9
11
  * Whether this device belongs to the group right now.
10
12
  *
11
- * Only the two states that have a writer. An `invited` state would have none:
13
+ * An `invited` state would have no writer:
12
14
  * a group row is written when a join COMPLETES, so there is no moment at which
13
15
  * a row exists and the device has merely been asked.
14
16
  */
15
- export type GroupStatus = 'joined' | 'left';
17
+ export type GroupStatus = 'joined' | 'leaving' | 'left';
18
+ export type GroupKind = 'standard' | 'ownAgents';
16
19
  export type GroupTable = {
17
20
  id: string;
18
21
  name: string;
19
22
  description: DefaultedStringColumn;
20
23
  created_by: string;
21
24
  hlc: string;
25
+ kind: ColumnType<GroupKind, GroupKind | undefined, GroupKind>;
22
26
  /**
23
27
  * Leaving keeps the row: its circles, circle members, peer announcements and
24
28
  * hub bindings hang off it, and a device invited back would otherwise have to
@@ -28,11 +32,22 @@ export type GroupTable = {
28
32
  * row for it.
29
33
  */
30
34
  status: DefaultedStringColumn;
35
+ /**
36
+ * Set in the same transaction as every handle advance, to a token naming that
37
+ * advance; cleared only by a projection reconcile that started from the same
38
+ * token. NULL means the control projections match the current handle.
39
+ */
40
+ projections_dirty: NullableStringColumn;
41
+ /**
42
+ * The handle epoch the last successful reconcile caught the projections up
43
+ * to. NULL until a reconcile completes.
44
+ */
45
+ projections_epoch: NullableNumberColumn;
31
46
  created_at: CreatedAtColumn;
32
47
  updated_at: UpdatedAtColumn;
33
48
  };
34
49
  export type Group = Selectable<GroupTable>;
35
- export type InsertGroup = Insertable<GroupTable>;
50
+ export type InsertGroup = Omit<Insertable<GroupTable>, 'kind'>;
36
51
  export type GroupMemberTable = {
37
52
  group_id: string;
38
53
  member_did: string;
@@ -44,12 +59,67 @@ export type GroupMemberTable = {
44
59
  };
45
60
  export type GroupMember = Selectable<GroupMemberTable>;
46
61
  export type InsertGroupMember = Insertable<GroupMemberTable>;
62
+ export type CatalogOfferTable = {
63
+ owner: string;
64
+ exposure_id: string;
65
+ catalog_id: string;
66
+ group_id: string;
67
+ circle_id: string;
68
+ };
69
+ export type CatalogOfferRow = Selectable<CatalogOfferTable>;
70
+ export type CatalogRouteTable = {
71
+ owner: string;
72
+ catalog_id: string;
73
+ group_id: string;
74
+ pull: number;
75
+ push: number;
76
+ };
77
+ export type CatalogCursorTable = {
78
+ peer_did: string;
79
+ group_id: string;
80
+ rules_hash: string;
81
+ last_key: NullableStringColumn;
82
+ last_token: NullableStringColumn;
83
+ cycle_clear: DefaultedNumberColumn;
84
+ /** JSON object: bound-free rule key to its absolute `{ from, to }`. */
85
+ bounds: string;
86
+ };
87
+ export type DocumentRepairTable = {
88
+ group_id: string;
89
+ document_id: string;
90
+ listed_by: NullableStringColumn;
91
+ holdings_digest: string;
92
+ created_at: number;
93
+ attempted_at: number;
94
+ };
95
+ export type DocumentRepairAnswerTable = {
96
+ group_id: string;
97
+ document_id: string;
98
+ peer_did: string;
99
+ answer: string;
100
+ };
101
+ export type ObservationRepairScheduleTable = {
102
+ document_id: string;
103
+ fetch_hlc: string;
104
+ attempts: number;
105
+ next_attempt_at: number;
106
+ tried_count: number;
107
+ };
108
+ export type ObservationRepairPeerTable = {
109
+ document_id: string;
110
+ peer_did: string;
111
+ };
112
+ export type GroupLeaveRequestTable = {
113
+ group_id: string;
114
+ member_did: string;
115
+ hlc: string;
116
+ };
117
+ export type GroupLeaveRequest = Selectable<GroupLeaveRequestTable>;
47
118
  export type CircleTable = {
48
119
  id: string;
49
120
  group_id: string;
50
121
  name: string;
51
122
  description: DefaultedStringColumn;
52
- catalog_ids: JSONArrayColumn<string>;
53
123
  hlc: string;
54
124
  removed_at_hlc: NullableStringColumn;
55
125
  open: DefaultedNumberColumn;
@@ -72,6 +142,10 @@ export type InsertCircleMember = Insertable<CircleMemberTable>;
72
142
  export type GroupDelegationTable = {
73
143
  group_id: string;
74
144
  jti: string;
145
+ pending_grant_hlc: NullableStringColumn;
146
+ pending_grant_digest: NullableStringColumn;
147
+ pending_revocation_hlc: NullableStringColumn;
148
+ pending_revocation_digest: NullableStringColumn;
75
149
  };
76
150
  export type GroupDelegation = Selectable<GroupDelegationTable>;
77
151
  export type InsertGroupDelegation = Insertable<GroupDelegationTable>;
@@ -81,6 +155,12 @@ export type GroupMLSStateTable = {
81
155
  mls_state: Uint8Array;
82
156
  credential: string;
83
157
  epoch: number;
158
+ /** Orders secret-tree mutations within one MLS epoch. */
159
+ state_version: DefaultedNumberColumn;
160
+ /** Retained absence marker; preserves the row version across leave and rejoin. */
161
+ is_deleted: DefaultedNumberColumn;
162
+ /** Exclusive upper bound of sender generations reserved durably for this epoch. */
163
+ sender_generation_hwm: DefaultedNumberColumn;
84
164
  /**
85
165
  * The handle's signed control-ledger tokens, JSON-encoded in enactment order.
86
166
  * Replayed into `restoreGroup` so a restored handle folds the same roster it
@@ -120,6 +200,14 @@ export type BroadcastLogTable = {
120
200
  };
121
201
  export type BroadcastLogEntry = Selectable<BroadcastLogTable>;
122
202
  export type InsertBroadcastLogEntry = Insertable<BroadcastLogTable>;
203
+ export type LogHandlerCompletionTable = {
204
+ group_id: string;
205
+ procedure: string;
206
+ entity: string;
207
+ hlc: string;
208
+ notify: DefaultedNumberColumn;
209
+ completed: DefaultedNumberColumn;
210
+ };
123
211
  export type ControlRequestTable = {
124
212
  id: string;
125
213
  group_id: string;
@@ -129,6 +217,7 @@ export type ControlRequestTable = {
129
217
  result: NullableStringColumn;
130
218
  enqueued_at: string;
131
219
  settled_at: NullableStringColumn;
220
+ settled_epoch: NullableNumberColumn;
132
221
  };
133
222
  export type ControlRequest = Selectable<ControlRequestTable>;
134
223
  export type InsertControlRequest = Insertable<ControlRequestTable>;
@@ -142,16 +231,34 @@ export type CommitJournalEntryTable = {
142
231
  bodies: JSONArrayColumn<string>;
143
232
  kind: string;
144
233
  journal: Uint8Array;
234
+ holds_log_sends: DefaultedNumberColumn;
235
+ };
236
+ export type CommitJournalEntry = Omit<Selectable<CommitJournalEntryTable>, 'holds_log_sends'> & {
237
+ holds_log_sends: boolean;
145
238
  };
146
- export type CommitJournalEntry = Selectable<CommitJournalEntryTable>;
147
239
  export type InsertCommitJournalEntry = Insertable<CommitJournalEntryTable>;
148
240
  export type GroupAnchorTable = {
149
241
  group_id: string;
150
242
  secret: Uint8Array;
151
243
  epoch: number;
244
+ pending: ColumnType<AnchorSlot['pending'] | null, AnchorSlot['pending'] | null, AnchorSlot['pending'] | null>;
152
245
  };
153
246
  export type GroupAnchor = Selectable<GroupAnchorTable>;
154
247
  export type InsertGroupAnchor = Insertable<GroupAnchorTable>;
248
+ export type AppOutboxTable = {
249
+ group_id: string;
250
+ seq: number;
251
+ protocol: string;
252
+ prc: string;
253
+ data: Uint8Array;
254
+ iv: Uint8Array;
255
+ last_attempt: JSONValueColumn<AppOutboxEntry['lastAttempt']>;
256
+ };
257
+ export type AppOutboxStateTable = {
258
+ group_id: string;
259
+ key: ColumnType<Uint8Array | null, Uint8Array | null | undefined, Uint8Array | null>;
260
+ commit_cursor: ColumnType<CommitCursor | null, CommitCursor | null | undefined, CommitCursor | null>;
261
+ };
155
262
  export type AppCursorTable = {
156
263
  group_id: string;
157
264
  topic_id: string;
@@ -159,6 +266,19 @@ export type AppCursorTable = {
159
266
  };
160
267
  export type AppCursor = Selectable<AppCursorTable>;
161
268
  export type InsertAppCursor = Insertable<AppCursorTable>;
269
+ export type PendingAppFrameTable = {
270
+ group_id: string;
271
+ device_id: string;
272
+ frame_id: string;
273
+ topic_id: string;
274
+ protocol: string;
275
+ segment: number;
276
+ position: string;
277
+ payload: Uint8Array;
278
+ sender_did: string;
279
+ created_at: CreatedAtColumn;
280
+ };
281
+ export type PendingAppFrameRow = Selectable<PendingAppFrameTable>;
162
282
  export type HubTable = {
163
283
  id: string;
164
284
  label: string | null;
@@ -222,9 +342,68 @@ export type PaginatedResult<T> = {
222
342
  entries: Array<T>;
223
343
  hasMore: boolean;
224
344
  };
345
+ export type OwnAgentsRecordTable = {
346
+ type: string;
347
+ owner: string;
348
+ id: string;
349
+ hlc: string;
350
+ op_jwt: string;
351
+ op_hash: string;
352
+ payload: Uint8Array | null;
353
+ payload_digest: string | null;
354
+ deleted: number;
355
+ meta: JSONValueColumn<Record<string, unknown>>;
356
+ created_at: CreatedAtColumn;
357
+ };
358
+ export type OwnAgentsGrantKind = 'trustedGrant' | 'leaf' | 'leafDirect';
359
+ export type OwnAgentsGrantTable = {
360
+ controller_did: string;
361
+ kind: OwnAgentsGrantKind;
362
+ token: string;
363
+ issued_at: number;
364
+ expires_at: number;
365
+ };
366
+ export type OwnAgentsJoinCapabilityTable = {
367
+ group_id: string;
368
+ recipient_did: string;
369
+ capability_id: string;
370
+ token: string;
371
+ expires_at: number;
372
+ signer_did: NullableStringColumn;
373
+ signer_leaf_key: ColumnType<Uint8Array | null, Uint8Array | null | undefined, Uint8Array | null>;
374
+ signer_capability: NullableStringColumn;
375
+ signer_prefix: ColumnType<{
376
+ events: Array<unknown>;
377
+ } | null, {
378
+ events: Array<unknown>;
379
+ } | null | undefined, {
380
+ events: Array<unknown>;
381
+ } | null>;
382
+ };
383
+ export type BindingEvidenceTable = {
384
+ controller_did: string;
385
+ device_did: string;
386
+ first_seen_at: CreatedAtColumn;
387
+ };
225
388
  export type P2PStoreTables = {
389
+ kubun_p2p_access_default_revocations: {
390
+ controller_did: string;
391
+ device_did: string;
392
+ };
393
+ kubun_p2p_binding_evidence: BindingEvidenceTable;
394
+ kubun_p2p_own_agents_records: OwnAgentsRecordTable;
395
+ kubun_p2p_own_agents_grants: OwnAgentsGrantTable;
396
+ kubun_p2p_own_agents_join_capabilities: OwnAgentsJoinCapabilityTable;
226
397
  kubun_p2p_groups: GroupTable;
227
398
  kubun_p2p_group_members: GroupMemberTable;
399
+ kubun_p2p_catalog_offers: CatalogOfferTable;
400
+ kubun_p2p_catalog_routes: CatalogRouteTable;
401
+ kubun_p2p_catalog_cursors: CatalogCursorTable;
402
+ kubun_p2p_document_repairs: DocumentRepairTable;
403
+ kubun_p2p_document_repair_answers: DocumentRepairAnswerTable;
404
+ kubun_p2p_observation_repairs: ObservationRepairScheduleTable;
405
+ kubun_p2p_observation_repair_peers: ObservationRepairPeerTable;
406
+ kubun_p2p_group_leave_requests: GroupLeaveRequestTable;
228
407
  kubun_p2p_circles: CircleTable;
229
408
  kubun_p2p_circle_members: CircleMemberTable;
230
409
  kubun_p2p_group_delegation: GroupDelegationTable;
@@ -232,10 +411,14 @@ export type P2PStoreTables = {
232
411
  kubun_p2p_sync_peers: SyncPeerTable;
233
412
  kubun_p2p_sync_checkpoints: SyncCheckpointTable;
234
413
  kubun_p2p_broadcast_log: BroadcastLogTable;
414
+ kubun_p2p_log_handler_completion: LogHandlerCompletionTable;
235
415
  kubun_p2p_control_requests: ControlRequestTable;
236
416
  kubun_p2p_commit_journal: CommitJournalEntryTable;
237
417
  kubun_p2p_group_anchor: GroupAnchorTable;
418
+ kubun_p2p_app_outbox: AppOutboxTable;
419
+ kubun_p2p_app_outbox_state: AppOutboxStateTable;
238
420
  kubun_p2p_app_cursor: AppCursorTable;
421
+ kubun_p2p_app_pending: PendingAppFrameTable;
239
422
  kubun_p2p_hubs: HubTable;
240
423
  kubun_p2p_group_hubs: GroupHubTable;
241
424
  kubun_p2p_peer_announcements: PeerAnnouncementTable;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubun/store-p2p",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "keywords": [],
5
5
  "license": "see LICENSE.md",
6
6
  "sideEffects": false,
@@ -15,18 +15,23 @@
15
15
  "LICENSE.md"
16
16
  ],
17
17
  "dependencies": {
18
- "@kokuin/capability": "^0.3.0",
19
- "@kokuin/token": "^0.5.0",
20
- "@kubun/db": "^0.17.0",
21
- "@kubun/db-adapter": "^0.17.0",
22
- "@kubun/logger": "^0.17.0",
23
- "kysely": "^0.29.5"
18
+ "@kokuin/capability": "^0.4.2",
19
+ "@kokuin/controller": "^0.1.0",
20
+ "@kokuin/token": "^0.5.1",
21
+ "@kubun/db": "^0.18.0",
22
+ "@kubun/db-adapter": "^0.18.0",
23
+ "@kubun/logger": "^0.18.0",
24
+ "@kubun/records": "^0.18.0",
25
+ "@kumiai/rpc": "^0.10.9",
26
+ "@noble/ciphers": "^2.4.0",
27
+ "@sozai/runtime": "^0.1.0",
28
+ "kysely": "^0.29.7"
24
29
  },
25
30
  "devDependencies": {
26
- "@kubun/db-better-sqlite": "^0.17.0",
27
- "@kubun/db-postgres": "^0.17.0",
28
- "@kubun/hlc": "^0.17.0",
29
- "@testcontainers/postgresql": "^12.1.0"
31
+ "@kubun/db-better-sqlite": "^0.18.0",
32
+ "@kubun/db-postgres": "^0.18.0",
33
+ "@kubun/hlc": "^0.18.0",
34
+ "@testcontainers/postgresql": "^12.2.1"
30
35
  },
31
36
  "publishConfig": {
32
37
  "access": "public"