On 15 August I wrote here that two witnesses disagreed about my server and both were right. My own gate said green for seven days. A stranger in Spain said 522. The boundary between them turned out to be a Cloudflare subrequest guard that neither of us could have seen alone.
That post ended with an invitation: if your report contradicts my own instrument, that is the best possible submission.
This is what happened when I turned the same idea around and pointed it at the person who took up that invitation.
Witness B in that story was Federico Blanco Sanchez Llanos, who runs invinoveritas. Three days later I was the one making an assertion about his system, and the assertion I chose to make was false on purpose.
Everything below was measured on 17 and 18 August 2026. Every command is real and you can run it.
Background
There are more than twenty thousand MCP servers. When an agent picks one, it can read a name, a description and a URL. All three were written by the party being evaluated.
Discovery is solved. Choice is not. A directory that lists servers is not the same thing as a record of what happened when someone actually called them.
The instrument
Federico's /verify-proof endpoint lets any third party recompute a signed verdict rather than take his word for it. He then added /verify-proof-log: every call that asserts expect_intended_verifier becomes an append only row.
Fetch it yourself:
curl -s https://api.babyblueviper.com/verify-proof-log | python3 -m json.tool
When I first read it, the interesting part was not the data. It was this:
{
"assertions": [],
"count": 0,
"scope": "all events",
"honest_limit": "only records calls that asserted expect_intended_verifier -- a caller who asserts nothing leaves nothing to log a mismatch against."
}
The instrument states its own blind spot inside its own response body. A caller who claims nothing is invisible to it. That is structural and it cannot be patched away, and he put it in the payload rather than in a footnote.
Then he invited me to point it at a real proof while claiming an identity I do not hold.
Two decisions taken before running anything
I picked the ledger entry myself. He offered to hand me one with a fresh intended_verifier already set. If he had, he would have controlled both sides of the run, and the resulting record would be worth less than the electricity it took to write.
I asserted a reserved example domain. RFC 2606 reserves .example for exactly this. A mismatch row is permanent. Putting a real third party's name into one, to make a point about a system they never agreed to join, is a cost imposed on someone who is not in the room.
Hold that second decision. It comes back at the end, from a direction I did not expect.
Walk 0: the one that failed
Entry 100. GET /ledger/{N} returns the proof object under the key proof_event, so I posted it back under the key it arrived in.
{"proof_event": { ... }, "expect_intended_verifier": "not-the-horizons.example"}
HTTP 200, and:
{
"error": "provide `event` (the signed proof object), `proof_id`, or `event_id`.",
"published_pubkey": "6786e18a864893a900bd9858e650f67ccc3513f248fed374b591e2ff6922fbb7",
"how_to_verify": "Recompute the Nostr event id = sha256([0,pubkey,created_at,kind,tags,content]); verify the schnorr sig against pubkey; confirm pubkey == published_pubkey."
}
Nothing was logged. I read it as my own mistake, switched the key to event, and moved on.
Notice what the error carried even while being wrong: the published public key and the exact recompute recipe. I could keep working without asking anyone for help.
It was not my mistake
He went and checked instead of accepting my correction, and found the cause. Pydantic silently drops unrecognised fields.
class VerifyProofRequest(BaseModel):
event: dict | None = None
proof_id: str | None = None
event_id: str | None = None
expect_intended_verifier: str | None = None
# proof_event was never declared, so it is discarded before validation runs
The default model_config is extra="ignore". So posting under proof_event, the exact key that his own /ledger route uses when it hands you that object, made the field vanish before any code could see it. The request then fell through to a generic "provide event" message that named neither the offending key nor the reason.
This is the part worth taking away even if you never touch MCP.
extra="ignore" reads as forgiving. From the caller's side it is the opposite: it produces an error that cannot be debugged, because you cannot tell a wrong key from a wrong value from a typo. extra="forbid" would have answered "unknown field proof_event" and cost me nothing.
He shipped an alias, added two tests, and confirmed the existing hundred and thirty tests across the touched files still passed.
Walk 1: assert something you do not own
Entry 180, chosen by me.
{
"valid": false,
"checks": {
"id_integrity": true,
"signature_valid": true,
"issued_by_invinoveritas": true,
"is_proof_event": true,
"decision_ref_recomputes": true,
"intended_verifier_matches": false
}
}
Five integrity checks passed. The single check that failed was not about the proof. It was about who I said I was.
The row that landed:
{
"ts": 1787009262,
"event_id": "f6432dc260770aa8fad82a7751b543dc044a75514e521d4b0fd7f5030bc170de",
"asserted_identity": "not-the-horizons.example",
"declared_intended_verifier": "",
"matched": false,
"presenter_authenticated": null
}
The log held zero assertions when I read it minutes earlier. This was the first row ever written to it.
Walk 2: assert nothing, same bytes
Same proof object. No assertion.
{"valid": true, "checks": {"id_integrity": true, "signature_valid": true, "issued_by_invinoveritas": true, "is_proof_event": true, "decision_ref_recomputes": true}}
valid: true, and the log did not grow.
Same bytes, two verdicts. The only variable was what the caller claimed about himself.
That sentence is the whole design in one line. A verifier that only checks the artefact will pass a forged presenter every time, because the artefact really is valid. The forgery is in the claim wrapped around it.
The part that is recomputable rather than asserted
Every walk wrote its request, its response, the headers and the timestamps to disk, then shasum -a 256 over all of it.
walk 1 04_log_all.json 6ac96b110025fb39a8dbf888e0e47d3467cfde6e79e27a75fcbd289218671f02
walk 2 04_log_all.json 6ac96b110025fb39a8dbf888e0e47d3467cfde6e79e27a75fcbd289218671f02
walk 3 01_log_before.json 6ac96b110025fb39a8dbf888e0e47d3467cfde6e79e27a75fcbd289218671f02
walk 3 05_log_after.json 6ac96b110025fb39a8dbf888e0e47d3467cfde6e79e27a75fcbd289218671f02
Four fetches of the entire log across a forty eight minute window, byte identical. The walks that asserted nothing added nothing, byte for byte, rather than merely leaving the count unchanged.
walk 1 01_ledger_entry.json f9766ae501da205b7d1b71d29396c06f37bbb6a93c53ec122e87d4f63626e89e
walk 2 01_ledger_entry.json f9766ae501da205b7d1b71d29396c06f37bbb6a93c53ec122e87d4f63626e89e
Identical input hash, opposite verdicts. "Same bytes, two verdicts" stops being a sentence I wrote and becomes something a stranger can recompute.
Walk 3: check the fix rather than record his word for it
He verified his own alias against entry 180. I ran it against entry 80, which I chose and he did not, using the proof_event key that had failed in walk 0.
valid: True
checks: id_integrity, signature_valid, issued_by_invinoveritas,
is_proof_event, decision_ref_recomputes all true
error : none
log count before: 1 after: 1
The alias is real. The stated limit still holds: a caller who asserts nothing still leaves no row.
Putting it somewhere neither of us can edit
The four walks went into a jidec-path-v1 record and were submitted to an open witness intake. Acceptance is mechanical: schema, size, rate, duplicate, signature validity where a signature is present. There is no code path by which the operator declines a schema valid submission, which matters mostly because that operator is me.
The record carries a section listing what it does not prove:
- three entries out of two hundred and forty four were touched
- the append only claim was read from the operator's statement, not from a code audit
- nothing is established about the presenter signature path
- one anomaly was observed on 17 August and did not reproduce, and it is recorded anyway, without an explanation
A record that shows only the runs that worked is a brochure. Walk 0 is in there for the same reason.
Then it came back the other way
The next morning he submitted his own walk of the same target, from his own infrastructure. His vantage field reads:
invinoveritas operating infrastructure (not the target's residential/independent network)
He wrote the weakness of his own vantage into the record before anyone asked him for it. In August his measurement contradicted mine and that is how the Cloudflare boundary got found. This time he arrived with the limits of his own position already declared.
And the identity he asserted was eip155:1:0x000000000000000000000000000000000000dEaD. The Ethereum burn address.
We never discussed it. I picked a reserved domain, he picked a burn address, and both of us independently arrived at an identity that cannot belong to anyone by construction. That constraint is nowhere in the schema. It showed up in practice, twice, from two people who did not compare notes.
witness pool count 2, from two vantages
mismatch log count 2, both matched false
Two is a small number and I am not going to dress it up. The point is which two.
Lessons
-
extra="ignore"is not forgiving, it is undebuggable. From outside your process it turns a wrong key into a wrong value. Considerforbid. -
Put the recompute recipe in the error body. His error carried
published_pubkeyandhow_to_verify, so a failing call still told me how to check him. He then concluded it should not have errored at all. -
An instrument can state its own limit in its own response. One
honest_limitline determined the entire shape of this experiment, because it told me exactly which experiment was worth running. - A verification system is a design until someone who did not build it runs it. My pool held exactly one record until that morning, and its author was the person who built the pool. That is not a register, it is a mirror.
An invitation, again
Last time I asked for reports that contradict my own gate. One person took it up, and this article exists because he did.
So the same offer, from the other side: if you operate an MCP server or any API that issues verdicts, assert something false at it on purpose and see whether the record it keeps can be read by a stranger. If the record disagrees with what I have published, submit it. A contradiction from an independent vantage is worth more to me than agreement from my own.
curl -s https://api.babyblueviper.com/verify-proof-log | python3 -m json.tool
curl -s https://ledger.horizonshield.dev/witness/pending | python3 -m json.tool
curl -s https://ledger.horizonshield.dev/witness/344e248da148d5c3d4fe4c74d8ef56552998697d69c3d0a86f4bbfcb2eca6de8
You do not have to believe either of us. That is the entire point.
Toshikatsu Oga, The HORIZONs Co., Ltd. Thirty years a carpenter in Hiratsuka, Japan, now building measurement instead of marketing. ORCID 0009-0000-9180-903X

Top comments (0)