Docs

Configuration

Trust

Which configuration keys are read from the default branch instead of the pull request, which are ignored in the global file, and what happens when the default-branch read fails.

Default branch
Trust anchor
Global file
Fixed rules

About trust

A pull request under review can edit vinaya.config.json 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 ~/.vinaya/config.json. 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.config.json 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_REPOSITORY, then from the origin remote, in that order.

Keys read from the default branch

KeyRead byRule
principalsThe review gate, waiver checks and the Principal checks on forge writesAlways the default-branch copy
releaseActorThe release exemption on body-bare-digitsAlways the default-branch copy
reviewPolicyThe review gate and the loopAlways the default-branch copy, so a change cannot lower its own threshold. Gate and loop read the same source
securityScanThe review loopAlways the default-branch copy
runtimeDirThe driver, when unattendedHonoured only if the default branch declares exactly the same value
logs destination (folder, url, headers)The Log sink, when unattendedHonoured 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.events and logs.readHeaders. They name what may be recorded and this machine's read credential, not a destination.
  • gateCutovers and planning. 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_UNATTENDED=1 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 ~/.vinaya/config.json at load, with one warning naming the file:

  • checks
  • roles
  • principals
  • releaseActor
  • tokens
  • runtimeDir

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

SituationResult
No vinaya.config.json on the default branchBuilt-in defaults, silently
gh missing, unauthenticated, unreachable or rate-limited; malformed JSON; schema mismatchBuilt-in defaults: no Principal (every Principal-deciding check and command refuses), github-actions[bot], the default review policy, no scanner. A warning goes to stdout, never stderr, which is a check's error channel
Unattended runtimeDir does not matchThe per-repository default runtime directory, never the working tree's value
Unattended logs.url does not matchThe default folder, with the reason recorded (anchor-unreadable 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 init prints the recommendation, and vinaya doctor reports when it is missing.
  • One test seam exists: VINAYA_CI_CONFIG_PROBE_REF lets one CI job read the pull request's own config as if it were the default branch. It works only when GITHUB_ACTIONS=true and no forge token is present. Merge-authority reads run in pull_request_target jobs that never set it.
  • tokens.collect has its own trust: a per-machine approval pinned to the script's blob hash. See tokens.

What's next