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.
- package/LICENSE +201 -0
- package/README.md +883 -0
- package/cli/abi/ContributionRegistry.json +881 -0
- package/cli/agent.js +2173 -0
- package/cli/anchor-artifact.js +853 -0
- package/cli/anchor.js +400 -0
- package/cli/claim.js +881 -0
- package/cli/core/agent-commit.js +448 -0
- package/cli/core/agent-session.js +598 -0
- package/cli/core/anchor-binding.js +663 -0
- package/cli/core/attestation.js +580 -0
- package/cli/core/evidence-plans.js +495 -0
- package/cli/core/fixtures/evidence-plans/baseline.json +19 -0
- package/cli/core/fulfill-intake.js +1082 -0
- package/cli/core/go-live-preflight.js +481 -0
- package/cli/core/license.js +534 -0
- package/cli/core/manifest.js +243 -0
- package/cli/core/packetseal.js +591 -0
- package/cli/core/registryArtifact.js +49 -0
- package/cli/core/revocation.js +539 -0
- package/cli/core/rfc3161.js +389 -0
- package/cli/core/timestamp.js +482 -0
- package/cli/core/trust-asof.js +479 -0
- package/cli/dataset.js +2950 -0
- package/cli/evidence.js +2227 -0
- package/cli/fulfill-webhook-http.js +438 -0
- package/cli/git.js +220 -0
- package/cli/hash.js +550 -0
- package/cli/identity.js +1072 -0
- package/cli/journal-cli.js +1110 -0
- package/cli/journal-log.js +454 -0
- package/cli/journal.js +334 -0
- package/cli/lineage.js +447 -0
- package/cli/list.js +287 -0
- package/cli/parcel.js +1509 -0
- package/cli/proof.js +578 -0
- package/cli/prove.js +300 -0
- package/cli/receipt.js +631 -0
- package/cli/registry.js +331 -0
- package/cli/reputation.js +344 -0
- package/cli/revocation.js +495 -0
- package/cli/serve-verify-http.js +298 -0
- package/cli/serve-verify.js +333 -0
- package/cli/show.js +339 -0
- package/cli/verify.js +383 -0
- package/cli/vh.js +3927 -0
- package/docs/ADOPT.md +183 -0
- package/docs/ADOPTION.json +11 -0
- package/docs/AGENTTRACE.md +247 -0
- package/docs/ANCHORING.md +167 -0
- package/docs/AUDIT.md +55 -0
- package/docs/CONFORMANCE.md +107 -0
- package/docs/DATALEDGER.md +638 -0
- package/docs/DECIDE.md +47 -0
- package/docs/DECISIONS-PENDING.md +27 -0
- package/docs/DEPLOY-PUBLIC-SITE.md +301 -0
- package/docs/ENGINE-LEDGER.json +12 -0
- package/docs/EVIDENCE.md +519 -0
- package/docs/GO-LIVE.md +66 -0
- package/docs/IDENTITY.md +123 -0
- package/docs/INDEPENDENT-VERIFICATION.md +377 -0
- package/docs/INTEGRITY-JOURNAL.md +337 -0
- package/docs/KEY-LIFECYCLE.md +179 -0
- package/docs/LICENSING.md +46 -0
- package/docs/LINEAGE.md +307 -0
- package/docs/LOOP-AUDIT-2026-07-03.json +580 -0
- package/docs/LOOP-HARDENING-PLAN.md +44 -0
- package/docs/MERKLE-LEAVES.md +113 -0
- package/docs/METRICS.jsonl +31 -0
- package/docs/MORNING.md +204 -0
- package/docs/PILOT.md +444 -0
- package/docs/PROOFPARCEL.md +227 -0
- package/docs/PROOFS.md +262 -0
- package/docs/RECEIPTS.md +341 -0
- package/docs/REPUTATION.md +158 -0
- package/docs/SDK.md +301 -0
- package/docs/STRATEGY-ARCHIVE.md +5055 -0
- package/docs/SUPERVISOR-RUNBOOK.md +52 -0
- package/docs/TRUST-BOUNDARIES.md +335 -0
- package/docs/TRUSTLEDGER.md +1976 -0
- package/docs/USAGE-BUDGET.json +121 -0
- package/docs/VERIFY-SERVICE.md +168 -0
- package/index.js +160 -0
- package/package.json +41 -0
- package/trustledger/build-standalone.js +796 -0
- package/trustledger/cli.js +3179 -0
- package/trustledger/close.js +391 -0
- package/trustledger/corpus.js +159 -0
- package/trustledger/dist/BUILD-PROVENANCE.json +99 -0
- package/trustledger/dist/trustledger-standalone.html +6197 -0
- package/trustledger/dist/trustledger-standalone.html.sha256 +1 -0
- package/trustledger/door-core.js +442 -0
- package/trustledger/fixtures/bank.csv +7 -0
- package/trustledger/fixtures/bank.malformed.csv +3 -0
- package/trustledger/fixtures/bank.noalias.csv +5 -0
- package/trustledger/fixtures/bank.ofx +34 -0
- package/trustledger/fixtures/bank.real.csv +5 -0
- package/trustledger/fixtures/corpus/_shared/prior-close.json +22 -0
- package/trustledger/fixtures/corpus/bank-book-mismatch--benign-twin/inputs.json +14 -0
- package/trustledger/fixtures/corpus/bank-book-mismatch--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/bank-book-mismatch--out-of-trust/inputs.json +14 -0
- package/trustledger/fixtures/corpus/bank-book-mismatch--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/corpus/continuity-break--benign-twin/inputs.json +15 -0
- package/trustledger/fixtures/corpus/continuity-break--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/continuity-break--out-of-trust/inputs.json +15 -0
- package/trustledger/fixtures/corpus/continuity-break--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/corpus/negative-tenant-ledger--benign-twin/inputs.json +13 -0
- package/trustledger/fixtures/corpus/negative-tenant-ledger--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/negative-tenant-ledger--out-of-trust/inputs.json +13 -0
- package/trustledger/fixtures/corpus/negative-tenant-ledger--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/corpus/owner-overdraw--benign-twin/inputs.json +15 -0
- package/trustledger/fixtures/corpus/owner-overdraw--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/owner-overdraw--out-of-trust/inputs.json +15 -0
- package/trustledger/fixtures/corpus/owner-overdraw--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/corpus/security-deposit-segregation--benign-twin/inputs.json +16 -0
- package/trustledger/fixtures/corpus/security-deposit-segregation--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/security-deposit-segregation--out-of-trust/inputs.json +13 -0
- package/trustledger/fixtures/corpus/security-deposit-segregation--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/corpus/subledger-out-of-balance--benign-twin/inputs.json +13 -0
- package/trustledger/fixtures/corpus/subledger-out-of-balance--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/subledger-out-of-balance--out-of-trust/inputs.json +13 -0
- package/trustledger/fixtures/corpus/subledger-out-of-balance--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/e2e/bank.aliased.csv +4 -0
- package/trustledger/fixtures/e2e/bank.csv +4 -0
- package/trustledger/fixtures/e2e/bank.nsf.csv +4 -0
- package/trustledger/fixtures/e2e/quickbooks.csv +6 -0
- package/trustledger/fixtures/e2e/quickbooks.nsf.csv +8 -0
- package/trustledger/fixtures/e2e/rentroll.csv +6 -0
- package/trustledger/fixtures/e2e/rentroll.nsf.csv +8 -0
- package/trustledger/fixtures/e2e/rentroll.short.csv +5 -0
- package/trustledger/fixtures/plans/baseline.json +25 -0
- package/trustledger/fixtures/plans/price-binding.example.json +27 -0
- package/trustledger/fixtures/policy/ambiguous-deposit-example.json +12 -0
- package/trustledger/fixtures/policy/baseline.json +19 -0
- package/trustledger/fixtures/policy/ca-example.json +12 -0
- package/trustledger/fixtures/policy/negative-tenant-ledger-example.json +12 -0
- package/trustledger/fixtures/policy/owner-overdraw-example.json +12 -0
- package/trustledger/fixtures/quickbooks.csv +7 -0
- package/trustledger/fixtures/quickbooks.real.csv +5 -0
- package/trustledger/fixtures/rentroll.csv +6 -0
- package/trustledger/fixtures/rentroll.real.csv +4 -0
- package/trustledger/ingest.js +1163 -0
- package/trustledger/lib/policy-bundled-loader.js +44 -0
- package/trustledger/lib/sha256-vendored.js +227 -0
- package/trustledger/license.js +563 -0
- package/trustledger/match.js +551 -0
- package/trustledger/plans.js +551 -0
- package/trustledger/policy.js +398 -0
- package/trustledger/public/index.html +512 -0
- package/trustledger/reconcile.js +1486 -0
- package/trustledger/report.js +887 -0
- package/trustledger/seal.js +854 -0
- package/trustledger/server.js +391 -0
- package/trustledger/valueproof.js +350 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_doc": "Loop spend governor. The /loop SUPERVISOR (not the engine) reads & enforces this at each relaunch boundary. A run is atomic once launched; cap is checked BEFORE launching the next. Tune any field freely.",
|
|
3
|
+
"windowDays": 7,
|
|
4
|
+
"windowStartIso": "2026-07-01T17:44:01Z",
|
|
5
|
+
"windowStartEpoch": 1782927841,
|
|
6
|
+
"windowResetIso": "2026-07-08T17:44:01Z",
|
|
7
|
+
"ceilingTokens": 120000000,
|
|
8
|
+
"cooldownSeconds": 7200,
|
|
9
|
+
"atCap": "pause-and-wait",
|
|
10
|
+
"estPerRunTokens": 5308720,
|
|
11
|
+
"spentTokens": 24638756,
|
|
12
|
+
"runs": [
|
|
13
|
+
{
|
|
14
|
+
"runId": "wf_7247856c-81c",
|
|
15
|
+
"tokens": 4337689,
|
|
16
|
+
"endEpoch": 1782332559,
|
|
17
|
+
"endIso": "2026-06-24T20:22:39Z"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"runId": "wf_231b8535-d68",
|
|
21
|
+
"tokens": 5308720,
|
|
22
|
+
"endEpoch": 1782354321,
|
|
23
|
+
"endIso": "2026-06-25T02:25:21Z"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"runId": "wf_1e4dd9f7-e36",
|
|
27
|
+
"tokens": 4570608,
|
|
28
|
+
"endEpoch": 1782374753,
|
|
29
|
+
"endIso": "2026-06-25T08:05:53Z"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"runId": "wf_574564f9-394",
|
|
33
|
+
"tokens": 4611799,
|
|
34
|
+
"endEpoch": 1782394073,
|
|
35
|
+
"endIso": "2026-06-25T13:27:53Z"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"runId": "wf_5df912fe-b52",
|
|
39
|
+
"tokens": 5395384,
|
|
40
|
+
"endEpoch": 1782418083,
|
|
41
|
+
"endIso": "2026-06-25T20:08:03Z"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"runId": "wf_4c16c5a7-142",
|
|
45
|
+
"tokens": 4717447,
|
|
46
|
+
"endEpoch": 1782435921,
|
|
47
|
+
"endIso": "2026-06-26T01:05:21Z"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"runId": "wf_c1d79f30-b27",
|
|
51
|
+
"tokens": 4789932,
|
|
52
|
+
"endEpoch": 1782454664,
|
|
53
|
+
"endIso": "2026-06-26T06:17:44Z"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"runId": "wf_63748963-3e7",
|
|
57
|
+
"tokens": 7723935,
|
|
58
|
+
"endEpoch": 1782482715,
|
|
59
|
+
"endIso": "2026-06-26T14:05:15Z"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"runId": "wf_6cd34703-102",
|
|
63
|
+
"tokens": 3913754,
|
|
64
|
+
"endEpoch": 1782502218,
|
|
65
|
+
"endIso": "2026-06-26T19:30:18Z"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"runId": "wf_eca980df-3d0",
|
|
69
|
+
"tokens": 12141525,
|
|
70
|
+
"tokensEstimated": true,
|
|
71
|
+
"note": "TaskStopped at user request (token-saving) on its 6th task; tokens estimated from transcript (output+input+cache_creation, excl. cache-reads)",
|
|
72
|
+
"endEpoch": 1782526595,
|
|
73
|
+
"endIso": "2026-06-27T02:16:35Z"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"runId": "wf_7265354b-ac9",
|
|
77
|
+
"tokens": 5660334,
|
|
78
|
+
"endEpoch": 1782890998,
|
|
79
|
+
"endIso": "2026-07-01T07:29:58Z",
|
|
80
|
+
"note": "8 tasks all VERIFIED (SDK-adoption arc T-56.2..T-59.1); Architect promoted engine #22 (usefulness min()->median); tokens authoritative from subagent_tokens"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"runId": "wf_5b304124-b3d",
|
|
84
|
+
"tokens": 2859078,
|
|
85
|
+
"endEpoch": 1782908347,
|
|
86
|
+
"endIso": "2026-07-01T12:19:07Z",
|
|
87
|
+
"note": "verify-as-a-service line: T-59.2 (serve-verify HTTP) + T-59.3 (CI drop-in) + T-60.1 (integrity-journal CORE) all VERIFIED/committed; T-60.2 (journal CLI verb) built+reviewed but its Critique panel, commit, and the run's Report/Manager/Architect phases FAILED on the subscription WEEKLY LIMIT ('resets 2pm UTC'). Engine unchanged (engineUpgrade none, md5 0bc9bbc9). tokens authoritative from subagent_tokens. endEpoch estimated (notification epoch lost across compaction; cooldown satisfied regardless)."
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"runId": "wf_c9795182-acb",
|
|
91
|
+
"tokens": 6283496,
|
|
92
|
+
"endEpoch": 1782955200,
|
|
93
|
+
"endIso": "2026-07-02T01:20:00Z",
|
|
94
|
+
"note": "FINAL run before intentional loop STOP (user restarting session to enable Fable 5). ranTasks 6: VERIFIED T-60.3, T-61.3, T-62.1, T-62.2 (4); BLOCKED T-61.1 (auto-build x3) + T-63.1 (targeted 33/33 but full suite RED -> reverted, tree confirmed green 3314 passing). Strategist invented EPIC-61/62/63/64; EPIC-64 self-PARKED per qualityStall. engineUpgrade none; Manager/Reporter/Architect died on API 529 Overloaded so METRICS.jsonl line NOT written and these planning files were committed manually. tokens authoritative from subagent_tokens (agent_count 92, ~8.3h). WINDOW ROLLED at 2026-07-01T17:44:01Z: this is the only run in the new window, so spentTokens reset to 6283496; prior-window runs remain above for audit (pre-roll file in git history)."
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"runId": "wf_07ba35e7-d72",
|
|
98
|
+
"tokens": 5192993,
|
|
99
|
+
"endEpoch": 1782981244,
|
|
100
|
+
"endIso": "2026-07-02T08:34:04Z",
|
|
101
|
+
"note": "FIRST FABLE-TRIAL run (engine #23): 8/8 VERIFIED, 0 blocked, ALL on attempt 1 — fableFirstShots 8/8 (100%) vs Opus baseline ~33%; avgUsefulness 3.88 (prior 3.38); converted both previously-BLOCKED tasks (T-61.1, T-63.1); completed EPIC-63 transparency log end-to-end; Strategist (Fable) invented EPIC-65 zero-install browser TrustLedger (T-65.1/2/3 shipped); Manager made 1 team change; Fable Architect shipped engine #24 (dissenter-only re-score = efficiency-audit finding #5), independently re-gated PASS+SMOKE-PASS, model pins intact. CHEAPER (5.19M vs 6.28M tokens) and FASTER (5.2h vs 8.3h) than the prior all-Opus run. 1 agent errored (absorbed). tokens authoritative from subagent_tokens."
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"runId": "wf_1f634aeb-b9f",
|
|
105
|
+
"tokens": 7286045,
|
|
106
|
+
"endEpoch": 1783018585,
|
|
107
|
+
"endIso": "2026-07-02T18:56:25Z",
|
|
108
|
+
"note": "FABLE-TRIAL run 2 (engine #24): 8/8 VERIFIED, 0 blocked, ALL attempt 1 — fableFirstShots 8/8 (100%) AGAIN (cumulative 16/16 vs ~33% Opus baseline); avgUsefulness 4.13 / min 4 (best ever; 3.38->3.88->4.13); rework 4 with 0 reverted; 97/97 agents clean. Shipped: EPIC-66 browser verifier (in-memory seam + offline HTML page w/ 60s challenge, usefulness 5, + funnel wiring), EPIC-67 site-release assembler + drift visibility (--diff/--mark-deployed vs DEPLOYED.json), NEW Strategist-invented EPIC-68 AGENTTRACE (agent-session provenance: pure core + vh agent CLI w/ license-gated paid sign + zero-install verify). Manager: 2 team changes (TrustIntegrity AGENTTRACE clause). engineUpgrade none (md5 88bf8ee0 unchanged). tokens authoritative from subagent_tokens."
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"runId": "wf_72ed879b-35c",
|
|
112
|
+
"tokens": 5876222,
|
|
113
|
+
"endEpoch": 1783054303,
|
|
114
|
+
"endIso": "2026-07-03T04:51:43Z",
|
|
115
|
+
"note": "FINAL run of the session; TaskStopped mid-8th-build by user to save weekly Fable usage (run was ~6.4h, unusually long). 7 tasks VERIFIED/committed (EPIC-69 T-69.1/2/3, EPIC-70 T-70.1/2/3 + one more); abandoned in-flight task git-stashed (recoverable). tokens AUTHORITATIVE from the workflow state json totalTokens (79 agents), NOT an estimate. Architect phase never reached, so NO engine swap this run (live md5 88bf8ee0 unchanged). Loop STOPPED after this per user; no relaunch until explicit go."
|
|
116
|
+
}
|
|
117
|
+
],
|
|
118
|
+
"note": "Max $200 plan, weekly allowance >> 40M. Cooldown is the primary lever; ceiling is a safe backstop (pause-at-cap, so erring low is harmless). Raise ceilingTokens or lower cooldownSeconds to go faster.",
|
|
119
|
+
"lastRunEndEpoch": 1783054303,
|
|
120
|
+
"lastRunEndIso": "2026-07-03T04:51:43Z"
|
|
121
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# verifyhash verify-service — the drop-in HTTP verify endpoint (`vh serve-verify`)
|
|
2
|
+
|
|
3
|
+
`vh serve-verify` stands the **verify** half of verifyhash up as a tiny, dependency-free HTTP service so a
|
|
4
|
+
CI pipeline or another microservice can **POST a seal and get an ACCEPT/REJECT** — the *"CI plugin that
|
|
5
|
+
imports rather than shells out"*. It is a **drop-in dependency**: boot it once, POST many seals, read the
|
|
6
|
+
JSON verdict, gate your build on the HTTP status. It reuses the **exact same** verify cores the `vh` CLI and
|
|
7
|
+
the `require("verifyhash")` SDK run (`verifySeal` / `verifySignedSeal`) — no fork, no second implementation.
|
|
8
|
+
|
|
9
|
+
This file is the **canonical reference** for the service's request schema, response fields, status mapping,
|
|
10
|
+
and trust boundary. It is **machine-checked**: `test/verify-service.example.test.js` byte-matches the request
|
|
11
|
+
`kind`s and response fields documented here against the live `cli/serve-verify.js › verifyRequest` core, so
|
|
12
|
+
this doc **cannot silently drift** from the code.
|
|
13
|
+
|
|
14
|
+
> **Booting it (a human deploy step).** The loop only BUILDS + locally TESTS. Exposing this service publicly
|
|
15
|
+
> (behind *your* nginx/Cloudflare, on *your* domain, with TLS) is an explicit **human** deploy step — see
|
|
16
|
+
> **STRATEGY.md P-9**. By default it binds **loopback** (`127.0.0.1`) and is never auto-deployed.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Boot the service
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
vh serve-verify [--port <n>] [--host <h>] [--max-body <bytes>]
|
|
24
|
+
# default: 127.0.0.1:4180 — loopback only, verify-only, Node-core http (ZERO new dependency)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
- `POST /verify` → a JSON verdict on a CI-mappable status.
|
|
28
|
+
- `GET /healthz` → `{ ok: true, ... }` (a liveness/readiness probe; holds no key, touches nothing).
|
|
29
|
+
|
|
30
|
+
The server is **verify-only**: it never signs, holds **no** private key, and writes **no** file.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Request schema
|
|
35
|
+
|
|
36
|
+
Every request is a single JSON object with a `kind` field that selects one of the two verify paths. The
|
|
37
|
+
schema envelope version is **`vh.verify-request/1`** (the `schema` field of every response, below).
|
|
38
|
+
|
|
39
|
+
### `kind: "verify-seal"` — UNSIGNED tamper-evidence
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"kind": "verify-seal",
|
|
44
|
+
"seal": { "...": "a seal object OR its serialized JSON string" },
|
|
45
|
+
"entries": [
|
|
46
|
+
{ "relPath": "dist/app.js", "content": "<encoded bytes>", "encoding": "base64" }
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- `seal` — a seal **object** *or* its serialized JSON **string** (both accepted; strictly validated first).
|
|
52
|
+
- `entries[]` — the bytes to re-verify against the seal, each `{ relPath, content, encoding }` where
|
|
53
|
+
`encoding` is one of **`utf8`**, **`base64`**, or **`hex`** (default `utf8`). The service **re-derives**
|
|
54
|
+
the Merkle root from *these* bytes — never the seal's own stored hashes — so a one-byte tamper flips
|
|
55
|
+
`ACCEPTED` → `REJECTED`.
|
|
56
|
+
|
|
57
|
+
### `kind: "verify-signed-seal"` — SIGNED / vendor-address-pinned
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"kind": "verify-signed-seal",
|
|
62
|
+
"container": { "...": "a signed-seal container OR its JSON string" },
|
|
63
|
+
"expectedSigner": "0x<20-byte address>",
|
|
64
|
+
"entries": [
|
|
65
|
+
{ "relPath": "dist/app.js", "content": "<encoded bytes>", "encoding": "base64" }
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- `container` — a signed-seal **object** *or* its JSON **string** (strictly validated first).
|
|
71
|
+
- `expectedSigner` *(optional)* — a `0x` address the recovered signer must equal (the vendor **pin**). A
|
|
72
|
+
genuine signature that recovers to a *different* address is a **REJECTED**, not an error.
|
|
73
|
+
- `entries[]` *(optional)* — when supplied, the canonical seal bytes are recomputed from these entries and
|
|
74
|
+
required byte-identical to the signed payload (a set that does not match is a clean **REJECTED**).
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Response schema
|
|
79
|
+
|
|
80
|
+
### OK response (the request was evaluated)
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"schema": "vh.verify-request/1",
|
|
85
|
+
"service": "vh-serve-verify",
|
|
86
|
+
"verdict": "ACCEPTED",
|
|
87
|
+
"kind": "verify-seal",
|
|
88
|
+
"detail": { "verdict": "ACCEPTED", "accepted": true, "...": "the core verdict, fields unchanged" }
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- `schema` — the envelope version, always **`vh.verify-request/1`**.
|
|
93
|
+
- `service` — always **`vh-serve-verify`**.
|
|
94
|
+
- `verdict` — the top-level answer: **`ACCEPTED`** or **`REJECTED`** (copied verbatim from `detail.verdict`,
|
|
95
|
+
never re-derived).
|
|
96
|
+
- `kind` — the request kind that was dispatched.
|
|
97
|
+
- `detail` — the **unchanged** core verdict from `verifySeal` / `verifySignedSeal`. Its fields are the same
|
|
98
|
+
contract the CLI and SDK already ship (e.g. `accepted`, `rootMatches`, `counts`, `changed` for a seal;
|
|
99
|
+
`recoveredSigner`, `checks`, `failedChecks` for a signed container).
|
|
100
|
+
|
|
101
|
+
### ERROR response (the request could **not** be evaluated)
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"schema": "vh.verify-request/1",
|
|
106
|
+
"service": "vh-serve-verify",
|
|
107
|
+
"verdict": "ERROR",
|
|
108
|
+
"code": "ERR_UNKNOWN_KIND",
|
|
109
|
+
"message": "a human-readable, non-sensitive reason"
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
An `ERROR` is **fail-closed**: a malformed / oversized / unknown request is **never** a silent `ACCEPTED`.
|
|
114
|
+
`verdict` is **`ERROR`**, `code` is a stable machine-readable string, and `message` is a human reason. The
|
|
115
|
+
stable error `code`s are: `ERR_BODY_NOT_OBJECT`, `ERR_BODY_TOO_LARGE`, `ERR_UNKNOWN_KIND`,
|
|
116
|
+
`ERR_MISSING_SEAL`, `ERR_BAD_SEAL`, `ERR_BAD_ENTRIES`, `ERR_MISSING_CONTAINER`, `ERR_BAD_CONTAINER`,
|
|
117
|
+
`ERR_BAD_EXPECTED_SIGNER`, `ERR_INTERNAL`.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Status mapping (CI-mappable — gate on the code alone)
|
|
122
|
+
|
|
123
|
+
| Verdict / condition | HTTP status | Meaning for a CI gate |
|
|
124
|
+
| -------------------------------------------- | ----------- | ---------------------------------------------- |
|
|
125
|
+
| `ACCEPTED` | **200** | the seal/container verified → **pass** |
|
|
126
|
+
| `REJECTED` | **422** | well-formed request that did NOT verify → fail |
|
|
127
|
+
| `ERROR` (malformed / unknown request) | **400** | the request itself is bad → fail |
|
|
128
|
+
| body over `--max-body` | **413** | payload too large → fail |
|
|
129
|
+
| wrong path / wrong method | **404/405** | routing error → fail |
|
|
130
|
+
|
|
131
|
+
A build should gate on **HTTP 200** exactly: anything else is a non-pass. The shipped
|
|
132
|
+
[`verifier/ci/verify-service.generic.sh`](../verifier/ci/verify-service.generic.sh) and
|
|
133
|
+
[`verifier/ci/verify-service.github-actions.yml`](../verifier/ci/verify-service.github-actions.yml) do
|
|
134
|
+
exactly this (curl POST → map the HTTP status to a non-zero exit on anything but 200).
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Trust boundary (the service will not let you overclaim)
|
|
139
|
+
|
|
140
|
+
A **200 ACCEPTED** for a seal means **tamper-evidence**: *these exact bytes re-derive the sealed Merkle
|
|
141
|
+
root.* A valid **signature** (`verify-signed-seal`) additionally proves **who vouched** — the holder of the
|
|
142
|
+
pinned address's key — for those bytes. Neither of these:
|
|
143
|
+
|
|
144
|
+
- is a **trusted timestamp** ("sealed / signed since date *T*") — that rides the **human-owned** signing /
|
|
145
|
+
timestamp / anchor trust-root (`needs-human`; **STRATEGY.md P-3**);
|
|
146
|
+
- is a **legal opinion**.
|
|
147
|
+
|
|
148
|
+
The service is **verify-only**: it never signs, holds **no** private key, and writes **no** file. It binds
|
|
149
|
+
**loopback** (`127.0.0.1`) by default; exposing it publicly is a **human** deploy step (your nginx /
|
|
150
|
+
Cloudflare / domain / TLS) — **never** auto-deployed. See [`docs/TRUST-BOUNDARIES.md`](./TRUST-BOUNDARIES.md).
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Drop it into your pipeline
|
|
155
|
+
|
|
156
|
+
- **Any shell CI** (GitLab, CircleCI, Jenkins, Makefile): boot the service, then run
|
|
157
|
+
[`verifier/ci/verify-service.generic.sh`](../verifier/ci/verify-service.generic.sh) with
|
|
158
|
+
`VH_VERIFY_URL` + `VH_REQUEST` (the path to a prepared request-body JSON).
|
|
159
|
+
- **GitHub Actions**: copy
|
|
160
|
+
[`verifier/ci/verify-service.github-actions.yml`](../verifier/ci/verify-service.github-actions.yml) — it
|
|
161
|
+
boots `vh serve-verify`, builds a request body with the SDK, POSTs it, and fails the job on anything but
|
|
162
|
+
200.
|
|
163
|
+
- **A tiny dependency-free client**: [`examples/verify-service-client.js`](../examples/verify-service-client.js)
|
|
164
|
+
boots the service, POSTs a clean seal (ACCEPT) then a tampered one (REJECT), and exits 0 — copy it as your
|
|
165
|
+
in-process integration. It imports **only** `require("verifyhash")`, the `vh` command, and Node built-ins.
|
|
166
|
+
|
|
167
|
+
This doc, the example, and the CI scripts are all test-gated by `test/verify-service.example.test.js` on
|
|
168
|
+
every `npx hardhat test`, so they can never silently rot.
|
package/index.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// index.js — the SINGLE, documented, semver-guarded PUBLIC API entrypoint for verifyhash (T-57.1).
|
|
4
|
+
//
|
|
5
|
+
// WHAT THIS IS
|
|
6
|
+
// A THIN re-export of the already-built, already-tested core so a downstream program can `require`
|
|
7
|
+
// verifyhash as a library and get the EXACT SAME functions the `vh` CLI runs — no fork, no second
|
|
8
|
+
// implementation, no new crypto or mechanism introduced here. Every symbol below is the SAME function
|
|
9
|
+
// object exported by its `cli/…` source module (an identity re-export, asserted by the tests). If the
|
|
10
|
+
// CLI's behavior changes, this API changes with it automatically, because it IS the CLI's code.
|
|
11
|
+
//
|
|
12
|
+
// The embedded ("SDK") path and the CLI path are therefore the same code path: a seal built via
|
|
13
|
+
// `sdk.buildSeal(...)` verifies (ACCEPT) with `sdk.verifySeal(...)`, and a one-byte tamper is REJECTED
|
|
14
|
+
// — identical to `vh evidence seal` / `vh evidence verify`. The SAME holds for the SIGNED / vendor-pinned
|
|
15
|
+
// path: a seal signed via `sdk.signSealWith(...)` verifies (ACCEPT) with `sdk.verifySignedSeal(...)` under
|
|
16
|
+
// the matching expected signer, and a WRONG expected signer or a one-byte-tampered container is REJECTED
|
|
17
|
+
// — byte-identical to `vh evidence verify-signed`. So an embedder can verify a SIGNED, address-pinned
|
|
18
|
+
// packet in-process, with no shell-out to the `vh` binary.
|
|
19
|
+
//
|
|
20
|
+
// TRUST BOUNDARY (unchanged from the CLI — this wrapper adds nothing)
|
|
21
|
+
// A seal proves TAMPER-EVIDENCE + OFFLINE RE-COMPUTE ("these exact bytes are what was sealed"), NOT a
|
|
22
|
+
// trusted timestamp and NOT who authored the bytes. Signing/timestamping still ride the human-owned
|
|
23
|
+
// trust-root. `verifySeal` RE-DERIVES the Merkle root from the bytes YOU supply — never the seal's own
|
|
24
|
+
// stored hashes. See docs/TRUST-BOUNDARIES.md.
|
|
25
|
+
//
|
|
26
|
+
// STABILITY / SEMVER
|
|
27
|
+
// This module is the package's stability contract. `apiVersion` mirrors `package.json`'s version and
|
|
28
|
+
// is the semver-guarded surface: anything re-exported here is a PUBLIC symbol whose removal or breaking
|
|
29
|
+
// change requires a semver-major bump; symbols NOT re-exported here (deep `cli/…` internals) carry no
|
|
30
|
+
// stability guarantee. Add to this surface deliberately.
|
|
31
|
+
|
|
32
|
+
const pkg = require("./package.json");
|
|
33
|
+
|
|
34
|
+
// ---- Source modules (the already-built core; NOT re-implemented here) -------------------------------
|
|
35
|
+
// The seal SDK is bound to a REAL product config (`vh evidence`) over the generic packetseal core, so
|
|
36
|
+
// the embedded path is byte-identical to the CLI seal path.
|
|
37
|
+
const evidence = require("./cli/evidence");
|
|
38
|
+
const receipt = require("./cli/receipt");
|
|
39
|
+
const packetseal = require("./cli/core/packetseal");
|
|
40
|
+
const attestation = require("./cli/core/attestation");
|
|
41
|
+
const hash = require("./cli/hash");
|
|
42
|
+
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// THE PUBLIC SURFACE — each value is the SAME function object as its `cli/…` source (identity re-export).
|
|
45
|
+
// Grouped for humans; also spread flat at the top level for convenience.
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
// (1) Seal SDK — build / verify a tamper-evident evidence seal from a flat { relPath, bytes } entry list.
|
|
49
|
+
// buildSeal(entries) -> seal object
|
|
50
|
+
// validateSeal(seal) -> throws on structural / root-mismatch problems
|
|
51
|
+
// serializeSeal(seal) -> canonical, byte-deterministic JSON (newline-terminated)
|
|
52
|
+
// readSeal(jsonOrObject) -> parsed + strictly validated seal
|
|
53
|
+
// verifySeal(seal, entries) -> { verdict: "ACCEPTED" | "REJECTED", accepted, ... } (RE-DERIVES root)
|
|
54
|
+
const seal = Object.freeze({
|
|
55
|
+
KIND: evidence.SEAL_KIND,
|
|
56
|
+
SCHEMA_VERSION: evidence.SEAL_SCHEMA_VERSION,
|
|
57
|
+
TRUST_NOTE: evidence.EVIDENCE_TRUST_NOTE,
|
|
58
|
+
buildSeal: evidence.buildSeal,
|
|
59
|
+
validateSeal: evidence.validateSeal,
|
|
60
|
+
serializeSeal: evidence.serializeSeal,
|
|
61
|
+
readSeal: evidence.readSeal,
|
|
62
|
+
verifySeal: evidence.verifySeal,
|
|
63
|
+
// The generic, product-agnostic seal core the above are bound to (advanced / custom products).
|
|
64
|
+
PacketSealError: packetseal.PacketSealError,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// (1b) SIGNED / vendor-pinned verify SDK — the embedded twin of `vh evidence verify-signed`. This is the
|
|
68
|
+
// SAME code the CLI runs (identity re-exports of the already-green, already-CLI-shipped functions),
|
|
69
|
+
// so a signed packet a buyer verifies in-process is byte-identical to the `vh evidence verify-signed`
|
|
70
|
+
// path — no shelling out to the binary to verify a SIGNED, vendor-address-pinned seal.
|
|
71
|
+
//
|
|
72
|
+
// signSealWith(seal, signer) -> a signed-seal container (WRAPS the canonical seal bytes)
|
|
73
|
+
// validateSignedSeal(container) -> strict structural validation of a signed container
|
|
74
|
+
// verifySignedSeal({container,expectedSigner,expectedCanonical})
|
|
75
|
+
// -> the PURE core verdict (recover signer; optional pin/bind)
|
|
76
|
+
// verifySignedSealAttestation({container,expectedSigner,dir})
|
|
77
|
+
// -> the strict signed-verify the CLI runs (--signer / --dir)
|
|
78
|
+
// recoverSigner(container) -> the address the signature recovers to (offline, key-free)
|
|
79
|
+
// verifySignedAttestation(params) -> the generic, product-agnostic signed-attestation verifier
|
|
80
|
+
//
|
|
81
|
+
// TRUST BOUNDARY: a valid signature proves WHO vouched (the holder of `signer`'s key) for THIS sealed
|
|
82
|
+
// packet — NOT a trusted timestamp ("signed since T" rides the human-owned trust-root, STRATEGY.md P-3)
|
|
83
|
+
// and NOT a legal opinion. Verification is OFFLINE / key-free: it recovers a PUBLIC address, holds no
|
|
84
|
+
// private key, and contacts nothing.
|
|
85
|
+
const signed = Object.freeze({
|
|
86
|
+
KIND: evidence.SIGNED_SEAL_KIND,
|
|
87
|
+
TRUST_NOTE: evidence.VERIFY_SIGNED_SEAL_TRUST_NOTE,
|
|
88
|
+
signSealWith: evidence.signSealWith,
|
|
89
|
+
validateSignedSeal: evidence.validateSignedSeal,
|
|
90
|
+
verifySignedSeal: evidence.verifySignedSeal,
|
|
91
|
+
verifySignedSealAttestation: evidence.verifySignedSealAttestation,
|
|
92
|
+
// The generic, product-agnostic signed-attestation core the evidence path is bound to.
|
|
93
|
+
recoverSigner: attestation.recoverSigner,
|
|
94
|
+
verifySignedAttestation: attestation.verifySignedAttestation,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// (2) Receipts — the anchor/claim receipt codec + the path-bound manifest diff.
|
|
98
|
+
const receipts = Object.freeze({
|
|
99
|
+
SCHEMA_VERSION: receipt.SCHEMA_VERSION,
|
|
100
|
+
CLAIM_RECEIPT_KIND: receipt.CLAIM_RECEIPT_KIND,
|
|
101
|
+
ANCHOR_RECEIPT_KIND: receipt.ANCHOR_RECEIPT_KIND,
|
|
102
|
+
buildReceipt: receipt.buildReceipt,
|
|
103
|
+
buildAnchorReceipt: receipt.buildAnchorReceipt,
|
|
104
|
+
writeReceipt: receipt.writeReceipt,
|
|
105
|
+
readReceipt: receipt.readReceipt,
|
|
106
|
+
diffManifest: receipt.diffManifest,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// (3) Hashing primitives — the SAME keccak/Merkle helpers every seal + receipt is built on.
|
|
110
|
+
const hashing = Object.freeze({
|
|
111
|
+
hashBytes: hash.hashBytes,
|
|
112
|
+
hashFile: hash.hashFile,
|
|
113
|
+
hashEntries: hash.hashEntries,
|
|
114
|
+
hashDir: hash.hashDir,
|
|
115
|
+
hashPath: hash.hashPath,
|
|
116
|
+
buildTree: hash.buildTree,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// Flat top-level export map. Every function property is the identity re-export of its source.
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
module.exports = Object.freeze({
|
|
123
|
+
// Semver-guarded version of THIS public surface (mirrors package.json).
|
|
124
|
+
apiVersion: pkg.version,
|
|
125
|
+
|
|
126
|
+
// Grouped namespaces.
|
|
127
|
+
seal,
|
|
128
|
+
signed,
|
|
129
|
+
receipts,
|
|
130
|
+
hashing,
|
|
131
|
+
|
|
132
|
+
// Flat convenience re-exports (identity with the grouped + source symbols).
|
|
133
|
+
// --- seal SDK ---
|
|
134
|
+
buildSeal: evidence.buildSeal,
|
|
135
|
+
validateSeal: evidence.validateSeal,
|
|
136
|
+
serializeSeal: evidence.serializeSeal,
|
|
137
|
+
readSeal: evidence.readSeal,
|
|
138
|
+
verifySeal: evidence.verifySeal,
|
|
139
|
+
PacketSealError: packetseal.PacketSealError,
|
|
140
|
+
// --- signed / vendor-pinned verify SDK (the embedded twin of `vh evidence verify-signed`) ---
|
|
141
|
+
signSealWith: evidence.signSealWith,
|
|
142
|
+
validateSignedSeal: evidence.validateSignedSeal,
|
|
143
|
+
verifySignedSeal: evidence.verifySignedSeal,
|
|
144
|
+
verifySignedSealAttestation: evidence.verifySignedSealAttestation,
|
|
145
|
+
recoverSigner: attestation.recoverSigner,
|
|
146
|
+
verifySignedAttestation: attestation.verifySignedAttestation,
|
|
147
|
+
// --- receipts ---
|
|
148
|
+
buildReceipt: receipt.buildReceipt,
|
|
149
|
+
buildAnchorReceipt: receipt.buildAnchorReceipt,
|
|
150
|
+
writeReceipt: receipt.writeReceipt,
|
|
151
|
+
readReceipt: receipt.readReceipt,
|
|
152
|
+
diffManifest: receipt.diffManifest,
|
|
153
|
+
// --- hashing ---
|
|
154
|
+
hashBytes: hash.hashBytes,
|
|
155
|
+
hashFile: hash.hashFile,
|
|
156
|
+
hashEntries: hash.hashEntries,
|
|
157
|
+
hashDir: hash.hashDir,
|
|
158
|
+
hashPath: hash.hashPath,
|
|
159
|
+
buildTree: hash.buildTree,
|
|
160
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "verifyhash",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Tamper-evident, permissionless on-chain registry of code-contribution hashes, plus an offline data-provenance toolkit (the `vh` CLI).",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.js",
|
|
9
|
+
"./package.json": "./package.json"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"vh": "cli/vh.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"index.js",
|
|
16
|
+
"cli/",
|
|
17
|
+
"trustledger/",
|
|
18
|
+
"README.md",
|
|
19
|
+
"docs/"
|
|
20
|
+
],
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "hardhat compile",
|
|
26
|
+
"test": "hardhat test",
|
|
27
|
+
"go-live": "node scripts/go-live-check.js",
|
|
28
|
+
"sdk:surface": "node scripts/gen-sdk-surface.cjs",
|
|
29
|
+
"node": "hardhat node",
|
|
30
|
+
"deploy:amoy": "hardhat run scripts/deploy.js --network amoy"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"ethers": "^6.17.0",
|
|
34
|
+
"js-sha3": "^0.8.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
|
|
38
|
+
"hardhat": "^2.22.0",
|
|
39
|
+
"dotenv": "^16.4.5"
|
|
40
|
+
}
|
|
41
|
+
}
|