plugin-scanner 2.0.64__tar.gz → 2.0.65__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/PKG-INFO +1 -1
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/pyproject.toml +1 -1
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/pyproject.toml.bak +1 -1
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/connect_flow.py +10 -10
- plugin_scanner-2.0.65/src/codex_plugin_scanner/guard/edge_events.py +88 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/runtime/__init__.py +2 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/runtime/runner.py +98 -0
- plugin_scanner-2.0.65/src/codex_plugin_scanner/guard/schemas/guard_event_v1.py +74 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/store.py +95 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/version.py +1 -1
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_cli.py +33 -21
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_connect_flow.py +9 -9
- plugin_scanner-2.0.65/tests/test_guard_event_schema_v1.py +260 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.clusterfuzzlite/Dockerfile +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.clusterfuzzlite/build.sh +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.clusterfuzzlite/project.yaml +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.clusterfuzzlite/requirements-atheris.txt +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.dockerignore +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.github/CODEOWNERS +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.github/ISSUE_TEMPLATE/bug-report.yml +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.github/dependabot.yml +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.github/workflows/ci.yml +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.github/workflows/codeql.yml +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.github/workflows/dependabot-uv-lock.yml +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.github/workflows/fuzz.yml +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.github/workflows/harness-smoke.yml +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.github/workflows/publish.yml +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.github/workflows/scorecard.yml +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.gitignore +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.pre-commit-hooks.yaml +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/CONTRIBUTING.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/Dockerfile +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/LICENSE +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/README.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/SECURITY.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/index.html +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/package.json +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/pnpm-lock.yaml +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/public/brand/Logo_Whole.png +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/src/app.tsx +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/src/approval-center-layout.tsx +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/src/approval-center-primitives.tsx +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/src/approval-center-utils.ts +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/src/fleet-workspace.tsx +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/src/guard-api.ts +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/src/guard-demo.ts +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/src/guard-types.ts +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/src/main.tsx +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/src/receipts-workspace.tsx +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/src/runtime-overview.tsx +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/src/styles.css +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/src/vite-env.d.ts +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/tsconfig.json +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/vite.config.ts +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/docker-requirements.txt +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/docs/guard/approval-audit.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/docs/guard/architecture.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/docs/guard/get-started.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/docs/guard/harness-support.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/docs/guard/local-vs-cloud.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/docs/guard/testing-matrix.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/docs/trust/mcp-trust-draft.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/docs/trust/plugin-trust-draft.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/docs/trust/skill-trust-local.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/fuzzers/manifest_fuzzer.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/requirements.txt +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/schemas/plugin-quality.v1.json +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/schemas/scan-result.v1.json +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/schemas/verify-result.v1.json +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/__init__.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/action_runner.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/argparse_utils.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/__init__.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/best_practices.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/claude.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/code_quality.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/ecosystem_common.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/gemini.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/manifest.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/manifest_support.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/marketplace.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/mcp_security.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/opencode.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/operational_security.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/security.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/skill_security.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/cli.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/cli_ui.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/config.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/ecosystems/__init__.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/ecosystems/base.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/ecosystems/claude.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/ecosystems/codex.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/ecosystems/detect.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/ecosystems/gemini.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/ecosystems/opencode.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/ecosystems/registry.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/ecosystems/types.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/github_reporting.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/__init__.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/__init__.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/antigravity.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/base.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/claude_code.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/codex.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/copilot.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/cursor.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/gemini.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/hermes.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/mcp_servers.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/opencode.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/opencode_artifacts.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/approvals.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/bridge/__init__.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/capabilities.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/__init__.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/approval_commands.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/bootstrap.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/commands.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/install_commands.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/product.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/prompt.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/render.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/update_commands.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/codex_config.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/config.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/consumer/__init__.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/consumer/service.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/daemon/__init__.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/daemon/client.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/daemon/manager.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/daemon/server.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/daemon/static/assets/guard-dashboard.js +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/daemon/static/assets/index.css +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Whole.png +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/daemon/static/index.html +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/incident.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/launcher.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/mcp_tool_calls.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/models.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/policy/__init__.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/policy/engine.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/protect.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/proxy/__init__.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/proxy/remote.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/proxy/runtime_mcp.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/proxy/stdio.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/receipts/__init__.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/receipts/manager.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/risk.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/runtime/secret_file_requests.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/runtime/surface_server.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/schemas/__init__.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/schemas/consumer_mode.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/schemas/surface_server.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/shims.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/store_approvals.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/store_connect.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/types.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/integrations/__init__.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/integrations/cisco_mcp_scanner.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/integrations/cisco_skill_scanner.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/lint_fixes.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/marketplace_support.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/models.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/path_support.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/policy.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/quality_artifact.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/repo_detect.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/reporting.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/rules/__init__.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/rules/registry.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/rules/specs.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/scanner.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/submission.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/suppressions.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/trust_domain_scoring.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/trust_helpers.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/trust_mcp_scoring.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/trust_models.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/trust_plugin_scoring.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/trust_scoring.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/trust_skill_scoring.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/trust_specs.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/verification.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/__init__.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/conftest.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/__init__.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/bad-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/bad-plugin/.mcp.json +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/bad-plugin/secrets.js +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/claude-plugin-good/.claude-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/claude-plugin-good/LICENSE +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/claude-plugin-good/README.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/claude-plugin-good/SECURITY.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/claude-plugin-good/hooks/hooks.json +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/claude-plugin-good/skills/example/SKILL.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/code-quality-bad/evil.js +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/code-quality-bad/inject.js +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/gemini-extension-good/GEMINI.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/gemini-extension-good/LICENSE +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/gemini-extension-good/README.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/gemini-extension-good/SECURITY.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/gemini-extension-good/commands/hello.toml +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/gemini-extension-good/gemini-extension.json +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/good-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/good-plugin/.codexignore +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/good-plugin/LICENSE +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/good-plugin/README.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/good-plugin/SECURITY.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/good-plugin/assets/icon.svg +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/good-plugin/assets/logo.svg +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/good-plugin/assets/screenshot.svg +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/good-plugin/skills/example/SKILL.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/guard-codex-malicious-mcp/.codex/config.toml +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/hermes-plugin-evil/config.yaml +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/hermes-plugin-evil/mcp_servers.json +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/hermes-plugin-evil/skills/security/malicious/SKILL.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/SKILL.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/references/api-setup.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/scripts/deploy.sh +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/hermes-plugin-evil/skills/utils/benign/SKILL.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/malformed-json/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/malicious-skill-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/malicious-skill-plugin/.codexignore +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/malicious-skill-plugin/LICENSE +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/malicious-skill-plugin/README.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/malicious-skill-plugin/SECURITY.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/malicious-skill-plugin/skills/leaky-skill/SKILL.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/mcp-canary-server.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/minimal-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/missing-fields/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/mit-license/LICENSE +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-ecosystem-repo/codex-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-ecosystem-repo/codex-plugin/LICENSE +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-ecosystem-repo/codex-plugin/README.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-ecosystem-repo/codex-plugin/SECURITY.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-ecosystem-repo/gemini-ext/README.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-ecosystem-repo/gemini-ext/gemini-extension.json +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-plugin-repo/.agents/plugins/marketplace.json +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codexignore +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/LICENSE +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/README.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/SECURITY.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/skills/example/SKILL.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/skills/example/SKILL.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/no-version/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/opencode-good/.opencode/commands/hello.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/opencode-good/.opencode/plugins/example.ts +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/opencode-good/LICENSE +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/opencode-good/README.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/opencode-good/SECURITY.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/opencode-good/opencode.jsonc +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/skills-missing-dir/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/skills-no-frontmatter/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/skills-no-frontmatter/skills/bad-skill/SKILL.md +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/with-marketplace/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/with-marketplace/marketplace-broken.json +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/with-marketplace/marketplace.json +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test-trust-scoring.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test-trust-specs.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_action_runner.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_best_practices.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_cisco_install_surfaces.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_cli.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_code_quality.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_config.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_coverage_remaining.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_ecosystems.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_edge_cases.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_final_coverage.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_approvals.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_bootstrap.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_capabilities.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_claude_adapter.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_codex_e2e.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_codex_install.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_codex_proxy.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_config_paths.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_consumer_mode.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_copilot_adapter.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_copilot_proxy.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_daemon_manager.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_events.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_launch_env.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_opencode_proxy.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_product_flow.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_protect.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_render.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_risk.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_runtime.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_store_migrations.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_surface_server.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_verdicts.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_hermes_adapter.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_integration.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_lint_fixes.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_live_cisco_smoke.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_manifest.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_marketplace.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_mcp_security.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_operational_security.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_policy.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_quality_artifact.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_rule_registry.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_scanner.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_schema_contracts.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_security.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_security_ops.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_skill_security.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_submission.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_trust_scoring.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_trust_specs.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_verification.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_versioning.py +0 -0
- {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plugin-scanner
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.65
|
|
4
4
|
Summary: Lint, verify, and gate plugin ecosystems for maintainers, CI, and publish workflows.
|
|
5
5
|
Project-URL: Homepage, https://github.com/hashgraph-online/ai-plugin-scanner
|
|
6
6
|
Project-URL: Repository, https://github.com/hashgraph-online/ai-plugin-scanner
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "plugin-scanner"
|
|
7
|
-
version = "2.0.
|
|
7
|
+
version = "2.0.65"
|
|
8
8
|
description = "Lint, verify, and gate plugin ecosystems for maintainers, CI, and publish workflows."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "hol-guard"
|
|
7
|
-
version = "2.0.
|
|
7
|
+
version = "2.0.65"
|
|
8
8
|
description = "Protect local AI harnesses with HOL Guard and run scanner checks for Codex, Claude, Cursor, Gemini, and OpenCode."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
{plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/connect_flow.py
RENAMED
|
@@ -107,36 +107,37 @@ def run_guard_connect_command(
|
|
|
107
107
|
sync_payload = sync_receipts(store)
|
|
108
108
|
except GuardSyncNotAvailableError as plan_error:
|
|
109
109
|
plan_msg = str(plan_error).strip() or "Cloud sync requires a paid Guard plan."
|
|
110
|
+
pending_sync_payload = dict(runtime_sync_summary)
|
|
111
|
+
pending_sync_payload["synced_at"] = None
|
|
110
112
|
pending_state = _record_connect_result(
|
|
111
113
|
daemon_client=daemon_client,
|
|
112
114
|
store=store,
|
|
113
115
|
request_id=str(connect_request["request_id"]),
|
|
114
|
-
status="
|
|
115
|
-
milestone="
|
|
116
|
+
status="retry_required",
|
|
117
|
+
milestone="first_sync_failed",
|
|
116
118
|
reason=plan_msg,
|
|
119
|
+
sync=pending_sync_payload,
|
|
117
120
|
)
|
|
118
121
|
return build_connect_payload(
|
|
119
122
|
state=pending_state,
|
|
120
123
|
browser_opened=browser_opened,
|
|
121
124
|
connect_url=browser_url,
|
|
122
125
|
sync_url=sync_url,
|
|
123
|
-
connected=
|
|
126
|
+
connected=False,
|
|
127
|
+
sync=pending_sync_payload,
|
|
124
128
|
sync_available=False,
|
|
125
129
|
sync_message=plan_msg,
|
|
126
130
|
)
|
|
127
131
|
except (RuntimeError, OSError, urllib.error.URLError, json.JSONDecodeError) as error:
|
|
128
132
|
sync_message = str(error)
|
|
129
|
-
if runtime_sync_error and not _is_paid_plan_sync_error(sync_message):
|
|
130
|
-
sync_message = runtime_sync_error
|
|
131
|
-
sync_is_plan_limited = _is_paid_plan_sync_error(sync_message)
|
|
132
133
|
pending_sync_payload = dict(runtime_sync_summary)
|
|
133
134
|
pending_sync_payload["synced_at"] = None
|
|
134
135
|
pending_state = _record_connect_result(
|
|
135
136
|
daemon_client=daemon_client,
|
|
136
137
|
store=store,
|
|
137
138
|
request_id=str(connect_request["request_id"]),
|
|
138
|
-
status="
|
|
139
|
-
milestone="
|
|
139
|
+
status="retry_required",
|
|
140
|
+
milestone="first_sync_failed",
|
|
140
141
|
reason=sync_message,
|
|
141
142
|
sync=pending_sync_payload,
|
|
142
143
|
)
|
|
@@ -145,10 +146,9 @@ def run_guard_connect_command(
|
|
|
145
146
|
browser_opened=browser_opened,
|
|
146
147
|
connect_url=browser_url,
|
|
147
148
|
sync_url=sync_url,
|
|
148
|
-
connected=
|
|
149
|
+
connected=False,
|
|
149
150
|
sync=pending_sync_payload,
|
|
150
151
|
sync_message=sync_message,
|
|
151
|
-
sync_available=False if sync_is_plan_limited else None,
|
|
152
152
|
)
|
|
153
153
|
sync_payload["runtime_session_synced_at"] = runtime_sync_summary["runtime_session_synced_at"]
|
|
154
154
|
sync_payload["runtime_session_id"] = runtime_sync_summary["runtime_session_id"]
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Builders for Guard Cloud v1 events emitted by the local edge runtime."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
from typing import cast
|
|
7
|
+
|
|
8
|
+
from .models import GuardReceipt
|
|
9
|
+
from .schemas.guard_event_v1 import GuardEventType, GuardEventV1
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def build_receipt_event(
|
|
13
|
+
receipt: GuardReceipt,
|
|
14
|
+
*,
|
|
15
|
+
device_id: str | None = None,
|
|
16
|
+
workspace_id: str | None = None,
|
|
17
|
+
) -> GuardEventV1:
|
|
18
|
+
payload: dict[str, object] = {
|
|
19
|
+
"receiptId": receipt.receipt_id,
|
|
20
|
+
"harness": receipt.harness,
|
|
21
|
+
"artifactId": receipt.artifact_id,
|
|
22
|
+
"artifactHash": receipt.artifact_hash,
|
|
23
|
+
"artifactName": receipt.artifact_name,
|
|
24
|
+
"sourceScope": receipt.source_scope,
|
|
25
|
+
"policyDecision": receipt.policy_decision,
|
|
26
|
+
"capabilitiesSummary": receipt.capabilities_summary,
|
|
27
|
+
"changedCapabilities": list(receipt.changed_capabilities),
|
|
28
|
+
"provenanceSummary": receipt.provenance_summary,
|
|
29
|
+
"userOverride": receipt.user_override,
|
|
30
|
+
}
|
|
31
|
+
event_id = f"guard-event-{_fingerprint('receipt.created', receipt.receipt_id)[:32]}"
|
|
32
|
+
return GuardEventV1(
|
|
33
|
+
event_id=event_id,
|
|
34
|
+
idempotency_key=f"receipt.created:{receipt.receipt_id}",
|
|
35
|
+
event_type="receipt.created",
|
|
36
|
+
source="edge",
|
|
37
|
+
occurred_at=receipt.timestamp,
|
|
38
|
+
workspace_id=workspace_id,
|
|
39
|
+
device_id=device_id,
|
|
40
|
+
payload=payload,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def build_approval_event(
|
|
45
|
+
*,
|
|
46
|
+
request_id: str,
|
|
47
|
+
event_type: str,
|
|
48
|
+
occurred_at: str,
|
|
49
|
+
payload: dict[str, object],
|
|
50
|
+
device_id: str | None = None,
|
|
51
|
+
workspace_id: str | None = None,
|
|
52
|
+
) -> GuardEventV1:
|
|
53
|
+
if event_type not in {"approval.created", "approval.resolved"}:
|
|
54
|
+
raise ValueError("Approval event type must be approval.created or approval.resolved")
|
|
55
|
+
return GuardEventV1(
|
|
56
|
+
event_id=f"guard-event-{_fingerprint(event_type, request_id, occurred_at)[:32]}",
|
|
57
|
+
idempotency_key=f"{event_type}:{request_id}:{occurred_at}",
|
|
58
|
+
event_type=cast(GuardEventType, event_type),
|
|
59
|
+
source="approval-center",
|
|
60
|
+
occurred_at=occurred_at,
|
|
61
|
+
workspace_id=workspace_id,
|
|
62
|
+
device_id=device_id,
|
|
63
|
+
payload=payload,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def build_policy_event(
|
|
68
|
+
*,
|
|
69
|
+
policy_key: str,
|
|
70
|
+
occurred_at: str,
|
|
71
|
+
payload: dict[str, object],
|
|
72
|
+
device_id: str | None = None,
|
|
73
|
+
workspace_id: str | None = None,
|
|
74
|
+
) -> GuardEventV1:
|
|
75
|
+
return GuardEventV1(
|
|
76
|
+
event_id=f"guard-event-{_fingerprint('policy.changed', policy_key, occurred_at)[:32]}",
|
|
77
|
+
idempotency_key=f"policy.changed:{policy_key}:{occurred_at}",
|
|
78
|
+
event_type="policy.changed",
|
|
79
|
+
source="policy",
|
|
80
|
+
occurred_at=occurred_at,
|
|
81
|
+
workspace_id=workspace_id,
|
|
82
|
+
device_id=device_id,
|
|
83
|
+
payload=payload,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _fingerprint(*parts: str) -> str:
|
|
88
|
+
return hashlib.sha256(":".join(parts).encode()).hexdigest()
|
{plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/runtime/__init__.py
RENAMED
|
@@ -4,6 +4,7 @@ from .runner import (
|
|
|
4
4
|
GuardSyncNotAvailableError,
|
|
5
5
|
GuardSyncNotConfiguredError,
|
|
6
6
|
guard_run,
|
|
7
|
+
sync_guard_events,
|
|
7
8
|
sync_receipts,
|
|
8
9
|
sync_runtime_session,
|
|
9
10
|
)
|
|
@@ -12,6 +13,7 @@ __all__ = [
|
|
|
12
13
|
"GuardSyncNotAvailableError",
|
|
13
14
|
"GuardSyncNotConfiguredError",
|
|
14
15
|
"guard_run",
|
|
16
|
+
"sync_guard_events",
|
|
15
17
|
"sync_receipts",
|
|
16
18
|
"sync_runtime_session",
|
|
17
19
|
]
|
{plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/runtime/runner.py
RENAMED
|
@@ -480,10 +480,69 @@ def sync_receipts(store: GuardStore) -> dict[str, object]:
|
|
|
480
480
|
"inventory": 0,
|
|
481
481
|
"inventory_tracked": len(inventory),
|
|
482
482
|
}
|
|
483
|
+
summary["guard_events_v1"] = sync_guard_events(store)
|
|
483
484
|
store.set_sync_payload("sync_summary", summary, now)
|
|
484
485
|
return summary
|
|
485
486
|
|
|
486
487
|
|
|
488
|
+
def sync_guard_events(store: GuardStore) -> dict[str, object]:
|
|
489
|
+
"""Push pending GuardEventV1 envelopes to Guard Cloud."""
|
|
490
|
+
|
|
491
|
+
credentials = store.get_sync_credentials()
|
|
492
|
+
if credentials is None:
|
|
493
|
+
raise GuardSyncNotConfiguredError("Guard is not logged in.")
|
|
494
|
+
sync_url = _guard_events_sync_url(str(credentials["sync_url"]))
|
|
495
|
+
total_events = 0
|
|
496
|
+
total_accepted = 0
|
|
497
|
+
synced_at = _now()
|
|
498
|
+
while True:
|
|
499
|
+
pending_events = store.list_guard_events_v1(uploaded=False, limit=200)
|
|
500
|
+
if not pending_events:
|
|
501
|
+
break
|
|
502
|
+
body = json.dumps({"events": [event["payload"] for event in pending_events]}).encode("utf-8")
|
|
503
|
+
request = urllib.request.Request(
|
|
504
|
+
sync_url,
|
|
505
|
+
data=body,
|
|
506
|
+
method="POST",
|
|
507
|
+
headers=_guard_sync_headers(str(credentials["token"])),
|
|
508
|
+
)
|
|
509
|
+
try:
|
|
510
|
+
payload = _urlopen_json_with_timeout_retry(
|
|
511
|
+
request=request,
|
|
512
|
+
timeout_seconds=_SYNC_HTTP_TIMEOUT_SECONDS,
|
|
513
|
+
retry_timeout_seconds=_SYNC_HTTP_RETRY_TIMEOUT_SECONDS,
|
|
514
|
+
)
|
|
515
|
+
except urllib.error.HTTPError as error:
|
|
516
|
+
if error.code == 404:
|
|
517
|
+
summary = {
|
|
518
|
+
"synced_at": synced_at,
|
|
519
|
+
"events": total_events,
|
|
520
|
+
"accepted": total_accepted,
|
|
521
|
+
"sync_skipped": True,
|
|
522
|
+
"sync_reason": "guard_events_endpoint_unavailable",
|
|
523
|
+
}
|
|
524
|
+
store.set_sync_payload("guard_events_v1_summary", summary, synced_at)
|
|
525
|
+
return summary
|
|
526
|
+
if error.code == 403:
|
|
527
|
+
is_plan, message = _check_plan_restriction_403(error)
|
|
528
|
+
if is_plan:
|
|
529
|
+
raise GuardSyncNotAvailableError(message) from error
|
|
530
|
+
raise RuntimeError(message) from error
|
|
531
|
+
raise RuntimeError(_sync_http_error_message(error)) from error
|
|
532
|
+
except OSError as error:
|
|
533
|
+
raise RuntimeError(_sync_url_error_message(error)) from error
|
|
534
|
+
completed_ids = _completed_guard_event_ids(payload)
|
|
535
|
+
synced_at = _sync_timestamp(payload)
|
|
536
|
+
uploaded = store.mark_guard_events_v1_uploaded(completed_ids, synced_at)
|
|
537
|
+
total_events += len(pending_events)
|
|
538
|
+
total_accepted += uploaded
|
|
539
|
+
if uploaded == 0 or len(pending_events) < 200:
|
|
540
|
+
break
|
|
541
|
+
summary = {"synced_at": synced_at, "events": total_events, "accepted": total_accepted}
|
|
542
|
+
store.set_sync_payload("guard_events_v1_summary", summary, synced_at)
|
|
543
|
+
return summary
|
|
544
|
+
|
|
545
|
+
|
|
487
546
|
def sync_runtime_session(
|
|
488
547
|
store: GuardStore,
|
|
489
548
|
*,
|
|
@@ -951,6 +1010,45 @@ def _normalized_runtime_sessions_sync_url(sync_url: str) -> str:
|
|
|
951
1010
|
)
|
|
952
1011
|
|
|
953
1012
|
|
|
1013
|
+
def _guard_events_sync_url(sync_url: str) -> str:
|
|
1014
|
+
parsed = urllib.parse.urlsplit(_normalized_receipts_sync_url(sync_url))
|
|
1015
|
+
if parsed.path.rstrip("/").endswith("/api/v1/guard/events"):
|
|
1016
|
+
return urllib.parse.urlunsplit((parsed.scheme, parsed.netloc, parsed.path.rstrip("/"), parsed.query, ""))
|
|
1017
|
+
path = parsed.path.rstrip("/")
|
|
1018
|
+
for suffix in (
|
|
1019
|
+
"/api/guard/receipts/sync",
|
|
1020
|
+
"/guard/receipts/sync",
|
|
1021
|
+
"/registry/api/v1/guard/receipts/sync",
|
|
1022
|
+
):
|
|
1023
|
+
if path.endswith(suffix):
|
|
1024
|
+
path = path[: -len(suffix)]
|
|
1025
|
+
break
|
|
1026
|
+
return urllib.parse.urlunsplit(
|
|
1027
|
+
(
|
|
1028
|
+
parsed.scheme,
|
|
1029
|
+
parsed.netloc,
|
|
1030
|
+
path.rstrip("/") + "/api/v1/guard/events",
|
|
1031
|
+
parsed.query,
|
|
1032
|
+
"",
|
|
1033
|
+
)
|
|
1034
|
+
)
|
|
1035
|
+
|
|
1036
|
+
|
|
1037
|
+
def _completed_guard_event_ids(payload: dict[str, object]) -> list[str]:
|
|
1038
|
+
statuses = payload.get("statuses")
|
|
1039
|
+
if not isinstance(statuses, list):
|
|
1040
|
+
return []
|
|
1041
|
+
completed: list[str] = []
|
|
1042
|
+
for item in statuses:
|
|
1043
|
+
if not isinstance(item, dict):
|
|
1044
|
+
continue
|
|
1045
|
+
status = str(item.get("status") or "")
|
|
1046
|
+
event_id = item.get("eventId")
|
|
1047
|
+
if status in {"accepted", "duplicate", "rejected"} and isinstance(event_id, str):
|
|
1048
|
+
completed.append(event_id)
|
|
1049
|
+
return completed
|
|
1050
|
+
|
|
1051
|
+
|
|
954
1052
|
def _cloud_sync_receipts_payload(store: GuardStore, receipts: list[dict[str, object]]) -> list[dict[str, object]]:
|
|
955
1053
|
device_id, device_name = _guard_device_metadata(store)
|
|
956
1054
|
return [_cloud_sync_receipt_payload(receipt, device_id=device_id, device_name=device_name) for receipt in receipts]
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Guard Cloud event schema shared by the edge runtime and v1 ingest API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Literal, cast
|
|
7
|
+
|
|
8
|
+
GuardEventSource = Literal["edge", "approval-center", "policy", "protect-api"]
|
|
9
|
+
GuardEventType = Literal["receipt.created", "approval.created", "approval.resolved", "policy.changed"]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True, slots=True)
|
|
13
|
+
class GuardEventV1:
|
|
14
|
+
"""Versioned event envelope for replay-safe Guard Cloud sync."""
|
|
15
|
+
|
|
16
|
+
event_id: str
|
|
17
|
+
idempotency_key: str
|
|
18
|
+
event_type: GuardEventType
|
|
19
|
+
source: GuardEventSource
|
|
20
|
+
occurred_at: str
|
|
21
|
+
workspace_id: str | None = None
|
|
22
|
+
device_id: str | None = None
|
|
23
|
+
payload: dict[str, object] = field(default_factory=dict)
|
|
24
|
+
schema_version: str = "guard.event.v1"
|
|
25
|
+
|
|
26
|
+
def to_dict(self) -> dict[str, object]:
|
|
27
|
+
return {
|
|
28
|
+
"schemaVersion": self.schema_version,
|
|
29
|
+
"eventId": self.event_id,
|
|
30
|
+
"idempotencyKey": self.idempotency_key,
|
|
31
|
+
"eventType": self.event_type,
|
|
32
|
+
"source": self.source,
|
|
33
|
+
"occurredAt": self.occurred_at,
|
|
34
|
+
"workspaceId": self.workspace_id,
|
|
35
|
+
"deviceId": self.device_id,
|
|
36
|
+
"payload": self.payload,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def from_dict(cls, payload: dict[str, object]) -> GuardEventV1:
|
|
41
|
+
schema_version = str(payload.get("schemaVersion") or "")
|
|
42
|
+
if schema_version != "guard.event.v1":
|
|
43
|
+
raise ValueError("Guard event schemaVersion must be guard.event.v1")
|
|
44
|
+
event_id = _required_string(payload, "eventId")
|
|
45
|
+
idempotency_key = _required_string(payload, "idempotencyKey")
|
|
46
|
+
event_type = _required_string(payload, "eventType")
|
|
47
|
+
source = _required_string(payload, "source")
|
|
48
|
+
occurred_at = _required_string(payload, "occurredAt")
|
|
49
|
+
event_payload = payload.get("payload")
|
|
50
|
+
if not isinstance(event_payload, dict):
|
|
51
|
+
raise ValueError("Guard event payload must be an object")
|
|
52
|
+
if event_type not in {"receipt.created", "approval.created", "approval.resolved", "policy.changed"}:
|
|
53
|
+
raise ValueError(f"Unsupported Guard event type: {event_type}")
|
|
54
|
+
if source not in {"edge", "approval-center", "policy", "protect-api"}:
|
|
55
|
+
raise ValueError(f"Unsupported Guard event source: {source}")
|
|
56
|
+
workspace_id = payload.get("workspaceId")
|
|
57
|
+
device_id = payload.get("deviceId")
|
|
58
|
+
return cls(
|
|
59
|
+
event_id=event_id,
|
|
60
|
+
idempotency_key=idempotency_key,
|
|
61
|
+
event_type=cast(GuardEventType, event_type),
|
|
62
|
+
source=cast(GuardEventSource, source),
|
|
63
|
+
occurred_at=occurred_at,
|
|
64
|
+
workspace_id=workspace_id if isinstance(workspace_id, str) else None,
|
|
65
|
+
device_id=device_id if isinstance(device_id, str) else None,
|
|
66
|
+
payload={str(key): value for key, value in event_payload.items()},
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _required_string(payload: dict[str, object], key: str) -> str:
|
|
71
|
+
value = payload.get(key)
|
|
72
|
+
if not isinstance(value, str) or not value.strip():
|
|
73
|
+
raise ValueError(f"Guard event {key} must be a non-empty string")
|
|
74
|
+
return value
|
|
@@ -17,7 +17,9 @@ from uuid import uuid4
|
|
|
17
17
|
|
|
18
18
|
from cryptography.fernet import Fernet, InvalidToken
|
|
19
19
|
|
|
20
|
+
from .edge_events import build_receipt_event
|
|
20
21
|
from .models import GuardApprovalRequest, GuardArtifact, GuardReceipt, GuardRuntimeState, PolicyDecision
|
|
22
|
+
from .schemas.guard_event_v1 import GuardEventV1
|
|
21
23
|
from .store_approvals import (
|
|
22
24
|
add_approval_request as persist_approval_request,
|
|
23
25
|
)
|
|
@@ -548,6 +550,20 @@ class GuardStore:
|
|
|
548
550
|
)
|
|
549
551
|
""",
|
|
550
552
|
"""
|
|
553
|
+
create table if not exists guard_cloud_events (
|
|
554
|
+
event_id text primary key,
|
|
555
|
+
idempotency_key text not null unique,
|
|
556
|
+
event_type text not null,
|
|
557
|
+
payload_json text not null,
|
|
558
|
+
occurred_at text not null,
|
|
559
|
+
uploaded_at text
|
|
560
|
+
)
|
|
561
|
+
""",
|
|
562
|
+
"""
|
|
563
|
+
create index if not exists idx_guard_cloud_events_sync
|
|
564
|
+
on guard_cloud_events (uploaded_at, occurred_at)
|
|
565
|
+
""",
|
|
566
|
+
"""
|
|
551
567
|
create table if not exists guard_runtime_state (
|
|
552
568
|
state_key text primary key,
|
|
553
569
|
session_id text not null,
|
|
@@ -1213,6 +1229,19 @@ class GuardStore:
|
|
|
1213
1229
|
receipt.timestamp,
|
|
1214
1230
|
),
|
|
1215
1231
|
)
|
|
1232
|
+
self._ensure_local_device(connection)
|
|
1233
|
+
row = connection.execute(
|
|
1234
|
+
"select installation_id from guard_devices where device_key = ?",
|
|
1235
|
+
(_DEVICE_ROW_KEY,),
|
|
1236
|
+
).fetchone()
|
|
1237
|
+
device_id = str(row["installation_id"]) if row is not None else None
|
|
1238
|
+
self._add_guard_event_v1(
|
|
1239
|
+
connection,
|
|
1240
|
+
build_receipt_event(
|
|
1241
|
+
receipt,
|
|
1242
|
+
device_id=device_id,
|
|
1243
|
+
),
|
|
1244
|
+
)
|
|
1216
1245
|
|
|
1217
1246
|
def list_receipts(self, limit: int = 50) -> list[dict[str, object]]:
|
|
1218
1247
|
with self._connect() as connection:
|
|
@@ -1698,6 +1727,72 @@ class GuardStore:
|
|
|
1698
1727
|
(state_key,),
|
|
1699
1728
|
)
|
|
1700
1729
|
|
|
1730
|
+
def add_guard_event_v1(self, event: GuardEventV1) -> None:
|
|
1731
|
+
with self._connect() as connection:
|
|
1732
|
+
self._add_guard_event_v1(connection, event)
|
|
1733
|
+
|
|
1734
|
+
@staticmethod
|
|
1735
|
+
def _add_guard_event_v1(connection: sqlite3.Connection, event: GuardEventV1) -> None:
|
|
1736
|
+
payload = event.to_dict()
|
|
1737
|
+
connection.execute(
|
|
1738
|
+
"""
|
|
1739
|
+
insert or ignore into guard_cloud_events (
|
|
1740
|
+
event_id, idempotency_key, event_type, payload_json, occurred_at, uploaded_at
|
|
1741
|
+
)
|
|
1742
|
+
values (?, ?, ?, ?, ?, null)
|
|
1743
|
+
""",
|
|
1744
|
+
(
|
|
1745
|
+
event.event_id,
|
|
1746
|
+
event.idempotency_key,
|
|
1747
|
+
event.event_type,
|
|
1748
|
+
json.dumps(payload, sort_keys=True),
|
|
1749
|
+
event.occurred_at,
|
|
1750
|
+
),
|
|
1751
|
+
)
|
|
1752
|
+
|
|
1753
|
+
def list_guard_events_v1(self, *, uploaded: bool | None = None, limit: int = 200) -> list[dict[str, object]]:
|
|
1754
|
+
query = """
|
|
1755
|
+
select event_id, idempotency_key, event_type, payload_json, occurred_at, uploaded_at
|
|
1756
|
+
from guard_cloud_events
|
|
1757
|
+
"""
|
|
1758
|
+
params: list[object] = []
|
|
1759
|
+
if uploaded is True:
|
|
1760
|
+
query += " where uploaded_at is not null"
|
|
1761
|
+
elif uploaded is False:
|
|
1762
|
+
query += " where uploaded_at is null"
|
|
1763
|
+
query += " order by occurred_at asc, event_id asc limit ?"
|
|
1764
|
+
params.append(limit)
|
|
1765
|
+
with self._connect() as connection:
|
|
1766
|
+
rows = connection.execute(query, tuple(params)).fetchall()
|
|
1767
|
+
events: list[dict[str, object]] = []
|
|
1768
|
+
for row in rows:
|
|
1769
|
+
payload = json.loads(str(row["payload_json"]))
|
|
1770
|
+
if not isinstance(payload, dict):
|
|
1771
|
+
payload = {}
|
|
1772
|
+
events.append(
|
|
1773
|
+
{
|
|
1774
|
+
"event_id": str(row["event_id"]),
|
|
1775
|
+
"idempotency_key": str(row["idempotency_key"]),
|
|
1776
|
+
"event_type": str(row["event_type"]),
|
|
1777
|
+
"occurred_at": str(row["occurred_at"]),
|
|
1778
|
+
"uploaded_at": row["uploaded_at"],
|
|
1779
|
+
"payload": payload,
|
|
1780
|
+
}
|
|
1781
|
+
)
|
|
1782
|
+
return events
|
|
1783
|
+
|
|
1784
|
+
def mark_guard_events_v1_uploaded(self, event_ids: list[str], uploaded_at: str) -> int:
|
|
1785
|
+
clean_ids = [event_id for event_id in event_ids if event_id.strip()]
|
|
1786
|
+
if not clean_ids:
|
|
1787
|
+
return 0
|
|
1788
|
+
placeholders = ", ".join("?" for _ in clean_ids)
|
|
1789
|
+
with self._connect() as connection:
|
|
1790
|
+
cursor = connection.execute(
|
|
1791
|
+
f"update guard_cloud_events set uploaded_at = ? where event_id in ({placeholders})",
|
|
1792
|
+
(uploaded_at, *clean_ids),
|
|
1793
|
+
)
|
|
1794
|
+
return int(cursor.rowcount)
|
|
1795
|
+
|
|
1701
1796
|
def add_event(self, event_name: str, payload: dict[str, object], now: str) -> None:
|
|
1702
1797
|
with self._connect() as connection:
|
|
1703
1798
|
connection.execute(
|
|
@@ -224,6 +224,8 @@ class _SyncRequestHandler(BaseHTTPRequestHandler):
|
|
|
224
224
|
response_code = 200
|
|
225
225
|
captured_headers: ClassVar[dict[str, str]] = {}
|
|
226
226
|
captured_body: ClassVar[dict[str, object] | None] = None
|
|
227
|
+
captured_bodies: ClassVar[list[dict[str, object]]] = []
|
|
228
|
+
captured_paths: ClassVar[list[str]] = []
|
|
227
229
|
raw_response_body: ClassVar[str | None] = None
|
|
228
230
|
response_payload: ClassVar[dict[str, object]] = {
|
|
229
231
|
"syncedAt": "2026-04-09T00:00:00Z",
|
|
@@ -235,6 +237,8 @@ class _SyncRequestHandler(BaseHTTPRequestHandler):
|
|
|
235
237
|
body = self.rfile.read(length).decode("utf-8") if length else "{}"
|
|
236
238
|
_SyncRequestHandler.captured_headers = {key.lower(): value for key, value in self.headers.items()}
|
|
237
239
|
_SyncRequestHandler.captured_body = json.loads(body)
|
|
240
|
+
_SyncRequestHandler.captured_bodies.append(_SyncRequestHandler.captured_body)
|
|
241
|
+
_SyncRequestHandler.captured_paths.append(self.path)
|
|
238
242
|
self.send_response(self.response_code)
|
|
239
243
|
self.send_header("Content-Type", "application/json")
|
|
240
244
|
self.end_headers()
|
|
@@ -5401,6 +5405,8 @@ url = http://127.0.0.1:8787/guard-canary
|
|
|
5401
5405
|
"syncedAt": "2026-04-09T00:00:00Z",
|
|
5402
5406
|
"receiptsStored": 1,
|
|
5403
5407
|
}
|
|
5408
|
+
_SyncRequestHandler.captured_bodies = []
|
|
5409
|
+
_SyncRequestHandler.captured_paths = []
|
|
5404
5410
|
|
|
5405
5411
|
server = HTTPServer(("127.0.0.1", 0), _SyncRequestHandler)
|
|
5406
5412
|
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
|
@@ -5464,10 +5470,16 @@ url = http://127.0.0.1:8787/guard-canary
|
|
|
5464
5470
|
assert status_output["cloud_state"] == "paired_active"
|
|
5465
5471
|
assert status_output["last_sync_at"] == "2026-04-09T00:00:00Z"
|
|
5466
5472
|
assert _SyncRequestHandler.captured_headers["authorization"] == "Bearer demo-token"
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
|
|
5473
|
+
receipt_body = next(
|
|
5474
|
+
body
|
|
5475
|
+
for body in _SyncRequestHandler.captured_bodies
|
|
5476
|
+
if isinstance(body.get("receipts"), list) and len(body["receipts"]) >= 1
|
|
5477
|
+
)
|
|
5478
|
+
event_body = next(body for body in _SyncRequestHandler.captured_bodies if "events" in body)
|
|
5479
|
+
assert len(receipt_body["receipts"]) >= 1
|
|
5480
|
+
assert "inventory" not in receipt_body
|
|
5481
|
+
assert len(event_body["events"]) >= 1
|
|
5482
|
+
first_receipt = receipt_body["receipts"][0]
|
|
5471
5483
|
assert "artifactId" in first_receipt
|
|
5472
5484
|
assert "artifact_id" not in first_receipt
|
|
5473
5485
|
assert "receiptId" in first_receipt
|
|
@@ -5805,10 +5817,10 @@ url = http://127.0.0.1:8787/guard-canary
|
|
|
5805
5817
|
finally:
|
|
5806
5818
|
daemon.stop()
|
|
5807
5819
|
|
|
5808
|
-
assert connect_rc ==
|
|
5809
|
-
assert connect_output["connected"] is
|
|
5810
|
-
assert connect_output["status"] == "
|
|
5811
|
-
assert connect_output["milestone"] == "
|
|
5820
|
+
assert connect_rc == 1
|
|
5821
|
+
assert connect_output["connected"] is False
|
|
5822
|
+
assert connect_output["status"] == "retry_required"
|
|
5823
|
+
assert connect_output["milestone"] == "first_sync_failed"
|
|
5812
5824
|
assert connect_output["reason"] == "sync_unreachable"
|
|
5813
5825
|
assert connect_output["sync_message"] == "sync_unreachable"
|
|
5814
5826
|
|
|
@@ -5898,10 +5910,10 @@ url = http://127.0.0.1:8787/guard-canary
|
|
|
5898
5910
|
"receiptsStored": 1,
|
|
5899
5911
|
}
|
|
5900
5912
|
|
|
5901
|
-
assert connect_rc ==
|
|
5902
|
-
assert connect_output["connected"] is
|
|
5903
|
-
assert connect_output["status"] == "
|
|
5904
|
-
assert connect_output["milestone"] == "
|
|
5913
|
+
assert connect_rc == 1
|
|
5914
|
+
assert connect_output["connected"] is False
|
|
5915
|
+
assert connect_output["status"] == "retry_required"
|
|
5916
|
+
assert connect_output["milestone"] == "first_sync_failed"
|
|
5905
5917
|
assert connect_output["reason"] == "Guard sync requires a Pro or Team plan."
|
|
5906
5918
|
assert connect_output["sync_message"] == "Guard sync requires a Pro or Team plan."
|
|
5907
5919
|
|
|
@@ -6239,9 +6251,9 @@ url = http://127.0.0.1:8787/guard-canary
|
|
|
6239
6251
|
wait_timeout_seconds=1,
|
|
6240
6252
|
)
|
|
6241
6253
|
|
|
6242
|
-
assert payload["connected"] is
|
|
6243
|
-
assert payload["status"] == "
|
|
6244
|
-
assert payload["milestone"] == "
|
|
6254
|
+
assert payload["connected"] is False
|
|
6255
|
+
assert payload["status"] == "retry_required"
|
|
6256
|
+
assert payload["milestone"] == "first_sync_failed"
|
|
6245
6257
|
assert payload["sync_message"] == "<urlopen error offline>"
|
|
6246
6258
|
|
|
6247
6259
|
def test_guard_connect_persists_success_when_daemon_result_write_fails(self, tmp_path, monkeypatch):
|
|
@@ -6413,9 +6425,9 @@ url = http://127.0.0.1:8787/guard-canary
|
|
|
6413
6425
|
wait_timeout_seconds=1,
|
|
6414
6426
|
)
|
|
6415
6427
|
|
|
6416
|
-
assert payload["connected"] is
|
|
6417
|
-
assert payload["status"] == "
|
|
6418
|
-
assert payload["milestone"] == "
|
|
6428
|
+
assert payload["connected"] is False
|
|
6429
|
+
assert payload["status"] == "retry_required"
|
|
6430
|
+
assert payload["milestone"] == "first_sync_failed"
|
|
6419
6431
|
assert payload["sync_message"] == "Guard Cloud sync requires a paid Guard plan"
|
|
6420
6432
|
|
|
6421
6433
|
def test_guard_connect_reports_guard_plan_required_without_failing_pairing(self, tmp_path, monkeypatch):
|
|
@@ -6496,9 +6508,9 @@ url = http://127.0.0.1:8787/guard-canary
|
|
|
6496
6508
|
wait_timeout_seconds=1,
|
|
6497
6509
|
)
|
|
6498
6510
|
|
|
6499
|
-
assert payload["connected"] is
|
|
6500
|
-
assert payload["status"] == "
|
|
6501
|
-
assert payload["milestone"] == "
|
|
6511
|
+
assert payload["connected"] is False
|
|
6512
|
+
assert payload["status"] == "retry_required"
|
|
6513
|
+
assert payload["milestone"] == "first_sync_failed"
|
|
6502
6514
|
assert payload["sync_message"] == "Guard plan required"
|
|
6503
6515
|
|
|
6504
6516
|
def test_guard_sync_persists_advisories_from_endpoint(self, tmp_path, capsys):
|
|
@@ -156,9 +156,9 @@ def test_guard_connect_preserves_pairing_when_first_sync_fails(
|
|
|
156
156
|
finally:
|
|
157
157
|
daemon.stop()
|
|
158
158
|
|
|
159
|
-
assert payload["connected"] is
|
|
160
|
-
assert payload["status"] == "
|
|
161
|
-
assert payload["milestone"] == "
|
|
159
|
+
assert payload["connected"] is False
|
|
160
|
+
assert payload["status"] == "retry_required"
|
|
161
|
+
assert payload["milestone"] == "first_sync_failed"
|
|
162
162
|
assert payload["reason"] == "sync_unreachable"
|
|
163
163
|
assert payload["sync_message"] == "sync_unreachable"
|
|
164
164
|
assert payload["request_id"].startswith("connect-")
|
|
@@ -235,9 +235,9 @@ def test_guard_connect_prefers_paid_plan_sync_note_over_runtime_sync_timeout(
|
|
|
235
235
|
finally:
|
|
236
236
|
daemon.stop()
|
|
237
237
|
|
|
238
|
-
assert payload["connected"] is
|
|
239
|
-
assert payload["status"] == "
|
|
240
|
-
assert payload["milestone"] == "
|
|
238
|
+
assert payload["connected"] is False
|
|
239
|
+
assert payload["status"] == "retry_required"
|
|
240
|
+
assert payload["milestone"] == "first_sync_failed"
|
|
241
241
|
assert payload["reason"] == "Guard Cloud sync requires a paid Guard plan"
|
|
242
242
|
assert payload["sync_message"] == "Guard Cloud sync requires a paid Guard plan"
|
|
243
243
|
|
|
@@ -311,9 +311,9 @@ def test_guard_connect_preserves_http_402_payment_required_sync_note(
|
|
|
311
311
|
finally:
|
|
312
312
|
daemon.stop()
|
|
313
313
|
|
|
314
|
-
assert payload["connected"] is
|
|
315
|
-
assert payload["status"] == "
|
|
316
|
-
assert payload["milestone"] == "
|
|
314
|
+
assert payload["connected"] is False
|
|
315
|
+
assert payload["status"] == "retry_required"
|
|
316
|
+
assert payload["milestone"] == "first_sync_failed"
|
|
317
317
|
assert payload["reason"] == "HTTP Error 402: Payment Required"
|
|
318
318
|
assert payload["sync_message"] == "HTTP Error 402: Payment Required"
|
|
319
319
|
|