Core concepts¶
Three ideas. The reference reads as arbitrary until these are in place, and almost every surprising behaviour in octoform follows from one of them.
1. Every setting is tri-state¶
A setting is not on or off. It is on, off, or none of your business.
| Value | Meaning | What plan does |
|---|---|---|
true |
it must be on | turns it on if it is off |
false |
it must be off | turns it off if it is on |
| omitted | not managed | ignores it entirely, whatever it is |
null |
stop managing it | same as omitted, but cancels an inherited value |
The distinction that matters is false versus omitting. false is a policy:
keep this off, and correct it if somebody turns it on. Omitting is staying
out of the way: decide this per repository, from the GitHub UI, and I will
never touch it.
Without that distinction a reconciler is unusable, because it spends its life fighting settings that somebody set by hand on purpose. With it, you can manage four settings out of forty and genuinely leave the other thirty-six alone.
The consequence worth stating: no value has a default in the code. An empty configuration file changes nothing, and there is a test that says so. If octoform is about to change something, it is because your file asked for it.
null exists for one job — cancelling something a wider layer already said.
See precedence, below.
2. Precedence: widest to narrowest, key by key¶
Resolved key by key, not layer by layer. A repository can disagree with its type about one setting without restating the other twenty.
defaults:
merge: { allow_squash: true, delete_branch_on_merge: true }
types:
library:
merge: { allow_rebase: false } # adds to, does not replace, the above
repos:
the-odd-one:
merge: { delete_branch_on_merge: null } # cancels just this one
the-odd-one ends up with allow_squash: true, allow_rebase: false, and
delete_branch_on_merge unmanaged.
Lists replace, they do not accumulate. rulesets, environments, files
and ensure_branches are whole values: a repository declaring rulesets:
replaces its type's rulesets rather than adding to them. This is what lets a
repository opt out of a ruleset its type declares — with merging there would be
no way to say "not that one".
Three escape hatches, narrowest to widest¶
| Hatch | Effect |
|---|---|
repos.<name>.<key>: null |
stop managing that one setting, in that one repository |
repos.<name>.manage: false |
inventory and audit it, apply nothing |
exclude.repos: [...] |
drop it entirely, including from audits |
manage: false and exclude are not the same thing. An excluded repository
does not appear in reports at all; a manage: false one still shows up in
audit, which is usually what you want for something you have decided not to
govern but still want to see.
3. Blocked is not skipped¶
Everything octoform cannot do is reported, with the reason. Nothing is dropped in silence — a tool that quietly does less than you asked for is worse than one that fails, because you find out months later, from the consequences.
Three shapes of "not done":
Blocked — it cannot happen, and apply will not attempt it. A policy with
no REST endpoint, a value this GitHub plan does not expose, a ruleset the
current owner plan and token cannot manage on a private repository, a current
value that could not be read at all.
Warned — it will happen, and something else breaks as a result. Renaming a default branch that workflow files name by hand is the case this exists for.
my-repo
default_branch.name: master -> main [warning: .github/workflows/ci.yml names "master" and will stop triggering until updated]
Unreadable — GitHub would not tell us the current value. This is deliberately not the same as "off": planning a change from an answer you never got is how a tool ends up turning things on that were already on, or reporting drift that does not exist. It is reported as blocked.
my-repo
security.secret_scanning: (unreadable) -> true [skipped: current value could not be read, probably not available on this plan]
What follows from all three¶
plan is read-only, always, and apply computes its diff by calling plan —
not by recomputing it. So apply can never do something octoform plan did
not just tell you it would do. That is the whole design in one sentence.