• v2.2.0 7263f85936

    v2.2.0
    CI / verify (push) Successful in 44s
    Release / release (push) Successful in 36s
    Stable

    torben released this 2026-09-01 16:09:37 +00:00 | 105 commits to main since this release

    2.2.0 - 2026-09-01 - Publish-Remote Gate: publish schreibt nur an erklaerte Ziele

    Author: Torben Nehmer

    Der Stack bekommt sein drittes Gate. Die beiden bestehenden fragen, ob eine Änderung zu groß
    ist und ob ein Rebase gefährlich ist. Dieses fragt, was darunter liegt: ob das überhaupt das
    richtige Repository ist.

    Das Problem entsteht erst durch die private Instanz. Ein Checkout mit eigenem Inhalt hat
    typischerweise zwei Remotes — sein eigenes und das öffentliche Upstream, von dem er
    Stack-Updates zieht. Git unterscheidet die beim Push nicht, also legt ein falsches --remote
    einen privaten Korpus auf ein öffentliches Repository. Das ist nicht billig rückholbar, und
    zwar nachweislich: Beim Veröffentlichen dieses Repos blieb die gesamte alte History nach dem
    Force-Push per SHA abrufbar, bis auf dem Server die Reflogs verfielen und git gc --prune=now
    lief. Ein Force-Push bewegt den Branch, nicht die Objekte.

    .wikitool-remotes.json nennt die erlaubten Push-URLs. Nicht die Remote-Namen: Eine
    Namensliste ließe ein publish durch, dessen origin umgebogen wurde, und genau das ist der
    Fall, den das Gate fangen soll. Gelesen wird die pushurl, wenn der Remote eine setzt, denn
    dorthin schreibt git push tatsächlich.

    Pro Checkout und gitignored, aus demselben Grund wie ENVIRONMENT.md: Zwei Klone pushen an
    zwei verschiedene Orte, eine committete Kopie würde einem privaten Klon also mitteilen, das
    öffentliche Upstream sei ein legitimes Ziel für seinen eigenen Inhalt. Fehlt die Datei, gilt
    keine Beschränkung
    — ein Checkout mit einem Remote und ohne Privates hat nichts zu schützen,
    und eine Pflichtdatei würde aus einer Sicherung Papierkram machen. Eine kaputte Datei ist
    dagegen ein Fehler und kein „keine Beschränkung": Eine beschädigte Sicherung darf sich nicht
    wie eine abgeschaltete verhalten.

    Kein Token, keine Flagge. Die anderen beiden Gates lösen sich mit einem --confirm <token>,
    weil ihre Frage („ist diese Änderung richtig?") für genau ein Changeset beantwortbar ist. Dieses
    fragt „gehört dieser Inhalt in jenes Repository?", und das ist eine stehende Eigenschaft des
    Checkouts, kein Einzelfallurteil. Der Weg daran vorbei ist ein bewusster Edit des Nutzers.
    Ein Agent, der die Datei anfasst, um an einer Verweigerung vorbeizukommen, öffnet ein Gate aus
    eigenem Antrieb — Invariante 6.

    doctor meldet den Zustand statt ihn zu erzwingen: OK mit Anzahl der Ziele, OK bei
    Abwesenheit mit einem Remote, und WARN bei mehr als einem Remote ohne Allowlist — also genau in
    der Form, die eine private Instanz annimmt, sobald sie das Upstream hinzufügt.

    Und die Prozedur, für die das Gate gebaut wurde. instructions/private-instance.md (neu)
    beschreibt die dritte Instanz-Form neben „frisch aufsetzen" und „Repo klonen": eine private
    Arbeitsinstanz, die Stack-Updates von einem öffentlichen Upstream per git merge zieht und
    deren eigener Inhalt nie zurückwandert. Der Grund, warum das dem Tarball-Weg vorzuziehen ist,
    steht dort ausformuliert — cp -r hat keinen Drei-Wege-Merge und keine Konflikterkennung.
    Schritt 4 der Anleitung ist das Gate, und zwar ausdrücklich vor dem ersten publish:
    später hinzugefügt schützt es das Fenster nicht, das es schließen soll.

    Dateien: config.PUBLISH_REMOTES_FILENAME, git_publish.read_allowed_push_urls(),
    push_url_for(), publish_remote_refusal() und die Prüfung vor dem Reconcile-Schritt,
    doctor.check_publish_remotes(), .gitignore, instructions/gates.md,
    instructions/private-instance.md (neu), AGENTS.md (Gate-Liste und Bootstrap-Routing),
    12 neue Tests in test_git_publish.py.

    Downloads