Skill Tracer
Skills › Compound Engineering › ce-work › unit_workspace_ignored.py

unit_workspace_ignored.py

93 lines · skills/ce-work/scripts/unit_workspace_ignored.py at 3449230

1"""Metadata inventory of the canonical checkout's git-ignored entries.
2
3Verification runs in the canonical checkout; ignored state is never copied or
4restored. Two inventories taken before and after verification diff into a
5disclosure of changed, removed, and created ignored paths.
6"""
7
8from __future__ import annotations
9
10import os
11import stat
13from unit_workspace_state import Operational, git
16def ignored_paths(repo: str) -> set[str]:
17 raw = git(repo, "ls-files", "--others", "--ignored", "--exclude-standard", "-z", "--")
18 return set(filter(None, raw.decode("utf-8", "surrogateescape").split("\0")))
21def artifact_path(repo: str, rel: str) -> str:
22 repo = os.path.abspath(repo)
23 target = os.path.abspath(os.path.join(repo, rel))
24 if target == repo or os.path.commonpath([repo, target]) != repo:
25 raise Operational("BLOCKED", "ignored artifact path escaped canonical repository")
26 return target
29def _entry_type(mode: int) -> str:
30 if stat.S_ISLNK(mode):
31 return "symlink"
32 if stat.S_ISDIR(mode):
33 return "directory"
34 if stat.S_ISREG(mode):
35 return "file"
36 return "other"
39def inventory_ignored_state(repo: str) -> dict[str, tuple]:
40 """lstat every ignored entry; an entry that cannot be inspected is recorded, not refused."""
41 repo = os.path.abspath(repo)
42 inventory: dict[str, tuple] = {}
43 for rel in ignored_paths(repo):
44 try:
45 entry = os.lstat(artifact_path(repo, rel))
46 except OSError:
47 inventory[rel] = ("uninspectable",)
48 continue
49 inventory[rel] = (
50 _entry_type(entry.st_mode),
51 entry.st_size,
52 entry.st_mtime_ns,
53 entry.st_ino,
54 entry.st_dev,
55 entry.st_nlink,
56 stat.S_IMODE(entry.st_mode),
57 entry.st_ctime_ns,
58 )
59 return inventory
62def _comparable(record: tuple) -> tuple:
63 # Windows ctime is creation time, so it cannot signal an in-place mutation there.
64 return record if os.name != "nt" else record[:-1]
67def diff_ignored_state(before: dict[str, tuple], after: dict[str, tuple], sample_limit: int = 20) -> dict:
68 changed: list[str] = []
69 uninspectable = 0
70 for rel in before.keys() & after.keys():
71 old, new = before[rel], after[rel]
72 if old[0] == "uninspectable" or new[0] == "uninspectable":
73 uninspectable += 1
74 elif _comparable(old) != _comparable(new):
75 changed.append(rel)
76 removed = sorted(before.keys() - after.keys())
77 created = sorted(after.keys() - before.keys())
78 changed.sort()
79 return {
80 "before": len(before),
81 "after": len(after),
82 "changed": len(changed),
83 "removed": len(removed),
84 "created": len(created),
85 "uninspectable": uninspectable,
86 "sample": {
87 "changed": changed[:sample_limit],
88 "removed": removed[:sample_limit],
89 "created": created[:sample_limit],
90 },
91 "sample_limit": sample_limit,
92 "restored": False,
93 }