@serfab/cadre-core 1.13.0 → 1.15.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.
Files changed (230) hide show
  1. package/README.md +293 -274
  2. package/dist/cadre-invite-protocol.d.ts +383 -0
  3. package/dist/cadre-invite-protocol.js +774 -0
  4. package/dist/cadre-invite-protocol.js.map +1 -0
  5. package/dist/cadre-join-request.d.ts +42 -0
  6. package/dist/cadre-join-request.js +144 -0
  7. package/dist/cadre-join-request.js.map +1 -0
  8. package/dist/cadre-node.d.ts +849 -210
  9. package/dist/cadre-node.js +1574 -374
  10. package/dist/cadre-node.js.map +1 -1
  11. package/dist/claim-proof.d.ts +45 -0
  12. package/dist/claim-proof.js +132 -0
  13. package/dist/claim-proof.js.map +1 -0
  14. package/dist/control-authorization.d.ts +80 -19
  15. package/dist/control-authorization.js +55 -13
  16. package/dist/control-authorization.js.map +1 -1
  17. package/dist/control-cohort.js +1 -1
  18. package/dist/control-cohort.js.map +1 -1
  19. package/dist/control-database.d.ts +605 -108
  20. package/dist/control-database.js +1299 -280
  21. package/dist/control-database.js.map +1 -1
  22. package/dist/control-protocol-guard.d.ts +142 -0
  23. package/dist/control-protocol-guard.js +293 -0
  24. package/dist/control-protocol-guard.js.map +1 -0
  25. package/dist/control-schema.d.ts +1 -1
  26. package/dist/control-schema.js +817 -1067
  27. package/dist/control-schema.js.map +1 -1
  28. package/dist/control-stream.d.ts +4 -0
  29. package/dist/control-stream.js +22 -7
  30. package/dist/control-stream.js.map +1 -1
  31. package/dist/delegate-admission.d.ts +1 -1
  32. package/dist/delegate-admission.js +1 -1
  33. package/dist/device-token.d.ts +11 -9
  34. package/dist/device-token.js +14 -12
  35. package/dist/device-token.js.map +1 -1
  36. package/dist/ed25519-key.d.ts +22 -9
  37. package/dist/ed25519-key.js +47 -10
  38. package/dist/ed25519-key.js.map +1 -1
  39. package/dist/fetch-impl.d.ts +12 -0
  40. package/dist/fetch-impl.js +23 -0
  41. package/dist/fetch-impl.js.map +1 -0
  42. package/dist/file-durable-slot.js +2 -1
  43. package/dist/file-durable-slot.js.map +1 -1
  44. package/dist/file-safe-name.d.ts +25 -0
  45. package/dist/file-safe-name.js +29 -0
  46. package/dist/file-safe-name.js.map +1 -0
  47. package/dist/formation-approval.js +5 -11
  48. package/dist/formation-approval.js.map +1 -1
  49. package/dist/fs-atomic.d.ts +0 -19
  50. package/dist/fs-atomic.js +3 -24
  51. package/dist/fs-atomic.js.map +1 -1
  52. package/dist/index.d.ts +22 -10
  53. package/dist/index.js +54 -11
  54. package/dist/index.js.map +1 -1
  55. package/dist/invitation-bootstrap.d.ts +50 -12
  56. package/dist/invitation-bootstrap.js +76 -13
  57. package/dist/invitation-bootstrap.js.map +1 -1
  58. package/dist/joined-strand-store.d.ts +7 -0
  59. package/dist/joined-strand-store.js +9 -0
  60. package/dist/joined-strand-store.js.map +1 -1
  61. package/dist/key-store-file.js +2 -1
  62. package/dist/key-store-file.js.map +1 -1
  63. package/dist/link-budget.d.ts +25 -33
  64. package/dist/link-budget.js +25 -35
  65. package/dist/link-budget.js.map +1 -1
  66. package/dist/machine-removal.d.ts +78 -0
  67. package/dist/machine-removal.js +166 -0
  68. package/dist/machine-removal.js.map +1 -0
  69. package/dist/membership-connection-gater.d.ts +191 -151
  70. package/dist/membership-connection-gater.js +236 -173
  71. package/dist/membership-connection-gater.js.map +1 -1
  72. package/dist/node-claim-payload.d.ts +74 -0
  73. package/dist/node-claim-payload.js +224 -0
  74. package/dist/node-claim-payload.js.map +1 -0
  75. package/dist/node-default-transports.d.ts +27 -0
  76. package/dist/node-default-transports.js +35 -0
  77. package/dist/node-default-transports.js.map +1 -0
  78. package/dist/node-transports.d.ts +30 -0
  79. package/dist/node-transports.js +40 -0
  80. package/dist/node-transports.js.map +1 -0
  81. package/dist/owner-anchor-sync.d.ts +22 -0
  82. package/dist/owner-anchor-sync.js +143 -0
  83. package/dist/owner-anchor-sync.js.map +1 -0
  84. package/dist/peer-authorization.d.ts +113 -18
  85. package/dist/peer-authorization.js +225 -29
  86. package/dist/peer-authorization.js.map +1 -1
  87. package/dist/peer-dial.d.ts +143 -27
  88. package/dist/peer-dial.js +276 -31
  89. package/dist/peer-dial.js.map +1 -1
  90. package/dist/peer-join-backfill.d.ts +26 -2
  91. package/dist/peer-join-backfill.js +60 -12
  92. package/dist/peer-join-backfill.js.map +1 -1
  93. package/dist/peer-record.d.ts +17 -52
  94. package/dist/peer-record.js +20 -58
  95. package/dist/peer-record.js.map +1 -1
  96. package/dist/pending-join-runner.d.ts +66 -58
  97. package/dist/pending-join-runner.js +136 -145
  98. package/dist/pending-join-runner.js.map +1 -1
  99. package/dist/primary-lan-address.d.ts +8 -0
  100. package/dist/primary-lan-address.js +46 -0
  101. package/dist/primary-lan-address.js.map +1 -0
  102. package/dist/provider/client.d.ts +131 -0
  103. package/dist/provider/client.js +226 -0
  104. package/dist/provider/client.js.map +1 -0
  105. package/dist/provider/descriptor.d.ts +41 -0
  106. package/dist/provider/descriptor.js +103 -0
  107. package/dist/provider/descriptor.js.map +1 -0
  108. package/dist/provider/index.d.ts +9 -0
  109. package/dist/provider/index.js +10 -0
  110. package/dist/provider/index.js.map +1 -0
  111. package/dist/provider/oauth.d.ts +125 -0
  112. package/dist/provider/oauth.js +199 -0
  113. package/dist/provider/oauth.js.map +1 -0
  114. package/dist/provider/rent-node.d.ts +101 -0
  115. package/dist/provider/rent-node.js +220 -0
  116. package/dist/provider/rent-node.js.map +1 -0
  117. package/dist/push-fanout.d.ts +8 -2
  118. package/dist/push-fanout.js +8 -2
  119. package/dist/push-fanout.js.map +1 -1
  120. package/dist/relay-addrs.d.ts +9 -4
  121. package/dist/relay-addrs.js +9 -4
  122. package/dist/relay-addrs.js.map +1 -1
  123. package/dist/relay-reservation.d.ts +51 -16
  124. package/dist/relay-reservation.js +107 -24
  125. package/dist/relay-reservation.js.map +1 -1
  126. package/dist/seed-bootstrap.d.ts +165 -123
  127. package/dist/seed-bootstrap.js +313 -275
  128. package/dist/seed-bootstrap.js.map +1 -1
  129. package/dist/seed-trust-policy.d.ts +98 -37
  130. package/dist/seed-trust-policy.js +188 -2
  131. package/dist/seed-trust-policy.js.map +1 -1
  132. package/dist/strand-addr-protocol.d.ts +24 -26
  133. package/dist/strand-addr-protocol.js +19 -26
  134. package/dist/strand-addr-protocol.js.map +1 -1
  135. package/dist/strand-commit-durability.d.ts +54 -0
  136. package/dist/strand-commit-durability.js +74 -0
  137. package/dist/strand-commit-durability.js.map +1 -0
  138. package/dist/strand-database.d.ts +23 -8
  139. package/dist/strand-database.js +41 -13
  140. package/dist/strand-database.js.map +1 -1
  141. package/dist/strand-first-sync-gate.d.ts +7 -1
  142. package/dist/strand-first-sync-gate.js +7 -1
  143. package/dist/strand-first-sync-gate.js.map +1 -1
  144. package/dist/strand-formation-protocol.js +5 -2
  145. package/dist/strand-formation-protocol.js.map +1 -1
  146. package/dist/strand-fret-addrs.d.ts +19 -1
  147. package/dist/strand-fret-addrs.js +27 -9
  148. package/dist/strand-fret-addrs.js.map +1 -1
  149. package/dist/strand-instance-manager.d.ts +81 -19
  150. package/dist/strand-instance-manager.js +216 -105
  151. package/dist/strand-instance-manager.js.map +1 -1
  152. package/dist/strand-move.d.ts +124 -0
  153. package/dist/strand-move.js +438 -0
  154. package/dist/strand-move.js.map +1 -0
  155. package/dist/strand-network-config.js +7 -6
  156. package/dist/strand-network-config.js.map +1 -1
  157. package/dist/strand-peer-redial.d.ts +113 -0
  158. package/dist/strand-peer-redial.js +236 -0
  159. package/dist/strand-peer-redial.js.map +1 -0
  160. package/dist/strand-revocation-enforcer.d.ts +11 -11
  161. package/dist/strand-revocation-enforcer.js +11 -11
  162. package/dist/strand-wake-protocol.d.ts +13 -16
  163. package/dist/strand-wake-protocol.js +23 -27
  164. package/dist/strand-wake-protocol.js.map +1 -1
  165. package/dist/trusted-owner-store.d.ts +35 -5
  166. package/dist/trusted-owner-store.js +50 -8
  167. package/dist/trusted-owner-store.js.map +1 -1
  168. package/dist/types.d.ts +512 -89
  169. package/dist/types.js +10 -0
  170. package/dist/types.js.map +1 -1
  171. package/package.json +19 -10
  172. package/src/cadre-invite-protocol.ts +1052 -0
  173. package/src/cadre-join-request.ts +167 -0
  174. package/src/cadre-node.ts +1703 -386
  175. package/src/claim-proof.ts +147 -0
  176. package/src/control-authorization.ts +203 -117
  177. package/src/control-cohort.ts +1 -1
  178. package/src/control-database.ts +4663 -3453
  179. package/src/control-protocol-guard.ts +348 -0
  180. package/src/control-schema.ts +828 -1078
  181. package/src/control-stream.ts +26 -7
  182. package/src/delegate-admission.ts +1 -1
  183. package/src/device-token.ts +14 -10
  184. package/src/ed25519-key.ts +47 -10
  185. package/src/fetch-impl.ts +23 -0
  186. package/src/file-durable-slot.ts +64 -63
  187. package/src/file-safe-name.ts +32 -0
  188. package/src/formation-approval.ts +604 -610
  189. package/src/fs-atomic.ts +92 -117
  190. package/src/index.ts +789 -648
  191. package/src/invitation-bootstrap.ts +91 -13
  192. package/src/joined-strand-store.ts +508 -498
  193. package/src/key-store-file.ts +142 -141
  194. package/src/link-budget.ts +25 -36
  195. package/src/machine-removal.ts +216 -0
  196. package/src/membership-connection-gater.ts +259 -191
  197. package/src/node-claim-payload.ts +259 -0
  198. package/src/node-default-transports.ts +38 -0
  199. package/src/node-transports.ts +49 -0
  200. package/src/owner-anchor-sync.ts +171 -0
  201. package/src/peer-authorization.ts +258 -29
  202. package/src/peer-dial.ts +604 -315
  203. package/src/peer-join-backfill.ts +62 -11
  204. package/src/peer-record.ts +159 -197
  205. package/src/pending-join-runner.ts +729 -735
  206. package/src/primary-lan-address.ts +47 -0
  207. package/src/provider/client.ts +326 -0
  208. package/src/provider/descriptor.ts +129 -0
  209. package/src/provider/index.ts +59 -0
  210. package/src/provider/oauth.ts +285 -0
  211. package/src/provider/rent-node.ts +275 -0
  212. package/src/push-fanout.ts +8 -2
  213. package/src/relay-addrs.ts +9 -4
  214. package/src/relay-reservation.ts +1211 -1121
  215. package/src/seed-bootstrap.ts +1502 -1448
  216. package/src/seed-trust-policy.ts +353 -140
  217. package/src/strand-addr-protocol.ts +26 -35
  218. package/src/strand-commit-durability.ts +116 -0
  219. package/src/strand-database.ts +46 -14
  220. package/src/strand-first-sync-gate.ts +7 -1
  221. package/src/strand-formation-protocol.ts +1039 -1036
  222. package/src/strand-fret-addrs.ts +179 -145
  223. package/src/strand-instance-manager.ts +2026 -1883
  224. package/src/strand-move.ts +585 -0
  225. package/src/strand-network-config.ts +7 -6
  226. package/src/strand-peer-redial.ts +307 -0
  227. package/src/strand-revocation-enforcer.ts +11 -11
  228. package/src/strand-wake-protocol.ts +23 -30
  229. package/src/trusted-owner-store.ts +242 -171
  230. package/src/types.ts +2687 -2242
