Skip to main content
Glama

Verification Service ("the atom") — v1 backend

The production backend for the verification atom: "check this one thing for me, and prove you did." A client POSTs one question (http_observation of a URL); the service performs a real observation and returns a signed proof packet with transparent cost accounting. Every verification is appended to a public, permanent ledger — the board.

Honesty, up front:

  • cost_usd on every packet is an operator-configured estimate, not a metered cloud bill ("cost_basis": "estimate"). Recalibrate before pricing.

  • Payments are disabled. There is no payment provider integration in v1, and the service refuses to start if payments.enabled is true without one.

  • This service makes no revenue claims. Quoted prices are quotes; nothing is charged, because nothing can be charged yet.

Architecture

Related MCP server: Web Resource Ledger

MCP server (mcp/)

This repo also ships a Model Context Protocol server that exposes the verification atom as an MCP tool, so MCP-compatible agents can request signed proof packets directly.

  • Install: uvx verify-atom-mcp (published on PyPI as verify-atom-mcp)

  • Listed in the official MCP registry as io.github.asherengos/verify-atom

  • Transport: stdio · Tool: verify — performs an HTTP observation of a URL and returns the Ed25519-signed proof packet

  • Source: mcp/mcp_server.py (zero dependencies; mcp/README.md has the details)

File

Role

app.py

Flask app: routes, SSRF-guarded HTTP observation, packet assembly

signing.py

Ed25519 key management + sign/verify (crypto core vendored from the Asher AI Studios Work Kernel, 2026-09-25; pure Python, RFC 8032, zero deps)

costs.py / cost_model.yaml

Per-verification usage measurement × operator-configured rates

ledger.py

Append-only JSONL ledger (data/ledger.jsonl): the board's source of truth and the honest loop's input

mini_yaml.py

Strict subset YAML reader (keeps deps to flask + waitress; not a general parser)

config.yaml

Service config: port, data dir, price, guardrails, payments switch

tests/test_service.py

Hermetic pytest suite (local stub server; no external network)

A verification flows: POST /verify → SSRF guard → real socket-level HTTP fetch (single DNS resolution; the checked address is the connected address; redirects never followed) → observation + evidence → cost accounting → Ed25519 signature over canonical JSON → append to ledger → proof packet.

Quick start

cd ~/workspace/verify-service
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt   # flask + waitress only; free
.venv/bin/python app.py                     # serves on 127.0.0.1:5057 via waitress

First run generates an Ed25519 keypair under data/keys/ (0600). data/ is gitignored: ledger, keys, and runtime state never enter the repo.

Development (Flask built-in server, do not use in production):

.venv/bin/python -m flask --app app:create_app run -p 5057

API reference

POST /verify — perform a verification

curl -s -X POST http://127.0.0.1:5057/verify \
  -H 'Content-Type: application/json' \
  -d '{"type":"http_observation","url":"https://example.com"}' | python3 -m json.tool

Response: a signed proof packet.

{
  "id": "VRF-20260926-3f9a2c1d",
  "type": "http_observation",
  "verdict": "CONFIRMED",
  "target": {"url": "https://example.com"},
  "observation": {
    "reachable": true,
    "http_status": 200,
    "page_title": "Example Domain",
    "body_sha256": "…",
    "tls": {"subject": "…", "issuer": "…", "not_after": "…"},
    "dns": {"host": "example.com", "addresses": ["93.184.216.34"]}
  },
  "evidence": [{"kind": "http_status", "value": 200}, …],
  "timestamp_utc": "2026-09-26T02:30:00Z",
  "usage": {"wall_time_s": 0.41, "bytes_in": 1830, "http_requests": 1},
  "cost_usd": 0.0000109,
  "cost_basis": "estimate",
  "price_quote_usd": 0.005,
  "margin_usd": 0.0049891,
  "below_cost": false,
  "signature": "<64-byte ed25519 hex>",
  "signing": {"algorithm": "Ed25519", "public_key_hex": "…",
              "signed_fields": "all fields except 'signature' and 'signing'"}
}

Verdicts: CONFIRMED (fetched, HTTP 2xx), UNCONFIRMED (fetched, other status — a real negative observation), ERROR (refused or failed; the observation.reason explains why). Unknown type → 400 unknown_type.

Spot-checking a packet (no trust required):

# fetch the service public key, then verify the signature offline:
curl -s http://127.0.0.1:5057/key

