octoform plan¶
plan loads configuration, discovers repositories, resolves effective policy,
observes requested state, and reports executable and blocked differences. It
never mutates GitHub.
octoform plan --config octoform.yml
octoform plan --config octoform.yml --owner example-org
octoform plan --config octoform.yml --repo example-org/octoform
octoform plan --config octoform.yml --type npm-package
octoform plan --config octoform.yml --format json
octoform plan --config octoform.yml --out plan.json --expires-in 30
Selection¶
--owner limits the run to the named accounts and is repeatable. --repo
selects one exact discovered repository, and accepts a qualified owner/name
when the same repository name exists under more than one selected account.
--type selects every repository with one resolved type. Supplying none of
them evaluates the complete managed set after exclusions. Use a narrow
selector for initial rollout and incident diagnosis.
What a plan covers¶
A plan covers the selected repositories and the account above them. An
organization block produces changes in
the same plan, confirmed the same way, grouped under a heading that cannot be
mistaken for a repository name — a repository really can be called the same
thing as the organization that owns it:
3 change(s) across 2 repositories:
(example-org: the organisation itself)
organization.members.base_permission: write -> read
organization.teams.platform-oncall: (unset) -> platform-oncall (Platform on-call); closed; under platform
example-service
access.teams.platform: (unset) -> maintain
Owner-level work is planned and applied before any repository, so a change to the floor under every repository is never briefly wider than the file asks for.
Operation identity¶
Every operation carries:
- a stable identifier, unchanged between two runs that plan the same change, so results can be correlated across runs and across output formats;
- the account it belongs to, and the repository when it belongs to one;
- an operation kind:
create,update,attach,detachordelete; - a risk level:
normal,sensitive,destructiveorcost; - its prerequisites, and its before and after values.
Identifiers address a change in Octoform's own model. They are never a GitHub object identity.
Changed in 0.5.0
attach, detach and delete are new operation kinds, and destructive
is a new risk level. A change belonging to the account itself has no
repository at all: Change.repo is optional, and inventing a value for it
would make the change group and count as though it belonged to a
repository. Programmatic callers that read change.repo as a string have
to handle its absence.
Risk levels¶
| Level | What it means | Examples |
|---|---|---|
normal |
Metadata | A description, a label colour, a team's notification setting |
sensitive |
It affects access, merge safety, or reaches beyond what the file names | base_permission, an organization ruleset, an organization role, any access grant, branch protection, visibility, a repository property value |
destructive |
Something is removed and GitHub does not keep it | A deleted team, a deleted property definition, a revoked grant, a deleted label |
cost |
It has a billing consequence | Nothing Octoform manages today |
Risk is carried on every change in --format json and in a saved plan. Text
output does not print it; it prints the reason a change is blocked or warned,
which is what a reader acts on.
Determinism and concurrency¶
Two runs against unchanged state produce the same operations in the same order, regardless of the order the API happened to answer in. Ordering is part of the contract, not an accident of pagination.
--concurrency <n> bounds how many repositories are worked on at once within
one account; it defaults to 4. Accounts themselves run one at a time, in the
order the configuration declares them, so their reports never interleave.
A failure inside one account does not stop another: by default the run
continues and reports every account's outcome. --fail-fast stops at the
first account that fails instead. A repository whose plan cannot be computed
at all is reported as a distinct failure and never treated as "no changes".
Plan categories¶
| Result | Meaning |
|---|---|
| Executable change | Current state is readable, differs from policy, and the operation is available. |
| Warning | The change can execute but has a consequence requiring attention, such as workflow references during branch rename. |
| Blocked change | Desired and current state cannot be reconciled safely, or the capability is unavailable. It will never reach the applier. |
| No change | The field matches or is not managed. |
Blocked changes remain visible with their reason. Unreadable state is not converted into a guessed current value.
Available since 0.4.1
A reason explains itself from what was observed. Where repository visibility settles the question — GitHub hides scanning settings outside a public repository without Advanced Security — the report names that; where it does not, it says only that the value could not be read, rather than guessing at a commercial plan.
Reading the summary¶
The per-account counts put each repository in exactly one bucket, so they sum to the number scanned. A repository that has both an executable change and a blocked one is therefore counted as changed.
Available since 0.4.1
Because that would understate how much of a run cannot be applied, the number of repositories carrying blocked work is stated alongside the totals whenever it is larger:
Total — scanned: 49, changed: 18, blocked: 9, failed: 0, unchanged: 22
16 repositories carry blocked work; 7 of them are counted above as
changed because they also have changes to apply.
PlanSummary exposes the same number as blockedRepositories.
Saving a plan¶
--out <path> writes the reviewed plan to a versioned JSON file, with
owner-only permissions where the platform supports them. The file records the
schema version, the version of Octoform that produced it, the authenticated
actor, each target account's numeric identity, a digest of the resolved
configuration and of every source file that contributed to it, the observation
time, an expiry, and the operations themselves.
--expires-in <minutes> sets that expiry; it defaults to 60.
apply --plan re-checks every one of those
claims before it touches anything.
The plan file is exactly as sensitive as the configuration it came from: it names private repositories and their settings. It carries no credential.
Exit and output¶
| Situation | Exit |
|---|---|
| Nothing to do | 0 |
| Executable changes found | 1 |
| One or more changes blocked | 4 |
| One or more repositories could not be examined | 5 |
| Configuration or selector error | 2 |
| Authentication or permission failure | 3 |
Text output is human-oriented; do not depend on its layout as a stable machine
API. Use --format json for a versioned envelope instead, described in the
execution contract.