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
@@ -0,0 +1,52 @@
1
+ # Supervisor Runbook — operating the autonomous build loop
2
+
3
+ The committed, versioned protocol for the layer ABOVE the engine (the supervising Claude session / a
4
+ human operator). Memory files may point here; this file is the source of truth (audit 2026-07-03:
5
+ "memory is the wrong home for protocol state").
6
+
7
+ ## Launch protocol (every run)
8
+
9
+ 1. **Gate the engine (mechanical, mandatory):** `node scripts/pre-run-gate.cjs`
10
+ - Runs BOTH self-upgrade gates against the live `build-loop.workflow.js`, records the md5 in
11
+ `docs/ENGINE-LEDGER.json` (append-only). **Never launch on GATE-FAIL** — revert with
12
+ `cp build-loop.prev.js build-loop.workflow.js` and re-gate.
13
+ 2. **Check the spend governor** (`docs/USAGE-BUDGET.json`):
14
+ - cooldown: `now - lastRunEndEpoch >= cooldownSeconds` (7200)
15
+ - cap: `spentTokens < ceilingTokens` (pause-and-wait at cap)
16
+ - window: if `now >= windowResetIso`, reset `spentTokens=0` and advance the window fields.
17
+ - NOTE (audit): the repo cap has never been the binding constraint — the SUBSCRIPTION weekly
18
+ allowance (esp. per-model Fable) is. Check that too before relaunching after a pause.
19
+ 3. **Launch:** `Workflow({scriptPath: "<repo>/build-loop.workflow.js"})` — never two concurrent runs.
20
+ 4. **Arm a stall net** (ScheduleWakeup ~40-60 min): confirm liveness via newest mtime under the run's
21
+ transcript dir + `git log` for new commits; re-arm each wakeup.
22
+
23
+ ## Completion protocol (every run end)
24
+
25
+ 1. Reconcile authoritative `subagent_tokens` from the task-notification into `docs/USAGE-BUDGET.json`:
26
+ `spentTokens += tokens`, append `runs[]` row `{runId, tokens, endEpoch, endIso, note}`, set
27
+ `lastRunEndEpoch/Iso`. (Until the engine writes its own row — planned fix — this is manual; be exact.)
28
+ 2. Re-gate any engine swap: `node scripts/pre-run-gate.cjs` (this also updates the ledger). If FAIL →
29
+ revert to `build-loop.prev.js` and note it in the run record.
30
+ 3. Commit the run record (scoped: the ledger + budget + any recovered planning files). Local only.
31
+ 4. **Site auto-deploy (user-approved 2026-07-03):** if `node scripts/site-release.js --diff` shows
32
+ drift: `site-release.js` → `--check` → `sudo -n /usr/local/bin/verifyhash-deploy` → `--mark-deployed`
33
+ → commit `site/DEPLOYED.json`. Notify the user one line (what went live + backup path).
34
+ 5. Enforce cooldown/cap, then relaunch from step 1 of the Launch protocol — unless a pause is on
35
+ (check memory + the latest run-record note).
36
+
37
+ ## Standing guardrails (unchanged by any of the above)
38
+
39
+ - Local commits only; **never `git push`** (also enforced by `.git/hooks/pre-push`) and never any
40
+ network git operation.
41
+ - The loop ENGINE never deploys, never holds keys or funds, never issues a token/coin/security.
42
+ - The SUPERVISOR may: publish the site via the narrow-sudo path above, and execute a **specific,
43
+ user-directed** on-chain deploy with user-provided, capped funds. Nothing standing, nothing autonomous.
44
+ - Anything legal / funds / pricing / outward-facing stays `needs-human` (see `docs/DECISIONS-PENDING.md`).
45
+
46
+ ## Known live state (2026-07-03)
47
+
48
+ - ContributionRegistry LIVE on Polygon mainnet: `0x77d8eF881D5aeEda64788968D13f9146fE1A609B`
49
+ (deployer `0xAA1C…B455`, key at `/home/loopdev/.verifyhash-deploy-key.txt` — user-owned; ~3.5 POL remains).
50
+ - verifyhash.com live-deployed via `/usr/local/bin/verifyhash-deploy` (root-owned; sudoers grant for that
51
+ one command only). Webroot backups: `/var/www/verifyhash.com/html.bak.<ts>`.
52
+ - Loop PAUSED after run wf_72ed879b-35c per user (subscription weekly usage); resume only on user go.
@@ -0,0 +1,335 @@
1
+ # Trust boundaries — what a verifyhash record does and does NOT prove
2
+
3
+ This is the canonical, plain-language statement of what you may rely on when you read a
4
+ `ContributionRegistry` record, and what you must verify yourself. It mirrors the `@notice
5
+ TRUST BOUNDARIES` block in `contracts/ContributionRegistry.sol`; if the two ever drift, the
6
+ NatSpec in the contract is authoritative. Resolves audit findings **F17** and **C3**.
7
+
8
+ A record returned by `getRecord(contentHash)` has these fields:
9
+
10
+ ```solidity
11
+ struct Record {
12
+ address contributor; // who is recorded — meaning depends on authorBound (see below)
13
+ bool authorBound; // true => front-running-resistant claim (commit-reveal); false => first anchorer
14
+ uint64 timestamp; // block.timestamp at anchor time
15
+ uint64 blockNumber; // block.number at anchor time
16
+ string uri; // off-chain pointer hint
17
+ bytes32 parent; // optional predecessor edge; bytes32(0) == lineage root (see below)
18
+ }
19
+ ```
20
+
21
+ The one and only thing the chain guarantees about a record is this:
22
+
23
+ > The exact 32-byte `contentHash` you queried was anchored on-chain by `contributor`,
24
+ > in block `blockNumber`, at (approximately) `timestamp`, and has not changed since.
25
+
26
+ Everything else below is about how *little* the other fields are allowed to mean.
27
+
28
+ ---
29
+
30
+ ## `uri` is an UNTRUSTED hint — always re-derive and re-hash
31
+
32
+ `uri` is a free-form string (an IPFS CID, a commit URL, a Swarm hash, anything). It is supplied by
33
+ whoever anchored the hash and is stored verbatim.
34
+
35
+ **The contract never fetches it, never validates it, never hashes it, and never compares it to
36
+ anything.** It is metadata for humans, not a security guarantee. A `uri` can:
37
+
38
+ - point at content whose hash is *not* the anchored `contentHash` (mismatched or swapped later),
39
+ - point at content that no longer exists, or never existed,
40
+ - point at completely unrelated content,
41
+ - be empty.
42
+
43
+ None of those make the record "invalid" — the record only ever attested to the `contentHash`, not
44
+ to the `uri`.
45
+
46
+ ### How a consumer trusts a record
47
+
48
+ To rely on a record you must do the integrity check yourself:
49
+
50
+ 1. Obtain the content you care about (e.g. fetch what the `uri` claims to point at, or take a local
51
+ file/directory).
52
+ 2. **Re-derive its hash** with the *same scheme* the registry uses — `vh hash <path>` (see
53
+ `docs/MERKLE-LEAVES.md` for the exact directory-root construction). Do not trust a hash someone
54
+ else computed.
55
+ 3. **Compare** your recomputed hash to the anchored `contentHash`. They must be byte-for-byte equal.
56
+
57
+ If and only if they match, you know the content is exactly what was anchored. The `vh verify`
58
+ command automates exactly this re-derive-and-compare flow and is read-only (no key, no funds).
59
+ If they do not match, the content was either never anchored or has been tampered with — regardless
60
+ of what the `uri` says.
61
+
62
+ > Rule of thumb: **the `contentHash` is the proof; the `uri` is just a convenience pointer.**
63
+
64
+ ### Reading a record (`vh list` / `vh show`) does NOT validate its content
65
+
66
+ `vh list` enumerates the registry and `vh show <0xhash>` looks up one record by hash. Both are
67
+ **read-only and need no key** — they take a provider only, never a signer — and both exist for
68
+ *discovery and audit*: answering "what is in the registry?" and "what does the record for this hash
69
+ say?". Neither command touches your files, so **a hit does not bind the record to any real bytes you
70
+ hold.** Seeing a record in `vh list`, or a populated record from `vh show`, tells you only that some
71
+ hash was anchored — it is *not* the integrity check.
72
+
73
+ The integrity check is unchanged: it is the same re-derive-and-compare flow above. To trust that some
74
+ content is what a record attests, you must still independently obtain the content, **re-derive its
75
+ hash** (`vh hash`), and confirm it equals the anchored `contentHash` — which is exactly what
76
+ `vh verify <path>` automates. Until you have done that, treat a listed/shown record's `uri` as an
77
+ untrusted hint and its `contributor` per the `authorBound` rule below. The read commands lead their
78
+ human-readable output with this caveat verbatim, so a browser of the registry is never lulled into
79
+ treating a `list`/`show` hit as proof that any file is authentic.
80
+
81
+ > Rule of thumb: **`list`/`show` tell you a hash is on-chain; only `vh verify` binds that hash to
82
+ > bytes.**
83
+
84
+ ### A `--receipt` manifest is an UNTRUSTED hint too — it localizes, it does not verify
85
+
86
+ `vh anchor <dir> --receipt <p>` records a `manifest`: the sorted list of `{ path, contentHash, leaf }`
87
+ for every file in the directory (exactly what `vh hash <dir>` computes). `vh verify <dir> --receipt <p>`
88
+ then loads that manifest and prints a precise per-file diff — files **ADDED / REMOVED / CHANGED**
89
+ (old→new `contentHash`) — so a `MISMATCH` tells you *which* file diverged, not just *that* the tree
90
+ diverged.
91
+
92
+ The manifest is a **local convenience, not a trust anchor.** The authoritative verdict is still the
93
+ same re-derive-and-compare check above: `vh verify` recomputes the directory's Merkle **root** from
94
+ the files on disk and compares that root to the on-chain record. **MATCH/MISMATCH comes only from
95
+ that comparison.** The manifest never participates in the verdict; a malicious or stale receipt can at
96
+ worst mislabel which file moved, and even that is caught — `vh verify` flags a receipt whose recorded
97
+ root does not match the recomputed root (`receiptHashMismatch`) and reports it as a different snapshot
98
+ rather than silently pretending the files line up. The verify output prints this caveat inline, and
99
+ the receipt schema's NatSpec (`cli/receipt.js`) states it as well.
100
+
101
+ > Rule of thumb: **the on-chain root decides MATCH/MISMATCH; the receipt manifest only points at the file.**
102
+
103
+ ---
104
+
105
+ ## Authenticating the registry you read from — "don't believe a record until you know who answered"
106
+
107
+ Everything above is about trusting a *record*. This section is about trusting the *source* of the
108
+ record. The project's core promise is to prove things **without trusting any server**, but the
109
+ `(rpc, address)` pair a reader uses is itself **untrusted** — it comes from a prover, a receipt's
110
+ `contractAddress`, a proof artifact's `chainId`, a README, or a forwarded event. None of those are
111
+ the chain; any of them can be wrong or hostile.
112
+
113
+ ### The threat — a wrong/rogue RPC+address can fabricate verdicts
114
+
115
+ A read command does `getRecord(contentHash)` (or `isAnchored` / `verifyLeaf`) against whatever
116
+ `(rpc, address)` it was handed and reports the answer. Two ways that silently produces a
117
+ confident-looking-but-wrong verdict:
118
+
119
+ - **Wrong address / wrong network.** Point at an address with *no contract* (a typo, or the right
120
+ address on the wrong chain) and `getRecord` returns empty — read naively as "not anchored", so a
121
+ genuinely-anchored contribution is mislabeled `MISMATCH`/absent.
122
+ - **Rogue look-alike contract.** Point at a *deployed but different* contract that implements the
123
+ same ABI shape and it can return `isAnchored = true` / fabricated records, making the CLI print
124
+ `MATCH` / `ACCEPTED` for content that was never anchored. The consumer is then trusting exactly the
125
+ server the promise says they should not have to.
126
+
127
+ ### What the read path now does to defend against it (T-11.1 / T-11.2)
128
+
129
+ Before believing any record, **every read command** (`vh verify`, `vh show`, `vh list`,
130
+ `vh lineage`, `vh verify-proof`) runs a shared, side-effect-free preflight
131
+ (`cli/registry.js › assertRegistry`) that authenticates the registry first, in this order:
132
+
133
+ 1. **Bytecode-present check (`getCode`).** Confirm a contract is *actually deployed* at the address —
134
+ so a typo'd address or right-address-wrong-network is caught with an actionable
135
+ "no contract at &lt;addr&gt; on this RPC" error instead of a silent false `MISMATCH`.
136
+ 2. **`REGISTRY_ID` / `REGISTRY_VERSION` identity probe.** Read the contract's immutable, ownerless
137
+ self-identification marker (T-11.1: `REGISTRY_ID == keccak256("verifyhash.ContributionRegistry.v1")`
138
+ plus a monotonic `REGISTRY_VERSION`) and **refuse to trust** a contract that does not self-identify
139
+ as a genuine verifyhash registry of a version this build understands — closing the rogue-look-alike
140
+ gap.
141
+ 3. **Receipt/artifact `chainId` cross-check.** For `vh verify-proof` (whose artifact records the
142
+ `chainId` it was anchored on), cross-check the provider's chainId against it, so a verdict is never
143
+ reported against the wrong network — a root anchored on chain X says nothing about chain Y.
144
+
145
+ A genuine RPC/network error is surfaced **as itself** — never masqueraded as an identity failure
146
+ (mirroring the `isNotAnchoredError` discipline `vh verify` already uses). On success the human output
147
+ prints a one-line `registry authenticated: REGISTRY_ID ok (vN), chainId N` **before** any
148
+ verdict/record, and `--json` carries a `registry: { id, version, chainId }` block, so you can *see*
149
+ the check ran.
150
+
151
+ ### The residual caveat — the ID is a "right interface" signal, NOT a sole root of trust
152
+
153
+ The identity probe proves you are talking to a contract that **exists**, **self-identifies as the
154
+ right interface**, and (for artifacts) lives on the **expected chain**. It does **NOT** make the
155
+ records honest beyond the contract's own immutable first-writer-wins + commit-reveal rules — `uri` is
156
+ still an untrusted hint and `contributor` still means proven authorship only when `authorBound` is
157
+ `true`, exactly per the sections above.
158
+
159
+ Crucially, `REGISTRY_ID` is a **POSITIVE "right interface" signal verified ALONGSIDE the deployed
160
+ bytecode and chainId — never a sole root of trust.** The constant is part of the open source, so
161
+ **a fork or copy-paste deployment can compile and return the same `REGISTRY_ID`.** The marker proves
162
+ "this is the right interface", not "this is *the* canonical registry". Therefore a consumer who needs
163
+ a **SPECIFIC** deployment (not merely *some* contract that speaks the interface) must **also pin the
164
+ address out-of-band** — confirm you are on the expected chain at the expected address with the
165
+ expected code — and not rely on the ID alone. This is the same caveat the contract's NatSpec states
166
+ verbatim under "ON-CHAIN IDENTITY MARKER" (`contracts/ContributionRegistry.sol`); if the two ever
167
+ drift, the contract NatSpec is authoritative.
168
+
169
+ ### The loud opt-out (`--skip-identity-check`)
170
+
171
+ If you KNOW you are pointed at a not-yet-deployed / local-dev contract, every read command accepts a
172
+ **non-default, loud** `--skip-identity-check` that bypasses the preflight. When used, the output says
173
+ so unmistakably (human: `registry authentication: SKIPPED (--skip-identity-check) … the verdict is
174
+ only as trustworthy as the RPC/address you supplied`; `--json`: `registry: { "skipped": true, "note":
175
+ … }`). Without the flag, **every read command authenticates by default.**
176
+
177
+ > Rule of thumb: **authenticate the registry before you believe it — the `REGISTRY_ID` proves the
178
+ > right interface (alongside bytecode + chainId), but pin the address yourself if you need a specific
179
+ > deployment.**
180
+
181
+ ---
182
+
183
+ ## `timestamp` / `blockNumber` prove ordering + an UPPER BOUND on existence — NOT authorship time
184
+
185
+ `timestamp` is the `block.timestamp` and `blockNumber` is the `block.number` of the anchoring
186
+ transaction. They let you say two true things:
187
+
188
+ 1. **On-chain ordering.** If record A's `blockNumber` is less than record B's, A was anchored first.
189
+ Within a block, `index` (the insertion order) breaks ties.
190
+ 2. **An upper bound on existence time.** The content *existed no later than* that block — you cannot
191
+ anchor the hash of content that does not yet exist. So "this content existed by block N / by time
192
+ T" is provable.
193
+
194
+ They do **NOT** prove:
195
+
196
+ - **Authorship time.** The content may have been created long before it was anchored. The anchor
197
+ timestamp is when someone *recorded* the hash, not when the work was done.
198
+ - **A lower bound.** Nothing here says the content did *not* exist earlier; it only caps how late it
199
+ could have appeared.
200
+ - **Who authored it — for a one-shot `anchor()` record (`authorBound == false`).** There,
201
+ `contributor` is only the first *anchorer* (broadcaster), not a proven author: anyone who learns a
202
+ `contentHash` (for example from the public mempool) can `anchor` it first. A
203
+ commit-reveal record (`authorBound == true`) is different — see below.
204
+
205
+ ---
206
+
207
+ ## `contributor` — two attribution strengths, told apart by `authorBound`
208
+
209
+ This was decision **D-1** / task **T-0.3**: one-shot anchoring is front-runnable (a mempool watcher
210
+ can copy your `contentHash` and `anchor` it first, becoming the recorded `contributor`). The fix is a
211
+ **commit-reveal** path that binds the claimant to the content *before* the content hash is public.
212
+ Both paths write the same `Record`; `authorBound` tells you which guarantee you actually have:
213
+
214
+ | How the record was written | `authorBound` | What `contributor` means |
215
+ |----------------------------|---------------|--------------------------|
216
+ | `anchor(contentHash, uri)` (one tx) | `false` | **First anchorer only.** Front-runnable; NOT proven authorship. Use for cheap existence/timestamp proofs where attribution does not matter. |
217
+ | `commit(commitment)` then `reveal(contentHash, salt, uri)` | `true` | **Proven first claimant.** Front-running-resistant: the committer is hashed into the commitment before the content hash is exposed, so a copier cannot redirect attribution. |
218
+
219
+ **Why commit-reveal defeats the front-runner.** The commitment is
220
+ `keccak256(abi.encode(contentHash, committer, salt))`. Only that opaque hash goes on-chain first
221
+ (it leaks nothing about the content and is bound to the committer's address + a secret salt). After
222
+ `MIN_REVEAL_DELAY` blocks the committer reveals `(contentHash, salt)`. An attacker who copies the
223
+ revealed values from the mempool and resubmits the reveal as themselves recomputes
224
+ `keccak256(abi.encode(contentHash, ATTACKER, salt))` — a commitment they never registered — so their
225
+ reveal reverts (`NoSuchCommitment`). The maturation window stops them from committing-then-revealing
226
+ fast enough to beat an already-matured legitimate commitment. Net result: `contributor` stays the
227
+ original committer.
228
+
229
+ The CLI exposes this as `vh claim <path>` (commit-reveal) versus `vh anchor <path>` (one-shot).
230
+ `vh verify` prints the attribution strength for the record it finds. Tests live in
231
+ `test/Attribution.test.js` (contract) and `test/cli.claim.test.js` (CLI + a live-node front-run
232
+ proof).
233
+
234
+ ### The contribution score (`vh reputation`) inherits this boundary — anti-sybil
235
+
236
+ `vh reputation <addr>` aggregates the records grouped under one address — via a single paged
237
+ `getRecordsByContributor` walk (`total` = the walked records; `contributorRecordCount` is the companion
238
+ O(1) count it does not itself call) — into a **score**. That score is a
239
+ **NON-TRANSFERABLE DERIVED VIEW** —
240
+ re-derivable by anyone from the same registry, holding no value and granting no rights — **NOT a
241
+ token** (any tradeable/reputation-token layer is the human-gated D-2 / P-1 decision in
242
+ [`STRATEGY.md`](../STRATEGY.md), not built here). It is **read-only and needs no key** (provider only,
243
+ never a signer), and like `vh list`/`vh show` it does **NOT validate content** — re-derive + `vh verify`
244
+ for that. Crucially it does **NOT upgrade a front-runnable anchor's attribution**: grouping by
245
+ `contributor` is a raw enumeration, so an anchor-only record stays "first anchorer only", never proven
246
+ authorship, exactly per the rule above.
247
+
248
+ **Anti-sybil.** Address creation and one-shot `anchor()` are cheap, so the `total` / anchor-only counts
249
+ are trivially inflatable and prove only order-of-anchoring. The **meaningful signal is the `authorBound`
250
+ (commit-reveal) count**, because producing a front-running-resistant claim has a real cost (commit a
251
+ sender-bound, salt-blinded commitment, wait out `MIN_REVEAL_DELAY`, then reveal — only the original
252
+ committer can). `vh reputation` therefore reports `authorBound` and `anchor-only` **separately and never
253
+ sums them**. Full definition in [`docs/REPUTATION.md`](REPUTATION.md).
254
+
255
+ > Rule of thumb: **a contribution score is a re-derivable VIEW, not a token; weight the `authorBound`
256
+ > (commit-reveal) count, because anchor-only and address creation are cheap.**
257
+
258
+ ### `timestamp` is validator-influenced — don't treat it as a precise clock
259
+
260
+ `block.timestamp` is chosen by the block proposer, constrained only loosely by consensus (it must
261
+ move forward and stay within a tolerance of real time). A proposer has a few seconds of slack and a
262
+ small incentive surface to nudge it. Therefore:
263
+
264
+ - Use `timestamp` for **coarse ordering** and **"existed by roughly T"** statements.
265
+ - Do **not** use it as a trustworthy wall clock, for sub-minute precision, or anywhere a few seconds
266
+ of adversarial drift would matter.
267
+ - Prefer **`blockNumber`** when you need a hard, monotonic, harder-to-game ordering — block height
268
+ cannot be reordered or nudged the way a timestamp can.
269
+
270
+ ---
271
+
272
+ ## `parent` is a CLAIMED predecessor — not proof of ancestry, not a transfer of authorship
273
+
274
+ `parent` is an OPTIONAL, immutable predecessor edge (`bytes32(0)` == "no predecessor / root of a
275
+ lineage"). A record written with `vh anchor/claim --parent <hash>` names an **already-anchored**
276
+ predecessor; because the parent must pre-exist, the lineage graph is **acyclic by construction** (a
277
+ DAG), and the on-chain check is O(1) with no chain-walk. It asserts ONLY that the author of THIS record
278
+ CLAIMED the named predecessor. It does **NOT**:
279
+
280
+ - **prove content ancestry** — that the predecessor's bytes are genuinely an earlier version of, or
281
+ were derived into, this content. Anyone can name any anchored hash as a parent; consumers must still
282
+ **independently re-derive BOTH contents** (`vh hash`) and judge the relationship themselves.
283
+ - **transfer or imply authorship** — naming a parent grants this record nothing from it. Each record's
284
+ `contributor`/`authorBound` stand alone, per the rule above.
285
+
286
+ `vh lineage <0xhash>` walks the `parent` chain from a record UP to its lineage root, and `vh show
287
+ <0xhash>` surfaces a record's `parent`. Both are **read-only and need no key** (provider only, never a
288
+ signer), exactly like `vh list`/`vh show` — walking a public, immutable lineage must never require the
289
+ ability to write to it. As with `list`/`show`, a lineage walk does **NOT validate content**: it only
290
+ reads what is on-chain. The human output of `vh lineage` leads with both this lineage caveat and the
291
+ shared record caveat (untrusted `uri`; `contributor` per `authorBound`), and an off-chain indexer
292
+ reconstructs the graph from the `Linked(child, parent)` event. Full detail and a worked example are in
293
+ [`docs/LINEAGE.md`](LINEAGE.md).
294
+
295
+ > Rule of thumb: **a `parent` edge is a claim of "I built on that", not a proof of "that became this".**
296
+
297
+ ---
298
+
299
+ ## One-line summary
300
+
301
+ | Field | Trust it for | Do NOT trust it for |
302
+ |-------|--------------|---------------------|
303
+ | `contentHash` | integrity of the exact content (after you re-hash and compare) | — |
304
+ | `contributor` (`authorBound = true`) | proven first *claimant* (commit-reveal; front-running-resistant) | — |
305
+ | `contributor` (`authorBound = false`) | who *anchored* it first | who *authored* it |
306
+ | `blockNumber` | hard on-chain ordering; "existed by block N" | authorship time; a lower time bound |
307
+ | `timestamp` | coarse ordering; "existed by ~T" | precise wall-clock time; authorship time |
308
+ | `uri` | a human hint of where the content might be | anything security-relevant — re-fetch + re-hash |
309
+ | `parent` | the child author's *claim* that it built on that predecessor (anchored earlier) | genuine content ancestry; any transfer of the parent's authorship — re-derive both |
310
+
311
+ ## Tests
312
+
313
+ `test/TrustBoundaries.test.js` proves these boundaries are documented and behaviourally true:
314
+
315
+ - the compiled NatSpec (devdoc/userdoc) actually contains the "untrusted"/"re-derive"/"re-hash" and
316
+ "upper bound … NOT authorship time" statements (so the docs can't silently rot),
317
+ - a record can be anchored with a `uri` that points at the *wrong* content, and the contract accepts
318
+ it unchanged — demonstrating the `uri` is never validated, so consumers must re-hash,
319
+ - `timestamp`/`blockNumber` reflect the *anchoring* block (set by the chain at anchor time), and the
320
+ same content can be anchored long after it was created, demonstrating they are not authorship time.
321
+
322
+ `test/cli.readside.docs.test.js` additionally guards that the read-side caveat above can't rot: it
323
+ asserts that README.md and this file keep documenting `vh list` / `vh show` as read-only/no-key and
324
+ keep stating that listing or showing a record does NOT validate its content (you still re-derive +
325
+ `vh verify`), pinned to the caveats the read commands actually export. The same guard pins the
326
+ "Authenticating the registry you read from" section (T-11.3): that this file and the README keep
327
+ stating the threat (a wrong/rogue RPC+address can fabricate verdicts), the defence (the
328
+ `REGISTRY_ID`/version probe + the bytecode-present `getCode` check + the artifact `chainId`
329
+ cross-check), the residual caveat (the ID is a "right interface" signal verified alongside bytecode +
330
+ chainId, NOT a sole root of trust — a fork can reuse it, so pin the address out-of-band for a SPECIFIC
331
+ deployment), and the loud `--skip-identity-check` opt-out.
332
+
333
+
334
+ ---
335
+ <sub>© 2026 verifyhash.com · Licensed under Apache-2.0 (SPDX-License-Identifier: Apache-2.0) — see the [LICENSE](https://verifyhash.com/LICENSE) and [NOTICE](https://verifyhash.com/NOTICE) served with this file.</sub>