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
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Publish `verify-vh` — the HUMAN checklist (one page)
|
|
2
|
+
|
|
3
|
+
**Publishing stays human.** The build loop only builds and tests locally — it never runs
|
|
4
|
+
`npm publish`, never deploys, never holds registry credentials (STRATEGY.md hard guardrails).
|
|
5
|
+
This page is what the human follows when they decide to publish the standalone verifier package.
|
|
6
|
+
|
|
7
|
+
**Why publish at all.** The front-door line in [docs/ADOPT.md](ADOPT.md) —
|
|
8
|
+
`npx --yes verify-vh demo` — **404s until `verify-vh` is published to npm**. Everything else about
|
|
9
|
+
that line is already true and machine-tested from the repo; publish is the single missing (human) step.
|
|
10
|
+
|
|
11
|
+
**The gate that must be green first.** `test/verify-vh.pack.test.js` runs `npm pack` on `verifier/`
|
|
12
|
+
(offline), extracts the tarball OUTSIDE the repo, proves the bare tree *cannot* fall back to the
|
|
13
|
+
repo's modules, provisions ONLY the one declared dependency (`js-sha3`), and then proves `demo` and
|
|
14
|
+
the real `--vendor` verify path work from the extracted tree alone — so a file missing from
|
|
15
|
+
`verifier/package.json` `files` fails that suite, not the first stranger's `npx` run.
|
|
16
|
+
|
|
17
|
+
## Checklist
|
|
18
|
+
|
|
19
|
+
1. **Gate** (repo root): `npx hardhat test test/verify-vh.pack.test.js` → must be green.
|
|
20
|
+
2. **Inspect the shipment** (from `verifier/`): `cd verifier && npm pack --dry-run` — expect
|
|
21
|
+
`verify-vh.js`, `lib/*.js`, `README.md`, `package.json`, and nothing else.
|
|
22
|
+
3. **Log in as the human npm account**: `npm whoami` (then `npm login` if needed). The loop holds no
|
|
23
|
+
npm credentials and must never be given any.
|
|
24
|
+
4. **Publish from `verifier/` — NOT the repo root** (the root package is `verifyhash`, a different
|
|
25
|
+
surface): `cd verifier && npm publish`. Re-publishing later requires a version bump in
|
|
26
|
+
`verifier/package.json` first (npm rejects a reused version).
|
|
27
|
+
5. **Post-publish smoke**, from any directory OUTSIDE this repo (a clean machine is even better):
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx --yes verify-vh demo
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Expect exit `0`; the transcript must ACCEPT the genuine packet naming signer
|
|
34
|
+
`0x70997970c51812dc3a010c7d01b50e0d17dc79c8` (the fixed TEST-ONLY hardhat #1 key — never a real
|
|
35
|
+
key), then REJECT the one-byte-tampered copy naming `model-card.md`.
|
|
36
|
+
6. **Hands-on smoke** (optional): `npx --yes verify-vh demo ./vh-demo`, then run the verify / tamper /
|
|
37
|
+
restore commands it prints.
|
|
38
|
+
7. **If the smoke fails**: `npm deprecate verify-vh@<version> "broken — do not use"`, fix, bump, and
|
|
39
|
+
re-run this checklist from step 1. Never leave a broken version as `latest`.
|
|
40
|
+
|
|
41
|
+
## The honest boundary (say no more than this, anywhere the package is announced)
|
|
42
|
+
|
|
43
|
+
The demo proves tamper-evidence + signer-pin, NOT a trusted timestamp, NOT a legal opinion.
|
|
44
|
+
A verified packet means: the bytes you hold match what the named signer sealed — nothing about
|
|
45
|
+
*when* it was sealed, and nothing about whether the content is true, licensed, or lawful.
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# verifyhash — runnable examples
|
|
2
|
+
|
|
3
|
+
Two committed, self-checking, fully-offline examples. Zero setup, one command each.
|
|
4
|
+
|
|
5
|
+
## `sdk-verify.js` — embed the SDK exactly as an external developer would
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
node examples/sdk-verify.js
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This is the **consumer** example: it imports the package **only** through its single public entrypoint,
|
|
12
|
+
`require("verifyhash")` (plus `ethers`, verifyhash's **own** declared dependency, used only to mint an
|
|
13
|
+
**ephemeral throwaway** signing key that stands in for a real, out-of-band vendor key) — **no** deep
|
|
14
|
+
`cli/core/...` reach-in, no network, no third-party non-core dependency, no real key. It runs **two acts**:
|
|
15
|
+
the free-tier tamper-evidence path, and the paid, revenue-relevant **signed + vendor-pinned verify gate**.
|
|
16
|
+
|
|
17
|
+
**Act 1 — UNSIGNED tamper-evidence (free tier):**
|
|
18
|
+
|
|
19
|
+
1. **`buildSeal`** — seal an in-memory `{ relPath, bytes }` file set (no directory, no disk).
|
|
20
|
+
2. **`verifySeal`** (untouched bytes) → **ACCEPTED** — the root is re-derived from the bytes you hold.
|
|
21
|
+
3. **`verifySeal`** (one byte flipped) → **REJECTED** — and it prints the per-file **diff** the verdict is
|
|
22
|
+
built from (which `relPath` changed, expected vs. actual hash).
|
|
23
|
+
4. **`serializeSeal` / `readSeal`** — the canonical, byte-deterministic packet a counterparty can re-read.
|
|
24
|
+
|
|
25
|
+
**Act 2 — SIGNED + vendor-PINNED verify gate (the paid embed).** This is the integration a downstream
|
|
26
|
+
service pays for: **verify in-process that a packet was signed by _our_ published vendor address**, with
|
|
27
|
+
**no** shell-out to the `vh` binary (STRATEGY.md **P-9** / EPIC-58 — "verified by verifyhash, signed &
|
|
28
|
+
pinned, inside _your_ product").
|
|
29
|
+
|
|
30
|
+
5. **`signSealWith`** — a publisher signs the seal (ephemeral key here; a real out-of-band key in prod).
|
|
31
|
+
6. **`verifySignedSeal`** pinned to **our** vendor address → **ACCEPTED**.
|
|
32
|
+
7. **`verifySignedSeal`** pinned to a **different** vendor → **REJECTED** — the signature is *genuine*; only
|
|
33
|
+
the **pin** fails. "Signed by someone, but not by us" must reject; that is the security property a paying
|
|
34
|
+
integrator's gate enforces (it is **not** tamper-evidence — the bytes are fine).
|
|
35
|
+
8. **`verifySignedSeal`** on a one-byte-tampered signature → **REJECTED** (recovered signer ≠ claimed).
|
|
36
|
+
|
|
37
|
+
It leads with the standing **trust note** (a seal proves *tamper-evidence*; a valid **signature** proves
|
|
38
|
+
*who vouched* — the pinned address's key-holder — for those bytes; **neither** proves a trusted timestamp
|
|
39
|
+
and **neither** is a legal opinion — timestamping rides the human-owned trust-root, `needs-human`, P-3 in
|
|
40
|
+
[`STRATEGY.md`](../STRATEGY.md)), prints a clear **PASS** summary naming both acts, and exits 0. The only
|
|
41
|
+
key it ever uses is an **ephemeral, in-memory throwaway** (never persisted, funded, or logged). It is
|
|
42
|
+
test-gated by [`test/sdk.example.test.js`](../test/sdk.example.test.js) on every `npx hardhat test` — a grep
|
|
43
|
+
there asserts the example uses **only** the public surface (`require("verifyhash")` + `ethers`, no deep
|
|
44
|
+
`cli/*` import), so the "public API stands alone" claim can never silently rot.
|
|
45
|
+
|
|
46
|
+
## `verify-service-client.js` — call the `vh serve-verify` HTTP endpoint as a drop-in dependency
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
node examples/verify-service-client.js
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The **verify-service client** example: it boots the `vh serve-verify` HTTP endpoint (the *"CI plugin that
|
|
53
|
+
imports rather than shells out"*), then treats it as a drop-in dependency. It **builds** a seal with
|
|
54
|
+
`require("verifyhash")`, **POSTs** a clean seal to the booted service (HTTP **200 ACCEPTED** → prints
|
|
55
|
+
`ACCEPT`), then **POSTs** the same seal with **one byte flipped** (HTTP **422 REJECTED** → prints `REJECT`),
|
|
56
|
+
tears the service down, and exits 0. It imports **only** `require("verifyhash")`, the `vh` **command**
|
|
57
|
+
(spawned from the package's own `bin.vh`), and Node built-ins (`http`, `child_process`, `path`) — **no**
|
|
58
|
+
deep `cli/*` reach-in and **no** third-party dependency. See
|
|
59
|
+
[`docs/VERIFY-SERVICE.md`](../docs/VERIFY-SERVICE.md) for the request/response schema, the status mapping,
|
|
60
|
+
and the trust boundary. It is test-gated by
|
|
61
|
+
[`test/verify-service.example.test.js`](../test/verify-service.example.test.js) on every `npx hardhat test`,
|
|
62
|
+
so it can never silently rot.
|
|
63
|
+
|
|
64
|
+
## `journal-ci.js` — continuous-integrity CI step (the `vh journal` integrity journal)
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
node examples/journal-ci.js
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The **continuous-integrity** example: it treats the `vh journal` **append-only, hash-chained integrity
|
|
71
|
+
journal** as a drop-in CI step. It **builds** a seal with `require("verifyhash")`, writes the sealed bytes +
|
|
72
|
+
packet to a throwaway workdir, then shells out to `vh journal append` **twice** (recording two hash-chained
|
|
73
|
+
verdicts, strictly additively), runs `vh journal verify` (reporting an **unbroken chain**, exit 0), and exits
|
|
74
|
+
0. It imports **only** `require("verifyhash")`, the `vh` **command** (spawned from the package's own `bin.vh`),
|
|
75
|
+
and Node built-ins (`fs`, `os`, `path`, `child_process`) — **no** deep `cli/*` reach-in (not even the pure
|
|
76
|
+
journal core) and **no** third-party dependency. The `ts` on each entry is **self-asserted**, **not** a
|
|
77
|
+
trusted timestamp — the journal never claims *"unaltered since date T"* on its own (STRATEGY.md **P-3**). See
|
|
78
|
+
[`docs/INTEGRITY-JOURNAL.md`](../docs/INTEGRITY-JOURNAL.md) for the schema, the chain guarantee, the 0/3
|
|
79
|
+
contract, and the honesty boundary. It is test-gated by
|
|
80
|
+
[`test/journal.example.test.js`](../test/journal.example.test.js) on every `npx hardhat test`, so it can
|
|
81
|
+
never silently rot.
|
|
82
|
+
|
|
83
|
+
## `sdk-verify-signed.js` — the SIGNED + vendor-PINNED verify gate, in-process
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
node examples/sdk-verify-signed.js
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
This is the **buyer's** signed-verify example: it plays a downstream service that **receives** a signed,
|
|
90
|
+
vendor-address-pinned deliverable (a model, a dataset, a build artifact) and clears the **two gates that
|
|
91
|
+
gate a real purchase** — **(a)** *"was this signed by our published vendor address?"* and **(b)** *"are the
|
|
92
|
+
exact files I received **on disk** the ones that vendor signed?"* — **in-process**, with **no** shell-out to
|
|
93
|
+
the `vh` binary. It is the SIGNED twin of `vh evidence verify-signed` (including its `--signer` pin and
|
|
94
|
+
`--dir` binding), byte-identical because it **is** the same code (`index.js` is a thin identity re-export;
|
|
95
|
+
see [`../docs/SDK.md`](../docs/SDK.md)). This is the paid, revenue-relevant embed (STRATEGY.md **P-9** /
|
|
96
|
+
EPIC-58 — "verified by verifyhash, signed & pinned, inside _your_ product").
|
|
97
|
+
|
|
98
|
+
The verify example imports **only** `require("verifyhash")` and **relative** example files — nothing else,
|
|
99
|
+
**no** `child_process`, **no** built-in (`fs`/`os`/`path`), **no** network, **no** deep `cli/*` reach-in.
|
|
100
|
+
Because a buyer **never signs** and does not do its own disk plumbing, both the publisher-side key handling
|
|
101
|
+
(minting an **ephemeral throwaway** key that stands in for the publisher's real out-of-band key) **and** the
|
|
102
|
+
"receive the deliverable to a throwaway temp dir / corrupt one received file / clean up" plumbing are
|
|
103
|
+
quarantined in [`lib/ephemeral-publisher.js`](./lib/ephemeral-publisher.js) — the only place `ethers`
|
|
104
|
+
(verifyhash's own dependency) and Node built-ins are touched. It runs one **ACCEPT** then **four** REJECT/
|
|
105
|
+
ACCEPT steps that **escalate in value**:
|
|
106
|
+
|
|
107
|
+
1. **`verifySignedSeal`** pinned to **our** vendor address → **ACCEPTED**.
|
|
108
|
+
2. **`verifySignedSeal`** pinned to a **different** vendor → **REJECTED** — a **wrong-signer** reject: the
|
|
109
|
+
signature is *genuine* and the bytes are fine; it just recovers to a signer we do **not** pin. "Signed by
|
|
110
|
+
someone, but not by us" must reject; that is the security property a paying integrator's gate enforces.
|
|
111
|
+
3. **`verifySignedSeal`** on a one-byte-**tampered** signature → **REJECTED** — the recovered signer no
|
|
112
|
+
longer matches the claimed one, so it rejects even under the correct vendor pin.
|
|
113
|
+
4. **`verifySignedSealAttestation`** — the **strict, on-disk BINDING gate** a paying integrator actually
|
|
114
|
+
buys — pinned to **our** vendor address **and bound to the actual files received on disk**:
|
|
115
|
+
- **[4a]** the **untouched** received deliverable → **ACCEPTED** — both gates pass: our vendor signed it
|
|
116
|
+
**and** the bytes on disk are byte-identical to what was signed (`manifestBindsAttestation=true`).
|
|
117
|
+
- **[4b]** the received deliverable with **one file corrupted on disk** → **REJECTED** — the vendor
|
|
118
|
+
signature over the **original** bytes is **still genuine** and the pin **still** matches; only the
|
|
119
|
+
on-disk bytes drifted, so `manifestBindsAttestation=false`. This is the **real fraud** the buyer cares
|
|
120
|
+
about — a **genuine our-vendor signature attached to a substituted download** — and it is the case the
|
|
121
|
+
signature-only path (1–3) **cannot** catch. The on-disk binding does.
|
|
122
|
+
|
|
123
|
+
It leads with the standing **trust note** (a valid **signature** proves *who vouched* — the pinned address's
|
|
124
|
+
key-holder — for those exact sealed bytes; it does **not** prove a trusted timestamp and is **not** a legal
|
|
125
|
+
opinion — timestamping rides the human-owned trust-root, `needs-human`, P-3 in
|
|
126
|
+
[`../STRATEGY.md`](../STRATEGY.md)), prints a clear **PASS** summary, and exits 0. The received deliverable
|
|
127
|
+
is written to a **throwaway OS temp dir** (never the repo tree) and cleaned up in a `finally`. Verification
|
|
128
|
+
is **offline and key-free**: it recovers a **public** address from the signature, holds no private key, and
|
|
129
|
+
contacts nothing. It is test-gated by [`test/sdk.example.signed.test.js`](../test/sdk.example.signed.test.js)
|
|
130
|
+
on every `npx hardhat test` — a grep there asserts the example imports **only** `require("verifyhash")` +
|
|
131
|
+
relative files, with **no** deep `cli/*` import, **no** `child_process`, **no** built-in, and **no** network,
|
|
132
|
+
and a snapshot check asserts it leaves no temp dir in the repo — so the "in-process public verify stands
|
|
133
|
+
alone" claim can never silently rot.
|
|
134
|
+
|
|
135
|
+
## `run.js` — the end-to-end DataLedger + ProofParcel buyer pipeline
|
|
136
|
+
|
|
137
|
+
One command, zero setup, fully offline:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
node examples/run.js
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
This drives the **real** DataLedger + ProofParcel buyer pipeline against the tiny committed sample data
|
|
144
|
+
in this directory, using the **same module entrypoints the `vh` CLI dispatches to** (`cli/dataset.js`,
|
|
145
|
+
`cli/parcel.js`) — it is not a brittle shell pipeline of string parsing. It prints a clear **PASS/FAIL**
|
|
146
|
+
summary with the produced artifact paths.
|
|
147
|
+
|
|
148
|
+
## What it runs
|
|
149
|
+
|
|
150
|
+
- **DataLedger:** `dataset build` → `check --policy` (a PASS against a lenient policy **and** a FAIL
|
|
151
|
+
against a strict one) → `verify` (a MATCH against the untouched sample **and** a MISMATCH after a
|
|
152
|
+
one-byte tamper) → `report` (one filed evidence document) → `attest` (the canonical UNSIGNED bytes).
|
|
153
|
+
- **ProofParcel:** `parcel build` → `verify` (MATCH **and** a tamper MISMATCH) → `attest`.
|
|
154
|
+
|
|
155
|
+
The sample contains **deliberate** problems so the gates have something real to catch:
|
|
156
|
+
|
|
157
|
+
- `vendored/gpl-snippet.txt` carries a `GPL-3.0` license hint → flagged by `denyLicenses`.
|
|
158
|
+
- `data/unlabeled.txt` carries no license hint → flagged by `requireLicense`.
|
|
159
|
+
|
|
160
|
+
## Where it writes
|
|
161
|
+
|
|
162
|
+
The committed sample under `examples/` is **read-only** to the script. Everything it produces (manifests,
|
|
163
|
+
the report, the unsigned attestation bytes, and the working copies it deliberately tampers) goes to a
|
|
164
|
+
fresh **OS temp dir**. Override the location with `VH_EXAMPLE_OUT=/some/path`; keep the artifacts for
|
|
165
|
+
inspection with `VH_EXAMPLE_KEEP=1`. **Nothing is ever scattered into the repo working tree.**
|
|
166
|
+
|
|
167
|
+
## Trust posture (read this — the script will not let you forget it)
|
|
168
|
+
|
|
169
|
+
The example proves **tamper-evidence** (any edit, rename, add, or remove flips the Merkle root) and emits
|
|
170
|
+
the canonical **UNSIGNED** attestation bytes a trust-root would sign. It does **not**, and cannot, prove
|
|
171
|
+
*"unaltered since date T"*: that standing claim rides the **human-owned** signing / timestamp / anchor
|
|
172
|
+
trust-root (`needs-human`, P-3 in [`STRATEGY.md`](../STRATEGY.md)). The script **references but never
|
|
173
|
+
executes** those `sign` / `timestamp` / anchor steps, and says exactly where the human handoff is. See
|
|
174
|
+
[`docs/TRUST-BOUNDARIES.md`](../docs/TRUST-BOUNDARIES.md), [`docs/DATALEDGER.md`](../docs/DATALEDGER.md),
|
|
175
|
+
and [`docs/PROOFPARCEL.md`](../docs/PROOFPARCEL.md).
|
|
176
|
+
|
|
177
|
+
## It cannot rot
|
|
178
|
+
|
|
179
|
+
`test/cli.examples.test.js` runs this example end-to-end against the committed sample on every
|
|
180
|
+
`npx hardhat test`, asserting the pipeline completes, the policy violation is flagged, the tamper is
|
|
181
|
+
caught, and the run leaves the working tree clean.
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
<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>
|
package/examples/run.js
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// =================================================================================================
|
|
5
|
+
// verifyhash — runnable, self-checking, OFFLINE end-to-end EXAMPLE (T-21.2)
|
|
6
|
+
//
|
|
7
|
+
// WHAT THIS IS
|
|
8
|
+
// A single script a buyer/evaluator (and CI) can run with ZERO setup — no key, no TSA, no RPC, no
|
|
9
|
+
// network — to watch the real DataLedger + ProofParcel buyer pipeline work against the committed
|
|
10
|
+
// sample data in examples/. It reuses the EXACT module entrypoints the `vh` CLI dispatches to
|
|
11
|
+
// (cli/dataset.js, cli/parcel.js) — it is NOT a brittle shell pipeline of string parsing.
|
|
12
|
+
//
|
|
13
|
+
// DataLedger : dataset build -> check --policy (PASS + FAIL) -> verify (MATCH + TAMPER) -> report -> attest
|
|
14
|
+
// ProofParcel: parcel build -> verify (MATCH + TAMPER) -> attest
|
|
15
|
+
//
|
|
16
|
+
// FILESYSTEM HYGIENE (load-bearing)
|
|
17
|
+
// The committed sample data under examples/ is READ-ONLY here. Everything this script produces —
|
|
18
|
+
// manifests, the report, the unsigned attestation bytes, and the working copies it deliberately
|
|
19
|
+
// tampers — is written to a fresh OS temp workspace (or VH_EXAMPLE_OUT if you set it). NOTHING is
|
|
20
|
+
// ever scattered into the repo working tree. Set VH_EXAMPLE_KEEP=1 to keep the temp dir for inspection.
|
|
21
|
+
//
|
|
22
|
+
// TRUST POSTURE (read this; the script will not let you forget it)
|
|
23
|
+
// This example demonstrates TAMPER-EVIDENCE (any edit/rename/add/remove flips the Merkle root) and
|
|
24
|
+
// emits the canonical UNSIGNED attestation bytes a human trust-root would sign. It does NOT, and
|
|
25
|
+
// cannot, prove "unaltered since date T": that standing claim rides the HUMAN-OWNED signing/timestamp/
|
|
26
|
+
// anchor trust-root (needs-human, P-3 in STRATEGY.md). This script references — but never executes —
|
|
27
|
+
// those `sign` / `timestamp` / anchor steps, and says exactly where the human handoff is.
|
|
28
|
+
// =================================================================================================
|
|
29
|
+
|
|
30
|
+
const fs = require("fs");
|
|
31
|
+
const os = require("os");
|
|
32
|
+
const path = require("path");
|
|
33
|
+
|
|
34
|
+
const dataset = require("../cli/dataset");
|
|
35
|
+
const parcel = require("../cli/parcel");
|
|
36
|
+
|
|
37
|
+
const EX_DIR = __dirname;
|
|
38
|
+
const SAMPLE_DATASET = path.join(EX_DIR, "sample-dataset");
|
|
39
|
+
const SAMPLE_DATASET_HINTS = path.join(EX_DIR, "sample-dataset.hints.json");
|
|
40
|
+
const POLICY_LENIENT = path.join(EX_DIR, "policy.lenient.json");
|
|
41
|
+
const POLICY_STRICT = path.join(EX_DIR, "policy.strict.json");
|
|
42
|
+
const SAMPLE_PARCEL = path.join(EX_DIR, "sample-parcel");
|
|
43
|
+
|
|
44
|
+
// ---- tiny output + check helpers -----------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
const out = (s) => process.stdout.write(s + "\n");
|
|
47
|
+
const hr = () => out("-".repeat(88));
|
|
48
|
+
|
|
49
|
+
const checks = []; // { ok, label } — the script's verdict is the AND of every one of these.
|
|
50
|
+
function check(label, ok, detail) {
|
|
51
|
+
checks.push({ ok: !!ok, label });
|
|
52
|
+
out(` [${ok ? "PASS" : "FAIL"}] ${label}${detail ? " — " + detail : ""}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Recursively copy a directory tree (the sample is tiny). Used so the TAMPER step mutates a working
|
|
56
|
+
// COPY in the temp workspace, never the committed sample under examples/.
|
|
57
|
+
function copyDir(src, dst) {
|
|
58
|
+
fs.mkdirSync(dst, { recursive: true });
|
|
59
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
60
|
+
const s = path.join(src, entry.name);
|
|
61
|
+
const d = path.join(dst, entry.name);
|
|
62
|
+
if (entry.isDirectory()) copyDir(s, d);
|
|
63
|
+
else fs.copyFileSync(s, d);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// A stdout sink that swallows the command's own human output — the example prints its OWN narration so
|
|
68
|
+
// the PASS/FAIL summary stays readable. (The commands still run for real; we just don't echo their block.)
|
|
69
|
+
const quiet = () => {};
|
|
70
|
+
|
|
71
|
+
// =================================================================================================
|
|
72
|
+
|
|
73
|
+
function main() {
|
|
74
|
+
// LEAD with the standing trust note so the example can never overclaim.
|
|
75
|
+
hr();
|
|
76
|
+
out("verifyhash — OFFLINE end-to-end example (DataLedger + ProofParcel)");
|
|
77
|
+
hr();
|
|
78
|
+
out("TRUST NOTE (DataLedger): " + dataset.TRUST_NOTE);
|
|
79
|
+
out("TRUST NOTE (ProofParcel): " + parcel.TRUST_NOTE);
|
|
80
|
+
out("");
|
|
81
|
+
out("This example proves TAMPER-EVIDENCE and emits the canonical UNSIGNED attestation bytes.");
|
|
82
|
+
out('It does NOT prove "unaltered since date T" — that rides the HUMAN-OWNED signing/timestamp/');
|
|
83
|
+
out("anchor trust-root (needs-human, P-3). Those steps are referenced below but NEVER executed.");
|
|
84
|
+
hr();
|
|
85
|
+
|
|
86
|
+
// Fresh, isolated output workspace. Default: an OS temp dir; override via VH_EXAMPLE_OUT.
|
|
87
|
+
const work =
|
|
88
|
+
process.env.VH_EXAMPLE_OUT && process.env.VH_EXAMPLE_OUT.trim()
|
|
89
|
+
? path.resolve(process.env.VH_EXAMPLE_OUT.trim())
|
|
90
|
+
: fs.mkdtempSync(path.join(os.tmpdir(), "vh-example-"));
|
|
91
|
+
fs.mkdirSync(work, { recursive: true });
|
|
92
|
+
out(`output workspace (gitignored / OS temp — never the repo): ${work}`);
|
|
93
|
+
out("");
|
|
94
|
+
|
|
95
|
+
const artifacts = {};
|
|
96
|
+
|
|
97
|
+
// ===============================================================================================
|
|
98
|
+
// PART 1 — DataLedger: the AI training-data provenance pipeline.
|
|
99
|
+
// ===============================================================================================
|
|
100
|
+
out("== DataLedger ==");
|
|
101
|
+
|
|
102
|
+
const dsManifest = path.join(work, "dataset.manifest.json");
|
|
103
|
+
const hints = JSON.parse(fs.readFileSync(SAMPLE_DATASET_HINTS, "utf8"));
|
|
104
|
+
|
|
105
|
+
// build — tamper-evident manifest (Merkle root + per-file leaves) with the UNTRUSTED hints attached.
|
|
106
|
+
const dsBuild = dataset.runDatasetBuild({
|
|
107
|
+
dir: SAMPLE_DATASET,
|
|
108
|
+
out: dsManifest,
|
|
109
|
+
hints,
|
|
110
|
+
stdout: quiet,
|
|
111
|
+
});
|
|
112
|
+
artifacts.datasetManifest = dsManifest;
|
|
113
|
+
check(
|
|
114
|
+
"dataset build produced a manifest with a Merkle root over every file",
|
|
115
|
+
/^0x[0-9a-fA-F]{64}$/.test(dsBuild.root) && dsBuild.fileCount === 5,
|
|
116
|
+
`root=${dsBuild.root.slice(0, 14)}… files=${dsBuild.fileCount}`
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// check --policy (LENIENT) — the org-policy gate should PASS (CI exit 0).
|
|
120
|
+
const lenient = dataset.runDatasetCheck({
|
|
121
|
+
manifest: dsManifest,
|
|
122
|
+
policy: POLICY_LENIENT,
|
|
123
|
+
stdout: quiet,
|
|
124
|
+
});
|
|
125
|
+
check(
|
|
126
|
+
"dataset check --policy (lenient) PASSes → CLI exit 0",
|
|
127
|
+
lenient.verdict === "PASS",
|
|
128
|
+
`verdict=${lenient.verdict}`
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// check --policy (STRICT) — the DELIBERATE violations in the sample (a GPL-3.0 file + a file with no
|
|
132
|
+
// license hint under requireLicense) must be FLAGGED (CI exit 3). This is the core "the gate works" proof.
|
|
133
|
+
const strict = dataset.runDatasetCheck({
|
|
134
|
+
manifest: dsManifest,
|
|
135
|
+
policy: POLICY_STRICT,
|
|
136
|
+
stdout: quiet,
|
|
137
|
+
});
|
|
138
|
+
const flaggedGpl = strict.violations.some(
|
|
139
|
+
(v) => v.rule === "denyLicenses" && v.value === "GPL-3.0"
|
|
140
|
+
);
|
|
141
|
+
const flaggedMissing = strict.violations.some((v) => v.rule === "requireLicense");
|
|
142
|
+
check(
|
|
143
|
+
"dataset check --policy (strict) FLAGS the deliberate violations → CLI exit 3",
|
|
144
|
+
strict.verdict === "FAIL" && flaggedGpl && flaggedMissing,
|
|
145
|
+
`verdict=${strict.verdict} violations=${strict.violations.length} (GPL-3.0 + missing-license)`
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
// verify (MATCH) — re-derive the root from the UNTOUCHED committed sample; must MATCH (CI exit 0).
|
|
149
|
+
const dsVerifyOk = dataset.runDatasetVerify({
|
|
150
|
+
dir: SAMPLE_DATASET,
|
|
151
|
+
manifest: dsManifest,
|
|
152
|
+
stdout: quiet,
|
|
153
|
+
});
|
|
154
|
+
check(
|
|
155
|
+
"dataset verify against the untouched sample → MATCH (CLI exit 0)",
|
|
156
|
+
dsVerifyOk.status === "MATCH",
|
|
157
|
+
`status=${dsVerifyOk.status}`
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
// verify (TAMPER) — copy the sample, mutate ONE file, and confirm verify catches it as MISMATCH and
|
|
161
|
+
// localizes the CHANGED file. The mutation happens on the temp COPY, never the committed sample.
|
|
162
|
+
const dsTamperDir = path.join(work, "tampered-dataset");
|
|
163
|
+
copyDir(SAMPLE_DATASET, dsTamperDir);
|
|
164
|
+
const tamperedFile = path.join(dsTamperDir, "corpus", "mit-notes.txt");
|
|
165
|
+
fs.appendFileSync(tamperedFile, "\nTAMPER: one extra byte changes the file's bytes.\n");
|
|
166
|
+
const dsVerifyTamper = dataset.runDatasetVerify({
|
|
167
|
+
dir: dsTamperDir,
|
|
168
|
+
manifest: dsManifest,
|
|
169
|
+
stdout: quiet,
|
|
170
|
+
});
|
|
171
|
+
const caughtChanged = dsVerifyTamper.diff.changed.some(
|
|
172
|
+
(c) => c.path === "corpus/mit-notes.txt"
|
|
173
|
+
);
|
|
174
|
+
check(
|
|
175
|
+
"dataset verify catches a one-byte TAMPER → MISMATCH, localized to the CHANGED file (CLI exit 3)",
|
|
176
|
+
dsVerifyTamper.status === "MISMATCH" && caughtChanged,
|
|
177
|
+
`status=${dsVerifyTamper.status} changed=[${dsVerifyTamper.diff.changed
|
|
178
|
+
.map((c) => c.path)
|
|
179
|
+
.join(", ")}]`
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
// report — ONE filed evidence document (identity + license roll-up + embedded verify + policy verdict).
|
|
183
|
+
// Run with BOTH gates against the strict policy: the report is the combined CI gate and reports FAIL.
|
|
184
|
+
const dsReport = path.join(work, "dataset.evidence.md");
|
|
185
|
+
const reportRes = dataset.runDatasetReport({
|
|
186
|
+
manifest: dsManifest,
|
|
187
|
+
verifyDir: SAMPLE_DATASET,
|
|
188
|
+
policy: POLICY_STRICT,
|
|
189
|
+
out: dsReport,
|
|
190
|
+
stdout: quiet,
|
|
191
|
+
});
|
|
192
|
+
artifacts.datasetReport = dsReport;
|
|
193
|
+
check(
|
|
194
|
+
"dataset report wrote ONE evidence document (verify=MATCH embedded, policy=FAIL embedded)",
|
|
195
|
+
fs.existsSync(dsReport) &&
|
|
196
|
+
reportRes.verifyStatus === "MATCH" &&
|
|
197
|
+
reportRes.policyVerdict === "FAIL",
|
|
198
|
+
`verify=${reportRes.verifyStatus} policy=${reportRes.policyVerdict}`
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// attest — emit the canonical UNSIGNED attestation bytes the human trust-root would sign.
|
|
202
|
+
const dsAttest = path.join(work, "dataset.attestation.json");
|
|
203
|
+
const attestRes = dataset.runDatasetAttest({
|
|
204
|
+
manifest: dsManifest,
|
|
205
|
+
out: dsAttest,
|
|
206
|
+
stdout: quiet,
|
|
207
|
+
});
|
|
208
|
+
artifacts.datasetAttestation = dsAttest;
|
|
209
|
+
check(
|
|
210
|
+
"dataset attest emitted canonical UNSIGNED bytes (signed:false) for the human trust-root to sign",
|
|
211
|
+
fs.existsSync(dsAttest) &&
|
|
212
|
+
attestRes.envelope.signed === false &&
|
|
213
|
+
typeof attestRes.canonical === "string" &&
|
|
214
|
+
attestRes.canonical.length > 0,
|
|
215
|
+
`signed=${attestRes.envelope.signed}`
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
out("");
|
|
219
|
+
|
|
220
|
+
// ===============================================================================================
|
|
221
|
+
// PART 2 — ProofParcel: the B2B data-delivery receipt pipeline.
|
|
222
|
+
// ===============================================================================================
|
|
223
|
+
out("== ProofParcel ==");
|
|
224
|
+
|
|
225
|
+
const pManifest = path.join(work, "parcel.manifest.json");
|
|
226
|
+
|
|
227
|
+
// build — tamper-evident delivery receipt (root + per-file leaves + UNTRUSTED parcel metadata).
|
|
228
|
+
const pBuild = parcel.runParcelBuild({
|
|
229
|
+
dir: SAMPLE_PARCEL,
|
|
230
|
+
out: pManifest,
|
|
231
|
+
parcel: {
|
|
232
|
+
parcelId: "EX-0001",
|
|
233
|
+
sender: "acme-data-co",
|
|
234
|
+
recipient: "globex-ml",
|
|
235
|
+
},
|
|
236
|
+
stdout: quiet,
|
|
237
|
+
});
|
|
238
|
+
artifacts.parcelManifest = pManifest;
|
|
239
|
+
check(
|
|
240
|
+
"parcel build produced a delivery receipt with a Merkle root over every delivered file",
|
|
241
|
+
/^0x[0-9a-fA-F]{64}$/.test(pBuild.root) && pBuild.fileCount === 3,
|
|
242
|
+
`root=${pBuild.root.slice(0, 14)}… files=${pBuild.fileCount}`
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
// verify (MATCH) — the recipient re-derives the root from the delivered bytes; must MATCH (CI exit 0).
|
|
246
|
+
const pVerifyOk = parcel.runParcelVerify({
|
|
247
|
+
dir: SAMPLE_PARCEL,
|
|
248
|
+
manifest: pManifest,
|
|
249
|
+
stdout: quiet,
|
|
250
|
+
});
|
|
251
|
+
check(
|
|
252
|
+
"parcel verify against the delivered files → MATCH (CLI exit 0)",
|
|
253
|
+
pVerifyOk.status === "MATCH",
|
|
254
|
+
`status=${pVerifyOk.status}`
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
// verify (TAMPER) — mutate ONE delivered file on a temp copy; verify must catch it as MISMATCH.
|
|
258
|
+
const pTamperDir = path.join(work, "tampered-parcel");
|
|
259
|
+
copyDir(SAMPLE_PARCEL, pTamperDir);
|
|
260
|
+
fs.appendFileSync(path.join(pTamperDir, "data", "records.csv"), "4,delta,400\n");
|
|
261
|
+
const pVerifyTamper = parcel.runParcelVerify({
|
|
262
|
+
dir: pTamperDir,
|
|
263
|
+
manifest: pManifest,
|
|
264
|
+
stdout: quiet,
|
|
265
|
+
});
|
|
266
|
+
const pCaughtChanged = pVerifyTamper.diff.changed.some((c) => c.path === "data/records.csv");
|
|
267
|
+
check(
|
|
268
|
+
"parcel verify catches a TAMPER → MISMATCH, localized to the CHANGED file (CLI exit 3)",
|
|
269
|
+
pVerifyTamper.status === "MISMATCH" && pCaughtChanged,
|
|
270
|
+
`status=${pVerifyTamper.status} changed=[${pVerifyTamper.diff.changed
|
|
271
|
+
.map((c) => c.path)
|
|
272
|
+
.join(", ")}]`
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
// attest — emit the canonical UNSIGNED parcel-attestation bytes the human trust-root would sign.
|
|
276
|
+
const pAttest = path.join(work, "parcel.attestation.json");
|
|
277
|
+
const pAttestRes = parcel.runParcelAttest({
|
|
278
|
+
manifest: pManifest,
|
|
279
|
+
out: pAttest,
|
|
280
|
+
stdout: quiet,
|
|
281
|
+
});
|
|
282
|
+
artifacts.parcelAttestation = pAttest;
|
|
283
|
+
check(
|
|
284
|
+
"parcel attest emitted canonical UNSIGNED bytes (signed:false) for the human trust-root to sign",
|
|
285
|
+
fs.existsSync(pAttest) &&
|
|
286
|
+
pAttestRes.envelope.signed === false &&
|
|
287
|
+
typeof pAttestRes.canonical === "string" &&
|
|
288
|
+
pAttestRes.canonical.length > 0,
|
|
289
|
+
`signed=${pAttestRes.envelope.signed}`
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
// ===============================================================================================
|
|
293
|
+
// Summary + the human-gated trust-root handoff (referenced, NOT executed).
|
|
294
|
+
// ===============================================================================================
|
|
295
|
+
out("");
|
|
296
|
+
hr();
|
|
297
|
+
out("Produced artifacts (all under the temp workspace — none in the repo):");
|
|
298
|
+
for (const [k, v] of Object.entries(artifacts)) out(` ${k}: ${v}`);
|
|
299
|
+
out("");
|
|
300
|
+
out("NEXT — the HUMAN-OWNED trust-root steps (NOT run here; they need a key / a TSA / an RPC):");
|
|
301
|
+
out(
|
|
302
|
+
" vh dataset sign " +
|
|
303
|
+
dsAttest +
|
|
304
|
+
" --key-file <YOUR-KEY> # sign the UNSIGNED bytes with a key YOU provisioned"
|
|
305
|
+
);
|
|
306
|
+
out(
|
|
307
|
+
" vh dataset timestamp-request " +
|
|
308
|
+
dsManifest +
|
|
309
|
+
" # then stamp the digest at an RFC-3161 TSA"
|
|
310
|
+
);
|
|
311
|
+
out(
|
|
312
|
+
" vh parcel sign " +
|
|
313
|
+
pAttest +
|
|
314
|
+
" --key-file <YOUR-KEY> # same handoff for the parcel attestation"
|
|
315
|
+
);
|
|
316
|
+
out(
|
|
317
|
+
" (on-chain anchor is a deploy + real funds — also needs-human, P-2 in STRATEGY.md)"
|
|
318
|
+
);
|
|
319
|
+
out(
|
|
320
|
+
' These convert "the bytes are these" into "signed/stamped by a trusted party at time T". The'
|
|
321
|
+
);
|
|
322
|
+
out(" example deliberately stops at the unsigned bytes — that boundary is the product's honesty line.");
|
|
323
|
+
hr();
|
|
324
|
+
|
|
325
|
+
const failed = checks.filter((c) => !c.ok);
|
|
326
|
+
if (failed.length === 0) {
|
|
327
|
+
out(`RESULT: PASS — all ${checks.length} pipeline checks passed.`);
|
|
328
|
+
} else {
|
|
329
|
+
out(`RESULT: FAIL — ${failed.length} of ${checks.length} checks failed:`);
|
|
330
|
+
for (const c of failed) out(` - ${c.label}`);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return {
|
|
334
|
+
code: failed.length === 0 ? 0 : 1,
|
|
335
|
+
work,
|
|
336
|
+
artifacts,
|
|
337
|
+
checks: checks.slice(),
|
|
338
|
+
// A temp workspace is OURS to delete on success; a caller-chosen VH_EXAMPLE_OUT is theirs to keep.
|
|
339
|
+
ownsWorkspace: !(process.env.VH_EXAMPLE_OUT && process.env.VH_EXAMPLE_OUT.trim()),
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Run, then clean up the temp workspace (unless the caller asked to keep it or chose their own --out).
|
|
344
|
+
if (require.main === module) {
|
|
345
|
+
let result;
|
|
346
|
+
try {
|
|
347
|
+
result = main();
|
|
348
|
+
} catch (e) {
|
|
349
|
+
process.stderr.write("example crashed: " + (e && e.stack ? e.stack : e) + "\n");
|
|
350
|
+
process.exit(1);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
const keep = process.env.VH_EXAMPLE_KEEP === "1";
|
|
354
|
+
if (result.work && result.ownsWorkspace && !keep) {
|
|
355
|
+
try {
|
|
356
|
+
fs.rmSync(result.work, { recursive: true, force: true });
|
|
357
|
+
} catch {
|
|
358
|
+
/* best-effort cleanup; never fail the example over it */
|
|
359
|
+
}
|
|
360
|
+
} else if (result.work) {
|
|
361
|
+
out(`(kept workspace: ${result.work})`);
|
|
362
|
+
}
|
|
363
|
+
process.exit(result.code);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
module.exports = { main };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Sample training dataset for the verifyhash / DataLedger end-to-end example.
|
|
2
|
+
|
|
3
|
+
This directory stands in for a real AI training dataset. It is intentionally tiny
|
|
4
|
+
(a handful of small text files, no secrets, no large blobs) so the example runs in
|
|
5
|
+
well under a second and the whole pipeline is auditable by eye.
|
|
6
|
+
|
|
7
|
+
The per-file source/license hints live in ../sample-dataset.hints.json. Those hints
|
|
8
|
+
are UNTRUSTED, self-asserted metadata: they are NOT bound into the Merkle root and
|
|
9
|
+
prove nothing on their own. The example's policy check is a gate on those CLAIMS,
|
|
10
|
+
not a verification that any license is genuinely correct.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
A vendored snippet whose license hint is GPL-3.0.
|
|
2
|
+
|
|
3
|
+
This file exists ON PURPOSE so the example's strict policy (which denies copyleft
|
|
4
|
+
licenses) has a real violation to flag. A proprietary-product org policy that lists
|
|
5
|
+
GPL-3.0 in denyLicenses will FAIL the dataset on this file.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"README.txt": { "source": "verifyhash-examples", "license": "MIT" },
|
|
3
|
+
"corpus/mit-notes.txt": { "source": "verifyhash-examples", "license": "MIT" },
|
|
4
|
+
"corpus/cc-by-poem.txt": { "source": "verifyhash-examples", "license": "CC-BY-4.0" },
|
|
5
|
+
"vendored/gpl-snippet.txt": { "source": "third-party-vendor", "license": "GPL-3.0" },
|
|
6
|
+
"data/unlabeled.txt": { "source": "verifyhash-examples" }
|
|
7
|
+
}
|