@@ -0,0 +1,45 @@
1
+ import type { SeedRefusalCode } from './types.js';
2
+ /** Length of a claim secret: 256 bits, so guessing one is infeasible. */
3
+ export declare const CLAIM_SECRET_BYTES = 32;
4
+ /**
5
+ * Thrown by `CadreNode.claimNode` when the node answered the claim seed with a refusal.
6
+ * Distinct from `PeerUnreachableError` (no connection to the node formed) and from a
7
+ * failure after the node was reached (thrown by `deliverSeed` as is): here the node was
8
+ * reached and said no. `code` is the node's machine-readable cause
9
+ * when its policy supplied one (`already-claimed`, `claim-proof-invalid`, ...), so a
10
+ * phone can tell "someone else owns it" from "wrong secret" without parsing `reason`.
11
+ */
12
+ export declare class ClaimRefusedError extends Error {
13
+ readonly nodePeerId: string;
14
+ readonly reason: string;
15
+ readonly code?: SeedRefusalCode | undefined;
16
+ constructor(nodePeerId: string, reason: string, code?: SeedRefusalCode | undefined);
17
+ }
18
+ /**
19
+ * Decode a claim secret from the base64url text the host shows and the phone scans.
20
+ *
21
+ * Refuses anything that is not base64url of exactly {@link CLAIM_SECRET_BYTES} bytes,
22
+ * naming the problem. The rejected value is never echoed: unlike a public key, a
23
+ * mistyped secret may still be a secret.
24
+ *
25
+ * @returns The 32 secret bytes.
26
+ */
27
+ export declare function parseClaimSecret(text: string): Uint8Array;
28
+ /**
29
+ * Compute the claim proof the owner side sends beside its seed.
30
+ *
31
+ * @param secret - The parsed 32-byte claim secret ({@link parseClaimSecret}).
32
+ * @param nodePeerId - The peer id of the node being claimed.
33
+ * @param signerKey - The owner key that signed the seed (base64url).
34
+ * @param seedDigest - The seed's signature digest (`seedDigest()` in `seed-bootstrap.ts`).
35
+ * @returns The proof, base64url.
36
+ */
37
+ export declare function claimProof(secret: Uint8Array, nodePeerId: string, signerKey: string, seedDigest: string): string;
38
+ /**
39
+ * Check a presented proof against the receiver's own view of the three bound fields.
40
+ *
41
+ * Recomputes the tag and compares in constant time. A proof that does not decode as
42
+ * base64url is simply invalid. `nodePeerId` must be the RECEIVER's own peer id, never
43
+ * one the sender supplied; that is what makes a captured proof useless elsewhere.
44
+ */
45
+ export declare function verifyClaimProof(secret: Uint8Array, nodePeerId: string, signerKey: string, seedDigest: string, proof: string): boolean;
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Claim proof: how the first seed delivered to a brand-new node proves that its sender
3
+ * holds the node's one-time claim secret.
4
+ *
5
+ * A node that belongs to nobody yet (a cadre-host child before its owner scans the QR
6
+ * code) has an empty trusted-owner anchor, so every seed trust policy refuses its first
7
+ * seed. The claim secret is the out-of-band channel that breaks the deadlock: the host
8
+ * mints 32 random bytes, shows them beside the node's addresses, and the owner's phone
9
+ * proves possession by sending, next to the seed, an HMAC keyed by those bytes.
10
+ *
11
+ * The proof is `base64url(HMAC-SHA256(secret, utf8(canonicalJson(payload))))` where
12
+ * `payload` is `{ purpose: 'sereus-node-claim', v: 1, nodePeerId, signerKey, seedDigest }`:
13
+ *
14
+ * - `nodePeerId` binds the proof to one node, so a captured proof cannot claim another
15
+ * node minted with the same secret;
16
+ * - `signerKey` binds it to one owner, so it cannot anchor a different key;
17
+ * - `seedDigest` (the same digest the seed signature covers, `seedDigest()` in
18
+ * `seed-bootstrap.ts`) binds it to one seed, so it cannot be reattached to another.
19
+ *
20
+ * A passive observer of one delivery therefore learns a proof that is useless anywhere
21
+ * else, and useless on this node once it is claimed. A keyed MAC is the standard way to
22
+ * prove possession of a secret without revealing it; the crypto plugin's framed
23
+ * `digest([...])` helper is not specified as one, so the secret is never fed to it.
24
+ *
25
+ * Verified by `claimSecretTrustPolicy` (`seed-trust-policy.ts`). Imports only
26
+ * `@noble/hashes`, `uint8arrays` and `canonical-json.ts`: this module loads on React
27
+ * Native and in the browser as well as Node.
28
+ */
29
+ import debug from 'debug';
30
+ import { hmac } from '@noble/hashes/hmac.js';
31
+ import { sha256 } from '@noble/hashes/sha2.js';
32
+ import { toString as uint8ArrayToString, fromString as uint8ArrayFromString } from 'uint8arrays';
33
+ import { canonicalJson } from './canonical-json.js';
34
+ const log = debug('sereus:cadre:claim-proof');
35
+ /** Length of a claim secret: 256 bits, so guessing one is infeasible. */
36
+ export const CLAIM_SECRET_BYTES = 32;
37
+ /**
38
+ * Thrown by `CadreNode.claimNode` when the node answered the claim seed with a refusal.
39
+ * Distinct from `PeerUnreachableError` (no connection to the node formed) and from a
40
+ * failure after the node was reached (thrown by `deliverSeed` as is): here the node was
41
+ * reached and said no. `code` is the node's machine-readable cause
42
+ * when its policy supplied one (`already-claimed`, `claim-proof-invalid`, ...), so a
43
+ * phone can tell "someone else owns it" from "wrong secret" without parsing `reason`.
44
+ */
45
+ export class ClaimRefusedError extends Error {
46
+ constructor(nodePeerId, reason, code) {
47
+ super(`Node ${nodePeerId} refused the claim${code ? ` (${code})` : ''}: ${reason}`);
48
+ this.nodePeerId = nodePeerId;
49
+ this.reason = reason;
50
+ this.code = code;
51
+ this.name = 'ClaimRefusedError';
52
+ }
53
+ }
54
+ /**
55
+ * Decode a claim secret from the base64url text the host shows and the phone scans.
56
+ *
57
+ * Refuses anything that is not base64url of exactly {@link CLAIM_SECRET_BYTES} bytes,
58
+ * naming the problem. The rejected value is never echoed: unlike a public key, a
59
+ * mistyped secret may still be a secret.
60
+ *
61
+ * @returns The 32 secret bytes.
62
+ */
63
+ export function parseClaimSecret(text) {
64
+ const trimmed = text.trim();
65
+ if (trimmed.length === 0) {
66
+ throw new Error('A claim secret is required (received an empty or whitespace-only value)');
67
+ }
68
+ let decoded;
69
+ try {
70
+ decoded = uint8ArrayFromString(trimmed, 'base64url');
71
+ }
72
+ catch (error) {
73
+ throw new Error('A claim secret must be base64url text (the value could not be decoded as base64url)', { cause: error });
74
+ }
75
+ if (decoded.length !== CLAIM_SECRET_BYTES) {
76
+ throw new Error(`A claim secret must decode to exactly ${CLAIM_SECRET_BYTES} bytes (the value decoded to ${decoded.length})`);
77
+ }
78
+ return decoded;
79
+ }
80
+ /** The HMAC tag bytes over the canonical claim payload (see the module doc). */
81
+ function claimProofBytes(secret, nodePeerId, signerKey, seedDigest) {
82
+ const payload = canonicalJson({ purpose: 'sereus-node-claim', v: 1, nodePeerId, signerKey, seedDigest });
83
+ return hmac(sha256, secret, new TextEncoder().encode(payload));
84
+ }
85
+ /**
86
+ * Compute the claim proof the owner side sends beside its seed.
87
+ *
88
+ * @param secret - The parsed 32-byte claim secret ({@link parseClaimSecret}).
89
+ * @param nodePeerId - The peer id of the node being claimed.
90
+ * @param signerKey - The owner key that signed the seed (base64url).
91
+ * @param seedDigest - The seed's signature digest (`seedDigest()` in `seed-bootstrap.ts`).
92
+ * @returns The proof, base64url.
93
+ */
94
+ export function claimProof(secret, nodePeerId, signerKey, seedDigest) {
95
+ return uint8ArrayToString(claimProofBytes(secret, nodePeerId, signerKey, seedDigest), 'base64url');
96
+ }
97
+ /**
98
+ * Check a presented proof against the receiver's own view of the three bound fields.
99
+ *
100
+ * Recomputes the tag and compares in constant time. A proof that does not decode as
101
+ * base64url is simply invalid. `nodePeerId` must be the RECEIVER's own peer id, never
102
+ * one the sender supplied; that is what makes a captured proof useless elsewhere.
103
+ */
104
+ export function verifyClaimProof(secret, nodePeerId, signerKey, seedDigest, proof) {
105
+ let presented;
106
+ try {
107
+ presented = uint8ArrayFromString(proof, 'base64url');
108
+ }
109
+ catch (error) {
110
+ log('claim proof is not base64url: %o', error);
111
+ return false;
112
+ }
113
+ return constantTimeEqual(presented, claimProofBytes(secret, nodePeerId, signerKey, seedDigest));
114
+ }
115
+ /**
116
+ * Byte equality whose running time does not depend on WHERE the arrays differ, so a
117
+ * remote cannot learn a valid proof prefix from response timing. The length check
118
+ * short-circuits, which leaks nothing: a valid proof's length is public. A plain loop
119
+ * rather than `node:crypto`'s `timingSafeEqual` because this module must load on React
120
+ * Native and in the browser.
121
+ */
122
+ function constantTimeEqual(a, b) {
123
+ if (a.length !== b.length) {
124
+ return false;
125
+ }
126
+ let diff = 0;
127
+ for (let i = 0; i < a.length; i++) {
128
+ diff |= a[i] ^ b[i];
129
+ }
130
+ return diff === 0;
131
+ }
132
+ //# sourceMappingURL=claim-proof.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claim-proof.js","sourceRoot":"","sources":["../src/claim-proof.ts"],"names":[],"mappings":"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"}
@@ -6,14 +6,24 @@
6
6
  * byte layout cannot drift between them or away from the SQL constraints in
