"""Executable read-only sampler for the existing durable release HealthObserver. No signer, identity admission or mutation is provided. Each failed/unavailable probe records False, missed intervals reset, and a new ledger starts now. """ from __future__ import annotations import argparse from datetime import datetime, timezone import ipaddress import json from pathlib import Path import time import urllib.request import yaml from .release_observer import HealthObserver from .release_operations import revision, update_child from .release_transport import run CLUSTER_UID = 'a553c742-0115-43d4-99a4-a5ca56fe0786' BASELINE = 'a86a14332d76d92a7ed176ea34dca4429902c396' DEPLOYMENTS = {'actcore-api', 'actcore-worker', 'actcore-event-router'} RESOURCE_SET = { ('ConfigMap', name) for name in ('actcore-runtime-config','actcore-external-activity-definitions','actcore-report-schemas','actcore-ops-service-inventory') } | {('Service',name) for name in ('actcore-api','actcore-worker-metrics')} | {('Deployment',name) for name in DEPLOYMENTS} def http_health(url): with urllib.request.urlopen(url, timeout=20) as response: if response.status != 200: raise ValueError('invariant service unavailable') body = response.read(16385) if len(body) > 16384: raise ValueError('invariant response exceeds fixed bound') return json.loads(body) def invariant_matches(value, now): if (not isinstance(value, dict) or value.get('schema_version') != 'activity-core-release-health/v1' or value.get('healthy') is not True or value.get('definitions_and_report_sinks_match') is not True or value.get('schedules_match') is not True or value.get('definition_count') != 3 or value.get('schedule_count') != 3 or 'error' in value): return False at = datetime.fromisoformat(value['observed_at'].replace('Z', '+00:00')) # Same estate observer should use synchronized clocks; unknown/future time # is a failed sample, never a manufactured healthy interval. return at.tzinfo is not None and 0 <= (now-at).total_seconds() <= 90 class Probe: """Only fixed GETs, a cluster-pinned service origin and public boolean health.""" def __init__(self, runner=run, reader=http_health): self.runner, self.reader = runner, reader def get(self, *args): return json.loads(self.runner(['kubectl','--request-timeout=20s',*args,'-o','json'],timeout=25)) def __call__(self): nodes = self.get('get','nodes') if '92.205.62.239' not in [a['address'] for n in nodes['items'] for a in n['status']['addresses']]: raise ValueError('estate identity mismatch') if self.get('get','namespace','kube-system')['metadata']['uid'] != CLUSTER_UID: raise ValueError('cluster UID mismatch') app = self.get('-n','argocd','get','application','activity-core') target = revision(app['spec']['source']['targetRevision']) update_child(yaml.safe_dump(app),target,target) status = app.get('status',{}) healthy = (not app.get('operation') and status.get('sync',{}).get('status') == 'Synced' and status.get('sync',{}).get('revision') == target and status.get('health',{}).get('status') == 'Healthy' and not status.get('conditions')) resources = status.get('resources',[]) healthy &= (len(resources) == 9 and {(r['kind'],r['name']) for r in resources} == RESOURCE_SET and all(r.get('namespace') == 'activity-core' and r.get('status') == 'Synced' and (r.get('group') or '') == ('apps' if r['kind']=='Deployment' else '') for r in resources)) for name in sorted(DEPLOYMENTS): deployment = self.get('-n','activity-core','get','deployment',name) desired = deployment['spec'].get('replicas',1);ds=deployment.get('status',{}) healthy &= (type(desired) is int and desired>0 and ds.get('observedGeneration') == deployment['metadata']['generation'] and all(ds.get(k,0) == desired for k in ('readyReplicas','updatedReplicas','availableReplicas'))) service = self.get('-n','activity-core','get','service','actcore-api') address = ipaddress.ip_address(service['spec']['clusterIP']) if not any(p.get('port') == 8010 for p in service['spec'].get('ports',[])): raise ValueError('fixed invariant API port unavailable') host = f'[{address}]' if address.version == 6 else str(address) value = self.reader(f'http://{host}:8010/release/health') healthy &= invariant_matches(value,datetime.now(timezone.utc)) return target,bool(healthy) class Sampler: def __init__(self, database, probe, *, anchor_revision=BASELINE): self.observer=HealthObserver(database,max_gap_seconds=90) self.probe=probe;self.anchor=revision(anchor_revision) def sample(self,now=None): explicit_now=now try: target,healthy=self.probe() revision(target) if type(healthy) is not bool:raise ValueError('explicit health required') except Exception: # Use the last durably seen identity only for a failed sample. No # raw error, fabricated timestamp or successful identity is emitted. with self.observer.connect() as db: row=db.execute('SELECT revision FROM observations ORDER BY rowid DESC LIMIT 1').fetchone() target=row[0] if row else self.anchor;healthy=False # Measure after the probe, not before a slow/failed network operation. measured=explicit_now if explicit_now is not None else datetime.now(timezone.utc) since=self.observer.observe(target,healthy,measured) try:attestation=self.observer.attestation(target,measured) except ValueError:attestation=None return {'schema_version':'activity-core-release-sample/v1','revision':target, 'observed_at':measured.isoformat(),'healthy':healthy,'healthy_since':since, 'continuous_24h_ready':attestation is not None,'attestation':attestation, 'native_mutation':False} def main(): parser=argparse.ArgumentParser(description=__doc__) parser.add_argument('--database',type=Path,required=True) parser.add_argument('--receipt',type=Path,required=True) parser.add_argument('--watch',action='store_true',help='read-only 60s sampler; requires an owner-deployed process') args=parser.parse_args() sampler=Sampler(args.database,Probe()) while True: try:result=sampler.sample() except Exception:result={'schema_version':'activity-core-release-sample/v1','healthy':False,'continuous_24h_ready':False,'error':'durable observation unavailable','native_mutation':False} args.receipt.write_text(json.dumps(result,sort_keys=True)+'\n') if not args.watch: print(json.dumps(result,sort_keys=True));return 0 if result.get('healthy') else 1 time.sleep(60) if __name__=='__main__':raise SystemExit(main())