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.
87 lines
3.0 KiB
Python
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()
|