Confirm model conditions
In a fictional exercise, a pre-authorized model permits updating up to two nodes, sequentially, with healthy redundancy. A request for two parallel updates meets the quantity limit but fails sequencing. If one node is already unavailable, it also fails the redundancy condition. Analysis should compare all relevant conditions with current state. Twenty successful test executions do not establish applicability to a different production topology. Request the model version, evidence of its conditions, and the prescribed treatment of deviations. One response is to reshape the request to meet the model; another is to route the exception to someone mandated to decide it. The Change Manager can coordinate preparation without assuming acceptance of every risk. Task frequency and team familiarity support investigation but do not replace agreed conditions. Record which condition is unmet so that the next owner can make a specific decision.
Analyze shared service and recovery
Change A removes the primary node and B removes the secondary. Each isolated request states that the other node keeps service running. Infrastructure identifiers differ, but the continuity assumption is shared. Request a simple diagram showing service, nodes, recovery dependencies, and exposure intervals. Two servers for different applications may also depend on the same gateway to recover files. If gateway maintenance coincides with the other intervention, planned recovery may become unavailable. A spreadsheet without repeated identifiers does not prove independence. Its schedule depends on the quality of supplied relationships. At work, confirm those relationships with technical and service owners. If one change can be deferred while another has a confirmed deadline, compare a sequence preserving required capacity, including validation between interventions. Record who confirms the dependency and what would invalidate the proposed sequence before execution begins.
Reserve time until capacity is validated
N1 is unavailable from 01:00 to 01:25 UTC and N2 from 01:15 to 01:40 UTC. Ten minutes overlap, from 01:15 to 01:25. Forty minutes would describe the entire span of both intervals. Now consider another proposal: the N1 command finishes at 01:25, but necessary validation ends at 01:35; B removes N2 at 01:25. Commands do not overlap, yet required validated capacity remains unavailable for ten minutes. Represent this interval in the plan and establish whether B can start after validation. In a separate calculation, forty minutes of execution followed by fifteen of validation, both complete by 02:00, require starting by 01:05 without slack. These numbers are teaching conditions, not BNP Paribas windows or policies. Estimates, recovery, and contingency require additional analysis in the actual context. A calculated latest start is only as useful as its assumptions and applicable completion criterion.
Run and critique the local model
Save the complete code below as change-control.py and run python3 change-control.py. The program uses only the standard library and prints thirteen groups of checks. It was executed with CPython 3.13.1. Intervals use minutes within the same UTC day; in this model, the ending boundary is excluded. There is no timezone conversion, midnight crossing, or general input validation. Observe the difference between command and exposure intervals. Then remove B's declared dependencies: the result stops showing shared resources, although the program has discovered nothing about infrastructure. Try changing service S to T while retaining the decision; remaining time does not expand scope. Explain in writing what each result establishes and what information remains necessary. This code verifies neither identity, human authority, GitHub/GitLab configuration, nor actual production permission. Treat it as an aid for discussing assumptions with the relevant owners.
"""Original DR review aid with fictional inputs, not an authorization engine."""
from fractions import Fraction
import hashlib
import json
import platform
from pathlib import Path
def overlap(left, right):
"""Integer minutes in the same UTC day; half-open intervals in this exercise."""
if any(start > end for start, end in [left, right]):
raise ValueError("Invalid interval")
return max(0, min(left[1], right[1]) - max(left[0], right[0]))
def shared_exposure(a, b):
duration = overlap(a["exposure"], b["exposure"])
return {"minutes": duration, "services": sorted(set(a["services"]) & set(b["services"])),
"recoveryDependencies": sorted(set(a["recoveryDependencies"]) & set(b["recoveryDependencies"]))}
def model_gaps(request):
gaps = []
if request["nodes"] > 2:
gaps.append("node-count")
if request["mode"]!= "sequential":
gaps.append("sequence")
if not request["healthyRedundancy"]:
gaps.append("redundancy")
return gaps
def decision_gaps(decision, request, now):
# All identities, authority, observations and times are supplied, not verified.
gaps = []
if decision["state"]!= "approved":
gaps.append("decision-not-active")
if request["service"] not in decision["services"]:
gaps.append("scope")
if not decision["start"] <= now < decision["end"]:
gaps.append("time")
return gaps
def config_diff(proposed, actual):
keys = proposed.keys | actual.keys
return sorted(k for k in keys if k not in proposed or k not in actual or proposed[k]!= actual[k])
def run:
checks = []
def check(name, actual, expected):
if actual!= expected:
raise AssertionError((name, actual, expected))
checks.append({"name": name, "actual": actual, "passed": True})
check("two_nodes_unavailable_together", overlap((60, 85), (75, 100)), 10)
check("command_intervals_touch_but_validation_extends_exposure",
{"commands": overlap((60, 85), (85, 110)), "exposure": overlap((60, 95), (85, 110))},
{"commands": 0, "exposure": 10})
check("replanned_second_node_starts_after_validation", overlap((60, 95), (95, 120)), 0)
a = {"exposure": (60, 95), "services": ["funds"], "recoveryDependencies": ["gateway-G"]}
b = {"exposure": (85, 110), "services": ["funds"], "recoveryDependencies": ["gateway-G"]}
check("distinct_nodes_can_share_service_and_recovery", shared_exposure(a, b),
{"minutes": 10, "services": ["funds"], "recoveryDependencies": ["gateway-G"]})
omitted = {**b, "services": [], "recoveryDependencies": []}
check("omitted_dependencies_are_not_discovered", shared_exposure(a, omitted),
{"minutes": 10, "services": [], "recoveryDependencies": []})
check("latest_start_includes_validation", 120 - 40 - 15, 65) # 01:05 UTC
request = {"nodes": 2, "mode": "parallel", "healthyRedundancy": False}
check("count_alone_does_not_satisfy_model", model_gaps(request), ["sequence", "redundancy"])
check("matching_model_fields_only", model_gaps({**request, "mode": "sequential", "healthyRedundancy": True}), [])
decision = {"state": "approved", "services": ["S"], "start": 120, "end": 180}
check("remaining_time_does_not_expand_scope", decision_gaps(decision, {"service": "T"}, 170), ["scope"])
withdrawn = {**decision, "state": "withdrawn"}
check("withdrawal_overrides_old_display_label", decision_gaps(withdrawn, {"service": "S"}, 175), ["decision-not-active"])
check("fictional_expiry_boundary_is_exclusive",
[decision_gaps(decision, {"service": "S"}, t) for t in [179, 180]], [[], ["time"]])
proposed = {"binary": "R4", "access": "restricted", "pool": 8}
actual = {**proposed, "access": "expanded"}
check("successful_process_does_not_erase_config_deviation", config_diff(proposed, actual), ["access"])
requests, canceled, implemented, failed = 50, 20, 30, 3
check("failure_rate_uses_defined_implemented_population",
{"requested": requests, "canceled": canceled, "implemented": implemented,
"failureRate": str(Fraction(failed, implemented)), "incorrectRequestRate": str(Fraction(failed, requests))},
{"requested": 50, "canceled": 20, "implemented": 30, "failureRate": "1/10", "incorrectRequestRate": "3/50"})
return {"scope": "Fictional calendar, eligibility and record comparisons only. No real authority, GitHub or GitLab configuration, deployment, identity authentication or production permission is verified. Empty gap lists establish only supplied model conditions; omitted dependencies remain unknown.",
"python": platform.python_version, "scriptSha256": hashlib.sha256(Path(__file__).read_bytes).hexdigest,
"groups": len(checks), "checks": checks}
if __name__ == "__main__":
print(json.dumps(run, ensure_ascii=False, indent=2))A and B have different servers but both depend on gateway G. Nonintersecting node lists do not answer the recovery question.
Common pitfalls
Counting repetition as eligibility; overlooking shared dependencies; treating command completion as available capacity; treating incomplete inputs as proof of independence.
Related topics: Aggregate risk · Technical resilience · Change scheduling
Coordinate the interval in which capacity is exposed, with explicit model conditions and dependencies.
Reference: Guide for Security-Focused Configuration Management of Information Systems · NIST SP 800-128 updated October 2019; DORA five-metric model and change approval guidance; vendor documentation inspected 2026-10-01