← Technical Lead: decisions, quality, and team
07 / 10 · 60 MIN

Contracts, versions, and compatible migration

Review meaning, consumers, and intermediate states before recommending interface evolution.

The contract includes meaning

An interface may keep producing valid JSON while no longer serving consumers correctly. In a fictional example, duration retains its name and type but changes from milliseconds to seconds. The reader still divides by one thousand and interprets 1.5 seconds as 0.0015. Type validation misses this change in meaning. The same issue appears when a producer adds HELD to a text state that its consumer handles only as READY or DONE. Before recommending the change, identify units, allowed values, absence, null, invariants, and error behavior. For the exercise's monetary values, document currency and minor units; do not assume two visible decimal places establish correct calculation. Useful review requests examples challenging fragile assumptions alongside the path already working. These contracts are invented for learning and describe no bank APIs. Connect every compatibility claim to a consumer behavior that was actually checked.

Build the transition matrix

C1 reads only amount. C2 reads amount and amountMinor. P1 writes only amount; P2 writes only amountMinor. The four combinations are not equivalent: C1/P2 fails. If all required readers can migrate to C2 first, P1 remains usable during that stage and P2 can be introduced afterward. If twenty percent remain C1, an updated majority does not establish complete compatibility. The plan also needs reversion analysis: reverting only readers to C1 while retaining P2 recreates the incompatible combination. Record the sequence, each consumer's owner, transition criteria, and recovery alternatives. A mock already returning amountMinor can hide that the actual first-stage producer returns only amount. Tie evidence to observed versions and combinations. No calls for 48 hours do not establish migration of a partner using the interface only at quarter end. Clarify that consumer's migration or agreed treatment before retiring its contract.

Review partial modification and repetition

Semantics depend on format and contract. In RFC 7396 JSON Merge Patch, null for an existing member means removal; omitting it does not request the same modification. Do not generalize this rule to every PATCH format. For repetition, RFC 9110 distinguishes idempotent methods and limits automatic retry of non-idempotent requests without additional knowledge. A POST whose response was lost may already have produced effects. In the laboratory's fictional contract, a key binds to original content: repeating the same key and amount returns the earlier logical result, while another amount produces conflict. This model defines neither a universal protocol nor a durable implementation. In actual review, ask about operation identity, retention, concurrency, external effects, reconciliation, and behavior after restart. Merely having a key does not answer those questions. Separate the intended contract from evidence that an implementation preserves it under failure.

Run local cases and bound conclusions

Save the complete code as technical-contracts.py and run python3 technical-contracts.py. It was executed with CPython 3.13.1, requires no extra packages, and produces seventeen groups of checks. The first four exercise fictional readers and producers, followed by state, contact, units, precision, and total examples. The contact_patch helper supports only one flat string-or-null field; it is not a general RFC implementation. read_minor represents only the exercise's two payloads and does not validate arbitrary monetary contracts. The operation dictionary provides no atomicity, authentication, or persistence. Change consumer C1 to accept the new representation and predict the resulting matrix. Then remove the total assertion: explain which defect would become unobserved. Use outcomes to propose actual checks without presenting them as architecture approval, a load test, or a production migration. Preserve failing combinations as evidence for discussion rather than hiding them behind the passing final state.

"""Original DR fictional contract fixtures; no server or production validation."""
from decimal import Decimal
from math import ceil
import hashlib
import json
import platform
from pathlib import Path


def read_minor(reader, payload):
 # Only the two fictional representations below are modeled.
 if reader == "C2" and "amountMinor" in payload:
 return payload["amountMinor"]
 return int(Decimal(payload["amount"]) * 100)


def contact_patch(record, patch):
 # Deliberately restricted to one flat string-or-null member, not RFC conformance.
 if set(patch) - {"contact"} or any(v is not None and not isinstance(v, str) for v in patch.values):
 raise ValueError("Only a flat contact string or null is supported")
 result = dict(record)
 if "contact" in patch:
 if patch["contact"] is None:
 result.pop("contact", None)
 else:
 result["contact"] = patch["contact"]
 return result


def operation(store, key, amount):
 # Single-process teaching dictionary: no authentication, atomicity or durability.
 if key in store:
 return "replayed" if store[key] == amount else "conflict"
 store[key] = amount
 return "created"


