Airframe

Why Airframe

One catalog for the whole organization

Who works here, which team they are on, which products exist and who owns each one: Airframe keeps all of it as files in one git repository, the catalog. Every automation that needs those facts reads the files, so a change is made once, reviewed once, and every reader follows it.

How one repository describes the organization

  1. The organization as files

    The catalog is a git repository of small YAML files. Journeys, domains, contexts and products form its hierarchy, each a file named by its path, such as contexts/shop.store.ordering.yaml, and a product's file names the team that owns it. A team's file names its context, its lead and its members with their roles. A person's file holds a name, an email address, a status (active, pending or departed), the person's GitLab and Grafana usernames once they have accounts, and a profile: about, expertise, links and hours. The ownership rules, in the same repository, say who approves what. On every merge request the catalog's lint checks each file against its JSON Schema, and the references between files.

  2. Changed by merge request

    Nobody changes the organization in a tool's settings: a change is a merge request to the catalog, and the catalog's own ownership rules decide who must approve it. A team's file needs one of its team leads or of its context's leads; a person's own profile, that person, who approves the change or opens it with only their own commits; the rest of a person's file, a lead of one of their teams; and the structure, from the journeys to the rules themselves, two members of the platform team. The gate resolves each rule to people from the catalog's protected main, never from the change it judges, and the merge waits for its approval. Every change therefore has a reviewer, a history in git, and an undo: another merge request.

  3. One source, many readers

    Today these read it: the gate, for every approval it gives; landing-zone's groups root, for a Google group per team, per level's leads and per product's users, the audience the product's hostname admits behind single sign-on; control-plane's GitLab roots, for each person's group memberships and the list of who may merge on each product's main; operations' Grafana root, for the organization's members and a team per catalog team, with its folders, its contact point and its alert route; the folders root, for a folder per journey, domain and context; and the architecture catalog, for a page per team and per person, with the person's about, expertise and hours. The project factories let a team's group view its products' projects and their logs; the rest of what a product's entry derives is the story of self-service by merge request.

  4. Nothing to keep in sync

    When a person or a team changes, the readers follow on their next run, and a merge to the catalog starts it. The gate reads the catalog's main at every evaluation. The catalog's pipeline on main triggers the chain of reconcilers: landing-zone's pipeline, then operations', then control-plane's, then landing-zone's again, each running only the roots that read the catalog. The same pipeline publishes the catalog's pages to the architecture catalog. For what the readers derive, there is no second inventory and no sync job. Two things still sit outside the catalog: the group-wide approval policy names its approvers in security-policies, where only a merge request changes them, and a check after every catalog merge warns when that list and the catalog differ, while a daily run fails and keeps an issue open until they match; and the platform and break-glass groups are administered by hand.

  5. The next automation is one more reader

    An automation that needs to know who owns a product, who leads a domain or who is on a team reads the same files at the catalog's main, and inherits what they already carry: each fact was approved by its owners, has a history and can be undone. It needs no list, form or sync job of its own. A new Terraform root in landing-zone, operations or control-plane that reads the catalog says so in its pipeline, with the tf-apply component's reads_catalog input, and the chain runs it after every catalog merge.

What makes it true

Every catalog file is checked before it merges.
control-plane's validate component runs the catalog's lint on every merge request and on main: each file against its JSON Schema, each level's parent present, every lead and team member a person who has not departed, and each team's lead one of its members with the team lead role. Each finding names its file.
Only the current owners can approve a change to the catalog.
The gate reads the ownership rules, the teams and the people from the catalog's protected main, never from the change it judges, and counts only active people with a GitLab account, never the author or the authors of its commits. An approval policy in security-policies makes the gate's approval a condition of merging in the catalog, as in every other repository the gate guards.
A catalog merge reaches the readers with no one running a command.
The catalog's pipeline on its protected main triggers landing-zone's, operations' and control-plane's pipelines in turn, then landing-zone's again, each waiting for the one before. In them the tf-apply component runs only the roots that declare reads_catalog, against the catalog's main, each with its own pipeline identity.
Every reader derives memberships and approvals from active people alone.
The groups root syncs memberships for active people alone; the GitLab roots give memberships and merge rights only to active people with a GitLab username, and admit them to the platform repositories and security-policies through one group, projectairframe/contributors, so a departure takes a person out of all of them on the chain's next run; the Grafana root adds only active people with a Grafana username; the gate counts only active people. The lint refuses a departed person in any team or lead list, so the merge request that marks someone departed also takes them out of every team and lead list.
The approval policy's list of people cannot quietly drift from the catalog.
The group-wide approval policy names its approvers one by one. Its check, policy:approvers, compares that list with the catalog's active people with a GitLab account and names each person to add or remove. In security-policies it runs on every merge request and on main, and fails on any difference. In control-plane it runs on the pass the catalog's chain triggers after every merge, where a person to add or remove only warns, so the chain goes on, and on a daily schedule, where it fails and policy:approvers:report keeps one issue open with the exact change to make until the lists match. A role, a group or an id among the approvers, or any approver but the gate's account on the gate's own rule, fails every run. Until the list follows, a person not yet on it cannot approve, and a departed person still on it reaches no platform repository. A failed check changes nothing by itself: the list changes by a merge request in security-policies.