@totemsdk/proofgraph 1.0.1 → 1.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.
- package/LICENSE +202 -21
- package/NOTICE +60 -0
- package/README.md +1 -1
- package/dist/canonical.js +11 -19
- package/dist/cjs/canonical.d.ts +22 -0
- package/dist/cjs/canonical.js +51 -0
- package/dist/cjs/durable-store.d.ts +50 -0
- package/dist/cjs/durable-store.js +178 -0
- package/dist/cjs/evidence.d.ts +89 -0
- package/dist/cjs/evidence.js +130 -0
- package/dist/cjs/graph.d.ts +103 -0
- package/dist/cjs/graph.js +298 -0
- package/dist/cjs/index.d.ts +26 -0
- package/dist/cjs/index.js +84 -0
- package/dist/cjs/io.d.ts +26 -0
- package/dist/cjs/io.js +173 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/query.d.ts +85 -0
- package/dist/cjs/query.js +189 -0
- package/dist/cjs/replicated-evidence.d.ts +42 -0
- package/dist/cjs/replicated-evidence.js +98 -0
- package/dist/cjs/types.d.ts +62 -0
- package/dist/cjs/types.js +7 -0
- package/dist/cjs/verify.d.ts +23 -0
- package/dist/cjs/verify.js +77 -0
- package/dist/durable-store.js +9 -12
- package/dist/evidence.js +14 -19
- package/dist/graph.d.ts +11 -0
- package/dist/graph.js +47 -34
- package/dist/index.d.ts +3 -2
- package/dist/index.js +8 -52
- package/dist/io.js +4 -8
- package/dist/query.d.ts +4 -0
- package/dist/query.js +28 -36
- package/dist/replicated-evidence.d.ts +42 -0
- package/dist/replicated-evidence.js +95 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -2
- package/dist/verify.js +3 -40
- package/package.json +21 -14
- package/rust/pkg/package.json +2 -2
- package/rust/pkg/proofgraph_wasm_bg.wasm +0 -0
- package/rust/pkg-node/package.json +2 -2
- package/rust/pkg-node/proofgraph_wasm_bg.wasm +0 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable evidence-chain owner for @totemsdk/proofgraph (RFC-007 G8 / §4.3).
|
|
3
|
+
*
|
|
4
|
+
* Evidence *bytes* are owned by the minimal `ArtifactStore` over a pluggable
|
|
5
|
+
* backend; graph references alone do not satisfy the flight-evidence gate. This
|
|
6
|
+
* module wires a ProofGraph's `evidence` nodes to content-addressed artifacts,
|
|
7
|
+
* and restores the bytes on demand with commit-time digest verification.
|
|
8
|
+
*
|
|
9
|
+
* Restore semantics (RFC-007 §4.3): `corrupt` (bytes present but digest
|
|
10
|
+
* mismatch — tampered or truncated) is surfaced strictly, never collapsed into
|
|
11
|
+
* `not-found` (bytes absent); `unavailable` (backend unreachable, or the
|
|
12
|
+
* backend deleted a blob the local index still references) is also surfaced.
|
|
13
|
+
*/
|
|
14
|
+
import type { ProofGraph } from './types.js';
|
|
15
|
+
import { ArtifactStore } from '@totemsdk/storage/artifacts';
|
|
16
|
+
import type { ArtifactRef } from '@totemsdk/storage/artifacts';
|
|
17
|
+
import type { StorageAdapter } from '@totemsdk/core';
|
|
18
|
+
/**
|
|
19
|
+
* RFC-007 Amendment A (A3): the enforced binding between a claim's commitment
|
|
20
|
+
* and the artifact that addresses it.
|
|
21
|
+
*
|
|
22
|
+
* Invariant: `contentHash === artifact.digest`. `assertEvidenceBinding` throws
|
|
23
|
+
* `StorageError('corrupt')` when it does not hold, so an unrecoverable
|
|
24
|
+
* commitment cannot be constructed or restored.
|
|
25
|
+
*/
|
|
26
|
+
export interface EvidenceArtifactBinding {
|
|
27
|
+
readonly nodeId: string;
|
|
28
|
+
/** The commitment from the claim (hex, with or without `0x`). */
|
|
29
|
+
readonly contentHash: string;
|
|
30
|
+
/** The content-addressed artifact; `artifact.digest` must equal `contentHash`. */
|
|
31
|
+
readonly artifact: ArtifactRef;
|
|
32
|
+
}
|
|
33
|
+
export declare function normalizeEvidenceHash(hash: string): string;
|
|
34
|
+
/** Throws `StorageError('corrupt')` unless `contentHash === artifact.digest`. */
|
|
35
|
+
export declare function assertEvidenceBinding(binding: EvidenceArtifactBinding): void;
|
|
36
|
+
export interface ProofGraphEvidenceStoreOptions {
|
|
37
|
+
/** Namespace the ArtifactStore writes artifact bytes under. */
|
|
38
|
+
namespace?: string;
|
|
39
|
+
/**
|
|
40
|
+
* true (default) — a `corrupt` or `unavailable` evidence read throws
|
|
41
|
+
* `StorageError` (never fail-open). `not-found` is always returned as a
|
|
42
|
+
* result, never thrown. Set false to return audit-able results instead.
|
|
43
|
+
*/
|
|
44
|
+
strict?: boolean;
|
|
45
|
+
}
|
|
46
|
+
/** Per-evidence-node restore result. */
|
|
47
|
+
export interface ProofGraphEvidenceResult {
|
|
48
|
+
nodeId: string;
|
|
49
|
+
status: 'ok' | 'not-found' | 'corrupt' | 'unavailable';
|
|
50
|
+
ref?: ArtifactRef;
|
|
51
|
+
/** The claim commitment this evidence is bound to, when known. */
|
|
52
|
+
contentHash?: string;
|
|
53
|
+
bytes?: Uint8Array;
|
|
54
|
+
message?: string;
|
|
55
|
+
}
|
|
56
|
+
export interface PutEvidenceOptions {
|
|
57
|
+
/**
|
|
58
|
+
* Explicit commitment per node. Falls back to the evidence node's
|
|
59
|
+
* `data.contentHash` (set by `addProof`). When a commitment is known, the
|
|
60
|
+
* stored bytes MUST hash to it and the artifact digest MUST equal it, or the
|
|
61
|
+
* put throws `StorageError('corrupt')`.
|
|
62
|
+
*/
|
|
63
|
+
contentHashFor?: (nodeId: string) => string | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* When true, every evidence node must have a commitment — a missing
|
|
66
|
+
* commitment throws. Default false (back-compat for nodes without one).
|
|
67
|
+
*/
|
|
68
|
+
requireContentHash?: boolean;
|
|
69
|
+
}
|
|
70
|
+
export interface ProofGraphEvidenceStore {
|
|
71
|
+
/**
|
|
72
|
+
* Persist evidence bytes for every `evidence` node of `graph`.
|
|
73
|
+
* `bytesFor` maps a node id to the bytes to store; nodes with no bytes are
|
|
74
|
+
* skipped. When a commitment is known (node `data.contentHash` or
|
|
75
|
+
* `options.contentHashFor`), the claim→hash→artifact binding is enforced:
|
|
76
|
+
* `sha3_256(bytes) === contentHash === artifact.digest`.
|
|
77
|
+
* Returns the refs persisted, in graph node order.
|
|
78
|
+
*/
|
|
79
|
+
putEvidence(graph: ProofGraph, bytesFor: (nodeId: string) => Uint8Array | undefined, options?: PutEvidenceOptions): Promise<ArtifactRef[]>;
|
|
80
|
+
/**
|
|
81
|
+
* Restore evidence bytes for every `evidence` node of `graph`, verifying
|
|
82
|
+
* each artifact's committed digest on read. With `strict` (default), corrupt
|
|
83
|
+
* or unavailable reads throw; otherwise a per-node result is returned.
|
|
84
|
+
*/
|
|
85
|
+
restoreEvidence(graph: ProofGraph): Promise<ProofGraphEvidenceResult[]>;
|
|
86
|
+
/** Count of local index entries (persisted evidence mappings). */
|
|
87
|
+
listCount(): Promise<number>;
|
|
88
|
+
}
|
|
89
|
+
export declare function createProofGraphEvidenceStore(artifacts: ArtifactStore, index: StorageAdapter, options?: ProofGraphEvidenceStoreOptions): ProofGraphEvidenceStore;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Durable evidence-chain owner for @totemsdk/proofgraph (RFC-007 G8 / §4.3).
|
|
4
|
+
*
|
|
5
|
+
* Evidence *bytes* are owned by the minimal `ArtifactStore` over a pluggable
|
|
6
|
+
* backend; graph references alone do not satisfy the flight-evidence gate. This
|
|
7
|
+
* module wires a ProofGraph's `evidence` nodes to content-addressed artifacts,
|
|
8
|
+
* and restores the bytes on demand with commit-time digest verification.
|
|
9
|
+
*
|
|
10
|
+
* Restore semantics (RFC-007 §4.3): `corrupt` (bytes present but digest
|
|
11
|
+
* mismatch — tampered or truncated) is surfaced strictly, never collapsed into
|
|
12
|
+
* `not-found` (bytes absent); `unavailable` (backend unreachable, or the
|
|
13
|
+
* backend deleted a blob the local index still references) is also surfaced.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.normalizeEvidenceHash = normalizeEvidenceHash;
|
|
17
|
+
exports.assertEvidenceBinding = assertEvidenceBinding;
|
|
18
|
+
exports.createProofGraphEvidenceStore = createProofGraphEvidenceStore;
|
|
19
|
+
const errors_1 = require("@totemsdk/storage/errors");
|
|
20
|
+
const core_1 = require("@totemsdk/core");
|
|
21
|
+
const DEFAULT_NS = 'totem-proofgraph-evidence';
|
|
22
|
+
const INDEX_PREFIX = 'totem_proofgraph_evidence:v1:';
|
|
23
|
+
function normalizeEvidenceHash(hash) {
|
|
24
|
+
return hash.replace(/^0x/i, '').toLowerCase();
|
|
25
|
+
}
|
|
26
|
+
/** Throws `StorageError('corrupt')` unless `contentHash === artifact.digest`. */
|
|
27
|
+
function assertEvidenceBinding(binding) {
|
|
28
|
+
if (normalizeEvidenceHash(binding.contentHash) !== normalizeEvidenceHash(binding.artifact.digest)) {
|
|
29
|
+
throw new errors_1.StorageError(`evidence binding mismatch for node "${binding.nodeId}": contentHash ${binding.contentHash} !== artifact.digest ${binding.artifact.digest}`, 'corrupt', { key: binding.nodeId });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function evidenceNodeIds(graph) {
|
|
33
|
+
return graph.nodes.filter((node) => node.type === 'evidence').map((node) => node.id);
|
|
34
|
+
}
|
|
35
|
+
/** The claim commitment carried on an evidence node (`data.contentHash`). */
|
|
36
|
+
function nodeContentHash(graph, nodeId) {
|
|
37
|
+
const value = graph.nodes.find((node) => node.id === nodeId)?.data?.contentHash;
|
|
38
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
39
|
+
}
|
|
40
|
+
function createProofGraphEvidenceStore(artifacts, index, options = {}) {
|
|
41
|
+
if (!artifacts.capabilities.writable) {
|
|
42
|
+
throw new Error('createProofGraphEvidenceStore requires a writable artifact store');
|
|
43
|
+
}
|
|
44
|
+
const namespace = options.namespace ?? DEFAULT_NS;
|
|
45
|
+
const strict = options.strict ?? true;
|
|
46
|
+
const indexKey = (nodeId) => `${INDEX_PREFIX}${nodeId}`;
|
|
47
|
+
return {
|
|
48
|
+
async putEvidence(graph, bytesFor, options = {}) {
|
|
49
|
+
const refs = [];
|
|
50
|
+
for (const nodeId of evidenceNodeIds(graph)) {
|
|
51
|
+
const bytes = bytesFor(nodeId);
|
|
52
|
+
if (!bytes)
|
|
53
|
+
continue;
|
|
54
|
+
const contentHash = options.contentHashFor?.(nodeId) ?? nodeContentHash(graph, nodeId);
|
|
55
|
+
if (!contentHash && options.requireContentHash) {
|
|
56
|
+
throw new errors_1.StorageError(`evidence commitment missing for node "${nodeId}"`, 'corrupt', { key: indexKey(nodeId) });
|
|
57
|
+
}
|
|
58
|
+
// Byte-identity invariant: the stored bytes must hash to the commitment.
|
|
59
|
+
const digest = (0, core_1.toHex)((0, core_1.sha3_256)(bytes));
|
|
60
|
+
if (contentHash && normalizeEvidenceHash(contentHash) !== digest) {
|
|
61
|
+
throw new errors_1.StorageError(`evidence commitment mismatch for node "${nodeId}": sha3(bytes) ${digest} !== contentHash ${contentHash}`, 'corrupt', { key: indexKey(nodeId) });
|
|
62
|
+
}
|
|
63
|
+
const receipt = await artifacts.put(namespace, bytes).catch((err) => {
|
|
64
|
+
throw new errors_1.StorageError(`evidence put failed for node "${nodeId}": ${err.message}`, 'write-failed', { key: indexKey(nodeId), cause: err });
|
|
65
|
+
});
|
|
66
|
+
if (contentHash) {
|
|
67
|
+
// The artifact must address exactly the committed hash.
|
|
68
|
+
assertEvidenceBinding({ nodeId, contentHash, artifact: receipt.ref });
|
|
69
|
+
}
|
|
70
|
+
const entry = {
|
|
71
|
+
ref: receipt.ref,
|
|
72
|
+
...(contentHash ? { contentHash: normalizeEvidenceHash(contentHash) } : {}),
|
|
73
|
+
};
|
|
74
|
+
await index.set(indexKey(nodeId), entry);
|
|
75
|
+
refs.push(receipt.ref);
|
|
76
|
+
}
|
|
77
|
+
return refs;
|
|
78
|
+
},
|
|
79
|
+
async restoreEvidence(graph) {
|
|
80
|
+
const results = [];
|
|
81
|
+
const fail = (err) => {
|
|
82
|
+
if (err instanceof errors_1.StorageError)
|
|
83
|
+
throw err;
|
|
84
|
+
throw new errors_1.StorageError(`evidence read failed: ${err.message}`, 'unavailable', { cause: err });
|
|
85
|
+
};
|
|
86
|
+
for (const nodeId of evidenceNodeIds(graph)) {
|
|
87
|
+
const entry = await index.get(indexKey(nodeId));
|
|
88
|
+
if (!entry) {
|
|
89
|
+
results.push({ nodeId, status: 'not-found', message: 'no evidence artifact persisted for node' });
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const { ref, contentHash } = entry;
|
|
93
|
+
if (contentHash) {
|
|
94
|
+
// Index-tamper guard: the enforced binding must still hold.
|
|
95
|
+
assertEvidenceBinding({ nodeId, contentHash, artifact: ref });
|
|
96
|
+
}
|
|
97
|
+
const read = await artifacts.get(ref).catch(fail);
|
|
98
|
+
switch (read.status) {
|
|
99
|
+
case 'ok':
|
|
100
|
+
results.push({ nodeId, status: 'ok', ref, ...(contentHash ? { contentHash } : {}), bytes: read.bytes });
|
|
101
|
+
break;
|
|
102
|
+
case 'corrupt':
|
|
103
|
+
if (strict) {
|
|
104
|
+
throw new errors_1.StorageError(`evidence artifact corrupt for node "${nodeId}": ${read.message ?? 'digest mismatch'}`, 'corrupt', { key: indexKey(nodeId) });
|
|
105
|
+
}
|
|
106
|
+
results.push({ nodeId, status: 'corrupt', ref, message: read.message ?? 'digest mismatch' });
|
|
107
|
+
break;
|
|
108
|
+
case 'unavailable':
|
|
109
|
+
if (strict) {
|
|
110
|
+
throw new errors_1.StorageError(`evidence artifact unavailable for node "${nodeId}": ${read.message ?? 'backend unavailable'}`, 'unavailable', { key: indexKey(nodeId) });
|
|
111
|
+
}
|
|
112
|
+
results.push({ nodeId, status: 'unavailable', ref, message: read.message ?? 'backend unavailable' });
|
|
113
|
+
break;
|
|
114
|
+
case 'not-found':
|
|
115
|
+
results.push({ nodeId, status: 'not-found', ref, message: read.message ?? 'evidence artifact not found' });
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return results;
|
|
120
|
+
},
|
|
121
|
+
async listCount() {
|
|
122
|
+
let count = 0;
|
|
123
|
+
for (const key of await index.keys()) {
|
|
124
|
+
if (key.startsWith(INDEX_PREFIX))
|
|
125
|
+
count++;
|
|
126
|
+
}
|
|
127
|
+
return count;
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proof graph mutation functions — all pure, all returning NEW graphs.
|
|
3
|
+
*
|
|
4
|
+
* Immutable pattern: every mutation returns a brand-new ProofGraph with a
|
|
5
|
+
* recomputed graphId. Nodes and edges are deduplicated by ID so adding the
|
|
6
|
+
* same proof twice is idempotent.
|
|
7
|
+
*/
|
|
8
|
+
import type { SignedProof } from '@totemsdk/proof';
|
|
9
|
+
import type { SignedIdentityClaim, TotemIdentityDocument } from '@totemsdk/identity';
|
|
10
|
+
import type { SignedManifest } from '@totemsdk/manifest';
|
|
11
|
+
import type { ProofGraph, ProofGraphEdge, ProofGraphEdgeType, ProofGraphNodeType, ReceiptLikeInput, AnchorInput } from './types.js';
|
|
12
|
+
/**
|
|
13
|
+
* Create an empty ProofGraph.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createProofGraph(metadata?: Record<string, unknown>): ProofGraph;
|
|
16
|
+
/**
|
|
17
|
+
* Attach or replace mutable metadata. Does NOT affect graphId.
|
|
18
|
+
*/
|
|
19
|
+
export declare function setGraphMetadata(graph: ProofGraph, metadata: Record<string, unknown>): ProofGraph;
|
|
20
|
+
/**
|
|
21
|
+
* Build a ProofGraphEdge with a deterministic ID from its fields.
|
|
22
|
+
* Convenience helper so callers don't need to import computeEdgeId.
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildEdge(type: ProofGraphEdgeType, from: string, to: string, proofId?: string, data?: Record<string, unknown>): ProofGraphEdge;
|
|
25
|
+
/**
|
|
26
|
+
* Add a node directly (type + refId pair). Idempotent — skipped if the node already exists.
|
|
27
|
+
*/
|
|
28
|
+
export declare function addNode(graph: ProofGraph, type: ProofGraphNodeType, refId: string, data?: Record<string, unknown>): ProofGraph;
|
|
29
|
+
/**
|
|
30
|
+
* Append a ProofGraphEdge and recompute graphId. Idempotent by edge ID.
|
|
31
|
+
* Spec API: addEdge(graph, edge). Use buildEdge() to construct the edge object.
|
|
32
|
+
*/
|
|
33
|
+
export declare function addEdge(graph: ProofGraph, edge: ProofGraphEdge): ProofGraph;
|
|
34
|
+
/**
|
|
35
|
+
* Index a TotemIdentityDocument into the graph.
|
|
36
|
+
* Creates an 'identity' node with refId = doc.id.
|
|
37
|
+
*/
|
|
38
|
+
export declare function addIdentityDocument(graph: ProofGraph, doc: TotemIdentityDocument): ProofGraph;
|
|
39
|
+
/**
|
|
40
|
+
* Index a SignedProof into the graph.
|
|
41
|
+
*
|
|
42
|
+
* Nodes created:
|
|
43
|
+
* proof — proofId (stores the full SignedProof in .data)
|
|
44
|
+
* identity — signature.address (issuer key address)
|
|
45
|
+
* subject — proof.subject.id
|
|
46
|
+
* evidence — ev.id (one per evidence ref)
|
|
47
|
+
* anchor — anchor.hash (if present)
|
|
48
|
+
*
|
|
49
|
+
* Edges created (all referencing the proofId):
|
|
50
|
+
* proves proof → subject
|
|
51
|
+
* issued_by proof → identity (signer)
|
|
52
|
+
* about proof → subject
|
|
53
|
+
* references proof → evidence (one per evidence ref, in array order)
|
|
54
|
+
* anchored_to proof → anchor (if present)
|
|
55
|
+
*/
|
|
56
|
+
export declare function addProof(graph: ProofGraph, signedProof: SignedProof): ProofGraph;
|
|
57
|
+
/**
|
|
58
|
+
* Index a SignedIdentityClaim into the graph.
|
|
59
|
+
*
|
|
60
|
+
* Nodes created:
|
|
61
|
+
* identity-claim — claim.id (stores the full signed claim in .data)
|
|
62
|
+
* address — issuer address
|
|
63
|
+
* address/identity — target address (for delegates_to / rotates_to / revokes)
|
|
64
|
+
*
|
|
65
|
+
* Edges created based on claim.type:
|
|
66
|
+
* all types → issued_by (claim-node → issuer address)
|
|
67
|
+
* delegates_to → delegates_to edge (claim-node → delegated address)
|
|
68
|
+
* revokes → revokes edge (claim-node → subject identity)
|
|
69
|
+
* rotates_to → controls edge (claim-node → new address)
|
|
70
|
+
*/
|
|
71
|
+
export declare function addIdentityClaim(graph: ProofGraph, signedClaim: SignedIdentityClaim): ProofGraph;
|
|
72
|
+
/**
|
|
73
|
+
* Index a SignedManifest into the graph.
|
|
74
|
+
*
|
|
75
|
+
* Nodes created:
|
|
76
|
+
* manifest — computed manifest ID (stores manifest content in .data)
|
|
77
|
+
* address — resolved author/agent/operator address
|
|
78
|
+
*
|
|
79
|
+
* Edge created:
|
|
80
|
+
* manifests_as manifest → address
|
|
81
|
+
*/
|
|
82
|
+
export declare function addManifest(graph: ProofGraph, signedManifest: SignedManifest): ProofGraph;
|
|
83
|
+
/**
|
|
84
|
+
* Add a signed manifest **revision** without collapsing other revisions.
|
|
85
|
+
*
|
|
86
|
+
* Creates:
|
|
87
|
+
* manifest (logical) refId = manifestId
|
|
88
|
+
* manifest (revision) refId = manifestEnvelopeId, data = { manifestId, manifestEnvelopeId, signedManifest }
|
|
89
|
+
* address resolved author/operator address
|
|
90
|
+
* manifests_as manifestId → address
|
|
91
|
+
* revision_of manifestEnvelopeId → manifestId
|
|
92
|
+
*/
|
|
93
|
+
export declare function addManifestRevision(graph: ProofGraph, signedManifest: SignedManifest): ProofGraph;
|
|
94
|
+
/**
|
|
95
|
+
* Add a receipt-like node (payment, subscription, claim receipt) to the graph.
|
|
96
|
+
* If receipt.proofId is set, adds a 'supports' edge from the receipt to that proof.
|
|
97
|
+
*/
|
|
98
|
+
export declare function addReceiptLike(graph: ProofGraph, receipt: ReceiptLikeInput): ProofGraph;
|
|
99
|
+
/**
|
|
100
|
+
* Add a standalone anchor node. If anchor.proofId is set, adds an 'anchored_to'
|
|
101
|
+
* edge from the proof to this anchor.
|
|
102
|
+
*/
|
|
103
|
+
export declare function addAnchor(graph: ProofGraph, anchor: AnchorInput): ProofGraph;
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Proof graph mutation functions — all pure, all returning NEW graphs.
|
|
4
|
+
*
|
|
5
|
+
* Immutable pattern: every mutation returns a brand-new ProofGraph with a
|
|
6
|
+
* recomputed graphId. Nodes and edges are deduplicated by ID so adding the
|
|
7
|
+
* same proof twice is idempotent.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.createProofGraph = createProofGraph;
|
|
11
|
+
exports.setGraphMetadata = setGraphMetadata;
|
|
12
|
+
exports.buildEdge = buildEdge;
|
|
13
|
+
exports.addNode = addNode;
|
|
14
|
+
exports.addEdge = addEdge;
|
|
15
|
+
exports.addIdentityDocument = addIdentityDocument;
|
|
16
|
+
exports.addProof = addProof;
|
|
17
|
+
exports.addIdentityClaim = addIdentityClaim;
|
|
18
|
+
exports.addManifest = addManifest;
|
|
19
|
+
exports.addManifestRevision = addManifestRevision;
|
|
20
|
+
exports.addReceiptLike = addReceiptLike;
|
|
21
|
+
exports.addAnchor = addAnchor;
|
|
22
|
+
const manifest_1 = require("@totemsdk/manifest");
|
|
23
|
+
const canonical_js_1 = require("./canonical.js");
|
|
24
|
+
const evidence_js_1 = require("./evidence.js");
|
|
25
|
+
function makeNode(type, refId, data) {
|
|
26
|
+
return {
|
|
27
|
+
id: (0, canonical_js_1.computeNodeId)(type, refId),
|
|
28
|
+
type,
|
|
29
|
+
refId,
|
|
30
|
+
...(data !== undefined ? { data } : {}),
|
|
31
|
+
createdAt: Date.now(),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function makeEdge(type, from, to, proofId, data) {
|
|
35
|
+
return {
|
|
36
|
+
id: (0, canonical_js_1.computeEdgeId)(type, from, to, proofId, data),
|
|
37
|
+
type,
|
|
38
|
+
from,
|
|
39
|
+
to,
|
|
40
|
+
...(proofId !== undefined ? { proofId } : {}),
|
|
41
|
+
...(data !== undefined ? { data } : {}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function mergeNodes(existing, incoming) {
|
|
45
|
+
const byId = new Map(existing.map((n) => [n.id, n]));
|
|
46
|
+
for (const node of incoming) {
|
|
47
|
+
if (!byId.has(node.id)) {
|
|
48
|
+
byId.set(node.id, node);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return Array.from(byId.values());
|
|
52
|
+
}
|
|
53
|
+
function mergeEdges(existing, incoming) {
|
|
54
|
+
const byId = new Map(existing.map((e) => [e.id, e]));
|
|
55
|
+
for (const edge of incoming) {
|
|
56
|
+
if (!byId.has(edge.id)) {
|
|
57
|
+
byId.set(edge.id, edge);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return Array.from(byId.values());
|
|
61
|
+
}
|
|
62
|
+
function rebuild(graph, newNodes, newEdges) {
|
|
63
|
+
const nodes = mergeNodes(graph.nodes, newNodes);
|
|
64
|
+
const edges = mergeEdges(graph.edges, newEdges);
|
|
65
|
+
return {
|
|
66
|
+
...graph,
|
|
67
|
+
nodes,
|
|
68
|
+
edges,
|
|
69
|
+
graphId: (0, canonical_js_1.computeProofGraphId)(nodes, edges),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Create an empty ProofGraph.
|
|
74
|
+
*/
|
|
75
|
+
function createProofGraph(metadata) {
|
|
76
|
+
const nodes = [];
|
|
77
|
+
const edges = [];
|
|
78
|
+
return {
|
|
79
|
+
graphId: (0, canonical_js_1.computeProofGraphId)(nodes, edges),
|
|
80
|
+
nodes,
|
|
81
|
+
edges,
|
|
82
|
+
createdAt: Date.now(),
|
|
83
|
+
...(metadata !== undefined ? { metadata } : {}),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Attach or replace mutable metadata. Does NOT affect graphId.
|
|
88
|
+
*/
|
|
89
|
+
function setGraphMetadata(graph, metadata) {
|
|
90
|
+
return { ...graph, metadata };
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Build a ProofGraphEdge with a deterministic ID from its fields.
|
|
94
|
+
* Convenience helper so callers don't need to import computeEdgeId.
|
|
95
|
+
*/
|
|
96
|
+
function buildEdge(type, from, to, proofId, data) {
|
|
97
|
+
return makeEdge(type, from, to, proofId, data);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Add a node directly (type + refId pair). Idempotent — skipped if the node already exists.
|
|
101
|
+
*/
|
|
102
|
+
function addNode(graph, type, refId, data) {
|
|
103
|
+
return rebuild(graph, [makeNode(type, refId, data)], []);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Append a ProofGraphEdge and recompute graphId. Idempotent by edge ID.
|
|
107
|
+
* Spec API: addEdge(graph, edge). Use buildEdge() to construct the edge object.
|
|
108
|
+
*/
|
|
109
|
+
function addEdge(graph, edge) {
|
|
110
|
+
const edges = mergeEdges(graph.edges, [edge]);
|
|
111
|
+
return {
|
|
112
|
+
...graph,
|
|
113
|
+
edges,
|
|
114
|
+
graphId: (0, canonical_js_1.computeProofGraphId)(graph.nodes, edges),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Index a TotemIdentityDocument into the graph.
|
|
119
|
+
* Creates an 'identity' node with refId = doc.id.
|
|
120
|
+
*/
|
|
121
|
+
function addIdentityDocument(graph, doc) {
|
|
122
|
+
return rebuild(graph, [makeNode('identity', doc.id, { ...doc })], []);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Index a SignedProof into the graph.
|
|
126
|
+
*
|
|
127
|
+
* Nodes created:
|
|
128
|
+
* proof — proofId (stores the full SignedProof in .data)
|
|
129
|
+
* identity — signature.address (issuer key address)
|
|
130
|
+
* subject — proof.subject.id
|
|
131
|
+
* evidence — ev.id (one per evidence ref)
|
|
132
|
+
* anchor — anchor.hash (if present)
|
|
133
|
+
*
|
|
134
|
+
* Edges created (all referencing the proofId):
|
|
135
|
+
* proves proof → subject
|
|
136
|
+
* issued_by proof → identity (signer)
|
|
137
|
+
* about proof → subject
|
|
138
|
+
* references proof → evidence (one per evidence ref, in array order)
|
|
139
|
+
* anchored_to proof → anchor (if present)
|
|
140
|
+
*/
|
|
141
|
+
function addProof(graph, signedProof) {
|
|
142
|
+
const proofNode = makeNode('proof', signedProof.proofId, {
|
|
143
|
+
...signedProof,
|
|
144
|
+
});
|
|
145
|
+
const identityNode = makeNode('identity', signedProof.signature.address);
|
|
146
|
+
const subjectNode = makeNode('subject', signedProof.subject.id);
|
|
147
|
+
const newNodes = [proofNode, identityNode, subjectNode];
|
|
148
|
+
const newEdges = [
|
|
149
|
+
makeEdge('proves', signedProof.proofId, signedProof.subject.id, signedProof.proofId),
|
|
150
|
+
makeEdge('issued_by', signedProof.proofId, signedProof.signature.address, signedProof.proofId),
|
|
151
|
+
makeEdge('about', signedProof.proofId, signedProof.subject.id, signedProof.proofId),
|
|
152
|
+
];
|
|
153
|
+
if (signedProof.evidence) {
|
|
154
|
+
for (const ev of signedProof.evidence) {
|
|
155
|
+
// RFC-007 Amendment A (A3): carry the claim commitment on the evidence
|
|
156
|
+
// node so the evidence store can enforce claim→contentHash→artifact.
|
|
157
|
+
newNodes.push(makeNode('evidence', ev.id, ev.hash ? { contentHash: (0, evidence_js_1.normalizeEvidenceHash)(ev.hash) } : undefined));
|
|
158
|
+
newEdges.push(makeEdge('references', signedProof.proofId, ev.id, signedProof.proofId));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (signedProof.anchor) {
|
|
162
|
+
newNodes.push(makeNode('anchor', signedProof.anchor.hash));
|
|
163
|
+
newEdges.push(makeEdge('anchored_to', signedProof.proofId, signedProof.anchor.hash, signedProof.proofId));
|
|
164
|
+
}
|
|
165
|
+
return rebuild(graph, newNodes, newEdges);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Index a SignedIdentityClaim into the graph.
|
|
169
|
+
*
|
|
170
|
+
* Nodes created:
|
|
171
|
+
* identity-claim — claim.id (stores the full signed claim in .data)
|
|
172
|
+
* address — issuer address
|
|
173
|
+
* address/identity — target address (for delegates_to / rotates_to / revokes)
|
|
174
|
+
*
|
|
175
|
+
* Edges created based on claim.type:
|
|
176
|
+
* all types → issued_by (claim-node → issuer address)
|
|
177
|
+
* delegates_to → delegates_to edge (claim-node → delegated address)
|
|
178
|
+
* revokes → revokes edge (claim-node → subject identity)
|
|
179
|
+
* rotates_to → controls edge (claim-node → new address)
|
|
180
|
+
*/
|
|
181
|
+
function addIdentityClaim(graph, signedClaim) {
|
|
182
|
+
const { claim } = signedClaim;
|
|
183
|
+
const claimNode = makeNode('identity-claim', claim.id, {
|
|
184
|
+
...signedClaim,
|
|
185
|
+
});
|
|
186
|
+
const issuerNode = makeNode('address', claim.issuer);
|
|
187
|
+
const newNodes = [claimNode, issuerNode];
|
|
188
|
+
const newEdges = [
|
|
189
|
+
makeEdge('issued_by', claim.id, claim.issuer),
|
|
190
|
+
];
|
|
191
|
+
if (claim.type === 'delegates_to') {
|
|
192
|
+
newNodes.push(makeNode('address', claim.object));
|
|
193
|
+
newEdges.push(makeEdge('delegates_to', claim.id, claim.object));
|
|
194
|
+
}
|
|
195
|
+
else if (claim.type === 'revokes') {
|
|
196
|
+
newNodes.push(makeNode('identity', claim.subject));
|
|
197
|
+
newEdges.push(makeEdge('revokes', claim.id, claim.subject));
|
|
198
|
+
}
|
|
199
|
+
else if (claim.type === 'rotates_to') {
|
|
200
|
+
newNodes.push(makeNode('address', claim.object));
|
|
201
|
+
newEdges.push(makeEdge('controls', claim.id, claim.object));
|
|
202
|
+
}
|
|
203
|
+
return rebuild(graph, newNodes, newEdges);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Resolve the author address from a manifest based on its type.
|
|
207
|
+
* AppManifest / DAppManifest → authorAddress
|
|
208
|
+
* CapabilityManifest → agentAddress
|
|
209
|
+
* EdgeServiceManifest → operatorAddress
|
|
210
|
+
* Falls back to signedManifest.authorAddress if the manifest type is unknown.
|
|
211
|
+
*/
|
|
212
|
+
function resolveManifestAddress(signedManifest) {
|
|
213
|
+
const m = signedManifest.manifest;
|
|
214
|
+
if (m['type'] === 'capability' && typeof m['agentAddress'] === 'string') {
|
|
215
|
+
return m['agentAddress'];
|
|
216
|
+
}
|
|
217
|
+
if (m['type'] === 'edge-service' && typeof m['operatorAddress'] === 'string') {
|
|
218
|
+
return m['operatorAddress'];
|
|
219
|
+
}
|
|
220
|
+
if (typeof m['authorAddress'] === 'string') {
|
|
221
|
+
return m['authorAddress'];
|
|
222
|
+
}
|
|
223
|
+
return signedManifest.authorAddress;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Index a SignedManifest into the graph.
|
|
227
|
+
*
|
|
228
|
+
* Nodes created:
|
|
229
|
+
* manifest — computed manifest ID (stores manifest content in .data)
|
|
230
|
+
* address — resolved author/agent/operator address
|
|
231
|
+
*
|
|
232
|
+
* Edge created:
|
|
233
|
+
* manifests_as manifest → address
|
|
234
|
+
*/
|
|
235
|
+
function addManifest(graph, signedManifest) {
|
|
236
|
+
const manifestId = (0, manifest_1.computeManifestId)(signedManifest.manifest);
|
|
237
|
+
const address = resolveManifestAddress(signedManifest);
|
|
238
|
+
const manifestNode = makeNode('manifest', manifestId, {
|
|
239
|
+
...signedManifest,
|
|
240
|
+
});
|
|
241
|
+
const addressNode = makeNode('address', address);
|
|
242
|
+
return rebuild(graph, [manifestNode, addressNode], [
|
|
243
|
+
makeEdge('manifests_as', manifestId, address),
|
|
244
|
+
]);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Add a signed manifest **revision** without collapsing other revisions.
|
|
248
|
+
*
|
|
249
|
+
* Creates:
|
|
250
|
+
* manifest (logical) refId = manifestId
|
|
251
|
+
* manifest (revision) refId = manifestEnvelopeId, data = { manifestId, manifestEnvelopeId, signedManifest }
|
|
252
|
+
* address resolved author/operator address
|
|
253
|
+
* manifests_as manifestId → address
|
|
254
|
+
* revision_of manifestEnvelopeId → manifestId
|
|
255
|
+
*/
|
|
256
|
+
function addManifestRevision(graph, signedManifest) {
|
|
257
|
+
const manifestId = (0, manifest_1.computeManifestId)(signedManifest.manifest);
|
|
258
|
+
const manifestEnvelopeId = (0, manifest_1.computeManifestEnvelopeId)(signedManifest);
|
|
259
|
+
const address = resolveManifestAddress(signedManifest);
|
|
260
|
+
const logicalNode = makeNode('manifest', manifestId, { manifestId });
|
|
261
|
+
const revisionNode = makeNode('manifest', manifestEnvelopeId, {
|
|
262
|
+
manifestId,
|
|
263
|
+
manifestEnvelopeId,
|
|
264
|
+
signedManifest,
|
|
265
|
+
});
|
|
266
|
+
const addressNode = makeNode('address', address);
|
|
267
|
+
return rebuild(graph, [logicalNode, revisionNode, addressNode], [
|
|
268
|
+
makeEdge('manifests_as', manifestId, address),
|
|
269
|
+
makeEdge('revision_of', manifestEnvelopeId, manifestId),
|
|
270
|
+
]);
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Add a receipt-like node (payment, subscription, claim receipt) to the graph.
|
|
274
|
+
* If receipt.proofId is set, adds a 'supports' edge from the receipt to that proof.
|
|
275
|
+
*/
|
|
276
|
+
function addReceiptLike(graph, receipt) {
|
|
277
|
+
const { id, data, proofId } = receipt;
|
|
278
|
+
const receiptNode = makeNode('receipt', id, data);
|
|
279
|
+
const newEdges = [];
|
|
280
|
+
if (proofId !== undefined) {
|
|
281
|
+
newEdges.push(makeEdge('supports', id, proofId, proofId));
|
|
282
|
+
}
|
|
283
|
+
return rebuild(graph, [receiptNode], newEdges);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Add a standalone anchor node. If anchor.proofId is set, adds an 'anchored_to'
|
|
287
|
+
* edge from the proof to this anchor.
|
|
288
|
+
*/
|
|
289
|
+
function addAnchor(graph, anchor) {
|
|
290
|
+
const { hash, proofId, ...rest } = anchor;
|
|
291
|
+
const nodeData = { ...rest };
|
|
292
|
+
const anchorNode = makeNode('anchor', hash, Object.keys(nodeData).length > 0 ? nodeData : undefined);
|
|
293
|
+
const newEdges = [];
|
|
294
|
+
if (proofId !== undefined) {
|
|
295
|
+
newEdges.push(makeEdge('anchored_to', proofId, hash, proofId));
|
|
296
|
+
}
|
|
297
|
+
return rebuild(graph, [anchorNode], newEdges);
|
|
298
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @totemsdk/proofgraph
|
|
3
|
+
*
|
|
4
|
+
* Local deterministic proof relationship graph for Totem Edge.
|
|
5
|
+
*
|
|
6
|
+
* Indexes SignedProofs, SignedIdentityClaims, SignedManifests, identity
|
|
7
|
+
* documents, and custom evidence nodes into an immutable, queryable DAG
|
|
8
|
+
* keyed by a content-derived graphId. Pure package — no network, no DHT,
|
|
9
|
+
* no blockchain submission, no crypto reimplementation.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* const g0 = createProofGraph();
|
|
13
|
+
* const g1 = addProof(g0, signedProof);
|
|
14
|
+
* const result = verifyProofGraph(g1);
|
|
15
|
+
*/
|
|
16
|
+
export type { ProofGraphNodeType, ProofGraphEdgeType, ProofGraphNode, ProofGraphEdge, ProofGraph, ProofGraphVerifyResult, ProofGraphStoragePort, ReceiptLikeInput, AnchorInput, } from './types.js';
|
|
17
|
+
export { createDurableProofGraphStore, } from './durable-store.js';
|
|
18
|
+
export type { DurableProofGraphStore, DurableProofGraphStoreOptions, ProofGraphRecoveryReport, } from './durable-store.js';
|
|
19
|
+
export { createProofGraphEvidenceStore, assertEvidenceBinding, normalizeEvidenceHash, } from './evidence.js';
|
|
20
|
+
export type { ProofGraphEvidenceStore, ProofGraphEvidenceStoreOptions, ProofGraphEvidenceResult, EvidenceArtifactBinding, PutEvidenceOptions, } from './evidence.js';
|
|
21
|
+
export { toHex, canonicalJson, computeNodeId, computeEdgeId, computeProofGraphId, recomputeGraphId, } from './canonical.js';
|
|
22
|
+
export { createProofGraph, setGraphMetadata, buildEdge, addNode, addEdge, addIdentityDocument, addProof, addIdentityClaim, addManifest, addManifestRevision, addReceiptLike, addAnchor, } from './graph.js';
|
|
23
|
+
export { findNode, getEdgesFrom, getEdgesTo, getEdgesByType, getProofNodes, findProofsBySubject, findProofsByIssuer, findAnchorsForProof, findRevocations, findSupersessions, findConflicts, getEvidenceTrail, getProofLineage, resolveCurrentProofSet, getManifestRevisions, getManifestsForAddress, getEdgesBetween, reachableFrom, } from './query.js';
|
|
24
|
+
export { verifyProofGraph, verifyGraphProofs, } from './verify.js';
|
|
25
|
+
export { exportProofGraph, importProofGraph, } from './io.js';
|
|
26
|
+
export * from './replicated-evidence.js';
|