Verify brief publication against live origin and record historical gaps
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a09cbd-43c1-79f3-809e-1ee97b40b64d
This commit is contained in:
parent
8a65297d56
commit
8bd2e5ff92
10 changed files with 565 additions and 90 deletions
|
|
@ -1,114 +1,192 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Fail if State Hub fi_daily_brief events are not on origin/main.
|
||||
"""Audit Hub completions against a freshly resolved origin/main Git tree.
|
||||
|
||||
A brief day is complete only when briefs/YYYY/MM/YYYY-MM-DD.md exists in
|
||||
this git repository (the clone should be origin/main). Hub events that
|
||||
claim wrote/committed without a matching file are the 2026-08/09 failure
|
||||
mode: local commit on railiance, due-bit cleared, durable memory empty.
|
||||
|
||||
Usage:
|
||||
python3 scripts/verify_brief_durability.py
|
||||
STATE_HUB_URL=http://127.0.0.1:8000 python3 scripts/verify_brief_durability.py
|
||||
|
||||
Exit 0 = every hub success event has a brief file.
|
||||
Exit 1 = mismatch (or hub unreachable when --require-hub is set).
|
||||
Exit 0: checks pass (or explicitly allowed acknowledged historical gaps).
|
||||
Exit 1: missing/invalid evidence. Exit 2: verification could not run reliably.
|
||||
--require-date YYYY-MM-DD also requires a valid completion for that day.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from datetime import date
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
REPO = 'freedom-intelligence'
|
||||
CONTRACT_START = '2026-09-13'
|
||||
CORRECTION_TYPE = 'fi_daily_brief_reconciliation'
|
||||
SHA = re.compile(r'[0-9a-f]{40}')
|
||||
|
||||
|
||||
def brief_path(date: str) -> Path:
|
||||
year, month, _ = date.split("-")
|
||||
return ROOT / "briefs" / year / month / f"{date}.md"
|
||||
def brief_path(day: str) -> str:
|
||||
if date.fromisoformat(day).isoformat() != day:
|
||||
raise ValueError('date must be YYYY-MM-DD')
|
||||
return f'briefs/{day[:4]}/{day[5:7]}/{day}.md'
|
||||
|
||||
|
||||
def load_events(url: str, timeout: float) -> list[dict]:
|
||||
req = urllib.request.Request(
|
||||
f"{url.rstrip('/')}/progress/?event_type=fi_daily_brief",
|
||||
headers={"Accept": "application/json"},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
data = json.loads(resp.read().decode())
|
||||
if isinstance(data, list):
|
||||
return data
|
||||
if isinstance(data, dict):
|
||||
for key in ("items", "events", "results"):
|
||||
if isinstance(data.get(key), list):
|
||||
return data[key]
|
||||
raise RuntimeError(f"unexpected hub payload type {type(data)}")
|
||||
def git(root: Path, *args: str, check=True):
|
||||
result = subprocess.run(['git', '-C', str(root), *args], capture_output=True,
|
||||
text=True, timeout=120)
|
||||
if check and result.returncode:
|
||||
raise RuntimeError(f'git {args[0]} failed: {result.stderr.strip()}')
|
||||
return result
|
||||
|
||||
|
||||
class Origin:
|
||||
def __init__(self, root: Path):
|
||||
self.root = root
|
||||
# Never trust a stale tracking ref or the checked-out branch.
|
||||
remote = git(root, 'ls-remote', '--exit-code', 'origin', 'refs/heads/main').stdout.split()
|
||||
if len(remote) != 2 or not SHA.fullmatch(remote[0]):
|
||||
raise RuntimeError('origin/main did not resolve to one commit')
|
||||
self.sha = remote[0]
|
||||
if git(root, 'cat-file', '-e', f'{self.sha}^{{commit}}', check=False).returncode:
|
||||
git(root, 'fetch', '--no-tags', '--no-write-fetch-head', 'origin', 'refs/heads/main')
|
||||
git(root, 'cat-file', '-e', f'{self.sha}^{{commit}}')
|
||||
|
||||
def blob(self, path: str, revision: str | None = None) -> bool:
|
||||
revision = revision or self.sha
|
||||
if not SHA.fullmatch(revision):
|
||||
return False
|
||||
entry = git(self.root, 'ls-tree', revision, '--', path, check=False)
|
||||
fields = entry.stdout.split()
|
||||
# Reject symlinks, trees and empty files as completion artifacts.
|
||||
if entry.returncode or len(fields) != 4 or fields[0] not in {'100644', '100755'}:
|
||||
return False
|
||||
return fields[1] == 'blob' and int(git(self.root, 'cat-file', '-s', fields[2]).stdout) > 0
|
||||
|
||||
def published(self, revision: str, path: str) -> bool:
|
||||
if not SHA.fullmatch(revision):
|
||||
return False
|
||||
return (git(self.root, 'merge-base', '--is-ancestor', revision, self.sha,
|
||||
check=False).returncode == 0 and self.blob(path, revision))
|
||||
|
||||
def correction_recorded(self, detail: dict) -> bool:
|
||||
revision = str(detail.get('evidence_sha', ''))
|
||||
path = str(detail.get('evidence_path', ''))
|
||||
if not self.published(revision, path):
|
||||
return False
|
||||
try:
|
||||
evidence = json.loads(git(self.root, 'show', f'{revision}:{path}').stdout)
|
||||
records = evidence['corrections']
|
||||
except (ValueError, KeyError, TypeError):
|
||||
return False
|
||||
fields = ('original_event_id', 'date', 'path', 'disposition', 'reason')
|
||||
return any(isinstance(row, dict) and all(row.get(k) == detail.get(k) for k in fields)
|
||||
for row in records)
|
||||
|
||||
|
||||
def load_events(url: str, timeout: float, event_type='fi_daily_brief', page_size=1000) -> list[dict]:
|
||||
events = []
|
||||
seen = set()
|
||||
for offset in range(0, 1_000_000, page_size):
|
||||
query = urllib.parse.urlencode(dict(event_type=event_type, limit=page_size, offset=offset))
|
||||
req = urllib.request.Request(f"{url.rstrip('/')}/progress/?{query}",
|
||||
headers={'Accept': 'application/json'})
|
||||
with urllib.request.urlopen(req, timeout=timeout) as response:
|
||||
page = json.load(response)
|
||||
if not isinstance(page, list):
|
||||
raise RuntimeError('unexpected Hub progress payload: expected a list')
|
||||
for event in page:
|
||||
if not isinstance(event, dict) or not event.get('id') or event['id'] in seen:
|
||||
raise RuntimeError('malformed or unstable Hub pagination; retry verification')
|
||||
seen.add(event['id'])
|
||||
events.extend(page)
|
||||
if len(page) < page_size:
|
||||
return events
|
||||
raise RuntimeError('Hub pagination exceeded safety bound')
|
||||
|
||||
|
||||
def assess(events, corrections, origin, required_date=None):
|
||||
result = {'origin_sha': origin.sha, 'valid': [], 'legacy': [], 'reconciled': [], 'failures': []}
|
||||
correction_index = {}
|
||||
for correction in corrections:
|
||||
d = correction.get('detail') or {}
|
||||
if not isinstance(d, dict) or d.get('repo') != REPO:
|
||||
continue
|
||||
if (d.get('disposition') == 'false_completion' and d.get('reason')
|
||||
and origin.correction_recorded(d)):
|
||||
correction_index.setdefault(d.get('original_event_id'), []).append(d)
|
||||
for event in events:
|
||||
d = event.get('detail') or {}
|
||||
if not isinstance(d, dict):
|
||||
result['failures'].append({'id': event.get('id'), 'reason': 'invalid event detail'})
|
||||
continue
|
||||
if d.get('repo') not in {None, REPO}:
|
||||
continue
|
||||
row = {'id': event.get('id'), 'date': d.get('date')}
|
||||
try:
|
||||
path = brief_path(d.get('date', ''))
|
||||
except (ValueError, TypeError):
|
||||
result['failures'].append(dict(row, reason='invalid or missing date'))
|
||||
continue
|
||||
if not origin.blob(path):
|
||||
acknowledged = any(c.get('date') == d['date'] and c.get('path') == path
|
||||
for c in correction_index.get(event.get('id'), []))
|
||||
target = 'reconciled' if acknowledged else 'failures'
|
||||
result[target].append(dict(row, reason='brief absent from origin/main', path=path))
|
||||
continue
|
||||
if d.get('path', path) != path:
|
||||
result['failures'].append(dict(row, reason='event path does not match date'))
|
||||
continue
|
||||
modern = d['date'] >= CONTRACT_START or 'pushed' in d or 'origin_sha' in d
|
||||
if modern:
|
||||
if (d.get('pushed') is not True or d.get('path') != path
|
||||
or not origin.published(str(d.get('origin_sha', '')), path)):
|
||||
result['failures'].append(dict(row, reason='invalid pushed/origin_sha publication evidence'))
|
||||
continue
|
||||
result['valid'].append(row)
|
||||
else:
|
||||
# Historic files exist on origin but predate the push-attestation contract.
|
||||
result['legacy'].append(row)
|
||||
if required_date and required_date not in {r['date'] for r in result['valid']}:
|
||||
result['failures'].append({'date': required_date, 'reason': 'required day has no valid published completion'})
|
||||
return result
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(description=__doc__)
|
||||
ap.add_argument(
|
||||
"--hub",
|
||||
default=os.environ.get("STATE_HUB_URL", "http://127.0.0.1:8000"),
|
||||
)
|
||||
ap.add_argument("--timeout", type=float, default=8.0)
|
||||
ap.add_argument(
|
||||
"--require-hub",
|
||||
action="store_true",
|
||||
help="exit 1 if the hub cannot be reached (default: warn and check git only)",
|
||||
)
|
||||
ap.add_argument('--hub', default=os.environ.get('STATE_HUB_URL', 'http://127.0.0.1:8000'))
|
||||
ap.add_argument('--timeout', type=float, default=8)
|
||||
ap.add_argument('--require-hub', action='store_true', help='compatibility flag; Hub is always required')
|
||||
ap.add_argument('--require-date', help='require a valid pushed completion for this exact day')
|
||||
ap.add_argument('--allow-reconciled', action='store_true', help='acknowledged historical gaps do not fail the audit')
|
||||
ap.add_argument('--json', action='store_true', help='machine-readable audit result')
|
||||
args = ap.parse_args()
|
||||
|
||||
git_dates = sorted(
|
||||
p.stem
|
||||
for p in (ROOT / "briefs").rglob("20*.md")
|
||||
if p.name[0].isdigit()
|
||||
)
|
||||
print(f"git briefs: {len(git_dates)} last={git_dates[-1] if git_dates else '-'}")
|
||||
|
||||
try:
|
||||
if args.require_date:
|
||||
brief_path(args.require_date)
|
||||
origin = Origin(ROOT)
|
||||
events = load_events(args.hub, args.timeout)
|
||||
except (urllib.error.URLError, TimeoutError, json.JSONDecodeError, RuntimeError) as exc:
|
||||
msg = f"hub unreachable or unreadable at {args.hub}: {exc}"
|
||||
if args.require_hub:
|
||||
print(f"FAIL {msg}", file=sys.stderr)
|
||||
return 1
|
||||
print(f"WARN {msg}")
|
||||
return 0
|
||||
|
||||
missing: list[str] = []
|
||||
present: list[str] = []
|
||||
for ev in events:
|
||||
detail = ev.get("detail") or {}
|
||||
date = str(detail.get("date") or "")
|
||||
if not date:
|
||||
continue
|
||||
repo = detail.get("repo")
|
||||
if repo and repo != "freedom-intelligence":
|
||||
continue
|
||||
path = brief_path(date)
|
||||
if path.is_file():
|
||||
present.append(date)
|
||||
corrections = load_events(args.hub, args.timeout, CORRECTION_TYPE)
|
||||
result = assess(events, corrections, origin, args.require_date)
|
||||
except (OSError, ValueError, RuntimeError, subprocess.SubprocessError) as exc:
|
||||
if args.json:
|
||||
print(json.dumps({'ok': False, 'error': str(exc)}))
|
||||
else:
|
||||
missing.append(date)
|
||||
|
||||
missing = sorted(set(missing))
|
||||
present = sorted(set(present))
|
||||
print(f"hub fi_daily_brief dates on git: {len(present)}")
|
||||
if missing:
|
||||
print("FAIL hub events with no brief file:")
|
||||
for date in missing:
|
||||
print(f" {date} expected {brief_path(date).relative_to(ROOT)}")
|
||||
print(
|
||||
"A local-only commit must not clear due. "
|
||||
"Push to origin, or treat the hub event as a false completion."
|
||||
)
|
||||
return 1
|
||||
print("ok: every hub fi_daily_brief date has a brief file")
|
||||
return 0
|
||||
print(f'ERROR verification unavailable: {exc}', file=sys.stderr)
|
||||
return 2
|
||||
result['ok'] = not result['failures'] and (args.allow_reconciled or not result['reconciled'])
|
||||
if args.json:
|
||||
print(json.dumps(result, indent=2))
|
||||
else:
|
||||
print(f'origin/main: {origin.sha} (resolved from live remote)')
|
||||
print(f"valid published events: {len(result['valid'])}; legacy files on origin: {len(result['legacy'])}")
|
||||
for row in result['reconciled']:
|
||||
print(f"GAP acknowledged false completion: {row['date']} event={row['id']}")
|
||||
for row in result['failures']:
|
||||
print(f"FAIL {row.get('date', '-')}: {row['reason']}")
|
||||
print('PASS (acknowledged gaps remain missing)' if result['ok'] and result['reconciled']
|
||||
else 'PASS' if result['ok'] else 'FAIL durability audit')
|
||||
return 0 if result['ok'] else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue