@serfab/quereus-plugin-sereus 0.8.0 → 0.8.1

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.
@@ -18,7 +18,7 @@
18
18
  *
19
19
  * Crypto idiom (matches `schemas/control.qsql` and the sApp RBAC fixture
20
20
  * `packages/integration-tests/fixtures/simple-sapp.qsql`): every signed write
21
- * proves itself with `verify(digest(<concatenated payload>, 'sha256', 'utf8'),
21
+ * proves itself with `verify(digest(<single joined payload>),
22
22
  * <signature>, <pubkey>, 'ed25519')`. Member keys are ed25519, so the explicit
23
23
  * curve arg is REQUIRED (verify() otherwise defaults to secp256k1).
24
24
  *
@@ -27,4 +27,4 @@
27
27
  * the founding `Authority`/`Member`, and the invite/peer flows is owned by
28
28
  * `strand-membership-lifecycle-population`.
29
29
  */
30
- export declare const STRAND_SCHEMA = " table Header (\n Id text, -- UUID of this network - generated at inception - immutable\n\n -- Types: 'o' = Open, 'c' = Closed\n -- Any peers can join an open network, but only members can join a closed network\n -- Open can still control writes in the app, but only Closed prevents reads\n Type text check (Type in ('o', 'c')),\n\n -- The app that this strand faciliates - public key from author to prevent tampering\n sAppId text,\n\n -- The version of the sApp that this strand faciliates\n sAppVersion text,\n\n -- The schema of the sApp that this strand faciliates\n sAppSchema text,\n\n -- The author's signature on the schema to guarantee authorship\n sAppSignature text,\n\n -- The engine (rules logic system) that is assumed to manage this strand\n Engine text,\n\n -- The version of the engine that is assumed to manage this strand\n EngineVersion text,\n\n primary key (/* empty - singleton */),\n constraint InsertOnly check on update, delete (false), -- One-time insert only for now - TODO: revisit for versioning\n );\n\n -- An invitation to join a strand as a member\n table Invite (\n Key text primary key,\n Expiration datetime null,\n -- TODO: ability to deactivate?\n constraint InsertOnly check on update, delete (false),\n constraint OnlyClosed check (\n exists (select 1 from Header H where H.Type = 'c')\n ),\n constraint InviteValid check on insert (\n -- Can only be inserted by an authority,\n exists (select 1 from Authority A\n where A.MemberKey = context.AuthorityKey\n and verify(digest(new.Key || '|' || coalesce(new.Expiration, ''), 'sha256', 'utf8'), context.AuthoritySignature, A.MemberKey, 'ed25519')\n )\n -- and must also prove invite private key held by issuing authority\n and verify(digest(new.Key || '|' || coalesce(new.Expiration, ''), 'sha256', 'utf8'), context.InviteSignature, new.Key, 'ed25519')\n )\n ) with context (AuthorityKey text null, AuthoritySignature text null, InviteSignature text null);\n\n -- Invite [InviteKey] has been used to add [MemberKey] as a member\n table ConsumedInvite (\n InviteKey text primary key,\n MemberKey text,\n constraint InsertOnly check on update, delete (false),\n constraint InviteExists check (exists (select 1 from Invite I where I.Key = new.InviteKey)),\n constraint MemberExists check (exists (select 1 from Member M where M.Key = new.MemberKey)),\n constraint ValidUsage check on insert (\n exists (select 1 from Invite I where I.Key = new.InviteKey and verify(digest(new.InviteKey || '|' || new.MemberKey, 'sha256', 'utf8'), context.InviteSignature, new.InviteKey, 'ed25519'))\n ),\n constraint MemberValid check (exists (select 1 from Member M where M.Key = new.MemberKey))\n ) with context (InviteSignature text null);\n\n -- A party in the closed strand network\n table Member (\n Key text primary key,\n constraint NoUpdate check on update (false),\n constraint OnlyClosed check (\n exists (select 1 from Header H where H.Type = 'c')\n ),\n constraint Authorized check on insert (\n -- There are no other records - first member needs no authorization\n (select count(1) from Member) <= 1\n\n -- or added directly by authority\n or exists (\n select 1 from Authority A\n where A.MemberKey = context.AuthorityKey\n and verify(digest(new.Key, 'sha256', 'utf8'), context.AuthoritySignature, A.MemberKey, 'ed25519')\n )\n\n -- or added by invite\n or exists (\n select 1 from ConsumedInvite CI where CI.MemberKey = new.Key\n )\n ),\n -- TODO: handle member revocation constraint\n ) with context (AuthorityKey text null, AuthoritySignature text null);\n\n -- A member-associated peer (node)\n table MemberPeer (\n MemberKey text,\n PeerId text,\n primary key (MemberKey, PeerId),\n constraint MemberExists check (exists (select 1 from Member M where M.Key = new.MemberKey)),\n constraint Authorized check (\n verify(\n digest(coalesce(new.MemberKey, old.MemberKey) || '|' || coalesce(new.PeerId, old.PeerId), 'sha256', 'utf8'),\n context.Signature,\n coalesce(new.MemberKey, old.MemberKey),\n 'ed25519'\n )\n ),\n ) with context (Signature text null);\n\n -- An authority is a member that can issue invites, authorize members, and rotate authorities\n table Authority (\n MemberKey text primary key,\n constraint OnlyClosed check (\n exists (select 1 from Header H where H.Type = 'c')\n ),\n constraint Authorized check (\n -- There are no existing records - first authority needs no authorization\n (select count(1) from Authority) <= 1\n\n -- or authorized by this former authority\n or (\n old.MemberKey is not null\n and old.MemberKey = context.AuthorityKey\n and verify(digest(old.MemberKey, 'sha256', 'utf8'), context.Signature, old.MemberKey, 'ed25519')\n )\n\n -- or authorized by another existing authority\n or exists (\n select 1 from Authority A\n where A.MemberKey = context.AuthorityKey\n and verify(digest(coalesce(new.MemberKey, old.MemberKey), 'sha256', 'utf8'), context.Signature, A.MemberKey, 'ed25519')\n )\n )\n ) with context (AuthorityKey text null, Signature text null);\n";
30
+ export declare const STRAND_SCHEMA = " table Header (\n Id text, -- UUID of this network - generated at inception - immutable\n\n -- Types: 'o' = Open, 'c' = Closed\n -- Any peers can join an open network, but only members can join a closed network\n -- Open can still control writes in the app, but only Closed prevents reads\n Type text check (Type in ('o', 'c')),\n\n -- The app that this strand faciliates - public key from author to prevent tampering\n sAppId text,\n\n -- The version of the sApp that this strand faciliates\n sAppVersion text,\n\n -- The schema of the sApp that this strand faciliates\n sAppSchema text,\n\n -- The author's signature on the schema to guarantee authorship\n sAppSignature text,\n\n -- The engine (rules logic system) that is assumed to manage this strand\n Engine text,\n\n -- The version of the engine that is assumed to manage this strand\n EngineVersion text,\n\n primary key (/* empty - singleton */),\n constraint InsertOnly check on update, delete (false), -- One-time insert only for now - TODO: revisit for versioning\n );\n\n -- An invitation to join a strand as a member\n table Invite (\n Key text primary key,\n Expiration datetime null,\n -- TODO: ability to deactivate?\n constraint InsertOnly check on update, delete (false),\n constraint OnlyClosed check (\n exists (select 1 from Header H where H.Type = 'c')\n ),\n constraint InviteValid check on insert (\n -- Can only be inserted by an authority,\n exists (select 1 from Authority A\n where A.MemberKey = context.AuthorityKey\n and verify(digest(new.Key || '|' || coalesce(new.Expiration, '')), context.AuthoritySignature, A.MemberKey, 'ed25519')\n )\n -- and must also prove invite private key held by issuing authority\n and verify(digest(new.Key || '|' || coalesce(new.Expiration, '')), context.InviteSignature, new.Key, 'ed25519')\n )\n ) with context (AuthorityKey text null, AuthoritySignature text null, InviteSignature text null);\n\n -- Invite [InviteKey] has been used to add [MemberKey] as a member\n table ConsumedInvite (\n InviteKey text primary key,\n MemberKey text,\n constraint InsertOnly check on update, delete (false),\n constraint InviteExists check (exists (select 1 from Invite I where I.Key = new.InviteKey)),\n constraint MemberExists check (exists (select 1 from Member M where M.Key = new.MemberKey)),\n constraint ValidUsage check on insert (\n exists (select 1 from Invite I where I.Key = new.InviteKey and verify(digest(new.InviteKey || '|' || new.MemberKey), context.InviteSignature, new.InviteKey, 'ed25519'))\n ),\n -- An invite with a non-null Expiration may only be consumed while it is still\n -- in the future. context.Now is the canonical-datetime \"now\" supplied by the\n -- consumeInvite writer (same canonicalDatetime() transform used to store\n -- Invite.Expiration), so both sides of the comparison are byte-identical\n -- canonical strings and the lexical \">\" comparison orders chronologically. A\n -- null Expiration never expires. Mirrors CadreControl.FormationUsage's\n -- \"FI.ExpiresAt is null or FI.ExpiresAt > context.Now\" gate.\n constraint NotExpired check on insert (\n exists (select 1 from Invite I where I.Key = new.InviteKey and (I.Expiration is null or I.Expiration > context.Now))\n ),\n constraint MemberValid check (exists (select 1 from Member M where M.Key = new.MemberKey))\n ) with context (InviteSignature text null, Now datetime null);\n\n -- A party in the closed strand network\n table Member (\n Key text primary key,\n constraint NoUpdate check on update (false),\n constraint OnlyClosed check (\n exists (select 1 from Header H where H.Type = 'c')\n ),\n constraint Authorized check on insert (\n -- There are no other records - first member needs no authorization\n (select count(1) from Member) <= 1\n\n -- or added directly by authority\n or exists (\n select 1 from Authority A\n where A.MemberKey = context.AuthorityKey\n and verify(digest(new.Key), context.AuthoritySignature, A.MemberKey, 'ed25519')\n )\n\n -- or added by invite\n or exists (\n select 1 from ConsumedInvite CI where CI.MemberKey = new.Key\n )\n ),\n -- TODO: handle member revocation constraint\n ) with context (AuthorityKey text null, AuthoritySignature text null);\n\n -- A member-associated peer (node)\n table MemberPeer (\n MemberKey text,\n PeerId text,\n primary key (MemberKey, PeerId),\n constraint MemberExists check (exists (select 1 from Member M where M.Key = new.MemberKey)),\n constraint Authorized check on insert, update, delete (\n verify(\n digest(coalesce(new.MemberKey, old.MemberKey) || '|' || coalesce(new.PeerId, old.PeerId)),\n context.Signature,\n coalesce(new.MemberKey, old.MemberKey),\n 'ed25519'\n )\n ),\n ) with context (Signature text null);\n\n -- An authority is a member that can issue invites, authorize members, and rotate authorities\n table Authority (\n MemberKey text primary key,\n constraint OnlyClosed check (\n exists (select 1 from Header H where H.Type = 'c')\n ),\n constraint Authorized check on insert, update, delete (\n -- There are no existing records - first authority needs no authorization\n (select count(1) from Authority) <= 1\n\n -- or authorized by this former authority\n or (\n old.MemberKey is not null\n and old.MemberKey = context.AuthorityKey\n and verify(digest(old.MemberKey), context.Signature, old.MemberKey, 'ed25519')\n )\n\n -- or authorized by another existing authority\n or exists (\n select 1 from Authority A\n where A.MemberKey = context.AuthorityKey\n and verify(digest(coalesce(new.MemberKey, old.MemberKey)), context.Signature, A.MemberKey, 'ed25519')\n )\n )\n ) with context (AuthorityKey text null, Signature text null);\n";
@@ -18,7 +18,7 @@
18
18
  *
19
19
  * Crypto idiom (matches `schemas/control.qsql` and the sApp RBAC fixture
20
20
  * `packages/integration-tests/fixtures/simple-sapp.qsql`): every signed write
21
- * proves itself with `verify(digest(<concatenated payload>, 'sha256', 'utf8'),
21
+ * proves itself with `verify(digest(<single joined payload>),
22
22
  * <signature>, <pubkey>, 'ed25519')`. Member keys are ed25519, so the explicit
23
23
  * curve arg is REQUIRED (verify() otherwise defaults to secp256k1).
24
24
  *
@@ -70,10 +70,10 @@ export const STRAND_SCHEMA = ` table Header (
70
70
  -- Can only be inserted by an authority,
71
71
  exists (select 1 from Authority A
72
72
  where A.MemberKey = context.AuthorityKey
73
- and verify(digest(new.Key || '|' || coalesce(new.Expiration, ''), 'sha256', 'utf8'), context.AuthoritySignature, A.MemberKey, 'ed25519')
73
+ and verify(digest(new.Key || '|' || coalesce(new.Expiration, '')), context.AuthoritySignature, A.MemberKey, 'ed25519')
74
74
  )
75
75
  -- and must also prove invite private key held by issuing authority
76
- and verify(digest(new.Key || '|' || coalesce(new.Expiration, ''), 'sha256', 'utf8'), context.InviteSignature, new.Key, 'ed25519')
76
+ and verify(digest(new.Key || '|' || coalesce(new.Expiration, '')), context.InviteSignature, new.Key, 'ed25519')
77
77
  )
78
78
  ) with context (AuthorityKey text null, AuthoritySignature text null, InviteSignature text null);
79
79
 
@@ -85,10 +85,20 @@ export const STRAND_SCHEMA = ` table Header (
85
85
  constraint InviteExists check (exists (select 1 from Invite I where I.Key = new.InviteKey)),
86
86
  constraint MemberExists check (exists (select 1 from Member M where M.Key = new.MemberKey)),
87
87
  constraint ValidUsage check on insert (
88
- exists (select 1 from Invite I where I.Key = new.InviteKey and verify(digest(new.InviteKey || '|' || new.MemberKey, 'sha256', 'utf8'), context.InviteSignature, new.InviteKey, 'ed25519'))
88
+ exists (select 1 from Invite I where I.Key = new.InviteKey and verify(digest(new.InviteKey || '|' || new.MemberKey), context.InviteSignature, new.InviteKey, 'ed25519'))
89
+ ),
90
+ -- An invite with a non-null Expiration may only be consumed while it is still
91
+ -- in the future. context.Now is the canonical-datetime "now" supplied by the
92
+ -- consumeInvite writer (same canonicalDatetime() transform used to store
93
+ -- Invite.Expiration), so both sides of the comparison are byte-identical
94
+ -- canonical strings and the lexical ">" comparison orders chronologically. A
95
+ -- null Expiration never expires. Mirrors CadreControl.FormationUsage's
96
+ -- "FI.ExpiresAt is null or FI.ExpiresAt > context.Now" gate.
97
+ constraint NotExpired check on insert (
98
+ exists (select 1 from Invite I where I.Key = new.InviteKey and (I.Expiration is null or I.Expiration > context.Now))
89
99
  ),
90
100
  constraint MemberValid check (exists (select 1 from Member M where M.Key = new.MemberKey))
91
- ) with context (InviteSignature text null);
101
+ ) with context (InviteSignature text null, Now datetime null);
92
102
 
