Skip to content

maj - #188

Closed
traoreera wants to merge 2 commits into
mainfrom
fix-bug
Closed

maj#188
traoreera wants to merge 2 commits into
mainfrom
fix-bug

Conversation

@traoreera

@traoreera traoreera commented Apr 27, 2026 •

Copy link
Copy Markdown
Owner

Summary by Sourcery

Introduce v2 plugin structure and health validation while tightening sandbox security and CI linting.

New Features:

  • Add support for v2 plugin layout based on plugin.yaml and src/main.py manifests and entry points.
  • Expose authentication backend APIs and event/hook systems at the xcore package level for use by plugins and SDK decorators.
  • Add an AST security visitor with accessors on ASTScanner to support fine-grained unit tests.

Bug Fixes:

  • Ensure sandbox resource limits cover both memory and CPU and are properly exercised by tests.
  • Align security scanner error messages and tests for forbidden builtins, imports, and attribute access to prevent bypasses.
  • Enable sandbox subprocess ping checks and crash handling to detect non-responsive workers.

Enhancements:

  • Skip loading plugins whose required framework version is incompatible, logging a warning instead of failing the whole load.
  • Perform non-blocking AST scans during trusted plugin activation and log any security issues.
  • Instantiate EventBus, HookManager, and default service providers earlier for compatibility with decorators and legacy tests.
  • Relax strict_trusted plugin config default to allow trusted plugins without strict enforcement.
  • Improve logging, error messages, and minor formatting in runtime, sandbox, IPC, and supervisor components.

Build:

  • Add lint-check, pre-commit, and validate-plugins make targets and wire CI to use them along with xcore plugin health validation.
  • Add black, isort, and flake8 as development dependencies and configure pre-commit hooks for code style and security checks.

CI:

  • Update PR workflow to validate app-based plugins using plugin.yaml and src/ layout instead of legacy config.yaml/run.py structure.
  • Switch CI lint job to run non-mutating lint checks instead of auto-fixing code.

Tests:

  • Update and extend unit tests for sandbox worker limits, security scanners, AST bypass detection, and plugin fixtures to match the new plugin v2 structure.
  • Add a reusable temporary directory fixture for tests that need isolated filesystem state.

Chores:

  • Introduce a default ServiceContainer provider list constant for backward compatibility with older tests.

@sourcery-ai

sourcery-ai Bot commented Apr 27, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer's Guide

This PR introduces v2 plugin structure support and stricter security/runtime behavior: manifests now return compatibility information, AST security scanning gains a reusable visitor and clearer messages, sandbox resource limits are generalized, plugin loading respects framework version constraints, and CI/linting/pre-commit tooling is modernized to enforce the new conventions.

Sequence diagram for plugin loading with framework version compatibility

sequenceDiagram
    actor Dev
    participant PluginLoader
    participant ManifestValidator
    participant PluginsDir
    participant Logger

    Dev->>PluginLoader: load_all()
    PluginLoader->>PluginsDir: list plugin directories
    loop for each plugin_dir
        PluginLoader->>ManifestValidator: load_and_validate(plugin_dir)
        ManifestValidator->>ManifestValidator: read plugin.yaml or plugin.json
        ManifestValidator->>ManifestValidator: validate required fields
        ManifestValidator->>ManifestValidator: validate_version = check_compatibility(framework_version, __version__)
        ManifestValidator-->>PluginLoader: manifest, validate_version, __version__
        alt validate_version is True
            PluginLoader->>PluginLoader: manifests.append(manifest)
        else validate_version is False
            PluginLoader->>Logger: warning incompatible framework version
        end
    end
    PluginLoader-->>Dev: report (loaded, skipped plugins)
Loading

Sequence diagram for sandboxed plugin process start with ping check

sequenceDiagram
    actor Kernel
    participant ProcessManager
    participant SandboxProcess
    participant IPCClient

    Kernel->>ProcessManager: start()
    alt state is not READY
        ProcessManager-->>Kernel: return (no-op)
    else state is READY
        ProcessManager->>ProcessManager: _state = STARTING
        ProcessManager->>ProcessManager: _spawn()
        ProcessManager->>SandboxProcess: create subprocess worker
        ProcessManager->>ProcessManager: _ping_check()
        ProcessManager->>IPCClient: call("ping", {})
        IPCClient-->>ProcessManager: IPCResponse(success=True)
        ProcessManager->>ProcessManager: _state = RUNNING
        ProcessManager->>ProcessManager: _started_at = now, _restarts = 0
        ProcessManager-->>Kernel: started
    end