7
7
  * `schemas/control.qsql`.
8
8
  *
9
- * Every vector leads with two fixed literals:
9
+ * Every vector leads with two fixed literals and the party id:
10
10
  *
11
- * digest(<domain>, <action>, <row field 1>, ..., <row field n>)
11
+ * digest(<domain>, <action>, <party id>, <row field 1>, ..., <row field n>)
12
12
  *
13
- * so a signature verifies ONLY against the one rule it was minted for. Without
14
- * the tags, several rules built byte-identical tuples (e.g. `ValidationKey`
15
- * insert and `OwnerKey` insert both signed `digest(Key, StampId)`), so an
16
- * approval for a narrow grant doubled as an approval for full ownership.
13
+ * so a signature verifies ONLY against the one rule, in the one party, it was
14
+ * minted for. Without the tags, several rules built byte-identical tuples (e.g.
15
+ * `ValidationKey` insert and `OwnerKey` insert both signed `digest(Key, StampId)`),
16
+ * so an approval for a narrow grant doubled as an approval for full ownership.
17
+ * Without the party, two parties sharing an owner key (one node key owning two
18
+ * cadres, a hardware owner key) would accept each other's approvals. The SQL side
19
+ * reads the party from the node-local `party_id()` function `ControlDatabase`
20
+ * registers (never from a context value or a replicated table), and every
21
+ * TypeScript signer and verifier passes the party it is configured for.
22
+ *
23
+ * Two digests carry NO party ({@link UnboundControlDomain}): the `FormationUsage`
24
+ * `'consent'` and `'vouch'` digests, whose signers do not know the party (see the
25
+ * schema's `FormationUsage.Authorized` comment), and the offline `'Cadre.Enrollment'`
26
+ * vouch, which no table checks.
17
27
  *
18
28
  * This module deliberately has no Quereus / Optimystic / libp2p imports so the
19
29
  * lightweight verifiers (`peer-authorization.ts`, consumed by the offline
@@ -33,7 +43,7 @@
33
43
  * CHECK the `'reissue'`-tagged digest `ControlDatabase.reissueRevocations`
34
44
  * signs — and `countRows` counts it.
35
45
  */
36
- export declare const CONTROL_TABLES: readonly ["OwnerKey", "ValidationKey", "Strand", "StrandPartyKey", "JoinedStrand", "PendingJoin", "CadrePeer", "DeviceToken", "FormationInvite", "FormationUsage", "Revocation"];
46
+ export declare const CONTROL_TABLES: readonly ["OwnerKey", "ValidationKey", "Strand", "StrandPartyKey", "JoinedStrand", "JoinRequest", "JoinSuccess", "JoinFailure", "CadrePeer", "DeviceToken", "FormationInvite", "FormationUsage", "CadreInvite", "CadreInviteUsage", "Revocation"];
37
47
  /**
38
48
  * Name of a CadreControl table. Doubles as the table half of a
39
49
  * {@link ControlDomain} tag.
@@ -47,11 +57,17 @@ export type ControlTable = typeof CONTROL_TABLES[number];
47
57
  * {@link ControlTable} rather than a fresh literal list, so a renamed table is a
48
58
  * compile error here instead of a silently dead branch.
49
59
  *
60
+ * `CadreInvite` is the one member whose rows are never deleted: its tombstone is a
61
+ * withdrawal over a row that stays (`NotRevoked` refuses re-seating the withdrawn
62
+ * row; there is no `RevocationRecorded`, since nothing is ever removed), so it is
63
+ * revocable without being removable — `control-database.ts`'s `RemovableTable`
64
+ * excludes it.
65
+ *
50
66
  * Lives in this import-free module rather than beside its main consumer
51
67
  * (`control-database.ts`) so the lightweight signers — `peer-authorization.ts`'s
52
68
  * `revocationDigest` — can type against it without pulling in the runtime.
53
69
  */
54
- export type RevocableTable = Extract<ControlTable, 'OwnerKey' | 'CadrePeer' | 'ValidationKey' | 'Strand' | 'StrandPartyKey' | 'JoinedStrand' | 'PendingJoin' | 'DeviceToken'>;
70
+ export type RevocableTable = Extract<ControlTable, 'OwnerKey' | 'CadrePeer' | 'ValidationKey' | 'Strand' | 'StrandPartyKey' | 'JoinedStrand' | 'JoinRequest' | 'DeviceToken' | 'CadreInvite'>;
55
71
  /**
56
72
  * What a signature authorizes, scoped to one table rule — or, for
57
73
  * `'Cadre.Enrollment'`, the offline peer vouch that `cadre enroll register`
@@ -59,6 +75,15 @@ export type RevocableTable = Extract<ControlTable, 'OwnerKey' | 'CadrePeer' | 'V
59
75
  * from every table rule).
60
76
  */
61
77
  export type ControlDomain = `CadreControl.${ControlTable}` | 'Cadre.Enrollment';
78
+ /**
79
+ * The domains whose digests bind no party id — the only two
80
+ * {@link unboundAuthorizationFields} accepts. `FormationUsage`: neither signer knows
81
+ * the party, and the invitation token scopes the digest instead (the reason is at the
82
+ * schema's `FormationUsage.Authorized` comment). `Cadre.Enrollment`: no table checks it.
83
+ */
84
+ export type UnboundControlDomain = 'CadreControl.FormationUsage' | 'Cadre.Enrollment';
85
+ /** Every domain whose digests bind the party id — the only ones {@link controlAuthorizationFields} accepts. */
86
+ export type BoundControlDomain = Exclude<ControlDomain, UnboundControlDomain>;
62
87
  /**
63
88
  * The action half of the tag:
64
89
  * - `'add'` / `'remove'` — insert / delete of the named row.
@@ -72,22 +97,58 @@ export type ControlDomain = `CadreControl.${ControlTable}` | 'Cadre.Enrollment';
72
97
  * - `'publish'` — a peer self-signs its OWN record (the `CadrePeer` /
73
98
  * `DeviceToken` self-update branches), with its own key rather than an
74
99
  * owner key.
75
- * - `'consent'` — a peer self-signs its OWN `FormationUsage` redemption (the
76
- * joiner proving it agreed to join), with its own key. Distinct from the
77
- * approver's `'vouch'` over the same table so the two stored signatures are
78
- * never interchangeable.
100
+ * - `'consent'` — a peer self-signs its OWN `FormationUsage` or
101
+ * `CadreInviteUsage` redemption (the joiner proving it agreed to join), with
102
+ * its own key. Distinct from the approver's `'vouch'` over the same table so
103
+ * the two stored signatures are never interchangeable.
104
+ * - `'redeem'` — the holder of a `CadreInvite`'s private key proves possession
105
+ * on a `CadreInviteUsage` row. Distinct from the device's `'consent'` over the
106
+ * same fields: the two signatures are by different keys and neither may stand
107
+ * in for the other.
79
108
  * - `'reissue'` — an owner re-writes an existing `Revocation` tombstone,
80
109
  * bumping its `ReissuedAt` counter so a tombstone committed while the node
81
110
  * was alone can be re-broadcast. Distinct from `'remove'` so a tombstone
82
111
  * append approval can never be replayed as a re-issue and vice versa.
83
112
  */
84
- export type ControlAction = 'add' | 'remove' | 'vouch' | 'publish' | 'consent' | 'reissue';
113
+ export type ControlAction = 'add' | 'remove' | 'vouch' | 'publish' | 'consent' | 'redeem' | 'reissue';
114
+ /**
115
+ * The `CadreInvite` columns its `'add'` signature binds, in the schema's order and
116
+ * with the row's stored forms: `expiresAt` is the engine-canonical `datetime` string
117
+ * (`canonicalDatetime`), never epoch ms, because the SQL side digests
118
+ * `cast(new.ExpiresAt as text)` after coercion. Structurally satisfied by a
119
+ * `CadreInviteRow` (`types.ts`), which adds the issuer pair the signature does not cover.
120
+ */
121
+ export interface CadreInviteSignedFields {
122
+ key: string;
123
+ peerId: string | null;
124
+ grantsOwner: boolean;
125
+ expiresAt: string | null;
126
+ totalUses: number | null;
127
+ stampId: string;
128
+ }
129
+ /**
130
+ * The row-field half of a `CadreInvite` `'add'` digest, shared by the signer
131
+ * (`control-database.ts`'s `cadreInviteAddMessage`, raw bytes) and the verifier
132
+ * (`peer-authorization.ts`'s `cadreInviteAddDigest`, base64url) so the two cannot
133
+ * drift. SQL mirror, in `CadreInvite.AuthorizedInsert`:
134
+ * `new.Key, coalesce(new.PeerId, ''), cast(new.GrantsOwner as text),
135
+ * coalesce(cast(new.ExpiresAt as text), ''), coalesce(cast(new.TotalUses as text), ''), new.StampId`.
136
+ */
137
+ export declare function cadreInviteRowFields(row: CadreInviteSignedFields): string[];
138
+ /**
139
+ * The full ordered field vector a party-bound control-plane signature covers. Digest
140
+ * this with the crypto plugin's injective multi-field encoding (every field TEXT);
141
+ * the SQL mirror passes the same literals as leading `digest(...)` arguments and
142
+ * reads the party from its node-local `party_id()`:
143
+ *
144
+ * TS: digest(controlAuthorizationFields('CadreControl.X', 'add', partyId, [a, b]), 'sha256', ...)
145
+ * SQL: digest('CadreControl.X', 'add', party_id(), new.A, new.B)
146
+ */
147
+ export declare function controlAuthorizationFields(domain: BoundControlDomain, action: ControlAction, partyId: string, rowFields: string[]): string[];
85
148
  /**
86
- * The full ordered field vector a control-plane signature covers. Digest this
87
- * with the crypto plugin's injective multi-field encoding (every field TEXT);
88
- * the SQL mirror passes the same literals as leading `digest(...)` arguments:
149
+ * The field vector of the two party-free digests ({@link UnboundControlDomain}):
89
150
  *
90
- * TS: digest(controlAuthorizationFields('CadreControl.X', 'add', [a, b]), 'sha256', ...)
91
- * SQL: digest('CadreControl.X', 'add', new.A, new.B)
151
+ * TS: digest(unboundAuthorizationFields('CadreControl.FormationUsage', 'consent', [a, b]), 'sha256', ...)
152
+ * SQL: digest('CadreControl.FormationUsage', 'consent', new.A, new.B)
92
153
  */
93
- export declare function controlAuthorizationFields(domain: ControlDomain, action: ControlAction, rowFields: string[]): string[];
154
+ export declare function unboundAuthorizationFields(domain: UnboundControlDomain, action: ControlAction, rowFields: string[]): string[];
@@ -6,14 +6,24 @@
6
6
  * byte layout cannot drift between them or away from the SQL constraints in
7
7
  * `schemas/control.qsql`.
8
8
  *
9
- * Every vector leads with two fixed literals:
9
+ * Every vector leads with two fixed literals and the party id:
10
10
  *
11
- * digest(<domain>, <action>, <row field 1>, ..., <row field n>)
11
+ * digest(<domain>, <action>, <party id>, <row field 1>, ..., <row field n>)
12
12
  *
13
- * so a signature verifies ONLY against the one rule it was minted for. Without
14
- * the tags, several rules built byte-identical tuples (e.g. `ValidationKey`
15
- * insert and `OwnerKey` insert both signed `digest(Key, StampId)`), so an
16
- * approval for a narrow grant doubled as an approval for full ownership.
13
+ * so a signature verifies ONLY against the one rule, in the one party, it was
14
+ * minted for. Without the tags, several rules built byte-identical tuples (e.g.
15
+ * `ValidationKey` insert and `OwnerKey` insert both signed `digest(Key, StampId)`),
16
+ * so an approval for a narrow grant doubled as an approval for full ownership.
17
+ * Without the party, two parties sharing an owner key (one node key owning two
18
+ * cadres, a hardware owner key) would accept each other's approvals. The SQL side
19
+ * reads the party from the node-local `party_id()` function `ControlDatabase`
20
+ * registers (never from a context value or a replicated table), and every
21
+ * TypeScript signer and verifier passes the party it is configured for.
22
+ *
23
+ * Two digests carry NO party ({@link UnboundControlDomain}): the `FormationUsage`
24
+ * `'consent'` and `'vouch'` digests, whose signers do not know the party (see the
25
+ * schema's `FormationUsage.Authorized` comment), and the offline `'Cadre.Enrollment'`
26
+ * vouch, which no table checks.
17
27
  *
18
28
  * This module deliberately has no Quereus / Optimystic / libp2p imports so the
19
29
  * lightweight verifiers (`peer-authorization.ts`, consumed by the offline
@@ -39,22 +49,54 @@ export const CONTROL_TABLES = [
39
49
  'Strand',
40
50
  'StrandPartyKey',
41
51
  'JoinedStrand',
42
- 'PendingJoin',
52
+ 'JoinRequest',
53
+ 'JoinSuccess',
54
+ 'JoinFailure',
43
55
  'CadrePeer',
44
56
  'DeviceToken',
45
57
  'FormationInvite',
46
58
  'FormationUsage',
59
+ 'CadreInvite',
60
+ 'CadreInviteUsage',
47
61
  'Revocation',
48
62
  ];
49
63
  /**
50
- * The full ordered field vector a control-plane signature covers. Digest this
51
- * with the crypto plugin's injective multi-field encoding (every field TEXT);
52
- * the SQL mirror passes the same literals as leading `digest(...)` arguments:
64
+ * The row-field half of a `CadreInvite` `'add'` digest, shared by the signer
65
+ * (`control-database.ts`'s `cadreInviteAddMessage`, raw bytes) and the verifier
66
+ * (`peer-authorization.ts`'s `cadreInviteAddDigest`, base64url) so the two cannot
67
+ * drift. SQL mirror, in `CadreInvite.AuthorizedInsert`:
68
+ * `new.Key, coalesce(new.PeerId, ''), cast(new.GrantsOwner as text),
69
+ * coalesce(cast(new.ExpiresAt as text), ''), coalesce(cast(new.TotalUses as text), ''), new.StampId`.
70
+ */
71
+ export function cadreInviteRowFields(row) {
72
+ return [
73
+ row.key,
74
+ row.peerId ?? '',
75
+ row.grantsOwner ? '1' : '0',
76
+ row.expiresAt ?? '',
77
+ row.totalUses === null ? '' : String(row.totalUses),
78
+ row.stampId,
79
+ ];
80
+ }
81
+ /**
82
+ * The full ordered field vector a party-bound control-plane signature covers. Digest
83
+ * this with the crypto plugin's injective multi-field encoding (every field TEXT);
84
+ * the SQL mirror passes the same literals as leading `digest(...)` arguments and
85
+ * reads the party from its node-local `party_id()`:
86
+ *
87
+ * TS: digest(controlAuthorizationFields('CadreControl.X', 'add', partyId, [a, b]), 'sha256', ...)
88
+ * SQL: digest('CadreControl.X', 'add', party_id(), new.A, new.B)
89
+ */
90
+ export function controlAuthorizationFields(domain, action, partyId, rowFields) {
91
+ return [domain, action, partyId, ...rowFields];
92
+ }
93
+ /**
94
+ * The field vector of the two party-free digests ({@link UnboundControlDomain}):
53
95
  *
54
- * TS: digest(controlAuthorizationFields('CadreControl.X', 'add', [a, b]), 'sha256', ...)
55
- * SQL: digest('CadreControl.X', 'add', new.A, new.B)
96
+ * TS: digest(unboundAuthorizationFields('CadreControl.FormationUsage', 'consent', [a, b]), 'sha256', ...)
97
+ * SQL: digest('CadreControl.FormationUsage', 'consent', new.A, new.B)
56
98
  */
57
- export function controlAuthorizationFields(domain, action, rowFields) {
99
+ export function unboundAuthorizationFields(domain, action, rowFields) {
58
100
  return [domain, action, ...rowFields];
59
101
  }
60
102
  //# sourceMappingURL=control-authorization.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"control-authorization.js","sourceRoot":"","sources":["../src/control-authorization.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,UAAU;IACV,eAAe;IACf,QAAQ;IACR,gBAAgB;IAChB,cAAc;IACd,aAAa;IACb,WAAW;IACX,aAAa;IACb,iBAAiB;IACjB,gBAAgB;IAChB,YAAY;CACJ,CAAC;AAsDX;;;;;;;GAOG;AACH,MAAM,UAAU,0BAA0B,CACxC,MAAqB,EACrB,MAAqB,EACrB,SAAmB;IAEnB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;AACxC,CAAC"}
1
+ {"version":3,"file":"control-authorization.js","sourceRoot":"","sources":["../src/control-authorization.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,UAAU;IACV,eAAe;IACf,QAAQ;IACR,gBAAgB;IAChB,cAAc;IACd,aAAa;IACb,aAAa;IACb,aAAa;IACb,WAAW;IACX,aAAa;IACb,iBAAiB;IACjB,gBAAgB;IAChB,aAAa;IACb,kBAAkB;IAClB,YAAY;CACJ,CAAC;AA2FX;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAA4B;IAC/D,OAAO;QACL,GAAG,CAAC,GAAG;QACP,GAAG,CAAC,MAAM,IAAI,EAAE;QAChB,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;QAC3B,GAAG,CAAC,SAAS,IAAI,EAAE;QACnB,GAAG,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC;QACnD,GAAG,CAAC,OAAO;KACZ,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,0BAA0B,CACxC,MAA0B,EAC1B,MAAqB,EACrB,OAAe,EACf,SAAmB;IAEnB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,SAAS,CAAC,CAAC;AACjD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,0BAA0B,CACxC,MAA4B,EAC5B,MAAqB,EACrB,SAAmB;IAEnB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;AACxC,CAAC"}
@@ -20,7 +20,7 @@
20
20
  * correct, cheap, and matches what the convergence tests do by hand; for a large
21
21
  * party it bounds out-degree to `backbone + targetDegree`.
22
22
  */
23
- import { ed25519PublicKeyB64FromPeerId } from './seed-bootstrap.js';
23
+ import { ed25519PublicKeyB64FromPeerId } from './ed25519-key.js';
24
24
  /**
25
25
  * Default recurring cadence for {@link CadreNode.reconcileControlCohort} (ms).
26
26
  * In the same spirit as the 5s strand-watch poll but lighter, since a control
@@ -1 +1 @@
1
- {"version":3,"file":"control-cohort.js","sourceRoot":"","sources":["../src/control-cohort.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,6BAA6B,EAAE,MAAM,qBAAqB,CAAC;AAGpE;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,mCAAmC,GAAG,KAAM,CAAC;AAE1D;;;;GAIG;AACH,MAAM,CAAC,MAAM,oCAAoC,GAAG,CAAC,CAAC;AAkCtD,mFAAmF;AACnF,SAAS,QAAQ,CAAC,CAAgB,EAAE,CAAgB;IAClD,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM;QAAE,OAAO,CAAC,CAAC,CAAC;IACnC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM;QAAE,OAAO,CAAC,CAAC;IAClC,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,wBAAwB,CACtC,QAAyB,EACzB,SAA8B,EAC9B,YAAoB;IAEpB,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,MAAM,IAAI,GAAoB,EAAE,CAAC;IAEjC,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,6BAA6B,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1D,IAAI,MAAM,KAAK,IAAI,IAAI,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7C,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IAED,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACxB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAEpB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC;IACtC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAEhC,OAAO;QACL,KAAK,EAAE,CAAC,GAAG,QAAQ,EAAE,GAAG,IAAI,CAAC;QAC7B,cAAc,EAAE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM;KAC1C,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"control-cohort.js","sourceRoot":"","sources":["../src/control-cohort.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,6BAA6B,EAAE,MAAM,kBAAkB,CAAC;AAGjE;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,mCAAmC,GAAG,KAAM,CAAC;AAE1D;;;;GAIG;AACH,MAAM,CAAC,MAAM,oCAAoC,GAAG,CAAC,CAAC;AAkCtD,mFAAmF;AACnF,SAAS,QAAQ,CAAC,CAAgB,EAAE,CAAgB;IAClD,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM;QAAE,OAAO,CAAC,CAAC,CAAC;IACnC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM;QAAE,OAAO,CAAC,CAAC;IAClC,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,wBAAwB,CACtC,QAAyB,EACzB,SAA8B,EAC9B,YAAoB;IAEpB,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,MAAM,IAAI,GAAoB,EAAE,CAAC;IAEjC,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,6BAA6B,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1D,IAAI,MAAM,KAAK,IAAI,IAAI,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7C,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IAED,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACxB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAEpB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC;IACtC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAEhC,OAAO;QACL,KAAK,EAAE,CAAC,GAAG,QAAQ,EAAE,GAAG,IAAI,CAAC;QAC7B,cAAc,EAAE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM;KAC1C,CAAC;AACJ,CAAC"}