we build real is a movement for verifiable history.
PACT is an open protocol (Provenance of Accumulated Checkable Traces).
PBM Labs LLC provides the first reference implementation, hosted at webuildreal.dev.
This repo is that reference implementation: leftover traces from systems that already exist, recorded as separate kinds on one Merkle tree. Mail (DMARC aggregate reports), Certificate Transparency, and Rekor are leftover kinds — never a blended score. The public site is the movement and the kind-plus-identity query. Mail intake lives at /connect.
Protocol specification: docs/pact_protocol.md.
Agent evidence interface: docs/agent_evidence.md (GET /v1/kinds, GET /v1/evidence).
Whitepaper: webuildreal.dev/whitepaper.
The manifesto video under apps/web/public/ is archival (~11MB, tracked in git). It is not shown on the public landing. Prefer R2/CDN if that media is used again.
Movement: we build real
Intake: rua@pact.webuildreal.dev
Contact: hello@pbm-labs.com
First reference implementation: PBM Labs LLC
Legacy intake (still reaches ingest): rua@pact.pbm-labs.com
packages/pact-core Protocol logic (leaf encodings for mail / CT / Rekor, merkle, dmarc parser)
packages/contracts Foundry — PactRoots (spec §9). Base Sepolia deployed; mainnet not.
apps/web leftover — uncommissioned evidence (leftover.webuildreal.dev)
apps/web-live Frozen public site (webuildreal.dev) @ 6b78925, DMARC-only
workers/ingest Cloudflare Email Worker + queue → D1 ledger + on-chain publishRoot
examples/score Informative scoring example (`example-score-0.1`) — not protocol
docs/ Protocol specification + examples
pnpm install
pnpm test # pact-core + example-score + web tests
pnpm typecheck
pnpm --filter @pact/core build
pnpm dev:web # http://localhost:3000 — evidence/v2
pnpm deploy:web # Cloudflare Workers (leftover.webuildreal.dev)
pnpm deploy:web-live # Cloudflare Workers (webuildreal.dev, frozen)
pnpm deploy:ingest # Email ingest + ledger API (ledger.webuildreal.dev)All secrets stay in repo-root .env.local (gitignored). Never commit it.
cp .env.example .env.local
# fill in LEDGER_URL, Cloudflare OAuth, CONNECT_STATE_SECRET, etc.Used by pnpm dev:web and (via wrangler secrets) the Workers.
| Variable | Used by |
|---|---|
LEDGER_URL |
Web app — ingest HTTP API (https://ledger.webuildreal.dev in prod) |
LEDGER_WRITE_SECRET |
Web + ingest — Bearer token for POST /v1/domains |
PUBLISHER_PRIVATE_KEY |
Ingest — PactRoots owner key (Sepolia) |
CLOUDFLARE_OAUTH_CLIENT_ID, CLOUDFLARE_OAUTH_CLIENT_SECRET |
/connect Cloudflare connect |
NEXT_PUBLIC_APP_URL |
App URL (https://webuildreal.dev in prod) |
CONNECT_STATE_SECRET |
HMAC for OAuth state |
Cloudflare Worker production secrets:
cd workers/ingest && npx wrangler secret put PUBLISHER_PRIVATE_KEY
cd workers/ingest && npx wrangler secret put LEDGER_WRITE_SECRET
cd apps/web && npx wrangler secret put LEDGER_WRITE_SECRET
cd apps/web && npx wrangler secret put CLOUDFLARE_OAUTH_CLIENT_ID
cd apps/web && npx wrangler secret put CLOUDFLARE_OAUTH_CLIENT_SECRET
cd apps/web && npx wrangler secret put CONNECT_STATE_SECRET
cd apps/web-live && npx wrangler secret put LEDGER_WRITE_SECRET
cd apps/web-live && npx wrangler secret put CLOUDFLARE_OAUTH_CLIENT_ID
cd apps/web-live && npx wrangler secret put CLOUDFLARE_OAUTH_CLIENT_SECRET
cd apps/web-live && npx wrangler secret put CONNECT_STATE_SECRETLEDGER_URL is a wrangler var on pact-web and pact-web-evidence (not a secret): https://ledger.webuildreal.dev.
Local Cloudflare preview: cp apps/web/.dev.vars.example apps/web/.dev.vars
D1 schema (once, after creating pact-ledger):
cd workers/ingest
npx wrangler d1 execute pact-ledger --remote --file=src/schema.sql
# Existing D1 only (already applied on fresh schema.sql):
npx wrangler d1 execute pact-ledger --remote --file=src/migrate-ct.sql
npx wrangler d1 execute pact-ledger --remote --file=src/migrate-rekor-subject.sqlOAuth is pinned to webuildreal.dev only (redirect_uri + Client URL). Do not use pact.pbm-labs.com for the OAuth client. Cloudflare connect on the public site is https://webuildreal.dev/connect. leftover at leftover.webuildreal.dev needs its own redirect URI (https://leftover.webuildreal.dev/api/connect/cloudflare/callback) before that OAuth path works there.
- Cloudflare dashboard → Manage Account → OAuth clients → Edit client
- Redirect URL:
https://webuildreal.dev/api/connect/cloudflare/callback - Client URL:
https://webuildreal.dev(HTTPS required; verify with publisher TXT on the apex) - Logo URL:
https://webuildreal.dev/we-build-real-logo.png(hosted inapps/web/public/) - Add the publisher TXT from the OAuth client page as an apex TXT on
webuildreal.dev - Promote to public after domain verification (required for external users)
- Put client ID and secret in
.env.localand Worker secrets (see.env.example)
Optional: CLOUDFLARE_OAUTH_SCOPES, CONNECT_STATE_SECRET — see .env.example.
Copy the _dmarc snippet or rua address on /connect. Those paths also put the sending domain on the ledger (GET /api/connect/register → POST /v1/domains) so mail leftover is not dropped. Certificate leftover and Rekor leftover are queried by the identity those logs already used — not by this form. If someone only updates DNS and skips the site form, ingest upserts the domain row on the first valid aggregate report so the mail leaf is not dropped.
| Host | Role |
|---|---|
webuildreal.dev / www |
Public movement site, frozen DMARC-only UI (pact-web / apps/web-live) |
leftover.webuildreal.dev |
leftover (pact-web-evidence / apps/web) — noindex. evidence.webuildreal.dev 308s here |
ledger.webuildreal.dev |
Public ledger HTTP API (pact-ingest) |
hello@pbm-labs.com |
Legal / operator contact (PBM Labs LLC) |
rua@pact.webuildreal.dev |
DMARC intake (canonical) |
rua@pact.pbm-labs.com |
Legacy DMARC intake (still has Cloudflare MX) |
pact.pbm-labs.com (HTTP) |
No app — DNS A 192.0.2.1 proxied only for legacy MX / rua@ mail |
Apply primary DNS in Cloudflare on the webuildreal.dev zone. Legacy pact.pbm-labs.com intake stays on the PBM Labs LLC zone only for backward compatibility.
| Record | Purpose |
|---|---|
Apex A proxied |
Worker placeholder for pact-web |
www CNAME → webuildreal.dev proxied |
www → apex |
Apex MX mail.protonmail.ch / mailsec.protonmail.ch |
Proton (hello@) |
TXT @ SPF v=spf1 include:_spf.protonmail.ch ~all |
Proton sending |
CNAME protonmail[123]._domainkey |
Proton DKIM |
TXT @ cloudflare_oauth_client_publisher=… |
OAuth client URL verification |
TXT _dmarc rua=mailto:rua@pact.webuildreal.dev |
Zone’s own DMARC + PACT intake |
pact MX → route*.mx.cloudflare.net |
Email Routing for DMARC intake |
pact TXT SPF |
Cloudflare Email Routing |
TXT _report._dmarc.pact v=DMARC1 |
Authorize external reports to canonical rua |
| Email Routing rule | rua@pact.webuildreal.dev → Worker pact-ingest |
Worker routes: webuildreal.dev/*, www.webuildreal.dev/* (apps/web-live/wrangler.jsonc); custom domain leftover.webuildreal.dev (apps/web/wrangler.jsonc); ledger.webuildreal.dev (workers/ingest/wrangler.toml).
Kept solely so existing DMARC records that still point at rua@pact.pbm-labs.com keep working. Not a public brand surface.
| Record | Purpose |
|---|---|
pact MX → route*.mx.cloudflare.net |
Legacy DMARC mail intake |
pact proxied A 192.0.2.1 |
Needed alongside MX (not an app host) |
pact TXT SPF |
Cloudflare Email Routing |
_report._dmarc.pact TXT v=DMARC1 |
Authorize external reports to legacy rua |
Do not keep OAuth publisher TXT on pact.pbm-labs.com — publisher verification lives on webuildreal.dev only.
| Address | MX | Handler |
|---|---|---|
hello@pbm-labs.com |
PBM Labs LLC | Legal / operator inbox |
hello@webuildreal.dev |
Proton (apex) | Movement domain only — not a legal inbox |
rua@pact.webuildreal.dev |
route*.mx.cloudflare.net |
pact-ingest (canonical) |
rua@pact.pbm-labs.com |
route*.mx.cloudflare.net |
pact-ingest (legacy) |
In Proton Mail → Settings → Domain names → add webuildreal.dev and publish the MX / SPF / DKIM / verification TXT Proton shows.
webuildreal.dev
| Custom address | Action |
|---|---|
rua@pact.webuildreal.dev |
Send to Worker → pact-ingest |
Legal contact is hello@pbm-labs.com (PBM Labs LLC). Keep webuildreal.dev apex MX on Proton if that mailbox still exists; it is not the legal inbox. Do not put Cloudflare Email Routing MX on the apex.
Legacy (PBM Labs LLC zone)
| Custom address | Action |
|---|---|
rua@pact.pbm-labs.com |
Send to Worker → pact-ingest |
dig @1.1.1.1 MX webuildreal.dev +short # mail.protonmail.ch
dig @1.1.1.1 MX pact.webuildreal.dev +short # route*.mx.cloudflare.net
dig @1.1.1.1 MX pact.pbm-labs.com +short # legacy rua MX
dig @1.1.1.1 TXT _dmarc.webuildreal.dev +short # rua@pact.webuildreal.dev
dig @1.1.1.1 TXT webuildreal.dev +short | grep oauth # publisher TXT
curl -sI https://webuildreal.dev/ | head -1 # app
curl -s https://ledger.webuildreal.dev/v1/health # ledger API
# pact.pbm-labs.com has no Worker route — HTTP is not the app (mail MX remains)Send test to hello@webuildreal.dev → Proton on the movement domain (not the legal inbox).
Legal / operator contact is hello@pbm-labs.com.
New DMARC reports must use rua@pact.webuildreal.dev → worker (~24–48h). rua@pact.pbm-labs.com still reaches ingest. rua@webuildreal.dev does not (apex MX is Proton).
pnpm deploy:web # leftover.webuildreal.dev — apps/web
pnpm deploy:web-live # webuildreal.dev (+ www) — apps/web-live, frozen @ 6b78925cd workers/ingest
npx wrangler login # once
npx wrangler queues create pact-reports # once
npx wrangler d1 create pact-ledger # once; put database_id in wrangler.toml
npx wrangler d1 execute pact-ledger --remote --file=src/schema.sql
npx wrangler d1 execute pact-ledger --remote --file=src/migrate-ct.sql # existing D1 only
npx wrangler d1 execute pact-ledger --remote --file=src/migrate-rekor-subject.sql # existing D1: Rekor by leftover subject
npx wrangler secret put PUBLISHER_PRIVATE_KEY
npx wrangler secret put LEDGER_WRITE_SECRET
pnpm run deploy # not `pnpm deploy`Worker flow: email handler → pact-reports queue → parse/auth/dedup → D1 leaf → publishRoot on Base Sepolia PactRoots. The same 15-minute cron also indexes CT (crt.sh) for connected domains and Rekor (rekor.sigstore.dev) for leftover subjects (host, URI, or email — not a connected-domain FK), then publishes if the tree moved. If a publishRoot tx fails (public RPC blip), ingest still keeps the leaf and the cron retries until the live tree matches the chain. Manual retry: POST /v1/root/publish with Bearer LEDGER_WRITE_SECRET. Queue retries cannot republish — a redelivered report is already stored, so processed=0. GitHub cron would need a second copy of the publisher key; keep it on the Worker.
Google DMARC reports arrive as ZIP attachments (application/zip); the ingest worker must unzip before parsing XML.
Public ledger API (CORS open for GET):
| Method | Path | Notes |
|---|---|---|
| GET | /v1/health |
Contract address + chain |
| GET | /v1/root |
Latest on-chain root |
| GET | /v1/kinds |
Leftover kind catalog (key shape, tag, stake, kind_root: { type: "shared" }) |
| GET | /v1/evidence |
?kind=mail|ct|rekor&identity= — echoes identity; 200 with zero rows if none |
| GET | /v1/domains |
{ domains, leaves, ct, rekor } — domain rows plus per-stream count summaries (leaves is mail) |
| GET | /v1/domains/:domain |
Domain, mail leaves, CT certs, host leftover Rekor, global hashes, on-chain root |
| GET | /v1/leaves/:hash |
One leaf by keccak256 (kind: dmarc, ct, or rekor) plus inclusion proof vs the named shared root |
| GET | /v1/wrappers/:hash |
Stored wrapper + DKIM TXT snapshot |
| GET | /v1/wrappers/:hash/check |
Hash matches the leaf; DNS key is on record |
| GET | /v1/wrappers/:hash/rfc822 |
Wrapper bytes |
| POST | /v1/domains |
Bearer LEDGER_WRITE_SECRET |
| POST | /v1/root/publish |
Bearer LEDGER_WRITE_SECRET — publish the live tree if it is ahead of chain |
| POST | /v1/ct/ingest |
Bearer LEDGER_WRITE_SECRET — index CT for one domain or a batch |
| POST | /v1/rekor/ingest |
Bearer LEDGER_WRITE_SECRET — { identity } leftover subject (or { domain } as host leftover) |
| Method | What it tests |
|---|---|
pnpm test |
pact-core, example-score, and web unit tests |
pnpm typecheck |
All packages including ingest |
forge test (in packages/contracts) |
PactRoots (Merkle root publication) |
Send mail to hello@pbm-labs.com |
PBM Labs LLC operator inbox |
| Wait ~24–48h | Real Google/Microsoft DMARC reports |
Real reports must pass wrapper DKIM whose d= matches the reporter (or an allowlisted forwarder), plus the org_name / envelope-from allowlist. Resend/test mail is rejected — that is intentional.
Intake
-
webuildreal.devDNS:_dmarc,_report._dmarc.pact, MX/SPF/DKIM, OAuth publisher TXT - Email Routing
rua@pact.webuildreal.dev→pact-ingest - Legacy
rua@pact.pbm-labs.comstill routed for existing DMARC records - Worker deployed with D1 + queue + publisher key
Public site + agent interface
- Parser, dedup, leaves
- Public site is the agent evidence interface (
GET /v1/kinds,GET /v1/evidence) — no domain profile - Cloudflare OAuth + manual DNS + existing-tool mail intake (
/connect) - Manual / tool paths register via
GET /api/connect/register→POST /v1/domains - OAuth client on
webuildreal.dev(callback + publisher TXT) - Legacy
pact.pbm-labs.comkept for mail only (no HTTP app route) - Merkle inclusion proofs on
GET /v1/leaves/:hashagainst the named shared root - Leftover kinds stay separate (mail / CT / Rekor) — never blended
- End-to-end with live reporter data (
webuildreal.dev)
Streams (CT / Rekor)
- D1
migrate-ct.sql/migrate-rekor-subject.sql(Rekor keyed by leftover subject) - 15-minute cron indexes crt.sh (connected domains) and rekor.sigstore.dev (leftover subjects)
-
GET /v1/domainsincludesctandrekorsummaries -
GET /v1/kindsandGET /v1/evidence
On-chain (boundary 1)
- Deploy
PactRootson Base Sepolia (0x873e76897BC3Fe8EBdfa67cb73404dA75B2d64ee) - First
publishRootfrom ingest after a real leaf - Base mainnet
Leaf availability
- Wrapper bytes + DKIM TXT snapshot in Supabase Storage, content-addressed by keccak256 (
GET /v1/wrappers/{hash}andGET /v1/wrappers/{hash}/checkon the ledger) - CID with each
publishRoot - IPFS pin of the same bytes
- Independent third-party mirrors
Reporter mail (boundary 2)
- DKIM-verify of Gmail/Microsoft wrapper mail when the Email Worker copy still verifies; otherwise DKIM-Signature / allowlisted envelope
- Wrapper witness in the leaf (
d=/ selector + keccak256 of the RFC822) - Received wrapper + DKIM key snapshot stored; recheck is hash matches the leaf and the DNS key is on record
| Module | Spec reference |
|---|---|
| Leaf encoding | Appendix C |
| Sparse Merkle | §3.3.1 (32 levels) |
| On-chain roots | PactRoots — §9. Base Sepolia: 0x873e76897BC3Fe8EBdfa67cb73404dA75B2d64ee |
| Scoring (example, not protocol) | example-score-0.1 — docs/examples/scoring.md |
| Agent evidence | docs/agent_evidence.md — GET /v1/kinds, GET /v1/evidence |
| Allowlist + wrapper DKIM | §3.1.1 seed in packages/pact-core/src/auth/allowlist.ts; ingest verifies RFC 6376 when the wrapper bytes still verify, otherwise uses DKIM-Signature / allowlisted envelope d=, then commits keccak256(RFC822) + those d=/s= in the leaf (Appendix C.5). Received wrapper + DKIM TXT snapshot: GET /v1/wrappers/{hash}. Recheck: GET /v1/wrappers/{hash}/check (hash matches the leaf; DNS key is on record). |
PACT is an open protocol — freely implementable.
we build real is the movement.
PBM Labs LLC provides the first reference implementation (this repo).