Loading

Updated class diagram for security validation and AST scanning

classDiagram
    class ManifestValidator {
        +load_and_validate(plugin_dir: Path) tuple
        -_read_raw(plugin_dir: Path) dict
    }

    class _SecurityVisitor {
        +forbidden: set~str~
        +allowed_exact: set~str~
        +allowed_prefixes: set~str~
        +filename: str
        +path: Path
        +errors: list~str~
        +warnings: list~str~
        +forbidden_builtins: set~str~
        +_SecurityVisitor(forbidden: set~str~, allowed: set~str~, filename: str, path: Path)
        +_is_allowed(module: str) bool
        +_check(module: str, lineno: int) void
        +visit_Import(node: ast.Import) void
        +visit_ImportFrom(node: ast.ImportFrom) void
        +visit_Name(node: ast.Name) void
        +visit_Attribute(node: ast.Attribute) void
    }

    class ASTScanner {
        -_forbidden: set~str~
        -_allowed_exact: set~str~
        -_allowed_prefixes: set~str~
        +forbidden: set~str~
        +allowed: set~str~
        +scan(path: Path) ScanResult
        +_scan_imports_python(path: Path, src: str, result: ScanResult) void
    }

    class ast_NodeVisitor {
        +visit(node: ast.AST) Any
    }

    ast_NodeVisitor <|-- _SecurityVisitor
    ASTScanner ..> _SecurityVisitor : uses for fine-grained tests
Loading

File-Level Changes

Change Details Files
Manifest validation and plugin loader now surface framework version compatibility and adjust loading accordingly.
  • ManifestValidator.load_and_validate now returns a tuple including the manifest, a compatibility flag/value from check_compatibility, and the current framework version.
  • PluginLoader.load_all unpacks the new tuple, only appends manifests when compatibility passes, and logs a warning for incompatible plugins including required vs current framework version.
  • PluginLoader.load still uses ManifestValidator.load_and_validate but must be checked for tuple vs manifest expectations during review.
xcore/kernel/security/validation.py
xcore/kernel/runtime/loader.py
Security/AST scanning gains a reusable _SecurityVisitor plus aligned error messages and tests.
  • Implemented _SecurityVisitor to walk AST nodes, normalize allowed imports, and record forbidden imports, builtins, and attributes with consistent French diagnostics.
  • Exposed forbidden/allowed sets via ASTScanner properties for test compatibility.
  • Aligned _check_builtins_and_attrs error messages with the new wording and updated tests to assert on the new messages.
xcore/kernel/security/validation.py
xcore/kernel/security/scanner_core.py
tests/unit/security/test_sentinel_fixes.py
tests/unit/security/test_ast_bypass.py
Sandbox resource limits and process management are hardened and their tests updated.
  • Renamed/extended memory limit logic into _apply_resource_limits to also cover CPU, and updated tests to use combined env variables and relaxed assertions about exact setrlimit calls.
  • Re-enabled sandbox subprocess ping check on start and tightened error messages and logging for IPC, worker, and process lifecycle paths.
  • Adjusted FilesystemGuard, import hooks, and sandbox middleware code for clearer PermissionError messages and improved logging/formatting.
tests/unit/kernel/test_sandbox_worker.py
xcore/kernel/sandbox/worker.py
xcore/kernel/sandbox/process_manager.py
xcore/kernel/sandbox/ipc.py
xcore/kernel/sandbox/middlewares/middleware.py
Runtime supervisor, kernel bootstrap, and service container are adjusted for new event/hook lifecycle and default providers.
  • Xcore now instantiates EventBus and HookManager in init (instead of boot) so decorators can use them even before boot; boot no longer recreates them.
  • Supervisor middleware registration lambdas are reformatted and PluginLoader wiring is slightly adjusted, including minor logging improvements.
  • ServiceContainer exposes DEFAULT_PROVIDERS for legacy tests and uses that constant in load_default_providers (to be checked in surrounding code).
