Files
management/analysis/scripts/inv_timestamps.py
T
Thore Cimbal 959bf882e9 analysis: resolve SHA references and measure timestamp anonymisation
Two checks the human asked for after Phase 1: whether the mapping list
from the 2026-08-07 history rewrite still resolves older references, and
how far the anonymisation rule actually reaches.

sha_refs.tsv verifies all 251 mapping rows against the repos and
resolves every SHA cited in a management doc.
timestamp_anonymisation.tsv separates the project's own commits from
upstream fork history before counting non-compliant timestamps.
2026-08-10 12:00:00 +00:00

87 lines
3.0 KiB
Python

"""Measure the 2026-08-07 timestamp anonymisation -> timestamp_anonymisation.tsv.
CLAUDE.md:116-134 requires author *and* committer date of every commit in
the group to be 12:00:00 UTC, so that personal working hours cannot be
read out of the history. This script measures how far that holds.
Upstream fork history is separated out: threadnet-call and ThreadNet-Web
carry thousands of commits by Element/Matrix contributors that were never
this project's to rewrite. Counting those as a leak would be dishonest,
so every row carries the author identity and an `own_identity` flag.
Own identities are listed explicitly rather than guessed, and were read
off the actual author list of the six repos.
"""
import re
from collections import Counter
from common import cell, git, repos, write_tsv
# The one person behind this project, in every spelling that appears in
# the six repos' author fields -- including the malformed one.
OWN_IDENTITIES = {
"thore cimbal <cfx@riot.8shield.net>",
"scrublord macbad <scrublord@mac.bad>",
"scrublordmcbad <gamemaster@axion1337.de>",
"sorb <gamemaster@axion1337.de>",
"sorb <cfxqriot.8shield.net>",
}
# Agent-authored commits: not a person's working hours, but they were made
# during one, so they are reported separately rather than ignored.
AGENT_IDENTITIES = {"claude <noreply@anthropic.com>"}
ANONYMISED = "12:00:00"
def classify(ident):
low = ident.lower()
if low in OWN_IDENTITIES:
return "own"
if low in AGENT_IDENTITIES:
return "agent"
return "upstream-or-bot"
def main():
rows = []
for name, repo in repos():
# Author time forced to UTC: the rule is stated in UTC, and reading
# it in local time silently classifies every commit as non-compliant.
log = git(repo, "log", "--all", "--date=format-local:%H:%M:%S",
"--format=%H%x09%ad%x09%an <%ae>", env_tz="UTC")
main_shas = {
line.strip()
for line in git(repo, "log", "--format=%H", "origin/main").splitlines()
if line.strip()
}
buckets = Counter()
for line in log.splitlines():
if not line.strip():
continue
sha, when, ident = line.split("\t", 2)
anon = when == ANONYMISED
buckets[(classify(ident), anon, sha in main_shas, ident)] += 1
for (who, anon, on_main, ident), n in sorted(buckets.items()):
rows.append([name, who, ident,
"anonymised" if anon else "real-clock-time",
"main" if on_main else "side-branch-only", n])
rows.sort(key=lambda r: (r[0], r[1], r[3], r[4], r[2]))
write_tsv(
"timestamp_anonymisation.tsv",
["repo", "identity_class", "author", "timestamp_state", "reachable_from",
"commits"],
rows,
)
leak = sum(int(r[5]) for r in rows
if r[1] in ("own", "agent") and r[3] == "real-clock-time")
print(f" commits by own/agent identities still carrying real clock time: {leak}")
if __name__ == "__main__":
main()