@serfab/quereus-plugin-sereus 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/compose-strand.d.ts +4 -14
- package/dist/compose-strand.js +1 -1
- package/dist/compose-strand.js.map +1 -1
- package/dist/connect-browser.js.map +1 -1
- package/dist/plugin-browser.js +91253 -57470
- package/dist/plugin-browser.js.map +4 -4
- package/dist/strand-schema.d.ts +3 -3
- package/dist/strand-schema.js +41 -31
- package/dist/strand-schema.js.map +1 -1
- package/package.json +8 -8
- package/src/compose-strand.ts +12 -12
- package/src/connect-browser.ts +5 -5
- package/src/strand-schema.ts +41 -31
package/dist/strand-schema.d.ts
CHANGED
|
@@ -18,13 +18,13 @@
|
|
|
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(<
|
|
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
|
*
|
|
25
25
|
* Population note: this ticket applies the schema and makes its constraints
|
|
26
26
|
* active — it does NOT write membership rows at runtime. Inserting the `Header`,
|
|
27
|
-
* the founding `
|
|
27
|
+
* the founding `Manager`/`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
|
|
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 a manager,\n exists (select 1 from Manager A\n where A.MemberKey = context.ManagerKey\n and verify(digest(new.Key || '|' || coalesce(new.Expiration, '')), context.ManagerSignature, A.MemberKey, 'ed25519')\n )\n -- and must also prove invite private key held by issuing manager\n and verify(digest(new.Key || '|' || coalesce(new.Expiration, '')), context.InviteSignature, new.Key, 'ed25519')\n )\n ) with context (ManagerKey text null, ManagerSignature 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 manager\n or exists (\n select 1 from Manager A\n where A.MemberKey = context.ManagerKey\n and verify(digest(new.Key), context.ManagerSignature, 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 (ManagerKey text null, ManagerSignature 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 -- A manager is a member that can issue invites, authorize members, and rotate managers\n table Manager (\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 manager needs no authorization\n (select count(1) from Manager) <= 1\n\n -- or authorized by this former manager\n or (\n old.MemberKey is not null\n and old.MemberKey = context.ManagerKey\n and verify(digest(old.MemberKey), context.Signature, old.MemberKey, 'ed25519')\n )\n\n -- or authorized by another existing manager\n or exists (\n select 1 from Manager A\n where A.MemberKey = context.ManagerKey\n and verify(digest(coalesce(new.MemberKey, old.MemberKey)), context.Signature, A.MemberKey, 'ed25519')\n )\n )\n ) with context (ManagerKey text null, Signature text null);\n";
|
package/dist/strand-schema.js
CHANGED
|
@@ -18,13 +18,13 @@
|
|
|
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(<
|
|
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
|
*
|
|
25
25
|
* Population note: this ticket applies the schema and makes its constraints
|
|
26
26
|
* active — it does NOT write membership rows at runtime. Inserting the `Header`,
|
|
27
|
-
* the founding `
|
|
27
|
+
* the founding `Manager`/`Member`, and the invite/peer flows is owned by
|
|
28
28
|
* `strand-membership-lifecycle-population`.
|
|
29
29
|
*/
|
|
30
30
|
export const STRAND_SCHEMA = ` table Header (
|
|
@@ -67,15 +67,15 @@ export const STRAND_SCHEMA = ` table Header (
|
|
|
67
67
|
exists (select 1 from Header H where H.Type = 'c')
|
|
68
68
|
),
|
|
69
69
|
constraint InviteValid check on insert (
|
|
70
|
-
-- Can only be inserted by
|
|
71
|
-
exists (select 1 from
|
|
72
|
-
where A.MemberKey = context.
|
|
73
|
-
and verify(digest(new.Key || '|' || coalesce(new.Expiration, '')
|
|
70
|
+
-- Can only be inserted by a manager,
|
|
71
|
+
exists (select 1 from Manager A
|
|
72
|
+
where A.MemberKey = context.ManagerKey
|
|
73
|
+
and verify(digest(new.Key || '|' || coalesce(new.Expiration, '')), context.ManagerSignature, A.MemberKey, 'ed25519')
|
|
74
74
|
)
|
|
75
|
-
-- and must also prove invite private key held by issuing
|
|
76
|
-
and verify(digest(new.Key || '|' || coalesce(new.Expiration, '')
|
|
75
|
+
-- and must also prove invite private key held by issuing manager
|
|
76
|
+
and verify(digest(new.Key || '|' || coalesce(new.Expiration, '')), context.InviteSignature, new.Key, 'ed25519')
|
|
77
77
|
)
|
|
78
|
-
) with context (
|
|
78
|
+
) with context (ManagerKey text null, ManagerSignature text null, InviteSignature text null);
|
|
79
79
|
|
|
80
80
|
-- Invite [InviteKey] has been used to add [MemberKey] as a member
|
|
81
81
|
table ConsumedInvite (
|
|
@@ -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
|
|
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 (
|
|
@@ -101,11 +111,11 @@ export const STRAND_SCHEMA = ` table Header (
|
|
|
101
111
|
-- There are no other records - first member needs no authorization
|
|
102
112
|
(select count(1) from Member) <= 1
|
|
103
113
|
|
|
104
|
-
-- or added directly by
|
|
114
|
+
-- or added directly by manager
|
|
105
115
|
or exists (
|
|
106
|
-
select 1 from
|
|
107
|
-
where A.MemberKey = context.
|
|
108
|
-
and verify(digest(new.Key
|
|
116
|
+
select 1 from Manager A
|
|
117
|
+
where A.MemberKey = context.ManagerKey
|
|
118
|
+
and verify(digest(new.Key), context.ManagerSignature, A.MemberKey, 'ed25519')
|
|
109
119
|
)
|
|
110
120
|
|
|
111
121
|
-- or added by invite
|
|
@@ -114,7 +124,7 @@ export const STRAND_SCHEMA = ` table Header (
|
|
|
114
124
|
)
|
|
115
125
|
),
|
|
116
126
|
-- TODO: handle member revocation constraint
|
|
117
|
-
) with context (
|
|
127
|
+
) with context (ManagerKey text null, ManagerSignature text null);
|
|
118
128
|
|
|
119
129
|
-- A member-associated peer (node)
|
|
120
130
|
table MemberPeer (
|
|
@@ -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)
|
|
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'
|
|
@@ -132,30 +142,30 @@ export const STRAND_SCHEMA = ` table Header (
|
|
|
132
142
|
),
|
|
133
143
|
) with context (Signature text null);
|
|
134
144
|
|
|
135
|
-
--
|
|
136
|
-
table
|
|
145
|
+
-- A manager is a member that can issue invites, authorize members, and rotate managers
|
|
146
|
+
table Manager (
|
|
137
147
|
MemberKey text primary key,
|
|
138
148
|
constraint OnlyClosed check (
|
|
139
149
|
exists (select 1 from Header H where H.Type = 'c')
|
|
140
150
|
),
|
|
141
|
-
constraint Authorized check (
|
|
142
|
-
-- There are no existing records - first
|
|
143
|
-
(select count(1) from
|
|
151
|
+
constraint Authorized check on insert, update, delete (
|
|
152
|
+
-- There are no existing records - first manager needs no authorization
|
|
153
|
+
(select count(1) from Manager) <= 1
|
|
144
154
|
|
|
145
|
-
-- or authorized by this former
|
|
155
|
+
-- or authorized by this former manager
|
|
146
156
|
or (
|
|
147
157
|
old.MemberKey is not null
|
|
148
|
-
and old.MemberKey = context.
|
|
149
|
-
and verify(digest(old.MemberKey
|
|
158
|
+
and old.MemberKey = context.ManagerKey
|
|
159
|
+
and verify(digest(old.MemberKey), context.Signature, old.MemberKey, 'ed25519')
|
|
150
160
|
)
|
|
151
161
|
|
|
152
|
-
-- or authorized by another existing
|
|
162
|
+
-- or authorized by another existing manager
|
|
153
163
|
or exists (
|
|
154
|
-
select 1 from
|
|
155
|
-
where A.MemberKey = context.
|
|
156
|
-
and verify(digest(coalesce(new.MemberKey, old.MemberKey)
|
|
164
|
+
select 1 from Manager A
|
|
165
|
+
where A.MemberKey = context.ManagerKey
|
|
166
|
+
and verify(digest(coalesce(new.MemberKey, old.MemberKey)), context.Signature, A.MemberKey, 'ed25519')
|
|
157
167
|
)
|
|
158
168
|
)
|
|
159
|
-
) with context (
|
|
169
|
+
) with context (ManagerKey text null, Signature text null);
|
|
160
170
|
`;
|
|
161
171
|
//# sourceMappingURL=strand-schema.js.map
|
|
@@ -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
|
|
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.
|
|
3
|
+
"version": "0.9.0",
|
|
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.
|
|
71
|
-
"@optimystic/db-p2p": "^0.
|
|
72
|
-
"@optimystic/db-p2p-storage-fs": "^0.
|
|
73
|
-
"@optimystic/db-p2p-storage-web": "^0.
|
|
74
|
-
"@optimystic/quereus-plugin-crypto": "^0.
|
|
75
|
-
"@optimystic/quereus-plugin-optimystic": "^0.
|
|
76
|
-
"@quereus/quereus": "^
|
|
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.4.0",
|
|
77
77
|
"debug": "^4.4.3"
|
|
78
78
|
},
|
|
79
79
|
"quereus": {
|
package/src/compose-strand.ts
CHANGED
|
@@ -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:
|
|
31
|
-
functions:
|
|
32
|
-
collations?:
|
|
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?:
|
|
47
|
-
functions?:
|
|
48
|
-
collations?:
|
|
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
|
|
60
|
+
db.registerModule(vtable.name, vtable.module, vtable.auxData);
|
|
61
61
|
}
|
|
62
62
|
for (const func of result.functions ?? []) {
|
|
63
|
-
db.registerFunction(func.schema
|
|
63
|
+
db.registerFunction(func.schema);
|
|
64
64
|
}
|
|
65
65
|
for (const collation of result.collations ?? []) {
|
|
66
|
-
db.registerCollation(collation.name, collation.func
|
|
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
|
|
|
@@ -254,7 +254,7 @@ export async function composeStrand(
|
|
|
254
254
|
//
|
|
255
255
|
// This ticket only makes the tables present and their constraints active;
|
|
256
256
|
// nothing here writes membership rows. Founder bootstrap (Header, founding
|
|
257
|
-
//
|
|
257
|
+
// Manager/Member, invite/peer flows) is owned by the lifecycle ticket
|
|
258
258
|
// `strand-membership-lifecycle-population`.
|
|
259
259
|
log('Applying Strand membership schema for strand %s', strandId);
|
|
260
260
|
await db.exec(`
|
package/src/connect-browser.ts
CHANGED
|
@@ -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:
|
|
14
|
-
functions:
|
|
15
|
-
collations:
|
|
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;
|
package/src/strand-schema.ts
CHANGED
|
@@ -18,13 +18,13 @@
|
|
|
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(<
|
|
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
|
*
|
|
25
25
|
* Population note: this ticket applies the schema and makes its constraints
|
|
26
26
|
* active — it does NOT write membership rows at runtime. Inserting the `Header`,
|
|
27
|
-
* the founding `
|
|
27
|
+
* the founding `Manager`/`Member`, and the invite/peer flows is owned by
|
|
28
28
|
* `strand-membership-lifecycle-population`.
|
|
29
29
|
*/
|
|
30
30
|
export const STRAND_SCHEMA = ` table Header (
|
|
@@ -67,15 +67,15 @@ export const STRAND_SCHEMA = ` table Header (
|
|
|
67
67
|
exists (select 1 from Header H where H.Type = 'c')
|
|
68
68
|
),
|
|
69
69
|
constraint InviteValid check on insert (
|
|
70
|
-
-- Can only be inserted by
|
|
71
|
-
exists (select 1 from
|
|
72
|
-
where A.MemberKey = context.
|
|
73
|
-
and verify(digest(new.Key || '|' || coalesce(new.Expiration, '')
|
|
70
|
+
-- Can only be inserted by a manager,
|
|
71
|
+
exists (select 1 from Manager A
|
|
72
|
+
where A.MemberKey = context.ManagerKey
|
|
73
|
+
and verify(digest(new.Key || '|' || coalesce(new.Expiration, '')), context.ManagerSignature, A.MemberKey, 'ed25519')
|
|
74
74
|
)
|
|
75
|
-
-- and must also prove invite private key held by issuing
|
|
76
|
-
and verify(digest(new.Key || '|' || coalesce(new.Expiration, '')
|
|
75
|
+
-- and must also prove invite private key held by issuing manager
|
|
76
|
+
and verify(digest(new.Key || '|' || coalesce(new.Expiration, '')), context.InviteSignature, new.Key, 'ed25519')
|
|
77
77
|
)
|
|
78
|
-
) with context (
|
|
78
|
+
) with context (ManagerKey text null, ManagerSignature text null, InviteSignature text null);
|
|
79
79
|
|
|
80
80
|
-- Invite [InviteKey] has been used to add [MemberKey] as a member
|
|
81
81
|
table ConsumedInvite (
|
|
@@ -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
|
|
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 (
|
|
@@ -101,11 +111,11 @@ export const STRAND_SCHEMA = ` table Header (
|
|
|
101
111
|
-- There are no other records - first member needs no authorization
|
|
102
112
|
(select count(1) from Member) <= 1
|
|
103
113
|
|
|
104
|
-
-- or added directly by
|
|
114
|
+
-- or added directly by manager
|
|
105
115
|
or exists (
|
|
106
|
-
select 1 from
|
|
107
|
-
where A.MemberKey = context.
|
|
108
|
-
and verify(digest(new.Key
|
|
116
|
+
select 1 from Manager A
|
|
117
|
+
where A.MemberKey = context.ManagerKey
|
|
118
|
+
and verify(digest(new.Key), context.ManagerSignature, A.MemberKey, 'ed25519')
|
|
109
119
|
)
|
|
110
120
|
|
|
111
121
|
-- or added by invite
|
|
@@ -114,7 +124,7 @@ export const STRAND_SCHEMA = ` table Header (
|
|
|
114
124
|
)
|
|
115
125
|
),
|
|
116
126
|
-- TODO: handle member revocation constraint
|
|
117
|
-
) with context (
|
|
127
|
+
) with context (ManagerKey text null, ManagerSignature text null);
|
|
118
128
|
|
|
119
129
|
-- A member-associated peer (node)
|
|
120
130
|
table MemberPeer (
|
|
@@ -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)
|
|
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'
|
|
@@ -132,29 +142,29 @@ export const STRAND_SCHEMA = ` table Header (
|
|
|
132
142
|
),
|
|
133
143
|
) with context (Signature text null);
|
|
134
144
|
|
|
135
|
-
--
|
|
136
|
-
table
|
|
145
|
+
-- A manager is a member that can issue invites, authorize members, and rotate managers
|
|
146
|
+
table Manager (
|
|
137
147
|
MemberKey text primary key,
|
|
138
148
|
constraint OnlyClosed check (
|
|
139
149
|
exists (select 1 from Header H where H.Type = 'c')
|
|
140
150
|
),
|
|
141
|
-
constraint Authorized check (
|
|
142
|
-
-- There are no existing records - first
|
|
143
|
-
(select count(1) from
|
|
151
|
+
constraint Authorized check on insert, update, delete (
|
|
152
|
+
-- There are no existing records - first manager needs no authorization
|
|
153
|
+
(select count(1) from Manager) <= 1
|
|
144
154
|
|
|
145
|
-
-- or authorized by this former
|
|
155
|
+
-- or authorized by this former manager
|
|
146
156
|
or (
|
|
147
157
|
old.MemberKey is not null
|
|
148
|
-
and old.MemberKey = context.
|
|
149
|
-
and verify(digest(old.MemberKey
|
|
158
|
+
and old.MemberKey = context.ManagerKey
|
|
159
|
+
and verify(digest(old.MemberKey), context.Signature, old.MemberKey, 'ed25519')
|
|
150
160
|
)
|
|
151
161
|
|
|
152
|
-
-- or authorized by another existing
|
|
162
|
+
-- or authorized by another existing manager
|
|
153
163
|
or exists (
|
|
154
|
-
select 1 from
|
|
155
|
-
where A.MemberKey = context.
|
|
156
|
-
and verify(digest(coalesce(new.MemberKey, old.MemberKey)
|
|
164
|
+
select 1 from Manager A
|
|
165
|
+
where A.MemberKey = context.ManagerKey
|
|
166
|
+
and verify(digest(coalesce(new.MemberKey, old.MemberKey)), context.Signature, A.MemberKey, 'ed25519')
|
|
157
167
|
)
|
|
158
168
|
)
|
|
159
|
-
) with context (
|
|
169
|
+
) with context (ManagerKey text null, Signature text null);
|
|
160
170
|
`;
|