verifyhash 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.
Files changed (154) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +883 -0
  3. package/cli/abi/ContributionRegistry.json +881 -0
  4. package/cli/agent.js +2173 -0
  5. package/cli/anchor-artifact.js +853 -0
  6. package/cli/anchor.js +400 -0
  7. package/cli/claim.js +881 -0
  8. package/cli/core/agent-commit.js +448 -0
  9. package/cli/core/agent-session.js +598 -0
  10. package/cli/core/anchor-binding.js +663 -0
  11. package/cli/core/attestation.js +580 -0
  12. package/cli/core/evidence-plans.js +495 -0
  13. package/cli/core/fixtures/evidence-plans/baseline.json +19 -0
  14. package/cli/core/fulfill-intake.js +1082 -0
  15. package/cli/core/go-live-preflight.js +481 -0
  16. package/cli/core/license.js +534 -0
  17. package/cli/core/manifest.js +243 -0
  18. package/cli/core/packetseal.js +591 -0
  19. package/cli/core/registryArtifact.js +49 -0
  20. package/cli/core/revocation.js +539 -0
  21. package/cli/core/rfc3161.js +389 -0
  22. package/cli/core/timestamp.js +482 -0
  23. package/cli/core/trust-asof.js +479 -0
  24. package/cli/dataset.js +2950 -0
  25. package/cli/evidence.js +2227 -0
  26. package/cli/fulfill-webhook-http.js +438 -0
  27. package/cli/git.js +220 -0
  28. package/cli/hash.js +550 -0
  29. package/cli/identity.js +1072 -0
  30. package/cli/journal-cli.js +1110 -0
  31. package/cli/journal-log.js +454 -0
  32. package/cli/journal.js +334 -0
  33. package/cli/lineage.js +447 -0
  34. package/cli/list.js +287 -0
  35. package/cli/parcel.js +1509 -0
  36. package/cli/proof.js +578 -0
  37. package/cli/prove.js +300 -0
  38. package/cli/receipt.js +631 -0
  39. package/cli/registry.js +331 -0
  40. package/cli/reputation.js +344 -0
  41. package/cli/revocation.js +495 -0
  42. package/cli/serve-verify-http.js +298 -0
  43. package/cli/serve-verify.js +333 -0
  44. package/cli/show.js +339 -0
  45. package/cli/verify.js +383 -0
  46. package/cli/vh.js +3927 -0
  47. package/docs/ADOPT.md +183 -0
  48. package/docs/ADOPTION.json +11 -0
  49. package/docs/AGENTTRACE.md +247 -0
  50. package/docs/ANCHORING.md +167 -0
  51. package/docs/AUDIT.md +55 -0
  52. package/docs/CONFORMANCE.md +107 -0
  53. package/docs/DATALEDGER.md +638 -0
  54. package/docs/DECIDE.md +47 -0
  55. package/docs/DECISIONS-PENDING.md +27 -0
  56. package/docs/DEPLOY-PUBLIC-SITE.md +301 -0
  57. package/docs/ENGINE-LEDGER.json +12 -0
  58. package/docs/EVIDENCE.md +519 -0
  59. package/docs/GO-LIVE.md +66 -0
  60. package/docs/IDENTITY.md +123 -0
  61. package/docs/INDEPENDENT-VERIFICATION.md +377 -0
  62. package/docs/INTEGRITY-JOURNAL.md +337 -0
  63. package/docs/KEY-LIFECYCLE.md +179 -0
  64. package/docs/LICENSING.md +46 -0
  65. package/docs/LINEAGE.md +307 -0
  66. package/docs/LOOP-AUDIT-2026-07-03.json +580 -0
  67. package/docs/LOOP-HARDENING-PLAN.md +44 -0
  68. package/docs/MERKLE-LEAVES.md +113 -0
  69. package/docs/METRICS.jsonl +31 -0
  70. package/docs/MORNING.md +204 -0
  71. package/docs/PILOT.md +444 -0
  72. package/docs/PROOFPARCEL.md +227 -0
  73. package/docs/PROOFS.md +262 -0
  74. package/docs/RECEIPTS.md +341 -0
  75. package/docs/REPUTATION.md +158 -0
  76. package/docs/SDK.md +301 -0
  77. package/docs/STRATEGY-ARCHIVE.md +5055 -0
  78. package/docs/SUPERVISOR-RUNBOOK.md +52 -0
  79. package/docs/TRUST-BOUNDARIES.md +335 -0
  80. package/docs/TRUSTLEDGER.md +1976 -0
  81. package/docs/USAGE-BUDGET.json +121 -0
  82. package/docs/VERIFY-SERVICE.md +168 -0
  83. package/index.js +160 -0
  84. package/package.json +41 -0
  85. package/trustledger/build-standalone.js +796 -0
  86. package/trustledger/cli.js +3179 -0
  87. package/trustledger/close.js +391 -0
  88. package/trustledger/corpus.js +159 -0
  89. package/trustledger/dist/BUILD-PROVENANCE.json +99 -0
  90. package/trustledger/dist/trustledger-standalone.html +6197 -0
  91. package/trustledger/dist/trustledger-standalone.html.sha256 +1 -0
  92. package/trustledger/door-core.js +442 -0
  93. package/trustledger/fixtures/bank.csv +7 -0
  94. package/trustledger/fixtures/bank.malformed.csv +3 -0
  95. package/trustledger/fixtures/bank.noalias.csv +5 -0
  96. package/trustledger/fixtures/bank.ofx +34 -0
  97. package/trustledger/fixtures/bank.real.csv +5 -0
  98. package/trustledger/fixtures/corpus/_shared/prior-close.json +22 -0
  99. package/trustledger/fixtures/corpus/bank-book-mismatch--benign-twin/inputs.json +14 -0
  100. package/trustledger/fixtures/corpus/bank-book-mismatch--benign-twin/meta.json +7 -0
  101. package/trustledger/fixtures/corpus/bank-book-mismatch--out-of-trust/inputs.json +14 -0
  102. package/trustledger/fixtures/corpus/bank-book-mismatch--out-of-trust/meta.json +7 -0
  103. package/trustledger/fixtures/corpus/continuity-break--benign-twin/inputs.json +15 -0
  104. package/trustledger/fixtures/corpus/continuity-break--benign-twin/meta.json +7 -0
  105. package/trustledger/fixtures/corpus/continuity-break--out-of-trust/inputs.json +15 -0
  106. package/trustledger/fixtures/corpus/continuity-break--out-of-trust/meta.json +7 -0
  107. package/trustledger/fixtures/corpus/negative-tenant-ledger--benign-twin/inputs.json +13 -0
  108. package/trustledger/fixtures/corpus/negative-tenant-ledger--benign-twin/meta.json +7 -0
  109. package/trustledger/fixtures/corpus/negative-tenant-ledger--out-of-trust/inputs.json +13 -0
  110. package/trustledger/fixtures/corpus/negative-tenant-ledger--out-of-trust/meta.json +7 -0
  111. package/trustledger/fixtures/corpus/owner-overdraw--benign-twin/inputs.json +15 -0
  112. package/trustledger/fixtures/corpus/owner-overdraw--benign-twin/meta.json +7 -0
  113. package/trustledger/fixtures/corpus/owner-overdraw--out-of-trust/inputs.json +15 -0
  114. package/trustledger/fixtures/corpus/owner-overdraw--out-of-trust/meta.json +7 -0
  115. package/trustledger/fixtures/corpus/security-deposit-segregation--benign-twin/inputs.json +16 -0
  116. package/trustledger/fixtures/corpus/security-deposit-segregation--benign-twin/meta.json +7 -0
  117. package/trustledger/fixtures/corpus/security-deposit-segregation--out-of-trust/inputs.json +13 -0
  118. package/trustledger/fixtures/corpus/security-deposit-segregation--out-of-trust/meta.json +7 -0
  119. package/trustledger/fixtures/corpus/subledger-out-of-balance--benign-twin/inputs.json +13 -0
  120. package/trustledger/fixtures/corpus/subledger-out-of-balance--benign-twin/meta.json +7 -0
  121. package/trustledger/fixtures/corpus/subledger-out-of-balance--out-of-trust/inputs.json +13 -0
  122. package/trustledger/fixtures/corpus/subledger-out-of-balance--out-of-trust/meta.json +7 -0
  123. package/trustledger/fixtures/e2e/bank.aliased.csv +4 -0
  124. package/trustledger/fixtures/e2e/bank.csv +4 -0
  125. package/trustledger/fixtures/e2e/bank.nsf.csv +4 -0
  126. package/trustledger/fixtures/e2e/quickbooks.csv +6 -0
  127. package/trustledger/fixtures/e2e/quickbooks.nsf.csv +8 -0
  128. package/trustledger/fixtures/e2e/rentroll.csv +6 -0
  129. package/trustledger/fixtures/e2e/rentroll.nsf.csv +8 -0
  130. package/trustledger/fixtures/e2e/rentroll.short.csv +5 -0
  131. package/trustledger/fixtures/plans/baseline.json +25 -0
  132. package/trustledger/fixtures/plans/price-binding.example.json +27 -0
  133. package/trustledger/fixtures/policy/ambiguous-deposit-example.json +12 -0
  134. package/trustledger/fixtures/policy/baseline.json +19 -0
  135. package/trustledger/fixtures/policy/ca-example.json +12 -0
  136. package/trustledger/fixtures/policy/negative-tenant-ledger-example.json +12 -0
  137. package/trustledger/fixtures/policy/owner-overdraw-example.json +12 -0
  138. package/trustledger/fixtures/quickbooks.csv +7 -0
  139. package/trustledger/fixtures/quickbooks.real.csv +5 -0
  140. package/trustledger/fixtures/rentroll.csv +6 -0
  141. package/trustledger/fixtures/rentroll.real.csv +4 -0
  142. package/trustledger/ingest.js +1163 -0
  143. package/trustledger/lib/policy-bundled-loader.js +44 -0
  144. package/trustledger/lib/sha256-vendored.js +227 -0
  145. package/trustledger/license.js +563 -0
  146. package/trustledger/match.js +551 -0
  147. package/trustledger/plans.js +551 -0
  148. package/trustledger/policy.js +398 -0
  149. package/trustledger/public/index.html +512 -0
  150. package/trustledger/reconcile.js +1486 -0
  151. package/trustledger/report.js +887 -0
  152. package/trustledger/seal.js +854 -0
  153. package/trustledger/server.js +391 -0
  154. package/trustledger/valueproof.js +350 -0
