The verification report
Generating an SDK once is easy; proving it’s still correct on every regeneration, forever is the hard part — and that proof is the verification report. Glotto regenerates your SDKs, docs site and MCP server in memory, compares against the committed output, and emits a deterministic report that records, for the exact inputs used:
- Pinned inputs — the spec hash (canonical and raw), the
glotto.ymlconfig hash, and the tool version. Two runs over identical inputs produce byte-identical reports. - Per-target results — for each generated target: the drift status (does the committed
output still match what the generator produces?), the managed-file integrity status (has a
checksum-stamped generated file been hand-edited? — your custom code in
lib/is yours and is never flagged), and the compile and contract statuses (not-run/ok/failed— see check results below for how these become real). - Two renderings — a JSON document (
reportVersion: 1, with a machine-readablesummary) and a Markdown rendering for humans. Both land in your CI artifacts, and the Markdown is what appears on the pull request.
Drift and integrity are deliberately distinct signals: drift is checksum-aware, so a hand-edited
managed file with an intact embedded checksum does not drift — only the integrity check
catches hand-edits. The report carries both, and verification exits non-zero when either
fails. See verification report in the CLI reference for the full flag
set, including checkout-free verification of a remote branch (--provider).
What a report looks like
Nothing below is typed by hand. Every pane is what renderVerificationReportMarkdown emitted
over the committed fixture this repo dogfoods, produced the same way your CI produces yours.
a clean regeneration of the committed SDK
A clean regeneration. Six targets, no drift, every checksum-stamped file intact.
- targets 6
- drifted 0
- hand-edited managed files 0
# Glotto verification report
**Result: ✓ verified**
## Inputs
| Input | Value |
|---|---|
| Spec | `./petstore.openapi.yaml` (openapi) |
| Spec sha256 (canonical) | `a7da38d032e4642349d2e4531a12400d2634905b9d78dadc681b571c1d064760` |
| Spec sha256 (raw) | `88fd4f083e7abc781f553ba21280d697d86116c40b9ced90f18a4f06f7e472f8` |
| Config | `glotto.yml` |
| Config sha256 | `a243677cd26512cf42a63076089373818acb76aeba92de35441555cd5945cc77` |
| Tool | glotto 0.0.0 |
## Targets
| Target | Kind | Files | Drift | Custom code | Compile | Contract |
|---|---|--:|---|---|---|---|
| cli | sdk | 9 | ✓ in-sync | none | not-run | not-run |
| graph | sdk | 4 | ✓ in-sync | none | not-run | not-run |
| mcp | mcp | 38 | ✓ in-sync | ✓ intact (31 managed) | not-run | not-run |
| root | root | 1 | ✓ in-sync | none | not-run | not-run |
| spec_repo | sdk | 10 | ✓ in-sync | none | not-run | not-run |
| typescript | sdk | 18 | ✓ in-sync | ✓ intact (11 managed) | not-run | not-run |
renderVerificationReportMarkdown (@glotto/cli) packages/cli/tests/fixtures/drift-gate 43c4d424bd49 compile and contract read not-run above because building a report never runs a toolchain —
the report says what it verified and what it did not, rather than implying more. Real statuses
enter as a declared input, and then the same report carries them:
the same regeneration, with the TypeScript target's compile status folded in from a real `tsc --noEmit` run
The same regeneration, with the TypeScript target's compile status folded in from a real tsc run. One status attested; the rest still say not-run, because they were not.
- targets 6
- compile statuses attested 1
- failed checks 0
# Glotto verification report
**Result: ✓ verified**
## Inputs
| Input | Value |
|---|---|
| Spec | `./petstore.openapi.yaml` (openapi) |
| Spec sha256 (canonical) | `a7da38d032e4642349d2e4531a12400d2634905b9d78dadc681b571c1d064760` |
| Spec sha256 (raw) | `88fd4f083e7abc781f553ba21280d697d86116c40b9ced90f18a4f06f7e472f8` |
| Config | `glotto.yml` |
| Config sha256 | `a243677cd26512cf42a63076089373818acb76aeba92de35441555cd5945cc77` |
| Tool | glotto 0.0.0 |
## Targets
| Target | Kind | Files | Drift | Custom code | Compile | Contract |
|---|---|--:|---|---|---|---|
| cli | sdk | 9 | ✓ in-sync | none | not-run | not-run |
| graph | sdk | 4 | ✓ in-sync | none | not-run | not-run |
| mcp | mcp | 38 | ✓ in-sync | ✓ intact (31 managed) | not-run | not-run |
| root | root | 1 | ✓ in-sync | none | not-run | not-run |
| spec_repo | sdk | 10 | ✓ in-sync | none | not-run | not-run |
| typescript | sdk | 18 | ✓ in-sync | ✓ intact (11 managed) | ok | not-run |
renderVerificationReportMarkdown (@glotto/cli) packages/cli/tests/fixtures/drift-gate ea9ae2632587 And when a managed file has been hand-edited in place, the report is where you find out — drift alone will not tell you, because drift is checksum-aware:
a managed file hand-edited in place — `typescript/src/client.ts` no longer hashes to its embedded checksum
One managed file edited without restamping. Nothing drifted; the integrity check caught it and the report fails.
- targets 6
- drifted 0
- hand-edited managed files 1
# Glotto verification report
**Result: ✗ verification failed**
## Inputs
| Input | Value |
|---|---|
| Spec | `./petstore.openapi.yaml` (openapi) |
| Spec sha256 (canonical) | `a7da38d032e4642349d2e4531a12400d2634905b9d78dadc681b571c1d064760` |
| Spec sha256 (raw) | `88fd4f083e7abc781f553ba21280d697d86116c40b9ced90f18a4f06f7e472f8` |
| Config | `glotto.yml` |
| Config sha256 | `a243677cd26512cf42a63076089373818acb76aeba92de35441555cd5945cc77` |
| Tool | glotto 0.0.0 |
## Targets
| Target | Kind | Files | Drift | Custom code | Compile | Contract |
|---|---|--:|---|---|---|---|
| cli | sdk | 9 | ✓ in-sync | none | not-run | not-run |
| graph | sdk | 4 | ✓ in-sync | none | not-run | not-run |
| mcp | mcp | 38 | ✓ in-sync | ✓ intact (31 managed) | not-run | not-run |
| root | root | 1 | ✓ in-sync | none | not-run | not-run |
| spec_repo | sdk | 10 | ✓ in-sync | none | not-run | not-run |
| typescript | sdk | 18 | ✓ in-sync | ✗ modified (1 of 11 managed) | not-run | not-run |
### Modified managed files: `typescript`
- `typescript/src/client.ts` — hand-edited (embedded @glotto:generated-checksum no longer matches)
renderVerificationReportMarkdown (@glotto/cli) packages/cli/tests/fixtures/drift-gate 3a6c95e8e1a5 Where the report lands
The same report is delivered on four surfaces, so the proof is visible wherever you review a regeneration.
In CI, on every PR
The CI workflows Glotto emits (the CI workflow Glotto emits) gate every PR/MR with verification and publish the report to the job summary and the build artifacts — on GitHub, GitLab, Bitbucket, and Azure alike. The check fails on drift or a hand-edited managed file, and the report is attached even when the job fails. See Drift detection for the gate itself.
On the release PR, as a comment
Glotto’s hosted verification can post the report directly on your open release PR — the place you actually review a regeneration. The semantics are deliberately conservative:
- Opt-in, default off. Commenting writes to your repository, so it only happens when you
enable the verification source’s
pr_commentflag — it is never on retroactively. - One comment, refreshed in place. The comment is marker-tagged per project: every succeeded verification run — and every checks ingest into the latest run — updates the same comment rather than stacking a new one per run.
- Best-effort. A comment failure (no open PR on the verified branch, a provider error) never changes the verification run, the ingest result, or the API response — the report is the record; the comment is a convenience.
In the console
The project’s surface health panel shows the latest verification runs — verified or not,
which targets drifted, per-target compile/contract statuses — rendered honestly from the stored
report (a status that never ran shows as not-run, never as a green check).
As a signed attestation
Any stored successful run can be downloaded as a signed attestation: a single
self-contained JSON file — a DSSE v1 envelope, Ed25519-signed — that binds the exact report
document to the run that produced it. Verify it offline against the public key served by the
unauthenticated GET /v1/verification/attestation-key endpoint; the envelope is standard DSSE,
so a stock crypto library is all an auditor needs. This is the artifact an enterprise buyer can
file: cryptographic evidence that a given SDK surface was verified against a given spec.
Verify it with one command — glotto verify-attestation attestation.json — which resolves the
signing key by the fingerprint the envelope itself records (so an artifact stays verifiable across a
key rotation), works completely offline from an archived key
file, and reports a revoked key as a distinct fact from a bad signature.
Counter-signed by you, not just by us
A single-party attestation is only as trustworthy as the party issuing it. You can co-sign an attestation with your own Ed25519 key, so the artifact carries a signature that does not depend on trusting Glotto:
glotto verify-attestation attestation.json --countersign my-key.pem --upload
Three things this deliberately does:
- Verification runs first. An envelope that failed verification is never counter-signed — the command has no mode that vouches for something it did not check.
- The signature is verified before it is stored. Your co-signature is checked against the run’s own payload server-side; an unverifiable one is rejected rather than filed, because an envelope that looks multi-party while proving nothing is worse than one that carries no counter-signature.
- A co-signature binds one exact payload. If a later CI check-results upload changes the report,
every counter-signature over the old bytes is dropped from the envelope and listed as
stalerather than quietly served — re-run the command to sign the new payload.
A counter-signature proves who else vouched for these exact bytes. It carries no timestamp and is not a trusted clock: Glotto deliberately ships no RFC 3161 or transparency-log integration, so nothing in the artifact claims to establish when it was signed.
Real compile & contract statuses (the checks loop)
verification never runs a compiler — your SDKs actually compile, and the contract suites
actually run, in your CI, where the toolchains and the generated output live. So the
compile/contract statuses enter the report as a declared input: your CI produces a
checksVersion-1 check-results document recording per-target ok/failed outcomes, then
either
- folds it into the report before rendering, where a
failedstatus makes verification fail; or - posts it to the hosted run —
POST /v1/projects/:projectId/verification-runs/:runId/checksfolds it into the stored report, and the updated statuses ripple to the console panel, the attestation, and the release-PR comment.
The document can pin the spec hash it was produced against, so results from different spec bytes are rejected rather than silently folded in.
Verified provenance in CI
When your CI posts check-results to the hosted run, the control plane can verify which CI run produced them — not merely that a valid API token uploaded them. Your runner presents a provider-signed CI-run OIDC token alongside the upload; the server checks it against the provider’s public keys and marks the run verified, and that verified origin is bound into the signed attestation an auditor downloads.
- Turn-key on GitHub and GitLab. The workflows Glotto emits wire this for you: the GitHub
template grants the job
id-token: writeso the runner can mint an OIDC token, and the GitLab template pre-mints one via anid_tokens:block. With hosted mode enabled (a project id + API token), verified provenance is on by default — there is nothing else to configure. - Manual on other CI. On Bitbucket, Azure Pipelines, a self-hosted runner, or any custom CI,
inject a pre-minted CI-run OIDC token into the environment variable
GLOTTO_CI_OIDC_TOKEN; hosted mode presents it automatically. (GitHub’s request-and-exchange path readsACTIONS_ID_TOKEN_REQUEST_*instead, so it needs no explicit token variable.) - The audience contract. The token’s
audclaim must equal your Glotto API URL, which must in turn match the server’sCI_OIDC_AUDIENCE. For the default hosted service that ishttps://api.glotto.dev— already the value the emitted templates use, so there is nothing to set. If you self-host the control plane, set the token audience (the GitLabid_tokens:aud, orGLOTTO_API_URLfor GitHub’s exchange) to your own API URL and configure the server’sCI_OIDC_AUDIENCEto match.
Provenance verification is optional and non-breaking: with no token the upload still succeeds — the run is simply recorded unverified rather than verified.
Why this works
The report is only as trustworthy as the regeneration behind it, and that’s the point of the deterministic pipeline: a byte-stable canonical spec means regeneration is reproducible, so a drift result is a real change and a byte-identical report is a real guarantee — not noise.