Przejdź do głównej zawartości

Od intencji do produkcji: kompletny potok na przykładzie

Potok od intencji do produkcji prowadzi jedną zmianę agenta od zapisanej intencji do pełnego ruchu produkcyjnego bez ręcznych kroków poza akceptacjami, których wymaga jej klasa ryzyka. CI wylicza klasę, ruleset GitHuba i auto-merge scalają według niej, klasa wyznacza budżet wdrożenia, naruszenie SLO uruchamia automatyczny rollback, a każdy incydent staje się ewaluacją.

Twój potok od zgłoszenia do PR działa, a jego pull requesty przychodzą z zielonymi bramkami i pakietem dowodów. Potem czekają: ktoś scala w czwartek, ktoś wdraża w piątek, w sobotę regresja budzi dyżurnego, a wniosek ląduje w wątku na czacie, którego żaden agent nie przeczyta. Pisanie kodu zostało zautomatyzowane, droga na produkcję i z powrotem — nie.

Ten tutorial jest dla dewelopera, który podpina potok, tech leada, który ustala klasy i limity, oraz CTO, który decyduje, co może trafić na produkcję bez czytania kodu przez człowieka. Prowadzi jedną zmianę, paginację kursorem dla GET /orders, przez wszystkie etapy, a każdy plik jest do skopiowania.

  • Mapę etapów od intencji do produkcji, z plikiem i rolą człowieka na każdym etapie.
  • Poprawki integracyjne, dzięki którym potok od zgłoszenia do PR i pakiet dowodów działają jako całość.
  • merge-by-class.yml, który włącza auto-merge tylko dla pull requestów low, z joba, który nigdy nie dotyka kodu pull requesta.
  • Job planujący w deploy.yml, który wybiera budżet zasięgu szkód według klasy scalonego pull requesta i przy high czeka na wskazanego zatwierdzającego.
  • slo-rollback.yml i open-revert.sh: alert SLO przywraca poprzednie wydanie, otwiera zgłoszenie incydentu i pull request z revertem oraz wstrzymuje wdrożenia.
  • Pętlę od incydentu do ewaluacji, trzy prompty do skopiowania, pięć ćwiczeń akceptacyjnych i liczby, które pokazują, że łańcuch działa.

Potok ma osiem etapów. Ostatnią kolumnę przeczytaj dwa razy: ludzie zostają w pętli tam, gdzie ich osąd coś wnosi, a nie przy każdym diffie.

EtapCo się dziejePlik w repozytoriumKto decydujeRola człowieka
1. IntencjaPotrzeba produktowa staje się zgłoszeniem gotowym dla agenta: kryteria akceptacji, dozwolone ścieżki i metryka progu.github/ISSUE_TEMPLATE/agent-task.ymlWłaściciel zgłoszeniaPisze albo zatwierdza intencję, nadaje agent:ready
2. BudowaAgent implementuje zmianę w izolowanym przebiegu, otwiera się szkic pull requestaagent-issue.yml, agent-loop.ymlWorkflowBrak, dopóki pętla mieści się w limitach
3. KlasyfikacjaCI wylicza klasę ryzyka z diffa i nadaje etykietęevidence-bundle.yml, check-evidence.mjsCI, według polityki z gałęzi bazowejJest właścicielem pliku polityki
4. Scalenielow scala się automatycznie po akceptacji; standard czeka na recenzenta pakietu; high czeka na właściciela kodumerge-by-class.yml, ruleset, CODEOWNERSRulesetAkceptuje na podstawie pakietu; właściciele kodu czytają kod high
5. WydanieKlasa wybiera ekspozycję: pełne wdrożenie, canary albo wdrożenie etapowedeploy.yml, scripts/deploy.shPlan wdrożeniaWskazany zatwierdzający promuje high
6. ObserwacjaProgi i alerty SLO oceniają zmianę na prawdziwym ruchuAnaliza wdrożenia, reguły alertówKontroler albo alertBrak
7. RollbackRuch wraca do poprzedniego wydania; otwierają się pull request z revertem i zgłoszenie incydentuslo-rollback.yml, scripts/open-revert.shAlert, przez zdarzenie dispatchDyżurny czyta zgłoszenie incydentu
8. NaukaIncydent staje się ewaluacją i, jeśli trzeba, zmianą politykievals/incidents/, evidence-policy.ymlPotok, potem właściciel koduZatwierdza ewaluację i zmianę polityki

Zmiana z przykładu przechodzi przez te etapy w ciągu jednego przedpołudnia. Godziny są ilustracją, nie pomiarem.

