v0.1.0-pre.011-fix-01
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# file: scripts/audit_khadhroony_workspace_rules.py
|
||||
# version: 4
|
||||
# version: 5
|
||||
|
||||
"""Audit mechanically verifiable rules specific to khadhroony-bot3."""
|
||||
|
||||
@@ -66,16 +66,16 @@ def audit_tracing(root: pathlib.Path) -> list[Violation]:
|
||||
if not crate_root.exists():
|
||||
crate_root = crate / "src/main.rs"
|
||||
relative_root = crate_root.relative_to(root).as_posix()
|
||||
if not crate_root.exists() or "pub(crate) use crate::constants::TRACING_TARGET;" not in crate_root.read_text(encoding="utf-8"):
|
||||
if not crate_root.exists() or "pub(crate) use self::constants::TRACING_TARGET;" not in crate_root.read_text(encoding="utf-8"):
|
||||
violations.append(Violation("KH_TRACE002", relative_root, 1, "missing crate-root TRACING_TARGET re-export"))
|
||||
for path in crate.glob("src/**/*.rs"):
|
||||
relative = path.relative_to(root).as_posix()
|
||||
for index, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
|
||||
if "khbot." in line:
|
||||
violations.append(Violation("KH_TRACE003", relative, index, "legacy khbot tracing target is forbidden"))
|
||||
if "crate::constants::TRACING_TARGET" in line and path.name not in {"lib.rs", "main.rs"}:
|
||||
if "crate::constants::TRACING_TARGET" in line:
|
||||
violations.append(Violation("KH_TRACE004", relative, index, "use crate::TRACING_TARGET outside crate root"))
|
||||
if re.search(r"(?:crate::[A-Za-z0-9_]+::|(?<!crate::))TRACING_TARGET", line) and "const TRACING_TARGET" not in line and "use crate::constants::TRACING_TARGET" not in line:
|
||||
if re.search(r"(?:crate::[A-Za-z0-9_]+::|(?<!crate::))TRACING_TARGET", line) and "const TRACING_TARGET" not in line and "use self::constants::TRACING_TARGET" not in line:
|
||||
if "crate::TRACING_TARGET" not in line:
|
||||
violations.append(Violation("KH_TRACE005", relative, index, "non-canonical TRACING_TARGET path"))
|
||||
return violations
|
||||
@@ -90,18 +90,17 @@ def audit_kb_lib_tracing(root: pathlib.Path) -> list[Violation]:
|
||||
if not crate_root.exists():
|
||||
return violations
|
||||
root_text = crate_root.read_text(encoding="utf-8")
|
||||
aliases = set()
|
||||
alias_pattern = re.compile(
|
||||
r"^pub\(crate\) use crate::[A-Za-z0-9_:]+::"
|
||||
r"([A-Z][A-Z0-9_]+)(?: as ([A-Z][A-Z0-9_]+))?;$",
|
||||
re_exports = set()
|
||||
re_export_pattern = re.compile(
|
||||
r"^pub\(crate\) use self::[A-Za-z0-9_:]+::"
|
||||
r"([A-Z][A-Z0-9_]+);$",
|
||||
re.MULTILINE,
|
||||
)
|
||||
for match in alias_pattern.finditer(root_text):
|
||||
exported_name = match.group(2) if match.group(2) is not None else match.group(1)
|
||||
if exported_name.endswith("_TRACING_TARGET"):
|
||||
aliases.add(exported_name)
|
||||
for match in re_export_pattern.finditer(root_text):
|
||||
if match.group(1).endswith("_TRACING_TARGET"):
|
||||
re_exports.add(match.group(1))
|
||||
target_pattern = re.compile(
|
||||
r'^pub\(crate\) const TRACING_TARGET: &str = "([^"]+)";$',
|
||||
r'^pub\(crate\) const ([A-Z][A-Z0-9_]+_TRACING_TARGET): &str = "([^"]+)";$',
|
||||
re.MULTILINE,
|
||||
)
|
||||
for constants in sorted((crate / "src").rglob("constants.rs")):
|
||||
@@ -112,13 +111,13 @@ def audit_kb_lib_tracing(root: pathlib.Path) -> list[Violation]:
|
||||
component = constants.parent.relative_to(crate / "src")
|
||||
expected = "kb-lib." + ".".join(component.parts)
|
||||
relative = constants.relative_to(root).as_posix()
|
||||
if match.group(1) != expected:
|
||||
if match.group(2) != expected:
|
||||
violations.append(
|
||||
Violation(
|
||||
"KH_TRACE101",
|
||||
relative,
|
||||
text[: match.start()].count("\n") + 1,
|
||||
f"expected hierarchical target `{expected}`",
|
||||
f"`{match.group(1)}` must use hierarchical target `{expected}`",
|
||||
)
|
||||
)
|
||||
macro_pattern = re.compile(
|
||||
@@ -138,7 +137,7 @@ def audit_kb_lib_tracing(root: pathlib.Path) -> list[Violation]:
|
||||
)
|
||||
)
|
||||
for match in macro_pattern.finditer(text):
|
||||
if match.group(1) not in aliases:
|
||||
if match.group(1) not in re_exports:
|
||||
violations.append(
|
||||
Violation(
|
||||
"KH_TRACE103",
|
||||
@@ -164,6 +163,85 @@ def audit_solana_types(root: pathlib.Path) -> list[Violation]:
|
||||
return violations
|
||||
|
||||
|
||||
def audit_kb_lib_symbol_prefixes(root: pathlib.Path) -> list[Violation]:
|
||||
"""Require stable family prefixes on non-method kb-lib declarations."""
|
||||
|
||||
violations: list[Violation] = []
|
||||
families = {
|
||||
"decoder": ("DC_", "Dc", "decoder_"),
|
||||
"executor": ("EX_", "Ex", "executor_"),
|
||||
"materializer": ("MT_", "Mt", "materializer_"),
|
||||
"model": ("MD_", "Md", "model_"),
|
||||
}
|
||||
declaration = re.compile(
|
||||
r"^pub(?:\(crate\))?\s+"
|
||||
r"(?:(?:async|const|unsafe)\s+)*"
|
||||
r"(const|static|type|struct|enum|trait|union|fn)\s+"
|
||||
r"([A-Za-z_][A-Za-z0-9_]*)"
|
||||
)
|
||||
for family, prefixes in families.items():
|
||||
family_root = root / "kb-lib/src" / family
|
||||
paths = [family_root.with_suffix(".rs")]
|
||||
if family_root.is_dir():
|
||||
paths.extend(sorted(family_root.rglob("*.rs")))
|
||||
for path in paths:
|
||||
if not path.is_file():
|
||||
continue
|
||||
relative = path.relative_to(root).as_posix()
|
||||
for index, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
|
||||
match = declaration.match(line)
|
||||
if match is None:
|
||||
continue
|
||||
kind = match.group(1)
|
||||
name = match.group(2)
|
||||
expected = prefixes[0] if kind in {"const", "static"} else prefixes[2] if kind == "fn" else prefixes[1]
|
||||
if not name.startswith(expected):
|
||||
violations.append(
|
||||
Violation(
|
||||
"KH_NAME001",
|
||||
relative,
|
||||
index,
|
||||
f"`{name}` must start with `{expected}` for the {family} family",
|
||||
)
|
||||
)
|
||||
return violations
|
||||
|
||||
|
||||
def audit_token2022_naming(root: pathlib.Path) -> list[Violation]:
|
||||
"""Reject obsolete internal Token2022 spellings while preserving legacy evidence."""
|
||||
|
||||
violations: list[Violation] = []
|
||||
obsolete = (
|
||||
"kb-lib.decoder.spl.token_2022",
|
||||
"docs/SPL_TOKEN_2022_",
|
||||
"`core_spl_token_2022",
|
||||
"`spl_token_2022`",
|
||||
)
|
||||
candidates = [
|
||||
root / "README.md",
|
||||
root / "ROADMAP.md",
|
||||
root / "RULES.md",
|
||||
root / "kb-lib/README.md",
|
||||
]
|
||||
candidates.extend(sorted((root / "docs").glob("*.md")))
|
||||
for path in candidates:
|
||||
if not path.is_file() or path.name == "RUST_WORKSPACE_RULE_AUDIT.md":
|
||||
continue
|
||||
relative = path.relative_to(root).as_posix()
|
||||
for index, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
|
||||
for spelling in obsolete:
|
||||
if spelling in line:
|
||||
violations.append(
|
||||
Violation(
|
||||
"KH_NAME002",
|
||||
relative,
|
||||
index,
|
||||
f"obsolete internal Token2022 spelling `{spelling}`",
|
||||
)
|
||||
)
|
||||
return violations
|
||||
|
||||
|
||||
def audit_wincode_resolution(root: pathlib.Path) -> list[Violation]:
|
||||
"""Require the currently compatible Solana wincode dependency family."""
|
||||
|
||||
@@ -238,6 +316,8 @@ def main() -> int:
|
||||
violations = (
|
||||
audit_tracing(root)
|
||||
+ audit_kb_lib_tracing(root)
|
||||
+ audit_kb_lib_symbol_prefixes(root)
|
||||
+ audit_token2022_naming(root)
|
||||
+ audit_solana_types(root)
|
||||
+ audit_wincode_resolution(root)
|
||||
)
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
#!/usr/bin/env python3
|
||||
# file: scripts/audit_rust_export_completeness.py
|
||||
# version: 2
|
||||
# version: 3
|
||||
|
||||
"""Report missing crate-root exports and replaceable long internal paths.
|
||||
"""Audit missing crate-root exports and replaceable long internal paths.
|
||||
|
||||
The audit distinguishes declarations from their crate-root aliases. Crate-root
|
||||
files are excluded from long-path findings because re-export declarations must
|
||||
name their source module. Findings remain advisory until the affected crate is
|
||||
compiled.
|
||||
The audit resolves explicit transitive `self::` re-exports through private
|
||||
module façades. Crate-root files are excluded from long-path findings because
|
||||
re-export declarations must name their source module.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -86,17 +85,35 @@ def declaration_candidates(crate: pathlib.Path, path: pathlib.Path) -> list[Decl
|
||||
return found
|
||||
|
||||
|
||||
def root_exports(crate_root: pathlib.Path) -> dict[tuple[str, str], str]:
|
||||
"""Return source module/name pairs and their crate-root aliases."""
|
||||
def transitive_root_exports(crate: pathlib.Path) -> dict[tuple[str, str], str]:
|
||||
"""Return declarations that reach the crate root through explicit re-exports."""
|
||||
|
||||
exports: dict[tuple[str, str], str] = {}
|
||||
edges: dict[tuple[str, str], set[tuple[str, str]]] = {}
|
||||
pattern = re.compile(
|
||||
r"^\s*pub(?:\(crate\))?\s+use\s+crate::([A-Za-z_][A-Za-z0-9_:]*)::([A-Za-z_][A-Za-z0-9_]*)(?:\s+as\s+([A-Za-z_][A-Za-z0-9_]*))?\s*;",
|
||||
r"^\s*pub(?:\(crate\))?\s+use\s+self::([A-Za-z_][A-Za-z0-9_:]*)::([A-Za-z_][A-Za-z0-9_]*)\s*;",
|
||||
re.MULTILINE,
|
||||
)
|
||||
for match in pattern.finditer(crate_root.read_text(encoding="utf-8")):
|
||||
exports[(match.group(1), match.group(2))] = match.group(3) or match.group(2)
|
||||
return exports
|
||||
for path in sorted((crate / "src").rglob("*.rs")):
|
||||
current = "" if path.name in {"lib.rs", "main.rs"} else module_path(crate, path)
|
||||
for match in pattern.finditer(path.read_text(encoding="utf-8")):
|
||||
source_module = "::".join(part for part in (current, match.group(1)) if part)
|
||||
source = (source_module, match.group(2))
|
||||
destination = (current, match.group(2))
|
||||
edges.setdefault(source, set()).add(destination)
|
||||
resolved: dict[tuple[str, str], str] = {}
|
||||
for source in edges:
|
||||
pending = [source]
|
||||
visited: set[tuple[str, str]] = set()
|
||||
while pending:
|
||||
candidate = pending.pop()
|
||||
if candidate in visited:
|
||||
continue
|
||||
visited.add(candidate)
|
||||
if candidate[0] == "":
|
||||
resolved[source] = candidate[1]
|
||||
break
|
||||
pending.extend(edges.get(candidate, set()))
|
||||
return resolved
|
||||
|
||||
|
||||
def audit_crate(workspace: pathlib.Path, crate: pathlib.Path) -> list[Candidate]:
|
||||
@@ -105,7 +122,7 @@ def audit_crate(workspace: pathlib.Path, crate: pathlib.Path) -> list[Candidate]
|
||||
crate_root = crate / "src/lib.rs"
|
||||
if not crate_root.is_file():
|
||||
crate_root = crate / "src/main.rs"
|
||||
exports = root_exports(crate_root)
|
||||
exports = transitive_root_exports(crate)
|
||||
candidates: list[Candidate] = []
|
||||
declarations: dict[tuple[str, str], Declaration] = {}
|
||||
for path in sorted((crate / "src").rglob("*.rs")):
|
||||
@@ -114,7 +131,10 @@ def audit_crate(workspace: pathlib.Path, crate: pathlib.Path) -> list[Candidate]
|
||||
for declaration in declaration_candidates(crate, path):
|
||||
key = (declaration.module, declaration.name)
|
||||
declarations[key] = declaration
|
||||
if key not in exports:
|
||||
is_migration_scaffold = declaration.name.endswith(
|
||||
("_LEGACY_CRATE", "_MIGRATION_BOUNDARIES", "_MIGRATION_STATUS")
|
||||
)
|
||||
if key not in exports and not is_migration_scaffold:
|
||||
relative = path.relative_to(workspace).as_posix()
|
||||
candidates.append(
|
||||
Candidate(
|
||||
@@ -149,10 +169,11 @@ def audit_crate(workspace: pathlib.Path, crate: pathlib.Path) -> list[Candidate]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
"""Run the advisory export-completeness audit."""
|
||||
"""Run the export-completeness audit."""
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", default=".")
|
||||
parser.add_argument("--report-only", action="store_true")
|
||||
parser.add_argument("--summary-only", action="store_true")
|
||||
arguments = parser.parse_args()
|
||||
workspace = pathlib.Path(arguments.root).resolve()
|
||||
@@ -167,7 +188,7 @@ def main() -> int:
|
||||
if not arguments.summary_only:
|
||||
for item in candidates:
|
||||
sys.stdout.write(f"{item.code} {item.path}:{item.line}: {item.message}\n")
|
||||
return 0
|
||||
return 0 if arguments.report_only or not candidates else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# file: scripts/audit_rust_general_rules.py
|
||||
# version: 4
|
||||
# version: 5
|
||||
|
||||
"""Audit mechanically verifiable Rust rules shared by all Rust projects."""
|
||||
|
||||
@@ -45,6 +45,11 @@ def audit_file(root: pathlib.Path, path: pathlib.Path) -> list[Violation]:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
lines = text.splitlines()
|
||||
violations: list[Violation] = []
|
||||
local_modules = {
|
||||
match.group(1)
|
||||
for line in lines
|
||||
if (match := re.fullmatch(r"\s*mod\s+([A-Za-z_][A-Za-z0-9_]*)\s*;", line)) is not None
|
||||
}
|
||||
expected_header = f"// file: {relative}"
|
||||
if not lines or lines[0] != expected_header:
|
||||
violations.append(Violation("RUST001", relative, 1, f"expected `{expected_header}`"))
|
||||
@@ -54,16 +59,13 @@ def audit_file(root: pathlib.Path, path: pathlib.Path) -> list[Violation]:
|
||||
violations.append(Violation("RUST003", relative, max(len(lines), 1), "file must end with exactly one newline"))
|
||||
for index, line in enumerate(lines, 1):
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("pub mod ") and (
|
||||
relative == "kb-lib/src/lib.rs" and stripped == "pub mod materializer;"
|
||||
or relative.startswith("kb-lib/src/materializer")
|
||||
):
|
||||
if stripped.startswith("pub mod "):
|
||||
violations.append(
|
||||
Violation(
|
||||
"RUST014",
|
||||
relative,
|
||||
index,
|
||||
"consolidated materializer modules must remain private",
|
||||
"modules must remain private and APIs must use explicit re-exports",
|
||||
)
|
||||
)
|
||||
if re.match(r"^(?:pub(?:\(crate\))?\s+)?use\s+", stripped) is None:
|
||||
@@ -78,34 +80,62 @@ def audit_file(root: pathlib.Path, path: pathlib.Path) -> list[Violation]:
|
||||
if "{" in stripped or "}" in stripped:
|
||||
code = "RUST011" if is_export else "RUST013"
|
||||
violations.append(Violation(code, relative, index, "grouped use declarations are forbidden"))
|
||||
if path.name in {"lib.rs", "main.rs"}:
|
||||
for attribute in ("#![warn(missing_docs)]", "#![deny(unreachable_pub)]", "#![forbid(unsafe_code)]"):
|
||||
if attribute not in lines[:20]:
|
||||
violations.append(Violation("RUST020", relative, 1, f"missing `{attribute}`"))
|
||||
previous_export_line: int | None = None
|
||||
previous_export_visibility: str | None = None
|
||||
for index, line in enumerate(lines, 1):
|
||||
if not (line.startswith("pub use ") or line.startswith("pub(crate) use ")):
|
||||
continue
|
||||
previous = lines[index - 2].strip() if index >= 2 else ""
|
||||
if not previous.startswith("///"):
|
||||
violations.append(Violation("RUST021", relative, index, "crate-root re-export requires adjacent rustdoc"))
|
||||
internal_re_export = re.match(
|
||||
r"^pub(?:\(crate\))?\s+use\s+(?:crate::)?([A-Za-z_][A-Za-z0-9_]*)::",
|
||||
stripped,
|
||||
)
|
||||
if is_export and (
|
||||
re.match(r"^pub(?:\(crate\))?\s+use\s+crate::", stripped)
|
||||
or (
|
||||
internal_re_export is not None
|
||||
and internal_re_export.group(1) in local_modules
|
||||
)
|
||||
):
|
||||
violations.append(Violation("RUST015", relative, index, "internal re-export must start with `self::`"))
|
||||
if is_export and re.search(r"\s+as\s+[A-Za-z_][A-Za-z0-9_]*\s*;", stripped):
|
||||
violations.append(Violation("RUST016", relative, index, "re-export aliases are forbidden"))
|
||||
previous_export_line: int | None = None
|
||||
previous_export_visibility: str | None = None
|
||||
blank_since_export = False
|
||||
for index, line in enumerate(lines, 1):
|
||||
if not line.strip():
|
||||
if previous_export_line is not None:
|
||||
blank_since_export = True
|
||||
continue
|
||||
if line.startswith("///"):
|
||||
continue
|
||||
if line.startswith("pub use ") or line.startswith("pub(crate) use "):
|
||||
visibility = "pub(crate)" if line.startswith("pub(crate) use ") else "pub"
|
||||
if (
|
||||
previous_export_line is not None
|
||||
and previous_export_visibility == visibility
|
||||
and any(not candidate.strip() for candidate in lines[previous_export_line:index - 1])
|
||||
and blank_since_export
|
||||
):
|
||||
violations.append(
|
||||
Violation(
|
||||
"RUST022",
|
||||
relative,
|
||||
index,
|
||||
"crate-root re-export block contains an empty line",
|
||||
"re-export block contains an empty line",
|
||||
)
|
||||
)
|
||||
previous_export_line = index
|
||||
previous_export_visibility = visibility
|
||||
blank_since_export = False
|
||||
continue
|
||||
previous_export_line = None
|
||||
previous_export_visibility = None
|
||||
blank_since_export = False
|
||||
if path.name in {"lib.rs", "main.rs"}:
|
||||
for attribute in ("#![warn(missing_docs)]", "#![deny(unreachable_pub)]", "#![forbid(unsafe_code)]"):
|
||||
if attribute not in lines[:20]:
|
||||
violations.append(Violation("RUST020", relative, 1, f"missing `{attribute}`"))
|
||||
for index, line in enumerate(lines, 1):
|
||||
if not (line.startswith("pub use ") or line.startswith("pub(crate) use ")):
|
||||
continue
|
||||
previous = lines[index - 2].strip() if index >= 2 else ""
|
||||
if not previous.startswith("///"):
|
||||
violations.append(Violation("RUST021", relative, index, "crate-root re-export requires adjacent rustdoc"))
|
||||
return violations
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
# file: scripts/audit_rust_workspace_rules.py
|
||||
# version: 2
|
||||
# version: 3
|
||||
|
||||
"""Run both the general Rust and khadhroony-specific workspace audits."""
|
||||
"""Run the general, export-completeness and khadhroony workspace audits."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -12,7 +12,7 @@ import subprocess
|
||||
|
||||
|
||||
def main() -> int:
|
||||
"""Run both independent audit scripts."""
|
||||
"""Run all independent audit scripts."""
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", default=".")
|
||||
@@ -21,6 +21,7 @@ def main() -> int:
|
||||
script_dir = pathlib.Path(__file__).resolve().parent
|
||||
commands = [
|
||||
["python3", str(script_dir / "audit_rust_general_rules.py"), "--root", arguments.root],
|
||||
["python3", str(script_dir / "audit_rust_export_completeness.py"), "--root", arguments.root],
|
||||
["python3", str(script_dir / "audit_khadhroony_workspace_rules.py"), "--root", arguments.root],
|
||||
]
|
||||
if arguments.report_only:
|
||||
|
||||
Reference in New Issue
Block a user