This is the design of Provostry Verify, published as it is written for the people who build it. It says what a verdict proves, who is allowed to produce one, what the engine refuses to pretend, and where the edges are. A sample report shows the output end to end.
01 · The gap this closes
Provostry's ledger is tamper-evident: it proves who said what, and that nobody edited it afterward. It does not prove that what was said is true. An agent that appends evidence_bundle { verified: true } is grading its own homework — the chain will faithfully preserve a lie.
Verify adds the missing half: a system actor that checks claimed outcomes against reality and records what it actually observed, in the same append-only chain as the claims themselves.
02 · The loop
verification_contract: the verbatim goal plus 1–12 observable pass/fail clauses that define "done". Contracts are immutable, versioned revisions; amending one produces a new version that supersedes the old, and every verdict is bound to the exact content hash it judged — so a moved goalpost invalidates prior verdicts rather than silently inheriting them.verification_observation with human-readable evidence: what was probed, what came back, how long it took, how many attempts. Engine-only — the generic ingest path refuses this type.verification_verdict: pass or fail, nothing else. Pass requires every critical clause to pass. An errored or inconclusive critical clause is a fail — missing evidence blocks, it never softens into "partial". Partial success is a fail with a clause-level breakdown attached.evidence_bundle claims verified: true on a work item that has contracts, the server appends an escalation for every contract that does not back the claim: no passing verdict, still awaiting approval, or rejected by a human. "Never approved" is a weaker basis for a completion claim than "failed", not a stronger one. Every contract on the item is checked, not the first, so a decoy contract with one trivially passing clause cannot mask a real one beside it. The claim still lands in the ledger — evidence is never refused — it just cannot pass silently.| Status | Meaning | Counts as verified work |
|---|---|---|
verified | Contract(s) approved and passing, or a passing CI attestation on contract-less work | Yes |
self_reported | Claimed, but nothing independent backs it | No |
unverified | Claimed against a contract that is unapproved, rejected, stale or failing, or against failing CI | No |
not_claimed | The bundle made no completion claim | No |
evidence_bundle may name the commit and repo it describes. For a repository the organization has registered as a trusted CI source, the server fetches that commit's check runs from GitHub itself and appends a ci_attestation under a reserved actor — refused at generic ingest, like a verdict. The organization may name the checks that must succeed per repository; without a list, every completed check must succeed and at least one must actually have run, so a skipped-only run never certifies anything. A contract, when present, stays authoritative — CI passing does not rescue a failing or unapproved definition of done. An unregistered repository is never fetched: a green build somewhere the organization never vouched for proves nothing about its work.
Nor does the poster's say-so bind a commit to work. A commit counts for a work item only if the repository declares that item or the commit message names it; anything else is recorded unbound and certifies nothing. And only the commit's author may claim it: a human session is matched to the author by the email the server verified at sign-in and nothing else (a name or login is free text to anyone with push access), an agent by the name or login the organization's own sync uses. A repository may list trusted posters (an organization's CI bot) who may cite any commit. Anyone else's claim is recorded foreign, certifies nothing, and reserves nothing — ledger arrival order never decides ownership. A commit certifies one evidence event, ever. "No check runs yet" is pending and re-examined for up to an hour before it is none, because the first look often lands before CI has registered a run, and a terminal answer there would be wrong forever.
03 · The trust model, stated plainly
.., no query strings) and the resolved URL must stay inside the registered base path, so a source pins a scope, not just a host. An agent that could hand the verifier a URL could point it at a fixture server and "pass" anything.requiresApproval) need a human decision_record before the engine will run them; approval events from non-human actors are ignored. Once any version of a contract required approval, every later version does too: superseding an approved contract with a weaker revision puts the new version back in the approval queue, it does not slip it past. Decisions are namespaced by subject kind, so a decision about some other object that shares the id never counts.hosted today: the server itself ran the probe). Future levels — a customer-side sidecar, self-report — will be labeled as such, so a reader can weigh evidence by who produced it rather than treating all green as equal.04 · Temporal semantics
Clauses carry time bounds instead of pretending a single instantaneous read is truth:
withinMs — a deadline: the engine polls until the assertion first passes or the deadline lapses.stableForMs — a stability window: after a first pass, the engine waits and probes again; both reads must pass. This catches the value that flickers true during a deploy and false after.Evidence strings record attempt counts and elapsed time, so a verdict shows when reality was sampled, not just what it looked like. A whole run shares a 120-second wall-clock budget across its clauses; evidence is truncated (bounded entries, bounded length — observed values are excerpted, never dumped) and response bodies are read through a streaming size cap. The verdict also records the origin each named source resolved to at run time, so a later re-pointing of a source name is visible across verdicts rather than silently inheriting their credibility.
05 · Limits
A clause author cannot raise one, and a contract that asks for more is refused or errored rather than quietly granted.
| Bound | Value | Why |
|---|---|---|
| Per-request timeout | 10 s | Tightened further when the run budget has less left than that. |
| Response body | 256 KB | Read as a stream with a running byte counter; the transfer is aborted mid-flight, not measured after the memory is spent. |
| Poll interval | 1 s | Between re-probes while a withinMs deadline is open. |
withinMs | ≤ 60 000 ms | Schema ceiling per clause. |
stableForMs | ≤ 30 000 ms | A window the remaining budget cannot honour is an error, never a pass on partial evidence. |
| Whole run | 120 000 ms | One shared budget across every clause. A clause the budget never reaches is recorded error with that stated as its evidence. |
| Evidence | ≤ 20 entries × 400 chars | Observed values are excerpted at 200 chars. One clause may never dominate the ledger. |
| Redirects | never followed | A 3xx is a result, not a hop, so a source cannot drag the probe to a host the registration guard never saw. |
Plaintext http:// | the platform operator's loopback only | An organization's sources are https, and never the server's own loopback by any spelling. |
| Contract versions | jump warns, backslide ignored | v1 → v3 is accepted with a warning; a lower or equal version is ignored as immutable. |
| Gate escalation | deduped on structured fields | Never on prose, so an agent cannot pre-silence the gate by copying its wording. |
| Clause outcomes | pass, fail, error, unknown | unknown on a critical clause fails the verdict like error. |
06 · The probe DSL is closed
Probes and assertions are data: one probe kind today (http GET/HEAD against a registered source's base URL plus a path) and three assertions (status_equals, json_path_equals, json_path_exists over a bounded dotted path). Every check is deterministic — the engine compares what came back to what the clause said, and no model reasons about whether it "looks right". New probe kinds are added server-side, deliberately, with their own evidence schemas — not by widening what callers may pass.
07 · Your first check, without a founder
An organization's owner or admin registers an evidence source of its own — an https base URL, optionally with a credential header whose value is encrypted at rest with the same key as every other customer secret and never returned by any API. The server proves it can reach the source before storing it. Every request to a customer-named host — the reachability check and every later probe — is resolved first, every address it resolves to is checked against the private, loopback, link-local and cloud-metadata denylist, and the connection is pinned to those addresses, so a hostname the organization controls, or DNS rebinding, cannot point the server at an internal host. A refusal says only that the destination is not allowed; a failed reachability check says only that the source did not answer. The route is not a scanner.
Contracts come from templates over the same closed DSL — "the endpoint answers 200", "a JSON field has the expected value" — bound to a work item and authored by the human in the session. The dashboard walks exactly this path: a source you own, a check from a template, a run, a verdict the server wrote.
08 · API surface
| Route | What it does |
|---|---|
POST /api/v1/events (type verification_contract) | Register a contract through normal ingest. |
POST /api/v1/verify/contracts | Build a contract from a template, bound to a work item, authored by the human in the session. |
POST /api/v1/verify/:contractId/decision | Human approve or reject — required before the engine will run a contract flagged requiresApproval. A rejection parks it; a later approval reinstates it. Recorded under the caller's own identity. |
POST /api/v1/verify/:contractId/run | Execute the probes; append observations and a verdict. Rate-limited; whole-run budget; 409 while a contract awaits approval or after a rejection. |
GET /api/v1/verify | Materialized contracts with status, latest bound verdict, and clause results. |
PUT /api/v1/verify/sources/:name · GET /api/v1/verify/sources · GET /api/v1/verify/templates | An organization's own sources (owner/admin; reachability-tested; credential encrypted, never returned), the list every member may read (names and hosts, never a credential), and the templates. |
PUT /api/v1/integrations/github | Register the repositories whose CI may attest the organization's work, the checks that must succeed, and any trusted posters. |
The integration page has the event shapes and a worked example; the sample report shows what comes out.
09 · What Verify deliberately does not do