Definir o que falta demonstrar
Uma revisão de prontidão operacional liga as condições de entrada em serviço a observações concretas. Num serviço fictício de processamento de fundos, a lista inclui acesso APS, encaminhamento de um alerta e execução de uma tarefa de recuperação. Dois resultados positivos e uma tarefa não executada significam evidência incompleta. Não demonstram falha da recuperação nem prontidão por maioria. Regista critério, executor, identidade da versão e configuração, ambiente, momento, resultado observado e limitação. O responsável pela decisão precisa de perceber exatamente a lacuna, a sua consequência e quem pode resolvê-la. A orientação AWS sobre revisões de prontidão descreve uma prática adaptável ao contexto, não um formulário obrigatório do banco. O exercício também não permite transformar uma exceção aceite em prova de que a tarefa passou. Se houver decisão explícita de avançar com uma condição por cumprir, preserva separadamente essa condição e os limites da decisão.
Ensaiar com quem vai operar
Pede ao operador APS que execute a tarefa com a sua própria conta, o procedimento aplicável e os contactos disponíveis durante a janela. Observar o autor a executar com privilégios superiores não demonstra autonomia do operador. Um runbook deve identificar pré-condições, passos, resultado esperado, tratamento de erros e escalonamento. Quando o passo quatro diverge entre o documento e o estado do serviço, interrompe a passagem desse procedimento e esclarece a combinação correta antes de o considerar utilizável. Um ensaio R8/C3/E2 não cobre automaticamente R8/C4/E2: neste caso, C4 muda o destino do alerta, pelo que é necessário verificar encaminhamento e receção no contexto de destino. A evidência anterior continua útil para o que realmente exercitou. Não precisa de ser apagada, nem pode ser alargada a propriedades não observadas. Confirma também que as pessoas necessárias à recuperação continuam disponíveis depois da instalação; a sua presença apenas no início não cobre a decisão tardia.
Calcular a janela com as restrições reais
O plano didático prevê drenagem de oito minutos, aplicação de doze e middleware de dezoito em paralelo, smoke test de dez e validação de quinze. Como a junção espera ambos os ramos, a duração mínima é 8 + max(12,18) + 10 + 15 = 51 minutos. Se ambos os ramos exigirem o mesmo executor e este só puder realizar uma tarefa de cada vez, passam a ser sequenciais: 63 minutos. Para este exercício, o acordo exige ainda preservar 25 minutos de recuperação após a execução normal. São então necessários 88 minutos, mais 33 que a janela de 55. Conseguir um segundo executor habilitado reduz a necessidade para 76 minutos, continuando a faltar 21. Esta reserva adicional é uma restrição explícita do caso; não é uma regra universal nem um modelo de todos os caminhos de recuperação. Antes de confirmar, revê âmbito, sequência, recursos ou janela com os responsáveis. Abrir duas sessões não remove a restrição humana declarada.
Distinguir observação, exceção e decisão
O painel apresenta zero erros, mas a última amostra tem seis minutos e o critério fictício exige idade máxima de dois. O sinal está desatualizado: não prova saúde atual nem, isoladamente, indisponibilidade. Da mesma forma, uma autorização urgente para o incidente X até às 18:00 não cobre execução às 19:00 com uma funcionalidade Y acrescentada. Expõe lacunas de tempo e âmbito à autoridade definida. Se ocorrer um incidente, alinha a recuperação com a coordenação desse incidente; dois planos concorrentes podem alterar o estado um do outro. Guarda o código completo abaixo como release-readiness.py e executa python3 release-readiness.py. As catorze verificações exercitam apenas aritmética, dependências declaradas e lacunas em observações fornecidas. Altera uma duração ou observação e antecipa a asserção que falhará antes de executar. O programa não mede um serviço, não verifica permissões reais e não autoriza produção. Entrega uma nota com lacuna, evidência necessária, responsável e momento de decisão.
"""Original DR teaching fixtures. No network, deployment or production approval."""
import hashlib
import json
import platform
from pathlib import Path
def finish_times(tasks):
"""tasks: name -> (nonnegative minutes, already-declared predecessors)."""
finished = {}
for name, (minutes, predecessors) in tasks.items():
if minutes < 0 or any(p not in finished for p in predecessors):
raise ValueError("Durations must be nonnegative and predecessors declared first")
finished[name] = max((finished[p] for p in predecessors), default=0) + minutes
return finished
def requirement_gaps(required, observations):
# A missing result is different from an observed failure.
return {name: observations.get(name, "not-run") for name in required
if observations.get(name) != "pass"}
def changed_context(observed, target):
return sorted(k for k in observed.keys() | target.keys()
if k not in observed or k not in target or observed[k] != target[k])
def sample_state(now_minute, sampled_minute, maximum_age):
age = now_minute - sampled_minute
if age < 0:
return "clock-inconsistent"
return "current" if age <= maximum_age else "stale"
def exception_gaps(now_minute, expires_minute, requested, covered):
# This exercise makes expiry exclusive. Real policy must define its own boundary.
return {"expired": now_minute >= expires_minute,
"uncovered": sorted(set(requested) - set(covered))}
def run():
checks = []
def check(name, actual, expected):
if actual != expected:
raise AssertionError((name, actual, expected))
checks.append({"name": name, "actual": actual, "passed": True})
parallel = {
"drain": (8, []), "app": (12, ["drain"]),
"middleware": (18, ["drain"]),
"smoke": (10, ["app", "middleware"]), "validate": (15, ["smoke"]),
}
serial = {**parallel, "middleware": (18, ["app"])}
p, s = finish_times(parallel), finish_times(serial)
check("parallel_dependency_timing", p,
{"drain": 8, "app": 20, "middleware": 26, "smoke": 36, "validate": 51})
check("exclusive_executor_timing", s,
{"drain": 8, "app": 20, "middleware": 38, "smoke": 48, "validate": 63})
window, reserve = 55, 25
check("window_plus_explicit_recovery_reserve",
{"parallelRequired": p["validate"] + reserve,
"serialRequired": s["validate"] + reserve,
"parallelGap": p["validate"] + reserve - window,
"serialGap": s["validate"] + reserve - window},
{"parallelRequired": 76, "serialRequired": 88, "parallelGap": 21, "serialGap": 33})
required = ["aps-access", "alert-route", "recovery-task"]
observations = {"aps-access": "pass", "alert-route": "pass"}
check("missing_is_not_observed_failure", requirement_gaps(required, observations),
{"recovery-task": "not-run"})
check("failed_is_retained", requirement_gaps(required, {**observations, "recovery-task": "fail"}),
{"recovery-task": "fail"})
check("all_observed_is_only_model_coverage", requirement_gaps(required, {**observations, "recovery-task": "pass"}), {})
observed = {"build": "R8", "config": "C3", "environment": "E2"}
check("same_build_different_config", changed_context(observed, {**observed, "config": "C4"}), ["config"])
check("absent_target_context_is_not_equivalence", changed_context(observed, {"build": "R8", "config": "C3"}), ["environment"])
check("stale_zero_error_sample", sample_state(19 * 60, 19 * 60 - 6, 2), "stale")
check("freshness_boundaries", [sample_state(100, 98, 2), sample_state(100, 101, 2)], ["current", "clock-inconsistent"])
check("exception_time_and_scope_are_separate", exception_gaps(19 * 60, 18 * 60, ["incident-X", "feature-Y"], ["incident-X"]),
{"expired": True, "uncovered": ["feature-Y"]})
check("exception_expiry_boundary", [exception_gaps(t, 1080, ["incident-X"], ["incident-X"])["expired"] for t in [1079, 1080]], [False, True])
milestones = {"apiAvailable": 20 * 60 + 10, "backlogComplete": 20 * 60 + 45, "reconciled": 21 * 60}
check("distinct_recovery_milestones",
[milestones["backlogComplete"] - milestones["apiAvailable"], milestones["reconciled"] - milestones["backlogComplete"], milestones["reconciled"] - milestones["apiAvailable"]], [35, 15, 50])
residual = {"status": "accepted-open", "owner": "service-owner", "reviewDay": 10}
check("accepted_is_not_closed_and_review_can_be_overdue", {"status": residual["status"], "daysOverdue": max(0, 12 - residual["reviewDay"])},
{"status": "accepted-open", "daysOverdue": 2})
return {"scope": "Synthetic arithmetic and evidence-gap fixtures only. No production gate, deployment, service health measurement or authorization decision. Durations and thresholds are fictional; the input observations are assumed, not collected.",
"python": platform.python_version(), "scriptSha256": hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
"groups": len(checks), "checks": checks}
if __name__ == "__main__":
print(json.dumps(run(), indent=2, ensure_ascii=False))Janela de 55 minutos, executor único e reserva adicional de 25: execução de 63, necessidade total de 88, lacuna de 33 minutos. Uma segunda pessoa reduz a lacuna para 21, sem eliminar a necessidade de decisão.
Armadilhas comuns
Confundir não executado com falhado; aceitar zero erros com amostras antigas; considerar dois ramos paralelos apesar de executor exclusivo; prolongar uma exceção sem nova decisão.
Tópicos relacionados: Capacidade e dependências · Prontidão operacional · Recuperação e escalonamento
Confirma a aplicabilidade da evidência e a viabilidade da sequência antes de pedir a decisão sobre a janela.
Referência: OPS07-BP02 Ensure a consistent review of operational readiness · Google SRE release and canary guidance; GitHub immutable releases and GitLab release evidence and deployment safety; DORA five-metric model; inspected 2026-10-01