@mikeargento/bitgraph 1.9.1 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/README.md +21 -21
  2. package/dist/fuse-cli.js +2 -2
  3. package/dist/fuse-cli.js.map +1 -1
  4. package/dist/fuse.d.ts +15 -1
  5. package/dist/fuse.d.ts.map +1 -1
  6. package/dist/fuse.js +29 -12
  7. package/dist/fuse.js.map +1 -1
  8. package/package.json +12 -8
  9. package/src/fuse-cli.ts +2 -2
  10. package/src/fuse.ts +46 -13
  11. package/dist/__tests__/audit-anchors.test.d.ts +0 -2
  12. package/dist/__tests__/audit-anchors.test.d.ts.map +0 -1
  13. package/dist/__tests__/audit-anchors.test.js +0 -152
  14. package/dist/__tests__/audit-anchors.test.js.map +0 -1
  15. package/dist/__tests__/audit-anomalies.test.d.ts +0 -2
  16. package/dist/__tests__/audit-anomalies.test.d.ts.map +0 -1
  17. package/dist/__tests__/audit-anomalies.test.js +0 -663
  18. package/dist/__tests__/audit-anomalies.test.js.map +0 -1
  19. package/dist/__tests__/audit-attestation.test.d.ts +0 -2
  20. package/dist/__tests__/audit-attestation.test.d.ts.map +0 -1
  21. package/dist/__tests__/audit-attestation.test.js +0 -403
  22. package/dist/__tests__/audit-attestation.test.js.map +0 -1
  23. package/dist/__tests__/audit-bench-smoke.test.d.ts +0 -2
  24. package/dist/__tests__/audit-bench-smoke.test.d.ts.map +0 -1
  25. package/dist/__tests__/audit-bench-smoke.test.js +0 -87
  26. package/dist/__tests__/audit-bench-smoke.test.js.map +0 -1
  27. package/dist/__tests__/audit-boundary.test.d.ts +0 -20
  28. package/dist/__tests__/audit-boundary.test.d.ts.map +0 -1
  29. package/dist/__tests__/audit-boundary.test.js +0 -84
  30. package/dist/__tests__/audit-boundary.test.js.map +0 -1
  31. package/dist/__tests__/audit-cli.test.d.ts +0 -2
  32. package/dist/__tests__/audit-cli.test.d.ts.map +0 -1
  33. package/dist/__tests__/audit-cli.test.js +0 -239
  34. package/dist/__tests__/audit-cli.test.js.map +0 -1
  35. package/dist/__tests__/audit-dependencies.test.d.ts +0 -2
  36. package/dist/__tests__/audit-dependencies.test.d.ts.map +0 -1
  37. package/dist/__tests__/audit-dependencies.test.js +0 -116
  38. package/dist/__tests__/audit-dependencies.test.js.map +0 -1
  39. package/dist/__tests__/audit-fixtures.d.ts +0 -265
  40. package/dist/__tests__/audit-fixtures.d.ts.map +0 -1
  41. package/dist/__tests__/audit-fixtures.js +0 -511
  42. package/dist/__tests__/audit-fixtures.js.map +0 -1
  43. package/dist/__tests__/audit-ingest.test.d.ts +0 -2
  44. package/dist/__tests__/audit-ingest.test.d.ts.map +0 -1
  45. package/dist/__tests__/audit-ingest.test.js +0 -473
  46. package/dist/__tests__/audit-ingest.test.js.map +0 -1
  47. package/dist/__tests__/audit-memory-ingest.test.d.ts +0 -12
  48. package/dist/__tests__/audit-memory-ingest.test.d.ts.map +0 -1
  49. package/dist/__tests__/audit-memory-ingest.test.js +0 -135
  50. package/dist/__tests__/audit-memory-ingest.test.js.map +0 -1
  51. package/dist/__tests__/audit-property.test.d.ts +0 -2
  52. package/dist/__tests__/audit-property.test.d.ts.map +0 -1
  53. package/dist/__tests__/audit-property.test.js +0 -771
  54. package/dist/__tests__/audit-property.test.js.map +0 -1
  55. package/dist/__tests__/audit-real-fixtures.test.d.ts +0 -2
  56. package/dist/__tests__/audit-real-fixtures.test.d.ts.map +0 -1
  57. package/dist/__tests__/audit-real-fixtures.test.js +0 -132
  58. package/dist/__tests__/audit-real-fixtures.test.js.map +0 -1
  59. package/dist/__tests__/audit-real-live.test.d.ts +0 -2
  60. package/dist/__tests__/audit-real-live.test.d.ts.map +0 -1
  61. package/dist/__tests__/audit-real-live.test.js +0 -107
  62. package/dist/__tests__/audit-real-live.test.js.map +0 -1
  63. package/dist/__tests__/audit-reconstruct.test.d.ts +0 -2
  64. package/dist/__tests__/audit-reconstruct.test.d.ts.map +0 -1
  65. package/dist/__tests__/audit-reconstruct.test.js +0 -287
  66. package/dist/__tests__/audit-reconstruct.test.js.map +0 -1
  67. package/dist/__tests__/audit-report.test.d.ts +0 -2
  68. package/dist/__tests__/audit-report.test.d.ts.map +0 -1
  69. package/dist/__tests__/audit-report.test.js +0 -186
  70. package/dist/__tests__/audit-report.test.js.map +0 -1
  71. package/dist/__tests__/audit-roundtrip.test.d.ts +0 -2
  72. package/dist/__tests__/audit-roundtrip.test.d.ts.map +0 -1
  73. package/dist/__tests__/audit-roundtrip.test.js +0 -368
  74. package/dist/__tests__/audit-roundtrip.test.js.map +0 -1
  75. package/dist/__tests__/audit-temporal.test.d.ts +0 -2
  76. package/dist/__tests__/audit-temporal.test.d.ts.map +0 -1
  77. package/dist/__tests__/audit-temporal.test.js +0 -364
  78. package/dist/__tests__/audit-temporal.test.js.map +0 -1
  79. package/dist/__tests__/audit-tiers.test.d.ts +0 -2
  80. package/dist/__tests__/audit-tiers.test.d.ts.map +0 -1
  81. package/dist/__tests__/audit-tiers.test.js +0 -282
  82. package/dist/__tests__/audit-tiers.test.js.map +0 -1
  83. package/dist/__tests__/audit-witness-real.test.d.ts +0 -19
  84. package/dist/__tests__/audit-witness-real.test.d.ts.map +0 -1
  85. package/dist/__tests__/audit-witness-real.test.js +0 -82
  86. package/dist/__tests__/audit-witness-real.test.js.map +0 -1
  87. package/dist/__tests__/audit-witness.test.d.ts +0 -2
  88. package/dist/__tests__/audit-witness.test.d.ts.map +0 -1
  89. package/dist/__tests__/audit-witness.test.js +0 -308
  90. package/dist/__tests__/audit-witness.test.js.map +0 -1
  91. package/dist/__tests__/canonical.test.d.ts +0 -2
  92. package/dist/__tests__/canonical.test.d.ts.map +0 -1
  93. package/dist/__tests__/canonical.test.js +0 -162
  94. package/dist/__tests__/canonical.test.js.map +0 -1
  95. package/dist/__tests__/chain-hash-anchor.test.d.ts +0 -22
  96. package/dist/__tests__/chain-hash-anchor.test.d.ts.map +0 -1
  97. package/dist/__tests__/chain-hash-anchor.test.js +0 -55
  98. package/dist/__tests__/chain-hash-anchor.test.js.map +0 -1
  99. package/dist/__tests__/commit-service.integration.test.d.ts +0 -2
  100. package/dist/__tests__/commit-service.integration.test.d.ts.map +0 -1
  101. package/dist/__tests__/commit-service.integration.test.js +0 -95
  102. package/dist/__tests__/commit-service.integration.test.js.map +0 -1
  103. package/dist/__tests__/constructor.test.d.ts +0 -2
  104. package/dist/__tests__/constructor.test.d.ts.map +0 -1
  105. package/dist/__tests__/constructor.test.js +0 -267
  106. package/dist/__tests__/constructor.test.js.map +0 -1
  107. package/dist/__tests__/declared-proof-fixture.d.ts +0 -66
  108. package/dist/__tests__/declared-proof-fixture.d.ts.map +0 -1
  109. package/dist/__tests__/declared-proof-fixture.js +0 -66
  110. package/dist/__tests__/declared-proof-fixture.js.map +0 -1
  111. package/dist/__tests__/fuse-inline.test.d.ts +0 -2
  112. package/dist/__tests__/fuse-inline.test.d.ts.map +0 -1
  113. package/dist/__tests__/fuse-inline.test.js +0 -181
  114. package/dist/__tests__/fuse-inline.test.js.map +0 -1
  115. package/dist/__tests__/fuse-placement.test.d.ts +0 -2
  116. package/dist/__tests__/fuse-placement.test.d.ts.map +0 -1
  117. package/dist/__tests__/fuse-placement.test.js +0 -31
  118. package/dist/__tests__/fuse-placement.test.js.map +0 -1
  119. package/dist/__tests__/fuse-roundtrip.test.d.ts +0 -2
  120. package/dist/__tests__/fuse-roundtrip.test.d.ts.map +0 -1
  121. package/dist/__tests__/fuse-roundtrip.test.js +0 -241
  122. package/dist/__tests__/fuse-roundtrip.test.js.map +0 -1
  123. package/dist/__tests__/fuse-sdk.test.d.ts +0 -2
  124. package/dist/__tests__/fuse-sdk.test.d.ts.map +0 -1
  125. package/dist/__tests__/fuse-sdk.test.js +0 -117
  126. package/dist/__tests__/fuse-sdk.test.js.map +0 -1
  127. package/dist/__tests__/fuse-set-sdk.test.d.ts +0 -2
  128. package/dist/__tests__/fuse-set-sdk.test.d.ts.map +0 -1
  129. package/dist/__tests__/fuse-set-sdk.test.js +0 -721
  130. package/dist/__tests__/fuse-set-sdk.test.js.map +0 -1
  131. package/dist/__tests__/fuse-set.test.d.ts +0 -2
  132. package/dist/__tests__/fuse-set.test.d.ts.map +0 -1
  133. package/dist/__tests__/fuse-set.test.js +0 -889
  134. package/dist/__tests__/fuse-set.test.js.map +0 -1
  135. package/dist/__tests__/fuse-set2.test.d.ts +0 -2
  136. package/dist/__tests__/fuse-set2.test.d.ts.map +0 -1
  137. package/dist/__tests__/fuse-set2.test.js +0 -294
  138. package/dist/__tests__/fuse-set2.test.js.map +0 -1
  139. package/dist/__tests__/fuse-vectors.test.d.ts +0 -2
  140. package/dist/__tests__/fuse-vectors.test.d.ts.map +0 -1
  141. package/dist/__tests__/fuse-vectors.test.js +0 -175
  142. package/dist/__tests__/fuse-vectors.test.js.map +0 -1
  143. package/dist/__tests__/fuse-verify.test.d.ts +0 -2
  144. package/dist/__tests__/fuse-verify.test.d.ts.map +0 -1
  145. package/dist/__tests__/fuse-verify.test.js +0 -250
  146. package/dist/__tests__/fuse-verify.test.js.map +0 -1
  147. package/dist/__tests__/hello-world.test.d.ts +0 -2
  148. package/dist/__tests__/hello-world.test.d.ts.map +0 -1
  149. package/dist/__tests__/hello-world.test.js +0 -9
  150. package/dist/__tests__/hello-world.test.js.map +0 -1
  151. package/dist/__tests__/mock-proof-fixture.d.ts +0 -15
  152. package/dist/__tests__/mock-proof-fixture.d.ts.map +0 -1
  153. package/dist/__tests__/mock-proof-fixture.js +0 -34
  154. package/dist/__tests__/mock-proof-fixture.js.map +0 -1
  155. package/dist/__tests__/policy-enforcement.test.d.ts +0 -2
  156. package/dist/__tests__/policy-enforcement.test.d.ts.map +0 -1
  157. package/dist/__tests__/policy-enforcement.test.js +0 -188
  158. package/dist/__tests__/policy-enforcement.test.js.map +0 -1
  159. package/dist/__tests__/proof-hash-regression.test.d.ts +0 -2
  160. package/dist/__tests__/proof-hash-regression.test.d.ts.map +0 -1
  161. package/dist/__tests__/proof-hash-regression.test.js +0 -95
  162. package/dist/__tests__/proof-hash-regression.test.js.map +0 -1
  163. package/dist/__tests__/proof-hash.test.d.ts +0 -2
  164. package/dist/__tests__/proof-hash.test.d.ts.map +0 -1
  165. package/dist/__tests__/proof-hash.test.js +0 -116
  166. package/dist/__tests__/proof-hash.test.js.map +0 -1
  167. package/dist/__tests__/proof-integrity.test.d.ts +0 -2
  168. package/dist/__tests__/proof-integrity.test.d.ts.map +0 -1
  169. package/dist/__tests__/proof-integrity.test.js +0 -454
  170. package/dist/__tests__/proof-integrity.test.js.map +0 -1
  171. package/dist/__tests__/realistic-proof-fixture.d.ts +0 -56
  172. package/dist/__tests__/realistic-proof-fixture.d.ts.map +0 -1
  173. package/dist/__tests__/realistic-proof-fixture.js +0 -57
  174. package/dist/__tests__/realistic-proof-fixture.js.map +0 -1
  175. package/dist/__tests__/run.test.d.ts +0 -2
  176. package/dist/__tests__/run.test.d.ts.map +0 -1
  177. package/dist/__tests__/run.test.js +0 -178
  178. package/dist/__tests__/run.test.js.map +0 -1
  179. package/dist/__tests__/verifier.test.d.ts +0 -2
  180. package/dist/__tests__/verifier.test.d.ts.map +0 -1
  181. package/dist/__tests__/verifier.test.js +0 -531
  182. package/dist/__tests__/verifier.test.js.map +0 -1
  183. package/dist/canonical.d.ts +0 -54
  184. package/dist/canonical.d.ts.map +0 -1
  185. package/dist/canonical.js +0 -119
  186. package/dist/canonical.js.map +0 -1
  187. package/dist/proof-hash.d.ts +0 -9
  188. package/dist/proof-hash.d.ts.map +0 -1
  189. package/dist/proof-hash.js +0 -75
  190. package/dist/proof-hash.js.map +0 -1
  191. package/dist/types.d.ts +0 -705
  192. package/dist/types.d.ts.map +0 -1
  193. package/dist/types.js +0 -3
  194. package/dist/types.js.map +0 -1
  195. package/dist/verifier.d.ts +0 -27
  196. package/dist/verifier.d.ts.map +0 -1
  197. package/dist/verifier.js +0 -844
  198. package/dist/verifier.js.map +0 -1
  199. package/src/__tests__/audit-anchors.test.ts +0 -188
  200. package/src/__tests__/audit-anomalies.test.ts +0 -815
  201. package/src/__tests__/audit-attestation.test.ts +0 -529
  202. package/src/__tests__/audit-bench-smoke.test.ts +0 -108
  203. package/src/__tests__/audit-boundary.test.ts +0 -96
  204. package/src/__tests__/audit-cli.test.ts +0 -288
  205. package/src/__tests__/audit-dependencies.test.ts +0 -141
  206. package/src/__tests__/audit-fixtures.ts +0 -736
  207. package/src/__tests__/audit-ingest.test.ts +0 -575
  208. package/src/__tests__/audit-memory-ingest.test.ts +0 -156
  209. package/src/__tests__/audit-property.test.ts +0 -892
  210. package/src/__tests__/audit-real-fixtures.test.ts +0 -153
  211. package/src/__tests__/audit-real-live.test.ts +0 -128
  212. package/src/__tests__/audit-reconstruct.test.ts +0 -353
  213. package/src/__tests__/audit-report.test.ts +0 -222
  214. package/src/__tests__/audit-roundtrip.test.ts +0 -464
  215. package/src/__tests__/audit-temporal.test.ts +0 -446
  216. package/src/__tests__/audit-tiers.test.ts +0 -343
  217. package/src/__tests__/audit-witness-real.test.ts +0 -92
  218. package/src/__tests__/audit-witness.test.ts +0 -345
  219. package/src/__tests__/canonical.test.ts +0 -253
  220. package/src/__tests__/chain-hash-anchor.test.ts +0 -67
  221. package/src/__tests__/commit-service.integration.test.ts +0 -117
  222. package/src/__tests__/constructor.test.ts +0 -340
  223. package/src/__tests__/declared-proof-fixture.ts +0 -65
  224. package/src/__tests__/fuse-inline.test.ts +0 -211
  225. package/src/__tests__/fuse-placement.test.ts +0 -32
  226. package/src/__tests__/fuse-roundtrip.test.ts +0 -243
  227. package/src/__tests__/fuse-sdk.test.ts +0 -142
  228. package/src/__tests__/fuse-set-sdk.test.ts +0 -852
  229. package/src/__tests__/fuse-set.test.ts +0 -1027
  230. package/src/__tests__/fuse-set2.test.ts +0 -343
  231. package/src/__tests__/fuse-vectors.test.ts +0 -218
  232. package/src/__tests__/fuse-verify.test.ts +0 -278
  233. package/src/__tests__/hello-world.test.ts +0 -10
  234. package/src/__tests__/mock-proof-fixture.ts +0 -36
  235. package/src/__tests__/policy-enforcement.test.ts +0 -226
  236. package/src/__tests__/proof-hash-regression.test.ts +0 -107
  237. package/src/__tests__/proof-hash.test.ts +0 -129
  238. package/src/__tests__/proof-integrity.test.ts +0 -576
  239. package/src/__tests__/real-fixtures/anchor-after-112762.json +0 -47
  240. package/src/__tests__/real-fixtures/anchor-with-ethereum-112758.json +0 -47
  241. package/src/__tests__/real-fixtures/live-proof-0.json +0 -42
  242. package/src/__tests__/real-fixtures/live-proof-1.json +0 -43
  243. package/src/__tests__/real-fixtures/live-proof-2.json +0 -43
  244. package/src/__tests__/real-fixtures/successor-112760.json +0 -38
  245. package/src/__tests__/real-fixtures/witness-25525146.json +0 -6
  246. package/src/__tests__/real-fixtures/witness-25525147.json +0 -6
  247. package/src/__tests__/realistic-proof-fixture.ts +0 -57
  248. package/src/__tests__/verifier.test.ts +0 -624
@@ -1,575 +0,0 @@
1
- // Copyright (c) Argento Computing Inc. All rights reserved. See LICENSE.
2
-
3
- /**
4
- * Tests for @mikeargento/bitgraph-audit ingest: container handling
5
- * (directory, tar, tar.gz), discovery by shape, the version policy,
6
- * duplicate detection, embedded proofHash cross-checks, manifest
7
- * validation, the deterministic contents hash, and path safety.
8
- */
9
-
10
- import { describe, test, before, after } from "node:test";
11
- import * as assert from "node:assert/strict";
12
- import { rm, writeFile } from "node:fs/promises";
13
- import { join } from "node:path";
14
- import { gzipSync } from "node:zlib";
15
- import { computeProofHash } from "@mikeargento/bitgraph-verify";
16
- import {
17
- computeContentsHashB64,
18
- ingestBundle,
19
- } from "@mikeargento/bitgraph-audit";
20
- import type { BitGraphProof } from "@mikeargento/bitgraph-verify";
21
- import {
22
- b64,
23
- makeChainIdProof,
24
- makeConstructorProof,
25
- makeTar,
26
- makeTempDir,
27
- proofJson,
28
- storedProofJson,
29
- utf8,
30
- writeBundleDir,
31
- } from "./audit-fixtures.js";
32
-
33
- const tempDirs: string[] = [];
34
-
35
- async function newBundleDir(): Promise<string> {
36
- const dir = await makeTempDir("bitgraph-audit-ingest-");
37
- tempDirs.push(dir);
38
- return dir;
39
- }
40
-
41
- after(async () => {
42
- for (const dir of tempDirs) {
43
- await rm(dir, { recursive: true, force: true });
44
- }
45
- });
46
-
47
- // Shared fixtures
48
- let fxProof!: BitGraphProof;
49
- let fxBytes!: Uint8Array;
50
- let fxHash!: string;
51
-
52
- before(async () => {
53
- const made = await makeConstructorProof();
54
- fxProof = made.proof;
55
- fxBytes = made.bytes;
56
- fxHash = computeProofHash(fxProof);
57
- });
58
-
59
- // ---------------------------------------------------------------------------
60
- // Directory bundles
61
- // ---------------------------------------------------------------------------
62
-
63
- describe("audit ingest: directory bundles", () => {
64
- test("discovers proofs by shape, matches artifacts by content, ignores unrelated files", async () => {
65
- const dir = await newBundleDir();
66
- await writeBundleDir(dir, {
67
- "proofs/anything-goes.txt": storedProofJson(fxProof, fxHash),
68
- "media/original.bin": fxBytes,
69
- "notes/README.txt": "operator notes, not evidence",
70
- });
71
-
72
- const result = await ingestBundle(dir);
73
-
74
- assert.equal(result.container, "directory");
75
- assert.equal(result.counts.observed, 1);
76
- assert.equal(result.counts.proofFiles, 1);
77
- const proof = result.proofs[0]!;
78
- assert.equal(proof.proofHash, fxHash);
79
- assert.equal(proof.sources.length, 1);
80
- assert.equal(proof.sources[0]!.path, "proofs/anything-goes.txt");
81
- assert.equal(proof.embeddedProofHash, "match");
82
-
83
- // Artifact matched content-addressed; the README is an unmatched candidate.
84
- const matched = result.artifacts.filter((a) => a.matchedProofHashes.length > 0);
85
- assert.equal(matched.length, 1);
86
- assert.deepEqual(matched[0]!.paths, ["media/original.bin"]);
87
- assert.deepEqual(matched[0]!.matchedProofHashes, [fxHash]);
88
- const unmatched = result.artifacts.filter((a) => a.matchedProofHashes.length === 0);
89
- assert.equal(unmatched.length, 1);
90
- assert.deepEqual(unmatched[0]!.paths, ["notes/README.txt"]);
91
-
92
- // Unrelated files produce no findings.
93
- assert.deepEqual(result.findings, []);
94
- });
95
-
96
- test("version policy: occ/1 proof-shaped file is rejected with the stable code", async () => {
97
- const dir = await newBundleDir();
98
- await writeBundleDir(dir, {
99
- "legacy.json": JSON.stringify({
100
- version: "occ/1",
101
- artifact: { hashAlg: "sha256", digestB64: "AAAA" },
102
- commit: { nonceB64: "AAAA" },
103
- signer: { publicKeyB64: "AAAA", signatureB64: "AAAA" },
104
- }),
105
- "good.json": proofJson(fxProof),
106
- });
107
-
108
- const result = await ingestBundle(dir);
109
-
110
- assert.equal(result.counts.observed, 1);
111
- assert.equal(result.counts.unsupportedVersion, 1);
112
- const rejected = result.unsupportedVersions[0]!;
113
- assert.equal(rejected.code, "unsupported-version");
114
- assert.equal(rejected.path, "legacy.json");
115
- assert.equal(rejected.version, "occ/1");
116
- const finding = result.findings.find((f) => f.code === "unsupported-version");
117
- assert.ok(finding);
118
- assert.equal(finding.path, "legacy.json");
119
- // Excluded from observation entirely.
120
- assert.ok(result.proofs.every((p) => p.sources[0]!.path !== "legacy.json"));
121
- });
122
-
123
- test("unknown fields tolerated: signed chainId is extracted, absence normalizes to global", async () => {
124
- const withChain = await makeChainIdProof({ chainId: "bitgraph:main", counter: "2", epochId: "epoch-x" });
125
- const dir = await newBundleDir();
126
- await writeBundleDir(dir, {
127
- "a.json": proofJson(withChain.proof),
128
- "b.json": proofJson(fxProof),
129
- });
130
-
131
- const result = await ingestBundle(dir);
132
- assert.equal(result.counts.observed, 2);
133
- const chained = result.proofs.find((p) => p.sources[0]!.path === "a.json")!;
134
- const plain = result.proofs.find((p) => p.sources[0]!.path === "b.json")!;
135
- assert.equal(chained.chainId, "bitgraph:main");
136
- assert.equal(chained.counter, "2");
137
- assert.equal(chained.epochId, "epoch-x");
138
- assert.equal(chained.chainless, false);
139
- assert.equal(plain.chainId, "global");
140
- });
141
-
142
- test("exact duplicates: byte-identical copies collapse to one observed proof", async () => {
143
- const dir = await newBundleDir();
144
- const body = proofJson(fxProof);
145
- await writeBundleDir(dir, {
146
- "one/copy.json": body,
147
- "two/copy.json": body,
148
- });
149
-
150
- const result = await ingestBundle(dir);
151
- assert.equal(result.counts.observed, 1);
152
- assert.equal(result.counts.proofFiles, 2);
153
- assert.equal(result.counts.exactDuplicates, 1);
154
- assert.equal(result.counts.semanticDuplicates, 0);
155
- assert.equal(result.proofs[0]!.sources.length, 2);
156
- const finding = result.findings.find((f) => f.code === "exact-duplicate");
157
- assert.ok(finding);
158
- assert.equal(finding.details?.["proofHash"], fxHash);
159
- });
160
-
161
- test("semantic duplicates: different encodings of the same canonical identity", async () => {
162
- const dir = await newBundleDir();
163
- await writeBundleDir(dir, {
164
- "compact.json": JSON.stringify(fxProof),
165
- "pretty.json": JSON.stringify(fxProof, null, 2),
166
- });
167
-
168
- const result = await ingestBundle(dir);
169
- assert.equal(result.counts.observed, 1);
170
- assert.equal(result.counts.exactDuplicates, 0);
171
- assert.equal(result.counts.semanticDuplicates, 1);
172
- assert.equal(result.proofs[0]!.proofHash, fxHash);
173
- assert.equal(result.proofs[0]!.sources.length, 2);
174
- assert.ok(result.findings.find((f) => f.code === "semantic-duplicate"));
175
- });
176
-
177
- test("embedded proofHash mismatch is flagged with a stable code but still observed", async () => {
178
- const dir = await newBundleDir();
179
- await writeBundleDir(dir, {
180
- "tampered-hash.json": storedProofJson(fxProof, b64(new Uint8Array(32))),
181
- });
182
-
183
- const result = await ingestBundle(dir);
184
- assert.equal(result.counts.observed, 1);
185
- const proof = result.proofs[0]!;
186
- assert.equal(proof.embeddedProofHash, "mismatch");
187
- // Identity is always computed, never read from the file.
188
- assert.equal(proof.proofHash, fxHash);
189
- const finding = result.findings.find((f) => f.code === "proofhash-mismatch");
190
- assert.ok(finding);
191
- assert.equal(finding.details?.["computed"], proof.proofHash);
192
- });
193
-
194
- test("chainless classification: no counter and no epochId means observed-but-unchained", async () => {
195
- const chained = await makeConstructorProof({ withCounter: true, epochId: "epoch-chained" });
196
- const dir = await newBundleDir();
197
- await writeBundleDir(dir, {
198
- "chainless.json": proofJson(fxProof),
199
- "chained.json": proofJson(chained.proof),
200
- });
201
-
202
- const result = await ingestBundle(dir);
203
- const unchained = result.proofs.find((p) => p.sources[0]!.path === "chainless.json")!;
204
- const linked = result.proofs.find((p) => p.sources[0]!.path === "chained.json")!;
205
- assert.equal(unchained.chainless, true);
206
- assert.equal(linked.chainless, false);
207
- assert.equal(linked.counter, "1");
208
- assert.equal(linked.epochId, "epoch-chained");
209
- // Chainless is not an anomaly: no finding is emitted for it.
210
- assert.deepEqual(result.findings, []);
211
- });
212
- });
213
-
214
- // ---------------------------------------------------------------------------
215
- // Manifest
216
- // ---------------------------------------------------------------------------
217
-
218
- describe("audit ingest: manifest", () => {
219
- test("contents hash verifies over every entry except the root manifest", async () => {
220
- const dir = await newBundleDir();
221
- const proofBytes = utf8(proofJson(fxProof));
222
- const contentsHashB64 = computeContentsHashB64([
223
- { path: "proofs/p.json", content: proofBytes },
224
- { path: "media/original.bin", content: fxBytes },
225
- ]);
226
- await writeBundleDir(dir, {
227
- "proofs/p.json": proofBytes,
228
- "media/original.bin": fxBytes,
229
- "manifest.json": JSON.stringify({
230
- version: "bitgraph-bundle/1",
231
- proofCount: 1,
232
- contentsHashB64,
233
- artifactsIncluded: true,
234
- }),
235
- });
236
-
237
- const result = await ingestBundle(dir);
238
- assert.ok(result.manifest);
239
- assert.equal(result.manifest.recognized, true);
240
- assert.deepEqual(result.manifest.problems, []);
241
- assert.equal(result.manifest.contentsHash?.match, true);
242
- assert.equal(result.computedContentsHashB64, contentsHashB64);
243
- assert.equal(result.findings.filter((f) => f.code === "manifest-contents-hash-mismatch").length, 0);
244
- // The manifest is never a proof, artifact, or witness.
245
- assert.equal(result.counts.observed, 1);
246
- assert.ok(result.artifacts.every((a) => !a.paths.includes("manifest.json")));
247
- });
248
-
249
- test("contents hash mismatch is an advisory finding, never a proof failure", async () => {
250
- const dir = await newBundleDir();
251
- await writeBundleDir(dir, {
252
- "proofs/p.json": proofJson(fxProof),
253
- "manifest.json": JSON.stringify({
254
- version: "bitgraph-bundle/1",
255
- contentsHashB64: "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
256
- }),
257
- });
258
-
259
- const result = await ingestBundle(dir);
260
- assert.equal(result.manifest?.contentsHash?.match, false);
261
- const finding = result.findings.find((f) => f.code === "manifest-contents-hash-mismatch");
262
- assert.ok(finding);
263
- // Advisory: the proof is still observed normally.
264
- assert.equal(result.counts.observed, 1);
265
- assert.equal(result.proofs[0]!.proofHash, fxHash);
266
- });
267
-
268
- test("manifest field type problems are reported per spec section 7.1", async () => {
269
- const dir = await newBundleDir();
270
- await writeBundleDir(dir, {
271
- "manifest.json": JSON.stringify({
272
- version: "bitgraph-bundle/1",
273
- proofCount: "five",
274
- epochIds: "not-an-array",
275
- futureUnknownField: { tolerated: true },
276
- }),
277
- });
278
-
279
- const result = await ingestBundle(dir);
280
- assert.ok(result.manifest);
281
- assert.equal(result.manifest.recognized, true);
282
- assert.equal(result.manifest.problems.length, 2);
283
- assert.equal(result.findings.filter((f) => f.code === "manifest-field-invalid").length, 2);
284
- });
285
-
286
- test("unrecognized manifest version is reported and the manifest is not interpreted", async () => {
287
- const dir = await newBundleDir();
288
- await writeBundleDir(dir, {
289
- "manifest.json": JSON.stringify({ version: "bitgraph-bundle/2", proofCount: 1 }),
290
- "proofs/p.json": proofJson(fxProof),
291
- });
292
-
293
- const result = await ingestBundle(dir);
294
- assert.equal(result.manifest?.recognized, false);
295
- assert.equal(result.manifest?.manifest, undefined);
296
- assert.ok(result.findings.find((f) => f.code === "manifest-unrecognized-version"));
297
- // Processing continues as if no manifest were present.
298
- assert.equal(result.counts.observed, 1);
299
- });
300
- });
301
-
302
- // ---------------------------------------------------------------------------
303
- // Tar and tar.gz containers
304
- // ---------------------------------------------------------------------------
305
-
306
- describe("audit ingest: tar and tar.gz containers", () => {
307
- test("plain tar with a PAX long-name entry", async () => {
308
- const dir = await newBundleDir();
309
- const longPath = `proofs/${"very-long-directory-name-".repeat(6)}segment/proof.json`;
310
- assert.ok(longPath.length > 100);
311
- const tar = makeTar([
312
- { name: "long-name-truncated", paxPath: longPath, content: proofJson(fxProof) },
313
- { name: "media/original.bin", content: fxBytes },
314
- { name: "notes.txt", content: "unrelated" },
315
- ]);
316
- const tarPath = join(dir, "bundle.tar");
317
- await writeFile(tarPath, tar);
318
-
319
- const result = await ingestBundle(tarPath);
320
- assert.equal(result.container, "tar");
321
- assert.equal(result.counts.observed, 1);
322
- assert.equal(result.proofs[0]!.sources[0]!.path, longPath);
323
- const matched = result.artifacts.find((a) => a.matchedProofHashes.length > 0);
324
- assert.deepEqual(matched?.paths, ["media/original.bin"]);
325
- });
326
-
327
- test("GNU long-name entries are accepted", async () => {
328
- const dir = await newBundleDir();
329
- const longPath = `archive/${"gnu-long-name-".repeat(9)}tail/proof.json`;
330
- assert.ok(longPath.length > 100);
331
- const tar = makeTar([
332
- { name: "gnu-truncated", gnuLongName: longPath, content: proofJson(fxProof) },
333
- // Root-level entry so the single-top-level-directory rule does not apply.
334
- { name: "notes.txt", content: "unrelated" },
335
- ]);
336
- const tarPath = join(dir, "gnu.tar");
337
- await writeFile(tarPath, tar);
338
-
339
- const result = await ingestBundle(tarPath);
340
- assert.equal(result.strippedRootPrefix, undefined);
341
- assert.equal(result.counts.observed, 1);
342
- assert.equal(result.proofs[0]!.sources[0]!.path, longPath);
343
- });
344
-
345
- test("tar.gz round-trips identically to the plain tar", async () => {
346
- const dir = await newBundleDir();
347
- const tar = makeTar([
348
- { name: "proofs/p.json", content: proofJson(fxProof) },
349
- { name: "media/original.bin", content: fxBytes },
350
- ]);
351
- const tgzPath = join(dir, "bundle.tgz");
352
- await writeFile(tgzPath, gzipSync(tar));
353
-
354
- const result = await ingestBundle(tgzPath);
355
- assert.equal(result.container, "tar-gz");
356
- assert.equal(result.counts.observed, 1);
357
- assert.equal(result.proofs[0]!.proofHash, fxHash);
358
- assert.equal(result.artifacts.filter((a) => a.matchedProofHashes.length > 0).length, 1);
359
- });
360
-
361
- test("a single common top-level directory is stripped as the bundle root", async () => {
362
- const dir = await newBundleDir();
363
- const proofBytes = utf8(proofJson(fxProof));
364
- // Contents hash computed over bundle-root-relative (stripped) paths.
365
- const contentsHashB64 = computeContentsHashB64([
366
- { path: "proofs/p.json", content: proofBytes },
367
- { path: "media/original.bin", content: fxBytes },
368
- ]);
369
- const tar = makeTar([
370
- { name: "mybundle/proofs/p.json", content: proofBytes },
371
- { name: "mybundle/media/original.bin", content: fxBytes },
372
- {
373
- name: "mybundle/manifest.json",
374
- content: JSON.stringify({ version: "bitgraph-bundle/1", contentsHashB64 }),
375
- },
376
- ]);
377
- const tarPath = join(dir, "wrapped.tar");
378
- await writeFile(tarPath, tar);
379
-
380
- const result = await ingestBundle(tarPath);
381
- assert.equal(result.strippedRootPrefix, "mybundle");
382
- assert.equal(result.proofs[0]!.sources[0]!.path, "proofs/p.json");
383
- assert.ok(result.manifest, "manifest at the stripped root is the root manifest");
384
- assert.equal(result.manifest.contentsHash?.match, true);
385
- });
386
-
387
- test("unsafe paths are skipped and reported, and never contribute anywhere", async () => {
388
- const dir = await newBundleDir();
389
- const tar = makeTar([
390
- { name: "../escape.json", content: proofJson(fxProof) },
391
- { name: "/absolute.json", content: proofJson(fxProof) },
392
- { name: "good/p.json", content: proofJson(fxProof) },
393
- ]);
394
- const tarPath = join(dir, "unsafe.tar");
395
- await writeFile(tarPath, tar);
396
-
397
- const result = await ingestBundle(tarPath);
398
- assert.equal(result.counts.skippedUnsafePaths, 2);
399
- assert.equal(result.findings.filter((f) => f.code === "unsafe-path").length, 2);
400
- assert.equal(result.counts.observed, 1);
401
- assert.equal(result.proofs[0]!.sources.length, 1);
402
- assert.equal(result.proofs[0]!.sources[0]!.path, "good/p.json");
403
- // Skipped entries are excluded from the contents hash.
404
- assert.equal(
405
- result.computedContentsHashB64,
406
- computeContentsHashB64([{ path: "good/p.json", content: utf8(proofJson(fxProof)) }])
407
- );
408
- });
409
-
410
- test("duplicate tar paths: the last entry wins and the duplication is reported", async () => {
411
- const dir = await newBundleDir();
412
- const tar = makeTar([
413
- { name: "data/blob.bin", content: "first version" },
414
- { name: "data/blob.bin", content: "second version" },
415
- // Root-level entry so the single-top-level-directory rule does not apply.
416
- { name: "notes.txt", content: "unrelated" },
417
- ]);
418
- const tarPath = join(dir, "dupes.tar");
419
- await writeFile(tarPath, tar);
420
-
421
- const result = await ingestBundle(tarPath);
422
- assert.ok(result.findings.find((f) => f.code === "duplicate-path" && f.path === "data/blob.bin"));
423
- const blob = result.artifacts.find((a) => a.paths.includes("data/blob.bin"))!;
424
- assert.equal(blob.byteLength, utf8("second version").length);
425
- // Exactly one entry per path participates: the shadowed first version is gone.
426
- assert.equal(
427
- result.artifacts.filter((a) => a.paths.includes("data/blob.bin")).length,
428
- 1
429
- );
430
- });
431
- });
432
-
433
- // ---------------------------------------------------------------------------
434
- // Untrusted-archive resource caps (gzip-bomb / entry-flood defense)
435
- // ---------------------------------------------------------------------------
436
-
437
- describe("audit ingest: untrusted-archive resource caps", () => {
438
- test("an entry count over the cap aborts cleanly instead of unbounded scanning", async () => {
439
- const dir = await newBundleDir();
440
- // Many zero-length entries: a small archive that would scan forever
441
- // under an unbounded reader.
442
- const entries = [];
443
- for (let i = 0; i < 200; i++) {
444
- entries.push({ name: `empty-${i}.bin`, content: new Uint8Array(0) });
445
- }
446
- const tarPath = join(dir, "flood.tar.gz");
447
- await writeFile(tarPath, gzipSync(makeTar(entries)));
448
-
449
- await assert.rejects(
450
- ingestBundle(tarPath, {
451
- maxEntryCount: 25,
452
- maxTotalBytes: 1 << 30,
453
- maxMetadataEntryBytes: 8 * 1024 * 1024,
454
- }),
455
- /archive exceeds the maximum of 25 entries/
456
- );
457
- });
458
-
459
- test("a total decompressed-byte ceiling aborts a bomb before it is fully read", async () => {
460
- const dir = await newBundleDir();
461
- // A single large artifact whose decompressed size exceeds a low cap.
462
- const big = new Uint8Array(512 * 1024).fill(0x41);
463
- const tarPath = join(dir, "big.tar.gz");
464
- await writeFile(tarPath, gzipSync(makeTar([{ name: "blob.bin", content: big }])));
465
-
466
- await assert.rejects(
467
- ingestBundle(tarPath, {
468
- maxEntryCount: 1_000_000,
469
- maxTotalBytes: 64 * 1024, // far below the 512 KiB payload
470
- maxMetadataEntryBytes: 8 * 1024 * 1024,
471
- }),
472
- /archive exceeds the maximum decompressed size of 65536 bytes/
473
- );
474
- });
475
-
476
- test("a PAX header declaring an absurd size aborts before allocating that buffer", async () => {
477
- const dir = await newBundleDir();
478
- // The PAX 'x' header claims 2 GiB; the archive itself is tiny. A reader
479
- // that trusted the declared size would try to allocate 2 GiB.
480
- const tar = makeTar([
481
- {
482
- name: "pax-truncated",
483
- paxPath: "renamed/proof.json",
484
- paxHeaderDeclaredSize: 2 * 1024 * 1024 * 1024,
485
- content: proofJson(fxProof),
486
- },
487
- ]);
488
- const tarPath = join(dir, "pax-bomb.tar.gz");
489
- await writeFile(tarPath, gzipSync(tar));
490
-
491
- // Default limits: the 8 MiB metadata cap trips before any allocation.
492
- await assert.rejects(
493
- ingestBundle(tarPath),
494
- /PAX metadata entry of \d+ bytes exceeds the \d+-byte cap/
495
- );
496
- });
497
-
498
- test("legitimate archives ingest normally under the default caps", async () => {
499
- const dir = await newBundleDir();
500
- const tarPath = join(dir, "ok.tar.gz");
501
- await writeFile(
502
- tarPath,
503
- gzipSync(
504
- makeTar([
505
- { name: "proofs/p.json", content: proofJson(fxProof) },
506
- { name: "media/original.bin", content: fxBytes },
507
- ])
508
- )
509
- );
510
- // No limits argument: DEFAULT_INGEST_LIMITS apply and never trip.
511
- const result = await ingestBundle(tarPath);
512
- assert.equal(result.counts.observed, 1);
513
- assert.equal(result.proofs[0]!.proofHash, fxHash);
514
- });
515
- });
516
-
517
- // ---------------------------------------------------------------------------
518
- // Deterministic contents hash vectors (docs/BUNDLE-FORMAT.md section 8.3)
519
- // ---------------------------------------------------------------------------
520
-
521
- describe("audit ingest: contents hash test vectors", () => {
522
- test("empty hashed set reproduces the published vector", () => {
523
- assert.equal(
524
- computeContentsHashB64([]),
525
- "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="
526
- );
527
- });
528
-
529
- test("two-entry set reproduces the published vector", () => {
530
- const entries = [
531
- // Deliberately supplied out of sorted order; the scheme sorts by path bytes.
532
- { path: "proofs/example.json", content: utf8('{"hello":"world"}\n') },
533
- { path: "artifacts/a.bin", content: new Uint8Array([0x00, 0x01, 0x02]) },
534
- ];
535
- assert.equal(
536
- computeContentsHashB64(entries),
537
- "uO+wswRbTl4WWwAuXVrdRjEVDs2jKq72iJhI95XoH3s="
538
- );
539
- });
540
-
541
- test("streaming ingest agrees with the one-shot vector implementation", async () => {
542
- const dir = await newBundleDir();
543
- await writeBundleDir(dir, {
544
- "artifacts/a.bin": new Uint8Array([0x00, 0x01, 0x02]),
545
- "proofs/example.json": utf8('{"hello":"world"}\n'),
546
- });
547
- const result = await ingestBundle(dir);
548
- assert.equal(result.computedContentsHashB64, "uO+wswRbTl4WWwAuXVrdRjEVDs2jKq72iJhI95XoH3s=");
549
- });
550
- });
551
-
552
- // ---------------------------------------------------------------------------
553
- // Witness discovery
554
- // ---------------------------------------------------------------------------
555
-
556
- describe("audit ingest: anchor witness discovery", () => {
557
- test("witnesses are discovered by version discriminator, never by filename", async () => {
558
- const dir = await newBundleDir();
559
- await writeBundleDir(dir, {
560
- "some/dir/whatever.dat": JSON.stringify({
561
- version: "bitgraph-anchor-witness/1",
562
- headerRlpHex: "0xf90201",
563
- blockNumber: 19000000,
564
- blockHash: `0x${"ab".repeat(32)}`,
565
- }),
566
- });
567
-
568
- const result = await ingestBundle(dir);
569
- assert.equal(result.counts.witnesses, 1);
570
- assert.equal(result.witnesses[0]!.path, "some/dir/whatever.dat");
571
- assert.equal(result.witnesses[0]!.witness["blockNumber"], 19000000);
572
- // Witnesses are not artifact candidates.
573
- assert.equal(result.artifacts.length, 0);
574
- });
575
- });