@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,575 +0,0 @@
1
- // Copyright (c) Mike Argento. 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
- });