@mikeargento/bitgraph 1.9.1 → 1.9.2

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 (239) hide show
  1. package/package.json +9 -6
  2. package/dist/__tests__/audit-anchors.test.d.ts +0 -2
  3. package/dist/__tests__/audit-anchors.test.d.ts.map +0 -1
  4. package/dist/__tests__/audit-anchors.test.js +0 -152
  5. package/dist/__tests__/audit-anchors.test.js.map +0 -1
  6. package/dist/__tests__/audit-anomalies.test.d.ts +0 -2
  7. package/dist/__tests__/audit-anomalies.test.d.ts.map +0 -1
  8. package/dist/__tests__/audit-anomalies.test.js +0 -663
  9. package/dist/__tests__/audit-anomalies.test.js.map +0 -1
  10. package/dist/__tests__/audit-attestation.test.d.ts +0 -2
  11. package/dist/__tests__/audit-attestation.test.d.ts.map +0 -1
  12. package/dist/__tests__/audit-attestation.test.js +0 -403
  13. package/dist/__tests__/audit-attestation.test.js.map +0 -1
  14. package/dist/__tests__/audit-bench-smoke.test.d.ts +0 -2
  15. package/dist/__tests__/audit-bench-smoke.test.d.ts.map +0 -1
  16. package/dist/__tests__/audit-bench-smoke.test.js +0 -87
  17. package/dist/__tests__/audit-bench-smoke.test.js.map +0 -1
  18. package/dist/__tests__/audit-boundary.test.d.ts +0 -20
  19. package/dist/__tests__/audit-boundary.test.d.ts.map +0 -1
  20. package/dist/__tests__/audit-boundary.test.js +0 -84
  21. package/dist/__tests__/audit-boundary.test.js.map +0 -1
  22. package/dist/__tests__/audit-cli.test.d.ts +0 -2
  23. package/dist/__tests__/audit-cli.test.d.ts.map +0 -1
  24. package/dist/__tests__/audit-cli.test.js +0 -239
  25. package/dist/__tests__/audit-cli.test.js.map +0 -1
  26. package/dist/__tests__/audit-dependencies.test.d.ts +0 -2
  27. package/dist/__tests__/audit-dependencies.test.d.ts.map +0 -1
  28. package/dist/__tests__/audit-dependencies.test.js +0 -116
  29. package/dist/__tests__/audit-dependencies.test.js.map +0 -1
  30. package/dist/__tests__/audit-fixtures.d.ts +0 -265
  31. package/dist/__tests__/audit-fixtures.d.ts.map +0 -1
  32. package/dist/__tests__/audit-fixtures.js +0 -511
  33. package/dist/__tests__/audit-fixtures.js.map +0 -1
  34. package/dist/__tests__/audit-ingest.test.d.ts +0 -2
  35. package/dist/__tests__/audit-ingest.test.d.ts.map +0 -1
  36. package/dist/__tests__/audit-ingest.test.js +0 -473
  37. package/dist/__tests__/audit-ingest.test.js.map +0 -1
  38. package/dist/__tests__/audit-memory-ingest.test.d.ts +0 -12
  39. package/dist/__tests__/audit-memory-ingest.test.d.ts.map +0 -1
  40. package/dist/__tests__/audit-memory-ingest.test.js +0 -135
  41. package/dist/__tests__/audit-memory-ingest.test.js.map +0 -1
  42. package/dist/__tests__/audit-property.test.d.ts +0 -2
  43. package/dist/__tests__/audit-property.test.d.ts.map +0 -1
  44. package/dist/__tests__/audit-property.test.js +0 -771
  45. package/dist/__tests__/audit-property.test.js.map +0 -1
  46. package/dist/__tests__/audit-real-fixtures.test.d.ts +0 -2
  47. package/dist/__tests__/audit-real-fixtures.test.d.ts.map +0 -1
  48. package/dist/__tests__/audit-real-fixtures.test.js +0 -132
  49. package/dist/__tests__/audit-real-fixtures.test.js.map +0 -1
  50. package/dist/__tests__/audit-real-live.test.d.ts +0 -2
  51. package/dist/__tests__/audit-real-live.test.d.ts.map +0 -1
  52. package/dist/__tests__/audit-real-live.test.js +0 -107
  53. package/dist/__tests__/audit-real-live.test.js.map +0 -1
  54. package/dist/__tests__/audit-reconstruct.test.d.ts +0 -2
  55. package/dist/__tests__/audit-reconstruct.test.d.ts.map +0 -1
  56. package/dist/__tests__/audit-reconstruct.test.js +0 -287
  57. package/dist/__tests__/audit-reconstruct.test.js.map +0 -1
  58. package/dist/__tests__/audit-report.test.d.ts +0 -2
  59. package/dist/__tests__/audit-report.test.d.ts.map +0 -1
  60. package/dist/__tests__/audit-report.test.js +0 -186
  61. package/dist/__tests__/audit-report.test.js.map +0 -1
  62. package/dist/__tests__/audit-roundtrip.test.d.ts +0 -2
  63. package/dist/__tests__/audit-roundtrip.test.d.ts.map +0 -1
  64. package/dist/__tests__/audit-roundtrip.test.js +0 -368
  65. package/dist/__tests__/audit-roundtrip.test.js.map +0 -1
  66. package/dist/__tests__/audit-temporal.test.d.ts +0 -2
  67. package/dist/__tests__/audit-temporal.test.d.ts.map +0 -1
  68. package/dist/__tests__/audit-temporal.test.js +0 -364
  69. package/dist/__tests__/audit-temporal.test.js.map +0 -1
  70. package/dist/__tests__/audit-tiers.test.d.ts +0 -2
  71. package/dist/__tests__/audit-tiers.test.d.ts.map +0 -1
  72. package/dist/__tests__/audit-tiers.test.js +0 -282
  73. package/dist/__tests__/audit-tiers.test.js.map +0 -1
  74. package/dist/__tests__/audit-witness-real.test.d.ts +0 -19
  75. package/dist/__tests__/audit-witness-real.test.d.ts.map +0 -1
  76. package/dist/__tests__/audit-witness-real.test.js +0 -82
  77. package/dist/__tests__/audit-witness-real.test.js.map +0 -1
  78. package/dist/__tests__/audit-witness.test.d.ts +0 -2
  79. package/dist/__tests__/audit-witness.test.d.ts.map +0 -1
  80. package/dist/__tests__/audit-witness.test.js +0 -308
  81. package/dist/__tests__/audit-witness.test.js.map +0 -1
  82. package/dist/__tests__/canonical.test.d.ts +0 -2
  83. package/dist/__tests__/canonical.test.d.ts.map +0 -1
  84. package/dist/__tests__/canonical.test.js +0 -162
  85. package/dist/__tests__/canonical.test.js.map +0 -1
  86. package/dist/__tests__/chain-hash-anchor.test.d.ts +0 -22
  87. package/dist/__tests__/chain-hash-anchor.test.d.ts.map +0 -1
  88. package/dist/__tests__/chain-hash-anchor.test.js +0 -55
  89. package/dist/__tests__/chain-hash-anchor.test.js.map +0 -1
  90. package/dist/__tests__/commit-service.integration.test.d.ts +0 -2
  91. package/dist/__tests__/commit-service.integration.test.d.ts.map +0 -1
  92. package/dist/__tests__/commit-service.integration.test.js +0 -95
  93. package/dist/__tests__/commit-service.integration.test.js.map +0 -1
  94. package/dist/__tests__/constructor.test.d.ts +0 -2
  95. package/dist/__tests__/constructor.test.d.ts.map +0 -1
  96. package/dist/__tests__/constructor.test.js +0 -267
  97. package/dist/__tests__/constructor.test.js.map +0 -1
  98. package/dist/__tests__/declared-proof-fixture.d.ts +0 -66
  99. package/dist/__tests__/declared-proof-fixture.d.ts.map +0 -1
  100. package/dist/__tests__/declared-proof-fixture.js +0 -66
  101. package/dist/__tests__/declared-proof-fixture.js.map +0 -1
  102. package/dist/__tests__/fuse-inline.test.d.ts +0 -2
  103. package/dist/__tests__/fuse-inline.test.d.ts.map +0 -1
  104. package/dist/__tests__/fuse-inline.test.js +0 -181
  105. package/dist/__tests__/fuse-inline.test.js.map +0 -1
  106. package/dist/__tests__/fuse-placement.test.d.ts +0 -2
  107. package/dist/__tests__/fuse-placement.test.d.ts.map +0 -1
  108. package/dist/__tests__/fuse-placement.test.js +0 -31
  109. package/dist/__tests__/fuse-placement.test.js.map +0 -1
  110. package/dist/__tests__/fuse-roundtrip.test.d.ts +0 -2
  111. package/dist/__tests__/fuse-roundtrip.test.d.ts.map +0 -1
  112. package/dist/__tests__/fuse-roundtrip.test.js +0 -241
  113. package/dist/__tests__/fuse-roundtrip.test.js.map +0 -1
  114. package/dist/__tests__/fuse-sdk.test.d.ts +0 -2
  115. package/dist/__tests__/fuse-sdk.test.d.ts.map +0 -1
  116. package/dist/__tests__/fuse-sdk.test.js +0 -117
  117. package/dist/__tests__/fuse-sdk.test.js.map +0 -1
  118. package/dist/__tests__/fuse-set-sdk.test.d.ts +0 -2
  119. package/dist/__tests__/fuse-set-sdk.test.d.ts.map +0 -1
  120. package/dist/__tests__/fuse-set-sdk.test.js +0 -721
  121. package/dist/__tests__/fuse-set-sdk.test.js.map +0 -1
  122. package/dist/__tests__/fuse-set.test.d.ts +0 -2
  123. package/dist/__tests__/fuse-set.test.d.ts.map +0 -1
  124. package/dist/__tests__/fuse-set.test.js +0 -889
  125. package/dist/__tests__/fuse-set.test.js.map +0 -1
  126. package/dist/__tests__/fuse-set2.test.d.ts +0 -2
  127. package/dist/__tests__/fuse-set2.test.d.ts.map +0 -1
  128. package/dist/__tests__/fuse-set2.test.js +0 -294
  129. package/dist/__tests__/fuse-set2.test.js.map +0 -1
  130. package/dist/__tests__/fuse-vectors.test.d.ts +0 -2
  131. package/dist/__tests__/fuse-vectors.test.d.ts.map +0 -1
  132. package/dist/__tests__/fuse-vectors.test.js +0 -175
  133. package/dist/__tests__/fuse-vectors.test.js.map +0 -1
  134. package/dist/__tests__/fuse-verify.test.d.ts +0 -2
  135. package/dist/__tests__/fuse-verify.test.d.ts.map +0 -1
  136. package/dist/__tests__/fuse-verify.test.js +0 -250
  137. package/dist/__tests__/fuse-verify.test.js.map +0 -1
  138. package/dist/__tests__/hello-world.test.d.ts +0 -2
  139. package/dist/__tests__/hello-world.test.d.ts.map +0 -1
  140. package/dist/__tests__/hello-world.test.js +0 -9
  141. package/dist/__tests__/hello-world.test.js.map +0 -1
  142. package/dist/__tests__/mock-proof-fixture.d.ts +0 -15
  143. package/dist/__tests__/mock-proof-fixture.d.ts.map +0 -1
  144. package/dist/__tests__/mock-proof-fixture.js +0 -34
  145. package/dist/__tests__/mock-proof-fixture.js.map +0 -1
  146. package/dist/__tests__/policy-enforcement.test.d.ts +0 -2
  147. package/dist/__tests__/policy-enforcement.test.d.ts.map +0 -1
  148. package/dist/__tests__/policy-enforcement.test.js +0 -188
  149. package/dist/__tests__/policy-enforcement.test.js.map +0 -1
  150. package/dist/__tests__/proof-hash-regression.test.d.ts +0 -2
  151. package/dist/__tests__/proof-hash-regression.test.d.ts.map +0 -1
  152. package/dist/__tests__/proof-hash-regression.test.js +0 -95
  153. package/dist/__tests__/proof-hash-regression.test.js.map +0 -1
  154. package/dist/__tests__/proof-hash.test.d.ts +0 -2
  155. package/dist/__tests__/proof-hash.test.d.ts.map +0 -1
  156. package/dist/__tests__/proof-hash.test.js +0 -116
  157. package/dist/__tests__/proof-hash.test.js.map +0 -1
  158. package/dist/__tests__/proof-integrity.test.d.ts +0 -2
  159. package/dist/__tests__/proof-integrity.test.d.ts.map +0 -1
  160. package/dist/__tests__/proof-integrity.test.js +0 -454
  161. package/dist/__tests__/proof-integrity.test.js.map +0 -1
  162. package/dist/__tests__/realistic-proof-fixture.d.ts +0 -56
  163. package/dist/__tests__/realistic-proof-fixture.d.ts.map +0 -1
  164. package/dist/__tests__/realistic-proof-fixture.js +0 -57
  165. package/dist/__tests__/realistic-proof-fixture.js.map +0 -1
  166. package/dist/__tests__/run.test.d.ts +0 -2
  167. package/dist/__tests__/run.test.d.ts.map +0 -1
  168. package/dist/__tests__/run.test.js +0 -178
  169. package/dist/__tests__/run.test.js.map +0 -1
  170. package/dist/__tests__/verifier.test.d.ts +0 -2
  171. package/dist/__tests__/verifier.test.d.ts.map +0 -1
  172. package/dist/__tests__/verifier.test.js +0 -531
  173. package/dist/__tests__/verifier.test.js.map +0 -1
  174. package/dist/canonical.d.ts +0 -54
  175. package/dist/canonical.d.ts.map +0 -1
  176. package/dist/canonical.js +0 -119
  177. package/dist/canonical.js.map +0 -1
  178. package/dist/proof-hash.d.ts +0 -9
  179. package/dist/proof-hash.d.ts.map +0 -1
  180. package/dist/proof-hash.js +0 -75
  181. package/dist/proof-hash.js.map +0 -1
  182. package/dist/types.d.ts +0 -705
  183. package/dist/types.d.ts.map +0 -1
  184. package/dist/types.js +0 -3
  185. package/dist/types.js.map +0 -1
  186. package/dist/verifier.d.ts +0 -27
  187. package/dist/verifier.d.ts.map +0 -1
  188. package/dist/verifier.js +0 -844
  189. package/dist/verifier.js.map +0 -1
  190. package/src/__tests__/audit-anchors.test.ts +0 -188
  191. package/src/__tests__/audit-anomalies.test.ts +0 -815
  192. package/src/__tests__/audit-attestation.test.ts +0 -529
  193. package/src/__tests__/audit-bench-smoke.test.ts +0 -108
  194. package/src/__tests__/audit-boundary.test.ts +0 -96
  195. package/src/__tests__/audit-cli.test.ts +0 -288
  196. package/src/__tests__/audit-dependencies.test.ts +0 -141
  197. package/src/__tests__/audit-fixtures.ts +0 -736
  198. package/src/__tests__/audit-ingest.test.ts +0 -575
  199. package/src/__tests__/audit-memory-ingest.test.ts +0 -156
  200. package/src/__tests__/audit-property.test.ts +0 -892
  201. package/src/__tests__/audit-real-fixtures.test.ts +0 -153
  202. package/src/__tests__/audit-real-live.test.ts +0 -128
  203. package/src/__tests__/audit-reconstruct.test.ts +0 -353
  204. package/src/__tests__/audit-report.test.ts +0 -222
  205. package/src/__tests__/audit-roundtrip.test.ts +0 -464
  206. package/src/__tests__/audit-temporal.test.ts +0 -446
  207. package/src/__tests__/audit-tiers.test.ts +0 -343
  208. package/src/__tests__/audit-witness-real.test.ts +0 -92
  209. package/src/__tests__/audit-witness.test.ts +0 -345
  210. package/src/__tests__/canonical.test.ts +0 -253
  211. package/src/__tests__/chain-hash-anchor.test.ts +0 -67
  212. package/src/__tests__/commit-service.integration.test.ts +0 -117
  213. package/src/__tests__/constructor.test.ts +0 -340
  214. package/src/__tests__/declared-proof-fixture.ts +0 -65
  215. package/src/__tests__/fuse-inline.test.ts +0 -211
  216. package/src/__tests__/fuse-placement.test.ts +0 -32
  217. package/src/__tests__/fuse-roundtrip.test.ts +0 -243
  218. package/src/__tests__/fuse-sdk.test.ts +0 -142
  219. package/src/__tests__/fuse-set-sdk.test.ts +0 -852
  220. package/src/__tests__/fuse-set.test.ts +0 -1027
  221. package/src/__tests__/fuse-set2.test.ts +0 -343
  222. package/src/__tests__/fuse-vectors.test.ts +0 -218
  223. package/src/__tests__/fuse-verify.test.ts +0 -278
  224. package/src/__tests__/hello-world.test.ts +0 -10
  225. package/src/__tests__/mock-proof-fixture.ts +0 -36
  226. package/src/__tests__/policy-enforcement.test.ts +0 -226
  227. package/src/__tests__/proof-hash-regression.test.ts +0 -107
  228. package/src/__tests__/proof-hash.test.ts +0 -129
  229. package/src/__tests__/proof-integrity.test.ts +0 -576
  230. package/src/__tests__/real-fixtures/anchor-after-112762.json +0 -47
  231. package/src/__tests__/real-fixtures/anchor-with-ethereum-112758.json +0 -47
  232. package/src/__tests__/real-fixtures/live-proof-0.json +0 -42
  233. package/src/__tests__/real-fixtures/live-proof-1.json +0 -43
  234. package/src/__tests__/real-fixtures/live-proof-2.json +0 -43
  235. package/src/__tests__/real-fixtures/successor-112760.json +0 -38
  236. package/src/__tests__/real-fixtures/witness-25525146.json +0 -6
  237. package/src/__tests__/real-fixtures/witness-25525147.json +0 -6
  238. package/src/__tests__/realistic-proof-fixture.ts +0 -57
  239. package/src/__tests__/verifier.test.ts +0 -624
