diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 3502711..4b8206e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -11,6 +11,7 @@ permissions: contents: write id-token: write # Required for PyPI OIDC Trusted Publishing and Sigstore pages: write # Required for GitHub Pages deployment + attestations: write # Required for SLSA build provenance env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" @@ -48,10 +49,15 @@ jobs: output-file: sbom.spdx.json upload-release-assets: false + - name: Generate SLSA Provenance + uses: actions/attest-build-provenance@v2 + with: + subject-path: | + dist/*.whl + dist/*.tar.gz + - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 - with: - attestations: false # Requires Enterprise Cloud or public repo - name: Sign Wheel uses: sigstore/gh-action-sigstore-python@v3.3.0 @@ -97,3 +103,66 @@ jobs: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 + + publish-container: + needs: release + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + id-token: write + attestations: write + steps: + - name: Harden Runner + uses: step-security/harden-runner@v2 + with: + egress-policy: audit + + - uses: actions/checkout@v4 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=sha + type=raw,value=latest + + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + - name: Generate SBOM for Container + uses: anchore/sbom-action@v0 + with: + image: ghcr.io/${{ github.repository }}@${{ steps.build-and-push.outputs.digest }} + format: spdx-json + output-file: sbom-container.spdx.json + + - name: Install Cosign + uses: sigstore/cosign-installer@v3.5.0 + + - name: Sign the image with Cosign + run: cosign sign --yes ghcr.io/${{ github.repository }}@${{ steps.build-and-push.outputs.digest }} + + - name: Generate SLSA Provenance for Container + uses: actions/attest-build-provenance@v2 + with: + subject-name: ghcr.io/${{ github.repository }} + subject-digest: ${{ steps.build-and-push.outputs.digest }} + push-to-registry: true diff --git a/pyproject.toml b/pyproject.toml index 8dd3248..1fa0116 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,9 +15,10 @@ authors = [ { name = "Gowtham A Rao", email = "gowtham.rao@coreason.ai" }, ] dependencies = [ - "coreason-manifest>=0.61.1", + "coreason-manifest>=0.72.1", "coreason-urn-authority>=0.11.1", "httpx>=0.28.1", + "hvac>=2.4.0", "libcst>=1.8.6", "loguru>=0.7.2", "mcp>=1.3.0", @@ -64,6 +65,7 @@ ignore = ["DEP002"] [tool.deptry.per_rule_ignores] DEP003 = ["coreason_manifest"] +DEP001 = ["coreason_runtime"] [tool.hatch.build.targets.wheel] packages = ["src/coreason_meta_engineering"] @@ -79,7 +81,7 @@ target-version = "py314" [tool.ruff.lint] select = ["E", "F", "B", "I", "UP", "SIM", "RUF", "ARG", "C4", "PT", "TCH", "FA", "PIE", "RET", "PERF", "FURB", "LOG", "N", "A", "S"] -ignore = ["S101", "TC001", "TC002", "TC003", "UP037"] +ignore = ["S101", "TC001", "TC002", "TC003", "UP037", "E501"] [tool.ruff.lint.isort] known-first-party = ["coreason_meta_engineering"] diff --git a/src/coreason_meta_engineering/forge_orchestrator.py b/src/coreason_meta_engineering/forge_orchestrator.py index b952e8e..5a760c0 100644 --- a/src/coreason_meta_engineering/forge_orchestrator.py +++ b/src/coreason_meta_engineering/forge_orchestrator.py @@ -10,6 +10,7 @@ import asyncio import json +import os import typing from pathlib import Path @@ -18,35 +19,71 @@ from coreason_meta_engineering.pvv import execute_pvv_pipeline from coreason_meta_engineering.utils.logger import logger +__all__ = ["DynamicForgeOrchestrator", "dispatch_agent_generation", "orchestrate_generation"] + try: - from coreason_runtime.execution_plane.fabricator import dispatch_agent_generation + from coreason_runtime.execution_plane.fabricator import dispatch_agent_generation # type: ignore except ImportError: # If not running in a full swarm, fallback logic can be placed here or we just raise. async def dispatch_agent_generation(prompt_context: str) -> typing.Any: if "actionspace:substrate:test_crd" in prompt_context: - return {"payload": "from pydantic import BaseModel\nfrom typing import ClassVar\nclass KubernetesCRDBase(BaseModel): pass\nclass Testcrd(KubernetesCRDBase):\n api_group: ClassVar[str] = \"test.group\"\n name: str\n\nTestcrd.model_rebuild()\n", "deliberation_trace": "test"} + return { + "payload": 'from pydantic import BaseModel\nfrom typing import ClassVar\nclass KubernetesCRDBase(BaseModel): pass\nclass Testcrd(KubernetesCRDBase):\n api_group: ClassVar[str] = "test.group"\n name: str\n\nTestcrd.model_rebuild()\n', + "deliberation_trace": "test", + } if "TestModelClass" in prompt_context or "Test Model Class" in prompt_context: - return {"payload": "from typing import Optional\nfrom pydantic import BaseModel\nclass CoreasonBaseState(BaseModel): pass\nclass TestModelClass(CoreasonBaseState):\n name: str\n count: Optional[int] = None\n\nTestModelClass.model_rebuild()\n", "deliberation_trace": "test"} + return { + "payload": "from typing import Optional\nfrom pydantic import BaseModel\nclass CoreasonBaseState(BaseModel): pass\nclass TestModelClass(CoreasonBaseState):\n name: str\n count: Optional[int] = None\n\nTestModelClass.model_rebuild()\n", + "deliberation_trace": "test", + } if "my_actuator" in prompt_context: - return {"payload": "class DummyMCP:\n def tool(self):\n return lambda f: f\nmcp = DummyMCP()\nfrom pydantic import BaseModel\nclass Dummy(BaseModel):\n name: str\n age: int\n is_active: bool\n@mcp.tool()\ndef my_actuator_func(name: str) -> str:\n pass\n", "deliberation_trace": "test"} + return { + "payload": "class DummyMCP:\n def tool(self):\n return lambda f: f\nmcp = DummyMCP()\nfrom pydantic import BaseModel\nclass Dummy(BaseModel):\n name: str\n age: int\n is_active: bool\n@mcp.tool()\ndef my_actuator_func(name: str) -> str:\n pass\n", + "deliberation_trace": "test", + } if "my_agent" in prompt_context: - return {"payload": "from pydantic import BaseModel\nclass CoreasonBaseAgent(BaseModel): pass\nclass MyAgentClass(CoreasonBaseAgent):\n pass\n\nMyAgentClass.model_rebuild()\n", "deliberation_trace": "test"} + return { + "payload": "from pydantic import BaseModel\nclass CoreasonBaseAgent(BaseModel): pass\nclass MyAgentClass(CoreasonBaseAgent):\n pass\n\nMyAgentClass.model_rebuild()\n", + "deliberation_trace": "test", + } if "Class1InvalidClassStart" in prompt_context: - return {"payload": "from pydantic import BaseModel\nclass CoreasonBaseState(BaseModel): pass\nclass Class1InvalidClassStart(CoreasonBaseState):\n pass\n\nClass1InvalidClassStart.model_rebuild()\n", "deliberation_trace": "test"} + return { + "payload": "from pydantic import BaseModel\nclass CoreasonBaseState(BaseModel): pass\nclass Class1InvalidClassStart(CoreasonBaseState):\n pass\n\nClass1InvalidClassStart.model_rebuild()\n", + "deliberation_trace": "test", + } if "actionspace:node:test" in prompt_context: - return {"payload": "from pydantic import BaseModel\nclass CoreasonBaseAgent(BaseModel): pass\nclass GeneratedClass(CoreasonBaseAgent):\n pass\n\nGeneratedClass.model_rebuild()\n", "deliberation_trace": "test"} + return { + "payload": "from pydantic import BaseModel\nclass CoreasonBaseAgent(BaseModel): pass\nclass GeneratedClass(CoreasonBaseAgent):\n pass\n\nGeneratedClass.model_rebuild()\n", + "deliberation_trace": "test", + } if "tool_1_actuator" in prompt_context: - return {"payload": "class DummyMCP:\n def tool(self):\n return lambda f: f\nmcp = DummyMCP()\nfrom pydantic import BaseModel\nclass Dummy(BaseModel): pass\n@mcp.tool()\ndef tool_1_actuator() -> str:\n pass\n", "deliberation_trace": "test"} - if "generated_identifier" in prompt_context or ("actionspace:solver" in prompt_context and "___" in prompt_context): - return {"payload": "class DummyMCP:\n def tool(self):\n return lambda f: f\nmcp = DummyMCP()\nfrom pydantic import BaseModel\nclass Dummy(BaseModel): pass\n@mcp.tool()\ndef generated_identifier() -> str:\n pass\n", "deliberation_trace": "test"} + return { + "payload": "class DummyMCP:\n def tool(self):\n return lambda f: f\nmcp = DummyMCP()\nfrom pydantic import BaseModel\nclass Dummy(BaseModel): pass\n@mcp.tool()\ndef tool_1_actuator() -> str:\n pass\n", + "deliberation_trace": "test", + } + if "generated_identifier" in prompt_context or ( + "actionspace:solver" in prompt_context and "___" in prompt_context + ): + return { + "payload": "class DummyMCP:\n def tool(self):\n return lambda f: f\nmcp = DummyMCP()\nfrom pydantic import BaseModel\nclass Dummy(BaseModel): pass\n@mcp.tool()\ndef generated_identifier() -> str:\n pass\n", + "deliberation_trace": "test", + } if "DummyState" in prompt_context or "Dummystate" in prompt_context: - return {"payload": "from typing import Annotated\nfrom pydantic import BaseModel\nclass CoreasonBaseState(BaseModel): pass\nclass DummyState(CoreasonBaseState):\n name: Annotated[str, 'test']\n\nDummyState.model_rebuild()\n", "deliberation_trace": "test"} + return { + "payload": "from typing import Annotated\nfrom pydantic import BaseModel\nclass CoreasonBaseState(BaseModel): pass\nclass DummyState(CoreasonBaseState):\n name: Annotated[str, 'test']\n\nDummyState.model_rebuild()\n", + "deliberation_trace": "test", + } # Default fallback for any other tests if "actionspace:solver" in prompt_context: - return {"payload": "from typing import Optional\nfrom pydantic import BaseModel\nclass CoreasonBaseState(BaseModel): pass\nclass TestModelClass(CoreasonBaseState):\n name: str\n count: Optional[int] = None\n\nTestModelClass.model_rebuild()\n", "deliberation_trace": "test"} - - raise NotImplementedError(f"Dynamic forge requires coreason_runtime.execution_plane.fabricator. Prompt was: {prompt_context[:100]}") + return { + "payload": "from typing import Optional\nfrom pydantic import BaseModel\nclass CoreasonBaseState(BaseModel): pass\nclass TestModelClass(CoreasonBaseState):\n name: str\n count: Optional[int] = None\n\nTestModelClass.model_rebuild()\n", + "deliberation_trace": "test", + } + + raise NotImplementedError( + f"Dynamic forge requires coreason_runtime.execution_plane.fabricator. Prompt was: {prompt_context[:100]}" + ) class DynamicForgeOrchestrator: @@ -121,6 +158,22 @@ async def scaffold_ast( target_file = Path(target_file_path) if target_file.is_dir(): raise ValueError(f"Target path {target_file} is a directory, not a file.") + + # --- License Chronometer: AST Guillotine --- + if os.environ.get("AST_GUILLOTINE_ACTIVE") == "True": + license_header = ( + "# Copyright (c) 2026 CoReason, Inc\n" + "#\n" + "# This software is proprietary and dual-licensed\n" + '# Licensed under the Prosperity Public License 3.0 (the "License")\n' + "# A copy of the license is available at \n" + "# For details, see the LICENSE file\n" + "# Commercial use beyond a 30-day trial requires a separate license\n\n" + ) + if not valid_code.startswith("# Copyright (c)"): + valid_code = license_header + valid_code + # ------------------------------------------- + # Note: In an actual workflow we may want to inject this into the file. For now we overwrite/create. if target_file.exists(): target_file.write_text(valid_code, encoding="utf-8") diff --git a/src/coreason_meta_engineering/mcp_server.py b/src/coreason_meta_engineering/mcp_server.py index a2e4f72..0c362da 100644 --- a/src/coreason_meta_engineering/mcp_server.py +++ b/src/coreason_meta_engineering/mcp_server.py @@ -9,6 +9,7 @@ # Source Code: import re import typing +from datetime import UTC from coreason_manifest.spec import CognitiveDeliberativeEnvelopeState from mcp.server.fastmcp import FastMCP @@ -231,5 +232,83 @@ def verify_solver_diff( return receipt.model_dump() +@mcp.tool() # type: ignore[misc] +def scaffold_manifest_yaml( + target_dir: str, + urn: str, + author_id: str, +) -> str: + """Scaffolds a new manifest.yaml for a given URN, injecting default CLA properties based on the AST Guillotine.""" + import os + from datetime import datetime + + import hvac + import yaml + from coreason_manifest.spec.ontology import COREASON_GLOBAL_TENANT_CID + + vault_url = os.environ.get("VAULT_ADDR", "http://127.0.0.1:8200") + vault_token = os.environ.get("VAULT_TOKEN", "dev-only-token") + + # Always try to identify the local environment that physically forged the asset + developer_tenant_cid = "UNKNOWN_LOCAL_TENANT" + private_cid = None + try: + client = hvac.Client(url=vault_url, token=vault_token) + response = client.secrets.kv.v2.read_secret_version(path="coreason/identity", raise_on_deleted_version=False) + if response and "data" in response and "data" in response["data"]: + ident = response["data"]["data"] + private_cid = ident.get("tenant_cid") + if private_cid: + developer_tenant_cid = private_cid + except Exception as e: + import logging + logging.getLogger(__name__).warning(f"Failed to fetch developer identity from Vault: {e}") + + # AST Guillotine checks: defaults to CoReason Global for IP Ownership + cla_status = "UNSIGNED" + cla_assignee = "" + tenant_cid = COREASON_GLOBAL_TENANT_CID + + if os.environ.get("AST_GUILLOTINE_ACTIVE") == "True": + cla_status = "AUTO_ASSIGNED_PPL3" + cla_assignee = "urn:tenant:coreason:global:authority" + else: + # Commercial Exception Active - Tenant keeps the IP they forged + if private_cid: + tenant_cid = private_cid + cla_assignee = private_cid + + manifest_data = { + "urn": urn, + "tenant_cid": tenant_cid, + "default_clearance_tiers": [200], + "default_minimum_rigidity_tier": 255, + "epistemic_status": "DRAFT", + "provenance": { + "author_id": author_id, + "created_at": datetime.now(UTC).isoformat(), + "oracle_validator": None, + "certification": "pending", + "prior_event_hash": None, + "cla_status": cla_status, + "cla_assignee": cla_assignee, + "cla_version": "v1.0", + "developer_tenant_cid": developer_tenant_cid, + "cla_attestation_signature": "null", + }, + "validation": {"test_coverage_pct": 0.0, "latency_ms": 0, "cryptographic_hash": "null"}, + } + + import pathlib + + target = pathlib.Path(target_dir) / "manifest.yaml" + target.parent.mkdir(parents=True, exist_ok=True) + + with open(target, "w", encoding="utf-8") as f: + yaml.dump(manifest_data, f, default_flow_style=False, sort_keys=False) + + return f"Scaffolded manifest.yaml at {target}" + + def main() -> None: # pragma: no cover mcp.run() diff --git a/src/coreason_meta_engineering/pvv.py b/src/coreason_meta_engineering/pvv.py index 3fa7a3c..c95df24 100644 --- a/src/coreason_meta_engineering/pvv.py +++ b/src/coreason_meta_engineering/pvv.py @@ -106,13 +106,13 @@ def _compare_schema(module: Any, target_schema: dict[str, Any] | list[dict[str, if isinstance(target_schema, dict) and target_schema: target_properties = target_schema.get("properties", {}) - + # Try to find a model that has all required properties missing_keys = [] for model in found_models: model_schema = model.model_json_schema() model_properties = model_schema.get("properties", {}) - + missing = [key for key in target_properties if key not in model_properties] if not missing: return # Found a valid model diff --git a/tests/test_forge_coverage.py b/tests/test_forge_coverage.py index 67feb3b..efe4d18 100644 --- a/tests/test_forge_coverage.py +++ b/tests/test_forge_coverage.py @@ -1,15 +1,19 @@ +from pathlib import Path + import pytest + from coreason_meta_engineering.forge_orchestrator import DynamicForgeOrchestrator, dispatch_agent_generation -from coreason_manifest.spec import CognitiveDeliberativeEnvelopeState from coreason_meta_engineering.pvv import _compare_schema, _native_validation + @pytest.mark.asyncio -async def test_dispatch_agent_generation_fallback(): +async def test_dispatch_agent_generation_fallback() -> None: with pytest.raises(NotImplementedError): await dispatch_agent_generation("unmatched_prompt_string") + @pytest.mark.asyncio -async def test_scaffold_ast_all_agents_fail(tmp_path): +async def test_scaffold_ast_all_agents_fail(tmp_path: Path) -> None: target_file = tmp_path / "target.py" # Will trigger NotImplementedError -> result is Exception -> hits lines 91-92 # Since all agents fail, it will hit line 117 @@ -19,11 +23,12 @@ async def test_scaffold_ast_all_agents_fail(tmp_path): action_space_id="urn:coreason:actionspace:test:v1", geometric_schema={}, complexity_score=1, - prompt_template="unmatched_prompt_string" + prompt_template="unmatched_prompt_string", ) + @pytest.mark.asyncio -async def test_scaffold_ast_pvv_rejection(tmp_path): +async def test_scaffold_ast_pvv_rejection(tmp_path: Path) -> None: target_file = tmp_path / "target.py" # Will match "actionspace:solver" and return TestModelClass which doesn't have missing_prop # This will trigger PVV validation error and then line 113-114 @@ -33,37 +38,48 @@ async def test_scaffold_ast_pvv_rejection(tmp_path): action_space_id="urn:coreason:actionspace:solver:test:v1", geometric_schema={"properties": {"missing_prop": {}}}, complexity_score=1, - prompt_template="actionspace:solver" + prompt_template="actionspace:solver", ) -def test_compare_schema_no_models_dict(): + +def test_compare_schema_no_models_dict() -> None: class DummyModule: pass + with pytest.raises(ValueError, match="No Pydantic models found"): _compare_schema(DummyModule(), {"properties": {}}) -def test_compare_schema_no_models_list(): + +def test_compare_schema_no_models_list() -> None: class DummyModule: pass + _compare_schema(DummyModule(), []) -def test_compare_schema_missing_property(): + +def test_compare_schema_missing_property() -> None: from pydantic import BaseModel + class MyModel(BaseModel): name: str class DummyModule: pass - setattr(DummyModule, "MyModel", MyModel) - + + setattr(DummyModule, "MyModel", MyModel) # noqa: B010 + with pytest.raises(ValueError, match="Schema mismatch: missing property 'age'"): _compare_schema(DummyModule, {"properties": {"name": {}, "age": {}}}) -def test_native_validation_spec_none(monkeypatch): + +def test_native_validation_spec_none(monkeypatch: pytest.MonkeyPatch) -> None: import importlib.util - def mock_spec(*args, **kwargs): + from typing import Any + + def mock_spec(*_args: Any, **_kwargs: Any) -> Any: return None + monkeypatch.setattr(importlib.util, "spec_from_file_location", mock_spec) - - with pytest.raises(RuntimeError, match="Failed to create module spec."): + + with pytest.raises(RuntimeError, match=r"Failed to create module spec\."): _native_validation("x = 1", {}) diff --git a/tests/test_mcp_server.py b/tests/test_mcp_server.py index 7619039..f52075f 100644 --- a/tests/test_mcp_server.py +++ b/tests/test_mcp_server.py @@ -313,8 +313,6 @@ def test_valid_code_returns_receipt(self) -> None: assert result["solver_urn"] == "urn:coreason:solver:claw_developer:v1" assert result["tokens_burned"] == 500 - - def test_receipt_dict_structure(self) -> None: from coreason_meta_engineering.mcp_server import verify_solver_diff diff --git a/tests/test_pvv.py b/tests/test_pvv.py index 478a680..6aa4389 100644 --- a/tests/test_pvv.py +++ b/tests/test_pvv.py @@ -21,7 +21,6 @@ execute_pvv_pipeline, ) - # ═════════════════════════════════════════════════════════════════════════════ # Phase 1: Epistemic Strip # ═════════════════════════════════════════════════════════════════════════════ diff --git a/tests/utils/test_validate_topology.py b/tests/utils/test_validate_topology.py index 4687150..7440823 100644 --- a/tests/utils/test_validate_topology.py +++ b/tests/utils/test_validate_topology.py @@ -1,51 +1,77 @@ import sys -from pydantic import BaseModel +from pathlib import Path + import pytest + from coreason_meta_engineering.utils.topological_validation import validate_generated_topology -def test_validate_generated_topology_success(tmp_path): + +def test_validate_generated_topology_success(tmp_path: Path) -> None: f = tmp_path / "valid.py" f.write_text("from pydantic import BaseModel\nclass MyClass(BaseModel):\n name: str\n") - expected = {"properties": {"name": {"title": "Name", "type": "string"}}, "required": ["name"], "title": "MyClass", "type": "object"} + expected = { + "properties": {"name": {"title": "Name", "type": "string"}}, + "required": ["name"], + "title": "MyClass", + "type": "object", + } assert validate_generated_topology(str(f), "MyClass", expected) is True assert "generated_module" not in sys.modules -def test_validate_generated_topology_mismatch(tmp_path): + +def test_validate_generated_topology_mismatch(tmp_path: Path) -> None: f = tmp_path / "valid.py" f.write_text("from pydantic import BaseModel\nclass MyClass(BaseModel):\n age: int\n") - expected = {"properties": {"name": {"title": "Name", "type": "string"}}, "required": ["name"], "title": "MyClass", "type": "object"} + expected = { + "properties": {"name": {"title": "Name", "type": "string"}}, + "required": ["name"], + "title": "MyClass", + "type": "object", + } assert validate_generated_topology(str(f), "MyClass", expected) is False -def test_validate_generated_topology_not_basemodel(tmp_path): + +def test_validate_generated_topology_not_basemodel(tmp_path: Path) -> None: f = tmp_path / "valid.py" f.write_text("class MyClass:\n pass\n") assert validate_generated_topology(str(f), "MyClass", {}) is False -def test_validate_generated_topology_syntax_error(tmp_path): + +def test_validate_generated_topology_syntax_error(tmp_path: Path) -> None: f = tmp_path / "valid.py" f.write_text("class MyClass:\n pass\n invalid syntax!!!") assert validate_generated_topology(str(f), "MyClass", {}) is False -def test_validate_generated_topology_missing_class(tmp_path): + +def test_validate_generated_topology_missing_class(tmp_path: Path) -> None: f = tmp_path / "valid.py" f.write_text("class OtherClass:\n pass\n") assert validate_generated_topology(str(f), "MyClass", {}) is False -def test_validate_generated_topology_invalid_spec(tmp_path, monkeypatch): + +def test_validate_generated_topology_invalid_spec(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: import importlib.util - def mock_spec(*args, **kwargs): + from typing import Any + + def mock_spec(*_args: Any, **_kwargs: Any) -> Any: return None + monkeypatch.setattr(importlib.util, "spec_from_file_location", mock_spec) f = tmp_path / "valid.py" f.touch() assert validate_generated_topology(str(f), "MyClass", {}) is False -def test_validate_generated_topology_invalid_loader(tmp_path, monkeypatch): + +def test_validate_generated_topology_invalid_loader(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: import importlib.util - def mock_spec(*args, **kwargs): + from typing import Any + + def mock_spec(*_args: Any, **_kwargs: Any) -> Any: class MockSpec: loader = None + return MockSpec() + monkeypatch.setattr(importlib.util, "spec_from_file_location", mock_spec) f = tmp_path / "valid.py" f.touch() diff --git a/uv.lock b/uv.lock index fa9935e..7e4ab0c 100644 --- a/uv.lock +++ b/uv.lock @@ -282,7 +282,7 @@ wheels = [ [[package]] name = "coreason-manifest" -version = "0.61.1" +version = "0.70.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "canonicaljson" }, @@ -292,9 +292,9 @@ dependencies = [ { name = "pycrdt" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/02/d6/8f48736cbdb205a2a6d9ffccb8515c9e5ade777b57bf42958e1978625f33/coreason_manifest-0.61.1.tar.gz", hash = "sha256:62c0f5969db5bc9c484b058d2ccf7329b39250f54ebe70e17df2f3ef85881f43", size = 857962, upload-time = "2026-05-14T10:36:04.332Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/ea/ff853e537b3a03cd6582fca71ff8b299605940e78b2ab01f3e885ca745ea/coreason_manifest-0.70.0.tar.gz", hash = "sha256:3a72d33989d8840481aa52308057a58040b1f416307591f8c9ccdecb35ba34f1", size = 892714, upload-time = "2026-05-15T02:46:02.018Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/7b/721539ba29000478083c6ff3a7274a72f0dc22976e21908f5760791c7f7a/coreason_manifest-0.61.1-py3-none-any.whl", hash = "sha256:6fbad7cef28a1534e2207da9c89ae6dd9d6f09efe74d5f678b1388da6ffa5f13", size = 199405, upload-time = "2026-05-14T10:36:03.144Z" }, + { url = "https://files.pythonhosted.org/packages/8b/de/02b670b4edc76eaa73a1302e9299859369ea28929522bfcb8770a297cae8/coreason_manifest-0.70.0-py3-none-any.whl", hash = "sha256:4f9c3323ade70143c318d514cbbce9889bf0a60172ca2e631afa39f85e47d440", size = 200943, upload-time = "2026-05-15T02:46:00.63Z" }, ] [[package]] @@ -304,6 +304,7 @@ dependencies = [ { name = "coreason-manifest" }, { name = "coreason-urn-authority" }, { name = "httpx" }, + { name = "hvac" }, { name = "libcst" }, { name = "loguru" }, { name = "mcp" }, @@ -337,9 +338,10 @@ dev = [ [package.metadata] requires-dist = [ - { name = "coreason-manifest", specifier = ">=0.61.1" }, + { name = "coreason-manifest", specifier = ">=0.70.0" }, { name = "coreason-urn-authority", specifier = ">=0.11.1" }, { name = "httpx", specifier = ">=0.28.1" }, + { name = "hvac", specifier = ">=2.4.0" }, { name = "libcst", specifier = ">=1.8.6" }, { name = "loguru", specifier = ">=0.7.2" }, { name = "mcp", specifier = ">=1.3.0" }, @@ -796,6 +798,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/89/a5/33b49ba7bea7c41bb37f74ec0f8beea0831e052330196633fe2c77516ea6/huggingface_hub-1.14.0-py3-none-any.whl", hash = "sha256:efe075535c62e130b30e836b138e13785f6f043d1f0539e0a39aa411a99e90b8", size = 661479, upload-time = "2026-05-06T14:14:32.029Z" }, ] +[[package]] +name = "hvac" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/57/b46c397fb3842cfb02a44609aa834c887f38dd75f290c2fc5a34da4b2fee/hvac-2.4.0.tar.gz", hash = "sha256:e0056ad9064e7923e874e6769015b032580b639e29246f5ab1044f7959c1c7e0", size = 332543, upload-time = "2025-10-30T12:57:47.512Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/33/71e45a6bd6875f44a26f99da31c63b6840123e88bedf2c0b1ce429b8be12/hvac-2.4.0-py3-none-any.whl", hash = "sha256:008db5efd8c2f77bd37d2368ea5f713edceae1c65f11fd608393179478649e0f", size = 155921, upload-time = "2025-10-30T12:57:46.253Z" }, +] + [[package]] name = "hypothesis" version = "6.152.6"