GodzinaZdarzenie
09:02Zgłoszenie #418, „Paginate GET /orders with a cursor”, dostaje etykietę agent:ready; o 09:31 otwiera się pull request #423 z pakietem.
10:07CI sklasyfikował go jako standard (zmiana zachowania w pokrytym kodzie, nowe testy), więc recenzent zaakceptował go na podstawie pakietu i scalił. deploy.yml uruchamia canary na 5%.
10:24Odsetek odpowiedzi 5xx w canary przekracza próg. Kontroler przerywa wdrożenie, alert wysyła slo-breach, otwierają się zgłoszenie incydentu #431 i pull request z revertem #432, a wdrożenia zostają wstrzymane.
11:40Zadanie ewaluacji z #431 przechodzi przez ten sam potok; ewaluacja oblewa zły commit, przechodzi na revercie, a właściciel kodu ją zatwierdza.

Krok 1: zapisz intencję tak, by potok umiał ją skierować

Dział zatytułowany „Krok 1: zapisz intencję tak, by potok umiał ją skierować”

Formularz zgłoszenia z potoku od zgłoszenia do PR pyta już o rezultat, kryteria akceptacji i dozwolone ścieżki. Dodaj dwa pola, których potrzebują dalsze etapy. Próg pochodzi z dokumentu SLO usługi i powstaje przed zmianą, bo agent, który wybiera próg po obejrzeniu liczb z canary, sam ocenia swoją pracę.

# Append to the body of .github/ISSUE_TEMPLATE/agent-task.yml
- type: dropdown
id: expected-risk
attributes:
label: Expected risk class
description: CI computes the real class from the diff. This can only raise it.
options: [low, standard, high]
validations: { required: true }
- type: textarea
id: guardrail
attributes:
label: Guardrail
description: "Service, SLO document and the metric that triggers rollback, e.g. orders-api, docs/slo/orders-api.md, 5xx ratio < 0.01 on the canary"
validations: { required: true }

Pierwszy prompt zamienia notatkę produktową w zgłoszenie z wypełnionymi polami. Działa bez zmian w Claude Code, Codeksie i Cursorze.

Krok 2: połącz potok od zgłoszenia do PR z pakietem dowodów

Dział zatytułowany „Krok 2: połącz potok od zgłoszenia do PR z pakietem dowodów”

Obie strony powstały tak, by działały osobno, więc uruchomienie ich razem wymaga trzech zmian. Bez nich każdy pull request agenta oblewa check pakietu, ruleset nie odróżnia dwóch checków albo krok z etykietami kończy się błędem.

  1. Dołącz pakiet do pull requesta agenta. Workflow od zgłoszenia do PR buduje opis pull requesta z raportu agenta, a checker pakietu szuka bloku YAML zaczynającego się od evidence_bundle:. Dodaj krok do .github/agent/implement.md, żeby agent taki blok pisał:

    8. Write .agent/bundle.yml in the format of the evidence_bundle block in
    .github/pull_request_template.md, starting with the line
    "evidence_bundle: 1". Copy the guardrail from the issue into
    rollout.guardrails. Declare the risk class from the issue or higher.

    Dodaj .agent/bundle.yml do ścieżek artefaktu w jobie agent, a potem dołącz go w jobie open-pr, po raporcie agenta i przed gh pr create:

    Okno terminala
    if [ -s /tmp/agent/.agent/bundle.yml ]; then
    { echo; echo '## Evidence bundle'; echo; echo '~~~yaml'
    cat /tmp/agent/.agent/bundle.yml
    echo '~~~'; } >> body.md
    fi

    To samo zrób w push-fix przez gh pr edit "$PR" --body-file, żeby poprawka zmieniająca testy aktualizowała też pakiet. Wyzwalacz edited w evidence-bundle.yml uruchomi wtedy check ponownie.

  2. Nadaj każdemu checkowi własną nazwę. Job gates w agent-loop.yml raportuje się jako evidence, podobnie jak job evidence w evidence-bundle.yml. Zmień nazwę pierwszego na name: agent-gates, zostaw jego identyfikator gates, żeby odwołania needs: dalej działały, i wymagaj obu nazw w rulesecie. Dokumentacja GitHuba nie mówi, który wynik ruleset odczyta, gdy dwa joby raportują tę samą nazwę checka, więc nie zostawiaj tego przypadkowi.

  3. Utwórz nowe etykiety. revert, incident i incident:agent, obok etykiet risk:*, których używa już pakiet dowodów. gh nie tworzy brakujących etykiet.

