Skip to content

Every fleet repository's GitHub settings and the organisation rulesets live as code in platform #10

Open
13 of 13 issues completed

Description

@FlexOr2

The GitHub settings of every fleet repository and the organisation rulesets are declared in platform, applied from it, and a drift turns a check red.

Caller

What is known

  • Today (API read, 06.11.2026). The 12 non-archived, non-fork repositories (aco, agent-runtime, appkit, hal, hopin, marketplace, platform, presentator, quality, songmaker, speech-runtime, webauth, all public) have secret scanning, push protection and Dependabot alerts on and delete-branch-on-merge on; only platform is squash-only, the other 11 also allow merge commits and rebase. The fork ACE-Step-1.5 has secret scanning and push protection on and Dependabot alerts off. Six archived repositories (private) are outside the contract. Three organisation rulesets (main: PR only; quality / verdict required; release tags immutable), all active with empty bypass lists, exist in the web UI only; CodeQL default setup is not configured anywhere. The private repository platform-secrets was created on 06.11.2026 and has been declared since The settings root declares the private repository platform-secrets #34 (e6be2c6).
  • Private repositories (The settings root declares the private repository platform-secrets #34, line A, ruled 06.11.2026). On a private repository, secret scanning and push protection need GitHub Secret Protection. So the root holds them only on public repositories. platform-secrets holds Dependabot alerts, squash-only merging and delete-branch-on-merge, and no security_and_analysis block. Line 1's scanning and push protection apply to public repositories, and G3 does not fail on them for a private one.
  • F1, what a read-only App sees. A read-only App cannot read ruleset definitions by id, their bypass list or the REST merge settings; GitHub returns those only to a credential that can write. It can read the effects: the organisation listing, property values, a repository's effective rules (so the targeting by quality_gate), rulesets through the repository endpoint, rule suites (so bypass use), Dependabot alerts, CodeQL default setup and the property schema. The weekly check is therefore narrowed to those reads. What stays unseen in CI and is checked on every run of the operator's command instead: a non-empty bypass list nobody has used, a targeting change that still agrees with every repository's current value, and the merge settings unless GraphQL shows them to the App. That is the residual line 2 of the expectations asks him to accept.
  • The state. An import makes every plan exit 2, and the provider's importer never sets archive_on_destroy, so a stateless run would show 12 state-only updates every time. The apply root keeps a local state at ${XDG_STATE_HOME:-$HOME/.local/state}/platform/opentofu/github.tfstate outside the checkout, as hal does; the import blocks stay and recover a lost state. The weekly check keeps no state: it reads through data sources into a throwaway state in the runner's temporary directory. State, kept plans and the cache hold the user record the provider reads, the organisation's repository listing (the names of the private archived repositories among them), ruleset definitions and the login; they are protected by mode 700 and umask 077. Encryption is not needed, because that data is what his own account already exposes to his own user and to GitHub, the files never leave his machine, and a passphrase would be a new secret outside platform and a new way to lose the state. The plan carries no token; slice G1 proves that on the decoded kept plan and state.
  • Placement. The root is src/opentofu/github/: src/ is on quality's root base list, and opentofu/ is the seam for a later src/opentofu/cloudflare/. No new root entry.
  • Pins. tofu is a mise tool, its version and URL in mise.lock (P3a, P3a: the cluster job reconciles a pull request's tree in k3d from the pinned tools #14, sets [tool_config] locked = true); the provider is = 6.13.0 in required_providers with src/opentofu/github/.terraform.lock.hcl checked with -lockfile=readonly; required_version is a floor, never an exact pin. CI reaches tofu through scripts/bootstrap_pc.sh --only tools, P3a: the cluster job reconciles a pull request's tree in k3d from the pinned tools #14's path. Dependabot's opentofu ecosystem and .aco/board.toml dependency_paths cover the two paths.
  • The apply command is scripts/apply_github_settings.sh. Without arguments it plans (exit 0 no change, 2 changes, 1 failure) on any commit and writes no state; --apply plans, shows and applies exactly that run's own plan file, only from a clean checkout at origin/main. It runs tofu under an allowlisted child environment, takes no token itself (the provider runs gh auth token), checks a login before any tofu call and reads GET /user in the plan so an anonymous plan cannot render. Every run holds an exclusive flock on a lock file in its cache directory for its whole length, so a second run waits a bounded time, then exits 1; each run writes its plan to a mktemp file and renames it to last.tfplan only when it ends. No destroy mode and no other option.
  • Safeguards. Every github_repository carries prevent_destroy and archive_on_destroy = true (line 3 of the expectations decides the mechanism; the ruled form is built unless it is ruled otherwise first); every ruleset carries prevent_destroy; the code creates a repository only through a created entry that exists until the repository does, as hopin#629 line 4 (A, 09.11.2026) ruled for figure (platform's GitHub configuration creates overnightworks/figure #312), and never deletes one; attributes outside the contract go into ignore_changes so no description or wiki is wiped; the code never manages quality_gate values; no raw response body reaches a diagnostic, log or artifact. The OpenTofu GitHub provider (integrations/github v6.13.0) has no resource for organisation code security configurations; the per-repository flags cover the contract without it. safe-settings is rejected for now: it needs a hosted process before the cluster exists and an org-admin key where it runs.
  • Deletion risk and its barrier today. A destroyed github_repository deletes the repository. The login lacks the delete_repo scope, one more barrier outside the code's control; nothing relies on it.

Contract with quality

quality's head, platform's head chat, 06.11.2026: quality does not own the ruleset's definition; platform owning it as OpenTofu code is cleaner and keeps quality's merge path, as long as these four hold.

  1. The check's name is quality's. The ruleset requires the context quality / verdict from GitHub Actions, written exactly, with no pattern, and pinned to GitHub Actions with integration_id = 15368 (today's ruleset has no pin; quality's head agreed to the pin on 06.11.2026, since gate.yml is the only producer and quality's merge command posts no status or check run); a rename is a change coordinated with quality.
  2. The targeting stays on the property. The ruleset reaches a repository only through quality_gate=true. Today's definition stays: type true_false, values editable by organisation actors, which the operator's login is. quality's merge command flips the value per repository, so the drift check ignores the values.
  3. The bypass list stays empty. quality's merge command is the one deliberate way past, logged on the pull request.
  4. Enforcement stays active.

Today (API read, 06.11.2026) the ruleset already holds all four. When this item lands, quality's docs/gate.md points at platform's definition instead of describing the ruleset; that docs line is quality's.

Slices

Order Slice Done when
1 G3 After line 2 of the expectations is ruled and the operator has created the read-only App, settings-drift.yml runs weekly and on dispatch in the environment settings-drift outside the gate and fails, naming repository and setting (a private repository by its id), on an undeclared, missing, archived or fork listing entry, Dependabot alerts off, CodeQL default setup configured, a differing property schema, ruleset rules, target or enforcement that differ, a targeting mismatch (unmanaged repositories included), any bypass rule suite in the last week, and what the App sees of scanning (on public repositories only, #34) and merge settings; it fails as "cannot judge" on a field the App cannot see. Material risk: cross-provider gate and a security read of the workflow. Follows G2.

Unknowns

Unknown Settled in
Which keyring variables gh needs under the command's allowlist; whether tofu accepts TF_CLI_CONFIG_FILE=/dev/null; whether the operator's mise meets platform's min_version; lock behaviour after an aborted local run G1
import blocks under mock_provider in tofu test; github_rest_api with a query string in its endpoint G1
Under a ruling of B on line 3: the persisted destroy flag in state after the switch G1
Whether Administration read returns security_and_analysis to the App; whether GraphQL returns merge settings to an App with Metadata read; who receives the scheduled failure email G3

Neighbours

version = 1
now = "G1 (#22, 8e56c0c), #34 (e6be2c6), #214 (#215, 18aa412) and G2 (#221, d47e7e0) are landed. An agent's apply of 08.11.2026 imported the three organisation rulesets and the property schema (4 imported, 1 changed, 0 destroyed) and the next plan is empty, so the rulesets and every declared repository match."
next = "G3 is cut, the weekly drift check; it runs once the operator has created the read-only App."
done_when = "Every fleet repository's security and merge settings and the organisation rulesets are declared in platform and applied from it the ruled way, a scheduled check fails on drift and on an undeclared repository, and no setting the code holds is set by hand any more."

size = "M"

[[expectation]]
text = "Die Sicherheits- und Merge-Einstellungen aller Repos und die Regeln der Organisation werden auf dem gewählten Weg gesetzt und gehalten."
asked_on = 2026-11-06
ruling = "A"
ruled_on = 2026-11-06
card_hash = "sha256:a51603139ab8da42e721d35ff4063b2e3221156aaca03edb41c7eb4757049328"
via = "chat"
question = "Sollen die Einstellungen aller deiner Repos an einer Stelle stehen, mit Prüfung, und wie werden Änderungen wirksam?"
problem = "Jedes Repo wird von Hand eingestellt; dreht jemand einen Schutz ab oder kommt ein Repo dazu, merkt es niemand."
decision_kind = "data_risk"
area = "GitHub-Organisation"
topic = "Repo-Einstellungen als Code"
recommended = "A"
reason = "Einstellungen ändern sich selten, ein Befehl nach dem Merge kostet also wenig; dafür liegt nirgends ein neuer Schlüssel, der Repos löschen könnte."

[[expectation.option]]
name = "Code, du wendest an"
effect = "Alle Einstellungen stehen als Code in platform; nach dem Merge setzt ein mitgelieferter Befehl sie mit deinem GitHub-Login."
notice = "Du mergst wie immer, dann läuft ein Befehl, auf deinen Zuruf auch durch mich; weicht etwas ab, wird jede Woche ein Check rot."
cost = "Eine Lane, rund drei Stunden Agentenzeit; du legst einmal im Browser eine App an, die nur lesen darf."
undo = "reversible"
undo_how = "Code und App entfernen; die Einstellungen bleiben, wie sie sind."
contract = "OpenTofu with the integrations/github provider in platform declares, for every non-archived non-fork fleet repository, secret scanning, push protection, Dependabot alerts, squash-only merging and delete-branch-on-merge, plus the organisation rulesets. Apply runs only through a command platform ships, with the operator's own gh login on his machine, started by him or on his word by an agent in his session; no credential that can write organisation or repository settings is stored in CI. A weekly scheduled CI run with a read-only organisation GitHub App fails on drift and on a repository the code does not declare. Repositories are never destroyed (prevent_destroy, archive_on_destroy). CodeQL default setup stays off."

[[expectation.option]]
name = "Code, Merge wendet an"
effect = "Wie eben, nur setzt ein Lauf nach jedem Merge die Einstellungen selbst, mit einer App, die Repos verwalten darf."
notice = "Du mergst, Minuten später gilt es in allen Repos; weicht etwas ab, wird jede Woche ein Check rot."
cost = "Wie eben; dazu liegt der Schlüssel einer App mit Admin-Rechten, die auch Repos löschen könnte, als Geheimnis in GitHub."
undo = "reversible"
undo_how = "App löschen und den Lauf entfernen; die Einstellungen bleiben, wie sie sind."
contract = "The same declarations; a push to main applies them through an organisation GitHub App with repository administration and organisation administration write, whose key is a secret of a GitHub environment limited to main; the same App's weekly scheduled plan fails on drift and on an undeclared repository. Repositories are never destroyed (prevent_destroy, archive_on_destroy). CodeQL default setup stays off."

[[expectation.option]]
name = "Nur GitHub-Vorgabe"
effect = "Ich setze einmal die Sicherheitsvorgabe der Organisation für alle und neue Repos und stelle alle auf Squash um; sonst bleibt alles Handarbeit."
notice = "Nichts Neues im Repo; eine Abweichung fällt erst beim nächsten Audit auf."
cost = "Kein Bau, keine App; Regeln und Merge-Einstellungen bleiben von Hand und ungeprüft."
undo = "reversible"
undo_how = "Die Vorgabe lässt sich in GitHub jederzeit ändern."
contract = "One organisation code security configuration with secret scanning, push protection and Dependabot alerts is attached to every repository and set as the default for new ones, and every repository is set squash-only with delete-branch-on-merge, through the API with the operator's login once he says yes to that change; rulesets stay as they are in the web UI; no drift check."

[[expectation]]
text = "Der wöchentliche Abgleich der GitHub-Einstellungen läuft auf dem gewählten Weg."
asked_on = 2026-11-06
ruling = "A"
ruled_on = 2026-11-06
card_hash = "sha256:0df985e17743bf131b51e12c0a35575d698d97be6dad6b41da6542d93196a51e"
via = "chat"
question = "Soll der wöchentliche Abgleich ohne jeden Schlüssel mit Schreibrecht laufen, auch wenn er dann zwei Dinge nur bei deinen eigenen Läufen sieht?"
problem = "GitHub zeigt Ausnahmelisten der Regeln und Merge-Einstellungen nur Schlüsseln mit Schreibrecht; ein Lese-Check sieht sie nicht."
decision_kind = "data_risk"
area = "GitHub-Organisation"
topic = "Wöchentlicher Abgleich"
recommended = "A"
reason = "Fast alles wird jede Woche geprüft, auch ob jemand eine Regel umgangen hat, und nirgends liegt ein Schlüssel, der etwas ändern kann; den Rest prüft jeder Lauf deines Befehls."

[[expectation.option]]
name = "Nur lesen, fast alles"
effect = "Eine App, die nur lesen darf, prüft jede Woche fast alles; eine noch nie benutzte Ausnahme und vielleicht die Merge-Einstellungen prüft nur dein Befehl."
notice = "Jede Woche ein Check, der bei einer Abweichung rot wird und das Repo nennt; dein Befehl prüft bei jedem Lauf alles."
cost = "Eine kleine Lücke zwischen deinen Läufen; du legst einmal im Browser die Lese-App an."
undo = "reversible"
undo_how = "Später auf einen der anderen Wege umstellbar."
contract = "G3: a read-only organisation GitHub App (Repository Administration read, Metadata read, Organization custom properties read; key in a GitHub environment limited to main) runs a weekly scheduled CI check outside the gate. It fails on an undeclared repository, on Dependabot alerts or CodeQL setup differing, on the property schema, on the rulesets' visible rules and enforcement, on targeting by effect (properties/values against rules/branches, for managed and unmanaged repositories), on any bypass use (rule-suites with rule_suite_result=bypass), on scanning where visible, and on merge settings if GraphQL shows them to the App. Not seen weekly, and checked on every run of the operator's command instead: a non-empty bypass list nobody has used, a targeting change that still agrees with every repository's current value, and merge settings unless GraphQL answers. Reopen when GitHub gives Apps read access to ruleset definitions or merge settings."

[[expectation.option]]
name = "App mit Schreibrecht"
effect = "Eine App mit Admin-Rechten prüft jede Woche wirklich alles."
notice = "Jede Woche ein vollständiger Check."
cost = "Der Schlüssel einer App, die Einstellungen ändern kann, liegt dann doch als Geheimnis in GitHub; genau das hast du mit deiner Wahl ausgeschlossen."
undo = "reversible"
undo_how = "App löschen; die Einstellungen bleiben, wie sie sind."
contract = "G3 runs the full plan weekly in CI through an organisation GitHub App with organisation administration write and repository administration and contents write, its key a secret of a GitHub environment limited to main; this replaces the read-only App of the ruled option A."

[[expectation.option]]
name = "Abgleich auf deinem PC"
effect = "Dein PC startet jede Woche selbst den vollständigen Abgleich mit deinem Login."
notice = "Läuft nur, wenn dein PC an ist; das Ergebnis siehst du als Meldung auf dem PC, nicht in GitHub."
cost = "Eine Änderung an deinem laufenden PC vor der Umstellung, und kein Check, den andere sehen."
undo = "reversible"
undo_how = "Den Zeitplan auf dem PC wieder entfernen."
contract = "A systemd user timer on the operator's PC runs the shipped command plan-only weekly with his own login; no App and no CI run; a host change before P10, so it waits for the operator's word to touch the live PC."

[[expectation]]
text = "Ein Repo, das nicht mehr verwaltet werden soll, wird auf dem gewählten Weg aus der Verwaltung genommen, und kein Weg löscht je ein Repo."
asked_on = 2026-11-06
ruling = "B"
ruled_on = 2026-11-06
card_hash = "sha256:70534633a0082801bedcb2c63bcfdeee2edd7acde4a4a2739ffef8646533b572"
via = "chat"
question = "Soll ein Repo, das nicht mehr verwaltet werden soll, mit einer sichtbaren Zeile im Pull Request freigegeben werden statt mit einem Handgriff?"
problem = "Mit dem bisher gewählten Schutz bricht das Werkzeug ab, wenn ein Repo aus dem Code genommen wird."
decision_kind = "data_risk"
area = "GitHub-Organisation"
topic = "Repo aus Verwaltung nehmen"
recommended = "B"
reason = "Beide Wege löschen nie; der zweite macht das Freigeben zu einer Zeile, die du im Pull Request siehst, und der wöchentliche Abgleich meldet trotzdem jedes vergessene Repo."

[[expectation.option]]
name = "Sperre, wie gewählt"
effect = "Nimmt jemand ein Repo aus dem Code, bricht der Lauf ab; zum Freigeben braucht es einen Handgriff am gespeicherten Zustand."
same_as_today = true
notice = "Beim Archivieren von hal tippt jemand einen Extra-Befehl, den kein Review sieht."
cost = "Ein Handgriff außerhalb des Codes."
undo = "reversible"
undo_how = "Später auf den anderen Weg umstellbar."
contract = "Every managed repository carries lifecycle prevent_destroy = true and archive_on_destroy = true; taking one out of management needs a hand tofu state rm."

[[expectation.option]]
name = "Nur loslassen, nie löschen"
effect = "Nimmt jemand ein Repo aus dem Code, lässt das Werkzeug es nur los und löscht nie; der Abgleich meldet es, bis es mit Grund als nicht verwaltet eingetragen ist."
notice = "Ein Repo freigeben ist eine Zeile im Pull Request, die du siehst und mergst."
cost = "Braucht eine etwas neuere Version des Werkzeugs; sonst kein Mehraufwand."
undo = "reversible"
undo_how = "Später auf die Sperre umstellbar."
contract = "Every managed repository carries lifecycle destroy = false and archive_on_destroy = true, with OpenTofu 1.12.0 or later as the required_version floor; prevent_destroy is not used; a removed key is forgotten, never destroyed, and the undeclared-repository check stays red until the repository is listed as unmanaged with a reason. Until this line is ruled, G1 builds the ruled prevent_destroy form."

[[expectation]]
text = "Eine Änderung, die eine neue schwere Sicherheitswarnung des Code-Scanners mitbringt, kommt nicht unbemerkt in den Hauptstand."
asked_on = 2026-11-09
ruling = "A"
ruled_on = 2026-11-09
card_hash = "sha256:4ee62cf642f9a58b3b2408bd210849ec6f25f5abefecdd502cb9629b4d429656"
via = "board"
question = "Soll eine Änderung mit einer neuen schweren Sicherheitswarnung gar nicht erst in den Hauptstand kommen?"
problem = "Eine schwere Sicherheitswarnung kann unbemerkt im Hauptstand liegen; bei einer App lagen zwei fast drei Wochen."
decision_kind = "technical"
area = "Sicherheit"
topic = "Code-Scanner als Sperre"
recommended = "A"
reason = "Bei einer App lagen zwei schwere Warnungen vom 18.10. bis zur Prüfung am 06.11. unbemerkt im Hauptstand. Der Scanner läuft schon bei jeder Änderung, nur sperrt sein Ergebnis nichts. Dein Grundsatz sagt: Was eine Maschine prüfen kann, gehört in die Prüfungen, nicht ins Erinnern. A sperrt nur, was eine Änderung neu mitbringt; alte Warnungen halten nichts auf. Die wöchentliche Durchsicht bei B ließe bis zu einer Woche Lücke."

[[expectation.option]]
name = "Neue schwere Warnung sperrt"
effect = "Bringt eine Änderung eine neue schwere oder kritische Sicherheitswarnung des Code-Scanners mit, lässt sie sich nicht mergen. Das gilt, bis die Warnung behoben oder mit Begründung abgewiesen ist."
notice = "Du siehst am Pull Request einen roten Pflicht-Check mit der Warnung. Eine Fehlwarnung weist ein Agent mit Begründung ab, dann geht es weiter."
cost = "Eine Regel in den Regeln der Organisation. Eine Fehlwarnung hält eine Änderung auf, bis sie abgewiesen ist. Jedes betroffene Repository muss den Scanner laufen haben; das wird vor dem Anwenden je Repository geprüft."
undo = "reversible"
undo_how = "Die Regel aus den Organisationsregeln entfernen und erneut anwenden."
contract = "In `src/opentofu/github` (module `organization/`): a code-scanning rule (provider `required_code_scanning`, tool CodeQL, `security_alerts_threshold = \"high_or_higher\"`, `alerts_threshold = \"none\"`) reaching repositories through `quality_gate=true`, with `prevent_destroy` and `destroy = false` like the other rulesets; applied only by `scripts/apply_github_settings.sh --apply` from a clean `origin/main`. Verified at the pull, else this card returns: provider 6.13.0 carries the rule, every `quality_gate=true` repository runs CodeQL through quality's `codeql.yml`, and the rule judges only alerts a pull request introduces."

[[expectation.option]]
name = "Wöchentliche Durchsicht"
effect = "Nichts sperrt. Ein geplanter Lauf geht jede Woche jedes Repository durch und macht aus neuen Warnungen einen Eintrag zum Abarbeiten."
notice = "Spätestens nach einer Woche steht eine neue Warnung auf der Liste. Kein Merge wird aufgehalten."
cost = "Bis zu einer Woche, in der eine Warnung im Hauptstand liegt. Es hängt daran, dass der Lauf jede Woche wirklich startet."
undo = "reversible"
undo_how = "Den geplanten Lauf wieder abstellen."
contract = "No ruleset change; the `sonar-distributor` skill's weekly scheduled run per fleet repository owns untriaged CodeQL alerts (one distributor issue per repository), scheduled on marketplace's board."

[[expectation.option]]
name = "Wie heute"
effect = "Der Scanner läuft bei jeder Änderung, sein Ergebnis sperrt nichts, und niemand sieht planmäßig nach."
same_as_today = true
notice = "Eine Warnung fällt auf, wenn jemand zufällig hinschaut oder eine Prüfung sie findet."
cost = "Nichts zu bauen. Warnungen können wochenlang unbemerkt liegen."
undo = "reversible"
undo_how = "Eine der anderen Wahlen später nachholen."
contract = "No change; marketplace#484's row S5 stays an accepted risk named on #10."

[[expectation]]
text = "Die Sperre gegen neue schwere Sicherheitswarnungen gilt für jedes Repo am Qualitäts-Tor, und das Repo der verschlüsselten Geheimnisse ist auf dem gewählten Weg geregelt."
asked_on = 2026-11-09
ruling = "A"
ruled_on = 2026-11-09
card_hash = "sha256:c9234a31d2c3c5723658e985a9b08ef2e066be062ce1fd002f0ccfe2a364b320"
via = "board"
question = "Wie soll die neue Sperre mit dem Geheimnis-Repo umgehen, das keinen Code-Scanner hat?"
problem = "Das Geheimnis-Repo hat keinen Code-Scanner; die Sperre würde dort jede Änderung für immer blockieren."
decision_kind = "access"
area = "Sicherheit"
topic = "Code-Scanner-Sperre"
recommended = "A"
reason = "Du hast heute A gewählt: Eine Änderung mit neuer schwerer Warnung wird nicht gemergt. Bei der Prüfung vor dem Bau zeigte sich: 14 der 16 Repos am Qualitäts-Tor haben den Scanner. Ein Repo ist archiviert und nimmt keine Änderungen mehr an. Das Geheimnis-Repo ist privat und hat den Scanner nicht. GitHub sperrt dort jede Änderung, solange der Scanner fehlt. Das Repo enthält nur verschlüsselte Dateien und keinen Programmcode, also hätte der Scanner dort nichts zu prüfen. A nimmt nur dieses eine Repo aus und behält die Sperre für alle anderen. B kostet monatlich Geld und prüft nichts Sinnvolles."

[[expectation.option]]
name = "Geheimnis-Repo ausnehmen"
effect = "Die Sperre gilt für jedes Repo am Qualitäts-Tor außer dem Geheimnis-Repo, das ausdrücklich ausgenommen wird."
notice = "In 15 Repos wird eine Änderung mit neuer schwerer Warnung nicht mehr gemergt. Im Geheimnis-Repo ändert sich nichts."
cost = "Eine Ausnahme mehr in den Regeln. Ein späteres Repo ohne Scanner bräuchte wieder eine Entscheidung."
undo = "reversible"
undo_how = "Die Ausnahme wieder streichen."
contract = "G4 as drafted (a new organisation ruleset with the rule required_code_scanning: tool CodeQL, security_alerts_threshold high_or_higher, alerts_threshold none; prevent_destroy and destroy = false) targets quality_gate=true and excludes platform-secrets: by a repository-name exclusion if the provider and GitHub's API allow it beside the property condition, else by a second custom property declared in src/opentofu/github/organization whose excluded value only platform-secrets carries; verified at the pull. Condition 3 (only alerts a pull request introduces) is proven by a throwaway pull request after the apply; if it fails, this line returns."

[[expectation.option]]
name = "Scanner dort einschalten"
effect = "Das Geheimnis-Repo bekommt GitHubs kostenpflichtige Code-Sicherheit und einen eigenen Scanner-Lauf. Die Sperre gilt dann überall gleich."
notice = "Keine Ausnahme in den Regeln. Im Geheimnis-Repo läuft bei jeder Änderung ein Scanner, der keinen Programmcode findet."
cost = "Monatliche Kosten für das private Repo. Ein Scanner-Lauf ohne Programmcode kann scheitern und Änderungen dort trotzdem sperren."
undo = "reversible"
undo_how = "Die Code-Sicherheit wieder abschalten, dann gilt A oder C."
contract = "GitHub Code Security is enabled for platform-secrets (the operator's purchase), it gains a workflow calling quality's codeql.yml, and G4's ruleset targets quality_gate=true without exception."

[[expectation.option]]
name = "Sperre vorerst nicht"
effect = "Es kommt keine neue Sperre. Neue schwere Warnungen sammelt weiter der wöchentliche Prüflauf je Repo."
same_as_today = true
notice = "Eine Änderung mit neuer schwerer Warnung kann gemergt werden, wenn das Review sie übersieht."
cost = "Ein Sicherheitsproblem fällt erst beim wöchentlichen Prüflauf auf."
undo = "reversible"
undo_how = "Später A oder B wählen."
contract = "No ruleset change; the sonar-distributor skill's weekly scheduled run per fleet repository owns untriaged CodeQL alerts, as line 4's option B described."

[[expectation]]
text = "Die neue Sperre verhindert, dass ein Pull Request mit einem neuen schweren Sicherheitsfund gemergt wird. Eingerichtet und ausgelesen ist sie; gesehen, wie sie einen echten Merge abweist, hat sie noch niemand. Mein Testlauf dafür wurde vom Sicherheitsfilter dieser Sitzung abgelehnt, weil er einen absichtlichen Merge-Versuch enthält."
asked_on = 2026-11-09
ruling = "A"
ruled_on = 2026-11-09
card_hash = "sha256:f22316dbc3a34ba3b0b5f9e1d547db07b0d3f758178ddf14b26dd7e0f0bd2837"
via = "board"
question = "Wie soll geprüft werden, dass die neue Merge-Sperre bei Sicherheitsfunden wirklich greift?"
problem = "Die Sperre ist gesetzt, aber nie im Ernstfall gesehen; ein Testlauf braucht einen absichtlichen Merge-Versuch, den der Sicherheitsfilter ablehnt."
decision_kind = "technical"
area = "GitHub"
topic = "Merge-Sperre bei Sicherheitsfunden prüfen"
recommended = "A"
reason = "Die Regel ist gesetzt und ausgelesen; ein Lesetest kostet fast nichts und braucht keinen Merge-Versuch. Den Merge-Versuch würde ich nur nehmen, wenn dir die stärkere Antwort den Aufwand wert ist."

[[expectation.option]]
name = "Nur Zustand lesen"
effect = "Ein Agent öffnet einen Wegwerf-Pull-Request mit einem absichtlichen Fund, liest nur ab, ob GitHub ihn als gesperrt zeigt, und löscht ihn wieder. Es gibt keinen Merge-Versuch."
notice = "Du merkst nichts. Der Beweis ist etwas schwächer, weil GitHub beim Sperren oft mehrere Gründe zugleich hat."
cost = "Ein CI-Lauf und etwa 15 Minuten eines Agenten."
undo = "reversible"
undo_how = "Der Pull Request wird geschlossen und sein Zweig gelöscht."
contract = "Throwaway PR planting py/request-without-cert-validation; reads mergeStateStatus and the code-scanning alert; no PUT /merge; closed unmerged, branch deleted. #308 rows 6 and 7 are recorded as read, not as refused."

[[expectation.option]]
name = "Merge-Versuch erlauben"
effect = "Du gibst frei, dass ein Agent einmal absichtlich versucht, diesen Wegwerf-Pull-Request zu mergen, nur solange GitHub ihn als gesperrt zeigt. Die Antwort nennt genau die Regel, die greift."
notice = "Du merkst nichts. Sollte die Sperre wider Erwarten nicht greifen, landet eine harmlose Testdatei auf dem Hauptzweig."
cost = "Ein CI-Lauf, dazu deine Freigabe, weil der Filter den Versuch sonst ablehnt."
undo = "reversible"
undo_how = "Ein Pull Request nimmt die Testdatei wieder heraus."
contract = "As A, plus one PUT /repos/overnightworks/platform/pulls/N/merge with sha, only while mergeStateStatus is BLOCKED and current_user_can_bypass is never on every ruleset of main; #308 rows 6 and 7 and aco's request for the refusal text."

[[expectation.option]]
name = "Nicht testen"
effect = "Die Regel bleibt, wie sie ist. Der erste echte Fund zeigt, ob sie greift."
notice = "Du merkst nichts, außer sie greift nicht: dann läge ein echter Sicherheitsfund schon auf dem Hauptzweig."
cost = "Keine Arbeit. Das Restrisiko bleibt, bis der erste echte Fall kommt."
undo = "reversible"
undo_how = "Der Testlauf lässt sich jederzeit nachholen."
contract = "#308 rows 6 and 7 stay not driven and are named as such; no throwaway PR."

Activity

  1. added theissue type on Nov 6, 2026
  2. changed the issue type fromtoon Nov 6, 2026
  3. FlexOr2 commented on Nov 6, 2026

    @FlexOr2
    MemberAuthor

    Follow-ups from the G1 lane (#20, PR #22, tip 6782e01), for the next G slice. None of them blocks G1.

    1. Before the first --apply. The PC's mise is 2026.10.23, below min_version = "2026.11.3". The command stops loudly at mise, as cell C10 expects. mise must be updated first. The pinned binary comes from scripts/bootstrap_pc.sh --only tools, which is the first host step's job.
    2. The caller's GITHUB_TOKEN reaches mise. git fetch, mise install --quiet opentofu and mise which tofu run in the caller's environment, outside the env -i allowlist. A caller's GITHUB_TOKEN is therefore used by mise for its GitHub downloads. tofu itself never sees it, which is what S5 covers.
    3. Stale unmanaged entries. Nothing checks that an unmanaged entry is still listed. A stale reason, such as a vendor fork that was deleted or archived, stays silently.
    4. Landing cell C14. destroy = false makes archive_on_destroy unreachable on a forget. Read C14 against the ruled sentence of Every fleet repository's GitHub settings and the organisation rulesets live as code in platform #10's line 3 (B), which keeps both.
    5. Leftovers from interrupted runs.
      • A SIGKILL of the script leaves its plan.XXXXXX in the cache, and no later run removes it.
      • An interrupted lane run left one orphaned terraform-provider-github process and an empty /tmp/apply_github_settings.* directory. The head ended both.
      • A failed run leaves the previous last.tfplan in place. --apply applies only its own run's plan, so this cannot change what it applies, but the stale file can mislead a reader.
    6. Output polish.
      • A plan that fails a listing precondition prints a full plan summary before its error, which can read as if a plan had been kept.
      • --help and -h exit 1.
      • When gh has no login, gh's own hint suggests setting GH_TOKEN, which the command drops by design. The command's own line correctly says gh auth login.
  4. FlexOr2 commented on Nov 6, 2026

    @FlexOr2
    MemberAuthor

    Journal, 07.11.2026: the operator ran scripts/apply_github_settings.sh --apply at origin/main e6be2c6. It printed Apply complete! Resources: 26 imported, 0 added, 14 changed, 0 destroyed. The next plan by the head exited 0 with No changes. Your infrastructure matches the configuration. So S3 holds, and with it G1's landing cell on #10 (the next plan is empty after the first apply).

  5. FlexOr2 commented on Nov 7, 2026

    @FlexOr2
    MemberAuthor

    Gap from #86, closed as not planned. The organisation flag deploy_keys_enabled_for_repositories, which Flux's deploy key on platform-secrets needs, stays set by hand (true, since #61's B6). integrations/github 6.13.0 has no resource that carries it; upstream, integrations/terraform-provider-github#3388 tracks it. #86 reopens when the provider ships the attribute.

  6. FlexOr2 commented on Nov 8, 2026

    @FlexOr2
    MemberAuthor

    Follow-ups from the last two reviews of #219 (PR 221), owner this epic, to be carried by G3's lane, which touches the same files:

    1. AGENTS.md, "a replacement included": a replacement of an object with destroy = false plans forget, create and passes, and every object of this root is such an object today. It deletes nothing, but the clause reads wider than the code. Say that it passes.
    2. AGENTS.md, "a deliberate removal is a removed block": this cannot apply to a repository key. removed with an instance key fails at init, and on the whole resource it fails while its block stands. Say that a key removed from managed is forgotten without one, which is ruling 3 B's path.
    3. mise.toml: the description of github:test omits the refusal test the task now runs.
    4. tests/github/plan_with_a_delete_is_refused.sh: its fixtures always hold a no-op resource. Add empty and absent resource_changes, several deletions, and a failing tofu show or yq under --apply. A review drove these by hand on the shipped expression and found no defect.

    From the same lane, for G3 to account for: the default of quality_gate is invisible to drift detection, and require_extra_approval_for_unattributed_changes is inert at approval count 0, with the test holding the count at 0.

  7. FlexOr2 commented on Nov 9, 2026

    @FlexOr2
    MemberAuthor

    Routed from the fleet audit's distributor overnightworks/marketplace#484, row S5 (marketplace's head, 09.11.2026): "CodeQL gates nothing". No organisation ruleset requires a code-scanning result, so a high alert can sit on main unnoticed. Evidence: hopin had 2 untriaged high alerts from 18.10 until the audit of 06.11. They were fixed in hopin#523, and hopin has no open alert since.

    Whether CodeQL should gate needs the operator's yes (the audit marked it so). It belongs here because this item owns the organisation rulesets as code. The open question for its card: a required code-scanning rule in the rulesets, or no gate, with the sonar-distributor's weekly run as the owner of untriaged alerts. Its remaining risk is the three weeks hopin's alerts sat. No recommendation from marketplace; the owner weighs it.

  8. 1 remaining item

  9. FlexOr2 commented on Nov 9, 2026

    @FlexOr2
    MemberAuthor

    Follow-up from #313's lane (PR #347), pre-existing and not introduced there: tests/github/plan_with_a_delete_is_refused.sh makes a git commit in a throwaway repository and so runs the machine's global hooks (core.hooksPath). On the PC that hook fails with "mise could not locate gitleaks", so mise run github:test ends red locally while CI's opentofu job is green. The test is not independent of the caller's git configuration; its owner is this item's next slice that touches tests/github/.

  10. FlexOr2 commented on Nov 9, 2026

    @FlexOr2
    MemberAuthor

    Follow-up from #367's review (PR #368), pre-existing and not introduced by that lane's two files: src/opentofu/github has no ignore rule for .terraform/. mise run github:test uses a temporary TF_DATA_DIR, but a plain tofu init in a worktree writes .terraform/ with the 26 MB provider binary into the tree, and a git add -A then commits it, as happened on that pull request before its review caught it. Neither the gate's root-layout check nor a size check refused it. The owner is this item's next slice that touches the root: an ignore file beside the root, or a check that fails on a tracked .terraform path.

  11. FlexOr2 commented on Nov 9, 2026

    @FlexOr2
    MemberAuthor

    Follow-up of #367 (PR #368, 7f971ed), owed when aco's head reports overnightworks/scratch-landing-drive archived (the throwaway repository of aco#773's drive): remove its entry from local.unmanaged in src/opentofu/github/repositories.tf and the run an_unmanaged_repository_listed_as_public_and_not_a_fork_passes in repository_listing.tftest.hcl, the two places that name it. An archived repository is outside the listing check, so the removal fails no plan.

  12. FlexOr2 commented on Nov 10, 2026

    @FlexOr2
    MemberAuthor

    From aco's head, 10.11.2026, a fact of the drive for overnightworks/aco#773 that bears on the rulesets this root holds: disabling a ruleset that carries the merge_queue rule while an entry is building drops that entry silently; the pull request stays open, and gh pr merge --match-head-commit then merges it directly at once. So flipping a conditioned ruleset off for a landing would still work under a merge queue. The throwaway repository overnightworks/scratch-landing-drive is archived; #374 removes its entry.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions