Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Project: migrate receipt inference callers without breaking decisions

Last updated: 7 Oct 20265 min read
project
AdvancedBy AITrove Editorial

Ship a new receipt inference contract through caller tests, staged traffic and a documented v1 retirement gate.

Freeze current caller behavior

The review UI reads route, decision ID and score; a nightly ledger reads route and model digest. Capture redacted v1 requests, expected error codes and client retry behavior. The proposed v2 adds an explicit fallback reason and revises the route names. Treat that rename as breaking, even though both versions still return JSON. Register both owners and schedules in the consumer inventory. Keep the feature vector hidden behind the public API boundary.

Implement and test the adapter

Provide a v1 response adapter that maps v2 decision states to old route names without discarding the decision ID. Reject unmappable states instead of fabricating a score. Replay client fixtures through both routes and force invalid currency, stale feature, timeout and duplicate request key. Assert that the ledger never interprets a fallback as an ordinary score. Compatibility tests should be owned by callers and run before moving real traffic.

Cut over on separate clocks

Move the review UI first and inspect rejected requests, fallback, tail latency and retry counts by client ID. Keep the nightly ledger on v1 until its scheduled run and reconciliation check pass on v2. Log API version, decision ID and served model digest with redacted identifiers. If a bad route mapping appears, return the UI to v1 while preserving already issued decision records. A reversible pointer handles routing; it does not undo a decision already consumed.

Prove retirement or hold

Watch v1 traffic for a window longer than the least frequent caller schedule. Require owner sign-off and a gateway inventory showing no unknown v1 clients. If the ledger makes one old call, retain v1 and investigate it; a planned deprecation date does not override observed traffic. Deliver a matrix of caller, fixture result, rollout state, last old call and rollback path. The incident drill is a useful follow-up for a contract failure discovered after cutover.

Implementation

python
def adapt_route_for_v1(decision):
    routes = {"HUMAN_REVIEW": "review", "AUTO_CLEAR": "clear"}
    if decision["route"] not in routes:
        raise ValueError("v1 cannot represent this decision state")
    return {"decision_id": decision["decision_id"],
            "route": routes[decision["route"]],
            "score": decision["score"]}

new_decision = {"decision_id": "decision-47", "route": "HUMAN_REVIEW",
                "score": 0.82}
assert adapt_route_for_v1(new_decision)["route"] == "review"
try:
    adapt_route_for_v1({**new_decision, "route": "FEATURE_UNAVAILABLE"})
except ValueError:
    pass
else:
    raise AssertionError("unmappable fallback must fail closed")

Performance and operating cost

The adapter is O(1) time and space per response. Running two contracts costs test maintenance, gateway routing and monitoring until the old caller population is gone. The adapter deliberately rejects an unrepresentable state; preserving an old schema by concealing a new failure could cause a downstream decision error.

Common Mistakes

  • Mapping feature failure to a valid score just to satisfy v1.
  • Cutting over a scheduled ledger before its first full v2 run.
  • Deleting v1 after owner sign-off while gateway still sees old calls.
  • Rolling back traffic without preserving decisions already issued.

Read next

ai-data
mlops
Storage details