The signature covers the canonical JSON (sorted keys, compact separators) of every packet field except signature and signing. Recompute it and verify with any Ed25519 implementation.

GET /verify/ — retrieve a stored proof packet (404 if unknown)

curl -s http://127.0.0.1:5057/verify/VRF-20260926-3f9a2c1d | python3 -m json.tool

GET /board/feed — public append-only feed, oldest first

curl -s http://127.0.0.1:5057/board/feed | python3 -m json.tool

Each entry: {id, timestamp_utc, type, verdict, url}.

GET /health

curl -s http://127.0.0.1:5057/health
# {"status":"ok","version":"0.1.0","payments_enabled":false}

GET /key — the service's Ed25519 public key

curl -s http://127.0.0.1:5057/key
# {"algorithm":"Ed25519","public_key_hex":"…"}

The private key is never logged, never served, never leaves signing.py.

Configuration (config.yaml)

Key

Default

Meaning

service.port

5057

Listen port

service.data_dir

data

Runtime state (ledger, keys); gitignored

verify.base_price_usd

0.005

Quoted price per verification (a quote, not a charge)

verify.timeout_s

10

Network timeout per observation

verify.max_body_bytes

2097152

2 MB body cap; overage → ERROR verdict

verify.allow_private

false

Never true in production. Lets the hermetic tests use a 127.0.0.1 stub

cost_model

cost_model.yaml

Path to operator-configured rates

payments.enabled

false

Master switch. true without a configured provider → refuse to start

Key management

  • First run: KeyStore generates an Ed25519 keypair into data/keys/{private.key,public.key} (directory 0700, files 0600; refuses to overwrite an existing key).

  • Back up private.key offline; losing it orphans the board's trust chain (old packets still verify against the published public key, but the service can no longer sign).

  • Rotation (not automated in v1): generate a new keypair, publish the new public key at /key with a key_id, keep the old public key published for historical verification. See go-live checklist.

Deployment notes

The $0 go-live bundle lives in deploy/:

File

Role

deploy/verify-service.service

Hardened systemd unit (unprivileged verify user, NoNewPrivileges, PrivateTmp, ProtectSystem=strict, restart on failure)

deploy/Caddyfile

Reverse proxy with automatic TLS; forwards the real client IP (X-Forwarded-For) — the app trusts that header only from 127.0.0.1/::1

deploy/DEPLOY.md

Ordered runbook for a non-expert: Oracle Cloud Always Free ARM VM, hardening, first-boot keypair, systemd, Caddy, DNS, go-live checks

Built-in abuse guard: POST /verify is rate-limited per client IP (sliding 60s window, verify.rate_limit_per_minute, default 30/min). Over the cap → 429 JSON with a Retry-After header. Set rate_limit_per_minute: 0 to disable (never disable on a public endpoint).

Run one worker per data directory (v1 has no cross-process locking on the ledger). The service binds 127.0.0.1 only; public traffic arrives via the reverse proxy.

Machine-readable docs for humans and agents: GET /llms.txt (text/plain) — endpoint contract, proof-packet fields, how to verify a signature offline, and the honesty notes.

Known v1 limitations (stated plainly)

  • HTTP/1.1 only; redirects are never followed (recorded as UNCONFIRMED).

  • No HTTP/2, no JavaScript rendering: the observation is the raw HTTP response, which is exactly what is hashed and signed.

  • DNS is resolved once per verification; a hostile DNS that flips answers between resolution and connect within milliseconds is out of scope for v1 (documented, not mitigated beyond the single-resolution design).

  • Single worker; ledger scans are O(n).

  • Pure-Python Ed25519: correct, but slower than a C implementation — fine at v1 volumes; adopt a vetted C library before scaling.