Krok 3: scalaj według klasy ryzyka rulesetem i auto-merge

Dział zatytułowany „Krok 3: scalaj według klasy ryzyka rulesetem i auto-merge”

Ruleset decyduje, czy scalenie jest dozwolone, CODEOWNERS decyduje, kto musi czytać, a mały workflow prosi GitHuba o scalenie w chwili, gdy ruleset na to pozwoli, wyłącznie dla low. Skonfiguruj ruleset na gałęzi domyślnej (Settings, potem Rulesets):

RegułaUstawienieJaką lukę zamyka
Require a pull request before merging1 akceptacja, odrzucanie nieaktualnych akceptacji, wymagane review właścicieli koduGitHub odrzuca akceptację jako nieaktualną, gdy diff zmieni się po niej, więc poprawka wypchnięta po akceptacji nie przejdzie na starej
Require status checks to passevidence, agent-gates i twój job z testami; każdy przypięty do aplikacji GitHub Actions jako oczekiwanego źródłaStatus może ustawić każda integracja z prawem zapisu; przypięcie źródła sprawia, że check zazielenią tylko twoje workflowy
Allowed merge methodsTylko squashScalenie squash ma jednego rodzica, więc git revert z kroku 5 nie potrzebuje -m i nie zawiedzie na commicie scalającym
Block force pushesWłączoneNikt nie przepisze historii pod zaakceptowanym commitem
Lista wyjątków (bypass list)Pusta, nigdy z aplikacją wydań ani agentaAplikacja na liście wyjątków scala z pominięciem wszystkiego powyżej

Potem dodaj workflow. Działa na workflow_run, po zakończeniu evidence-bundle, więc uruchamia się z gałęzi domyślnej i nigdy nie pobiera kodu pull requesta. Dlatego może bezpiecznie trzymać token aplikacji wydań.

