@mikeargento/bitgraph-mcp 0.5.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/README.md +1 -1
- package/dist/__tests__/carrier.test.d.ts +2 -0
- package/dist/__tests__/carrier.test.d.ts.map +1 -0
- package/dist/__tests__/carrier.test.js +243 -0
- package/dist/__tests__/carrier.test.js.map +1 -0
- package/dist/__tests__/encoding.test.js +1 -1
- package/dist/__tests__/encoding.test.js.map +1 -1
- package/dist/__tests__/format.test.js +6 -6
- package/dist/__tests__/format.test.js.map +1 -1
- package/dist/__tests__/scan.test.js +1 -1
- package/dist/__tests__/scan.test.js.map +1 -1
- package/dist/__tests__/tools.test.js +8 -7
- package/dist/__tests__/tools.test.js.map +1 -1
- package/dist/api.js +1 -1
- package/dist/api.js.map +1 -1
- package/dist/carrier-io.d.ts +52 -0
- package/dist/carrier-io.d.ts.map +1 -0
- package/dist/carrier-io.js +91 -0
- package/dist/carrier-io.js.map +1 -0
- package/dist/encoding.js +1 -1
- package/dist/encoding.js.map +1 -1
- package/dist/format.d.ts +17 -3
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +36 -15
- package/dist/format.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/scan.d.ts +4 -0
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +44 -1
- package/dist/scan.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +151 -29
- package/dist/server.js.map +1 -1
- package/dist/task.d.ts.map +1 -1
- package/dist/task.js +2 -2
- package/dist/task.js.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +13 -5
- package/src/__tests__/carrier.test.ts +271 -0
- package/src/__tests__/encoding.test.ts +1 -1
- package/src/__tests__/format.test.ts +6 -6
- package/src/__tests__/scan.test.ts +1 -1
- package/src/__tests__/tools.test.ts +8 -7
- package/src/api.ts +1 -1
- package/src/carrier-io.ts +120 -0
- package/src/encoding.ts +1 -1
- package/src/format.ts +56 -19
- package/src/index.ts +1 -1
- package/src/scan.ts +48 -1
- package/src/server.ts +169 -34
- package/src/task.ts +2 -2
- package/src/types.ts +1 -1
package/dist/scan.d.ts
CHANGED
|
@@ -29,7 +29,11 @@ export interface ScannedFile {
|
|
|
29
29
|
* then needs the bytes again.
|
|
30
30
|
*/
|
|
31
31
|
state: Hash | null;
|
|
32
|
+
/** True when the bytes carry what looks like a C2PA manifest (Content Credentials). */
|
|
33
|
+
c2pa: boolean;
|
|
32
34
|
}
|
|
35
|
+
/** One-shot form for bytes already in memory (a carrier's committed bytes). */
|
|
36
|
+
export declare function sniffC2paBytes(bytes: Uint8Array): boolean;
|
|
33
37
|
/** Hash one file in a single pass. Throws, before any network call, when the path is not a regular file. */
|
|
34
38
|
export declare function scanFile(path: string): Promise<ScannedFile>;
|
|
35
39
|
/**
|
package/dist/scan.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAc,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;AAOpD,6FAA6F;AAC7F,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,aAAa,CAAC;AAExD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,UAAU,CAAC;IACzB,SAAS,EAAE,aAAa,CAAC;IACzB;;;;OAIG;IACH,KAAK,EAAE,IAAI,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAc,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;AAOpD,6FAA6F;AAC7F,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,aAAa,CAAC;AAExD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,UAAU,CAAC;IACzB,SAAS,EAAE,aAAa,CAAC;IACzB;;;;OAIG;IACH,KAAK,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,uFAAuF;IACvF,IAAI,EAAE,OAAO,CAAC;CACf;AAuCD,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAEzD;AAKD,4GAA4G;AAC5G,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAuDjE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,GAAG,UAAU,CAMpF;AAED,MAAM,WAAW,SAAS;IACxB,uGAAuG;IACvG,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,+CAA+C;IAC/C,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CA2C7F"}
|
package/dist/scan.js
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
|
* @mikeargento/bitgraph-mcp: the scan.
|
|
4
4
|
*
|
|
@@ -19,6 +19,46 @@ import { readdir, stat } from "node:fs/promises";
|
|
|
19
19
|
import { basename, join } from "node:path";
|
|
20
20
|
import { placementForBytes } from "@mikeargento/bitgraph";
|
|
21
21
|
import { getPlacement } from "@mikeargento/bitgraph-verify";
|
|
22
|
+
/**
|
|
23
|
+
* Content Credentials detection, structural and cheap: a C2PA manifest lives
|
|
24
|
+
* in a JUMBF box whose description ("jumd") is followed within a few bytes by
|
|
25
|
+
* the store label "c2pa", in every container (JPEG APP11, PNG caBX, ISO BMFF).
|
|
26
|
+
* Requiring the pair in order and near each other keeps a stray "c2pa" in
|
|
27
|
+
* prose from counting. Detection, not judgment: the proof page parses and
|
|
28
|
+
* displays the manifest itself.
|
|
29
|
+
*/
|
|
30
|
+
const JUMD = Buffer.from("jumd", "ascii");
|
|
31
|
+
const C2PA_LABEL = Buffer.from("c2pa", "ascii");
|
|
32
|
+
const C2PA_NEAR = 96;
|
|
33
|
+
function windowHasC2pa(buf) {
|
|
34
|
+
for (let i = buf.indexOf(JUMD); i !== -1; i = buf.indexOf(JUMD, i + 1)) {
|
|
35
|
+
const from = i + JUMD.length;
|
|
36
|
+
const to = Math.min(buf.length, i + C2PA_NEAR);
|
|
37
|
+
if (buf.subarray(from, to).indexOf(C2PA_LABEL) !== -1)
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
class C2paSniffer {
|
|
43
|
+
found = false;
|
|
44
|
+
/** The last C2PA_NEAR + 4 bytes of the previous chunk, so a pair split across chunks is seen whole. */
|
|
45
|
+
carry = Buffer.alloc(0);
|
|
46
|
+
update(chunk) {
|
|
47
|
+
if (this.found)
|
|
48
|
+
return;
|
|
49
|
+
const buf = this.carry.length > 0 ? Buffer.concat([this.carry, chunk]) : chunk;
|
|
50
|
+
if (windowHasC2pa(buf)) {
|
|
51
|
+
this.found = true;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const keep = Math.min(buf.length, C2PA_NEAR + JUMD.length);
|
|
55
|
+
this.carry = Buffer.from(buf.subarray(buf.length - keep));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** One-shot form for bytes already in memory (a carrier's committed bytes). */
|
|
59
|
+
export function sniffC2paBytes(bytes) {
|
|
60
|
+
return windowHasC2pa(Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength));
|
|
61
|
+
}
|
|
22
62
|
/** Bytes the placement decision reads: every magic number sits in the first 16. */
|
|
23
63
|
const SNIFF = 64;
|
|
24
64
|
/** Hash one file in a single pass. Throws, before any network call, when the path is not a regular file. */
|
|
@@ -40,10 +80,12 @@ export async function scanFile(path) {
|
|
|
40
80
|
if (prefix !== null && prefix.length > 0)
|
|
41
81
|
run.fused = createHash("sha256").update(prefix);
|
|
42
82
|
};
|
|
83
|
+
const sniffer = new C2paSniffer();
|
|
43
84
|
const feed = (chunk) => {
|
|
44
85
|
origin.update(chunk);
|
|
45
86
|
if (run.fused !== null)
|
|
46
87
|
run.fused.update(chunk);
|
|
88
|
+
sniffer.update(chunk);
|
|
47
89
|
};
|
|
48
90
|
for await (const raw of createReadStream(path)) {
|
|
49
91
|
const chunk = raw;
|
|
@@ -78,6 +120,7 @@ export async function scanFile(path) {
|
|
|
78
120
|
originDigest: Uint8Array.from(digest),
|
|
79
121
|
placement,
|
|
80
122
|
state,
|
|
123
|
+
c2pa: sniffer.found,
|
|
81
124
|
};
|
|
82
125
|
}
|
|
83
126
|
/**
|
package/dist/scan.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan.js","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"scan.js","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { type FuseSetProgress } from "@mikeargento/bitgraph";
|
|
|
17
17
|
import { type ApiConfig } from "./api.js";
|
|
18
18
|
import { type ScannedFile } from "./scan.js";
|
|
19
19
|
import type { BitGraphProof } from "./types.js";
|
|
20
|
-
export declare const SERVER_VERSION = "0.
|
|
20
|
+
export declare const SERVER_VERSION = "0.6.0";
|
|
21
21
|
/** Files one call may BitGraph after directories expand: one set. The site's own ceiling for a set/2. */
|
|
22
22
|
export declare const MAX_MEMBERS = 100000;
|
|
23
23
|
/** Rows the structured result lists in full; every fused row shares the set's position. */
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAKpE,OAAO,EAA4F,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACvJ,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,UAAU,CAAC;AAmBlB,OAAO,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAKpE,OAAO,EAA4F,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACvJ,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,UAAU,CAAC;AAmBlB,OAAO,EAAyD,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAIpG,OAAO,KAAK,EAAE,aAAa,EAAuB,MAAM,YAAY,CAAC;AAErE,eAAO,MAAM,cAAc,UAAU,CAAC;AAKtC,yGAAyG;AACzG,eAAO,MAAM,WAAW,SAAU,CAAC;AAOnC,2FAA2F;AAC3F,eAAO,MAAM,OAAO,MAAM,CAAC;AAC3B,qEAAqE;AACrE,eAAO,MAAM,eAAe,OAAO,CAAC;AAEpC,mFAAmF;AACnF,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC;IACvB,KAAK,EAAE,aAAa,CAAC;IACrB,wDAAwD;IACxD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,OAAO,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,yCAAyC;IACzC,OAAO,EAAE,KAAK,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,mDAAmD;QACnD,aAAa,EAAE,MAAM,CAAC;QACtB,SAAS,EAAE,MAAM,CAAC;QAClB,eAAe,EAAE,MAAM,CAAC;QACxB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,+DAA+D;QAC/D,WAAW,CAAC,EAAE,OAAO,CAAC;KACvB,CAAC,CAAC;CACJ;AACD,MAAM,MAAM,SAAS,GAAG,CACtB,KAAK,EAAE,SAAS,WAAW,EAAE,EAC7B,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE;IAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,eAAe,KAAK,IAAI,CAAA;CAAE,KACxE,OAAO,CAAC,UAAU,CAAC,CAAC;AAEzB,8CAA8C;AAC9C,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,aAAa,CAAC;IACrB,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;CACzB;AACD,MAAM,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;AAEzF,MAAM,WAAW,UAAU;IACzB,oHAAoH;IACpH,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,yHAAyH;IACzH,QAAQ,CAAC,EAAE,UAAU,CAAC;CACvB;AAyND,2EAA2E;AAC3E,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAmCD,wBAAgB,WAAW,CAAC,IAAI,GAAE,UAAe,GAAG,SAAS,CAunB5D"}
|
package/dist/server.js
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
|
* @mikeargento/bitgraph-mcp: tool definitions.
|
|
4
4
|
*
|
|
@@ -20,9 +20,11 @@ import { FuseError, MAX_SET_MEMBERS, builderFor, fuse, fuseSet, fusedNamesFor }
|
|
|
20
20
|
import { ApiError, batchCheck, configFromEnv, getProofDetail, indexSetMembers, search, } from "./api.js";
|
|
21
21
|
import { fromUrlSafeB64, looksLikeDigest, mapConcurrent, sha256FileB64, toUrlSafeB64, } from "./encoding.js";
|
|
22
22
|
import { capJson, positionOf, proofUrl, renderCheckMarkdown, renderProofMarkdown, renderRecordMarkdown, } from "./format.js";
|
|
23
|
-
import { expandPaths, fusedDigestFor, scanFile } from "./scan.js";
|
|
23
|
+
import { expandPaths, fusedDigestFor, scanFile, sniffC2paBytes } from "./scan.js";
|
|
24
|
+
import { readCarrierFile, sniffCarrierTail } from "./carrier-io.js";
|
|
25
|
+
import { stat } from "node:fs/promises";
|
|
24
26
|
import { SLOT_TTL_SECONDS, TASK_INSTRUCTIONS, beginTask, decodeTaskToken, sealTask, writeProofBeside } from "./task.js";
|
|
25
|
-
export const SERVER_VERSION = "0.
|
|
27
|
+
export const SERVER_VERSION = "0.6.0";
|
|
26
28
|
const SCAN_CONCURRENCY = 4;
|
|
27
29
|
/** Paths per call; a directory counts once and expands to its files. */
|
|
28
30
|
const MAX_PATHS = 2000;
|
|
@@ -100,6 +102,29 @@ async function fuseSetDefault(files, config, opts) {
|
|
|
100
102
|
})),
|
|
101
103
|
};
|
|
102
104
|
}
|
|
105
|
+
async function classifyPath(p) {
|
|
106
|
+
const info = await stat(p);
|
|
107
|
+
if (info.isFile() && (await sniffCarrierTail(p, info.size))) {
|
|
108
|
+
if (info.size > MAX_LOADED_BYTES) {
|
|
109
|
+
return {
|
|
110
|
+
kind: "carrier", path: p, status: "too-large", innerDigestB64: null, view: null, proof: null, c2pa: false,
|
|
111
|
+
reason: `a BitGraphed file larger than this tool loads (${Math.round(MAX_LOADED_BYTES / (1024 * 1024))} MiB); judge it offline with: npx @mikeargento/bitgraph-audit "${p}"`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const r = await readCarrierFile(p);
|
|
115
|
+
if (r.status === "corrupt" || r.innerDigestB64 === null) {
|
|
116
|
+
return {
|
|
117
|
+
kind: "carrier", path: p, status: "corrupt", innerDigestB64: null, view: null, proof: null, c2pa: false,
|
|
118
|
+
reason: `a carrier block was found at the end of the file but is unreadable: ${r.corruptReason ?? "unspecified"}. A corrupted block, not a forgery; nothing was minted for it.`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
kind: "carrier", path: p, status: "ok", innerDigestB64: r.innerDigestB64, view: r.view,
|
|
123
|
+
proof: r.result?.payload?.proof ?? null, c2pa: r.inner !== null && sniffC2paBytes(r.inner), reason: null,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
return { kind: "plain", file: await scanFile(p) };
|
|
127
|
+
}
|
|
103
128
|
/** Hash the given paths (bounded concurrency). Throws before any network call. */
|
|
104
129
|
async function hashPaths(paths) {
|
|
105
130
|
const failures = [];
|
|
@@ -215,17 +240,19 @@ export function buildServer(deps = {}) {
|
|
|
215
240
|
name: "bitgraph-mcp-server",
|
|
216
241
|
version: SERVER_VERSION,
|
|
217
242
|
}, {
|
|
218
|
-
instructions: "BitGraph gives a file's bytes a causal position in a public
|
|
243
|
+
instructions: "BitGraph gives a file's bytes a causal position in a public sequence bracketed by Ethereum anchors. bitgraph_record makes ONE BitGraph of everything in a call, files and folders alike: a single file is fused on its own; two or more become one set under one slot, one position, every file's new fused bytes listed by digest in the committed artifact. " +
|
|
219
244
|
"Files are read on this machine and never uploaded or modified; the new bytes are virtual and never written. Recordings are permanent: only make BitGraphs of files the user asked for, and never generate content just to record it. bitgraph_check and bitgraph_get_proof are read-only. " +
|
|
245
|
+
"A BitGraphed file (one that carries its own proof, bitgraph-carrier/1) is recognized by its structure: bitgraph_check judges it offline from the proof inside and states the window, and bitgraph_record never re-mints it, because the envelope is not the recorded thing, the bytes inside are. " +
|
|
220
246
|
"To do work INSIDE a BitGraph, call bitgraph_open BEFORE starting: it returns a position and its commitment; put the commitment string into the task, seal the task with bitgraph_commit within 120 seconds, then record the outputs with bitgraph_record. The task then could not have existed before the position's floor block, and the outputs sit after it.",
|
|
221
247
|
});
|
|
222
248
|
server.registerTool("bitgraph_record", {
|
|
223
249
|
title: "Make a BitGraph",
|
|
224
|
-
description: "Make a BitGraph of files or folders. Everything in one call becomes ONE BitGraph, the way a drop on the site works: a single file is fused on its own slot; two or more files become a set under a single slot
|
|
250
|
+
description: "Make a BitGraph of files or folders. Everything in one call becomes ONE BitGraph, the way a drop on the site works: a single file is fused on its own slot; two or more files become a set under a single slot on BitGraph (bitgraph.ing), one position for all of them. " +
|
|
225
251
|
"On this machine each file is read once for its SHA-256 (the origin) and a hasher state; an unused slot is allocated before any new file exists; every file's new fused bytes (the original plus a registered placement carrying the slot's commitment: a 48-byte trailer for JPEG, PNG, GIF, TIFF and TIFF-based raws, BMP, WebP, WAV and AVI, a small tar container with the original first for everything else) are hashed from that state without being written or held; and for a set the canonical list of those digests (above 2,000 files, a Merkle root over it) is committed under the same slot. " +
|
|
226
252
|
"Files are never modified and never uploaded: only digests, the committed artifact and slot records leave the machine. " +
|
|
227
253
|
"Give file paths, directory paths, or both (absolute paths preferred): a directory is every regular file under it, recursively, with hidden entries and symbolic links left out. " +
|
|
228
|
-
"Files
|
|
254
|
+
"Files already on record are NOT made again by default; they come back as 'on record' with their earliest position. A file can also hold a BitGraph its holder keeps, which no lookup sees. Pass again=true to make a new BitGraph regardless. " +
|
|
255
|
+
"A BitGraphed file (bitgraph-carrier/1, a file that carries its own proof) is never minted, with or without again: its carried proof is judged offline and reported, because the envelope is not the recorded thing, the bytes inside are. " +
|
|
229
256
|
"Positions are permanent and the proof comes back to you to keep, so only BitGraph files the user asked to, and never generate content just to record it. " +
|
|
230
257
|
"Returns one outcome per file: 'fused' (for a set, its row, one of N, and the set's position and proof page; for a single file, its own position and Frame), 'on record', or 'not fused' (with the reason). Keep the proof beside the files; BitGraph does not index it. " +
|
|
231
258
|
"Use bitgraph_check instead when the user only wants to know whether files are on record.",
|
|
@@ -238,7 +265,7 @@ export function buildServer(deps = {}) {
|
|
|
238
265
|
again: z
|
|
239
266
|
.boolean()
|
|
240
267
|
.default(false)
|
|
241
|
-
.describe("false (default): files
|
|
268
|
+
.describe("false (default): files already on record are returned as-is, nothing made. true: put every file in the set regardless. Outcomes are per unique file content: two paths with identical bytes are one member."),
|
|
242
269
|
response_format: responseFormatSchema,
|
|
243
270
|
},
|
|
244
271
|
annotations: {
|
|
@@ -264,15 +291,18 @@ export function buildServer(deps = {}) {
|
|
|
264
291
|
return fail(`Error: ${flushed.pending} members of an earlier set are still waiting to be indexed and the site could not take them; nothing was BitGraphed. Run bitgraph_record again in a moment: the waiting evidence is sent first.`);
|
|
265
292
|
}
|
|
266
293
|
}
|
|
267
|
-
// 2. The scan: one pass per file.
|
|
294
|
+
// 2. The scan: one pass per file. A BitGraphed file is set aside here,
|
|
295
|
+
// judged from the proof it carries, and never enters the mint below.
|
|
268
296
|
let scanned = 0;
|
|
269
297
|
report(0, files.length, `hashing ${files.length} files`);
|
|
270
|
-
const
|
|
271
|
-
const
|
|
298
|
+
const classified = await mapConcurrent(files, SCAN_CONCURRENCY, async (p) => {
|
|
299
|
+
const c = await classifyPath(p);
|
|
272
300
|
scanned += 1;
|
|
273
301
|
report(scanned, files.length, `hashed ${scanned} of ${files.length}`);
|
|
274
|
-
return
|
|
302
|
+
return c;
|
|
275
303
|
});
|
|
304
|
+
const scans = classified.filter((c) => c.kind === "plain").map((c) => c.file);
|
|
305
|
+
const carriers = classified.filter((c) => c.kind === "carrier");
|
|
276
306
|
// 3. Unique by content; the first path names the member, every path is reported.
|
|
277
307
|
const byDigest = new Map();
|
|
278
308
|
for (const s of scans) {
|
|
@@ -281,15 +311,24 @@ export function buildServer(deps = {}) {
|
|
|
281
311
|
byDigest.set(s.digestB64, entry);
|
|
282
312
|
}
|
|
283
313
|
const unique = [...byDigest.keys()];
|
|
284
|
-
// 4. What is on record already.
|
|
285
|
-
|
|
286
|
-
|
|
314
|
+
// 4. What is on record already. A BitGraphed file is looked up by the
|
|
315
|
+
// digest of its committed bytes, never by the envelope's.
|
|
316
|
+
report(0, 1, "checking BitGraph's copy");
|
|
317
|
+
const carrierInner = [...new Set(carriers.filter((c) => c.innerDigestB64 !== null).map((c) => c.innerDigestB64))];
|
|
318
|
+
const lookups = [...new Set([...unique, ...carrierInner])];
|
|
319
|
+
const checked = lookups.length > 0 ? await batchCheck(config, lookups.map(toUrlSafeB64)) : { results: {} };
|
|
287
320
|
const existing = new Map();
|
|
288
321
|
for (const d of unique) {
|
|
289
322
|
const entry = checked.results[toUrlSafeB64(d)];
|
|
290
323
|
if (entry && entry.proofs.length > 0)
|
|
291
324
|
existing.set(d, entry.proofs);
|
|
292
325
|
}
|
|
326
|
+
const carrierLedger = new Map();
|
|
327
|
+
for (const d of carrierInner) {
|
|
328
|
+
const entry = checked.results[toUrlSafeB64(d)];
|
|
329
|
+
if (entry && entry.proofs.length > 0)
|
|
330
|
+
carrierLedger.set(d, entry.proofs);
|
|
331
|
+
}
|
|
293
332
|
// 5. The set: every fresh file (every file, with again), one call.
|
|
294
333
|
const excluded = new Map();
|
|
295
334
|
const toMint = [];
|
|
@@ -366,7 +405,7 @@ export function buildServer(deps = {}) {
|
|
|
366
405
|
const m = memberOf.get(digest);
|
|
367
406
|
const prior = existing.get(digest);
|
|
368
407
|
for (const path of entry.paths) {
|
|
369
|
-
const base = { path, digest: toUrlSafeB64(digest) };
|
|
408
|
+
const base = { path, digest: toUrlSafeB64(digest), ...(entry.file.c2pa ? { c2pa: true } : {}) };
|
|
370
409
|
if (solo !== null && solo.file.digestB64 === digest) {
|
|
371
410
|
const { counter, epoch } = positionOf(solo.proof);
|
|
372
411
|
outcomes.push({
|
|
@@ -430,8 +469,37 @@ export function buildServer(deps = {}) {
|
|
|
430
469
|
}
|
|
431
470
|
}
|
|
432
471
|
}
|
|
472
|
+
// 7b. BitGraphed files: judged from the proof inside, never minted.
|
|
473
|
+
for (const c of carriers) {
|
|
474
|
+
if (c.status !== "ok" || c.innerDigestB64 === null) {
|
|
475
|
+
outcomes.push({
|
|
476
|
+
path: c.path, digest: "", outcome: "not fused", artifact_digest: null, placement: null,
|
|
477
|
+
counter: null, epoch: null, member: null, member_count: null, total_positions: 0, proof_url: null,
|
|
478
|
+
error: c.reason ?? "unreadable BitGraphed file",
|
|
479
|
+
});
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
const inner = c.innerDigestB64;
|
|
483
|
+
const rows = carrierLedger.get(inner);
|
|
484
|
+
const extras = { ...(c.view ? { carrier: c.view } : {}), ...(c.c2pa ? { c2pa: true } : {}) };
|
|
485
|
+
if (rows !== undefined && rows.length > 0) {
|
|
486
|
+
const first = rows[0];
|
|
487
|
+
const { counter, epoch } = positionOf(first.proof);
|
|
488
|
+
outcomes.push({
|
|
489
|
+
path: c.path, digest: toUrlSafeB64(inner), outcome: "on record", artifact_digest: null, placement: null,
|
|
490
|
+
counter, epoch, member: first.member ? first.member.index + 1 : null, member_count: first.member ? first.member.count : null,
|
|
491
|
+
total_positions: rows.length, proof_url: proofUrl(config.baseUrl, inner), ...extras,
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
outcomes.push({
|
|
496
|
+
path: c.path, digest: toUrlSafeB64(inner), outcome: "carried", artifact_digest: null, placement: null,
|
|
497
|
+
counter: null, epoch: null, member: null, member_count: null, total_positions: 0, proof_url: null, ...extras,
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
}
|
|
433
501
|
// Rows that need reading come first, so a cap drops fused rows, which all share one position.
|
|
434
|
-
const order = { "not fused": 0, "on record": 1,
|
|
502
|
+
const order = { "not fused": 0, "on record": 1, carried: 2, fused: 3 };
|
|
435
503
|
outcomes.sort((a, b) => order[a.outcome] - order[b.outcome]);
|
|
436
504
|
const listed = outcomes.slice(0, ROW_CAP);
|
|
437
505
|
const omitted = outcomes.length - listed.length;
|
|
@@ -440,6 +508,7 @@ export function buildServer(deps = {}) {
|
|
|
440
508
|
directories: expanded.directories,
|
|
441
509
|
fused: outcomes.filter((o) => o.outcome === "fused").length,
|
|
442
510
|
on_record: outcomes.filter((o) => o.outcome === "on record").length,
|
|
511
|
+
carried: outcomes.filter((o) => o.outcome === "carried").length,
|
|
443
512
|
not_fused: outcomes.filter((o) => o.outcome === "not fused").length,
|
|
444
513
|
};
|
|
445
514
|
const structured = {
|
|
@@ -531,7 +600,7 @@ export function buildServer(deps = {}) {
|
|
|
531
600
|
}
|
|
532
601
|
}
|
|
533
602
|
else {
|
|
534
|
-
proofNote = "Save the JSON below as a file beside the task bytes, whole and unedited, every field: a proof missing slotAllocation or
|
|
603
|
+
proofNote = "Save the JSON below as a file beside the task bytes, whole and unedited, every field, including environment.attestation.reportB64, the long base64 string, copied exactly: a proof missing slotAllocation, environment, or the attestation cannot be verified. ";
|
|
535
604
|
}
|
|
536
605
|
const structured = { outcome: "sealed", slot_counter: state.slot.counter, counter, epoch: epoch ? toUrlSafeB64(epoch) : null, floor_block: floor, artifact_digest: toUrlSafeB64(sealed.artifactDigestB64), commitment_offsets: sealed.offsets, proof_path: proofPath, instructions: proofNote.trim(), proof: sealed.proof };
|
|
537
606
|
if (response_format === "json")
|
|
@@ -548,9 +617,10 @@ export function buildServer(deps = {}) {
|
|
|
548
617
|
});
|
|
549
618
|
server.registerTool("bitgraph_check", {
|
|
550
619
|
title: "Check for BitGraphs",
|
|
551
|
-
description: "Check whether files or digests are on record in
|
|
620
|
+
description: "Check whether files or digests are on record in BitGraph's copy, without recording anything. " +
|
|
552
621
|
`Accepts file paths and directory paths (every regular file under them, up to ${MAX_CHECK_FILES} in all; hashed locally, only digests are sent) and/or raw SHA-256 digests in standard or URL-safe base64. ` +
|
|
553
622
|
"Returns, per item: on_record (the bytes are on record, as an exact recording, as the original a new file was made from, or as a member of a set), every position by counter, and the proof page URL. " +
|
|
623
|
+
"A BitGraphed file (bitgraph-carrier/1) is recognized by structure: the proof it carries is verified OFFLINE here (signatures, floor identity, block-header witnesses) and its verdict and window are reported, and the lookup uses the committed bytes inside, never the envelope. " +
|
|
554
624
|
"Read-only. Use bitgraph_record to BitGraph files that turn out not to be on record.",
|
|
555
625
|
inputSchema: {
|
|
556
626
|
paths: z
|
|
@@ -577,8 +647,19 @@ export function buildServer(deps = {}) {
|
|
|
577
647
|
const inputs = [];
|
|
578
648
|
if (paths && paths.length > 0) {
|
|
579
649
|
const { files } = await expandPaths(paths, MAX_CHECK_FILES);
|
|
580
|
-
const
|
|
581
|
-
|
|
650
|
+
const classified = await mapConcurrent(files, SCAN_CONCURRENCY, classifyPath);
|
|
651
|
+
classified.forEach((c, i) => {
|
|
652
|
+
const label = files[i];
|
|
653
|
+
if (c.kind === "plain") {
|
|
654
|
+
inputs.push({ label, standardDigest: c.file.digestB64, ...(c.file.c2pa ? { c2pa: true } : {}) });
|
|
655
|
+
}
|
|
656
|
+
else if (c.status === "ok" && c.innerDigestB64 !== null) {
|
|
657
|
+
inputs.push({ label, standardDigest: c.innerDigestB64, ...(c.view ? { carrier: c.view } : {}), ...(c.c2pa ? { c2pa: true } : {}) });
|
|
658
|
+
}
|
|
659
|
+
else {
|
|
660
|
+
inputs.push({ label, standardDigest: "", note: c.reason ?? "unreadable BitGraphed file" });
|
|
661
|
+
}
|
|
662
|
+
});
|
|
582
663
|
}
|
|
583
664
|
for (const d of digests ?? []) {
|
|
584
665
|
const trimmed = d.trim();
|
|
@@ -593,19 +674,23 @@ export function buildServer(deps = {}) {
|
|
|
593
674
|
if (inputs.length > MAX_CHECK_FILES) {
|
|
594
675
|
return fail(`Error: ${inputs.length} items; check at most ${MAX_CHECK_FILES} at a time.`);
|
|
595
676
|
}
|
|
596
|
-
const
|
|
677
|
+
const lookable = [...new Set(inputs.filter((i) => i.standardDigest !== "").map((i) => toUrlSafeB64(i.standardDigest)))];
|
|
678
|
+
const checked = lookable.length > 0 ? await batchCheck(config, lookable) : { results: {} };
|
|
597
679
|
const outcomes = inputs.map((input) => {
|
|
598
|
-
const entry = checked.results[toUrlSafeB64(input.standardDigest)];
|
|
680
|
+
const entry = input.standardDigest === "" ? undefined : checked.results[toUrlSafeB64(input.standardDigest)];
|
|
599
681
|
const proofs = entry?.proofs ?? [];
|
|
600
682
|
const positions = proofs.map((p) => ({ ...positionOf(p.proof), ...(p.member ? { member: p.member } : {}) }));
|
|
601
683
|
return {
|
|
602
684
|
input: input.label,
|
|
603
|
-
digest: toUrlSafeB64(input.standardDigest),
|
|
685
|
+
digest: input.standardDigest === "" ? "" : toUrlSafeB64(input.standardDigest),
|
|
604
686
|
// A fused descendant that names these bytes as origin is not a recording of them.
|
|
605
687
|
// The original and the new file made from it find the same proof.
|
|
606
688
|
on_record: proofs.length > 0,
|
|
607
689
|
positions,
|
|
608
690
|
proof_url: proofs.length > 0 ? proofUrl(config.baseUrl, input.standardDigest) : null,
|
|
691
|
+
...(input.carrier ? { carrier: input.carrier } : {}),
|
|
692
|
+
...(input.c2pa ? { c2pa: true } : {}),
|
|
693
|
+
...(input.note !== undefined ? { note: input.note } : {}),
|
|
609
694
|
};
|
|
610
695
|
});
|
|
611
696
|
const structured = {
|
|
@@ -625,7 +710,8 @@ export function buildServer(deps = {}) {
|
|
|
625
710
|
server.registerTool("bitgraph_get_proof", {
|
|
626
711
|
title: "Get a BitGraph proof",
|
|
627
712
|
description: "Fetch a BitGraph proof and its context: causal position, every position the same bytes occupy, the row a set member holds (one of N), and the two-sided Ethereum anchor window " +
|
|
628
|
-
"('
|
|
713
|
+
"(after the floor block's time; before the ANCHORING of the later block, a position bound, never that block's mine time). Look up by digest (base64, either form), by BitGraph number (e.g. '4523' or '#4,523', current epoch), or by file path (hashed locally). " +
|
|
714
|
+
"A path to a BitGraphed file (bitgraph-carrier/1) looks up the committed bytes inside it, and when the ledger has no row the proof the file carries answers offline. " +
|
|
629
715
|
"Exactly one of digest, number, or path is required. Read-only. " +
|
|
630
716
|
"markdown returns a summary; json returns the full proof object with positions and anchor window.",
|
|
631
717
|
inputSchema: {
|
|
@@ -667,6 +753,7 @@ export function buildServer(deps = {}) {
|
|
|
667
753
|
}
|
|
668
754
|
let urlSafeDigest;
|
|
669
755
|
let selCounter = counter;
|
|
756
|
+
let carrierRow = null;
|
|
670
757
|
if (number !== undefined) {
|
|
671
758
|
const result = await search(config, number);
|
|
672
759
|
if (!result.found || result.digest === undefined) {
|
|
@@ -677,8 +764,17 @@ export function buildServer(deps = {}) {
|
|
|
677
764
|
selCounter = result.counter;
|
|
678
765
|
}
|
|
679
766
|
else if (path !== undefined) {
|
|
680
|
-
const
|
|
681
|
-
|
|
767
|
+
const c = await classifyPath(path);
|
|
768
|
+
if (c.kind === "carrier") {
|
|
769
|
+
if (c.status !== "ok" || c.innerDigestB64 === null) {
|
|
770
|
+
return fail(`Error: ${c.reason ?? "unreadable BitGraphed file"}`);
|
|
771
|
+
}
|
|
772
|
+
carrierRow = c;
|
|
773
|
+
urlSafeDigest = toUrlSafeB64(c.innerDigestB64);
|
|
774
|
+
}
|
|
775
|
+
else {
|
|
776
|
+
urlSafeDigest = toUrlSafeB64(c.file.digestB64);
|
|
777
|
+
}
|
|
682
778
|
}
|
|
683
779
|
else {
|
|
684
780
|
const trimmed = digest.trim();
|
|
@@ -691,13 +787,39 @@ export function buildServer(deps = {}) {
|
|
|
691
787
|
const selEpoch = epoch !== undefined ? toUrlSafeB64(fromUrlSafeB64(epoch)) : undefined;
|
|
692
788
|
const detail = await getProofDetail(config, urlSafeDigest, selCounter, selEpoch);
|
|
693
789
|
if (detail.proofs.length === 0) {
|
|
790
|
+
if (carrierRow !== null && carrierRow.view !== null && carrierRow.proof !== null) {
|
|
791
|
+
// The ledger has no row, and the file answers for itself.
|
|
792
|
+
const v = carrierRow.view;
|
|
793
|
+
const synth = {
|
|
794
|
+
proofs: [{ proof: carrierRow.proof }],
|
|
795
|
+
positions: [],
|
|
796
|
+
causalWindow: {
|
|
797
|
+
anchorBefore: v.not_before
|
|
798
|
+
? { blockNumber: v.not_before.block, blockHash: v.not_before.hash, etherscanUrl: `https://etherscan.io/block/${v.not_before.block}`, blockTime: v.not_before.time }
|
|
799
|
+
: null,
|
|
800
|
+
anchorAfter: v.not_after
|
|
801
|
+
? { blockNumber: v.not_after.block, blockHash: v.not_after.hash, etherscanUrl: `https://etherscan.io/block/${v.not_after.block}`, blockTime: v.not_after.time }
|
|
802
|
+
: null,
|
|
803
|
+
},
|
|
804
|
+
};
|
|
805
|
+
const offlineNote = `Judged offline from the proof this BitGraphed file carries: carried proof ${v.verdict}${v.not_after === null ? ", closing anchor NOT FETCHED" : ""}. Not found in this ledger.`;
|
|
806
|
+
const structured = { ...synth, carrier: v };
|
|
807
|
+
if (response_format === "json")
|
|
808
|
+
return ok(capJson(structured).text, structured);
|
|
809
|
+
return ok(`${renderProofMarkdown(synth, config.baseUrl)}\n\n${offlineNote}`, structured);
|
|
810
|
+
}
|
|
694
811
|
return fail(`Not on record: no proof exists for digest ${urlSafeDigest}. Use bitgraph_record to make a BitGraph of the file.`);
|
|
695
812
|
}
|
|
696
813
|
if (response_format === "json") {
|
|
697
|
-
const
|
|
698
|
-
|
|
814
|
+
const withCarrier = carrierRow?.view ? { ...detail, carrier: carrierRow.view } : detail;
|
|
815
|
+
const capped = capJson(withCarrier);
|
|
816
|
+
return ok(capped.text, withCarrier);
|
|
699
817
|
}
|
|
700
|
-
|
|
818
|
+
const md = renderProofMarkdown(detail, config.baseUrl);
|
|
819
|
+
const carrierNote = carrierRow?.view
|
|
820
|
+
? `\n\nThis file carries its own proof (carried proof ${carrierRow.view.verdict}, judged offline).`
|
|
821
|
+
: "";
|
|
822
|
+
return ok(`${md}${carrierNote}`, detail);
|
|
701
823
|
}
|
|
702
824
|
catch (err) {
|
|
703
825
|
return fail(errorText(err));
|