@serfab/cadre-core 0.12.0 → 1.0.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 +23 -8
- package/dist/bootstrap-peer-store-file.js +2 -2
- package/dist/bootstrap-peer-store.d.ts +32 -12
- package/dist/bootstrap-peer-store.js +41 -15
- package/dist/bootstrap-peer-store.js.map +1 -1
- package/dist/cadre-node.d.ts +883 -102
- package/dist/cadre-node.js +1800 -209
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-authorization.d.ts +2 -2
- package/dist/control-authorization.js +1 -0
- package/dist/control-authorization.js.map +1 -1
- package/dist/control-cohort.d.ts +13 -26
- package/dist/control-cohort.js +0 -26
- package/dist/control-cohort.js.map +1 -1
- package/dist/control-database.d.ts +318 -44
- package/dist/control-database.js +516 -74
- package/dist/control-database.js.map +1 -1
- package/dist/control-read-retry.d.ts +40 -11
- package/dist/control-read-retry.js +48 -15
- package/dist/control-read-retry.js.map +1 -1
- package/dist/control-retry.d.ts +58 -3
- package/dist/control-retry.js +58 -3
- package/dist/control-retry.js.map +1 -1
- package/dist/control-schema.d.ts +1 -1
- package/dist/control-schema.js +133 -21
- package/dist/control-schema.js.map +1 -1
- package/dist/control-write-retry.d.ts +80 -11
- package/dist/control-write-retry.js +168 -29
- package/dist/control-write-retry.js.map +1 -1
- package/dist/enrolled-machine-store-file.d.ts +13 -0
- package/dist/enrolled-machine-store-file.js +41 -0
- package/dist/enrolled-machine-store-file.js.map +1 -0
- package/dist/enrolled-machine-store.d.ts +81 -0
- package/dist/enrolled-machine-store.js +225 -0
- package/dist/enrolled-machine-store.js.map +1 -0
- package/dist/file-durable-slot.js +5 -3
- package/dist/file-durable-slot.js.map +1 -1
- package/dist/index.d.ts +17 -8
- package/dist/index.js +54 -9
- package/dist/index.js.map +1 -1
- package/dist/membership-connection-gater.d.ts +15 -1
- package/dist/membership-connection-gater.js +9 -3
- package/dist/membership-connection-gater.js.map +1 -1
- package/dist/node-local-snapshot.d.ts +21 -7
- package/dist/node-local-snapshot.js +30 -9
- package/dist/node-local-snapshot.js.map +1 -1
- package/dist/peer-dial.d.ts +143 -0
- package/dist/peer-dial.js +176 -0
- package/dist/peer-dial.js.map +1 -0
- package/dist/{strand-backfill.d.ts → peer-join-backfill.d.ts} +49 -20
- package/dist/{strand-backfill.js → peer-join-backfill.js} +133 -52
- package/dist/peer-join-backfill.js.map +1 -0
- package/dist/relay-addrs.d.ts +160 -49
- package/dist/relay-addrs.js +287 -60
- package/dist/relay-addrs.js.map +1 -1
- package/dist/relay-reservation.d.ts +71 -3
- package/dist/relay-reservation.js +116 -8
- package/dist/relay-reservation.js.map +1 -1
- package/dist/seed-bootstrap.d.ts +30 -1
- package/dist/seed-bootstrap.js +62 -17
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/storage-scope.d.ts +62 -0
- package/dist/storage-scope.js +75 -0
- package/dist/storage-scope.js.map +1 -0
- package/dist/strand-database.d.ts +41 -11
- package/dist/strand-database.js +48 -18
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-first-sync-gate.d.ts +152 -0
- package/dist/strand-first-sync-gate.js +208 -0
- package/dist/strand-first-sync-gate.js.map +1 -0
- package/dist/strand-formation-manager.d.ts +99 -5
- package/dist/strand-formation-manager.js +119 -8
- package/dist/strand-formation-manager.js.map +1 -1
- package/dist/strand-formation-protocol.d.ts +97 -3
- package/dist/strand-formation-protocol.js +108 -2
- package/dist/strand-formation-protocol.js.map +1 -1
- package/dist/strand-instance-manager.d.ts +389 -19
- package/dist/strand-instance-manager.js +678 -65
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-member-key.d.ts +20 -12
- package/dist/strand-member-key.js +20 -12
- package/dist/strand-member-key.js.map +1 -1
- package/dist/strand-member-registry.js +3 -0
- package/dist/strand-member-registry.js.map +1 -1
- package/dist/strand-membership-reconciler.d.ts +361 -0
- package/dist/strand-membership-reconciler.js +548 -0
- package/dist/strand-membership-reconciler.js.map +1 -0
- package/dist/strand-membership-writer.d.ts +143 -11
- package/dist/strand-membership-writer.js +419 -135
- package/dist/strand-membership-writer.js.map +1 -1
- package/dist/strand-network-config.d.ts +116 -0
- package/dist/strand-network-config.js +192 -0
- package/dist/strand-network-config.js.map +1 -0
- package/dist/strand-revocation-enforcer.d.ts +392 -0
- package/dist/strand-revocation-enforcer.js +533 -0
- package/dist/strand-revocation-enforcer.js.map +1 -0
- package/dist/strand-solicitation.d.ts +17 -1
- package/dist/strand-solicitation.js +13 -2
- package/dist/strand-solicitation.js.map +1 -1
- package/dist/strand-wake-protocol.d.ts +11 -7
- package/dist/strand-wake-protocol.js +20 -52
- package/dist/strand-wake-protocol.js.map +1 -1
- package/dist/strand-watcher.js +12 -1
- package/dist/strand-watcher.js.map +1 -1
- package/dist/timed-step.d.ts +12 -0
- package/dist/timed-step.js +28 -0
- package/dist/timed-step.js.map +1 -0
- package/dist/timeout-scheduler.d.ts +19 -0
- package/dist/timeout-scheduler.js +22 -0
- package/dist/timeout-scheduler.js.map +1 -0
- package/dist/types.d.ts +472 -46
- package/dist/types.js +7 -1
- package/dist/types.js.map +1 -1
- package/package.json +16 -8
- package/src/bootstrap-peer-store-file.ts +2 -2
- package/src/bootstrap-peer-store.ts +69 -27
- package/src/cadre-node.ts +1926 -228
- package/src/control-authorization.ts +2 -1
- package/src/control-cohort.ts +122 -130
- package/src/control-database.ts +586 -74
- package/src/control-read-retry.ts +202 -167
- package/src/control-retry.ts +297 -190
- package/src/control-schema.ts +133 -21
- package/src/control-write-retry.ts +525 -383
- package/src/enrolled-machine-store-file.ts +43 -0
- package/src/enrolled-machine-store.ts +280 -0
- package/src/file-durable-slot.ts +5 -3
- package/src/index.ts +134 -15
- package/src/membership-connection-gater.ts +9 -3
- package/src/node-local-snapshot.ts +38 -9
- package/src/peer-addr-book.ts +251 -250
- package/src/peer-dial.ts +215 -0
- package/src/{strand-backfill.ts → peer-join-backfill.ts} +478 -389
- package/src/relay-addrs.ts +458 -218
- package/src/relay-reservation.ts +951 -828
- package/src/seed-bootstrap.ts +76 -18
- package/src/storage-scope.ts +78 -0
- package/src/strand-database.ts +61 -21
- package/src/strand-first-sync-gate.ts +258 -0
- package/src/strand-formation-manager.ts +162 -8
- package/src/strand-formation-protocol.ts +850 -692
- package/src/strand-instance-manager.ts +877 -74
- package/src/strand-member-key.ts +20 -12
- package/src/strand-member-registry.ts +3 -0
- package/src/strand-membership-reconciler.ts +680 -0
- package/src/strand-membership-writer.ts +550 -190
- package/src/strand-network-config.ts +250 -0
- package/src/strand-revocation-enforcer.ts +653 -0
- package/src/strand-solicitation.ts +499 -471
- package/src/strand-wake-protocol.ts +21 -66
- package/src/strand-watcher.ts +325 -314
- package/src/timed-step.ts +28 -0
- package/src/timeout-scheduler.ts +28 -0
- package/src/types.ts +487 -52
- package/dist/strand-backfill.js.map +0 -1
package/src/control-schema.ts
CHANGED
|
@@ -142,6 +142,17 @@ declare schema CadreControl {
|
|
|
142
142
|
Id text primary key, -- UUID
|
|
143
143
|
MemberPrivateKey text null unique, -- Our private key as a member of this strand
|
|
144
144
|
Type text, -- Types: 'o' = Open, 'c' = Closed -- Open can still control writes in the sApp, but only Closed controls reads
|
|
145
|
+
FounderOwnerKey text null, -- ed25519 (base64url) owner key of the MACHINE that published this row
|
|
146
|
+
-- (== insert context.OwnerKey; in the reference model each machine's
|
|
147
|
+
-- owner key is the key behind its PeerId, so this names the founding
|
|
148
|
+
-- machine — the one machine that runs the strand's one-time founder
|
|
149
|
+
-- bootstrap). cadre-core derives "am I the founder?" at launch by
|
|
150
|
+
-- comparing it to the node's own owner key (cadre-node.ts
|
|
151
|
+
-- launchStrand). Null on a consent-seated strand — the consent branch
|
|
152
|
+
-- of AuthorizedInsert carries no signature, so there is no
|
|
153
|
+
-- trustworthy signer to record (see the NOTE on that branch below).
|
|
154
|
+
-- Provenance, not content: cadre-node.ts strandRowMismatches
|
|
155
|
+
-- deliberately excludes it from the identical-content comparison.
|
|
145
156
|
StampId text not null unique, -- single-use authorization nonce (anti-replay).
|
|
146
157
|
-- \`unique\` holds over LIVE rows only; a removed row's stamp is retired
|
|
147
158
|
-- permanently into Revocation (NotRevoked below).
|
|
@@ -172,8 +183,16 @@ declare schema CadreControl {
|
|
|
172
183
|
-- Mirrors OwnerKey.NoUpdate / FormationInvite.Immutable.
|
|
173
184
|
constraint NoUpdate check on update (false),
|
|
174
185
|
constraint AuthorizedInsert check on insert (
|
|
175
|
-
-- Authorized by an owner signing over THIS row (Id, Type, MemberPrivateKey, StampId); single-use via unique StampId
|
|
176
|
-
|
|
186
|
+
-- Authorized by an owner signing over THIS row (Id, Type, MemberPrivateKey, StampId); single-use via unique StampId.
|
|
187
|
+
-- FounderOwnerKey persists WHO published the row, so the founding machine can later
|
|
188
|
+
-- recognise its own strand (founder derivation, cadre-node.ts launchStrand). The
|
|
189
|
+
-- equality pins the stored column to the VERIFIED signer — same shape as
|
|
190
|
+
-- CadrePeer.VouchOwner — so a writer cannot record a founder it is not. Deliberately
|
|
191
|
+
-- NOT added to the signed digest: the equality already binds it to the signature's
|
|
192
|
+
-- context.OwnerKey, and widening the digest would churn every existing
|
|
193
|
+
-- insertStrand field-order contract for nothing.
|
|
194
|
+
(exists (select 1 from OwnerKey A where A.Key = context.OwnerKey and verify(digest('CadreControl.Strand', 'add', new.Id, new.Type, coalesce(new.MemberPrivateKey, ''), new.StampId), context.Signature, A.Key, 'ed25519'))
|
|
195
|
+
and new.FounderOwnerKey = context.OwnerKey)
|
|
177
196
|
|
|
178
197
|
-- or authorized WITHOUT a signature by a redemption record naming THIS EXACT ROW: a
|
|
179
198
|
-- FormationUsage row carrying both this strand's Id and its one-off StampId
|
|
@@ -240,6 +259,15 @@ declare schema CadreControl {
|
|
|
240
259
|
or (
|
|
241
260
|
new.Type = 'o'
|
|
242
261
|
and new.MemberPrivateKey is null
|
|
262
|
+
-- NOTE: a consent-seated strand records NO founder machine — this branch
|
|
263
|
+
-- carries no signature, so there is no trustworthy signer to persist.
|
|
264
|
+
-- Consequence: the restart-before-founding orphan shape (a relaunch cannot
|
|
265
|
+
-- derive founder-ness from the row) persists for consent-formed strands
|
|
266
|
+
-- only; the responder that provisions one must keep passing an explicit
|
|
267
|
+
-- founder flag at launch. Revisit if consent-formed strands ever hit the
|
|
268
|
+
-- empty-Header symptom in practice: FormationUsage names the redeeming
|
|
269
|
+
-- peer (PeerKey), which is the candidate derivation source.
|
|
270
|
+
and new.FounderOwnerKey is null
|
|
243
271
|
and exists (
|
|
244
272
|
select 1 from FormationUsage FU
|
|
245
273
|
where FU.StrandId = new.Id
|
|
@@ -277,6 +305,66 @@ declare schema CadreControl {
|
|
|
277
305
|
)
|
|
278
306
|
) with context (OwnerKey text, Signature text);
|
|
279
307
|
|
|
308
|
+
-- ONE party's own membership identity for one strand: the private key whose public
|
|
309
|
+
-- half seats this party's \`Strand.Member\` / \`Strand.Manager\` rows. Deliberately a
|
|
310
|
+
-- SEPARATE key from Strand.MemberPrivateKey — that column is the strand-wide read
|
|
311
|
+
-- secret formation hands to EVERY joining party, so an identity derived from it is
|
|
312
|
+
-- one every joiner can forge (gotchoices/sereus#4). A side table rather than a
|
|
313
|
+
-- Strand column because a JOINER holds no control Strand row at all (reference apps
|
|
314
|
+
-- attach formed strands from an in-memory row) yet must persist its own party key
|
|
315
|
+
-- too; keyed by strand id it serves founder and joiner uniformly, and it leaves the
|
|
316
|
+
-- heavily-audited Strand insert digest untouched. Deliberately NO closed-strands-only
|
|
317
|
+
-- cross-table check: the joiner writes this row with no local Strand row to check
|
|
318
|
+
-- against. Party-private in the same sense as MemberPrivateKey — replicated in
|
|
319
|
+
-- plaintext to every machine the party owns (docs/strands.md → "Closed-Strand Member
|
|
320
|
+
-- Key Handling"; that replication is what lets any of the party's machines sign
|
|
321
|
+
-- membership writes) — but unlike MemberPrivateKey it is NEVER put on the formation
|
|
322
|
+
-- wire.
|
|
323
|
+
table StrandPartyKey (
|
|
324
|
+
Id text primary key, -- the strand id this key is for
|
|
325
|
+
PrivateKey text not null, -- THIS party's ed25519 strand member private key, base64
|
|
326
|
+
-- protobuf — the same encoding as Strand.MemberPrivateKey;
|
|
327
|
+
-- decode with cadre-core's strandMemberKeyPair.
|
|
328
|
+
StampId text not null unique, -- single-use authorization nonce (anti-replay).
|
|
329
|
+
-- \`unique\` holds over LIVE rows only; a removed row's stamp is
|
|
330
|
+
-- retired permanently into Revocation (NotRevoked below).
|
|
331
|
+
-- A removed row's StampId is retired into Revocation, and this refuses any insert
|
|
332
|
+
-- naming a retired stamp: the approval that seated this row can never re-seat it
|
|
333
|
+
-- after removal. Same rationale as OwnerKey.NotRevoked, stated in full there.
|
|
334
|
+
constraint NotRevoked check on insert (
|
|
335
|
+
not exists (select 1 from Revocation R where R.TableName = 'StrandPartyKey' and R.StampId = new.StampId)
|
|
336
|
+
),
|
|
337
|
+
-- Retirement is MANDATORY: a delete must carry the matching Revocation row in the same
|
|
338
|
+
-- transaction, or the stamp would free up again. Deferred (subquery), so the sibling
|
|
339
|
+
-- insert is visible at commit regardless of statement order. The tombstone must also
|
|
340
|
+
-- name the removed ROW, not just its stamp — see Revocation.RowKey.
|
|
341
|
+
constraint RevocationRecorded check on delete (
|
|
342
|
+
exists (select 1 from Revocation R where R.TableName = 'StrandPartyKey' and R.StampId = old.StampId and R.RowKey = old.Id)
|
|
343
|
+
),
|
|
344
|
+
-- Insert + delete only; a rotation is remove-then-insert (fresh stamp, fresh
|
|
345
|
+
-- signature). Required because AuthorizedInsert / AuthorizedDelete below cover
|
|
346
|
+
-- neither update — an in-place rewrite would swap the party's identity key with
|
|
347
|
+
-- no signature at all. Mirrors ValidationKey.NoUpdate.
|
|
348
|
+
constraint NoUpdate check on update (false),
|
|
349
|
+
constraint AuthorizedInsert check on insert (
|
|
350
|
+
-- Owners authorize by signing over THIS row (Id, PrivateKey, StampId);
|
|
351
|
+
-- single-use via unique StampId. Binding PrivateKey means a captured approval
|
|
352
|
+
-- can only ever reproduce the exact key it approved, never seat an
|
|
353
|
+
-- attacker-chosen identity under the party's name.
|
|
354
|
+
exists (select 1 from OwnerKey A where A.Key = context.OwnerKey and verify(digest('CadreControl.StrandPartyKey', 'add', new.Id, new.PrivateKey, new.StampId), context.Signature, A.Key, 'ed25519'))
|
|
355
|
+
),
|
|
356
|
+
constraint AuthorizedDelete check on delete (
|
|
357
|
+
-- Delete is authorized by a signature over the DISTINCT 'remove'-tagged digest
|
|
358
|
+
-- bound to the STORED row (Id, StampId), so the enrollment approval — which
|
|
359
|
+
-- never expires — can never be replayed as a removal.
|
|
360
|
+
-- DELIBERATELY no REAP branch (cf. ValidationKey.AuthorizedDelete): like
|
|
361
|
+
-- Strand.MemberPrivateKey, PrivateKey is a secret stored nowhere else, so this
|
|
362
|
+
-- delete's effect is unrecoverable and stays strictly owner-signed. Same
|
|
363
|
+
-- reasoning as Strand.AuthorizedDelete.
|
|
364
|
+
exists (select 1 from OwnerKey A where A.Key = context.OwnerKey and verify(digest('CadreControl.StrandPartyKey', 'remove', old.Id, old.StampId), context.Signature, A.Key, 'ed25519'))
|
|
365
|
+
)
|
|
366
|
+
) with context (OwnerKey text, Signature text);
|
|
367
|
+
|
|
280
368
|
-- A peer (node) that is part of the cadre, carrying a self-published,
|
|
281
369
|
-- freshness-stamped, self-signed address record (see PeerAddressRecord in cadre-core).
|
|
282
370
|
-- The row IS the peer-address record: a resolver reads it, re-verifies Sig against
|
|
@@ -676,25 +764,26 @@ declare schema CadreControl {
|
|
|
676
764
|
|
|
677
765
|
-- Token is no longer a primary-key prefix (the key is the redemption nonce), so the
|
|
678
766
|
-- per-token filters — the cap count in Authorized above, and cadre-core's
|
|
679
|
-
-- countFormationUsage / isTokenUsed / hasOutstandingFormationInvite — are served by
|
|
680
|
-
--
|
|
681
|
-
--
|
|
767
|
+
-- countFormationUsage / isTokenUsed / hasOutstandingFormationInvite — are served by this
|
|
768
|
+
-- index rather than by a full scan of a table that is append-only and grows for the life
|
|
769
|
+
-- of the party.
|
|
682
770
|
--
|
|
683
|
-
--
|
|
684
|
-
--
|
|
685
|
-
--
|
|
686
|
-
--
|
|
687
|
-
--
|
|
688
|
-
--
|
|
689
|
-
--
|
|
690
|
-
-- sits at a different revision while their copies of the table itself agree, and the
|
|
691
|
-
-- refresh that runs immediately before the descent does not close the gap. Tracked in
|
|
692
|
-
-- \`tickets/blocked/secondary-index-seek-blind-to-sibling-rows.md\`, and upstream as
|
|
693
|
-
-- \`bug-index-subcollection-sits-one-revision-behind-on-the-sibling\`.
|
|
771
|
+
-- The cap count is only ever as correct as this index's convergence across machines. That
|
|
772
|
+
-- convergence failed once: from 2026-08-04 to 2026-08-25 a descent on a second machine
|
|
773
|
+
-- returned only the rows THAT machine wrote, so the index was removed and the reads went
|
|
774
|
+
-- back to the scan. The engine defect was fixed upstream and re-measured here on
|
|
775
|
+
-- 2026-09-17, so the index is declared again. Its guard is the integration-tests scenario
|
|
776
|
+
-- \`strand-formation-concurrent-redemption\`, which asserts BOTH machines' views of a raced
|
|
777
|
+
-- redemption and therefore goes red if that convergence regresses.
|
|
694
778
|
--
|
|
695
|
-
--
|
|
696
|
-
--
|
|
697
|
-
--
|
|
779
|
+
-- NOTE: the write side has its own exposure. Every FormationUsage insert now maintains this
|
|
780
|
+
-- sub-collection as well as the table, and an intermittent engine failure once hit exactly that
|
|
781
|
+
-- step on other tables (a table's rows committed while its index did not; closed 2026-09-17 with
|
|
782
|
+
-- the upstream sync fixes, \`tickets/complete/strand-unique-index-sync-stale-revision\`). Nothing
|
|
783
|
+
-- has been observed here, and redemptions are rare writes; but if joins start failing
|
|
784
|
+
-- intermittently with an index-sync error naming FormationUsage, this declaration is the first
|
|
785
|
+
-- thing to suspect.
|
|
786
|
+
index FormationUsageByToken on FormationUsage (Token);
|
|
698
787
|
|
|
699
788
|
-- Append-only retirement record for the one-off StampId nonces of removed OwnerKey /
|
|
700
789
|
-- CadrePeer / ValidationKey / Strand rows. Without it a removal was undoable: the add
|
|
@@ -716,10 +805,20 @@ declare schema CadreControl {
|
|
|
716
805
|
-- A tombstone committed while the node was alone is local-only (never broadcast); the
|
|
717
806
|
-- ReissuedAt counter below exists so an owner can re-write — and therefore re-broadcast —
|
|
718
807
|
-- such a tombstone once peers are reachable, without changing what it means.
|
|
808
|
+
-- One row retires nothing: the singleton ledger marker ('Revocation', 'ledger', 'opened'),
|
|
809
|
+
-- filed once by an owner's connected reconcile pass (ControlDatabase.openRevocationLedger).
|
|
810
|
+
-- It exists only so this table is never a never-written block. The storage layer consults
|
|
811
|
+
-- a block's cohort on EVERY read of a block it does not hold, and every membership lookup
|
|
812
|
+
-- and every guarded insert (NotRevoked) reads this table; once any row exists the block is
|
|
813
|
+
-- held and re-checked on the storage layer's normal read-repair schedule, like every other
|
|
814
|
+
-- populated table. It cannot read as a retirement: every reader of this table filters on
|
|
815
|
+
-- its own guarded TableName, 'Revocation' is not one, and ControlDatabase.queryRevocations
|
|
816
|
+
-- skips the marker, so nothing reaps or re-issues it.
|
|
719
817
|
table Revocation (
|
|
720
|
-
TableName text, -- 'OwnerKey' | 'CadrePeer' | 'ValidationKey' | 'Strand' | '
|
|
818
|
+
TableName text, -- 'OwnerKey' | 'CadrePeer' | 'ValidationKey' | 'Strand' | 'StrandPartyKey' | 'DeviceToken',
|
|
819
|
+
-- or 'Revocation' for the ledger marker only (all confined by RowIsGone below)
|
|
721
820
|
RowKey text not null, -- primary key of the removed row: OwnerKey.Key / ValidationKey.Key /
|
|
722
|
-
-- CadrePeer.PeerId / DeviceToken.PeerId / Strand.Id
|
|
821
|
+
-- CadrePeer.PeerId / DeviceToken.PeerId / Strand.Id / StrandPartyKey.Id
|
|
723
822
|
-- Every guarded table's RevocationRecorded CHECK requires the
|
|
724
823
|
-- accompanying tombstone to carry the removed row's key here, so
|
|
725
824
|
-- a REMOVAL cannot file a misnamed tombstone. A tombstone with no
|
|
@@ -759,12 +858,19 @@ declare schema CadreControl {
|
|
|
759
858
|
-- it, stamp1) then re-admitted (stamp2) would leave a node that converges on the
|
|
760
859
|
-- re-add FIRST rejecting the stamp1 tombstone forever, never learning that stamp is
|
|
761
860
|
-- retired. The stamp-only form passes there (stamp1 is not live).
|
|
861
|
+
-- The last branch admits the ledger marker (see the table comment) and nothing else under
|
|
862
|
+
-- TableName 'Revocation': the whole triple is pinned, so with the primary key
|
|
863
|
+
-- (TableName, StampId) it is a singleton and the append-only growth surface is unchanged.
|
|
864
|
+
-- 'opened' cannot collide with a real stamp (43 base64url characters, generateStampId),
|
|
865
|
+
-- and the primary key includes TableName anyway.
|
|
762
866
|
constraint RowIsGone check on insert (
|
|
763
867
|
(new.TableName = 'OwnerKey' and not exists (select 1 from OwnerKey K where K.StampId = new.StampId))
|
|
764
868
|
or (new.TableName = 'CadrePeer' and not exists (select 1 from CadrePeer P where P.StampId = new.StampId))
|
|
765
869
|
or (new.TableName = 'ValidationKey' and not exists (select 1 from ValidationKey V where V.StampId = new.StampId))
|
|
766
870
|
or (new.TableName = 'Strand' and not exists (select 1 from Strand S where S.StampId = new.StampId))
|
|
871
|
+
or (new.TableName = 'StrandPartyKey' and not exists (select 1 from StrandPartyKey K where K.StampId = new.StampId))
|
|
767
872
|
or (new.TableName = 'DeviceToken' and not exists (select 1 from DeviceToken D where D.StampId = new.StampId))
|
|
873
|
+
or (new.TableName = 'Revocation' and new.RowKey = 'ledger' and new.StampId = 'opened')
|
|
768
874
|
),
|
|
769
875
|
-- Appending a tombstone is an OWNER action, like every other write in this schema.
|
|
770
876
|
-- Ungated, any writer that could reach the control database could retire a stamp:
|
|
@@ -798,6 +904,12 @@ declare schema CadreControl {
|
|
|
798
904
|
-- than that one.
|
|
799
905
|
-- RowIsGone and NoDelete are unchanged: they guard an owner retiring a stamp early
|
|
800
906
|
-- or withdrawing one later, which authorization does not cover.
|
|
907
|
+
-- The ledger marker is signed under this same rule, over
|
|
908
|
+
-- digest('CadreControl.Revocation', 'remove', 'Revocation', 'ledger', 'opened'). The
|
|
909
|
+
-- 'remove' tag on a row that retires nothing is deliberate: reusing the one append rule
|
|
910
|
+
-- keeps every append owner-signed without adding an unsigned or differently tagged
|
|
911
|
+
-- branch, and a captured marker signature can only re-file the identical row, whose
|
|
912
|
+
-- primary key is already taken.
|
|
801
913
|
constraint Authorized check on insert (
|
|
802
914
|
exists (select 1 from OwnerKey A where A.Key = context.OwnerKey and verify(digest('CadreControl.Revocation', 'remove', new.TableName, new.RowKey, new.StampId), context.Signature, A.Key, 'ed25519'))
|
|
803
915
|
),
|