-
Notifications
You must be signed in to change notification settings - Fork 0
v0.5.0a1 The Sentinel: plugin-enabled scanning, adaptive engine hardening, and preflight green #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -71,6 +71,8 @@ | |||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| if TYPE_CHECKING: | ||||||||||||||||||
| from importlib.metadata import EntryPoint | ||||||||||||||||||
|
|
||||||||||||||||||
| from zenzic.models.vsm import VSM, Route | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
|
|
@@ -684,10 +686,7 @@ def _to_canonical_url(href: str) -> str | None: | |||||||||||||||||
| # ─── Plugin discovery ───────────────────────────────────────────────────────── | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| from dataclasses import dataclass as _dc # noqa: E402 — module-level, after all classes | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| @_dc | ||||||||||||||||||
| @dataclass(slots=True) | ||||||||||||||||||
| class PluginRuleInfo: | ||||||||||||||||||
| """Metadata about a discovered plugin rule. | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
@@ -705,6 +704,118 @@ class PluginRuleInfo: | |||||||||||||||||
| origin: str | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| class PluginRegistry: | ||||||||||||||||||
| """Registry wrapper around ``importlib.metadata`` entry-points. | ||||||||||||||||||
|
|
||||||||||||||||||
| Provides read-only discovery for the CLI and explicit rule loading for the | ||||||||||||||||||
| scanner. Discovery is best-effort; loading configured plugins is strict. | ||||||||||||||||||
| """ | ||||||||||||||||||
|
|
||||||||||||||||||
| def __init__(self, group: str = "zenzic.rules") -> None: | ||||||||||||||||||
| self._group = group | ||||||||||||||||||
|
|
||||||||||||||||||
| def _entry_points(self) -> list[EntryPoint]: | ||||||||||||||||||
| """Return sorted entry-points for the configured group.""" | ||||||||||||||||||
| from importlib.metadata import entry_points | ||||||||||||||||||
|
|
||||||||||||||||||
| return sorted(entry_points(group=self._group), key=lambda ep: ep.name) | ||||||||||||||||||
|
|
||||||||||||||||||
| def list_rules(self) -> list[PluginRuleInfo]: | ||||||||||||||||||
| """Discover all plugin rules as metadata for CLI inspection.""" | ||||||||||||||||||
| results: list[PluginRuleInfo] = [] | ||||||||||||||||||
| for ep in self._entry_points(): | ||||||||||||||||||
| try: | ||||||||||||||||||
| cls = ep.load() | ||||||||||||||||||
| instance = cls() | ||||||||||||||||||
| if not isinstance(instance, BaseRule): | ||||||||||||||||||
| continue | ||||||||||||||||||
| except Exception: # noqa: BLE001 | ||||||||||||||||||
| continue | ||||||||||||||||||
| dist_name = ep.dist.name if ep.dist is not None else "zenzic" | ||||||||||||||||||
| results.append( | ||||||||||||||||||
| PluginRuleInfo( | ||||||||||||||||||
| rule_id=instance.rule_id, | ||||||||||||||||||
| class_name=f"{cls.__module__}.{cls.__qualname__}", | ||||||||||||||||||
| source=ep.name, | ||||||||||||||||||
| origin=dist_name, | ||||||||||||||||||
| ) | ||||||||||||||||||
| ) | ||||||||||||||||||
| if not any(r.source == "broken-links" for r in results): | ||||||||||||||||||
| results.append( | ||||||||||||||||||
| PluginRuleInfo( | ||||||||||||||||||
| rule_id=VSMBrokenLinkRule().rule_id, | ||||||||||||||||||
| class_name=f"{VSMBrokenLinkRule.__module__}.{VSMBrokenLinkRule.__qualname__}", | ||||||||||||||||||
| source="broken-links", | ||||||||||||||||||
| origin="zenzic", | ||||||||||||||||||
| ) | ||||||||||||||||||
| ) | ||||||||||||||||||
| # Keep ordering deterministic regardless of fallback insertion order. | ||||||||||||||||||
| results.sort(key=lambda r: r.source) | ||||||||||||||||||
| return results | ||||||||||||||||||
|
|
||||||||||||||||||
| def load_core_rules(self) -> list[BaseRule]: | ||||||||||||||||||
| """Load core rules registered by the ``zenzic`` distribution.""" | ||||||||||||||||||
| core_eps = [ | ||||||||||||||||||
| ep for ep in self._entry_points() if ep.dist is not None and ep.dist.name == "zenzic" | ||||||||||||||||||
| ] | ||||||||||||||||||
| loaded = [self._load_entry_point(ep) for ep in core_eps] | ||||||||||||||||||
| if not any(rule.rule_id == "Z001" for rule in loaded): | ||||||||||||||||||
| loaded.append(VSMBrokenLinkRule()) | ||||||||||||||||||
| return loaded | ||||||||||||||||||
|
|
||||||||||||||||||
| def load_selected_rules(self, plugin_ids: Sequence[str]) -> list[BaseRule]: | ||||||||||||||||||
| """Load only the configured plugin IDs from the entry-point group. | ||||||||||||||||||
|
|
||||||||||||||||||
| Args: | ||||||||||||||||||
| plugin_ids: Entry-point names declared in ``config.plugins``. | ||||||||||||||||||
|
|
||||||||||||||||||
| Raises: | ||||||||||||||||||
| PluginContractError: If a configured plugin is missing or invalid. | ||||||||||||||||||
| """ | ||||||||||||||||||
| from zenzic.core.exceptions import PluginContractError # deferred: avoid circular import | ||||||||||||||||||
|
|
||||||||||||||||||
| requested = [pid.strip() for pid in plugin_ids if pid.strip()] | ||||||||||||||||||
|
||||||||||||||||||
| requested = [pid.strip() for pid in plugin_ids if pid.strip()] | |
| requested: list[str] = [] | |
| seen: set[str] = set() | |
| for pid in plugin_ids: | |
| cleaned = pid.strip() | |
| if cleaned and cleaned not in seen: | |
| seen.add(cleaned) | |
| requested.append(cleaned) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Italian copy: "La linea 0.4.x e stata abbandonata; la stabilizzazione attiva e 0.5.x." is missing accents/grammar (e.g., "è stata", "è 0.5.x" / "è la 0.5.x"). Please correct to avoid typos in published docs.