Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Snapshots

A run reads exactly two states of the repository: a base and a candidate. Each is named by a full commit ID, or the candidate can be the staged index. Nothing else counts as input. There is no working-directory mode, no branch-name resolution, and no fetching. If a needed object is not in the local object store, the run refuses; see Limits and refusals.

The staged index is the one Git keeps for this process: index in the private git directory, or the file GIT_INDEX_FILE names when Git sets it, as it does for a hook under git commit -a or a partial commit. That file has to sit in the same private git directory; one anywhere else is refused with GIT_INDEX_OUTSIDE_REPOSITORY rather than read.

The working directory is never judged, but a staged check does look at it once, after the verdict. It names on stderr each document it read whose working copy holds other bytes than the staged blob, since a pass over stale staging says nothing about the edit still waiting to be added. A CRLF copy of a LF blob, which core.autocrlf and eol=crlf write and git status calls clean, is not a difference, and neither is a copy a sparse checkout left out.

What refs name

Branch refs describe identity and link scope; they never select either snapshot. The rolling request and report contracts carry candidate_ref, the candidate or source branch whose links are being evaluated, separately from target_ref, the protected branch to which branch-scoped controls bind. A direct branch update normally uses the same value for both, while a pull or merge request may use a feature branch as the candidate and the protected base branch as the target.

The default-branch ref is a third fact, used for URL resolution and never inferred to be the target. The public CLI exposes only its existing candidate --ref claim; the complete split is currently reachable only through the internal request and bootstrap surface.

The candidate identity

In staged-index mode the identity covers the complete logical stage-zero index, including skip-worktree entries. The digest chain has three steps: hash the sorted index projection, hash a synthetic snapshot over that projection, then bind the result into the staged candidate identity. A commit-pair run uses the corresponding commit candidate identity.

identityprojectionsorted indexprojectionsyntheticsyntheticsnapshotprojection->syntheticidentitycandidate identity+ repository, dialect,refs, basesynthetic->identity

Both refs are part of that identity preimage, alongside the repository, selected URL dialect, base, and candidate, so a trusted-time statement bound to one source and target relationship cannot be replayed for another. These JSON files are digest preimages, not accepted request documents, and the identity golden test validates each against its report-schema definition and reproduces the full chain through the production builders.

The snapshot request’s repository_handle: 3 is a stable protocol ordinal, not a claim that the operating system passed file descriptor 3. In the current safe-Rust subprocess path the bootstrap maps that logical handle to the fixed repository working directory before launch, and the engine opens only that directory. A future isolation backend may map the same ordinal to a different pre-opened mechanism without changing the request wire.

The provider identity

The provider controller uses a stricter orchestration identity containing the provider instance, repository and change, URL dialect, candidate, target and default-branch refs, object format, base and candidate commits, and both tree IDs. Each provider lane binds that identity from independently authenticated input, refreshes the change and protected merge gate through its own credential, and acquires authenticated SHA-1 commit wants through the same fixed-budget Git protocol-v2 path before launch. Bootstrap refuses unless the acquired roots reproduce the evaluation identity. The engine then reads and re-hashes their objects normally; fetching remains outside the engine. The provider’s Check Run, policy-job result, or dedicated review remains merge evidence rather than a third engine snapshot.

Reading Git directly

Amiss reads Git’s storage itself instead of asking the git command. Loose objects, packfiles, deltas, and the index file are parsed by the engine, and the parsers reject instead of repairing. A tree with entries out of order, an index whose checksum does not match, a delta chain deeper than the published limit: each one is a typed refusal, never a best-effort read. Every SHA-1 object is re-hashed as it is read, with collision detection switched on, so an object that does not hash to its own name simply does not exist as far as the evaluation is concerned.

Two rejections follow Git rather than the letter of its formats. An index written under index.skipHash, which feature.manyFiles turns on, carries an all-zero checksum that Git does not verify, and neither does this reader. A pack or pack index left without its partner by an interrupted fetch is skipped, as Git skips it. A split or a sparse index is a format this reader does not expand; it is refused with GIT_INDEX_FORMAT_UNSUPPORTED, whose text names how to turn it off.

The supported repository forms are the non-bare checkouts: a primary checkout with a real .git directory, a linked worktree, or a separate-git-dir checkout, the latter two through one bounded gitdir: indirection and at most one bounded commondir hop, with symlinked targets refused. A bare repository is refused directly, though its linked worktrees read through their commondir, and objects available only through Git alternates are not consulted. These are explicit boundaries of the direct repository reader, not empty snapshots or silently missing documents.

File access happens through directory handles opened step by step, never following links. A symlink, junction, or reparse point at the repository root, at .git, at objects, or anywhere along the path to an object is refused outright. The refusal is a different error from the object being absent, and that difference is deliberate: someone who can plant a link must not be able to make the scanner read files outside the repository, and must equally not be able to disguise the attempt as a missing object.

What a refusal looks like

In the report’s errors array, a base commit the store does not hold appears as:

{
  "code": "GIT_OBJECT_MISSING",
  "phase": "git"
}

The row also carries path, path_bytes, resource, configured_limit, and observed_lower_bound fields, null wherever they do not apply, so every refusal has the same shape and a consumer never parses two formats. When the refused thing is a name the path grammar rejects, path_bytes holds its exact bytes as an array of byte values, so the report never swallows what it refused.

Neither snapshot is trusted more than the other. A base commit missing from a shallow clone is a refusal, not an empty tree, because treating an absent base as empty would make every document look newly added and flood the report with false introduced findings. Comparing two trees only means something when both trees are exactly the ones you asked for.

Last change: , commit: 0987dade