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.
Files changed (320) hide show
  1. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/PKG-INFO +1 -1
  2. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/pyproject.toml +1 -1
  3. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/pyproject.toml.bak +1 -1
  4. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/connect_flow.py +10 -10
  5. plugin_scanner-2.0.65/src/codex_plugin_scanner/guard/edge_events.py +88 -0
  6. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/runtime/__init__.py +2 -0
  7. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/runtime/runner.py +98 -0
  8. plugin_scanner-2.0.65/src/codex_plugin_scanner/guard/schemas/guard_event_v1.py +74 -0
  9. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/store.py +95 -0
  10. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/version.py +1 -1
  11. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_cli.py +33 -21
  12. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_connect_flow.py +9 -9
  13. plugin_scanner-2.0.65/tests/test_guard_event_schema_v1.py +260 -0
  14. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.clusterfuzzlite/Dockerfile +0 -0
  15. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.clusterfuzzlite/build.sh +0 -0
  16. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.clusterfuzzlite/project.yaml +0 -0
  17. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.clusterfuzzlite/requirements-atheris.txt +0 -0
  18. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.dockerignore +0 -0
  19. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.github/CODEOWNERS +0 -0
  20. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.github/ISSUE_TEMPLATE/bug-report.yml +0 -0
  21. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  22. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
  23. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.github/dependabot.yml +0 -0
  24. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.github/workflows/ci.yml +0 -0
  25. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.github/workflows/codeql.yml +0 -0
  26. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.github/workflows/dependabot-uv-lock.yml +0 -0
  27. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.github/workflows/fuzz.yml +0 -0
  28. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.github/workflows/harness-smoke.yml +0 -0
  29. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.github/workflows/publish.yml +0 -0
  30. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.github/workflows/scorecard.yml +0 -0
  31. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.gitignore +0 -0
  32. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/.pre-commit-hooks.yaml +0 -0
  33. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/CONTRIBUTING.md +0 -0
  34. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/Dockerfile +0 -0
  35. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/LICENSE +0 -0
  36. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/README.md +0 -0
  37. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/SECURITY.md +0 -0
  38. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/index.html +0 -0
  39. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/package.json +0 -0
  40. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/pnpm-lock.yaml +0 -0
  41. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/public/brand/Logo_Whole.png +0 -0
  42. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/src/app.tsx +0 -0
  43. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/src/approval-center-layout.tsx +0 -0
  44. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/src/approval-center-primitives.tsx +0 -0
  45. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/src/approval-center-utils.ts +0 -0
  46. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/src/fleet-workspace.tsx +0 -0
  47. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/src/guard-api.ts +0 -0
  48. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/src/guard-demo.ts +0 -0
  49. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/src/guard-types.ts +0 -0
  50. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/src/main.tsx +0 -0
  51. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/src/receipts-workspace.tsx +0 -0
  52. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/src/runtime-overview.tsx +0 -0
  53. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/src/styles.css +0 -0
  54. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/src/vite-env.d.ts +0 -0
  55. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/tsconfig.json +0 -0
  56. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/dashboard/vite.config.ts +0 -0
  57. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/docker-requirements.txt +0 -0
  58. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/docs/guard/approval-audit.md +0 -0
  59. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/docs/guard/architecture.md +0 -0
  60. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/docs/guard/get-started.md +0 -0
  61. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/docs/guard/harness-support.md +0 -0
  62. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/docs/guard/local-vs-cloud.md +0 -0
  63. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/docs/guard/testing-matrix.md +0 -0
  64. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/docs/trust/mcp-trust-draft.md +0 -0
  65. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/docs/trust/plugin-trust-draft.md +0 -0
  66. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/docs/trust/skill-trust-local.md +0 -0
  67. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/fuzzers/manifest_fuzzer.py +0 -0
  68. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/requirements.txt +0 -0
  69. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/schemas/plugin-quality.v1.json +0 -0
  70. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/schemas/scan-result.v1.json +0 -0
  71. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/schemas/verify-result.v1.json +0 -0
  72. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/__init__.py +0 -0
  73. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/action_runner.py +0 -0
  74. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/argparse_utils.py +0 -0
  75. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/__init__.py +0 -0
  76. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/best_practices.py +0 -0
  77. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/claude.py +0 -0
  78. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/code_quality.py +0 -0
  79. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/ecosystem_common.py +0 -0
  80. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/gemini.py +0 -0
  81. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/manifest.py +0 -0
  82. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/manifest_support.py +0 -0
  83. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/marketplace.py +0 -0
  84. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/mcp_security.py +0 -0
  85. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/opencode.py +0 -0
  86. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/operational_security.py +0 -0
  87. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/security.py +0 -0
  88. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/skill_security.py +0 -0
  89. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/cli.py +0 -0
  90. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/cli_ui.py +0 -0
  91. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/config.py +0 -0
  92. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/ecosystems/__init__.py +0 -0
  93. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/ecosystems/base.py +0 -0
  94. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/ecosystems/claude.py +0 -0
  95. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/ecosystems/codex.py +0 -0
  96. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/ecosystems/detect.py +0 -0
  97. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/ecosystems/gemini.py +0 -0
  98. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/ecosystems/opencode.py +0 -0
  99. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/ecosystems/registry.py +0 -0
  100. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/ecosystems/types.py +0 -0
  101. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/github_reporting.py +0 -0
  102. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/__init__.py +0 -0
  103. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/__init__.py +0 -0
  104. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/antigravity.py +0 -0
  105. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/base.py +0 -0
  106. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/claude_code.py +0 -0
  107. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/codex.py +0 -0
  108. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/copilot.py +0 -0
  109. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/cursor.py +0 -0
  110. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/gemini.py +0 -0
  111. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/hermes.py +0 -0
  112. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/mcp_servers.py +0 -0
  113. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/opencode.py +0 -0
  114. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/opencode_artifacts.py +0 -0
  115. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/approvals.py +0 -0
  116. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/bridge/__init__.py +0 -0
  117. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/capabilities.py +0 -0
  118. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/__init__.py +0 -0
  119. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/approval_commands.py +0 -0
  120. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/bootstrap.py +0 -0
  121. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/commands.py +0 -0
  122. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/install_commands.py +0 -0
  123. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/product.py +0 -0
  124. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/prompt.py +0 -0
  125. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/render.py +0 -0
  126. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/update_commands.py +0 -0
  127. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/codex_config.py +0 -0
  128. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/config.py +0 -0
  129. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/consumer/__init__.py +0 -0
  130. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/consumer/service.py +0 -0
  131. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/daemon/__init__.py +0 -0
  132. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/daemon/client.py +0 -0
  133. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/daemon/manager.py +0 -0
  134. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/daemon/server.py +0 -0
  135. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/daemon/static/assets/guard-dashboard.js +0 -0
  136. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/daemon/static/assets/index.css +0 -0
  137. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Whole.png +0 -0
  138. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/daemon/static/index.html +0 -0
  139. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/incident.py +0 -0
  140. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/launcher.py +0 -0
  141. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/mcp_tool_calls.py +0 -0
  142. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/models.py +0 -0
  143. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/policy/__init__.py +0 -0
  144. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/policy/engine.py +0 -0
  145. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/protect.py +0 -0
  146. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/proxy/__init__.py +0 -0
  147. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/proxy/remote.py +0 -0
  148. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/proxy/runtime_mcp.py +0 -0
  149. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/proxy/stdio.py +0 -0
  150. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/receipts/__init__.py +0 -0
  151. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/receipts/manager.py +0 -0
  152. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/risk.py +0 -0
  153. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/runtime/secret_file_requests.py +0 -0
  154. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/runtime/surface_server.py +0 -0
  155. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/schemas/__init__.py +0 -0
  156. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/schemas/consumer_mode.py +0 -0
  157. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/schemas/surface_server.py +0 -0
  158. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/shims.py +0 -0
  159. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/store_approvals.py +0 -0
  160. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/store_connect.py +0 -0
  161. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/types.py +0 -0
  162. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/integrations/__init__.py +0 -0
  163. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/integrations/cisco_mcp_scanner.py +0 -0
  164. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/integrations/cisco_skill_scanner.py +0 -0
  165. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/lint_fixes.py +0 -0
  166. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/marketplace_support.py +0 -0
  167. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/models.py +0 -0
  168. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/path_support.py +0 -0
  169. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/policy.py +0 -0
  170. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/quality_artifact.py +0 -0
  171. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/repo_detect.py +0 -0
  172. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/reporting.py +0 -0
  173. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/rules/__init__.py +0 -0
  174. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/rules/registry.py +0 -0
  175. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/rules/specs.py +0 -0
  176. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/scanner.py +0 -0
  177. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/submission.py +0 -0
  178. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/suppressions.py +0 -0
  179. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/trust_domain_scoring.py +0 -0
  180. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/trust_helpers.py +0 -0
  181. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/trust_mcp_scoring.py +0 -0
  182. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/trust_models.py +0 -0
  183. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/trust_plugin_scoring.py +0 -0
  184. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/trust_scoring.py +0 -0
  185. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/trust_skill_scoring.py +0 -0
  186. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/trust_specs.py +0 -0
  187. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/verification.py +0 -0
  188. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/__init__.py +0 -0
  189. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/conftest.py +0 -0
  190. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/__init__.py +0 -0
  191. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/bad-plugin/.codex-plugin/plugin.json +0 -0
  192. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/bad-plugin/.mcp.json +0 -0
  193. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/bad-plugin/secrets.js +0 -0
  194. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/claude-plugin-good/.claude-plugin/plugin.json +0 -0
  195. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/claude-plugin-good/LICENSE +0 -0
  196. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/claude-plugin-good/README.md +0 -0
  197. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/claude-plugin-good/SECURITY.md +0 -0
  198. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/claude-plugin-good/hooks/hooks.json +0 -0
  199. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/claude-plugin-good/skills/example/SKILL.md +0 -0
  200. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/code-quality-bad/evil.js +0 -0
  201. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/code-quality-bad/inject.js +0 -0
  202. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/gemini-extension-good/GEMINI.md +0 -0
  203. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/gemini-extension-good/LICENSE +0 -0
  204. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/gemini-extension-good/README.md +0 -0
  205. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/gemini-extension-good/SECURITY.md +0 -0
  206. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/gemini-extension-good/commands/hello.toml +0 -0
  207. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/gemini-extension-good/gemini-extension.json +0 -0
  208. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/good-plugin/.codex-plugin/plugin.json +0 -0
  209. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/good-plugin/.codexignore +0 -0
  210. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/good-plugin/LICENSE +0 -0
  211. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/good-plugin/README.md +0 -0
  212. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/good-plugin/SECURITY.md +0 -0
  213. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/good-plugin/assets/icon.svg +0 -0
  214. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/good-plugin/assets/logo.svg +0 -0
  215. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/good-plugin/assets/screenshot.svg +0 -0
  216. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/good-plugin/skills/example/SKILL.md +0 -0
  217. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/guard-codex-malicious-mcp/.codex/config.toml +0 -0
  218. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/hermes-plugin-evil/config.yaml +0 -0
  219. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/hermes-plugin-evil/mcp_servers.json +0 -0
  220. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/hermes-plugin-evil/skills/security/malicious/SKILL.md +0 -0
  221. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/SKILL.md +0 -0
  222. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/references/api-setup.md +0 -0
  223. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/scripts/deploy.sh +0 -0
  224. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/hermes-plugin-evil/skills/utils/benign/SKILL.md +0 -0
  225. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/malformed-json/.codex-plugin/plugin.json +0 -0
  226. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/malicious-skill-plugin/.codex-plugin/plugin.json +0 -0
  227. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/malicious-skill-plugin/.codexignore +0 -0
  228. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/malicious-skill-plugin/LICENSE +0 -0
  229. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/malicious-skill-plugin/README.md +0 -0
  230. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/malicious-skill-plugin/SECURITY.md +0 -0
  231. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/malicious-skill-plugin/skills/leaky-skill/SKILL.md +0 -0
  232. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/mcp-canary-server.py +0 -0
  233. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/minimal-plugin/.codex-plugin/plugin.json +0 -0
  234. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/missing-fields/.codex-plugin/plugin.json +0 -0
  235. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/mit-license/LICENSE +0 -0
  236. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-ecosystem-repo/codex-plugin/.codex-plugin/plugin.json +0 -0
  237. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-ecosystem-repo/codex-plugin/LICENSE +0 -0
  238. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-ecosystem-repo/codex-plugin/README.md +0 -0
  239. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-ecosystem-repo/codex-plugin/SECURITY.md +0 -0
  240. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-ecosystem-repo/gemini-ext/README.md +0 -0
  241. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-ecosystem-repo/gemini-ext/gemini-extension.json +0 -0
  242. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-plugin-repo/.agents/plugins/marketplace.json +0 -0
  243. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codex-plugin/plugin.json +0 -0
  244. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codexignore +0 -0
  245. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/LICENSE +0 -0
  246. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/README.md +0 -0
  247. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/SECURITY.md +0 -0
  248. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/skills/example/SKILL.md +0 -0
  249. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/.codex-plugin/plugin.json +0 -0
  250. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/skills/example/SKILL.md +0 -0
  251. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/no-version/.codex-plugin/plugin.json +0 -0
  252. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/opencode-good/.opencode/commands/hello.md +0 -0
  253. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/opencode-good/.opencode/plugins/example.ts +0 -0
  254. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/opencode-good/LICENSE +0 -0
  255. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/opencode-good/README.md +0 -0
  256. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/opencode-good/SECURITY.md +0 -0
  257. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/opencode-good/opencode.jsonc +0 -0
  258. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/skills-missing-dir/.codex-plugin/plugin.json +0 -0
  259. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/skills-no-frontmatter/.codex-plugin/plugin.json +0 -0
  260. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/skills-no-frontmatter/skills/bad-skill/SKILL.md +0 -0
  261. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/with-marketplace/.codex-plugin/plugin.json +0 -0
  262. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/with-marketplace/marketplace-broken.json +0 -0
  263. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/fixtures/with-marketplace/marketplace.json +0 -0
  264. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test-trust-scoring.py +0 -0
  265. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test-trust-specs.py +0 -0
  266. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_action_runner.py +0 -0
  267. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_best_practices.py +0 -0
  268. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_cisco_install_surfaces.py +0 -0
  269. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_cli.py +0 -0
  270. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_code_quality.py +0 -0
  271. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_config.py +0 -0
  272. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_coverage_remaining.py +0 -0
  273. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_ecosystems.py +0 -0
  274. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_edge_cases.py +0 -0
  275. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_final_coverage.py +0 -0
  276. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_approvals.py +0 -0
  277. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_bootstrap.py +0 -0
  278. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_capabilities.py +0 -0
  279. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_claude_adapter.py +0 -0
  280. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_codex_e2e.py +0 -0
  281. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_codex_install.py +0 -0
  282. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_codex_proxy.py +0 -0
  283. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_config_paths.py +0 -0
  284. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_consumer_mode.py +0 -0
  285. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_copilot_adapter.py +0 -0
  286. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_copilot_proxy.py +0 -0
  287. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_daemon_manager.py +0 -0
  288. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_events.py +0 -0
  289. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_launch_env.py +0 -0
  290. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_opencode_proxy.py +0 -0
  291. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_product_flow.py +0 -0
  292. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_protect.py +0 -0
  293. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_render.py +0 -0
  294. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_risk.py +0 -0
  295. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_runtime.py +0 -0
  296. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_store_migrations.py +0 -0
  297. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_surface_server.py +0 -0
  298. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_guard_verdicts.py +0 -0
  299. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_hermes_adapter.py +0 -0
  300. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_integration.py +0 -0
  301. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_lint_fixes.py +0 -0
  302. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_live_cisco_smoke.py +0 -0
  303. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_manifest.py +0 -0
  304. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_marketplace.py +0 -0
  305. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_mcp_security.py +0 -0
  306. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_operational_security.py +0 -0
  307. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_policy.py +0 -0
  308. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_quality_artifact.py +0 -0
  309. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_rule_registry.py +0 -0
  310. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_scanner.py +0 -0
  311. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_schema_contracts.py +0 -0
  312. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_security.py +0 -0
  313. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_security_ops.py +0 -0
  314. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_skill_security.py +0 -0
  315. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_submission.py +0 -0
  316. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_trust_scoring.py +0 -0
  317. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_trust_specs.py +0 -0
  318. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_verification.py +0 -0
  319. {plugin_scanner-2.0.64 → plugin_scanner-2.0.65}/tests/test_versioning.py +0 -0
  320. {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.64
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.64"
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.64"
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"
@@ -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="connected",
115
- milestone="sync_not_available",
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=True,
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="connected",
139
- milestone="first_sync_pending",
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=True,
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()
@@ -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
  ]
