fix(v2): enforce hardened M0 contracts
This commit is contained in:
+45
-18
@@ -14,12 +14,37 @@ LEGACY_PATHS = (
|
||||
Path("backend/tests"),
|
||||
Path("frontend/src/api/client.ts"),
|
||||
)
|
||||
SCAN_ROOTS = (Path("backend/src"), Path("frontend/src"), Path("openapi"))
|
||||
TEXT_SUFFIXES = {".json", ".py", ".ts", ".tsx", ".yaml", ".yml"}
|
||||
SCAN_ROOTS = (
|
||||
Path("backend/src"),
|
||||
Path("frontend/src"),
|
||||
Path("openapi"),
|
||||
Path("config"),
|
||||
Path(".github/workflows"),
|
||||
)
|
||||
SCAN_FILES = (
|
||||
Path("backend/Dockerfile"),
|
||||
Path("frontend/Dockerfile"),
|
||||
Path("frontend/nginx.conf"),
|
||||
Path("frontend/vite.config.ts"),
|
||||
Path("docker-compose.yml"),
|
||||
Path("compose.yaml"),
|
||||
Path("backend/pyproject.toml"),
|
||||
Path("frontend/package.json"),
|
||||
)
|
||||
TEXT_SUFFIXES = {".json", ".py", ".toml", ".ts", ".tsx", ".yaml", ".yml"}
|
||||
FORBIDDEN = (
|
||||
re.compile(r"/api/v1(?:/|\b)"),
|
||||
re.compile(r"\blegacy_(?:reader|importer?|converter?)\b", re.IGNORECASE),
|
||||
re.compile(r"\btimestamp_directory\b", re.IGNORECASE),
|
||||
re.compile(
|
||||
r"\b(?:legacy|v1)[_-]?(?:database|db|payload|backup)?[_-]?(?:reader|importer?|converter?)\b",
|
||||
re.IGNORECASE,
|
||||
),
|
||||
re.compile(
|
||||
r"\b(?:read|open|load|import|convert)[_-]?(?:legacy|v1)[_-]?(?:database|db|payload|backup)\b",
|
||||
re.IGNORECASE,
|
||||
),
|
||||
re.compile(r"\bbackup_tool\.(?:db|sqlite3?)\b", re.IGNORECASE),
|
||||
re.compile(r"%Y-%m-%d_%H%M%S"),
|
||||
re.compile(r"\btimestamp[_-]?(?:directory|parser)\b", re.IGNORECASE),
|
||||
re.compile(r"\b(?:app\.main|backup\.engine)\b"),
|
||||
)
|
||||
|
||||
@@ -30,23 +55,25 @@ def scan(root: Path) -> list[str]:
|
||||
if (root / relative).exists():
|
||||
findings.append(f"legacy path exists: {relative}")
|
||||
|
||||
candidates = {root / relative for relative in SCAN_FILES if (root / relative).is_file()}
|
||||
for relative_root in SCAN_ROOTS:
|
||||
scan_root = root / relative_root
|
||||
if not scan_root.exists():
|
||||
if scan_root.exists():
|
||||
candidates.update(
|
||||
path
|
||||
for path in scan_root.rglob("*")
|
||||
if path.is_file() and path.suffix in TEXT_SUFFIXES
|
||||
)
|
||||
|
||||
for path in sorted(candidates):
|
||||
try:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
except (OSError, UnicodeError) as error:
|
||||
findings.append(f"cannot read {path.relative_to(root)}: {error}")
|
||||
continue
|
||||
for path in sorted(scan_root.rglob("*")):
|
||||
if not path.is_file() or path.suffix not in TEXT_SUFFIXES:
|
||||
continue
|
||||
try:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
except (OSError, UnicodeError) as error:
|
||||
findings.append(f"cannot read {path.relative_to(root)}: {error}")
|
||||
continue
|
||||
for pattern in FORBIDDEN:
|
||||
if pattern.search(text):
|
||||
findings.append(
|
||||
f"forbidden symbol {pattern.pattern!r}: {path.relative_to(root)}"
|
||||
)
|
||||
for pattern in FORBIDDEN:
|
||||
if pattern.search(text):
|
||||
findings.append(f"forbidden symbol {pattern.pattern!r}: {path.relative_to(root)}")
|
||||
return findings
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user