package/cli/list.js ADDED
@@ -0,0 +1,287 @@
1
+ "use strict";
2
+
3
+ // `vh list [--contract a] [--rpc u] [filters]` — enumerate the registry, read-only.
4
+ //
5
+ // This is the discovery + audit counterpart to `vh verify`: where verify answers "is THIS content
6
+ // anchored?", list answers "WHAT is in the registry?". It reads `total()` and pages through the
7
+ // contract's `getRecords(start, count)` view, printing one block per record (or a JSON array under
8
+ // `--json`).
9
+ //
10
+ // Read-only by construction: it takes a PROVIDER only, never a signer and never a key. Enumerating a
11
+ // public, immutable registry must never require the ability to write to it.
12
+ //
13
+ // Trust posture (mirrors docs/TRUST-BOUNDARIES.md): the per-record `uri` is an UNTRUSTED hint the
14
+ // contract never validated, and `contributor` only means "proven first claimant" when `authorBound`
15
+ // is true — otherwise it is merely "first anchorer". Human output always leads with that caveat so a
16
+ // browser of the list does not over-trust either field.
17
+
18
+ const ARTIFACT = require("./core/registryArtifact");
19
+ const ABI = ARTIFACT.abi;
20
+
21
+ // Reuse the lineage-root predicate from `show` so `list` and `show` never disagree about what a
22
+ // "root" (parent == bytes32(0)) is. T-10.1.
23
+ const { isRoot } = require("./show");
24
+ const {
25
+ assertRegistry,
26
+ formatRegistryLine,
27
+ formatSkippedLine,
28
+ jsonRegistryBlock,
29
+ jsonSkippedBlock,
30
+ } = require("./registry");
31
+
32
+ // Default page size for walking getRecords(). The contract clamps a window to what exists, so this is
33
+ // purely a request-batching knob; it never affects which records come back.
34
+ const DEFAULT_PAGE = 100;
35
+
36
+ // The one-line trust caveat that leads every human-readable run. Kept consistent with the
37
+ // `uri`/`contributor` rows in docs/TRUST-BOUNDARIES.md so a reader of the list does not over-trust
38
+ // the off-chain pointer or the recorded address.
39
+ const TRUST_CAVEAT =
40
+ "NOTE: `uri` is an UNTRUSTED hint (never fetched/validated — re-fetch + re-hash yourself); " +
41
+ "`contributor` only means proven authorship when authorBound is true (commit-reveal), " +
42
+ "otherwise it is merely the first anchorer.";
43
+
44
+ // The two attribution strings, reused verbatim from cli/verify.js so list and verify never drift.
45
+ const ATTRIBUTION_PROVEN = "proven first claimant (commit-reveal)";
46
+ const ATTRIBUTION_ANCHOR_ONLY = "first anchorer only — NOT authorship";
47
+
48
+ /**
49
+ * Read every record from the registry in insertion order by paging through getRecords().
50
+ * Read-only: uses only the provider. Returns a plain array of normalized record objects with their
51
+ * insertion `index`, so callers can filter/slice client-side without re-reading the chain.
52
+ *
53
+ * @param {object} contract an ethers v6 Contract bound to a provider
54
+ * @param {number} [pageSize]
55
+ * @returns {Promise<Array<{
56
+ * index:number, contentHash:string, contributor:string, authorBound:boolean,
57
+ * timestamp:bigint, blockNumber:bigint, uri:string, parent:string
58
+ * }>>}
59
+ */
60
+ async function readAllRecords(contract, pageSize = DEFAULT_PAGE) {
61
+ const total = Number(await contract.total());
62
+ const out = [];
63
+ for (let start = 0; start < total; start += pageSize) {
64
+ const [contentHashes, records] = await contract.getRecords(start, pageSize);
65
+ // Defensive: the contract clamps, so a window past the end returns empty and we stop.
66
+ if (contentHashes.length === 0) break;
67
+ for (let i = 0; i < contentHashes.length; i++) {
68
+ const r = records[i];
69
+ out.push({
70
+ index: start + i,
71
+ contentHash: contentHashes[i],
72
+ contributor: r.contributor,
73
+ authorBound: Boolean(r.authorBound),
74
+ timestamp: BigInt(r.timestamp),
75
+ blockNumber: BigInt(r.blockNumber),
76
+ uri: r.uri,
77
+ // The immutable lineage edge (T-10.1). Normalized to a lowercase 0x string; a root reads back
78
+ // as the 32-byte zero hash (isRoot() flags it for the JSON/human shapes below).
79
+ parent: String(r.parent).toLowerCase(),
80
+ });
81
+ }
82
+ }
83
+ return out;
84
+ }
85
+
86
+ /**
87
+ * Apply the client-side filters to an in-order record array. All filters are optional and combine
88
+ * (logical AND); offset/limit page the *filtered* result so `vh list --contributor X --limit 5`
89
+ * means "the first 5 of X's records", which is what a reader expects.
90
+ *
91
+ * @param {Array} records normalized records from readAllRecords (insertion order preserved)
92
+ * @param {object} filters
93
+ * @param {string} [filters.contributor] lowercase-compared address; keep only this contributor
94
+ * @param {boolean} [filters.authorBound] if true, keep only authorBound (commit-reveal) records
95
+ * @param {number} [filters.offset] skip this many of the filtered records (default 0)
96
+ * @param {number} [filters.limit] keep at most this many after the offset (default: all)
97
+ * @returns {Array} the filtered + windowed records (insertion order preserved)
98
+ */
99
+ function applyFilters(records, filters = {}) {
100
+ let rows = records;
101
+ if (filters.contributor) {
102
+ const want = String(filters.contributor).toLowerCase();
103
+ rows = rows.filter((r) => String(r.contributor).toLowerCase() === want);
104
+ }
105
+ if (filters.authorBound) {
106
+ rows = rows.filter((r) => r.authorBound === true);
107
+ }
108
+ const offset = filters.offset || 0;
109
+ const start = offset;
110
+ const end = filters.limit == null ? rows.length : start + filters.limit;
111
+ return rows.slice(start, end);
112
+ }
113
+
114
+ /** The human attribution phrase for a record, reusing verify.js wording exactly. */
115
+ function attributionFor(authorBound) {
116
+ return authorBound ? ATTRIBUTION_PROVEN : ATTRIBUTION_ANCHOR_ONLY;
117
+ }
118
+
119
+ /** Format a unix-seconds bigint as an ISO-8601 UTC string for human display. */
120
+ function isoFromUnix(unixSeconds) {
121
+ try {
122
+ return new Date(Number(unixSeconds) * 1000).toISOString();
123
+ } catch (_) {
124
+ return "(unparseable)";
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Render one record as the human-readable block printed per row. Includes the acceptance fields:
130
+ * index, contentHash, contributor, attribution strength, timestamp (+ISO), blockNumber, uri.
131
+ */
132
+ function formatRecord(r) {
133
+ // `parent` (T-10.1): a root (0x0) shows "(none) — lineage root"; a parented record shows the
134
+ // predecessor hash. The edge is only a CLAIM (see the trust caveat), so it is reported, not trusted.
135
+ const parentLine = isRoot(r.parent)
136
+ ? " parent: (none) — lineage root"
137
+ : ` parent: ${r.parent}`;
138
+ return [
139
+ `[${r.index}] ${r.contentHash}`,
140
+ ` contributor: ${r.contributor}`,
141
+ ` attribution: ${attributionFor(r.authorBound)}`,
142
+ ` timestamp: ${r.timestamp} (${isoFromUnix(r.timestamp)})`,
143
+ ` blockNumber: ${r.blockNumber}`,
144
+ ` uri: ${r.uri ? r.uri : "(none)"}`,
145
+ parentLine,
146
+ ].join("\n");
147
+ }
148
+
149
+ /**
150
+ * Shape a record for `--json`: BigInts become Numbers (unix seconds / block heights fit safely) and
151
+ * the attribution phrase is included so a machine consumer gets the same semantics as the human block.
152
+ */
153
+ function jsonRecord(r) {
154
+ const root = isRoot(r.parent);
155
+ return {
156
+ index: r.index,
157
+ contentHash: r.contentHash,
158
+ contributor: r.contributor,
159
+ authorBound: r.authorBound,
160
+ attribution: attributionFor(r.authorBound),
161
+ timestamp: Number(r.timestamp),
162
+ timestampISO: isoFromUnix(r.timestamp),
163
+ blockNumber: Number(r.blockNumber),
164
+ uri: r.uri ? r.uri : null,
165
+ // Lineage edge (T-10.1): a root serializes parent:null + isRoot:true (distinguishable from a
166
+ // missing key); a parented record carries the predecessor hash + isRoot:false. So an indexer can
167
+ // reconstruct the full edge set from `vh list --json` alone, mirroring the on-chain Linked logs.
168
+ parent: root ? null : r.parent,
169
+ isRoot: root,
170
+ };
171
+ }
172
+
173
+ /**
174
+ * Enumerate the registry, read-only. Reads `total`, pages through `getRecords`, applies the
175
+ * client-side filters, then emits either a human block (led by the trust caveat) per record or a
176
+ * machine-readable JSON array (`--json`). Requires a provider; NEVER a signer or key.
177
+ *
178
+ * @param {object} opts
179
+ * @param {string} opts.contractAddress deployed ContributionRegistry address to read from
180
+ * @param {object} opts.provider ethers v6 Provider (read-only RPC connection)
181
+ * @param {object} [opts.filters] { contributor, authorBound, offset, limit } (see applyFilters)
182
+ * @param {boolean} [opts.json] emit a JSON array instead of the human block
183
+ * @param {object} [opts.ethers] ethers v6 module (defaults to the bundled one)
184
+ * @param {(s:string)=>void} [opts.log] sink for output (defaults to process.stdout)
185
+ * @returns {Promise<{records: Array, total: number, shown: number, json: boolean}>}
186
+ * `records` is the normalized + filtered list actually emitted (the JSON-shaped objects).
187
+ */
188
+ async function runList(opts) {
189
+ const ethersLib = opts.ethers || require("ethers");
190
+ const log = opts.log || ((s) => process.stdout.write(s));
191
+
192
+ const { contractAddress, provider } = opts;
193
+ if (!contractAddress) {
194
+ throw new Error(
195
+ "no contract address: pass --contract <address> or set VH_CONTRACT in the environment"
196
+ );
197
+ }
198
+ if (!ethersLib.isAddress(contractAddress)) {
199
+ throw new Error(`invalid contract address: ${contractAddress}`);
200
+ }
201
+ if (!provider) {
202
+ throw new Error("no provider: pass --rpc <url> or set VH_RPC_URL / AMOY_RPC_URL");
203
+ }
204
+
205
+ const filters = opts.filters || {};
206
+ // Validate a --contributor address up front so a typo'd address hard-errors rather than silently
207
+ // matching nothing (which would look like an empty registry).
208
+ if (filters.contributor && !ethersLib.isAddress(filters.contributor)) {
209
+ throw new Error(`invalid --contributor address: ${filters.contributor}`);
210
+ }
211
+
212
+ // T-11.2: authenticate the registry BEFORE enumerating it — no records are reported until we have
213
+ // confirmed there is a real verifyhash ContributionRegistry at this address (unless the caller
214
+ // explicitly, loudly opts out with skipIdentityCheck for a known not-yet-deployed/local-dev target).
215
+ let registryAuth = null;
216
+ if (!opts.skipIdentityCheck) {
217
+ registryAuth = await assertRegistry({ provider, contractAddress, ethers: ethersLib });
218
+ }
219
+
220
+ const contract = new ethersLib.Contract(
221
+ ethersLib.getAddress(contractAddress),
222
+ ABI,
223
+ provider
224
+ );
225
+
226
+ const all = await readAllRecords(contract);
227
+ const filtered = applyFilters(all, {
228
+ contributor: filters.contributor,
229
+ authorBound: filters.authorBound,
230
+ offset: filters.offset,
231
+ limit: filters.limit,
232
+ });
233
+
234
+ const jsonRows = filtered.map(jsonRecord);
235
+
236
+ // T-11.2: the machine-readable registry block — the same identity a UI/indexer can depend on to know
237
+ // the list was read from an authenticated registry (or that the check was skipped).
238
+ const registryBlock = opts.skipIdentityCheck
239
+ ? jsonSkippedBlock()
240
+ : registryAuth
241
+ ? jsonRegistryBlock(registryAuth)
242
+ : null;
243
+
244
+ if (opts.json) {
245
+ // Machine-readable ENVELOPE (T-11.2): `{ registry, records }`. The `registry` block proves the
246
+ // records came from an authenticated registry; `records` is the array a consumer iterates. (Before
247
+ // T-11.2 this was a bare top-level array; the envelope is the documented contract now.)
248
+ log(JSON.stringify({ registry: registryBlock, records: jsonRows }, null, 2) + "\n");
249
+ } else {
250
+ // Human-readable: lead with the trust caveat, then the registry-authentication confirmation (or the
251
+ // loud skip warning), then one block per record.
252
+ log(TRUST_CAVEAT + "\n\n");
253
+ if (opts.skipIdentityCheck) {
254
+ log(formatSkippedLine() + "\n\n");
255
+ } else if (registryAuth) {
256
+ log(formatRegistryLine(registryAuth) + "\n\n");
257
+ }
258
+ if (filtered.length === 0) {
259
+ log("no records\n");
260
+ } else {
261
+ log(filtered.map(formatRecord).join("\n\n") + "\n");
262
+ }
263
+ }
264
+
265
+ return {
266
+ records: jsonRows,
267
+ registry: registryBlock,
268
+ total: all.length,
269
+ shown: filtered.length,
270
+ json: Boolean(opts.json),
271
+ };
272
+ }
273
+
274
+ module.exports = {
275
+ runList,
276
+ readAllRecords,
277
+ applyFilters,
278
+ formatRecord,
279
+ jsonRecord,
280
+ attributionFor,
281
+ isoFromUnix,
282
+ TRUST_CAVEAT,
283
+ ATTRIBUTION_PROVEN,
284
+ ATTRIBUTION_ANCHOR_ONLY,
285
+ DEFAULT_PAGE,
286
+ ABI,
287
+ };