@mikeargento/bitgraph 1.9.0 → 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 (252) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +58 -35
  3. package/dist/constructor.js +1 -1
  4. package/dist/constructor.js.map +1 -1
  5. package/dist/fuse-cli.js +1 -1
  6. package/dist/fuse-cli.js.map +1 -1
  7. package/dist/fuse.js +1 -1
  8. package/dist/fuse.js.map +1 -1
  9. package/dist/host.js +1 -1
  10. package/dist/host.js.map +1 -1
  11. package/dist/index.d.ts +2 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +2 -2
  14. package/dist/index.js.map +1 -1
  15. package/dist/policy.js +1 -1
  16. package/dist/policy.js.map +1 -1
  17. package/package.json +23 -8
  18. package/src/constructor.ts +1 -1
  19. package/src/fuse-cli.ts +1 -1
  20. package/src/fuse.ts +1 -1
  21. package/src/host.ts +1 -1
  22. package/src/index.ts +3 -2
  23. package/src/policy.ts +1 -1
  24. package/dist/__tests__/audit-anchors.test.d.ts +0 -2
  25. package/dist/__tests__/audit-anchors.test.d.ts.map +0 -1
  26. package/dist/__tests__/audit-anchors.test.js +0 -152
  27. package/dist/__tests__/audit-anchors.test.js.map +0 -1
  28. package/dist/__tests__/audit-anomalies.test.d.ts +0 -2
  29. package/dist/__tests__/audit-anomalies.test.d.ts.map +0 -1
  30. package/dist/__tests__/audit-anomalies.test.js +0 -663
  31. package/dist/__tests__/audit-anomalies.test.js.map +0 -1
  32. package/dist/__tests__/audit-attestation.test.d.ts +0 -2
  33. package/dist/__tests__/audit-attestation.test.d.ts.map +0 -1
  34. package/dist/__tests__/audit-attestation.test.js +0 -403
  35. package/dist/__tests__/audit-attestation.test.js.map +0 -1
  36. package/dist/__tests__/audit-bench-smoke.test.d.ts +0 -2
  37. package/dist/__tests__/audit-bench-smoke.test.d.ts.map +0 -1
  38. package/dist/__tests__/audit-bench-smoke.test.js +0 -87
  39. package/dist/__tests__/audit-bench-smoke.test.js.map +0 -1
  40. package/dist/__tests__/audit-boundary.test.d.ts +0 -20
  41. package/dist/__tests__/audit-boundary.test.d.ts.map +0 -1
  42. package/dist/__tests__/audit-boundary.test.js +0 -84
  43. package/dist/__tests__/audit-boundary.test.js.map +0 -1
  44. package/dist/__tests__/audit-cli.test.d.ts +0 -2
  45. package/dist/__tests__/audit-cli.test.d.ts.map +0 -1
  46. package/dist/__tests__/audit-cli.test.js +0 -239
  47. package/dist/__tests__/audit-cli.test.js.map +0 -1
  48. package/dist/__tests__/audit-dependencies.test.d.ts +0 -2
  49. package/dist/__tests__/audit-dependencies.test.d.ts.map +0 -1
  50. package/dist/__tests__/audit-dependencies.test.js +0 -116
  51. package/dist/__tests__/audit-dependencies.test.js.map +0 -1
  52. package/dist/__tests__/audit-fixtures.d.ts +0 -265
  53. package/dist/__tests__/audit-fixtures.d.ts.map +0 -1
  54. package/dist/__tests__/audit-fixtures.js +0 -511
  55. package/dist/__tests__/audit-fixtures.js.map +0 -1
  56. package/dist/__tests__/audit-ingest.test.d.ts +0 -2
  57. package/dist/__tests__/audit-ingest.test.d.ts.map +0 -1
  58. package/dist/__tests__/audit-ingest.test.js +0 -473
  59. package/dist/__tests__/audit-ingest.test.js.map +0 -1
  60. package/dist/__tests__/audit-memory-ingest.test.d.ts +0 -12
  61. package/dist/__tests__/audit-memory-ingest.test.d.ts.map +0 -1
  62. package/dist/__tests__/audit-memory-ingest.test.js +0 -135
  63. package/dist/__tests__/audit-memory-ingest.test.js.map +0 -1
  64. package/dist/__tests__/audit-property.test.d.ts +0 -2
  65. package/dist/__tests__/audit-property.test.d.ts.map +0 -1
  66. package/dist/__tests__/audit-property.test.js +0 -771
  67. package/dist/__tests__/audit-property.test.js.map +0 -1
  68. package/dist/__tests__/audit-real-fixtures.test.d.ts +0 -2
  69. package/dist/__tests__/audit-real-fixtures.test.d.ts.map +0 -1
  70. package/dist/__tests__/audit-real-fixtures.test.js +0 -132
  71. package/dist/__tests__/audit-real-fixtures.test.js.map +0 -1
  72. package/dist/__tests__/audit-real-live.test.d.ts +0 -2
  73. package/dist/__tests__/audit-real-live.test.d.ts.map +0 -1
  74. package/dist/__tests__/audit-real-live.test.js +0 -107
  75. package/dist/__tests__/audit-real-live.test.js.map +0 -1
  76. package/dist/__tests__/audit-reconstruct.test.d.ts +0 -2
  77. package/dist/__tests__/audit-reconstruct.test.d.ts.map +0 -1
  78. package/dist/__tests__/audit-reconstruct.test.js +0 -287
  79. package/dist/__tests__/audit-reconstruct.test.js.map +0 -1
  80. package/dist/__tests__/audit-report.test.d.ts +0 -2
  81. package/dist/__tests__/audit-report.test.d.ts.map +0 -1
  82. package/dist/__tests__/audit-report.test.js +0 -186
  83. package/dist/__tests__/audit-report.test.js.map +0 -1
  84. package/dist/__tests__/audit-roundtrip.test.d.ts +0 -2
  85. package/dist/__tests__/audit-roundtrip.test.d.ts.map +0 -1
  86. package/dist/__tests__/audit-roundtrip.test.js +0 -368
  87. package/dist/__tests__/audit-roundtrip.test.js.map +0 -1
  88. package/dist/__tests__/audit-temporal.test.d.ts +0 -2
  89. package/dist/__tests__/audit-temporal.test.d.ts.map +0 -1
  90. package/dist/__tests__/audit-temporal.test.js +0 -364
  91. package/dist/__tests__/audit-temporal.test.js.map +0 -1
  92. package/dist/__tests__/audit-tiers.test.d.ts +0 -2
  93. package/dist/__tests__/audit-tiers.test.d.ts.map +0 -1
  94. package/dist/__tests__/audit-tiers.test.js +0 -282
  95. package/dist/__tests__/audit-tiers.test.js.map +0 -1
  96. package/dist/__tests__/audit-witness-real.test.d.ts +0 -19
  97. package/dist/__tests__/audit-witness-real.test.d.ts.map +0 -1
  98. package/dist/__tests__/audit-witness-real.test.js +0 -82
  99. package/dist/__tests__/audit-witness-real.test.js.map +0 -1
  100. package/dist/__tests__/audit-witness.test.d.ts +0 -2
  101. package/dist/__tests__/audit-witness.test.d.ts.map +0 -1
  102. package/dist/__tests__/audit-witness.test.js +0 -308
  103. package/dist/__tests__/audit-witness.test.js.map +0 -1
  104. package/dist/__tests__/canonical.test.d.ts +0 -2
  105. package/dist/__tests__/canonical.test.d.ts.map +0 -1
  106. package/dist/__tests__/canonical.test.js +0 -162
  107. package/dist/__tests__/canonical.test.js.map +0 -1
  108. package/dist/__tests__/chain-hash-anchor.test.d.ts +0 -22
  109. package/dist/__tests__/chain-hash-anchor.test.d.ts.map +0 -1
  110. package/dist/__tests__/chain-hash-anchor.test.js +0 -55
  111. package/dist/__tests__/chain-hash-anchor.test.js.map +0 -1
  112. package/dist/__tests__/commit-service.integration.test.d.ts +0 -2
  113. package/dist/__tests__/commit-service.integration.test.d.ts.map +0 -1
  114. package/dist/__tests__/commit-service.integration.test.js +0 -95
  115. package/dist/__tests__/commit-service.integration.test.js.map +0 -1
  116. package/dist/__tests__/constructor.test.d.ts +0 -2
  117. package/dist/__tests__/constructor.test.d.ts.map +0 -1
  118. package/dist/__tests__/constructor.test.js +0 -267
  119. package/dist/__tests__/constructor.test.js.map +0 -1
  120. package/dist/__tests__/declared-proof-fixture.d.ts +0 -66
  121. package/dist/__tests__/declared-proof-fixture.d.ts.map +0 -1
  122. package/dist/__tests__/declared-proof-fixture.js +0 -66
  123. package/dist/__tests__/declared-proof-fixture.js.map +0 -1
  124. package/dist/__tests__/fuse-placement.test.d.ts +0 -2
  125. package/dist/__tests__/fuse-placement.test.d.ts.map +0 -1
  126. package/dist/__tests__/fuse-placement.test.js +0 -31
  127. package/dist/__tests__/fuse-placement.test.js.map +0 -1
  128. package/dist/__tests__/fuse-roundtrip.test.d.ts +0 -2
  129. package/dist/__tests__/fuse-roundtrip.test.d.ts.map +0 -1
  130. package/dist/__tests__/fuse-roundtrip.test.js +0 -241
  131. package/dist/__tests__/fuse-roundtrip.test.js.map +0 -1
  132. package/dist/__tests__/fuse-sdk.test.d.ts +0 -2
  133. package/dist/__tests__/fuse-sdk.test.d.ts.map +0 -1
  134. package/dist/__tests__/fuse-sdk.test.js +0 -117
  135. package/dist/__tests__/fuse-sdk.test.js.map +0 -1
  136. package/dist/__tests__/fuse-set-sdk.test.d.ts +0 -2
  137. package/dist/__tests__/fuse-set-sdk.test.d.ts.map +0 -1
  138. package/dist/__tests__/fuse-set-sdk.test.js +0 -721
  139. package/dist/__tests__/fuse-set-sdk.test.js.map +0 -1
  140. package/dist/__tests__/fuse-set.test.d.ts +0 -2
  141. package/dist/__tests__/fuse-set.test.d.ts.map +0 -1
  142. package/dist/__tests__/fuse-set.test.js +0 -868
  143. package/dist/__tests__/fuse-set.test.js.map +0 -1
  144. package/dist/__tests__/fuse-set2.test.d.ts +0 -2
  145. package/dist/__tests__/fuse-set2.test.d.ts.map +0 -1
  146. package/dist/__tests__/fuse-set2.test.js +0 -294
  147. package/dist/__tests__/fuse-set2.test.js.map +0 -1
  148. package/dist/__tests__/fuse-vectors.test.d.ts +0 -2
  149. package/dist/__tests__/fuse-vectors.test.d.ts.map +0 -1
  150. package/dist/__tests__/fuse-vectors.test.js +0 -175
  151. package/dist/__tests__/fuse-vectors.test.js.map +0 -1
  152. package/dist/__tests__/fuse-verify.test.d.ts +0 -2
  153. package/dist/__tests__/fuse-verify.test.d.ts.map +0 -1
  154. package/dist/__tests__/fuse-verify.test.js +0 -250
  155. package/dist/__tests__/fuse-verify.test.js.map +0 -1
  156. package/dist/__tests__/hello-world.test.d.ts +0 -2
  157. package/dist/__tests__/hello-world.test.d.ts.map +0 -1
  158. package/dist/__tests__/hello-world.test.js +0 -9
  159. package/dist/__tests__/hello-world.test.js.map +0 -1
  160. package/dist/__tests__/mock-proof-fixture.d.ts +0 -15
  161. package/dist/__tests__/mock-proof-fixture.d.ts.map +0 -1
  162. package/dist/__tests__/mock-proof-fixture.js +0 -34
  163. package/dist/__tests__/mock-proof-fixture.js.map +0 -1
  164. package/dist/__tests__/policy-enforcement.test.d.ts +0 -2
  165. package/dist/__tests__/policy-enforcement.test.d.ts.map +0 -1
  166. package/dist/__tests__/policy-enforcement.test.js +0 -188
  167. package/dist/__tests__/policy-enforcement.test.js.map +0 -1
  168. package/dist/__tests__/proof-hash-regression.test.d.ts +0 -2
  169. package/dist/__tests__/proof-hash-regression.test.d.ts.map +0 -1
  170. package/dist/__tests__/proof-hash-regression.test.js +0 -95
  171. package/dist/__tests__/proof-hash-regression.test.js.map +0 -1
  172. package/dist/__tests__/proof-hash.test.d.ts +0 -2
  173. package/dist/__tests__/proof-hash.test.d.ts.map +0 -1
  174. package/dist/__tests__/proof-hash.test.js +0 -116
  175. package/dist/__tests__/proof-hash.test.js.map +0 -1
  176. package/dist/__tests__/proof-integrity.test.d.ts +0 -2
  177. package/dist/__tests__/proof-integrity.test.d.ts.map +0 -1
  178. package/dist/__tests__/proof-integrity.test.js +0 -426
  179. package/dist/__tests__/proof-integrity.test.js.map +0 -1
  180. package/dist/__tests__/realistic-proof-fixture.d.ts +0 -56
  181. package/dist/__tests__/realistic-proof-fixture.d.ts.map +0 -1
  182. package/dist/__tests__/realistic-proof-fixture.js +0 -57
  183. package/dist/__tests__/realistic-proof-fixture.js.map +0 -1
  184. package/dist/__tests__/verifier.test.d.ts +0 -2
  185. package/dist/__tests__/verifier.test.d.ts.map +0 -1
  186. package/dist/__tests__/verifier.test.js +0 -531
  187. package/dist/__tests__/verifier.test.js.map +0 -1
  188. package/dist/canonical.d.ts +0 -54
  189. package/dist/canonical.d.ts.map +0 -1
  190. package/dist/canonical.js +0 -119
  191. package/dist/canonical.js.map +0 -1
  192. package/dist/proof-hash.d.ts +0 -9
  193. package/dist/proof-hash.d.ts.map +0 -1
  194. package/dist/proof-hash.js +0 -75
  195. package/dist/proof-hash.js.map +0 -1
  196. package/dist/types.d.ts +0 -705
  197. package/dist/types.d.ts.map +0 -1
  198. package/dist/types.js +0 -3
  199. package/dist/types.js.map +0 -1
  200. package/dist/verifier.d.ts +0 -27
  201. package/dist/verifier.d.ts.map +0 -1
  202. package/dist/verifier.js +0 -844
  203. package/dist/verifier.js.map +0 -1
  204. package/src/__tests__/audit-anchors.test.ts +0 -188
  205. package/src/__tests__/audit-anomalies.test.ts +0 -815
  206. package/src/__tests__/audit-attestation.test.ts +0 -529
  207. package/src/__tests__/audit-bench-smoke.test.ts +0 -108
  208. package/src/__tests__/audit-boundary.test.ts +0 -96
  209. package/src/__tests__/audit-cli.test.ts +0 -288
  210. package/src/__tests__/audit-dependencies.test.ts +0 -141
  211. package/src/__tests__/audit-fixtures.ts +0 -736
  212. package/src/__tests__/audit-ingest.test.ts +0 -575
  213. package/src/__tests__/audit-memory-ingest.test.ts +0 -156
  214. package/src/__tests__/audit-property.test.ts +0 -892
  215. package/src/__tests__/audit-real-fixtures.test.ts +0 -153
  216. package/src/__tests__/audit-real-live.test.ts +0 -128
  217. package/src/__tests__/audit-reconstruct.test.ts +0 -353
  218. package/src/__tests__/audit-report.test.ts +0 -222
  219. package/src/__tests__/audit-roundtrip.test.ts +0 -464
  220. package/src/__tests__/audit-temporal.test.ts +0 -446
  221. package/src/__tests__/audit-tiers.test.ts +0 -343
  222. package/src/__tests__/audit-witness-real.test.ts +0 -92
  223. package/src/__tests__/audit-witness.test.ts +0 -345
  224. package/src/__tests__/canonical.test.ts +0 -253
  225. package/src/__tests__/chain-hash-anchor.test.ts +0 -67
  226. package/src/__tests__/commit-service.integration.test.ts +0 -117
  227. package/src/__tests__/constructor.test.ts +0 -340
  228. package/src/__tests__/declared-proof-fixture.ts +0 -65
  229. package/src/__tests__/fuse-placement.test.ts +0 -32
  230. package/src/__tests__/fuse-roundtrip.test.ts +0 -243
  231. package/src/__tests__/fuse-sdk.test.ts +0 -142
  232. package/src/__tests__/fuse-set-sdk.test.ts +0 -852
  233. package/src/__tests__/fuse-set.test.ts +0 -1005
  234. package/src/__tests__/fuse-set2.test.ts +0 -343
  235. package/src/__tests__/fuse-vectors.test.ts +0 -218
  236. package/src/__tests__/fuse-verify.test.ts +0 -278
  237. package/src/__tests__/hello-world.test.ts +0 -10
  238. package/src/__tests__/mock-proof-fixture.ts +0 -36
  239. package/src/__tests__/policy-enforcement.test.ts +0 -226
  240. package/src/__tests__/proof-hash-regression.test.ts +0 -107
  241. package/src/__tests__/proof-hash.test.ts +0 -129
  242. package/src/__tests__/proof-integrity.test.ts +0 -541
  243. package/src/__tests__/real-fixtures/anchor-after-112762.json +0 -47
  244. package/src/__tests__/real-fixtures/anchor-with-ethereum-112758.json +0 -47
  245. package/src/__tests__/real-fixtures/live-proof-0.json +0 -42
  246. package/src/__tests__/real-fixtures/live-proof-1.json +0 -43
  247. package/src/__tests__/real-fixtures/live-proof-2.json +0 -43
  248. package/src/__tests__/real-fixtures/successor-112760.json +0 -38
  249. package/src/__tests__/real-fixtures/witness-25525146.json +0 -6
  250. package/src/__tests__/real-fixtures/witness-25525147.json +0 -6
  251. package/src/__tests__/realistic-proof-fixture.ts +0 -57
  252. package/src/__tests__/verifier.test.ts +0 -624
@@ -1,663 +0,0 @@
1
- // Copyright (c) Mike Argento. 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