Evidence connectors

Connectors are typed, read-only fact collectors. They return raw evidence plus a tamper-evident provenance receipt; they never decide whether a claim is true. The deterministic claim judge remains the only component allowed to issue a verdict.

Every receipt records the connector and version, evidence capability, credential-free source identity and revision, observation time, freshness basis, a plain description, and a unique evidence ID plus request, response, artifact, and combined SHA-256 digests. The artifact digest lets a passport reader match a receipt to an emitted or minimized test without publishing rejected test code, commands, or raw logs a second time. Security metadata is explicit rather than inferred: the local development executor reports host_subprocess, while the production Docker executor reports container; both operate on a disposable source copy. Docker reports network and credential access as disabled. The development-only local executor truthfully reports host_unrestricted network and ambient_host credentials rather than claiming process containment it does not provide.

The test adapter wraps candidate and minimization execution. Environment preparation, suite preflight, mutation testing, and the flip judge remain unchanged. Network-enabled test requests are rejected at this connector boundary.

The Git metadata adapter is the first non-test evidence source. It reads an already-local checkout only: no clone, fetch, credential helper, or other network operation. Fixed-argv, time-bounded Git object commands resolve an immutable commit and record its tree, parents, author/committer timestamps, and raw changed-path entries without names or email addresses. Hooks, pagers, global/system config, external diff helpers, prompts, and optional locks are disabled. Output is hard size-bounded at the subprocess file limit, process groups are killed on timeout, and untrusted revisions are rejected before Git starts. Partial/promisor repositories and object alternates are rejected so a metadata read cannot silently fetch a missing object or cross into another object store. Because v1 has no OS network sandbox around Git itself, its receipt truthfully reports host_unrestricted network and ambient_host credential access despite the connector’s local-only command set. V1 supports standard SHA-1 repositories with UTF-8 paths; other object formats, linked worktrees, and non-UTF-8 path bytes fail closed rather than being normalized.

The CI status adapters are the first remote evidence sources. The GitHub implementation reads check runs; the GitLab implementation reads the commit-status endpoint. Each performs outbound HTTPS GETs only: no write scope, re-run trigger, or artifact download. Plain HTTP is refused unless the host is a numeric loopback address, so a self-hosted forge can be pointed at locally without silently downgrading a real one. Ambient proxies and redirects are disabled, the aggregate response is size-bounded, and the check count is capped. Oversized input is rejected rather than truncated so a partial read can never look complete. Credentials come only from a named environment variable, are validated for control characters, and never reach the payload, receipt, or an error message; a connector configured without one truthfully reports credential_access: none. Because reads happen in the engine process rather than a subprocess or container, receipts report isolation: in_process.

GitLab project paths are URL-encoded as a single API path component. The adapter requests the latest status per name with an explicit stable order and 100-item page size, then follows every rel=next link. Each next link must stay on the original origin and endpoint, retain the exact allowlisted query, and increment the page by one. Empty intermediate pages, cycles, more than 32 pages, more than 250 statuses, and cross-origin or parameter-injecting links fail closed. The raw-artifact commitment length-prefixes every page before hashing, so page boundaries are authenticated. These rules follow GitLab’s documented commit-status endpoint and REST pagination contract.

The adapters report what the forge said and stop there. They record each run’s name, normalized status/conclusion, and timestamps, and never aggregate those runs into a “CI passed” claim — that inference belongs to a judge, not a collector. Check runs are sorted by name so the evidence digest does not depend on the order the forge happened to return. GitLab’s overlapping states map conservatively onto the existing vocabulary: pending/running remain incomplete, while success, failure, cancellation, and skip map to their equivalent completed conclusions. Any new valid GitLab state is retained as an explicit gitlab:<state> incomplete status, which makes release policy UNCERTAIN instead of guessing. Unlike Git metadata, CI status is mutable: a re-run changes it, so receipts report point_in_time freshness and carry the latest completion timestamp observed.

Deterministic release policy

release-policy/v1 is the first consumer of normalized CI facts. A policy names one or more exact, case-sensitive required checks and an evidence age from one second through 31 days. Its pure evaluator receives a normalized status payload, its provenance receipt, the policy, and an explicit timezone-aware evaluation instant; it performs no I/O and reads no ambient clock.

