verifyhash 0.1.0 → 0.1.1
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/README.md +5 -3
- package/cli/agent-hook.js +431 -0
- package/docs/ADOPT.md +15 -5
- package/docs/AGENT-HOOK.md +111 -0
- package/docs/PUBLISH-VERIFY-VH.md +45 -0
- package/examples/README.md +185 -0
- package/examples/policy.lenient.json +5 -0
- package/examples/policy.strict.json +6 -0
- package/examples/run.js +366 -0
- package/examples/sample-dataset/README.txt +10 -0
- package/examples/sample-dataset/corpus/cc-by-poem.txt +8 -0
- package/examples/sample-dataset/corpus/mit-notes.txt +4 -0
- package/examples/sample-dataset/data/unlabeled.txt +5 -0
- package/examples/sample-dataset/vendored/gpl-snippet.txt +5 -0
- package/examples/sample-dataset.hints.json +7 -0
- package/examples/sample-parcel/data/manifest-of-contents.txt +7 -0
- package/examples/sample-parcel/data/records.csv +4 -0
- package/examples/sample-parcel/delivery-note.txt +9 -0
- package/package.json +25 -3
- package/verifier/README.md +555 -0
- package/verifier/action/README.md +87 -0
- package/verifier/action/action.yml +146 -0
- package/verifier/build-standalone-html.js +1287 -0
- package/verifier/build-standalone.js +989 -0
- package/verifier/ci/journal.generic.sh +96 -0
- package/verifier/ci/journal.github-actions.yml +99 -0
- package/verifier/ci/reproduce-vh.generic.sh +59 -0
- package/verifier/ci/reproduce-vh.github-actions.yml +49 -0
- package/verifier/ci/verify-service.generic.sh +96 -0
- package/verifier/ci/verify-service.github-actions.yml +88 -0
- package/verifier/ci/verify-vh.generic.sh +75 -0
- package/verifier/ci/verify-vh.github-actions.yml +56 -0
- package/verifier/dist/BUILD-PROVENANCE.json +210 -0
- package/verifier/dist/seal-vh-standalone.js +876 -0
- package/verifier/dist/seal-vh-standalone.js.sha256 +1 -0
- package/verifier/dist/verify-vh-standalone.html +3373 -0
- package/verifier/dist/verify-vh-standalone.html.sha256 +1 -0
- package/verifier/dist/verify-vh-standalone.js +4121 -0
- package/verifier/dist/verify-vh-standalone.js.sha256 +1 -0
- package/verifier/lib/canonical.js +141 -0
- package/verifier/lib/keccak.js +30 -0
- package/verifier/lib/keccak256-vendored.js +206 -0
- package/verifier/lib/merkle.js +145 -0
- package/verifier/lib/revocation-core.js +606 -0
- package/verifier/lib/revocation.js +200 -0
- package/verifier/lib/seal-cli.js +374 -0
- package/verifier/lib/seal-evidence.js +237 -0
- package/verifier/lib/secp256k1-recover.js +249 -0
- package/verifier/package.json +39 -0
- package/verifier/verify-vh.js +2374 -0
- package/docs/ADOPTION.json +0 -11
- package/docs/AUDIT.md +0 -55
- package/docs/DECIDE.md +0 -47
- package/docs/DECISIONS-PENDING.md +0 -27
- package/docs/DEPLOY-PUBLIC-SITE.md +0 -301
- package/docs/ENGINE-LEDGER.json +0 -12
- package/docs/LOOP-AUDIT-2026-07-03.json +0 -580
- package/docs/LOOP-HARDENING-PLAN.md +0 -44
- package/docs/METRICS.jsonl +0 -31
- package/docs/MORNING.md +0 -204
- package/docs/STRATEGY-ARCHIVE.md +0 -5055
- package/docs/SUPERVISOR-RUNBOOK.md +0 -52
- package/docs/USAGE-BUDGET.json +0 -121
package/docs/ADOPTION.json
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"_doc": "The loop's EXTERNAL adoption signal (audit 2026-07-03: 'the loop cannot fail its own test because its test has no market term'). Updated by the human (or future analytics). The Strategist/panel MUST read this and weigh it: novelty with zero adoption scores LOW; no new vertical/epic while every number here is zero.",
|
|
3
|
-
"updatedAt": "2026-07-03T04:35:00Z",
|
|
4
|
-
"distinctExternalUsers7d": 0,
|
|
5
|
-
"npmPublished": false,
|
|
6
|
-
"npmDownloads7d": null,
|
|
7
|
-
"pilotsActive": 0,
|
|
8
|
-
"buyerConversations": 0,
|
|
9
|
-
"revenueUsdTotal": 0,
|
|
10
|
-
"notes": "Live surfaces: verifyhash.com (site + zero-install verifier), Polygon-mainnet registry 0x77d8eF881D5aeEda64788968D13f9146fE1A609B. Nothing measured yet."
|
|
11
|
-
}
|
package/docs/AUDIT.md
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
# Security audit — ContributionRegistry (run wmkm6kzoj, 2026-06-23)
|
|
2
|
-
|
|
3
|
-
8-lens adversarial audit + 3-judge verification panel. **21 raw → 10 confirmed, 13 dismissed.**
|
|
4
|
-
Full machine output: `tasks/wmkm6kzoj.output`.
|
|
5
|
-
|
|
6
|
-
## Staged Epic-0 fix-tasks (fold into BACKLOG.md when no driver run is active)
|
|
7
|
-
|
|
8
|
-
Append these under `## EPIC-0` in BACKLOG.md, then relaunch the driver.
|
|
9
|
-
|
|
10
|
-
- **T-0.1** `TODO` Domain-separate Merkle leaves from internal nodes (verifyLeaf + CLI). deps: none. files: contracts/ContributionRegistry.sol, cli/hash.js, test/
|
|
11
|
-
- Acceptance: leaves are domain-separated so a crafted internal node can NOT be verified as a leaf
|
|
12
|
-
(double-hash leaves per OZ StandardMerkleTree, or tag the hash); `cli/hash.js` and the JS Merkle
|
|
13
|
-
helper in tests use the identical convention; a test constructs a second-preimage forgery attempt and
|
|
14
|
-
asserts `verifyLeaf` rejects it; full suite green. *(Covers F15, F11, F13. → Builder, `contract` persona.)*
|
|
15
|
-
|
|
16
|
-
- **T-0.2** `TODO` Bind file paths into repo Merkle leaves. deps: T-0.1. files: cli/hash.js, test/
|
|
17
|
-
- Acceptance: directory leaf = `keccak256(domainPrefix ‖ relPath ‖ 0x00 ‖ keccak256(content))`; the root
|
|
18
|
-
commits to names+content so renaming/moving a file changes the root; docs state exactly what the root
|
|
19
|
-
commits to; a test proves a rename is detected; suite green. *(Covers C2. → Builder, `cli` persona.)*
|
|
20
|
-
|
|
21
|
-
- **T-0.3** `TODO needs-decision` Resolve attribution front-running (this IS decision D-1). deps: none. files: contracts/, cli/, test/
|
|
22
|
-
- Engineering fork for the Decider. Options: (a) **commit–reveal** — `commit(keccak256(contentHash, msg.sender, salt))`
|
|
23
|
-
then `reveal(contentHash, salt)` after N blocks (audit's recommendation; defeats both theft and griefing);
|
|
24
|
-
(b) **per-author namespacing** — key records by `keccak256(addr, contentHash)`; (c) **accept + document** —
|
|
25
|
-
redefine `contributor` as "first anchorer", drop authorship claims from spec/NatSpec.
|
|
26
|
-
- Acceptance (after the Decider chooses): chosen scheme implemented; a test proves a front-runner who copies
|
|
27
|
-
the mempool value canNOT become the recorded author (or, for option c, NatSpec/README/spec updated to drop
|
|
28
|
-
authorship claims + a test asserting only the weaker "existed by block N" guarantee); suite green.
|
|
29
|
-
*(Covers F4, F14, F2, F5. → Decider, then Builder.)*
|
|
30
|
-
|
|
31
|
-
- **T-0.4** `TODO` Document trust boundaries: uri + timestamp. deps: none. files: contracts/ (NatSpec), README/docs
|
|
32
|
-
- Acceptance: NatSpec + docs state plainly that `uri` is an untrusted hint (consumers must re-derive and
|
|
33
|
-
re-hash content) and that `timestamp`/`blockNumber` prove on-chain ordering and an upper bound on existence
|
|
34
|
-
time, NOT authorship time and not validator-proof. *(Covers F17, C3. → Builder, docs.)*
|
|
35
|
-
|
|
36
|
-
## Confirmed findings (record)
|
|
37
|
-
|
|
38
|
-
| ID | Sev | Panel | Title |
|
|
39
|
-
|----|-----|-------|-------|
|
|
40
|
-
| F4 | High | 3/3 | anchor() front-runnable — permanent mis-attribution |
|
|
41
|
-
| F14 | High | 3/3 | attribution = first broadcaster, not author |
|
|
42
|
-
| F15 | High | 3/3 | verifyLeaf accepts internal nodes as leaves (second-preimage) |
|
|
43
|
-
| F2 | Med | 3/3 | no msg.sender↔content binding → authorship squatting |
|
|
44
|
-
| F5 | Med | 3/3 | griefing pre-emption with poisoned uri |
|
|
45
|
-
| F11 | Med | 2/3 | verifyLeaf no leaf/node domain separation |
|
|
46
|
-
| F17 | Med | 3/3 | uri unauthenticated, unbound, fixed by first anchorer |
|
|
47
|
-
| C2 | Med | 3/3 | Merkle root doesn't commit to file names/paths |
|
|
48
|
-
| C3 | Low | 2/3 | timestamp/blockNumber validator-influenced, not trustworthy time |
|
|
49
|
-
| F13 | Info | 2/3 | sorted-pair odd-node promotion enables ambiguous tree shapes |
|
|
50
|
-
|
|
51
|
-
## Dismissed (13 — panel refuted as non-issues / accepted risk)
|
|
52
|
-
|
|
53
|
-
No reentrancy surface · ownerless-immutable remediation gap · verifyLeaf no MEV (pure) · unbounded proof loop
|
|
54
|
-
(self-paid) · unbounded uri (self-paid) · uint64 truncation · `unchecked total+1` sound · empty-proof leaf==root
|
|
55
|
-
(×2) · Anchored event omits blockNumber (×2) · evmVersion not pinned · incomplete NatSpec.
|
package/docs/DECIDE.md
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
# DECIDE — the one human decision
|
|
2
|
-
|
|
3
|
-
This is the **only** page a human needs to read to unblock the loop. Everything below is BUILT and
|
|
4
|
-
locally TESTED; the loop cannot take the one remaining step for you, because it must never provision a
|
|
5
|
-
key, set a price, or contact a buyer. There is exactly one open human item, and it is `needs-human` P-8
|
|
6
|
-
(see [`docs/MORNING.md`](MORNING.md) § Needs-human). This page does not add a new ask — it just makes P-8
|
|
7
|
-
legible on one screen. **It is GENERATED from the P-8 block in MORNING.md (`scripts/sync-decide.cjs`), so
|
|
8
|
-
it can never silently drift from the real ask.**
|
|
9
|
-
|
|
10
|
-
## The decision (pick this)
|
|
11
|
-
|
|
12
|
-
**Run ONE time-boxed design-partner pilot of the EVIDENCE vertical (P-7).** EVIDENCE is the
|
|
13
|
-
lighter-gated path: no CPA, no counsel, no per-state liability layer, no real-funds deploy — just a
|
|
14
|
-
vendor key, a price, and one partner. (The heavier TrustLedger vertical, P-5, is the fallback channel.)
|
|
15
|
-
|
|
16
|
-
## The 3 first actions (verbatim from P-8)
|
|
17
|
-
|
|
18
|
-
1. **Pick the lighter-gated vertical FIRST: EVIDENCE (P-7), not TrustLedger (P-5)** — no CPA/legal/per-state
|
|
19
|
-
2. **One concrete first target this week:** an incident-response / digital-forensics team OR an
|
|
20
|
-
3. **3-step first contact (no slide deck):** (a) `vh identity publish` once the vendor key exists; (b) hand
|
|
21
|
-
|
|
22
|
-
## The one command to run on their folder
|
|
23
|
-
|
|
24
|
-
```
|
|
25
|
-
node pilot/run-pilot.js --certificate <path>
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
Add `--evidence-dir /path/to/their/folder` to seal THEIR files; the result is a single, forwardable
|
|
29
|
-
`*.vhevidence.json` certificate their security team verifies independently with `verify-vh` — offline, no
|
|
30
|
-
key, no network. The full runbook is [`docs/PILOT.md`](PILOT.md).
|
|
31
|
-
|
|
32
|
-
## Time box / stop criterion
|
|
33
|
-
|
|
34
|
-
3–5 prospects over ~2 weeks. **Success = ONE** who keeps using the free verify/challenge weekly OR agrees
|
|
35
|
-
to a paid pilot. **Zero after the box → stop**: the signal is "wrong buyer archetype" → switch to the
|
|
36
|
-
TrustLedger broker channel, NOT "build more product."
|
|
37
|
-
|
|
38
|
-
## Revenue-integrity boundary
|
|
39
|
-
|
|
40
|
-
Income is a subscription / license / meter for delivered software value; the license is an
|
|
41
|
-
ACCESS credential, NOT a token/coin/NFT, not tradeable, not an appreciating asset. The loop ships only the
|
|
42
|
-
mechanism + ephemeral test keys and must NEVER provision a key, set a price, contact a prospect, host,
|
|
43
|
-
take payment, or run the pilot itself — those are the human steps in P-8.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
---
|
|
47
|
-
<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>
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# The 3 decisions that unblock everything (one page)
|
|
2
|
-
|
|
3
|
-
Audit 2026-07-03: the ~11 stacked `needs-human` proposals in STRATEGY.md reduce to THREE distinct
|
|
4
|
-
decisions. Each has a pre-filled recommended default — **replying "accept all defaults" is a valid,
|
|
5
|
-
complete answer.** Everything else in the queue is a duplicate or depends on these.
|
|
6
|
-
|
|
7
|
-
## 1. Reputation unit: soulbound or tradeable? (P-1 / D-2)
|
|
8
|
-
**Default: (A) soulbound, non-transferable contribution score.** Zero securities exposure, matches the
|
|
9
|
-
project goal, buildable immediately. Option (B) tradeable token requires securities counsel BEFORE any
|
|
10
|
-
design work and pulls the project toward being a token project. → One word unblocks EPIC-3.
|
|
11
|
-
|
|
12
|
-
## 2. First-dollar config: price + vendor key (collapses P-3/P-5/P-6/P-7/P-9/P-10)
|
|
13
|
-
**Default:** self-serve **evidence license** at **$29/month or $290/year** (draft catalog price),
|
|
14
|
-
vendor key provisioned by YOU on your machine (never the loop):
|
|
15
|
-
`node -e "const {Wallet}=require('ethers');const w=Wallet.createRandom();console.log(w.address, w.privateKey)"`
|
|
16
|
-
— keep the private key in your password manager; publish only the address. The whole mint→deliver→verify
|
|
17
|
-
pipeline is already built and gated (`npm run go-live` proves it end-to-end). → A price + a key = sellable.
|
|
18
|
-
|
|
19
|
-
## 3. Distribution: put the free tool in front of real people (the audit's #1 finding)
|
|
20
|
-
**Default:** (a) publish the package to npm (`npm publish` from your logged-in account — the package is
|
|
21
|
-
ready), (b) show the zero-install browser verifier (verifyhash.com/verify-vh-standalone.html) to ~10
|
|
22
|
-
people in ONE segment — recommended: AI-agent builders (AGENTTRACE angle) or property managers
|
|
23
|
-
(TrustLedger angle) — and count who uses it twice. That count becomes `docs/ADOPTION.json`, the loop's
|
|
24
|
-
first external signal. **No new product epics until it is non-zero.**
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
*Answering #2 + #3 makes the first dollar mechanically possible. #1 just unblocks a parked epic.*
|
|
@@ -1,301 +0,0 @@
|
|
|
1
|
-
# verifyhash.com — Public Static Site Deployment Runbook (REPLACE mode)
|
|
2
|
-
|
|
3
|
-
> **Audience:** a Claude instance (or operator) with privileges this repo's autonomous loop does
|
|
4
|
-
> **not** have — i.e. can write to `/var/www/`, edit files under `/etc/nginx/`, and `sudo systemctl
|
|
5
|
-
> reload nginx`. Hand this whole file over; it is self-contained.
|
|
6
|
-
>
|
|
7
|
-
> **This file is an internal ops/deploy doc — do NOT serve it.** It is not in the publish set.
|
|
8
|
-
>
|
|
9
|
-
> Generated by the verifyhash loop supervisor, 2026-06-26. Hash source of truth = the committed
|
|
10
|
-
> `*.sha256` sidecars in `verifier/dist/`; the build script re-verifies, so don't trust this doc's copy.
|
|
11
|
-
|
|
12
|
-
## ENVIRONMENT — already in place (confirmed 2026-06-26 19:49Z)
|
|
13
|
-
|
|
14
|
-
- **Domain `verifyhash.com`** (+`www`) → resolves to **this host** (85.215.206.196). DNS done.
|
|
15
|
-
- **TLS done** — Let's Encrypt cert live at `/etc/letsencrypt/live/verifyhash.com/`; HTTPS serves HTTP/2 200.
|
|
16
|
-
- **nginx vhost exists**: `/etc/nginx/conf.d/verifyhash.com.conf` (http→https redirect, `root /var/www/verifyhash.com/html`, a static-asset cache `location`, a `location /api/ → localhost:3005` proxy, and a `.git` deny).
|
|
17
|
-
- **The webroot already holds a DIFFERENT, older site** — *"VerifyHash — Decentralized JSON Storage Network"* (`index.html` + `js/`) with a Node backend proxied at `/api/ → :3005` that is currently **down (502)**. **Decision: REPLACE it.** This runbook retires that old site and serves the provenance product at the `verifyhash.com` root.
|
|
18
|
-
|
|
19
|
-
So there is **no DNS, no certbot, no nginx install** to do. The job is: (1) swap the webroot contents, (2) two small vhost edits, (3) reload, (4) verify.
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## 0. What you are deploying (and what you are NOT)
|
|
24
|
-
|
|
25
|
-
A **static, read-only** site publishing verifyhash's independent-verification artifacts:
|
|
26
|
-
- the single-file, zero-dependency, **offline** verifier `verify-vh-standalone.js` + its `sha256`,
|
|
27
|
-
- the companion offline **sealer** `seal-vh-standalone.js` + `sha256`,
|
|
28
|
-
- the **build-provenance manifest** `build-provenance.json`,
|
|
29
|
-
- the version-controlled **landing page** (`site/index.html` — download links, verify one-liner, reproduce-from-source steps, honest boundary),
|
|
30
|
-
- the **prospect-facing docs** (CONFORMANCE, PILOT, KEY-LIFECYCLE, INDEPENDENT-VERIFICATION, …),
|
|
31
|
-
- a generated **`RELEASE-MANIFEST.json`** (sorted paths, per-file sha256 + source, total bytes) so the upload can be verified file-by-file.
|
|
32
|
-
|
|
33
|
-
The exact publish set is the committed allowlist **`site/publish-set.json`** (published path → committed
|
|
34
|
-
repo source). `scripts/site-release.js` assembles the webroot from EXACTLY that mapping — nothing outside
|
|
35
|
-
it can enter `public/`, which makes the "must NEVER be served" table below *structural*, not just prose.
|
|
36
|
-
|
|
37
|
-
**NOT** in scope (deliberately): no backend, no uploads, no API, no DB, no key, no payment, no token.
|
|
38
|
-
The product's trust model is *offline* verification — a server that "verifies for you" would weaken it.
|
|
39
|
-
(The repo's one server, `trustledger/server.js`, is a separate localhost-only broker demo, not part of this site.)
|
|
40
|
-
|
|
41
|
-
---
|
|
42
|
-
|
|
43
|
-
## 1. ⚠️ CRITICAL SAFETY RULES — read before touching anything
|
|
44
|
-
|
|
45
|
-
The nginx `root` for verifyhash.com must stay **`/var/www/verifyhash.com/html`** and that directory
|
|
46
|
-
must contain **only** the allowlisted static files below. A scan of this box found **real secrets** that
|
|
47
|
-
would be catastrophic to expose if the webroot were ever pointed at the repo or home dir.
|
|
48
|
-
|
|
49
|
-
**NEVER set `root`/`alias`/`try_files` to** `/home/loopdev/verifyhash` (repo), `/home/loopdev` (home),
|
|
50
|
-
or anything but the dedicated webroot. **Deploy by copying real files — never symlink into the repo.**
|
|
51
|
-
|
|
52
|
-
**Must NEVER be served:**
|
|
53
|
-
|
|
54
|
-
| Path | Why |
|
|
55
|
-
|---|---|
|
|
56
|
-
| `/home/loopdev/.claude/.credentials.json` | **LIVE Claude.ai OAuth token** — the single most dangerous file on the box. |
|
|
57
|
-
| `/home/loopdev/.claude.json`, `/home/loopdev/.claude/` | CLI config + full transcripts / session history / memory. |
|
|
58
|
-
| `/home/loopdev/verifyhash/.git/` | 38 MB history — clonable = the whole repo incl. every internal doc. |
|
|
59
|
-
| `test/cli.commit.parent.test.js`, `test/cli.claim.test.js` | committed `0x…` **private keys** (Hardhat dev keys; no real funds, but scanners flag them). |
|
|
60
|
-
| `.env` (none today; pre-deny), `**/*.vhclaim.json` (secret salt; pre-deny) | would leak keys/salts. |
|
|
61
|
-
| `STRATEGY.md`, `BACKLOG.md`, `HANDOFF.md`, `AGENT_TEAM.md`, `team.json` | internal roadmap / ops. |
|
|
62
|
-
| `build-loop.workflow.js`, `build-loop.prev.js` | the autonomous loop engine + guardrails. |
|
|
63
|
-
| `docs/USAGE-BUDGET.json`, `docs/METRICS.jsonl`, `docs/MORNING.md`, `docs/AUDIT.md`, `docs/DEPLOY-PUBLIC-SITE.md` | internal ops telemetry + this runbook. |
|
|
64
|
-
| `node_modules/`, `artifacts/`, `cache/`, `scripts/`, `hardhat.config.js`, `.scope-baseline.json` | build internals. |
|
|
65
|
-
|
|
66
|
-
The safe pattern below `rsync --delete`s a vetted, allowlist-only tree into the webroot — nothing else
|
|
67
|
-
can leak because nothing else is ever copied there. (Scan also confirmed: no real `.env`, no PEM/SSH
|
|
68
|
-
keys, no mnemonics, no API keys anywhere. The only risk is *where nginx points*.)
|
|
69
|
-
|
|
70
|
-
---
|
|
71
|
-
|
|
72
|
-
## 2. Prerequisites (most are already satisfied)
|
|
73
|
-
|
|
74
|
-
- ✅ Domain, DNS, TLS, nginx — all already in place for verifyhash.com (see ENVIRONMENT).
|
|
75
|
-
- You need: `sudo` (to write `/var/www/verifyhash.com/html` and edit the vhost), `rsync`, and `node`
|
|
76
|
-
(only to run the one-time `--check` integrity gate; the site needs no node at runtime).
|
|
77
|
-
- Read access to `/home/loopdev/verifyhash` (to copy the artifacts from).
|
|
78
|
-
|
|
79
|
-
---
|
|
80
|
-
|
|
81
|
-
## 3. Deploy the webroot (replaces the old site)
|
|
82
|
-
|
|
83
|
-
**The upload step is:** run `node scripts/site-release.js`, upload `public/`, verify against
|
|
84
|
-
`RELEASE-MANIFEST.json`. The assembler regenerates `public/` **deterministically** from the committed
|
|
85
|
-
allowlist `site/publish-set.json` (nothing outside the allowlist can enter the webroot), and
|
|
86
|
-
`--check` re-proves byte-for-byte integrity before you copy anything.
|
|
87
|
-
|
|
88
|
-
### 3a — assemble + integrity gate, then atomic replace
|
|
89
|
-
|
|
90
|
-
```bash
|
|
91
|
-
set -euo pipefail
|
|
92
|
-
REPO=/home/loopdev/verifyhash
|
|
93
|
-
SRC="$REPO/public" # the assembled, allowlist-only webroot
|
|
94
|
-
WEBROOT=/var/www/verifyhash.com/html
|
|
95
|
-
|
|
96
|
-
# 1) ASSEMBLE the webroot from the committed publish set (deterministic; writes only inside the repo)
|
|
97
|
-
node "$REPO/scripts/site-release.js"
|
|
98
|
-
|
|
99
|
-
# 2) INTEGRITY GATE — refuse to deploy a tampered/stale webroot (offline, writes nothing)
|
|
100
|
-
node "$REPO/scripts/site-release.js" --check # MUST print OK and exit 0
|
|
101
|
-
( cd "$REPO/verifier/dist" && sha256sum -c verify-vh-standalone.js.sha256 && sha256sum -c seal-vh-standalone.js.sha256 )
|
|
102
|
-
node "$REPO/verifier/build-standalone.js" --check # MUST print all-MATCH and exit 0
|
|
103
|
-
( cd "$SRC" && sha256sum -c verify-vh-standalone.js.sha256 && sha256sum -c seal-vh-standalone.js.sha256 )
|
|
104
|
-
|
|
105
|
-
# 3) back up the OLD site once, then REPLACE it (—delete removes anything not in the publish set)
|
|
106
|
-
sudo cp -a "$WEBROOT" "/var/www/verifyhash.com/html.bak.$(date +%Y%m%d-%H%M%S)" || true
|
|
107
|
-
sudo rsync -a --delete "$SRC"/ "$WEBROOT"/
|
|
108
|
-
|
|
109
|
-
# 4) ownership nginx expects
|
|
110
|
-
sudo chown -R www-data:www-data "$WEBROOT"
|
|
111
|
-
|
|
112
|
-
# 5) VERIFY THE UPLOAD against the manifest that shipped inside it (per-file sha256, all must match)
|
|
113
|
-
( cd "$WEBROOT" && node -e '
|
|
114
|
-
const fs=require("fs"),c=require("crypto");
|
|
115
|
-
const m=JSON.parse(fs.readFileSync("RELEASE-MANIFEST.json","utf8"));let bad=0;
|
|
116
|
-
for(const f of m.files){const h=c.createHash("sha256").update(fs.readFileSync(f.path)).digest("hex");
|
|
117
|
-
if(h!==f.sha256){bad++;console.error("MISMATCH "+f.path);}}
|
|
118
|
-
console.log(bad?"UPLOAD BROKEN — do not announce":"upload verified: "+m.files.length+" files match RELEASE-MANIFEST.json");
|
|
119
|
-
process.exit(bad?1:0);' )
|
|
120
|
-
|
|
121
|
-
echo "Deployed. Old site backed up to /var/www/verifyhash.com/html.bak.*"
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
> There is no hand-copy recipe anymore — the publish set lives in **`site/publish-set.json`**
|
|
125
|
-
> (published path → committed source, incl. the renames like `challenge/README.md` →
|
|
126
|
-
> `docs/challenge-README.md`) and `scripts/site-release.js` is the only generator. To change what the
|
|
127
|
-
> site serves, edit the allowlist, re-run the assembler, and commit the regenerated
|
|
128
|
-
> `site/RELEASE-MANIFEST.json`. **Never glob `docs/*.md` into the webroot — it contains internal files;
|
|
129
|
-
> the assembler refuses forbidden entries (`.git*`, env/key-shaped names, this runbook, ops telemetry) by construction.**
|
|
130
|
-
> After the upload, record what went live: `node scripts/site-release.js --mark-deployed` rewrites
|
|
131
|
-
> `site/DEPLOYED.json` (the drift baseline) — see §3c.
|
|
132
|
-
|
|
133
|
-
### 3b — the landing page
|
|
134
|
-
|
|
135
|
-
The landing page is **version-controlled at `site/index.html`** (the assembler stages it as
|
|
136
|
-
`public/index.html`). It carries the verifyhash.com URLs, the published sha256, the verify one-liner,
|
|
137
|
-
the reproduce-from-source steps, and the honest boundary. To change it, edit `site/index.html`,
|
|
138
|
-
re-run `node scripts/site-release.js`, and commit. If you want to set a real producer address in the
|
|
139
|
-
example, edit the `0x<…>` placeholder in §2 of that page (in `site/index.html`, then re-assemble).
|
|
140
|
-
|
|
141
|
-
> ⚠️ **Whenever the verifier bundle changes, update the page's `Published SHA-256:` to match.** The
|
|
142
|
-
> page's whole pitch is "don't trust us — download `verify-vh-standalone.js` and compare its hash
|
|
143
|
-
> yourself", so its advertised hash must equal the sha256 of the `verify-vh-standalone.js` this release
|
|
144
|
-
> ships (and the `.sha256` sidecar). If you rebuild the bundle but forget the page, the assembled
|
|
145
|
-
> webroot would fail its own cross-check — a false "tampered?" signal for buyers. **`node
|
|
146
|
-
> scripts/site-release.js` now REFUSES to assemble (and `--check` goes RED, naming `LANDING PAGE DRIFT`)
|
|
147
|
-
> when the page's `Published SHA-256:` ≠ the shipped bundle's sha256**, so this drift can never ship
|
|
148
|
-
> silently — but fix it at the source by editing `site/index.html`'s `Published SHA-256:` value.
|
|
149
|
-
|
|
150
|
-
### 3c — close the loop: `--mark-deployed`, then keep `--diff` clean (the P-11 refresh)
|
|
151
|
-
|
|
152
|
-
The full refresh flow is: **release → upload → `--mark-deployed` → `--diff` clean.**
|
|
153
|
-
|
|
154
|
-
1. **release** — `node scripts/site-release.js` assembles `public/` + writes both manifests (§3a step 1),
|
|
155
|
-
and `--check` gates it (§3a step 2).
|
|
156
|
-
2. **upload** — the rsync + upload-verify of §3a steps 3–5. This is the human-owned step.
|
|
157
|
-
3. **`--mark-deployed`** — `node scripts/site-release.js --mark-deployed` rewrites `site/DEPLOYED.json`
|
|
158
|
-
to the manifest you just uploaded + an ISO date note (`markedDeployedAt`). **Commit it.** This is the
|
|
159
|
-
ONE command you run AFTER uploading — it records what went live so the next `--diff` is truthful.
|
|
160
|
-
It records only; it never uploads anything.
|
|
161
|
-
4. **`--diff` clean** — `node scripts/site-release.js --diff` compares `site/DEPLOYED.json` (what is
|
|
162
|
-
believed LIVE) against a fresh assembly and prints a per-file `ADDED`/`CHANGED`/`REMOVED`/`UNCHANGED`
|
|
163
|
-
table + a one-line verdict. Right after a correct upload + `--mark-deployed` it must print
|
|
164
|
-
`live site matches the current release`. From then on, whenever it prints
|
|
165
|
-
`live site is stale: N of M published files differ — refresh per P-11`, redo this section.
|
|
166
|
-
Staleness is a HUMAN decision signal, not a CI failure: `--diff` exits `0` whether stale or clean;
|
|
167
|
-
only a malformed/missing `site/DEPLOYED.json` exits `3` (with a named error). The standing test
|
|
168
|
-
suite pins the committed `site/RELEASE-MANIFEST.json` to a fresh assembly (so the drift signal can
|
|
169
|
-
never itself go stale), but a stale `site/DEPLOYED.json` never fails the suite.
|
|
170
|
-
|
|
171
|
-
**Boundary (verbatim):** the loop assembles and diffs INSIDE the repo only; uploading to the live host
|
|
172
|
-
is the human-owned P-11 step — never auto-executed. (P-11 in `STRATEGY.md` is the recurring ~10-minute
|
|
173
|
-
refresh action this section implements.)
|
|
174
|
-
|
|
175
|
-
---
|
|
176
|
-
|
|
177
|
-
## 4. nginx vhost — two small edits to the EXISTING block
|
|
178
|
-
|
|
179
|
-
Edit `/etc/nginx/conf.d/verifyhash.com.conf`. Keep the `listen`/`server_name`/`ssl_*`/redirect lines.
|
|
180
|
-
Make these changes inside the `server { … }` that serves 443:
|
|
181
|
-
|
|
182
|
-
**(a) Remove the dead backend proxy** (the old JSON-storage app):
|
|
183
|
-
```nginx
|
|
184
|
-
# DELETE this whole block — there is no backend in the provenance product:
|
|
185
|
-
# location /api/ { proxy_pass http://localhost:3005/api/; ... }
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
**(b) Make `/` 404 on missing files (not an SPA fallback), and serve the verifier as a named download.**
|
|
189
|
-
Replace the existing `location /` and the static-asset cache block with:
|
|
190
|
-
```nginx
|
|
191
|
-
autoindex off;
|
|
192
|
-
server_tokens off;
|
|
193
|
-
add_header X-Content-Type-Options "nosniff" always;
|
|
194
|
-
|
|
195
|
-
# never serve dotfiles even if one ever lands in the webroot
|
|
196
|
-
location ~ /\. { deny all; return 404; }
|
|
197
|
-
|
|
198
|
-
# verifier + sealer: force download under the EXACT pinned filename so `sha256sum -c` works verbatim
|
|
199
|
-
location = /verify-vh-standalone.js {
|
|
200
|
-
default_type application/javascript;
|
|
201
|
-
add_header Content-Disposition 'attachment; filename="verify-vh-standalone.js"';
|
|
202
|
-
add_header X-Content-Type-Options "nosniff" always;
|
|
203
|
-
}
|
|
204
|
-
location = /seal-vh-standalone.js {
|
|
205
|
-
default_type application/javascript;
|
|
206
|
-
add_header Content-Disposition 'attachment; filename="seal-vh-standalone.js"';
|
|
207
|
-
add_header X-Content-Type-Options "nosniff" always;
|
|
208
|
-
}
|
|
209
|
-
location ~ \.sha256$ { default_type text/plain; }
|
|
210
|
-
location = /build-provenance.json { default_type application/json; }
|
|
211
|
-
location ~ \.md$ { default_type "text/markdown; charset=utf-8"; }
|
|
212
|
-
|
|
213
|
-
location / { try_files $uri $uri/ =404; } # NOT `/index.html` — a real 404 for missing paths
|
|
214
|
-
```
|
|
215
|
-
Keep `root /var/www/verifyhash.com/html;` and `index index.html;` as they are.
|
|
216
|
-
|
|
217
|
-
**Apply:**
|
|
218
|
-
```bash
|
|
219
|
-
sudo nginx -t && sudo systemctl reload nginx
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
---
|
|
223
|
-
|
|
224
|
-
## 5. DNS + TLS — already done
|
|
225
|
-
|
|
226
|
-
Nothing to do. DNS resolves here and the Let's Encrypt cert is live (certbot's renewal timer already
|
|
227
|
-
covers it). If you ever add a `verify.verifyhash.com` second origin (§7), that one needs its own cert.
|
|
228
|
-
|
|
229
|
-
---
|
|
230
|
-
|
|
231
|
-
## 6. Pre-go-live verification checklist
|
|
232
|
-
|
|
233
|
-
Run **all**; every line must pass before you announce it.
|
|
234
|
-
|
|
235
|
-
```bash
|
|
236
|
-
D=https://verifyhash.com
|
|
237
|
-
|
|
238
|
-
# a) verifier downloads and its published hash checks out end-to-end
|
|
239
|
-
curl -fsS -o /tmp/v.js "$D/verify-vh-standalone.js"
|
|
240
|
-
curl -fsS -o /tmp/v.js.sha256 "$D/verify-vh-standalone.js.sha256"
|
|
241
|
-
( cd /tmp && sha256sum -c v.js.sha256 ) # must say: verify-vh-standalone.js: OK
|
|
242
|
-
|
|
243
|
-
# b) landing page is the NEW site (provenance), not the old JSON-storage one, and its advertised
|
|
244
|
-
# Published SHA-256 matches the bundle you just uploaded (read the served sidecar's hash and
|
|
245
|
-
# confirm the landing page advertises that exact string — no hard-coded hash to go stale)
|
|
246
|
-
VERIFY_SHA=$(curl -fsS "$D/verify-vh-standalone.js.sha256" | cut -d' ' -f1)
|
|
247
|
-
curl -fsS "$D/" | grep -q "$VERIFY_SHA" && echo "landing OK (advertises the shipped bundle hash)" || echo "STOP: landing page hash != shipped bundle"
|
|
248
|
-
curl -fsS "$D/" | grep -qi "JSON Storage Network" && echo "STOP: old site still served" || echo "old site gone OK"
|
|
249
|
-
|
|
250
|
-
# c) the dead backend is detached — /api/ must NOT proxy anymore
|
|
251
|
-
curl -s -o /dev/null -w 'api/ -> %{http_code} (expect 404)\n' "$D/api/health"
|
|
252
|
-
|
|
253
|
-
# d) NONE of the forbidden paths are reachable (expect 404/403 for ALL)
|
|
254
|
-
for p in .git/HEAD .git/config .env ../STRATEGY.md ../BACKLOG.md \
|
|
255
|
-
../build-loop.workflow.js ../../.claude/.credentials.json \
|
|
256
|
-
../docs/USAGE-BUDGET.json ../test/cli.claim.test.js; do
|
|
257
|
-
echo "$(curl -s -o /dev/null -w '%{http_code}' "$D/$p") $p" # a 200 anywhere is STOP-SHIP
|
|
258
|
-
done
|
|
259
|
-
|
|
260
|
-
# e) no directory listing
|
|
261
|
-
curl -s "$D/docs/" | grep -qi "index of" && echo "FAIL: autoindex on" || echo "no listing OK"
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
Any forbidden path returning `200`, or the old "JSON Storage Network" string still present → **stop**,
|
|
265
|
-
fix, re-run. Once clean, the old-site backup `/var/www/verifyhash.com/html.bak.*` can be deleted.
|
|
266
|
-
|
|
267
|
-
---
|
|
268
|
-
|
|
269
|
-
## 7. Optional: retire the old `:3005` backend + a second download origin
|
|
270
|
-
|
|
271
|
-
- **The old Node backend** on `localhost:3005` (the JSON-storage app) is already down and now detached
|
|
272
|
-
from the domain (§4a). Stopping/disabling its service + deleting its files is the owner's call — it is
|
|
273
|
-
unrelated to this site and **not** required for go-live. Don't delete anything you can't identify.
|
|
274
|
-
- **Second origin (nice-to-have):** serving the verifier from `verify.verifyhash.com` (its own vhost +
|
|
275
|
-
cert) makes the "don't trust the vendor" pitch literally true at the network layer. The hash is the
|
|
276
|
-
trust anchor, so same-origin is already safe — this is a fast-follow, not a blocker.
|
|
277
|
-
|
|
278
|
-
---
|
|
279
|
-
|
|
280
|
-
## 8. Guardrail boundary — division of labor
|
|
281
|
-
|
|
282
|
-
- **The loop (unprivileged) builds artifacts in-tree** — the `verifier/dist/*` bundles and the prebuilt
|
|
283
|
-
`public/` webroot — as local commits. It **did not** and **cannot** write to `/var/www`, edit nginx,
|
|
284
|
-
bind a port, hold a key, or touch the `:3005` service. (`public/` is git-ignored; rebuild it anytime.)
|
|
285
|
-
- **You (privileged agent / human) do the outward-facing steps** — `rsync` into `/var/www`, edit the
|
|
286
|
-
vhost, reload nginx. That is the human action the loop is forbidden from taking.
|
|
287
|
-
- **Nothing here is a deployed service.** Static publication of already-public, git-tracked files crosses
|
|
288
|
-
no no-deploy / no-funds / no-token line: no key, no payment, no data custody, no token issuance.
|
|
289
|
-
|
|
290
|
-
---
|
|
291
|
-
|
|
292
|
-
## Appendix — file inventory & hashes
|
|
293
|
-
|
|
294
|
-
The canonical inventory is **generated, not hand-typed**: `site/RELEASE-MANIFEST.json` (committed twin;
|
|
295
|
-
the same bytes ship inside the webroot as `public/RELEASE-MANIFEST.json`) lists every published path,
|
|
296
|
-
its committed source path, its byte count, and its SHA-256, in sorted order. The allowlist it is built
|
|
297
|
-
from is `site/publish-set.json`; the snapshot of what is believed live is `site/DEPLOYED.json`.
|
|
298
|
-
|
|
299
|
-
**Always re-derive hashes from `node scripts/site-release.js --check` + the committed sidecars; never
|
|
300
|
-
trust a hand-copied table.** Assembled webroot to deploy: **`/home/loopdev/verifyhash/public/`** →
|
|
301
|
-
**`/var/www/verifyhash.com/html`**.
|
package/docs/ENGINE-LEDGER.json
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"_doc": "Append-only ledger of engine md5s that passed BOTH self-upgrade gates at launch time (scripts/pre-run-gate.cjs). The supervisor refuses to launch an engine not gated here. Never rewrite prior entries.",
|
|
3
|
-
"engines": [
|
|
4
|
-
{
|
|
5
|
-
"md5": "88bf8ee0db9038b8847a8e7468428457",
|
|
6
|
-
"gatedAt": "2026-07-03T04:33:10.820Z",
|
|
7
|
-
"prevMd5": "db5b3aceda33973e6be0df366caebd93",
|
|
8
|
-
"validate": "PASS",
|
|
9
|
-
"smoke": "SMOKE-PASS (4 scenarios: all-pass commits with verify-before-commit [20 calls]; FAILING verdict BLOCKs with NO commit; t"
|
|
10
|
-
}
|
|
11
|
-
]
|
|
12
|
-
}
|