@serfab/cadre-core 1.15.0 → 1.17.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/README.md +1 -1
- package/dist/bootstrap-peer-store-file.js +1 -1
- package/dist/bootstrap-peer-store.d.ts +1 -1
- package/dist/bootstrap-peer-store.js +4 -4
- package/dist/cadre-invite-protocol.d.ts +55 -138
- package/dist/cadre-invite-protocol.js +94 -449
- package/dist/cadre-invite-protocol.js.map +1 -1
- package/dist/cadre-join-request.d.ts +15 -2
- package/dist/cadre-join-request.js +22 -3
- package/dist/cadre-join-request.js.map +1 -1
- package/dist/cadre-node.d.ts +530 -247
- package/dist/cadre-node.js +1046 -525
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-authorization.d.ts +36 -14
- package/dist/control-authorization.js +20 -3
- package/dist/control-authorization.js.map +1 -1
- package/dist/control-cohort.d.ts +22 -23
- package/dist/control-cohort.js +24 -24
- package/dist/control-cohort.js.map +1 -1
- package/dist/control-database.d.ts +416 -136
- package/dist/control-database.js +757 -378
- package/dist/control-database.js.map +1 -1
- package/dist/control-protocol-guard.d.ts +5 -4
- package/dist/control-protocol-guard.js +6 -3
- package/dist/control-protocol-guard.js.map +1 -1
- package/dist/control-schema-version.d.ts +80 -0
- package/dist/control-schema-version.js +203 -0
- package/dist/control-schema-version.js.map +1 -0
- package/dist/control-schema.d.ts +45 -5
- package/dist/control-schema.js +254 -70
- package/dist/control-schema.js.map +1 -1
- package/dist/control-write-retry.d.ts +5 -5
- package/dist/control-write-retry.js +10 -8
- package/dist/control-write-retry.js.map +1 -1
- package/dist/delegate-admission.d.ts +13 -0
- package/dist/delegate-admission.js +27 -0
- package/dist/delegate-admission.js.map +1 -1
- package/dist/ed25519-key.d.ts +10 -7
- package/dist/ed25519-key.js +23 -20
- package/dist/ed25519-key.js.map +1 -1
- package/dist/edge-node/config.d.ts +122 -0
- package/dist/edge-node/config.js +199 -0
- package/dist/edge-node/config.js.map +1 -0
- package/dist/edge-node/dissolve.d.ts +95 -0
- package/dist/edge-node/dissolve.js +184 -0
- package/dist/edge-node/dissolve.js.map +1 -0
- package/dist/edge-node/edge-join.d.ts +44 -0
- package/dist/edge-node/edge-join.js +227 -0
- package/dist/edge-node/edge-join.js.map +1 -0
- package/dist/edge-node/edge-node.d.ts +295 -0
- package/dist/edge-node/edge-node.js +704 -0
- package/dist/edge-node/edge-node.js.map +1 -0
- package/dist/edge-node/index.d.ts +18 -0
- package/dist/edge-node/index.js +18 -0
- package/dist/edge-node/index.js.map +1 -0
- package/dist/edge-node/join-credential.d.ts +11 -0
- package/dist/edge-node/join-credential.js +11 -0
- package/dist/edge-node/join-credential.js.map +1 -0
- package/dist/edge-node/lazy-raw-storage.d.ts +40 -0
- package/dist/edge-node/lazy-raw-storage.js +134 -0
- package/dist/edge-node/lazy-raw-storage.js.map +1 -0
- package/dist/edge-node/node-local.d.ts +60 -0
- package/dist/edge-node/node-local.js +58 -0
- package/dist/edge-node/node-local.js.map +1 -0
- package/dist/edge-node/options.d.ts +83 -0
- package/dist/edge-node/options.js +110 -0
- package/dist/edge-node/options.js.map +1 -0
- package/dist/edge-node/owner-grant.d.ts +41 -0
- package/dist/edge-node/owner-grant.js +38 -0
- package/dist/edge-node/owner-grant.js.map +1 -0
- package/dist/edge-node/platform.d.ts +140 -0
- package/dist/edge-node/platform.js +12 -0
- package/dist/edge-node/platform.js.map +1 -0
- package/dist/edge-node/strand-move.d.ts +115 -0
- package/dist/edge-node/strand-move.js +223 -0
- package/dist/edge-node/strand-move.js.map +1 -0
- package/dist/edge-node/strands.d.ts +28 -0
- package/dist/edge-node/strands.js +60 -0
- package/dist/edge-node/strands.js.map +1 -0
- package/dist/index.d.ts +16 -7
- package/dist/index.js +26 -7
- package/dist/index.js.map +1 -1
- package/dist/joined-strand-store.d.ts +3 -3
- package/dist/joined-strand-store.js +3 -3
- package/dist/joined-strand-store.js.map +1 -1
- package/dist/key-store.d.ts +9 -4
- package/dist/key-store.js +9 -4
- package/dist/key-store.js.map +1 -1
- package/dist/machine-removal.d.ts +19 -39
- package/dist/machine-removal.js +24 -98
- package/dist/machine-removal.js.map +1 -1
- package/dist/member-redemption.d.ts +250 -0
- package/dist/member-redemption.js +485 -0
- package/dist/member-redemption.js.map +1 -0
- package/dist/owner-anchor-sync.d.ts +4 -4
- package/dist/owner-anchor-sync.js +16 -15
- package/dist/owner-anchor-sync.js.map +1 -1
- package/dist/owner-grant-protocol.d.ts +247 -0
- package/dist/owner-grant-protocol.js +330 -0
- package/dist/owner-grant-protocol.js.map +1 -0
- package/dist/owner-key-export.d.ts +70 -0
- package/dist/owner-key-export.js +279 -0
- package/dist/owner-key-export.js.map +1 -0
- package/dist/owner-key-flows.d.ts +55 -0
- package/dist/owner-key-flows.js +60 -0
- package/dist/owner-key-flows.js.map +1 -0
- package/dist/owner-key-store.d.ts +31 -0
- package/dist/owner-key-store.js +81 -0
- package/dist/owner-key-store.js.map +1 -0
- package/dist/owner-keys.d.ts +78 -0
- package/dist/owner-keys.js +133 -0
- package/dist/owner-keys.js.map +1 -0
- package/dist/owner-signer.d.ts +54 -0
- package/dist/owner-signer.js +51 -0
- package/dist/owner-signer.js.map +1 -0
- package/dist/peer-authorization.d.ts +83 -18
- package/dist/peer-authorization.js +123 -53
- package/dist/peer-authorization.js.map +1 -1
- package/dist/provider/rent-node.js +1 -1
- package/dist/provider/rent-node.js.map +1 -1
- package/dist/seed-bootstrap.d.ts +131 -109
- package/dist/seed-bootstrap.js +260 -248
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/seed-trust-policy.js +4 -4
- package/dist/strand-addr-protocol.d.ts +4 -1
- package/dist/strand-addr-protocol.js +8 -2
- package/dist/strand-addr-protocol.js.map +1 -1
- package/dist/strand-cohort.d.ts +3 -0
- package/dist/strand-cohort.js.map +1 -1
- package/dist/strand-database.d.ts +1 -0
- package/dist/strand-database.js +3 -0
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-first-sync-gate.d.ts +4 -3
- package/dist/strand-first-sync-gate.js +4 -3
- package/dist/strand-first-sync-gate.js.map +1 -1
- package/dist/strand-instance-manager.d.ts +1 -1
- package/dist/strand-membership-writer.d.ts +13 -2
- package/dist/strand-membership-writer.js +52 -16
- package/dist/strand-membership-writer.js.map +1 -1
- package/dist/strand-move.d.ts +6 -5
- package/dist/strand-move.js +31 -30
- package/dist/strand-move.js.map +1 -1
- package/dist/strand-peer-redial.js +2 -3
- package/dist/strand-peer-redial.js.map +1 -1
- package/dist/types.d.ts +286 -103
- package/dist/types.js.map +1 -1
- package/package.json +16 -7
- package/src/bootstrap-peer-store-file.ts +44 -44
- package/src/bootstrap-peer-store.ts +214 -214
- package/src/cadre-invite-protocol.ts +131 -531
- package/src/cadre-join-request.ts +188 -167
- package/src/cadre-node.ts +1166 -541
- package/src/control-authorization.ts +47 -14
- package/src/control-cohort.ts +127 -128
- package/src/control-database.ts +940 -456
- package/src/control-protocol-guard.ts +7 -4
- package/src/control-schema-version.ts +251 -0
- package/src/control-schema.ts +265 -70
- package/src/control-write-retry.ts +569 -567
- package/src/delegate-admission.ts +37 -0
- package/src/ed25519-key.ts +174 -171
- package/src/edge-node/config.ts +277 -0
- package/src/edge-node/dissolve.ts +251 -0
- package/src/edge-node/edge-join.ts +319 -0
- package/src/edge-node/edge-node.ts +1045 -0
- package/src/edge-node/index.ts +59 -0
- package/src/edge-node/join-credential.ts +16 -0
- package/src/edge-node/lazy-raw-storage.ts +178 -0
- package/src/edge-node/node-local.ts +84 -0
- package/src/edge-node/options.ts +179 -0
- package/src/edge-node/owner-grant.ts +63 -0
- package/src/edge-node/platform.ts +151 -0
- package/src/edge-node/strand-move.ts +270 -0
- package/src/edge-node/strands.ts +72 -0
- package/src/index.ts +102 -5
- package/src/joined-strand-store.ts +4 -4
- package/src/key-store.ts +10 -4
- package/src/machine-removal.ts +26 -111
- package/src/member-redemption.ts +642 -0
- package/src/owner-anchor-sync.ts +19 -18
- package/src/owner-grant-protocol.ts +532 -0
- package/src/owner-key-export.ts +348 -0
- package/src/owner-key-flows.ts +109 -0
- package/src/owner-key-store.ts +85 -0
- package/src/owner-keys.ts +178 -0
- package/src/owner-signer.ts +94 -0
- package/src/peer-authorization.ts +558 -488
- package/src/provider/rent-node.ts +1 -1
- package/src/seed-bootstrap.ts +301 -312
- package/src/seed-trust-policy.ts +4 -4
- package/src/strand-addr-protocol.ts +547 -538
- package/src/strand-cohort.ts +4 -0
- package/src/strand-database.ts +3 -0
- package/src/strand-first-sync-gate.ts +4 -3
- package/src/strand-instance-manager.ts +1 -1
- package/src/strand-membership-writer.ts +61 -16
- package/src/strand-move.ts +45 -36
- package/src/strand-peer-redial.ts +2 -3
- package/src/types.ts +304 -103
package/src/control-schema.ts
CHANGED
|
@@ -1,15 +1,43 @@
|
|
|
1
|
+
import { Parser } from '@quereus/quereus';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
|
-
*
|
|
4
|
+
* The version of the declared control schema, carried in the schema text as the
|
|
5
|
+
* {@link CONTROL_SCHEMA_VERSION_TAG} tag on the `OwnerKey` table. A warm start compares
|
|
6
|
+
* it with the version the store records before applying anything
|
|
7
|
+
* (`control-schema-version.ts`, which also says why that is the version the store was
|
|
8
|
+
* created under).
|
|
3
9
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
10
|
+
* When it changes, what a new version may change, and the fixture each version keeps:
|
|
11
|
+
* docs/architecture.md → "Control schema changes and incompatible stores", enforced by
|
|
12
|
+
* `control-schema-versions.spec.ts`.
|
|
13
|
+
*/
|
|
14
|
+
export const CONTROL_SCHEMA_VERSION = 3;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The first control schema version whose fixture is kept, and the oldest a store can be
|
|
18
|
+
* migrated from. Stores that record no version (written by 1.16.0 and earlier) are not
|
|
19
|
+
* migrated; docs/architecture.md says why.
|
|
20
|
+
*/
|
|
21
|
+
export const CONTROL_SCHEMA_BASELINE_VERSION = 3;
|
|
22
|
+
|
|
23
|
+
/** The `OwnerKey` table tag that records {@link CONTROL_SCHEMA_VERSION} in the schema and in every store. */
|
|
24
|
+
export const CONTROL_SCHEMA_VERSION_TAG = 'sereus.controlSchemaVersion';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The `declare schema CadreControl { … }` statement, with its header comment.
|
|
28
|
+
*
|
|
29
|
+
* Together with {@link APPLY_CONTROL_SCHEMA} this is the authoritative runtime copy of
|
|
30
|
+
* the CadreControl authorization schema ({@link CONTROL_SCHEMA}). It is duplicated from
|
|
31
|
+
* `schemas/control.qsql` so that React Native and other filesystem-less environments
|
|
32
|
+
* get the schema without a runtime file read. Held apart from the apply because
|
|
33
|
+
* `ControlDatabase.loadSchema` declares, checks the pending changes, and only then
|
|
34
|
+
* applies.
|
|
7
35
|
*
|
|
8
36
|
* The two copies MUST stay identical — `control-schema-drift.spec.ts` fails the
|
|
9
37
|
* build if they drift. Any edit here must be mirrored in `schemas/control.qsql`
|
|
10
38
|
* and vice versa.
|
|
11
39
|
*/
|
|
12
|
-
export const
|
|
40
|
+
export const CONTROL_SCHEMA_DECLARATION = `-- A Sereus party's cadre (its nodes) and their participation in strands (networks).
|
|
13
41
|
--
|
|
14
42
|
-- Every write is authorized by a check: an owner signs an action-tagged digest of the row
|
|
15
43
|
-- (cadre-core control-authorization.ts), or a peer signs its own record. Each digest leads
|
|
@@ -21,13 +49,14 @@ export const CONTROL_SCHEMA = `-- A Sereus party's cadre (its nodes) and their p
|
|
|
21
49
|
-- two digests scoped by the invitation token instead (FormationUsage.Authorized).
|
|
22
50
|
--
|
|
23
51
|
-- Rows of the guarded tables (OwnerKey, ValidationKey, Strand, StrandPartyKey, JoinedStrand,
|
|
24
|
-
-- JoinRequest, CadrePeer, DeviceToken, CadreInvite) carry a one-off StampId bound
|
|
25
|
-
-- add and remove digests. \`unique\` holds over live rows only, so a delete retires
|
|
26
|
-
-- into Revocation in the same transaction (RevocationRecorded) and an insert refuses
|
|
27
|
-
-- stamp (NotRevoked): the never-expiring add approval cannot re-seat a removed row.
|
|
28
|
-
-- legitimate re-add mints a fresh stamp and a fresh signature. CadreInvite
|
|
29
|
-
--
|
|
30
|
-
-- because the rows it admitted prove
|
|
52
|
+
-- JoinRequest, CadrePeer, DeviceToken, CadreInvite, OwnerGrant) carry a one-off StampId bound
|
|
53
|
+
-- into their add and remove digests. \`unique\` holds over live rows only, so a delete retires
|
|
54
|
+
-- the stamp into Revocation in the same transaction (RevocationRecorded) and an insert refuses
|
|
55
|
+
-- a retired stamp (NotRevoked): the never-expiring add approval cannot re-seat a removed row.
|
|
56
|
+
-- A legitimate re-add mints a fresh stamp and a fresh signature. CadreInvite and OwnerGrant
|
|
57
|
+
-- are the two guarded tables that are never deleted: a tombstone there withdraws the
|
|
58
|
+
-- invitation or grant while the row stays, because the rows it admitted prove themselves
|
|
59
|
+
-- through it.
|
|
31
60
|
--
|
|
32
61
|
-- A check whose expression evaluates to null passes (SQL three-valued logic), so every
|
|
33
62
|
-- comparison over a nullable column that is meant to pin a value is written null-safe
|
|
@@ -47,12 +76,16 @@ export const CONTROL_SCHEMA = `-- A Sereus party's cadre (its nodes) and their p
|
|
|
47
76
|
declare schema CadreControl {
|
|
48
77
|
-- A key that can authorize control changes. A row is seated one of three ways, told apart
|
|
49
78
|
-- by the vouch columns: the founding row (all three null), an owner-signed add (VouchOwner
|
|
50
|
-
-- and VouchSig pinned to the insert context, VouchUsage null), or an owner
|
|
51
|
-
--
|
|
52
|
-
--
|
|
53
|
-
--
|
|
79
|
+
-- and VouchSig pinned to the insert context, VouchUsage null), or an owner grant redeemed
|
|
80
|
+
-- by consent (VouchOwner = the grant's IssuerKey, VouchSig null, VouchUsage = the
|
|
81
|
+
-- OwnerGrantUsage row that seated it). A machine invitation (CadreInvite) admits a machine
|
|
82
|
+
-- and never seats a row here: a machine's identity key is never an owner key. A reader
|
|
83
|
+
-- judges the stored proof against its node-local anchor, never against this replicated
|
|
84
|
+
-- table.
|
|
54
85
|
table OwnerKey (
|
|
55
86
|
Key text primary key,
|
|
87
|
+
Label text null, -- the user's name for the key ("phone", "paper backup"); bound into the
|
|
88
|
+
-- signed add, so a relayed approval cannot seat the key under another name
|
|
56
89
|
StampId text unique,
|
|
57
90
|
VouchOwner text null,
|
|
58
91
|
VouchSig text null,
|
|
@@ -60,6 +93,10 @@ declare schema CadreControl {
|
|
|
60
93
|
constraint NotRevoked check on insert (
|
|
61
94
|
not exists (select 1 from Revocation R where R.TableName = 'OwnerKey' and R.StampId = new.StampId)
|
|
62
95
|
),
|
|
96
|
+
-- Never empty: the add digest binds coalesce(Label, ''), so an empty label would sign as none.
|
|
97
|
+
constraint LabelLength check (
|
|
98
|
+
Label is null or length(Label) between 1 and 64
|
|
99
|
+
),
|
|
63
100
|
-- The tombstone must name the removed row, not just its stamp (Revocation.RowKey).
|
|
64
101
|
constraint RevocationRecorded check on delete (
|
|
65
102
|
exists (select 1 from Revocation R where R.TableName = 'OwnerKey' and R.StampId = old.StampId and R.RowKey = old.Key)
|
|
@@ -81,29 +118,34 @@ declare schema CadreControl {
|
|
|
81
118
|
-- deliberately raw, the one owner read here that ignores Revocation: a machine holding only
|
|
82
119
|
-- a retired owner row must still be refused a second genesis, or a removed device could
|
|
83
120
|
-- re-found the party on its own copy. An owner cannot sign its own removal.
|
|
84
|
-
-- The consent branch seats an owner with no signature at all:
|
|
85
|
-
-- written in the same transaction names this exact row (Key and stamp), and the
|
|
86
|
-
--
|
|
87
|
-
--
|
|
88
|
-
-- redeemed in one transaction; the usage
|
|
121
|
+
-- The consent branch seats an owner with no signature at all: an OwnerGrantUsage row
|
|
122
|
+
-- written in the same transaction names this exact row (Key and stamp), and the grant
|
|
123
|
+
-- it redeemed was issued by the stored VouchOwner and carries the row's label (the
|
|
124
|
+
-- issuer's choice, bound into the grant's signature). The grant is read from its
|
|
125
|
+
-- committed table, so it cannot be seated and redeemed in one transaction; the usage
|
|
126
|
+
-- row carries the liveness conditions.
|
|
89
127
|
constraint Authorized check on insert, delete (
|
|
90
128
|
(old.Key is null and (select count(1) from committed.OwnerKey) = 0
|
|
91
129
|
and new.VouchOwner is null and new.VouchSig is null and new.VouchUsage is null)
|
|
92
|
-
or (old.Key is null and exists (select 1 from committed.OwnerKey A where A.Key = context.OwnerKey and not exists (select 1 from Revocation R where R.TableName = 'OwnerKey' and R.StampId = A.StampId) and verify(digest('CadreControl.OwnerKey', 'add', party_id(), new.Key, new.StampId), context.Signature, A.Key, 'ed25519'))
|
|
130
|
+
or (old.Key is null and exists (select 1 from committed.OwnerKey A where A.Key = context.OwnerKey and not exists (select 1 from Revocation R where R.TableName = 'OwnerKey' and R.StampId = A.StampId) and verify(digest('CadreControl.OwnerKey', 'add', party_id(), new.Key, coalesce(new.Label, ''), new.StampId), context.Signature, A.Key, 'ed25519'))
|
|
93
131
|
and coalesce(new.VouchOwner, '') = context.OwnerKey
|
|
94
132
|
and coalesce(new.VouchSig, '') = context.Signature
|
|
95
133
|
and new.VouchUsage is null)
|
|
96
134
|
or (old.Key is null and new.VouchSig is null and new.VouchUsage is not null
|
|
97
135
|
and exists (
|
|
98
|
-
select 1 from
|
|
136
|
+
select 1 from OwnerGrantUsage U
|
|
99
137
|
where U.UsageStampId = new.VouchUsage
|
|
100
|
-
and U.
|
|
101
|
-
and U.
|
|
102
|
-
and exists (select 1 from committed.
|
|
138
|
+
and U.SeatedKey = new.Key
|
|
139
|
+
and U.SeatedStampId = new.StampId
|
|
140
|
+
and exists (select 1 from committed.OwnerGrant G
|
|
141
|
+
where G.Key = U.GrantKey and G.IssuerKey = new.VouchOwner
|
|
142
|
+
and coalesce(G.Label, '') = coalesce(new.Label, ''))
|
|
103
143
|
))
|
|
104
144
|
or (new.Key is null and exists (select 1 from committed.OwnerKey A where A.Key = context.OwnerKey and A.Key <> old.Key and not exists (select 1 from Revocation R where R.TableName = 'OwnerKey' and R.StampId = A.StampId) and verify(digest('CadreControl.OwnerKey', 'remove', party_id(), old.Key, old.StampId), context.Signature, A.Key, 'ed25519')))
|
|
105
145
|
)
|
|
106
|
-
|
|
146
|
+
-- The tag is the version of this whole schema (cadre-core CONTROL_SCHEMA_VERSION): every edit
|
|
147
|
+
-- to the schema bumps it, and a warm start reads the store's copy before applying anything.
|
|
148
|
+
) with tags ("${CONTROL_SCHEMA_VERSION_TAG}" = ${CONTROL_SCHEMA_VERSION}) with context (OwnerKey text null, Signature text null);
|
|
107
149
|
|
|
108
150
|
-- A key that can validate a strand formation disclosure (FormationUsage.Authorized).
|
|
109
151
|
table ValidationKey (
|
|
@@ -136,9 +178,12 @@ declare schema CadreControl {
|
|
|
136
178
|
Id text primary key, -- UUID
|
|
137
179
|
MemberPrivateKey text null unique, -- the strand's shared read secret; closed strands only
|
|
138
180
|
Type text, -- 'o' open | 'c' closed: open gates writes in the sApp, only closed gates reads
|
|
139
|
-
FounderOwnerKey text null, -- owner key
|
|
140
|
-
--
|
|
141
|
-
|
|
181
|
+
FounderOwnerKey text null, -- the owner key that signed the insert; null on a consent-seated strand,
|
|
182
|
+
-- whose insert carries no signature. Provenance, not content (strandRowMismatches).
|
|
183
|
+
FounderPeerId text null, -- peer id of the machine that runs the founder bootstrap (cadre-node.ts
|
|
184
|
+
-- launchStrand), bound into the signed add; null on a consent-seated strand.
|
|
185
|
+
-- Not pinned to a CadrePeer row: a solo founder publishes before its own
|
|
186
|
+
-- row exists, and only an owner can name it. Provenance, as FounderOwnerKey.
|
|
142
187
|
StampId text unique,
|
|
143
188
|
constraint NotRevoked check on insert (
|
|
144
189
|
not exists (select 1 from Revocation R where R.TableName = 'Strand' and R.StampId = new.StampId)
|
|
@@ -149,9 +194,10 @@ declare schema CadreControl {
|
|
|
149
194
|
-- The consent branch below carries no signature, so an update rule would let anyone rewrite
|
|
150
195
|
-- a consent-seated strand.
|
|
151
196
|
constraint NoUpdate check on update (false),
|
|
152
|
-
-- Owner-signed, with FounderOwnerKey pinned to the verified signer
|
|
153
|
-
-- FormationUsage row naming this exact row (id
|
|
154
|
-
-- (ControlDatabase.redeemInvitation). Binding
|
|
197
|
+
-- Owner-signed, with FounderOwnerKey pinned to the verified signer and FounderPeerId bound
|
|
198
|
+
-- into the signature; or consent-seated by a FormationUsage row naming this exact row (id
|
|
199
|
+
-- and stamp), written in the same transaction (ControlDatabase.redeemInvitation). Binding
|
|
200
|
+
-- the stamp is what makes a removal stick:
|
|
155
201
|
-- the usage row outlives the strand, and keyed on the id alone it would re-authorize an
|
|
156
202
|
-- unsigned re-seat under a fresh stamp. The consent branch is narrow, each clause closing
|
|
157
203
|
-- one door:
|
|
@@ -164,12 +210,14 @@ declare schema CadreControl {
|
|
|
164
210
|
-- A standalone tombstone (no accompanying delete, Revocation.Authorized) lets an owner
|
|
165
211
|
-- foreclose consent-seating of an id that never existed: owner-only, and ids are random.
|
|
166
212
|
constraint AuthorizedInsert check on insert (
|
|
167
|
-
(exists (select 1 from OwnerKey A where A.Key = context.OwnerKey and not exists (select 1 from Revocation R where R.TableName = 'OwnerKey' and R.StampId = A.StampId) and verify(digest('CadreControl.Strand', 'add', party_id(), new.Id, new.Type, coalesce(new.MemberPrivateKey, ''), new.StampId), context.Signature, A.Key, 'ed25519'))
|
|
168
|
-
and new.FounderOwnerKey = context.OwnerKey
|
|
213
|
+
(exists (select 1 from OwnerKey A where A.Key = context.OwnerKey and not exists (select 1 from Revocation R where R.TableName = 'OwnerKey' and R.StampId = A.StampId) and verify(digest('CadreControl.Strand', 'add', party_id(), new.Id, new.Type, coalesce(new.MemberPrivateKey, ''), new.FounderPeerId, new.StampId), context.Signature, A.Key, 'ed25519'))
|
|
214
|
+
and new.FounderOwnerKey = context.OwnerKey
|
|
215
|
+
and new.FounderPeerId is not null)
|
|
169
216
|
or (
|
|
170
217
|
new.Type = 'o'
|
|
171
218
|
and new.MemberPrivateKey is null
|
|
172
219
|
and new.FounderOwnerKey is null
|
|
220
|
+
and new.FounderPeerId is null
|
|
173
221
|
and exists (
|
|
174
222
|
select 1 from FormationUsage FU
|
|
175
223
|
where FU.StrandId = new.Id
|
|
@@ -623,11 +671,13 @@ declare schema CadreControl {
|
|
|
623
671
|
-- convergence is guarded by the integration scenario strand-formation-concurrent-redemption.
|
|
624
672
|
index FormationUsageByToken on FormationUsage (Token);
|
|
625
673
|
|
|
626
|
-
-- An owner-signed invitation to join this cadre, redeemable on the owner's
|
|
627
|
-
-- member machine (ControlDatabase.redeemCadreInvite).
|
|
674
|
+
-- An owner-signed invitation to join this cadre as a machine, redeemable on the owner's
|
|
675
|
+
-- behalf by any member machine (ControlDatabase.redeemCadreInvite). It admits a machine
|
|
676
|
+
-- and grants nothing else; ownership is an OwnerGrant. The invitation is a keypair, not a
|
|
628
677
|
-- bearer token: the row holds the public half and the holder proves possession by signing
|
|
629
|
-
-- with the private half (CadreInviteUsage.InvitePossessed), so
|
|
630
|
-
-- can
|
|
678
|
+
-- with the private half (CadreInviteUsage.InvitePossessed), so a member that only relays
|
|
679
|
+
-- it can neither admit a machine of its own choosing with it nor spend its uses. Rows are
|
|
680
|
+
-- never deleted: a device admitted
|
|
631
681
|
-- through one carries no owner signature of its own, and its membership is the chain
|
|
632
682
|
-- invitation -> usage -> peer row, verified at read time, so the row must outlive every
|
|
633
683
|
-- device it admitted. Withdrawing an invitation is an owner-signed Revocation tombstone
|
|
@@ -640,7 +690,6 @@ declare schema CadreControl {
|
|
|
640
690
|
table CadreInvite (
|
|
641
691
|
Key text primary key, -- the invitation's ed25519 public key (base64url)
|
|
642
692
|
PeerId text null, -- the device this invitation is for; null: whoever redeems first designates it
|
|
643
|
-
GrantsOwner integer check (GrantsOwner = 0 or GrantsOwner = 1), -- 1: redemption also seats an OwnerKey row
|
|
644
693
|
ExpiresAt datetime null,
|
|
645
694
|
TotalUses int null check (TotalUses >= 0),
|
|
646
695
|
IssuerKey text, -- the owner that signed the row (== insert context.OwnerKey). A reader checks it
|
|
@@ -661,7 +710,6 @@ declare schema CadreControl {
|
|
|
661
710
|
'CadreControl.CadreInvite', 'add', party_id(),
|
|
662
711
|
new.Key,
|
|
663
712
|
coalesce(new.PeerId, ''),
|
|
664
|
-
cast(new.GrantsOwner as text),
|
|
665
713
|
coalesce(cast(new.ExpiresAt as text), ''),
|
|
666
714
|
coalesce(cast(new.TotalUses as text), ''),
|
|
667
715
|
new.StampId
|
|
@@ -676,8 +724,7 @@ declare schema CadreControl {
|
|
|
676
724
|
) with context (OwnerKey text, Signature text);
|
|
677
725
|
|
|
678
726
|
-- Append-only record of cadre invitation redemptions, one row per redemption: the proof a
|
|
679
|
-
-- CadrePeer row
|
|
680
|
-
-- consent rather than by an owner signature.
|
|
727
|
+
-- CadrePeer row was admitted by consent rather than by an owner signature.
|
|
681
728
|
table CadreInviteUsage (
|
|
682
729
|
UsageStampId text primary key, -- nonce minted by the redeeming device and signed into both signatures
|
|
683
730
|
-- below. The primary key: this table never deletes, so the key alone
|
|
@@ -691,22 +738,20 @@ declare schema CadreControl {
|
|
|
691
738
|
PeerStampId text, -- the CadrePeer.StampId this usage seats: one row incarnation, not a peer id
|
|
692
739
|
-- forever. Minted by the writer inside the redeeming transaction and not
|
|
693
740
|
-- signed (FormationUsage.StrandStampId).
|
|
694
|
-
OwnerStampId text null, -- the OwnerKey.StampId this usage seats when the invitation grants ownership
|
|
695
741
|
InviteSig text, -- by the invitation's private key over the 'redeem' digest: proof of possession
|
|
696
742
|
PeerSig text, -- by PeerKey over the 'consent' digest: the device agreed to join
|
|
697
743
|
constraint InsertOnly check on update, delete (false),
|
|
698
744
|
-- The invitation is read from the pre-transaction snapshot, so an invitation cannot be
|
|
699
|
-
-- seated and redeemed in one transaction (the
|
|
745
|
+
-- seated and redeemed in one transaction (the CadrePeer consent branch rests on that).
|
|
700
746
|
-- NOTE: accepted tradeoff — the use cap counts committed usage, so two redeemers racing
|
|
701
|
-
-- for a single-use invitation on different machines may both be admitted until
|
|
702
|
-
-- and after merge both rows stand (FormationUsage.Authorized)
|
|
703
|
-
--
|
|
704
|
-
-- the
|
|
705
|
-
--
|
|
706
|
-
--
|
|
707
|
-
--
|
|
708
|
-
--
|
|
709
|
-
-- satisfies); devices it already admitted stay members.
|
|
747
|
+
-- for a single-use machine invitation on different machines may both be admitted until
|
|
748
|
+
-- merge, and after merge both rows stand (FormationUsage.Authorized). Serializing would
|
|
749
|
+
-- need cross-machine coordination the control plane does not have; the mitigations are
|
|
750
|
+
-- the single-use default, the targeted form, and the owner seeing the extra machine and
|
|
751
|
+
-- removing it. Revisit if the control plane gains a cross-machine sequencer. The owner
|
|
752
|
+
-- grant's half of this tradeoff is at OwnerGrantUsage.Authorized. An issuer removed
|
|
753
|
+
-- since issuing takes its outstanding invitations with it (the committed.OwnerKey
|
|
754
|
+
-- clause, which a tombstone alone satisfies); devices it already admitted stay members.
|
|
710
755
|
constraint Authorized check on insert (
|
|
711
756
|
exists (
|
|
712
757
|
select 1 from committed.CadreInvite I
|
|
@@ -731,33 +776,143 @@ declare schema CadreControl {
|
|
|
731
776
|
),
|
|
732
777
|
-- Deferred, so the same-transaction peer insert counts: a usage row can never be held in
|
|
733
778
|
-- reserve to seat a row later (FormationUsage.StrandExists). PeerKey must be the key the
|
|
734
|
-
-- peer row stores
|
|
735
|
-
-- OwnerKey row (no peer insert to pin it) cannot name a key other than that device's.
|
|
779
|
+
-- peer row stores, so the usage names the admitted machine's own key and no other.
|
|
736
780
|
constraint PeerExists check (exists (
|
|
737
781
|
select 1 from CadrePeer P where P.PeerId = new.PeerId and P.StampId = new.PeerStampId and P.PublicKey = new.PeerKey
|
|
738
|
-
))
|
|
739
|
-
constraint OwnerExists check (
|
|
740
|
-
new.OwnerStampId is null
|
|
741
|
-
or exists (select 1 from OwnerKey K where K.Key = new.PeerKey and K.StampId = new.OwnerStampId)
|
|
742
|
-
)
|
|
782
|
+
))
|
|
743
783
|
) with context (Now datetime);
|
|
744
784
|
|
|
745
785
|
-- Serves the cap count above and the liveness reads (cadre-core countCadreInviteUsage,
|
|
746
786
|
-- hasLiveCadreInvite) on an append-only table, as FormationUsageByToken does.
|
|
747
787
|
index CadreInviteUsageByInvite on CadreInviteUsage (InviteKey);
|
|
748
788
|
|
|
789
|
+
-- An owner-signed grant of ownership to whoever holds the grant's private half: it seats an
|
|
790
|
+
-- OwnerKey row for a key the holder generates or already holds, and admits no machine (a
|
|
791
|
+
-- machine is admitted by a CadreInvite; peer identity and ownership are unrelated, so the
|
|
792
|
+
-- row names no peer id). The grant is a keypair as the invitation is: the row holds the
|
|
793
|
+
-- public half, the holder proves possession by signing with the private half
|
|
794
|
+
-- (OwnerGrantUsage.GrantPossessed), and the member machine that writes the redemption can
|
|
795
|
+
-- verify that signature but never produce it, so it cannot seat an owner for itself. Rows
|
|
796
|
+
-- are never deleted: a key seated through one carries no owner signature of its own, and
|
|
797
|
+
-- its proof is the chain grant -> usage -> OwnerKey row, verified at read time, so the row
|
|
798
|
+
-- must outlive every key it seated. Withdrawal is an owner-signed Revocation tombstone over
|
|
799
|
+
-- the row's stamp with the row kept (Revocation.RowIsGone); liveness is read as no
|
|
800
|
+
-- tombstone, unexpired, uses left, issuer still an owner (OwnerGrantUsage.Authorized).
|
|
801
|
+
-- NOTE: expired and withdrawn rows accumulate for the life of the party, as CadreInvite's.
|
|
802
|
+
-- The label is the issuer's: it is bound into the grant's signature and the seated OwnerKey
|
|
803
|
+
-- row must carry it (the grant branch of OwnerKey.Authorized), so a key seated on a fresh
|
|
804
|
+
-- device during recovery needs no naming prompt, and nothing the holder writes names it.
|
|
805
|
+
table OwnerGrant (
|
|
806
|
+
Key text primary key, -- the grant's ed25519 public key (base64url)
|
|
807
|
+
Label text null, -- the label the seated OwnerKey row carries, chosen by the issuer
|
|
808
|
+
ExpiresAt datetime null, -- null: never. The writer defaults to a short expiry; a lifetime with
|
|
809
|
+
-- no end is the issuer's explicit choice (a backup grant)
|
|
810
|
+
TotalUses int null check (TotalUses >= 0),
|
|
811
|
+
IssuerKey text, -- the owner that signed the row (== insert context.OwnerKey); a reader checks
|
|
812
|
+
-- it against its node-local anchor, never against the replicated OwnerKey table
|
|
813
|
+
IssuerSig text, -- that owner's 'add' signature (== insert context.Signature), stored so any
|
|
814
|
+
-- reader can re-verify the row from the holder's copy or the replicated one
|
|
815
|
+
StampId text unique,
|
|
816
|
+
-- A withdrawn grant cannot be re-seated from the signed copy its holder keeps, on any
|
|
817
|
+
-- node that holds the tombstone (schema header for the node that does not).
|
|
818
|
+
constraint NotRevoked check on insert (
|
|
819
|
+
not exists (select 1 from Revocation R where R.TableName = 'OwnerGrant' and R.StampId = new.StampId)
|
|
820
|
+
),
|
|
821
|
+
-- Never empty, as OwnerKey.LabelLength: the add digest binds coalesce(Label, '').
|
|
822
|
+
constraint LabelLength check (
|
|
823
|
+
Label is null or length(Label) between 1 and 64
|
|
824
|
+
),
|
|
825
|
+
-- The owner signs the whole row, nullable columns as '' (cadre-core ownerGrantRowFields),
|
|
826
|
+
-- and the stored issuer pair must be the pair verified here, as CadreInvite pins its issuer.
|
|
827
|
+
constraint AuthorizedInsert check on insert (
|
|
828
|
+
exists (select 1 from OwnerKey A where A.Key = context.OwnerKey and not exists (select 1 from Revocation R where R.TableName = 'OwnerKey' and R.StampId = A.StampId) and verify(
|
|
829
|
+
digest(
|
|
830
|
+
'CadreControl.OwnerGrant', 'add', party_id(),
|
|
831
|
+
new.Key,
|
|
832
|
+
coalesce(new.Label, ''),
|
|
833
|
+
coalesce(cast(new.ExpiresAt as text), ''),
|
|
834
|
+
coalesce(cast(new.TotalUses as text), ''),
|
|
835
|
+
new.StampId
|
|
836
|
+
),
|
|
837
|
+
context.Signature, A.Key, 'ed25519'))
|
|
838
|
+
and coalesce(new.IssuerKey, '') = context.OwnerKey
|
|
839
|
+
and coalesce(new.IssuerSig, '') = context.Signature
|
|
840
|
+
),
|
|
841
|
+
constraint NoUpdate check on update (false),
|
|
842
|
+
-- Never deleted (table comment). Withdrawal is the tombstone alone.
|
|
843
|
+
constraint NoDelete check on delete (false)
|
|
844
|
+
) with context (OwnerKey text, Signature text);
|
|
845
|
+
|
|
846
|
+
-- Append-only record of owner-grant redemptions, one row per redemption: the proof an
|
|
847
|
+
-- OwnerKey row was seated by consent rather than by an owner signature.
|
|
848
|
+
table OwnerGrantUsage (
|
|
849
|
+
UsageStampId text primary key, -- nonce minted by the holder and signed into both signatures below. The
|
|
850
|
+
-- primary key: this table never deletes, so the key alone makes a nonce
|
|
851
|
+
-- permanent and there is no NotRevoked pair here (CadreInviteUsage).
|
|
852
|
+
GrantKey text, -- the OwnerGrant redeemed
|
|
853
|
+
SeatedKey text, -- the owner key seated (base64url ed25519): the holder's, fresh or already held
|
|
854
|
+
SeatedStampId text, -- the OwnerKey.StampId this usage seats: one row incarnation, not a key
|
|
855
|
+
-- forever. Minted by the writer inside the redeeming transaction and not
|
|
856
|
+
-- signed (CadreInviteUsage.PeerStampId).
|
|
857
|
+
GrantSig text, -- by the grant's private key over the 'redeem' digest: proof of possession
|
|
858
|
+
OwnerSig text, -- by SeatedKey over the 'consent' digest: the holder holds the key it seats
|
|
859
|
+
constraint InsertOnly check on update, delete (false),
|
|
860
|
+
-- The grant is read from the pre-transaction snapshot, so a grant cannot be seated and
|
|
861
|
+
-- redeemed in one transaction (the grant branch of OwnerKey.Authorized rests on that).
|
|
862
|
+
-- NOTE: accepted tradeoff — the use cap counts committed usage, so two holders of one
|
|
863
|
+
-- single-use grant redeeming on different machines may both be seated until merge, and
|
|
864
|
+
-- after merge both rows stand (CadreInviteUsage.Authorized). Serializing would need
|
|
865
|
+
-- cross-machine coordination the control plane does not have; the mitigations are the
|
|
866
|
+
-- defaults the writer sets (single use and a 15-minute expiry, a longer lifetime only
|
|
867
|
+
-- when the user chooses one) and the owner seeing the extra key in the owner-key list
|
|
868
|
+
-- and removing it. Revisit if the control plane gains a cross-machine sequencer. An
|
|
869
|
+
-- issuer removed since issuing takes its outstanding grants with it (the
|
|
870
|
+
-- committed.OwnerKey clause, which a tombstone alone satisfies); keys it already
|
|
871
|
+
-- seated stay owners.
|
|
872
|
+
constraint Authorized check on insert (
|
|
873
|
+
exists (
|
|
874
|
+
select 1 from committed.OwnerGrant G
|
|
875
|
+
where G.Key = new.GrantKey
|
|
876
|
+
and not exists (select 1 from Revocation R where R.TableName = 'OwnerGrant' and R.StampId = G.StampId)
|
|
877
|
+
and (G.ExpiresAt is null or G.ExpiresAt > context.Now)
|
|
878
|
+
and (G.TotalUses is null or G.TotalUses > (select count(1) from committed.OwnerGrantUsage U where U.GrantKey = new.GrantKey))
|
|
879
|
+
and exists (select 1 from committed.OwnerKey K where K.Key = G.IssuerKey and not exists (select 1 from Revocation R where R.TableName = 'OwnerKey' and R.StampId = K.StampId))
|
|
880
|
+
)
|
|
881
|
+
),
|
|
882
|
+
-- Verified against the grant key itself: the member writing the row can check this
|
|
883
|
+
-- signature but, holding only the public half, cannot produce it.
|
|
884
|
+
constraint GrantPossessed check on insert (
|
|
885
|
+
verify(digest('CadreControl.OwnerGrantUsage', 'redeem', party_id(), new.GrantKey, new.UsageStampId, new.SeatedKey),
|
|
886
|
+
new.GrantSig, new.GrantKey, 'ed25519')
|
|
887
|
+
),
|
|
888
|
+
-- The holder proves it holds the key being seated (CadreInviteUsage.PeerConsented).
|
|
889
|
+
constraint OwnerConsented check on insert (
|
|
890
|
+
verify(digest('CadreControl.OwnerGrantUsage', 'consent', party_id(), new.GrantKey, new.UsageStampId, new.SeatedKey),
|
|
891
|
+
new.OwnerSig, new.SeatedKey, 'ed25519')
|
|
892
|
+
),
|
|
893
|
+
-- Deferred, so the same-transaction OwnerKey insert counts: a usage row can never be
|
|
894
|
+
-- held in reserve to seat a row later (CadreInviteUsage.PeerExists).
|
|
895
|
+
constraint SeatedExists check (
|
|
896
|
+
exists (select 1 from OwnerKey K where K.Key = new.SeatedKey and K.StampId = new.SeatedStampId)
|
|
897
|
+
)
|
|
898
|
+
) with context (Now datetime);
|
|
899
|
+
|
|
900
|
+
-- Serves the cap count above and the liveness reads (cadre-core countOwnerGrantUsage,
|
|
901
|
+
-- listOwnerGrantStatuses) on an append-only table, as CadreInviteUsageByInvite does.
|
|
902
|
+
index OwnerGrantUsageByGrant on OwnerGrantUsage (GrantKey);
|
|
903
|
+
|
|
749
904
|
-- Append-only retirement record for the stamps of removed rows of every guarded table, with
|
|
750
905
|
-- the removed row's key. Every append is owner-signed, so the growth surface is the owner
|
|
751
906
|
-- keys. One singleton marker row, ('Revocation', 'ledger', 'opened'), retires nothing: an
|
|
752
907
|
-- owner's connected reconcile pass files it (ControlDatabase.openRevocationLedger) so this
|
|
753
908
|
-- table is never a never-written block, which the storage layer would consult the cohort
|
|
754
909
|
-- for on every guarded insert and membership lookup. Readers filter on their own guarded
|
|
755
|
-
-- TableName, and queryRevocations skips it. A 'CadreInvite' row withdraws
|
|
756
|
-
-- whose row stays (
|
|
910
|
+
-- TableName, and queryRevocations skips it. A 'CadreInvite' or 'OwnerGrant' row withdraws
|
|
911
|
+
-- an invitation or a grant whose row stays (the table comments).
|
|
757
912
|
table Revocation (
|
|
758
913
|
TableName text, -- 'OwnerKey' | 'ValidationKey' | 'Strand' | 'StrandPartyKey' | 'JoinedStrand' |
|
|
759
|
-
-- 'JoinRequest' | 'CadrePeer' | 'DeviceToken' | 'CadreInvite'
|
|
760
|
-
-- for the marker
|
|
914
|
+
-- 'JoinRequest' | 'CadrePeer' | 'DeviceToken' | 'CadreInvite' | 'OwnerGrant', or
|
|
915
|
+
-- 'Revocation' for the marker
|
|
761
916
|
RowKey text, -- primary key of the removed row
|
|
762
917
|
StampId text, -- the retired stamp
|
|
763
918
|
ReissuedAt integer default 0, -- bumped by an owner-signed re-issue so a tombstone committed while the
|
|
@@ -785,9 +940,9 @@ declare schema CadreControl {
|
|
|
785
940
|
-- counts). Keyed on the stamp, not RowKey: a node that converges on a re-admission before
|
|
786
941
|
-- the earlier removal's tombstone must still accept that tombstone. Retiring a stamp that
|
|
787
942
|
-- never existed is harmless: stamps carry 128 random bits (generateStampId). For
|
|
788
|
-
-- 'CadreInvite' there is no row-gone test: a tombstone there is a
|
|
789
|
-
-- row stays (
|
|
790
|
-
-- nothing else under 'Revocation'.
|
|
943
|
+
-- 'CadreInvite' and 'OwnerGrant' there is no row-gone test: a tombstone there is a
|
|
944
|
+
-- withdrawal, and the row stays (the table comments). The last branch admits the marker
|
|
945
|
+
-- row and nothing else under 'Revocation'.
|
|
791
946
|
constraint RowIsGone check on insert (
|
|
792
947
|
(new.TableName = 'OwnerKey' and not exists (select 1 from OwnerKey K where K.StampId = new.StampId))
|
|
793
948
|
or (new.TableName = 'CadrePeer' and not exists (select 1 from CadrePeer P where P.StampId = new.StampId))
|
|
@@ -798,6 +953,7 @@ declare schema CadreControl {
|
|
|
798
953
|
or (new.TableName = 'JoinRequest' and not exists (select 1 from JoinRequest J where J.StampId = new.StampId))
|
|
799
954
|
or (new.TableName = 'DeviceToken' and not exists (select 1 from DeviceToken D where D.StampId = new.StampId))
|
|
800
955
|
or (new.TableName = 'CadreInvite')
|
|
956
|
+
or (new.TableName = 'OwnerGrant')
|
|
801
957
|
or (new.TableName = 'Revocation' and new.RowKey = 'ledger' and new.StampId = 'opened')
|
|
802
958
|
),
|
|
803
959
|
-- Owner-signed over the whole row under its own domain tag, so the delete's 'remove'
|
|
@@ -822,7 +978,46 @@ declare schema CadreControl {
|
|
|
822
978
|
context.Signature, A.Key, 'ed25519'))
|
|
823
979
|
)
|
|
824
980
|
) with context (OwnerKey text, Signature text);
|
|
825
|
-
}
|
|
981
|
+
}`;
|
|
982
|
+
|
|
983
|
+
/** The name the control schema is declared under; Quereus compares schema names without regard to case. */
|
|
984
|
+
export const CONTROL_SCHEMA_NAME = 'CadreControl';
|
|
985
|
+
|
|
986
|
+
/** The statement that applies {@link CONTROL_SCHEMA_DECLARATION}, run only after the store has been checked against it. */
|
|
987
|
+
export const APPLY_CONTROL_SCHEMA = `apply schema ${CONTROL_SCHEMA_NAME};`;
|
|
988
|
+
|
|
989
|
+
/** The whole control schema as `schemas/control.qsql` holds it: the declaration, then the apply. */
|
|
990
|
+
export const CONTROL_SCHEMA = `${CONTROL_SCHEMA_DECLARATION}
|
|
826
991
|
|
|
827
|
-
|
|
992
|
+
${APPLY_CONTROL_SCHEMA}
|
|
828
993
|
`;
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
* The declaration statement of a control schema file (`ControlDatabaseConfig.schemaPath`),
|
|
997
|
+
* cut from the file's own text.
|
|
998
|
+
*
|
|
999
|
+
* Refuses a file that is anything other than one `declare schema CadreControl { … }`
|
|
1000
|
+
* followed by one plain `apply schema CadreControl` — the shape of {@link CONTROL_SCHEMA}.
|
|
1001
|
+
* `loadSchema` runs its own {@link APPLY_CONTROL_SCHEMA} once the store has been checked,
|
|
1002
|
+
* so any other statement in the file, or an option on its apply, would be dropped or run
|
|
1003
|
+
* unchecked.
|
|
1004
|
+
*/
|
|
1005
|
+
export function controlSchemaFileDeclaration(source: string): string {
|
|
1006
|
+
const statements = new Parser().parseAll(source);
|
|
1007
|
+
const [declaration, apply] = statements;
|
|
1008
|
+
if (statements.length !== 2
|
|
1009
|
+
|| declaration.type !== 'declareSchema' || !isControlSchemaName(declaration.schemaName)
|
|
1010
|
+
|| apply.type !== 'applySchema' || !isControlSchemaName(apply.schemaName)
|
|
1011
|
+
|| apply.options !== undefined || apply.toVersion !== undefined || apply.withSeed === true
|
|
1012
|
+
|| declaration.loc === undefined) {
|
|
1013
|
+
throw new Error(
|
|
1014
|
+
`A control schema file must hold exactly \`declare schema ${CONTROL_SCHEMA_NAME} { … }\` ` +
|
|
1015
|
+
`followed by \`${APPLY_CONTROL_SCHEMA}\`, the shape of schemas/control.qsql.`
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
return source.slice(declaration.loc.start.offset, declaration.loc.end.offset);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
function isControlSchemaName(name: string | undefined): boolean {
|
|
1022
|
+
return name?.toLowerCase() === CONTROL_SCHEMA_NAME.toLowerCase();
|
|
1023
|
+
}
|