F-Keys\Tools\SayDo _◻✕
← Back Forward → ↑ Up Home Find Status Log
Address 📁 F-Keys\Tools\SayDo

SayDo

Signing proves a tool's code is what the publisher released. It does not prove the tool behaves as its description promises. SayDo tests that second claim: a contract per tool, a harness that checks the tool against it, and a hash-chained receipt anyone can verify without trusting us.

Working proof of concept · pip install saydo · name provisional · declarations are drafts · receipts are Ed25519-signed with a proof-of-concept key · no trust mark issued

The gap

Signing and provenance prove a tool’s code is what the publisher released. They do not prove the tool behaves as its description promises. Tool poisoning, capability rug-pulls and silent behavioral drift are one problem wearing three names: the distance between what a tool declares and what it does.

The provenance standards say so themselves. TBOM states it cannot verify that tool behavior matches descriptions. CTMS states it verifies that a tool’s claims have not changed, not that they are true. That out-of-scope line is what this is for.

SayDo does not replace either. It extends them: a declaration binds to the same tool-definition digests a Tool Bill of Materials already records, and attaches to one without changing its schema.

Four layers

DeclarationA signed, machine-readable contract per tool: the behavior it is permitted to show — no network, writes only here, deterministic, returns errors as values. Thirteen invariant types in the current draft.
ConformanceA harness exercises the tool under observation — valid calls, adversarial input, egress and filesystem monitoring, and reports pass, fail or not-covered per invariant. It cannot prove an invariant it did not exercise, and it says so rather than passing it.
ReceiptEvery run emits a hash-chained record, one row per verdict, chained by row_hash = sha256(prev + row). Edit one row and the chain breaks at it. An auditor re-verifies it in a browser — no account, no request to us.
Trust markThe registry, and what it is still willing to say. tools/registry.py publishes an entry from a receipt, stamps it with an expiresAt so the claim goes stale on its own, keeps a revocation sticky against a later passing receipt, and returns a badge line that carries its evidence rather than a bare grade. Nothing is served and no mark has been issued to anybody. The mechanism exists, the hosted registry does not.

Run it

Install one of the covered servers, then put it under SayDo. The command captures the tool definitions, exercises the server, and writes a receipt.

saydo verify certivl
  certivl   CONFORMANT
  tally     {'pass': 9}
  receipt   receipts/certivl.receipt.jsonl
  head      353e94cd497115aa49e10ab76874ab7bd9645fed…

A harness that only ever passes is worth nothing, so it ships with a server built to fail. saydo selfcheck runs that one and requires the harness to catch every violation — the same discipline the rest of this catalog is built on, pointed at itself.

A receipt, actually

This is the receipt for saydo verify certivl, from the repository. Thirteen rows: the declaration it was checked against, the captured tool definitions, what the monitor could and could not see, one row per invariant, and a close. Each row carries the hash of the row before it.

#rowprev_hashrow_hash
1openpkg:pypi/[email protected]31b446b87a387b3d…2911aee89d746f02…
2capture2911aee89d746f02…8bbcbdebf275fae5…
3monitor8bbcbdebf275fae5…8db9473951715e22…
4verdictrefusal.scope8db9473951715e22…a192845eb5d858e8…
5verdictnetwork.nonea192845eb5d858e8…94ff9d07b07e1140…
6verdictwrites.none94ff9d07b07e1140…2792b46310d90e1e…
7verdictreads.none2792b46310d90e1e…6953d604c4a27794…
8verdictsubprocess.none6953d604c4a27794…fe0193b67cfeab17…
9verdictanswers.deterministicfe0193b67cfeab17…e83fd3722b937d43…
10verdicterrors.are-valuese83fd3722b937d43…8d7fb1429449889e…
11verdictundecided.on-overlap8d7fb1429449889e…27850d284c2f007b…
12verdictdecimal.read-exactly27850d284c2f007b…6f174eaa56827918…
13closetally {'pass': 9}6f174eaa56827918…754a675f79ea0f92…

Every prev_hash above equals the row_hash on the line before it — checked, not asserted. Change any row and every hash below it stops matching, which is the whole mechanism. Paste it into verifier/index.html and it checks offline, with no account and no request to anybody.

What the monitor admits it cannot see

Row 3 is not a result. It is the harness recording its own blind spots — that it observes filesystem opens and socket connects at the host process, and does not observe activity below the Python runtime, such as a native extension. An invariant it did not exercise is reported not-covered rather than passed.

What it is not, yet

This is a working proof of concept and the name is provisional. Every declaration currently carries status: "draft" and none of them carries a supplier signature. Receipts are signed, but with a proof-of-concept key held by F-Keys Creative LLC, and production signing is not settled. The registry runs, and nothing is served from it. Nothing here is a claim of conformance about anybody’s software, including ours.

Those sentences are on this page in the same size type as the rest, because a tool whose entire purpose is the distance between what software claims and what it does would be a poor place to start overstating.

Prior art it builds on

TBOM v1.0.2, Jason M. Lovell, 2026. 10.5281/zenodo.18459260. CTMS 1.0, George Kanellopoulos, 2026. gkanellopoulos/ctms. Both are provenance; both state that verifying behavior against the metadata is out of their scope.

How it works

  1. Declare what the tool may doA machine-readable contract per tool: no network, writes only here, deterministic, errors returned as values. Thirteen invariant types in the current draft.
  2. Put the tool under the harnesssaydo verify certivl captures the live tool definitions, exercises the server with valid and adversarial input while watching egress and the filesystem, and reports pass, fail or not-covered per invariant.
  3. Hand somebody the receiptEvery run emits a hash-chained record, one row per verdict. They check it in a browser with no account and no request to us.

What is actually built

Thirteen invariant typesno-network, network-allowlist, no-write, write-scope, read-scope, no-subprocess, subprocess-scope, deterministic, error-as-value, refusal-tool, no-data-egress, output-hygiene, property.
Eleven declarationsSix F-Keys MCP servers, one seeded fixture and four third-party ones. All draft and all unsigned, and the page says so because they are.
A GitHub Actionaction.yml, published as SayDo conformance. The build fails if the tool breaks its declaration, and also if the run established nothing.
A sandbox--sandbox runs the server in a container whose only route out is a recording proxy. Linux and Docker. Without it the run is observed rather than enforced, and the receipt records which it was.
A data-egress counterfactualThe tool is run twice with different inputs, and each destination is classified by whether what it received changed with the input. It sent your input to example.com is a different statement from it made a request.
A chain that breaks visiblyrow_hash = sha256(prev + row). Edit one row and every hash below it stops matching.
It reports what it did not testAn invariant the harness could not exercise comes back not-covered rather than passed. A harness that only ever passes is worth nothing.
It admits its blind spotsThe receipt records that the monitor does not see below the Python runtime — a native extension is outside what it can observe, and it says so in the artifact.
Offline verificationA self-contained browser verifier. No account, no network, no request to anybody, including us.

Specifications

StatusWorking proof of concept — name provisional
Installpip install saydo
Declarations11 — 6 F-Keys servers, 1 seeded fixture, 4 third-party; all draft, all unsigned
ReceiptsEd25519-signed with a proof-of-concept key; production signing not settled
Invariant types13 in the current draft
LicenseApache-2.0 on the open layers
Sourcevince-gonzalez/saydo

Questions

Does this replace TBOM or CTMS?
No. It extends them. Both are provenance and both state that verifying behavior against the metadata is out of their scope — that line is the reason this exists. A declaration binds to the same tool-definition digests a Tool Bill of Materials records.
Is anything here certified?
No. Every declaration carries status: "draft" and carries no supplier signature. Receipts are signed, but with a proof-of-concept key; production signing is not settled. Nothing here is a claim of conformance about anybody's software, including ours.
What about the trust mark?
The mechanism is built and nothing is issued. tools/registry.py publishes, revokes and looks up an entry, writes an expiresAt so a claim goes stale rather than staying green, keeps a revocation sticky against a later passing receipt, and returns one badge line that never reads as a bare grade. What does not exist: anywhere to serve it from, a mark on anybody's page, and a key anyone should rely on.
Why should I believe the harness?
Do not. Run saydo selfcheck. It ships a server built to fail and requires the harness to catch every violation.
Is the name final?
No. It was WARRANT, it is SayDo, and it is marked provisional because it is.
1 item Log  ·  Status F-Keys