@sanning/proof 0.14.0 → 0.15.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/dist/cli.d.ts CHANGED
@@ -1,6 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { type WitnessLine } from "./case.js";
3
- import { type CachedWitnessTime, type WitnessTimeAnswer, type WitnessTimeCheck } from "./witness-cache.js";
4
2
  interface Cli {
5
3
  out: (s: string) => void;
6
4
  err: (s: string) => void;
@@ -106,46 +104,6 @@ export declare function readFileEntry(entry: TreeEntry, key: string): Promise<Ui
106
104
  */
107
105
  export declare function treeFromArguments(paths: readonly string[]): Promise<Map<string, TreeEntry>>;
108
106
  export declare const CASE_USAGE: string;
109
- interface OnChainRow {
110
- ref: string;
111
- witnesses: number;
112
- confirmed: number;
113
- mismatched: number;
114
- unreachable: number;
115
- not_checked: number;
116
- /** Present only where the pack caches a witness time (5.6.6, v1.25, A30). */
117
- witness_times?: WitnessTimeRow[];
118
- }
119
- interface WitnessTimeRow {
120
- stamp_ref: string;
121
- cached: {
122
- block_height: number;
123
- block_time: number;
124
- };
125
- outcome: WitnessTimeCheck["outcome"];
126
- answers: {
127
- gateway: string;
128
- outcome: WitnessTimeAnswer["outcome"];
129
- block_height: number | null;
130
- block_time: number | null;
131
- }[];
132
- }
133
- interface OnChainHalf {
134
- /** Present only where a lookup ran: each gateway that answered, in the order given. */
135
- answered?: string[];
136
- checked: boolean;
137
- gateways: string[];
138
- packs: OnChainRow[];
139
- verdict: "not_checked" | "confirmed" | "failed" | "undetermined";
140
- }
141
- /**
142
- * `cached` is every witness time the packs cache (5.6.6, v1.25, A30). Where no
143
- * lookup ran, each is printed labelled not checked. Where one ran, each time
144
- * the public record matched is printed as the record's, each time no lookup
145
- * settled stays labelled not checked, and a time the record contradicts is the
146
- * reason the line reads `differs`.
147
- */
148
- export declare function witnessLine(onChain: OnChainHalf, cached?: readonly CachedWitnessTime[]): WitnessLine;
149
107
  /**
150
108
  * The case verdict's code, moved only by an on-chain half that was asked for:
151
109
  * a witness that does not match is a failure whatever the reading said, and an
package/dist/cli.js CHANGED
@@ -48,7 +48,7 @@ import { basename, join, resolve } from "node:path";
48
48
  import { fileURLToPath } from "node:url";
49
49
  import { isAgentProofSpec, verifyAgentProof } from "./agent-proof.js";
50
50
  import { CHAIN_READING_SCOPE, readChains, renderChainFinding } from "./chain.js";
51
- import { VERDICT_DISPLAY_TITLES, WITNESS_NOT_LOOKED_UP, caseReliance, readCase, renderCaseNote, renderCaseSummary, renderException, renderWitnessLine, } from "./case.js";
51
+ import { VERDICT_DISPLAY_TITLES, caseReliance, readCase, renderCaseNote, renderCaseSummary, renderException, renderWitnessLine, } from "./case.js";
52
52
  import { CASE_FILE_INDEX_NAME, jsonDepthExceeds, readCaseFileIndex, renderCaseFileFinding, } from "./case-file.js";
53
53
  import { ZipReadError, readZip } from "./zip-reader.js";
54
54
  import { composeExport } from "./compose.js";
@@ -56,7 +56,13 @@ import { isHexField, sha256Hex, utf8 } from "./crypto.js";
56
56
  import { verifyEvidenceBundle } from "./evidence.js";
57
57
  import { DuplicateKeyError, parseRefusingDuplicates } from "./verifier.js";
58
58
  import { fileMatchToJson, matchCaseFiles, matchFiles, renderFileMatch } from "./file-match.js";
59
- import { WITNESS_CACHE_NAME, checkWitnessCache, readWitnessCache, renderWitnessTime, } from "./witness-cache.js";
59
+ import { WITNESS_CACHE_NAME, readWitnessCache, } from "./witness-cache.js";
60
+ // The public-record (witness) lookup this command runs with `--gateways`:
61
+ // moved to its own pure module (proof#200) so a consumer that is not this CLI,
62
+ // the verify page, can call the SAME implementation. `onChainHalf` and
63
+ // `witnessLine` are this command's own two building blocks, unchanged; see
64
+ // `./witness-lookup.ts` for the exported entry point, `lookupPublicRecord`.
65
+ import { onChainHalf, witnessLine } from "./witness-lookup.js";
60
66
  import { checkDeclaration, renderDeclarationSummary, renderNote } from "./declaration.js";
61
67
  import { completenessNotes, gradePack, renderCause } from "./grading.js";
62
68
  import { EXIT_CODES_HELP, EXIT_FAILED, EXIT_MALFORMED, EXIT_UNDETERMINED, EXIT_VERIFIED, exitCodeFor, } from "./exit-codes.js";
@@ -1744,162 +1750,6 @@ function sortKeys(v) {
1744
1750
  function jsonText(v) {
1745
1751
  return JSON.stringify(sortKeys(v), null, 2);
1746
1752
  }
1747
- function witnessTimeRow(t) {
1748
- return {
1749
- stamp_ref: t.cached.stampRef,
1750
- cached: { block_height: t.cached.blockHeight, block_time: t.cached.blockTime },
1751
- outcome: t.outcome,
1752
- answers: t.answers.map((a) => ({
1753
- gateway: a.gateway,
1754
- outcome: a.outcome,
1755
- block_height: a.blockHeight,
1756
- block_time: a.blockTime,
1757
- })),
1758
- };
1759
- }
1760
- // Each pack's witnesses, re-fetched through `verifyEvidenceBundle`. Outside the
1761
- // case reading on purpose: `readCase` makes no network call, and neither does
1762
- // the verify page's case reading, so the case verdict stays the one both
1763
- // surfaces compute. This adds the half the reading names as not checked.
1764
- async function onChainHalf(packs, gateways, io) {
1765
- if (gateways.length === 0)
1766
- return { checked: false, gateways: [], packs: [], verdict: "not_checked" };
1767
- const rows = [];
1768
- const answered = new Set();
1769
- for (const [ref, bundle, cached] of packs) {
1770
- const result = await verifyEvidenceBundle(bundle, {
1771
- gateways,
1772
- ...(io.fetchImpl ? { fetchImpl: io.fetchImpl } : {}),
1773
- });
1774
- const witnesses = [...result.checkpoints, ...(result.stamps ?? [])];
1775
- const row = {
1776
- ref,
1777
- witnesses: witnesses.length,
1778
- confirmed: 0,
1779
- mismatched: 0,
1780
- unreachable: 0,
1781
- not_checked: 0,
1782
- };
1783
- for (const w of witnesses) {
1784
- const rollup = w.onChain?.rollup ?? null;
1785
- if (rollup === "confirm")
1786
- row.confirmed++;
1787
- else if (rollup === "mismatch")
1788
- row.mismatched++;
1789
- else if (rollup === "unreachable")
1790
- row.unreachable++;
1791
- else
1792
- row.not_checked++;
1793
- }
1794
- // The witness times the pack's witness-cache.json gives, looked up where
1795
- // it gives any (5.6.6, v1.25, A30). A pack with no cache gets no key and
1796
- // no request.
1797
- const times = await checkWitnessCache(cached, gateways, io.fetchImpl ?? fetch);
1798
- if (times.length > 0)
1799
- row.witness_times = times.map(witnessTimeRow);
1800
- rows.push(row);
1801
- for (const w of witnesses) {
1802
- for (const outcome of w.onChain?.perGateway ?? []) {
1803
- if (outcome.outcome === "confirm" || outcome.outcome === "mismatch") {
1804
- answered.add(outcome.gateway);
1805
- }
1806
- }
1807
- }
1808
- for (const t of times) {
1809
- for (const a of t.answers) {
1810
- if (a.outcome === "matches" || a.outcome === "differs")
1811
- answered.add(a.gateway);
1812
- }
1813
- }
1814
- }
1815
- const verdict = rows.some((r) => r.mismatched > 0 || (r.witness_times ?? []).some((t) => t.outcome === "differs"))
1816
- ? "failed"
1817
- : rows.some((r) => r.witnesses === 0 || r.unreachable > 0 || r.not_checked > 0)
1818
- ? "undetermined"
1819
- : "confirmed";
1820
- return {
1821
- answered: gateways.filter((g) => answered.has(g)),
1822
- checked: true,
1823
- gateways: [...gateways],
1824
- packs: rows,
1825
- verdict,
1826
- };
1827
- }
1828
- /**
1829
- * The witness line (evidence-bundle 5.5.6 v1.20, A24, D7) from the on-chain
1830
- * half: `confirmed` is `confirmed`, `not_checked` and `undetermined` are
1831
- * `not_checked` with the reason, and `failed` is `differs`. It never moves the
1832
- * headline.
1833
- */
1834
- // Distinct `[blockHeight, blockTime]` pairs, in time order.
1835
- function distinctTimes(times) {
1836
- const seen = new Map();
1837
- for (const [h, t] of times)
1838
- seen.set(`${h}:${t}`, [h, t]);
1839
- return [...seen.values()].sort((a, b) => a[1] - b[1] || a[0] - b[0]);
1840
- }
1841
- // Only the keys that carry something: a line with no cached or recorded time
1842
- // is the line 0.13.0 printed, field for field.
1843
- function withTimes(line, cached, recorded) {
1844
- return {
1845
- ...line,
1846
- ...(cached.length > 0 ? { cached } : {}),
1847
- ...(recorded.length > 0 ? { recorded } : {}),
1848
- };
1849
- }
1850
- /**
1851
- * `cached` is every witness time the packs cache (5.6.6, v1.25, A30). Where no
1852
- * lookup ran, each is printed labelled not checked. Where one ran, each time
1853
- * the public record matched is printed as the record's, each time no lookup
1854
- * settled stays labelled not checked, and a time the record contradicts is the
1855
- * reason the line reads `differs`.
1856
- */
1857
- export function witnessLine(onChain, cached = []) {
1858
- if (!onChain.checked) {
1859
- return withTimes({
1860
- state: WITNESS_NOT_LOOKED_UP.state,
1861
- reasons: [...WITNESS_NOT_LOOKED_UP.reasons],
1862
- services: [...WITNESS_NOT_LOOKED_UP.services],
1863
- }, distinctTimes(cached.map((c) => [c.blockHeight, c.blockTime])), []);
1864
- }
1865
- const services = [...(onChain.answered ?? [])];
1866
- const rows = onChain.packs;
1867
- const times = rows.flatMap((r) => r.witness_times ?? []);
1868
- const unsettled = distinctTimes(times
1869
- .filter((t) => t.outcome === "not_checked")
1870
- .map((t) => [t.cached.block_height, t.cached.block_time]));
1871
- const recorded = distinctTimes(times
1872
- .filter((t) => t.outcome === "matches")
1873
- .flatMap((t) => t.answers.filter((a) => a.outcome === "matches"))
1874
- .map((a) => [a.block_height, a.block_time]));
1875
- const contradicted = [];
1876
- for (const t of times) {
1877
- if (t.outcome !== "differs")
1878
- continue;
1879
- const a = t.answers.find((x) => x.outcome === "differs");
1880
- const reason = `a pack dates the witness ${renderWitnessTime(t.cached.block_height, t.cached.block_time)}` +
1881
- `, and the public record dates it ${renderWitnessTime(a.block_height, a.block_time)}`;
1882
- if (!contradicted.includes(reason))
1883
- contradicted.push(reason);
1884
- }
1885
- if (onChain.verdict === "failed") {
1886
- return withTimes({ state: "differs", reasons: contradicted, services }, unsettled, recorded);
1887
- }
1888
- if (onChain.verdict === "confirmed") {
1889
- return withTimes({ state: "confirmed", reasons: [], services }, unsettled, recorded);
1890
- }
1891
- const reasons = [];
1892
- if (rows.some((r) => r.witnesses === 0))
1893
- reasons.push("a pack names no witness");
1894
- if (rows.some((r) => r.unreachable > 0)) {
1895
- reasons.push("a lookup did not settle: the service could not be reached, or it " +
1896
- "holds no such witness yet");
1897
- }
1898
- if (rows.some((r) => r.not_checked > 0)) {
1899
- reasons.push("no lookup is made for a witness kind a pack names");
1900
- }
1901
- return withTimes({ state: "not_checked", reasons, services }, unsettled, recorded);
1902
- }
1903
1753
  /**
1904
1754
  * The case verdict's code, moved only by an on-chain half that was asked for:
1905
1755
  * a witness that does not match is a failure whatever the reading said, and an
@@ -2137,7 +1987,7 @@ async function caseCommand(argv, io) {
2137
1987
  for (const [p, bundle] of bundles) {
2138
1988
  caches.push(readWitnessCache(bundle, await witnessCacheBytes(tree, split, p)));
2139
1989
  }
2140
- const onChain = await onChainHalf(bundles.map(([p, bundle], i) => [p.ref, bundle, caches[i]]), gateways, io);
1990
+ const onChain = await onChainHalf(bundles.map(([p, bundle], i) => [p.ref, bundle, caches[i]]), gateways, { ...(io.fetchImpl ? { fetchImpl: io.fetchImpl } : {}) });
2141
1991
  const witness = witnessLine(onChain, caches.flat());
2142
1992
  const code = caseExitCode(reading.verdict, onChain.verdict);
2143
1993
  const nil = (v) => (v === undefined ? null : v);
package/dist/index.d.ts CHANGED
@@ -11,6 +11,8 @@ export type { DeclarationCheck, DeclarationClaims, DeclarationContentFinding, De
11
11
  export { CASE_BODY_TYPES, CASE_EXCEPTION_KINDS, CASE_QUARANTINED_PROFILES, CASE_RECORD_PROFILES, CASE_NOTE_KINDS, CASE_RELIANCE, CASE_RELIANCE_HANDOFF_CLAUSE, CASE_VERDICTS, CONTENT_WITHHELD_BY_DECLARATION, CO_SIGNATURES_PRESENT, VERDICT_DISPLAY_TITLES, WITNESS_NOT_LOOKED_UP, WITNESS_STATES, WITNESS_TIME_CACHED, WITNESS_TIME_RECORDED, caseReliance, readCase, renderCaseNote, renderCaseSummary, renderException, renderWitnessLine, } from "./case.js";
12
12
  export { MAX_BLOCK_TIME, MAX_WITNESS_CACHE_DEPTH, WITNESS_CACHE_NAME, WITNESS_CACHE_VERSION, checkWitnessCache, formatBlockTime, readWitnessCache, renderWitnessTime, witnessTimeQuery, } from "./witness-cache.js";
13
13
  export type { CachedWitnessTime, WitnessTimeAnswer, WitnessTimeCheck } from "./witness-cache.js";
14
+ export { lookupPublicRecord, onChainHalf, witnessLine } from "./witness-lookup.js";
15
+ export type { OnChainHalf, OnChainRow, PublicRecordPack, PublicRecordResult, PublicRecordState, WitnessLookupOptions, WitnessTimeRow, } from "./witness-lookup.js";
14
16
  export type { CaseException, CaseExceptionKind, CaseNote, CaseNoteKind, CasePack, CasePackReading, CaseReading, CaseRecordReading, CaseVerdict, WitnessLine, WitnessState, } from "./case.js";
15
17
  export { CASE_FILE_FINDING_KINDS, CASE_FILE_INDEX_NAME, CASE_FILE_VERSION, HOW_TO_VERIFY_NAME, REPORT_README_FIRST_LINE, howToVerifyText, isPackRef, readCaseFileIndex, renderCaseFileFinding, } from "./case-file.js";
16
18
  export type { CaseFileFinding, CaseFileFindingKind, CaseFileIndexReading, } from "./case-file.js";
package/dist/index.js CHANGED
@@ -89,6 +89,21 @@ export { CASE_BODY_TYPES, CASE_EXCEPTION_KINDS, CASE_QUARANTINED_PROFILES, CASE_
89
89
  // The witnessed-time cache (evidence-bundle 5.6.6, v1.25, A30): read it, look it
90
90
  // up, print it. Never an input to a verdict.
91
91
  export { MAX_BLOCK_TIME, MAX_WITNESS_CACHE_DEPTH, WITNESS_CACHE_NAME, WITNESS_CACHE_VERSION, checkWitnessCache, formatBlockTime, readWitnessCache, renderWitnessTime, witnessTimeQuery, } from "./witness-cache.js";
92
+ // The public-record (witness) lookup the CLI runs with `--gateways`
93
+ // (src/witness-lookup.ts, proof#200): moved out of the CLI so a consumer
94
+ // that is not the CLI, the verify page, calls the SAME implementation
95
+ // rather than keeping its own copy (verify-page#70). Pure kernel scope: the
96
+ // one network call lives in `lookupPublicRecord` and `onChainHalf`, runs
97
+ // only when a caller hands in a gateway, and goes through `options.fetchImpl`
98
+ // where a caller supplies one (a browser page's own `fetch`) or the ambient
99
+ // global `fetch` otherwise, so this export runs in a browser exactly as it
100
+ // runs in Node. `onChainHalf` and `witnessLine` are the CLI's own unchanged
101
+ // building blocks; `lookupPublicRecord` is the new one-call, four-state entry
102
+ // point a page wants, additive over both. `confirmed` from
103
+ // `lookupPublicRecord` is a claim about the BYTES only, never about whether
104
+ // the pack itself verifies, and a caller must never show it apart from the
105
+ // pack's own verdict.
106
+ export { lookupPublicRecord, onChainHalf, witnessLine } from "./witness-lookup.js";
92
107
  // The case file's index (src/case-file.ts, evidence-bundle.md §5.6, A25):
93
108
  // compared with the drop it describes, with NO verdict contribution. The index
94
109
  // is never an input to `readCase`. Pure: bytes and a file list in.
@@ -0,0 +1,112 @@
1
+ import type { WitnessLine, WitnessState } from "./case.js";
2
+ import { type CachedWitnessTime, type WitnessTimeAnswer, type WitnessTimeCheck } from "./witness-cache.js";
3
+ /** The seam every network call in this module goes through: used where given, else the ambient global `fetch`. */
4
+ export interface WitnessLookupOptions {
5
+ fetchImpl?: typeof fetch;
6
+ }
7
+ export interface OnChainRow {
8
+ ref: string;
9
+ witnesses: number;
10
+ confirmed: number;
11
+ mismatched: number;
12
+ unreachable: number;
13
+ not_checked: number;
14
+ /** Present only where the pack caches a witness time (5.6.6, v1.25, A30). */
15
+ witness_times?: WitnessTimeRow[];
16
+ }
17
+ export interface WitnessTimeRow {
18
+ stamp_ref: string;
19
+ cached: {
20
+ block_height: number;
21
+ block_time: number;
22
+ };
23
+ outcome: WitnessTimeCheck["outcome"];
24
+ answers: {
25
+ gateway: string;
26
+ outcome: WitnessTimeAnswer["outcome"];
27
+ block_height: number | null;
28
+ block_time: number | null;
29
+ }[];
30
+ }
31
+ export interface OnChainHalf {
32
+ /** Present only where a lookup ran: each gateway that answered, in the order given. */
33
+ answered?: string[];
34
+ checked: boolean;
35
+ gateways: string[];
36
+ packs: OnChainRow[];
37
+ verdict: "not_checked" | "confirmed" | "failed" | "undetermined";
38
+ }
39
+ /**
40
+ * Each pack's witnesses, re-fetched through `verifyEvidenceBundle`. Outside
41
+ * the case reading on purpose: `readCase` makes no network call, and neither
42
+ * does the verify page's case reading, so the case verdict stays the one both
43
+ * surfaces compute. This adds the half the reading names as not checked.
44
+ *
45
+ * Moved from `cli.ts` unchanged, apart from taking `fetchImpl` directly
46
+ * instead of a `Cli` stream (`io.fetchImpl ?? fetch`, the same fallback the
47
+ * CLI already applied at its own call site).
48
+ */
49
+ export declare function onChainHalf(packs: readonly (readonly [string, unknown, readonly CachedWitnessTime[]])[], gateways: readonly string[], options?: WitnessLookupOptions): Promise<OnChainHalf>;
50
+ /**
51
+ * `cached` is every witness time the packs cache (5.6.6, v1.25, A30). Where no
52
+ * lookup ran, each is printed labelled not checked. Where one ran, each time
53
+ * the public record matched is printed as the record's, each time no lookup
54
+ * settled stays labelled not checked, and a time the record contradicts is the
55
+ * reason the line reads `differs`.
56
+ *
57
+ * Moved from `cli.ts` unchanged: same inputs, same three states
58
+ * (`WitnessState`), same rendering. The CLI's `case` command still calls this
59
+ * exact function, so its witness line is unchanged.
60
+ */
61
+ export declare function witnessLine(onChain: OnChainHalf, cached?: readonly CachedWitnessTime[]): WitnessLine;
62
+ /** The four states this export's result can carry. The CLI's own `WitnessState` plus `no_public_record`. */
63
+ export type PublicRecordState = WitnessState | "no_public_record";
64
+ /**
65
+ * One pack, as this lookup needs it: the parsed `bundle.json` and, where the
66
+ * pack carries one, the RAW BYTES of its `witness-cache.json`, read as part
67
+ * of the pack, per proof#200's acceptance criteria, rather than as a
68
+ * separately-loaded side file. `null` and `undefined` both mean "this pack has
69
+ * no witness-cache.json", the same reading `readWitnessCache` gives a missing
70
+ * file.
71
+ */
72
+ export interface PublicRecordPack {
73
+ /** How this pack is named in the result; carries no meaning beyond that. */
74
+ ref: string;
75
+ bundle: unknown;
76
+ witnessCache?: Uint8Array | null;
77
+ }
78
+ export interface PublicRecordResult {
79
+ state: PublicRecordState;
80
+ reasons: string[];
81
+ services: string[];
82
+ /** Witness times a pack caches that no lookup settled (5.6.6, v1.25, A30). */
83
+ cached?: [number, number][];
84
+ /** Witness times the public record returned that matched a pack's cache. */
85
+ recorded?: [number, number][];
86
+ /** The line as both the CLI and a page print it, one string, kernel words. */
87
+ rendered: string;
88
+ }
89
+ /**
90
+ * The public-record lookup the CLI runs with `--gateways`, exported so a
91
+ * consumer that is not the CLI can run the SAME lookup rather than copy it
92
+ * (proof#200; the copy this closes is verify-page#70's `src/lib/verify.ts`).
93
+ *
94
+ * `packs` is every pack in the drop being read; `gateways` is empty for an
95
+ * offline reading, which never makes a network call and always answers
96
+ * `not_checked` or `no_public_record`. `options.fetchImpl` is the one seam a
97
+ * network call goes through, a browser page supplies its own `fetchImpl`;
98
+ * with none given this falls back to the ambient global `fetch`, so a
99
+ * browser page and a test can each supply their own and Node's `fetch` is
100
+ * never assumed where a caller passed something else.
101
+ *
102
+ * `state` is `confirmed` only about the BYTES: the public record holds what a
103
+ * pack names. It says nothing about whether the pack itself verifies (its
104
+ * wrapper signature, its `body_hash`), and a caller must never show
105
+ * `confirmed` apart from the pack's own verdict (proof#201).
106
+ *
107
+ * Never throws. Every input, including a malformed pack, a non-string
108
+ * gateway, or a `fetchImpl` that itself misbehaves, is read as untrusted, and
109
+ * anything this function or what it calls cannot make sense of settles as
110
+ * `not_checked` rather than escape as an exception.
111
+ */
112
+ export declare function lookupPublicRecord(packs: readonly PublicRecordPack[], gateways: readonly string[], options?: WitnessLookupOptions): Promise<PublicRecordResult>;