@@ -1,663 +0,0 @@
1
- // Copyright (c) Argento Computing Inc. All rights reserved. See LICENSE.
2
- /**
3
- * Tests for @mikeargento/bitgraph-audit anomaly classification and
4
- * authority analysis: the G2 two-position gap logic, collisions, forks,
5
- * chain breaks, multiple genesis, slot ordering, epochLink anomalies,
6
- * divergence records (valid parties versus invalid observed context),
7
- * and intra-epoch authority changes.
8
- */
9
- import { describe, test, after } from "node:test";
10
- import * as assert from "node:assert/strict";
11
- import { rm } from "node:fs/promises";
12
- import { sha256 } from "@noble/hashes/sha256";
13
- import { analyzeAuthorities, classifyAnomalies, ingestBundle, reconstructChains, verifyObservedProofs, } from "@mikeargento/bitgraph-audit";
14
- import { computeProofHash, computeChainHash } from "@mikeargento/bitgraph-verify";
15
- import { b64, healthyPairs, makeCounterChain, makeEpochLinkProof, makeKey, makeTempDir, proofJson, signBody, utf8, writeBundleDir, } from "./audit-fixtures.js";
16
- const tempDirs = [];
17
- after(async () => {
18
- for (const dir of tempDirs) {
19
- await rm(dir, { recursive: true, force: true });
20
- }
21
- });
22
- async function auditBundle(files) {
23
- const dir = await makeTempDir("bitgraph-audit-anomalies-");
24
- tempDirs.push(dir);
25
- await writeBundleDir(dir, files);
26
- const ingest = await ingestBundle(dir);
27
- await verifyObservedProofs(ingest);
28
- const reconstruction = await reconstructChains(ingest);
29
- const report = await classifyAnomalies(ingest, reconstruction);
30
- return { ingest, reconstruction, report };
31
- }
32
- function codes(report) {
33
- return report.anomalies.map((a) => a.code).sort();
34
- }
35
- // ---------------------------------------------------------------------------
36
- // G2 gap logic
37
- // ---------------------------------------------------------------------------
38
- describe("anomalies: G2 gap logic", () => {
39
- test("interleaved slot allocation is healthy: no unexplained positions", async () => {
40
- // Concurrent slot allocation: slot 1 and slot 2 are both taken before
41
- // either commit lands (commits at 3 and 4). M is not N+1 here, and
42
- // that is still a complete record.
43
- const chain = await makeCounterChain({
44
- epochId: "epoch-interleaved",
45
- pairs: [
46
- { slot: "1", commit: "3" },
47
- { slot: "2", commit: "4" },
48
- ],
49
- });
50
- const { report } = await auditBundle({
51
- "p0.json": proofJson(chain.proofs[0].proof),
52
- "p1.json": proofJson(chain.proofs[1].proof),
53
- });
54
- assert.deepEqual(report.anomalies, []);
55
- });
56
- test("a missing middle proof yields exactly its slot and commit positions plus a chain break", async () => {
57
- const chain = await makeCounterChain({ epochId: "epoch-gap2", pairs: healthyPairs(3) });
58
- // Drop the middle proof (slot 3 / commit 4).
59
- const { report } = await auditBundle({
60
- "p0.json": proofJson(chain.proofs[0].proof),
61
- "p2.json": proofJson(chain.proofs[2].proof),
62
- });
63
- assert.deepEqual(codes(report), ["chain-break-missing", "unexplained-counter-positions"]);
64
- const gap = report.anomalies.find((a) => a.code === "unexplained-counter-positions");
65
- const detail = gap.details;
66
- assert.deepEqual(detail.positions, ["3", "4"]);
67
- assert.equal(detail.count, "2");
68
- assert.deepEqual(detail.ranges, [{ start: "3", end: "4" }]);
69
- assert.equal(detail.truncated, false);
70
- // Report language: absence from the bundle, never asserted authority failure.
71
- assert.match(gap.message, /absent\s+from the bundle/);
72
- assert.match(gap.message, /does not, by itself, establish/);
73
- // The message must NOT presume a proof exists: an uncommitted slot is a
74
- // routine, benign alternative the offline audit cannot rule out.
75
- assert.match(gap.message, /allocated but never committed/);
76
- assert.match(gap.message, /failed to create or withheld any proof/);
77
- const chainBreak = report.anomalies.find((a) => a.code === "chain-break-missing");
78
- assert.deepEqual(chainBreak.proofHashes, [chain.proofs[2].proofHash]);
79
- assert.equal(chainBreak.details["prevB64"], chain.proofs[1].chainHash);
80
- assert.deepEqual(report.divergences, []);
81
- });
82
- test("a partition missing its head is an expected boundary, not an anomaly", async () => {
83
- const chain = await makeCounterChain({ epochId: "epoch-headless", pairs: healthyPairs(3) });
84
- // Only the last two proofs are in the bundle: positions 3,4,5,6.
85
- const { report } = await auditBundle({
86
- "p1.json": proofJson(chain.proofs[1].proof),
87
- "p2.json": proofJson(chain.proofs[2].proof),
88
- });
89
- // Positions 1 and 2 are outside the observed [min, max] range and are not
90
- // flagged. The earliest included proof's dangling prevB64 is the excerpt's
91
- // starting boundary — an expected bundle frontier, not a chain-integrity
92
- // anomaly — so there are no anomalies at all.
93
- assert.deepEqual(codes(report), []);
94
- assert.equal(report.boundaryEntryPoints.length, 1);
95
- assert.equal(report.boundaryEntryPoints[0].proofHash, chain.proofs[1].proofHash);
96
- assert.equal(report.boundaryEntryPoints[0].prevB64, chain.proofs[0].chainHash);
97
- });
98
- });
99
- // ---------------------------------------------------------------------------
100
- // Collisions and divergence
101
- // ---------------------------------------------------------------------------
102
- describe("anomalies: collisions", () => {
103
- test("a commit counter collision between valid proofs produces a divergence with all parties", async () => {
104
- const key = await makeKey();
105
- const chain = await makeCounterChain({
106
- key,
107
- epochId: "epoch-collision",
108
- pairs: healthyPairs(2), // p0: 1/2, p1: 3/4
109
- });
110
- // A second valid proof also claiming commit counter 4, linked after p1.
111
- const rival = await makeCounterChain({
112
- key,
113
- epochId: "epoch-collision",
114
- pairs: [{ commit: "4" }],
115
- payloadPrefix: "rival",
116
- });
117
- const rivalProof = JSON.parse(proofJson(rival.proofs[0].proof));
118
- rivalProof["commit"]["prevB64"] = chain.proofs[1].chainHash;
119
- // Re-sign with the same key over the modified commit.
120
- const resigned = await signBody(key, rival.proofs[0].proof.artifact, rivalProof["commit"], "test-measurement-chain");
121
- const { report } = await auditBundle({
122
- "p0.json": proofJson(chain.proofs[0].proof),
123
- "p1.json": proofJson(chain.proofs[1].proof),
124
- "p2.json": proofJson(resigned),
125
- });
126
- assert.deepEqual(codes(report), ["counter-collision"]);
127
- assert.equal(report.divergences.length, 1);
128
- const divergence = report.divergences[0];
129
- assert.equal(divergence.kind, "counter-collision");
130
- assert.deepEqual(divergence.contested, { counter: "4" });
131
- assert.equal(divergence.parties.length, 2);
132
- assert.deepEqual(divergence.invalidContext, []);
133
- assert.deepEqual(divergence.parties.map((p) => p.proofHash).sort(), [chain.proofs[1].proofHash, computeProofHash(resigned)].sort());
134
- // Every party keeps its verification status; no winner is selected.
135
- for (const party of divergence.parties) {
136
- assert.equal(party.verificationStatus, "artifact-unavailable");
137
- assert.equal(party.counter, "4");
138
- }
139
- assert.match(divergence.explanation, /does not choose/);
140
- });
141
- test("a slot counter collision between valid proofs is classified separately", async () => {
142
- const key = await makeKey();
143
- const chain = await makeCounterChain({
144
- key,
145
- epochId: "epoch-slot-collision",
146
- pairs: [
147
- { slot: "1", commit: "2" },
148
- { slot: "3", commit: "4" },
149
- ],
150
- });
151
- // A third proof consuming slot 3 again with a later commit counter.
152
- const rival = await makeCounterChain({
153
- key,
154
- epochId: "epoch-slot-collision",
155
- pairs: [{ slot: "3", commit: "5" }],
156
- payloadPrefix: "slot-rival",
157
- });
158
- const rivalCommit = JSON.parse(proofJson(rival.proofs[0].proof));
159
- rivalCommit["commit"]["prevB64"] = chain.proofs[1].chainHash;
160
- const resigned = await signBody(key, rival.proofs[0].proof.artifact, rivalCommit["commit"], "test-measurement-chain");
161
- const { report } = await auditBundle({
162
- "p0.json": proofJson(chain.proofs[0].proof),
163
- "p1.json": proofJson(chain.proofs[1].proof),
164
- "p2.json": proofJson(resigned),
165
- });
166
- assert.deepEqual(codes(report), ["slot-collision"]);
167
- const divergence = report.divergences[0];
168
- assert.equal(divergence.kind, "slot-collision");
169
- assert.deepEqual(divergence.contested, { slotCounter: "3" });
170
- assert.equal(divergence.parties.length, 2);
171
- });
172
- test("an invalid proof sharing a counter is observed context, never a competing branch", async () => {
173
- const key = await makeKey();
174
- const chain = await makeCounterChain({
175
- key,
176
- epochId: "epoch-invalid-context",
177
- pairs: healthyPairs(2), // p1 has commit 4
178
- });
179
- // A second VALID claimant of commit counter 4.
180
- const rivalValid = await makeCounterChain({
181
- key,
182
- epochId: "epoch-invalid-context",
183
- pairs: [{ commit: "4" }],
184
- payloadPrefix: "valid-rival",
185
- });
186
- const validCommit = JSON.parse(proofJson(rivalValid.proofs[0].proof));
187
- validCommit["commit"]["prevB64"] = chain.proofs[1].chainHash;
188
- const resignedValid = await signBody(key, rivalValid.proofs[0].proof.artifact, validCommit["commit"], "test-measurement-chain");
189
- // An INVALID claimant: distinct content, garbage signature.
190
- const rivalInvalid = await makeCounterChain({
191
- key,
192
- epochId: "epoch-invalid-context",
193
- pairs: [{ commit: "4" }],
194
- payloadPrefix: "invalid-rival",
195
- });
196
- const forged = JSON.parse(proofJson(rivalInvalid.proofs[0].proof));
197
- forged.signer.signatureB64 = b64(new Uint8Array(64)); // not a valid signature
198
- const { report } = await auditBundle({
199
- "p0.json": proofJson(chain.proofs[0].proof),
200
- "p1.json": proofJson(chain.proofs[1].proof),
201
- "valid-rival.json": proofJson(resignedValid),
202
- "forged.json": JSON.stringify(forged),
203
- });
204
- const divergence = report.divergences.find((d) => d.kind === "counter-collision");
205
- assert.equal(divergence.parties.length, 2);
206
- assert.equal(divergence.invalidContext.length, 1);
207
- assert.equal(divergence.invalidContext[0].proofHash, computeProofHash(forged));
208
- assert.equal(divergence.invalidContext[0].verificationStatus, "failed");
209
- assert.ok(!divergence.parties.some((p) => p.proofHash === computeProofHash(forged)), "the forged proof must never appear as a valid competing branch");
210
- });
211
- });
212
- // ---------------------------------------------------------------------------
213
- // Cross-kind position reuse
214
- // ---------------------------------------------------------------------------
215
- describe("anomalies: cross-kind position reuse", () => {
216
- test("one proof's commit counter equal to a different proof's slot counter is a double-allocation", async () => {
217
- // Same signer, epoch, and chain. p0 commits position 5; p1 reserves
218
- // position 5 as its slot. Positions stay contiguous (4,5,6) so no gap
219
- // is produced, isolating the cross-kind detection.
220
- const chain = await makeCounterChain({
221
- epochId: "epoch-cross-kind",
222
- pairs: [
223
- { slot: "4", commit: "5" },
224
- { slot: "5", commit: "6" },
225
- ],
226
- });
227
- const { report } = await auditBundle({
228
- "p0.json": proofJson(chain.proofs[0].proof),
229
- "p1.json": proofJson(chain.proofs[1].proof),
230
- });
231
- assert.deepEqual(codes(report), ["cross-kind-position-reuse"]);
232
- const anomaly = report.anomalies[0];
233
- assert.equal(anomaly.details["position"], "5");
234
- assert.equal(report.divergences.length, 1);
235
- const divergence = report.divergences[0];
236
- assert.equal(divergence.kind, "cross-kind-position-reuse");
237
- assert.deepEqual(divergence.contested, { position: "5" });
238
- assert.equal(divergence.parties.length, 2);
239
- assert.deepEqual(divergence.parties.map((p) => p.proofHash).sort(), [chain.proofs[0].proofHash, chain.proofs[1].proofHash].sort());
240
- assert.deepEqual(divergence.invalidContext, []);
241
- assert.match(divergence.explanation, /does not choose/);
242
- });
243
- test("a single proof whose own slot equals its own commit stays slot-order, never cross-kind", async () => {
244
- const chain = await makeCounterChain({
245
- epochId: "epoch-self-position",
246
- pairs: [{ slot: "5", commit: "5" }],
247
- });
248
- const { report } = await auditBundle({ "p.json": proofJson(chain.proofs[0].proof) });
249
- assert.deepEqual(codes(report), ["slot-order-violation"]);
250
- });
251
- });
252
- // ---------------------------------------------------------------------------
253
- // Predecessor reuse (forks)
254
- // ---------------------------------------------------------------------------
255
- describe("anomalies: predecessor reuse", () => {
256
- test("one predecessor claimed by two valid successors is a fork with all branches preserved", async () => {
257
- const key = await makeKey();
258
- const chain = await makeCounterChain({
259
- key,
260
- epochId: "epoch-fork",
261
- pairs: healthyPairs(2), // p0 (1/2) <- p1 (3/4)
262
- });
263
- // A second successor of p0 at different positions (5/6).
264
- const branch = await makeCounterChain({
265
- key,
266
- epochId: "epoch-fork",
267
- pairs: [{ slot: "5", commit: "6" }],
268
- payloadPrefix: "branch",
269
- });
270
- const branchCommit = JSON.parse(proofJson(branch.proofs[0].proof));
271
- branchCommit["commit"]["prevB64"] = chain.proofs[0].chainHash;
272
- const resigned = await signBody(key, branch.proofs[0].proof.artifact, branchCommit["commit"], "test-measurement-chain");
273
- const { reconstruction, report } = await auditBundle({
274
- "p0.json": proofJson(chain.proofs[0].proof),
275
- "p1.json": proofJson(chain.proofs[1].proof),
276
- "branch.json": proofJson(resigned),
277
- });
278
- // One component: the fork is connected through the shared predecessor.
279
- const partition = reconstruction.partitions[0];
280
- assert.equal(partition.components.length, 1);
281
- const component = partition.components[0];
282
- assert.deepEqual(component.terminalProofHashes.length, 2);
283
- assert.deepEqual(component.genesisProofHashes, [chain.proofs[0].proofHash]);
284
- // Positions 1..6 all explained: the fork itself creates no gap.
285
- assert.deepEqual(codes(report), ["predecessor-reuse"]);
286
- const divergence = report.divergences[0];
287
- assert.equal(divergence.kind, "predecessor-reuse");
288
- assert.deepEqual(divergence.contested, { prevB64: chain.proofs[0].chainHash });
289
- assert.equal(divergence.parties.length, 2);
290
- assert.deepEqual(divergence.invalidContext, []);
291
- assert.match(divergence.explanation, /fork/);
292
- });
293
- });
294
- // ---------------------------------------------------------------------------
295
- // Chain breaks
296
- // ---------------------------------------------------------------------------
297
- describe("anomalies: chain breaks", () => {
298
- test("a malformed prevB64 is distinguished from a missing predecessor", async () => {
299
- const key = await makeKey();
300
- const first = await makeCounterChain({
301
- key,
302
- epochId: "epoch-malformed",
303
- pairs: [{ slot: "1", commit: "2" }],
304
- });
305
- const badCommit = {
306
- nonceB64: b64(crypto.getRandomValues(new Uint8Array(16))),
307
- counter: "4",
308
- slotCounter: "3",
309
- prevB64: "!!!not-base64!!!",
310
- epochId: "epoch-malformed",
311
- };
312
- const malformed = await signBody(key, { hashAlg: "sha256", digestB64: b64(sha256(utf8("malformed-prev-payload"))) }, badCommit, "test-measurement-chain");
313
- const { report } = await auditBundle({
314
- "p0.json": proofJson(first.proofs[0].proof),
315
- "p1.json": proofJson(malformed),
316
- });
317
- assert.deepEqual(codes(report), ["chain-break-malformed"]);
318
- const anomaly = report.anomalies[0];
319
- assert.deepEqual(anomaly.proofHashes, [computeProofHash(malformed)]);
320
- assert.equal(anomaly.details["prevB64"], "!!!not-base64!!!");
321
- });
322
- test("prevB64 resolving into another partition is a cross-partition break, not a merge", async () => {
323
- const chainA = await makeCounterChain({ epochId: "epoch-cross-A", pairs: healthyPairs(1) });
324
- const keyB = await makeKey();
325
- const crossCommit = {
326
- nonceB64: b64(crypto.getRandomValues(new Uint8Array(16))),
327
- counter: "2",
328
- slotCounter: "1",
329
- prevB64: chainA.proofs[0].chainHash, // points into epoch A
330
- epochId: "epoch-cross-B",
331
- };
332
- const crossProof = await signBody(keyB, { hashAlg: "sha256", digestB64: b64(sha256(utf8("cross-partition-payload"))) }, crossCommit, "test-measurement-chain");
333
- const { reconstruction, report } = await auditBundle({
334
- "a0.json": proofJson(chainA.proofs[0].proof),
335
- "b0.json": proofJson(crossProof),
336
- });
337
- // Two partitions; prevB64 never bridges them.
338
- assert.equal(reconstruction.partitions.length, 2);
339
- assert.deepEqual(codes(report), ["chain-break-cross-partition"]);
340
- const anomaly = report.anomalies[0];
341
- assert.equal(anomaly.details["predecessorProofHash"], chainA.proofs[0].proofHash);
342
- const predecessorPartition = anomaly.details["predecessorPartition"];
343
- assert.equal(predecessorPartition["epochId"], "epoch-cross-A");
344
- });
345
- });
346
- // ---------------------------------------------------------------------------
347
- // Multiple genesis and slot ordering
348
- // ---------------------------------------------------------------------------
349
- describe("anomalies: multiple genesis", () => {
350
- test("two valid no-prevB64 proofs in one partition are an anomaly and a divergence", async () => {
351
- const key = await makeKey();
352
- const first = await makeCounterChain({
353
- key,
354
- epochId: "epoch-two-genesis",
355
- pairs: [{ slot: "1", commit: "2" }],
356
- payloadPrefix: "genesis-one",
357
- });
358
- const second = await makeCounterChain({
359
- key,
360
- epochId: "epoch-two-genesis",
361
- pairs: [{ slot: "3", commit: "4" }],
362
- payloadPrefix: "genesis-two",
363
- });
364
- const { reconstruction, report } = await auditBundle({
365
- "g1.json": proofJson(first.proofs[0].proof),
366
- "g2.json": proofJson(second.proofs[0].proof),
367
- });
368
- assert.equal(reconstruction.partitions[0].components.length, 2);
369
- assert.deepEqual(codes(report), ["multiple-genesis"]);
370
- const divergence = report.divergences[0];
371
- assert.equal(divergence.kind, "multiple-genesis");
372
- assert.equal(divergence.parties.length, 2);
373
- });
374
- test("a single genesis without prevB64 is normal and never an anomaly (G1)", async () => {
375
- const chain = await makeCounterChain({ epochId: "epoch-one-genesis", pairs: healthyPairs(1) });
376
- const { report } = await auditBundle({ "g.json": proofJson(chain.proofs[0].proof) });
377
- assert.deepEqual(report.anomalies, []);
378
- });
379
- });
380
- describe("anomalies: slot ordering", () => {
381
- test("slotCounter >= counter is classified even on an otherwise valid proof", async () => {
382
- const chain = await makeCounterChain({
383
- epochId: "epoch-slot-order",
384
- pairs: [{ slot: "2", commit: "2" }],
385
- });
386
- const { report } = await auditBundle({ "p.json": proofJson(chain.proofs[0].proof) });
387
- assert.deepEqual(codes(report), ["slot-order-violation"]);
388
- assert.deepEqual(report.anomalies[0].details, { slotCounter: "2", counter: "2" });
389
- });
390
- });
391
- // ---------------------------------------------------------------------------
392
- // Epoch link anomalies
393
- // ---------------------------------------------------------------------------
394
- describe("anomalies: epoch links", () => {
395
- test("terminal-missing when the prior epoch is observed but its terminal is not", async () => {
396
- const chainA = await makeCounterChain({ epochId: "epoch-tm-A", pairs: healthyPairs(1) });
397
- const genesis = await makeEpochLinkProof({
398
- prevEpochId: "epoch-tm-A",
399
- prevCounter: "4",
400
- prevProofHashB64: b64(sha256(utf8("an-unobserved-terminal"))),
401
- prevPublicKeyB64: chainA.key.publicKeyB64,
402
- toEpochId: "epoch-tm-B",
403
- counter: "2",
404
- slotCounter: "1",
405
- });
406
- const { report } = await auditBundle({
407
- "a0.json": proofJson(chainA.proofs[0].proof),
408
- "b-genesis.json": proofJson(genesis.proof),
409
- });
410
- assert.deepEqual(codes(report), ["epochlink-terminal-missing"]);
411
- });
412
- test("dangling when neither the epoch nor the terminal is observed", async () => {
413
- const genesis = await makeEpochLinkProof({
414
- prevEpochId: "epoch-nowhere",
415
- prevCounter: "8",
416
- prevProofHashB64: b64(sha256(utf8("nowhere-terminal"))),
417
- toEpochId: "epoch-dl-B",
418
- counter: "2",
419
- slotCounter: "1",
420
- });
421
- const { report } = await auditBundle({ "b.json": proofJson(genesis.proof) });
422
- assert.deepEqual(codes(report), ["epochlink-dangling"]);
423
- });
424
- test("mismatch when the hash matches but the declared metadata disagrees", async () => {
425
- const chainA = await makeCounterChain({ epochId: "epoch-mm-A", pairs: healthyPairs(1) });
426
- const genesis = await makeEpochLinkProof({
427
- prevEpochId: "epoch-mm-A",
428
- prevCounter: "999", // the observed terminal's counter is 2
429
- prevProofHashB64: chainA.proofs[0].chainHash,
430
- prevPublicKeyB64: chainA.key.publicKeyB64,
431
- toEpochId: "epoch-mm-B",
432
- counter: "2",
433
- slotCounter: "1",
434
- });
435
- const { reconstruction, report } = await auditBundle({
436
- "a0.json": proofJson(chainA.proofs[0].proof),
437
- "b-genesis.json": proofJson(genesis.proof),
438
- });
439
- assert.deepEqual(codes(report), ["epochlink-mismatch"]);
440
- const edge = reconstruction.epochRelationships.edges[0];
441
- assert.equal(edge.metadataConsistent, false);
442
- assert.equal(edge.hardEdge, false);
443
- assert.deepEqual(reconstruction.epochRelationships.orderedPairs, []);
444
- });
445
- test("two successor epochs consuming one terminal is a fork; both branches stay valid parties", async () => {
446
- const chainA = await makeCounterChain({ epochId: "epoch-ef-A", pairs: healthyPairs(2) });
447
- const terminal = chainA.proofs[1];
448
- const linkFields = {
449
- prevEpochId: "epoch-ef-A",
450
- prevCounter: terminal.proof.commit.counter,
451
- prevProofHashB64: terminal.chainHash,
452
- prevPublicKeyB64: chainA.key.publicKeyB64,
453
- };
454
- const genesisB = await makeEpochLinkProof({
455
- ...linkFields,
456
- toEpochId: "epoch-ef-B",
457
- counter: "2",
458
- slotCounter: "1",
459
- });
460
- const genesisC = await makeEpochLinkProof({
461
- ...linkFields,
462
- toEpochId: "epoch-ef-C",
463
- counter: "2",
464
- slotCounter: "1",
465
- });
466
- const { ingest, reconstruction, report } = await auditBundle({
467
- "a0.json": proofJson(chainA.proofs[0].proof),
468
- "a1.json": proofJson(terminal.proof),
469
- "b-genesis.json": proofJson(genesisB.proof),
470
- "c-genesis.json": proofJson(genesisC.proof),
471
- });
472
- // Whichever genesis verified second failed the run with the canonical
473
- // verifier's fork detection; that run status is preserved.
474
- const failed = ingest.proofs.filter((p) => p.verification.status === "failed");
475
- assert.equal(failed.length, 1);
476
- assert.match(failed[0].verification.reason ?? "", /FORK DETECTED/);
477
- assert.deepEqual(codes(report), ["epochlink-fork"]);
478
- const divergence = report.divergences[0];
479
- assert.equal(divergence.kind, "epochlink-fork");
480
- // Both branches are intrinsically valid competing parties: the run
481
- // failure was an artifact of verification order, and the audit never
482
- // selects a winner by that order.
483
- assert.equal(divergence.parties.length, 2);
484
- assert.deepEqual(divergence.invalidContext, []);
485
- assert.match(divergence.explanation, /verification order/);
486
- // Both lineage edges are hard ordering evidence: the terminal existed
487
- // before each successor genesis regardless of which branch wins.
488
- const hardEdges = reconstruction.epochRelationships.edges.filter((e) => e.hardEdge);
489
- assert.equal(hardEdges.length, 2);
490
- assert.deepEqual(reconstruction.epochRelationships.orderedPairs, [
491
- { beforeEpochId: "epoch-ef-A", afterEpochId: "epoch-ef-B" },
492
- { beforeEpochId: "epoch-ef-A", afterEpochId: "epoch-ef-C" },
493
- ]);
494
- });
495
- test("a lineage cycle is classified and contradictory ordering is asserted in neither direction", async () => {
496
- const keyA = await makeKey();
497
- const keyB = await makeKey();
498
- // Epoch A terminal.
499
- const chainA = await makeCounterChain({
500
- key: keyA,
501
- epochId: "epoch-cy-A",
502
- pairs: healthyPairs(1),
503
- payloadPrefix: "cycle-a",
504
- });
505
- const terminalA = chainA.proofs[0];
506
- // Epoch B genesis consumes A's terminal.
507
- const genesisB = await makeEpochLinkProof({
508
- prevEpochId: "epoch-cy-A",
509
- prevCounter: "2",
510
- prevProofHashB64: terminalA.chainHash,
511
- prevPublicKeyB64: keyA.publicKeyB64,
512
- key: keyB,
513
- toEpochId: "epoch-cy-B",
514
- counter: "2",
515
- slotCounter: "1",
516
- });
517
- // Epoch B continues to a terminal.
518
- const terminalB = await signBody(keyB, { hashAlg: "sha256", digestB64: b64(sha256(utf8("cycle-b-terminal"))) }, {
519
- nonceB64: b64(crypto.getRandomValues(new Uint8Array(16))),
520
- counter: "4",
521
- slotCounter: "3",
522
- prevB64: computeChainHash(genesisB.proof),
523
- epochId: "epoch-cy-B",
524
- }, "test-measurement-epochlink");
525
- // A later epoch A proof claims lineage FROM epoch B: the cycle.
526
- const cycleCloser = await makeEpochLinkProof({
527
- prevEpochId: "epoch-cy-B",
528
- prevCounter: "4",
529
- prevProofHashB64: computeChainHash(terminalB),
530
- prevPublicKeyB64: keyB.publicKeyB64,
531
- key: keyA,
532
- toEpochId: "epoch-cy-A",
533
- counter: "4",
534
- slotCounter: "3",
535
- prevB64: terminalA.chainHash,
536
- });
537
- const { reconstruction, report } = await auditBundle({
538
- "a-terminal.json": proofJson(terminalA.proof),
539
- "b-genesis.json": proofJson(genesisB.proof),
540
- "b-terminal.json": proofJson(terminalB),
541
- "a-closer.json": proofJson(cycleCloser.proof),
542
- });
543
- const cycleAnomalies = report.anomalies.filter((a) => a.code === "epochlink-cycle");
544
- assert.equal(cycleAnomalies.length, 1);
545
- assert.deepEqual(cycleAnomalies[0].details["epochIds"].slice().sort(), ["epoch-cy-A", "epoch-cy-B"]);
546
- // Contradictory ordering evidence: assert neither direction.
547
- assert.deepEqual(reconstruction.epochRelationships.orderedPairs, []);
548
- });
549
- });
550
- // ---------------------------------------------------------------------------
551
- // Authority analysis
552
- // ---------------------------------------------------------------------------
553
- describe("authority: intra-epoch changes", () => {
554
- test("two signer keys within one epochId is flagged", async () => {
555
- const chainOne = await makeCounterChain({
556
- epochId: "epoch-shared",
557
- pairs: healthyPairs(1),
558
- payloadPrefix: "signer-one",
559
- });
560
- const chainTwo = await makeCounterChain({
561
- epochId: "epoch-shared",
562
- pairs: [{ slot: "3", commit: "4" }],
563
- payloadPrefix: "signer-two",
564
- });
565
- const dir = await makeTempDir("bitgraph-audit-authority-");
566
- tempDirs.push(dir);
567
- await writeBundleDir(dir, {
568
- "one.json": proofJson(chainOne.proofs[0].proof),
569
- "two.json": proofJson(chainTwo.proofs[0].proof),
570
- });
571
- const ingest = await ingestBundle(dir);
572
- await verifyObservedProofs(ingest);
573
- const authority = analyzeAuthorities(ingest);
574
- assert.equal(authority.anomalies.length, 1);
575
- const anomaly = authority.anomalies[0];
576
- assert.equal(anomaly.code, "mid-epoch-signer-change");
577
- assert.deepEqual(anomaly.details["publicKeysB64"], [chainOne.key.publicKeyB64, chainTwo.key.publicKeyB64].sort());
578
- assert.deepEqual(authority.sharedSignersAcrossEpochs, []);
579
- });
580
- test("two declared measurements within one epochId is flagged; declared is never attested", async () => {
581
- const key = await makeKey();
582
- const first = await makeCounterChain({
583
- key,
584
- epochId: "epoch-measure",
585
- pairs: healthyPairs(1),
586
- measurement: "measurement-one",
587
- payloadPrefix: "m-one",
588
- });
589
- const secondCommit = {
590
- nonceB64: b64(crypto.getRandomValues(new Uint8Array(16))),
591
- counter: "4",
592
- slotCounter: "3",
593
- prevB64: first.proofs[0].chainHash,
594
- epochId: "epoch-measure",
595
- };
596
- const second = await signBody(key, { hashAlg: "sha256", digestB64: b64(sha256(utf8("m-two-payload"))) }, secondCommit, "measurement-two");
597
- const dir = await makeTempDir("bitgraph-audit-authority-");
598
- tempDirs.push(dir);
599
- await writeBundleDir(dir, {
600
- "one.json": proofJson(first.proofs[0].proof),
601
- "two.json": proofJson(second),
602
- });
603
- const ingest = await ingestBundle(dir);
604
- await verifyObservedProofs(ingest);
605
- const authority = analyzeAuthorities(ingest);
606
- assert.equal(authority.anomalies.length, 1);
607
- assert.equal(authority.anomalies[0].code, "mid-epoch-measurement-change");
608
- assert.deepEqual(authority.anomalies[0].details["measurements"], [
609
- "measurement-one",
610
- "measurement-two",
611
- ]);
612
- // The attested extension point is never populated by authority analysis.
613
- for (const group of authority.groups) {
614
- assert.equal(group.attested, undefined);
615
- assert.equal(group.attestationPresent, false);
616
- }
617
- });
618
- test("the same signer across different epochs is normal transition evidence, not an anomaly", async () => {
619
- const key = await makeKey();
620
- const epochOne = await makeCounterChain({
621
- key,
622
- epochId: "epoch-span-1",
623
- pairs: healthyPairs(1),
624
- payloadPrefix: "span-one",
625
- });
626
- const epochTwo = await makeCounterChain({
627
- key,
628
- epochId: "epoch-span-2",
629
- pairs: healthyPairs(1),
630
- payloadPrefix: "span-two",
631
- });
632
- const dir = await makeTempDir("bitgraph-audit-authority-");
633
- tempDirs.push(dir);
634
- await writeBundleDir(dir, {
635
- "one.json": proofJson(epochOne.proofs[0].proof),
636
- "two.json": proofJson(epochTwo.proofs[0].proof),
637
- });
638
- const ingest = await ingestBundle(dir);
639
- await verifyObservedProofs(ingest);
640
- const authority = analyzeAuthorities(ingest);
641
- assert.deepEqual(authority.anomalies, []);
642
- assert.deepEqual(authority.sharedSignersAcrossEpochs, [
643
- { publicKeyB64: key.publicKeyB64, epochIds: ["epoch-span-1", "epoch-span-2"] },
644
- ]);
645
- });
646
- test("a healthy single-authority chain has no authority anomalies", async () => {
647
- const chain = await makeCounterChain({ epochId: "epoch-clean", pairs: healthyPairs(3) });
648
- const dir = await makeTempDir("bitgraph-audit-authority-");
649
- tempDirs.push(dir);
650
- await writeBundleDir(dir, {
651
- "p0.json": proofJson(chain.proofs[0].proof),
652
- "p1.json": proofJson(chain.proofs[1].proof),
653
- "p2.json": proofJson(chain.proofs[2].proof),
654
- });
655
- const ingest = await ingestBundle(dir);
656
- await verifyObservedProofs(ingest);
657
- const authority = analyzeAuthorities(ingest);
658
- assert.deepEqual(authority.anomalies, []);
659
- assert.equal(authority.groups.length, 1);
660
- assert.equal(authority.groups[0].proofHashes.length, 3);
661
- });
662
- });
663
- //# sourceMappingURL=audit-anomalies.test.js.map