@kubun/store-p2p 0.19.0 → 0.20.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/lib/api.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { StoreAPIContext } from '@kubun/db';
1
2
  import type { Adapter } from '@kubun/db-adapter';
2
3
  import { type Runtime } from '@sozai/runtime';
3
4
  import type { Kysely } from 'kysely';
@@ -17,6 +18,7 @@ import { type LogHandlerCompletionOps } from './ops/log-handler-completion.js';
17
18
  import { type MLSOps } from './ops/mls.js';
18
19
  import { type OwnAgentsOps } from './ops/own-agents.js';
19
20
  import { type SyncOps } from './ops/sync.js';
21
+ import { type P2PScope } from './scope.js';
20
22
  import type { P2PStoreTables } from './tables.js';
21
23
  export type { AppLaneOps, OpenedAppFrameExpectedVersion, OpenedAppFrameRecord, SaveOpenedAppFrameParams, } from './ops/app-lane.js';
22
24
  export type { AppOutboxOps } from './ops/app-outbox.js';
@@ -39,8 +41,9 @@ export type { PruneBroadcastLogParams, SyncOps } from './ops/sync.js';
39
41
  * tables and its own helpers; this intersection is the only coupling between
40
42
  * them, so a caller still sees one flat API.
41
43
  */
42
- export type P2PStoreAPI = GroupOps & CatalogOfferOps & CatalogRouteOps & CatalogSyncOps & CircleOps & DelegationOps & MLSOps & SyncOps & ControlOps & AppLaneOps & AppOutboxOps & LogHandlerCompletionOps & HubOps & DiscoveryOps & DocumentSweepOps & OwnAgentsOps;
44
+ export type P2PStoreAPI = P2PScope & GroupOps & CatalogOfferOps & CatalogRouteOps & CatalogSyncOps & CircleOps & DelegationOps & MLSOps & SyncOps & ControlOps & AppLaneOps & AppOutboxOps & LogHandlerCompletionOps & HubOps & DiscoveryOps & DocumentSweepOps & OwnAgentsOps;
43
45
  export type CreateP2PStoreOptions = {
46
+ context: StoreAPIContext;
44
47
  getRandomValues?: Runtime['getRandomValues'];
45
48
  };
46
- export declare function createP2PStore(db: Kysely<P2PStoreTables>, adapter: Adapter, options?: CreateP2PStoreOptions): P2PStoreAPI;
49
+ export declare function createP2PStore(db: Kysely<P2PStoreTables>, adapter: Adapter, options: CreateP2PStoreOptions): P2PStoreAPI;
package/lib/api.js CHANGED
@@ -15,20 +15,24 @@ import { createLogHandlerCompletionOps } from './ops/log-handler-completion.js';
15
15
  import { createMLSOps } from './ops/mls.js';
16
16
  import { createOwnAgentsOps } from './ops/own-agents.js';
17
17
  import { createSyncOps } from './ops/sync.js';
