# -*- coding: utf-8 -*-
"""stale_source_monitor.py — GOV-F1.16a
Standalone script that probes every SRC URL for liveness and content drift,
logging results to 03_Registers/SITE.csv and WARNING to daily_audit_log.md.
Run manually or on a schedule. NOT invoked during build_all.py.
"""
import os, sys, csv, hashlib, time, urllib.request
HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(HERE)
SRC_CSV = os.path.join(ROOT, "03_Registers", "SRC.csv")
SITE_CSV = os.path.join(ROOT, "03_Registers", "SITE.csv")
LOG = os.path.join(HERE, "daily_audit_log.md")
def main():
if not os.path.exists(SRC_CSV):
print("SRC.csv not found — run build_all.py first")
sys.exit(1)
# Ensure SITE.csv exists with headers
if not os.path.exists(SITE_CSV):
with open(SITE_CSV, "w", newline="", encoding="utf-8") as f:
csv.writer(f).writerow(["date_checked", "url", "status_code", "response_time_ms", "changed_flag"])
rows = []
with open(SRC_CSV, "r", encoding="utf-8") as f:
r = csv.DictReader(f)
for row in r:
url = row.get("Source URL", "").strip()
if url.startswith("http"):
rows.append(row)
today = time.strftime("%Y-%m-%d")
new_records = []
warnings = []
for row in rows:
url = row["Source URL"]
try:
req = urllib.request.Request(url, method="HEAD")
start = time.time()
resp = urllib.request.urlopen(req, timeout=30)
elapsed_ms = int((time.time() - start) * 1000)
status = resp.getcode()
# Content drift: hash first 1KB of body (fall back to HEAD headers if body empty)
try:
body = resp.read(1024)
except Exception:
body = b""
content_hash = hashlib.sha256(body).hexdigest()[:16]
changed = False # Would need persistent storage of previous hash to detect change
except urllib.error.HTTPError as e:
elapsed_ms = 0
status = e.code
content_hash = ""
except Exception as e:
elapsed_ms = 0
status = "ERR: %s" % type(e).__name__
content_hash = ""
new_records.append([today, url, status, elapsed_ms, "N/A"])
if str(status) != "200":
msg = "WARNING: %s returned %s" % (url, status)
warnings.append(msg)
print(msg)
# Append to SITE.csv
with open(SITE_CSV, "a", newline="", encoding="utf-8") as f:
w = csv.writer(f)
for rec in new_records:
w.writerow(rec)
# Write warnings to audit log
if warnings:
with open(LOG, "a", encoding="utf-8") as f:
f.write("\n## Stale-source monitor %s\n" % today)
for w in warnings:
f.write("- %s\n" % w)
print("Checked %d URLs. Results appended to %s" % (len(new_records), SITE_CSV))
if __name__ == "__main__":
main()