def nearest_rank(values, percentile):
 # Explicit exercise convention; not a model of Prometheus interpolation.
 if not values or not 0 < percentile <= 100:
 raise ValueError("Nonempty values and a percentile in (0, 100] required")
 return sorted(values)[ceil(len(values) * percentile / 100) - 1]


def run:
 checks = []

 def check(name, actual, expected):
 if actual!= expected:
 raise AssertionError((name, actual, expected))
 checks.append({"name": name, "actual": actual, "passed": True})

 producers = {"P1": {"amount": "12.34", "currency": "EUR"},
 "P2": {"amountMinor": 1234, "currency": "EUR"}}
 for reader in ["C1", "C2"]:
 for producer, payload in producers.items:
 try:
 actual = read_minor(reader, payload)
 except KeyError:
 actual = "missing-field"
 check(f"contract_{reader}_{producer}", actual,
 "missing-field" if (reader, producer) == ("C1", "P2") else 1234)
 check("new_state_is_not_accepted_by_old_consumer", "HELD" in {"READY", "DONE"}, False)
 record = {"contact": "ops@example.test", "name": "fictional"}
 check("omitted_contact_is_retained", contact_patch(record, {}), record)
 check("null_contact_removes_member", contact_patch(record, {"contact": None}), {"name": "fictional"})
 check("unit_change_breaks_old_conversion", str(Decimal("1.5") / 1000), "0.0015")
 check("representation_affects_exact_comparison",
 {"decimal": Decimal("0.10") + Decimal("0.20") == Decimal("0.30"), "float": 0.1 + 0.2 == 0.3},
 {"decimal": True, "float": False})
 check("numeric_types_do_not_establish_total", sum([40, 60]) == 90, False)
 store = {}
 check("same_key_same_content_and_changed_content",
 [operation(store, "OP1", 1234), operation(store, "OP1", 1234), operation(store, "OP1", 1300)],
 ["created", "replayed", "conflict"])
 check("sequential_path_has_ten_ms_before_overhead", 300 - sum([150, 80, 60]), 10)
 check("attempts_and_waits_exceed_deadline", {"durationMs": 3 * 100 + 20 + 40, "excessMs": 3 * 100 + 20 + 40 - 300},
 {"durationMs": 360, "excessMs": 60})
 check("nested_total_attempts_multiply", 2 * 3, 6)
 check("constant_lossless_queue_accumulates", (200 - 150) * 20, 1000)
 check("tenant_identifier_is_part_of_identity",
 {"idOnlyCollides": 7 == 7, "tupleCollides": ("A", 7) == ("B", 7)},
 {"idOnlyCollides": True, "tupleCollides": False})
 a, b = [1] * 19 + [100], [10] * 20
 check("mean_of_percentiles_is_not_global_percentile",
 {"aP95": nearest_rank(a, 95), "bP95": nearest_rank(b, 95),
 "meanOfP95": (nearest_rank(a, 95) + nearest_rank(b, 95)) / 2,
 "globalP95": nearest_rank(a + b, 95)},
 {"aP95": 1, "bP95": 10, "meanOfP95": 5.5, "globalP95": 10})
 return {"scope": "Fictional local representations, arithmetic and dictionary state only. No network, concurrent SDK, durable idempotency, authorization enforcement, production migration, load test or full RFC implementation is exercised. Supplied tenant names are not authenticated. Percentiles use an explicit nearest-rank convention, not Prometheus interpolation.",
 "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))
IN PRACTICE

A response with lines=[40,60] and total=90 passes numeric types but violates total=sum(lines). The test must observe that relationship.

Common pitfalls

Type treated as meaning; majority as compatibility; a mock as provider evidence; omission equated with null; a key as a guarantee of exactly one effect.

Related topics: Integration contracts · Code review · Idempotency and reconciliation

Take this idea with you

Review all relevant transition states and the meaning of values, including the recovery path.

Create account

Reference: Best practices for RESTful web API design · Google Engineering Practices, SRE and DORA; Microsoft architecture decision and collaboration guidance; OWASP threat modeling; UK lead developer framework; inspected 2026-10-01