xcore/__init__.py
xcore/kernel/runtime/supervisor.py
xcore/services/container.py
API surface gains auth primitives and minor contract improvements.
  • xcore.all and xcore.kernel.api.all now export AuthBackend/AuthPayload and helper functions for registering/retrieving auth backends and current user/session ID.
  • AuthPayload gains an optional 'user' field and AuthBackend protocol is cleaned up.
  • Base plugin contract adds call_plugin helper (spacing-only change to review for style).
xcore/__init__.py
xcore/kernel/api/__init__.py
xcore/kernel/api/auth.py
xcore/kernel/api/contract.py
Plugin structure tooling (fixtures, CI, make targets, PR checks) is updated for v2 plugins and linting/pre-commit.
  • tests.conftest.fake_plugin_dir now builds a v2-style plugin (plugin.yaml, src/main.py, TrustedBase) and a temp_dir fixture is added for tests needing clean directories.
  • CI lint job uses a new lint-check target instead of lint-fix, and plugin validation job delegates to make validate-plugins which calls 'xcore plugin health'.
  • PR workflow now checks app/* plugins for plugin.yaml and src/ presence instead of legacy plugins/* config.yaml/run.py structure.
  • Makefile adds lint-check, pre-commit, and validate-plugins targets; pyproject.dev dependencies add black/isort/flake8 and a .pre-commit-config.yaml defines corresponding hooks.
tests/conftest.py
.github/workflows/ci.yml
.github/workflows/pr.yml
makefile
pyproject.toml
.pre-commit-config.yaml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • ManifestValidator.load_and_validate now returns a tuple, but loader.load still treats it as a single manifest object (e.g. iterating manifest.requires), which will break at runtime; either adjust load() to unpack the tuple as in load_all() or keep load_and_validate’s return type consistent.
  • The new FrameworkVersionVersion exception in security/validation.py has a confusing name and is not used anywhere; consider renaming it to something clearer (e.g. FrameworkVersionError) and wiring it into the version compatibility logic, or remove it if it’s not needed.
  • The auth/rbac API wiring appears inconsistent: api/init.py imports get_auth_backend from both .auth and .rbac and exports it multiple times, and auth.py now imports Any from typing_extensions instead of typing (typing_extensions.Any is not standard); clean up these imports/exports to avoid name collisions and use typing.Any for the TypedDict annotation.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- ManifestValidator.load_and_validate now returns a tuple, but loader.load still treats it as a single manifest object (e.g. iterating manifest.requires), which will break at runtime; either adjust load() to unpack the tuple as in load_all() or keep load_and_validate’s return type consistent.
- The new FrameworkVersionVersion exception in security/validation.py has a confusing name and is not used anywhere; consider renaming it to something clearer (e.g. FrameworkVersionError) and wiring it into the version compatibility logic, or remove it if it’s not needed.
- The auth/rbac API wiring appears inconsistent: api/__init__.py imports get_auth_backend from both .auth and .rbac and exports it multiple times, and auth.py now imports Any from typing_extensions instead of typing (typing_extensions.Any is not standard); clean up these imports/exports to avoid name collisions and use typing.Any for the TypedDict annotation.

## Individual Comments

### Comment 1
<location path="xcore/kernel/runtime/loader.py" line_range="225-233" />
<code_context>
+            raise FileNotFoundError(
+                f"Dossier plugin introuvable : {plugin_dir}")

         manifest = self._validator.load_and_validate(plugin_dir)
         for dep in manifest.requires:
             dep_name = dep.name if hasattr(dep, "name") else str(dep)
             if dep_name not in self._handlers:
-                logger.info(f"[{plugin_name}] Dépendance '{dep_name}' → chargement...")
+                logger.info(
+                    f"[{plugin_name}] Dépendance '{dep_name}' → chargement...")
                 await self.load(dep_name)

         await self._activate(manifest)
</code_context>
<issue_to_address>
**issue (bug_risk):** load() still assumes load_and_validate returns a manifest, but it now returns a tuple in load_all)

`load_all` now unpacks `(manifest, validate_version, frameversion)` from `load_and_validate`, but `load()` still assigns the whole return value to `manifest` and then accesses `manifest.requires`. If `load_and_validate` always returns a tuple, this will fail at runtime. Please either unpack the tuple in `load()` as well, or revert `load_and_validate` to returning just a manifest and handle version checks separately so both call sites agree on the return type.
</issue_to_address>

### Comment 2
<location path="xcore/kernel/api/auth.py" line_range="6-13" />
<code_context>

 from typing import List, NotRequired, Protocol, TypedDict, runtime_checkable

+from typing_extensions import Any
+

 class AuthPayload(TypedDict):
     sub: str
     roles: NotRequired[List[str]]
     permissions: NotRequired[List[str]]
+    user: NotRequired[dict[str, Any]]


</code_context>
<issue_to_address>
**issue (bug_risk):** Using Any from typing_extensions will fail; use typing.Any instead

Importing `Any` from `typing_extensions` will raise an `ImportError` and prevent this module from loading. Please import it from `typing` instead, e.g. `from typing import Any, List, NotRequired, Protocol, TypedDict, runtime_checkable`.
</issue_to_address>

### Comment 3
<location path="xcore/kernel/security/validation.py" line_range="48-45" />
<code_context>
     pass


+class FrameworkVersionVersion(Exception):
+    pass
+
+
</code_context>
<issue_to_address>
**nitpick (typo):** The exception name FrameworkVersionVersion looks like a typo or misnaming

The name `FrameworkVersionVersion` looks duplicated and could be confusing. If this is for framework-version mismatches, consider renaming to something like `FrameworkVersionError` or `FrameworkVersionMismatch`, or another name that clearly reflects the condition it represents.

Suggested implementation:

```python
class FrameworkVersionError(Exception):
    """Raised when there is a framework version mismatch or invalid framework version."""
    pass

```

1. Search the codebase for all usages of `FrameworkVersionVersion` and replace them with `FrameworkVersionError`.
2. If this exception is part of the public API (imported/exported elsewhere), update any re-exports or documentation references to the new name.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 225 to 233
manifest = self._validator.load_and_validate(plugin_dir)
for dep in manifest.requires:
dep_name = dep.name if hasattr(dep, "name") else str(dep)
if dep_name not in self._handlers:
logger.info(f"[{plugin_name}] Dépendance '{dep_name}' → chargement...")
logger.info(
f"[{plugin_name}] Dépendance '{dep_name}' → chargement...")
await self.load(dep_name)

await self._activate(manifest)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): load() still assumes load_and_validate returns a manifest, but it now returns a tuple in load_all)

load_all now unpacks (manifest, validate_version, frameversion) from load_and_validate, but load() still assigns the whole return value to manifest and then accesses manifest.requires. If load_and_validate always returns a tuple, this will fail at runtime. Please either unpack the tuple in load() as well, or revert load_and_validate to returning just a manifest and handle version checks separately so both call sites agree on the return type.

Comment thread xcore/kernel/api/auth.py
Comment on lines +6 to +13
from typing_extensions import Any


class AuthPayload(TypedDict):
sub: str
roles: NotRequired[List[str]]
permissions: NotRequired[List[str]]
user: NotRequired[dict[str, Any]]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Using Any from typing_extensions will fail; use typing.Any instead

Importing Any from typing_extensions will raise an ImportError and prevent this module from loading. Please import it from typing instead, e.g. from typing import Any, List, NotRequired, Protocol, TypedDict, runtime_checkable.

@@ -45,6 +45,10 @@ class ManifestError(Exception):
pass

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick (typo): The exception name FrameworkVersionVersion looks like a typo or misnaming

The name FrameworkVersionVersion looks duplicated and could be confusing. If this is for framework-version mismatches, consider renaming to something like FrameworkVersionError or FrameworkVersionMismatch, or another name that clearly reflects the condition it represents.

Suggested implementation:

class FrameworkVersionError(Exception):
    """Raised when there is a framework version mismatch or invalid framework version."""
    pass
  1. Search the codebase for all usages of FrameworkVersionVersion and replace them with FrameworkVersionError.
  2. If this exception is part of the public API (imported/exported elsewhere), update any re-exports or documentation references to the new name.

- Fix 13 failing tests (security, configuration, lifecycle)
- Integrate pytest-benchmark and create kernel benchmarks
- Add automated 'make test' and 'lint-fix' to pre-commit hooks
- Update documentation (README.md, GEMINI.md) and version to 2.1.1
- Apply project-wide formatting via make lint-fix
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ PR très large (4789 lignes modifiées). Pensez à la découper en plusieurs PRs plus petites pour faciliter la revue.

@traoreera traoreera closed this Apr 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants