Ferrum Family · GitOps control plane

Ferrum GitForgeOps

Your gateway, as code.

A repository template and CLI that turns a GitHub repository into the control plane for your Ferrum Edge gateways. Resources live as YAML, pull requests get a validated plan and policy review against the live gateway, merges apply per environment after approval, and scheduled checks report API drift after approval or through a separate monitor environment. Everything runs on GitHub Actions, Environments, Secrets and APIs, with no third-party secret manager.

Source only Edge v0.9.11 historically qualified; v0.9.15 pairing pending; v0.9.17 not qualified GitHub Actions Rust CLI PolyForm Noncommercial
Status
Source onlyv0.1.0 baseline merged; no GitHub release yet
Ships as
Repository template + CLIThe gitforgeops binary, built by your own workflows
Built with
Rust + GitHub ActionsEnvironments, Secrets and the GitHub API; age encryption
Ferrum Edge
v0.9.11 historical qualificationSeven hosted local-gateway scenarios passed. The pinned v0.9.15 pairing awaits lifecycle qualification; v0.9.17 is not qualified
Lifecycle-qualified source; first release not accepted. PR #463 merged qualified source head 8013a14716fd9eeffc0ad5f2447c12409d362ec8 baseline to main as 74efb135da1557990f695f0cea92dbd0a68e608b. Hosted lifecycle run 37245424910 passed all seven runnable local-gateway scenarios using the published Edge v0.9.11 binary. This historical run does not qualify Edge v0.9.15 or v0.9.17. (SHA-256 97cbd7cd277feee8f661e9d2cd97a6383be969fd2fc2b61143601614922f988f) and contracts-edge-0.9.11 at 390edbd5b2485af0988e02f7827fde778d76ae0a. Six external GitHub, disposable-repository, approval and fault-injection scenarios were explicitly skipped. This qualifies that lifecycle only; it does not establish human GitHub acceptance, a first release, or broader operational support. The first release remains unpublished and unaccepted, and issue #266's human release checklist remains required. The CLI, configuration and state formats may change without an upgrade path. Once the release record reads supported, adopt only the source revision and gateway pairing it names; see the release notes.
Declare, review, merge

Gateway changes that go through the same review as code

Each proxy, consumer, upstream, plugin config and mesh fragment is a YAML file under resources/<namespace>/. The directory names the namespace, and overlays/<environment>/ holds the few keys that differ per environment.

  • Open a PR: static validation for everyone, a live plan and review comment for trusted PRs
  • Merge: each environment applies in its own job, after its required reviewer approves
  • Scheduled drift checks: API environments compare the live gateway with the repository after approval, or through a separately configured monitor environment
kind: Proxy
spec:
  id: "orders-proxy"
  name: "Orders API"
  listen_path: "/orders"
  backend_scheme: https
  backend_host: "orders.internal"
  backend_port: 8080
  strip_listen_path: true
The quickstart's proxy, verbatim from tests/fixtures/quickstart/. A production overlay changes only backend_host.

A Control Plane Made of Pull Requests

Review, approval, audit trail and secrets come from GitHub itself. GitForgeOps adds the gateway-aware parts.

Plans on every pull request

A review comment per environment shows validation, the Add / Modify / Delete changes, breaking changes, security findings, unmanaged resources and an apply verdict.

Many environments, one repository

Declare staging, production and more in .gitforgeops/config.yaml. Each deploys to its own gateway through its own GitHub Environment, with deep-merge overlays.

Ownership modes

shared (the default) touches only what the repository has applied and leaves hand-made changes alone. exclusive makes Git authoritative for named namespaces.

Policy rules

Eleven opt-in rules, from TLS-only backends to required authentication, WAF and AI guardrails. severity: error blocks apply unless a maintainer overrides it on the PR.

Credential broker

Resource files hold ${gh-env-secret:...} placeholders. Values are generated at apply time, kept in Environment Secrets and delivered age-encrypted to the PR author.

Ownership-aware drift detection

A daily gitforgeops diff per API environment. By default it waits for the deployment environment approval; unattended monitoring requires the documented separate monitor environment. Shared mode alerts on managed resources that changed or vanished, so unmanaged additions stay quiet.

Staged promotion

With promotion.requires: staging, production waits until staging has applied the same revision and passed its declared traffic checks.

Adopt a running gateway

gitforgeops import turns a live gateway or backup into a resource tree. Every secret becomes a placeholder; real values go to a separate private bundle.

File mode and mesh

For file-mode gateways, apply commits an assembled document with placeholders intact. MeshConfig fragments merge into one standalone mesh document. Fleet delivery and live convergence observation require an external system.

apply-on-merge.yml

Approved, ordered, and safe to re-run

After merge, each environment applies in its own job, bound to its GitHub Environment, so its required reviewer, branch policy and secrets all apply. The person who merged cannot approve the apply.

  • Namespace by namespace, in dependency order
  • One apply per environment at a time; a run moves to the current head of main
  • Refuses stale or superseded deployments instead of replaying old state
  • Re-running a failed apply is safe: finished work is skipped
  • The state-writer GitHub App commits the ownership ledger, .state/<env>.json, back to main
Triggering commit: 4f2c…
Protected main HEAD: 4f2c…
...
Allocated 1 credential slot(s):
  ferrum/orders-client/keyauth/key -> @you (ssh SHA256:…)
Applied: 4 created, 0 updated, 0 deleted, 0 deletes deferred, 0 unmanaged skipped, 0 spec-owned skipped
convergence: mode=cp, 1 data-plane node(s), 0 mesh node(s) connected; oldest last_sync_at …
...
Pushed state on attempt 1.
A successful first apply, as shown in the repository's docs/quickstart.md (§8).
Credential broker

Consumer credentials that never touch the repository

Credentials are declared as placeholders. alloc=generate creates the value on the first apply, stores it in the environment's FERRUM_CREDS_BUNDLE* secrets and posts it to the PR, age-encrypted to the author's GitHub SSH key.

  • A literal secret in resource YAML is a blocking security finding
  • alloc=require (the default) expects a value you seeded
  • Rotate a slot with the rotate.yml workflow or gitforgeops rotate
  • Bundles shard automatically: roughly 7,000 slots per environment
kind: Consumer
spec:
  id: "orders-client"
  username: "orders-client"
  credentials:
    # The broker allocates this on the first apply and delivers it,
    # age-encrypted, to the pull request's author. Never write a literal
    # secret here: `apply` refuses the document before it reads a bundle.
    #
    # Slot: ferrum/orders-client/keyauth/key
    keyauth:
      - key: "${gh-env-secret:alloc=generate}"
Verbatim from the quickstart fixture. The author decrypts the delivered key with age -d -i ~/.ssh/id_ed25519.
.gitforgeops/policies.yaml

Your standards, enforced in review

Every rule is off until you enable it. Violations name the rule, the resource, the current value and a fix. error blocks apply; warning and info only report.

  • Override on the PR only: a permitted account adds the override label and submits an override review on the current head
  • A new push needs a new override review
  • Overridden findings stay visible, marked OVERRIDDEN by @user, and the ledger records the evidence
  • Validation, credential and ownership gates are never overridable
version: 1

policies:
  backend_scheme:
    enabled: true
    severity: error              # error | warning | info
    allowed_protocols: [https, tcps, dtls]

  require_auth_plugin:
    enabled: true
    severity: error

overrides:
  require_label: gitforgeops/policy-override
  required_permission: write
From the repository's docs/policies.md. policies.example.yaml documents every key.
RuleChecks
proxy_timeout_bandsProxy connect, read and write timeouts stay inside min/max bands
backend_schemeThe effective backend_scheme is in allowed_protocols
require_auth_pluginEvery proxy runs an enabled authenticator on every protocol it serves
forbid_tls_verify_disabledNo proxy or upstream disables backend certificate verification
allowed_proxy_pluginsProxies use only allowlisted plugins
allowed_backend_domainsStatically authored destinations match an allowlist
waf_enforcementAttached waf plugins actually block
require_ai_guardrailsAI proxies carry an enforcing content guardrail
rate_limit_completenessRate limiters have a usable budget
plugin_name_is_knownplugin_name is a built-in or an allowlisted custom name
priority_override_rangepriority_override is within 0..=10000

