Skip to content

Multi-owner

One document, two accounts, and the shared policy written once. This is the shape the 0.4 line introduced, and the reason the rest of the release exists.

octoform.yml
# Two accounts governed by one reviewed document — the shape 0.4 introduced.
#
# What is worth noticing here is what is *not* repeated. The root `defaults`
# and the named policy under `policies` are written once, and each account
# takes what applies to it. An account is not a copy of the document; it is a
# selection within it.
#
# `example-org` and `example-personal` are placeholders — replace them, then:
#   export GITHUB_TOKEN=...
#   octoform plan --config octoform.yml --owner example-org
#   octoform plan --config octoform.yml            # both, one at a time
#
# Accounts run in declaration order and their reports never interleave. By
# default a failure in one does not stop the other; add --fail-fast to stop at
# the first.

version: 1

defaults:
  merge:
    delete_branch_on_merge: true
    allow_merge_commit: true
    allow_rebase: false

policies:
  published-library:
    features:
      issues: true
    security:
      secret_scanning: true
      secret_scanning_push_protection: true

owners:
  # An organisation: rulesets are available at owner level, and custom
  # properties exist, so classification can be persisted.
  example-org:
    classify:
      rules:
        - when: { file_exists: package.json }
          type: npm-package
    types:
      npm-package:
        policies: [published-library]
        default_branch:
          name: main

  # A personal account: the same named policy applies, but nothing here
  # depends on an organisation-only feature. `octoform inspect capabilities`
  # reports any declaration that would not apply, with the reason.
  example-personal:
    repos:
      example-tool:
        policies: [published-library]

Download YAML

What is shared and what is not

defaults and policies sit at the root, so both accounts see them. Each account's own block states only what is true of that account: the organization classifies repositories and applies the named policy to a type, the personal account applies the same named policy to one repository by name.

An account block is not a copy of the document. It is a selection within it, and reading one top to bottom still tells the whole story for that account — a referenced policy never overrides a key the referencing layer states itself.

Running it

octoform plan --config octoform.yml --owner example-org
octoform plan --config octoform.yml

Without --owner, both accounts run, one at a time, in declaration order. Their reports never interleave. A failure in one does not stop the other unless you pass --fail-fast, and the run's exit class is the most severe any account produced.

A --repo name that exists under both accounts is rejected rather than guessed at, and the error lists the qualified forms that would resolve it:

octoform plan --config octoform.yml --repo example-org/example-service

Before the first run against a new account

octoform inspect capabilities --config octoform.yml

This reports each account's kind, its numeric identity, whether organization-wide rulesets are available, and any declaration that does not apply to that kind of account. The personal account here declares nothing organization-only, but the check is what proves it rather than assumes it.

Migrating an existing single-owner file

A file that names its account in a root owner field keeps its exact meaning and produces the same plans. Nothing has to change. When you want this shape:

octoform config migrate --config octoform.yml
octoform config migrate --config octoform.yml --write

It previews by default, preserves comments and ordering, and refuses --write when the file has uncommitted changes, so the conversion is always reviewable as a diff. See octoform config.