No supplied evidence yields NOT_ASSESSED. Supplied but partial, stale, future-dated, digest-mismatched, duplicated, missing, pending, neutral, skipped, or unrecognized evidence yields UNCERTAIN. A fresh required check with a failure, cancellation, timeout, action requirement, or startup failure yields UNSAFE. SAFE requires every named check to have one fresh completed/success observation. Non-required checks are ignored, so the policy does not silently expand when a repository adds a new job.

This vocabulary is deliberately bounded: SAFE means only that the pinned revision satisfied the named CI policy at the recorded evaluation instant. It is not a claim of program correctness. The evaluator returns release truth and a reason but has no access to the claim verdict, execution truth, or goal truth. A green forge response therefore cannot admit failed or uncertain claim evidence.

The release-evidence command is the end-to-end consumer. It requires an exact remote owner/name, a full lowercase target SHA matching the Case, a strict named policy, and an explicit evaluation timestamp. The credential argument is an environment-variable name, never a token. Configuration, path, origin, policy, key, and credential presence are validated before the single collection call. The resulting Case stores a result-free policy/receipt/evaluation record beside release truth; offline EEF verification reads the archived receipt and re-runs the pure policy evaluator, rejecting a changed policy, reason, receipt, or release label even when the archive has been coherently re-signed.

Hosted API connectors do not inherit process sandboxing from CLI-shaped providers. The GitHub and GitLab adapters therefore state their narrower posture explicitly: direct in-process HTTPS, ambient-host credential access, no redirects or proxies, bounded response/check counts, and read-only source semantics. Future HTTP adapters must supply and document their own network, credential, redirect, size, and source-origin controls rather than assuming the Codex CLI subprocess sandbox applies to them.

Exhibit A CI dogfood

The checked-in exhibit_a_ci example pins public revision de669e7e09aa5694911fe524ab30253f75a6b5cc and the exact normalized receipt collected for it. GitHub reported five completed successful checks; the local policy names only engine and web. The full-fidelity fixture retains all five names so completeness can be verified, while the public passport exposes the two allowed names plus an omitted count of three.

The generator never contacts GitHub. It treats the checked receipt as immutable historical input, evaluates it at its recorded collection instant, and byte-compares the regenerated JSON and HTML. This keeps routine verification independent of GitHub availability and of later check reruns. Its SAFE value means only “the two required checks satisfied this policy at collection time”; it neither asserts code correctness nor changes the separately derived VERIFIED timeout-bug verdict.

Test-execution receipts are stored in Case.evidence_sources and therefore covered by the existing EEF hash manifest and signature. The Git adapter returns its typed payload and the same receipt shape for callers to persist when they opt into that source. Local filesystem paths and URL credentials are not included in public provenance.

EEF v3 can additionally archive remote CI payloads alongside their receipts. The canonical, size-bounded section is committed in both the manifest and attestation predicate, and each entry is bound to the exact claim hash, canonical repository origin and identity, and full target revision. The verifier recomputes the digest of the normalized request, normalized response, and their content link without contacting the forge. Connector-specific source rules bind both adapters to the repository’s expected API origin and provider-specific path; a future adapter must add its own fail-closed rule. The raw response is deliberately omitted; its artifact digest is a signed commitment to the collected bytes, not a body an offline reader can independently rehash. V1 and v2 archives remain readable and expose an empty remote-receipt collection; minting without remote evidence remains byte-compatible v2.

The EEF signature protects these receipts from parties that do not hold the shared HMAC key. It does not stop a trusted key holder from authoring a different internally coherent receipt, and it does not independently authenticate GitHub’s response. Public-key signer identity and upstream provenance are separate roadmap decisions.

Connectors are trusted evidence collectors, but they have no verdict authority. Before raw test output reaches the deterministic judge, the engine validates the connector’s payload type, descriptor-bound metadata, and request/response hash.

Local test receipts are version 2. Their request digest binds the state and revision, contract artifact, fixed command, timeout, network policy, and prepared environment handle. A shared fail-closed collector also validates source identity, observation time, descriptor metadata, primitive outcome fields, and every digest before returning a fresh outcome snapshot. Both bug-flip and refactor workflows use this same distrust boundary.