Architecture & Agent Guide
Tidyorg is a configuration-driven control plane for GitHub organizations. It replaces fragile manual settings UI clicks with declarative YAML manifests, audited Pull Requests, and idempotent Terraform reconciliation.
Core Principles
- Rules are bound to roles, not people: Team members receive access via role mappings. When a developer changes teams, only their single role assignment changes, not the repository rules.
- Strict Separation of Proposer & Reconciler: The web dashboard only opens Pull Requests against the configuration repository. It does not call Terraform or talk to the Terraform daemon directly.
- One-Way Reconciliation: Anything manually changed in the GitHub UI is reverted on the next apply. The YAML configuration is the sole authority.
- Escalation Immunity: The dashboard UI and proposal service never generate edits for
privileged.yml. Privilege escalation remains review-gated by CODEOWNERS and branch protection.
YAML Schema Specifications
The configuration directory resides at config/ and contains four distinct components:
1. organization.yml
Defines organization-wide role mappings, permission semantics, repository defaults, and the public organization profile.
version: 1
organization: your-org # Must match TF_VAR_github_org_name
roles:
head-of-engineering:
scope: organization
repo_permission: admin
mentor:
scope: repository
repo_permission: admin
developer:
scope: repository
repo_permission: push
viewer:
scope: repository
repo_permission: pull
# Default settings inherited by every repository unless overridden
defaults:
visibility: private
has_issues: true
has_projects: false
has_wiki: false
delete_branch_on_merge: true
allow_squash_merge: true
allow_merge_commit: false
allow_rebase_merge: false
branch_protection:
required_reviews: 2
require_code_owner_review: true
require_status_checks:
- ci/test
profile:
name: "Your Organization"
description: "Declarative Infrastructure"
blog: "https://example.com"
2. repositories/<repo-name>.yml
One file per repository. The filename must match the repository name on GitHub. Only fields that differ from organization.yml defaults need to be declared.
description: "Payment gateway integration service"
language: go
visibility: private
default_branch: main
# Mentors get 'admin' permission on the repository (at least 1 required)
mentors:
- tony
# Developers receive 'push' permission
developers:
- bob
- carol
# Viewers receive read-only pull permission
viewers:
- george
# Branch protection overrides for this repository
protected_branches:
main:
required_reviews: 3 # Overrides default 2
develop:
required_reviews: 0 # Experimental branch
# Path-based review routing
code_owners:
"/proto/": [tony]
"/billing/": [bob]
3. people.yml
Contains the flat list of all GitHub usernames that belong to the organization. Members inherit the base permission defined in organization defaults, and receive repository access through team assignments.
version: 1
members:
- tony
- bob
- carol
- george
4. privileged.yml
HUMANS ONLY: Defines organization owners and org-scoped roles. The dashboard does not generate edits for this file; GitHub CODEOWNERS and branch protection require administrator review before changes can be merged.
version: 1
org_owners:
- tony
- frank
roles:
head-of-engineering:
- tony
GitHub Apps Configuration
Tidyorg uses up to two dedicated GitHub Apps. No personal access tokens (PATs) are used:
1. Engine Bot (Required)
Installed across the whole organization. Used by the Terraform runner to reconcile GitHub state.
| Permission Scope | Required Access | Purpose |
|---|---|---|
| Organization → Administration | Read & Write | Manage org settings, profile, and base permissions |
| Organization → Members | Read & Write | Reconcile team memberships and org members |
| Repository → Administration | Read & Write | Create repositories, branch protection, and settings |
| Repository → Contents | Read & Write | Manage CODEOWNERS and distributed files |
| Repository → Issues | Read & Write | Manage default issue labels |
| Repository → Workflows | Read & Write | Distribute CI/CD workflows |
| Repository → Metadata | Read-only | Query repo information |
2. Dashboard App (Optional)
Used only if running the web dashboard. Installed only on the configuration repository with minimal scopes.
| Permission Scope | Required Access | Purpose |
|---|---|---|
| Repository → Contents | Read & Write | Create proposal branch & commits |
| Repository → Pull requests | Read & Write | Open batched proposal PRs |
| Repository → Actions | Read-only | Display live sync status badge |
| Repository → Metadata | Read-only | Read repository info |
Docker & Compose Deployment
The standard docker-compose.ghcr.yml allows quick multi-container setup:
services:
engine:
image: \${TIDYORG_REGISTRY:-ghcr.io/uslanozan}/tidyorg:\${TIDYORG_VERSION:-0.1.3}
pull_policy: always
volumes:
- ./config:/config
- ./state:/state
- ./app.pem:/secrets/app.pem:ro
environment:
TF_VAR_github_org_name: your-org
TF_VAR_github_app_id: "123456"
TF_VAR_github_app_installation_id: "12345678"
command: ["plan"]
dashboard:
image: \${TIDYORG_REGISTRY:-ghcr.io/uslanozan}/tidyorg-dashboard:\${TIDYORG_VERSION:-0.1.3}
pull_policy: always
ports:
- "8080:8080"
environment:
GITHUB_CLIENT_ID: your_client_id
CONFIG_OWNER: your-org
CONFIG_REPO: your-config-repo
CONFIG_BRANCH: main
To use the Docker Hub automated mirror instead of GHCR, set TIDYORG_REGISTRY=uslanozan.
Terraform State Backends
The TF_STATE environment variable specifies where Terraform persists state:
| TF_STATE Value | Location | Required Environment Variables |
|---|---|---|
local (Default) |
Mounted /state volume |
None (zero external dependencies) |
hcp |
HCP Terraform (Terraform Cloud) | TF_CLOUD_ORGANIZATION, TF_WORKSPACE, TF_TOKEN_app_terraform_io |
custom |
AWS S3, Google Cloud Storage, Azure Blob | Mount your backend HCL file at /engine/backend.tf |
Review Gates & Access Controls
In a production Tidyorg deployment, changes travel through two distinct review gates:
-
Branch Protection on the Config Repo: The
mainbranch of the configuration repository requires passing status checks (terraform plan) and at least 1 or 2 code review approvals. -
CODEOWNERS on
privileged.yml: Only members of theorg_admin_team(or designated organization owners) can approve modifications toconfig/privileged.yml.
Repository Deletion Safety Locks
Tidyorg is specifically architected to prevent catastrophic accidents:
- No Hard Deletes in Dashboard: The dashboard UI does not feature a hard delete button. The most a mentor can propose is archiving a repository (
archived: true), keeping the repository read-only while preserving all commit history, issues, PR discussions, and tags. - prevent_destroy Lifecycle Lock: The Terraform module has a
prevent_destroyblock on repository resources. If someone accidentally deletes a repository's YAML file,terraform applywill refuse to execute with a safety error. - Deliberate Destruction Runbook: To permanently delete a repository, a platform administrator must deliberately remove the YAML file via reviewed PR, manually run
terraform state rm 'module.repositories["<repo>"]', and run the dedicated repository deletion script in the engine repository.
Handling Configuration Drift
If someone makes manual modifications in the GitHub web interface (for example, inviting a member directly, or disabling branch rules), those changes are treated as drift.
When terraform plan runs, it flags the delta. When terraform apply runs, the unauthorized changes are undone and reverted to match the YAML files. The only way to make permanent modifications is through a merged Pull Request against the configuration repository.