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.
Co daje ci potok od intencji do produkcji
Dział zatytułowany „Co daje ci potok od intencji do produkcji”- 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ówlow, 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 przyhighczeka na wskazanego zatwierdzającego. slo-rollback.ymliopen-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.
Jak wygląda potok od intencji do produkcji?
Dział zatytułowany „Jak wygląda potok od intencji do produkcji?”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.
| Etap | Co się dzieje | Plik w repozytorium | Kto decyduje | Rola człowieka |
|---|---|---|---|---|
| 1. Intencja | Potrzeba produktowa staje się zgłoszeniem gotowym dla agenta: kryteria akceptacji, dozwolone ścieżki i metryka progu | .github/ISSUE_TEMPLATE/agent-task.yml | Właściciel zgłoszenia | Pisze albo zatwierdza intencję, nadaje agent:ready |
| 2. Budowa | Agent implementuje zmianę w izolowanym przebiegu, otwiera się szkic pull requesta | agent-issue.yml, agent-loop.yml | Workflow | Brak, dopóki pętla mieści się w limitach |
| 3. Klasyfikacja | CI wylicza klasę ryzyka z diffa i nadaje etykietę | evidence-bundle.yml, check-evidence.mjs | CI, według polityki z gałęzi bazowej | Jest właścicielem pliku polityki |
| 4. Scalenie | low scala się automatycznie po akceptacji; standard czeka na recenzenta pakietu; high czeka na właściciela kodu | merge-by-class.yml, ruleset, CODEOWNERS | Ruleset | Akceptuje na podstawie pakietu; właściciele kodu czytają kod high |
| 5. Wydanie | Klasa wybiera ekspozycję: pełne wdrożenie, canary albo wdrożenie etapowe | deploy.yml, scripts/deploy.sh | Plan wdrożenia | Wskazany zatwierdzający promuje high |
| 6. Obserwacja | Progi i alerty SLO oceniają zmianę na prawdziwym ruchu | Analiza wdrożenia, reguły alertów | Kontroler albo alert | Brak |
| 7. Rollback | Ruch wraca do poprzedniego wydania; otwierają się pull request z revertem i zgłoszenie incydentu | slo-rollback.yml, scripts/open-revert.sh | Alert, przez zdarzenie dispatch | Dyżurny czyta zgłoszenie incydentu |
| 8. Nauka | Incydent staje się ewaluacją i, jeśli trzeba, zmianą polityki | evals/incidents/, evidence-policy.yml | Potok, potem właściciel kodu | Zatwierdza ewaluację i zmianę polityki |
Zmiana z przykładu przechodzi przez te etapy w ciągu jednego przedpołudnia. Godziny są ilustracją, nie pomiarem.
| Godzina | Zdarzenie |
|---|---|
| 09:02 | Zgłoszenie #418, „Paginate GET /orders with a cursor”, dostaje etykietę agent:ready; o 09:31 otwiera się pull request #423 z pakietem. |
| 10:07 | CI 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:24 | Odsetek 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:40 | Zadanie 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.
-
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 intorollout.guardrails. Declare the risk class from the issue or higher.Dodaj
.agent/bundle.ymldo ścieżek artefaktu w jobieagent, a potem dołącz go w jobieopen-pr, po raporcie agenta i przedgh pr create:Okno terminala if [ -s /tmp/agent/.agent/bundle.yml ]; then{ echo; echo '## Evidence bundle'; echo; echo '~~~yaml'cat /tmp/agent/.agent/bundle.ymlecho '~~~'; } >> body.mdfiTo samo zrób w
push-fixprzezgh pr edit "$PR" --body-file, żeby poprawka zmieniająca testy aktualizowała też pakiet. Wyzwalaczeditedwevidence-bundle.ymluruchomi wtedy check ponownie. -
Nadaj każdemu checkowi własną nazwę. Job
gateswagent-loop.ymlraportuje się jakoevidence, podobnie jak jobevidencewevidence-bundle.yml. Zmień nazwę pierwszego naname: agent-gates, zostaw jego identyfikatorgates, żeby odwołanianeeds: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. -
Utwórz nowe etykiety.
revert,incidentiincident:agent, obok etykietrisk:*, których używa już pakiet dowodów.ghnie 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ła | Ustawienie | Jaką lukę zamyka |
|---|---|---|
| Require a pull request before merging | 1 akceptacja, odrzucanie nieaktualnych akceptacji, wymagane review właścicieli kodu | GitHub 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 pass | evidence, agent-gates i twój job z testami; każdy przypięty do aplikacji GitHub Actions jako oczekiwanego źródła | Status może ustawić każda integracja z prawem zapisu; przypięcie źródła sprawia, że check zazielenią tylko twoje workflowy |
| Allowed merge methods | Tylko squash | Scalenie squash ma jednego rodzica, więc git revert z kroku 5 nie potrzebuje -m i nie zawiedzie na commicie scalającym |
| Block force pushes | Włączone | Nikt nie przepisze historii pod zaakceptowanym commitem |
| Lista wyjątków (bypass list) | Pusta, nigdy z aplikacją wydań ani agenta | Aplikacja 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ń.
name: merge-by-classon: 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 fiBezpieczeń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.
Codex recenzuje pull request po komentarzu @codex review, a własne reguły review żyją w AGENTS.md (sprawdzone 2026-08-28). Dokumentacja OpenAI przy ostatnim sprawdzeniu (2026-08-28) nie opisywała funkcji akceptowania pull requestów, więc low akceptuje człowiek na podstawie pakietu, jak w zakładce Claude Code. Wpisz klasy ryzyka do reguł review, żeby każde review Codeksa nazywało klasę, którą widzi.
PR Routing & Approval „assigns reviewers based on code ownership and commit history, and can approve low-risk PRs when your criteria are met” (cursor.com, sprawdzone 2026-08-28). Kryteria mają wskazywać etykietę risk:low, nigdy klasę zadeklarowaną w pakiecie, a tożsamość Cursora nie może być w CODEOWNERS ani na liście wyjątków. Treść kryteriów, dwutygodniowy tryb cienia i zapytanie audytowe znajdziesz w Rollouts oraz PR Routing & Approval w Cursorze. Nowszych funkcji Cursora działających po scaleniu nie udało się zweryfikować 2026-09-26, więc ten potok od nich nie zależy.
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: deployon: 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:
# 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.
name: slo-rollbackon: 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.shscripts/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 changeMDissue_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 revertDwie 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ć:
# 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/incidentscp 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 failnpm test -- evals/incidents/INC-431.test.ts # must passgit worktree remove --force /tmp/inc-431Dopiero 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:
# 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 2Nadaj zgłoszeniu ewaluacji etykietę agent:ready, a wersja agent-issue.yml dla Codeksa uruchomi je przez openai/codex-action@v1 z profilem uprawnień :workspace. Żeby przygotować zadanie lokalnie, użyj profilu tylko do odczytu, który nie może zapisywać w repozytorium roboczym:
# Terminal, repository root (Codex CLI 0.157.1)codex exec -c default_permissions=":read-only" \ -o incident-task.md "$(cat .github/agent/incident-to-task.md)"Tekst incydentu i pull requesta pobierz przez gh przed uruchomieniem i wklej do promptu, bo przebieg tylko do odczytu nie może go pobrać.
Nadaj zgłoszeniu ewaluacji etykietę agent:ready, a wersja agent-issue.yml dla Cursora uruchomi Cloud Agenta przez @cursor/sdk. Cursor Automations potrafią też uruchomić Cloud Agenta z wyzwalacza Sentry albo PagerDuty (sprawdzone 2026-08-28), więc zadanie ewaluacji może powstać w chwili, gdy odpali alert; jego wynikiem niech będzie zgłoszenie do oznaczenia przez człowieka, nie pull request. Zobacz Cloud Agents i Automations w Cursorze.
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.
| Ćwiczenie | Co robisz | Zaliczone, gdy |
|---|---|---|
Ścieżka low | Agent poprawia literówkę w docs/setup.md | Etykieta 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: low | CI nadaje risk:high, auto-merge pozostaje wyłączony, prośba o review trafia do właściciela kodu, approve-high czeka |
| Przerwany canary | Wydaj build standard, który zwraca HTTP 500 dla 5% żądań GET /orders | Analiza przerywa wdrożenie, przychodzi dispatch ze stage: canary, otwierają się zgłoszenie incydentu i pull request z revertem, restore zostaje pominięty |
| Pełny rollback | Wydaj zmianę low i ręcznie odpal alert SLO | restore uruchamia rollback.sh bez builda, otwiera się revert, a kolejny push do main oblewa job planujący z komunikatem o wstrzymaniu |
| Dowód ewaluacji | Uruchom zadanie od incydentu do ewaluacji dla ćwiczenia z canary | Ewaluacja 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.
| Miara | Definicja | Co z nią zrobić |
|---|---|---|
| Czas od intencji do produkcji | Od agent:ready do 100% ekspozycji, mediana, dla każdej klasy | Porównaj ze zmianami ludzi w tej samej klasie; różnica pokazuje kolejkę |
| Odsetek nieudanych zmian według klasy | Scalone zmiany, które spowodowały rollback, revert albo incydent, podzielone przez scalone zmiany, dla każdej klasy | low 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 rollbacki | Niska wartość oznacza, że defekty pierwsi widzą klienci; dodaj progi |
| Czas do zerowej ekspozycji | Od pierwszego przekroczenia progu albo alertu do 0% ruchu na zmianie, mediana | Godziny 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ów | Poniż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.
Co psuje się między scaleniem a produkcją?
Dział zatytułowany „Co psuje się między scaleniem a produkcją?”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.