@mikeargento/bitgraph-audit 0.4.1 → 0.6.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.
- package/LICENSE +1 -1
- package/dist/anchors.d.ts +0 -24
- package/dist/anchors.d.ts.map +1 -1
- package/dist/anchors.js +54 -12
- package/dist/anchors.js.map +1 -1
- package/dist/anomalies.js +1 -1
- package/dist/anomalies.js.map +1 -1
- package/dist/attestation.js +1 -1
- package/dist/attestation.js.map +1 -1
- package/dist/audit.d.ts +1 -1
- package/dist/audit.js +2 -2
- package/dist/audit.js.map +1 -1
- package/dist/authority.js +1 -1
- package/dist/authority.js.map +1 -1
- package/dist/aws-nitro-root-ca.js +1 -1
- package/dist/aws-nitro-root-ca.js.map +1 -1
- package/dist/cli.js +70 -5
- package/dist/cli.js.map +1 -1
- package/dist/contents-hash.js +1 -1
- package/dist/contents-hash.js.map +1 -1
- package/dist/export.js +1 -1
- package/dist/export.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/ingest.js +1 -1
- package/dist/ingest.js.map +1 -1
- package/dist/reconstruct.js +1 -1
- package/dist/reconstruct.js.map +1 -1
- package/dist/report-json.js +1 -1
- package/dist/report-json.js.map +1 -1
- package/dist/report-md.js +1 -1
- package/dist/report-md.js.map +1 -1
- package/dist/rlp.js +1 -1
- package/dist/rlp.js.map +1 -1
- package/dist/tar.js +1 -1
- package/dist/tar.js.map +1 -1
- package/dist/temporal.js +1 -1
- package/dist/temporal.js.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/dist/validity.js +1 -1
- package/dist/validity.js.map +1 -1
- package/dist/verify-tiers.js +1 -1
- package/dist/verify-tiers.js.map +1 -1
- package/dist/witness.js +1 -1
- package/dist/witness.js.map +1 -1
- package/package.json +11 -2
- package/src/__tests__/anchors.test.ts +139 -0
- package/src/anchors.ts +31 -12
- package/src/anomalies.ts +1 -1
- package/src/attestation.ts +1 -1
- package/src/audit.ts +2 -2
- package/src/authority.ts +1 -1
- package/src/aws-nitro-root-ca.ts +1 -1
- package/src/cli.ts +64 -5
- package/src/contents-hash.ts +1 -1
- package/src/export.ts +1 -1
- package/src/index.ts +1 -1
- package/src/ingest.ts +1 -1
- package/src/reconstruct.ts +1 -1
- package/src/report-json.ts +1 -1
- package/src/report-md.ts +1 -1
- package/src/rlp.ts +1 -1
- package/src/tar.ts +1 -1
- package/src/temporal.ts +1 -1
- package/src/types.ts +1 -1
- package/src/validity.ts +1 -1
- package/src/verify-tiers.ts +1 -1
- package/src/witness.ts +1 -1
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026 Argento Computing Inc.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* What counts as an anchor, and where its block is read from.
|
|
5
|
+
*
|
|
6
|
+
* This package's job is rigour about anchors, and until now it had no test at
|
|
7
|
+
* all for the discriminator it documents. The discriminator also just changed:
|
|
8
|
+
* `attribution.name === "Ethereum Anchor"` was the only test, and it is now the
|
|
9
|
+
* fallback behind the signed `commit.anchor` that enclave v7 introduced.
|
|
10
|
+
*
|
|
11
|
+
* The case that matters is the last one. An anchor fused like every other
|
|
12
|
+
* artifact needs its attribution for the `bitgraph-fuse/1` marker, so the name
|
|
13
|
+
* is gone and the block has to come from `commit.anchor`. If that stops
|
|
14
|
+
* working, fused anchors become invisible to an audit.
|
|
15
|
+
*
|
|
16
|
+
* Run: node --test packages/audit/src/__tests__/anchors.test.ts
|
|
17
|
+
*/
|
|
18
|
+
import { test } from "node:test";
|
|
19
|
+
import assert from "node:assert/strict";
|
|
20
|
+
import { identifyAnchors } from "../anchors.ts";
|
|
21
|
+
import type { IngestResult, ObservedProof } from "../types.ts";
|
|
22
|
+
|
|
23
|
+
const BLOCK = 25928913;
|
|
24
|
+
const HASH = "0x0ce4f044093cdd272ce4d7f47903a2f77e9195c4a5267a3eb7200e3a7d9ba7fa";
|
|
25
|
+
|
|
26
|
+
function observed(proof: Record<string, unknown>, proofHash: string): ObservedProof {
|
|
27
|
+
return {
|
|
28
|
+
proofHash,
|
|
29
|
+
chainHash: `chain-${proofHash}`,
|
|
30
|
+
proof: proof as never,
|
|
31
|
+
sources: [],
|
|
32
|
+
version: "bitgraph/1",
|
|
33
|
+
chainId: "bitgraph:main",
|
|
34
|
+
} as unknown as ObservedProof;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function ingest(proofs: ObservedProof[]): IngestResult {
|
|
38
|
+
return { bundlePath: "test", container: "directory", proofs } as unknown as IngestResult;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Pre-v7: the signed name is the only thing saying anchor. Most of the ledger. */
|
|
42
|
+
const legacy = observed({
|
|
43
|
+
commit: { counter: "10956" },
|
|
44
|
+
attribution: {
|
|
45
|
+
name: "Ethereum Anchor",
|
|
46
|
+
title: `https://etherscan.io/block/${BLOCK}`,
|
|
47
|
+
message: HASH,
|
|
48
|
+
},
|
|
49
|
+
metadata: { type: "ethereum-anchor" },
|
|
50
|
+
}, "legacy");
|
|
51
|
+
|
|
52
|
+
/** v7 onward: both the signed mark and the name. */
|
|
53
|
+
const authenticated = observed({
|
|
54
|
+
commit: { counter: "10957", anchor: { blockNumber: BLOCK, blockHash: HASH } },
|
|
55
|
+
attribution: {
|
|
56
|
+
name: "Ethereum Anchor",
|
|
57
|
+
title: `https://etherscan.io/block/${BLOCK}`,
|
|
58
|
+
message: HASH,
|
|
59
|
+
},
|
|
60
|
+
metadata: { type: "ethereum-anchor" },
|
|
61
|
+
}, "authenticated");
|
|
62
|
+
|
|
63
|
+
/** Fused: attribution spent on the fuse marker, block only in commit.anchor. */
|
|
64
|
+
const fused = observed({
|
|
65
|
+
commit: { counter: "10958", anchor: { blockNumber: BLOCK, blockHash: HASH } },
|
|
66
|
+
attribution: { name: "bitgraph-fuse/1", title: "container/2" },
|
|
67
|
+
metadata: { type: "ethereum-anchor" },
|
|
68
|
+
}, "fused");
|
|
69
|
+
|
|
70
|
+
/** An unsigned claim with nothing behind it. Never an anchor. */
|
|
71
|
+
const metadataOnly = observed({
|
|
72
|
+
commit: { counter: "10959" },
|
|
73
|
+
attribution: { name: "Someone" },
|
|
74
|
+
metadata: { type: "ethereum-anchor" },
|
|
75
|
+
}, "metadata-only");
|
|
76
|
+
|
|
77
|
+
const notAnAnchor = observed({
|
|
78
|
+
commit: { counter: "10960" },
|
|
79
|
+
attribution: { name: "Someone" },
|
|
80
|
+
}, "ordinary");
|
|
81
|
+
|
|
82
|
+
test("a legacy anchor is still an anchor, and its block still comes from attribution", () => {
|
|
83
|
+
const { anchors, findings } = identifyAnchors(ingest([legacy]));
|
|
84
|
+
assert.equal(anchors.length, 1, "the attribution name must keep identifying pre-v7 anchors");
|
|
85
|
+
assert.equal(anchors[0].blockHash, HASH);
|
|
86
|
+
assert.equal(anchors[0].blockNumber, String(BLOCK));
|
|
87
|
+
assert.equal(findings.length, 0);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("an authenticated anchor reads its block from the signed commit.anchor", () => {
|
|
91
|
+
const { anchors, findings } = identifyAnchors(ingest([authenticated]));
|
|
92
|
+
assert.equal(anchors.length, 1);
|
|
93
|
+
assert.equal(anchors[0].blockHash, HASH);
|
|
94
|
+
assert.equal(anchors[0].blockNumber, String(BLOCK));
|
|
95
|
+
assert.equal(findings.length, 0);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("a fused anchor is identified with no attribution name at all", () => {
|
|
99
|
+
// The whole point of the migration: attribution is spent on the fuse marker.
|
|
100
|
+
const { anchors, findings } = identifyAnchors(ingest([fused]));
|
|
101
|
+
assert.equal(anchors.length, 1, "commit.anchor must identify an anchor on its own");
|
|
102
|
+
assert.equal(anchors[0].blockHash, HASH, "the block must be readable without attribution");
|
|
103
|
+
assert.equal(anchors[0].blockNumber, String(BLOCK));
|
|
104
|
+
assert.equal(
|
|
105
|
+
findings.filter((f) => f.code === "anchor-title-unparseable").length,
|
|
106
|
+
0,
|
|
107
|
+
"an authenticated anchor does not need a parseable title, so absence is not a finding",
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("an unsigned metadata claim is recorded and refused", () => {
|
|
112
|
+
const { anchors, metadataOnlyProofHashes, findings } = identifyAnchors(ingest([metadataOnly]));
|
|
113
|
+
assert.equal(anchors.length, 0);
|
|
114
|
+
assert.deepEqual(metadataOnlyProofHashes, ["metadata-only"]);
|
|
115
|
+
assert.equal(findings.filter((f) => f.code === "anchor-metadata-only-claim").length, 1);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("an ordinary proof is not an anchor and raises nothing", () => {
|
|
119
|
+
const { anchors, metadataOnlyProofHashes, findings } = identifyAnchors(ingest([notAnAnchor]));
|
|
120
|
+
assert.equal(anchors.length, 0);
|
|
121
|
+
assert.equal(metadataOnlyProofHashes.length, 0);
|
|
122
|
+
assert.equal(findings.length, 0);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("a legacy anchor without a parseable title still reports that", () => {
|
|
126
|
+
const noTitle = observed({
|
|
127
|
+
commit: { counter: "1" },
|
|
128
|
+
attribution: { name: "Ethereum Anchor", message: HASH },
|
|
129
|
+
}, "legacy-no-title");
|
|
130
|
+
const { anchors, findings } = identifyAnchors(ingest([noTitle]));
|
|
131
|
+
assert.equal(anchors.length, 1);
|
|
132
|
+
assert.equal(anchors[0].blockNumber, undefined);
|
|
133
|
+
assert.equal(findings.filter((f) => f.code === "anchor-title-unparseable").length, 1);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("all forms are found together, in observation order", () => {
|
|
137
|
+
const { anchors } = identifyAnchors(ingest([legacy, notAnAnchor, authenticated, metadataOnly, fused]));
|
|
138
|
+
assert.deepEqual(anchors.map((a) => a.proofHash), ["legacy", "authenticated", "fused"]);
|
|
139
|
+
});
|
package/src/anchors.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Copyright (c) 2024-2026
|
|
1
|
+
// Copyright (c) 2024-2026 Argento Computing Inc. Licensed under the MIT License. See LICENSE.
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* bitgraph-audit anchor identification (G5)
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
* verification status; identification itself reads only the signed body.
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
+
import { anchorKindOf, anchorMarkOf } from "@mikeargento/bitgraph-verify";
|
|
28
29
|
import type {
|
|
29
30
|
AnchorIdentification,
|
|
30
31
|
AnchorRecord,
|
|
@@ -33,7 +34,6 @@ import type {
|
|
|
33
34
|
ObservedProof,
|
|
34
35
|
} from "./types.js";
|
|
35
36
|
|
|
36
|
-
const ANCHOR_ATTRIBUTION_NAME = "Ethereum Anchor";
|
|
37
37
|
const ANCHOR_METADATA_TYPE = "ethereum-anchor";
|
|
38
38
|
|
|
39
39
|
/**
|
|
@@ -54,11 +54,16 @@ export function identifyAnchors(ingest: IngestResult): AnchorIdentification {
|
|
|
54
54
|
const findings: AuditFinding[] = [];
|
|
55
55
|
|
|
56
56
|
for (const proof of ingest.proofs) {
|
|
57
|
-
const attributionName = readAttributionField(proof, "name");
|
|
58
57
|
const metadataType = readMetadataType(proof);
|
|
59
|
-
|
|
58
|
+
// ⚠️ Two forms, and they are not equally strong. "authenticated" means the
|
|
59
|
+
// enclave wrote commit.anchor after verifying the anchor service's
|
|
60
|
+
// signature over the claim; "legacy-attribution" means only the signed
|
|
61
|
+
// name says so, which is every anchor written before enclave v7
|
|
62
|
+
// (2026-09-06) and most of the ledger. Both are anchors; only one is
|
|
63
|
+
// backed by something inside the proof.
|
|
64
|
+
const kind = anchorKindOf(proof.proof);
|
|
60
65
|
|
|
61
|
-
if (
|
|
66
|
+
if (kind === null) {
|
|
62
67
|
if (metadataType === ANCHOR_METADATA_TYPE) {
|
|
63
68
|
// Unsigned claim with no signed backing: recorded, never trusted.
|
|
64
69
|
metadataOnlyProofHashes.push(proof.proofHash);
|
|
@@ -66,9 +71,9 @@ export function identifyAnchors(ingest: IngestResult): AnchorIdentification {
|
|
|
66
71
|
code: "anchor-metadata-only-claim",
|
|
67
72
|
...pathOf(proof),
|
|
68
73
|
message:
|
|
69
|
-
"unsigned metadata.type claims ethereum-anchor, but the
|
|
70
|
-
"
|
|
71
|
-
"not treated as an anchor.",
|
|
74
|
+
"unsigned metadata.type claims ethereum-anchor, but the proof carries neither a signed " +
|
|
75
|
+
"commit.anchor nor the signed attribution name. Metadata is advisory and unsigned; the " +
|
|
76
|
+
"proof is not treated as an anchor.",
|
|
72
77
|
details: { proofHash: proof.proofHash },
|
|
73
78
|
});
|
|
74
79
|
}
|
|
@@ -89,16 +94,30 @@ export function identifyAnchors(ingest: IngestResult): AnchorIdentification {
|
|
|
89
94
|
code: "anchor-metadata-disagreement",
|
|
90
95
|
...pathOf(proof),
|
|
91
96
|
message:
|
|
92
|
-
`
|
|
97
|
+
`this proof is a signed Ethereum anchor, but unsigned metadata.type is ` +
|
|
93
98
|
`${JSON.stringify(metadataType)}. The signed field governs; the disagreement is recorded.`,
|
|
94
99
|
details: { proofHash: proof.proofHash, metadataType: metadataType as string },
|
|
95
100
|
});
|
|
96
101
|
}
|
|
97
102
|
|
|
98
|
-
|
|
103
|
+
// An authenticated anchor carries its block in commit.anchor, which the
|
|
104
|
+
// enclave signed. Reading it from there rather than from attribution is
|
|
105
|
+
// what lets an anchor's attribution be used for something else — a
|
|
106
|
+
// bitgraph-fuse/1 marker, for instance — without the block becoming
|
|
107
|
+
// unreadable. Legacy anchors keep the old path exactly.
|
|
108
|
+
const authenticated = anchorMarkOf(proof.proof);
|
|
109
|
+
const blockHash = authenticated?.blockHash ?? readAttributionField(proof, "message");
|
|
99
110
|
const title = readAttributionField(proof, "title");
|
|
100
|
-
const blockNumber =
|
|
101
|
-
|
|
111
|
+
const blockNumber =
|
|
112
|
+
authenticated !== null
|
|
113
|
+
? String(authenticated.blockNumber)
|
|
114
|
+
: title !== undefined
|
|
115
|
+
? (ETHERSCAN_BLOCK_URL.exec(title)?.[1] ?? undefined)
|
|
116
|
+
: undefined;
|
|
117
|
+
// Only a legacy anchor depends on the title for its block number; an
|
|
118
|
+
// authenticated one has already answered, so an absent title is not a
|
|
119
|
+
// finding against it.
|
|
120
|
+
if (blockNumber === undefined && kind === "legacy-attribution") {
|
|
102
121
|
findings.push({
|
|
103
122
|
code: "anchor-title-unparseable",
|
|
104
123
|
...pathOf(proof),
|
package/src/anomalies.ts
CHANGED
package/src/attestation.ts
CHANGED
package/src/audit.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Copyright (c) 2024-2026
|
|
1
|
+
// Copyright (c) 2024-2026 Argento Computing Inc. Licensed under the MIT License. See LICENSE.
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Audit orchestrator: the full offline pipeline in canonical order.
|
|
@@ -37,7 +37,7 @@ import type { AuditOptions, AuditResult, ExitFlags, IngestResult } from "./types
|
|
|
37
37
|
* this equals package.json's version, so the constant cannot drift silently
|
|
38
38
|
* across releases.
|
|
39
39
|
*/
|
|
40
|
-
export const AUDIT_VERSION = "0.
|
|
40
|
+
export const AUDIT_VERSION = "0.6.0";
|
|
41
41
|
|
|
42
42
|
/** The audit package's own version. */
|
|
43
43
|
export function auditToolVersion(): string {
|
package/src/authority.ts
CHANGED
package/src/aws-nitro-root-ca.ts
CHANGED
package/src/cli.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Copyright (c) 2024-2026
|
|
2
|
+
// Copyright (c) 2024-2026 Argento Computing Inc. Licensed under the MIT License. See LICENSE.
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* bitgraph-audit CLI: run the full offline audit pipeline over a bundle
|
|
@@ -18,8 +18,10 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { readFileSync } from "node:fs";
|
|
21
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
22
|
-
import {
|
|
21
|
+
import { mkdir, mkdtemp, stat, writeFile } from "node:fs/promises";
|
|
22
|
+
import { tmpdir } from "node:os";
|
|
23
|
+
import { join, basename } from "node:path";
|
|
24
|
+
import { parseCarrier, verifyCarrier } from "@mikeargento/bitgraph-verify";
|
|
23
25
|
import type { VerificationPolicy } from "@mikeargento/bitgraph-verify";
|
|
24
26
|
import { auditToolVersion, computeExitFlags, runAudit } from "./audit.js";
|
|
25
27
|
import { buildJsonReport } from "./report-json.js";
|
|
@@ -57,7 +59,11 @@ function helpText(): string {
|
|
|
57
59
|
"",
|
|
58
60
|
USAGE_LINE,
|
|
59
61
|
"",
|
|
60
|
-
"The bundle may be a directory, a .tar archive,
|
|
62
|
+
"The bundle may be a directory, a .tar archive, a .tar.gz/.tgz,",
|
|
63
|
+
"or a single bitgraph-carrier/1 file (a file with its proof inside):",
|
|
64
|
+
"a carrier is unpacked and audited as the bundle it carries, and its",
|
|
65
|
+
"own verdict (TRUE / FALSE / UNDETERMINED, with the time window) is",
|
|
66
|
+
"printed first.",
|
|
61
67
|
"archive. The audit runs entirely offline: no RPC, no HTTP, no DNS.",
|
|
62
68
|
"",
|
|
63
69
|
"Options:",
|
|
@@ -236,10 +242,59 @@ async function main(): Promise<number> {
|
|
|
236
242
|
throw err;
|
|
237
243
|
}
|
|
238
244
|
|
|
245
|
+
/* A single carrier file: unpack it to a temp bundle (the committed bytes,
|
|
246
|
+
the proof, the floor anchor and witness, the ceiling pair when present)
|
|
247
|
+
and audit THAT, after printing the carrier's own offline verdict. The
|
|
248
|
+
temp dir is the audit's input, so the reports describe exactly what the
|
|
249
|
+
file carries; everything else about the run is unchanged. Detection is
|
|
250
|
+
from the last 8 bytes only, so no ordinary bundle path changes behaviour. */
|
|
251
|
+
let bundlePath = parsed.bundlePath;
|
|
252
|
+
let carrierFlags = 0;
|
|
253
|
+
try {
|
|
254
|
+
const info = await stat(parsed.bundlePath);
|
|
255
|
+
const lower = parsed.bundlePath.toLowerCase();
|
|
256
|
+
const isArchive = lower.endsWith(".tar") || lower.endsWith(".tar.gz") || lower.endsWith(".tgz");
|
|
257
|
+
if (info.isFile() && !isArchive && info.size >= 26) {
|
|
258
|
+
const bytes = new Uint8Array(readFileSync(parsed.bundlePath));
|
|
259
|
+
const parsedCarrier = parseCarrier(bytes);
|
|
260
|
+
if (parsedCarrier.kind !== "none") {
|
|
261
|
+
const verdict = await verifyCarrier(bytes);
|
|
262
|
+
const b = verdict.bounds;
|
|
263
|
+
const lines = [
|
|
264
|
+
`carrier: ${verdict.verdict}${verdict.carrier === "corrupt" ? " (block unreadable: corrupted, not judged)" : ""}`,
|
|
265
|
+
b ? ` not before: block ${b.notBefore.blockNumber}${b.notBefore.timestamp !== null ? ` (${new Date(b.notBefore.timestamp * 1000).toISOString()})` : ""}` : null,
|
|
266
|
+
b ? ` not after: ${b.notAfter === null ? "NOT FETCHED (the closing anchor is not inside this file)" : `block ${b.notAfter.blockNumber}${b.notAfter.timestamp !== null ? ` (${new Date(b.notAfter.timestamp * 1000).toISOString()})` : ""}`}` : null,
|
|
267
|
+
...verdict.reasons.map((r) => ` - ${r}`),
|
|
268
|
+
].filter((l): l is string => l !== null);
|
|
269
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
270
|
+
if (verdict.verdict === "FALSE") carrierFlags |= 1;
|
|
271
|
+
if (verdict.carrier === "corrupt") carrierFlags |= 2;
|
|
272
|
+
if (parsedCarrier.kind === "carrier") {
|
|
273
|
+
const dir = await mkdtemp(join(tmpdir(), "bitgraph-carrier-"));
|
|
274
|
+
const name = basename(parsed.bundlePath).replace(/\.bitgraph(\.[^.]+)$/i, "$1").replace(/\.bitgraph$/i, "") || "artifact";
|
|
275
|
+
await writeFile(join(dir, name), parsedCarrier.inner);
|
|
276
|
+
await writeFile(join(dir, "proof.json"), JSON.stringify(parsedCarrier.payload.proof, null, 2));
|
|
277
|
+
const anchors = join(dir, "ethereum-anchors");
|
|
278
|
+
await mkdir(anchors);
|
|
279
|
+
await writeFile(join(anchors, "anchor-floor.json"), JSON.stringify(parsedCarrier.payload.floor.anchor, null, 2));
|
|
280
|
+
await writeFile(join(anchors, "anchor-floor.witness.json"), JSON.stringify({ version: "bitgraph-anchor-witness/1", ...parsedCarrier.payload.floor.witness }, null, 2));
|
|
281
|
+
if (parsedCarrier.payload.ceiling.status === "present") {
|
|
282
|
+
await writeFile(join(anchors, "anchor-ceiling.json"), JSON.stringify(parsedCarrier.payload.ceiling.anchor, null, 2));
|
|
283
|
+
await writeFile(join(anchors, "anchor-ceiling.witness.json"), JSON.stringify({ version: "bitgraph-anchor-witness/1", ...parsedCarrier.payload.ceiling.witness }, null, 2));
|
|
284
|
+
}
|
|
285
|
+
bundlePath = dir;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
} catch (err) {
|
|
290
|
+
// Detection must never take down an ordinary audit: fall through whole.
|
|
291
|
+
process.stderr.write(`bitgraph-audit: carrier detection skipped: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
292
|
+
}
|
|
293
|
+
|
|
239
294
|
let result;
|
|
240
295
|
try {
|
|
241
296
|
result = await runAudit(
|
|
242
|
-
|
|
297
|
+
bundlePath,
|
|
243
298
|
trustAnchors !== undefined ? { trustAnchors } : undefined
|
|
244
299
|
);
|
|
245
300
|
} catch (err) {
|
|
@@ -264,6 +319,10 @@ async function main(): Promise<number> {
|
|
|
264
319
|
}
|
|
265
320
|
|
|
266
321
|
const flags = computeExitFlags(result);
|
|
322
|
+
// A carrier target folds its own verdict into the same two bits.
|
|
323
|
+
if (carrierFlags & 1) { flags.verificationFailures = true; }
|
|
324
|
+
if (carrierFlags & 2) { flags.chainAnomaliesOrDivergences = true; }
|
|
325
|
+
flags.code = flags.code | carrierFlags;
|
|
267
326
|
process.stdout.write(
|
|
268
327
|
`bitgraph-audit ${auditToolVersion()}: wrote ${written.join(", ")}\n` +
|
|
269
328
|
`exit ${flags.code}: ${exitMeaning(flags)}\n`
|
package/src/contents-hash.ts
CHANGED
package/src/export.ts
CHANGED
package/src/index.ts
CHANGED
package/src/ingest.ts
CHANGED
package/src/reconstruct.ts
CHANGED
package/src/report-json.ts
CHANGED
package/src/report-md.ts
CHANGED
package/src/rlp.ts
CHANGED
package/src/tar.ts
CHANGED
package/src/temporal.ts
CHANGED
package/src/types.ts
CHANGED
package/src/validity.ts
CHANGED
package/src/verify-tiers.ts
CHANGED
package/src/witness.ts
CHANGED