Go-live checklist

  • Deploy bundle (deploy/, built 2026-09-26): hardened systemd unit, Caddyfile with automatic TLS, DEPLOY.md runbook (Oracle Cloud Always Free ARM VM, human steps marked), in-app per-IP rate limiting on POST /verify (30/min default, 429 + Retry-After), /llms.txt machine-readable API docs. All $0, stdlib-only, no payment code paths. Tests: 26/26 green.

  • Hosting: provision the VM per deploy/DEPLOY.md; decide the data-dir backup story (ledger + key backup are in the runbook).

  • Payment provider integration: NOT BUILT. Implement the charge flow, wire it to payments.provider, then flip enabled: true.

  • Fresh production keypair: generated on first boot per the runbook (never copy data/ from dev); publish the public key; back up the private key offline.

  • Real cost calibration: replace cost_model.yaml estimates with metered values; run a calibration batch; confirm margin_usd > 0 before quoting real prices.

  • Terms & abuse policy: acceptable use, refund policy for failed (ERROR) verifications — written and published. (Rate limiting itself is now built in; the edge policy is the remaining piece.)

  • TLS + reverse proxy: covered by deploy/Caddyfile; replace the placeholder domain, validate, reload.

  • Key rotation procedure documented and rehearsed.

  • Monitoring: UptimeRobot free monitor on /health; ledger growth, below_cost counts, error rates — the honest loop must page someone when the loop turns red.

Available Tools

1 tool
verify_urlA

Observe a public web page and return a cryptographically signed proof packet: HTTP status, page title, body SHA-256, DNS resolution, and TLS certificate details, timestamped and Ed25519-signed. Use when you need verifiable evidence of what a webpage contained — e.g. to back a claim with proof instead of asking to be trusted. Anyone can independently verify the packet's signature against the service's published public key.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlYesThe public http(s) URL to observe and prove.

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does substantial work: it discloses the signed-packet output format, the Ed25519 signature, timestamping, and the independent-verification path against a published public key. It implies a read-only observation but does not state auth requirements, rate limits, timeout behavior, or how failures (DNS/TLS errors) are surfaced.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, zero filler. The output enumeration is front-loaded before the usage clause, so an agent scanning the first line immediately learns what it gets back.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter tool with no output schema and no annotations, the description compensates well by describing the return packet contents in detail, which the missing output schema would otherwise leave opaque. Remaining gaps are operational: auth, rate limits, and error handling.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Only one parameter and schema description coverage is 100% — the schema already states 'The public http(s) URL to observe and prove.' The description's 'public web page' merely restates that constraint, adding no format, protocol, or edge-case detail beyond the schema. Baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb ('Observe') and resource ('public web page') and then enumerates exactly what the tool produces: HTTP status, page title, body SHA-256, DNS resolution, TLS certificate details, Ed25519-signed and timestamped. An agent knows precisely what this tool does and what it returns without opening anything else.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly names the triggering scenario: 'Use when you need verifiable evidence of what a webpage contained — e.g. to back a claim with proof instead of asking to be trusted.' There are no siblings to route away from, so the absence of alternatives is not a gap, but there is no when-not guidance (e.g. non-public URLs, pages behind auth).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 1 tool updatev0.1.0
    • First observedverify_url

TDQS

A4.3/5.0

Scored across 1 tool

Disambiguation5/5

With only one tool, there is no possibility of confusion or misselection. The tool's purpose is clear and distinct.

Naming Consistency5/5

The single tool follows a clear verb_noun pattern (verify_url) in snake_case. While trivial, it is consistent and readable.

Tool Count4/5

One tool is slightly under the typical 3-15 range, but it is well-scoped for a focused verification service. Each tool earns its place; here, the single operation is sufficient for the core purpose.

Completeness4/5

The tool covers proof generation (HTTP status, hashes, signatures), but there is no companion tool for retrieving the public key or verifying a packet locally. These are minor gaps that agents can work around externally.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    B
    maintenance
    Third-party certifying proxy — sign any HTTP call (AI agents, webhooks, microservices) with an independent Ed25519 signature, RFC 3161 timestamp, and Sigstore Rekor anchor.
    4
    220 PyPI
    2
    MIT
  • A
    license
    Not graded
    quality
    F
    maintenance
    📇 ☁️ - Capture web pages as cryptographically signed, tamper-evident evidence. Ed25519 signatures, RFC 3161 timestamps, and WACZ archives. Four tools: capture_url, get_capture, list_captures, verify_capture.
    1 npm
    1
    MIT
  • F
    license
    Not graded
    quality
    A
    maintenance
    Verifiable document intelligence for AI agents. Extract, summarize, claim-check, and notarize PDFs & URLs with cryptographic proofs, cross-document search, and on-chain attestation via Base L2.
    -
  • A
    license
    A
    quality
    B
    maintenance
    Provides cryptographic truth infrastructure for AI agents, enabling them to seal content with SHA-256 and Ed25519, verify receipts, anchor them to Bitcoin via OpenTimestamps, generate citations, and audit chains of receipts.
    5
    51 npm
    1
    MIT