@mh-gg/protocol 0.1.1-alpha.20260613T085325975Z

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.
@@ -0,0 +1,101 @@
1
+ const assert = require("node:assert/strict");
2
+ const test = require("node:test");
3
+
4
+ const {
5
+ advanceHlc,
6
+ codePointCompare,
7
+ compareHlc,
8
+ createHlcClock,
9
+ ensureOperationIdentity,
10
+ formatHlc,
11
+ operationContentHash,
12
+ operationIdMatchesContent,
13
+ parseHlc,
14
+ validateHlc,
15
+ validateRoomOperation
16
+ } = require("../src/index.cjs");
17
+
18
+ function baseOperation(overrides = {}) {
19
+ return {
20
+ roomId: "room",
21
+ appPackId: "app",
22
+ appPackHash: "sha256-app",
23
+ pluginId: "plugin",
24
+ type: "thing.create",
25
+ actor: { memberId: "alice", deviceId: "dev_alice", role: "admin" },
26
+ seq: 1,
27
+ createdAt: 1000,
28
+ payload: { name: "A" },
29
+ auth: { credentialId: "cred", signature: "sig" },
30
+ ...overrides
31
+ };
32
+ }
33
+
34
+ test("HLC formatting, parsing, and advancement preserve causal order", () => {
35
+ const first = formatHlc({ physical: 1000, logical: 0, nodeId: "dev" });
36
+ assert.equal(first, "000000000001000:000000:dev");
37
+ assert.deepEqual(parseHlc(first), { physical: 1000, logical: 0, nodeId: "dev", value: first });
38
+ const next = advanceHlc(first, { now: 999, nodeId: "dev" });
39
+ assert.equal(next, "000000000001000:000001:dev");
40
+ assert.equal(compareHlc(first, next), -1);
41
+
42
+ const clock = createHlcClock({ nodeId: "node-b", now: () => 1001 });
43
+ clock.observe(next);
44
+ assert.equal(clock.next(), "000000000001001:000000:node-b");
45
+ });
46
+
47
+ test("operation ids are content hashes bound to HLC and payload", () => {
48
+ const op = ensureOperationIdentity(baseOperation(), { now: 1000, nodeId: "dev_alice" });
49
+ assert.equal(op.id, operationContentHash(op));
50
+ assert.equal(operationIdMatchesContent(op), true);
51
+ validateRoomOperation(op, { now: 1000 });
52
+
53
+ const tampered = { ...op, payload: { name: "B" } };
54
+ assert.equal(operationIdMatchesContent(tampered), false);
55
+ assert.throws(() => validateRoomOperation(tampered, { now: 1000 }), /operation.id/);
56
+ });
57
+
58
+ test("operation HLC is required and bounded by local future skew", () => {
59
+ assert.throws(() => validateRoomOperation({ ...baseOperation(), id: "sha256:bad" }, { now: 1000 }), /operation.hlc/);
60
+ const okay = ensureOperationIdentity(baseOperation({ hlc: formatHlc({ physical: 1000 + 60_000, logical: 0, nodeId: "dev" }) }), { now: 1000, nodeId: "dev" });
61
+ validateRoomOperation(okay, { now: 1000 });
62
+ const tooFar = ensureOperationIdentity(baseOperation({ hlc: formatHlc({ physical: 1000 + 600_000, logical: 0, nodeId: "dev" }) }), { now: 1000, nodeId: "dev" });
63
+ assert.throws(() => validateRoomOperation(tooFar, { now: 1000 }), /future skew/);
64
+ });
65
+
66
+ test("byte comparison is locale-independent for operation ordering inputs", () => {
67
+ const values = ["é", "e\u0301", "Z", "a"].sort(codePointCompare);
68
+ assert.deepEqual(values, ["Z", "a", "e\u0301", "é"]);
69
+ const oldLocale = process.env.LC_ALL;
70
+ process.env.LC_ALL = "sv_SE.UTF-8";
71
+ try {
72
+ assert.deepEqual(["é", "e\u0301", "Z", "a"].sort(codePointCompare), values);
73
+ } finally {
74
+ if (oldLocale === undefined) delete process.env.LC_ALL;
75
+ else process.env.LC_ALL = oldLocale;
76
+ }
77
+ });
78
+
79
+ test("HLC helpers cover invalid, empty, and function-now branches", () => {
80
+ assert.equal(parseHlc("bad"), undefined);
81
+ assert.throws(() => formatHlc({ physical: -1, logical: 0, nodeId: "dev" }), /physical/);
82
+ assert.throws(() => formatHlc({ physical: 1, logical: 0, nodeId: "bad node" }), /nodeId/);
83
+ assert.equal(validateHlc("bad", { now: () => 1 }).ok, false);
84
+ assert.equal(validateHlc(formatHlc({ physical: 1, logical: 0, nodeId: "dev" }), { now: () => 1 }).ok, true);
85
+ assert.equal(validateHlc(formatHlc({ physical: 12, logical: 0, nodeId: "dev" }), { now: 1, maxFutureSkewMs: 20 }).ok, true);
86
+ assert.equal(validateHlc(formatHlc({ physical: 12, logical: 0, nodeId: "dev" }), { now: 1, maxFutureSkewMs: 1 }).ok, false);
87
+ assert.equal(advanceHlc(undefined, { now: "not-number", nodeId: "dev" }), "000000000000000:000000:dev");
88
+ });
89
+
90
+ test("operation identity helpers cover empty and mismatch branches", () => {
91
+ const a = formatHlc({ physical: 1, logical: 0, nodeId: "a" });
92
+ const b = formatHlc({ physical: 2, logical: 0, nodeId: "b" });
93
+ assert.equal(require("../src/index.cjs").maxHlc("", b), b);
94
+ assert.equal(require("../src/index.cjs").maxHlc(a, ""), a);
95
+ assert.equal(require("../src/index.cjs").maxHlc(a, b), b);
96
+ const assigned = require("../src/index.cjs").assignOperationId(baseOperation({ hlc: a }));
97
+ assert.equal(assigned.id, operationContentHash(assigned));
98
+ assert.equal(operationIdMatchesContent(null), false);
99
+ assert.equal(operationIdMatchesContent({ ...assigned, id: "sha256:wrong" }), false);
100
+ assert.equal(require("../src/index.cjs").unsignedOperationBodyForId(undefined).id, undefined);
101
+ });
@@ -0,0 +1,267 @@
1
+ const assert = require("node:assert/strict");
2
+ const test = require("node:test");
3
+ const { canonicalJson } = require("@mh-gg/event/canonicalJson");
4
+ const {
5
+ MATTERHORN_DEVICE_SIGNATURE_NOSTR_KIND,
6
+ MATTERHORN_ROOM_DEVICE_SIGNING_ALG,
7
+ MATTERHORN_ROOM_DEVICE_SIGNING_SCHEME,
8
+ unsignedRoomOperationForDeviceSignature,
9
+ validateRoomDeviceSignedOperationAuth,
10
+ validateRoomMemberKeyClaim,
11
+ verifyRoomDeviceKeyClaim,
12
+ verifyRoomDeviceOperationSignature,
13
+ verifyRoomDeviceSignedOperation,
14
+ verifyRoomMemberKeyClaim
15
+ } = require("../src/index.cjs");
16
+
17
+ let nostr;
18
+ try {
19
+ nostr = require("nostr-tools");
20
+ } catch {
21
+ nostr = undefined;
22
+ }
23
+
24
+ function bytes(hex) {
25
+ return nostr.utils.hexToBytes(hex);
26
+ }
27
+
28
+ function eventForSignature({ purpose, roomName, memberId, deviceId, keyId, publicKey, content, createdAt = 1700000000 }) {
29
+ return {
30
+ kind: MATTERHORN_DEVICE_SIGNATURE_NOSTR_KIND,
31
+ created_at: createdAt,
32
+ tags: [
33
+ ["protocol", "matterhorn-sdk"],
34
+ ["scheme", MATTERHORN_ROOM_DEVICE_SIGNING_SCHEME],
35
+ ["purpose", purpose],
36
+ ["room", roomName],
37
+ ["member", memberId],
38
+ ["device", deviceId],
39
+ ["key", keyId]
40
+ ],
41
+ content,
42
+ pubkey: publicKey,
43
+ id: "",
44
+ sig: ""
45
+ };
46
+ }
47
+
48
+ function unsignedClaim(claim) {
49
+ const next = JSON.parse(JSON.stringify(claim));
50
+ delete next.proof;
51
+ delete next.rootProof;
52
+ return next;
53
+ }
54
+
55
+ function unsignedOperation(operation) {
56
+ const next = JSON.parse(JSON.stringify(operation));
57
+ if (next.auth) {
58
+ delete next.auth.signature;
59
+ delete next.auth.signatureEventId;
60
+ delete next.auth.eventId;
61
+ delete next.auth.signedAt;
62
+ }
63
+ delete next.committedRoomVersion;
64
+ delete next.committedAt;
65
+ delete next.receivedAt;
66
+ delete next.relayId;
67
+ return next;
68
+ }
69
+
70
+ function copy(value) {
71
+ return JSON.parse(JSON.stringify(value));
72
+ }
73
+
74
+ function buildClaim() {
75
+ const rootPrivateKey = "1".repeat(64);
76
+ const devicePrivateKey = "2".repeat(64);
77
+ const rootPublicKey = nostr.getPublicKey(bytes(rootPrivateKey));
78
+ const publicKey = nostr.getPublicKey(bytes(devicePrivateKey));
79
+ const claim = {
80
+ kind: "matterhorn.member-key-claim",
81
+ type: "matterhorn/member-key/v1",
82
+ version: 1,
83
+ scheme: MATTERHORN_ROOM_DEVICE_SIGNING_SCHEME,
84
+ alg: MATTERHORN_ROOM_DEVICE_SIGNING_ALG,
85
+ roomId: "room",
86
+ roomName: "room",
87
+ memberId: "alice",
88
+ deviceId: "device-a",
89
+ keyId: "rk_test_device_a",
90
+ rootPublicKey,
91
+ publicKey,
92
+ publicKeyFingerprint: "sha256:" + "a".repeat(64),
93
+ encryptionAlg: "x25519",
94
+ encryptionKeyId: "rkx_test_device_a",
95
+ encryptionPublicKey: Buffer.from("encryption-public-key").toString("base64url"),
96
+ createdAt: 1700000000000
97
+ };
98
+ const content = canonicalJson(unsignedClaim(claim));
99
+ const rootEvent = nostr.finalizeEvent(eventForSignature({ purpose: "member-key", roomName: "room", memberId: "alice", deviceId: "device-a", keyId: claim.keyId, publicKey: rootPublicKey, content }), bytes(rootPrivateKey));
100
+ const deviceEvent = nostr.finalizeEvent(eventForSignature({ purpose: "member-key", roomName: "room", memberId: "alice", deviceId: "device-a", keyId: claim.keyId, publicKey, content }), bytes(devicePrivateKey));
101
+ claim.rootProof = { alg: MATTERHORN_ROOM_DEVICE_SIGNING_ALG, eventId: rootEvent.id, sig: rootEvent.sig, createdAt: rootEvent.created_at };
102
+ claim.proof = { alg: MATTERHORN_ROOM_DEVICE_SIGNING_ALG, eventId: deviceEvent.id, sig: deviceEvent.sig, createdAt: deviceEvent.created_at };
103
+ return { claim, devicePrivateKey };
104
+ }
105
+
106
+ function buildSignedOperation(claim, devicePrivateKey) {
107
+ const operation = {
108
+ id: "sha256:test",
109
+ roomId: "room",
110
+ appPackId: "app",
111
+ appPackHash: "sha256-app",
112
+ pluginId: "plugin",
113
+ type: "todo.add",
114
+ actor: { memberId: "alice", deviceId: "device-a", role: "member" },
115
+ seq: 1,
116
+ createdAt: 1700000000001,
117
+ hlc: "001700000000001:000000:device-a",
118
+ payload: { text: "ship" },
119
+ auth: {
120
+ kind: "matterhorn.operation-device-signature",
121
+ version: 1,
122
+ scheme: MATTERHORN_ROOM_DEVICE_SIGNING_SCHEME,
123
+ alg: MATTERHORN_ROOM_DEVICE_SIGNING_ALG,
124
+ credentialId: claim.keyId,
125
+ memberId: claim.memberId,
126
+ deviceId: claim.deviceId,
127
+ keyId: claim.keyId,
128
+ rootPublicKey: claim.rootPublicKey,
129
+ publicKey: claim.publicKey,
130
+ publicKeyFingerprint: claim.publicKeyFingerprint,
131
+ claim,
132
+ issuedAt: 1700000000001
133
+ }
134
+ };
135
+ const event = nostr.finalizeEvent(eventForSignature({ purpose: "operation", roomName: "room", memberId: "alice", deviceId: "device-a", keyId: claim.keyId, publicKey: claim.publicKey, content: canonicalJson(unsignedOperation(operation)), createdAt: 1700000000 }), bytes(devicePrivateKey));
136
+ operation.auth.signature = event.sig;
137
+ operation.auth.signatureEventId = event.id;
138
+ operation.auth.eventId = event.id;
139
+ operation.auth.signedAt = event.created_at;
140
+ return operation;
141
+ }
142
+
143
+ test("room-device member key claims and operations verify end to end", { skip: !nostr && "nostr-tools is not installed in this sandbox" }, () => {
144
+ const { claim, devicePrivateKey } = buildClaim();
145
+ assert.equal(verifyRoomMemberKeyClaim(claim).ok, true);
146
+
147
+ const operation = buildSignedOperation(claim, devicePrivateKey);
148
+ assert.equal(verifyRoomDeviceSignedOperation(operation, claim).ok, true);
149
+
150
+ const tampered = { ...operation, payload: { text: "tampered" } };
151
+ assert.equal(verifyRoomDeviceSignedOperation(tampered, claim).ok, false);
152
+
153
+ const wrongActor = { ...operation, actor: { ...operation.actor, memberId: "mallory" } };
154
+ assert.equal(verifyRoomDeviceSignedOperation(wrongActor, claim).ok, false);
155
+ });
156
+
157
+ test("room-device signing validators reject malformed claims and auth", { skip: !nostr && "nostr-tools is not installed in this sandbox" }, () => {
158
+ const { claim, devicePrivateKey } = buildClaim();
159
+ const operation = buildSignedOperation(claim, devicePrivateKey);
160
+
161
+ assert.equal(verifyRoomDeviceKeyClaim(claim), true);
162
+ assert.equal(verifyRoomDeviceOperationSignature(operation, claim).ok, true);
163
+ assert.equal(verifyRoomDeviceSignedOperation(operation).ok, true);
164
+
165
+ const withoutSignatureEventId = copy(operation);
166
+ delete withoutSignatureEventId.auth.signatureEventId;
167
+ assert.equal(verifyRoomDeviceSignedOperation(withoutSignatureEventId).ok, true);
168
+
169
+ assert.deepEqual(validateRoomMemberKeyClaim(null), { ok: false, error: "member key claim is required" });
170
+ for (const [field, value, error] of [
171
+ ["kind", "bad", "member key claim kind is invalid"],
172
+ ["type", "bad", "member key claim type is invalid"],
173
+ ["version", 2, "member key claim version is invalid"],
174
+ ["scheme", "bad", "member key claim scheme is invalid"],
175
+ ["alg", "bad", "member key claim alg is invalid"],
176
+ ["memberId", "", "member key claim memberId is required"],
177
+ ["roomName", "", "member key claim room is required"],
178
+ ["rootPublicKey", "bad", "member key claim rootPublicKey is invalid"],
179
+ ["publicKey", "bad", "member key claim publicKey is invalid"],
180
+ ["encryptionAlg", "bad", "member key claim encryptionAlg is invalid"],
181
+ ["encryptionKeyId", "", "member key claim encryptionKeyId is required"],
182
+ ["encryptionPublicKey", "", "member key claim encryptionPublicKey is required"],
183
+ ["createdAt", "bad", "member key claim createdAt is invalid"]
184
+ ]) {
185
+ const candidate = copy(claim);
186
+ if (field === "roomName") delete candidate.roomId;
187
+ candidate[field] = value;
188
+ assert.deepEqual(validateRoomMemberKeyClaim(candidate), { ok: false, error });
189
+ }
190
+
191
+ for (const [proofField, patch, error] of [
192
+ ["rootProof", undefined, "member key root proof is required"],
193
+ ["rootProof", { ...claim.rootProof, alg: "bad" }, "member key root proof alg is invalid"],
194
+ ["rootProof", { ...claim.rootProof, eventId: "bad" }, "member key root proof shape is invalid"],
195
+ ["proof", undefined, "member key device proof is required"],
196
+ ["proof", { ...claim.proof, alg: "bad" }, "member key device proof alg is invalid"],
197
+ ["proof", { ...claim.proof, sig: "bad" }, "member key device proof shape is invalid"]
198
+ ]) {
199
+ const candidate = copy(claim);
200
+ candidate[proofField] = patch;
201
+ assert.deepEqual(validateRoomMemberKeyClaim(candidate), { ok: false, error });
202
+ }
203
+
204
+ const badRootProof = copy(claim);
205
+ badRootProof.rootProof.sig = "0".repeat(128);
206
+ assert.deepEqual(verifyRoomMemberKeyClaim(badRootProof), { ok: false, error: "member key root proof is invalid" });
207
+
208
+ const badDeviceProof = copy(claim);
209
+ badDeviceProof.proof.sig = "0".repeat(128);
210
+ assert.deepEqual(verifyRoomMemberKeyClaim(badDeviceProof), { ok: false, error: "member key device proof is invalid" });
211
+
212
+ assert.deepEqual(validateRoomDeviceSignedOperationAuth(null), { ok: false, error: "operation is required" });
213
+ for (const [mutate, error] of [
214
+ [(candidate) => { delete candidate.auth; }, "operation auth is required"],
215
+ [(candidate) => { candidate.auth.scheme = "bad"; }, "operation auth scheme is invalid"],
216
+ [(candidate) => { candidate.auth.alg = "bad"; }, "operation auth alg is invalid"],
217
+ [(candidate) => { candidate.auth.kind = "bad"; }, "operation auth kind is invalid"],
218
+ [(candidate) => { candidate.auth.signature = ""; }, "operation auth signature is required"],
219
+ [(candidate) => { candidate.auth.signature = "bad"; }, "operation device signature shape is invalid"],
220
+ [(candidate) => { candidate.auth.issuedAt = "bad"; }, "operation auth issuedAt is invalid"],
221
+ [(candidate) => { candidate.auth.signedAt = "bad"; }, "operation auth signedAt is invalid"],
222
+ [(candidate) => { candidate.auth.credentialId = "other"; }, "operation auth credentialId must match keyId"],
223
+ [(candidate) => { candidate.actor.memberId = "mallory"; }, "operation auth member mismatch"],
224
+ [(candidate) => { candidate.actor.deviceId = "device-b"; }, "operation auth device mismatch"]
225
+ ]) {
226
+ const candidate = copy(operation);
227
+ mutate(candidate);
228
+ assert.deepEqual(validateRoomDeviceSignedOperationAuth(candidate), { ok: false, error });
229
+ }
230
+
231
+ for (const [mutate, error] of [
232
+ [(candidate) => { candidate.auth.keyId = "other"; candidate.auth.credentialId = "other"; }, "operation key does not match member key claim"],
233
+ [(candidate) => { candidate.auth.memberId = "mallory"; candidate.actor.memberId = "mallory"; }, "operation actor does not match member key claim"],
234
+ [(candidate) => { candidate.auth.rootPublicKey = "0".repeat(64); }, "operation root public key does not match member key claim"],
235
+ [(candidate) => { candidate.auth.publicKeyFingerprint = "sha256:" + "b".repeat(64); }, "operation public key does not match member key claim"]
236
+ ]) {
237
+ const candidate = copy(operation);
238
+ mutate(candidate);
239
+ assert.deepEqual(verifyRoomDeviceSignedOperation(candidate, claim), { ok: false, error });
240
+ }
241
+
242
+ const unsigned = unsignedRoomOperationForDeviceSignature({
243
+ ...operation,
244
+ ledgerId: "740755850904834048",
245
+ snowflakeId: "740755850904834048",
246
+ committedRoomVersion: 2,
247
+ committedAt: 3,
248
+ receivedAt: 4,
249
+ relayId: "relay"
250
+ });
251
+ assert.equal(unsigned.auth.signature, undefined);
252
+ assert.equal(unsigned.ledgerId, undefined);
253
+ assert.equal(unsigned.snowflakeId, undefined);
254
+ assert.equal(unsigned.committedRoomVersion, undefined);
255
+ });
256
+
257
+ test("room-device operation signatures survive host-assigned ledger ids", { skip: !nostr && "nostr-tools is not installed in this sandbox" }, () => {
258
+ const { claim, devicePrivateKey } = buildClaim();
259
+ const operation = buildSignedOperation(claim, devicePrivateKey);
260
+ const withHostLedger = {
261
+ ...operation,
262
+ ledgerId: "740755850904834048",
263
+ snowflakeId: "740755850904834048"
264
+ };
265
+
266
+ assert.equal(verifyRoomDeviceSignedOperation(withHostLedger, claim).ok, true);
267
+ });
@@ -0,0 +1,119 @@
1
+ const assert = require("node:assert/strict");
2
+ const test = require("node:test");
3
+
4
+ const {
5
+ DEFAULT_SNOWFLAKE_EPOCH,
6
+ SNOWFLAKE_MAX_SEQUENCE,
7
+ compareEntityIdsBySnowflake,
8
+ compareMaybeSnowflakeIds,
9
+ compareSnowflakeIds,
10
+ createSnowflakeGenerator,
11
+ extractSnowflakeId,
12
+ isSnowflakeId,
13
+ parseSnowflakeId,
14
+ prefixedSnowflakeId,
15
+ snowflakeBoundsForTimestamp,
16
+ snowflakeIdForOperation,
17
+ snowflakeIdFromParts,
18
+ snowflakeIdFromTimestamp,
19
+ snowflakeTimestamp,
20
+ stableSnowflakeNodeId,
21
+ stableSnowflakeSequence
22
+ } = require("../src/operations/snowflake.cjs");
23
+
24
+ test("snowflake IDs are decimal, parseable, and sortable by timestamp/sequence", () => {
25
+ const generator = createSnowflakeGenerator({ epoch: 0, nodeId: 7, now: () => 1000 });
26
+ const first = generator.next();
27
+ const second = generator.next();
28
+ const later = generator.next(1001);
29
+
30
+ assert.equal(isSnowflakeId(first), true);
31
+ assert.equal(compareSnowflakeIds(first, second), -1);
32
+ assert.equal(compareSnowflakeIds(second, later), -1);
33
+ assert.deepEqual(parseSnowflakeId(first, { epoch: 0 }), { id: first, timestampMs: 1000, nodeId: 7, sequence: 0, epoch: 0 });
34
+ assert.deepEqual(parseSnowflakeId(second, { epoch: 0 }), { id: second, timestampMs: 1000, nodeId: 7, sequence: 1, epoch: 0 });
35
+ });
36
+
37
+ test("operation snowflakes prefer assigned ledger IDs and support prefixed entity IDs", () => {
38
+ const existing = "4194304000";
39
+ assert.equal(snowflakeIdForOperation({ ledgerId: existing }), existing);
40
+ assert.equal(snowflakeIdForOperation({ snowflakeId: existing }), existing);
41
+ assert.equal(prefixedSnowflakeId("task", existing), `task_${existing}`);
42
+ assert.equal(prefixedSnowflakeId("bad prefix!", existing), `bad_prefix__${existing}`);
43
+
44
+ const generated = snowflakeIdForOperation({ createdAt: 1000, actor: { deviceId: "device_a" }, id: "sha256:abc" });
45
+ assert.equal(isSnowflakeId(generated), true);
46
+ });
47
+
48
+ test("snowflake helpers reject invalid input and expose timestamp bounds", () => {
49
+ assert.equal(isSnowflakeId(null), false);
50
+ assert.equal(isSnowflakeId(""), false);
51
+ assert.equal(isSnowflakeId("01"), false);
52
+ assert.equal(isSnowflakeId("-1"), false);
53
+ assert.equal(isSnowflakeId("9223372036854775808"), false);
54
+ assert.equal(parseSnowflakeId("not-a-snowflake"), undefined);
55
+ assert.equal(snowflakeTimestamp("not-a-snowflake"), undefined);
56
+
57
+ assert.throws(() => snowflakeIdFromParts({ timestampMs: DEFAULT_SNOWFLAKE_EPOCH - 1 }), /before the Snowflake epoch/);
58
+ assert.throws(() => snowflakeIdFromParts({ timestampMs: "bad" }), /timestampMs must be an integer/);
59
+ assert.throws(() => stableSnowflakeNodeId(1024), /nodeId must be between/);
60
+ assert.throws(() => stableSnowflakeSequence(4096), /sequence must be between/);
61
+
62
+ const bounds = snowflakeBoundsForTimestamp(DEFAULT_SNOWFLAKE_EPOCH + 10);
63
+ assert.equal(isSnowflakeId(bounds.min), true);
64
+ assert.equal(isSnowflakeId(bounds.max), true);
65
+ assert.equal(compareSnowflakeIds(bounds.min, bounds.max), -1);
66
+ });
67
+
68
+ test("snowflake comparisons fall back predictably when ids are mixed", () => {
69
+ const first = snowflakeIdFromTimestamp(DEFAULT_SNOWFLAKE_EPOCH + 10_000_000, { nodeId: 1 });
70
+ const second = snowflakeIdFromTimestamp(DEFAULT_SNOWFLAKE_EPOCH + 10_000_001, { nodeId: 1 });
71
+
72
+ assert.equal(extractSnowflakeId(`task_${first}`), first);
73
+ assert.equal(extractSnowflakeId("task_without_id"), undefined);
74
+ assert.equal(compareEntityIdsBySnowflake(`task_${first}`, `task_${second}`), -1);
75
+ assert.equal(compareEntityIdsBySnowflake(`task_${first}`, "plain"), -1);
76
+ assert.equal(compareEntityIdsBySnowflake("plain", `task_${second}`), 1);
77
+ assert.equal(compareEntityIdsBySnowflake("alpha", "beta"), -1);
78
+ assert.equal(compareMaybeSnowflakeIds(first, second), -1);
79
+ assert.equal(compareMaybeSnowflakeIds("beta", "alpha"), 1);
80
+ assert.equal(compareMaybeSnowflakeIds("", ""), 0);
81
+ });
82
+
83
+ test("snowflake defaults and malformed values take documented fallback paths", () => {
84
+ const generated = snowflakeIdFromParts();
85
+ assert.equal(isSnowflakeId(generated), true);
86
+ assert.equal(isSnowflakeId({}), false);
87
+ assert.equal(parseSnowflakeId({}), undefined);
88
+ assert.equal(stableSnowflakeNodeId(""), stableSnowflakeNodeId(""));
89
+ assert.equal(stableSnowflakeSequence(""), stableSnowflakeSequence(""));
90
+ assert.equal(compareMaybeSnowflakeIds("same", "same"), 0);
91
+ assert.equal(compareEntityIdsBySnowflake("same", "same"), 0);
92
+ assert.equal(prefixedSnowflakeId("", undefined).startsWith("id_"), true);
93
+
94
+ const fromRoom = snowflakeIdForOperation({ createdAt: DEFAULT_SNOWFLAKE_EPOCH + 5, roomId: "room-a", type: "messageSend" });
95
+ const fromFallback = snowflakeIdForOperation({ createdAt: DEFAULT_SNOWFLAKE_EPOCH + 5 });
96
+ assert.equal(isSnowflakeId(fromRoom), true);
97
+ assert.equal(isSnowflakeId(fromFallback), true);
98
+ });
99
+
100
+ test("snowflake generator handles clock rollback and sequence rollover", () => {
101
+ let now = DEFAULT_SNOWFLAKE_EPOCH + 20;
102
+ const generator = createSnowflakeGenerator({ nodeId: "node-a", now: () => now });
103
+ const first = generator.next();
104
+ now -= 10;
105
+ const rollback = generator.next();
106
+ assert.equal(generator.inspect(first).timestampMs, DEFAULT_SNOWFLAKE_EPOCH + 20);
107
+ assert.equal(generator.inspect(rollback).timestampMs, DEFAULT_SNOWFLAKE_EPOCH + 20);
108
+ assert.equal(compareSnowflakeIds(first, rollback), -1);
109
+
110
+ let rollover = rollback;
111
+ for (let index = 0; index < SNOWFLAKE_MAX_SEQUENCE; index += 1) rollover = generator.next(DEFAULT_SNOWFLAKE_EPOCH + 20);
112
+ const inspected = generator.inspect(rollover);
113
+ assert.equal(inspected.timestampMs, DEFAULT_SNOWFLAKE_EPOCH + 21);
114
+ assert.equal(generator.fromTimestamp(DEFAULT_SNOWFLAKE_EPOCH + 30, 7), snowflakeIdFromParts({
115
+ timestampMs: DEFAULT_SNOWFLAKE_EPOCH + 30,
116
+ nodeId: generator.nodeId,
117
+ sequence: 7
118
+ }));
119
+ });