93
103
  -- A party in the closed strand network
94
104
  table Member (
@@ -105,7 +115,7 @@ export const STRAND_SCHEMA = ` table Header (
105
115
  or exists (
106
116
  select 1 from Authority A
107
117
  where A.MemberKey = context.AuthorityKey
108
- and verify(digest(new.Key, 'sha256', 'utf8'), context.AuthoritySignature, A.MemberKey, 'ed25519')
118
+ and verify(digest(new.Key), context.AuthoritySignature, A.MemberKey, 'ed25519')
109
119
  )
110
120
 
111
121
  -- or added by invite
@@ -122,9 +132,9 @@ export const STRAND_SCHEMA = ` table Header (
122
132
  PeerId text,
123
133
  primary key (MemberKey, PeerId),
124
134
  constraint MemberExists check (exists (select 1 from Member M where M.Key = new.MemberKey)),
125
- constraint Authorized check (
135
+ constraint Authorized check on insert, update, delete (
126
136
  verify(
127
- digest(coalesce(new.MemberKey, old.MemberKey) || '|' || coalesce(new.PeerId, old.PeerId), 'sha256', 'utf8'),
137
+ digest(coalesce(new.MemberKey, old.MemberKey) || '|' || coalesce(new.PeerId, old.PeerId)),
128
138
  context.Signature,
129
139
  coalesce(new.MemberKey, old.MemberKey),
130
140
  'ed25519'
@@ -138,7 +148,7 @@ export const STRAND_SCHEMA = ` table Header (
138
148
  constraint OnlyClosed check (
139
149
  exists (select 1 from Header H where H.Type = 'c')
140
150
  ),
141
- constraint Authorized check (
151
+ constraint Authorized check on insert, update, delete (
142
152
  -- There are no existing records - first authority needs no authorization
143
153
  (select count(1) from Authority) <= 1
144
154
 
@@ -146,14 +156,14 @@ export const STRAND_SCHEMA = ` table Header (
146
156
  or (
147
157
  old.MemberKey is not null
148
158
  and old.MemberKey = context.AuthorityKey
149
- and verify(digest(old.MemberKey, 'sha256', 'utf8'), context.Signature, old.MemberKey, 'ed25519')
159
+ and verify(digest(old.MemberKey), context.Signature, old.MemberKey, 'ed25519')
150
160
  )
151
161
 
152
162
  -- or authorized by another existing authority
153
163
  or exists (
154
164
  select 1 from Authority A
155
165
  where A.MemberKey = context.AuthorityKey
156
- and verify(digest(coalesce(new.MemberKey, old.MemberKey), 'sha256', 'utf8'), context.Signature, A.MemberKey, 'ed25519')
166
+ and verify(digest(coalesce(new.MemberKey, old.MemberKey)), context.Signature, A.MemberKey, 'ed25519')
157
167
  )
158
168
  )
159
169
  ) with context (AuthorityKey text null, Signature text null);
@@ -1 +1 @@
1
- {"version":3,"file":"strand-schema.js","sourceRoot":"","sources":["../src/strand-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkI5B,CAAC"}
1
+ {"version":3,"file":"strand-schema.js","sourceRoot":"","sources":["../src/strand-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4I5B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serfab/quereus-plugin-sereus",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "type": "module",
5
5
  "description": "Quereus plugin that connects to a Sereus strand for SQL access",
6
6
  "main": "dist/index.js",
@@ -67,13 +67,13 @@
67
67
  "@libp2p/circuit-relay-v2": "^4.1.3",
68
68
  "@libp2p/interface": "^3.1.0",
69
69
  "@libp2p/websockets": "^10.1.3",
70
- "@optimystic/db-core": "^0.13.5",
71
- "@optimystic/db-p2p": "^0.13.5",
72
- "@optimystic/db-p2p-storage-fs": "^0.13.5",
73
- "@optimystic/db-p2p-storage-web": "^0.13.5",
74
- "@optimystic/quereus-plugin-crypto": "^0.13.5",
75
- "@optimystic/quereus-plugin-optimystic": "^0.13.5",
76
- "@quereus/quereus": "^3.2.1",
70
+ "@optimystic/db-core": "^0.14.1",
71
+ "@optimystic/db-p2p": "^0.14.1",
72
+ "@optimystic/db-p2p-storage-fs": "^0.14.1",
73
+ "@optimystic/db-p2p-storage-web": "^0.14.1",
74
+ "@optimystic/quereus-plugin-crypto": "^0.14.1",
75
+ "@optimystic/quereus-plugin-optimystic": "^0.14.1",
76
+ "@quereus/quereus": "^4.2.1",
77
77
  "debug": "^4.4.3"
78
78
  },
79
79
  "quereus": {
@@ -1,5 +1,5 @@
1
1
  import debug from 'debug';
2
- import type { Database } from '@quereus/quereus';
2
+ import type { Database, VTablePluginInfo, FunctionPluginInfo, CollationPluginInfo } from '@quereus/quereus';
3
3
  import optimysticPlugin from '@optimystic/quereus-plugin-optimystic/plugin';
4
4
  import type { Libp2p } from '@libp2p/interface';
5
5
  import type { IRepo } from '@optimystic/db-core';
@@ -27,9 +27,9 @@ interface CollectionFactory {
27
27
  */
28
28
  interface OptimysticPluginResult {
29
29
  collectionFactory: CollectionFactory;
30
- vtables: Array<{ name: string; module: unknown; auxData: unknown }>;
31
- functions: Array<{ schema: unknown }>;
32
- collations?: Array<{ name: string; func: unknown; normalizer?: unknown }>;
30
+ vtables: VTablePluginInfo[];
31
+ functions: FunctionPluginInfo[];
32
+ collations?: CollationPluginInfo[];
33
33
  /**
34
34
  * Hydrate Quereus's in-memory catalog from persisted optimystic vtab schemas.
35
35
  * Must run BEFORE `apply schema App;` on a warm restart so the declarative
@@ -43,9 +43,9 @@ interface OptimysticPluginResult {
43
43
 
44
44
  /** Registration shape shared by the crypto and optimystic plugin results. */
45
45
  interface PluginRegistrations {
46
- vtables?: Array<{ name: string; module: unknown; auxData: unknown }>;
47
- functions?: Array<{ schema: unknown }>;
48
- collations?: Array<{ name: string; func: unknown; normalizer?: unknown }>;
46
+ vtables?: VTablePluginInfo[];
47
+ functions?: FunctionPluginInfo[];
48
+ collations?: CollationPluginInfo[];
49
49
  }
50
50
 
51
51
  /**
@@ -57,13 +57,13 @@ interface PluginRegistrations {
57
57
  */
58
58
  export function applyRegistrations(db: Database, result: PluginRegistrations): void {
59
59
  for (const vtable of result.vtables ?? []) {
60
- db.registerModule(vtable.name, vtable.module as any, vtable.auxData);
60
+ db.registerModule(vtable.name, vtable.module, vtable.auxData);
61
61
  }
62
62
  for (const func of result.functions ?? []) {
63
- db.registerFunction(func.schema as any);
63
+ db.registerFunction(func.schema);
64
64
  }
65
65
  for (const collation of result.collations ?? []) {
66
- db.registerCollation(collation.name, collation.func as any, collation.normalizer as any);
66
+ db.registerCollation(collation.name, collation.func, collation.normalizer);
67
67
  }
68
68
  }
69
69
 
@@ -185,7 +185,7 @@ export async function composeStrand(
185
185
  const pluginResult = optimysticPlugin(
186
186
  db,
187
187
  pluginConfig as unknown as Parameters<typeof optimysticPlugin>[1],
188
- ) as OptimysticPluginResult;
188
+ ) as unknown as OptimysticPluginResult;
189
189
  applyRegistrations(db, pluginResult);
190
190
  log('Registered optimystic vtables and functions');
191
191
 
@@ -1,4 +1,4 @@
1
- import type { Database } from '@quereus/quereus';
1
+ import type { Database, VTablePluginInfo, FunctionPluginInfo, CollationPluginInfo } from '@quereus/quereus';
2
2
  import cryptoPlugin from '@optimystic/quereus-plugin-crypto/plugin';
3
3
  import { createLibp2pNode } from '@optimystic/db-p2p/rn';
4
4
  import { webSockets } from '@libp2p/websockets';
@@ -10,9 +10,9 @@ import { composeStrand, applyRegistrations } from './compose-strand.js';
10
10
 
11
11
  /** The subset of the crypto plugin result we register inline in the browser. */
12
12
  interface CryptoPluginResult {
13
- vtables: Array<{ name: string; module: unknown; auxData: unknown }>;
14
- functions: Array<{ schema: unknown }>;
15
- collations: Array<{ name: string; func: unknown; normalizer?: unknown }>;
13
+ vtables: VTablePluginInfo[];
14
+ functions: FunctionPluginInfo[];
15
+ collations: CollationPluginInfo[];
16
16
  }
17
17
 
18
18
  /**
@@ -32,7 +32,7 @@ export async function connectToStrandBrowser(
32
32
  ): Promise<SereusPluginResult> {
33
33
  return composeStrand(db, options, {
34
34
  registerCrypto(database) {
35
- applyRegistrations(database, cryptoPlugin(database, {}) as CryptoPluginResult);
35
+ applyRegistrations(database, cryptoPlugin(database, {}) as unknown as CryptoPluginResult);
36
36
  },
37
37
  async resolveStorage({ strandId, resolvedTransactor, requestedStorage }) {
38
38
  if (requestedStorage) return requestedStorage;
@@ -18,7 +18,7 @@
18
18
  *
19
19
  * Crypto idiom (matches `schemas/control.qsql` and the sApp RBAC fixture
20
20
  * `packages/integration-tests/fixtures/simple-sapp.qsql`): every signed write
21
- * proves itself with `verify(digest(<concatenated payload>, 'sha256', 'utf8'),
21
+ * proves itself with `verify(digest(<single joined payload>),
22
22
  * <signature>, <pubkey>, 'ed25519')`. Member keys are ed25519, so the explicit
23
23
  * curve arg is REQUIRED (verify() otherwise defaults to secp256k1).
24
24
  *
@@ -70,10 +70,10 @@ export const STRAND_SCHEMA = ` table Header (
70
70
  -- Can only be inserted by an authority,
71
71
  exists (select 1 from Authority A
72
72
  where A.MemberKey = context.AuthorityKey
73
- and verify(digest(new.Key || '|' || coalesce(new.Expiration, ''), 'sha256', 'utf8'), context.AuthoritySignature, A.MemberKey, 'ed25519')
73
+ and verify(digest(new.Key || '|' || coalesce(new.Expiration, '')), context.AuthoritySignature, A.MemberKey, 'ed25519')
74
74
  )
75
75
  -- and must also prove invite private key held by issuing authority
76
- and verify(digest(new.Key || '|' || coalesce(new.Expiration, ''), 'sha256', 'utf8'), context.InviteSignature, new.Key, 'ed25519')
76
+ and verify(digest(new.Key || '|' || coalesce(new.Expiration, '')), context.InviteSignature, new.Key, 'ed25519')
77
77
  )
78
78
  ) with context (AuthorityKey text null, AuthoritySignature text null, InviteSignature text null);
79
79
 
@@ -85,10 +85,20 @@ export const STRAND_SCHEMA = ` table Header (
85
85
  constraint InviteExists check (exists (select 1 from Invite I where I.Key = new.InviteKey)),
86
86
  constraint MemberExists check (exists (select 1 from Member M where M.Key = new.MemberKey)),
87
87
  constraint ValidUsage check on insert (
88
- exists (select 1 from Invite I where I.Key = new.InviteKey and verify(digest(new.InviteKey || '|' || new.MemberKey, 'sha256', 'utf8'), context.InviteSignature, new.InviteKey, 'ed25519'))
88
+ exists (select 1 from Invite I where I.Key = new.InviteKey and verify(digest(new.InviteKey || '|' || new.MemberKey), context.InviteSignature, new.InviteKey, 'ed25519'))
89
+ ),
90
+ -- An invite with a non-null Expiration may only be consumed while it is still
91
+ -- in the future. context.Now is the canonical-datetime "now" supplied by the
92
+ -- consumeInvite writer (same canonicalDatetime() transform used to store
93
+ -- Invite.Expiration), so both sides of the comparison are byte-identical
94
+ -- canonical strings and the lexical ">" comparison orders chronologically. A
95
+ -- null Expiration never expires. Mirrors CadreControl.FormationUsage's
96
+ -- "FI.ExpiresAt is null or FI.ExpiresAt > context.Now" gate.
97
+ constraint NotExpired check on insert (
98
+ exists (select 1 from Invite I where I.Key = new.InviteKey and (I.Expiration is null or I.Expiration > context.Now))
89
99
  ),
90
100
  constraint MemberValid check (exists (select 1 from Member M where M.Key = new.MemberKey))
91
- ) with context (InviteSignature text null);
101
+ ) with context (InviteSignature text null, Now datetime null);
92
102
 
93
103
  -- A party in the closed strand network
94
104
  table Member (
@@ -105,7 +115,7 @@ export const STRAND_SCHEMA = ` table Header (
105
115
  or exists (
106
116
  select 1 from Authority A
107
117
  where A.MemberKey = context.AuthorityKey
108
- and verify(digest(new.Key, 'sha256', 'utf8'), context.AuthoritySignature, A.MemberKey, 'ed25519')
118
+ and verify(digest(new.Key), context.AuthoritySignature, A.MemberKey, 'ed25519')
109
119
  )
110
120
 
111
121
  -- or added by invite
@@ -122,9 +132,9 @@ export const STRAND_SCHEMA = ` table Header (
122
132
  PeerId text,
123
133
  primary key (MemberKey, PeerId),
124
134
  constraint MemberExists check (exists (select 1 from Member M where M.Key = new.MemberKey)),
125
- constraint Authorized check (
135
+ constraint Authorized check on insert, update, delete (
126
136
  verify(
127
- digest(coalesce(new.MemberKey, old.MemberKey) || '|' || coalesce(new.PeerId, old.PeerId), 'sha256', 'utf8'),
137
+ digest(coalesce(new.MemberKey, old.MemberKey) || '|' || coalesce(new.PeerId, old.PeerId)),
128
138
  context.Signature,
129
139
  coalesce(new.MemberKey, old.MemberKey),
130
140
  'ed25519'
@@ -138,7 +148,7 @@ export const STRAND_SCHEMA = ` table Header (
138
148
  constraint OnlyClosed check (
139
149
  exists (select 1 from Header H where H.Type = 'c')
140
150
  ),
141
- constraint Authorized check (
151
+ constraint Authorized check on insert, update, delete (
142
152
  -- There are no existing records - first authority needs no authorization
143
153
  (select count(1) from Authority) <= 1
144
154
 
@@ -146,14 +156,14 @@ export const STRAND_SCHEMA = ` table Header (
146
156
  or (
147
157
  old.MemberKey is not null
148
158
  and old.MemberKey = context.AuthorityKey
149
- and verify(digest(old.MemberKey, 'sha256', 'utf8'), context.Signature, old.MemberKey, 'ed25519')
159
+ and verify(digest(old.MemberKey), context.Signature, old.MemberKey, 'ed25519')
150
160
  )
151
161
 
152
162
  -- or authorized by another existing authority
153
163
  or exists (
154
164
  select 1 from Authority A
155
165
  where A.MemberKey = context.AuthorityKey
156
- and verify(digest(coalesce(new.MemberKey, old.MemberKey), 'sha256', 'utf8'), context.Signature, A.MemberKey, 'ed25519')
166
+ and verify(digest(coalesce(new.MemberKey, old.MemberKey)), context.Signature, A.MemberKey, 'ed25519')
157
167
  )
158
168
  )
159
169
  ) with context (AuthorityKey text null, Signature text null);