About trust
A pull request under review can edit vinaya. in its own diff. Some keys decide who may merge, or name a destination or a program that an unattended run would follow. For those keys, the harness reads the repository's default-branch copy instead. This copy is the trust anchor: a change cannot rewrite its own rules. See Vinaya keys.
A second, separate rule covers the machine-wide ~/. Keys that decide what runs or who is trusted are honoured only from the reviewed, committed repository file, never from the global file.
How the default-branch read works
The harness fetches vinaya. through the GitHub API with no branch named, so GitHub serves the default branch. The content never comes from local git or the working tree. The repository is identified from GITHUB_, then from the origin remote, in that order.
Keys read from the default branch
| Key | Read by | Rule |
|---|---|---|
principals | The review gate, waiver checks and the Principal checks on forge writes | Always the default-branch copy |
releaseActor | The release exemption on body-bare-digits | Always the default-branch copy |
reviewPolicy | The review gate and the loop | Always the default-branch copy, so a change cannot lower its own threshold. Gate and loop read the same source |
securityScan | The review loop | Always the default-branch copy |
runtimeDir | The driver, when unattended | Honoured only if the default branch declares exactly the same value |
logs destination (folder, url, headers) | The Log sink, when unattended | Honoured only if the default branch declares the same folder or url. A server's headers always come from the default branch |
Some keys are deliberately not anchored:
logs.eventsandlogs.readHeaders. They name what may be recorded and this machine's read credential, not a destination.gateCutoversandplanning. They are read from the working tree on purpose, so a transition can land on its own branch.
Attended and unattended runs
A process is unattended when VINAYA_ is set or VINAYA_ROLE is non-empty. A person at a terminal is attended, and the working tree's runtimeDir and logs are honoured directly.
Keys ignored in the global file
These keys are stripped from ~/ at load, with one warning naming the file:
checksrolesprincipalsreleaseActortokensruntimeDir
runtimeDir is stripped because a machine-wide value carries no repository segment, and would merge every repository's run files into one tree.
When the read fails
| Situation | Result |
|---|---|
No vinaya. on the default branch | Built-in defaults, silently |
gh missing, unauthenticated, unreachable or rate-limited; malformed JSON; schema mismatch | Built-in defaults: no Principal (every Principal-deciding check and command refuses), github-, the default review policy, no scanner. A warning goes to stdout, never stderr, which is a check's error channel |
Unattended runtimeDir does not match | The per-repository default runtime directory, never the working tree's value |
Unattended logs.url does not match | The default folder, with the reason recorded (anchor- or anchor-mismatch). A CI job never falls back to a folder |
One reader must not guess. When the loop reads the review policy, a genuine read failure stops it instead of falling back (a missing file still means defaults). A verdict cast under a guessed policy would be rejected by the merge gate.
Limits
- The next step is branch protection with the check required. Even that is satisfied by any conclusion reported under the check's name.
vinaya initprints the recommendation, andvinaya doctorreports when it is missing. - One test seam exists:
VINAYA_lets one CI job read the pull request's own config as if it were the default branch. It works only whenCI_ CONFIG_ PROBE_ REF GITHUB_and no forge token is present. Merge-authority reads run inACTIONS=true pull_jobs that never set it.request_ target tokens.collecthas its own trust: a per-machine approval pinned to the script's blob hash. Seetokens.