.github/workflows/merge-by-class.yml
name: merge-by-class
on:
workflow_run:
workflows: [evidence-bundle]
types: [completed]
permissions: {}
jobs:
route:
if: github.event.workflow_run.event == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/create-github-app-token@v3
id: app
with:
client-id: ${{ vars.RELEASE_APP_CLIENT_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
- name: Arm auto-merge for low risk only, pinned to the checked commit
env:
GH_TOKEN: ${{ steps.app.outputs.token }}
GH_REPO: ${{ github.repository }}
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
CONCLUSION: ${{ github.event.workflow_run.conclusion }}
run: |
pr=$(gh api "repos/$GH_REPO/commits/$HEAD_SHA/pulls" \
--jq '[.[] | select(.state == "open")] | first // {} | .number // empty')
[ -n "$pr" ] || { echo "No open pull request for $HEAD_SHA"; exit 0; }
classes=$(gh pr view "$pr" --json labels \
--jq '[.labels[].name | select(startswith("risk:"))] | join(",")')
if [ "$CONCLUSION" = "success" ] && [ "$classes" = "risk:low" ]; then
gh pr merge "$pr" --auto --squash --match-head-commit "$HEAD_SHA"
else
gh pr merge "$pr" --disable-auto || true
fi

Bezpieczeństwo opiera się na trzech szczegółach. --match-head-commit scala tylko ten commit, który został sklasyfikowany. Gałąź else wyłącza auto-merge, gdy późniejszy push podniesie klasę. A scalenie używa tokenu aplikacji GitHub, bo według dokumentacji GitHuba zdarzenia utworzone tokenem GITHUB_TOKEN repozytorium nie uruchamiają nowych workflowów, więc scalenie tym tokenem nigdy nie wystartowałoby deploy.yml. Utwórz aplikację wydań z prawem zapisu do Contents, Pull requests i Issues tylko w tym repozytorium, włącz Allow auto-merge w ustawieniach repozytorium i nie wpisuj aplikacji do CODEOWNERS.

Ruleset nadal wymaga jednej akceptacji, także dla low. Kto jej udziela, to jedyne miejsce, w którym trzy narzędzia się różnią.

Claude Code nie akceptuje pull requestów. Zarządzany Code Review publikuje uwagi, a jego check run „always completes with a neutral conclusion so it never blocks merging through branch protection rules” (dokumentacja Claude Code, sprawdzone 2026-09-26). Przekazuj te uwagi recenzentowi standard, a pull requesty low niech akceptuje człowiek na podstawie pakietu. Jeśli budujesz własną tożsamość akceptującą, sprawdź na testowej gałęzi, że ruleset liczy jej akceptację. Konfiguracja jest opisana w automatycznych przeglądach kodu.

Krok 4: wydawaj każdą klasę z jej budżetem zasięgu szkód

Dział zatytułowany „Krok 4: wydawaj każdą klasę z jej budżetem zasięgu szkód”

Workflow wdrożenia odczytuje klasę pull requesta, z którego pochodzi commit, a nie klasę zadeklarowaną przez agenta. Budżety (pierwsza ekspozycja, kroki promocji, czas obserwacji i wyzwalacz rollbacku dla każdej klasy) są zdefiniowane w progressive delivery dla zmian pisanych przez agentów; ten workflow tylko między nimi wybiera.

# .github/workflows/deploy.yml: plan and approval jobs. Build and push your image before "deploy".
name: deploy
on:
push:
branches: [main]
concurrency:
group: deploy-production
cancel-in-progress: false
permissions: {}
jobs:
plan:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
outputs:
risk: ${{ steps.plan.outputs.risk }}
steps:
- id: plan
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
# An open revert means production runs an older release on purpose.
if [ "$(gh pr list --label revert --state open --json number --jq length)" != "0" ]; then
echo "An open revert pull request freezes deploys. Merge or close it first."; exit 1
fi
# No merged pull request, or no class label, means high.
read -r pr risk < <(gh api "repos/$GH_REPO/commits/$GITHUB_SHA/pulls" \
--jq '[.[] | select(.merged_at != null)] | first // {}
| "\(.number // "none") \(([.labels[]?.name | select(startswith("risk:"))] | first) // "risk:high")"')
echo "Pull request $pr, class ${risk#risk:}"
echo "risk=${risk#risk:}" >> "$GITHUB_OUTPUT"
approve-high:
needs: plan
if: needs.plan.outputs.risk == 'high'
runs-on: ubuntu-latest
environment: production-approval # required reviewers, no secrets
steps:
- run: echo "Staged rollout of $GITHUB_SHA approved"
deploy:
needs: [plan, approve-high]
if: >-
always() && needs.plan.result == 'success' &&
(needs.approve-high.result == 'success' || needs.approve-high.result == 'skipped')
runs-on: ubuntu-latest
environment: production
permissions:
contents: read
steps:
- uses: actions/checkout@v7
with: { persist-credentials: false }
- name: Deploy with this class's budget
env:
RISK: ${{ needs.plan.outputs.risk }}
run: scripts/deploy.sh "$RISK" "$GITHUB_SHA"

scripts/deploy.sh piszesz sam: mapuje low na zwykłe wdrożenie, standard na canary z analizą, a high na wdrożenie etapowe. Trzymaj to mapowanie w jednym skrypcie, żeby zmiana budżetu była jednym recenzowanym diffem. Prompt poniżej szkicuje go razem ze skryptem rollbacku z kroku 5, w każdym z trzech narzędzi.

Środowisko production-approval trzyma dla high akceptację produkcji jako granicę systemu: do sześciu wymaganych recenzentów i zablokowaną samoakceptację, więc osoba, która scaliła zmianę, nie może jej też wydać. W planach Free, Pro i Team wymagani recenzenci środowisk są dostępni tylko w repozytoriach publicznych (dokumentacja GitHuba, sprawdzone 2026-09-26), więc sprawdź swój plan, zanim oprzesz na nich projekt.

Krok 5: wycofuj zmianę po naruszeniu SLO bez czekania na człowieka

Dział zatytułowany „Krok 5: wycofuj zmianę po naruszeniu SLO bez czekania na człowieka”

Kontroler canary łapie złe wydanie poniżej pełnej ekspozycji i już przywrócił ruch. Alert SLO łapie zmianę low, która od razu poszła na 100%, i nic jeszcze nie zostało cofnięte. Obie wysyłają do GitHuba to samo zdarzenie, a resztą zajmuje się jeden workflow.

Twój system alertów (Alertmanager i alerty Grafany potrafią wysłać webhook) wywołuje mały przekaźnik, który wysyła repository dispatch z usługą, SHA commita raportowanym przez usługę jako wersja, nazwą alertu i ekspozycją, canary albo full:

Okno terminala
# In the relay. GITHUB_DISPATCH_TOKEN comes from the relay's secret store.
curl -sS -X POST "https://api.github.com/repos/acme/shop/dispatches" \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GITHUB_DISPATCH_TOKEN" \
-d '{"event_type":"slo-breach","client_payload":{"service":"orders-api","sha":"'"$VERSION_SHA"'","alert":"OrdersErrorBudgetBurn","stage":"canary"}}'

Wystaw ten token z aplikacji GitHub zainstalowanej tylko w tym repozytorium, z minimalnym uprawnieniem, które referencja REST GitHuba podaje dla tworzenia zdarzenia repository dispatch. Workflow repository_dispatch zawsze działa z gałęzi domyślnej, więc ładunek może wybierać wartości, ale nigdy kod.

.github/workflows/slo-rollback.yml
name: slo-rollback
on:
repository_dispatch:
types: [slo-breach]
concurrency:
group: slo-rollback-${{ github.event.client_payload.service }}
cancel-in-progress: false
permissions: {}
jobs:
restore:
# A canary abort has already restored traffic; a full deploy has not.
if: github.event.client_payload.stage == 'full'
runs-on: ubuntu-latest
environment: production-rollback # branch rule only, no reviewers: a rollback must not wait
permissions:
contents: read
steps:
- uses: actions/checkout@v7
with: { persist-credentials: false }
- name: Return the service to its previous release, with no build
env:
SERVICE: ${{ github.event.client_payload.service }}
run: |
[[ "$SERVICE" =~ ^[a-z0-9-]{1,40}$ ]] || { echo "Invalid service"; exit 1; }
scripts/rollback.sh "$SERVICE"
record:
needs: restore
if: always() # open the incident and the revert even if the restore failed
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/create-github-app-token@v3
id: app
with:
client-id: ${{ vars.RELEASE_APP_CLIENT_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
- uses: actions/checkout@v7
with:
fetch-depth: 0
token: ${{ steps.app.outputs.token }}
- name: Open the incident issue and the revert pull request
env:
GH_TOKEN: ${{ steps.app.outputs.token }}
SHA: ${{ github.event.client_payload.sha }}
SERVICE: ${{ github.event.client_payload.service }}
ALERT: ${{ github.event.client_payload.alert }}
STAGE: ${{ github.event.client_payload.stage }}
RESTORE: ${{ needs.restore.result }}
HUMAN_OWNER: ${{ vars.ONCALL_OWNER }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: scripts/open-revert.sh

scripts/rollback.sh opakowuje mechanizm cofania twojej platformy, na przykład kubectl rollout undo deployment/"$SERVICE" dla zwykłego Deploymentu w Kubernetesie. Nie może niczego budować: rollback czekający na CI to drugi incydent. Środowisko production-rollback ma te same poświadczenia wdrożeniowe co production, z regułą gałęzi i bez wymaganych recenzentów.

Skrypt najpierw otwiera zgłoszenie incydentu, żeby pakiet revertu mógł do niego linkować, potem commituje revert i otwiera pull request z pakietem wygenerowanym z diffa:

#!/usr/bin/env bash
# scripts/open-revert.sh: run by slo-rollback.yml on the default branch with the release
# app's token in GH_TOKEN. Opens the incident issue and a revert pull request for the
# merge that breached an SLO. It never builds or runs the reverted code.
set -euo pipefail
[[ "$SHA" =~ ^[0-9a-f]{7,40}$ ]] || { echo "Invalid sha: $SHA"; exit 1; }
[[ "$SERVICE" =~ ^[a-z0-9-]{1,40}$ ]] || { echo "Invalid service: $SERVICE"; exit 1; }
SHA=$(git rev-parse --verify "$SHA^{commit}")
# Provenance: the pull request that merged the commit, and whether an agent wrote it.
pr_json=$(gh api "repos/$GITHUB_REPOSITORY/commits/$SHA/pulls" \
--jq '[.[] | select(.merged_at != null)] | first // {}')
pr=$(jq -r '.number // empty' <<<"$pr_json")
labels=$(jq -r '[.labels[]?.name] | join(",")' <<<"$pr_json")
# Same files, same computed floor; the reverted class is at least that floor, so declare it.
class=$(jq -r '[.labels[]?.name | select(startswith("risk:"))] | first // "risk:high"' <<<"$pr_json")
class=${class#risk:}
kind=incident
[[ ",$labels," == *",agent-pr,"* ]] && kind=incident:agent
cat > /tmp/incident.md <<MD
**Alert:** $ALERT on \`$SERVICE\` · **Stage:** $STAGE · **Restore job:** $RESTORE
**Pull request:** ${pr:+#$pr} (\`${SHA:0:12}\`) · **Labels:** ${labels:-none} · **Run:** $RUN_URL
Close this issue only when each box is ticked with a link:
- [ ] Eval or test in \`evals/incidents/\` that fails on \`${SHA:0:12}\` and passes on the revert
- [ ] Policy change, if the risk class or the guardrail let this through
- [ ] Loop level decision, if an agent loop produced the change
MD
issue_url=$(gh issue create --title "SLO breach on $SERVICE after ${pr:+#$pr }${SHA:0:12}" \
--label "$kind" --body-file /tmp/incident.md)
# The revert, and one check a reviewer can trust: every file the commit touched is back to its parent.
branch="revert/${SHA:0:12}"
git switch -c "$branch"
git -c user.name="release-bot" -c user.email="release-bot@users.noreply.github.com" \
revert --no-edit "$SHA"
mapfile -t files < <(git diff --name-only "$SHA^" "$SHA")
if git diff --quiet "$SHA^" HEAD -- "${files[@]}"; then restored=pass; code=0; else restored=fail; code=1; fi
{
echo "Reverts ${pr:+#$pr }(\`${SHA:0:12}\`) after an SLO breach. Incident: $issue_url"
echo
echo '~~~yaml'
echo 'evidence_bundle: 1'
echo 'spec:'
echo " link: $issue_url"
echo " delta: [\"$SERVICE behaves as it did before ${SHA:0:12}\"]"
echo ' unrequested: []'
echo 'acceptance:'
echo " - criterion: every file ${SHA:0:12} changed matches its parent commit"
echo " check: git diff --quiet ${SHA:0:12}^ HEAD -- <the ${#files[@]} files>"
echo " result: $restored"
echo 'checks:'
echo " - command: git diff --quiet ${SHA:0:12}^ HEAD -- <the ${#files[@]} files>"
echo " exit_code: $code"
echo 'risk:'
echo " class: $class # the reverted pull request's class; CI fails anything lower"
echo ' touches: [auth, money, schema, migrations, infra] # over-declared on purpose'
echo ' oracle_changes:'
for f in "${files[@]}"; do
printf ' - { path: "%s", direction: neutral, reason: "revert of %s" }\n' "$f" "${SHA:0:12}"
done
echo " rollback: revert this revert once the incident eval passes"
echo 'provenance:'
echo ' agent: slo-rollback workflow (no agent)'
echo ' model: none'
echo " session: $RUN_URL"
echo " task: $issue_url"
echo " human_owner: \"$HUMAN_OWNER\""
echo '~~~'
} > /tmp/revert.md
git push -u origin "$branch"
gh pr create --head "$branch" --title "Revert ${pr:+#$pr }(${SHA:0:12}): SLO breach on $SERVICE" \
--body-file /tmp/revert.md --label revert

Dwie decyzje w wygenerowanym pakiecie wymagają wyjaśnienia. Pakiet deklaruje wszystkie wrażliwe klasy i wpisuje każdy cofany plik jako zmianę wyroczni typu neutral, bo checker oblewa pakiet, który pomija dotkniętą klasę albo plik wyroczni, a minimum nadal wynika z diffa: cofany plik testów podnosi je co najmniej do standard. Deklaruje też klasę pull requesta, który cofa, czyli co najmniej minimum, jakie checker wyliczy dla tych samych plików, więc revert zmiany w dokumentacji zostaje low i może scalić się automatycznie, a revert zmiany w rozliczeniach ma high i czeka na właściciela kodu. Jeśli cofany pull request nie ma etykiety risk:*, skrypt deklaruje high. To oczekiwanie jest bezpieczne, bo ruch wrócił już do poprzedniego wydania, a deploy.yml nie wyda main, dopóki revert jest otwarty.

Krok 6: zamień incydent w ewaluację tym samym potokiem

Dział zatytułowany „Krok 6: zamień incydent w ewaluację tym samym potokiem”

Zgłoszenie incydentu zamyka się dopiero wtedy, gdy wniosek istnieje jako sprawdzenie. Ewaluacja jest zadaniem dla agenta i przechodzi przez etapy od 1 do 4 jak każda inna zmiana; różni się dowód: musi oblać zły commit i przejść na revercie. Postmortem, izolowanie incydentu i degradowanie pętli opisuje strona gdy incydent wywoła agent.

Dodaj /evals/ @acme/tech-leads do CODEOWNERS, żeby każdą ewaluację zatwierdzał człowiek. Potem zamień incydent w zadanie:

Człowiek przegląda zgłoszenie, nadaje agent:ready, a potok buduje ewaluację. Zanim zaakceptuje ten pull request, właściciel kodu uruchamia dowód, bo pakiet może go tylko deklarować:

Okno terminala
# Terminal, repository root. BAD_SHA is the reverted commit from the incident issue.
git worktree add /tmp/inc-431 "$BAD_SHA"
mkdir -p /tmp/inc-431/evals/incidents
cp evals/incidents/INC-431.test.ts /tmp/inc-431/evals/incidents/
(cd /tmp/inc-431 && npm ci && npm test -- evals/incidents/INC-431.test.ts) # must fail
npm test -- evals/incidents/INC-431.test.ts # must pass
git worktree remove --force /tmp/inc-431

Dopiero wtedy zmiana wraca: cofnij revert, popraw defekt tak, żeby ewaluacja przechodziła, i wyślij całość ponownie przez potok. Ewaluacja zostaje w zestawie i uruchamia się przy każdej zmianie środowiska agenta, jak opisują ciągłe ewaluacje.

Narzędzia różnią się tylko wyzwalaczem. Do lokalnych uruchomień zapisz prompt jako .github/agent/incident-to-task.md z wpisanym numerem incydentu.

Nadaj zgłoszeniu ewaluacji etykietę agent:ready, a agent-issue.yml uruchomi je przez anthropics/claude-code-action@v1 jak każde inne zadanie. Żeby przygotować zadanie lokalnie, uruchom prompt bez interfejsu; claude -p startuje w trybie uprawnień Manual, więc narzędzia spoza listy są odrzucane:

Okno terminala
# Terminal, repository root (Claude Code 2.1.283)
claude -p "$(cat .github/agent/incident-to-task.md)" \
--allowedTools "Read,Grep,Glob,Bash(gh issue view *),Bash(gh pr view *)" \
--max-budget-usd 2

Jak udowodnić, że cały potok działa, zanim cokolwiek wdroży?

Dział zatytułowany „Jak udowodnić, że cały potok działa, zanim cokolwiek wdroży?”

Przeprowadź te pięć ćwiczeń na usłudze stagingowej z tym samym rulesetem i workflowami. Każde sprawdza jedno ogniwo łańcucha.

ĆwiczenieCo robiszZaliczone, gdy
Ścieżka lowAgent poprawia literówkę w docs/setup.mdEtykieta risk:low, akceptacja, automatyczne scalenie, deploy.yml robi zwykłe wdrożenie
Klasy nie da się obniżyćAgent zmienia jedną linię w src/billing/refund.ts i deklaruje class: lowCI nadaje risk:high, auto-merge pozostaje wyłączony, prośba o review trafia do właściciela kodu, approve-high czeka
Przerwany canaryWydaj build standard, który zwraca HTTP 500 dla 5% żądań GET /ordersAnaliza przerywa wdrożenie, przychodzi dispatch ze stage: canary, otwierają się zgłoszenie incydentu i pull request z revertem, restore zostaje pominięty
Pełny rollbackWydaj zmianę low i ręcznie odpal alert SLOrestore uruchamia rollback.sh bez builda, otwiera się revert, a kolejny push do main oblewa job planujący z komunikatem o wstrzymaniu
Dowód ewaluacjiUruchom zadanie od incydentu do ewaluacji dla ćwiczenia z canaryEwaluacja oblewa zły commit i przechodzi na main, a właściciel kodu ją zatwierdza

W ćwiczeniach z canary i pełnym rollbackiem zmierz czas od złego wdrożenia do zerowej ekspozycji: to ta liczba, nie tabela budżetów, mówi, jak długo defekt dociera do klientów.

Po jakich liczbach poznasz, że potok od intencji do produkcji działa?

Dział zatytułowany „Po jakich liczbach poznasz, że potok od intencji do produkcji działa?”

Te miary pochodzą z etykiet, wdrożeń i zgłoszeń incydentów, które już masz. Tech lead przegląda je co miesiąc; CTO śledzi trend obok miar z DORA, SPACE, DX Core 4 i pomiaru AI.

MiaraDefinicjaCo z nią zrobić
Czas od intencji do produkcjiOd agent:ready do 100% ekspozycji, mediana, dla każdej klasyPorównaj ze zmianami ludzi w tej samej klasie; różnica pokazuje kolejkę
Odsetek nieudanych zmian według klasyScalone zmiany, które spowodowały rollback, revert albo incydent, podzielone przez scalone zmiany, dla każdej klasylow wyżej niż standard znaczy, że reguły low są za szerokie
Złapane przed 100%Rollbacki na etapie canary albo wdrożenia etapowego, podzielone przez wszystkie rollbackiNiska wartość oznacza, że defekty pierwsi widzą klienci; dodaj progi
Czas do zerowej ekspozycjiOd pierwszego przekroczenia progu albo alertu do 0% ruchu na zmianie, medianaGodziny znaczą, że rollback czeka na build albo człowieka
Domykanie incydentów ewaluacjąZgłoszenia incydentów zamknięte udowodnioną ewaluacją albo egzekwowaną zmianą polityki, podzielone przez wszystkie zgłoszenia incydentówPoniżej 100% znaczy, że wnioski wciąż żyją w wątkach na czacie

Raport DORA 2025 (Google Cloud, 23 września 2025) stwierdził „a positive relationship between AI adoption on both software delivery throughput and product performance”, a zarazem, że „AI adoption does continue to have a negative relationship with software delivery stability”. AI Engineering Report 2026 firmy Faros AI (kwiecień 2026, telemetria dostawcy z 22 000 deweloperów) zmierzył, że przepustowość zadań na dewelopera wzrosła o 33,7%, a liczba incydentów na pull request o 242,7%. Te miary pokazują, po której stronie tych wyników jest twój potok.

Zatwierdzanie zostaje przy wskazanych osobach. Właściciel zgłoszenia zatwierdza intencję; tech lead odpowiada za plik polityki, limity i tabelę ryzyka; właściciel kodu czyta każdą zmianę high i każdą ewaluację. CTO decyduje, które klasy trafiają na produkcję bez czytania kodu przez człowieka, i zmienia to po jednej klasie naraz, na podstawie tych liczb.

Każdy pull request agenta oblewa evidence z komunikatem „No evidence bundle found”. Opis powstał bez pakietu. Naprawa: zastosuj krok 2 i popraw opisy otwartych pull requestów; wyzwalacz edited uruchomi check ponownie.

Auto-merge działa, ale wdrożenie nie startuje. Scalenie poszło przez GITHUB_TOKEN, więc zdarzenie push nie uruchomiło żadnego workflowu. Naprawa: włączaj auto-merge tokenem aplikacji wydań, jak robi to merge-by-class.yml.

Pull request low nigdy się nie scala. Nikt go nie zaakceptował albo reguła dodaje akceptację. Rulesety GitHuba domyślnie wymagają jednej dodatkowej akceptacji dla pull requestów Copilot cloud agent, które nie są przypisane do osoby (public preview, dokumentacja GitHuba sprawdzona 2026-09-26), więc te potrzebują dwóch. Naprawa: ustal, kto akceptuje low (zakładki kroku 3), i sprawdzaj ruleset, gdy nowy agent zaczyna otwierać pull requesty.

Merge queue zatrzymuje każdy pull request. Wymagane checki nigdy nie uruchomiły się na merge_group, zdarzeniu, którym według dokumentacji merge queue GitHuba trzeba wyzwalać workflowy Actions dla pull requestów w kolejce. Checker pakietu czyta opis pull requesta, którego to zdarzenie nie niesie. Naprawa: dodaj wyzwalacz merge_group do każdego wymaganego workflowu, z jobem pakietu, który bierze numer pull requesta z nazwy gałęzi kolejki (gh-readonly-queue/<base>/pr-<number>-<sha>), odczytuje head SHA tego pull requesta i przechodzi tylko wtedy, gdy gh api repos/$GH_REPO/commits/$SHA/check-runs zwraca dla niego udany run evidence. Albo wyłącz kolejkę.

Rollback czekał na zatwierdzającego. Job przywracający użył środowiska production, którego recenzenci obowiązują też przy rollbacku. Naprawa: daj rollbackowi własne środowisko production-rollback z regułą gałęzi i bez recenzentów, i przećwicz je ćwiczeniem pełnego rollbacku.

Pull request z revertem oblewa własny check pakietu. Cofana zmiana dotykała ścieżek UI wymagających dowodów z działania, których skrypt nie wytworzy, albo późniejsze commity zmieniły te same pliki. Naprawa: produkcja jest już przywrócona, więc człowiek kończy revert ręcznie i zapisuje powód w zgłoszeniu incydentu.

Dispatch przychodzi ze złym SHA. Usługa raportuje numer builda zamiast commita albo przekaźnik czyta stabilne pody zamiast canary. Naprawa: eksportuj SHA commita jako etykietę wersji na każdym podzie i czytaj ją z serii canary; skrypt odrzuca wszystko, co nie jest commitem w repozytorium.

Incydenty zamykają się bez ewaluacji. Dyżurny odhacza pola, żeby wyczyścić kolejkę. Naprawa: linki sprawdza ktoś inny niż właściciel incydentu, a miara domykania trafia na comiesięczny przegląd.