68 lines
4.3 KiB
Python
68 lines
4.3 KiB
Python
"""Read-only owner-source and primary projection check after source checkpoints."""
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
import hashlib
|
|
import json
|
|
import re
|
|
import subprocess
|
|
import urllib.request
|
|
import yaml
|
|
|
|
HERE = Path(__file__).resolve().parent
|
|
PRIOR = HERE.parent / 'native-gates-r1/native-gate-audit.json'
|
|
prior = json.loads(PRIOR.read_text())
|
|
def get(path):
|
|
return json.load(urllib.request.urlopen('http://127.0.0.1:8000' + path, timeout=30))
|
|
def git(root, *args):
|
|
return subprocess.check_output(['git', '--no-optional-locks', '-C', str(root), *args], stderr=subprocess.PIPE).decode().strip()
|
|
health = get('/state/health')
|
|
assert (health['instance_role'], health['instance_label']) == ('primary', 'railiance01')
|
|
repos = sorted({Path(r['path']).parents[1].name for r in prior['owner_refs']})
|
|
with ThreadPoolExecutor(max_workers=4) as pool:
|
|
snapshots = dict(zip(repos, pool.map(lambda repo: get('/repos/' + repo + '/work-record-projection/snapshot'), repos)))
|
|
heads = {repo: git(Path('/home/worsch') / repo, 'rev-parse', 'HEAD') for repo in repos}
|
|
rows = []
|
|
issues = []
|
|
for ref in prior['owner_refs']:
|
|
path = Path(ref['path']); root = path.parents[1]; repo = root.name
|
|
raw = path.read_text(); front = yaml.safe_load(raw.split('---', 2)[1])
|
|
if git(root, 'show', heads[repo] + ':' + str(path.relative_to(root))) != raw.strip():
|
|
issues.append({'repo': repo, 'record': front['id'], 'field': 'source_not_committed',
|
|
'source': str(path), 'actual': heads[repo]})
|
|
tasks = [yaml.safe_load(s) for s in re.findall(r'```task\n(.*?)\n```', raw, re.S)]
|
|
snapshot = snapshots[repo]
|
|
plan = next(p for p in snapshot['workplans'] if p['id'] == front['state_hub_workstream_id'])
|
|
if plan['status'] != front['status']:
|
|
issues.append({'repo': repo, 'record': front['id'], 'field': 'status', 'source': front['status'], 'actual': plan['status']})
|
|
actual = [t for t in snapshot['tasks'] if t.get('workplan_id', t.get('workstream_id')) == plan['id']]
|
|
if len(actual) != len(tasks):
|
|
issues.append({'repo': repo, 'record': front['id'], 'field': 'task_count', 'source': len(tasks), 'actual': len(actual)})
|
|
byid = {t['id']: t for t in actual}
|
|
for task in tasks:
|
|
t = byid.get(task['state_hub_task_id'])
|
|
if t is None:
|
|
issues.append({'repo': repo, 'record': task['id'], 'field': 'uuid_or_parent', 'source': task['state_hub_task_id'], 'actual': None})
|
|
continue
|
|
for field, expected in [('record_id', task['id']), ('status', task['status'])]:
|
|
if t.get(field) != expected:
|
|
issues.append({'repo': repo, 'record': task['id'], 'uuid': t['id'], 'field': field, 'source': expected, 'actual': t.get(field)})
|
|
if 'needs_human' in task:
|
|
if bool(t.get('needs_human')) != bool(task['needs_human']):
|
|
issues.append({'repo': repo, 'record': task['id'], 'field': 'needs_human', 'source': bool(task['needs_human']), 'actual': bool(t.get('needs_human'))})
|
|
rows.append({'repo': repo, 'workplan': front['id'], 'commit': heads[repo],
|
|
'path': str(path), 'sha256': hashlib.sha256(path.read_bytes()).hexdigest(),
|
|
'status': front['status'], 'flavor': front.get('flavor'),
|
|
'tasks': [{'record_id': t['record_id'], 'uuid': t['id'], 'status': t['status']} for t in actual]})
|
|
out = {'schema': 'custodian.native-owner-binding-audit.v2',
|
|
'observed_at': datetime.now(timezone.utc).isoformat(), 'status': 'verified' if not issues else 'refused',
|
|
'primary': 'primary/railiance01', 'workplans': len(rows),
|
|
'task_bindings': sum(len(r['tasks']) for r in rows), 'rows': rows,
|
|
'prior_audit_sha256': hashlib.sha256(PRIOR.read_bytes()).hexdigest(),
|
|
'scope': 'Current source/primary workplan status, task UUID, status, parent and explicit human flag only; no new native gate acceptance inferred.',
|
|
'native_target_gates_closed': 0, 'issues': issues}
|
|
destination = 'owner-bindings.json' if not issues else 'owner-bindings-refused-r2.json'
|
|
(HERE / destination).write_text(json.dumps(out, indent=2) + '\n')
|
|
print(json.dumps({**{k: out[k] for k in ['status', 'workplans', 'task_bindings', 'native_target_gates_closed']},
|
|
'issues': issues}))
|
|
raise SystemExit(bool(issues))
|