fix(kvb-check-gate): skip parent journal rows whose text cannot be encoded
A row with a lone surrogate in gate-parents.jsonl, which sandboxed agents can write, raised UnicodeEncodeError at the TEMP table insert and crashed every commit-msg, pre-push and pre-merge-commit hook and the hub gate view. opened() now keeps only rows whose repo, branch and parent encode; the test fails without the change. 🎯 Fix: 97 % 💥 Break: 2 % 💸 Cost: 100 % ⏱️ Time: 100 % 🛡️ Security: 100 % 🏆 Score: 95 🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 % Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c8b2e586de
commit
9f34768497
2 files changed
+17
-1
No files matched your search
@@ -184,6 +184,16 @@ def journal(origin: Path, kept_dir: Path, kept: Path) -> list[dict]:
|
|||||||
return list(merged.values())
|
return list(merged.values())
|
||||||
|
|
||||||
|
|
||||||
|
def encodable(value: object) -> bool:
|
||||||
|
if not isinstance(value, str):
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
value.encode()
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def journal_lock(path: Path) -> contextlib.AbstractContextManager:
|
def journal_lock(path: Path) -> contextlib.AbstractContextManager:
|
||||||
return watch.nofollow.locked(watch.ROOT, path.with_suffix(".lock"), FILE_MODE, None)
|
return watch.nofollow.locked(watch.ROOT, path.with_suffix(".lock"), FILE_MODE, None)
|
||||||
|
|
||||||
@@ -262,7 +272,7 @@ def opened(path: Path, write: bool, kept_dir: Path) -> sqlite3.Connection:
|
|||||||
db.executemany("INSERT INTO recorded VALUES (?, ?, ?)", [
|
db.executemany("INSERT INTO recorded VALUES (?, ?, ?)", [
|
||||||
(row["repo"], row["branch"], row["parent"])
|
(row["repo"], row["branch"], row["parent"])
|
||||||
for row in journal(watch.PARENTS, kept_dir, kept_dir / watch.KEPT_PARENTS.name)
|
for row in journal(watch.PARENTS, kept_dir, kept_dir / watch.KEPT_PARENTS.name)
|
||||||
if all(isinstance(row.get(key), str) for key in ("repo", "branch", "parent")) and row["branch"] != row["parent"]
|
if all(encodable(row.get(key)) for key in ("repo", "branch", "parent")) and row["branch"] != row["parent"]
|
||||||
])
|
])
|
||||||
except BaseException:
|
except BaseException:
|
||||||
db.close()
|
db.close()
|
||||||
|
|||||||
@@ -139,6 +139,12 @@ class TestGates(Scratch):
|
|||||||
check = self.gates("receipt", "check", "--repo", repo, "--tree", tree, "--level", "test")
|
check = self.gates("receipt", "check", "--repo", repo, "--tree", tree, "--level", "test")
|
||||||
self.assertEqual((check.returncode, check.stderr), (0, ""))
|
self.assertEqual((check.returncode, check.stderr), (0, ""))
|
||||||
|
|
||||||
|
def test_a_parent_row_with_a_lone_surrogate_is_skipped_and_the_others_still_count(self):
|
||||||
|
self.council.mkdir(exist_ok=True)
|
||||||
|
rows = [{"id": "bad", "repo": str(self.repo), "branch": "main", "parent": "\ud800"}, {"id": "good", "repo": str(self.repo), "branch": "main", "parent": "feature"}]
|
||||||
|
(self.council / "gate-parents.jsonl").write_text("".join(json.dumps(row) + "\n" for row in rows), encoding="utf-8")
|
||||||
|
self.assertEqual(self.parents("main"), [{"parent": "feature", "source": "recorded"}])
|
||||||
|
|
||||||
def test_a_receipt_older_than_30_days_neither_counts_nor_survives_the_next_write(self):
|
def test_a_receipt_older_than_30_days_neither_counts_nor_survives_the_next_write(self):
|
||||||
repo, tree = str(self.repo), self.out("rev-parse", "HEAD^{tree}")
|
repo, tree = str(self.repo), self.out("rev-parse", "HEAD^{tree}")
|
||||||
old = {"id": "old", "repo": repo, "tree": tree, "level": "test", "gpu": "false", "at": time.time() - 31 * 86400}
|
old = {"id": "old", "repo": repo, "tree": tree, "level": "test", "gpu": "false", "at": time.time() - 31 * 86400}
|
||||||
|
|||||||
Reference in new issue
Block a user