"""Author: Sandeep Belgavi. Offline release gate, not a deployment controller.""" from dataclasses import dataclass, asdict import hashlib import json @dataclass(frozen=True) class Bundle: code: str prompt: str model: str tools: str schema: str def digest(self): if not all(isinstance(v, str) and v for v in asdict(self).values()): raise ValueError('Complete version bundle required') return hashlib.sha256(json.dumps(asdict(self), sort_keys=True).encode()).hexdigest() class ReleaseGate: def __init__(self, baseline, candidate, percent=10): baseline.digest() candidate.digest() if baseline.schema != candidate.schema: raise ValueError('Schema compatibility requires separate migration review') if not 0 <= percent <= 100: raise ValueError('Invalid canary percentage') self.baseline, self.candidate, self.percent = baseline, candidate, percent self.active = baseline def cohort(self, tenant, incident): if not tenant or not incident: raise ValueError('Stable cohort identity required') key = json.dumps([tenant, incident], separators=(',', ':')).encode() bucket = int(hashlib.sha256(key).hexdigest(), 16) % 100 return 'candidate' if bucket < self.percent else 'baseline' def assess(self, control_n, control_fail, candidate_n, candidate_fail, unauthorized=0, approved=False): values = (control_n, control_fail, candidate_n, candidate_fail, unauthorized) if any(not isinstance(v, int) or isinstance(v, bool) or v < 0 for v in values): raise ValueError('Nonnegative integer observations required') if control_fail > control_n or candidate_fail > candidate_n: raise ValueError('Failures exceed observations') if unauthorized: self.active = self.baseline return 'rollback' if min(control_n, candidate_n) < 20: return 'hold' if candidate_fail / candidate_n > control_fail / control_n + 0.10: self.active = self.baseline return 'rollback' if not approved: return 'await_approval' self.active = self.candidate return 'promote' def main(): base = Bundle('c1', 'p1', 'm1', 't1', 's1') candidate = Bundle('c2', 'p2', 'm1', 't1', 's1') gate = ReleaseGate(base, candidate) hold = gate.assess(100, 2, 5, 0) decision = gate.assess(100, 2, 20, 1, approved=True) rollback = gate.assess(100, 2, 20, 1, unauthorized=1) print(json.dumps({'hold': hold, 'decision': decision, 'rollback': rollback, 'active_code': gate.active.code}, sort_keys=True)) if __name__ == '__main__': main()