From Pull Request to Running Gateway

GitForgeOps drives each gateway through the Ferrum Edge Admin API, the same interface Foundry uses. Nothing runs on the gateway itself.

Pull requestYAML under resources/ and overlays/
GitHub EnvironmentRequired reviewer, branch policy, gateway secrets
GitForgeOpsReconciles one environment and commits its ledger
Ferrum EdgeOne gateway per environment

PR code never sees secrets

validate-pr.yml has a read-only token and no Environment. The live review runs the protected-branch binary on a sanitized copy of the PR's YAML. Fork PRs get static review only.

Validated by the gateway binary

Every assembled document is checked with ferrum-edge validate, using a validator build pinned by SHA-256 in .github/ferrum-edge-checksums.txt.

API mode or file mode

API mode reconciles a running gateway. File mode assembles a document you deliver yourself; set live_review: false for environments without an Admin API.

Bundled Workflows, and the Same Engine Locally

The template ships the GitHub Actions workflows; the gitforgeops CLI runs the same operations from your machine.

WorkflowTriggerPurpose
validate-pr.ymlPull requestSecretless static validation, the gitforgeops-required-static-validation check
trusted-pr-review.ymlAfter PR validationEnvironment-gated live review comment
state-guard.ymlPull requestRejects PR edits to .state/
apply-on-merge.ymlPush to mainApply per environment, then staged promotions
drift-check.ymlDaily, manualDrift report per environment
rotate.ymlManualRotate one consumer credential
materialize-file.ymlManualEncrypted, fully resolved file for file-mode gateways
settings-audit.ymlWeekly, manualChecks GitHub protections against the launch baseline
validator-pin-canary.ymlDaily, manualOpens an issue when the validator digest pin is stale
CommandDoes
gitforgeops validateAssembles the tree, runs the security audit and ferrum-edge validate
gitforgeops planShows what apply would do and exits non-zero for every reason it would refuse
gitforgeops applyReconciles the gateway (API mode) or writes the assembled files (file mode)
gitforgeops diffCompares desired state with the live gateway; --exit-on-drift exits 2 on drift
gitforgeops reviewRenders, and with --pr posts, the PR review comment
gitforgeops importTurns a live gateway or backup file into a resource tree
gitforgeops exportWrites the assembled document; --materialize --encrypt-to LOGIN resolves and encrypts it
gitforgeops verifyRuns the environment's declared traffic checks
gitforgeops rotateRotates one consumer credential slot
gitforgeops doctorRead-only readiness diagnosis
gitforgeops envs / versionLists environments for CI matrices / prints build metadata
gitforgeops doctor

Is this repository ready to deploy, and what is still wrong?

Doctor changes nothing and prints no secret values. It checks the local tree, the GitHub settings against the launch baseline, and, when you opt in, the gateway itself.

  • local: config, overlays, resource tree, policy file, validator binary and digest
  • github: rulesets, App bypass, environments, labels and required checks
  • gateway: GET /health and GET /cluster prove connectivity and JWT claims
  • UNKNOWN is never counted as a pass; exit 3 means something is blocking
FAIL    Configured overlays exist: missing overlay directories: staging -> overlays/staging
        -> Create the directory (an empty one is valid) or remove `overlay:` from that environment.
UNKNOWN Repository settings match the launch baseline: no GH_TOKEN with Administration: read

4 passed, 2 failed, 0 warned, 1 unknown, 3 skipped.
Example output from the repository README's Setup doctor section.

GitHub-Native, with a Rust Engine

Your template copy builds the engine from its own checkout. Dependencies are pinned: Actions by commit SHA, Rust and tools by exact version, Docker bases by digest.

