Changelog¶
The documentation selector remains 0.5 for compatible 0.5.x packages.
Entries below identify the exact patch that introduced each change. A page
marked Available since 0.5.1 would apply to 0.5.1 and every later
0.5.x patch, but not to 0.5.0.
Earlier lines keep their own published documentation; the 0.4 and 0.3
entries below remain here as the patch index for those lines.
0.5.0 — 2026-08-16¶
Applies from: 0.5.0
Compatibility: every existing configuration file keeps working. Two
exported shapes moved, and three fixes change what a run does to a file you
have not edited — see Changed and Fixed.
Octoform now governs the account above the repositories as well as the repositories themselves, and finishes the repository surface it had started.
Added — the organization itself¶
- An
organizationblock, planned and applied like any other change rather than through a command of its own. Its profile, and what members may do without being asked: the base permission, what they may create, forking, Pages, web commit sign-off, deploy keys and projects. - The base permission and the creation switches are reported as
sensitive, because they reach every repository the organization owns including ones no policy names. The organization is applied before any repository, so a lowered floor is never briefly wider than the file asks for. - Custom property definitions under
organization.properties. The write endpoint replaces rather than patches, so the current definition is read first and every field the configuration is silent about is carried forward. - Organization rulesets under
organization.rulesets: the same rules a repository can carry, aimed at repositories selected by name or by custom property value. Every one issensitive. - Teams under
organization.teams, keyed by slug, with nesting. A child team waits for a parent the same run is creating, and is blocked rather than attempted when that creation fails. - Team membership under
organization.teams.<slug>.membership: additive unless the block saysauthoritative, and a pending invitation counts as somebody already asked. - Organization role assignment under
organization.roles, granting and revoking a role for users and teams. octoform inspect members: owners, members, outside collaborators, waiting and failed invitations, and which people the configuration names are in no part of the organization.octoform members invite,removeandconvert: one login per invocation, each saying what it will do and asking first.
Added — the repository surface, completed¶
- Every ruleset rule type, target and condition, modelled as one table that reading, writing and comparison are all derived from.
- Ruleset bypass actors — users, teams, apps, repository roles, deploy keys and organization admins — resolved by name while reading, so an unknown name is a blocked line in the plan rather than an exception thrown mid-apply.
- Classic branch protection under
branch_protection. A branch governed by both protection and a ruleset blocks on both sides. access.usersandaccess.teams, with pending invitations read so a grant to somebody who is not a collaborator yet is not re-sent on every run. Revocation is spellednone.- Labels, milestones and repository custom
property values, with
rename_fromandmode: absent. repo.visibility,repo.archived,repo.templateand repository rename. Unarchiving is sent first and everything waits for it; archiving is sent last and only if everything else succeeded.- The four repository settings only GraphQL exposes.
features.discussionsis now changeable instead of permanently blocked. - Merge message defaults and
security.immutable_releases. - A warning when
security.code_scanning_default_setupwould disable a workflow that uploads code scanning results, which GitHub refuses without either side reporting a failure. - A resource dependency graph: apply order comes from the graph rather than from the order the steps happen to be written in, and a dependent whose prerequisite failed is blocked rather than attempted.
octoform inspect capabilities --repo <name>.- A GraphQL transport, normalized against the REST one.
Changed¶
Change.repois now optional. An organization setting has no repository to name. Programmatic callers that readchange.repoas a string have to handle its absence.planOrganization,setPropertyValuesandputPropertySchematake different arguments.classify --applyandproperties syncsend thirty repositories per request. Neither did, and exceeding that limit is the ordinary case for an organization large enough to want either command.
Fixed¶
properties syncno longer clears the fields it says nothing about. It sent the allowed values alone, which on a property that already existed reset its description, its default value and who may edit it — every run, silently.- Updating a ruleset no longer deletes the rules Octoform does not model, or the ones the policy does not mention. The update replaces the whole rule list, and what was not sent back was being removed with nothing in the plan to say so.
- An undeclared ruleset key is no longer treated as a demand for GitHub's default, which made every run offer to strip approvals and protections nobody had asked about.
- Two different lists of objects of the same length no longer compare as identical when a ruleset is compared.
- A seeded file is read as bytes, so a file that is not valid UTF-8 text is no longer corrupted, and its existence is checked on the branch it would be created on rather than on the default branch.
- The topics, PUT/DELETE toggle, code scanning and branch rename steps record their failures, so anything depending on them is blocked instead of attempted.
config migratemoves areposblock at an imported file's root under the account the root file declares, instead of refusing the whole migration.
The 0.5.0 application Release contains the package evidence and SHA-256 manifest.
0.4.1 — 2026-08-15¶
Applies from: 0.4.1
Compatibility: no exit class, schema, or exported signature moved. Every
change is a fix to what a command reports or refuses.
These came from running 0.4.0 against real multi-account configurations
rather than against fixtures.
Fixed¶
config migrateno longer produces a configuration that fails to load. It converted the root file'sownertoownerswhile leaving areposblock at the root of an imported file, which is accepted besideownerand rejected besideowners. It now stops, names the files that have to move first, and writes nothing.- A setting whose current value could not be read is explained from what was observed rather than from a guessed commercial plan. Where repository visibility settles the question, the reason names it.
- A plan states how many repositories carry blocked work, instead of leaving those that also have changes counted only as changed.
Added¶
PlanSummarygainsblockedRepositories: repositories with at least one blocked change, whether or not they also changed.
0.4.0 — 2026-08-15¶
Applies from: 0.4.0
Compatibility: every existing configuration file keeps its exact meaning
and produces the same plans. Two contracts changed — see Changed below.
Added¶
- One configuration can describe several GitHub accounts. A root
ownersmapping keyed by login replaces one file per account, and the root of the file holds whatever those accounts share. - A root
versionfield states the configuration contract a file is written against.1is the only accepted value, required wheneverownersis used. - A root
policiesmapping declares named, reusable policy fragments that any layer folds in before its own keys. --owner <login>, repeatable, narrows a run to the named accounts and rejects a login the configuration does not declare.--repoaccepts a qualifiedowner/name.--strictfails a run when a declaration does not apply to the account that declared it.octoform config validateandoctoform config migrate, both offline.octoform inspect configandoctoform inspect capabilities.- Capability answers carry status, reason, source, and observation time
instead of a bare boolean, so an opaque
404no longer reads the same as a confirmed denial. - Owner discovery resolves an account's kind and GitHub's numeric identity for it, which is the identity that survives a rename.
--concurrency <n>bounds repositories worked on at once per account, and a failure in one account no longer aborts the rest;--fail-fastopts back into stopping at the first failure.plan --outsaves a versioned plan andapply --planperforms exactly that plan or refuses with a named reason.--format jsonwraps output in a versioned envelope onplanand bothinspectcommands.- The pinned REST API version is now sent on every request.
Changed¶
- Exit codes are a frozen set of six classes.
auditnow exits1when it reports findings rather than0, andapplyfollows the same classes. A pipeline that tested for success without inspecting the code will now fail on drift, which is the intended behaviour. See the execution contract. - The programmatic API changed.
loadConfigreturns a resolved configuration holding one scope per owner, soconfig.ownerbecomesconfig.owners[n].owner;planRepotakes the account as its first argument; andPlanOptions.rulesetCapabilityreplacesrulesetsEnforcedOnPrivate. Configuration files are unaffected.
Security¶
- Credentials come from an explicit token or token provider, then
GITHUB_TOKEN, thenGH_TOKEN, and nowhere else. A credential-shaped value in a configuration file is rejected when it loads, naming the YAML path without echoing the value. - A declared owner is checked against GitHub's own login format, so one
carrying a path separator, a control character, or a homoglyph fails with
the reason instead of an opaque
404mid-run. - Control characters in anything read from GitHub — repository names, descriptions, topics, property values, API errors — are escaped before being printed, so a crafted value cannot rewrite the report or imitate the confirmation prompt.
0.3.2 — 2026-08-14¶
Applies from: 0.3.2
Compatibility: maintenance-only; no configuration, CLI, public API,
capability, permission, or plan/apply migration.
Changed¶
- Unified deterministic formatting, verification, generated-reference, test, build, and packed-package checks.
- Hardened pull-request and release workflows with pinned actions, explicit permissions, timeouts, concurrency, and Node.js 20, 22, and 24 coverage.
- Added dependency review, CodeQL, secret-scanning verification, and scheduled read-only REST/GraphQL contract-drift detection.
- Enforced valid TSDoc for authored production and tooling comments.
- Published deterministic TypeDoc, configuration-schema, CLI, capability, permission, GitHub API, and SHA-256 release artifacts.
- Moved maintained product guidance to this dedicated documentation site.
The 0.3.2 application Release contains the package evidence and SHA-256 manifest.
0.3.1 — 2026-08-12¶
Applies from: 0.3.1
Fixed¶
- Private repositories owned by personal accounts no longer have repository rulesets blocked solely because organization-wide rulesets are unavailable. Planning probes capability for the repository and current token without hard-coding commercial plan names.
See Branches and rulesets for the operational boundary and the 0.3.1 Release for the immutable application record.
0.3.0 — 2026-08-10¶
Applies from: 0.3.0
Added¶
- Existing environment reviewers can be reconciled; a team reviewer remains a
blocked operation because the
0.3model resolves declared reviewers as users. - The domain model documents declared policy, observed repository state, and planned changes.
Changed¶
RepoStructure.environmentschanged fromstring[]toExistingEnvironment[]. This affects direct programmatic consumers ofgetRepoDetail; the CLI and YAML configuration remain compatible.
See the 0.3.0 Release for the immutable application record.
Upgrade rule¶
Before changing the installed patch, review every entry after the current
version and up to the target. If a future change cannot be documented as
compatible within 0.5, its guidance and selector entry move to the next minor
line instead of silently changing the 0.5 contract.