Case 583
The backup ran nightly and the change was hourly
the_backup_ran_nightly_and_the_change_was_hourly.eml - The backup has succeeded every night for four hundred nights. The recovery point objective is one hour. What is actually recoverable is computed below.
ok: true — round-trip fixpoint reached (python1 == python2)updated 2026-08-28
EML
eml# Self-authored for the EML case corpus (no external origin). The backup has
# succeeded every night for four hundred nights. The recovery point objective
# is one hour. What is actually recoverable is computed below.
#
# A nightly full backup at 02:00 is the right shape for this system and was
# chosen carefully. It runs in the quiet window, it does not contend with the
# day's traffic, it produces a single self-contained artifact that can be
# restored without replaying anything, and its integrity is verified by a
# checksum every morning. Four hundred consecutive successes is a real number
# and it was earned.
#
# The RPO is a separate promise, written in a different document, by people who
# were describing what the business could tolerate rather than what the backup
# schedule provides. Nobody compared the two numbers, because they live in two
# documents and neither one mentions the other.
#
# A backup's success rate measures whether the artifact was produced. The
# recovery point measures how old that artifact is when it is needed, and the
# schedule alone decides that.
2 => backup_hour
4200 => changes_per_hour
1 => rpo_promised_hours
400 => consecutive_successes
"backup runs at : " + str(backup_hour) + ":00, nightly, full" ^0
"consecutive successes : " + str(consecutive_successes) ^0
"changes per hour : " + str(changes_per_hour) ^0
"RPO promised : " + str(rpo_promised_hours) + " hour" ^0
"" ^0
# ---- what is lost, by hour of failure ----
"failure at hours since backup changes lost within the " + str(rpo_promised_hours) + "-hour RPO" ^0
0 => worst_lost
0 => hours_within_rpo
0 => hours_measured
for h in [3:23]:
h - backup_hour => age
age * changes_per_hour => lost
hours_measured + 1 => hours_measured
if age <= rpo_promised_hours:
hours_within_rpo + 1 => hours_within_rpo
if lost > worst_lost:
lost => worst_lost
if h % 4 == 0:
if age <= rpo_promised_hours:
" " + str(h) + ":00 " + str(age) + " " + str(lost) + " yes" ^0
else:
" " + str(h) + ":00 " + str(age) + " " + str(lost) + " no" ^0
"" ^0
" hours in the day measured : " + str(hours_measured) ^0
" hours that meet the RPO : " + str(hours_within_rpo) ^0
" worst case, a failure just before the next backup : " + str(worst_lost) + " changes" ^0
"" ^0
# ---- the promise and the schedule ----
"to meet a " + str(rpo_promised_hours) + "-hour RPO the backup must run every " + str(rpo_promised_hours) + " hour" ^0
" backups per day required : " + str(int(24 / rpo_promised_hours)) ^0
" backups per day scheduled: 1" ^0
" the schedule is short by a factor of " + str(int(24 / rpo_promised_hours)) ^0
"" ^0
" no amount of backup SUCCESS closes that gap" ^0
" a backup that never fails still cannot be newer than its schedule" ^0
"" ^0
# ---- what the success rate can and cannot see ----
"what " + str(consecutive_successes) + " consecutive successes establishes" ^0
" the job runs yes" ^0
" the artifact is complete yes, checksummed" ^0
" the artifact can be restored only if a restore was actually run" ^0
" the artifact is recent enough no, and this is not a property of the job" ^0
"" ^0
" the first two are about the backup" ^0
" the fourth is about the SCHEDULE, which cannot fail and therefore cannot" ^0
" appear in a success rate at all" ^0
"" ^0
# ---- the control ----
#
# The backup itself, judged against everything it claims. It has never failed,
# never produced a corrupt artifact, and never missed its window. Every review
# of the backup passes.
"control - is the backup doing what it says" ^0
" nights run : " + str(consecutive_successes) ^0
" nights succeeded : " + str(consecutive_successes) ^0
" corrupt artifacts : 0" ^0
" missed windows : 0" ^0
" failures found : 0 of " + str(consecutive_successes) ^0
" the backup is excellent and the number above is real" ^0
"" ^0
" it answers 'did we take a copy', and the RPO asks 'how old is it'" ^0
"" ^0
# ---- the null control ----
#
# The same nightly backup over a system that changes once a week. The gap is
# the same twenty-two hours and it costs nothing, because almost nothing
# happens inside it. The schedule is not wrong in itself; it is wrong against
# a change rate, and nobody put the two on one page.
1 => nc_changes_per_week
"null control - the same nightly backup over a weekly-changing system" ^0
" changes per week : " + str(nc_changes_per_week) ^0
" worst-case changes lost : " + str(nc_changes_per_week) ^0
" RPO of 1 hour : met on most days by accident" ^0
" same schedule, same job, same success rate" ^0
" the exposure is the change rate times the interval, and only one of" ^0
" those two numbers is in the backup's documentation" ^0
"" ^0
# ---- the rule ----
"two numbers that describe a backup, and where each lives" ^0
" success rate in the job's monitoring, and it is 400 of 400" ^0
" recovery point in the SCHEDULE, and nothing monitors a schedule" ^0
" the first is measured continuously" ^0
" the second is decided once and never observed again" ^0
" a green backup dashboard is consistent with any RPO whatsoever" ^0
"" ^0
"the measurement that would have found this is one subtraction:" ^0
"interval between backups, against the interval the RPO promises" ^0
"" ^0
"A nightly full at " + str(backup_hour) + ":00 runs in the quiet window, produces one self-contained" ^0
"artifact, and has succeeded " + str(consecutive_successes) + " nights running with a verified checksum every" ^0
"morning. A failure at 21:00 loses " + str(19 * changes_per_hour) + " changes against an RPO of " + str(rpo_promised_hours) + " hour, and" ^0
"the worst hour of the day loses " + str(worst_lost) + ". The success rate cannot move in response to" ^0
"any of that, because the schedule is not something the job can fail at." ^0Python (deterministic transpilation)
pythonbackup_hour = 2
changes_per_hour = 4200
rpo_promised_hours = 1
consecutive_successes = 400
print("backup runs at : " + str(backup_hour) + ":00, nightly, full")
print("consecutive successes : " + str(consecutive_successes))
print("changes per hour : " + str(changes_per_hour))
print("RPO promised : " + str(rpo_promised_hours) + " hour")
print("")
print("failure at hours since backup changes lost within the " + str(rpo_promised_hours) + "-hour RPO")
worst_lost = 0
hours_within_rpo = 0
hours_measured = 0
for h in range(3, 24):
age = h - backup_hour
lost = age * changes_per_hour
hours_measured = hours_measured + 1
if age <= rpo_promised_hours:
hours_within_rpo = hours_within_rpo + 1
if lost > worst_lost:
worst_lost = lost
if h % 4 == 0:
if age <= rpo_promised_hours:
print(" " + str(h) + ":00 " + str(age) + " " + str(lost) + " yes")
else:
print(" " + str(h) + ":00 " + str(age) + " " + str(lost) + " no")
print("")
print(" hours in the day measured : " + str(hours_measured))
print(" hours that meet the RPO : " + str(hours_within_rpo))
print(" worst case, a failure just before the next backup : " + str(worst_lost) + " changes")
print("")
print("to meet a " + str(rpo_promised_hours) + "-hour RPO the backup must run every " + str(rpo_promised_hours) + " hour")
print(" backups per day required : " + str(int(24 / rpo_promised_hours)))
print(" backups per day scheduled: 1")
print(" the schedule is short by a factor of " + str(int(24 / rpo_promised_hours)))
print("")
print(" no amount of backup SUCCESS closes that gap")
print(" a backup that never fails still cannot be newer than its schedule")
print("")
print("what " + str(consecutive_successes) + " consecutive successes establishes")
print(" the job runs yes")
print(" the artifact is complete yes, checksummed")
print(" the artifact can be restored only if a restore was actually run")
print(" the artifact is recent enough no, and this is not a property of the job")
print("")
print(" the first two are about the backup")
print(" the fourth is about the SCHEDULE, which cannot fail and therefore cannot")
print(" appear in a success rate at all")
print("")
print("control - is the backup doing what it says")
print(" nights run : " + str(consecutive_successes))
print(" nights succeeded : " + str(consecutive_successes))
print(" corrupt artifacts : 0")
print(" missed windows : 0")
print(" failures found : 0 of " + str(consecutive_successes))
print(" the backup is excellent and the number above is real")
print("")
print(" it answers 'did we take a copy', and the RPO asks 'how old is it'")
print("")
nc_changes_per_week = 1
print("null control - the same nightly backup over a weekly-changing system")
print(" changes per week : " + str(nc_changes_per_week))
print(" worst-case changes lost : " + str(nc_changes_per_week))
print(" RPO of 1 hour : met on most days by accident")
print(" same schedule, same job, same success rate")
print(" the exposure is the change rate times the interval, and only one of")
print(" those two numbers is in the backup's documentation")
print("")
print("two numbers that describe a backup, and where each lives")
print(" success rate in the job's monitoring, and it is 400 of 400")
print(" recovery point in the SCHEDULE, and nothing monitors a schedule")
print(" the first is measured continuously")
print(" the second is decided once and never observed again")
print(" a green backup dashboard is consistent with any RPO whatsoever")
print("")
print("the measurement that would have found this is one subtraction:")
print("interval between backups, against the interval the RPO promises")
print("")
print("A nightly full at " + str(backup_hour) + ":00 runs in the quiet window, produces one self-contained")
print("artifact, and has succeeded " + str(consecutive_successes) + " nights running with a verified checksum every")
print("morning. A failure at 21:00 loses " + str(19 * changes_per_hour) + " changes against an RPO of " + str(rpo_promised_hours) + " hour, and")
print("the worst hour of the day loses " + str(worst_lost) + ". The success rate cannot move in response to")
print("any of that, because the schedule is not something the job can fail at.")stdout (executed)
textbackup runs at : 2:00, nightly, full
consecutive successes : 400
changes per hour : 4200
RPO promised : 1 hour
failure at hours since backup changes lost within the 1-hour RPO
4:00 2 8400 no
8:00 6 25200 no
12:00 10 42000 no
16:00 14 58800 no
20:00 18 75600 no
hours in the day measured : 21
hours that meet the RPO : 1
worst case, a failure just before the next backup : 88200 changes
to meet a 1-hour RPO the backup must run every 1 hour
backups per day required : 24
backups per day scheduled: 1
the schedule is short by a factor of 24
no amount of backup SUCCESS closes that gap
a backup that never fails still cannot be newer than its schedule
what 400 consecutive successes establishes
the job runs yes
the artifact is complete yes, checksummed
the artifact can be restored only if a restore was actually run
the artifact is recent enough no, and this is not a property of the job
the first two are about the backup
the fourth is about the SCHEDULE, which cannot fail and therefore cannot
appear in a success rate at all
control - is the backup doing what it says
nights run : 400
nights succeeded : 400
corrupt artifacts : 0
missed windows : 0
failures found : 0 of 400
the backup is excellent and the number above is real
it answers 'did we take a copy', and the RPO asks 'how old is it'
null control - the same nightly backup over a weekly-changing system
changes per week : 1
worst-case changes lost : 1
RPO of 1 hour : met on most days by accident
same schedule, same job, same success rate
the exposure is the change rate times the interval, and only one of
those two numbers is in the backup's documentation
two numbers that describe a backup, and where each lives
success rate in the job's monitoring, and it is 400 of 400
recovery point in the SCHEDULE, and nothing monitors a schedule
the first is measured continuously
the second is decided once and never observed again
a green backup dashboard is consistent with any RPO whatsoever
the measurement that would have found this is one subtraction:
interval between backups, against the interval the RPO promises
A nightly full at 2:00 runs in the quiet window, produces one self-contained
artifact, and has succeeded 400 nights running with a verified checksum every
morning. A failure at 21:00 loses 79800 changes against an RPO of 1 hour, and
the worst hour of the day loses 88200. The success rate cannot move in response to
any of that, because the schedule is not something the job can fail at.Trace event types
eml:run:starteml:assigneml:outputeml:run:done