Rust 1.98The gitforgeops CLI, with clap, Tokio and serde
GitHub ActionsValidate, review, apply, drift, rotate, audit
Environments & SecretsApprovals, gateway credentials, credential bundles
ageCredential delivery encrypted to GitHub SSH keys
reqwest + rustlsHTTPS to the Admin API, optional mTLS
HS256 admin JWTsSigned with the gateway's own secret
Python helpersSettings bootstrap, audit and template updates
Docker build recipeBundles gitforgeops and digest-pinned Edge v0.9.11; historical lifecycle qualification only, first release awaits human acceptance

Quick Start

One API-mode gateway, one namespace, one environment. The full quickstart walks through every step and what to expect.

gh with repo admin A second maintainer to approve applies Edge v0.9.11 historical qualification; v0.9.15 pairing pending, v0.9.17 unqualified; first release not accepted, https:// Admin API Rust + age for local runs
  1. Create your repository from the template

    bash
    gh repo create acme/gateway-config \
      --template ferrum-edge/ferrum-edge-git-forge-ops --private

    Use a template copy, not a fork. Check the plan requirements below before choosing visibility. Once the release record is supported, start from its exact source revision instead, using the adoption steps.

  2. Apply the launch controls

    bash
    export GH_TOKEN=$(gh auth token)
    
    python3 .github/scripts/bootstrap_repo_settings.py --repo OWNER/REPO \
      --state-writer-app-id 123456 \
      --environment production \
      --reviewer SECOND-MAINTAINER --apply

    First create the state-writer GitHub App (Contents: read and write only) and set GITFORGEOPS_STATE_APP_ID. The script writes rulesets, Actions policy, labels and environments; without --apply it only prints the plan. It never reads or prints a secret.

  3. Declare the environment

    .gitforgeops/config.yaml
    version: 1
    
    environments:
      production:
        overlay: production
        apply_strategy: incremental
        ownership:
          mode: shared
    
    default_environment: production

    GitHub Actions deploys nothing without this file. It never holds URLs, secret names or credentials, and the name must match the GitHub Environment.

  4. Set the environment secrets

    bash
    gh secret set FERRUM_GATEWAY_URL        --repo OWNER/REPO --env production
    gh secret set FERRUM_ADMIN_JWT_SECRET   --repo OWNER/REPO --env production
    gh secret set GITFORGEOPS_STATE_APP_PRIVATE_KEY --repo OWNER/REPO --env production \
      < app-private-key.pem
    gh secret set FERRUM_GH_PROVISIONER_TOKEN --repo OWNER/REPO --env production
    gh secret set SETTINGS_AUDIT_TOKEN      --repo OWNER/REPO --env settings-audit

    The gateway URL must be https:// and the JWT secret must be the gateway's own signing secret. The broker creates FERRUM_CREDS_BUNDLE* itself.

  5. Add resources, check locally, open a pull request

    bash
    cargo build
    ./target/debug/gitforgeops validate
    ./target/debug/gitforgeops --env production plan

    Put files under resources/ferrum/ (see the examples above) and add an SSH key to your GitHub account so a generated credential can be delivered to you. Merge the PR, have your second maintainer approve the apply, then decrypt the delivered key.

  6. Confirm the loop is closed

    bash
    gitforgeops --env production diff --exit-on-drift   # exit 0: in sync

GitHub Plan Requirements

Three controls depend on repository visibility and plan, per GitHub's documentation as checked by the project on September 16, 2026. Plans change, so confirm with GitHub.

ControlPublic repositoryPrivate or internal repository
Environments and environment secretsAll plans, including FreePro, Team or Enterprise; not Free
Deployment branch policyAll plansPro, Team or Enterprise
Required reviewers on environmentsAll plansEnterprise only
Actions minutesFree on standard hosted runnersPlan allowance, then billed

A private repository with a required reviewer needs Enterprise. On Free, Pro or Team, either make the repository public (every control works, and your gateway configuration is public while credential values are not) or accept that a private repository on Pro or Team has no human approval before apply, which the settings audit reports.

Works With the Rest of the Stack

Licensing

Ferrum GitForgeOps is licensed under PolyForm Noncommercial 1.0.0 for non-commercial use; commercial licenses are available.