@mikeargento/bitgraph-player 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.
- package/LICENSE +21 -0
- package/README.md +98 -0
- package/SPEC.md +234 -0
- package/dist/__tests__/cast-order.test.d.ts +2 -0
- package/dist/__tests__/cast-order.test.d.ts.map +1 -0
- package/dist/__tests__/cast-order.test.js +434 -0
- package/dist/__tests__/cast-order.test.js.map +1 -0
- package/dist/__tests__/evaluate-verdict.test.d.ts +2 -0
- package/dist/__tests__/evaluate-verdict.test.d.ts.map +1 -0
- package/dist/__tests__/evaluate-verdict.test.js +228 -0
- package/dist/__tests__/evaluate-verdict.test.js.map +1 -0
- package/dist/__tests__/fixtures.d.ts +63 -0
- package/dist/__tests__/fixtures.d.ts.map +1 -0
- package/dist/__tests__/fixtures.js +150 -0
- package/dist/__tests__/fixtures.js.map +1 -0
- package/dist/__tests__/logic-rule.test.d.ts +2 -0
- package/dist/__tests__/logic-rule.test.d.ts.map +1 -0
- package/dist/__tests__/logic-rule.test.js +190 -0
- package/dist/__tests__/logic-rule.test.js.map +1 -0
- package/dist/cast.d.ts +29 -0
- package/dist/cast.d.ts.map +1 -0
- package/dist/cast.js +85 -0
- package/dist/cast.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +130 -0
- package/dist/cli.js.map +1 -0
- package/dist/evaluate.d.ts +30 -0
- package/dist/evaluate.d.ts.map +1 -0
- package/dist/evaluate.js +177 -0
- package/dist/evaluate.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/logic.d.ts +16 -0
- package/dist/logic.d.ts.map +1 -0
- package/dist/logic.js +29 -0
- package/dist/logic.js.map +1 -0
- package/dist/order.d.ts +50 -0
- package/dist/order.d.ts.map +1 -0
- package/dist/order.js +290 -0
- package/dist/order.js.map +1 -0
- package/dist/rule.d.ts +40 -0
- package/dist/rule.d.ts.map +1 -0
- package/dist/rule.js +324 -0
- package/dist/rule.js.map +1 -0
- package/dist/types.d.ts +205 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/dist/verdict.d.ts +14 -0
- package/dist/verdict.d.ts.map +1 -0
- package/dist/verdict.js +146 -0
- package/dist/verdict.js.map +1 -0
- package/package.json +42 -0
- package/src/__tests__/cast-order.test.ts +473 -0
- package/src/__tests__/evaluate-verdict.test.ts +266 -0
- package/src/__tests__/fixtures.ts +206 -0
- package/src/__tests__/logic-rule.test.ts +221 -0
- package/src/cast.ts +120 -0
- package/src/cli.ts +143 -0
- package/src/evaluate.ts +238 -0
- package/src/index.ts +38 -0
- package/src/logic.ts +39 -0
- package/src/order.ts +380 -0
- package/src/rule.ts +340 -0
- package/src/types.ts +224 -0
- package/src/verdict.ts +159 -0
package/dist/verdict.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026 Mike Argento. Licensed under the MIT License. See LICENSE.
|
|
2
|
+
/**
|
|
3
|
+
* Verdict construction and serialization.
|
|
4
|
+
*
|
|
5
|
+
* Determinism is the product claim, so this module is where it is
|
|
6
|
+
* enforced: no wall-clock reads, no random values, no filesystem paths,
|
|
7
|
+
* and a fixed field order. Two machines evaluating the same rule bytes
|
|
8
|
+
* over the same bundle contents must produce byte-identical verdicts.
|
|
9
|
+
*
|
|
10
|
+
* The verdict labels its own trust boundary. `derived` is what BitGraph
|
|
11
|
+
* established; `declared` is what a named party asserted and Player took
|
|
12
|
+
* on faith — including the closed world itself, which nothing in BitGraph
|
|
13
|
+
* establishes: "there is no cancellation before approval" and "among the
|
|
14
|
+
* recordings you told me to consider, none precedes approval" are
|
|
15
|
+
* different claims, and the closed-world entry is what keeps the verdict
|
|
16
|
+
* from passing the first off as the second.
|
|
17
|
+
*/
|
|
18
|
+
import { readFileSync } from "node:fs";
|
|
19
|
+
import { normalizeDigest } from "./rule.js";
|
|
20
|
+
let cachedVersion;
|
|
21
|
+
/** The player package's own version, read once from its package.json. */
|
|
22
|
+
export function playerVersion() {
|
|
23
|
+
if (cachedVersion === undefined) {
|
|
24
|
+
const raw = readFileSync(new URL("../package.json", import.meta.url), "utf8");
|
|
25
|
+
cachedVersion = JSON.parse(raw).version;
|
|
26
|
+
}
|
|
27
|
+
return cachedVersion;
|
|
28
|
+
}
|
|
29
|
+
function resolutionLabel(res) {
|
|
30
|
+
switch (res.kind) {
|
|
31
|
+
case "resolved":
|
|
32
|
+
return "resolved";
|
|
33
|
+
case "absent":
|
|
34
|
+
return res.optional ? "absent-optional" : "absent-required";
|
|
35
|
+
case "ambiguous":
|
|
36
|
+
return "ambiguous";
|
|
37
|
+
case "invalid":
|
|
38
|
+
return "invalid";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function buildVerdict(rule, ruleSha256Hex, resolutions, evaluation, audit) {
|
|
42
|
+
// Cast summary, in declaration order. Null prototype so a role named
|
|
43
|
+
// "__proto__" serializes as an ordinary key.
|
|
44
|
+
const cast = Object.create(null);
|
|
45
|
+
for (const [role, entry] of Object.entries(rule.cast)) {
|
|
46
|
+
const res = resolutions.get(role);
|
|
47
|
+
const summary = {
|
|
48
|
+
digestB64: normalizeDigest(entry.digest),
|
|
49
|
+
resolution: resolutionLabel(res),
|
|
50
|
+
};
|
|
51
|
+
if (res.kind === "resolved") {
|
|
52
|
+
summary.proofHash = res.proof.proofHash;
|
|
53
|
+
if (res.proof.epochId !== undefined)
|
|
54
|
+
summary.epochId = res.proof.epochId;
|
|
55
|
+
summary.chainId = res.proof.chainId;
|
|
56
|
+
if (res.proof.counter !== undefined)
|
|
57
|
+
summary.counter = res.proof.counter;
|
|
58
|
+
if (res.proof.slotCounter !== undefined)
|
|
59
|
+
summary.slotCounter = res.proof.slotCounter;
|
|
60
|
+
}
|
|
61
|
+
cast[role] = summary;
|
|
62
|
+
}
|
|
63
|
+
// Declared assertions, in cast declaration order, closed world last.
|
|
64
|
+
const declared = [];
|
|
65
|
+
for (const [role, entry] of Object.entries(rule.cast)) {
|
|
66
|
+
if (entry.means !== undefined) {
|
|
67
|
+
declared.push({
|
|
68
|
+
assertion: "means",
|
|
69
|
+
role,
|
|
70
|
+
verifiedHere: false,
|
|
71
|
+
means: entry.means,
|
|
72
|
+
digestB64: normalizeDigest(entry.digest),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
for (const [role, entry] of Object.entries(rule.cast)) {
|
|
77
|
+
if (entry.at !== undefined) {
|
|
78
|
+
declared.push({
|
|
79
|
+
assertion: "pinned-occurrence",
|
|
80
|
+
role,
|
|
81
|
+
verifiedHere: false,
|
|
82
|
+
at: entry.at,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
for (const [role, entry] of Object.entries(rule.cast)) {
|
|
87
|
+
if (entry.signedBy !== undefined) {
|
|
88
|
+
declared.push({
|
|
89
|
+
assertion: "signedBy",
|
|
90
|
+
role,
|
|
91
|
+
verifiedHere: false,
|
|
92
|
+
evidence: entry.signedBy,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
declared.push({
|
|
97
|
+
assertion: "closed-world",
|
|
98
|
+
verifiedHere: false,
|
|
99
|
+
castSize: Object.keys(rule.cast).length,
|
|
100
|
+
recordingsInBundle: audit.ingest.counts.observed,
|
|
101
|
+
claim: "Absence is asserted only among the declared cast. Nothing in BitGraph establishes that the declared cast is complete; that completeness is the rule author's assertion.",
|
|
102
|
+
});
|
|
103
|
+
const verdict = {
|
|
104
|
+
verdict: "bitgraph-player-verdict/1",
|
|
105
|
+
result: evaluation.result,
|
|
106
|
+
rule: { id: rule.id, sha256: ruleSha256Hex },
|
|
107
|
+
cast,
|
|
108
|
+
derived: evaluation.steps,
|
|
109
|
+
declared,
|
|
110
|
+
evaluator: { name: "@mikeargento/bitgraph-player", version: playerVersion() },
|
|
111
|
+
network: "none",
|
|
112
|
+
};
|
|
113
|
+
if (rule.then !== undefined)
|
|
114
|
+
verdict.then = rule.then;
|
|
115
|
+
if (evaluation.weakestEvidence !== undefined) {
|
|
116
|
+
verdict.weakestEvidence = evaluation.weakestEvidence;
|
|
117
|
+
}
|
|
118
|
+
return verdict;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Deterministic bytes. Fixed key order (construction order), two-space
|
|
122
|
+
* indent, trailing newline. No timestamp: a `generatedAt` field would
|
|
123
|
+
* break byte-identical reproduction, which is the entire product claim.
|
|
124
|
+
* If a run timestamp is wanted it belongs in a sidecar, never here.
|
|
125
|
+
*/
|
|
126
|
+
export function serializeVerdict(verdict) {
|
|
127
|
+
// Emit top-level fields in the canonical order regardless of
|
|
128
|
+
// construction history, so optional fields land in a fixed place.
|
|
129
|
+
const ordered = {
|
|
130
|
+
verdict: verdict.verdict,
|
|
131
|
+
result: verdict.result,
|
|
132
|
+
rule: verdict.rule,
|
|
133
|
+
};
|
|
134
|
+
if (verdict.then !== undefined)
|
|
135
|
+
ordered["then"] = verdict.then;
|
|
136
|
+
if (verdict.weakestEvidence !== undefined) {
|
|
137
|
+
ordered["weakestEvidence"] = verdict.weakestEvidence;
|
|
138
|
+
}
|
|
139
|
+
ordered["cast"] = verdict.cast;
|
|
140
|
+
ordered["derived"] = verdict.derived;
|
|
141
|
+
ordered["declared"] = verdict.declared;
|
|
142
|
+
ordered["evaluator"] = verdict.evaluator;
|
|
143
|
+
ordered["network"] = verdict.network;
|
|
144
|
+
return JSON.stringify(ordered, null, 2) + "\n";
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=verdict.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verdict.js","sourceRoot":"","sources":["../src/verdict.ts"],"names":[],"mappings":"AAAA,qFAAqF;AAErF;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAGvC,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAG5C,IAAI,aAAiC,CAAC;AAEtC,yEAAyE;AACzE,MAAM,UAAU,aAAa;IAC3B,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;QAChC,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;QAC9E,aAAa,GAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAyB,CAAC,OAAO,CAAC;IACnE,CAAC;IACD,OAAO,aAAa,CAAC;AACvB,CAAC;AAED,SAAS,eAAe,CAAC,GAAe;IACtC,QAAQ,GAAG,CAAC,IAAI,EAAE,CAAC;QACjB,KAAK,UAAU;YACb,OAAO,UAAU,CAAC;QACpB,KAAK,QAAQ;YACX,OAAO,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,iBAAiB,CAAC;QAC9D,KAAK,WAAW;YACd,OAAO,WAAW,CAAC;QACrB,KAAK,SAAS;YACZ,OAAO,SAAS,CAAC;IACrB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,IAAU,EACV,aAAqB,EACrB,WAAoC,EACpC,UAAsB,EACtB,KAAkB;IAElB,qEAAqE;IACrE,6CAA6C;IAC7C,MAAM,IAAI,GAAoB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAoB,CAAC;IACrE,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACtD,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,CAAe,CAAC;QAChD,MAAM,OAAO,GAA4B;YACvC,SAAS,EAAE,eAAe,CAAC,KAAK,CAAC,MAAM,CAAW;YAClD,UAAU,EAAE,eAAe,CAAC,GAAG,CAAC;SACjC,CAAC;QACF,IAAI,GAAG,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YAC5B,OAAO,CAAC,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC;YACxC,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,KAAK,SAAS;gBAAE,OAAO,CAAC,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC;YACzE,OAAO,CAAC,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC;YACpC,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,KAAK,SAAS;gBAAE,OAAO,CAAC,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC;YACzE,IAAI,GAAG,CAAC,KAAK,CAAC,WAAW,KAAK,SAAS;gBAAE,OAAO,CAAC,WAAW,GAAG,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC;QACvF,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC;IACvB,CAAC;IAED,qEAAqE;IACrE,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACtD,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YAC9B,QAAQ,CAAC,IAAI,CAAC;gBACZ,SAAS,EAAE,OAAO;gBAClB,IAAI;gBACJ,YAAY,EAAE,KAAK;gBACnB,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,SAAS,EAAE,eAAe,CAAC,KAAK,CAAC,MAAM,CAAW;aACnD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACtD,IAAI,KAAK,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;YAC3B,QAAQ,CAAC,IAAI,CAAC;gBACZ,SAAS,EAAE,mBAAmB;gBAC9B,IAAI;gBACJ,YAAY,EAAE,KAAK;gBACnB,EAAE,EAAE,KAAK,CAAC,EAAE;aACb,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACtD,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YACjC,QAAQ,CAAC,IAAI,CAAC;gBACZ,SAAS,EAAE,UAAU;gBACrB,IAAI;gBACJ,YAAY,EAAE,KAAK;gBACnB,QAAQ,EAAE,KAAK,CAAC,QAAQ;aACzB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,QAAQ,CAAC,IAAI,CAAC;QACZ,SAAS,EAAE,cAAc;QACzB,YAAY,EAAE,KAAK;QACnB,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM;QACvC,kBAAkB,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ;QAChD,KAAK,EACH,yKAAyK;KAC5K,CAAC,CAAC;IAEH,MAAM,OAAO,GAAY;QACvB,OAAO,EAAE,2BAA2B;QACpC,MAAM,EAAE,UAAU,CAAC,MAAM;QACzB,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE;QAC5C,IAAI;QACJ,OAAO,EAAE,UAAU,CAAC,KAAK;QACzB,QAAQ;QACR,SAAS,EAAE,EAAE,IAAI,EAAE,8BAA8B,EAAE,OAAO,EAAE,aAAa,EAAE,EAAE;QAC7E,OAAO,EAAE,MAAM;KAChB,CAAC;IACF,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACtD,IAAI,UAAU,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;QAC7C,OAAO,CAAC,eAAe,GAAG,UAAU,CAAC,eAAe,CAAC;IACvD,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAgB;IAC/C,6DAA6D;IAC7D,kEAAkE;IAClE,MAAM,OAAO,GAA4B;QACvC,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,IAAI,EAAE,OAAO,CAAC,IAAI;KACnB,CAAC;IACF,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAC/D,IAAI,OAAO,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;QAC1C,OAAO,CAAC,iBAAiB,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC;IACvD,CAAC;IACD,OAAO,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAC/B,OAAO,CAAC,SAAS,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IACrC,OAAO,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC;IACvC,OAAO,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC;IACzC,OAAO,CAAC,SAAS,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IACrC,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;AACjD,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mikeargento/bitgraph-player",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Deterministic evaluation of causal rules over BitGraph proof bundles: a pure function from (rule, verified evidence) to a reproducible verdict.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"bitgraph-play": "./dist/cli.js"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"src",
|
|
21
|
+
"SPEC.md",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc",
|
|
27
|
+
"prepack": "tsc",
|
|
28
|
+
"typecheck": "tsc --noEmit",
|
|
29
|
+
"test": "tsc && node --test \"dist/__tests__/*.test.js\""
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=20"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@mikeargento/bitgraph-audit": "^0.1.3",
|
|
36
|
+
"@mikeargento/bitgraph-verify": "^1.1.2"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^20.0.0",
|
|
40
|
+
"typescript": "^5.4.0"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026 Mike Argento. Licensed under the MIT License. See LICENSE.
|
|
2
|
+
|
|
3
|
+
import { strict as assert } from "node:assert";
|
|
4
|
+
import { test } from "node:test";
|
|
5
|
+
import { resolveRole } from "../cast.js";
|
|
6
|
+
import { compare } from "../order.js";
|
|
7
|
+
import { digestFor, makeAudit, proofHashOf } from "./fixtures.js";
|
|
8
|
+
import type { ObservedProof } from "@mikeargento/bitgraph-audit";
|
|
9
|
+
|
|
10
|
+
const D1 = digestFor("doc-one");
|
|
11
|
+
const D2 = digestFor("doc-two");
|
|
12
|
+
|
|
13
|
+
function proofByName(audit: ReturnType<typeof makeAudit>, name: string): ObservedProof {
|
|
14
|
+
const p = audit.ingest.proofs.find((x) => x.proofHash === proofHashOf(name));
|
|
15
|
+
assert.ok(p, `fixture proof ${name}`);
|
|
16
|
+
return p as ObservedProof;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Cast resolution: the multiplicity table.
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
test("exactly one verified match resolves", () => {
|
|
24
|
+
const audit = makeAudit({
|
|
25
|
+
proofs: [{ name: "a", digestB64: D1, epochId: "E1", counter: "10" }],
|
|
26
|
+
partitions: [{ epochId: "E1", members: ["a"] }],
|
|
27
|
+
});
|
|
28
|
+
const res = resolveRole("doc", { digest: D1 }, audit);
|
|
29
|
+
assert.equal(res.kind, "resolved");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("zero matches: absent, optional vs required distinguished", () => {
|
|
33
|
+
const audit = makeAudit({ proofs: [] });
|
|
34
|
+
const required = resolveRole("doc", { digest: D1 }, audit);
|
|
35
|
+
assert.deepEqual(required, { kind: "absent", role: "doc", optional: false });
|
|
36
|
+
const optional = resolveRole("doc", { digest: D1, optional: true }, audit);
|
|
37
|
+
assert.deepEqual(optional, { kind: "absent", role: "doc", optional: true });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("two recordings of the same bits with no pin is ambiguous, never a silent pick", () => {
|
|
41
|
+
const audit = makeAudit({
|
|
42
|
+
proofs: [
|
|
43
|
+
{ name: "a1", digestB64: D1, epochId: "E1", counter: "10" },
|
|
44
|
+
{ name: "a2", digestB64: D1, epochId: "E2", counter: "3" },
|
|
45
|
+
],
|
|
46
|
+
partitions: [
|
|
47
|
+
{ epochId: "E1", members: ["a1"] },
|
|
48
|
+
{ epochId: "E2", members: ["a2"], publicKeyB64: "key-B" },
|
|
49
|
+
],
|
|
50
|
+
});
|
|
51
|
+
const res = resolveRole("doc", { digest: D1 }, audit);
|
|
52
|
+
assert.equal(res.kind, "ambiguous");
|
|
53
|
+
assert.equal((res as { matchCount: number }).matchCount, 2);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("a pin selects one occurrence among several", () => {
|
|
57
|
+
const audit = makeAudit({
|
|
58
|
+
proofs: [
|
|
59
|
+
{ name: "a1", digestB64: D1, epochId: "E1", counter: "10" },
|
|
60
|
+
{ name: "a2", digestB64: D1, epochId: "E2", counter: "3" },
|
|
61
|
+
],
|
|
62
|
+
});
|
|
63
|
+
const byHash = resolveRole("doc", { digest: D1, at: { proofHash: proofHashOf("a2") } }, audit);
|
|
64
|
+
assert.equal(byHash.kind, "resolved");
|
|
65
|
+
assert.equal((byHash as { proof: ObservedProof }).proof.proofHash, proofHashOf("a2"));
|
|
66
|
+
|
|
67
|
+
const byPosition = resolveRole(
|
|
68
|
+
"doc",
|
|
69
|
+
{ digest: D1, at: { epochId: "E1", counter: "10" } },
|
|
70
|
+
audit
|
|
71
|
+
);
|
|
72
|
+
assert.equal(byPosition.kind, "resolved");
|
|
73
|
+
assert.equal((byPosition as { proof: ObservedProof }).proof.proofHash, proofHashOf("a1"));
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("a pin matching nothing is invalid, not silently ignored", () => {
|
|
77
|
+
const audit = makeAudit({
|
|
78
|
+
proofs: [{ name: "a1", digestB64: D1, epochId: "E1", counter: "10" }],
|
|
79
|
+
});
|
|
80
|
+
const res = resolveRole("doc", { digest: D1, at: { proofHash: "nope" } }, audit);
|
|
81
|
+
assert.equal(res.kind, "invalid");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("matches that all fail verification are invalid, not absent", () => {
|
|
85
|
+
// "Only broken recordings" and "no recordings" are different situations:
|
|
86
|
+
// only the second supports a closed-world absence claim.
|
|
87
|
+
const audit = makeAudit({
|
|
88
|
+
proofs: [{ name: "a1", digestB64: D1, epochId: "E1", counter: "10", verified: false }],
|
|
89
|
+
});
|
|
90
|
+
const res = resolveRole("doc", { digest: D1, optional: true }, audit);
|
|
91
|
+
assert.equal(res.kind, "invalid");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("a failed-verification copy does not shadow the verified recording", () => {
|
|
95
|
+
const audit = makeAudit({
|
|
96
|
+
proofs: [
|
|
97
|
+
{ name: "bad", digestB64: D1, epochId: "E1", counter: "9", verified: false },
|
|
98
|
+
{ name: "good", digestB64: D1, epochId: "E1", counter: "10" },
|
|
99
|
+
],
|
|
100
|
+
});
|
|
101
|
+
const res = resolveRole("doc", { digest: D1 }, audit);
|
|
102
|
+
assert.equal(res.kind, "resolved");
|
|
103
|
+
assert.equal((res as { proof: ObservedProof }).proof.proofHash, proofHashOf("good"));
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// Ordering: same partition.
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
test("a directed prevB64 path is chain-link, strict, both directions", () => {
|
|
111
|
+
const audit = makeAudit({
|
|
112
|
+
proofs: [
|
|
113
|
+
{ name: "a", digestB64: D1, epochId: "E1", counter: "10" },
|
|
114
|
+
{ name: "b", digestB64: D2, epochId: "E1", counter: "20", prev: "a" },
|
|
115
|
+
],
|
|
116
|
+
partitions: [{ epochId: "E1", members: ["a", "b"], components: [["a", "b"]] }],
|
|
117
|
+
});
|
|
118
|
+
const ab = compare(proofByName(audit, "a"), proofByName(audit, "b"), audit);
|
|
119
|
+
assert.equal(ab.relation, "before");
|
|
120
|
+
assert.equal(ab.basis, "chain-link");
|
|
121
|
+
assert.equal(ab.tier, "hash-linked");
|
|
122
|
+
assert.equal(ab.weaker, false);
|
|
123
|
+
assert.equal(ab.assumptionDependent, false);
|
|
124
|
+
const ba = compare(proofByName(audit, "b"), proofByName(audit, "a"), audit);
|
|
125
|
+
assert.equal(ba.relation, "after");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("fork branches with no path between them are counter-order, NOT chain-link", () => {
|
|
129
|
+
// Predecessor-reuse fork: x and y both link to g, no path between them.
|
|
130
|
+
// Their relative order rests solely on counter discipline — which the
|
|
131
|
+
// fork itself demonstrates is broken — so the hash-linked tier must not
|
|
132
|
+
// be claimed even though all three share one undirected component.
|
|
133
|
+
const audit = makeAudit({
|
|
134
|
+
proofs: [
|
|
135
|
+
{ name: "g", digestB64: digestFor("genesis"), epochId: "E1", counter: "1" },
|
|
136
|
+
{ name: "x", digestB64: D1, epochId: "E1", counter: "2", prev: "g" },
|
|
137
|
+
{ name: "y", digestB64: D2, epochId: "E1", counter: "3", prev: "g" },
|
|
138
|
+
],
|
|
139
|
+
partitions: [{ epochId: "E1", members: ["g", "x", "y"], components: [["g", "x", "y"]] }],
|
|
140
|
+
});
|
|
141
|
+
const xy = compare(proofByName(audit, "x"), proofByName(audit, "y"), audit);
|
|
142
|
+
assert.equal(xy.relation, "before");
|
|
143
|
+
assert.equal(xy.basis, "counter-order");
|
|
144
|
+
assert.equal(xy.tier, "assumption-dependent");
|
|
145
|
+
assert.equal(xy.weaker, true);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("a verified path contradicted by the counters is an anomaly, not an order", () => {
|
|
149
|
+
// b hash-links after a, but b's counter is SMALLER: link and counter
|
|
150
|
+
// evidence disagree, so neither may answer.
|
|
151
|
+
const audit = makeAudit({
|
|
152
|
+
proofs: [
|
|
153
|
+
{ name: "a", digestB64: D1, epochId: "E1", counter: "20" },
|
|
154
|
+
{ name: "b", digestB64: D2, epochId: "E1", counter: "10", prev: "a" },
|
|
155
|
+
],
|
|
156
|
+
partitions: [{ epochId: "E1", members: ["a", "b"], components: [["a", "b"]] }],
|
|
157
|
+
});
|
|
158
|
+
assert.equal(compare(proofByName(audit, "a"), proofByName(audit, "b"), audit).relation, "unordered");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("non-decimal counters are no counter evidence: no crash, no fabricated order", () => {
|
|
162
|
+
for (const bad of ["abc", "1e5", "0x10", "-3", " 7 ", ""]) {
|
|
163
|
+
const audit = makeAudit({
|
|
164
|
+
proofs: [
|
|
165
|
+
{ name: "a", digestB64: D1, epochId: "E1", counter: bad },
|
|
166
|
+
{ name: "b", digestB64: D2, epochId: "E1", counter: "20" },
|
|
167
|
+
],
|
|
168
|
+
partitions: [{ epochId: "E1", members: ["a", "b"], components: [["a"], ["b"]] }],
|
|
169
|
+
});
|
|
170
|
+
const result = compare(proofByName(audit, "a"), proofByName(audit, "b"), audit);
|
|
171
|
+
assert.equal(result.relation, "unordered", `counter ${JSON.stringify(bad)}`);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("different components in one partition: counter-order, assumption-dependent", () => {
|
|
176
|
+
const audit = makeAudit({
|
|
177
|
+
proofs: [
|
|
178
|
+
{ name: "a", digestB64: D1, epochId: "E1", counter: "10" },
|
|
179
|
+
{ name: "b", digestB64: D2, epochId: "E1", counter: "20" },
|
|
180
|
+
],
|
|
181
|
+
partitions: [{ epochId: "E1", members: ["a", "b"], components: [["a"], ["b"]] }],
|
|
182
|
+
});
|
|
183
|
+
const ab = compare(proofByName(audit, "a"), proofByName(audit, "b"), audit);
|
|
184
|
+
assert.equal(ab.relation, "before");
|
|
185
|
+
assert.equal(ab.basis, "counter-order");
|
|
186
|
+
assert.equal(ab.assumptionDependent, true);
|
|
187
|
+
assert.equal(ab.weaker, true);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("interleaved slot/commit ranges with clear commit order is BEFORE, not needless UNDETERMINED", () => {
|
|
191
|
+
// A reserved its slot first (48) but committed second (51); B reserved
|
|
192
|
+
// second (49) but committed first (50). The artifact position is the
|
|
193
|
+
// commit position; commit order decides.
|
|
194
|
+
const audit = makeAudit({
|
|
195
|
+
proofs: [
|
|
196
|
+
{ name: "b", digestB64: D2, epochId: "E1", counter: "50", slotCounter: "49" },
|
|
197
|
+
{ name: "a", digestB64: D1, epochId: "E1", counter: "51", slotCounter: "48", prev: "b" },
|
|
198
|
+
],
|
|
199
|
+
partitions: [{ epochId: "E1", members: ["a", "b"], components: [["a", "b"]] }],
|
|
200
|
+
});
|
|
201
|
+
const ba = compare(proofByName(audit, "b"), proofByName(audit, "a"), audit);
|
|
202
|
+
assert.equal(ba.relation, "before");
|
|
203
|
+
assert.equal(ba.basis, "chain-link");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("equal commit counters on distinct recordings is an anomaly, not an order", () => {
|
|
207
|
+
const audit = makeAudit({
|
|
208
|
+
proofs: [
|
|
209
|
+
{ name: "a", digestB64: D1, epochId: "E1", counter: "10" },
|
|
210
|
+
{ name: "b", digestB64: D2, epochId: "E1", counter: "10" },
|
|
211
|
+
],
|
|
212
|
+
partitions: [{ epochId: "E1", members: ["a", "b"], components: [["a"], ["b"]] }],
|
|
213
|
+
});
|
|
214
|
+
assert.equal(compare(proofByName(audit, "a"), proofByName(audit, "b"), audit).relation, "unordered");
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test("same recording for both roles is 'same'", () => {
|
|
218
|
+
const audit = makeAudit({
|
|
219
|
+
proofs: [{ name: "a", digestB64: D1, epochId: "E1", counter: "10" }],
|
|
220
|
+
partitions: [{ epochId: "E1", members: ["a"] }],
|
|
221
|
+
});
|
|
222
|
+
const p = proofByName(audit, "a");
|
|
223
|
+
assert.equal(compare(p, p, audit).relation, "same");
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
227
|
+
// Ordering: across partitions and epochs.
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
|
|
230
|
+
test("same epochId under different signer keys is not counter-comparable", () => {
|
|
231
|
+
const audit = makeAudit({
|
|
232
|
+
proofs: [
|
|
233
|
+
{ name: "a", digestB64: D1, epochId: "E1", counter: "10", publicKeyB64: "key-A" },
|
|
234
|
+
{ name: "b", digestB64: D2, epochId: "E1", counter: "20", publicKeyB64: "key-B" },
|
|
235
|
+
],
|
|
236
|
+
partitions: [
|
|
237
|
+
{ epochId: "E1", members: ["a"], publicKeyB64: "key-A" },
|
|
238
|
+
{ epochId: "E1", members: ["b"], publicKeyB64: "key-B" },
|
|
239
|
+
],
|
|
240
|
+
});
|
|
241
|
+
assert.equal(compare(proofByName(audit, "a"), proofByName(audit, "b"), audit).relation, "unordered");
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test("a covered epochLink succession orders the pair at the hash-linked tier", () => {
|
|
245
|
+
// a IS the referenced predecessor; b carries the link (successor key).
|
|
246
|
+
const audit = makeAudit({
|
|
247
|
+
proofs: [
|
|
248
|
+
{ name: "a", digestB64: D1, epochId: "E1", counter: "999", publicKeyB64: "key-A" },
|
|
249
|
+
{ name: "b", digestB64: D2, epochId: "E2", counter: "1", publicKeyB64: "key-B" },
|
|
250
|
+
],
|
|
251
|
+
partitions: [
|
|
252
|
+
{ epochId: "E1", members: ["a"], publicKeyB64: "key-A" },
|
|
253
|
+
{ epochId: "E2", members: ["b"], publicKeyB64: "key-B" },
|
|
254
|
+
],
|
|
255
|
+
edges: [{ predecessor: "a", via: "b" }],
|
|
256
|
+
});
|
|
257
|
+
const ab = compare(proofByName(audit, "a"), proofByName(audit, "b"), audit);
|
|
258
|
+
assert.equal(ab.relation, "before");
|
|
259
|
+
assert.equal(ab.basis, "epoch-lineage");
|
|
260
|
+
assert.equal(ab.tier, "hash-linked");
|
|
261
|
+
assert.equal(ab.assumptionDependent, false);
|
|
262
|
+
const ba = compare(proofByName(audit, "b"), proofByName(audit, "a"), audit);
|
|
263
|
+
assert.equal(ba.relation, "after");
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("lineage covers a path-linked ancestor of the referenced predecessor at hash-linked tier", () => {
|
|
267
|
+
const audit = makeAudit({
|
|
268
|
+
proofs: [
|
|
269
|
+
{ name: "a0", digestB64: digestFor("earlier"), epochId: "E1", counter: "5", publicKeyB64: "key-A" },
|
|
270
|
+
{ name: "p", digestB64: D1, epochId: "E1", counter: "999", publicKeyB64: "key-A", prev: "a0" },
|
|
271
|
+
{ name: "b", digestB64: D2, epochId: "E2", counter: "1", publicKeyB64: "key-B" },
|
|
272
|
+
],
|
|
273
|
+
partitions: [
|
|
274
|
+
{ epochId: "E1", members: ["a0", "p"], publicKeyB64: "key-A", components: [["a0", "p"]] },
|
|
275
|
+
{ epochId: "E2", members: ["b"], publicKeyB64: "key-B" },
|
|
276
|
+
],
|
|
277
|
+
edges: [{ predecessor: "p", via: "b" }],
|
|
278
|
+
});
|
|
279
|
+
const result = compare(proofByName(audit, "a0"), proofByName(audit, "b"), audit);
|
|
280
|
+
assert.equal(result.relation, "before");
|
|
281
|
+
assert.equal(result.tier, "hash-linked");
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test("counter-only predecessor coverage downgrades lineage to the assumption-dependent tier", () => {
|
|
285
|
+
// a1 is not path-linked to the referenced predecessor p; only its
|
|
286
|
+
// smaller counter places it before p, so the whole answer rests on
|
|
287
|
+
// counter discipline.
|
|
288
|
+
const audit = makeAudit({
|
|
289
|
+
proofs: [
|
|
290
|
+
{ name: "a1", digestB64: digestFor("unlinked"), epochId: "E1", counter: "7", publicKeyB64: "key-A" },
|
|
291
|
+
{ name: "p", digestB64: D1, epochId: "E1", counter: "999", publicKeyB64: "key-A" },
|
|
292
|
+
{ name: "b", digestB64: D2, epochId: "E2", counter: "1", publicKeyB64: "key-B" },
|
|
293
|
+
],
|
|
294
|
+
partitions: [
|
|
295
|
+
{ epochId: "E1", members: ["a1", "p"], publicKeyB64: "key-A", components: [["a1"], ["p"]] },
|
|
296
|
+
{ epochId: "E2", members: ["b"], publicKeyB64: "key-B" },
|
|
297
|
+
],
|
|
298
|
+
edges: [{ predecessor: "p", via: "b" }],
|
|
299
|
+
});
|
|
300
|
+
const result = compare(proofByName(audit, "a1"), proofByName(audit, "b"), audit);
|
|
301
|
+
assert.equal(result.relation, "before");
|
|
302
|
+
assert.equal(result.basis, "epoch-lineage");
|
|
303
|
+
assert.equal(result.tier, "assumption-dependent");
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test("lineage does NOT cover a recording after the referenced predecessor", () => {
|
|
307
|
+
// a2's counter exceeds the referenced predecessor's: the link only
|
|
308
|
+
// evidences that E2 began after p existed, which says nothing about a2.
|
|
309
|
+
const audit = makeAudit({
|
|
310
|
+
proofs: [
|
|
311
|
+
{ name: "p", digestB64: D1, epochId: "E1", counter: "10", publicKeyB64: "key-A" },
|
|
312
|
+
{ name: "a2", digestB64: digestFor("later"), epochId: "E1", counter: "999", publicKeyB64: "key-A" },
|
|
313
|
+
{ name: "b", digestB64: D2, epochId: "E2", counter: "1", publicKeyB64: "key-B" },
|
|
314
|
+
],
|
|
315
|
+
partitions: [
|
|
316
|
+
{ epochId: "E1", members: ["p", "a2"], publicKeyB64: "key-A", components: [["p"], ["a2"]] },
|
|
317
|
+
{ epochId: "E2", members: ["b"], publicKeyB64: "key-B" },
|
|
318
|
+
],
|
|
319
|
+
edges: [{ predecessor: "p", via: "b" }],
|
|
320
|
+
});
|
|
321
|
+
assert.equal(compare(proofByName(audit, "a2"), proofByName(audit, "b"), audit).relation, "unordered");
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
test("a self-declared epochId string cannot ride another epoch's lineage", () => {
|
|
325
|
+
// evil declares epochId "E2" under its own key; the lineage edge covers
|
|
326
|
+
// only the partition the via proof belongs to, so evil gets nothing.
|
|
327
|
+
const audit = makeAudit({
|
|
328
|
+
proofs: [
|
|
329
|
+
{ name: "a", digestB64: D1, epochId: "E1", counter: "10", publicKeyB64: "key-A" },
|
|
330
|
+
{ name: "b", digestB64: digestFor("real-successor"), epochId: "E2", counter: "1", publicKeyB64: "key-B" },
|
|
331
|
+
{ name: "evil", digestB64: D2, epochId: "E2", counter: "1", publicKeyB64: "key-EVIL" },
|
|
332
|
+
],
|
|
333
|
+
partitions: [
|
|
334
|
+
{ epochId: "E1", members: ["a"], publicKeyB64: "key-A" },
|
|
335
|
+
{ epochId: "E2", members: ["b"], publicKeyB64: "key-B" },
|
|
336
|
+
{ epochId: "E2", members: ["evil"], publicKeyB64: "key-EVIL" },
|
|
337
|
+
],
|
|
338
|
+
edges: [{ predecessor: "a", via: "b" }],
|
|
339
|
+
});
|
|
340
|
+
assert.equal(compare(proofByName(audit, "a"), proofByName(audit, "evil"), audit).relation, "unordered");
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test("anchor bounds order cross-epoch recordings only on strict block inequality", () => {
|
|
344
|
+
const audit = makeAudit({
|
|
345
|
+
proofs: [
|
|
346
|
+
{ name: "a", digestB64: D1, epochId: "E1", counter: "5", publicKeyB64: "key-A" },
|
|
347
|
+
{ name: "b", digestB64: D2, epochId: "E2", counter: "5", publicKeyB64: "key-B" },
|
|
348
|
+
],
|
|
349
|
+
partitions: [
|
|
350
|
+
{ epochId: "E1", members: ["a"], publicKeyB64: "key-A" },
|
|
351
|
+
{ epochId: "E2", members: ["b"], publicKeyB64: "key-B" },
|
|
352
|
+
],
|
|
353
|
+
segments: [
|
|
354
|
+
{
|
|
355
|
+
members: ["a"],
|
|
356
|
+
upperBounds: [{ blockNumber: "100", timestamp: 1000, basis: "causal-precedence" }],
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
members: ["b"],
|
|
360
|
+
lowerBounds: [{ blockNumber: "200", timestamp: 2000 }],
|
|
361
|
+
},
|
|
362
|
+
],
|
|
363
|
+
});
|
|
364
|
+
const ab = compare(proofByName(audit, "a"), proofByName(audit, "b"), audit);
|
|
365
|
+
assert.equal(ab.relation, "before");
|
|
366
|
+
assert.equal(ab.basis, "anchor-bounds");
|
|
367
|
+
assert.equal(ab.assumptionDependent, true);
|
|
368
|
+
const ba = compare(proofByName(audit, "b"), proofByName(audit, "a"), audit);
|
|
369
|
+
assert.equal(ba.relation, "after");
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
test("equal anchor blocks prove nothing: overlapping windows stay unordered", () => {
|
|
373
|
+
const audit = makeAudit({
|
|
374
|
+
proofs: [
|
|
375
|
+
{ name: "a", digestB64: D1, epochId: "E1", counter: "5", publicKeyB64: "key-A" },
|
|
376
|
+
{ name: "b", digestB64: D2, epochId: "E2", counter: "5", publicKeyB64: "key-B" },
|
|
377
|
+
],
|
|
378
|
+
partitions: [
|
|
379
|
+
{ epochId: "E1", members: ["a"], publicKeyB64: "key-A" },
|
|
380
|
+
{ epochId: "E2", members: ["b"], publicKeyB64: "key-B" },
|
|
381
|
+
],
|
|
382
|
+
segments: [
|
|
383
|
+
{ members: ["a"], upperBounds: [{ blockNumber: "100", timestamp: 1000 }] },
|
|
384
|
+
{ members: ["b"], lowerBounds: [{ blockNumber: "100", timestamp: 1000 }] },
|
|
385
|
+
],
|
|
386
|
+
});
|
|
387
|
+
assert.equal(compare(proofByName(audit, "a"), proofByName(audit, "b"), audit).relation, "unordered");
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
test("anchor bounds strict in BOTH directions are contradictory: unordered", () => {
|
|
391
|
+
const audit = makeAudit({
|
|
392
|
+
proofs: [
|
|
393
|
+
{ name: "a", digestB64: D1, epochId: "E1", counter: "5", publicKeyB64: "key-A" },
|
|
394
|
+
{ name: "b", digestB64: D2, epochId: "E2", counter: "5", publicKeyB64: "key-B" },
|
|
395
|
+
],
|
|
396
|
+
partitions: [
|
|
397
|
+
{ epochId: "E1", members: ["a"], publicKeyB64: "key-A" },
|
|
398
|
+
{ epochId: "E2", members: ["b"], publicKeyB64: "key-B" },
|
|
399
|
+
],
|
|
400
|
+
segments: [
|
|
401
|
+
{
|
|
402
|
+
members: ["a"],
|
|
403
|
+
upperBounds: [{ blockNumber: "100", timestamp: 1000 }],
|
|
404
|
+
lowerBounds: [{ blockNumber: "500", timestamp: 5000 }],
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
members: ["b"],
|
|
408
|
+
upperBounds: [{ blockNumber: "100", timestamp: 1000 }],
|
|
409
|
+
lowerBounds: [{ blockNumber: "500", timestamp: 5000 }],
|
|
410
|
+
},
|
|
411
|
+
],
|
|
412
|
+
});
|
|
413
|
+
assert.equal(compare(proofByName(audit, "a"), proofByName(audit, "b"), audit).relation, "unordered");
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
test("integrity-tier passes resolve: a proof verified without artifact bytes is a recording", () => {
|
|
417
|
+
const audit = makeAudit({
|
|
418
|
+
proofs: [{ name: "a", digestB64: D1, epochId: "E1", counter: "10", tier: "integrity" }],
|
|
419
|
+
partitions: [{ epochId: "E1", members: ["a"] }],
|
|
420
|
+
});
|
|
421
|
+
const res = resolveRole("doc", { digest: D1 }, audit);
|
|
422
|
+
assert.equal(res.kind, "resolved");
|
|
423
|
+
assert.equal((res as { verificationTier: string }).verificationTier, "integrity");
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
test("weaker bound evidence propagates to the answer", () => {
|
|
427
|
+
const audit = makeAudit({
|
|
428
|
+
proofs: [
|
|
429
|
+
{ name: "a", digestB64: D1, epochId: "E1", counter: "5", publicKeyB64: "key-A" },
|
|
430
|
+
{ name: "b", digestB64: D2, epochId: "E2", counter: "5", publicKeyB64: "key-B" },
|
|
431
|
+
],
|
|
432
|
+
partitions: [
|
|
433
|
+
{ epochId: "E1", members: ["a"], publicKeyB64: "key-A" },
|
|
434
|
+
{ epochId: "E2", members: ["b"], publicKeyB64: "key-B" },
|
|
435
|
+
],
|
|
436
|
+
segments: [
|
|
437
|
+
{
|
|
438
|
+
members: ["a"],
|
|
439
|
+
upperBounds: [{ blockNumber: "100", timestamp: 1000, evidence: "counter-order", weaker: true }],
|
|
440
|
+
},
|
|
441
|
+
{ members: ["b"], lowerBounds: [{ blockNumber: "200", timestamp: 2000 }] },
|
|
442
|
+
],
|
|
443
|
+
});
|
|
444
|
+
const ab = compare(proofByName(audit, "a"), proofByName(audit, "b"), audit);
|
|
445
|
+
assert.equal(ab.relation, "before");
|
|
446
|
+
assert.equal(ab.weaker, true);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
test("concurrent epochs with no evidence are unordered", () => {
|
|
450
|
+
const audit = makeAudit({
|
|
451
|
+
proofs: [
|
|
452
|
+
{ name: "a", digestB64: D1, epochId: "E1", counter: "5", publicKeyB64: "key-A" },
|
|
453
|
+
{ name: "b", digestB64: D2, epochId: "E2", counter: "5", publicKeyB64: "key-B" },
|
|
454
|
+
],
|
|
455
|
+
partitions: [
|
|
456
|
+
{ epochId: "E1", members: ["a"], publicKeyB64: "key-A" },
|
|
457
|
+
{ epochId: "E2", members: ["b"], publicKeyB64: "key-B" },
|
|
458
|
+
],
|
|
459
|
+
});
|
|
460
|
+
assert.equal(compare(proofByName(audit, "a"), proofByName(audit, "b"), audit).relation, "unordered");
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
test("chainless recordings hold no causal position to compare", () => {
|
|
464
|
+
const audit = makeAudit({
|
|
465
|
+
proofs: [
|
|
466
|
+
{ name: "a", digestB64: D1 },
|
|
467
|
+
{ name: "b", digestB64: D2, epochId: "E1", counter: "5" },
|
|
468
|
+
],
|
|
469
|
+
partitions: [{ epochId: "E1", members: ["b"] }],
|
|
470
|
+
unchained: ["a"],
|
|
471
|
+
});
|
|
472
|
+
assert.equal(compare(proofByName(audit, "a"), proofByName(audit, "b"), audit).relation, "unordered");
|
|
473
|
+
});
|