@@ -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(
@@ -1,3 +1,3 @@
1
1
  """Single source of truth for tool version."""
2
2
 
3
- __version__ = "2.0.64"
3
+ __version__ = "2.0.65"
@@ -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
- assert _SyncRequestHandler.captured_body is not None
5468
- assert len(_SyncRequestHandler.captured_body["receipts"]) >= 1
5469
- assert "inventory" not in _SyncRequestHandler.captured_body
5470
- first_receipt = _SyncRequestHandler.captured_body["receipts"][0]
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 == 0
5809
- assert connect_output["connected"] is True
5810
- assert connect_output["status"] == "connected"
5811
- assert connect_output["milestone"] == "first_sync_pending"
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 == 0
5902
- assert connect_output["connected"] is True
5903
- assert connect_output["status"] == "connected"
5904
- assert connect_output["milestone"] == "sync_not_available"
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 True
6243
- assert payload["status"] == "connected"
6244
- assert payload["milestone"] == "first_sync_pending"
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 True
6417
- assert payload["status"] == "connected"
6418
- assert payload["milestone"] == "first_sync_pending"
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 True
6500
- assert payload["status"] == "connected"
6501
- assert payload["milestone"] == "first_sync_pending"
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 True
160
- assert payload["status"] == "connected"
161
- assert payload["milestone"] == "first_sync_pending"
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 True
239
- assert payload["status"] == "connected"
240
- assert payload["milestone"] == "first_sync_pending"
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 True
315
- assert payload["status"] == "connected"
316
- assert payload["milestone"] == "first_sync_pending"
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