@mikeargento/bitgraph-audit 0.1.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 (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +11 -0
  3. package/dist/anchors.d.ts +31 -0
  4. package/dist/anchors.d.ts.map +1 -0
  5. package/dist/anchors.js +116 -0
  6. package/dist/anchors.js.map +1 -0
  7. package/dist/anomalies.d.ts +42 -0
  8. package/dist/anomalies.d.ts.map +1 -0
  9. package/dist/anomalies.js +642 -0
  10. package/dist/anomalies.js.map +1 -0
  11. package/dist/attestation.d.ts +20 -0
  12. package/dist/attestation.d.ts.map +1 -0
  13. package/dist/attestation.js +795 -0
  14. package/dist/attestation.js.map +1 -0
  15. package/dist/audit.d.ts +19 -0
  16. package/dist/audit.d.ts.map +1 -0
  17. package/dist/audit.js +116 -0
  18. package/dist/audit.js.map +1 -0
  19. package/dist/authority.d.ts +37 -0
  20. package/dist/authority.d.ts.map +1 -0
  21. package/dist/authority.js +126 -0
  22. package/dist/authority.js.map +1 -0
  23. package/dist/aws-nitro-root-ca.d.ts +24 -0
  24. package/dist/aws-nitro-root-ca.d.ts.map +1 -0
  25. package/dist/aws-nitro-root-ca.js +44 -0
  26. package/dist/aws-nitro-root-ca.js.map +1 -0
  27. package/dist/cli.d.ts +3 -0
  28. package/dist/cli.d.ts.map +1 -0
  29. package/dist/cli.js +257 -0
  30. package/dist/cli.js.map +1 -0
  31. package/dist/contents-hash.d.ts +30 -0
  32. package/dist/contents-hash.d.ts.map +1 -0
  33. package/dist/contents-hash.js +73 -0
  34. package/dist/contents-hash.js.map +1 -0
  35. package/dist/export.d.ts +64 -0
  36. package/dist/export.d.ts.map +1 -0
  37. package/dist/export.js +192 -0
  38. package/dist/export.js.map +1 -0
  39. package/dist/index.d.ts +31 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +17 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/ingest.d.ts +36 -0
  44. package/dist/ingest.d.ts.map +1 -0
  45. package/dist/ingest.js +828 -0
  46. package/dist/ingest.js.map +1 -0
  47. package/dist/reconstruct.d.ts +40 -0
  48. package/dist/reconstruct.d.ts.map +1 -0
  49. package/dist/reconstruct.js +400 -0
  50. package/dist/reconstruct.js.map +1 -0
  51. package/dist/report-json.d.ts +3 -0
  52. package/dist/report-json.d.ts.map +1 -0
  53. package/dist/report-json.js +290 -0
  54. package/dist/report-json.js.map +1 -0
  55. package/dist/report-md.d.ts +3 -0
  56. package/dist/report-md.d.ts.map +1 -0
  57. package/dist/report-md.js +853 -0
  58. package/dist/report-md.js.map +1 -0
  59. package/dist/rlp.d.ts +37 -0
  60. package/dist/rlp.d.ts.map +1 -0
  61. package/dist/rlp.js +120 -0
  62. package/dist/rlp.js.map +1 -0
  63. package/dist/tar.d.ts +47 -0
  64. package/dist/tar.d.ts.map +1 -0
  65. package/dist/tar.js +483 -0
  66. package/dist/tar.js.map +1 -0
  67. package/dist/temporal.d.ts +55 -0
  68. package/dist/temporal.d.ts.map +1 -0
  69. package/dist/temporal.js +461 -0
  70. package/dist/temporal.js.map +1 -0
  71. package/dist/types.d.ts +1389 -0
  72. package/dist/types.d.ts.map +1 -0
  73. package/dist/types.js +3 -0
  74. package/dist/types.js.map +1 -0
  75. package/dist/validity.d.ts +21 -0
  76. package/dist/validity.d.ts.map +1 -0
  77. package/dist/validity.js +86 -0
  78. package/dist/validity.js.map +1 -0
  79. package/dist/verify-tiers.d.ts +11 -0
  80. package/dist/verify-tiers.d.ts.map +1 -0
  81. package/dist/verify-tiers.js +120 -0
  82. package/dist/verify-tiers.js.map +1 -0
  83. package/dist/witness.d.ts +25 -0
  84. package/dist/witness.d.ts.map +1 -0
  85. package/dist/witness.js +308 -0
  86. package/dist/witness.js.map +1 -0
  87. package/package.json +42 -0
  88. package/src/anchors.ts +168 -0
  89. package/src/anomalies.ts +773 -0
  90. package/src/attestation.ts +878 -0
  91. package/src/audit.ts +136 -0
  92. package/src/authority.ts +165 -0
  93. package/src/aws-nitro-root-ca.ts +47 -0
  94. package/src/cli.ts +284 -0
  95. package/src/contents-hash.ts +95 -0
  96. package/src/export.ts +278 -0
  97. package/src/index.ts +122 -0
  98. package/src/ingest.ts +967 -0
  99. package/src/reconstruct.ts +491 -0
  100. package/src/report-json.ts +331 -0
  101. package/src/report-md.ts +1067 -0
  102. package/src/rlp.ts +142 -0
  103. package/src/tar.ts +587 -0
  104. package/src/temporal.ts +629 -0
  105. package/src/types.ts +1528 -0
  106. package/src/validity.ts +90 -0
  107. package/src/verify-tiers.ts +137 -0
  108. package/src/witness.ts +407 -0
@@ -0,0 +1,331 @@
1
+ // Copyright (c) 2024-2026 Mike Argento. Licensed under the MIT License. See LICENSE.
2
+
3
+ /**
4
+ * JSON report builder: turns an AuditResult into the audit-report.json
5
+ * object (schema "bitgraph-audit-report/1").
6
+ *
7
+ * Contract for machine consumers: every classification is a stable code
8
+ * or typed field; prose (message, claim, explanation fields) is
9
+ * supplementary and never needs parsing. The report is deterministic
10
+ * given the same bundle except for the runMetadata block, which is
11
+ * explicitly flagged as the only nondeterministic section.
12
+ *
13
+ * Ordering: objects are built in fixed key order; arrays are either
14
+ * sorted here by stable keys (proofs by canonical hash, rejected inputs
15
+ * by path, unordered epoch pairs by epochId) or carry the producing
16
+ * stage's documented deterministic order (partitions, components,
17
+ * anomalies, divergences, witness outcomes, temporal segments).
18
+ *
19
+ * Read-only over the AuditResult. The raw parsed proof objects retained
20
+ * on ObservedProof records are never serialized; per-proof records carry
21
+ * the compact metadata fields only.
22
+ */
23
+
24
+ import { computeExitFlags } from "./audit.js";
25
+ import type {
26
+ AnchorOrderedPair,
27
+ AuditFinding,
28
+ AuditJsonReport,
29
+ AuditResult,
30
+ ChainAnomaly,
31
+ ObservedProof,
32
+ PartitionKey,
33
+ ReportAnomaly,
34
+ ReportPartition,
35
+ ReportProofRecord,
36
+ ReportSummary,
37
+ TemporalSegmentStatus,
38
+ UnorderedEpochPair,
39
+ } from "./types.js";
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // Public API
43
+ // ---------------------------------------------------------------------------
44
+
45
+ export function buildJsonReport(result: AuditResult): AuditJsonReport {
46
+ const proofs = buildProofRecords(result);
47
+ const unsupportedVersions = [...result.ingest.unsupportedVersions].sort((a, b) =>
48
+ a.path < b.path ? -1 : a.path > b.path ? 1 : 0
49
+ );
50
+ const partitions = buildPartitions(result);
51
+ const anomalies = buildUnifiedAnomalies(result);
52
+ const unorderedPairs = buildUnorderedEpochPairs(result);
53
+ const summary = buildSummary(result, partitions, anomalies);
54
+
55
+ return {
56
+ reportSchemaVersion: "bitgraph-audit-report/1",
57
+ toolVersion: result.runMetadata.toolVersion,
58
+ runMetadata: {
59
+ nondeterministic: true,
60
+ note:
61
+ "This block is the only nondeterministic section of the report. " +
62
+ "startedAt is the wall-clock start time of the audit run; every " +
63
+ "other value in this report is a pure function of the bundle " +
64
+ "contents and the tool version.",
65
+ toolVersion: result.runMetadata.toolVersion,
66
+ startedAt: result.runMetadata.startedAt,
67
+ bundlePath: result.runMetadata.bundlePath,
68
+ container: result.runMetadata.container,
69
+ },
70
+ input: {
71
+ container: result.ingest.container,
72
+ entriesScanned: result.ingest.entriesScanned,
73
+ ...(result.ingest.strippedRootPrefix !== undefined
74
+ ? { strippedRootPrefix: result.ingest.strippedRootPrefix }
75
+ : {}),
76
+ computedContentsHashB64: result.ingest.computedContentsHashB64,
77
+ ...(result.ingest.manifest !== undefined ? { manifest: result.ingest.manifest } : {}),
78
+ counts: {
79
+ observed: result.ingest.counts.observed,
80
+ proofFiles: result.ingest.counts.proofFiles,
81
+ exactDuplicates: result.ingest.counts.exactDuplicates,
82
+ semanticDuplicates: result.ingest.counts.semanticDuplicates,
83
+ unsupportedVersion: result.ingest.counts.unsupportedVersion,
84
+ verified: result.verification.verified,
85
+ failed: result.verification.failed,
86
+ artifactUnavailable: result.verification.artifactUnavailable,
87
+ chainless: result.verification.chainless,
88
+ artifacts: result.ingest.counts.artifacts,
89
+ witnesses: result.ingest.counts.witnesses,
90
+ skippedUnsafePaths: result.ingest.counts.skippedUnsafePaths,
91
+ },
92
+ },
93
+ proofs,
94
+ unsupportedVersions,
95
+ partitions,
96
+ unchainedProofHashes: result.reconstruction.unchainedProofHashes,
97
+ unpartitionedProofHashes: result.reconstruction.unpartitionedProofHashes,
98
+ epochRelationships: {
99
+ epochs: result.reconstruction.epochRelationships.epochs,
100
+ lineageEdges: result.reconstruction.epochRelationships.edges,
101
+ orderedPairs: result.reconstruction.epochRelationships.orderedPairs,
102
+ anchorOrderedPairs: result.temporal.anchorOrderedPairs,
103
+ unorderedPairs,
104
+ },
105
+ anomalies,
106
+ divergences: result.anomalies.divergences,
107
+ authorities: {
108
+ groups: result.authorities.groups,
109
+ sharedSignersAcrossEpochs: result.authorities.sharedSignersAcrossEpochs,
110
+ },
111
+ attestations: {
112
+ records: result.attestations.records,
113
+ counts: result.attestations.counts,
114
+ },
115
+ anchors: {
116
+ records: result.anchors.anchors,
117
+ metadataOnlyProofHashes: result.anchors.metadataOnlyProofHashes,
118
+ },
119
+ witnesses: {
120
+ outcomes: result.witnesses.outcomes,
121
+ },
122
+ temporal: {
123
+ segments: result.temporal.segments,
124
+ verifiedAnchorProofHashes: result.temporal.verifiedAnchorProofHashes,
125
+ unverifiedAnchorProofHashes: result.temporal.unverifiedAnchorProofHashes,
126
+ },
127
+ summary,
128
+ };
129
+ }
130
+
131
+ // ---------------------------------------------------------------------------
132
+ // Per-proof records
133
+ // ---------------------------------------------------------------------------
134
+
135
+ function buildProofRecords(result: AuditResult): ReportProofRecord[] {
136
+ const records = result.ingest.proofs.map((proof) => toProofRecord(proof));
137
+ records.sort((a, b) => (a.proofHash < b.proofHash ? -1 : a.proofHash > b.proofHash ? 1 : 0));
138
+ return records;
139
+ }
140
+
141
+ function toProofRecord(proof: ObservedProof): ReportProofRecord {
142
+ const v = proof.verification;
143
+ return {
144
+ proofHash: proof.proofHash,
145
+ sources: proof.sources,
146
+ ...(v !== undefined ? { verificationTier: v.tier, verificationStatus: v.status } : {}),
147
+ ...(v?.reason !== undefined ? { verificationReason: v.reason } : {}),
148
+ ...(v?.artifactPath !== undefined ? { artifactPath: v.artifactPath } : {}),
149
+ embeddedProofHash: proof.embeddedProofHash,
150
+ ...(proof.counter !== undefined ? { counter: proof.counter } : {}),
151
+ ...(proof.slotCounter !== undefined ? { slotCounter: proof.slotCounter } : {}),
152
+ ...(proof.prevB64 !== undefined ? { prevB64: proof.prevB64 } : {}),
153
+ ...(proof.epochId !== undefined ? { epochId: proof.epochId } : {}),
154
+ chainId: proof.chainId,
155
+ ...(proof.publicKeyB64 !== undefined ? { publicKeyB64: proof.publicKeyB64 } : {}),
156
+ ...(proof.measurement !== undefined ? { measurement: proof.measurement } : {}),
157
+ ...(proof.enforcement !== undefined ? { enforcement: proof.enforcement } : {}),
158
+ chainless: proof.chainless,
159
+ hasSlotAllocation: proof.hasSlotAllocation,
160
+ hasAttestation: proof.hasAttestation,
161
+ hasAgency: proof.hasAgency,
162
+ hasEpochLink: proof.hasEpochLink,
163
+ };
164
+ }
165
+
166
+ // ---------------------------------------------------------------------------
167
+ // Partitions with intactness
168
+ // ---------------------------------------------------------------------------
169
+
170
+ function partitionKeyId(key: PartitionKey): string {
171
+ return `${key.publicKeyB64}\u0000${key.epochId ?? ""}\u0000${key.chainId}`;
172
+ }
173
+
174
+ function buildPartitions(result: AuditResult): ReportPartition[] {
175
+ const anomalousPartitions = new Set<string>();
176
+ for (const anomaly of result.anomalies.anomalies) {
177
+ if (anomaly.partition !== undefined) anomalousPartitions.add(partitionKeyId(anomaly.partition));
178
+ }
179
+ for (const divergence of result.anomalies.divergences) {
180
+ if (divergence.partition !== undefined) {
181
+ anomalousPartitions.add(partitionKeyId(divergence.partition));
182
+ }
183
+ }
184
+ return result.reconstruction.partitions.map((partition) => ({
185
+ key: partition.key,
186
+ memberProofHashes: partition.memberProofHashes,
187
+ components: partition.components,
188
+ intact:
189
+ partition.components.length === 1 && !anomalousPartitions.has(partitionKeyId(partition.key)),
190
+ }));
191
+ }
192
+
193
+ // ---------------------------------------------------------------------------
194
+ // Unified anomaly list
195
+ // ---------------------------------------------------------------------------
196
+
197
+ function fromFinding(stage: ReportAnomaly["stage"], finding: AuditFinding): ReportAnomaly {
198
+ return {
199
+ stage,
200
+ code: finding.code,
201
+ message: finding.message,
202
+ ...(finding.path !== undefined ? { path: finding.path } : {}),
203
+ ...(finding.details !== undefined ? { details: finding.details } : {}),
204
+ };
205
+ }
206
+
207
+ function fromChainAnomaly(stage: ReportAnomaly["stage"], anomaly: ChainAnomaly): ReportAnomaly {
208
+ return {
209
+ stage,
210
+ code: anomaly.code,
211
+ message: anomaly.message,
212
+ ...(anomaly.partition !== undefined ? { partition: anomaly.partition } : {}),
213
+ proofHashes: anomaly.proofHashes,
214
+ ...(anomaly.details !== undefined ? { details: anomaly.details } : {}),
215
+ };
216
+ }
217
+
218
+ function buildUnifiedAnomalies(result: AuditResult): ReportAnomaly[] {
219
+ const out: ReportAnomaly[] = [];
220
+ for (const finding of result.ingest.findings) out.push(fromFinding("ingest", finding));
221
+ for (const anomaly of result.anomalies.anomalies) out.push(fromChainAnomaly("chain", anomaly));
222
+ for (const anomaly of result.authorities.anomalies) {
223
+ out.push(fromChainAnomaly("authority", anomaly));
224
+ }
225
+ for (const finding of result.anchors.findings) out.push(fromFinding("anchor", finding));
226
+ for (const finding of result.witnesses.findings) out.push(fromFinding("witness", finding));
227
+ for (const finding of result.attestations.findings) {
228
+ out.push(fromFinding("attestation", finding));
229
+ }
230
+ return out;
231
+ }
232
+
233
+ // ---------------------------------------------------------------------------
234
+ // Epoch pairs without ordering evidence
235
+ // ---------------------------------------------------------------------------
236
+
237
+ function buildUnorderedEpochPairs(result: AuditResult): UnorderedEpochPair[] {
238
+ const epochIds = result.reconstruction.epochRelationships.epochs.map((e) => e.epochId);
239
+ const ordered = new Set<string>();
240
+ const mark = (a: string, b: string): void => {
241
+ ordered.add(`${a}\u0000${b}`);
242
+ ordered.add(`${b}\u0000${a}`);
243
+ };
244
+ for (const pair of result.reconstruction.epochRelationships.orderedPairs) {
245
+ mark(pair.beforeEpochId, pair.afterEpochId);
246
+ }
247
+ for (const pair of result.temporal.anchorOrderedPairs as AnchorOrderedPair[]) {
248
+ mark(pair.beforeEpochId, pair.afterEpochId);
249
+ }
250
+ const out: UnorderedEpochPair[] = [];
251
+ for (let i = 0; i < epochIds.length; i++) {
252
+ for (let j = i + 1; j < epochIds.length; j++) {
253
+ const a = epochIds[i] as string;
254
+ const b = epochIds[j] as string;
255
+ if (!ordered.has(`${a}\u0000${b}`)) out.push({ epochIdA: a, epochIdB: b });
256
+ }
257
+ }
258
+ return out;
259
+ }
260
+
261
+ // ---------------------------------------------------------------------------
262
+ // Summary statistics
263
+ // ---------------------------------------------------------------------------
264
+
265
+ function buildSummary(
266
+ result: AuditResult,
267
+ partitions: ReportPartition[],
268
+ anomalies: ReportAnomaly[]
269
+ ): ReportSummary {
270
+ const countsByCode = new Map<string, number>();
271
+ for (const anomaly of anomalies) {
272
+ countsByCode.set(anomaly.code, (countsByCode.get(anomaly.code) ?? 0) + 1);
273
+ }
274
+ const anomalyCountsByCode = Object.fromEntries(
275
+ [...countsByCode.entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
276
+ );
277
+
278
+ const signers = new Set<string>();
279
+ const measurements = new Set<string>();
280
+ for (const proof of result.ingest.proofs) {
281
+ if (proof.publicKeyB64 !== undefined) signers.add(proof.publicKeyB64);
282
+ if (proof.measurement !== undefined) measurements.add(proof.measurement);
283
+ }
284
+
285
+ const segmentCount = (status: TemporalSegmentStatus): number =>
286
+ result.temporal.segments.filter((s) => s.status === status).length;
287
+
288
+ const partitionsIntact = partitions.filter((p) => p.intact).length;
289
+ const exit = computeExitFlags(result);
290
+
291
+ return {
292
+ proofsObserved: result.ingest.counts.observed,
293
+ fullyVerified: result.verification.verified,
294
+ failed: result.verification.failed,
295
+ artifactUnavailable: result.verification.artifactUnavailable,
296
+ unsupportedVersion: result.ingest.counts.unsupportedVersion,
297
+ chainless: result.verification.chainless,
298
+ exactDuplicates: result.ingest.counts.exactDuplicates,
299
+ semanticDuplicates: result.ingest.counts.semanticDuplicates,
300
+ partitions: partitions.length,
301
+ partitionsIntact,
302
+ chainIntact:
303
+ partitionsIntact === partitions.length &&
304
+ result.anomalies.anomalies.length === 0 &&
305
+ result.authorities.anomalies.length === 0 &&
306
+ result.anomalies.divergences.length === 0,
307
+ epochsObserved: result.reconstruction.epochRelationships.epochs.length,
308
+ anomalyCountsByCode,
309
+ divergenceCount: result.anomalies.divergences.length,
310
+ authorityGroupCount: result.authorities.groups.length,
311
+ distinctSignerCount: signers.size,
312
+ distinctDeclaredMeasurementCount: measurements.size,
313
+ attestation: {
314
+ declaredMeasurementPresent: result.attestations.counts.proofsWithDeclaredMeasurement,
315
+ documentsPresent: result.attestations.counts.proofsWithDocument,
316
+ documentsValidated: result.attestations.counts.documentsValidated,
317
+ pcr0MatchesDeclared: result.attestations.counts.pcr0Matches,
318
+ userDataBound: result.attestations.counts.userDataBound,
319
+ },
320
+ temporal: {
321
+ anchorsIdentified: result.anchors.anchors.length,
322
+ anchorsWithVerifiedWitness: result.temporal.verifiedAnchorProofHashes.length,
323
+ segments: result.temporal.segments.length,
324
+ segmentsBracketed: segmentCount("bracketed"),
325
+ segmentsLowerBounded: segmentCount("lower-bounded"),
326
+ segmentsUpperBounded: segmentCount("upper-bounded"),
327
+ segmentsUnanchored: segmentCount("ordered-but-unanchored"),
328
+ },
329
+ exit,
330
+ };
331
+ }