Attestation
Most of a real application's read API is filtered reads, and a filter's meaning is not in the code's structure. Attestation lets a named person assert that meaning in a committed, reviewed file. The tool carries the claim exactly, labels it on every surface, and still proves everything around it.
When to use it
Your survey refused a read you know is safe and useful: a filtered list, a read that returns a view object the structure cannot tie to an entity, a read that delegates to a remote service. Structure cannot prove what it selects or returns, so discovery will never expose it. A person who knows can say so, on the record.
Attestation shipped in 0.3.0. It adds to discovery and never changes it: every attestation phase measured byte-identical generated output on all three benchmarks.
Reads only
A write or an action cannot be attested. That boundary needs a policy gate that does not exist yet, and an assertion is not a policy gate.
What the tool still proves
An attestation replaces one missing fact, not the whole proof. Around every entry the tool still checks:
- the target resolves to exactly one method declaration in your source, and the binding is structural;
- the input types, and that your entry covers every modeled input exactly;
- read-safety over everything it read: if it sees a write there, the entry is refused;
- that the subject entity you name exists, and that your claim does not contradict what the code provably reads;
- a by-primary-key load, wherever your entry claims an identifier.
If the structural evidence contradicts an assertion, at intake or after a code change, the attestation is refused loudly and nothing ships. The tool never silently prefers either side.
The four categories
| Category | What the human asserts |
|---|---|
predicate | What a filtered read selects. |
shape | What a read returns, for a read structure cannot tie to an entity. |
both | A filtered read whose return type also cannot be tied: one entry, one name, closing both holes. |
boundary | A remote delegation, including that it is read-only, which is the one property the tool cannot check for that shape. It needs a named security owner and an explicit affirmation, and it is not exposed unless you opt in. |
An entry
The record lives at tibyan/attestations.json in your application, a JSON array, committed beside the source it describes.
[
{
"target": "com.example.billing.service.InvoiceServiceImpl#getInvoicesByBuilding(String, Integer, Integer)",
"category": "predicate",
"name": "invoicesByBuilding",
"resultSemantic": "Invoices belonging to one building, ordered by date descending and returned one page at a time.",
"parameters": {
"buildingId": "The numeric identifier of the building whose invoices are returned, passed as a string.",
"pageNum": "paging",
"pageSize": "paging"
},
"provenance": {
"assertedBy": "a.reviewer",
"origin": "human-authored",
"date": "2026-09-01",
"fingerprint": ""
}
}
]| Field | Rule |
|---|---|
target | Must resolve to exactly one method: <fully qualified class>#<method>(<parameter types>). Zero or two matches refuse. |
name | Yours to mint. The tool adds the attested_ prefix itself (attested_boundary_ for a boundary entry), so do not include it. |
resultSemantic | The sentence that reaches the agent, verbatim. See the next section. |
parameters | Must cover every modeled input exactly. Each value is a sentence, or one of two reserved marks: "paging", only on a structurally recognised paging or ordering parameter, and "id", only on an identifier input where the tool can prove a by-primary-key load. |
subjectEntity | The entity you assert the read is about. It must exist in the model and must not contradict what the code provably reads. |
securityOwner, readOnlyAffirmed, assertedAuthorities | Boundary entries only. Silence is never an affirmation: an absent or false readOnlyAffirmed refuses. |
provenance | Who asserted it, how the words were produced (human-authored, ai-draft, or ai-drafted-human-confirmed), the date, and the fingerprint. Leave the fingerprint empty: the CLI stamps it. |
Writing the sentence
The tool description an agent reads is built from resultSemantic, behind the label "Human-attested, not tool-verified". A vague sentence produces confident wrong answers. A good one says what comes back and how many, what is silently excluded, what an omitted parameter defaults to, and whether the result is paged or capped.
| Sentence | |
|---|---|
| Too vague | Returns the costs for a building. |
| Useful | Collected, outstanding and overdue totals for one building's rental payments, computed over at most the first 25 matching rows because the read leaves the default page size in place; it is not a summary of all of a building's payments. |
The workflow
- Write the entry in
tibyan/attestations.json. - Stamp it. This computes the fingerprint of the code the claim is about and writes it into the file.
- Re-scan. The survey reports the entry as attested, or refuses it with a located reason.
- Commit the file and review it like code. Your next build carries it into the manifest.
tibyan attest /path/to/app --date 2026-09-01
tibyan scan /path/to/appThe command reference is in the CLI docs.
Fingerprints and drift
The fingerprint is a SHA-256 over the structural slice the claim covers. It is recomputed on every scan. When the code under an attestation changes, the attestation stops matching and is refused loudly rather than carried, so a claim can never silently outlive the code it was made about. Re-read the method, confirm the sentence is still true, and stamp again.
Drafts
An entry whose origin is ai-draft is a third thing, neither attested nor refused. It is queued in the survey, counted apart, and ships nothing: no capability, no manifest entry, no OpenAPI operation. Any drafter works, human or otherwise, so long as the entry carries that origin. Confirmation is a per-entry, named human act:
tibyan attest /path/to/app --confirm "<target>" --by a.reviewerThere is deliberately no confirm-all, in the CLI or the API beneath it. If you want an AI agent to do the drafting, the repository has a guided adoption prompt (opens in a new tab) that keeps every entry a draft until a person confirms it.
What ships, and how it is labeled
- The MCP tool name itself starts with
attested_, so the class is readable before any description is. - Every description and document says human-attested. It is never presented as verified and never counted among the verified capabilities.
- The OpenAPI document serves attested capabilities under
/attested/<name>. - An attested capability executes exactly like a verified one, with the same access. The label changes what the tool claims about a call, never how the call is made.
Exposure controls
Three runtime properties decide what registers: tibyan.attested.expose (default true), tibyan.attested.exposeSecurityUnverifiable (default false), and tibyan.attested.exposeBoundary (default false). They are described in the runtime reference.
Who owns what
| Owner | Responsibility |
|---|---|
| The tool | Faithful carriage of your file: the claim you ship is exactly the claim you committed, attached to exactly the method the entry targets, labeled, and invalidated the moment the fingerprinted code changes. |
| The asserter | The claim's truth. If the assertion is false, the tool cannot know, will carry the false sentence exactly, and an agent will act on it. |
| Your review process | The integrity of the file. The tool does not authenticate who typed an entry, which is exactly why the record is a committed, reviewed file and not tool state. |
The bound, at its true strength
Before accepting an assertion the tool refuses if it sees a write in what it read. A write hidden below what it read, in a hand-written repository or query-builder implementation, is not caught. The guarantee is "no write in what the tool read", not "no write anywhere". Review attestations with the same seriousness as code.
The authoritative text is docs/SECURITY.md and the changelog (opens in a new tab) in the repository. If this page and that document ever disagree, the repository wins.