{
  "markdown": "```\n╔════════════════════════════════════════════════════════════════════════════════════════════╗\n║                                                                                            ║\n║                         ███████╗ █████╗ ██╗   ██╗██████╗  ██████╗                          ║\n║                         ██╔════╝██╔══██╗╚██╗ ██╔╝██╔══██╗██╔═══██╗                         ║\n║                         ███████╗███████║ ╚████╔╝ ██║  ██║██║   ██║                         ║\n║                         ╚════██║██╔══██║  ╚██╔╝  ██║  ██║██║   ██║                         ║\n║                         ███████║██║  ██║   ██║   ██████╔╝╚██████╔╝                         ║\n║                         ╚══════╝╚═╝  ╚═╝   ╚═╝   ╚═════╝  ╚═════╝                          ║\n║                                                                                            ║\n║                               does the tool do what it says                                ║\n║                                                                                            ║\n╚════════════════════════════════════════════════════════════════════════════════════════════╝\n```\n\n**DRAFT COPY — pending the owner's pass.** Everything below is measured or\nmechanical, and none of it is a marketing claim, but the wording has not been\napproved.\n\nA publisher declares, in a signed machine-readable contract, exactly what a\ntool does. A harness makes the tool run and checks it against that contract.\nEvery run emits a hash-chained receipt anyone verifies in a browser, with no\naccount, and without trusting the issuer.\n\nThe question it answers is narrow on purpose: **does this tool do what it\nsays?** Not is it safe, not is it good. A tool that refuses to act has not been\nshown to be safe — it has not been shown anything, and SayDo says that in those\nwords rather than returning a green tick.\n\n## What a verdict can be\n\n    pass          the declared behaviour held under the run\n    fail          the tool did something it declared it would not\n    not-covered   the run established nothing either way\n\n`not-covered` is never quietly upgraded to `pass`. Most of the interesting\nfailures in this domain come from silence being read as good behaviour.\n\nAnd a whole run resolves to one of four things, not two:\n\n    CONFORMANT      it did what it said, and it was shown doing it\n    NOT CONFORMANT  it did something it declared it would not\n    INCONCLUSIVE    nothing failed, and nothing was established\n    (revoked / expired, once a registry has spoken)\n\n`INCONCLUSIVE` carries most of the weight. A server that declines every call —\nthe commonest kind in the public sweep — makes no network request, writes no\nfile and starts no process, so a naive harness marks all three as passes and\nissues a warrant for a program that did nothing. SayDo requires a run to have\ndemonstrated *conduct* before any of those checks can pass, and a server\ndescribing itself does not count as conduct.\n\n## In your CI\n\n    - uses: vince-gonzalez/saydo@main\n      with:\n        command: python -m my_server\n        declaration: saydo.declaration.json\n\nThe build fails if the tool breaks its declaration, and also if the run\nestablished nothing — because a green check that proves nothing is worse than\nno check, being a claim of conformance that nobody actually made. Set\n`require-coverage: false` to let such a build through; the receipt still\nrecords it as `inconclusive` and never as a warrant.\n\nThis is the place the tool is worth the most. Coverage comes from the tool\nactually doing its work, which needs the credentials and inputs it accepts —\nand those exist in the publisher's own CI and essentially nowhere else.\n\n## Run it on anything\n\n`saydo` puts one tool under test and writes a receipt. The tool does not have\nto be yours, and it does not have to be an F-Keys one:\n\n    python cli/saydo.py verify --npm  @modelcontextprotocol/server-memory --sandbox\n    python cli/saydo.py verify --pypi mcp-server-time --sandbox\n    python cli/saydo.py verify --command \"python my_server.py\" --declaration mine.json\n\nWith `--declaration`, the author's own contract is the thing being tested.\nWithout one, a deliberately conservative contract is inferred and most\ninvariants come back `not-covered` — the honest result for a stranger poking at\na server they have no credentials for.\n\n`--sandbox` runs the server inside a container whose only route out is a\nrecording proxy (Linux and Docker; the containment is proven in CI, and\n`tools/SANDBOX.md` states plainly what it does and does not cover). Without it\nthe run is *observed*, not enforced, and the receipt says `observed`, so no\nclaim can outrun the mechanism that produced it.\n\nThen check the result yourself:\n\n    python cli/saydo.py status certivl        # the one-shot verdict an agent reads\n    python cli/saydo.py selfcheck             # prove the harness can fail\n\nOpen `verifier/index.html`, paste in the receipt and the anchor, and the page\nrecomputes the entire chain and the signature in your browser. No request\nleaves it.\n\n## The four layers\n\n    1  declaration   what the publisher promises, signed  (spec/)\n    2  harness       makes the tool act, and watches      (tools/harness.py)\n    3  receipt       hash-chained, signed, verifiable     (tools/receipt.py)\n    4  registry      what is still true, and until when   (tools/registry.py)\n\nLayer 4 exists because a receipt is a fact about one moment, and left there it\nanswers the wrong question. Nobody choosing a tool wants to know it behaved in\nAugust. Claims therefore expire at read time, so nothing stays green merely\nbecause no one re-ran it, and a revocation is sticky: publishing a newer\npassing receipt over a withdrawn claim records the attempt and does not lift\nthe withdrawal.\n\n## Two things here that are not standard practice\n\n**The counterfactual.** Watching a tool make a network call tells you it made a\ncall. It does not tell you whether your data left. So the tool is run twice\nwith different inputs, and each destination is classified by whether what it\nreceived changed with the input: `input-dependent` means the payload carried\nwhat you gave it, `input-independent` means it did not, and an opaque body is\nreported `unexamined` rather than clean. *It sent your input to example.com* is\na different sentence from *it made a request*, and only the first one is worth\nanything to the person deciding.\n\nDemonstrated by `seeded/leakserver.py`, which exists because this was claimed\nhere before it was tested. Two tools contact the same host on every call.\n`submit` posts the text it was given; `ping` posts a fixed body:\n\n    FAIL  network.none     egress: ping->example.com; submit->example.com\n    FAIL  data.stays-put   the tool sent its own input data out:\n                           submit -> example.com. Established by changing the\n                           input between runs: example.com is input-dependent\n\n`network.none` sees both and cannot separate them, which is what watching\ntraffic gets you. `data.stays-put` names only `submit`.\n\n**The correction that made this real.** Until 2026-08-31 the marker was planted\nonly in `SAYDO_CANARY`, an environment variable, and the only fixture that ever\ncarried it out read it from there — which is the mechanism the prior art\nalready uses, not a different one. Nothing put the marker into a tool's\narguments, so across 279 third-party servers the proxy searched for a string\nthat had never been in any input, and all 279 came back `unexamined`. The\nmarker now goes into the arguments each run is given, one per run, which is\nwhat the paragraph above always said it did.\n\n**Drift.** A tool redefined under an unchanged version number looks completely\nordinary in any single receipt; the deception exists only as a difference\nbetween two runs. Receipts therefore chain to their predecessor, and a\nredefinition with no version change is graded `serious` rather than noted.\n\nThis one is **not** unusual, and it was listed here as though it were until a\nproper look at the field turned up several projects doing it — see *Adjacent\nwork* below. `askalf/truecopy` pins a vetted tool definition by content hash\nand fails a CI run when the bytes change, which is drift detection by another\nname and shipped before this. The distinctive claim is narrower than it first\nappeared: the counterfactual above, and testing behaviour against a contract\nthe author wrote, rather than checking that the bytes are the ones you vetted.\n\n## Layout\n\n    spec/                  the declaration schema and its field semantics\n    tools/harness.py       the conformance harness      (tools/HARNESS.md)\n    tools/runner.py        local vs contained execution (tools/SANDBOX.md)\n    tools/egress_proxy.py  recording proxy, allowlist enforcement, TLS inspection\n    tools/canary.py        marker generation and body examination\n    tools/differential.py  the two-run counterfactual classifier\n    tools/drift.py         what changed since the previous receipt\n    tools/registry.py      expiry and sticky revocation\n    tools/status.py        receipt -> the compact object an agent reads\n    tools/declare.py       draft a declaration from an observed run\n    tools/discover.py      find MCP servers on npm and PyPI\n    tools/sweep_scale.py   measure many of them, in batches\n    action.yml             SayDo as a GitHub Action\n    verifier/index.html    self-contained browser verifier, no network\n    seeded/malserver.py    a server built to lie, so the harness can be tested\n    seeded/silentserver.py a server that declines everything, so the harness\n                           can be tested against reporting silence as clean\n\n## Verify the verifier\n\n`selfcheck` runs a seeded server that breaks six of its own promises on\npurpose. If the harness reports it conformant, or attributes a finding to the\nwrong invariant, then the harness is broken and no receipt it has ever produced\nis worth anything. The declaration validator is checked the same way, against\nfive mutations it must reject.\n\nA second fixture does the opposite and matters just as much: it starts, lists\nits tools, and declines every call. The harness must report that as\n`INCONCLUSIVE`. When it reported `CONFORMANT` — which it did, and the receipt\nwas signed — the mark meant nothing, since that is what most public MCP servers\ndo when a stranger calls them.\n\nCI asserts both, on the library and on the Action: a build must fail on a tool\nthat lies, and on a run that shows nothing.\n\nA check that cannot fail for the reason you care about is not a check.\n\n## Prior art this builds on\n\n- **TBOM v1.0.2**, Jason M. Lovell, 2026 — 10.5281/zenodo.18459260\n- **CTMS 1.0**, George Kanellopoulos, 2026 — github.com/gkanellopoulos/ctms\n\nBoth are provenance: they establish that tool metadata is what the publisher\nreleased, and both state that verifying behaviour against that metadata is out\nof their scope. That out-of-scope line is what this repository is for. A SayDo\ndeclaration attaches through TBOM's existing `attestations[]` and reuses its\n`ToolDigest` format, so nothing here forks either of them.\n\n## Adjacent work, and where the line is\n\nThis section keeps growing, which is the useful thing about it. Each of these\nwas found after something here had already been described as unusual, so the\nlist is also a record of claims that had to be narrowed.\n\n- **Pipelock**, github.com/luckyPipewrench/pipelock — an agent firewall that\n  mediates a running agent's traffic and emits signed receipts.\n- **truecopy**, github.com/askalf/truecopy — vets a tool definition, pins it by\n  content hash with an optional Ed25519 signature, and fails CI when the bytes\n  change. Paired with **redstamp** for runtime containment.\n- **Proofpane**, github.com/Proofpane/releases — a governance proxy recording\n  every tool call to a hash-chained audit log, exported as an Ed25519-signed,\n  offline-verifiable evidence pack. Closed source; that repository is a\n  download mirror.\n- **dcl-webhook**, github.com/Fronesis-Labs/dcl-webhook — policy verdicts\n  written to a tamper-evident SHA-256 hash chain.\n\nThe line is not the cryptography. A hash-chained, signed, offline-verifiable\nrecord is now a common design, and this repository does not claim otherwise.\n\nThe line is **what the record is about**. Pipelock and Proofpane attest to\n**actions** a deployed agent took. truecopy attests that a tool is **the same\nbytes** you vetted — integrity, which catches a silent update and is silent\nabout a tool that was always misbehaving. A SayDo receipt attests that **one\nversion of one tool did what its author said it would**, tested by running it,\nagainst a contract written before the run.\n\nIntegrity asks *is this the thing I approved?* Conformance asks *does the thing\ndo what it claims?* Both are worth having, and neither answers the other.\n\nWhat still appears to be unshared, on the evidence gathered so far: the\ntwo-run counterfactual that separates *your data left* from *a request\nhappened*, and refusing to call a tool clean when it merely declined to act.\nIf either turns out to have prior art, it belongs in this list and the claim\nabove should be cut rather than defended.\n\nThat first claim was overstated here for a week. The difference from Pipelock\nwas given as *their canary is an environment variable, ours is the tool's own\ninput* — and ours was also an environment variable. It is the tool's input as\nof 2026-08-31, with a fixture that demonstrates the argument case, and the\nsentence is kept honest by that fixture rather than by this paragraph.\n\n## Status\n\nPublic working repository. Receipts produced here are signed with a\nproof-of-concept key held by F-Keys Creative LLC; production signing is not\nsettled. The corpus measurements are a draft pending the owner's pass, and\nevery figure in them is a lower bound — servers were run without credentials,\nso a credentialed server may do more than was recorded.\n\nLicence: the open layers — the declaration schema and spec text (`spec/`), the\nreference tooling (`tools/`), the browser verifier (`verifier/`), and the\nseeded fixture (`seeded/`) — are Apache-2.0 (see LICENSE, NOTICE). Signed\npublic artifacts are not placed under any licence by this repository.\n\n---\n\n```\n╔════════════════════════════════════════════════════════════╗\n║                                                            ║\n║      ███████╗      ██╗  ██╗███████╗██╗   ██╗███████╗       ║\n║      ██╔════╝      ██║ ██╔╝██╔════╝╚██╗ ██╔╝██╔════╝       ║\n║      █████╗  █████╗█████╔╝ █████╗   ╚████╔╝ ███████╗       ║\n║      ██╔══╝  ╚════╝██╔═██╗ ██╔══╝    ╚██╔╝  ╚════██║       ║\n║      ██║           ██║  ██╗███████╗   ██║   ███████║       ║\n║      ╚═╝           ╚═╝  ╚═╝╚══════╝   ╚═╝   ╚══════╝       ║\n║                                                            ║\n║               ·   C  R  E  A  T  I  V  E   ·               ║\n║                                                            ║\n║          ────────────────────────────────────────          ║\n║                                                            ║\n║                      Vincent Gonzalez                      ║\n║                         f-keys.com                         ║\n║                 ORCID 0009-0005-3640-014X                  ║\n║                                                            ║\n╚════════════════════════════════════════════════════════════╝\n```\n\nPart of [F-Keys](https://f-keys.com) — independent hardware, software\nand internet products. See the [working log](https://f-keys.com/log/)\nand [live status](https://f-keys.com/status/).\n",
  "bytes": 15532,
  "sha": "b4882d61a97f212f561f00c0a77eb47de47e8fb2249196bd9eb76389905c09ad",
  "repo_slug": "vince-gonzalez/saydo",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_vince_gonzalez_saydo_6db810c3/readme"
}