18
- export function createP2PStore(db, adapter, options = {}) {
18
+ import { runP2PScope } from './scope.js';
19
+ export function createP2PStore(db, adapter, options) {
19
20
  return {
20
- ...createGroupOps(db, adapter),
21
- ...createOwnAgentsOps(db, adapter),
21
+ async [runP2PScope] (run) {
22
+ return await run(db, options.context);
23
+ },
24
+ ...createGroupOps(db, adapter, options.context),
25
+ ...createOwnAgentsOps(db, adapter, options.context),
22
26
  ...createCatalogOfferOps(db),
23
27
  ...createCatalogRouteOps(db),
24
- ...createCatalogSyncOps(db, adapter),
28
+ ...createCatalogSyncOps(db, adapter, options.context),
25
29
  ...createCircleOps(db, adapter),
26
- ...createDelegationOps(db),
30
+ ...createDelegationOps(db, options.context),
27
31
  ...createMLSOps(db, adapter),
28
32
  ...createSyncOps(db, adapter),
29
33
  ...createControlOps(db, adapter),
30
34
  ...createAppLaneOps(db, adapter),
31
- ...createAppOutboxOps(db, adapter, options.getRandomValues ?? createRuntime().getRandomValues),
35
+ ...createAppOutboxOps(db, adapter, options.getRandomValues ?? createRuntime().getRandomValues, options.context),
32
36
  ...createHubOps(db, adapter),
33
37
  ...createLogHandlerCompletionOps(db),
34
38
  ...createDiscoveryOps(db, adapter),
@@ -1,4 +1,9 @@
1
- import type { StoreDefinition } from '@kubun/db';
1
+ import type { StoreDefinition, StoreProvider } from '@kubun/db';
2
+ import type { Runtime } from '@sozai/runtime';
2
3
  import type { P2PStoreAPI } from './api.js';
3
4
  import type { P2PStoreTables } from './tables.js';
5
+ export declare class P2PStoreConfigurationError extends Error {
6
+ constructor(field: 'getRandomValues');
7
+ }
8
+ export declare function configureP2PStore(provider: StoreProvider, deps: Pick<Runtime, 'getRandomValues'>): void;
4
9
  export declare const p2pStoreDefinition: StoreDefinition<P2PStoreTables, P2PStoreAPI>;
package/lib/definition.js CHANGED
@@ -1,7 +1,43 @@
1
+ import { createRuntime } from '@sozai/runtime';
1
2
  import { createP2PStore } from './api.js';
2
3
  import { getP2PMigrations } from './migrations.js';
4
+ const dependencies = new WeakMap();
5
+ const fallbackRuntime = createRuntime();
6
+ export class P2PStoreConfigurationError extends Error {
7
+ constructor(field){
8
+ super(`p2p: conflicting dependency ${field}`);
9
+ this.name = 'P2PStoreConfigurationError';
10
+ }
11
+ }
12
+ export function configureP2PStore(provider, deps) {
13
+ const existing = dependencies.get(provider);
14
+ if (existing != null) {
15
+ if (existing.getRandomValues !== deps.getRandomValues) {
16
+ throw new P2PStoreConfigurationError('getRandomValues');
17
+ }
18
+ return;
19
+ }
20
+ dependencies.set(provider, {
21
+ ...deps
22
+ });
23
+ }
3
24
  export const p2pStoreDefinition = {
4
25
  name: 'p2p',
26
+ unprefixedTables: ()=>false,
5
27
  migrations: getP2PMigrations,
6
- createAPI: (db, adapter)=>createP2PStore(db, adapter)
28
+ sync: {
29
+ p2p_own_agents_grants: {
30
+ key: [
31
+ 'controller_did',
32
+ 'kind'
33
+ ],
34
+ compare: [
35
+ 'issued_at'
36
+ ]
37
+ }
38
+ },
39
+ createAPI: (db, adapter, context)=>createP2PStore(db, adapter, {
40
+ context,
41
+ getRandomValues: (array)=>(dependencies.get(context.root) ?? fallbackRuntime).getRandomValues(array)
42
+ })
7
43
  };
package/lib/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import type { StoreProvider } from '@kubun/db';
2
2
  import type { P2PStoreAPI } from './api.js';
3
3
  export type { BeginLogHandlerCompletionParams, BindingEvidence, CatalogCursorBounds, CatalogCursorState, CatalogOffer, CatalogRoute, CatalogSyncOps, ControlRequestKind, ControlRequestOutcome, CreateP2PStoreOptions, DocumentRepairAnswerKind, DocumentRepairRow, DueObservationRepairQuery, GetMemberRemovalsParams, LogHandlerCompletionKey, LogHandlerCompletionOps, ObservationRepairPosition, ObservationRepairSchedule, OpenedAppFrameExpectedVersion, OpenedAppFrameRecord, OwnAgentsGrant, OwnAgentsJoinCapability, OwnAgentsOps, OwnAgentsRecord, OwnAgentsRecordKey, P2PStoreAPI, PruneBroadcastLogParams, PutPendingJoinRequestParams, RecordDocumentRepairParams, ReserveSenderGenerationsParams, SaveOpenedAppFrameParams, SetLocalPeerProfileParams, UpsertPeerAnnouncementParams, } from './api.js';
4
4
  export { createP2PStore } from './api.js';
5
- export { p2pStoreDefinition } from './definition.js';
5
+ export { configureP2PStore, P2PStoreConfigurationError, p2pStoreDefinition } from './definition.js';
6
6
  export { canonicalHubURL } from './hub-url.js';
7
7
  export { AppOutboxDecryptionError, type AppOutboxPortOptions, AppOutboxTransactionError, type ClearAppOutboxRowsOptions, PendingRevocationAdmissionError, UndeliveredRevocationsError, } from './ops/app-outbox.js';
8
8
  export { MAX_REPAIRS_PER_LISTER } from './ops/catalog-sync.js';
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { createP2PStore } from './api.js';
2
- export { p2pStoreDefinition } from './definition.js';
2
+ export { configureP2PStore, P2PStoreConfigurationError, p2pStoreDefinition } from './definition.js';
3
3
  export { canonicalHubURL } from './hub-url.js';
4
4
  export { AppOutboxDecryptionError, AppOutboxTransactionError, PendingRevocationAdmissionError, UndeliveredRevocationsError } from './ops/app-outbox.js';
5
5
  export { MAX_REPAIRS_PER_LISTER } from './ops/catalog-sync.js';
package/lib/migrations.js CHANGED
@@ -23,37 +23,37 @@ import { sql } from 'kysely';
23
23
  const now = ctx.functions.now;
24
24
  const init = {
25
25
  async up (db) {
26
- await db.schema.createTable('kubun_p2p_access_default_revocations').ifNotExists().addColumn('controller_did', t.text, (col)=>col.notNull()).addColumn('device_did', t.text, (col)=>col.notNull()).addPrimaryKeyConstraint('pk_p2p_access_default_revocations', [
26
+ await db.schema.createTable('p2p_access_default_revocations').ifNotExists().addColumn('controller_did', t.text, (col)=>col.notNull()).addColumn('device_did', t.text, (col)=>col.notNull()).addPrimaryKeyConstraint('pk_p2p_access_default_revocations', [
27
27
  'controller_did',
28
28
  'device_did'
29
29
  ]).execute();
30
30
  // Every authenticated device binding this device has observed. Append-only:
31
31
  // ownership is historical, so revocation, removal or MLS state loss never
32
32
  // takes a row away.
33
- await db.schema.createTable('kubun_p2p_binding_evidence').ifNotExists().addColumn('controller_did', t.text, (col)=>col.notNull()).addColumn('device_did', t.text, (col)=>col.notNull()).addColumn('first_seen_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addPrimaryKeyConstraint('pk_p2p_binding_evidence', [
33
+ await db.schema.createTable('p2p_binding_evidence').ifNotExists().addColumn('controller_did', t.text, (col)=>col.notNull()).addColumn('device_did', t.text, (col)=>col.notNull()).addColumn('first_seen_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addPrimaryKeyConstraint('pk_p2p_binding_evidence', [
34
34
  'controller_did',
35
35
  'device_did'
36
36
  ]).execute();
37
- await db.schema.createIndex('idx_p2p_binding_evidence_device').ifNotExists().on('kubun_p2p_binding_evidence').column('device_did').execute();
37
+ await db.schema.createIndex('idx_p2p_binding_evidence_device').ifNotExists().on('p2p_binding_evidence').column('device_did').execute();
38
38
  // Groups
39
- await db.schema.createTable('kubun_p2p_groups').ifNotExists().addColumn('id', t.text, (col)=>col.notNull().primaryKey()).addColumn('name', t.text, (col)=>col.notNull()).addColumn('description', t.text, (col)=>col.notNull().defaultTo('')).addColumn('created_by', t.text, (col)=>col.notNull()).addColumn('hlc', t.text, (col)=>col.notNull()).addColumn('kind', t.text, (col)=>col.notNull().defaultTo('standard'))// A completed join or local create starts joined; see `GroupStatus`.
39
+ await db.schema.createTable('p2p_groups').ifNotExists().addColumn('id', t.text, (col)=>col.notNull().primaryKey()).addColumn('name', t.text, (col)=>col.notNull()).addColumn('description', t.text, (col)=>col.notNull().defaultTo('')).addColumn('created_by', t.text, (col)=>col.notNull()).addColumn('hlc', t.text, (col)=>col.notNull()).addColumn('kind', t.text, (col)=>col.notNull().defaultTo('standard'))// A completed join or local create starts joined; see `GroupStatus`.
40
40
  .addColumn('status', t.text, (col)=>col.notNull().defaultTo('joined'))// Token of the last handle advance whose projections no reconcile has
41
41
  // caught up with yet; NULL when they match the current handle.
42
42
  .addColumn('projections_dirty', t.text)// Epoch the last successful reconcile caught the projections up to.
43
43
  .addColumn('projections_epoch', 'integer').addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addColumn('updated_at', t.timestamp).execute();
44
- await db.schema.createTable('kubun_p2p_own_agents_records').ifNotExists().addColumn('type', t.text, (col)=>col.notNull()).addColumn('owner', t.text, (col)=>col.notNull()).addColumn('id', t.text, (col)=>col.notNull()).addColumn('hlc', t.text, (col)=>col.notNull()).addColumn('op_jwt', t.text, (col)=>col.notNull()).addColumn('op_hash', t.text, (col)=>col.notNull()).addColumn('payload', t.binary).addColumn('payload_digest', t.text).addColumn('deleted', 'integer', (col)=>col.notNull()).addColumn('meta', t.json, (col)=>col.notNull()).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addPrimaryKeyConstraint('pk_p2p_own_agents_records', [
44
+ await db.schema.createTable('p2p_own_agents_records').ifNotExists().addColumn('type', t.text, (col)=>col.notNull()).addColumn('owner', t.text, (col)=>col.notNull()).addColumn('id', t.text, (col)=>col.notNull()).addColumn('hlc', t.text, (col)=>col.notNull()).addColumn('op_jwt', t.text, (col)=>col.notNull()).addColumn('op_hash', t.text, (col)=>col.notNull()).addColumn('payload', t.binary).addColumn('payload_digest', t.text).addColumn('deleted', 'integer', (col)=>col.notNull()).addColumn('meta', t.json, (col)=>col.notNull()).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addPrimaryKeyConstraint('pk_p2p_own_agents_records', [
45
45
  'type',
46
46
  'owner',
47
47
  'id',
48
48
  'op_hash'
49
49
  ]).execute();
50
- await db.schema.createTable('kubun_p2p_own_agents_grants').ifNotExists().addColumn('controller_did', t.text, (col)=>col.notNull()).addColumn('kind', t.text, (col)=>col.notNull()).addColumn('token', t.text, (col)=>col.notNull()).addColumn('issued_at', t.bigint, (col)=>col.notNull()).addColumn('expires_at', t.bigint, (col)=>col.notNull()).addPrimaryKeyConstraint('pk_p2p_own_agents_grants', [
50
+ await db.schema.createTable('p2p_own_agents_grants').ifNotExists().addColumn('controller_did', t.text, (col)=>col.notNull()).addColumn('kind', t.text, (col)=>col.notNull()).addColumn('token', t.text, (col)=>col.notNull()).addColumn('issued_at', t.bigint, (col)=>col.notNull()).addColumn('expires_at', t.bigint, (col)=>col.notNull()).addPrimaryKeyConstraint('pk_p2p_own_agents_grants', [
51
51
  'controller_did',
52
52
  'kind'
53
53
  ]).execute();
54
- await db.schema.createTable('kubun_p2p_own_agents_join_capabilities').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull().primaryKey()).addColumn('recipient_did', t.text, (col)=>col.notNull()).addColumn('capability_id', t.text, (col)=>col.notNull()).addColumn('token', t.text, (col)=>col.notNull()).addColumn('expires_at', t.bigint, (col)=>col.notNull()).addColumn('signer_did', t.text).addColumn('signer_leaf_key', t.binary).addColumn('signer_capability', t.text).addColumn('signer_prefix', t.json).execute();
54
+ await db.schema.createTable('p2p_own_agents_join_capabilities').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull().primaryKey()).addColumn('recipient_did', t.text, (col)=>col.notNull()).addColumn('capability_id', t.text, (col)=>col.notNull()).addColumn('token', t.text, (col)=>col.notNull()).addColumn('expires_at', t.bigint, (col)=>col.notNull()).addColumn('signer_did', t.text).addColumn('signer_leaf_key', t.binary).addColumn('signer_capability', t.text).addColumn('signer_prefix', t.json).execute();
55
55
  // Group members
56
- await db.schema.createTable('kubun_p2p_group_members').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('member_did', t.text, (col)=>col.notNull()).addColumn('role', t.text, (col)=>col.notNull().defaultTo('member'))// LWW version key for the row, set on insert and refreshed on re-add.
56
+ await db.schema.createTable('p2p_group_members').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('member_did', t.text, (col)=>col.notNull()).addColumn('role', t.text, (col)=>col.notNull().defaultTo('member'))// LWW version key for the row, set on insert and refreshed on re-add.
57
57
  .addColumn('hlc', t.text, (col)=>col.notNull())// HLC at which this peer learned the member was removed from the MLS
58
58
  // group. NULL means the member is active; a non-null value tombstones
59
59
  // the row so read-ACL / sync-scope queries exclude it while the
@@ -62,28 +62,28 @@ import { sql } from 'kysely';
62
62
  'group_id',
63
63
  'member_did'
64
64
  ]).execute();
65
- await db.schema.createIndex('idx_p2p_group_members_did').ifNotExists().on('kubun_p2p_group_members').column('member_did').execute();
66
- await db.schema.createTable('kubun_p2p_catalog_offers').ifNotExists().addColumn('owner', t.text, (col)=>col.notNull()).addColumn('exposure_id', t.text, (col)=>col.notNull()).addColumn('catalog_id', t.text, (col)=>col.notNull()).addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('circle_id', t.text, (col)=>col.notNull()).addPrimaryKeyConstraint('pk_p2p_catalog_offers', [
65
+ await db.schema.createIndex('idx_p2p_group_members_did').ifNotExists().on('p2p_group_members').column('member_did').execute();
66
+ await db.schema.createTable('p2p_catalog_offers').ifNotExists().addColumn('owner', t.text, (col)=>col.notNull()).addColumn('exposure_id', t.text, (col)=>col.notNull()).addColumn('catalog_id', t.text, (col)=>col.notNull()).addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('circle_id', t.text, (col)=>col.notNull()).addPrimaryKeyConstraint('pk_p2p_catalog_offers', [
67
67
  'owner',
68
68
  'exposure_id'
69
69
  ]).execute();
70
- await db.schema.createIndex('idx_p2p_catalog_offers_group_circle').ifNotExists().on('kubun_p2p_catalog_offers').columns([
70
+ await db.schema.createIndex('idx_p2p_catalog_offers_group_circle').ifNotExists().on('p2p_catalog_offers').columns([
71
71
  'group_id',
72
72
  'circle_id'
73
73
  ]).execute();
74
- await db.schema.createTable('kubun_p2p_catalog_routes').ifNotExists().addColumn('owner', t.text, (col)=>col.notNull()).addColumn('catalog_id', t.text, (col)=>col.notNull()).addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('pull', 'integer', (col)=>col.notNull().defaultTo(0)).addColumn('push', 'integer', (col)=>col.notNull().defaultTo(0)).addPrimaryKeyConstraint('pk_p2p_catalog_routes', [
74
+ await db.schema.createTable('p2p_catalog_routes').ifNotExists().addColumn('owner', t.text, (col)=>col.notNull()).addColumn('catalog_id', t.text, (col)=>col.notNull()).addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('pull', 'integer', (col)=>col.notNull().defaultTo(0)).addColumn('push', 'integer', (col)=>col.notNull().defaultTo(0)).addPrimaryKeyConstraint('pk_p2p_catalog_routes', [
75
75
  'owner',
76
76
  'catalog_id',
77
77
  'group_id'
78
78
  ]).execute();
79
79
  // Groups whose document data this device does not serve; absence means served.
80
- await db.schema.createTable('kubun_p2p_unserved_groups').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull().primaryKey()).execute();
80
+ await db.schema.createTable('p2p_unserved_groups').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull().primaryKey()).execute();
81
81
  // Document-sync progress with one peer in one group: the last resolved
82
82
  // document key, whether the current cycle's examined ranges were all clear,
83
83
  // the hash of the accepted rule set without absolute bounds, and the
84
84
  // absolute bounds in use until the cursor wraps.
85
- await db.schema.createTable('kubun_p2p_catalog_cursors').ifNotExists().addColumn('peer_did', t.text, (col)=>col.notNull()).addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('rules_hash', t.text, (col)=>col.notNull()).addColumn('last_key', t.text)// The responder's sealed position for `last_key`; opaque here.
86
- .addColumn('last_token', t.text).addColumn('cycle_clear', 'integer', (col)=>col.notNull().defaultTo(1)).addColumn('bounds', t.text, (col)=>col.notNull()).addPrimaryKeyConstraint('pk_p2p_catalog_cursors', [
85
+ await db.schema.createTable('p2p_catalog_cursors').ifNotExists().addColumn('peer_did', t.text, (col)=>col.notNull()).addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('rules_hash', t.text, (col)=>col.notNull()).addColumn('last_key', t.text)// The responder's sealed position for `last_key`; opaque here.
86
+ .addColumn('last_token', t.text).addColumn('cycle_clear', 'integer', (col)=>col.notNull().defaultTo(1)).addColumn('bounds', t.json, (col)=>col.notNull()).addPrimaryKeyConstraint('pk_p2p_catalog_cursors', [
87
87
  'peer_did',
88
88
  'group_id'
89
89
  ]).execute();
@@ -92,22 +92,22 @@ import { sql } from 'kysely';
92
92
  // retry; null when no peer listed it and the row only remembers answers.
93
93
  // Rows expire from `created_at`, so none outlives its window whoever
94
94
  // listed it.
95
- await db.schema.createTable('kubun_p2p_document_repairs').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('document_id', t.text, (col)=>col.notNull()).addColumn('listed_by', t.text).addColumn('holdings_digest', t.text, (col)=>col.notNull()).addColumn('created_at', t.bigint, (col)=>col.notNull()).addColumn('attempted_at', t.bigint, (col)=>col.notNull()).addPrimaryKeyConstraint('pk_p2p_document_repairs', [
95
+ await db.schema.createTable('p2p_document_repairs').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('document_id', t.text, (col)=>col.notNull()).addColumn('listed_by', t.text).addColumn('holdings_digest', t.text, (col)=>col.notNull()).addColumn('created_at', t.bigint, (col)=>col.notNull()).addColumn('attempted_at', t.bigint, (col)=>col.notNull()).addPrimaryKeyConstraint('pk_p2p_document_repairs', [
96
96
  'group_id',
97
97
  'document_id'
98
98
  ]).execute();
99
- await db.schema.createIndex('idx_p2p_document_repairs_lister').ifNotExists().on('kubun_p2p_document_repairs').columns([
99
+ await db.schema.createIndex('idx_p2p_document_repairs_lister').ifNotExists().on('p2p_document_repairs').columns([
100
100
  'group_id',
101
101
  'listed_by',
102
102
  'attempted_at'
103
103
  ]).execute();
104
- await db.schema.createIndex('idx_p2p_document_repairs_created').ifNotExists().on('kubun_p2p_document_repairs').columns([
104
+ await db.schema.createIndex('idx_p2p_document_repairs_created').ifNotExists().on('p2p_document_repairs').columns([
105
105
  'group_id',
106
106
  'created_at'
107
107
  ]).execute();
108
108
  // A peer's last answer, "unavailable" or "refused", for a repair row's
109
109
  // current holdings. A change of holdings removes the answers.
110
- await db.schema.createTable('kubun_p2p_document_repair_answers').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('document_id', t.text, (col)=>col.notNull()).addColumn('peer_did', t.text, (col)=>col.notNull()).addColumn('answer', t.text, (col)=>col.notNull()).addPrimaryKeyConstraint('pk_p2p_document_repair_answers', [
110
+ await db.schema.createTable('p2p_document_repair_answers').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('document_id', t.text, (col)=>col.notNull()).addColumn('peer_did', t.text, (col)=>col.notNull()).addColumn('answer', t.text, (col)=>col.notNull()).addPrimaryKeyConstraint('pk_p2p_document_repair_answers', [
111
111
  'group_id',
112
112
  'document_id',
113
113
  'peer_did'
@@ -115,13 +115,13 @@ import { sql } from 'kysely';
115
115
  // When this device next asks peers for an observed document's value, for
116
116
  // the obligation observed at `fetch_hlc`. The connector owns the
117
117
  // obligation; a row leaves only when it resolves.
118
- await db.schema.createTable('kubun_p2p_observation_repairs').ifNotExists().addColumn('document_id', t.text, (col)=>col.notNull().primaryKey()).addColumn('fetch_hlc', t.text, (col)=>col.notNull()).addColumn('attempts', 'integer', (col)=>col.notNull()).addColumn('next_attempt_at', t.bigint, (col)=>col.notNull()).addColumn('tried_count', 'integer', (col)=>col.notNull()).execute();
119
- await db.schema.createIndex('idx_p2p_observation_repairs_next').ifNotExists().on('kubun_p2p_observation_repairs').columns([
118
+ await db.schema.createTable('p2p_observation_repairs').ifNotExists().addColumn('document_id', t.text, (col)=>col.notNull().primaryKey()).addColumn('fetch_hlc', t.text, (col)=>col.notNull()).addColumn('attempts', 'integer', (col)=>col.notNull()).addColumn('next_attempt_at', t.bigint, (col)=>col.notNull()).addColumn('tried_count', 'integer', (col)=>col.notNull()).execute();
119
+ await db.schema.createIndex('idx_p2p_observation_repairs_next').ifNotExists().on('p2p_observation_repairs').columns([
120
120
  'next_attempt_at',
121
121
  'document_id'
122
122
  ]).execute();
123
123
  // The peers already asked for an obligation that answered without it.
124
- await db.schema.createTable('kubun_p2p_observation_repair_peers').ifNotExists().addColumn('document_id', t.text, (col)=>col.notNull()).addColumn('peer_did', t.text, (col)=>col.notNull()).addPrimaryKeyConstraint('pk_p2p_observation_repair_peers', [
124
+ await db.schema.createTable('p2p_observation_repair_peers').ifNotExists().addColumn('document_id', t.text, (col)=>col.notNull()).addColumn('peer_did', t.text, (col)=>col.notNull()).addPrimaryKeyConstraint('pk_p2p_observation_repair_peers', [
125
125
  'document_id',
126
126
  'peer_did'
127
127
  ]).execute();
@@ -129,12 +129,12 @@ import { sql } from 'kysely';
129
129
  // or duplicate request is not surfaced again. Separate from the membership
130
130
  // row because it must outlive a removal: a re-added member's old requests
131
131
  // stay spent.
132
- await db.schema.createTable('kubun_p2p_group_leave_requests').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('member_did', t.text, (col)=>col.notNull()).addColumn('hlc', t.text, (col)=>col.notNull()).addPrimaryKeyConstraint('pk_p2p_group_leave_requests', [
132
+ await db.schema.createTable('p2p_group_leave_requests').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('member_did', t.text, (col)=>col.notNull()).addColumn('hlc', t.text, (col)=>col.notNull()).addPrimaryKeyConstraint('pk_p2p_group_leave_requests', [
133
133
  'group_id',
134
134
  'member_did'
135
135
  ]).execute();
136
136
  // Circles
137
- await db.schema.createTable('kubun_p2p_circles').ifNotExists().addColumn('id', t.text, (col)=>col.notNull().primaryKey()).addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('name', t.text, (col)=>col.notNull()).addColumn('description', t.text, (col)=>col.notNull().defaultTo('')).addColumn('hlc', t.text, (col)=>col.notNull())// Projection of the group's current `openCircleIDs`: 1 marks the circle
137
+ await db.schema.createTable('p2p_circles').ifNotExists().addColumn('id', t.text, (col)=>col.notNull().primaryKey()).addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('name', t.text, (col)=>col.notNull()).addColumn('description', t.text, (col)=>col.notNull().defaultTo('')).addColumn('hlc', t.text, (col)=>col.notNull())// Projection of the group's current `openCircleIDs`: 1 marks the circle
138
138
  // open (every group member has implicit access), 0 closed. Integer 0/1
139
139
  // mirrors the catalog `active` flag, keeping the read-back value uniform
140
140
  // across SQLite and Postgres. A circle is closed until a settings fold
@@ -142,38 +142,38 @@ import { sql } from 'kysely';
142
142
  .addColumn('open', 'integer', (col)=>col.notNull().defaultTo(0))// NULL means the circle is active; a non-null value tombstones the row
143
143
  // so reads exclude it while the removal HLC stays comparable.
144
144
  .addColumn('removed_at_hlc', t.text).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addColumn('updated_at', t.timestamp).execute();
145
- await db.schema.createIndex('idx_p2p_circles_group').ifNotExists().on('kubun_p2p_circles').column('group_id').execute();
145
+ await db.schema.createIndex('idx_p2p_circles_group').ifNotExists().on('p2p_circles').column('group_id').execute();
146
146
  // Circle members
147
- await db.schema.createTable('kubun_p2p_circle_members').ifNotExists().addColumn('circle_id', t.text, (col)=>col.notNull()).addColumn('member_did', t.text, (col)=>col.notNull()).addColumn('role', t.text, (col)=>col.notNull().defaultTo('member')).addColumn('hlc', t.text, (col)=>col.notNull())// NULL means the circle member is active; a non-null value tombstones
147
+ await db.schema.createTable('p2p_circle_members').ifNotExists().addColumn('circle_id', t.text, (col)=>col.notNull()).addColumn('member_did', t.text, (col)=>col.notNull()).addColumn('role', t.text, (col)=>col.notNull().defaultTo('member')).addColumn('hlc', t.text, (col)=>col.notNull())// NULL means the circle member is active; a non-null value tombstones
148
148
  // the row so reads exclude it while the removal HLC stays comparable.
149
149
  .addColumn('removed_at_hlc', t.text).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addColumn('updated_at', t.timestamp).addPrimaryKeyConstraint('pk_p2p_circle_members', [
150
150
  'circle_id',
151
151
  'member_did'
152
152
  ]).execute();
153
- await db.schema.createIndex('idx_p2p_circle_members_did').ifNotExists().on('kubun_p2p_circle_members').column('member_did').execute();
153
+ await db.schema.createIndex('idx_p2p_circle_members_did').ifNotExists().on('p2p_circle_members').column('member_did').execute();
154
154
  // Group-delegation edge. The delegation token and revocation rows live in
155
155
  // the core delegation store (group-agnostic); this thin edge records the
156
156
  // one group a delegation `jti` was granted in, so the p2p layer can still
157
157
  // resolve a cap's group for revoke-broadcast targeting and listing
158
158
  // enrichment. One group per `jti`, so `jti` is the primary key.
159
- await db.schema.createTable('kubun_p2p_group_delegation').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('jti', t.text, (col)=>col.notNull().primaryKey()).addColumn('pending_grant_hlc', t.text).addColumn('pending_grant_digest', t.text).addColumn('pending_revocation_hlc', t.text).addColumn('pending_revocation_digest', t.text).execute();
160
- await db.schema.createIndex('idx_p2p_group_delegation_pending_grant').ifNotExists().on('kubun_p2p_group_delegation').columns([
159
+ await db.schema.createTable('p2p_group_delegation').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('jti', t.text, (col)=>col.notNull().primaryKey()).addColumn('pending_grant_hlc', t.text).addColumn('pending_grant_digest', t.text).addColumn('pending_revocation_hlc', t.text).addColumn('pending_revocation_digest', t.text).execute();
160
+ await db.schema.createIndex('idx_p2p_group_delegation_pending_grant').ifNotExists().on('p2p_group_delegation').columns([
161
161
  'group_id',
162
162
  'pending_grant_hlc'
163
163
  ]).execute();
164
- await db.schema.createIndex('idx_p2p_group_delegation_pending_revocation').ifNotExists().on('kubun_p2p_group_delegation').columns([
164
+ await db.schema.createIndex('idx_p2p_group_delegation_pending_revocation').ifNotExists().on('p2p_group_delegation').columns([
165
165
  'group_id',
166
166
  'pending_revocation_hlc'
167
167
  ]).execute();
168
168
  // Group MLS state
169
- await db.schema.createTable('kubun_p2p_group_mls_state').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('device_id', t.text, (col)=>col.notNull()).addColumn('mls_state', t.binary, (col)=>col.notNull()).addColumn('credential', t.text, (col)=>col.notNull()).addColumn('epoch', 'integer', (col)=>col.notNull())// Orders in-place secret-tree mutations within one epoch.
169
+ await db.schema.createTable('p2p_group_mls_state').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('device_id', t.text, (col)=>col.notNull()).addColumn('mls_state', t.binary, (col)=>col.notNull()).addColumn('credential', t.json, (col)=>col.notNull()).addColumn('epoch', 'integer', (col)=>col.notNull())// Orders in-place secret-tree mutations within one epoch.
170
170
  .addColumn('state_version', 'integer', (col)=>col.notNull().defaultTo(0)).addColumn('is_deleted', 'integer', (col)=>col.notNull().defaultTo(0))// Highest sender generation reserved at this epoch; restore skips past it.
171
- .addColumn('sender_generation_hwm', 'integer', (col)=>col.notNull().defaultTo(0)).addColumn('ledger_entries', t.text, (col)=>col.notNull().defaultTo('[]')).addColumn('updated_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addPrimaryKeyConstraint('pk_p2p_group_mls_state', [
171
+ .addColumn('sender_generation_hwm', 'integer', (col)=>col.notNull().defaultTo(0)).addColumn('ledger_entries', t.json, (col)=>col.notNull().defaultTo(sql`'[]'`)).addColumn('updated_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addPrimaryKeyConstraint('pk_p2p_group_mls_state', [
172
172
  'group_id',
173
173
  'device_id'
174
174
  ]).execute();
175
175
  // Sync peers
176
- await db.schema.createTable('kubun_p2p_sync_peers').ifNotExists().addColumn('id', t.text, (col)=>col.notNull().primaryKey()).addColumn('peer_did', t.text, (col)=>col.notNull().unique()).addColumn('endpoint', t.text, (col)=>col.notNull())// No `trust_level`, `allowed_users`, `priority` or `mode`. They were
176
+ await db.schema.createTable('p2p_sync_peers').ifNotExists().addColumn('id', t.text, (col)=>col.notNull().primaryKey()).addColumn('peer_did', t.text, (col)=>col.notNull().unique()).addColumn('endpoint', t.text, (col)=>col.notNull())// No `trust_level`, `allowed_users`, `priority` or `mode`. They were
177
177
  // stored and never read: what a peer may see is decided per document at
178
178
  // the far end, and a locally-written row is not a credential. `priority`
179
179
  // in particular looked like the ranking knob after ranking had moved to
@@ -183,27 +183,27 @@ import { sql } from 'kysely';
183
183
  // argument, per call.
184
184
  .addColumn('config', t.json, (col)=>col.notNull()).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addColumn('updated_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).execute();
185
185
  // Sync checkpoints
186
- await db.schema.createTable('kubun_p2p_sync_checkpoints').ifNotExists().addColumn('id', t.text, (col)=>col.notNull().primaryKey()).addColumn('peer_did', t.text, (col)=>col.notNull()).addColumn('scope', t.text, (col)=>col.notNull()).addColumn('checkpoint_hlc', t.text, (col)=>col.notNull()).addColumn('updated_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).execute();
186
+ await db.schema.createTable('p2p_sync_checkpoints').ifNotExists().addColumn('id', t.text, (col)=>col.notNull().primaryKey()).addColumn('peer_did', t.text, (col)=>col.notNull()).addColumn('scope', t.text, (col)=>col.notNull()).addColumn('checkpoint_hlc', t.text, (col)=>col.notNull()).addColumn('updated_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).execute();
187
187
  // Broadcast log
188
- await db.schema.createTable('kubun_p2p_broadcast_log').ifNotExists().addColumn('doc_id', t.text, (col)=>col.notNull()).addColumn('version', t.text, (col)=>col.notNull()).addColumn('mls_group_id', t.text, (col)=>col.notNull()).addColumn('broadcast_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addPrimaryKeyConstraint('pk_p2p_broadcast_log', [
188
+ await db.schema.createTable('p2p_broadcast_log').ifNotExists().addColumn('doc_id', t.text, (col)=>col.notNull()).addColumn('version', t.text, (col)=>col.notNull()).addColumn('mls_group_id', t.text, (col)=>col.notNull()).addColumn('broadcast_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addPrimaryKeyConstraint('pk_p2p_broadcast_log', [
189
189
  'doc_id',
190
190
  'version',
191
191
  'mls_group_id'
192
192
  ]).execute();
193
- await db.schema.createIndex('idx_p2p_broadcast_log_doc').ifNotExists().on('kubun_p2p_broadcast_log').column('doc_id').execute();
193
+ await db.schema.createIndex('idx_p2p_broadcast_log_doc').ifNotExists().on('p2p_broadcast_log').column('doc_id').execute();
194
194
  // The broadcast log is pruned by age.
195
- await db.schema.createIndex('idx_p2p_broadcast_log_broadcast_at').ifNotExists().on('kubun_p2p_broadcast_log').column('broadcast_at').execute();
195
+ await db.schema.createIndex('idx_p2p_broadcast_log_broadcast_at').ifNotExists().on('p2p_broadcast_log').column('broadcast_at').execute();
196
196
  // Hubs (device-local, never synced — no hlc column)
197
- await db.schema.createTable('kubun_p2p_hubs').ifNotExists().addColumn('id', t.text, (col)=>col.notNull().primaryKey()).addColumn('label', t.text).addColumn('url', t.text, (col)=>col.notNull().unique()).addColumn('server_did', t.text).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addColumn('updated_at', t.timestamp).execute();
197
+ await db.schema.createTable('p2p_hubs').ifNotExists().addColumn('id', t.text, (col)=>col.notNull().primaryKey()).addColumn('label', t.text).addColumn('url', t.text, (col)=>col.notNull().unique()).addColumn('server_did', t.text).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addColumn('updated_at', t.timestamp).execute();
198
198
  // Group hubs (join table, device-local — no hlc column). A group commits
199
199
  // through exactly one bound hub; `is_commit_hub` marks which. Integer 0/1
200
200
  // mirrors the circle `open` flag so the value reads back uniformly across
201
201
  // SQLite and Postgres.
202
- await db.schema.createTable('kubun_p2p_group_hubs').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('hub_id', t.text, (col)=>col.notNull()).addColumn('is_commit_hub', 'integer', (col)=>col.notNull().defaultTo(0)).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addPrimaryKeyConstraint('pk_p2p_group_hubs', [
202
+ await db.schema.createTable('p2p_group_hubs').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('hub_id', t.text, (col)=>col.notNull()).addColumn('is_commit_hub', 'integer', (col)=>col.notNull().defaultTo(0)).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addPrimaryKeyConstraint('pk_p2p_group_hubs', [
203
203
  'group_id',
204
204
  'hub_id'
205
205
  ]).execute();
206
- await db.schema.createIndex('idx_p2p_group_hubs_hub').ifNotExists().on('kubun_p2p_group_hubs').column('hub_id').execute();
206
+ await db.schema.createIndex('idx_p2p_group_hubs_hub').ifNotExists().on('p2p_group_hubs').column('hub_id').execute();
207
207
  // The single designation is a database constraint, not a convention a
208
208
  // writer upholds. A transaction alone cannot uphold it: two devices
209
209
  // binding their first hub concurrently touch DIFFERENT rows, so under
@@ -211,7 +211,7 @@ import { sql } from 'kysely';
211
211
  // both claim it — the group then commits to two logs, which is the fork
212
212
  // the designation exists to prevent. Partial unique indexes carry the
213
213
  // same meaning on both adapter families (SQLite >= 3.8, Postgres).
214
- await db.schema.createIndex(COMMIT_HUB_UNIQUE_INDEX).ifNotExists().unique().on('kubun_p2p_group_hubs').column('group_id').where(sql.ref('is_commit_hub'), '=', 1).execute();
214
+ await db.schema.createIndex(COMMIT_HUB_UNIQUE_INDEX).ifNotExists().unique().on('p2p_group_hubs').column('group_id').where(sql.ref('is_commit_hub'), '=', 1).execute();
215
215
  // ControlRequest work queue: the client-visible record of a control-plane
216
216
  // request (a ledger write, an invite, a removal) from enqueue to
217
217
  // settlement, keyed by the same id the commit journal carries for the
@@ -220,39 +220,39 @@ import { sql } from 'kysely';
220
220
  // nullable because most requests produce nothing to hand back; it is
221
221
  // written on settle and deleted with the row on ack, so the retention of
222
222
  // whatever it holds is bounded by the request's own lifetime.
223
- await db.schema.createTable('kubun_p2p_control_requests').ifNotExists().addColumn('id', t.text, (col)=>col.notNull().primaryKey()).addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('kind', t.text, (col)=>col.notNull()).addColumn('status', t.text, (col)=>col.notNull().defaultTo('pending')).addColumn('error', t.text).addColumn('result', t.text).addColumn('enqueued_at', t.text, (col)=>col.notNull()).addColumn('settled_at', t.text)// Epoch of the advance that settled the request committed.
223
+ await db.schema.createTable('p2p_control_requests').ifNotExists().addColumn('id', t.text, (col)=>col.notNull().primaryKey()).addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('kind', t.text, (col)=>col.notNull()).addColumn('status', t.text, (col)=>col.notNull().defaultTo('pending')).addColumn('error', t.text).addColumn('result', t.text).addColumn('enqueued_at', t.text, (col)=>col.notNull()).addColumn('settled_at', t.text)// Epoch of the advance that settled the request committed.
224
224
  .addColumn('settled_epoch', 'integer').execute();
225
- await db.schema.createIndex('idx_p2p_control_requests_group').ifNotExists().on('kubun_p2p_control_requests').columns([
225
+ await db.schema.createIndex('idx_p2p_control_requests_group').ifNotExists().on('p2p_control_requests').columns([
226
226
  'group_id',
227
227
  'enqueued_at'
228
228
  ]).execute();
229
229
  // CommitJournal store (backs the `@kumiai/rpc` CommitJournal port).
230
230
  // `group_id` is the primary key: a single slot per group, so a fresh `put`
231
231
  // upserts/replaces the whole row.
232
- await db.schema.createTable('kubun_p2p_commit_journal').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull().primaryKey()).addColumn('publish_id', t.text, (col)=>col.notNull()).addColumn('expected_head', t.text).addColumn('epoch', t.bigint, (col)=>col.notNull()).addColumn('accepted_as', t.text).addColumn('commit', t.binary, (col)=>col.notNull()).addColumn('bodies', t.json, (col)=>col.notNull()).addColumn('kind', t.text, (col)=>col.notNull()).addColumn('journal', t.binary, (col)=>col.notNull()).addColumn('holds_log_sends', 'integer', (col)=>col.notNull().defaultTo(0)).execute();
232
+ await db.schema.createTable('p2p_commit_journal').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull().primaryKey()).addColumn('publish_id', t.text, (col)=>col.notNull()).addColumn('expected_head', t.text).addColumn('epoch', t.bigint, (col)=>col.notNull()).addColumn('accepted_as', t.text).addColumn('commit', t.binary, (col)=>col.notNull()).addColumn('bodies', t.json, (col)=>col.notNull()).addColumn('kind', t.text, (col)=>col.notNull()).addColumn('journal', t.binary, (col)=>col.notNull()).addColumn('holds_log_sends', 'integer', (col)=>col.notNull().defaultTo(0)).execute();
233
233
  // App-lane anchor + per-topic read positions (back the `@kumiai/rpc`
234
234
  // `AnchorStore` and `AppCursorStore` ports). The anchor table is a single
235
235
  // slot per group, so a rotation upserts and replaces the whole row;
236
236
  // cursors are keyed by (group, topic) so a deleted group takes its cursors
237
237
  // with it.
238
- await db.schema.createTable('kubun_p2p_group_anchor').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull().primaryKey()).addColumn('secret', t.binary, (col)=>col.notNull()).addColumn('epoch', t.bigint, (col)=>col.notNull()).addColumn('pending', t.json).execute();
239
- await db.schema.createTable('kubun_p2p_app_outbox_state').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull().primaryKey()).addColumn('key', t.binary).addColumn('commit_cursor', t.json).execute();
240
- await db.schema.createTable('kubun_p2p_app_outbox').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('seq', t.bigint, (col)=>col.notNull()).addColumn('protocol', t.text, (col)=>col.notNull()).addColumn('prc', t.text, (col)=>col.notNull()).addColumn('data', t.binary, (col)=>col.notNull()).addColumn('iv', t.binary, (col)=>col.notNull()).addColumn('last_attempt', t.json).addPrimaryKeyConstraint('pk_p2p_app_outbox', [
238
+ await db.schema.createTable('p2p_group_anchor').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull().primaryKey()).addColumn('secret', t.binary, (col)=>col.notNull()).addColumn('epoch', t.bigint, (col)=>col.notNull()).addColumn('pending', t.json).execute();
239
+ await db.schema.createTable('p2p_app_outbox_state').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull().primaryKey()).addColumn('key', t.binary).addColumn('commit_cursor', t.json).execute();
240
+ await db.schema.createTable('p2p_app_outbox').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('seq', t.bigint, (col)=>col.notNull()).addColumn('protocol', t.text, (col)=>col.notNull()).addColumn('prc', t.text, (col)=>col.notNull()).addColumn('data', t.binary, (col)=>col.notNull()).addColumn('iv', t.binary, (col)=>col.notNull()).addColumn('last_attempt', t.json).addPrimaryKeyConstraint('pk_p2p_app_outbox', [
241
241
  'group_id',
242
242
  'seq'
243
243
  ]).execute();
244
- await db.schema.createTable('kubun_p2p_app_cursor').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('topic_id', t.text, (col)=>col.notNull()).addColumn('position', t.text, (col)=>col.notNull()).addPrimaryKeyConstraint('pk_p2p_app_cursor', [
244
+ await db.schema.createTable('p2p_app_cursor').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('topic_id', t.text, (col)=>col.notNull()).addColumn('position', t.text, (col)=>col.notNull()).addPrimaryKeyConstraint('pk_p2p_app_cursor', [
245
245
  'group_id',
246
246
  'topic_id'
247
247
  ]).execute();
248
248
  // Decrypted app frames awaiting application. The frame ID is stable across
249
249
  // redelivery, so the first staged record wins until it is completed.
250
- await db.schema.createTable('kubun_p2p_app_pending').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('device_id', t.text, (col)=>col.notNull()).addColumn('frame_id', t.text, (col)=>col.notNull()).addColumn('topic_id', t.text, (col)=>col.notNull()).addColumn('protocol', t.text, (col)=>col.notNull()).addColumn('segment', 'integer', (col)=>col.notNull()).addColumn('position', t.text, (col)=>col.notNull()).addColumn('payload', t.binary, (col)=>col.notNull()).addColumn('sender_did', t.text, (col)=>col.notNull()).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addPrimaryKeyConstraint('pk_p2p_app_pending', [
250
+ await db.schema.createTable('p2p_app_pending').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('device_id', t.text, (col)=>col.notNull()).addColumn('frame_id', t.text, (col)=>col.notNull()).addColumn('topic_id', t.text, (col)=>col.notNull()).addColumn('protocol', t.text, (col)=>col.notNull()).addColumn('segment', 'integer', (col)=>col.notNull()).addColumn('position', t.text, (col)=>col.notNull()).addColumn('payload', t.binary, (col)=>col.notNull()).addColumn('sender_did', t.text, (col)=>col.notNull()).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addPrimaryKeyConstraint('pk_p2p_app_pending', [
251
251
  'group_id',
252
252
  'device_id',
253
253
  'frame_id'
254
254
  ]).execute();
255
- await db.schema.createTable('kubun_p2p_log_handler_completion').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('procedure', t.text, (col)=>col.notNull()).addColumn('entity', t.text, (col)=>col.notNull()).addColumn('hlc', t.text, (col)=>col.notNull()).addColumn('notify', 'integer', (col)=>col.notNull().defaultTo(0)).addColumn('completed', 'integer', (col)=>col.notNull().defaultTo(0)).addPrimaryKeyConstraint('pk_p2p_log_handler_completion', [
255
+ await db.schema.createTable('p2p_log_handler_completion').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('procedure', t.text, (col)=>col.notNull()).addColumn('entity', t.text, (col)=>col.notNull()).addColumn('hlc', t.text, (col)=>col.notNull()).addColumn('notify', 'integer', (col)=>col.notNull().defaultTo(0)).addColumn('completed', 'integer', (col)=>col.notNull().defaultTo(0)).addPrimaryKeyConstraint('pk_p2p_log_handler_completion', [
256
256
  'group_id',
257
257
  'procedure',
258
258
  'entity',
@@ -262,65 +262,65 @@ import { sql } from 'kysely';
262
262
  // authenticated `peer/announce` frames. `peer_did` is the MLS-authenticated
263
263
  // announcer, which is why the primary key is (group, peer) and there is no
264
264
  // column for a body-supplied DID: nothing self-asserted may pick the row.
265
- await db.schema.createTable('kubun_p2p_peer_announcements').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('peer_did', t.text, (col)=>col.notNull()).addColumn('label', t.text, (col)=>col.notNull().defaultTo('')).addColumn('availability', t.text, (col)=>col.notNull()).addColumn('capabilities', t.json, (col)=>col.notNull().defaultTo('[]')).addColumn('hlc', t.text, (col)=>col.notNull()).addColumn('announced_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addColumn('announced_epoch', 'integer', (col)=>col.notNull().defaultTo(0)).addPrimaryKeyConstraint('pk_p2p_peer_announcements', [
265
+ await db.schema.createTable('p2p_peer_announcements').ifNotExists().addColumn('group_id', t.text, (col)=>col.notNull()).addColumn('peer_did', t.text, (col)=>col.notNull()).addColumn('label', t.text, (col)=>col.notNull().defaultTo('')).addColumn('availability', t.text, (col)=>col.notNull()).addColumn('capabilities', t.json, (col)=>col.notNull().defaultTo('[]')).addColumn('hlc', t.text, (col)=>col.notNull()).addColumn('announced_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addColumn('announced_epoch', 'integer', (col)=>col.notNull().defaultTo(0)).addPrimaryKeyConstraint('pk_p2p_peer_announcements', [
266
266
  'group_id',
267
267
  'peer_did'
268
268
  ]).execute();
269
269
  // Answers "which groups have I seen this device in", the read a roster
270
270
  // removal and a cross-group route lookup both need.
271
- await db.schema.createIndex('idx_p2p_peer_announcements_peer').ifNotExists().on('kubun_p2p_peer_announcements').column('peer_did').execute();
271
+ await db.schema.createIndex('idx_p2p_peer_announcements_peer').ifNotExists().on('p2p_peer_announcements').column('peer_did').execute();
272
272
  // This device's own advertisement, device-wide. One row, keyed on the
273
273
  // device's DID rather than on a fixed sentinel, so a profile written under
274
274
  // one identity is never read back under another.
275
- await db.schema.createTable('kubun_p2p_local_peer_profile').ifNotExists().addColumn('peer_did', t.text, (col)=>col.notNull().primaryKey()).addColumn('label', t.text, (col)=>col.notNull().defaultTo('')).addColumn('availability', t.text, (col)=>col.notNull()).addColumn('capabilities', t.json, (col)=>col.notNull().defaultTo('[]')).addColumn('updated_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).execute();
275
+ await db.schema.createTable('p2p_local_peer_profile').ifNotExists().addColumn('peer_did', t.text, (col)=>col.notNull().primaryKey()).addColumn('label', t.text, (col)=>col.notNull().defaultTo('')).addColumn('availability', t.text, (col)=>col.notNull()).addColumn('capabilities', t.json, (col)=>col.notNull().defaultTo('[]')).addColumn('updated_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).execute();
276
276
  // The private half of a join request, held between `prepareJoinRequest`
277
277
  // and `completeJoin`. The offline exchange spans a human round-trip, so
278
278
  // the two calls can be different processes — a CLI device is always two.
279
279
  // Keyed on the requesting DID: a second request replaces the first rather
280
280
  // than leaving dead key material behind, and the row is deleted the moment
281
281
  // it is consumed.
282
- await db.schema.createTable('kubun_p2p_pending_join_requests').ifNotExists().addColumn('did', t.text, (col)=>col.notNull().primaryKey())// The caller's own encoding, opaque here: a key package holds BigInts
282
+ await db.schema.createTable('p2p_pending_join_requests').ifNotExists().addColumn('did', t.text, (col)=>col.notNull().primaryKey())// The caller's own encoding, opaque here: a key package holds BigInts
283
283
  // and binary, which plain JSON cannot carry.
284
284
  .addColumn('payload', t.text, (col)=>col.notNull()).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).execute();
285
285
  },
286
286
  async down (db) {
287
- await db.schema.dropTable('kubun_p2p_access_default_revocations').ifExists().execute();
288
- await db.schema.dropTable('kubun_p2p_binding_evidence').ifExists().execute();
289
- await db.schema.dropTable('kubun_p2p_own_agents_join_capabilities').ifExists().execute();
290
- await db.schema.dropTable('kubun_p2p_own_agents_grants').ifExists().execute();
291
- await db.schema.dropTable('kubun_p2p_own_agents_records').ifExists().execute();
292
- await db.schema.dropTable('kubun_p2p_observation_repair_peers').ifExists().execute();
293
- await db.schema.dropTable('kubun_p2p_observation_repairs').ifExists().execute();
294
- await db.schema.dropTable('kubun_p2p_document_repair_answers').ifExists().execute();
295
- await db.schema.dropTable('kubun_p2p_document_repairs').ifExists().execute();
296
- await db.schema.dropTable('kubun_p2p_catalog_cursors').ifExists().execute();
297
- await db.schema.dropTable('kubun_p2p_catalog_routes').ifExists().execute();
298
- await db.schema.dropTable('kubun_p2p_unserved_groups').ifExists().execute();
299
- await db.schema.dropTable('kubun_p2p_catalog_offers').ifExists().execute();
300
- await db.schema.dropTable('kubun_p2p_pending_join_requests').ifExists().execute();
301
- await db.schema.dropTable('kubun_p2p_local_peer_profile').ifExists().execute();
302
- await db.schema.dropTable('kubun_p2p_peer_announcements').ifExists().execute();
303
- await db.schema.dropTable('kubun_p2p_app_pending').ifExists().execute();
304
- await db.schema.dropTable('kubun_p2p_log_handler_completion').ifExists().execute();
305
- await db.schema.dropTable('kubun_p2p_app_cursor').ifExists().execute();
306
- await db.schema.dropTable('kubun_p2p_app_outbox').ifExists().execute();
307
- await db.schema.dropTable('kubun_p2p_app_outbox_state').ifExists().execute();
308
- await db.schema.dropTable('kubun_p2p_group_anchor').ifExists().execute();
309
- await db.schema.dropTable('kubun_p2p_commit_journal').ifExists().execute();
310
- await db.schema.dropTable('kubun_p2p_control_requests').ifExists().execute();
287
+ await db.schema.dropTable('p2p_access_default_revocations').ifExists().execute();
288
+ await db.schema.dropTable('p2p_binding_evidence').ifExists().execute();
289
+ await db.schema.dropTable('p2p_own_agents_join_capabilities').ifExists().execute();
290
+ await db.schema.dropTable('p2p_own_agents_grants').ifExists().execute();
291
+ await db.schema.dropTable('p2p_own_agents_records').ifExists().execute();
292
+ await db.schema.dropTable('p2p_observation_repair_peers').ifExists().execute();
293
+ await db.schema.dropTable('p2p_observation_repairs').ifExists().execute();
294
+ await db.schema.dropTable('p2p_document_repair_answers').ifExists().execute();
295
+ await db.schema.dropTable('p2p_document_repairs').ifExists().execute();
296
+ await db.schema.dropTable('p2p_catalog_cursors').ifExists().execute();
297
+ await db.schema.dropTable('p2p_catalog_routes').ifExists().execute();
298
+ await db.schema.dropTable('p2p_unserved_groups').ifExists().execute();
299
+ await db.schema.dropTable('p2p_catalog_offers').ifExists().execute();
300
+ await db.schema.dropTable('p2p_pending_join_requests').ifExists().execute();
301
+ await db.schema.dropTable('p2p_local_peer_profile').ifExists().execute();
302
+ await db.schema.dropTable('p2p_peer_announcements').ifExists().execute();
303
+ await db.schema.dropTable('p2p_app_pending').ifExists().execute();
304
+ await db.schema.dropTable('p2p_log_handler_completion').ifExists().execute();
305
+ await db.schema.dropTable('p2p_app_cursor').ifExists().execute();
306
+ await db.schema.dropTable('p2p_app_outbox').ifExists().execute();
307
+ await db.schema.dropTable('p2p_app_outbox_state').ifExists().execute();
308
+ await db.schema.dropTable('p2p_group_anchor').ifExists().execute();
309
+ await db.schema.dropTable('p2p_commit_journal').ifExists().execute();
310
+ await db.schema.dropTable('p2p_control_requests').ifExists().execute();
311
311
  await db.schema.dropIndex(COMMIT_HUB_UNIQUE_INDEX).ifExists().execute();
312
- await db.schema.dropTable('kubun_p2p_group_hubs').ifExists().execute();
313
- await db.schema.dropTable('kubun_p2p_hubs').ifExists().execute();
314
- await db.schema.dropTable('kubun_p2p_broadcast_log').ifExists().execute();
315
- await db.schema.dropTable('kubun_p2p_sync_checkpoints').ifExists().execute();
316
- await db.schema.dropTable('kubun_p2p_sync_peers').ifExists().execute();
317
- await db.schema.dropTable('kubun_p2p_group_mls_state').ifExists().execute();
318
- await db.schema.dropTable('kubun_p2p_group_delegation').ifExists().execute();
319
- await db.schema.dropTable('kubun_p2p_circle_members').ifExists().execute();
320
- await db.schema.dropTable('kubun_p2p_circles').ifExists().execute();
321
- await db.schema.dropTable('kubun_p2p_group_leave_requests').ifExists().execute();
322
- await db.schema.dropTable('kubun_p2p_group_members').ifExists().execute();
323
- await db.schema.dropTable('kubun_p2p_groups').ifExists().execute();
312
+ await db.schema.dropTable('p2p_group_hubs').ifExists().execute();
313
+ await db.schema.dropTable('p2p_hubs').ifExists().execute();
314
+ await db.schema.dropTable('p2p_broadcast_log').ifExists().execute();
315
+ await db.schema.dropTable('p2p_sync_checkpoints').ifExists().execute();
316
+ await db.schema.dropTable('p2p_sync_peers').ifExists().execute();
317
+ await db.schema.dropTable('p2p_group_mls_state').ifExists().execute();
318
+ await db.schema.dropTable('p2p_group_delegation').ifExists().execute();
319
+ await db.schema.dropTable('p2p_circle_members').ifExists().execute();
320
+ await db.schema.dropTable('p2p_circles').ifExists().execute();
321
+ await db.schema.dropTable('p2p_group_leave_requests').ifExists().execute();
322
+ await db.schema.dropTable('p2p_group_members').ifExists().execute();
323
+ await db.schema.dropTable('p2p_groups').ifExists().execute();
324
324
  }
325
325
  };
326
326
  return {
@@ -1,4 +1,4 @@
1
- import type { Adapter } from '@kubun/db-adapter';
1
+ import { type Adapter } from '@kubun/db-adapter';
2
2
  import { type AnchorSlot } from '@kumiai/rpc';
3
3
  import type { Kysely } from 'kysely';
4
4
  import type { AppCursor, GroupAnchor, InsertGroupMLSState, P2PStoreTables, PendingAppFrameRow } from '../tables.js';